index.vue 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  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">看见改变正在发生</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">小明(10岁,体校生)</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="解锁完整成长档案"
  131. :items="[
  132. '查看干预进度与追踪记录',
  133. '完成每日打卡累积连续记录',
  134. '参与家庭挑战获得里程碑奖励'
  135. ]" />
  136. </view>
  137. </view>
  138. <view v-else>
  139. <PageBanner theme="action" tagline="干预 · 记录 · 成长" quote="知行合一,每天进步一点点" />
  140. <view class="reminder-bar" v-if="hasIncomplete">
  141. <text class="reminder-text">⚠ 今日还有 {{ incompleteCount }} 项未记录</text>
  142. <text class="reminder-close" @click="dismissReminder">✕</text>
  143. </view>
  144. <view class="section">
  145. <view class="progress-card">
  146. <view class="progress-header">
  147. <text class="progress-title">今日干预</text>
  148. <text class="progress-date">{{ todayDate }}</text>
  149. </view>
  150. <view class="progress-grid">
  151. <view class="progress-grid-item" v-for="(item, idx) in progressItems" :key="getProgressKey(item)" @click="handleProgressClick(item)">
  152. <text class="progress-grid-icon">{{ item.icon }}</text>
  153. <text class="progress-grid-label">{{ item.label }}</text>
  154. <text class="progress-grid-status" :class="item.done ? 'status-done' : 'status-pending'">{{ item.done ? '✔' : (item.actionText || '去记录') }}</text>
  155. </view>
  156. </view>
  157. <view class="progress-footer">
  158. <text class="footer-text">今日完成 {{ doneCount }}/{{ totalCount }}</text>
  159. <text class="footer-streak" v-if="streakDays > 0">🔥 连续打卡 {{ streakDays }} 天</text>
  160. </view>
  161. </view>
  162. </view>
  163. <view class="section" v-if="cautionCount > 0">
  164. <view class="action-card" @click="goToCaution">
  165. <text class="action-icon">⚠️</text>
  166. <view class="action-info">
  167. <text class="action-title">食材戒备匹配</text>
  168. <text class="action-desc">{{ cautionCount }} 种食材需关注,点击查看戒备详情</text>
  169. </view>
  170. <text class="action-arrow">›</text>
  171. </view>
  172. </view>
  173. <view class="section">
  174. <view class="action-card" @click="goToDiet">
  175. <text class="action-icon">🍽️</text>
  176. <view class="action-info">
  177. <text class="action-title">饮食主页</text>
  178. <text class="action-desc">今日共餐 · 食材推荐 · 饮食记录</text>
  179. </view>
  180. <text class="action-arrow">›</text>
  181. </view>
  182. </view>
  183. <view class="section">
  184. <view class="action-card" @click="goToFoods">
  185. <text class="action-icon">🥗</text>
  186. <view class="action-info">
  187. <text class="action-title">饮食推荐</text>
  188. <text class="action-desc">基于菌群报告的个性化食材建议</text>
  189. </view>
  190. <text class="action-arrow">›</text>
  191. </view>
  192. </view>
  193. <view class="section">
  194. <view class="diet-card">
  195. <view class="diet-card-header">
  196. <text class="diet-card-title">🥬 今日推荐食材</text>
  197. <text class="diet-card-link" @click="goToDiet">饮食主页 ›</text>
  198. </view>
  199. <view class="diet-card-content" v-if="ingredients.length > 0">
  200. <view class="diet-item" v-for="(item, index) in ingredients" :key="getIngredientKey(item, index)">
  201. <text class="ing-name">{{ item.name }}</text>
  202. <text class="ing-reason" v-if="item.reason">{{ item.reason }}</text>
  203. </view>
  204. </view>
  205. <view class="diet-empty" v-else>
  206. <text class="empty-text">{{ ingredientsLoading ? '加载中...' : '暂无推荐食材' }}</text>
  207. <text class="empty-hint" v-if="!ingredientsLoading">上传菌群报告后生成个性化推荐</text>
  208. </view>
  209. </view>
  210. </view>
  211. <!-- 推荐文章 -->
  212. <view class="section" v-if="healthArticles.length > 0 || growthArticles.length > 0">
  213. <view class="rec-section-title">📖 推荐阅读</view>
  214. <view class="rec-card" v-for="item in healthArticles" :key="getArticleKey(item)" @click="goToArticle(item.id)">
  215. <image class="rec-img" :src="item.coverImage" mode="aspectFill" v-if="item.coverImage"/>
  216. <view class="rec-info">
  217. <text class="rec-title">{{ item.title }}</text>
  218. <text class="rec-desc">{{ item.summary }}</text>
  219. </view>
  220. </view>
  221. <view class="rec-card" v-for="item in growthArticles" :key="getArticleKey(item)" @click="goToArticle(item.id)">
  222. <image class="rec-img" :src="item.coverImage" mode="aspectFill" v-if="item.coverImage"/>
  223. <view class="rec-info">
  224. <text class="rec-title">{{ item.title }}</text>
  225. <text class="rec-desc">{{ item.summary }}</text>
  226. </view>
  227. </view>
  228. </view>
  229. <!-- 推荐商品 -->
  230. <view class="section" v-if="healthProducts.length > 0 || growthProducts.length > 0">
  231. <view class="rec-section-title">🛒 推荐商品</view>
  232. <view class="rec-product" v-for="item in healthProducts" :key="getProductKey(item)" @click="goToProduct(item.id)">
  233. <image class="rec-pimg" :src="item.coverImage" mode="aspectFill"/>
  234. <view class="rec-pinfo">
  235. <text class="rec-pname">{{ item.name }}</text>
  236. <text class="rec-pdesc">{{ item.intro || item.description || '' }}</text>
  237. </view>
  238. </view>
  239. <view class="rec-product" v-for="item in growthProducts" :key="getProductKey(item)" @click="goToProduct(item.id)">
  240. <image class="rec-pimg" :src="item.coverImage" mode="aspectFill"/>
  241. <view class="rec-pinfo">
  242. <text class="rec-pname">{{ item.name }}</text>
  243. <text class="rec-pdesc">{{ item.intro || item.description || '' }}</text>
  244. </view>
  245. </view>
  246. </view>
  247. <view class="section">
  248. <view class="section-title-row">📊 记录概览</view>
  249. <view class="record-grid">
  250. <view class="record-card" v-for="tab in trackingTabs" :key="tab.key" @click="viewAllRecordsByType(tab.key)">
  251. <view class="record-card-header">
  252. <text class="record-card-icon">{{ tab.icon }}</text>
  253. <text class="record-card-label">{{ tab.label }}</text>
  254. </view>
  255. <view class="record-card-body" v-if="(trackingData[tab.key] || []).length > 0">
  256. <view class="record-card-item" v-for="(rec, rIdx) in (trackingData[tab.key] || []).slice(0, 2)" :key="getRecordKey(rec)">
  257. <text class="record-card-date">{{ rec._date || rec._text }}</text>
  258. <text class="record-card-content">{{ rec._content }}</text>
  259. </view>
  260. </view>
  261. <view class="record-card-empty" v-else>
  262. <text class="empty-text">暂无记录</text>
  263. </view>
  264. <view class="record-card-footer">查看全部 ›</view>
  265. </view>
  266. </view>
  267. </view>
  268. <view class="section">
  269. <view class="milestone-card">
  270. <text class="section-title-row">🏆 里程碑</text>
  271. <view class="milestone-grid">
  272. <view class="milestone-item">
  273. <text class="milestone-icon">🔥</text>
  274. <text class="milestone-value">{{ streakDays }}天</text>
  275. <text class="milestone-label">连续打卡</text>
  276. </view>
  277. <view class="milestone-item">
  278. <text class="milestone-icon">🎯</text>
  279. <text class="milestone-value">{{ activeChallenges }}</text>
  280. <text class="milestone-label">进行中挑战</text>
  281. </view>
  282. <view class="milestone-item">
  283. <text class="milestone-icon">📈</text>
  284. <text class="milestone-value">{{ completeness }}%</text>
  285. <text class="milestone-label">档案完整度</text>
  286. </view>
  287. </view>
  288. </view>
  289. </view>
  290. <view class="section">
  291. <view class="survey-card">
  292. <text class="section-title-row">📋 成长记录</text>
  293. <view class="survey-pending" v-if="pendingSurvey">
  294. <text class="survey-alert">⚠ 本周还未完成调研</text>
  295. <text class="survey-title-text">{{ pendingSurvey.title }}</text>
  296. <text class="survey-btn" @click="startSurvey">[开始填写]</text>
  297. </view>
  298. <view class="survey-done" v-else-if="allDone"><text class="survey-done-text">✅ 所有调研已完成</text></view>
  299. <view class="survey-history-list" v-if="surveyHistory.length > 0">
  300. <text class="history-label">历史调研:</text>
  301. <view class="history-item" v-for="(h, hIdx) in surveyHistory" :key="getHistoryKey(h)">
  302. <text class="history-date">{{ h._date }}</text>
  303. <text class="history-content">{{ h._content }}</text>
  304. </view>
  305. <text class="survey-more" @click="viewAllHistory">查看全部调研 ›</text>
  306. </view>
  307. <view class="survey-empty" v-else-if="!pendingSurvey"><text class="survey-empty-text">暂无待完成调研</text></view>
  308. </view>
  309. </view>
  310. <DimensionActivities :isLoggedIn="true" @activityClick="goActivityDetail" @moreActivities="goMoreActivities" style="margin-bottom:60rpx" />
  311. </view>
  312. </view>
  313. </template>
  314. <script>
  315. import PageBanner from '../../components/PageBanner.vue'
  316. import LoginGuideCard from '../../components/LoginGuideCard.vue'
  317. import DimensionActivities from '../../components/DimensionActivities.vue'
  318. import { healthCheckinList, getDietDailyRecords, getDietIngredients, getSurveyStatus, getSurveyHistory, getFoodCautionList, getGrowthRecommendations, getExerciseList, getMindCheckinList } from '../../utils/api.js'
  319. export default {
  320. components: { PageBanner, LoginGuideCard, DimensionActivities },
  321. 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,
  322. healthArticles: [],
  323. healthProducts: [],
  324. growthArticles: [],
  325. growthProducts: [],
  326. pendingSurvey: null, allDone: false, surveyHistory: [], isLoggedIn: false,
  327. ingredients: [],
  328. ingredientsLoading: false } },
  329. computed: {
  330. 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 },
  331. totalCount: function() { return this.progressItems.length }
  332. },
  333. onShow() {
  334. this.isLoggedIn = !!uni.getStorageSync('token')
  335. if (this.isLoggedIn) {
  336. this.memberId = uni.getStorageSync('currentChildId') || ''
  337. var now = new Date()
  338. this.todayDate = now.getFullYear() + '-' + this.pad(now.getMonth() + 1) + '-' + this.pad(now.getDate())
  339. this.loadAllData()
  340. }
  341. },
  342. methods: {
  343. pad: function(n) { return n < 10 ? '0' + n : '' + n },
  344. getProgressKey: function(item) { return item && item.label ? item.label : 'p-' + Math.random() },
  345. getRecordKey: function(rec) { return rec && rec._text ? rec._text + rec._content : 'r-' + Math.random() },
  346. getIngredientKey: function(item, index) { return 'ing-' + (item.id || index) },
  347. getHistoryKey: function(h) { return h && h._date ? h._date + h._content : 'h-' + Math.random() },
  348. getArticleKey: function(item) { return item && item.id ? 'a' + item.id : 'a-' + Math.random() },
  349. getProductKey: function(item) { return item && item.id ? 'p' + item.id : 'p-' + Math.random() },
  350. dismissReminder: function() { this.hasIncomplete = false },
  351. viewAllRecordsByType: function(key) {
  352. var m = { diet: '/pages/health/diet-index', exercise: '/pages/health/exercise-index', sleep: '/pages/health/sleep-index', emotion: '/pages/mind-detail/emotion-checkin' }
  353. if (m[key]) uni.navigateTo({ url: m[key] })
  354. },
  355. handleProgressClick: function(item) {
  356. if (!item || !item.key) return
  357. 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-detail/emotion-checkin' }
  358. var url = m[item.key]
  359. if (item.key === 'survey' && this.pendingSurvey && this.pendingSurvey.templateId) {
  360. url = '/pages/health/survey-questionnaire?templateId=' + this.pendingSurvey.templateId + '&templateTitle=' + encodeURIComponent(this.pendingSurvey.title) + '&templateDimension=' + (this.pendingSurvey.dimension || '')
  361. }
  362. if (url) uni.navigateTo({ url: url })
  363. },
  364. 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 || '') }) },
  365. viewAllHistory: function() { uni.navigateTo({ url: '/pages/health/survey-history?memberId=' + this.memberId }) },
  366. goActivityDetail: function(act) { uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id }) },
  367. goMoreActivities: function() { uni.navigateTo({ url: '/pages/activity/index' }) },
  368. handleGuestFeatureTap: function() { uni.showToast({ title: '登录后即可体验该功能', icon: 'none' }) },
  369. goToCaution: function() { uni.navigateTo({ url: '/pages/growth/caution-detail' }) },
  370. goToDiet: function() { uni.navigateTo({ url: '/pages/diet/index' }) },
  371. goToFoods: function() { uni.navigateTo({ url: '/pages/growth/foods-detail' }) },
  372. goToArticle: function(id) { uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id }) },
  373. goToProduct: function(id) { uni.navigateTo({ url: '/pages/shop/detail/detail?id=' + id }) },
  374. goPage: function(url) { uni.navigateTo({ url: url }) },
  375. async loadAllData() {
  376. var self = this
  377. self.loadProgressItems()
  378. self.loadSurveyData()
  379. self.loadGrowthRecommendations()
  380. self.loadCautionData()
  381. self.loadMilestones()
  382. self.loadDietIngredients()
  383. },
  384. async loadProgressItems() {
  385. var self = this
  386. var items = []
  387. var checkinDone = false
  388. try {
  389. var ciRes = await healthCheckinList({ yearMonth: self.todayDate.substring(0, 7) })
  390. if (ciRes.code === 200 && ciRes.data) {
  391. for (var ci = 0; ci < ciRes.data.length; ci++) { if (ciRes.data[ci] && ciRes.data[ci].checkinDate && ciRes.data[ci].checkinDate.substring(0, 10) === self.todayDate) { checkinDone = true; break } }
  392. }
  393. } catch(e) {}
  394. items.push({ key: 'checkin', icon: '✅', label: '今日打卡', done: checkinDone, actionText: '打卡' })
  395. var dietDone = false
  396. try {
  397. var dRes = await getDietDailyRecords({ date: self.todayDate })
  398. if (dRes.code === 200 && dRes.data && dRes.data.length > 0) dietDone = true
  399. } catch(e) {}
  400. items.push({ key: 'diet', icon: '🍽️', label: '饮食记录', done: dietDone, actionText: '补录' })
  401. var exerciseDone = false
  402. try {
  403. var exRes = await getExerciseList({})
  404. if (exRes.code === 200 && exRes.data && exRes.data.length > 0) {
  405. for (var ex = 0; ex < exRes.data.length; ex++) { var exRec = exRes.data[ex]; if (exRec && exRec.startTime && typeof exRec.startTime === 'string' && exRec.startTime.substring(0, 10) === self.todayDate) { exerciseDone = true; break } }
  406. }
  407. } catch(e) {}
  408. items.push({ key: 'exercise', icon: '🏃', label: '运动记录', done: exerciseDone, actionText: '记录' })
  409. var emotionDone = false
  410. try {
  411. var emRes = await getMindCheckinList({ page: 1, size: 20 })
  412. if (emRes.code === 200 && emRes.data && emRes.data.length > 0) {
  413. for (var em = 0; em < emRes.data.length; em++) { var emRec = emRes.data[em]; if (emRec && emRec.checkinDate && typeof emRec.checkinDate === 'string' && emRec.checkinDate.substring(0, 10) === self.todayDate) { emotionDone = true; break } }
  414. }
  415. } catch(e) {}
  416. items.push({ key: 'emotion', icon: '😊', label: '心情记录', done: emotionDone, actionText: '记录' })
  417. items.push({ key: 'survey', icon: '📋', label: '本周调研', done: self.allDone, actionText: self.allDone ? '' : '开始' })
  418. self.progressItems = items.filter(function(it) { return it && it.key })
  419. self.incompleteCount = 0
  420. for (var pi = 0; pi < items.length; pi++) if (items[pi] && !items[pi].done) self.incompleteCount++
  421. self.hasIncomplete = self.incompleteCount > 0
  422. },
  423. async loadSurveyData() {
  424. var self = this
  425. try {
  426. var statusRes = await getSurveyStatus({})
  427. if (statusRes.code === 200 && statusRes.data) {
  428. var list = statusRes.data
  429. self.pendingSurvey = null; self.allDone = true; var hasAny = false
  430. 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 } }
  431. if (!hasAny) { self.pendingSurvey = null; self.allDone = false }
  432. }
  433. } catch(e) {}
  434. try {
  435. var histRes = await getSurveyHistory({})
  436. if (histRes.code === 200 && histRes.data) {
  437. var hlist = histRes.data; self.surveyHistory = []
  438. 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: '已完成调研' }) }
  439. }
  440. } catch(e) {}
  441. },
  442. async loadGrowthRecommendations() {
  443. var self = this
  444. try {
  445. var res = await getGrowthRecommendations('health', 6)
  446. if (res && res.code === 200 && res.data) {
  447. var arts = res.data.articles || []
  448. var prods = res.data.products || []
  449. // 按 growth_category 分组
  450. self.healthArticles = arts.filter(function(a){ return a.growthCategory === 'health' })
  451. self.healthProducts = prods.filter(function(p){ return p.growthCategory === 'health' })
  452. self.growthArticles = arts.filter(function(a){ return a.growthCategory === 'growth' })
  453. self.growthProducts = prods.filter(function(p){ return p.growthCategory === 'growth' })
  454. }
  455. } catch(e) {}
  456. },
  457. async loadCautionData() {
  458. var self = this
  459. try {
  460. var res = await getFoodCautionList()
  461. if (res && res.code === 200) self.cautionCount = (res.data || []).length
  462. } catch(e) {}
  463. },
  464. async loadMilestones() {
  465. var self = this
  466. try {
  467. var ciRes = await healthCheckinList({})
  468. if (ciRes.code === 200 && ciRes.data) self.streakDays = Math.min(ciRes.data.length, 30)
  469. } catch(e) {}
  470. self.activeChallenges = 0
  471. self.completeness = self.streakDays > 0 ? Math.min(100, Math.round(self.streakDays * 10)) : 0
  472. },
  473. async loadDietIngredients() {
  474. var self = this
  475. self.ingredientsLoading = true
  476. try {
  477. var res = await getDietIngredients()
  478. self.ingredientsLoading = false
  479. if (res && res.ingredients) {
  480. self.ingredients = res.ingredients
  481. }
  482. } catch(e) { self.ingredientsLoading = false; console.error('loadDietIngredients error:', e) }
  483. }
  484. }
  485. }
  486. </script>
  487. <style scoped>
  488. .container{background:#FFF7ED;min-height:100vh;padding-bottom:calc(160rpx + env(safe-area-inset-bottom))}
  489. .section{margin:16rpx 24rpx}
  490. /* 游客态 */
  491. .brand-strap{margin:0 24rpx 16rpx;background:linear-gradient(135deg,#F0FFF4,rgba(16,185,129,0.1));border-radius:16rpx;padding:20rpx 24rpx}
  492. .brand-strap-text{font-size:26rpx;color:#10B981;font-weight:600}
  493. .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)}
  494. .concept-quote{font-size:30rpx;font-weight:bold;color:#333;display:block;margin-bottom:8rpx}
  495. .concept-desc{font-size:24rpx;color:#888;display:block;line-height:1.6}
  496. .case-card{margin:0 24rpx 16rpx;background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  497. .case-header{margin-bottom:12rpx}
  498. .case-label{font-size:24rpx;color:#10B981;font-weight:bold}
  499. .case-body{padding:8rpx 0}
  500. .case-name{font-size:26rpx;color:#333;font-weight:600;display:block;margin-bottom:6rpx}
  501. .case-change{font-size:24rpx;color:#666;display:block;margin-bottom:12rpx}
  502. .case-metrics{display:flex;gap:16rpx;margin-bottom:12rpx}
  503. .metric-item{display:flex;flex-direction:column;align-items:center;flex:1;background:#F0FFF4;border-radius:12rpx;padding:12rpx 8rpx}
  504. .metric-up{font-size:26rpx;font-weight:bold;color:#10B981}
  505. .metric-label{font-size:20rpx;color:#999;margin-top:4rpx}
  506. .case-note{font-size:22rpx;color:#888;display:block}
  507. .guest-features{display:flex;flex-wrap:wrap;justify-content:space-between;margin:0 24rpx 16rpx}
  508. .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)}
  509. .feat-icon{font-size:40rpx;display:block;margin-bottom:8rpx}
  510. .feat-title{font-size:26rpx;font-weight:600;color:#333;display:block;margin-bottom:4rpx}
  511. .feat-desc{font-size:22rpx;color:#999;display:block}
  512. .login-guide-section{margin:0 24rpx 24rpx}
  513. /* 成长行动卡片 */
  514. .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)}
  515. .action-card:active{opacity:0.85}
  516. .action-icon{font-size:44rpx;margin-right:20rpx}
  517. .action-info{flex:1}
  518. .action-title{font-size:28rpx;font-weight:600;color:#333;display:block}
  519. .action-desc{font-size:22rpx;color:#888;margin-top:4rpx;display:block}
  520. .action-arrow{font-size:32rpx;color:#ccc}
  521. /* 成长推荐卡片 */
  522. .rec-section-title{font-size:28rpx;font-weight:600;color:#333;padding:20rpx 28rpx 8rpx}
  523. .rec-card{display:flex;background:#fff;border-radius:16rpx;margin:0 24rpx 12rpx;padding:16rpx;box-shadow:0 2rpx 8rpx rgba(0,0,0,0.06)}
  524. .rec-card:active{opacity:0.85}
  525. .rec-img{width:160rpx;height:110rpx;border-radius:12rpx;flex-shrink:0;margin-right:16rpx;background:#f5f5f5}
  526. .rec-info{flex:1;min-width:0}
  527. .rec-title{font-size:26rpx;font-weight:600;color:#333;display:block;margin-bottom:6rpx;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  528. .rec-desc{font-size:22rpx;color:#888;display:block;line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
  529. .rec-product{display:flex;background:#fff;border-radius:16rpx;margin:0 24rpx 12rpx;padding:16rpx;box-shadow:0 2rpx 8rpx rgba(0,0,0,0.06)}
  530. .rec-product:active{opacity:0.85}
  531. .rec-pimg{width:140rpx;height:140rpx;border-radius:12rpx;flex-shrink:0;margin-right:16rpx;background:#f5f5f5}
  532. .rec-pinfo{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}
  533. .rec-pname{font-size:26rpx;font-weight:600;color:#333;display:block;margin-bottom:6rpx;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  534. .rec-pdesc{font-size:22rpx;color:#888;display:block;line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
  535. /* 登录后原有样式 */
  536. .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}
  537. .reminder-text{font-size:24rpx;color:#E65100}
  538. .reminder-close{font-size:28rpx;color:#999}
  539. .progress-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  540. .progress-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16rpx}
  541. .progress-title{font-size:28rpx;font-weight:bold;color:#333}
  542. .progress-date{font-size:22rpx;color:#999}
  543. .progress-grid{display:flex;flex-wrap:wrap;gap:12rpx}
  544. .progress-grid-item{width:calc(33.33% - 8rpx);box-sizing:border-box;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#FFF7ED;border-radius:16rpx;padding:20rpx 12rpx;border:1rpx solid rgba(249,115,22,0.12)}
  545. .progress-grid-item:active{opacity:0.85;background:#FFEDD5}
  546. .progress-grid-icon{font-size:40rpx;margin-bottom:8rpx}
  547. .progress-grid-label{font-size:24rpx;color:#333;margin-bottom:8rpx;text-align:center}
  548. .progress-grid-status{font-size:20rpx;flex-shrink:0}
  549. .status-done{color:#10B981}
  550. .status-pending{color:#F97316}
  551. .progress-footer{display:flex;justify-content:space-between;align-items:center;margin-top:16rpx;padding-top:16rpx;border-top:1rpx solid #f5f5f5}
  552. .footer-text{font-size:24rpx;color:#666;font-weight:bold}
  553. .footer-streak{font-size:24rpx;color:#F97316;font-weight:bold}
  554. /* 记录概览:平铺卡片 */
  555. .record-grid{display:flex;flex-wrap:wrap;justify-content:space-between}
  556. .record-card{width:calc(50% - 8rpx);box-sizing:border-box;background:#fff;border-radius:20rpx;padding:20rpx;margin-bottom:16rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  557. .record-card:active{opacity:0.85}
  558. .record-card-header{display:flex;align-items:center;margin-bottom:12rpx}
  559. .record-card-icon{font-size:32rpx;margin-right:8rpx}
  560. .record-card-label{font-size:26rpx;font-weight:bold;color:#333}
  561. .record-card-body{display:flex;flex-direction:column}
  562. .record-card-item{display:flex;flex-direction:column;padding:10rpx 0;border-bottom:1rpx solid #f7f7f7}
  563. .record-card-item:last-child{border-bottom:none}
  564. .record-card-date{font-size:20rpx;color:#999;margin-bottom:2rpx}
  565. .record-card-content{font-size:24rpx;color:#666;overflow:hidden;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical}
  566. .record-card-empty{display:flex;align-items:center;justify-content:center;padding:24rpx 0}
  567. .record-card-empty .empty-text{font-size:22rpx;color:#bbb}
  568. .record-card-footer{font-size:22rpx;color:#F97316;text-align:right;padding-top:10rpx}
  569. /* 今日推荐食材 */
  570. .diet-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  571. .diet-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16rpx}
  572. .diet-card-title{font-size:28rpx;font-weight:bold;color:#333}
  573. .diet-card-link{font-size:24rpx;color:#F97316}
  574. .diet-card-content{display:flex;flex-direction:column}
  575. .diet-item{display:flex;flex-direction:column;padding:16rpx 0;border-bottom:1rpx solid #f5f5f5}
  576. .diet-item:last-child{border-bottom:none}
  577. .ing-name{font-size:26rpx;font-weight:bold;color:#333;margin-bottom:4rpx}
  578. .ing-reason{font-size:22rpx;color:#888}
  579. .diet-empty{display:flex;flex-direction:column;align-items:center;padding:40rpx 0}
  580. .diet-empty .empty-text{font-size:26rpx;color:#999}
  581. .diet-empty .empty-hint{font-size:22rpx;color:#ccc;margin-top:8rpx}
  582. .milestone-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  583. .section-title-row{font-size:26rpx;font-weight:bold;color:#333;margin-bottom:16rpx;display:block}
  584. .milestone-grid{display:flex;justify-content:space-between}
  585. .milestone-item{display:flex;flex-direction:column;align-items:center;flex:1}
  586. .milestone-icon{font-size:40rpx;margin-bottom:4rpx}
  587. .milestone-value{font-size:28rpx;font-weight:bold;color:#F97316;margin-bottom:2rpx}
  588. .milestone-label{font-size:22rpx;color:#999}
  589. .survey-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
  590. .survey-pending{background:#FFF3E0;border-radius:12rpx;padding:16rpx;margin-bottom:12rpx}
  591. .survey-alert{font-size:24rpx;color:#E65100;font-weight:bold;display:block;margin-bottom:4rpx}
  592. .survey-title-text{font-size:24rpx;color:#333;display:block;margin-bottom:8rpx}
  593. .survey-btn{font-size:22rpx;color:#F97316;font-weight:bold}
  594. .survey-done{padding:8rpx 0}
  595. .survey-done-text{font-size:24rpx;color:#10B981}
  596. .survey-history-list{border-top:1rpx solid #f5f5f5;padding-top:12rpx}
  597. .history-label{font-size:22rpx;color:#999;display:block;margin-bottom:6rpx}
  598. .history-item{display:flex;align-items:center;padding:6rpx 0}
  599. .history-date{font-size:22rpx;color:#999;width:120rpx;flex-shrink:0}
  600. .history-content{font-size:24rpx;color:#666;flex:1}
  601. .survey-more{text-align:right;font-size:22rpx;color:#F97316;padding-top:8rpx}
  602. .survey-empty{padding:12rpx 0}
  603. .survey-empty-text{font-size:24rpx;color:#999}
  604. /* ===== 五维闭环体系样式 ===== */
  605. .wuxing-section { margin: 0 24rpx 16rpx; background: #fff; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
  606. .wuxing-title { font-size: 30rpx; font-weight: bold; color: #333; text-align: center; margin-bottom: 6rpx; }
  607. .wuxing-desc { font-size: 22rpx; color: #999; text-align: center; margin-bottom: 20rpx; }
  608. .wuxing-diagram { position: relative; height: 320rpx; margin: 0 auto 20rpx; }
  609. .wuxing-relations { margin-top: 8rpx; }
  610. .rel-group { margin-bottom: 16rpx; }
  611. .rel-title { font-size: 24rpx; font-weight: bold; color: #333; margin-bottom: 8rpx; display: block; }
  612. .rel-chain { display: flex; flex-wrap: wrap; align-items: center; gap: 4rpx; margin-bottom: 6rpx; }
  613. .rel-item { font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 8rpx; background: #f7f7f7; color: #555; }
  614. .rel-arrow { font-size: 20rpx; color: #10B981; font-weight: bold; }
  615. .rel-op { font-size: 20rpx; color: #f56c6c; font-weight: bold; }
  616. .rel-dot { font-size: 20rpx; color: #ccc; }
  617. .rel-hint { font-size: 20rpx; color: #999; line-height: 1.5; display: block; }
  618. .wuxing-cards { display: flex; flex-direction: column; gap: 10rpx; margin-top: 16rpx; }
  619. .wc-card { display: flex; align-items: center; padding: 16rpx 20rpx; border-radius: 16rpx; border: 2rpx solid #f0f0f0; }
  620. .wc-card:active { opacity: 0.85; }
  621. .wc-icon { font-size: 36rpx; margin-right: 16rpx; flex-shrink: 0; }
  622. .wc-info { flex: 1; min-width: 0; display: flex; flex-direction: column; }
  623. .wc-name { font-size: 24rpx; font-weight: 600; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  624. .wc-text { font-size: 20rpx; color: #999; display: block; margin-top: 4rpx; white-space: normal; }
  625. .wc-body { border-color: rgba(255,140,66,0.3); background: rgba(255,140,66,0.04); border-left: 8rpx solid #FF8C42; }
  626. .wc-body .wc-name { color: #FF8C42; }
  627. .wc-wisdom { border-color: rgba(99,102,241,0.3); background: rgba(99,102,241,0.04); border-left: 8rpx solid #6366F1; }
  628. .wc-wisdom .wc-name { color: #6366F1; }
  629. .wc-wealth { border-color: rgba(245,158,11,0.3); background: rgba(245,158,11,0.04); border-left: 8rpx solid #F59E0B; }
  630. .wc-wealth .wc-name { color: #F59E0B; }
  631. .wc-action { border-color: rgba(16,185,129,0.3); background: rgba(16,185,129,0.04); border-left: 8rpx solid #10B981; }
  632. .wc-action .wc-name { color: #10B981; }
  633. .wc-mind { border-color: rgba(255,107,157,0.3); background: rgba(255,107,157,0.04); border-left: 8rpx solid #FF6B9D; }
  634. .wc-mind .wc-name { color: #FF6B9D; }
  635. </style>