2026-08-28-diet-redesign.md 20 KB

共餐 & 食谱页面美化 实现计划

面向 AI 代理的工作者: 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(- [ ])语法来跟踪进度。

目标: 按「🍳 暖灶 · 家的味道」设计重做三个活跃页面:pages/diet/index.vue(饮食首页)、pages/diet/meal-config.vue(共餐配置)、pages/diet/recommendation.vue(食谱推荐)。纯样式/布局/文案重做,零接口与业务逻辑改动。

架构: 每个 .vue 在 scoped <style> 顶部定义页面级 CSS 令牌变量(主橙 #FF8C42 渐变、米底 #FFF7ED、深橙 #C2410C、嫩绿 #10B981),template 结构调整为新的视觉区块,methods/data 逻辑保持不变。

技术栈: uni-app Vue 2(Options API),微信小程序目标;flexbox 布局;无新依赖。

规格文档: cfc-frontend/docs/superpowers/specs/2026-08-28-diet-redesign.md(唯一视觉契约,本节不重复细节,务必对照实现)


文件结构

文件 职责 变更类型
cfc-frontend/pages/diet/index.vue 饮食首页:今日餐桌主视觉 + 今日菜篮 + 厨房导航 + 本周饮食 修改 template + style
cfc-frontend/pages/diet/meal-config.vue 共餐配置:胶囊 tabs + 渐变餐次卡 + 成员 chips 修改 template + style
cfc-frontend/pages/diet/recommendation.vue 食谱推荐:空态插图 + 餐次菜单卡 + 营养汇总 修改 template + style

三个页面相互独立、无共享状态,可并行实现(同一文件多处改动时避免并发冲突,按任务顺序在各自 commit 边界内操作)。

全局校验命令(每个任务提交前必跑)

# 1) 提取 .vue 的 <script> 块做 JS 语法校验(在 cfc-frontend 下)
node -e "
const fs=require('fs');
const s=fs.readFileSync(process.argv[1],'utf8');
const m=s.match(/<script>([\s\S]*?)<\/script>/);
if(!m){console.log('no script');process.exit(0)}
new Function(m[1].replace(/import\s[^;]+;/g,'').replace(/export\s+default/,'return'));
console.log('script OK');
" pages/diet/index.vue

# 2) 禁止模式扫描(输出应为空)
grep -nE '\?\.|display:\s*grid|:key="[^"]*(\|\||&&|\+)' pages/diet/index.vue pages/diet/meal-config.vue pages/diet/recommendation.vue

# 3) 中文类名扫描(输出应为空)
grep -nE 'class="[^"]*[\u4e00-\u9fa5]|\.\w*[\u4e00-\u9fa5]' pages/diet/index.vue pages/diet/meal-config.vue pages/diet/recommendation.vue

注意:代理禁止执行 npm run build:mp-weixin / npm run dev:mp-weixin(项目规范),打包由用户用 HBuilderX 完成。


任务 1:diet/index.vue — 今日餐桌主视觉(替换原 page-header + 今日共餐卡片)

文件: 修改 cfc-frontend/pages/diet/index.vue

  • 步骤 1:重写页面顶部 template(保留数据绑定,改造区块)

将原 <view class="page-header">...</view> + 「今日共餐」<view class="card"> 合并为一个暖橙渐变主视觉大卡:

<!-- ===== 今日餐桌主视觉 ===== -->
<view class="dining-hero">
  <view class="hero-top">
    <view class="hero-icon-wrap"><text class="hero-icon">🍳</text></view>
    <view class="hero-text">
      <text class="hero-title">今日共餐</text>
      <text class="hero-subtitle">{{ heroSubtitle }}</text>
    </view>
  </view>
  <view class="hero-meals" v-if="hasMealConfig">
    <view class="hero-meal" v-for="m in heroMeals" :key="m.key">
      <text class="hero-meal-emoji">{{ m.emoji }}</text>
      <text class="hero-meal-label">{{ m.label }}</text>
      <text class="hero-meal-count">{{ m.count || '—' }}</text>
    </view>
  </view>
  <view class="hero-empty" v-else @tap="navTo('/pages/diet/meal-config')">
    <text class="hero-empty-text">还没设置共餐?点这里安排家人一起吃饭</text>
    <view class="hero-empty-btn">去设置共餐</view>
  </view>
</view>
  • 步骤 2:在 <script> 增加 heroSubtitle / heroMeals / hasMealConfig 计算字段(不改接口)

在 computed 中新增(methods 原有逻辑不动):

computed: {
  heroSubtitle: function() {
    var self = this
    return (self.isWeekend(new Date()) ? '周末' : '工作日') + ' · 全家一起吃饭'
  },
  heroMeals: function() {
    var defs = [
      { key: 'breakfast', label: '早餐', emoji: '🌅' },
      { key: 'lunch', label: '午餐', emoji: '☀️' },
      { key: 'dinner', label: '晚餐', emoji: '🌙' }
    ]
    var self = this
    return defs.map(function(d) {
      return { key: d.key, label: d.label, emoji: d.emoji, count: self.mealSummary[d.key] || '' }
    })
  },
  hasMealConfig: function() {
    return !!(this.mealSummary.breakfast || this.mealSummary.lunch || this.mealSummary.dinner)
  }
}
  • [ ] 步骤 3:新增今日餐桌主视觉样式(在 <style scoped> 顶部先定义页面令牌)

    /* ===== 页面令牌(暖灶·家的味道) ===== */
    .diet-page {
    min-height: 100vh;
    background: linear-gradient(180deg, #FFF7ED 0%, #FFF3E6 100%);
    overflow: hidden;
    }
    .dining-hero {
    background: linear-gradient(135deg, #FF8C42, #FFB366);
    border-radius: 32rpx;
    padding: 36rpx 32rpx;
    margin-bottom: 24rpx;
    box-shadow: 0 8rpx 32rpx rgba(255, 140, 66, 0.25);
    }
    .hero-top { display: flex; align-items: center; margin-bottom: 24rpx; }
    .hero-icon-wrap {
    width: 88rpx; height: 88rpx; border-radius: 50%;
    background: rgba(255,255,255,0.22);
    display: flex; align-items: center; justify-content: center;
    margin-right: 20rpx; flex-shrink: 0;
    }
    .hero-icon { font-size: 44rpx; }
    .hero-title { font-size: 38rpx; font-weight: 700; color: #FFFFFF; display: block; }
    .hero-subtitle { font-size: 24rpx; color: rgba(255,255,255,0.85); margin-top: 6rpx; display: block; }
    .hero-meals { display: flex; }
    .hero-meal {
    flex: 1; margin: 0 8rpx;
    background: rgba(255,255,255,0.18);
    border-radius: 20rpx; padding: 20rpx 8rpx;
    display: flex; flex-direction: column; align-items: center;
    }
    .hero-meal:first-child { margin-left: 0; }
    .hero-meal:last-child { margin-right: 0; }
    .hero-meal-emoji { font-size: 40rpx; margin-bottom: 8rpx; }
    .hero-meal-label { font-size: 24rpx; color: #FFFFFF; }
    .hero-meal-count { font-size: 22rpx; color: rgba(255,255,255,0.9); margin-top: 6rpx; font-weight: 600; }
    .hero-empty { display: flex; flex-direction: column; align-items: center; padding: 12rpx 0; }
    .hero-empty-text { font-size: 26rpx; color: #FFFFFF; margin-bottom: 20rpx; }
    .hero-empty-btn {
    background: #FFFFFF; color: #C2410C; font-size: 28rpx; font-weight: 600;
    padding: 18rpx 44rpx; border-radius: 999rpx;
    }
    
  • [ ] 步骤 4:运行全局校验命令(见上文「全局校验命令」)确认无脚本语法错误、无禁止模式

  • [ ] 步骤 5:Commit

    git add cfc-frontend/pages/diet/index.vue
    git commit -m "feat(frontend): 饮食首页今日餐桌主视觉(暖灶主题)"
    

任务 2:diet/index.vue — 今日菜篮 + 厨房导航 + 本周饮食

文件: 修改 cfc-frontend/pages/diet/index.vue(承接任务 1)

  • 步骤 1:改造「今日食材推荐」卡为「今日菜篮」

template 头部改为(保留 action-bar 三个按钮的绑定与事件不动):

<view class="card">
  <view class="card-header">
    <view class="card-basket-icon"><text class="card-basket-emoji">🧺</text></view>
    <text class="card-title">今日菜篮</text>
  </view>
  <view class="card-body">
    <!-- 三个按钮:class 更新,事件绑定原样 -->
    <view class="action-bar">
      <view class="action-btn" @tap="refreshIngredients">
        <text class="action-btn-text">🔄 换一批</text>
      </view>
      <view class="action-btn action-btn-primary" @tap="showFoodPicker">
        <text class="action-btn-text-primary">+ 添加食材</text>
      </view>
      <view class="action-btn action-btn-accent" @tap="generateRecipe"
        :class="{'action-btn-disabled': selectedIngredients.length === 0}">
        <text class="action-btn-text-accent">✨ 生成食谱</text>
      </view>
    </view>
    <!-- 食材列表保留现有 v-for 结构 -->
  </view>
</view>
  • 步骤 2:更新食材列表项与按钮样式(嫩绿生成按钮 + 淡橙选中态)

在 <style scoped> 中替换/新增:

.card-basket-icon {
  width: 64rpx; height: 64rpx; border-radius: 18rpx;
  background: linear-gradient(135deg, #FF8C42, #FFB366);
  display: flex; align-items: center; justify-content: center;
  margin-right: 16rpx; flex-shrink: 0;
}
.card-basket-emoji { font-size: 32rpx; }
.action-btn-accent { background: linear-gradient(135deg, #10B981, #34D399); }
.action-btn-text-accent { color: #FFFFFF; }
.ingredient-item:active { background: #FFF9F0; }
.ingredient-badge {
  background: linear-gradient(135deg, #FF8C42, #FFB366);
  /* 保留现有圆角方块结构与其余属性 */
}
  • 步骤 3:厨房导航 5 格改暖橙系深浅渐变(保留 20% 网格与跳转)

替换 nav-icon-* 五个背景为暖调(可整体替换五个类名背景值,类名保留以免改 template):

.nav-icon-green { background: linear-gradient(135deg, #FFF1E6, #FFE0C0); }
.nav-icon-blue  { background: linear-gradient(135deg, #FFF7ED, #FFEEDD); }
.nav-icon-orange{ background: linear-gradient(135deg, #FF8C42, #FFB366); }
.nav-icon-purple{ background: linear-gradient(135deg, #FFE8D6, #FFD4B0); }
.nav-icon-red   { background: linear-gradient(135deg, #E8F8F1, #C8F0E0); }
.nav-item:active .nav-icon-wrap { transform: scale(0.92); }
.nav-icon-wrap { transition: transform 0.15s ease; }
  • [ ] 步骤 4:本周饮食卡暖化(分隔线浅橙 + 大数字橙字已具,改分隔线)

    .trend-divider { background: #FDE8D0; }
    
  • [ ] 步骤 5:运行全局校验命令确认无禁止模式

  • [ ] 步骤 6:Commit

    git add cfc-frontend/pages/diet/index.vue
    git commit -m "feat(frontend): 饮食首页菜篮/导航/趋势暖灶主题化"
    

任务 3:diet/meal-config.vue — 共餐配置页美化

文件: 修改 cfc-frontend/pages/diet/meal-config.vue

  • 步骤 1:日期切换改两段式胶囊 tabs

template 的 date-tabs 保留结构,样式整体替换:

<view class="date-tabs">
  <view class="tab-item" :class="{'tab-active': dateType === 'weekday'}" @tap="switchDateType('weekday')">
    <text class="tab-text">工作日</text>
  </view>
  <view class="tab-item" :class="{'tab-active': dateType === 'weekend'}" @tap="switchDateType('weekend')">
    <text class="tab-text">周末</text>
  </view>
</view>
.date-tabs {
  display: flex; background: #FFF1E6; border-radius: 999rpx;
  padding: 6rpx; margin-bottom: 30rpx;
}
.tab-item {
  flex: 1; text-align: center; padding: 18rpx 0;
  border-radius: 999rpx; font-size: 28rpx; color: #C2410C;
}
.tab-active {
  background: linear-gradient(135deg, #FF8C42, #FFB366);
  color: #FFFFFF; font-weight: 600;
  box-shadow: 0 4rpx 12rpx rgba(255, 140, 66, 0.3);
}
  • 步骤 2:餐次卡 icon 渐变圆底 + 已选人数角标

template 的 meal-header 增加已选人数:

<view class="meal-header">
  <view class="meal-icon-wrap" :class="'meal-icon-' + meal.value"><text class="meal-icon">{{ meal.icon }}</text></view>
  <text class="meal-name">{{ meal.label }}</text>
  <text class="meal-count" v-if="meal.selectedCount > 0">已选 {{ meal.selectedCount }} 人</text>
</view>

同步在 computed.mealGroups 的返回对象中增加 selectedCount: ids.length。

.meal-icon-wrap { width: 72rpx; height: 72rpx; border-radius: 22rpx; display: flex; align-items: center; justify-content: center; margin-right: 16rpx; }
.meal-icon-breakfast { background: linear-gradient(135deg, #FFB366, #FFD48A); }
.meal-icon-lunch     { background: linear-gradient(135deg, #FBBF24, #FCD34D); }
.meal-icon-dinner    { background: linear-gradient(135deg, #A78BFA, #C4B5FD); }
.meal-count { font-size: 24rpx; color: #C2410C; }
  • 步骤 3:成员 chips 选中态 + 彩色渐变字母圈

替换 member-avatar / member-selected 样式:

.member-item {
  box-sizing: border-box; position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 16rpx 20rpx; border: 2rpx solid #E0E0E0; border-radius: 20rpx;
  background: #FAFAFA; min-width: 132rpx;
  transition: all 0.15s ease;
}
.member-selected {
  border-color: #FF8C42; border-width: 3rpx;
  background: #FFF5EB; box-shadow: 0 0 0 4rpx rgba(255, 140, 66, 0.18);
}
.member-avatar {
  width: 60rpx; height: 60rpx; border-radius: 50%;
  color: #FFFFFF; display: flex; align-items: center; justify-content: center;
  font-size: 24rpx; font-weight: 600; margin-bottom: 8rpx;
}
/* 底色按 gender:需在 template 的成员对象上加 _avatarCls,methods 中根据 m.gender 映射 */
.member-avatar-male   { background: linear-gradient(135deg, #5B9BD5, #8FC5E8); }
.member-avatar-female { background: linear-gradient(135deg, #F472B6, #F9A8D4); }
.member-avatar-unknown{ background: linear-gradient(135deg, #FF8C42, #FFB366); }

注意:gender 在 meal.members 数据中来自 /api/family/member/list 的 member.gender。在 computed.mealGroups 组装时给每个成员加 _avatarCls(用 m.gender === 'male' ? 'member-avatar-male' : (m.gender === 'female' ? 'member-avatar-female' : 'member-avatar-unknown')),template 用 :class="member._avatarCls"。禁止在 template 内调用方法返回类名。

  • [ ] 步骤 4:提示条暖橙化 + 保存按钮渐变

    .tip-card { background: #FFF1E6; border-radius: 16rpx; padding: 24rpx 30rpx; margin-bottom: 30rpx; }
    .tip-text { font-size: 24rpx; color: #C2410C; line-height: 1.6; }
    .save-btn {
    background: linear-gradient(135deg, #FF8C42, #FFB366);
    color: #FFFFFF; text-align: center; padding: 28rpx 0; border-radius: 999rpx;
    font-size: 30rpx; font-weight: 600;
    box-shadow: 0 6rpx 20rpx rgba(255, 140, 66, 0.25);
    }
    
  • [ ] 步骤 5:运行全局校验命令确认无禁止模式

  • [ ] 步骤 6:Commit

    git add cfc-frontend/pages/diet/meal-config.vue
    git commit -m "feat(frontend): 共餐配置页暖灶主题美化"
    

任务 4:diet/recommendation.vue — 食谱推荐页美化

文件: 修改 cfc-frontend/pages/diet/recommendation.vue

  • 步骤 1:空态改插图式(暖橙渐变圆 + 主文案 + 生成按钮)

template 替换空态区块:

<view class="empty-state" v-if="!recommendation">
  <view class="empty-icon-wrap"><text class="empty-icon">🍽️</text></view>
  <text class="empty-title">今日菜单空着</text>
  <text class="empty-sub">点击生成今日三餐食谱</text>
  <view class="generate-btn" @tap="generateRecipe">
    <text>生成食谱</text>
  </view>
</view>
.empty-state { display: flex; flex-direction: column; align-items: center; padding: 120rpx 40rpx; }
.empty-icon-wrap {
  width: 160rpx; height: 160rpx; border-radius: 50%;
  background: linear-gradient(135deg, #FF8C42, #FFB366);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 10rpx 30rpx rgba(255, 140, 66, 0.3);
  margin-bottom: 40rpx;
}
.empty-icon { font-size: 72rpx; }
.empty-title { font-size: 34rpx; font-weight: 700; color: #333; display: block; margin-bottom: 16rpx; }
.empty-sub { font-size: 26rpx; color: #999; display: block; margin-bottom: 48rpx; }
.generate-btn {
  background: linear-gradient(135deg, #FF8C42, #FFB366);
  color: #FFFFFF; padding: 28rpx 60rpx; border-radius: 999rpx;
  font-size: 30rpx; font-weight: 600;
  box-shadow: 0 6rpx 20rpx rgba(255, 140, 66, 0.25);
}
  • 步骤 2:日期卡 + 状态徽章 + 餐次菜单卡(渐变 icon + 淡橙菜品块)

card-accent 改为橙渐变竖条、meal-header 增加渐变 icon 圆底(复用任务 3 的三餐配色类)、dish-item 改淡橙底块:

.card-accent { width: 8rpx; background: linear-gradient(180deg, #FF8C42, #FFB366); flex-shrink: 0; }
.status-pending { background: #FFF1E6; color: #C2410C; }
.meal-header { display: flex; align-items: center; padding: 24rpx 30rpx; border-bottom: 1rpx solid #FDE8D0; }
.meal-icon-wrap { width: 64rpx; height: 64rpx; border-radius: 20rpx; display: flex; align-items: center; justify-content: center; margin-right: 16rpx; }
.meal-icon-breakfast { background: linear-gradient(135deg, #FFB366, #FFD48A); }
.meal-icon-lunch     { background: linear-gradient(135deg, #FBBF24, #FCD34D); }
.meal-icon-dinner    { background: linear-gradient(135deg, #A78BFA, #C4B5FD); }
.dish-item { padding: 24rpx; border-radius: 20rpx; background: #FFF9F0; margin-bottom: 16rpx; }
.dish-item:last-child { margin-bottom: 0; }
.dish-name { font-size: 28rpx; font-weight: 600; color: #333; }
.dish-cal { font-size: 24rpx; color: #C2410C; font-weight: 600; }

注意:餐次 icon 需要 meal.type 映射(breakfast/lunch/dinner)。在 parseMenu() 组装 self.meals 时给每个 meal 加 iconCls 字段(如 'meal-icon-' + meal.type),template 用 :class="meal.iconCls"。禁止在 template 内方法调用。

  • 步骤 3:操作按钮升级为图标+文字胶囊

template 的 dish-actions 三个按钮改为「图标 + 文字」(保留 @tap 事件绑定):

<view class="dish-actions">
  <view class="action-btn like-btn" @tap="likeDish(meal.name, dIndex)">👍 好吃</view>
  <view class="action-btn swap-btn" @tap="swapDish(meal.name, dIndex)">🔄 替换</view>
  <view class="action-btn skip-btn" @tap="skipDish(meal.name, dIndex)">✕ 跳过</view>
</view>
.dish-actions { display: flex; gap: 16rpx; margin-top: 8rpx; }
.action-btn { padding: 12rpx 26rpx; border-radius: 999rpx; font-size: 24rpx; }
.like-btn { background: #E8F5E9; color: #4CAF50; }
.swap-btn { background: #E8F1FB; color: #2196F3; }
.skip-btn { background: #FFEBEE; color: #F44336; }
  • [ ] 步骤 4:营养汇总卡浅暖底 + 宫格分隔浅橙

    .nutrition-card .card-body { background: #FFF9F0; border-radius: 0 0 20rpx 20rpx; }
    .nutrition-value { font-size: 40rpx; font-weight: 700; color: #C2410C; display: block; }
    
  • [ ] 步骤 5:底部操作(重新生成描边 / 标记完成渐变)

    .btn-secondary {
    flex: 1; padding: 28rpx 0; background: #FFFFFF;
    border: 2rpx solid #FF8C42; border-radius: 999rpx;
    text-align: center; font-size: 28rpx; color: #C2410C;
    }
    .btn-primary {
    flex: 1; padding: 28rpx 0;
    background: linear-gradient(135deg, #FF8C42, #FFB366);
    border-radius: 999rpx; text-align: center;
    font-size: 28rpx; color: #FFFFFF; font-weight: 600;
    box-shadow: 0 6rpx 20rpx rgba(255, 140, 66, 0.25);
    }
    
  • [ ] 步骤 6:运行全局校验命令确认无禁止模式

  • [ ] 步骤 7:Commit

    git add cfc-frontend/pages/diet/recommendation.vue
    git commit -m "feat(frontend): 食谱推荐页暖灶主题美化"
    

任务 5:跨页一致性复核 + 收尾

文件: 全部三个页面(只读复核)

  • [ ] 步骤 1:核对规格 §设计令牌 的色值在三个页面 scoped 样式中的使用是否统一(主橙 #FF8C42、深橙 #C2410C、米底 #FFF7ED、嫩绿 #10B981 出现位置与规格一致)

  • [ ] 步骤 2:复核业务逻辑零改动:git diff 三个文件,确认 methods/data/onLoad/onShow 仅新增 computed 字段与类映射字段,未改任何接口调用、参数、跳转路径

  • [ ] 步骤 3:跑一次全量校验命令(三文件同时)确认最终无禁止模式

  • [ ] 步骤 4:Commit(若有遗漏修正)

    git add cfc-frontend/pages/diet/index.vue cfc-frontend/pages/diet/meal-config.vue cfc-frontend/pages/diet/recommendation.vue
    git commit -m "fix(frontend): 饮食三页暖灶主题一致性复核修正"
    

自检记录

  • 规格覆盖度:规格 §1(index 四区块)→ 任务1+2;§2(meal-config 五小节)→ 任务3;§3(recommendation 五小节)→ 任务4;令牌/约束 → 各任务样式与校验
  • 占位符扫描:无 TODO/待定;每个样式步骤含实际 CSS 代码
  • 类型一致性:meal.iconCls、member._avatarCls、heroMeals[].key 等字段在对应 computed 中定义后才在 template 使用