Files
Web_BAI_Manage_ApiServer/script/pocketbase.cart-order.js
XuJiacheng 91fcdcd65a feat: 添加购物车与订单管理页面及相关API支持
- 新增购物车与订单管理页面,包含用户列表、购物车详情和订单记录展示功能。
- 实现用户搜索、刷新、重置和退出登录功能。
- 新增购物车和订单数据表结构初始化脚本,包含字段、索引及权限规则设置。
- 实现数据表的创建与更新逻辑,并进行结构校验。
2026-04-03 10:50:31 +08:00

283 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createRequire } from 'module';
import PocketBase from 'pocketbase';
const require = createRequire(import.meta.url);
let runtimeConfig = {};
try {
runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
} catch (_error) {
runtimeConfig = {};
}
const PB_URL = (process.env.PB_URL || 'https://bai-api.blv-oa.com/pb').replace(/\/+$/, '');
const AUTH_TOKEN = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
const OWNER_AUTH_RULE = '@request.auth.id != ""';
const CART_OWNER_MATCH_RULE = 'cart_owner = @request.auth.openid';
const ORDER_OWNER_MATCH_RULE = 'order_owner = @request.auth.openid';
if (!AUTH_TOKEN) {
console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN无法执行建表。');
process.exit(1);
}
const pb = new PocketBase(PB_URL);
const collections = [
{
name: 'tbl_cart',
type: 'base',
listRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
viewRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
createRule: `${OWNER_AUTH_RULE} && @request.body.cart_owner = @request.auth.openid`,
updateRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
deleteRule: `${OWNER_AUTH_RULE} && ${CART_OWNER_MATCH_RULE}`,
fields: [
{ name: 'cart_id', type: 'text', required: true },
{ name: 'cart_number', type: 'text', required: true },
{ name: 'cart_create', type: 'autodate', onCreate: true, onUpdate: false },
{ name: 'cart_owner', type: 'text', required: true },
{ name: 'cart_product_id', type: 'text', required: true },
{ name: 'cart_product_quantity', type: 'number', required: true },
{ name: 'cart_status', type: 'text', required: true },
{ name: 'cart_at_price', type: 'number', required: true },
{ name: 'cart_remark', type: 'text' },
],
indexes: [
'CREATE UNIQUE INDEX idx_tbl_cart_cart_id ON tbl_cart (cart_id)',
'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)',
'CREATE INDEX idx_tbl_cart_cart_status ON tbl_cart (cart_status)',
'CREATE INDEX idx_tbl_cart_cart_create ON tbl_cart (cart_create)',
'CREATE INDEX idx_tbl_cart_owner_number ON tbl_cart (cart_owner, cart_number)',
'CREATE INDEX idx_tbl_cart_owner_status ON tbl_cart (cart_owner, cart_status)',
],
},
{
name: 'tbl_order',
type: 'base',
listRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE}`,
viewRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE}`,
createRule: `${OWNER_AUTH_RULE} && @request.body.order_owner = @request.auth.openid`,
updateRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE}`,
deleteRule: `${OWNER_AUTH_RULE} && ${ORDER_OWNER_MATCH_RULE}`,
fields: [
{ name: 'order_id', type: 'text', required: true },
{ name: 'order_number', type: 'text', required: true },
{ name: 'order_create', type: 'autodate', onCreate: true, onUpdate: false },
{ name: 'order_owner', type: 'text', required: true },
{ name: 'order_source', type: 'text', required: true },
{ name: 'order_status', type: 'text', required: true },
{ name: 'order_source_id', type: 'text', required: true },
{ name: 'order_snap', type: 'json', required: true },
{ name: 'order_amount', type: 'number', required: true },
{ name: 'order_remark', type: 'text' },
],
indexes: [
'CREATE UNIQUE INDEX idx_tbl_order_order_id ON tbl_order (order_id)',
'CREATE UNIQUE INDEX idx_tbl_order_order_number ON tbl_order (order_number)',
'CREATE INDEX idx_tbl_order_order_owner ON tbl_order (order_owner)',
'CREATE INDEX idx_tbl_order_order_source ON tbl_order (order_source)',
'CREATE INDEX idx_tbl_order_order_status ON tbl_order (order_status)',
'CREATE INDEX idx_tbl_order_order_source_id ON tbl_order (order_source_id)',
'CREATE INDEX idx_tbl_order_order_create ON tbl_order (order_create)',
'CREATE INDEX idx_tbl_order_owner_status ON tbl_order (order_owner, order_status)',
],
},
];
function normalizeFieldPayload(field, existingField) {
const payload = existingField
? Object.assign({}, existingField)
: {
name: field.name,
type: field.type,
};
if (existingField && existingField.id) {
payload.id = existingField.id;
}
payload.name = field.name;
payload.type = field.type;
if (typeof field.required !== 'undefined') {
payload.required = field.required;
}
if (field.type === 'autodate') {
payload.onCreate = typeof field.onCreate === 'boolean' ? field.onCreate : true;
payload.onUpdate = typeof field.onUpdate === 'boolean' ? field.onUpdate : false;
}
return payload;
}
function buildCollectionPayload(collectionData, existingCollection) {
if (!existingCollection) {
return {
name: collectionData.name,
type: collectionData.type,
listRule: Object.prototype.hasOwnProperty.call(collectionData, 'listRule') ? collectionData.listRule : null,
viewRule: Object.prototype.hasOwnProperty.call(collectionData, 'viewRule') ? collectionData.viewRule : null,
createRule: Object.prototype.hasOwnProperty.call(collectionData, 'createRule') ? collectionData.createRule : null,
updateRule: Object.prototype.hasOwnProperty.call(collectionData, 'updateRule') ? collectionData.updateRule : null,
deleteRule: Object.prototype.hasOwnProperty.call(collectionData, 'deleteRule') ? collectionData.deleteRule : null,
fields: collectionData.fields.map((field) => normalizeFieldPayload(field, null)),
indexes: collectionData.indexes,
};
}
const targetFieldMap = new Map(collectionData.fields.map((field) => [field.name, field]));
const fields = (existingCollection.fields || []).map((existingField) => {
const targetField = targetFieldMap.get(existingField.name);
if (!targetField) {
return existingField;
}
targetFieldMap.delete(existingField.name);
return normalizeFieldPayload(targetField, existingField);
});
for (const field of targetFieldMap.values()) {
fields.push(normalizeFieldPayload(field, null));
}
return {
name: collectionData.name,
type: collectionData.type,
listRule: Object.prototype.hasOwnProperty.call(collectionData, 'listRule') ? collectionData.listRule : existingCollection.listRule,
viewRule: Object.prototype.hasOwnProperty.call(collectionData, 'viewRule') ? collectionData.viewRule : existingCollection.viewRule,
createRule: Object.prototype.hasOwnProperty.call(collectionData, 'createRule') ? collectionData.createRule : existingCollection.createRule,
updateRule: Object.prototype.hasOwnProperty.call(collectionData, 'updateRule') ? collectionData.updateRule : existingCollection.updateRule,
deleteRule: Object.prototype.hasOwnProperty.call(collectionData, 'deleteRule') ? collectionData.deleteRule : existingCollection.deleteRule,
fields: fields,
indexes: collectionData.indexes,
};
}
function normalizeFieldList(fields) {
return (fields || []).map((field) => ({
name: field.name,
type: field.type,
required: !!field.required,
}));
}
async function createOrUpdateCollection(collectionData) {
console.log(`🔄 正在处理表: ${collectionData.name} ...`);
try {
const list = await pb.collections.getFullList({
sort: '-created',
});
const existing = list.find((item) => item.name === collectionData.name);
if (existing) {
await pb.collections.update(existing.id, buildCollectionPayload(collectionData, existing));
console.log(`♻️ ${collectionData.name} 已存在,已按最新结构更新。`);
return;
}
await pb.collections.create(buildCollectionPayload(collectionData, null));
console.log(`${collectionData.name} 创建完成。`);
} catch (error) {
console.error(`❌ 处理集合 ${collectionData.name} 失败:`, {
status: error.status,
message: error.message,
response: error.response,
});
throw error;
}
}
async function getCollectionByName(collectionName) {
const list = await pb.collections.getFullList({
sort: '-created',
});
return list.find((item) => item.name === collectionName) || null;
}
async function verifyCollections(targetCollections) {
console.log('\n🔍 开始校验购物车与订单表结构及索引...');
for (const target of targetCollections) {
const remote = await getCollectionByName(target.name);
if (!remote) {
throw new Error(`${target.name} 不存在`);
}
const remoteFields = normalizeFieldList(remote.fields);
const targetFields = normalizeFieldList(target.fields);
const remoteFieldMap = new Map(remoteFields.map((field) => [field.name, field.type]));
const remoteRequiredMap = new Map(remoteFields.map((field) => [field.name, field.required]));
const missingFields = [];
const mismatchedTypes = [];
const mismatchedRequired = [];
for (const field of targetFields) {
if (!remoteFieldMap.has(field.name)) {
missingFields.push(field.name);
continue;
}
if (remoteFieldMap.get(field.name) !== field.type) {
mismatchedTypes.push(`${field.name}:${remoteFieldMap.get(field.name)}!=${field.type}`);
}
if (remoteRequiredMap.get(field.name) !== !!field.required) {
mismatchedRequired.push(`${field.name}:${remoteRequiredMap.get(field.name)}!=${!!field.required}`);
}
}
const remoteIndexes = new Set(remote.indexes || []);
const missingIndexes = target.indexes.filter((indexSql) => !remoteIndexes.has(indexSql));
if (remote.type !== target.type) {
throw new Error(`${target.name} 类型不匹配,期望 ${target.type},实际 ${remote.type}`);
}
if (!missingFields.length && !mismatchedTypes.length && !mismatchedRequired.length && !missingIndexes.length) {
console.log(`${target.name} 校验通过。`);
continue;
}
console.log(`${target.name} 校验失败:`);
if (missingFields.length) {
console.log(` - 缺失字段: ${missingFields.join(', ')}`);
}
if (mismatchedTypes.length) {
console.log(` - 字段类型不匹配: ${mismatchedTypes.join(', ')}`);
}
if (mismatchedRequired.length) {
console.log(` - 字段必填属性不匹配: ${mismatchedRequired.join(', ')}`);
}
if (missingIndexes.length) {
console.log(` - 缺失索引: ${missingIndexes.join(' | ')}`);
}
throw new Error(`${target.name} 结构与预期不一致`);
}
}
async function init() {
try {
console.log(`🔄 正在连接 PocketBase: ${PB_URL}`);
pb.authStore.save(AUTH_TOKEN, null);
console.log('✅ 已使用 POCKETBASE_AUTH_TOKEN 载入认证状态。');
for (const collectionData of collections) {
await createOrUpdateCollection(collectionData);
}
await verifyCollections(collections);
console.log('\n🎉 购物车与订单表结构初始化并校验完成!');
} catch (error) {
console.error('❌ 初始化失败:', error.response?.data || error.message);
process.exitCode = 1;
}
}
init();