# 五维家庭自检 · 15 天复检周期 + 首页分数 + 题目轮换 实现计划 > **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。 **目标:** 为五维家庭自检增加 15 天复检周期、首页显示上次自检分数、中间页二选一(再次自检/忽略)、以及合并题库随机抽题轮换。 **架构:** 后端新增 `self_check_ignores` 表 + 两个新接口(`/status` + `/ignore`);改造现有 `/questions`(合并题库抽题)和 `/submit`(按提交题号计分 + 落库题号);前端新增中间页 `self-check-entry.vue`,首页入口接入 `/status` 并跳转中间页。 **技术栈:** Java 8 / Spring Boot 2.7.18 / MyBatis-Plus / uni-app Vue 2 Options API / MySQL 8.0 **规格文档:** `docs/superpowers/specs/2026-08-31-self-check-reminder-design.md` --- ## 文件结构 | 文件 | 职责 | 变更类型 | |------|------|---------| | `cfc-backend/src/main/java/com/etotem/cfc/entity/SelfCheckIgnore.java` | 忽略记录实体 | 新建 | | `cfc-backend/src/main/java/com/etotem/cfc/mapper/SelfCheckIgnoreMapper.java` | 忽略记录 Mapper | 新建 | | `cfc-backend/src/main/java/com/etotem/cfc/entity/FiveDimensionSelfCheck.java` | 自检记录实体(新增 `questionIdsJson`) | 修改 | | `cfc-backend/src/main/java/com/etotem/cfc/service/FiveDimensionSelfCheckService.java` | 抽题/状态/忽略/计分改造 | 修改 | | `cfc-backend/src/main/java/com/etotem/cfc/controller/family/FiveDimensionSelfCheckController.java` | 新增 `/status` + `/ignore` | 修改 | | `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` | 迁移 269/270 | 修改 | | `cfc-backend/src/main/resources/schema.sql` | 表定义同步 | 修改 | | `cfc-frontend/pages/family/self-check-entry.vue` | 中间页(结果摘要 + 再次自检/忽略) | 新建 | | `cfc-frontend/pages/index-home/index.vue` | 首页入口显示自检总分 | 修改 | | `cfc-frontend/pages/home-pages/parent-index.vue` | 家长首页入口显示自检总分 | 修改 | | `cfc-frontend/pages/family/self-check-result.vue` | 「重新自检」改跳中间页 | 修改 | | `cfc-frontend/pages.json` | 注册中间页 | 修改 | | `cfc-frontend/utils/api.js` | 新增 `getSelfCheckStatus` / `ignoreSelfCheck` | 修改 | --- ## 任务 1:后端 — 数据库迁移(表 + 列) **文件:** - 新建:`cfc-backend/src/main/java/com/etotem/cfc/entity/SelfCheckIgnore.java` - 新建:`cfc-backend/src/main/java/com/etotem/cfc/mapper/SelfCheckIgnoreMapper.java` - 修改:`cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java:9574`(`migrateCouponStatus` 方法后追加迁移 269/270) - 修改:`cfc-backend/src/main/resources/schema.sql`(`five_dimension_self_checks` 表 + 追加 `self_check_ignores` 表) - [ ] **步骤 1:创建 SelfCheckIgnore 实体** 创建文件 `cfc-backend/src/main/java/com/etotem/cfc/entity/SelfCheckIgnore.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("self_check_ignores") public class SelfCheckIgnore implements Serializable { @TableId(type = IdType.AUTO) private Long id; private Long userId; private Long checkId; private Date createdAt; } ``` - [ ] **步骤 2:创建 SelfCheckIgnoreMapper** 创建文件 `cfc-backend/src/main/java/com/etotem/cfc/mapper/SelfCheckIgnoreMapper.java`: ```java package com.etotem.cfc.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.etotem.cfc.entity.SelfCheckIgnore; import org.apache.ibatis.annotations.Mapper; @Mapper public interface SelfCheckIgnoreMapper extends BaseMapper { } ``` - [ ] **步骤 3:在 DatabaseInitializer 追加迁移 269/270** 在 `DatabaseInitializer.java` 中,于 `migrateCouponStatus()` 方法结束后、`migrateSysMenuReorg()` 方法开始前,插入: ```java // 迁移269: five_dimension_self_checks 表添加 question_ids_json 列(自检轮换用) ensureColumn("five_dimension_self_checks", "question_ids_json", "VARCHAR(255) COMMENT '本次自检使用的题号JSON: [1,5,9,...]'"); // 迁移270: 创建 self_check_ignores 表(记录用户忽略自检提醒的动作,作为 15 天周期重置依据) try { jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS self_check_ignores (" + "id BIGINT AUTO_INCREMENT PRIMARY KEY, " + "user_id BIGINT NOT NULL COMMENT '用户ID', " + "check_id BIGINT COMMENT '忽略的自检记录ID(可空)', " + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '忽略时间', " + "INDEX idx_user_id (user_id)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维自检忽略提醒记录'"); log.info("已创建 self_check_ignores 表"); } catch (Exception e) { log.warn("创建 self_check_ignores 表失败: {}", e.getMessage()); } ``` `ensureColumn` 方法已在 DatabaseInitializer 第 4099 行定义,直接复用。 - [ ] **步骤 4:同步 schema.sql** 在 `cfc-backend/src/main/resources/schema.sql` 中找到 `five_dimension_self_checks` 建表语句(约第 4285 行),在 `created_at DATETIME DEFAULT CURRENT_TIMESTAMP` 之后追加: ```sql question_ids_json VARCHAR(255) DEFAULT NULL COMMENT '本次自检使用的题号JSON: [1,5,9,...]', ``` 在 schema.sql 末尾追加 `self_check_ignores` 建表语句: ```sql -- ============================================================ -- 五维自检忽略提醒记录表(迁移270) -- ============================================================ CREATE TABLE IF NOT EXISTS self_check_ignores ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL COMMENT '用户ID', check_id BIGINT COMMENT '忽略的自检记录ID(可空)', created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '忽略时间', INDEX idx_user_id (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维自检忽略提醒记录'; ``` - [ ] **步骤 5:编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:BUILD SUCCESS - [ ] **步骤 6:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/entity/SelfCheckIgnore.java \ cfc-backend/src/main/java/com/etotem/cfc/mapper/SelfCheckIgnoreMapper.java \ cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java \ cfc-backend/src/main/resources/schema.sql git commit -m "feat(self-check): 新增 self_check_ignores 表和 question_ids_json 列(迁移269/270)" ``` --- ## 任务 2:后端 — Entity 与 Service 改造 **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/entity/FiveDimensionSelfCheck.java`(新增字段) - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/FiveDimensionSelfCheckService.java`(抽题/状态/忽略/计分改造) - [ ] **步骤 1:FiveDimensionSelfCheck 实体新增字段** 在 `FiveDimensionSelfCheck.java` 的 `private Date createdAt;` 之前追加: ```java /** 本次自检使用的题号JSON,如 [1,2,3,101] */ private String questionIdsJson; ``` - [ ] **步骤 2:Service 新增私有字段 — 合并题库(34题)** 在 `FiveDimensionSelfCheckService.java` 中,于 `SUB_DIMENSION_QUESTION_BANK` 字段之后,追加: ```java /** 合并题库:主题库(15题) + 子维度附加库(19题),用于轮换抽题 */ private static final List FULL_QUESTION_POOL = buildFullPool(); @SuppressWarnings("unchecked") private static List buildFullPool() { List pool = new ArrayList<>(QUESTION_BANK); pool.addAll(SUB_DIMENSION_QUESTION_BANK); return pool; } /** 按 dimension 分组的合并题库 Map> */ private Map> groupByDimension(List pool) { Map> groups = new LinkedHashMap<>(); for (SelfCheckQuestionVO q : pool) { String dim = q.getDimension(); if (dim == null) continue; groups.computeIfAbsent(dim, k -> new ArrayList<>()).add(q); } return groups; } /** 随机抽题:从 pool 中抽 count 题,优先排除 excludeIds */ private List pickQuestions(List pool, List excludeIds, int count) { List excludeSet = new ArrayList<>(excludeIds != null ? excludeIds : Collections.emptyList()); List candidates = new ArrayList<>(); for (SelfCheckQuestionVO q : pool) { if (!excludeSet.contains(q.getId())) { candidates.add(q); } } // 洗牌 Collections.shuffle(candidates); List result = new ArrayList<>(); int need = Math.min(count, candidates.size()); for (int i = 0; i < need; i++) { result.add(candidates.get(i)); } // 不足时从 excludeSet 补足(保持顺序) if (result.size() < count) { for (SelfCheckQuestionVO q : pool) { if (result.size() >= count) break; if (!excludeSet.contains(q.getId())) continue; boolean alreadyIn = false; for (SelfCheckQuestionVO r : result) { if (r.getId().equals(q.getId())) { alreadyIn = true; break; } } if (!alreadyIn) result.add(q); } } return result; } ``` 同时在文件头部导入 `Collections`: ```java import java.util.Collections; ``` - [ ] **步骤 3:Service 新增 `getQuestionsForRetake` 方法** 在 `getQuestions()` 方法后追加: ```java /** * 返回轮换后的题库(用于再次自检): * 合并题库按维度分组,每维度随机抽 3 题,优先排除上次使用的题号。 * body 维度仅 3 题,若排除后不足 3 题则允许复用。 */ public List getQuestionsForRetake(Long userId) { // 查最近一次自检的题号 LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(FiveDimensionSelfCheck::getUserId, userId) .orderByDesc(FiveDimensionSelfCheck::getCreatedAt) .last("LIMIT 1"); SortUtil.applySort(wrapper); FiveDimensionSelfCheck lastRecord = selfCheckMapper.selectOne(wrapper); List lastQuestionIds = parseQuestionIds(lastRecord != null ? lastRecord.getQuestionIdsJson() : null); // 按维度分组抽题 Map> dimGroups = groupByDimension(FULL_QUESTION_POOL); String[] dimOrder = {"body", "wisdom", "wealth", "action", "mind"}; List result = new ArrayList<>(); for (String dim : dimOrder) { List pool = dimGroups.getOrDefault(dim, Collections.emptyList()); result.addAll(pickQuestions(pool, lastQuestionIds, 3)); } return result; } private List parseQuestionIds(String json) { if (json == null || json.isEmpty()) return Collections.emptyList(); try { return objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, Integer.class)); } catch (Exception e) { log.warn("解析 questionIdsJson 失败: {}", e.getMessage()); return Collections.emptyList(); } } ``` - [ ] **步骤 4:Service 改造 `submitSelfCheck` — 按提交题号计分 + 落库题号** 将 `submitSelfCheck` 方法中第 348-380 行的计分逻辑替换为: ```java // 构建 题号→题目 映射(从合并池) Map questionMap = new LinkedHashMap<>(); for (SelfCheckQuestionVO q : FULL_QUESTION_POOL) { questionMap.put(q.getId(), q); } // 题目ID → 答案 Map answerMap = new LinkedHashMap<>(); for (SubmitSelfCheckDTO.AnswerItem item : dto.getAnswers()) { if (item.getQuestionId() == null || item.getAnswer() == null || item.getAnswer().isEmpty()) { continue; } answerMap.put(item.getQuestionId(), item.getAnswer().trim().toUpperCase()); } // 按维度分组计分 Map> dimGroups = groupByDimension(FULL_QUESTION_POOL); List dimensions = new ArrayList<>(); int totalScore = 0; List submittedQuestionIds = new ArrayList<>(); for (Map.Entry entry : DIMENSION_META.entrySet()) { String dim = entry.getKey(); List dimQuestions = dimGroups.getOrDefault(dim, Collections.emptyList()); int sum = 0; int answered = 0; for (SelfCheckQuestionVO qv : dimQuestions) { String ans = answerMap.get(qv.getId()); if (ans == null || "E".equals(ans)) continue; submittedQuestionIds.add(qv.getId()); sum += scoreOf(ans); answered++; } if (answered == 0) { throw new IllegalArgumentException("维度[" + dim + "]至少回答1题"); } int dimScore = new BigDecimal(sum * 3.0 / answered).setScale(0, RoundingMode.HALF_UP).intValue(); if (dimScore > 9) dimScore = 9; totalScore += dimScore; dimensions.add(buildDimensionScoreVO(dim, dimScore)); } ``` 并在落库部分(`selfCheckMapper.insert(record)` 之前)追加: ```java record.setQuestionIdsJson(toJson(submittedQuestionIds)); ``` 同时确保 `submittedQuestionIds` 升序排列: ```java Collections.sort(submittedQuestionIds); ``` - [ ] **步骤 5:Service 新增 `getStatus` 方法** ```java /** * 返回自检状态:是否有记录、是否可检、距下次提醒天数、上次题号 */ @SuppressWarnings("unchecked") public Map getStatus(Long userId) { Map result = new HashMap<>(); // 最近一次自检 LambdaQueryWrapper checkWrapper = new LambdaQueryWrapper() .eq(FiveDimensionSelfCheck::getUserId, userId) .orderByDesc(FiveDimensionSelfCheck::getCreatedAt) .last("LIMIT 1"); SortUtil.applySort(checkWrapper); FiveDimensionSelfCheck lastCheck = selfCheckMapper.selectOne(checkWrapper); result.put("hasCheck", lastCheck != null); result.put("lastResult", lastCheck != null ? toVO(lastCheck) : null); result.put("lastQuestionIds", parseQuestionIds(lastCheck != null ? lastCheck.getQuestionIdsJson() : null)); // 最近一次忽略 LambdaQueryWrapper ignoreWrapper = new LambdaQueryWrapper() .eq(SelfCheckIgnore::getUserId, userId) .orderByDesc(SelfCheckIgnore::getCreatedAt) .last("LIMIT 1"); SelfCheckIgnore lastIgnore = ignoreMapper.selectOne(ignoreWrapper); // 最近决定性时间 Date lastDecisiveTime = null; if (lastCheck != null) lastDecisiveTime = lastCheck.getCreatedAt(); if (lastIgnore != null && (lastDecisiveTime == null || lastIgnore.getCreatedAt().after(lastDecisiveTime))) { lastDecisiveTime = lastIgnore.getCreatedAt(); } if (lastDecisiveTime == null) { // 从未自检/忽略 result.put("canCheck", true); result.put("daysLeft", 0); } else { long diffDays = (System.currentTimeMillis() - lastDecisiveTime.getTime()) / (1000L * 60 * 60 * 24); boolean canCheck = diffDays >= 15; int daysLeft = canCheck ? 0 : (int) (15 - diffDays); result.put("canCheck", canCheck); result.put("daysLeft", daysLeft); } return result; } ``` 需要新增 `ignoreMapper` 注入: ```java @Resource private SelfCheckIgnoreMapper ignoreMapper; ``` - [ ] **步骤 6:Service 新增 `ignore` 方法** ```java /** * 记录忽略动作(幂等:15 天内重复忽略不插入) */ public Date ignoreSelfCheck(Long userId, Long checkId) { // 查最近一次忽略(15 天内) LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(SelfCheckIgnore::getUserId, userId) .orderByDesc(SelfCheckIgnore::getCreatedAt) .last("LIMIT 1"); SelfCheckIgnore lastIgnore = ignoreMapper.selectOne(wrapper); if (lastIgnore != null) { long diffDays = (System.currentTimeMillis() - lastIgnore.getCreatedAt().getTime()) / (1000L * 60 * 60 * 24); if (diffDays < 15) { return lastIgnore.getCreatedAt(); // 幂等,返回已有忽略时间 } } SelfCheckIgnore ignore = new SelfCheckIgnore(); ignore.setUserId(userId); ignore.setCheckId(checkId); ignore.setCreatedAt(new Date()); ignoreMapper.insert(ignore); return ignore.getCreatedAt(); } ``` - [ ] **步骤 7:编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:BUILD SUCCESS - [ ] **步骤 8:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/entity/FiveDimensionSelfCheck.java \ cfc-backend/src/main/java/com/etotem/cfc/service/FiveDimensionSelfCheckService.java git commit -m "feat(self-check): Service 层抽题/状态/忽略/计分改造" ``` --- ## 任务 3:后端 — Controller 新增接口 **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/controller/family/FiveDimensionSelfCheckController.java` - [ ] **步骤 1:注入 ignoreMapper** 在 `FiveDimensionSelfCheckController.java` 中追加字段: ```java @Resource private com.etotem.cfc.mapper.SelfCheckIgnoreMapper selfCheckIgnoreMapper; ``` - [ ] **步骤 2:新增 `/status` 接口** 在类中追加方法: ```java @Operation(summary = "获取自检状态(是否有记录、是否可检、距下次提醒天数)") @PostMapping("/status") public Result> getStatus(@RequestAttribute("userId") Long userId) { if (userId == null) { return Result.error("请先登录"); } return Result.success(selfCheckService.getStatus(userId)); } ``` - [ ] **步骤 3:新增 `/ignore` 接口** ```java @Operation(summary = "忽略本次自检提醒(重置 15 天计时)") @PostMapping("/ignore") public Result> ignore(@RequestBody(required = false) Map body, @RequestAttribute("userId") Long userId) { if (userId == null) { return Result.error("请先登录"); } Long checkId = body != null ? ParamUtils.getLong(body.get("checkId")) : null; Date ignoredAt = selfCheckService.ignoreSelfCheck(userId, checkId); Map resp = new HashMap<>(); resp.put("nextRemindAt", new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").format( new Date(ignoredAt.getTime() + 15L * 24 * 60 * 60 * 1000))); return Result.success(resp); } ``` - [ ] **步骤 4:修改 `/questions` 接口 — 区分首次/再次** 将 `/questions` 方法改为: ```java @Operation(summary = "获取自检问卷题目(首次固定 15 题;再次合并题库随机抽)") @PostMapping("/questions") public Result> getQuestions( @RequestAttribute("userId") Long userId, @RequestBody(required = false) Map body) { if (userId == null) { return Result.error("请先登录"); } Boolean retake = body != null && body.get("retake") != null && Boolean.TRUE.equals(body.get("retake")); if (retake) { return Result.success(selfCheckService.getQuestionsForRetake(userId)); } return Result.success(selfCheckService.getQuestions()); } ``` - [ ] **步骤 5:编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:BUILD SUCCESS - [ ] **步骤 6:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/controller/family/FiveDimensionSelfCheckController.java git commit -m "feat(self-check): 新增 /status 和 /ignore 接口;/questions 支持 retake 参数" ``` --- ## 任务 4:前端 — utils/api.js 新增方法 **文件:** - 修改:`cfc-frontend/utils/api.js`(在自检相关方法后追加) - [ ] **步骤 1:追加两个 API 方法** 在 `cfc-frontend/utils/api.js` 中,于 `getSelfCheckHistory` 方法之后追加: ```js export const getSelfCheckStatus = () => { return request('/api/family/self-check/status', 'POST', {}) } export const ignoreSelfCheck = (data) => { return request('/api/family/self-check/ignore', 'POST', data || {}) } ``` - [ ] **步骤 2:语法校验** ```bash node --check cfc-frontend/utils/api.js ``` 预期:无输出(语法正确) - [ ] **步骤 3:Commit** ```bash git add cfc-frontend/utils/api.js git commit -m "feat(self-check): 新增 getSelfCheckStatus 和 ignoreSelfCheck API 方法" ``` --- ## 任务 5:前端 — 新增中间页 self-check-entry.vue **文件:** - 新建:`cfc-frontend/pages/family/self-check-entry.vue` - [ ] **步骤 1:创建中间页文件** 创建 `cfc-frontend/pages/family/self-check-entry.vue`: ```vue ``` - [ ] **步骤 2:语法校验** ```bash node --check cfc-frontend/pages/family/self-check-entry.vue 2>&1 || echo "Vue 文件跳过 node --check" ``` 预期:Vue 文件无法直接 node --check,跳过即可;重点检查 script 块语法。 - [ ] **步骤 3:Commit** ```bash git add cfc-frontend/pages/family/self-check-entry.vue git commit -m "feat(self-check): 新增自检中间页 self-check-entry.vue" ``` --- ## 任务 6:前端 — 注册中间页 + 修改结果页跳转 **文件:** - 修改:`cfc-frontend/pages.json` - 修改:`cfc-frontend/pages/family/self-check-result.vue` - [ ] **步骤 1:pages.json 注册中间页** 在 `cfc-frontend/pages.json` 的 `pages/family` 分包中找到 `self-check-result` 注册,在其后追加: ```json { "path": "self-check-entry", "style": { "navigationBarTitleText": "自检" } } ``` - [ ] **步骤 2:self-check-result.vue 修改「重新自检」按钮** 在 `cfc-frontend/pages/family/self-check-result.vue` 中,找到 `retake` 方法(约第 394 行),将其修改为: ```javascript retake: function() { uni.navigateTo({ url: '/pages/family/self-check-entry' }) } ``` - [ ] **步骤 3:Commit** ```bash git add cfc-frontend/pages.json cfc-frontend/pages/family/self-check-result.vue git commit -m "feat(self-check): 注册 self-check-entry 页面;结果页「重新自检」改跳中间页" ``` --- ## 任务 7:前端 — 首页入口接入自检状态 **文件:** - 修改:`cfc-frontend/pages/index-home/index.vue` - 修改:`cfc-frontend/pages/home-pages/parent-index.vue` - [ ] **步骤 1:index-home/index.vue — 加载自检状态 + 显示分数** 在 `index.vue` 的 `data()` 中追加: ```javascript selfCheckScore: null, selfCheckLoading: false, ``` 在 `methods` 中追加方法(放入 `loadFamilyMembers` 调用链之后): ```javascript loadSelfCheckStatus: function() { var self = this getSelfCheckStatus().then(function(res) { if (res.code === 200 && res.data && res.data.lastResult) { self.selfCheckScore = res.data.lastResult.totalScore } }).catch(function() {}) } ``` 在 `loadDimensionData` 方法中追加调用: ```javascript loadDimensionData: function() { this.loadSandboxData() this.loadEnergyOverview() this.loadActionArticles() this.loadChallenges() this.loadSelfCheckStatus() } ``` 修改 `selfTestSub` 计算属性(约第 538 行): ```javascript selfTestSub: function() { if (this.sandboxData) { if (this.selfCheckScore != null) { return '最近自检得分 ' + this.selfCheckScore + '/45 分 · 点击查看' } return '最近综合得分 ' + (this.sandboxData.overallScore || 0) + ' 分 · 点击查看' } return '15 题五维自检 · 约 20 秒' } ``` 修改自检入口点击事件,改为跳转中间页: ```javascript // 找到原 goToSelfCheck 或类似方法,改为: goToSelfCheck: function() { uni.navigateTo({ url: '/pages/family/self-check-entry' }) } ``` 同时确保文件头部已导入 `getSelfCheckStatus`: ```javascript import { getSelfCheckStatus } from '@/utils/api' ``` - [ ] **步骤 2:home-pages/parent-index.vue — 加载自检状态 + 显示分数** 在 `data()` 中追加: ```javascript selfCheckScore: null, ``` 在 `methods` 中追加: ```javascript loadSelfCheckStatus: function() { var self = this getSelfCheckStatus().then(function(res) { if (res.code === 200 && res.data && res.data.lastResult) { self.selfCheckScore = res.data.lastResult.totalScore } }).catch(function() {}) } ``` 在 `onLoad` 或 `onShow` 中调用 `loadSelfCheckStatus()`(找到现有初始化调用处追加)。 修改 `selfcheck-subtitle` 或相关文案显示逻辑(找到渲染 `selfcheck-subtitle` 的模板部分),改为: ```html 最近得分 {{ selfCheckScore }}/45 分 15题 · 5分钟 · 生成五行相生寻源建议 ``` 修改 `goToSelfCheck` 方法(约第 1086 行): ```javascript goToSelfCheck: function() { uni.navigateTo({ url: '/pages/family/self-check-entry' }) } ``` 在文件头部导入 `getSelfCheckStatus`: ```javascript import { getSelfCheckStatus } from '@/utils/api' ``` - [ ] **步骤 3:语法校验** ```bash node --check cfc-frontend/pages/index-home/index.vue 2>&1 || echo "跳过" node --check cfc-frontend/pages/home-pages/parent-index.vue 2>&1 || echo "跳过" ``` - [ ] **步骤 4:Commit** ```bash git add cfc-frontend/pages/index-home/index.vue cfc-frontend/pages/home-pages/parent-index.vue git commit -m "feat(self-check): 首页入口显示自检总分 + 跳转中间页" ``` --- ## 任务 8:前端 — self-check.vue 支持 retake 参数 **文件:** - 修改:`cfc-frontend/pages/family/self-check.vue` - [ ] **步骤 1:onLoad 读取 retake 参数并传给 questions 接口** 在 `cfc-frontend/pages/family/self-check.vue` 中,修改 `loadQuestions` 方法: ```javascript loadQuestions: function() { var self = this var retake = this.$route && this.$route.query && this.$route.query.retake === '1' var apiCall = retake ? getSelfCheckQuestions({ retake: true }) : getSelfCheckQuestions() apiCall.then(function(res) { self.loading = false if (res.code === 200 && res.data) { self.questions = res.data || [] } else { uni.showToast({ title: (res && res.message) || '加载失败', icon: 'none' }) } }).catch(function() { self.loading = false uni.showToast({ title: '网络异常,请重试', icon: 'none' }) }) } ``` 同时修改 `api.js` 中 `getSelfCheckQuestions` 支持传参: ```javascript export const getSelfCheckQuestions = (params) => { return request('/api/family/self-check/questions', 'POST', params || {}) } ``` - [ ] **步骤 2:语法校验** ```bash node --check cfc-frontend/utils/api.js ``` - [ ] **步骤 3:Commit** ```bash git add cfc-frontend/utils/api.js cfc-frontend/pages/family/self-check.vue git commit -m "feat(self-check): 支持 retake 参数获取轮换题库" ``` --- ## 任务 9:最终验证 - [ ] **步骤 1:后端编译** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:BUILD SUCCESS - [ ] **步骤 2:前端语法校验** ```bash node --check cfc-frontend/utils/api.js && echo "api.js OK" ``` - [ ] **步骤 3:提交最终 commit(如有遗漏)** ```bash git add -A git diff --cached --stat git commit -m "feat(self-check): 五维自检 15 天复检周期全栈实现" ``` --- ## 自检清单 **规格覆盖度:** - [x] 15 天复检周期(个人维度)→ 任务 2(getStatus)、任务 3(Controller) - [x] 首页显示上次自检分数 → 任务 7 - [x] 进入时显示上次结果 + 二选一 → 任务 5(中间页) - [x] 忽略后重置计时 → 任务 2(ignoreSelfCheck)、任务 3(/ignore) - [x] 题目轮换 → 任务 2(getQuestionsForRetake + pickQuestions) **占位符扫描:** 无"待定"/"TODO"/"后续实现" **类型一致性:** - `SelfCheckIgnore` / `SelfCheckIgnoreMapper` — 任务 1 定义,任务 3 注入 - `getStatus` / `ignoreSelfCheck` / `getQuestionsForRetake` — 任务 2 定义,任务 3 Controller 调用 - `getSelfCheckStatus` / `ignoreSelfCheck` — 任务 4 定义,任务 5/7 调用