index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. <template>
  2. <view class="wisdom-container">
  3. <!-- Tabbar 切换过渡页 -->
  4. <tab-transition v-if="showTabTransition" dimCode="wisdom" />
  5. <!-- 1. 品牌头部 -->
  6. <PageBanner theme="wisdom"
  7. tagline="学以启智 · 慧及未来"
  8. quote="知者不惑,仁者不忧" />
  9. <!-- 2. 游客专属区块 -->
  10. <template v-if="!isLoggedIn">
  11. <LoginGuideCard
  12. title="登录后查看全家智慧成长数据"
  13. :items="[
  14. '查看全家认知能量数据和测评报告',
  15. '记录每日成长,获取智慧能量',
  16. '参与认知训练,提升综合能力'
  17. ]" />
  18. </template>
  19. <!-- 3. 五维能量条(登录后) -->
  20. <FamilyEnergyBar
  21. v-if="isLoggedIn"
  22. dimensionCode="wisdom"
  23. :sandboxData="sandboxData"
  24. :dualDimension="dualDimension" />
  25. <!-- 4. 认知雷达图(登录后) -->
  26. <view class="section" v-if="isLoggedIn && cognitiveReport">
  27. <view class="section-header">
  28. <text class="section-title">认知雷达</text>
  29. <text class="section-more" @click="goCognitiveReport">查看详情 ›</text>
  30. </view>
  31. <RadarChart
  32. :dimensions="cognitiveDimensions"
  33. :scores="cognitiveScores"
  34. :childName="activeChildName"
  35. fillColor="#6366F1"
  36. gridColor="#E0E7FF"
  37. labelColor="#3730A3" />
  38. </view>
  39. <!-- 5. 家庭成员关系图谱(登录后) -->
  40. <FamilyRelationGraph
  41. v-if="isLoggedIn && familyMembersVisible.length > 0"
  42. dimensionCode="wisdom"
  43. :selfId="activeChildId"
  44. :members="familyMembersVisible"
  45. :energyMap="energyMapForGraph"
  46. :intimacyMap="intimacyMapForGraph"
  47. @memberTap="goMemberDetail" />
  48. <!-- 6. 用户快捷入口 -->
  49. <UserQuickEntry
  50. v-if="isLoggedIn"
  51. :userName="userName"
  52. roleName="家长"
  53. :currentChildName="activeChildName"
  54. :children="children"
  55. @childChanged="onChildChanged"
  56. @scrollTo="scrollToSection" />
  57. <!-- 7. 功能入口 -->
  58. <view class="func-section" v-if="sectionVisible('func_entries')">
  59. <view class="func-grid">
  60. <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
  61. <view class="func-icon-wrap">
  62. <text class="func-icon">{{ item.icon }}</text>
  63. </view>
  64. <text class="func-label">{{ item.label }}</text>
  65. </view>
  66. </view>
  67. </view>
  68. <!-- 8. 维度任务 -->
  69. <DimensionTasks
  70. v-if="isLoggedIn"
  71. dimensionCode="wisdom"
  72. :tasks="dimensionTasks"
  73. @taskClick="onTaskClick"
  74. @moreTasks="goTasks" />
  75. <!-- 9. 维度活动 -->
  76. <DimensionActivities
  77. dimensionCode="wisdom"
  78. :activities="dimensionActivities"
  79. :isLoggedIn="isLoggedIn"
  80. @activityClick="goActivityDetail"
  81. @moreActivities="goMoreActivities" />
  82. <!-- 10. 维度商品 -->
  83. <DimensionProducts
  84. dimensionCode="wisdom"
  85. :products="dimensionProducts"
  86. @productClick="goProductDetail"
  87. @moreProducts="goMoreProducts" />
  88. <!-- 11. 推荐阅读 -->
  89. <DimensionArticles
  90. v-if="sectionVisible('recommended_reading')"
  91. dimensionCode="wisdom"
  92. :articles="wisdomArticles"
  93. :isLoggedIn="isLoggedIn"
  94. @articleClick="goArticleDetail"
  95. @moreArticles="goMoreArticles" />
  96. <!-- 12. 智慧小贴士 -->
  97. <view class="section" v-if="sectionVisible('wisdom_tips')">
  98. <view class="section-header">
  99. <text class="section-title">智慧小贴士</text>
  100. </view>
  101. <view class="tips-list">
  102. <view class="tip-card" v-for="item in wisdomTips" :key="item.id">
  103. <text class="tip-title">{{ item.title }}</text>
  104. <text class="tip-summary">{{ item.summary }}</text>
  105. </view>
  106. </view>
  107. </view>
  108. <!-- 13. 快捷操作按钮 -->
  109. <view class="section quick-actions" v-if="isLoggedIn">
  110. <view class="action-btn" @click="goCognitiveReport">
  111. <text class="action-icon">📊</text>
  112. <text class="action-text">认知报告</text>
  113. </view>
  114. <view class="action-btn" @click="goTrainingHub">
  115. <text class="action-icon">🧠</text>
  116. <text class="action-text">认知训练</text>
  117. </view>
  118. <view class="action-btn" @click="goAssessment">
  119. <text class="action-icon">📋</text>
  120. <text class="action-text">测评预约</text>
  121. </view>
  122. <view class="action-btn" @click="goHome">
  123. <text class="action-icon">🏠</text>
  124. <text class="action-text">返回首页</text>
  125. </view>
  126. </view>
  127. <!-- 底部占位 -->
  128. <view class="bottom-spacer"></view>
  129. <AIFloatingAvatar />
  130. </view>
  131. </template>
  132. <script>
  133. import TabTransition from '../../components/tab-transition.vue'
  134. import PageBanner from '../../components/PageBanner.vue'
  135. import LoginGuideCard from '../../components/LoginGuideCard.vue'
  136. import RadarChart from '../../components/RadarChart.vue'
  137. import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
  138. import UserQuickEntry from '../../components/UserQuickEntry.vue'
  139. import DimensionTasks from '../../components/DimensionTasks.vue'
  140. import DimensionActivities from '../../components/DimensionActivities.vue'
  141. import DimensionProducts from '../../components/DimensionProducts.vue'
  142. import DimensionArticles from '../../components/DimensionArticles.vue'
  143. import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
  144. import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
  145. import { getVisibleSections, getChildren, getTodayTasksByCategory, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getAssessmentLatestResult, getFeaturedArticles } from '../../utils/api.js'
  146. export default {
  147. components: { TabTransition, PageBanner, LoginGuideCard, RadarChart, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, FamilyRelationGraph, AIFloatingAvatar },
  148. data() {
  149. return {
  150. isLoggedIn: false,
  151. role: null,
  152. showTabTransition: true,
  153. activeChildId: null,
  154. activeChildName: '',
  155. userName: '',
  156. children: [],
  157. familyMembersVisible: [],
  158. sandboxData: null,
  159. dualDimension: null,
  160. visibleSections: [],
  161. cognitiveReport: null,
  162. dimensionTasks: [],
  163. dimensionActivities: [],
  164. dimensionProducts: [],
  165. articles: [],
  166. wisdomTips: [],
  167. funcList: [
  168. { icon: '\u{1F4CA}', label: '认知报告', needLogin: true, page: '/pages/wisdom-detail/cognitive-report' },
  169. { icon: '\u{270F}\uFE0F', label: '自己出题', needLogin: true, page: '/pages/wisdom-detail/self-quiz' },
  170. { icon: '\u{1F9E0}', label: '训练中心', needLogin: true, page: '/pages/wisdom-detail/training-hub' },
  171. { icon: '\u{1F3AF}', label: '测评预约', needLogin: false, page: '/pages/assessment/apply' },
  172. { icon: '\u{1F4D6}', label: '阅读天地', needLogin: false, page: '/pages/mind/articles' }
  173. ]
  174. }
  175. },
  176. computed: {
  177. energyMapForGraph: function() {
  178. var map = {}
  179. if (this.sandboxData && this.sandboxData.members) {
  180. for (var i = 0; i < this.sandboxData.members.length; i++) {
  181. var m = this.sandboxData.members[i]
  182. map[m.memberId || m.id] = {
  183. bodyScore: m.bodyScore || 0,
  184. mindScore: m.mindScore || 0,
  185. actionScore: m.actionScore || 0,
  186. wisdomScore: m.wisdomScore || 0
  187. }
  188. }
  189. }
  190. return map
  191. },
  192. intimacyMapForGraph: function() {
  193. return {}
  194. },
  195. wisdomArticles: function() {
  196. if (!this.articles || this.articles.length === 0) return []
  197. var result = []
  198. for (var i = 0; i < this.articles.length; i++) {
  199. var a = this.articles[i]
  200. if (a.relatedDimensions && a.relatedDimensions.indexOf('wisdom') !== -1) {
  201. result.push(a)
  202. }
  203. }
  204. if (result.length === 0) return this.articles.slice(0, 5)
  205. return result.slice(0, 5)
  206. },
  207. cognitiveDimensions: function() {
  208. return [
  209. { label: '感知能力', key: 'perceptionScore' },
  210. { label: '专注力', key: 'focusScore' },
  211. { label: '记忆力', key: 'memoryScore' },
  212. { label: '逻辑思维', key: 'logicScore' },
  213. { label: '空间思维', key: 'spatialScore' },
  214. { label: '加工速度', key: 'processingSpeedScore' }
  215. ]
  216. },
  217. cognitiveScores: function() {
  218. if (!this.cognitiveReport) return []
  219. var dims = this.cognitiveDimensions
  220. var scores = []
  221. for (var i = 0; i < dims.length; i++) {
  222. scores.push(this.cognitiveReport[dims[i].key] || 0)
  223. }
  224. return scores
  225. }
  226. },
  227. onShow() {
  228. // Tabbar 切换过渡动画(最少停留 1 秒)
  229. this.showTabTransition = true
  230. var self = this
  231. setTimeout(function() {
  232. self.showTabTransition = false
  233. }, 1800)
  234. var token = uni.getStorageSync('token')
  235. this.isLoggedIn = !!token
  236. if (this.isLoggedIn) {
  237. this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  238. this.userName = uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '家长'
  239. }
  240. this.loadSectionConfig()
  241. if (this.isLoggedIn) {
  242. this.loadChildren()
  243. this.loadFamilyMembersVisible()
  244. this.loadCognitiveReport()
  245. }
  246. this.loadDimensionActivities()
  247. this.loadDimensionProducts()
  248. // 游客也能浏览文章
  249. this.loadFeaturedArticles()
  250. },
  251. methods: {
  252. loadSectionConfig: function() {
  253. var baseSections = ['func_entries', 'wisdom_tips']
  254. if (!uni.getStorageSync('token')) {
  255. this.visibleSections = baseSections
  256. return
  257. }
  258. var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  259. var self = this
  260. getVisibleSections({ pageKey: 'wisdom', role: role }).then(function(res) {
  261. if (res.code === 200 && res.data) {
  262. var keys = res.data.map(function(s) { return s.sectionKey })
  263. var merged = baseSections.slice()
  264. keys.forEach(function(k) { if (merged.indexOf(k) === -1) merged.push(k) })
  265. self.visibleSections = merged
  266. }
  267. }).catch(function(e) {
  268. self.visibleSections = ['func_entries', 'wisdom_tips']
  269. })
  270. },
  271. loadChildren: function() {
  272. var self = this
  273. getChildren().then(function(res) {
  274. if (res.code === 200 && res.data) {
  275. self.children = res.data
  276. if (self.children.length > 0) {
  277. var activeId = uni.getStorageSync('currentChildId')
  278. if (activeId) {
  279. for (var i = 0; i < self.children.length; i++) {
  280. if (self.children[i].id == activeId) {
  281. self.activeChildId = activeId
  282. self.activeChildName = self.children[i].nickname || self.children[i].name || ''
  283. break
  284. }
  285. }
  286. }
  287. if (!self.activeChildId) {
  288. self.activeChildId = self.children[0].id
  289. self.activeChildName = self.children[0].nickname || self.children[0].name || ''
  290. uni.setStorageSync('currentChildId', self.activeChildId)
  291. }
  292. }
  293. // Load cognitive report AFTER activeChildId is confirmed set
  294. self.loadCognitiveReport()
  295. }
  296. }).catch(function(e) {})
  297. },
  298. loadFamilyMembersVisible: function() {
  299. var self = this
  300. getFamilyEnergySandbox().then(function(res) {
  301. if (res && res.data) {
  302. self.sandboxData = res.data.sandboxData || null
  303. self.dualDimension = res.data.dualDimension || null
  304. if (res.data.members) {
  305. self.familyMembersVisible = res.data.members
  306. }
  307. }
  308. }).catch(function(e) {})
  309. },
  310. loadCognitiveReport: function() {
  311. var self = this
  312. var childId = this.activeChildId
  313. if (!childId) return
  314. getAssessmentLatestResult(childId).then(function(res) {
  315. if (res.code === 200 && res.data) {
  316. self.cognitiveReport = res.data
  317. }
  318. }).catch(function(e) {})
  319. },
  320. onChildChanged: function(childId) {
  321. this.activeChildId = childId
  322. uni.setStorageSync('currentChildId', childId)
  323. for (var i = 0; i < this.children.length; i++) {
  324. if (this.children[i].id == childId) {
  325. this.activeChildName = this.children[i].nickname || this.children[i].name || ''
  326. break
  327. }
  328. }
  329. this.loadFamilyMembersVisible()
  330. this.loadCognitiveReport()
  331. },
  332. loadDimensionTasks: function() {
  333. var self = this
  334. var childId = this.activeChildId
  335. if (!childId) return
  336. getTodayTasksByCategory(childId, 'wisdom').then(function(res) {
  337. if (res && res.data) {
  338. self.dimensionTasks = res.data.slice(0, 5)
  339. }
  340. }).catch(function(e) {})
  341. },
  342. loadDimensionActivities: function() {
  343. var self = this
  344. getActivityList({ dimensionCode: 'wisdom', page: 1, size: 5 }).then(function(res) {
  345. if (res && res.data) {
  346. self.dimensionActivities = res.data.records || res.data
  347. }
  348. }).catch(function(e) {})
  349. },
  350. loadDimensionProducts: function() {
  351. var self = this
  352. getProductsByDomain('wisdom', 1, 4).then(function(res) {
  353. if (res && res.data) {
  354. self.dimensionProducts = res.data.records || res.data
  355. }
  356. }).catch(function(e) {})
  357. },
  358. sectionVisible: function(key) {
  359. return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
  360. },
  361. onFuncClick: function(item) {
  362. if (item.needLogin && !this.isLoggedIn) {
  363. uni.showModal({
  364. title: '提示',
  365. content: '请先登录后使用此功能',
  366. success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
  367. })
  368. return
  369. }
  370. if (item.page) {
  371. var nativeTabs = ['/pages/tasks/tasks', '/pages/index/index', '/pages/body/index', '/pages/mind/index', '/pages/action/index', '/pages/wisdom/index', '/pages/profile/profile']
  372. if (nativeTabs.indexOf(item.page) !== -1) {
  373. uni.switchTab({ url: item.page })
  374. } else {
  375. uni.navigateTo({ url: item.page })
  376. }
  377. }
  378. },
  379. scrollToSection: function(section) {},
  380. goMemberDetail: function(memberId) {
  381. uni.navigateTo({ url: '/pages/wisdom-detail/member-wisdom-detail?memberId=' + memberId })
  382. },
  383. goCognitiveReport: function() {
  384. uni.navigateTo({ url: '/pages/wisdom-detail/cognitive-report' })
  385. },
  386. goSelfQuiz: function() {
  387. uni.navigateTo({ url: '/pages/wisdom-detail/self-quiz' })
  388. },
  389. goTrainingHub: function() {
  390. uni.navigateTo({ url: '/pages/wisdom-detail/training-hub' })
  391. },
  392. goAssessment: function() {
  393. uni.navigateTo({ url: '/pages/assessment/apply' })
  394. },
  395. goHome: function() {
  396. uni.switchTab({ url: '/pages/index/index' })
  397. },
  398. onTaskClick: function(task) {},
  399. goTasks: function() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
  400. goActivityDetail: function(id) {},
  401. goMoreActivities: function() {
  402. uni.navigateTo({ url: '/pages/activity/index' })
  403. },
  404. goActivityDetail: function(act) {
  405. if (act && act.id) uni.navigateTo({ url: '/pages/discover-detail/activity-detail/activity-detail?id=' + act.id })
  406. },
  407. goProductDetail: function(prod) {
  408. if (prod && prod.id) {
  409. var token = uni.getStorageSync('token')
  410. if (token) {
  411. uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + prod.id })
  412. } else {
  413. uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/wisdom/index') })
  414. }
  415. }
  416. },
  417. goMoreProducts: function() {
  418. uni.navigateTo({ url: '/pages/shop/index' })
  419. },
  420. loadFeaturedArticles: function() {
  421. var self = this
  422. getFeaturedArticles({ size: 5, dimensionCode: 'wisdom' }).then(function(res) {
  423. if (res.code === 200 && res.data) {
  424. var gradientColors = [
  425. 'linear-gradient(135deg, #6366F1, #818CF8)',
  426. 'linear-gradient(135deg, #8B5CF6, #A78BFA)',
  427. 'linear-gradient(135deg, #5B9BD5, #8FC5E8)',
  428. 'linear-gradient(135deg, #10B981, #34D399)',
  429. 'linear-gradient(135deg, #F97316, #FB923C)'
  430. ]
  431. var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
  432. self.articles = list.map(function(item, index) {
  433. return {
  434. id: item.id,
  435. category: item.categoryName || '智慧文章',
  436. categoryColor: gradientColors[index % gradientColors.length],
  437. title: item.title || '',
  438. summary: item.summary || '',
  439. views: item.viewCount ? String(item.viewCount) : '0',
  440. likes: '0',
  441. date: item.publishedAt ? item.publishedAt.slice(0, 10) : '',
  442. relatedDimensions: item.relatedDimensions || []
  443. }
  444. })
  445. }
  446. }).catch(function() {})
  447. },
  448. goMoreArticles: function() {
  449. uni.navigateTo({ url: '/pages/mind/articles' })
  450. },
  451. goArticleDetail: function(article) {
  452. uni.navigateTo({ url: '/pages/mind-detail/article-detail?id=' + (article.id || article) })
  453. }
  454. }
  455. }
  456. </script>
  457. <style scoped>
  458. .wisdom-container {
  459. min-height: 100vh;
  460. background: #f5f7fa;
  461. padding-bottom: 120rpx;
  462. }
  463. .func-section {
  464. margin: -40rpx 20rpx 0;
  465. position: relative;
  466. z-index: 2;
  467. }
  468. .func-grid {
  469. background: #fff;
  470. border-radius: 24rpx;
  471. padding: 30rpx 16rpx;
  472. display: flex;
  473. box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
  474. }
  475. .func-item {
  476. flex: 1;
  477. display: flex;
  478. flex-direction: column;
  479. align-items: center;
  480. }
  481. .func-item:active {
  482. opacity: 0.7;
  483. }
  484. .func-icon-wrap {
  485. width: 88rpx;
  486. height: 88rpx;
  487. border-radius: 50%;
  488. background: #FFF8E1;
  489. display: flex;
  490. align-items: center;
  491. justify-content: center;
  492. margin-bottom: 10rpx;
  493. }
  494. .func-icon {
  495. font-size: 40rpx;
  496. }
  497. .func-label {
  498. font-size: 24rpx;
  499. color: #333;
  500. font-weight: 500;
  501. }
  502. /* ===== Section ===== */
  503. .section {
  504. margin: 20rpx 20rpx;
  505. }
  506. .section-header {
  507. display: flex;
  508. justify-content: space-between;
  509. align-items: center;
  510. margin-bottom: 20rpx;
  511. }
  512. .section-title {
  513. font-size: 30rpx;
  514. font-weight: bold;
  515. color: #333;
  516. }
  517. .section-more {
  518. font-size: 24rpx;
  519. color: #999;
  520. }
  521. /* ===== 智慧小贴士 ===== */
  522. .tips-list {
  523. display: flex;
  524. flex-direction: column;
  525. }
  526. .tip-card {
  527. background: #fff;
  528. border-radius: 16rpx;
  529. padding: 24rpx 20rpx;
  530. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  531. margin-bottom: 16rpx;
  532. }
  533. .tip-title {
  534. font-size: 28rpx;
  535. font-weight: bold;
  536. color: #333;
  537. display: block;
  538. margin-bottom: 8rpx;
  539. }
  540. .tip-summary {
  541. font-size: 24rpx;
  542. color: #888;
  543. line-height: 1.5;
  544. display: block;
  545. }
  546. /* ===== 快捷操作按钮 ===== */
  547. .quick-actions {
  548. display: flex;
  549. flex-direction: row;
  550. flex-wrap: wrap;
  551. justify-content: space-between;
  552. }
  553. .action-btn {
  554. width: 48%;
  555. background: #fff;
  556. border-radius: 16rpx;
  557. padding: 24rpx 0;
  558. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  559. display: flex;
  560. flex-direction: column;
  561. align-items: center;
  562. margin-bottom: 20rpx;
  563. }
  564. .action-btn:active {
  565. opacity: 0.8;
  566. }
  567. .action-icon {
  568. font-size: 40rpx;
  569. margin-bottom: 10rpx;
  570. }
  571. .action-text {
  572. font-size: 26rpx;
  573. color: #333;
  574. font-weight: 500;
  575. }
  576. /* ===== 底部占位 ===== */
  577. .bottom-spacer {
  578. height: 120rpx;
  579. }
  580. </style>