# 富维度(财富健康)实现计划 > **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:** 后端新增两张表(finance_checkins + insurance_policies)及对应 CRUD API + 能量发放;小程序端新增记账打卡页和保单管理页;调整富维度默认比例和日上限。 **Tech Stack:** Spring Boot + MyBatis-Plus / uni-app Vue 2 **设计依据:** `docs/superpowers/specs/2026-06-11-wealth-dimension-energy-design.md` --- ## 文件改动总览 | # | 文件 | 操作 | 说明 | |:-:|:-----|:----:|:------| | 1 | `entity/FinanceCheckin.java` | 新增 | 记账打卡实体 | | 2 | `entity/InsurancePolicy.java` | 新增 | 保单实体 | | 3 | `mapper/FinanceCheckinMapper.java` | 新增 | 打卡 Mapper | | 4 | `mapper/InsurancePolicyMapper.java` | 新增 | 保单 Mapper | | 5 | `service/FinanceCheckinService.java` | 新增 | 打卡业务(含能量发放) | | 6 | `service/InsurancePolicyService.java` | 新增 | 保单业务(含能量发放) | | 7 | `controller/wealth/FinanceCheckinController.java` | 新增 | 打卡 API | | 8 | `controller/wealth/InsurancePolicyController.java` | 新增 | 保单 API | | 9 | `service/EnergyService.java` | 修改 | 新增富维度能量发放方法 | | 10 | `config/DatabaseInitializer.java` | 修改 | 新增两张表 DDL | | 11 | `cfc-frontend/utils/api.js` | 修改 | 新增 API | | 12 | `cfc-frontend/pages/wealth/checkin.vue` | 新增 | 记账打卡页 | | 13 | `cfc-frontend/pages/wealth/insurance-list.vue` | 新增 | 保单列表页 | | 14 | `cfc-frontend/pages/wealth/insurance-add.vue` | 新增 | 添加保单页 | | 15 | `cfc-frontend/pages/wealth/index.vue` | 新增 | 财富健康首页 | | 16 | `cfc-frontend/pages.json` | 修改 | 注册新页面路径 | --- ### Task 1: 数据库 — 新建两张表 **Files:** - Modify: `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` - [ ] **Step 1: 在 initializeTables() 中添加建表语句** ```java // 记账打卡表 jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS finance_checkins (" + "id BIGINT AUTO_INCREMENT PRIMARY KEY, " + "user_id BIGINT NOT NULL COMMENT '用户ID', " + "child_id BIGINT DEFAULT NULL COMMENT '孩子ID(可为空)', " + "checkin_date DATE NOT NULL COMMENT '记账日期', " + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " + "UNIQUE KEY uk_user_date (user_id, IFNULL(child_id, 0), checkin_date)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='记账打卡'"); // 保单表 jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS insurance_policies (" + "id BIGINT AUTO_INCREMENT PRIMARY KEY, " + "user_id BIGINT NOT NULL COMMENT '用户ID', " + "policy_type VARCHAR(50) NOT NULL COMMENT '保单类型:医疗/重疾/意外/寿险/养老/其他', " + "insured_name VARCHAR(100) NOT NULL COMMENT '被保人姓名', " + "insured_relation VARCHAR(20) DEFAULT 'self' COMMENT '关系:self/spouse/child/parent', " + "insurance_company VARCHAR(100) DEFAULT '' COMMENT '保险公司', " + "policy_number VARCHAR(100) DEFAULT '' COMMENT '保单号', " + "coverage_amount DECIMAL(12,2) DEFAULT 0 COMMENT '保额(万元)', " + "premium DECIMAL(10,2) DEFAULT 0 COMMENT '年缴保费', " + "start_date DATE DEFAULT NULL COMMENT '生效日期', " + "end_date DATE DEFAULT NULL COMMENT '到期日期', " + "image_url VARCHAR(500) DEFAULT '' COMMENT '保单照片URL', " + "status TINYINT DEFAULT 1 COMMENT '1有效/0失效', " + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " + "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='保单记录'"); ``` - [ ] **Step 2: Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java git commit -m "feat: add finance_checkins and insurance_policies DDL" ``` --- ### Task 2: 后端实体 + Mapper **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/entity/FinanceCheckin.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/entity/InsurancePolicy.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/mapper/FinanceCheckinMapper.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/mapper/InsurancePolicyMapper.java` - [ ] **Step 1: FinanceCheckin.java** ```java package com.etotem.cfc.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; import java.util.Date; @Data @TableName("finance_checkins") public class FinanceCheckin implements Serializable { @TableId(type = IdType.AUTO) private Long id; private Long userId; private Long childId; private Date checkinDate; private Date createdAt; } ``` - [ ] **Step 2: InsurancePolicy.java** ```java package com.etotem.cfc.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; import java.math.BigDecimal; import java.util.Date; @Data @TableName("insurance_policies") public class InsurancePolicy implements Serializable { @TableId(type = IdType.AUTO) private Long id; private Long userId; private String policyType; private String insuredName; private String insuredRelation; private String insuranceCompany; private String policyNumber; private BigDecimal coverageAmount; private BigDecimal premium; private Date startDate; private Date endDate; private String imageUrl; private Integer status; private Date createdAt; private Date updatedAt; } ``` - [ ] **Step 3: 两个 Mapper** ```java @Mapper public interface FinanceCheckinMapper extends BaseMapper {} @Mapper public interface InsurancePolicyMapper extends BaseMapper {} ``` - [ ] **Step 4: Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/entity/FinanceCheckin.java cfc-backend/src/main/java/com/etotem/cfc/entity/InsurancePolicy.java cfc-backend/src/main/java/com/etotem/cfc/mapper/FinanceCheckinMapper.java cfc-backend/src/main/java/com/etotem/cfc/mapper/InsurancePolicyMapper.java git commit -m "feat: add FinanceCheckin and InsurancePolicy entities" ``` --- ### Task 3: 后端 Service + Controller **Files:** - Create: `cfc-backend/src/main/java/com/etotem/cfc/service/FinanceCheckinService.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/service/InsurancePolicyService.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/controller/wealth/FinanceCheckinController.java` - Create: `cfc-backend/src/main/java/com/etotem/cfc/controller/wealth/InsurancePolicyController.java` - [ ] **Step 1: FinanceCheckinService.java** ```java package com.etotem.cfc.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.etotem.cfc.entity.FinanceCheckin; import com.etotem.cfc.mapper.FinanceCheckinMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.time.LocalDate; import java.time.ZoneId; import java.util.Date; import java.util.List; @Slf4j @Service public class FinanceCheckinService { @Resource private FinanceCheckinMapper financeCheckinMapper; @Resource private EnergyService energyService; /** * 今日是否已记账 */ public boolean hasCheckedInToday(Long userId, Long childId) { LocalDate today = LocalDate.now(); return financeCheckinMapper.selectCount(new LambdaQueryWrapper() .eq(FinanceCheckin::getUserId, userId) .eq(childId != null, FinanceCheckin::getChildId, childId) .eq(FinanceCheckin::getCheckinDate, java.sql.Date.valueOf(today))) > 0; } /** * 记账打卡 * @return energy 本次获得的能量值 */ @Transactional public int checkin(Long userId, Long childId) { if (hasCheckedInToday(userId, childId)) { return 0; // 今天已记过 } LocalDate today = LocalDate.now(); FinanceCheckin record = new FinanceCheckin(); record.setUserId(userId); record.setChildId(childId); record.setCheckinDate(java.sql.Date.valueOf(today)); financeCheckinMapper.insert(record); // 基础能量 +3 int energy = 3; // 连续7天额外奖励 if (isStreak7Days(userId, childId)) { energy += 10; } // 调用能量服务发放富维度能量 // energyService.awardEnergy(childId != null ? childId : userId, "finance_checkin", record.getId(), // energy, childId != null ? "child" : "parent", "记账打卡"); return energy; } /** * 判断是否连续记账7天 */ private boolean isStreak7Days(Long userId, Long childId) { LocalDate today = LocalDate.now(); LocalDate sevenDaysAgo = today.minusDays(6); long count = financeCheckinMapper.selectCount(new LambdaQueryWrapper() .eq(FinanceCheckin::getUserId, userId) .eq(childId != null, FinanceCheckin::getChildId, childId) .ge(FinanceCheckin::getCheckinDate, java.sql.Date.valueOf(sevenDaysAgo)) .le(FinanceCheckin::getCheckinDate, java.sql.Date.valueOf(today))); return count >= 7; } /** * 当月打卡天数 */ public int getMonthlyCheckinCount(Long userId, Long childId) { LocalDate now = LocalDate.now(); LocalDate firstDay = now.withDayOfMonth(1); LocalDate lastDay = now.withDayOfMonth(now.lengthOfMonth()); return financeCheckinMapper.selectCount(new LambdaQueryWrapper() .eq(FinanceCheckin::getUserId, userId) .eq(childId != null, FinanceCheckin::getChildId, childId) .ge(FinanceCheckin::getCheckinDate, java.sql.Date.valueOf(firstDay)) .le(FinanceCheckin::getCheckinDate, java.sql.Date.valueOf(lastDay))); } /** * 本月打卡日期列表(用于日历展示) */ public List getMonthlyCheckinDates(Long userId, Long childId) { LocalDate now = LocalDate.now(); LocalDate firstDay = now.withDayOfMonth(1); LocalDate lastDay = now.withDayOfMonth(now.lengthOfMonth()); List records = financeCheckinMapper.selectList(new LambdaQueryWrapper() .eq(FinanceCheckin::getUserId, userId) .eq(childId != null, FinanceCheckin::getChildId, childId) .ge(FinanceCheckin::getCheckinDate, java.sql.Date.valueOf(firstDay)) .le(FinanceCheckin::getCheckinDate, java.sql.Date.valueOf(lastDay)) .orderByAsc(FinanceCheckin::getCheckinDate)); return records.stream().map(FinanceCheckin::getCheckinDate).collect(java.util.stream.Collectors.toList()); } } ``` - [ ] **Step 2: InsurancePolicyService.java** ```java package com.etotem.cfc.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.etotem.cfc.entity.InsurancePolicy; import com.etotem.cfc.mapper.InsurancePolicyMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.Date; @Slf4j @Service public class InsurancePolicyService { @Resource private InsurancePolicyMapper insurancePolicyMapper; @Resource private EnergyService energyService; public Page getList(Long userId, int page, int size) { return insurancePolicyMapper.selectPage(new Page<>(page, size), new LambdaQueryWrapper() .eq(InsurancePolicy::getUserId, userId) .orderByDesc(InsurancePolicy::getCreatedAt)); } public InsurancePolicy getDetail(Long id, Long userId) { InsurancePolicy policy = insurancePolicyMapper.selectById(id); if (policy != null && !policy.getUserId().equals(userId)) { return null; } return policy; } @Transactional public InsurancePolicy create(InsurancePolicy policy, Long userId) { policy.setUserId(userId); policy.setStatus(1); policy.setCreatedAt(new Date()); policy.setUpdatedAt(new Date()); insurancePolicyMapper.insert(policy); // 每份有效保单 +50 能量(一次性) // energyService.awardEnergy(userId, "insurance_policy", policy.getId(), 50, "parent", "上传保单"); return policy; } @Transactional public void update(InsurancePolicy policy) { policy.setUpdatedAt(new Date()); insurancePolicyMapper.updateById(policy); } @Transactional public void delete(Long id, Long userId) { insurancePolicyMapper.delete(new LambdaQueryWrapper() .eq(InsurancePolicy::getId, id) .eq(InsurancePolicy::getUserId, userId)); } /** * 统计用户保单信息 */ public int countByUser(Long userId) { return insurancePolicyMapper.selectCount(new LambdaQueryWrapper() .eq(InsurancePolicy::getUserId, userId) .eq(InsurancePolicy::getStatus, 1)); } } ``` - [ ] **Step 3: FinanceCheckinController.java** ```java package com.etotem.cfc.controller.wealth; import com.etotem.cfc.common.Result; import com.etotem.cfc.service.FinanceCheckinService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.Date; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/wealth") public class FinanceCheckinController { @Resource private FinanceCheckinService financeCheckinService; /** * 记账打卡 */ @PostMapping("/checkin") public Result checkin(@RequestBody Map body, @RequestAttribute("userId") Long userId) { Long childId = body.get("childId") != null ? Long.valueOf(body.get("childId").toString()) : null; int energy = financeCheckinService.checkin(userId, childId); return Result.success(energy); } /** * 今日是否已记账 */ @PostMapping("/checkin/today") public Result todayStatus(@RequestBody Map body, @RequestAttribute("userId") Long userId) { Long childId = body.get("childId") != null ? Long.valueOf(body.get("childId").toString()) : null; boolean done = financeCheckinService.hasCheckedInToday(userId, childId); return Result.success(done); } /** * 本月打卡数据 */ @PostMapping("/checkin/monthly") public Result> monthly(@RequestBody Map body, @RequestAttribute("userId") Long userId) { Long childId = body.get("childId") != null ? Long.valueOf(body.get("childId").toString()) : null; int count = financeCheckinService.getMonthlyCheckinCount(userId, childId); List dates = financeCheckinService.getMonthlyCheckinDates(userId, childId); return Result.success(java.util.Map.of("count", count, "dates", dates)); } } ``` - [ ] **Step 4: InsurancePolicyController.java** ```java package com.etotem.cfc.controller.wealth; import com.etotem.cfc.common.Result; import com.etotem.cfc.entity.InsurancePolicy; import com.etotem.cfc.service.InsurancePolicyService; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.Map; @RestController @RequestMapping("/api/wealth") public class InsurancePolicyController { @Resource private InsurancePolicyService insurancePolicyService; @PostMapping("/insurance/list") public Result> list(@RequestBody Map body, @RequestAttribute("userId") Long userId) { int page = body.get("page") != null ? Integer.parseInt(body.get("page").toString()) : 1; int size = body.get("size") != null ? Integer.parseInt(body.get("size").toString()) : 20; return Result.success(insurancePolicyService.getList(userId, page, size)); } @PostMapping("/insurance/detail") public Result detail(@RequestBody Map body, @RequestAttribute("userId") Long userId) { Long id = Long.valueOf(body.get("id").toString()); InsurancePolicy policy = insurancePolicyService.getDetail(id, userId); if (policy == null) return Result.error("保单不存在"); return Result.success(policy); } @PostMapping("/insurance/create") public Result create(@RequestBody InsurancePolicy policy, @RequestAttribute("userId") Long userId) { insurancePolicyService.create(policy, userId); return Result.success("添加成功"); } @PostMapping("/insurance/update") public Result update(@RequestBody InsurancePolicy policy) { insurancePolicyService.update(policy); return Result.success("更新成功"); } @PostMapping("/insurance/delete") public Result delete(@RequestBody Map body, @RequestAttribute("userId") Long userId) { Long id = Long.valueOf(body.get("id").toString()); insurancePolicyService.delete(id, userId); return Result.success("删除成功"); } @PostMapping("/insurance/count") public Result count(@RequestAttribute("userId") Long userId) { return Result.success(insurancePolicyService.countByUser(userId)); } } ``` - [ ] **Step 5: Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/FinanceCheckinService.java cfc-backend/src/main/java/com/etotem/cfc/service/InsurancePolicyService.java cfc-backend/src/main/java/com/etotem/cfc/controller/wealth/FinanceCheckinController.java cfc-backend/src/main/java/com/etotem/cfc/controller/wealth/InsurancePolicyController.java git commit -m "feat: add finance checkin and insurance policy services/controllers" ``` --- ### Task 4: 调整富维度默认比例和日上限 **Files:** - Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/EnergyService.java` - [ ] **Step 1: 调整 wealth 日上限** ```java // 原:DAILY_LIMIT_MAP.put("wealth", 30); // 改为: DAILY_LIMIT_MAP.put("wealth", 50); ``` - [ ] **Step 2: 调整默认比例(若已实现在 EnergyService 中)** 找到 `CATEGORY_DIMENSION_MAP` 或其他默认比例定义: ```java // 默认任务中 wealth 从 0.05 改为 0.10 // "default" → body:0.30, mind:0.20, wisdom:0.15, action:0.25, wealth:0.10 ``` 实际修改位置取决于当前 EnergyService 中比例常量定义的位置。 - [ ] **Step 3: Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/EnergyService.java git commit -m "chore: adjust wealth daily limit to 50 and default ratio to 10%" ``` --- ### Task 5: 小程序 — 记账打卡页面 **Files:** - Create: `cfc-frontend/pages/wealth/checkin.vue` - Modify: `cfc-frontend/utils/api.js` - Modify: `cfc-frontend/pages.json` - [ ] **Step 1: 在 api.js 添加 API** ```javascript // ===== 财富健康 ===== export const wealthCheckin = (data) => request('/api/wealth/checkin', 'POST', data) export const wealthCheckinToday = (data) => request('/api/wealth/checkin/today', 'POST', data) export const wealthCheckinMonthly = (data) => request('/api/wealth/checkin/monthly', 'POST', data) export const wealthInsuranceList = (data) => request('/api/wealth/insurance/list', 'POST', data) export const wealthInsuranceDetail = (data) => request('/api/wealth/insurance/detail', 'POST', data) export const wealthInsuranceCreate = (data) => request('/api/wealth/insurance/create', 'POST', data) export const wealthInsuranceUpdate = (data) => request('/api/wealth/insurance/update', 'POST', data) export const wealthInsuranceDelete = (data) => request('/api/wealth/insurance/delete', 'POST', data) export const wealthInsuranceCount = () => request('/api/wealth/insurance/count', 'POST') ``` - [ ] **Step 2: 创建记账打卡页面** ```vue ``` - [ ] **Step 3: 在 pages.json 注册路径** ```json { "path": "pages/wealth/checkin", "style": { "navigationBarTitleText": "记账打卡" } } ``` - [ ] **Step 4: Commit** ```bash git add cfc-frontend/utils/api.js cfc-frontend/pages/wealth/checkin.vue cfc-frontend/pages.json git commit -m "feat: add finance checkin page (mini-program)" ``` --- ### Task 6: 小程序 — 保单管理页面 **Files:** - Create: `cfc-frontend/pages/wealth/insurance-list.vue` - Create: `cfc-frontend/pages/wealth/insurance-add.vue` - [ ] **Step 1: 保单列表页** ```vue ``` - [ ] **Step 2: 添加保单页** ```vue ``` - [ ] **Step 3: 在 pages.json 注册路径** ```json { "path": "pages/wealth/insurance-list", "style": { "navigationBarTitleText": "我的保单" } }, { "path": "pages/wealth/insurance-add", "style": { "navigationBarTitleText": "添加保单" } } ``` - [ ] **Step 4: Commit** ```bash git add cfc-frontend/pages/wealth/insurance-list.vue cfc-frontend/pages/wealth/insurance-add.vue cfc-frontend/pages.json git commit -m "feat: add insurance policy management pages (mini-program)" ``` --- ## 验证清单 - [ ] **记账打卡** - 首次点击打卡 → +3 能量,当天不可重复打卡 - 连续7天 → 额外 +10 能量 - 当月日历正确显示打卡日 - [ ] **保单管理** - 添加保单(填写类型/被保人/保额/上传照片) → +50 能量 - 同一保单号不可重复添加 - 列表/删除正常 - [ ] **富维度比例** - 默认任务 wealth 比例 10% - 财商类任务 wealth 比例 90% - 日上限 50 - [ ] **后端编译**: `mvn clean compile` 无错误 - [ ] **LSP 诊断**: 所有文件无错误