diff --git a/.tmp-openapi-validate/make-openapi-standalone.cjs b/.tmp-openapi-validate/make-openapi-standalone.cjs new file mode 100644 index 0000000..64b92b7 --- /dev/null +++ b/.tmp-openapi-validate/make-openapi-standalone.cjs @@ -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.`); diff --git a/docs/pb_tbl_product_list.md b/docs/pb_tbl_product_list.md index 165bc6a..2ef0e86 100644 --- a/docs/pb_tbl_product_list.md +++ b/docs/pb_tbl_product_list.md @@ -16,6 +16,7 @@ | `prod_list_id` | `text` | 是 | 产品列表业务 ID,唯一标识 | | `prod_list_name` | `text` | 是 | 产品名称 | | `prod_list_modelnumber` | `text` | 否 | 产品型号 | +| `prod_list_barcode` | `text` | 否 | 产品料号 | | `prod_list_icon` | `text` | 否 | 产品图标,保存 `tbl_attachments.attachments_id`,多图时以 `|` 分隔 | | `prod_list_description` | `text` | 否 | 产品说明(editor 内容,建议保存 Markdown 或已净化 HTML) | | `prod_list_feature` | `text` | 否 | 产品特色 | @@ -50,6 +51,7 @@ ## 补充约定 - `prod_list_icon` 仅保存附件业务 ID,真实文件统一在 `tbl_attachments`;多图时按上传顺序使用 `|` 聚合。 +- `prod_list_barcode` 为可空文本字段,用于保存产品料号;当前已接入产品管理页录入、产品列表模糊检索以及方案模板中的产品快照展示。 - `is_delete` 用于软删除控制,产品停用/删除时建议优先标记为 `1`。 - 集合默认查询规则已内置 `is_delete = 0`,产品列表默认不返回已软删除记录。 - 当前预构建脚本中已将 `listRule` 与 `viewRule` 设置为空字符串(`""`),对应 PocketBase 的“任何人可查看”。 @@ -61,4 +63,4 @@ - 前端渲染 `prod_list_description` 时应保持和存储格式一致(Markdown 渲染器或受控 HTML 渲染),避免直接注入未净化内容。 - `prod_list_basic_price` 使用 `number`,如需货币精度策略建议后续统一为“分”或固定小数位。 - PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。 -- 本文档为预构建结构说明,尚未执行线上建表。 +- 本文档已同步当前产品表建表脚本;若已执行脚本,则以 PocketBase 实际结构为准。 diff --git a/docs/pb_tbl_scheme_template.md b/docs/pb_tbl_scheme_template.md index 687f949..8998949 100644 --- a/docs/pb_tbl_scheme_template.md +++ b/docs/pb_tbl_scheme_template.md @@ -22,7 +22,7 @@ | `scheme_template_status` | `text` | 否 | 模板状态,如有效 / 主推 / 过期 | | `scheme_template_solution_type` | `text` | 否 | 适用方案类型 | | `scheme_template_solution_feature` | `text` | 否 | 适用方案特点 | -| `scheme_template_product_list` | `json` | 否 | 产品清单,建议格式:`[{"product_id":"PROD-xxx","qty":5,"note":"客厅使用"}]` | +| `scheme_template_product_list` | `json` | 否 | 产品清单,支持保存精简结构或完整产品快照数组 | | `scheme_template_description` | `text` | 否 | 模板说明 | | `scheme_template_remark` | `text` | 否 | 备注 | | `is_delete` | `number` | 否 | 软删除标记,`0` 表示未删除,`1` 表示已删除,默认 `0` | @@ -53,7 +53,11 @@ ## 补充约定 - `scheme_template_icon` 建议延续现有项目做法,仅保存 `attachments_id`,真实文件统一走 `tbl_attachments`。 -- `scheme_template_product_list` 建议使用 `json` 数组,单项推荐结构:`product_id`、`qty`、`note`;不要保存自由格式长字符串。 +- `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 字段清单里单独声明。 diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js index 06a781c..42460bc 100644 --- a/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js +++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js @@ -525,6 +525,7 @@ function validateProductMutationBody(e, isUpdate) { prod_list_id: payload.prod_list_id || '', prod_list_name: payload.prod_list_name || '', 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_description: payload.prod_list_description || '', prod_list_feature: payload.prod_list_feature || '', @@ -698,7 +699,7 @@ function validateCartMutationBody(e, isUpdate) { if (!isUpdate) { 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') { throw createAppError(400, 'cart_product_quantity 为必填项') diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js index ac9b865..391ce2b 100644 --- a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js +++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js @@ -227,6 +227,7 @@ function buildProductInfo(record) { prod_list_id: '', prod_list_name: '', prod_list_modelnumber: '', + prod_list_barcode: '', prod_list_basic_price: null, } } @@ -235,6 +236,7 @@ function buildProductInfo(record) { prod_list_id: record.getString('prod_list_id'), prod_list_name: record.getString('prod_list_name'), 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'), } } @@ -295,6 +297,7 @@ function exportCartRecord(record, productRecord) { cart_remark: record.getString('cart_remark'), product_name: productInfo.prod_list_name, product_modelnumber: productInfo.prod_list_modelnumber, + product_barcode: productInfo.prod_list_barcode, product_basic_price: productInfo.prod_list_basic_price, created: String(record.created || ''), updated: String(record.updated || ''), @@ -340,6 +343,7 @@ function exportAdminCartRecord(record) { cart_remark: record && typeof record.getString === 'function' ? record.getString('cart_remark') : String(record && record.cart_remark || ''), product_name: productInfo.prod_list_name, product_modelnumber: productInfo.prod_list_modelnumber, + product_barcode: productInfo.prod_list_barcode, product_basic_price: productInfo.prod_list_basic_price, created: String(record && record.created || ''), updated: String(record && record.updated || ''), diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/productService.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/productService.js index c6bd8ad..ff85726 100644 --- a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/productService.js +++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/productService.js @@ -580,6 +580,7 @@ function exportProductRecord(record, extra) { prod_list_id: record.getString('prod_list_id'), prod_list_name: record.getString('prod_list_name'), prod_list_modelnumber: record.getString('prod_list_modelnumber'), + prod_list_barcode: record.getString('prod_list_barcode'), prod_list_icon: iconIds.join('|'), prod_list_icon_ids: iconIds, prod_list_icon_attachments: iconAttachments, @@ -639,6 +640,7 @@ function listProducts(payload) { || item.prod_list_id.toLowerCase().indexOf(keyword) !== -1 || item.prod_list_name.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 const matchedStatus = !status || item.prod_list_status === status const matchedCategory = !category || item.prod_list_category === category @@ -695,6 +697,7 @@ function createProduct(_userOpenid, payload) { record.set('prod_list_id', targetProductId) record.set('prod_list_name', normalizeText(payload.prod_list_name)) 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_description', normalizeText(payload.prod_list_description)) record.set('prod_list_feature', normalizeText(payload.prod_list_feature)) @@ -746,6 +749,7 @@ function updateProduct(_userOpenid, payload) { record.set('prod_list_name', normalizeText(payload.prod_list_name)) 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_description', normalizeText(payload.prod_list_description)) record.set('prod_list_feature', normalizeText(payload.prod_list_feature)) diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/schemeService.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/schemeService.js index 16722e5..8f70d48 100644 --- a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/schemeService.js +++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/schemeService.js @@ -237,12 +237,29 @@ function normalizeTemplateProductList(value) { continue } - const productId = normalizeText(item.product_id) + const productId = normalizeText(item.prod_list_id || item.product_id) if (!productId) { - throw createAppError(400, 'scheme_template_product_list 第 ' + (i + 1) + ' 项 product_id 为必填项') + 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 必须为正数') diff --git a/pocket-base/bai_web_pb_hooks/views/cart-order-manage.html b/pocket-base/bai_web_pb_hooks/views/cart-order-manage.html index 70557d3..8a199da 100644 --- a/pocket-base/bai_web_pb_hooks/views/cart-order-manage.html +++ b/pocket-base/bai_web_pb_hooks/views/cart-order-manage.html @@ -574,12 +574,12 @@ } return '
| 商品名称 | 型号 | 数量 | 单价 | 状态 | 加入时间 | 购物车名 | ' + + '商品名称 | 型号/料号 | 数量 | 单价 | 状态 | 加入时间 | 购物车名 | ' + '||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
' + escapeHtml(item.product_name || item.cart_product_business_id || item.cart_product_id || '-') + ' recordId:' + escapeHtml(item.cart_product_id || '-') + ' 业务ID:' + escapeHtml(item.cart_product_business_id || '-') + ' | '
- + '' + escapeHtml(item.product_modelnumber || '-') + ' | ' + + '' + escapeHtml(item.product_modelnumber || '-') + ' 料号:' + escapeHtml(item.product_barcode || '-') + ' | '
+ '' + escapeHtml(item.cart_product_quantity || 0) + ' | ' + '¥' + escapeHtml(item.cart_at_price || 0) + ' | ' + '' + escapeHtml(item.cart_status || '-') + ' | ' diff --git a/pocket-base/bai_web_pb_hooks/views/product-manage.html b/pocket-base/bai_web_pb_hooks/views/product-manage.html index e8248e2..07ec8f3 100644 --- a/pocket-base/bai_web_pb_hooks/views/product-manage.html +++ b/pocket-base/bai_web_pb_hooks/views/product-manage.html @@ -122,7 +122,7 @@||||||||||||
' + escapeHtml(item.prod_list_name || '') + ' ' + escapeHtml(item.prod_list_modelnumber || '') + ' | '
+ + '' + escapeHtml(item.prod_list_name || '') + ' 型号:' + escapeHtml(item.prod_list_modelnumber || '-') + ' 料号:' + escapeHtml(item.prod_list_barcode || '-') + ' | '
+ '' + escapeHtml(item.prod_list_category || '-') + ' 方案:' + escapeHtml(item.prod_list_plantype || '-') + ' / 系列:' + escapeHtml(item.prod_list_series || '-') + ' 分类排序: ' + escapeHtml(item.prod_list_sort === null || typeof item.prod_list_sort === 'undefined' ? '-' : item.prod_list_sort) + '(第 ' + escapeHtml(item.prod_list_category_rank || '-') + ' 位) | '
+ '' + escapeHtml(item.prod_list_tags || '-') + ' | '
+ '' + escapeHtml(item.prod_list_status || '-') + ' ¥' + escapeHtml(item.prod_list_basic_price === null || typeof item.prod_list_basic_price === 'undefined' ? '-' : item.prod_list_basic_price) + ' | '
@@ -1863,12 +1879,13 @@
}).join('')
}
- function buildCopyPayload(source, nextName, nextModel) {
+ function buildCopyPayload(source, nextName, nextModel, nextBarcode) {
return {
id: generatePocketBaseRecordId(),
prod_list_id: '',
prod_list_name: normalizeText(nextName),
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_description: source && source.prod_list_description ? normalizeText(source.prod_list_description) : '',
prod_list_feature: source && source.prod_list_feature ? normalizeText(source.prod_list_feature) : '',
@@ -1894,9 +1911,11 @@
state.copySourceProductId = source ? normalizeText(source.prod_list_id) : ''
const defaultName = normalizeText(source && source.prod_list_name)
const defaultModel = normalizeText(source && source.prod_list_modelnumber)
+ const defaultBarcode = normalizeText(source && source.prod_list_barcode)
copyNameInputEl.value = defaultName ? (defaultName + '-副本') : ''
copyModelInputEl.value = defaultModel
+ copyBarcodeInputEl.value = defaultBarcode
copyModalEl.classList.add('show')
setTimeout(function () {
@@ -1910,6 +1929,7 @@
copyModalEl.classList.remove('show')
copyNameInputEl.value = ''
copyModelInputEl.value = ''
+ copyBarcodeInputEl.value = ''
}
async function copyProduct(productId) {
@@ -1949,7 +1969,7 @@
return
}
- const payload = buildCopyPayload(source, normalizedName, copyModelInputEl.value)
+ const payload = buildCopyPayload(source, normalizedName, copyModelInputEl.value, copyBarcodeInputEl.value)
setStatus('正在复制产品...', '')
showLoading('正在复制产品,请稍候...')
@@ -2126,6 +2146,7 @@
prod_list_id: state.mode === 'edit' ? state.editingId : '',
prod_list_name: normalizeText(fields.name.value),
prod_list_modelnumber: normalizeText(fields.model.value),
+ prod_list_barcode: normalizeText(fields.barcode.value),
prod_list_icon: joinPipe(finalIconIds),
prod_list_description: normalizeText(fields.description.value),
prod_list_feature: normalizeText(fields.feature.value),
@@ -2497,6 +2518,12 @@
}
})
+ copyBarcodeInputEl.addEventListener('keydown', function (event) {
+ if (event.key === 'Enter') {
+ confirmCopyProduct()
+ }
+ })
+
document.getElementById('submitBtn').addEventListener('click', function () {
submitProduct()
})
diff --git a/pocket-base/bai_web_pb_hooks/views/scheme-manage.html b/pocket-base/bai_web_pb_hooks/views/scheme-manage.html
index 7d7e423..fe1d0d0 100644
--- a/pocket-base/bai_web_pb_hooks/views/scheme-manage.html
+++ b/pocket-base/bai_web_pb_hooks/views/scheme-manage.html
@@ -17,10 +17,10 @@
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; background: linear-gradient(180deg, #edf6ff 0%, #f8fafc 100%); color: #0f172a; }
.container { max-width: 1520px; margin: 0 auto; padding: 24px 14px 42px; }
+ .topbar { background: rgba(255,255,255,0.96); border: 1px solid #dbe3f0; border-radius: 20px; box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08); padding: 18px; margin-bottom: 14px; }
.panel { background: rgba(255,255,255,0.96); border: 1px solid #dbe3f0; border-radius: 20px; box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08); padding: 18px; }
.panel + .panel { margin-top: 14px; }
- .header-row, .actions, .toolbar, .form-actions, .upload-row, .summary { display: flex; flex-wrap: wrap; gap: 10px; }
- .header-row { justify-content: space-between; align-items: center; }
+ .actions, .toolbar, .form-actions, .upload-row, .summary, .product-picker-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.toolbar { display: grid; grid-template-columns: 1.2fr 1fr 1fr 1fr auto; gap: 10px; }
.toolbar.simple { grid-template-columns: 1.5fr 1fr 1fr auto; }
.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
@@ -60,6 +60,20 @@
.thumb { width: 120px; height: 120px; border-radius: 16px; border: 1px solid #dbe3f0; object-fit: cover; background: #f8fafc; }
.thumb-empty { display: flex; align-items: center; justify-content: center; color: #94a3b8; font-size: 13px; }
.section-title { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 12px; }
+ .product-picker-box { border: 1px solid #dbe3f0; border-radius: 16px; background: linear-gradient(180deg, #f8fbff 0%, #ffffff 100%); padding: 14px; }
+ .product-picker-grid { display: grid; grid-template-columns: 1fr 1.2fr auto auto; gap: 10px; align-items: end; }
+ .product-picker-actions { justify-content: flex-end; }
+ .product-list-summary { margin-top: 12px; border: 1px solid #dbe3f0; border-radius: 14px; background: #fff; overflow: hidden; }
+ .product-list-summary:empty { display: none; }
+ .product-list-empty { padding: 16px; color: #64748b; font-size: 13px; text-align: center; }
+ .product-summary-row { display: grid; grid-template-columns: auto 1fr auto; gap: 12px; align-items: center; padding: 12px 14px; border-bottom: 1px solid #e5e7eb; }
+ .product-summary-row:last-child { border-bottom: none; }
+ .product-summary-order { width: 28px; height: 28px; border-radius: 999px; background: #eff6ff; color: #1d4ed8; display: inline-flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; }
+ .product-summary-main { min-width: 0; }
+ .product-summary-title { font-weight: 700; color: #0f172a; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+ .product-summary-meta { margin-top: 4px; color: #64748b; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+ .product-summary-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
+ .product-summary-actions .btn { padding: 8px 12px; font-size: 13px; }
.loading-mask { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; padding: 24px; background: rgba(15, 23, 42, 0.42); z-index: 9999; }
.loading-mask.show { display: flex; }
.loading-card { min-width: min(92vw, 320px); padding: 24px 20px; border-radius: 20px; background: rgba(255,255,255,0.98); border: 1px solid #dbe3f0; text-align: center; box-shadow: 0 28px 70px rgba(15, 23, 42, 0.2); }
@@ -67,7 +81,7 @@
.muted-block { padding: 12px 14px; border-radius: 14px; background: #f8fafc; border: 1px solid #e2e8f0; color: #475569; font-size: 13px; line-height: 1.7; }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@media (max-width: 1080px) {
- .grid, .toolbar, .toolbar.simple, .thumb-box { grid-template-columns: 1fr; }
+ .grid, .toolbar, .toolbar.simple, .thumb-box, .product-picker-grid { grid-template-columns: 1fr; }
}
@media (max-width: 920px) {
table, thead, tbody, th, td, tr { display: block; }
@@ -75,22 +89,20 @@
tr { margin-bottom: 12px; border-bottom: 1px solid #e5e7eb; }
td { display: flex; justify-content: space-between; gap: 10px; }
td::before { content: attr(data-label); font-weight: 700; color: #475569; }
+ .product-summary-row { grid-template-columns: 1fr; }
+ .product-summary-actions { justify-content: flex-start; }
}
{{ template "theme_head" . }}
|||||||||||||
' + escapeHtml(item.scheme_template_id) + ' | '
@@ -640,7 +982,7 @@
+ '' + escapeHtml(item.scheme_template_owner) + ' | '
+ '' + escapeHtml(item.scheme_template_label || '-') + ' ' + escapeHtml(item.scheme_template_status || '-') + ' | '
+ '' + escapeHtml(item.scheme_template_solution_type || '-') + ' ' + escapeHtml(item.scheme_template_solution_feature || '-') + ' | '
- + '' + escapeHtml(toJsonText(productList)) + ' | '
+ + '' + escapeHtml(productSummary) + ' | '
+ ''
+ ''
+ ''
@@ -698,11 +1040,14 @@
templateFields.solutionFeature.value = ''
templateFields.iconId.value = ''
templateFields.iconFile.value = ''
- templateFields.productList.value = '[]'
+ templateFields.productCategoryFilter.value = ''
+ templateFields.productSelect.innerHTML = ''
templateFields.description.value = ''
templateFields.remark.value = ''
state.lastUploadedTemplateIcon = null
+ setTemplateProductItems([])
renderTemplateIcon('')
+ renderTemplateProductOptions()
}
function resetSchemeForm() {
@@ -796,8 +1141,11 @@
async function refreshAll() {
showLoading('正在刷新模板与方案数据...')
try {
- await loadTemplates()
- await loadSchemes()
+ await Promise.all([
+ loadTemplates(),
+ loadSchemes(),
+ loadTemplateProductResources(true),
+ ])
setStatus(pageStatusEl, '数据刷新完成。', 'success')
} catch (error) {
setStatus(pageStatusEl, error.message || '刷新失败', 'error')
@@ -824,7 +1172,7 @@
templateFields.solutionType.value = detail.scheme_template_solution_type || ''
templateFields.solutionFeature.value = detail.scheme_template_solution_feature || ''
templateFields.iconId.value = detail.scheme_template_icon || ''
- templateFields.productList.value = toJsonText(detail.scheme_template_product_list || [])
+ setTemplateProductItems(detail.scheme_template_product_list || [])
templateFields.description.value = detail.scheme_template_description || ''
templateFields.remark.value = detail.scheme_template_remark || ''
state.lastUploadedTemplateIcon = detail.scheme_template_icon_attachment || null
@@ -877,6 +1225,8 @@
}
function buildTemplatePayload() {
+ const productList = parseJsonTextarea(templateFields.productList.value, 'scheme_template_product_list')
+ setTemplateProductItems(productList)
return {
scheme_template_id: normalizeText(templateFields.id.value),
scheme_template_name: normalizeText(templateFields.name.value),
@@ -886,7 +1236,7 @@
scheme_template_status: normalizeText(templateFields.status.value),
scheme_template_solution_type: normalizeText(templateFields.solutionType.value),
scheme_template_solution_feature: normalizeText(templateFields.solutionFeature.value),
- scheme_template_product_list: parseJsonTextarea(templateFields.productList.value, 'scheme_template_product_list'),
+ scheme_template_product_list: productList,
scheme_template_description: templateFields.description.value || '',
scheme_template_remark: templateFields.remark.value || '',
}
@@ -1066,12 +1416,31 @@
document.getElementById('uploadTemplateIconBtn').addEventListener('click', function () {
handleTemplateIconUpload()
})
+ document.getElementById('addTemplateProductBtn').addEventListener('click', function () {
+ addSelectedTemplateProduct()
+ })
+ document.getElementById('reloadTemplateProductsBtn').addEventListener('click', function () {
+ loadTemplateProductResources()
+ })
document.getElementById('clearTemplateIconBtn').addEventListener('click', function () {
templateFields.iconId.value = ''
templateFields.iconFile.value = ''
state.lastUploadedTemplateIcon = null
renderTemplateIcon('')
})
+ templateFields.productCategoryFilter.addEventListener('change', function () {
+ renderTemplateProductOptions()
+ })
+ templateFields.productList.addEventListener('blur', function () {
+ if (!normalizeText(templateFields.productList.value)) {
+ setTemplateProductItems([])
+ return
+ }
+
+ try {
+ hydrateTemplateProductItemsFromTextarea()
+ } catch (_error) {}
+ })
templateFields.iconId.addEventListener('input', function () {
if (!normalizeText(templateFields.iconId.value)) {
@@ -1101,6 +1470,24 @@
return
}
+ const templateProductUpIndex = target.getAttribute('data-template-product-up')
+ if (templateProductUpIndex !== null) {
+ moveTemplateProductItem(templateProductUpIndex, -1)
+ return
+ }
+
+ const templateProductDownIndex = target.getAttribute('data-template-product-down')
+ if (templateProductDownIndex !== null) {
+ moveTemplateProductItem(templateProductDownIndex, 1)
+ return
+ }
+
+ const templateProductRemoveIndex = target.getAttribute('data-template-product-remove')
+ if (templateProductRemoveIndex !== null) {
+ removeTemplateProductItem(templateProductRemoveIndex)
+ return
+ }
+
const schemeEditId = target.getAttribute('data-scheme-edit')
if (schemeEditId) {
editScheme(schemeEditId)
diff --git a/pocket-base/spec/openapi-manage.yaml b/pocket-base/spec/openapi-manage.yaml
deleted file mode 100644
index 29ad05a..0000000
--- a/pocket-base/spec/openapi-manage.yaml
+++ /dev/null
@@ -1,1811 +0,0 @@
-openapi: 3.1.0
-info:
- title: BAI PocketBase Manage API
- description: |
- 基于 PocketBase `bai_api_pb_hooks` 的对外接口文档,可直接导入 Postman。
- 当前 `tbl_auth_users.openid` 已被定义为全平台统一身份锚点:
- - 微信用户:`openid = 微信 openid`
- - 平台用户:`openid = 服务端生成的 GUID`
- 请在 Apifox 环境中统一设置全局 Header:`Authorization: Bearer {{token}}`。
- version: 1.0.0-manage
- license:
- name: Proprietary
- identifier: LicenseRef-Proprietary
-servers:
- - url: https://bai-api.blv-oa.com
- description: "生产环境"
- - url: http://localhost:8090
- description: "PocketBase 本地环境"
-tags:
- - name: 系统
- description: "基础检查接口"
- - name: 平台认证
- description: "面向平台用户的认证接口;平台用户会生成 GUID 并写入统一 `openid` 字段。"
- - name: 字典管理
- description: "字典读接口公开;字典写接口面向 ManagePlatform 用户。"
- - name: 附件管理
- description: "面向 ManagePlatform 用户的附件上传、查询与删除接口。"
- - name: 文档管理
- description: "面向 ManagePlatform 用户的文档新增、查询、修改、删除接口;查询时会自动返回关联附件的 PocketBase 文件流链接。"
- - name: 文档历史
- description: "面向 ManagePlatform 用户的文档操作历史查询接口。"
-components:
- schemas:
- ApiResponse:
- type: object
- required: [statusCode, errMsg, data]
- properties:
- statusCode:
- type: integer
- description: "业务状态码"
- example: 200
- errMsg:
- type: string
- description: "业务提示信息"
- example: 操作成功
- data:
- description: "业务响应数据"
- type: object
- additionalProperties: true
- HealthData:
- type: object
- properties:
- status:
- type: string
- example: healthy
- version:
- type: string
- description: "当前已部署 hooks 版本号,用于确认发布是否生效"
- example: 2026.03.26-health-probe.1
- timestamp:
- type: string
- format: date-time
- UsersCountData:
- type: object
- properties:
- total_users:
- type: integer
- description: "tbl_auth_users 表中的用户总数"
- example: 128
- HelloWorldData:
- type: object
- properties:
- message:
- type: string
- example: Hello, World!
- timestamp:
- type: string
- format: date-time
- status:
- type: string
- example: success
- build_time:
- type:
- - string
- - 'null'
- format: date-time
- CompanyInfo:
- anyOf:
- - type: object
- description: 用户所属公司信息;当用户尚未绑定公司时返回 `null`
- properties:
- pb_id:
- type: string
- description: PocketBase 记录主键 id
- company_id:
- type: string
- description: 公司业务 id,由数据库自动生成
- 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: 备注
- created:
- type: string
- description: 记录创建时间
- updated:
- type: string
- description: 记录更新时间
- 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'
- UserInfo:
- type: object
- description: |
- 统一用户视图。
- 其中 `openid` 为全平台统一身份标识:微信用户使用微信 openid,平台用户使用服务端生成 GUID。
- properties:
- pb_id:
- description: "PocketBase 记录主键 id"
- type: string
- users_convers_id:
- description: "会话侧用户 ID"
- type: string
- users_id:
- description: "用户业务 ID"
- type: string
- users_idtype:
- description: "用户身份来源类型"
- anyOf:
- - type: string
- enum: [WeChat, ManagePlatform]
- - type: string
- users_id_number:
- description: "证件号"
- type: string
- users_status:
- description: "用户状态"
- type: string
- users_rank_level:
- description: "用户星级数值"
- type: [number, string]
- users_auth_type:
- description: "账户类型"
- type: [number, string]
- users_type:
- description: "用户类型"
- anyOf:
- - type: string
- enum: [游客, 注册用户]
- - type: string
- users_name:
- description: "用户姓名 / 昵称"
- type: string
- users_phone:
- description: "手机号"
- type: string
- users_phone_masked:
- type: string
- users_level:
- type: string
- description: "用户等级"
- users_level_name:
- type: string
- description: "用户等级名称,按 `users_level -> 数据-会员等级` 字典描述实时解析"
- users_tag:
- type: string
- description: "用户标签"
- users_picture:
- type: string
- description: "用户头像附件的 `attachments_id`"
- users_picture_url:
- type: string
- description: "根据 `users_picture -> tbl_attachments` 自动解析出的头像文件流链接"
- users_id_pic_a:
- type: string
- description: "证件正面附件的 `attachments_id`"
- users_id_pic_a_url:
- type: string
- description: "根据 `users_id_pic_a -> tbl_attachments` 自动解析出的文件流链接"
- users_id_pic_b:
- type: string
- description: "证件反面附件的 `attachments_id`"
- users_id_pic_b_url:
- type: string
- description: "根据 `users_id_pic_b -> tbl_attachments` 自动解析出的文件流链接"
- users_title_picture:
- type: string
- description: "资质附件的 `attachments_id`"
- users_title_picture_url:
- type: string
- description: "根据 `users_title_picture -> tbl_attachments` 自动解析出的文件流链接"
- openid:
- type: string
- description: "全平台统一身份标识;微信用户为微信 openid,平台用户为服务端生成的 GUID"
- company_id:
- type: string
- description: "公司业务 id,存储 `tbl_company.company_id`"
- users_parent_id:
- type: string
- description: "上级用户业务 id"
- users_promo_code:
- type: string
- description: "推广码"
- usergroups_id:
- type: string
- description: "用户组业务 id"
- company:
- $ref: '#/components/schemas/CompanyInfo'
- created:
- type: string
- description: "用户创建时间"
- updated:
- type: string
- description: "用户更新时间"
- example:
- pb_id: PocketBase记录主键id | string
- users_convers_id: 会话侧用户ID | string
- users_id: 用户业务ID | string
- users_idtype: 用户身份来源类型 | string
- users_id_number: 证件号 | string
- users_type: 用户类型 | string
- users_name: 用户姓名或昵称 | string
- users_status: 用户状态 | string
- users_rank_level: 用户星级数值 | number
- users_auth_type: 账户类型 | number
- users_phone: 手机号 | string
- users_phone_masked: 手机号脱敏值 | string
- users_level: 用户等级 | string
- users_level_name: 用户等级名称 | string
- users_tag: 用户标签 | string
- users_picture: 用户头像附件ID | string
- users_picture_url: 用户头像文件流链接 | string
- users_id_pic_a: 证件正面附件ID | string
- users_id_pic_a_url: 证件正面文件流链接 | string
- users_id_pic_b: 证件反面附件ID | string
- users_id_pic_b_url: 证件反面文件流链接 | string
- users_title_picture: 资质附件ID | string
- users_title_picture_url: 资质附件文件流链接 | string
- openid: 全平台统一身份标识 | string
- company_id: 公司业务id | string
- users_parent_id: 上级用户业务id | string
- users_promo_code: 推广码 | string
- usergroups_id: 用户组业务id | string
- company:
- 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
- created: 用户创建时间 | string
- updated: 用户更新时间 | string
- PocketBaseAuthResponse:
- type: object
- description: |
- 项目统一认证响应。
- 所有对外接口统一返回 `statusCode`、`errMsg`、`data`,认证成功时额外返回顶层 `token`。
- properties:
- statusCode:
- type: integer
- description: "业务状态码"
- example: 200
- errMsg:
- type: string
- description: "业务提示信息"
- example: 登录成功
- data:
- description: "业务响应数据"
- type: object
- properties:
- status:
- anyOf:
- - type: string
- enum: [register_success, login_success]
- - type: string
- is_info_complete:
- type: [boolean, string]
- user:
- $ref: '#/components/schemas/UserInfo'
- token:
- type: string
- description: "PocketBase 原生 auth token;仅认证类接口在成功时额外返回"
- example:
- statusCode: 200
- errMsg: 登录成功
- data:
- status: 登录或注册状态 | string
- is_info_complete: 资料是否完整 | boolean
- user:
- pb_id: PocketBase记录主键id | string
- users_id: 用户业务ID | string
- users_idtype: 用户身份来源类型 | string
- users_name: 用户姓名或昵称 | string
- users_phone: 手机号 | string
- users_phone_masked: 手机号脱敏值 | string
- users_status: 用户状态 | string
- users_rank_level: 用户星级数值 | number
- users_auth_type: 账户类型 | number
- users_type: 用户类型 | string
- users_picture: 用户头像附件ID | string
- users_picture_url: 用户头像文件流链接 | string
- users_id_pic_a: 证件正面附件ID | string
- users_id_pic_a_url: 证件正面文件流链接 | string
- users_id_pic_b: 证件反面附件ID | string
- users_id_pic_b_url: 证件反面文件流链接 | string
- users_title_picture: 资质附件ID | string
- users_title_picture_url: 资质附件文件流链接 | string
- openid: 全平台统一身份标识 | string
- company_id: 公司业务id | string
- users_parent_id: 上级用户业务id | string
- users_promo_code: 推广码 | string
- usergroups_id: 用户组业务id | string
- company:
- 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
- created: 用户创建时间 | string
- updated: 用户更新时间 | string
- token: PocketBase原生认证token | string
- WechatLoginRequest:
- type: object
- required: [users_wx_code]
- description: "微信小程序登录/注册请求体。"
- properties:
- users_wx_code:
- type: string
- description: "微信小程序登录临时凭证 code"
- example: 0a1b2c3d4e5f6g
- WechatProfileRequest:
- type: object
- description: "微信用户资料完善请求体。"
- properties:
- users_name:
- type: string
- description: "用户姓名 / 昵称"
- example: 张三
- users_phone_code:
- type: string
- description: "可选。若传入,服务端优先通过微信接口换取真实手机号并写入数据库"
- example: 2b7d9f2e3c4a5b6d7e8f
- users_phone:
- type: string
- description: "可选。未传 `users_phone_code` 时,可直接写入手机号"
- example: '13800138000'
- users_type:
- type: string
- description: "可选。用户类型;仅在传入非空值时更新"
- example: 服务商
- company_id:
- type: string
- description: "可选。公司业务 id;仅在传入非空值时更新"
- example: WX-COMPANY-10001
- users_tag:
- type: string
- description: "可选。用户标签;非空时才更新"
- example: 核心客户
- users_picture:
- type: string
- description: "可选。用户头像附件的 `attachments_id`"
- example: ATT-1743123456789-abc123
- users_id_pic_a:
- type: string
- description: "可选。证件正面附件的 `attachments_id`"
- users_id_pic_b:
- type: string
- description: "可选。证件反面附件的 `attachments_id`"
- users_title_picture:
- type: string
- description: "可选。资质附件的 `attachments_id`"
- WechatProfileResponseData:
- type: object
- properties:
- status:
- type: string
- enum: [update_success]
- user:
- $ref: '#/components/schemas/UserInfo'
- PlatformRegisterRequest:
- type: object
- required: [users_name, users_phone, password, passwordConfirm, users_picture]
- description: "平台用户注册请求体;注册成功后将生成 GUID 并写入统一 `openid` 字段。"
- properties:
- users_name:
- type: string
- description: "用户姓名 / 昵称"
- example: 张三
- users_phone:
- type: string
- description: "手机号"
- example: 13800138000
- password:
- type: string
- description: "PocketBase 认证密码"
- example: 12345678
- passwordConfirm:
- type: string
- example: 12345678
- users_picture:
- type: string
- description: "用户头像附件的 `attachments_id`"
- example: ATT-1743123456789-abc123
- users_id_number:
- description: "证件号"
- type: string
- users_id_pic_a:
- type: string
- description: "可选。证件正面附件的 `attachments_id`"
- users_id_pic_b:
- type: string
- description: "可选。证件反面附件的 `attachments_id`"
- users_title_picture:
- type: string
- description: "可选。资质附件的 `attachments_id`"
- users_level:
- description: "用户等级文本"
- type: string
- users_tag:
- description: "用户标签"
- type: string
- users_type:
- description: "用户类型"
- type: string
- company_id:
- description: "所属公司业务 ID"
- type: string
- users_parent_id:
- description: "上级用户业务 ID"
- type: string
- users_promo_code:
- description: "推广码"
- type: string
- usergroups_id:
- description: "用户组 / 角色 ID"
- type: string
- PlatformLoginRequest:
- type: object
- required: [login_account, password]
- description: "平台用户登录请求体;前端使用邮箱或手机号 + 密码提交,服务端内部转换为 PocketBase 原生 password auth。"
- properties:
- login_account:
- type: string
- description: "支持邮箱或手机号"
- example: admin@example.com
- password:
- type: string
- description: "PocketBase 认证密码"
- example: 12345678
- SystemRefreshTokenRequest:
- type: object
- description: |
- 系统刷新 token 请求体。
- `users_wx_code` 允许为空。
- 当 `Authorization` 对应 token 有效时,可不传或传空;
- 当 token 失效时,需提供 `users_wx_code` 走微信 code 重新签发流程。
- properties:
- users_wx_code:
- type:
- - string
- - 'null'
- description: "微信小程序登录临时凭证 code"
- example: 0a1b2c3d4e5f6g
- RefreshTokenData:
- type: object
- properties: {}
- DictionaryItem:
- type: object
- required: [enum, description, sortOrder]
- properties:
- enum:
- type: string
- example: enabled
- description:
- type: string
- example: 启用
- image:
- type: string
- description: "对应图片附件的 `attachments_id`,允许为空"
- example: ATT-1743037200000-abc123
- imageUrl:
- type: string
- description: "根据 `image -> tbl_attachments` 自动解析出的图片文件流链接"
- imageAttachment:
- anyOf:
- - $ref: '#/components/schemas/AttachmentRecord'
- - type: 'null'
- sortOrder:
- type: [integer, string]
- example: 1
- example:
- enum: 枚举值 | string
- description: 枚举描述 | string
- image: 图片附件ID | string
- imageUrl: 图片文件流链接 | string
- imageAttachment:
- attachments_id: 附件业务ID | string
- attachments_filename: 原始文件名 | string
- sortOrder: 排序值 | integer
- DictionaryRecord:
- type: object
- properties:
- pb_id:
- description: "PocketBase 记录主键 id"
- type: string
- system_dict_id:
- description: "字典业务 ID"
- type: string
- dict_name:
- description: "字典名称,当前按全局唯一维护"
- type: string
- dict_word_is_enabled:
- description: "字典是否启用"
- type: [boolean, string]
- dict_word_parent_id:
- description: "父级字典业务 ID"
- type: string
- dict_word_remark:
- description: "备注"
- type: string
- items:
- type: array
- items:
- $ref: '#/components/schemas/DictionaryItem'
- created:
- description: "记录创建时间"
- type: string
- updated:
- description: "记录更新时间"
- type: string
- example:
- pb_id: PocketBase记录主键id | string
- system_dict_id: 字典业务ID | string
- dict_name: 字典名称 | string
- dict_word_is_enabled: 字典是否启用 | boolean
- dict_word_parent_id: 父级字典业务ID | string
- dict_word_remark: 备注 | string
- items:
- - enum: UT1
- description: 枚举描述 | string
- image: 图片附件ID | string
- imageUrl: 图片文件流链接 | string
- sortOrder: 排序值 | integer
- created: 记录创建时间 | string
- updated: 记录更新时间 | string
- DictionaryListRequest:
- type: object
- properties:
- keyword:
- type: string
- description: "对 `dict_name` 的模糊搜索关键字"
- example: 状态
- DictionaryDetailRequest:
- type: object
- required: [dict_name]
- properties:
- dict_name:
- type: string
- description: "字典名称,当前按全局唯一维护"
- example: 用户状态
- DictionaryMutationRequest:
- type: object
- required: [dict_name, items]
- properties:
- original_dict_name:
- type: string
- description: "更新时用于定位原始记录;新增时可不传"
- example: 用户状态
- dict_name:
- type: string
- description: "字典名称,当前按全局唯一维护"
- example: 用户状态
- dict_word_is_enabled:
- type: boolean
- description: "字典是否启用"
- example: true
- dict_word_parent_id:
- type: string
- description: "父级字典业务 ID"
- example: ''
- dict_word_remark:
- type: string
- description: "备注"
- example: 系统状态字典
- items:
- type: array
- minItems: 1
- description: "每项会分别序列化写入 `dict_word_enum`、`dict_word_description`、`dict_word_image`、`dict_word_sort_order`"
- items:
- $ref: '#/components/schemas/DictionaryItem'
- DictionaryDeleteRequest:
- type: object
- required: [dict_name]
- properties:
- dict_name:
- type: string
- description: "字典名称,当前按全局唯一维护"
- example: 用户状态
- AttachmentRecord:
- type: object
- properties:
- pb_id:
- description: "PocketBase 记录主键 id"
- type: string
- attachments_id:
- description: "附件业务 ID"
- type: string
- attachments_link:
- type: string
- description: "PocketBase 实际存储的文件名"
- attachments_url:
- type: string
- description: "附件文件流访问链接"
- attachments_download_url:
- type: string
- description: "附件下载链接"
- attachments_filename:
- description: "原始文件名"
- type: string
- attachments_filetype:
- description: "文件类型 / MIME"
- type: string
- attachments_size:
- description: "文件大小"
- type: [number, string]
- attachments_owner:
- description: "上传者业务标识"
- type: string
- attachments_md5:
- description: "文件 MD5"
- type: string
- attachments_ocr:
- description: "OCR 识别结果"
- type: string
- attachments_status:
- description: "附件状态"
- type: string
- attachments_remark:
- description: "备注"
- type: string
- created:
- description: "记录创建时间"
- type: string
- updated:
- description: "记录更新时间"
- type: string
- example:
- pb_id: PocketBase记录主键id | string
- attachments_id: 附件业务ID | string
- attachments_link: PocketBase实际存储文件名 | string
- attachments_url: 附件文件流访问链接 | string
- attachments_download_url: 附件下载链接 | 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
- created: 记录创建时间 | string
- updated: 记录更新时间 | string
- AttachmentListRequest:
- type: object
- properties:
- keyword:
- type: string
- description: "对 `attachments_id`、`attachments_filename` 的模糊搜索关键字"
- example: 手册
- status:
- type: string
- description: "按附件状态过滤"
- example: active
- AttachmentDetailRequest:
- type: object
- required: [attachments_id]
- properties:
- attachments_id:
- type: string
- description: "附件业务 ID"
- example: ATT-1743037200000-abc123
- AttachmentUploadRequest:
- type: object
- required: [attachments_link]
- properties:
- attachments_link:
- type: string
- format: binary
- description: "要上传到 `tbl_attachments` 的单个文件"
- attachments_filename:
- type: string
- description: "原始文件名;不传时可由前端直接使用文件名"
- attachments_filetype:
- type: string
- description: "文件 MIME 类型"
- attachments_size:
- type: number
- description: "文件大小"
- attachments_md5:
- description: "文件 MD5"
- type: string
- attachments_ocr:
- description: "OCR 识别结果"
- type: string
- attachments_status:
- type: string
- description: "附件状态"
- example: active
- attachments_remark:
- description: "备注"
- type: string
- DocumentRecord:
- type: object
- properties:
- pb_id:
- description: "PocketBase 记录主键 id"
- type: string
- document_id:
- description: "文档业务 ID"
- type: string
- document_create:
- type: string
- description: "文档创建时间,由数据库自动生成"
- document_effect_date:
- description: "文档生效日期"
- type: string
- document_expiry_date:
- description: "文档到期日期"
- type: string
- document_type:
- type: string
- description: "多选时按 `system_dict_id@dict_word_enum|...` 保存"
- document_title:
- description: "文档标题"
- type: string
- document_subtitle:
- description: "文档副标题"
- type: string
- document_summary:
- description: "文档摘要"
- type: string
- document_content:
- description: "正文内容,保存 Markdown"
- type: string
- document_image:
- type: string
- description: "关联多个 `attachments_id`,底层使用 `|` 分隔保存"
- document_image_ids:
- type: array
- description: "`document_image` 解析后的附件 id 列表"
- items:
- type: string
- document_image_urls:
- type: array
- description: "根据 `document_image -> tbl_attachments` 自动解析出的图片文件流链接列表"
- items:
- type: string
- document_image_url:
- type: string
- description: "兼容字段,返回第一张图片的文件流链接"
- document_image_attachments:
- type: array
- items:
- $ref: '#/components/schemas/AttachmentRecord'
- document_image_attachment:
- anyOf:
- - $ref: '#/components/schemas/AttachmentRecord'
- - type: 'null'
- document_video:
- type: string
- description: "关联多个 `attachments_id`,底层使用 `|` 分隔保存"
- document_video_ids:
- type: array
- description: "`document_video` 解析后的附件 id 列表"
- items:
- type: string
- document_video_urls:
- type: array
- description: "根据 `document_video -> tbl_attachments` 自动解析出的视频文件流链接列表"
- items:
- type: string
- document_video_url:
- type: string
- description: "兼容字段,返回第一个视频的文件流链接"
- document_video_attachments:
- type: array
- items:
- $ref: '#/components/schemas/AttachmentRecord'
- document_video_attachment:
- anyOf:
- - $ref: '#/components/schemas/AttachmentRecord'
- - type: 'null'
- document_file:
- type: string
- description: "关联多个 `attachments_id`,底层使用 `|` 分隔保存"
- document_file_ids:
- type: array
- description: "`document_file` 解析后的附件 id 列表"
- items:
- type: string
- document_file_urls:
- type: array
- description: "根据 `document_file -> tbl_attachments` 自动解析出的文件流链接列表"
- items:
- type: string
- document_file_url:
- type: string
- description: "兼容字段,返回第一个文件的文件流链接"
- document_file_attachments:
- type: array
- items:
- $ref: '#/components/schemas/AttachmentRecord'
- document_file_attachment:
- anyOf:
- - $ref: '#/components/schemas/AttachmentRecord'
- - type: 'null'
- document_owner:
- type: string
- description: "上传者 openid"
- document_relation_model:
- description: "关联机型 / 模型标识"
- type: string
- document_keywords:
- type: string
- description: "固定字典多选字段,使用 `|` 分隔"
- document_share_count:
- description: "分享次数"
- type: [number, string]
- document_download_count:
- description: "下载次数"
- type: [number, string]
- document_favorite_count:
- description: "收藏次数"
- type: [number, string]
- document_status:
- type: string
- description: "文档状态,仅允许 `有效` 或 `过期`,由系统根据生效日期和到期日期自动计算;当两者都为空时默认 `有效`"
- document_embedding_status:
- description: "文档嵌入状态"
- type: string
- document_embedding_error:
- description: "文档嵌入错误原因"
- type: string
- document_embedding_lasttime:
- description: "最后一次嵌入更新时间"
- type: string
- document_vector_version:
- description: "向量版本号 / 模型名称"
- type: string
- document_product_categories:
- type: string
- description: "固定字典多选字段,使用 `|` 分隔"
- document_application_scenarios:
- type: string
- description: "固定字典多选字段,使用 `|` 分隔"
- document_hotel_type:
- type: string
- description: "固定字典多选字段,使用 `|` 分隔"
- document_remark:
- description: "备注"
- type: string
- created:
- description: "记录创建时间"
- type: string
- updated:
- description: "记录更新时间"
- type: string
- example:
- pb_id: PocketBase记录主键id | string
- document_id: 文档业务ID | string
- document_create: 文档创建时间,由数据库自动生成 | string
- document_effect_date: 文档生效日期 | string
- document_expiry_date: 文档到期日期 | string
- document_type: 文档类型,按system_dict_id@dict_word_enum保存 | string
- document_title: 文档标题 | string
- document_subtitle: 文档副标题 | string
- document_summary: 文档摘要 | string
- document_content: 正文内容Markdown | string
- document_image: 图片附件ID列表,使用竖线分隔 | string
- document_image_ids:
- - 图片附件ID | string
- document_image_urls:
- - 图片文件流链接 | string
- document_image_url: 第一张图片文件流链接 | string
- document_video: 视频附件ID列表,使用竖线分隔 | string
- document_video_ids:
- - 视频附件ID | string
- document_video_urls:
- - 视频文件流链接 | string
- document_video_url: 第一个视频文件流链接 | string
- document_file: 文件附件ID列表,使用竖线分隔 | string
- document_file_ids:
- - 文件附件ID | string
- document_file_urls:
- - 文件流链接 | string
- document_file_url: 第一个文件流链接 | 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_status: 文档状态 | string
- 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
- created: 记录创建时间 | string
- updated: 记录更新时间 | string
- DocumentListRequest:
- type: object
- properties:
- keyword:
- type: string
- description: "对 `document_id`、`document_title`、`document_subtitle`、`document_summary`、`document_keywords` 的模糊搜索关键字"
- example: 安装
- status:
- type: string
- example: active
- document_type:
- type: string
- description: "支持按存储值过滤;多选时格式为 `system_dict_id@dict_word_enum|...`"
- example: 说明书
- DocumentDetailRequest:
- type: object
- required: [document_id]
- properties:
- document_id:
- type: string
- description: "文档业务 ID"
- example: DOC-1743037200000-abc123
- DocumentMutationRequest:
- type: object
- required: [document_title, document_type]
- properties:
- document_id:
- type: string
- description: "创建时可不传,由服务端自动生成;更新时必填"
- example: DOC-1743037200000-abc123
- document_effect_date:
- type: string
- description: "支持 `YYYY-MM-DD` 或 PocketBase 可识别日期时间字符串"
- example: 2026-03-27
- document_expiry_date:
- type: string
- description: "支持 `YYYY-MM-DD` 或 PocketBase 可识别日期时间字符串"
- example: 2027-03-27
- document_type:
- type: string
- description: "必填;前端显示为字典项描述,存库时按 `system_dict_id@dict_word_enum|...` 保存"
- document_title:
- description: "文档标题"
- type: string
- document_subtitle:
- description: "文档副标题"
- type: string
- document_summary:
- description: "文档摘要"
- type: string
- document_content:
- description: "正文内容,保存 Markdown"
- type: string
- document_image:
- oneOf:
- - type: string
- description: "多个图片附件 id 使用 `|` 分隔"
- - type: array
- items:
- type: string
- description: "图片附件 id 列表;支持数组或 `|` 分隔字符串"
- document_video:
- oneOf:
- - type: string
- description: "多个视频附件 id 使用 `|` 分隔"
- - type: array
- items:
- type: string
- description: "视频附件 id 列表;支持数组或 `|` 分隔字符串"
- document_file:
- oneOf:
- - type: string
- description: "多个文件附件 id 使用 `|` 分隔"
- - type: array
- items:
- type: string
- description: "文件附件 id 列表;支持数组或 `|` 分隔字符串"
- document_relation_model:
- description: "关联机型 / 模型标识"
- type: string
- document_keywords:
- type: string
- description: "从 `文档-关键词` 字典多选后使用 `|` 分隔保存"
- document_share_count:
- description: "分享次数"
- type: number
- document_download_count:
- description: "下载次数"
- type: number
- document_favorite_count:
- description: "收藏次数"
- type: number
- document_status:
- type: string
- description: "文档状态,仅允许 `有效` 或 `过期`,由系统根据生效日期和到期日期自动计算;当两者都为空时默认 `有效`"
- document_embedding_status:
- description: "文档嵌入状态"
- type: string
- document_embedding_error:
- description: "文档嵌入错误原因"
- type: string
- document_embedding_lasttime:
- description: "最后一次嵌入更新时间"
- type: string
- document_vector_version:
- description: "向量版本号 / 模型名称"
- type: string
- document_product_categories:
- type: string
- description: "从 `文档-产品关联文档` 字典多选后使用 `|` 分隔保存"
- document_application_scenarios:
- type: string
- description: "从 `文档-筛选依据` 字典多选后使用 `|` 分隔保存"
- document_hotel_type:
- type: string
- description: "从 `文档-适用场景` 字典多选后使用 `|` 分隔保存"
- document_remark:
- description: "备注"
- type: string
- DocumentDeleteRequest:
- type: object
- required: [document_id]
- properties:
- document_id:
- type: string
- description: "文档业务 ID"
- example: DOC-1743037200000-abc123
- DocumentHistoryRecord:
- type: object
- properties:
- pb_id:
- description: "PocketBase 记录主键 id"
- type: string
- doh_id:
- description: "文档操作历史业务 ID"
- type: string
- doh_document_id:
- description: "关联文档业务 ID"
- type: string
- doh_operation_type:
- description: "操作类型"
- type: string
- doh_user_id:
- description: "操作人业务 ID"
- type: string
- doh_current_count:
- description: "本次操作对应次数"
- type: [number, string]
- doh_remark:
- description: "备注"
- type: string
- created:
- description: "记录创建时间"
- type: string
- updated:
- description: "记录更新时间"
- type: string
- example:
- pb_id: PocketBase记录主键id | string
- doh_id: 文档操作历史业务ID | string
- doh_document_id: 关联文档业务ID | string
- doh_operation_type: 操作类型 | string
- doh_user_id: 操作人业务ID | string
- doh_current_count: 本次操作对应次数 | number
- doh_remark: 备注 | string
- created: 记录创建时间 | string
- updated: 记录更新时间 | string
- DocumentHistoryListRequest:
- type: object
- properties:
- document_id:
- type: string
- description: "可选;传入时仅查询指定文档的操作历史"
- example: DOC-1743037200000-abc123
-paths:
- /pb/api/system/test-helloworld:
- post:
- tags: [系统]
- summary: HelloWorld 测试接口
- responses:
- '200':
- description: "成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- $ref: '#/components/schemas/HelloWorldData'
- /pb/api/system/health:
- post:
- tags: [系统]
- summary: 健康检查
- responses:
- '200':
- description: "成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- $ref: '#/components/schemas/HealthData'
- /pb/api/system/users-count:
- post:
- tags: [系统]
- summary: 查询用户总数
- description: "统计 `tbl_auth_users` 集合中的记录总数,并返回一个数值。"
- responses:
- '200':
- description: "成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- $ref: '#/components/schemas/UsersCountData'
- /pb/api/platform/register:
- post:
- tags: [平台认证]
- summary: 平台用户注册
- description: |
- 创建平台用户 auth record。
- 服务端会自动生成 GUID 并写入统一身份字段 `openid`,同时写入 `users_idtype = ManagePlatform`。
- 前端以 `users_phone + password/passwordConfirm` 注册,但服务端仍会创建 PocketBase 原生 auth 用户。
- 首次注册创建时,`users_level` 默认保持为空,不自动写入会员等级。
- 注册成功后直接返回 PocketBase 原生 auth token。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/PlatformRegisterRequest'
- responses:
- '200':
- description: "注册成功"
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/PocketBaseAuthResponse'
- '400':
- description: "参数错误或手机号已存在"
- '500':
- description: "GUID 生成失败、auth identity 缺失或保存用户失败"
- '415':
- description: "请求体必须为 application/json"
- '429':
- description: "重复请求过于频繁"
- /pb/api/platform/login:
- post:
- tags: [平台认证]
- summary: 平台用户登录
- description: |
- 前端使用平台注册时保存的 `邮箱或手机号 + password` 登录。
- 仅允许 `users_idtype = ManagePlatform` 的用户通过该接口登录。
- 服务端会根据 `login_account` 自动判断邮箱或手机号,并定位平台用户,再使用该用户的 PocketBase 原生 identity(当前为 `email`)执行原生 password auth。
- 返回体中的 `user.users_level_name` 为服务端按“数据-会员等级”字典实时解析后的等级名称。
- 登录成功后直接返回 PocketBase 原生 auth token。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/PlatformLoginRequest'
- example:
- login_account: 13509214696
- password: Momo123456
- responses:
- '200':
- description: "登录成功"
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/PocketBaseAuthResponse'
- example:
- statusCode: 200
- errMsg: 登录成功
- data:
- status: login_success
- is_info_complete: false
- user:
- pb_id: vtukf6agem2xbcv
- users_id: ''
- users_idtype: ManagePlatform
- users_name: momo
- users_phone: '13509214696'
- users_phone_masked: '135****4696'
- users_status: ''
- users_rank_level: 0
- users_auth_type: 0
- users_type: ''
- users_picture: ''
- openid: app_momo
- company_id: ''
- users_parent_id: ''
- users_promo_code: ''
- usergroups_id: ''
- company: null
- created: ''
- updated: ''
- token: eyJhbGciOi...
- '400':
- description: "参数错误、密码错误或用户类型不匹配"
- '404':
- description: "平台用户不存在"
- '500':
- description: "平台用户缺少原生登录 identity 或服务端内部错误"
- '415':
- description: "请求体必须为 application/json"
- '429':
- description: "重复请求过于频繁"
- /pb/api/dictionary/list:
- post:
- tags: [字典管理]
- summary: 查询字典列表
- description: |
- 公开读接口,无需 token。
- 支持按 `dict_name` 模糊搜索,返回字典全量信息,并将三个聚合字段组装为 `items`。
- requestBody:
- required: false
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DictionaryListRequest'
- responses:
- '200':
- description: "查询成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- type: object
- properties:
- items:
- type: array
- items:
- $ref: '#/components/schemas/DictionaryRecord'
- '415':
- description: "请求体必须为 application/json"
- /pb/api/dictionary/detail:
- post:
- tags: [字典管理]
- summary: 查询指定字典
- description: |
- 公开读接口,无需 token。
- 按唯一键 `dict_name` 查询单条字典,并返回组装后的 `items`。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DictionaryDetailRequest'
- responses:
- '200':
- description: "查询成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- $ref: '#/components/schemas/DictionaryRecord'
- '400':
- description: "参数错误"
- '404':
- description: "未找到对应字典"
- '415':
- description: "请求体必须为 application/json"
- /pb/api/dictionary/create:
- post:
- tags: [字典管理]
- summary: 新增字典
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- `system_dict_id` 由服务端自动生成;`dict_name` 必须唯一;
- `items` 会分别序列化写入 `dict_word_enum`、`dict_word_description`、`dict_word_image`、`dict_word_sort_order` 四个字段。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DictionaryMutationRequest'
- responses:
- '200':
- description: "新增成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- $ref: '#/components/schemas/DictionaryRecord'
- '400':
- description: "参数错误或 dict_name 已存在"
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '415':
- description: "请求体必须为 application/json"
- '429':
- description: "重复请求过于频繁"
- /pb/api/dictionary/update:
- post:
- tags: [字典管理]
- summary: 修改字典
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- 根据 `original_dict_name`(未传时回退为 `dict_name`)定位原记录并更新。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DictionaryMutationRequest'
- responses:
- '200':
- description: "修改成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- $ref: '#/components/schemas/DictionaryRecord'
- '400':
- description: "参数错误或 dict_name 冲突"
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '404':
- description: "未找到待修改字典"
- '415':
- description: "请求体必须为 application/json"
- '429':
- description: "重复请求过于频繁"
- /pb/api/dictionary/delete:
- post:
- tags: [字典管理]
- summary: 删除字典
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- 按 `dict_name` 真删除对应记录。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DictionaryDeleteRequest'
- responses:
- '200':
- description: "删除成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- type: object
- properties:
- dict_name:
- description: "字典名称,当前按全局唯一维护"
- type: string
- '400':
- description: "参数错误或删除失败"
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '404':
- description: "未找到待删除字典"
- '415':
- description: "请求体必须为 application/json"
- '429':
- description: "重复请求过于频繁"
- /pb/api/attachment/list:
- post:
- tags: [附件管理]
- summary: 查询附件列表
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- 支持按 `attachments_id`、`attachments_filename` 模糊搜索,并可按 `attachments_status` 过滤。
- requestBody:
- required: false
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/AttachmentListRequest'
- responses:
- '200':
- description: "查询成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- type: object
- properties:
- items:
- type: array
- items:
- $ref: '#/components/schemas/AttachmentRecord'
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '415':
- description: "请求体必须为 application/json"
- /pb/api/attachment/detail:
- post:
- tags: [附件管理]
- summary: 查询附件详情
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- 按 `attachments_id` 查询单条附件,并返回 PocketBase 文件流链接与下载链接。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/AttachmentDetailRequest'
- responses:
- '200':
- description: "查询成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- $ref: '#/components/schemas/AttachmentRecord'
- '400':
- description: "参数错误"
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '404':
- description: "未找到对应附件"
- '415':
- description: "请求体必须为 application/json"
- /pb/api/attachment/upload:
- post:
- tags: [附件管理]
- summary: 上传附件
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- 使用 `multipart/form-data` 上传单个文件到 `tbl_attachments`,服务端会自动生成 `attachments_id`,
- 并返回可直接访问的 PocketBase 文件流链接。
- requestBody:
- required: true
- content:
- multipart/form-data:
- schema:
- $ref: '#/components/schemas/AttachmentUploadRequest'
- responses:
- '200':
- description: "上传成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- $ref: '#/components/schemas/AttachmentRecord'
- '400':
- description: "参数错误、缺少文件或附件保存失败"
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- /pb/api/attachment/delete:
- post:
- tags: [附件管理]
- summary: 删除附件
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- 按 `attachments_id` 真删除附件;若附件已被 `tbl_document.document_image`、`document_video` 或 `document_file` 引用,则拒绝删除。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/AttachmentDetailRequest'
- responses:
- '200':
- description: "删除成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- type: object
- properties:
- attachments_id:
- description: "附件业务 ID"
- type: string
- '400':
- description: "参数错误、附件已被文档引用或删除失败"
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '404':
- description: "未找到待删除附件"
- '415':
- description: "请求体必须为 application/json"
- '429':
- description: "重复请求过于频繁"
- /pb/api/document/list:
- post:
- tags: [文档管理]
- summary: 查询文档列表
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- 支持按标题、摘要、关键词等字段模糊搜索,并可按 `document_status`、`document_type` 过滤。
- 返回结果会自动根据 `document_image`、`document_video`、`document_file` 中的多个 `attachments_id` 关联 `tbl_attachments`,
- 额外补充 `document_image_urls`、`document_video_urls`、`document_file_urls` 以及对应附件对象数组。
- requestBody:
- required: false
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DocumentListRequest'
- responses:
- '200':
- description: "查询成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- type: object
- properties:
- items:
- type: array
- items:
- $ref: '#/components/schemas/DocumentRecord'
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '415':
- description: "请求体必须为 application/json"
- /pb/api/document/detail:
- post:
- tags: [文档管理]
- summary: 查询文档详情
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- 按 `document_id` 查询单条文档,并返回与附件表联动解析后的多文件流链接。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DocumentDetailRequest'
- responses:
- '200':
- description: "查询成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- $ref: '#/components/schemas/DocumentRecord'
- '400':
- description: "参数错误"
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '404':
- description: "未找到对应文档"
- '415':
- description: "请求体必须为 application/json"
- /pb/api/document/create:
- post:
- tags: [文档管理]
- summary: 新增文档
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- `document_id` 可选;未传时服务端自动生成。
- `document_title`、`document_type` 为必填;其余字段均允许为空。
- `document_image`、`document_video`、`document_file` 支持传入多个已存在于 `tbl_attachments` 的 `attachments_id`。
- 成功后会同步写入一条 `tbl_document_operation_history`,操作类型为 `create`。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DocumentMutationRequest'
- responses:
- '200':
- description: "新增成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- $ref: '#/components/schemas/DocumentRecord'
- '400':
- description: "参数错误、附件不存在或文档创建失败"
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '415':
- description: "请求体必须为 application/json"
- '429':
- description: "重复请求过于频繁"
- /pb/api/document/update:
- post:
- tags: [文档管理]
- summary: 修改文档
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- 按 `document_id` 定位现有文档并更新。
- `document_title`、`document_type` 为必填;其余字段均允许为空。
- 若传入 `document_image`、`document_video`、`document_file`,则支持多个 `attachments_id`,并会逐一校验是否存在。
- 成功后会同步写入一条 `tbl_document_operation_history`,操作类型为 `update`。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DocumentMutationRequest'
- responses:
- '200':
- description: "修改成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- $ref: '#/components/schemas/DocumentRecord'
- '400':
- description: "参数错误、附件不存在或修改失败"
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '404':
- description: "未找到待修改文档"
- '415':
- description: "请求体必须为 application/json"
- '429':
- description: "重复请求过于频繁"
- /pb/api/document/delete:
- post:
- tags: [文档管理]
- summary: 删除文档
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- 按 `document_id` 真删除文档,并在删除前同步写入一条 `tbl_document_operation_history`,操作类型为 `delete`。
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DocumentDeleteRequest'
- responses:
- '200':
- description: "删除成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- type: object
- properties:
- document_id:
- description: "文档业务 ID"
- type: string
- '400':
- description: "参数错误或删除失败"
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '404':
- description: "未找到待删除文档"
- '415':
- description: "请求体必须为 application/json"
- '429':
- description: "重复请求过于频繁"
- /pb/api/document-history/list:
- post:
- tags: [文档历史]
- summary: 查询文档操作历史
- description: |
- 仅允许 `ManagePlatform` 用户访问。
- 若 body 传入 `document_id`,则仅查询该文档的历史;否则返回全部文档操作历史,按创建时间倒序排列。
- requestBody:
- required: false
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/DocumentHistoryListRequest'
- responses:
- '200':
- description: "查询成功"
- content:
- application/json:
- schema:
- description: "业务响应数据"
- type: object
- properties:
- items:
- type: array
- items:
- $ref: '#/components/schemas/DocumentHistoryRecord'
- '401':
- description: "token 无效或已过期"
- '403':
- description: "非 ManagePlatform 用户无权访问"
- '415':
- description: "请求体必须为 application/json"
diff --git a/pocket-base/spec/openapi-manage/cart.yaml b/pocket-base/spec/openapi-manage/cart.yaml
index 49a82ab..966410f 100644
--- a/pocket-base/spec/openapi-manage/cart.yaml
+++ b/pocket-base/spec/openapi-manage/cart.yaml
@@ -1,5 +1,17 @@
+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:
- cartList:
+ /pb/api/cart/list:
post:
tags: [购物车]
summary: 查询购物车列表
@@ -31,7 +43,7 @@ paths:
description: 请求体必须为 application/json
'500':
description: 服务端错误
- cartDetail:
+ /pb/api/cart/detail:
post:
tags: [购物车]
summary: 查询购物车详情
@@ -65,7 +77,7 @@ paths:
description: 请求体必须为 application/json
'500':
description: 服务端错误
- cartCreate:
+ /pb/api/cart/create:
post:
tags: [购物车]
summary: 新增购物车记录
@@ -102,7 +114,7 @@ paths:
description: 重复请求过于频繁
'500':
description: 服务端错误
- cartUpdate:
+ /pb/api/cart/update:
post:
tags: [购物车]
summary: 修改购物车记录
@@ -144,7 +156,7 @@ paths:
description: 重复请求过于频繁
'500':
description: 服务端错误
- cartDelete:
+ /pb/api/cart/delete:
post:
tags: [购物车]
summary: 删除购物车记录
@@ -233,6 +245,9 @@ components:
product_modelnumber:
type: string
description: 产品型号
+ product_barcode:
+ type: string
+ description: 产品料号
product_basic_price:
type: [integer, number, 'null']
description: 产品基础价格
@@ -253,6 +268,7 @@ components:
is_delete: 删除标记|integer
product_name: 产品名称|string
product_modelnumber: 产品型号|string
+ product_barcode: 产品料号|string
product_basic_price: 产品基础价格|integer
CartListRequest:
type: object
@@ -360,6 +376,10 @@ components:
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:
diff --git a/pocket-base/spec/openapi-manage/openapi.yaml b/pocket-base/spec/openapi-manage/openapi.yaml
index a55d92e..402f96a 100644
--- a/pocket-base/spec/openapi-manage/openapi.yaml
+++ b/pocket-base/spec/openapi-manage/openapi.yaml
@@ -6,6 +6,8 @@ info:
面向管理端与自定义 hooks 的接口文档。
本目录仅收敛自定义 hooks API,不包含 PocketBase 原生 records API。
+ 本文件为目录索引,支持单文件独立导入,不依赖其他 YAML。
+
文档约定:
- 不单独配置鉴权组件;如接口需要登录,请直接在说明中关注 `Authorization: Bearer |