index.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. <template>
  2. <view class="body-container">
  3. <!-- Tabbar 切换过渡页 -->
  4. <tab-transition v-if="showTabTransition" dimCode="body" />
  5. <!-- 品牌头部 -->
  6. <PageBanner theme="body"
  7. tagline="科学管理全家健康"
  8. quote="身体健康是幸福的基础" />
  9. <!-- 游客专属区块 -->
  10. <template v-if="!isLoggedIn">
  11. <LoginGuideCard
  12. title="登录后查看全家健康数据"
  13. :items="[
  14. '查看全家身体能量数据和健康报告',
  15. '记录每日健康打卡,获取身体能量',
  16. '查看专属健康商品和活动价格'
  17. ]" />
  18. <HealthTips dimension="body" />
  19. </template>
  20. <!-- 登录后:五维能量条 -->
  21. <FamilyEnergyBar
  22. v-if="isLoggedIn"
  23. dimensionCode="body"
  24. :sandboxData="sandboxData"
  25. :dualDimension="dualDimension" />
  26. <!-- 关系图谱(登录后可见) -->
  27. <FamilyRelationGraph
  28. v-if="isLoggedIn && familyMembersVisible.length > 0"
  29. dimensionCode="body"
  30. :selfId="activeChildId"
  31. :members="familyMembersVisible"
  32. :energyMap="energyMapForGraph"
  33. :intimacyMap="intimacyMapForGraph"
  34. @memberTap="goMemberDetail" />
  35. <!-- 登录后:用户快捷入口 -->
  36. <UserQuickEntry
  37. v-if="isLoggedIn"
  38. :userName="userName"
  39. roleName="家长"
  40. :currentChildName="activeChildName"
  41. :children="children"
  42. @childChanged="onChildChanged"
  43. @scrollTo="scrollToSection" />
  44. <!-- 功能入口 -->
  45. <view class="func-section" v-if="sectionVisible('func_entries')">
  46. <view class="func-grid">
  47. <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
  48. <view class="func-icon-wrap">
  49. <text class="func-icon">{{ item.icon }}</text>
  50. </view>
  51. <text class="func-label">{{ item.label }}</text>
  52. </view>
  53. </view>
  54. </view>
  55. <!-- 健康报告摘要(登录后可见) -->
  56. <view class="section" v-if="sectionVisible('health_report') && isLoggedIn">
  57. <view class="section-header">
  58. <text class="section-title">健康报告</text>
  59. <text class="section-more" @click="goToHealthReport">查看详情 ›</text>
  60. </view>
  61. <view class="report-card" v-if="latestReport">
  62. <view class="report-type-tag">{{ reportTypeLabel }}</view>
  63. <view class="report-score-row">
  64. <view class="score-circle">
  65. <text class="score-value">{{ latestReport.overallScore || '--' }}</text>
  66. <text class="score-label">综合评分</text>
  67. </view>
  68. <view class="report-meta">
  69. <view class="meta-item" v-if="latestReport.gutHealthScore">
  70. <text class="meta-label">肠道健康</text>
  71. <text class="meta-value">{{ latestReport.gutHealthScore }}</text>
  72. </view>
  73. <view class="meta-item" v-if="latestReport.nutritionScore">
  74. <text class="meta-label">营养状况</text>
  75. <text class="meta-value">{{ latestReport.nutritionScore }}</text>
  76. </view>
  77. <view class="meta-item" v-if="latestReport.gutAge">
  78. <text class="meta-label">肠道年龄</text>
  79. <text class="meta-value">{{ latestReport.gutAge }}</text>
  80. </view>
  81. </view>
  82. </view>
  83. <text class="report-date">报告日期:{{ formatDate(latestReport.reportDate) }}</text>
  84. </view>
  85. <view class="report-empty" v-else>
  86. <text class="empty-tip">暂无健康报告</text>
  87. <text class="empty-sub">规划师录入后将自动显示</text>
  88. </view>
  89. </view>
  90. <!-- ===== 精准营养(上传报告+产品+文章) ===== -->
  91. <view class="section nutrition-section" v-if="isLoggedIn">
  92. <view class="section-header">
  93. <text class="section-title">🥗 精准营养</text>
  94. </view>
  95. <!-- 上传报告入口 -->
  96. <view class="nutrition-report-card" @click="goToUploadReport">
  97. <view class="nutrition-report-left">
  98. <text class="nutrition-icon-large">📋</text>
  99. </view>
  100. <view class="nutrition-report-right">
  101. <text class="nutrition-title">上传健康报告</text>
  102. <text class="nutrition-desc">上传体检报告或肠道检测报告,AI智能解读分析,获取个性化营养方案</text>
  103. <view class="nutrition-report-tags">
  104. <text class="tag">体检报告</text>
  105. <text class="tag">肠道检测</text>
  106. <text class="tag">AI解读</text>
  107. </view>
  108. </view>
  109. <text class="nutrition-arrow">›</text>
  110. </view>
  111. <!-- 菌群检测产品链接 -->
  112. <view class="nutrition-links">
  113. <text class="links-label">🔥 相关产品</text>
  114. <view class="links-row">
  115. <view class="link-item" @click="goToShopProduct('gut_flora')">
  116. <text class="link-icon">🧪</text>
  117. <view class="link-texts">
  118. <text class="link-name">肠道菌群检测</text>
  119. <text class="link-desc">了解孩子的肠道健康状况</text>
  120. </view>
  121. <text class="link-arrow">›</text>
  122. </view>
  123. <view class="link-item" @click="goToShopProduct('probiotic')">
  124. <text class="link-icon">🦠</text>
  125. <view class="link-texts">
  126. <text class="link-name">益生菌补充</text>
  127. <text class="link-desc">平衡肠道菌群,增强免疫力</text>
  128. </view>
  129. <text class="link-arrow">›</text>
  130. </view>
  131. </view>
  132. </view>
  133. <!-- 相关文章链接 -->
  134. <view class="nutrition-links">
  135. <text class="links-label">📖 相关文章</text>
  136. <view class="links-row">
  137. <view class="link-item" @click="goToArticle('gut_health')">
  138. <text class="link-icon">📝</text>
  139. <view class="link-texts">
  140. <text class="link-name">肠道菌群与儿童健康</text>
  141. <text class="link-desc">肠道菌群如何影响孩子的免疫力</text>
  142. </view>
  143. <text class="link-arrow">›</text>
  144. </view>
  145. <view class="link-item" @click="goToArticle('nutrition_guide')">
  146. <text class="link-icon">📝</text>
  147. <view class="link-texts">
  148. <text class="link-name">儿童精准营养指南</text>
  149. <text class="link-desc">科学搭配饮食,促进健康成长</text>
  150. </view>
  151. <text class="link-arrow">›</text>
  152. </view>
  153. </view>
  154. </view>
  155. </view>
  156. <!-- 维度任务 -->
  157. <DimensionTasks
  158. v-if="isLoggedIn"
  159. dimensionCode="body"
  160. :tasks="dimensionTasks"
  161. @taskClick="onTaskClick"
  162. @moreTasks="goTasks" />
  163. <!-- 维度活动 -->
  164. <DimensionActivities
  165. dimensionCode="body"
  166. :activities="dimensionActivities"
  167. @activityClick="goActivityDetail"
  168. @moreActivities="goMoreActivities" />
  169. <!-- 维度商品 -->
  170. <DimensionProducts
  171. dimensionCode="body"
  172. :products="dimensionProducts"
  173. @productClick="goProductDetail"
  174. @moreProducts="goMoreProducts" />
  175. <!-- 健康小贴士 -->
  176. <view class="section" v-if="sectionVisible('health_tips')">
  177. <view class="section-header">
  178. <text class="section-title">健康小贴士</text>
  179. </view>
  180. <view class="tips-list">
  181. <view class="tip-card" v-for="item in healthTips" :key="item.id">
  182. <text class="tip-title">{{ item.title }}</text>
  183. <text class="tip-summary">{{ item.summary }}</text>
  184. </view>
  185. </view>
  186. </view>
  187. <!-- 快捷操作按钮 -->
  188. <view class="section quick-actions" v-if="isLoggedIn">
  189. <view class="action-btn" @click="goToHealthReport">
  190. <text class="action-icon">📋</text>
  191. <text class="action-text">报告详情</text>
  192. </view>
  193. <view class="action-btn" @click="goToCheckin">
  194. <text class="action-icon">🏃</text>
  195. <text class="action-text">健康打卡</text>
  196. </view>
  197. <view class="action-btn" @click="goToNutritionProfile">
  198. <text class="action-icon">🥗</text>
  199. <text class="action-text">营养档案</text>
  200. </view>
  201. </view>
  202. <!-- 底部占位 -->
  203. <view class="bottom-spacer"></view>
  204. </view>
  205. </template>
  206. <script>
  207. import TabTransition from '../../components/tab-transition.vue'
  208. import PageBanner from '../../components/PageBanner.vue'
  209. import LoginGuideCard from '../../components/LoginGuideCard.vue'
  210. import HealthTips from '../../components/HealthTips.vue'
  211. import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
  212. import UserQuickEntry from '../../components/UserQuickEntry.vue'
  213. import DimensionTasks from '../../components/DimensionTasks.vue'
  214. import DimensionActivities from '../../components/DimensionActivities.vue'
  215. import DimensionProducts from '../../components/DimensionProducts.vue'
  216. import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
  217. import { getVisibleSections, getEnergyOverview, getChildren, getTodayTasksByCategory, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getVisibleFamilyMembers } from '../../utils/api.js'
  218. import config from '../../config.js'
  219. var BASE_URL = config.api('')
  220. const healthRequest = function(url, data) {
  221. var token = uni.getStorageSync('token')
  222. return new Promise(function(resolve, reject) {
  223. uni.request({
  224. url: BASE_URL + url,
  225. method: 'POST',
  226. data: data,
  227. header: {
  228. 'Content-Type': 'application/json',
  229. 'Authorization': token ? 'Bearer ' + token : ''
  230. },
  231. success: function(res) {
  232. if (res.data.code === 200) {
  233. resolve(res.data)
  234. } else {
  235. reject(res.data)
  236. }
  237. },
  238. fail: function(err) {
  239. reject(err)
  240. }
  241. })
  242. })
  243. }
  244. export default {
  245. components: { TabTransition, PageBanner, LoginGuideCard, HealthTips, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, FamilyRelationGraph },
  246. data() {
  247. return {
  248. isLoggedIn: false,
  249. role: null,
  250. showTabTransition: false,
  251. activeChildId: null,
  252. activeChildName: '',
  253. userName: '',
  254. children: [],
  255. familyMembersVisible: [],
  256. sandboxData: null,
  257. dualDimension: null,
  258. visibleSections: [],
  259. latestReport: null,
  260. dimensionTasks: [],
  261. dimensionActivities: [],
  262. dimensionProducts: [],
  263. funcList: [
  264. { icon: '\u{1F3C3}', label: '运动', needLogin: true, page: '' },
  265. { icon: '\u{1F957}', label: '饮食', needLogin: true, page: '' },
  266. { icon: '\u{1F634}', label: '作息', needLogin: true, page: '' },
  267. { icon: '\u{1F9A0}', label: '菌群', needLogin: true, page: '' },
  268. { icon: '\u{1F9D8}', label: '冥想', needLogin: true, page: '' },
  269. { icon: '\u{1F4CA}', label: '健康维度', needLogin: true, page: 'health-dimensions' }
  270. ],
  271. healthTips: [
  272. { id: 1, title: '儿童每日运动指南', summary: '不同年龄段儿童每天需要多少运动量?科学运动助力健康成长。' },
  273. { id: 2, title: '五行饮食搭配法则', summary: '根据五行理论合理搭配膳食,均衡营养从每一餐开始。' },
  274. { id: 3, title: '优质睡眠从小养成', summary: '帮助孩子建立规律的作息习惯,提升睡眠质量。' },
  275. { id: 4, title: '肠道菌群与免疫力', summary: '了解益生菌对儿童健康的重要作用,从内部提升抵抗力。' }
  276. ]
  277. }
  278. },
  279. computed: {
  280. reportTypeLabel: function() {
  281. if (!this.latestReport) return ''
  282. return this.latestReport.reportType === 'gut_flora' ? '肠道检测报告' : '体检报告'
  283. },
  284. energyMapForGraph: function() {
  285. var map = {}
  286. if (this.sandboxData && this.sandboxData.members) {
  287. for (var i = 0; i < this.sandboxData.members.length; i++) {
  288. var m = this.sandboxData.members[i]
  289. map[m.memberId || m.id] = {
  290. bodyScore: m.bodyScore || 0,
  291. mindScore: m.mindScore || 0,
  292. actionScore: m.actionScore || 0
  293. }
  294. }
  295. }
  296. return map
  297. },
  298. intimacyMapForGraph: function() {
  299. return {}
  300. }
  301. },
  302. onShow() {
  303. // Tabbar 切换过渡动画(最少停留 1 秒)
  304. this.showTabTransition = true
  305. var self = this
  306. setTimeout(function() {
  307. self.showTabTransition = false
  308. }, 1100)
  309. var token = uni.getStorageSync('token')
  310. this.isLoggedIn = !!token
  311. if (this.isLoggedIn) {
  312. this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  313. this.userName = uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '家长'
  314. }
  315. this.loadSectionConfig()
  316. if (this.isLoggedIn) {
  317. this.loadChildren()
  318. this.loadFamilyMembersVisible()
  319. }
  320. // 游客也能浏览活动和商品
  321. this.loadDimensionActivities()
  322. this.loadDimensionProducts()
  323. },
  324. methods: {
  325. loadSectionConfig: function() {
  326. var baseSections = ['func_entries']
  327. if (!uni.getStorageSync('token')) {
  328. this.visibleSections = baseSections
  329. return
  330. }
  331. var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  332. var self = this
  333. getVisibleSections({ pageKey: 'body', role: role }).then(function(res) {
  334. if (res.code === 200 && res.data) {
  335. var keys = res.data.map(function(s) { return s.sectionKey })
  336. var merged = baseSections.slice()
  337. keys.forEach(function(k) { if (merged.indexOf(k) === -1) merged.push(k) })
  338. self.visibleSections = merged
  339. }
  340. }).catch(function(e) {
  341. self.visibleSections = ['func_entries']
  342. })
  343. },
  344. loadChildren: function() {
  345. var self = this
  346. getChildren().then(function(res) {
  347. if (res.code === 200 && res.data) {
  348. self.children = res.data
  349. var savedChildId = uni.getStorageSync('currentChildId')
  350. var found = false
  351. for (var i = 0; i < self.children.length; i++) {
  352. if (self.children[i].childId === savedChildId) {
  353. self.activeChildId = savedChildId
  354. self.activeChildName = self.children[i].name || self.children[i].nickname || '孩子'
  355. found = true
  356. break
  357. }
  358. }
  359. if (!found && self.children.length > 0) {
  360. self.activeChildId = self.children[0].childId
  361. self.activeChildName = self.children[0].name || self.children[0].nickname || '孩子'
  362. uni.setStorageSync('currentChildId', self.activeChildId)
  363. }
  364. if (self.activeChildId) {
  365. self.loadDashboardData()
  366. }
  367. }
  368. }).catch(function(e) {
  369. console.log('获取孩子列表失败', e)
  370. })
  371. },
  372. loadDashboardData: function() {
  373. if (!this.activeChildId) return
  374. this.loadHealthReport()
  375. this.loadEnergyData()
  376. this.loadDimensionTasks()
  377. this.loadDimensionActivities()
  378. this.loadDimensionProducts()
  379. this.loadSandboxData()
  380. },
  381. loadSandboxData: function() {
  382. var self = this
  383. getFamilyEnergySandbox().then(function(res) {
  384. if (res && res.data) {
  385. self.sandboxData = res.data
  386. }
  387. }).catch(function(e) {
  388. console.log('获取沙盘数据失败', e)
  389. })
  390. },
  391. loadHealthReport: function() {
  392. var self = this
  393. healthRequest('/api/health/report/latest', { childId: this.activeChildId }).then(function(res) {
  394. if (res.data) {
  395. self.latestReport = res.data
  396. }
  397. }).catch(function(e) {
  398. console.log('获取健康报告失败', e)
  399. })
  400. },
  401. loadEnergyData: function() {
  402. var self = this
  403. getEnergyOverview(this.activeChildId).then(function(res) {
  404. if (res && res.data) {
  405. var dims = res.data.dimensions
  406. if (dims && dims.length > 0) {
  407. for (var i = 0; i < dims.length; i++) {
  408. if (dims[i].code === 'body') {
  409. self.dualDimension = dims[i]
  410. break
  411. }
  412. }
  413. }
  414. }
  415. }).catch(function(e) {
  416. console.log('获取能量概览失败', e)
  417. })
  418. },
  419. loadDimensionTasks: function() {
  420. var self = this
  421. if (!this.activeChildId) return
  422. getTodayTasksByCategory(this.activeChildId, 'body').then(function(res) {
  423. if (res && res.data) {
  424. self.dimensionTasks = Array.isArray(res.data) ? res.data.slice(0, 5) : []
  425. }
  426. }).catch(function() { self.dimensionTasks = [] })
  427. },
  428. loadDimensionActivities: function() {
  429. var self = this
  430. getActivityList({ dimensionCode: 'body', page: 1, size: 3 }).then(function(res) {
  431. if (res && res.data) {
  432. var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
  433. self.dimensionActivities = list.slice(0, 3)
  434. }
  435. }).catch(function() { self.dimensionActivities = [] })
  436. },
  437. loadDimensionProducts: function() {
  438. var self = this
  439. getProductsByDomain('body', 1, 4).then(function(res) {
  440. if (res && res.data) {
  441. var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
  442. self.dimensionProducts = list.slice(0, 4)
  443. }
  444. }).catch(function() { self.dimensionProducts = [] })
  445. },
  446. onChildChanged: function(child) {
  447. this.activeChildId = child.childId
  448. this.activeChildName = child.name || child.nickname || '孩子'
  449. this.loadDashboardData()
  450. },
  451. loadFamilyMembersVisible: function() {
  452. var self = this
  453. getVisibleFamilyMembers().then(function(res) {
  454. self.familyMembersVisible = res.data || []
  455. }).catch(function(e) {
  456. console.log('获取可见家庭成员失败', e)
  457. self.familyMembersVisible = []
  458. })
  459. },
  460. scrollToSection: function(section) {
  461. uni.pageScrollTo({ selector: '#' + section, duration: 300 })
  462. },
  463. onTaskClick: function(task) {
  464. uni.navigateTo({ url: '/pages/tasks/tasks' })
  465. },
  466. goActivityDetail: function(act) {
  467. if (act && act.id) uni.navigateTo({ url: '/pages/discover/activity-detail/activity-detail?id=' + act.id })
  468. },
  469. goMoreActivities: function() {
  470. uni.navigateTo({ url: '/pages/discover/index?type=activity&dimension=body' })
  471. },
  472. goProductDetail: function(prod) {
  473. if (prod && prod.id) uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + prod.id })
  474. },
  475. goMoreProducts: function() {
  476. uni.navigateTo({ url: '/pages/discover/index?type=product&domain=body' })
  477. },
  478. goTasks: function() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
  479. goMemberDetail: function(member) {
  480. if (!member || !member.memberId) return
  481. uni.navigateTo({
  482. url: '/pages/body/member-body-detail?childId=' + member.memberId
  483. })
  484. },
  485. sectionVisible: function(key) {
  486. return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
  487. },
  488. onFuncClick: function(item) {
  489. if (item.needLogin && !this.isLoggedIn) {
  490. uni.showModal({
  491. title: '提示',
  492. content: '请先登录后使用此功能',
  493. success: function(res) {
  494. if (res.confirm) uni.navigateTo({ url: '/pages/login/login' })
  495. }
  496. })
  497. return
  498. }
  499. if (item.page === 'health-dimensions') {
  500. if (!this.activeChildId) {
  501. uni.showToast({ title: '请先选择孩子', icon: 'none' })
  502. return
  503. }
  504. uni.navigateTo({ url: '/pages/body/health-dimensions?childId=' + this.activeChildId })
  505. return
  506. }
  507. if (item.page) uni.navigateTo({ url: item.page })
  508. },
  509. goToHealthReport: function() {
  510. if (!this.activeChildId) {
  511. uni.showToast({ title: '请先选择孩子', icon: 'none' })
  512. return
  513. }
  514. uni.navigateTo({ url: '/pages/body/health-report?childId=' + this.activeChildId })
  515. },
  516. goToCheckin: function() {
  517. if (!this.activeChildId) {
  518. uni.showToast({ title: '请先选择孩子', icon: 'none' })
  519. return
  520. }
  521. uni.navigateTo({ url: '/pages/body/checkin?childId=' + this.activeChildId })
  522. },
  523. goToNutritionProfile: function() {
  524. if (!this.activeChildId) {
  525. uni.showToast({ title: '请先选择孩子', icon: 'none' })
  526. return
  527. }
  528. uni.navigateTo({ url: '/pages/health/nutrition-profile?childId=' + this.activeChildId })
  529. },
  530. goToUploadReport: function() {
  531. // 使用当前选中孩子;如果 loadChildren 异步未完成则尝试从 storage 读取
  532. var id = this.activeChildId || uni.getStorageSync('currentChildId') || ''
  533. uni.navigateTo({ url: '/pages/health/report-upload?childId=' + id + '&from=body' })
  534. },
  535. goToShopProduct: function(type) {
  536. if (type === 'gut_flora') {
  537. uni.navigateTo({ url: '/pages/discover/index?type=product&domain=body&keyword=' + encodeURIComponent('肠道菌群检测') })
  538. } else if (type === 'probiotic') {
  539. uni.navigateTo({ url: '/pages/discover/index?type=product&domain=body&keyword=' + encodeURIComponent('益生菌') })
  540. }
  541. },
  542. goToArticle: function(topic) {
  543. if (topic === 'gut_health') {
  544. uni.navigateTo({ url: '/pages/mind/articles?tag=' + encodeURIComponent('肠道健康') })
  545. } else if (topic === 'nutrition_guide') {
  546. uni.navigateTo({ url: '/pages/mind/articles?tag=' + encodeURIComponent('精准营养') })
  547. }
  548. },
  549. formatDate: function(dateStr) {
  550. if (!dateStr) return '--'
  551. try {
  552. var d = new Date(dateStr)
  553. var y = d.getFullYear()
  554. var m = ('' + (d.getMonth() + 1)).padStart(2, '0')
  555. var day = ('' + d.getDate()).padStart(2, '0')
  556. return y + '-' + m + '-' + day
  557. } catch (e) {
  558. return dateStr
  559. }
  560. }
  561. }
  562. }
  563. </script>
  564. <style scoped>
  565. .body-container {
  566. min-height: 100vh;
  567. background: #f5f7fa;
  568. padding-bottom: 120rpx;
  569. }
  570. /* ===== 功能入口 ===== */
  571. .func-section {
  572. margin: 20rpx 30rpx;
  573. }
  574. .func-grid {
  575. display: flex;
  576. flex-direction: row;
  577. justify-content: space-between;
  578. background: #fff;
  579. border-radius: 20rpx;
  580. padding: 24rpx 16rpx;
  581. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  582. }
  583. .func-item {
  584. display: flex;
  585. flex-direction: column;
  586. align-items: center;
  587. flex: 1;
  588. }
  589. .func-icon-wrap {
  590. width: 80rpx;
  591. height: 80rpx;
  592. border-radius: 40rpx;
  593. background: linear-gradient(135deg, #D6EAF8, #EBF2FA);
  594. display: flex;
  595. align-items: center;
  596. justify-content: center;
  597. margin-bottom: 10rpx;
  598. }
  599. .func-icon {
  600. font-size: 40rpx;
  601. }
  602. .func-label {
  603. font-size: 22rpx;
  604. color: #666;
  605. font-weight: 500;
  606. }
  607. /* ===== 通用区块 ===== */
  608. .section {
  609. margin: 20rpx 30rpx;
  610. }
  611. .section-header {
  612. display: flex;
  613. flex-direction: row;
  614. align-items: center;
  615. justify-content: space-between;
  616. margin-bottom: 16rpx;
  617. }
  618. .section-title {
  619. font-size: 30rpx;
  620. font-weight: bold;
  621. color: #333;
  622. }
  623. .section-more {
  624. font-size: 24rpx;
  625. color: #5B9BD5;
  626. }
  627. /* ===== 健康报告 ===== */
  628. .report-card {
  629. background: #fff;
  630. border-radius: 20rpx;
  631. padding: 24rpx;
  632. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  633. }
  634. .report-type-tag {
  635. display: inline-block;
  636. background: #E8F5E9;
  637. color: #2E7D32;
  638. font-size: 22rpx;
  639. padding: 4rpx 16rpx;
  640. border-radius: 20rpx;
  641. margin-bottom: 16rpx;
  642. }
  643. .report-score-row {
  644. display: flex;
  645. flex-direction: row;
  646. align-items: center;
  647. }
  648. .score-circle {
  649. width: 140rpx;
  650. height: 140rpx;
  651. border-radius: 70rpx;
  652. background: linear-gradient(135deg, #E3F2FD, #BBDEFB);
  653. display: flex;
  654. flex-direction: column;
  655. align-items: center;
  656. justify-content: center;
  657. margin-right: 24rpx;
  658. }
  659. .score-value {
  660. font-size: 48rpx;
  661. font-weight: bold;
  662. color: #1565C0;
  663. }
  664. .score-label {
  665. font-size: 20rpx;
  666. color: #64B5F6;
  667. }
  668. .report-meta {
  669. flex: 1;
  670. }
  671. .meta-item {
  672. display: flex;
  673. flex-direction: row;
  674. align-items: center;
  675. margin-bottom: 12rpx;
  676. }
  677. .meta-label {
  678. font-size: 24rpx;
  679. color: #666;
  680. width: 140rpx;
  681. }
  682. .meta-value {
  683. font-size: 26rpx;
  684. color: #333;
  685. font-weight: 500;
  686. }
  687. .report-date {
  688. font-size: 22rpx;
  689. color: #bbb;
  690. margin-top: 16rpx;
  691. }
  692. .report-empty {
  693. background: #fff;
  694. border-radius: 20rpx;
  695. padding: 40rpx;
  696. text-align: center;
  697. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  698. }
  699. .empty-tip {
  700. font-size: 28rpx;
  701. color: #999;
  702. display: block;
  703. }
  704. .empty-sub {
  705. font-size: 24rpx;
  706. color: #ccc;
  707. display: block;
  708. margin-top: 8rpx;
  709. }
  710. /* ===== 健康小贴士 ===== */
  711. .tips-list {
  712. display: flex;
  713. flex-direction: column;
  714. }
  715. .tip-card {
  716. background: #fff;
  717. border-radius: 20rpx;
  718. padding: 24rpx;
  719. margin-bottom: 16rpx;
  720. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  721. }
  722. .tip-title {
  723. font-size: 28rpx;
  724. font-weight: bold;
  725. color: #333;
  726. display: block;
  727. margin-bottom: 10rpx;
  728. }
  729. .tip-summary {
  730. font-size: 24rpx;
  731. color: #888;
  732. line-height: 1.5;
  733. display: block;
  734. }
  735. /* ===== 快捷操作 ===== */
  736. .quick-actions {
  737. display: flex;
  738. flex-direction: row;
  739. margin-top: 30rpx;
  740. }
  741. .action-btn {
  742. flex: 1;
  743. background: #fff;
  744. border-radius: 16rpx;
  745. padding: 24rpx;
  746. display: flex;
  747. flex-direction: column;
  748. align-items: center;
  749. margin-right: 20rpx;
  750. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  751. }
  752. .action-btn:last-child {
  753. margin-right: 0;
  754. }
  755. .action-icon {
  756. font-size: 48rpx;
  757. margin-bottom: 8rpx;
  758. }
  759. .action-text {
  760. font-size: 26rpx;
  761. color: #666;
  762. }
  763. /* ===== 底部 ===== */
  764. .bottom-spacer {
  765. height: 120rpx;
  766. }
  767. .func-item:active, .action-btn:active {
  768. opacity: 0.7;
  769. }
  770. /* ===== 精准营养 ===== */
  771. .nutrition-section {
  772. margin-top: 20rpx;
  773. }
  774. .nutrition-report-card {
  775. display: flex;
  776. flex-direction: row;
  777. align-items: center;
  778. background: linear-gradient(135deg, #E8F5E9, #F1F8E9);
  779. border-radius: 20rpx;
  780. padding: 28rpx 24rpx;
  781. margin-bottom: 20rpx;
  782. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  783. }
  784. .nutrition-report-left {
  785. width: 100rpx;
  786. height: 100rpx;
  787. background: rgba(255,255,255,0.7);
  788. border-radius: 20rpx;
  789. display: flex;
  790. align-items: center;
  791. justify-content: center;
  792. margin-right: 20rpx;
  793. flex-shrink: 0;
  794. }
  795. .nutrition-icon-large {
  796. font-size: 52rpx;
  797. }
  798. .nutrition-report-right {
  799. flex: 1;
  800. }
  801. .nutrition-title {
  802. font-size: 30rpx;
  803. font-weight: bold;
  804. color: #2E7D32;
  805. display: block;
  806. margin-bottom: 6rpx;
  807. }
  808. .nutrition-desc {
  809. font-size: 22rpx;
  810. color: #558B2F;
  811. line-height: 1.5;
  812. display: block;
  813. margin-bottom: 10rpx;
  814. }
  815. .nutrition-report-tags {
  816. display: flex;
  817. flex-direction: row;
  818. gap: 10rpx;
  819. }
  820. .nutrition-report-tags .tag {
  821. font-size: 20rpx;
  822. color: #33691E;
  823. background: rgba(255,255,255,0.6);
  824. padding: 4rpx 14rpx;
  825. border-radius: 20rpx;
  826. }
  827. .nutrition-arrow {
  828. font-size: 40rpx;
  829. color: #A5D6A7;
  830. margin-left: 12rpx;
  831. }
  832. .nutrition-links {
  833. margin-bottom: 20rpx;
  834. }
  835. .links-label {
  836. font-size: 26rpx;
  837. font-weight: bold;
  838. color: #333;
  839. display: block;
  840. margin-bottom: 12rpx;
  841. }
  842. .links-row {
  843. display: flex;
  844. flex-direction: row;
  845. gap: 16rpx;
  846. }
  847. .link-item {
  848. flex: 1;
  849. display: flex;
  850. flex-direction: row;
  851. align-items: center;
  852. background: #fff;
  853. border-radius: 16rpx;
  854. padding: 20rpx 18rpx;
  855. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
  856. gap: 12rpx;
  857. }
  858. .link-item:active {
  859. opacity: 0.7;
  860. }
  861. .link-icon {
  862. font-size: 36rpx;
  863. flex-shrink: 0;
  864. }
  865. .link-texts {
  866. flex: 1;
  867. }
  868. .link-name {
  869. font-size: 24rpx;
  870. font-weight: 500;
  871. color: #333;
  872. display: block;
  873. margin-bottom: 4rpx;
  874. }
  875. .link-desc {
  876. font-size: 20rpx;
  877. color: #999;
  878. display: block;
  879. }
  880. .link-arrow {
  881. font-size: 32rpx;
  882. color: #CCC;
  883. flex-shrink: 0;
  884. }
  885. </style>