# 管理员分配爱心值给家庭成员 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 管理员在家庭成员列表中,可从用户总账号余额分配爱心值给指定成员,同时写两条流水记录。 **Architecture:** 后端新增 `assignLoveValue` 接口,在事务中完成余额校验、双向扣增、流水写入;前端在 `familyMember.vue` 新增爱心值列、分配按钮和弹窗,通过新增 prop `userLoveValue` 从 `detail.vue` 获取总账号余额。 **Tech Stack:** Java 17、Spring Boot、MyBatis-Plus(后端);Vue 2.6、Element UI(前端) --- ## 文件变更清单 | 操作 | 文件 | |------|------| | Modify | `happytree-sys/src/main/java/com/ringzle/modules/core/dto/FamilyMemberDTO.java` | | Modify | `happytree-sys/src/main/java/com/ringzle/modules/core/service/FamilyMemberService.java` | | Modify | `happytree-sys/src/main/java/com/ringzle/modules/core/service/impl/FamilyMemberServiceImpl.java` | | Modify | `happytree-admin/src/main/java/com/ringzle/modules/core/FamilyMemberController.java` | | Modify | `SxsnPC/src/views/modules/member/components/familyMember.vue` | | Modify | `SxsnPC/src/views/modules/member/detail.vue` | --- ### Task 1: FamilyMemberDTO 新增 loveValue 字段 **Files:** - Modify: `happytree-sys/src/main/java/com/ringzle/modules/core/dto/FamilyMemberDTO.java` - [ ] **Step 1: 在 channelIds 字段之前插入 loveValue 字段** 找到文件中 `channelIds` 字段: ```java @ApiModelProperty(value = "可用渠道ID,逗号分隔") private String channelIds; ``` 在其前面插入: ```java @ApiModelProperty(value = "爱心值") private BigDecimal loveValue; ``` 同时确认文件顶部已有 `import java.math.BigDecimal;`(已有则跳过)。 - [ ] **Step 2: Commit** ```bash cd /workspace/sxsn/happytree git add happytree-sys/src/main/java/com/ringzle/modules/core/dto/FamilyMemberDTO.java git commit -m "feat: FamilyMemberDTO 新增 loveValue 字段" ``` --- ### Task 2: FamilyMemberService 新增 assignLoveValue 接口声明 **Files:** - Modify: `happytree-sys/src/main/java/com/ringzle/modules/core/service/FamilyMemberService.java` - [ ] **Step 1: 在接口末尾新增方法声明** 找到文件末尾 `getMemberChannels` 方法声明后,在 `}` 之前插入: ```java /** * 管理员从总账号分配爱心值给家庭成员 * @param memberId 家庭成员 ID * @param userId 总账号用户 ID * @param amount 分配数量,必须 > 0 且 <= 总账号余额 */ void assignLoveValue(Long memberId, Long userId, BigDecimal amount); ``` 同时在文件顶部 import 区域添加(若缺少): ```java import java.math.BigDecimal; ``` - [ ] **Step 2: Commit** ```bash cd /workspace/sxsn/happytree git add happytree-sys/src/main/java/com/ringzle/modules/core/service/FamilyMemberService.java git commit -m "feat: FamilyMemberService 新增 assignLoveValue 接口" ``` --- ### Task 3: FamilyMemberServiceImpl — page() 填充 loveValue + 实现 assignLoveValue **Files:** - Modify: `happytree-sys/src/main/java/com/ringzle/modules/core/service/impl/FamilyMemberServiceImpl.java` 背景: - `FamilyMemberServiceImpl` 已注入 `appUserService`(`AppUserService`) - `FamilyMemberServiceImpl` 已有 `page()` 重写方法,其中已通过 `baseDao.selectBatchIds` 批量查出 Entity 列表(用于填充 channelNames) - `LoveValueRecordEntity` 使用 Builder 模式,字段:`userId`, `capitalFlow`, `transactionType`, `memberId`, `loveQuantity` - `transactionType=3` 为本次新增类型(管理员分配) - `capitalFlow=1` 收入,`capitalFlow=2` 支出 #### Step 1: 在 page() 方法中复用 entities,填充 loveValue 找到 `page()` 方法中已有的 entities 批量查询和 channelNames 填充逻辑,在 `list.forEach` 填充 channelNames 的同时,追加填充 loveValue。 当前 `page()` 方法中有: ```java List entities = baseDao.selectBatchIds( list.stream().map(FamilyMemberDTO::getId).collect(Collectors.toList()) ); ``` 在该查询之后,建立 `memberId -> loveValue` 的 Map: ```java Map memberLoveValueMap = entities.stream() .collect(Collectors.toMap(FamilyMemberEntity::getId, e -> e.getLoveValue() != null ? e.getLoveValue() : BigDecimal.ZERO)); ``` 然后在 `list.forEach` 中,在 `dto.setChannelNames(names)` 之后追加: ```java dto.setLoveValue(memberLoveValueMap.getOrDefault(dto.getId(), BigDecimal.ZERO)); ``` - [ ] **Step 1: 修改 page() 方法,填充 loveValue** 读取当前 `page()` 方法完整内容,按上述说明修改。完整修改后的 `page()` 方法应如下: ```java @Override public PageData page(Map params) { PageData pageData = super.page(params); List list = pageData.getList(); if (CollectionUtil.isEmpty(list)) { return pageData; } List entities = baseDao.selectBatchIds( list.stream().map(FamilyMemberDTO::getId).collect(Collectors.toList()) ); Map memberChannelIdsMap = list.stream() .filter(dto -> StringUtils.isNotBlank(dto.getChannelIds())) .collect(Collectors.toMap(FamilyMemberDTO::getId, FamilyMemberDTO::getChannelIds)); Set channelIdSet = memberChannelIdsMap.values().stream() .flatMap(ids -> Arrays.stream(ids.split(","))) .map(String::trim) .filter(StringUtils::isNotBlank) .flatMap(id -> { try { return java.util.stream.Stream.of(Long.parseLong(id)); } catch (NumberFormatException e) { return java.util.stream.Stream.empty(); } }) .collect(Collectors.toSet()); Map channelNameMap = new HashMap<>(); if (!channelIdSet.isEmpty()) { Collection channels = channelDao.selectBatchIds(channelIdSet); channels.forEach(c -> channelNameMap.put(c.getId(), c.getChannelName())); } Map memberLoveValueMap = entities.stream() .collect(Collectors.toMap(FamilyMemberEntity::getId, e -> e.getLoveValue() != null ? e.getLoveValue() : BigDecimal.ZERO)); list.forEach(dto -> { String channelIds = memberChannelIdsMap.get(dto.getId()); if (StringUtils.isBlank(channelIds)) { dto.setChannelNames(""); } else { String names = Arrays.stream(channelIds.split(",")) .map(String::trim) .filter(StringUtils::isNotBlank) .map(id -> { try { return channelNameMap.getOrDefault(Long.parseLong(id), ""); } catch (NumberFormatException e) { return ""; } }) .filter(StringUtils::isNotBlank) .collect(Collectors.joining("、")); dto.setChannelNames(names); } dto.setLoveValue(memberLoveValueMap.getOrDefault(dto.getId(), BigDecimal.ZERO)); }); return pageData; } ``` - [ ] **Step 2: 新增 assignLoveValue() 方法** 在 `getMemberChannels()` 方法之后,类的 `}` 之前插入: ```java @Override @Transactional(rollbackFor = Exception.class) public void assignLoveValue(Long memberId, Long userId, BigDecimal amount) { if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) { throw new RenException("分配数量必须大于0"); } // 校验总账号余额 AppUserEntity user = appUserService.getEntityById(userId); if (user == null) { throw new RenException("用户不存在"); } if (user.getLoveValue() == null || user.getLoveValue().compareTo(amount) < 0) { throw new RenException("总账号余额不足"); } // 校验成员属于该用户 FamilyMemberEntity member = baseDao.selectById(memberId); if (member == null || !member.getUserId().equals(userId)) { throw new RenException("家庭成员不存在"); } // 扣减总账号余额 user.setLoveValue(user.getLoveValue().subtract(amount)); appUserService.updateById(user); // 增加成员余额 BigDecimal memberLove = member.getLoveValue() != null ? member.getLoveValue() : BigDecimal.ZERO; member.setLoveValue(memberLove.add(amount)); baseDao.updateById(member); // 写流水:总账号支出 loveValueRecordDao.insert(LoveValueRecordEntity.builder() .userId(userId) .capitalFlow(2) .transactionType(3) .memberId(memberId) .loveQuantity(amount.negate()) .build()); // 写流水:成员收入 loveValueRecordDao.insert(LoveValueRecordEntity.builder() .userId(userId) .capitalFlow(1) .transactionType(3) .memberId(memberId) .loveQuantity(amount) .build()); } ``` - [ ] **Step 3: 注入 LoveValueRecordDao 和相关 import** 在 `FamilyMemberServiceImpl` 的 `@Autowired` 注入区域新增: ```java @Autowired private com.ringzle.modules.core.dao.LoveValueRecordDao loveValueRecordDao; ``` 在文件顶部 import 区域新增(若缺少): ```java import com.ringzle.modules.core.dao.LoveValueRecordDao; import com.ringzle.modules.core.entity.LoveValueRecordEntity; ``` - [ ] **Step 4: 确认 appUserService.getEntityById() 方法存在** 检查 `AppUserService` 接口是否有 `getEntityById(Long id)` 方法: ```bash grep -n "getEntityById\|getById\|selectById" /workspace/sxsn/happytree/happytree-sys/src/main/java/com/ringzle/modules/core/service/AppUserService.java | head -10 ``` 若没有 `getEntityById`,改用 `appUserService` 的父类方法。检查 `AppUserServiceImpl` 中是否有直接查 Entity 的方法: ```bash grep -n "getEntityById\|selectById\|AppUserEntity" /workspace/sxsn/happytree/happytree-sys/src/main/java/com/ringzle/modules/core/service/impl/AppUserServiceImpl.java | head -20 ``` 若无,则直接注入 `AppUserDao` 来查询 Entity: ```java @Autowired private com.ringzle.modules.core.dao.AppUserDao appUserDao; ``` 并将 `assignLoveValue` 中的 `appUserService.getEntityById(userId)` 替换为: ```java AppUserEntity user = appUserDao.selectById(userId); ``` 将 `appUserService.updateById(user)` 替换为: ```java appUserDao.updateById(user); ``` - [ ] **Step 5: Commit** ```bash cd /workspace/sxsn/happytree git add happytree-sys/src/main/java/com/ringzle/modules/core/service/impl/FamilyMemberServiceImpl.java git commit -m "feat: page() 填充 loveValue,新增 assignLoveValue 实现" ``` --- ### Task 4: FamilyMemberController 新增接口 **Files:** - Modify: `happytree-admin/src/main/java/com/ringzle/modules/core/FamilyMemberController.java` - [ ] **Step 1: 新增 assignLoveValue 接口** 在 `export` 方法之后,类的 `}` 之前插入: ```java @PostMapping("assignLoveValue") @ApiOperation("分配爱心值给家庭成员") @LogOperation("分配爱心值") public Result assignLoveValue(@RequestParam Long memberId, @RequestParam Long userId, @RequestParam java.math.BigDecimal amount) { familyMemberService.assignLoveValue(memberId, userId, amount); return new Result(); } ``` - [ ] **Step 2: 确认 import** 文件顶部已有 `import com.ringzle.common.utils.Result;` 和 `import com.ringzle.common.annotation.LogOperation;`,无需新增。 - [ ] **Step 3: Commit** ```bash cd /workspace/sxsn/happytree git add happytree-admin/src/main/java/com/ringzle/modules/core/FamilyMemberController.java git commit -m "feat: 新增 POST /core/family/member/assignLoveValue 接口" ``` --- ### Task 5: 前端 familyMember.vue — 爱心值列 + 分配按钮 + 弹窗 **Files:** - Modify: `SxsnPC/src/views/modules/member/components/familyMember.vue` - [ ] **Step 1: 新增 userLoveValue prop** 找到: ```js props: ['userId'], ``` 替换为: ```js props: ['userId', 'userLoveValue'], ``` - [ ] **Step 2: 在「所属渠道」列后新增「爱心值」列** 找到: ```html ``` 在其后插入: ```html ``` - [ ] **Step 3: 新增操作列(分配按钮)** 在「参与活动次数」列之后、`` 之前插入: ```html ``` - [ ] **Step 4: 新增分配弹窗** 在 `` 最外层结束标签之前(`