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 |
Files:
Modify: happytree-sys/src/main/java/com/ringzle/modules/core/dto/FamilyMemberDTO.java
[ ] Step 1: 在 channelIds 字段之前插入 loveValue 字段
找到文件中 channelIds 字段:
@ApiModelProperty(value = "可用渠道ID,逗号分隔")
private String channelIds;
在其前面插入:
@ApiModelProperty(value = "爱心值")
private BigDecimal loveValue;
同时确认文件顶部已有 import java.math.BigDecimal;(已有则跳过)。
cd /workspace/sxsn/happytree
git add happytree-sys/src/main/java/com/ringzle/modules/core/dto/FamilyMemberDTO.java
git commit -m "feat: FamilyMemberDTO 新增 loveValue 字段"
Files:
Modify: happytree-sys/src/main/java/com/ringzle/modules/core/service/FamilyMemberService.java
[ ] Step 1: 在接口末尾新增方法声明
找到文件末尾 getMemberChannels 方法声明后,在 } 之前插入:
/**
* 管理员从总账号分配爱心值给家庭成员
* @param memberId 家庭成员 ID
* @param userId 总账号用户 ID
* @param amount 分配数量,必须 > 0 且 <= 总账号余额
*/
void assignLoveValue(Long memberId, Long userId, BigDecimal amount);
同时在文件顶部 import 区域添加(若缺少):
import java.math.BigDecimal;
cd /workspace/sxsn/happytree
git add happytree-sys/src/main/java/com/ringzle/modules/core/service/FamilyMemberService.java
git commit -m "feat: FamilyMemberService 新增 assignLoveValue 接口"
Files:
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, loveQuantitytransactionType=3 为本次新增类型(管理员分配)capitalFlow=1 收入,capitalFlow=2 支出找到 page() 方法中已有的 entities 批量查询和 channelNames 填充逻辑,在 list.forEach 填充 channelNames 的同时,追加填充 loveValue。
当前 page() 方法中有:
List<FamilyMemberEntity> entities = baseDao.selectBatchIds(
list.stream().map(FamilyMemberDTO::getId).collect(Collectors.toList())
);
在该查询之后,建立 memberId -> loveValue 的 Map:
Map<Long, BigDecimal> memberLoveValueMap = entities.stream()
.collect(Collectors.toMap(FamilyMemberEntity::getId, e ->
e.getLoveValue() != null ? e.getLoveValue() : BigDecimal.ZERO));
然后在 list.forEach 中,在 dto.setChannelNames(names) 之后追加:
dto.setLoveValue(memberLoveValueMap.getOrDefault(dto.getId(), BigDecimal.ZERO));
读取当前 page() 方法完整内容,按上述说明修改。完整修改后的 page() 方法应如下:
@Override
public PageData<FamilyMemberDTO> page(Map<String, Object> params) {
PageData<FamilyMemberDTO> pageData = super.page(params);
List<FamilyMemberDTO> list = pageData.getList();
if (CollectionUtil.isEmpty(list)) {
return pageData;
}
List<FamilyMemberEntity> entities = baseDao.selectBatchIds(
list.stream().map(FamilyMemberDTO::getId).collect(Collectors.toList())
);
Map<Long, String> memberChannelIdsMap = list.stream()
.filter(dto -> StringUtils.isNotBlank(dto.getChannelIds()))
.collect(Collectors.toMap(FamilyMemberDTO::getId, FamilyMemberDTO::getChannelIds));
Set<Long> 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<Long, String> channelNameMap = new HashMap<>();
if (!channelIdSet.isEmpty()) {
Collection<ChannelEntity> channels = channelDao.selectBatchIds(channelIdSet);
channels.forEach(c -> channelNameMap.put(c.getId(), c.getChannelName()));
}
Map<Long, BigDecimal> 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;
}
在 getMemberChannels() 方法之后,类的 } 之前插入:
@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());
}
在 FamilyMemberServiceImpl 的 @Autowired 注入区域新增:
@Autowired
private com.ringzle.modules.core.dao.LoveValueRecordDao loveValueRecordDao;
在文件顶部 import 区域新增(若缺少):
import com.ringzle.modules.core.dao.LoveValueRecordDao;
import com.ringzle.modules.core.entity.LoveValueRecordEntity;
检查 AppUserService 接口是否有 getEntityById(Long id) 方法:
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 的方法:
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:
@Autowired
private com.ringzle.modules.core.dao.AppUserDao appUserDao;
并将 assignLoveValue 中的 appUserService.getEntityById(userId) 替换为:
AppUserEntity user = appUserDao.selectById(userId);
将 appUserService.updateById(user) 替换为:
appUserDao.updateById(user);
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 实现"
Files:
Modify: happytree-admin/src/main/java/com/ringzle/modules/core/FamilyMemberController.java
[ ] Step 1: 新增 assignLoveValue 接口
在 export 方法之后,类的 } 之前插入:
@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();
}
文件顶部已有 import com.ringzle.common.utils.Result; 和 import com.ringzle.common.annotation.LogOperation;,无需新增。
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 接口"
Files:
Modify: SxsnPC/src/views/modules/member/components/familyMember.vue
[ ] Step 1: 新增 userLoveValue prop
找到:
props: ['userId'],
替换为:
props: ['userId', 'userLoveValue'],
找到:
<el-table-column label="所属渠道">
<template slot-scope="scope">{{ scope.row.channelNames || '-' }}</template>
</el-table-column>
在其后插入:
<el-table-column label="爱心值" width="100">
<template slot-scope="scope">{{ scope.row.loveValue || 0 }}</template>
</el-table-column>
在「参与活动次数」列之后、</el-table> 之前插入:
<el-table-column label="操作" width="80">
<template slot-scope="scope">
<el-button type="text" @click="handleAssign(scope.row)">分配</el-button>
</template>
</el-table-column>
在 </div> 最外层结束标签之前(<style> 之前)插入弹窗:
<el-dialog width="400px" :visible.sync="assignShow" title="分配爱心值" @close="cancelAssign">
<el-form label-width="120px" style="width: 90%; margin: 0 auto;">
<el-form-item label="成员姓名">
<span>{{ assignForm.memberName }}</span>
</el-form-item>
<el-form-item label="总账号余额">
<span>{{ userLoveValue || 0 }}</span>
</el-form-item>
<el-form-item label="分配数量">
<el-input-number
v-model="assignForm.amount"
:min="1"
:max="userLoveValue || 0"
:precision="0"
style="width: 100%;"
></el-input-number>
</el-form-item>
</el-form>
<div style="display: flex; justify-content: flex-end; margin-top: 20px; padding-right: 5%;">
<el-button :loading="assignLoading" type="primary" @click="submitAssign">确 认</el-button>
<el-button @click="cancelAssign">取 消</el-button>
</div>
</el-dialog>
找到 data() 的 return 对象,在 loading: false 后新增:
assignShow: false,
assignLoading: false,
assignForm: {
memberId: null,
memberName: '',
amount: 1
},
在 pageCurrentChangeHandle 方法之后新增:
handleAssign (row) {
this.assignForm.memberId = row.id
this.assignForm.memberName = row.name
this.assignForm.amount = 1
this.assignShow = true
},
submitAssign () {
if (!this.assignForm.amount || this.assignForm.amount <= 0) {
return this.$message.error('分配数量必须大于0')
}
this.assignLoading = true
this.$http.post('/core/family/member/assignLoveValue', null, {
params: {
memberId: this.assignForm.memberId,
userId: this.userId,
amount: this.assignForm.amount
}
}).then(res => {
if (res.data.code !== 0) {
this.$message.error(res.data.msg)
} else {
this.$message.success('分配成功')
this.cancelAssign()
this.getList()
}
}).catch(() => {
this.$message.error('操作失败,请重试')
}).finally(() => {
this.assignLoading = false
})
},
cancelAssign () {
this.assignShow = false
this.assignForm = { memberId: null, memberName: '', amount: 1 }
}
cd /workspace/sxsn/SxsnPC
git add src/views/modules/member/components/familyMember.vue
git commit -m "feat: 家庭成员列表新增爱心值列和分配功能"
Files:
Modify: SxsnPC/src/views/modules/member/detail.vue
[ ] Step 1: 找到 family-member 组件调用处
找到(约第 100 行):
<family-member v-else-if="tabIndex===3" :userId="userId"></family-member>
替换为:
<family-member v-else-if="tabIndex===3" :userId="userId" :userLoveValue="assetInfo ? assetInfo.loveValue : 0"></family-member>
打开管理端会员详情页,切换到「家庭成员」tab:
输入数量后点击确认,列表刷新,成员爱心值增加,总账号余额减少
[ ] Step 3: Commit
cd /workspace/sxsn/SxsnPC
git add src/views/modules/member/detail.vue
git commit -m "feat: detail.vue 向家庭成员组件传入总账号爱心值余额"
amount.compareTo(BigDecimal.ZERO) <= 0 校验user.getLoveValue().compareTo(amount) < 0 校验subtractaddloveValueRecordDao.insertamount.negate()getList()appUserService.getEntityById 可能不存在的情况