Преглед изворни кода

docs(需求): 补齐当日3项需求变更说明(报告关联商品/优惠券按钮/团队收益口径+邀请分享)

Agnes Agent пре 5 дана
родитељ
комит
6a3e6a2d25

+ 82 - 0
docs/需求分析/2026-11-06-优惠券已领取券按钮修复需求变更.md

@@ -0,0 +1,82 @@
+# 我的优惠券:已领取券按钮修复(转赠+使用)+ 使用按券类型跳转 — 需求变更说明(2026-11-06)
+
+> **版本:** v1.0(主理人裁定,TDD 编排器入池依据)
+> **来源:** 用户 2026-11-06 需求 @skill:tdd-orchestrator "我的优惠券中,显示的是已经领取的,当前还显示为'立即领取',点击会报优惠券不存在。已经领取的优惠券应该是转赠和使用两个按钮,如果是指定商品的优惠券,则直接跳到商品下单页面,并自动选上优惠券,如果是全品类的,跳到商城;如果是会员券,跳到开通会员。"
+> **关联台账:** `docs/superpowers/specs/2026-11-06-coupon-claimed-actions.md`(实现任务书)
+> **状态:** ✅ 已完成开发 + 独立核验 + 已合入 cfclub(`1392c7e0b`)
+> **范围:** 仅小程序 `cfc-frontend/pages/profile-extra/coupons.vue` + `cfc-frontend/pages/shop/checkout/checkout.vue`;后端零改动
+
+---
+
+## 一、需求清单(原话)
+
+1. **修复 bug**:「我的优惠券」已领取的券仍显示「立即领取」,点击报「优惠券不存在」→ 应为**「转赠」+「使用」**两个按钮。
+2. **使用按钮按券类型跳转**:指定商品券 → 商品下单页并**自动选上优惠券**;全品类 → 商城;会员券 → 开通会员。
+
+---
+
+## 二、现状与根因(cfclub @ 2026-11-06)
+
+| 项 | 定位 | 关键事实 |
+|----|------|---------|
+| 券实例 vs 券模板 | `CouponService.toCouponVO`(L100-128) | 返回 `{id(券实例id=family_coupon.id), userCouponId, couponId(模板id), applicableTo, productId, ...}`——**`id` 与 `userCouponId` 同值、`couponId` 是模板 id** |
+| bug 根源 | `coupons.vue` L82-85 + L247-251 | `canClaim(coupon)` 对 AVAILABLE 券返回 true → 显示「立即领取」→ `handleClaim` 调 `claimCoupon(coupon.id)`,而 `coupon.id` 是**券实例 id** 非模板 id → 后端查无模板 → 报「优惠券不存在」 |
+| 券类型分发 | `coupons.vue` 数据 | `applicableTo`(PRODUCT/ALL/MEMBERSHIP)+ `productId`(指定商品券)已随 toCouponVO 返回 |
+| 确认订单页 | `checkout.vue` L644 `onLoad`、L806 `selectCoupon`、L615 `ownedCoupons` computed | 未处理 `options.couponId`;`ownedCoupons` 已按商品/金额/可用性过滤(PRODUCT 券按 `c.productId===currentProductId` 匹配) |
+| 目标路由 | 已有 | 确认订单 `/pages/shop/checkout/checkout`;商城 `/pages/shop/index/index`;开通会员 `/pages/membership/upgrade`;赠送券 `/pages/membership/gift-coupon` |
+| 商品详情/下单 | `productDetail({id})`(api.js L1684)+ `product-detail.vue` L773 | 详情页写 `checkoutItems` storage 后跳 checkout——本任务改用 coupons.vue 直接组装 checkoutItems |
+
+**结论:** 根因是「已领取券误显『立即领取』」+「claimCoupon 用错 id(券实例当模板)」。后端契约已完备,**纯前端**改动(2 文件)。
+
+---
+
+## 三、需求变更定义(目标态)
+
+### 变更 C1 · 已领取券显示「转赠」+「使用」双按钮(删除「立即领取」)
+- `coupons.vue` 模板 L82-89 替换:删除 `canClaim`/`handleClaim` 分支(及 `claimCoupon` import),已领取券(可用 tab、status AVAILABLE/空)显示 `<view class="coupon-actions"><button class="use-btn">使用</button><button class="gift-btn">转赠</button></view>`。
+- 已使用/已过期券不显示操作按钮(保持状态徽标)。
+
+### 变更 C2 · 「使用」按券类型分派
+- `useCoupon(coupon)`:
+  - **PRODUCT**(指定商品):`productDetail({id: coupon.productId})` 取商品 → `uni.setStorageSync('checkoutItems', [{productId, productName, coverImage, unitPrice, quantity:1}])` → `uni.navigateTo('/pages/shop/checkout/checkout?from=coupon&id=<商品id>&couponId=<coupon.id>')`(**couponId 必须用券实例 id**)。
+  - **ALL**(全品类):`uni.navigateTo('/pages/shop/index/index')`。
+  - **MEMBERSHIP**(会员券):`uni.navigateTo('/pages/membership/upgrade')`。
+  - 兜底(ASSESSMENT/其他/null):跳商城 `/pages/shop/index/index`。
+  - 未登录:先 `openLoginSheet()`;`productId` 为空:兜底跳商城。
+- `giftCoupon(coupon)`:`uni.navigateTo('/pages/membership/gift-coupon')`。
+
+### 变更 C3 · checkout 自动选券(from=coupon 场景)
+- `checkout.vue` `onLoad`:`this._autoCouponId = options && options.couponId ? parseInt(options.couponId) : null`。
+- `loadCoupons` 回调后 `autoSelectCoupon()`:在 `ownedCoupons`(已按商品/金额过滤)中匹配 `c.id === this._autoCouponId`(券实例 id)→ 命中 `selectCoupon(matched)` 触发服务端重算折扣;**未命中静默忽略**(minSpend 不满足/券失效/被过滤不报错不强制)。
+- 只影响 `from=coupon` 场景;其他入口(购物车/商品详情/扫码特价)行为不变。
+- **验收口径**:已领取券显示「转赠/使用」不报「优惠券不存在」;指定商品券使用→确认订单页+自动选中;全品类→商城;会员券→开通会员;转赠→赠送页;已用/过期无操作按钮;构建 exit 0。
+
+---
+
+## 四、TDD 入池
+
+| 能力键 | 变更 | requirement 溯源 | design 溯源 |
+|--------|------|-----------------|------------|
+| `coupon.claimed.actions` | C1+C2 | 本文档需求 1+2 | `CouponService.toCouponVO`(券实例 id 契约,已合入)+ `productDetail` + 目标路由 |
+| `coupon.checkout.autoselect` | C3 | 本文档需求 2 | `checkout.vue ownedCoupons/selectCoupon`(已合入) |
+
+> RED 契约以「已合入 origin/cfclub 的 `CouponService.toCouponVO` 券实例 id 契约」为准。
+
+---
+
+## 五、风险与注意事项(主理人核实)
+
+1. **跳转必须用 `coupon.id`(券实例 id)作为 checkout 的 couponId 参数**,勿用 `coupon.couponId`(模板 id,会导致后端查无)——实现已遵循,核验通过。
+2. **自动选券只匹配 ownedCoupons 内**(避免选到不可用券),未命中静默忽略。
+3. **`ownedCoupons` 对 MEMBERSHIP 券会过滤**(checkout 不用于会员券),会员券走「使用→开通会员」不经 checkout,无冲突。
+4. **CF 值兑换 tab 与领券中心不受影响**(getExchangeableCoupons/exchangeCouponByCf 保留;coupon-center.vue 独立)。
+
+---
+
+## 六、关联记录
+
+- **实现任务书**:`docs/superpowers/specs/2026-11-06-coupon-claimed-actions.md`
+- **开发**:commit `1392c7e0b`(仅 coupons.vue +111/-19、checkout.vue +22;后端零改动)
+- **核验**:主理人独立核验通过(双按钮 ✓ / 按类型分派 ✓ / 券实例 id ✓ / 自动匹配静默忽略 ✓)
+- **合入**:已入 cfclub `1392c7e0b`
+- **backlog**:`DOC-2026-11-06-coupon-claimed-actions`(done)

+ 74 - 0
docs/需求分析/2026-11-06-我的团队收益口径与邀请分享需求变更.md

@@ -0,0 +1,74 @@
+# 我的团队:推广收益内外口径对齐 + 邀请成员直接分享 — 需求变更说明(2026-11-06)
+
+> **版本:** v1.0(主理人裁定,TDD 编排器入池依据)
+> **来源:** 用户 2026-11-06 需求 @skill:tdd-orchestrator "1、打卡和成长记录是如何定义的? 2、我的团队中的推广收益,外面显示的和里面不一致 3、在我的团队中邀请成员,应该直接分享页面,要满足推荐、下级绑定的需求"
+> **关联台账:** `docs/superpowers/specs/2026-11-06-team-invite-share.md`(实现任务书)
+> **状态:** ✅ 已完成开发 + 独立核验 + 已合入 cfclub(`037aeca7e`)
+> **范围:** 仅小程序 `cfc-frontend/pages/profile-main/profile.vue`;后端零改动
+
+---
+
+## 一、需求清单(原话)
+
+1. (理解性,已另行回答)**打卡和成长记录是如何定义的**——查证结论:打卡=家庭全部成员 `family_members.streak_days` 的**最高值**(max);成长记录=`growth_record` 按 `parent_id=当前登录用户` 统计(家长维度)——均来自 `UserController.getProfileStats`。**无代码改动。**
+2. **推广收益内外不一致**(bug 修复):profile「我的团队」外面显示的推广收益 与 推广中心里面显示的 不一致 → 统一口径。
+3. **邀请成员直接分享**:团队区块加「邀请成员」入口 → 点击**直接打开分享**(分享海报),满足**推荐 + 下级绑定**。
+
+---
+
+## 二、现状与根因(cfclub @ 2026-11-06)
+
+| 项 | 定位 | 关键事实 |
+|----|------|---------|
+| 收益-外面(旧口径) | `profile.vue` `loadReferralEarnings()`(L625-631) | `getReferralSummary().totalEarnings` = `CommissionRecord(status='settled')` 佣金求和(`CommissionService.getReferralSummary` L234-254) |
+| 收益-里面(新口径) | 推广中心 `promotion/index.vue` 收益总览(L119) | `summary.totalEarned` = `getCfSummary()`(`POST /api/commission/cf/summary`,CF 值总览) |
+| **不一致根因** | 两个数据源 | 外面=旧佣金表、里面=CF 值总览 → **数字不一致** |
+| 邀请入口 | `profile.vue`「我的团队」区块(L190-212) | 现有「我的团队」`goTeamTree`(L197)+「推广收益」`goToWealthCenter`(L204),**无邀请成员入口** |
+| 复用分享组件 | `profile.vue` 已有 `ContentSharePoster`(L346-357)+ `showShareQr()`(L682-709) | `scene='ref='+referralCode`、路径 `pages/index-home/index`,海报二维码含推荐码——**复用即可满足推荐/下级绑定** |
+
+**结论:** 需求 1 无代码改动;需求 2/3 均为**纯前端**改动(1 文件 profile.vue),后端零改动。
+
+---
+
+## 三、需求变更定义(目标态)
+
+### 变更 T1 · 推广收益改 CF 总览口径(与推广中心一致)
+- `loadReferralEarnings()` 由 `getReferralSummary().totalEarnings` 改为 `getCfSummary().totalEarned`(CF 值总览口径,与推广中心「累计获取CF」一致)。
+- **单位**:promotion 用 `cfVal(summary.totalEarned)` 直接展示原始值(`cfVal` 不分→元换算),故 profile 侧**同样不除 100**,保证「我的团队」推广收益数字 = 推广中心「累计获取CF」数字。
+- import:`getReferralSummary` → `getCfSummary`(确认无其他引用后替换)。
+
+### 变更 T2 · 团队区块加「🤝 邀请成员」→ 直接分享
+- 「我的团队」区块(L190-212)新增 `ach-item`「🤝 邀请成员」`@click="showShareQr"`(**复用现有 ContentSharePoster**,`scene='ref='+推荐码`,路径 `pages/index-home/index`)。
+- 好友扫码进首页自动绑定推荐人 → **下级绑定满足**。
+- 团队网格 2 项→3 项:新增 `.achievement-grid.team-grid .ach-item { width: 33.33% }`(3 列等宽),保持既有视觉协调(不触碰 `.achievement-grid` 全局样式,避免影响成就/卡券区块)。
+- **验收口径**:profile 推广收益 = 推广中心累计收益(元,数字一致);团队区块有「邀请成员」入口点击直接打开分享海报(含推荐码二维码);分享后好友扫码注册自动绑定推荐人;构建 exit 0。
+
+---
+
+## 四、TDD 入池
+
+| 能力键 | 变更 | requirement 溯源 | design 溯源 |
+|--------|------|-----------------|------------|
+| `team.referral.earnings.cf` | T1 | 本文档需求 2 | `CommissionController /api/commission/cf/summary`(CF 总览,已合入)+ `promotion/index.vue cfVal` 口径 |
+| `team.invite.share` | T2 | 本文档需求 3 | `profile.vue ContentSharePoster/showShareQr`(已合入,scene=ref=推荐码) |
+
+> RED 契约以「已合入 origin/cfclub 的 `CommissionController.cf/summary` 契约」为准。
+
+---
+
+## 五、风险与注意事项(主理人核实)
+
+1. **单位换算**:promotion 用 cfVal 且不分→元换算,profile 需**保持一致(不除 100)**——实现已按此执行,核验通过;若后端返回单位变更需同步。
+2. **网格 2 项→3 项**:布局可能拥挤,已用 `team-grid` 独立类 3 列等宽隔离,不触碰全局 `.achievement-grid`(不影响成就 5 列/卡券中心布局)。
+3. **邀请分享复用现有组件**:不新建、不改 ContentSharePoster 组件。
+4. **需求 1(定义)无代码改动**,不在本任务范围。
+
+---
+
+## 六、关联记录
+
+- **实现任务书**:`docs/superpowers/specs/2026-11-06-team-invite-share.md`
+- **开发**:commit `037aeca7e`(仅 profile.vue +20/-5;后端、api.js 零改动)
+- **核验**:主理人独立核验通过(CF 口径一致 ✓ / 邀请分享复用 ✓ / 3 列等宽隔离 ✓)
+- **合入**:已入 cfclub `037aeca7e`
+- **backlog**:`DOC-2026-11-06-team-invite-share`(done)

+ 71 - 0
docs/需求分析/2026-11-06-报告上传页关联商品卡片需求变更.md

@@ -0,0 +1,71 @@
+# 报告上传页关联商品卡片区 + 成员选择固定四列 — 需求变更说明(2026-11-06)
+
+> **版本:** v1.0(主理人裁定,TDD 编排器入池依据)
+> **来源:** 用户 2026-11-06 需求 @skill:tdd-orchestrator "小程序上传报告页面下面,增加关联的商品,可直接点击购买。注意报告对应的商品可能会有重复。报告上传页面的成员选择,固定为四列"
+> **关联台账:** `docs/superpowers/specs/2026-11-06-report-upload-product-cards.md`(实现任务书)
+> **状态:** ✅ 已完成开发 + 独立核验 + 合入 cfclub(`0bdc78def`,cherry-pick 自 feature/report-upload-product-cards 77f54d998)
+> **范围:** 仅小程序报告上传页 `cfc-frontend/pages/health/report-upload.vue`;后端零改动
+
+---
+
+## 一、需求清单(原话)
+
+1. 小程序上传报告页面下面,**增加关联的商品**,可直接点击购买;**报告对应的商品可能会有重复**(须按商品 id 去重)。
+2. 报告上传页面的**成员选择固定为四列**。
+
+---
+
+## 二、现状核查(cfclub @ 2026-11-06)
+
+| 项 | 定位 | 关键事实 |
+|----|------|---------|
+| 报告类型→商品配置 | `report_type_registry.linked_product_id`(后台 `ReportTypeManagement.vue` 可配) | 10 个报告类型,初始 linked_product_id 全 NULL(**用户需在后台配置后才展示**) |
+| 公开接口 | `ReportParserController` L57-92 `POST /api/report-parser/types/links` | 返回 `[{typeId, linkedProductId, productName}]`(is_active=1 且 linked_product_id 非空,productName 批量回填)——**已存在** |
+| 前端数据源 | `report-upload.vue` L172 `reportTypeLinks`、L248-256 `loadReportTypeLinks()`、L151 import `getReportTypeLinks` | 现有「支持的报告类型」guide-section 已用 `getTypeLink(typeId)` + `goProductDetail(typeId)` 显示「查看商品」小链接 |
+| 商品详情页 | `/pages/discover-detail/product-detail/product-detail?id=<productId>`(已有) | 详情页含购买/加购入口,可直接复用 |
+| 成员 4 列 | `.member-item { width: calc((100% - 48rpx) / 4) }`(commit `750f3d8b2` 已入 cfclub) | **已生效**;本任务只核验未被回退(核验通过,无需再改) |
+
+**结论:** 后端接口与商品详情页均已具备,**纯前端**改动(1 文件),复用既有 API,不新增后端接口。
+
+---
+
+## 三、需求变更定义(目标态)
+
+### 变更 R1 · 报告上传页下方新增「关联商品」卡片区(去重,可点击购买)
+
+- **数据**:computed `linkedProducts`——遍历 `reportTypeLinks`,**用 Set 按商品 id(`linkedProductId`)去重**(`parseInt` 后过滤 null/0/NaN),返回 `[{productId, productName}]`;同商品多个报告类型只保留一条(productName 取首个匹配);接口失败/未配置返回 `[]`。
+- **UI**:`guide-section` **之后**新增 `<view class="linked-products-section" v-if="linkedProducts.length > 0">`——标题「关联商品」+ 说明「以下商品与您的报告类型对应,点击可查看并购买」+ 商品卡片列表(网格 2 列),每卡片含商品名 + 「查看 / 购买」按钮;视觉与 guide-section 一致(圆角/背景/`$uni-space-*` 令牌)。
+- **交互**:点击卡片/按钮 → `goProductDetailByProduct(productId)` → `uni.navigateTo('/pages/discover-detail/product-detail/product-detail?id=' + productId)`(直接按商品 id 跳转,不依赖 typeId)。
+- **静默回退**:未配置任何关联商品 / 接口失败 → 卡片区**不显示**(不报错、不占空白)。
+- **核验项(非改动)**:成员选择 `.member-item` 4 列样式在 cfclub 仍生效(未被后续提交回退)。
+- **验收口径**:后台配置后卡片区出现且可购买;两类型挂同商品只显示一次;未配置不显示;成员 4 列;构建 `npm run build:mp-weixin` exit 0;仅改 report-upload.vue。
+
+---
+
+## 四、TDD 入池
+
+| 能力键 | 变更 | requirement 溯源 | design 溯源 |
+|--------|------|-----------------|------------|
+| `report.upload.linked-products` | R1 | 本文档需求 1 | `ReportParserController /api/report-parser/types/links`(已合入)+ `report_type_registry.linked_product_id` + 商品详情页路由 `product-detail?id=` |
+| `report.upload.member-4col` | 核验项 | 本文档需求 2 | `.member-item` 4 列样式(`750f3d8b2` 已合入) |
+
+> RED 契约以「已合入 origin/cfclub 的 `ReportParserController.types/links` 契约」为准(返回 `{typeId, linkedProductId, productName}`)。
+
+---
+
+## 五、风险与注意事项(主理人核实)
+
+1. **去重键必须用商品 id(linkedProductId),非报告类型 id**——任务书强调过,实现已按 Set<商品id> 去重,核验通过。
+2. **空值防护**:`link && link.linkedProductId` + `parseInt` 过滤无效条目(null/0/NaN),避免脏数据进入卡片区。
+3. **linked_product_id 全 NULL 是初始状态**:用户需在后台配置后卡片区才出现——这是"数据配置"而非"功能缺失",前端静默隐藏即正确行为。
+4. **成员 4 列仅核验**:`750f3d8b2` 已合入生效,本任务未改动该样式。
+
+---
+
+## 六、关联记录
+
+- **实现任务书**:`docs/superpowers/specs/2026-11-06-report-upload-product-cards.md`
+- **开发**:feature 分支 `feature/report-upload-product-cards`,commit `77f54d998`(仅 report-upload.vue +129 行,无后端/api.js/pages.json 改动)
+- **核验**:主理人独立核验通过(去重 Set 语义 ✓ / 空值防护 ✓ / 静默隐藏 ✓ / 4 列已生效 ✓ / 仅 1 文件 ✓)
+- **合入**:cherry-pick 至 cfclub `0bdc78def`
+- **backlog**:`DOC-2026-11-06-report-upload-product-cards`(done)