index.vue 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209
  1. <template>
  2. <view class="wisdom-container">
  3. <!-- Tabbar 切换过渡页 -->
  4. <tab-transition v-if="showTabTransition" dimCode="wisdom" ref="tabTransition" @close="showTabTransition = false" />
  5. <!-- 品牌定位语(登录前显示) -->
  6. <view class="brand-strap" v-if="!isLoggedIn">
  7. <text class="brand-strap-text">提升全家认知能力与学习效率</text>
  8. </view>
  9. <!-- 2. 学习能力评估图(始终显示,无数据时仅显示人群平均值) -->
  10. <view class="section">
  11. <view class="section-header">
  12. <text class="section-title">学习能力评估</text>
  13. <text class="section-sub">通过测评了解全家认知能力,找出可以提升的方向</text>
  14. <text class="avg-badge" v-if="!cognitiveReport">同龄平均值 · 完成认知测评后显示个人数据</text>
  15. <text class="section-more" v-if="isLoggedIn" @click="goCognitiveReport">查看详情 ›</text>
  16. </view>
  17. <RadarChart
  18. :dimensions="cognitiveDimensions"
  19. :scores="cognitiveScores"
  20. :avg-scores="cognitiveFamilyScores"
  21. :population-scores="cognitivePopulationScores"
  22. :childName="activeChildName"
  23. fillColor="#6366F1"
  24. gridColor="#E0E7FF"
  25. labelColor="#3730A3" />
  26. </view>
  27. <!-- 家庭成员选择条 -->
  28. <FamilyMemberStrip
  29. v-if="currentView === 'self' && isLoggedIn"
  30. :members="familyMembersVisible"
  31. :selectedMemberId="selectedMemberId"
  32. @select="onFamilyMemberSelect"
  33. @removed="loadFamilyMembersVisible" />
  34. <!-- 游客引导语 -->
  35. <view class="monthly-guidance" v-if="!isLoggedIn">
  36. <text class="guidance-icon">⚡</text>
  37. <text class="guidance-text">{{ monthlyGuidance }}</text>
  38. </view>
  39. <!-- 4. 五维能量条(登录后) -->
  40. <FamilyEnergyBar
  41. v-if="isLoggedIn"
  42. dimensionCode="wisdom"
  43. :sandboxData="sandboxData"
  44. :dualDimension="dualDimension" />
  45. <!-- 认知指标卡片 -->
  46. <view class="indicator-cards-section" v-if="isLoggedIn && wisdomIndicators.length > 0">
  47. <view class="section-header">
  48. <text class="section-title">⚡ 认知指标</text>
  49. <text class="section-more" @click="goWisdomDetail">更多 ›</text>
  50. </view>
  51. <view class="indicator-cards">
  52. <view
  53. v-for="(ind, i) in wisdomIndicators"
  54. :key="ind.code"
  55. class="indicator-card"
  56. :class="{ 'no-data': !ind.value }"
  57. @click="goWisdomDetail()"
  58. >
  59. <view class="indicator-header">
  60. <text class="indicator-name">{{ ind.name }}</text>
  61. <text class="indicator-unit" v-if="ind.unit">{{ ind.unit }}</text>
  62. </view>
  63. <view class="indicator-body">
  64. <text class="indicator-value" v-if="ind.value || ind.numericValue !== null">{{ ind.value || formatNumeric(ind.numericValue) }}</text>
  65. <text class="indicator-placeholder" v-else>暂无数据</text>
  66. </view>
  67. <view class="indicator-footer">
  68. <text class="indicator-range" v-if="ind.refRange">参考: {{ ind.refRange }}</text>
  69. </view>
  70. </view>
  71. </view>
  72. </view>
  73. <!-- 学习解读 -->
  74. <view class="section score-explain-card" v-if="isLoggedIn && currentView === 'self' && dualDimension">
  75. <view class="explain-header">
  76. <text class="explain-icon">{{ (dualDimension.primary && dualDimension.primary.score || 0) >= 80 ? '✅' : (dualDimension.primary && dualDimension.primary.score || 0) >= 60 ? '⚠️' : '❗' }}</text>
  77. <text class="explain-title">学习解读</text>
  78. </view>
  79. <text class="explain-text">{{ (dualDimension.primary && dualDimension.primary.score || 0) >= 80 ? '学习能力良好,继续保持学习节奏。' : (dualDimension.primary && dualDimension.primary.score || 0) >= 60 ? '学习能力一般,建议多做认知训练。' : '学习能力需要提升,建议进行专业评估。' }}</text>
  80. </view>
  81. <!-- ===== 认知强项与提升空间 ===== -->
  82. <view class="section cognitive-insight" v-if="isLoggedIn && cognitiveReport">
  83. <view class="section-header">
  84. <text class="section-title">📈 认知强项与提升</text>
  85. </view>
  86. <template v-if="cogStrengths.length > 0 || cogImproves.length > 0">
  87. <view class="insight-group" v-if="cogStrengths.length > 0">
  88. <text class="insight-group-title">💪 你的强项</text>
  89. <view class="insight-item" v-for="(item, idx) in cogStrengths" :key="getInsightKey('s', idx)">
  90. <text class="insight-item-name">{{ item.label }}</text>
  91. <text class="insight-item-score">{{ item.score }}分</text>
  92. <text class="insight-item-tag tag-strong">强项</text>
  93. </view>
  94. </view>
  95. <view class="insight-group" v-if="cogImproves.length > 0">
  96. <text class="insight-group-title">🌱 提升空间</text>
  97. <view class="insight-item" v-for="(item, idx) in cogImproves" :key="getInsightKey('i', idx)">
  98. <text class="insight-item-name">{{ item.label }}</text>
  99. <text class="insight-item-score">{{ item.score }}分</text>
  100. <text class="insight-item-tag tag-improve">待提升</text>
  101. </view>
  102. </view>
  103. <view class="insight-tip" v-if="cogStrengths.length > 0 && cogImproves.length > 0">
  104. <text class="insight-tip-text">强项保持,薄弱维建议通过训练中心针对性提升</text>
  105. </view>
  106. </template>
  107. <view class="insight-empty" v-else-if="!cognitiveReport || !hasCognitiveScore">
  108. <text class="insight-empty-text">完成认知测评,查看强项与提升建议</text>
  109. <button class="insight-btn" @click="goCognitiveReport">去测评</button>
  110. </view>
  111. </view>
  112. <!-- 心维度画像 - 总体说明 -->
  113. <view class="dimension-portrait-section" v-if="isLoggedIn && wisdomPortrait">
  114. <view class="section-header">
  115. <text class="section-title">🧭 心维度画像</text>
  116. <text class="portrait-tier">{{ wisdomPortrait.tierLabel }}</text>
  117. </view>
  118. <view class="portrait-summary">
  119. <text class="portrait-desc">{{ wisdomPortrait.summary }}</text>
  120. </view>
  121. <view class="portrait-dimensions">
  122. <view class="portrait-dim-item" v-for="dim in wisdomPortrait.dims" :key="dim.code">
  123. <view class="portrait-dim-header">
  124. <view class="portrait-dim-icon" :style="{ background: dim.color }">{{ dim.icon }}</view>
  125. <text class="portrait-dim-name">{{ dim.name }}</text>
  126. <text class="portrait-dim-score">{{ dim.score }}</text>
  127. </view>
  128. <view class="portrait-dim-bar-track">
  129. <view class="portrait-dim-bar-fill" :style="{ width: dim.score + '%', background: dim.color }"></view>
  130. </view>
  131. <text class="portrait-dim-desc">{{ dim.desc }}</text>
  132. </view>
  133. </view>
  134. <view class="portrait-tip" v-if="wisdomPortrait.tip">
  135. <text class="portrait-tip-text">💡 {{ wisdomPortrait.tip }}</text>
  136. </view>
  137. </view>
  138. <!-- ===== DAN 报告(登录后) ===== -->
  139. <view class="section" v-if="isLoggedIn">
  140. <view class="section-header">
  141. <text class="section-title">&#x1F4CA; DAN 报告</text>
  142. <text class="section-more" @click="goDanUpload">上传 +</text>
  143. </view>
  144. <view class="dan-card" v-for="report in danReports" :key="report.id" @click="viewDanReport(report)">
  145. <view class="dan-card-header">
  146. <text class="dan-report-date">{{ formatDate(report.assessmentDate) }}</text>
  147. <text :class="['dan-status', report.draftStatus === 'confirmed' ? 'confirmed' : 'pending']">
  148. {{ report.draftStatus === 'confirmed' ? '已确认' : '待确认' }}
  149. </text>
  150. </view>
  151. <text class="dan-report-desc">DAN B4 智维度测评报告</text>
  152. </view>
  153. <view class="dan-empty" v-if="danReports.length === 0">
  154. <text class="dan-empty-text">暂无 DAN 报告,点击上方「上传 +」上传测评报告</text>
  155. </view>
  156. </view>
  157. <!-- 肠胃与认知卡片(有菌群报告显示认知指标,无报告引导上传) -->
  158. <view class="section gut-cognition-card" v-if="isLoggedIn">
  159. <view class="section-header">
  160. <text class="section-title">🦠 肠胃与认知</text>
  161. </view>
  162. <text class="gut-cog-subtitle">肠脑轴 · 肠道菌群,影响学习与思维</text>
  163. <template v-if="cognitionGutIndicators && cognitionGutIndicators.length > 0">
  164. <view class="gut-indicator-list">
  165. <view class="gut-indicator-item" v-for="ind in cognitionGutIndicators" :key="ind.id">
  166. <view class="gut-indicator-left">
  167. <text class="gut-indicator-name">{{ ind.indicatorName || ind.name }}</text>
  168. <text class="gut-indicator-impact" v-if="cognitiveImpactLabel(ind.indicatorName || ind.name)">影响{{ cognitiveImpactLabel(ind.indicatorName || ind.name) }}</text>
  169. </view>
  170. <view class="gut-indicator-bar-track">
  171. <view class="gut-indicator-bar-fill" :style="{ width: indicatorBarWidth(ind), background: indicatorColor(ind.status) }"></view>
  172. </view>
  173. <text class="gut-indicator-value">{{ formatIndicatorValue(ind) }}{{ ind.unit || '%' }}</text>
  174. <text :class="['gut-indicator-status', 'status-' + (ind.status || 'normal')]">{{ statusLabel(ind.status) }}</text>
  175. </view>
  176. </view>
  177. </template>
  178. <view class="gut-empty" v-else>
  179. <text class="gut-empty-icon">📄</text>
  180. <text class="gut-empty-text">上传菌群报告,查看认知相关指标</text>
  181. <button class="gut-upload-btn" @click="goReportUpload">去上传</button>
  182. </view>
  183. </view>
  184. <!-- 6. 功能入口 -->
  185. <view class="func-section" v-if="sectionVisible('func_entries')">
  186. <view class="func-grid">
  187. <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
  188. <view class="func-icon-wrap">
  189. <text class="func-icon">{{ item.icon }}</text>
  190. </view>
  191. <text class="func-label">{{ item.label }}</text>
  192. </view>
  193. </view>
  194. </view>
  195. <!-- 智维度介绍卡片(登录后显示) -->
  196. <DimensionIntroCard dimCode="wisdom" v-if="isLoggedIn" learn-more-url="/pages/wisdom-detail/dimension-intro" />
  197. <!-- 底部占位 -->
  198. <view class="bottom-spacer"></view>
  199. <FloatingAvatar />
  200. </view>
  201. </template>
  202. <script>
  203. import nav from '../../utils/nav'
  204. import TabTransition from '../../components/tab-transition.vue'
  205. import RadarChart from '../../components/RadarChart.vue'
  206. import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
  207. import FloatingAvatar from '../../components/AIFloatingAvatar.vue'
  208. import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
  209. import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
  210. import { getVisibleSections, getChildren, getFamilyEnergySandbox, getEnergySandbox, getAssessmentLatestResult, getDanReportByChild, getGutCognitionIndicators, getLatestIndicators, getRadarData } from '../../utils/api.js'
  211. import { parseDate } from '../../utils/format.js'
  212. export default {
  213. components: { TabTransition, RadarChart, FamilyEnergyBar, FloatingAvatar, FamilyMemberStrip, DimensionIntroCard },
  214. data() {
  215. return {
  216. isLoggedIn: false,
  217. role: null,
  218. showTabTransition: true,
  219. activeChildId: null,
  220. activeChildName: '',
  221. userName: '',
  222. children: [],
  223. familyMembersVisible: [],
  224. selectedMemberId: null,
  225. wisdomSubDims: [],
  226. wisdomIndicators: [],
  227. sandboxData: null,
  228. currentView: 'self', // 'self' | 'family'
  229. dualDimension: null,
  230. visibleSections: [],
  231. cognitiveReport: null,
  232. articles: [],
  233. /** 认知雷达图:家庭平均分(默认取同龄同性 P50 常模) */
  234. cognitiveFamilyScores: [],
  235. /** 认知雷达图:人群位置(同龄同性 P50 常模,接口返回,失败时不显示假数据) */
  236. cognitivePopulationScores: [],
  237. danReports: [],
  238. cognitionGutIndicators: [],
  239. funcList: [
  240. { icon: '\u{1F4CA}', label: '认知报告', needLogin: true, page: '/pages/wisdom-detail/cognitive-report' },
  241. { icon: '\u{1F9E0}', label: '训练中心', needLogin: true, page: '/pages/wisdom-detail/training-hub' },
  242. { icon: '\u{1F3AF}', label: '测评预约', needLogin: false, page: '/pages/assessment/apply' },
  243. { icon: '\u{1F4D6}', label: '阅读天地', needLogin: false, page: '/pages/article-center/index?dimension=wisdom' }
  244. ]
  245. }
  246. },
  247. computed: {
  248. wisdomArticles: function() {
  249. if (!this.articles || this.articles.length === 0) return []
  250. var result = []
  251. for (var i = 0; i < this.articles.length; i++) {
  252. var a = this.articles[i]
  253. if (a.relatedDimensions && a.relatedDimensions.indexOf('wisdom') !== -1) {
  254. result.push(a)
  255. }
  256. }
  257. if (result.length === 0) return this.articles.slice(0, 5)
  258. return result.slice(0, 5)
  259. },
  260. cognitiveDimensions: function() {
  261. return [
  262. { label: '感知能力', key: 'perceptionScore' },
  263. { label: '专注力', key: 'focusScore' },
  264. { label: '记忆力', key: 'memoryScore' },
  265. { label: '逻辑思维', key: 'logicScore' },
  266. { label: '空间思维', key: 'spatialScore' },
  267. { label: '加工速度', key: 'processingSpeedScore' },
  268. { label: '成长型思维', key: 'wisdomGrowthMindset' }
  269. ]
  270. },
  271. cognitiveScores: function() {
  272. if (!this.cognitiveReport) return []
  273. var dims = this.cognitiveDimensions
  274. var scores = []
  275. // 解析 structuredAnalysis 中的成长型思维
  276. var growthMindset = 0
  277. if (this.cognitiveReport.structuredAnalysis) {
  278. try {
  279. var analysis = JSON.parse(this.cognitiveReport.structuredAnalysis)
  280. if (analysis && analysis.extra && analysis.extra.growthMindset) {
  281. var gm = analysis.extra.growthMindset
  282. var vals = Object.values(gm)
  283. if (vals.length > 0) growthMindset = parseInt(vals[0]) || 0
  284. }
  285. } catch (e) { /* ignore parse error */ }
  286. }
  287. for (var i = 0; i < dims.length; i++) {
  288. var key = dims[i].key
  289. if (key === 'wisdomGrowthMindset') {
  290. scores.push(growthMindset)
  291. } else {
  292. scores.push(this.cognitiveReport[key] || 0)
  293. }
  294. }
  295. return scores
  296. },
  297. /** 认知测评是否已有有效得分(任一维度 > 0) */
  298. hasCognitiveScore: function() {
  299. if (!this.cognitiveReport) return false
  300. var dims = this.cognitiveDimensions
  301. for (var i = 0; i < dims.length; i++) {
  302. var key = dims[i].key
  303. var v = 0
  304. if (key === 'wisdomGrowthMindset') {
  305. v = this.cognitiveScores[this.cognitiveDimensions.length - 1] || 0
  306. } else {
  307. v = this.cognitiveReport[key] || 0
  308. }
  309. if (v > 0) return true
  310. }
  311. return false
  312. },
  313. /** 认知强项:得分 ≥75 的维度 */
  314. cogStrengths: function() {
  315. var dims = this.cognitiveDimensions
  316. var result = []
  317. for (var i = 0; i < dims.length; i++) {
  318. var score = this.cognitiveScores[i] || 0
  319. if (score >= 75) {
  320. result.push({ label: dims[i].label, score: score })
  321. }
  322. }
  323. return result
  324. },
  325. /** 认知提升空间:得分 <60 的维度 */
  326. cogImproves: function() {
  327. var dims = this.cognitiveDimensions
  328. var result = []
  329. for (var i = 0; i < dims.length; i++) {
  330. var score = this.cognitiveScores[i] || 0
  331. if (score > 0 && score < 60) {
  332. result.push({ label: dims[i].label, score: score })
  333. }
  334. }
  335. return result
  336. },
  337. /** 每月引导语 */
  338. monthlyGuidance: function() {
  339. var month = new Date().getMonth()
  340. var messages = [
  341. '一月:新年新目标,制定学习计划',
  342. '二月:春节收获知识,拓展认知边界',
  343. '三月:植树节到,播种智慧种子',
  344. '四月:清明踏青,激发创造力',
  345. '五月:劳动节后,专注能力提升',
  346. '六月:考试季,科学备考策略',
  347. '七月:暑期黄金期,查漏补缺',
  348. '八月:开学准备,提前规划学习',
  349. '九月:新学期,设定学习目标',
  350. '十月:国庆假期,阅读充电',
  351. '十一月:年终冲刺,巩固知识',
  352. '十二月:年度总结,规划来年学习'
  353. ]
  354. return messages[month] || messages[0]
  355. },
  356. wisdomPortrait: function() {
  357. var self = this
  358. if (!this.isLoggedIn || !this.cognitiveReport) return null
  359. var cognitiveReport = this.cognitiveReport
  360. var dims = [
  361. { key: 'perceptionScore', label: '感知能力', color: '#FF6B9D', desc: '感官获取信息的能力' },
  362. { key: 'focusScore', label: '专注力', color: '#F59E0B', desc: '保持注意力集中的能力' },
  363. { key: 'memoryScore', label: '记忆力', color: '#6366F1', desc: '记忆和回忆信息的能力' },
  364. { key: 'logicScore', label: '逻辑思维', color: '#10B981', desc: '分析和推理能力' },
  365. { key: 'spatialScore', label: '空间思维', color: '#F59E0B', desc: '操作空间关系的能力' },
  366. { key: 'processingSpeedScore', label: '加工速度', color: '#FF8C42', desc: '快速处理信息的能力' },
  367. { key: 'growthMindset', label: '成长型思维', color: '#EC4899', desc: '相信能力可提升的信念' }
  368. ]
  369. var result = { overall: 0, tierLabel: '', summary: '', dims: [], tip: '' }
  370. var total = 0
  371. var count = 0
  372. var dimList = []
  373. for (var i = 0; i < dims.length; i++) {
  374. var key = dims[i].key
  375. var score = 0
  376. if (key === 'growthMindset' && cognitiveReport.structuredAnalysis) {
  377. try {
  378. var analysis = JSON.parse(cognitiveReport.structuredAnalysis)
  379. if (analysis && analysis.extra && analysis.extra.growthMindset) {
  380. var gm = analysis.extra.growthMindset
  381. var vals = Object.values(gm)
  382. if (vals.length > 0) score = parseInt(vals[0]) || 0
  383. }
  384. } catch (e) { /* ignore parse error */ }
  385. } else {
  386. score = cognitiveReport[key] || 0
  387. }
  388. if (isNaN(score)) score = 0
  389. score = Math.max(0, Math.min(100, Number(score)))
  390. total += score
  391. count++
  392. var matched = dims.find(function(d) { return d.key === key })
  393. dimList.push({
  394. code: key,
  395. name: matched ? matched.label : key,
  396. score: Math.round(score),
  397. color: matched ? matched.color : '#FF6B9D',
  398. desc: matched ? matched.desc : ''
  399. })
  400. }
  401. if (count === 0) return null
  402. result.overall = Math.round(total / count)
  403. result.dims = dimList
  404. var o = result.overall
  405. if (o >= 80) {
  406. result.tierLabel = '认知卓越者'
  407. result.summary = '您的认知能力卓越,各项能力均衡强劲,学习效率高。'
  408. result.tip = '保持学习节奏,可进一步拓展更广的知识边界'
  409. } else if (o >= 60) {
  410. result.tierLabel = '认知进取者'
  411. result.summary = '您的认知能力处于良好水平,具备较强的学习潜力。'
  412. result.tip = '针对薄弱维度进行针对性训练,逐步突破瓶颈'
  413. } else if (o >= 40) {
  414. result.tierLabel = '认知探索者'
  415. result.summary = '您的认知能力正在建立基础,关注学习习惯的养成很重要。'
  416. result.tip = '从最容易提升的维度开始,每天投入少量学习时间'
  417. } else {
  418. result.tierLabel = '认知起步者'
  419. result.summary = '您的认知能力需要更多关注和引导,建议从基础认能训练开始。'
  420. result.tip = '建议咨询专业教育者,制定个性化认知发展计划'
  421. }
  422. return result
  423. }
  424. },
  425. onShow() {
  426. this.showTabTransition = true
  427. var token = uni.getStorageSync('token')
  428. this.isLoggedIn = !!token
  429. if (this.isLoggedIn) {
  430. this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  431. this.userName = uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '家长'
  432. }
  433. this.loadSectionConfig()
  434. if (this.isLoggedIn) {
  435. this.loadChildren()
  436. this.loadFamilyMembersVisible()
  437. this.loadCognitiveReport()
  438. this.loadDanReports()
  439. this.loadGutCognitionData()
  440. }
  441. // 游客也能浏览文章
  442. this._watchPageReady()
  443. },
  444. methods: {
  445. getInsightKey: function(prefix, idx) { return prefix + idx },
  446. onFamilyMemberSelect: function(member) {
  447. this.selectedMemberId = member.id
  448. },
  449. loadSectionConfig: function() {
  450. var baseSections = ['func_entries', 'recommended_reading']
  451. if (!uni.getStorageSync('token')) {
  452. this.visibleSections = baseSections
  453. return
  454. }
  455. var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  456. var self = this
  457. getVisibleSections({ pageKey: 'wisdom', role: role }).then(function(res) {
  458. if (res.code === 200 && res.data) {
  459. var keys = res.data.map(function(s) { return s.sectionKey })
  460. var merged = baseSections.slice()
  461. keys.forEach(function(k) { if (merged.indexOf(k) === -1) merged.push(k) })
  462. self.visibleSections = merged
  463. }
  464. }).catch(function(e) {
  465. self.visibleSections = ['func_entries', 'recommended_reading']
  466. })
  467. },
  468. loadChildren: function() {
  469. var self = this
  470. getChildren().then(function(res) {
  471. if (res.code === 200 && res.data) {
  472. self.children = res.data
  473. if (self.children.length > 0) {
  474. var activeId = uni.getStorageSync('currentChildId')
  475. if (activeId) {
  476. for (var i = 0; i < self.children.length; i++) {
  477. if (self.children[i].id == activeId) {
  478. self.activeChildId = activeId
  479. self.activeChildName = self.children[i].nickname || self.children[i].name || ''
  480. break
  481. }
  482. }
  483. }
  484. // Fallback: 首次进入或未选孩子时,默认选第一个孩子以加载认知数据(仅本地状态,不触发全局切换)
  485. if (!self.activeChildId && self.children.length > 0) {
  486. self.activeChildId = self.children[0].id
  487. self.activeChildName = self.children[0].nickname || self.children[0].name || ''
  488. }
  489. }
  490. // Load cognitive report if activeChildId is available
  491. if (self.activeChildId) {
  492. self.loadCognitiveReport()
  493. }
  494. }
  495. }).catch(function(e) {})
  496. },
  497. loadWisdomIndicators: function(memberId) {
  498. var self = this
  499. getLatestIndicators({ memberId: memberId, domain: 'wisdom' }).then(function(res) {
  500. if (res.code === 200) self.wisdomIndicators = res.data || []
  501. }).catch(function(e) { console.log('[wisdom] loadWisdomIndicators failed', e) })
  502. },
  503. goWisdomDetail: function() {
  504. var id = this.activeChildId || uni.getStorageSync('currentChildId')
  505. if (!id) return
  506. uni.navigateTo({ url: '/pages/wisdom-detail/member-wisdom-detail?memberId=' + id })
  507. },
  508. formatNumeric: function(v) {
  509. if (v === null || v === undefined) return ''
  510. var n = parseFloat(v)
  511. if (isNaN(n)) return String(v)
  512. return n % 1 === 0 ? String(n) : n.toFixed(1)
  513. },
  514. loadFamilyMembersVisible: function() {
  515. var self = this
  516. getFamilyEnergySandbox().then(function(res) {
  517. if (res && res.data) {
  518. self.sandboxData = res.data.sandboxData || null
  519. self.dualDimension = res.data.dualDimension || null
  520. if (res.data.members) {
  521. self.familyMembersVisible = res.data.members
  522. }
  523. }
  524. }).catch(function(e) {})
  525. },
  526. loadCognitiveReport: function() {
  527. var self = this
  528. var memberId = this.activeChildId
  529. if (!memberId) return
  530. getAssessmentLatestResult(memberId).then(function(res) {
  531. if (res.code === 200 && res.data) {
  532. self.cognitiveReport = res.data
  533. self.wisdomSubDims = (function() { var r = self.cognitiveReport || {}; return [{label:'感知能力', score:r.perceptionScore||0, desc:'通过感官获取信息'}, {label:'专注力', score:r.focusScore||0, desc:'保持注意力集中'}, {label:'记忆力', score:r.memoryScore||0, desc:'记忆和回忆信息'}, {label:'逻辑思维', score:r.logicScore||0, desc:'分析和推理能力'}, {label:'空间思维', score:r.spatialScore||0, desc:'操作空间关系'}, {label:'加工速度', score:r.processingSpeedScore||0, desc:'快速处理信息'}, {label:'成长型思维', score:r.growthMindset||0, desc:'相信能力可提升'}]; })();
  534. self.loadWisdomIndicators(memberId)
  535. self.loadCognitiveRadarData(memberId)
  536. }
  537. }).catch(function(e) {})
  538. },
  539. loadCognitiveRadarData: function(memberId) {
  540. var self = this
  541. if (!memberId) return
  542. getRadarData({ memberId: memberId, dimensions: ['perception','focus','memory','logic','spatial','processingSpeed','wisdomGrowthMindset'] }).then(function(res) {
  543. if (res && res.data) {
  544. self.cognitiveFamilyScores = res.data.familyScores || []
  545. self.cognitivePopulationScores = res.data.populationScores || []
  546. }
  547. }).catch(function(e) {
  548. console.warn('[wisdom] radar-data failed', e)
  549. })
  550. },
  551. onChildChanged: function(memberId) {
  552. this.activeChildId = memberId
  553. // FIX: 同步提交到 Vuex store
  554. this.$store.commit('switchToChild', memberId)
  555. for (var i = 0; i < this.children.length; i++) {
  556. if (this.children[i].id == memberId) {
  557. this.activeChildName = this.children[i].nickname || this.children[i].name || ''
  558. break
  559. }
  560. }
  561. this.loadFamilyMembersVisible()
  562. this.loadCognitiveReport()
  563. this.loadDanReports()
  564. },
  565. sectionVisible: function(key) {
  566. return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
  567. },
  568. onFuncClick: function(item) {
  569. if (item.needLogin && !this.isLoggedIn) {
  570. uni.showModal({
  571. title: '提示',
  572. content: '请先登录后使用此功能',
  573. success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
  574. })
  575. return
  576. }
  577. if (item.page) {
  578. nav.goPage(item.page)
  579. }
  580. },
  581. scrollToSection: function(section) {},
  582. goCognitiveReport: function() {
  583. uni.navigateTo({ url: '/pages/wisdom-detail/cognitive-report' })
  584. },
  585. _watchPageReady() {
  586. var self = this
  587. var unwatch = this.$watch(function() {
  588. if (self.isLoggedIn) {
  589. return !!(self.cognitiveReport || self.sandboxData)
  590. } }, function(val) {
  591. if (val) {
  592. if (unwatch) unwatch()
  593. if (self.$refs.tabTransition) self.$refs.tabTransition.markReady()
  594. }
  595. })
  596. },
  597. // ===== DAN 报告 =====
  598. loadDanReports: function() {
  599. var self = this
  600. var memberId = self.activeChildId
  601. if (!memberId) return
  602. getDanReportByChild(memberId, 'wisdom').then(function(res) {
  603. if (res.code === 200 && res.data) {
  604. self.danReports = res.data
  605. }
  606. }).catch(function() {})
  607. },
  608. // ===== 肠胃与认知(肠脑轴认知指标) =====
  609. loadGutCognitionData: function() {
  610. var self = this
  611. var memberId = self.activeChildId
  612. if (!memberId) return
  613. getGutCognitionIndicators(memberId).then(function(res) {
  614. if (res.code === 200 && res.data) {
  615. var list = res.data
  616. self.cognitionGutIndicators = Array.isArray(list) ? list.slice(0, 6) : []
  617. } else {
  618. self.cognitionGutIndicators = []
  619. }
  620. }).catch(function() {
  621. self.cognitionGutIndicators = []
  622. })
  623. },
  624. // 认知影响映射:指标名 → 影响的认知功能
  625. cognitiveImpactLabel: function(name) {
  626. if (!name) return ''
  627. if (name.indexOf('多巴胺') !== -1 || name.indexOf('Dopamine') !== -1) return '专注力'
  628. if (name.indexOf('谷氨酸') !== -1 || name.indexOf('Glutamate') !== -1) return '学习记忆'
  629. if (name.indexOf('乙酰胆碱') !== -1) return '记忆力'
  630. if (name.indexOf('色氨酸') !== -1 || name.indexOf('Tryptophan') !== -1) return '睡眠·记忆'
  631. if (name.indexOf('丁酸') !== -1 || name.indexOf('Butyrate') !== -1) return '脑健康'
  632. if (name.indexOf('丙酸') !== -1 || name.indexOf('Propionate') !== -1) return '脑健康'
  633. if (name.indexOf('乙酸') !== -1 || name.indexOf('Acetate') !== -1) return '脑健康'
  634. if (name.indexOf('组胺') !== -1 || name.indexOf('Histamine') !== -1) return '注意力·觉醒'
  635. if (name.indexOf('短链脂肪酸') !== -1) return '脑健康'
  636. return ''
  637. },
  638. indicatorColor: function(status) {
  639. if (status === 'abnormal' || status === '偏高' || status === '偏低' || status === '过高' || status === '缺乏') return '#EF4444'
  640. if (status === 'warning' || status === '注意' || status === '不足') return '#F59E0B'
  641. return '#10B981'
  642. },
  643. statusLabel: function(status) {
  644. if (status === 'normal') return '正常'
  645. if (status === 'abnormal') return '异常'
  646. if (status === 'warning') return '警讯'
  647. if (status === '过高' || status === '过多') return '偏高'
  648. if (status === '缺乏' || status === '不足') return '偏低'
  649. return status || '正常'
  650. },
  651. // 指标值展示:后端字段为 indicatorValue,兼容数值型(如 0.32)
  652. formatIndicatorValue: function(ind) {
  653. if (!ind) return ''
  654. var v = ind.value !== undefined && ind.value !== null ? ind.value : ind.indicatorValue
  655. if (v === undefined || v === null || v === '') return ''
  656. return String(v)
  657. },
  658. // 指标条宽度:将指标值归一化为 0-100 百分比(仅对纯数字有效,否则按状态给默认宽度)
  659. indicatorBarWidth: function(ind) {
  660. var raw = ind.value !== undefined && ind.value !== null ? ind.value : ind.indicatorValue
  661. var n = parseFloat(raw)
  662. if (!isNaN(n) && n >= 0 && n <= 100) return n + '%'
  663. if (!isNaN(n) && n > 100) return '100%'
  664. // 非数值(如 "偏低"/"未检出"),按状态给默认可视化宽度
  665. var st = ind.status || ''
  666. if (st === '偏低' || st === '缺乏' || st === '不足' || st === 'abnormal') return '30%'
  667. if (st === '过高' || st === '过多' || st === 'warning') return '75%'
  668. return '55%'
  669. },
  670. goReportUpload: function() {
  671. uni.navigateTo({ url: '/pages/health/report-upload' })
  672. },
  673. goDanUpload: function() {
  674. uni.navigateTo({ url: '/pages/health/report-upload?type=a2' })
  675. },
  676. viewDanReport: function(report) {
  677. var reportId = report.sourceReportId || report.id
  678. uni.navigateTo({ url: '/pages/dan-assessment/report-result?reportId=' + reportId })
  679. },
  680. formatDate: function(dateStr) {
  681. if (!dateStr) return ''
  682. var d = parseDate(dateStr)
  683. if (!d) return ''
  684. return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate()
  685. }
  686. }
  687. }
  688. </script>
  689. <style scoped>
  690. .wisdom-container {
  691. min-height: 100vh;
  692. background: #f5f7fa;
  693. padding-bottom: 120rpx;
  694. }
  695. .func-section {
  696. margin: 20rpx 20rpx 0;
  697. }
  698. .func-grid {
  699. background: #fff;
  700. border-radius: 24rpx;
  701. padding: 30rpx 16rpx;
  702. display: flex;
  703. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  704. }
  705. .func-item {
  706. flex: 1;
  707. display: flex;
  708. flex-direction: column;
  709. align-items: center;
  710. }
  711. .func-item:active {
  712. opacity: 0.7;
  713. }
  714. .func-icon-wrap {
  715. width: 88rpx;
  716. height: 88rpx;
  717. border-radius: 50%;
  718. background: rgba(99,102,241,0.12);
  719. display: flex;
  720. align-items: center;
  721. justify-content: center;
  722. margin-bottom: 10rpx;
  723. }
  724. .func-icon {
  725. font-size: 40rpx;
  726. }
  727. .func-label {
  728. font-size: 24rpx;
  729. color: #333;
  730. font-weight: 500;
  731. }
  732. /* ===== Section ===== */
  733. .section {
  734. margin: 20rpx 20rpx;
  735. }
  736. .section-header {
  737. display: flex;
  738. flex-wrap: wrap;
  739. justify-content: space-between;
  740. align-items: center;
  741. margin-bottom: 20rpx;
  742. }
  743. .section-title {
  744. font-size: 30rpx;
  745. font-weight: bold;
  746. color: #333;
  747. }
  748. /* 页面说明(独占一行,避免与标题挤在同一行) */
  749. .section-sub {
  750. flex: 1 1 100%;
  751. font-size: 22rpx;
  752. color: #94A3B8;
  753. line-height: 1.4;
  754. margin-top: 6rpx;
  755. display: block;
  756. }
  757. .section-more {
  758. flex-shrink: 0;
  759. font-size: 24rpx;
  760. color: #6366F1;
  761. }
  762. /* 得分解读卡片 */
  763. .score-explain-card {
  764. background: #fff;
  765. border-radius: 24rpx;
  766. padding: 28rpx 24rpx;
  767. border-left: 8rpx solid #6366F1;
  768. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  769. }
  770. .explain-header {
  771. display: flex;
  772. align-items: center;
  773. margin-bottom: 14rpx;
  774. }
  775. .explain-icon {
  776. font-size: 36rpx;
  777. margin-right: 10rpx;
  778. }
  779. .explain-title {
  780. font-size: 30rpx;
  781. font-weight: bold;
  782. color: #333;
  783. }
  784. .explain-text {
  785. font-size: 26rpx;
  786. color: #666;
  787. line-height: 1.6;
  788. display: block;
  789. }
  790. /* 主按钮 */
  791. .primary-btn {
  792. background: linear-gradient(135deg, #6366F1, #4F46E5);
  793. color: #fff;
  794. font-size: 26rpx;
  795. font-weight: bold;
  796. border: none;
  797. border-radius: 48rpx;
  798. padding: 18rpx 56rpx;
  799. line-height: 1.6;
  800. margin: 0;
  801. box-shadow: 0 4rpx 16rpx rgba(79,70,229,0.35);
  802. }
  803. .primary-btn:active {
  804. opacity: 0.88;
  805. }
  806. /* ===== 底部占位 ===== */
  807. .indicator-cards-section { margin: 20rpx 30rpx; }
  808. .indicator-cards { display: flex; flex-direction: row; flex-wrap: wrap; gap: 16rpx; }
  809. .indicator-card { width: calc(50% - 8rpx); background: #fff; border-radius: 20rpx; padding: 24rpx 20rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
  810. .indicator-card.no-data { opacity: 0.5; }
  811. .indicator-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
  812. .indicator-name { font-size: 26rpx; color: #666; font-weight: 500; }
  813. .indicator-unit { font-size: 22rpx; color: #999; }
  814. .indicator-body { margin-bottom: 8rpx; }
  815. .indicator-value { font-size: 40rpx; font-weight: bold; color: #6366F1; }
  816. .indicator-placeholder { font-size: 26rpx; color: #ccc; }
  817. .indicator-range { font-size: 20rpx; color: #bbb; }
  818. .bottom-spacer { height: 120rpx; }
  819. /* DAN 报告卡片 */
  820. .dan-card {
  821. background: #fff;
  822. border-radius: 16rpx;
  823. padding: 24rpx;
  824. margin-bottom: 16rpx;
  825. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
  826. }
  827. .dan-card-header {
  828. display: flex;
  829. justify-content: space-between;
  830. align-items: center;
  831. margin-bottom: 12rpx;
  832. }
  833. .dan-report-date {
  834. font-size: 26rpx;
  835. color: #666;
  836. }
  837. .dan-status {
  838. font-size: 22rpx;
  839. padding: 4rpx 16rpx;
  840. border-radius: 8rpx;
  841. }
  842. .dan-status.confirmed {
  843. background: #D1FAE5;
  844. color: #065F46;
  845. }
  846. .dan-status.pending {
  847. background: #FEF3C7;
  848. color: #92400E;
  849. }
  850. .dan-report-desc {
  851. font-size: 28rpx;
  852. color: #333;
  853. font-weight: 500;
  854. margin-bottom: 12rpx;
  855. display: block;
  856. }
  857. .dan-card-footer {
  858. text-align: right;
  859. }
  860. .dan-view-link {
  861. font-size: 26rpx;
  862. color: #6366F1;
  863. }
  864. .dan-empty {
  865. background: #fff;
  866. border-radius: 16rpx;
  867. padding: 40rpx 24rpx;
  868. text-align: center;
  869. }
  870. .dan-empty-text {
  871. font-size: 24rpx;
  872. color: #999;
  873. }
  874. .avg-badge {
  875. flex: 1 1 100%;
  876. font-size: 20rpx;
  877. color: #94A3B8;
  878. margin-top: 6rpx;
  879. display: block;
  880. }
  881. /* 品牌定位语 */
  882. .brand-strap {
  883. margin: 16rpx 30rpx 0;
  884. padding: 24rpx 28rpx;
  885. background: linear-gradient(135deg, #F0F0FF, rgba(99,102,241,0.16));
  886. border-radius: 16rpx;
  887. border-left: 4rpx solid #6366F1;
  888. box-shadow: 0 2rpx 12rpx rgba(99,102,241,0.12);
  889. }
  890. .brand-strap-text {
  891. font-size: 26rpx;
  892. color: #3730A3;
  893. font-weight: bold;
  894. line-height: 1.5;
  895. }
  896. /* 每月引导语 */
  897. .monthly-guidance {
  898. margin: 16rpx 30rpx 0;
  899. padding: 20rpx 28rpx;
  900. background: linear-gradient(135deg, #F0F0FF, rgba(99,102,241,0.08));
  901. border-radius: 16rpx;
  902. border-left: 4rpx solid #6366F1;
  903. display: flex;
  904. align-items: center;
  905. gap: 12rpx;
  906. }
  907. .guidance-icon { font-size: 32rpx; }
  908. .guidance-text {
  909. font-size: 26rpx;
  910. color: #3730A3;
  911. font-weight: 500;
  912. }
  913. /* ===== 肠胃与认知卡片 ===== */
  914. .gut-cog-subtitle {
  915. font-size: 24rpx;
  916. color: #6366F1;
  917. margin-bottom: 16rpx;
  918. display: block;
  919. }
  920. .gut-indicator-list {
  921. display: flex;
  922. flex-direction: column;
  923. gap: 12rpx;
  924. }
  925. .gut-indicator-item {
  926. display: flex;
  927. align-items: center;
  928. gap: 12rpx;
  929. }
  930. .gut-indicator-left {
  931. width: 110rpx;
  932. flex-shrink: 0;
  933. }
  934. .gut-indicator-name {
  935. font-size: 22rpx;
  936. color: #666;
  937. font-weight: 500;
  938. display: block;
  939. }
  940. .gut-indicator-impact {
  941. font-size: 18rpx;
  942. color: #6366F1;
  943. display: block;
  944. margin-top: 2rpx;
  945. }
  946. .gut-indicator-bar-track {
  947. flex: 1;
  948. height: 16rpx;
  949. background: #F0F0F0;
  950. border-radius: 8rpx;
  951. overflow: hidden;
  952. }
  953. .gut-indicator-bar-fill {
  954. height: 100%;
  955. border-radius: 8rpx;
  956. transition: width 0.3s;
  957. }
  958. .gut-indicator-value {
  959. font-size: 22rpx;
  960. color: #666;
  961. width: 60rpx;
  962. text-align: right;
  963. }
  964. .gut-indicator-status {
  965. font-size: 20rpx;
  966. padding: 2rpx 12rpx;
  967. border-radius: 10rpx;
  968. width: 60rpx;
  969. text-align: center;
  970. }
  971. .status-normal {
  972. background: #D1FAE5;
  973. color: #059669;
  974. }
  975. .status-abnormal {
  976. background: #FEE2E2;
  977. color: #DC2626;
  978. }
  979. .status-warning {
  980. background: #FEF3C7;
  981. color: #D97706;
  982. }
  983. .gut-empty {
  984. display: flex;
  985. flex-direction: column;
  986. align-items: center;
  987. padding: 40rpx 0;
  988. }
  989. .gut-empty-icon {
  990. font-size: 56rpx;
  991. margin-bottom: 16rpx;
  992. }
  993. .gut-empty-text {
  994. font-size: 26rpx;
  995. color: #999;
  996. margin-bottom: 24rpx;
  997. }
  998. .gut-upload-btn {
  999. background: linear-gradient(135deg, #6366F1, #818CF8);
  1000. color: #fff;
  1001. font-size: 26rpx;
  1002. font-weight: 600;
  1003. border: none;
  1004. border-radius: 40rpx;
  1005. padding: 16rpx 64rpx;
  1006. line-height: 1.6;
  1007. margin: 0;
  1008. }
  1009. /* ===== 认知强项与提升卡片 ===== */
  1010. .insight-group {
  1011. margin-bottom: 16rpx;
  1012. }
  1013. .insight-group-title {
  1014. font-size: 26rpx;
  1015. font-weight: 600;
  1016. color: #333;
  1017. margin-bottom: 10rpx;
  1018. display: block;
  1019. }
  1020. .insight-item {
  1021. display: flex;
  1022. align-items: center;
  1023. padding: 12rpx 16rpx;
  1024. background: #F8FAFF;
  1025. border-radius: 12rpx;
  1026. margin-bottom: 8rpx;
  1027. }
  1028. .insight-item-name {
  1029. flex: 1;
  1030. font-size: 26rpx;
  1031. color: #333;
  1032. }
  1033. .insight-item-score {
  1034. font-size: 24rpx;
  1035. color: #666;
  1036. margin-right: 12rpx;
  1037. }
  1038. .insight-item-tag {
  1039. font-size: 20rpx;
  1040. padding: 4rpx 16rpx;
  1041. border-radius: 8rpx;
  1042. font-weight: 500;
  1043. }
  1044. .insight-item-tag.tag-strong {
  1045. background: #D1FAE5;
  1046. color: #059669;
  1047. }
  1048. .insight-item-tag.tag-improve {
  1049. background: #FEF3C7;
  1050. color: #D97706;
  1051. }
  1052. .insight-tip {
  1053. padding: 12rpx 16rpx;
  1054. background: #EEF2FF;
  1055. border-radius: 12rpx;
  1056. }
  1057. .insight-tip-text {
  1058. font-size: 22rpx;
  1059. color: #6366F1;
  1060. }
  1061. .insight-empty {
  1062. display: flex;
  1063. flex-direction: column;
  1064. align-items: center;
  1065. padding: 30rpx 0;
  1066. }
  1067. .insight-empty-text {
  1068. font-size: 24rpx;
  1069. color: #999;
  1070. margin-bottom: 20rpx;
  1071. }
  1072. .insight-btn {
  1073. background: linear-gradient(135deg, #6366F1, #818CF8);
  1074. color: #fff;
  1075. font-size: 26rpx;
  1076. font-weight: 600;
  1077. border: none;
  1078. border-radius: 40rpx;
  1079. padding: 14rpx 60rpx;
  1080. line-height: 1.6;
  1081. margin: 0;
  1082. }
  1083. /* 维度画像样式 */
  1084. .dimension-portrait-section {
  1085. margin: 24rpx 30rpx;
  1086. background: #fff;
  1087. border-radius: 24rpx;
  1088. padding: 32rpx 24rpx;
  1089. box-shadow: 0 4rpx 20rpx rgba(99, 102, 241, 0.12);
  1090. border: 2rpx solid rgba(99, 102, 241, 0.1);
  1091. }
  1092. .portrait-tier {
  1093. font-size: 26rpx;
  1094. color: #6366F1;
  1095. font-weight: bold;
  1096. margin-left: 16rpx;
  1097. }
  1098. .portrait-summary {
  1099. margin-bottom: 24rpx;
  1100. }
  1101. .portrait-desc {
  1102. font-size: 26rpx;
  1103. color: #666;
  1104. line-height: 1.6;
  1105. display: block;
  1106. }
  1107. .portrait-dimensions {
  1108. margin-bottom: 20rpx;
  1109. }
  1110. .portrait-dim-item {
  1111. margin-bottom: 18rpx;
  1112. }
  1113. .portrait-dim-header {
  1114. display: flex;
  1115. align-items: center;
  1116. margin-bottom: 6rpx;
  1117. }
  1118. .portrait-dim-icon {
  1119. width: 36rpx;
  1120. height: 36rpx;
  1121. border-radius: 18rpx;
  1122. display: flex;
  1123. align-items: center;
  1124. justify-content: center;
  1125. margin-right: 10rpx;
  1126. font-size: 20rpx;
  1127. }
  1128. .portrait-dim-name {
  1129. font-size: 24rpx;
  1130. color: #333;
  1131. flex: 1;
  1132. }
  1133. .portrait-dim-score {
  1134. font-size: 26rpx;
  1135. font-weight: bold;
  1136. color: #6366F1;
  1137. margin-right: 12rpx;
  1138. }
  1139. .portrait-dim-bar-track {
  1140. height: 10rpx;
  1141. background: #F0F4FF;
  1142. border-radius: 5rpx;
  1143. overflow: hidden;
  1144. margin-bottom: 4rpx;
  1145. flex: 1;
  1146. }
  1147. .portrait-dim-bar-fill {
  1148. height: 100%;
  1149. border-radius: 5rpx;
  1150. transition: width 0.3s ease;
  1151. }
  1152. .portrait-dim-desc {
  1153. font-size: 20rpx;
  1154. color: #999;
  1155. display: block;
  1156. }
  1157. .portrait-tip {
  1158. padding: 16rpx 20rpx;
  1159. background: linear-gradient(135deg, #F0F4FF, #E8F1FE);
  1160. border-radius: 12rpx;
  1161. border-left: 4rpx solid #6366F1;
  1162. margin-top: 16rpx;
  1163. }
  1164. .portrait-tip-text {
  1165. font-size: 22rpx;
  1166. color: #1D1D1F;
  1167. line-height: 1.5;
  1168. }
  1169. </style>