Bladeren bron

feat: add psychological assessment module (PHQ-9, RSES-7)
- New pages: pages/psych-assessment/index & quiz
- pages.json registers psych-assessment routes
- Dimensions: phq9_score, phq4_score, reses_score

E2E Test Bot 1 week geleden
bovenliggende
commit
87b3897c7f
2 gewijzigde bestanden met toevoegingen van 1040 en 0 verwijderingen
  1. 529 0
      cfc-frontend/pages/psych-assessment/index.vue
  2. 511 0
      cfc-frontend/pages/psych-assessment/quiz.vue

+ 529 - 0
cfc-frontend/pages/psych-assessment/index.vue

@@ -0,0 +1,529 @@
+<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="scale-list">
+      <view class="scale-card" @click="startScale('phq9')">
+        <view class="scale-icon" style="background: rgba(99,102,241,0.15);">
+          <text class="scale-icon-text">💙</text>
+        </view>
+        <view class="scale-info">
+          <text class="scale-name">情绪健康筛查</text>
+          <text class="scale-desc">PHQ-9 抑郁症状筛查 (9题)</text>
+          <text class="scale-status" v-if="scores.phq9 != null">已完成</text>
+          <text class="scale-status-pending" v-else>未测评</text>
+        </view>
+        <text class="scale-arrow">›</text>
+      </view>
+
+      <view class="scale-card" @click="startScale('phq4')">
+        <view class="scale-icon" style="background: rgba(239,68,68,0.15);">
+          <text class="scale-icon-text">😔</text>
+        </view>
+        <view class="scale-info">
+          <text class="scale-name">快速心理筛查</text>
+          <text class="scale-desc">PHQ-4 抑郁+焦虑快速筛查 (4题)</text>
+          <text class="scale-status" v-if="scores.phq4 != null">已完成</text>
+          <text class="scale-status-pending" v-else>未测评</text>
+        </view>
+        <text class="scale-arrow">›</text>
+      </view>
+
+      <view class="scale-card" @click="startScale('reses')">
+        <view class="scale-icon" style="background: rgba(245,158,11,0.15);">
+          <text class="scale-icon-text">⭐</text>
+        </view>
+        <view class="scale-info">
+          <text class="scale-name">自尊水平评估</text>
+          <text class="scale-desc">RSES-7 自我价值感评估 (7题)</text>
+          <text class="scale-status" v-if="scores.reses != null">已完成</text>
+          <text class="scale-status-pending" v-else>未测评</text>
+        </view>
+        <text class="scale-arrow">›</text>
+      </view>
+    </view>
+
+    <view class="result-section" v-if="scores">
+      <view class="section-title">测评结果</view>
+      
+      <view class="score-card" v-if="scores.phq9 != null">
+        <view class="score-header">
+          <text class="score-label">PHQ-9 抑郁筛查</text>
+          <view class="score-badge" :class="'score-badge-' + getPhq9Severity(scores.phq9)">
+            <text class="badge-text">{{ getPhq9SeverityLabel(scores.phq9) }}</text>
+          </view>
+        </view>
+        <view class="score-row">
+          <text class="score-value">{{ scores.phq9 }}</text>
+          <text class="score-unit">分</text>
+        </view>
+        <view class="score-bar">
+          <view class="score-bar-fill" :style="{ width: getPhq9Percent(scores.phq9) + '%', background: getPhq9Color(scores.phq9) }"></view>
+        </view>
+        <text class="score-note">{{ getPhq9Note(scores.phq9) }}</text>
+      </view>
+
+      <view class="score-card" v-if="scores.reses != null">
+        <view class="score-header">
+          <text class="score-label">RSES-7 自尊水平</text>
+          <view class="score-badge" :class="'score-badge-' + getResesLevel(scores.reses)">
+            <text class="badge-text">{{ getResesLevelLabel(scores.reses) }}</text>
+          </view>
+        </view>
+        <view class="score-row">
+          <text class="score-value">{{ scores.reses }}</text>
+          <text class="score-unit">分</text>
+        </view>
+        <view class="score-bar">
+          <view class="score-bar-fill" :style="{ width: getResesPercent(scores.reses) + '%', background: '#F59E0B' }"></view>
+        </view>
+      </view>
+    </view>
+
+    <view class="empty-state" v-if="!scores">
+      <text class="empty-icon">🧠</text>
+      <text class="empty-text">暂无心理测评数据</text>
+      <text class="empty-hint">完成任意量表后即可在此查看结果</text>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getCognitiveChildScores, submitCognitiveSelfTest } from '../../utils/api.js'
+import { getFamilyMemberList } from '../../utils/api.js'
+
+// PHQ-9 量表题目(已验证的中文版)
+var PHQ9_QUESTIONS = [
+  { id: 1, text: '做事时提不起劲或没有兴趣', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { id: 2, text: '感到心情低落、沮丧或绝望', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { id: 3, text: '入睡困难、睡不安稳或睡眠过多', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { id: 4, text: '感觉疲倦或没有活力', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { id: 5, text: '食欲不振或吃太多', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { id: 6, text: '觉得自己很糟糕,或觉得自己很失败,让自己或家人失望', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { id: 7, text: '对事物专注有困难,例如看报纸或看电视时', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { id: 8, text: '动作或说话速度缓慢到别人已经察觉,或正好相反,坐立不安或比平时更激动', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { id: 9, text: '有不如死掉或用某种方式伤害自己的念头', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] }
+]
+
+// RSES-7 量表题目(移除5/8/9后的中文版)
+var RSES_QUESTIONS = [
+  { id: 1, text: '我觉得自己是一个有价值的人,至少与其他人一样', options: ['非常不符合', '比较不符合', '比较符合', '完全符合'] },
+  { id: 2, text: '我感到自己有许多优点', options: ['非常不符合', '比较不符合', '比较符合', '完全符合'] },
+  { id: 3, text: '我对自己持肯定的态度', options: ['非常不符合', '比较不符合', '比较符合', '完全符合'] },
+  { id: 4, text: '我能够像大多数人一样把事情做好', options: ['非常不符合', '比较不符合', '比较符合', '完全符合'] },
+  { id: 5, text: '我对自己的人生态度感到满意', options: ['非常不符合', '比较不符合', '比较符合', '完全符合'] },
+  { id: 6, text: '我觉得我并没有什么值得自豪的地方', options: ['完全符合', '比较符合', '比较不符合', '非常不符合'] },
+  { id: 7, text: '总的来说,我倾向于认为自己是一个失败者', options: ['完全符合', '比较符合', '比较不符合', '非常不符合'] }
+]
+
+export default {
+  data() {
+    return {
+      selectedMemberId: null,
+      members: [],
+      scores: null,
+      loading: true,
+      // 当前进行中的测评
+      currentScale: null,
+      currentQuestionIndex: 0,
+      currentAnswers: [],
+      showResult: false
+    }
+  },
+  computed: {
+    currentQuestions() {
+      if (this.currentScale === 'phq9') return PHQ9_QUESTIONS
+      if (this.currentScale === 'reses') return RSES_QUESTIONS
+      return []
+    }
+  },
+  onLoad: function(options) {
+    if (options && options.memberId) {
+      this.selectedMemberId = options.memberId
+    } else {
+      this.selectedMemberId = uni.getStorageSync('currentChildId') || ''
+    }
+    this.loadMembers()
+    this.loadScores()
+  },
+  methods: {
+    loadMembers: function() {
+      var self = this
+      getFamilyMemberList({ visibleOnly: true })
+        .then(function(res) {
+          if (res && res.code === 200 && res.data) {
+            self.members = res.data
+            if (!self.selectedMemberId && res.data.length > 0) {
+              self.selectedMemberId = res.data[0].id
+              self.loadScores()
+            }
+          }
+        })
+        .catch(function() {
+          // 静默失败
+        })
+    },
+    selectMember: function(id) {
+      this.selectedMemberId = id
+      this.loadScores()
+    },
+    loadScores: function() {
+      var self = this
+      var memberId = this.selectedMemberId
+      if (!memberId) {
+        this.loading = false
+        return
+      }
+      this.loading = true
+      getCognitiveChildScores(memberId)
+        .then(function(res) {
+          if (res && res.code === 200 && res.data) {
+            // 映射字段到新的dimension key
+            self.scores = {
+              phq9: res.data.phq9_score || null,
+              phq4: res.data.phq4_score || null,
+              reses: res.data.reses_score || null
+            }
+          } else {
+            self.scores = null
+          }
+        })
+        .catch(function() {
+          self.scores = null
+        })
+        .finally(function() {
+          self.loading = false
+        })
+    },
+    startScale: function(scale) {
+      this.currentScale = scale
+      this.currentQuestionIndex = 0
+      this.currentAnswers = []
+      this.showResult = false
+      uni.navigateTo({
+        url: '/pages/psych-assessment/quiz?scale=' + scale + '&memberId=' + this.selectedMemberId
+      })
+    },
+    // PHQ-9 评分逻辑
+    getPhq9Severity: function(score) {
+      if (score <= 4) return 'minimal'
+      if (score <= 9) return 'mild'
+      if (score <= 14) return 'moderate'
+      if (score <= 19) return 'moderately_severe'
+      return 'severe'
+    },
+    getPhq9SeverityLabel: function(score) {
+      if (score <= 4) return '轻度'
+      if (score <= 9) return '中度'
+      if (score <= 14) return '中重度'
+      if (score <= 19) return '重度'
+      return '极重度'
+    },
+    getPhq9Color: function(score) {
+      if (score <= 4) return '#10B981'
+      if (score <= 9) return '#F59E0B'
+      if (score <= 14) return '#F97316'
+      if (score <= 19) return '#EF4444'
+      return '#7C3AED'
+    },
+    getPhq9Percent: function(score) {
+      return Math.min(100, Math.round(score / 27 * 100))
+    },
+    getPhq9Note: function(score) {
+      if (score <= 4) return '建议:目前情绪状态良好,建议定期监测'
+      if (score <= 9) return '建议:轻度情绪困扰,建议关注并适当调节'
+      if (score <= 14) return '建议:中度情绪困扰,建议寻求专业咨询'
+      if (score <= 19) return '建议:重度情绪困扰,建议尽快寻求专业帮助'
+      return '建议:极重度情绪困扰,请立即寻求专业医疗帮助'
+    },
+    // RSES 评分逻辑
+    getResesLevel: function(score) {
+      if (score >= 28) return 'high'
+      if (score >= 21) return 'normal'
+      return 'low'
+    },
+    getResesLevelLabel: function(score) {
+      if (score >= 28) return '高自尊'
+      if (score >= 21) return '中等自尊'
+      return '低自尊'
+    },
+    getResesPercent: function(score) {
+      return Math.min(100, Math.round(score / 28 * 100))
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 40rpx;
+}
+
+.header {
+  padding: 40rpx 30rpx 30rpx;
+  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: 24rpx 30rpx;
+  background: #fff;
+  margin: 20rpx 0;
+  border-radius: 16rpx;
+  margin-left: 30rpx;
+  margin-right: 30rpx;
+}
+
+.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;
+}
+
+.scale-list {
+  padding: 0 30rpx;
+}
+
+.scale-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);
+}
+
+.scale-icon {
+  width: 88rpx;
+  height: 88rpx;
+  border-radius: 20rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.scale-icon-text {
+  font-size: 44rpx;
+}
+
+.scale-info {
+  flex: 1;
+  margin-left: 24rpx;
+}
+
+.scale-name {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #1a1a2e;
+  display: block;
+}
+
+.scale-desc {
+  font-size: 24rpx;
+  color: #888;
+  margin-top: 6rpx;
+  display: block;
+}
+
+.scale-status {
+  font-size: 22rpx;
+  color: #10B981;
+  margin-top: 8rpx;
+  display: block;
+}
+
+.scale-status-pending {
+  font-size: 22rpx;
+  color: #aaa;
+  margin-top: 8rpx;
+  display: block;
+}
+
+.scale-arrow {
+  font-size: 40rpx;
+  color: #ccc;
+  margin-left: 16rpx;
+}
+
+.result-section {
+  padding: 30rpx;
+}
+
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+}
+
+.score-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+
+.score-header {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+
+.score-label {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.score-badge {
+  padding: 6rpx 20rpx;
+  border-radius: 20rpx;
+  font-size: 22rpx;
+}
+
+.score-badge-minimal {
+  background: rgba(16,185,129,0.1);
+  color: #10B981;
+}
+
+.score-badge-mild {
+  background: rgba(245,158,11,0.1);
+  color: #F59E0B;
+}
+
+.score-badge-moderate {
+  background: rgba(249,115,22,0.1);
+  color: #F97316;
+}
+
+.score-badge-moderately_severe {
+  background: rgba(239,68,68,0.1);
+  color: #EF4444;
+}
+
+.score-badge-severe {
+  background: rgba(124,58,237,0.1);
+  color: #7C3AED;
+}
+
+.score-row {
+  display: flex;
+  align-items: baseline;
+  margin-bottom: 16rpx;
+}
+
+.score-value {
+  font-size: 72rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.score-unit {
+  font-size: 28rpx;
+  color: #999;
+  margin-left: 8rpx;
+}
+
+.score-bar {
+  height: 16rpx;
+  background: rgba(0,0,0,0.08);
+  border-radius: 8rpx;
+  overflow: hidden;
+  margin-bottom: 12rpx;
+}
+
+.score-bar-fill {
+  height: 100%;
+  border-radius: 8rpx;
+  transition: width 0.3s ease;
+}
+
+.score-note {
+  font-size: 24rpx;
+  color: #666;
+  line-height: 1.6;
+}
+
+.empty-state {
+  text-align: center;
+  padding: 80rpx 40rpx;
+}
+
+.empty-icon {
+  font-size: 80rpx;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.empty-text {
+  font-size: 28rpx;
+  color: #999;
+  display: block;
+}
+
+.empty-hint {
+  font-size: 24rpx;
+  color: #bbb;
+  margin-top: 10rpx;
+  display: block;
+}
+</style>

+ 511 - 0
cfc-frontend/pages/psych-assessment/quiz.vue

@@ -0,0 +1,511 @@
+<template>
+  <view class="page">
+    <!-- 介绍页 -->
+    <view class="intro" v-if="!started">
+      <view class="intro-icon">
+        <text class="intro-icon-text">{{ scaleIcon }}</text>
+      </view>
+      <text class="intro-title">{{ scaleTitle }}</text>
+      <text class="intro-desc">{{ scaleDesc }}</text>
+      <view class="intro-info">
+        <view class="info-row">
+          <text class="info-label">题目数量</text>
+          <text class="info-value">{{ totalQuestions }} 题</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">每题时限</text>
+          <text class="info-value">无限制</text>
+        </view>
+      </view>
+      <button class="btn-primary" @click="startQuiz">开始测评</button>
+    </view>
+
+    <!-- 答题页 -->
+    <view class="quiz" v-else-if="!showResult">
+      <view class="quiz-header">
+        <text class="quiz-progress">第 {{ currentQ + 1 }} / {{ totalQuestions }} 题</text>
+      </view>
+      <view class="progress-track">
+        <view class="progress-fill" :style="{ width: progressPct + '%' }"></view>
+      </view>
+      
+      <view class="question-card">
+        <text class="question-text">{{ currentQuestion && currentQuestion.text }}</text>
+        <view class="options">
+          <view
+            class="option"
+            v-for="(opt, oi) in currentQuestion && currentQuestion.options"
+            :key="oi"
+            :class="{ 'option-selected': selectedIndex === oi }"
+            @click="selectOption(oi)"
+          >
+            <text class="option-text">{{ opt }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 结果页 -->
+    <view class="result" v-else>
+      <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 }} / {{ totalQuestions }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">测评维度</text>
+          <view class="dim-tag">
+            <text class="dim-tag-text">{{ scaleLabel }}</text>
+          </view>
+        </view>
+      </view>
+      <button class="btn-primary" :disabled="submitting" @click="submitScore">
+        {{ submitting ? '提交中...' : '提交成绩' }}
+      </button>
+      <button class="btn-secondary" @click="restartQuiz">重新测评</button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { submitCognitiveSelfTest } from '../../utils/api.js'
+
+// PHQ-9 题目(得分:完全不会=0, 好几天=1, 一半天数=2, 几乎每天=3)
+var PHQ9_QUESTIONS = [
+  { text: '做事时提不起劲或没有兴趣', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { text: '感到心情低落、沮丧或绝望', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { text: '入睡困难、睡不安稳或睡眠过多', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { text: '感觉疲倦或没有活力', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { text: '食欲不振或吃太多', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { text: '觉得自己很糟糕,或觉得自己很失败,让自己或家人失望', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { text: '对事物专注有困难,例如看报纸或看电视时', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { text: '动作或说话速度缓慢到别人已经察觉,或正好相反,坐立不安或比平时更激动', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] },
+  { text: '有不如死掉或用某种方式伤害自己的念头', options: ['完全不会', '好几天', '一半以上的天数', '几乎每天'] }
+]
+
+// RSES-7 题目(反向计分,得分:非常不符合=1, 比较不符合=2, 比较符合=3, 完全符合=4)
+var RSES_QUESTIONS = [
+  { text: '我觉得自己是一个有价值的人,至少与其他人一样', options: ['非常不符合', '比较不符合', '比较符合', '完全符合'] },
+  { text: '我感到自己有许多优点', options: ['非常不符合', '比较不符合', '比较符合', '完全符合'] },
+  { text: '我对自己持肯定的态度', options: ['非常不符合', '比较不符合', '比较符合', '完全符合'] },
+  { text: '我能够像大多数人一样把事情做好', options: ['非常不符合', '比较不符合', '比较符合', '完全符合'] },
+  { text: '我对自己的人生态度感到满意', options: ['非常不符合', '比较不符合', '比较符合', '完全符合'] },
+  { text: '我觉得我并没有什么值得自豪的地方', options: ['完全符合', '比较符合', '比较不符合', '非常不符合'] },
+  { text: '总的来说,我倾向于认为自己是一个失败者', options: ['完全符合', '比较符合', '比较不符合', '非常不符合'] }
+]
+
+export default {
+  data() {
+    return {
+      scale: '',
+      memberId: '',
+      started: false,
+      showResult: false,
+      questions: [],
+      currentQ: 0,
+      selectedIndex: -1,
+      correctCount: 0,
+      score: 0,
+      submitting: false
+    }
+  },
+  computed: {
+    currentQuestion: function() {
+      if (this.questions.length === 0) return null
+      return this.questions[this.currentQ]
+    },
+    totalQuestions: function() {
+      return this.questions.length
+    },
+    progressPct: function() {
+      return ((this.currentQ + 1) / this.totalQuestions) * 100
+    },
+    scaleIcon: function() {
+      return this.scale === 'phq9' ? '💙' : '⭐'
+    },
+    scaleTitle: function() {
+      return this.scale === 'phq9' ? '情绪健康筛查' : '自尊水平评估'
+    },
+    scaleDesc: function() {
+      return this.scale === 'phq9' 
+        ? 'PHQ-9 是国际通用的抑郁症状筛查量表,过去两周内有以下症状的频率?'
+        : 'RSES-7 是国际通用的自尊水平评估量表,请根据实际情况选择最符合的选项'
+    },
+    scaleLabel: function() {
+      return this.scale === 'phq9' ? '情绪健康' : '自尊水平'
+    },
+    scoreComment: function() {
+      if (this.scale === 'phq9') {
+        if (this.score <= 4) return '情绪状态良好,建议定期监测'
+        if (this.score <= 9) return '轻度情绪困扰,建议关注并适当调节'
+        if (this.score <= 14) return '中度情绪困扰,建议寻求专业咨询'
+        return '建议尽快寻求专业帮助'
+      } else {
+        if (this.score >= 28) return '自尊水平较高,自信心强'
+        if (this.score >= 21) return '自尊水平中等,建议持续提升自我价值感'
+        return '自尊水平较低,建议寻求专业支持'
+      }
+    }
+  },
+  onLoad: function(options) {
+    if (options && options.scale) {
+      this.scale = options.scale
+      this.memberId = options.memberId || uni.getStorageSync('currentChildId') || ''
+      this.initQuestions()
+    }
+  },
+  methods: {
+    initQuestions: function() {
+      if (this.scale === 'phq9') {
+        this.questions = PHQ9_QUESTIONS.slice()
+      } else if (this.scale === 'reses') {
+        this.questions = RSES_QUESTIONS.slice()
+      }
+    },
+    startQuiz: function() {
+      this.started = true
+      this.currentQ = 0
+      this.selectedIndex = -1
+      this.correctCount = 0
+      this.score = 0
+    },
+    selectOption: function(idx) {
+      this.selectedIndex = idx
+      var points = this.scale === 'phq9' ? idx : idx + 1
+      this.correctCount += points
+      var self = this
+      setTimeout(function() {
+        if (self.currentQ < self.totalQuestions - 1) {
+          self.currentQ++
+          self.selectedIndex = -1
+        } else {
+          self.finishQuiz()
+        }
+      }, 400)
+    },
+    finishQuiz: function() {
+      this.score = this.scale === 'phq9' ? this.correctCount : this.correctCount
+      this.showResult = true
+    },
+    submitScore: function() {
+      if (this.submitting) return
+      if (!this.memberId) {
+        uni.showToast({ title: '未获取到成员信息', icon: 'none' })
+        return
+      }
+      this.submitting = true
+      var dimension = this.scale === 'phq9' ? 'phq9_score' : 'reses_score'
+      submitCognitiveSelfTest(this.memberId, dimension, this.score)
+        .then(function(res) {
+          if (res && res.code === 200) {
+            uni.showToast({ title: '提交成功', icon: 'success' })
+            setTimeout(function() {
+              uni.navigateBack()
+            }, 800)
+          }
+        })
+        .catch(function() {
+          uni.showToast({ title: '提交失败', icon: 'none' })
+        })
+        .finally(function() {
+          this.submitting = false
+        }.bind(this))
+    },
+    restartQuiz: function() {
+      this.showResult = false
+      this.currentQ = 0
+      this.selectedIndex = -1
+      this.correctCount = 0
+      this.score = 0
+    }
+  }
+}
+</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;
+}
+
+/* 介绍页 */
+.intro {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 100%;
+}
+
+.intro-icon {
+  width: 160rpx;
+  height: 160rpx;
+  border-radius: 50%;
+  background: rgba(99,102,241,0.15);
+  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: #fff;
+  margin-bottom: 16rpx;
+}
+
+.intro-desc {
+  font-size: 28rpx;
+  color: rgba(255,255,255,0.7);
+  line-height: 1.6;
+  text-align: center;
+  margin-bottom: 40rpx;
+  padding: 0 20rpx;
+}
+
+.intro-info {
+  width: 100%;
+  background: rgba(255,255,255,0.06);
+  border-radius: 20rpx;
+  padding: 20rpx 30rpx;
+  margin-bottom: 60rpx;
+}
+
+.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: 26rpx;
+  color: #6366f1;
+  font-weight: bold;
+}
+
+/* 答题页 */
+.quiz {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.quiz-header {
+  font-size: 30rpx;
+  color: #fff;
+  font-weight: bold;
+  margin-bottom: 16rpx;
+}
+
+.progress-track {
+  width: 100%;
+  max-width: 620rpx;
+  height: 12rpx;
+  background: rgba(255,255,255,0.1);
+  border-radius: 6rpx;
+  overflow: hidden;
+  margin-bottom: 50rpx;
+}
+
+.progress-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #6366f1, #8b5cf6);
+  border-radius: 6rpx;
+  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: 32rpx;
+  color: #fff;
+  font-weight: bold;
+  text-align: center;
+  display: block;
+  margin-bottom: 40rpx;
+  line-height: 1.5;
+}
+
+.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: #6366f1;
+  background: rgba(99,102,241,0.12);
+}
+
+.option:active {
+  transform: scale(0.98);
+}
+
+.option-text {
+  font-size: 28rpx;
+  color: #fff;
+  font-weight: 500;
+}
+
+.option-selected .option-text {
+  color: #a5b4fc;
+}
+
+/* 结果页 */
+.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, #6366f1, #8b5cf6);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 8rpx 40rpx rgba(99,102,241,0.35);
+}
+
+.score-num {
+  font-size: 80rpx;
+  font-weight: bold;
+  color: #fff;
+  line-height: 1;
+}
+
+.score-unit {
+  font-size: 28rpx;
+  color: rgba(255,255,255,0.8);
+  margin-top: 8rpx;
+}
+
+.score-comment {
+  font-size: 30rpx;
+  color: #a5b4fc;
+  margin-top: 30rpx;
+  margin-bottom: 50rpx;
+}
+
+.result-info {
+  width: 100%;
+  max-width: 620rpx;
+  background: rgba(255,255,255,0.06);
+  border-radius: 20rpx;
+  padding: 20rpx 30rpx;
+  margin-bottom: 60rpx;
+}
+
+.result-info .info-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16rpx 0;
+}
+
+.result-info .info-row + .info-row {
+  border-top: 1rpx solid rgba(255,255,255,0.08);
+}
+
+.dim-tag {
+  padding: 6rpx 24rpx;
+  border-radius: 24rpx;
+  border: 3rpx solid #6366f1;
+  background: rgba(99,102,241,0.12);
+}
+
+.dim-tag-text {
+  font-size: 24rpx;
+  color: #a5b4fc;
+  font-weight: bold;
+}
+
+/* 按钮 */
+.btn-primary {
+  margin-top: 60rpx;
+  width: 480rpx;
+  height: 96rpx;
+  line-height: 96rpx;
+  border-radius: 48rpx;
+  background: linear-gradient(135deg, #6366f1, #8b5cf6);
+  color: #fff;
+  font-size: 34rpx;
+  font-weight: 600;
+  border: none;
+  box-shadow: 0 8rpx 24rpx rgba(99,102,241,0.35);
+}
+
+.btn-primary::after {
+  border: none;
+}
+
+.btn-primary[disabled] {
+  opacity: 0.6;
+}
+
+.btn-secondary {
+  margin-top: 30rpx;
+  width: 480rpx;
+  height: 96rpx;
+  line-height: 96rpx;
+  border-radius: 48rpx;
+  background: transparent;
+  color: #a5b4fc;
+  font-size: 32rpx;
+  border: 2rpx solid rgba(99,102,241,0.5);
+}
+
+.btn-secondary::after {
+  border: none;
+}
+</style>