index.vue 76 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594
  1. <template>
  2. <view class="container">
  3. <!-- 通用登录浮层 -->
  4. <login-sheet ref="loginSheet" @success="onSheetLoginSuccess" />
  5. <!-- Tabbar 切换过渡页 -->
  6. <tab-transition v-if="showTabTransition" dimCode="mind" ref="tabTransition" @close="showTabTransition = false" />
  7. <!-- 品牌头部 -->
  8. <PageBanner theme="mind-wisdom"
  9. tagline="心智成长 · 智慧人生"
  10. quote="知己知彼,百战不殆" />
  11. <DimensionPageSkeleton v-if="pageLoading" dim-code="mind" />
  12. <!-- 品牌定位语(登录前显示) -->
  13. <view class="brand-strap" v-if="!isLoggedIn">
  14. <text class="brand-strap-text">关注全家心理健康与情绪成长</text>
  15. </view>
  16. <!-- ===== 家庭天盘模块(今日运势 / 今日心情 / 吉位) ===== -->
  17. <FamilyTianpanCard
  18. v-if="isLoggedIn"
  19. :visible="isLoggedIn"
  20. :fortune="dailyFortuneSafe"
  21. :mood="todayMood"
  22. :dominantElement="dominantElementSafe"
  23. :weeklyTip="familyWeeklyInsight"
  24. :traditionalData="traditionalData" />
  25. <!-- 家庭成员选择条 -->
  26. <DimensionMemberTabs
  27. v-if="currentView === 'self' && isLoggedIn"
  28. :members="familyMembersVisible"
  29. :selectedMemberId="selectedMemberId"
  30. dimension-code="mind"
  31. :score-map="memberScoreMap"
  32. @select="onFamilyMemberSelect"
  33. @longpress="onMemberLongPress"
  34. @manage="onMemberManage"
  35. @removed="loadFamilyMembersVisible" />
  36. <!-- 游客引导语 -->
  37. <view class="monthly-guidance" v-if="!isLoggedIn">
  38. <text class="guidance-icon">🔥</text>
  39. <text class="guidance-text">{{ monthlyGuidance }}</text>
  40. </view>
  41. <!-- 游客价值卡(统一组件:维度色 CTA + 价值预览) -->
  42. <DimensionVisitorCard
  43. v-if="!isLoggedIn"
  44. dimCode="mind"
  45. card-title="心维度 · 心理成长"
  46. :benefits="[
  47. '心理测评 EQ 报告与情绪画像',
  48. '情绪与肠胃关联解读',
  49. '家庭关系质量分析'
  50. ]"
  51. cta-text="登录查看心理画像"
  52. secondary-text="了解心理维度"
  53. secondary-url="/pages/mind-detail/dimension-intro"
  54. preview-text="登录后展示你的心理画像与情绪关联" />
  55. <!-- 登录后:五维能量条 -->
  56. <FamilyEnergyBar
  57. v-if="isLoggedIn"
  58. dimensionCode="mind"
  59. :sandboxData="sandboxData" />
  60. <!-- 心理指标卡片 -->
  61. <view class="indicator-cards-section" v-if="isLoggedIn && mindIndicators.length > 0">
  62. <view class="section-header">
  63. <text class="section-title">🔥 心理指标</text>
  64. <text class="section-more" @click="goMindDetail">更多 ›</text>
  65. </view>
  66. <view class="indicator-cards">
  67. <view
  68. v-for="(ind, i) in mindIndicators"
  69. :key="ind.code"
  70. class="indicator-card"
  71. :class="{ 'no-data': !ind.value }"
  72. @click="goMindDetail()"
  73. >
  74. <view class="indicator-header">
  75. <text class="indicator-name">{{ ind.name }}</text>
  76. <text class="indicator-unit" v-if="ind.unit">{{ ind.unit }}</text>
  77. </view>
  78. <view class="indicator-body">
  79. <text class="indicator-value" v-if="ind.value || ind.numericValue !== null">{{ ind.value || formatNumeric(ind.numericValue) }}</text>
  80. <text class="indicator-placeholder" v-else>暂无数据</text>
  81. </view>
  82. <view class="indicator-footer">
  83. <text class="indicator-range" v-if="ind.refRange">参考: {{ ind.refRange }}</text>
  84. </view>
  85. </view>
  86. </view>
  87. </view>
  88. <!-- 心理报告卡片(登录后可见) -->
  89. <view class="emi-card" v-if="isLoggedIn && emiData">
  90. <view class="emi-header">
  91. <text class="emi-title">心理测评报告</text>
  92. <view class="emi-overall-wrap">
  93. <text class="emi-overall-label">综合EQ分</text>
  94. <text class="emi-overall-value">{{ emiData.overallScore || 0 }}</text>
  95. <text :class="['emi-overall-level', 'level-' + getScoreLevel(emiData.overallScore || 0)]">
  96. {{ getScoreLevelText(emiData.overallScore || 0) }}
  97. </text>
  98. </view>
  99. </view>
  100. <view class="emi-dimensions">
  101. <view class="emi-dimension" v-for="dim in emiDimensions" :key="dim.key">
  102. <view class="emi-dim-header">
  103. <text class="emi-dim-label">{{ dim.label }}</text>
  104. <view class="emi-dim-values">
  105. <text class="emi-dim-score">{{ emiData[dim.key] || 0 }}</text>
  106. <text :class="['emi-dim-level', 'level-' + getScoreLevel(emiData[dim.key] || 0)]">
  107. {{ getScoreLevelText(emiData[dim.key] || 0) }}
  108. </text>
  109. </view>
  110. </view>
  111. <view class="emi-dim-bar">
  112. <view class="emi-dim-bar-fill" :style="{ width: (emiData[dim.key] || 0) + '%', background: dim.color }"></view>
  113. </view>
  114. </view>
  115. </view>
  116. <view class="emi-footer" @click="goEmotionReport">
  117. <text class="emi-footer-text">查看详情</text>
  118. <text class="emi-footer-icon">&#x2192;</text>
  119. </view>
  120. </view>
  121. <!-- 心维度画像 - 总体说明 -->
  122. <view class="dimension-portrait-section" v-if="isLoggedIn && mindPortrait">
  123. <view class="section-header">
  124. <text class="section-title">🧭 心维度画像</text>
  125. <text class="portrait-tier">{{ mindPortrait.tierLabel }}</text>
  126. </view>
  127. <view class="portrait-summary">
  128. <text class="portrait-desc">{{ mindPortrait.summary }}</text>
  129. </view>
  130. <view class="portrait-dimensions">
  131. <view class="portrait-dim-item" v-for="dim in mindPortrait.dims" :key="dim.code">
  132. <view class="portrait-dim-header">
  133. <view class="portrait-dim-icon" :style="{ background: dim.color }">{{ dim.icon }}</view>
  134. <text class="portrait-dim-name">{{ dim.name }}</text>
  135. <text class="portrait-dim-score">{{ dim.score }}</text>
  136. </view>
  137. <view class="portrait-dim-bar-track">
  138. <view class="portrait-dim-bar-fill" :style="{ width: dim.score + '%', background: dim.color }"></view>
  139. </view>
  140. <text class="portrait-dim-desc">{{ dim.desc }}</text>
  141. </view>
  142. </view>
  143. <view class="portrait-tip" v-if="mindPortrait.tip">
  144. <text class="portrait-tip-text">💡 {{ mindPortrait.tip }}</text>
  145. </view>
  146. </view>
  147. <!-- ===== DAN 报告(登录后) ===== -->
  148. <view class="section" v-if="isLoggedIn">
  149. <view class="section-header">
  150. <text class="section-title">📊 DAN 报告</text>
  151. <text class="section-more" @click="goDanUpload('mind')">上传 +</text>
  152. </view>
  153. <view class="dan-card" v-for="report in danReports" :key="report.id" @click="viewDanReport(report)">
  154. <view class="dan-card-header">
  155. <text class="dan-report-date">{{ formatDate(report.assessmentDate) }}</text>
  156. <text :class="['dan-status', report.draftStatus === 'confirmed' ? 'confirmed' : 'pending']">
  157. {{ report.draftStatus === 'confirmed' ? '已确认' : '待确认' }}
  158. </text>
  159. </view>
  160. <text class="dan-report-desc">DAN A2 心维度测评报告</text>
  161. </view>
  162. <view class="dan-empty" v-if="danReports.length === 0">
  163. <text class="dan-empty-text">暂无 DAN 报告,点击上方「上传 +」上传测评报告</text>
  164. </view>
  165. </view>
  166. <!-- 肠脑轴情绪卡片 -->
  167. <view class="section" v-if="gutIndicators && gutIndicators.length > 0">
  168. <view class="section-header">
  169. <text class="section-title">🌱 情绪与肠胃</text>
  170. </view>
  171. <view class="gut-insight-text" v-if="gutInsight">
  172. <text>{{ gutInsight }}</text>
  173. </view>
  174. <view class="gut-indicator-list">
  175. <view class="gut-indicator-item" v-for="ind in gutIndicators" :key="ind.id">
  176. <view class="gut-indicator-left">
  177. <text class="gut-indicator-name">{{ ind.indicatorName || ind.name }}</text>
  178. <text class="gut-indicator-impact" v-if="emotionImpactLabel(ind.indicatorName || ind.name)">影响{{ emotionImpactLabel(ind.indicatorName || ind.name) }}</text>
  179. </view>
  180. <view class="gut-indicator-bar-track">
  181. <view class="gut-indicator-bar-fill" :style="{ width: indicatorBarWidth(ind), background: indicatorColor(ind.status) }"></view>
  182. </view>
  183. <text class="gut-indicator-value">{{ formatIndicatorValue(ind) }}{{ ind.unit || '%' }}</text>
  184. <text :class="['gut-indicator-status', 'status-' + (ind.status || 'normal')]">{{ statusLabel(ind.status) }}</text>
  185. </view>
  186. </view>
  187. </view>
  188. <!-- ===== Phase 1.2: 心理告警横幅 ===== -->
  189. <PsychCrisisBanner v-if="isLoggedIn" :memberId="currentChildId" source="screening" />
  190. <!-- ===== 底部:功能入口 Grid(通用,两行) ===== -->
  191. <view class="func-section" v-if="sectionVisible('func_entries')">
  192. <view class="func-grid">
  193. <view class="func-row">
  194. <view class="func-item" v-for="item in funcListRow1" :key="item.label" @click="onFuncClick(item)">
  195. <view class="func-icon-wrap">
  196. <text class="func-icon">{{ item.icon }}</text>
  197. </view>
  198. <text class="func-label">{{ item.label }}</text>
  199. </view>
  200. </view>
  201. <view class="func-row">
  202. <view class="func-item" v-for="item in funcListRow2" :key="item.label" @click="onFuncClick(item)">
  203. <view class="func-icon-wrap">
  204. <text class="func-icon">{{ item.icon }}</text>
  205. </view>
  206. <text class="func-label">{{ item.label }}</text>
  207. </view>
  208. </view>
  209. </view>
  210. </view>
  211. <!-- 心13维雷达图(分组展示,登录后且选中成员时) -->
  212. <view class="section" v-if="isLoggedIn && currentMemberEnergies">
  213. <view class="section-header">
  214. <text class="section-title">🧠 情绪与性格</text>
  215. </view>
  216. <view class="radar-group" v-for="(group, gIdx) in mindRadarGroups" :key="gIdx">
  217. <text class="radar-group-label">{{ group.label }}</text>
  218. <RadarChart
  219. :dimensions="group.dimensions"
  220. :scores="getMindGroupScores(group)"
  221. :avg-scores="getMindGroupFamilyScores(group)"
  222. :population-scores="getGroupPopulationScores(group)"
  223. fillColor="#FF6B9D"
  224. gridColor="#FCE7F3"
  225. labelColor="#9D174D"
  226. :width="540"
  227. :height="540" />
  228. </view>
  229. </view>
  230. <!-- 心维度介绍卡片(登录后显示) -->
  231. <DimensionIntroCard dimCode="mind" v-if="isLoggedIn" learn-more-url="/pages/mind-detail/dimension-intro" />
  232. <!-- 底部占位 -->
  233. <view class="bottom-spacer"></view>
  234. <FloatingAvatar />
  235. </view>
  236. </template>
  237. <script>
  238. import TabTransition from '../../components/tab-transition.vue'
  239. import PageBanner from '../../components/PageBanner.vue'
  240. import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
  241. import DimensionVisitorCard from '../../components/DimensionVisitorCard.vue'
  242. import RadarChart from '../../components/RadarChart.vue'
  243. import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
  244. import PsychCrisisBanner from '@/pages/mind-detail/components/PsychCrisisBanner.vue'
  245. import FloatingAvatar from '../../components/AIFloatingAvatar.vue'
  246. import FamilyTianpanCard from '@/pages/mind-detail/components/FamilyTianpanCard.vue'
  247. import DimensionPageSkeleton from '../../components/DimensionPageSkeleton.vue'
  248. import DimensionMemberTabs from '../../components/DimensionMemberTabs.vue'
  249. import { getVisibleSections, getEmiReport, getFamilyEnergySandbox, getEnergySandbox, getChildren, getFamilyMemberList, getEnergyOverview, getContactList, getHealthAlerts, getMilestones, getDanReportByChild, getFamilyFortune, getLatestIndicators, getRadarData, kickFamilyMemberById } from '../../utils/api.js'
  250. import nav from '../../utils/nav.js'
  251. import config from '../../config.js'
  252. import { parseDate } from '../../utils/format.js'
  253. export default {
  254. components: { TabTransition, PageBanner, RadarChart, FamilyEnergyBar, PsychCrisisBanner, FloatingAvatar, FamilyTianpanCard, DimensionMemberTabs, DimensionIntroCard, DimensionVisitorCard, DimensionPageSkeleton },
  255. data() {
  256. return {
  257. memberScoreMap: {},
  258. pageLoading: false,
  259. isLoggedIn: false,
  260. role: null,
  261. showTabTransition: true,
  262. currentChildId: null,
  263. selectedMemberId: null,
  264. visibleSections: [],
  265. familyMembersVisible: [],
  266. // 心理 Tab 数据
  267. emiData: null,
  268. emiLoading: true,
  269. emiDimensions: [
  270. { key: 'emotionStability', label: '情绪稳定性', color: '#FF6B35' }
  271. ],
  272. // 大五维度(用于雷达图)
  273. personalityDimensions: [
  274. { key: 'opennessScore', label: '开放性' },
  275. { key: 'conscientiousnessScore', label: '尽责性' },
  276. { key: 'extraversionScore', label: '外向性' },
  277. { key: 'agreeablenessScore', label: '宜人性' },
  278. { key: 'neuroticismScore', label: '神经质' }
  279. ],
  280. // 功能入口
  281. funcList: [
  282. { icon: '\u{1F4D6}', label: '阅读', needLogin: false, page: '/pages/article-center/index?dimension=mind' },
  283. { icon: '\u{1F4CA}', label: '测评报告', needLogin: true, page: '/pages/mind-detail/emotion-report' },
  284. { icon: '\u{1F4DD}', label: '成长记录', needLogin: true, page: '/pages/growth-main/index' },
  285. { icon: '\u{2728}', label: '情绪打卡', needLogin: true, page: '/pages/mind-detail/emotion-checkin' },
  286. { icon: '\u{1F4C8}', label: '情绪趋势', needLogin: true, page: '/pages/mind-detail/emotion-trend' },
  287. { icon: '\u{1F9E9}', label: '心理自评', needLogin: true, page: '/pages/mind-detail/screening' },
  288. { icon: '\u{1F3E0}', label: '家庭天盘', needLogin: true, page: '/pages/mind-detail/family-dashboard' },
  289. { icon: '\u{1F9D8}', label: '疏导工具', needLogin: true, page: '/pages/mind-detail/soothe-toolbox' },
  290. { icon: '\u{1F31F}', label: '先天画像', needLogin: true, page: '/pages/mind-detail/innate-portrait' }
  291. ],
  292. articles: [],
  293. // 肠脑轴数据
  294. gutIndicators: null,
  295. gutInsight: '',
  296. // 传统文化数据
  297. traditionalData: null,
  298. traditionalLoading: true,
  299. compatibilityData: null,
  300. wuxingConfig: {
  301. wood: { label: '木', color: '#10B981' },
  302. fire: { label: '火', color: '#FF6B9D' },
  303. earth: { label: '土', color: '#FF8C42' },
  304. metal: { label: '金', color: '#6366F1' },
  305. water: { label: '水', color: '#3B82F6' }
  306. },
  307. pillarLabels: { year: '年柱', month: '月柱', day: '日柱', hour: '时柱' },
  308. familyScore: 0,
  309. // 五维组件数据
  310. userName: '',
  311. children: [],
  312. mindSubDims: [],
  313. mindIndicators: [],
  314. sandboxData: null,
  315. currentView: 'self', // 'self' | 'family'
  316. contactList: [],
  317. healthAlerts: [],
  318. milestones: [],
  319. danReports: [],
  320. // 心13维雷达图分组配置
  321. mindRadarGroups: [
  322. {
  323. label: '大五人格',
  324. dimensions: [
  325. { key: 'bigFiveOpenness', label: '开放性' },
  326. { key: 'bigFiveConscientiousness', label: '尽责性' },
  327. { key: 'bigFiveExtraversion', label: '外向性' },
  328. { key: 'bigFiveAgreeableness', label: '宜人性' },
  329. { key: 'bigFiveNeuroticism', label: '神经质' }
  330. ]
  331. },
  332. {
  333. label: '情绪稳定性',
  334. dimensions: [
  335. { key: 'emotionStability', label: '情绪稳定性' }
  336. ]
  337. },
  338. {
  339. label: '自驱力',
  340. dimensions: [
  341. { key: 'driveAutonomy', label: '自主性' },
  342. { key: 'driveCompetence', label: '胜任感' },
  343. { key: 'driveRelatedness', label: '归属感' }
  344. ]
  345. },
  346. {
  347. label: '自我概念',
  348. dimensions: [
  349. { key: 'selfConceptOverall', label: '综合自我概念' }
  350. ]
  351. }
  352. ],
  353. // 家庭天盘 - 今日心情(占位数据)
  354. todayMood: {
  355. emoji: '\u{1F60A}',
  356. text: '平静',
  357. score: 65
  358. },
  359. // 家庭天盘 - 每日运势数据
  360. dailyFortuneData: null,
  361. // 心维度雷达图人群位置分数 (dimensionKey -> score)
  362. mindPopulationScores: {}
  363. }
  364. },
  365. computed: {
  366. /** 每月引导语 */
  367. monthlyGuidance: function() {
  368. var month = new Date().getMonth()
  369. var messages = [
  370. '一月:新年新起点,从情绪觉察开始',
  371. '二月:春节团聚,关注家人心理联结',
  372. '三月:春意盎然,适合情绪释放',
  373. '四月:清明时节,整理内心情绪',
  374. '五月:劳动之余,关注心理健康',
  375. '六月:中考高考季,减压情绪管理',
  376. '七月:暑期到来,培养情绪调节能力',
  377. '八月:夏末秋初,回顾情绪成长',
  378. '九月:开学季,关注家庭成员心理适应',
  379. '十月:国庆假期,增进亲子沟通',
  380. '十一月:秋冬交替,预防情绪低落',
  381. '十二月:年终总结,规划心理成长'
  382. ]
  383. return messages[month] || messages[0]
  384. },
  385. personalityScores: function() {
  386. if (!this.emiData) return []
  387. return [
  388. this.emiData.opennessScore || 0,
  389. this.emiData.conscientiousnessScore || 0,
  390. this.emiData.extraversionScore || 0,
  391. this.emiData.agreeablenessScore || 0,
  392. this.emiData.neuroticismScore || 0
  393. ]
  394. },
  395. emotionArticles: function() {
  396. if (!this.articles || this.articles.length === 0) return []
  397. var result = []
  398. for (var i = 0; i < this.articles.length; i++) {
  399. var a = this.articles[i]
  400. if (a.relatedDimensions && a.relatedDimensions.indexOf('mind') !== -1) {
  401. result.push(a)
  402. }
  403. }
  404. if (result.length === 0) return this.articles.slice(0, 5)
  405. return result.slice(0, 5)
  406. },
  407. wuxingData: function() {
  408. if (!this.traditionalData || !this.traditionalData.wuxingElements) return []
  409. var elements = this.traditionalData.wuxingElements
  410. var keys = ['wood', 'fire', 'earth', 'metal', 'water']
  411. var result = []
  412. for (var i = 0; i < keys.length; i++) {
  413. var k = keys[i]
  414. if (elements[k] !== undefined) {
  415. result.push({
  416. key: k,
  417. label: this.wuxingConfig[k].label,
  418. color: this.wuxingConfig[k].color,
  419. value: elements[k]
  420. })
  421. }
  422. }
  423. return result.sort(function(a, b) { return b.value - a.value })
  424. },
  425. currentDimensionCode: function() {
  426. return 'mind'
  427. },
  428. funcListRow1: function() {
  429. return this.funcList.slice(0, 4)
  430. },
  431. funcListRow2: function() {
  432. return this.funcList.slice(4)
  433. },
  434. // ===== 关键信息 - 家庭 =====
  435. familyWuxingLabel: function() {
  436. if (!this.compatibilityData || !this.compatibilityData.familyWuxing || !this.compatibilityData.familyWuxing.aggregatePercentages) return null
  437. var pcts = this.compatibilityData.familyWuxing.aggregatePercentages
  438. var keys = ['wood', 'fire', 'earth', 'metal', 'water']
  439. var maxKey = null
  440. var maxVal = -1
  441. for (var i = 0; i < keys.length; i++) {
  442. if (pcts[keys[i]] !== undefined && pcts[keys[i]] > maxVal) {
  443. maxVal = pcts[keys[i]]
  444. maxKey = keys[i]
  445. }
  446. }
  447. if (!maxKey) return null
  448. return { key: maxKey, label: this.wuxingConfig[maxKey].label, color: this.wuxingConfig[maxKey].color, value: maxVal }
  449. },
  450. familyMemberCount: function() {
  451. if (!this.compatibilityData || !this.compatibilityData.members) return 0
  452. return this.compatibilityData.members.length
  453. },
  454. familyWeeklyInsight: function() {
  455. if (!this.compatibilityData || !this.compatibilityData.weeklyInsight) return ''
  456. return this.compatibilityData.weeklyInsight
  457. },
  458. // ===== 关键信息 - 个人 =====
  459. dominantElement: function() {
  460. if (!this.traditionalData || !this.traditionalData.wuxingElements) return null
  461. var elements = this.traditionalData.wuxingElements
  462. var keys = ['wood', 'fire', 'earth', 'metal', 'water']
  463. var maxKey = null
  464. var maxVal = -1
  465. for (var i = 0; i < keys.length; i++) {
  466. if (elements[keys[i]] !== undefined && elements[keys[i]] > maxVal) {
  467. maxVal = elements[keys[i]]
  468. maxKey = keys[i]
  469. }
  470. }
  471. return maxKey
  472. },
  473. luckyColorInfo: function() {
  474. var colorMap = {
  475. wood: { name: '绿', hex: '#10B981' },
  476. fire: { name: '红', hex: '#FF6B9D' },
  477. earth: { name: '黄', hex: '#FF8C42' },
  478. metal: { name: '白', hex: '#6366F1' },
  479. water: { name: '蓝', hex: '#3B82F6' }
  480. }
  481. var elem = this.dominantElement
  482. if (!elem) return null
  483. return colorMap[elem] || null
  484. },
  485. luckyNumber: function() {
  486. if (this.traditionalData && this.traditionalData.lifeNumber) {
  487. return this.traditionalData.lifeNumber
  488. }
  489. var numMap = { wood: 3, fire: 9, earth: 5, metal: 7, water: 1 }
  490. var elem = this.dominantElement
  491. return elem ? numMap[elem] : null
  492. },
  493. fortuneSeed: function() {
  494. if (this.traditionalData && this.traditionalData.lifeNumber) {
  495. return String(this.traditionalData.lifeNumber)
  496. }
  497. return '0'
  498. },
  499. // WXML 不支持模板内 || {} 字面量,需在 computed 中预先做 fallback
  500. dailyFortuneSafe: function() {
  501. return this.dailyFortune || {}
  502. },
  503. // WXML 不支持模板内 || '' 字面量,需在 computed 中预先做 fallback
  504. dominantElementSafe: function() {
  505. return this.dominantElement || ''
  506. },
  507. dailyFortune: function() {
  508. // 优先使用后端接口数据
  509. if (this.dailyFortuneData) {
  510. var levels = ['大吉', '吉', '平', '凶', '大凶']
  511. return {
  512. level: this.dailyFortuneData.level,
  513. text: levels[this.dailyFortuneData.level],
  514. description: this.dailyFortuneData.description
  515. }
  516. }
  517. // 兼容模式:没有接口数据时使用原算法
  518. if (!this.dominantElement) return null
  519. var today = new Date()
  520. var dateStr = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate()
  521. var seedStr = dateStr + (this.fortuneSeed || '')
  522. var hash = 0
  523. for (var i = 0; i < seedStr.length; i++) {
  524. hash = ((hash << 5) - hash) + seedStr.charCodeAt(i)
  525. hash = hash & hash
  526. }
  527. var level = Math.abs(hash) % 5
  528. var levels = ['大吉', '吉', '平', '凶', '大凶']
  529. var descMap = {
  530. wood: ['大吉:木气旺盛,宜规划', '吉:行动力强', '平:静心养性', '凶:谨言慎行', '大凶:忌冲动'],
  531. fire: ['大吉:热情高涨', '吉:精力充沛', '平:适当休息', '凶:注意情绪', '大凶:静心冥想'],
  532. earth: ['大吉:稳重踏实', '吉:贵人运佳', '平:按部就班', '凶:注意肠胃', '大凶:不宜投资'],
  533. metal: ['大吉:决策力强', '吉:思维清晰', '平:保持专注', '凶:注意言辞', '大凶:不宜强求'],
  534. water: ['大吉:灵感丰富', '吉:人际和谐', '平:安静内省', '凶:情绪波动', '大凶:宜藏不宜放']
  535. }
  536. var descList = descMap[this.dominantElement] || descMap.wood
  537. return { level: level, text: levels[level], description: descList[level] }
  538. },
  539. /** 当前选中成员的能量数据(从 sandboxData.members 中按 selectedMemberId 查找) */
  540. currentMemberEnergies: function() {
  541. if (!this.sandboxData || !this.sandboxData.members) return null
  542. var members = this.sandboxData.members
  543. var targetId = this.selectedMemberId || this.currentChildId
  544. for (var i = 0; i < members.length; i++) {
  545. var m = members[i]
  546. if (m && (m.memberId === targetId)) return m
  547. }
  548. return members[0] || null
  549. },
  550. mindPortrait: function() {
  551. var self = this
  552. if (!this.isLoggedIn || !this.emiData) return null
  553. var e = this.emiData
  554. var dims = [
  555. { key: 'emotionStability', label: '情绪稳定性', color: '#FF6B9D', desc: '情绪识别与调控能力' },
  556. { key: 'opennessScore', label: '开放性', color: '#6366F1', desc: '对新事物的接受程度' },
  557. { key: 'conscientiousnessScore', label: '尽责性', color: '#F59E0B', desc: '做事有条理、自律程度' },
  558. { key: 'extraversionScore', label: '外向性', color: '#10B981', desc: '社交活跃与外向程度' },
  559. { key: 'agreeablenessScore', label: '宜人性', color: '#FF8C42', desc: '与人相处的和谐程度' },
  560. { key: 'neuroticismScore', label: '神经质', color: '#F97316', desc: '情绪波动程度(反向)' }
  561. ]
  562. var result = { overall: 0, tierLabel: '', summary: '', dims: [], tip: '' }
  563. var total = 0
  564. var count = 0
  565. var dimList = []
  566. for (var i = 0; i < dims.length; i++) {
  567. var key = dims[i].key
  568. var score = e[key] || 0
  569. if (isNaN(score)) score = 0
  570. score = Math.max(0, Math.min(100, Number(score)))
  571. total += score
  572. count++
  573. var matched = dims.find(function(d) { return d.key === key })
  574. dimList.push({
  575. code: key,
  576. name: matched ? matched.label : key,
  577. score: Math.round(score),
  578. color: matched ? matched.color : '#FF6B9D',
  579. desc: matched ? matched.desc : ''
  580. })
  581. }
  582. if (count === 0) return null
  583. result.overall = Math.round(total / count)
  584. result.dims = dimList
  585. var o = result.overall
  586. if (o >= 80) {
  587. result.tierLabel = '心灵智者'
  588. result.summary = '您的心理状态非常健康,情绪稳定、人格成熟且内心强大。'
  589. result.tip = '保持内心平衡,可分享经验帮助家人共同成长'
  590. } else if (o >= 60) {
  591. result.tierLabel = '心灵进取者'
  592. result.summary = '您的心理状态良好,具备较强的情绪调节能力和自我认知。'
  593. result.tip = '关注情绪波动,练习正念冥想,持续提升心理韧性'
  594. } else if (o >= 40) {
  595. result.tierLabel = '心灵探索者'
  596. result.summary = '您的心理状态正在建立基础,情绪管理有待加强。'
  597. result.tip = '从每日情绪打卡开始,逐步建立情绪觉察习惯'
  598. } else {
  599. result.tierLabel = '心灵关怀者'
  600. result.summary = '您可能正处于情绪波动期,需要更多关注与支持。'
  601. result.tip = '建议寻求专业心理帮助,或向家人朋友倾诉'
  602. }
  603. return result
  604. }
  605. },
  606. created() {
  607. // 首屏加载骨架:已登录用户的数据请求期间占位,稳态视觉零变化
  608. // 未登录走静态内容,不进 loading,故此处不读 isLoggedIn
  609. var token = uni.getStorageSync('token')
  610. if (token) {
  611. this.pageLoading = true
  612. var self = this
  613. setTimeout(function() { self.pageLoading = false }, 600)
  614. }
  615. },
  616. onShow() {
  617. this.showTabTransition = true
  618. var token = uni.getStorageSync('token')
  619. this.isLoggedIn = !!token
  620. if (this.isLoggedIn) {
  621. this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  622. // FIX: 从 Vuex store 读取 currentChildId,而非仅从 localStorage
  623. this.currentChildId = (this.$store.state && this.$store.state.currentChildId) || uni.getStorageSync('currentChildId') || null
  624. this.userName = uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '家长'
  625. }
  626. this.loadSectionConfig()
  627. if (this.isLoggedIn) {
  628. this.loadChildren()
  629. this.loadFamilyMembersVisible()
  630. this.loadSandboxData()
  631. this.loadMindRadarPopulationData()
  632. this.loadEmiData()
  633. this.loadGutData()
  634. this.loadEnergyData()
  635. this.loadTraditionalCultureData()
  636. this.loadContacts()
  637. this.loadHealthAlerts()
  638. this.loadMilestones()
  639. this.loadDanReports()
  640. }
  641. // 游客也能浏览活动和商品
  642. this.loadFamilyFortune()
  643. this._watchPageReady()
  644. },
  645. methods: {
  646. /** 获取心维度分组雷达图的分值数组 */
  647. getMindGroupScores: function(group) {
  648. var member = this.currentMemberEnergies
  649. if (!member) return []
  650. var scores = []
  651. var dims = group.dimensions
  652. for (var i = 0; i < dims.length; i++) {
  653. scores.push(member[dims[i].key] || 0)
  654. }
  655. return scores
  656. },
  657. /** 获取心维度分组雷达图的家庭平均分(sandboxData.members 各维度均值) */
  658. getMindGroupFamilyScores: function(group) {
  659. if (!this.sandboxData || !this.sandboxData.members) return []
  660. var members = this.sandboxData.members
  661. var dims = group.dimensions
  662. var scores = []
  663. for (var i = 0; i < dims.length; i++) {
  664. var key = dims[i].key
  665. var sum = 0, count = 0
  666. for (var j = 0; j < members.length; j++) {
  667. var val = members[j][key]
  668. if (val !== undefined && val !== null && val > 0) { sum += val; count++ }
  669. }
  670. scores.push(count > 0 ? Math.round(sum / count) : 0)
  671. }
  672. return scores
  673. },
  674. /** 获取分组对应的人群位置分数 */
  675. getGroupPopulationScores: function(group) {
  676. if (!this.mindPopulationScores) return []
  677. var scores = []
  678. var dims = group.dimensions
  679. for (var i = 0; i < dims.length; i++) {
  680. scores.push(this.mindPopulationScores[dims[i].key] || 0)
  681. }
  682. return scores
  683. },
  684. // ===== 区块可见性 =====
  685. loadSectionConfig: function() {
  686. var self = this
  687. var baseSections = ['func_entries', 'daily_tip', 'recommended_reading']
  688. if (!uni.getStorageSync('token')) {
  689. self.visibleSections = baseSections
  690. return
  691. }
  692. var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  693. getVisibleSections({ pageKey: 'mind', role: role }).then(function(res) {
  694. if (res.code === 200 && res.data) {
  695. var keys = res.data.map(function(s) { return s.sectionKey })
  696. var merged = baseSections.slice()
  697. keys.forEach(function(k) { if (merged.indexOf(k) === -1) merged.push(k) })
  698. self.visibleSections = merged
  699. }
  700. }).catch(function() {
  701. self.visibleSections = ['func_entries', 'daily_tip', 'recommended_reading', 'mind_tasks', 'related_activities', 'purchased_courses']
  702. })
  703. },
  704. // 加载家庭天盘数据
  705. loadFamilyFortune: function() {
  706. var self = this
  707. if (!self.isLoggedIn) return
  708. getFamilyFortune().then(function(res) {
  709. if (res.code === 200 && res.data) {
  710. // 更新 todayMood 占位数据为真实接口数据
  711. self.todayMood = {
  712. emoji: res.data.moodEmoji || '\u{1F60A}',
  713. text: res.data.moodText || '平静',
  714. score: res.data.moodScore || 65
  715. }
  716. // 更新 dailyFortune computed 函数依赖的数据
  717. self.dailyFortuneData = {
  718. level: res.data.fortuneLevel,
  719. text: res.data.fortuneLevelText,
  720. description: res.data.fortuneDescription
  721. }
  722. // 更新主导五行
  723. self.traditionalData = self.traditionalData || {}
  724. self.traditionalData.wuxingElements = {}
  725. if (res.data.dominantElement) {
  726. self.traditionalData.wuxingElements[res.data.dominantElement] = 100
  727. }
  728. // 更新周报提示
  729. self.compatibilityData = self.compatibilityData || {}
  730. self.compatibilityData.weeklyInsight = res.data.weeklyTip || self.compatibilityData.weeklyInsight
  731. }
  732. }).catch(function(err) {
  733. console.error('Failed to load family fortune:', err)
  734. })
  735. },
  736. viewReport: function(report) {
  737. if (report && report.fileUrl) {
  738. uni.downloadFile({
  739. url: report.fileUrl,
  740. success: function(res) {
  741. if (res.statusCode === 200) {
  742. uni.openDocument({
  743. filePath: res.tempFilePath,
  744. success: function() {
  745. console.log('打开报告成功')
  746. }
  747. })
  748. }
  749. }
  750. })
  751. }
  752. },
  753. formatDate: function(dateStr) {
  754. if (!dateStr) return ''
  755. var d = parseDate(dateStr)
  756. if (!d) return ''
  757. var month = d.getMonth() + 1
  758. var day = d.getDate()
  759. return month + '月' + day + '日'
  760. },
  761. sectionVisible: function(key) {
  762. return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
  763. },
  764. // ===== 心理 Tab:EMI 数据 =====
  765. loadEmiData: function() {
  766. var self = this
  767. var memberId = self.currentChildId
  768. if (!memberId) {
  769. self.emiLoading = false
  770. return
  771. }
  772. self.emiLoading = true
  773. getEmiReport(memberId).then(function(res) {
  774. if (res.code === 200 && res.data) {
  775. self.emiData = res.data
  776. self.mindSubDims = (function() { var e = self.emiData || {}; return [{label:'情绪稳定性', score:e.emotionStability||0, desc:'识别、理解和管理情绪的综合能力'}, {label:'开放性', score:e.opennessScore||0, desc:'对新事物的接受程度'}, {label:'尽责性', score:e.conscientiousnessScore||0, desc:'做事有条理的程度'}, {label:'外向性', score:e.extraversionScore||0, desc:'社交活跃度'}, {label:'宜人性', score:e.agreeablenessScore||0, desc:'与人相处的和谐度'}, {label:'神经质', score:e.neuroticismScore||0, desc:'情绪波动程度(反向)'}, {label:'自主性', score:e.driveAutonomy||0, desc:'内在驱动力'}, {label:'胜任感', score:e.driveCompetence||0, desc:'对自己能力的信心'}, {label:'归属感', score:e.driveRelatedness||0, desc:'感受被接纳'}]; })();
  777. self.loadMindIndicators(memberId)
  778. }
  779. self.emiLoading = false
  780. }).catch(function() {
  781. self.emiLoading = false
  782. })
  783. },
  784. // ===== 肠脑轴情绪指标 =====
  785. loadGutData: function() {
  786. var self = this
  787. var memberId = self.currentChildId
  788. if (!memberId) return
  789. var token = uni.getStorageSync('token')
  790. // 获取神经递质指标
  791. uni.request({
  792. url: config.API_BASE_URL + '/api/mind/neurotransmitters',
  793. method: 'POST',
  794. header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
  795. data: { memberId: memberId },
  796. success: function(r) {
  797. if (r.statusCode === 200 && r.data && r.data.code === 200) {
  798. var list = r.data.data
  799. if (Array.isArray(list) && list.length > 0) {
  800. self.gutIndicators = list.slice(0, 4)
  801. }
  802. }
  803. }
  804. })
  805. // 获取情绪洞察摘要
  806. uni.request({
  807. url: config.API_BASE_URL + '/api/mind/gut-emotion-insight',
  808. method: 'POST',
  809. header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
  810. data: { memberId: memberId },
  811. success: function(r) {
  812. if (r.statusCode === 200 && r.data && r.data.code === 200 && r.data.data) {
  813. self.gutInsight = r.data.data.summary || ''
  814. }
  815. }
  816. })
  817. },
  818. indicatorColor: function(status) {
  819. if (status === 'abnormal' || status === '偏高' || status === '偏低') return '#EF4444'
  820. if (status === 'warning' || status === '偏高' || status === '偏低') return '#F59E0B'
  821. return '#10B981'
  822. },
  823. statusLabel: function(status) {
  824. if (status === 'normal') return '正常'
  825. if (status === 'abnormal') return '异常'
  826. if (status === 'warning') return '警讯'
  827. return status || '正常'
  828. },
  829. // 情绪影响映射:指标名 → 影响的情绪功能
  830. emotionImpactLabel: function(name) {
  831. if (!name) return ''
  832. if (name.indexOf('血清素') !== -1 || name.indexOf('5-HT') !== -1) return '情绪稳定'
  833. if (name.indexOf('多巴胺') !== -1 || name.indexOf('Dopamine') !== -1) return '愉悦·动力'
  834. if (name.indexOf('GABA') !== -1 || name.indexOf('氨基丁酸') !== -1) return '放松·减压'
  835. if (name.indexOf('色氨酸') !== -1 || name.indexOf('Tryptophan') !== -1) return '睡眠·情绪'
  836. if (name.indexOf('褪黑素') !== -1 || name.indexOf('Melatonin') !== -1) return '睡眠调节'
  837. if (name.indexOf('皮质醇') !== -1 || name.indexOf('Cortisol') !== -1) return '压力水平'
  838. return ''
  839. },
  840. // 指标值展示:后端字段为 indicatorValue,兼容数值型
  841. formatIndicatorValue: function(ind) {
  842. if (!ind) return ''
  843. var v = ind.value !== undefined && ind.value !== null ? ind.value : ind.indicatorValue
  844. if (v === undefined || v === null || v === '') return ''
  845. return String(v)
  846. },
  847. // 指标条宽度:将指标值归一化为 0-100% 百分比
  848. indicatorBarWidth: function(ind) {
  849. var raw = ind.value !== undefined && ind.value !== null ? ind.value : ind.indicatorValue
  850. var n = parseFloat(raw)
  851. if (!isNaN(n) && n >= 0 && n <= 100) return n + '%'
  852. if (!isNaN(n) && n > 100) return '100%'
  853. var st = ind.status || ''
  854. if (st === '偏低' || st === '缺乏' || st === 'abnormal') return '30%'
  855. if (st === '偏高' || st === '过高') return '75%'
  856. return '55%'
  857. },
  858. // ===== EMI 分数评级 =====
  859. getScoreLevel: function(score) {
  860. if (score >= 85) return 'excellent'
  861. if (score >= 70) return 'good'
  862. if (score >= 55) return 'average'
  863. return 'low'
  864. },
  865. getScoreLevelText: function(score) {
  866. if (score >= 85) return '优秀'
  867. if (score >= 70) return '良好'
  868. if (score >= 55) return '一般'
  869. return '需提升'
  870. },
  871. // ===== 导航 =====
  872. goEmotionReport: function() {
  873. var memberId = this.currentChildId || ''
  874. uni.navigateTo({ url: '/pages/mind-detail/emotion-report?memberId=' + memberId })
  875. },
  876. goArticles: function() {
  877. uni.navigateTo({ url: '/pages/article-center/index?dimension=mind' })
  878. },
  879. onFuncClick: function(item) {
  880. if (item.needLogin && !this.isLoggedIn) {
  881. uni.showModal({
  882. title: '提示',
  883. content: '请先登录后使用此功能',
  884. success: (res) => { if (res.confirm) this.openLoginSheet() }
  885. })
  886. return
  887. }
  888. if (item.page) {
  889. var url = item.page
  890. if (item.page.indexOf('innate-portrait') !== -1 || item.page.indexOf('innate-trajectory') !== -1) {
  891. var memberId = this.currentChildId
  892. if (!memberId) {
  893. uni.showToast({ title: '请先选择家庭成员', icon: 'none' })
  894. return
  895. }
  896. var familyId = (this.$store.state && this.$store.state.familyId) || uni.getStorageSync('familyId') || ''
  897. url += '?memberId=' + memberId + '&memberType=child&familyId=' + familyId
  898. }
  899. nav.goPage(url)
  900. }
  901. },
  902. // ===== 孩子列表与维度数据 =====
  903. loadChildren: function() {
  904. var self = this
  905. getChildren().then(function(res) {
  906. if (res.code === 200 && res.data) {
  907. self.children = res.data
  908. var savedChildId = uni.getStorageSync('currentChildId')
  909. var found = false
  910. for (var i = 0; i < self.children.length; i++) {
  911. if (self.children[i].memberId === savedChildId) {
  912. self.currentChildId = savedChildId
  913. found = true
  914. break
  915. }
  916. }
  917. // FIX: 不再自动选择第一个孩子。仅在用户通过 UserQuickEntry 切换时设置 currentChildId
  918. if (found) {
  919. self.loadDimensionData()
  920. self.loadEmiData()
  921. }
  922. }
  923. }).catch(function(e) {
  924. console.log('获取成员列表失败', e)
  925. })
  926. },
  927. loadDimensionData: function() {
  928. if (this.familyMembersVisible.length > 0) {
  929. this.loadCompatibilityData()
  930. }
  931. },
  932. loadSandboxData: function() {
  933. var self = this
  934. getFamilyEnergySandbox().then(function(res) {
  935. if (res && res.data) {
  936. self.sandboxData = res.data
  937. }
  938. }).catch(function() {})
  939. },
  940. loadMindRadarPopulationData: function() {
  941. var self = this
  942. if (!this.currentChildId && !this.selectedMemberId) return
  943. var memberId = this.selectedMemberId || this.currentChildId
  944. if (!memberId) return
  945. getRadarData({
  946. memberId: memberId,
  947. dimensions: ['bigFiveOpenness','bigFiveConscientiousness','bigFiveExtraversion','bigFiveAgreeableness','bigFiveNeuroticism','emotionStability','driveAutonomy','driveCompetence','driveRelatedness','selfConceptOverall']
  948. }).then(function(res) {
  949. if (res && res.data && res.data.populationScores) {
  950. var dims = res.data.dimensions || []
  951. var scores = res.data.populationScores
  952. var map = {}
  953. for (var i = 0; i < dims.length && i < scores.length; i++) {
  954. map[dims[i]] = scores[i]
  955. }
  956. self.mindPopulationScores = map
  957. }
  958. }).catch(function(e) { console.warn('[mind] radar-data failed', e) })
  959. },
  960. switchView: function(view) {
  961. this.currentView = view
  962. if (view === 'family') {
  963. this.selectedMemberId = null
  964. var self = this
  965. getEnergySandbox('family').then(function(res) {
  966. if (res && res.data && !res.data.locked) {
  967. self.sandboxData = res.data.familyData || res.data
  968. }
  969. }).catch(function(e) { console.log('获取家庭数据失败', e) })
  970. } else {
  971. this.loadSandboxData()
  972. }
  973. },
  974. },
  975. loadEnergyData: function() {
  976. var self = this
  977. if (!this.currentChildId) return
  978. getEnergyOverview(this.currentChildId).then(function(res) {
  979. if (res && res.data) {
  980. var dims = res.data.dimensions
  981. if (dims && dims.length > 0) {
  982. for (var i = 0; i < dims.length; i++) {
  983. if (dims[i] && dims[i].code === 'mind' && res.data.energyScores) {
  984. self.familyScore = res.data.energyScores.mindScore || 0
  985. }
  986. }
  987. }
  988. }
  989. }).catch(function(e) {
  990. console.log('获取能量概览失败', e)
  991. })
  992. },
  993. // 切换成员:同步成员ID并持久化,重载该页全部成员相关数据
  994. onFamilyMemberSelect: function(member) {
  995. if (!member || member.id === this.selectedMemberId) return
  996. this.selectedMemberId = member.id
  997. this.currentChildId = member.id
  998. uni.setStorageSync('currentChildId', member.id)
  999. if (this.$store) this.$store.commit('switchToChild', member.id)
  1000. this.reloadForMember()
  1001. },
  1002. // 切换成员后的统一重载入口
  1003. reloadForMember: function() {
  1004. if (!this.isLoggedIn) return
  1005. this.loadSandboxData()
  1006. this.loadMindRadarPopulationData()
  1007. this.loadEmiData()
  1008. this.loadGutData()
  1009. this.loadEnergyData()
  1010. this.loadTraditionalCultureData()
  1011. this.loadContacts()
  1012. this.loadHealthAlerts()
  1013. this.loadMilestones()
  1014. this.loadDanReports()
  1015. },
  1016. // 成员标签长按:唤出管理动作面板
  1017. onMemberLongPress: function(member) {
  1018. this.showMemberActions(member)
  1019. },
  1020. // 成员标签管理入口
  1021. onMemberManage: function() {
  1022. uni.navigateTo({ url: '/pages/profile-extra/family-members' })
  1023. },
  1024. // 成员管理动作面板(系统 ActionSheet,触摸目标由微信保证)
  1025. showMemberActions: function(member) {
  1026. if (!member) return
  1027. var self = this
  1028. var items = ['切换到该成员视角', '编辑资料', '移除成员']
  1029. uni.showActionSheet({
  1030. itemList: items,
  1031. success: function(res) {
  1032. if (res.tapIndex === 0) {
  1033. uni.navigateTo({ url: '/pages/action-detail/relationship-questionnaire?memberId=' + member.id })
  1034. } else if (res.tapIndex === 1) {
  1035. uni.navigateTo({ url: '/pages/family/add-member?memberId=' + member.id })
  1036. } else if (res.tapIndex === 2) {
  1037. uni.showModal({
  1038. title: '确认移除',
  1039. content: '确定要移除 ' + (member.nickname || '该成员') + ' 吗?',
  1040. success: function(m) {
  1041. if (m.confirm) {
  1042. uni.showLoading({ title: '移除中...' })
  1043. kickFamilyMemberById(member.id).then(function() {
  1044. uni.hideLoading()
  1045. uni.showToast({ title: '已移除', icon: 'success' })
  1046. self.loadFamilyMembersVisible()
  1047. }).catch(function(err) {
  1048. uni.hideLoading()
  1049. uni.showToast({ title: (err && err.message) || '移除失败', icon: 'none' })
  1050. })
  1051. }
  1052. }
  1053. })
  1054. }
  1055. }
  1056. })
  1057. },
  1058. onChildChanged: function(child) {
  1059. this.currentChildId = child.memberId
  1060. // FIX: 同步提交到 Vuex store
  1061. this.$store.commit('switchToChild', child.memberId)
  1062. this.loadDimensionData()
  1063. this.loadEmiData()
  1064. this.loadTraditionalCultureData()
  1065. },
  1066. loadMindIndicators: function(memberId) {
  1067. var self = this
  1068. getLatestIndicators({ memberId: memberId, domain: 'mind' }).then(function(res) {
  1069. if (res.code === 200) self.mindIndicators = res.data || []
  1070. }).catch(function(e) { console.log('[mind] loadMindIndicators failed', e) })
  1071. },
  1072. goMindDetail: function() {
  1073. var id = this.activeChildId || uni.getStorageSync('currentChildId')
  1074. if (!id) return
  1075. uni.navigateTo({ url: '/pages/mind-detail/member-mind-detail?memberId=' + id })
  1076. },
  1077. formatNumeric: function(v) {
  1078. if (v === null || v === undefined) return ''
  1079. var n = parseFloat(v)
  1080. if (isNaN(n)) return String(v)
  1081. return n % 1 === 0 ? String(n) : n.toFixed(1)
  1082. },
  1083. loadFamilyMembersVisible: function() {
  1084. var self = this
  1085. getFamilyMemberList({ visibleOnly: true }).then(function(res) {
  1086. self.familyMembersVisible = res.data || []
  1087. }).catch(function(e) {
  1088. console.log('获取可见家庭成员失败', e)
  1089. self.familyMembersVisible = []
  1090. })
  1091. },
  1092. loadCompatibilityData: function() {
  1093. var self = this
  1094. var token = uni.getStorageSync('token')
  1095. uni.request({
  1096. url: config.API_BASE_URL + '/api/mind/traditional/family-dashboard',
  1097. method: 'POST',
  1098. header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
  1099. data: {},
  1100. success: function(res) {
  1101. if (res.data && res.data.code === 200 && res.data.data) {
  1102. self.compatibilityData = res.data.data
  1103. }
  1104. },
  1105. fail: function() {}
  1106. })
  1107. },
  1108. scrollToSection: function(section) {
  1109. uni.pageScrollTo({ selector: '#' + section, duration: 300 })
  1110. },
  1111. goAssessment: function() { uni.navigateTo({ url: '/pages/assessment/apply' }) },
  1112. goLogin: function() { this.openLoginSheet() },
  1113. // 登录成功:刷新登录态数据(镜像 onShow 已登录分支)
  1114. onLoginSuccess: function() {
  1115. this.isLoggedIn = true
  1116. this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  1117. this.currentChildId = (this.$store.state && this.$store.state.currentChildId) || uni.getStorageSync('currentChildId') || null
  1118. this.userName = uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '家长'
  1119. this.loadSectionConfig()
  1120. this.loadChildren()
  1121. this.loadFamilyMembersVisible()
  1122. this.loadSandboxData()
  1123. this.loadMindRadarPopulationData()
  1124. this.loadEmiData()
  1125. this.loadGutData()
  1126. this.loadEnergyData()
  1127. this.loadTraditionalCultureData()
  1128. this.loadContacts()
  1129. this.loadHealthAlerts()
  1130. this.loadMilestones()
  1131. this.loadDanReports()
  1132. this._watchPageReady()
  1133. },
  1134. // ===== 传统文化数据 =====
  1135. // ===== 联系人 / 关系健康 =====
  1136. loadContacts: function() {
  1137. var self = this
  1138. getContactList({}).then(function(res) {
  1139. if (res.code === 200) {
  1140. self.contactList = Array.isArray(res.data) ? res.data : []
  1141. } else {
  1142. self.contactList = []
  1143. }
  1144. }).catch(function() {
  1145. self.contactList = []
  1146. })
  1147. },
  1148. loadHealthAlerts: function() {
  1149. var self = this
  1150. getHealthAlerts().then(function(res) {
  1151. if (res.code === 200) {
  1152. self.healthAlerts = Array.isArray(res.data) ? res.data : []
  1153. } else {
  1154. self.healthAlerts = []
  1155. }
  1156. }).catch(function() {
  1157. self.healthAlerts = []
  1158. })
  1159. },
  1160. loadMilestones: function() {
  1161. var self = this
  1162. getMilestones(30).then(function(res) {
  1163. if (res.code === 200) {
  1164. self.milestones = Array.isArray(res.data) ? res.data : []
  1165. } else {
  1166. self.milestones = []
  1167. }
  1168. }).catch(function() {
  1169. self.milestones = []
  1170. })
  1171. },
  1172. loadTraditionalCultureData: function() {
  1173. var self = this
  1174. var memberId = this.currentChildId
  1175. if (!memberId) {
  1176. self.traditionalLoading = false
  1177. return
  1178. }
  1179. self.traditionalLoading = true
  1180. uni.request({
  1181. url: config.API_BASE_URL + '/api/mind/traditional/mirror',
  1182. method: 'POST',
  1183. data: { memberId: memberId, memberType: 'child' },
  1184. success: function(res) {
  1185. self.traditionalLoading = false
  1186. if (res.data && res.data.code === 200 && res.data.data) {
  1187. var d = res.data.data
  1188. if (d.eightCharacters || d.wuxingElements || d.zodiac || d.numSoul) {
  1189. self.traditionalData = d
  1190. } else {
  1191. self.traditionalData = null
  1192. }
  1193. } else {
  1194. self.traditionalData = null
  1195. }
  1196. },
  1197. fail: function() {
  1198. self.traditionalLoading = false
  1199. self.traditionalData = null
  1200. }
  1201. })
  1202. },
  1203. _watchPageReady() {
  1204. var self = this
  1205. var unwatch = this.$watch(function() {
  1206. if (self.isLoggedIn) {
  1207. return !!(self.emiData || self.energyOverview)
  1208. } }, function(val) {
  1209. if (val) {
  1210. if (unwatch) unwatch()
  1211. if (self.$refs.tabTransition) self.$refs.tabTransition.markReady()
  1212. }
  1213. })
  1214. },
  1215. // ===== DAN 报告 =====
  1216. loadDanReports: function() {
  1217. var self = this
  1218. var memberId = self.currentChildId
  1219. if (!memberId) return
  1220. getDanReportByChild(memberId, 'mind').then(function(res) {
  1221. if (res.code === 200 && res.data) {
  1222. self.danReports = res.data
  1223. }
  1224. }).catch(function() {})
  1225. },
  1226. goDanUpload: function(dimension) {
  1227. var memberId = this.selectedMemberId || this.currentChildId
  1228. var url = '/pages/health/report-upload?type=a2'
  1229. if (memberId) {
  1230. url += '&memberId=' + memberId
  1231. }
  1232. uni.navigateTo({ url: url })
  1233. },
  1234. viewDanReport: function(report) {
  1235. var reportId = report.sourceReportId || report.id
  1236. uni.navigateTo({ url: '/pages/health/report-detail?reportType=dan&reportId=' + reportId })
  1237. },
  1238. formatDate: function(dateStr) {
  1239. if (!dateStr) return ''
  1240. var d = parseDate(dateStr)
  1241. if (!d) return ''
  1242. return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate()
  1243. }
  1244. }
  1245. </script>
  1246. <style scoped lang="scss">
  1247. // 页面级局部调色板:仅本页专有色阶,跨页共性一律走 uni.scss 全局令牌
  1248. // 心·极深文字
  1249. $c-mind-ink: #9D174D;
  1250. // 玫瑰浅底
  1251. $tint-rose: #FDF2F7;
  1252. // 天蓝浅底
  1253. $tint-sky: #EBF2FA;
  1254. // 天蓝浅底·深(旧蓝系浅档,C-2 待翻转)
  1255. $tint-sky-deep: #D6EAF8;
  1256. // 中性浅底
  1257. $tint-gray: #F8F9FA;
  1258. // 青灰浅底
  1259. $tint-cyan: #F0F6F7;
  1260. .container {
  1261. min-height: 100vh;
  1262. background: $app-bg;
  1263. padding-bottom: 120rpx;
  1264. }
  1265. /* ===== 心·心理 标题栏 ===== */
  1266. .section-title-bar {
  1267. display: flex;
  1268. flex-direction: row;
  1269. align-items: center;
  1270. margin: 20rpx 30rpx 10rpx;
  1271. padding: 16rpx 24rpx;
  1272. background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
  1273. border-radius: 20rpx;
  1274. box-shadow: 0 4rpx 16rpx rgba(139,92,246,0.25);
  1275. }
  1276. .section-title-icon {
  1277. font-size: 32rpx;
  1278. color: $app-surface;
  1279. margin-right: 12rpx;
  1280. }
  1281. .section-title-text {
  1282. font-size: 32rpx;
  1283. font-weight: bold;
  1284. color: $app-surface;
  1285. }
  1286. /* ===== 心理报告上传卡片 ===== */
  1287. .mind-report-section {
  1288. margin: 20rpx 30rpx;
  1289. }
  1290. .mind-report-card {
  1291. display: flex;
  1292. flex-direction: row;
  1293. align-items: center;
  1294. background: linear-gradient(135deg, $tint-purple-soft, $dim-mind-tint);
  1295. border-radius: 20rpx;
  1296. padding: 28rpx 24rpx;
  1297. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1298. }
  1299. .mind-report-left {
  1300. width: 100rpx;
  1301. height: 100rpx;
  1302. background: rgba(255,255,255,0.7);
  1303. border-radius: 20rpx;
  1304. display: flex;
  1305. align-items: center;
  1306. justify-content: center;
  1307. margin-right: 20rpx;
  1308. flex-shrink: 0;
  1309. }
  1310. .mind-report-icon {
  1311. font-size: 52rpx;
  1312. }
  1313. .mind-report-right {
  1314. flex: 1;
  1315. }
  1316. .mind-report-title {
  1317. font-size: 30rpx;
  1318. font-weight: bold;
  1319. color: $facade-violet-deep;
  1320. display: block;
  1321. margin-bottom: 6rpx;
  1322. }
  1323. .mind-report-desc {
  1324. font-size: 22rpx;
  1325. color: $facade-violet-light-2;
  1326. line-height: 1.5;
  1327. display: block;
  1328. margin-bottom: 10rpx;
  1329. }
  1330. .mind-report-tags {
  1331. display: flex;
  1332. flex-direction: row;
  1333. gap: 10rpx;
  1334. }
  1335. .mind-tag {
  1336. font-size: 20rpx;
  1337. color: $facade-violet-deep;
  1338. background: rgba(255,255,255,0.6);
  1339. padding: 4rpx 14rpx;
  1340. border-radius: 20rpx;
  1341. }
  1342. .mind-report-arrow {
  1343. font-size: 36rpx;
  1344. color: $brand-primary-light;
  1345. margin-left: 10rpx;
  1346. flex-shrink: 0;
  1347. }
  1348. .mind-report-card:active {
  1349. opacity: 0.7;
  1350. }
  1351. .mind-report-list {
  1352. margin-top: 12rpx;
  1353. }
  1354. .mind-report-item {
  1355. display: flex;
  1356. flex-direction: row;
  1357. align-items: center;
  1358. background: $app-surface;
  1359. border-radius: 14rpx;
  1360. padding: 20rpx 20rpx;
  1361. margin-bottom: 8rpx;
  1362. box-shadow: 0 1rpx 6rpx rgba(0,0,0,0.05);
  1363. }
  1364. .mind-report-item:active {
  1365. opacity: 0.7;
  1366. }
  1367. .mind-report-item-icon {
  1368. font-size: 28rpx;
  1369. margin-right: 14rpx;
  1370. }
  1371. .mind-report-item-name {
  1372. font-size: 26rpx;
  1373. color: $app-text;
  1374. flex: 1;
  1375. }
  1376. .mind-report-item-date {
  1377. font-size: 22rpx;
  1378. color: $app-text-muted;
  1379. margin-right: 14rpx;
  1380. }
  1381. .mind-report-item-status {
  1382. font-size: 22rpx;
  1383. padding: 2rpx 12rpx;
  1384. border-radius: 20rpx;
  1385. }
  1386. .mind-report-item-status.parsed {
  1387. color: $facade-violet-deep;
  1388. background: $tint-purple-soft;
  1389. }
  1390. .mind-report-item-status.pending {
  1391. color: $app-text-muted;
  1392. background: $app-surface-subtle;
  1393. }
  1394. .mind-report-empty {
  1395. display: flex;
  1396. flex-direction: row;
  1397. align-items: center;
  1398. justify-content: center;
  1399. padding: 24rpx;
  1400. margin-top: 8rpx;
  1401. }
  1402. .mind-report-empty-icon {
  1403. font-size: 32rpx;
  1404. margin-right: 10rpx;
  1405. }
  1406. .mind-report-empty-text {
  1407. font-size: 24rpx;
  1408. color: $app-border;
  1409. }
  1410. /* ===== 心理能量概况 ===== */
  1411. .energy-overview {
  1412. display: flex;
  1413. flex-direction: row;
  1414. margin: 20rpx 30rpx;
  1415. background: $app-surface;
  1416. border-radius: 20rpx;
  1417. padding: 24rpx 16rpx;
  1418. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1419. justify-content: space-around;
  1420. }
  1421. .energy-badge {
  1422. display: flex;
  1423. flex-direction: column;
  1424. align-items: center;
  1425. flex: 1;
  1426. }
  1427. .badge-icon {
  1428. font-size: 36rpx;
  1429. margin-bottom: 8rpx;
  1430. }
  1431. .badge-value {
  1432. font-size: 36rpx;
  1433. font-weight: bold;
  1434. color: $app-text;
  1435. }
  1436. .badge-label {
  1437. font-size: 20rpx;
  1438. color: $app-text-muted;
  1439. margin-top: 4rpx;
  1440. }
  1441. /* ===== EMI 四维条形图 ===== */
  1442. .emi-section {
  1443. margin: 20rpx 30rpx;
  1444. }
  1445. .emi-card {
  1446. background: $app-surface;
  1447. border-radius: 24rpx;
  1448. padding: 30rpx;
  1449. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  1450. }
  1451. .emi-card-title {
  1452. font-size: 30rpx;
  1453. font-weight: bold;
  1454. color: $app-text;
  1455. display: block;
  1456. }
  1457. .personality-header {
  1458. display: flex;
  1459. flex-direction: row;
  1460. align-items: center;
  1461. justify-content: space-between;
  1462. margin-bottom: 24rpx;
  1463. }
  1464. .test-btn {
  1465. background: $brand-primary;
  1466. padding: 8rpx 28rpx;
  1467. border-radius: 30rpx;
  1468. }
  1469. .test-btn-text {
  1470. font-size: 24rpx;
  1471. color: $app-surface;
  1472. font-weight: 500;
  1473. }
  1474. .emi-bar-item {
  1475. display: flex;
  1476. flex-direction: row;
  1477. align-items: center;
  1478. margin-bottom: 20rpx;
  1479. }
  1480. .emi-bar-label {
  1481. font-size: 24rpx;
  1482. color: $app-text-secondary;
  1483. width: 120rpx;
  1484. flex-shrink: 0;
  1485. }
  1486. .emi-bar-track {
  1487. flex: 1;
  1488. height: 20rpx;
  1489. background: $app-surface-subtle;
  1490. border-radius: 10rpx;
  1491. overflow: hidden;
  1492. margin: 0 16rpx;
  1493. }
  1494. .emi-bar-fill {
  1495. height: 100%;
  1496. border-radius: 10rpx;
  1497. transition: width 0.5s;
  1498. }
  1499. .emi-bar-score {
  1500. font-size: 26rpx;
  1501. font-weight: bold;
  1502. color: $app-text;
  1503. width: 60rpx;
  1504. text-align: right;
  1505. flex-shrink: 0;
  1506. }
  1507. .emi-bar-level {
  1508. font-size: 20rpx;
  1509. width: 80rpx;
  1510. text-align: right;
  1511. flex-shrink: 0;
  1512. }
  1513. .level-excellent { color: $status-success; }
  1514. .level-good { color: $status-info; }
  1515. .level-average { color: $brand-primary; }
  1516. .level-low { color: $status-danger; }
  1517. .emi-footer {
  1518. display: flex;
  1519. flex-direction: row;
  1520. justify-content: space-between;
  1521. align-items: center;
  1522. margin-top: 20rpx;
  1523. padding-top: 20rpx;
  1524. border-top: 1rpx solid $app-surface-subtle;
  1525. }
  1526. .emi-overall {
  1527. font-size: 28rpx;
  1528. font-weight: bold;
  1529. color: $mind-accent;
  1530. }
  1531. .emi-link {
  1532. display: flex;
  1533. flex-direction: row;
  1534. align-items: center;
  1535. }
  1536. .emi-link-text {
  1537. font-size: 26rpx;
  1538. color: $mind-accent;
  1539. font-weight: 500;
  1540. }
  1541. .emi-link-arrow {
  1542. font-size: 26rpx;
  1543. color: $mind-accent;
  1544. margin-left: 8rpx;
  1545. }
  1546. /* ===== EMI 心理报告卡片 ===== */
  1547. .emi-card {
  1548. margin: 30rpx;
  1549. background: $app-surface;
  1550. border-radius: 24rpx;
  1551. padding: 30rpx;
  1552. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  1553. }
  1554. .emi-header {
  1555. margin-bottom: 24rpx;
  1556. }
  1557. .emi-title {
  1558. font-size: 30rpx;
  1559. font-weight: bold;
  1560. color: $app-text;
  1561. display: block;
  1562. }
  1563. .emi-overall-wrap {
  1564. display: flex;
  1565. flex-direction: column;
  1566. align-items: center;
  1567. padding: 32rpx 0;
  1568. background: linear-gradient(135deg, $mind-accent, $accent-gold);
  1569. border-radius: 16rpx;
  1570. margin: 20rpx 0;
  1571. color: $app-surface;
  1572. }
  1573. .emi-overall-label {
  1574. font-size: 26rpx;
  1575. margin-bottom: 6rpx;
  1576. }
  1577. .emi-overall-value {
  1578. font-size: 64rpx;
  1579. font-weight: bold;
  1580. margin-bottom: 6rpx;
  1581. }
  1582. .emi-overall-level {
  1583. font-size: 24rpx;
  1584. padding: 4rpx 16rpx;
  1585. border-radius: 12rpx;
  1586. background: rgba(255,255,255,0.2);
  1587. }
  1588. .emi-dimensions {
  1589. display: flex;
  1590. flex-direction: column;
  1591. gap: 20rpx;
  1592. }
  1593. .emi-dimension {
  1594. display: flex;
  1595. flex-direction: column;
  1596. }
  1597. .emi-dim-header {
  1598. display: flex;
  1599. flex-direction: row;
  1600. justify-content: space-between;
  1601. align-items: center;
  1602. margin-bottom: 8rpx;
  1603. }
  1604. .emi-dim-label {
  1605. font-size: 26rpx;
  1606. color: $app-text-secondary;
  1607. }
  1608. .emi-dim-values {
  1609. display: flex;
  1610. flex-direction: row;
  1611. align-items: center;
  1612. }
  1613. .emi-dim-score {
  1614. font-size: 28rpx;
  1615. font-weight: bold;
  1616. color: $mind-accent;
  1617. margin-right: 12rpx;
  1618. }
  1619. .emi-dim-level {
  1620. font-size: 20rpx;
  1621. padding: 2rpx 12rpx;
  1622. border-radius: 8rpx;
  1623. }
  1624. .emi-dim-bar {
  1625. height: 14rpx;
  1626. background: $app-surface-subtle;
  1627. border-radius: 7rpx;
  1628. overflow: hidden;
  1629. }
  1630. .emi-dim-bar-fill {
  1631. height: 100%;
  1632. border-radius: 7rpx;
  1633. transition: width 0.5s;
  1634. }
  1635. .emi-footer {
  1636. display: flex;
  1637. justify-content: flex-end;
  1638. align-items: center;
  1639. padding-top: 20rpx;
  1640. border-top: 1rpx solid $app-surface-subtle;
  1641. margin-top: 20rpx;
  1642. }
  1643. .emi-footer-text {
  1644. font-size: 24rpx;
  1645. color: $brand-primary;
  1646. font-weight: 500;
  1647. }
  1648. .emi-footer-icon {
  1649. font-size: 24rpx;
  1650. margin-left: 8rpx;
  1651. }
  1652. .level-excellent { color: $status-success; background: $status-success-tint; }
  1653. .level-good { color: $status-info; background: $status-info-tint; }
  1654. .level-average { color: $brand-primary; background: $dim-wealth-tint; }
  1655. .level-low { color: $status-danger; background: $status-danger-tint; }
  1656. /* 雷达图内的空状态 */
  1657. .placeholder-content {
  1658. display: flex;
  1659. flex-direction: column;
  1660. align-items: center;
  1661. padding: 40rpx 20rpx;
  1662. }
  1663. /* ===== 占位卡 ===== */
  1664. .placeholder-card {
  1665. margin: 30rpx;
  1666. background: $app-surface;
  1667. border-radius: 24rpx;
  1668. padding: 60rpx 40rpx;
  1669. display: flex;
  1670. flex-direction: column;
  1671. align-items: center;
  1672. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  1673. }
  1674. .placeholder-icon {
  1675. font-size: 80rpx;
  1676. margin-bottom: 20rpx;
  1677. }
  1678. .placeholder-title {
  1679. font-size: 32rpx;
  1680. font-weight: bold;
  1681. color: $app-text;
  1682. margin-bottom: 16rpx;
  1683. }
  1684. .placeholder-desc {
  1685. font-size: 26rpx;
  1686. color: $app-text-muted;
  1687. text-align: center;
  1688. line-height: 1.6;
  1689. margin-bottom: 30rpx;
  1690. }
  1691. .placeholder-btn {
  1692. background: linear-gradient(135deg, $mind-accent, $accent-gold);
  1693. color: $app-surface;
  1694. font-size: 28rpx;
  1695. font-weight: bold;
  1696. padding: 16rpx 64rpx;
  1697. border-radius: 40rpx;
  1698. box-shadow: 0 4rpx 16rpx rgba(255,107,53,0.3);
  1699. }
  1700. /* ===== 传统文化卡片 ===== */
  1701. .culture-section {
  1702. margin: 20rpx 30rpx;
  1703. }
  1704. .culture-card {
  1705. background: $app-surface;
  1706. border-radius: 20rpx;
  1707. padding: 24rpx;
  1708. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
  1709. margin-bottom: 16rpx;
  1710. }
  1711. .culture-card-header {
  1712. display: flex;
  1713. align-items: center;
  1714. margin-bottom: 16rpx;
  1715. }
  1716. .culture-card-icon {
  1717. font-size: 32rpx;
  1718. margin-right: 10rpx;
  1719. }
  1720. .culture-card-title {
  1721. font-size: 28rpx;
  1722. font-weight: 600;
  1723. color: $app-text;
  1724. }
  1725. .culture-card-body {
  1726. font-size: 24rpx;
  1727. color: $app-text-secondary;
  1728. line-height: 1.6;
  1729. }
  1730. /* 八字四柱 */
  1731. .bazi-grid {
  1732. display: flex;
  1733. justify-content: space-around;
  1734. padding: 8rpx 0;
  1735. }
  1736. .bazi-column {
  1737. display: flex;
  1738. flex-direction: column;
  1739. align-items: center;
  1740. }
  1741. .bazi-pillar-label {
  1742. font-size: 22rpx;
  1743. color: $app-text-muted;
  1744. margin-bottom: 6rpx;
  1745. }
  1746. .bazi-pillar-value {
  1747. font-size: 32rpx;
  1748. font-weight: 700;
  1749. color: $brand-primary;
  1750. background: $facade-violet-tint;
  1751. padding: 10rpx 14rpx;
  1752. border-radius: 10rpx;
  1753. }
  1754. /* 五行能量 */
  1755. .wuxing-list {
  1756. display: flex;
  1757. flex-direction: column;
  1758. gap: 12rpx;
  1759. }
  1760. .wuxing-item {
  1761. display: flex;
  1762. align-items: center;
  1763. gap: 12rpx;
  1764. }
  1765. .wuxing-label {
  1766. font-size: 26rpx;
  1767. font-weight: 600;
  1768. width: 44rpx;
  1769. text-align: center;
  1770. }
  1771. .wuxing-bar-track {
  1772. flex: 1;
  1773. height: 20rpx;
  1774. background: $app-surface-subtle;
  1775. border-radius: 10rpx;
  1776. overflow: hidden;
  1777. }
  1778. .wuxing-bar-fill {
  1779. height: 100%;
  1780. border-radius: 10rpx;
  1781. }
  1782. .wuxing-value {
  1783. font-size: 22rpx;
  1784. color: $app-text-secondary;
  1785. width: 56rpx;
  1786. text-align: right;
  1787. }
  1788. .energy-card {
  1789. margin-top: 20rpx;
  1790. padding: 24rpx 20rpx;
  1791. background: linear-gradient(135deg, $app-ink-night 0%, $app-ink-night-2 50%, $app-ink-night-3 100%);
  1792. border-radius: 20rpx;
  1793. position: relative;
  1794. overflow: hidden;
  1795. }
  1796. .energy-card::before {
  1797. content: '';
  1798. position: absolute;
  1799. top: -60rpx;
  1800. right: -60rpx;
  1801. width: 200rpx;
  1802. height: 200rpx;
  1803. background: radial-gradient(circle, rgba(255,215,0,0.15) 0%, transparent 70%);
  1804. border-radius: 50%;
  1805. }
  1806. .energy-card::after {
  1807. content: '';
  1808. position: absolute;
  1809. bottom: -40rpx;
  1810. left: -40rpx;
  1811. width: 160rpx;
  1812. height: 160rpx;
  1813. background: radial-gradient(circle, rgba(99,102,241,0.12) 0%, transparent 70%);
  1814. border-radius: 50%;
  1815. }
  1816. .energy-header {
  1817. display: flex;
  1818. align-items: center;
  1819. margin-bottom: 14rpx;
  1820. position: relative;
  1821. z-index: 1;
  1822. }
  1823. .energy-icon {
  1824. font-size: 32rpx;
  1825. margin-right: 10rpx;
  1826. }
  1827. .energy-label {
  1828. font-size: 28rpx;
  1829. font-weight: 700;
  1830. background: linear-gradient(135deg, $dim-wealth, $dim-mind);
  1831. -webkit-background-clip: text;
  1832. -webkit-text-fill-color: transparent;
  1833. background-clip: text;
  1834. }
  1835. .energy-divider {
  1836. height: 2rpx;
  1837. background: linear-gradient(90deg, rgba(255,215,0,0.5), rgba(255,107,157,0.3), transparent);
  1838. margin-bottom: 18rpx;
  1839. position: relative;
  1840. z-index: 1;
  1841. }
  1842. .energy-quote {
  1843. font-size: 26rpx;
  1844. color: $app-border-subtle;
  1845. line-height: 1.8;
  1846. display: block;
  1847. margin-bottom: 14rpx;
  1848. position: relative;
  1849. z-index: 1;
  1850. padding-left: 20rpx;
  1851. letter-spacing: 1rpx;
  1852. }
  1853. .energy-quote::before {
  1854. content: '';
  1855. position: absolute;
  1856. left: 0;
  1857. top: 4rpx;
  1858. bottom: 4rpx;
  1859. width: 4rpx;
  1860. background: linear-gradient(180deg, $dim-wealth, $dim-mind);
  1861. border-radius: 2rpx;
  1862. }
  1863. .energy-advice {
  1864. font-size: 24rpx;
  1865. color: $app-text-subtle;
  1866. line-height: 1.7;
  1867. display: block;
  1868. position: relative;
  1869. z-index: 1;
  1870. padding: 14rpx 18rpx;
  1871. background: rgba(255,255,255,0.05);
  1872. border-radius: 12rpx;
  1873. border: 1rpx solid rgba(255,255,255,0.08);
  1874. }
  1875. .energy-footer {
  1876. display: flex;
  1877. justify-content: center;
  1878. gap: 12rpx;
  1879. margin-top: 18rpx;
  1880. position: relative;
  1881. z-index: 1;
  1882. }
  1883. .energy-footer-dot {
  1884. width: 8rpx;
  1885. height: 8rpx;
  1886. border-radius: 50%;
  1887. background: rgba(255,255,255,0.3);
  1888. }
  1889. .energy-footer-dot:nth-child(2) {
  1890. background: rgba(255,215,0,0.6);
  1891. width: 10rpx;
  1892. height: 10rpx;
  1893. }
  1894. /* 星座+灵数双栏 */
  1895. .culture-dual {
  1896. display: flex;
  1897. gap: 16rpx;
  1898. }
  1899. .culture-dual-item {
  1900. flex: 1;
  1901. background: $app-surface;
  1902. border-radius: 20rpx;
  1903. padding: 20rpx;
  1904. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
  1905. display: flex;
  1906. flex-direction: column;
  1907. align-items: center;
  1908. }
  1909. .culture-dual-icon {
  1910. font-size: 36rpx;
  1911. margin-bottom: 8rpx;
  1912. }
  1913. .culture-dual-label {
  1914. font-size: 22rpx;
  1915. color: $app-text-muted;
  1916. margin-bottom: 6rpx;
  1917. }
  1918. .culture-dual-value {
  1919. font-size: 28rpx;
  1920. font-weight: 700;
  1921. color: $brand-primary;
  1922. }
  1923. .culture-empty {
  1924. text-align: center;
  1925. padding: 30rpx 0;
  1926. color: $app-border;
  1927. font-size: 24rpx;
  1928. }
  1929. /* ===== 每日心理 ===== */
  1930. .tip-card {
  1931. background: $app-surface;
  1932. border-radius: 20rpx;
  1933. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1934. display: flex;
  1935. flex-direction: row;
  1936. overflow: hidden;
  1937. }
  1938. .tip-left-bar {
  1939. width: 8rpx;
  1940. background: $dim-mind;
  1941. flex-shrink: 0;
  1942. }
  1943. .tip-body {
  1944. flex: 1;
  1945. padding: 28rpx 24rpx;
  1946. }
  1947. .tip-title {
  1948. font-size: 28rpx;
  1949. font-weight: bold;
  1950. color: $dim-mind;
  1951. display: block;
  1952. margin-bottom: 12rpx;
  1953. }
  1954. .tip-content {
  1955. font-size: 24rpx;
  1956. color: $app-text-secondary;
  1957. line-height: 1.6;
  1958. display: block;
  1959. margin-bottom: 16rpx;
  1960. }
  1961. .tip-footer {
  1962. display: flex;
  1963. flex-direction: row;
  1964. justify-content: flex-end;
  1965. }
  1966. .tip-source {
  1967. font-size: 22rpx;
  1968. color: $app-border;
  1969. }
  1970. /* ===== 通用区块 ===== */
  1971. .section {
  1972. margin: 20rpx 30rpx;
  1973. }
  1974. .section-header {
  1975. margin-bottom: 20rpx;
  1976. }
  1977. .section-title {
  1978. font-size: 30rpx;
  1979. font-weight: bold;
  1980. color: $app-text;
  1981. }
  1982. /* ===== 推荐阅读 ===== */
  1983. .article-list {
  1984. display: flex;
  1985. flex-direction: column;
  1986. }
  1987. .article-card {
  1988. background: $app-surface;
  1989. border-radius: 20rpx;
  1990. padding: 28rpx 24rpx;
  1991. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1992. margin-bottom: 20rpx;
  1993. }
  1994. .article-category {
  1995. display: inline-block;
  1996. padding: 4rpx 18rpx;
  1997. border-radius: 20rpx;
  1998. margin-bottom: 14rpx;
  1999. }
  2000. .article-category-text {
  2001. font-size: 20rpx;
  2002. color: $app-surface;
  2003. font-weight: 500;
  2004. }
  2005. .article-title {
  2006. font-size: 28rpx;
  2007. font-weight: bold;
  2008. color: $app-text;
  2009. display: block;
  2010. margin-bottom: 10rpx;
  2011. }
  2012. .article-summary {
  2013. font-size: 24rpx;
  2014. color: $app-text-muted;
  2015. line-height: 1.5;
  2016. display: block;
  2017. margin-bottom: 20rpx;
  2018. }
  2019. .article-meta {
  2020. display: flex;
  2021. flex-direction: row;
  2022. align-items: center;
  2023. justify-content: space-between;
  2024. }
  2025. .meta-item {
  2026. display: flex;
  2027. flex-direction: row;
  2028. align-items: center;
  2029. margin-right: 20rpx;
  2030. }
  2031. .meta-icon {
  2032. font-size: 22rpx;
  2033. margin-right: 4rpx;
  2034. }
  2035. .meta-text {
  2036. font-size: 22rpx;
  2037. color: $app-border;
  2038. }
  2039. .article-date {
  2040. font-size: 22rpx;
  2041. color: $app-border;
  2042. margin-left: auto;
  2043. }
  2044. /* ===== 功能区入口 ===== */
  2045. .func-section {
  2046. margin: 20rpx 30rpx;
  2047. }
  2048. .func-grid {
  2049. display: flex;
  2050. flex-direction: column;
  2051. gap: 16rpx;
  2052. background: $app-surface;
  2053. border-radius: 20rpx;
  2054. padding: 24rpx 16rpx;
  2055. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  2056. }
  2057. .func-row {
  2058. display: flex;
  2059. flex-direction: row;
  2060. justify-content: space-between;
  2061. }
  2062. .func-item {
  2063. display: flex;
  2064. flex-direction: column;
  2065. align-items: center;
  2066. flex: 1;
  2067. }
  2068. .func-icon-wrap {
  2069. width: 80rpx;
  2070. height: 80rpx;
  2071. border-radius: 40rpx;
  2072. background: linear-gradient(135deg, $tint-sky-deep, $tint-sky);
  2073. display: flex;
  2074. align-items: center;
  2075. justify-content: center;
  2076. margin-bottom: 10rpx;
  2077. }
  2078. .func-icon {
  2079. font-size: 40rpx;
  2080. }
  2081. .func-label {
  2082. font-size: 22rpx;
  2083. color: $app-text-secondary;
  2084. font-weight: 500;
  2085. }
  2086. .func-item:active {
  2087. opacity: 0.7;
  2088. }
  2089. /* ===== 底部 ===== */
  2090. .indicator-cards-section { margin: 20rpx 30rpx; }
  2091. .indicator-cards { display: flex; flex-direction: row; flex-wrap: wrap; gap: 16rpx; }
  2092. .indicator-card { width: calc(50% - 8rpx); background: $app-surface; border-radius: 20rpx; padding: 24rpx 20rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
  2093. .indicator-card.no-data { opacity: 0.5; }
  2094. .indicator-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
  2095. .indicator-name { font-size: 26rpx; color: $app-text-secondary; font-weight: 500; }
  2096. .indicator-unit { font-size: 22rpx; color: $app-text-muted; }
  2097. .indicator-body { margin-bottom: 8rpx; }
  2098. .indicator-value { font-size: 40rpx; font-weight: bold; color: $dim-mind; }
  2099. .indicator-placeholder { font-size: 26rpx; color: $app-border; }
  2100. .indicator-range { font-size: 20rpx; color: $app-border; }
  2101. .bottom-spacer { height: 120rpx; }
  2102. /* ===== 肠脑轴 ===== */
  2103. .gut-insight-text {
  2104. font-size: 24rpx;
  2105. color: $app-text-secondary;
  2106. margin-bottom: 16rpx;
  2107. padding: 12rpx 16rpx;
  2108. background: $dim-action-tint;
  2109. border-radius: 12rpx;
  2110. line-height: 1.6;
  2111. }
  2112. .gut-indicator-list {
  2113. display: flex;
  2114. flex-direction: column;
  2115. gap: 12rpx;
  2116. }
  2117. .gut-indicator-item {
  2118. display: flex;
  2119. align-items: center;
  2120. gap: 12rpx;
  2121. }
  2122. .gut-indicator-left {
  2123. width: 110rpx;
  2124. flex-shrink: 0;
  2125. }
  2126. .gut-indicator-name {
  2127. font-size: 22rpx;
  2128. color: $app-text-secondary;
  2129. font-weight: 500;
  2130. display: block;
  2131. }
  2132. .gut-indicator-impact {
  2133. font-size: 18rpx;
  2134. color: $dim-mind;
  2135. display: block;
  2136. margin-top: 2rpx;
  2137. }
  2138. .gut-indicator-bar-track {
  2139. flex: 1;
  2140. height: 16rpx;
  2141. background: $app-surface-subtle;
  2142. border-radius: 8rpx;
  2143. overflow: hidden;
  2144. }
  2145. .gut-indicator-bar-fill {
  2146. height: 100%;
  2147. border-radius: 8rpx;
  2148. transition: width 0.3s;
  2149. }
  2150. .gut-indicator-value {
  2151. font-size: 22rpx;
  2152. color: $app-text-secondary;
  2153. width: 60rpx;
  2154. text-align: right;
  2155. }
  2156. .gut-indicator-status {
  2157. font-size: 20rpx;
  2158. padding: 2rpx 12rpx;
  2159. border-radius: 10rpx;
  2160. width: 60rpx;
  2161. text-align: center;
  2162. }
  2163. .status-normal {
  2164. background: $status-success-soft;
  2165. color: $status-success-deep;
  2166. }
  2167. .status-abnormal {
  2168. background: $status-danger-soft;
  2169. color: $status-danger-deep;
  2170. }
  2171. .status-warning {
  2172. background: $status-warning-soft;
  2173. color: $status-warning-deep;
  2174. }
  2175. /* ===== 关键信息(家庭+个人) ===== */
  2176. .key-info-card {
  2177. background: $app-surface;
  2178. border-radius: 20rpx;
  2179. padding: 24rpx;
  2180. margin-top: 20rpx;
  2181. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  2182. }
  2183. .key-info-header {
  2184. display: flex;
  2185. align-items: center;
  2186. margin-bottom: 20rpx;
  2187. }
  2188. .key-info-icon {
  2189. font-size: 28rpx;
  2190. margin-right: 8rpx;
  2191. }
  2192. .key-info-title {
  2193. font-size: 28rpx;
  2194. font-weight: 600;
  2195. color: $app-text;
  2196. }
  2197. .key-info-body {
  2198. display: flex;
  2199. flex-direction: row;
  2200. gap: 20rpx;
  2201. }
  2202. .key-info-col {
  2203. flex: 1;
  2204. background: $tint-gray;
  2205. border-radius: 14rpx;
  2206. padding: 16rpx;
  2207. min-width: 0;
  2208. }
  2209. .ki-subtitle {
  2210. font-size: 22rpx;
  2211. color: $app-text-muted;
  2212. margin-bottom: 12rpx;
  2213. font-weight: 500;
  2214. }
  2215. .ki-row {
  2216. display: flex;
  2217. align-items: center;
  2218. margin-bottom: 10rpx;
  2219. flex-wrap: nowrap;
  2220. }
  2221. .ki-row:last-child {
  2222. margin-bottom: 0;
  2223. }
  2224. .ki-label {
  2225. font-size: 22rpx;
  2226. color: $app-text-muted;
  2227. margin-right: 8rpx;
  2228. white-space: nowrap;
  2229. }
  2230. .ki-dot {
  2231. width: 16rpx;
  2232. height: 16rpx;
  2233. border-radius: 50%;
  2234. margin-right: 6rpx;
  2235. flex-shrink: 0;
  2236. }
  2237. .ki-valuel {
  2238. font-size: 24rpx;
  2239. color: $app-text;
  2240. font-weight: 500;
  2241. white-space: nowrap;
  2242. }
  2243. .ki-valued {
  2244. font-size: 24rpx;
  2245. color: $app-text;
  2246. font-weight: 500;
  2247. }
  2248. .ki-swatch {
  2249. width: 28rpx;
  2250. height: 28rpx;
  2251. border-radius: 50%;
  2252. margin-right: 6rpx;
  2253. border: 2rpx solid rgba(0,0,0,0.06);
  2254. flex-shrink: 0;
  2255. }
  2256. .ki-num {
  2257. font-size: 30rpx;
  2258. font-weight: 700;
  2259. color: $dim-mind;
  2260. }
  2261. .ki-fortune {
  2262. font-size: 24rpx;
  2263. font-weight: 700;
  2264. padding: 2rpx 12rpx;
  2265. border-radius: 10rpx;
  2266. }
  2267. .ki-fortune-0 {
  2268. color: $fortune-0-ink;
  2269. background: $fortune-0-tint;
  2270. }
  2271. .ki-fortune-1 {
  2272. color: $fortune-1-ink;
  2273. background: $fortune-1-tint;
  2274. }
  2275. .ki-fortune-2 {
  2276. color: $fortune-2-ink;
  2277. background: $fortune-2-tint;
  2278. }
  2279. .ki-fortune-3 {
  2280. color: $fortune-3-ink;
  2281. background: $fortune-3-tint;
  2282. }
  2283. .ki-fortune-4 {
  2284. color: $fortune-4-ink;
  2285. background: $fortune-4-tint;
  2286. }
  2287. .ki-insight {
  2288. display: flex;
  2289. align-items: flex-start;
  2290. margin-top: 16rpx;
  2291. padding: 16rpx;
  2292. background: $dim-body-tint;
  2293. border-radius: 12rpx;
  2294. }
  2295. .ki-insight-icon {
  2296. font-size: 24rpx;
  2297. margin-right: 8rpx;
  2298. flex-shrink: 0;
  2299. }
  2300. .ki-insight-text {
  2301. font-size: 22rpx;
  2302. color: $mind-insight-ink;
  2303. line-height: 1.5;
  2304. }
  2305. .ki-footer {
  2306. margin-top: 16rpx;
  2307. text-align: center;
  2308. padding: 12rpx;
  2309. font-size: 24rpx;
  2310. color: $brand-primary;
  2311. font-weight: 500;
  2312. background: $facade-violet-tint-2;
  2313. border-radius: 12rpx;
  2314. }
  2315. .ki-footer:active {
  2316. opacity: 0.7;
  2317. }
  2318. /* DAN 报告卡片 */
  2319. .dan-card {
  2320. background: $app-surface;
  2321. border-radius: 16rpx;
  2322. padding: 24rpx;
  2323. margin-bottom: 16rpx;
  2324. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
  2325. }
  2326. .dan-card-header {
  2327. display: flex;
  2328. justify-content: space-between;
  2329. align-items: center;
  2330. margin-bottom: 12rpx;
  2331. }
  2332. .dan-report-date {
  2333. font-size: 26rpx;
  2334. color: $app-text-secondary;
  2335. }
  2336. .dan-status {
  2337. font-size: 22rpx;
  2338. padding: 4rpx 16rpx;
  2339. border-radius: 8rpx;
  2340. }
  2341. .dan-status.confirmed {
  2342. background: $status-success-soft;
  2343. color: $status-success-strong;
  2344. }
  2345. .dan-status.pending {
  2346. background: $status-warning-soft;
  2347. color: $status-warning-strong;
  2348. }
  2349. .dan-report-desc {
  2350. font-size: 28rpx;
  2351. color: $app-text;
  2352. font-weight: 500;
  2353. margin-bottom: 12rpx;
  2354. display: block;
  2355. }
  2356. .dan-card-footer {
  2357. text-align: right;
  2358. }
  2359. .dan-view-link {
  2360. font-size: 26rpx;
  2361. color: $dim-wisdom;
  2362. }
  2363. .section-header {
  2364. display: flex;
  2365. justify-content: space-between;
  2366. align-items: center;
  2367. margin-bottom: 20rpx;
  2368. }
  2369. .section-title {
  2370. font-size: 32rpx;
  2371. font-weight: 700;
  2372. color: $app-text;
  2373. }
  2374. .section-more {
  2375. position: relative;
  2376. font-size: 26rpx;
  2377. color: $dim-wisdom;
  2378. padding: 8rpx 16rpx;
  2379. }
  2380. /* 触摸目标扩展至 88rpx(微信最小可点区域):文字链 ~52rpx -> 88rpx,视觉零变化 */
  2381. .section-more::after {
  2382. content: '';
  2383. position: absolute;
  2384. left: -8rpx;
  2385. right: -16rpx;
  2386. top: -18rpx;
  2387. bottom: -18rpx;
  2388. }
  2389. /* 品牌定位语 */
  2390. .brand-strap {
  2391. margin: 16rpx 30rpx 0;
  2392. padding: 20rpx 28rpx;
  2393. background: linear-gradient(135deg, $dim-mind-tint, rgba(255,107,157,0.12));
  2394. border-radius: 16rpx;
  2395. border-left: 8rpx solid $dim-mind;
  2396. }
  2397. .brand-strap-text {
  2398. font-size: 26rpx;
  2399. color: $c-mind-ink;
  2400. font-weight: 500;
  2401. }
  2402. /* 每月引导语 */
  2403. .monthly-guidance {
  2404. margin: 16rpx 30rpx 0;
  2405. padding: 20rpx 28rpx;
  2406. background: linear-gradient(135deg, $dim-mind-tint, rgba(255,107,157,0.08));
  2407. border-radius: 16rpx;
  2408. border-left: 8rpx solid $dim-mind;
  2409. display: flex;
  2410. align-items: center;
  2411. gap: 12rpx;
  2412. }
  2413. .guidance-icon { font-size: 32rpx; }
  2414. .guidance-text {
  2415. font-size: 26rpx;
  2416. color: $c-mind-ink;
  2417. font-weight: 500;
  2418. }
  2419. /* 雷达图分组 */
  2420. .radar-group { margin-bottom: 30rpx; }
  2421. .radar-group-label {
  2422. font-size: 28rpx;
  2423. font-weight: bold;
  2424. color: $c-mind-ink;
  2425. margin-bottom: 16rpx;
  2426. padding-left: 10rpx;
  2427. }
  2428. /* 维度画像样式 */
  2429. .dimension-portrait-section {
  2430. margin: 24rpx 30rpx;
  2431. background: $app-surface;
  2432. border-radius: 24rpx;
  2433. padding: 32rpx 24rpx;
  2434. box-shadow: 0 4rpx 20rpx rgba(255, 107, 157, 0.12);
  2435. border: 2rpx solid rgba(255, 107, 157, 0.1);
  2436. }
  2437. .portrait-tier {
  2438. font-size: 26rpx;
  2439. color: $dim-mind;
  2440. font-weight: bold;
  2441. margin-left: 16rpx;
  2442. }
  2443. .portrait-summary {
  2444. margin-bottom: 24rpx;
  2445. }
  2446. .portrait-desc {
  2447. font-size: 26rpx;
  2448. color: $app-text-secondary;
  2449. line-height: 1.6;
  2450. display: block;
  2451. }
  2452. .portrait-dimensions {
  2453. margin-bottom: 20rpx;
  2454. }
  2455. .portrait-dim-item {
  2456. margin-bottom: 18rpx;
  2457. }
  2458. .portrait-dim-header {
  2459. display: flex;
  2460. align-items: center;
  2461. margin-bottom: 6rpx;
  2462. }
  2463. .portrait-dim-icon {
  2464. width: 36rpx;
  2465. height: 36rpx;
  2466. border-radius: 18rpx;
  2467. display: flex;
  2468. align-items: center;
  2469. justify-content: center;
  2470. margin-right: 10rpx;
  2471. font-size: 20rpx;
  2472. }
  2473. .portrait-dim-name {
  2474. font-size: 24rpx;
  2475. color: $app-text;
  2476. flex: 1;
  2477. }
  2478. .portrait-dim-score {
  2479. font-size: 26rpx;
  2480. font-weight: bold;
  2481. color: $dim-mind;
  2482. margin-right: 12rpx;
  2483. }
  2484. .portrait-dim-bar-track {
  2485. height: 10rpx;
  2486. background: $tint-cyan;
  2487. border-radius: 5rpx;
  2488. overflow: hidden;
  2489. margin-bottom: 4rpx;
  2490. flex: 1;
  2491. }
  2492. .portrait-dim-bar-fill {
  2493. height: 100%;
  2494. border-radius: 5rpx;
  2495. transition: width 0.3s ease;
  2496. }
  2497. .portrait-dim-desc {
  2498. font-size: 20rpx;
  2499. color: $app-text-muted;
  2500. display: block;
  2501. }
  2502. .portrait-tip {
  2503. padding: 16rpx 20rpx;
  2504. background: linear-gradient(135deg, $dim-mind-tint, $tint-rose);
  2505. border-radius: 12rpx;
  2506. border-left: 8rpx solid $dim-mind;
  2507. margin-top: 16rpx;
  2508. }
  2509. .portrait-tip-text {
  2510. font-size: 22rpx;
  2511. color: $brand-primary-deep;
  2512. line-height: 1.5;
  2513. }
  2514. </style>