Parcourir la source

feat(dan): 重设计 DAN 报告详情页为测评报告风格

- Hero 头部:紫蓝渐变、姓名/维度徽章/日期、总分+百分位大数字与进度条
- 核心能力:认知分项横向进度条,低于50%橙色警示(如注意力49%)
- 大五人格:1-5分进度条+同龄平均线标记,突出优势/偏低提示
- 情绪状态、基本信息(身高体重)独立卡片
- 成长建议长文本折叠展开;过滤联系电话/报告编码等OCR噪音项
- 通用适配其他维度 indicator 块
Xiaogang Liao il y a 1 semaine
Parent
commit
148cfdfbcf
1 fichiers modifiés avec 520 ajouts et 43 suppressions
  1. 520 43
      cfc-frontend/pages/dan-assessment/report-result.vue

+ 520 - 43
cfc-frontend/pages/dan-assessment/report-result.vue

@@ -1,23 +1,170 @@
 <template>
   <view class="container">
-    <!-- 报告头部 -->
-    <view class="report-head" v-if="headInfo">
-      <text class="head-name">{{ headInfo.personName || headInfo.childName || 'DAN 测评报告' }}</text>
-      <text class="head-date">{{ headInfo.dateText }}</text>
-      <text class="head-dim" v-if="dimensionLabel">{{ dimensionLabel }}</text>
-    </view>
+    <!-- ==================== Hero 头部 ==================== -->
+    <view class="hero">
+      <view class="hero-bg-circle hero-bg-circle-1"></view>
+      <view class="hero-bg-circle hero-bg-circle-2"></view>
+
+      <view class="hero-top">
+        <text class="hero-name">{{ childName }}</text>
+        <view class="hero-dim-row">
+          <text class="hero-dim-badge">{{ dimensionLabel }}</text>
+          <text class="hero-date">{{ dateText }}</text>
+        </view>
+      </view>
 
-    <view class="loading-wrap" v-if="loading">
-      <text class="loading-text">加载中...</text>
+      <!-- 总分区 -->
+      <view class="hero-score-wrap" v-if="totalScore !== null">
+        <view class="hero-score-col">
+          <text class="hero-score-num">{{ totalScore }}</text>
+          <text class="hero-score-label">总得分</text>
+        </view>
+        <view class="hero-score-divider"></view>
+        <view class="hero-score-col">
+          <text class="hero-score-num hero-score-num-pct">{{ percentile }}<text class="hero-score-pct-unit">%</text></text>
+          <text class="hero-score-label">百分位</text>
+        </view>
+      </view>
+
+      <!-- 百分位进度条:位置标记 -->
+      <view class="hero-pct-bar-wrap" v-if="totalScore !== null">
+        <view class="hero-pct-track">
+          <view class="hero-pct-fill" :style="'width:' + percentile + '%'"></view>
+          <view class="hero-pct-marker" :style="'left:' + percentile + '%'"></view>
+        </view>
+        <view class="hero-pct-foot">
+          <text class="hero-pct-foot-text">超过 {{ percentile }}% 的同龄人</text>
+        </view>
+      </view>
     </view>
 
-    <template v-else-if="blocks && blocks.length > 0">
-      <!-- 报告内容块(后端按类型组装,前端通用渲染) -->
-      <report-blocks-renderer :blocks="blocks" />
-    </template>
+    <view class="body">
+      <view class="loading-wrap" v-if="loading">
+        <text class="loading-text">加载中...</text>
+      </view>
+
+      <template v-else-if="hasContent">
+        <!-- ==================== 核心能力(认知类指标) ==================== -->
+        <view class="card" v-if="abilityBlock">
+          <view class="card-title-row">
+            <view class="card-title-bar"></view>
+            <text class="card-title">{{ abilityBlock.title }}</text>
+          </view>
+          <view class="metric-row" v-for="(m, mi) in abilityMetrics" :key="mi">
+            <view class="metric-head">
+              <text class="metric-name">{{ m.name }}</text>
+              <view class="metric-value-wrap">
+                <text class="metric-warn-badge" v-if="m.warn">需关注</text>
+                <text class="metric-value" :class="m.warn ? 'metric-value-warn' : ''">{{ m.value }}<text class="metric-unit">%</text></text>
+              </view>
+            </view>
+            <view class="metric-track">
+              <view class="metric-fill" :class="m.warn ? 'metric-fill-warn' : 'metric-fill-main'"
+                    :style="'width:' + m.pct + '%'"></view>
+              <!-- 50% 参考线 -->
+              <view class="metric-ref-line" style="left:50%"></view>
+            </view>
+          </view>
+        </view>
+
+        <!-- ==================== 大五人格 ==================== -->
+        <view class="card" v-if="bigFiveBlock">
+          <view class="card-title-row">
+            <view class="card-title-bar"></view>
+            <text class="card-title">{{ bigFiveBlock.title }}</text>
+            <text class="card-title-sub">1 ~ 5 分,3 分为同龄平均</text>
+          </view>
+          <view class="bf-row" v-for="(m, mi) in bigFiveMetrics" :key="mi">
+            <view class="bf-head">
+              <text class="bf-name">{{ m.name }}</text>
+              <text class="bf-value">{{ m.value }}</text>
+            </view>
+            <view class="bf-track">
+              <view class="bf-fill" :class="m.tone" :style="'width:' + m.pct + '%'"></view>
+              <!-- 3分平均线 -->
+              <view class="bf-avg-line"></view>
+            </view>
+            <text class="bf-hint" v-if="m.hint">{{ m.hint }}</text>
+          </view>
+        </view>
+
+        <!-- ==================== 情绪状态 ==================== -->
+        <view class="card" v-if="emotionBlock">
+          <view class="card-title-row">
+            <view class="card-title-bar"></view>
+            <text class="card-title">{{ emotionBlock.title }}</text>
+          </view>
+          <view class="emo-row" v-for="(m, mi) in emotionMetrics" :key="mi">
+            <view class="emo-chip">
+              <text class="emo-name">{{ m.name }}</text>
+              <text class="emo-value">{{ m.value }}</text>
+            </view>
+            <view class="emo-track">
+              <view class="emo-fill" :style="'width:' + m.pct + '%'"></view>
+            </view>
+          </view>
+        </view>
+
+        <!-- ==================== 其他指标(身高体重等,过滤噪音) ==================== -->
+        <view class="card" v-if="otherMetrics.length > 0">
+          <view class="card-title-row">
+            <view class="card-title-bar"></view>
+            <text class="card-title">基本信息</text>
+          </view>
+          <view class="info-grid">
+            <view class="info-cell" v-for="(m, mi) in otherMetrics" :key="mi">
+              <text class="info-value">{{ m.value }}<text class="info-unit" v-if="m.unit">{{ m.unit }}</text></text>
+              <text class="info-name">{{ m.name }}</text>
+            </view>
+          </view>
+        </view>
+
+        <!-- ==================== 通用指标块(其他未分类维度,如执行功能/学习动机等) ==================== -->
+        <view class="card" v-for="(blk, bi) in genericBlocks" :key="'g' + bi">
+          <view class="card-title-row">
+            <view class="card-title-bar"></view>
+            <text class="card-title">{{ blk.title }}</text>
+          </view>
+          <view class="metric-row" v-for="(m, mi) in blk.metrics" :key="mi">
+            <view class="metric-head">
+              <text class="metric-name">{{ m.name }}</text>
+              <text class="metric-value">{{ m.value }}</text>
+            </view>
+            <view class="metric-track" v-if="m.pct !== null">
+              <view class="metric-fill metric-fill-main" :style="'width:' + m.pct + '%'"></view>
+            </view>
+          </view>
+        </view>
 
-    <view class="empty-wrap" v-else>
-      <text class="empty-text">暂无数据</text>
+        <!-- ==================== 成长建议(长文本折叠) ==================== -->
+        <view class="card" v-if="adviceText">
+          <view class="card-title-row">
+            <view class="card-title-bar card-title-bar-gold"></view>
+            <text class="card-title">{{ adviceTitle }}</text>
+          </view>
+          <text class="advice-content" :class="!adviceExpanded ? 'advice-clamped' : ''">{{ adviceText }}</text>
+          <view class="advice-toggle" @click="adviceExpanded = !adviceExpanded" v-if="adviceText.length > 160">
+            <text class="advice-toggle-text">{{ adviceExpanded ? '收起 ▲' : '展开全部 ▼' }}</text>
+          </view>
+        </view>
+
+        <!-- ==================== 报告评语 ==================== -->
+        <view class="card" v-if="summaryText">
+          <view class="card-title-row">
+            <view class="card-title-bar"></view>
+            <text class="card-title">报告评语</text>
+          </view>
+          <text class="advice-content">{{ summaryText }}</text>
+        </view>
+
+        <view class="foot-note">
+          <text class="foot-note-text">本报告由 DAN 测评系统生成,结果仅供成长参考</text>
+        </view>
+      </template>
+
+      <view class="empty-wrap" v-else>
+        <text class="empty-text">暂无数据</text>
+      </view>
     </view>
 
     <view class="bottom-spacer"></view>
@@ -25,33 +172,173 @@
 </template>
 
 <script>
-import ReportBlocksRenderer from '../../components/report-blocks-renderer.vue'
 import { getDanReportDetail } from '../../utils/api.js'
 
+// 噪音指标过滤(OCR 带出的非测评数据)
+var NOISE_NAME_KEYS = ['联系电话', '报告编码', '测试时间', '档案', '具体情况', '页', '公众号']
+
 export default {
-  components: { ReportBlocksRenderer },
   data() {
     return {
       reportId: null,
       loading: true,
-      headInfo: null,
-      blocks: []
+      childName: '',
+      dateText: '',
+      dimension: '',
+      reportType: '',
+      totalScore: null,
+      percentile: 0,
+      blocks: [],
+      adviceExpanded: false,
+      adviceSourceTitle: '',
+      adviceSourceText: '',
+      summarySourceText: ''
     }
   },
   computed: {
+    hasContent() {
+      return this.blocks.length > 0
+    },
     dimensionLabel() {
-      if (!this.headInfo || !this.headInfo.dimension) return ''
       var map = {
-        mind: '心维度 (A2)',
-        wisdom: '智维度 (B4)',
-        cognition: '认知维度 (A1)',
-        learning: '学习能力 (B3)',
-        behavior: '行为教养 (B2)',
-        career: '职业发展 (B6)',
-        campus: '校园标准 (C1)',
-        adolescent: '青春期挑战 (B5)'
+        mind: '心维度',
+        wisdom: '智维度',
+        cognition: '认知维度',
+        learning: '学习能力',
+        behavior: '行为教养',
+        career: '职业发展',
+        campus: '校园标准',
+        adolescent: '青春期挑战'
       }
-      return map[this.headInfo.dimension] || ''
+      var base = map[this.dimension] || ''
+      if (this.reportType) base += ' · ' + this.reportType
+      return base || 'DAN 测评'
+    },
+    // 认知能力/核心能力块(标题含"能力"或"认知")
+    abilityBlock() {
+      for (var i = 0; i < this.blocks.length; i++) {
+        var b = this.blocks[i]
+        if (b.type === 'indicator' && b.items && b.items.length &&
+            (String(b.title).indexOf('认知') >= 0 || String(b.title).indexOf('能力') >= 0)) {
+          return b
+        }
+      }
+      return null
+    },
+    abilityMetrics() {
+      if (!this.abilityBlock) return []
+      var self = this
+      return (this.abilityBlock.items || []).filter(function(it) {
+        return !self.isNoise(it.name)
+      }).map(function(it) {
+        var v = parseFloat(it.value)
+        var pct = isNaN(v) ? 0 : Math.min(100, Math.max(0, v))
+        return {
+          name: it.name.replace('百分位', ''),
+          value: it.value,
+          pct: pct,
+          warn: !isNaN(v) && v < 50
+        }
+      })
+    },
+    // 大五人格块
+    bigFiveBlock() {
+      for (var i = 0; i < this.blocks.length; i++) {
+        var b = this.blocks[i]
+        if (b.type === 'indicator' && b.title && String(b.title).indexOf('人格') >= 0) {
+          return b
+        }
+      }
+      return null
+    },
+    bigFiveMetrics() {
+      if (!this.bigFiveBlock) return []
+      return (this.bigFiveBlock.items || []).map(function(it) {
+        var v = parseFloat(it.value)
+        var pct = isNaN(v) ? 0 : Math.min(100, Math.max(0, v / 5 * 100))
+        var tone = 'bf-neutral'
+        var hint = ''
+        if (!isNaN(v)) {
+          if (v >= 4) { tone = 'bf-high'; hint = '突出优势' }
+          else if (v <= 2.5) { tone = 'bf-low'; hint = '低于同龄平均' }
+        }
+        return { name: it.name, value: it.value, pct: pct, tone: tone, hint: hint }
+      })
+    },
+    // 情绪状态块
+    emotionBlock() {
+      for (var i = 0; i < this.blocks.length; i++) {
+        var b = this.blocks[i]
+        if (b.type === 'indicator' && b.title && String(b.title).indexOf('情绪') >= 0) {
+          return b
+        }
+      }
+      return null
+    },
+    emotionMetrics() {
+      if (!this.emotionBlock) return []
+      return (this.emotionBlock.items || []).filter(function(it) {
+        return NOISE_NAME_KEYS.every(function(k) { return String(it.name).indexOf(k) < 0 })
+      }).map(function(it) {
+        var v = parseFloat(it.value)
+        var pct = isNaN(v) ? 0 : Math.min(100, Math.max(0, v * 10))
+        return { name: it.name, value: it.value, pct: pct }
+      })
+    },
+    // 其他指标("其他"类,过滤噪音,提取身高体重等)
+    otherMetrics() {
+      for (var i = 0; i < this.blocks.length; i++) {
+        var b = this.blocks[i]
+        if (b.type === 'indicator' && b.title && String(b.title).indexOf('其他') >= 0) {
+          var self = this
+          return (b.items || []).filter(function(it) {
+            return !self.isNoise(it.name)
+          }).map(function(it) {
+            var unit = ''
+            var name = it.name
+            if (name.indexOf('身高') >= 0) { unit = 'cm' }
+            if (name.indexOf('体重') >= 0) { unit = 'kg' }
+            return { name: name, value: it.value, unit: unit }
+          })
+        }
+      }
+      return []
+    },
+    // 通用指标块(除已专项渲染的 score/认知/人格/情绪/其他 之外的 indicator/text)
+    genericBlocks() {
+      var taken = []
+      if (this.abilityBlock) taken.push(this.abilityBlock)
+      if (this.bigFiveBlock) taken.push(this.bigFiveBlock)
+      if (this.emotionBlock) taken.push(this.emotionBlock)
+      var self = this
+      return this.blocks.filter(function(b) {
+        if (b.type !== 'indicator') return false
+        if (taken.indexOf(b) >= 0) return false
+        if (b.title && String(b.title).indexOf('其他') >= 0) return false
+        return b.items && b.items.length > 0
+      }).map(function(b) {
+        return {
+          title: b.title,
+          metrics: (b.items || []).filter(function(it) {
+            return !self.isNoise(it.name)
+          }).map(function(it) {
+            var v = parseFloat(it.value)
+            var pct = null
+            if (!isNaN(v) && v >= 0 && v <= 100) pct = v
+            return { name: it.name, value: it.value, pct: pct }
+          })
+        }
+      }).filter(function(b) { return b.metrics.length > 0 })
+    },
+    // 建议/评语(text 块优先,其次 result.growthSuggestions)
+    adviceTitle() {
+      return this.adviceSourceTitle || '成长建议'
+    },
+    adviceText() {
+      return this.adviceSourceText || ''
+    },
+    summaryText() {
+      return this.summarySourceText || ''
     }
   },
   onLoad(options) {
@@ -59,6 +346,13 @@ export default {
     if (this.reportId) this.loadData()
   },
   methods: {
+    isNoise(name) {
+      if (!name) return true
+      for (var i = 0; i < NOISE_NAME_KEYS.length; i++) {
+        if (String(name).indexOf(NOISE_NAME_KEYS[i]) >= 0) return true
+      }
+      return false
+    },
     loadData() {
       var self = this
       this.loading = true
@@ -69,12 +363,47 @@ export default {
           return
         }
         var data = res.data
-        self.headInfo = {
-          personName: (data.result && data.result.childName) || data.childName || '',
-          dateText: data.confirmedAt ? data.confirmedAt.substring(0, 10) : '',
-          dimension: data.dimension
+        var result = data.result || {}
+        self.childName = result.childName || data.childName || ''
+        self.dateText = data.confirmedAt ? String(data.confirmedAt).substring(0, 10) : ''
+        self.dimension = data.dimension || ''
+        self.reportType = data.reportType || ''
+        self.blocks = (data.blocks || []).filter(function(b) {
+          return b && ((b.items && b.items.length) || b.content)
+        })
+        // 提取总分/百分位(score 块)
+        for (var i = 0; i < self.blocks.length; i++) {
+          var b = self.blocks[i]
+          if (b.type === 'score' && b.items) {
+            for (var j = 0; j < b.items.length; j++) {
+              var it = b.items[j]
+              if (String(it.label).indexOf('总') >= 0) {
+                self.totalScore = parseFloat(it.value)
+                if (isNaN(self.totalScore)) self.totalScore = null
+              } else if (String(it.label).indexOf('百分位') >= 0) {
+                var p = parseFloat(it.value)
+                self.percentile = isNaN(p) ? 0 : Math.min(100, Math.max(0, p))
+              }
+            }
+          }
+        }
+        // score 块本身不再单独渲染(已并入 Hero)
+        // text 块:成长建议优先(非空 content),评语单独
+        for (var k = 0; k < self.blocks.length; k++) {
+          var tb = self.blocks[k]
+          if (tb.type === 'text' && tb.content) {
+            if (String(tb.title).indexOf('建议') >= 0) {
+              self.adviceSourceTitle = tb.title
+              self.adviceSourceText = tb.content
+            } else {
+              self.summarySourceText = tb.content
+            }
+          }
+        }
+        // 兜底:无 text 建议块时用 result.growthSuggestions
+        if (!self.adviceSourceText && result.growthSuggestions) {
+          self.adviceSourceText = result.growthSuggestions
         }
-        self.blocks = (data.blocks || []).filter(function(b) { return b && (b.items && b.items.length || b.content) })
       }).catch(function(e) {
         self.loading = false
         uni.showToast({ title: '加载异常', icon: 'none' })
@@ -85,16 +414,164 @@ export default {
 </script>
 
 <style scoped>
-.container { min-height: 100vh; background: #F8F8F8; padding-bottom: 40rpx; }
-.loading-wrap { display: flex; justify-content: center; padding: 120rpx 0; }
-.loading-text { font-size: 28rpx; color: #999; }
-.empty-wrap { display: flex; justify-content: center; padding: 120rpx 0; }
-.empty-text { font-size: 28rpx; color: #999; }
+.container { min-height: 100vh; background: #F3F2FA; padding-bottom: 40rpx; }
+
+/* ==================== Hero ==================== */
+.hero {
+  position: relative;
+  background: linear-gradient(160deg, #4F46E5 0%, #7C3AED 55%, #8B5CF6 100%);
+  padding: 48rpx 36rpx 44rpx;
+  overflow: hidden;
+  border-bottom-left-radius: 36rpx;
+  border-bottom-right-radius: 36rpx;
+}
+.hero-bg-circle { position: absolute; border-radius: 50%; background: rgba(255,255,255,0.06); }
+.hero-bg-circle-1 { width: 380rpx; height: 380rpx; right: -120rpx; top: -140rpx; }
+.hero-bg-circle-2 { width: 220rpx; height: 220rpx; left: -80rpx; bottom: -60rpx; }
+
+.hero-top { position: relative; z-index: 1; }
+.hero-name { font-size: 44rpx; font-weight: 700; color: #FFFFFF; display: block; }
+.hero-dim-row { display: flex; align-items: center; margin-top: 14rpx; }
+.hero-dim-badge {
+  font-size: 22rpx; color: #FFFFFF;
+  background: rgba(255,255,255,0.22);
+  border-radius: 24rpx; padding: 6rpx 18rpx;
+  margin-right: 16rpx;
+}
+.hero-date { font-size: 24rpx; color: rgba(255,255,255,0.75); }
+
+.hero-score-wrap {
+  position: relative; z-index: 1;
+  display: flex; align-items: center; justify-content: center;
+  margin-top: 36rpx;
+}
+.hero-score-col { flex: 1; display: flex; flex-direction: column; align-items: center; }
+.hero-score-divider { width: 1rpx; height: 72rpx; background: rgba(255,255,255,0.25); }
+.hero-score-num { font-size: 88rpx; font-weight: 700; color: #FFFFFF; line-height: 1; }
+.hero-score-num-pct { font-size: 72rpx; }
+.hero-score-pct-unit { font-size: 32rpx; font-weight: 400; margin-left: 2rpx; }
+.hero-score-label { font-size: 24rpx; color: rgba(255,255,255,0.75); margin-top: 12rpx; }
+
+.hero-pct-bar-wrap { position: relative; z-index: 1; margin-top: 32rpx; }
+.hero-pct-track {
+  position: relative; height: 14rpx; border-radius: 8rpx;
+  background: rgba(255,255,255,0.18);
+}
+.hero-pct-fill {
+  height: 100%; border-radius: 8rpx;
+  background: linear-gradient(90deg, #A5F3FC, #67E8F9, #FDE68A);
+}
+.hero-pct-marker {
+  position: absolute; top: -6rpx; width: 4rpx; height: 26rpx;
+  background: #FFFFFF; border-radius: 2rpx; transform: translateX(-2rpx);
+}
+.hero-pct-foot { margin-top: 12rpx; text-align: center; }
+.hero-pct-foot-text { font-size: 22rpx; color: rgba(255,255,255,0.85); }
+
+/* ==================== 通用卡片 ==================== */
+.body { padding: 28rpx 24rpx 0; }
+.card {
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 28rpx 26rpx;
+  margin-bottom: 22rpx;
+  box-shadow: 0 4rpx 16rpx rgba(99,102,241,0.06);
+}
+.card-title-row { display: flex; align-items: baseline; margin-bottom: 24rpx; }
+.card-title-bar {
+  width: 8rpx; height: 30rpx; border-radius: 4rpx;
+  background: linear-gradient(180deg, #6366F1, #8B5CF6);
+  margin-right: 12rpx; align-self: center;
+}
+.card-title-bar-gold { background: linear-gradient(180deg, #F59E0B, #F97316); }
+.card-title { font-size: 30rpx; font-weight: 600; color: #1F2937; }
+.card-title-sub { font-size: 22rpx; color: #9CA3AF; margin-left: 14rpx; }
 
-.report-head { background: linear-gradient(135deg, #6366F1, #8B5CF6); padding: 32rpx 28rpx 28rpx; color: #fff; }
-.head-name { font-size: 36rpx; font-weight: bold; display: block; }
-.head-date { font-size: 24rpx; opacity: 0.85; display: block; margin-top: 8rpx; }
-.head-dim { font-size: 22rpx; background: rgba(255,255,255,0.2); border-radius: 20rpx; padding: 4rpx 16rpx; margin-top: 12rpx; display: inline-block; }
+/* ==================== 核心能力指标 ==================== */
+.metric-row { margin-bottom: 26rpx; }
+.metric-row:last-child { margin-bottom: 4rpx; }
+.metric-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12rpx; }
+.metric-name { font-size: 26rpx; color: #374151; font-weight: 500; }
+.metric-value-wrap { display: flex; align-items: center; }
+.metric-value { font-size: 30rpx; font-weight: 700; color: #4F46E5; }
+.metric-value-warn { color: #EA580C; }
+.metric-unit { font-size: 20rpx; font-weight: 400; }
+.metric-warn-badge {
+  font-size: 20rpx; color: #EA580C;
+  background: #FFF7ED; border: 1rpx solid #FDBA74;
+  border-radius: 16rpx; padding: 2rpx 12rpx; margin-right: 10rpx;
+}
+.metric-track {
+  position: relative; height: 16rpx; border-radius: 8rpx; background: #EEF0F6;
+}
+.metric-fill { height: 100%; border-radius: 8rpx; }
+.metric-fill-main { background: linear-gradient(90deg, #818CF8, #6366F1); }
+.metric-fill-warn { background: linear-gradient(90deg, #FDBA74, #EA580C); }
+.metric-ref-line {
+  position: absolute; top: -4rpx; width: 2rpx; height: 24rpx;
+  background: #C7C9D9; border-radius: 1rpx;
+}
+
+/* ==================== 大五人格 ==================== */
+.bf-row { margin-bottom: 24rpx; }
+.bf-row:last-child { margin-bottom: 4rpx; }
+.bf-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 10rpx; }
+.bf-name { font-size: 26rpx; color: #374151; font-weight: 500; }
+.bf-value { font-size: 30rpx; font-weight: 700; color: #7C3AED; }
+.bf-track { position: relative; height: 14rpx; border-radius: 7rpx; background: #F0ECFA; }
+.bf-fill { height: 100%; border-radius: 7rpx; }
+.bf-neutral { background: linear-gradient(90deg, #C4B5FD, #8B5CF6); }
+.bf-high { background: linear-gradient(90deg, #6EE7B7, #10B981); }
+.bf-low { background: linear-gradient(90deg, #FCD34D, #F59E0B); }
+.bf-avg-line {
+  position: absolute; top: -5rpx; left: 60%; width: 2rpx; height: 24rpx;
+  background: #9CA3AF; border-radius: 1rpx;
+}
+.bf-hint { display: block; font-size: 20rpx; color: #9CA3AF; margin-top: 6rpx; }
+
+/* ==================== 情绪状态 ==================== */
+.emo-row { display: flex; align-items: center; margin-bottom: 20rpx; }
+.emo-row:last-child { margin-bottom: 4rpx; }
+.emo-chip {
+  display: flex; align-items: baseline;
+  background: #F5F3FF; border-radius: 14rpx;
+  padding: 10rpx 18rpx; margin-right: 18rpx; flex-shrink: 0;
+}
+.emo-name { font-size: 24rpx; color: #6D28D9; margin-right: 12rpx; }
+.emo-value { font-size: 28rpx; font-weight: 700; color: #7C3AED; }
+.emo-track { flex: 1; height: 12rpx; border-radius: 6rpx; background: #F0ECFA; overflow: hidden; }
+.emo-fill { height: 100%; border-radius: 6rpx; background: linear-gradient(90deg, #A78BFA, #7C3AED); }
 
-.bottom-spacer { height: 40rpx; }
+/* ==================== 基本信息 ==================== */
+.info-grid { display: flex; flex-wrap: wrap; }
+.info-cell {
+  width: 33.33%; display: flex; flex-direction: column; align-items: center;
+  margin-bottom: 16rpx;
+}
+.info-value { font-size: 36rpx; font-weight: 700; color: #1F2937; }
+.info-unit { font-size: 20rpx; font-weight: 400; color: #9CA3AF; margin-left: 4rpx; }
+.info-name { font-size: 22rpx; color: #9CA3AF; margin-top: 6rpx; }
+
+/* ==================== 成长建议/评语 ==================== */
+.advice-content {
+  font-size: 26rpx; color: #4B5563; line-height: 1.8;
+  white-space: pre-wrap; word-break: break-all;
+}
+.advice-clamped {
+  max-height: 320rpx; overflow: hidden;
+  -webkit-mask-image: linear-gradient(180deg, #000 60%, transparent 100%);
+  mask-image: linear-gradient(180deg, #000 60%, transparent 100%);
+}
+.advice-toggle { text-align: center; margin-top: 16rpx; }
+.advice-toggle-text { font-size: 24rpx; color: #6366F1; font-weight: 500; }
+
+.foot-note { text-align: center; padding: 18rpx 0 8rpx; }
+.foot-note-text { font-size: 20rpx; color: #B0B3C3; }
+
+/* ==================== 其他 ==================== */
+.loading-wrap { display: flex; justify-content: center; padding: 140rpx 0; }
+.loading-text { font-size: 28rpx; color: #999; }
+.empty-wrap { display: flex; justify-content: center; padding: 140rpx 0; }
+.empty-text { font-size: 28rpx; color: #999; }
+.bottom-spacer { height: 60rpx; }
 </style>