index.vue 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353
  1. <template>
  2. <view class="container">
  3. <!-- 品牌头部 -->
  4. <PageBanner theme="mind-wisdom"
  5. tagline="心智成长 · 智慧人生"
  6. quote="知己知彼,百战不殆" />
  7. <!-- 家庭成员切换条 -->
  8. <FamilyMemberStrip
  9. v-if="isLoggedIn && familyMembersVisible.length > 0"
  10. :members="familyMembersVisible"
  11. :selectedMemberId="selectedMemberId"
  12. :selectedMemberType="selectedMemberType"
  13. @select="onMemberSelect" />
  14. <!-- 游客登录引导卡 -->
  15. <template v-if="!isLoggedIn">
  16. <LoginGuideCard
  17. title="登录后查看心智成长报告"
  18. :items="[
  19. '查看EMI心理测评四维分析',
  20. '查看DAN认知六维雷达图',
  21. '阅读专属文章、参与认知训练'
  22. ]" />
  23. </template>
  24. <!-- 登录后:五维能量条 -->
  25. <FamilyEnergyBar
  26. v-if="isLoggedIn"
  27. dimensionCode="mind"
  28. :sandboxData="sandboxData"
  29. :dualDimension="dualDimension" />
  30. <!-- 登录后:用户快捷入口 -->
  31. <UserQuickEntry
  32. v-if="isLoggedIn"
  33. :userName="userName"
  34. roleName="家长"
  35. :currentChildName="currentChildName"
  36. :children="children"
  37. @childChanged="onChildChanged"
  38. @scrollTo="scrollToSection" />
  39. <!-- ===== 心理能量概况 ===== -->
  40. <view class="energy-overview" v-if="emiData">
  41. <view class="energy-badge">
  42. <text class="badge-icon">&#x2764;</text>
  43. <text class="badge-value">{{ emiData.overallScore || 0 }}</text>
  44. <text class="badge-label">心能量</text>
  45. </view>
  46. <view class="energy-badge">
  47. <text class="badge-icon">&#x1F4AA;</text>
  48. <text class="badge-value">{{ emiData.resilienceScore || 0 }}</text>
  49. <text class="badge-label">韧性评分</text>
  50. </view>
  51. <view class="energy-badge">
  52. <text class="badge-icon">&#x1F64F;</text>
  53. <text class="badge-value">{{ emiData.emotionScore || 0 }}</text>
  54. <text class="badge-label">情绪评分</text>
  55. </view>
  56. </view>
  57. <!-- EMI 四维条形图 -->
  58. <view class="emi-section" v-if="emiData">
  59. <view class="emi-card">
  60. <text class="emi-card-title">EMI 心理四维</text>
  61. <view class="emi-bar-item" v-for="dim in emiDimensions" :key="dim.key">
  62. <text class="emi-bar-label">{{ dim.label }}</text>
  63. <view class="emi-bar-track">
  64. <view class="emi-bar-fill" :style="{ width: (emiData[dim.key] || 0) + '%', background: dim.color }"></view>
  65. </view>
  66. <text class="emi-bar-score">{{ emiData[dim.key] || 0 }}</text>
  67. <text :class="['emi-bar-level', 'level-' + getScoreLevel(emiData[dim.key] || 0)]">{{ getScoreLevelText(emiData[dim.key] || 0) }}</text>
  68. </view>
  69. <view class="emi-footer">
  70. <text class="emi-overall">综合EQ分: {{ emiData.overallScore || 0 }}</text>
  71. <view class="emi-link" @click="goEmotionReport">
  72. <text class="emi-link-text">查看完整报告</text>
  73. <text class="emi-link-arrow">&#x2192;</text>
  74. </view>
  75. </view>
  76. </view>
  77. </view>
  78. <!-- 无 EMI 数据占位 -->
  79. <view class="placeholder-card" v-if="!emiData && !emiLoading">
  80. <text class="placeholder-icon">&#x1F9E0;</text>
  81. <text class="placeholder-title">心理评估</text>
  82. <text class="placeholder-desc">完成 EMI 心理测评后,可在此查看详细的心理分析报告</text>
  83. <view class="placeholder-btn" @click="goAssessment" v-if="isLoggedIn">预约测评</view>
  84. <view class="placeholder-btn" @click="goLogin" v-else>立即登录</view>
  85. </view>
  86. <!-- 每日心理 -->
  87. <view class="section" v-if="sectionVisible('daily_tip')">
  88. <view class="section-header">
  89. <text class="section-title">每日心理</text>
  90. </view>
  91. <view class="tip-card">
  92. <view class="tip-left-bar"></view>
  93. <view class="tip-body">
  94. <text class="tip-title">{{ dailyTip.title }}</text>
  95. <text class="tip-content">{{ dailyTip.content }}</text>
  96. <view class="tip-footer">
  97. <text class="tip-source">{{ dailyTip.source }}</text>
  98. </view>
  99. </view>
  100. </view>
  101. </view>
  102. <!-- 推荐阅读 -->
  103. <view class="section" v-if="sectionVisible('recommended_reading')">
  104. <view class="section-header">
  105. <text class="section-title">推荐阅读</text>
  106. </view>
  107. <view class="article-list">
  108. <view class="article-card" v-for="article in articles" :key="article.id" @click="goArticleDetail(article.id)">
  109. <view class="article-category" :style="{ background: article.categoryColor }">
  110. <text class="article-category-text">{{ article.category }}</text>
  111. </view>
  112. <text class="article-title">{{ article.title }}</text>
  113. <text class="article-summary">{{ article.summary }}</text>
  114. <view class="article-meta">
  115. <view class="meta-item">
  116. <text class="meta-icon">&#x1F4D6;</text>
  117. <text class="meta-text">{{ article.views }}</text>
  118. </view>
  119. <view class="meta-item">
  120. <text class="meta-icon">&#x1F44D;</text>
  121. <text class="meta-text">{{ article.likes }}</text>
  122. </view>
  123. <text class="article-date">{{ article.date }}</text>
  124. </view>
  125. </view>
  126. </view>
  127. </view>
  128. <!-- ===== 认知区块 ===== -->
  129. <!-- 综合认知评分 -->
  130. <view class="cognitive-overview" v-if="assessmentData">
  131. <view class="cognitive-score-card">
  132. <view class="score-row">
  133. <view class="score-cell">
  134. <text class="score-label">DAN等级</text>
  135. <text class="score-value highlight">{{ assessmentData.danLevel || '-' }}</text>
  136. </view>
  137. <view class="score-cell">
  138. <text class="score-label">综合得分</text>
  139. <text class="score-value">{{ assessmentData.overallScore || 0 }}</text>
  140. </view>
  141. <view class="score-cell">
  142. <text class="score-label">上次测评</text>
  143. <text class="score-value small">{{ assessmentData.assessmentDate || '-' }}</text>
  144. </view>
  145. </view>
  146. <view class="score-actions">
  147. <view class="score-link" @click="goCognitiveReport">
  148. <text class="score-link-text">查看完整报告</text>
  149. <text class="score-link-arrow">&#x2192;</text>
  150. </view>
  151. <view class="score-link secondary" @click="goAssessment">
  152. <text class="score-link-text">预约测评</text>
  153. </view>
  154. </view>
  155. </view>
  156. </view>
  157. <!-- 无测评数据占位 -->
  158. <view class="placeholder-card" v-if="!assessmentData && !assessmentLoading">
  159. <text class="placeholder-icon">&#x1F9E0;</text>
  160. <text class="placeholder-title">认知评估</text>
  161. <text class="placeholder-desc">完成 DAN 认知测评后,可在此查看详细的六维认知分析报告</text>
  162. <view class="placeholder-btn" @click="goAssessment" v-if="isLoggedIn">预约测评</view>
  163. <view class="placeholder-btn" @click="goLogin" v-else>立即登录</view>
  164. </view>
  165. <!-- 认知六维雷达图 -->
  166. <view class="radar-section" v-if="assessmentData && familyMembers.length > 0">
  167. <scroll-view class="member-scroll" scroll-x show-scrollbar="false">
  168. <view class="member-row">
  169. <view
  170. v-for="member in familyMembers"
  171. :key="member.childId"
  172. :class="['member-tab', member.childId === selectedChildId ? 'active' : '']"
  173. @click="selectMember(member.childId)"
  174. >
  175. <text class="member-name">{{ member.childName }}</text>
  176. <text class="member-overall">{{ member.overallScore || 0 }}分</text>
  177. </view>
  178. </view>
  179. </scroll-view>
  180. <view class="radar-card">
  181. <RadarChart
  182. :dimensions="cognitiveDimensions"
  183. :scores="currentScores"
  184. :childName="currentChildName"
  185. @dimensionClick="goTraining"
  186. />
  187. <text class="radar-hint">点击维度标签进入能力训练</text>
  188. </view>
  189. </view>
  190. <!-- 认知训练入口 -->
  191. <view class="section" v-if="isLoggedIn">
  192. <view class="section-header">
  193. <text class="section-title">认知训练</text>
  194. </view>
  195. <view class="game-grid">
  196. <view class="game-item" @click="goGame('schulte')">
  197. <view class="game-icon-wrap" style="background: #FFF7ED">
  198. <text class="game-icon">&#x1F3AF;</text>
  199. </view>
  200. <text class="game-name">舒尔特方格</text>
  201. </view>
  202. <view class="game-item" @click="goGame('1a2b')">
  203. <view class="game-icon-wrap" style="background: #EFF6FF">
  204. <text class="game-icon">&#x1F522;</text>
  205. </view>
  206. <text class="game-name">猜数字</text>
  207. </view>
  208. <view class="game-item" @click="goGame('sudoku')">
  209. <view class="game-icon-wrap" style="background: #EDE9FE">
  210. <text class="game-icon">&#x1F9E9;</text>
  211. </view>
  212. <text class="game-name">数独</text>
  213. </view>
  214. </view>
  215. </view>
  216. <!-- 本月阅读统计 -->
  217. <view class="section" v-if="isLoggedIn && readingStats">
  218. <view class="section-header">
  219. <text class="section-title">本月阅读</text>
  220. </view>
  221. <view class="reading-card">
  222. <view class="reading-stats">
  223. <text class="reading-count">{{ readingStats.totalArticles || 0 }}篇</text>
  224. <text class="reading-divider">/</text>
  225. <text class="reading-duration">{{ readingMinutes }}分钟</text>
  226. </view>
  227. <view class="reading-progress-wrap">
  228. <view class="reading-progress-track">
  229. <view class="reading-progress-fill" :style="{ width: (readingStats.progress || 0) + '%' }"></view>
  230. </view>
  231. <text class="reading-progress-text">{{ readingStats.progress || 0 }}%</text>
  232. </view>
  233. <view class="reading-link" @click="goArticles">
  234. <text class="reading-link-text">去阅读 &#x2192;</text>
  235. </view>
  236. </view>
  237. </view>
  238. <!-- ===== 底部:维度任务/活动/商品 ===== -->
  239. <DimensionTasks
  240. v-if="isLoggedIn"
  241. dimensionCode="mind"
  242. :tasks="dimensionTasks"
  243. @taskClick="onTaskClick"
  244. @moreTasks="goTasks" />
  245. <DimensionActivities
  246. v-if="isLoggedIn"
  247. dimensionCode="mind"
  248. :activities="dimensionActivities"
  249. @activityClick="goActivityDetail"
  250. @moreActivities="goMoreActivities" />
  251. <DimensionProducts
  252. v-if="isLoggedIn"
  253. dimensionCode="mind"
  254. :products="dimensionProducts"
  255. @productClick="goProductDetail"
  256. @moreProducts="goMoreProducts" />
  257. <!-- ===== 底部:功能入口 Grid(通用) ===== -->
  258. <view class="func-section" v-if="sectionVisible('func_entries')">
  259. <view class="func-grid">
  260. <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
  261. <view class="func-icon-wrap">
  262. <text class="func-icon">{{ item.icon }}</text>
  263. </view>
  264. <text class="func-label">{{ item.label }}</text>
  265. </view>
  266. </view>
  267. </view>
  268. <!-- 底部占位 -->
  269. <view class="bottom-spacer"></view>
  270. </view>
  271. </template>
  272. <script>
  273. import PageBanner from '../../components/PageBanner.vue'
  274. import LoginGuideCard from '../../components/LoginGuideCard.vue'
  275. import RadarChart from '../../components/RadarChart.vue'
  276. import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
  277. import UserQuickEntry from '../../components/UserQuickEntry.vue'
  278. import DimensionTasks from '../../components/DimensionTasks.vue'
  279. import DimensionActivities from '../../components/DimensionActivities.vue'
  280. import DimensionProducts from '../../components/DimensionProducts.vue'
  281. import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
  282. import { getVisibleSections, getFeaturedArticles, getEmiReport, getAssessmentLatestResult, getDailyTip, getReadingStats, getEnergyOverview, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren, getVisibleFamilyMembers } from '../../utils/api.js'
  283. import config from '../../config.js'
  284. export default {
  285. components: { PageBanner, RadarChart, LoginGuideCard, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, FamilyMemberStrip },
  286. data() {
  287. return {
  288. isLoggedIn: false,
  289. role: null,
  290. currentChildId: null,
  291. visibleSections: [],
  292. familyMembersVisible: [],
  293. selectedMemberId: 0,
  294. selectedMemberType: 'all',
  295. // 心理 Tab 数据
  296. emiData: null,
  297. emiLoading: true,
  298. dailyTip: {
  299. title: '成长型思维',
  300. content: '拥有成长型思维的孩子相信自己的能力可以通过努力提升,他们更愿意接受挑战,从失败中学习。作为家长,多鼓励孩子的努力而非天赋,可以帮助孩子建立成长型思维。',
  301. source: '—— 卡罗尔·德韦克《终身成长》'
  302. },
  303. emiDimensions: [
  304. { key: 'emotionScore', label: '情绪商数', color: '#FF6B35' },
  305. { key: 'resilienceScore', label: '心理韧性', color: '#F97316' },
  306. { key: 'stressCopingScore', label: '压力应对', color: '#FB923C' },
  307. { key: 'selfAwarenessScore', label: '自我认知', color: '#FBBF24' }
  308. ],
  309. // 认知 Tab 数据
  310. assessmentData: null,
  311. assessmentLoading: true,
  312. cognitiveDimensions: [
  313. { label: '感知', key: 'perception' },
  314. { label: '专注', key: 'focus' },
  315. { label: '记忆', key: 'memory' },
  316. { label: '逻辑', key: 'logic' },
  317. { label: '空间', key: 'spatial' },
  318. { label: '加工速度', key: 'processingSpeed' }
  319. ],
  320. familyMembers: [],
  321. selectedChildId: null,
  322. currentScores: [0, 0, 0, 0, 0, 0],
  323. currentChildName: '',
  324. readingStats: null,
  325. // 功能入口
  326. funcList: [
  327. { icon: '\u{1F4D6}', label: '阅读', needLogin: false, page: '/pages/mind/articles' },
  328. { icon: '\u{1F3AE}', label: '小游戏', needLogin: true, page: '/pages/games/list' },
  329. { icon: '\u{1F4CA}', label: '测评报告', needLogin: true, action: 'switchCognitive' },
  330. { icon: '\u{1F9EA}', label: '训练中心', needLogin: true, page: '/pages/mind/training' },
  331. { icon: '\u{1F4DD}', label: '成长档案', needLogin: true, page: '/pages/growth/index' }
  332. ],
  333. articles: [],
  334. // 五维组件数据
  335. userName: '',
  336. children: [],
  337. sandboxData: null,
  338. dualDimension: null,
  339. dimensionTasks: [],
  340. dimensionActivities: [],
  341. dimensionProducts: []
  342. }
  343. },
  344. computed: {
  345. readingMinutes: function() {
  346. var stats = this.readingStats
  347. if (!stats || !stats.totalDurationSeconds) return 0
  348. return Math.floor((stats.totalDurationSeconds || 0) / 60)
  349. }
  350. },
  351. onShow() {
  352. var token = uni.getStorageSync('token')
  353. this.isLoggedIn = !!token
  354. if (this.isLoggedIn) {
  355. this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  356. this.currentChildId = uni.getStorageSync('currentChildId') || null
  357. this.userName = uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '家长'
  358. }
  359. this.loadSectionConfig()
  360. this.loadFeaturedArticles()
  361. this.loadDailyTip()
  362. if (this.isLoggedIn) {
  363. this.loadChildren()
  364. this.loadFamilyMembersVisible()
  365. this.loadSandboxData()
  366. this.loadEmiData()
  367. this.loadAssessmentData()
  368. this.loadReadingStats()
  369. this.loadFamilyCognitiveData()
  370. }
  371. },
  372. methods: {
  373. // ===== 区块可见性 =====
  374. loadSectionConfig: function() {
  375. var self = this
  376. var baseSections = ['func_entries', 'daily_tip', 'recommended_reading']
  377. if (!uni.getStorageSync('token')) {
  378. self.visibleSections = baseSections
  379. return
  380. }
  381. var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  382. getVisibleSections({ pageKey: 'mind', role: role }).then(function(res) {
  383. if (res.code === 200 && res.data) {
  384. var keys = res.data.map(function(s) { return s.sectionKey })
  385. var merged = baseSections.slice()
  386. keys.forEach(function(k) { if (merged.indexOf(k) === -1) merged.push(k) })
  387. self.visibleSections = merged
  388. }
  389. }).catch(function() {
  390. self.visibleSections = ['func_entries', 'daily_tip', 'recommended_reading', 'mind_tasks', 'related_activities', 'purchased_courses']
  391. })
  392. },
  393. sectionVisible: function(key) {
  394. return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
  395. },
  396. // ===== 心理 Tab:EMI 数据 =====
  397. loadEmiData: function() {
  398. var self = this
  399. var childId = self.currentChildId
  400. if (!childId) {
  401. self.emiLoading = false
  402. return
  403. }
  404. self.emiLoading = true
  405. getEmiReport(childId).then(function(res) {
  406. if (res.code === 200 && res.data) {
  407. self.emiData = res.data
  408. }
  409. self.emiLoading = false
  410. }).catch(function() {
  411. self.emiLoading = false
  412. })
  413. },
  414. // ===== 认知 Tab:测评数据 =====
  415. loadAssessmentData: function() {
  416. var self = this
  417. var childId = self.currentChildId
  418. if (!childId) {
  419. self.assessmentLoading = false
  420. return
  421. }
  422. self.assessmentLoading = true
  423. getAssessmentLatestResult(childId).then(function(res) {
  424. if (res.code === 200 && res.data) {
  425. self.assessmentData = res.data
  426. }
  427. self.assessmentLoading = false
  428. }).catch(function() {
  429. self.assessmentLoading = false
  430. })
  431. },
  432. // ===== 认知 Tab:六维认知数据 =====
  433. loadFamilyCognitiveData: function() {
  434. var self = this
  435. var token = uni.getStorageSync('token')
  436. var userId = uni.getStorageSync('userId') || ''
  437. if (!token) return
  438. uni.request({
  439. url: config.API_BASE_URL + '/api/cognitive/family',
  440. method: 'POST',
  441. header: {
  442. 'Content-Type': 'application/json',
  443. 'Authorization': 'Bearer ' + token,
  444. 'X-User-Id': userId
  445. },
  446. success: function(r) {
  447. if (r.statusCode === 200 && r.data && r.data.code === 200 && r.data.data) {
  448. var data = r.data.data
  449. if (Array.isArray(data) && data.length > 0) {
  450. self.familyMembers = data
  451. var first = data[0]
  452. self.selectedChildId = first.childId
  453. self.updateCurrentScores(first)
  454. }
  455. }
  456. }
  457. })
  458. },
  459. selectMember: function(childId) {
  460. if (childId === this.selectedChildId) return
  461. this.selectedChildId = childId
  462. var member = this.familyMembers.find(function(m) { return m.childId === childId })
  463. if (member) {
  464. this.updateCurrentScores(member)
  465. }
  466. },
  467. updateCurrentScores: function(member) {
  468. this.currentChildName = member.childName || ''
  469. this.currentScores = [
  470. member.perceptionScore || 0,
  471. member.focusScore || 0,
  472. member.memoryScore || 0,
  473. member.logicScore || 0,
  474. member.spatialScore || 0,
  475. member.processingSpeedScore || 0
  476. ]
  477. },
  478. goTraining: function(dimensionKey) {
  479. if (!this.selectedChildId) {
  480. uni.showToast({ title: '请先选择家庭成员', icon: 'none' })
  481. return
  482. }
  483. uni.navigateTo({ url: '/pages/mind/training?dimension=' + dimensionKey + '&childId=' + this.selectedChildId })
  484. },
  485. // ===== 每日心理 =====
  486. loadDailyTip: function() {
  487. var self = this
  488. getDailyTip().then(function(res) {
  489. if (res.code === 200 && res.data) {
  490. self.dailyTip = res.data
  491. }
  492. }).catch(function() {})
  493. },
  494. // ===== 阅读统计 =====
  495. loadReadingStats: function() {
  496. var self = this
  497. var childId = self.currentChildId
  498. if (!childId) return
  499. getReadingStats(childId).then(function(res) {
  500. if (res.code === 200 && res.data) {
  501. self.readingStats = res.data
  502. }
  503. }).catch(function() {})
  504. },
  505. // ===== 文章 =====
  506. loadFeaturedArticles: function() {
  507. var self = this
  508. getFeaturedArticles({ size: 5 }).then(function(res) {
  509. if (res.code === 200 && res.data) {
  510. var gradientColors = [
  511. 'linear-gradient(135deg, #8B5CF6, #A78BFA)',
  512. 'linear-gradient(135deg, #5B9BD5, #8FC5E8)',
  513. 'linear-gradient(135deg, #10B981, #34D399)',
  514. 'linear-gradient(135deg, #8B5CF6, #C084FC)',
  515. 'linear-gradient(135deg, #F97316, #FB923C)'
  516. ]
  517. var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
  518. self.articles = list.map(function(item, index) {
  519. return {
  520. id: item.id,
  521. category: item.categoryName || '成长文章',
  522. categoryColor: gradientColors[index % gradientColors.length],
  523. title: item.title || '',
  524. summary: item.summary || '',
  525. views: item.viewCount ? String(item.viewCount) : '0',
  526. likes: '0',
  527. date: item.publishedAt ? item.publishedAt.slice(0, 10) : ''
  528. }
  529. })
  530. }
  531. }).catch(function() {})
  532. },
  533. // ===== EMI 分数评级 =====
  534. getScoreLevel: function(score) {
  535. if (score >= 85) return 'excellent'
  536. if (score >= 70) return 'good'
  537. if (score >= 55) return 'average'
  538. return 'low'
  539. },
  540. getScoreLevelText: function(score) {
  541. if (score >= 85) return '优秀'
  542. if (score >= 70) return '良好'
  543. if (score >= 55) return '一般'
  544. return '需提升'
  545. },
  546. // ===== 导航 =====
  547. goEmotionReport: function() {
  548. var childId = this.currentChildId || ''
  549. uni.navigateTo({ url: '/pages/mind/emotion-report?childId=' + childId })
  550. },
  551. goCognitiveReport: function() {
  552. var childId = this.currentChildId || ''
  553. uni.navigateTo({ url: '/pages/wisdom/cognitive-report?childId=' + childId })
  554. },
  555. goGame: function(gameCode) {
  556. if (!this.isLoggedIn) {
  557. uni.showModal({
  558. title: '提示',
  559. content: '请先登录后使用此功能',
  560. success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
  561. })
  562. return
  563. }
  564. uni.navigateTo({ url: '/pages/games/' + gameCode })
  565. },
  566. goArticles: function() {
  567. uni.navigateTo({ url: '/pages/mind/articles' })
  568. },
  569. onFuncClick: function(item) {
  570. if (item.needLogin && !this.isLoggedIn) {
  571. uni.showModal({
  572. title: '提示',
  573. content: '请先登录后使用此功能',
  574. success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
  575. })
  576. return
  577. }
  578. if (item.page) uni.navigateTo({ url: item.page })
  579. },
  580. goArticleDetail: function(id) {
  581. var url = '/pages/mind/article-detail?id=' + id
  582. uni.navigateTo({ url: url })
  583. },
  584. // ===== 孩子列表与维度数据 =====
  585. loadChildren: function() {
  586. var self = this
  587. getChildren().then(function(res) {
  588. if (res.code === 200 && res.data) {
  589. self.children = res.data
  590. var savedChildId = uni.getStorageSync('currentChildId')
  591. var found = false
  592. for (var i = 0; i < self.children.length; i++) {
  593. if (self.children[i].childId === savedChildId) {
  594. self.currentChildId = savedChildId
  595. found = true
  596. break
  597. }
  598. }
  599. if (!found && self.children.length > 0) {
  600. self.currentChildId = self.children[0].childId
  601. uni.setStorageSync('currentChildId', self.currentChildId)
  602. }
  603. if (self.currentChildId) {
  604. self.loadDimensionData()
  605. }
  606. }
  607. }).catch(function(e) {
  608. console.log('获取孩子列表失败', e)
  609. })
  610. },
  611. loadDimensionData: function() {
  612. this.loadEnergyData()
  613. this.loadDimensionTasks()
  614. this.loadDimensionActivities()
  615. this.loadDimensionProducts()
  616. },
  617. loadEnergyData: function() {
  618. var self = this
  619. if (!this.currentChildId) return
  620. getEnergyOverview(this.currentChildId).then(function(res) {
  621. if (res && res.data) {
  622. var dims = res.data.dimensions
  623. if (dims && dims.length > 0) {
  624. for (var i = 0; i < dims.length; i++) {
  625. if (dims[i].code === 'mind') {
  626. self.dualDimension = dims[i]
  627. break
  628. }
  629. }
  630. }
  631. }
  632. }).catch(function(e) {
  633. console.log('获取能量概览失败', e)
  634. })
  635. },
  636. loadSandboxData: function() {
  637. var self = this
  638. getFamilyEnergySandbox().then(function(res) {
  639. if (res && res.data) {
  640. self.sandboxData = res.data
  641. }
  642. }).catch(function() {})
  643. },
  644. loadDimensionTasks: function() {
  645. var self = this
  646. if (!this.currentChildId) return
  647. getTodayTasksByCategory(this.currentChildId, 'mind').then(function(res) {
  648. if (res && res.data) {
  649. self.dimensionTasks = Array.isArray(res.data) ? res.data.slice(0, 5) : []
  650. }
  651. }).catch(function() { self.dimensionTasks = [] })
  652. },
  653. loadDimensionActivities: function() {
  654. var self = this
  655. getActivityList({ dimensionCode: 'mind', page: 1, size: 3 }).then(function(res) {
  656. if (res && res.data) {
  657. var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
  658. self.dimensionActivities = list.slice(0, 3)
  659. }
  660. }).catch(function() { self.dimensionActivities = [] })
  661. },
  662. loadDimensionProducts: function() {
  663. var self = this
  664. getProductsByDomain('mind', 1, 4).then(function(res) {
  665. if (res && res.data) {
  666. var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
  667. self.dimensionProducts = list.slice(0, 4)
  668. }
  669. }).catch(function() { self.dimensionProducts = [] })
  670. },
  671. onChildChanged: function(child) {
  672. this.currentChildId = child.childId
  673. uni.setStorageSync('currentChildId', child.childId)
  674. this.loadDimensionData()
  675. this.loadEmiData()
  676. this.loadAssessmentData()
  677. this.loadReadingStats()
  678. },
  679. loadFamilyMembersVisible: function() {
  680. var self = this
  681. getVisibleFamilyMembers().then(function(res) {
  682. self.familyMembersVisible = res.data || []
  683. }).catch(function(e) {
  684. console.log('获取可见家庭成员失败', e)
  685. self.familyMembersVisible = []
  686. })
  687. },
  688. onMemberSelect: function(member) {
  689. if (member.memberType === 'all') {
  690. this.selectedMemberId = 0
  691. this.selectedMemberType = 'all'
  692. } else {
  693. this.selectedMemberId = member.id
  694. this.selectedMemberType = member.memberType
  695. }
  696. // TODO: 根据选中成员重新加载对应维度数据
  697. },
  698. scrollToSection: function(section) {
  699. uni.pageScrollTo({ selector: '#' + section, duration: 300 })
  700. },
  701. onTaskClick: function(task) {
  702. uni.navigateTo({ url: '/pages/tasks/tasks' })
  703. },
  704. goActivityDetail: function(act) {
  705. if (act && act.id) uni.navigateTo({ url: '/pages/discover/index?type=activity&id=' + act.id })
  706. },
  707. goMoreActivities: function() {
  708. uni.navigateTo({ url: '/pages/discover/index?type=activity&dimension=mind' })
  709. },
  710. goProductDetail: function(prod) {
  711. if (prod && prod.id) uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + prod.id })
  712. },
  713. goMoreProducts: function() {
  714. uni.navigateTo({ url: '/pages/discover/index?type=product&domain=mind' })
  715. },
  716. goTasks: function() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
  717. goAssessment: function() { uni.navigateTo({ url: '/pages/assessment/apply' }) },
  718. goLogin: function() { uni.navigateTo({ url: '/pages/login/login' }) }
  719. }
  720. }
  721. </script>
  722. <style scoped>
  723. .container {
  724. min-height: 100vh;
  725. background: #f5f7fa;
  726. padding-bottom: 120rpx;
  727. }
  728. /* ===== 双 Tab 切换 ===== */
  729. .tab-bar {
  730. display: flex;
  731. flex-direction: row;
  732. margin: 20rpx 30rpx 0;
  733. background: #fff;
  734. border-radius: 40rpx;
  735. padding: 6rpx;
  736. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  737. }
  738. .tab-item {
  739. flex: 1;
  740. display: flex;
  741. align-items: center;
  742. justify-content: center;
  743. padding: 18rpx 0;
  744. border-radius: 36rpx;
  745. }
  746. .tab-item.tab-active {
  747. background: linear-gradient(135deg, #FF6B35, #FFD700);
  748. box-shadow: 0 4rpx 12rpx rgba(255,107,53,0.3);
  749. }
  750. .tab-text {
  751. font-size: 28rpx;
  752. font-weight: bold;
  753. color: #666;
  754. }
  755. .tab-active .tab-text {
  756. color: #fff;
  757. }
  758. /* ===== 心理能量概况 ===== */
  759. .energy-overview {
  760. display: flex;
  761. flex-direction: row;
  762. margin: 20rpx 30rpx;
  763. background: #fff;
  764. border-radius: 20rpx;
  765. padding: 24rpx 16rpx;
  766. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  767. justify-content: space-around;
  768. }
  769. .energy-badge {
  770. display: flex;
  771. flex-direction: column;
  772. align-items: center;
  773. flex: 1;
  774. }
  775. .badge-icon {
  776. font-size: 36rpx;
  777. margin-bottom: 8rpx;
  778. }
  779. .badge-value {
  780. font-size: 36rpx;
  781. font-weight: bold;
  782. color: #333;
  783. }
  784. .badge-label {
  785. font-size: 20rpx;
  786. color: #999;
  787. margin-top: 4rpx;
  788. }
  789. /* ===== EMI 四维条形图 ===== */
  790. .emi-section {
  791. margin: 20rpx 30rpx;
  792. }
  793. .emi-card {
  794. background: #fff;
  795. border-radius: 24rpx;
  796. padding: 30rpx;
  797. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  798. }
  799. .emi-card-title {
  800. font-size: 30rpx;
  801. font-weight: bold;
  802. color: #333;
  803. margin-bottom: 24rpx;
  804. display: block;
  805. }
  806. .emi-bar-item {
  807. display: flex;
  808. flex-direction: row;
  809. align-items: center;
  810. margin-bottom: 20rpx;
  811. }
  812. .emi-bar-label {
  813. font-size: 24rpx;
  814. color: #666;
  815. width: 120rpx;
  816. flex-shrink: 0;
  817. }
  818. .emi-bar-track {
  819. flex: 1;
  820. height: 20rpx;
  821. background: #f0f0f0;
  822. border-radius: 10rpx;
  823. overflow: hidden;
  824. margin: 0 16rpx;
  825. }
  826. .emi-bar-fill {
  827. height: 100%;
  828. border-radius: 10rpx;
  829. transition: width 0.5s;
  830. }
  831. .emi-bar-score {
  832. font-size: 26rpx;
  833. font-weight: bold;
  834. color: #333;
  835. width: 60rpx;
  836. text-align: right;
  837. flex-shrink: 0;
  838. }
  839. .emi-bar-level {
  840. font-size: 20rpx;
  841. width: 80rpx;
  842. text-align: right;
  843. flex-shrink: 0;
  844. }
  845. .level-excellent { color: #10B981; }
  846. .level-good { color: #3B82F6; }
  847. .level-average { color: #F97316; }
  848. .level-low { color: #EF4444; }
  849. .emi-footer {
  850. display: flex;
  851. flex-direction: row;
  852. justify-content: space-between;
  853. align-items: center;
  854. margin-top: 20rpx;
  855. padding-top: 20rpx;
  856. border-top: 1rpx solid #f0f0f0;
  857. }
  858. .emi-overall {
  859. font-size: 28rpx;
  860. font-weight: bold;
  861. color: #FF6B35;
  862. }
  863. .emi-link {
  864. display: flex;
  865. flex-direction: row;
  866. align-items: center;
  867. }
  868. .emi-link-text {
  869. font-size: 26rpx;
  870. color: #FF6B35;
  871. font-weight: 500;
  872. }
  873. .emi-link-arrow {
  874. font-size: 26rpx;
  875. color: #FF6B35;
  876. margin-left: 8rpx;
  877. }
  878. /* ===== 占位卡 ===== */
  879. .placeholder-card {
  880. margin: 30rpx;
  881. background: #fff;
  882. border-radius: 24rpx;
  883. padding: 60rpx 40rpx;
  884. display: flex;
  885. flex-direction: column;
  886. align-items: center;
  887. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  888. }
  889. .placeholder-icon {
  890. font-size: 80rpx;
  891. margin-bottom: 20rpx;
  892. }
  893. .placeholder-title {
  894. font-size: 32rpx;
  895. font-weight: bold;
  896. color: #333;
  897. margin-bottom: 16rpx;
  898. }
  899. .placeholder-desc {
  900. font-size: 26rpx;
  901. color: #999;
  902. text-align: center;
  903. line-height: 1.6;
  904. margin-bottom: 30rpx;
  905. }
  906. .placeholder-btn {
  907. background: linear-gradient(135deg, #FF6B35, #FFD700);
  908. color: #fff;
  909. font-size: 28rpx;
  910. font-weight: bold;
  911. padding: 16rpx 64rpx;
  912. border-radius: 40rpx;
  913. box-shadow: 0 4rpx 16rpx rgba(255,107,53,0.3);
  914. }
  915. /* ===== 认知评分卡 ===== */
  916. .cognitive-overview {
  917. margin: 20rpx 30rpx;
  918. }
  919. .cognitive-score-card {
  920. background: #fff;
  921. border-radius: 24rpx;
  922. padding: 30rpx;
  923. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  924. }
  925. .score-row {
  926. display: flex;
  927. flex-direction: row;
  928. justify-content: space-around;
  929. margin-bottom: 24rpx;
  930. }
  931. .score-cell {
  932. display: flex;
  933. flex-direction: column;
  934. align-items: center;
  935. flex: 1;
  936. }
  937. .score-label {
  938. font-size: 24rpx;
  939. color: #999;
  940. margin-bottom: 10rpx;
  941. }
  942. .score-value {
  943. font-size: 40rpx;
  944. font-weight: bold;
  945. color: #333;
  946. }
  947. .score-value.highlight {
  948. color: #FFD700;
  949. }
  950. .score-value.small {
  951. font-size: 24rpx;
  952. color: #666;
  953. }
  954. .score-actions {
  955. display: flex;
  956. flex-direction: row;
  957. justify-content: space-between;
  958. align-items: center;
  959. border-top: 1rpx solid #f0f0f0;
  960. padding-top: 20rpx;
  961. }
  962. .score-link {
  963. display: flex;
  964. flex-direction: row;
  965. align-items: center;
  966. background: linear-gradient(135deg, #667eea, #764ba2);
  967. border-radius: 32rpx;
  968. padding: 12rpx 24rpx;
  969. }
  970. .score-link.secondary {
  971. background: #f0f0f0;
  972. }
  973. .score-link-text {
  974. font-size: 24rpx;
  975. color: #fff;
  976. font-weight: 500;
  977. }
  978. .score-link.secondary .score-link-text {
  979. color: #666;
  980. }
  981. .score-link-arrow {
  982. font-size: 24rpx;
  983. color: #fff;
  984. margin-left: 8rpx;
  985. }
  986. /* ===== 雷达图区域 ===== */
  987. .radar-section {
  988. margin: 20rpx 30rpx;
  989. }
  990. .member-scroll {
  991. width: 100%;
  992. margin-bottom: 16rpx;
  993. }
  994. .member-row {
  995. display: flex;
  996. flex-direction: row;
  997. padding: 4rpx 0;
  998. white-space: nowrap;
  999. }
  1000. .member-tab {
  1001. display: inline-flex;
  1002. flex-direction: column;
  1003. align-items: center;
  1004. padding: 16rpx 32rpx;
  1005. margin-right: 16rpx;
  1006. background: #fff;
  1007. border-radius: 16rpx;
  1008. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05);
  1009. min-width: 120rpx;
  1010. flex-shrink: 0;
  1011. }
  1012. .member-tab.active {
  1013. background: linear-gradient(135deg, #FF6B35, #FFD700);
  1014. box-shadow: 0 4rpx 16rpx rgba(255,107,53,0.3);
  1015. }
  1016. .member-tab.active .member-name {
  1017. color: #fff;
  1018. }
  1019. .member-tab.active .member-overall {
  1020. color: rgba(255,255,255,0.9);
  1021. }
  1022. .member-name {
  1023. font-size: 26rpx;
  1024. font-weight: bold;
  1025. color: #333;
  1026. }
  1027. .member-overall {
  1028. font-size: 20rpx;
  1029. color: #999;
  1030. margin-top: 4rpx;
  1031. }
  1032. .radar-card {
  1033. background: #fff;
  1034. border-radius: 24rpx;
  1035. padding: 30rpx 20rpx 20rpx;
  1036. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  1037. display: flex;
  1038. flex-direction: column;
  1039. align-items: center;
  1040. }
  1041. .radar-hint {
  1042. font-size: 22rpx;
  1043. color: #bbb;
  1044. margin-top: 16rpx;
  1045. }
  1046. /* ===== 认知训练入口 ===== */
  1047. .game-grid {
  1048. display: flex;
  1049. flex-direction: row;
  1050. justify-content: space-around;
  1051. background: #fff;
  1052. border-radius: 20rpx;
  1053. padding: 24rpx 16rpx;
  1054. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1055. }
  1056. .game-item {
  1057. display: flex;
  1058. flex-direction: column;
  1059. align-items: center;
  1060. flex: 1;
  1061. }
  1062. .game-icon-wrap {
  1063. width: 80rpx;
  1064. height: 80rpx;
  1065. border-radius: 40rpx;
  1066. display: flex;
  1067. align-items: center;
  1068. justify-content: center;
  1069. margin-bottom: 10rpx;
  1070. }
  1071. .game-icon {
  1072. font-size: 40rpx;
  1073. }
  1074. .game-name {
  1075. font-size: 22rpx;
  1076. color: #666;
  1077. font-weight: 500;
  1078. }
  1079. .game-item:active {
  1080. opacity: 0.7;
  1081. }
  1082. /* ===== 阅读统计 ===== */
  1083. .reading-card {
  1084. background: #fff;
  1085. border-radius: 20rpx;
  1086. padding: 28rpx 24rpx;
  1087. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1088. }
  1089. .reading-stats {
  1090. display: flex;
  1091. flex-direction: row;
  1092. align-items: baseline;
  1093. margin-bottom: 16rpx;
  1094. }
  1095. .reading-count {
  1096. font-size: 36rpx;
  1097. font-weight: bold;
  1098. color: #FF6B35;
  1099. }
  1100. .reading-divider {
  1101. font-size: 28rpx;
  1102. color: #ccc;
  1103. margin: 0 8rpx;
  1104. }
  1105. .reading-duration {
  1106. font-size: 36rpx;
  1107. font-weight: bold;
  1108. color: #333;
  1109. }
  1110. .reading-progress-wrap {
  1111. display: flex;
  1112. flex-direction: row;
  1113. align-items: center;
  1114. margin-bottom: 16rpx;
  1115. }
  1116. .reading-progress-track {
  1117. flex: 1;
  1118. height: 16rpx;
  1119. background: #f0f0f0;
  1120. border-radius: 8rpx;
  1121. overflow: hidden;
  1122. margin-right: 16rpx;
  1123. }
  1124. .reading-progress-fill {
  1125. height: 100%;
  1126. background: linear-gradient(90deg, #FF6B35, #FFD700);
  1127. border-radius: 8rpx;
  1128. transition: width 0.5s;
  1129. }
  1130. .reading-progress-text {
  1131. font-size: 24rpx;
  1132. color: #999;
  1133. width: 80rpx;
  1134. text-align: right;
  1135. flex-shrink: 0;
  1136. }
  1137. .reading-link {
  1138. display: flex;
  1139. justify-content: flex-end;
  1140. }
  1141. .reading-link-text {
  1142. font-size: 26rpx;
  1143. color: #FF6B35;
  1144. font-weight: 500;
  1145. }
  1146. /* ===== 每日心理 ===== */
  1147. .tip-card {
  1148. background: #fff;
  1149. border-radius: 20rpx;
  1150. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1151. display: flex;
  1152. flex-direction: row;
  1153. overflow: hidden;
  1154. }
  1155. .tip-left-bar {
  1156. width: 6rpx;
  1157. background: linear-gradient(180deg, #5B9BD5, #8FC5E8);
  1158. flex-shrink: 0;
  1159. }
  1160. .tip-body {
  1161. flex: 1;
  1162. padding: 28rpx 24rpx;
  1163. }
  1164. .tip-title {
  1165. font-size: 28rpx;
  1166. font-weight: bold;
  1167. color: #5B9BD5;
  1168. display: block;
  1169. margin-bottom: 12rpx;
  1170. }
  1171. .tip-content {
  1172. font-size: 24rpx;
  1173. color: #666;
  1174. line-height: 1.6;
  1175. display: block;
  1176. margin-bottom: 16rpx;
  1177. }
  1178. .tip-footer {
  1179. display: flex;
  1180. flex-direction: row;
  1181. justify-content: flex-end;
  1182. }
  1183. .tip-source {
  1184. font-size: 22rpx;
  1185. color: #bbb;
  1186. }
  1187. /* ===== 通用区块 ===== */
  1188. .section {
  1189. margin: 20rpx 30rpx;
  1190. }
  1191. .section-header {
  1192. margin-bottom: 20rpx;
  1193. }
  1194. .section-title {
  1195. font-size: 30rpx;
  1196. font-weight: bold;
  1197. color: #333;
  1198. }
  1199. /* ===== 推荐阅读 ===== */
  1200. .article-list {
  1201. display: flex;
  1202. flex-direction: column;
  1203. }
  1204. .article-card {
  1205. background: #fff;
  1206. border-radius: 20rpx;
  1207. padding: 28rpx 24rpx;
  1208. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1209. margin-bottom: 20rpx;
  1210. }
  1211. .article-category {
  1212. display: inline-block;
  1213. padding: 4rpx 18rpx;
  1214. border-radius: 20rpx;
  1215. margin-bottom: 14rpx;
  1216. }
  1217. .article-category-text {
  1218. font-size: 20rpx;
  1219. color: #fff;
  1220. font-weight: 500;
  1221. }
  1222. .article-title {
  1223. font-size: 28rpx;
  1224. font-weight: bold;
  1225. color: #333;
  1226. display: block;
  1227. margin-bottom: 10rpx;
  1228. }
  1229. .article-summary {
  1230. font-size: 24rpx;
  1231. color: #888;
  1232. line-height: 1.5;
  1233. display: block;
  1234. margin-bottom: 20rpx;
  1235. }
  1236. .article-meta {
  1237. display: flex;
  1238. flex-direction: row;
  1239. align-items: center;
  1240. justify-content: space-between;
  1241. }
  1242. .meta-item {
  1243. display: flex;
  1244. flex-direction: row;
  1245. align-items: center;
  1246. margin-right: 20rpx;
  1247. }
  1248. .meta-icon {
  1249. font-size: 22rpx;
  1250. margin-right: 4rpx;
  1251. }
  1252. .meta-text {
  1253. font-size: 22rpx;
  1254. color: #bbb;
  1255. }
  1256. .article-date {
  1257. font-size: 22rpx;
  1258. color: #bbb;
  1259. margin-left: auto;
  1260. }
  1261. /* ===== 功能区入口 ===== */
  1262. .func-section {
  1263. margin: 20rpx 30rpx;
  1264. }
  1265. .func-grid {
  1266. display: flex;
  1267. flex-direction: row;
  1268. justify-content: space-between;
  1269. background: #fff;
  1270. border-radius: 20rpx;
  1271. padding: 24rpx 16rpx;
  1272. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  1273. }
  1274. .func-item {
  1275. display: flex;
  1276. flex-direction: column;
  1277. align-items: center;
  1278. flex: 1;
  1279. }
  1280. .func-icon-wrap {
  1281. width: 80rpx;
  1282. height: 80rpx;
  1283. border-radius: 40rpx;
  1284. background: linear-gradient(135deg, #D6EAF8, #EBF2FA);
  1285. display: flex;
  1286. align-items: center;
  1287. justify-content: center;
  1288. margin-bottom: 10rpx;
  1289. }
  1290. .func-icon {
  1291. font-size: 40rpx;
  1292. }
  1293. .func-label {
  1294. font-size: 22rpx;
  1295. color: #666;
  1296. font-weight: 500;
  1297. }
  1298. .func-item:active {
  1299. opacity: 0.7;
  1300. }
  1301. /* ===== 底部 ===== */
  1302. .bottom-spacer {
  1303. height: 120rpx;
  1304. }
  1305. </style>