Ver Fonte

merge: 同步 origin/cfclub

Xiaogang Liao há 5 dias atrás
pai
commit
617975e5ae

+ 140 - 0
docs/superpowers/specs/2026-11-07-signature-page-landscape.md

@@ -0,0 +1,140 @@
+# 签名功能改造:内嵌画布 → 独立横屏签名页
+
+- 状态:已裁决(2026-11-07,主理人需求)
+- 类型:需求变更(交互改造,复用现有 `HandSignature` 组件)
+- 溯源场景:`pages/health/health-plan-summary.vue`(问卷完成签名)、`pages/shop/consignee-edit/consignee-edit.vue`(水机购买收货人签名)
+- 关联规格:无前置规格;本文档为首版需求变更
+
+## 1. 变更背景
+
+当前签名功能在两处使用**内嵌画布组件** `components/hand-signature/hand-signature.vue`(通用 `HandSignature`):
+
+1. **问卷完成签名**:`pages/health/health-plan-summary.vue` Step 3 把 `<hand-signature>` 直接嵌在竖向滚动页内(`requireAgree=true`,确认后存 `signatureDataUrl`,按钮「确认签名并生成方案」)。
+2. **水机购买签名**:`pages/shop/consignee-edit/consignee-edit.vue` 把 `<hand-signature>` 嵌在收货人编辑表单内(`requireAgree=true`,确认后存 `form.handSignature`)。
+
+**「不好用」的根因**:
+
+- 组件默认画布高度仅 `200px`,且内嵌在竖向长页面里,手指可书写区域小、竖屏窄,签名体验差。
+- 签名画布不随设备横屏,用户无法获得宽敞的横向书写面(签名天然适合横向长条画布)。
+- 确认/重写/同意勾选与页面其它内容挤在一起,缺乏专注的签署环境。
+
+**目标**:把签名从「页内小画布」改为「点击按钮打开一个**独立、全屏、横屏**的签名页」,签完确认后把 base64 图片回传给原页面。复用现有 `HandSignature` 的绘制与导出能力,降低改动风险。
+
+## 2. 影响范围
+
+| 模块 | 文件 | 改动类型 | 说明 |
+|---|---|---|---|
+| 新增·签名页 | `cfc-frontend/pages/signature/signature.vue` | 新建 | 独立横屏全屏签名页,内嵌复用 `HandSignature` |
+| 新增·签名页样式 | `cfc-frontend/pages/signature/signature.vue` `<style>` | 新建 | 横屏全屏布局、底部操作栏 |
+| 路由 | `cfc-frontend/pages.json` | 修改 | 注册 `pages/signature/signature` 路由(含页面 style) |
+| 前端·问卷签名 | `cfc-frontend/pages/health/health-plan-summary.vue` | 修改 | 移除内嵌组件,改「去签名」按钮 + 预览图;经签名页回传 `signatureDataUrl` |
+| 前端·水机签名 | `cfc-frontend/pages/shop/consignee-edit/consignee-edit.vue` | 修改 | 移除内嵌组件,改「去签名」按钮 + 预览图;经签名页回传 `form.handSignature` |
+| 复用·签名组件 | `cfc-frontend/components/hand-signature/hand-signature.vue` | 无改动(复用) | 保持 `confirm/clear/agree-change` 事件与 base64 导出不变 |
+| 小程序端约束 | 上述 `.vue` | 遵守 | 禁用可选链 `?.`、禁用 CSS Grid、禁用 `:key` 表达式、禁止 `new Date(string)` |
+
+**影响边界**:仅改签名的交互承载方式(内嵌→独立页),不改签名数据的存储结构(`signatureDataUrl` / `form.handSignature` 仍为 base64 PNG data URL)、不改后端接口、不改 `HandSignature` 组件本身。两处调用点的业务语义(问卷需勾选确认、水机为选填)保持不变。
+
+## 3. 验收标准(需求级)
+
+- **AC-R1(独立页跳转)**:原两处页面移除内嵌画布,改为一个「去签名 / 重新签名」按钮;点击即 `navigateTo` 打开独立签名页。
+- **AC-R2(横屏全屏)**:签名页为**横屏全屏**;画布铺满横屏视口,书写区域宽敞;加载时切横屏、离开时恢复竖屏(H5 不支持时优雅降级提示「请横置设备」)。
+- **AC-R3(签署回传)**:在签名页完成书写并点「确认」后,base64 图片回传原页面并显示预览;原页面确认/保存按钮按原逻辑启用(问卷未签名按钮保持禁用;水机未签名仍可保存)。
+- **AC-R4(复用与一致)**:行为与原内嵌版一致——`requireAgree` 同意声明、`agreeText` 文案、重写、确认导出 base64 全部保留。
+- **AC-R5(无回归)**:两处入口均走新页面,无遗留内嵌 `hand-signature`;`uni` 编译(小程序 + H5)通过;`pages.json` 路由注册正确。
+
+## 4. 实现要点(契约)
+
+### 4.1 新增签名页 `pages/signature/signature.vue`
+
+- **入参**(调用方传入):
+  - 初始值 `value`(base64,用于重新签名回显)——因 base64 过长不宜放 URL,用**模块级临时变量** `signaturePrefill`(新建 `utils/signature-bus.js`)在跳转前写入、页面 `onLoad` 读取后清空。
+  - `requireAgree`(0/1,URL 参数)、`agreeText`(URL 参数,短文本)。
+- **横屏**:`onLoad` 调 `wx.setPageOrientation({ orientation: 'landscape' })`(微信小程序);H5 用 `screen.orientation.lock('landscape')` 包裹 try/catch,失败时降级(CSS 提示横置设备)。`onUnload` 恢复 `portrait`。
+- **布局**:内嵌 `<hand-signature :value="prefill" :require-agree="requireAgree" :agree-text="agreeText" @confirm="onConfirm" @clear="onClear" />`,`height` 取横屏视口高度(如 `uni.getSystemInfoSync().windowHeight`,横屏时为短边)。底部固定操作栏:「重写」「确认」「返回」。
+- **回传**:`onConfirm(dataUrl)` 中 `this.getOpenerEventChannel().emit('signatureConfirm', dataUrl)` 后 `uni.navigateBack()`;「返回」直接 `navigateBack()`(不回传)。
+- 小程序约束:横屏下坐标换算已由 `HandSignature` 的 `boundingClientRect` 处理,无需重写。
+
+### 4.2 调用方改造(两处统一模式)
+
+```js
+// 跳转前写入初始值(编辑场景)
+import { setSignaturePrefill } from '@/utils/signature-bus.js'
+setSignaturePrefill(this.signatureDataUrl) // 或 this.form.handSignature
+uni.navigateTo({
+  url: '/pages/signature/signature?requireAgree=1&agreeText=' + encodeURIComponent('我已阅读并确认…'),
+  events: {
+    signatureConfirm: (dataUrl) => { this.signatureDataUrl = dataUrl } // 或 this.form.handSignature = dataUrl
+  }
+})
+```
+
+- 原页用 `<image :src="signatureDataUrl" mode="widthFix" />`(或 `form.handSignature`)展示已签预览,替代原内嵌组件。
+
+### 4.3 `pages.json` 路由
+
+```json
+{ "path": "pages/signature/signature", "style": { "navigationBarTitleText": "签名", "navigationStyle": "custom" } }
+```
+(`navigationStyle: custom` 让签名页真正全屏;若需保留返回,可改用默认导航栏 + 页面内「返回」按钮。)
+
+## 5. 开发任务拆分(工作项 + 验收条件)
+
+### WI-1 新建独立横屏签名页(核心)
+- **文件**:`cfc-frontend/pages/signature/signature.vue`(新建)、`cfc-frontend/utils/signature-bus.js`(新建,模块级 prefill 存取)
+- **工作**:实现全屏横屏签名页,内嵌复用 `HandSignature`;`onLoad` 切横屏、`onUnload` 恢复;底部「重写/确认/返回」操作栏;`onConfirm` 经 `eventChannel` 回传 base64 并 `navigateBack`;经 `signature-bus` 读取初始值回显。
+- **验收条件**:
+  - AC1.1 页面独立、可被 `navigateTo` 打开;全屏渲染。
+  - AC1.2 加载切横屏、离开恢复竖屏(小程序真机/模拟器验证)。
+  - AC1.3 内嵌 `HandSignature` 全屏铺满横屏视口;「重写」「确认」「返回」三按钮可用。
+  - AC1.4 点「确认」→ `emit('signatureConfirm', dataUrl)` + `navigateBack`;点「返回」→ 不回传直接退出。
+  - AC1.5 支持初始值回显(编辑场景)与 `requireAgree`/`agreeText` 透传。
+  - AC1.6 遵守小程序约束(无可选链/CSS Grid/`:key`/`new Date(string)`)。
+
+### WI-2 横屏适配与降级
+- **文件**:`cfc-frontend/pages/signature/signature.vue`
+- **工作**:微信 `wx.setPageOrientation` 切横屏;H5 `screen.orientation.lock` 包裹 try/catch,不支持时显示「请横置设备」提示;横屏下画布坐标换算正确无偏移。
+- **验收条件**:
+  - AC2.1 微信小程序进入即横屏,书写流畅无偏移。
+  - AC2.2 H5 支持 `screen.orientation.lock` 时横屏;不支持时显示降级提示且不报错。
+  - AC2.3 离开签名页回到原页恢复竖屏,不污染其它页面。
+
+### WI-3 问卷完成签名改造
+- **文件**:`cfc-frontend/pages/health/health-plan-summary.vue`
+- **工作**:移除 Step 3 内嵌 `<hand-signature>`,改为「去签名」按钮 + 已签预览图;点击跳转签名页(`requireAgree=1` + 原 `agreeText`),`events.signatureConfirm` 写回 `signatureDataUrl`;原「确认签名并生成方案」按钮逻辑不变(仍依 `signatureDataUrl` 判空禁用)。
+- **验收条件**:
+  - AC3.1 Step 3 不再出现内嵌画布,仅「去签名」按钮 + 签名预览。
+  - AC3.2 点击跳转签名页,返回后 `signatureDataUrl` 被赋值,预览显示;按钮恢复可用。
+  - AC3.3 未签名时「确认签名并生成方案」保持禁用(行为与原版一致)。
+
+### WI-4 水机购买签名改造
+- **文件**:`cfc-frontend/pages/shop/consignee-edit/consignee-edit.vue`
+- **工作**:移除表单内嵌 `<hand-signature>`,改「去签名」按钮 + 已签预览;跳转签名页(`requireAgree=1` + 原 `agreeText`),`events.signatureConfirm` 写回 `form.handSignature`;保存逻辑不变(水机签名为选填)。
+- **验收条件**:
+  - AC4.1 表单不再内嵌画布,仅「去签名」按钮 + 预览。
+  - AC4.2 返回后 `form.handSignature` 被赋值,预览显示。
+  - AC4.3 未签名仍可保存(选填语义不变)。
+
+### WI-5 路由注册与联调验证
+- **文件**:`cfc-frontend/pages.json`(注册路由)、上述各文件联调
+- **工作**:`pages.json` 注册 `pages/signature/signature`;`uni` 编译通过(小程序 + H5);端到端走通两处「按钮→横屏签名→返回→预览→提交/保存」。
+- **验收条件**:
+  - AC5.1 `pages.json` 路由正确,无重复 path。
+  - AC5.2 `uni` 编译(小程序 + H5)无报错。
+  - AC5.3 两处入口均走新签名页,全局再无 `hand-signature` 内嵌于业务页(仅签名页内复用)。
+  - AC5.4 端到端:问卷与水机两路径均「跳转→横屏书写→确认→返回预览→原页可继续」。
+
+## 6. 任务登记(TDD 账本)
+
+| 工作项 | 能力键 | requirement 锚点 | design 锚点 | 阶段 |
+|---|---|---|---|---|
+| WI-1 签名页 | `family.signature.openPage` | 本规格 §3 AC-R1/R2 / §5 WI-1 | `pages/signature/signature.vue` + `HandSignature` 组件 | UNTRACKED |
+| WI-2 横屏适配 | `family.signature.landscape` | 本规格 §3 AC-R2 / §5 WI-2 | `wx.setPageOrientation` + `screen.orientation.lock` | UNTRACKED |
+| WI-3 问卷改造 | `family.signature.healthPlan` | 本规格 §3 AC-R3 / §5 WI-3 | `health-plan-summary.vue` Step 3 | UNTRACKED |
+| WI-4 水机改造 | `family.signature.consignee` | 本规格 §3 AC-R3 / §5 WI-4 | `consignee-edit.vue` 手签名段 | UNTRACKED |
+| WI-5 路由+联调 | `family.signature.routeIntegrate` | 本规格 §3 AC-R5 / §5 WI-5 | `pages.json` 路由 | UNTRACKED |
+
+## 7. 遗留与待决
+
+- **导航栏样式**:签名页用 `navigationStyle: custom`(真全屏)还是保留系统导航栏(靠页面内「返回」)?默认建议 `custom` 以获得最大书写区,待主理人确认。
+- **H5 横屏**:`screen.orientation.lock` 仅在部分浏览器(含 HTTPS + 用户手势)可用,其余降级提示;是否接受降级方案待确认。
+- **多成员签署**:`health-plan-summary` 当前仅单人签署(Step 3 单 `signatureDataUrl`);本变更不改多签逻辑,沿用现状。

+ 151 - 1
tools/xaf_agent_hub/tasks_backlog.json

@@ -1876,5 +1876,155 @@
     "dispatched_at": "2026-11-07 07:00:00",
     "dispatched_to": "lan-248",
     "model": "opencode/deepseek-v4.1-flash-free"
+  },
+  {
+    "id": "DOC-2026-11-07-referral-team-T1",
+    "title": "T1 团队邀请",
+    "priority": "P0",
+    "module": "cfc-backend,cfc-frontend",
+    "instance": "",
+    "status": "pending",
+    "spec": "docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §12-T1 + §5 + §7.7 + §11-#11",
+    "entity": "",
+    "gap_kinds": [],
+    "prompt": "实现团队邀请(§5):场景A 未注册用户扫团队邀请码→注册时建立 推荐人关系+团队关系 两次幂等(§5.1);场景B 已加入平台老用户扫码→只建团队关系不覆盖推荐人关系(§9#9);复用 distribution_relations.invite_code 不新建表(§7.7);落地页+小程序码 独立页(§11#11 拍板),参照 pages/invite/join 家庭邀请实现。",
+    "tdd_key": "team.invitation.create",
+    "generated_from": "user",
+    "generated_at": "2026-11-07T22:20:00"
+  },
+  {
+    "id": "DOC-2026-11-07-referral-team-T2",
+    "title": "T2 后端分润",
+    "priority": "P0",
+    "module": "cfc-backend",
+    "instance": "",
+    "status": "pending",
+    "spec": "docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §12-T2 + §2 + §4 + §7.1/7.3/7.4/7.5 + §11-#3/#5",
+    "entity": "",
+    "gap_kinds": [],
+    "prompt": "实现团队分润核心(§2/§4):建表 profit_pool_order(§7.1)/team_share_record(§7.3)/team_award_record(§7.4)/service_cost_config(§7.5)+ProfitPoolService(§2 总账 S-C-O-F-D)+TeamProfitService(§4.2 8份分配 每层min(该层成员星级,剩余) 每层最多6份 最少2层 最多3层 #5拍板 + §4.4 团队奖 归属平台自有团队 platform_rule #3拍板)。唯一键幂等(§8.3)。",
+    "tdd_key": "team.profit.settle",
+    "generated_from": "user",
+    "generated_at": "2026-11-07T22:20:00"
+  },
+  {
+    "id": "DOC-2026-11-07-referral-team-T3",
+    "title": "T3 支付成功集成",
+    "priority": "P0",
+    "module": "cfc-backend",
+    "instance": "",
+    "status": "pending",
+    "spec": "docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §12-T3 + §2.2 + 复用 CfCommissionService.settle 调用点",
+    "entity": "",
+    "gap_kinds": [],
+    "prompt": "将 6 个订单服务支付成功钩子接入 ProfitPoolService.settle()(§2.2 触发条件),复用现有 CfCommissionService.settle 调用点、同点位并行调用,不新增支付链路改动。",
+    "tdd_key": "team.profit.hook",
+    "generated_from": "user",
+    "generated_at": "2026-11-07T22:20:00"
+  },
+  {
+    "id": "DOC-2026-11-07-referral-team-T4",
+    "title": "T4 定时任务",
+    "priority": "P1",
+    "module": "cfc-backend",
+    "instance": "",
+    "status": "pending",
+    "spec": "docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §12-T4 + §8.1/8.2 + §4.3 + §11-#4/#15",
+    "entity": "",
+    "gap_kinds": [],
+    "prompt": "实现定时结算与重评(§8):冷静期7天自动 settled 入可提现余额(§8.2)、月度星级重评含升级双门槛校验(业绩分≥档位 AND 团队直推≥1 #15拍板, §4.3)、退款冲正(§8.2/§9#7)、StarScoreCalculator.isPromotable(userId,targetStar)。",
+    "tdd_key": "team.profit.cron",
+    "generated_from": "user",
+    "generated_at": "2026-11-07T22:20:00"
+  },
+  {
+    "id": "DOC-2026-11-07-referral-team-T5",
+    "title": "T5 管理端",
+    "priority": "P2",
+    "module": "cfc-web",
+    "instance": "",
+    "status": "pending",
+    "spec": "docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §12-T5 + §7.8 + §11-#6/#7/#12",
+    "entity": "",
+    "gap_kinds": [],
+    "prompt": "管理端配置与流水(§7.8):成本价/星级档位(star_tiers 后台可调 #12拍板)/团队门槛(team_threshold #6拍板)/团队奖分档配置(award_distribute=platform_rule #3拍板);分润流水查询与提现审核(复用 withdrawal_requests type=team_share/team_award #7拍板,参照现有佣金模式)。",
+    "tdd_key": "team.admin.manage",
+    "generated_from": "user",
+    "generated_at": "2026-11-07T22:20:00"
+  },
+  {
+    "id": "DOC-2026-11-07-referral-team-T6",
+    "title": "T6 用户端",
+    "priority": "P3",
+    "module": "cfc-frontend",
+    "instance": "",
+    "status": "pending",
+    "spec": "docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §12-T6 + §4.6 + §7.3/7.4",
+    "entity": "",
+    "gap_kinds": [],
+    "prompt": "用户端入口(§4.6):团队成员分润账单、星级展示、团队邀请入口(§5)、提现入口(复用 withdrawal_requests);小程序限制遵守 AGENTS.md(禁可选链/禁CSS Grid/用parseDate)。",
+    "tdd_key": "team.user.portal",
+    "generated_from": "user",
+    "generated_at": "2026-11-07T22:20:00"
+  },
+  {
+    "id": "DOC-2026-11-07-referral-team-T7",
+    "title": "T7 星级看板与换算",
+    "priority": "P1",
+    "module": "cfc-backend,cfc-frontend",
+    "instance": "",
+    "status": "pending",
+    "spec": "docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §12-T7 + §4.3.1 ①② + §11-#13",
+    "entity": "",
+    "gap_kinds": [],
+    "prompt": "星级透明化(§4.3.1 ①② #13拍板):POST /api/team-star/my-view + 小程序「我的星级」页——当前星级/三维构成(直接服务50%+团队服务40%+推荐消费10%)/距下一档差距/升级双门槛进度(业绩分✓+直推✓)/三条换算路径/近12月轨迹。",
+    "tdd_key": "team.star.myView",
+    "generated_from": "user",
+    "generated_at": "2026-11-07T22:20:00"
+  },
+  {
+    "id": "DOC-2026-11-07-referral-team-T8",
+    "title": "T8 星级简报推送",
+    "priority": "P2",
+    "module": "cfc-backend,cfc-frontend",
+    "instance": "",
+    "status": "pending",
+    "spec": "docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §12-T8 + §4.3.1 ③④ + §11-#13",
+    "entity": "",
+    "gap_kinds": [],
+    "prompt": "星级月度简报(§4.3.1 ③④ #13拍板):每月1日重评后发微信订阅消息(升/降/持平 + 行动建议),复用现有订阅消息通道。",
+    "tdd_key": "team.star.digest",
+    "generated_from": "user",
+    "generated_at": "2026-11-07T22:20:00"
+  },
+  {
+    "id": "DOC-2026-11-07-referral-team-T9",
+    "title": "T9 收入管道化",
+    "priority": "P1",
+    "module": "cfc-backend",
+    "instance": "",
+    "status": "pending",
+    "spec": "docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §12-T9 + §4.7 杠杆1 + §11-#14 + §10#5",
+    "entity": "",
+    "gap_kinds": [],
+    "prompt": "收入管道化(§4.7 杠杆1 #14拍板):profit_pool_order 触发源扩展 membership/subscription/assessment_retest + 续费/复购持续分润(只扩触发源、不提比率,§10合规第5条)。",
+    "tdd_key": "team.profit.pipeline",
+    "generated_from": "user",
+    "generated_at": "2026-11-07T22:20:00"
+  },
+  {
+    "id": "DOC-2026-11-07-referral-team-T10",
+    "title": "T10 星级倍率与里程碑",
+    "priority": "P1",
+    "module": "cfc-backend,cfc-frontend",
+    "instance": "",
+    "status": "pending",
+    "spec": "docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md v0.7 §12-T10 + §4.7 杠杆2/3/4 + §11-#14 + §10#5",
+    "entity": "",
+    "gap_kinds": [],
+    "prompt": "星级倍率与里程碑(§4.7 杠杆2/3/4 #14拍板):8份池改星级权重分配 star_multiplier(6星=3.0,恒≤T×80% 只重分不增量)+ 带教里程碑奖(星耀导师) + 订单完成实时显示本单分润。",
+    "tdd_key": "team.star.multiplier",
+    "generated_from": "user",
+    "generated_at": "2026-11-07T22:20:00"
   }
-]
+]