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: Add a game-based cognitive assessment system that measures seven cognitive dimensions — perception, focus, memory, logic, spatial reasoning, processing speed, and language expression — via interactive mini-games in the mini-program, with scores written to the existing DanAssessmentResult entity.
Architecture: Seven standalone cognitive test pages live in pages/cognitive-test/, each implementing a distinct game type. Scores post to the existing POST /api/cognitive/self-test endpoint, which already handles both new-record creation and updates to DanAssessmentResult. One new database column (language_score) is added for the new language dimension; the other five dimensions already have columns. A central portal page (cognitive-hall.vue) lists all seven tests with completion status.
Tech Stack: Java 8 / Spring Boot 2.7.18 / MyBatis-Plus (backend); uni-app Vue 2 Options API (frontend); MySQL 8.0.
Spec: This plan implements the design described in the brainstorming session (2026-09-19): seven cognitive dimensions, one game per dimension, results written to existing dan_assessment_results via the existing self-test API. No new backend endpoints are required.
@PostMapping only — no @GetMapping, @PutMapping, @DeleteMapping.@Resource, field names must match default Bean Name.schema.sql.?.), CSS Grid, or :key expressions — use methods instead.yyyy-MM-dd HH:mm:ss (full) or yyyy-MM-dd (date only).pages.json via navigationBarTitleText, not in templates.perception-test, logic-test, memory-test, spatial-test, speed-test) must remain untouched.language-test page follows the same pattern as memory-test.vue (intro → play → result → submit).| Action | Path | Responsibility |
|---|---|---|
| Modify | cfc-backend/src/main/java/com/etotem/cfc/entity/DanAssessmentResult.java |
Add languageScore field |
| Modify | cfc-backend/src/main/java/com/etotem/cfc/service/CognitiveService.java |
Add languageScore case to submitSelfTestScore() switch |
| Modify | cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java |
Add migration 323 to add language_score column |
| Modify | cfc-backend/src/main/resources/schema.sql |
Add language_score column to dan_assessment_results CREATE TABLE |
| Create | cfc-frontend/pages/cognitive-test/language-test.vue |
Language expression game (4 questions, word-relation matching) |
| Modify | cfc-frontend/pages.json |
Register language-test page in the existing pages/cognitive-test subpackage |
| Create | cfc-frontend/pages/cognitive-hall.vue |
Central portal listing all 7 tests with completion status |
| Modify | cfc-frontend/pages/wisdom/cognitive-report.vue |
Add languageScore dimension row to the report bar chart |
Files:
cfc-backend/src/main/java/com/etotem/cfc/entity/DanAssessmentResult.javacfc-backend/src/main/java/com/etotem/cfc/service/CognitiveService.javacfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.javacfc-backend/src/main/resources/schema.sqlInterfaces:
DanAssessmentResult fields and CognitiveService.submitSelfTestScore() methodProduces: new languageScore field on entity; migration 323 that is idempotent
[ ] Step 1: Add languageScore to DanAssessmentResult
In cfc-backend/src/main/java/com/etotem/cfc/entity/DanAssessmentResult.java, add after the existing processingSpeedScore field (around line 46):
private Integer languageScore; // 语言表达得分 (0-100)
The field goes between processingSpeedScore (line 46) and the // === EMI 心理子维度 comment (line 48).
languageScore case to CognitiveService.submitSelfTestScore()In cfc-backend/src/main/java/com/etotem/cfc/service/CognitiveService.java, within the switch (dimension) block starting around line 117, add before the default: case:
case "languageScore":
latest.setLanguageScore(score);
break;
Also update the overall score calculation loop (lines 139–144) to include languageScore:
for (Integer s : Arrays.asList(
latest.getAttentionScore(), latest.getFocusScore(),
latest.getMemoryScore(), latest.getLogicScore(),
latest.getPerceptionScore(), latest.getSpatialScore(),
latest.getProcessingSpeedScore(), latest.getLanguageScore())) {
DatabaseInitializer.javaIn cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java, insert before the closing } of the runMigrations() method (which ends at line 11133, right before line 11135's /**).
Add these lines (use the exact pattern from existing migrations):
// 迁移323: dan_assessment_results 添加 language_score 列(游戏化认知测评:语言表达维度)
ensureColumn("dan_assessment_results", "language_score", "INT COMMENT '语言表达得分'");
log.info("迁移323: 已确保 language_score 列存在于 dan_assessment_results");
schema.sqlIn cfc-backend/src/main/resources/schema.sql, find the dan_assessment_results CREATE TABLE (around line 421). After the processing_speed_score column definition (line 437), add:
language_score INT COMMENT '语言表达得分',
Run: cd cfc-backend && mvn clean compile
Expected: exit code 0, no errors.
[ ] Step 6: Commit
git add cfc-backend/src/main/java/com/etotem/cfc/entity/DanAssessmentResult.java
git add cfc-backend/src/main/java/com/etotem/cfc/service/CognitiveService.java
git add cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
git add cfc-backend/src/main/resources/schema.sql
git commit -m "feat: add language_score dimension to cognitive assessment (migration 323)"
language-test.vue)Files:
cfc-frontend/pages/cognitive-test/language-test.vuecfc-frontend/pages.jsonInterfaces:
submitCognitiveSelfTest(memberId, 'languageScore', score) from utils/api.jsProduces: a new quiz page matching the visual and interaction style of existing cognitive tests
[ ] Step 1: Write the page
Create cfc-frontend/pages/cognitive-test/language-test.vue with this exact content:
<template>
<view class="page">
<!-- Intro -->
<view class="intro" v-if="step === 'intro'">
<view class="intro-icon"><text class="intro-icon-text">🗣️</text></view>
<text class="intro-title">语言表达测试</text>
<text class="intro-desc">观察每组词语,找出与其他词语关系不同的那个。测试你的语言理解和词汇联想能力。</text>
<view class="intro-rule">
<text class="rule-item">共 6 题</text>
<text class="rule-item">每题限时 12 秒</text>
<text class="rule-item">测评维度:语言表达</text>
</view>
<button class="primary-btn" @click="startTest">开始测试</button>
</view>
<!-- Playing -->
<view class="playing" v-if="step === 'playing'">
<view class="progress-header">
<text class="progress-label">第 {{ currentQ + 1 }}/{{ totalQ }} 题</text>
<text class="timer-display">{{ timerSeconds }}s</text>
</view>
<view class="progress-track">
<view class="progress-fill" :style="{ width: progressPct + '%' }"></view>
</view>
<view class="question-card">
<text class="question-text">{{ currentQuestion.prompt }}</text>
<view class="options">
<view
class="option"
v-for="(opt, oi) in currentQuestion.options"
:key="oi"
:class="{ 'option-selected': selectedIndex === oi, 'option-correct': showFeedback && oi === currentQuestion.answerIndex, 'option-wrong': showFeedback && selectedIndex === oi && selectedIndex !== currentQuestion.answerIndex }"
@click="selectOption(oi)"
>
<text class="option-text">{{ opt }}</text>
</view>
</view>
</view>
<view class="feedback-overlay" v-if="showFeedback">
<text class="feedback-text">{{ feedbackMessage }}</text>
</view>
</view>
<!-- Result -->
<view class="result" v-if="step === 'result'">
<view class="score-circle">
<text class="score-num">{{ score }}</text>
<text class="score-unit">分</text>
</view>
<text class="score-comment">{{ scoreComment }}</text>
<view class="result-info">
<view class="info-row">
<text class="info-label">答对题数</text>
<text class="info-value">{{ correctCount }} / {{ totalQ }}</text>
</view>
<view class="info-row">
<text class="info-label">测评维度</text>
<view class="dim-tag"><text class="dim-tag-text">语言表达</text></view>
</view>
</view>
<button class="primary-btn" :disabled="submitting" @click="submitScore">{{ submitting ? '提交中...' : '提交成绩' }}</button>
<button class="ghost-btn" @click="startTest">再测一次</button>
</view>
</view>
</template>
<script>
import { submitCognitiveSelfTest } from '../../utils/api.js'
var TOTAL_Q = 6
var TIMER_SECONDS = 12
var QUESTION_BANK = [
{ prompt: '找出不同类的一项', options: ['苹果', '香蕉', '胡萝卜', '橘子'], answerIndex: 2 },
{ prompt: '找出不同类的一项', options: ['狗', '猫', '马', '鱼'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['红色', '蓝色', '绿色', '桌子'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['钢琴', '小提琴', '吉他', '足球'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['春天', '夏天', '秋天', '月亮'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['跑步', '游泳', '跳跃', '思考'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['父亲', '母亲', '姐姐', '老师'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['火车', '飞机', '轮船', '自行车'], answerIndex: 2 },
{ prompt: '找出不同类的一项', options: ['黄金', '白银', '铜', '铁'], answerIndex: 0 },
{ prompt: '找出不同类的一项', options: ['广州', '深圳', '香港', '北京'], answerIndex: 2 },
{ prompt: '找出不同类的一项', options: ['三角形', '圆形', '方形', '菱形'], answerIndex: 1 },
{ prompt: '找出不同类的一项', options: ['莎士比亚', '李白', '杜甫', '爱因斯坦'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['幸福', '快乐', '悲伤', '满足'], answerIndex: 2 },
{ prompt: '找出不同类的一项', options: ['书本', '报纸', '杂志', '电脑'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['太阳', '月亮', '星星', '云朵'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['钢笔', '铅笔', '毛笔', '画笔'], answerIndex: 0 },
{ prompt: '找出不同类的一项', options: ['苹果', '芒果', '榴莲', '土豆'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['大海', '河流', '湖泊', '雨水'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['红色', '橙色', '黄色', '白色'], answerIndex: 3 },
{ prompt: '找出不同类的一项', options: ['钢琴', '鼓', '喇叭', '篮球'], answerIndex: 3 }
]
export default {
data() {
return {
step: 'intro',
questions: [],
currentQ: 0,
selectedIndex: -1,
showFeedback: false,
feedbackMessage: '',
correctCount: 0,
timeLeft: TIMER_SECONDS,
timer: null,
score: 0,
memberId: '',
submitting: false,
totalQ: TOTAL_Q
}
},
computed: {
currentQuestion: function() {
if (this.questions.length === 0) return null
return this.questions[this.currentQ]
},
progressPct: function() {
return ((this.currentQ + 1) / this.totalQ) * 100
},
timerSeconds: function() {
return Math.ceil(this.timeLeft)
},
scoreComment: function() {
var s = this.score
if (s >= 90) return '语言表达出色,词汇联想能力很强!'
if (s >= 70) return '语言表达良好,继续锻炼会更强!'
if (s >= 50) return '表现不错,多阅读和多表达会有帮助!'
return '多练习词汇分类,表达能力会逐步提升!'
}
},
onLoad: function(options) {
var id = (options && options.memberId) || uni.getStorageSync('currentChildId') || ''
this.memberId = id
},
onUnload: function() {
this.clearTimer()
},
methods: {
shuffle: function(arr) {
var a = arr.slice()
for (var i = a.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1))
var tmp = a[i]
a[i] = a[j]
a[j] = tmp
}
return a
},
clearTimer: function() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
},
startTest: function() {
this.clearTimer()
var shuffled = this.shuffle(QUESTION_BANK)
this.questions = shuffled.slice(0, this.totalQ)
this.currentQ = 0
this.correctCount = 0
this.score = 0
this.selectedIndex = -1
this.showFeedback = false
this.step = 'playing'
this.startTimer()
},
startTimer: function() {
this.clearTimer()
this.timeLeft = TIMER_SECONDS
var self = this
this.timer = setInterval(function() {
self.timeLeft -= 0.1
if (self.timeLeft <= 0.01) {
self.timeLeft = 0
self.onTimeout()
}
}, 100)
},
selectOption: function(idx) {
if (this.showFeedback) return
this.showFeedback = true
this.selectedIndex = idx
this.clearTimer()
var q = this.currentQuestion
if (q && idx === q.answerIndex) {
this.correctCount++
this.feedbackMessage = '正确!'
} else {
this.feedbackMessage = '错误!'
}
var self = this
setTimeout(function() {
self.showFeedback = false
self.selectedIndex = -1
self.advance()
}, 900)
},
onTimeout: function() {
if (this.showFeedback) return
this.showFeedback = true
this.feedbackMessage = '超时!'
this.clearTimer()
var self = this
setTimeout(function() {
self.showFeedback = false
self.advance()
}, 900)
},
advance: function() {
if (this.currentQ < this.totalQ - 1) {
this.currentQ++
this.startTimer()
} else {
this.finishTest()
}
},
finishTest: function() {
this.clearTimer()
this.score = Math.round((this.correctCount / this.totalQ) * 100)
this.step = 'result'
},
submitScore: function() {
if (this.submitting) return
var mid = this.memberId
if (!mid) {
uni.showToast({ title: '未获取到成员信息', icon: 'none' })
return
}
this.submitting = true
submitCognitiveSelfTest(mid, 'languageScore', this.score)
.then(function(res) {
if (res && res.code === 200) {
uni.showToast({ title: '提交成功', icon: 'success' })
var self = this
setTimeout(function() {
var pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack()
} else {
uni.reLaunch({ url: '/pages/index/index' })
}
}, 800)
} else {
uni.showToast({ title: '提交失败', icon: 'none' })
}
}.bind(this))
.catch(function() {
uni.showToast({ title: '提交失败', icon: 'none' })
})
.finally(function() {
this.submitting = false
}.bind(this))
}
}
}
</script>
<style scoped>
.page {
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
display: flex;
flex-direction: column;
align-items: center;
padding: 60rpx 40rpx 80rpx;
box-sizing: border-box;
}
.primary-btn {
margin-top: 60rpx;
width: 480rpx;
height: 96rpx;
line-height: 96rpx;
border-radius: 48rpx;
background: linear-gradient(135deg, #F59E0B 0%, #FBBF24 100%);
color: #1a1a2e;
font-size: 34rpx;
font-weight: 600;
border: none;
box-shadow: 0 8rpx 24rpx rgba(245, 158, 11, 0.35);
}
.primary-btn::after { border: none; }
.primary-btn[disabled] { opacity: 0.6; }
.ghost-btn {
margin-top: 30rpx;
width: 480rpx;
height: 96rpx;
line-height: 96rpx;
border-radius: 48rpx;
background: transparent;
color: #FBBF24;
font-size: 32rpx;
border: 2rpx solid rgba(245, 158, 11, 0.5);
}
.ghost-btn::after { border: none; }
.intro { display: flex; flex-direction: column; align-items: center; width: 100%; padding-top: 80rpx; }
.intro-icon {
width: 160rpx; height: 160rpx; border-radius: 50%;
background: rgba(245, 158, 11, 0.15); border: 3rpx solid #F59E0B;
display: flex; align-items: center; justify-content: center; margin-bottom: 40rpx;
}
.intro-icon-text { font-size: 80rpx; }
.intro-title { font-size: 44rpx; font-weight: bold; color: #FBBF24; margin-bottom: 16rpx; display: block; }
.intro-desc { font-size: 26rpx; color: rgba(255,255,255,0.7); margin-bottom: 50rpx; display: block; text-align: center; padding: 0 20rpx; }
.intro-rule { display: flex; flex-direction: row; flex-wrap: wrap; justify-content: center; }
.rule-item {
font-size: 24rpx; color: #FBBF24;
background: rgba(245, 158, 11, 0.12); border-radius: 24rpx;
padding: 10rpx 24rpx; margin: 8rpx 12rpx;
}
.playing { width: 100%; display: flex; flex-direction: column; align-items: center; }
.progress-header { display: flex; justify-content: space-between; width: 100%; max-width: 620rpx; margin-bottom: 16rpx; }
.progress-label { font-size: 30rpx; color: #fff; font-weight: bold; }
.timer-display { font-size: 30rpx; color: #FBBF24; font-weight: bold; }
.progress-track {
width: 100%; max-width: 620rpx; height: 12rpx;
border-radius: 6rpx; background: rgba(255,255,255,0.1);
overflow: hidden; margin-bottom: 50rpx;
}
.progress-fill { height: 100%; border-radius: 6rpx; background: linear-gradient(90deg, #F59E0B, #FBBF24); transition: width 0.3s ease; }
.question-card {
background: rgba(255,255,255,0.06); border-radius: 24rpx;
padding: 40rpx 30rpx; width: 100%; max-width: 620rpx; box-sizing: border-box;
}
.question-text { font-size: 34rpx; color: #fff; font-weight: bold; text-align: center; display: block; margin-bottom: 40rpx; }
.options { display: flex; flex-direction: column; }
.option {
padding: 26rpx 0; border-radius: 16rpx; border: 3rpx solid rgba(255,255,255,0.2);
background: #24243e; text-align: center; margin-bottom: 20rpx; transition: all 0.2s;
}
.option:last-child { margin-bottom: 0; }
.option-selected { border-color: #FBBF24; background: rgba(245, 158, 11, 0.12); }
.option-correct { border-color: #10B981; background: rgba(16, 185, 129, 0.15); }
.option-wrong { border-color: #EF4444; background: rgba(239, 68, 68, 0.15); }
.option:active { transform: scale(0.98); }
.option-text { font-size: 32rpx; color: #fff; font-weight: 500; }
.option-selected .option-text, .option-correct .option-text { color: #FBBF24; }
.option-correct .option-text { color: #10B981; }
.option-wrong .option-text { color: #EF4444; }
.feedback-overlay {
position: fixed; left: 0; right: 0; top: 0; bottom: 0;
display: flex; align-items: center; justify-content: center; z-index: 99;
animation: feedbackFade 0.8s ease both;
}
.feedback-overlay:has(.feedback-text) { }
.feedback-text {
font-size: 56rpx; font-weight: bold; color: #fff;
padding: 24rpx 60rpx; border-radius: 24rpx; background: rgba(0,0,0,0.35);
}
@keyframes feedbackFade {
0% { opacity: 0; }
20% { opacity: 1; }
80% { opacity: 1; }
100% { opacity: 0; }
}
.result { display: flex; flex-direction: column; align-items: center; width: 100%; padding-top: 100rpx; }
.score-circle {
width: 240rpx; height: 240rpx; border-radius: 50%;
background: linear-gradient(135deg, #F59E0B, #FBBF24);
display: flex; flex-direction: column; align-items: center; justify-content: center;
box-shadow: 0 8rpx 40rpx rgba(245, 158, 11, 0.35);
}
.score-num { font-size: 80rpx; font-weight: bold; color: #1a1a2e; line-height: 1; }
.score-unit { font-size: 28rpx; color: rgba(26,26,46,0.7); margin-top: 4rpx; }
.score-comment { font-size: 30rpx; color: #FBBF24; margin-top: 30rpx; margin-bottom: 50rpx; display: block; }
.result-info {
width: 100%; max-width: 620rpx; background: rgba(255,255,255,0.06);
border-radius: 20rpx; padding: 20rpx 30rpx; margin-bottom: 60rpx; box-sizing: border-box;
}
.info-row { display: flex; justify-content: space-between; align-items: center; padding: 16rpx 0; }
.info-row + .info-row { border-top: 1rpx solid rgba(255,255,255,0.08); }
.info-label { font-size: 26rpx; color: rgba(255,255,255,0.7); }
.info-value { font-size: 28rpx; color: #fff; font-weight: bold; }
.dim-tag { padding: 6rpx 24rpx; border-radius: 24rpx; border: 3rpx solid #F59E0B; background: rgba(245, 158, 11, 0.12); }
.dim-tag-text { font-size: 24rpx; color: #FBBF24; font-weight: bold; }
</style>
pages.jsonIn cfc-frontend/pages.json, find the pages/cognitive-test subpackage entry (around line 328). Add the new page entry inside that subpackage's pages array, after the existing entries:
{
"path": "language-test",
"style": {
"navigationBarTitleText": "语言表达测试"
}
}
Insert before the closing ] of that subpackage's pages array (which currently ends at line 361 with the speed-test entry).
Run: node --check cfc-frontend/pages/cognitive-test/language-test.vue is not valid — Vue files don't support --check. Instead, verify the JS in the <script> block parses:
Extract the script block and run node -e "require('fs').readFileSync('cfc-frontend/pages/cognitive-test/language-test.vue','utf8').match(/<script>([\s\S]*?)<\/script>/)[1]" | node --input-type=module 2>&1 | head -5 or simply visually inspect that there are no syntax errors.
Alternatively, verify the pages.json is valid JSON:
node -e "JSON.parse(require('fs').readFileSync('cfc-frontend/pages.json','utf8')); console.log('pages.json OK')"
Expected: pages.json OK
[ ] Step 4: Commit
git add cfc-frontend/pages/cognitive-test/language-test.vue
git add cfc-frontend/pages.json
git commit -m "feat: add language expression cognitive test game (七维认知测评)"
Files:
cfc-frontend/pages/cognitive-hall.vuecfc-frontend/pages.jsonInterfaces:
getCognitiveFamilyScores() from utils/api.js (returns list of members with cognitive scores)Produces: a portal page navigable from the wisdom (智) TabBar or a dedicated menu entry
[ ] Step 1: Create the cognitive hall page
Create cfc-frontend/pages/cognitive-hall.vue:
<template>
<view class="page">
<view class="header">
<text class="title">认知能力测评</text>
<text class="subtitle">通过小游戏评估七项核心认知能力</text>
</view>
<view class="member-selector" v-if="members.length > 1">
<text class="selector-label">选择测评对象:</text>
<view class="member-tabs">
<view
class="member-tab"
:class="{ 'member-tab-active': selectedMemberId === m.id }"
v-for="m in members"
:key="m.id"
@click="selectMember(m.id)"
>
<text>{{ m.nickname }}</text>
</view>
</view>
</view>
<view class="loading" v-if="loading">
<text>加载中...</text>
</view>
<view class="game-list" v-if="!loading">
<view
class="game-card"
v-for="game in games"
:key="game.code"
@click="navigateTo(game.path)"
>
<view class="game-icon-wrap" :style="{ background: game.colorBg }">
<text class="game-icon">{{ game.icon }}</text>
</view>
<view class="game-info">
<text class="game-name">{{ game.name }}</text>
<text class="game-desc">{{ game.desc }}</text>
<text class="game-status" v-if="game.completed">✓ 已完成</text>
<text class="game-status-pending" v-else>未测评</text>
</view>
<text class="game-arrow">›</text>
</view>
</view>
<view class="empty" v-if="!loading && scores === null">
<text class="empty-icon">🧠</text>
<text class="empty-text">暂无认知数据</text>
<text class="empty-hint">完成任意一项测试后即可查看详细报告</text>
</view>
<view class="action-row">
<view class="btn btn--outline" @click="goReport">查看完整报告</view>
</view>
</view>
</template>
<script>
import { getCognitiveChildScores } from '../../utils/api.js'
export default {
data() {
return {
memberId: '',
members: [],
selectedMemberId: null,
scores: null,
loading: true
}
},
computed: {
games: function() {
var s = this.scores
return [
{ code: 'perception', name: '感知能力', desc: '感官信息处理能力', icon: '👁️', colorBg: 'rgba(99,102,241,0.15)', path: '/pages/cognitive-test/perception-test', completed: s && s.perceptionScore != null },
{ code: 'focus', name: '专注力', desc: '持续集中和抗干扰能力', icon: '🎯', colorBg: 'rgba(239,68,68,0.15)', path: '/pages/games/schulte', completed: s && s.focusScore != null },
{ code: 'memory', name: '记忆力', desc: '信息编码和回忆能力', icon: '🧠', colorBg: 'rgba(99,102,241,0.15)', path: '/pages/cognitive-test/memory-test', completed: s && s.memoryScore != null },
{ code: 'logic', name: '逻辑推理', desc: '推理和问题解决能力', icon: '💡', colorBg: 'rgba(251,191,36,0.15)', path: '/pages/cognitive-test/logic-test', completed: s && s.logicScore != null },
{ code: 'spatial', name: '空间思维', desc: '空间想象和方位判断', icon: '🗺️', colorBg: 'rgba(16,185,129,0.15)', path: '/pages/cognitive-test/spatial-test', completed: s && s.spatialScore != null },
{ code: 'speed', name: '加工速度', desc: '信息处理和反应速度', icon: '⚡', colorBg: 'rgba(245,158,11,0.15)', path: '/pages/cognitive-test/speed-test', completed: s && s.processingSpeedScore != null },
{ code: 'language', name: '语言表达', desc: '词汇理解与语言组织', icon: '🗣️', colorBg: 'rgba(236,72,153,0.15)', path: '/pages/cognitive-test/language-test', completed: s && s.languageScore != null }
]
}
},
onLoad: function(options) {
if (options && options.memberId) {
this.memberId = options.memberId
} else {
this.memberId = uni.getStorageSync('currentChildId') || ''
}
this.selectedMemberId = this.memberId
this.loadScores()
},
methods: {
selectMember: function(id) {
this.selectedMemberId = id
this.loadScores()
},
loadScores: function() {
var self = this
if (!this.selectedMemberId) {
this.loading = false
return
}
this.loading = true
getCognitiveChildScores(this.selectedMemberId)
.then(function(res) {
if (res && res.code === 200 && res.data) {
self.scores = res.data
} else {
self.scores = null
}
})
.catch(function() {
self.scores = null
})
.finally(function() {
self.loading = false
})
},
navigateTo: function(path) {
var url = path + '?memberId=' + this.selectedMemberId
uni.navigateTo({ url: url })
},
goReport: function() {
uni.navigateTo({ url: '/pages/wisdom/cognitive-report?memberId=' + (this.selectedMemberId || '') })
}
}
}
</script>
<style scoped>
.page { min-height: 100vh; background: #f5f7fa; padding-bottom: 40rpx; }
.header { padding: 40rpx 30rpx 20rpx; background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); color: #fff; }
.title { font-size: 44rpx; font-weight: bold; display: block; }
.subtitle { font-size: 26rpx; opacity: 0.85; margin-top: 10rpx; display: block; }
.member-selector { padding: 20rpx 30rpx; background: #fff; margin: 20rpx 0; border-radius: 16rpx; }
.selector-label { font-size: 26rpx; color: #666; }
.member-tabs { display: flex; flex-direction: row; flex-wrap: wrap; margin-top: 16rpx; }
.member-tab {
padding: 10rpx 28rpx; border-radius: 30rpx; margin: 8rpx 8rpx 0 0;
background: #eee; font-size: 26rpx; color: #666;
}
.member-tab-active { background: #6366f1; color: #fff; }
.game-list { padding: 0 30rpx; }
.game-card {
display: flex; flex-direction: row; align-items: center;
background: #fff; border-radius: 20rpx; padding: 28rpx; margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
}
.game-icon-wrap {
width: 88rpx; height: 88rpx; border-radius: 20rpx;
display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.game-icon { font-size: 44rpx; }
.game-info { flex: 1; margin-left: 24rpx; }
.game-name { font-size: 30rpx; font-weight: bold; color: #1a1a2e; display: block; }
.game-desc { font-size: 24rpx; color: #888; margin-top: 6rpx; display: block; }
.game-status { font-size: 22rpx; color: #10B981; margin-top: 8rpx; display: block; }
.game-status-pending { font-size: 22rpx; color: #aaa; margin-top: 8rpx; display: block; }
.game-arrow { font-size: 40rpx; color: #ccc; margin-left: 16rpx; }
.loading, .empty { text-align: center; padding: 80rpx 40rpx; color: #999; font-size: 28rpx; }
.empty-icon { font-size: 80rpx; display: block; margin-bottom: 20rpx; }
.empty-hint { font-size: 24rpx; color: #bbb; margin-top: 10rpx; display: block; }
.action-row { padding: 30rpx; }
.btn { width: 100%; padding: 24rpx 0; border-radius: 50rpx; text-align: center; font-size: 30rpx; font-weight: bold; box-sizing: border-box; }
.btn--outline { border: 2rpx solid #6366f1; color: #6366f1; background: transparent; }
.btn--outline:active { background: rgba(99,102,241,0.08); }
</style>
pages.jsonAdd the new page to the main pages array in cfc-frontend/pages.json. Insert before the first subpackage entry. Find a good spot — add it near the other wisdom-related pages or as a standalone entry:
{
"path": "pages/cognitive-hall",
"style": {
"navigationBarTitleText": "认知能力测评"
}
}
[ ] Step 3: Verify JSON
node -e "JSON.parse(require('fs').readFileSync('cfc-frontend/pages.json','utf8')); console.log('pages.json OK')"
Expected: pages.json OK
[ ] Step 4: Commit
git add cfc-frontend/pages/cognitive-hall.vue
git add cfc-frontend/pages.json
git commit -m "feat: add cognitive assessment hall portal (七维认知能力总览)"
Files:
cfc-frontend/pages/wisdom/cognitive-report.vueInterfaces:
dimensions array and report objectProduces: a report showing all 7 dimensions including the new languageScore
[ ] Step 1: Add language dimension to the report
In cfc-frontend/pages/wisdom/cognitive-report.vue, in the data() function, find the dimensions array (around line 89) and add the new language dimension at the end:
{ key: 'languageScore', label: '语言表达', icon: '🗣️', desc: '词汇理解与语言组织能力' }
The full updated array should be:
dimensions: [
{ key: 'perceptionScore', label: '感知能力', icon: '👁️', desc: '感官信息处理能力' },
{ key: 'focusScore', label: '专注力', icon: '🎯', desc: '持续集中和抗干扰能力' },
{ key: 'memoryScore', label: '记忆力', icon: '🧠', desc: '信息编码和回忆能力' },
{ key: 'logicScore', label: '逻辑思维', icon: '💡', desc: '推理和问题解决能力' },
{ key: 'spatialScore', label: '空间思维', icon: '🗺️', desc: '空间想象和方位判断' },
{ key: 'processingSpeedScore', label: '加工速度', icon: '⚡', desc: '信息处理和反应速度' },
{ key: 'languageScore', label: '语言表达', icon: '🗣️', desc: '词汇理解与语言组织能力' }
]
In the same file, find the empty state text around line 73 and update the description to mention 7 dimensions:
Change:
<text class="empty-desc">完成 DAN 认知评估后,可在此查看详细的六维认知分析报告</text>
To:
<text class="empty-desc">完成认知能力游戏测评或DAN评估后,可在此查看详细的七维认知分析报告</text>
[ ] Step 3: Commit
git add cfc-frontend/pages/wisdom/cognitive-report.vue
git commit -m "feat: add language dimension to cognitive report (七维认知报告)"
Run: cd cfc-backend && mvn clean compile
Expected: exit code 0, no compilation errors.
Run: node -e "JSON.parse(require('fs').readFileSync('cfc-frontend/pages.json','utf8')); console.log('pages.json OK')"
Expected: pages.json OK
[ ] Step 3: Quick JS syntax check on new Vue files
node -e "
var fs = require('fs');
['cfc-frontend/pages/cognitive-test/language-test.vue','cfc-frontend/pages/cognitive-hall.vue'].forEach(function(f) {
var content = fs.readFileSync(f, 'utf8');
var scriptMatch = content.match(/<script>([\\s\\S]*?)<\\/script>/);
if (scriptMatch) {
try { new Function(scriptMatch[1]); console.log(f + ': JS OK'); }
catch(e) { console.error(f + ': JS ERROR: ' + e.message); }
}
});
"
Expected: both files print JS OK.
If verification revealed issues, fix and commit. Otherwise no additional commit needed.
POST /api/cognitive/self-test already handles score submission.attentionScore and focusScore exist in the entity but have no game pages. They can be set via self-test or manual entry but are not part of this deliverable. If needed later, add a similar page.submitCognitiveSelfTest with dimension keys: perceptionScore, logicScore, memoryScore, spatialScore, processingSpeedScore. The new page adds languageScore.overallScore calculation in CognitiveService.submitSelfTestScore() iterates over all 7 dimension fields, so adding languageScore automatically includes it in the composite average.cognitive-hall.vue → sees 7 cards → taps one → plays game → submits score → returns to hall (hall refreshes status).