瀏覽代碼

docs: 修正团队层级设计 - 订单状态字段/树响应字段/UI实现

- 修正 product_orders 完成状态: fulfillment_status = 'completed'
- 树响应 nodes[] 新增 memberConsumption、myCfEarnedFromMember 字段
- 根节点('我')不包含在 nodes 数组中
- 更新 UI 实现代码片段(无根节点、flex column-reverse、L1为起点)
- audit 通过(0 error)
Sisyphus 1 周之前
父節點
當前提交
4cba3a46f7
共有 1 個文件被更改,包括 23 次插入30 次删除
  1. 23 30
      docs/superpowers/specs/2026-09-09-team-hierarchy-design.md

+ 23 - 30
docs/superpowers/specs/2026-09-09-team-hierarchy-design.md

@@ -56,14 +56,14 @@
 
 | 表 | 关键字段 | 用途 |
 |---|---|---|
-| `users` | `id`, `referrerId`, `referralCode`, `nickname`, `avatar` | 用户与推荐关系 |
+| `users` | `id`, `referrerId`, `referralCode`, `nickname`, `avatar`, `created_at` | 用户与推荐关系 |
 | `referral_tree` | `parentId`, `childId`, `level`, `path` | 物化全层级推荐树 |
-| `product_orders` | `buyerId`, `totalAmount`, `status`, `paidAt` | 消费订单 |
-| `commission_records` | `referrerId`, `buyerId`, `commissionAmount`, `level`, `status` | 佣金记录(L1/L2) |
-| `user_platform_balance` | `userId`, `totalEarned`, `available`, `frozen`, `withdrawn` | CF 值钱包 |
-| `cf_rate_tier` | `minTeamSize`, `ratePercent`, `enabled` | 返佣阶梯(现有) |
+| `product_orders` | `buyerId`, `totalAmount`, `status`, `fulfillStatus`, `paidAt` | 消费订单(fulfillStatus=completed 表示已完成履约) |
+| `commission_records` | `referrerId`, `buyerId`, `commissionAmount`, `level`, `status` | 佣金记录(L1/L2;status=settled/cancelled) |
+| `user_platform_balance` | `userId`, `totalEarned`, `available`, `frozen`, `withdrawn` | CF 值钱包(现有,本需求不直接读) |
+| `cf_rate_tier` | `minTeamSize`, `ratePercent`, `enabled` | 返佣阶梯(现有,复用其 maxLevel 配置) |
 
-> 注:`referral_tree.level` = 代际差(1=直推,2=间推...),`path` = `/最远祖先/.../直接推荐人/`
+> 注:`referral_tree.level` = 代际差(1=直推,2=间推...);`commission_records.level` = 1/2(仅 L1/L2 结算佣金);订单完成以 `fulfillStatus = 'completed'` 为准。
 
 ### 3.2 后端接口设计
 
@@ -93,8 +93,8 @@
 ```
 
 **实现逻辑:**
-- `memberConsumption`:查 `product_orders` 表 `buyerId = 该成员` 且 `status IN ('completed', 'paid', 'received')` 的 `totalAmount` 总和
-- `myCfEarnedFromMember`:查 `commission_records` 表 `referrerId = 我` 且 `buyerId = 该成员` 且 `status != 'cancelled'` 的 `commissionAmount` 总和
+- `memberConsumption`:查 `product_orders` 表 `buyerId = 该成员` 且 `fulfillStatus = 'completed'` 的 `totalAmount` 总和(仅统计已完成履约的订单)
+- `myCfEarnedFromMember`:查 `commission_records` 表 `referrerId = 我` 且 `buyerId = 该成员` 且 `status != 'cancelled'` 的 `commissionAmount` 总和(含 settled + pending)
 
 #### 3.2.2 新增 `/api/commission/team/consumption`
 
@@ -117,7 +117,7 @@
 **实现逻辑:**
 1. 查 `referral_tree` 表 `parentId = 我` 且 `level <= maxLevel` 的所有 `childId`
 2. 按 `level` 分组统计 `memberCount`
-3. 查这些 `childId` 的 `product_orders`(completed 状态)`totalAmount` 总和,按 `level` 汇总
+3. 查这些 `childId` 的 `product_orders`(`fulfillStatus = 'completed'`)`totalAmount` 总和,按 `level` 汇总
 
 **配置化:**
 - 读取 `cf_rate_tier` 表中 `enabled=1` 的记录,或新增 `commission_config` 表存储 `team_consumption_max_level`(默认 3)
@@ -150,23 +150,16 @@
 ```json
 {
   "nodes": [
-    { "userId": 100, "nickname": "我", "level": 0, "parentId": null, "referrerId": 456 },
-    { "userId": 101, "nickname": "直推A", "level": 1, "parentId": 100, "referrerId": 100 },
-    { "userId": 102, "nickname": "间推B", "level": 2, "parentId": 101, "referrerId": 101 },
-    { "userId": 103, "nickname": "直推C", "level": 1, "parentId": 100, "referrerId": 100 }
+    { "userId": 101, "nickname": "直推A", "avatar": "https://...", "level": 1, "parentId": 100, "referrerId": 100, "memberConsumption": 32000, "myCfEarnedFromMember": 9600 },
+    { "userId": 102, "nickname": "间推B", "avatar": "https://...", "level": 2, "parentId": 101, "referrerId": 101, "memberConsumption": 8000, "myCfEarnedFromMember": 2400 },
+    { "userId": 103, "nickname": "直推C", "avatar": "https://...", "level": 1, "parentId": 100, "referrerId": 100, "memberConsumption": 15000, "myCfEarnedFromMember": 4500 }
   ],
   "totalMembers": 3,
   "maxLevel": 3
 }
 ```
 
-**实现逻辑:**
-1. 查 `referral_tree` `parentId = 我` 且 `level <= maxLevel` → 后代集合 `{childId, level}`
-2. 对每个 `childId`,查 `users` 表得 `nickname`, `avatar`, `referrerId`
-3. `referrerId` 即为树中的直接父节点(`referrerId = 我` 则父节点为我)
-4. 组装扁平数组,前端递归渲染树形
-
-> 也可复用 `DistributionService.getTeamTree` 逻辑,但该服务基于 `distribution_relations` 表,当前推荐体系基于 `users.referrerId` + `referral_tree`,建议在 `CommissionService` 新增实现。
+> **说明:** `nodes` 数组**不包含根节点("我")**——仅包含 team 成员(level >= 1);根节点仅用于前端统计/上下文。每个节点已包含 `memberConsumption`(该成员自身已完成履约订单总额)和 `myCfEarnedFromMember`(我从该成员消费获得的 CF 总额)。前端按 `parentId` 递归构建树,`level` 用于层级标签与缩进。
 
 ---
 
@@ -182,25 +175,25 @@
 
 **树形 UI 实现思路(从下往上生长):**
 ```vue
-<!-- 树形容器:flex column-reverse,根节点在底部 -->
+<!-- 树形容器:flex column-reverse,L1节点在底部,分支向上延伸 -->
 <view class="tree-container" style="flex-direction: column-reverse;">
-  <!-- 递归组件 TreeNode -->
-  <TreeNode :node="rootNode" :level="0" :maxLevel="maxLevel" />
+  <!-- L1节点作为根:扁平 nodes 先按 parentId 组装为树,再渲染 -->
+  <TreeNode v-for="root in level1Roots" :key="root.userId" :node="root" :level="1" :maxLevel="maxLevel" />
 </view>
 
 <!-- TreeNode.vue -->
-<view class="tree-node" :style="{ 'padding-left': level * 30 + 'rpx' }">
+<view class="tree-node" :style="{ 'padding-left': (level - 1) * 30 + 'rpx' }">
   <view class="node-main" @click="toggle">
     <image :src="node.avatar" />
     <text>{{ node.nickname }}</text>
-    <tag v-if="node.level > 0">L{{ node.level }}</tag>
-    <text class="consumption">消费: ¥{{ node.memberConsumption/100 }}</text>
-    <text class="my-cf">我得CF: ¥{{ node.myCfEarnedFromMember/100 }}</text>
+    <tag>L{{ node.level }}</tag>
+    <text class="consumption">消费: ¥{{ (node.memberConsumption/100).toFixed(2) }}</text>
+    <text class="my-cf">我得CF: ¥{{ (node.myCfEarnedFromMember/100).toFixed(2) }}</text>
     <icon :name="expanded ? 'up' : 'down'" />
   </view>
   <!-- 子节点:因为父容器 column-reverse,子节点会在上方渲染 -->
   <view v-if="expanded && node.children" class="children">
-    <TreeNode v-for="child in node.children" :key="child.userId" :node="child" :level="level+1" />
+    <TreeNode v-for="child in node.children" :key="child.userId" :node="child" :level="level+1" :maxLevel="maxLevel" />
   </view>
   <!-- 连接线:::before 画竖线,父节点中心向上 -->
 </view>
@@ -275,7 +268,7 @@ public Result<Map<String, Object>> teamTree(@RequestAttribute("userId") Long use
 | 团队成员无消费订单 | 消费额显示 0,CF 获得显示 0 |
 | `maxLevel` 超过实际层级 | 按实际最大层级统计 |
 | `referral_tree` 数据不一致 | 以 `users.referrerId` 为准,`referral_tree` 为辅助加速查询 |
-| 订单状态:仅统计 `completed`/`paid`/`received` | 排除 `cancelled`/`refunding`/`refunded` |
+| 订单状态:仅统计已完成履约 | 高 | 严格筛选 `fulfillStatus = 'completed'`(排除 pending/confirmed_by_vendor/received_by_user) |
 
 ---
 
@@ -328,7 +321,7 @@ public Result<Map<String, Object>> teamTree(@RequestAttribute("userId") Long use
 | 风险 | 等级 | 对策 |
 |---|---|---|
 | `referral_tree` 数据量大,树形查询慢 | 中 | 限制 `maxLevel`≤5,加索引 `idx_parent_level(parentId, level)` |
-| 消费额统计包含退款订单 | 高 | 严格筛选 `status` 仅 completed/paid/received |
+| 消费额统计包含退款订单 | 高 | 严格筛选 `fulfillStatus = 'completed'`(仅统计已完成履约的订单,排除未结算/退货状态) |
 | 树形 UI 在小程序渲染卡顿 | 低 | 虚拟列表/分页加载,层级>3 懒加载 |
 | 后台配置未生效(缓存) | 低 | 配置读取走 Redis,更新后清理 |