index.vue 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. <template>
  2. <view class="container">
  3. <view v-if="!isLoggedIn">
  4. <PageBanner theme="action" tagline="骞查 路 璁板綍 路 鎴愰暱" quote="鐭ヨ鍚堜竴锛屾瘡澶╄繘姝ヤ竴鐐圭偣" />
  5. <!-- 鍝佺墝瀹氫綅璇?-->
  6. <view class="brand-strap"><text class="brand-strap-text">鐪嬭鏀瑰彉姝e湪鍙戠敓</text></view>
  7. <!-- 鐞嗗康鍗?-->
  8. <view class="concept-card">
  9. <text class="concept-quote">璁板綍涓嶆槸鐩殑锛岀湅瑙佹敼鍙樻墠鏄€?/text>
  10. <text class="concept-desc">姣忎竴浠介ギ椋熴€佽繍鍔ㄣ€佸績鎯呯殑璁板綍锛岄兘鍦ㄤ负鎴愰暱鎻愪緵渚濇嵁銆?/text>
  11. </view>
  12. <!-- 绀轰緥妗堜緥 -->
  13. <view class="case-card">
  14. <view class="case-header">
  15. <text class="case-label">馃摉 鐪熷疄妗堜緥锛堣劚鏁忥級</text>
  16. </view>
  17. <view class="case-body">
  18. <text class="case-name">灏忔槑锛?0宀侊紝浣撴牎鐢燂級</text>
  19. <text class="case-change">3涓湀骞查鍓嶅悗锛?/text>
  20. <view class="case-metrics">
  21. <view class="metric-item">
  22. <text class="metric-up">鈫?5鍒?/text>
  23. <text class="metric-label">韬淮搴?/text>
  24. </view>
  25. <view class="metric-item">
  26. <text class="metric-up">鈫?3鍒?/text>
  27. <text class="metric-label">鏅虹淮搴?/text>
  28. </view>
  29. <view class="metric-item">
  30. <text class="metric-up">鈫?8澶?/text>
  31. <text class="metric-label">杩炵画鎵撳崱</text>
  32. </view>
  33. </view>
  34. <text class="case-note">閫氳繃楗璁板綍 + 鐫$湢鎵撳崱锛屽厤鐤姏鏄捐憲鎻愬崌</text>
  35. </view>
  36. </view>
  37. <!-- 鍔熻兘棰勮 -->
  38. <view class="guest-features">
  39. <view class="feat-card" @click="handleGuestFeatureTap">
  40. <text class="feat-icon">鉁?/text>
  41. <text class="feat-title">姣忔棩鎵撳崱</text>
  42. <text class="feat-desc">楗路杩愬姩路鐫$湢路蹇冩儏</text>
  43. </view>
  44. <view class="feat-card" @click="handleGuestFeatureTap">
  45. <text class="feat-icon">馃搵</text>
  46. <text class="feat-title">鎴愰暱璁板綍</text>
  47. <text class="feat-desc">姣忓懆瀹跺涵鍋ュ悍闂嵎</text>
  48. </view>
  49. <view class="feat-card" @click="handleGuestFeatureTap">
  50. <text class="feat-icon">馃弳</text>
  51. <text class="feat-title">閲岀▼纰?/text>
  52. <text class="feat-desc">杩炵画鎵撳崱路鎸戞垬瀹屾垚</text>
  53. </view>
  54. <view class="feat-card" @click="handleGuestFeatureTap">
  55. <text class="feat-icon">馃幆</text>
  56. <text class="feat-title">瀹跺涵鎸戞垬</text>
  57. <text class="feat-desc">鍏ㄥ鍙備笌鐨勪簰鍔ㄤ换鍔?/text>
  58. </view>
  59. </view>
  60. <!-- 浜旂淮鍋ュ悍浣撶郴 -->
  61. <view class="wuxing-section">
  62. <view class="wuxing-title">韬峰績路鏅郝疯路瀵?路 鍙墽琛岀殑鎴愰暱闂幆</view>
  63. <view class="wuxing-desc">浜旇鐩哥敓鐩稿厠锛屽姩璧锋潵銆佸潥鎸佸仛锛岃鎴愰暱鐪熸鍙戠敓</view>
  64. <view class="wuxing-relations">
  65. <view class="rel-group">
  66. <text class="rel-title">鐩哥敓 鈫?绯荤粺鍓嶈繘</text>
  67. <view class="rel-chain">
  68. <text class="rel-item">馃審 韬?/text><text class="rel-arrow">鈫?/text>
  69. <text class="rel-item">鈿?鏅?/text><text class="rel-arrow">鈫?/text>
  70. <text class="rel-item">馃挧 瀵?/text><text class="rel-arrow">鈫?/text>
  71. <text class="rel-item">馃尶 琛?/text><text class="rel-arrow">鈫?/text>
  72. <text class="rel-item">馃敟 蹇?/text><text class="rel-arrow">鈫?/text>
  73. <text class="rel-item">馃審 韬?/text>
  74. </view>
  75. <text class="rel-hint">韬嫟鍒欏己锛屾櫤鍕ゅ垯寮€锛屽瘜鍕ゅ垯鑱氾紝琛屽嫟鍒欏拰锛屽績鍕ゅ垯瀹?/text>
  76. </view>
  77. <view class="rel-group">
  78. <text class="rel-title">鐩稿厠 鈫?绯荤粺骞宠 </text>
  79. <view class="rel-chain">
  80. <text class="rel-item">馃審 韬?/text><text class="rel-op">鍏?/text><text class="rel-item">馃挧 瀵?/text><text class="rel-dot">路</text>
  81. <text class="rel-item">鈿?鏅?/text><text class="rel-op">鍏?/text><text class="rel-item">馃尶 琛?/text><text class="rel-dot">路</text>
  82. <text class="rel-item">馃挧 瀵?/text><text class="rel-op">鍏?/text><text class="rel-item">馃敟 蹇?/text><text class="rel-dot">路</text>
  83. <text class="rel-item">馃尶 琛?/text><text class="rel-op">鍏?/text><text class="rel-item">馃審 韬?/text><text class="rel-dot">路</text>
  84. <text class="rel-item">馃敟 蹇?/text><text class="rel-op">鍏?/text><text class="rel-item">鈿?鏅?/text>
  85. </view>
  86. <text class="rel-hint">涓嶆嬁韬綋鎹㈠揩閽憋紝涓嶇畻璁″叧绯诲緱澶憋紝璐㈠瘜鏈嶅姟瀹跺涵锛屽叧鐖辨湁搴︺€佷笉婧虹埍瓒婄晫</text>
  87. </view>
  88. </view>
  89. <view class="wuxing-cards">
  90. <view class="wc-card wc-body" @click="goPage('/pages/body-detail/index')">
  91. <text class="wc-icon">馃審</text>
  92. <view class="wc-info">
  93. <text class="wc-name">韬?路 涓诲姩鍋ュ悍</text>
  94. <text class="wc-text">姣忓ぉ鍔ㄤ竴鍔ㄣ€佺潯寰楅锛岃韩浣撶姸鎬佺湅寰楄鍦板彉濂?/text>
  95. </view>
  96. </view>
  97. <view class="wc-card wc-wisdom" @click="goPage('/pages/wisdom-detail/index')">
  98. <text class="wc-icon">鈿?/text>
  99. <view class="wc-info">
  100. <text class="wc-name">鏅?路 璧嬭兘鏈潵</text>
  101. <text class="wc-text">姣忓ぉ璇诲嚑椤点€佺粌涓€浼氫笓娉紝鏅烘収鏄粌鍑烘潵鐨?/text>
  102. </view>
  103. </view>
  104. <view class="wc-card wc-wealth" @click="goPage('/pages/wealth/index')">
  105. <text class="wc-icon">馃挧</text>
  106. <view class="wc-info">
  107. <text class="wc-name">瀵?路 鍒╀粬鍒涘瘜</text>
  108. <text class="wc-text">鍧氭寔鍒嗕韩涓庣Н绱紝鎶婄浜屾敹鍏ユ洸绾垮仛鍑烘潵</text>
  109. </view>
  110. </view>
  111. <view class="wc-card wc-action" @click="goPage('/pages/action-detail/member-action-detail')">
  112. <text class="wc-icon">馃尶</text>
  113. <view class="wc-info">
  114. <text class="wc-name">琛?路 浜洪檯鍜岃皭</text>
  115. <text class="wc-text">浠庝竴娆″€惧惉寮€濮嬶紝璁╁搴叧绯绘瘡澶╁崌娓?/text>
  116. </view>
  117. </view>
  118. <view class="wc-card wc-mind" @click="goPage('/pages/mind-detail/index')">
  119. <text class="wc-icon">馃敟</text>
  120. <view class="wc-info">
  121. <text class="wc-name">蹇?路 澶х埍鐩搁殢</text>
  122. <text class="wc-text">姣忓ぉ鐣?10 鍒嗛挓璇磋璇濓紝鎶婂浜虹殑蹇冧簨鎺ヤ綇</text>
  123. </view>
  124. </view>
  125. </view>
  126. </view>
  127. <!-- 鐧诲綍寮曞 -->
  128. <view class="login-guide-section">
  129. <LoginGuideCard
  130. title="瑙i攣瀹屾暣鎴愰暱妗f"
  131. :items="[
  132. '鏌ョ湅骞查杩涘害涓庤拷韪褰?,
  133. '瀹屾垚姣忔棩鎵撳崱绱Н杩炵画璁板綍',
  134. '鍙備笌瀹跺涵鎸戞垬鑾峰緱閲岀▼纰戝鍔?
  135. ]" />
  136. </view>
  137. </view>
  138. <view v-else>
  139. <PageBanner theme="action" tagline="骞查 路 璁板綍 路 鎴愰暱" quote="鐭ヨ鍚堜竴锛屾瘡澶╄繘姝ヤ竴鐐圭偣" />
  140. <!-- 閲岀▼纰?-->
  141. <view class="section">
  142. <view class="milestone-card">
  143. <text class="section-title-row">馃弳 閲岀▼纰?/text>
  144. <view class="milestone-grid">
  145. <view class="milestone-item">
  146. <text class="milestone-icon">馃敟</text>
  147. <text class="milestone-value">{{ streakDays }}澶?/text>
  148. <text class="milestone-label">杩炵画鎵撳崱</text>
  149. </view>
  150. <view class="milestone-item">
  151. <text class="milestone-icon">馃幆</text>
  152. <text class="milestone-value">{{ activeChallenges }}</text>
  153. <text class="milestone-label">杩涜涓寫鎴?/text>
  154. </view>
  155. <view class="milestone-item">
  156. <text class="milestone-icon">馃搱</text>
  157. <text class="milestone-value">{{ completeness }}%</text>
  158. <text class="milestone-label">妗f瀹屾暣搴?/text>
  159. </view>
  160. </view>
  161. </view>
  162. </view>
  163. <!-- 鎴愰暱璁板綍 -->
  164. <view class="section">
  165. <view class="survey-card">
  166. <text class="section-title-row">馃搵 鎴愰暱璁板綍</text>
  167. <view class="survey-pending" v-if="pendingSurvey">
  168. <text class="survey-alert">鈿?鏈懆杩樻湭瀹屾垚璋冪爺</text>
  169. <text class="survey-title-text">{{ pendingSurvey.title }}</text>
  170. <text class="survey-btn" @click="startSurvey">[寮€濮嬪~鍐橾</text>
  171. </view>
  172. <view class="survey-done" v-else-if="allDone"><text class="survey-done-text">鉁?鎵€鏈夎皟鐮斿凡瀹屾垚</text></view>
  173. <view class="survey-history-list" v-if="surveyHistory.length > 0">
  174. <text class="history-label">鍘嗗彶璋冪爺:</text>
  175. <view class="history-item" v-for="(h, hIdx) in surveyHistory" :key="getHistoryKey(h)">
  176. <text class="history-date">{{ h._date }}</text>
  177. <text class="history-content">{{ h._content }}</text>
  178. </view>
  179. <text class="survey-more" @click="viewAllHistory">鏌ョ湅鍏ㄩ儴璋冪爺 鈥?/text>
  180. </view>
  181. <view class="survey-empty" v-else-if="!pendingSurvey"><text class="survey-empty-text">鏆傛棤寰呭畬鎴愯皟鐮?/text></view>
  182. </view>
  183. </view>
  184. <view class="reminder-bar" v-if="hasIncomplete">
  185. <text class="reminder-text">鈿?浠婃棩杩樻湁 {{ incompleteCount }} 椤规湭璁板綍</text>
  186. <text class="reminder-close" @click="dismissReminder">鉁?/text>
  187. </view>
  188. <view class="section">
  189. <view class="progress-card">
  190. <view class="progress-header">
  191. <text class="progress-title">浠婃棩鎴愰暱</text> <text class="progress-date">{{ todayDate }}</text>
  192. </view>
  193. <view class="progress-item" v-for="(item, idx) in progressItems" :key="getProgressKey(item)">
  194. <text class="progress-icon">{{ item.icon }}</text>
  195. <text class="progress-label">{{ item.label }}</text>
  196. <text class="progress-status" :class="item.done ? 'status-done' : 'status-pending'">{{ item.done ? '鉁?宸插畬鎴? : '' }}</text>
  197. <text class="progress-btn" v-if="!item.done" @click="handleProgressClick(item)">{{ item.actionText || '琛ュ綍' }}</text>
  198. </view>
  199. <view class="progress-footer">
  200. <text class="footer-text">浠婃棩瀹屾垚 {{ doneCount }}/{{ totalCount }}</text>
  201. <text class="footer-streak" v-if="streakDays > 0">馃敟 杩炵画鎵撳崱 {{ streakDays }} 澶?/text>
  202. </view>
  203. </view>
  204. </view>
  205. <view class="section" v-if="cautionCount > 0">
  206. <view class="action-card" @click="goToCaution">
  207. <text class="action-icon">鈿狅笍</text>
  208. <view class="action-info">
  209. <text class="action-title">椋熸潗鎴掑鍖归厤</text>
  210. <text class="action-desc">{{ cautionCount }} 绉嶉鏉愰渶鍏虫敞锛岀偣鍑绘煡鐪嬫垝澶囪鎯?/text>
  211. </view>
  212. <text class="action-arrow">鈥?/text>
  213. </view>
  214. </view>
  215. <view class="section">
  216. <view class="diet-card">
  217. <view class="diet-card-header">
  218. <text class="diet-card-title">馃ガ 浠婃棩鎺ㄨ崘椋熸潗</text>
  219. <text class="diet-card-link" @click="goToDiet">楗涓婚〉 鈥?/text>
  220. </view>
  221. <view class="diet-card-content" v-if="ingredients.length > 0">
  222. <view class="diet-item" v-for="(item, index) in ingredients" :key="getIngredientKey(item, index)">
  223. <text class="ing-name">{{ item.name }}</text>
  224. <text class="ing-reason" v-if="item.reason">{{ item.reason }}</text>
  225. </view>
  226. </view>
  227. <view class="diet-empty" v-else>
  228. <text class="empty-text">{{ ingredientsLoading ? '鍔犺浇涓?..' : '鏆傛棤鎺ㄨ崘椋熸潗' }}</text>
  229. <text class="empty-hint" v-if="!ingredientsLoading">涓婁紶鑿岀兢鎶ュ憡鍚庣敓鎴愪釜鎬у寲鎺ㄨ崘</text>
  230. </view>
  231. </view>
  232. </view>
  233. <!-- ===== 鎺ㄨ崘鍟嗗搧 ===== -->
  234. <RecommendedFeed
  235. :products="recommendedProducts"
  236. :activities="[]"
  237. :loading="loadingProducts"
  238. @itemClick="onFeedItemClick"
  239. @more="goMoreDiscover" />
  240. <!-- ===== 鎺ㄨ崘闃呰 ===== -->
  241. <ArticleBookshelf
  242. :articles="indexArticles"
  243. :loading="articlesLoading"
  244. :isLoggedIn="isLoggedIn"
  245. @articleClick="goArticleDetail"
  246. @moreArticles="goMoreArticles" />
  247. <view class="section">
  248. <view class="tracking-card">
  249. <view class="tracking-tabs">
  250. <view class="tracking-tab" :class="activeTab === tab.key ? 'tab-active' : ''" v-for="tab in trackingTabs" :key="tab.key" @click="switchTab(tab.key)">
  251. <text class="tab-text" :class="activeTab === tab.key ? 'tab-active-text' : ''">{{ tab.label }}</text>
  252. </view>
  253. </view>
  254. <view class="tracking-content">
  255. <view class="tracking-empty" v-if="currentRecords.length === 0">
  256. <text class="empty-icon">馃搳</text>
  257. <text class="empty-text">杩樻病鏈夎褰?/text>
  258. <text class="empty-hint">寮€濮嬭褰晎{ activeTabLabel }}鍚?/text>
  259. </view>
  260. <view class="tracking-record" v-for="(rec, rIdx) in currentRecords" :key="getRecordKey(rec)">
  261. <text class="record-date">{{ rec._date || rec._text }}</text>
  262. <text class="record-content">{{ rec._content }}</text>
  263. </view>
  264. <view class="tracking-more" v-if="currentRecords.length > 0" @click="viewAllRecords">鏌ョ湅鍏ㄩ儴 鈥?/view>
  265. </view>
  266. </view>
  267. </view>
  268. <DimensionActivities :isLoggedIn="true" @activityClick="goActivityDetail" @moreActivities="goMoreActivities" style="margin-bottom:60rpx" />
  269. </view>
  270. </view>
  271. </template>
  272. <script>
  273. import PageBanner from '../../components/PageBanner.vue'
  274. import LoginGuideCard from '../../components/LoginGuideCard.vue'
  275. import DimensionActivities from '../../components/DimensionActivities.vue'
  276. import RecommendedFeed from '../../components/RecommendedFeed.vue'
  277. import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
  278. import { healthCheckinList, getDietIngredients, getSurveyStatus, getSurveyHistory, getFoodCautionList, getExerciseList, getMindCheckinList, getSleepList, getHealthMealList, productList, getFeaturedArticles } from '../../utils/api.js'
  279. export default {
  280. components: { PageBanner, LoginGuideCard, DimensionActivities, RecommendedFeed, ArticleBookshelf },
  281. data() { return { memberId: '', todayDate: '', hasIncomplete: false, incompleteCount: 0, progressItems: [], trackingTabs: [{ key: 'diet', label: '楗', icon: '馃嵔锔? }, { key: 'exercise', label: '杩愬姩', icon: '馃弮' }, { key: 'sleep', label: '鐫$湢', icon: '馃槾' }, { key: 'emotion', label: '蹇冩儏', icon: '馃槉' }], trackingData: { diet: [], exercise: [], sleep: [], emotion: [] }, streakDays: 0, activeChallenges: 0, completeness: 0, cautionCount: 0,
  282. pendingSurvey: null, allDone: false, surveyHistory: [], isLoggedIn: false,
  283. ingredients: [],
  284. ingredientsLoading: false,
  285. // 鎺ㄨ崘鍟嗗搧 + 鎺ㄨ崘闃呰锛堜笌棣栭〉 parent-index 涓€鑷达級
  286. recommendedProducts: [],
  287. loadingProducts: false,
  288. indexArticles: [],
  289. articlesLoading: false,
  290. gradientColors: [
  291. 'linear-gradient(135deg, #6366F1, #818CF8)',
  292. 'linear-gradient(135deg, #8B5CF6, #A78BFA)',
  293. 'linear-gradient(135deg, #5B9BD5, #8FC5E8)',
  294. 'linear-gradient(135deg, #10B981, #34D399)',
  295. 'linear-gradient(135deg, #F97316, #FB923C)'
  296. ] } }, computed: {
  297. doneCount: function() { var c = 0; for (var i = 0; i < this.progressItems.length; i++) if (this.progressItems[i] && this.progressItems[i].done) c++; return c },
  298. totalCount: function() { return this.progressItems.length },
  299. activeTabLabel: function() { var m = { diet: '楗', exercise: '杩愬姩', sleep: '鐫$湢', emotion: '蹇冩儏' }; return m[this.activeTab] || '' },
  300. currentRecords: function() { return this.trackingData[this.activeTab] || [] }
  301. },
  302. onShow() {
  303. this.isLoggedIn = !!uni.getStorageSync('token')
  304. if (this.isLoggedIn) {
  305. this.memberId = uni.getStorageSync('currentChildId') || ''
  306. var now = new Date()
  307. this.todayDate = now.getFullYear() + '-' + this.pad(now.getMonth() + 1) + '-' + this.pad(now.getDate())
  308. this.loadAllData()
  309. }
  310. },
  311. methods: {
  312. pad: function(n) { return n < 10 ? '0' + n : '' + n },
  313. getProgressKey: function(item) { return item && item.label ? item.label : 'p-' + Math.random() },
  314. getRecordKey: function(rec) { return rec && (rec._id || rec.id) ? 'r' + (rec._id || rec.id) : (rec && rec._text ? rec._text + rec._content : 'r-' + Math.random()) },
  315. getHistoryKey: function(h) { return h && h._date ? h._date + h._content : 'h-' + Math.random() },
  316. dismissReminder: function() { this.hasIncomplete = false },
  317. switchTab: function(key) { this.activeTab = key; this.loadTrackingData(key) },
  318. handleProgressClick: function(item) {
  319. if (!item || !item.key) return
  320. var m = { checkin: '/pages/health/daily-checkin', diet: '/pages/health/diet-index', exercise: '/pages/health/exercise-index', sleep: '/pages/health/sleep-index', emotion: '/pages/mind/index' }
  321. var url = m[item.key]
  322. if (item.key === 'survey' && this.pendingSurvey && this.pendingSurvey.templateId) {
  323. url = '/pages/health/survey-questionnaire?templateId=' + this.pendingSurvey.templateId + '&templateTitle=' + encodeURIComponent(this.pendingSurvey.title) + '&templateDimension=' + (this.pendingSurvey.dimension || '')
  324. }
  325. if (url) uni.navigateTo({ url: url })
  326. },
  327. startSurvey: function() { if (this.pendingSurvey && this.pendingSurvey.templateId) uni.navigateTo({ url: '/pages/health/survey-questionnaire?templateId=' + this.pendingSurvey.templateId + '&templateTitle=' + encodeURIComponent(this.pendingSurvey.title) + '&templateDimension=' + (this.pendingSurvey.dimension || '') }) },
  328. viewAllRecords: function() { var m = { diet: '/pages/health/diet-index', exercise: '/pages/health/exercise-index', sleep: '/pages/health/sleep-index', emotion: '/pages/mind/index' }; if (m[this.activeTab]) uni.navigateTo({ url: m[this.activeTab] }) },
  329. viewAllHistory: function() { uni.navigateTo({ url: '/pages/health/survey-history?memberId=' + this.memberId }) },
  330. goActivityDetail: function(act) { uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id }) },
  331. goMoreActivities: function() { uni.navigateTo({ url: '/pages/activity/index' }) },
  332. handleGuestFeatureTap: function() { uni.showToast({ title: '鐧诲綍鍚庡嵆鍙綋楠岃鍔熻兘', icon: 'none' }) },
  333. goToCaution: function() { uni.navigateTo({ url: '/pages/growth/caution-detail' }) },
  334. goToDiet: function() { uni.navigateTo({ url: '/pages/diet/index' }) },
  335. goPage: function(url) { uni.navigateTo({ url: url }) },
  336. // ===== 鎺ㄨ崘鍟嗗搧 + 鎺ㄨ崘闃呰锛堜笌棣栭〉 parent-index 涓€鑷达級 =====
  337. onFeedItemClick: function(item) {
  338. if (item && item.type === 'product' && item.id) {
  339. uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + item.id })
  340. }
  341. },
  342. goMoreDiscover: function() {
  343. uni.navigateTo({ url: '/pages/shop/index/index' })
  344. },
  345. goArticleDetail: function(articleOrId) {
  346. var id = typeof articleOrId === 'object' ? articleOrId.id : articleOrId
  347. if (id) uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id })
  348. },
  349. goMoreArticles: function() {
  350. uni.navigateTo({ url: '/pages/article-center/index' })
  351. },
  352. async loadAllData() {
  353. var self = this
  354. self.loadProgressItems()
  355. self.loadSurveyData()
  356. self.loadRecommendedProducts()
  357. self.loadFeaturedArticles()
  358. self.loadCautionData()
  359. self.loadMilestones()
  360. self.loadDietIngredients() },
  361. loadRecommendedProducts: function() {
  362. var self = this
  363. self.loadingProducts = true
  364. productList({ page: 1, size: 6 }).then(function(res) {
  365. self.loadingProducts = false
  366. if (res && res.code === 200 && res.data) {
  367. var list = res.data.records || []
  368. self.recommendedProducts = list.map(function(p) {
  369. p.type = 'product'
  370. return p
  371. })
  372. }
  373. }).catch(function() {
  374. self.loadingProducts = false
  375. self.recommendedProducts = []
  376. })
  377. },
  378. loadFeaturedArticles: function() {
  379. var self = this
  380. self.articlesLoading = true
  381. getFeaturedArticles({ size: 5 }).then(function(res) {
  382. self.articlesLoading = false
  383. if (res && res.code === 200) {
  384. var rawList = res.data && (Array.isArray(res.data) ? res.data : (res.data.data || res.data.records || []))
  385. var list = Array.isArray(rawList) ? rawList : []
  386. self.indexArticles = list.map(function(item, index) {
  387. return {
  388. id: item.id,
  389. category: item.categoryName || item.category || '鐑棬鏂囩珷',
  390. categoryColor: self.gradientColors[index % self.gradientColors.length],
  391. title: item.title || '',
  392. summary: item.summary || '',
  393. views: item.viewCount ? String(item.viewCount) : '0',
  394. date: item.publishedAt ? item.publishedAt.slice(0, 10) : '',
  395. coverImage: item.coverImage
  396. }
  397. })
  398. }
  399. }).catch(function() {
  400. self.articlesLoading = false
  401. self.indexArticles = []
  402. })
  403. },
  404. async loadProgressItems() {
  405. var self = this
  406. var items = []
  407. var dietDone = false
  408. try {
  409. var dRes = await getHealthMealList({ memberId: self.memberId })
  410. if (dRes.code === 200 && dRes.data && dRes.data.length > 0) {
  411. for (var dm = 0; dm < dRes.data.length; dm++) {
  412. var dRec = dRes.data[dm]
  413. if (!dRec) continue
  414. var dDate = dRec.createdAt
  415. if (dDate instanceof Date) dDate = dDate.toISOString().substring(0, 10)
  416. else if (typeof dDate === 'string') dDate = dDate.substring(0, 10)
  417. else continue
  418. if (dDate === self.todayDate) { dietDone = true; break }
  419. } }
  420. } catch(e) {}
  421. items.push({ key: 'diet', icon: '馃嵔锔?, label: '楗璁板綍', done: dietDone, actionText: '琛ュ綍' })
  422. // 杩愬姩璁板綍
  423. var exerciseDone = false
  424. try {
  425. var exRes = await getExerciseList({ memberId: self.memberId })
  426. if (exRes.code === 200 && exRes.data && exRes.data.length > 0) {
  427. for (var ex = 0; ex < exRes.data.length; ex++) {
  428. var exRec = exRes.data[ex]
  429. if (!exRec) continue
  430. var exDate = exRec.createdAt
  431. if (exDate instanceof Date) exDate = exDate.toISOString().substring(0, 10)
  432. else if (typeof exDate === 'string') exDate = exDate.substring(0, 10)
  433. else continue
  434. if (exDate === self.todayDate) { exerciseDone = true; break } }
  435. }
  436. } catch(e) {}
  437. items.push({ key: 'exercise', icon: '馃弮', label: '杩愬姩璁板綍', done: exerciseDone, actionText: '璁板綍' })
  438. var sleepDone = false
  439. try {
  440. var slRes = await getSleepList({ memberId: self.memberId })
  441. if (slRes.code === 200 && slRes.data && slRes.data.length > 0) {
  442. for (var sl = 0; sl < slRes.data.length; sl++) {
  443. var slRec = slRes.data[sl]
  444. if (!slRec) continue
  445. var slDate = slRec.createdAt
  446. if (slDate instanceof Date) slDate = slDate.toISOString().substring(0, 10)
  447. else if (typeof slDate === 'string') slDate = slDate.substring(0, 10)
  448. else continue
  449. if (slDate === self.todayDate) { sleepDone = true; break } }
  450. }
  451. } catch(e) {}
  452. items.push({ key: 'sleep', icon: '馃槾', label: '鐫$湢璁板綍', done: sleepDone, actionText: '璁板綍' })
  453. var emotionDone = false
  454. try {
  455. var emRes = await getMindCheckinList({ page: 1, size: 20, memberId: self.memberId })
  456. if (emRes.code === 200 && emRes.data && emRes.data.length > 0) {
  457. for (var em = 0; em < emRes.data.length; em++) {
  458. var emRec = emRes.data[em]
  459. if (!emRec) continue
  460. var emDate = emRec.checkinDate
  461. if (emDate instanceof Date) emDate = emDate.toISOString().substring(0, 10)
  462. else if (typeof emDate === 'string') emDate = emDate.substring(0, 10)
  463. else continue
  464. if (emDate === self.todayDate) { emotionDone = true; break }
  465. } }
  466. } catch(e) {}
  467. items.push({ key: 'emotion', icon: '馃槉', label: '蹇冩儏璁板綍', done: emotionDone, actionText: '璁板綍' })
  468. self.progressItems = items.filter(function(it) { return it && it.key })
  469. self.incompleteCount = 0
  470. for (var pi = 0; pi < items.length; pi++) if (items[pi] && !items[pi].done) self.incompleteCount++
  471. self.hasIncomplete = self.incompleteCount > 0
  472. },
  473. async loadSurveyData() {
  474. var self = this
  475. try {
  476. var statusRes = await getSurveyStatus({})
  477. if (statusRes.code === 200 && statusRes.data) {
  478. var list = statusRes.data
  479. self.pendingSurvey = null; self.allDone = true; var hasAny = false
  480. for (var si = 0; si < list.length; si++) { var s = list[si]; if (!s) continue; hasAny = true; if (s.status === 'pending') { self.pendingSurvey = { templateId: s.templateId, title: s.title || '鎴愰暱璁板綍', dimension: s.dimension || '' }; self.allDone = false } }
  481. if (!hasAny) { self.pendingSurvey = null; self.allDone = false }
  482. }
  483. } catch(e) {}
  484. try {
  485. var histRes = await getSurveyHistory({})
  486. if (histRes.code === 200 && histRes.data) {
  487. var hlist = histRes.data; self.surveyHistory = []
  488. for (var hi = 0; hi < Math.min(hlist.length, 3); hi++) { var h = hlist[hi]; self.surveyHistory.push({ _date: h && h.submittedAt ? h.submittedAt.substring(0, 10) : '', _content: '宸插畬鎴愯皟鐮? }) }
  489. }
  490. } catch(e) {}
  491. },
  492. async loadCautionData() {
  493. var self = this
  494. try {
  495. var res = await getFoodCautionList()
  496. if (res && res.code === 200) self.cautionCount = (res.data || []).length
  497. } catch(e) {}
  498. },
  499. async loadMilestones() {
  500. var self = this
  501. try {
  502. var ciRes = await healthCheckinList({})
  503. if (ciRes.code === 200 && ciRes.data) self.streakDays = Math.min(ciRes.data.length, 30)
  504. } catch(e) {}
  505. self.activeChallenges = 0
  506. self.completeness = self.streakDays > 0 ? Math.min(100, Math.round(self.streakDays * 10)) : 0
  507. }
  508. }
  509. }
  510. </script>
  511. <style scoped>
  512. .container{background:#FFF7ED;min-height:100vh;padding-bottom:calc(160rpx + env(safe-area-inset-bottom))}
  513. .section{margin:16rpx 24rpx}
  514. /* 娓稿鎬?*/
  515. .brand-strap{margin:0 24rpx 16rpx;background:linear-gradient(135deg,#F0FFF4,rgba(16,185,129,0.1));border-radius:16rpx;padding:20rpx 24rpx}
  516. .brand-strap-text{font-size:26rpx;color:#10B981;font-weight:600}
  517. .concept-card{margin:0 24rpx 16rpx;background:#fff;border-radius:20rpx;padding:28rpx;border-left:6rpx solid #10B981;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  518. .concept-quote{font-size:30rpx;font-weight:bold;color:#333;display:block;margin-bottom:8rpx}
  519. .concept-desc{font-size:24rpx;color:#888;display:block;line-height:1.6}
  520. .case-card{margin:0 24rpx 16rpx;background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  521. .case-header{margin-bottom:12rpx}
  522. .case-label{font-size:24rpx;color:#10B981;font-weight:bold}
  523. .case-body{padding:8rpx 0}
  524. .case-name{font-size:26rpx;color:#333;font-weight:600;display:block;margin-bottom:6rpx}
  525. .case-change{font-size:24rpx;color:#666;display:block;margin-bottom:12rpx}
  526. .case-metrics{display:flex;gap:16rpx;margin-bottom:12rpx}
  527. .metric-item{display:flex;flex-direction:column;align-items:center;flex:1;background:#F0FFF4;border-radius:12rpx;padding:12rpx 8rpx}
  528. .metric-up{font-size:26rpx;font-weight:bold;color:#10B981}
  529. .metric-label{font-size:20rpx;color:#999;margin-top:4rpx}
  530. .case-note{font-size:22rpx;color:#888;display:block}
  531. .guest-features{display:flex;flex-wrap:wrap;justify-content:space-between;margin:0 24rpx 16rpx}
  532. .feat-card{width:calc(50% - 8rpx);box-sizing:border-box;background:#fff;border-radius:20rpx;padding:24rpx;margin-bottom:16rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  533. .feat-icon{font-size:40rpx;display:block;margin-bottom:8rpx}
  534. .feat-title{font-size:26rpx;font-weight:600;color:#333;display:block;margin-bottom:4rpx}
  535. .feat-desc{font-size:22rpx;color:#999;display:block}
  536. .login-guide-section{margin:0 24rpx 24rpx}
  537. /* 鎴愰暱琛屽姩鍗$墖 */
  538. .action-card{display:flex;align-items:center;padding:24rpx 28rpx;background:#fff;border-radius:16rpx;margin:0 24rpx 16rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  539. .action-card:active{opacity:0.85}
  540. .action-icon{font-size:44rpx;margin-right:20rpx}
  541. .action-info{flex:1}
  542. .action-title{font-size:28rpx;font-weight:600;color:#333;display:block}
  543. .action-desc{font-size:22rpx;color:#888;margin-top:4rpx;display:block}
  544. .action-arrow{font-size:32rpx;color:#ccc}
  545. /* 鐧诲綍鍚庡師鏈夋牱寮?*/
  546. .reminder-bar{margin:12rpx 24rpx;background:#FFF3E0;border:1rpx solid #FFB74D;border-radius:12rpx;padding:14rpx 20rpx;display:flex;align-items:center;justify-content:space-between}
  547. .reminder-text{font-size:24rpx;color:#E65100}
  548. .reminder-close{font-size:28rpx;color:#999}
  549. .progress-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  550. .progress-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16rpx}
  551. .progress-title{font-size:28rpx;font-weight:bold;color:#333}
  552. .progress-date{font-size:22rpx;color:#999}
  553. .progress-grid{display:flex}
  554. .progress-grid-item{flex:1;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#FFF7ED;border-radius:16rpx;padding:20rpx 8rpx;border:1rpx solid rgba(249,115,22,0.12)}
  555. .progress-grid-item:active{opacity:0.85;background:#FFEDD5}
  556. .progress-grid-icon{font-size:40rpx;margin-bottom:8rpx}
  557. .progress-grid-label{font-size:24rpx;color:#333;margin-bottom:8rpx;text-align:center}
  558. .progress-grid-status{font-size:20rpx;flex-shrink:0}.status-done{color:#10B981}
  559. .status-pending{color:#ccc}
  560. .progress-btn{font-size:22rpx;color:#F97316;padding:4rpx 16rpx;border:1rpx solid #F97316;border-radius:20rpx;flex-shrink:0}
  561. .progress-footer{display:flex;justify-content:space-between;align-items:center;margin-top:16rpx;padding-top:16rpx;border-top:1rpx solid #f5f5f5}
  562. .footer-text{font-size:24rpx;color:#666;font-weight:bold}
  563. .footer-streak{font-size:24rpx;color:#F97316;font-weight:bold}
  564. .tracking-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  565. .tracking-tabs{display:flex;margin-bottom:16rpx}
  566. .tracking-tab{flex:1;text-align:center;padding:10rpx 0;border-radius:20rpx;margin-right:8rpx;background:#FFF7ED}
  567. .tracking-tab:last-child{margin-right:0}
  568. .tab-active{background:#F97316}
  569. .tab-text{font-size:24rpx;color:#666}
  570. .tab-active-text{color:#fff;font-weight:bold}
  571. .tracking-content{min-height:200rpx}
  572. .tracking-empty{display:flex;flex-direction:column;align-items:center;padding:60rpx 0}
  573. .empty-icon{font-size:60rpx;margin-bottom:12rpx}
  574. .empty-text{font-size:26rpx;color:#999}
  575. .empty-hint{font-size:22rpx;color:#ccc;margin-top:8rpx}
  576. .tracking-record{display:flex;align-items:center;padding:10rpx 0;border-bottom:1rpx solid #f5f5f5}
  577. .record-date{font-size:22rpx;color:#999;width:120rpx;flex-shrink:0}
  578. .record-content{font-size:24rpx;color:#333;flex:1}
  579. .tracking-more{text-align:right;font-size:24rpx;color:#F97316;padding:12rpx 0 0 0}
  580. .milestone-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  581. .section-title-row{font-size:26rpx;font-weight:bold;color:#333;margin-bottom:16rpx;display:block}
  582. .milestone-grid{display:flex;justify-content:space-between}
  583. .milestone-item{display:flex;flex-direction:column;align-items:center;flex:1}
  584. .milestone-icon{font-size:40rpx;margin-bottom:4rpx}
  585. .milestone-value{font-size:28rpx;font-weight:bold;color:#F97316;margin-bottom:2rpx}
  586. .milestone-label{font-size:22rpx;color:#999}
  587. .survey-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  588. .survey-pending{background:#FFF3E0;border-radius:12rpx;padding:16rpx;margin-bottom:12rpx}
  589. .survey-alert{font-size:24rpx;color:#E65100;font-weight:bold;display:block;margin-bottom:4rpx}
  590. .survey-title-text{font-size:24rpx;color:#333;display:block;margin-bottom:8rpx}
  591. .survey-btn{font-size:22rpx;color:#F97316;font-weight:bold}
  592. .survey-done{padding:8rpx 0}
  593. .survey-done-text{font-size:24rpx;color:#10B981}
  594. .survey-history-list{border-top:1rpx solid #f5f5f5;padding-top:12rpx}
  595. .history-label{font-size:22rpx;color:#999;display:block;margin-bottom:6rpx}
  596. .history-item{display:flex;align-items:center;padding:6rpx 0}
  597. .history-date{font-size:22rpx;color:#999;width:120rpx;flex-shrink:0}
  598. .history-content{font-size:24rpx;color:#666;flex:1}
  599. .survey-more{text-align:right;font-size:22rpx;color:#F97316;padding-top:8rpx}
  600. .survey-empty{padding:12rpx 0}
  601. .survey-empty-text{font-size:24rpx;color:#999}
  602. /* ===== 浜旂淮闂幆浣撶郴鏍峰紡 ===== */
  603. .wuxing-section { margin: 0 24rpx 16rpx; background: #fff; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
  604. .wuxing-title { font-size: 30rpx; font-weight: bold; color: #333; text-align: center; margin-bottom: 6rpx; }
  605. .wuxing-desc { font-size: 22rpx; color: #999; text-align: center; margin-bottom: 20rpx; }
  606. .wuxing-diagram { position: relative; height: 320rpx; margin: 0 auto 20rpx; }
  607. .wuxing-relations { margin-top: 8rpx; }
  608. .rel-group { margin-bottom: 16rpx; }
  609. .rel-title { font-size: 24rpx; font-weight: bold; color: #333; margin-bottom: 8rpx; display: block; }
  610. .rel-chain { display: flex; flex-wrap: wrap; align-items: center; gap: 4rpx; margin-bottom: 6rpx; }
  611. .rel-item { font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 8rpx; background: #f7f7f7; color: #555; }
  612. .rel-arrow { font-size: 20rpx; color: #10B981; font-weight: bold; }
  613. .rel-op { font-size: 20rpx; color: #f56c6c; font-weight: bold; }
  614. .rel-dot { font-size: 20rpx; color: #ccc; }
  615. .rel-hint { font-size: 20rpx; color: #999; line-height: 1.5; display: block; }
  616. .wuxing-cards { display: flex; flex-direction: column; gap: 10rpx; margin-top: 16rpx; }
  617. .wc-card { display: flex; align-items: center; padding: 16rpx 20rpx; border-radius: 16rpx; border: 2rpx solid #f0f0f0; }
  618. .wc-card:active { opacity: 0.85; }
  619. .wc-icon { font-size: 36rpx; margin-right: 16rpx; flex-shrink: 0; }
  620. .wc-info { flex: 1; min-width: 0; display: flex; flex-direction: column; }
  621. .wc-name { font-size: 24rpx; font-weight: 600; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  622. .wc-text { font-size: 20rpx; color: #999; display: block; margin-top: 4rpx; white-space: normal; }
  623. .wc-body { border-color: rgba(255,140,66,0.3); background: rgba(255,140,66,0.04); border-left: 8rpx solid #FF8C42; }
  624. .wc-body .wc-name { color: #FF8C42; }
  625. .wc-wisdom { border-color: rgba(99,102,241,0.3); background: rgba(99,102,241,0.04); border-left: 8rpx solid #6366F1; }
  626. .wc-wisdom .wc-name { color: #6366F1; }
  627. .wc-wealth { border-color: rgba(245,158,11,0.3); background: rgba(245,158,11,0.04); border-left: 8rpx solid #F59E0B; }
  628. .wc-wealth .wc-name { color: #F59E0B; }
  629. .wc-action { border-color: rgba(16,185,129,0.3); background: rgba(16,185,129,0.04); border-left: 8rpx solid #10B981; }
  630. .wc-action .wc-name { color: #10B981; }
  631. .wc-mind { border-color: rgba(255,107,157,0.3); background: rgba(255,107,157,0.04); border-left: 8rpx solid #FF6B9D; }
  632. .wc-mind .wc-name { color: #FF6B9D; }
  633. </style>