index.vue 24 KB

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