Kaynağa Gözat

feat(growth): 成长记录页 UI 重构 + 打卡页面样式统一

- growth-main/index.vue: 首页 UI 全面改版
  - Hero 区增加渐变光晕背景、品牌标语区
  - 案例卡片升级为 card-head 结构,指标改为 pill 样式
  - 五行相生相克区域视觉优化
  - 功能预览区改为 feat-grid 网格布局
- diet-checkin.vue / exercise-checkin.vue: 打卡表单样式统一
- emotion-checkin.vue: 情绪打卡输入框样式修正
- task-upload-purchase.vue: 任务购买上传区 UI 微调
Sisyphus 4 gün önce
ebeveyn
işleme
7b61d73a0c

+ 609 - 150
cfc-frontend/pages/growth-main/index.vue

@@ -1,42 +1,49 @@
 <template>
   <view class="container">
-    <!-- 通用登录浮层 -->
-    <login-sheet ref="loginSheet" @success="onSheetLoginSuccess" />
+    <login-sheet ref="loginSheet" @success="onLoginSuccess" />
     <view v-if="!isLoggedIn">
-      <!-- 品牌定位语 -->
-      <view class="brand-strap"><text class="brand-strap-text">看见改变正在发生</text></view>
-      <!-- 理念卡 -->
-      <view class="concept-card">
-        <text class="concept-quote">记录不是目的,看见改变才是。</text>
-        <text class="concept-desc">每一份饮食、运动、心情的记录,都在为成长提供依据。</text>
+      <!-- Hero 品牌区 -->
+      <view class="hero">
+        <view class="hero-glow"></view>
+        <view class="hero-content">
+          <text class="hero-eyebrow">浠艾福 · 成长记录</text>
+          <text class="hero-title">看见改变正在发生</text>
+          <text class="hero-desc">记录不是目的,看见改变才是。每一份饮食、运动、心情的记录,都在为成长提供依据。</text>
+        </view>
       </view>
-      <!-- 示例案例 -->
-      <view class="case-card">
-        <view class="case-header">
-          <text class="case-label">📖 真实案例(脱敏)</text>
+
+      <!-- 真实案例 -->
+      <view class="card case-card">
+        <view class="card-head">
+          <text class="card-head-icon">📖</text>
+          <text class="card-head-title">真实案例(脱敏)</text>
         </view>
         <view class="case-body">
           <text class="case-name">小明(10岁,体校生)</text>
           <text class="case-change">3个月干预前后:</text>
           <view class="case-metrics">
-            <view class="metric-item">
-              <text class="metric-up">↑ 5分</text>
+            <view class="metric-pill">
+              <text class="metric-value up">↑ 5分</text>
               <text class="metric-label">身维度</text>
             </view>
-            <view class="metric-item">
-              <text class="metric-up">↑ 3分</text>
+            <view class="metric-pill">
+              <text class="metric-value up">↑ 3分</text>
               <text class="metric-label">智维度</text>
             </view>
-            <view class="metric-item">
-              <text class="metric-up">↑ 8天</text>
+            <view class="metric-pill">
+              <text class="metric-value up">↑ 8天</text>
               <text class="metric-label">连续打卡</text>
             </view>
           </view>
           <text class="case-note">通过饮食记录 + 睡眠打卡,免疫力显著提升</text>
         </view>
       </view>
+
       <!-- 功能预览 -->
-      <view class="guest-features">
+      <view class="section-title-row">
+        <text class="section-title-text">解锁完整功能</text>
+      </view>
+      <view class="feat-grid">
         <view class="feat-card" @click="handleGuestFeatureTap">
           <text class="feat-icon">✅</text>
           <text class="feat-title">每日打卡</text>
@@ -58,10 +65,14 @@
           <text class="feat-desc">执行任务·完成挑战</text>
         </view>
       </view>
+
       <!-- 五维健康体系 -->
-      <view class="wuxing-section">
-        <view class="wuxing-title">身·心·智·行·富 · 可执行的成长闭环</view>
-        <view class="wuxing-desc">五行相生相克,动起来、坚持做,让成长真正发生</view>
+      <view class="card wuxing-card">
+        <view class="card-head">
+          <text class="card-head-icon">🌿</text>
+          <text class="card-head-title">身·心·智·行·富 · 可执行的成长闭环</text>
+        </view>
+        <text class="wuxing-desc">五行相生相克,动起来、坚持做,让成长真正发生</text>
         <view class="wuxing-relations">
           <view class="rel-group">
             <text class="rel-title">相生 → 系统前进</text>
@@ -125,6 +136,7 @@
           </view>
         </view>
       </view>
+
       <!-- 登录引导 -->
       <view class="login-guide-section">
         <LoginGuideCard
@@ -136,57 +148,71 @@
           ]" />
       </view>
     </view>
+
     <view v-else>
+      <!-- 未完成提醒 -->
       <view class="reminder-bar" v-if="hasIncomplete">
         <text class="reminder-text">⚠ 今日还有 {{ incompleteCount }} 项未记录</text>
         <text class="reminder-close" @click="dismissReminder">✕</text>
       </view>
-      <!-- 里程碑(最上) -->
-      <view class="section">
+
+      <!-- 里程碑统计 -->
+      <view class="section milestone-section">
         <view class="milestone-card">
-          <text class="section-title-row">🏆 里程碑</text>
+          <view class="milestone-header">
+            <text class="milestone-title">🏆 里程碑</text>
+            <text class="milestone-sub">{{ todayDate }}</text>
+          </view>
           <view class="milestone-grid">
-            <view class="milestone-item">
-              <text class="milestone-icon">🔥</text>
-              <text class="milestone-value">{{ streakDays }}天</text>
-              <text class="milestone-label">连续打卡</text>
+            <view class="milestone-tile">
+              <text class="milestone-tile-icon">🔥</text>
+              <text class="milestone-tile-value">{{ streakDays }}</text>
+              <text class="milestone-tile-label">连续打卡</text>
             </view>
-            <view class="milestone-item">
-              <text class="milestone-icon">🎯</text>
-              <text class="milestone-value">{{ activeChallenges }}</text>
-              <text class="milestone-label">进行中挑战</text>
+            <view class="milestone-tile">
+              <text class="milestone-tile-icon">🎯</text>
+              <text class="milestone-tile-value">{{ activeChallenges }}</text>
+              <text class="milestone-tile-label">进行中挑战</text>
             </view>
-            <view class="milestone-item">
-              <text class="milestone-icon">📈</text>
-              <text class="milestone-value">{{ completeness }}%</text>
-              <text class="milestone-label">档案完整度</text>
+            <view class="milestone-tile">
+              <text class="milestone-tile-icon">📈</text>
+              <text class="milestone-tile-value">{{ completeness }}%</text>
+              <text class="milestone-tile-label">档案完整度</text>
             </view>
           </view>
         </view>
       </view>
+
       <!-- 今日成长 -->
       <view class="section">
         <view class="progress-card">
           <view class="progress-header">
-            <text class="progress-title">今日成长</text>
-            <text class="progress-date">{{ todayDate }}</text>
+            <text class="progress-title">📊 今日成长</text>
+            <text class="progress-footer-text">完成 {{ doneCount }}/{{ totalCount }}</text>
           </view>
           <view class="progress-grid" v-if="progressItems.length > 0">
-            <view class="progress-grid-item" v-for="(item, idx) in progressItems" :key="getProgressKey(item)" @click="handleProgressClick(idx)">
-              <text class="progress-grid-icon">{{ item.icon }}</text>
-              <text class="progress-grid-label">{{ item.label }}</text>
-              <text class="progress-grid-status" :class="item.done ? 'status-done' : 'status-pending'">{{ item.done ? '✔' : (item.actionText || '去记录') }}</text>
+            <view class="progress-tile" v-for="(item, idx) in progressItems" :key="getProgressKey(item)" @click="handleProgressClick(idx)">
+              <view class="progress-tile-icon-wrap" :class="'color-' + item.key">
+                <text class="progress-tile-icon">{{ item.icon }}</text>
+              </view>
+              <text class="progress-tile-label">{{ item.label }}</text>
+              <text class="progress-tile-status" :class="item.done ? 'status-done' : 'status-pending'">
+                {{ item.done ? '✔' : (item.actionText || '去记录') }}
+              </text>
             </view>
           </view>
-          <view class="progress-footer">
-            <text class="footer-text">今日完成 {{ doneCount }}/{{ totalCount }}</text>
-            <text class="footer-streak" v-if="streakDays > 0">🔥 连续打卡 {{ streakDays }} 天</text>
+          <view class="streak-footer" v-if="streakDays > 0">
+            <text class="streak-badge">🔥 连续打卡 {{ streakDays }} 天</text>
           </view>
         </view>
       </view>
+
+      <!-- 食材戒备匹配 -->
       <view class="section" v-if="cautionCount > 0">
         <view class="action-card" @click="goToCaution">
-          <text class="action-icon">⚠️</text>
+          <view class="action-icon-wrap danger">
+            <text class="action-icon">⚠️</text>
+          </view>
           <view class="action-info">
             <text class="action-title">食材戒备匹配</text>
             <text class="action-desc">{{ cautionCount }} 种食材需关注,点击查看戒备详情</text>
@@ -194,10 +220,13 @@
           <text class="action-arrow">›</text>
         </view>
       </view>
+
       <!-- 饮食推荐 -->
       <view class="section">
         <view class="action-card" @click="goToFoods">
-          <text class="action-icon">🍽️</text>
+          <view class="action-icon-wrap warm">
+            <text class="action-icon">🍽️</text>
+          </view>
           <view class="action-info">
             <text class="action-title">饮食推荐</text>
             <text class="action-desc">选同住人,获取今日个性化饮食建议</text>
@@ -205,6 +234,7 @@
           <text class="action-arrow">›</text>
         </view>
       </view>
+
       <!-- 任务中心(内联) -->
       <view class="section" v-if="recentTasks.length > 0">
         <view class="task-preview-card">
@@ -226,7 +256,9 @@
           </view>
         </view>
       </view>
+
       <DimensionActivities :isLoggedIn="true" @activityClick="goActivityDetail" @moreActivities="goMoreActivities" style="margin-bottom:60rpx" />
+
       <!-- 推荐阅读 -->
       <view class="section" v-if="recommendArticles.length > 0">
         <view class="section-header">
@@ -235,6 +267,7 @@
         </view>
         <ArticleBookshelf :articles="recommendArticles" :isLoggedIn="true" @articleClick="goToArticle" />
       </view>
+
       <!-- 推荐商品(放最底部) -->
       <view class="section">
         <view class="section-header">
@@ -243,17 +276,18 @@
         </view>
         <DimensionProducts :isLoggedIn="true" :showMore="false" @productClick="goToProduct" />
       </view>
+
       <view class="bottom-spacer" style="height: 40rpx;"></view>
     </view>
   </view>
 </template>
+
 <script>
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
 import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
-import { healthCheckinList, getHealthMealList, getFoodCautionList, getGrowthRecommendations, getExerciseRecords, getSleepRecords, getEmotionCheckinList, getCheckinList, getTodayTasks, reportAppEnter
-} from '../../utils/api.js'
+import { healthCheckinList, getHealthMealList, getFoodCautionList, getGrowthRecommendations, getExerciseRecords, getSleepRecords, getEmotionCheckinList, getCheckinList, getTodayTasks, reportAppEnter } from '../../utils/api.js'
 import { parseDate } from '../../utils/format.js'
 export default {
   components: { LoginGuideCard, DimensionActivities, DimensionProducts, ArticleBookshelf },
@@ -273,7 +307,7 @@ export default {
   },
   onShow() {
     reportAppEnter('growth');
-    
+
     this.isLoggedIn = !!uni.getStorageSync('token')
     if (this.isLoggedIn) {
       this.memberId = uni.getStorageSync('currentChildId') || uni.getStorageSync('currentMemberId') || uni.getStorageSync('userId') || ''
@@ -460,111 +494,252 @@ export default {
 }
 </script>
 <style scoped lang="scss">
-// ===== 页面级局部调色板(单页专有色阶;同值才映射全局令牌,约定见 uni.scss §14) =====
-$c-warm-tint-mat:  #FFF3E0;   // Material orange-50
-$c-brand-ink-mat:  #E65100;   // Material orange-900
-$c-danger-el:      #F56C6C;   // Element 红
+// ===== 成长记录页专属色阶 =====
+$c-growth-hero-from: #F97316;
+$c-growth-hero-to:   #EA580C;
+$c-growth-hero-bg:   #FFF7ED;
+$c-growth-streak:    #F97316;
+$c-danger-soft:      #FEE2E2;
+$c-danger-ink:       #DC2626;
+
+.container {
+  background: $facade-brand-tint;
+  min-height: 100vh;
+  padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
+}
+.section { margin: 20rpx 24rpx 0; }
+
+// ---- 公共卡片基础 ----
+.card {
+  background: $app-surface;
+  border-radius: 24rpx;
+  padding: 28rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.06);
+  overflow: hidden;
+}
+.card-head {
+  display: flex;
+  align-items: center;
+  gap: 12rpx;
+  margin-bottom: 20rpx;
+}
+.card-head-icon {
+  font-size: 32rpx;
+  flex-shrink: 0;
+}
+.card-head-title {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: $facade-text-strong;
+  flex: 1;
+}
+
+// ==================== 游客态 ====================
+.hero {
+  position: relative;
+  margin: 0 24rpx 24rpx;
+  padding: 48rpx 32rpx 40rpx;
+  border-radius: 28rpx;
+  overflow: hidden;
+  background: linear-gradient(145deg, $c-growth-hero-from, $c-growth-hero-to);
+  box-shadow: 0 16rpx 48rpx rgba(249,115,22,0.32);
+  color: #fff;
+}
+.hero-glow {
+  position: absolute;
+  top: -100rpx; right: -60rpx;
+  width: 320rpx; height: 320rpx;
+  border-radius: 50%;
+  background: radial-gradient(circle, rgba(255,255,255,0.28) 0%, transparent 70%);
+}
+.hero-content {
+  position: relative; z-index: 1;
+  display: flex; flex-direction: column; gap: 12rpx;
+}
+.hero-eyebrow {
+  font-size: 22rpx;
+  opacity: 0.85;
+  letter-spacing: 2rpx;
+  text-transform: uppercase;
+}
+.hero-title {
+  font-size: 44rpx;
+  font-weight: 800;
+  line-height: 1.2;
+}
+.hero-desc {
+  font-size: 24rpx;
+  opacity: 0.92;
+  line-height: 1.7;
+}
 
-  .container{background:$facade-brand-tint;min-height:100vh;padding-bottom:calc(160rpx + env(safe-area-inset-bottom))}
-.section{margin:16rpx 24rpx}
-/* 游客态 */
-.brand-strap{margin:0 24rpx 16rpx;background:linear-gradient(135deg,$dim-action-tint,rgba($dim-action, 0.1));border-radius:16rpx;padding:20rpx 24rpx}
-.brand-strap-text{font-size:26rpx;color:$dim-action;font-weight:600}
-.concept-card{margin:0 24rpx 16rpx;background:$app-surface;border-radius:20rpx;padding:28rpx;border-left:6rpx solid $dim-action;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
-.concept-quote{font-size:30rpx;font-weight:bold;color:$facade-text-strong;display:block;margin-bottom:8rpx}
-.concept-desc{font-size:24rpx;color:$facade-text-faint;display:block;line-height:1.6}
-.case-card{margin:0 24rpx 16rpx;background:$app-surface;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
-.case-header{margin-bottom:12rpx}
-.case-label{font-size:24rpx;color:$dim-action;font-weight:bold}
-.case-body{padding:8rpx 0}
-.case-name{font-size:26rpx;color:$facade-text-strong;font-weight:600;display:block;margin-bottom:6rpx}
-.case-change{font-size:24rpx;color:$facade-text-body;display:block;margin-bottom:12rpx}
-.case-metrics{display:flex;gap:16rpx;margin-bottom:12rpx}
-.metric-item{display:flex;flex-direction:column;align-items:center;flex:1;background:$dim-action-tint;border-radius:12rpx;padding:12rpx 8rpx}
-.metric-up{font-size:26rpx;font-weight:bold;color:$dim-action}
-.metric-label{font-size:20rpx;color:$facade-text-muted;margin-top:4rpx}
-.case-note{font-size:22rpx;color:$facade-text-faint;display:block}
-.guest-features{display:flex;flex-wrap:wrap;justify-content:space-between;margin:0 24rpx 16rpx}
-.feat-card{width:calc(50% - 8rpx);box-sizing:border-box;background:$app-surface;border-radius:20rpx;padding:24rpx;margin-bottom:16rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
-.feat-icon{font-size:40rpx;display:block;margin-bottom:8rpx}
-.feat-title{font-size:26rpx;font-weight:600;color:$facade-text-strong;display:block;margin-bottom:4rpx}
-.feat-desc{font-size:22rpx;color:$facade-text-muted;display:block}
-.login-guide-section{margin:0 24rpx 24rpx}
-/* 成长行动卡片 */
-.action-card{display:flex;align-items:center;padding:24rpx 28rpx;background:$app-surface;border-radius:16rpx;margin:0 24rpx 16rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
-.action-card:active{opacity:0.85}
-.action-icon{font-size:44rpx;margin-right:20rpx}
-.action-info{flex:1}
-.action-title{font-size:28rpx;font-weight:600;color:$facade-text-strong;display:block}
-.action-desc{font-size:22rpx;color:$facade-text-faint;margin-top:4rpx;display:block}
-.action-arrow{font-size:32rpx;color:$facade-text-ghost}
-/* section-header 组件通用样式 */
-.section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12rpx}
-.section-title{font-size:28rpx;font-weight:600;color:$facade-text-strong}
-.section-more{font-size:24rpx;color:$brand-primary}
-/* 登录后 */
-.reminder-bar{margin:12rpx 24rpx;background:$c-warm-tint-mat;border:1rpx solid $uni-warning;border-radius:12rpx;padding:14rpx 20rpx;display:flex;align-items:center;justify-content:space-between}
-.reminder-text{font-size:24rpx;color:$c-brand-ink-mat}
-.reminder-close{font-size:28rpx;color:$facade-text-muted}
-/* 里程碑 */
-.milestone-card{background:$app-surface;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
-.section-title-row{font-size:26rpx;font-weight:bold;color:$facade-text-strong;margin-bottom:16rpx;display:block}
-.milestone-grid{display:flex;justify-content:space-between}
-.milestone-item{display:flex;flex-direction:column;align-items:center;flex:1}
-.milestone-icon{font-size:40rpx;margin-bottom:4rpx}
-.milestone-value{font-size:28rpx;font-weight:bold;color:$brand-primary;margin-bottom:2rpx}
-.milestone-label{font-size:22rpx;color:$facade-text-muted}
-/* 今日成长 */
-.progress-card{background:$app-surface;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
-.progress-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16rpx}
-.progress-title{font-size:28rpx;font-weight:bold;color:$facade-text-strong}
-.progress-date{font-size:22rpx;color:$facade-text-muted}
-.progress-grid{display:flex;justify-content:space-between}
-.progress-grid-item{flex:1;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:20rpx 8rpx;background:$facade-brand-tint;border-radius:16rpx;margin-right:12rpx}
-.progress-grid-item:last-child{margin-right:0}
-.progress-grid-item:active{opacity:0.85;background:$facade-brand-tint-2}
-.progress-grid-icon{font-size:40rpx;margin-bottom:8rpx}
-.progress-grid-label{font-size:24rpx;color:$facade-text-strong;margin-bottom:8rpx;text-align:center}
-.progress-grid-status{font-size:20rpx;flex-shrink:0}
-.status-done{color:$dim-action}
-.status-pending{color:$facade-text-ghost}
-.progress-footer{display:flex;justify-content:space-between;align-items:center;margin-top:16rpx;padding-top:16rpx;border-top:1rpx solid $facade-fill-2}
-.footer-text{font-size:24rpx;color:$facade-text-body;font-weight:bold}
-.footer-streak{font-size:24rpx;color:$brand-primary;font-weight:bold}
-/* 任务中心预览 */
-.task-preview-card{background:$app-surface;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
-.task-preview-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16rpx}
-.task-preview-title{font-size:28rpx;font-weight:bold;color:$facade-text-strong}
-.task-preview-more{font-size:24rpx;color:$brand-primary}
-.task-preview-list{display:flex;flex-direction:column}
-.task-preview-item{display:flex;align-items:center;padding:16rpx 0;border-bottom:1rpx solid $facade-fill-2}
-.task-preview-item:last-child{border-bottom:none}
-.task-preview-left{flex:1;min-width:0}
-.task-preview-name{font-size:26rpx;color:$facade-text-strong;display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
-.task-preview-meta{display:flex;align-items:center;gap:12rpx;margin-top:6rpx}
-.task-preview-points{font-size:22rpx;color:$brand-primary;font-weight:bold}
-.task-preview-deadline{font-size:20rpx;color:$facade-text-muted}
-.task-preview-arrow{font-size:28rpx;color:$facade-text-ghost;flex-shrink:0;margin-left:12rpx}
-/* ===== 五维闭环体系样式 ===== */
-.wuxing-section { margin: 0 24rpx 16rpx; background: $app-surface; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
-.wuxing-title { font-size: 30rpx; font-weight: bold; color: $facade-text-strong; text-align: center; margin-bottom: 6rpx; }
-.wuxing-desc { font-size: 22rpx; color: $facade-text-muted; text-align: center; margin-bottom: 20rpx; }
+.case-card { margin-bottom: 20rpx; }
+.case-body { padding: 4rpx 0; }
+.case-name {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: $facade-text-strong;
+  display: block;
+  margin-bottom: 4rpx;
+}
+.case-change {
+  font-size: 24rpx;
+  color: $facade-text-body;
+  display: block;
+  margin-bottom: 16rpx;
+}
+.case-metrics {
+  display: flex;
+  gap: 12rpx;
+  margin-bottom: 12rpx;
+}
+.metric-pill {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 16rpx 8rpx;
+  border-radius: 18rpx;
+  background: $dim-action-tint;
+  border: 1rpx solid rgba($dim-action, 0.12);
+}
+.metric-value {
+  font-size: 26rpx;
+  font-weight: 700;
+}
+.metric-value.up { color: $dim-action; }
+.metric-label {
+  font-size: 20rpx;
+  color: $facade-text-muted;
+  margin-top: 4rpx;
+}
+.case-note {
+  font-size: 22rpx;
+  color: $facade-text-faint;
+  display: block;
+}
+
+.section-title-row { margin: 28rpx 24rpx 12rpx; }
+.section-title-text {
+  font-size: 24rpx;
+  font-weight: 600;
+  color: $facade-text-muted;
+  letter-spacing: 1rpx;
+}
+
+.feat-grid {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  margin: 0 24rpx 20rpx;
+}
+.feat-card {
+  width: calc(50% - 12rpx);
+  box-sizing: border-box;
+  background: $app-surface;
+  border-radius: 24rpx;
+  padding: 28rpx 20rpx;
+  margin-bottom: 16rpx;
+  box-shadow: 0 2rpx 16rpx rgba(0,0,0,0.05);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  text-align: center;
+}
+.feat-card:active { opacity: 0.88; transform: scale(0.98); }
+.feat-icon { font-size: 48rpx; display: block; margin-bottom: 10rpx; }
+.feat-title {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: $facade-text-strong;
+  display: block;
+  margin-bottom: 6rpx;
+}
+.feat-desc {
+  font-size: 22rpx;
+  color: $facade-text-muted;
+  line-height: 1.5;
+  display: block;
+}
+
+.wuxing-card {
+  margin: 0 24rpx 20rpx;
+  background: $app-surface;
+}
+.wuxing-desc {
+  font-size: 22rpx;
+  color: $facade-text-muted;
+  text-align: center;
+  display: block;
+  margin-bottom: 20rpx;
+}
 .wuxing-relations { margin-top: 8rpx; }
-.rel-group { margin-bottom: 16rpx; }
-.rel-title { font-size: 24rpx; font-weight: bold; color: $facade-text-strong; margin-bottom: 8rpx; display: block; }
-.rel-chain { display: flex; flex-wrap: wrap; align-items: center; gap: 4rpx; margin-bottom: 6rpx; }
-.rel-item { font-size: 22rpx; padding: 4rpx 10rpx; border-radius: 8rpx; background: $facade-fill-cool; color: $facade-text-deep; }
+.rel-group { margin-bottom: 18rpx; }
+.rel-title {
+  font-size: 24rpx;
+  font-weight: 600;
+  color: $facade-text-strong;
+  margin-bottom: 8rpx;
+  display: block;
+}
+.rel-chain {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 4rpx;
+  margin-bottom: 6rpx;
+}
+.rel-item {
+  font-size: 22rpx;
+  padding: 4rpx 10rpx;
+  border-radius: 8rpx;
+  background: $facade-fill-cool;
+  color: $facade-text-deep;
+}
 .rel-arrow { font-size: 20rpx; color: $dim-action; font-weight: bold; }
-.rel-op { font-size: 20rpx; color: $c-danger-el; font-weight: bold; }
+.rel-op { font-size: 20rpx; color: $c-danger-ink; font-weight: bold; }
 .rel-dot { font-size: 20rpx; color: $facade-text-ghost; }
-.rel-hint { font-size: 20rpx; color: $facade-text-muted; line-height: 1.5; display: block; }
-.wuxing-cards { display: flex; flex-direction: column; gap: 10rpx; margin-top: 16rpx; }
-.wc-card { display: flex; align-items: center; padding: 16rpx 20rpx; border-radius: 16rpx; border: 2rpx solid $facade-fill; }
-.wc-card:active { opacity: 0.85; }
+.rel-hint {
+  font-size: 20rpx;
+  color: $facade-text-muted;
+  line-height: 1.5;
+  display: block;
+}
+.wuxing-cards {
+  display: flex;
+  flex-direction: column;
+  gap: 12rpx;
+  margin-top: 18rpx;
+  padding-top: 18rpx;
+  border-top: 1rpx solid $facade-fill-2;
+}
+.wc-card {
+  display: flex;
+  align-items: center;
+  padding: 18rpx 20rpx;
+  border-radius: 18rpx;
+  border: 2rpx solid $facade-fill;
+  background: $app-surface;
+}
+.wc-card:active { opacity: 0.88; transform: scale(0.985); }
 .wc-icon { font-size: 36rpx; margin-right: 16rpx; flex-shrink: 0; }
 .wc-info { flex: 1; min-width: 0; display: flex; flex-direction: column; }
-.wc-name { font-size: 24rpx; font-weight: 600; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
-.wc-text { font-size: 20rpx; color: $facade-text-muted; display: block; margin-top: 4rpx; white-space: normal; }
+.wc-name {
+  font-size: 24rpx;
+  font-weight: 600;
+  display: block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.wc-text {
+  font-size: 20rpx;
+  color: $facade-text-muted;
+  display: block;
+  margin-top: 4rpx;
+  white-space: normal;
+}
 .wc-body { border-color: rgba($dim-body, 0.3); background: rgba($dim-body, 0.04); border-left: 8rpx solid $dim-body; }
 .wc-body .wc-name { color: $dim-body; }
 .wc-wisdom { border-color: rgba($dim-wisdom, 0.3); background: rgba($dim-wisdom, 0.04); border-left: 8rpx solid $dim-wisdom; }
@@ -575,4 +750,288 @@ $c-danger-el:      #F56C6C;   // Element 红
 .wc-action .wc-name { color: $dim-action; }
 .wc-mind { border-color: rgba($dim-mind, 0.3); background: rgba($dim-mind, 0.04); border-left: 8rpx solid $dim-mind; }
 .wc-mind .wc-name { color: $dim-mind; }
+
+.login-guide-section { margin: 0 24rpx 28rpx; }
+
+// ==================== 已登录态 ====================
+.reminder-bar {
+  margin: 12rpx 24rpx;
+  background: linear-gradient(90deg, $c-growth-hero-bg, #FFF0DD);
+  border: 1rpx solid rgba($c-growth-hero-from, 0.25);
+  border-radius: 16rpx;
+  padding: 14rpx 20rpx;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
+}
+.reminder-text { font-size: 24rpx; color: $c-danger-ink; font-weight: 500; }
+.reminder-close { font-size: 28rpx; color: $facade-text-muted; }
+
+// ---- 里程碑 ----
+.milestone-section { margin-top: 20rpx; }
+.milestone-card {
+  background: $app-surface;
+  border-radius: 24rpx;
+  padding: 28rpx;
+  box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.06);
+}
+.milestone-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+.milestone-title {
+  font-size: 28rpx;
+  font-weight: 700;
+  color: $facade-text-strong;
+}
+.milestone-sub {
+  font-size: 22rpx;
+  color: $facade-text-muted;
+}
+.milestone-grid {
+  display: flex;
+  justify-content: space-between;
+  gap: 16rpx;
+}
+.milestone-tile {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20rpx 8rpx;
+  border-radius: 20rpx;
+  background: $facade-brand-tint;
+  border: 1rpx solid rgba($c-growth-hero-from, 0.1);
+}
+.milestone-tile-icon {
+  font-size: 40rpx;
+  display: block;
+  margin-bottom: 6rpx;
+}
+.milestone-tile-value {
+  font-size: 32rpx;
+  font-weight: 800;
+  color: $c-growth-hero-from;
+  line-height: 1;
+  margin-bottom: 4rpx;
+}
+.milestone-tile-label {
+  font-size: 20rpx;
+  color: $facade-text-muted;
+}
+
+// ---- 今日成长 ----
+.progress-card {
+  background: $app-surface;
+  border-radius: 24rpx;
+  padding: 28rpx;
+  box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.06);
+}
+.progress-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+.progress-title {
+  font-size: 28rpx;
+  font-weight: 700;
+  color: $facade-text-strong;
+}
+.progress-footer-text {
+  font-size: 22rpx;
+  color: $facade-text-muted;
+  font-weight: 500;
+}
+.progress-grid {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  gap: 12rpx;
+}
+.progress-tile {
+  width: calc(33.333% - 8rpx);
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20rpx 8rpx 16rpx;
+  border-radius: 20rpx;
+  background: $facade-fill-2;
+  border: 2rpx solid transparent;
+  transition: all 0.18s ease;
+}
+.progress-tile:active { opacity: 0.82; transform: scale(0.96); }
+.progress-tile-icon-wrap {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 22rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 10rpx;
+  background: #fff;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
+}
+.progress-tile-icon { font-size: 36rpx; }
+.color-diet { border-color: rgba($dim-body, 0.18); background: rgba($dim-body, 0.04); }
+.color-diet .progress-tile-icon-wrap { background: $dim-body-tint; }
+.color-exercise { border-color: rgba($dim-action, 0.18); background: rgba($dim-action, 0.04); }
+.color-exercise .progress-tile-icon-wrap { background: $dim-action-tint; }
+.color-sleep { border-color: rgba($dim-wisdom, 0.18); background: rgba($dim-wisdom, 0.04); }
+.color-sleep .progress-tile-icon-wrap { background: $dim-wisdom-tint; }
+.color-emotion { border-color: rgba($dim-mind, 0.18); background: rgba($dim-mind, 0.04); }
+.color-emotion .progress-tile-icon-wrap { background: $dim-mind-tint; }
+.color-checkin { border-color: rgba($dim-wealth, 0.18); background: rgba($dim-wealth, 0.04); }
+.color-checkin .progress-tile-icon-wrap { background: $dim-wealth-tint; }
+.progress-tile-label {
+  font-size: 22rpx;
+  color: $facade-text-strong;
+  font-weight: 500;
+  margin-bottom: 6rpx;
+  text-align: center;
+}
+.progress-tile-status {
+  font-size: 18rpx;
+  font-weight: 600;
+  padding: 4rpx 12rpx;
+  border-radius: 999rpx;
+  background: $facade-fill-2;
+  color: $facade-text-muted;
+}
+.status-done {
+  background: $dim-action-tint;
+  color: $dim-action;
+}
+.status-pending {
+  background: $facade-fill-2;
+  color: $facade-text-muted;
+}
+.streak-footer {
+  margin-top: 18rpx;
+  padding-top: 16rpx;
+  border-top: 1rpx solid $facade-fill-2;
+  display: flex;
+  justify-content: center;
+}
+.streak-badge {
+  font-size: 24rpx;
+  font-weight: 600;
+  color: $c-growth-streak;
+  background: $facade-brand-tint;
+  padding: 8rpx 24rpx;
+  border-radius: 999rpx;
+}
+
+// ---- 通用操作卡片 ----
+.action-card {
+  display: flex;
+  align-items: center;
+  padding: 24rpx 24rpx;
+  background: $app-surface;
+  border-radius: 20rpx;
+  margin-bottom: 12rpx;
+  box-shadow: 0 2rpx 16rpx rgba(0,0,0,0.05);
+}
+.action-card:active { opacity: 0.88; transform: scale(0.985); }
+.action-icon-wrap {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 20rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 18rpx;
+  flex-shrink: 0;
+}
+.action-icon-wrap.danger { background: $c-danger-soft; }
+.action-icon-wrap.warm { background: $dim-body-tint; }
+.action-icon { font-size: 36rpx; }
+.action-info { flex: 1; min-width: 0; }
+.action-title {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: $facade-text-strong;
+  display: block;
+}
+.action-desc {
+  font-size: 22rpx;
+  color: $facade-text-muted;
+  margin-top: 4rpx;
+  display: block;
+}
+.action-arrow { font-size: 32rpx; color: $facade-text-ghost; flex-shrink: 0; margin-left: 12rpx; }
+
+// ---- 任务预览 ----
+.task-preview-card {
+  background: $app-surface;
+  border-radius: 24rpx;
+  padding: 24rpx 28rpx;
+  box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.06);
+}
+.task-preview-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.task-preview-title {
+  font-size: 28rpx;
+  font-weight: 700;
+  color: $facade-text-strong;
+}
+.task-preview-more {
+  font-size: 24rpx;
+  color: $brand-primary;
+}
+.task-preview-list { display: flex; flex-direction: column; }
+.task-preview-item {
+  display: flex;
+  align-items: center;
+  padding: 16rpx 0;
+  border-bottom: 1rpx solid $facade-fill-2;
+}
+.task-preview-item:last-child { border-bottom: none; }
+.task-preview-left { flex: 1; min-width: 0; }
+.task-preview-name {
+  font-size: 26rpx;
+  color: $facade-text-strong;
+  display: block;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.task-preview-meta {
+  display: flex;
+  align-items: center;
+  gap: 12rpx;
+  margin-top: 6rpx;
+}
+.task-preview-points {
+  font-size: 22rpx;
+  color: $brand-primary;
+  font-weight: 700;
+}
+.task-preview-deadline { font-size: 20rpx; color: $facade-text-muted; }
+.task-preview-arrow {
+  font-size: 28rpx;
+  color: $facade-text-ghost;
+  flex-shrink: 0;
+  margin-left: 12rpx;
+}
+
+// ---- 通用 section-header ----
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12rpx;
+}
+.section-title { font-size: 28rpx; font-weight: 600; color: $facade-text-strong; }
+.section-more { font-size: 24rpx; color: $brand-primary; }
+
+.bottom-spacer { height: 40rpx; }
 </style>

+ 10 - 3
cfc-frontend/pages/growth/diet-checkin.vue

@@ -309,11 +309,12 @@ export default {
             try {
               var url = await self.uploadFile(path)
               if (url) {
+                var absUrl = url.indexOf('http') === 0 ? url : config.API_BASE_URL + url
                 var recognizeRes = await new Promise(function(resolve) {
                   uni.request({
                     url: config.API_BASE_URL + '/api/diet/record/recognize',
                     method: 'POST',
-                    data: { image_url: url },
+                    data: { image_url: absUrl },
                     header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + uni.getStorageSync('token') },
                     success: function(r) { resolve(r) },
                     fail: function() { resolve({ data: { code: 500 } }) }
@@ -404,9 +405,15 @@ export default {
           success: function(res) {
             try {
               var data = JSON.parse(res.data)
-              resolve(data.data || data.url || '')
+              if (data && data.code === 200 && data.data && data.data.url) {
+                resolve(data.data.url)
+              } else if (data && data.url) {
+                resolve(data.url)
+              } else {
+                resolve('')
+              }
             } catch (e) {
-              resolve(res.data || '')
+              resolve('')
             }
           },
           fail: function() { reject() }

+ 12 - 2
cfc-frontend/pages/growth/exercise-checkin.vue

@@ -401,8 +401,18 @@ export default {
           name: 'file',
           header: { 'Authorization': 'Bearer ' + uni.getStorageSync('token') },
           success: function(res) {
-            try { var data = JSON.parse(res.data); resolve(data.data || data.url || '') }
-            catch (e) { resolve(res.data || '') }
+            try {
+              var data = JSON.parse(res.data)
+              if (data && data.code === 200 && data.data && data.data.url) {
+                resolve(data.data.url)
+              } else if (data && data.url) {
+                resolve(data.url)
+              } else {
+                resolve('')
+              }
+            } catch (e) {
+              resolve('')
+            }
           },
           fail: function() { reject() }
         })

+ 6 - 2
cfc-frontend/pages/mind-detail/emotion-checkin.vue

@@ -785,9 +785,13 @@ export default {
             let mediaUrl = ''
             try {
               const data = JSON.parse(uploadRes.data)
-              mediaUrl = data.data || data.url || ''
+              if (data && data.code === 200 && data.data && data.data.url) {
+                mediaUrl = data.data.url
+              } else if (data && data.url) {
+                mediaUrl = data.url
+              }
             } catch (e) {
-              mediaUrl = uploadRes.data
+              mediaUrl = ''
             }
             doSubmit(mediaUrl)
           },

+ 8 - 2
cfc-frontend/pages/tasks/task-upload-purchase.vue

@@ -105,9 +105,15 @@ export default {
           success: function(res) {
             try {
               var data = JSON.parse(res.data)
-              resolve(data.data || data.url || '')
+              if (data && data.code === 200 && data.data && data.data.url) {
+                resolve(data.data.url)
+              } else if (data && data.url) {
+                resolve(data.url)
+              } else {
+                resolve('')
+              }
             } catch (e) {
-              resolve(res.data || '')
+              resolve('')
             }
           },
           fail: function() { reject() }