index.vue 42 KB

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