# 团队层级树与推荐关系可视化 实现计划 > **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。 **目标:** 实现「我的」页面推荐人卡片、团队层级入口、独立树形页面(从下往上生长),去掉富页面推荐码,新增 3 个后端接口并扩展 1 个现有接口。 **架构:** - 后端:在 `CommissionService` 新增 `getTeamConsumption`、`getMyReferrer`、`getTeamTree`,扩展 `getTeamList`;`CommissionController` 暴露 4 个 POST 接口 - 前端:删除 `wealth/index.vue` 推荐码横条;`profile/profile.vue` 新增推荐人卡片 + 入口按钮;新建 `profile/team-tree.vue` 树形页面(flex column-reverse + 递归组件);`utils/api.js` 新增 3 个方法;`pages.json` 注册新页面 **技术栈:** Spring Boot 2.7 + MyBatis-Plus / uni-app Vue 2 (Options API) --- ### 任务 1:后端 - 新增 Mapper 依赖注入 **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java:39-60` - [ ] **步骤 1:编写测试用例(手工验证)** ```bash # 编译验证 cd cfc-backend && mvn clean compile ``` - [ ] **步骤 2:添加注入字段** 在 `CommissionService` 类中已有的注入字段后追加: ```java @Resource private ProductOrderMapper productOrderMapper; @Resource private ReferralTreeMapper referralTreeMapper; @Resource private CfReferralService cfReferralService; ``` - [ ] **步骤 3:编译验证** ```bash cd cfc-backend && mvn clean compile ``` - [ ] **步骤 4:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java git commit -m "feat: 注入 ProductOrderMapper、ReferralTreeMapper、CfReferralService 到 CommissionService" ``` --- ### 任务 2:后端 - 实现 `getTeamConsumption`(团队消费统计) **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java`(在 `getTeamStats` 方法后,约第 599 行后追加) - [ ] **步骤 1:编写 curl 测试** ```bash curl -X POST http://localhost:9082/api/commission/team/consumption \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"maxLevel": 3}' # 预期:{"code":200,"data":{"totalConsumption":...,"levelBreakdown":[...],"totalMembers":...,"maxLevel":3}} ``` - [ ] **步骤 2:实现方法** ```java /** * 团队总消费额分级汇总(按 referral_tree level 聚合) * maxLevel 默认 3,可后台配置 */ public Map getTeamConsumption(Long userId, Integer maxLevel) { if (maxLevel == null || maxLevel <= 0) maxLevel = 3; if (maxLevel > 10) maxLevel = 10; // 安全上限 Map result = new LinkedHashMap<>(); List> levelBreakdown = new ArrayList<>(); // 1. 查 referral_tree: parentId = userId 且 level <= maxLevel List treeNodes = referralTreeMapper.selectList( new LambdaQueryWrapper() .eq(ReferralTree::getParentId, userId) .le(ReferralTree::getLevel, maxLevel) .orderByAsc(ReferralTree::getLevel) ); if (treeNodes.isEmpty()) { result.put("totalConsumption", 0); result.put("levelBreakdown", levelBreakdown); result.put("totalMembers", 0); result.put("maxLevel", maxLevel); return result; } // 2. 按 level 分组收集 childId Map> levelToChildIds = new LinkedHashMap<>(); for (ReferralTree node : treeNodes) { levelToChildIds.computeIfAbsent(node.getLevel(), k -> new ArrayList<>()).add(node.getChildId()); } // 3. 查 product_orders: buyerId IN childIds 且 fulfillStatus = 'completed' int totalConsumption = 0; for (Map.Entry> entry : levelToChildIds.entrySet()) { Integer level = entry.getKey(); List childIds = entry.getValue(); if (childIds.isEmpty()) continue; LambdaQueryWrapper orderWrapper = new LambdaQueryWrapper() .in(ProductOrder::getBuyerId, childIds) .eq(ProductOrder::getFulfillStatus, "completed"); Long sum = productOrderMapper.selectList(orderWrapper).stream() .mapToLong(o -> o.getTotalAmount() != null ? o.getTotalAmount() : 0) .sum(); int memberCount = childIds.size(); totalConsumption += sum; Map levelInfo = new LinkedHashMap<>(); levelInfo.put("level", level); levelInfo.put("memberCount", memberCount); levelInfo.put("consumption", (int) sum); levelBreakdown.add(levelInfo); } result.put("totalConsumption", totalConsumption); result.put("levelBreakdown", levelBreakdown); result.put("totalMembers", treeNodes.size()); result.put("maxLevel", maxLevel); return result; } ``` - [ ] **步骤 3:编译验证** ```bash cd cfc-backend && mvn clean compile ``` - [ ] **步骤 4:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java git commit -m "feat: 实现 getTeamConsumption 团队消费统计(按 referral_tree level 聚合,fulfillStatus=completed)" ``` --- ### 任务 3:后端 - 实现 `getMyReferrer`(我的推荐人) **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java`(在 getTeamConsumption 后新增) - [ ] **步骤 1:实现方法** ```java /** * 获取我的推荐人信息(直推人) */ public Map getMyReferrer(Long userId) { Map result = new LinkedHashMap<>(); User user = userMapper.selectById(userId); if (user == null || user.getReferrerId() == null) { result.put("hasReferrer", false); return result; } User referrer = userMapper.selectById(user.getReferrerId()); if (referrer == null) { result.put("hasReferrer", false); return result; } result.put("hasReferrer", true); result.put("referrerId", referrer.getId()); result.put("nickname", referrer.getNickname()); result.put("avatar", referrer.getAvatar()); result.put("referralCode", referrer.getReferralCode()); result.put("bindTime", user.getCreatedAt()); // 以用户创建时间作为绑定时间近似 return result; } ``` - [ ] **步骤 2:编译验证** ```bash cd cfc-backend && mvn clean compile ``` - [ ] **步骤 3:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java git commit -m "feat: 实现 getMyReferrer 获取我的直推人信息" ``` --- ### 任务 4:后端 - 实现 `getTeamTree`(树形扁平数据) **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java`(在 getMyReferrer 后新增) - [ ] **步骤 1:实现方法** ```java /** * 获取团队树形扁平数据(不含根节点,仅 level>=1 成员) * 每个节点包含 memberConsumption、myCfEarnedFromMember */ public Map getTeamTree(Long userId, Integer maxLevel) { if (maxLevel == null || maxLevel <= 0) maxLevel = 3; if (maxLevel > 10) maxLevel = 10; Map result = new LinkedHashMap<>(); List> nodes = new ArrayList<>(); // 1. 查 referral_tree List treeNodes = referralTreeMapper.selectList( new LambdaQueryWrapper() .eq(ReferralTree::getParentId, userId) .le(ReferralTree::getLevel, maxLevel) .orderByAsc(ReferralTree::getLevel) ); if (treeNodes.isEmpty()) { result.put("nodes", nodes); result.put("totalMembers", 0); result.put("maxLevel", maxLevel); return result; } // 2. 批量查用户基本信息 List childIds = treeNodes.stream().map(ReferralTree::getChildId).distinct().collect(Collectors.toList()); Map userMap = userMapper.selectBatchIds(childIds).stream() .collect(Collectors.toMap(User::getId, u -> u)); // 3. 按成员聚合 memberConsumption(fulfillStatus=completed 订单总额) Map memberConsumptionMap = new HashMap<>(); if (!childIds.isEmpty()) { List orders = productOrderMapper.selectList( new LambdaQueryWrapper() .in(ProductOrder::getBuyerId, childIds) .eq(ProductOrder::getFulfillStatus, "completed") ); for (ProductOrder o : orders) { if (o.getBuyerId() != null && o.getTotalAmount() != null) { memberConsumptionMap.merge(o.getBuyerId(), o.getTotalAmount(), Integer::sum); } } } // 4. 按成员聚合 myCfEarnedFromMember(commission_records 已结算+待结算,排除 cancelled) Map myCfEarnedMap = new HashMap<>(); if (!childIds.isEmpty()) { List records = commissionRecordMapper.selectList( new LambdaQueryWrapper() .eq(CommissionRecord::getReferrerId, userId) .in(CommissionRecord::getBuyerId, childIds) .ne(CommissionRecord::getStatus, "cancelled") ); for (CommissionRecord r : records) { if (r.getBuyerId() != null && r.getCommissionAmount() != null) { myCfEarnedMap.merge(r.getBuyerId(), r.getCommissionAmount(), Integer::sum); } } } // 5. 组装 nodes(不含根节点,仅 level>=1 的成员) for (ReferralTree node : treeNodes) { User u = userMap.get(node.getChildId()); if (u == null) continue; Map n = new LinkedHashMap<>(); n.put("userId", node.getChildId()); n.put("nickname", u.getNickname()); n.put("avatar", u.getAvatar()); n.put("level", node.getLevel()); n.put("parentId", node.getParentId()); n.put("referrerId", u.getReferrerId()); n.put("memberConsumption", memberConsumptionMap.getOrDefault(node.getChildId(), 0)); n.put("myCfEarnedFromMember", myCfEarnedMap.getOrDefault(node.getChildId(), 0)); nodes.add(n); } result.put("nodes", nodes); result.put("totalMembers", nodes.size()); result.put("maxLevel", maxLevel); return result; } ``` - [ ] **步骤 2:编译验证** ```bash cd cfc-backend && mvn clean compile ``` - [ ] **步骤 3:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java git commit -m "feat: 实现 getTeamTree 树形扁平数据(含 memberConsumption、myCfEarnedFromMember)" ``` --- ### 任务 5:后端 - 扩展 `getTeamList`(新增消费/CF 字段) **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java`(`getTeamList` 方法,约第 460-543 行) - [ ] **步骤 1:在 getTeamList 中,循环查询 memberConsumption 和 myCfEarnedFromMember 之前,改为预查所有成员的汇总** 在现有 `getTeamList` 方法中,找到 `for (Map.Entry> entry : memberMap.entrySet())` 循环**之前**,插入预查逻辑(替换现有循环内的逐成员查询): ```java // 预查所有成员的 memberConsumption 和 myCfEarnedFromMember Set allBuyerIds = memberMap.keySet(); Map memberConsumptionMap = new HashMap<>(); Map myCfEarnedMap = new HashMap<>(); if (!allBuyerIds.isEmpty()) { // memberConsumption:该成员已完成履约订单总额 LambdaQueryWrapper consumptionWrapper = new LambdaQueryWrapper() .in(ProductOrder::getBuyerId, allBuyerIds) .eq(ProductOrder::getFulfillStatus, "completed"); for (ProductOrder o : productOrderMapper.selectList(consumptionWrapper)) { if (o.getBuyerId() != null && o.getTotalAmount() != null) { memberConsumptionMap.merge(o.getBuyerId(), o.getTotalAmount(), Integer::sum); } } // myCfEarnedFromMember:我从该成员消费获得的 CF 总额 LambdaQueryWrapper cfWrapper = new LambdaQueryWrapper() .eq(CommissionRecord::getReferrerId, userId) .in(CommissionRecord::getBuyerId, allBuyerIds) .ne(CommissionRecord::getStatus, "cancelled"); if ("L1".equals(level)) cfWrapper.eq(CommissionRecord::getLevel, 1); else if ("L2".equals(level)) cfWrapper.eq(CommissionRecord::getLevel, 2); SortUtil.applySort(cfWrapper); for (CommissionRecord r : commissionRecordMapper.selectList(cfWrapper)) { if (r.getBuyerId() != null && r.getCommissionAmount() != null) { myCfEarnedMap.merge(r.getBuyerId(), r.getCommissionAmount(), Integer::sum); } } } ``` - [ ] **步骤 2:在现有循环内部,用预查结果替换原来的逐成员查询,加入新字段** 找到现有循环内: ```java int commissionEarned = commissionRecordMapper.selectList(commissionWrapper).stream() .mapToInt(r -> r.getCommissionAmount() != null ? r.getCommissionAmount() : 0) .sum(); memberInfo.put("commissionEarned", commissionEarned); ``` 替换为: ```java memberInfo.put("memberConsumption", memberConsumptionMap.getOrDefault(buyerId, 0)); memberInfo.put("myCfEarnedFromMember", myCfEarnedMap.getOrDefault(buyerId, 0)); // 保留原有 commissionEarned(用于展示佣金) int commissionEarned = commissionRecordMapper.selectList(commissionWrapper).stream() .mapToInt(r -> r.getCommissionAmount() != null ? r.getCommissionAmount() : 0) .sum(); memberInfo.put("commissionEarned", commissionEarned); ``` - [ ] **步骤 3:编译验证** ```bash cd cfc-backend && mvn clean compile ``` - [ ] **步骤 4:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java git commit -m "feat: 扩展 getTeamList 新增 memberConsumption、myCfEarnedFromMember 字段" ``` --- ### 任务 6:后端 - 新增 Controller 端点 **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/controller/CommissionController.java`(在类末尾前追加 3 个方法) - [ ] **步骤 1:追加 3 个新端点** ```java /** * 团队消费额分级汇总 */ @PostMapping("/team/consumption") public Result> teamConsumption(@RequestAttribute("userId") Long userId, @RequestBody Map params) { Integer maxLevel = params.get("maxLevel") != null ? ((Number) params.get("maxLevel")).intValue() : 3; return Result.success(commissionService.getTeamConsumption(userId, maxLevel)); } /** * 获取我的推荐人 */ @PostMapping("/my-referrer") public Result> myReferrer(@RequestAttribute("userId") Long userId) { return Result.success(commissionService.getMyReferrer(userId)); } /** * 团队树形扁平数据 */ @PostMapping("/team/tree") public Result> teamTree(@RequestAttribute("userId") Long userId, @RequestBody Map params) { Integer maxLevel = params.get("maxLevel") != null ? ((Number) params.get("maxLevel")).intValue() : 3; return Result.success(commissionService.getTeamTree(userId, maxLevel)); } ``` - [ ] **步骤 2:编译验证** ```bash cd cfc-backend && mvn clean compile ``` - [ ] **步骤 3:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/controller/CommissionController.java git commit -m "feat: 新增 /api/commission/team/consumption、/my-referrer、/team/tree 接口" ``` --- ### 任务 7:后端 - 更新 API_REFERENCE.md **文件:** - 修改:`docs/superpowers/api/API_REFERENCE.md`(在 4.21 佣金管理章节后追加) - [ ] **步骤 1:追加新接口文档** 在 `### 4.21 佣金管理(/api/commission/*)` 之后新增: ```markdown ### 4.22 团队消费与推荐关系(`/api/commission/*` 扩展) | 路径 | 说明 | |------|------| | `POST /api/commission/team/list` | 扩展:返回中每个成员新增 `memberConsumption`(该成员自身完成履约订单总额,分)、`myCfEarnedFromMember`(我从该成员消费获得的 CF 总额,分) | | `POST /api/commission/team/consumption` | 团队总消费额分级汇总,请求 `{maxLevel}`(默认 3),返回 `{totalConsumption: 分, levelBreakdown[{level, memberCount, consumption}], totalMembers, maxLevel}` | | `POST /api/commission/my-referrer` | 获取我的直推人信息,返回 `{hasReferrer: bool, referrerId, nickname, avatar, referralCode, bindTime}`,无推荐人时 hasReferrer=false | | `POST /api/commission/team/tree` | 团队树形扁平数据(不含根节点),请求 `{maxLevel}`(默认 3),返回 `{nodes[{userId, nickname, avatar, level, parentId, referrerId, memberConsumption, myCfEarnedFromMember}], totalMembers, maxLevel}` | ``` - [ ] **步骤 2:Commit** ```bash git add docs/superpowers/api/API_REFERENCE.md git commit -m "docs: 更新 API_REFERENCE.md 新增团队消费与推荐关系接口" ``` --- ### 任务 8:前端 - 删除富页面推荐码横条 **文件:** - 修改:`cfc-frontend/pages/wealth/index.vue:161-165` - [ ] **步骤 1:删除模板中的 `referral-code-bar` 区块** ```diff - - - 我的邀请码 - {{ wealthData.referralCode }} - 复制 - ``` - [ ] **步骤 2:删除 CSS 中 `.referral-code-bar` 相关样式** ```diff - /* 邀请码复制条 */ - .referral-code-bar { - display: flex; - align-items: center; - background: #fff; - margin-top: 16rpx; - border-radius: 16rpx; - padding: 24rpx 30rpx; - box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); - } - .referral-code-label { font-size: 26rpx; color: #666; margin-right: 16rpx; } - .referral-code-value { flex: 1; font-size: 30rpx; font-weight: bold; color: #F59E0B; letter-spacing: 2rpx; } - .referral-code-copy { font-size: 24rpx; color: #fff; background: linear-gradient(135deg, #F59E0B, #FBBF24); padding: 8rpx 24rpx; border-radius: 20rpx; } ``` - [ ] **步骤 3:删除 methods 中的 `copyReferralCode` 方法(如不再使用)** 检查 `copyReferralCode` 是否在其他地方使用,若未使用则删除。 - [ ] **步骤 4:Commit** ```bash git add cfc-frontend/pages/wealth/index.vue git commit -m "feat(wealth): 删除富页面推荐码横条" ``` --- ### 任务 9:前端 - 我的页面新增推荐人卡片 + 团队入口 **文件:** - 修改:`cfc-frontend/pages/profile-main/profile.vue` - [ ] **步骤 1:import 部分追加** ```js // 在现有 import 中追加 import { getMyReferrer } from '../../utils/api.js' ``` - [ ] **步骤 2:data() 追加字段** ```js data() { return { // ...existing fields... myReferrer: null } } ``` - [ ] **步骤 3:methods 新增加载与工具方法** ```js methods: { // ...existing methods... async loadMyReferrer() { var self = this try { var res = await getMyReferrer() if (res && res.code === 200 && res.data && res.data.hasReferrer) { self.myReferrer = res.data } } catch (e) { console.error('获取推荐人失败', e) } }, goTeamTree() { uni.navigateTo({ url: '/pages/profile/team-tree' }) }, copyReferrerCode() { if (!this.myReferrer || !this.myReferrer.referralCode) return uni.setClipboardData({ data: this.myReferrer.referralCode, success: function() { uni.showToast({ title: '邀请码已复制', icon: 'success' }) } }) } } ``` - [ ] **步骤 4:onShow 中调用 loadMyReferrer** ```js onShow() { if (!this.isLoggedIn) return // ...existing... this.loadMyReferrer() } ``` - [ ] **步骤 5:模板中在「个人成就统计」section 前插入以下 HTML** ```html 我的推荐人 {{ myReferrer.nickname ? myReferrer.nickname.charAt(0) : '?' }} {{ myReferrer.nickname }} 邀请码:{{ myReferrer.referralCode }} {{ formatReferralTime(myReferrer.bindTime) }} 复制 🌳 团队层级 查看团队结构与消费明细 › ``` - [ ] **步骤 6:添加时间格式化方法** ```js methods: { // ...existing... formatReferralTime(ts) { if (!ts) return '' var d = ts if (typeof d === 'string' && d.indexOf('T') > -1) { d = d.substring(0, 16).replace('T', ' ') } return '绑定于 ' + d } } ``` - [ ] **步骤 7:在 style 末尾追加样式** ```css /* 我的推荐人卡片 */ .referrer-card { margin: 0 30rpx 20rpx; background: #fff; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); } .referrer-header { margin-bottom: 16rpx; } .referrer-title { font-size: 28rpx; font-weight: 600; color: #1E293B; } .referrer-content { display: flex; align-items: center; gap: 20rpx; } .referrer-avatar-wrap { width: 72rpx; height: 72rpx; border-radius: 50%; background: #FEF3C7; display: flex; align-items: center; justify-content: center; overflow: hidden; flex-shrink: 0; } .referrer-avatar { width: 72rpx; height: 72rpx; border-radius: 50%; } .referrer-avatar-text { font-size: 28rpx; color: #F97316; } .referrer-info { flex: 1; min-width: 0; } .referrer-name { font-size: 28rpx; font-weight: 500; color: #1E293B; display: block; } .referrer-code { font-size: 24rpx; color: #F97316; margin-top: 4rpx; display: block; } .referrer-time { font-size: 22rpx; color: #94A3B8; margin-top: 4rpx; display: block; } .referrer-copy-btn { font-size: 26rpx; color: #F97316; padding: 8rpx 16rpx; border: 1rpx solid #FED7AA; border-radius: 20rpx; flex-shrink: 0; } .team-tree-entry { margin: 0 30rpx 20rpx; background: linear-gradient(135deg, #FFF7ED, rgba(249,115,22,0.06)); border-radius: 20rpx; padding: 24rpx; display: flex; align-items: center; gap: 20rpx; box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08); border-left: 6rpx solid #F97316; } .entry-icon { font-size: 40rpx; } .entry-text { flex: 1; min-width: 0; } .entry-title { font-size: 28rpx; font-weight: 600; color: #1E293B; display: block; } .entry-desc { font-size: 22rpx; color: #94A3B8; margin-top: 4rpx; display: block; } .entry-arrow { font-size: 28rpx; color: #F97316; } ``` - [ ] **步骤 8:Commit** ```bash git add cfc-frontend/pages/profile-main/profile.vue git commit -m "feat(profile): 新增我的推荐人卡片与团队层级入口" ``` --- ### 任务 10:前端 - 新增 api.js 方法 **文件:** - 修改:`cfc-frontend/utils/api.js`(在现有 commission 相关 export 后追加) - [ ] **步骤 1:追加 3 个 export 函数** ```javascript export const getTeamConsumption = (maxLevel = 3) => request('/api/commission/team/consumption', 'POST', { maxLevel }) export const getMyReferrer = () => request('/api/commission/my-referrer', 'POST') export const getTeamTree = (maxLevel = 3) => request('/api/commission/team/tree', 'POST', { maxLevel }) ``` - [ ] **步骤 2:Commit** ```bash git add cfc-frontend/utils/api.js git commit -m "feat(api): 新增 getTeamConsumption、getMyReferrer、getTeamTree" ``` --- ### 任务 11:前端 - 新建 `pages/profile/team-tree.vue` **文件:** - 创建:`cfc-frontend/pages/profile/team-tree.vue` - [ ] **步骤 1:创建完整页面文件** ```vue ``` - [ ] **步骤 2:语法检查** ```bash cd cfc-frontend && node --check pages/profile/team-tree.vue 2>&1 || true ``` - [ ] **步骤 3:Commit** ```bash git add cfc-frontend/pages/profile/team-tree.vue git commit -m "feat(profile): 新建 team-tree 页面(树形从下往上生长、层级切换、消费/CF明细)" ``` --- ### 任务 12:前端 - 注册 `pages.json` 新页面 **文件:** - 修改:`cfc-frontend/pages.json`(在 `pages/profile` root 下的 `pages` 数组追加) - [ ] **步骤 1:找到 `pages/profile` root 并追加** 在 `pages.json` 中找到 `"root": "pages/profile"` 的 section,在数组末尾追加: ```json { "path": "pages/profile/team-tree", "style": { "navigationBarTitleText": "团队层级", "enablePullDownRefresh": false } } ``` - [ ] **步骤 2:语法验证** ```bash cd cfc-frontend && node -e "JSON.parse(require('fs').readFileSync('pages.json','utf8'))" && echo "JSON valid" ``` - [ ] **步骤 3:Commit** ```bash git add cfc-frontend/pages.json git commit -m "feat(pages.json): 注册 team-tree 页面" ``` --- ### 任务 13:联调验证 + API 审计 **文件:** - 无新增文件 - [ ] **步骤 1:编译后端** ```bash cd cfc-backend && mvn clean compile ``` - [ ] **步骤 2:启动后端服务(HBuilderX 或命令行)并手动验证接口** ```bash # 后端编译通过后启动,在微信开发者工具中打开小程序测试 # 关键接口验证点: # 1. GET /api/commission/team/consumption?maxLevel=3 → 200 # 2. POST /api/commission/my-referrer → 200 # 3. POST /api/commission/team/tree?maxLevel=3 → 200 # 4. POST /api/commission/team/list?level=L1 → 200, records[].memberConsumption exists ``` - [ ] **步骤 3:HBuilderX 打包小程序** 在 HBuilderX 中打开 `cfc-frontend/`,运行到微信开发者工具,依次验证: - 富页面:无推荐码横条,有「邀请人数」 - 我的页面:有推荐人卡片(显示昵称/头像/邀请码/复制按钮)、有「团队层级」入口 - 点击「团队层级」→ 进入 team-tree,显示统计卡 + 层级 tabs + 树形 - 切换层级 tabs → 树形和统计更新 - 点击节点 → 展开/折叠 - 无推荐人用户 → 推荐人卡片不显示 - 空团队 → 显示「暂无团队成员」 - [ ] **步骤 4:API 重复调用审计** ```bash cd cfc-frontend && node scripts/audit-duplicate-api-calls.js # 预期:0 error ``` - [ ] **步骤 5:Commit** ```bash git add -A git commit -m "chore: 联调验证通过 + API审计0 error" ``` --- ## 自检清单(实施后逐条确认) - [ ] 富页面无推荐码横条,保留「邀请人数」数字 - [ ] 我的页面有推荐人卡片(有推荐人时显示;无推荐人时不显示) - [ ] 我的页面有「团队层级」入口,点击跳转 `/pages/profile/team-tree` - [ ] team-tree 页面: - 顶部统计卡:总消费额(分转元)、人数、层级数 - 1-5 级切换 tabs,切换后统计和树形同步更新 - 树形从下往上生长(L1 在底部,L2/L3 向上延伸) - 每节点显示:头像、昵称、L标签、消费额、我得CF、展开图标 - 点击节点展开/折叠,子节点在上方渲染,虚线连接正确 - 空团队显示「暂无团队成员」 - [ ] 复制推荐码按钮正常工作 - [ ] 后端 4 个接口响应正确,字段名与设计文档一致 - [ ] `mvn clean compile` 通过 - [ ] `audit-duplicate-api-calls.js` 0 error - [ ] API_REFERENCE.md 已同步更新 --- **文档结束**