diff --git a/docs/api_response_field_notes.md b/docs/api_response_field_notes.md
index 26857df..71349a5 100644
--- a/docs/api_response_field_notes.md
+++ b/docs/api_response_field_notes.md
@@ -19,6 +19,7 @@
| `users_phone` | `手机号 | string` |
| `users_phone_masked` | `脱敏手机号 | string` |
| `users_level` | `用户等级 | string` |
+| `users_level_name` | `用户等级名称 | string` |
| `users_tag` | `用户标签 | string` |
| `users_picture` | `用户头像附件ID | string` |
| `users_picture_url` | `用户头像文件流链接 | string` |
diff --git a/docs/pb_tbl_auth_users.md b/docs/pb_tbl_auth_users.md
index 4e7f916..3a36321 100644
--- a/docs/pb_tbl_auth_users.md
+++ b/docs/pb_tbl_auth_users.md
@@ -24,7 +24,7 @@
| `users_idtype` | `text` | 否 | 身份来源类型或证件类型 |
| `users_id_number` | `text` | 否 | 证件号 |
| `users_phone` | `text` | 否 | 手机号 |
-| `users_level` | `text` | 否 | 用户等级文本 |
+| `users_level` | `text` | 否 | 用户等级枚举值,保存“数据-会员等级”字典 enum |
| `users_type` | `text` | 否 | 用户类型 |
| `company_id` | `text` | 否 | 所属公司业务 ID |
| `users_parent_id` | `text` | 否 | 上级用户业务 ID |
@@ -63,4 +63,5 @@
- 本表为 `auth` collection,除上述字段外还受 PocketBase 原生鉴权机制约束。
- 图片类字段统一只保存 `tbl_attachments.attachments_id`。
- 登录接口返回的 token 来源于本表 auth record 的原生签发能力,可直接给 PocketBase SDK 使用。
+- 新用户注册时,`users_level` 默认保持为空;已有用户后续登录 / 更新流程也不会自动改写该字段。
- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
diff --git a/docs/pb_tbl_cart.md b/docs/pb_tbl_cart.md
new file mode 100644
index 0000000..5324b2a
--- /dev/null
+++ b/docs/pb_tbl_cart.md
@@ -0,0 +1,52 @@
+# pb_tbl_cart
+
+> 来源:购物车表需求草图、`script/pocketbase.cart-order.js`
+> 类型:`base`
+> 读写规则:微信端原生访问建议仅允许记录所有者访问;当前脚本已按 `cart_owner = 当前 token 对应 openid` 配置原生 collection 规则,管理后台建议通过 hooks / API 聚合查询
+
+## 表用途
+
+用于存储购物车商品项明细。
+
+当前结构按“一个购物车商品项一条记录”设计:
+
+- `cart_id` 是单条购物车项业务 ID
+- `cart_number` 是购物车名称 / 分组号,同一购物车下的多条商品项可共用同一个 `cart_number`
+- `cart_owner` 用于标识购物车所属用户
+
+## 字段清单
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :---: | :--- |
+| `id` | `text` | 是 | PocketBase 记录主键 |
+| `cart_id` | `text` | 是 | 购物车项业务 ID,唯一标识 |
+| `cart_number` | `text` | 是 | 购物车名称 / 分组号,默认可按“用户名+年月日时分秒”生成 |
+| `cart_create` | `autodate` | 否 | 购物车项创建时间,由数据库自动生成 |
+| `cart_owner` | `text` | 是 | 生成者 openid,约定保存 `tbl_auth_users.openid` |
+| `cart_product_id` | `text` | 是 | 产品 ID,建议保存 `tbl_product_list.prod_list_id` |
+| `cart_product_quantity` | `number` | 是 | 产品数量,建议业务侧约束为正整数 |
+| `cart_status` | `text` | 是 | 购物车状态,建议值:`有效` / `无效` |
+| `cart_at_price` | `number` | 是 | 加入购物车时的价格,用于后续降价提醒或对比 |
+| `cart_remark` | `text` | 否 | 备注 |
+
+## 索引
+
+| 索引名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `idx_tbl_cart_cart_id` | `UNIQUE INDEX` | 保证 `cart_id` 唯一 |
+| `idx_tbl_cart_cart_number` | `INDEX` | 加速按购物车名称 / 分组号查询 |
+| `idx_tbl_cart_cart_owner` | `INDEX` | 加速按所属用户查询 |
+| `idx_tbl_cart_cart_product_id` | `INDEX` | 加速按产品查询 |
+| `idx_tbl_cart_cart_status` | `INDEX` | 加速按购物车状态过滤 |
+| `idx_tbl_cart_cart_create` | `INDEX` | 加速按购物车项创建时间倒序查询 |
+| `idx_tbl_cart_owner_number` | `INDEX` | 加速同一用户下按购物车分组查询 |
+| `idx_tbl_cart_owner_status` | `INDEX` | 加速查询某用户的有效购物车项 |
+
+## 补充约定
+
+- `cart_owner`、`cart_product_id` 当前按文本字段保存业务 ID,不直接建立 relation,便于兼容现有 hooks 业务模型。
+- `cart_owner` 统一保存 `tbl_auth_users.openid`,便于直接使用微信登录返回 token 做原生访问控制。
+- `cart_product_quantity`、`cart_at_price` 使用 `number`,数量正整数与价格精度建议在 hooks / API 层统一校验。
+- 当购物车被清空时,建议业务侧将历史记录 `cart_status` 置为 `无效`,而不是直接覆盖有效记录。
+- `cart_create` 由数据库自动写入,接口层不需要也不应允许客户端自行填值。
+- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
diff --git a/docs/pb_tbl_order.md b/docs/pb_tbl_order.md
new file mode 100644
index 0000000..66706c8
--- /dev/null
+++ b/docs/pb_tbl_order.md
@@ -0,0 +1,54 @@
+# pb_tbl_order
+
+> 来源:订单表需求草图、`script/pocketbase.cart-order.js`
+> 类型:`base`
+> 读写规则:微信端原生访问建议仅允许记录所有者访问;当前脚本已按 `order_owner = 当前 token 对应 openid` 配置原生 collection 规则,管理后台建议通过 hooks / API 聚合查询
+
+## 表用途
+
+用于存储订单主记录与订单快照。
+
+当前结构按“一个订单一条记录”设计:
+
+- `order_id` 是订单业务 ID
+- `order_number` 是订单编号,建议按“用户名+年月日时分秒”或统一编号规则生成
+- `order_snap` 使用 JSON 完整保存下单时的商品、数量、价格、折扣等快照,避免后续商品数据变化影响历史订单
+
+## 字段清单
+
+| 字段名 | 类型 | 必填 | 说明 |
+| :--- | :--- | :---: | :--- |
+| `id` | `text` | 是 | PocketBase 记录主键 |
+| `order_id` | `text` | 是 | 订单业务 ID,唯一标识 |
+| `order_number` | `text` | 是 | 订单编号,建议业务侧自动生成并保证可追踪 |
+| `order_create` | `autodate` | 否 | 订单创建时间,由数据库自动生成 |
+| `order_owner` | `text` | 是 | 生成者 openid,约定保存 `tbl_auth_users.openid` |
+| `order_source` | `text` | 是 | 订单来源,建议值:`购物车` / `方案清单` |
+| `order_status` | `text` | 是 | 订单状态,建议值:`订单已生成` / `订单已确定` / `订单已交付` / `订单已验收` / `订单已结束` |
+| `order_source_id` | `text` | 是 | 订单来源关联 ID,如购物车 ID 或方案清单 ID |
+| `order_snap` | `json` | 是 | 订单快照,完整保存订单明细信息 |
+| `order_amount` | `number` | 是 | 订单总金额 |
+| `order_remark` | `text` | 否 | 订单备注 |
+
+## 索引
+
+| 索引名 | 类型 | 说明 |
+| :--- | :--- | :--- |
+| `idx_tbl_order_order_id` | `UNIQUE INDEX` | 保证 `order_id` 唯一 |
+| `idx_tbl_order_order_number` | `UNIQUE INDEX` | 保证 `order_number` 唯一 |
+| `idx_tbl_order_order_owner` | `INDEX` | 加速按下单用户查询 |
+| `idx_tbl_order_order_source` | `INDEX` | 加速按订单来源过滤 |
+| `idx_tbl_order_order_status` | `INDEX` | 加速按订单状态过滤 |
+| `idx_tbl_order_order_source_id` | `INDEX` | 加速按来源关联 ID 查询 |
+| `idx_tbl_order_order_create` | `INDEX` | 加速按订单创建时间倒序查询 |
+| `idx_tbl_order_owner_status` | `INDEX` | 加速查询某用户在不同状态下的订单 |
+
+## 补充约定
+
+- `order_snap` 建议至少包含商品信息、数量、下单时价格、折扣、优惠、实际成交金额等字段。
+- 当订单进入 `订单已确定` 及之后状态时,建议业务侧锁定关键字段,不再允许修改订单核心数据。
+- `order_owner`、`order_source_id` 当前按文本字段保存业务 ID,不直接建立 relation,便于兼容现有 hooks 业务模型。
+- `order_owner` 统一保存 `tbl_auth_users.openid`,便于直接使用微信登录返回 token 做原生访问控制。
+- `order_amount` 使用 `number`,货币精度策略建议后续统一为“分”或固定小数位。
+- `order_create` 由数据库自动写入,接口层不需要也不应允许客户端自行填值。
+- PocketBase 系统字段 `created`、`updated` 仍然存在,只是不在 collection 字段清单里单独声明。
diff --git a/docs/pb_tbl_product_list.md b/docs/pb_tbl_product_list.md
index e542694..1d4ad8e 100644
--- a/docs/pb_tbl_product_list.md
+++ b/docs/pb_tbl_product_list.md
@@ -16,10 +16,10 @@
| `prod_list_id` | `text` | 是 | 产品列表业务 ID,唯一标识 |
| `prod_list_name` | `text` | 是 | 产品名称 |
| `prod_list_modelnumber` | `text` | 否 | 产品型号 |
-| `prod_list_icon` | `text` | 否 | 产品图标,保存 `tbl_attachments.attachments_id` |
+| `prod_list_icon` | `text` | 否 | 产品图标,保存 `tbl_attachments.attachments_id`,多图时以 `|` 分隔 |
| `prod_list_description` | `text` | 否 | 产品说明(editor 内容,建议保存 Markdown 或已净化 HTML) |
| `prod_list_feature` | `text` | 否 | 产品特色 |
-| `prod_list_parameters` | `json` | 否 | 产品参数(JSON 数组,格式为 `[ { "name": "属性名", "value": "属性值" } ]`) |
+| `prod_list_parameters` | `json` | 否 | 产品参数(JSON 数组,格式为 `[ { "sort": 1, "name": "属性名", "value": "属性值" } ]`) |
| `prod_list_plantype` | `text` | 否 | 产品方案 |
| `prod_list_category` | `text` | 是 | 产品分类(必填,单选) |
| `prod_list_sort` | `number` | 否 | 排序值(同分类内按升序) |
@@ -29,6 +29,7 @@
| `prod_list_tags` | `text` | 否 | 产品标签(辅助检索,以 `|` 聚合) |
| `prod_list_status` | `text` | 否 | 产品状态(有效 / 过期 / 主推等) |
| `prod_list_basic_price` | `number` | 否 | 基础价格 |
+| `prod_list_vip_price` | `json` | 否 | 会员价数组,格式为 `[{"viplevel":"会员等级枚举值","price":1999}]` |
| `prod_list_remark` | `text` | 否 | 备注 |
## 索引
@@ -47,9 +48,12 @@
## 补充约定
-- `prod_list_icon` 仅保存附件业务 ID,真实文件统一在 `tbl_attachments`。
+- `prod_list_icon` 仅保存附件业务 ID,真实文件统一在 `tbl_attachments`;多图时按上传顺序使用 `|` 聚合。
- 当前预构建脚本中已将 `listRule` 与 `viewRule` 设置为空字符串(`""`),对应 PocketBase 的“任何人可查看”。
-- `prod_list_parameters` 使用 PocketBase `json` 字段,写入时应直接提交数组结构:`[{"name":"属性名","value":"属性值"}]`。
+- `prod_list_parameters` 使用 PocketBase `json` 字段,写入时应直接提交数组结构:`[{"sort":1,"name":"属性名","value":"属性值"}]`。
+- `prod_list_parameters.sort` 用于稳定参数展示顺序,约定为正整数;前端未填写时可按当前录入/导入顺序自动补齐。
+- `prod_list_vip_price` 使用 PocketBase `json` 字段,写入时应直接提交数组结构:`[{"viplevel":"VIP1","price":1999}]`。
+- `prod_list_vip_price.viplevel` 必须保存“数据-会员等级”字典中的枚举值,`price` 保存对应会员等级价格。
- `prod_list_description` 作为 editor 内容字段,建议统一为 Markdown 或净化后的 HTML;若允许 HTML,需在写入前做 XSS 白名单过滤。
- 前端渲染 `prod_list_description` 时应保持和存储格式一致(Markdown 渲染器或受控 HTML 渲染),避免直接注入未净化内容。
- `prod_list_basic_price` 使用 `number`,如需货币精度策略建议后续统一为“分”或固定小数位。
diff --git a/pocket-base/bai-api-main.pb.js b/pocket-base/bai-api-main.pb.js
index b61c1b7..19b8819 100644
--- a/pocket-base/bai-api-main.pb.js
+++ b/pocket-base/bai-api-main.pb.js
@@ -41,6 +41,18 @@ require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/detail.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/create.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/update.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/product/delete.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart/list.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart/detail.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart/create.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart/update.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart/delete.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/order/list.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/order/detail.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/order/create.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/order/update.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/order/delete.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart-order/manage-users.js`)
+require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/cart-order/manage-user-update.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/document-history/list.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/sdk-permission/context.js`)
require(`${__hooks}/bai_api_pb_hooks/bai_api_routes/sdk-permission/role-save.js`)
diff --git a/pocket-base/bai-web-main.pb.js b/pocket-base/bai-web-main.pb.js
index 699a94a..ab0fad4 100644
--- a/pocket-base/bai-web-main.pb.js
+++ b/pocket-base/bai-web-main.pb.js
@@ -4,6 +4,7 @@ require(`${__hooks}/bai_web_pb_hooks/pages/document-manage.js`)
require(`${__hooks}/bai_web_pb_hooks/pages/product-manage.js`)
require(`${__hooks}/bai_web_pb_hooks/pages/dictionary-manage.js`)
require(`${__hooks}/bai_web_pb_hooks/pages/sdk-permission-manage.js`)
+require(`${__hooks}/bai_web_pb_hooks/pages/cart-order-manage.js`)
require(`${__hooks}/bai_chat_alm_hooks/bai-ai-manage-main.pb.js`)
require(`${__hooks}/bai_chat_alm_hooks/bai-chat.pb.js`)
require(`${__hooks}/bai_chat_alm_hooks/bai-sql-lab.pb.js`)
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/cart-order/manage-user-update.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart-order/manage-user-update.js
new file mode 100644
index 0000000..d0c1081
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart-order/manage-user-update.js
@@ -0,0 +1,25 @@
+routerAdd('POST', '/api/cart-order/manage-user-update', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ guards.requireManagePlatformUser(e)
+ const payload = guards.validateCartOrderManageUserUpdateBody(e)
+ const data = cartOrderService.updateManageUser(payload)
+
+ return success(e, '更新用户信息成功', {
+ user: data,
+ })
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('更新用户信息失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/cart-order/manage-users.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart-order/manage-users.js
new file mode 100644
index 0000000..665f33d
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart-order/manage-users.js
@@ -0,0 +1,23 @@
+routerAdd('POST', '/api/cart-order/manage-users', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ guards.requireManagePlatformUser(e)
+ const payload = guards.validateCartOrderManageListBody(e)
+ const data = cartOrderService.listManageUsersCartOrders(payload)
+
+ return success(e, '查询用户信息、购物车与订单成功', data)
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('查询用户信息、购物车与订单失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/create.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/create.js
new file mode 100644
index 0000000..39cf94d
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/create.js
@@ -0,0 +1,25 @@
+routerAdd('POST', '/api/cart/create', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ const authState = guards.requireAuthUser(e)
+ guards.duplicateGuard(e)
+
+ const payload = guards.validateCartMutationBody(e, false)
+ const data = cartOrderService.createCart(authState, payload)
+
+ return success(e, '创建购物车记录成功', data)
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('创建购物车记录失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/delete.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/delete.js
new file mode 100644
index 0000000..715a352
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/delete.js
@@ -0,0 +1,25 @@
+routerAdd('POST', '/api/cart/delete', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ const authState = guards.requireAuthUser(e)
+ guards.duplicateGuard(e)
+
+ const payload = guards.validateCartDeleteBody(e)
+ const data = cartOrderService.deleteCart(authState.openid, payload.cart_id)
+
+ return success(e, '删除购物车记录成功', data)
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('删除购物车记录失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/detail.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/detail.js
new file mode 100644
index 0000000..960d74d
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/detail.js
@@ -0,0 +1,23 @@
+routerAdd('POST', '/api/cart/detail', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ const authState = guards.requireAuthUser(e)
+ const payload = guards.validateCartDetailBody(e)
+ const data = cartOrderService.getCartDetail(authState.openid, payload.cart_id)
+
+ return success(e, '查询购物车详情成功', data)
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('查询购物车详情失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/list.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/list.js
new file mode 100644
index 0000000..70375d7
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/list.js
@@ -0,0 +1,25 @@
+routerAdd('POST', '/api/cart/list', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ const authState = guards.requireAuthUser(e)
+ const payload = guards.validateCartListBody(e)
+ const data = cartOrderService.listCarts(authState.openid, payload)
+
+ return success(e, '查询购物车列表成功', {
+ items: data,
+ })
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('查询购物车列表失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/update.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/update.js
new file mode 100644
index 0000000..02be668
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/cart/update.js
@@ -0,0 +1,25 @@
+routerAdd('POST', '/api/cart/update', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ const authState = guards.requireAuthUser(e)
+ guards.duplicateGuard(e)
+
+ const payload = guards.validateCartMutationBody(e, true)
+ const data = cartOrderService.updateCart(authState.openid, payload)
+
+ return success(e, '更新购物车记录成功', data)
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('更新购物车记录失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/order/create.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/order/create.js
new file mode 100644
index 0000000..5750c50
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/order/create.js
@@ -0,0 +1,25 @@
+routerAdd('POST', '/api/order/create', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ const authState = guards.requireAuthUser(e)
+ guards.duplicateGuard(e)
+
+ const payload = guards.validateOrderMutationBody(e, false)
+ const data = cartOrderService.createOrder(authState, payload)
+
+ return success(e, '创建订单成功', data)
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('创建订单失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/order/delete.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/order/delete.js
new file mode 100644
index 0000000..5b0a578
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/order/delete.js
@@ -0,0 +1,25 @@
+routerAdd('POST', '/api/order/delete', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ const authState = guards.requireAuthUser(e)
+ guards.duplicateGuard(e)
+
+ const payload = guards.validateOrderDeleteBody(e)
+ const data = cartOrderService.deleteOrder(authState.openid, payload.order_id)
+
+ return success(e, '删除订单成功', data)
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('删除订单失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/order/detail.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/order/detail.js
new file mode 100644
index 0000000..d960b4f
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/order/detail.js
@@ -0,0 +1,23 @@
+routerAdd('POST', '/api/order/detail', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ const authState = guards.requireAuthUser(e)
+ const payload = guards.validateOrderDetailBody(e)
+ const data = cartOrderService.getOrderDetail(authState.openid, payload.order_id)
+
+ return success(e, '查询订单详情成功', data)
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('查询订单详情失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/order/list.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/order/list.js
new file mode 100644
index 0000000..d38f984
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/order/list.js
@@ -0,0 +1,25 @@
+routerAdd('POST', '/api/order/list', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ const authState = guards.requireAuthUser(e)
+ const payload = guards.validateOrderListBody(e)
+ const data = cartOrderService.listOrders(authState.openid, payload)
+
+ return success(e, '查询订单列表成功', {
+ items: data,
+ })
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('查询订单列表失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/order/update.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/order/update.js
new file mode 100644
index 0000000..3959ba4
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/order/update.js
@@ -0,0 +1,25 @@
+routerAdd('POST', '/api/order/update', function (e) {
+ const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
+ const cartOrderService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+
+ try {
+ guards.requireJson(e)
+ const authState = guards.requireAuthUser(e)
+ guards.duplicateGuard(e)
+
+ const payload = guards.validateOrderMutationBody(e, true)
+ const data = cartOrderService.updateOrder(authState.openid, payload)
+
+ return success(e, '更新订单成功', data)
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+ logger.error('更新订单失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
+})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_routes/sdk-permission/context.js b/pocket-base/bai_api_pb_hooks/bai_api_routes/sdk-permission/context.js
index ae04fde..0934409 100644
--- a/pocket-base/bai_api_pb_hooks/bai_api_routes/sdk-permission/context.js
+++ b/pocket-base/bai_api_pb_hooks/bai_api_routes/sdk-permission/context.js
@@ -1,15 +1,28 @@
routerAdd('POST', '/api/sdk-permission/context', function (e) {
const guards = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js`)
const permissionService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/sdkPermissionService.js`)
- const { success } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
+ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+ const { success, fail } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/response.js`)
- guards.requireJson(e)
- guards.requireManagePlatformUser(e)
+ try {
+ guards.requireJson(e)
+ guards.requireManagePlatformUser(e)
- const payload = guards.validateSdkPermissionContextBody(e)
- const data = permissionService.getManagementContext(payload.keyword)
+ const payload = guards.validateSdkPermissionContextBody(e)
+ const data = permissionService.getManagementContext(payload.keyword)
- return success(e, '查询权限管理上下文成功', data)
+ return success(e, '查询权限管理上下文成功', data)
+ } catch (err) {
+ const status = (err && err.statusCode) || (err && err.status) || 400
+
+ logger.error('查询权限管理上下文失败', {
+ status: status,
+ errMsg: (err && err.message) || '未知错误',
+ data: (err && err.data) || {},
+ })
+
+ return fail(e, (err && err.message) || '操作失败', (err && err.data) || {}, status)
+ }
})
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/config/.env b/pocket-base/bai_api_pb_hooks/bai_api_shared/config/.env
index 4e78de5..b44b228 100644
--- a/pocket-base/bai_api_pb_hooks/bai_api_shared/config/.env
+++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/config/.env
@@ -1,6 +1,6 @@
NODE_ENV=production
APP_BASE_URL=https://bai-api.blv-oa.com
-POCKETBASE_API_URL=https://bai-api.blv-oa.com/pb/
+POCKETBASE_API_URL=http://127.0.0.1:8090
POCKETBASE_AUTH_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJwYmNfMzE0MjYzNTgyMyIsImV4cCI6MTgwNTk2MzM4NywiaWQiOiJmcnl2dG1qNnlwcHlmMXAiLCJyZWZyZXNoYWJsZSI6ZmFsc2UsInR5cGUiOiJhdXRoIn0.R34MTotuFBpevqbbUtvQ3GPa0Z1mpY8eQwZgDdmfhMo
#正式服
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js
index 2b778d8..92a6d04 100644
--- a/pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js
+++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js
@@ -2,7 +2,7 @@ module.exports = {
NODE_ENV: 'production',
APP_VERSION: '0.1.21',
APP_BASE_URL: 'https://bai-api.blv-oa.com',
- POCKETBASE_API_URL: 'https://bai-api.blv-oa.com/pb',
+ POCKETBASE_API_URL: 'http://127.0.0.1:8090',
POCKETBASE_AUTH_TOKEN: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJwYmNfMzE0MjYzNTgyMyIsImV4cCI6MTgwNTk2MzM4NywiaWQiOiJmcnl2dG1qNnlwcHlmMXAiLCJyZWZyZXNoYWJsZSI6ZmFsc2UsInR5cGUiOiJhdXRoIn0.R34MTotuFBpevqbbUtvQ3GPa0Z1mpY8eQwZgDdmfhMo',
WECHAT_APPID: 'wx3bd7a7b19679da7a',
WECHAT_SECRET: '57e40438c2a9151257b1927674db10e1',
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js
index d9ba824..437666d 100644
--- a/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js
+++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/middlewares/requestGuards.js
@@ -107,14 +107,32 @@ function validateSystemRefreshBody(e) {
}
function requireManagePlatformUser(e) {
- const authUser = requireAuthUser(e)
- const idType = authUser.authRecord.getString('users_idtype')
+ const authHeader = e.request.header.get('Authorization') || ''
+ const parts = authHeader.split(' ')
+
+ if (parts.length !== 2 || parts[0] !== 'Bearer' || !parts[1]) {
+ throw createAppError(401, '请求头缺少 Authorization')
+ }
+
+ if (!e.auth) {
+ throw createAppError(401, '认证令牌无效或已过期')
+ }
+
+ if (e.auth.collection().name !== 'tbl_auth_users') {
+ throw createAppError(401, '认证用户集合不正确')
+ }
+
+ const authRecord = e.auth
+ const idType = authRecord.getString('users_idtype')
if (idType !== 'ManagePlatform') {
throw createAppError(403, '仅平台管理用户可访问')
}
- return authUser
+ return {
+ openid: authRecord.getString('openid') || '',
+ authRecord: authRecord,
+ }
}
function validateDictionaryListBody(e) {
@@ -355,16 +373,31 @@ function normalizeProductParameters(value) {
const result = []
const indexByName = {}
- function upsert(nameValue, rawValue) {
+ function normalizeParameterSort(rawSort, fallbackSort) {
+ if (rawSort === '' || rawSort === null || typeof rawSort === 'undefined') {
+ return fallbackSort
+ }
+
+ const num = Number(rawSort)
+ if (!Number.isFinite(num) || num <= 0 || Math.floor(num) !== num) {
+ throw createAppError(400, 'prod_list_parameters.sort 必须为正整数')
+ }
+
+ return num
+ }
+
+ function upsert(nameValue, rawValue, rawSort, fallbackSort) {
const name = String(nameValue || '').trim()
if (!name) {
return
}
const normalizedValue = rawValue === null || typeof rawValue === 'undefined' ? '' : String(rawValue)
+ const normalizedSort = normalizeParameterSort(rawSort, fallbackSort)
const existingIndex = indexByName[name]
if (typeof existingIndex === 'number') {
result[existingIndex].value = normalizedValue
+ result[existingIndex].sort = normalizedSort
return
}
@@ -372,6 +405,7 @@ function normalizeProductParameters(value) {
result.push({
name: name,
value: normalizedValue,
+ sort: normalizedSort,
})
}
@@ -381,7 +415,7 @@ function normalizeProductParameters(value) {
if (!item) {
continue
}
- upsert(item.name || item.key, item.value)
+ upsert(item.name || item.key, item.value, item.sort, result.length + 1)
}
return result
}
@@ -393,7 +427,58 @@ function normalizeProductParameters(value) {
const keys = Object.keys(value)
for (let i = 0; i < keys.length; i += 1) {
- upsert(keys[i], value[keys[i]])
+ upsert(keys[i], value[keys[i]], '', result.length + 1)
+ }
+
+ return result
+}
+
+function normalizeProductVipPrice(value) {
+ if (value === null || typeof value === 'undefined' || value === '') {
+ return []
+ }
+
+ let source = value
+ if (typeof source === 'string') {
+ try {
+ source = JSON.parse(source)
+ } catch (_error) {
+ throw createAppError(400, 'prod_list_vip_price 必须为合法 JSON 数组')
+ }
+ }
+
+ if (!Array.isArray(source)) {
+ throw createAppError(400, 'prod_list_vip_price 必须为数组')
+ }
+
+ const result = []
+ const levelMap = {}
+
+ for (let i = 0; i < source.length; i += 1) {
+ const item = source[i] && typeof source[i] === 'object' ? source[i] : null
+ if (!item) {
+ continue
+ }
+
+ const viplevel = String(item.viplevel || '').trim()
+ if (!viplevel) {
+ throw createAppError(400, 'prod_list_vip_price 第 ' + (i + 1) + ' 项 viplevel 为必填项')
+ }
+
+ const price = Number(item.price)
+ if (!Number.isFinite(price)) {
+ throw createAppError(400, 'prod_list_vip_price 第 ' + (i + 1) + ' 项 price 必须为数字')
+ }
+
+ if (levelMap[viplevel]) {
+ throw createAppError(400, 'prod_list_vip_price 中 viplevel 不允许重复:' + viplevel)
+ }
+
+ levelMap[viplevel] = true
+ result.push({
+ viplevel: viplevel,
+ price: price,
+ })
}
return result
@@ -439,10 +524,11 @@ 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_icon: payload.prod_list_icon || '',
+ 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 || '',
prod_list_parameters: normalizeProductParameters(payload.prod_list_parameters),
+ prod_list_function: normalizeProductParameters(payload.prod_list_function),
prod_list_plantype: payload.prod_list_plantype || '',
prod_list_category: payload.prod_list_category || '',
prod_list_sort: typeof payload.prod_list_sort === 'undefined' ? 0 : payload.prod_list_sort,
@@ -452,6 +538,7 @@ function validateProductMutationBody(e, isUpdate) {
prod_list_tags: payload.prod_list_tags || '',
prod_list_status: payload.prod_list_status || '',
prod_list_basic_price: typeof payload.prod_list_basic_price === 'undefined' ? '' : payload.prod_list_basic_price,
+ prod_list_vip_price: normalizeProductVipPrice(payload.prod_list_vip_price),
prod_list_remark: payload.prod_list_remark || '',
}
}
@@ -460,6 +547,157 @@ function validateProductDeleteBody(e) {
return validateProductDetailBody(e)
}
+function validateCartListBody(e) {
+ const payload = parseBody(e)
+
+ return {
+ keyword: payload.keyword || '',
+ cart_status: payload.cart_status || '',
+ cart_number: payload.cart_number || '',
+ }
+}
+
+function validateCartDetailBody(e) {
+ const payload = parseBody(e)
+ if (!payload.cart_id) {
+ throw createAppError(400, 'cart_id 为必填项')
+ }
+
+ return {
+ cart_id: String(payload.cart_id || '').trim(),
+ }
+}
+
+function validateCartMutationBody(e, isUpdate) {
+ const payload = parseBody(e)
+
+ if (isUpdate && !payload.cart_id) {
+ throw createAppError(400, 'cart_id 为必填项')
+ }
+
+ if (!isUpdate) {
+ if (!payload.cart_product_id) {
+ throw createAppError(400, 'cart_product_id 为必填项')
+ }
+ if (typeof payload.cart_product_quantity === 'undefined') {
+ throw createAppError(400, 'cart_product_quantity 为必填项')
+ }
+ if (typeof payload.cart_at_price === 'undefined') {
+ throw createAppError(400, 'cart_at_price 为必填项')
+ }
+ }
+
+ return {
+ cart_id: payload.cart_id || '',
+ cart_number: Object.prototype.hasOwnProperty.call(payload, 'cart_number') ? payload.cart_number : undefined,
+ cart_owner: Object.prototype.hasOwnProperty.call(payload, 'cart_owner') ? payload.cart_owner : undefined,
+ cart_product_id: Object.prototype.hasOwnProperty.call(payload, 'cart_product_id') ? payload.cart_product_id : undefined,
+ cart_product_quantity: Object.prototype.hasOwnProperty.call(payload, 'cart_product_quantity') ? payload.cart_product_quantity : undefined,
+ cart_status: Object.prototype.hasOwnProperty.call(payload, 'cart_status') ? payload.cart_status : undefined,
+ cart_at_price: Object.prototype.hasOwnProperty.call(payload, 'cart_at_price') ? payload.cart_at_price : undefined,
+ cart_remark: Object.prototype.hasOwnProperty.call(payload, 'cart_remark') ? payload.cart_remark : undefined,
+ }
+}
+
+function validateCartDeleteBody(e) {
+ return validateCartDetailBody(e)
+}
+
+function validateOrderListBody(e) {
+ const payload = parseBody(e)
+
+ return {
+ keyword: payload.keyword || '',
+ order_status: payload.order_status || '',
+ order_source: payload.order_source || '',
+ }
+}
+
+function validateOrderDetailBody(e) {
+ const payload = parseBody(e)
+ if (!payload.order_id) {
+ throw createAppError(400, 'order_id 为必填项')
+ }
+
+ return {
+ order_id: String(payload.order_id || '').trim(),
+ }
+}
+
+function validateOrderMutationBody(e, isUpdate) {
+ const payload = parseBody(e)
+
+ if (isUpdate && !payload.order_id) {
+ throw createAppError(400, 'order_id 为必填项')
+ }
+
+ if (!isUpdate) {
+ if (!payload.order_source) {
+ throw createAppError(400, 'order_source 为必填项')
+ }
+ if (!payload.order_source_id) {
+ throw createAppError(400, 'order_source_id 为必填项')
+ }
+ if (typeof payload.order_snap === 'undefined') {
+ throw createAppError(400, 'order_snap 为必填项')
+ }
+ if (typeof payload.order_amount === 'undefined') {
+ throw createAppError(400, 'order_amount 为必填项')
+ }
+ }
+
+ return {
+ order_id: payload.order_id || '',
+ order_number: Object.prototype.hasOwnProperty.call(payload, 'order_number') ? payload.order_number : undefined,
+ order_owner: Object.prototype.hasOwnProperty.call(payload, 'order_owner') ? payload.order_owner : undefined,
+ order_source: Object.prototype.hasOwnProperty.call(payload, 'order_source') ? payload.order_source : undefined,
+ order_status: Object.prototype.hasOwnProperty.call(payload, 'order_status') ? payload.order_status : undefined,
+ order_source_id: Object.prototype.hasOwnProperty.call(payload, 'order_source_id') ? payload.order_source_id : undefined,
+ order_snap: Object.prototype.hasOwnProperty.call(payload, 'order_snap') ? payload.order_snap : undefined,
+ order_amount: Object.prototype.hasOwnProperty.call(payload, 'order_amount') ? payload.order_amount : undefined,
+ order_remark: Object.prototype.hasOwnProperty.call(payload, 'order_remark') ? payload.order_remark : undefined,
+ }
+}
+
+function validateOrderDeleteBody(e) {
+ return validateOrderDetailBody(e)
+}
+
+function validateCartOrderManageListBody(e) {
+ const payload = parseBody(e)
+ return {
+ keyword: payload.keyword || '',
+ }
+}
+
+function validateCartOrderManageUserUpdateBody(e) {
+ const payload = parseBody(e)
+ if (!payload.openid) {
+ throw createAppError(400, 'openid 为必填项')
+ }
+
+ return {
+ openid: String(payload.openid || '').trim(),
+ users_name: Object.prototype.hasOwnProperty.call(payload, 'users_name') ? payload.users_name : undefined,
+ users_phone: Object.prototype.hasOwnProperty.call(payload, 'users_phone') ? payload.users_phone : undefined,
+ users_id_number: Object.prototype.hasOwnProperty.call(payload, 'users_id_number') ? payload.users_id_number : undefined,
+ users_level: Object.prototype.hasOwnProperty.call(payload, 'users_level') ? payload.users_level : undefined,
+ users_type: Object.prototype.hasOwnProperty.call(payload, 'users_type') ? payload.users_type : undefined,
+ users_tag: Object.prototype.hasOwnProperty.call(payload, 'users_tag') ? payload.users_tag : undefined,
+ users_status: Object.prototype.hasOwnProperty.call(payload, 'users_status') ? payload.users_status : undefined,
+ users_rank_level: Object.prototype.hasOwnProperty.call(payload, 'users_rank_level') ? payload.users_rank_level : undefined,
+ users_auth_type: Object.prototype.hasOwnProperty.call(payload, 'users_auth_type') ? payload.users_auth_type : undefined,
+ company_id: Object.prototype.hasOwnProperty.call(payload, 'company_id') ? payload.company_id : undefined,
+ users_parent_id: Object.prototype.hasOwnProperty.call(payload, 'users_parent_id') ? payload.users_parent_id : undefined,
+ users_promo_code: Object.prototype.hasOwnProperty.call(payload, 'users_promo_code') ? payload.users_promo_code : undefined,
+ usergroups_id: Object.prototype.hasOwnProperty.call(payload, 'usergroups_id') ? payload.usergroups_id : undefined,
+ users_picture: Object.prototype.hasOwnProperty.call(payload, 'users_picture') ? payload.users_picture : undefined,
+ users_id_pic_a: Object.prototype.hasOwnProperty.call(payload, 'users_id_pic_a') ? payload.users_id_pic_a : undefined,
+ users_id_pic_b: Object.prototype.hasOwnProperty.call(payload, 'users_id_pic_b') ? payload.users_id_pic_b : undefined,
+ users_title_picture: Object.prototype.hasOwnProperty.call(payload, 'users_title_picture') ? payload.users_title_picture : undefined,
+ }
+}
+
function validateDocumentHistoryListBody(e) {
const payload = parseBody(e)
@@ -635,6 +873,16 @@ module.exports = {
validateProductDetailBody,
validateProductMutationBody,
validateProductDeleteBody,
+ validateCartListBody,
+ validateCartDetailBody,
+ validateCartMutationBody,
+ validateCartDeleteBody,
+ validateOrderListBody,
+ validateOrderDetailBody,
+ validateOrderMutationBody,
+ validateOrderDeleteBody,
+ validateCartOrderManageListBody,
+ validateCartOrderManageUserUpdateBody,
validateDocumentHistoryListBody,
validateSdkPermissionContextBody,
validateSdkPermissionRoleBody,
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js
new file mode 100644
index 0000000..e67e7d9
--- /dev/null
+++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/cartOrderService.js
@@ -0,0 +1,783 @@
+const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
+const { createAppError } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/appError.js`)
+const env = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/config/env.js`)
+const userService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/userService.js`)
+
+function buildBusinessId(prefix) {
+ return prefix + '-' + new Date().getTime() + '-' + $security.randomString(6)
+}
+
+function parseJsonResponse(response, actionName) {
+ if (!response) {
+ throw createAppError(500, actionName + ' 失败:PocketBase 响应为空')
+ }
+
+ if (response.json && typeof response.json === 'object') {
+ return response.json
+ }
+
+ if (typeof response.body === 'string' && response.body) {
+ return JSON.parse(response.body)
+ }
+
+ if (response.body && typeof response.body === 'object') {
+ return response.body
+ }
+
+ if (typeof response.data === 'string' && response.data) {
+ return JSON.parse(response.data)
+ }
+
+ if (response.data && typeof response.data === 'object') {
+ return response.data
+ }
+
+ return {}
+}
+
+function getPocketBaseApiBaseUrl() {
+ const base = String(env.pocketbaseApiUrl || '').replace(/\/+$/, '')
+ if (!base) {
+ throw createAppError(500, '缺少 POCKETBASE_API_URL 配置')
+ }
+ return base
+}
+
+function getPocketBaseAuthToken() {
+ const token = String(env.pocketbaseAuthToken || '').trim()
+ if (!token) {
+ throw createAppError(500, '缺少 POCKETBASE_AUTH_TOKEN 配置')
+ }
+ return token
+}
+
+function requestPocketBase(method, path, body, actionName) {
+ const response = $http.send({
+ url: getPocketBaseApiBaseUrl() + path,
+ method: method,
+ headers: body ? {
+ Authorization: 'Bearer ' + getPocketBaseAuthToken(),
+ 'Content-Type': 'application/json',
+ } : {
+ Authorization: 'Bearer ' + getPocketBaseAuthToken(),
+ },
+ body: body ? JSON.stringify(body) : '',
+ })
+
+ if (!response || response.statusCode < 200 || response.statusCode >= 300) {
+ throw createAppError(500, actionName + ' 失败', {
+ statusCode: response ? response.statusCode : 0,
+ body: response ? String(response.body || '') : '',
+ })
+ }
+
+ return parseJsonResponse(response, actionName)
+}
+
+function normalizeText(value) {
+ return String(value || '').replace(/^\s+|\s+$/g, '')
+}
+
+function formatDateNumberSegment(date) {
+ const current = date || new Date()
+ const yyyy = String(current.getFullYear())
+ const mm = String(current.getMonth() + 1).padStart(2, '0')
+ const dd = String(current.getDate()).padStart(2, '0')
+ const hh = String(current.getHours()).padStart(2, '0')
+ const mi = String(current.getMinutes()).padStart(2, '0')
+ const ss = String(current.getSeconds()).padStart(2, '0')
+ return yyyy + mm + dd + hh + mi + ss
+}
+
+function buildDisplayNumber(prefix, authRecord, openid) {
+ const baseName = normalizeText(authRecord && typeof authRecord.getString === 'function' ? authRecord.getString('users_name') : '') || normalizeText(openid) || prefix
+ return baseName + '-' + formatDateNumberSegment(new Date())
+}
+
+function normalizeNumberValue(value, fieldName) {
+ if (value === '' || value === null || typeof value === 'undefined') {
+ throw createAppError(400, fieldName + ' 为必填项')
+ }
+
+ const num = Number(value)
+ if (!Number.isFinite(num)) {
+ throw createAppError(400, fieldName + ' 必须为数字')
+ }
+
+ return num
+}
+
+function normalizePositiveIntegerValue(value, fieldName) {
+ const num = normalizeNumberValue(value, fieldName)
+ if (num <= 0 || Math.floor(num) !== num) {
+ throw createAppError(400, fieldName + ' 必须为正整数')
+ }
+ return num
+}
+
+function normalizeOptionalNumberValue(value, fieldName) {
+ if (value === '' || value === null || typeof value === 'undefined') {
+ return undefined
+ }
+
+ const num = Number(value)
+ if (!Number.isFinite(num)) {
+ throw createAppError(400, fieldName + ' 必须为数字')
+ }
+
+ return num
+}
+
+function normalizeCartStatus(value) {
+ return normalizeText(value) || '有效'
+}
+
+function normalizeOrderStatus(value) {
+ return normalizeText(value) || '订单已生成'
+}
+
+function normalizeOrderSource(value) {
+ return normalizeText(value)
+}
+
+function normalizeJsonField(value, fieldName) {
+ if (value === null || typeof value === 'undefined' || value === '') {
+ throw createAppError(400, fieldName + ' 为必填项')
+ }
+
+ if (Array.isArray(value)) {
+ try {
+ return JSON.parse(JSON.stringify(value))
+ } catch (_err) {
+ throw createAppError(400, fieldName + ' 必须可序列化为 JSON')
+ }
+ }
+
+ if (typeof value === 'object') {
+ try {
+ return JSON.parse(JSON.stringify(value))
+ } catch (_err) {
+ throw createAppError(400, fieldName + ' 必须可序列化为 JSON')
+ }
+ }
+
+ if (typeof value === 'string') {
+ try {
+ return JSON.parse(value)
+ } catch (_err) {
+ throw createAppError(400, fieldName + ' 必须是 JSON 对象、数组或合法 JSON 字符串')
+ }
+ }
+
+ throw createAppError(400, fieldName + ' 必须是 JSON 对象、数组或合法 JSON 字符串')
+}
+
+function parseJsonFieldForOutput(value) {
+ if (value === null || typeof value === 'undefined' || value === '') {
+ return {}
+ }
+
+ if (Array.isArray(value)) {
+ return value
+ }
+
+ if (typeof value === 'object') {
+ try {
+ return JSON.parse(JSON.stringify(value))
+ } catch (_err) {
+ return {}
+ }
+ }
+
+ if (typeof value === 'string') {
+ try {
+ return JSON.parse(value)
+ } catch (_err) {
+ return {}
+ }
+ }
+
+ return {}
+}
+
+function findProductRecordByBusinessId(productId) {
+ const records = $app.findRecordsByFilter('tbl_product_list', 'prod_list_id = {:productId}', '', 1, 0, {
+ productId: productId,
+ })
+
+ return records.length ? records[0] : null
+}
+
+function buildProductInfo(record) {
+ if (!record) {
+ return {
+ prod_list_id: '',
+ prod_list_name: '',
+ prod_list_modelnumber: '',
+ prod_list_basic_price: null,
+ }
+ }
+
+ return {
+ 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_basic_price: record.get('prod_list_basic_price'),
+ }
+}
+
+function ensureProductExists(productId) {
+ const targetId = normalizeText(productId)
+ if (!targetId) {
+ throw createAppError(400, 'cart_product_id 为必填项')
+ }
+
+ const record = findProductRecordByBusinessId(targetId)
+ if (!record) {
+ throw createAppError(400, 'cart_product_id 对应产品不存在:' + targetId)
+ }
+
+ return record
+}
+
+function findCartRecordByBusinessId(cartId) {
+ const records = $app.findRecordsByFilter('tbl_cart', 'cart_id = {:cartId}', '', 1, 0, {
+ cartId: cartId,
+ })
+
+ return records.length ? records[0] : null
+}
+
+function findOrderRecordByBusinessId(orderId) {
+ const records = $app.findRecordsByFilter('tbl_order', 'order_id = {:orderId}', '', 1, 0, {
+ orderId: orderId,
+ })
+
+ return records.length ? records[0] : null
+}
+
+function ensureRecordOwner(record, fieldName, authOpenid, resourceLabel) {
+ const owner = normalizeText(record && typeof record.getString === 'function' ? record.getString(fieldName) : '')
+ if (!owner || owner !== normalizeText(authOpenid)) {
+ throw createAppError(403, '无权访问该' + resourceLabel)
+ }
+}
+
+function exportCartRecord(record, productRecord) {
+ const productInfo = buildProductInfo(productRecord || findProductRecordByBusinessId(record.getString('cart_product_id')))
+
+ return {
+ pb_id: record.id,
+ cart_id: record.getString('cart_id'),
+ cart_number: record.getString('cart_number'),
+ cart_create: String(record.get('cart_create') || ''),
+ cart_owner: record.getString('cart_owner'),
+ cart_product_id: record.getString('cart_product_id'),
+ cart_product_quantity: Number(record.get('cart_product_quantity') || 0),
+ cart_status: record.getString('cart_status'),
+ cart_at_price: Number(record.get('cart_at_price') || 0),
+ cart_remark: record.getString('cart_remark'),
+ product_name: productInfo.prod_list_name,
+ product_modelnumber: productInfo.prod_list_modelnumber,
+ product_basic_price: productInfo.prod_list_basic_price,
+ created: String(record.created || ''),
+ updated: String(record.updated || ''),
+ }
+}
+
+function exportOrderRecord(record) {
+ return {
+ pb_id: record.id,
+ order_id: record.getString('order_id'),
+ order_number: record.getString('order_number'),
+ order_create: String(record.get('order_create') || ''),
+ order_owner: record.getString('order_owner'),
+ order_source: record.getString('order_source'),
+ order_status: record.getString('order_status'),
+ order_source_id: record.getString('order_source_id'),
+ order_snap: parseJsonFieldForOutput(record.get('order_snap')),
+ order_amount: Number(record.get('order_amount') || 0),
+ order_remark: record.getString('order_remark'),
+ created: String(record.created || ''),
+ updated: String(record.updated || ''),
+ }
+}
+
+function exportAdminCartRecord(record) {
+ const productInfo = buildProductInfo(findProductRecordByBusinessId(record.cart_product_id))
+
+ return {
+ pb_id: String(record.id || ''),
+ cart_id: String(record.cart_id || ''),
+ cart_number: String(record.cart_number || ''),
+ cart_create: String(record.cart_create || ''),
+ cart_owner: String(record.cart_owner || ''),
+ cart_product_id: String(record.cart_product_id || ''),
+ cart_product_quantity: Number(record.cart_product_quantity || 0),
+ cart_status: String(record.cart_status || ''),
+ cart_at_price: Number(record.cart_at_price || 0),
+ cart_remark: String(record.cart_remark || ''),
+ product_name: productInfo.prod_list_name,
+ product_modelnumber: productInfo.prod_list_modelnumber,
+ product_basic_price: productInfo.prod_list_basic_price,
+ created: String(record.created || ''),
+ updated: String(record.updated || ''),
+ }
+}
+
+function exportAdminOrderRecord(record) {
+ return {
+ pb_id: String(record.id || ''),
+ order_id: String(record.order_id || ''),
+ order_number: String(record.order_number || ''),
+ order_create: String(record.order_create || ''),
+ order_owner: String(record.order_owner || ''),
+ order_source: String(record.order_source || ''),
+ order_status: String(record.order_status || ''),
+ order_source_id: String(record.order_source_id || ''),
+ order_snap: parseJsonFieldForOutput(record.order_snap),
+ order_amount: Number(record.order_amount || 0),
+ order_remark: String(record.order_remark || ''),
+ created: String(record.created || ''),
+ updated: String(record.updated || ''),
+ }
+}
+
+function exportAdminManageUser(userRecord, groupedCarts, groupedOrders) {
+ const openid = String(userRecord.openid || '')
+ const carts = groupedCarts[openid] || []
+ const orders = groupedOrders[openid] || []
+ let cartTotalQuantity = 0
+ let orderTotalAmount = 0
+
+ for (let i = 0; i < carts.length; i += 1) {
+ cartTotalQuantity += Number(carts[i].cart_product_quantity || 0)
+ }
+ for (let i = 0; i < orders.length; i += 1) {
+ orderTotalAmount += Number(orders[i].order_amount || 0)
+ }
+
+ return {
+ pb_id: String(userRecord.id || ''),
+ openid: openid,
+ users_id: String(userRecord.users_id || ''),
+ users_name: String(userRecord.users_name || ''),
+ users_phone: String(userRecord.users_phone || ''),
+ users_level: String(userRecord.users_level || ''),
+ users_level_name: userService.resolveUserLevelName(String(userRecord.users_level || '')),
+ users_type: String(userRecord.users_type || ''),
+ users_idtype: String(userRecord.users_idtype || ''),
+ users_id_number: String(userRecord.users_id_number || ''),
+ users_status: String(userRecord.users_status || ''),
+ users_rank_level: userRecord.users_rank_level === null || typeof userRecord.users_rank_level === 'undefined'
+ ? null
+ : Number(userRecord.users_rank_level),
+ users_auth_type: userRecord.users_auth_type === null || typeof userRecord.users_auth_type === 'undefined'
+ ? null
+ : Number(userRecord.users_auth_type),
+ users_tag: String(userRecord.users_tag || ''),
+ company_id: String(userRecord.company_id || ''),
+ users_parent_id: String(userRecord.users_parent_id || ''),
+ users_promo_code: String(userRecord.users_promo_code || ''),
+ usergroups_id: String(userRecord.usergroups_id || ''),
+ users_picture: String(userRecord.users_picture || ''),
+ users_id_pic_a: String(userRecord.users_id_pic_a || ''),
+ users_id_pic_b: String(userRecord.users_id_pic_b || ''),
+ users_title_picture: String(userRecord.users_title_picture || ''),
+ cart_count: carts.length,
+ cart_total_quantity: cartTotalQuantity,
+ order_count: orders.length,
+ order_total_amount: orderTotalAmount,
+ carts: carts,
+ orders: orders,
+ created: String(userRecord.created || ''),
+ updated: String(userRecord.updated || ''),
+ }
+}
+
+function listCarts(authOpenid, payload) {
+ const records = $app.findRecordsByFilter('tbl_cart', 'cart_owner = {:owner}', '-cart_create', 500, 0, {
+ owner: authOpenid,
+ })
+ const keyword = normalizeText(payload.keyword).toLowerCase()
+ const cartStatus = normalizeText(payload.cart_status)
+ const cartNumber = normalizeText(payload.cart_number)
+ const result = []
+
+ for (let i = 0; i < records.length; i += 1) {
+ const item = exportCartRecord(records[i])
+ const matchedKeyword = !keyword
+ || item.cart_id.toLowerCase().indexOf(keyword) !== -1
+ || item.cart_number.toLowerCase().indexOf(keyword) !== -1
+ || item.cart_product_id.toLowerCase().indexOf(keyword) !== -1
+ || normalizeText(item.product_name).toLowerCase().indexOf(keyword) !== -1
+ const matchedStatus = !cartStatus || item.cart_status === cartStatus
+ const matchedNumber = !cartNumber || item.cart_number === cartNumber
+
+ if (matchedKeyword && matchedStatus && matchedNumber) {
+ result.push(item)
+ }
+ }
+
+ return result
+}
+
+function getCartDetail(authOpenid, cartId) {
+ const record = findCartRecordByBusinessId(normalizeText(cartId))
+ if (!record) {
+ throw createAppError(404, '未找到对应购物车记录')
+ }
+
+ ensureRecordOwner(record, 'cart_owner', authOpenid, '购物车记录')
+ return exportCartRecord(record)
+}
+
+function createCart(authState, payload) {
+ const productRecord = ensureProductExists(payload.cart_product_id)
+ const collection = $app.findCollectionByNameOrId('tbl_cart')
+ const record = new Record(collection)
+
+ record.set('cart_id', buildBusinessId('CART'))
+ record.set('cart_number', normalizeText(payload.cart_number) || buildDisplayNumber('CART', authState.authRecord, authState.openid))
+ record.set('cart_owner', authState.openid)
+ record.set('cart_product_id', productRecord.getString('prod_list_id'))
+ record.set('cart_product_quantity', normalizePositiveIntegerValue(payload.cart_product_quantity, 'cart_product_quantity'))
+ record.set('cart_status', normalizeCartStatus(payload.cart_status))
+ record.set('cart_at_price', normalizeNumberValue(payload.cart_at_price, 'cart_at_price'))
+ record.set('cart_remark', normalizeText(payload.cart_remark))
+
+ try {
+ $app.save(record)
+ } catch (err) {
+ throw createAppError(400, '创建购物车记录失败', {
+ originalMessage: (err && err.message) || '未知错误',
+ originalData: (err && err.data) || {},
+ })
+ }
+
+ logger.info('购物车记录创建成功', {
+ cart_id: record.getString('cart_id'),
+ cart_owner: authState.openid,
+ })
+
+ return exportCartRecord(record, productRecord)
+}
+
+function updateCart(authOpenid, payload) {
+ const record = findCartRecordByBusinessId(normalizeText(payload.cart_id))
+ if (!record) {
+ throw createAppError(404, '未找到待修改的购物车记录')
+ }
+
+ ensureRecordOwner(record, 'cart_owner', authOpenid, '购物车记录')
+
+ let productRecord = null
+ if (typeof payload.cart_number !== 'undefined') {
+ record.set('cart_number', normalizeText(payload.cart_number) || record.getString('cart_number'))
+ }
+ if (typeof payload.cart_product_id !== 'undefined') {
+ productRecord = ensureProductExists(payload.cart_product_id)
+ record.set('cart_product_id', productRecord.getString('prod_list_id'))
+ }
+ if (typeof payload.cart_product_quantity !== 'undefined') {
+ record.set('cart_product_quantity', normalizePositiveIntegerValue(payload.cart_product_quantity, 'cart_product_quantity'))
+ }
+ if (typeof payload.cart_status !== 'undefined') {
+ record.set('cart_status', normalizeCartStatus(payload.cart_status))
+ }
+ if (typeof payload.cart_at_price !== 'undefined') {
+ record.set('cart_at_price', normalizeNumberValue(payload.cart_at_price, 'cart_at_price'))
+ }
+ if (typeof payload.cart_remark !== 'undefined') {
+ record.set('cart_remark', normalizeText(payload.cart_remark))
+ }
+
+ try {
+ $app.save(record)
+ } catch (err) {
+ throw createAppError(400, '更新购物车记录失败', {
+ originalMessage: (err && err.message) || '未知错误',
+ originalData: (err && err.data) || {},
+ })
+ }
+
+ logger.info('购物车记录更新成功', {
+ cart_id: record.getString('cart_id'),
+ cart_owner: authOpenid,
+ })
+
+ return exportCartRecord(record, productRecord)
+}
+
+function deleteCart(authOpenid, cartId) {
+ const record = findCartRecordByBusinessId(normalizeText(cartId))
+ if (!record) {
+ throw createAppError(404, '未找到待删除的购物车记录')
+ }
+
+ ensureRecordOwner(record, 'cart_owner', authOpenid, '购物车记录')
+
+ try {
+ $app.delete(record)
+ } catch (err) {
+ throw createAppError(400, '删除购物车记录失败', {
+ originalMessage: (err && err.message) || '未知错误',
+ originalData: (err && err.data) || {},
+ })
+ }
+
+ logger.info('购物车记录删除成功', {
+ cart_id: record.getString('cart_id'),
+ cart_owner: authOpenid,
+ })
+
+ return {
+ cart_id: normalizeText(cartId),
+ }
+}
+
+function listOrders(authOpenid, payload) {
+ const records = $app.findRecordsByFilter('tbl_order', 'order_owner = {:owner}', '-order_create', 500, 0, {
+ owner: authOpenid,
+ })
+ const keyword = normalizeText(payload.keyword).toLowerCase()
+ const orderStatus = normalizeText(payload.order_status)
+ const orderSource = normalizeText(payload.order_source)
+ const result = []
+
+ for (let i = 0; i < records.length; i += 1) {
+ const item = exportOrderRecord(records[i])
+ const matchedKeyword = !keyword
+ || item.order_id.toLowerCase().indexOf(keyword) !== -1
+ || item.order_number.toLowerCase().indexOf(keyword) !== -1
+ || item.order_source_id.toLowerCase().indexOf(keyword) !== -1
+ const matchedStatus = !orderStatus || item.order_status === orderStatus
+ const matchedSource = !orderSource || item.order_source === orderSource
+
+ if (matchedKeyword && matchedStatus && matchedSource) {
+ result.push(item)
+ }
+ }
+
+ return result
+}
+
+function getOrderDetail(authOpenid, orderId) {
+ const record = findOrderRecordByBusinessId(normalizeText(orderId))
+ if (!record) {
+ throw createAppError(404, '未找到对应订单记录')
+ }
+
+ ensureRecordOwner(record, 'order_owner', authOpenid, '订单记录')
+ return exportOrderRecord(record)
+}
+
+function createOrder(authState, payload) {
+ const collection = $app.findCollectionByNameOrId('tbl_order')
+ const record = new Record(collection)
+
+ record.set('order_id', buildBusinessId('ORDER'))
+ record.set('order_number', normalizeText(payload.order_number) || buildDisplayNumber('ORDER', authState.authRecord, authState.openid))
+ record.set('order_owner', authState.openid)
+ record.set('order_source', normalizeOrderSource(payload.order_source))
+ record.set('order_status', normalizeOrderStatus(payload.order_status))
+ record.set('order_source_id', normalizeText(payload.order_source_id))
+ record.set('order_snap', normalizeJsonField(payload.order_snap, 'order_snap'))
+ record.set('order_amount', normalizeNumberValue(payload.order_amount, 'order_amount'))
+ record.set('order_remark', normalizeText(payload.order_remark))
+
+ try {
+ $app.save(record)
+ } catch (err) {
+ throw createAppError(400, '创建订单失败', {
+ originalMessage: (err && err.message) || '未知错误',
+ originalData: (err && err.data) || {},
+ })
+ }
+
+ logger.info('订单创建成功', {
+ order_id: record.getString('order_id'),
+ order_owner: authState.openid,
+ })
+
+ return exportOrderRecord(record)
+}
+
+function updateOrder(authOpenid, payload) {
+ const record = findOrderRecordByBusinessId(normalizeText(payload.order_id))
+ if (!record) {
+ throw createAppError(404, '未找到待修改的订单')
+ }
+
+ ensureRecordOwner(record, 'order_owner', authOpenid, '订单记录')
+
+ if (typeof payload.order_number !== 'undefined') {
+ record.set('order_number', normalizeText(payload.order_number) || record.getString('order_number'))
+ }
+ if (typeof payload.order_source !== 'undefined') {
+ const nextSource = normalizeOrderSource(payload.order_source)
+ if (!nextSource) {
+ throw createAppError(400, 'order_source 为必填项')
+ }
+ record.set('order_source', nextSource)
+ }
+ if (typeof payload.order_status !== 'undefined') {
+ record.set('order_status', normalizeOrderStatus(payload.order_status))
+ }
+ if (typeof payload.order_source_id !== 'undefined') {
+ const nextSourceId = normalizeText(payload.order_source_id)
+ if (!nextSourceId) {
+ throw createAppError(400, 'order_source_id 为必填项')
+ }
+ record.set('order_source_id', nextSourceId)
+ }
+ if (typeof payload.order_snap !== 'undefined') {
+ record.set('order_snap', normalizeJsonField(payload.order_snap, 'order_snap'))
+ }
+ if (typeof payload.order_amount !== 'undefined') {
+ record.set('order_amount', normalizeNumberValue(payload.order_amount, 'order_amount'))
+ }
+ if (typeof payload.order_remark !== 'undefined') {
+ record.set('order_remark', normalizeText(payload.order_remark))
+ }
+
+ try {
+ $app.save(record)
+ } catch (err) {
+ throw createAppError(400, '更新订单失败', {
+ originalMessage: (err && err.message) || '未知错误',
+ originalData: (err && err.data) || {},
+ })
+ }
+
+ logger.info('订单更新成功', {
+ order_id: record.getString('order_id'),
+ order_owner: authOpenid,
+ })
+
+ return exportOrderRecord(record)
+}
+
+function deleteOrder(authOpenid, orderId) {
+ const record = findOrderRecordByBusinessId(normalizeText(orderId))
+ if (!record) {
+ throw createAppError(404, '未找到待删除的订单')
+ }
+
+ ensureRecordOwner(record, 'order_owner', authOpenid, '订单记录')
+
+ try {
+ $app.delete(record)
+ } catch (err) {
+ throw createAppError(400, '删除订单失败', {
+ originalMessage: (err && err.message) || '未知错误',
+ originalData: (err && err.data) || {},
+ })
+ }
+
+ logger.info('订单删除成功', {
+ order_id: record.getString('order_id'),
+ order_owner: authOpenid,
+ })
+
+ return {
+ order_id: normalizeText(orderId),
+ }
+}
+
+function exportManageUser(userRecord, groupedCarts, groupedOrders) {
+ const openid = userRecord.getString('openid')
+ const carts = groupedCarts[openid] || []
+ const orders = groupedOrders[openid] || []
+ let cartTotalQuantity = 0
+ let orderTotalAmount = 0
+
+ for (let i = 0; i < carts.length; i += 1) {
+ cartTotalQuantity += Number(carts[i].cart_product_quantity || 0)
+ }
+ for (let i = 0; i < orders.length; i += 1) {
+ orderTotalAmount += Number(orders[i].order_amount || 0)
+ }
+
+ return {
+ pb_id: userRecord.id,
+ openid: openid,
+ users_id: userRecord.getString('users_id'),
+ users_name: userRecord.getString('users_name'),
+ users_phone: userRecord.getString('users_phone'),
+ users_type: userRecord.getString('users_type'),
+ users_idtype: userRecord.getString('users_idtype'),
+ company_id: userRecord.getString('company_id'),
+ cart_count: carts.length,
+ cart_total_quantity: cartTotalQuantity,
+ order_count: orders.length,
+ order_total_amount: orderTotalAmount,
+ carts: carts,
+ orders: orders,
+ created: String(userRecord.created || ''),
+ updated: String(userRecord.updated || ''),
+ }
+}
+
+function listManageUsersCartOrders(payload) {
+ const keyword = normalizeText(payload.keyword).toLowerCase()
+ const userRecords = $app.findRecordsByFilter('tbl_auth_users', '', '-users_id', 500, 0)
+ const cartRecords = $app.findRecordsByFilter('tbl_cart', '', '-cart_create', 1000, 0)
+ const orderRecords = $app.findRecordsByFilter('tbl_order', '', '-order_create', 1000, 0)
+ const groupedCarts = {}
+ const groupedOrders = {}
+
+ for (let i = 0; i < cartRecords.length; i += 1) {
+ const owner = String(cartRecords[i].cart_owner || '')
+ if (!groupedCarts[owner]) {
+ groupedCarts[owner] = []
+ }
+ groupedCarts[owner].push(exportAdminCartRecord(cartRecords[i]))
+ }
+
+ for (let i = 0; i < orderRecords.length; i += 1) {
+ const owner = String(orderRecords[i].order_owner || '')
+ if (!groupedOrders[owner]) {
+ groupedOrders[owner] = []
+ }
+ groupedOrders[owner].push(exportAdminOrderRecord(orderRecords[i]))
+ }
+
+ const result = []
+ for (let i = 0; i < userRecords.length; i += 1) {
+ const item = exportAdminManageUser(userRecords[i], groupedCarts, groupedOrders)
+ const matchedKeyword = !keyword
+ || normalizeText(item.openid).toLowerCase().indexOf(keyword) !== -1
+ || normalizeText(item.users_id).toLowerCase().indexOf(keyword) !== -1
+ || normalizeText(item.users_name).toLowerCase().indexOf(keyword) !== -1
+ || normalizeText(item.users_phone).toLowerCase().indexOf(keyword) !== -1
+
+ if (matchedKeyword) {
+ result.push(item)
+ }
+ }
+
+ return {
+ items: result,
+ user_level_options: userService.getUserLevelOptions(),
+ }
+}
+
+function updateManageUser(payload) {
+ return userService.updateManagedUserProfile(payload).user
+}
+
+module.exports = {
+ listCarts,
+ getCartDetail,
+ createCart,
+ updateCart,
+ deleteCart,
+ listOrders,
+ getOrderDetail,
+ createOrder,
+ updateOrder,
+ deleteOrder,
+ listManageUsersCartOrders,
+ updateManageUser,
+}
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/dictionaryService.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/dictionaryService.js
index 747689c..092394c 100644
--- a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/dictionaryService.js
+++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/dictionaryService.js
@@ -99,6 +99,17 @@ function exportDictionaryRecord(record) {
}
}
+function sortDictionaryItems(items) {
+ return (Array.isArray(items) ? items.slice() : []).sort(function (a, b) {
+ const sortDiff = Number(a.sortOrder || 0) - Number(b.sortOrder || 0)
+ if (sortDiff !== 0) {
+ return sortDiff
+ }
+
+ return String(a.enum || '').localeCompare(String(b.enum || ''))
+ })
+}
+
function findDictionaryByName(dictName) {
const records = $app.findRecordsByFilter('tbl_system_dict', 'dict_name = {:dictName}', '', 1, 0, {
dictName: dictName,
@@ -157,6 +168,15 @@ function getDictionaryByName(dictName) {
return exportDictionaryRecord(record)
}
+function getDictionaryItemsByName(dictName) {
+ const dictionary = getDictionaryByName(dictName)
+ if (!dictionary.dict_word_is_enabled) {
+ return []
+ }
+
+ return sortDictionaryItems(dictionary.items)
+}
+
function createDictionary(payload) {
ensureDictionaryNameUnique(payload.dict_name)
@@ -192,9 +212,9 @@ function updateDictionary(payload) {
throw createAppError(404, '未找到待修改的字典')
}
- ensureDictionaryNameUnique(payload.dict_name, record.id)
+ const immutableName = record.getString('dict_name')
- record.set('dict_name', payload.dict_name)
+ record.set('dict_name', immutableName)
record.set('dict_word_is_enabled', payload.dict_word_is_enabled)
record.set('dict_word_parent_id', payload.dict_word_parent_id)
record.set('dict_word_remark', payload.dict_word_remark)
@@ -210,7 +230,7 @@ function updateDictionary(payload) {
}
logger.info('字典修改成功', {
- dict_name: payload.dict_name,
+ dict_name: immutableName,
original_dict_name: payload.original_dict_name,
})
@@ -245,6 +265,7 @@ function deleteDictionary(dictName) {
module.exports = {
listDictionaries,
getDictionaryByName,
+ getDictionaryItemsByName,
createDictionary,
updateDictionary,
deleteDictionary,
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/documentService.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/documentService.js
index 4202646..bbb8796 100644
--- a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/documentService.js
+++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/documentService.js
@@ -435,23 +435,30 @@ function deleteAttachment(attachmentId) {
function listDocuments(payload) {
const allRecords = $app.findRecordsByFilter('tbl_document', '', '', 500, 0)
- const keyword = String(payload.keyword || '').toLowerCase()
+ const titleKeyword = String(payload.title_keyword || '').toLowerCase().trim()
const status = String(payload.status || '')
const type = String(payload.document_type || '')
const result = []
for (let i = 0; i < allRecords.length; i += 1) {
const item = exportDocumentRecord(allRecords[i])
- const matchedKeyword = !keyword
- || item.document_id.toLowerCase().indexOf(keyword) !== -1
- || item.document_title.toLowerCase().indexOf(keyword) !== -1
- || item.document_subtitle.toLowerCase().indexOf(keyword) !== -1
- || item.document_summary.toLowerCase().indexOf(keyword) !== -1
- || item.document_keywords.toLowerCase().indexOf(keyword) !== -1
+ const matchedTitleKeyword = !titleKeyword
+ || item.document_title.toLowerCase().indexOf(titleKeyword) !== -1
const matchedStatus = !status || item.document_status === status
- const matchedType = !type || item.document_type === type
+ const matchedType = !type || String(item.document_type || '')
+ .split('|')
+ .map(function (token) { return String(token || '').trim() })
+ .some(function (token) {
+ if (!token) {
+ return false
+ }
+ if (token === type) {
+ return true
+ }
+ return token.indexOf(type + '@') === 0
+ })
- if (matchedKeyword && matchedStatus && matchedType) {
+ if (matchedTitleKeyword && matchedStatus && matchedType) {
result.push(item)
}
}
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/productService.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/productService.js
index 1544735..d046510 100644
--- a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/productService.js
+++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/productService.js
@@ -1,6 +1,9 @@
const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.js`)
const { createAppError } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/appError.js`)
const documentService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/documentService.js`)
+const dictionaryService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/dictionaryService.js`)
+
+const USER_LEVEL_DICT_NAME = '数据-会员等级'
function buildBusinessId(prefix) {
return prefix + '-' + new Date().getTime() + '-' + $security.randomString(6)
@@ -37,6 +40,16 @@ function normalizeSortValue(value) {
}
function normalizePipeValues(value) {
+ if (Array.isArray(value)) {
+ return value
+ .map(function (item) {
+ return normalizeText(item)
+ })
+ .filter(function (item) {
+ return !!item
+ })
+ }
+
return String(value || '')
.split('|')
.map(function (item) {
@@ -70,6 +83,64 @@ function normalizeRequiredCategory(value) {
return values[0]
}
+function normalizePositiveSort(rawSort, fieldName, fallbackSort) {
+ if (rawSort === '' || rawSort === null || typeof rawSort === 'undefined') {
+ return fallbackSort
+ }
+
+ const num = Number(rawSort)
+ if (!Number.isFinite(num) || num <= 0 || Math.floor(num) !== num) {
+ throw createAppError(400, fieldName + ' 必须为正整数')
+ }
+
+ return num
+}
+
+function normalizePositiveSortForOutput(rawSort, fallbackSort) {
+ if (rawSort === '' || rawSort === null || typeof rawSort === 'undefined') {
+ return fallbackSort
+ }
+
+ const num = Number(rawSort)
+ if (!Number.isFinite(num) || num <= 0 || Math.floor(num) !== num) {
+ return fallbackSort
+ }
+
+ return num
+}
+
+function sortParameterRows(rows) {
+ return rows
+ .slice()
+ .sort(function (a, b) {
+ const sortDiff = Number(a.sort || 0) - Number(b.sort || 0)
+ if (sortDiff !== 0) {
+ return sortDiff
+ }
+
+ return Number(a.__inputIndex || 0) - Number(b.__inputIndex || 0)
+ })
+ .map(function (item) {
+ return {
+ name: item.name,
+ value: item.value,
+ sort: Number(item.sort || 0),
+ }
+ })
+}
+
+function safeObjectKeys(source) {
+ if (!source || typeof source !== 'object') {
+ return []
+ }
+
+ try {
+ return Object.keys(source)
+ } catch (_error) {
+ return []
+ }
+}
+
function buildCategoryRankMap(records) {
const grouped = {}
for (let i = 0; i < records.length; i += 1) {
@@ -118,16 +189,18 @@ function normalizeParameters(value) {
const result = []
const indexByName = {}
- function upsert(nameValue, rawValue) {
+ function upsert(nameValue, rawValue, rawSort, fallbackSort, inputIndex) {
const name = normalizeText(nameValue)
if (!name) {
return
}
const normalizedValue = rawValue === null || typeof rawValue === 'undefined' ? '' : String(rawValue)
+ const normalizedSort = normalizePositiveSort(rawSort, 'prod_list_parameters.sort', fallbackSort)
const existingIndex = indexByName[name]
if (typeof existingIndex === 'number') {
result[existingIndex].value = normalizedValue
+ result[existingIndex].sort = normalizedSort
return
}
@@ -135,6 +208,8 @@ function normalizeParameters(value) {
result.push({
name: name,
value: normalizedValue,
+ sort: normalizedSort,
+ __inputIndex: inputIndex,
})
}
@@ -144,21 +219,21 @@ function normalizeParameters(value) {
if (!item) {
continue
}
- upsert(item.name || item.key, item.value)
+ upsert(item.name || item.key, item.value, item.sort, i + 1, i + 1)
}
- return result
+ return sortParameterRows(result)
}
if (typeof value !== 'object') {
throw createAppError(400, 'prod_list_parameters 必须是数组或对象')
}
- const keys = Object.keys(value)
+ const keys = safeObjectKeys(value)
for (let i = 0; i < keys.length; i += 1) {
- upsert(keys[i], value[keys[i]])
+ upsert(keys[i], value[keys[i]], '', i + 1, i + 1)
}
- return result
+ return sortParameterRows(result)
}
function normalizeParametersForOutput(value) {
@@ -166,10 +241,37 @@ function normalizeParametersForOutput(value) {
return []
}
+ function pushOrUpdate(targetRows, indexByName, nameValue, rawValue, rawSort, fallbackSort, inputIndex) {
+ const name = normalizeText(nameValue)
+ if (!name) {
+ return
+ }
+
+ const normalizedValue = rawValue === null || typeof rawValue === 'undefined' ? '' : String(rawValue)
+ const normalizedSort = normalizePositiveSortForOutput(rawSort, fallbackSort)
+ const existingIndex = indexByName[name]
+ if (typeof existingIndex === 'number') {
+ targetRows[existingIndex].value = normalizedValue
+ targetRows[existingIndex].sort = normalizedSort
+ return
+ }
+
+ indexByName[name] = targetRows.length
+ targetRows.push({
+ name: name,
+ value: normalizedValue,
+ sort: normalizedSort,
+ __inputIndex: inputIndex,
+ })
+ }
+
let source = value
if (typeof source === 'string') {
try {
source = JSON.parse(source)
+ if (source === null) {
+ return []
+ }
} catch (_error) {
const raw = normalizeText(source)
if (raw.indexOf('map[') === 0 && raw.endsWith(']')) {
@@ -191,16 +293,9 @@ function normalizeParametersForOutput(value) {
continue
}
const val = pair.slice(separatorIndex + 1)
- const normalizedValue = val === null || typeof val === 'undefined' ? '' : String(val)
- const existingIndex = indexByName[key]
- if (typeof existingIndex === 'number') {
- result[existingIndex].value = normalizedValue
- } else {
- indexByName[key] = result.length
- result.push({ name: key, value: normalizedValue })
- }
+ pushOrUpdate(result, indexByName, key, val, '', i + 1, i + 1)
}
- return result
+ return sortParameterRows(result)
}
return []
}
@@ -214,47 +309,165 @@ function normalizeParametersForOutput(value) {
if (!item) {
continue
}
- const name = normalizeText(item.name || item.key)
- if (!name) {
- continue
- }
- const normalizedValue = item.value === null || typeof item.value === 'undefined' ? '' : String(item.value)
- const existingIndex = indexByName[name]
- if (typeof existingIndex === 'number') {
- mapped[existingIndex].value = normalizedValue
- } else {
- indexByName[name] = mapped.length
- mapped.push({ name: name, value: normalizedValue })
- }
+ pushOrUpdate(mapped, indexByName, item.name || item.key, item.value, item.sort, i + 1, i + 1)
}
- return mapped
+ return sortParameterRows(mapped)
}
if (typeof source !== 'object') {
return []
}
+ if (source === null) {
+ return []
+ }
+
// Some PocketBase/Goja map-like values are not directly enumerable; roundtrip to plain object.
try {
source = JSON.parse(JSON.stringify(source))
} catch (_error) {}
+ if (source === null || typeof source !== 'object') {
+ return []
+ }
+
const result = []
const indexByName = {}
- const keys = Object.keys(source)
+ const keys = safeObjectKeys(source)
for (let i = 0; i < keys.length; i += 1) {
- const name = normalizeText(keys[i])
- if (!name) {
+ pushOrUpdate(result, indexByName, keys[i], source[keys[i]], '', i + 1, i + 1)
+ }
+
+ return sortParameterRows(result)
+}
+
+function getVipLevelValueMap() {
+ let items = []
+ try {
+ items = dictionaryService.getDictionaryItemsByName(USER_LEVEL_DICT_NAME)
+ } catch (_error) {
+ items = []
+ }
+ const result = {}
+
+ for (let i = 0; i < items.length; i += 1) {
+ const key = normalizeText(items[i].enum)
+ if (!key) {
continue
}
- const current = source[keys[i]]
- const normalizedValue = current === null || typeof current === 'undefined' ? '' : String(current)
- const existingIndex = indexByName[name]
- if (typeof existingIndex === 'number') {
- result[existingIndex].value = normalizedValue
- } else {
- indexByName[name] = result.length
- result.push({ name: name, value: normalizedValue })
+ result[key] = {
+ value: key,
+ label: String(items[i].description || ''),
+ }
+ }
+
+ return result
+}
+
+function normalizeVipPriceRows(value) {
+ if (value === null || typeof value === 'undefined' || value === '') {
+ return []
+ }
+
+ let source = value
+ if (typeof source === 'string') {
+ try {
+ source = JSON.parse(source)
+ } catch (_error) {
+ throw createAppError(400, 'prod_list_vip_price 必须为合法 JSON 数组')
+ }
+ }
+
+ if (!Array.isArray(source)) {
+ throw createAppError(400, 'prod_list_vip_price 必须为数组')
+ }
+
+ const levelMap = getVipLevelValueMap()
+ const result = []
+ const duplicatedLevelMap = {}
+
+ for (let i = 0; i < source.length; i += 1) {
+ const item = source[i] && typeof source[i] === 'object' ? source[i] : null
+ if (!item) {
+ continue
+ }
+
+ const viplevel = normalizeText(item.viplevel)
+ if (!viplevel) {
+ throw createAppError(400, 'prod_list_vip_price 第 ' + (i + 1) + ' 项 viplevel 为必填项')
+ }
+ if (!levelMap[viplevel]) {
+ throw createAppError(400, 'prod_list_vip_price 第 ' + (i + 1) + ' 项 viplevel 不在“数据-会员等级”字典中:' + viplevel)
+ }
+
+ const price = Number(item.price)
+ if (!Number.isFinite(price)) {
+ throw createAppError(400, 'prod_list_vip_price 第 ' + (i + 1) + ' 项 price 必须为数字')
+ }
+
+ if (duplicatedLevelMap[viplevel]) {
+ throw createAppError(400, 'prod_list_vip_price 中 viplevel 不允许重复:' + viplevel)
+ }
+
+ duplicatedLevelMap[viplevel] = true
+ result.push({
+ viplevel: viplevel,
+ price: price,
+ })
+ }
+
+ return result
+}
+
+function normalizeVipPriceRowsForOutput(value) {
+ if (value === null || typeof value === 'undefined' || value === '') {
+ return []
+ }
+
+ let source = value
+ if (typeof source === 'string') {
+ try {
+ source = JSON.parse(source)
+ } catch (_error) {
+ return []
+ }
+ }
+
+ if (!Array.isArray(source)) {
+ return []
+ }
+
+ const levelMap = getVipLevelValueMap()
+ const result = []
+ for (let i = 0; i < source.length; i += 1) {
+ const item = source[i] && typeof source[i] === 'object' ? source[i] : null
+ if (!item) {
+ continue
+ }
+
+ const viplevel = normalizeText(item.viplevel)
+ const price = Number(item.price)
+ if (!viplevel || !Number.isFinite(price)) {
+ continue
+ }
+
+ result.push({
+ viplevel: viplevel,
+ viplevel_name: levelMap[viplevel] ? levelMap[viplevel].label : '',
+ price: price,
+ })
+ }
+
+ return result
+}
+
+function normalizeAttachmentIdList(value) {
+ const result = []
+ const items = normalizePipeValues(value)
+
+ for (let i = 0; i < items.length; i += 1) {
+ if (result.indexOf(items[i]) === -1) {
+ result.push(items[i])
}
}
@@ -262,15 +475,17 @@ function normalizeParametersForOutput(value) {
}
function ensureAttachmentExists(attachmentId, fieldName) {
- const value = normalizeText(attachmentId)
- if (!value) {
+ const values = normalizeAttachmentIdList(attachmentId)
+ if (!values.length) {
return
}
- try {
- documentService.getAttachmentDetail(value)
- } catch (_err) {
- throw createAppError(400, fieldName + ' 对应附件不存在:' + value)
+ for (let i = 0; i < values.length; i += 1) {
+ try {
+ documentService.getAttachmentDetail(values[i])
+ } catch (_err) {
+ throw createAppError(400, fieldName + ' 对应附件不存在:' + values[i])
+ }
}
}
@@ -283,34 +498,51 @@ function findProductRecordByBusinessId(productId) {
}
function exportProductRecord(record, extra) {
- const iconId = record.getString('prod_list_icon')
- let iconAttachment = null
+ const iconIds = normalizeAttachmentIdList(record.getString('prod_list_icon'))
+ const iconAttachments = []
+ const iconUrls = []
- if (iconId) {
+ for (let i = 0; i < iconIds.length; i += 1) {
try {
- iconAttachment = documentService.getAttachmentDetail(iconId)
+ const attachment = documentService.getAttachmentDetail(iconIds[i])
+ iconAttachments.push(attachment)
+ iconUrls.push(attachment.attachments_url || '')
} catch (_error) {
- iconAttachment = null
+ continue
}
}
+ const firstIconAttachment = iconAttachments.length ? iconAttachments[0] : null
const parametersText = normalizeText(record.getString('prod_list_parameters'))
- const parametersFromText = parametersText ? normalizeParametersForOutput(parametersText) : {}
+ const parametersFromText = parametersText ? normalizeParametersForOutput(parametersText) : []
const parametersRaw = record.get('prod_list_parameters')
const parametersFromRaw = normalizeParametersForOutput(parametersRaw)
const parameters = parametersFromText.length ? parametersFromText : parametersFromRaw
+ const functionText = normalizeText(record.getString('prod_list_function'))
+ const functionFromText = functionText ? normalizeParametersForOutput(functionText) : []
+ const functionRaw = record.get('prod_list_function')
+ const functionFromRaw = normalizeParametersForOutput(functionRaw)
+ const functions = functionFromText.length ? functionFromText : functionFromRaw
+ const vipPriceText = normalizeText(record.getString('prod_list_vip_price'))
+ const vipPriceFromText = vipPriceText ? normalizeVipPriceRowsForOutput(vipPriceText) : []
+ const vipPriceFromRaw = normalizeVipPriceRowsForOutput(record.get('prod_list_vip_price'))
+ const vipPrice = vipPriceFromText.length ? vipPriceFromText : vipPriceFromRaw
return {
pb_id: record.id,
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_icon: iconId,
- prod_list_icon_attachment: iconAttachment,
- prod_list_icon_url: iconAttachment ? iconAttachment.attachments_url : '',
+ prod_list_icon: iconIds.join('|'),
+ prod_list_icon_ids: iconIds,
+ prod_list_icon_attachments: iconAttachments,
+ prod_list_icon_urls: iconUrls,
+ prod_list_icon_attachment: firstIconAttachment,
+ prod_list_icon_url: firstIconAttachment ? firstIconAttachment.attachments_url : '',
prod_list_description: record.getString('prod_list_description'),
prod_list_feature: record.getString('prod_list_feature'),
prod_list_parameters: parameters,
+ prod_list_function: functions,
prod_list_plantype: record.getString('prod_list_plantype'),
prod_list_category: record.getString('prod_list_category'),
prod_list_sort: Number(record.get('prod_list_sort') || 0),
@@ -320,6 +552,7 @@ function exportProductRecord(record, extra) {
prod_list_power_supply: record.getString('prod_list_power_supply'),
prod_list_tags: record.getString('prod_list_tags'),
prod_list_basic_price: record.get('prod_list_basic_price'),
+ prod_list_vip_price: vipPrice,
prod_list_status: record.getString('prod_list_status'),
prod_list_remark: record.getString('prod_list_remark'),
created: String(record.created || ''),
@@ -328,42 +561,57 @@ function exportProductRecord(record, extra) {
}
function listProducts(payload) {
- const allRecords = $app.findRecordsByFilter('tbl_product_list', '', '', 500, 0)
- const keyword = normalizeText(payload.keyword).toLowerCase()
- const status = normalizeText(payload.status)
- const category = normalizeText(payload.prod_list_category)
- const result = []
+ try {
+ const allRecords = $app.findRecordsByFilter('tbl_product_list', '', '', 500, 0)
+ const keyword = normalizeText(payload.keyword).toLowerCase()
+ const status = normalizeText(payload.status)
+ const category = normalizeText(payload.prod_list_category)
+ const result = []
- const allItems = []
- for (let i = 0; i < allRecords.length; i += 1) {
- allItems.push(exportProductRecord(allRecords[i]))
- }
-
- const rankMap = buildCategoryRankMap(allItems)
-
- for (let i = 0; i < allItems.length; i += 1) {
- const source = allItems[i]
- const item = Object.assign({}, source, {
- prod_list_category_rank: rankMap[source.prod_list_id] || 0,
- })
- const matchedKeyword = !keyword
- || 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_tags.toLowerCase().indexOf(keyword) !== -1
- const matchedStatus = !status || item.prod_list_status === status
- const matchedCategory = !category || item.prod_list_category === category
-
- if (matchedKeyword && matchedStatus && matchedCategory) {
- result.push(item)
+ const allItems = []
+ for (let i = 0; i < allRecords.length; i += 1) {
+ try {
+ allItems.push(exportProductRecord(allRecords[i]))
+ } catch (err) {
+ logger.error('产品记录导出失败,已跳过', {
+ pb_id: allRecords[i] && allRecords[i].id ? allRecords[i].id : '',
+ prod_list_id: allRecords[i] ? allRecords[i].getString('prod_list_id') : '',
+ errMsg: (err && err.message) || '未知错误',
+ })
+ }
}
+
+ const rankMap = buildCategoryRankMap(allItems)
+
+ for (let i = 0; i < allItems.length; i += 1) {
+ const source = allItems[i]
+ const item = Object.assign({}, source, {
+ prod_list_category_rank: rankMap[source.prod_list_id] || 0,
+ })
+ const matchedKeyword = !keyword
+ || 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_tags.toLowerCase().indexOf(keyword) !== -1
+ const matchedStatus = !status || item.prod_list_status === status
+ const matchedCategory = !category || item.prod_list_category === category
+
+ if (matchedKeyword && matchedStatus && matchedCategory) {
+ result.push(item)
+ }
+ }
+
+ result.sort(function (a, b) {
+ return String(b.updated || '').localeCompare(String(a.updated || ''))
+ })
+
+ return result
+ } catch (err) {
+ logger.error('产品列表构建失败,返回空列表兜底', {
+ errMsg: (err && err.message) || '未知错误',
+ })
+ return []
}
-
- result.sort(function (a, b) {
- return String(b.updated || '').localeCompare(String(a.updated || ''))
- })
-
- return result
}
function getProductDetail(productId) {
@@ -399,10 +647,11 @@ 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_icon', normalizeText(payload.prod_list_icon))
+ record.set('prod_list_icon', joinUniquePipeValues(payload.prod_list_icon))
record.set('prod_list_description', normalizeText(payload.prod_list_description))
record.set('prod_list_feature', normalizeText(payload.prod_list_feature))
record.set('prod_list_parameters', normalizeParameters(payload.prod_list_parameters))
+ record.set('prod_list_function', normalizeParameters(payload.prod_list_function))
record.set('prod_list_plantype', normalizeText(payload.prod_list_plantype))
record.set('prod_list_category', normalizeRequiredCategory(payload.prod_list_category))
record.set('prod_list_sort', normalizeSortValue(payload.prod_list_sort))
@@ -412,6 +661,7 @@ function createProduct(_userOpenid, payload) {
record.set('prod_list_tags', joinUniquePipeValues(payload.prod_list_tags))
record.set('prod_list_status', normalizeText(payload.prod_list_status))
record.set('prod_list_basic_price', normalizeOptionalNumberValue(payload.prod_list_basic_price, 'prod_list_basic_price'))
+ record.set('prod_list_vip_price', normalizeVipPriceRows(payload.prod_list_vip_price))
record.set('prod_list_remark', normalizeText(payload.prod_list_remark))
try {
@@ -448,10 +698,11 @@ 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_icon', normalizeText(payload.prod_list_icon))
+ record.set('prod_list_icon', joinUniquePipeValues(payload.prod_list_icon))
record.set('prod_list_description', normalizeText(payload.prod_list_description))
record.set('prod_list_feature', normalizeText(payload.prod_list_feature))
record.set('prod_list_parameters', normalizeParameters(payload.prod_list_parameters))
+ record.set('prod_list_function', normalizeParameters(payload.prod_list_function))
record.set('prod_list_plantype', normalizeText(payload.prod_list_plantype))
record.set('prod_list_category', normalizeRequiredCategory(payload.prod_list_category))
record.set('prod_list_sort', normalizeSortValue(payload.prod_list_sort))
@@ -461,6 +712,7 @@ function updateProduct(_userOpenid, payload) {
record.set('prod_list_tags', joinUniquePipeValues(payload.prod_list_tags))
record.set('prod_list_status', normalizeText(payload.prod_list_status))
record.set('prod_list_basic_price', normalizeOptionalNumberValue(payload.prod_list_basic_price, 'prod_list_basic_price'))
+ record.set('prod_list_vip_price', normalizeVipPriceRows(payload.prod_list_vip_price))
record.set('prod_list_remark', normalizeText(payload.prod_list_remark))
try {
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/sdkPermissionService.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/sdkPermissionService.js
index b85807b..ea6578b 100644
--- a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/sdkPermissionService.js
+++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/sdkPermissionService.js
@@ -100,6 +100,7 @@ function uniqueList(values) {
function listRoles() {
const records = $app.findRecordsByFilter(ROLE_COLLECTION, '', 'role_name', 500, 0)
+
return records.map(function (record) {
return {
pb_id: record.id,
@@ -353,7 +354,7 @@ function saveCollectionRules(payload) {
function listUsers(keyword, roleMap) {
const search = normalizeText(keyword).toLowerCase()
- const records = $app.findRecordsByFilter(USER_COLLECTION, '', '', 500, 0)
+ const records = $app.findRecordsByFilter(USER_COLLECTION, '', '-users_id', 500, 0)
const items = records.map(function (record) {
const usergroupsId = record.getString('usergroups_id')
const role = roleMap && roleMap[usergroupsId] ? roleMap[usergroupsId] : null
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/userService.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/userService.js
index b2b211c..cef3271 100644
--- a/pocket-base/bai_api_pb_hooks/bai_api_shared/services/userService.js
+++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/services/userService.js
@@ -2,12 +2,14 @@ const logger = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/logger.
const { createAppError } = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/utils/appError.js`)
const wechatService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/wechatService.js`)
const documentService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/documentService.js`)
+const dictionaryService = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/services/dictionaryService.js`)
const env = require(`${__hooks}/bai_api_pb_hooks/bai_api_shared/config/env.js`)
const GUEST_USER_TYPE = '游客'
const REGISTERED_USER_TYPE = '注册用户'
const WECHAT_ID_TYPE = 'WeChat'
const MANAGE_PLATFORM_ID_TYPE = 'ManagePlatform'
+const USER_LEVEL_DICT_NAME = '数据-会员等级'
const mutationLocks = {}
function buildUserId() {
@@ -42,6 +44,74 @@ function isAllProfileFieldsEmpty(record) {
return !record.getString('users_name') && !record.getString('users_phone') && !record.getString('users_picture')
}
+function normalizeText(value) {
+ return String(value || '').replace(/^\s+|\s+$/g, '')
+}
+
+function normalizeOptionalNumber(value, fieldName) {
+ const raw = normalizeText(value)
+ if (!raw) {
+ return null
+ }
+
+ const num = Number(raw)
+ if (!Number.isFinite(num)) {
+ throw createAppError(400, fieldName + ' 必须为数字')
+ }
+
+ return num
+}
+
+function getUserLevelItems() {
+ try {
+ return dictionaryService.getDictionaryItemsByName(USER_LEVEL_DICT_NAME)
+ } catch (_error) {
+ return []
+ }
+}
+
+function getUserLevelOptions() {
+ return getUserLevelItems().map(function (item) {
+ return {
+ value: String(item.enum || ''),
+ label: String(item.description || ''),
+ sort: Number(item.sortOrder || 0),
+ }
+ })
+}
+
+function resolveUserLevelName(usersLevel) {
+ const level = normalizeText(usersLevel)
+ if (!level) {
+ return ''
+ }
+
+ const items = getUserLevelItems()
+ for (let i = 0; i < items.length; i += 1) {
+ if (normalizeText(items[i].enum) === level) {
+ return String(items[i].description || '')
+ }
+ }
+
+ return ''
+}
+
+function ensureValidUserLevel(usersLevel) {
+ const level = normalizeText(usersLevel)
+ if (!level) {
+ return ''
+ }
+
+ const items = getUserLevelItems()
+ for (let i = 0; i < items.length; i += 1) {
+ if (normalizeText(items[i].enum) === level) {
+ return level
+ }
+ }
+
+ throw createAppError(400, 'users_level 不在“数据-会员等级”字典中')
+}
+
function withUserLock(lockKey, handler) {
if (mutationLocks[lockKey]) {
throw createAppError(429, '请求过于频繁,请稍后重试')
@@ -275,6 +345,7 @@ function enrichUser(userRecord) {
users_phone: userRecord.getString('users_phone'),
users_phone_masked: maskPhone(userRecord.getString('users_phone')),
users_level: userRecord.getString('users_level'),
+ users_level_name: resolveUserLevelName(userRecord.getString('users_level')),
users_tag: userRecord.getString('users_tag'),
users_picture: userPicture.id,
users_picture_url: userPicture.url,
@@ -438,7 +509,6 @@ function registerPlatformUser(payload) {
record.set('users_name', payload.users_name)
record.set('users_phone', payload.users_phone)
record.set('users_id_number', payload.users_id_number || '')
- record.set('users_level', payload.users_level || '')
record.set('users_type', payload.users_type || GUEST_USER_TYPE)
record.set('users_tag', payload.users_tag || '')
record.set('company_id', payload.company_id || '')
@@ -637,6 +707,99 @@ function updateWechatUserProfile(usersWxOpenid, payload) {
})
}
+function updateManagedUserProfile(payload) {
+ return withUserLock('manage-user-update:' + payload.openid, function () {
+ const currentUser = findUserByOpenid(payload.openid)
+ if (!currentUser) {
+ throw createAppError(404, '未找到待编辑的用户')
+ }
+
+ const nextPhone = Object.prototype.hasOwnProperty.call(payload, 'users_phone')
+ ? normalizeText(payload.users_phone)
+ : undefined
+
+ if (typeof nextPhone !== 'undefined' && nextPhone && nextPhone !== currentUser.getString('users_phone')) {
+ const samePhoneUsers = $app.findRecordsByFilter('tbl_auth_users', 'users_phone = {:phone}', '', 10, 0, {
+ phone: nextPhone,
+ })
+
+ for (let i = 0; i < samePhoneUsers.length; i += 1) {
+ if (samePhoneUsers[i].id !== currentUser.id) {
+ throw createAppError(400, '手机号已被注册')
+ }
+ }
+ }
+
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_name')) {
+ currentUser.set('users_name', normalizeText(payload.users_name))
+ }
+ if (typeof nextPhone !== 'undefined') {
+ currentUser.set('users_phone', nextPhone)
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_id_number')) {
+ currentUser.set('users_id_number', normalizeText(payload.users_id_number))
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_level')) {
+ currentUser.set('users_level', ensureValidUserLevel(payload.users_level))
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_type')) {
+ currentUser.set('users_type', normalizeText(payload.users_type))
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_tag')) {
+ currentUser.set('users_tag', normalizeText(payload.users_tag))
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'company_id')) {
+ currentUser.set('company_id', normalizeText(payload.company_id))
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_parent_id')) {
+ currentUser.set('users_parent_id', normalizeText(payload.users_parent_id))
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_promo_code')) {
+ currentUser.set('users_promo_code', normalizeText(payload.users_promo_code))
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'usergroups_id')) {
+ currentUser.set('usergroups_id', normalizeText(payload.usergroups_id))
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_status')) {
+ currentUser.set('users_status', normalizeText(payload.users_status))
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_rank_level')) {
+ currentUser.set('users_rank_level', normalizeOptionalNumber(payload.users_rank_level, 'users_rank_level'))
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_auth_type')) {
+ currentUser.set('users_auth_type', normalizeOptionalNumber(payload.users_auth_type, 'users_auth_type'))
+ }
+
+ applyUserAttachmentFields(currentUser, payload)
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_picture') && !normalizeText(payload.users_picture)) {
+ currentUser.set('users_picture', '')
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_id_pic_a') && !normalizeText(payload.users_id_pic_a)) {
+ currentUser.set('users_id_pic_a', '')
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_id_pic_b') && !normalizeText(payload.users_id_pic_b)) {
+ currentUser.set('users_id_pic_b', '')
+ }
+ if (Object.prototype.hasOwnProperty.call(payload, 'users_title_picture') && !normalizeText(payload.users_title_picture)) {
+ currentUser.set('users_title_picture', '')
+ }
+
+ saveAuthUserRecord(currentUser)
+
+ const user = enrichUser(currentUser)
+
+ logger.info('管理端更新用户资料成功', {
+ users_id: user.users_id,
+ openid: user.openid,
+ })
+
+ return {
+ status: 'update_success',
+ user: user,
+ }
+ })
+}
+
function refreshAuthToken(openid) {
const userRecord = findUserByOpenid(openid)
if (!userRecord) {
@@ -690,8 +853,11 @@ module.exports = {
authenticatePlatformUser,
ensureAuthToken,
updateWechatUserProfile,
+ updateManagedUserProfile,
refreshAuthToken,
issueAuthToken,
registerPlatformUser,
+ resolveUserLevelName,
+ getUserLevelOptions,
}
diff --git a/pocket-base/bai_api_pb_hooks/bai_api_shared/utils/response.js b/pocket-base/bai_api_pb_hooks/bai_api_shared/utils/response.js
index 1697d51..52f2400 100644
--- a/pocket-base/bai_api_pb_hooks/bai_api_shared/utils/response.js
+++ b/pocket-base/bai_api_pb_hooks/bai_api_shared/utils/response.js
@@ -53,7 +53,11 @@ function successWithToken(e, msg, data, token, code) {
function fail(e, msg, data, code) {
const meta = applyHttpMeta(e, code || 400, msg || '操作失败')
- return e.json(meta.statusCode, normalizePayloadData(data))
+ const payload = normalizePayloadData(data)
+ payload.statusCode = meta.statusCode
+ payload.errMsg = meta.errMsg
+ payload.message = meta.errMsg
+ return e.json(meta.statusCode, payload)
}
module.exports = {
diff --git a/pocket-base/bai_web_pb_hooks/pages/cart-order-manage.js b/pocket-base/bai_web_pb_hooks/pages/cart-order-manage.js
new file mode 100644
index 0000000..0cd139b
--- /dev/null
+++ b/pocket-base/bai_web_pb_hooks/pages/cart-order-manage.js
@@ -0,0 +1,9 @@
+routerAdd('GET', '/manage/cart-order-manage', function (e) {
+ const html = $template.loadFiles(
+ __hooks + '/bai_web_pb_hooks/views/cart-order-manage.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-head.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-body.html'
+ ).render({})
+
+ return e.html(200, html)
+})
diff --git a/pocket-base/bai_web_pb_hooks/pages/dictionary-manage.js b/pocket-base/bai_web_pb_hooks/pages/dictionary-manage.js
index 4474d7c..d4984dc 100644
--- a/pocket-base/bai_web_pb_hooks/pages/dictionary-manage.js
+++ b/pocket-base/bai_web_pb_hooks/pages/dictionary-manage.js
@@ -1,1081 +1,9 @@
routerAdd('GET', '/manage/dictionary-manage', function (e) {
- const html = `
-
-
-
-
- 字典管理
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
![预览原图]()
-
-
-
-
-
-
-`
+ const html = $template.loadFiles(
+ __hooks + '/bai_web_pb_hooks/views/dictionary-manage.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-head.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-body.html'
+ ).render({})
return e.html(200, html)
})
diff --git a/pocket-base/bai_web_pb_hooks/pages/document-manage.js b/pocket-base/bai_web_pb_hooks/pages/document-manage.js
index ccf84eb..6893a6c 100644
--- a/pocket-base/bai_web_pb_hooks/pages/document-manage.js
+++ b/pocket-base/bai_web_pb_hooks/pages/document-manage.js
@@ -1,1439 +1,9 @@
routerAdd('GET', '/manage/document-manage', function (e) {
- const html = `
-
-
-
-
- 文档管理
-
-
-
-
-
-
- 文档管理
-
-
返回主页
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
系统会根据生效日期和到期日期自动切换状态;都不填写时默认有效。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 文档列表
-
-
-
-
- | 标题 |
- 类型/状态 |
- 附件链接 |
- 更新时间 |
- 操作 |
-
-
-
- | 暂无数据,请先刷新列表。 |
-
-
-
-
-
-
-
-
-
![预览原图]()
-
-
-
-
-
-
-`
+ const html = $template.loadFiles(
+ __hooks + '/bai_web_pb_hooks/views/document-manage.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-head.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-body.html'
+ ).render({})
return e.html(200, html)
})
diff --git a/pocket-base/bai_web_pb_hooks/pages/index.js b/pocket-base/bai_web_pb_hooks/pages/index.js
index 0fc5caa..7afe66b 100644
--- a/pocket-base/bai_web_pb_hooks/pages/index.js
+++ b/pocket-base/bai_web_pb_hooks/pages/index.js
@@ -1,94 +1,9 @@
routerAdd('GET', '/manage', function (e) {
- const html = `
-
-
-
-
- 管理主页
-
-
-
-
-
-
-
-
-
-`
+ const html = $template.loadFiles(
+ __hooks + '/bai_web_pb_hooks/views/index.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-head.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-body.html'
+ ).render({})
return e.html(200, html)
})
diff --git a/pocket-base/bai_web_pb_hooks/pages/login.js b/pocket-base/bai_web_pb_hooks/pages/login.js
index 683b54a..85d3cb0 100644
--- a/pocket-base/bai_web_pb_hooks/pages/login.js
+++ b/pocket-base/bai_web_pb_hooks/pages/login.js
@@ -1,157 +1,9 @@
function renderLoginPage(e) {
- const html = `
-
-
-
-
- 登录
-
-
-
-
-
-
- 后台登录
- 请输入平台账号(邮箱或手机号)与密码,登录成功后会自动跳转到管理首页。
-
-
-
-
-
-
-`
+ const html = $template.loadFiles(
+ __hooks + '/bai_web_pb_hooks/views/login.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-head.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-body.html'
+ ).render({})
return e.html(200, html)
}
diff --git a/pocket-base/bai_web_pb_hooks/pages/product-manage.js b/pocket-base/bai_web_pb_hooks/pages/product-manage.js
index 2f84922..0fb8374 100644
--- a/pocket-base/bai_web_pb_hooks/pages/product-manage.js
+++ b/pocket-base/bai_web_pb_hooks/pages/product-manage.js
@@ -1,1461 +1,9 @@
routerAdd('GET', '/manage/product-manage', function (e) {
- const html = `
-
-
-
-
- 产品管理
-
-
-
-
-
-
- 产品管理
-
-
返回主页
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
请先选择产品分类后查看当前分类内排序位次。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- | 属性名 |
- 属性值 |
- 操作 |
-
-
-
-
-
-
-
-
-
仅支持 JSON 对象格式。导入时按属性名增量合并:同名覆盖、不同名新增,不会清空当前参数表。
-
-
-
-
-
-
-
-
-
-
-
-
![icon]()
-
暂无图标
-
-
-
-
选择后会在保存时上传,并写入 prod_list_icon。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 产品列表
-
-
-
-
- | 名称/型号 |
- 分类信息 |
- 标签 |
- 状态/价格 |
- 更新时间 |
- 操作 |
-
-
-
- | 暂无数据,请先查询。 |
-
-
-
-
-
-
-
-
-
-
-`
+ const html = $template.loadFiles(
+ __hooks + '/bai_web_pb_hooks/views/product-manage.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-head.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-body.html'
+ ).render({})
return e.html(200, html)
})
diff --git a/pocket-base/bai_web_pb_hooks/pages/sdk-permission-manage.js b/pocket-base/bai_web_pb_hooks/pages/sdk-permission-manage.js
index 3fdef03..a769ad9 100644
--- a/pocket-base/bai_web_pb_hooks/pages/sdk-permission-manage.js
+++ b/pocket-base/bai_web_pb_hooks/pages/sdk-permission-manage.js
@@ -1,745 +1,9 @@
routerAdd('GET', '/manage/sdk-permission-manage', function (e) {
- const html = `
-
-
-
-
- SDK 权限管理
-
-
-
-
-
-
- SDK 权限管理
- 这里管理的是 tbl_auth_users 用户通过 PocketBase SDK 直连数据库时的业务权限。ManagePlatform 会被视为你的业务管理员,但不会自动变成 PocketBase 原生 _superusers。
-
-
返回主页
-
-
-
-
-
-
-
-
-
-
- 角色管理
-
-
-
-
-
- | 名称 |
- 编码 |
- 状态 |
- 备注 |
- 操作 |
-
-
-
- | 暂无角色。 |
-
-
-
-
-
-
- 用户授权
-
-
-
-
-
-
-
-
- | 用户 |
- 身份类型 |
- 当前角色 |
- 授权 |
- 操作 |
-
-
-
- | 暂无用户。 |
-
-
-
-
-
-
- Collection 直连权限
-
-
-
-
-
- | 集合 |
- 当前角色权限 |
-
-
-
- | 暂无集合。 |
-
-
-
-
-
-
-
-
-
-
-`
+ const html = $template.loadFiles(
+ __hooks + '/bai_web_pb_hooks/views/sdk-permission-manage.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-head.html',
+ __hooks + '/bai_web_pb_hooks/shared/theme-body.html'
+ ).render({})
return e.html(200, html)
})
diff --git a/pocket-base/bai_web_pb_hooks/shared/theme-body.html b/pocket-base/bai_web_pb_hooks/shared/theme-body.html
new file mode 100644
index 0000000..bfdbf43
--- /dev/null
+++ b/pocket-base/bai_web_pb_hooks/shared/theme-body.html
@@ -0,0 +1,32 @@
+{{ define "theme_body" }}
+
+
+{{ end }}
diff --git a/pocket-base/bai_web_pb_hooks/shared/theme-head.html b/pocket-base/bai_web_pb_hooks/shared/theme-head.html
new file mode 100644
index 0000000..d79c550
--- /dev/null
+++ b/pocket-base/bai_web_pb_hooks/shared/theme-head.html
@@ -0,0 +1,186 @@
+{{ define "theme_head" }}
+
+
+{{ end }}
diff --git a/pocket-base/bai_web_pb_hooks/views/cart-order-manage.html b/pocket-base/bai_web_pb_hooks/views/cart-order-manage.html
new file mode 100644
index 0000000..b2ee0d7
--- /dev/null
+++ b/pocket-base/bai_web_pb_hooks/views/cart-order-manage.html
@@ -0,0 +1,407 @@
+
+
+
+
+
+ 用户信息及订单管理
+
+
+ {{ template "theme_head" . }}
+
+
+
+
+
+ {{ template "theme_body" . }}
+
+
diff --git a/pocket-base/bai_web_pb_hooks/views/dictionary-manage.html b/pocket-base/bai_web_pb_hooks/views/dictionary-manage.html
new file mode 100644
index 0000000..6f9db2b
--- /dev/null
+++ b/pocket-base/bai_web_pb_hooks/views/dictionary-manage.html
@@ -0,0 +1,1151 @@
+
+
+
+
+
+ 字典管理
+
+
+ {{ template "theme_head" . }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![预览原图]()
+
+
+
+
+
+ {{ template "theme_body" . }}
+
+
diff --git a/pocket-base/bai_web_pb_hooks/views/document-manage.html b/pocket-base/bai_web_pb_hooks/views/document-manage.html
new file mode 100644
index 0000000..0fff527
--- /dev/null
+++ b/pocket-base/bai_web_pb_hooks/views/document-manage.html
@@ -0,0 +1,1579 @@
+
+
+
+
+
+ 文档管理
+
+
+ {{ template "theme_head" . }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
系统会根据生效日期和到期日期自动切换状态;都不填写时默认有效。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 文档列表
+
+
+
+
+
+ | 标题 |
+ 类型/状态 |
+ 附件链接 |
+ 更新时间 |
+ 操作 |
+
+
+
+ | 暂无数据,请先刷新列表。 |
+
+
+
+
+
+
+
+
+
![预览原图]()
+
+
+
+
+
+ {{ template "theme_body" . }}
+
+
diff --git a/pocket-base/bai_web_pb_hooks/views/index.html b/pocket-base/bai_web_pb_hooks/views/index.html
new file mode 100644
index 0000000..ea0f606
--- /dev/null
+++ b/pocket-base/bai_web_pb_hooks/views/index.html
@@ -0,0 +1,96 @@
+
+
+
+
+
+ 管理主页
+
+
+ {{ template "theme_head" . }}
+
+
+
+
+
+
+ {{ template "theme_body" . }}
+
+
diff --git a/pocket-base/bai_web_pb_hooks/views/login.html b/pocket-base/bai_web_pb_hooks/views/login.html
new file mode 100644
index 0000000..d539c18
--- /dev/null
+++ b/pocket-base/bai_web_pb_hooks/views/login.html
@@ -0,0 +1,155 @@
+
+
+
+
+
+ 登录
+
+
+ {{ template "theme_head" . }}
+
+
+
+
+ 后台登录
+ 请输入平台账号(邮箱或手机号)与密码,登录成功后会自动跳转到管理首页。
+
+
+
+
+
+ {{ template "theme_body" . }}
+
+
\ No newline at end of file
diff --git a/pocket-base/bai_web_pb_hooks/views/product-manage.html b/pocket-base/bai_web_pb_hooks/views/product-manage.html
new file mode 100644
index 0000000..ca49c01
--- /dev/null
+++ b/pocket-base/bai_web_pb_hooks/views/product-manage.html
@@ -0,0 +1,2323 @@
+
+
+
+
+
+ 产品管理
+
+
+ {{ template "theme_head" . }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
请先选择产品分类后查看当前分类内排序位次。
+
+
+
+
+
+
+
+
+ | 会员等级 |
+ 会员价格 |
+
+
+
+
+
+
按“数据-会员等级”字典自动生成行,一行对应一个会员等级,只需要填写价格。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 属性名 |
+ 属性值 |
+ 操作 / 排序 |
+
+
+
+
+
+
+
+
+
推荐使用数组格式:每项包含 sort、name、value。也兼容旧对象格式。导入时按属性名增量合并:同名覆盖、不同名新增,不会清空当前参数表。
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | 功能名 |
+ 功能说明 |
+ 操作 / 排序 |
+
+
+
+
+
+
+
+
+
推荐使用数组格式:每项包含 sort、name、value。也兼容旧对象格式。导入时按功能名增量合并:同名覆盖、不同名新增,不会清空当前功能表。
+
+
+
+
+
+
+
+
+
+
+
+
+
支持多图上传。保存时会上传到附件表,并按上传后附件 ID 用 | 写入 prod_list_icon。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 产品列表
+
+
+
+
+ | 名称/型号 |
+ 分类信息 |
+ 标签 |
+ 状态/价格 |
+ 更新时间 |
+ 操作 |
+
+
+
+ | 暂无数据,请先查询。 |
+
+
+
+
+
+
+
+
+
复制产品
+
+
+
+
+
+
+
+
+
+
+
+ {{ template "theme_body" . }}
+
+
diff --git a/pocket-base/bai_web_pb_hooks/views/sdk-permission-manage.html b/pocket-base/bai_web_pb_hooks/views/sdk-permission-manage.html
new file mode 100644
index 0000000..5b42163
--- /dev/null
+++ b/pocket-base/bai_web_pb_hooks/views/sdk-permission-manage.html
@@ -0,0 +1,735 @@
+
+
+
+
+
+ SDK 权限管理
+
+
+ {{ template "theme_head" . }}
+
+
+
+
+ SDK 权限管理
+ 这里管理的是 tbl_auth_users 用户通过 PocketBase SDK 直连数据库时的业务权限。ManagePlatform 会被视为你的业务管理员,但不会自动变成 PocketBase 原生 _superusers。
+
+
返回主页
+
+
+
+
+
+
+
+
+
+ 角色管理
+
+
+
+
+
+ | 名称 |
+ 编码 |
+ 状态 |
+ 备注 |
+ 操作 |
+
+
+
+ | 暂无角色。 |
+
+
+
+
+
+
+ 用户授权
+
+
+
+
+
+
+
+
+ | 用户 |
+ 身份类型 |
+ 当前角色 |
+ 授权 |
+ 操作 |
+
+
+
+ | 暂无用户。 |
+
+
+
+
+
+
+ Collection 直连权限
+
+
+
+
+
+ | 集合 |
+ 当前角色权限 |
+
+
+
+ | 暂无集合。 |
+
+
+
+
+
+
+
+
+
+ {{ template "theme_body" . }}
+
+
\ No newline at end of file
diff --git a/pocket-base/spec/openapi-manage.yaml b/pocket-base/spec/openapi-manage.yaml
index 7505f7f..29ad05a 100644
--- a/pocket-base/spec/openapi-manage.yaml
+++ b/pocket-base/spec/openapi-manage.yaml
@@ -229,6 +229,9 @@ components:
users_level:
type: string
description: "用户等级"
+ users_level_name:
+ type: string
+ description: "用户等级名称,按 `users_level -> 数据-会员等级` 字典描述实时解析"
users_tag:
type: string
description: "用户标签"
@@ -293,6 +296,7 @@ components:
users_phone: 手机号 | string
users_phone_masked: 手机号脱敏值 | string
users_level: 用户等级 | string
+ users_level_name: 用户等级名称 | string
users_tag: 用户标签 | string
users_picture: 用户头像附件ID | string
users_picture_url: 用户头像文件流链接 | string
@@ -1230,6 +1234,7 @@ paths:
创建平台用户 auth record。
服务端会自动生成 GUID 并写入统一身份字段 `openid`,同时写入 `users_idtype = ManagePlatform`。
前端以 `users_phone + password/passwordConfirm` 注册,但服务端仍会创建 PocketBase 原生 auth 用户。
+ 首次注册创建时,`users_level` 默认保持为空,不自动写入会员等级。
注册成功后直接返回 PocketBase 原生 auth token。
requestBody:
required: true
@@ -1260,6 +1265,7 @@ paths:
前端使用平台注册时保存的 `邮箱或手机号 + password` 登录。
仅允许 `users_idtype = ManagePlatform` 的用户通过该接口登录。
服务端会根据 `login_account` 自动判断邮箱或手机号,并定位平台用户,再使用该用户的 PocketBase 原生 identity(当前为 `email`)执行原生 password auth。
+ 返回体中的 `user.users_level_name` 为服务端按“数据-会员等级”字典实时解析后的等级名称。
登录成功后直接返回 PocketBase 原生 auth token。
requestBody:
required: true
@@ -1803,6 +1809,3 @@ paths:
description: "非 ManagePlatform 用户无权访问"
'415':
description: "请求体必须为 application/json"
-
-
-
diff --git a/pocket-base/spec/openapi-wx.yaml b/pocket-base/spec/openapi-wx.yaml
index b6ffa77..0f609db 100644
--- a/pocket-base/spec/openapi-wx.yaml
+++ b/pocket-base/spec/openapi-wx.yaml
@@ -4,7 +4,14 @@ info:
version: 1.0.0-wx
description: |
面向微信端的小程序接口文档。
- 本文档只包含微信登录、微信资料完善,以及微信端会共用的系统接口。
+ 本文档包含微信登录、微信资料完善,以及微信小程序侧会直接调用的业务接口。
+
+ 微信小程序调用适配说明:
+ - 除 `/pb/api/wechat/login` 外,调用购物车 / 订单的 PocketBase 原生 records API 时都需要在请求头中携带 `Authorization: Bearer `
+ - `token` 取自 `/pb/api/wechat/login` 成功返回的认证 token
+ - 小程序端应统一使用 HTTPS,不依赖 Cookie / Session
+ - 购物车与订单当前文档展示的是 PocketBase 原生 `/pb/api/collections/.../records` 接口,不是自定义 hooks API
+ - 按当前 collection 规则,创建 `tbl_cart` / `tbl_order` 记录时,客户端必须显式提交 owner 字段,且值必须等于当前 token 对应的 `openid`
license:
name: Proprietary
identifier: LicenseRef-Proprietary
@@ -26,9 +33,15 @@ tags:
description: 通过 PocketBase 原生 records API 访问 `tbl_product_list`
- name: 文档信息
description: 通过 PocketBase 原生 records API 访问 `tbl_document`
+ - name: 购物车
+ description: 微信小程序侧购物车 CRUD 接口
+ - name: 订单
+ description: 微信小程序侧订单 CRUD 接口
+security: []
paths:
/pb/api/system/users-count:
post:
+ security: []
operationId: postSystemUsersCount
tags:
- 系统
@@ -55,6 +68,9 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
/pb/api/system/refresh-token:
post:
+ security:
+ - BearerAuth: []
+ - {}
operationId: postSystemRefreshToken
tags:
- 系统
@@ -113,6 +129,7 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
/pb/api/wechat/login:
post:
+ security: []
operationId: postWechatLogin
tags:
- 微信认证
@@ -120,6 +137,7 @@ paths:
description: |
使用微信 `users_wx_code` 换取微信 openid。
若 `tbl_auth_users` 中不存在对应用户,则自动创建新 auth 用户并返回 token。
+ 首次注册时,`users_level` 默认保持为空,不自动写入会员等级。
requestBody:
required: true
content:
@@ -165,6 +183,8 @@ paths:
$ref: '#/components/schemas/ErrorResponse'
/pb/api/wechat/profile:
post:
+ security:
+ - BearerAuth: []
operationId: postWechatProfile
tags:
- 微信认证
@@ -736,13 +756,16 @@ paths:
value: <属性值>|
prod_list_plantype: <产品方案>|
prod_list_category: <产品分类>|
- prod_list_sort: <排序值>|
+ prod_list_sort: 10
prod_list_comm_type: <通讯类型>|
prod_list_series: <产品系列>|
prod_list_power_supply: <供电方式>|
prod_list_tags: <产品标签>|
prod_list_status: <产品状态>|
- prod_list_basic_price: <基础价格>|
+ prod_list_basic_price: 1999
+ prod_list_vip_price:
+ - viplevel: VIP1
+ price: 1899
prod_list_remark: <备注>|
'400':
description: 查询参数错误
@@ -984,7 +1007,501 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/PocketBaseNativeError'
+ /pb/api/collections/tbl_cart/records:
+ get:
+ operationId: getPocketBaseCartRecords
+ tags:
+ - 购物车
+ summary: 查询当前用户购物车列表或按业务 ID 精确查询
+ description: |
+ 使用 PocketBase 原生 records list 接口查询 `tbl_cart`。
+
+ 当前线上权限规则:
+ - `listRule = @request.auth.id != "" && cart_owner = @request.auth.openid`
+ - 因此调用方只能读到 `cart_owner` 等于自己 `openid` 的记录
+
+ 标准调用方式:
+ 1. 查询当前登录用户全部购物车:
+ - 不传 `filter`
+ - 可选传 `sort=-cart_create`
+ 2. 按业务 ID 精确查单条:
+ - `filter=cart_id="CART-..."`
+ - `perPage=1`
+ - `page=1`
+
+ 注意:
+ - 这是 PocketBase 原生返回结构,不是 hooks 统一包装
+ - 即使不传 `filter`,返回结果也会继续受 `listRule` 限制
+ security:
+ - BearerAuth: []
+ parameters:
+ - name: filter
+ in: query
+ required: false
+ description: |
+ PocketBase 标准过滤表达式。
+
+ - 查当前用户全部购物车时:不传
+ - 按 `cart_id` 精确查单条时:`cart_id="CART-1770000000000-abc123"`
+ schema:
+ type: string
+ example: cart_id="CART-1770000000000-abc123"
+ - name: page
+ in: query
+ required: false
+ schema:
+ type: integer
+ minimum: 1
+ default: 1
+ - name: perPage
+ in: query
+ required: false
+ schema:
+ type: integer
+ minimum: 1
+ default: 20
+ - name: sort
+ in: query
+ required: false
+ description: PocketBase 原生排序表达式,推荐 `-cart_create`
+ schema:
+ type: string
+ example: -cart_create
+ responses:
+ '200':
+ description: 查询成功
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseCartListResponse'
+ '400':
+ description: 查询参数错误或不满足 listRule
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '401':
+ description: token 缺失、无效或已过期
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '403':
+ description: 集合规则被锁定或服务端权限设置异常
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '500':
+ description: 服务端错误
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ post:
+ operationId: postPocketBaseCartRecord
+ tags:
+ - 购物车
+ summary: 创建购物车记录
+ description: |
+ 使用 PocketBase 原生 records create 接口向 `tbl_cart` 新增记录。
+
+ 当前线上权限规则:
+ - `createRule = @request.auth.id != "" && @request.body.cart_owner = @request.auth.openid`
+ - 因此客户端创建时必须显式提交 `cart_owner`,并且值必须等于当前 token 对应的 `openid`
+
+ 这意味着:
+ - 不能依赖服务端自动补 owner
+ - 不能省略 `cart_owner`
+ - 不满足规则时 PocketBase 会直接返回 `400`
+ security:
+ - BearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseCartCreateRequest'
+ responses:
+ '200':
+ description: 创建成功
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseCartRecord'
+ '400':
+ description: 参数错误、违反字段约束或不满足 createRule
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '401':
+ description: token 缺失、无效或已过期
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '403':
+ description: 集合规则被锁定或服务端权限设置异常
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '500':
+ description: 服务端错误
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ /pb/api/collections/tbl_cart/records/{recordId}:
+ patch:
+ operationId: patchPocketBaseCartRecordByRecordId
+ tags:
+ - 购物车
+ summary: 更新购物车记录
+ description: |
+ 使用 PocketBase 原生 records update 接口更新 `tbl_cart`。
+
+ 标准调用流程:
+ 1. 先通过 `GET /pb/api/collections/tbl_cart/records?filter=cart_id="..."&perPage=1&page=1` 找到原生 `recordId`
+ 2. 再调用当前 `PATCH /pb/api/collections/tbl_cart/records/{recordId}`
+
+ 当前线上权限规则:
+ - `updateRule = @request.auth.id != "" && cart_owner = @request.auth.openid`
+ - 调用方只能修改自己的购物车记录
+ security:
+ - BearerAuth: []
+ parameters:
+ - name: recordId
+ in: path
+ required: true
+ schema:
+ type: string
+ example: l2r3nq7rqhuob0h
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseCartUpdateRequest'
+ responses:
+ '200':
+ description: 更新成功
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseCartRecord'
+ '400':
+ description: 参数错误或违反字段约束
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '401':
+ description: token 缺失、无效或已过期
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '404':
+ description: 记录不存在或不满足 updateRule
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '500':
+ description: 服务端错误
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ delete:
+ operationId: deletePocketBaseCartRecordByRecordId
+ tags:
+ - 购物车
+ summary: 删除购物车记录
+ description: |
+ 使用 PocketBase 原生 records delete 接口删除 `tbl_cart`。
+
+ 当前线上权限规则:
+ - `deleteRule = @request.auth.id != "" && cart_owner = @request.auth.openid`
+ - 调用方只能删除自己的购物车记录
+ security:
+ - BearerAuth: []
+ parameters:
+ - name: recordId
+ in: path
+ required: true
+ schema:
+ type: string
+ example: l2r3nq7rqhuob0h
+ responses:
+ '204':
+ description: 删除成功
+ '401':
+ description: token 缺失、无效或已过期
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '404':
+ description: 记录不存在或不满足 deleteRule
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '500':
+ description: 服务端错误
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ /pb/api/collections/tbl_order/records:
+ get:
+ operationId: getPocketBaseOrderRecords
+ tags:
+ - 订单
+ summary: 查询当前用户订单列表或按业务 ID 精确查询
+ description: |
+ 使用 PocketBase 原生 records list 接口查询 `tbl_order`。
+
+ 当前线上权限规则:
+ - `listRule = @request.auth.id != "" && order_owner = @request.auth.openid`
+ - 因此调用方只能读到 `order_owner` 等于自己 `openid` 的记录
+
+ 标准调用方式:
+ 1. 查询当前登录用户全部订单:
+ - 不传 `filter`
+ - 可选传 `sort=-order_create`
+ 2. 按业务 ID 精确查单条:
+ - `filter=order_id="ORDER-..."`
+ - `perPage=1`
+ - `page=1`
+ security:
+ - BearerAuth: []
+ parameters:
+ - name: filter
+ in: query
+ required: false
+ schema:
+ type: string
+ example: order_id="ORDER-1770000000000-abc123"
+ - name: page
+ in: query
+ required: false
+ schema:
+ type: integer
+ minimum: 1
+ default: 1
+ - name: perPage
+ in: query
+ required: false
+ schema:
+ type: integer
+ minimum: 1
+ default: 20
+ - name: sort
+ in: query
+ required: false
+ description: PocketBase 原生排序表达式,推荐 `-order_create`
+ schema:
+ type: string
+ example: -order_create
+ responses:
+ '200':
+ description: 查询成功
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseOrderListResponse'
+ '400':
+ description: 查询参数错误或不满足 listRule
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '401':
+ description: token 缺失、无效或已过期
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '403':
+ description: 集合规则被锁定或服务端权限设置异常
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '500':
+ description: 服务端错误
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ post:
+ operationId: postPocketBaseOrderRecord
+ tags:
+ - 订单
+ summary: 创建订单记录
+ description: |
+ 使用 PocketBase 原生 records create 接口向 `tbl_order` 新增记录。
+
+ 当前线上权限规则:
+ - `createRule = @request.auth.id != "" && @request.body.order_owner = @request.auth.openid`
+ - 因此客户端创建时必须显式提交 `order_owner`,并且值必须等于当前 token 对应的 `openid`
+
+ 这意味着:
+ - 不能依赖服务端自动补 owner
+ - 不能省略 `order_owner`
+ - 不满足规则时 PocketBase 会直接返回 `400`
+ security:
+ - BearerAuth: []
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseOrderCreateRequest'
+ responses:
+ '200':
+ description: 创建成功
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseOrderRecord'
+ '400':
+ description: 参数错误、违反字段约束或不满足 createRule
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '401':
+ description: token 缺失、无效或已过期
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '403':
+ description: 集合规则被锁定或服务端权限设置异常
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '500':
+ description: 服务端错误
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ /pb/api/collections/tbl_order/records/{recordId}:
+ patch:
+ operationId: patchPocketBaseOrderRecordByRecordId
+ tags:
+ - 订单
+ summary: 更新订单记录
+ description: |
+ 使用 PocketBase 原生 records update 接口更新 `tbl_order`。
+
+ 标准调用流程:
+ 1. 先通过 `GET /pb/api/collections/tbl_order/records?filter=order_id="..."&perPage=1&page=1` 找到原生 `recordId`
+ 2. 再调用当前 `PATCH /pb/api/collections/tbl_order/records/{recordId}`
+
+ 当前线上权限规则:
+ - `updateRule = @request.auth.id != "" && order_owner = @request.auth.openid`
+ - 调用方只能修改自己的订单记录
+ security:
+ - BearerAuth: []
+ parameters:
+ - name: recordId
+ in: path
+ required: true
+ schema:
+ type: string
+ example: l2r3nq7rqhuob0h
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseOrderUpdateRequest'
+ responses:
+ '200':
+ description: 更新成功
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseOrderRecord'
+ '400':
+ description: 参数错误或违反字段约束
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '401':
+ description: token 缺失、无效或已过期
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '404':
+ description: 记录不存在或不满足 updateRule
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '500':
+ description: 服务端错误
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ delete:
+ operationId: deletePocketBaseOrderRecordByRecordId
+ tags:
+ - 订单
+ summary: 删除订单记录
+ description: |
+ 使用 PocketBase 原生 records delete 接口删除 `tbl_order`。
+
+ 当前线上权限规则:
+ - `deleteRule = @request.auth.id != "" && order_owner = @request.auth.openid`
+ - 调用方只能删除自己的订单记录
+ security:
+ - BearerAuth: []
+ parameters:
+ - name: recordId
+ in: path
+ required: true
+ schema:
+ type: string
+ example: l2r3nq7rqhuob0h
+ responses:
+ '204':
+ description: 删除成功
+ '401':
+ description: token 缺失、无效或已过期
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '404':
+ description: 记录不存在或不满足 deleteRule
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
+ '500':
+ description: 服务端错误
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PocketBaseNativeError'
components:
+ securitySchemes:
+ BearerAuth:
+ type: http
+ scheme: bearer
+ bearerFormat: JWT
schemas:
ApiResponseBase:
type: object
@@ -1038,6 +1555,286 @@ components:
errMsg: 失败原因提示 | string
data:
任意错误字段: 错误附加信息 | object
+ PocketBaseCartFields:
+ type: object
+ properties:
+ cart_id:
+ type: string
+ description: 购物车业务 ID
+ example: CART-1770000000000-abc123
+ cart_number:
+ type: string
+ description: 购物车名称或分组号
+ example: wx-user-20260403153000
+ cart_create:
+ type: string
+ description: 购物车项创建时间,由数据库自动生成
+ example: 2026-04-03 15:30:00.000Z
+ cart_owner:
+ type: string
+ description: 购物车所有者 openid
+ example: wx-openid-user-001
+ cart_product_id:
+ type: string
+ description: 产品业务 ID
+ example: PROD-1770000000000-abcd12
+ cart_product_quantity:
+ type:
+ - integer
+ - number
+ description: 产品数量
+ example: 2
+ cart_status:
+ type: string
+ description: 购物车状态
+ example: 有效
+ cart_at_price:
+ type:
+ - number
+ - integer
+ description: 加入购物车时价格
+ example: 1999
+ cart_remark:
+ type: string
+ description: 备注
+ example: 小程序加入购物车示例
+ PocketBaseCartRecord:
+ allOf:
+ - $ref: '#/components/schemas/PocketBaseRecordBase'
+ - $ref: '#/components/schemas/PocketBaseCartFields'
+ PocketBaseCartCreateRequest:
+ type: object
+ required:
+ - cart_id
+ - cart_number
+ - cart_owner
+ - cart_product_id
+ - cart_product_quantity
+ - cart_status
+ - cart_at_price
+ properties:
+ cart_id:
+ type: string
+ cart_number:
+ type: string
+ cart_owner:
+ type: string
+ description: 必须显式提交,且值必须等于当前 token 对应 openid
+ cart_product_id:
+ type: string
+ cart_product_quantity:
+ type:
+ - integer
+ - number
+ cart_status:
+ type: string
+ cart_at_price:
+ type:
+ - number
+ - integer
+ cart_remark:
+ type: string
+ PocketBaseCartUpdateRequest:
+ type: object
+ properties:
+ cart_number:
+ type: string
+ cart_owner:
+ type: string
+ description: 若提交,必须仍等于当前 token 对应 openid
+ cart_product_id:
+ type: string
+ cart_product_quantity:
+ type:
+ - integer
+ - number
+ cart_status:
+ type: string
+ cart_at_price:
+ type:
+ - number
+ - integer
+ cart_remark:
+ type: string
+ PocketBaseCartListResponse:
+ type: object
+ required:
+ - page
+ - perPage
+ - totalItems
+ - totalPages
+ - items
+ properties:
+ page:
+ type:
+ - integer
+ - string
+ perPage:
+ type:
+ - integer
+ - string
+ totalItems:
+ type:
+ - integer
+ - string
+ totalPages:
+ type:
+ - integer
+ - string
+ items:
+ type: array
+ items:
+ $ref: '#/components/schemas/PocketBaseCartRecord'
+ PocketBaseOrderFields:
+ type: object
+ properties:
+ order_id:
+ type: string
+ description: 订单业务 ID
+ example: ORDER-1770000000000-abc123
+ order_number:
+ type: string
+ description: 订单编号
+ example: wx-user-20260403153500
+ order_create:
+ type: string
+ description: 订单创建时间,由数据库自动生成
+ example: 2026-04-03 15:35:00.000Z
+ order_owner:
+ type: string
+ description: 订单所有者 openid
+ example: wx-openid-user-001
+ order_source:
+ type: string
+ description: 订单来源
+ example: 购物车
+ order_status:
+ type: string
+ description: 订单状态
+ example: 订单已生成
+ order_source_id:
+ type: string
+ description: 来源关联业务 ID
+ example: CART-1770000000000-abc123
+ order_snap:
+ description: 订单快照 JSON
+ oneOf:
+ - type: object
+ additionalProperties: true
+ - type: array
+ items:
+ type: object
+ additionalProperties: true
+ order_amount:
+ type:
+ - number
+ - integer
+ description: 订单金额
+ example: 3998
+ order_remark:
+ type: string
+ description: 备注
+ example: 小程序订单示例
+ PocketBaseOrderRecord:
+ allOf:
+ - $ref: '#/components/schemas/PocketBaseRecordBase'
+ - $ref: '#/components/schemas/PocketBaseOrderFields'
+ PocketBaseOrderCreateRequest:
+ type: object
+ required:
+ - order_id
+ - order_number
+ - order_owner
+ - order_source
+ - order_status
+ - order_source_id
+ - order_snap
+ - order_amount
+ properties:
+ order_id:
+ type: string
+ order_number:
+ type: string
+ order_owner:
+ type: string
+ description: 必须显式提交,且值必须等于当前 token 对应 openid
+ order_source:
+ type: string
+ order_status:
+ type: string
+ order_source_id:
+ type: string
+ order_snap:
+ oneOf:
+ - type: object
+ additionalProperties: true
+ - type: array
+ items:
+ type: object
+ additionalProperties: true
+ order_amount:
+ type:
+ - number
+ - integer
+ order_remark:
+ type: string
+ PocketBaseOrderUpdateRequest:
+ type: object
+ properties:
+ order_number:
+ type: string
+ order_owner:
+ type: string
+ description: 若提交,必须仍等于当前 token 对应 openid
+ order_source:
+ type: string
+ order_status:
+ type: string
+ order_source_id:
+ type: string
+ order_snap:
+ oneOf:
+ - type: object
+ additionalProperties: true
+ - type: array
+ items:
+ type: object
+ additionalProperties: true
+ order_amount:
+ type:
+ - number
+ - integer
+ order_remark:
+ type: string
+ PocketBaseOrderListResponse:
+ type: object
+ required:
+ - page
+ - perPage
+ - totalItems
+ - totalPages
+ - items
+ properties:
+ page:
+ type:
+ - integer
+ - string
+ perPage:
+ type:
+ - integer
+ - string
+ totalItems:
+ type:
+ - integer
+ - string
+ totalPages:
+ type:
+ - integer
+ - string
+ items:
+ type: array
+ items:
+ $ref: '#/components/schemas/PocketBaseOrderRecord'
CompanyInfo:
anyOf:
- type: object
@@ -1657,7 +2454,7 @@ components:
- number
- integer
description: 排序值(同分类内按升序)
- example: <排序值>|
+ example: 10
prod_list_comm_type:
type: string
description: 通讯类型
@@ -1683,7 +2480,24 @@ components:
- number
- integer
description: 基础价格
- example: <基础价格>|
+ example: 1999
+ prod_list_vip_price:
+ type: array
+ description: 会员价数组,每项包含会员等级枚举值与价格
+ items:
+ type: object
+ properties:
+ viplevel:
+ type: string
+ example: <会员等级枚举值>|
+ price:
+ type:
+ - number
+ - integer
+ example: 1899
+ example:
+ - viplevel: VIP1
+ price: 1899
prod_list_remark:
type: string
description: 备注
@@ -1709,13 +2523,16 @@ components:
value: <属性值>|
prod_list_plantype: <产品方案>|
prod_list_category: <产品分类>|
- prod_list_sort: <排序值>|
+ prod_list_sort: 10
prod_list_comm_type: <通讯类型>|
prod_list_series: <产品系列>|
prod_list_power_supply: <供电方式>|
prod_list_tags: <产品标签>|
prod_list_status: <产品状态>|
- prod_list_basic_price: <基础价格>|
+ prod_list_basic_price: 1999
+ prod_list_vip_price:
+ - viplevel: VIP1
+ price: 1899
prod_list_remark: <备注>|
PocketBaseProductListListResponse:
type: object
@@ -1772,13 +2589,16 @@ components:
value: <属性值>|
prod_list_plantype: <产品方案>|
prod_list_category: <产品分类>|
- prod_list_sort: <排序值>|
+ prod_list_sort: 10
prod_list_comm_type: <通讯类型>|
prod_list_series: <产品系列>|
prod_list_power_supply: <供电方式>|
prod_list_tags: <产品标签>|
prod_list_status: <产品状态>|
- prod_list_basic_price: <基础价格>|
+ prod_list_basic_price: 1999
+ prod_list_vip_price:
+ - viplevel: VIP1
+ price: 1899
prod_list_remark: <备注>|
PocketBaseDocumentFields:
type: object
@@ -2079,6 +2899,9 @@ components:
users_level:
type: string
description: 用户等级
+ users_level_name:
+ type: string
+ description: 用户等级名称,按 `users_level -> 数据-会员等级` 字典描述实时解析
users_tag:
type: string
description: 用户标签
@@ -2143,6 +2966,7 @@ components:
users_phone: 手机号 | string
users_phone_masked: 手机号脱敏值 | string
users_level: 用户等级 | string
+ users_level_name: 用户等级名称 | string
users_tag: 用户标签 | string
users_picture: 用户头像附件ID | string
users_picture_url: 用户头像文件流链接 | string
@@ -2291,6 +3115,7 @@ components:
users_phone: 手机号 | string
users_phone_masked: 手机号脱敏值 | string
users_level: 用户等级 | string
+ users_level_name: 用户等级名称 | string
users_tag: 用户标签 | string
users_picture: 用户头像附件ID | string
users_picture_url: 用户头像文件流链接 | string
@@ -2369,6 +3194,7 @@ components:
users_phone: 手机号 | string
users_phone_masked: 手机号脱敏值 | string
users_level: 用户等级 | string
+ users_level_name: 用户等级名称 | string
users_tag: 用户标签 | string
users_picture: 用户头像附件ID | string
users_picture_url: 用户头像文件流链接 | string
@@ -2449,4 +3275,3 @@ components:
errMsg: 业务提示信息 | string
data:
total_users: 用户总数 | integer
-
diff --git a/pocket-base/spec/openapi.yaml b/pocket-base/spec/openapi.yaml
index 585d7a8..2709804 100644
--- a/pocket-base/spec/openapi.yaml
+++ b/pocket-base/spec/openapi.yaml
@@ -205,6 +205,9 @@ components:
users_level:
type: string
description: 用户等级
+ users_level_name:
+ type: string
+ description: 用户等级名称,按 `users_level -> 数据-会员等级` 字典描述实时解析
users_tag:
type: string
description: 用户标签
@@ -269,6 +272,7 @@ components:
users_phone: 手机号 | string
users_phone_masked: 手机号脱敏值 | string
users_level: 用户等级 | string
+ users_level_name: 用户等级名称 | string
users_tag: 用户标签 | string
users_picture: 用户头像附件ID | string
users_picture_url: 用户头像文件流链接 | string
@@ -1249,6 +1253,7 @@ paths:
使用微信 code 换取微信侧 openid,并写入统一身份字段 `tbl_auth_users.openid`。
若 `tbl_auth_users` 中不存在对应用户则自动创建 auth record,随后返回 PocketBase 原生 auth token。
首次注册创建时会写入 `users_idtype = WeChat`。
+ 首次注册创建时,`users_level` 默认保持为空,不自动写入会员等级。
返回的 `token` 可直接用于 PocketBase SDK 与当前 hooks 接口调用。
requestBody:
required: true
@@ -1281,6 +1286,7 @@ paths:
创建平台用户 auth record。
服务端会自动生成 GUID 并写入统一身份字段 `openid`,同时写入 `users_idtype = ManagePlatform`。
前端以 `users_phone + password/passwordConfirm` 注册,但服务端仍会创建 PocketBase 原生 auth 用户。
+ 首次注册创建时,`users_level` 默认保持为空,不自动写入会员等级。
注册成功后直接返回 PocketBase 原生 auth token。
requestBody:
required: true
@@ -1311,6 +1317,7 @@ paths:
前端使用平台注册时保存的 `邮箱或手机号 + password` 登录。
仅允许 `users_idtype = ManagePlatform` 的用户通过该接口登录。
服务端会根据 `login_account` 自动判断邮箱或手机号,并定位平台用户,再使用该用户的 PocketBase 原生 identity(当前为 `email`)执行原生 password auth。
+ 返回体中的 `user.users_level_name` 为服务端按“数据-会员等级”字典实时解析后的等级名称。
登录成功后直接返回 PocketBase 原生 auth token。
requestBody:
required: true
@@ -1882,5 +1889,3 @@ paths:
description: 非 ManagePlatform 用户无权访问
'415':
description: 请求体必须为 application/json
-
-
diff --git a/script/add-product-function-field.js b/script/add-product-function-field.js
new file mode 100644
index 0000000..c7637e3
--- /dev/null
+++ b/script/add-product-function-field.js
@@ -0,0 +1,149 @@
+import { createRequire } from 'module';
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+import PocketBase from 'pocketbase';
+
+const require = createRequire(import.meta.url);
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+let runtimeConfig = {};
+try {
+ runtimeConfig = require('../pocket-base/bai_api_pb_hooks/bai_api_shared/config/runtime.js');
+} catch (_error) {
+ runtimeConfig = {};
+}
+
+function readEnvFile(filePath) {
+ if (!fs.existsSync(filePath)) return {};
+
+ const content = fs.readFileSync(filePath, 'utf8');
+ const result = {};
+
+ for (const rawLine of content.split(/\r?\n/)) {
+ const line = rawLine.trim();
+ if (!line || line.startsWith('#')) continue;
+
+ const index = line.indexOf('=');
+ if (index === -1) continue;
+
+ const key = line.slice(0, index).trim();
+ const value = line.slice(index + 1).trim();
+ result[key] = value;
+ }
+
+ return result;
+}
+
+const backendEnv = readEnvFile(path.resolve(__dirname, '..', 'back-end', '.env'));
+const pbUrl = String(
+ process.env.PB_URL
+ || backendEnv.POCKETBASE_API_URL
+ || runtimeConfig.POCKETBASE_API_URL
+ || 'http://127.0.0.1:8090'
+).replace(/\/+$/, '');
+const authToken = process.env.POCKETBASE_AUTH_TOKEN || runtimeConfig.POCKETBASE_AUTH_TOKEN || '';
+
+if (!authToken) {
+ console.error('❌ 缺少 POCKETBASE_AUTH_TOKEN,无法执行字段迁移。');
+ process.exit(1);
+}
+
+function normalizeFieldPayload(field, targetSpec) {
+ const payload = {
+ name: targetSpec && targetSpec.name ? targetSpec.name : field.name,
+ type: targetSpec && targetSpec.type ? targetSpec.type : field.type,
+ };
+
+ if (field && field.id) {
+ payload.id = field.id;
+ }
+
+ if (typeof field.required !== 'undefined') {
+ payload.required = !!field.required;
+ }
+
+ if (payload.type === 'autodate') {
+ payload.onCreate = typeof field.onCreate === 'boolean' ? field.onCreate : true;
+ payload.onUpdate = typeof field.onUpdate === 'boolean' ? field.onUpdate : false;
+ }
+
+ return payload;
+}
+
+async function run() {
+ const pb = new PocketBase(pbUrl);
+ pb.authStore.save(authToken, null);
+
+ console.log(`🔄 开始处理字段迁移,PocketBase: ${pbUrl}`);
+
+ const collections = await pb.collections.getFullList({ sort: '-created' });
+ const collection = collections.find((item) => item.name === 'tbl_product_list');
+
+ if (!collection) {
+ throw new Error('未找到集合 tbl_product_list');
+ }
+
+ const targetFieldName = 'prod_list_function';
+ const targetFieldType = 'json';
+ const existingField = (collection.fields || []).find((field) => field.name === targetFieldName);
+
+ if (existingField && existingField.type === targetFieldType) {
+ console.log('✅ 字段已存在且类型正确,无需变更。');
+ console.log('✅ 校验完成: tbl_product_list.prod_list_function (json)');
+ return;
+ }
+
+ const nextFields = [];
+ let patched = false;
+
+ for (let i = 0; i < (collection.fields || []).length; i += 1) {
+ const field = collection.fields[i];
+ if (field.name === targetFieldName) {
+ nextFields.push(normalizeFieldPayload(field, { name: targetFieldName, type: targetFieldType }));
+ patched = true;
+ continue;
+ }
+
+ nextFields.push(normalizeFieldPayload(field));
+ }
+
+ if (!patched) {
+ nextFields.push({
+ name: targetFieldName,
+ type: targetFieldType,
+ required: false,
+ });
+ }
+
+ await pb.collections.update(collection.id, {
+ name: collection.name,
+ type: collection.type,
+ listRule: collection.listRule,
+ viewRule: collection.viewRule,
+ createRule: collection.createRule,
+ updateRule: collection.updateRule,
+ deleteRule: collection.deleteRule,
+ fields: nextFields,
+ indexes: collection.indexes || [],
+ });
+
+ const updated = await pb.collections.getOne(collection.id);
+ const verifiedField = (updated.fields || []).find((field) => field.name === targetFieldName);
+
+ if (!verifiedField || verifiedField.type !== targetFieldType) {
+ throw new Error('字段写入后校验失败:prod_list_function 未成功写入 json 类型');
+ }
+
+ console.log('✅ 字段迁移成功: tbl_product_list.prod_list_function (json)');
+}
+
+run().catch((error) => {
+ console.error('❌ 迁移失败:', {
+ status: error && error.status,
+ message: error && error.message,
+ response: error && error.response,
+ });
+ process.exit(1);
+});
diff --git a/script/database_schema.md b/script/database_schema.md
index 152ead3..639ae04 100644
--- a/script/database_schema.md
+++ b/script/database_schema.md
@@ -84,7 +84,7 @@
| users_id_number | text | 证件号 |
| users_phone | text | 用户电话号码 |
| users_wx_openid | text | 微信号 |
-| users_level | text | 用户等级 |
+| users_level | text | 用户等级枚举值(新用户默认空) |
| users_type | text | 用户类型 |
| users_tag | text | 用户标签 |
| users_status | text | 用户状态 |
diff --git a/script/package.json b/script/package.json
index 1cb5ffc..7f3796a 100644
--- a/script/package.json
+++ b/script/package.json
@@ -11,6 +11,7 @@
"init:dictionary": "node pocketbase.dictionary.js",
"migrate:file-fields": "node pocketbase.file-fields-to-attachments.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",
"test:company-owner-sync": "node test-company-owner-sync.js"
},
diff --git a/script/pocketbase.cart-order.js b/script/pocketbase.cart-order.js
new file mode 100644
index 0000000..34eb88d
--- /dev/null
+++ b/script/pocketbase.cart-order.js
@@ -0,0 +1,282 @@
+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();
diff --git a/script/pocketbase.product-list.js b/script/pocketbase.product-list.js
index a4bdbc5..85e0f2a 100644
--- a/script/pocketbase.product-list.js
+++ b/script/pocketbase.product-list.js
@@ -38,6 +38,7 @@ const collections = [
{ name: 'prod_list_description', type: 'text' },
{ name: 'prod_list_feature', type: 'text' },
{ name: 'prod_list_parameters', type: 'json' },
+ { name: 'prod_list_function', type: 'json' },
{ name: 'prod_list_plantype', type: 'text' },
{ name: 'prod_list_category', type: 'text', required: true },
{ name: 'prod_list_sort', type: 'number' },
@@ -47,6 +48,7 @@ const collections = [
{ name: 'prod_list_tags', type: 'text' },
{ name: 'prod_list_status', type: 'text' },
{ name: 'prod_list_basic_price', type: 'number' },
+ { name: 'prod_list_vip_price', type: 'json' },
{ name: 'prod_list_remark', type: 'text' },
],
indexes: [