From 500aac306e7d7547bd7f641d85f2abc0ce779965 Mon Sep 17 00:00:00 2001
From: xcbyy <2578898740@qq.com>
Date: Mon, 19 Oct 2026 14:46:05 +0800
Subject: [PATCH] =?UTF-8?q?feat(expert):=20=E5=A4=A7=E7=A5=9E=E6=9C=8D?=
=?UTF-8?q?=E5=8A=A1=E5=8C=85=E8=87=AA=E5=8A=A9=E7=BB=B4=E6=8A=A4=EF=BC=88?=
=?UTF-8?q?=E6=96=B0=E5=A2=9E/=E7=BC=96=E8=BE=91/=E5=88=A0=E9=99=A4/?=
=?UTF-8?q?=E4=B8=8A=E4=B8=8B=E6=9E=B6=EF=BC=89+=20=E5=8E=BB=E6=8E=89?=
=?UTF-8?q?=E6=BC=94=E7=A4=BA=E6=9C=8D=E5=8A=A1=E5=8C=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 后端:新增 POST/PUT/DELETE /api/companions/:id/services[...](仅本人大神或管理员),含名称/单价区间(平台定价规范)/长度/内容安全/每人20个上限校验
- 仓储:createExpertService/updateExpertService/deleteExpertService/countExpertServices/getCompanionByUserId;移除"首次读取自动生成 3 个演示服务包",并在建表后一次性清理历史演示数据
- 新增 GET /api/expert/me:工作台按登录用户解析真实大神档案(不再固定演示 id 101)
- 小程序:api.js 增加 getMyExpertProfile/createExpertService/updateExpertService/deleteExpertService;工作台新增「新增服务」入口、每项「编辑/删除」按钮与服务包表单弹窗,收益/服务统计改用真实大神 id
- 新增回归护栏 expert-service-crud.spec.ts(93 套件/662 用例全绿);冒烟 9/9(含跨大神越权 403、低价 400)
---
docs/部署验收与1分钱支付闭环.md | 6 +
pwuniapp/common/api.js | 38 ++++
pwuniapp/pages/expert/workspace.vue | 219 ++++++++++++++++++++++-
server/src/db/mysql.ts | 10 ++
server/src/db/repository.ts | 203 ++++++++++++++++-----
server/src/index.ts | 152 ++++++++++++++++
server/tests/expert-service-crud.spec.ts | 48 +++++
7 files changed, 619 insertions(+), 57 deletions(-)
create mode 100644 server/tests/expert-service-crud.spec.ts
diff --git a/docs/部署验收与1分钱支付闭环.md b/docs/部署验收与1分钱支付闭环.md
index 5247b00..224f8b4 100644
--- a/docs/部署验收与1分钱支付闭环.md
+++ b/docs/部署验收与1分钱支付闭环.md
@@ -150,6 +150,12 @@ MYSQL_DATABASE=peiwan
- `40037` 排查:点后台「**拉取小程序模板列表(核对ID)**」,会显示当前 appId(脱敏)与其下真实存在的订阅消息模板;若后台填写的 ID 不在列表中,即为 appId 与建模板的小程序不一致(或用了公众号/服务号的模板ID)。用列表里的真实 ID 覆盖即可。
6. 订阅消息为**一次性**:用户授权一次只能收 1 条;高频场景需每次动作前请求授权(代码已在下单/报名/提现前请求)。
+### 大神服务包自助维护(已内置)
+- 大神工作台可**自助新增/编辑/删除/上下架**服务包(`POST|PUT|DELETE /api/companions/:id/services[...]`,仅本人大神或管理员);
+- 校验:名称必填 ≤40 字、单价须落在平台定价规范区间(`expert_rules` 的 min/maxHourlyRate)、说明 ≤200 字、通过内容安全检测、每人上限 20 个;
+- `GET /api/expert/me` 用于工作台解析当前登录用户的大神档案(不再使用演示 id);
+- 已移除"首次读取自动生成 3 个演示服务包"的旧逻辑,并在建表后一次性清理历史演示服务包。
+
### 站内消息通知中心(已内置,无需额外配置)
- 关键节点(订单开始服务/接单/拒单/申请完成/完成结算/提现到账/报名成功/退款受理)都会写入 `user_notifications`;
- 小程序「个人中心 → 消息通知」可查看历史与未读数(`GET /api/notifications`、`POST /api/notifications/read`);
diff --git a/pwuniapp/common/api.js b/pwuniapp/common/api.js
index 62dba1b..1000693 100644
--- a/pwuniapp/common/api.js
+++ b/pwuniapp/common/api.js
@@ -1034,6 +1034,44 @@ export async function updateExpertServiceStatus(expertId = 101, serviceId, statu
}
}
+/**
+ * 获取当前登录用户的大神档案(大神工作台使用)
+ */
+export async function getMyExpertProfile(userId) {
+ const uid = normalizeUserId(userId);
+ try {
+ const res = await http.get(`/expert/me?userId=${uid}`);
+ return res || null;
+ } catch (err) {
+ console.warn('[API] 获取大神档案异常:', err.message);
+ return null;
+ }
+}
+
+/**
+ * 新增服务包(仅本人大神)
+ */
+export async function createExpertService(expertId, payload = {}) {
+ const res = await http.post(`/companions/${Number(expertId)}/services`, { ...payload, userId: normalizeUserId(payload.userId) });
+ return res;
+}
+
+/**
+ * 编辑服务包(整体覆盖,仅本人大神)
+ */
+export async function updateExpertService(expertId, serviceId, payload = {}) {
+ const res = await http.put(`/companions/${Number(expertId)}/services/${serviceId}`, { ...payload, userId: normalizeUserId(payload.userId) });
+ return res;
+}
+
+/**
+ * 删除服务包(仅本人大神)
+ */
+export async function deleteExpertService(expertId, serviceId) {
+ const res = await http.delete(`/companions/${Number(expertId)}/services/${serviceId}`);
+ return res;
+}
+
/**
* 获取用户收藏的陪玩列表
*/
diff --git a/pwuniapp/pages/expert/workspace.vue b/pwuniapp/pages/expert/workspace.vue
index 7d714c1..669d014 100644
--- a/pwuniapp/pages/expert/workspace.vue
+++ b/pwuniapp/pages/expert/workspace.vue
@@ -156,6 +156,7 @@
陪玩服务管理 ({{ expertServices.length }})
+
@@ -173,6 +174,10 @@
¥{{ srv.price }}
/{{ srv.unit }}
+
+ 编辑
+ 删除
+
@@ -386,6 +391,48 @@
+
+
+
+
+
+
+ 服务名称
+
+
+
+ 服务类目
+
+
+
+ 单价(元/{{ serviceForm.unit || '小时' }})
+
+
+
+ 计价单位
+
+
+
+ 服务模式
+
+
+
+ 服务说明
+
+
+ 单价需符合平台定价规范(可在平台配置中查看上下限)。
+
+
+
+
+
+
+
+