Просмотр исходного кода

feat(dan): 补齐DanAssessmentResultDTO并新增DAN报告显示页面开发计划

- dto/DanAssessmentResultDTO: 补齐原缺失的认知六维(perception/spatial/processingSpeed/language)、
  EMI心理四维、大五OCEAN五维+综合、EMI情绪商数四维,及报告外壳字段
  dimension/danLevel/structuredAnalysis,成为完整结果视图
- service/TeacherService: toAssessmentResultDTO 同步补全上述字段赋值
- docs/design: 新增 DAN报告页面开发计划.md(8类报告开发里程碑/任务清单/技术约束/风险),
  连同既有设计规范与交互原型一并纳入版本控制
E2E Test Bot 1 неделя назад
Родитель
Сommit
0682fcf239

+ 47 - 13
cfc-backend/src/main/java/com/etotem/cfc/dto/DanAssessmentResultDTO.java

@@ -9,21 +9,55 @@ public class DanAssessmentResultDTO {
     private Long memberId;
     private Long teacherId;
     private Long appointmentId;
-    private Integer attentionScore;
-    private Integer focusScore;
-    private Integer memoryScore;
-    private Integer logicScore;
-    private Integer overallScore;
+
+    // === 认知六维(COG,百分位 0-100) ===
+    private Integer attentionScore;        // 注意力得分
+    private Integer focusScore;            // 专注力得分
+    private Integer memoryScore;           // 记忆力得分
+    private Integer logicScore;            // 逻辑思维得分
+    private Integer perceptionScore;       // 感知觉得分
+    private Integer spatialScore;          // 空间思维得分
+    private Integer processingSpeedScore;  // 加工速度得分
+    private Integer languageScore;         // 语言表达得分
+
+    // === EMI 心理子维度(0-100) ===
+    private Integer emotionScore;          // 情绪商数
+    private Integer resilienceScore;       // 心理韧性
+    private Integer stressCopingScore;     // 压力应对
+    private Integer selfAwarenessScore;    // 自我认知
+
+    // === 大五人格维度(Big Five / OCEAN,0-100) ===
+    private Integer opennessScore;         // 开放性
+    private Integer conscientiousnessScore; // 尽责性
+    private Integer extraversionScore;     // 外向性
+    private Integer agreeablenessScore;    // 宜人性
+    private Integer neuroticismScore;      // 神经质
+    private Integer bigFiveOverallScore;   // 大五人格综合分
+
+    // === EMI 情绪商数评估子维度(0-100) ===
+    private Integer emotionManagementScore;  // 情绪管理
+    private Integer empathyScore;            // 同理心
+    private Integer socialAdaptabilityScore; // 社会适应
+    private Integer selfMotivationScore;     // 自我激励
+
+    private Integer overallScore;          // 综合得分
+
     private Date assessmentDate;
-    private Integer durationMinutes;
-    private String gameRecords;
-    private String analysisReport;
-    private String growthSuggestions;
-    private Date nextReviewDate;
-    private String status;
+    private Integer durationMinutes;       // 测评时长
+    private String gameRecords;            // 游戏记录详情(JSON)
+    private String analysisReport;         // 分析报告
+    private String growthSuggestions;      // 成长建议
+    private Date nextReviewDate;           // 下次复测日期
+    private String status;                 // draft/completed/archived
+
+    // === 报告外壳字段(与 DanReportUpload 对齐,供 8 类报告页复用) ===
+    private String dimension;              // mind(心-A2)/wisdom(智-B4)/cognition(A1)/learning(B3)/behavior(B2)/career(B6)/campus(C1)/adolescent(B5)
+    private String danLevel;               // DAN 等级 A/B/C/D/U
+    private String structuredAnalysis;     // 结构化分析JSON(summary/dimensions/strengths/concerns/suggestions)
+
     private Date createdAt;
-    
-    // 关联信息
+
+    // === 关联信息 ===
     private String childName;
     private String childNickname;
     private String teacherName;

+ 26 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/TeacherService.java

@@ -292,6 +292,28 @@ public class TeacherService {
         dto.setFocusScore(result.getFocusScore());
         dto.setMemoryScore(result.getMemoryScore());
         dto.setLogicScore(result.getLogicScore());
+        // 认知六维补全(COG)
+        dto.setPerceptionScore(result.getPerceptionScore());
+        dto.setSpatialScore(result.getSpatialScore());
+        dto.setProcessingSpeedScore(result.getProcessingSpeedScore());
+        dto.setLanguageScore(result.getLanguageScore());
+        // EMI 心理子维度
+        dto.setEmotionScore(result.getEmotionScore());
+        dto.setResilienceScore(result.getResilienceScore());
+        dto.setStressCopingScore(result.getStressCopingScore());
+        dto.setSelfAwarenessScore(result.getSelfAwarenessScore());
+        // 大五人格维度(OCEAN)
+        dto.setOpennessScore(result.getOpennessScore());
+        dto.setConscientiousnessScore(result.getConscientiousnessScore());
+        dto.setExtraversionScore(result.getExtraversionScore());
+        dto.setAgreeablenessScore(result.getAgreeablenessScore());
+        dto.setNeuroticismScore(result.getNeuroticismScore());
+        dto.setBigFiveOverallScore(result.getBigFiveOverallScore());
+        // EMI 情绪商数子维度
+        dto.setEmotionManagementScore(result.getEmotionManagementScore());
+        dto.setEmpathyScore(result.getEmpathyScore());
+        dto.setSocialAdaptabilityScore(result.getSocialAdaptabilityScore());
+        dto.setSelfMotivationScore(result.getSelfMotivationScore());
         dto.setOverallScore(result.getOverallScore());
         dto.setAssessmentDate(result.getAssessmentDate());
         dto.setDurationMinutes(result.getDurationMinutes());
@@ -300,6 +322,10 @@ public class TeacherService {
         dto.setGrowthSuggestions(result.getGrowthSuggestions());
         dto.setNextReviewDate(result.getNextReviewDate());
         dto.setStatus(result.getStatus());
+        // 报告外壳字段(对齐 DanReportUpload,供 8 类报告页复用)
+        dto.setDimension(result.getDimension());
+        dto.setDanLevel(result.getDanLevel());
+        dto.setStructuredAnalysis(result.getStructuredAnalysis());
         dto.setCreatedAt(result.getCreatedAt());
         
         // 补充关联信息

+ 374 - 0
docs/design/DAN报告显示页面设计.md

@@ -0,0 +1,374 @@
+# DAN 报告显示页面设计
+
+> 配套可交互原型:`docs/design/dan-report-pages.html`(左侧切换 8 类报告预览)
+
+---
+
+## 0. 结论先行
+
+### 0.1 口径纠正(易错点)
+
+文档里的「DAN 四大评估维度」与代码里的「报告类别」是**两套口径**,设计时不可混淆:
+
+| 口径 | 内容 | 用途 |
+|---|---|---|
+| 文档四大维度 | COG 认知 / EMI 情绪心理 / SOC 社会关系 / PHY 身体健康 | **能量沙盘积分**口径,不是报告类别 |
+| 代码报告类别 | `mind`/`wisdom`/`cognition`/`learning`/`behavior`/`career`/`campus`/`adolescent` | **本报告设计的对象**,共 8 类 |
+
+依据:`docs/需求分析/健康能量设计.md:737-744`(四大维度)vs `DanReportUpload.java:79-86`(8 个 `DIM_*` 常量)、`DanReportParseService.java:67-83`(8 路分发)。
+
+### 0.2 现状问题
+
+当前**只有 1 个通用页** `pages/dan-assessment/report-result.vue`(859 行),靠 `genericBlocks` + `resolveMax()` 量纲推断兜底渲染全部 8 类。专项渲染只覆盖 4 个区块(认知/大五/情绪/体质),其余 21 个 category 全部退化为扁平指标条 —— 8 类报告同质化严重,专业测评的差异化价值没有表达出来。
+
+### 0.3 本方案
+
+**统一外壳 + 每类专属区块序列**:
+
+- **外壳统一**:Hero(姓名 / 维度徽章 / 等级徽章 / 日期 / 综合分 + 百分位 / 百分位进度条)+ 卡片流 + 免责脚注 —— 保证品牌一致性
+- **区块按需**:8 类各自拥有固定的 category 序列(`ReportBlockAssembler.DAN_CATEGORY_TITLES` 决定顺序),每类按数据形态选配最合适的渲染器
+
+---
+
+## 1. 八类报告清单
+
+| # | dimension | reportType | 报告名 | 解析器 | API |
+|---|---|---|---|---|---|
+| 1 | `mind` | **A2** | 心维度(核心素养评估) | `parseA2Report` `:89` | `/api/dan-report/list` `dimension=mind` |
+| 2 | `wisdom` | **B4** | 智维度 | `parseB4Report` `:211` | 同上 |
+| 3 | `cognition` | **A1** | 儿童核心认知 | `parseA1Report` `:305` | 同上 |
+| 4 | `learning` | **B3** | 核心学习能力 | `parseB3Report` `:442` | 同上 |
+| 5 | `behavior` | **B2** | 儿童自我与家庭教养 | `parseB2Report` `:541` | 同上 |
+| 6 | `career` | **B6** | 职业发展 | `parseB6Report` `:660` | 同上 |
+| 7 | `campus` | **C1** | 校园标准版 | `parseC1Report` `:793` | 同上 |
+| 8 | `adolescent` | **B5** | 青春期挑战 | `parseB5Report` `:918` | 同上 |
+
+统一取数:`POST /api/dan-report/{id}` → `DanReportUploadVO`(含 `blocks` + `result`)。
+对比形态已存在:`POST /api/dan-report/compare` + `pages/dan-assessment/comparison.vue`。
+
+---
+
+## 2. 统一外壳(Report Shell)
+
+### 2.1 Hero
+
+```
+┌─────────────────────────────────────┐
+│ 陈乐乐                               │  姓名
+│ [心维度] [女 · 9岁] [等级 B] [日期]   │  徽章行
+│                                     │
+│  78分              72%              │  综合分 + 百分位(双大数字)
+│  综合得分          百分位            │
+│  ▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░               │  百分位进度条
+│  超过 72% 的同龄人                   │
+└─────────────────────────────────────┘
+```
+
+- 背景:`linear-gradient(160deg, #4C3F91, #6B5BD2 55%, #8B7BE8)`(沿用现有紫罗兰主题)
+- 等级徽章 `danLevel`(A/B/C/D/U)用暖色强调 `.bd-strong`
+- 装饰圆:`rgba(255,255,255,.06)` 双圆定位,不干扰文字
+
+### 2.2 卡片流与脚注
+
+- 卡片:白底 + `radius 16px` + 极浅阴影,`margin 11px 12px 0`
+- 卡片头:左侧标题(紫罗兰 `#5B4BA8`)+ 右侧 note(量纲/口径说明,灰)
+- 脚注:「本报告由 DAN 测评系统生成,结果仅供成长参考」
+
+---
+
+## 3. 区块渲染器注册表
+
+共 10 个渲染器,按 category 数据形态选配:
+
+| 渲染器 | 适用 category | 数据形态 | 视觉 |
+|---|---|---|---|
+| `radar` | cognitive / bigFive / selfConcept / selfDriving / executiveFunction / hollandInterest / socialAbility / multipleIntelligence | ≥3 个同质维度 | 雷达图 + 下方指标条明细 |
+| `bars` | 单组同质维度(<3 维或不宜雷达) | 一维数值 | 指标条 + 参考线 + 需关注徽章 |
+| `chips` | emotion | 0-15 分测验 | 紧凑条 + 分数 |
+| `grid` | body / 基本信息 | 带单位数值 | 三列数值格 + 习惯文本行 |
+| `matrix` | social(社会关系依恋) | 二维矩阵 | 行×列 + 迷你条 |
+| `duration` | sleep / exercise / internet | 时长类 | 时长卡 + 达标状态徽章 |
+| `compass` | compass(人际指南针) | 象限距离 | 四象限圆点图 |
+| `two` | 优势/待提升 | 两组文本 | 双色对照卡 |
+| `rec` | 职业方向推荐 | 标签集 | chip 组 |
+| `text` | 评语 / 成长建议 | 长文本 | >160 字折叠 + 遮罩渐变 |
+
+---
+
+## 4. 量纲映射表(**最大的设计陷阱**)
+
+不同 category 的量纲完全不同,直接统一按 0-100 渲染必然出错。现有 `report-result.vue:539-546` 的 `resolveMax()` 已实现推断,**新页面必须复用或显式声明**:
+
+| category | 量纲 | 参考线 | 阈值(需关注) |
+|---|---|---|---|
+| `overall` / `cognitive` | 百分位 0-100 | P50 | < 50 |
+| `bigFive` | 1-5 | — | ≤ 2.5(低)/ ≥ 4(高亮优势) |
+| `multipleIntelligence` | 1-15 | — | < 8 |
+| `learningMotivation` / `learningStrategy` | 0-10 | — | < 5 |
+| `selfConcept` / `behavior` / `family` / `selfDriving` / `socialAbility` | 0-10(或 1-5) | — | < 5 |
+| `emotion`(A2 分测验) | 0-15 | — | ≥ 6(反向:越高越需关注) |
+| `emotionRegulation` | 1-7 | — | 表达抑制偏高需关注 |
+| `academicStress` / `interpersonal` | 1-5 | — | ≥ 4(压力源反向) |
+| `sleep` / `exercise` / `internet` | 时长 | — | 年龄作息标准 |
+
+> **设计约定**:量纲必须在卡片右侧 note 里显式写出(如「1-5 分制」「0-15 分制,分数越高越需关注」),避免家长误读反向计分项。
+
+---
+
+## 5. 八类页面模板
+
+### 5.1 A2 · 心维度(mind)
+
+`overall` → `cognitive` → `bigFive` → `emotion` → `social` → `body` → `ability` → `text`
+
+```
+Hero(综合分 78 / 百分位 72 / 等级 B)
+认知能力     radar  [感知觉 82, 记忆力 74, 注意力 68]      百分位
+大五人格     radar  [开放性 4.2, 尽责性 3.8, 外向性 3.1, 宜人性 4.5, 神经质 2.4]  1-5
+情绪状态     chips  [自卑感 4, 抑郁 3, 焦虑 6, 无力感 3]    0-15
+社会关系     matrix 母亲/父亲/同伴 × 信任/沟通/亲近
+体质健康     grid   身高 132cm | 体重 28kg | BMI 16.1
+             habit 饮食 规律 / 睡眠 约9小时 / 运动 每周3次
+综合能力     bars   [语言表达 8.1, 逻辑思维 7.4, 创造力 7.8]
+成长建议     text   折叠
+```
+
+**设计要点**:A2 是唯一含「社会关系依恋矩阵」的报告,matrix 是为其专属设计的渲染器。情绪分测验为**反向计分**,需在 note 明示。
+
+### 5.2 B4 · 智维度(wisdom)
+
+`cognitive` → `selfConcept` → `growthMindset` → `selfDriving` → `text`
+
+```
+Hero
+认知能力      radar  [推理能力 88, 空间能力 76, 加工速度 71]
+自我概念      bars   [学业自我 7.6, 身体自我 7.1, 社会自我 8.0, 情绪自我 6.8]
+成长型思维    bars   [努力信念 8.2, 挑战接纳 7.3, 失败归因 6.4]
+自驱力        bars   [自主性 7.8, 胜任感 7.2, 坚持性 6.6]
+成长建议      text   折叠
+```
+
+### 5.3 A1 · 儿童核心认知(cognition)
+
+`overall` → `cognitive`(六维)→ `two` → `text`
+
+```
+Hero
+认知能力      radar  六维百分位(感知觉/注意力/记忆力/推理/空间/加工速度)
+认知能力      bars   六维明细 + P50 标记线
+优势与待提升  two    优势:推理88 感知觉85 记忆力80
+                    待提升:加工速度69 注意力72
+成长建议      text   折叠
+```
+
+**设计要点**:A1 是**纯认知报告**,六维雷达是页面视觉主体。六维明细条必须带 **P50 同龄中位标记线**(复用 `cognitive-report.vue` 的 `.norm-marker` 做法),否则家长无法判断高低。
+
+### 5.4 B3 · 核心学习能力(learning)
+
+`executiveFunction` → `learningMotivation` → `learningStrategy` → `text`
+
+```
+Hero
+执行功能      radar  [抑制控制 68, 工作记忆 74, 认知灵活性 71]  百分位
+学习动机      bars   [深层动机 7.2, 表面动机 5.4, 自我效能感 6.8]  十分制
+学习策略      bars   [深层方法 6.1, 表面方法 5.8, 自我调节 5.2]  十分制
+成长建议      text   折叠
+```
+
+**设计要点**:B3 的动机/策略是**十分制**(不是百分位),note 必须标注。深层 vs 表面动机成对出现,条相邻排布便于对比。
+
+### 5.5 B2 · 儿童自我与家庭教养(behavior)
+
+`selfConcept` → `behavior` → `family` → `text`
+
+```
+Hero
+自我概念      radar  六维(行为表现/能力与学校/躯体外貌/情绪状态/合群/幸福与满足)
+行为表现      bars   八维(品行/情绪/学习/社交/生活习惯/多动倾向/刻板行为/拖延行为)
+家庭环境      bars   十维(亲密/情感表达/矛盾/独立/成就/文化/娱乐/道德宗教/组织/控制)
+教养建议      text   折叠
+```
+
+**设计要点**:B2 的 `behavior` 中「多动倾向/刻板行为/拖延行为」为**反向计分**(分数越低越好),note 必须明示,否则家长会把低分误读为问题。8 维与 10 维项目多,用 bars 而非 radar 保证可读性。
+
+### 5.6 B6 · 职业发展(career)
+
+`hollandInterest` → `multipleIntelligence` → `careerValue` → `rec` → `text`
+
+```
+Hero
+职业兴趣      radar  Holland 六型(艺术/社会/企业/常规/现实/研究)  百分位
+多元智能      bars   八维 1-15 分制(内省12 空间10 音乐8 人际13 自然9 运动7 语言11 逻辑12)
+职业价值观    bars   [成就感 8.4, 助人 8.8, 独立性 7.2, 创造性 8.1]
+适配方向      rec    [教育/培训★] [心理咨询★] [内容创作] [科研] [公共关系]
+成长建议      text   折叠
+```
+
+**设计要点**:B6 是唯一产出**行动建议标签**的报告,`rec` 渲染器为其专属。Holland 六边形是职业测评的经典表达,用 radar 承载。多元智能 **1-15 分制**量纲特殊,必须显式声明。
+
+### 5.7 C1 · 校园标准版(campus)
+
+`cognitive` → `bigFive` → `selfDriving` → `selfConcept` → `text`
+
+```
+Hero
+认知能力      radar  六维百分位
+大五人格      radar  五维 1-5(开放性/宜人性/责任心/外倾性/神经质)
+自驱力        bars   三维(自主性 6.8 / 胜任感 7.2 / 归属感 7.6)
+自我概念      radar  六维 十分制
+校园适应建议  text   折叠
+```
+
+**设计要点**:C1 是 A1 + B2 + B4 的**校园场景组合包**,含 2 个雷达图,页面偏长,卡片顺序严格按 `DAN_CATEGORY_TITLES` 排列。
+
+### 5.8 B5 · 青春期挑战(adolescent)
+
+`emotionRegulation` → `academicStress` → `interpersonal` → `socialAbility` → 生活节律 → `compass` → `text`
+
+```
+Hero
+情绪调节      bars   [认知重评 5.2, 表达抑制 4.6]   1-7
+学业压力      bars   五源 1-5(学业负担3.4 家庭期望3.8 师生2.6 自我期望4.1 同伴竞争3.2)
+人际关系      bars   四维 1-5(母亲4.2 父亲3.9 师生3.4 同伴4.0)
+社交能力      radar  五维 1-5
+生活节律      duration [睡眠 7.2h 偏少] [运动 45min 达标] [网络 3.5h 偏多]
+人际指南针    compass  四象限(母亲/父亲/同伴/师生,距圆心越近关系越紧密)
+成长建议      text   折叠
+```
+
+**设计要点**:B5 是最特殊的一类 —— 含**唯一的三张时长卡**与**唯一的人际指南针**。`duration` 与 `compass` 是为其专属设计的渲染器。压力源、表达抑制为反向/需关注指标。
+
+---
+
+## 6. 落地指引
+
+### 6.1 目录与组件
+
+```
+cfc-frontend/
+├── components/dan-report/              # 新增:DAN 报告专用组件
+│   ├── DanReportHero.vue               # 统一 Hero(提取自现有 report-result.vue)
+│   ├── DanBlockRadar.vue               # radar 渲染器
+│   ├── DanBlockBars.vue                # bars 渲染器
+│   ├── DanBlockChips.vue               # chips 渲染器
+│   ├── DanBlockGrid.vue                # grid 渲染器
+│   ├── DanBlockMatrix.vue              # matrix 渲染器(A2 专属)
+│   ├── DanBlockDuration.vue            # duration 渲染器(B5 专属)
+│   ├── DanBlockCompass.vue             # compass 渲染器(B5 专属)
+│   └── danBlockRegistry.js             # category → 渲染器映射表
+├── pages/dan-assessment/
+│   ├── report-result.vue               # 保留:作为 8 类统一入口(按 dimension 分派)
+│   └── comparison.vue                  # 保留:对比报告
+```
+
+### 6.2 category → 渲染器映射表(`danBlockRegistry.js`)
+
+```js
+export const DAN_BLOCK_RENDERERS = {
+  overall:          'bars',
+  cognitive:        'radar',
+  bigFive:          'radar',
+  selfConcept:      'radar',
+  selfDriving:      'bars',
+  executiveFunction:'radar',
+  hollandInterest:  'radar',
+  multipleIntelligence:'bars',
+  socialAbility:    'radar',
+  learningMotivation:'bars',
+  learningStrategy: 'bars',
+  growthMindset:    'bars',
+  behavior:         'bars',
+  family:           'bars',
+  careerValue:      'bars',
+  emotion:          'chips',
+  emotionRegulation:'bars',
+  academicStress:   'bars',
+  interpersonal:    'bars',
+  social:           'matrix',
+  body:             'grid',
+  ability:          'bars',
+  sleep:            'duration',
+  exercise:         'duration',
+  internet:         'duration',
+  compass:          'compass'
+};
+
+// 量纲表(复用 resolveMax 的推断逻辑,显式化)
+export const DAN_CATEGORY_SCALE = {
+  overall:{max:100,ref:50}, cognitive:{max:100,ref:50},
+  bigFive:{max:5}, multipleIntelligence:{max:15},
+  learningMotivation:{max:10}, learningStrategy:{max:10},
+  selfConcept:{max:10}, behavior:{max:10}, family:{max:10},
+  selfDriving:{max:10}, socialAbility:{max:5},
+  emotion:{max:15,reverse:true},
+  emotionRegulation:{max:7}, academicStress:{max:5,reverse:true},
+  interpersonal:{max:5}
+};
+```
+
+### 6.3 API 映射
+
+| 用途 | API | 说明 |
+|---|---|---|
+| 报告详情 | `POST /api/dan-report/{id}` | 返回 `blocks`(已按 category 分组)+ `result` |
+| 成员报告列表 | `POST /api/dan-report/list` `{memberId, dimension}` | 8 类分别过滤 |
+| 成员某维度 | `POST /api/dan-assessment/report/child` `{memberId, dimension}` | 仅 `draftStatus=confirmed` |
+| 结构化分析 | `POST /api/dan-assessment/results/{id}/structured` | `{structuredAnalysis}` |
+| 两版对比 | `POST /api/dan-report/compare` `{uploadIdA, uploadIdB}` | 复用 `comparison.vue` |
+| 认知趋势 | `POST /api/cognitive/trend` | 目前仅认知维度有 |
+
+> 页面传参沿用现有约定:`/pages/dan-assessment/report-result?reportId=` 。
+
+### 6.4 小程序技术约束(必守)
+
+| 约束 | 做法 |
+|---|---|
+| 禁可选链 `?.` | 用 `a && a.b` |
+| 禁 CSS Grid | 用 flexbox(`.grid` 用 `flex-wrap` + `flex:0 0 calc(33.333% - 6px)`) |
+| 禁 `:key` 表达式 | 用方法调用 `getItemKey(item)` |
+| 禁 `new Date(string)` | 用 `utils/format.js` 的 `parseDate()` |
+| Vue 2 Options API | 禁 Composition API |
+| 禁 `:class` 内带参方法调用 | 用内联表达式 / 计算属性 |
+| 禁 `uni.getSystemInfoSync()` | 用 `uni.getWindowInfo()` |
+| 新页面须注册 `pages.json` | 现有两页已注册(root `pages/dan-assessment`) |
+| 同 URL 首次加载只允许一次请求 | 沿用 `_onLoadFired` 模式 |
+| 时间格式 | `yyyy-MM-dd`,禁 `toLocale*String` |
+
+图表沿用自研 canvas 组件 `components/RadarChart.vue` / `TrendChart.vue`(项目**无** echarts / uCharts)。
+
+### 6.5 提交前自检
+
+```bash
+node scripts/verify-vue-compile.js
+node scripts/audit-mp-antipatterns.js     # ERROR 归零
+node scripts/audit-duplicate-api-calls.js
+```
+
+---
+
+## 7. 与现有实现的关系
+
+| 项 | 现状 | 本方案 |
+|---|---|---|
+| 页面数 | 1 个通用页兜底 8 类 | 保留 1 个入口页 + 10 个区块组件,按 dimension 分派渲染器 |
+| 专项渲染 | 仅 4 个区块(认知/大五/情绪/体质) | 全部 25 个 category 均有对应渲染器 |
+| 量纲 | `resolveMax()` 运行时推断 | 显式 `DAN_CATEGORY_SCALE` 表,可维护可校验 |
+| 视觉差异 | 8 类几乎同质 | 每类有专属主体(A1 六维雷达 / B6 Holland 六边形 / B5 时长卡+指南针 / A2 关系矩阵) |
+
+**建议改造路径**(非破坏式):
+
+1. 先抽取 `DanReportHero.vue` + 10 个区块组件 + 注册表
+2. `report-result.vue` 改为「按 `dimension` 查注册表 → 分派区块组件」,保留 `genericBlocks` 作为兜底
+3. 数据缺失/解析失败时,区块自动降级为 `bars` 通用条,不白屏
+4. 逐类验证(建议顺序:A1 → A2 → B5 → B6,数据形态最典型)
+
+---
+
+## 8. 遗留待确认项
+
+| 项 | 状态 |
+|---|---|
+| `DanAssessmentResultDTO` 为残缺 DTO(仅 attention/focus/memory/logic/overall,缺 perception/spatial/EMI) | 新页面若走该 DTO 需先补齐,或直接用实体 `DanAssessmentResult` |
+| B5 的 `compass`(人际指南针)为图像解析项 | 需确认后端是否返回可渲染的坐标数据,否则降级为文本 |
+| 对比/趋势报告目前仅认知维度有趋势数据 | 其余 7 类的趋势能力需后端补齐后才可设计趋势页 |
+| 家长端无 DAN 数据(文档 `:812` 明确家长维度走任务完成率 + 先天属性分) | 报告页仅面向成员(孩子),不做家长版本 |

+ 125 - 0
docs/design/DAN报告页面开发计划.md

@@ -0,0 +1,125 @@
+# DAN 所有类别报告显示页面 · 开发计划
+
+> 设计依据:`docs/design/DAN报告显示页面设计.md`(含 8 类交互原型 `dan-report-pages.html`)
+> 代码依据:`DanReportUpload.java`(8 个 `DIM_*` 常量)、`DanReportParseService.java`(8 路分发)、`DanReportUploadVO.blocks`、`DanAssessmentResult` / `DanAssessmentResultDTO`
+> 关联分支:`cfclub` 关联需求:DAN 8 类报告显示页面
+
+---
+
+## 0. 已完成前置(本次提交)
+
+| 项 | 状态 | 说明 |
+|---|---|---|
+| DTO 补齐 | ✅ 已完成 | `DanAssessmentResultDTO` 原仅含 `attention/focus/memory/logic/overall`,已补齐:认知六维(`perception/spatial/processingSpeed/language`)、EMI 心理四维、大五 OCEAN 五维 + 综合、EMI 情绪商数四维,及报告外壳字段 `dimension/danLevel/structuredAnalysis`;`TeacherService.toAssessmentResultDTO` 同步补全赋值 |
+| 交互原型 | ✅ 已完成 | `docs/design/dan-report-pages.html`(左侧切换 8 类报告预览) |
+| 设计规范 | ✅ 已完成 | `docs/design/DAN报告显示页面设计.md`(8 类清单、区块渲染器注册表、量纲映射表、技术约束) |
+
+---
+
+## 1. 目标与范围
+
+- **目标**:将当前 1 个通用兜底页 `pages/dan-assessment/report-result.vue` 升级为「统一外壳 + 每类专属区块序列」,使 8 类 DAN 报告差异化呈现,专业测评价值可表达。
+- **范围(8 类报告,共 25+ category)**:
+
+  | dimension | reportType | 报告名 | 区块序列(来自设计 §5) |
+  |---|---|---|---|
+  | `mind` | A2 | 心维度 | overall→cognitive→bigFive→emotion→social→body→ability→text |
+  | `wisdom` | B4 | 智维度 | cognitive→selfConcept→growthMindset→selfDriving→text |
+  | `cognition` | A1 | 儿童核心认知 | overall→cognitive(六维)→two→text |
+  | `learning` | B3 | 核心学习能力 | executiveFunction→learningMotivation→learningStrategy→text |
+  | `behavior` | B2 | 儿童自我与家庭教养 | selfConcept→behavior→family→text |
+  | `career` | B6 | 职业发展 | hollandInterest→multipleIntelligence→careerValue→rec→text |
+  | `campus` | C1 | 校园标准版 | cognitive→bigFive→selfDriving→selfConcept→text |
+  | `adolescent` | B5 | 青春期挑战 | emotionRegulation→academicStress→interpersonal→socialAbility→duration→compass→text |
+
+- **非范围**:家长端报告(文档明确家长维度走任务完成率 + 先天属性分,不作报告页);其余 7 类趋势页(目前仅认知维度有趋势数据,待后端补齐)。
+
+---
+
+## 2. 阶段划分(里程碑)
+
+### P0 · 基础设施(约 2 天)
+1. **抽取统一外壳** `components/dan-report/DanReportHero.vue`
+   - 输入:`result`(含 `dimension`、`danLevel`、`overallScore`、百分位、`assessmentDate`、成员快照姓名/性别/年龄)
+   - 产出:Hero(姓名 / 维度徽章 / 等级徽章 / 日期 / 综合分 + 百分位 + 进度条)+ 免责脚注
+   - 迁移自现有 `report-result.vue` 的 Hero 逻辑,紫罗兰主题 `linear-gradient(160deg,#4C3F91,#6B5BD2 55%,#8B7BE8)`
+2. **区块渲染器注册表** `components/dan-report/danBlockRegistry.js`
+   - 落地设计 §6.2 的 `DAN_BLOCK_RENDERERS` + `DAN_CATEGORY_SCALE`(量纲表)
+3. **通用区块组件**(先实现 `bars`/`radar`/`text`/`grid` 四个,其余在 P1)
+   - `DanBlockBars.vue`:指标条 + 参考线 + 需关注徽章(复用 `resolveMax()` 量纲推断,并显式标注 note)
+   - `DanBlockRadar.vue`:自研 `RadarChart.vue`(项目无 echarts/uCharts)承载 ≥3 维雷达
+   - `DanBlockText.vue`:>160 字折叠 + 遮罩渐变
+   - `DanBlockGrid.vue`:三列数值格 + 习惯文本行(`flex-wrap` 实现,禁 CSS Grid)
+
+### P1 · 专属渲染器(约 2.5 天)
+4. `DanBlockChips.vue`(emotion 0-15 分测验,反向计分 note)
+5. `DanBlockMatrix.vue`(A2 社会关系依恋:母亲/父亲/同伴 × 信任/沟通/亲近)
+6. `DanBlockDuration.vue`(B5 睡眠/运动/网络时长卡 + 达标徽章)
+7. `DanBlockCompass.vue`(B5 人际指南针四象限;若后端无坐标,降级为 text)
+8. `DanBlockTwo.vue`(优势/待提升双色对照卡)
+9. `DanBlockRec.vue`(B6 职业方向推荐标签)
+
+### P2 · 入口装配与降级(约 1.5 天)
+10. 改造 `pages/dan-assessment/report-result.vue`:
+    - 保留为 8 类统一入口,按 `dimension` 查 `DAN_BLOCK_RENDERERS` 分派区块组件
+    - `blocks` 顺序严格按 `DAN_CATEGORY_TITLES` 排列
+    - 数据缺失/解析失败时,区块自动降级为 `bars` 通用条,**不白屏**
+11. 复用现有 API:`POST /api/dan-report/{id}`(`blocks` + `result`);沿用 `_onLoadFired` 单次加载模式
+
+### P3 · 验证与上线(约 1.5 天)
+12. 提交前自检(设计 §6.5):
+    - `node scripts/verify-vue-compile.js`
+    - `node scripts/audit-mp-antipatterns.js`(ERROR 归零:禁可选链/禁 CSS Grid/禁 `:key` 表达式/禁 `new Date(string)`)
+    - `node scripts/audit-duplicate-api-calls.js`
+13. 逐类真机验证,建议顺序 **A1 → A2 → B5 → B6**(数据形态最典型)
+14. Code Review + 合入 `cfclub`
+
+---
+
+## 3. 任务清单(可跟踪)
+
+| ID | 任务 | 阶段 | 依赖 | 验收标准 |
+|---|---|---|---|---|
+| T01 | 补齐 `DanAssessmentResultDTO` 及 mapper | P0 | — | ✅ 已提交;字段覆盖实体全部评分维度 |
+| T02 | 抽 `DanReportHero.vue` | P0 | 设计 §2 | Hero 视觉与现有页一致,参数可注入 |
+| T03 | 落地 `danBlockRegistry.js`(渲染器 + 量纲表) | P0 | 设计 §6.2 | 8 类 25 category 全部有映射 |
+| T04 | `DanBlockBars/Radar/Text/Grid` | P0 | T03 | 反向/同向量纲 note 显式;雷达用自研组件 |
+| T05 | `DanBlockChips/Matrix` | P1 | T03 | A2 情绪反向 + 关系矩阵正确渲染 |
+| T06 | `DanBlockDuration/Compass` | P1 | T03 | B5 时长卡 + 指南针;无坐标降级 text |
+| T07 | `DanBlockTwo/Rec` | P1 | T03 | B2 对照卡 / B6 推荐标签 |
+| T08 | 改造 `report-result.vue` 入口分派 + 降级 | P2 | T04–T07 | 8 类均可渲染;缺数据降级不白屏 |
+| T09 | 小程序技术约束审计 + 真机验证 | P3 | T08 | 审计 ERROR=0;8 类真机通过 |
+| T10 | 合入 `cfclub` + 回归 | P3 | T09 | CR 通过,无回归 |
+
+---
+
+## 4. 关键技术约束(必守,设计 §6.4)
+
+| 约束 | 做法 |
+|---|---|
+| 禁可选链 `?.` | 用 `a && a.b` |
+| 禁 CSS Grid | 用 flexbox(`flex-wrap` + `flex:0 0 calc(33.333% - 6px)`) |
+| 禁 `:key` 表达式 | 用 `getItemKey(item)` 方法 |
+| 禁 `new Date(string)` | 用 `utils/format.js` 的 `parseDate()` |
+| Vue 2 Options API | 禁 Composition API;禁 `:class` 内带参方法;禁 `uni.getSystemInfoSync()` |
+| 图表 | 自研 `RadarChart.vue` / `TrendChart.vue`(无 echarts/uCharts) |
+| 首次加载 | 同 URL 仅一次请求(`_onLoadFired`);时间格式 `yyyy-MM-dd` |
+
+---
+
+## 5. 风险与遗留项
+
+| 项 | 状态 / 处置 |
+|---|---|
+| `DanReportParseService` 的 `compass`(B5 人际指南针)为图像解析 | 需确认后端是否返回可渲染坐标;否则 `compass` 降级为 text(T06 含降级分支) |
+| 对比/趋势报告仅认知维度有趋势数据 | 其余 7 类趋势页暂缓,列入二期 |
+| 家长端无 DAN 数据 | 报告页仅面向成员(孩子),不做家长版本 |
+| 量纲误读风险 | 所有反向/特殊量纲必须在卡片 note 显式标注(`DAN_CATEGORY_SCALE` 驱动) |
+
+---
+
+## 6. 交付物
+
+- 代码:`components/dan-report/*`(1 Hero + 10 区块 + 注册表)、改造后的 `pages/dan-assessment/report-result.vue`
+- 文档:`docs/design/dan-report-pages.html`(原型)、`docs/design/DAN报告显示页面设计.md`(规范)、本计划
+- 数据契约:补齐后的 `DanAssessmentResultDTO`(后端)、`DanReportUploadVO.blocks`(既有)

+ 585 - 0
docs/design/dan-report-pages.html

@@ -0,0 +1,585 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>DAN 报告显示页面设计 · 8 类报告</title>
+<style>
+  :root{
+    --bg:#F3F2FA;
+    --card:#FFFFFF;
+    --violet:#6B5BD2;
+    --violet-deep:#4C3F91;
+    --violet-mid:#7A6ADB;
+    --violet-soft:#A99CF0;
+    --violet-text:#5B4BA8;
+    --violet-grid:#EDE8FB;
+    --violet-wash:#F7F5FE;
+    --ink:#33334A;
+    --ink-2:#6E6E85;
+    --ink-3:#9A9AB0;
+    --line:#EFEDF8;
+    --orange:#F0A05A;
+    --orange-wash:#FDF3E7;
+    --cyan:#67E8F9;
+    --green:#5FC08A;
+    --red:#E8776B;
+    --radius:16px;
+  }
+  *{box-sizing:border-box;margin:0;padding:0}
+  html,body{height:100%}
+  body{
+    font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif;
+    background:#E7E4F3;color:var(--ink);-webkit-font-smoothing:antialiased;
+  }
+  .app{display:flex;min-height:100vh}
+
+  /* ============ 侧边栏 ============ */
+  .sidebar{
+    width:272px;flex:0 0 272px;background:#fff;padding:26px 20px;
+    border-right:1px solid #E4E0F0;position:sticky;top:0;height:100vh;overflow-y:auto;
+  }
+  .sidebar h1{font-size:19px;font-weight:700;color:var(--violet-deep);letter-spacing:.5px}
+  .sidebar .sub{font-size:12px;color:var(--ink-3);margin-top:5px;line-height:1.6}
+  .sidebar .meta{
+    margin-top:14px;padding:10px 12px;background:var(--violet-wash);
+    border-radius:10px;font-size:11.5px;color:var(--violet-text);line-height:1.7
+  }
+  .sidebar .meta b{color:var(--violet-deep)}
+  nav{margin-top:20px;display:flex;flex-direction:column;gap:6px}
+  .tab{
+    display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:11px;
+    cursor:pointer;border:1px solid transparent;transition:.16s;background:transparent;text-align:left;
+    font-family:inherit;width:100%;
+  }
+  .tab:hover{background:var(--violet-wash)}
+  .tab.active{background:linear-gradient(100deg,#F0EDFD,#F7F5FE);border-color:#DDD5F7}
+  .tab-code{
+    flex:0 0 30px;height:30px;border-radius:8px;display:flex;align-items:center;justify-content:center;
+    font-size:11.5px;font-weight:700;background:var(--violet-grid);color:var(--violet-text)
+  }
+  .tab.active .tab-code{background:var(--violet);color:#fff}
+  .tab-name{font-size:13.5px;font-weight:600;color:var(--ink)}
+  .tab-desc{font-size:11px;color:var(--ink-3);margin-top:1px}
+  .legend{margin-top:22px;padding-top:16px;border-top:1px solid var(--line)}
+  .legend h3{font-size:12px;color:var(--ink-2);margin-bottom:10px;font-weight:600}
+  .lg-row{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--ink-2);margin-bottom:7px}
+  .lg-dot{width:11px;height:11px;border-radius:3px;flex:0 0 11px}
+
+  /* ============ 舞台 ============ */
+  .stage{flex:1;display:flex;flex-direction:column;align-items:center;padding:30px 24px 50px;overflow-y:auto;height:100vh}
+  .stage-head{text-align:center;margin-bottom:18px}
+  .stage-head .t{font-size:15px;font-weight:600;color:var(--violet-deep)}
+  .stage-head .d{font-size:12px;color:var(--ink-3);margin-top:4px}
+  .phone{
+    width:392px;background:var(--bg);border-radius:30px;overflow:hidden;
+    box-shadow:0 18px 48px rgba(60,45,120,.20),0 2px 8px rgba(60,45,120,.10);
+    border:7px solid #2E2A44;
+  }
+  .screen{min-height:640px;padding-bottom:18px}
+
+  /* ============ Hero ============ */
+  .hero{
+    position:relative;overflow:hidden;color:#fff;padding:22px 20px 20px;
+    background:linear-gradient(160deg,#4C3F91 0%,#6B5BD2 55%,#8B7BE8 100%);
+    border-bottom-left-radius:22px;border-bottom-right-radius:22px;
+  }
+  .hero-c1{position:absolute;width:190px;height:190px;border-radius:50%;background:rgba(255,255,255,.06);right:-70px;top:-78px}
+  .hero-c2{position:absolute;width:112px;height:112px;border-radius:50%;background:rgba(255,255,255,.05);left:-42px;bottom:-34px}
+  .hero-top{position:relative;z-index:1}
+  .hero-name{font-size:21px;font-weight:700;letter-spacing:.3px}
+  .hero-badges{display:flex;align-items:center;gap:6px;margin-top:8px;flex-wrap:wrap}
+  .bd{font-size:10.5px;padding:3px 9px;border-radius:20px;background:rgba(255,255,255,.20);backdrop-filter:blur(2px)}
+  .bd-strong{background:#FFD9A8;color:#7A4A12;font-weight:700}
+  .hero-scores{display:flex;align-items:flex-end;gap:26px;margin-top:16px;position:relative;z-index:1}
+  .hs{display:flex;flex-direction:column;gap:2px}
+  .hs-num{font-size:32px;font-weight:800;line-height:1;letter-spacing:-.5px}
+  .hs-num small{font-size:13px;font-weight:600;opacity:.8;margin-left:2px}
+  .hs-lab{font-size:10.5px;opacity:.82}
+  .pct-wrap{margin-top:14px;position:relative;z-index:1}
+  .pct-track{height:7px;border-radius:4px;background:rgba(255,255,255,.24);position:relative;overflow:hidden}
+  .pct-fill{height:100%;border-radius:4px;background:linear-gradient(90deg,#A5F3FC,#67E8F9,#FFD9A8)}
+  .pct-txt{margin-top:6px;font-size:10.5px;opacity:.9}
+
+  /* ============ 卡片 ============ */
+  .card{background:var(--card);border-radius:var(--radius);margin:11px 12px 0;padding:15px 15px 16px;box-shadow:0 1px 3px rgba(80,60,140,.05)}
+  .card-hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:2px}
+  .card-title{font-size:14.5px;font-weight:700;color:var(--violet-text)}
+  .card-note{font-size:10.5px;color:var(--ink-3)}
+  .card-sub{font-size:11px;color:var(--ink-3);margin-top:3px;line-height:1.6}
+
+  /* 雷达 + 指标条 */
+  .radar-box{display:flex;justify-content:center;margin:6px 0 2px}
+  .m-list{margin-top:10px}
+  .metric{display:flex;align-items:center;gap:9px;padding:6px 0}
+  .metric-lab{flex:0 0 88px;font-size:11.5px;color:var(--ink-2);line-height:1.3}
+  .metric-track{flex:1;height:8px;border-radius:4px;background:var(--violet-grid);position:relative;overflow:hidden}
+  .metric-fill{height:100%;border-radius:4px;background:linear-gradient(90deg,var(--violet-soft),var(--violet))}
+  .metric-fill.warn{background:linear-gradient(90deg,#F7C08A,var(--orange))}
+  .metric-fill.good{background:linear-gradient(90deg,#8FD9B0,var(--green))}
+  .metric-ref{position:absolute;top:-2px;width:1.5px;height:12px;background:#B9B4D4;border-radius:1px}
+  .metric-val{flex:0 0 46px;text-align:right;font-size:11.5px;font-weight:700;color:var(--ink)}
+  .metric-val.warn{color:var(--orange)}
+  .tag-mini{font-size:9px;padding:1px 5px;border-radius:4px;background:var(--orange-wash);color:#B4702A;margin-left:4px;font-weight:600}
+  .tag-mini.good{background:#E9F7EF;color:#2E8B5C}
+  .metric-val .tag-mini{display:inline-block}
+
+  /* chips */
+  .chip-row{display:flex;align-items:center;gap:9px;padding:6px 0}
+  .chip-lab{flex:0 0 60px;font-size:11.5px;color:var(--violet-text);font-weight:600}
+  .chip-track{flex:1;height:7px;border-radius:4px;background:var(--violet-grid);overflow:hidden}
+  .chip-fill{height:100%;border-radius:4px;background:linear-gradient(90deg,#B9AEF5,var(--violet))}
+  .chip-fill.warn{background:linear-gradient(90deg,#F7C08A,var(--orange))}
+  .chip-val{flex:0 0 40px;text-align:right;font-size:11px;color:var(--ink-2)}
+
+  /* grid */
+  .grid{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
+  .gi{flex:0 0 calc(33.333% - 6px);background:var(--violet-wash);border-radius:11px;padding:11px 6px;text-align:center}
+  .gi-v{font-size:16px;font-weight:800;color:var(--violet-deep);line-height:1}
+  .gi-v small{font-size:10px;font-weight:600;color:var(--ink-3);margin-left:1px}
+  .gi-l{font-size:10px;color:var(--ink-3);margin-top:5px}
+  .habit{margin-top:10px;border-top:1px solid var(--line);padding-top:9px}
+  .habit-row{display:flex;justify-content:space-between;font-size:11.5px;color:var(--ink-2);padding:4px 0}
+  .habit-row b{color:var(--ink);font-weight:600}
+
+  /* matrix(社会关系依恋) */
+  .mx-head,.mx-row{display:flex;align-items:center;padding:7px 0;font-size:11.5px}
+  .mx-head{border-bottom:1px solid var(--line);color:var(--ink-3);font-size:10.5px;font-weight:600}
+  .mx-c0{flex:0 0 52px;color:var(--ink-2)}
+  .mx-c{flex:1;text-align:center}
+  .mx-bar{height:6px;border-radius:3px;background:var(--violet-grid);overflow:hidden;margin:0 8px}
+  .mx-bar i{display:block;height:100%;border-radius:3px;background:linear-gradient(90deg,var(--violet-soft),var(--violet))}
+  .mx-num{font-weight:700;color:var(--violet-text)}
+
+  /* duration(时长卡) */
+  .dur{display:flex;gap:8px;margin-top:10px}
+  .du{flex:1;background:var(--violet-wash);border-radius:12px;padding:11px 9px;text-align:center}
+  .du-v{font-size:18px;font-weight:800;color:var(--violet-deep);line-height:1}
+  .du-v small{font-size:10px;color:var(--ink-3);font-weight:600;margin-left:1px}
+  .du-l{font-size:10px;color:var(--ink-3);margin-top:5px}
+  .du-s{font-size:9.5px;margin-top:6px;padding:2px 6px;border-radius:5px;display:inline-block;font-weight:600}
+  .du-s.ok{background:#E9F7EF;color:#2E8B5C}
+  .du-s.warn{background:var(--orange-wash);color:#B4702A}
+  .du-s.bad{background:#FDECEA;color:#C25A4E}
+
+  /* compass */
+  .compass-note{font-size:11px;color:var(--ink-2);line-height:1.7;margin-top:8px}
+
+  /* text */
+  .txt{font-size:12px;color:var(--ink-2);line-height:1.85;margin-top:8px}
+  .txt.collapsed{
+    display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden;
+    -webkit-mask-image:linear-gradient(180deg,#000 55%,transparent 100%);
+  }
+  .more{font-size:11.5px;color:var(--violet);margin-top:6px;font-weight:600;cursor:pointer;background:none;border:none;padding:0;font-family:inherit}
+
+  /* 优势/待提升 */
+  .two{display:flex;gap:8px;margin-top:10px}
+  .two-col{flex:1;background:var(--violet-wash);border-radius:12px;padding:11px}
+  .two-col.warn{background:var(--orange-wash)}
+  .two-h{font-size:11px;font-weight:700;margin-bottom:7px}
+  .two-col .two-h{color:#2E8B5C}
+  .two-col.warn .two-h{color:#B4702A}
+  .two-item{font-size:11px;color:var(--ink-2);padding:2.5px 0}
+
+  /* 推荐 chips */
+  .rec{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}
+  .rec-chip{font-size:11px;padding:5px 10px;border-radius:20px;background:var(--violet-wash);color:var(--violet-text);font-weight:600}
+  .rec-chip.hot{background:linear-gradient(100deg,#F0EDFD,#E7E1FB);color:var(--violet-deep)}
+
+  .foot{text-align:center;font-size:10px;color:#B0AEC4;padding:18px 26px 4px;line-height:1.7}
+  .hint{font-size:11px;color:var(--ink-3);margin-top:14px;text-align:center}
+</style>
+</head>
+<body>
+<div class="app">
+  <aside class="sidebar">
+    <h1>DAN 报告体系</h1>
+    <div class="sub">8 类报告 · 显示页面设计原型</div>
+    <div class="meta">
+      数据来源 <b>DanReportUpload.DIM_*</b><br>
+      区块顺序 <b>ReportBlockAssembler<br>.DAN_CATEGORY_TITLES</b><br>
+      渲染口径 <b>score / indicator / text</b>
+    </div>
+    <nav id="tabs"></nav>
+    <div class="legend">
+      <h3>区块渲染器图例</h3>
+      <div class="lg-row"><span class="lg-dot" style="background:var(--violet)"></span>雷达图(≥3 维)</div>
+      <div class="lg-row"><span class="lg-dot" style="background:var(--violet-soft)"></span>指标条(带参考线)</div>
+      <div class="lg-row"><span class="lg-dot" style="background:var(--orange)"></span>需关注(低于阈值)</div>
+      <div class="lg-row"><span class="lg-dot" style="background:var(--green)"></span>优势项</div>
+      <div class="lg-row"><span class="lg-dot" style="background:#D7D2EC"></span>数值格 / 时长卡</div>
+    </div>
+  </aside>
+
+  <main class="stage">
+    <div class="stage-head">
+      <div class="t" id="stageTitle"></div>
+      <div class="d" id="stageDesc"></div>
+    </div>
+    <div class="phone"><div class="screen" id="screen"></div></div>
+    <div class="hint">切换左侧报告类别查看各自专属页面布局</div>
+  </main>
+</div>
+
+<script>
+/* ===================== 雷达图(SVG) ===================== */
+function radarSVG(items, opt){
+  opt = opt || {};
+  var size=250, cx=125, cy=121, r=82, n=items.length, rings='', axes='', i, a, pts;
+  for(var g=1; g<=4; g++){
+    pts=[];
+    for(i=0;i<n;i++){ a=-Math.PI/2+i*2*Math.PI/n; pts.push((cx+r*g/4*Math.cos(a)).toFixed(1)+','+(cy+r*g/4*Math.sin(a)).toFixed(1)); }
+    rings+='<polygon points="'+pts.join(' ')+'" fill="'+(g===4?'#F7F5FE':'none')+'" stroke="#E6E1F7" stroke-width="1"/>';
+  }
+  for(i=0;i<n;i++){
+    a=-Math.PI/2+i*2*Math.PI/n;
+    axes+='<line x1="'+cx+'" y1="'+cy+'" x2="'+(cx+r*Math.cos(a)).toFixed(1)+'" y2="'+(cy+r*Math.sin(a)).toFixed(1)+'" stroke="#E6E1F7" stroke-width="1"/>';
+  }
+  var dpts=items.map(function(it,i){
+    var a=-Math.PI/2+i*2*Math.PI/n, ratio=Math.max(0,Math.min(1,it.value/it.max));
+    return (cx+r*ratio*Math.cos(a)).toFixed(1)+','+(cy+r*ratio*Math.sin(a)).toFixed(1);
+  });
+  var data='<polygon points="'+dpts.join(' ')+'" fill="rgba(107,91,210,.20)" stroke="#6B5BD2" stroke-width="2" stroke-linejoin="round"/>';
+  var dots=items.map(function(it,i){
+    var a=-Math.PI/2+i*2*Math.PI/n, ratio=Math.max(0,Math.min(1,it.value/it.max));
+    var warn = it.max && it.value/it.max < 0.5;
+    return '<circle cx="'+(cx+r*ratio*Math.cos(a)).toFixed(1)+'" cy="'+(cy+r*ratio*Math.sin(a)).toFixed(1)+'" r="3" fill="'+(warn?'#F0A05A':'#6B5BD2')+'"/>';
+  }).join('');
+  var labels=items.map(function(it,i){
+    var a=-Math.PI/2+i*2*Math.PI/n, lx=cx+(r+24)*Math.cos(a), ly=cy+(r+24)*Math.sin(a);
+    var cos=Math.cos(a), anchor = Math.abs(cos)<0.25 ? 'middle' : (cos>0?'start':'end');
+    var dy = Math.sin(a)>0.6 ? 8 : (Math.sin(a)<-0.6 ? -2 : 4);
+    return '<text x="'+lx.toFixed(1)+'" y="'+(ly+dy).toFixed(1)+'" text-anchor="'+anchor+'" font-size="10.5" fill="#6E6E85">'+it.label+'</text>';
+  }).join('');
+  return '<svg viewBox="0 0 '+size+' '+size+'" width="100%" style="max-width:250px;display:block">'+rings+axes+data+dots+labels+'</svg>';
+}
+
+/* ===================== 区块渲染器 ===================== */
+function pct(v,m){ return Math.round(Math.max(0,Math.min(1,v/m))*100); }
+
+function rBars(b){
+  var h='<div class="m-list">';
+  b.items.forEach(function(it){
+    var w=pct(it.value,it.max), warn = it.value/it.max < 0.5, good = it.value/it.max >= 0.8;
+    h+='<div class="metric">'
+      +'<div class="metric-lab">'+it.label+'</div>'
+      +'<div class="metric-track"><div class="metric-fill '+(warn?'warn':(good?'good':''))+'" style="width:'+w+'%"></div>'
+        +(it.ref?'<div class="metric-ref" style="left:'+pct(it.ref,it.max)+'%"></div>':'')+'</div>'
+      +'<div class="metric-val'+(warn?' warn':'')+'">'+it.value
+        +(b.maxLabel?'<span style="font-weight:500;color:#9A9AB0;font-size:10px">/'+it.max+'</span>':'')
+        +(warn?'<span class="tag-mini">需关注</span>':'')+'</div>'
+      +'</div>';
+  });
+  return h+'</div>';
+}
+function rRadar(b){
+  return '<div class="radar-box">'+radarSVG(b.items)+'</div>'+rBars(b);
+}
+function rChips(b){
+  var h='<div class="m-list">';
+  b.items.forEach(function(it){
+    var w=pct(it.value,it.max), warn=it.value/it.max>=0.5;
+    h+='<div class="chip-row"><div class="chip-lab">'+it.label+'</div>'
+      +'<div class="chip-track"><div class="chip-fill '+(warn?'warn':'')+'" style="width:'+w+'%"></div></div>'
+      +'<div class="chip-val">'+it.value+'/'+it.max+'</div></div>';
+  });
+  return h+'</div>';
+}
+function rGrid(b){
+  var h='<div class="grid">';
+  b.items.forEach(function(it){
+    h+='<div class="gi"><div class="gi-v">'+it.value+'<small>'+(it.unit||'')+'</small></div><div class="gi-l">'+it.label+'</div></div>';
+  });
+  h+='</div>';
+  if(b.habits){
+    h+='<div class="habit">';
+    b.habits.forEach(function(x){ h+='<div class="habit-row"><span>'+x.k+'</span><b>'+x.v+'</b></div>'; });
+    h+='</div>';
+  }
+  return h;
+}
+function rMatrix(b){
+  var h='<div class="mx-head"><div class="mx-c0"></div>';
+  b.cols.forEach(function(c){ h+='<div class="mx-c">'+c+'</div>'; });
+  h+='</div>';
+  b.rows.forEach(function(r){
+    h+='<div class="mx-row"><div class="mx-c0">'+r.label+'</div>';
+    r.vals.forEach(function(v){
+      h+='<div class="mx-c"><div class="mx-bar"><i style="width:'+v+'%"></i></div><div class="mx-num" style="font-size:10.5px;margin-top:3px">'+v+'</div></div>';
+    });
+    h+='</div>';
+  });
+  return h;
+}
+function rDuration(b){
+  var h='<div class="dur">';
+  b.items.forEach(function(it){
+    h+='<div class="du"><div class="du-v">'+it.value+'<small>'+it.unit+'</small></div>'
+      +'<div class="du-l">'+it.label+'</div>'
+      +'<div class="du-s '+it.status+'">'+it.note+'</div></div>';
+  });
+  return h+'</div>';
+}
+function rCompass(b){
+  var svg='<svg viewBox="0 0 220 190" width="100%" style="max-width:240px;display:block;margin:4px auto 0">'
+    +'<circle cx="110" cy="95" r="78" fill="#F7F5FE" stroke="#E6E1F7"/>'
+    +'<circle cx="110" cy="95" r="52" fill="none" stroke="#EDE8FB"/>'
+    +'<circle cx="110" cy="95" r="26" fill="none" stroke="#EDE8FB"/>'
+    +'<line x1="32" y1="95" x2="188" y2="95" stroke="#E6E1F7"/>'
+    +'<line x1="110" y1="17" x2="110" y2="173" stroke="#E6E1F7"/>';
+  var pos=[{x:110,y:35},{x:110,y:155},{x:47,y:95},{x:173,y:95}];
+  b.items.forEach(function(it,i){
+    var p=pos[i], rr=(1-it.value/it.max)*72;
+    var ang=Math.atan2(p.y-95,p.x-110);
+    var dx=110+rr*Math.cos(ang), dy=95+rr*Math.sin(ang);
+    svg+='<line x1="110" y1="95" x2="'+dx.toFixed(1)+'" y2="'+dy.toFixed(1)+'" stroke="#C9C1EE" stroke-dasharray="3 3"/>';
+    svg+='<circle cx="'+dx.toFixed(1)+'" cy="'+dy.toFixed(1)+'" r="'+(6+it.value/it.max*9).toFixed(1)+'" fill="rgba(107,91,210,.28)" stroke="#6B5BD2" stroke-width="1.6"/>';
+    var lx = p.x===110 ? 110 : (p.x<110? 40 : 180), ly = p.y<95 ? 22 : 172;
+    var anchor = p.x===110 ? 'middle' : (p.x<110?'start':'end');
+    svg+='<text x="'+lx+'" y="'+ly+'" text-anchor="'+anchor+'" font-size="11" fill="#5B4BA8" font-weight="600">'+it.label+'</text>';
+    svg+='<text x="'+lx+'" y="'+(ly+13)+'" text-anchor="'+anchor+'" font-size="9.5" fill="#9A9AB0">'+it.value+'/'+it.max+'</text>';
+  });
+  svg+='</svg>';
+  return svg+'<div class="compass-note">'+b.note+'</div>';
+}
+function rText(b){
+  var cls = b.collapsible ? 'txt collapsed' : 'txt';
+  var h='<div class="'+cls+'" id="'+b._id+'">'+b.content+'</div>';
+  if(b.collapsible) h+='<button class="more" data-t="'+b._id+'">展开全文 ›</button>';
+  return h;
+}
+function rTwo(b){
+  var h='<div class="two">';
+  h+='<div class="two-col"><div class="two-h">优势维度</div>';
+  b.good.forEach(function(x){ h+='<div class="two-item">· '+x+'</div>'; });
+  h+='</div><div class="two-col warn"><div class="two-h">待提升</div>';
+  b.warn.forEach(function(x){ h+='<div class="two-item">· '+x+'</div>'; });
+  h+='</div></div>';
+  return h;
+}
+function rRec(b){
+  var h='<div class="rec">';
+  b.items.forEach(function(it){ h+='<span class="rec-chip'+(it.hot?' hot':'')+'">'+it.label+'</span>'; });
+  return h+'</div>';
+}
+
+var RENDER={radar:rRadar,bars:rBars,chips:rChips,grid:rGrid,matrix:rMatrix,duration:rDuration,compass:rCompass,text:rText,two:rTwo,rec:rRec};
+
+/* ===================== 8 类报告数据 ===================== */
+var MEMBER={name:'陈乐乐',gender:'女',age:'9岁'};
+var REPORTS=[
+{
+  id:'mind',code:'A2',name:'心维度',desc:'核心素养评估 · mind',
+  total:78,percentile:72,level:'B',date:'2026-10-18',
+  blocks:[
+    {type:'radar',title:'认知能力',note:'百分位',maxLabel:true,items:[
+      {label:'感知觉',value:82,max:100},{label:'记忆力',value:74,max:100},{label:'注意力',value:68,max:100}]},
+    {type:'radar',title:'大五人格',note:'1-5 分制',maxLabel:true,items:[
+      {label:'开放性',value:4.2,max:5},{label:'尽责性',value:3.8,max:5},{label:'外向性',value:3.1,max:5},
+      {label:'宜人性',value:4.5,max:5},{label:'神经质',value:2.4,max:5}]},
+    {type:'chips',title:'情绪状态',note:'0-15 分制,分数越高越需关注',items:[
+      {label:'自卑感',value:4,max:15},{label:'抑郁',value:3,max:15},{label:'焦虑',value:6,max:15},{label:'无力感',value:3,max:15}]},
+    {type:'matrix',title:'社会关系',note:'依恋质量 · 母亲 / 父亲 / 同伴',
+      cols:['信任','沟通','亲近'],
+      rows:[
+        {label:'母亲',vals:[86,78,82]},
+        {label:'父亲',vals:[71,64,69]},
+        {label:'同伴',vals:[74,80,77]}]},
+    {type:'grid',title:'体质健康',items:[
+      {label:'身高',value:132,unit:'cm'},{label:'体重',value:28,unit:'kg'},{label:'BMI',value:16.1,unit:''}],
+      habits:[{k:'饮食',v:'规律'},{k:'睡眠',v:'约 9 小时/天'},{k:'运动',v:'每周 3 次'}]},
+    {type:'bars',title:'综合能力',maxLabel:true,items:[
+      {label:'语言表达',value:8.1,max:10},{label:'逻辑思维',value:7.4,max:10},{label:'创造力',value:7.8,max:10}]},
+    {type:'text',title:'成长建议',collapsible:true,content:'孩子在宜人性与同伴信任维度表现突出,说明其社会性发展良好,建议继续提供稳定的同伴交往场景。注意力百分位 68 略低于认知整体水平,可在家庭学习中引入"分段专注 + 即时反馈"的训练方式,每次专注时长从 15 分钟起步逐步延长。父亲维度的沟通与亲近得分相对偏低,建议增加父子单独相处的高质量陪伴时间。焦虑分项 6/15 处于中等水平,可通过睡前情绪复盘帮助孩子外化压力。'}
+  ]
+},
+{
+  id:'wisdom',code:'B4',name:'智维度',desc:'认知与发展评估 · wisdom',
+  total:82,percentile:76,level:'A',date:'2026-10-18',
+  blocks:[
+    {type:'radar',title:'认知能力',note:'推理 / 空间 / 加工速度',maxLabel:true,items:[
+      {label:'推理能力',value:88,max:100},{label:'空间能力',value:76,max:100},{label:'加工速度',value:71,max:100}]},
+    {type:'bars',title:'自我概念',maxLabel:true,items:[
+      {label:'学业自我',value:7.6,max:10},{label:'身体自我',value:7.1,max:10},{label:'社会自我',value:8.0,max:10},{label:'情绪自我',value:6.8,max:10}]},
+    {type:'bars',title:'成长型思维',maxLabel:true,items:[
+      {label:'努力信念',value:8.2,max:10},{label:'挑战接纳',value:7.3,max:10},{label:'失败归因',value:6.4,max:10}]},
+    {type:'bars',title:'自驱力',maxLabel:true,items:[
+      {label:'自主性',value:7.8,max:10},{label:'胜任感',value:7.2,max:10},{label:'坚持性',value:6.6,max:10}]},
+    {type:'text',title:'成长建议',collapsible:true,content:'推理能力百分位 88 是显著优势,适合提供开放性、探究式的学习任务(如科学实验、编程启蒙)。加工速度 71 相对偏弱,在限时测验场景中可能吃亏,建议日常做"限时但不计分"的练习降低速度焦虑。失败归因得分 6.4 偏低,孩子容易把挫折归为能力不足,家长反馈时应更多描述过程与策略而非评价天赋。'}
+  ]
+},
+{
+  id:'cognition',code:'A1',name:'儿童核心认知',desc:'认知六维评估 · cognition',
+  total:82,percentile:78,level:'A',date:'2026-10-12',
+  blocks:[
+    {type:'radar',title:'认知能力',note:'六维百分位(P50 为同龄中位)',maxLabel:true,items:[
+      {label:'感知觉',value:85,max:100},{label:'注意力',value:72,max:100},{label:'记忆力',value:80,max:100},
+      {label:'推理能力',value:88,max:100},{label:'空间能力',value:76,max:100},{label:'加工速度',value:69,max:100}]},
+    {type:'bars',title:'认知能力',note:'明细 · 灰线为同龄 P50',maxLabel:true,items:[
+      {label:'感知觉',value:85,max:100,ref:50},{label:'注意力',value:72,max:100,ref:50},
+      {label:'记忆力',value:80,max:100,ref:50},{label:'推理能力',value:88,max:100,ref:50},
+      {label:'空间能力',value:76,max:100,ref:50},{label:'加工速度',value:69,max:100,ref:50}]},
+    {type:'two',title:'优势与待提升',good:['推理能力(88)','感知觉(85)','记忆力(80)'],warn:['加工速度(69)','注意力(72)']},
+    {type:'text',title:'成长建议',collapsible:true,content:'整体认知水平位于同龄前 22%,推理与感知觉是双优势维度。加工速度是唯一低于 P50+20 的维度,表现为信息摄取与反应偏慢,建议通过速读、视听反应类小游戏做专项训练,每次 10 分钟、每周 4 次。注意力 72 尚可,但与推理能力的落差提示"会做但不够稳",考试场景下易因分心丢分,可配合番茄钟与错题归因习惯。'}
+  ]
+},
+{
+  id:'learning',code:'B3',name:'核心学习能力',desc:'执行功能 · 动机 · 策略 · learning',
+  total:74,percentile:70,level:'B',date:'2026-10-15',
+  blocks:[
+    {type:'radar',title:'执行功能',note:'百分位',maxLabel:true,items:[
+      {label:'抑制控制',value:68,max:100},{label:'工作记忆',value:74,max:100},{label:'认知灵活性',value:71,max:100}]},
+    {type:'bars',title:'学习动机',note:'十分制',maxLabel:true,items:[
+      {label:'深层动机',value:7.2,max:10},{label:'表面动机',value:5.4,max:10},{label:'自我效能感',value:6.8,max:10}]},
+    {type:'bars',title:'学习策略',note:'十分制',maxLabel:true,items:[
+      {label:'深层方法与策略',value:6.1,max:10},{label:'表面方法与策略',value:5.8,max:10},{label:'学习自我调节',value:5.2,max:10}]},
+    {type:'text',title:'成长建议',collapsible:true,content:'执行功能三项均在 68-74 的中上区间,工作记忆相对最好,能支撑多步指令的完成。深层动机 7.2 高于表面动机 5.4,说明孩子 intrinsic 学习兴趣健康,应减少外在奖励依赖。学习自我调节 5.2 是三项策略中最低,表现为计划性差、不会复盘,建议引入"学习计划表 + 周回顾"双件套,家长只做陪伴不做代劳。'}
+  ]
+},
+{
+  id:'behavior',code:'B2',name:'儿童自我与家庭教养',desc:'自我概念 · 行为 · 家庭环境 · behavior',
+  total:76,percentile:68,level:'B',date:'2026-10-16',
+  blocks:[
+    {type:'radar',title:'自我概念',note:'六维 · 十分制',maxLabel:true,items:[
+      {label:'行为表现',value:7.5,max:10},{label:'能力与学校',value:6.8,max:10},{label:'躯体外貌',value:7.2,max:10},
+      {label:'情绪状态',value:6.1,max:10},{label:'合群',value:7.8,max:10},{label:'幸福与满足',value:7.0,max:10}]},
+    {type:'bars',title:'行为表现',note:'八维 · 十分制(多动/刻板/拖延为反向计分)',maxLabel:true,items:[
+      {label:'品行',value:8.2,max:10},{label:'情绪',value:6.4,max:10},{label:'学习',value:6.0,max:10},{label:'社交',value:7.6,max:10},
+      {label:'生活习惯',value:6.8,max:10},{label:'多动倾向',value:4.5,max:10},{label:'刻板行为',value:3.2,max:10},{label:'拖延行为',value:5.6,max:10}]},
+    {type:'bars',title:'家庭环境',note:'十维 · 十分制',maxLabel:true,items:[
+      {label:'亲密度',value:7.2,max:10},{label:'情感表达',value:6.4,max:10},{label:'矛盾性',value:4.1,max:10},
+      {label:'独立性',value:6.8,max:10},{label:'成就性',value:7.0,max:10},{label:'文化性',value:6.2,max:10},
+      {label:'娱乐性',value:6.6,max:10},{label:'道德宗教',value:6.9,max:10},{label:'组织性',value:7.3,max:10},{label:'控制性',value:5.8,max:10}]},
+    {type:'text',title:'教养建议',collapsible:true,content:'合群 7.8 与品行 8.2 是孩子的社交与品行优势,同伴关系稳定。刻板行为 3.2、多动倾向 4.5 均处低位,无需过度担忧。家庭环境中"情感表达 6.4"与"控制性 5.8"是两处可优化点:控制性偏高叠加情感表达偏弱,容易让孩子在遇到挫折时不主动求助,建议家长每天固定 10 分钟"只倾听不给建议"的对话时段。学习维度 6.0 与自我概念中的"能力与学校 6.8"相互印证,可通过降低比较、强调进步来提升学业自我效能。'}
+  ]
+},
+{
+  id:'career',code:'B6',name:'职业发展',desc:'兴趣 · 多元智能 · 价值观 · career',
+  total:80,percentile:74,level:'A',date:'2026-10-20',
+  blocks:[
+    {type:'radar',title:'职业兴趣',note:'Holland 六型 · 百分位',maxLabel:true,items:[
+      {label:'艺术型',value:72,max:100},{label:'社会型',value:85,max:100},{label:'企业型',value:63,max:100},
+      {label:'常规型',value:55,max:100},{label:'现实型',value:48,max:100},{label:'研究型',value:78,max:100}]},
+    {type:'bars',title:'多元智能',note:'1-15 分制',maxLabel:true,items:[
+      {label:'内省智能',value:12,max:15},{label:'空间智能',value:10,max:15},{label:'音乐智能',value:8,max:15},{label:'人际智能',value:13,max:15},
+      {label:'自然观察',value:9,max:15},{label:'运动智能',value:7,max:15},{label:'语言智能',value:11,max:15},{label:'逻辑智能',value:12,max:15}]},
+    {type:'bars',title:'职业价值观',maxLabel:true,items:[
+      {label:'成就感',value:8.4,max:10},{label:'助人',value:8.8,max:10},{label:'独立性',value:7.2,max:10},{label:'创造性',value:8.1,max:10}]},
+    {type:'rec',title:'适配方向',note:'基于兴趣 × 智能 × 价值观',items:[
+      {label:'教育 / 培训',hot:true},{label:'心理咨询',hot:true},{label:'内容创作'},{label:'科研'},{label:'公共关系'}]},
+    {type:'text',title:'成长建议',collapsible:true,content:'社会型 85 与人际智能 13 形成强共振,叠加"助人"价值观 8.8,指向明显的服务与教育导向。研究型 78 与逻辑智能 12、内省智能 12 构成第二优势轴,适合做需要深度思考的工作。现实型 48 与运动智能 7 是相对短板,动手操作类活动需更多鼓励而非回避。建议在中学阶段提供志愿服务、同伴辅导类实践机会,让优势轴在真实场景中得到验证。'}
+  ]
+},
+{
+  id:'campus',code:'C1',name:'校园标准版',desc:'认知 · 人格 · 自驱 · 自我概念 · campus',
+  total:79,percentile:73,level:'B',date:'2026-10-19',
+  blocks:[
+    {type:'radar',title:'认知能力',note:'六维百分位',maxLabel:true,items:[
+      {label:'感知觉',value:83,max:100},{label:'注意力',value:70,max:100},{label:'记忆力',value:79,max:100},
+      {label:'推理能力',value:84,max:100},{label:'空间能力',value:74,max:100},{label:'加工速度',value:68,max:100}]},
+    {type:'radar',title:'大五人格',note:'1-5 分制',maxLabel:true,items:[
+      {label:'开放性',value:4.0,max:5},{label:'宜人性',value:4.3,max:5},{label:'责任心',value:3.6,max:5},
+      {label:'外倾性',value:3.2,max:5},{label:'神经质',value:2.7,max:5}]},
+    {type:'bars',title:'自驱力',note:'三分维度 · 十分制',maxLabel:true,items:[
+      {label:'自主性',value:6.8,max:10},{label:'胜任感',value:7.2,max:10},{label:'归属感',value:7.6,max:10}]},
+    {type:'radar',title:'自我概念',note:'六维 · 十分制',maxLabel:true,items:[
+      {label:'行为表现',value:7.4,max:10},{label:'能力与学校',value:6.9,max:10},{label:'躯体外貌',value:7.0,max:10},
+      {label:'情绪状态',value:6.3,max:10},{label:'合群',value:7.9,max:10},{label:'幸福与满足',value:7.1,max:10}]},
+    {type:'text',title:'校园适应建议',collapsible:true,content:'归属感 7.6 与合群 7.9 说明孩子在学校的人际关系适应良好,是校园适应的保护性因素。责任心 3.6 与加工速度 68 是需要关注的两处:前者影响作业完成的一致性,后者影响课堂与考试的节奏,建议家校配合建立"作业清单 + 时间盒"机制。神经质 2.7 处于低位,情绪稳定性好。'}
+  ]
+},
+{
+  id:'adolescent',code:'B5',name:'青春期挑战',desc:'情绪 · 压力 · 人际 · 生活节律 · adolescent',
+  total:71,percentile:64,level:'C',date:'2026-10-22',
+  blocks:[
+    {type:'bars',title:'情绪调节',note:'1-7 分制',maxLabel:true,items:[
+      {label:'认知重评',value:5.2,max:7},{label:'表达抑制',value:4.6,max:7}]},
+    {type:'bars',title:'学业压力',note:'压力源五维 · 1-5 分制',maxLabel:true,items:[
+      {label:'学业负担',value:3.4,max:5},{label:'家庭期望',value:3.8,max:5},{label:'师生关系',value:2.6,max:5},
+      {label:'自我期望',value:4.1,max:5},{label:'同伴竞争',value:3.2,max:5}]},
+    {type:'bars',title:'人际关系',note:'四维 · 1-5 分制',maxLabel:true,items:[
+      {label:'母亲',value:4.2,max:5},{label:'父亲',value:3.9,max:5},{label:'师生',value:3.4,max:5},{label:'同伴',value:4.0,max:5}]},
+    {type:'radar',title:'社交能力',note:'五维 · 1-5 分制',maxLabel:true,items:[
+      {label:'主动交往',value:3.6,max:5},{label:'情感支持',value:4.1,max:5},{label:'情感表达',value:3.5,max:5},
+      {label:'表达影响',value:3.8,max:5},{label:'冲突解决',value:3.3,max:5}]},
+    {type:'duration',title:'生活节律',note:'睡眠 / 运动 / 网络',items:[
+      {label:'睡眠',value:7.2,unit:'h/天',status:'warn',note:'偏少'},
+      {label:'运动',value:45,unit:'min/天',status:'ok',note:'达标'},
+      {label:'网络',value:3.5,unit:'h/天',status:'bad',note:'偏多'}]},
+    {type:'compass',title:'人际指南针',note:'圆心为自我,点越靠近圆心表示关系越紧密。当前同伴与母亲维度最紧密,师生维度距离最远。',
+      items:[
+        {label:'母亲',value:4.2,max:5},{label:'父亲',value:3.9,max:5},
+        {label:'同伴',value:4.0,max:5},{label:'师生',value:3.4,max:5}]},
+    {type:'text',title:'成长建议',collapsible:true,content:'自我期望 4.1 是全部压力源中最高的一项,说明压力主要来自内部而非外部,需警惕完美主义倾向。网络使用 3.5 小时/天明显偏多,且睡眠仅 7.2 小时,两者高度相关——建议先从不带电子设备进卧室这一条规则开始,而不是全面禁网。表达抑制 4.6 偏高、认知重评 5.2 尚可,孩子更倾向于憋住情绪而非重新解读,可通过写情绪日记逐步建立重评习惯。师生关系 3.4 与社交能力中的冲突解决 3.3 均为相对短板,可鼓励孩子主动向老师提问,从低风险的学业求助开始练习。'}
+  ]
+}
+];
+
+/* ===================== 渲染 ===================== */
+var tabsEl=document.getElementById('tabs'), screenEl=document.getElementById('screen');
+var stageTitle=document.getElementById('stageTitle'), stageDesc=document.getElementById('stageDesc');
+var activeId=REPORTS[0].id;
+
+function buildTabs(){
+  tabsEl.innerHTML = REPORTS.map(function(r){
+    return '<button class="tab'+(r.id===activeId?' active':'')+'" data-id="'+r.id+'">'
+      +'<span class="tab-code">'+r.code+'</span>'
+      +'<span><span class="tab-name">'+r.name+'</span><br><span class="tab-desc">'+r.desc.split(' · ')[0]+'</span></span>'
+      +'</button>';
+  }).join('');
+  tabsEl.querySelectorAll('.tab').forEach(function(b){
+    b.addEventListener('click',function(){ activeId=b.getAttribute('data-id'); buildTabs(); render(); });
+  });
+}
+
+function render(){
+  var r=REPORTS.filter(function(x){return x.id===activeId;})[0];
+  var uid=0;
+  var h='';
+
+  /* Hero */
+  h+='<div class="hero"><div class="hero-c1"></div><div class="hero-c2"></div>'
+    +'<div class="hero-top"><div class="hero-name">'+MEMBER.name+'</div>'
+    +'<div class="hero-badges">'
+      +'<span class="bd">'+r.name+'</span>'
+      +'<span class="bd">'+MEMBER.gender+' · '+MEMBER.age+'</span>'
+      +'<span class="bd bd-strong">等级 '+r.level+'</span>'
+      +'<span class="bd">'+r.date+'</span>'
+    +'</div></div>'
+    +'<div class="hero-scores">'
+      +'<div class="hs"><div class="hs-num">'+r.total+'<small>分</small></div><div class="hs-lab">综合得分</div></div>'
+      +'<div class="hs"><div class="hs-num">'+r.percentile+'<small>%</small></div><div class="hs-lab">百分位</div></div>'
+    +'</div>'
+    +'<div class="pct-wrap"><div class="pct-track"><div class="pct-fill" style="width:'+r.percentile+'%"></div></div>'
+      +'<div class="pct-txt">超过 '+r.percentile+'% 的同龄人</div></div>'
+    +'</div>';
+
+  /* Blocks */
+  r.blocks.forEach(function(b){
+    b._id='txt'+(uid++);
+    h+='<div class="card"><div class="card-hd"><div class="card-title">'+b.title+'</div>'
+      +(b.note?'<div class="card-note">'+b.note+'</div>':'')+'</div>'
+      +(RENDER[b.type]?RENDER[b.type](b):'')+'</div>';
+  });
+
+  h+='<div class="foot">本报告由 DAN 测评系统生成,结果仅供成长参考,不构成医学或教育诊断结论。</div>';
+  screenEl.innerHTML=h;
+  stageTitle.textContent=r.code+' · '+r.name;
+  stageDesc.textContent=r.desc;
+
+  /* 展开/收起 */
+  screenEl.querySelectorAll('.more').forEach(function(btn){
+    btn.addEventListener('click',function(){
+      var el=document.getElementById(btn.getAttribute('data-t'));
+      if(el.classList.contains('collapsed')){ el.classList.remove('collapsed'); btn.textContent='收起 ‹'; }
+      else { el.classList.add('collapsed'); btn.textContent='展开全文 ›'; }
+    });
+  });
+}
+
+buildTabs();
+render();
+</script>
+</body>
+</html>