index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. <template>
  2. <view class="body-container">
  3. <!-- 品牌头部 -->
  4. <PageBanner theme="body"
  5. tagline="科学管理全家健康"
  6. quote="身体健康是幸福的基础" />
  7. <!-- 功能入口 -->
  8. <view class="func-section" v-if="sectionVisible('func_entries')">
  9. <view class="func-grid">
  10. <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
  11. <view class="func-icon-wrap">
  12. <text class="func-icon">{{ item.icon }}</text>
  13. </view>
  14. <text class="func-label">{{ item.label }}</text>
  15. </view>
  16. </view>
  17. </view>
  18. <!-- 今日身体数据(登录后可见) -->
  19. <view class="section" v-if="sectionVisible('daily_stats') && isLoggedIn">
  20. <view class="section-header">
  21. <text class="section-title">今日身体数据</text>
  22. </view>
  23. <view class="stats-card">
  24. <view class="stat-item">
  25. <text class="stat-value">{{ dailyStats.steps }}</text>
  26. <text class="stat-unit">步</text>
  27. <text class="stat-label">今日步数</text>
  28. </view>
  29. <view class="stat-divider"></view>
  30. <view class="stat-item">
  31. <text class="stat-value">{{ dailyStats.water }}</text>
  32. <text class="stat-unit">L</text>
  33. <text class="stat-label">饮水量</text>
  34. </view>
  35. <view class="stat-divider"></view>
  36. <view class="stat-item">
  37. <text class="stat-value">{{ dailyStats.sleep }}</text>
  38. <text class="stat-unit">h</text>
  39. <text class="stat-label">睡眠时长</text>
  40. </view>
  41. </view>
  42. </view>
  43. <!-- 健康小贴士 -->
  44. <view class="section" v-if="sectionVisible('health_tips')">
  45. <view class="section-header">
  46. <text class="section-title">健康小贴士</text>
  47. </view>
  48. <view class="tips-list">
  49. <view class="tip-card" v-for="item in healthTips" :key="item.id">
  50. <text class="tip-title">{{ item.title }}</text>
  51. <text class="tip-summary">{{ item.summary }}</text>
  52. <view class="tip-footer">
  53. <text class="tip-date">{{ item.date }}</text>
  54. <text class="tip-tag">阅读更多</text>
  55. </view>
  56. </view>
  57. </view>
  58. </view>
  59. <!-- 能量一周(登录后可见) -->
  60. <view class="section" v-if="sectionVisible('energy_week') && isLoggedIn">
  61. <view class="section-header">
  62. <text class="section-title">能量一周</text>
  63. </view>
  64. <view class="energy-week-bar">
  65. <view class="week-column" v-for="(day, idx) in energyWeek" :key="idx">
  66. <view class="bar-fill" :style="{ height: day.percent + '%' }"></view>
  67. <text class="bar-label">{{ day.label }}</text>
  68. </view>
  69. </view>
  70. </view>
  71. <!-- 规划师推荐(服务商可见,后台可配置) -->
  72. <view class="section" v-if="sectionVisible('teacher_tips')">
  73. <view class="section-header">
  74. <text class="section-title">👨‍🏫 规划师推荐</text>
  75. </view>
  76. <view class="tips-list">
  77. <view class="tip-card" v-for="item in teacherTips" :key="item.id">
  78. <text class="tip-title">{{ item.title }}</text>
  79. <text class="tip-summary">{{ item.summary }}</text>
  80. <view class="tip-footer">
  81. <text class="tip-date">{{ item.date }}</text>
  82. <text class="tip-tag">查看详情</text>
  83. </view>
  84. </view>
  85. </view>
  86. </view>
  87. <!-- 底部占位 -->
  88. <view class="bottom-spacer"></view>
  89. </view>
  90. </template>
  91. <script>
  92. import PageBanner from '../../components/PageBanner.vue'
  93. import { getVisibleSections, getEnergyOverview } from '../../utils/api.js'
  94. export default {
  95. components: { PageBanner },
  96. data() {
  97. return {
  98. isLoggedIn: false,
  99. role: null,
  100. currentChildId: null,
  101. totalEnergy: null,
  102. totalHealthIndex: null,
  103. visibleSections: [],
  104. funcList: [
  105. { icon: '\u{1F3C3}', label: '运动', needLogin: false, page: '' },
  106. { icon: '\u{1F957}', label: '饮食', needLogin: false, page: '' },
  107. { icon: '\u{1F634}', label: '作息', needLogin: false, page: '' },
  108. { icon: '\u{1F9A0}', label: '菌群', needLogin: false, page: '' },
  109. { icon: '\u{1F9D8}', label: '冥想', needLogin: false, page: '' }
  110. ],
  111. dailyStats: {
  112. steps: '6,582',
  113. water: '1.2',
  114. sleep: '8.5'
  115. },
  116. healthTips: [
  117. { id: 1, title: '儿童每日运动指南', summary: '不同年龄段儿童每天需要多少运动量?科学运动助力健康成长。', date: '2024-01-15' },
  118. { id: 2, title: '五行饮食搭配法则', summary: '根据五行理论合理搭配膳食,均衡营养从每一餐开始。', date: '2024-01-12' },
  119. { id: 3, title: '优质睡眠从小养成', summary: '帮助孩子建立规律的作息习惯,提升睡眠质量。', date: '2024-01-10' },
  120. { id: 4, title: '肠道菌群与免疫力', summary: '了解益生菌对儿童健康的重要作用,从内部提升抵抗力。', date: '2024-01-08' }
  121. ],
  122. teacherTips: [
  123. { id: 1, title: '儿童体态评估与纠正', summary: '专业体态评估方法,及早发现并纠正不良体态。', date: '2024-01-14' },
  124. { id: 2, title: '感统训练家庭方案', summary: '针对不同年龄段的感觉统合训练建议,在家也能做。', date: '2024-01-11' }
  125. ],
  126. energyWeek: [
  127. { label: '一', percent: 60 },
  128. { label: '二', percent: 75 },
  129. { label: '三', percent: 45 },
  130. { label: '四', percent: 80 },
  131. { label: '五', percent: 65 },
  132. { label: '六', percent: 90 },
  133. { label: '日', percent: 70 }
  134. ]
  135. }
  136. },
  137. onShow() {
  138. var token = uni.getStorageSync('token')
  139. this.isLoggedIn = !!token
  140. if (this.isLoggedIn) {
  141. this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  142. this.currentChildId = uni.getStorageSync('currentChildId') || null
  143. }
  144. this.loadSectionConfig()
  145. if (this.isLoggedIn) {
  146. this.loadEnergyData()
  147. }
  148. },
  149. methods: {
  150. async loadSectionConfig() {
  151. if (!uni.getStorageSync('token')) {
  152. this.visibleSections = ['func_entries']
  153. return
  154. }
  155. var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
  156. try {
  157. var res = await getVisibleSections({ pageKey: 'body', role: role })
  158. if (res.code === 200 && res.data) {
  159. this.visibleSections = res.data.map(function(s) { return s.sectionKey })
  160. }
  161. } catch (e) {
  162. this.visibleSections = ['func_entries']
  163. }
  164. },
  165. async loadEnergyData() {
  166. var childId = this.currentChildId
  167. if (!childId) return
  168. try {
  169. var res = await getEnergyOverview(childId)
  170. if (res && res.data) {
  171. this.visibleSections = res.data.dimensions || []
  172. this.totalEnergy = res.data.totalEnergy || 0
  173. this.totalHealthIndex = res.data.totalHealthIndex || 0
  174. }
  175. } catch (e) {
  176. console.log('获取能量概览失败', e)
  177. }
  178. },
  179. sectionVisible(key) {
  180. return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
  181. },
  182. onFuncClick(item) {
  183. if (item.needLogin && !this.isLoggedIn) {
  184. uni.showModal({
  185. title: '提示',
  186. content: '请先登录后使用此功能',
  187. success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
  188. })
  189. return
  190. }
  191. if (item.page) uni.navigateTo({ url: item.page })
  192. },
  193. goLogin() {
  194. uni.navigateTo({ url: '/pages/login/login' })
  195. }
  196. }
  197. }
  198. </script>
  199. <style scoped>
  200. .body-container {
  201. min-height: 100vh;
  202. background: #f5f7fa;
  203. padding-bottom: 120rpx;
  204. }
  205. /* ===== 功能入口 ===== */
  206. .func-section {
  207. margin: 20rpx 30rpx;
  208. }
  209. .func-grid {
  210. display: flex;
  211. flex-direction: row;
  212. justify-content: space-between;
  213. background: #fff;
  214. border-radius: 20rpx;
  215. padding: 24rpx 16rpx;
  216. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  217. }
  218. .func-item {
  219. display: flex;
  220. flex-direction: column;
  221. align-items: center;
  222. flex: 1;
  223. }
  224. .func-icon-wrap {
  225. width: 80rpx;
  226. height: 80rpx;
  227. border-radius: 40rpx;
  228. background: linear-gradient(135deg, #D6EAF8, #EBF2FA);
  229. display: flex;
  230. align-items: center;
  231. justify-content: center;
  232. margin-bottom: 10rpx;
  233. }
  234. .func-icon {
  235. font-size: 40rpx;
  236. }
  237. .func-label {
  238. font-size: 22rpx;
  239. color: #666;
  240. font-weight: 500;
  241. }
  242. /* ===== 通用区块 ===== */
  243. .section {
  244. margin: 20rpx 30rpx;
  245. }
  246. .section-header {
  247. margin-bottom: 20rpx;
  248. }
  249. .section-title {
  250. font-size: 30rpx;
  251. font-weight: bold;
  252. color: #333;
  253. }
  254. /* ===== 今日数据 ===== */
  255. .stats-card {
  256. background: #fff;
  257. border-radius: 20rpx;
  258. padding: 30rpx 20rpx;
  259. display: flex;
  260. flex-direction: row;
  261. align-items: center;
  262. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  263. }
  264. .stat-item {
  265. flex: 1;
  266. display: flex;
  267. flex-direction: column;
  268. align-items: center;
  269. }
  270. .stat-value {
  271. font-size: 44rpx;
  272. font-weight: bold;
  273. color: #5B9BD5;
  274. }
  275. .stat-unit {
  276. font-size: 22rpx;
  277. color: #999;
  278. margin-top: -4rpx;
  279. }
  280. .stat-label {
  281. font-size: 24rpx;
  282. color: #666;
  283. margin-top: 8rpx;
  284. }
  285. .stat-divider {
  286. width: 1rpx;
  287. height: 80rpx;
  288. background: #f0f0f0;
  289. }
  290. /* ===== 健康小贴士 ===== */
  291. .tips-list {
  292. display: flex;
  293. flex-direction: column;
  294. gap: 20rpx;
  295. }
  296. .tip-card {
  297. background: #fff;
  298. border-radius: 20rpx;
  299. padding: 24rpx;
  300. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  301. }
  302. .tip-title {
  303. font-size: 28rpx;
  304. font-weight: bold;
  305. color: #333;
  306. display: block;
  307. margin-bottom: 10rpx;
  308. }
  309. .tip-summary {
  310. font-size: 24rpx;
  311. color: #888;
  312. line-height: 1.5;
  313. display: block;
  314. margin-bottom: 16rpx;
  315. }
  316. .tip-footer {
  317. display: flex;
  318. flex-direction: row;
  319. align-items: center;
  320. justify-content: space-between;
  321. }
  322. .tip-date {
  323. font-size: 22rpx;
  324. color: #bbb;
  325. }
  326. .tip-tag {
  327. font-size: 22rpx;
  328. color: #5B9BD5;
  329. font-weight: 500;
  330. }
  331. /* ===== 能量一周 ===== */
  332. .energy-week-bar {
  333. display: flex;
  334. align-items: flex-end;
  335. justify-content: space-around;
  336. height: 200rpx;
  337. background: #fff;
  338. border-radius: 20rpx;
  339. padding: 30rpx 20rpx;
  340. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
  341. }
  342. .week-column {
  343. display: flex;
  344. flex-direction: column;
  345. align-items: center;
  346. width: 60rpx;
  347. }
  348. .bar-fill {
  349. width: 40rpx;
  350. background: linear-gradient(180deg, #F97316, #FBBF24);
  351. border-radius: 20rpx 20rpx 4rpx 4rpx;
  352. min-height: 8rpx;
  353. }
  354. .bar-label {
  355. font-size: 22rpx;
  356. color: #999;
  357. margin-top: 12rpx;
  358. }
  359. .func-item:active {
  360. opacity: 0.7;
  361. }
  362. /* ===== 底部 ===== */
  363. .bottom-spacer {
  364. height: 120rpx;
  365. }
  366. </style>