feat: 添加购物车相关迁移和索引功能
- 在 package.json 中添加迁移脚本 `migrate:cart-active-unique-index`。 - 修改 `pocketbase.cart-order.js` 文件,更新 `cart_id` 和 `cart_product_id` 字段的必填属性,并添加唯一索引 `idx_tbl_cart_owner_product_active_unique`。 - 在 `pocketbase.ensure-cart-order-autogen-id.js` 中,调整 `cart_id` 字段的必填属性为可选,并确保 `order_id` 字段为必填。 - 在 `pocketbase.product-list.js` 中,新增 `prod_list_barcode` 字段。 - 新增 `make-openapi-standalone.cjs` 脚本,用于处理 OpenAPI 文档。 - 新增 `pocketbase.cart-active-unique-index.js` 脚本,处理购物车的唯一索引和去重逻辑。
This commit is contained in:
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.`);
|
||||
@@ -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 实际结构为准。
|
||||
|
||||
@@ -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 字段清单里单独声明。
|
||||
|
||||
@@ -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 为必填项')
|
||||
|
||||
@@ -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 || ''),
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 必须为正数')
|
||||
|
||||
@@ -574,12 +574,12 @@
|
||||
}
|
||||
|
||||
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>'
|
||||
+ items.map(function (item) {
|
||||
return '<tr>'
|
||||
+ '<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_at_price || 0) + '</td>'
|
||||
+ '<td data-label="状态">' + escapeHtml(item.cart_status || '-') + '</td>'
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
|
||||
<section class="panel">
|
||||
<div class="toolbar">
|
||||
<input id="keywordInput" placeholder="按名称/型号/标签搜索" />
|
||||
<input id="keywordInput" placeholder="按名称/型号/料号/标签搜索" />
|
||||
<select id="statusFilter"><option value="">全部状态</option></select>
|
||||
<select id="categoryFilter"><option value="">全部分类</option></select>
|
||||
<button class="btn btn-secondary" id="searchBtn" type="button">查询</button>
|
||||
@@ -135,14 +135,20 @@
|
||||
<div class="muted" id="editorMode">当前模式:新建</div>
|
||||
</div>
|
||||
<div class="grid" style="margin-top:14px;">
|
||||
<div>
|
||||
<div class="full quarter-row">
|
||||
<div class="quarter-col-2">
|
||||
<label for="prodListName">产品名称</label>
|
||||
<input id="prodListName" placeholder="必填" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="quarter-col">
|
||||
<label for="prodListBarcode">产品料号</label>
|
||||
<input id="prodListBarcode" placeholder="可选" />
|
||||
</div>
|
||||
<div class="quarter-col">
|
||||
<label for="prodListModel">产品型号</label>
|
||||
<input id="prodListModel" placeholder="可选" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>产品分类(必填,单选)</label>
|
||||
<div class="option-box">
|
||||
@@ -344,6 +350,10 @@
|
||||
<label for="copyModelInput">新产品型号</label>
|
||||
<input id="copyModelInput" placeholder="请输入新产品型号" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="copyBarcodeInput">新产品料号</label>
|
||||
<input id="copyBarcodeInput" placeholder="请输入新产品料号" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-light" id="copyCancelBtn" type="button">取消</button>
|
||||
@@ -388,6 +398,7 @@
|
||||
const copyModalEl = document.getElementById('copyModal')
|
||||
const copyNameInputEl = document.getElementById('copyNameInput')
|
||||
const copyModelInputEl = document.getElementById('copyModelInput')
|
||||
const copyBarcodeInputEl = document.getElementById('copyBarcodeInput')
|
||||
const loadingMaskEl = document.getElementById('loadingMask')
|
||||
const loadingTextEl = document.getElementById('loadingText')
|
||||
const iconPreviewListEl = document.getElementById('iconPreviewList')
|
||||
@@ -401,6 +412,7 @@
|
||||
keyword: document.getElementById('keywordInput'),
|
||||
name: document.getElementById('prodListName'),
|
||||
model: document.getElementById('prodListModel'),
|
||||
barcode: document.getElementById('prodListBarcode'),
|
||||
basicPrice: document.getElementById('prodListBasicPrice'),
|
||||
feature: document.getElementById('prodListFeature'),
|
||||
sort: document.getElementById('prodListSort'),
|
||||
@@ -611,6 +623,7 @@
|
||||
prod_list_id: normalizeText(item && item.prod_list_id),
|
||||
prod_list_name: normalizeText(item && item.prod_list_name),
|
||||
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_description: normalizeText(item && item.prod_list_description),
|
||||
prod_list_feature: normalizeText(item && item.prod_list_feature),
|
||||
@@ -683,6 +696,7 @@
|
||||
|| normalizeText(item.prod_list_id).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_barcode).toLowerCase().indexOf(keyword) !== -1
|
||||
|| normalizeText(item.prod_list_tags).toLowerCase().indexOf(keyword) !== -1
|
||||
const matchedStatus = !status || normalizeText(item.prod_list_status) === status
|
||||
const matchedCategory = !category || normalizeText(item.prod_list_category) === category
|
||||
@@ -1741,6 +1755,7 @@
|
||||
function resetForm() {
|
||||
fields.name.value = ''
|
||||
fields.model.value = ''
|
||||
fields.barcode.value = ''
|
||||
fields.basicPrice.value = ''
|
||||
fields.feature.value = ''
|
||||
fields.sort.value = '0'
|
||||
@@ -1791,6 +1806,7 @@
|
||||
function fillFormFromItem(item) {
|
||||
fields.name.value = item.prod_list_name || ''
|
||||
fields.model.value = item.prod_list_modelnumber || ''
|
||||
fields.barcode.value = item.prod_list_barcode || ''
|
||||
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.feature.value = item.prod_list_feature || ''
|
||||
@@ -1849,7 +1865,7 @@
|
||||
|
||||
tableBodyEl.innerHTML = state.list.map(function (item) {
|
||||
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_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>'
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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,23 +89,21 @@
|
||||
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; }
|
||||
}
|
||||
</style>
|
||||
{{ template "theme_head" . }}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<section class="panel">
|
||||
<div class="header-row">
|
||||
<div>
|
||||
<section class="topbar">
|
||||
<h1>方案预设管理</h1>
|
||||
<div class="tiny" style="margin-top:8px;">页面负责管理 `tbl_scheme_template` 与 `tbl_scheme`。模板图标上传会写入附件表,再把附件 ID 回填到模板记录。</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="actions" style="margin-top:12px;">
|
||||
<a class="btn btn-light" href="/pb/manage">返回主页</a>
|
||||
<button class="btn btn-light" id="refreshAllBtn" type="button">刷新全部</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary" style="margin-top:16px;">
|
||||
<div class="summary-card">
|
||||
<strong id="templateCount">0</strong>
|
||||
@@ -113,7 +125,7 @@
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>方案模板</h2>
|
||||
<div class="tiny" style="margin-top:6px;">录入和管理 `tbl_scheme_template`。`scheme_template_product_list` 使用 JSON 数组编辑。</div>
|
||||
<div class="tiny" style="margin-top:6px;">录入和管理 `tbl_scheme_template`。产品清单支持按分类挑选产品,并自动同步为 JSON 数组。</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn btn-secondary" id="newTemplateBtn" type="button">新建模板</button>
|
||||
@@ -202,10 +214,39 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="full">
|
||||
<label>产品清单构建器</label>
|
||||
<div class="product-picker-box">
|
||||
<div class="product-picker-grid">
|
||||
<div>
|
||||
<label for="templateProductCategoryFilter">产品分类</label>
|
||||
<select id="templateProductCategoryFilter">
|
||||
<option value="">请先加载分类</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="templateProductSelect">选择产品</label>
|
||||
<select id="templateProductSelect">
|
||||
<option value="">请先选择分类</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="product-picker-actions">
|
||||
<button class="btn btn-secondary" id="addTemplateProductBtn" type="button">加入产品</button>
|
||||
</div>
|
||||
<div class="product-picker-actions">
|
||||
<button class="btn btn-light" id="reloadTemplateProductsBtn" type="button">刷新产品源</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">选择逻辑与产品管理页一致:先选分类,再选产品。加入后会把该产品整行数据写入下方 JSON 数组。</div>
|
||||
<div class="product-list-summary" id="templateProductSummary">
|
||||
<div class="product-list-empty">暂无已选产品,请先选择分类和产品后加入。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="full">
|
||||
<label for="templateProductList">产品清单 JSON</label>
|
||||
<textarea id="templateProductList" placeholder='示例:[{"product_id":"PROD-001","qty":2,"note":"主卧"}]'></textarea>
|
||||
<div class="hint">必须是数组,每项至少包含 `product_id` 与正数 `qty`。</div>
|
||||
<textarea id="templateProductList" placeholder='示例:[{"prod_list_id":"PROD-001","prod_list_name":"智能面板","prod_list_barcode":"PLB-001","prod_list_category":"客控主机"}]'></textarea>
|
||||
<div class="hint">默认由上方构建器自动同步。你也可以直接手动编辑 JSON,失去焦点后页面会尝试重新渲染简要清单。</div>
|
||||
</div>
|
||||
<div class="full">
|
||||
<label for="templateDescription">模板说明</label>
|
||||
@@ -373,6 +414,11 @@
|
||||
const currentModeEl = document.getElementById('currentMode')
|
||||
const templateCountEl = document.getElementById('templateCount')
|
||||
const schemeCountEl = document.getElementById('schemeCount')
|
||||
const templateProductSummaryEl = document.getElementById('templateProductSummary')
|
||||
|
||||
const productDictNameMap = {
|
||||
category: '产品-产品分类',
|
||||
}
|
||||
|
||||
const state = {
|
||||
templateMode: 'idle',
|
||||
@@ -382,6 +428,9 @@
|
||||
templates: [],
|
||||
schemes: [],
|
||||
lastUploadedTemplateIcon: null,
|
||||
productDictionariesByName: {},
|
||||
selectableProducts: [],
|
||||
templateProductItems: [],
|
||||
}
|
||||
|
||||
const templateFields = {
|
||||
@@ -394,6 +443,9 @@
|
||||
solutionFeature: document.getElementById('templateSolutionFeature'),
|
||||
iconId: document.getElementById('templateIconId'),
|
||||
iconFile: document.getElementById('templateIconFile'),
|
||||
productCategoryFilter: document.getElementById('templateProductCategoryFilter'),
|
||||
productSelect: document.getElementById('templateProductSelect'),
|
||||
productSummary: templateProductSummaryEl,
|
||||
productList: document.getElementById('templateProductList'),
|
||||
description: document.getElementById('templateDescription'),
|
||||
remark: document.getElementById('templateRemark'),
|
||||
@@ -583,6 +635,290 @@
|
||||
}
|
||||
}
|
||||
|
||||
function cloneJsonValue(value) {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(value))
|
||||
} catch (_error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function findProductDictByName(name) {
|
||||
return state.productDictionariesByName[name] || null
|
||||
}
|
||||
|
||||
function getProductDictItems(dictName) {
|
||||
const dict = findProductDictByName(dictName)
|
||||
if (!dict || !Array.isArray(dict.items)) {
|
||||
return []
|
||||
}
|
||||
return dict.items
|
||||
}
|
||||
|
||||
function fillSelectByDict(selectEl, dictName, defaultLabel) {
|
||||
if (!selectEl) {
|
||||
return
|
||||
}
|
||||
|
||||
const items = getProductDictItems(dictName)
|
||||
const currentValue = selectEl.value || ''
|
||||
let html = '<option value="">' + escapeHtml(defaultLabel || '请选择') + '</option>'
|
||||
|
||||
for (let i = 0; i < items.length; i += 1) {
|
||||
const value = normalizeText(items[i].enum || '')
|
||||
const label = normalizeText(items[i].description || value)
|
||||
html += '<option value="' + escapeHtml(value) + '">' + escapeHtml(label) + '</option>'
|
||||
}
|
||||
|
||||
selectEl.innerHTML = html
|
||||
if (currentValue) {
|
||||
selectEl.value = currentValue
|
||||
}
|
||||
}
|
||||
|
||||
function getTemplateProductId(item) {
|
||||
return normalizeText(item && (item.prod_list_id || item.product_id))
|
||||
}
|
||||
|
||||
function getProductSourceById(productId) {
|
||||
const targetId = normalizeText(productId)
|
||||
if (!targetId) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (let i = 0; i < state.selectableProducts.length; i += 1) {
|
||||
if (normalizeText(state.selectableProducts[i].prod_list_id) === targetId) {
|
||||
return state.selectableProducts[i]
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function buildTemplateProductSummary(item) {
|
||||
const current = item && typeof item === 'object' ? item : {}
|
||||
const productId = getTemplateProductId(current)
|
||||
const source = getProductSourceById(productId) || current
|
||||
const title = normalizeText(source.prod_list_name || source.product_name || productId || '未命名产品')
|
||||
const model = normalizeText(source.prod_list_modelnumber)
|
||||
const barcode = normalizeText(source.prod_list_barcode || source.product_barcode)
|
||||
const category = normalizeText(source.prod_list_category)
|
||||
const status = normalizeText(source.prod_list_status)
|
||||
const price = source && (source.prod_list_basic_price || source.prod_list_basic_price === 0)
|
||||
? ('¥' + source.prod_list_basic_price)
|
||||
: ''
|
||||
const qty = current && (current.qty || current.qty === 0) ? ('数量 ' + current.qty) : ''
|
||||
const note = normalizeText(current && current.note)
|
||||
|
||||
return {
|
||||
productId: productId,
|
||||
title: title,
|
||||
meta: [productId, model, barcode, category, status, price, qty, note].filter(Boolean).join(' / '),
|
||||
}
|
||||
}
|
||||
|
||||
function syncTemplateProductListTextarea() {
|
||||
templateFields.productList.value = toJsonText(state.templateProductItems)
|
||||
}
|
||||
|
||||
function renderTemplateProductSummary() {
|
||||
if (!templateFields.productSummary) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!state.templateProductItems.length) {
|
||||
templateFields.productSummary.innerHTML = '<div class="product-list-empty">暂无已选产品,请先选择分类和产品后加入。</div>'
|
||||
return
|
||||
}
|
||||
|
||||
const rows = []
|
||||
for (let i = 0; i < state.templateProductItems.length; i += 1) {
|
||||
const summary = buildTemplateProductSummary(state.templateProductItems[i])
|
||||
rows.push(
|
||||
'<div class="product-summary-row">'
|
||||
+ '<div class="product-summary-order">' + (i + 1) + '</div>'
|
||||
+ '<div class="product-summary-main">'
|
||||
+ '<div class="product-summary-title">' + escapeHtml(summary.title) + '</div>'
|
||||
+ '<div class="product-summary-meta">' + escapeHtml(summary.meta || '未补充摘要信息') + '</div>'
|
||||
+ '</div>'
|
||||
+ '<div class="product-summary-actions">'
|
||||
+ '<button class="btn btn-light" type="button" data-template-product-up="' + i + '"' + (i === 0 ? ' disabled' : '') + '>上移</button>'
|
||||
+ '<button class="btn btn-light" type="button" data-template-product-down="' + i + '"' + (i === state.templateProductItems.length - 1 ? ' disabled' : '') + '>下移</button>'
|
||||
+ '<button class="btn btn-danger" type="button" data-template-product-remove="' + i + '">移除</button>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
)
|
||||
}
|
||||
|
||||
templateFields.productSummary.innerHTML = rows.join('')
|
||||
}
|
||||
|
||||
function setTemplateProductItems(items, options) {
|
||||
const safeItems = Array.isArray(items) ? items : []
|
||||
const nextItems = []
|
||||
|
||||
for (let i = 0; i < safeItems.length; i += 1) {
|
||||
const cloned = cloneJsonValue(safeItems[i])
|
||||
if (cloned && typeof cloned === 'object') {
|
||||
nextItems.push(cloned)
|
||||
}
|
||||
}
|
||||
|
||||
state.templateProductItems = nextItems
|
||||
if (!options || !options.skipTextareaSync) {
|
||||
syncTemplateProductListTextarea()
|
||||
}
|
||||
renderTemplateProductSummary()
|
||||
}
|
||||
|
||||
function hydrateTemplateProductItemsFromTextarea() {
|
||||
try {
|
||||
const parsed = parseJsonTextarea(templateFields.productList.value, 'scheme_template_product_list')
|
||||
setTemplateProductItems(parsed)
|
||||
setStatus(templateStatusEl, '产品清单 JSON 已同步为简要列表。', 'success')
|
||||
return parsed
|
||||
} catch (error) {
|
||||
setStatus(templateStatusEl, error.message || '产品清单 JSON 解析失败', 'error')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
function renderTemplateProductCategoryOptions() {
|
||||
fillSelectByDict(templateFields.productCategoryFilter, productDictNameMap.category, '全部分类')
|
||||
}
|
||||
|
||||
function renderTemplateProductOptions() {
|
||||
const category = normalizeText(templateFields.productCategoryFilter.value)
|
||||
const options = ['<option value="">' + escapeHtml(category ? '请选择产品' : '请先选择分类') + '</option>']
|
||||
const items = []
|
||||
|
||||
for (let i = 0; i < state.selectableProducts.length; i += 1) {
|
||||
const item = state.selectableProducts[i]
|
||||
if (category && normalizeText(item.prod_list_category) !== category) {
|
||||
continue
|
||||
}
|
||||
items.push(item)
|
||||
}
|
||||
|
||||
items.sort(function (a, b) {
|
||||
const sortDiff = Number(a.prod_list_sort || 0) - Number(b.prod_list_sort || 0)
|
||||
if (sortDiff !== 0) {
|
||||
return sortDiff
|
||||
}
|
||||
return String(a.prod_list_name || '').localeCompare(String(b.prod_list_name || ''))
|
||||
})
|
||||
|
||||
for (let i = 0; i < items.length; i += 1) {
|
||||
const item = items[i]
|
||||
const value = normalizeText(item.prod_list_id)
|
||||
const label = [
|
||||
normalizeText(item.prod_list_name),
|
||||
normalizeText(item.prod_list_modelnumber),
|
||||
normalizeText(item.prod_list_barcode),
|
||||
normalizeText(item.prod_list_category),
|
||||
].filter(Boolean).join(' / ')
|
||||
options.push('<option value="' + escapeHtml(value) + '">' + escapeHtml(label || value) + '</option>')
|
||||
}
|
||||
|
||||
templateFields.productSelect.innerHTML = options.join('')
|
||||
}
|
||||
|
||||
async function loadTemplateProductResources(skipStatus) {
|
||||
if (!skipStatus) {
|
||||
showLoading('正在加载产品分类与产品库...')
|
||||
}
|
||||
try {
|
||||
const dictionaries = await requestJson('/dictionary/list', {})
|
||||
const dictItems = Array.isArray(dictionaries && dictionaries.items) ? dictionaries.items : []
|
||||
state.productDictionariesByName = {}
|
||||
for (let i = 0; i < dictItems.length; i += 1) {
|
||||
state.productDictionariesByName[dictItems[i].dict_name] = dictItems[i]
|
||||
}
|
||||
|
||||
const products = await requestJson('/product/list', {
|
||||
keyword: '',
|
||||
status: '',
|
||||
prod_list_category: '',
|
||||
})
|
||||
state.selectableProducts = Array.isArray(products && products.items) ? products.items : []
|
||||
renderTemplateProductCategoryOptions()
|
||||
renderTemplateProductOptions()
|
||||
renderTemplateTable()
|
||||
renderTemplateProductSummary()
|
||||
|
||||
if (!skipStatus) {
|
||||
setStatus(pageStatusEl, '模板可选产品数据已刷新。', 'success')
|
||||
}
|
||||
} catch (error) {
|
||||
if (!skipStatus) {
|
||||
setStatus(pageStatusEl, error.message || '加载模板可选产品失败', 'error')
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
if (!skipStatus) {
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function addSelectedTemplateProduct() {
|
||||
const productId = normalizeText(templateFields.productSelect.value)
|
||||
if (!productId) {
|
||||
setStatus(templateStatusEl, '请先选择要加入模板的产品。', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < state.templateProductItems.length; i += 1) {
|
||||
if (getTemplateProductId(state.templateProductItems[i]) === productId) {
|
||||
setStatus(templateStatusEl, '该产品已在清单中,可直接调整顺序。', 'error')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
showLoading('正在读取产品详情...')
|
||||
try {
|
||||
const detail = await requestJson('/product/detail', { prod_list_id: productId })
|
||||
const nextItems = state.templateProductItems.slice()
|
||||
nextItems.push(detail)
|
||||
setTemplateProductItems(nextItems)
|
||||
setStatus(templateStatusEl, '产品已加入模板清单。', 'success')
|
||||
} catch (error) {
|
||||
setStatus(templateStatusEl, error.message || '加入产品失败', 'error')
|
||||
} finally {
|
||||
hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
function moveTemplateProductItem(index, offset) {
|
||||
const currentIndex = Number(index)
|
||||
const targetIndex = currentIndex + Number(offset)
|
||||
if (!Number.isInteger(currentIndex) || currentIndex < 0 || currentIndex >= state.templateProductItems.length) {
|
||||
return
|
||||
}
|
||||
if (!Number.isInteger(targetIndex) || targetIndex < 0 || targetIndex >= state.templateProductItems.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextItems = state.templateProductItems.slice()
|
||||
const currentItem = nextItems[currentIndex]
|
||||
nextItems[currentIndex] = nextItems[targetIndex]
|
||||
nextItems[targetIndex] = currentItem
|
||||
setTemplateProductItems(nextItems)
|
||||
setStatus(templateStatusEl, '产品顺序已调整。', 'success')
|
||||
}
|
||||
|
||||
function removeTemplateProductItem(index) {
|
||||
const currentIndex = Number(index)
|
||||
if (!Number.isInteger(currentIndex) || currentIndex < 0 || currentIndex >= state.templateProductItems.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextItems = state.templateProductItems.slice()
|
||||
nextItems.splice(currentIndex, 1)
|
||||
setTemplateProductItems(nextItems)
|
||||
setStatus(templateStatusEl, '产品已从模板清单移除。', 'success')
|
||||
}
|
||||
|
||||
function formatDateTimeInput(value) {
|
||||
const text = normalizeText(value)
|
||||
if (!text) {
|
||||
@@ -633,6 +969,12 @@
|
||||
for (let i = 0; i < state.templates.length; i += 1) {
|
||||
const item = state.templates[i]
|
||||
const productList = Array.isArray(item.scheme_template_product_list) ? item.scheme_template_product_list : []
|
||||
const productSummary = productList.length
|
||||
? productList.map(function (productItem, index) {
|
||||
const summary = buildTemplateProductSummary(productItem)
|
||||
return (index + 1) + '. ' + summary.title + (summary.meta ? (' / ' + summary.meta) : '')
|
||||
}).join('\n')
|
||||
: '-'
|
||||
rows.push(
|
||||
'<tr>'
|
||||
+ '<td data-label="模板 ID"><div class="code">' + escapeHtml(item.scheme_template_id) + '</div></td>'
|
||||
@@ -640,7 +982,7 @@
|
||||
+ '<td data-label="Owner"><div class="code">' + escapeHtml(item.scheme_template_owner) + '</div></td>'
|
||||
+ '<td data-label="标签 / 状态">' + escapeHtml(item.scheme_template_label || '-') + '<br /><span class="tiny">' + escapeHtml(item.scheme_template_status || '-') + '</span></td>'
|
||||
+ '<td data-label="方案类型 / 特色">' + escapeHtml(item.scheme_template_solution_type || '-') + '<br /><span class="tiny">' + escapeHtml(item.scheme_template_solution_feature || '-') + '</span></td>'
|
||||
+ '<td data-label="产品清单"><div class="json-preview tiny">' + escapeHtml(toJsonText(productList)) + '</div></td>'
|
||||
+ '<td data-label="产品清单"><div class="json-preview tiny">' + escapeHtml(productSummary) + '</div></td>'
|
||||
+ '<td data-label="操作"><div class="actions">'
|
||||
+ '<button class="btn btn-light" type="button" data-template-edit="' + escapeHtml(item.scheme_template_id) + '">编辑</button>'
|
||||
+ '<button class="btn btn-danger" type="button" data-template-delete="' + escapeHtml(item.scheme_template_id) + '">删除</button>'
|
||||
@@ -698,11 +1040,14 @@
|
||||
templateFields.solutionFeature.value = ''
|
||||
templateFields.iconId.value = ''
|
||||
templateFields.iconFile.value = ''
|
||||
templateFields.productList.value = '[]'
|
||||
templateFields.productCategoryFilter.value = ''
|
||||
templateFields.productSelect.innerHTML = '<option value="">请先选择分类</option>'
|
||||
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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
|
||||
@@ -6,6 +6,8 @@ info:
|
||||
面向管理端与自定义 hooks 的接口文档。
|
||||
本目录仅收敛自定义 hooks API,不包含 PocketBase 原生 records API。
|
||||
|
||||
本文件为目录索引,支持单文件独立导入,不依赖其他 YAML。
|
||||
|
||||
文档约定:
|
||||
- 不单独配置鉴权组件;如接口需要登录,请直接在说明中关注 `Authorization: Bearer <token>`
|
||||
- 示例字段值统一使用 `<字段说明>|<类型>` 风格
|
||||
@@ -34,70 +36,8 @@ tags:
|
||||
description: hooks 购物车接口
|
||||
- name: 订单
|
||||
description: hooks 订单接口
|
||||
paths:
|
||||
/pb/api/system/test-helloworld:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1system~1test-helloworld'
|
||||
/pb/api/system/health:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1system~1health'
|
||||
/pb/api/system/users-count:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1system~1users-count'
|
||||
/pb/api/system/refresh-token:
|
||||
$ref: '../openapi.yaml#/paths/~1pb~1api~1system~1refresh-token'
|
||||
/pb/api/wechat/login:
|
||||
$ref: '../openapi.yaml#/paths/~1pb~1api~1wechat~1login'
|
||||
/pb/api/wechat/profile:
|
||||
$ref: '../openapi.yaml#/paths/~1pb~1api~1wechat~1profile'
|
||||
/pb/api/platform/register:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1platform~1register'
|
||||
/pb/api/platform/login:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1platform~1login'
|
||||
/pb/api/dictionary/list:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1dictionary~1list'
|
||||
/pb/api/dictionary/detail:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1dictionary~1detail'
|
||||
/pb/api/dictionary/create:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1dictionary~1create'
|
||||
/pb/api/dictionary/update:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1dictionary~1update'
|
||||
/pb/api/dictionary/delete:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1dictionary~1delete'
|
||||
/pb/api/attachment/list:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1attachment~1list'
|
||||
/pb/api/attachment/detail:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1attachment~1detail'
|
||||
/pb/api/attachment/upload:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1attachment~1upload'
|
||||
/pb/api/attachment/delete:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1attachment~1delete'
|
||||
/pb/api/document/list:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1document~1list'
|
||||
/pb/api/document/detail:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1document~1detail'
|
||||
/pb/api/document/create:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1document~1create'
|
||||
/pb/api/document/update:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1document~1update'
|
||||
/pb/api/document/delete:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1document~1delete'
|
||||
/pb/api/document-history/list:
|
||||
$ref: '../openapi-manage.yaml#/paths/~1pb~1api~1document-history~1list'
|
||||
/pb/api/cart/list:
|
||||
$ref: './cart.yaml#/paths/cartList'
|
||||
/pb/api/cart/detail:
|
||||
$ref: './cart.yaml#/paths/cartDetail'
|
||||
/pb/api/cart/create:
|
||||
$ref: './cart.yaml#/paths/cartCreate'
|
||||
/pb/api/cart/update:
|
||||
$ref: './cart.yaml#/paths/cartUpdate'
|
||||
/pb/api/cart/delete:
|
||||
$ref: './cart.yaml#/paths/cartDelete'
|
||||
/pb/api/order/list:
|
||||
$ref: './order.yaml#/paths/orderList'
|
||||
/pb/api/order/detail:
|
||||
$ref: './order.yaml#/paths/orderDetail'
|
||||
/pb/api/order/create:
|
||||
$ref: './order.yaml#/paths/orderCreate'
|
||||
/pb/api/order/update:
|
||||
$ref: './order.yaml#/paths/orderUpdate'
|
||||
/pb/api/order/delete:
|
||||
$ref: './order.yaml#/paths/orderDelete'
|
||||
paths: {}
|
||||
x-index:
|
||||
files:
|
||||
- cart.yaml
|
||||
- order.yaml
|
||||
@@ -1,5 +1,17 @@
|
||||
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:
|
||||
orderList:
|
||||
/pb/api/order/list:
|
||||
post:
|
||||
tags: [订单]
|
||||
summary: 查询订单列表
|
||||
@@ -31,7 +43,7 @@ paths:
|
||||
description: 请求体必须为 application/json
|
||||
'500':
|
||||
description: 服务端错误
|
||||
orderDetail:
|
||||
/pb/api/order/detail:
|
||||
post:
|
||||
tags: [订单]
|
||||
summary: 查询订单详情
|
||||
@@ -65,7 +77,7 @@ paths:
|
||||
description: 请求体必须为 application/json
|
||||
'500':
|
||||
description: 服务端错误
|
||||
orderCreate:
|
||||
/pb/api/order/create:
|
||||
post:
|
||||
tags: [订单]
|
||||
summary: 新增订单记录
|
||||
@@ -104,7 +116,7 @@ paths:
|
||||
description: 重复请求过于频繁
|
||||
'500':
|
||||
description: 服务端错误
|
||||
orderUpdate:
|
||||
/pb/api/order/update:
|
||||
post:
|
||||
tags: [订单]
|
||||
summary: 修改订单记录
|
||||
@@ -148,7 +160,7 @@ paths:
|
||||
description: 重复请求过于频繁
|
||||
'500':
|
||||
description: 服务端错误
|
||||
orderDelete:
|
||||
/pb/api/order/delete:
|
||||
post:
|
||||
tags: [订单]
|
||||
summary: 删除订单记录
|
||||
|
||||
@@ -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
|
||||
@@ -1,44 +0,0 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: BAI PocketBase Native API
|
||||
version: 1.0.0-wx
|
||||
description: |
|
||||
顶层兼容入口。
|
||||
当前原生 API 文档已整理到 `spec/openapi-wx/` 目录下;本文件保留为兼容总入口。
|
||||
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:
|
||||
/pb/api/collections/tbl_company/records:
|
||||
$ref: './openapi-wx/company.yaml#/paths/companyRecords'
|
||||
/pb/api/collections/tbl_company/records/{recordId}:
|
||||
$ref: './openapi-wx/company.yaml#/paths/companyRecordById'
|
||||
/pb/api/collections/tbl_attachments/records:
|
||||
$ref: './openapi-wx/attachments.yaml#/paths/attachmentRecords'
|
||||
/pb/api/collections/tbl_product_list/records:
|
||||
$ref: './openapi-wx/products.yaml#/paths/productRecords'
|
||||
/pb/api/collections/tbl_document/records:
|
||||
$ref: './openapi-wx/documents.yaml#/paths/documentRecords'
|
||||
/pb/api/collections/tbl_cart/records:
|
||||
$ref: './openapi-wx/cart.yaml#/paths/cartRecords'
|
||||
/pb/api/collections/tbl_cart/records/{recordId}:
|
||||
$ref: './openapi-wx/cart.yaml#/paths/cartRecordById'
|
||||
/pb/api/collections/tbl_order/records:
|
||||
$ref: './openapi-wx/order.yaml#/paths/orderRecords'
|
||||
/pb/api/collections/tbl_order/records/{recordId}:
|
||||
$ref: './openapi-wx/order.yaml#/paths/orderRecordById'
|
||||
@@ -1,5 +1,17 @@
|
||||
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:
|
||||
attachmentRecords:
|
||||
/pb/api/collections/tbl_attachments/records:
|
||||
get:
|
||||
operationId: getPocketBaseAttachmentRecords
|
||||
tags:
|
||||
@@ -38,7 +50,7 @@ paths:
|
||||
- 不传该参数时,返回分页列表
|
||||
schema:
|
||||
type: string
|
||||
example: attachments_id="ATT-1774599142438-8n1UcU"
|
||||
example: 过滤表达式|string
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
@@ -60,8 +72,6 @@ paths:
|
||||
description: 查询成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseAttachmentListResponse
|
||||
example:
|
||||
page: page|integer
|
||||
perPage: perPage|integer
|
||||
@@ -85,8 +95,6 @@ paths:
|
||||
description: 查询参数错误
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -97,7 +105,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -108,7 +116,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -240,8 +248,6 @@ components:
|
||||
example: 总页数 | integer
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseAttachmentRecord
|
||||
example:
|
||||
page: page|integer
|
||||
perPage: perPage|integer
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,17 @@
|
||||
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:
|
||||
companyRecords:
|
||||
/pb/api/collections/tbl_company/records:
|
||||
post:
|
||||
operationId: postPocketBaseCompanyRecord
|
||||
tags:
|
||||
@@ -21,7 +33,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseCompanyCreateRequest
|
||||
$ref: #/components/schemas/PocketBaseCompanyCreateRequest
|
||||
example:
|
||||
company_name: 公司名称|string
|
||||
company_type: 公司类型|string
|
||||
@@ -47,7 +59,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseCompanyRecord
|
||||
$ref: #/components/schemas/PocketBaseCompanyRecord
|
||||
example:
|
||||
id: PocketBase 记录主键|string
|
||||
collectionId: collectionId|string
|
||||
@@ -78,7 +90,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -89,7 +101,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -100,7 +112,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -142,7 +154,7 @@ paths:
|
||||
- 查询整个列表时:不传该参数
|
||||
schema:
|
||||
type: string
|
||||
example: company_id="WX-COMPANY-10001"
|
||||
example: 过滤表达式|string
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
@@ -165,7 +177,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseCompanyListResponse
|
||||
$ref: #/components/schemas/PocketBaseCompanyListResponse
|
||||
example:
|
||||
page: page|integer
|
||||
perPage: perPage|integer
|
||||
@@ -201,7 +213,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -212,7 +224,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -223,13 +235,13 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
data:
|
||||
业务响应数据字段|string: 业务响应数据值|string
|
||||
companyRecordById:
|
||||
/pb/api/collections/tbl_company/records/{recordId}:
|
||||
patch:
|
||||
operationId: patchPocketBaseCompanyRecordByRecordId
|
||||
tags:
|
||||
@@ -253,13 +265,13 @@ paths:
|
||||
description: 通过 `company_id` 查询结果拿到的 PocketBase 记录主键 `id`
|
||||
schema:
|
||||
type: string
|
||||
example: l2r3nq7rqhuob0h
|
||||
example: PocketBase记录主键|string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseCompanyUpdateRequest
|
||||
$ref: #/components/schemas/PocketBaseCompanyUpdateRequest
|
||||
example:
|
||||
company_id: 所属公司业务 ID|string
|
||||
company_name: 公司名称|string
|
||||
@@ -286,7 +298,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseCompanyRecord
|
||||
$ref: #/components/schemas/PocketBaseCompanyRecord
|
||||
example:
|
||||
id: PocketBase 记录主键|string
|
||||
collectionId: collectionId|string
|
||||
@@ -317,7 +329,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -328,7 +340,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -339,7 +351,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -350,7 +362,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -653,8 +665,8 @@ components:
|
||||
company_remark: 备注|string
|
||||
PocketBaseCompanyRecord:
|
||||
allOf:
|
||||
- $ref: ../openapi-wx.yaml#/components/schemas/PocketBaseRecordBase
|
||||
- $ref: ../openapi-wx.yaml#/components/schemas/PocketBaseCompanyFields
|
||||
- $ref: #/components/schemas/PocketBaseRecordBase
|
||||
- $ref: #/components/schemas/PocketBaseCompanyFields
|
||||
example:
|
||||
id: PocketBase 记录主键|string
|
||||
collectionId: collectionId|string
|
||||
@@ -870,7 +882,7 @@ components:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseCompanyRecord
|
||||
$ref: #/components/schemas/PocketBaseCompanyRecord
|
||||
example:
|
||||
page: page|integer
|
||||
perPage: perPage|integer
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
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:
|
||||
documentRecords:
|
||||
/pb/api/collections/tbl_document/records:
|
||||
get:
|
||||
operationId: getPocketBaseDocumentRecords
|
||||
tags:
|
||||
@@ -57,7 +69,7 @@ paths:
|
||||
- 例如:`document_type ~ "DICT-1774599144591-hAEFQj" && document_type ~ "@UT1"`
|
||||
schema:
|
||||
type: string
|
||||
example: document_type ~ "DICT-1774599144591-hAEFQj" && document_type ~ "@UT1"
|
||||
example: 过滤表达式|string
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
@@ -84,14 +96,14 @@ paths:
|
||||
- `-document_create`:按最新上传倒序返回
|
||||
schema:
|
||||
type: string
|
||||
example: -document_create
|
||||
example: 排序表达式|string
|
||||
responses:
|
||||
"200":
|
||||
description: 查询成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseDocumentListResponse
|
||||
$ref: #/components/schemas/PocketBaseDocumentListResponse
|
||||
example:
|
||||
page: page|integer
|
||||
perPage: perPage|integer
|
||||
@@ -135,7 +147,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -146,7 +158,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -157,7 +169,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -289,15 +301,15 @@ components:
|
||||
document_share_count:
|
||||
type: number
|
||||
description: 分享次数
|
||||
example: 0
|
||||
example: 分享次数|number
|
||||
document_download_count:
|
||||
type: number
|
||||
description: 下载次数
|
||||
example: 0
|
||||
example: 下载次数|number
|
||||
document_favorite_count:
|
||||
type: number
|
||||
description: 收藏次数
|
||||
example: 0
|
||||
example: 收藏次数|number
|
||||
document_embedding_status:
|
||||
type: string
|
||||
description: 文档嵌入状态
|
||||
@@ -360,8 +372,8 @@ components:
|
||||
document_remark: 备注|string
|
||||
PocketBaseDocumentRecord:
|
||||
allOf:
|
||||
- $ref: ../openapi-wx.yaml#/components/schemas/PocketBaseRecordBase
|
||||
- $ref: ../openapi-wx.yaml#/components/schemas/PocketBaseDocumentFields
|
||||
- $ref: #/components/schemas/PocketBaseRecordBase
|
||||
- $ref: #/components/schemas/PocketBaseDocumentFields
|
||||
example:
|
||||
id: PocketBase 记录主键|string
|
||||
collectionId: collectionId|string
|
||||
@@ -427,7 +439,7 @@ components:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseDocumentRecord
|
||||
$ref: #/components/schemas/PocketBaseDocumentRecord
|
||||
example:
|
||||
page: page|integer
|
||||
perPage: perPage|integer
|
||||
|
||||
@@ -5,6 +5,8 @@ info:
|
||||
description: |
|
||||
本目录仅收敛 PocketBase 原生 API 文档,不包含自定义 hooks API。
|
||||
|
||||
本文件为目录索引,支持单文件独立导入,不依赖其他 YAML。
|
||||
|
||||
文档约定:
|
||||
- 不单独配置鉴权组件;如接口需要登录,请直接在说明中关注 `Authorization: Bearer <token>`
|
||||
- 示例字段值统一使用 `<字段说明>|<类型>` 风格
|
||||
@@ -27,22 +29,12 @@ tags:
|
||||
description: PocketBase 原生购物车记录接口
|
||||
- name: 订单
|
||||
description: PocketBase 原生订单记录接口
|
||||
paths:
|
||||
/pb/api/collections/tbl_company/records:
|
||||
$ref: './company.yaml#/paths/companyRecords'
|
||||
/pb/api/collections/tbl_company/records/{recordId}:
|
||||
$ref: './company.yaml#/paths/companyRecordById'
|
||||
/pb/api/collections/tbl_attachments/records:
|
||||
$ref: './attachments.yaml#/paths/attachmentRecords'
|
||||
/pb/api/collections/tbl_product_list/records:
|
||||
$ref: './products.yaml#/paths/productRecords'
|
||||
/pb/api/collections/tbl_document/records:
|
||||
$ref: './documents.yaml#/paths/documentRecords'
|
||||
/pb/api/collections/tbl_cart/records:
|
||||
$ref: './cart.yaml#/paths/cartRecords'
|
||||
/pb/api/collections/tbl_cart/records/{recordId}:
|
||||
$ref: './cart.yaml#/paths/cartRecordById'
|
||||
/pb/api/collections/tbl_order/records:
|
||||
$ref: './order.yaml#/paths/orderRecords'
|
||||
/pb/api/collections/tbl_order/records/{recordId}:
|
||||
$ref: './order.yaml#/paths/orderRecordById'
|
||||
paths: {}
|
||||
x-index:
|
||||
files:
|
||||
- company.yaml
|
||||
- attachments.yaml
|
||||
- products.yaml
|
||||
- documents.yaml
|
||||
- cart.yaml
|
||||
- order.yaml
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,17 @@
|
||||
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:
|
||||
productRecords:
|
||||
/pb/api/collections/tbl_product_list/records:
|
||||
get:
|
||||
operationId: getPocketBaseProductListRecords
|
||||
tags:
|
||||
@@ -30,7 +42,7 @@ paths:
|
||||
推荐写法:`prod_list_category="<产品分类>"`
|
||||
schema:
|
||||
type: string
|
||||
example: prod_list_category="<产品分类>"
|
||||
example: 过滤表达式|string
|
||||
- name: page
|
||||
in: query
|
||||
required: false
|
||||
@@ -57,14 +69,14 @@ paths:
|
||||
- `prod_list_sort`:按分类排序值从小到大
|
||||
schema:
|
||||
type: string
|
||||
example: prod_list_sort
|
||||
example: 排序表达式|string
|
||||
responses:
|
||||
"200":
|
||||
description: 查询成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseProductListListResponse
|
||||
$ref: #/components/schemas/PocketBaseProductListListResponse
|
||||
example:
|
||||
page: page|integer
|
||||
perPage: perPage|integer
|
||||
@@ -79,6 +91,7 @@ paths:
|
||||
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
|
||||
@@ -103,7 +116,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -114,7 +127,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -125,7 +138,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseNativeError
|
||||
$ref: #/components/schemas/PocketBaseNativeError
|
||||
example:
|
||||
code: 业务状态码|integer
|
||||
message: message|string
|
||||
@@ -202,6 +215,10 @@ components:
|
||||
type: string
|
||||
description: 产品型号
|
||||
example: <产品型号>|<string>
|
||||
prod_list_barcode:
|
||||
type: string
|
||||
description: 产品料号
|
||||
example: <产品料号>|<string>
|
||||
prod_list_icon:
|
||||
type: string
|
||||
description: 产品图标附件 ID,保存 `tbl_attachments.attachments_id`
|
||||
@@ -242,7 +259,7 @@ components:
|
||||
- number
|
||||
- integer
|
||||
description: 排序值(同分类内按升序)
|
||||
example: 10
|
||||
example: 排序值|number
|
||||
prod_list_comm_type:
|
||||
type: string
|
||||
description: 通讯类型
|
||||
@@ -268,7 +285,7 @@ components:
|
||||
- number
|
||||
- integer
|
||||
description: 基础价格
|
||||
example: 1999
|
||||
example: 基础价格|number
|
||||
prod_list_vip_price:
|
||||
type: array
|
||||
description: 会员价数组,每项包含会员等级枚举值与价格
|
||||
@@ -282,10 +299,10 @@ components:
|
||||
type:
|
||||
- number
|
||||
- integer
|
||||
example: 1899
|
||||
example: 会员价格|number
|
||||
example:
|
||||
- viplevel: VIP1
|
||||
price: 1899
|
||||
- viplevel: 会员等级|string
|
||||
price: 会员价格|number
|
||||
prod_list_remark:
|
||||
type: string
|
||||
description: 备注
|
||||
@@ -294,6 +311,7 @@ components:
|
||||
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
|
||||
@@ -315,8 +333,8 @@ components:
|
||||
prod_list_remark: 备注|string
|
||||
PocketBaseProductListRecord:
|
||||
allOf:
|
||||
- $ref: ../openapi-wx.yaml#/components/schemas/PocketBaseRecordBase
|
||||
- $ref: ../openapi-wx.yaml#/components/schemas/PocketBaseProductListFields
|
||||
- $ref: #/components/schemas/PocketBaseRecordBase
|
||||
- $ref: #/components/schemas/PocketBaseProductListFields
|
||||
example:
|
||||
id: PocketBase 记录主键|string
|
||||
collectionId: collectionId|string
|
||||
@@ -326,6 +344,7 @@ components:
|
||||
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
|
||||
@@ -377,7 +396,7 @@ components:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: ../openapi-wx.yaml#/components/schemas/PocketBaseProductListRecord
|
||||
$ref: #/components/schemas/PocketBaseProductListRecord
|
||||
example:
|
||||
page: page|integer
|
||||
perPage: perPage|integer
|
||||
@@ -392,6 +411,7 @@ components:
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@
|
||||
"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:add-product-function-field": "node add-product-function-field.js",
|
||||
"test:company-native-api": "node test-tbl-company-native-api.js",
|
||||
|
||||
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;
|
||||
});
|
||||
@@ -48,11 +48,11 @@ async function buildCollections() {
|
||||
updateRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
|
||||
deleteRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
|
||||
fields: [
|
||||
{ name: 'cart_id', type: 'text', required: true, autogeneratePattern: 'CART-[0-9]{13}-[A-Za-z0-9]{6}' },
|
||||
{ name: 'cart_id', type: 'text', required: false, autogeneratePattern: 'CART-[0-9]{13}-[A-Za-z0-9]{6}' },
|
||||
{ name: 'cart_number', type: 'text', required: false },
|
||||
{ name: 'cart_create', type: 'autodate', onCreate: true, onUpdate: false },
|
||||
{ name: 'cart_owner', type: 'text', required: true },
|
||||
{ name: 'cart_product_id', type: 'relation', required: true, collectionId: productCollectionId, maxSelect: 1, cascadeDelete: false },
|
||||
{ name: 'cart_product_id', type: 'relation', required: false, collectionId: productCollectionId, maxSelect: 1, cascadeDelete: false },
|
||||
{ name: 'cart_product_quantity', type: 'number', required: false },
|
||||
{ name: 'cart_status', type: 'text', required: false },
|
||||
{ name: 'cart_at_price', type: 'number', required: false },
|
||||
@@ -61,6 +61,7 @@ async function buildCollections() {
|
||||
],
|
||||
indexes: [
|
||||
'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_owner ON tbl_cart (cart_owner)',
|
||||
'CREATE INDEX idx_tbl_cart_cart_product_id ON tbl_cart (cart_product_id)',
|
||||
|
||||
@@ -57,11 +57,13 @@ 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,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -128,7 +130,7 @@ async function ensureAutoGenerateField(target) {
|
||||
|
||||
return normalizeFieldPayload(field, {
|
||||
type: 'text',
|
||||
required: true,
|
||||
required: typeof target.required === 'boolean' ? target.required : true,
|
||||
autogeneratePattern: target.autogeneratePattern,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,6 +35,7 @@ const collections = [
|
||||
{ name: 'prod_list_id', type: 'text', required: true },
|
||||
{ name: 'prod_list_name', type: 'text', required: true },
|
||||
{ name: 'prod_list_modelnumber', type: 'text' },
|
||||
{ name: 'prod_list_barcode', type: 'text' },
|
||||
{ name: 'prod_list_icon', type: 'text' },
|
||||
{ name: 'prod_list_description', type: 'text' },
|
||||
{ name: 'prod_list_feature', type: 'text' },
|
||||
|
||||
Reference in New Issue
Block a user