index.vue 36 KB

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