日期: 2026-06-09 状态: 待实施 分支: cfclub 工作量预估: 3~4周
| 文件 | 说明 |
|---|---|
EnergyController.java |
/api/energy/sandbox — 返回家庭能量沙盘(百分比评分) |
EnergyService.java |
基于任务完成率 + 游戏得分 + 测评结果 + 阅读时长 计算 0-100% 评分 |
EnergyDimensionConfig.java |
单表 energy_dimension_config(dimension/data_source/category/weight)⚠️ 将被废弃 |
EnergyDimensionConfigMapper.java |
旧表 Mapper ⚠️ 将被废弃 |
EnergySandboxDTO.java |
家庭聚合 + 成员列表 DTO |
MemberEnergyDTO.java |
成员五维评分DTO |
api.js |
getFamilyEnergySandbox() → /api/energy/sandbox |
energy_dimension_config 是单表维度权重配置,与新 4 表设计不兼容旧版评分系统 = 健康指数(healthIndex)的前身,新账本系统 = 能量值(energy)。
两个概念在 API 响应中并存:energy(账本余额)+ healthIndex(百分比,暂时返回 0)。
实施原则:
EnergyService 的评分计算方法EnergyService 中新增账本方法(award/deduct/overview/logs)/sandbox,新增 /overview /logs /configureenergy_dimension_config 暂不删除,新 4 表独立创建文件:
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java在 initSchema() 末尾(log.info("数据库迁移完成") 之前)添加。
以下 SQL 可直接复制。注意:旧表
energy_dimension_config不在此处创建(已在数据库中存在)。
[ ] 1.1 DDL: 创建 4 张能量表
// ==================== 五维能量系统(账本模式) ====================
// 维度定义表
jdbcTemplate.execute(
"CREATE TABLE IF NOT EXISTS energy_dimension (" +
"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
"code VARCHAR(16) NOT NULL UNIQUE COMMENT 'body/mind/wisdom/action/wealth', " +
"name VARCHAR(20) NOT NULL COMMENT '身/心/智/行/富', " +
"icon VARCHAR(16) COMMENT '图标emoji', " +
"element VARCHAR(10) COMMENT '五行: 土/火/金/木/水', " +
"sort_order INT DEFAULT 0 COMMENT '显示顺序', " +
"status TINYINT DEFAULT 1 COMMENT '1启用/0禁用', " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量维度定义表'"
);
// 服务-维度比例配置表
jdbcTemplate.execute(
"CREATE TABLE IF NOT EXISTS energy_source_config (" +
"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
"source_type VARCHAR(32) NOT NULL COMMENT '来源类型: task/activity/course/product/consultation', " +
"source_id BIGINT NOT NULL COMMENT '来源业务表ID', " +
"source_name VARCHAR(200) COMMENT '冗余名称', " +
"dimension_id BIGINT NOT NULL COMMENT '维度ID', " +
"ratio DECIMAL(5,4) NOT NULL COMMENT '占比如0.6000=60%', " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
"INDEX idx_source (source_type, source_id)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务-维度比例配置表'"
);
// 能量流水表
jdbcTemplate.execute(
"CREATE TABLE IF NOT EXISTS energy_log (" +
"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
"child_id BIGINT NOT NULL COMMENT '孩子ID', " +
"dimension_id BIGINT NOT NULL COMMENT '维度ID', " +
"amount INT NOT NULL COMMENT '变动量(正=获得,负=消耗)', " +
"balance_after INT NOT NULL COMMENT '变动后余额', " +
"source_type VARCHAR(32) COMMENT '来源类型: task/product/medical_report/daily_record', " +
"source_id BIGINT COMMENT '来源ID', " +
"ref_id BIGINT COMMENT '关联旧流水ID(覆盖时标记)', " +
"expires_at DATETIME COMMENT '过期时间(空=永不过期)', " +
"description VARCHAR(500) COMMENT '描述', " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
"INDEX idx_child_dim (child_id, dimension_id), " +
"INDEX idx_created (created_at), " +
"INDEX idx_source (source_type, source_id)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量流水表'"
);
// 维度余额表
jdbcTemplate.execute(
"CREATE TABLE IF NOT EXISTS energy_balance (" +
"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
"child_id BIGINT NOT NULL COMMENT '孩子ID', " +
"dimension_id BIGINT NOT NULL COMMENT '维度ID', " +
"balance INT DEFAULT 0 COMMENT '当前能量值(可为负)', " +
"total_earned INT DEFAULT 0 COMMENT '累计获得', " +
"total_spent INT DEFAULT 0 COMMENT '累计消耗', " +
"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
"UNIQUE KEY uk_child_dim (child_id, dimension_id)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维度余额表'"
);
[ ] 1.2 种子数据: 维度定义
// 维度种子数据(按相生链: 智→富→行→心→身)
Integer dimCount = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM energy_dimension", Integer.class);
if (dimCount == null || dimCount == 0) {
jdbcTemplate.execute("INSERT INTO energy_dimension (code, name, icon, element, sort_order, status) VALUES " +
"('mind', '心', '🔥', '火', 4, 1), " +
"('body', '身', '🌏', '土', 5, 1), " +
"('wisdom', '智', '⚔️', '金', 1, 1), " +
"('action', '行', '🌿', '木', 3, 1), " +
"('wealth', '富', '💧', '水', 2, 1)");
}
[ ] 1.3 种子数据: 已有商品映射
// 为已有商品插入默认比例配置(与Product.domain一致)
Integer productConfigCount = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM energy_source_config WHERE source_type = 'product'", Integer.class);
if (productConfigCount == null || productConfigCount == 0) {
String[][] domainMapping = {
{"action", "action"}, {"mind", "mind"}, {"body", "body"},
{"wisdom", "wisdom"}, {"wealth", "wealth"}
};
for (String[] mapping : domainMapping) {
jdbcTemplate.execute(
"INSERT INTO energy_source_config (source_type, source_id, source_name, dimension_id, ratio) " +
"SELECT 'product', p.id, p.name, d.id, 1.0000 " +
"FROM products p " +
"JOIN energy_dimension d ON d.code = '" + mapping[1] + "' " +
"WHERE p.domain = '" + mapping[0] + "' " +
"AND NOT EXISTS (" +
" SELECT 1 FROM energy_source_config esc " +
" WHERE esc.source_type = 'product' AND esc.source_id = p.id" +
")"
);
}
}
[ ] 1.4 验证: cd cfc-backend && mvn clean compile -q
新建:
entity/EnergyDimension.javaentity/EnergySourceConfig.javaentity/EnergyLog.javaentity/EnergyBalance.java遵循现有模式:@Data @TableName @TableId(type = IdType.AUTO) implements Serializable
注意: 新增
EnergyLog.expiresAt和EnergyLog.refId字段(与总设计 spec 相比新增,用于支持过期和覆盖机制)。
[ ] 2.1 创建 EnergyDimension.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("energy_dimension")
public class EnergyDimension implements Serializable {
@TableId(type = IdType.AUTO)
private Long id;
private String code; // body/mind/wisdom/action/wealth
private String name; // 身/心/智/行/富
private String icon; // emoji
private String element; // 土/火/金/木/水
private Integer sortOrder;
private Integer status; // 1启用/0禁用
private Date createdAt;
}
[ ] 2.2 创建 EnergySourceConfig.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("energy_source_config")
public class EnergySourceConfig implements Serializable {
@TableId(type = IdType.AUTO)
private Long id;
private String sourceType; // task/activity/course/product/consultation
private Long sourceId;
private String sourceName;
private Long dimensionId;
private BigDecimal ratio; // 0.6000 = 60%
private Date createdAt;
}
[ ] 2.3 创建 EnergyLog.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("energy_log")
public class EnergyLog implements Serializable {
@TableId(type = IdType.AUTO)
private Long id;
private Long childId;
private Long dimensionId;
private Integer amount;
private Integer balanceAfter;
private String sourceType; // task/product/medical_report/daily_record
private Long sourceId;
private Long refId; // 关联旧流水(覆盖时标记)
private Date expiresAt; // 过期时间(空=永不过期)
private String description;
private Date createdAt;
}
[ ] 2.4 创建 EnergyBalance.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("energy_balance")
public class EnergyBalance implements Serializable {
@TableId(type = IdType.AUTO)
private Long id;
private Long childId;
private Long dimensionId;
private Integer balance;
private Integer totalEarned;
private Integer totalSpent;
private Date updatedAt;
}
[ ] 2.5 验证: cd cfc-backend && mvn clean compile -q
新建:
mapper/EnergyDimensionMapper.javamapper/EnergySourceConfigMapper.javamapper/EnergyLogMapper.javamapper/EnergyBalanceMapper.java遵循现有模式:@Mapper interface XxxMapper extends BaseMapper<Xxx>,无自定义方法。
cd cfc-backend && mvn clean compile -q文件:
cfc-backend/src/main/java/com/etotem/cfc/service/EnergyService.java在现有EnergyService类中新增以下账本方法,保留旧方法不动。
[ ] 4.1 新增注入 + 维度缓存
// ==== 新增: 账本注入 ====
@Resource
private EnergyDimensionMapper energyDimensionMapper;
@Resource
private EnergySourceConfigMapper energySourceConfigMapper;
@Resource
private EnergyLogMapper energyLogMapper;
@Resource
private EnergyBalanceMapper energyBalanceMapper;
// 维度缓存(懒加载,维度数据极少变更)
private List<EnergyDimension> dimCache;
private Map<String, EnergyDimension> dimCodeMap;
[ ] 4.2 新增: awardEnergy() — 发放能量,自动查比例配置分配各维度
/**
* 发放能量 — 按 energy_source_config 比例分配至各维度
* @param childId 孩子ID
* @param sourceType 来源类型 (task/product/medical_report/etc)
* @param sourceId 来源ID
* @param totalAmount 总能量值
* @param description 描述
* @param daysToExpire 过期天数(null=不过期)
* @return Map<dimensionCode, amount>
*/
@Transactional
public Map<String, Integer> awardEnergy(Long childId, String sourceType, Long sourceId,
Integer totalAmount, String description,
Integer daysToExpire) {
if (totalAmount == null || totalAmount <= 0) return new HashMap<>();
// 1. 查比例配置 → Fallback链
List<EnergySourceConfig> configs = energySourceConfigMapper.selectList(
new LambdaQueryWrapper<EnergySourceConfig>()
.eq(EnergySourceConfig::getSourceType, sourceType)
.eq(EnergySourceConfig::getSourceId, sourceId)
);
Map<Long, BigDecimal> dimRatios = resolveDimensionRatios(configs, sourceType, sourceId);
if (dimRatios.isEmpty()) return new HashMap<>();
// 2. 日上限检查
if (isDailyLimitExceeded(childId, dimRatios.keySet(), totalAmount)) {
log.warn("维度日上限已达,跳过发放");
return new HashMap<>();
}
// 3. 按比例分配(整数,余数加到最大比例维度)
Map<Long, Integer> allocations = calculateAllocations(totalAmount, dimRatios);
// 4. 更新余额 + 写流水
Map<String, Integer> result = new LinkedHashMap<>();
Date now = new Date();
Date expiresAt = daysToExpire != null
? new Date(now.getTime() + (long) daysToExpire * 86400000L) : null;
for (Map.Entry<Long, Integer> entry : allocations.entrySet()) {
if (entry.getValue() <= 0) continue;
EnergyBalance balance = getOrCreateBalance(childId, entry.getKey());
balance.setBalance(balance.getBalance() + entry.getValue());
balance.setTotalEarned(balance.getTotalEarned() + entry.getValue());
balance.setUpdatedAt(now);
energyBalanceMapper.updateById(balance);
EnergyLog log = new EnergyLog();
log.setChildId(childId);
log.setDimensionId(entry.getKey());
log.setAmount(entry.getValue());
log.setBalanceAfter(balance.getBalance());
log.setSourceType(sourceType);
log.setSourceId(sourceId);
log.setExpiresAt(expiresAt);
log.setDescription(description);
log.setCreatedAt(now);
energyLogMapper.insert(log);
EnergyDimension dim = getDimById(entry.getKey());
if (dim != null) result.put(dim.getCode(), entry.getValue());
}
return result;
}
[ ] 4.3 新增: deductEnergy() — 扣除能量(余额不足返回 -1)
/**
* 扣除能量
* @return 扣除后余额,-1 = 余额不足
*/
@Transactional
public int deductEnergy(Long childId, Long dimensionId, Integer amount, String reason) {
if (amount == null || amount <= 0)
throw new IllegalArgumentException("扣除数量必须为正数");
EnergyBalance balance = getOrCreateBalance(childId, dimensionId);
if (balance.getBalance() < amount) return -1;
balance.setBalance(balance.getBalance() - amount);
balance.setTotalSpent(balance.getTotalSpent() + amount);
balance.setUpdatedAt(new Date());
energyBalanceMapper.updateById(balance);
EnergyLog log = new EnergyLog();
log.setChildId(childId);
log.setDimensionId(dimensionId);
log.setAmount(-amount);
log.setBalanceAfter(balance.getBalance());
log.setDescription(reason);
log.setCreatedAt(new Date());
energyLogMapper.insert(log);
return balance.getBalance();
}
[ ] 4.4 新增: getOverview() — 查询五维概览
/**
* 查询五维能量概览
*/
public Map<String, Object> getOverview(Long childId) {
List<EnergyDimension> allDims = getAllDimensions();
List<Map<String, Object>> dimList = new ArrayList<>();
int totalEnergy = 0;
for (EnergyDimension dim : allDims) {
if (dim.getStatus() != 1) continue;
EnergyBalance balance = energyBalanceMapper.selectOne(
new LambdaQueryWrapper<EnergyBalance>()
.eq(EnergyBalance::getChildId, childId)
.eq(EnergyBalance::getDimensionId, dim.getId())
);
int energy = balance != null ? balance.getBalance() : 0;
totalEnergy += energy;
Map<String, Object> item = new LinkedHashMap<>();
item.put("code", dim.getCode());
item.put("name", dim.getName());
item.put("icon", dim.getIcon());
item.put("element", dim.getElement());
item.put("energy", energy);
item.put("healthIndex", 0); // 算法待定
dimList.add(item);
}
Map<String, Object> result = new LinkedHashMap<>();
result.put("dimensions", dimList);
result.put("totalEnergy", totalEnergy);
result.put("totalHealthIndex", 0);
return result;
}
[ ] 4.5 新增: getLogs() — 流水查询(按维度筛选,分页)
public Page<EnergyLog> getLogs(Long childId, String dimensionCode, Integer page, Integer size) {
Page<EnergyLog> pageParam = new Page<>(page, size);
LambdaQueryWrapper<EnergyLog> wrapper = new LambdaQueryWrapper<EnergyLog>()
.eq(EnergyLog::getChildId, childId);
if (dimensionCode != null && !dimensionCode.isEmpty()) {
EnergyDimension dim = getDimByCode(dimensionCode);
if (dim != null) wrapper.eq(EnergyLog::getDimensionId, dim.getId());
}
wrapper.orderByDesc(EnergyLog::getCreatedAt);
return energyLogMapper.selectPage(pageParam, wrapper);
}
[ ] 4.6 新增: configureSourceRatios() — 配置来源比例
@Transactional
public void configureSourceRatios(String sourceType, Long sourceId, String sourceName,
List<Map<String, Object>> ratios) {
energySourceConfigMapper.delete(
new LambdaQueryWrapper<EnergySourceConfig>()
.eq(EnergySourceConfig::getSourceType, sourceType)
.eq(EnergySourceConfig::getSourceId, sourceId)
);
for (Map<String, Object> r : ratios) {
String dimCode = (String) r.get("dimensionCode");
BigDecimal ratioVal = new BigDecimal(r.get("ratio").toString());
EnergyDimension dim = getDimByCode(dimCode);
if (dim == null) continue;
EnergySourceConfig config = new EnergySourceConfig();
config.setSourceType(sourceType);
config.setSourceId(sourceId);
config.setSourceName(sourceName);
config.setDimensionId(dim.getId());
config.setRatio(ratioVal);
config.setCreatedAt(new Date());
energySourceConfigMapper.insert(config);
}
}
[ ] 4.7 新增: 内部辅助方法
// ===== 辅助方法 =====
private Map<Long, BigDecimal> resolveDimensionRatios(
List<EnergySourceConfig> configs, String sourceType, Long sourceId) {
if (!configs.isEmpty()) {
Map<Long, BigDecimal> result = new LinkedHashMap<>();
for (EnergySourceConfig c : configs) result.put(c.getDimensionId(), c.getRatio());
return result;
}
// Fallback 1: Product.domain
if ("product".equals(sourceType)) {
// 查 Product.domain → 单维度 100%
try { ... } catch (Exception ignored) {}
}
// Fallback 2: 默认 → 行(action) 100%
EnergyDimension action = getDimByCode("action");
if (action != null) return Collections.singletonMap(action.getId(), BigDecimal.ONE);
return Collections.emptyMap();
}
private Map<Long, Integer> calculateAllocations(Integer total, Map<Long, BigDecimal> ratios) { ... }
private EnergyBalance getOrCreateBalance(Long childId, Long dimId) { ... }
private boolean isDailyLimitExceeded(Long childId, Set<Long> dimIds, Integer amount) { ... }
private List<EnergyDimension> getAllDimensions() { ... }
private EnergyDimension getDimByCode(String code) { ... }
private EnergyDimension getDimById(Long id) { ... }
isDailyLimitExceeded逻辑:energy_log查 childId + dimensionId + 今日 created_at 的 amount 总和 + 本次发放量 > 该维度日上限 → true 同时校验五维总和日上限
默认日上限(从 remaining-dimensions 设计):
| 维度 | 日上限 |
|---|---|
| 身 | 50 |
| 心 | 40 |
| 智 | 80 |
| 行 | 60 |
| 富 | 30 |
| ⚠️ 全局 | 200 |
cd cfc-backend && mvn clean compile -q文件:
cfc-backend/src/main/java/com/etotem/cfc/controller/EnergyController.java保留现有 /api/energy/sandbox 端点,新增 3 个端点。
注意:现有 controller 在
controller/下(不在/energy子包),保持该目录。
[ ] 5.1 新增 3 个端点
@PostMapping("/overview")
public Result<Map<String, Object>> getOverview(@RequestBody Map<String, Object> params) { ... }
@PostMapping("/logs")
public Result<Page<EnergyLog>> getLogs(@RequestBody Map<String, Object> params) { ... }
@PostMapping("/configure")
public Result<String> configure(@RequestBody Map<String, Object> params,
@RequestAttribute("userId") Long userId) { ... }
路径映射确认(避免冲突):当前
@RequestMapping("/api/energy")下有/sandbox、/overview、/logs、/configure,无重复。
cd cfc-backend && mvn clean compile -q文件:
cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java在 completeTask() 中,积分发放之后、Map<String, Object> result 之前,添加能量发放调用。
[ ] 6.1 注入 EnergyService
@Resource
private EnergyService energyService;
[ ] 6.2 在 completeTask() 末尾添加
// 五维能量发放(与积分并行,独立系统,失败不影响积分)
try {
int energyAmount = Math.abs(pointsEarned);
if (energyAmount > 0) {
// 查 task.category 的过期天数配置,目前任务不过期
energyService.awardEnergy(childId, "task", taskId, energyAmount,
"完成任务: " + task.getTitle(), null);
}
} catch (Exception e) {
log.warn("能量发放失败(不影响积分): childId={}, taskId={}, error={}",
childId, taskId, e.getMessage());
}
[ ] 6.3 验证: cd cfc-backend && mvn clean compile -q
文件:
cfc-frontend/utils/api.js在末尾新增 3 个 API 调用。
[ ] 7.1 在 api.js 末尾添加
// ==== 五维能量系统 ====
export const getEnergyOverview = (childId) => {
return request('/api/energy/overview', 'POST', { childId })
}
export const getEnergyLogs = (childId, dimensionCode, page = 1, size = 10) => {
return request('/api/energy/logs', 'POST', { childId, dimensionCode, page, size })
}
已有
getFamilyEnergySandbox(旧版/api/energy/sandbox),保留不动。
文件:
cfc-frontend/components/wuxing-sandbox.vue组件改造为接收真实数据 props,实现三层视觉结构(外层维度角、中层健康指数扇区、内层中心圆)。
当前组件已有 props: mode, badgeText。
新增 props: dimensions, totalEnergy, totalHealthIndex。
props: {
mode: { type: String, default: 'preview' },
badgeText: { type: String, default: '综合成长力 0%' },
dimensions: { type: Array, default: () => [] },
totalEnergy: { type: Number, default: 0 },
totalHealthIndex: { type: Number, default: 0 }
},
computed: {
sortedDimensions: function () {
// 按顺序: 心/行/富/智/身 → mind/action/wealth/wisdom/body
var order = ['mind', 'action', 'wealth', 'wisdom', 'body'];
var dimMap = {}
for (var i = 0; i < this.dimensions.length; i++) {
dimMap[this.dimensions[i].code] = this.dimensions[i]
}
var result = []
for (var j = 0; j < order.length; j++) {
result.push(dimMap[order[j]] || { code: order[j], name: '', icon: '', element: '', energy: 0, healthIndex: 0 })
}
return result
}
}
⚠️ Vue 2 限制(不使用 Composition API): 禁止可选链
?.,用&&短路或用|| {}兜底
[ ] 8.2 template 改造
<!-- 心·火 (顶) → sortedDimensions[0] -->
<view class="star-point star-point-top" @click="onPointClick(0)">
<text class="point-icon">{{ sortedDimensions[0].icon }}</text>
<text class="point-label">{{ sortedDimensions[0].name }}·{{ sortedDimensions[0].element }}</text>
<text class="point-energy">{{ sortedDimensions[0].energy }}</text>
</view>
<!-- 行·木 (左), 富·水 (右), 智·金 (左下), 身·土 (右下) → index 1-4 -->
[ ] 8.3 验证: cd cfc-frontend && npm run dev:mp-weixin(检查编译)
文件:
cfc-frontend/pages/index/parent-index.vuecfc-frontend/pages/index/child-index.vue在 onShow 中调用 getEnergyOverview,将数据传入 wuxing-sandbox。
[ ] 9.1 parent-index.vue 修改
import { getEnergyOverview } from '../../utils/api.js'
// data 新增:
energyDimensions: [],
totalEnergy: 0,
totalHealthIndex: 0,
// methods 新增:
loadEnergyOverview: function () {
var that = this
var childId = that.currentChildId
if (!childId) return
getEnergyOverview(childId).then(function (res) {
if (res.data) {
that.energyDimensions = res.data.dimensions || []
that.totalEnergy = res.data.totalEnergy || 0
that.totalHealthIndex = res.data.totalHealthIndex || 0
}
}).catch(function () { /* 静默失败 */ })
},
// onShow 末尾调用:
this.loadEnergyOverview()
template 中的 wuxing-sandbox:
<wuxing-sandbox mode="parent"
:badgeText="'综合成长力 ' + totalHealthIndex + '%'"
:dimensions="energyDimensions"
:totalEnergy="totalEnergy"
:totalHealthIndex="totalHealthIndex"
@point-click="goToDomain" />
(同上模式,使用 that.childId 代替 currentChildId)
文件:
cfc-frontend/pages/energy/detail.vueModify: cfc-frontend/pages.json
[ ] 10.1 创建 detail.vue(维度详情页:头部显示维度信息 + 流水列表 + 加载更多)
[ ] 10.2 pages.json 注册
{
"path": "pages/energy/detail",
"style": {
"navigationBarTitleText": "维度能量详情"
}
}
[ ] 10.3 从 wuxing-sandbox 点击跳转
wuxing-sandbox 的 @point-click 事件在父页面处理:
goToDomain: function (code) {
var childId = this.currentChildId || this.childId
var dim = this.sortedDimensions ? this.sortedDimensions.find(function (d) { return d.code === code }) : null
if (!childId || !dim) return
uni.navigateTo({
url: '/pages/energy/detail?childId=' + childId + '&code=' + code
+ '&dimData=' + encodeURIComponent(JSON.stringify(dim))
})
}
以下任务在核心框架上线后,按业务优先级分步实施。
BodyHealthMetricController(或扩展现有)中接入 awardEnergy在 TaskService.completeTask() 中,若 energy_source_config 无配置,按 task.category 找默认比例:
| category | body | mind | wisdom | action | wealth |
|---|---|---|---|---|---|
| 运动类 | 60% | — | — | 40% | — |
| 学习类 | — | — | 100% | — | — |
| 生活习惯 | 40% | 30% | — | 30% | — |
| 家务类 | — | 10% | — | 90% | — |
| 阅读类 | — | 20% | 80% | — | — |
| 财商类 | — | — | 10% | — | 90% |
| 默认 | 30% | 20% | 20% | 25% | 5% |
CATEGORY_DIMENSION_MAP定义在EnergyService中,TaskService调用时自动 fallback
ScheduledEnergyExpiryTaskSELECT * FROM energy_log WHERE expires_at IS NOT NULL AND expires_at < NOW() AND ref_id IS NULLref_id)energy_dimension_config 表不再被任何功能引用EnergyDimensionConfig.java / EnergyDimensionConfigMapper.java / EnergySandboxDTO.javaMemberEnergyDTO.java 保留(仍是健康指数的基础)| Task | 内容 | 文件数 | 预估工时 | 依赖 |
|---|---|---|---|---|
| 1 | 数据库迁移 | 1 | 2h | — |
| 2 | 4个Entity | 4 | 1h | T1 |
| 3 | 4个Mapper | 4 | 0.5h | T2 |
| 4 | EnergyService账本方法 | 1 | 3h | T3 |
| 5 | EnergyController端点 | 1 | 1h | T4 |
| 6 | TaskService集成 | 1 | 1h | T4 |
| 7 | 前端API封装 | 1 | 0.5h | T5 |
| 8 | wuxing-sandbox重写 | 1 | 2h | T7 |
| 9 | 首页集成 | 2 | 1h | T7-T8 |
| 10 | 能量详情页 | 2 | 2h | T7 |
| Phase 1 | 基础框架上线 | 18 | ≈2周 | — |
| 11 | 身体维度集成 | 2+ | 2d | T4 |
| 12 | category映射 | 1 | 1d | T6 |
| 13 | 过期定时任务 | 1 | 1d | T4 |
| 14 | 旧代码清理 | 3 | 0.5d | T4-T10 |
| Phase 2 | 全功能上线 | — | ≈1周 | — |
mvn clean compile 通过POST /api/energy/overview 返回五维能量值POST /api/energy/logs 返回流水(分页)energy_log 产生记录GET /api/energy/sandbox 旧接口不破坏(向后兼容)docs/superpowers/specs/2026-06-05-five-dimension-energy-design.md — 五维能量系统总设计(4表方案)docs/superpowers/specs/2026-06-09-body-dimension-energy-design.md — 身维度计算规则docs/superpowers/specs/2026-06-09-remaining-dimensions-energy-design.md — 心/智/行/富四维 + category映射docs/superpowers/plans/2026-06-09-energy-system-implementation-plan.md — 本文件