index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. <template>
  2. <view class="body-container">
  3. <!-- Tabbar 切换过渡页 -->
  4. <tab-transition v-if="showTabTransition" dimCode="body" ref="tabTransition" @close="showTabTransition = false" />
  5. <!-- 品牌头部 -->
  6. <PageBanner theme="body"
  7. tagline="科学管理全家健康"
  8. quote="身体健康是幸福的基础" />
  9. <!-- 品牌定位语(登录前显示) -->
  10. <view class="brand-strap" v-if="!isLoggedIn">
  11. <text class="brand-strap-text">科学管理全家健康数据</text>
  12. </view>
  13. <!-- 雷达图(始终显示,无用户数据时仅显示人群平均值) -->
  14. <view class="section">
  15. <view class="section-header">
  16. <text class="section-title">健康指标</text>
  17. <text class="section-sub">身体健康各项指标,分数越高代表越健康</text>
  18. <text class="avg-badge" v-if="!dimensionData">同龄同性平均值 · 上传健康报告后显示个人数据</text>
  19. </view>
  20. <RadarChart
  21. v-if="radarDimensions && radarDimensions.length >= 3"
  22. :dimensions="radarDimensions"
  23. :scores="radarScores"
  24. :avg-scores="radarAvgScores"
  25. fillColor="#FF8C42"
  26. gridColor="#FED7AA"
  27. labelColor="#9A3412"
  28. :width="580"
  29. :height="580"
  30. @dimensionClick="goDimensionDetail" />
  31. </view>
  32. <!-- 家庭成员选择条 -->
  33. <FamilyMemberStrip
  34. v-if="currentView === 'self' && isLoggedIn"
  35. :members="familyMembersVisible"
  36. :selectedMemberId="selectedMemberId"
  37. @select="onFamilyMemberSelect"
  38. @removed="loadFamilyMembersVisible" />
  39. <!-- 游客引导语(每月更换) -->
  40. <view class="monthly-guidance" v-if="!isLoggedIn">
  41. <text class="guidance-icon">🌿</text>
  42. <text class="guidance-text">{{ monthlyGuidance }}</text>
  43. </view>
  44. <!-- 登录后:五维能量条 -->
  45. <FamilyEnergyBar
  46. v-if="isLoggedIn"
  47. dimensionCode="body"
  48. :sandboxData="sandboxData"
  49. :dualDimension="dualDimension" />
  50. <!-- 健康七维 -->
  51. <dimension-sub-dims
  52. v-if="isLoggedIn && healthSubDims.length > 0"
  53. title="🌿 健康七维"
  54. subtitle="身体各项健康指标,分数越高代表越健康"
  55. accent-color="#FF8C42"
  56. :dims="healthSubDims" />
  57. <!-- 功能入口 -->
  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. <!-- ===== 精准营养(上传报告+产品+文章) ===== -->
  69. <view class="section nutrition-section" v-if="isLoggedIn">
  70. <view class="section-header">
  71. <text class="section-title">🥗 精准营养</text>
  72. </view>
  73. <!-- 上传报告入口 -->
  74. <view class="nutrition-report-card" @click="goToUploadReport">
  75. <view class="nutrition-report-left">
  76. <text class="nutrition-icon-large">📋</text>
  77. </view>
  78. <view class="nutrition-report-right">
  79. <text class="nutrition-title">上传健康报告</text>
  80. <text class="nutrition-desc">上传体检报告或肠道检测报告,解读分析,获取个性化营养方案</text>
  81. <view class="nutrition-report-tags">
  82. <text class="tag">体检报告</text>
  83. <text class="tag">肠道检测</text>
  84. <text class="tag">解读</text>
  85. </view>
  86. </view>
  87. <text class="nutrition-arrow">›</text>
  88. </view>
  89. </view>
  90. <!-- 身维度介绍卡片(登录后显示) -->
  91. <DimensionIntroCard dimCode="body" v-if="isLoggedIn" learn-more-url="/pages/body-detail/dimension-intro" />
  92. <!-- 底部占位 -->
  93. <view class="bottom-spacer"></view>
  94. <FloatingAvatar />
  95. </view>
  96. </template>
  97. <script>
  98. import TabTransition from '../../components/tab-transition.vue'
  99. import PageBanner from '../../components/PageBanner.vue'
  100. import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
  101. import RadarChart from '../../components/RadarChart.vue'
  102. import DimensionSubDims from '../../components/DimensionSubDims.vue'
  103. import FloatingAvatar from '../../components/FloatingAvatar.vue'
  104. import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
  105. import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
  106. import { getVisibleSections, getEnergyOverview, getChildren, getFamilyEnergySandbox, getEnergySandbox, getFamilyMemberList, getDimensionOverview } from '../../utils/api.js'
  107. export default {
  108. components: { TabTransition, PageBanner, FamilyEnergyBar, RadarChart, DimensionSubDims, FloatingAvatar, FamilyMemberStrip, DimensionIntroCard },
  109. data() {
  110. return {
  111. isLoggedIn: false,
  112. role: null,
  113. showTabTransition: true,
  114. activeChildId: null,
  115. selectedMemberId: null,
  116. activeChildName: '',
  117. userName: '',
  118. children: [],
  119. familyMembersVisible: [],
  120. // 接口数据是否已检查(避免"暂无数据"闪烁)
  121. childrenChecked: false,
  122. membersChecked: false,
  123. healthSubDims: [],
  124. sandboxData: null,
  125. currentView: 'self', // 'self' | 'family'
  126. dualDimension: null,
  127. visibleSections: [],
  128. dimensionData: null,
  129. /** Mock 平均值(7维):生长发育/睡眠质量/视力健康/免疫力/营养均衡/肠胃健康/运动活力 */
  130. radarAvgScores: [72, 68, 75, 70, 73, 69, 71],
  131. funcList: [
  132. { icon: '\u{1F3C3}', label: '运动', needLogin: true, page: '/pages/body/exercise-record' },
  133. { icon: '\u{1F957}', label: '饮食', needLogin: true, page: '/pages/diet/index' },
  134. { icon: '\u{1F634}', label: '作息', needLogin: true, page: '/pages/body/sleep-record' },
  135. { icon: '\u{1F9D8}', label: '冥想', needLogin: true, page: '/pages/health/meditation-index' },
  136. { icon: '\u{1F4CA}', label: '舌诊', needLogin: true, page: '/pages/health/report-upload?reportType=tongue' },
  137. // 问答功能已屏蔽: { icon: '\u{1F3E5}', label: '健康管家', needLogin: true, page: '/pages/ai/chat?mode=butler&fromPage=pages/body/index' },
  138. { icon: '\u{1F9EC}', label: '健康维度', needLogin: true, page: 'health-dimensions' }
  139. ]
  140. }
  141. },
  142. computed: {
  143. /** 七维雷达图:维度标签(API无数据时使用默认标签) */
  144. radarDimensions: function() {
  145. var dims = this.dimensionData && this.dimensionData.dimensions
  146. if (dims && dims.length > 0) {
  147. var result = []
  148. for (var i = 0; i < dims.length; i++) {
  149. var d = dims[i]
  150. if (!d) continue
  151. result.push({ label: d.label || '', key: d.dimension || '' })
  152. }
  153. return result
  154. }
  155. // 无数据时使用默认七维标签
  156. return [
  157. { label: '生长发育', key: 'growth' },
  158. { label: '睡眠质量', key: 'sleep' },
  159. { label: '视力健康', key: 'vision' },
  160. { label: '免疫力', key: 'immunity' },
  161. { label: '营养均衡', key: 'nutrition' },
  162. { label: '肠胃健康', key: 'gut' },
  163. { label: '运动活力', key: 'exercise' }
  164. ]
  165. },
  166. /** 七维雷达图:分值(无数据时返回空数组,仅显示平均值) */
  167. radarScores: function() {
  168. var dims = this.dimensionData && this.dimensionData.dimensions
  169. if (!dims) return []
  170. var result = []
  171. for (var i = 0; i < dims.length; i++) {
  172. var d = dims[i]
  173. if (!d) { result.push(0); continue }
  174. var s = d.score
  175. if (typeof s !== 'number' || isNaN(s) || s === null) s = 0
  176. result.push(s)
  177. }
  178. return result
  179. },
  180. /** 每月引导语 */
  181. monthlyGuidance: function() {
  182. var month = new Date().getMonth()
  183. var messages = [
  184. '一月:新年新健康,从一次体检开始',
  185. '二月:春暖花开,适合调整作息',
  186. '三月:植树节到,守护呼吸健康',
  187. '四月:踏青正当时,关注视力保护',
  188. '五月:劳动节后,重视营养均衡',
  189. '六月:夏日来临,关注肠胃健康',
  190. '七月:暑期到来,规律运动不可少',
  191. '八月:秋前调养,提升免疫力',
  192. '九月:开学季,关注孩子生长发育',
  193. '十月:国庆长假,注意饮食控制',
  194. '十一月:秋冬交替,保暖防寒',
  195. '十二月:年终总结,规划来年健康'
  196. ]
  197. return messages[month] || messages[0]
  198. }
  199. },
  200. onShow() {
  201. this.showTabTransition = true
  202. var token = uni.getStorageSync('token')
  203. this.isLoggedIn = !!token
  204. if (this.isLoggedIn) {
  205. this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  206. this.userName = uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '家长'
  207. }
  208. this.loadSectionConfig()
  209. if (this.isLoggedIn) {
  210. this.loadChildren()
  211. this.loadFamilyMembersVisible()
  212. }
  213. // 游客也能浏览活动和商品
  214. // 游客也能浏览文章
  215. this._watchPageReady()
  216. },
  217. methods: {
  218. loadSectionConfig: function() {
  219. var baseSections = ['func_entries']
  220. if (!uni.getStorageSync('token')) {
  221. this.visibleSections = baseSections
  222. return
  223. }
  224. var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  225. var self = this
  226. getVisibleSections({ pageKey: 'body', role: role }).then(function(res) {
  227. if (res.code === 200 && res.data) {
  228. var keys = res.data.map(function(s) { return s.sectionKey })
  229. var merged = baseSections.slice()
  230. keys.forEach(function(k) { if (merged.indexOf(k) === -1) merged.push(k) })
  231. self.visibleSections = merged
  232. }
  233. }).catch(function(e) {
  234. self.visibleSections = ['func_entries']
  235. })
  236. },
  237. loadChildren: function() {
  238. var self = this
  239. getChildren().then(function(res) {
  240. if (res.code === 200 && res.data) {
  241. self.children = res.data
  242. var savedChildId = uni.getStorageSync('currentChildId')
  243. var found = false
  244. for (var i = 0; i < self.children.length; i++) {
  245. if (self.children[i].memberId === savedChildId) {
  246. self.activeChildId = savedChildId
  247. self.activeChildName = self.children[i].name || self.children[i].nickname || '孩子'
  248. found = true
  249. break
  250. }
  251. }
  252. if (!found && self.children.length > 0) {
  253. self.activeChildId = self.children[0].memberId
  254. self.activeChildName = self.children[0].name || self.children[0].nickname || '孩子'
  255. uni.setStorageSync('currentChildId', self.activeChildId)
  256. }
  257. if (self.activeChildId) {
  258. self.loadDashboardData()
  259. }
  260. }
  261. self.childrenChecked = true
  262. }).catch(function(e) {
  263. console.log('获取孩子列表失败', e)
  264. self.childrenChecked = true
  265. })
  266. },
  267. loadDashboardData: function() {
  268. if (!this.activeChildId) return
  269. this.loadEnergyData()
  270. this.loadSandboxData()
  271. this.loadHealthDimensions()
  272. },
  273. loadHealthDimensions: function() {
  274. var memberId = this.activeChildId || (this.userInfo && this.userInfo.userId)
  275. if (!memberId) {
  276. console.log('[body] loadHealthDimensions skipped: no memberId')
  277. return
  278. }
  279. var self = this
  280. getDimensionOverview({ memberId: memberId }).then(function(res) {
  281. if (res && res.data) {
  282. self.dimensionData = res.data
  283. self.healthSubDims = (self.dimensionData.dimensions || []).map(function(d) { return {label: d.label || d.dimension, score: d.score || 0, desc: d.description || ''}; });
  284. console.log('[body] dimensionData loaded:', res.data.dimensions ? res.data.dimensions.length : 0, 'dimensions')
  285. } else if (res) {
  286. console.warn('[body] dimension API error:', res.message || res.code)
  287. }
  288. }).catch(function(e) {
  289. console.error('[body] loadHealthDimensions failed', e)
  290. })
  291. },
  292. loadSandboxData: function() {
  293. var self = this
  294. getFamilyEnergySandbox().then(function(res) {
  295. if (res && res.data) {
  296. self.sandboxData = res.data
  297. }
  298. }).catch(function(e) {
  299. console.log('获取沙盘数据失败', e)
  300. })
  301. },
  302. switchView: function(view) {
  303. this.currentView = view
  304. if (view === 'family') {
  305. this.selectedMemberId = null
  306. var self = this
  307. getEnergySandbox('family').then(function(res) {
  308. if (res && res.data && !res.data.locked) {
  309. self.sandboxData = res.data.familyData || res.data
  310. }
  311. }).catch(function(e) { console.log('获取家庭数据失败', e) })
  312. } else {
  313. this.loadSandboxData()
  314. }
  315. },
  316. loadEnergyData: function() {
  317. var self = this
  318. getEnergyOverview(this.activeChildId).then(function(res) {
  319. if (res && res.data) {
  320. var dims = res.data.dimensions
  321. if (dims && dims.length > 0) {
  322. for (var i = 0; i < dims.length; i++) {
  323. if (dims[i].code === 'body') {
  324. self.dualDimension = dims[i]
  325. break
  326. }
  327. }
  328. }
  329. }
  330. }).catch(function(e) {
  331. console.log('获取能量概览失败', e)
  332. })
  333. },
  334. onFamilyMemberSelect: function(member) {
  335. this.selectedMemberId = member.id
  336. },
  337. onChildChanged: function(child) {
  338. this.activeChildId = child.memberId
  339. this.activeChildName = child.name || child.nickname || '孩子'
  340. this.loadDashboardData()
  341. },
  342. loadFamilyMembersVisible: function() {
  343. var self = this
  344. getFamilyMemberList({ visibleOnly: true }).then(function(res) {
  345. self.familyMembersVisible = res.data || []
  346. self.membersChecked = true
  347. }).catch(function(e) {
  348. console.log('获取可见家庭成员失败', e)
  349. self.familyMembersVisible = []
  350. self.membersChecked = true
  351. })
  352. },
  353. scrollToSection: function(section) {
  354. uni.pageScrollTo({ selector: '#' + section, duration: 300 })
  355. },
  356. sectionVisible: function(key) {
  357. return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
  358. },
  359. onFuncClick: function(item) {
  360. if (item.needLogin && !this.isLoggedIn) {
  361. uni.showModal({
  362. title: '提示',
  363. content: '请先登录后使用此功能',
  364. success: function(res) {
  365. if (res.confirm) uni.navigateTo({ url: '/pages/login/login' })
  366. }
  367. })
  368. return
  369. }
  370. if (item.page === 'health-dimensions') {
  371. if (!this.activeChildId) {
  372. uni.showToast({ title: '请先选择孩子', icon: 'none' })
  373. return
  374. }
  375. uni.navigateTo({ url: '/pages/body-detail/health-dimensions?memberId=' + this.activeChildId })
  376. return
  377. }
  378. if (item.page) uni.navigateTo({ url: item.page })
  379. },
  380. goDimensionDetail: function(dimKey) {
  381. uni.navigateTo({ url: '/pages/body-detail/dimension-detail?dimension=' + dimKey + '&memberId=' + this.activeChildId })
  382. },
  383. goToCheckin: function() {
  384. var id = this.activeChildId || uni.getStorageSync('currentChildId')
  385. if (!id) {
  386. uni.showToast({ title: '请先选择成员', icon: 'none' })
  387. return
  388. }
  389. uni.navigateTo({ url: '/pages/body-detail/checkin?memberId=' + id })
  390. },
  391. goToNutritionProfile: function() {
  392. var id = this.activeChildId || uni.getStorageSync('currentChildId')
  393. if (!id) {
  394. uni.showToast({ title: '请先选择成员', icon: 'none' })
  395. return
  396. }
  397. uni.navigateTo({ url: '/pages/health/nutrition-profile?memberId=' + id })
  398. },
  399. goToUploadReport: function() {
  400. var id = this.activeChildId || uni.getStorageSync('currentChildId') || ''
  401. uni.navigateTo({ url: '/pages/health/report-upload?memberId=' + id + '&from=body' })
  402. },
  403. _watchPageReady() {
  404. var self = this
  405. var unwatch = this.$watch(function() {
  406. if (self.isLoggedIn) {
  407. return !!(self.dualDimension || self.sandboxData)
  408. } }, function(val) {
  409. if (val) {
  410. if (unwatch) unwatch()
  411. if (self.$refs.tabTransition) self.$refs.tabTransition.markReady()
  412. }
  413. })
  414. }
  415. }
  416. }
  417. </script>
  418. .view-toggle {
  419. display: flex;
  420. margin: 20rpx 30rpx;
  421. background: rgba(255,255,255,0.12);
  422. border-radius: 12rpx;
  423. padding: 6rpx;
  424. }
  425. .toggle-btn {
  426. flex: 1;
  427. text-align: center;
  428. padding: 12rpx 0;
  429. border-radius: 8rpx;
  430. font-size: 26rpx;
  431. color: rgba(255,255,255,0.65);
  432. }
  433. .toggle-btn.active {
  434. background: rgba(255,255,255,0.22);
  435. color: #fff;
  436. font-weight: 600;
  437. }
  438. /* 页面说明 */
  439. .section-sub {
  440. font-size: 22rpx;
  441. color: rgba(255,255,255,0.55);
  442. margin-top: 6rpx;
  443. display: block;
  444. }
  445. /* 得分解读卡片 */
  446. .score-explain-card {
  447. margin: 20rpx 30rpx;
  448. padding: 28rpx 24rpx;
  449. background: rgba(255,255,255,0.1);
  450. border-radius: 20rpx;
  451. border: 1rpx solid rgba(255,255,255,0.15);
  452. }
  453. .explain-header {
  454. display: flex;
  455. align-items: center;
  456. margin-bottom: 14rpx;
  457. }
  458. .explain-icon {
  459. font-size: 36rpx;
  460. margin-right: 10rpx;
  461. }
  462. .explain-title {
  463. font-size: 28rpx;
  464. font-weight: 600;
  465. color: #fff;
  466. }
  467. .explain-text {
  468. font-size: 26rpx;
  469. color: rgba(255,255,255,0.8);
  470. line-height: 1.6;
  471. }
  472. /* 子维度评分卡 */
  473. .subdim-section {
  474. margin: 20rpx 30rpx;
  475. }
  476. .subdim-cards {
  477. background: rgba(255,255,255,0.08);
  478. border-radius: 20rpx;
  479. overflow: hidden;
  480. border: 1rpx solid rgba(255,255,255,0.12);
  481. }
  482. .subdim-card {
  483. padding: 24rpx 28rpx;
  484. border-bottom: 1rpx solid rgba(255,255,255,0.08);
  485. }
  486. .subdim-card:last-child {
  487. border-bottom: none;
  488. }
  489. .subdim-card-left {
  490. display: flex;
  491. justify-content: space-between;
  492. align-items: center;
  493. margin-bottom: 12rpx;
  494. }
  495. .subdim-card-label {
  496. font-size: 28rpx;
  497. font-weight: 600;
  498. color: #fff;
  499. }
  500. .subdim-card-desc {
  501. font-size: 22rpx;
  502. color: rgba(255,255,255,0.5);
  503. margin-top: 4rpx;
  504. }
  505. .subdim-card-right {
  506. text-align: right;
  507. }
  508. .subdim-card-score {
  509. font-size: 36rpx;
  510. font-weight: 700;
  511. }
  512. .subdim-bar-track {
  513. height: 8rpx;
  514. background: rgba(255,255,255,0.1);
  515. border-radius: 4rpx;
  516. overflow: hidden;
  517. margin-top: 8rpx;
  518. }
  519. .subdim-bar-fill {
  520. height: 100%;
  521. border-radius: 4rpx;
  522. transition: width 0.5s ease;
  523. }
  524. <style scoped>
  525. .body-container {
  526. min-height: 100vh;
  527. background: #f5f7fa;
  528. padding-bottom: 120rpx;
  529. }
  530. /* ===== 功能入口 ===== */
  531. .func-section {
  532. margin: 20rpx 30rpx;
  533. }
  534. .func-grid {
  535. display: flex;
  536. flex-direction: row;
  537. justify-content: space-between;
  538. background: #fff;
  539. border-radius: 20rpx;
  540. padding: 24rpx 16rpx;
  541. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  542. }
  543. .func-item {
  544. display: flex;
  545. flex-direction: column;
  546. align-items: center;
  547. flex: 1;
  548. }
  549. .func-icon-wrap {
  550. width: 80rpx;
  551. height: 80rpx;
  552. border-radius: 40rpx;
  553. background: linear-gradient(135deg, #D6EAF8, #EBF2FA);
  554. display: flex;
  555. align-items: center;
  556. justify-content: center;
  557. margin-bottom: 10rpx;
  558. }
  559. .func-icon {
  560. font-size: 40rpx;
  561. }
  562. .func-label {
  563. font-size: 22rpx;
  564. color: #666;
  565. font-weight: 500;
  566. }
  567. /* ===== 通用区块 ===== */
  568. .section {
  569. margin: 20rpx 30rpx;
  570. }
  571. .section-header {
  572. display: flex;
  573. flex-direction: row;
  574. align-items: center;
  575. justify-content: space-between;
  576. margin-bottom: 16rpx;
  577. }
  578. .section-title {
  579. font-size: 30rpx;
  580. font-weight: bold;
  581. color: #333;
  582. }
  583. .avg-badge {
  584. font-size: 20rpx;
  585. color: #F97316;
  586. background: #FFF7ED;
  587. padding: 4rpx 12rpx;
  588. border-radius: 20rpx;
  589. margin-left: 12rpx;
  590. }
  591. /* ===== 快捷操作 ===== */
  592. .quick-actions {
  593. display: flex;
  594. flex-direction: row;
  595. margin-top: 30rpx;
  596. }
  597. .action-btn {
  598. flex: 1;
  599. background: #fff;
  600. border-radius: 16rpx;
  601. padding: 24rpx;
  602. display: flex;
  603. flex-direction: column;
  604. align-items: center;
  605. margin-right: 20rpx;
  606. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  607. }
  608. .action-btn:last-child {
  609. margin-right: 0;
  610. }
  611. .action-icon {
  612. font-size: 48rpx;
  613. margin-bottom: 8rpx;
  614. }
  615. .action-text {
  616. font-size: 26rpx;
  617. color: #666;
  618. }
  619. /* ===== 底部 ===== */
  620. .bottom-spacer {
  621. height: 120rpx;
  622. }
  623. .func-item:active, .action-btn:active {
  624. opacity: 0.7;
  625. }
  626. /* ===== 精准营养 ===== */
  627. .nutrition-section {
  628. margin-top: 20rpx;
  629. }
  630. .nutrition-report-card {
  631. display: flex;
  632. flex-direction: row;
  633. align-items: center;
  634. background: linear-gradient(135deg, #E8F5E9, #F1F8E9);
  635. border-radius: 20rpx;
  636. padding: 28rpx 24rpx;
  637. margin-bottom: 20rpx;
  638. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  639. }
  640. .nutrition-report-left {
  641. width: 100rpx;
  642. height: 100rpx;
  643. background: rgba(255,255,255,0.7);
  644. border-radius: 20rpx;
  645. display: flex;
  646. align-items: center;
  647. justify-content: center;
  648. margin-right: 20rpx;
  649. flex-shrink: 0;
  650. }
  651. .nutrition-icon-large {
  652. font-size: 52rpx;
  653. }
  654. .nutrition-report-right {
  655. flex: 1;
  656. }
  657. .nutrition-title {
  658. font-size: 30rpx;
  659. font-weight: bold;
  660. color: #2E7D32;
  661. display: block;
  662. margin-bottom: 6rpx;
  663. }
  664. .nutrition-desc {
  665. font-size: 22rpx;
  666. color: #558B2F;
  667. line-height: 1.5;
  668. display: block;
  669. margin-bottom: 10rpx;
  670. }
  671. .nutrition-report-tags {
  672. display: flex;
  673. flex-direction: row;
  674. gap: 10rpx;
  675. }
  676. .nutrition-report-tags .tag {
  677. font-size: 20rpx;
  678. color: #33691E;
  679. background: rgba(255,255,255,0.6);
  680. padding: 4rpx 14rpx;
  681. border-radius: 20rpx;
  682. }
  683. .nutrition-arrow {
  684. font-size: 40rpx;
  685. color: #A5D6A7;
  686. margin-left: 12rpx;
  687. }
  688. .nutrition-links {
  689. margin-bottom: 20rpx;
  690. }
  691. .links-label {
  692. font-size: 26rpx;
  693. font-weight: bold;
  694. color: #333;
  695. display: block;
  696. margin-bottom: 12rpx;
  697. }
  698. .links-row {
  699. display: flex;
  700. flex-direction: row;
  701. gap: 16rpx;
  702. }
  703. .link-item {
  704. flex: 1;
  705. display: flex;
  706. flex-direction: row;
  707. align-items: center;
  708. background: #fff;
  709. border-radius: 16rpx;
  710. padding: 20rpx 18rpx;
  711. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
  712. gap: 12rpx;
  713. }
  714. .link-item:active {
  715. opacity: 0.7;
  716. }
  717. .link-icon {
  718. font-size: 36rpx;
  719. flex-shrink: 0;
  720. }
  721. .link-texts {
  722. flex: 1;
  723. }
  724. .link-name {
  725. font-size: 24rpx;
  726. font-weight: 500;
  727. color: #333;
  728. display: block;
  729. margin-bottom: 4rpx;
  730. }
  731. .link-desc {
  732. font-size: 20rpx;
  733. color: #999;
  734. display: block;
  735. }
  736. .link-arrow {
  737. font-size: 32rpx;
  738. color: #CCC;
  739. flex-shrink: 0;
  740. }
  741. /* ===== 推荐阅读 ===== */
  742. .article-list {
  743. display: flex;
  744. flex-direction: column;
  745. gap: 16rpx;
  746. }
  747. .article-card {
  748. background: #fff;
  749. border-radius: 16rpx;
  750. padding: 24rpx;
  751. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  752. }
  753. .article-card:active {
  754. opacity: 0.8;
  755. }
  756. .article-category {
  757. display: inline-block;
  758. padding: 4rpx 14rpx;
  759. border-radius: 8rpx;
  760. margin-bottom: 10rpx;
  761. }
  762. .article-category-text {
  763. font-size: 20rpx;
  764. color: #fff;
  765. }
  766. .article-title {
  767. display: block;
  768. font-size: 28rpx;
  769. font-weight: bold;
  770. color: #333;
  771. margin-bottom: 8rpx;
  772. line-height: 1.4;
  773. }
  774. .article-summary {
  775. display: block;
  776. font-size: 24rpx;
  777. color: #888;
  778. line-height: 1.5;
  779. margin-bottom: 12rpx;
  780. overflow: hidden;
  781. text-overflow: ellipsis;
  782. display: -webkit-box;
  783. -webkit-line-clamp: 2;
  784. -webkit-box-orient: vertical;
  785. }
  786. .article-meta {
  787. display: flex;
  788. align-items: center;
  789. gap: 16rpx;
  790. }
  791. .meta-item {
  792. display: flex;
  793. align-items: center;
  794. gap: 4rpx;
  795. }
  796. .meta-icon {
  797. font-size: 24rpx;
  798. }
  799. .meta-text {
  800. font-size: 22rpx;
  801. color: #999;
  802. }
  803. .article-date {
  804. font-size: 22rpx;
  805. color: #bbb;
  806. }
  807. /* 品牌定位语 */
  808. .brand-strap {
  809. margin: 16rpx 30rpx 0;
  810. padding: 20rpx 28rpx;
  811. background: linear-gradient(135deg, #FFF7ED, rgba(255,140,66,0.12));
  812. border-radius: 16rpx;
  813. border-left: 4rpx solid #FF8C42;
  814. }
  815. .brand-strap-text {
  816. font-size: 26rpx;
  817. color: #9A3412;
  818. font-weight: 500;
  819. }
  820. /* 每月引导语 */
  821. .monthly-guidance {
  822. margin: 16rpx 30rpx 0;
  823. padding: 20rpx 28rpx;
  824. background: linear-gradient(135deg, #FFF7ED, rgba(255,140,66,0.08));
  825. border-radius: 16rpx;
  826. border-left: 4rpx solid #FF8C42;
  827. display: flex;
  828. align-items: center;
  829. gap: 12rpx;
  830. }
  831. .guidance-icon {
  832. font-size: 32rpx;
  833. }
  834. .guidance-text {
  835. font-size: 26rpx;
  836. color: #9A3412;
  837. font-weight: 500;
  838. }
  839. </style>