index.vue 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257
  1. <template>
  2. <view class="container">
  3. <!-- Tabbar 切换过渡页 -->
  4. <tab-transition v-if="showTabTransition" dimCode="action" ref="tabTransition" @close="showTabTransition = false" />
  5. <!-- 品牌头部 -->
  6. <PageBanner theme="action"
  7. tagline="人际和谐 · 关系滋养"
  8. quote="和谐关系滋养幸福家庭" />
  9. <!-- 品牌定位语(登录前显示) -->
  10. <view class="brand-strap" v-if="!isLoggedIn">
  11. <text class="brand-strap-text">改善全家关系与沟通能力</text>
  12. </view>
  13. <!-- 家庭成员选择条 -->
  14. <FamilyMemberStrip
  15. v-if="currentView === 'self' && isLoggedIn"
  16. :members="familyMembersVisible"
  17. :selectedMemberId="selectedMemberId"
  18. @select="onFamilyMemberSelect"
  19. @removed="loadFamilyMembersVisible" />
  20. <!-- 游客引导语(每月更换) -->
  21. <view class="monthly-guidance" v-if="!isLoggedIn">
  22. <text class="guidance-icon">🌿</text>
  23. <text class="guidance-text">{{ monthlyGuidance }}</text>
  24. </view>
  25. <!-- 登录后:五维能量条 -->
  26. <FamilyEnergyBar
  27. v-if="isLoggedIn"
  28. dimensionCode="action"
  29. :sandboxData="sandboxData"
  30. :dualDimension="dualDimension" />
  31. <!-- 关系解读 -->
  32. <view class="section score-explain-card" :class="scoreExplainClass" v-if="isLoggedIn && currentView === 'self' && actionScore != null">
  33. <view class="explain-header">
  34. <view class="explain-icon-wrap" :class="'explain-icon-' + scoreExplainLevel">
  35. <text class="explain-icon">{{ actionScore >= 80 ? '\u2713' : actionScore >= 60 ? '\u26A0' : '\u2715' }}</text>
  36. </view>
  37. <view class="explain-info">
  38. <text class="explain-title">关系解读</text>
  39. <text class="explain-score">{{ actionScore }}<text class="explain-score-unit">分</text></text>
  40. </view>
  41. </view>
  42. <text class="explain-text">{{ actionScore >= 80 ? '家庭关系和谐,家人沟通顺畅。' : actionScore >= 60 ? '家庭关系一般,建议多花时间陪伴家人。' : '家庭关系需要改善,建议进行家庭沟通活动。' }}</text>
  43. </view>
  44. <!-- 行维度画像 - 总体说明 -->
  45. <view class="dimension-portrait-section" v-if="isLoggedIn && actionPortrait">
  46. <view class="section-header">
  47. <text class="section-title">🧭 行维度画像</text>
  48. <text class="portrait-tier">{{ actionPortrait.tierLabel }}</text>
  49. </view>
  50. <view class="portrait-summary">
  51. <text class="portrait-desc">{{ actionPortrait.summary }}</text>
  52. </view>
  53. <view class="portrait-dimensions">
  54. <view class="portrait-dim-item" v-for="dim in actionPortrait.dims" :key="dim.code">
  55. <view class="portrait-dim-header">
  56. <view class="portrait-dim-icon" :style="{ background: dim.color }">{{ dim.icon }}</view>
  57. <text class="portrait-dim-name">{{ dim.name }}</text>
  58. <text class="portrait-dim-score">{{ dim.score }}</text>
  59. </view>
  60. <view class="portrait-dim-bar-track">
  61. <view class="portrait-dim-bar-fill" :style="{ width: dim.score + '%', background: dim.color }"></view>
  62. </view>
  63. <text class="portrait-dim-desc">{{ dim.desc }}</text>
  64. </view>
  65. </view>
  66. <view class="portrait-tip" v-if="actionPortrait.tip">
  67. <text class="portrait-tip-text">💡 {{ actionPortrait.tip }}</text>
  68. </view>
  69. </view>
  70. <!-- 关系六维 -->
  71. <dimension-sub-dims
  72. v-if="isLoggedIn && actionSubDims.length > 0"
  73. title="🌲 关系六维"
  74. subtitle="家庭关系的六个方面"
  75. accent-color="#10B981"
  76. :dims="actionSubDims" />
  77. <!-- 登录后:家庭关系(行维度核心) -->
  78. <view class="section" v-if="isLoggedIn">
  79. <view class="section-header">
  80. <view class="section-title-wrap">
  81. <text class="help-icon" @tap="showHelp('家庭关系', '了解每位家人的关系质量,改善沟通方式。点击家人节点,查看关系详情。')">?</text>
  82. <view>
  83. <text class="section-title">家庭关系</text>
  84. <text class="section-sub">了解每位家人的关系质量,改善沟通方式 · 点击家人节点查看详情</text>
  85. </view>
  86. </view>
  87. </view>
  88. <FamilyRelationGraph
  89. dimensionCode="action"
  90. :selfId="selfId"
  91. :members="graphMembers"
  92. :energyMap="energyMapForGraph"
  93. :intimacyMap="intimacyMapForGraph"
  94. :interactive="true" />
  95. </view>
  96. <!-- 登录后:章鱼图(关键人拓展) -->
  97. <view class="section" v-if="isLoggedIn">
  98. <view class="section-header">
  99. <view class="section-title-wrap">
  100. <text class="help-icon" @tap="showHelp('章鱼图', '从成效出发,挖掘关键人,画出拓展路径。记录感恩时刻,建立有温度的人脉网络。')">?</text>
  101. <view>
  102. <text class="section-title">🐙 章鱼图</text>
  103. <text class="section-sub">从成效出发,挖掘关键人,画出拓展路径</text>
  104. </view>
  105. </view>
  106. <text class="section-link section-link-octopus" @tap="goOctopusEffectsList">+ 感恩记录</text>
  107. </view>
  108. <OctopusDiagram
  109. ref="octopusDiagram"
  110. :selfId="selfId"
  111. :effects="octopusEffects"
  112. :expansions="octopusExpansions"
  113. :interactive="true"
  114. @effect-click="onEffectClick"
  115. @expansion-click="onExpansionClick"
  116. @add-effect="goOctopusAddEffect"
  117. @reanalyze="onReanalyze" />
  118. </view>
  119. <!-- 登录后:珍珠图(我有什么) -->
  120. <view class="section" v-if="isLoggedIn">
  121. <view class="section-header">
  122. <view class="section-title-wrap">
  123. <text class="help-icon" @tap="showHelp('珍珠图', '把可调用的资源串成珍珠项链——盘点我有什么。登记人脉、资源、信息缺口,画出一条清晰的成长珍珠链。')">?</text>
  124. <view>
  125. <text class="section-title">🫧 珍珠图</text>
  126. <text class="section-sub">把可调用的资源串成珍珠项链——盘点我有什么</text>
  127. </view>
  128. </view>
  129. <text class="section-link section-link-pearl" @tap="goPearlAddResource">+ 登记</text>
  130. </view>
  131. <PearlDiagram
  132. ref="pearlDiagram"
  133. :selfId="selfId"
  134. :resources="pearlData"
  135. :overdue-map="overdueMap"
  136. :interactive="true"
  137. @add="goPearlAddResource"
  138. @delete="onDeletePearlResource" />
  139. </view>
  140. <!-- 登录后:能力图(谁会什么) -->
  141. <view class="section" v-if="isLoggedIn">
  142. <view class="section-header">
  143. <view class="section-title-wrap">
  144. <text class="help-icon" @tap="showHelp('能力图', '身边藏着什么技能——谁有能力帮到你。记录技能类帮助后,这里会画出大家的能力图谱。')">?</text>
  145. <view>
  146. <text class="section-title">💡 能力图</text>
  147. <text class="section-sub">身边藏着什么技能——谁有能力帮到你</text>
  148. </view>
  149. </view>
  150. <text class="section-link section-link-ability" @tap="goInteractionLog">+ 记录帮助</text>
  151. </view>
  152. <AbilityDiagram
  153. ref="abilityDiagram"
  154. :selfId="selfId"
  155. :abilities="abilityData"
  156. :interactive="true" />
  157. </view>
  158. <!-- 行6维关系域雷达图(登录后) -->
  159. <view class="section" v-if="isLoggedIn && currentMemberEnergies">
  160. <view class="section-header">
  161. <text class="section-title">🫂 关系质量</text>
  162. </view>
  163. <RadarChart
  164. :dimensions="actionDimensions"
  165. :scores="actionScores"
  166. :avg-scores="actionFamilyScores"
  167. :population-scores="actionPopulationScores"
  168. fillColor="#10B981"
  169. gridColor="#D1FAE5"
  170. labelColor="#065F46"
  171. :width="580"
  172. :height="580" />
  173. </view>
  174. <!-- 功能入口 -->
  175. <view class="func-section" v-if="sectionVisible('func_entries')">
  176. <view class="func-grid">
  177. <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
  178. <view class="func-icon-wrap">
  179. <text class="func-icon">{{ item.icon }}</text>
  180. </view>
  181. <text class="func-label">{{ item.label }}</text>
  182. </view>
  183. </view>
  184. </view>
  185. <!-- 登录后:今日任务 -->
  186. <DimensionTasks
  187. v-if="isLoggedIn"
  188. :memberId="activeChildId"
  189. @taskClick="onTaskClick"
  190. @moreTasks="goTasks" />
  191. <!-- 登录后:活动 -->
  192. <DimensionActivities
  193. v-if="isLoggedIn"
  194. :isLoggedIn="true"
  195. @activityClick="goActivityDetail"
  196. @moreActivities="goMoreActivities" />
  197. <!-- 登录后:商品 -->
  198. <DimensionProducts
  199. v-if="isLoggedIn"
  200. :isLoggedIn="true"
  201. dimension-code="action"
  202. @productClick="goProductDetail"
  203. @moreProducts="goMoreProducts" />
  204. <!-- 行维度介绍卡片(登录后显示) -->
  205. <DimensionIntroCard dimCode="action" v-if="isLoggedIn" learn-more-url="/pages/action-detail/dimension-intro" />
  206. <!-- 底部占位 -->
  207. <view class="bottom-spacer"></view>
  208. <FloatingAvatar />
  209. <!-- 帮助说明弹窗 -->
  210. <view class="help-mask" v-if="helpPopup.visible" @tap="closeHelp">
  211. <view class="help-dialog" @tap.stop>
  212. <view class="help-dialog-header">
  213. <text class="help-dialog-title">{{ helpPopup.title }}</text>
  214. <text class="help-dialog-close" @tap="closeHelp">✕</text>
  215. </view>
  216. <text class="help-dialog-content">{{ helpPopup.content }}</text>
  217. </view>
  218. </view>
  219. </view>
  220. </template>
  221. <script>
  222. import TabTransition from '../../components/tab-transition.vue'
  223. import PageBanner from '../../components/PageBanner.vue'
  224. import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
  225. import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
  226. import PearlDiagram from '@/pages/action-detail/components/PearlDiagram.vue'
  227. import AbilityDiagram from '@/pages/action-detail/components/AbilityDiagram.vue'
  228. import OctopusDiagram from '@/pages/action-detail/components/OctopusDiagram.vue'
  229. import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
  230. import DimensionTasks from '../../components/DimensionTasks.vue'
  231. import DimensionActivities from '../../components/DimensionActivities.vue'
  232. import DimensionProducts from '../../components/DimensionProducts.vue'
  233. import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
  234. import FloatingAvatar from '../../components/AIFloatingAvatar.vue'
  235. import RadarChart from '../../components/RadarChart.vue'
  236. import DimensionSubDims from '../../components/DimensionSubDims.vue'
  237. import { getVisibleSections, getEnergyOverview, getChildren, getFamilyEnergySandbox, getEnergySandbox, getFamilyMemberList, getOctopusTentacles, getPearlResources, deletePearlResource, getAbilityMap, getRadarData, getPearlReminders, getOctopusEffects, getOctopusAnalysisSummary, getOctopusExpansionSuggest, getOctopusEffectKeyPersons } from '../../utils/api.js'
  238. export default {
  239. components: { TabTransition, PageBanner, FamilyEnergyBar, FamilyRelationGraph, PearlDiagram, AbilityDiagram, OctopusDiagram, FamilyMemberStrip, DimensionTasks, DimensionActivities, DimensionProducts, DimensionIntroCard, FloatingAvatar, RadarChart, DimensionSubDims },
  240. data() {
  241. return {
  242. isLoggedIn: false,
  243. role: null,
  244. showTabTransition: true,
  245. activeChildId: null,
  246. selectedMemberId: null,
  247. activeChildName: '',
  248. userName: '',
  249. children: [],
  250. familyMembersVisible: [],
  251. childrenChecked: false,
  252. membersChecked: false,
  253. actionSubDims: [],
  254. sandboxData: null,
  255. actionPopulationScoreMap: {},
  256. currentView: 'self', // 'self' | 'family'
  257. dualDimension: null,
  258. selfId: null,
  259. visibleSections: [],
  260. pearlData: null,
  261. abilityData: null,
  262. octopusEffects: null,
  263. octopusExpansions: [],
  264. octopusKeyPersons: {},
  265. overdueMap: null,
  266. // 帮助弹窗
  267. helpPopup: { visible: false, title: '', content: '' },
  268. funcList: [
  269. { icon: '\u{1F4AC}', label: '记录互动', needLogin: true, page: '/pages/action-detail/interaction-log' },
  270. { icon: '\u{1F4DD}', label: '关系问卷', needLogin: true, page: '/pages/action-detail/relationship-questionnaire' },
  271. { icon: '\u{1F465}', label: '家庭成员', needLogin: true, page: '/pages/profile-extra/family-members' },
  272. { icon: '\u{1F4CB}', label: '任务中心', needLogin: true, page: '/pages/tasks/tasks' }
  273. ]
  274. }
  275. },
  276. computed: {
  277. /** 关系图谱:成员节点(来自沙盘数据) */
  278. graphMembers: function() {
  279. if (!this.sandboxData || !this.sandboxData.members) return []
  280. return this.sandboxData.members.map(function(m) {
  281. return {
  282. id: m.memberId || m.id,
  283. nickname: m.name || m.nickname || '成员',
  284. memberType: m.memberType || 'child'
  285. }
  286. })
  287. },
  288. /** 关系图谱:节点能量映射 */
  289. energyMapForGraph: function() {
  290. var map = {}
  291. if (this.sandboxData && this.sandboxData.members) {
  292. for (var i = 0; i < this.sandboxData.members.length; i++) {
  293. var m = this.sandboxData.members[i]
  294. map[m.memberId || m.id] = {
  295. bodyScore: m.bodyScore || 0,
  296. mindScore: m.mindScore || 0,
  297. actionScore: m.actionScore || 0
  298. }
  299. }
  300. }
  301. return map
  302. },
  303. intimacyMapForGraph: function() {
  304. return {}
  305. },
  306. /** 每月引导语 */
  307. monthlyGuidance: function() {
  308. var month = new Date().getMonth()
  309. var messages = [
  310. '一月:新年伊始,增进家庭沟通',
  311. '二月:春节团聚,珍惜亲子时光',
  312. '三月:春暖花开,多陪伴成员',
  313. '四月:清明踏青,共享家庭活动',
  314. '五月:劳动教育,培养家庭责任感',
  315. '六月:儿童节,倾听成员心声',
  316. '七月:暑期陪伴,增进亲子关系',
  317. '八月:开学准备,共同规划',
  318. '九月:新学期,建立家庭规则',
  319. '十月:国庆假期,家庭协作时光',
  320. '十一月:感恩季节,表达家庭关爱',
  321. '十二月:年终总结,回顾家庭成长'
  322. ]
  323. return messages[month] || messages[0]
  324. },
  325. /** 当前选中成员的能量数据(从 sandboxData.members 中查找) */
  326. currentMemberEnergies: function() {
  327. if (!this.sandboxData || !this.sandboxData.members) return null
  328. var members = this.sandboxData.members
  329. var targetId = this.selectedMemberId || this.activeChildId
  330. for (var i = 0; i < members.length; i++) {
  331. var m = members[i]
  332. if (m && (m.memberId === targetId)) return m
  333. }
  334. return members[0] || null
  335. },
  336. /** 行6维关系域维度定义(根据角色切换家长版/孩子版) */
  337. actionDimensions: function() {
  338. var role = this.role || uni.getStorageSync('currentRole') || ''
  339. var isParent = (role === 'parent')
  340. if (isParent) {
  341. return [
  342. { key: 'actionParentChild', label: '亲子关系' },
  343. { key: 'actionMarital', label: '夫妻关系' },
  344. { key: 'actionParental', label: '父母关系' },
  345. { key: 'actionLeadership', label: '领导关系' },
  346. { key: 'actionSociality', label: '社会性' },
  347. { key: 'actionInterpersonal', label: '人际韧性' }
  348. ]
  349. } else {
  350. return [
  351. { key: 'actionChildParentChild', label: '亲子关系' },
  352. { key: 'actionSibling', label: '同胞关系' },
  353. { key: 'actionRelative', label: '亲属关系' },
  354. { key: 'actionTeacherStudent', label: '师生关系' },
  355. { key: 'actionChildSociality', label: '社会性' },
  356. { key: 'actionChildInterpersonal', label: '人际韧性' }
  357. ]
  358. }
  359. },
  360. /** 当前选中成员的行维度分数 */
  361. actionScore: function() {
  362. var member = this.currentMemberEnergies
  363. return member ? (member.actionScore || 0) : null
  364. },
  365. /** 行6维分值(从当前成员中提取,与 dimensions 顺序对应) */
  366. actionScores: function() {
  367. var member = this.currentMemberEnergies
  368. if (!member) return []
  369. var dims = this.actionDimensions
  370. var scores = []
  371. for (var i = 0; i < dims.length; i++) {
  372. scores.push(member[dims[i].key] || 0)
  373. }
  374. return scores
  375. },
  376. /** 行6维家庭平均分(从 sandboxData.members 各成员同维度均值) */
  377. actionFamilyScores: function() {
  378. if (!this.sandboxData || !this.sandboxData.members) return []
  379. var members = this.sandboxData.members
  380. var dims = this.actionDimensions
  381. var scores = []
  382. for (var i = 0; i < dims.length; i++) {
  383. var key = dims[i].key
  384. var sum = 0, count = 0
  385. for (var j = 0; j < members.length; j++) {
  386. var val = members[j][key]
  387. if (val !== undefined && val !== null && val > 0) { sum += val; count++ }
  388. }
  389. scores.push(count > 0 ? Math.round(sum / count) : 0)
  390. }
  391. return scores
  392. },
  393. /** 行6维人群位置分数(按 actionDimensions 顺序从 map 取值) */
  394. actionPopulationScores: function() {
  395. var dims = this.actionDimensions
  396. var scores = []
  397. for (var i = 0; i < dims.length; i++) {
  398. scores.push(this.actionPopulationScoreMap[dims[i].key] || 0)
  399. }
  400. return scores
  401. },
  402. /** 关系解读状态:good(≥80) | warn(≥60) | bad(<60) */
  403. scoreExplainLevel: function() {
  404. var s = this.actionScore
  405. if (s == null) return 'bad'
  406. if (s >= 80) return 'good'
  407. if (s >= 60) return 'warn'
  408. return 'bad'
  409. },
  410. /** 关系解读卡片状态类(用于 :class 绑定) */
  411. scoreExplainClass: function() {
  412. return 'score-explain-' + this.scoreExplainLevel
  413. },
  414. actionPortrait: function() {
  415. var self = this
  416. if (!this.isLoggedIn) return null
  417. var member = this.currentMemberEnergies
  418. if (!member) return null
  419. var role = this.role || uni.getStorageSync('currentRole') || ''
  420. var isParent = (role === 'parent')
  421. var parentDims = [
  422. { key: 'actionParentChild', label: '亲子关系', color: '#10B981', desc: '与子女的沟通与陪伴质量' },
  423. { key: 'actionMarital', label: '夫妻关系', color: '#FF6B9D', desc: '夫妻间的理解与支持' },
  424. { key: 'actionParental', label: '父母关系', color: '#F59E0B', desc: '与长辈的相处模式' },
  425. { key: 'actionLeadership', label: '领导关系', color: '#6366F1', desc: '职场上下级沟通能力' },
  426. { key: 'actionSociality', label: '社会性', color: '#FF8C42', desc: '社会交往与参与度' },
  427. { key: 'actionInterpersonal', label: '人际韧性', color: '#06B6D4', desc: '应对人际关系挫折的能力' }
  428. ]
  429. var childDims = [
  430. { key: 'actionChildParentChild', label: '亲子关系', color: '#10B981', desc: '与父母的互动质量' },
  431. { key: 'actionSibling', label: '同胞关系', color: '#FF6B9D', desc: '兄弟姐妹间的关系' },
  432. { key: 'actionRelative', label: '亲属关系', color: '#F59E0B', desc: '与亲戚的交往频率' },
  433. { key: 'actionTeacherStudent', label: '师生关系', color: '#6366F1', desc: '与老师的沟通情况' },
  434. { key: 'actionChildSociality', label: '社会性', color: '#FF8C42', desc: '学校与社会参与度' },
  435. { key: 'actionChildInterpersonal', label: '人际韧性', color: '#06B6D4', desc: '应对社交困难的能力' }
  436. ]
  437. var dims = isParent ? parentDims : childDims
  438. var result = { overall: 0, tierLabel: '', summary: '', dims: [], tip: '' }
  439. var total = 0
  440. var count = 0
  441. var dimList = []
  442. for (var i = 0; i < dims.length; i++) {
  443. var key = dims[i].key
  444. var score = member[key] || 0
  445. if (isNaN(score)) score = 0
  446. score = Math.max(0, Math.min(100, Number(score)))
  447. total += score
  448. count++
  449. dimList.push({
  450. code: key,
  451. name: dims[i].label,
  452. score: Math.round(score),
  453. color: dims[i].color,
  454. desc: dims[i].desc
  455. })
  456. }
  457. if (count === 0) return null
  458. result.overall = Math.round(total / count)
  459. result.dims = dimList
  460. var o = result.overall
  461. if (o >= 80) {
  462. result.tierLabel = '关系大师'
  463. result.summary = '您的家庭关系和谐稳定,与家人建立了深厚的信任纽带。'
  464. result.tip = '继续保持沟通习惯,也可分享经验帮助其他家庭'
  465. } else if (o >= 60) {
  466. result.tierLabel = '关系进取者'
  467. result.summary = '您的家庭关系处于良好水平,具备基本的沟通能力。'
  468. result.tip = '多倾听家人心声,主动表达关爱,持续提升关系质量'
  469. } else if (o >= 40) {
  470. result.tierLabel = '关系探索者'
  471. result.summary = '您的家庭关系正在建立基础,需要更多沟通与理解。'
  472. result.tip = '建议定期进行家庭活动,增进彼此了解与信任'
  473. } else {
  474. result.tierLabel = '关系关怀者'
  475. result.summary = '您可能面临家庭关系挑战,需要更多关注与努力。'
  476. result.tip = '建议寻求专业家庭辅导,或从简单的家庭对话开始改善'
  477. }
  478. return result
  479. }
  480. },
  481. onShow() {
  482. this.showTabTransition = true
  483. var token = uni.getStorageSync('token')
  484. this.isLoggedIn = !!token
  485. if (this.isLoggedIn) {
  486. this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  487. this.userName = uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '家长'
  488. this.selfId = uni.getStorageSync('userId') || null
  489. }
  490. this.loadSectionConfig()
  491. if (this.isLoggedIn) {
  492. this.loadChildren()
  493. this.loadFamilyMembersVisible()
  494. this.loadPearlResources()
  495. this.loadAbilityMap()
  496. this.loadOctopusData()
  497. this.loadActionRadarPopulationData()
  498. }
  499. this._watchPageReady()
  500. },
  501. methods: {
  502. loadSectionConfig: function() {
  503. var baseSections = ['func_entries']
  504. if (!uni.getStorageSync('token')) {
  505. this.visibleSections = baseSections
  506. return
  507. }
  508. var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  509. var self = this
  510. getVisibleSections({ pageKey: 'action', role: role }).then(function(res) {
  511. if (res.code === 200 && res.data) {
  512. var keys = res.data.map(function(s) { return s.sectionKey })
  513. var merged = baseSections.slice()
  514. keys.forEach(function(k) { if (merged.indexOf(k) === -1) merged.push(k) })
  515. self.visibleSections = merged
  516. }
  517. }).catch(function(e) {
  518. self.visibleSections = ['func_entries']
  519. })
  520. },
  521. loadChildren: function() {
  522. var self = this
  523. getChildren().then(function(res) {
  524. if (res.code === 200 && res.data) {
  525. self.children = res.data
  526. var savedChildId = uni.getStorageSync('currentChildId')
  527. var found = false
  528. for (var i = 0; i < self.children.length; i++) {
  529. if (self.children[i].memberId === savedChildId) {
  530. self.activeChildId = savedChildId
  531. self.activeChildName = self.children[i].name || self.children[i].nickname || '成员'
  532. found = true
  533. break
  534. }
  535. }
  536. if (!found && self.children.length > 0) {
  537. self.activeChildId = self.children[0].memberId
  538. self.activeChildName = self.children[0].name || self.children[0].nickname || '成员'
  539. uni.setStorageSync('currentChildId', self.activeChildId)
  540. }
  541. if (self.activeChildId) {
  542. self.loadDashboardData()
  543. }
  544. }
  545. self.childrenChecked = true
  546. }).catch(function(e) {
  547. console.log('获取成员列表失败', e)
  548. self.childrenChecked = true
  549. })
  550. },
  551. loadDashboardData: function() {
  552. if (!this.activeChildId) return
  553. this.loadEnergyData()
  554. this.loadSandboxData()
  555. },
  556. loadSandboxData: function() {
  557. var self = this
  558. getFamilyEnergySandbox().then(function(res) {
  559. if (res && res.data) {
  560. self.sandboxData = res.data
  561. }
  562. }).catch(function(e) {
  563. console.log('获取沙盘数据失败', e)
  564. })
  565. },
  566. loadActionRadarPopulationData: function() {
  567. var self = this
  568. var memberId = this.selectedMemberId || this.activeChildId
  569. if (!memberId) return
  570. getRadarData({
  571. memberId: memberId,
  572. dimensions: ['actionParentChild','actionMarital','actionParental','actionLeadership','actionSociality','actionInterpersonal',
  573. 'actionChildParentChild','actionSibling','actionRelative','actionTeacherStudent','actionChildSociality','actionChildInterpersonal']
  574. }).then(function(res) {
  575. if (res && res.data && res.data.populationScores) {
  576. var dims = res.data.dimensions || []
  577. var scores = res.data.populationScores
  578. var map = {}
  579. for (var i = 0; i < dims.length && i < scores.length; i++) {
  580. map[dims[i]] = scores[i]
  581. }
  582. self.actionPopulationScoreMap = map
  583. }
  584. }).catch(function(e) { console.warn('[action] radar-data failed', e) })
  585. },
  586. switchView: function(view) {
  587. this.currentView = view
  588. if (view === 'family') {
  589. this.selectedMemberId = null
  590. var self = this
  591. getEnergySandbox('family').then(function(res) {
  592. if (res && res.data && !res.data.locked) {
  593. self.sandboxData = res.data.familyData || res.data
  594. }
  595. }).catch(function(e) { console.log('获取家庭数据失败', e) })
  596. } else {
  597. this.loadSandboxData()
  598. }
  599. },
  600. loadEnergyData: function() {
  601. var self = this
  602. getEnergyOverview(this.activeChildId).then(function(res) {
  603. if (res && res.data) {
  604. var dims = res.data.dimensions
  605. if (dims && dims.length > 0) {
  606. for (var i = 0; i < dims.length; i++) {
  607. if (dims[i].code === 'action') {
  608. self.dualDimension = dims[i]
  609. break
  610. }
  611. }
  612. }
  613. }
  614. }).catch(function(e) {
  615. console.log('获取能量概览失败', e)
  616. })
  617. },
  618. onFamilyMemberSelect: function(member) {
  619. this.selectedMemberId = member.id
  620. },
  621. loadFamilyMembersVisible: function() {
  622. var self = this
  623. getFamilyMemberList({ visibleOnly: true }).then(function(res) {
  624. self.familyMembersVisible = res.data || []
  625. self.membersChecked = true
  626. }).catch(function(e) {
  627. console.log('获取可见家庭成员失败', e)
  628. self.familyMembersVisible = []
  629. self.membersChecked = true
  630. })
  631. },
  632. sectionVisible: function(key) {
  633. return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
  634. },
  635. onFuncClick: function(item) {
  636. if (item.needLogin && !this.isLoggedIn) {
  637. uni.showModal({
  638. title: '提示',
  639. content: '请先登录后使用此功能',
  640. success: function(res) {
  641. if (res.confirm) uni.navigateTo({ url: '/pages/login/login' })
  642. }
  643. })
  644. return
  645. }
  646. if (item.page) uni.navigateTo({ url: item.page })
  647. },
  648. /** 加载珍珠图数据 */
  649. loadPearlResources: function() {
  650. var self = this
  651. getPearlResources().then(function(res) {
  652. self.pearlData = (res.code === 200 && res.data) ? res.data : null
  653. }).catch(function(e) {
  654. console.log('获取珍珠图数据失败', e)
  655. self.pearlData = null
  656. })
  657. // 构建逾期映射
  658. getPearlReminders().then(function(res) {
  659. var map = {}
  660. if (res.code === 200 && res.data) {
  661. for (var i = 0; i < res.data.length; i++) {
  662. map['' + res.data[i].resourceItemId] = res.data[i].daysSinceLast
  663. }
  664. }
  665. self.overdueMap = map
  666. }).catch(function(e) {
  667. self.overdueMap = null
  668. })
  669. },
  670. /** 登记珍珠图资源入口 */
  671. goPearlAddResource: function() {
  672. uni.navigateTo({ url: '/pages/action-detail/pearl-add-resource' })
  673. },
  674. /** 能力图记录帮助入口(跳转互动记录,记录技能类帮助) */
  675. goInteractionLog: function() {
  676. uni.navigateTo({ url: '/pages/action-detail/interaction-log' })
  677. },
  678. /** 删除珍珠图资源 */
  679. onDeletePearlResource: function(item) {
  680. if (!item || !item.id) return
  681. var self = this
  682. uni.showModal({
  683. title: '提示',
  684. content: '确定删除该资源吗?',
  685. success: function(res) {
  686. if (!res.confirm) return
  687. deletePearlResource({ itemId: item.id }).then(function(r) {
  688. if (r.code === 200) {
  689. uni.showToast({ title: '已删除', icon: 'success' })
  690. self.loadPearlResources()
  691. } else {
  692. uni.showToast({ title: r.message || '删除失败', icon: 'none' })
  693. }
  694. }).catch(function(e) {
  695. console.log('删除珍珠图资源失败', e)
  696. uni.showToast({ title: '删除失败', icon: 'none' })
  697. })
  698. }
  699. })
  700. },
  701. /** 加载能力图数据 */
  702. loadAbilityMap: function() {
  703. var self = this
  704. getAbilityMap().then(function(res) {
  705. self.abilityData = (res.code === 200 && Array.isArray(res.data)) ? res.data : []
  706. }).catch(function(e) {
  707. console.log('获取能力图数据失败', e)
  708. self.abilityData = []
  709. })
  710. },
  711. /** 加载章鱼图数据 */
  712. loadOctopusData: function() {
  713. var self = this
  714. getOctopusEffects().then(function(res) {
  715. self.octopusEffects = (res.code === 200 && res.data) ? res.data : null
  716. }).catch(function(e) {
  717. console.log('获取章鱼图数据失败', e)
  718. self.octopusEffects = null
  719. })
  720. getOctopusAnalysisSummary().then(function(res) {
  721. if (res.code === 200 && res.data && res.data.directions) {
  722. self.octopusExpansions = res.data.directions
  723. }
  724. }).catch(function(e) {
  725. console.log('获取拓展方向失败', e)
  726. })
  727. },
  728. /** 跳转成效录入页 */
  729. goOctopusAddEffect: function() {
  730. uni.navigateTo({ url: '/pages/action-detail/octopus-add-effect' })
  731. },
  732. /** 跳转感恩记录维护列表 */
  733. goOctopusEffectsList: function() {
  734. uni.navigateTo({ url: '/pages/action-detail/octopus-effects-list' })
  735. },
  736. /** 点击成效节点 */
  737. onEffectClick: function(effect) {
  738. var self = this
  739. if (!self.octopusKeyPersons[effect.id]) {
  740. getOctopusEffectKeyPersons({ effectId: effect.id }).then(function(res) {
  741. if (res.code === 200) self.$set(self.octopusKeyPersons, effect.id, res.data || [])
  742. }).catch(function(e) { console.log('获取关键人失败', e) })
  743. }
  744. },
  745. /** 点击拓展方向节点 */
  746. onExpansionClick: function(expansion) {
  747. var prefill = encodeURIComponent(JSON.stringify(expansion.prefill || {}))
  748. uni.navigateTo({ url: '/pages/action-detail/octopus-add-key-person?prefill=' + prefill })
  749. },
  750. /** 重新分析 */
  751. onReanalyze: function() {
  752. this.loadOctopusData()
  753. },
  754. /** 显示帮助弹窗 */
  755. showHelp: function(title, content) {
  756. this.helpPopup = { visible: true, title: title, content: content }
  757. },
  758. closeHelp: function() {
  759. this.helpPopup.visible = false
  760. },
  761. onTaskClick: function(task) {
  762. uni.navigateTo({ url: '/pages/tasks/tasks' })
  763. },
  764. goActivityDetail: function(act) {
  765. if (act && act.id) uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id })
  766. },
  767. goMoreActivities: function() {
  768. uni.navigateTo({ url: '/pages/activity/index' })
  769. },
  770. goProductDetail: function(prod) {
  771. if (prod && prod.id) uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + prod.id })
  772. },
  773. goMoreProducts: function() {
  774. uni.navigateTo({ url: '/pages/shop/index/index' })
  775. },
  776. goTasks: function() {
  777. uni.navigateTo({ url: '/pages/tasks/tasks' })
  778. },
  779. _watchPageReady() {
  780. var self = this
  781. var unwatch = this.$watch(function() {
  782. if (self.isLoggedIn) {
  783. return !!(self.dualDimension || self.sandboxData)
  784. }
  785. return true
  786. }, function(val) {
  787. if (val) {
  788. if (unwatch) unwatch()
  789. if (self.$refs.tabTransition) self.$refs.tabTransition.markReady()
  790. }
  791. })
  792. }
  793. }
  794. }
  795. </script>
  796. <style scoped>
  797. .container {
  798. min-height: 100vh;
  799. background: linear-gradient(180deg, #F0FFF4 0%, #F5F7FA 40%);
  800. padding-bottom: 120rpx;
  801. }
  802. /* 品牌定位语 */
  803. .brand-strap {
  804. margin: 24rpx 30rpx 0;
  805. padding: 28rpx 32rpx;
  806. background: linear-gradient(135deg, #10B981 0%, #34D399 50%, #6EE7B7 100%);
  807. border-radius: 24rpx;
  808. box-shadow: 0 8rpx 24rpx rgba(16, 185, 129, 0.25);
  809. position: relative;
  810. overflow: hidden;
  811. }
  812. .brand-strap::before {
  813. content: '';
  814. position: absolute;
  815. top: -30rpx;
  816. right: -30rpx;
  817. width: 120rpx;
  818. height: 120rpx;
  819. background: rgba(255, 255, 255, 0.15);
  820. border-radius: 50%;
  821. }
  822. .brand-strap::after {
  823. content: '';
  824. position: absolute;
  825. bottom: -20rpx;
  826. left: 20rpx;
  827. width: 60rpx;
  828. height: 60rpx;
  829. background: rgba(255, 255, 255, 0.1);
  830. border-radius: 50%;
  831. }
  832. .brand-strap-text {
  833. font-size: 30rpx;
  834. color: #FFFFFF;
  835. font-weight: 600;
  836. text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
  837. position: relative;
  838. z-index: 1;
  839. }
  840. /* 每月引导语 */
  841. .monthly-guidance {
  842. margin: 20rpx 30rpx 0;
  843. padding: 24rpx 28rpx;
  844. background: linear-gradient(135deg, #FFFFFF 0%, #F0FFF4 100%);
  845. border-radius: 20rpx;
  846. border: 1rpx solid rgba(16, 185, 129, 0.15);
  847. box-shadow: 0 4rpx 16rpx rgba(16, 185, 129, 0.08);
  848. display: flex;
  849. align-items: center;
  850. gap: 12rpx;
  851. }
  852. .guidance-icon { font-size: 36rpx; }
  853. .guidance-text {
  854. font-size: 26rpx;
  855. color: #065F46;
  856. font-weight: 500;
  857. }
  858. /* ===== 通用区块 ===== */
  859. .section {
  860. margin: 24rpx 30rpx 0;
  861. position: relative;
  862. }
  863. .section::before {
  864. content: '';
  865. position: absolute;
  866. top: 0;
  867. left: 24rpx;
  868. right: 24rpx;
  869. height: 3rpx;
  870. border-radius: 3rpx 3rpx 0 0;
  871. background: linear-gradient(90deg, #10B981, #34D399);
  872. opacity: 0.7;
  873. }
  874. .section-header {
  875. display: flex;
  876. flex-direction: row;
  877. align-items: center;
  878. justify-content: space-between;
  879. margin-bottom: 12rpx;
  880. }
  881. .section-title-wrap {
  882. display: flex;
  883. flex-direction: row;
  884. align-items: center;
  885. gap: 8rpx;
  886. border-left: 6rpx solid #10B981;
  887. padding-left: 16rpx;
  888. }
  889. .help-icon {
  890. width: 36rpx;
  891. height: 36rpx;
  892. border-radius: 50%;
  893. border: 2rpx solid #D1D5DB;
  894. color: #9CA3AF;
  895. font-size: 22rpx;
  896. display: flex;
  897. align-items: center;
  898. justify-content: center;
  899. flex-shrink: 0;
  900. transition: all 0.2s;
  901. background: #FFFFFF;
  902. }
  903. .help-icon:active {
  904. border-color: #10B981;
  905. color: #10B981;
  906. background: #F0FFF4;
  907. transform: scale(1.1);
  908. }
  909. .section-title {
  910. font-size: 34rpx;
  911. font-weight: 700;
  912. color: #1E293B;
  913. letter-spacing: 0.5rpx;
  914. }
  915. .section-sub {
  916. font-size: 24rpx;
  917. color: #94A3B8;
  918. margin-top: 4rpx;
  919. line-height: 1.4;
  920. }
  921. .section-link {
  922. display: inline-flex;
  923. align-items: center;
  924. padding: 12rpx 28rpx;
  925. border-radius: 40rpx;
  926. font-size: 24rpx;
  927. font-weight: 500;
  928. white-space: nowrap;
  929. transition: all 0.2s;
  930. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
  931. }
  932. .section-link:active {
  933. opacity: 0.85;
  934. transform: scale(0.98);
  935. }
  936. .section-link-octopus {
  937. background: linear-gradient(135deg, #F97316, #FB923C);
  938. color: #fff;
  939. box-shadow: 0 4rpx 16rpx rgba(249,115,22,0.3);
  940. }
  941. .section-link-pearl {
  942. background: linear-gradient(135deg, #0EA5E9, #38BDF8);
  943. color: #fff;
  944. box-shadow: 0 4rpx 16rpx rgba(14,165,233,0.3);
  945. }
  946. .section-link-ability {
  947. background: linear-gradient(135deg, #10B981, #34D399);
  948. color: #fff;
  949. box-shadow: 0 4rpx 16rpx rgba(16,185,129,0.3);
  950. }
  951. /* 关系解读卡片 — 状态色 */
  952. .score-explain-card {
  953. background: linear-gradient(135deg, #FFFFFF 0%, #F0FFF4 100%);
  954. border-radius: 20rpx;
  955. padding: 28rpx 32rpx;
  956. border: 1rpx solid rgba(16, 185, 129, 0.12);
  957. box-shadow: 0 4rpx 16rpx rgba(16, 185, 129, 0.08);
  958. }
  959. .score-explain-good {
  960. background: linear-gradient(135deg, #FFFFFF 0%, #D1FAE5 100%);
  961. border-color: rgba(16, 185, 129, 0.25);
  962. box-shadow: 0 4rpx 20rpx rgba(16, 185, 129, 0.15);
  963. }
  964. .score-explain-warn {
  965. background: linear-gradient(135deg, #FFFFFF 0%, #FEF3C7 100%);
  966. border-color: rgba(245, 158, 11, 0.25);
  967. box-shadow: 0 4rpx 20rpx rgba(245, 158, 11, 0.12);
  968. }
  969. .score-explain-bad {
  970. background: linear-gradient(135deg, #FFFFFF 0%, #FEE2E2 100%);
  971. border-color: rgba(239, 68, 68, 0.25);
  972. box-shadow: 0 4rpx 20rpx rgba(239, 68, 68, 0.12);
  973. }
  974. .explain-header {
  975. display: flex;
  976. flex-direction: row;
  977. align-items: center;
  978. gap: 16rpx;
  979. margin-bottom: 12rpx;
  980. }
  981. .explain-icon-wrap {
  982. width: 56rpx;
  983. height: 56rpx;
  984. border-radius: 50%;
  985. display: flex;
  986. align-items: center;
  987. justify-content: center;
  988. flex-shrink: 0;
  989. transition: transform 0.2s;
  990. }
  991. .explain-icon-wrap:active { transform: scale(0.95); }
  992. .explain-icon-good { background: linear-gradient(135deg, #10B981, #34D399); box-shadow: 0 4rpx 12rpx rgba(16,185,129,0.3); }
  993. .explain-icon-warn { background: linear-gradient(135deg, #F59E0B, #FBBF24); box-shadow: 0 4rpx 12rpx rgba(245,158,11,0.3); }
  994. .explain-icon-bad { background: linear-gradient(135deg, #EF4444, #F87171); box-shadow: 0 4rpx 12rpx rgba(239,68,68,0.3); }
  995. .explain-icon {
  996. font-size: 28rpx;
  997. color: #FFFFFF;
  998. font-weight: 700;
  999. }
  1000. .explain-info {
  1001. display: flex;
  1002. flex-direction: column;
  1003. }
  1004. .explain-title {
  1005. font-size: 28rpx;
  1006. font-weight: 600;
  1007. color: #1E293B;
  1008. line-height: 1.3;
  1009. }
  1010. .explain-score {
  1011. font-size: 48rpx;
  1012. font-weight: 800;
  1013. line-height: 1;
  1014. letter-spacing: -2rpx;
  1015. }
  1016. .score-explain-good .explain-score { color: #10B981; }
  1017. .score-explain-warn .explain-score { color: #D97706; }
  1018. .score-explain-bad .explain-score { color: #DC2626; }
  1019. .explain-score-unit {
  1020. font-size: 24rpx;
  1021. font-weight: 500;
  1022. color: #94A3B8;
  1023. margin-left: 2rpx;
  1024. letter-spacing: 0;
  1025. }
  1026. .explain-text {
  1027. font-size: 26rpx;
  1028. color: #64748B;
  1029. line-height: 1.7;
  1030. }
  1031. /* 帮助说明弹窗 */
  1032. .help-mask {
  1033. position: fixed;
  1034. top: 0;
  1035. left: 0;
  1036. right: 0;
  1037. bottom: 0;
  1038. background: rgba(0, 0, 0, 0.5);
  1039. display: flex;
  1040. align-items: center;
  1041. justify-content: center;
  1042. z-index: 999;
  1043. }
  1044. .help-dialog {
  1045. background: #FFFFFF;
  1046. border-radius: 24rpx;
  1047. width: 85%;
  1048. max-width: 520rpx;
  1049. max-height: 75vh;
  1050. overflow: hidden;
  1051. box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15);
  1052. }
  1053. .help-dialog-header {
  1054. display: flex;
  1055. justify-content: space-between;
  1056. align-items: center;
  1057. padding: 28rpx 32rpx 20rpx;
  1058. border-bottom: 1rpx solid #F1F5F9;
  1059. background: linear-gradient(135deg, #F0FFF4, #FFFFFF);
  1060. }
  1061. .help-dialog-title {
  1062. font-size: 32rpx;
  1063. font-weight: 700;
  1064. color: #1E293B;
  1065. }
  1066. .help-dialog-close {
  1067. font-size: 36rpx;
  1068. color: #94A3B8;
  1069. cursor: pointer;
  1070. width: 48rpx;
  1071. height: 48rpx;
  1072. display: flex;
  1073. align-items: center;
  1074. justify-content: center;
  1075. border-radius: 50%;
  1076. transition: background 0.2s;
  1077. }
  1078. .help-dialog-close:active {
  1079. background: #F1F5F9;
  1080. }
  1081. .help-dialog-content {
  1082. padding: 28rpx 32rpx;
  1083. font-size: 28rpx;
  1084. color: #64748B;
  1085. line-height: 1.8;
  1086. }
  1087. /* 功能入口网格 */
  1088. .func-section {
  1089. margin: 24rpx 30rpx 0;
  1090. }
  1091. .func-grid {
  1092. display: flex;
  1093. flex-direction: row;
  1094. justify-content: space-around;
  1095. background: linear-gradient(135deg, #FFFFFF 0%, #F0FFF4 100%);
  1096. border-radius: 24rpx;
  1097. padding: 32rpx 16rpx;
  1098. box-shadow: 0 8rpx 24rpx rgba(16, 185, 129, 0.1);
  1099. border: 1rpx solid rgba(16, 185, 129, 0.08);
  1100. }
  1101. .func-item {
  1102. display: flex;
  1103. flex-direction: column;
  1104. align-items: center;
  1105. flex: 1;
  1106. padding: 8rpx;
  1107. }
  1108. .func-icon-wrap {
  1109. width: 96rpx;
  1110. height: 96rpx;
  1111. border-radius: 28rpx;
  1112. background: linear-gradient(135deg, #D1FAE5, #A7F3D0);
  1113. display: flex;
  1114. align-items: center;
  1115. justify-content: center;
  1116. margin-bottom: 12rpx;
  1117. box-shadow: 0 4rpx 12rpx rgba(16, 185, 129, 0.15);
  1118. transition: all 0.2s;
  1119. }
  1120. .func-item:active .func-icon-wrap {
  1121. transform: scale(0.95);
  1122. box-shadow: 0 2rpx 8rpx rgba(16, 185, 129, 0.2);
  1123. }
  1124. .func-icon {
  1125. font-size: 44rpx;
  1126. }
  1127. .func-label {
  1128. font-size: 24rpx;
  1129. color: #475569;
  1130. font-weight: 500;
  1131. }
  1132. .bottom-spacer {
  1133. height: 40rpx;
  1134. }
  1135. /* 维度画像样式 */
  1136. .dimension-portrait-section {
  1137. margin: 24rpx 30rpx;
  1138. background: #fff;
  1139. border-radius: 24rpx;
  1140. padding: 32rpx 24rpx;
  1141. box-shadow: 0 4rpx 20rpx rgba(16, 185, 129, 0.12);
  1142. border: 2rpx solid rgba(16, 185, 129, 0.1);
  1143. }
  1144. .portrait-tier {
  1145. font-size: 26rpx;
  1146. color: #10B981;
  1147. font-weight: bold;
  1148. margin-left: 16rpx;
  1149. }
  1150. .portrait-summary {
  1151. margin-bottom: 24rpx;
  1152. }
  1153. .portrait-desc {
  1154. font-size: 26rpx;
  1155. color: #666;
  1156. line-height: 1.6;
  1157. display: block;
  1158. }
  1159. .portrait-dimensions {
  1160. margin-bottom: 20rpx;
  1161. }
  1162. .portrait-dim-item {
  1163. margin-bottom: 18rpx;
  1164. }
  1165. .portrait-dim-header {
  1166. display: flex;
  1167. align-items: center;
  1168. margin-bottom: 6rpx;
  1169. }
  1170. .portrait-dim-icon {
  1171. width: 36rpx;
  1172. height: 36rpx;
  1173. border-radius: 18rpx;
  1174. display: flex;
  1175. align-items: center;
  1176. justify-content: center;
  1177. margin-right: 10rpx;
  1178. font-size: 20rpx;
  1179. background: linear-gradient(135deg, #D1FAE5, #A7F3D0);
  1180. }
  1181. .portrait-dim-name {
  1182. font-size: 24rpx;
  1183. color: #333;
  1184. flex: 1;
  1185. }
  1186. .portrait-dim-score {
  1187. font-size: 26rpx;
  1188. font-weight: bold;
  1189. color: #10B981;
  1190. margin-right: 12rpx;
  1191. }
  1192. .portrait-dim-bar-track {
  1193. height: 10rpx;
  1194. background: #D1FAE5;
  1195. border-radius: 5rpx;
  1196. overflow: hidden;
  1197. margin-bottom: 4rpx;
  1198. flex: 1;
  1199. }
  1200. .portrait-dim-bar-fill {
  1201. height: 100%;
  1202. border-radius: 5rpx;
  1203. transition: width 0.3s ease;
  1204. }
  1205. .portrait-dim-desc {
  1206. font-size: 20rpx;
  1207. color: #666;
  1208. display: block;
  1209. }
  1210. .portrait-tip {
  1211. padding: 16rpx 20rpx;
  1212. background: linear-gradient(135deg, #F0FFF4, #E6FFF2);
  1213. border-radius: 12rpx;
  1214. border-left: 4rpx solid #10B981;
  1215. margin-top: 16rpx;
  1216. }
  1217. .portrait-tip-text {
  1218. font-size: 22rpx;
  1219. color: #065F46;
  1220. line-height: 1.5;
  1221. }
  1222. </style>