2026-05-24-parent-homepage-system-points.md 26 KB

家长首页数据接入 & 系统积分方案实现计划

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: 在现有积分体系上增加 category 分类标识区分系统积分与任务积分;测评完成时自动发放系统积分;家长首页对接真实 API 数据替代模拟数据。

Tech Stack: Spring Boot 2.7.18 + MyBatis-Plus + uni-app Vue 2


数据模型变更

children 表: + system_points INT DEFAULT 0   -- 系统积分余额
points_log 表: + category VARCHAR(20)         -- 'task' / 'system' 分类标识

API 变更

新增 POST /api/parent/dashboard          -- 家长首页统计数据
新增 POST /api/guide/record/create        -- 改造: 录入测评结果后自动发放系统积分
新增 POST /api/guide/record/complete      -- 字段增: 增加completeRecord方法使之额外发放系统积分

改造 POST /api/user/children/list         -- ChildInfoDTO 增加 systemPoints
改造 POST /api/user/family-members        -- 修复URL (前端) 或增加后端映射
改造 POST /api/guide/record/create        -- 录入结果后发放系统积分

Task 1: 数据库 Schema 变更

Files:

  • Modify: cfc-backend/src/main/resources/schema.sql
  • Modify: cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
  • Modify: cfc-backend/src/main/java/com/etotem/cfc/entity/PointsLog.java
  • Modify: cfc-backend/src/main/java/com/etotem/cfc/entity/Child.java

  • [ ] Step 1: schema.sql 添加字段

children 表添加 system_points 字段,在 points_log 表添加 category 字段:

-- schema.sql children 表: 在 total_points 后添加
system_points INT DEFAULT 0 COMMENT '系统积分(测评建档/平台发放)',

-- schema.sql points_log 表: 在 type 后添加
category VARCHAR(20) DEFAULT 'task' COMMENT '积分分类: task-任务积分, system-系统积分',
  • [ ] Step 2: DatabaseInitializer 添加迁移

    // DatabaseInitializer.java runMigrations() 末尾添加
    
    // 迁移20: children表添加system_points字段
    try {
    jdbcTemplate.execute("ALTER TABLE children ADD COLUMN system_points INT DEFAULT 0 COMMENT '系统积分'");
    log.info("已添加system_points列到children表");
    } catch (Exception e) {
    log.warn("添加system_points列可能已存在: {}", e.getMessage());
    }
    
    // 迁移21: points_log表添加category字段
    try {
    jdbcTemplate.execute("ALTER TABLE points_log ADD COLUMN category VARCHAR(20) DEFAULT 'task' COMMENT '积分分类: task任务积分, system系统积分'");
    log.info("已添加category列到points_log表");
    } catch (Exception e) {
    log.warn("添加category列可能已存在: {}", e.getMessage());
    }
    
  • [ ] Step 3: Child 实体添加 systemPoints

    // Child.java
    /** 系统积分(测评建档/平台发放) */
    private Integer systemPoints;
    
  • [ ] Step 4: PointsLog 实体添加 category

    // PointsLog.java
    /** 积分分类: task-任务积分, system-系统积分 */
    private String category;
    
  • [ ] Step 5: 编译验证

Run: cd cfc-backend && mvn clean compile Expected: BUILD SUCCESS


Task 2: PointsService 添加系统积分方法

Files:

  • Modify: cfc-backend/src/main/java/com/etotem/cfc/service/PointsService.java
  • Modify: cfc-backend/src/main/java/com/etotem/cfc/service/api/PointsServiceInterface.java
  • Modify: cfc-backend/src/main/java/com/etotem/cfc/mapper/PointsLogMapper.java (一般不需要改)

  • [ ] Step 1: PointsService 添加 awardSystemPoints 方法

    // PointsService.java
    
    /**
    * 发放系统积分
    * @param childId 孩子ID
    * @param amount 积分数量(正数)
    * @param reason 发放原因
    * @return 发放后的系统积分余额
    */
    @Transactional
    public int awardSystemPoints(Long childId, int amount, String reason) {
    if (amount <= 0) {
        throw new IllegalArgumentException("系统积分数量必须为正数");
    }
        
    Child child = childMapper.selectById(childId);
    if (child == null) {
        throw new RuntimeException("孩子不存在");
    }
        
    // 更新系统积分
    int newSystemPoints = (child.getSystemPoints() == null ? 0 : child.getSystemPoints()) + amount;
    child.setSystemPoints(newSystemPoints);
    child.setUpdatedAt(new Date());
    childMapper.updateById(child);
        
    // 同时增加总积分
    int newTotal = (child.getTotalPoints() == null ? 0 : child.getTotalPoints()) + amount;
    child.setTotalPoints(newTotal);
    child.setUpdatedAt(new Date());
    childMapper.updateById(child);
        
    // 记录流水
    PointsLog log = new PointsLog();
    log.setChildId(childId);
    log.setAmount(amount);
    log.setType("earn");
    log.setCategory("system");
    log.setDescription(reason);
    log.setCreatedAt(new Date());
    pointsLogMapper.insert(log);
        
    return newSystemPoints;
    }
    
    /**
    * 扣除系统积分
    * @param childId 孩子ID
    * @param amount 扣除数量(正数)
    * @param reason 扣除原因
    * @return 扣除后的系统积分余额,余额不足返回-1
    */
    @Transactional
    public int deductSystemPoints(Long childId, int amount, String reason) {
    if (amount <= 0) {
        throw new IllegalArgumentException("扣除数量必须为正数");
    }
        
    Child child = childMapper.selectById(childId);
    if (child == null) {
        throw new RuntimeException("孩子不存在");
    }
        
    int currentSystem = child.getSystemPoints() == null ? 0 : child.getSystemPoints();
    if (currentSystem < amount) {
        return -1; // 余额不足
    }
        
    int newSystemPoints = currentSystem - amount;
    child.setSystemPoints(newSystemPoints);
    child.setUpdatedAt(new Date());
    childMapper.updateById(child);
        
    // 同时扣除总积分
    int newTotal = (child.getTotalPoints() == null ? 0 : child.getTotalPoints()) - amount;
    child.setTotalPoints(newTotal);
    child.setUpdatedAt(new Date());
    childMapper.updateById(child);
        
    // 记录流水
    PointsLog log = new PointsLog();
    log.setChildId(childId);
    log.setAmount(-amount);
    log.setType("spend");
    log.setCategory("system");
    log.setDescription(reason);
    log.setCreatedAt(new Date());
    pointsLogMapper.insert(log);
        
    return newSystemPoints;
    }
    
    /**
    * 获取孩子可用的系统积分余额
    */
    public int getSystemPointsBalance(Long childId) {
    Child child = childMapper.selectById(childId);
    if (child == null) {
        return 0;
    }
    return child.getSystemPoints() == null ? 0 : child.getSystemPoints();
    }
    
    /**
    * 获取积分流水(支持按分类筛选)
    */
    public Page<PointsLog> getPointsLogsByCategory(Long childId, String category, Integer page, Integer size) {
    Page<PointsLog> pageParam = new Page<>(page, size);
    LambdaQueryWrapper<PointsLog> wrapper = new LambdaQueryWrapper<PointsLog>()
            .eq(PointsLog::getChildId, childId);
    if (category != null && !category.isEmpty()) {
        wrapper.eq(PointsLog::getCategory, category);
    }
    wrapper.orderByDesc(PointsLog::getCreatedAt);
    return pointsLogMapper.selectPage(pageParam, wrapper);
    }
    
  • [ ] Step 2: PointsServiceInterface 添加接口方法(如果存在)

    // PointsServiceInterface.java
    int awardSystemPoints(Long childId, int amount, String reason);
    int deductSystemPoints(Long childId, int amount, String reason);
    int getSystemPointsBalance(Long childId);
    Page<PointsLog> getPointsLogsByCategory(Long childId, String category, Integer page, Integer size);
    
  • [ ] Step 3: 编译验证

Run: cd cfc-backend && mvn clean compile Expected: BUILD SUCCESS


Task 3: 测评完成自动发放系统积分

Files:

  • Modify: cfc-backend/src/main/java/com/etotem/cfc/controller/guide/GuideRecordController.java
  • Modify: cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentService.java

  • [ ] Step 1: GuideRecordController.createRecord 中发放系统积分

    // GuideRecordController.java
    
    @Resource
    private PointsService pointsService;
    
    // 在 createRecord 方法末尾,return 之前增加系统积分发放
    @Operation(summary = "录入测评结果(含孩子快照)")
    @PostMapping("/create")
    public Result<DanAssessmentResult> createRecord(
        @RequestAttribute("userId") Long userId,
        @RequestAttribute("role") String role,
        @RequestBody DanAssessmentResult result) {
    if (!"teacher".equals(role)) {
        return Result.error("只有成长规划师可以录入测评结果");
    }
    result.setTeacherId(userId);
    result.setStatus("completed");
    result.setCreatedAt(new Date());
    result.setUpdatedAt(new Date());
    if (result.getAssessmentDate() == null) {
        result.setAssessmentDate(new Date());
    }
    DanAssessmentResult saved = assessmentService.recordResult(result);
        
    // 发放系统积分:首次建档奖励(从配置表读取积分值,默认50)
    if (saved != null && saved.getChildId() != null) {
        try {
            AssessmentPointsConfig config = assessmentService.getPointsConfig();
            int points = (config != null && config.getPointsValue() != null) 
                ? config.getPointsValue() : 50;
            pointsService.awardSystemPoints(saved.getChildId(), points, 
                "完成测评建档奖励");
        } catch (Exception e) {
            log.error("发放系统积分失败", e);
            // 不阻断主流程
        }
    }
        
    return Result.success(saved);
    }
    

需要在 GuideRecordController 中添加 Logger:

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GuideRecordController.class);
  • Step 2: AssessmentService 添加 getPointsConfig 返回类型修复

getPointsConfig() 已经是 public,可以直接从 Controller 调用,不需要修改。

  • Step 3: 编译验证

Run: cd cfc-backend && mvn clean compile Expected: BUILD SUCCESS


Task 4: ChildInfoDTO 增加 systemPoints

Files:

  • Modify: cfc-backend/src/main/java/com/etotem/cfc/dto/ChildInfoDTO.java
  • Modify: cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java

  • [ ] Step 1: ChildInfoDTO 添加 systemPoints

    // ChildInfoDTO.java
    private Integer systemPoints;
    
  • [ ] Step 2: toChildInfoDTO 映射 systemPoints

    // UserService.java toChildInfoDTO 方法中
    dto.setSystemPoints(child.getSystemPoints());
    
  • [ ] Step 3: 编译验证

Run: cd cfc-backend && mvn clean compile Expected: BUILD SUCCESS


Task 5: 家长首页统计数据 API

Files:

  • Create: cfc-backend/src/main/java/com/etotem/cfc/controller/parent/ParentDashboardController.java
  • Modify: cfc-backend/src/main/java/com/etotem/cfc/service/TaskStatsService.java (如需要)

  • [ ] Step 1: 创建 ParentDashboardController

    package com.etotem.cfc.controller.parent;
    
    import com.etotem.cfc.common.Result;
    import com.etotem.cfc.dto.ChildInfoDTO;
    import com.etotem.cfc.service.PointsService;
    import com.etotem.cfc.service.TaskService;
    import com.etotem.cfc.service.UserService;
    import io.swagger.v3.oas.annotations.Operation;
    import io.swagger.v3.oas.annotations.tags.Tag;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import java.util.*;
    
    @Tag(name = "家长首页", description = "家长首页数据接口")
    @RestController
    @RequestMapping("/api/parent")
    public class ParentDashboardController {
    
    @Resource
    private UserService userService;
    
    @Resource
    private TaskService taskService;
    
    @Resource
    private PointsService pointsService;
    
    @Operation(summary = "获取家长首页仪表盘数据")
    @PostMapping("/dashboard")
    public Result<Map<String, Object>> getDashboard(HttpServletRequest request) {
        Long userId = (Long) request.getAttribute("userId");
            
        // 获取孩子列表(复用现有接口)
        List<ChildInfoDTO> children = userService.getChildren(userId);
            
        List<Map<String, Object>> childStatsList = new ArrayList<>();
        int totalCompletionRate = 0;
        int totalHabitCount = 0;
        int childCount = children.size();
            
        for (ChildInfoDTO child : children) {
            Map<String, Object> stats = new HashMap<>();
            stats.put("id", child.getId());
            stats.put("nickname", child.getNickname());
            stats.put("totalPoints", child.getTotalPoints());
            stats.put("systemPoints", child.getSystemPoints());
            stats.put("streakDays", child.getStreakDays());
            stats.put("danLevel", child.getDanLevel());
            stats.put("age", child.getAge());
                
            // 计算任务完成率(今日/本周)
            // 这里简化处理:从TaskService获取统计数据
            Map<String, Object> taskStats = taskService.getChildTaskStats(child.getId());
            stats.put("completionRate", taskStats.getOrDefault("completionRate", 0));
            stats.put("pendingReviewCount", taskStats.getOrDefault("pendingReviewCount", 0));
                
            // 习惯打卡数量(今日已完成的任务中标记为习惯的数量)
            stats.put("habitCount", taskStats.getOrDefault("habitCount", 0));
                
            totalCompletionRate += (Integer) taskStats.getOrDefault("completionRate", 0);
            totalHabitCount += (Integer) taskStats.getOrDefault("habitCount", 0);
                
            childStatsList.add(stats);
        }
            
        Map<String, Object> result = new HashMap<>();
        result.put("children", childStatsList);
        result.put("avgCompletionRate", childCount > 0 ? totalCompletionRate / childCount : 0);
        result.put("totalHabitCount", totalHabitCount);
            
        return Result.success(result);
    }
    }
    
    import com.etotem.cfc.mapper.TaskMapper;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.etotem.cfc.entity.Task;
    
  • [ ] Step 2: TaskService 添加 getChildTaskStats 方法

    // TaskService.java
    
    @Resource
    private TaskMapper taskMapper;
    
    /**
    * 获取孩子的任务统计数据
    */
    public Map<String, Object> getChildTaskStats(Long childId) {
    Map<String, Object> stats = new HashMap<>();
        
    Date now = new Date();
        
    // 今日任务统计
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.set(Calendar.MINUTE, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    Date todayStart = cal.getTime();
        
    cal.set(Calendar.HOUR_OF_DAY, 23);
    cal.set(Calendar.MINUTE, 59);
    cal.set(Calendar.SECOND, 59);
    Date todayEnd = cal.getTime();
        
    // 获取孩子所有任务
    List<Task> allTasks = taskMapper.selectList(
        new LambdaQueryWrapper<Task>()
            .eq(Task::getChildId, childId)
            .between(Task::getDeadline, todayStart, todayEnd)
    );
        
    long completed = allTasks.stream()
        .filter(t -> "completed".equals(t.getStatus()))
        .count();
        
    int completionRate = allTasks.isEmpty() ? 0 : (int) (completed * 100 / allTasks.size());
        
    // 待审核任务
    List<Task> pendingReview = taskMapper.selectList(
        new LambdaQueryWrapper<Task>()
            .eq(Task::getChildId, childId)
            .eq(Task::getNeedReview, 1)
            .eq(Task::getStatus, "pending")
    );
        
    // 习惯打卡数量(统计今日完成的habit类任务)
    long habitCount = allTasks.stream()
        .filter(t -> "completed".equals(t.getStatus()))
        .filter(t -> "habit".equals(t.getCategory()))
        .count();
        
    stats.put("completionRate", completionRate);
    stats.put("pendingReviewCount", pendingReview.size());
    stats.put("habitCount", (int) habitCount);
        
    return stats;
    }
    

需要在 TaskService.java 的 import 区域添加:

import java.util.HashMap;
import java.util.Map;
  • Step 3: 编译验证

Run: cd cfc-backend && mvn clean compile Expected: BUILD SUCCESS


Task 6: 修复 getFamilyMembers() URL 并增加系统积分字段

Files:

  • Modify: cfc-frontend/utils/api.js
  • Modify: cfc-backend/src/main/java/com/etotem/cfc/controller/UserController.java

  • [ ] Step 1: 修复前端 API URL

    // api.js 第141行
    // 改前:
    export const getFamilyMembers = () => {
    return request('/api/user/family-members', 'POST')
    }
    // 改后:
    export const getFamilyMembers = () => {
    return request('/api/family/user/family-members', 'POST')
    }
    
  • [ ] Step 2: 后端 UserController 增加 /family-members 端点作为额外入口(可选,保持兼容)

如果不想改前端 URL,也可以在 UserController 中增加 /family-members 端点:

// UserController.java

@Resource
private FamilyMapper familyMapper;

@Resource
private ChildMapper childMapper;

@PostMapping("/family-members")
@Operation(summary = "获取家庭成员列表(兼容)")
public Result<Map<String, Object>> getFamilyMembers(HttpServletRequest request) {
    Long userId = (Long) request.getAttribute("userId");
    User user = userMapper.selectById(userId);
    if (user == null || user.getFamilyId() == null) {
        return Result.error("用户未加入家庭");
    }
    
    List<User> parents = userMapper.selectList(
        new LambdaQueryWrapper<User>()
            .eq(User::getFamilyId, user.getFamilyId())
            .eq(User::getRole, "parent")
    );
    
    List<Child> children = childMapper.selectList(
        new LambdaQueryWrapper<Child>()
            .eq(Child::getFamilyId, user.getFamilyId())
    );
    
    Map<String, Object> result = new HashMap<>();
    result.put("parents", parents);
    result.put("children", children);
    return Result.success(result);
}
  • Step 3: 编译验证

Run: cd cfc-backend && mvn clean compile Expected: BUILD SUCCESS


Task 7: 家长首页对接真实数据

Files:

  • Modify: cfc-frontend/pages/index/parent-index.vue

  • [ ] Step 1: 添加 dashboard API 调用

    // parent-index.vue script 顶部 import 区域添加
    import { getFamilyMembers, getChildren, getPendingReviewTasks, approveTask, rejectTask, getPendingWishes } from '../../utils/api.js'
    
  • [ ] Step 2: 新 API 封装

    // api.js 添加
    export const getParentDashboard = () => {
    return request('/api/parent/dashboard', 'POST')
    }
    
  • [ ] Step 3: 改造 loadData 方法

    // parent-index.vue loadData 方法改造
    
    async loadData() {
    try {
        const userInfo = uni.getStorageSync('userInfo')
        this.nickname = (userInfo && userInfo.nickname) ? userInfo.nickname : ''
    
        // 获取仪表盘数据(包含孩子列表 + 统计数据)
        try {
            const dashRes = await getParentDashboard()
            const dashData = dashRes.data || {}
            const dashChildren = dashData.children || []
                
            if (dashChildren.length > 0) {
                this.children = dashChildren
                this.selectedChild = dashChildren[0]
                this.taskCompletionRate = dashData.avgCompletionRate || 85
                this.habitCount = dashData.totalHabitCount || 0
            }
        } catch(e) {
            console.log('获取仪表盘数据失败,降级到单独接口', e)
            // 降级:使用原有逻辑
            try {
                const membersRes = await getFamilyMembers()
                // ... 原有 getFamilyMembers 逻辑
            } catch(e2) {
                console.log('获取家庭成员失败', e2)
            }
                
            const childrenRes = await getChildren()
            const rawChildren = childrenRes.data || []
            this.children = rawChildren
            if (this.children.length > 0 && !this.selectedChild) {
                this.selectedChild = this.children[0]
            }
        }
    
        // 获取待审核任务(保持不变)
        const reviewRes = await getPendingReviewTasks()
        const allReviewTasks = reviewRes.data || []
        this.pendingReviewTasks = allReviewTasks.slice(0, 3)
    
        // 获取待处理心愿(保持不变)
        try {
            const wishRes = await getPendingWishes()
            this.pendingReviewWishes = wishRes.data || []
        } catch (e) {
            console.log('获取待处理心愿失败', e)
            this.pendingReviewWishes = []
        }
    
        // 计算待处理数量(保持不变)
        const reviewMap = {}
        const wishMap = {}
        for (const task of allReviewTasks) {
            const cid = task.childId || task.assigneeId
            if (cid) {
                reviewMap[cid] = (reviewMap[cid] || 0) + 1
            }
        }
        for (const wish of (this.pendingReviewWishes || [])) {
            const cid = wish.childId
            if (cid) {
                wishMap[cid] = (wishMap[cid] || 0) + 1
            }
        }
        this.children = this.children.map(c => ({
            ...c,
            pendingReviewCount: reviewMap[c.id] || 0,
            pendingWishCount: wishMap[c.id] || 0
        }))
    
        // 移除模拟数据,改用真实数据
        // this.pendingTasks = [ ... ]  // 删除模拟数据
            
        // 如果有待审核任务或心愿,不显示模拟任务
        if (allReviewTasks.length > 0) {
            this.pendingTasks = allReviewTasks.slice(0, 3).map(t => ({
                id: t.id,
                title: t.title,
                childName: t.childName
            }))
        } else {
            this.pendingTasks = []
        }
            
        // 不再使用随机值
        // this.taskCompletionRate = Math.floor(Math.random() * 30) + 70  // 删除这行
    } catch (e) {
        console.error('加载数据失败', e)
    }
    },
    
  • [ ] Step 4: 子卡展示系统积分(可选增强)

    <!-- parent-index.vue 子卡中增加系统积分展示 -->
    <text class="child-card-system-points" v-if="child.systemPoints > 0">
    🏅 系统 {{ child.systemPoints }}
    </text>
    

在 style 区域添加:

.child-card-system-points {
  font-size: 20rpx;
  color: #667eea;
  font-weight: 500;
  margin-left: 8rpx;
}

Task 8: PointsController 增加系统积分相关接口

Files:

  • Modify: cfc-backend/src/main/java/com/etotem/cfc/controller/family/PointsController.java

  • [ ] Step 1: 添加系统积分余额接口

    // PointsController.java
    
    @PostMapping("/system-balance")
    public Result<Map<String, Object>> getSystemBalance(@RequestBody Map<String, Object> params) {
    Long childId = params.get("childId") != null ? Long.valueOf(params.get("childId").toString()) : null;
    if (childId == null) {
        return Result.error("childId不能为空");
    }
    int balance = pointsService.getSystemPointsBalance(childId);
    Map<String, Object> result = new HashMap<>();
    result.put("systemPoints", balance);
    return Result.success(result);
    }
    
    @PostMapping("/logs-by-category")
    public Result<Page<PointsLog>> getPointsLogsByCategory(@RequestBody Map<String, Object> params) {
    Long childId = params.get("childId") != null ? Long.valueOf(params.get("childId").toString()) : null;
    String category = params.get("category") != null ? params.get("category").toString() : null;
    Integer page = params.get("page") != null ? Integer.valueOf(params.get("page").toString()) : 1;
    Integer size = params.get("size") != null ? Integer.valueOf(params.get("size").toString()) : 10;
    Page<PointsLog> logs = pointsService.getPointsLogsByCategory(childId, category, page, size);
    return Result.success(logs);
    }
    
  • [ ] Step 2: 编译验证

Run: cd cfc-backend && mvn clean compile Expected: BUILD SUCCESS


Task 9: 系统积分前端展示

Files:

  • Modify: cfc-frontend/pages/rewards/rewards.vue(积分展示区域)
  • Modify: cfc-frontend/pages/profile/profile.vue(个人中心展示)

  • [ ] Step 1: 封装前端 API

    // api.js 添加
    export const getSystemBalance = (childId) => {
    return request('/api/points/system-balance', 'POST', { childId })
    }
    
  • [ ] Step 2: 积分展示页面调整

在 rewards.vue 或 profile.vue 的积分区域展示系统积分:

// profile.vue 或 rewards.vue 的 onShow/loadData 中
// 获取系统积分余额
try {
    if (this.children && this.children.length > 0) {
        const sysRes = await getSystemBalance(this.children[0].id)
        this.systemPoints = sysRes.data.systemPoints || 0
    }
} catch (e) {
    console.log('获取系统积分失败', e)
}

Task 10: 编译 & 启动验证

  • Step 1: 后端编译

Run: cd cfc-backend && mvn clean compile Expected: BUILD SUCCESS

  • [ ] Step 2: 检查路由冲突

    grep -rn '@GetMapping\|@PostMapping\|@PutMapping\|@DeleteMapping' src/main/java/com/etotem/cfc/controller/ | grep -oP '@\w+Mapping\("\K[^"]*' | sort -u | grep -E 'dashboard|system-balance|logs-by-category|family-members'
    

Expected: 无冲突路由

  • Step 3: 启动后端

Run: cd cfc-backend && mvn spring-boot:run Expected: Started Application in X seconds


自检清单

1. 需求覆盖:

  • ✅ 家长首页对接真实数据 (Task 5, 7)
  • ✅ 系统积分分类标识 (Task 1)
  • ✅ 测评建档发放系统积分 (Task 3)
  • ✅ 系统积分可用于复测/兑换 (Task 2, 8, 9)
  • ✅ 修复 getFamilyMembers() URL (Task 6)

2. 占位符检查:

  • 所有代码段已填充完整实现,无 TBD/TODO 模式
  • 所有步骤包含具体代码或命令

3. 类型一致性:

  • Child.systemPoints 类型一致 (Integer)
  • PointsLog.category 类型一致 (String)
  • PointsService.awardSystemPoints 返回 int
  • getChildTaskStats 返回 Map<String, Object>