|
@@ -1,23 +1,170 @@
|
|
|
<template>
|
|
<template>
|
|
|
<view class="container">
|
|
<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>
|
|
</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>
|
|
|
|
|
|
|
|
<view class="bottom-spacer"></view>
|
|
<view class="bottom-spacer"></view>
|
|
@@ -25,33 +172,173 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
-import ReportBlocksRenderer from '../../components/report-blocks-renderer.vue'
|
|
|
|
|
import { getDanReportDetail } from '../../utils/api.js'
|
|
import { getDanReportDetail } from '../../utils/api.js'
|
|
|
|
|
|
|
|
|
|
+// 噪音指标过滤(OCR 带出的非测评数据)
|
|
|
|
|
+var NOISE_NAME_KEYS = ['联系电话', '报告编码', '测试时间', '档案', '具体情况', '页', '公众号']
|
|
|
|
|
+
|
|
|
export default {
|
|
export default {
|
|
|
- components: { ReportBlocksRenderer },
|
|
|
|
|
data() {
|
|
data() {
|
|
|
return {
|
|
return {
|
|
|
reportId: null,
|
|
reportId: null,
|
|
|
loading: true,
|
|
loading: true,
|
|
|
- headInfo: null,
|
|
|
|
|
- blocks: []
|
|
|
|
|
|
|
+ childName: '',
|
|
|
|
|
+ dateText: '',
|
|
|
|
|
+ dimension: '',
|
|
|
|
|
+ reportType: '',
|
|
|
|
|
+ totalScore: null,
|
|
|
|
|
+ percentile: 0,
|
|
|
|
|
+ blocks: [],
|
|
|
|
|
+ adviceExpanded: false,
|
|
|
|
|
+ adviceSourceTitle: '',
|
|
|
|
|
+ adviceSourceText: '',
|
|
|
|
|
+ summarySourceText: ''
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
computed: {
|
|
computed: {
|
|
|
|
|
+ hasContent() {
|
|
|
|
|
+ return this.blocks.length > 0
|
|
|
|
|
+ },
|
|
|
dimensionLabel() {
|
|
dimensionLabel() {
|
|
|
- if (!this.headInfo || !this.headInfo.dimension) return ''
|
|
|
|
|
var map = {
|
|
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) {
|
|
onLoad(options) {
|
|
@@ -59,6 +346,13 @@ export default {
|
|
|
if (this.reportId) this.loadData()
|
|
if (this.reportId) this.loadData()
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
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() {
|
|
loadData() {
|
|
|
var self = this
|
|
var self = this
|
|
|
this.loading = true
|
|
this.loading = true
|
|
@@ -69,12 +363,47 @@ export default {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
var data = res.data
|
|
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) {
|
|
}).catch(function(e) {
|
|
|
self.loading = false
|
|
self.loading = false
|
|
|
uni.showToast({ title: '加载异常', icon: 'none' })
|
|
uni.showToast({ title: '加载异常', icon: 'none' })
|
|
@@ -85,16 +414,164 @@ export default {
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<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>
|
|
</style>
|