Compare commits
5 Commits
ab9f075d61
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ec6b59b4fa | |||
| 0bdaf54eed | |||
| e47060f54f | |||
| 614c0147e5 | |||
| cd0373be3c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/back-end/node_modules
|
/back-end/node_modules
|
||||||
/.tmp-upload-probe
|
/.tmp-upload-probe
|
||||||
|
/.tmp-openapi-validate/node_modules
|
||||||
|
|||||||
268
.tmp-openapi-validate/add-openapi-examples.js
Normal file
268
.tmp-openapi-validate/add-openapi-examples.js
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const YAML = require('yaml');
|
||||||
|
|
||||||
|
const repoRoot = process.cwd();
|
||||||
|
const rootFile = path.join(repoRoot, 'pocket-base', 'spec', 'openapi-wx.yaml');
|
||||||
|
const splitDir = path.join(repoRoot, 'pocket-base', 'spec', 'openapi-wx');
|
||||||
|
|
||||||
|
const filePaths = [
|
||||||
|
rootFile,
|
||||||
|
...fs
|
||||||
|
.readdirSync(splitDir)
|
||||||
|
.filter((name) => name.endsWith('.yaml'))
|
||||||
|
.map((name) => path.join(splitDir, name)),
|
||||||
|
];
|
||||||
|
|
||||||
|
const docs = new Map(
|
||||||
|
filePaths.map((filePath) => [filePath, YAML.parse(fs.readFileSync(filePath, 'utf8'))]),
|
||||||
|
);
|
||||||
|
|
||||||
|
function decodePointerSegment(segment) {
|
||||||
|
return segment.replace(/~1/g, '/').replace(/~0/g, '~');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getByPointer(documentData, pointer) {
|
||||||
|
if (!pointer || pointer === '#' || pointer === '') {
|
||||||
|
return documentData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const segments = pointer
|
||||||
|
.replace(/^#/, '')
|
||||||
|
.split('/')
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(decodePointerSegment);
|
||||||
|
|
||||||
|
let current = documentData;
|
||||||
|
for (const segment of segments) {
|
||||||
|
if (current == null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
current = current[segment];
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveRef(ref, currentFile) {
|
||||||
|
const [filePart, hashPart = ''] = ref.split('#');
|
||||||
|
const targetFile = filePart
|
||||||
|
? path.resolve(path.dirname(currentFile), filePart)
|
||||||
|
: currentFile;
|
||||||
|
const targetDoc = docs.get(targetFile);
|
||||||
|
if (!targetDoc) {
|
||||||
|
return { targetFile, targetKey: `${targetFile}#${hashPart}`, schema: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
const pointer = hashPart ? `#${hashPart}` : '#';
|
||||||
|
return {
|
||||||
|
targetFile,
|
||||||
|
targetKey: `${targetFile}${pointer}`,
|
||||||
|
schema: getByPointer(targetDoc, pointer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickType(typeValue) {
|
||||||
|
if (Array.isArray(typeValue)) {
|
||||||
|
return typeValue.find((item) => item !== 'null') || typeValue[0];
|
||||||
|
}
|
||||||
|
return typeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanLabelText(text, fallback) {
|
||||||
|
if (!text || typeof text !== 'string') {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = text
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((line) => line.replace(/^[\-\d\.\)\s]+/, '').trim())
|
||||||
|
.find((line) => line && !/^(可选|必填|说明|注意)[。::]?$/u.test(line));
|
||||||
|
|
||||||
|
if (!normalized) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized.replace(/[`"'<>]/g, '').trim() || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function placeholderKey(key) {
|
||||||
|
return typeof key === 'string' && key.includes('|');
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepMerge(baseValue, nextValue) {
|
||||||
|
if (
|
||||||
|
baseValue &&
|
||||||
|
nextValue &&
|
||||||
|
typeof baseValue === 'object' &&
|
||||||
|
typeof nextValue === 'object' &&
|
||||||
|
!Array.isArray(baseValue) &&
|
||||||
|
!Array.isArray(nextValue)
|
||||||
|
) {
|
||||||
|
const merged = { ...baseValue };
|
||||||
|
const nextKeys = Object.keys(nextValue);
|
||||||
|
const hasConcreteNextKeys = nextKeys.some((key) => !placeholderKey(key));
|
||||||
|
|
||||||
|
if (hasConcreteNextKeys) {
|
||||||
|
for (const key of Object.keys(merged)) {
|
||||||
|
if (placeholderKey(key)) {
|
||||||
|
delete merged[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(nextValue)) {
|
||||||
|
if (key in merged) {
|
||||||
|
merged[key] = deepMerge(merged[key], value);
|
||||||
|
} else {
|
||||||
|
merged[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
return nextValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLabel(schemaLike, fallback) {
|
||||||
|
return cleanLabelText(schemaLike?.description || schemaLike?.title, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fallbackScalar(label, fieldType) {
|
||||||
|
return `${label}|${fieldType || 'string'}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildExample(schemaLike, currentFile, fieldName = 'field', seenRefs = new Set()) {
|
||||||
|
if (schemaLike == null) {
|
||||||
|
return fallbackScalar(fieldName, 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof schemaLike !== 'object' || Array.isArray(schemaLike)) {
|
||||||
|
return fallbackScalar(fieldName, 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schemaLike.$ref) {
|
||||||
|
const { targetFile, targetKey, schema } = resolveRef(schemaLike.$ref, currentFile);
|
||||||
|
if (!schema) {
|
||||||
|
return fallbackScalar(fieldName, 'string');
|
||||||
|
}
|
||||||
|
if (seenRefs.has(targetKey)) {
|
||||||
|
return fallbackScalar(fieldName, 'object');
|
||||||
|
}
|
||||||
|
const nextSeen = new Set(seenRefs);
|
||||||
|
nextSeen.add(targetKey);
|
||||||
|
return buildExample(schema, targetFile, fieldName, nextSeen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schemaLike.allOf) {
|
||||||
|
return schemaLike.allOf.reduce((accumulator, item) => {
|
||||||
|
const nextValue = buildExample(item, currentFile, fieldName, seenRefs);
|
||||||
|
return deepMerge(accumulator, nextValue);
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schemaLike.oneOf && schemaLike.oneOf.length > 0) {
|
||||||
|
return buildExample(schemaLike.oneOf[0], currentFile, fieldName, seenRefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schemaLike.anyOf && schemaLike.anyOf.length > 0) {
|
||||||
|
return buildExample(schemaLike.anyOf[0], currentFile, fieldName, seenRefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldType =
|
||||||
|
pickType(schemaLike.type) ||
|
||||||
|
(schemaLike.properties || schemaLike.additionalProperties ? 'object' : undefined) ||
|
||||||
|
(schemaLike.items ? 'array' : undefined) ||
|
||||||
|
'string';
|
||||||
|
const fieldLabel = buildLabel(schemaLike, fieldName);
|
||||||
|
|
||||||
|
if (fieldType === 'object') {
|
||||||
|
const exampleObject = {};
|
||||||
|
|
||||||
|
if (schemaLike.properties && typeof schemaLike.properties === 'object') {
|
||||||
|
for (const [propertyName, propertySchema] of Object.entries(schemaLike.properties)) {
|
||||||
|
exampleObject[propertyName] = buildExample(
|
||||||
|
propertySchema,
|
||||||
|
currentFile,
|
||||||
|
propertyName,
|
||||||
|
seenRefs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(exampleObject).length === 0 && schemaLike.additionalProperties) {
|
||||||
|
exampleObject[`${fieldLabel}字段|string`] = `${fieldLabel}值|string`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(exampleObject).length === 0) {
|
||||||
|
exampleObject[`${fieldLabel}|string`] = `${fieldLabel}|string`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return exampleObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldType === 'array') {
|
||||||
|
const itemFieldName = fieldName.endsWith('s') ? fieldName.slice(0, -1) : `${fieldName}_item`;
|
||||||
|
return [buildExample(schemaLike.items || {}, currentFile, itemFieldName, seenRefs)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackScalar(fieldLabel, fieldType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function httpMethods(pathItem) {
|
||||||
|
return ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'trace'].filter(
|
||||||
|
(method) => pathItem && typeof pathItem[method] === 'object',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const filePath of filePaths.filter((file) => file !== rootFile)) {
|
||||||
|
const documentData = docs.get(filePath);
|
||||||
|
if (!documentData || typeof documentData !== 'object') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (documentData.components && documentData.components.schemas) {
|
||||||
|
for (const [schemaName, schemaValue] of Object.entries(documentData.components.schemas)) {
|
||||||
|
schemaValue.example = buildExample(schemaValue, filePath, schemaName, new Set());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (documentData.paths) {
|
||||||
|
for (const pathItem of Object.values(documentData.paths)) {
|
||||||
|
for (const method of httpMethods(pathItem)) {
|
||||||
|
const operation = pathItem[method];
|
||||||
|
|
||||||
|
const requestContent = operation?.requestBody?.content;
|
||||||
|
if (requestContent && typeof requestContent === 'object') {
|
||||||
|
for (const media of Object.values(requestContent)) {
|
||||||
|
if (media && media.schema) {
|
||||||
|
delete media.examples;
|
||||||
|
media.example = buildExample(media.schema, filePath, 'request', new Set());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const responses = operation?.responses;
|
||||||
|
if (responses && typeof responses === 'object') {
|
||||||
|
for (const response of Object.values(responses)) {
|
||||||
|
const responseContent = response?.content;
|
||||||
|
if (!responseContent || typeof responseContent !== 'object') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const media of Object.values(responseContent)) {
|
||||||
|
if (media && media.schema) {
|
||||||
|
delete media.examples;
|
||||||
|
media.example = buildExample(media.schema, filePath, 'response', new Set());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(filePath, YAML.stringify(documentData, { lineWidth: 0 }), 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Updated examples in ${filePaths.length - 1} split OpenAPI files.`);
|
||||||
132
.tmp-openapi-validate/make-openapi-standalone.cjs
Normal file
132
.tmp-openapi-validate/make-openapi-standalone.cjs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const YAML = require('yaml');
|
||||||
|
|
||||||
|
const repoRoot = path.resolve(process.cwd(), '..');
|
||||||
|
const specRoot = path.join(repoRoot, 'pocket-base', 'spec');
|
||||||
|
const folders = ['openapi-wx', 'openapi-manage'];
|
||||||
|
|
||||||
|
const allFiles = [];
|
||||||
|
for (const folder of folders) {
|
||||||
|
const dir = path.join(specRoot, folder);
|
||||||
|
for (const name of fs.readdirSync(dir)) {
|
||||||
|
if (name.endsWith('.yaml')) allFiles.push(path.join(dir, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const docCache = new Map();
|
||||||
|
function loadDoc(filePath) {
|
||||||
|
if (!docCache.has(filePath)) {
|
||||||
|
const text = fs.readFileSync(filePath, 'utf8');
|
||||||
|
docCache.set(filePath, YAML.parse(text));
|
||||||
|
}
|
||||||
|
return docCache.get(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepClone(v) {
|
||||||
|
return v == null ? v : JSON.parse(JSON.stringify(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodePointer(seg) {
|
||||||
|
return seg.replace(/~1/g, '/').replace(/~0/g, '~');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getByPointer(doc, pointer) {
|
||||||
|
if (!pointer || pointer === '#' || pointer === '') return doc;
|
||||||
|
const parts = pointer.replace(/^#/, '').split('/').filter(Boolean).map(decodePointer);
|
||||||
|
let cur = doc;
|
||||||
|
for (const p of parts) {
|
||||||
|
if (cur == null) return undefined;
|
||||||
|
cur = cur[p];
|
||||||
|
}
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeObjects(base, extra) {
|
||||||
|
if (base && typeof base === 'object' && !Array.isArray(base) && extra && typeof extra === 'object' && !Array.isArray(extra)) {
|
||||||
|
return { ...base, ...extra };
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isExternalRef(ref) {
|
||||||
|
if (typeof ref !== 'string') return false;
|
||||||
|
if (ref.startsWith('#')) return false;
|
||||||
|
if (/^https?:\/\//i.test(ref)) return false;
|
||||||
|
const [filePart] = ref.split('#');
|
||||||
|
return !!filePart;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveExternalRef(ref, baseFile) {
|
||||||
|
const [filePart, hashPart = ''] = ref.split('#');
|
||||||
|
const targetFile = path.resolve(path.dirname(baseFile), filePart);
|
||||||
|
const targetDoc = loadDoc(targetFile);
|
||||||
|
const pointer = hashPart ? `#${hashPart}` : '#';
|
||||||
|
const targetNode = getByPointer(targetDoc, pointer);
|
||||||
|
return { targetFile, targetNode: deepClone(targetNode) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function derefExternals(node, baseFile, seen = new Set()) {
|
||||||
|
if (Array.isArray(node)) {
|
||||||
|
return node.map((item) => derefExternals(item, baseFile, seen));
|
||||||
|
}
|
||||||
|
if (!node || typeof node !== 'object') {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof node.$ref === 'string' && isExternalRef(node.$ref)) {
|
||||||
|
const key = `${baseFile}::${node.$ref}`;
|
||||||
|
if (seen.has(key)) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
seen.add(key);
|
||||||
|
|
||||||
|
const { targetFile, targetNode } = resolveExternalRef(node.$ref, baseFile);
|
||||||
|
let inlined = derefExternals(targetNode, targetFile, seen);
|
||||||
|
|
||||||
|
const siblings = { ...node };
|
||||||
|
delete siblings.$ref;
|
||||||
|
if (Object.keys(siblings).length > 0) {
|
||||||
|
inlined = mergeObjects(inlined, derefExternals(siblings, baseFile, seen));
|
||||||
|
}
|
||||||
|
return inlined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = {};
|
||||||
|
for (const [k, v] of Object.entries(node)) {
|
||||||
|
out[k] = derefExternals(v, baseFile, seen);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureTopLevel(doc, filePath) {
|
||||||
|
if (!doc.openapi) doc.openapi = '3.1.0';
|
||||||
|
if (!doc.info || typeof doc.info !== 'object') {
|
||||||
|
const base = path.basename(filePath, '.yaml');
|
||||||
|
const group = filePath.includes('openapi-wx') ? 'WX Native' : 'Manage Hooks';
|
||||||
|
doc.info = {
|
||||||
|
title: `BAI ${group} API - ${base}`,
|
||||||
|
version: '1.0.0'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (!doc.info.title) doc.info.title = `BAI API - ${path.basename(filePath, '.yaml')}`;
|
||||||
|
if (!doc.info.version) doc.info.version = '1.0.0';
|
||||||
|
}
|
||||||
|
if (!Array.isArray(doc.servers)) {
|
||||||
|
doc.servers = [
|
||||||
|
{ url: 'https://bai-api.blv-oa.com', description: '生产环境' },
|
||||||
|
{ url: 'http://localhost:8090', description: 'PocketBase 本地环境' }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const filePath of allFiles) {
|
||||||
|
const original = loadDoc(filePath);
|
||||||
|
const transformed = derefExternals(deepClone(original), filePath);
|
||||||
|
ensureTopLevel(transformed, filePath);
|
||||||
|
const outText = YAML.stringify(transformed, { lineWidth: 0 });
|
||||||
|
fs.writeFileSync(filePath, outText, 'utf8');
|
||||||
|
docCache.set(filePath, transformed);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Processed ${allFiles.length} yaml files under openapi-wx/openapi-manage.`);
|
||||||
9
.tmp-openapi-validate/package.json
Normal file
9
.tmp-openapi-validate/package.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "tmp-openapi-validate",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@redocly/cli": "^2.25.4",
|
||||||
|
"yaml": "^2.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
152
docs/pb_collection_permission_overview.md
Normal file
152
docs/pb_collection_permission_overview.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# PocketBase Collection 权限总览
|
||||||
|
|
||||||
|
> 范围:当前仓库 `docs/pb_tbl_*.md` 已存在表结构文档 + 本次新增的 3 张方案相关草案表
|
||||||
|
> 目标:把“每张表该如何做 PocketBase 原生权限控制”整理成一份便于确认、评审和后续写脚本的总览
|
||||||
|
> 状态:`working draft`
|
||||||
|
|
||||||
|
## 阅读说明
|
||||||
|
|
||||||
|
- 本文档分为“现状规则”和“本次新增建议规则”两部分。
|
||||||
|
- “现状规则”优先参考现有 `docs/pb_tbl_*.md` 与 `script/pocketbase*.js`。
|
||||||
|
- “新增建议规则”对应你这次要加的 3 张表,当前仅为文档设计,尚未执行。
|
||||||
|
- 本文所有 owner 行级规则都默认使用 `@request.auth.openid` 作为业务身份锚点。
|
||||||
|
- 软删除约定统一为:`is_delete = 0` 才视为可见、可操作数据。
|
||||||
|
|
||||||
|
## 一、推荐统一规则模板
|
||||||
|
|
||||||
|
### 1. owner 私有数据表
|
||||||
|
|
||||||
|
适用场景:
|
||||||
|
|
||||||
|
- 购物车
|
||||||
|
- 订单
|
||||||
|
- 本次新增的方案 / 方案分享 / 方案模板
|
||||||
|
|
||||||
|
推荐模板:
|
||||||
|
|
||||||
|
| Rule | 推荐表达式 |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `listRule` | `@request.auth.id != "" && <owner_field> = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `viewRule` | `@request.auth.id != "" && <owner_field> = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `createRule` | `@request.auth.id != "" && @request.body.<owner_field> = @request.auth.openid` |
|
||||||
|
| `updateRule` | `@request.auth.id != "" && <owner_field> = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `deleteRule` | `@request.auth.id != "" && <owner_field> = @request.auth.openid && is_delete = 0` |
|
||||||
|
|
||||||
|
### 2. 公开可读、管理写
|
||||||
|
|
||||||
|
适用场景:
|
||||||
|
|
||||||
|
- 字典
|
||||||
|
- 产品
|
||||||
|
- 文档
|
||||||
|
- 附件
|
||||||
|
|
||||||
|
典型模式:
|
||||||
|
|
||||||
|
| Rule | 推荐表达式 |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `listRule` | `is_delete = 0` |
|
||||||
|
| `viewRule` | `is_delete = 0` |
|
||||||
|
| `createRule` | `@request.auth.users_idtype = "ManagePlatform" \|\| @request.auth.usergroups_id = "<管理角色ID>"` |
|
||||||
|
| `updateRule` | `@request.auth.users_idtype = "ManagePlatform" \|\| @request.auth.usergroups_id = "<管理角色ID>"` |
|
||||||
|
| `deleteRule` | `@request.auth.users_idtype = "ManagePlatform" \|\| @request.auth.usergroups_id = "<管理角色ID>"` |
|
||||||
|
|
||||||
|
### 3. 管理/权限元数据表
|
||||||
|
|
||||||
|
适用场景:
|
||||||
|
|
||||||
|
- 角色
|
||||||
|
- 资源
|
||||||
|
- 角色权限
|
||||||
|
- 用户覆盖权限
|
||||||
|
- 行级范围
|
||||||
|
- 文档操作历史
|
||||||
|
|
||||||
|
典型模式:
|
||||||
|
|
||||||
|
- 默认不建议公开给普通用户直接访问。
|
||||||
|
- 推荐仅管理端 / 管理角色通过 hooks 页面或内部脚本维护。
|
||||||
|
|
||||||
|
## 二、现有表规则总表
|
||||||
|
|
||||||
|
## 2.1 owner 私有数据
|
||||||
|
|
||||||
|
| 表名 | owner 字段 | list/view | create | update/delete | 说明 |
|
||||||
|
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||||
|
| `tbl_cart` | `cart_owner` | `@request.auth.id != "" && cart_owner = @request.auth.openid && is_delete = 0` | `@request.body.cart_owner != ""` | `@request.auth.id != "" && cart_owner = @request.auth.openid` | 已在 `script/pocketbase.cart-order.js` 明确;当前 create 已放宽为只要显式提交非空 owner 即可 |
|
||||||
|
| `tbl_order` | `order_owner` | `@request.auth.id != "" && order_owner = @request.auth.openid && is_delete = 0` | `@request.auth.id != "" && @request.body.order_owner = @request.auth.openid` | `@request.auth.id != "" && order_owner = @request.auth.openid` | 已在 `script/pocketbase.cart-order.js` 明确 |
|
||||||
|
|
||||||
|
## 2.2 公开可读、管理写
|
||||||
|
|
||||||
|
| 表名 | list/view | create/update/delete | 说明 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `tbl_system_dict` | `is_delete = 0` | `ManagePlatform` 或管理角色 | `script/pocketbase.dictionary.js` 已明确 |
|
||||||
|
| `tbl_product_list` | `is_delete = 0` | `ManagePlatform` 或管理角色 | `script/pocketbase.product-list.js` 已明确 |
|
||||||
|
| `tbl_document` | `is_delete = 0` | 文档当前脚本未显式开放原生写;实际业务通过 hooks 管理端写入 | `script/pocketbase.documents.js` + 现有文档口径 |
|
||||||
|
| `tbl_attachments` | `is_delete = 0` | 原生写默认不开放,实际通过 hooks / 管理端上传 | `script/pocketbase.documents.js` + 现有文档口径 |
|
||||||
|
|
||||||
|
## 2.3 管理端/权限模型表
|
||||||
|
|
||||||
|
| 表名 | 当前口径 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `tbl_auth_resources` | 管理用途,不建议公开给普通用户直连 | 现有文档写为“仅管理员 / 管理角色允许” |
|
||||||
|
| `tbl_auth_roles` | 管理用途,不建议公开给普通用户直连 | 同上 |
|
||||||
|
| `tbl_auth_role_perms` | 管理用途,不建议公开给普通用户直连 | 同上 |
|
||||||
|
| `tbl_auth_user_overrides` | 管理用途,不建议公开给普通用户直连 | 同上 |
|
||||||
|
| `tbl_auth_row_scopes` | 管理用途,不建议公开给普通用户直连 | 同上 |
|
||||||
|
| `tbl_document_operation_history` | 管理用途,不建议公开给普通用户直连 | 同上 |
|
||||||
|
|
||||||
|
## 2.4 特殊表
|
||||||
|
|
||||||
|
| 表名 | 当前口径 | 备注 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `tbl_auth_users` | 当前文档口径为“公开允许新增与修改;列表 / 详情 / 删除仅管理员 / 管理角色允许” | 这是 auth collection,既受 PB auth 机制影响,也受 hooks 认证流程影响 |
|
||||||
|
| `tbl_company` | 当前文档口径为“公开可创建、公开可列出;详情 / 更新 / 删除仅管理员或管理后台用户允许” | 更偏业务接入表,不属于 owner 私有表 |
|
||||||
|
|
||||||
|
## 三、本次新增 3 表建议规则
|
||||||
|
|
||||||
|
| 表名 | owner 字段 | list/view | create | update/delete | 备注 |
|
||||||
|
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||||
|
| `tbl_scheme` | `scheme_owner` | `@request.auth.id != "" && scheme_owner = @request.auth.openid && is_delete = 0` | `@request.auth.id != "" && @request.body.scheme_owner = @request.auth.openid` | `@request.auth.id != "" && scheme_owner = @request.auth.openid && is_delete = 0` | 私有方案表 |
|
||||||
|
| `tbl_scheme_share` | `scheme_share_owner` | `@request.auth.id != "" && scheme_share_owner = @request.auth.openid && is_delete = 0` | `@request.auth.id != "" && @request.body.scheme_share_owner = @request.auth.openid` | `@request.auth.id != "" && scheme_share_owner = @request.auth.openid && is_delete = 0` | 按你的要求,分享表也采用 owner 私有规则,因此接收方默认不能直连查看 |
|
||||||
|
| `tbl_scheme_template` | `scheme_template_owner` | `@request.auth.id != "" && scheme_template_owner = @request.auth.openid && is_delete = 0` | `@request.auth.id != "" && @request.body.scheme_template_owner = @request.auth.openid` | `@request.auth.id != "" && scheme_template_owner = @request.auth.openid && is_delete = 0` | 私有模板表 |
|
||||||
|
|
||||||
|
## 四、这次设计里需要你重点确认的点
|
||||||
|
|
||||||
|
### 1. `tbl_scheme_share` 是否接受新增 `scheme_share_owner`
|
||||||
|
|
||||||
|
这是本次最关键的结构补充。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 你要求 3 张表统一按 `xxx_owner = token 对应 openid` 做 PB 行级限制。
|
||||||
|
- 原始草图里的分享表没有 owner 字段,无法直接套用这套规则。
|
||||||
|
|
||||||
|
如果你确认这条设计,后续脚本会把它作为正式字段创建。
|
||||||
|
|
||||||
|
### 2. `createRule` 是否必须校验 `@request.body.<owner_field> = @request.auth.openid`
|
||||||
|
|
||||||
|
当前文档建议“要校验”。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 这样可以防止普通用户伪造别人的 owner 值直接创建越权数据。
|
||||||
|
- 这也是现有 `tbl_cart` / `tbl_order` 的做法,风格一致。
|
||||||
|
|
||||||
|
### 3. 分享表是否允许“被分享人”直接走 PB 原生 API 查看
|
||||||
|
|
||||||
|
当前文档按你的要求,先设计成:
|
||||||
|
|
||||||
|
- 只有 `scheme_share_owner` 能删改查
|
||||||
|
|
||||||
|
这最严格,但也意味着:
|
||||||
|
|
||||||
|
- `scheme_share_to` 对应的用户不能直接通过 PB 原生 `list/view` 看到分享记录
|
||||||
|
|
||||||
|
如果你的实际业务是“被分享人需要能直接看到收到的分享”,那这张表的 `listRule` / `viewRule` 需要改成双边可见,而不是纯 owner 模式。
|
||||||
|
|
||||||
|
## 五、对应文档索引
|
||||||
|
|
||||||
|
- [pb_tbl_scheme.md](/E:/Project_Class/BAI/Web_BAI_Manage_ApiServer/docs/pb_tbl_scheme.md)
|
||||||
|
- [pb_tbl_scheme_share.md](/E:/Project_Class/BAI/Web_BAI_Manage_ApiServer/docs/pb_tbl_scheme_share.md)
|
||||||
|
- [pb_tbl_scheme_template.md](/E:/Project_Class/BAI/Web_BAI_Manage_ApiServer/docs/pb_tbl_scheme_template.md)
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
| `attachments_ocr` | `text` | 否 | OCR 识别结果 |
|
| `attachments_ocr` | `text` | 否 | OCR 识别结果 |
|
||||||
| `attachments_status` | `text` | 否 | 附件状态 |
|
| `attachments_status` | `text` | 否 | 附件状态 |
|
||||||
| `attachments_remark` | `text` | 否 | 备注 |
|
| `attachments_remark` | `text` | 否 | 备注 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -35,5 +36,7 @@
|
|||||||
## 补充约定
|
## 补充约定
|
||||||
|
|
||||||
- 图片、视频、普通文件都统一走本表。
|
- 图片、视频、普通文件都统一走本表。
|
||||||
|
- `is_delete` 用于软删除控制,附件删除时建议先标记为 `1`,并由后续归档/清理任务处理物理文件。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,附件列表/详情默认不返回已软删除数据。
|
||||||
- 业务访问控制不放在本表,而由引用它的业务表与 hooks 接口控制。
|
- 业务访问控制不放在本表,而由引用它的业务表与 hooks 接口控制。
|
||||||
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
||||||
|
|||||||
@@ -17,6 +17,12 @@
|
|||||||
| `table_name` | `text` | 是 | 对应数据表名 |
|
| `table_name` | `text` | 是 | 对应数据表名 |
|
||||||
| `column_name` | `text` | 否 | 对应字段名;表级权限时可为空 |
|
| `column_name` | `text` | 否 | 对应字段名;表级权限时可为空 |
|
||||||
| `res_type` | `text` | 是 | 资源类型 |
|
| `res_type` | `text` | 是 | 资源类型 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
|
## 补充约定
|
||||||
|
|
||||||
|
- `is_delete` 用于软删除控制,资源定义如需停用应优先置为 `1`,避免直接物理删除影响权限审计。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,资源列表/详情默认不返回已软删除数据。
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
| `res_id` | `text` | 是 | 资源业务 ID |
|
| `res_id` | `text` | 是 | 资源业务 ID |
|
||||||
| `access_level` | `number` | 是 | 权限级别 |
|
| `access_level` | `number` | 是 | 权限级别 |
|
||||||
| `priority` | `number` | 否 | 优先级 |
|
| `priority` | `number` | 否 | 优先级 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -27,3 +28,8 @@
|
|||||||
| `idx_tbl_auth_role_perms_role_id` | `INDEX` | 加速按角色查询 |
|
| `idx_tbl_auth_role_perms_role_id` | `INDEX` | 加速按角色查询 |
|
||||||
| `idx_tbl_auth_role_perms_res_id` | `INDEX` | 加速按资源查询 |
|
| `idx_tbl_auth_role_perms_res_id` | `INDEX` | 加速按资源查询 |
|
||||||
| `idx_tbl_auth_role_perms_unique_map` | `UNIQUE INDEX` | 保证 `role_id + res_id` 唯一 |
|
| `idx_tbl_auth_role_perms_unique_map` | `UNIQUE INDEX` | 保证 `role_id + res_id` 唯一 |
|
||||||
|
|
||||||
|
## 补充约定
|
||||||
|
|
||||||
|
- `is_delete` 用于软删除控制,历史角色授权建议通过置 `1` 失效,以保留审计痕迹。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,角色权限映射默认不返回已软删除记录。
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
| `role_code` | `text` | 否 | 角色编码 |
|
| `role_code` | `text` | 否 | 角色编码 |
|
||||||
| `role_status` | `number` | 否 | 角色状态 |
|
| `role_status` | `number` | 否 | 角色状态 |
|
||||||
| `role_remark` | `text` | 否 | 备注 |
|
| `role_remark` | `text` | 否 | 备注 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -26,3 +27,8 @@
|
|||||||
| `idx_tbl_auth_roles_role_id` | `UNIQUE INDEX` | 保证 `role_id` 唯一 |
|
| `idx_tbl_auth_roles_role_id` | `UNIQUE INDEX` | 保证 `role_id` 唯一 |
|
||||||
| `idx_tbl_auth_roles_role_name` | `UNIQUE INDEX` | 保证 `role_name` 唯一 |
|
| `idx_tbl_auth_roles_role_name` | `UNIQUE INDEX` | 保证 `role_name` 唯一 |
|
||||||
| `idx_tbl_auth_roles_role_code` | `UNIQUE INDEX` | 保证 `role_code` 唯一 |
|
| `idx_tbl_auth_roles_role_code` | `UNIQUE INDEX` | 保证 `role_code` 唯一 |
|
||||||
|
|
||||||
|
## 补充约定
|
||||||
|
|
||||||
|
- `is_delete` 用于软删除控制,角色下线时应优先置为 `1`,避免直接物理删除导致历史授权链断裂。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,角色列表/详情默认不返回已软删除数据。
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
| `target_id` | `text` | 是 | 目标 ID |
|
| `target_id` | `text` | 是 | 目标 ID |
|
||||||
| `table_name` | `text` | 是 | 作用表名 |
|
| `table_name` | `text` | 是 | 作用表名 |
|
||||||
| `filter_sql` | `editor` | 是 | 行级过滤表达式 |
|
| `filter_sql` | `editor` | 是 | 行级过滤表达式 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -27,3 +28,8 @@
|
|||||||
| `idx_tbl_auth_row_scopes_target_type` | `INDEX` | 加速按目标类型查询 |
|
| `idx_tbl_auth_row_scopes_target_type` | `INDEX` | 加速按目标类型查询 |
|
||||||
| `idx_tbl_auth_row_scopes_target_id` | `INDEX` | 加速按目标 ID 查询 |
|
| `idx_tbl_auth_row_scopes_target_id` | `INDEX` | 加速按目标 ID 查询 |
|
||||||
| `idx_tbl_auth_row_scopes_table_name` | `INDEX` | 加速按作用表查询 |
|
| `idx_tbl_auth_row_scopes_table_name` | `INDEX` | 加速按作用表查询 |
|
||||||
|
|
||||||
|
## 补充约定
|
||||||
|
|
||||||
|
- `is_delete` 用于软删除控制,废弃的行级范围规则建议软删除保留,以便问题追溯。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,行级范围列表/详情默认隐藏已软删除规则。
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
| `res_id` | `text` | 是 | 资源业务 ID |
|
| `res_id` | `text` | 是 | 资源业务 ID |
|
||||||
| `access_level` | `number` | 是 | 权限级别 |
|
| `access_level` | `number` | 是 | 权限级别 |
|
||||||
| `priority` | `number` | 否 | 优先级 |
|
| `priority` | `number` | 否 | 优先级 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -27,3 +28,8 @@
|
|||||||
| `idx_tbl_auth_user_overrides_users_convers_id` | `INDEX` | 加速按用户查询 |
|
| `idx_tbl_auth_user_overrides_users_convers_id` | `INDEX` | 加速按用户查询 |
|
||||||
| `idx_tbl_auth_user_overrides_res_id` | `INDEX` | 加速按资源查询 |
|
| `idx_tbl_auth_user_overrides_res_id` | `INDEX` | 加速按资源查询 |
|
||||||
| `idx_tbl_auth_user_overrides_unique_map` | `UNIQUE INDEX` | 保证 `users_convers_id + res_id` 唯一 |
|
| `idx_tbl_auth_user_overrides_unique_map` | `UNIQUE INDEX` | 保证 `users_convers_id + res_id` 唯一 |
|
||||||
|
|
||||||
|
## 补充约定
|
||||||
|
|
||||||
|
- `is_delete` 用于软删除控制,用户覆盖权限取消时建议软删除保留记录。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,用户覆盖权限列表/详情默认隐藏已软删除记录。
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
| `users_id_pic_a` | `text` | 否 | 证件照正面附件 ID |
|
| `users_id_pic_a` | `text` | 否 | 证件照正面附件 ID |
|
||||||
| `users_id_pic_b` | `text` | 否 | 证件照反面附件 ID |
|
| `users_id_pic_b` | `text` | 否 | 证件照反面附件 ID |
|
||||||
| `users_tag` | `text` | 否 | 用户标签 |
|
| `users_tag` | `text` | 否 | 用户标签 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -62,6 +63,8 @@
|
|||||||
|
|
||||||
- 本表为 `auth` collection,除上述字段外还受 PocketBase 原生鉴权机制约束。
|
- 本表为 `auth` collection,除上述字段外还受 PocketBase 原生鉴权机制约束。
|
||||||
- 图片类字段统一只保存 `tbl_attachments.attachments_id`。
|
- 图片类字段统一只保存 `tbl_attachments.attachments_id`。
|
||||||
|
- `is_delete` 用于软删除控制,业务侧删除时应优先将其置为 `1`,而不是直接物理删除记录。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,管理侧常规列表/详情默认隐藏已软删除用户。
|
||||||
- 登录接口返回的 token 来源于本表 auth record 的原生签发能力,可直接给 PocketBase SDK 使用。
|
- 登录接口返回的 token 来源于本表 auth record 的原生签发能力,可直接给 PocketBase SDK 使用。
|
||||||
- 新用户注册时,`users_level` 默认保持为空;已有用户后续登录 / 更新流程也不会自动改写该字段。
|
- 新用户注册时,`users_level` 默认保持为空;已有用户后续登录 / 更新流程也不会自动改写该字段。
|
||||||
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
||||||
|
|||||||
@@ -20,14 +20,15 @@
|
|||||||
| :--- | :--- | :---: | :--- |
|
| :--- | :--- | :---: | :--- |
|
||||||
| `id` | `text` | 是 | PocketBase 记录主键 |
|
| `id` | `text` | 是 | PocketBase 记录主键 |
|
||||||
| `cart_id` | `text` | 是 | 购物车项业务 ID,唯一标识 |
|
| `cart_id` | `text` | 是 | 购物车项业务 ID,唯一标识 |
|
||||||
| `cart_number` | `text` | 是 | 购物车名称 / 分组号,默认可按“用户名+年月日时分秒”生成 |
|
| `cart_number` | `text` | 否 | 购物车名称 / 分组号,可为空;如需展示编号建议在 hooks / 业务侧补齐 |
|
||||||
| `cart_create` | `autodate` | 否 | 购物车项创建时间,由数据库自动生成 |
|
| `cart_create` | `autodate` | 否 | 购物车项创建时间,由数据库自动生成 |
|
||||||
| `cart_owner` | `text` | 是 | 生成者 openid,约定保存 `tbl_auth_users.openid` |
|
| `cart_owner` | `text` | 是 | 生成者 openid,约定保存 `tbl_auth_users.openid` |
|
||||||
| `cart_product_id` | `text` | 是 | 产品 ID,建议保存 `tbl_product_list.prod_list_id` |
|
| `cart_product_id` | `relation` | 是 | 原生关联 `tbl_product_list`,单选,保存的是目标产品记录的 PocketBase `recordId` |
|
||||||
| `cart_product_quantity` | `number` | 是 | 产品数量,建议业务侧约束为正整数 |
|
| `cart_product_quantity` | `number` | 否 | 产品数量,可为空;如传值,建议业务侧约束为正整数 |
|
||||||
| `cart_status` | `text` | 是 | 购物车状态,建议值:`有效` / `无效` |
|
| `cart_status` | `text` | 否 | 购物车状态,可为空;如传值,建议值:`有效` / `无效` |
|
||||||
| `cart_at_price` | `number` | 是 | 加入购物车时的价格,用于后续降价提醒或对比 |
|
| `cart_at_price` | `number` | 否 | 加入购物车时的价格,可为空;如传值用于后续降价提醒或对比 |
|
||||||
| `cart_remark` | `text` | 否 | 备注 |
|
| `cart_remark` | `text` | 否 | 备注 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -44,8 +45,15 @@
|
|||||||
|
|
||||||
## 补充约定
|
## 补充约定
|
||||||
|
|
||||||
- `cart_owner`、`cart_product_id` 当前按文本字段保存业务 ID,不直接建立 relation,便于兼容现有 hooks 业务模型。
|
- `cart_owner` 当前按文本字段保存业务 ID。
|
||||||
|
- `cart_product_id` 已改为 PocketBase 原生 `relation` 字段,关联目标集合为 `tbl_product_list`,且 `maxSelect = 1`。
|
||||||
|
- 调用原生 records create / update 接口时,`cart_product_id` 必须传 **`tbl_product_list` 的 PocketBase 记录主键 `id`**,不能再传业务 ID `prod_list_id`。
|
||||||
|
- 如前端当前拿到的是业务 ID `prod_list_id`,需先调用 `GET /pb/api/collections/tbl_product_list/records?filter=prod_list_id="..."&perPage=1&page=1` 查出对应 `recordId`,再把该 `recordId` 作为 `cart_product_id` 提交。
|
||||||
|
- 当前原生 collection 层面,创建时仅要求客户端显式提交非空 `cart_owner`;不再强制要求与当前 token 对应 `openid` 相等。
|
||||||
|
- 当前表结构层面,除 `cart_id`、`cart_owner`、`cart_product_id` 外,其余业务字段均允许为空。
|
||||||
- `cart_owner` 统一保存 `tbl_auth_users.openid`,便于直接使用微信登录返回 token 做原生访问控制。
|
- `cart_owner` 统一保存 `tbl_auth_users.openid`,便于直接使用微信登录返回 token 做原生访问控制。
|
||||||
|
- `is_delete` 用于软删除控制,购物车项删除时建议优先标记为 `1`。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,常规列表/详情不会返回已软删除数据。
|
||||||
- `cart_product_quantity`、`cart_at_price` 使用 `number`,数量正整数与价格精度建议在 hooks / API 层统一校验。
|
- `cart_product_quantity`、`cart_at_price` 使用 `number`,数量正整数与价格精度建议在 hooks / API 层统一校验。
|
||||||
- 当购物车被清空时,建议业务侧将历史记录 `cart_status` 置为 `无效`,而不是直接覆盖有效记录。
|
- 当购物车被清空时,建议业务侧将历史记录 `cart_status` 置为 `无效`,而不是直接覆盖有效记录。
|
||||||
- `cart_create` 由数据库自动写入,接口层不需要也不应允许客户端自行填值。
|
- `cart_create` 由数据库自动写入,接口层不需要也不应允许客户端自行填值。
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
| `company_level` | `text` | 否 | 公司等级 |
|
| `company_level` | `text` | 否 | 公司等级 |
|
||||||
| `company_owner_openid` | `text` | 否 | 公司所有者 openid |
|
| `company_owner_openid` | `text` | 否 | 公司所有者 openid |
|
||||||
| `company_remark` | `text` | 否 | 备注 |
|
| `company_remark` | `text` | 否 | 备注 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -44,5 +45,7 @@
|
|||||||
## 补充约定
|
## 补充约定
|
||||||
|
|
||||||
- 微信端原生 PocketBase 接口支持公开创建公司记录。
|
- 微信端原生 PocketBase 接口支持公开创建公司记录。
|
||||||
|
- `is_delete` 用于软删除控制,公司资料停用时应优先置为 `1`。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,公司列表/详情默认不返回已软删除数据。
|
||||||
- `company_id` 已切换为数据库自动生成,客户端不再需要提交。
|
- `company_id` 已切换为数据库自动生成,客户端不再需要提交。
|
||||||
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
| `document_remark` | `text` | 否 | 备注 |
|
| `document_remark` | `text` | 否 | 备注 |
|
||||||
| `document_file` | `text` | 否 | 普通文件附件 ID 集合,底层以 `|` 分隔 |
|
| `document_file` | `text` | 否 | 普通文件附件 ID 集合,底层以 `|` 分隔 |
|
||||||
| `document_create` | `autodate` | 否 | 文档创建时间,由数据库自动生成 |
|
| `document_create` | `autodate` | 否 | 文档创建时间,由数据库自动生成 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -57,6 +58,8 @@
|
|||||||
## 补充约定
|
## 补充约定
|
||||||
|
|
||||||
- 三类附件字段都只保存 `attachments_id`,真实文件统一在 `tbl_attachments`。
|
- 三类附件字段都只保存 `attachments_id`,真实文件统一在 `tbl_attachments`。
|
||||||
|
- `is_delete` 用于软删除控制,文档删除时建议优先标记为 `1`,避免直接物理删除。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,公开列表与详情默认隐藏已软删除文档。
|
||||||
- `document_create` 已作为原生 PocketBase 列表排序字段,推荐使用 `sort=-document_create`。
|
- `document_create` 已作为原生 PocketBase 列表排序字段,推荐使用 `sort=-document_create`。
|
||||||
- 面向用户填写的字段里,仅 `document_title`、`document_type` 必填,其余允许为空。
|
- 面向用户填写的字段里,仅 `document_title`、`document_type` 必填,其余允许为空。
|
||||||
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
| `doh_user_id` | `text` | 否 | 操作人业务 ID |
|
| `doh_user_id` | `text` | 否 | 操作人业务 ID |
|
||||||
| `doh_current_count` | `number` | 否 | 本次操作对应次数 |
|
| `doh_current_count` | `number` | 否 | 本次操作对应次数 |
|
||||||
| `doh_remark` | `text` | 否 | 备注 |
|
| `doh_remark` | `text` | 否 | 备注 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -32,4 +33,5 @@
|
|||||||
## 补充约定
|
## 补充约定
|
||||||
|
|
||||||
- 本表主要用于管理端审计与追溯,不对匿名用户开放。
|
- 本表主要用于管理端审计与追溯,不对匿名用户开放。
|
||||||
|
- `is_delete` 用于软删除控制,历史记录如需屏蔽显示可标记为 `1`,不建议直接物理删除。
|
||||||
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
| `order_snap` | `json` | 是 | 订单快照,完整保存订单明细信息 |
|
| `order_snap` | `json` | 是 | 订单快照,完整保存订单明细信息 |
|
||||||
| `order_amount` | `number` | 是 | 订单总金额 |
|
| `order_amount` | `number` | 是 | 订单总金额 |
|
||||||
| `order_remark` | `text` | 否 | 订单备注 |
|
| `order_remark` | `text` | 否 | 订单备注 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -49,6 +50,8 @@
|
|||||||
- 当订单进入 `订单已确定` 及之后状态时,建议业务侧锁定关键字段,不再允许修改订单核心数据。
|
- 当订单进入 `订单已确定` 及之后状态时,建议业务侧锁定关键字段,不再允许修改订单核心数据。
|
||||||
- `order_owner`、`order_source_id` 当前按文本字段保存业务 ID,不直接建立 relation,便于兼容现有 hooks 业务模型。
|
- `order_owner`、`order_source_id` 当前按文本字段保存业务 ID,不直接建立 relation,便于兼容现有 hooks 业务模型。
|
||||||
- `order_owner` 统一保存 `tbl_auth_users.openid`,便于直接使用微信登录返回 token 做原生访问控制。
|
- `order_owner` 统一保存 `tbl_auth_users.openid`,便于直接使用微信登录返回 token 做原生访问控制。
|
||||||
|
- `is_delete` 用于软删除控制,订单删除/归档时建议优先标记为 `1`。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,常规列表/详情不会返回已软删除数据。
|
||||||
- `order_amount` 使用 `number`,货币精度策略建议后续统一为“分”或固定小数位。
|
- `order_amount` 使用 `number`,货币精度策略建议后续统一为“分”或固定小数位。
|
||||||
- `order_create` 由数据库自动写入,接口层不需要也不应允许客户端自行填值。
|
- `order_create` 由数据库自动写入,接口层不需要也不应允许客户端自行填值。
|
||||||
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
| `prod_list_id` | `text` | 是 | 产品列表业务 ID,唯一标识 |
|
| `prod_list_id` | `text` | 是 | 产品列表业务 ID,唯一标识 |
|
||||||
| `prod_list_name` | `text` | 是 | 产品名称 |
|
| `prod_list_name` | `text` | 是 | 产品名称 |
|
||||||
| `prod_list_modelnumber` | `text` | 否 | 产品型号 |
|
| `prod_list_modelnumber` | `text` | 否 | 产品型号 |
|
||||||
|
| `prod_list_barcode` | `text` | 否 | 产品料号 |
|
||||||
| `prod_list_icon` | `text` | 否 | 产品图标,保存 `tbl_attachments.attachments_id`,多图时以 `|` 分隔 |
|
| `prod_list_icon` | `text` | 否 | 产品图标,保存 `tbl_attachments.attachments_id`,多图时以 `|` 分隔 |
|
||||||
| `prod_list_description` | `text` | 否 | 产品说明(editor 内容,建议保存 Markdown 或已净化 HTML) |
|
| `prod_list_description` | `text` | 否 | 产品说明(editor 内容,建议保存 Markdown 或已净化 HTML) |
|
||||||
| `prod_list_feature` | `text` | 否 | 产品特色 |
|
| `prod_list_feature` | `text` | 否 | 产品特色 |
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
| `prod_list_basic_price` | `number` | 否 | 基础价格 |
|
| `prod_list_basic_price` | `number` | 否 | 基础价格 |
|
||||||
| `prod_list_vip_price` | `json` | 否 | 会员价数组,格式为 `[{"viplevel":"会员等级枚举值","price":1999}]` |
|
| `prod_list_vip_price` | `json` | 否 | 会员价数组,格式为 `[{"viplevel":"会员等级枚举值","price":1999}]` |
|
||||||
| `prod_list_remark` | `text` | 否 | 备注 |
|
| `prod_list_remark` | `text` | 否 | 备注 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -49,6 +51,9 @@
|
|||||||
## 补充约定
|
## 补充约定
|
||||||
|
|
||||||
- `prod_list_icon` 仅保存附件业务 ID,真实文件统一在 `tbl_attachments`;多图时按上传顺序使用 `|` 聚合。
|
- `prod_list_icon` 仅保存附件业务 ID,真实文件统一在 `tbl_attachments`;多图时按上传顺序使用 `|` 聚合。
|
||||||
|
- `prod_list_barcode` 为可空文本字段,用于保存产品料号;当前已接入产品管理页录入、产品列表模糊检索以及方案模板中的产品快照展示。
|
||||||
|
- `is_delete` 用于软删除控制,产品停用/删除时建议优先标记为 `1`。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,产品列表默认不返回已软删除记录。
|
||||||
- 当前预构建脚本中已将 `listRule` 与 `viewRule` 设置为空字符串(`""`),对应 PocketBase 的“任何人可查看”。
|
- 当前预构建脚本中已将 `listRule` 与 `viewRule` 设置为空字符串(`""`),对应 PocketBase 的“任何人可查看”。
|
||||||
- `prod_list_parameters` 使用 PocketBase `json` 字段,写入时应直接提交数组结构:`[{"sort":1,"name":"属性名","value":"属性值"}]`。
|
- `prod_list_parameters` 使用 PocketBase `json` 字段,写入时应直接提交数组结构:`[{"sort":1,"name":"属性名","value":"属性值"}]`。
|
||||||
- `prod_list_parameters.sort` 用于稳定参数展示顺序,约定为正整数;前端未填写时可按当前录入/导入顺序自动补齐。
|
- `prod_list_parameters.sort` 用于稳定参数展示顺序,约定为正整数;前端未填写时可按当前录入/导入顺序自动补齐。
|
||||||
@@ -58,4 +63,4 @@
|
|||||||
- 前端渲染 `prod_list_description` 时应保持和存储格式一致(Markdown 渲染器或受控 HTML 渲染),避免直接注入未净化内容。
|
- 前端渲染 `prod_list_description` 时应保持和存储格式一致(Markdown 渲染器或受控 HTML 渲染),避免直接注入未净化内容。
|
||||||
- `prod_list_basic_price` 使用 `number`,如需货币精度策略建议后续统一为“分”或固定小数位。
|
- `prod_list_basic_price` 使用 `number`,如需货币精度策略建议后续统一为“分”或固定小数位。
|
||||||
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
||||||
- 本文档为预构建结构说明,尚未执行线上建表。
|
- 本文档已同步当前产品表建表脚本;若已执行脚本,则以 PocketBase 实际结构为准。
|
||||||
|
|||||||
73
docs/pb_tbl_scheme.md
Normal file
73
docs/pb_tbl_scheme.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# pb_tbl_scheme
|
||||||
|
|
||||||
|
> 来源:用户提供的结构草图(2026-04-08)与现有 `tbl_cart` / `tbl_order` owner 行级权限模式
|
||||||
|
> 类型:`base`
|
||||||
|
> 状态:`draft`,仅文档设计,尚未执行建表
|
||||||
|
> 读写规则:任意已登录用户可新增,但仅可访问 `scheme_owner = 当前 token 对应 openid` 且 `is_delete = 0` 的记录
|
||||||
|
|
||||||
|
## 表用途
|
||||||
|
|
||||||
|
用于存储用户自己的方案主表,承载方案名称、适用酒店类型、方案筛选条件、房型配置、设备偏好以及引用的高/中/低端模板。
|
||||||
|
|
||||||
|
## 字段清单
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 必填 | 说明 |
|
||||||
|
| :--- | :--- | :---: | :--- |
|
||||||
|
| `id` | `text` | 是 | PocketBase 记录主键 |
|
||||||
|
| `scheme_id` | `text` | 是 | 方案业务 ID,唯一标识 |
|
||||||
|
| `scheme_name` | `text` | 是 | 方案名称 |
|
||||||
|
| `scheme_owner` | `text` | 是 | 方案所有者 openid,保存 `tbl_auth_users.openid` |
|
||||||
|
| `scheme_share_status` | `text` | 否 | 方案分享状态,便于快速识别是否已分享 |
|
||||||
|
| `scheme_expires_at` | `date` | 否 | 方案有效期 |
|
||||||
|
| `scheme_hotel_type` | `text` | 否 | 酒店类型,如经济型 / 中高端 / 连锁型 / 特色名宿 / 特色酒店 |
|
||||||
|
| `scheme_solution_type` | `text` | 否 | 方案类型,建议保存枚举或 `|` 聚合值 |
|
||||||
|
| `scheme_solution_feature` | `text` | 否 | 方案特点,建议保存枚举或 `|` 聚合值 |
|
||||||
|
| `scheme_room_type` | `json` | 否 | 房型配置,建议格式:`[{"room_type":"大床房","qty":20}]` |
|
||||||
|
| `scheme_curtains` | `text` | 否 | 窗帘类型 |
|
||||||
|
| `scheme_voice_device` | `text` | 否 | 语音设备 |
|
||||||
|
| `scheme_ac_type` | `text` | 否 | 空调类型 |
|
||||||
|
| `scheme_template_highend` | `text` | 否 | 高端模板 ID,建议保存 `tbl_scheme_template.scheme_template_id` |
|
||||||
|
| `scheme_template_midend` | `text` | 否 | 中端模板 ID,建议保存 `tbl_scheme_template.scheme_template_id` |
|
||||||
|
| `scheme_template_lowend` | `text` | 否 | 地端模板 ID,建议保存 `tbl_scheme_template.scheme_template_id` |
|
||||||
|
| `scheme_status` | `text` | 否 | 方案状态,如草稿 / 生效 / 失效 |
|
||||||
|
| `scheme_remark` | `text` | 否 | 备注 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
|
## 索引
|
||||||
|
|
||||||
|
| 索引名 | 类型 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `idx_tbl_scheme_scheme_id` | `UNIQUE INDEX` | 保证 `scheme_id` 唯一 |
|
||||||
|
| `idx_tbl_scheme_scheme_owner` | `INDEX` | 加速按方案所有者查询 |
|
||||||
|
| `idx_tbl_scheme_scheme_name` | `INDEX` | 加速按方案名称检索 |
|
||||||
|
| `idx_tbl_scheme_scheme_share_status` | `INDEX` | 加速按分享状态过滤 |
|
||||||
|
| `idx_tbl_scheme_scheme_expires_at` | `INDEX` | 加速按有效期过滤 |
|
||||||
|
| `idx_tbl_scheme_scheme_hotel_type` | `INDEX` | 加速按酒店类型过滤 |
|
||||||
|
| `idx_tbl_scheme_scheme_solution_type` | `INDEX` | 加速按方案类型过滤 |
|
||||||
|
| `idx_tbl_scheme_scheme_solution_feature` | `INDEX` | 加速按方案特点过滤 |
|
||||||
|
| `idx_tbl_scheme_scheme_status` | `INDEX` | 加速按方案状态过滤 |
|
||||||
|
| `idx_tbl_scheme_owner_status` | `INDEX` | 加速同一用户下按状态查询 |
|
||||||
|
|
||||||
|
## 建议 PocketBase 原生权限规则
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 采用和 `tbl_cart` / `tbl_order` 一致的 owner 行级隔离模式。
|
||||||
|
- 这里按“任意已登录用户可创建自己的方案”设计,因此 `createRule` 仍要求携带有效 token。
|
||||||
|
- 若后续由 hooks 自动回填 `scheme_owner`,也建议保留 `@request.body.scheme_owner = @request.auth.openid` 约束,避免越权代建。
|
||||||
|
|
||||||
|
| Rule | 建议表达式 |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `listRule` | `@request.auth.id != "" && scheme_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `viewRule` | `@request.auth.id != "" && scheme_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `createRule` | `@request.auth.id != "" && @request.body.scheme_owner = @request.auth.openid` |
|
||||||
|
| `updateRule` | `@request.auth.id != "" && scheme_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `deleteRule` | `@request.auth.id != "" && scheme_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
|
||||||
|
## 补充约定
|
||||||
|
|
||||||
|
- `scheme_template_highend` / `scheme_template_midend` / `scheme_template_lowend` 当前建议先保存模板业务 ID,不直接建立 relation,便于兼容现有 hooks 风格。
|
||||||
|
- `scheme_room_type` 推荐使用 `json` 数组,避免后续字符串解析成本。
|
||||||
|
- `scheme_solution_type` 与 `scheme_solution_feature` 如果后续要做模板筛选,建议统一保存字典枚举值,而不是自由文本。
|
||||||
|
- `is_delete` 用于软删除控制;对外列表、详情、修改、删除规则都建议默认排除已删除数据。
|
||||||
|
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
||||||
71
docs/pb_tbl_scheme_share.md
Normal file
71
docs/pb_tbl_scheme_share.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# pb_tbl_scheme_share
|
||||||
|
|
||||||
|
> 来源:用户提供的结构草图(2026-04-08)与本次 owner 行级权限要求
|
||||||
|
> 类型:`base`
|
||||||
|
> 状态:`draft`,仅文档设计,尚未执行建表
|
||||||
|
> 读写规则:任意已登录用户可新增,但仅可访问 `scheme_share_owner = 当前 token 对应 openid` 且 `is_delete = 0` 的记录
|
||||||
|
|
||||||
|
## 表用途
|
||||||
|
|
||||||
|
用于存储方案分享记录,描述某个方案被分享给谁、分享权限、有效期以及接收状态。
|
||||||
|
|
||||||
|
## 关键设计说明
|
||||||
|
|
||||||
|
原始草图里没有 owner 字段,但你要求这 3 张表统一采用 `xxx_owner = 当前 token 对应 openid` 的 PB 行级约束。
|
||||||
|
|
||||||
|
因此本表文档中补充新增:
|
||||||
|
|
||||||
|
- `scheme_share_owner`
|
||||||
|
|
||||||
|
该字段用于表示“谁发起了这条分享记录”,并作为 PocketBase 原生访问控制的 owner 锚点。
|
||||||
|
|
||||||
|
## 字段清单
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 必填 | 说明 |
|
||||||
|
| :--- | :--- | :---: | :--- |
|
||||||
|
| `id` | `text` | 是 | PocketBase 记录主键 |
|
||||||
|
| `scheme_share_id` | `text` | 是 | 分享业务 ID,唯一标识 |
|
||||||
|
| `scheme_id` | `text` | 是 | 关联方案 ID,建议保存 `tbl_scheme.scheme_id` |
|
||||||
|
| `scheme_share_owner` | `text` | 是 | 分享发起人 openid,用于 owner 行级权限控制 |
|
||||||
|
| `scheme_share_to` | `text` | 是 | 被分享目标用户标识,建议保存目标用户 openid |
|
||||||
|
| `scheme_share_acceptance_status` | `text` | 否 | 分享接收状态,如待接收 / 已接收 / 已参与 / 已拒绝 |
|
||||||
|
| `scheme_share_expires_at` | `date` | 否 | 分享有效期 |
|
||||||
|
| `scheme_share_permission` | `text` | 否 | 分享权限,如只读 / 可复制 / 可编辑 |
|
||||||
|
| `scheme_share_remark` | `text` | 否 | 分享备注 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
|
## 索引
|
||||||
|
|
||||||
|
| 索引名 | 类型 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `idx_tbl_scheme_share_scheme_share_id` | `UNIQUE INDEX` | 保证 `scheme_share_id` 唯一 |
|
||||||
|
| `idx_tbl_scheme_share_scheme_id` | `INDEX` | 加速按方案查询分享记录 |
|
||||||
|
| `idx_tbl_scheme_share_scheme_share_owner` | `INDEX` | 加速按分享发起人查询 |
|
||||||
|
| `idx_tbl_scheme_share_scheme_share_to` | `INDEX` | 加速按被分享目标查询 |
|
||||||
|
| `idx_tbl_scheme_share_acceptance_status` | `INDEX` | 加速按接收状态过滤 |
|
||||||
|
| `idx_tbl_scheme_share_expires_at` | `INDEX` | 加速按有效期过滤 |
|
||||||
|
| `idx_tbl_scheme_share_owner_to` | `INDEX` | 加速同一发起人向某目标用户的查询 |
|
||||||
|
| `idx_tbl_scheme_share_unique_map` | `UNIQUE INDEX` | 保证同一发起人对同一方案给同一目标的分享记录唯一 |
|
||||||
|
|
||||||
|
## 建议 PocketBase 原生权限规则
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 严格按你的要求,所有删改查都只允许 owner 访问。
|
||||||
|
- 这意味着“被分享人”不能直接通过 PocketBase 原生 records API 读取这张表;如果后续要支持被分享方直连访问,需要另行放宽 `listRule` / `viewRule` 或改由 hooks 中转。
|
||||||
|
|
||||||
|
| Rule | 建议表达式 |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `listRule` | `@request.auth.id != "" && scheme_share_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `viewRule` | `@request.auth.id != "" && scheme_share_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `createRule` | `@request.auth.id != "" && @request.body.scheme_share_owner = @request.auth.openid` |
|
||||||
|
| `updateRule` | `@request.auth.id != "" && scheme_share_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `deleteRule` | `@request.auth.id != "" && scheme_share_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
|
||||||
|
## 补充约定
|
||||||
|
|
||||||
|
- `scheme_share_to` 当前建议保存目标用户 `openid`,这样后续如果要做 hooks 查询或消息通知,字段可直接复用。
|
||||||
|
- 若后续要支持“接收方修改 `scheme_share_acceptance_status`”,则当前 owner-only 原生规则不够,需要追加 hooks 或重新设计 PB 规则。
|
||||||
|
- 推荐把 `scheme_share_permission` 控制在有限枚举内,例如:`readonly`、`copyable`、`editable`。
|
||||||
|
- `is_delete` 用于软删除控制,撤销分享时建议优先标记为 `1`。
|
||||||
|
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
||||||
63
docs/pb_tbl_scheme_template.md
Normal file
63
docs/pb_tbl_scheme_template.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# pb_tbl_scheme_template
|
||||||
|
|
||||||
|
> 来源:用户提供的结构草图(2026-04-08)与现有附件 / 产品字段设计规范
|
||||||
|
> 类型:`base`
|
||||||
|
> 状态:`draft`,仅文档设计,尚未执行建表
|
||||||
|
> 读写规则:任意已登录用户可新增,但仅可访问 `scheme_template_owner = 当前 token 对应 openid` 且 `is_delete = 0` 的记录
|
||||||
|
|
||||||
|
## 表用途
|
||||||
|
|
||||||
|
用于存储方案模板,作为方案配置时可复用的标准模板库,承载图标、标签、方案适配条件以及模板内产品清单。
|
||||||
|
|
||||||
|
## 字段清单
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 必填 | 说明 |
|
||||||
|
| :--- | :--- | :---: | :--- |
|
||||||
|
| `id` | `text` | 是 | PocketBase 记录主键 |
|
||||||
|
| `scheme_template_id` | `text` | 是 | 模板业务 ID,唯一标识 |
|
||||||
|
| `scheme_template_icon` | `text` | 否 | 模板图标,建议保存 `tbl_attachments.attachments_id` |
|
||||||
|
| `scheme_template_label` | `text` | 否 | 模板标签,便于关键词检索 |
|
||||||
|
| `scheme_template_name` | `text` | 是 | 模板名称 |
|
||||||
|
| `scheme_template_owner` | `text` | 是 | 模板所有者 openid,保存 `tbl_auth_users.openid` |
|
||||||
|
| `scheme_template_status` | `text` | 否 | 模板状态,如有效 / 主推 / 过期 |
|
||||||
|
| `scheme_template_solution_type` | `text` | 否 | 适用方案类型 |
|
||||||
|
| `scheme_template_solution_feature` | `text` | 否 | 适用方案特点 |
|
||||||
|
| `scheme_template_product_list` | `json` | 否 | 产品清单,支持保存精简结构或完整产品快照数组 |
|
||||||
|
| `scheme_template_description` | `text` | 否 | 模板说明 |
|
||||||
|
| `scheme_template_remark` | `text` | 否 | 备注 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
|
## 索引
|
||||||
|
|
||||||
|
| 索引名 | 类型 | 说明 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| `idx_tbl_scheme_template_scheme_template_id` | `UNIQUE INDEX` | 保证 `scheme_template_id` 唯一 |
|
||||||
|
| `idx_tbl_scheme_template_scheme_template_owner` | `INDEX` | 加速按模板所有者查询 |
|
||||||
|
| `idx_tbl_scheme_template_scheme_template_name` | `INDEX` | 加速按模板名称检索 |
|
||||||
|
| `idx_tbl_scheme_template_scheme_template_label` | `INDEX` | 加速按模板标签检索 |
|
||||||
|
| `idx_tbl_scheme_template_scheme_template_status` | `INDEX` | 加速按模板状态过滤 |
|
||||||
|
| `idx_tbl_scheme_template_solution_type` | `INDEX` | 加速按适用方案类型过滤 |
|
||||||
|
| `idx_tbl_scheme_template_solution_feature` | `INDEX` | 加速按适用方案特点过滤 |
|
||||||
|
| `idx_tbl_scheme_template_owner_status` | `INDEX` | 加速同一用户下按状态查询 |
|
||||||
|
|
||||||
|
## 建议 PocketBase 原生权限规则
|
||||||
|
|
||||||
|
| Rule | 建议表达式 |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `listRule` | `@request.auth.id != "" && scheme_template_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `viewRule` | `@request.auth.id != "" && scheme_template_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `createRule` | `@request.auth.id != "" && @request.body.scheme_template_owner = @request.auth.openid` |
|
||||||
|
| `updateRule` | `@request.auth.id != "" && scheme_template_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
| `deleteRule` | `@request.auth.id != "" && scheme_template_owner = @request.auth.openid && is_delete = 0` |
|
||||||
|
|
||||||
|
## 补充约定
|
||||||
|
|
||||||
|
- `scheme_template_icon` 建议延续现有项目做法,仅保存 `attachments_id`,真实文件统一走 `tbl_attachments`。
|
||||||
|
- `scheme_template_product_list` 建议使用 `json` 数组。
|
||||||
|
- 兼容两种结构:
|
||||||
|
- 精简结构:`{"product_id":"PROD-xxx","qty":5,"note":"客厅使用"}`
|
||||||
|
- 完整快照结构:直接保存 `tbl_product_list` 导出的整行产品对象,至少应包含 `prod_list_id`,如存在产品型号 / 产品料号也建议一并保留(如 `prod_list_modelnumber`、`prod_list_barcode`)。
|
||||||
|
- 页面 `/manage/scheme` 当前优先通过产品选择器保存完整产品快照,便于后续直接展示产品摘要与排序。
|
||||||
|
- 若模板将来需要“官方模板 + 用户私有模板”并存,则需要额外引入发布状态字段或放宽公共模板读取规则;当前文档严格按 owner 私有模板设计。
|
||||||
|
- `is_delete` 用于软删除控制,模板删除时建议优先标记为 `1`。
|
||||||
|
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
| `dict_word_parent_id` | `text` | 否 | 父级字典业务 ID |
|
| `dict_word_parent_id` | `text` | 否 | 父级字典业务 ID |
|
||||||
| `dict_word_remark` | `text` | 否 | 备注 |
|
| `dict_word_remark` | `text` | 否 | 备注 |
|
||||||
| `dict_word_image` | `text` | 否 | 枚举图片附件 ID 集合,和枚举值一一对应 |
|
| `dict_word_image` | `text` | 否 | 枚举图片附件 ID 集合,和枚举值一一对应 |
|
||||||
|
| `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` |
|
||||||
|
|
||||||
## 索引
|
## 索引
|
||||||
|
|
||||||
@@ -34,5 +35,7 @@
|
|||||||
## 补充约定
|
## 补充约定
|
||||||
|
|
||||||
- 业务返回时,hooks 会把聚合字段转换成 `items[]` 结构,每个元素包含 `enum`、`description`、`image`、`imageUrl`、`sortOrder`。
|
- 业务返回时,hooks 会把聚合字段转换成 `items[]` 结构,每个元素包含 `enum`、`description`、`image`、`imageUrl`、`sortOrder`。
|
||||||
|
- `is_delete` 用于软删除控制,字典废弃时建议优先标记为 `1`。
|
||||||
|
- 集合默认查询规则已内置 `is_delete = 0`,字典列表/详情默认隐藏已软删除项。
|
||||||
- 字典项图片本体统一存放在 `tbl_attachments`,本表只保存 `attachments_id`。
|
- 字典项图片本体统一存放在 `tbl_attachments`,本表只保存 `attachments_id`。
|
||||||
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
|
||||||
|
|||||||
@@ -41,6 +41,16 @@ require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/detail.js`)
|
|||||||
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/create.js`)
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/create.js`)
|
||||||
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/update.js`)
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/update.js`)
|
||||||
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/delete.js`)
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/delete.js`)
|
||||||
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/scheme-template/list.js`)
|
||||||
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/scheme-template/detail.js`)
|
||||||
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/scheme-template/create.js`)
|
||||||
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/scheme-template/update.js`)
|
||||||
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/scheme-template/delete.js`)
|
||||||
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/scheme/list.js`)
|
||||||
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/scheme/detail.js`)
|
||||||
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/scheme/create.js`)
|
||||||
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/scheme/update.js`)
|
||||||
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/scheme/delete.js`)
|
||||||
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart/list.js`)
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart/list.js`)
|
||||||
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart/detail.js`)
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart/detail.js`)
|
||||||
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart/create.js`)
|
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart/create.js`)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ require(`${__hooks}/bai_web_pb_hooks/pages/index.js`)
|
|||||||
require(`${__hooks}/bai_web_pb_hooks/pages/login.js`)
|
require(`${__hooks}/bai_web_pb_hooks/pages/login.js`)
|
||||||
require(`${__hooks}/bai_web_pb_hooks/pages/document-manage.js`)
|
require(`${__hooks}/bai_web_pb_hooks/pages/document-manage.js`)
|
||||||
require(`${__hooks}/bai_web_pb_hooks/pages/product-manage.js`)
|
require(`${__hooks}/bai_web_pb_hooks/pages/product-manage.js`)
|
||||||
|
require(`${__hooks}/bai_web_pb_hooks/pages/scheme-manage.js`)
|
||||||
require(`${__hooks}/bai_web_pb_hooks/pages/dictionary-manage.js`)
|
require(`${__hooks}/bai_web_pb_hooks/pages/dictionary-manage.js`)
|
||||||
require(`${__hooks}/bai_web_pb_hooks/pages/sdk-permission-manage.js`)
|
require(`${__hooks}/bai_web_pb_hooks/pages/sdk-permission-manage.js`)
|
||||||
require(`${__hooks}/bai_web_pb_hooks/pages/cart-order-manage.js`)
|
require(`${__hooks}/bai_web_pb_hooks/pages/cart-order-manage.js`)
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
routerAdd('POST', '/api/scheme-template/create', function (e) {
|
||||||
|
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
|
||||||
|
const schemeService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/schemeService.js`)
|
||||||
|
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
|
||||||
|
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
guards.requireJson(e)
|
||||||
|
const authState = guards.requireManagePlatformUser(e)
|
||||||
|
guards.duplicateGuard(e)
|
||||||
|
|
||||||
|
const payload = guards.validateSchemeTemplateMutationBody(e, false)
|
||||||
|
const data = schemeService.createSchemeTemplate(authState.openid, payload)
|
||||||
|
|
||||||
|
return success(e, '新增方案模板成功', data)
|
||||||
|
} catch (err) {
|
||||||
|
const status = (err && err.statusCode) || (err && err.status) || 400
|
||||||
|
logger.error('新增方案模板失败', {
|
||||||
|
status: status,
|
||||||
|
errMsg: (err && err.message) || '未知错误',
|
||||||
|
data: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
routerAdd('POST', '/api/scheme-template/delete', function (e) {
|
||||||
|
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
|
||||||
|
const schemeService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/schemeService.js`)
|
||||||
|
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
|
||||||
|
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
guards.requireJson(e)
|
||||||
|
const authState = guards.requireManagePlatformUser(e)
|
||||||
|
guards.duplicateGuard(e)
|
||||||
|
|
||||||
|
const payload = guards.validateSchemeTemplateDeleteBody(e)
|
||||||
|
const data = schemeService.deleteSchemeTemplate(authState.openid, payload.scheme_template_id)
|
||||||
|
|
||||||
|
return success(e, '删除方案模板成功', data)
|
||||||
|
} catch (err) {
|
||||||
|
const status = (err && err.statusCode) || (err && err.status) || 400
|
||||||
|
logger.error('删除方案模板失败', {
|
||||||
|
status: status,
|
||||||
|
errMsg: (err && err.message) || '未知错误',
|
||||||
|
data: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
routerAdd('POST', '/api/scheme-template/detail', function (e) {
|
||||||
|
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
|
||||||
|
const schemeService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/schemeService.js`)
|
||||||
|
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
|
||||||
|
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
guards.requireJson(e)
|
||||||
|
guards.requireManagePlatformUser(e)
|
||||||
|
|
||||||
|
const payload = guards.validateSchemeTemplateDetailBody(e)
|
||||||
|
const data = schemeService.getSchemeTemplateDetail(payload.scheme_template_id)
|
||||||
|
|
||||||
|
return success(e, '查询方案模板详情成功', data)
|
||||||
|
} catch (err) {
|
||||||
|
const status = (err && err.statusCode) || (err && err.status) || 400
|
||||||
|
logger.error('查询方案模板详情失败', {
|
||||||
|
status: status,
|
||||||
|
errMsg: (err && err.message) || '未知错误',
|
||||||
|
data: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
routerAdd('POST', '/api/scheme-template/list', function (e) {
|
||||||
|
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
|
||||||
|
const schemeService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/schemeService.js`)
|
||||||
|
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
|
||||||
|
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
guards.requireJson(e)
|
||||||
|
guards.requireManagePlatformUser(e)
|
||||||
|
|
||||||
|
const payload = guards.validateSchemeTemplateListBody(e)
|
||||||
|
const data = schemeService.listSchemeTemplates(payload)
|
||||||
|
|
||||||
|
return success(e, '查询方案模板列表成功', {
|
||||||
|
items: data,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
const status = (err && err.statusCode) || (err && err.status) || 400
|
||||||
|
logger.error('查询方案模板列表失败', {
|
||||||
|
status: status,
|
||||||
|
errMsg: (err && err.message) || '未知错误',
|
||||||
|
data: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
routerAdd('POST', '/api/scheme-template/update', function (e) {
|
||||||
|
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
|
||||||
|
const schemeService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/schemeService.js`)
|
||||||
|
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
|
||||||
|
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
guards.requireJson(e)
|
||||||
|
const authState = guards.requireManagePlatformUser(e)
|
||||||
|
guards.duplicateGuard(e)
|
||||||
|
|
||||||
|
const payload = guards.validateSchemeTemplateMutationBody(e, true)
|
||||||
|
const data = schemeService.updateSchemeTemplate(authState.openid, payload)
|
||||||
|
|
||||||
|
return success(e, '更新方案模板成功', data)
|
||||||
|
} catch (err) {
|
||||||
|
const status = (err && err.statusCode) || (err && err.status) || 400
|
||||||
|
logger.error('更新方案模板失败', {
|
||||||
|
status: status,
|
||||||
|
errMsg: (err && err.message) || '未知错误',
|
||||||
|
data: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
25
pocket-base/bai_api_pb_hooks/bai_api_routes/scheme/create.js
Normal file
25
pocket-base/bai_api_pb_hooks/bai_api_routes/scheme/create.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
routerAdd('POST', '/api/scheme/create', function (e) {
|
||||||
|
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
|
||||||
|
const schemeService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/schemeService.js`)
|
||||||
|
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
|
||||||
|
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
guards.requireJson(e)
|
||||||
|
const authState = guards.requireManagePlatformUser(e)
|
||||||
|
guards.duplicateGuard(e)
|
||||||
|
|
||||||
|
const payload = guards.validateSchemeMutationBody(e, false)
|
||||||
|
const data = schemeService.createScheme(authState.openid, payload)
|
||||||
|
|
||||||
|
return success(e, '新增方案成功', data)
|
||||||
|
} catch (err) {
|
||||||
|
const status = (err && err.statusCode) || (err && err.status) || 400
|
||||||
|
logger.error('新增方案失败', {
|
||||||
|
status: status,
|
||||||
|
errMsg: (err && err.message) || '未知错误',
|
||||||
|
data: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
25
pocket-base/bai_api_pb_hooks/bai_api_routes/scheme/delete.js
Normal file
25
pocket-base/bai_api_pb_hooks/bai_api_routes/scheme/delete.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
routerAdd('POST', '/api/scheme/delete', function (e) {
|
||||||
|
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
|
||||||
|
const schemeService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/schemeService.js`)
|
||||||
|
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
|
||||||
|
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
guards.requireJson(e)
|
||||||
|
const authState = guards.requireManagePlatformUser(e)
|
||||||
|
guards.duplicateGuard(e)
|
||||||
|
|
||||||
|
const payload = guards.validateSchemeDeleteBody(e)
|
||||||
|
const data = schemeService.deleteScheme(authState.openid, payload.scheme_id)
|
||||||
|
|
||||||
|
return success(e, '删除方案成功', data)
|
||||||
|
} catch (err) {
|
||||||
|
const status = (err && err.statusCode) || (err && err.status) || 400
|
||||||
|
logger.error('删除方案失败', {
|
||||||
|
status: status,
|
||||||
|
errMsg: (err && err.message) || '未知错误',
|
||||||
|
data: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
24
pocket-base/bai_api_pb_hooks/bai_api_routes/scheme/detail.js
Normal file
24
pocket-base/bai_api_pb_hooks/bai_api_routes/scheme/detail.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
routerAdd('POST', '/api/scheme/detail', function (e) {
|
||||||
|
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
|
||||||
|
const schemeService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/schemeService.js`)
|
||||||
|
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
|
||||||
|
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
guards.requireJson(e)
|
||||||
|
guards.requireManagePlatformUser(e)
|
||||||
|
|
||||||
|
const payload = guards.validateSchemeDetailBody(e)
|
||||||
|
const data = schemeService.getSchemeDetail(payload.scheme_id)
|
||||||
|
|
||||||
|
return success(e, '查询方案详情成功', data)
|
||||||
|
} catch (err) {
|
||||||
|
const status = (err && err.statusCode) || (err && err.status) || 400
|
||||||
|
logger.error('查询方案详情失败', {
|
||||||
|
status: status,
|
||||||
|
errMsg: (err && err.message) || '未知错误',
|
||||||
|
data: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
26
pocket-base/bai_api_pb_hooks/bai_api_routes/scheme/list.js
Normal file
26
pocket-base/bai_api_pb_hooks/bai_api_routes/scheme/list.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
routerAdd('POST', '/api/scheme/list', function (e) {
|
||||||
|
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
|
||||||
|
const schemeService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/schemeService.js`)
|
||||||
|
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
|
||||||
|
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
guards.requireJson(e)
|
||||||
|
guards.requireManagePlatformUser(e)
|
||||||
|
|
||||||
|
const payload = guards.validateSchemeListBody(e)
|
||||||
|
const data = schemeService.listSchemes(payload)
|
||||||
|
|
||||||
|
return success(e, '查询方案列表成功', {
|
||||||
|
items: data,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
const status = (err && err.statusCode) || (err && err.status) || 400
|
||||||
|
logger.error('查询方案列表失败', {
|
||||||
|
status: status,
|
||||||
|
errMsg: (err && err.message) || '未知错误',
|
||||||
|
data: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
25
pocket-base/bai_api_pb_hooks/bai_api_routes/scheme/update.js
Normal file
25
pocket-base/bai_api_pb_hooks/bai_api_routes/scheme/update.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
routerAdd('POST', '/api/scheme/update', function (e) {
|
||||||
|
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
|
||||||
|
const schemeService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/schemeService.js`)
|
||||||
|
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
|
||||||
|
const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
guards.requireJson(e)
|
||||||
|
const authState = guards.requireManagePlatformUser(e)
|
||||||
|
guards.duplicateGuard(e)
|
||||||
|
|
||||||
|
const payload = guards.validateSchemeMutationBody(e, true)
|
||||||
|
const data = schemeService.updateScheme(authState.openid, payload)
|
||||||
|
|
||||||
|
return success(e, '更新方案成功', data)
|
||||||
|
} catch (err) {
|
||||||
|
const status = (err && err.statusCode) || (err && err.status) || 400
|
||||||
|
logger.error('更新方案失败', {
|
||||||
|
status: status,
|
||||||
|
errMsg: (err && err.message) || '未知错误',
|
||||||
|
data: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -521,9 +521,11 @@ function validateProductMutationBody(e, isUpdate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: payload.id || '',
|
||||||
prod_list_id: payload.prod_list_id || '',
|
prod_list_id: payload.prod_list_id || '',
|
||||||
prod_list_name: payload.prod_list_name || '',
|
prod_list_name: payload.prod_list_name || '',
|
||||||
prod_list_modelnumber: payload.prod_list_modelnumber || '',
|
prod_list_modelnumber: payload.prod_list_modelnumber || '',
|
||||||
|
prod_list_barcode: payload.prod_list_barcode || '',
|
||||||
prod_list_icon: normalizeAttachmentIdList(payload.prod_list_icon, 'prod_list_icon').join('|'),
|
prod_list_icon: normalizeAttachmentIdList(payload.prod_list_icon, 'prod_list_icon').join('|'),
|
||||||
prod_list_description: payload.prod_list_description || '',
|
prod_list_description: payload.prod_list_description || '',
|
||||||
prod_list_feature: payload.prod_list_feature || '',
|
prod_list_feature: payload.prod_list_feature || '',
|
||||||
@@ -547,6 +549,126 @@ function validateProductDeleteBody(e) {
|
|||||||
return validateProductDetailBody(e)
|
return validateProductDetailBody(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateSchemeTemplateListBody(e) {
|
||||||
|
const payload = parseBody(e)
|
||||||
|
|
||||||
|
return {
|
||||||
|
keyword: payload.keyword || '',
|
||||||
|
scheme_template_owner: payload.scheme_template_owner || '',
|
||||||
|
scheme_template_status: payload.scheme_template_status || '',
|
||||||
|
scheme_template_solution_type: payload.scheme_template_solution_type || '',
|
||||||
|
scheme_template_solution_feature: payload.scheme_template_solution_feature || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSchemeTemplateDetailBody(e) {
|
||||||
|
const payload = parseBody(e)
|
||||||
|
if (!payload.scheme_template_id) {
|
||||||
|
throw createAppError(400, 'scheme_template_id 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scheme_template_id: String(payload.scheme_template_id || '').trim(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSchemeTemplateMutationBody(e, isUpdate) {
|
||||||
|
const payload = parseBody(e)
|
||||||
|
|
||||||
|
if (isUpdate && !payload.scheme_template_id) {
|
||||||
|
throw createAppError(400, 'scheme_template_id 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payload.scheme_template_name) {
|
||||||
|
throw createAppError(400, 'scheme_template_name 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payload.scheme_template_owner) {
|
||||||
|
throw createAppError(400, 'scheme_template_owner 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scheme_template_id: payload.scheme_template_id || '',
|
||||||
|
scheme_template_icon: Object.prototype.hasOwnProperty.call(payload, 'scheme_template_icon') ? payload.scheme_template_icon : '',
|
||||||
|
scheme_template_label: payload.scheme_template_label || '',
|
||||||
|
scheme_template_name: payload.scheme_template_name || '',
|
||||||
|
scheme_template_owner: payload.scheme_template_owner || '',
|
||||||
|
scheme_template_status: payload.scheme_template_status || '',
|
||||||
|
scheme_template_solution_type: payload.scheme_template_solution_type || '',
|
||||||
|
scheme_template_solution_feature: payload.scheme_template_solution_feature || '',
|
||||||
|
scheme_template_product_list: Object.prototype.hasOwnProperty.call(payload, 'scheme_template_product_list') ? payload.scheme_template_product_list : [],
|
||||||
|
scheme_template_description: payload.scheme_template_description || '',
|
||||||
|
scheme_template_remark: payload.scheme_template_remark || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSchemeTemplateDeleteBody(e) {
|
||||||
|
return validateSchemeTemplateDetailBody(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSchemeListBody(e) {
|
||||||
|
const payload = parseBody(e)
|
||||||
|
|
||||||
|
return {
|
||||||
|
keyword: payload.keyword || '',
|
||||||
|
scheme_owner: payload.scheme_owner || '',
|
||||||
|
scheme_status: payload.scheme_status || '',
|
||||||
|
scheme_share_status: payload.scheme_share_status || '',
|
||||||
|
scheme_hotel_type: payload.scheme_hotel_type || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSchemeDetailBody(e) {
|
||||||
|
const payload = parseBody(e)
|
||||||
|
if (!payload.scheme_id) {
|
||||||
|
throw createAppError(400, 'scheme_id 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scheme_id: String(payload.scheme_id || '').trim(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSchemeMutationBody(e, isUpdate) {
|
||||||
|
const payload = parseBody(e)
|
||||||
|
|
||||||
|
if (isUpdate && !payload.scheme_id) {
|
||||||
|
throw createAppError(400, 'scheme_id 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payload.scheme_name) {
|
||||||
|
throw createAppError(400, 'scheme_name 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payload.scheme_owner) {
|
||||||
|
throw createAppError(400, 'scheme_owner 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
scheme_id: payload.scheme_id || '',
|
||||||
|
scheme_name: payload.scheme_name || '',
|
||||||
|
scheme_owner: payload.scheme_owner || '',
|
||||||
|
scheme_share_status: payload.scheme_share_status || '',
|
||||||
|
scheme_expires_at: payload.scheme_expires_at || '',
|
||||||
|
scheme_hotel_type: payload.scheme_hotel_type || '',
|
||||||
|
scheme_solution_type: payload.scheme_solution_type || '',
|
||||||
|
scheme_solution_feature: payload.scheme_solution_feature || '',
|
||||||
|
scheme_room_type: Object.prototype.hasOwnProperty.call(payload, 'scheme_room_type') ? payload.scheme_room_type : [],
|
||||||
|
scheme_curtains: payload.scheme_curtains || '',
|
||||||
|
scheme_voice_device: payload.scheme_voice_device || '',
|
||||||
|
scheme_ac_type: payload.scheme_ac_type || '',
|
||||||
|
scheme_template_highend: payload.scheme_template_highend || '',
|
||||||
|
scheme_template_midend: payload.scheme_template_midend || '',
|
||||||
|
scheme_template_lowend: payload.scheme_template_lowend || '',
|
||||||
|
scheme_status: payload.scheme_status || '',
|
||||||
|
scheme_remark: payload.scheme_remark || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSchemeDeleteBody(e) {
|
||||||
|
return validateSchemeDetailBody(e)
|
||||||
|
}
|
||||||
|
|
||||||
function validateCartListBody(e) {
|
function validateCartListBody(e) {
|
||||||
const payload = parseBody(e)
|
const payload = parseBody(e)
|
||||||
|
|
||||||
@@ -577,7 +699,7 @@ function validateCartMutationBody(e, isUpdate) {
|
|||||||
|
|
||||||
if (!isUpdate) {
|
if (!isUpdate) {
|
||||||
if (!payload.cart_product_id) {
|
if (!payload.cart_product_id) {
|
||||||
throw createAppError(400, 'cart_product_id 为必填项')
|
throw createAppError(400, 'cart_product_id 为必填项,且必须传 tbl_product_list 的 recordId')
|
||||||
}
|
}
|
||||||
if (typeof payload.cart_product_quantity === 'undefined') {
|
if (typeof payload.cart_product_quantity === 'undefined') {
|
||||||
throw createAppError(400, 'cart_product_quantity 为必填项')
|
throw createAppError(400, 'cart_product_quantity 为必填项')
|
||||||
@@ -873,6 +995,14 @@ module.exports = {
|
|||||||
validateProductDetailBody,
|
validateProductDetailBody,
|
||||||
validateProductMutationBody,
|
validateProductMutationBody,
|
||||||
validateProductDeleteBody,
|
validateProductDeleteBody,
|
||||||
|
validateSchemeTemplateListBody,
|
||||||
|
validateSchemeTemplateDetailBody,
|
||||||
|
validateSchemeTemplateMutationBody,
|
||||||
|
validateSchemeTemplateDeleteBody,
|
||||||
|
validateSchemeListBody,
|
||||||
|
validateSchemeDetailBody,
|
||||||
|
validateSchemeMutationBody,
|
||||||
|
validateSchemeDeleteBody,
|
||||||
validateCartListBody,
|
validateCartListBody,
|
||||||
validateCartDetailBody,
|
validateCartDetailBody,
|
||||||
validateCartMutationBody,
|
validateCartMutationBody,
|
||||||
|
|||||||
@@ -208,12 +208,26 @@ function findProductRecordByBusinessId(productId) {
|
|||||||
return records.length ? records[0] : null
|
return records.length ? records[0] : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findProductRecordByRecordId(recordId) {
|
||||||
|
const targetId = normalizeText(recordId)
|
||||||
|
if (!targetId) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $app.findRecordById('tbl_product_list', targetId)
|
||||||
|
} catch (_error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildProductInfo(record) {
|
function buildProductInfo(record) {
|
||||||
if (!record) {
|
if (!record) {
|
||||||
return {
|
return {
|
||||||
prod_list_id: '',
|
prod_list_id: '',
|
||||||
prod_list_name: '',
|
prod_list_name: '',
|
||||||
prod_list_modelnumber: '',
|
prod_list_modelnumber: '',
|
||||||
|
prod_list_barcode: '',
|
||||||
prod_list_basic_price: null,
|
prod_list_basic_price: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,6 +236,7 @@ function buildProductInfo(record) {
|
|||||||
prod_list_id: record.getString('prod_list_id'),
|
prod_list_id: record.getString('prod_list_id'),
|
||||||
prod_list_name: record.getString('prod_list_name'),
|
prod_list_name: record.getString('prod_list_name'),
|
||||||
prod_list_modelnumber: record.getString('prod_list_modelnumber'),
|
prod_list_modelnumber: record.getString('prod_list_modelnumber'),
|
||||||
|
prod_list_barcode: record.getString('prod_list_barcode'),
|
||||||
prod_list_basic_price: record.get('prod_list_basic_price'),
|
prod_list_basic_price: record.get('prod_list_basic_price'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,9 +247,9 @@ function ensureProductExists(productId) {
|
|||||||
throw createAppError(400, 'cart_product_id 为必填项')
|
throw createAppError(400, 'cart_product_id 为必填项')
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = findProductRecordByBusinessId(targetId)
|
const record = findProductRecordByRecordId(targetId)
|
||||||
if (!record) {
|
if (!record) {
|
||||||
throw createAppError(400, 'cart_product_id 对应产品不存在:' + targetId)
|
throw createAppError(400, 'cart_product_id 必须为 tbl_product_list 的 recordId,且对应产品必须存在:' + targetId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return record
|
return record
|
||||||
@@ -264,7 +279,9 @@ function ensureRecordOwner(record, fieldName, authOpenid, resourceLabel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function exportCartRecord(record, productRecord) {
|
function exportCartRecord(record, productRecord) {
|
||||||
const productInfo = buildProductInfo(productRecord || findProductRecordByBusinessId(record.getString('cart_product_id')))
|
const relationValue = record.getString('cart_product_id')
|
||||||
|
const productSource = productRecord || findProductRecordByRecordId(relationValue)
|
||||||
|
const productInfo = buildProductInfo(productSource)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pb_id: record.id,
|
pb_id: record.id,
|
||||||
@@ -272,13 +289,15 @@ function exportCartRecord(record, productRecord) {
|
|||||||
cart_number: record.getString('cart_number'),
|
cart_number: record.getString('cart_number'),
|
||||||
cart_create: String(record.get('cart_create') || ''),
|
cart_create: String(record.get('cart_create') || ''),
|
||||||
cart_owner: record.getString('cart_owner'),
|
cart_owner: record.getString('cart_owner'),
|
||||||
cart_product_id: record.getString('cart_product_id'),
|
cart_product_id: productSource ? productSource.id : relationValue,
|
||||||
|
cart_product_business_id: productInfo.prod_list_id,
|
||||||
cart_product_quantity: Number(record.get('cart_product_quantity') || 0),
|
cart_product_quantity: Number(record.get('cart_product_quantity') || 0),
|
||||||
cart_status: record.getString('cart_status'),
|
cart_status: record.getString('cart_status'),
|
||||||
cart_at_price: Number(record.get('cart_at_price') || 0),
|
cart_at_price: Number(record.get('cart_at_price') || 0),
|
||||||
cart_remark: record.getString('cart_remark'),
|
cart_remark: record.getString('cart_remark'),
|
||||||
product_name: productInfo.prod_list_name,
|
product_name: productInfo.prod_list_name,
|
||||||
product_modelnumber: productInfo.prod_list_modelnumber,
|
product_modelnumber: productInfo.prod_list_modelnumber,
|
||||||
|
product_barcode: productInfo.prod_list_barcode,
|
||||||
product_basic_price: productInfo.prod_list_basic_price,
|
product_basic_price: productInfo.prod_list_basic_price,
|
||||||
created: String(record.created || ''),
|
created: String(record.created || ''),
|
||||||
updated: String(record.updated || ''),
|
updated: String(record.updated || ''),
|
||||||
@@ -304,47 +323,55 @@ function exportOrderRecord(record) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function exportAdminCartRecord(record) {
|
function exportAdminCartRecord(record) {
|
||||||
const productInfo = buildProductInfo(findProductRecordByBusinessId(record.cart_product_id))
|
const productId = record && typeof record.getString === 'function'
|
||||||
|
? record.getString('cart_product_id')
|
||||||
|
: String(record && record.cart_product_id || '')
|
||||||
|
const productSource = findProductRecordByRecordId(productId)
|
||||||
|
const productInfo = buildProductInfo(productSource)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pb_id: String(record.id || ''),
|
pb_id: String(record && record.id || ''),
|
||||||
cart_id: String(record.cart_id || ''),
|
cart_id: record && typeof record.getString === 'function' ? record.getString('cart_id') : String(record && record.cart_id || ''),
|
||||||
cart_number: String(record.cart_number || ''),
|
cart_number: record && typeof record.getString === 'function' ? record.getString('cart_number') : String(record && record.cart_number || ''),
|
||||||
cart_create: String(record.cart_create || ''),
|
cart_create: record && typeof record.get === 'function' ? String(record.get('cart_create') || '') : String(record && record.cart_create || ''),
|
||||||
cart_owner: String(record.cart_owner || ''),
|
cart_owner: record && typeof record.getString === 'function' ? record.getString('cart_owner') : String(record && record.cart_owner || ''),
|
||||||
cart_product_id: String(record.cart_product_id || ''),
|
cart_product_id: productSource ? productSource.id : productId,
|
||||||
cart_product_quantity: Number(record.cart_product_quantity || 0),
|
cart_product_business_id: productInfo.prod_list_id,
|
||||||
cart_status: String(record.cart_status || ''),
|
cart_product_quantity: record && typeof record.get === 'function' ? Number(record.get('cart_product_quantity') || 0) : Number(record && record.cart_product_quantity || 0),
|
||||||
cart_at_price: Number(record.cart_at_price || 0),
|
cart_status: record && typeof record.getString === 'function' ? record.getString('cart_status') : String(record && record.cart_status || ''),
|
||||||
cart_remark: String(record.cart_remark || ''),
|
cart_at_price: record && typeof record.get === 'function' ? Number(record.get('cart_at_price') || 0) : Number(record && record.cart_at_price || 0),
|
||||||
|
cart_remark: record && typeof record.getString === 'function' ? record.getString('cart_remark') : String(record && record.cart_remark || ''),
|
||||||
product_name: productInfo.prod_list_name,
|
product_name: productInfo.prod_list_name,
|
||||||
product_modelnumber: productInfo.prod_list_modelnumber,
|
product_modelnumber: productInfo.prod_list_modelnumber,
|
||||||
|
product_barcode: productInfo.prod_list_barcode,
|
||||||
product_basic_price: productInfo.prod_list_basic_price,
|
product_basic_price: productInfo.prod_list_basic_price,
|
||||||
created: String(record.created || ''),
|
created: String(record && record.created || ''),
|
||||||
updated: String(record.updated || ''),
|
updated: String(record && record.updated || ''),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportAdminOrderRecord(record) {
|
function exportAdminOrderRecord(record) {
|
||||||
return {
|
return {
|
||||||
pb_id: String(record.id || ''),
|
pb_id: String(record && record.id || ''),
|
||||||
order_id: String(record.order_id || ''),
|
order_id: record && typeof record.getString === 'function' ? record.getString('order_id') : String(record && record.order_id || ''),
|
||||||
order_number: String(record.order_number || ''),
|
order_number: record && typeof record.getString === 'function' ? record.getString('order_number') : String(record && record.order_number || ''),
|
||||||
order_create: String(record.order_create || ''),
|
order_create: record && typeof record.get === 'function' ? String(record.get('order_create') || '') : String(record && record.order_create || ''),
|
||||||
order_owner: String(record.order_owner || ''),
|
order_owner: record && typeof record.getString === 'function' ? record.getString('order_owner') : String(record && record.order_owner || ''),
|
||||||
order_source: String(record.order_source || ''),
|
order_source: record && typeof record.getString === 'function' ? record.getString('order_source') : String(record && record.order_source || ''),
|
||||||
order_status: String(record.order_status || ''),
|
order_status: record && typeof record.getString === 'function' ? record.getString('order_status') : String(record && record.order_status || ''),
|
||||||
order_source_id: String(record.order_source_id || ''),
|
order_source_id: record && typeof record.getString === 'function' ? record.getString('order_source_id') : String(record && record.order_source_id || ''),
|
||||||
order_snap: parseJsonFieldForOutput(record.order_snap),
|
order_snap: record && typeof record.get === 'function' ? parseJsonFieldForOutput(record.get('order_snap')) : parseJsonFieldForOutput(record && record.order_snap),
|
||||||
order_amount: Number(record.order_amount || 0),
|
order_amount: record && typeof record.get === 'function' ? Number(record.get('order_amount') || 0) : Number(record && record.order_amount || 0),
|
||||||
order_remark: String(record.order_remark || ''),
|
order_remark: record && typeof record.getString === 'function' ? record.getString('order_remark') : String(record && record.order_remark || ''),
|
||||||
created: String(record.created || ''),
|
created: String(record && record.created || ''),
|
||||||
updated: String(record.updated || ''),
|
updated: String(record && record.updated || ''),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportAdminManageUser(userRecord, groupedCarts, groupedOrders) {
|
function exportAdminManageUser(userRecord, groupedCarts, groupedOrders) {
|
||||||
const openid = String(userRecord.openid || '')
|
const openid = userRecord && typeof userRecord.getString === 'function'
|
||||||
|
? userRecord.getString('openid')
|
||||||
|
: String(userRecord && userRecord.openid || '')
|
||||||
const carts = groupedCarts[openid] || []
|
const carts = groupedCarts[openid] || []
|
||||||
const orders = groupedOrders[openid] || []
|
const orders = groupedOrders[openid] || []
|
||||||
let cartTotalQuantity = 0
|
let cartTotalQuantity = 0
|
||||||
@@ -358,40 +385,44 @@ function exportAdminManageUser(userRecord, groupedCarts, groupedOrders) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pb_id: String(userRecord.id || ''),
|
pb_id: String(userRecord && userRecord.id || ''),
|
||||||
openid: openid,
|
openid: openid,
|
||||||
users_id: String(userRecord.users_id || ''),
|
users_id: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_id') : String(userRecord && userRecord.users_id || ''),
|
||||||
users_name: String(userRecord.users_name || ''),
|
users_name: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_name') : String(userRecord && userRecord.users_name || ''),
|
||||||
users_phone: String(userRecord.users_phone || ''),
|
users_phone: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_phone') : String(userRecord && userRecord.users_phone || ''),
|
||||||
users_level: String(userRecord.users_level || ''),
|
users_level: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_level') : String(userRecord && userRecord.users_level || ''),
|
||||||
users_level_name: userService.resolveUserLevelName(String(userRecord.users_level || '')),
|
users_level_name: userService.resolveUserLevelName(userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_level') : String(userRecord && userRecord.users_level || '')),
|
||||||
users_type: String(userRecord.users_type || ''),
|
users_type: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_type') : String(userRecord && userRecord.users_type || ''),
|
||||||
users_idtype: String(userRecord.users_idtype || ''),
|
users_idtype: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_idtype') : String(userRecord && userRecord.users_idtype || ''),
|
||||||
users_id_number: String(userRecord.users_id_number || ''),
|
users_id_number: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_id_number') : String(userRecord && userRecord.users_id_number || ''),
|
||||||
users_status: String(userRecord.users_status || ''),
|
users_status: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_status') : String(userRecord && userRecord.users_status || ''),
|
||||||
users_rank_level: userRecord.users_rank_level === null || typeof userRecord.users_rank_level === 'undefined'
|
users_rank_level: !userRecord || typeof userRecord.get !== 'function'
|
||||||
|
? (userRecord && (userRecord.users_rank_level === null || typeof userRecord.users_rank_level === 'undefined') ? null : Number(userRecord && userRecord.users_rank_level))
|
||||||
|
: (userRecord.get('users_rank_level') === null || typeof userRecord.get('users_rank_level') === 'undefined')
|
||||||
? null
|
? null
|
||||||
: Number(userRecord.users_rank_level),
|
: Number(userRecord.get('users_rank_level')),
|
||||||
users_auth_type: userRecord.users_auth_type === null || typeof userRecord.users_auth_type === 'undefined'
|
users_auth_type: !userRecord || typeof userRecord.get !== 'function'
|
||||||
|
? (userRecord && (userRecord.users_auth_type === null || typeof userRecord.users_auth_type === 'undefined') ? null : Number(userRecord && userRecord.users_auth_type))
|
||||||
|
: (userRecord.get('users_auth_type') === null || typeof userRecord.get('users_auth_type') === 'undefined')
|
||||||
? null
|
? null
|
||||||
: Number(userRecord.users_auth_type),
|
: Number(userRecord.get('users_auth_type')),
|
||||||
users_tag: String(userRecord.users_tag || ''),
|
users_tag: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_tag') : String(userRecord && userRecord.users_tag || ''),
|
||||||
company_id: String(userRecord.company_id || ''),
|
company_id: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('company_id') : String(userRecord && userRecord.company_id || ''),
|
||||||
users_parent_id: String(userRecord.users_parent_id || ''),
|
users_parent_id: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_parent_id') : String(userRecord && userRecord.users_parent_id || ''),
|
||||||
users_promo_code: String(userRecord.users_promo_code || ''),
|
users_promo_code: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_promo_code') : String(userRecord && userRecord.users_promo_code || ''),
|
||||||
usergroups_id: String(userRecord.usergroups_id || ''),
|
usergroups_id: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('usergroups_id') : String(userRecord && userRecord.usergroups_id || ''),
|
||||||
users_picture: String(userRecord.users_picture || ''),
|
users_picture: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_picture') : String(userRecord && userRecord.users_picture || ''),
|
||||||
users_id_pic_a: String(userRecord.users_id_pic_a || ''),
|
users_id_pic_a: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_id_pic_a') : String(userRecord && userRecord.users_id_pic_a || ''),
|
||||||
users_id_pic_b: String(userRecord.users_id_pic_b || ''),
|
users_id_pic_b: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_id_pic_b') : String(userRecord && userRecord.users_id_pic_b || ''),
|
||||||
users_title_picture: String(userRecord.users_title_picture || ''),
|
users_title_picture: userRecord && typeof userRecord.getString === 'function' ? userRecord.getString('users_title_picture') : String(userRecord && userRecord.users_title_picture || ''),
|
||||||
cart_count: carts.length,
|
cart_count: carts.length,
|
||||||
cart_total_quantity: cartTotalQuantity,
|
cart_total_quantity: cartTotalQuantity,
|
||||||
order_count: orders.length,
|
order_count: orders.length,
|
||||||
order_total_amount: orderTotalAmount,
|
order_total_amount: orderTotalAmount,
|
||||||
carts: carts,
|
carts: carts,
|
||||||
orders: orders,
|
orders: orders,
|
||||||
created: String(userRecord.created || ''),
|
created: String(userRecord && userRecord.created || ''),
|
||||||
updated: String(userRecord.updated || ''),
|
updated: String(userRecord && userRecord.updated || ''),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,6 +441,7 @@ function listCarts(authOpenid, payload) {
|
|||||||
|| item.cart_id.toLowerCase().indexOf(keyword) !== -1
|
|| item.cart_id.toLowerCase().indexOf(keyword) !== -1
|
||||||
|| item.cart_number.toLowerCase().indexOf(keyword) !== -1
|
|| item.cart_number.toLowerCase().indexOf(keyword) !== -1
|
||||||
|| item.cart_product_id.toLowerCase().indexOf(keyword) !== -1
|
|| item.cart_product_id.toLowerCase().indexOf(keyword) !== -1
|
||||||
|
|| normalizeText(item.cart_product_business_id).toLowerCase().indexOf(keyword) !== -1
|
||||||
|| normalizeText(item.product_name).toLowerCase().indexOf(keyword) !== -1
|
|| normalizeText(item.product_name).toLowerCase().indexOf(keyword) !== -1
|
||||||
const matchedStatus = !cartStatus || item.cart_status === cartStatus
|
const matchedStatus = !cartStatus || item.cart_status === cartStatus
|
||||||
const matchedNumber = !cartNumber || item.cart_number === cartNumber
|
const matchedNumber = !cartNumber || item.cart_number === cartNumber
|
||||||
@@ -440,7 +472,7 @@ function createCart(authState, payload) {
|
|||||||
record.set('cart_id', buildBusinessId('CART'))
|
record.set('cart_id', buildBusinessId('CART'))
|
||||||
record.set('cart_number', normalizeText(payload.cart_number) || buildDisplayNumber('CART', authState.authRecord, authState.openid))
|
record.set('cart_number', normalizeText(payload.cart_number) || buildDisplayNumber('CART', authState.authRecord, authState.openid))
|
||||||
record.set('cart_owner', authState.openid)
|
record.set('cart_owner', authState.openid)
|
||||||
record.set('cart_product_id', productRecord.getString('prod_list_id'))
|
record.set('cart_product_id', productRecord.id)
|
||||||
record.set('cart_product_quantity', normalizePositiveIntegerValue(payload.cart_product_quantity, 'cart_product_quantity'))
|
record.set('cart_product_quantity', normalizePositiveIntegerValue(payload.cart_product_quantity, 'cart_product_quantity'))
|
||||||
record.set('cart_status', normalizeCartStatus(payload.cart_status))
|
record.set('cart_status', normalizeCartStatus(payload.cart_status))
|
||||||
record.set('cart_at_price', normalizeNumberValue(payload.cart_at_price, 'cart_at_price'))
|
record.set('cart_at_price', normalizeNumberValue(payload.cart_at_price, 'cart_at_price'))
|
||||||
@@ -477,7 +509,7 @@ function updateCart(authOpenid, payload) {
|
|||||||
}
|
}
|
||||||
if (typeof payload.cart_product_id !== 'undefined') {
|
if (typeof payload.cart_product_id !== 'undefined') {
|
||||||
productRecord = ensureProductExists(payload.cart_product_id)
|
productRecord = ensureProductExists(payload.cart_product_id)
|
||||||
record.set('cart_product_id', productRecord.getString('prod_list_id'))
|
record.set('cart_product_id', productRecord.id)
|
||||||
}
|
}
|
||||||
if (typeof payload.cart_product_quantity !== 'undefined') {
|
if (typeof payload.cart_product_quantity !== 'undefined') {
|
||||||
record.set('cart_product_quantity', normalizePositiveIntegerValue(payload.cart_product_quantity, 'cart_product_quantity'))
|
record.set('cart_product_quantity', normalizePositiveIntegerValue(payload.cart_product_quantity, 'cart_product_quantity'))
|
||||||
@@ -728,7 +760,9 @@ function listManageUsersCartOrders(payload) {
|
|||||||
const groupedOrders = {}
|
const groupedOrders = {}
|
||||||
|
|
||||||
for (let i = 0; i < cartRecords.length; i += 1) {
|
for (let i = 0; i < cartRecords.length; i += 1) {
|
||||||
const owner = String(cartRecords[i].cart_owner || '')
|
const owner = cartRecords[i] && typeof cartRecords[i].getString === 'function'
|
||||||
|
? cartRecords[i].getString('cart_owner')
|
||||||
|
: String(cartRecords[i] && cartRecords[i].cart_owner || '')
|
||||||
if (!groupedCarts[owner]) {
|
if (!groupedCarts[owner]) {
|
||||||
groupedCarts[owner] = []
|
groupedCarts[owner] = []
|
||||||
}
|
}
|
||||||
@@ -736,7 +770,9 @@ function listManageUsersCartOrders(payload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < orderRecords.length; i += 1) {
|
for (let i = 0; i < orderRecords.length; i += 1) {
|
||||||
const owner = String(orderRecords[i].order_owner || '')
|
const owner = orderRecords[i] && typeof orderRecords[i].getString === 'function'
|
||||||
|
? orderRecords[i].getString('order_owner')
|
||||||
|
: String(orderRecords[i] && orderRecords[i].order_owner || '')
|
||||||
if (!groupedOrders[owner]) {
|
if (!groupedOrders[owner]) {
|
||||||
groupedOrders[owner] = []
|
groupedOrders[owner] = []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,6 +171,39 @@ function findAttachmentRecordByAttachmentId(attachmentId) {
|
|||||||
return records.length ? records[0] : null
|
return records.length ? records[0] : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fetchAllRecordsByFilter(collectionName, filter, sort, params) {
|
||||||
|
const batchSize = 200
|
||||||
|
const result = []
|
||||||
|
let offset = 0
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const batch = $app.findRecordsByFilter(
|
||||||
|
collectionName,
|
||||||
|
filter || '',
|
||||||
|
sort || '',
|
||||||
|
batchSize,
|
||||||
|
offset,
|
||||||
|
params || {}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!batch.length) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < batch.length; i += 1) {
|
||||||
|
result.push(batch[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batch.length < batchSize) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += batch.length
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
function resolveAttachmentList(value) {
|
function resolveAttachmentList(value) {
|
||||||
const ids = parseAttachmentIdList(value)
|
const ids = parseAttachmentIdList(value)
|
||||||
const attachments = []
|
const attachments = []
|
||||||
@@ -402,7 +435,7 @@ function deleteAttachment(attachmentId) {
|
|||||||
throw createAppError(404, '未找到待删除的附件')
|
throw createAppError(404, '未找到待删除的附件')
|
||||||
}
|
}
|
||||||
|
|
||||||
const documentRecords = $app.findRecordsByFilter('tbl_document', '', '', 500, 0)
|
const documentRecords = fetchAllRecordsByFilter('tbl_document', '', '')
|
||||||
for (let i = 0; i < documentRecords.length; i += 1) {
|
for (let i = 0; i < documentRecords.length; i += 1) {
|
||||||
const current = documentRecords[i]
|
const current = documentRecords[i]
|
||||||
const imageIds = parseAttachmentIdList(current.getString('document_image'))
|
const imageIds = parseAttachmentIdList(current.getString('document_image'))
|
||||||
@@ -434,7 +467,7 @@ function deleteAttachment(attachmentId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listDocuments(payload) {
|
function listDocuments(payload) {
|
||||||
const allRecords = $app.findRecordsByFilter('tbl_document', '', '', 500, 0)
|
const allRecords = fetchAllRecordsByFilter('tbl_document', '', '')
|
||||||
const titleKeyword = String(payload.title_keyword || '').toLowerCase().trim()
|
const titleKeyword = String(payload.title_keyword || '').toLowerCase().trim()
|
||||||
const status = String(payload.status || '')
|
const status = String(payload.status || '')
|
||||||
const type = String(payload.document_type || '')
|
const type = String(payload.document_type || '')
|
||||||
|
|||||||
@@ -9,10 +9,57 @@ function buildBusinessId(prefix) {
|
|||||||
return prefix + '-' + new Date().getTime() + '-' + $security.randomString(6)
|
return prefix + '-' + new Date().getTime() + '-' + $security.randomString(6)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildPocketBaseRecordId(raw) {
|
||||||
|
const normalized = normalizeText(raw).toLowerCase()
|
||||||
|
if (/^[a-z0-9]{15}$/.test(normalized)) {
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallback = String($security.randomString(15) || '').toLowerCase().replace(/[^a-z0-9]/g, 'a')
|
||||||
|
if (fallback.length >= 15) {
|
||||||
|
return fallback.slice(0, 15)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (fallback + 'aaaaaaaaaaaaaaa').slice(0, 15)
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeText(value) {
|
function normalizeText(value) {
|
||||||
return String(value || '').replace(/^\s+|\s+$/g, '')
|
return String(value || '').replace(/^\s+|\s+$/g, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fetchAllRecordsByFilter(collectionName, filter, sort, params) {
|
||||||
|
const batchSize = 200
|
||||||
|
const result = []
|
||||||
|
let offset = 0
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const batch = $app.findRecordsByFilter(
|
||||||
|
collectionName,
|
||||||
|
filter || '',
|
||||||
|
sort || '',
|
||||||
|
batchSize,
|
||||||
|
offset,
|
||||||
|
params || {}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!batch.length) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < batch.length; i += 1) {
|
||||||
|
result.push(batch[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batch.length < batchSize) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += batch.length
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeOptionalNumberValue(value, fieldName) {
|
function normalizeOptionalNumberValue(value, fieldName) {
|
||||||
if (value === '' || value === null || typeof value === 'undefined') {
|
if (value === '' || value === null || typeof value === 'undefined') {
|
||||||
return null
|
return null
|
||||||
@@ -533,6 +580,7 @@ function exportProductRecord(record, extra) {
|
|||||||
prod_list_id: record.getString('prod_list_id'),
|
prod_list_id: record.getString('prod_list_id'),
|
||||||
prod_list_name: record.getString('prod_list_name'),
|
prod_list_name: record.getString('prod_list_name'),
|
||||||
prod_list_modelnumber: record.getString('prod_list_modelnumber'),
|
prod_list_modelnumber: record.getString('prod_list_modelnumber'),
|
||||||
|
prod_list_barcode: record.getString('prod_list_barcode'),
|
||||||
prod_list_icon: iconIds.join('|'),
|
prod_list_icon: iconIds.join('|'),
|
||||||
prod_list_icon_ids: iconIds,
|
prod_list_icon_ids: iconIds,
|
||||||
prod_list_icon_attachments: iconAttachments,
|
prod_list_icon_attachments: iconAttachments,
|
||||||
@@ -562,7 +610,7 @@ function exportProductRecord(record, extra) {
|
|||||||
|
|
||||||
function listProducts(payload) {
|
function listProducts(payload) {
|
||||||
try {
|
try {
|
||||||
const allRecords = $app.findRecordsByFilter('tbl_product_list', '', '', 500, 0)
|
const allRecords = fetchAllRecordsByFilter('tbl_product_list', '', '')
|
||||||
const keyword = normalizeText(payload.keyword).toLowerCase()
|
const keyword = normalizeText(payload.keyword).toLowerCase()
|
||||||
const status = normalizeText(payload.status)
|
const status = normalizeText(payload.status)
|
||||||
const category = normalizeText(payload.prod_list_category)
|
const category = normalizeText(payload.prod_list_category)
|
||||||
@@ -592,6 +640,7 @@ function listProducts(payload) {
|
|||||||
|| item.prod_list_id.toLowerCase().indexOf(keyword) !== -1
|
|| item.prod_list_id.toLowerCase().indexOf(keyword) !== -1
|
||||||
|| item.prod_list_name.toLowerCase().indexOf(keyword) !== -1
|
|| item.prod_list_name.toLowerCase().indexOf(keyword) !== -1
|
||||||
|| item.prod_list_modelnumber.toLowerCase().indexOf(keyword) !== -1
|
|| item.prod_list_modelnumber.toLowerCase().indexOf(keyword) !== -1
|
||||||
|
|| item.prod_list_barcode.toLowerCase().indexOf(keyword) !== -1
|
||||||
|| item.prod_list_tags.toLowerCase().indexOf(keyword) !== -1
|
|| item.prod_list_tags.toLowerCase().indexOf(keyword) !== -1
|
||||||
const matchedStatus = !status || item.prod_list_status === status
|
const matchedStatus = !status || item.prod_list_status === status
|
||||||
const matchedCategory = !category || item.prod_list_category === category
|
const matchedCategory = !category || item.prod_list_category === category
|
||||||
@@ -620,7 +669,7 @@ function getProductDetail(productId) {
|
|||||||
throw createAppError(404, '未找到对应产品')
|
throw createAppError(404, '未找到对应产品')
|
||||||
}
|
}
|
||||||
|
|
||||||
const allRecords = $app.findRecordsByFilter('tbl_product_list', '', '', 500, 0)
|
const allRecords = fetchAllRecordsByFilter('tbl_product_list', '', '')
|
||||||
const allItems = []
|
const allItems = []
|
||||||
for (let i = 0; i < allRecords.length; i += 1) {
|
for (let i = 0; i < allRecords.length; i += 1) {
|
||||||
allItems.push(exportProductRecord(allRecords[i]))
|
allItems.push(exportProductRecord(allRecords[i]))
|
||||||
@@ -644,9 +693,11 @@ function createProduct(_userOpenid, payload) {
|
|||||||
const collection = $app.findCollectionByNameOrId('tbl_product_list')
|
const collection = $app.findCollectionByNameOrId('tbl_product_list')
|
||||||
const record = new Record(collection)
|
const record = new Record(collection)
|
||||||
|
|
||||||
|
record.set('id', buildPocketBaseRecordId(payload && payload.id))
|
||||||
record.set('prod_list_id', targetProductId)
|
record.set('prod_list_id', targetProductId)
|
||||||
record.set('prod_list_name', normalizeText(payload.prod_list_name))
|
record.set('prod_list_name', normalizeText(payload.prod_list_name))
|
||||||
record.set('prod_list_modelnumber', normalizeText(payload.prod_list_modelnumber))
|
record.set('prod_list_modelnumber', normalizeText(payload.prod_list_modelnumber))
|
||||||
|
record.set('prod_list_barcode', normalizeText(payload.prod_list_barcode))
|
||||||
record.set('prod_list_icon', joinUniquePipeValues(payload.prod_list_icon))
|
record.set('prod_list_icon', joinUniquePipeValues(payload.prod_list_icon))
|
||||||
record.set('prod_list_description', normalizeText(payload.prod_list_description))
|
record.set('prod_list_description', normalizeText(payload.prod_list_description))
|
||||||
record.set('prod_list_feature', normalizeText(payload.prod_list_feature))
|
record.set('prod_list_feature', normalizeText(payload.prod_list_feature))
|
||||||
@@ -698,6 +749,7 @@ function updateProduct(_userOpenid, payload) {
|
|||||||
|
|
||||||
record.set('prod_list_name', normalizeText(payload.prod_list_name))
|
record.set('prod_list_name', normalizeText(payload.prod_list_name))
|
||||||
record.set('prod_list_modelnumber', normalizeText(payload.prod_list_modelnumber))
|
record.set('prod_list_modelnumber', normalizeText(payload.prod_list_modelnumber))
|
||||||
|
record.set('prod_list_barcode', normalizeText(payload.prod_list_barcode))
|
||||||
record.set('prod_list_icon', joinUniquePipeValues(payload.prod_list_icon))
|
record.set('prod_list_icon', joinUniquePipeValues(payload.prod_list_icon))
|
||||||
record.set('prod_list_description', normalizeText(payload.prod_list_description))
|
record.set('prod_list_description', normalizeText(payload.prod_list_description))
|
||||||
record.set('prod_list_feature', normalizeText(payload.prod_list_feature))
|
record.set('prod_list_feature', normalizeText(payload.prod_list_feature))
|
||||||
|
|||||||
@@ -0,0 +1,772 @@
|
|||||||
|
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
|
||||||
|
const { createAppError } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/appError.js`)
|
||||||
|
const documentService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/documentService.js`)
|
||||||
|
|
||||||
|
const TEMPLATE_COLLECTION = 'tbl_scheme_template'
|
||||||
|
const SCHEME_COLLECTION = 'tbl_scheme'
|
||||||
|
const USER_COLLECTION = 'tbl_auth_users'
|
||||||
|
const PRODUCT_COLLECTION = 'tbl_product_list'
|
||||||
|
|
||||||
|
function tryFindCollection(collectionName) {
|
||||||
|
try {
|
||||||
|
return $app.findCollectionByNameOrId(collectionName)
|
||||||
|
} catch (_error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureCollectionReady(collectionName) {
|
||||||
|
const collection = tryFindCollection(collectionName)
|
||||||
|
if (!collection) {
|
||||||
|
throw createAppError(400, '集合未初始化:' + collectionName + ',请先执行方案建表脚本')
|
||||||
|
}
|
||||||
|
|
||||||
|
return collection
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBusinessId(prefix) {
|
||||||
|
return prefix + '-' + new Date().getTime() + '-' + $security.randomString(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeText(value) {
|
||||||
|
return String(value || '').replace(/^\s+|\s+$/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDateValue(value) {
|
||||||
|
const text = normalizeText(value)
|
||||||
|
if (!text) return null
|
||||||
|
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}$/.test(text)) {
|
||||||
|
return text + ' 00:00:00.000Z'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}\s/.test(text) || text.indexOf('T') !== -1) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
throw createAppError(400, '日期字段格式错误')
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJsonInput(value, fieldName) {
|
||||||
|
if (value === null || typeof value === 'undefined' || value === '') {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.stringify(value))
|
||||||
|
} catch (_error) {
|
||||||
|
throw createAppError(400, fieldName + ' 必须是可序列化的 JSON 数组')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.stringify(value))
|
||||||
|
} catch (_error) {
|
||||||
|
throw createAppError(400, fieldName + ' 必须是可序列化的 JSON 对象或数组')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value)
|
||||||
|
} catch (_error) {
|
||||||
|
throw createAppError(400, fieldName + ' 必须为合法 JSON')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw createAppError(400, fieldName + ' 必须为 JSON')
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJsonForOutput(value, fallback) {
|
||||||
|
if (value === null || typeof value === 'undefined' || value === '') {
|
||||||
|
return typeof fallback === 'undefined' ? [] : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.stringify(value))
|
||||||
|
} catch (_error) {
|
||||||
|
return typeof fallback === 'undefined' ? [] : fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.stringify(value))
|
||||||
|
} catch (_error) {
|
||||||
|
return typeof fallback === 'undefined' ? [] : fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value)
|
||||||
|
} catch (_error) {
|
||||||
|
return typeof fallback === 'undefined' ? [] : fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof fallback === 'undefined' ? [] : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUserRecordByOpenid(openid) {
|
||||||
|
const value = normalizeText(openid)
|
||||||
|
if (!value) return null
|
||||||
|
if (!tryFindCollection(USER_COLLECTION)) return null
|
||||||
|
|
||||||
|
const records = $app.findRecordsByFilter(USER_COLLECTION, 'openid = {:openid}', '', 1, 0, {
|
||||||
|
openid: value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!records.length) return null
|
||||||
|
if (Number(records[0].get('is_delete') || 0) === 1) return null
|
||||||
|
return records[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureUserExists(openid, fieldName) {
|
||||||
|
const value = normalizeText(openid)
|
||||||
|
if (!value) {
|
||||||
|
throw createAppError(400, fieldName + ' 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = getUserRecordByOpenid(value)
|
||||||
|
if (!record) {
|
||||||
|
throw createAppError(400, fieldName + ' 对应用户不存在:' + value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureAttachmentExists(attachmentId, fieldName) {
|
||||||
|
const value = normalizeText(attachmentId)
|
||||||
|
if (!value) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
documentService.getAttachmentDetail(value)
|
||||||
|
} catch (_error) {
|
||||||
|
throw createAppError(400, fieldName + ' 对应附件不存在:' + value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProductRecordById(productId) {
|
||||||
|
const value = normalizeText(productId)
|
||||||
|
if (!value) return null
|
||||||
|
if (!tryFindCollection(PRODUCT_COLLECTION)) return null
|
||||||
|
|
||||||
|
const records = $app.findRecordsByFilter(PRODUCT_COLLECTION, 'prod_list_id = {:productId}', '', 1, 0, {
|
||||||
|
productId: value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!records.length) return null
|
||||||
|
if (Number(records[0].get('is_delete') || 0) === 1) return null
|
||||||
|
return records[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureProductExists(productId, fieldName) {
|
||||||
|
const value = normalizeText(productId)
|
||||||
|
if (!value) {
|
||||||
|
throw createAppError(400, fieldName + ' 不能为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = getProductRecordById(value)
|
||||||
|
if (!record) {
|
||||||
|
throw createAppError(400, fieldName + ' 对应产品不存在:' + value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
|
function findTemplateRecordByTemplateId(templateId) {
|
||||||
|
const value = normalizeText(templateId)
|
||||||
|
if (!value) return null
|
||||||
|
if (!tryFindCollection(TEMPLATE_COLLECTION)) return null
|
||||||
|
|
||||||
|
const records = $app.findRecordsByFilter(TEMPLATE_COLLECTION, 'scheme_template_id = {:templateId}', '', 1, 0, {
|
||||||
|
templateId: value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!records.length) return null
|
||||||
|
if (Number(records[0].get('is_delete') || 0) === 1) return null
|
||||||
|
return records[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureTemplateExists(templateId, fieldName) {
|
||||||
|
const value = normalizeText(templateId)
|
||||||
|
if (!value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = findTemplateRecordByTemplateId(value)
|
||||||
|
if (!record) {
|
||||||
|
throw createAppError(400, fieldName + ' 对应模板不存在:' + value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSchemeRecordBySchemeId(schemeId) {
|
||||||
|
const value = normalizeText(schemeId)
|
||||||
|
if (!value) return null
|
||||||
|
if (!tryFindCollection(SCHEME_COLLECTION)) return null
|
||||||
|
|
||||||
|
const records = $app.findRecordsByFilter(SCHEME_COLLECTION, 'scheme_id = {:schemeId}', '', 1, 0, {
|
||||||
|
schemeId: value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!records.length) return null
|
||||||
|
if (Number(records[0].get('is_delete') || 0) === 1) return null
|
||||||
|
return records[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeTemplateProductList(value) {
|
||||||
|
const parsed = parseJsonInput(value, 'scheme_template_product_list')
|
||||||
|
if (!Array.isArray(parsed)) {
|
||||||
|
throw createAppError(400, 'scheme_template_product_list 必须为 JSON 数组')
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = []
|
||||||
|
for (let i = 0; i < parsed.length; i += 1) {
|
||||||
|
const item = parsed[i] && typeof parsed[i] === 'object' ? parsed[i] : null
|
||||||
|
if (!item) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const productId = normalizeText(item.prod_list_id || item.product_id)
|
||||||
|
if (!productId) {
|
||||||
|
throw createAppError(400, 'scheme_template_product_list 第 ' + (i + 1) + ' 项必须包含 product_id 或 prod_list_id')
|
||||||
|
}
|
||||||
|
ensureProductExists(productId, 'scheme_template_product_list.product_id')
|
||||||
|
|
||||||
|
const hasProductSnapshot = Object.keys(item).some(function (key) {
|
||||||
|
return key.indexOf('prod_list_') === 0 || key === 'pb_id'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (hasProductSnapshot) {
|
||||||
|
let snapshot = null
|
||||||
|
try {
|
||||||
|
snapshot = JSON.parse(JSON.stringify(item))
|
||||||
|
} catch (_error) {
|
||||||
|
throw createAppError(400, 'scheme_template_product_list 第 ' + (i + 1) + ' 项必须为可序列化的产品对象')
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.prod_list_id = productId
|
||||||
|
result.push(snapshot)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const qty = Number(item.qty)
|
||||||
|
if (!Number.isFinite(qty) || qty <= 0) {
|
||||||
|
throw createAppError(400, 'scheme_template_product_list 第 ' + (i + 1) + ' 项 qty 必须为正数')
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
product_id: productId,
|
||||||
|
qty: qty,
|
||||||
|
note: normalizeText(item.note),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSchemeRoomType(value) {
|
||||||
|
const parsed = parseJsonInput(value, 'scheme_room_type')
|
||||||
|
if (!Array.isArray(parsed)) {
|
||||||
|
throw createAppError(400, 'scheme_room_type 必须为 JSON 数组')
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = []
|
||||||
|
for (let i = 0; i < parsed.length; i += 1) {
|
||||||
|
const item = parsed[i] && typeof parsed[i] === 'object' ? parsed[i] : null
|
||||||
|
if (!item) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const roomType = normalizeText(item.room_type || item.name)
|
||||||
|
if (!roomType) {
|
||||||
|
throw createAppError(400, 'scheme_room_type 第 ' + (i + 1) + ' 项 room_type 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
const qty = Number(item.qty)
|
||||||
|
if (!Number.isFinite(qty) || qty <= 0) {
|
||||||
|
throw createAppError(400, 'scheme_room_type 第 ' + (i + 1) + ' 项 qty 必须为正数')
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
room_type: roomType,
|
||||||
|
qty: qty,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAttachment(attachmentId) {
|
||||||
|
const value = normalizeText(attachmentId)
|
||||||
|
if (!value) {
|
||||||
|
return {
|
||||||
|
id: '',
|
||||||
|
url: '',
|
||||||
|
attachment: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const attachment = documentService.getAttachmentDetail(value)
|
||||||
|
return {
|
||||||
|
id: value,
|
||||||
|
url: attachment ? attachment.attachments_url : '',
|
||||||
|
attachment: attachment,
|
||||||
|
}
|
||||||
|
} catch (_error) {
|
||||||
|
return {
|
||||||
|
id: value,
|
||||||
|
url: '',
|
||||||
|
attachment: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTemplateSummaryMap() {
|
||||||
|
if (!tryFindCollection(TEMPLATE_COLLECTION)) {
|
||||||
|
logger.warn('方案模板集合未初始化,方案模板摘要映射将返回空结果', {
|
||||||
|
collection: TEMPLATE_COLLECTION,
|
||||||
|
})
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const records = $app.findRecordsByFilter(TEMPLATE_COLLECTION, 'is_delete = 0', '-updated', 1000, 0)
|
||||||
|
const map = {}
|
||||||
|
|
||||||
|
for (let i = 0; i < records.length; i += 1) {
|
||||||
|
const templateId = normalizeText(records[i].getString('scheme_template_id'))
|
||||||
|
if (!templateId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
map[templateId] = {
|
||||||
|
scheme_template_id: templateId,
|
||||||
|
scheme_template_name: records[i].getString('scheme_template_name'),
|
||||||
|
scheme_template_owner: records[i].getString('scheme_template_owner'),
|
||||||
|
scheme_template_status: records[i].getString('scheme_template_status'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportTemplateRecord(record) {
|
||||||
|
const icon = resolveAttachment(record.getString('scheme_template_icon'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
pb_id: record.id,
|
||||||
|
scheme_template_id: record.getString('scheme_template_id'),
|
||||||
|
scheme_template_icon: icon.id,
|
||||||
|
scheme_template_icon_url: icon.url,
|
||||||
|
scheme_template_icon_attachment: icon.attachment,
|
||||||
|
scheme_template_label: record.getString('scheme_template_label'),
|
||||||
|
scheme_template_name: record.getString('scheme_template_name'),
|
||||||
|
scheme_template_owner: record.getString('scheme_template_owner'),
|
||||||
|
scheme_template_status: record.getString('scheme_template_status'),
|
||||||
|
scheme_template_solution_type: record.getString('scheme_template_solution_type'),
|
||||||
|
scheme_template_solution_feature: record.getString('scheme_template_solution_feature'),
|
||||||
|
scheme_template_product_list: parseJsonForOutput(record.get('scheme_template_product_list'), []),
|
||||||
|
scheme_template_description: record.getString('scheme_template_description'),
|
||||||
|
scheme_template_remark: record.getString('scheme_template_remark'),
|
||||||
|
is_delete: Number(record.get('is_delete') || 0),
|
||||||
|
created: String(record.created || ''),
|
||||||
|
updated: String(record.updated || ''),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportSchemeRecord(record, templateMap) {
|
||||||
|
const highendId = record.getString('scheme_template_highend')
|
||||||
|
const midendId = record.getString('scheme_template_midend')
|
||||||
|
const lowendId = record.getString('scheme_template_lowend')
|
||||||
|
const templates = templateMap || {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
pb_id: record.id,
|
||||||
|
scheme_id: record.getString('scheme_id'),
|
||||||
|
scheme_name: record.getString('scheme_name'),
|
||||||
|
scheme_owner: record.getString('scheme_owner'),
|
||||||
|
scheme_share_status: record.getString('scheme_share_status'),
|
||||||
|
scheme_expires_at: String(record.get('scheme_expires_at') || ''),
|
||||||
|
scheme_hotel_type: record.getString('scheme_hotel_type'),
|
||||||
|
scheme_solution_type: record.getString('scheme_solution_type'),
|
||||||
|
scheme_solution_feature: record.getString('scheme_solution_feature'),
|
||||||
|
scheme_room_type: parseJsonForOutput(record.get('scheme_room_type'), []),
|
||||||
|
scheme_curtains: record.getString('scheme_curtains'),
|
||||||
|
scheme_voice_device: record.getString('scheme_voice_device'),
|
||||||
|
scheme_ac_type: record.getString('scheme_ac_type'),
|
||||||
|
scheme_template_highend: highendId,
|
||||||
|
scheme_template_highend_name: templates[highendId] ? templates[highendId].scheme_template_name : '',
|
||||||
|
scheme_template_midend: midendId,
|
||||||
|
scheme_template_midend_name: templates[midendId] ? templates[midendId].scheme_template_name : '',
|
||||||
|
scheme_template_lowend: lowendId,
|
||||||
|
scheme_template_lowend_name: templates[lowendId] ? templates[lowendId].scheme_template_name : '',
|
||||||
|
scheme_status: record.getString('scheme_status'),
|
||||||
|
scheme_remark: record.getString('scheme_remark'),
|
||||||
|
is_delete: Number(record.get('is_delete') || 0),
|
||||||
|
created: String(record.created || ''),
|
||||||
|
updated: String(record.updated || ''),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTemplatePayload(record, payload) {
|
||||||
|
ensureUserExists(payload.scheme_template_owner, 'scheme_template_owner')
|
||||||
|
|
||||||
|
record.set('scheme_template_name', normalizeText(payload.scheme_template_name))
|
||||||
|
record.set('scheme_template_owner', normalizeText(payload.scheme_template_owner))
|
||||||
|
record.set('scheme_template_icon', ensureAttachmentExists(payload.scheme_template_icon, 'scheme_template_icon'))
|
||||||
|
record.set('scheme_template_label', normalizeText(payload.scheme_template_label))
|
||||||
|
record.set('scheme_template_status', normalizeText(payload.scheme_template_status))
|
||||||
|
record.set('scheme_template_solution_type', normalizeText(payload.scheme_template_solution_type))
|
||||||
|
record.set('scheme_template_solution_feature', normalizeText(payload.scheme_template_solution_feature))
|
||||||
|
record.set('scheme_template_product_list', normalizeTemplateProductList(payload.scheme_template_product_list))
|
||||||
|
record.set('scheme_template_description', normalizeText(payload.scheme_template_description))
|
||||||
|
record.set('scheme_template_remark', normalizeText(payload.scheme_template_remark))
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySchemePayload(record, payload) {
|
||||||
|
ensureUserExists(payload.scheme_owner, 'scheme_owner')
|
||||||
|
ensureTemplateExists(payload.scheme_template_highend, 'scheme_template_highend')
|
||||||
|
ensureTemplateExists(payload.scheme_template_midend, 'scheme_template_midend')
|
||||||
|
ensureTemplateExists(payload.scheme_template_lowend, 'scheme_template_lowend')
|
||||||
|
|
||||||
|
record.set('scheme_name', normalizeText(payload.scheme_name))
|
||||||
|
record.set('scheme_owner', normalizeText(payload.scheme_owner))
|
||||||
|
record.set('scheme_share_status', normalizeText(payload.scheme_share_status))
|
||||||
|
record.set('scheme_expires_at', normalizeDateValue(payload.scheme_expires_at))
|
||||||
|
record.set('scheme_hotel_type', normalizeText(payload.scheme_hotel_type))
|
||||||
|
record.set('scheme_solution_type', normalizeText(payload.scheme_solution_type))
|
||||||
|
record.set('scheme_solution_feature', normalizeText(payload.scheme_solution_feature))
|
||||||
|
record.set('scheme_room_type', normalizeSchemeRoomType(payload.scheme_room_type))
|
||||||
|
record.set('scheme_curtains', normalizeText(payload.scheme_curtains))
|
||||||
|
record.set('scheme_voice_device', normalizeText(payload.scheme_voice_device))
|
||||||
|
record.set('scheme_ac_type', normalizeText(payload.scheme_ac_type))
|
||||||
|
record.set('scheme_template_highend', normalizeText(payload.scheme_template_highend))
|
||||||
|
record.set('scheme_template_midend', normalizeText(payload.scheme_template_midend))
|
||||||
|
record.set('scheme_template_lowend', normalizeText(payload.scheme_template_lowend))
|
||||||
|
record.set('scheme_status', normalizeText(payload.scheme_status))
|
||||||
|
record.set('scheme_remark', normalizeText(payload.scheme_remark))
|
||||||
|
}
|
||||||
|
|
||||||
|
function listSchemeTemplates(payload) {
|
||||||
|
if (!tryFindCollection(TEMPLATE_COLLECTION)) {
|
||||||
|
logger.warn('方案模板集合未初始化,模板列表返回空结果', {
|
||||||
|
collection: TEMPLATE_COLLECTION,
|
||||||
|
})
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const records = $app.findRecordsByFilter(TEMPLATE_COLLECTION, 'is_delete = 0', '-updated', 1000, 0)
|
||||||
|
const keyword = normalizeText(payload.keyword).toLowerCase()
|
||||||
|
const owner = normalizeText(payload.scheme_template_owner)
|
||||||
|
const status = normalizeText(payload.scheme_template_status)
|
||||||
|
const solutionType = normalizeText(payload.scheme_template_solution_type)
|
||||||
|
const solutionFeature = normalizeText(payload.scheme_template_solution_feature)
|
||||||
|
const result = []
|
||||||
|
|
||||||
|
for (let i = 0; i < records.length; i += 1) {
|
||||||
|
const item = exportTemplateRecord(records[i])
|
||||||
|
const matchedKeyword = !keyword
|
||||||
|
|| normalizeText(item.scheme_template_id).toLowerCase().indexOf(keyword) !== -1
|
||||||
|
|| normalizeText(item.scheme_template_name).toLowerCase().indexOf(keyword) !== -1
|
||||||
|
|| normalizeText(item.scheme_template_label).toLowerCase().indexOf(keyword) !== -1
|
||||||
|
|| normalizeText(item.scheme_template_owner).toLowerCase().indexOf(keyword) !== -1
|
||||||
|
const matchedOwner = !owner || item.scheme_template_owner === owner
|
||||||
|
const matchedStatus = !status || item.scheme_template_status === status
|
||||||
|
const matchedSolutionType = !solutionType || item.scheme_template_solution_type === solutionType
|
||||||
|
const matchedSolutionFeature = !solutionFeature || item.scheme_template_solution_feature === solutionFeature
|
||||||
|
|
||||||
|
if (matchedKeyword && matchedOwner && matchedStatus && matchedSolutionType && matchedSolutionFeature) {
|
||||||
|
result.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSchemeTemplateDetail(templateId) {
|
||||||
|
ensureCollectionReady(TEMPLATE_COLLECTION)
|
||||||
|
|
||||||
|
const record = findTemplateRecordByTemplateId(templateId)
|
||||||
|
if (!record) {
|
||||||
|
throw createAppError(404, '未找到对应方案模板')
|
||||||
|
}
|
||||||
|
|
||||||
|
return exportTemplateRecord(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSchemeTemplate(userOpenid, payload) {
|
||||||
|
const collection = ensureCollectionReady(TEMPLATE_COLLECTION)
|
||||||
|
const templateId = normalizeText(payload.scheme_template_id) || buildBusinessId('SCHTPL')
|
||||||
|
if (findTemplateRecordByTemplateId(templateId)) {
|
||||||
|
throw createAppError(400, 'scheme_template_id 已存在')
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = new Record(collection)
|
||||||
|
|
||||||
|
record.set('scheme_template_id', templateId)
|
||||||
|
record.set('is_delete', 0)
|
||||||
|
applyTemplatePayload(record, payload)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$app.save(record)
|
||||||
|
} catch (err) {
|
||||||
|
throw createAppError(400, '新增方案模板失败', {
|
||||||
|
originalMessage: (err && err.message) || '未知错误',
|
||||||
|
originalData: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('方案模板创建成功', {
|
||||||
|
scheme_template_id: templateId,
|
||||||
|
operator_openid: userOpenid || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
return exportTemplateRecord(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSchemeTemplate(userOpenid, payload) {
|
||||||
|
ensureCollectionReady(TEMPLATE_COLLECTION)
|
||||||
|
|
||||||
|
const templateId = normalizeText(payload.scheme_template_id)
|
||||||
|
if (!templateId) {
|
||||||
|
throw createAppError(400, 'scheme_template_id 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = findTemplateRecordByTemplateId(templateId)
|
||||||
|
if (!record) {
|
||||||
|
throw createAppError(404, '未找到待修改的方案模板')
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTemplatePayload(record, payload)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$app.save(record)
|
||||||
|
} catch (err) {
|
||||||
|
throw createAppError(400, '更新方案模板失败', {
|
||||||
|
originalMessage: (err && err.message) || '未知错误',
|
||||||
|
originalData: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('方案模板更新成功', {
|
||||||
|
scheme_template_id: templateId,
|
||||||
|
operator_openid: userOpenid || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
return exportTemplateRecord(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSchemeTemplate(userOpenid, templateId) {
|
||||||
|
ensureCollectionReady(TEMPLATE_COLLECTION)
|
||||||
|
ensureCollectionReady(SCHEME_COLLECTION)
|
||||||
|
|
||||||
|
const value = normalizeText(templateId)
|
||||||
|
if (!value) {
|
||||||
|
throw createAppError(400, 'scheme_template_id 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = findTemplateRecordByTemplateId(value)
|
||||||
|
if (!record) {
|
||||||
|
throw createAppError(404, '未找到待删除的方案模板')
|
||||||
|
}
|
||||||
|
|
||||||
|
const usedSchemes = $app.findRecordsByFilter(
|
||||||
|
SCHEME_COLLECTION,
|
||||||
|
'is_delete = 0 && (scheme_template_highend = {:templateId} || scheme_template_midend = {:templateId} || scheme_template_lowend = {:templateId})',
|
||||||
|
'',
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
{ templateId: value }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (usedSchemes.length) {
|
||||||
|
throw createAppError(400, '方案模板已被方案引用,无法删除')
|
||||||
|
}
|
||||||
|
|
||||||
|
record.set('is_delete', 1)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$app.save(record)
|
||||||
|
} catch (err) {
|
||||||
|
throw createAppError(400, '删除方案模板失败', {
|
||||||
|
originalMessage: (err && err.message) || '未知错误',
|
||||||
|
originalData: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('方案模板删除成功', {
|
||||||
|
scheme_template_id: value,
|
||||||
|
operator_openid: userOpenid || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
scheme_template_id: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listSchemes(payload) {
|
||||||
|
if (!tryFindCollection(SCHEME_COLLECTION)) {
|
||||||
|
logger.warn('方案集合未初始化,方案列表返回空结果', {
|
||||||
|
collection: SCHEME_COLLECTION,
|
||||||
|
})
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateMap = buildTemplateSummaryMap()
|
||||||
|
const records = $app.findRecordsByFilter(SCHEME_COLLECTION, 'is_delete = 0', '-updated', 1000, 0)
|
||||||
|
const keyword = normalizeText(payload.keyword).toLowerCase()
|
||||||
|
const owner = normalizeText(payload.scheme_owner)
|
||||||
|
const status = normalizeText(payload.scheme_status)
|
||||||
|
const shareStatus = normalizeText(payload.scheme_share_status)
|
||||||
|
const hotelType = normalizeText(payload.scheme_hotel_type)
|
||||||
|
const result = []
|
||||||
|
|
||||||
|
for (let i = 0; i < records.length; i += 1) {
|
||||||
|
const item = exportSchemeRecord(records[i], templateMap)
|
||||||
|
const matchedKeyword = !keyword
|
||||||
|
|| normalizeText(item.scheme_id).toLowerCase().indexOf(keyword) !== -1
|
||||||
|
|| normalizeText(item.scheme_name).toLowerCase().indexOf(keyword) !== -1
|
||||||
|
|| normalizeText(item.scheme_owner).toLowerCase().indexOf(keyword) !== -1
|
||||||
|
const matchedOwner = !owner || item.scheme_owner === owner
|
||||||
|
const matchedStatus = !status || item.scheme_status === status
|
||||||
|
const matchedShareStatus = !shareStatus || item.scheme_share_status === shareStatus
|
||||||
|
const matchedHotelType = !hotelType || item.scheme_hotel_type === hotelType
|
||||||
|
|
||||||
|
if (matchedKeyword && matchedOwner && matchedStatus && matchedShareStatus && matchedHotelType) {
|
||||||
|
result.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSchemeDetail(schemeId) {
|
||||||
|
ensureCollectionReady(SCHEME_COLLECTION)
|
||||||
|
|
||||||
|
const record = findSchemeRecordBySchemeId(schemeId)
|
||||||
|
if (!record) {
|
||||||
|
throw createAppError(404, '未找到对应方案')
|
||||||
|
}
|
||||||
|
|
||||||
|
return exportSchemeRecord(record, buildTemplateSummaryMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
function createScheme(userOpenid, payload) {
|
||||||
|
const collection = ensureCollectionReady(SCHEME_COLLECTION)
|
||||||
|
const schemeId = normalizeText(payload.scheme_id) || buildBusinessId('SCHEME')
|
||||||
|
if (findSchemeRecordBySchemeId(schemeId)) {
|
||||||
|
throw createAppError(400, 'scheme_id 已存在')
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = new Record(collection)
|
||||||
|
|
||||||
|
record.set('scheme_id', schemeId)
|
||||||
|
record.set('is_delete', 0)
|
||||||
|
applySchemePayload(record, payload)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$app.save(record)
|
||||||
|
} catch (err) {
|
||||||
|
throw createAppError(400, '新增方案失败', {
|
||||||
|
originalMessage: (err && err.message) || '未知错误',
|
||||||
|
originalData: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('方案创建成功', {
|
||||||
|
scheme_id: schemeId,
|
||||||
|
operator_openid: userOpenid || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
return exportSchemeRecord(record, buildTemplateSummaryMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateScheme(userOpenid, payload) {
|
||||||
|
ensureCollectionReady(SCHEME_COLLECTION)
|
||||||
|
|
||||||
|
const schemeId = normalizeText(payload.scheme_id)
|
||||||
|
if (!schemeId) {
|
||||||
|
throw createAppError(400, 'scheme_id 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = findSchemeRecordBySchemeId(schemeId)
|
||||||
|
if (!record) {
|
||||||
|
throw createAppError(404, '未找到待修改的方案')
|
||||||
|
}
|
||||||
|
|
||||||
|
applySchemePayload(record, payload)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$app.save(record)
|
||||||
|
} catch (err) {
|
||||||
|
throw createAppError(400, '更新方案失败', {
|
||||||
|
originalMessage: (err && err.message) || '未知错误',
|
||||||
|
originalData: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('方案更新成功', {
|
||||||
|
scheme_id: schemeId,
|
||||||
|
operator_openid: userOpenid || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
return exportSchemeRecord(record, buildTemplateSummaryMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteScheme(userOpenid, schemeId) {
|
||||||
|
ensureCollectionReady(SCHEME_COLLECTION)
|
||||||
|
|
||||||
|
const value = normalizeText(schemeId)
|
||||||
|
if (!value) {
|
||||||
|
throw createAppError(400, 'scheme_id 为必填项')
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = findSchemeRecordBySchemeId(value)
|
||||||
|
if (!record) {
|
||||||
|
throw createAppError(404, '未找到待删除的方案')
|
||||||
|
}
|
||||||
|
|
||||||
|
record.set('is_delete', 1)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$app.save(record)
|
||||||
|
} catch (err) {
|
||||||
|
throw createAppError(400, '删除方案失败', {
|
||||||
|
originalMessage: (err && err.message) || '未知错误',
|
||||||
|
originalData: (err && err.data) || {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('方案删除成功', {
|
||||||
|
scheme_id: value,
|
||||||
|
operator_openid: userOpenid || '',
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
scheme_id: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
listSchemeTemplates,
|
||||||
|
getSchemeTemplateDetail,
|
||||||
|
createSchemeTemplate,
|
||||||
|
updateSchemeTemplate,
|
||||||
|
deleteSchemeTemplate,
|
||||||
|
listSchemes,
|
||||||
|
getSchemeDetail,
|
||||||
|
createScheme,
|
||||||
|
updateScheme,
|
||||||
|
deleteScheme,
|
||||||
|
}
|
||||||
9
pocket-base/bai_web_pb_hooks/pages/scheme-manage.js
Normal file
9
pocket-base/bai_web_pb_hooks/pages/scheme-manage.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
routerAdd('GET', '/manage/scheme', function (e) {
|
||||||
|
const html = $template.loadFiles(
|
||||||
|
__hooks + '/bai_web_pb_hooks/views/scheme-manage.html',
|
||||||
|
__hooks + '/bai_web_pb_hooks/shared/theme-head.html',
|
||||||
|
__hooks + '/bai_web_pb_hooks/shared/theme-body.html'
|
||||||
|
).render({})
|
||||||
|
|
||||||
|
return e.html(200, html)
|
||||||
|
})
|
||||||
@@ -46,8 +46,44 @@
|
|||||||
.summary-label { color: #64748b; font-size: 13px; }
|
.summary-label { color: #64748b; font-size: 13px; }
|
||||||
.summary-value { margin-top: 8px; font-size: 22px; font-weight: 700; color: #0f172a; }
|
.summary-value { margin-top: 8px; font-size: 22px; font-weight: 700; color: #0f172a; }
|
||||||
.profile-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-bottom: 14px; }
|
.profile-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-bottom: 14px; }
|
||||||
|
.preview-form { border: 1px solid #dbe3f0; border-radius: 14px; background: #f8fbff; overflow: hidden; margin-bottom: 10px; }
|
||||||
|
.preview-form-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; }
|
||||||
|
.preview-form-summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; flex: 1; min-width: 0; }
|
||||||
|
.preview-summary-item { min-width: 0; }
|
||||||
|
.preview-summary-label { font-size: 11px; color: #64748b; margin-bottom: 2px; }
|
||||||
|
.preview-summary-value { font-size: 13px; font-weight: 700; color: #0f172a; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
|
.preview-toggle { min-width: 88px; }
|
||||||
|
.preview-form-body { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows 220ms ease, opacity 220ms ease; }
|
||||||
|
.preview-form-body.open { grid-template-rows: 1fr; opacity: 1; }
|
||||||
|
.preview-form-inner { min-height: 0; overflow: hidden; padding: 0 12px 0; }
|
||||||
|
.preview-form-body.open .preview-form-inner { padding-bottom: 12px; }
|
||||||
|
.preview-fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; padding-top: 2px; }
|
||||||
.field-block { display: grid; gap: 6px; }
|
.field-block { display: grid; gap: 6px; }
|
||||||
.field-label { font-size: 13px; color: #64748b; }
|
.field-block.full { grid-column: 1 / -1; }
|
||||||
|
.field-label { font-size: 12px; color: #64748b; }
|
||||||
|
.preview-card { border: 1px solid #dbe3f0; border-radius: 12px; padding: 8px 10px; background: rgba(255,255,255,0.65); min-width: 0; display: grid; gap: 3px; }
|
||||||
|
.preview-value { color: #0f172a; font-size: 13px; font-weight: 700; line-height: 1.35; word-break: break-word; }
|
||||||
|
.user-profile-shell { display: grid; gap: 12px; }
|
||||||
|
.profile-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; }
|
||||||
|
.edit-hint { display: inline-flex; align-items: center; gap: 6px; padding: 6px 10px; border-radius: 999px; font-size: 12px; font-weight: 700; }
|
||||||
|
.edit-hint.clean { background: #ecfdf5; color: #047857; border: 1px solid #a7f3d0; }
|
||||||
|
.edit-hint.dirty { background: #fff7ed; color: #c2410c; border: 1px solid #fdba74; }
|
||||||
|
.edit-panel { border-top: 1px dashed #dbe3f0; display: grid; grid-template-rows: 0fr; opacity: 0; transform: translateY(-4px); transition: grid-template-rows 220ms ease, opacity 220ms ease, transform 220ms ease, padding-top 220ms ease, margin-top 220ms ease; padding-top: 0; margin-top: 0; overflow: hidden; }
|
||||||
|
.edit-panel.open { grid-template-rows: 1fr; opacity: 1; transform: translateY(0); padding-top: 14px; margin-top: 2px; }
|
||||||
|
.edit-panel-inner { min-height: 0; overflow: hidden; }
|
||||||
|
.attachment-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-bottom: 12px; }
|
||||||
|
.attachment-grid.editing { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
.attachment-panel { border: 1px solid #dbe3f0; border-radius: 14px; padding: 12px; background: #f8fbff; }
|
||||||
|
.attachment-panel.compact { padding: 10px; min-width: 0; }
|
||||||
|
.attachment-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
|
||||||
|
.attachment-actions .btn { padding: 8px 12px; font-size: 13px; }
|
||||||
|
.attachment-preview { margin-top: 8px; display: flex; align-items: center; gap: 10px; min-height: 60px; }
|
||||||
|
.attachment-thumb { width: 60px; height: 60px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; object-fit: cover; }
|
||||||
|
.attachment-empty-thumb { width: 60px; height: 60px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #94a3b8; display: flex; align-items: center; justify-content: center; font-size: 11px; text-align: center; padding: 6px; }
|
||||||
|
.attachment-meta { min-width: 0; display: grid; gap: 4px; }
|
||||||
|
.attachment-meta.compact { gap: 2px; }
|
||||||
|
.attachment-link { color: #2563eb; text-decoration: none; font-size: 12px; font-weight: 600; word-break: break-all; }
|
||||||
|
.attachment-hidden-input { display: none; }
|
||||||
.detail-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
|
.detail-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
|
||||||
.section + .section { margin-top: 14px; }
|
.section + .section { margin-top: 14px; }
|
||||||
.section-title { margin: 0 0 10px; font-size: 18px; color: #0f172a; }
|
.section-title { margin: 0 0 10px; font-size: 18px; color: #0f172a; }
|
||||||
@@ -60,17 +96,60 @@
|
|||||||
@media (max-width: 1080px) {
|
@media (max-width: 1080px) {
|
||||||
.layout { grid-template-columns: 1fr; }
|
.layout { grid-template-columns: 1fr; }
|
||||||
.summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
.summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
.preview-form-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
.preview-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
.attachment-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
|
.attachment-grid.editing { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
.profile-grid { grid-template-columns: 1fr; }
|
.profile-grid { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.toolbar { grid-template-columns: 1fr; }
|
.toolbar { grid-template-columns: 1fr; }
|
||||||
.summary-grid { grid-template-columns: 1fr; }
|
.summary-grid { grid-template-columns: 1fr; }
|
||||||
|
.preview-form-head { flex-direction: column; align-items: stretch; }
|
||||||
|
.preview-form-summary { grid-template-columns: 1fr; }
|
||||||
|
.preview-fields { grid-template-columns: 1fr; }
|
||||||
|
.attachment-grid { grid-template-columns: 1fr; }
|
||||||
|
.attachment-grid.editing { grid-template-columns: 1fr; }
|
||||||
table, thead, tbody, th, td, tr { display: block; }
|
table, thead, tbody, th, td, tr { display: block; }
|
||||||
thead { display: none; }
|
thead { display: none; }
|
||||||
tr { border-bottom: 1px solid #e5e7eb; }
|
tr { border-bottom: 1px solid #e5e7eb; }
|
||||||
td { display: flex; justify-content: space-between; gap: 10px; }
|
td { display: flex; justify-content: space-between; gap: 10px; }
|
||||||
td::before { content: attr(data-label); font-weight: 700; color: #475569; }
|
td::before { content: attr(data-label); font-weight: 700; color: #475569; }
|
||||||
}
|
}
|
||||||
|
html[data-theme="dark"] .preview-form,
|
||||||
|
html[data-theme="dark"] .preview-card,
|
||||||
|
html[data-theme="dark"] .attachment-panel {
|
||||||
|
background: rgba(0, 0, 0, 0.88) !important;
|
||||||
|
border-color: rgba(148, 163, 184, 0.22) !important;
|
||||||
|
color: #e5e7eb !important;
|
||||||
|
box-shadow: 0 18px 50px rgba(2, 6, 23, 0.18) !important;
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .preview-summary-value {
|
||||||
|
color: #f8fafc !important;
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .preview-value,
|
||||||
|
html[data-theme="dark"] .attachment-link {
|
||||||
|
color: #f8fafc !important;
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .attachment-thumb,
|
||||||
|
html[data-theme="dark"] .attachment-empty-thumb {
|
||||||
|
background: rgba(15, 23, 42, 0.7) !important;
|
||||||
|
border-color: rgba(148, 163, 184, 0.22) !important;
|
||||||
|
color: #94a3b8 !important;
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .edit-panel {
|
||||||
|
border-top-color: rgba(148, 163, 184, 0.22) !important;
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .edit-hint.clean {
|
||||||
|
background: rgba(16, 185, 129, 0.14) !important;
|
||||||
|
color: #86efac !important;
|
||||||
|
border-color: rgba(110, 231, 183, 0.28) !important;
|
||||||
|
}
|
||||||
|
html[data-theme="dark"] .edit-hint.dirty {
|
||||||
|
background: rgba(249, 115, 22, 0.14) !important;
|
||||||
|
color: #fdba74 !important;
|
||||||
|
border-color: rgba(251, 146, 60, 0.28) !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{{ template "theme_head" . }}
|
{{ template "theme_head" . }}
|
||||||
</head>
|
</head>
|
||||||
@@ -112,7 +191,16 @@
|
|||||||
const state = {
|
const state = {
|
||||||
users: [],
|
users: [],
|
||||||
selectedOpenid: '',
|
selectedOpenid: '',
|
||||||
|
previewExpanded: false,
|
||||||
|
isEditMode: false,
|
||||||
|
userEditDraft: null,
|
||||||
userLevelOptions: [],
|
userLevelOptions: [],
|
||||||
|
attachmentDetails: {
|
||||||
|
userUsersPicture: null,
|
||||||
|
userUsersIdPicA: null,
|
||||||
|
userUsersIdPicB: null,
|
||||||
|
userUsersTitlePicture: null,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusEl = document.getElementById('status')
|
const statusEl = document.getElementById('status')
|
||||||
@@ -172,6 +260,291 @@
|
|||||||
}) || null
|
}) || null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildUserDraft(user) {
|
||||||
|
return {
|
||||||
|
openid: user.openid || '',
|
||||||
|
users_name: user.users_name || '',
|
||||||
|
users_phone: user.users_phone || '',
|
||||||
|
users_level: user.users_level || '',
|
||||||
|
users_type: user.users_type || '',
|
||||||
|
users_status: user.users_status || '',
|
||||||
|
users_rank_level: user.users_rank_level === null || typeof user.users_rank_level === 'undefined' ? '' : user.users_rank_level,
|
||||||
|
users_auth_type: user.users_auth_type === null || typeof user.users_auth_type === 'undefined' ? '' : user.users_auth_type,
|
||||||
|
company_id: user.company_id || '',
|
||||||
|
users_tag: user.users_tag || '',
|
||||||
|
users_parent_id: user.users_parent_id || '',
|
||||||
|
users_promo_code: user.users_promo_code || '',
|
||||||
|
usergroups_id: user.usergroups_id || '',
|
||||||
|
users_id_number: user.users_id_number || '',
|
||||||
|
users_picture: user.users_picture || '',
|
||||||
|
users_id_pic_a: user.users_id_pic_a || '',
|
||||||
|
users_id_pic_b: user.users_id_pic_b || '',
|
||||||
|
users_title_picture: user.users_title_picture || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureEditDraft(user) {
|
||||||
|
if (!user) {
|
||||||
|
state.userEditDraft = null
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.userEditDraft || normalizeText(state.userEditDraft.openid) !== normalizeText(user.openid)) {
|
||||||
|
state.userEditDraft = buildUserDraft(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.userEditDraft
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDraftSnapshot(source) {
|
||||||
|
const current = source || {}
|
||||||
|
return {
|
||||||
|
openid: normalizeText(current.openid),
|
||||||
|
users_name: normalizeText(current.users_name),
|
||||||
|
users_phone: normalizeText(current.users_phone),
|
||||||
|
users_level: normalizeText(current.users_level),
|
||||||
|
users_type: normalizeText(current.users_type),
|
||||||
|
users_status: normalizeText(current.users_status),
|
||||||
|
users_rank_level: normalizeText(current.users_rank_level),
|
||||||
|
users_auth_type: normalizeText(current.users_auth_type),
|
||||||
|
company_id: normalizeText(current.company_id),
|
||||||
|
users_tag: normalizeText(current.users_tag),
|
||||||
|
users_parent_id: normalizeText(current.users_parent_id),
|
||||||
|
users_promo_code: normalizeText(current.users_promo_code),
|
||||||
|
usergroups_id: normalizeText(current.usergroups_id),
|
||||||
|
users_id_number: normalizeText(current.users_id_number),
|
||||||
|
users_picture: normalizeText(current.users_picture),
|
||||||
|
users_id_pic_a: normalizeText(current.users_id_pic_a),
|
||||||
|
users_id_pic_b: normalizeText(current.users_id_pic_b),
|
||||||
|
users_title_picture: normalizeText(current.users_title_picture),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasUnsavedChanges(user) {
|
||||||
|
if (!state.isEditMode || !user) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const original = normalizeDraftSnapshot(buildUserDraft(user))
|
||||||
|
const current = normalizeDraftSnapshot(state.userEditDraft || buildUserDraft(user))
|
||||||
|
return JSON.stringify(original) !== JSON.stringify(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncEditDraftFromDom() {
|
||||||
|
if (!state.isEditMode || !state.userEditDraft) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldMap = {
|
||||||
|
userUsersName: 'users_name',
|
||||||
|
userUsersPhone: 'users_phone',
|
||||||
|
userUsersLevel: 'users_level',
|
||||||
|
userUsersType: 'users_type',
|
||||||
|
userUsersStatus: 'users_status',
|
||||||
|
userUsersRankLevel: 'users_rank_level',
|
||||||
|
userUsersAuthType: 'users_auth_type',
|
||||||
|
userCompanyId: 'company_id',
|
||||||
|
userUsersTag: 'users_tag',
|
||||||
|
userUsersParentId: 'users_parent_id',
|
||||||
|
userUsersPromoCode: 'users_promo_code',
|
||||||
|
userUsergroupsId: 'usergroups_id',
|
||||||
|
userUsersIdNumber: 'users_id_number',
|
||||||
|
userUsersPicture: 'users_picture',
|
||||||
|
userUsersIdPicA: 'users_id_pic_a',
|
||||||
|
userUsersIdPicB: 'users_id_pic_b',
|
||||||
|
userUsersTitlePicture: 'users_title_picture',
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(fieldMap).forEach(function (id) {
|
||||||
|
const el = document.getElementById(id)
|
||||||
|
if (el) {
|
||||||
|
state.userEditDraft[fieldMap[id]] = el.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseJsonSafe(res) {
|
||||||
|
const contentType = String(res.headers.get('content-type') || '').toLowerCase()
|
||||||
|
const rawText = await res.text()
|
||||||
|
const isJson = contentType.indexOf('application/json') !== -1
|
||||||
|
|
||||||
|
if (!rawText) {
|
||||||
|
return { json: null, text: '', isJson: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isJson) {
|
||||||
|
try {
|
||||||
|
return { json: JSON.parse(rawText), text: rawText, isJson: true }
|
||||||
|
} catch (_error) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { json: null, text: rawText, isJson: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadAttachment(file, fieldKey) {
|
||||||
|
const token = getToken()
|
||||||
|
if (!token) {
|
||||||
|
window.location.replace('/pb/manage/login')
|
||||||
|
throw new Error('登录状态已失效,请重新登录')
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = new FormData()
|
||||||
|
form.append('attachments_link', file)
|
||||||
|
form.append('attachments_filename', file.name || '')
|
||||||
|
form.append('attachments_filetype', file.type || '')
|
||||||
|
form.append('attachments_size', String(file.size || 0))
|
||||||
|
form.append('attachments_status', 'active')
|
||||||
|
form.append('attachments_remark', 'cart-order-manage:' + fieldKey)
|
||||||
|
|
||||||
|
const res = await fetch(API_BASE + '/attachment/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'Bearer ' + token,
|
||||||
|
},
|
||||||
|
body: form,
|
||||||
|
})
|
||||||
|
|
||||||
|
const parsed = await parseJsonSafe(res)
|
||||||
|
const data = parsed.json || {}
|
||||||
|
if (!res.ok) {
|
||||||
|
if (res.status === 413) {
|
||||||
|
throw new Error('上传图片失败:文件过大或服务端 bodyLimit 未生效')
|
||||||
|
}
|
||||||
|
if (!parsed.isJson && parsed.text) {
|
||||||
|
throw new Error('上传图片失败:服务端返回了非 JSON 响应')
|
||||||
|
}
|
||||||
|
throw new Error((data && (data.errMsg || data.message)) || '上传图片失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadAttachmentDetail(attachmentId) {
|
||||||
|
const id = normalizeText(attachmentId)
|
||||||
|
if (!id) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await requestJson('/attachment/detail', { attachments_id: id })
|
||||||
|
} catch (_error) {
|
||||||
|
return {
|
||||||
|
attachments_id: id,
|
||||||
|
attachments_filename: id,
|
||||||
|
attachments_url: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshSelectedAttachmentDetails() {
|
||||||
|
const user = getSelectedUser()
|
||||||
|
if (!user) {
|
||||||
|
state.attachmentDetails = {
|
||||||
|
userUsersPicture: null,
|
||||||
|
userUsersIdPicA: null,
|
||||||
|
userUsersIdPicB: null,
|
||||||
|
userUsersTitlePicture: null,
|
||||||
|
}
|
||||||
|
renderDetail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentOpenid = normalizeText(user.openid)
|
||||||
|
const results = await Promise.all([
|
||||||
|
loadAttachmentDetail(user.users_picture),
|
||||||
|
loadAttachmentDetail(user.users_id_pic_a),
|
||||||
|
loadAttachmentDetail(user.users_id_pic_b),
|
||||||
|
loadAttachmentDetail(user.users_title_picture),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (normalizeText(state.selectedOpenid) !== currentOpenid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.attachmentDetails = {
|
||||||
|
userUsersPicture: results[0],
|
||||||
|
userUsersIdPicA: results[1],
|
||||||
|
userUsersIdPicB: results[2],
|
||||||
|
userUsersTitlePicture: results[3],
|
||||||
|
}
|
||||||
|
renderDetail()
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAttachmentCard(fieldId, label, value, editable) {
|
||||||
|
const detail = state.attachmentDetails[fieldId] || null
|
||||||
|
const url = detail && detail.attachments_url ? detail.attachments_url : ''
|
||||||
|
const filename = detail && (detail.attachments_filename || detail.attachments_id)
|
||||||
|
? (detail.attachments_filename || detail.attachments_id)
|
||||||
|
: normalizeText(value)
|
||||||
|
|
||||||
|
return '<div class="attachment-panel compact">'
|
||||||
|
+ '<label class="field-label" for="' + fieldId + '">' + escapeHtml(label) + '</label>'
|
||||||
|
+ (editable
|
||||||
|
? '<input id="' + fieldId + '" value="' + escapeHtml(value || '') + '" />'
|
||||||
|
: '<div class="preview-card" style="margin-top:6px;"><div class="preview-value">' + escapeHtml(value || '-') + '</div></div>')
|
||||||
|
+ (editable
|
||||||
|
? '<input class="attachment-hidden-input" id="' + fieldId + 'File" type="file" accept="image/*" data-upload-field="' + escapeHtml(fieldId) + '" />'
|
||||||
|
: '')
|
||||||
|
+ (editable
|
||||||
|
? '<div class="attachment-actions">'
|
||||||
|
+ '<button class="btn btn-light" type="button" data-upload-trigger="' + escapeHtml(fieldId) + '">上传图片</button>'
|
||||||
|
+ '<button class="btn btn-light" type="button" data-clear-attachment="' + escapeHtml(fieldId) + '">清空ID</button>'
|
||||||
|
+ '</div>'
|
||||||
|
: '')
|
||||||
|
+ '<div class="attachment-preview">'
|
||||||
|
+ (url
|
||||||
|
? '<img class="attachment-thumb" src="' + escapeHtml(url) + '" alt="' + escapeHtml(label) + '" />'
|
||||||
|
: '<div class="attachment-empty-thumb">暂无预览</div>')
|
||||||
|
+ '<div class="attachment-meta compact">'
|
||||||
|
+ '<div class="muted">当前附件ID:' + escapeHtml(value || '-') + '</div>'
|
||||||
|
+ (url
|
||||||
|
? '<a class="attachment-link" href="' + escapeHtml(url) + '" target="_blank" rel="noreferrer">查看图片:' + escapeHtml(filename || '附件') + '</a>'
|
||||||
|
: '<div class="muted">上传后将先保存到 tbl_attachments,再自动回填附件ID</div>')
|
||||||
|
+ '</div>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '</div>'
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPreviewField(label, value) {
|
||||||
|
return '<div class="preview-card">'
|
||||||
|
+ '<div class="field-label">' + escapeHtml(label) + '</div>'
|
||||||
|
+ '<div class="preview-value">' + escapeHtml(value || '-') + '</div>'
|
||||||
|
+ '</div>'
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPreviewForm(user) {
|
||||||
|
return '<div class="preview-form">'
|
||||||
|
+ '<div class="preview-form-head">'
|
||||||
|
+ '<div class="preview-form-summary">'
|
||||||
|
+ '<div class="preview-summary-item"><div class="preview-summary-label">用户名称</div><div class="preview-summary-value">' + escapeHtml(user.users_name || '-') + '</div></div>'
|
||||||
|
+ '<div class="preview-summary-item"><div class="preview-summary-label">手机号</div><div class="preview-summary-value">' + escapeHtml(user.users_phone || '-') + '</div></div>'
|
||||||
|
+ '<div class="preview-summary-item"><div class="preview-summary-label">会员等级</div><div class="preview-summary-value">' + escapeHtml(user.users_level_name || user.users_level || '-') + '</div></div>'
|
||||||
|
+ '<div class="preview-summary-item"><div class="preview-summary-label">用户类型</div><div class="preview-summary-value">' + escapeHtml(user.users_type || '-') + '</div></div>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '<button class="btn btn-light preview-toggle" id="togglePreviewBtn" type="button">' + (state.previewExpanded ? '收起详情' : '展开详情') + '</button>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '<div class="preview-form-body' + (state.previewExpanded ? ' open' : '') + '"><div class="preview-form-inner">'
|
||||||
|
+ '<div class="preview-fields">'
|
||||||
|
+ renderPreviewField('用户状态', user.users_status)
|
||||||
|
+ renderPreviewField('用户星级', user.users_rank_level)
|
||||||
|
+ renderPreviewField('账户类型', user.users_auth_type)
|
||||||
|
+ renderPreviewField('公司ID', user.company_id)
|
||||||
|
+ renderPreviewField('用户标签', user.users_tag)
|
||||||
|
+ renderPreviewField('上级用户ID', user.users_parent_id)
|
||||||
|
+ renderPreviewField('推广码', user.users_promo_code)
|
||||||
|
+ renderPreviewField('用户组ID', user.usergroups_id)
|
||||||
|
+ renderPreviewField('证件号', user.users_id_number)
|
||||||
|
+ '</div>'
|
||||||
|
+ '<div class="attachment-grid" style="margin-top:10px; margin-bottom:0;">'
|
||||||
|
+ renderAttachmentCard('userUsersPicture', '头像附件ID', user.users_picture || '', false)
|
||||||
|
+ renderAttachmentCard('userUsersIdPicA', '证件正面附件ID', user.users_id_pic_a || '', false)
|
||||||
|
+ renderAttachmentCard('userUsersIdPicB', '证件反面附件ID', user.users_id_pic_b || '', false)
|
||||||
|
+ renderAttachmentCard('userUsersTitlePicture', '资质附件ID', user.users_title_picture || '', false)
|
||||||
|
+ '</div>'
|
||||||
|
+ '</div></div>'
|
||||||
|
+ '</div>'
|
||||||
|
}
|
||||||
|
|
||||||
function renderUserList() {
|
function renderUserList() {
|
||||||
if (!state.users.length) {
|
if (!state.users.length) {
|
||||||
userListEl.innerHTML = '<div class="empty">暂无匹配用户。</div>'
|
userListEl.innerHTML = '<div class="empty">暂无匹配用户。</div>'
|
||||||
@@ -201,12 +574,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return '<div class="table-wrap"><table><thead><tr>'
|
return '<div class="table-wrap"><table><thead><tr>'
|
||||||
+ '<th>商品名称</th><th>型号</th><th>数量</th><th>单价</th><th>状态</th><th>加入时间</th><th>购物车名</th>'
|
+ '<th>商品名称</th><th>型号/料号</th><th>数量</th><th>单价</th><th>状态</th><th>加入时间</th><th>购物车名</th>'
|
||||||
+ '</tr></thead><tbody>'
|
+ '</tr></thead><tbody>'
|
||||||
+ items.map(function (item) {
|
+ items.map(function (item) {
|
||||||
return '<tr>'
|
return '<tr>'
|
||||||
+ '<td data-label="商品名称"><div>' + escapeHtml(item.product_name || item.cart_product_id || '-') + '</div><div class="muted">' + escapeHtml(item.cart_product_id || '-') + '</div></td>'
|
+ '<td data-label="商品名称"><div>' + escapeHtml(item.product_name || item.cart_product_business_id || item.cart_product_id || '-') + '</div><div class="muted">recordId:' + escapeHtml(item.cart_product_id || '-') + '</div><div class="muted">业务ID:' + escapeHtml(item.cart_product_business_id || '-') + '</div></td>'
|
||||||
+ '<td data-label="型号">' + escapeHtml(item.product_modelnumber || '-') + '</td>'
|
+ '<td data-label="型号/料号"><div>' + escapeHtml(item.product_modelnumber || '-') + '</div><div class="muted">料号:' + escapeHtml(item.product_barcode || '-') + '</div></td>'
|
||||||
+ '<td data-label="数量">' + escapeHtml(item.cart_product_quantity || 0) + '</td>'
|
+ '<td data-label="数量">' + escapeHtml(item.cart_product_quantity || 0) + '</td>'
|
||||||
+ '<td data-label="单价">¥' + escapeHtml(item.cart_at_price || 0) + '</td>'
|
+ '<td data-label="单价">¥' + escapeHtml(item.cart_at_price || 0) + '</td>'
|
||||||
+ '<td data-label="状态">' + escapeHtml(item.cart_status || '-') + '</td>'
|
+ '<td data-label="状态">' + escapeHtml(item.cart_status || '-') + '</td>'
|
||||||
@@ -233,27 +606,44 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderUserProfileForm(user) {
|
function renderUserProfileForm(user) {
|
||||||
return '<div class="section"><h3 class="section-title">用户信息维护</h3>'
|
const draft = state.isEditMode ? ensureEditDraft(user) : null
|
||||||
+ '<div class="profile-grid">'
|
const source = draft || user
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersName">用户名称</label><input id="userUsersName" value="' + escapeHtml(user.users_name || '') + '" /></div>'
|
const dirty = hasUnsavedChanges(user)
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersPhone">手机号</label><input id="userUsersPhone" value="' + escapeHtml(user.users_phone || '') + '" /></div>'
|
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersLevel">会员等级</label><select id="userUsersLevel">' + renderUserLevelOptions(user.users_level) + '</select></div>'
|
return '<div class="section user-profile-shell"><div class="profile-toolbar"><h3 class="section-title" style="margin:0;">用户信息维护</h3>'
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersType">用户类型</label><input id="userUsersType" value="' + escapeHtml(user.users_type || '') + '" /></div>'
|
+ (state.isEditMode
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersStatus">用户状态</label><input id="userUsersStatus" value="' + escapeHtml(user.users_status || '') + '" /></div>'
|
? '<span class="edit-hint ' + (dirty ? 'dirty' : 'clean') + '">' + (dirty ? '● 有未保存变更' : '✓ 暂无未保存变更') + '</span>'
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersRankLevel">用户星级</label><input id="userUsersRankLevel" value="' + escapeHtml(user.users_rank_level === null || typeof user.users_rank_level === 'undefined' ? '' : user.users_rank_level) + '" /></div>'
|
: '')
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersAuthType">账户类型</label><input id="userUsersAuthType" value="' + escapeHtml(user.users_auth_type === null || typeof user.users_auth_type === 'undefined' ? '' : user.users_auth_type) + '" /></div>'
|
|
||||||
+ '<div class="field-block"><label class="field-label" for="userCompanyId">公司ID</label><input id="userCompanyId" value="' + escapeHtml(user.company_id || '') + '" /></div>'
|
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersTag">用户标签</label><input id="userUsersTag" value="' + escapeHtml(user.users_tag || '') + '" /></div>'
|
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersParentId">上级用户ID</label><input id="userUsersParentId" value="' + escapeHtml(user.users_parent_id || '') + '" /></div>'
|
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersPromoCode">推广码</label><input id="userUsersPromoCode" value="' + escapeHtml(user.users_promo_code || '') + '" /></div>'
|
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsergroupsId">用户组ID</label><input id="userUsergroupsId" value="' + escapeHtml(user.usergroups_id || '') + '" /></div>'
|
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersIdNumber">证件号</label><input id="userUsersIdNumber" value="' + escapeHtml(user.users_id_number || '') + '" /></div>'
|
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersPicture">头像附件ID</label><input id="userUsersPicture" value="' + escapeHtml(user.users_picture || '') + '" /></div>'
|
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersIdPicA">证件正面附件ID</label><input id="userUsersIdPicA" value="' + escapeHtml(user.users_id_pic_a || '') + '" /></div>'
|
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersIdPicB">证件反面附件ID</label><input id="userUsersIdPicB" value="' + escapeHtml(user.users_id_pic_b || '') + '" /></div>'
|
|
||||||
+ '<div class="field-block"><label class="field-label" for="userUsersTitlePicture">资质附件ID</label><input id="userUsersTitlePicture" value="' + escapeHtml(user.users_title_picture || '') + '" /></div>'
|
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
+ '<div class="detail-actions"><button class="btn btn-primary" id="saveUserBtn" type="button">保存用户信息</button></div>'
|
+ '<div class="detail-actions">'
|
||||||
|
+ (state.isEditMode
|
||||||
|
? '<button class="btn btn-primary" id="saveUserBtn" type="button">保存用户信息</button><button class="btn btn-light" id="cancelEditBtn" type="button">取消编辑</button>'
|
||||||
|
: '<button class="btn btn-primary" id="enterEditBtn" type="button">编辑用户信息</button>')
|
||||||
|
+ '</div>'
|
||||||
|
+ (state.isEditMode ? '' : renderPreviewForm(user))
|
||||||
|
+ '<div class="edit-panel' + (state.isEditMode ? ' open' : '') + '"><div class="edit-panel-inner">'
|
||||||
|
+ '<div class="attachment-grid editing">'
|
||||||
|
+ renderAttachmentCard('userUsersPicture', '头像附件ID', source.users_picture || '', true)
|
||||||
|
+ renderAttachmentCard('userUsersIdPicA', '证件正面附件ID', source.users_id_pic_a || '', true)
|
||||||
|
+ renderAttachmentCard('userUsersIdPicB', '证件反面附件ID', source.users_id_pic_b || '', true)
|
||||||
|
+ renderAttachmentCard('userUsersTitlePicture', '资质附件ID', source.users_title_picture || '', true)
|
||||||
|
+ '</div>'
|
||||||
|
+ '<div class="profile-grid">'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsersName">用户名称</label><input id="userUsersName" value="' + escapeHtml(source.users_name || '') + '" /></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsersPhone">手机号</label><input id="userUsersPhone" value="' + escapeHtml(source.users_phone || '') + '" /></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsersLevel">会员等级</label><select id="userUsersLevel">' + renderUserLevelOptions(source.users_level) + '</select></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsersType">用户类型</label><input id="userUsersType" value="' + escapeHtml(source.users_type || '') + '" /></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsersStatus">用户状态</label><input id="userUsersStatus" value="' + escapeHtml(source.users_status || '') + '" /></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsersRankLevel">用户星级</label><input id="userUsersRankLevel" value="' + escapeHtml(source.users_rank_level === null || typeof source.users_rank_level === 'undefined' ? '' : source.users_rank_level) + '" /></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsersAuthType">账户类型</label><input id="userUsersAuthType" value="' + escapeHtml(source.users_auth_type === null || typeof source.users_auth_type === 'undefined' ? '' : source.users_auth_type) + '" /></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userCompanyId">公司ID</label><input id="userCompanyId" value="' + escapeHtml(source.company_id || '') + '" /></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsersTag">用户标签</label><input id="userUsersTag" value="' + escapeHtml(source.users_tag || '') + '" /></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsersParentId">上级用户ID</label><input id="userUsersParentId" value="' + escapeHtml(source.users_parent_id || '') + '" /></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsersPromoCode">推广码</label><input id="userUsersPromoCode" value="' + escapeHtml(source.users_promo_code || '') + '" /></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsergroupsId">用户组ID</label><input id="userUsergroupsId" value="' + escapeHtml(source.usergroups_id || '') + '" /></div>'
|
||||||
|
+ '<div class="field-block"><label class="field-label" for="userUsersIdNumber">证件号</label><input id="userUsersIdNumber" value="' + escapeHtml(source.users_id_number || '') + '" /></div>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '</div></div>'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,6 +704,9 @@
|
|||||||
})
|
})
|
||||||
state.users = Array.isArray(data.items) ? data.items : []
|
state.users = Array.isArray(data.items) ? data.items : []
|
||||||
state.userLevelOptions = Array.isArray(data.user_level_options) ? data.user_level_options : []
|
state.userLevelOptions = Array.isArray(data.user_level_options) ? data.user_level_options : []
|
||||||
|
state.previewExpanded = false
|
||||||
|
state.isEditMode = false
|
||||||
|
state.userEditDraft = null
|
||||||
if (!state.users.length) {
|
if (!state.users.length) {
|
||||||
state.selectedOpenid = ''
|
state.selectedOpenid = ''
|
||||||
} else if (!getSelectedUser()) {
|
} else if (!getSelectedUser()) {
|
||||||
@@ -321,6 +714,7 @@
|
|||||||
}
|
}
|
||||||
renderUserList()
|
renderUserList()
|
||||||
renderDetail()
|
renderDetail()
|
||||||
|
refreshSelectedAttachmentDetails()
|
||||||
setStatus('加载完成,共 ' + state.users.length + ' 位用户。', 'success')
|
setStatus('加载完成,共 ' + state.users.length + ' 位用户。', 'success')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
renderUserList()
|
renderUserList()
|
||||||
@@ -336,26 +730,29 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncEditDraftFromDom()
|
||||||
|
const draft = ensureEditDraft(user)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await requestJson('/cart-order/manage-user-update', {
|
const data = await requestJson('/cart-order/manage-user-update', {
|
||||||
openid: user.openid,
|
openid: user.openid,
|
||||||
users_name: document.getElementById('userUsersName').value,
|
users_name: draft.users_name,
|
||||||
users_phone: document.getElementById('userUsersPhone').value,
|
users_phone: draft.users_phone,
|
||||||
users_level: document.getElementById('userUsersLevel').value,
|
users_level: draft.users_level,
|
||||||
users_type: document.getElementById('userUsersType').value,
|
users_type: draft.users_type,
|
||||||
users_status: document.getElementById('userUsersStatus').value,
|
users_status: draft.users_status,
|
||||||
users_rank_level: document.getElementById('userUsersRankLevel').value,
|
users_rank_level: draft.users_rank_level,
|
||||||
users_auth_type: document.getElementById('userUsersAuthType').value,
|
users_auth_type: draft.users_auth_type,
|
||||||
company_id: document.getElementById('userCompanyId').value,
|
company_id: draft.company_id,
|
||||||
users_tag: document.getElementById('userUsersTag').value,
|
users_tag: draft.users_tag,
|
||||||
users_parent_id: document.getElementById('userUsersParentId').value,
|
users_parent_id: draft.users_parent_id,
|
||||||
users_promo_code: document.getElementById('userUsersPromoCode').value,
|
users_promo_code: draft.users_promo_code,
|
||||||
usergroups_id: document.getElementById('userUsergroupsId').value,
|
usergroups_id: draft.usergroups_id,
|
||||||
users_id_number: document.getElementById('userUsersIdNumber').value,
|
users_id_number: draft.users_id_number,
|
||||||
users_picture: document.getElementById('userUsersPicture').value,
|
users_picture: draft.users_picture,
|
||||||
users_id_pic_a: document.getElementById('userUsersIdPicA').value,
|
users_id_pic_a: draft.users_id_pic_a,
|
||||||
users_id_pic_b: document.getElementById('userUsersIdPicB').value,
|
users_id_pic_b: draft.users_id_pic_b,
|
||||||
users_title_picture: document.getElementById('userUsersTitlePicture').value,
|
users_title_picture: draft.users_title_picture,
|
||||||
})
|
})
|
||||||
|
|
||||||
const updatedUser = data && data.user ? data.user : null
|
const updatedUser = data && data.user ? data.user : null
|
||||||
@@ -367,6 +764,8 @@
|
|||||||
state.users[index] = Object.assign({}, state.users[index], updatedUser)
|
state.users[index] = Object.assign({}, state.users[index], updatedUser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
state.isEditMode = false
|
||||||
|
state.userEditDraft = null
|
||||||
renderUserList()
|
renderUserList()
|
||||||
renderDetail()
|
renderDetail()
|
||||||
setStatus('用户信息保存成功。', 'success')
|
setStatus('用户信息保存成功。', 'success')
|
||||||
@@ -375,6 +774,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleAttachmentUpload(fieldId, file) {
|
||||||
|
if (!file) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = document.getElementById(fieldId)
|
||||||
|
if (!input) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
syncEditDraftFromDom()
|
||||||
|
|
||||||
|
const labelMap = {
|
||||||
|
userUsersPicture: '头像',
|
||||||
|
userUsersIdPicA: '证件正面',
|
||||||
|
userUsersIdPicB: '证件反面',
|
||||||
|
userUsersTitlePicture: '资质附件',
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setStatus('正在上传' + (labelMap[fieldId] || '附件') + '...', '')
|
||||||
|
const uploaded = await uploadAttachment(file, fieldId)
|
||||||
|
input.value = uploaded.attachments_id || ''
|
||||||
|
if (state.userEditDraft) {
|
||||||
|
const draftFieldMap = {
|
||||||
|
userUsersPicture: 'users_picture',
|
||||||
|
userUsersIdPicA: 'users_id_pic_a',
|
||||||
|
userUsersIdPicB: 'users_id_pic_b',
|
||||||
|
userUsersTitlePicture: 'users_title_picture',
|
||||||
|
}
|
||||||
|
state.userEditDraft[draftFieldMap[fieldId]] = uploaded.attachments_id || ''
|
||||||
|
}
|
||||||
|
state.attachmentDetails[fieldId] = uploaded
|
||||||
|
renderDetail()
|
||||||
|
setStatus((labelMap[fieldId] || '附件') + '上传成功,请点击“保存用户信息”完成写入。', 'success')
|
||||||
|
} catch (err) {
|
||||||
|
setStatus(err.message || '上传附件失败', 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
userListEl.addEventListener('click', function (event) {
|
userListEl.addEventListener('click', function (event) {
|
||||||
const target = event.target && event.target.closest ? event.target.closest('[data-openid]') : null
|
const target = event.target && event.target.closest ? event.target.closest('[data-openid]') : null
|
||||||
if (!target) {
|
if (!target) {
|
||||||
@@ -383,13 +822,91 @@
|
|||||||
state.selectedOpenid = normalizeText(target.getAttribute('data-openid'))
|
state.selectedOpenid = normalizeText(target.getAttribute('data-openid'))
|
||||||
renderUserList()
|
renderUserList()
|
||||||
renderDetail()
|
renderDetail()
|
||||||
|
refreshSelectedAttachmentDetails()
|
||||||
})
|
})
|
||||||
|
|
||||||
detailWrapEl.addEventListener('click', function (event) {
|
detailWrapEl.addEventListener('click', function (event) {
|
||||||
const target = event.target
|
const target = event.target
|
||||||
|
if (target && target.id === 'enterEditBtn') {
|
||||||
|
const user = getSelectedUser()
|
||||||
|
state.isEditMode = true
|
||||||
|
state.previewExpanded = true
|
||||||
|
state.userEditDraft = user ? buildUserDraft(user) : null
|
||||||
|
renderDetail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target && target.id === 'togglePreviewBtn') {
|
||||||
|
state.previewExpanded = !state.previewExpanded
|
||||||
|
renderDetail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target && target.id === 'cancelEditBtn') {
|
||||||
|
state.isEditMode = false
|
||||||
|
state.userEditDraft = null
|
||||||
|
renderDetail()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (target && target.id === 'saveUserBtn') {
|
if (target && target.id === 'saveUserBtn') {
|
||||||
saveSelectedUser()
|
saveSelectedUser()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (target && target.getAttribute) {
|
||||||
|
const uploadTrigger = target.getAttribute('data-upload-trigger')
|
||||||
|
if (uploadTrigger) {
|
||||||
|
const fileInput = document.getElementById(uploadTrigger + 'File')
|
||||||
|
if (fileInput) {
|
||||||
|
fileInput.click()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearAttachment = target.getAttribute('data-clear-attachment')
|
||||||
|
if (clearAttachment) {
|
||||||
|
syncEditDraftFromDom()
|
||||||
|
const input = document.getElementById(clearAttachment)
|
||||||
|
const fileInput = document.getElementById(clearAttachment + 'File')
|
||||||
|
if (input) {
|
||||||
|
input.value = ''
|
||||||
|
}
|
||||||
|
if (fileInput) {
|
||||||
|
fileInput.value = ''
|
||||||
|
}
|
||||||
|
if (state.userEditDraft) {
|
||||||
|
const draftFieldMap = {
|
||||||
|
userUsersPicture: 'users_picture',
|
||||||
|
userUsersIdPicA: 'users_id_pic_a',
|
||||||
|
userUsersIdPicB: 'users_id_pic_b',
|
||||||
|
userUsersTitlePicture: 'users_title_picture',
|
||||||
|
}
|
||||||
|
state.userEditDraft[draftFieldMap[clearAttachment]] = ''
|
||||||
|
}
|
||||||
|
state.attachmentDetails[clearAttachment] = null
|
||||||
|
renderDetail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
detailWrapEl.addEventListener('input', function () {
|
||||||
|
syncEditDraftFromDom()
|
||||||
|
})
|
||||||
|
|
||||||
|
detailWrapEl.addEventListener('change', function (event) {
|
||||||
|
const target = event.target
|
||||||
|
if (!target || !target.getAttribute) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldId = target.getAttribute('data-upload-field')
|
||||||
|
if (!fieldId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = target.files && target.files[0] ? target.files[0] : null
|
||||||
|
handleAttachmentUpload(fieldId, file)
|
||||||
})
|
})
|
||||||
|
|
||||||
document.getElementById('searchBtn').addEventListener('click', loadUsers)
|
document.getElementById('searchBtn').addEventListener('click', loadUsers)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
.btn-danger { background: #dc2626; color: #fff; }
|
.btn-danger { background: #dc2626; color: #fff; }
|
||||||
.btn-light { background: #f8fafc; color: #334155; border: 1px solid #dbe3f0; }
|
.btn-light { background: #f8fafc; color: #334155; border: 1px solid #dbe3f0; }
|
||||||
.panel { border-radius: 20px; padding: 18px; }
|
.panel { border-radius: 20px; padding: 18px; }
|
||||||
.toolbar { display: grid; grid-template-columns: 1.3fr 1fr 1fr auto auto; gap: 12px; margin-bottom: 18px; }
|
.toolbar { display: grid; grid-template-columns: 1.3fr auto 1fr 1fr auto auto; gap: 12px; margin-bottom: 18px; }
|
||||||
input, textarea, select { width: 100%; border: 1px solid #cbd5e1; border-radius: 12px; padding: 10px 12px; font-size: 14px; background: #fff; }
|
input, textarea, select { width: 100%; border: 1px solid #cbd5e1; border-radius: 12px; padding: 10px 12px; font-size: 14px; background: #fff; }
|
||||||
textarea { min-height: 88px; resize: vertical; }
|
textarea { min-height: 88px; resize: vertical; }
|
||||||
table { width: 100%; border-collapse: collapse; overflow: hidden; border-radius: 16px; }
|
table { width: 100%; border-collapse: collapse; overflow: hidden; border-radius: 16px; }
|
||||||
@@ -113,6 +113,7 @@
|
|||||||
<section class="panel">
|
<section class="panel">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<input id="keywordInput" placeholder="按字典名称模糊搜索" />
|
<input id="keywordInput" placeholder="按字典名称模糊搜索" />
|
||||||
|
<select id="categorySelect"><option value="">全部分类</option></select>
|
||||||
<input id="detailInput" placeholder="查询指定字典名称" />
|
<input id="detailInput" placeholder="查询指定字典名称" />
|
||||||
<button class="btn btn-secondary" id="listBtn" type="button">查询全部</button>
|
<button class="btn btn-secondary" id="listBtn" type="button">查询全部</button>
|
||||||
<button class="btn btn-light" id="detailBtn" type="button">查询指定</button>
|
<button class="btn btn-light" id="detailBtn" type="button">查询指定</button>
|
||||||
@@ -217,6 +218,7 @@
|
|||||||
|
|
||||||
const statusEl = document.getElementById('status')
|
const statusEl = document.getElementById('status')
|
||||||
const keywordInput = document.getElementById('keywordInput')
|
const keywordInput = document.getElementById('keywordInput')
|
||||||
|
const categorySelect = document.getElementById('categorySelect')
|
||||||
const detailInput = document.getElementById('detailInput')
|
const detailInput = document.getElementById('detailInput')
|
||||||
const tableBody = document.getElementById('tableBody')
|
const tableBody = document.getElementById('tableBody')
|
||||||
const editorModal = document.getElementById('editorModal')
|
const editorModal = document.getElementById('editorModal')
|
||||||
@@ -405,6 +407,44 @@
|
|||||||
.replace(/'/g, ''')
|
.replace(/'/g, ''')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractCategory(dictName) {
|
||||||
|
var name = String(dictName || '')
|
||||||
|
if (name.length >= 2) {
|
||||||
|
return name.substring(0, 2)
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateCategorySelect(list) {
|
||||||
|
var categories = {}
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
var cat = extractCategory(list[i].dict_name)
|
||||||
|
if (cat) {
|
||||||
|
categories[cat] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var sorted = Object.keys(categories).sort()
|
||||||
|
var current = categorySelect.value
|
||||||
|
categorySelect.innerHTML = '<option value="">全部分类</option>'
|
||||||
|
for (var j = 0; j < sorted.length; j++) {
|
||||||
|
var opt = document.createElement('option')
|
||||||
|
opt.value = sorted[j]
|
||||||
|
opt.textContent = sorted[j]
|
||||||
|
categorySelect.appendChild(opt)
|
||||||
|
}
|
||||||
|
if (current && categories[current]) {
|
||||||
|
categorySelect.value = current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterByCategory(list) {
|
||||||
|
var selected = categorySelect.value
|
||||||
|
if (!selected) return list
|
||||||
|
return list.filter(function (item) {
|
||||||
|
return extractCategory(item.dict_name) === selected
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function randomEnumSeed() {
|
function randomEnumSeed() {
|
||||||
const first = enumChars[Math.floor(Math.random() * enumChars.length)]
|
const first = enumChars[Math.floor(Math.random() * enumChars.length)]
|
||||||
const second = enumChars[Math.floor(Math.random() * enumChars.length)]
|
const second = enumChars[Math.floor(Math.random() * enumChars.length)]
|
||||||
@@ -775,7 +815,8 @@
|
|||||||
const data = await request(API_BASE + '/dictionary/list', { keyword: keywordInput.value.trim() })
|
const data = await request(API_BASE + '/dictionary/list', { keyword: keywordInput.value.trim() })
|
||||||
state.list = data.items || []
|
state.list = data.items || []
|
||||||
state.expandedPreviewKey = ''
|
state.expandedPreviewKey = ''
|
||||||
renderTable(state.list)
|
populateCategorySelect(state.list)
|
||||||
|
renderTable(filterByCategory(state.list))
|
||||||
setStatus('查询成功,共 ' + state.list.length + ' 条。', 'success')
|
setStatus('查询成功,共 ' + state.list.length + ' 条。', 'success')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setStatus(err.message || '查询失败', 'error')
|
setStatus(err.message || '查询失败', 'error')
|
||||||
@@ -797,7 +838,8 @@
|
|||||||
const data = await request(API_BASE + '/dictionary/detail', { dict_name: dictName })
|
const data = await request(API_BASE + '/dictionary/detail', { dict_name: dictName })
|
||||||
state.list = [data]
|
state.list = [data]
|
||||||
state.expandedPreviewKey = ''
|
state.expandedPreviewKey = ''
|
||||||
renderTable(state.list)
|
populateCategorySelect(state.list)
|
||||||
|
renderTable(filterByCategory(state.list))
|
||||||
setStatus('查询详情成功。', 'success')
|
setStatus('查询详情成功。', 'success')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setStatus(err.message || '查询失败', 'error')
|
setStatus(err.message || '查询失败', 'error')
|
||||||
@@ -1108,9 +1150,13 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
document.getElementById('listBtn').addEventListener('click', loadList)
|
document.getElementById('listBtn').addEventListener('click', loadList)
|
||||||
|
categorySelect.addEventListener('change', function () {
|
||||||
|
renderTable(filterByCategory(state.list))
|
||||||
|
})
|
||||||
document.getElementById('detailBtn').addEventListener('click', loadDetail)
|
document.getElementById('detailBtn').addEventListener('click', loadDetail)
|
||||||
document.getElementById('resetBtn').addEventListener('click', function () {
|
document.getElementById('resetBtn').addEventListener('click', function () {
|
||||||
keywordInput.value = ''
|
keywordInput.value = ''
|
||||||
|
categorySelect.value = ''
|
||||||
detailInput.value = ''
|
detailInput.value = ''
|
||||||
state.list = []
|
state.list = []
|
||||||
renderTable([])
|
renderTable([])
|
||||||
|
|||||||
@@ -84,6 +84,16 @@
|
|||||||
.loading-card { min-width: min(92vw, 360px); padding: 24px 22px; border-radius: 20px; background: rgba(255,255,255,0.98); box-shadow: 0 28px 70px rgba(15, 23, 42, 0.22); border: 1px solid #dbe3f0; text-align: center; }
|
.loading-card { min-width: min(92vw, 360px); padding: 24px 22px; border-radius: 20px; background: rgba(255,255,255,0.98); box-shadow: 0 28px 70px rgba(15, 23, 42, 0.22); border: 1px solid #dbe3f0; text-align: center; }
|
||||||
.loading-spinner { width: 44px; height: 44px; margin: 0 auto 14px; border-radius: 999px; border: 4px solid #dbeafe; border-top-color: #2563eb; animation: docSpin 0.9s linear infinite; }
|
.loading-spinner { width: 44px; height: 44px; margin: 0 auto 14px; border-radius: 999px; border: 4px solid #dbeafe; border-top-color: #2563eb; animation: docSpin 0.9s linear infinite; }
|
||||||
.loading-text { color: #0f172a; font-size: 15px; font-weight: 700; }
|
.loading-text { color: #0f172a; font-size: 15px; font-weight: 700; }
|
||||||
|
.pagination { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; }
|
||||||
|
.pagination-info { color: #475569; font-size: 14px; }
|
||||||
|
.pagination-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||||
|
.pagination-size { display: inline-flex; align-items: center; gap: 8px; color: #475569; font-size: 14px; }
|
||||||
|
.pagination-btn { min-width: 42px; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; cursor: pointer; font-weight: 600; }
|
||||||
|
.pagination-select { min-width: 88px; width: auto; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; font-size: 14px; }
|
||||||
|
.pagination-btn.active { background: #2563eb; border-color: #2563eb; color: #fff; }
|
||||||
|
.pagination-btn:disabled { cursor: not-allowed; color: #94a3b8; background: #f8fafc; }
|
||||||
|
.pagination-btn:not(:disabled):hover { border-color: #94a3b8; background: #f8fafc; }
|
||||||
|
.pagination-btn.active:hover { background: #2563eb; border-color: #2563eb; }
|
||||||
html[data-theme="dark"] .editor-banner { background: rgba(30, 41, 59, 0.86); color: #bfdbfe; }
|
html[data-theme="dark"] .editor-banner { background: rgba(30, 41, 59, 0.86); color: #bfdbfe; }
|
||||||
html[data-theme="dark"] .file-box { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.22); box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.08); }
|
html[data-theme="dark"] .file-box { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.22); box-shadow: inset 0 0 0 1px rgba(148, 163, 184, 0.08); }
|
||||||
html[data-theme="dark"] .choice-switch { background: rgba(15, 23, 42, 0.86); border-color: rgba(148, 163, 184, 0.26); }
|
html[data-theme="dark"] .choice-switch { background: rgba(15, 23, 42, 0.86); border-color: rgba(148, 163, 184, 0.26); }
|
||||||
@@ -97,6 +107,15 @@
|
|||||||
html[data-theme="dark"] .file-preview,
|
html[data-theme="dark"] .file-preview,
|
||||||
html[data-theme="dark"] .file-card-icon { background: rgba(2, 6, 23, 0.9); border-color: rgba(148, 163, 184, 0.24); color: #e2e8f0; }
|
html[data-theme="dark"] .file-card-icon { background: rgba(2, 6, 23, 0.9); border-color: rgba(148, 163, 184, 0.24); color: #e2e8f0; }
|
||||||
html[data-theme="dark"] .file-card-icon:hover { background: rgba(30, 41, 59, 0.82); border-color: rgba(148, 163, 184, 0.4); }
|
html[data-theme="dark"] .file-card-icon:hover { background: rgba(30, 41, 59, 0.82); border-color: rgba(148, 163, 184, 0.4); }
|
||||||
|
html[data-theme="dark"] .pagination-info,
|
||||||
|
html[data-theme="dark"] .pagination-size { color: #cbd5e1; }
|
||||||
|
html[data-theme="dark"] .pagination-btn { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #e2e8f0; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
|
||||||
|
html[data-theme="dark"] .pagination-btn:not(:disabled):hover { background: rgba(30, 41, 59, 0.96); border-color: rgba(96, 165, 250, 0.5); color: #f8fafc; }
|
||||||
|
html[data-theme="dark"] .pagination-btn.active,
|
||||||
|
html[data-theme="dark"] .pagination-btn.active:hover { background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%); border-color: #3b82f6; color: #eff6ff; box-shadow: 0 10px 24px rgba(37, 99, 235, 0.28); }
|
||||||
|
html[data-theme="dark"] .pagination-btn:disabled { background: rgba(15, 23, 42, 0.58); border-color: rgba(71, 85, 105, 0.45); color: #64748b; box-shadow: none; }
|
||||||
|
html[data-theme="dark"] .pagination-select { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #f8fafc; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
|
||||||
|
html[data-theme="dark"] .pagination-select:focus { outline: none; border-color: #60a5fa; box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.18); }
|
||||||
@keyframes docSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
@keyframes docSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||||||
@media (max-width: 960px) {
|
@media (max-width: 960px) {
|
||||||
.grid, .file-group { grid-template-columns: 1fr; }
|
.grid, .file-group { grid-template-columns: 1fr; }
|
||||||
@@ -295,6 +314,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pagination" id="listPagination"></div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -342,6 +362,7 @@
|
|||||||
const hotelTypeTagsEl = document.getElementById('hotelTypeTags')
|
const hotelTypeTagsEl = document.getElementById('hotelTypeTags')
|
||||||
const listTitleKeywordEl = document.getElementById('listTitleKeyword')
|
const listTitleKeywordEl = document.getElementById('listTitleKeyword')
|
||||||
const listTypeFilterEl = document.getElementById('listTypeFilter')
|
const listTypeFilterEl = document.getElementById('listTypeFilter')
|
||||||
|
const listPaginationEl = document.getElementById('listPagination')
|
||||||
const imageViewerEl = document.getElementById('imageViewer')
|
const imageViewerEl = document.getElementById('imageViewer')
|
||||||
const imageViewerImgEl = document.getElementById('imageViewerImg')
|
const imageViewerImgEl = document.getElementById('imageViewerImg')
|
||||||
const loadingMaskEl = document.getElementById('loadingMask')
|
const loadingMaskEl = document.getElementById('loadingMask')
|
||||||
@@ -388,6 +409,7 @@
|
|||||||
const state = {
|
const state = {
|
||||||
list: [],
|
list: [],
|
||||||
fullList: [],
|
fullList: [],
|
||||||
|
filteredList: [],
|
||||||
mode: 'idle',
|
mode: 'idle',
|
||||||
editingId: '',
|
editingId: '',
|
||||||
editingSource: null,
|
editingSource: null,
|
||||||
@@ -413,6 +435,12 @@
|
|||||||
titleKeyword: '',
|
titleKeyword: '',
|
||||||
type: '',
|
type: '',
|
||||||
},
|
},
|
||||||
|
pagination: {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
totalPages: 1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStatus(message, type) {
|
function setStatus(message, type) {
|
||||||
@@ -1005,6 +1033,102 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clampPage(page, totalPages) {
|
||||||
|
const normalizedPage = Number(page) || 1
|
||||||
|
const normalizedTotalPages = Math.max(1, Number(totalPages) || 1)
|
||||||
|
return Math.min(Math.max(1, normalizedPage), normalizedTotalPages)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVisiblePageNumbers(page, totalPages) {
|
||||||
|
const result = []
|
||||||
|
const start = Math.max(1, page - 2)
|
||||||
|
const end = Math.min(totalPages, start + 4)
|
||||||
|
const adjustedStart = Math.max(1, end - 4)
|
||||||
|
|
||||||
|
for (let current = adjustedStart; current <= end; current += 1) {
|
||||||
|
result.push(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPageSizeSelector() {
|
||||||
|
const options = [10, 20, 50, 100, 500]
|
||||||
|
return '<label class="pagination-size">每页'
|
||||||
|
+ '<select class="pagination-select" onchange="window.__setDocumentPageSize(this.value)">'
|
||||||
|
+ options.map(function (size) {
|
||||||
|
return '<option value="' + size + '"' + (Number(state.pagination.pageSize) === size ? ' selected' : '') + '>' + size + '</option>'
|
||||||
|
}).join('')
|
||||||
|
+ '</select>'
|
||||||
|
+ '条</label>'
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPagination() {
|
||||||
|
if (!listPaginationEl) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = Number(state.pagination.total || 0)
|
||||||
|
const page = Number(state.pagination.page || 1)
|
||||||
|
const totalPages = Math.max(1, Number(state.pagination.totalPages || 1))
|
||||||
|
const sizeSelector = renderPageSizeSelector()
|
||||||
|
|
||||||
|
if (!total) {
|
||||||
|
listPaginationEl.innerHTML = '<div class="pagination-info">共 0 条</div>'
|
||||||
|
+ '<div class="pagination-actions">' + sizeSelector + '</div>'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const visiblePageNumbers = getVisiblePageNumbers(page, totalPages)
|
||||||
|
const startIndex = (page - 1) * state.pagination.pageSize + 1
|
||||||
|
const endIndex = Math.min(total, page * state.pagination.pageSize)
|
||||||
|
const buttons = []
|
||||||
|
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(' + (page - 1) + ')" ' + (page <= 1 ? 'disabled' : '') + '>上一页</button>')
|
||||||
|
|
||||||
|
if (visiblePageNumbers[0] > 1) {
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(1)">1</button>')
|
||||||
|
if (visiblePageNumbers[0] > 2) {
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < visiblePageNumbers.length; i += 1) {
|
||||||
|
const current = visiblePageNumbers[i]
|
||||||
|
buttons.push('<button class="pagination-btn' + (current === page ? ' active' : '') + '" type="button" onclick="window.__gotoDocumentPage(' + current + ')">' + current + '</button>')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visiblePageNumbers.length && visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages) {
|
||||||
|
if (visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages - 1) {
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
|
||||||
|
}
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(' + totalPages + ')">' + totalPages + '</button>')
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoDocumentPage(' + (page + 1) + ')" ' + (page >= totalPages ? 'disabled' : '') + '>下一页</button>')
|
||||||
|
|
||||||
|
listPaginationEl.innerHTML = '<div class="pagination-info">共 ' + total + ' 条,显示第 ' + startIndex + '-' + endIndex + ' 条,第 ' + page + '/' + totalPages + ' 页</div>'
|
||||||
|
+ '<div class="pagination-actions">' + sizeSelector + buttons.join('') + '</div>'
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDocumentListView() {
|
||||||
|
const filteredList = applyListFiltersToList(state.fullList)
|
||||||
|
const pageSize = Math.max(1, Number(state.pagination.pageSize || 10))
|
||||||
|
const total = filteredList.length
|
||||||
|
const totalPages = Math.max(1, Math.ceil(total / pageSize))
|
||||||
|
const currentPage = clampPage(state.pagination.page, totalPages)
|
||||||
|
const startIndex = (currentPage - 1) * pageSize
|
||||||
|
|
||||||
|
state.filteredList = filteredList
|
||||||
|
state.pagination.page = currentPage
|
||||||
|
state.pagination.total = total
|
||||||
|
state.pagination.totalPages = totalPages
|
||||||
|
state.list = filteredList.slice(startIndex, startIndex + pageSize)
|
||||||
|
|
||||||
|
renderTable()
|
||||||
|
renderPagination()
|
||||||
|
}
|
||||||
|
|
||||||
function renderTable() {
|
function renderTable() {
|
||||||
if (!state.list.length) {
|
if (!state.list.length) {
|
||||||
tableBody.innerHTML = '<tr><td colspan="5" class="empty">暂无文档数据。</td></tr>'
|
tableBody.innerHTML = '<tr><td colspan="5" class="empty">暂无文档数据。</td></tr>'
|
||||||
@@ -1132,6 +1256,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goToDocumentPage(page) {
|
||||||
|
const nextPage = clampPage(page, state.pagination.totalPages)
|
||||||
|
if (nextPage === state.pagination.page) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.pagination.page = nextPage
|
||||||
|
updateDocumentListView()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDocumentPageSize(pageSize) {
|
||||||
|
const nextPageSize = Number(pageSize) || 10
|
||||||
|
if (nextPageSize === state.pagination.pageSize) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.pagination.pageSize = nextPageSize
|
||||||
|
state.pagination.page = 1
|
||||||
|
updateDocumentListView()
|
||||||
|
}
|
||||||
|
|
||||||
async function loadDocuments() {
|
async function loadDocuments() {
|
||||||
setStatus('正在加载文档列表...', '')
|
setStatus('正在加载文档列表...', '')
|
||||||
showLoading('正在加载文档列表...')
|
showLoading('正在加载文档列表...')
|
||||||
@@ -1140,9 +1285,8 @@
|
|||||||
document_type: state.listFilters.type,
|
document_type: state.listFilters.type,
|
||||||
})
|
})
|
||||||
state.fullList = Array.isArray(data.items) ? data.items : []
|
state.fullList = Array.isArray(data.items) ? data.items : []
|
||||||
state.list = applyListFiltersToList(state.fullList)
|
updateDocumentListView()
|
||||||
renderTable()
|
setStatus('文档列表已刷新,共 ' + state.pagination.total + ' 条。', 'success')
|
||||||
setStatus('文档列表已刷新,共 ' + state.list.length + ' 条。', 'success')
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setStatus(err.message || '加载列表失败', 'error')
|
setStatus(err.message || '加载列表失败', 'error')
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1177,8 +1321,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function queryDocumentsWithAutoSave() {
|
async function queryDocumentsWithAutoSave(resetPage) {
|
||||||
syncListFiltersFromInputs()
|
syncListFiltersFromInputs()
|
||||||
|
if (resetPage) {
|
||||||
|
state.pagination.page = 1
|
||||||
|
}
|
||||||
await saveAndHideEditorBeforeListQuery()
|
await saveAndHideEditorBeforeListQuery()
|
||||||
applyListFiltersToInputs()
|
applyListFiltersToInputs()
|
||||||
await loadDocuments()
|
await loadDocuments()
|
||||||
@@ -1490,23 +1637,33 @@
|
|||||||
updateSelection(fieldName, target.value, !!target.checked)
|
updateSelection(fieldName, target.value, !!target.checked)
|
||||||
})
|
})
|
||||||
|
|
||||||
document.getElementById('reloadBtn').addEventListener('click', queryDocumentsWithAutoSave)
|
window.__gotoDocumentPage = goToDocumentPage
|
||||||
document.getElementById('searchBtn').addEventListener('click', queryDocumentsWithAutoSave)
|
window.__setDocumentPageSize = setDocumentPageSize
|
||||||
|
|
||||||
|
document.getElementById('reloadBtn').addEventListener('click', function () {
|
||||||
|
queryDocumentsWithAutoSave(false)
|
||||||
|
})
|
||||||
|
document.getElementById('searchBtn').addEventListener('click', function () {
|
||||||
|
queryDocumentsWithAutoSave(true)
|
||||||
|
})
|
||||||
document.getElementById('clearSearchBtn').addEventListener('click', function () {
|
document.getElementById('clearSearchBtn').addEventListener('click', function () {
|
||||||
state.listFilters.titleKeyword = ''
|
state.listFilters.titleKeyword = ''
|
||||||
state.listFilters.type = ''
|
state.listFilters.type = ''
|
||||||
|
state.pagination.page = 1
|
||||||
applyListFiltersToInputs()
|
applyListFiltersToInputs()
|
||||||
queryDocumentsWithAutoSave()
|
queryDocumentsWithAutoSave(false)
|
||||||
})
|
})
|
||||||
if (listTitleKeywordEl) {
|
if (listTitleKeywordEl) {
|
||||||
listTitleKeywordEl.addEventListener('keydown', function (event) {
|
listTitleKeywordEl.addEventListener('keydown', function (event) {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
queryDocumentsWithAutoSave()
|
queryDocumentsWithAutoSave(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (listTypeFilterEl) {
|
if (listTypeFilterEl) {
|
||||||
listTypeFilterEl.addEventListener('change', queryDocumentsWithAutoSave)
|
listTypeFilterEl.addEventListener('change', function () {
|
||||||
|
queryDocumentsWithAutoSave(true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
document.getElementById('createModeBtn').addEventListener('click', function () {
|
document.getElementById('createModeBtn').addEventListener('click', function () {
|
||||||
enterCreateMode()
|
enterCreateMode()
|
||||||
|
|||||||
@@ -50,6 +50,10 @@
|
|||||||
<h2>产品管理</h2>
|
<h2>产品管理</h2>
|
||||||
<a class="btn" href="/pb/manage/product-manage">进入产品管理</a>
|
<a class="btn" href="/pb/manage/product-manage">进入产品管理</a>
|
||||||
</article>
|
</article>
|
||||||
|
<article class="card">
|
||||||
|
<h2>方案预设管理</h2>
|
||||||
|
<a class="btn" href="/pb/manage/scheme">进入方案预设管理</a>
|
||||||
|
</article>
|
||||||
<article class="card">
|
<article class="card">
|
||||||
<h2>SDK 权限管理</h2>
|
<h2>SDK 权限管理</h2>
|
||||||
<a class="btn" href="/pb/manage/sdk-permission-manage">进入权限管理</a>
|
<a class="btn" href="/pb/manage/sdk-permission-manage">进入权限管理</a>
|
||||||
|
|||||||
@@ -76,6 +76,25 @@
|
|||||||
.loading-card { min-width: min(92vw, 360px); padding: 24px 22px; border-radius: 20px; background: rgba(255,255,255,0.98); box-shadow: 0 28px 70px rgba(15, 23, 42, 0.22); border: 1px solid #dbe3f0; text-align: center; }
|
.loading-card { min-width: min(92vw, 360px); padding: 24px 22px; border-radius: 20px; background: rgba(255,255,255,0.98); box-shadow: 0 28px 70px rgba(15, 23, 42, 0.22); border: 1px solid #dbe3f0; text-align: center; }
|
||||||
.loading-spinner { width: 44px; height: 44px; margin: 0 auto 14px; border-radius: 999px; border: 4px solid #dbeafe; border-top-color: #2563eb; animation: spin 0.9s linear infinite; }
|
.loading-spinner { width: 44px; height: 44px; margin: 0 auto 14px; border-radius: 999px; border: 4px solid #dbeafe; border-top-color: #2563eb; animation: spin 0.9s linear infinite; }
|
||||||
.loading-text { color: #0f172a; font-size: 15px; font-weight: 700; }
|
.loading-text { color: #0f172a; font-size: 15px; font-weight: 700; }
|
||||||
|
.pagination { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; }
|
||||||
|
.pagination-info { color: #475569; font-size: 14px; }
|
||||||
|
.pagination-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||||
|
.pagination-size { display: inline-flex; align-items: center; gap: 8px; color: #475569; font-size: 14px; }
|
||||||
|
.pagination-btn { min-width: 42px; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; cursor: pointer; font-weight: 600; }
|
||||||
|
.pagination-select { min-width: 88px; width: auto; padding: 8px 12px; border-radius: 10px; border: 1px solid #dbe3f0; background: #fff; color: #334155; font-size: 14px; }
|
||||||
|
.pagination-btn.active { background: #2563eb; border-color: #2563eb; color: #fff; }
|
||||||
|
.pagination-btn:disabled { cursor: not-allowed; color: #94a3b8; background: #f8fafc; }
|
||||||
|
.pagination-btn:not(:disabled):hover { border-color: #94a3b8; background: #f8fafc; }
|
||||||
|
.pagination-btn.active:hover { background: #2563eb; border-color: #2563eb; }
|
||||||
|
html[data-theme="dark"] .pagination-info,
|
||||||
|
html[data-theme="dark"] .pagination-size { color: #cbd5e1; }
|
||||||
|
html[data-theme="dark"] .pagination-btn { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #e2e8f0; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
|
||||||
|
html[data-theme="dark"] .pagination-btn:not(:disabled):hover { background: rgba(30, 41, 59, 0.96); border-color: rgba(96, 165, 250, 0.5); color: #f8fafc; }
|
||||||
|
html[data-theme="dark"] .pagination-btn.active,
|
||||||
|
html[data-theme="dark"] .pagination-btn.active:hover { background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%); border-color: #3b82f6; color: #eff6ff; box-shadow: 0 10px 24px rgba(37, 99, 235, 0.28); }
|
||||||
|
html[data-theme="dark"] .pagination-btn:disabled { background: rgba(15, 23, 42, 0.58); border-color: rgba(71, 85, 105, 0.45); color: #64748b; box-shadow: none; }
|
||||||
|
html[data-theme="dark"] .pagination-select { background: rgba(15, 23, 42, 0.92); border-color: rgba(148, 163, 184, 0.24); color: #f8fafc; box-shadow: inset 0 1px 0 rgba(255,255,255,0.04); }
|
||||||
|
html[data-theme="dark"] .pagination-select:focus { outline: none; border-color: #60a5fa; box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.18); }
|
||||||
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
||||||
@media (max-width: 960px) {
|
@media (max-width: 960px) {
|
||||||
.grid, .toolbar, .option-list { grid-template-columns: 1fr; }
|
.grid, .toolbar, .option-list { grid-template-columns: 1fr; }
|
||||||
@@ -103,7 +122,7 @@
|
|||||||
|
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<input id="keywordInput" placeholder="按名称/型号/标签搜索" />
|
<input id="keywordInput" placeholder="按名称/型号/料号/标签搜索" />
|
||||||
<select id="statusFilter"><option value="">全部状态</option></select>
|
<select id="statusFilter"><option value="">全部状态</option></select>
|
||||||
<select id="categoryFilter"><option value="">全部分类</option></select>
|
<select id="categoryFilter"><option value="">全部分类</option></select>
|
||||||
<button class="btn btn-secondary" id="searchBtn" type="button">查询</button>
|
<button class="btn btn-secondary" id="searchBtn" type="button">查询</button>
|
||||||
@@ -116,13 +135,19 @@
|
|||||||
<div class="muted" id="editorMode">当前模式:新建</div>
|
<div class="muted" id="editorMode">当前模式:新建</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid" style="margin-top:14px;">
|
<div class="grid" style="margin-top:14px;">
|
||||||
<div>
|
<div class="full quarter-row">
|
||||||
|
<div class="quarter-col-2">
|
||||||
<label for="prodListName">产品名称</label>
|
<label for="prodListName">产品名称</label>
|
||||||
<input id="prodListName" placeholder="必填" />
|
<input id="prodListName" placeholder="必填" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="quarter-col">
|
||||||
|
<label for="prodListBarcode">产品料号</label>
|
||||||
|
<input id="prodListBarcode" placeholder="可选" />
|
||||||
|
</div>
|
||||||
|
<div class="quarter-col">
|
||||||
<label for="prodListModel">产品型号</label>
|
<label for="prodListModel">产品型号</label>
|
||||||
<input id="prodListModel" placeholder="可选" />
|
<input id="prodListModel" placeholder="可选" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>产品分类(必填,单选)</label>
|
<label>产品分类(必填,单选)</label>
|
||||||
@@ -309,6 +334,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pagination" id="listPagination"></div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -324,6 +350,10 @@
|
|||||||
<label for="copyModelInput">新产品型号</label>
|
<label for="copyModelInput">新产品型号</label>
|
||||||
<input id="copyModelInput" placeholder="请输入新产品型号" />
|
<input id="copyModelInput" placeholder="请输入新产品型号" />
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="copyBarcodeInput">新产品料号</label>
|
||||||
|
<input id="copyBarcodeInput" placeholder="请输入新产品料号" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button class="btn btn-light" id="copyCancelBtn" type="button">取消</button>
|
<button class="btn btn-light" id="copyCancelBtn" type="button">取消</button>
|
||||||
@@ -368,6 +398,7 @@
|
|||||||
const copyModalEl = document.getElementById('copyModal')
|
const copyModalEl = document.getElementById('copyModal')
|
||||||
const copyNameInputEl = document.getElementById('copyNameInput')
|
const copyNameInputEl = document.getElementById('copyNameInput')
|
||||||
const copyModelInputEl = document.getElementById('copyModelInput')
|
const copyModelInputEl = document.getElementById('copyModelInput')
|
||||||
|
const copyBarcodeInputEl = document.getElementById('copyBarcodeInput')
|
||||||
const loadingMaskEl = document.getElementById('loadingMask')
|
const loadingMaskEl = document.getElementById('loadingMask')
|
||||||
const loadingTextEl = document.getElementById('loadingText')
|
const loadingTextEl = document.getElementById('loadingText')
|
||||||
const iconPreviewListEl = document.getElementById('iconPreviewList')
|
const iconPreviewListEl = document.getElementById('iconPreviewList')
|
||||||
@@ -375,11 +406,13 @@
|
|||||||
const statusFilterEl = document.getElementById('statusFilter')
|
const statusFilterEl = document.getElementById('statusFilter')
|
||||||
const categoryFilterEl = document.getElementById('categoryFilter')
|
const categoryFilterEl = document.getElementById('categoryFilter')
|
||||||
const prodListSortHintEl = document.getElementById('prodListSortHint')
|
const prodListSortHintEl = document.getElementById('prodListSortHint')
|
||||||
|
const listPaginationEl = document.getElementById('listPagination')
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
keyword: document.getElementById('keywordInput'),
|
keyword: document.getElementById('keywordInput'),
|
||||||
name: document.getElementById('prodListName'),
|
name: document.getElementById('prodListName'),
|
||||||
model: document.getElementById('prodListModel'),
|
model: document.getElementById('prodListModel'),
|
||||||
|
barcode: document.getElementById('prodListBarcode'),
|
||||||
basicPrice: document.getElementById('prodListBasicPrice'),
|
basicPrice: document.getElementById('prodListBasicPrice'),
|
||||||
feature: document.getElementById('prodListFeature'),
|
feature: document.getElementById('prodListFeature'),
|
||||||
sort: document.getElementById('prodListSort'),
|
sort: document.getElementById('prodListSort'),
|
||||||
@@ -404,6 +437,7 @@
|
|||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
list: [],
|
list: [],
|
||||||
|
fullList: [],
|
||||||
mode: 'idle',
|
mode: 'idle',
|
||||||
editingId: '',
|
editingId: '',
|
||||||
editingSource: null,
|
editingSource: null,
|
||||||
@@ -423,6 +457,12 @@
|
|||||||
currentIconAttachments: [],
|
currentIconAttachments: [],
|
||||||
pendingIconFiles: [],
|
pendingIconFiles: [],
|
||||||
copySourceProductId: '',
|
copySourceProductId: '',
|
||||||
|
pagination: {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
totalPages: 1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const multiFieldConfig = {
|
const multiFieldConfig = {
|
||||||
@@ -583,6 +623,7 @@
|
|||||||
prod_list_id: normalizeText(item && item.prod_list_id),
|
prod_list_id: normalizeText(item && item.prod_list_id),
|
||||||
prod_list_name: normalizeText(item && item.prod_list_name),
|
prod_list_name: normalizeText(item && item.prod_list_name),
|
||||||
prod_list_modelnumber: normalizeText(item && item.prod_list_modelnumber),
|
prod_list_modelnumber: normalizeText(item && item.prod_list_modelnumber),
|
||||||
|
prod_list_barcode: normalizeText(item && item.prod_list_barcode),
|
||||||
prod_list_icon: normalizeText(item && item.prod_list_icon),
|
prod_list_icon: normalizeText(item && item.prod_list_icon),
|
||||||
prod_list_description: normalizeText(item && item.prod_list_description),
|
prod_list_description: normalizeText(item && item.prod_list_description),
|
||||||
prod_list_feature: normalizeText(item && item.prod_list_feature),
|
prod_list_feature: normalizeText(item && item.prod_list_feature),
|
||||||
@@ -609,24 +650,42 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestProductListFallback(payload) {
|
async function fetchAllFallbackProductRecords() {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
const res = await fetch(getApiUrl('/collections/tbl_product_list/records?page=1&perPage=500&sort=-updated'), {
|
const perPage = 200
|
||||||
method: 'GET',
|
const result = []
|
||||||
headers: {
|
let page = 1
|
||||||
Authorization: token ? ('Bearer ' + token) : '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const parsed = await parseJsonSafe(res)
|
while (true) {
|
||||||
if (!res.ok || !parsed.isJson || !parsed.json) {
|
const res = await fetch(getApiUrl('/collections/tbl_product_list/records?page=' + page + '&perPage=' + perPage + '&sort=-updated'), {
|
||||||
throw new Error('产品列表回退查询失败')
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: token ? ('Bearer ' + token) : '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const parsed = await parseJsonSafe(res)
|
||||||
|
if (!res.ok || !parsed.isJson || !parsed.json) {
|
||||||
|
throw new Error('产品列表回退查询失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawItems = Array.isArray(parsed.json.items) ? parsed.json.items : []
|
||||||
|
for (let i = 0; i < rawItems.length; i += 1) {
|
||||||
|
result.push(normalizeFallbackProductRecord(rawItems[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawItems.length < perPage) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
page += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawItems = Array.isArray(parsed.json.items) ? parsed.json.items : []
|
return result
|
||||||
const normalized = rawItems.map(function (item) {
|
}
|
||||||
return normalizeFallbackProductRecord(item)
|
|
||||||
})
|
async function requestProductListFallback(payload) {
|
||||||
|
const normalized = await fetchAllFallbackProductRecords()
|
||||||
|
|
||||||
const keyword = normalizeText(payload && payload.keyword).toLowerCase()
|
const keyword = normalizeText(payload && payload.keyword).toLowerCase()
|
||||||
const status = normalizeText(payload && payload.status)
|
const status = normalizeText(payload && payload.status)
|
||||||
@@ -637,6 +696,7 @@
|
|||||||
|| normalizeText(item.prod_list_id).toLowerCase().indexOf(keyword) !== -1
|
|| normalizeText(item.prod_list_id).toLowerCase().indexOf(keyword) !== -1
|
||||||
|| normalizeText(item.prod_list_name).toLowerCase().indexOf(keyword) !== -1
|
|| normalizeText(item.prod_list_name).toLowerCase().indexOf(keyword) !== -1
|
||||||
|| normalizeText(item.prod_list_modelnumber).toLowerCase().indexOf(keyword) !== -1
|
|| normalizeText(item.prod_list_modelnumber).toLowerCase().indexOf(keyword) !== -1
|
||||||
|
|| normalizeText(item.prod_list_barcode).toLowerCase().indexOf(keyword) !== -1
|
||||||
|| normalizeText(item.prod_list_tags).toLowerCase().indexOf(keyword) !== -1
|
|| normalizeText(item.prod_list_tags).toLowerCase().indexOf(keyword) !== -1
|
||||||
const matchedStatus = !status || normalizeText(item.prod_list_status) === status
|
const matchedStatus = !status || normalizeText(item.prod_list_status) === status
|
||||||
const matchedCategory = !category || normalizeText(item.prod_list_category) === category
|
const matchedCategory = !category || normalizeText(item.prod_list_category) === category
|
||||||
@@ -656,6 +716,101 @@
|
|||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clampPage(page, totalPages) {
|
||||||
|
const normalizedPage = Number(page) || 1
|
||||||
|
const normalizedTotalPages = Math.max(1, Number(totalPages) || 1)
|
||||||
|
return Math.min(Math.max(1, normalizedPage), normalizedTotalPages)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVisiblePageNumbers(page, totalPages) {
|
||||||
|
const result = []
|
||||||
|
const start = Math.max(1, page - 2)
|
||||||
|
const end = Math.min(totalPages, start + 4)
|
||||||
|
const adjustedStart = Math.max(1, end - 4)
|
||||||
|
|
||||||
|
for (let current = adjustedStart; current <= end; current += 1) {
|
||||||
|
result.push(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPageSizeSelector() {
|
||||||
|
const options = [10, 20, 50, 100, 500]
|
||||||
|
return '<label class="pagination-size">每页'
|
||||||
|
+ '<select class="pagination-select" onchange="window.__setProductPageSize(this.value)">'
|
||||||
|
+ options.map(function (size) {
|
||||||
|
return '<option value="' + size + '"' + (Number(state.pagination.pageSize) === size ? ' selected' : '') + '>' + size + '</option>'
|
||||||
|
}).join('')
|
||||||
|
+ '</select>'
|
||||||
|
+ '条</label>'
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPagination() {
|
||||||
|
if (!listPaginationEl) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = Number(state.pagination.total || 0)
|
||||||
|
const page = Number(state.pagination.page || 1)
|
||||||
|
const totalPages = Math.max(1, Number(state.pagination.totalPages || 1))
|
||||||
|
const sizeSelector = renderPageSizeSelector()
|
||||||
|
|
||||||
|
if (!total) {
|
||||||
|
listPaginationEl.innerHTML = '<div class="pagination-info">共 0 条</div>'
|
||||||
|
+ '<div class="pagination-actions">' + sizeSelector + '</div>'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const visiblePageNumbers = getVisiblePageNumbers(page, totalPages)
|
||||||
|
const startIndex = (page - 1) * state.pagination.pageSize + 1
|
||||||
|
const endIndex = Math.min(total, page * state.pagination.pageSize)
|
||||||
|
const buttons = []
|
||||||
|
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(' + (page - 1) + ')" ' + (page <= 1 ? 'disabled' : '') + '>上一页</button>')
|
||||||
|
|
||||||
|
if (visiblePageNumbers[0] > 1) {
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(1)">1</button>')
|
||||||
|
if (visiblePageNumbers[0] > 2) {
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < visiblePageNumbers.length; i += 1) {
|
||||||
|
const current = visiblePageNumbers[i]
|
||||||
|
buttons.push('<button class="pagination-btn' + (current === page ? ' active' : '') + '" type="button" onclick="window.__gotoProductPage(' + current + ')">' + current + '</button>')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visiblePageNumbers.length && visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages) {
|
||||||
|
if (visiblePageNumbers[visiblePageNumbers.length - 1] < totalPages - 1) {
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" disabled>...</button>')
|
||||||
|
}
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(' + totalPages + ')">' + totalPages + '</button>')
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push('<button class="pagination-btn" type="button" onclick="window.__gotoProductPage(' + (page + 1) + ')" ' + (page >= totalPages ? 'disabled' : '') + '>下一页</button>')
|
||||||
|
|
||||||
|
listPaginationEl.innerHTML = '<div class="pagination-info">共 ' + total + ' 条,显示第 ' + startIndex + '-' + endIndex + ' 条,第 ' + page + '/' + totalPages + ' 页</div>'
|
||||||
|
+ '<div class="pagination-actions">' + sizeSelector + buttons.join('') + '</div>'
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProductListView() {
|
||||||
|
const pageSize = Math.max(1, Number(state.pagination.pageSize || 10))
|
||||||
|
const total = state.fullList.length
|
||||||
|
const totalPages = Math.max(1, Math.ceil(total / pageSize))
|
||||||
|
const currentPage = clampPage(state.pagination.page, totalPages)
|
||||||
|
const startIndex = (currentPage - 1) * pageSize
|
||||||
|
|
||||||
|
state.pagination.page = currentPage
|
||||||
|
state.pagination.total = total
|
||||||
|
state.pagination.totalPages = totalPages
|
||||||
|
state.list = state.fullList.slice(startIndex, startIndex + pageSize)
|
||||||
|
|
||||||
|
renderTable()
|
||||||
|
renderPagination()
|
||||||
|
renderSortRankHint()
|
||||||
|
}
|
||||||
|
|
||||||
async function parseJsonSafe(res) {
|
async function parseJsonSafe(res) {
|
||||||
const contentType = String(res.headers.get('content-type') || '').toLowerCase()
|
const contentType = String(res.headers.get('content-type') || '').toLowerCase()
|
||||||
const rawText = await res.text()
|
const rawText = await res.text()
|
||||||
@@ -713,6 +868,16 @@
|
|||||||
return String(value || '').trim()
|
return String(value || '').trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generatePocketBaseRecordId() {
|
||||||
|
const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||||
|
let output = ''
|
||||||
|
for (let i = 0; i < 15; i += 1) {
|
||||||
|
const idx = Math.floor(Math.random() * alphabet.length)
|
||||||
|
output += alphabet[idx]
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
function splitPipe(value) {
|
function splitPipe(value) {
|
||||||
return String(value || '')
|
return String(value || '')
|
||||||
.split('|')
|
.split('|')
|
||||||
@@ -858,7 +1023,7 @@
|
|||||||
|
|
||||||
const targetSort = Math.floor(sortValue)
|
const targetSort = Math.floor(sortValue)
|
||||||
const targetId = state.mode === 'edit' ? normalizeText(state.editingId) : '__draft__'
|
const targetId = state.mode === 'edit' ? normalizeText(state.editingId) : '__draft__'
|
||||||
const sameCategoryItems = state.list
|
const sameCategoryItems = state.fullList
|
||||||
.filter(function (item) {
|
.filter(function (item) {
|
||||||
return normalizeText(item.prod_list_category) === category && normalizeText(item.prod_list_id) !== targetId
|
return normalizeText(item.prod_list_category) === category && normalizeText(item.prod_list_id) !== targetId
|
||||||
})
|
})
|
||||||
@@ -1590,6 +1755,7 @@
|
|||||||
function resetForm() {
|
function resetForm() {
|
||||||
fields.name.value = ''
|
fields.name.value = ''
|
||||||
fields.model.value = ''
|
fields.model.value = ''
|
||||||
|
fields.barcode.value = ''
|
||||||
fields.basicPrice.value = ''
|
fields.basicPrice.value = ''
|
||||||
fields.feature.value = ''
|
fields.feature.value = ''
|
||||||
fields.sort.value = '0'
|
fields.sort.value = '0'
|
||||||
@@ -1640,6 +1806,7 @@
|
|||||||
function fillFormFromItem(item) {
|
function fillFormFromItem(item) {
|
||||||
fields.name.value = item.prod_list_name || ''
|
fields.name.value = item.prod_list_name || ''
|
||||||
fields.model.value = item.prod_list_modelnumber || ''
|
fields.model.value = item.prod_list_modelnumber || ''
|
||||||
|
fields.barcode.value = item.prod_list_barcode || ''
|
||||||
state.productStatus = item.prod_list_status || '有效'
|
state.productStatus = item.prod_list_status || '有效'
|
||||||
fields.basicPrice.value = item.prod_list_basic_price === null || typeof item.prod_list_basic_price === 'undefined' ? '' : String(item.prod_list_basic_price)
|
fields.basicPrice.value = item.prod_list_basic_price === null || typeof item.prod_list_basic_price === 'undefined' ? '' : String(item.prod_list_basic_price)
|
||||||
fields.feature.value = item.prod_list_feature || ''
|
fields.feature.value = item.prod_list_feature || ''
|
||||||
@@ -1698,7 +1865,7 @@
|
|||||||
|
|
||||||
tableBodyEl.innerHTML = state.list.map(function (item) {
|
tableBodyEl.innerHTML = state.list.map(function (item) {
|
||||||
return '<tr>'
|
return '<tr>'
|
||||||
+ '<td data-label="名称/型号"><div><strong>' + escapeHtml(item.prod_list_name || '') + '</strong></div><div class="muted">' + escapeHtml(item.prod_list_modelnumber || '') + '</div></td>'
|
+ '<td data-label="名称/型号"><div><strong>' + escapeHtml(item.prod_list_name || '') + '</strong></div><div class="muted">型号:' + escapeHtml(item.prod_list_modelnumber || '-') + '</div><div class="muted">料号:' + escapeHtml(item.prod_list_barcode || '-') + '</div></td>'
|
||||||
+ '<td data-label="分类信息"><div>' + escapeHtml(item.prod_list_category || '-') + '</div><div class="muted">方案:' + escapeHtml(item.prod_list_plantype || '-') + ' / 系列:' + escapeHtml(item.prod_list_series || '-') + '</div><div class="muted">分类排序: ' + escapeHtml(item.prod_list_sort === null || typeof item.prod_list_sort === 'undefined' ? '-' : item.prod_list_sort) + '(第 ' + escapeHtml(item.prod_list_category_rank || '-') + ' 位)</div></td>'
|
+ '<td data-label="分类信息"><div>' + escapeHtml(item.prod_list_category || '-') + '</div><div class="muted">方案:' + escapeHtml(item.prod_list_plantype || '-') + ' / 系列:' + escapeHtml(item.prod_list_series || '-') + '</div><div class="muted">分类排序: ' + escapeHtml(item.prod_list_sort === null || typeof item.prod_list_sort === 'undefined' ? '-' : item.prod_list_sort) + '(第 ' + escapeHtml(item.prod_list_category_rank || '-') + ' 位)</div></td>'
|
||||||
+ '<td data-label="标签"><div>' + escapeHtml(item.prod_list_tags || '-') + '</div></td>'
|
+ '<td data-label="标签"><div>' + escapeHtml(item.prod_list_tags || '-') + '</div></td>'
|
||||||
+ '<td data-label="状态/价格"><div>' + escapeHtml(item.prod_list_status || '-') + '</div><div class="muted">¥' + escapeHtml(item.prod_list_basic_price === null || typeof item.prod_list_basic_price === 'undefined' ? '-' : item.prod_list_basic_price) + '</div></td>'
|
+ '<td data-label="状态/价格"><div>' + escapeHtml(item.prod_list_status || '-') + '</div><div class="muted">¥' + escapeHtml(item.prod_list_basic_price === null || typeof item.prod_list_basic_price === 'undefined' ? '-' : item.prod_list_basic_price) + '</div></td>'
|
||||||
@@ -1712,11 +1879,13 @@
|
|||||||
}).join('')
|
}).join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildCopyPayload(source, nextName, nextModel) {
|
function buildCopyPayload(source, nextName, nextModel, nextBarcode) {
|
||||||
return {
|
return {
|
||||||
|
id: generatePocketBaseRecordId(),
|
||||||
prod_list_id: '',
|
prod_list_id: '',
|
||||||
prod_list_name: normalizeText(nextName),
|
prod_list_name: normalizeText(nextName),
|
||||||
prod_list_modelnumber: normalizeText(nextModel),
|
prod_list_modelnumber: normalizeText(nextModel),
|
||||||
|
prod_list_barcode: normalizeText(nextBarcode),
|
||||||
prod_list_icon: source && source.prod_list_icon ? normalizeText(source.prod_list_icon) : '',
|
prod_list_icon: source && source.prod_list_icon ? normalizeText(source.prod_list_icon) : '',
|
||||||
prod_list_description: source && source.prod_list_description ? normalizeText(source.prod_list_description) : '',
|
prod_list_description: source && source.prod_list_description ? normalizeText(source.prod_list_description) : '',
|
||||||
prod_list_feature: source && source.prod_list_feature ? normalizeText(source.prod_list_feature) : '',
|
prod_list_feature: source && source.prod_list_feature ? normalizeText(source.prod_list_feature) : '',
|
||||||
@@ -1742,9 +1911,11 @@
|
|||||||
state.copySourceProductId = source ? normalizeText(source.prod_list_id) : ''
|
state.copySourceProductId = source ? normalizeText(source.prod_list_id) : ''
|
||||||
const defaultName = normalizeText(source && source.prod_list_name)
|
const defaultName = normalizeText(source && source.prod_list_name)
|
||||||
const defaultModel = normalizeText(source && source.prod_list_modelnumber)
|
const defaultModel = normalizeText(source && source.prod_list_modelnumber)
|
||||||
|
const defaultBarcode = normalizeText(source && source.prod_list_barcode)
|
||||||
|
|
||||||
copyNameInputEl.value = defaultName ? (defaultName + '-副本') : ''
|
copyNameInputEl.value = defaultName ? (defaultName + '-副本') : ''
|
||||||
copyModelInputEl.value = defaultModel
|
copyModelInputEl.value = defaultModel
|
||||||
|
copyBarcodeInputEl.value = defaultBarcode
|
||||||
copyModalEl.classList.add('show')
|
copyModalEl.classList.add('show')
|
||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
@@ -1758,10 +1929,11 @@
|
|||||||
copyModalEl.classList.remove('show')
|
copyModalEl.classList.remove('show')
|
||||||
copyNameInputEl.value = ''
|
copyNameInputEl.value = ''
|
||||||
copyModelInputEl.value = ''
|
copyModelInputEl.value = ''
|
||||||
|
copyBarcodeInputEl.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyProduct(productId) {
|
async function copyProduct(productId) {
|
||||||
const source = state.list.find(function (item) {
|
const source = state.fullList.find(function (item) {
|
||||||
return normalizeText(item.prod_list_id) === normalizeText(productId)
|
return normalizeText(item.prod_list_id) === normalizeText(productId)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1781,7 +1953,7 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = state.list.find(function (item) {
|
const source = state.fullList.find(function (item) {
|
||||||
return normalizeText(item.prod_list_id) === sourceId
|
return normalizeText(item.prod_list_id) === sourceId
|
||||||
})
|
})
|
||||||
if (!source) {
|
if (!source) {
|
||||||
@@ -1797,7 +1969,7 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = buildCopyPayload(source, normalizedName, copyModelInputEl.value)
|
const payload = buildCopyPayload(source, normalizedName, copyModelInputEl.value, copyBarcodeInputEl.value)
|
||||||
|
|
||||||
setStatus('正在复制产品...', '')
|
setStatus('正在复制产品...', '')
|
||||||
showLoading('正在复制产品,请稍候...')
|
showLoading('正在复制产品,请稍候...')
|
||||||
@@ -1830,20 +2002,18 @@
|
|||||||
status: payload.status,
|
status: payload.status,
|
||||||
prod_list_category: payload.prod_list_category,
|
prod_list_category: payload.prod_list_category,
|
||||||
})
|
})
|
||||||
state.list = Array.isArray(data.items) ? data.items : []
|
state.fullList = Array.isArray(data.items) ? data.items : []
|
||||||
renderTable()
|
updateProductListView()
|
||||||
renderSortRankHint()
|
setStatus('产品列表已刷新,共 ' + state.pagination.total + ' 条。', 'success')
|
||||||
setStatus('产品列表已刷新,共 ' + state.list.length + ' 条。', 'success')
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
try {
|
try {
|
||||||
state.list = await requestProductListFallback({
|
state.fullList = await requestProductListFallback({
|
||||||
keyword: normalizeText(fields.keyword.value),
|
keyword: normalizeText(fields.keyword.value),
|
||||||
status: normalizeText(statusFilterEl.value),
|
status: normalizeText(statusFilterEl.value),
|
||||||
prod_list_category: normalizeText(categoryFilterEl.value),
|
prod_list_category: normalizeText(categoryFilterEl.value),
|
||||||
})
|
})
|
||||||
renderTable()
|
updateProductListView()
|
||||||
renderSortRankHint()
|
setStatus('产品列表已通过回退链路刷新,共 ' + state.pagination.total + ' 条。', 'success')
|
||||||
setStatus('产品列表已通过回退链路刷新,共 ' + state.list.length + ' 条。', 'success')
|
|
||||||
} catch (_fallbackErr) {
|
} catch (_fallbackErr) {
|
||||||
setStatus(err.message || '加载产品列表失败', 'error')
|
setStatus(err.message || '加载产品列表失败', 'error')
|
||||||
}
|
}
|
||||||
@@ -1878,11 +2048,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function queryProductsWithAutoSave() {
|
async function queryProductsWithAutoSave(resetPage) {
|
||||||
|
if (resetPage) {
|
||||||
|
state.pagination.page = 1
|
||||||
|
}
|
||||||
await saveAndHideEditorBeforeProductQuery()
|
await saveAndHideEditorBeforeProductQuery()
|
||||||
await loadProducts()
|
await loadProducts()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goToProductPage(page) {
|
||||||
|
const nextPage = clampPage(page, state.pagination.totalPages)
|
||||||
|
if (nextPage === state.pagination.page) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.pagination.page = nextPage
|
||||||
|
updateProductListView()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProductPageSize(pageSize) {
|
||||||
|
const nextPageSize = Number(pageSize) || 10
|
||||||
|
if (nextPageSize === state.pagination.pageSize) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.pagination.pageSize = nextPageSize
|
||||||
|
state.pagination.page = 1
|
||||||
|
updateProductListView()
|
||||||
|
}
|
||||||
|
|
||||||
async function enterEditMode(productId) {
|
async function enterEditMode(productId) {
|
||||||
showLoading('正在加载产品详情...')
|
showLoading('正在加载产品详情...')
|
||||||
try {
|
try {
|
||||||
@@ -1948,9 +2142,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
|
id: state.mode === 'edit' ? '' : generatePocketBaseRecordId(),
|
||||||
prod_list_id: state.mode === 'edit' ? state.editingId : '',
|
prod_list_id: state.mode === 'edit' ? state.editingId : '',
|
||||||
prod_list_name: normalizeText(fields.name.value),
|
prod_list_name: normalizeText(fields.name.value),
|
||||||
prod_list_modelnumber: normalizeText(fields.model.value),
|
prod_list_modelnumber: normalizeText(fields.model.value),
|
||||||
|
prod_list_barcode: normalizeText(fields.barcode.value),
|
||||||
prod_list_icon: joinPipe(finalIconIds),
|
prod_list_icon: joinPipe(finalIconIds),
|
||||||
prod_list_description: normalizeText(fields.description.value),
|
prod_list_description: normalizeText(fields.description.value),
|
||||||
prod_list_feature: normalizeText(fields.feature.value),
|
prod_list_feature: normalizeText(fields.feature.value),
|
||||||
@@ -2218,14 +2414,37 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
window.__gotoProductPage = goToProductPage
|
||||||
|
window.__setProductPageSize = setProductPageSize
|
||||||
|
|
||||||
document.getElementById('reloadBtn').addEventListener('click', function () {
|
document.getElementById('reloadBtn').addEventListener('click', function () {
|
||||||
queryProductsWithAutoSave()
|
queryProductsWithAutoSave(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
document.getElementById('searchBtn').addEventListener('click', function () {
|
document.getElementById('searchBtn').addEventListener('click', function () {
|
||||||
queryProductsWithAutoSave()
|
queryProductsWithAutoSave(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (fields.keyword) {
|
||||||
|
fields.keyword.addEventListener('keydown', function (event) {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
queryProductsWithAutoSave(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusFilterEl) {
|
||||||
|
statusFilterEl.addEventListener('change', function () {
|
||||||
|
queryProductsWithAutoSave(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (categoryFilterEl) {
|
||||||
|
categoryFilterEl.addEventListener('change', function () {
|
||||||
|
queryProductsWithAutoSave(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('createModeBtn').addEventListener('click', function () {
|
document.getElementById('createModeBtn').addEventListener('click', function () {
|
||||||
enterCreateMode()
|
enterCreateMode()
|
||||||
})
|
})
|
||||||
@@ -2299,6 +2518,12 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
copyBarcodeInputEl.addEventListener('keydown', function (event) {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
confirmCopyProduct()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
document.getElementById('submitBtn').addEventListener('click', function () {
|
document.getElementById('submitBtn').addEventListener('click', function () {
|
||||||
submitProduct()
|
submitProduct()
|
||||||
})
|
})
|
||||||
|
|||||||
1513
pocket-base/bai_web_pb_hooks/views/scheme-manage.html
Normal file
1513
pocket-base/bai_web_pb_hooks/views/scheme-manage.html
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
392
pocket-base/spec/openapi-manage/cart.yaml
Normal file
392
pocket-base/spec/openapi-manage/cart.yaml
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: BAI PocketBase Manage Hooks API - Cart
|
||||||
|
version: 1.0.0
|
||||||
|
description: |
|
||||||
|
hooks 购物车接口文档。
|
||||||
|
本文件可单独导入使用,不依赖其他 YAML。
|
||||||
|
servers:
|
||||||
|
- url: https://bai-api.blv-oa.com
|
||||||
|
description: 生产环境
|
||||||
|
- url: http://localhost:8090
|
||||||
|
description: PocketBase 本地环境
|
||||||
|
paths:
|
||||||
|
/pb/api/cart/list:
|
||||||
|
post:
|
||||||
|
tags: [购物车]
|
||||||
|
summary: 查询购物车列表
|
||||||
|
description: |
|
||||||
|
调用自定义 hooks API 查询当前登录用户的购物车列表。
|
||||||
|
请求头请自行携带 `Authorization: Bearer <token>`。
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CartListRequest'
|
||||||
|
example:
|
||||||
|
keyword: 模糊搜索关键字|string
|
||||||
|
cart_status: 购物车状态|string
|
||||||
|
cart_number: 购物车名称或分组号|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 查询成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CartListResponse'
|
||||||
|
'400':
|
||||||
|
description: 参数错误
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
'415':
|
||||||
|
description: 请求体必须为 application/json
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
/pb/api/cart/detail:
|
||||||
|
post:
|
||||||
|
tags: [购物车]
|
||||||
|
summary: 查询购物车详情
|
||||||
|
description: |
|
||||||
|
调用自定义 hooks API 按 `cart_id` 查询单条购物车记录。
|
||||||
|
请求头请自行携带 `Authorization: Bearer <token>`。
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CartDetailRequest'
|
||||||
|
example:
|
||||||
|
cart_id: 购物车业务ID|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 查询成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CartRecord'
|
||||||
|
'400':
|
||||||
|
description: 参数错误
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
'403':
|
||||||
|
description: 无权访问该购物车记录
|
||||||
|
'404':
|
||||||
|
description: 未找到对应购物车记录
|
||||||
|
'415':
|
||||||
|
description: 请求体必须为 application/json
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
/pb/api/cart/create:
|
||||||
|
post:
|
||||||
|
tags: [购物车]
|
||||||
|
summary: 新增购物车记录
|
||||||
|
description: |
|
||||||
|
调用自定义 hooks API 新增购物车记录。
|
||||||
|
服务端会基于当前 token 自动写入 `cart_owner` 并生成 `cart_id`。
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CartCreateRequest'
|
||||||
|
example:
|
||||||
|
cart_number: 可选;未传时服务端自动生成|string
|
||||||
|
cart_product_id: tbl_product_list 的 PocketBase recordId|string
|
||||||
|
cart_product_quantity: 产品数量|integer
|
||||||
|
cart_status: 购物车状态|string
|
||||||
|
cart_at_price: 加入购物车时价格|integer
|
||||||
|
cart_remark: 备注|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 创建成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CartRecord'
|
||||||
|
'400':
|
||||||
|
description: 参数错误或创建失败
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
'415':
|
||||||
|
description: 请求体必须为 application/json
|
||||||
|
'429':
|
||||||
|
description: 重复请求过于频繁
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
/pb/api/cart/update:
|
||||||
|
post:
|
||||||
|
tags: [购物车]
|
||||||
|
summary: 修改购物车记录
|
||||||
|
description: |
|
||||||
|
调用自定义 hooks API 按 `cart_id` 更新购物车记录。
|
||||||
|
请求头请自行携带 `Authorization: Bearer <token>`。
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CartUpdateRequest'
|
||||||
|
example:
|
||||||
|
cart_id: 购物车业务ID|string
|
||||||
|
cart_number: 购物车名称或分组号|string
|
||||||
|
cart_product_id: tbl_product_list 的 PocketBase recordId|string
|
||||||
|
cart_product_quantity: 产品数量|integer
|
||||||
|
cart_status: 购物车状态|string
|
||||||
|
cart_at_price: 加入购物车时价格|integer
|
||||||
|
cart_remark: 备注|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 更新成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CartRecord'
|
||||||
|
'400':
|
||||||
|
description: 参数错误或更新失败
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
'403':
|
||||||
|
description: 无权访问该购物车记录
|
||||||
|
'404':
|
||||||
|
description: 未找到待修改记录
|
||||||
|
'415':
|
||||||
|
description: 请求体必须为 application/json
|
||||||
|
'429':
|
||||||
|
description: 重复请求过于频繁
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
/pb/api/cart/delete:
|
||||||
|
post:
|
||||||
|
tags: [购物车]
|
||||||
|
summary: 删除购物车记录
|
||||||
|
description: |
|
||||||
|
调用自定义 hooks API 按 `cart_id` 删除购物车记录。
|
||||||
|
当前后端实现以实际 hooks 逻辑为准。
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CartDeleteRequest'
|
||||||
|
example:
|
||||||
|
cart_id: 购物车业务ID|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 删除成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CartDeleteResponse'
|
||||||
|
'400':
|
||||||
|
description: 参数错误或删除失败
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
'403':
|
||||||
|
description: 无权访问该购物车记录
|
||||||
|
'404':
|
||||||
|
description: 未找到待删除记录
|
||||||
|
'415':
|
||||||
|
description: 请求体必须为 application/json
|
||||||
|
'429':
|
||||||
|
description: 重复请求过于频繁
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
CartRecord:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pb_id:
|
||||||
|
type: string
|
||||||
|
description: PocketBase记录主键id
|
||||||
|
created:
|
||||||
|
type: string
|
||||||
|
description: PocketBase系统创建时间
|
||||||
|
updated:
|
||||||
|
type: string
|
||||||
|
description: PocketBase系统更新时间
|
||||||
|
cart_id:
|
||||||
|
type: string
|
||||||
|
description: 购物车业务ID
|
||||||
|
cart_number:
|
||||||
|
type: string
|
||||||
|
description: 购物车名称或分组号
|
||||||
|
cart_create:
|
||||||
|
type: string
|
||||||
|
description: 购物车项创建时间
|
||||||
|
cart_owner:
|
||||||
|
type: string
|
||||||
|
description: 购物车所有者openid
|
||||||
|
cart_product_id:
|
||||||
|
type: string
|
||||||
|
description: 关联产品的 PocketBase recordId
|
||||||
|
cart_product_business_id:
|
||||||
|
type: string
|
||||||
|
description: 关联产品的业务 ID(即 prod_list_id),用于展示
|
||||||
|
cart_product_quantity:
|
||||||
|
type: [integer, number]
|
||||||
|
description: 产品数量
|
||||||
|
cart_status:
|
||||||
|
type: string
|
||||||
|
description: 购物车状态
|
||||||
|
cart_at_price:
|
||||||
|
type: [integer, number]
|
||||||
|
description: 加入购物车时价格
|
||||||
|
cart_remark:
|
||||||
|
type: string
|
||||||
|
description: 备注
|
||||||
|
is_delete:
|
||||||
|
type: [integer, number]
|
||||||
|
description: 删除标记
|
||||||
|
product_name:
|
||||||
|
type: string
|
||||||
|
description: 产品名称
|
||||||
|
product_modelnumber:
|
||||||
|
type: string
|
||||||
|
description: 产品型号
|
||||||
|
product_barcode:
|
||||||
|
type: string
|
||||||
|
description: 产品料号
|
||||||
|
product_basic_price:
|
||||||
|
type: [integer, number, 'null']
|
||||||
|
description: 产品基础价格
|
||||||
|
example:
|
||||||
|
pb_id: PocketBase记录主键id|string
|
||||||
|
created: PocketBase系统创建时间|string
|
||||||
|
updated: PocketBase系统更新时间|string
|
||||||
|
cart_id: 购物车业务ID|string
|
||||||
|
cart_number: 购物车名称或分组号|string
|
||||||
|
cart_create: 购物车项创建时间|string
|
||||||
|
cart_owner: 购物车所有者openid|string
|
||||||
|
cart_product_id: tbl_product_list 的 PocketBase recordId|string
|
||||||
|
cart_product_business_id: 产品业务ID|string
|
||||||
|
cart_product_quantity: 产品数量|integer
|
||||||
|
cart_status: 购物车状态|string
|
||||||
|
cart_at_price: 加入购物车时价格|integer
|
||||||
|
cart_remark: 备注|string
|
||||||
|
is_delete: 删除标记|integer
|
||||||
|
product_name: 产品名称|string
|
||||||
|
product_modelnumber: 产品型号|string
|
||||||
|
product_barcode: 产品料号|string
|
||||||
|
product_basic_price: 产品基础价格|integer
|
||||||
|
CartListRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
keyword:
|
||||||
|
type: string
|
||||||
|
description: 模糊搜索关键字
|
||||||
|
cart_status:
|
||||||
|
type: string
|
||||||
|
description: 购物车状态
|
||||||
|
cart_number:
|
||||||
|
type: string
|
||||||
|
description: 购物车名称或分组号
|
||||||
|
example:
|
||||||
|
keyword: 模糊搜索关键字|string
|
||||||
|
cart_status: 购物车状态|string
|
||||||
|
cart_number: 购物车名称或分组号|string
|
||||||
|
CartDetailRequest:
|
||||||
|
type: object
|
||||||
|
required: [cart_id]
|
||||||
|
properties:
|
||||||
|
cart_id:
|
||||||
|
type: string
|
||||||
|
description: 购物车业务ID
|
||||||
|
example:
|
||||||
|
cart_id: 购物车业务ID|string
|
||||||
|
CartCreateRequest:
|
||||||
|
type: object
|
||||||
|
required: [cart_product_id, cart_product_quantity, cart_at_price]
|
||||||
|
properties:
|
||||||
|
cart_number:
|
||||||
|
type: string
|
||||||
|
cart_product_id:
|
||||||
|
type: string
|
||||||
|
description: tbl_product_list 的 PocketBase recordId
|
||||||
|
cart_product_quantity:
|
||||||
|
type: [integer, number]
|
||||||
|
cart_status:
|
||||||
|
type: string
|
||||||
|
cart_at_price:
|
||||||
|
type: [integer, number]
|
||||||
|
cart_remark:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
cart_number: 可选;未传时服务端自动生成|string
|
||||||
|
cart_product_id: tbl_product_list 的 PocketBase recordId|string
|
||||||
|
cart_product_quantity: 产品数量|integer
|
||||||
|
cart_status: 购物车状态|string
|
||||||
|
cart_at_price: 加入购物车时价格|integer
|
||||||
|
cart_remark: 备注|string
|
||||||
|
CartUpdateRequest:
|
||||||
|
type: object
|
||||||
|
required: [cart_id]
|
||||||
|
properties:
|
||||||
|
cart_id:
|
||||||
|
type: string
|
||||||
|
cart_number:
|
||||||
|
type: string
|
||||||
|
cart_product_id:
|
||||||
|
type: string
|
||||||
|
description: tbl_product_list 的 PocketBase recordId
|
||||||
|
cart_product_quantity:
|
||||||
|
type: [integer, number]
|
||||||
|
cart_status:
|
||||||
|
type: string
|
||||||
|
cart_at_price:
|
||||||
|
type: [integer, number]
|
||||||
|
cart_remark:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
cart_id: 购物车业务ID|string
|
||||||
|
cart_number: 购物车名称或分组号|string
|
||||||
|
cart_product_id: tbl_product_list 的 PocketBase recordId|string
|
||||||
|
cart_product_quantity: 产品数量|integer
|
||||||
|
cart_status: 购物车状态|string
|
||||||
|
cart_at_price: 加入购物车时价格|integer
|
||||||
|
cart_remark: 备注|string
|
||||||
|
CartDeleteRequest:
|
||||||
|
type: object
|
||||||
|
required: [cart_id]
|
||||||
|
properties:
|
||||||
|
cart_id:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
cart_id: 购物车业务ID|string
|
||||||
|
CartListResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/CartRecord'
|
||||||
|
example:
|
||||||
|
items:
|
||||||
|
- pb_id: PocketBase记录主键id|string
|
||||||
|
created: PocketBase系统创建时间|string
|
||||||
|
updated: PocketBase系统更新时间|string
|
||||||
|
cart_id: 购物车业务ID|string
|
||||||
|
cart_number: 购物车名称或分组号|string
|
||||||
|
cart_create: 购物车项创建时间|string
|
||||||
|
cart_owner: 购物车所有者openid|string
|
||||||
|
cart_product_id: tbl_product_list 的 PocketBase recordId|string
|
||||||
|
cart_product_business_id: 产品业务ID|string
|
||||||
|
cart_product_quantity: 产品数量|integer
|
||||||
|
cart_status: 购物车状态|string
|
||||||
|
cart_at_price: 加入购物车时价格|integer
|
||||||
|
cart_remark: 备注|string
|
||||||
|
product_name: 产品名称|string
|
||||||
|
product_modelnumber: 产品型号|string
|
||||||
|
product_barcode: 产品料号|string
|
||||||
|
product_basic_price: 产品基础价格|integer
|
||||||
|
CartDeleteResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cart_id:
|
||||||
|
type: string
|
||||||
|
is_delete:
|
||||||
|
type: [integer, number]
|
||||||
|
example:
|
||||||
|
cart_id: 购物车业务ID|string
|
||||||
|
is_delete: 删除标记|integer
|
||||||
43
pocket-base/spec/openapi-manage/openapi.yaml
Normal file
43
pocket-base/spec/openapi-manage/openapi.yaml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: BAI PocketBase Manage Hooks API
|
||||||
|
version: 1.0.0-manage-folder
|
||||||
|
description: |
|
||||||
|
面向管理端与自定义 hooks 的接口文档。
|
||||||
|
本目录仅收敛自定义 hooks API,不包含 PocketBase 原生 records API。
|
||||||
|
|
||||||
|
本文件为目录索引,支持单文件独立导入,不依赖其他 YAML。
|
||||||
|
|
||||||
|
文档约定:
|
||||||
|
- 不单独配置鉴权组件;如接口需要登录,请直接在说明中关注 `Authorization: Bearer <token>`
|
||||||
|
- 示例字段值统一使用 `<字段说明>|<类型>` 风格
|
||||||
|
- 当前 `tbl_auth_users.openid` 为全平台统一身份锚点
|
||||||
|
servers:
|
||||||
|
- url: https://bai-api.blv-oa.com
|
||||||
|
description: 生产环境
|
||||||
|
- url: http://localhost:8090
|
||||||
|
description: PocketBase 本地环境
|
||||||
|
tags:
|
||||||
|
- name: 系统
|
||||||
|
description: hooks 系统基础接口
|
||||||
|
- name: 微信认证
|
||||||
|
description: hooks 微信认证与资料接口
|
||||||
|
- name: 平台认证
|
||||||
|
description: hooks 平台用户认证接口
|
||||||
|
- name: 字典管理
|
||||||
|
description: hooks 字典管理接口
|
||||||
|
- name: 附件管理
|
||||||
|
description: hooks 附件管理接口
|
||||||
|
- name: 文档管理
|
||||||
|
description: hooks 文档管理接口
|
||||||
|
- name: 文档历史
|
||||||
|
description: hooks 文档历史接口
|
||||||
|
- name: 购物车
|
||||||
|
description: hooks 购物车接口
|
||||||
|
- name: 订单
|
||||||
|
description: hooks 订单接口
|
||||||
|
paths: {}
|
||||||
|
x-index:
|
||||||
|
files:
|
||||||
|
- cart.yaml
|
||||||
|
- order.yaml
|
||||||
383
pocket-base/spec/openapi-manage/order.yaml
Normal file
383
pocket-base/spec/openapi-manage/order.yaml
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: BAI PocketBase Manage Hooks API - Order
|
||||||
|
version: 1.0.0
|
||||||
|
description: |
|
||||||
|
hooks 订单接口文档。
|
||||||
|
本文件可单独导入使用,不依赖其他 YAML。
|
||||||
|
servers:
|
||||||
|
- url: https://bai-api.blv-oa.com
|
||||||
|
description: 生产环境
|
||||||
|
- url: http://localhost:8090
|
||||||
|
description: PocketBase 本地环境
|
||||||
|
paths:
|
||||||
|
/pb/api/order/list:
|
||||||
|
post:
|
||||||
|
tags: [订单]
|
||||||
|
summary: 查询订单列表
|
||||||
|
description: |
|
||||||
|
调用自定义 hooks API 查询当前登录用户的订单列表。
|
||||||
|
请求头请自行携带 `Authorization: Bearer <token>`。
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OrderListRequest'
|
||||||
|
example:
|
||||||
|
keyword: 模糊搜索关键字|string
|
||||||
|
order_status: 订单状态|string
|
||||||
|
order_source: 订单来源|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 查询成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OrderListResponse'
|
||||||
|
'400':
|
||||||
|
description: 参数错误
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
'415':
|
||||||
|
description: 请求体必须为 application/json
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
/pb/api/order/detail:
|
||||||
|
post:
|
||||||
|
tags: [订单]
|
||||||
|
summary: 查询订单详情
|
||||||
|
description: |
|
||||||
|
调用自定义 hooks API 按 `order_id` 查询单条订单记录。
|
||||||
|
请求头请自行携带 `Authorization: Bearer <token>`。
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OrderDetailRequest'
|
||||||
|
example:
|
||||||
|
order_id: 订单业务ID|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 查询成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OrderRecord'
|
||||||
|
'400':
|
||||||
|
description: 参数错误
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
'403':
|
||||||
|
description: 无权访问该订单记录
|
||||||
|
'404':
|
||||||
|
description: 未找到对应订单记录
|
||||||
|
'415':
|
||||||
|
description: 请求体必须为 application/json
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
/pb/api/order/create:
|
||||||
|
post:
|
||||||
|
tags: [订单]
|
||||||
|
summary: 新增订单记录
|
||||||
|
description: |
|
||||||
|
调用自定义 hooks API 新增订单记录。
|
||||||
|
服务端会基于当前 token 自动写入 `order_owner` 并生成 `order_id`。
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OrderCreateRequest'
|
||||||
|
example:
|
||||||
|
order_number: 可选;未传时服务端自动生成|string
|
||||||
|
order_source: 订单来源|string
|
||||||
|
order_status: 订单状态|string
|
||||||
|
order_source_id: 来源关联业务ID|string
|
||||||
|
order_snap:
|
||||||
|
字段名|string: 字段值|string
|
||||||
|
order_amount: 订单金额|integer
|
||||||
|
order_remark: 备注|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 创建成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OrderRecord'
|
||||||
|
'400':
|
||||||
|
description: 参数错误或创建失败
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
'415':
|
||||||
|
description: 请求体必须为 application/json
|
||||||
|
'429':
|
||||||
|
description: 重复请求过于频繁
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
/pb/api/order/update:
|
||||||
|
post:
|
||||||
|
tags: [订单]
|
||||||
|
summary: 修改订单记录
|
||||||
|
description: |
|
||||||
|
调用自定义 hooks API 按 `order_id` 更新订单记录。
|
||||||
|
请求头请自行携带 `Authorization: Bearer <token>`。
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OrderUpdateRequest'
|
||||||
|
example:
|
||||||
|
order_id: 订单业务ID|string
|
||||||
|
order_number: 订单编号|string
|
||||||
|
order_source: 订单来源|string
|
||||||
|
order_status: 订单状态|string
|
||||||
|
order_source_id: 来源关联业务ID|string
|
||||||
|
order_snap:
|
||||||
|
字段名|string: 字段值|string
|
||||||
|
order_amount: 订单金额|integer
|
||||||
|
order_remark: 备注|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 更新成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OrderRecord'
|
||||||
|
'400':
|
||||||
|
description: 参数错误或更新失败
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
'403':
|
||||||
|
description: 无权访问该订单记录
|
||||||
|
'404':
|
||||||
|
description: 未找到待修改记录
|
||||||
|
'415':
|
||||||
|
description: 请求体必须为 application/json
|
||||||
|
'429':
|
||||||
|
description: 重复请求过于频繁
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
/pb/api/order/delete:
|
||||||
|
post:
|
||||||
|
tags: [订单]
|
||||||
|
summary: 删除订单记录
|
||||||
|
description: |
|
||||||
|
调用自定义 hooks API 按 `order_id` 删除订单记录。
|
||||||
|
当前后端实现以实际 hooks 逻辑为准。
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OrderDeleteRequest'
|
||||||
|
example:
|
||||||
|
order_id: 订单业务ID|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 删除成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/OrderDeleteResponse'
|
||||||
|
'400':
|
||||||
|
description: 参数错误或删除失败
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
'403':
|
||||||
|
description: 无权访问该订单记录
|
||||||
|
'404':
|
||||||
|
description: 未找到待删除记录
|
||||||
|
'415':
|
||||||
|
description: 请求体必须为 application/json
|
||||||
|
'429':
|
||||||
|
description: 重复请求过于频繁
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
OrderRecord:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pb_id:
|
||||||
|
type: string
|
||||||
|
description: PocketBase记录主键id
|
||||||
|
created:
|
||||||
|
type: string
|
||||||
|
description: PocketBase系统创建时间
|
||||||
|
updated:
|
||||||
|
type: string
|
||||||
|
description: PocketBase系统更新时间
|
||||||
|
order_id:
|
||||||
|
type: string
|
||||||
|
description: 订单业务ID
|
||||||
|
order_number:
|
||||||
|
type: string
|
||||||
|
description: 订单编号
|
||||||
|
order_create:
|
||||||
|
type: string
|
||||||
|
description: 订单创建时间
|
||||||
|
order_owner:
|
||||||
|
type: string
|
||||||
|
description: 订单所有者openid
|
||||||
|
order_source:
|
||||||
|
type: string
|
||||||
|
description: 订单来源
|
||||||
|
order_status:
|
||||||
|
type: string
|
||||||
|
description: 订单状态
|
||||||
|
order_source_id:
|
||||||
|
type: string
|
||||||
|
description: 来源关联业务ID
|
||||||
|
order_snap:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
description: 订单快照
|
||||||
|
order_amount:
|
||||||
|
type: [integer, number]
|
||||||
|
description: 订单金额
|
||||||
|
order_remark:
|
||||||
|
type: string
|
||||||
|
description: 备注
|
||||||
|
is_delete:
|
||||||
|
type: [integer, number]
|
||||||
|
description: 删除标记
|
||||||
|
example:
|
||||||
|
pb_id: PocketBase记录主键id|string
|
||||||
|
created: PocketBase系统创建时间|string
|
||||||
|
updated: PocketBase系统更新时间|string
|
||||||
|
order_id: 订单业务ID|string
|
||||||
|
order_number: 订单编号|string
|
||||||
|
order_create: 订单创建时间|string
|
||||||
|
order_owner: 订单所有者openid|string
|
||||||
|
order_source: 订单来源|string
|
||||||
|
order_status: 订单状态|string
|
||||||
|
order_source_id: 来源关联业务ID|string
|
||||||
|
order_snap:
|
||||||
|
字段名|string: 字段值|string
|
||||||
|
order_amount: 订单金额|integer
|
||||||
|
order_remark: 备注|string
|
||||||
|
is_delete: 删除标记|integer
|
||||||
|
OrderListRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
keyword:
|
||||||
|
type: string
|
||||||
|
order_status:
|
||||||
|
type: string
|
||||||
|
order_source:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
keyword: 模糊搜索关键字|string
|
||||||
|
order_status: 订单状态|string
|
||||||
|
order_source: 订单来源|string
|
||||||
|
OrderDetailRequest:
|
||||||
|
type: object
|
||||||
|
required: [order_id]
|
||||||
|
properties:
|
||||||
|
order_id:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
order_id: 订单业务ID|string
|
||||||
|
OrderCreateRequest:
|
||||||
|
type: object
|
||||||
|
required: [order_source, order_source_id, order_snap, order_amount]
|
||||||
|
properties:
|
||||||
|
order_number:
|
||||||
|
type: string
|
||||||
|
order_source:
|
||||||
|
type: string
|
||||||
|
order_status:
|
||||||
|
type: string
|
||||||
|
order_source_id:
|
||||||
|
type: string
|
||||||
|
order_snap:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
order_amount:
|
||||||
|
type: [integer, number]
|
||||||
|
order_remark:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
order_number: 可选;未传时服务端自动生成|string
|
||||||
|
order_source: 订单来源|string
|
||||||
|
order_status: 订单状态|string
|
||||||
|
order_source_id: 来源关联业务ID|string
|
||||||
|
order_snap:
|
||||||
|
字段名|string: 字段值|string
|
||||||
|
order_amount: 订单金额|integer
|
||||||
|
order_remark: 备注|string
|
||||||
|
OrderUpdateRequest:
|
||||||
|
type: object
|
||||||
|
required: [order_id]
|
||||||
|
properties:
|
||||||
|
order_id:
|
||||||
|
type: string
|
||||||
|
order_number:
|
||||||
|
type: string
|
||||||
|
order_source:
|
||||||
|
type: string
|
||||||
|
order_status:
|
||||||
|
type: string
|
||||||
|
order_source_id:
|
||||||
|
type: string
|
||||||
|
order_snap:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
order_amount:
|
||||||
|
type: [integer, number]
|
||||||
|
order_remark:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
order_id: 订单业务ID|string
|
||||||
|
order_number: 订单编号|string
|
||||||
|
order_source: 订单来源|string
|
||||||
|
order_status: 订单状态|string
|
||||||
|
order_source_id: 来源关联业务ID|string
|
||||||
|
order_snap:
|
||||||
|
字段名|string: 字段值|string
|
||||||
|
order_amount: 订单金额|integer
|
||||||
|
order_remark: 备注|string
|
||||||
|
OrderDeleteRequest:
|
||||||
|
type: object
|
||||||
|
required: [order_id]
|
||||||
|
properties:
|
||||||
|
order_id:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
order_id: 订单业务ID|string
|
||||||
|
OrderListResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/OrderRecord'
|
||||||
|
example:
|
||||||
|
items:
|
||||||
|
- pb_id: PocketBase记录主键id|string
|
||||||
|
created: PocketBase系统创建时间|string
|
||||||
|
updated: PocketBase系统更新时间|string
|
||||||
|
order_id: 订单业务ID|string
|
||||||
|
order_number: 订单编号|string
|
||||||
|
order_create: 订单创建时间|string
|
||||||
|
order_owner: 订单所有者openid|string
|
||||||
|
order_source: 订单来源|string
|
||||||
|
order_status: 订单状态|string
|
||||||
|
order_source_id: 来源关联业务ID|string
|
||||||
|
order_snap:
|
||||||
|
字段名|string: 字段值|string
|
||||||
|
order_amount: 订单金额|integer
|
||||||
|
order_remark: 备注|string
|
||||||
|
OrderDeleteResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
order_id:
|
||||||
|
type: string
|
||||||
|
is_delete:
|
||||||
|
type: [integer, number]
|
||||||
|
example:
|
||||||
|
order_id: 订单业务ID|string
|
||||||
|
is_delete: 删除标记|integer
|
||||||
@@ -1,571 +0,0 @@
|
|||||||
openapi: 3.1.0
|
|
||||||
info:
|
|
||||||
title: PocketBase MiniApp Company API
|
|
||||||
version: 1.0.0
|
|
||||||
summary: 小程序端通过 PocketBase JS SDK 直连 tbl_company 的基础 CRUD 文档
|
|
||||||
description: >-
|
|
||||||
本文档面向小程序端直接使用 PocketBase JS SDK / REST API 访问 `tbl_company`。
|
|
||||||
本文档统一以 PocketBase 原生记录主键 `id` 作为唯一识别键。
|
|
||||||
`company_id` 保留为普通业务字段,可用于展示、筛选和业务关联,但不再作为 CRUD 的唯一键。
|
|
||||||
当前线上 `tbl_company` 还包含 `company_owner_openid` 字段,用于保存公司所有者 openid,并带普通索引。
|
|
||||||
同时新增了国家、省、市、区的名称与编码字段,便于前端直接按行政区划存取。
|
|
||||||
license:
|
|
||||||
name: Proprietary
|
|
||||||
identifier: LicenseRef-Proprietary
|
|
||||||
servers:
|
|
||||||
- url: https://bai-api.blv-oa.com/pb
|
|
||||||
description: 线上 PocketBase 服务
|
|
||||||
tags:
|
|
||||||
- name: Company
|
|
||||||
description: tbl_company 公司信息基础 CRUD
|
|
||||||
paths:
|
|
||||||
/api/collections/tbl_company/records:
|
|
||||||
get:
|
|
||||||
tags: [Company]
|
|
||||||
operationId: listCompanyRecords
|
|
||||||
summary: 查询公司列表
|
|
||||||
description: >-
|
|
||||||
使用 PocketBase 原生 records list/search 接口查询 `tbl_company`。
|
|
||||||
支持三种常见模式:
|
|
||||||
1. 全表查询:不传 `filter`;
|
|
||||||
2. 精确查询:`filter=id="q1w2e3r4t5y6u7i"`;
|
|
||||||
3. 模糊查询:`filter=(company_name~"华住" || company_usci~"9131" || company_entity~"张三")`;
|
|
||||||
4. 按 `company_id` 查询单条:`filter=company_id="WX-COMPANY-10001"&perPage=1&page=1`。
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/Page'
|
|
||||||
- $ref: '#/components/parameters/PerPage'
|
|
||||||
- $ref: '#/components/parameters/Sort'
|
|
||||||
- $ref: '#/components/parameters/Filter'
|
|
||||||
- $ref: '#/components/parameters/Fields'
|
|
||||||
- $ref: '#/components/parameters/SkipTotal'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: 查询成功
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/CompanyListResponse'
|
|
||||||
examples:
|
|
||||||
all:
|
|
||||||
summary: 全表查询
|
|
||||||
value:
|
|
||||||
page: 1
|
|
||||||
perPage: 30
|
|
||||||
totalItems: 2
|
|
||||||
totalPages: 1
|
|
||||||
items:
|
|
||||||
- id: q1w2e3r4t5y6u7i
|
|
||||||
collectionId: pbc_company_demo
|
|
||||||
collectionName: tbl_company
|
|
||||||
created: '2026-03-27 10:00:00.000Z'
|
|
||||||
updated: '2026-03-27 10:00:00.000Z'
|
|
||||||
company_id: C10001
|
|
||||||
company_name: 宝镜科技
|
|
||||||
company_type: 渠道商
|
|
||||||
company_entity: 张三
|
|
||||||
company_usci: '91310000123456789A'
|
|
||||||
company_nationality: 中国
|
|
||||||
company_nationality_code: CN
|
|
||||||
company_province: 上海
|
|
||||||
company_province_code: '310000'
|
|
||||||
company_city: 上海
|
|
||||||
company_city_code: '310100'
|
|
||||||
company_district: 浦东新区
|
|
||||||
company_district_code: '310115'
|
|
||||||
company_postalcode: '200000'
|
|
||||||
company_add: 上海市浦东新区XX路1号
|
|
||||||
company_status: 有效
|
|
||||||
company_level: A
|
|
||||||
company_owner_openid: wx-openid-owner-001
|
|
||||||
company_remark: ''
|
|
||||||
exact:
|
|
||||||
summary: 按 id 精确查询
|
|
||||||
value:
|
|
||||||
page: 1
|
|
||||||
perPage: 1
|
|
||||||
totalItems: 1
|
|
||||||
totalPages: 1
|
|
||||||
items:
|
|
||||||
- id: q1w2e3r4t5y6u7i
|
|
||||||
collectionId: pbc_company_demo
|
|
||||||
collectionName: tbl_company
|
|
||||||
created: '2026-03-27 10:00:00.000Z'
|
|
||||||
updated: '2026-03-27 10:00:00.000Z'
|
|
||||||
company_id: C10001
|
|
||||||
company_name: 宝镜科技
|
|
||||||
company_type: 渠道商
|
|
||||||
company_entity: 张三
|
|
||||||
company_usci: '91310000123456789A'
|
|
||||||
company_nationality: 中国
|
|
||||||
company_nationality_code: CN
|
|
||||||
company_province: 上海
|
|
||||||
company_province_code: '310000'
|
|
||||||
company_city: 上海
|
|
||||||
company_city_code: '310100'
|
|
||||||
company_district: 浦东新区
|
|
||||||
company_district_code: '310115'
|
|
||||||
company_postalcode: '200000'
|
|
||||||
company_add: 上海市浦东新区XX路1号
|
|
||||||
company_status: 有效
|
|
||||||
company_level: A
|
|
||||||
company_owner_openid: wx-openid-owner-001
|
|
||||||
company_remark: ''
|
|
||||||
'400':
|
|
||||||
description: 过滤表达式或查询参数不合法
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
'403':
|
|
||||||
description: 当前调用方没有 list 权限
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
post:
|
|
||||||
tags: [Company]
|
|
||||||
operationId: createCompanyRecord
|
|
||||||
summary: 新增公司
|
|
||||||
description: >-
|
|
||||||
创建一条 `tbl_company` 记录。当前文档以 `id` 作为记录唯一识别键,
|
|
||||||
新建成功后由 PocketBase 自动生成 `id`;`company_id` 也由数据库自动生成,
|
|
||||||
客户端创建时不需要传入,但仍可作为后续业务查询字段。
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/CompanyCreateRequest'
|
|
||||||
examples:
|
|
||||||
default:
|
|
||||||
value:
|
|
||||||
company_name: 宝镜科技
|
|
||||||
company_type: 渠道商
|
|
||||||
company_entity: 张三
|
|
||||||
company_usci: '91310000123456789A'
|
|
||||||
company_nationality: 中国
|
|
||||||
company_nationality_code: CN
|
|
||||||
company_province: 上海
|
|
||||||
company_province_code: '310000'
|
|
||||||
company_city: 上海
|
|
||||||
company_city_code: '310100'
|
|
||||||
company_district: 浦东新区
|
|
||||||
company_district_code: '310115'
|
|
||||||
company_postalcode: '200000'
|
|
||||||
company_add: 上海市浦东新区XX路1号
|
|
||||||
company_status: 有效
|
|
||||||
company_level: A
|
|
||||||
company_owner_openid: wx-openid-owner-001
|
|
||||||
company_remark: 首次创建
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: 创建成功
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/CompanyRecord'
|
|
||||||
'400':
|
|
||||||
description: 校验失败,例如字段类型不合法或违反当前集合约束
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
'403':
|
|
||||||
description: 当前调用方没有 create 权限
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
'404':
|
|
||||||
description: 集合不存在
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
/api/collections/tbl_company/records/{recordId}:
|
|
||||||
get:
|
|
||||||
tags: [Company]
|
|
||||||
operationId: getCompanyRecordByRecordId
|
|
||||||
summary: 按 PocketBase 记录 id 查询公司
|
|
||||||
description: >-
|
|
||||||
这是 PocketBase 原生单条查询接口,路径参数必须传记录主键 `id`。
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/RecordId'
|
|
||||||
- $ref: '#/components/parameters/Fields'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: 查询成功
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/CompanyRecord'
|
|
||||||
'403':
|
|
||||||
description: 当前调用方没有 view 权限
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
'404':
|
|
||||||
description: 记录不存在
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
patch:
|
|
||||||
tags: [Company]
|
|
||||||
operationId: updateCompanyRecordByRecordId
|
|
||||||
summary: 按 PocketBase 记录 id 更新公司
|
|
||||||
description: >-
|
|
||||||
这是 PocketBase 原生更新接口,路径参数统一使用记录主键 `id`。
|
|
||||||
如果业务侧只有 `company_id`,标准流程是先调用 list 接口
|
|
||||||
`filter=company_id="..."&perPage=1&page=1` 查出对应记录,再用返回的 `id` 调用本接口。
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/RecordId'
|
|
||||||
- $ref: '#/components/parameters/Fields'
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/CompanyUpdateRequest'
|
|
||||||
examples:
|
|
||||||
default:
|
|
||||||
value:
|
|
||||||
company_name: 宝镜科技(更新)
|
|
||||||
company_status: 有效
|
|
||||||
company_level: S
|
|
||||||
company_owner_openid: wx-openid-owner-002
|
|
||||||
company_remark: 已更新基础资料
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: 更新成功
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/CompanyRecord'
|
|
||||||
'400':
|
|
||||||
description: 更新参数不合法
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
'403':
|
|
||||||
description: 当前调用方没有 update 权限
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
'404':
|
|
||||||
description: 记录不存在
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
delete:
|
|
||||||
tags: [Company]
|
|
||||||
operationId: deleteCompanyRecordByRecordId
|
|
||||||
summary: 按 PocketBase 记录 id 删除公司
|
|
||||||
description: >-
|
|
||||||
这是 PocketBase 原生删除接口,路径参数统一使用记录主键 `id`。
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/components/parameters/RecordId'
|
|
||||||
responses:
|
|
||||||
'204':
|
|
||||||
description: 删除成功
|
|
||||||
'400':
|
|
||||||
description: 删除失败,例如仍被必填 relation 引用
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
'403':
|
|
||||||
description: 当前调用方没有 delete 权限
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
'404':
|
|
||||||
description: 记录不存在
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/PocketBaseError'
|
|
||||||
components:
|
|
||||||
parameters:
|
|
||||||
Page:
|
|
||||||
name: page
|
|
||||||
in: query
|
|
||||||
description: 页码,默认 1
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
minimum: 1
|
|
||||||
default: 1
|
|
||||||
PerPage:
|
|
||||||
name: perPage
|
|
||||||
in: query
|
|
||||||
description: 每页返回条数,默认 30
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
minimum: 1
|
|
||||||
default: 30
|
|
||||||
Sort:
|
|
||||||
name: sort
|
|
||||||
in: query
|
|
||||||
description: 排序字段,例如 `-created,+company_name`
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
Filter:
|
|
||||||
name: filter
|
|
||||||
in: query
|
|
||||||
description: >-
|
|
||||||
PocketBase 过滤表达式。
|
|
||||||
精确查询示例:`id="q1w2e3r4t5y6u7i"`;
|
|
||||||
模糊查询示例:`(company_name~"宝镜" || company_usci~"9131" || company_entity~"张三")`
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
Fields:
|
|
||||||
name: fields
|
|
||||||
in: query
|
|
||||||
description: 逗号分隔的返回字段列表,例如 `id,company_id,company_name`
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
SkipTotal:
|
|
||||||
name: skipTotal
|
|
||||||
in: query
|
|
||||||
description: 是否跳过 totalItems/totalPages 统计
|
|
||||||
schema:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
RecordId:
|
|
||||||
name: recordId
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
description: PocketBase 记录主键 id
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
schemas:
|
|
||||||
CompanyBase:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
company_id:
|
|
||||||
type: string
|
|
||||||
description: 公司业务编号字段,不再作为 CRUD 唯一键
|
|
||||||
company_name:
|
|
||||||
type: string
|
|
||||||
description: 公司名称
|
|
||||||
company_type:
|
|
||||||
type: string
|
|
||||||
description: 公司类型
|
|
||||||
company_entity:
|
|
||||||
type: string
|
|
||||||
description: 公司法人
|
|
||||||
company_usci:
|
|
||||||
type: string
|
|
||||||
description: 统一社会信用代码
|
|
||||||
company_nationality:
|
|
||||||
type: string
|
|
||||||
description: 国家
|
|
||||||
company_nationality_code:
|
|
||||||
type: string
|
|
||||||
description: 国家编码
|
|
||||||
company_province:
|
|
||||||
type: string
|
|
||||||
description: 省份
|
|
||||||
company_province_code:
|
|
||||||
type: string
|
|
||||||
description: 省份编码
|
|
||||||
company_city:
|
|
||||||
type: string
|
|
||||||
description: 城市
|
|
||||||
company_city_code:
|
|
||||||
type: string
|
|
||||||
description: 城市编码
|
|
||||||
company_district:
|
|
||||||
type: string
|
|
||||||
description: 区/县
|
|
||||||
company_district_code:
|
|
||||||
type: string
|
|
||||||
description: 区/县编码
|
|
||||||
company_postalcode:
|
|
||||||
type: string
|
|
||||||
description: 邮编
|
|
||||||
company_add:
|
|
||||||
type: string
|
|
||||||
description: 地址
|
|
||||||
company_status:
|
|
||||||
type: string
|
|
||||||
description: 公司状态
|
|
||||||
company_level:
|
|
||||||
type: string
|
|
||||||
description: 公司等级
|
|
||||||
company_owner_openid:
|
|
||||||
type: string
|
|
||||||
description: 公司所有者 openid
|
|
||||||
company_remark:
|
|
||||||
type: string
|
|
||||||
description: 备注
|
|
||||||
CompanyCreateRequest:
|
|
||||||
type: object
|
|
||||||
description: 创建时不需要传 `company_id`,由数据库自动生成。
|
|
||||||
properties:
|
|
||||||
company_name:
|
|
||||||
description: "公司名称"
|
|
||||||
type: string
|
|
||||||
company_type:
|
|
||||||
description: "公司类型"
|
|
||||||
type: string
|
|
||||||
company_entity:
|
|
||||||
description: "公司法人"
|
|
||||||
type: string
|
|
||||||
company_usci:
|
|
||||||
description: "统一社会信用代码"
|
|
||||||
type: string
|
|
||||||
company_nationality:
|
|
||||||
description: "国家名称"
|
|
||||||
type: string
|
|
||||||
company_nationality_code:
|
|
||||||
description: "国家编码"
|
|
||||||
type: string
|
|
||||||
company_province:
|
|
||||||
description: "省份名称"
|
|
||||||
type: string
|
|
||||||
company_province_code:
|
|
||||||
description: "省份编码"
|
|
||||||
type: string
|
|
||||||
company_city:
|
|
||||||
description: "城市名称"
|
|
||||||
type: string
|
|
||||||
company_city_code:
|
|
||||||
description: "城市编码"
|
|
||||||
type: string
|
|
||||||
company_district:
|
|
||||||
description: "区 / 县名称"
|
|
||||||
type: string
|
|
||||||
company_district_code:
|
|
||||||
description: "区 / 县编码"
|
|
||||||
type: string
|
|
||||||
company_postalcode:
|
|
||||||
description: "邮编"
|
|
||||||
type: string
|
|
||||||
company_add:
|
|
||||||
description: "地址"
|
|
||||||
type: string
|
|
||||||
company_status:
|
|
||||||
description: "公司状态"
|
|
||||||
type: string
|
|
||||||
company_level:
|
|
||||||
description: "公司等级"
|
|
||||||
type: string
|
|
||||||
company_owner_openid:
|
|
||||||
description: "公司所有者 openid"
|
|
||||||
type: string
|
|
||||||
company_remark:
|
|
||||||
description: "备注"
|
|
||||||
type: string
|
|
||||||
additionalProperties: false
|
|
||||||
CompanyUpdateRequest:
|
|
||||||
type: object
|
|
||||||
description: >-
|
|
||||||
更新时可只传需要修改的字段;记录定位统一依赖路径参数 `id`。
|
|
||||||
properties:
|
|
||||||
company_id:
|
|
||||||
description: "所属公司业务 ID"
|
|
||||||
type: string
|
|
||||||
company_name:
|
|
||||||
description: "公司名称"
|
|
||||||
type: string
|
|
||||||
company_type:
|
|
||||||
description: "公司类型"
|
|
||||||
type: string
|
|
||||||
company_entity:
|
|
||||||
description: "公司法人"
|
|
||||||
type: string
|
|
||||||
company_usci:
|
|
||||||
description: "统一社会信用代码"
|
|
||||||
type: string
|
|
||||||
company_nationality:
|
|
||||||
description: "国家名称"
|
|
||||||
type: string
|
|
||||||
company_nationality_code:
|
|
||||||
description: "国家编码"
|
|
||||||
type: string
|
|
||||||
company_province:
|
|
||||||
description: "省份名称"
|
|
||||||
type: string
|
|
||||||
company_province_code:
|
|
||||||
description: "省份编码"
|
|
||||||
type: string
|
|
||||||
company_city:
|
|
||||||
description: "城市名称"
|
|
||||||
type: string
|
|
||||||
company_city_code:
|
|
||||||
description: "城市编码"
|
|
||||||
type: string
|
|
||||||
company_district:
|
|
||||||
description: "区 / 县名称"
|
|
||||||
type: string
|
|
||||||
company_district_code:
|
|
||||||
description: "区 / 县编码"
|
|
||||||
type: string
|
|
||||||
company_postalcode:
|
|
||||||
description: "邮编"
|
|
||||||
type: string
|
|
||||||
company_add:
|
|
||||||
description: "地址"
|
|
||||||
type: string
|
|
||||||
company_status:
|
|
||||||
description: "公司状态"
|
|
||||||
type: string
|
|
||||||
company_level:
|
|
||||||
description: "公司等级"
|
|
||||||
type: string
|
|
||||||
company_owner_openid:
|
|
||||||
description: "公司所有者 openid"
|
|
||||||
type: string
|
|
||||||
company_remark:
|
|
||||||
description: "备注"
|
|
||||||
type: string
|
|
||||||
CompanyRecord:
|
|
||||||
allOf:
|
|
||||||
- type: object
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
description: PocketBase 记录主键 id
|
|
||||||
collectionId:
|
|
||||||
type: string
|
|
||||||
collectionName:
|
|
||||||
type: string
|
|
||||||
created:
|
|
||||||
description: "记录创建时间"
|
|
||||||
type: string
|
|
||||||
updated:
|
|
||||||
description: "记录更新时间"
|
|
||||||
type: string
|
|
||||||
- $ref: '#/components/schemas/CompanyBase'
|
|
||||||
CompanyListResponse:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
page:
|
|
||||||
type: integer
|
|
||||||
perPage:
|
|
||||||
type: integer
|
|
||||||
totalItems:
|
|
||||||
type: integer
|
|
||||||
totalPages:
|
|
||||||
type: integer
|
|
||||||
items:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: '#/components/schemas/CompanyRecord'
|
|
||||||
PocketBaseError:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: integer
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
data:
|
|
||||||
description: "业务响应数据"
|
|
||||||
type: object
|
|
||||||
additionalProperties: true
|
|
||||||
File diff suppressed because it is too large
Load Diff
269
pocket-base/spec/openapi-wx/attachments.yaml
Normal file
269
pocket-base/spec/openapi-wx/attachments.yaml
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: BAI PocketBase Native API - Attachments
|
||||||
|
version: 1.0.0
|
||||||
|
description: |
|
||||||
|
PocketBase 原生 `tbl_attachments` records API 文档。
|
||||||
|
本文件可单独导入使用,不依赖其他 YAML。
|
||||||
|
servers:
|
||||||
|
- url: https://bai-api.blv-oa.com
|
||||||
|
description: 生产环境
|
||||||
|
- url: http://localhost:8090
|
||||||
|
description: PocketBase 本地环境
|
||||||
|
paths:
|
||||||
|
/pb/api/collections/tbl_attachments/records:
|
||||||
|
get:
|
||||||
|
operationId: getPocketBaseAttachmentRecords
|
||||||
|
tags:
|
||||||
|
- 附件信息
|
||||||
|
summary: 根据 attachments_id 查询单条或多条附件信息
|
||||||
|
description: |
|
||||||
|
使用 PocketBase 原生 records list 接口查询 `tbl_attachments`。
|
||||||
|
|
||||||
|
当前线上权限规则:
|
||||||
|
- `listRule = is_delete = 0`,因此任何客户端都可直接读取未软删除附件
|
||||||
|
- 原生 `create/update/delete` 仍仅管理员或管理后台用户允许
|
||||||
|
|
||||||
|
标准调用方式有两种:
|
||||||
|
1. 按 `attachments_id` 查询单条:
|
||||||
|
- `filter=attachments_id="ATT-1774599142438-8n1UcU"`
|
||||||
|
- `perPage=1`
|
||||||
|
- `page=1`
|
||||||
|
2. 按多个 `attachments_id` 批量查询:
|
||||||
|
- 使用 `||` 组合多个等值条件
|
||||||
|
- 例如:`filter=attachments_id="ATT-1774599142438-8n1UcU" || attachments_id="ATT-1774599143999-7pQkLm"`
|
||||||
|
- 传 `perPage` 为预期返回条数,`page=1`
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ statusCode, errMsg, data }` 包装
|
||||||
|
- `attachments_link` 返回的是 PocketBase 文件字段值,不是完整下载地址
|
||||||
|
- 若需文件流地址,可按 PocketBase 标准文件路径自行拼接:`/pb/api/files/{collectionId}/{recordId}/{attachments_link}`
|
||||||
|
parameters:
|
||||||
|
- name: filter
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
PocketBase 标准过滤表达式。
|
||||||
|
|
||||||
|
- 按 `attachments_id` 精确查询单条:`attachments_id="ATT-1774599142438-8n1UcU"`
|
||||||
|
- 按多个 `attachments_id` 批量查询:`attachments_id="ATT-1774599142438-8n1UcU" || attachments_id="ATT-1774599143999-7pQkLm"`
|
||||||
|
- 不传该参数时,返回分页列表
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: 过滤表达式|string
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: 页码
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 1
|
||||||
|
- name: perPage
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: 每页条数;单查建议为 `1`,批量查询建议设置为预期条数
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 20
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 查询成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
example:
|
||||||
|
page: page|integer
|
||||||
|
perPage: perPage|integer
|
||||||
|
totalItems: totalItems|integer
|
||||||
|
totalPages: totalPages|integer
|
||||||
|
items:
|
||||||
|
- id: PocketBase 记录主键|string
|
||||||
|
collectionId: 集合ID|string
|
||||||
|
collectionName: 集合名称|string
|
||||||
|
attachments_id: 附件业务 ID|string
|
||||||
|
attachments_link: PocketBase 文件字段值,可按标准文件路径拼接文件流地址|string
|
||||||
|
attachments_filename: 原始文件名|string
|
||||||
|
attachments_filetype: 文件类型 / MIME|string
|
||||||
|
attachments_size: 文件大小|number
|
||||||
|
attachments_owner: 上传者业务标识|string
|
||||||
|
attachments_md5: 文件 MD5|string
|
||||||
|
attachments_ocr: OCR 识别结果|string
|
||||||
|
attachments_status: 附件状态|string
|
||||||
|
attachments_remark: 备注|string
|
||||||
|
"400":
|
||||||
|
description: 查询参数错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"403":
|
||||||
|
description: 集合规则被锁定或服务端权限设置异常
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"500":
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
PocketBaseNativeError:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
description: 业务状态码
|
||||||
|
example: 错误状态码 | integer
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: PocketBase原生错误信息 | string
|
||||||
|
data:
|
||||||
|
description: 业务响应数据
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
PocketBaseAttachmentRecord:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: PocketBase 记录主键
|
||||||
|
example: PocketBase记录主键 | string
|
||||||
|
collectionId:
|
||||||
|
type: string
|
||||||
|
description: 集合ID
|
||||||
|
example: 集合ID | string
|
||||||
|
collectionName:
|
||||||
|
type: string
|
||||||
|
description: 集合名称
|
||||||
|
example: 集合名称 | string
|
||||||
|
attachments_id:
|
||||||
|
type: string
|
||||||
|
description: 附件业务 ID
|
||||||
|
example: 附件业务ID | string
|
||||||
|
attachments_link:
|
||||||
|
type: string
|
||||||
|
description: PocketBase 文件字段值,可按标准文件路径拼接文件流地址
|
||||||
|
example: PocketBase文件字段值,可拼接文件流地址 | string
|
||||||
|
attachments_filename:
|
||||||
|
type: string
|
||||||
|
description: 原始文件名
|
||||||
|
example: 原始文件名 | string
|
||||||
|
attachments_filetype:
|
||||||
|
type: string
|
||||||
|
description: 文件类型 / MIME
|
||||||
|
example: 文件类型或MIME | string
|
||||||
|
attachments_size:
|
||||||
|
type:
|
||||||
|
- number
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
description: 文件大小
|
||||||
|
example: 文件大小 | number
|
||||||
|
attachments_owner:
|
||||||
|
type: string
|
||||||
|
description: 上传者业务标识
|
||||||
|
example: 上传者业务标识 | string
|
||||||
|
attachments_md5:
|
||||||
|
type: string
|
||||||
|
description: 文件 MD5
|
||||||
|
example: 文件MD5 | string
|
||||||
|
attachments_ocr:
|
||||||
|
type: string
|
||||||
|
description: OCR 识别结果
|
||||||
|
example: OCR识别结果 | string
|
||||||
|
attachments_status:
|
||||||
|
type: string
|
||||||
|
description: 附件状态
|
||||||
|
example: 附件状态 | string
|
||||||
|
attachments_remark:
|
||||||
|
type: string
|
||||||
|
description: 备注
|
||||||
|
example: 备注 | string
|
||||||
|
example:
|
||||||
|
id: PocketBase 记录主键|string
|
||||||
|
collectionId: 集合ID|string
|
||||||
|
collectionName: 集合名称|string
|
||||||
|
attachments_id: 附件业务 ID|string
|
||||||
|
attachments_link: PocketBase 文件字段值,可按标准文件路径拼接文件流地址|string
|
||||||
|
attachments_filename: 原始文件名|string
|
||||||
|
attachments_filetype: 文件类型 / MIME|string
|
||||||
|
attachments_size: 文件大小|number
|
||||||
|
attachments_owner: 上传者业务标识|string
|
||||||
|
attachments_md5: 文件 MD5|string
|
||||||
|
attachments_ocr: OCR 识别结果|string
|
||||||
|
attachments_status: 附件状态|string
|
||||||
|
attachments_remark: 备注|string
|
||||||
|
PocketBaseAttachmentListResponse:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- page
|
||||||
|
- perPage
|
||||||
|
- totalItems
|
||||||
|
- totalPages
|
||||||
|
- items
|
||||||
|
properties:
|
||||||
|
page:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 页码 | integer
|
||||||
|
perPage:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 每页条数 | integer
|
||||||
|
totalItems:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 总记录数 | integer
|
||||||
|
totalPages:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 总页数 | integer
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
example:
|
||||||
|
page: page|integer
|
||||||
|
perPage: perPage|integer
|
||||||
|
totalItems: totalItems|integer
|
||||||
|
totalPages: totalPages|integer
|
||||||
|
items:
|
||||||
|
- id: PocketBase 记录主键|string
|
||||||
|
collectionId: 集合ID|string
|
||||||
|
collectionName: 集合名称|string
|
||||||
|
attachments_id: 附件业务 ID|string
|
||||||
|
attachments_link: PocketBase 文件字段值,可按标准文件路径拼接文件流地址|string
|
||||||
|
attachments_filename: 原始文件名|string
|
||||||
|
attachments_filetype: 文件类型 / MIME|string
|
||||||
|
attachments_size: 文件大小|number
|
||||||
|
attachments_owner: 上传者业务标识|string
|
||||||
|
attachments_md5: 文件 MD5|string
|
||||||
|
attachments_ocr: OCR 识别结果|string
|
||||||
|
attachments_status: 附件状态|string
|
||||||
|
attachments_remark: 备注|string
|
||||||
435
pocket-base/spec/openapi-wx/cart.yaml
Normal file
435
pocket-base/spec/openapi-wx/cart.yaml
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: BAI PocketBase Native API - Cart
|
||||||
|
version: 1.0.0
|
||||||
|
description: |
|
||||||
|
PocketBase 原生 `tbl_cart` records API 文档。
|
||||||
|
本文件可单独导入使用,不依赖其他 YAML。
|
||||||
|
servers:
|
||||||
|
- url: https://bai-api.blv-oa.com
|
||||||
|
description: 生产环境
|
||||||
|
- url: http://localhost:8090
|
||||||
|
description: PocketBase 本地环境
|
||||||
|
tags:
|
||||||
|
- name: 购物车
|
||||||
|
description: PocketBase 原生购物车记录接口
|
||||||
|
paths:
|
||||||
|
/pb/api/collections/tbl_cart/records:
|
||||||
|
get:
|
||||||
|
operationId: getPocketBaseCartRecords
|
||||||
|
tags: [购物车]
|
||||||
|
summary: 查询购物车记录列表
|
||||||
|
description: |
|
||||||
|
使用 PocketBase 原生 records list 接口查询 `tbl_cart`。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- `cart_product_id` 是 relation 字段,默认仅返回关联记录 id。
|
||||||
|
- 如需同时返回 `tbl_product_list` 详情,请传 `expand=cart_product_id`。
|
||||||
|
parameters:
|
||||||
|
- name: filter
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: PocketBase 原生过滤表达式
|
||||||
|
example: 过滤表达式|string
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 1
|
||||||
|
- name: perPage
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 20
|
||||||
|
- name: sort
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: 排序表达式|string
|
||||||
|
- name: expand
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: 关系字段展开;传 `cart_product_id` 时返回 `expand.cart_product_id`
|
||||||
|
example: 关联展开字段|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 查询成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseCartListResponse'
|
||||||
|
'400':
|
||||||
|
description: 查询参数错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'403':
|
||||||
|
description: 无权访问
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
post:
|
||||||
|
operationId: postPocketBaseCartRecord
|
||||||
|
tags: [购物车]
|
||||||
|
summary: 创建购物车记录
|
||||||
|
description: |
|
||||||
|
使用 PocketBase 原生 records create 接口向 `tbl_cart` 新增记录。
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseCartCreateRequest'
|
||||||
|
example:
|
||||||
|
cart_id: 购物车业务ID|string
|
||||||
|
cart_number: 购物车名称或分组号|string
|
||||||
|
cart_owner: 购物车所有者openid|string
|
||||||
|
cart_product_id: 产品业务ID|string
|
||||||
|
cart_product_quantity: 产品数量|integer
|
||||||
|
cart_status: 购物车状态|string
|
||||||
|
cart_at_price: 加入购物车时价格|number
|
||||||
|
cart_remark: 备注|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 创建成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseCartRecord'
|
||||||
|
'400':
|
||||||
|
description: 参数错误或违反集合规则
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'403':
|
||||||
|
description: 无权访问
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
/pb/api/collections/tbl_cart/records/{recordId}:
|
||||||
|
patch:
|
||||||
|
operationId: patchPocketBaseCartRecordByRecordId
|
||||||
|
tags: [购物车]
|
||||||
|
summary: 更新购物车记录
|
||||||
|
parameters:
|
||||||
|
- name: recordId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: PocketBase记录主键|string
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseCartUpdateRequest'
|
||||||
|
example:
|
||||||
|
cart_number: 购物车名称或分组号|string
|
||||||
|
cart_owner: 购物车所有者openid|string
|
||||||
|
cart_product_id: 产品业务ID|string
|
||||||
|
cart_product_quantity: 产品数量|integer
|
||||||
|
cart_status: 购物车状态|string
|
||||||
|
cart_at_price: 加入购物车时价格|number
|
||||||
|
cart_remark: 备注|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 更新成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseCartRecord'
|
||||||
|
'400':
|
||||||
|
description: 参数错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'404':
|
||||||
|
description: 记录不存在
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
delete:
|
||||||
|
operationId: deletePocketBaseCartRecordByRecordId
|
||||||
|
tags: [购物车]
|
||||||
|
summary: 删除购物车记录
|
||||||
|
parameters:
|
||||||
|
- name: recordId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: PocketBase记录主键|string
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: 删除成功
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'404':
|
||||||
|
description: 记录不存在
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
PocketBaseNativeError:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: [integer, string]
|
||||||
|
description: PocketBase错误码
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
description: PocketBase错误信息
|
||||||
|
data:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
description: PocketBase错误数据
|
||||||
|
example:
|
||||||
|
code: 错误状态码|integer
|
||||||
|
message: PocketBase原生错误信息|string
|
||||||
|
data:
|
||||||
|
字段名|string: 字段值|string
|
||||||
|
PocketBaseRecordBase:
|
||||||
|
type: object
|
||||||
|
required: [id, collectionId, collectionName, created, updated]
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: PocketBase记录主键
|
||||||
|
collectionId:
|
||||||
|
type: string
|
||||||
|
description: 集合ID
|
||||||
|
collectionName:
|
||||||
|
type: string
|
||||||
|
description: 集合名称
|
||||||
|
created:
|
||||||
|
type: string
|
||||||
|
description: 记录创建时间
|
||||||
|
updated:
|
||||||
|
type: string
|
||||||
|
description: 记录更新时间
|
||||||
|
example:
|
||||||
|
id: PocketBase记录主键|string
|
||||||
|
collectionId: 集合ID|string
|
||||||
|
collectionName: 集合名称|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
PocketBaseCartFields:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cart_id:
|
||||||
|
type: string
|
||||||
|
description: 购物车业务ID
|
||||||
|
cart_number:
|
||||||
|
type: string
|
||||||
|
description: 购物车名称或分组号
|
||||||
|
cart_create:
|
||||||
|
type: string
|
||||||
|
description: 购物车项创建时间
|
||||||
|
cart_owner:
|
||||||
|
type: string
|
||||||
|
description: 购物车所有者openid
|
||||||
|
cart_product_id:
|
||||||
|
type: string
|
||||||
|
description: 产品业务ID
|
||||||
|
cart_product_quantity:
|
||||||
|
type: [integer, number]
|
||||||
|
description: 产品数量
|
||||||
|
cart_status:
|
||||||
|
type: string
|
||||||
|
description: 购物车状态
|
||||||
|
cart_at_price:
|
||||||
|
type: [number, integer]
|
||||||
|
description: 加入购物车时价格
|
||||||
|
cart_remark:
|
||||||
|
type: string
|
||||||
|
description: 备注
|
||||||
|
example:
|
||||||
|
cart_id: 购物车业务ID|string
|
||||||
|
cart_number: 购物车名称或分组号|string
|
||||||
|
cart_create: 购物车项创建时间|string
|
||||||
|
cart_owner: 购物车所有者openid|string
|
||||||
|
cart_product_id: 产品业务ID|string
|
||||||
|
cart_product_quantity: 产品数量|integer
|
||||||
|
cart_status: 购物车状态|string
|
||||||
|
cart_at_price: 加入购物车时价格|number
|
||||||
|
cart_remark: 备注|string
|
||||||
|
PocketBaseCartRecord:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PocketBaseRecordBase'
|
||||||
|
- $ref: '#/components/schemas/PocketBaseCartFields'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
expand:
|
||||||
|
type: object
|
||||||
|
description: 关系字段展开结果(需请求参数 `expand=cart_product_id`)
|
||||||
|
properties:
|
||||||
|
cart_product_id:
|
||||||
|
$ref: '#/components/schemas/PocketBaseProductListExpand'
|
||||||
|
PocketBaseCartCreateRequest:
|
||||||
|
type: object
|
||||||
|
required: [cart_owner]
|
||||||
|
properties:
|
||||||
|
cart_id:
|
||||||
|
type: string
|
||||||
|
example: 购物车业务ID|string
|
||||||
|
cart_number:
|
||||||
|
type: string
|
||||||
|
example: 购物车名称或分组号|string
|
||||||
|
cart_owner:
|
||||||
|
type: string
|
||||||
|
example: 购物车所有者openid|string
|
||||||
|
cart_product_id:
|
||||||
|
type: string
|
||||||
|
example: 产品业务ID|string
|
||||||
|
cart_product_quantity:
|
||||||
|
type: [integer, number]
|
||||||
|
example: 产品数量|integer
|
||||||
|
cart_status:
|
||||||
|
type: string
|
||||||
|
example: 购物车状态|string
|
||||||
|
cart_at_price:
|
||||||
|
type: [number, integer]
|
||||||
|
example: 加入购物车时价格|number
|
||||||
|
cart_remark:
|
||||||
|
type: string
|
||||||
|
example: 备注|string
|
||||||
|
PocketBaseCartUpdateRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cart_number:
|
||||||
|
type: string
|
||||||
|
example: 购物车名称或分组号|string
|
||||||
|
cart_owner:
|
||||||
|
type: string
|
||||||
|
example: 购物车所有者openid|string
|
||||||
|
cart_product_id:
|
||||||
|
type: string
|
||||||
|
example: 产品业务ID|string
|
||||||
|
cart_product_quantity:
|
||||||
|
type: [integer, number]
|
||||||
|
example: 产品数量|integer
|
||||||
|
cart_status:
|
||||||
|
type: string
|
||||||
|
example: 购物车状态|string
|
||||||
|
cart_at_price:
|
||||||
|
type: [number, integer]
|
||||||
|
example: 加入购物车时价格|number
|
||||||
|
cart_remark:
|
||||||
|
type: string
|
||||||
|
example: 备注|string
|
||||||
|
PocketBaseCartListResponse:
|
||||||
|
type: object
|
||||||
|
required: [page, perPage, totalItems, totalPages, items]
|
||||||
|
properties:
|
||||||
|
page:
|
||||||
|
type: [integer, string]
|
||||||
|
perPage:
|
||||||
|
type: [integer, string]
|
||||||
|
totalItems:
|
||||||
|
type: [integer, string]
|
||||||
|
totalPages:
|
||||||
|
type: [integer, string]
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/PocketBaseCartRecord'
|
||||||
|
PocketBaseProductListExpand:
|
||||||
|
type: object
|
||||||
|
description: tbl_product_list 关联展开后的记录对象(示例字段)
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: PocketBase记录主键
|
||||||
|
example: PocketBase记录主键|string
|
||||||
|
collectionId:
|
||||||
|
type: string
|
||||||
|
description: 集合ID
|
||||||
|
example: 集合ID|string
|
||||||
|
collectionName:
|
||||||
|
type: string
|
||||||
|
description: 集合名称
|
||||||
|
example: 集合名称|string
|
||||||
|
prod_list_id:
|
||||||
|
type: string
|
||||||
|
description: 产品业务ID
|
||||||
|
example: 产品业务ID|string
|
||||||
|
prod_list_name:
|
||||||
|
type: string
|
||||||
|
description: 产品名称
|
||||||
|
example: 产品名称|string
|
||||||
|
prod_list_modelnumber:
|
||||||
|
type: string
|
||||||
|
description: 产品型号
|
||||||
|
example: 产品型号|string
|
||||||
|
prod_list_basic_price:
|
||||||
|
type: [number, integer]
|
||||||
|
description: 基础价格
|
||||||
|
example: 基础价格|number
|
||||||
|
prod_list_status:
|
||||||
|
type: string
|
||||||
|
description: 产品状态
|
||||||
|
example: 产品状态|string
|
||||||
915
pocket-base/spec/openapi-wx/company.yaml
Normal file
915
pocket-base/spec/openapi-wx/company.yaml
Normal file
@@ -0,0 +1,915 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: BAI PocketBase Native API - Company
|
||||||
|
version: 1.0.0
|
||||||
|
description: |
|
||||||
|
PocketBase 原生 `tbl_company` records API 文档。
|
||||||
|
本文件可单独导入使用,不依赖其他 YAML。
|
||||||
|
servers:
|
||||||
|
- url: https://bai-api.blv-oa.com
|
||||||
|
description: 生产环境
|
||||||
|
- url: http://localhost:8090
|
||||||
|
description: PocketBase 本地环境
|
||||||
|
paths:
|
||||||
|
/pb/api/collections/tbl_company/records:
|
||||||
|
post:
|
||||||
|
operationId: postPocketBaseCompanyRecord
|
||||||
|
tags:
|
||||||
|
- 企业信息
|
||||||
|
summary: 创建公司
|
||||||
|
description: |
|
||||||
|
使用 PocketBase 原生 records create 接口向 `tbl_company` 新增一行记录。
|
||||||
|
|
||||||
|
当前线上权限规则:
|
||||||
|
- `createRule = ""`,因此任何客户端都可直接创建
|
||||||
|
- 其他原生操作中,`update/delete/view` 仅管理员或管理后台用户允许
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ statusCode, errMsg, data }` 包装
|
||||||
|
- `company_id` 由数据库自动生成,客户端创建时不需要传
|
||||||
|
- `company_id` 仍带唯一索引,可用于后续按业务 id 查询
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseCompanyCreateRequest
|
||||||
|
example:
|
||||||
|
company_name: 公司名称|string
|
||||||
|
company_type: 公司类型|string
|
||||||
|
company_entity: 公司法人|string
|
||||||
|
company_usci: 统一社会信用代码|string
|
||||||
|
company_nationality: 国家名称|string
|
||||||
|
company_nationality_code: 国家编码|string
|
||||||
|
company_province: 省份名称|string
|
||||||
|
company_province_code: 省份编码|string
|
||||||
|
company_city: 城市名称|string
|
||||||
|
company_city_code: 城市编码|string
|
||||||
|
company_district: 区 / 县名称|string
|
||||||
|
company_district_code: 区 / 县编码|string
|
||||||
|
company_postalcode: 邮编|string
|
||||||
|
company_add: 地址|string
|
||||||
|
company_status: 公司状态|string
|
||||||
|
company_level: 公司等级|string
|
||||||
|
company_owner_openid: 公司所有者 openid|string
|
||||||
|
company_remark: 备注|string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 创建成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseCompanyRecord
|
||||||
|
example:
|
||||||
|
id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
company_id: 公司业务 id,由数据库自动生成|string
|
||||||
|
company_name: 公司名称|string
|
||||||
|
company_type: 公司类型|string
|
||||||
|
company_entity: 公司法人|string
|
||||||
|
company_usci: 统一社会信用代码|string
|
||||||
|
company_nationality: 国家名称|string
|
||||||
|
company_nationality_code: 国家编码|string
|
||||||
|
company_province: 省份名称|string
|
||||||
|
company_province_code: 省份编码|string
|
||||||
|
company_city: 城市名称|string
|
||||||
|
company_city_code: 城市编码|string
|
||||||
|
company_district: 区/县名称|string
|
||||||
|
company_district_code: 区/县编码|string
|
||||||
|
company_postalcode: 邮政编码|string
|
||||||
|
company_add: 公司地址|string
|
||||||
|
company_status: 公司状态|string
|
||||||
|
company_level: 公司等级|string
|
||||||
|
company_owner_openid: 公司所有者 openid|string
|
||||||
|
company_remark: 备注|string
|
||||||
|
"400":
|
||||||
|
description: 参数错误或违反当前集合约束
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"403":
|
||||||
|
description: 集合规则被锁定或服务端权限设置异常
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"500":
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
get:
|
||||||
|
operationId: getPocketBaseCompanyRecords
|
||||||
|
tags:
|
||||||
|
- 企业信息
|
||||||
|
summary: 查询整个 tbl_company 列表 / 根据 company_id 查询对应公司信息
|
||||||
|
description: |
|
||||||
|
使用 PocketBase 原生 records list 接口查询 `tbl_company`。
|
||||||
|
|
||||||
|
当前线上权限规则:
|
||||||
|
- `listRule = is_delete = 0`,因此默认只返回未软删除数据,且整个列表查询与条件查询都公开可读
|
||||||
|
- `createRule = ""`,因此创建也公开可调用
|
||||||
|
- `view/update/delete` 仅管理员或管理后台用户允许
|
||||||
|
|
||||||
|
标准调用方式有两种:
|
||||||
|
1. 根据 `company_id` 查询对应公司信息:
|
||||||
|
- `filter=company_id="WX-COMPANY-10001"`
|
||||||
|
- `perPage=1`
|
||||||
|
- `page=1`
|
||||||
|
2. 查询整个 `tbl_company` 列表:
|
||||||
|
- 不传 `filter`
|
||||||
|
- 按需传 `page`、`perPage`
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ statusCode, errMsg, data }` 包装
|
||||||
|
- PocketBase 原生标准接口里,“按 `company_id` 查询单条”和“查询整个列表”共用同一个 `GET /records` 路径,因此文档以同一个 GET operation 展示两种调用模式
|
||||||
|
parameters:
|
||||||
|
- name: filter
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
PocketBase 标准过滤表达式。
|
||||||
|
|
||||||
|
- 根据 `company_id` 查询单条时:`company_id="WX-COMPANY-10001"`
|
||||||
|
- 查询整个列表时:不传该参数
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: 过滤表达式|string
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: 页码
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 1
|
||||||
|
- name: perPage
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: 每页条数;按 `company_id` 单查时建议固定为 `1`
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 20
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 查询成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseCompanyListResponse
|
||||||
|
example:
|
||||||
|
page: page|integer
|
||||||
|
perPage: perPage|integer
|
||||||
|
totalItems: totalItems|integer
|
||||||
|
totalPages: totalPages|integer
|
||||||
|
items:
|
||||||
|
- id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
company_id: 公司业务 id,由数据库自动生成|string
|
||||||
|
company_name: 公司名称|string
|
||||||
|
company_type: 公司类型|string
|
||||||
|
company_entity: 公司法人|string
|
||||||
|
company_usci: 统一社会信用代码|string
|
||||||
|
company_nationality: 国家名称|string
|
||||||
|
company_nationality_code: 国家编码|string
|
||||||
|
company_province: 省份名称|string
|
||||||
|
company_province_code: 省份编码|string
|
||||||
|
company_city: 城市名称|string
|
||||||
|
company_city_code: 城市编码|string
|
||||||
|
company_district: 区/县名称|string
|
||||||
|
company_district_code: 区/县编码|string
|
||||||
|
company_postalcode: 邮政编码|string
|
||||||
|
company_add: 公司地址|string
|
||||||
|
company_status: 公司状态|string
|
||||||
|
company_level: 公司等级|string
|
||||||
|
company_owner_openid: 公司所有者 openid|string
|
||||||
|
company_remark: 备注|string
|
||||||
|
"400":
|
||||||
|
description: 查询参数错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"403":
|
||||||
|
description: 集合规则被锁定或服务端权限设置异常
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"500":
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
/pb/api/collections/tbl_company/records/{recordId}:
|
||||||
|
patch:
|
||||||
|
operationId: patchPocketBaseCompanyRecordByRecordId
|
||||||
|
tags:
|
||||||
|
- 企业信息
|
||||||
|
summary: 通过 company_id 定位后修改公司信息
|
||||||
|
description: |
|
||||||
|
这是 PocketBase 原生标准更新接口,实际写入路径参数仍然必须使用记录主键 `recordId`。
|
||||||
|
|
||||||
|
如果前端手里只有 `company_id`,标准调用流程是:
|
||||||
|
1. 先调用 `GET /pb/api/collections/tbl_company/records?filter=company_id="..."&perPage=1&page=1`
|
||||||
|
2. 从返回结果 `items[0].id` 中取出 PocketBase 原生记录主键
|
||||||
|
3. 再调用当前 `PATCH /pb/api/collections/tbl_company/records/{recordId}` 完成更新
|
||||||
|
|
||||||
|
当前线上权限规则:
|
||||||
|
- `updateRule` 仅管理员或管理后台用户允许
|
||||||
|
- 普通公开调用不能直接更新
|
||||||
|
parameters:
|
||||||
|
- name: recordId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: 通过 `company_id` 查询结果拿到的 PocketBase 记录主键 `id`
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: PocketBase记录主键|string
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseCompanyUpdateRequest
|
||||||
|
example:
|
||||||
|
company_id: 所属公司业务 ID|string
|
||||||
|
company_name: 公司名称|string
|
||||||
|
company_type: 公司类型|string
|
||||||
|
company_entity: 公司法人|string
|
||||||
|
company_usci: 统一社会信用代码|string
|
||||||
|
company_nationality: 国家名称|string
|
||||||
|
company_nationality_code: 国家编码|string
|
||||||
|
company_province: 省份名称|string
|
||||||
|
company_province_code: 省份编码|string
|
||||||
|
company_city: 城市名称|string
|
||||||
|
company_city_code: 城市编码|string
|
||||||
|
company_district: 区 / 县名称|string
|
||||||
|
company_district_code: 区 / 县编码|string
|
||||||
|
company_postalcode: 邮编|string
|
||||||
|
company_add: 地址|string
|
||||||
|
company_status: 公司状态|string
|
||||||
|
company_level: 公司等级|string
|
||||||
|
company_owner_openid: 公司所有者 openid|string
|
||||||
|
company_remark: 备注|string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 更新成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseCompanyRecord
|
||||||
|
example:
|
||||||
|
id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
company_id: 公司业务 id,由数据库自动生成|string
|
||||||
|
company_name: 公司名称|string
|
||||||
|
company_type: 公司类型|string
|
||||||
|
company_entity: 公司法人|string
|
||||||
|
company_usci: 统一社会信用代码|string
|
||||||
|
company_nationality: 国家名称|string
|
||||||
|
company_nationality_code: 国家编码|string
|
||||||
|
company_province: 省份名称|string
|
||||||
|
company_province_code: 省份编码|string
|
||||||
|
company_city: 城市名称|string
|
||||||
|
company_city_code: 城市编码|string
|
||||||
|
company_district: 区/县名称|string
|
||||||
|
company_district_code: 区/县编码|string
|
||||||
|
company_postalcode: 邮政编码|string
|
||||||
|
company_add: 公司地址|string
|
||||||
|
company_status: 公司状态|string
|
||||||
|
company_level: 公司等级|string
|
||||||
|
company_owner_openid: 公司所有者 openid|string
|
||||||
|
company_remark: 备注|string
|
||||||
|
"400":
|
||||||
|
description: 参数错误或违反集合约束
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"403":
|
||||||
|
description: 当前调用方没有 update 权限
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"404":
|
||||||
|
description: 记录不存在
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"500":
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
PocketBaseNativeError:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
description: 业务状态码
|
||||||
|
example: 错误状态码 | integer
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: PocketBase原生错误信息 | string
|
||||||
|
data:
|
||||||
|
description: 业务响应数据
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
PocketBaseRecordBase:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- collectionId
|
||||||
|
- collectionName
|
||||||
|
- created
|
||||||
|
- updated
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: PocketBase 记录主键
|
||||||
|
example: PocketBase记录主键 | string
|
||||||
|
collectionId:
|
||||||
|
type: string
|
||||||
|
example: 集合ID | string
|
||||||
|
collectionName:
|
||||||
|
type: string
|
||||||
|
example: 集合名称 | string
|
||||||
|
created:
|
||||||
|
type: string
|
||||||
|
description: 记录创建时间
|
||||||
|
example: 记录创建时间 | string
|
||||||
|
updated:
|
||||||
|
type: string
|
||||||
|
description: 记录更新时间
|
||||||
|
example: 记录更新时间 | string
|
||||||
|
example:
|
||||||
|
id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
CompanyInfo:
|
||||||
|
anyOf:
|
||||||
|
- type: object
|
||||||
|
description: 用户所属公司信息;当用户尚未绑定公司时返回 `null`
|
||||||
|
properties:
|
||||||
|
pb_id:
|
||||||
|
type: string
|
||||||
|
description: PocketBase 记录主键 id
|
||||||
|
example: PocketBase记录主键id | string
|
||||||
|
company_id:
|
||||||
|
type: string
|
||||||
|
description: 公司业务 id,由数据库自动生成
|
||||||
|
example: 公司业务id,由数据库自动生成 | string
|
||||||
|
company_name:
|
||||||
|
type: string
|
||||||
|
description: 公司名称
|
||||||
|
example: 公司名称 | string
|
||||||
|
company_type:
|
||||||
|
type: string
|
||||||
|
description: 公司类型
|
||||||
|
example: 公司类型 | string
|
||||||
|
company_entity:
|
||||||
|
type: string
|
||||||
|
description: 公司法人
|
||||||
|
example: 公司法人 | string
|
||||||
|
company_usci:
|
||||||
|
type: string
|
||||||
|
description: 统一社会信用代码
|
||||||
|
example: 统一社会信用代码 | string
|
||||||
|
company_nationality:
|
||||||
|
type: string
|
||||||
|
description: 国家名称
|
||||||
|
example: 国家名称 | string
|
||||||
|
company_nationality_code:
|
||||||
|
type: string
|
||||||
|
description: 国家编码
|
||||||
|
example: 国家编码 | string
|
||||||
|
company_province:
|
||||||
|
type: string
|
||||||
|
description: 省份名称
|
||||||
|
example: 省份名称 | string
|
||||||
|
company_province_code:
|
||||||
|
type: string
|
||||||
|
description: 省份编码
|
||||||
|
example: 省份编码 | string
|
||||||
|
company_city:
|
||||||
|
type: string
|
||||||
|
description: 城市名称
|
||||||
|
example: 城市名称 | string
|
||||||
|
company_city_code:
|
||||||
|
type: string
|
||||||
|
description: 城市编码
|
||||||
|
example: 城市编码 | string
|
||||||
|
company_district:
|
||||||
|
type: string
|
||||||
|
description: 区/县名称
|
||||||
|
example: 区县名称 | string
|
||||||
|
company_district_code:
|
||||||
|
type: string
|
||||||
|
description: 区/县编码
|
||||||
|
example: 区县编码 | string
|
||||||
|
company_postalcode:
|
||||||
|
type: string
|
||||||
|
description: 邮政编码
|
||||||
|
example: 邮政编码 | string
|
||||||
|
company_add:
|
||||||
|
type: string
|
||||||
|
description: 公司地址
|
||||||
|
example: 公司地址 | string
|
||||||
|
company_status:
|
||||||
|
type: string
|
||||||
|
description: 公司状态
|
||||||
|
example: 公司状态 | string
|
||||||
|
company_level:
|
||||||
|
type: string
|
||||||
|
description: 公司等级
|
||||||
|
example: 公司等级 | string
|
||||||
|
company_owner_openid:
|
||||||
|
type: string
|
||||||
|
description: 公司所有者 openid
|
||||||
|
example: 公司所有者openid | string
|
||||||
|
company_remark:
|
||||||
|
type: string
|
||||||
|
description: 备注
|
||||||
|
example: 备注 | string
|
||||||
|
created:
|
||||||
|
type: string
|
||||||
|
description: 记录创建时间
|
||||||
|
example: 记录创建时间 | string
|
||||||
|
updated:
|
||||||
|
type: string
|
||||||
|
description: 记录更新时间
|
||||||
|
example: 记录更新时间 | string
|
||||||
|
example:
|
||||||
|
pb_id: PocketBase记录主键id | string
|
||||||
|
company_id: 公司业务id,由数据库自动生成 | string
|
||||||
|
company_name: 公司名称 | string
|
||||||
|
company_type: 公司类型 | string
|
||||||
|
company_entity: 公司法人 | string
|
||||||
|
company_usci: 统一社会信用代码 | string
|
||||||
|
company_nationality: 国家名称 | string
|
||||||
|
company_nationality_code: 国家编码 | string
|
||||||
|
company_province: 省份名称 | string
|
||||||
|
company_province_code: 省份编码 | string
|
||||||
|
company_city: 城市名称 | string
|
||||||
|
company_city_code: 城市编码 | string
|
||||||
|
company_district: 区县名称 | string
|
||||||
|
company_district_code: 区县编码 | string
|
||||||
|
company_postalcode: 邮政编码 | string
|
||||||
|
company_add: 公司地址 | string
|
||||||
|
company_status: 公司状态 | string
|
||||||
|
company_level: 公司等级 | string
|
||||||
|
company_owner_openid: 公司所有者openid | string
|
||||||
|
company_remark: 备注 | string
|
||||||
|
created: 记录创建时间 | string
|
||||||
|
updated: 记录更新时间 | string
|
||||||
|
- type: "null"
|
||||||
|
example:
|
||||||
|
pb_id: PocketBase 记录主键 id|string
|
||||||
|
company_id: 公司业务 id,由数据库自动生成|string
|
||||||
|
company_name: 公司名称|string
|
||||||
|
company_type: 公司类型|string
|
||||||
|
company_entity: 公司法人|string
|
||||||
|
company_usci: 统一社会信用代码|string
|
||||||
|
company_nationality: 国家名称|string
|
||||||
|
company_nationality_code: 国家编码|string
|
||||||
|
company_province: 省份名称|string
|
||||||
|
company_province_code: 省份编码|string
|
||||||
|
company_city: 城市名称|string
|
||||||
|
company_city_code: 城市编码|string
|
||||||
|
company_district: 区/县名称|string
|
||||||
|
company_district_code: 区/县编码|string
|
||||||
|
company_postalcode: 邮政编码|string
|
||||||
|
company_add: 公司地址|string
|
||||||
|
company_status: 公司状态|string
|
||||||
|
company_level: 公司等级|string
|
||||||
|
company_owner_openid: 公司所有者 openid|string
|
||||||
|
company_remark: 备注|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
PocketBaseCompanyFields:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
company_id:
|
||||||
|
type: string
|
||||||
|
description: 公司业务 id,由数据库自动生成
|
||||||
|
example: 公司业务id,由数据库自动生成 | string
|
||||||
|
company_name:
|
||||||
|
type: string
|
||||||
|
description: 公司名称
|
||||||
|
example: 公司名称 | string
|
||||||
|
company_type:
|
||||||
|
type: string
|
||||||
|
description: 公司类型
|
||||||
|
example: 公司类型 | string
|
||||||
|
company_entity:
|
||||||
|
type: string
|
||||||
|
description: 公司法人
|
||||||
|
example: 公司法人 | string
|
||||||
|
company_usci:
|
||||||
|
type: string
|
||||||
|
description: 统一社会信用代码
|
||||||
|
example: 统一社会信用代码 | string
|
||||||
|
company_nationality:
|
||||||
|
type: string
|
||||||
|
description: 国家名称
|
||||||
|
example: 国家名称 | string
|
||||||
|
company_nationality_code:
|
||||||
|
type: string
|
||||||
|
description: 国家编码
|
||||||
|
example: 国家编码 | string
|
||||||
|
company_province:
|
||||||
|
type: string
|
||||||
|
description: 省份名称
|
||||||
|
example: 省份名称 | string
|
||||||
|
company_province_code:
|
||||||
|
type: string
|
||||||
|
description: 省份编码
|
||||||
|
example: 省份编码 | string
|
||||||
|
company_city:
|
||||||
|
type: string
|
||||||
|
description: 城市名称
|
||||||
|
example: 城市名称 | string
|
||||||
|
company_city_code:
|
||||||
|
type: string
|
||||||
|
description: 城市编码
|
||||||
|
example: 城市编码 | string
|
||||||
|
company_district:
|
||||||
|
type: string
|
||||||
|
description: 区/县名称
|
||||||
|
example: 区县名称 | string
|
||||||
|
company_district_code:
|
||||||
|
type: string
|
||||||
|
description: 区/县编码
|
||||||
|
example: 区县编码 | string
|
||||||
|
company_postalcode:
|
||||||
|
type: string
|
||||||
|
description: 邮政编码
|
||||||
|
example: 邮政编码 | string
|
||||||
|
company_add:
|
||||||
|
type: string
|
||||||
|
description: 公司地址
|
||||||
|
example: 公司地址 | string
|
||||||
|
company_status:
|
||||||
|
type: string
|
||||||
|
description: 公司状态
|
||||||
|
example: 公司状态 | string
|
||||||
|
company_level:
|
||||||
|
type: string
|
||||||
|
description: 公司等级
|
||||||
|
example: 公司等级 | string
|
||||||
|
company_owner_openid:
|
||||||
|
type: string
|
||||||
|
description: 公司所有者 openid
|
||||||
|
example: 公司所有者openid | string
|
||||||
|
company_remark:
|
||||||
|
type: string
|
||||||
|
description: 备注
|
||||||
|
example: 备注 | string
|
||||||
|
example:
|
||||||
|
company_id: 公司业务 id,由数据库自动生成|string
|
||||||
|
company_name: 公司名称|string
|
||||||
|
company_type: 公司类型|string
|
||||||
|
company_entity: 公司法人|string
|
||||||
|
company_usci: 统一社会信用代码|string
|
||||||
|
company_nationality: 国家名称|string
|
||||||
|
company_nationality_code: 国家编码|string
|
||||||
|
company_province: 省份名称|string
|
||||||
|
company_province_code: 省份编码|string
|
||||||
|
company_city: 城市名称|string
|
||||||
|
company_city_code: 城市编码|string
|
||||||
|
company_district: 区/县名称|string
|
||||||
|
company_district_code: 区/县编码|string
|
||||||
|
company_postalcode: 邮政编码|string
|
||||||
|
company_add: 公司地址|string
|
||||||
|
company_status: 公司状态|string
|
||||||
|
company_level: 公司等级|string
|
||||||
|
company_owner_openid: 公司所有者 openid|string
|
||||||
|
company_remark: 备注|string
|
||||||
|
PocketBaseCompanyRecord:
|
||||||
|
allOf:
|
||||||
|
- $ref: #/components/schemas/PocketBaseRecordBase
|
||||||
|
- $ref: #/components/schemas/PocketBaseCompanyFields
|
||||||
|
example:
|
||||||
|
id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
company_id: 公司业务 id,由数据库自动生成|string
|
||||||
|
company_name: 公司名称|string
|
||||||
|
company_type: 公司类型|string
|
||||||
|
company_entity: 公司法人|string
|
||||||
|
company_usci: 统一社会信用代码|string
|
||||||
|
company_nationality: 国家名称|string
|
||||||
|
company_nationality_code: 国家编码|string
|
||||||
|
company_province: 省份名称|string
|
||||||
|
company_province_code: 省份编码|string
|
||||||
|
company_city: 城市名称|string
|
||||||
|
company_city_code: 城市编码|string
|
||||||
|
company_district: 区/县名称|string
|
||||||
|
company_district_code: 区/县编码|string
|
||||||
|
company_postalcode: 邮政编码|string
|
||||||
|
company_add: 公司地址|string
|
||||||
|
company_status: 公司状态|string
|
||||||
|
company_level: 公司等级|string
|
||||||
|
company_owner_openid: 公司所有者 openid|string
|
||||||
|
company_remark: 备注|string
|
||||||
|
PocketBaseCompanyCreateRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
company_name:
|
||||||
|
description: 公司名称
|
||||||
|
type: string
|
||||||
|
company_type:
|
||||||
|
description: 公司类型
|
||||||
|
type: string
|
||||||
|
company_entity:
|
||||||
|
description: 公司法人
|
||||||
|
type: string
|
||||||
|
company_usci:
|
||||||
|
description: 统一社会信用代码
|
||||||
|
type: string
|
||||||
|
company_nationality:
|
||||||
|
description: 国家名称
|
||||||
|
type: string
|
||||||
|
company_nationality_code:
|
||||||
|
description: 国家编码
|
||||||
|
type: string
|
||||||
|
company_province:
|
||||||
|
description: 省份名称
|
||||||
|
type: string
|
||||||
|
company_province_code:
|
||||||
|
description: 省份编码
|
||||||
|
type: string
|
||||||
|
company_city:
|
||||||
|
description: 城市名称
|
||||||
|
type: string
|
||||||
|
company_city_code:
|
||||||
|
description: 城市编码
|
||||||
|
type: string
|
||||||
|
company_district:
|
||||||
|
description: 区 / 县名称
|
||||||
|
type: string
|
||||||
|
company_district_code:
|
||||||
|
description: 区 / 县编码
|
||||||
|
type: string
|
||||||
|
company_postalcode:
|
||||||
|
description: 邮编
|
||||||
|
type: string
|
||||||
|
company_add:
|
||||||
|
description: 地址
|
||||||
|
type: string
|
||||||
|
company_status:
|
||||||
|
description: 公司状态
|
||||||
|
type: string
|
||||||
|
company_level:
|
||||||
|
description: 公司等级
|
||||||
|
type: string
|
||||||
|
company_owner_openid:
|
||||||
|
description: 公司所有者 openid
|
||||||
|
type: string
|
||||||
|
company_remark:
|
||||||
|
description: 备注
|
||||||
|
type: string
|
||||||
|
additionalProperties: false
|
||||||
|
example:
|
||||||
|
company_name: 公司名称|string
|
||||||
|
company_type: 公司类型|string
|
||||||
|
company_entity: 公司法人|string
|
||||||
|
company_usci: 统一社会信用代码|string
|
||||||
|
company_nationality: 国家名称|string
|
||||||
|
company_nationality_code: 国家编码|string
|
||||||
|
company_province: 省份名称|string
|
||||||
|
company_province_code: 省份编码|string
|
||||||
|
company_city: 城市名称|string
|
||||||
|
company_city_code: 城市编码|string
|
||||||
|
company_district: 区 / 县名称|string
|
||||||
|
company_district_code: 区 / 县编码|string
|
||||||
|
company_postalcode: 邮编|string
|
||||||
|
company_add: 地址|string
|
||||||
|
company_status: 公司状态|string
|
||||||
|
company_level: 公司等级|string
|
||||||
|
company_owner_openid: 公司所有者 openid|string
|
||||||
|
company_remark: 备注|string
|
||||||
|
PocketBaseCompanyUpdateRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
company_id:
|
||||||
|
description: 所属公司业务 ID
|
||||||
|
type: string
|
||||||
|
company_name:
|
||||||
|
description: 公司名称
|
||||||
|
type: string
|
||||||
|
company_type:
|
||||||
|
description: 公司类型
|
||||||
|
type: string
|
||||||
|
company_entity:
|
||||||
|
description: 公司法人
|
||||||
|
type: string
|
||||||
|
company_usci:
|
||||||
|
description: 统一社会信用代码
|
||||||
|
type: string
|
||||||
|
company_nationality:
|
||||||
|
description: 国家名称
|
||||||
|
type: string
|
||||||
|
company_nationality_code:
|
||||||
|
description: 国家编码
|
||||||
|
type: string
|
||||||
|
company_province:
|
||||||
|
description: 省份名称
|
||||||
|
type: string
|
||||||
|
company_province_code:
|
||||||
|
description: 省份编码
|
||||||
|
type: string
|
||||||
|
company_city:
|
||||||
|
description: 城市名称
|
||||||
|
type: string
|
||||||
|
company_city_code:
|
||||||
|
description: 城市编码
|
||||||
|
type: string
|
||||||
|
company_district:
|
||||||
|
description: 区 / 县名称
|
||||||
|
type: string
|
||||||
|
company_district_code:
|
||||||
|
description: 区 / 县编码
|
||||||
|
type: string
|
||||||
|
company_postalcode:
|
||||||
|
description: 邮编
|
||||||
|
type: string
|
||||||
|
company_add:
|
||||||
|
description: 地址
|
||||||
|
type: string
|
||||||
|
company_status:
|
||||||
|
description: 公司状态
|
||||||
|
type: string
|
||||||
|
company_level:
|
||||||
|
description: 公司等级
|
||||||
|
type: string
|
||||||
|
company_owner_openid:
|
||||||
|
description: 公司所有者 openid
|
||||||
|
type: string
|
||||||
|
company_remark:
|
||||||
|
description: 备注
|
||||||
|
type: string
|
||||||
|
additionalProperties: false
|
||||||
|
example:
|
||||||
|
company_id: 所属公司业务 ID|string
|
||||||
|
company_name: 公司名称|string
|
||||||
|
company_type: 公司类型|string
|
||||||
|
company_entity: 公司法人|string
|
||||||
|
company_usci: 统一社会信用代码|string
|
||||||
|
company_nationality: 国家名称|string
|
||||||
|
company_nationality_code: 国家编码|string
|
||||||
|
company_province: 省份名称|string
|
||||||
|
company_province_code: 省份编码|string
|
||||||
|
company_city: 城市名称|string
|
||||||
|
company_city_code: 城市编码|string
|
||||||
|
company_district: 区 / 县名称|string
|
||||||
|
company_district_code: 区 / 县编码|string
|
||||||
|
company_postalcode: 邮编|string
|
||||||
|
company_add: 地址|string
|
||||||
|
company_status: 公司状态|string
|
||||||
|
company_level: 公司等级|string
|
||||||
|
company_owner_openid: 公司所有者 openid|string
|
||||||
|
company_remark: 备注|string
|
||||||
|
PocketBaseCompanyListResponse:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- page
|
||||||
|
- perPage
|
||||||
|
- totalItems
|
||||||
|
- totalPages
|
||||||
|
- items
|
||||||
|
properties:
|
||||||
|
page:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 页码 | integer
|
||||||
|
perPage:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 每页条数 | integer
|
||||||
|
totalItems:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 总记录数 | integer
|
||||||
|
totalPages:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 总页数 | integer
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: #/components/schemas/PocketBaseCompanyRecord
|
||||||
|
example:
|
||||||
|
page: page|integer
|
||||||
|
perPage: perPage|integer
|
||||||
|
totalItems: totalItems|integer
|
||||||
|
totalPages: totalPages|integer
|
||||||
|
items:
|
||||||
|
- id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
company_id: 公司业务 id,由数据库自动生成|string
|
||||||
|
company_name: 公司名称|string
|
||||||
|
company_type: 公司类型|string
|
||||||
|
company_entity: 公司法人|string
|
||||||
|
company_usci: 统一社会信用代码|string
|
||||||
|
company_nationality: 国家名称|string
|
||||||
|
company_nationality_code: 国家编码|string
|
||||||
|
company_province: 省份名称|string
|
||||||
|
company_province_code: 省份编码|string
|
||||||
|
company_city: 城市名称|string
|
||||||
|
company_city_code: 城市编码|string
|
||||||
|
company_district: 区/县名称|string
|
||||||
|
company_district_code: 区/县编码|string
|
||||||
|
company_postalcode: 邮政编码|string
|
||||||
|
company_add: 公司地址|string
|
||||||
|
company_status: 公司状态|string
|
||||||
|
company_level: 公司等级|string
|
||||||
|
company_owner_openid: 公司所有者 openid|string
|
||||||
|
company_remark: 备注|string
|
||||||
480
pocket-base/spec/openapi-wx/documents.yaml
Normal file
480
pocket-base/spec/openapi-wx/documents.yaml
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: BAI PocketBase Native API - Documents
|
||||||
|
version: 1.0.0
|
||||||
|
description: |
|
||||||
|
PocketBase 原生 `tbl_document` records API 文档。
|
||||||
|
本文件可单独导入使用,不依赖其他 YAML。
|
||||||
|
servers:
|
||||||
|
- url: https://bai-api.blv-oa.com
|
||||||
|
description: 生产环境
|
||||||
|
- url: http://localhost:8090
|
||||||
|
description: PocketBase 本地环境
|
||||||
|
paths:
|
||||||
|
/pb/api/collections/tbl_document/records:
|
||||||
|
get:
|
||||||
|
operationId: getPocketBaseDocumentRecords
|
||||||
|
tags:
|
||||||
|
- 文档信息
|
||||||
|
summary: 分页查询文档列表 / 按 system_dict_id 与 enum 双条件分页筛选文档
|
||||||
|
description: |
|
||||||
|
使用 PocketBase 原生 records list 接口查询 `tbl_document`。
|
||||||
|
|
||||||
|
当前线上权限规则:
|
||||||
|
- `listRule = is_delete = 0`,因此任何客户端都可直接分页查询未软删除文档
|
||||||
|
- `viewRule = is_delete = 0`,因此单条详情也只可读取未软删除文档
|
||||||
|
- `create/update/delete` 仍仅管理员或管理后台用户允许
|
||||||
|
|
||||||
|
`document_type` 的存储格式为:
|
||||||
|
- `system_dict_id@dict_word_enum|system_dict_id@dict_word_enum`
|
||||||
|
|
||||||
|
业务上这里是两个独立条件,并且查询时两个条件都要满足:
|
||||||
|
- 条件 1:包含某个 `system_dict_id`
|
||||||
|
- 条件 2:包含某个 `enum`
|
||||||
|
|
||||||
|
PocketBase 原生标准接口实际只有一个 `filter` 参数,因此应在同一个 `filter` 中写成两个 `contains` 条件,例如:
|
||||||
|
- `system_dict_id = DICT-1774599144591-hAEFQj`
|
||||||
|
- `enum = UT1`
|
||||||
|
- 最终:`document_type ~ "DICT-1774599144591-hAEFQj" && document_type ~ "@UT1"`
|
||||||
|
|
||||||
|
这条写法已经按线上真实数据验证通过。
|
||||||
|
|
||||||
|
排序说明:
|
||||||
|
- 当前线上统一按 `document_create` 排序
|
||||||
|
- 若要“最新上传的排在最前面”,请传 `sort=-document_create`
|
||||||
|
|
||||||
|
标准调用方式有两种:
|
||||||
|
1. 查询整个文档列表:
|
||||||
|
- 不传 `filter`
|
||||||
|
- 按需传 `page`、`perPage`
|
||||||
|
- 若要按最新上传倒序,传 `sort=-document_create`
|
||||||
|
2. 根据 `system_dict_id` 和 `enum` 两个业务条件分页筛选:
|
||||||
|
- 直接传 `filter=document_type ~ "<system_dict_id>" && document_type ~ "@<enum>"`
|
||||||
|
- 传 `page`、`perPage`
|
||||||
|
- 若要按最新上传倒序,传 `sort=-document_create`
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ statusCode, errMsg, data }` 包装
|
||||||
|
- 如果需要更复杂的条件组合,可继续使用 PocketBase 原生 `filter` 语法自行扩展
|
||||||
|
parameters:
|
||||||
|
- name: filter
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
PocketBase 标准过滤表达式。
|
||||||
|
|
||||||
|
- 查全部列表时:不传
|
||||||
|
- 按业务条件筛选时,同时写两个 `contains` 条件
|
||||||
|
- 第二个条件建议带上 `@` 前缀,避免误命中
|
||||||
|
- 例如:`document_type ~ "DICT-1774599144591-hAEFQj" && document_type ~ "@UT1"`
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: 过滤表达式|string
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: 页码
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 1
|
||||||
|
- name: perPage
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: 每页条数
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 20
|
||||||
|
- name: sort
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
PocketBase 原生排序表达式。
|
||||||
|
|
||||||
|
当前线上建议使用:
|
||||||
|
- `-document_create`:按最新上传倒序返回
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: 排序表达式|string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 查询成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseDocumentListResponse
|
||||||
|
example:
|
||||||
|
page: page|integer
|
||||||
|
perPage: perPage|integer
|
||||||
|
totalItems: totalItems|integer
|
||||||
|
totalPages: totalPages|integer
|
||||||
|
items:
|
||||||
|
- id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
document_id: 文档业务 ID|string
|
||||||
|
document_create: 文档创建时间,由数据库自动生成|string
|
||||||
|
document_effect_date: 文档生效日期|string
|
||||||
|
document_expiry_date: 文档到期日期|string
|
||||||
|
document_title: 文档标题|string
|
||||||
|
document_type: 文档类型,多选时按 system_dict_id@dict_word_enum|... 保存|string
|
||||||
|
document_subtitle: 文档副标题|string
|
||||||
|
document_summary: 文档摘要|string
|
||||||
|
document_content: 正文内容,保存 Markdown|string
|
||||||
|
document_image: 图片附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_video: 视频附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_file: 文件附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_status: 文档状态,仅 有效 / 过期|string
|
||||||
|
document_owner: 上传者 openid|string
|
||||||
|
document_relation_model: 关联机型 / 模型标识|string
|
||||||
|
document_keywords: 关键词,多选后以 | 分隔|string
|
||||||
|
document_share_count: 分享次数|number
|
||||||
|
document_download_count: 下载次数|number
|
||||||
|
document_favorite_count: 收藏次数|number
|
||||||
|
document_embedding_status: 文档嵌入状态|string
|
||||||
|
document_embedding_error: 文档嵌入错误原因|string
|
||||||
|
document_embedding_lasttime: 最后一次嵌入更新时间|string
|
||||||
|
document_vector_version: 向量版本号 / 模型名称|string
|
||||||
|
document_product_categories: 产品关联文档,多选后以 | 分隔|string
|
||||||
|
document_application_scenarios: 筛选依据,多选后以 | 分隔|string
|
||||||
|
document_hotel_type: 适用场景,多选后以 | 分隔|string
|
||||||
|
document_remark: 备注|string
|
||||||
|
"400":
|
||||||
|
description: 查询参数错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"403":
|
||||||
|
description: 集合规则被锁定或服务端权限设置异常
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"500":
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
PocketBaseNativeError:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
description: 业务状态码
|
||||||
|
example: 错误状态码 | integer
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: PocketBase原生错误信息 | string
|
||||||
|
data:
|
||||||
|
description: 业务响应数据
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
PocketBaseRecordBase:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- collectionId
|
||||||
|
- collectionName
|
||||||
|
- created
|
||||||
|
- updated
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: PocketBase 记录主键
|
||||||
|
example: PocketBase记录主键 | string
|
||||||
|
collectionId:
|
||||||
|
type: string
|
||||||
|
example: 集合ID | string
|
||||||
|
collectionName:
|
||||||
|
type: string
|
||||||
|
example: 集合名称 | string
|
||||||
|
created:
|
||||||
|
type: string
|
||||||
|
description: 记录创建时间
|
||||||
|
example: 记录创建时间 | string
|
||||||
|
updated:
|
||||||
|
type: string
|
||||||
|
description: 记录更新时间
|
||||||
|
example: 记录更新时间 | string
|
||||||
|
example:
|
||||||
|
id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
PocketBaseDocumentFields:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
document_id:
|
||||||
|
type: string
|
||||||
|
description: 文档业务 ID
|
||||||
|
example: 文档业务ID | string
|
||||||
|
document_create:
|
||||||
|
type: string
|
||||||
|
description: 文档创建时间,由数据库自动生成
|
||||||
|
example: 文档创建时间,由数据库自动生成 | string
|
||||||
|
document_effect_date:
|
||||||
|
type: string
|
||||||
|
description: 文档生效日期
|
||||||
|
example: 文档生效日期 | string
|
||||||
|
document_expiry_date:
|
||||||
|
type: string
|
||||||
|
description: 文档到期日期
|
||||||
|
example: 文档到期日期 | string
|
||||||
|
document_title:
|
||||||
|
type: string
|
||||||
|
description: 文档标题
|
||||||
|
example: 文档标题 | string
|
||||||
|
document_type:
|
||||||
|
type: string
|
||||||
|
description: 文档类型,多选时按 system_dict_id@dict_word_enum|... 保存
|
||||||
|
example: 文档类型,按system_dict_id@dict_word_enum保存 | string
|
||||||
|
document_subtitle:
|
||||||
|
type: string
|
||||||
|
description: 文档副标题
|
||||||
|
example: 文档副标题 | string
|
||||||
|
document_summary:
|
||||||
|
type: string
|
||||||
|
description: 文档摘要
|
||||||
|
example: 文档摘要 | string
|
||||||
|
document_content:
|
||||||
|
type: string
|
||||||
|
description: 正文内容,保存 Markdown
|
||||||
|
example: 正文内容 | string
|
||||||
|
document_image:
|
||||||
|
type: string
|
||||||
|
description: 图片附件 ID 集合,底层以 | 分隔
|
||||||
|
example: 图片附件ID串,底层按|分隔 | string
|
||||||
|
document_video:
|
||||||
|
type: string
|
||||||
|
description: 视频附件 ID 集合,底层以 | 分隔
|
||||||
|
example: 视频附件ID串,底层按|分隔 | string
|
||||||
|
document_file:
|
||||||
|
type: string
|
||||||
|
description: 文件附件 ID 集合,底层以 | 分隔
|
||||||
|
example: 文件附件ID串,底层按|分隔 | string
|
||||||
|
document_status:
|
||||||
|
type: string
|
||||||
|
description: 文档状态,仅 `有效` / `过期`
|
||||||
|
example: 文档状态 | string
|
||||||
|
document_owner:
|
||||||
|
type: string
|
||||||
|
description: 上传者 openid
|
||||||
|
example: 上传者openid | string
|
||||||
|
document_relation_model:
|
||||||
|
type: string
|
||||||
|
description: 关联机型 / 模型标识
|
||||||
|
example: 关联机型标识 | string
|
||||||
|
document_keywords:
|
||||||
|
type: string
|
||||||
|
description: 关键词,多选后以 | 分隔
|
||||||
|
example: 关键词,多选按|分隔 | string
|
||||||
|
document_share_count:
|
||||||
|
type: number
|
||||||
|
description: 分享次数
|
||||||
|
example: 分享次数|number
|
||||||
|
document_download_count:
|
||||||
|
type: number
|
||||||
|
description: 下载次数
|
||||||
|
example: 下载次数|number
|
||||||
|
document_favorite_count:
|
||||||
|
type: number
|
||||||
|
description: 收藏次数
|
||||||
|
example: 收藏次数|number
|
||||||
|
document_embedding_status:
|
||||||
|
type: string
|
||||||
|
description: 文档嵌入状态
|
||||||
|
example: 文档嵌入状态 | string
|
||||||
|
document_embedding_error:
|
||||||
|
type: string
|
||||||
|
description: 文档嵌入错误原因
|
||||||
|
example: 文档嵌入错误原因 | string
|
||||||
|
document_embedding_lasttime:
|
||||||
|
type: string
|
||||||
|
description: 最后一次嵌入更新时间
|
||||||
|
example: 最后一次嵌入更新时间 | string
|
||||||
|
document_vector_version:
|
||||||
|
type: string
|
||||||
|
description: 向量版本号 / 模型名称
|
||||||
|
example: 向量版本号或模型名称 | string
|
||||||
|
document_product_categories:
|
||||||
|
type: string
|
||||||
|
description: 产品关联文档,多选后以 | 分隔
|
||||||
|
example: 产品关联文档,多选按|分隔 | string
|
||||||
|
document_application_scenarios:
|
||||||
|
type: string
|
||||||
|
description: 筛选依据,多选后以 | 分隔
|
||||||
|
example: 筛选依据,多选按|分隔 | string
|
||||||
|
document_hotel_type:
|
||||||
|
type: string
|
||||||
|
description: 适用场景,多选后以 | 分隔
|
||||||
|
example: 适用场景,多选按|分隔 | string
|
||||||
|
document_remark:
|
||||||
|
type: string
|
||||||
|
description: 备注
|
||||||
|
example: 备注 | string
|
||||||
|
example:
|
||||||
|
document_id: 文档业务 ID|string
|
||||||
|
document_create: 文档创建时间,由数据库自动生成|string
|
||||||
|
document_effect_date: 文档生效日期|string
|
||||||
|
document_expiry_date: 文档到期日期|string
|
||||||
|
document_title: 文档标题|string
|
||||||
|
document_type: 文档类型,多选时按 system_dict_id@dict_word_enum|... 保存|string
|
||||||
|
document_subtitle: 文档副标题|string
|
||||||
|
document_summary: 文档摘要|string
|
||||||
|
document_content: 正文内容,保存 Markdown|string
|
||||||
|
document_image: 图片附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_video: 视频附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_file: 文件附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_status: 文档状态,仅 有效 / 过期|string
|
||||||
|
document_owner: 上传者 openid|string
|
||||||
|
document_relation_model: 关联机型 / 模型标识|string
|
||||||
|
document_keywords: 关键词,多选后以 | 分隔|string
|
||||||
|
document_share_count: 分享次数|number
|
||||||
|
document_download_count: 下载次数|number
|
||||||
|
document_favorite_count: 收藏次数|number
|
||||||
|
document_embedding_status: 文档嵌入状态|string
|
||||||
|
document_embedding_error: 文档嵌入错误原因|string
|
||||||
|
document_embedding_lasttime: 最后一次嵌入更新时间|string
|
||||||
|
document_vector_version: 向量版本号 / 模型名称|string
|
||||||
|
document_product_categories: 产品关联文档,多选后以 | 分隔|string
|
||||||
|
document_application_scenarios: 筛选依据,多选后以 | 分隔|string
|
||||||
|
document_hotel_type: 适用场景,多选后以 | 分隔|string
|
||||||
|
document_remark: 备注|string
|
||||||
|
PocketBaseDocumentRecord:
|
||||||
|
allOf:
|
||||||
|
- $ref: #/components/schemas/PocketBaseRecordBase
|
||||||
|
- $ref: #/components/schemas/PocketBaseDocumentFields
|
||||||
|
example:
|
||||||
|
id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
document_id: 文档业务 ID|string
|
||||||
|
document_create: 文档创建时间,由数据库自动生成|string
|
||||||
|
document_effect_date: 文档生效日期|string
|
||||||
|
document_expiry_date: 文档到期日期|string
|
||||||
|
document_title: 文档标题|string
|
||||||
|
document_type: 文档类型,多选时按 system_dict_id@dict_word_enum|... 保存|string
|
||||||
|
document_subtitle: 文档副标题|string
|
||||||
|
document_summary: 文档摘要|string
|
||||||
|
document_content: 正文内容,保存 Markdown|string
|
||||||
|
document_image: 图片附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_video: 视频附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_file: 文件附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_status: 文档状态,仅 有效 / 过期|string
|
||||||
|
document_owner: 上传者 openid|string
|
||||||
|
document_relation_model: 关联机型 / 模型标识|string
|
||||||
|
document_keywords: 关键词,多选后以 | 分隔|string
|
||||||
|
document_share_count: 分享次数|number
|
||||||
|
document_download_count: 下载次数|number
|
||||||
|
document_favorite_count: 收藏次数|number
|
||||||
|
document_embedding_status: 文档嵌入状态|string
|
||||||
|
document_embedding_error: 文档嵌入错误原因|string
|
||||||
|
document_embedding_lasttime: 最后一次嵌入更新时间|string
|
||||||
|
document_vector_version: 向量版本号 / 模型名称|string
|
||||||
|
document_product_categories: 产品关联文档,多选后以 | 分隔|string
|
||||||
|
document_application_scenarios: 筛选依据,多选后以 | 分隔|string
|
||||||
|
document_hotel_type: 适用场景,多选后以 | 分隔|string
|
||||||
|
document_remark: 备注|string
|
||||||
|
PocketBaseDocumentListResponse:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- page
|
||||||
|
- perPage
|
||||||
|
- totalItems
|
||||||
|
- totalPages
|
||||||
|
- items
|
||||||
|
properties:
|
||||||
|
page:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 页码 | integer
|
||||||
|
perPage:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 每页条数 | integer
|
||||||
|
totalItems:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 总记录数 | integer
|
||||||
|
totalPages:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: 总页数 | integer
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: #/components/schemas/PocketBaseDocumentRecord
|
||||||
|
example:
|
||||||
|
page: page|integer
|
||||||
|
perPage: perPage|integer
|
||||||
|
totalItems: totalItems|integer
|
||||||
|
totalPages: totalPages|integer
|
||||||
|
items:
|
||||||
|
- id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
document_id: 文档业务 ID|string
|
||||||
|
document_create: 文档创建时间,由数据库自动生成|string
|
||||||
|
document_effect_date: 文档生效日期|string
|
||||||
|
document_expiry_date: 文档到期日期|string
|
||||||
|
document_title: 文档标题|string
|
||||||
|
document_type: 文档类型,多选时按 system_dict_id@dict_word_enum|... 保存|string
|
||||||
|
document_subtitle: 文档副标题|string
|
||||||
|
document_summary: 文档摘要|string
|
||||||
|
document_content: 正文内容,保存 Markdown|string
|
||||||
|
document_image: 图片附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_video: 视频附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_file: 文件附件 ID 集合,底层以 | 分隔|string
|
||||||
|
document_status: 文档状态,仅 有效 / 过期|string
|
||||||
|
document_owner: 上传者 openid|string
|
||||||
|
document_relation_model: 关联机型 / 模型标识|string
|
||||||
|
document_keywords: 关键词,多选后以 | 分隔|string
|
||||||
|
document_share_count: 分享次数|number
|
||||||
|
document_download_count: 下载次数|number
|
||||||
|
document_favorite_count: 收藏次数|number
|
||||||
|
document_embedding_status: 文档嵌入状态|string
|
||||||
|
document_embedding_error: 文档嵌入错误原因|string
|
||||||
|
document_embedding_lasttime: 最后一次嵌入更新时间|string
|
||||||
|
document_vector_version: 向量版本号 / 模型名称|string
|
||||||
|
document_product_categories: 产品关联文档,多选后以 | 分隔|string
|
||||||
|
document_application_scenarios: 筛选依据,多选后以 | 分隔|string
|
||||||
|
document_hotel_type: 适用场景,多选后以 | 分隔|string
|
||||||
|
document_remark: 备注|string
|
||||||
40
pocket-base/spec/openapi-wx/openapi.yaml
Normal file
40
pocket-base/spec/openapi-wx/openapi.yaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: BAI PocketBase Native API
|
||||||
|
version: 1.0.0-wx-folder
|
||||||
|
description: |
|
||||||
|
本目录仅收敛 PocketBase 原生 API 文档,不包含自定义 hooks API。
|
||||||
|
|
||||||
|
本文件为目录索引,支持单文件独立导入,不依赖其他 YAML。
|
||||||
|
|
||||||
|
文档约定:
|
||||||
|
- 不单独配置鉴权组件;如接口需要登录,请直接在说明中关注 `Authorization: Bearer <token>`
|
||||||
|
- 示例字段值统一使用 `<字段说明>|<类型>` 风格
|
||||||
|
- 原生接口返回 PocketBase 标准 records 结构,不使用 hooks 统一响应信封
|
||||||
|
servers:
|
||||||
|
- url: https://bai-api.blv-oa.com
|
||||||
|
description: 生产环境
|
||||||
|
- url: http://127.0.0.1:8090
|
||||||
|
description: PocketBase 本地环境
|
||||||
|
tags:
|
||||||
|
- name: 企业信息
|
||||||
|
description: PocketBase 原生公司记录接口
|
||||||
|
- name: 附件信息
|
||||||
|
description: PocketBase 原生附件记录接口
|
||||||
|
- name: 产品信息
|
||||||
|
description: PocketBase 原生产品记录接口
|
||||||
|
- name: 文档信息
|
||||||
|
description: PocketBase 原生文档记录接口
|
||||||
|
- name: 购物车
|
||||||
|
description: PocketBase 原生购物车记录接口
|
||||||
|
- name: 订单
|
||||||
|
description: PocketBase 原生订单记录接口
|
||||||
|
paths: {}
|
||||||
|
x-index:
|
||||||
|
files:
|
||||||
|
- company.yaml
|
||||||
|
- attachments.yaml
|
||||||
|
- products.yaml
|
||||||
|
- documents.yaml
|
||||||
|
- cart.yaml
|
||||||
|
- order.yaml
|
||||||
392
pocket-base/spec/openapi-wx/order.yaml
Normal file
392
pocket-base/spec/openapi-wx/order.yaml
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: BAI PocketBase Native API - Order
|
||||||
|
version: 1.0.0
|
||||||
|
description: |
|
||||||
|
PocketBase 原生 `tbl_order` records API 文档。
|
||||||
|
本文件可单独导入使用,不依赖其他 YAML。
|
||||||
|
servers:
|
||||||
|
- url: https://bai-api.blv-oa.com
|
||||||
|
description: 生产环境
|
||||||
|
- url: http://localhost:8090
|
||||||
|
description: PocketBase 本地环境
|
||||||
|
tags:
|
||||||
|
- name: 订单
|
||||||
|
description: PocketBase 原生订单记录接口
|
||||||
|
paths:
|
||||||
|
/pb/api/collections/tbl_order/records:
|
||||||
|
get:
|
||||||
|
operationId: getPocketBaseOrderRecords
|
||||||
|
tags: [订单]
|
||||||
|
summary: 查询订单记录列表
|
||||||
|
parameters:
|
||||||
|
- name: filter
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: PocketBase 原生过滤表达式
|
||||||
|
example: order_id="订单业务ID|string"
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 1
|
||||||
|
- name: perPage
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 20
|
||||||
|
- name: sort
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: 排序表达式|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 查询成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseOrderListResponse'
|
||||||
|
'400':
|
||||||
|
description: 查询参数错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'403':
|
||||||
|
description: 无权访问
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
post:
|
||||||
|
operationId: postPocketBaseOrderRecord
|
||||||
|
tags: [订单]
|
||||||
|
summary: 创建订单记录
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseOrderCreateRequest'
|
||||||
|
example:
|
||||||
|
order_id: 订单业务ID|string
|
||||||
|
order_number: 订单编号|string
|
||||||
|
order_owner: 订单所有者openid|string
|
||||||
|
order_source: 订单来源|string
|
||||||
|
order_status: 订单状态|string
|
||||||
|
order_source_id: 来源关联业务ID|string
|
||||||
|
order_snap:
|
||||||
|
字段名|string: 字段值|string
|
||||||
|
order_amount: 订单金额|number
|
||||||
|
order_remark: 备注|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 创建成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseOrderRecord'
|
||||||
|
'400':
|
||||||
|
description: 参数错误或违反集合规则
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'403':
|
||||||
|
description: 无权访问
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
/pb/api/collections/tbl_order/records/{recordId}:
|
||||||
|
patch:
|
||||||
|
operationId: patchPocketBaseOrderRecordByRecordId
|
||||||
|
tags: [订单]
|
||||||
|
summary: 更新订单记录
|
||||||
|
parameters:
|
||||||
|
- name: recordId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: PocketBase记录主键|string
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseOrderUpdateRequest'
|
||||||
|
example:
|
||||||
|
order_number: 订单编号|string
|
||||||
|
order_owner: 订单所有者openid|string
|
||||||
|
order_source: 订单来源|string
|
||||||
|
order_status: 订单状态|string
|
||||||
|
order_source_id: 来源关联业务ID|string
|
||||||
|
order_snap:
|
||||||
|
字段名|string: 字段值|string
|
||||||
|
order_amount: 订单金额|number
|
||||||
|
order_remark: 备注|string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 更新成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseOrderRecord'
|
||||||
|
'400':
|
||||||
|
description: 参数错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'404':
|
||||||
|
description: 记录不存在
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
delete:
|
||||||
|
operationId: deletePocketBaseOrderRecordByRecordId
|
||||||
|
tags: [订单]
|
||||||
|
summary: 删除订单记录
|
||||||
|
parameters:
|
||||||
|
- name: recordId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: PocketBase记录主键|string
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: 删除成功
|
||||||
|
'401':
|
||||||
|
description: token 无效或已过期
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'404':
|
||||||
|
description: 记录不存在
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
'500':
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PocketBaseNativeError'
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
PocketBaseNativeError:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: [integer, string]
|
||||||
|
description: PocketBase错误码
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
description: PocketBase错误信息
|
||||||
|
data:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
description: PocketBase错误数据
|
||||||
|
example:
|
||||||
|
code: 错误状态码|integer
|
||||||
|
message: PocketBase原生错误信息|string
|
||||||
|
data:
|
||||||
|
字段名|string: 字段值|string
|
||||||
|
PocketBaseRecordBase:
|
||||||
|
type: object
|
||||||
|
required: [id, collectionId, collectionName, created, updated]
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: PocketBase记录主键
|
||||||
|
collectionId:
|
||||||
|
type: string
|
||||||
|
description: 集合ID
|
||||||
|
collectionName:
|
||||||
|
type: string
|
||||||
|
description: 集合名称
|
||||||
|
created:
|
||||||
|
type: string
|
||||||
|
description: 记录创建时间
|
||||||
|
updated:
|
||||||
|
type: string
|
||||||
|
description: 记录更新时间
|
||||||
|
example:
|
||||||
|
id: PocketBase记录主键|string
|
||||||
|
collectionId: 集合ID|string
|
||||||
|
collectionName: 集合名称|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
PocketBaseOrderFields:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
order_id:
|
||||||
|
type: string
|
||||||
|
description: 订单业务ID
|
||||||
|
order_number:
|
||||||
|
type: string
|
||||||
|
description: 订单编号
|
||||||
|
order_create:
|
||||||
|
type: string
|
||||||
|
description: 订单创建时间
|
||||||
|
order_owner:
|
||||||
|
type: string
|
||||||
|
description: 订单所有者openid
|
||||||
|
order_source:
|
||||||
|
type: string
|
||||||
|
description: 订单来源
|
||||||
|
order_status:
|
||||||
|
type: string
|
||||||
|
description: 订单状态
|
||||||
|
order_source_id:
|
||||||
|
type: string
|
||||||
|
description: 来源关联业务ID
|
||||||
|
order_snap:
|
||||||
|
oneOf:
|
||||||
|
- type: object
|
||||||
|
additionalProperties: true
|
||||||
|
- type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
description: 订单快照
|
||||||
|
order_amount:
|
||||||
|
type: [number, integer]
|
||||||
|
description: 订单金额
|
||||||
|
order_remark:
|
||||||
|
type: string
|
||||||
|
description: 备注
|
||||||
|
example:
|
||||||
|
order_id: 订单业务ID|string
|
||||||
|
order_number: 订单编号|string
|
||||||
|
order_create: 订单创建时间|string
|
||||||
|
order_owner: 订单所有者openid|string
|
||||||
|
order_source: 订单来源|string
|
||||||
|
order_status: 订单状态|string
|
||||||
|
order_source_id: 来源关联业务ID|string
|
||||||
|
order_snap:
|
||||||
|
字段名|string: 字段值|string
|
||||||
|
order_amount: 订单金额|number
|
||||||
|
order_remark: 备注|string
|
||||||
|
PocketBaseOrderRecord:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PocketBaseRecordBase'
|
||||||
|
- $ref: '#/components/schemas/PocketBaseOrderFields'
|
||||||
|
PocketBaseOrderCreateRequest:
|
||||||
|
type: object
|
||||||
|
required: [order_id, order_number, order_owner, order_source, order_status, order_source_id, order_snap, order_amount]
|
||||||
|
properties:
|
||||||
|
order_id:
|
||||||
|
type: string
|
||||||
|
order_number:
|
||||||
|
type: string
|
||||||
|
order_owner:
|
||||||
|
type: string
|
||||||
|
order_source:
|
||||||
|
type: string
|
||||||
|
order_status:
|
||||||
|
type: string
|
||||||
|
order_source_id:
|
||||||
|
type: string
|
||||||
|
order_snap:
|
||||||
|
oneOf:
|
||||||
|
- type: object
|
||||||
|
additionalProperties: true
|
||||||
|
- type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
order_amount:
|
||||||
|
type: [number, integer]
|
||||||
|
order_remark:
|
||||||
|
type: string
|
||||||
|
PocketBaseOrderUpdateRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
order_number:
|
||||||
|
type: string
|
||||||
|
order_owner:
|
||||||
|
type: string
|
||||||
|
order_source:
|
||||||
|
type: string
|
||||||
|
order_status:
|
||||||
|
type: string
|
||||||
|
order_source_id:
|
||||||
|
type: string
|
||||||
|
order_snap:
|
||||||
|
oneOf:
|
||||||
|
- type: object
|
||||||
|
additionalProperties: true
|
||||||
|
- type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
order_amount:
|
||||||
|
type: [number, integer]
|
||||||
|
order_remark:
|
||||||
|
type: string
|
||||||
|
PocketBaseOrderListResponse:
|
||||||
|
type: object
|
||||||
|
required: [page, perPage, totalItems, totalPages, items]
|
||||||
|
properties:
|
||||||
|
page:
|
||||||
|
type: [integer, string]
|
||||||
|
perPage:
|
||||||
|
type: [integer, string]
|
||||||
|
totalItems:
|
||||||
|
type: [integer, string]
|
||||||
|
totalPages:
|
||||||
|
type: [integer, string]
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/PocketBaseOrderRecord'
|
||||||
433
pocket-base/spec/openapi-wx/products.yaml
Normal file
433
pocket-base/spec/openapi-wx/products.yaml
Normal file
@@ -0,0 +1,433 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: BAI PocketBase Native API - Products
|
||||||
|
version: 1.0.0
|
||||||
|
description: |
|
||||||
|
PocketBase 原生 `tbl_product_list` records API 文档。
|
||||||
|
本文件可单独导入使用,不依赖其他 YAML。
|
||||||
|
servers:
|
||||||
|
- url: https://bai-api.blv-oa.com
|
||||||
|
description: 生产环境
|
||||||
|
- url: http://localhost:8090
|
||||||
|
description: PocketBase 本地环境
|
||||||
|
paths:
|
||||||
|
/pb/api/collections/tbl_product_list/records:
|
||||||
|
get:
|
||||||
|
operationId: getPocketBaseProductListRecords
|
||||||
|
tags:
|
||||||
|
- 产品信息
|
||||||
|
summary: 根据产品分类精确筛选并按分类排序值升序返回产品列表
|
||||||
|
description: |
|
||||||
|
使用 PocketBase 原生 records list 接口查询 `tbl_product_list`。
|
||||||
|
|
||||||
|
当前接口约定:
|
||||||
|
- 默认仅返回 `is_delete = 0` 的未软删除产品
|
||||||
|
- 条件:按 `prod_list_category` 精确匹配筛选
|
||||||
|
- 排序:按 `prod_list_sort` 从小到大排序
|
||||||
|
|
||||||
|
标准调用参数建议:
|
||||||
|
- `filter=prod_list_category="<产品分类>"`
|
||||||
|
- `sort=prod_list_sort`
|
||||||
|
|
||||||
|
注意:
|
||||||
|
- 这是 PocketBase 原生返回结构,不是 hooks 统一 `{ statusCode, errMsg, data }` 包装
|
||||||
|
- 若不传 `sort`,将由 PocketBase 默认排序策略决定返回顺序
|
||||||
|
parameters:
|
||||||
|
- name: filter
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: |
|
||||||
|
PocketBase 标准过滤表达式,当前要求按产品分类精确值筛选。
|
||||||
|
|
||||||
|
推荐写法:`prod_list_category="<产品分类>"`
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: 过滤表达式|string
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: 页码
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 1
|
||||||
|
- name: perPage
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: 每页条数
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 20
|
||||||
|
- name: sort
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: |
|
||||||
|
PocketBase 原生排序表达式。
|
||||||
|
|
||||||
|
当前要求使用:
|
||||||
|
- `prod_list_sort`:按分类排序值从小到大
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: 排序表达式|string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 查询成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseProductListListResponse
|
||||||
|
example:
|
||||||
|
page: page|integer
|
||||||
|
perPage: perPage|integer
|
||||||
|
totalItems: totalItems|integer
|
||||||
|
totalPages: totalPages|integer
|
||||||
|
items:
|
||||||
|
- id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
prod_list_id: 产品列表业务 ID,唯一标识|string
|
||||||
|
prod_list_name: 产品名称|string
|
||||||
|
prod_list_modelnumber: 产品型号|string
|
||||||
|
prod_list_barcode: 产品料号|string
|
||||||
|
prod_list_icon: 产品图标附件 ID,保存 tbl_attachments.attachments_id|string
|
||||||
|
prod_list_description: 产品说明|string
|
||||||
|
prod_list_feature: 产品特色|string
|
||||||
|
prod_list_parameters:
|
||||||
|
- name: name|string
|
||||||
|
value: value|string
|
||||||
|
prod_list_plantype: 产品方案|string
|
||||||
|
prod_list_category: 产品分类(必填,单选)|string
|
||||||
|
prod_list_sort: 排序值(同分类内按升序)|number
|
||||||
|
prod_list_comm_type: 通讯类型|string
|
||||||
|
prod_list_series: 产品系列|string
|
||||||
|
prod_list_power_supply: 供电方式|string
|
||||||
|
prod_list_tags: 产品标签(辅助检索,以 | 聚合)|string
|
||||||
|
prod_list_status: 产品状态(有效 / 过期 / 主推等)|string
|
||||||
|
prod_list_basic_price: 基础价格|number
|
||||||
|
prod_list_vip_price:
|
||||||
|
- viplevel: viplevel|string
|
||||||
|
price: price|number
|
||||||
|
prod_list_remark: 备注|string
|
||||||
|
"400":
|
||||||
|
description: 查询参数错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"403":
|
||||||
|
description: 集合规则被锁定或服务端权限设置异常
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
"500":
|
||||||
|
description: 服务端错误
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: #/components/schemas/PocketBaseNativeError
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
PocketBaseNativeError:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
description: 业务状态码
|
||||||
|
example: 错误状态码 | integer
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: PocketBase原生错误信息 | string
|
||||||
|
data:
|
||||||
|
description: 业务响应数据
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
example:
|
||||||
|
code: 业务状态码|integer
|
||||||
|
message: message|string
|
||||||
|
data:
|
||||||
|
业务响应数据字段|string: 业务响应数据值|string
|
||||||
|
PocketBaseRecordBase:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- collectionId
|
||||||
|
- collectionName
|
||||||
|
- created
|
||||||
|
- updated
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: PocketBase 记录主键
|
||||||
|
example: PocketBase记录主键 | string
|
||||||
|
collectionId:
|
||||||
|
type: string
|
||||||
|
example: 集合ID | string
|
||||||
|
collectionName:
|
||||||
|
type: string
|
||||||
|
example: 集合名称 | string
|
||||||
|
created:
|
||||||
|
type: string
|
||||||
|
description: 记录创建时间
|
||||||
|
example: 记录创建时间 | string
|
||||||
|
updated:
|
||||||
|
type: string
|
||||||
|
description: 记录更新时间
|
||||||
|
example: 记录更新时间 | string
|
||||||
|
example:
|
||||||
|
id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
PocketBaseProductListFields:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
prod_list_id:
|
||||||
|
type: string
|
||||||
|
description: 产品列表业务 ID,唯一标识
|
||||||
|
example: <产品列表业务ID>|<string>
|
||||||
|
prod_list_name:
|
||||||
|
type: string
|
||||||
|
description: 产品名称
|
||||||
|
example: <产品名称>|<string>
|
||||||
|
prod_list_modelnumber:
|
||||||
|
type: string
|
||||||
|
description: 产品型号
|
||||||
|
example: <产品型号>|<string>
|
||||||
|
prod_list_barcode:
|
||||||
|
type: string
|
||||||
|
description: 产品料号
|
||||||
|
example: <产品料号>|<string>
|
||||||
|
prod_list_icon:
|
||||||
|
type: string
|
||||||
|
description: 产品图标附件 ID,保存 `tbl_attachments.attachments_id`
|
||||||
|
example: <产品图标附件ID>|<string>
|
||||||
|
prod_list_description:
|
||||||
|
type: string
|
||||||
|
description: 产品说明
|
||||||
|
example: <产品说明>|<string>
|
||||||
|
prod_list_feature:
|
||||||
|
type: string
|
||||||
|
description: 产品特色
|
||||||
|
example: <产品特色>|<string>
|
||||||
|
prod_list_parameters:
|
||||||
|
type: array
|
||||||
|
description: 产品参数数组,每项包含 name/value
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: <属性名>|<string>
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
example: <属性值>|<string>
|
||||||
|
example:
|
||||||
|
- name: <属性名>|<string>
|
||||||
|
value: <属性值>|<string>
|
||||||
|
prod_list_plantype:
|
||||||
|
type: string
|
||||||
|
description: 产品方案
|
||||||
|
example: <产品方案>|<string>
|
||||||
|
prod_list_category:
|
||||||
|
type: string
|
||||||
|
description: 产品分类(必填,单选)
|
||||||
|
example: <产品分类>|<string>
|
||||||
|
prod_list_sort:
|
||||||
|
type:
|
||||||
|
- number
|
||||||
|
- integer
|
||||||
|
description: 排序值(同分类内按升序)
|
||||||
|
example: 排序值|number
|
||||||
|
prod_list_comm_type:
|
||||||
|
type: string
|
||||||
|
description: 通讯类型
|
||||||
|
example: <通讯类型>|<string>
|
||||||
|
prod_list_series:
|
||||||
|
type: string
|
||||||
|
description: 产品系列
|
||||||
|
example: <产品系列>|<string>
|
||||||
|
prod_list_power_supply:
|
||||||
|
type: string
|
||||||
|
description: 供电方式
|
||||||
|
example: <供电方式>|<string>
|
||||||
|
prod_list_tags:
|
||||||
|
type: string
|
||||||
|
description: 产品标签(辅助检索,以 `|` 聚合)
|
||||||
|
example: <产品标签>|<string>
|
||||||
|
prod_list_status:
|
||||||
|
type: string
|
||||||
|
description: 产品状态(有效 / 过期 / 主推等)
|
||||||
|
example: <产品状态>|<string>
|
||||||
|
prod_list_basic_price:
|
||||||
|
type:
|
||||||
|
- number
|
||||||
|
- integer
|
||||||
|
description: 基础价格
|
||||||
|
example: 基础价格|number
|
||||||
|
prod_list_vip_price:
|
||||||
|
type: array
|
||||||
|
description: 会员价数组,每项包含会员等级枚举值与价格
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
viplevel:
|
||||||
|
type: string
|
||||||
|
example: <会员等级枚举值>|<string>
|
||||||
|
price:
|
||||||
|
type:
|
||||||
|
- number
|
||||||
|
- integer
|
||||||
|
example: 会员价格|number
|
||||||
|
example:
|
||||||
|
- viplevel: 会员等级|string
|
||||||
|
price: 会员价格|number
|
||||||
|
prod_list_remark:
|
||||||
|
type: string
|
||||||
|
description: 备注
|
||||||
|
example: <备注>|<string>
|
||||||
|
example:
|
||||||
|
prod_list_id: 产品列表业务 ID,唯一标识|string
|
||||||
|
prod_list_name: 产品名称|string
|
||||||
|
prod_list_modelnumber: 产品型号|string
|
||||||
|
prod_list_barcode: 产品料号|string
|
||||||
|
prod_list_icon: 产品图标附件 ID,保存 tbl_attachments.attachments_id|string
|
||||||
|
prod_list_description: 产品说明|string
|
||||||
|
prod_list_feature: 产品特色|string
|
||||||
|
prod_list_parameters:
|
||||||
|
- name: name|string
|
||||||
|
value: value|string
|
||||||
|
prod_list_plantype: 产品方案|string
|
||||||
|
prod_list_category: 产品分类(必填,单选)|string
|
||||||
|
prod_list_sort: 排序值(同分类内按升序)|number
|
||||||
|
prod_list_comm_type: 通讯类型|string
|
||||||
|
prod_list_series: 产品系列|string
|
||||||
|
prod_list_power_supply: 供电方式|string
|
||||||
|
prod_list_tags: 产品标签(辅助检索,以 | 聚合)|string
|
||||||
|
prod_list_status: 产品状态(有效 / 过期 / 主推等)|string
|
||||||
|
prod_list_basic_price: 基础价格|number
|
||||||
|
prod_list_vip_price:
|
||||||
|
- viplevel: viplevel|string
|
||||||
|
price: price|number
|
||||||
|
prod_list_remark: 备注|string
|
||||||
|
PocketBaseProductListRecord:
|
||||||
|
allOf:
|
||||||
|
- $ref: #/components/schemas/PocketBaseRecordBase
|
||||||
|
- $ref: #/components/schemas/PocketBaseProductListFields
|
||||||
|
example:
|
||||||
|
id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
prod_list_id: 产品列表业务 ID,唯一标识|string
|
||||||
|
prod_list_name: 产品名称|string
|
||||||
|
prod_list_modelnumber: 产品型号|string
|
||||||
|
prod_list_barcode: 产品料号|string
|
||||||
|
prod_list_icon: 产品图标附件 ID,保存 tbl_attachments.attachments_id|string
|
||||||
|
prod_list_description: 产品说明|string
|
||||||
|
prod_list_feature: 产品特色|string
|
||||||
|
prod_list_parameters:
|
||||||
|
- name: name|string
|
||||||
|
value: value|string
|
||||||
|
prod_list_plantype: 产品方案|string
|
||||||
|
prod_list_category: 产品分类(必填,单选)|string
|
||||||
|
prod_list_sort: 排序值(同分类内按升序)|number
|
||||||
|
prod_list_comm_type: 通讯类型|string
|
||||||
|
prod_list_series: 产品系列|string
|
||||||
|
prod_list_power_supply: 供电方式|string
|
||||||
|
prod_list_tags: 产品标签(辅助检索,以 | 聚合)|string
|
||||||
|
prod_list_status: 产品状态(有效 / 过期 / 主推等)|string
|
||||||
|
prod_list_basic_price: 基础价格|number
|
||||||
|
prod_list_vip_price:
|
||||||
|
- viplevel: viplevel|string
|
||||||
|
price: price|number
|
||||||
|
prod_list_remark: 备注|string
|
||||||
|
PocketBaseProductListListResponse:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- page
|
||||||
|
- perPage
|
||||||
|
- totalItems
|
||||||
|
- totalPages
|
||||||
|
- items
|
||||||
|
properties:
|
||||||
|
page:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: <页码>|<integer>
|
||||||
|
perPage:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: <每页条数>|<integer>
|
||||||
|
totalItems:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: <总记录数>|<integer>
|
||||||
|
totalPages:
|
||||||
|
type:
|
||||||
|
- integer
|
||||||
|
- string
|
||||||
|
example: <总页数>|<integer>
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: #/components/schemas/PocketBaseProductListRecord
|
||||||
|
example:
|
||||||
|
page: page|integer
|
||||||
|
perPage: perPage|integer
|
||||||
|
totalItems: totalItems|integer
|
||||||
|
totalPages: totalPages|integer
|
||||||
|
items:
|
||||||
|
- id: PocketBase 记录主键|string
|
||||||
|
collectionId: collectionId|string
|
||||||
|
collectionName: collectionName|string
|
||||||
|
created: 记录创建时间|string
|
||||||
|
updated: 记录更新时间|string
|
||||||
|
prod_list_id: 产品列表业务 ID,唯一标识|string
|
||||||
|
prod_list_name: 产品名称|string
|
||||||
|
prod_list_modelnumber: 产品型号|string
|
||||||
|
prod_list_barcode: 产品料号|string
|
||||||
|
prod_list_icon: 产品图标附件 ID,保存 tbl_attachments.attachments_id|string
|
||||||
|
prod_list_description: 产品说明|string
|
||||||
|
prod_list_feature: 产品特色|string
|
||||||
|
prod_list_parameters:
|
||||||
|
- name: name|string
|
||||||
|
value: value|string
|
||||||
|
prod_list_plantype: 产品方案|string
|
||||||
|
prod_list_category: 产品分类(必填,单选)|string
|
||||||
|
prod_list_sort: 排序值(同分类内按升序)|number
|
||||||
|
prod_list_comm_type: 通讯类型|string
|
||||||
|
prod_list_series: 产品系列|string
|
||||||
|
prod_list_power_supply: 供电方式|string
|
||||||
|
prod_list_tags: 产品标签(辅助检索,以 | 聚合)|string
|
||||||
|
prod_list_status: 产品状态(有效 / 过期 / 主推等)|string
|
||||||
|
prod_list_basic_price: 基础价格|number
|
||||||
|
prod_list_vip_price:
|
||||||
|
- viplevel: viplevel|string
|
||||||
|
price: price|number
|
||||||
|
prod_list_remark: 备注|string
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -89,7 +89,9 @@ async function run() {
|
|||||||
const targetFieldType = 'json';
|
const targetFieldType = 'json';
|
||||||
const existingField = (collection.fields || []).find((field) => field.name === targetFieldName);
|
const existingField = (collection.fields || []).find((field) => field.name === targetFieldName);
|
||||||
|
|
||||||
if (existingField && existingField.type === targetFieldType) {
|
const hasBrokenCustomIdField = (collection.fields || []).some((field) => field && field.name === 'id');
|
||||||
|
|
||||||
|
if (existingField && existingField.type === targetFieldType && !hasBrokenCustomIdField) {
|
||||||
console.log('✅ 字段已存在且类型正确,无需变更。');
|
console.log('✅ 字段已存在且类型正确,无需变更。');
|
||||||
console.log('✅ 校验完成: tbl_product_list.prod_list_function (json)');
|
console.log('✅ 校验完成: tbl_product_list.prod_list_function (json)');
|
||||||
return;
|
return;
|
||||||
@@ -100,6 +102,10 @@ async function run() {
|
|||||||
|
|
||||||
for (let i = 0; i < (collection.fields || []).length; i += 1) {
|
for (let i = 0; i < (collection.fields || []).length; i += 1) {
|
||||||
const field = collection.fields[i];
|
const field = collection.fields[i];
|
||||||
|
if (field && field.name === 'id') {
|
||||||
|
// PocketBase system id is implicit; custom required id field will break record creation.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (field.name === targetFieldName) {
|
if (field.name === targetFieldName) {
|
||||||
nextFields.push(normalizeFieldPayload(field, { name: targetFieldName, type: targetFieldType }));
|
nextFields.push(normalizeFieldPayload(field, { name: targetFieldName, type: targetFieldType }));
|
||||||
patched = true;
|
patched = true;
|
||||||
|
|||||||
@@ -9,7 +9,12 @@
|
|||||||
"init:documents": "node pocketbase.documents.js",
|
"init:documents": "node pocketbase.documents.js",
|
||||||
"init:product-list": "node pocketbase.product-list.js",
|
"init:product-list": "node pocketbase.product-list.js",
|
||||||
"init:dictionary": "node pocketbase.dictionary.js",
|
"init:dictionary": "node pocketbase.dictionary.js",
|
||||||
|
"init:scheme": "node pocketbase.scheme.js",
|
||||||
"migrate:file-fields": "node pocketbase.file-fields-to-attachments.js",
|
"migrate:file-fields": "node pocketbase.file-fields-to-attachments.js",
|
||||||
|
"migrate:add-is-delete-field": "node pocketbase.add-is-delete-field.js",
|
||||||
|
"migrate:apply-soft-delete-rules": "node pocketbase.apply-soft-delete-rules.js",
|
||||||
|
"migrate:ensure-cart-order-autogen-id": "node pocketbase.ensure-cart-order-autogen-id.js",
|
||||||
|
"migrate:cart-active-unique-index": "node pocketbase.cart-active-unique-index.js",
|
||||||
"migrate:product-params-array": "node migrate-product-parameters-to-array.js",
|
"migrate:product-params-array": "node migrate-product-parameters-to-array.js",
|
||||||
"migrate:add-product-function-field": "node add-product-function-field.js",
|
"migrate:add-product-function-field": "node add-product-function-field.js",
|
||||||
"test:company-native-api": "node test-tbl-company-native-api.js",
|
"test:company-native-api": "node test-tbl-company-native-api.js",
|
||||||
|
|||||||
233
script/pocketbase.add-is-delete-field.js
Normal file
233
script/pocketbase.add-is-delete-field.js
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
import { createRequire } from 'module';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
let runtimeConfig = {};
|
||||||
|
try {
|
||||||
|
runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
|
||||||
|
} catch (_error) {
|
||||||
|
runtimeConfig = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readEnvFile(filePath) {
|
||||||
|
if (!fs.existsSync(filePath)) return {};
|
||||||
|
|
||||||
|
const content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
for (const rawLine of content.split(/\r?\n/)) {
|
||||||
|
const line = rawLine.trim();
|
||||||
|
if (!line || line.startsWith('#')) continue;
|
||||||
|
|
||||||
|
const index = line.indexOf('=');
|
||||||
|
if (index === -1) continue;
|
||||||
|
|
||||||
|
const key = line.slice(0, index).trim();
|
||||||
|
const value = line.slice(index + 1).trim();
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const backendEnv = readEnvFile(path.resolve(__dirname, '..', 'back-end', '.env'));
|
||||||
|
const PB_URL = String(
|
||||||
|
process.env.PB_URL
|
||||||
|
|| backendEnv.POCKETBASE_API_URL
|
||||||
|
|| runtimeConfig.POCKETBASE_API_URL
|
||||||
|
|| 'http://127.0.0.1:8090'
|
||||||
|
).replace(/\/+$/, '');
|
||||||
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
||||||
|
|
||||||
|
if (!AUTH_TOKEN) {
|
||||||
|
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行 is_delete 字段迁移。');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pb = new PocketBase(PB_URL);
|
||||||
|
const TARGET_FIELD = {
|
||||||
|
name: 'is_delete',
|
||||||
|
type: 'number',
|
||||||
|
required: false,
|
||||||
|
presentable: true,
|
||||||
|
hidden: false,
|
||||||
|
default: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 1,
|
||||||
|
onlyInt: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
function isSystemCollection(collection) {
|
||||||
|
return !!(collection && (collection.system || String(collection.name || '').startsWith('_')));
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFieldPayload(field, targetSpec) {
|
||||||
|
const payload = field ? Object.assign({}, field) : {};
|
||||||
|
const next = targetSpec || field || {};
|
||||||
|
|
||||||
|
if (field && field.id) {
|
||||||
|
payload.id = field.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.name = next.name;
|
||||||
|
payload.type = next.type;
|
||||||
|
|
||||||
|
if (typeof next.required !== 'undefined') {
|
||||||
|
payload.required = !!next.required;
|
||||||
|
}
|
||||||
|
if (typeof next.presentable !== 'undefined') {
|
||||||
|
payload.presentable = !!next.presentable;
|
||||||
|
}
|
||||||
|
if (typeof next.hidden !== 'undefined') {
|
||||||
|
payload.hidden = !!next.hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next.type === 'number') {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(next, 'default')) payload.default = next.default;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(next, 'min')) payload.min = next.min;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(next, 'max')) payload.max = next.max;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(next, 'onlyInt')) payload.onlyInt = !!next.onlyInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next.type === 'autodate') {
|
||||||
|
payload.onCreate = typeof next.onCreate === 'boolean' ? next.onCreate : (typeof field?.onCreate === 'boolean' ? field.onCreate : true);
|
||||||
|
payload.onUpdate = typeof next.onUpdate === 'boolean' ? next.onUpdate : (typeof field?.onUpdate === 'boolean' ? field.onUpdate : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next.type === 'file') {
|
||||||
|
payload.maxSelect = typeof next.maxSelect === 'number' ? next.maxSelect : (typeof field?.maxSelect === 'number' ? field.maxSelect : 0);
|
||||||
|
payload.maxSize = typeof next.maxSize === 'number' ? next.maxSize : (typeof field?.maxSize === 'number' ? field.maxSize : 0);
|
||||||
|
payload.mimeTypes = Array.isArray(next.mimeTypes)
|
||||||
|
? next.mimeTypes
|
||||||
|
: (Array.isArray(field?.mimeTypes) ? field.mimeTypes : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCollectionPayload(collection, fields) {
|
||||||
|
return {
|
||||||
|
name: collection.name,
|
||||||
|
type: collection.type,
|
||||||
|
listRule: collection.listRule,
|
||||||
|
viewRule: collection.viewRule,
|
||||||
|
createRule: collection.createRule,
|
||||||
|
updateRule: collection.updateRule,
|
||||||
|
deleteRule: collection.deleteRule,
|
||||||
|
fields,
|
||||||
|
indexes: collection.indexes || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addFieldToCollections() {
|
||||||
|
const collections = await pb.collections.getFullList({ sort: 'name' });
|
||||||
|
const changed = [];
|
||||||
|
|
||||||
|
for (const collection of collections) {
|
||||||
|
if (isSystemCollection(collection)) continue;
|
||||||
|
|
||||||
|
const currentFields = Array.isArray(collection.fields) ? collection.fields : [];
|
||||||
|
const existingField = currentFields.find((field) => field && field.name === TARGET_FIELD.name);
|
||||||
|
const nextFields = currentFields
|
||||||
|
.filter((field) => field && field.name !== 'id')
|
||||||
|
.map((field) => normalizeFieldPayload(field));
|
||||||
|
|
||||||
|
if (existingField) {
|
||||||
|
for (let i = 0; i < nextFields.length; i += 1) {
|
||||||
|
if (nextFields[i].name === TARGET_FIELD.name) {
|
||||||
|
nextFields[i] = normalizeFieldPayload(existingField, TARGET_FIELD);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextFields.push(normalizeFieldPayload(null, TARGET_FIELD));
|
||||||
|
}
|
||||||
|
|
||||||
|
await pb.collections.update(collection.id, buildCollectionPayload(collection, nextFields));
|
||||||
|
changed.push({ name: collection.name, action: existingField ? 'normalized' : 'added' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function backfillRecords() {
|
||||||
|
const collections = await pb.collections.getFullList({ sort: 'name' });
|
||||||
|
const summary = [];
|
||||||
|
|
||||||
|
for (const collection of collections) {
|
||||||
|
if (isSystemCollection(collection)) continue;
|
||||||
|
|
||||||
|
let page = 1;
|
||||||
|
let patched = 0;
|
||||||
|
const perPage = 200;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const list = await pb.collection(collection.name).getList(page, perPage, {
|
||||||
|
fields: 'id,is_delete',
|
||||||
|
skipTotal: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = Array.isArray(list.items) ? list.items : [];
|
||||||
|
for (const item of items) {
|
||||||
|
const value = item && Object.prototype.hasOwnProperty.call(item, 'is_delete') ? item.is_delete : null;
|
||||||
|
if (value === 0 || value === '0') continue;
|
||||||
|
|
||||||
|
await pb.collection(collection.name).update(item.id, { is_delete: 0 });
|
||||||
|
patched += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page >= list.totalPages) break;
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.push({ name: collection.name, backfilledRecords: patched });
|
||||||
|
}
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyCollections() {
|
||||||
|
const collections = await pb.collections.getFullList({ sort: 'name' });
|
||||||
|
const failed = [];
|
||||||
|
|
||||||
|
for (const collection of collections) {
|
||||||
|
if (isSystemCollection(collection)) continue;
|
||||||
|
const field = (collection.fields || []).find((item) => item && item.name === TARGET_FIELD.name);
|
||||||
|
if (!field || field.type !== 'number') failed.push(collection.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed.length) {
|
||||||
|
throw new Error('以下集合缺少 is_delete 字段或类型不正确: ' + failed.join(', '));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
console.log(`🔄 正在连接 PocketBase: ${PB_URL}`);
|
||||||
|
pb.authStore.save(AUTH_TOKEN, null);
|
||||||
|
console.log('✅ 已使用 POCKETBASE_AUTH_TOKEN 载入认证状态。');
|
||||||
|
|
||||||
|
const changed = await addFieldToCollections();
|
||||||
|
console.log('📝 已更新集合:');
|
||||||
|
console.log(JSON.stringify(changed, null, 2));
|
||||||
|
|
||||||
|
const backfilled = await backfillRecords();
|
||||||
|
console.log('📝 已回填记录:');
|
||||||
|
console.log(JSON.stringify(backfilled, null, 2));
|
||||||
|
|
||||||
|
await verifyCollections();
|
||||||
|
console.log('✅ 校验通过:所有业务集合已包含 is_delete(number, default=0) 字段。');
|
||||||
|
console.log('🎉 is_delete 字段迁移完成!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ is_delete 字段迁移失败:', error.response?.data || error.message || error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
176
script/pocketbase.apply-soft-delete-rules.js
Normal file
176
script/pocketbase.apply-soft-delete-rules.js
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import { createRequire } from 'module';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
let runtimeConfig = {};
|
||||||
|
try {
|
||||||
|
runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
|
||||||
|
} catch (_error) {
|
||||||
|
runtimeConfig = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readEnvFile(filePath) {
|
||||||
|
if (!fs.existsSync(filePath)) return {};
|
||||||
|
|
||||||
|
const content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
for (const rawLine of content.split(/\r?\n/)) {
|
||||||
|
const line = rawLine.trim();
|
||||||
|
if (!line || line.startsWith('#')) continue;
|
||||||
|
|
||||||
|
const index = line.indexOf('=');
|
||||||
|
if (index === -1) continue;
|
||||||
|
|
||||||
|
const key = line.slice(0, index).trim();
|
||||||
|
const value = line.slice(index + 1).trim();
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const backendEnv = readEnvFile(path.resolve(__dirname, '..', 'back-end', '.env'));
|
||||||
|
const PB_URL = String(
|
||||||
|
process.env.PB_URL
|
||||||
|
|| backendEnv.POCKETBASE_API_URL
|
||||||
|
|| runtimeConfig.POCKETBASE_API_URL
|
||||||
|
|| 'http://127.0.0.1:8090'
|
||||||
|
).replace(/\/+$/, '');
|
||||||
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
||||||
|
const SOFT_DELETE_RULE = 'is_delete = 0';
|
||||||
|
|
||||||
|
if (!AUTH_TOKEN) {
|
||||||
|
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行软删除规则迁移。');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pb = new PocketBase(PB_URL);
|
||||||
|
|
||||||
|
function isSystemCollection(collection) {
|
||||||
|
return !!(collection && (collection.system || String(collection.name || '').startsWith('_')));
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFieldPayload(field) {
|
||||||
|
const payload = Object.assign({}, field);
|
||||||
|
|
||||||
|
if (field.type === 'number') {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(field, 'default')) payload.default = field.default;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(field, 'min')) payload.min = field.min;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(field, 'max')) payload.max = field.max;
|
||||||
|
if (Object.prototype.hasOwnProperty.call(field, 'onlyInt')) payload.onlyInt = !!field.onlyInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.type === 'autodate') {
|
||||||
|
payload.onCreate = typeof field.onCreate === 'boolean' ? field.onCreate : true;
|
||||||
|
payload.onUpdate = typeof field.onUpdate === 'boolean' ? field.onUpdate : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.type === 'file') {
|
||||||
|
payload.maxSelect = typeof field.maxSelect === 'number' ? field.maxSelect : 0;
|
||||||
|
payload.maxSize = typeof field.maxSize === 'number' ? field.maxSize : 0;
|
||||||
|
payload.mimeTypes = Array.isArray(field.mimeTypes) ? field.mimeTypes : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRuleWithSoftDelete(rule) {
|
||||||
|
const currentRule = typeof rule === 'string' ? rule.trim() : '';
|
||||||
|
|
||||||
|
if (!currentRule) return SOFT_DELETE_RULE;
|
||||||
|
if (currentRule === SOFT_DELETE_RULE) return currentRule;
|
||||||
|
if (currentRule.includes(SOFT_DELETE_RULE)) return currentRule;
|
||||||
|
|
||||||
|
return `(${currentRule}) && ${SOFT_DELETE_RULE}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCollectionPayload(collection) {
|
||||||
|
return {
|
||||||
|
name: collection.name,
|
||||||
|
type: collection.type,
|
||||||
|
listRule: mergeRuleWithSoftDelete(collection.listRule),
|
||||||
|
viewRule: mergeRuleWithSoftDelete(collection.viewRule),
|
||||||
|
createRule: collection.createRule,
|
||||||
|
updateRule: collection.updateRule,
|
||||||
|
deleteRule: collection.deleteRule,
|
||||||
|
fields: (collection.fields || []).filter((field) => field && field.name !== 'id').map((field) => normalizeFieldPayload(field)),
|
||||||
|
indexes: collection.indexes || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyRules() {
|
||||||
|
const collections = await pb.collections.getFullList({ sort: 'name' });
|
||||||
|
const changed = [];
|
||||||
|
|
||||||
|
for (const collection of collections) {
|
||||||
|
if (isSystemCollection(collection)) continue;
|
||||||
|
|
||||||
|
const hasSoftDelete = (collection.fields || []).some((field) => field && field.name === 'is_delete');
|
||||||
|
if (!hasSoftDelete) continue;
|
||||||
|
|
||||||
|
const nextListRule = mergeRuleWithSoftDelete(collection.listRule);
|
||||||
|
const nextViewRule = mergeRuleWithSoftDelete(collection.viewRule);
|
||||||
|
const listChanged = nextListRule !== (collection.listRule || '');
|
||||||
|
const viewChanged = nextViewRule !== (collection.viewRule || '');
|
||||||
|
|
||||||
|
if (!listChanged && !viewChanged) {
|
||||||
|
changed.push({ name: collection.name, action: 'skipped', listRule: nextListRule, viewRule: nextViewRule });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await pb.collections.update(collection.id, buildCollectionPayload(collection));
|
||||||
|
changed.push({ name: collection.name, action: 'updated', listRule: nextListRule, viewRule: nextViewRule });
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyRules() {
|
||||||
|
const collections = await pb.collections.getFullList({ sort: 'name' });
|
||||||
|
const failed = [];
|
||||||
|
|
||||||
|
for (const collection of collections) {
|
||||||
|
if (isSystemCollection(collection)) continue;
|
||||||
|
const hasSoftDelete = (collection.fields || []).some((field) => field && field.name === 'is_delete');
|
||||||
|
if (!hasSoftDelete) continue;
|
||||||
|
|
||||||
|
const listRule = String(collection.listRule || '');
|
||||||
|
const viewRule = String(collection.viewRule || '');
|
||||||
|
|
||||||
|
if (!listRule.includes(SOFT_DELETE_RULE) || !viewRule.includes(SOFT_DELETE_RULE)) {
|
||||||
|
failed.push({ name: collection.name, listRule, viewRule });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed.length) {
|
||||||
|
throw new Error(`以下集合未正确应用软删除规则: ${JSON.stringify(failed, null, 2)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
console.log(`🔄 正在连接 PocketBase: ${PB_URL}`);
|
||||||
|
pb.authStore.save(AUTH_TOKEN, null);
|
||||||
|
console.log('✅ 已使用 POCKETBASE_AUTH_TOKEN 载入认证状态。');
|
||||||
|
|
||||||
|
const changed = await applyRules();
|
||||||
|
console.log('📝 规则更新结果:');
|
||||||
|
console.log(JSON.stringify(changed, null, 2));
|
||||||
|
|
||||||
|
await verifyRules();
|
||||||
|
console.log('✅ 校验通过:所有带 is_delete 的业务集合已默认过滤 is_delete = 0。');
|
||||||
|
console.log('🎉 软删除默认查询规则迁移完成!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 软删除默认查询规则迁移失败:', error.response?.data || error.message || error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
145
script/pocketbase.cart-active-unique-index.js
Normal file
145
script/pocketbase.cart-active-unique-index.js
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import { createRequire } from 'module';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
let runtimeConfig = {};
|
||||||
|
try {
|
||||||
|
runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
|
||||||
|
} catch (_error) {
|
||||||
|
runtimeConfig = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const PB_URL = String(process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
|
||||||
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
||||||
|
const COLLECTION = 'tbl_cart';
|
||||||
|
const ACTIVE_UNIQUE_INDEX = 'CREATE UNIQUE INDEX idx_tbl_cart_owner_product_active_unique ON tbl_cart (cart_owner, cart_product_id) WHERE is_delete = 0';
|
||||||
|
const TEMP_RULE = '@request.auth.id != ""';
|
||||||
|
|
||||||
|
if (!AUTH_TOKEN) {
|
||||||
|
console.error('Missing POCKETBASE_AUTH_TOKEN');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pb = new PocketBase(PB_URL);
|
||||||
|
|
||||||
|
function buildCollectionPayload(base, overrides = {}) {
|
||||||
|
const rawFields = Array.isArray(base.fields) ? base.fields : [];
|
||||||
|
const safeFields = rawFields.filter((field) => field && field.name !== 'id');
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: base.name,
|
||||||
|
type: base.type,
|
||||||
|
listRule: Object.prototype.hasOwnProperty.call(overrides, 'listRule') ? overrides.listRule : base.listRule,
|
||||||
|
viewRule: Object.prototype.hasOwnProperty.call(overrides, 'viewRule') ? overrides.viewRule : base.viewRule,
|
||||||
|
createRule: Object.prototype.hasOwnProperty.call(overrides, 'createRule') ? overrides.createRule : base.createRule,
|
||||||
|
updateRule: Object.prototype.hasOwnProperty.call(overrides, 'updateRule') ? overrides.updateRule : base.updateRule,
|
||||||
|
deleteRule: Object.prototype.hasOwnProperty.call(overrides, 'deleteRule') ? overrides.deleteRule : base.deleteRule,
|
||||||
|
fields: safeFields,
|
||||||
|
indexes: Object.prototype.hasOwnProperty.call(overrides, 'indexes') ? overrides.indexes : (base.indexes || []),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setTempRules(collection) {
|
||||||
|
const payload = buildCollectionPayload(collection, {
|
||||||
|
listRule: TEMP_RULE,
|
||||||
|
viewRule: TEMP_RULE,
|
||||||
|
createRule: TEMP_RULE,
|
||||||
|
updateRule: TEMP_RULE,
|
||||||
|
deleteRule: TEMP_RULE,
|
||||||
|
});
|
||||||
|
await pb.collections.update(collection.id, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreRules(collection) {
|
||||||
|
await pb.collections.update(collection.id, buildCollectionPayload(collection));
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupKey(record) {
|
||||||
|
const owner = String(record.cart_owner || '').trim();
|
||||||
|
const product = String(record.cart_product_id || '').trim();
|
||||||
|
return `${owner}||${product}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dedupeActiveRows() {
|
||||||
|
const rows = await pb.collection(COLLECTION).getFullList({
|
||||||
|
filter: 'is_delete = 0',
|
||||||
|
sort: '-updated',
|
||||||
|
fields: 'id,cart_owner,cart_product_id,is_delete,updated',
|
||||||
|
});
|
||||||
|
|
||||||
|
const groups = new Map();
|
||||||
|
for (const row of rows) {
|
||||||
|
const key = groupKey(row);
|
||||||
|
if (!groups.has(key)) groups.set(key, []);
|
||||||
|
groups.get(key).push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
let duplicateGroups = 0;
|
||||||
|
let softDeleted = 0;
|
||||||
|
|
||||||
|
for (const [, items] of groups) {
|
||||||
|
if (items.length <= 1) continue;
|
||||||
|
duplicateGroups += 1;
|
||||||
|
|
||||||
|
const keep = items[0];
|
||||||
|
for (let i = 1; i < items.length; i += 1) {
|
||||||
|
const row = items[i];
|
||||||
|
await pb.collection(COLLECTION).update(row.id, { is_delete: 1 });
|
||||||
|
softDeleted += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`dedupe group keep=${keep.id} deleted=${items.length - 1}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { total: rows.length, duplicateGroups, softDeleted };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyActiveUniqueIndex() {
|
||||||
|
const collection = await pb.collections.getOne(COLLECTION);
|
||||||
|
const indexes = Array.isArray(collection.indexes) ? collection.indexes.slice() : [];
|
||||||
|
if (!indexes.includes(ACTIVE_UNIQUE_INDEX)) {
|
||||||
|
indexes.push(ACTIVE_UNIQUE_INDEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = buildCollectionPayload(collection, { indexes });
|
||||||
|
await pb.collections.update(collection.id, payload);
|
||||||
|
|
||||||
|
const latest = await pb.collections.getOne(COLLECTION);
|
||||||
|
const ok = Array.isArray(latest.indexes) && latest.indexes.includes(ACTIVE_UNIQUE_INDEX);
|
||||||
|
if (!ok) {
|
||||||
|
throw new Error('Active unique index was not applied.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
pb.authStore.save(AUTH_TOKEN, null);
|
||||||
|
console.log(`connect ${PB_URL}`);
|
||||||
|
|
||||||
|
const original = await pb.collections.getOne(COLLECTION);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await setTempRules(original);
|
||||||
|
const dedupe = await dedupeActiveRows();
|
||||||
|
console.log(JSON.stringify(dedupe, null, 2));
|
||||||
|
} finally {
|
||||||
|
const latest = await pb.collections.getOne(COLLECTION);
|
||||||
|
const restoreBase = {
|
||||||
|
...latest,
|
||||||
|
listRule: original.listRule,
|
||||||
|
viewRule: original.viewRule,
|
||||||
|
createRule: original.createRule,
|
||||||
|
updateRule: original.updateRule,
|
||||||
|
deleteRule: original.deleteRule,
|
||||||
|
};
|
||||||
|
await restoreRules(restoreBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
await applyActiveUniqueIndex();
|
||||||
|
console.log('active unique index applied');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error('migration failed', error?.response || error?.message || error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
@@ -15,6 +15,7 @@ const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE
|
|||||||
const OWNER_AUTH_RULE = '@request.auth.id != ""';
|
const OWNER_AUTH_RULE = '@request.auth.id != ""';
|
||||||
const CART_OWNER_MATCH_RULE = 'cart_owner = @request.auth.openid';
|
const CART_OWNER_MATCH_RULE = 'cart_owner = @request.auth.openid';
|
||||||
const ORDER_OWNER_MATCH_RULE = 'order_owner = @request.auth.openid';
|
const ORDER_OWNER_MATCH_RULE = 'order_owner = @request.auth.openid';
|
||||||
|
const SOFT_DELETE_RULE = 'is_delete = 0';
|
||||||
|
|
||||||
if (!AUTH_TOKEN) {
|
if (!AUTH_TOKEN) {
|
||||||
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行建表。');
|
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行建表。');
|
||||||
@@ -23,28 +24,44 @@ if (!AUTH_TOKEN) {
|
|||||||
|
|
||||||
const pb = new PocketBase(PB_URL);
|
const pb = new PocketBase(PB_URL);
|
||||||
|
|
||||||
const collections = [
|
async function getCollectionIdByName(collectionName) {
|
||||||
|
const list = await pb.collections.getFullList({
|
||||||
|
sort: '-created',
|
||||||
|
});
|
||||||
|
const target = list.find((item) => item.name === collectionName);
|
||||||
|
if (!target) {
|
||||||
|
throw new Error(`未找到集合:${collectionName}`);
|
||||||
|
}
|
||||||
|
return target.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildCollections() {
|
||||||
|
const productCollectionId = await getCollectionIdByName('tbl_product_list');
|
||||||
|
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
name: 'tbl_cart',
|
name: 'tbl_cart',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
listRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
|
listRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE} && ${SOFT_DELETE_RULE}`,
|
||||||
viewRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
|
viewRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE} && ${SOFT_DELETE_RULE}`,
|
||||||
createRule: `${OWNER_AUTH_RULE} && @request.body.cart_owner = @request.auth.openid`,
|
createRule: '@request.body.cart_owner != ""',
|
||||||
updateRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
|
updateRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
|
||||||
deleteRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
|
deleteRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'cart_id', type: 'text', required: true },
|
{ name: 'cart_id', type: 'text', required: false, autogeneratePattern: 'CART-[0-9]{13}-[A-Za-z0-9]{6}' },
|
||||||
{ name: 'cart_number', type: 'text', required: true },
|
{ name: 'cart_number', type: 'text', required: false },
|
||||||
{ name: 'cart_create', type: 'autodate', onCreate: true, onUpdate: false },
|
{ name: 'cart_create', type: 'autodate', onCreate: true, onUpdate: false },
|
||||||
{ name: 'cart_owner', type: 'text', required: true },
|
{ name: 'cart_owner', type: 'text', required: true },
|
||||||
{ name: 'cart_product_id', type: 'text', required: true },
|
{ name: 'cart_product_id', type: 'relation', required: false, collectionId: productCollectionId, maxSelect: 1, cascadeDelete: false },
|
||||||
{ name: 'cart_product_quantity', type: 'number', required: true },
|
{ name: 'cart_product_quantity', type: 'number', required: false },
|
||||||
{ name: 'cart_status', type: 'text', required: true },
|
{ name: 'cart_status', type: 'text', required: false },
|
||||||
{ name: 'cart_at_price', type: 'number', required: true },
|
{ name: 'cart_at_price', type: 'number', required: false },
|
||||||
{ name: 'cart_remark', type: 'text' },
|
{ name: 'cart_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true },
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_tbl_cart_cart_id ON tbl_cart (cart_id)',
|
'CREATE UNIQUE INDEX idx_tbl_cart_cart_id ON tbl_cart (cart_id)',
|
||||||
|
'CREATE UNIQUE INDEX idx_tbl_cart_owner_product_active_unique ON tbl_cart (cart_owner, cart_product_id) WHERE is_delete = 0',
|
||||||
'CREATE INDEX idx_tbl_cart_cart_number ON tbl_cart (cart_number)',
|
'CREATE INDEX idx_tbl_cart_cart_number ON tbl_cart (cart_number)',
|
||||||
'CREATE INDEX idx_tbl_cart_cart_owner ON tbl_cart (cart_owner)',
|
'CREATE INDEX idx_tbl_cart_cart_owner ON tbl_cart (cart_owner)',
|
||||||
'CREATE INDEX idx_tbl_cart_cart_product_id ON tbl_cart (cart_product_id)',
|
'CREATE INDEX idx_tbl_cart_cart_product_id ON tbl_cart (cart_product_id)',
|
||||||
@@ -57,13 +74,13 @@ const collections = [
|
|||||||
{
|
{
|
||||||
name: 'tbl_order',
|
name: 'tbl_order',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
listRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE}`,
|
listRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE} && ${SOFT_DELETE_RULE}`,
|
||||||
viewRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE}`,
|
viewRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE} && ${SOFT_DELETE_RULE}`,
|
||||||
createRule: `${OWNER_AUTH_RULE} && @request.body.order_owner = @request.auth.openid`,
|
createRule: `${OWNER_AUTH_RULE} && @request.body.order_owner = @request.auth.openid`,
|
||||||
updateRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE}`,
|
updateRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE}`,
|
||||||
deleteRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE}`,
|
deleteRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE}`,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'order_id', type: 'text', required: true },
|
{ name: 'order_id', type: 'text', required: true, autogeneratePattern: 'ORDER-[0-9]{13}-[A-Za-z0-9]{6}' },
|
||||||
{ name: 'order_number', type: 'text', required: true },
|
{ name: 'order_number', type: 'text', required: true },
|
||||||
{ name: 'order_create', type: 'autodate', onCreate: true, onUpdate: false },
|
{ name: 'order_create', type: 'autodate', onCreate: true, onUpdate: false },
|
||||||
{ name: 'order_owner', type: 'text', required: true },
|
{ name: 'order_owner', type: 'text', required: true },
|
||||||
@@ -73,6 +90,7 @@ const collections = [
|
|||||||
{ name: 'order_snap', type: 'json', required: true },
|
{ name: 'order_snap', type: 'json', required: true },
|
||||||
{ name: 'order_amount', type: 'number', required: true },
|
{ name: 'order_amount', type: 'number', required: true },
|
||||||
{ name: 'order_remark', type: 'text' },
|
{ name: 'order_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true },
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_tbl_order_order_id ON tbl_order (order_id)',
|
'CREATE UNIQUE INDEX idx_tbl_order_order_id ON tbl_order (order_id)',
|
||||||
@@ -85,7 +103,8 @@ const collections = [
|
|||||||
'CREATE INDEX idx_tbl_order_owner_status ON tbl_order (order_owner, order_status)',
|
'CREATE INDEX idx_tbl_order_owner_status ON tbl_order (order_owner, order_status)',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeFieldPayload(field, existingField) {
|
function normalizeFieldPayload(field, existingField) {
|
||||||
const payload = existingField
|
const payload = existingField
|
||||||
@@ -111,6 +130,12 @@ function normalizeFieldPayload(field, existingField) {
|
|||||||
payload.onUpdate = typeof field.onUpdate === 'boolean' ? field.onUpdate : false;
|
payload.onUpdate = typeof field.onUpdate === 'boolean' ? field.onUpdate : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field.type === 'relation') {
|
||||||
|
payload.collectionId = field.collectionId;
|
||||||
|
payload.maxSelect = typeof field.maxSelect === 'number' ? field.maxSelect : 1;
|
||||||
|
payload.cascadeDelete = typeof field.cascadeDelete === 'boolean' ? field.cascadeDelete : false;
|
||||||
|
}
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +292,8 @@ async function init() {
|
|||||||
pb.authStore.save(AUTH_TOKEN, null);
|
pb.authStore.save(AUTH_TOKEN, null);
|
||||||
console.log('✅ 已使用 POCKETBASE_AUTH_TOKEN 载入认证状态。');
|
console.log('✅ 已使用 POCKETBASE_AUTH_TOKEN 载入认证状态。');
|
||||||
|
|
||||||
|
const collections = await buildCollections();
|
||||||
|
|
||||||
for (const collectionData of collections) {
|
for (const collectionData of collections) {
|
||||||
await createOrUpdateCollection(collectionData);
|
await createOrUpdateCollection(collectionData);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ try {
|
|||||||
|
|
||||||
const PB_URL = (process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
|
const PB_URL = (process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
|
||||||
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
||||||
|
const SOFT_DELETE_RULE = 'is_delete = 0';
|
||||||
|
|
||||||
if (!AUTH_TOKEN) {
|
if (!AUTH_TOKEN) {
|
||||||
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行字典建表。');
|
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行字典建表。');
|
||||||
@@ -23,8 +24,8 @@ const pb = new PocketBase(PB_URL);
|
|||||||
const collectionData = {
|
const collectionData = {
|
||||||
name: 'tbl_system_dict',
|
name: 'tbl_system_dict',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
listRule: '',
|
listRule: SOFT_DELETE_RULE,
|
||||||
viewRule: '',
|
viewRule: SOFT_DELETE_RULE,
|
||||||
createRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
createRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||||
updateRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
updateRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||||
deleteRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
deleteRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||||
@@ -38,6 +39,7 @@ const collectionData = {
|
|||||||
{ name: 'dict_word_sort_order', type: 'text' },
|
{ name: 'dict_word_sort_order', type: 'text' },
|
||||||
{ name: 'dict_word_parent_id', type: 'text' },
|
{ name: 'dict_word_parent_id', type: 'text' },
|
||||||
{ name: 'dict_word_remark', type: 'text' },
|
{ name: 'dict_word_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true },
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_system_dict_id ON tbl_system_dict (system_dict_id)',
|
'CREATE UNIQUE INDEX idx_system_dict_id ON tbl_system_dict (system_dict_id)',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ try {
|
|||||||
|
|
||||||
const PB_URL = (process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
|
const PB_URL = (process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
|
||||||
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
||||||
|
const SOFT_DELETE_RULE = 'is_delete = 0';
|
||||||
|
|
||||||
if (!AUTH_TOKEN) {
|
if (!AUTH_TOKEN) {
|
||||||
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行建表。');
|
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行建表。');
|
||||||
@@ -24,8 +25,8 @@ const collections = [
|
|||||||
{
|
{
|
||||||
name: 'tbl_attachments',
|
name: 'tbl_attachments',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
listRule: '',
|
listRule: SOFT_DELETE_RULE,
|
||||||
viewRule: '',
|
viewRule: SOFT_DELETE_RULE,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'attachments_id', type: 'text', required: true },
|
{ name: 'attachments_id', type: 'text', required: true },
|
||||||
{ name: 'attachments_link', type: 'file', maxSelect: 0, maxSize: 4294967296, mimeTypes: [] },
|
{ name: 'attachments_link', type: 'file', maxSelect: 0, maxSize: 4294967296, mimeTypes: [] },
|
||||||
@@ -37,6 +38,7 @@ const collections = [
|
|||||||
{ name: 'attachments_ocr', type: 'text' },
|
{ name: 'attachments_ocr', type: 'text' },
|
||||||
{ name: 'attachments_status', type: 'text' },
|
{ name: 'attachments_status', type: 'text' },
|
||||||
{ name: 'attachments_remark', type: 'text' },
|
{ name: 'attachments_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true },
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_tbl_attachments_attachments_id ON tbl_attachments (attachments_id)',
|
'CREATE UNIQUE INDEX idx_tbl_attachments_attachments_id ON tbl_attachments (attachments_id)',
|
||||||
@@ -47,8 +49,8 @@ const collections = [
|
|||||||
{
|
{
|
||||||
name: 'tbl_document',
|
name: 'tbl_document',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
listRule: '',
|
listRule: SOFT_DELETE_RULE,
|
||||||
viewRule: '',
|
viewRule: SOFT_DELETE_RULE,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'document_id', type: 'text', required: true },
|
{ name: 'document_id', type: 'text', required: true },
|
||||||
{ name: 'document_create', type: 'autodate', onCreate: true, onUpdate: false },
|
{ name: 'document_create', type: 'autodate', onCreate: true, onUpdate: false },
|
||||||
@@ -77,6 +79,7 @@ const collections = [
|
|||||||
{ name: 'document_application_scenarios', type: 'text' },
|
{ name: 'document_application_scenarios', type: 'text' },
|
||||||
{ name: 'document_hotel_type', type: 'text' },
|
{ name: 'document_hotel_type', type: 'text' },
|
||||||
{ name: 'document_remark', type: 'text' },
|
{ name: 'document_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true },
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_tbl_document_document_id ON tbl_document (document_id)',
|
'CREATE UNIQUE INDEX idx_tbl_document_document_id ON tbl_document (document_id)',
|
||||||
@@ -99,6 +102,7 @@ const collections = [
|
|||||||
{ name: 'doh_user_id', type: 'text' },
|
{ name: 'doh_user_id', type: 'text' },
|
||||||
{ name: 'doh_current_count', type: 'number' },
|
{ name: 'doh_current_count', type: 'number' },
|
||||||
{ name: 'doh_remark', type: 'text' },
|
{ name: 'doh_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true },
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_tbl_document_operation_history_doh_id ON tbl_document_operation_history (doh_id)',
|
'CREATE UNIQUE INDEX idx_tbl_document_operation_history_doh_id ON tbl_document_operation_history (doh_id)',
|
||||||
|
|||||||
200
script/pocketbase.ensure-cart-order-autogen-id.js
Normal file
200
script/pocketbase.ensure-cart-order-autogen-id.js
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import { createRequire } from 'module';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
let runtimeConfig = {};
|
||||||
|
try {
|
||||||
|
runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
|
||||||
|
} catch (_error) {
|
||||||
|
runtimeConfig = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readEnvFile(filePath) {
|
||||||
|
if (!fs.existsSync(filePath)) return {};
|
||||||
|
|
||||||
|
const content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
for (const rawLine of content.split(/\r?\n/)) {
|
||||||
|
const line = rawLine.trim();
|
||||||
|
if (!line || line.startsWith('#')) continue;
|
||||||
|
|
||||||
|
const index = line.indexOf('=');
|
||||||
|
if (index === -1) continue;
|
||||||
|
|
||||||
|
const key = line.slice(0, index).trim();
|
||||||
|
const value = line.slice(index + 1).trim();
|
||||||
|
result[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const backendEnv = readEnvFile(path.resolve(__dirname, '..', 'back-end', '.env'));
|
||||||
|
const PB_URL = String(
|
||||||
|
process.env.PB_URL
|
||||||
|
|| backendEnv.POCKETBASE_API_URL
|
||||||
|
|| runtimeConfig.POCKETBASE_API_URL
|
||||||
|
|| 'http://127.0.0.1:8090'
|
||||||
|
).replace(/\/+$/, '');
|
||||||
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
||||||
|
|
||||||
|
if (!AUTH_TOKEN) {
|
||||||
|
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行 cart/order 业务 ID 自动生成迁移。');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pb = new PocketBase(PB_URL);
|
||||||
|
|
||||||
|
const TARGETS = [
|
||||||
|
{
|
||||||
|
collectionName: 'tbl_cart',
|
||||||
|
fieldName: 'cart_id',
|
||||||
|
autogeneratePattern: 'CART-[0-9]{13}-[A-Za-z0-9]{6}',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
collectionName: 'tbl_order',
|
||||||
|
fieldName: 'order_id',
|
||||||
|
autogeneratePattern: 'ORDER-[0-9]{13}-[A-Za-z0-9]{6}',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function normalizeFieldPayload(field, overrides) {
|
||||||
|
const payload = Object.assign({}, field, overrides || {});
|
||||||
|
|
||||||
|
if (payload.type === 'number') {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(payload, 'onlyInt')) payload.onlyInt = !!payload.onlyInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.type === 'autodate') {
|
||||||
|
payload.onCreate = typeof payload.onCreate === 'boolean' ? payload.onCreate : true;
|
||||||
|
payload.onUpdate = typeof payload.onUpdate === 'boolean' ? payload.onUpdate : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.type === 'file') {
|
||||||
|
payload.maxSelect = typeof payload.maxSelect === 'number' ? payload.maxSelect : 0;
|
||||||
|
payload.maxSize = typeof payload.maxSize === 'number' ? payload.maxSize : 0;
|
||||||
|
payload.mimeTypes = Array.isArray(payload.mimeTypes) ? payload.mimeTypes : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCollectionPayload(collection, fields) {
|
||||||
|
return {
|
||||||
|
name: collection.name,
|
||||||
|
type: collection.type,
|
||||||
|
listRule: collection.listRule,
|
||||||
|
viewRule: collection.viewRule,
|
||||||
|
createRule: collection.createRule,
|
||||||
|
updateRule: collection.updateRule,
|
||||||
|
deleteRule: collection.deleteRule,
|
||||||
|
fields,
|
||||||
|
indexes: collection.indexes || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureAutoGenerateField(target) {
|
||||||
|
const collection = await pb.collections.getOne(target.collectionName);
|
||||||
|
const fields = Array.isArray(collection.fields) ? collection.fields : [];
|
||||||
|
const existingField = fields.find((field) => field && field.name === target.fieldName);
|
||||||
|
|
||||||
|
if (!existingField) {
|
||||||
|
throw new Error(`${target.collectionName}.${target.fieldName} 不存在`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPattern = String(existingField.autogeneratePattern || '');
|
||||||
|
if (existingField.type === 'text' && currentPattern === target.autogeneratePattern) {
|
||||||
|
return {
|
||||||
|
collectionName: target.collectionName,
|
||||||
|
fieldName: target.fieldName,
|
||||||
|
action: 'skipped',
|
||||||
|
autogeneratePattern: currentPattern,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextFields = fields
|
||||||
|
.filter((field) => field && field.name !== 'id')
|
||||||
|
.map((field) => {
|
||||||
|
if (field.name !== target.fieldName) {
|
||||||
|
return normalizeFieldPayload(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeFieldPayload(field, {
|
||||||
|
type: 'text',
|
||||||
|
required: typeof target.required === 'boolean' ? target.required : true,
|
||||||
|
autogeneratePattern: target.autogeneratePattern,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await pb.collections.update(collection.id, buildCollectionPayload(collection, nextFields));
|
||||||
|
|
||||||
|
return {
|
||||||
|
collectionName: target.collectionName,
|
||||||
|
fieldName: target.fieldName,
|
||||||
|
action: 'updated',
|
||||||
|
autogeneratePattern: target.autogeneratePattern,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyTargets() {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (const target of TARGETS) {
|
||||||
|
const collection = await pb.collections.getOne(target.collectionName);
|
||||||
|
const field = (collection.fields || []).find((item) => item && item.name === target.fieldName);
|
||||||
|
const pattern = String(field && field.autogeneratePattern || '');
|
||||||
|
const ok = !!field && field.type === 'text' && pattern === target.autogeneratePattern;
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
collectionName: target.collectionName,
|
||||||
|
fieldName: target.fieldName,
|
||||||
|
type: field ? field.type : '',
|
||||||
|
required: !!(field && field.required),
|
||||||
|
autogeneratePattern: pattern,
|
||||||
|
ok,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const failed = result.filter((item) => !item.ok);
|
||||||
|
if (failed.length) {
|
||||||
|
throw new Error(`以下字段未正确启用自动生成: ${JSON.stringify(failed, null, 2)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
console.log(`🔄 正在连接 PocketBase: ${PB_URL}`);
|
||||||
|
pb.authStore.save(AUTH_TOKEN, null);
|
||||||
|
console.log('✅ 已使用 POCKETBASE_AUTH_TOKEN 载入认证状态。');
|
||||||
|
|
||||||
|
const changed = [];
|
||||||
|
for (const target of TARGETS) {
|
||||||
|
changed.push(await ensureAutoGenerateField(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📝 业务 ID 自动生成处理结果:');
|
||||||
|
console.log(JSON.stringify(changed, null, 2));
|
||||||
|
|
||||||
|
const verified = await verifyTargets();
|
||||||
|
console.log('📝 校验结果:');
|
||||||
|
console.log(JSON.stringify(verified, null, 2));
|
||||||
|
|
||||||
|
console.log('🎉 cart_id / order_id 自动生成规则已就绪。');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ cart/order 业务 ID 自动生成迁移失败:', error.response?.data || error.message || error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -8,13 +8,14 @@ const ADMIN_PASSWORD = 'Momo123456';
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
|
|
||||||
const pb = new PocketBase(PB_URL);
|
const pb = new PocketBase(PB_URL);
|
||||||
|
const SOFT_DELETE_RULE = 'is_delete = 0';
|
||||||
|
|
||||||
const collections = [
|
const collections = [
|
||||||
{
|
{
|
||||||
name: 'tbl_system_dict',
|
name: 'tbl_system_dict',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
listRule: '',
|
listRule: SOFT_DELETE_RULE,
|
||||||
viewRule: '',
|
viewRule: SOFT_DELETE_RULE,
|
||||||
createRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
createRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||||
updateRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
updateRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||||
deleteRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
deleteRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||||
@@ -26,7 +27,8 @@ const collections = [
|
|||||||
{ name: 'dict_word_is_enabled', type: 'bool' },
|
{ name: 'dict_word_is_enabled', type: 'bool' },
|
||||||
{ name: 'dict_word_sort_order', type: 'text' },
|
{ name: 'dict_word_sort_order', type: 'text' },
|
||||||
{ name: 'dict_word_parent_id', type: 'text' },
|
{ name: 'dict_word_parent_id', type: 'text' },
|
||||||
{ name: 'dict_word_remark', type: 'text' }
|
{ name: 'dict_word_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true }
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_system_dict_id ON tbl_system_dict (system_dict_id)',
|
'CREATE UNIQUE INDEX idx_system_dict_id ON tbl_system_dict (system_dict_id)',
|
||||||
@@ -56,7 +58,8 @@ const collections = [
|
|||||||
{ name: 'company_status', type: 'text' },
|
{ name: 'company_status', type: 'text' },
|
||||||
{ name: 'company_level', type: 'text' },
|
{ name: 'company_level', type: 'text' },
|
||||||
{ name: 'company_owner_openid', type: 'text' },
|
{ name: 'company_owner_openid', type: 'text' },
|
||||||
{ name: 'company_remark', type: 'text' }
|
{ name: 'company_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true }
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_company_id ON tbl_company (company_id)',
|
'CREATE UNIQUE INDEX idx_company_id ON tbl_company (company_id)',
|
||||||
@@ -71,7 +74,8 @@ const collections = [
|
|||||||
{ name: 'usergroups_id', type: 'text', required: true },
|
{ name: 'usergroups_id', type: 'text', required: true },
|
||||||
{ name: 'usergroups_name', type: 'text' },
|
{ name: 'usergroups_name', type: 'text' },
|
||||||
{ name: 'usergroups_level', type: 'number' },
|
{ name: 'usergroups_level', type: 'number' },
|
||||||
{ name: 'usergroups_remark', type: 'text' }
|
{ name: 'usergroups_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true }
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_usergroups_id ON tbl_user_groups (usergroups_id)'
|
'CREATE UNIQUE INDEX idx_usergroups_id ON tbl_user_groups (usergroups_id)'
|
||||||
@@ -97,7 +101,8 @@ const collections = [
|
|||||||
{ name: 'users_id_pic_b', type: 'text' },
|
{ name: 'users_id_pic_b', type: 'text' },
|
||||||
{ name: 'users_title_picture', type: 'text' },
|
{ name: 'users_title_picture', type: 'text' },
|
||||||
{ name: 'users_picture', type: 'text' },
|
{ name: 'users_picture', type: 'text' },
|
||||||
{ name: 'usergroups_id', type: 'text' }
|
{ name: 'usergroups_id', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true }
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_users_id ON tbl_users (users_id)',
|
'CREATE UNIQUE INDEX idx_users_id ON tbl_users (users_id)',
|
||||||
|
|||||||
@@ -32,13 +32,14 @@ const ADMIN_PASSWORD = process.env.PB_ADMIN_PASSWORD || backendEnv.PB_ADMIN_PASS
|
|||||||
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || backendEnv.POCKETBASE_AUTH_TOKEN || '';
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || backendEnv.POCKETBASE_AUTH_TOKEN || '';
|
||||||
|
|
||||||
const pb = new PocketBase(PB_URL);
|
const pb = new PocketBase(PB_URL);
|
||||||
|
const SOFT_DELETE_RULE = 'is_delete = 0';
|
||||||
|
|
||||||
const collections = [
|
const collections = [
|
||||||
{
|
{
|
||||||
name: 'tbl_auth_users',
|
name: 'tbl_auth_users',
|
||||||
type: 'auth',
|
type: 'auth',
|
||||||
listRule: '@request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB"',
|
listRule: '@request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB" && is_delete = 0',
|
||||||
viewRule: '@request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB"',
|
viewRule: '@request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB" && is_delete = 0',
|
||||||
createRule: '',
|
createRule: '',
|
||||||
updateRule: '',
|
updateRule: '',
|
||||||
deleteRule: '@request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB"',
|
deleteRule: '@request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB"',
|
||||||
@@ -66,6 +67,7 @@ const collections = [
|
|||||||
{ name: 'users_id_pic_b', type: 'text' },
|
{ name: 'users_id_pic_b', type: 'text' },
|
||||||
{ name: 'users_title_picture', type: 'text' },
|
{ name: 'users_title_picture', type: 'text' },
|
||||||
{ name: 'users_picture', type: 'text' },
|
{ name: 'users_picture', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true },
|
||||||
{ name: 'usergroups_id', type: 'text' }
|
{ name: 'usergroups_id', type: 'text' }
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
@@ -86,11 +88,14 @@ const collections = [
|
|||||||
{
|
{
|
||||||
name: 'tbl_auth_resources',
|
name: 'tbl_auth_resources',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
|
listRule: SOFT_DELETE_RULE,
|
||||||
|
viewRule: SOFT_DELETE_RULE,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'res_id', type: 'text', required: true },
|
{ name: 'res_id', type: 'text', required: true },
|
||||||
{ name: 'table_name', type: 'text', required: true },
|
{ name: 'table_name', type: 'text', required: true },
|
||||||
{ name: 'column_name', type: 'text' },
|
{ name: 'column_name', type: 'text' },
|
||||||
{ name: 'res_type', type: 'text', required: true }
|
{ name: 'res_type', type: 'text', required: true },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true }
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_tbl_auth_resources_res_id ON tbl_auth_resources (res_id)',
|
'CREATE UNIQUE INDEX idx_tbl_auth_resources_res_id ON tbl_auth_resources (res_id)',
|
||||||
@@ -102,12 +107,15 @@ const collections = [
|
|||||||
{
|
{
|
||||||
name: 'tbl_auth_roles',
|
name: 'tbl_auth_roles',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
|
listRule: SOFT_DELETE_RULE,
|
||||||
|
viewRule: SOFT_DELETE_RULE,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'role_id', type: 'text', required: true },
|
{ name: 'role_id', type: 'text', required: true },
|
||||||
{ name: 'role_name', type: 'text', required: true },
|
{ name: 'role_name', type: 'text', required: true },
|
||||||
{ name: 'role_code', type: 'text' },
|
{ name: 'role_code', type: 'text' },
|
||||||
{ name: 'role_status', type: 'number' },
|
{ name: 'role_status', type: 'number' },
|
||||||
{ name: 'role_remark', type: 'text' }
|
{ name: 'role_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true }
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_tbl_auth_roles_role_id ON tbl_auth_roles (role_id)',
|
'CREATE UNIQUE INDEX idx_tbl_auth_roles_role_id ON tbl_auth_roles (role_id)',
|
||||||
@@ -118,12 +126,15 @@ const collections = [
|
|||||||
{
|
{
|
||||||
name: 'tbl_auth_role_perms',
|
name: 'tbl_auth_role_perms',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
|
listRule: SOFT_DELETE_RULE,
|
||||||
|
viewRule: SOFT_DELETE_RULE,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'role_perm_id', type: 'text', required: true },
|
{ name: 'role_perm_id', type: 'text', required: true },
|
||||||
{ name: 'role_id', type: 'text', required: true },
|
{ name: 'role_id', type: 'text', required: true },
|
||||||
{ name: 'res_id', type: 'text', required: true },
|
{ name: 'res_id', type: 'text', required: true },
|
||||||
{ name: 'access_level', type: 'number', required: true },
|
{ name: 'access_level', type: 'number', required: true },
|
||||||
{ name: 'priority', type: 'number' }
|
{ name: 'priority', type: 'number' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true }
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_tbl_auth_role_perms_role_perm_id ON tbl_auth_role_perms (role_perm_id)',
|
'CREATE UNIQUE INDEX idx_tbl_auth_role_perms_role_perm_id ON tbl_auth_role_perms (role_perm_id)',
|
||||||
@@ -135,12 +146,15 @@ const collections = [
|
|||||||
{
|
{
|
||||||
name: 'tbl_auth_user_overrides',
|
name: 'tbl_auth_user_overrides',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
|
listRule: SOFT_DELETE_RULE,
|
||||||
|
viewRule: SOFT_DELETE_RULE,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'override_id', type: 'text', required: true },
|
{ name: 'override_id', type: 'text', required: true },
|
||||||
{ name: 'users_convers_id', type: 'text', required: true },
|
{ name: 'users_convers_id', type: 'text', required: true },
|
||||||
{ name: 'res_id', type: 'text', required: true },
|
{ name: 'res_id', type: 'text', required: true },
|
||||||
{ name: 'access_level', type: 'number', required: true },
|
{ name: 'access_level', type: 'number', required: true },
|
||||||
{ name: 'priority', type: 'number' }
|
{ name: 'priority', type: 'number' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true }
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_tbl_auth_user_overrides_override_id ON tbl_auth_user_overrides (override_id)',
|
'CREATE UNIQUE INDEX idx_tbl_auth_user_overrides_override_id ON tbl_auth_user_overrides (override_id)',
|
||||||
@@ -152,12 +166,15 @@ const collections = [
|
|||||||
{
|
{
|
||||||
name: 'tbl_auth_row_scopes',
|
name: 'tbl_auth_row_scopes',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
|
listRule: SOFT_DELETE_RULE,
|
||||||
|
viewRule: SOFT_DELETE_RULE,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: 'scope_id', type: 'text', required: true },
|
{ name: 'scope_id', type: 'text', required: true },
|
||||||
{ name: 'target_type', type: 'text', required: true },
|
{ name: 'target_type', type: 'text', required: true },
|
||||||
{ name: 'target_id', type: 'text', required: true },
|
{ name: 'target_id', type: 'text', required: true },
|
||||||
{ name: 'table_name', type: 'text', required: true },
|
{ name: 'table_name', type: 'text', required: true },
|
||||||
{ name: 'filter_sql', type: 'editor', required: true }
|
{ name: 'filter_sql', type: 'editor', required: true },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true }
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_tbl_auth_row_scopes_scope_id ON tbl_auth_row_scopes (scope_id)',
|
'CREATE UNIQUE INDEX idx_tbl_auth_row_scopes_scope_id ON tbl_auth_row_scopes (scope_id)',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ try {
|
|||||||
|
|
||||||
const PB_URL = (process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
|
const PB_URL = (process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
|
||||||
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
||||||
|
const SOFT_DELETE_RULE = 'is_delete = 0';
|
||||||
|
|
||||||
if (!AUTH_TOKEN) {
|
if (!AUTH_TOKEN) {
|
||||||
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行建表。');
|
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行建表。');
|
||||||
@@ -25,8 +26,8 @@ const collections = [
|
|||||||
name: 'tbl_product_list',
|
name: 'tbl_product_list',
|
||||||
type: 'base',
|
type: 'base',
|
||||||
// Empty rules in PocketBase mean public read access.
|
// Empty rules in PocketBase mean public read access.
|
||||||
listRule: '',
|
listRule: SOFT_DELETE_RULE,
|
||||||
viewRule: '',
|
viewRule: SOFT_DELETE_RULE,
|
||||||
createRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
createRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||||
updateRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
updateRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||||
deleteRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
deleteRule: '(@request.auth.users_idtype = "ManagePlatform" || @request.auth.usergroups_id = "ROLE-1774666070666-9dDrTB")',
|
||||||
@@ -34,6 +35,7 @@ const collections = [
|
|||||||
{ name: 'prod_list_id', type: 'text', required: true },
|
{ name: 'prod_list_id', type: 'text', required: true },
|
||||||
{ name: 'prod_list_name', type: 'text', required: true },
|
{ name: 'prod_list_name', type: 'text', required: true },
|
||||||
{ name: 'prod_list_modelnumber', type: 'text' },
|
{ name: 'prod_list_modelnumber', type: 'text' },
|
||||||
|
{ name: 'prod_list_barcode', type: 'text' },
|
||||||
{ name: 'prod_list_icon', type: 'text' },
|
{ name: 'prod_list_icon', type: 'text' },
|
||||||
{ name: 'prod_list_description', type: 'text' },
|
{ name: 'prod_list_description', type: 'text' },
|
||||||
{ name: 'prod_list_feature', type: 'text' },
|
{ name: 'prod_list_feature', type: 'text' },
|
||||||
@@ -50,6 +52,7 @@ const collections = [
|
|||||||
{ name: 'prod_list_basic_price', type: 'number' },
|
{ name: 'prod_list_basic_price', type: 'number' },
|
||||||
{ name: 'prod_list_vip_price', type: 'json' },
|
{ name: 'prod_list_vip_price', type: 'json' },
|
||||||
{ name: 'prod_list_remark', type: 'text' },
|
{ name: 'prod_list_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true },
|
||||||
],
|
],
|
||||||
indexes: [
|
indexes: [
|
||||||
'CREATE UNIQUE INDEX idx_tbl_product_list_prod_list_id ON tbl_product_list (prod_list_id)',
|
'CREATE UNIQUE INDEX idx_tbl_product_list_prod_list_id ON tbl_product_list (prod_list_id)',
|
||||||
@@ -108,7 +111,9 @@ function buildCollectionPayload(collectionData, existingCollection) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const targetFieldMap = new Map(collectionData.fields.map((field) => [field.name, field]));
|
const targetFieldMap = new Map(collectionData.fields.map((field) => [field.name, field]));
|
||||||
const fields = (existingCollection.fields || []).map((existingField) => {
|
const fields = (existingCollection.fields || []).filter((existingField) => {
|
||||||
|
return existingField && existingField.name !== 'id';
|
||||||
|
}).map((existingField) => {
|
||||||
const targetField = targetFieldMap.get(existingField.name);
|
const targetField = targetFieldMap.get(existingField.name);
|
||||||
if (!targetField) {
|
if (!targetField) {
|
||||||
return existingField;
|
return existingField;
|
||||||
|
|||||||
328
script/pocketbase.scheme.js
Normal file
328
script/pocketbase.scheme.js
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
import { createRequire } from 'module';
|
||||||
|
import PocketBase from 'pocketbase';
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
let runtimeConfig = {};
|
||||||
|
try {
|
||||||
|
runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
|
||||||
|
} catch (_error) {
|
||||||
|
runtimeConfig = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const PB_URL = (process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
|
||||||
|
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
|
||||||
|
const OWNER_AUTH_RULE = '@request.auth.id != ""';
|
||||||
|
const SOFT_DELETE_RULE = 'is_delete = 0';
|
||||||
|
|
||||||
|
if (!AUTH_TOKEN) {
|
||||||
|
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行方案相关建表。');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pb = new PocketBase(PB_URL);
|
||||||
|
|
||||||
|
const collections = [
|
||||||
|
{
|
||||||
|
name: 'tbl_scheme_template',
|
||||||
|
type: 'base',
|
||||||
|
listRule: `${OWNER_AUTH_RULE} && scheme_template_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
viewRule: `${OWNER_AUTH_RULE} && scheme_template_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
createRule: `${OWNER_AUTH_RULE} && @request.body.scheme_template_owner = @request.auth.openid`,
|
||||||
|
updateRule: `${OWNER_AUTH_RULE} && scheme_template_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
deleteRule: `${OWNER_AUTH_RULE} && scheme_template_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
fields: [
|
||||||
|
{ name: 'scheme_template_id', type: 'text', required: true, autogeneratePattern: 'SCHTPL-[0-9]{13}-[A-Za-z0-9]{6}' },
|
||||||
|
{ name: 'scheme_template_icon', type: 'text' },
|
||||||
|
{ name: 'scheme_template_label', type: 'text' },
|
||||||
|
{ name: 'scheme_template_name', type: 'text', required: true },
|
||||||
|
{ name: 'scheme_template_owner', type: 'text', required: true },
|
||||||
|
{ name: 'scheme_template_status', type: 'text' },
|
||||||
|
{ name: 'scheme_template_solution_type', type: 'text' },
|
||||||
|
{ name: 'scheme_template_solution_feature', type: 'text' },
|
||||||
|
{ name: 'scheme_template_product_list', type: 'json' },
|
||||||
|
{ name: 'scheme_template_description', type: 'text' },
|
||||||
|
{ name: 'scheme_template_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true },
|
||||||
|
],
|
||||||
|
indexes: [
|
||||||
|
'CREATE UNIQUE INDEX idx_tbl_scheme_template_scheme_template_id ON tbl_scheme_template (scheme_template_id)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_template_scheme_template_owner ON tbl_scheme_template (scheme_template_owner)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_template_scheme_template_name ON tbl_scheme_template (scheme_template_name)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_template_scheme_template_label ON tbl_scheme_template (scheme_template_label)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_template_scheme_template_status ON tbl_scheme_template (scheme_template_status)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_template_solution_type ON tbl_scheme_template (scheme_template_solution_type)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_template_solution_feature ON tbl_scheme_template (scheme_template_solution_feature)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_template_owner_status ON tbl_scheme_template (scheme_template_owner, scheme_template_status)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tbl_scheme',
|
||||||
|
type: 'base',
|
||||||
|
listRule: `${OWNER_AUTH_RULE} && scheme_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
viewRule: `${OWNER_AUTH_RULE} && scheme_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
createRule: `${OWNER_AUTH_RULE} && @request.body.scheme_owner = @request.auth.openid`,
|
||||||
|
updateRule: `${OWNER_AUTH_RULE} && scheme_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
deleteRule: `${OWNER_AUTH_RULE} && scheme_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
fields: [
|
||||||
|
{ name: 'scheme_id', type: 'text', required: true, autogeneratePattern: 'SCHEME-[0-9]{13}-[A-Za-z0-9]{6}' },
|
||||||
|
{ name: 'scheme_name', type: 'text', required: true },
|
||||||
|
{ name: 'scheme_owner', type: 'text', required: true },
|
||||||
|
{ name: 'scheme_share_status', type: 'text' },
|
||||||
|
{ name: 'scheme_expires_at', type: 'date' },
|
||||||
|
{ name: 'scheme_hotel_type', type: 'text' },
|
||||||
|
{ name: 'scheme_solution_type', type: 'text' },
|
||||||
|
{ name: 'scheme_solution_feature', type: 'text' },
|
||||||
|
{ name: 'scheme_room_type', type: 'json' },
|
||||||
|
{ name: 'scheme_curtains', type: 'text' },
|
||||||
|
{ name: 'scheme_voice_device', type: 'text' },
|
||||||
|
{ name: 'scheme_ac_type', type: 'text' },
|
||||||
|
{ name: 'scheme_template_highend', type: 'text' },
|
||||||
|
{ name: 'scheme_template_midend', type: 'text' },
|
||||||
|
{ name: 'scheme_template_lowend', type: 'text' },
|
||||||
|
{ name: 'scheme_status', type: 'text' },
|
||||||
|
{ name: 'scheme_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true },
|
||||||
|
],
|
||||||
|
indexes: [
|
||||||
|
'CREATE UNIQUE INDEX idx_tbl_scheme_scheme_id ON tbl_scheme (scheme_id)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_scheme_owner ON tbl_scheme (scheme_owner)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_scheme_name ON tbl_scheme (scheme_name)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_scheme_share_status ON tbl_scheme (scheme_share_status)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_scheme_expires_at ON tbl_scheme (scheme_expires_at)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_scheme_hotel_type ON tbl_scheme (scheme_hotel_type)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_scheme_solution_type ON tbl_scheme (scheme_solution_type)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_scheme_solution_feature ON tbl_scheme (scheme_solution_feature)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_scheme_status ON tbl_scheme (scheme_status)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_owner_status ON tbl_scheme (scheme_owner, scheme_status)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tbl_scheme_share',
|
||||||
|
type: 'base',
|
||||||
|
listRule: `${OWNER_AUTH_RULE} && scheme_share_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
viewRule: `${OWNER_AUTH_RULE} && scheme_share_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
createRule: `${OWNER_AUTH_RULE} && @request.body.scheme_share_owner = @request.auth.openid`,
|
||||||
|
updateRule: `${OWNER_AUTH_RULE} && scheme_share_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
deleteRule: `${OWNER_AUTH_RULE} && scheme_share_owner = @request.auth.openid && ${SOFT_DELETE_RULE}`,
|
||||||
|
fields: [
|
||||||
|
{ name: 'scheme_share_id', type: 'text', required: true, autogeneratePattern: 'SCHSHARE-[0-9]{13}-[A-Za-z0-9]{6}' },
|
||||||
|
{ name: 'scheme_id', type: 'text', required: true },
|
||||||
|
{ name: 'scheme_share_owner', type: 'text', required: true },
|
||||||
|
{ name: 'scheme_share_to', type: 'text', required: true },
|
||||||
|
{ name: 'scheme_share_acceptance_status', type: 'text' },
|
||||||
|
{ name: 'scheme_share_expires_at', type: 'date' },
|
||||||
|
{ name: 'scheme_share_permission', type: 'text' },
|
||||||
|
{ name: 'scheme_share_remark', type: 'text' },
|
||||||
|
{ name: 'is_delete', type: 'number', default: 0, min: 0, max: 1, onlyInt: true },
|
||||||
|
],
|
||||||
|
indexes: [
|
||||||
|
'CREATE UNIQUE INDEX idx_tbl_scheme_share_scheme_share_id ON tbl_scheme_share (scheme_share_id)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_share_scheme_id ON tbl_scheme_share (scheme_id)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_share_scheme_share_owner ON tbl_scheme_share (scheme_share_owner)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_share_scheme_share_to ON tbl_scheme_share (scheme_share_to)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_share_acceptance_status ON tbl_scheme_share (scheme_share_acceptance_status)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_share_expires_at ON tbl_scheme_share (scheme_share_expires_at)',
|
||||||
|
'CREATE INDEX idx_tbl_scheme_share_owner_to ON tbl_scheme_share (scheme_share_owner, scheme_share_to)',
|
||||||
|
'CREATE UNIQUE INDEX idx_tbl_scheme_share_unique_map ON tbl_scheme_share (scheme_share_owner, scheme_id, scheme_share_to)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function normalizeFieldPayload(field, existingField) {
|
||||||
|
const payload = existingField
|
||||||
|
? Object.assign({}, existingField)
|
||||||
|
: {
|
||||||
|
name: field.name,
|
||||||
|
type: field.type,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (existingField && existingField.id) {
|
||||||
|
payload.id = existingField.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.name = field.name;
|
||||||
|
payload.type = field.type;
|
||||||
|
|
||||||
|
if (typeof field.required !== 'undefined') {
|
||||||
|
payload.required = field.required;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.type === 'autodate') {
|
||||||
|
payload.onCreate = typeof field.onCreate === 'boolean' ? field.onCreate : true;
|
||||||
|
payload.onUpdate = typeof field.onUpdate === 'boolean' ? field.onUpdate : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.type === 'date') {
|
||||||
|
payload.required = !!field.required;
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCollectionPayload(collectionData, existingCollection) {
|
||||||
|
if (!existingCollection) {
|
||||||
|
return {
|
||||||
|
name: collectionData.name,
|
||||||
|
type: collectionData.type,
|
||||||
|
listRule: Object.prototype.hasOwnProperty.call(collectionData, 'listRule') ? collectionData.listRule : null,
|
||||||
|
viewRule: Object.prototype.hasOwnProperty.call(collectionData, 'viewRule') ? collectionData.viewRule : null,
|
||||||
|
createRule: Object.prototype.hasOwnProperty.call(collectionData, 'createRule') ? collectionData.createRule : null,
|
||||||
|
updateRule: Object.prototype.hasOwnProperty.call(collectionData, 'updateRule') ? collectionData.updateRule : null,
|
||||||
|
deleteRule: Object.prototype.hasOwnProperty.call(collectionData, 'deleteRule') ? collectionData.deleteRule : null,
|
||||||
|
fields: collectionData.fields.map((field) => normalizeFieldPayload(field, null)),
|
||||||
|
indexes: collectionData.indexes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetFieldMap = new Map(collectionData.fields.map((field) => [field.name, field]));
|
||||||
|
const fields = (existingCollection.fields || []).map((existingField) => {
|
||||||
|
const targetField = targetFieldMap.get(existingField.name);
|
||||||
|
if (!targetField) {
|
||||||
|
return existingField;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetFieldMap.delete(existingField.name);
|
||||||
|
return normalizeFieldPayload(targetField, existingField);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const field of targetFieldMap.values()) {
|
||||||
|
fields.push(normalizeFieldPayload(field, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: collectionData.name,
|
||||||
|
type: collectionData.type,
|
||||||
|
listRule: Object.prototype.hasOwnProperty.call(collectionData, 'listRule') ? collectionData.listRule : existingCollection.listRule,
|
||||||
|
viewRule: Object.prototype.hasOwnProperty.call(collectionData, 'viewRule') ? collectionData.viewRule : existingCollection.viewRule,
|
||||||
|
createRule: Object.prototype.hasOwnProperty.call(collectionData, 'createRule') ? collectionData.createRule : existingCollection.createRule,
|
||||||
|
updateRule: Object.prototype.hasOwnProperty.call(collectionData, 'updateRule') ? collectionData.updateRule : existingCollection.updateRule,
|
||||||
|
deleteRule: Object.prototype.hasOwnProperty.call(collectionData, 'deleteRule') ? collectionData.deleteRule : existingCollection.deleteRule,
|
||||||
|
fields: fields,
|
||||||
|
indexes: collectionData.indexes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFieldList(fields) {
|
||||||
|
return (fields || []).map((field) => ({
|
||||||
|
name: field.name,
|
||||||
|
type: field.type,
|
||||||
|
required: !!field.required,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createOrUpdateCollection(collectionData) {
|
||||||
|
console.log(`🔄 正在处理表: ${collectionData.name} ...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const list = await pb.collections.getFullList({
|
||||||
|
sort: '-created',
|
||||||
|
});
|
||||||
|
const existing = list.find((item) => item.name === collectionData.name);
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
await pb.collections.update(existing.id, buildCollectionPayload(collectionData, existing));
|
||||||
|
console.log(`♻️ ${collectionData.name} 已存在,已按最新结构更新。`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await pb.collections.create(buildCollectionPayload(collectionData, null));
|
||||||
|
console.log(`✅ ${collectionData.name} 创建完成。`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ 处理集合 ${collectionData.name} 失败:`, {
|
||||||
|
status: error.status,
|
||||||
|
message: error.message,
|
||||||
|
response: error.response,
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCollectionByName(collectionName) {
|
||||||
|
const list = await pb.collections.getFullList({
|
||||||
|
sort: '-created',
|
||||||
|
});
|
||||||
|
return list.find((item) => item.name === collectionName) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyCollections(targetCollections) {
|
||||||
|
console.log('\n🔍 开始校验方案相关表结构与索引...');
|
||||||
|
|
||||||
|
for (const target of targetCollections) {
|
||||||
|
const remote = await getCollectionByName(target.name);
|
||||||
|
if (!remote) {
|
||||||
|
throw new Error(`${target.name} 不存在`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteFields = normalizeFieldList(remote.fields);
|
||||||
|
const targetFields = normalizeFieldList(target.fields);
|
||||||
|
const remoteFieldMap = new Map(remoteFields.map((field) => [field.name, field.type]));
|
||||||
|
const remoteRequiredMap = new Map(remoteFields.map((field) => [field.name, field.required]));
|
||||||
|
const missingFields = [];
|
||||||
|
const mismatchedTypes = [];
|
||||||
|
const mismatchedRequired = [];
|
||||||
|
|
||||||
|
for (const field of targetFields) {
|
||||||
|
if (!remoteFieldMap.has(field.name)) {
|
||||||
|
missingFields.push(field.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remoteFieldMap.get(field.name) !== field.type) {
|
||||||
|
mismatchedTypes.push(`${field.name}:${remoteFieldMap.get(field.name)}!=${field.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remoteRequiredMap.get(field.name) !== !!field.required) {
|
||||||
|
mismatchedRequired.push(`${field.name}:${remoteRequiredMap.get(field.name)}!=${!!field.required}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteIndexes = new Set(remote.indexes || []);
|
||||||
|
const missingIndexes = target.indexes.filter((indexSql) => !remoteIndexes.has(indexSql));
|
||||||
|
|
||||||
|
if (remote.type !== target.type) {
|
||||||
|
throw new Error(`${target.name} 类型不匹配,期望 ${target.type},实际 ${remote.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!missingFields.length && !mismatchedTypes.length && !mismatchedRequired.length && !missingIndexes.length) {
|
||||||
|
console.log(`✅ ${target.name} 校验通过。`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`❌ ${target.name} 校验失败:`);
|
||||||
|
if (missingFields.length) {
|
||||||
|
console.log(` - 缺失字段: ${missingFields.join(', ')}`);
|
||||||
|
}
|
||||||
|
if (mismatchedTypes.length) {
|
||||||
|
console.log(` - 字段类型不匹配: ${mismatchedTypes.join(', ')}`);
|
||||||
|
}
|
||||||
|
if (mismatchedRequired.length) {
|
||||||
|
console.log(` - 字段必填属性不匹配: ${mismatchedRequired.join(', ')}`);
|
||||||
|
}
|
||||||
|
if (missingIndexes.length) {
|
||||||
|
console.log(` - 缺失索引: ${missingIndexes.join(' | ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`${target.name} 结构与预期不一致`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
try {
|
||||||
|
console.log(`🔄 正在连接 PocketBase: ${PB_URL}`);
|
||||||
|
pb.authStore.save(AUTH_TOKEN, null);
|
||||||
|
console.log('✅ 已使用 POCKETBASE_AUTH_TOKEN 载入认证状态。');
|
||||||
|
|
||||||
|
for (const collectionData of collections) {
|
||||||
|
await createOrUpdateCollection(collectionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
await verifyCollections(collections);
|
||||||
|
console.log('\n🎉 方案相关表结构初始化并校验完成!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 初始化失败:', error.response?.data || error.message);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
||||||
Reference in New Issue
Block a user