|
|
@@ -9,6 +9,7 @@
|
|
|
<text class="hero-name">{{ childName }}</text>
|
|
|
<view class="hero-dim-row">
|
|
|
<text class="hero-dim-badge">{{ dimensionLabel }}</text>
|
|
|
+ <text class="hero-level-badge" v-if="level">等级 {{ level }}</text>
|
|
|
<text class="hero-date">{{ dateText }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -17,17 +18,19 @@
|
|
|
<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>
|
|
|
+ <text class="hero-score-label">{{ percentile !== null ? '总得分' : '综合得分' }}</text>
|
|
|
</view>
|
|
|
+ <template v-if="percentile !== null">
|
|
|
+ <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>
|
|
|
+ </template>
|
|
|
</view>
|
|
|
|
|
|
<!-- 百分位进度条:位置标记 -->
|
|
|
- <view class="hero-pct-bar-wrap" v-if="totalScore !== null">
|
|
|
+ <view class="hero-pct-bar-wrap" v-if="percentile !== null">
|
|
|
<view class="hero-pct-track">
|
|
|
<view class="hero-pct-fill" :style="'width:' + percentile + '%'"></view>
|
|
|
<view class="hero-pct-marker" :style="'left:' + percentile + '%'"></view>
|
|
|
@@ -44,7 +47,7 @@
|
|
|
</view>
|
|
|
|
|
|
<template v-else-if="hasContent">
|
|
|
- <!-- ==================== 核心能力(认知类指标) ==================== -->
|
|
|
+ <!-- ==================== 核心能力(认知/综合能力类,百分位制) ==================== -->
|
|
|
<view class="card" v-if="abilityBlock">
|
|
|
<view class="card-title-row">
|
|
|
<view class="card-title-bar"></view>
|
|
|
@@ -61,13 +64,12 @@
|
|
|
<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>
|
|
|
|
|
|
- <!-- ==================== 大五人格 ==================== -->
|
|
|
+ <!-- ==================== 大五人格(1-5 分制) ==================== -->
|
|
|
<view class="card" v-if="bigFiveBlock">
|
|
|
<view class="card-title-row">
|
|
|
<view class="card-title-bar"></view>
|
|
|
@@ -81,14 +83,13 @@
|
|
|
</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>
|
|
|
|
|
|
- <!-- ==================== 情绪状态 ==================== -->
|
|
|
+ <!-- ==================== 情绪状态(0-10 分制) ==================== -->
|
|
|
<view class="card" v-if="emotionBlock">
|
|
|
<view class="card-title-row">
|
|
|
<view class="card-title-bar"></view>
|
|
|
@@ -105,33 +106,37 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- ==================== 其他指标(身高体重等,过滤噪音) ==================== -->
|
|
|
- <view class="card" v-if="otherMetrics.length > 0">
|
|
|
+ <!-- ==================== 通用指标块(自我概念/自驱力等,量纲自适应) ==================== -->
|
|
|
+ <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">基本信息</text>
|
|
|
+ <text class="card-title">{{ blk.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 class="metric-row" v-for="(m, mi) in blk.metrics" :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>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="metric-track">
|
|
|
+ <view class="metric-fill" :class="m.warn ? 'metric-fill-warn' : 'metric-fill-main'"
|
|
|
+ :style="'width:' + m.pct + '%'"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- ==================== 通用指标块(其他未分类维度,如执行功能/学习动机等) ==================== -->
|
|
|
- <view class="card" v-for="(blk, bi) in genericBlocks" :key="'g' + bi">
|
|
|
+ <!-- ==================== 基本信息(身高体重等,过滤噪音) ==================== -->
|
|
|
+ <view class="card" v-if="otherMetrics.length > 0">
|
|
|
<view class="card-title-row">
|
|
|
<view class="card-title-bar"></view>
|
|
|
- <text class="card-title">{{ blk.title }}</text>
|
|
|
+ <text class="card-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 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>
|
|
|
@@ -176,6 +181,8 @@ import { getDanReportDetail } from '../../utils/api.js'
|
|
|
|
|
|
// 噪音指标过滤(OCR 带出的非测评数据)
|
|
|
var NOISE_NAME_KEYS = ['联系电话', '报告编码', '测试时间', '档案', '具体情况', '页', '公众号']
|
|
|
+// 百分位制 category(0-100)
|
|
|
+var PERCENT_CATEGORIES = ['overall', 'cognitive']
|
|
|
|
|
|
export default {
|
|
|
data() {
|
|
|
@@ -186,9 +193,11 @@ export default {
|
|
|
dateText: '',
|
|
|
dimension: '',
|
|
|
reportType: '',
|
|
|
+ level: '',
|
|
|
totalScore: null,
|
|
|
- percentile: 0,
|
|
|
+ percentile: null,
|
|
|
blocks: [],
|
|
|
+ catMap: {},
|
|
|
adviceExpanded: false,
|
|
|
adviceSourceTitle: '',
|
|
|
adviceSourceText: '',
|
|
|
@@ -214,16 +223,17 @@ export default {
|
|
|
if (this.reportType) base += ' · ' + this.reportType
|
|
|
return base || 'DAN 测评'
|
|
|
},
|
|
|
- // 认知能力/核心能力块(标题含"能力"或"认知")
|
|
|
+ // 核心能力块(优先"认知能力",其次含"能力"的块)
|
|
|
abilityBlock() {
|
|
|
+ var fallback = null
|
|
|
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
|
|
|
- }
|
|
|
+ if (b.type !== 'indicator' || !b.items || !b.items.length) continue
|
|
|
+ var t = String(b.title || '')
|
|
|
+ if (t.indexOf('认知') >= 0) return b
|
|
|
+ if (t.indexOf('能力') >= 0 && !fallback) fallback = b
|
|
|
}
|
|
|
- return null
|
|
|
+ return fallback
|
|
|
},
|
|
|
abilityMetrics() {
|
|
|
if (!this.abilityBlock) return []
|
|
|
@@ -232,9 +242,9 @@ export default {
|
|
|
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))
|
|
|
+ var pct = isNaN(v) ? 0 : self.clampPct(v)
|
|
|
return {
|
|
|
- name: it.name.replace('百分位', ''),
|
|
|
+ name: String(it.name).replace('百分位', ''),
|
|
|
value: it.value,
|
|
|
pct: pct,
|
|
|
warn: !isNaN(v) && v < 50
|
|
|
@@ -262,7 +272,7 @@ export default {
|
|
|
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 }
|
|
|
+ return { name: it.name, value: it.value, pct: Math.round(pct), tone: tone, hint: hint }
|
|
|
})
|
|
|
},
|
|
|
// 情绪状态块
|
|
|
@@ -277,15 +287,16 @@ export default {
|
|
|
},
|
|
|
emotionMetrics() {
|
|
|
if (!this.emotionBlock) return []
|
|
|
+ var self = this
|
|
|
return (this.emotionBlock.items || []).filter(function(it) {
|
|
|
- return NOISE_NAME_KEYS.every(function(k) { return String(it.name).indexOf(k) < 0 })
|
|
|
+ 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 * 10))
|
|
|
- return { name: it.name, value: it.value, pct: pct }
|
|
|
+ return { name: it.name, value: it.value, pct: Math.round(pct) }
|
|
|
})
|
|
|
},
|
|
|
- // 其他指标("其他"类,过滤噪音,提取身高体重等)
|
|
|
+ // 其他指标("其他指标"块,过滤噪音,提取身高体重等)
|
|
|
otherMetrics() {
|
|
|
for (var i = 0; i < this.blocks.length; i++) {
|
|
|
var b = this.blocks[i]
|
|
|
@@ -295,7 +306,7 @@ export default {
|
|
|
return !self.isNoise(it.name)
|
|
|
}).map(function(it) {
|
|
|
var unit = ''
|
|
|
- var name = it.name
|
|
|
+ var name = String(it.name)
|
|
|
if (name.indexOf('身高') >= 0) { unit = 'cm' }
|
|
|
if (name.indexOf('体重') >= 0) { unit = 'kg' }
|
|
|
return { name: name, value: it.value, unit: unit }
|
|
|
@@ -304,7 +315,7 @@ export default {
|
|
|
}
|
|
|
return []
|
|
|
},
|
|
|
- // 通用指标块(除已专项渲染的 score/认知/人格/情绪/其他 之外的 indicator/text)
|
|
|
+ // 通用指标块(自我概念/自驱力/社会关系等未专项渲染的 indicator)
|
|
|
genericBlocks() {
|
|
|
var taken = []
|
|
|
if (this.abilityBlock) taken.push(this.abilityBlock)
|
|
|
@@ -322,15 +333,11 @@ export default {
|
|
|
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 }
|
|
|
+ return self.toMetric(it)
|
|
|
})
|
|
|
}
|
|
|
}).filter(function(b) { return b.metrics.length > 0 })
|
|
|
},
|
|
|
- // 建议/评语(text 块优先,其次 result.growthSuggestions)
|
|
|
adviceTitle() {
|
|
|
return this.adviceSourceTitle || '成长建议'
|
|
|
},
|
|
|
@@ -353,6 +360,28 @@ export default {
|
|
|
}
|
|
|
return false
|
|
|
},
|
|
|
+ clampPct(v) {
|
|
|
+ return Math.round(Math.min(100, Math.max(0, v)))
|
|
|
+ },
|
|
|
+ // 依据 category / 名称推断量纲上限
|
|
|
+ resolveMax(name, category) {
|
|
|
+ if (name && String(name).indexOf('百分位') >= 0) return 100
|
|
|
+ if (category === 'bigFive') return 5
|
|
|
+ if (PERCENT_CATEGORIES.indexOf(category) >= 0) return 100
|
|
|
+ return 10
|
|
|
+ },
|
|
|
+ toMetric(it) {
|
|
|
+ var v = parseFloat(it.value)
|
|
|
+ if (isNaN(v)) {
|
|
|
+ return { name: it.name, value: it.value, pct: 0, warn: false }
|
|
|
+ }
|
|
|
+ var cat = this.catMap[it.name] || ''
|
|
|
+ var max = this.resolveMax(it.name, cat)
|
|
|
+ var pct = Math.min(100, Math.max(0, v / max * 100))
|
|
|
+ // 仅百分位制指标(A2 认知等)低于 50 才告警;人格/自我概念等非能力项不告警
|
|
|
+ var warn = (max === 100) && v < 50
|
|
|
+ return { name: it.name, value: it.value, pct: Math.round(pct), warn: warn }
|
|
|
+ },
|
|
|
loadData() {
|
|
|
var self = this
|
|
|
this.loading = true
|
|
|
@@ -369,26 +398,46 @@ export default {
|
|
|
self.dateText = dateRaw ? String(dateRaw).substring(0, 10) : ''
|
|
|
self.dimension = data.dimension || ''
|
|
|
self.reportType = data.reportType || ''
|
|
|
+ self.level = result.danLevel || ''
|
|
|
self.blocks = (data.blocks || []).filter(function(b) {
|
|
|
return b && ((b.items && b.items.length) || b.content)
|
|
|
})
|
|
|
+
|
|
|
+ // 从 structuredAnalysis 构建 名称→category 映射(blocks item 已丢 category)
|
|
|
+ var catMap = {}
|
|
|
+ if (result.structuredAnalysis) {
|
|
|
+ try {
|
|
|
+ var arr = JSON.parse(result.structuredAnalysis)
|
|
|
+ for (var ai = 0; ai < arr.length; ai++) {
|
|
|
+ if (arr[ai] && arr[ai].name) catMap[arr[ai].name] = arr[ai].category || ''
|
|
|
+ }
|
|
|
+ } catch (e) {}
|
|
|
+ }
|
|
|
+ self.catMap = catMap
|
|
|
+
|
|
|
// 提取总分/百分位(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))
|
|
|
+ var label = String(it.label)
|
|
|
+ if (label.indexOf('总') >= 0) {
|
|
|
+ var tv = parseFloat(it.value)
|
|
|
+ if (!isNaN(tv)) self.totalScore = tv
|
|
|
+ } else if (label.indexOf('百分位') >= 0) {
|
|
|
+ var pv = parseFloat(it.value)
|
|
|
+ if (!isNaN(pv)) self.percentile = Math.min(100, Math.max(0, pv))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- // score 块本身不再单独渲染(已并入 Hero)
|
|
|
+ // 兜底:无 score 块(如 B4 智维度)时,用 result.overallScore 作综合得分
|
|
|
+ if (self.totalScore === null && result.overallScore !== null && result.overallScore !== undefined) {
|
|
|
+ var ov = parseFloat(result.overallScore)
|
|
|
+ if (!isNaN(ov)) self.totalScore = ov
|
|
|
+ }
|
|
|
+
|
|
|
// text 块:成长建议优先(非空 content),评语单独
|
|
|
for (var k = 0; k < self.blocks.length; k++) {
|
|
|
var tb = self.blocks[k]
|
|
|
@@ -432,12 +481,18 @@ export default {
|
|
|
|
|
|
.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-row { display: flex; align-items: center; margin-top: 14rpx; flex-wrap: wrap; }
|
|
|
.hero-dim-badge {
|
|
|
font-size: 22rpx; color: #FFFFFF;
|
|
|
background: rgba(255,255,255,0.22);
|
|
|
border-radius: 24rpx; padding: 6rpx 18rpx;
|
|
|
- margin-right: 16rpx;
|
|
|
+ margin-right: 14rpx;
|
|
|
+}
|
|
|
+.hero-level-badge {
|
|
|
+ font-size: 22rpx; color: #7C2D12; font-weight: 600;
|
|
|
+ background: linear-gradient(135deg, #FDE68A, #FBBF24);
|
|
|
+ border-radius: 24rpx; padding: 6rpx 18rpx;
|
|
|
+ margin-right: 14rpx;
|
|
|
}
|
|
|
.hero-date { font-size: 24rpx; color: rgba(255,255,255,0.75); }
|
|
|
|
|
|
@@ -488,7 +543,7 @@ export default {
|
|
|
.card-title { font-size: 30rpx; font-weight: 600; color: #1F2937; }
|
|
|
.card-title-sub { font-size: 22rpx; color: #9CA3AF; margin-left: 14rpx; }
|
|
|
|
|
|
-/* ==================== 核心能力指标 ==================== */
|
|
|
+/* ==================== 指标条 ==================== */
|
|
|
.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; }
|