- Add company module (3-tier org, CEO planning, parallel departments) - Add company orchestrator, knowledge extractor, presets, scheduler - Add company API endpoints, models, and frontend views - Add 今天吃啥 PWA app (69 dishes, real images, offline support) - Add team_projects output directory structure - Add unified manage.ps1 for service lifecycle - Add Windows startup guide v1.0 - Add TTS troubleshooting doc - Update frontend (AgentChat UX overhaul, new views) - Update backend (voice engine fix, multi-tenant, RBAC) - Remove deprecated startup scripts and old docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
478 lines
27 KiB
HTML
478 lines
27 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="description" content="渭南家常菜「今天吃啥」推荐助手——随机推荐、周菜单规划、买菜清单">
|
||
<meta name="theme-color" content="#FF9800">
|
||
<title>今天吃啥 · 渭南家常菜</title>
|
||
<link rel="manifest" href="manifest.json">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||
<meta name="apple-mobile-web-app-title" content="今天吃啥">
|
||
<link rel="apple-touch-icon" href="icons/icon-192.png">
|
||
<link rel="stylesheet" href="style.css">
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ==================== 顶部导航栏 ==================== -->
|
||
<header class="app-header" role="banner">
|
||
<div class="header-inner">
|
||
<h1 class="app-title">
|
||
<span class="title-icon" aria-hidden="true">🍜</span>
|
||
今天吃啥
|
||
</h1>
|
||
<nav class="header-nav" aria-label="快捷导航">
|
||
<button class="nav-btn" data-nav="home" aria-current="page" aria-label="首页推荐">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
|
||
<path fill="currentColor" d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
|
||
</svg>
|
||
<span>推荐</span>
|
||
</button>
|
||
<button class="nav-btn" data-nav="weekly" aria-label="周菜单">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
|
||
<path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/>
|
||
</svg>
|
||
<span>周菜单</span>
|
||
</button>
|
||
<button class="nav-btn" data-nav="grocery" aria-label="买菜清单">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
|
||
<path fill="currentColor" d="M17 18c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM7 18c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM7 2l-.01.07L4.27 4H1v2h2l3.6 7.59-1.35 2.45C5.09 16.37 5.29 17 6 17h12v-2H6.72l.94-1.72L18.84 12H20V8H7.82l-.94-2H20V4H7z"/>
|
||
</svg>
|
||
<span>买菜清单</span>
|
||
</button>
|
||
<button class="nav-btn" data-nav="favorites" aria-label="我的收藏">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
|
||
<path fill="currentColor" d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
|
||
</svg>
|
||
<span>收藏</span>
|
||
</button>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="app-main" role="main">
|
||
|
||
<!-- ==================== 区域1:首页推荐区 ==================== -->
|
||
<section id="section-home" class="section section-home" aria-labelledby="home-heading">
|
||
<div class="section-header">
|
||
<h2 id="home-heading" class="section-title">
|
||
<svg class="section-icon" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
|
||
<path fill="currentColor" d="M8.1 13.34l2.83-2.83L3.91 3.5c-1.56 1.56-1.56 4.09 0 5.66l4.19 4.18zm6.78-1.81c1.53.71 3.68.21 5.27-1.38 1.91-1.91 2.28-4.65.81-6.12-1.46-1.46-4.2-1.1-6.12.81-1.59 1.59-2.09 3.74-1.38 5.27L3.7 19.87l1.41 1.41L12 14.41l6.88 6.88 1.41-1.41L13.41 13l1.47-1.47z"/>
|
||
</svg>
|
||
今天推荐
|
||
</h2>
|
||
<button class="btn btn-refresh" id="btn-random" aria-label="换一个推荐">
|
||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
|
||
<path fill="currentColor" d="M17.65 6.35A7.958 7.958 0 0012 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0112 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
|
||
</svg>
|
||
换一个
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 推荐卡片(骨架屏占位,JS动态填充) -->
|
||
<div class="recommend-card skeleton-loading" id="recommend-card" role="article" aria-label="今日推荐菜品">
|
||
<div class="card-img-wrap">
|
||
<div class="card-img-placeholder skeleton-pulse" aria-hidden="true">
|
||
<svg viewBox="0 0 120 120" width="80" height="80" aria-hidden="true">
|
||
<circle cx="60" cy="45" r="25" fill="#E0E0E0"/>
|
||
<ellipse cx="60" cy="85" rx="35" ry="12" fill="#E0E0E0"/>
|
||
</svg>
|
||
</div>
|
||
<span class="card-badge card-badge-category skeleton-text">分类加载中</span>
|
||
</div>
|
||
<div class="card-body">
|
||
<h3 class="card-dish-name skeleton-text skeleton-text-lg">菜品名称加载中</h3>
|
||
<p class="card-dish-tags skeleton-text">标签加载中</p>
|
||
<div class="card-meta">
|
||
<span class="meta-item skeleton-text">
|
||
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></svg>
|
||
约--分钟
|
||
</span>
|
||
<span class="meta-item skeleton-text">
|
||
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
|
||
荤素加载中
|
||
</span>
|
||
</div>
|
||
<button class="btn btn-primary btn-detail" aria-label="查看详情">查看做法</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ==================== 区域2:筛选区 ==================== -->
|
||
<section id="section-filter" class="section section-filter" aria-labelledby="filter-heading">
|
||
<div class="section-header">
|
||
<h2 id="filter-heading" class="section-title">
|
||
<svg class="section-icon" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
|
||
<path fill="currentColor" d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
|
||
</svg>
|
||
筛选菜品
|
||
</h2>
|
||
</div>
|
||
|
||
<!-- 分类筛选 -->
|
||
<div class="filter-group" role="group" aria-label="按分类筛选">
|
||
<button class="chip chip-active" data-filter="all" aria-pressed="true">全部</button>
|
||
<button class="chip" data-filter="主食" aria-pressed="false">🍚 主食</button>
|
||
<button class="chip" data-filter="热菜" aria-pressed="false">🔥 热菜</button>
|
||
<button class="chip" data-filter="凉菜" aria-pressed="false">🥗 凉菜</button>
|
||
<button class="chip" data-filter="汤羹" aria-pressed="false">🍲 汤羹</button>
|
||
<button class="chip" data-filter="小吃" aria-pressed="false">🥟 小吃</button>
|
||
</div>
|
||
|
||
<!-- 荤素筛选 -->
|
||
<div class="filter-group" role="group" aria-label="按荤素筛选">
|
||
<span class="filter-label">荤素:</span>
|
||
<button class="chip chip-sm" data-meat="all" aria-pressed="true">不限</button>
|
||
<button class="chip chip-sm" data-meat="荤" aria-pressed="false">🥩 荤</button>
|
||
<button class="chip chip-sm" data-meat="素" aria-pressed="false">🥬 素</button>
|
||
<button class="chip chip-sm" data-meat="荤素搭配" aria-pressed="false">🍱 荤素搭配</button>
|
||
</div>
|
||
|
||
<!-- 耗时筛选 -->
|
||
<div class="filter-group" role="group" aria-label="按耗时筛选">
|
||
<span class="filter-label">耗时:</span>
|
||
<button class="chip chip-sm" data-time="all" aria-pressed="true">不限</button>
|
||
<button class="chip chip-sm" data-time="quick" aria-pressed="false">⚡ ≤20分钟</button>
|
||
<button class="chip chip-sm" data-time="medium" aria-pressed="false">⏱ 20-60分钟</button>
|
||
<button class="chip chip-sm" data-time="slow" aria-pressed="false">🐢 >60分钟</button>
|
||
</div>
|
||
|
||
<!-- 场景标签筛选 -->
|
||
<div class="filter-group" role="group" aria-label="按场景筛选">
|
||
<span class="filter-label">场景:</span>
|
||
<button class="chip chip-sm" data-tag="all" aria-pressed="true">不限</button>
|
||
<button class="chip chip-sm" data-tag="早餐" aria-pressed="false">🌅 早餐</button>
|
||
<button class="chip chip-sm" data-tag="快手" aria-pressed="false">⚡ 快手</button>
|
||
<button class="chip chip-sm" data-tag="下饭" aria-pressed="false">🍚 下饭</button>
|
||
<button class="chip chip-sm" data-tag="宴客" aria-pressed="false">🎉 宴客</button>
|
||
<button class="chip chip-sm" data-tag="下酒" aria-pressed="false">🍺 下酒</button>
|
||
</div>
|
||
|
||
<!-- 忌口开关 -->
|
||
<div class="filter-group filter-dietary" role="group" aria-label="忌口设置">
|
||
<span class="filter-label">忌口:</span>
|
||
<label class="toggle-label">
|
||
<input type="checkbox" class="toggle-input" id="toggle-spicy" aria-label="忌辣">
|
||
<span class="toggle-switch"></span>
|
||
<span class="toggle-text">忌辣 🌶️</span>
|
||
</label>
|
||
<label class="toggle-label">
|
||
<input type="checkbox" class="toggle-input" id="toggle-pork" aria-label="忌猪肉">
|
||
<span class="toggle-switch"></span>
|
||
<span class="toggle-text">忌猪肉 🐷</span>
|
||
</label>
|
||
<label class="toggle-label">
|
||
<input type="checkbox" class="toggle-input" id="toggle-seafood" aria-label="忌海鲜">
|
||
<span class="toggle-switch"></span>
|
||
<span class="toggle-text">忌海鲜 🦐</span>
|
||
</label>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ==================== 区域3:结果展示区 ==================== -->
|
||
<section id="section-results" class="section section-results" aria-labelledby="results-heading">
|
||
<div class="section-header">
|
||
<h2 id="results-heading" class="section-title">
|
||
<svg class="section-icon" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
|
||
<path fill="currentColor" d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H8V4h12v12z"/>
|
||
</svg>
|
||
全部菜品
|
||
<span class="result-count" id="result-count">(69道)</span>
|
||
</h2>
|
||
<div class="sort-control">
|
||
<label for="sort-select" class="sr-only">排序方式</label>
|
||
<select id="sort-select" class="select-sort" aria-label="排序方式">
|
||
<option value="default">默认排序</option>
|
||
<option value="time-asc">耗时最短</option>
|
||
<option value="time-desc">耗时最长</option>
|
||
<option value="name">按菜名</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 菜品卡片网格(骨架屏占位) -->
|
||
<div class="dishes-grid" id="dishes-grid" role="list" aria-label="菜品列表">
|
||
<!-- 骨架屏占位卡片 x 6 -->
|
||
<div class="dish-card skeleton-loading" role="listitem" aria-hidden="true">
|
||
<div class="card-img-wrap">
|
||
<div class="card-img-placeholder skeleton-pulse"></div>
|
||
<span class="card-badge card-badge-category skeleton-text"></span>
|
||
</div>
|
||
<div class="card-body">
|
||
<h3 class="card-dish-name skeleton-text skeleton-text-lg"></h3>
|
||
<p class="card-dish-tags skeleton-text"></p>
|
||
</div>
|
||
</div>
|
||
<div class="dish-card skeleton-loading" role="listitem" aria-hidden="true">
|
||
<div class="card-img-wrap">
|
||
<div class="card-img-placeholder skeleton-pulse"></div>
|
||
<span class="card-badge card-badge-category skeleton-text"></span>
|
||
</div>
|
||
<div class="card-body">
|
||
<h3 class="card-dish-name skeleton-text skeleton-text-lg"></h3>
|
||
<p class="card-dish-tags skeleton-text"></p>
|
||
</div>
|
||
</div>
|
||
<div class="dish-card skeleton-loading" role="listitem" aria-hidden="true">
|
||
<div class="card-img-wrap">
|
||
<div class="card-img-placeholder skeleton-pulse"></div>
|
||
<span class="card-badge card-badge-category skeleton-text"></span>
|
||
</div>
|
||
<div class="card-body">
|
||
<h3 class="card-dish-name skeleton-text skeleton-text-lg"></h3>
|
||
<p class="card-dish-tags skeleton-text"></p>
|
||
</div>
|
||
</div>
|
||
<div class="dish-card skeleton-loading" role="listitem" aria-hidden="true">
|
||
<div class="card-img-wrap">
|
||
<div class="card-img-placeholder skeleton-pulse"></div>
|
||
<span class="card-badge card-badge-category skeleton-text"></span>
|
||
</div>
|
||
<div class="card-body">
|
||
<h3 class="card-dish-name skeleton-text skeleton-text-lg"></h3>
|
||
<p class="card-dish-tags skeleton-text"></p>
|
||
</div>
|
||
</div>
|
||
<div class="dish-card skeleton-loading" role="listitem" aria-hidden="true">
|
||
<div class="card-img-wrap">
|
||
<div class="card-img-placeholder skeleton-pulse"></div>
|
||
<span class="card-badge card-badge-category skeleton-text"></span>
|
||
</div>
|
||
<div class="card-body">
|
||
<h3 class="card-dish-name skeleton-text skeleton-text-lg"></h3>
|
||
<p class="card-dish-tags skeleton-text"></p>
|
||
</div>
|
||
</div>
|
||
<div class="dish-card skeleton-loading" role="listitem" aria-hidden="true">
|
||
<div class="card-img-wrap">
|
||
<div class="card-img-placeholder skeleton-pulse"></div>
|
||
<span class="card-badge card-badge-category skeleton-text"></span>
|
||
</div>
|
||
<div class="card-body">
|
||
<h3 class="card-dish-name skeleton-text skeleton-text-lg"></h3>
|
||
<p class="card-dish-tags skeleton-text"></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 空状态提示(默认隐藏) -->
|
||
<div class="empty-state" id="empty-state" hidden>
|
||
<svg viewBox="0 0 24 24" width="64" height="64" aria-hidden="true">
|
||
<path fill="#BDBDBD" d="M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/>
|
||
</svg>
|
||
<p class="empty-text">没有找到符合条件的菜品</p>
|
||
<button class="btn btn-secondary" id="btn-clear-filters">清除筛选条件</button>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ==================== 区域4:周菜单区 ==================== -->
|
||
<section id="section-weekly" class="section section-weekly" aria-labelledby="weekly-heading" hidden>
|
||
<div class="section-header">
|
||
<h2 id="weekly-heading" class="section-title">
|
||
<svg class="section-icon" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
|
||
<path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"/>
|
||
</svg>
|
||
本周菜单
|
||
</h2>
|
||
<div class="weekly-actions">
|
||
<button class="btn btn-secondary btn-sm" id="btn-regenerate-weekly" aria-label="重新生成周菜单">
|
||
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
|
||
<path fill="currentColor" d="M17.65 6.35A7.958 7.958 0 0012 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0112 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
|
||
</svg>
|
||
重新生成
|
||
</button>
|
||
<button class="btn btn-primary btn-sm" id="btn-weekly-to-grocery" aria-label="加入买菜清单">
|
||
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
|
||
<path fill="currentColor" d="M17 18c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM7 18c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM7 2l-.01.07L4.27 4H1v2h2l3.6 7.59-1.35 2.45C5.09 16.37 5.29 17 6 17h12v-2H6.72l.94-1.72L18.84 12H20V8H7.82l-.94-2H20V4H7z"/>
|
||
</svg>
|
||
生成买菜清单
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="weekly-grid" id="weekly-grid" role="list" aria-label="一周菜单">
|
||
<!-- 7天占位卡片 -->
|
||
<div class="weekly-day-card skeleton-loading" data-day="1" role="listitem" aria-hidden="true">
|
||
<div class="day-header"><span class="day-name skeleton-text"></span><span class="day-date skeleton-text"></span></div>
|
||
<div class="day-meals">
|
||
<div class="meal-slot"><span class="meal-label">午餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
<div class="meal-slot"><span class="meal-label">晚餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
</div>
|
||
</div>
|
||
<div class="weekly-day-card skeleton-loading" data-day="2" role="listitem" aria-hidden="true">
|
||
<div class="day-header"><span class="day-name skeleton-text"></span><span class="day-date skeleton-text"></span></div>
|
||
<div class="day-meals">
|
||
<div class="meal-slot"><span class="meal-label">午餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
<div class="meal-slot"><span class="meal-label">晚餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
</div>
|
||
</div>
|
||
<div class="weekly-day-card skeleton-loading" data-day="3" role="listitem" aria-hidden="true">
|
||
<div class="day-header"><span class="day-name skeleton-text"></span><span class="day-date skeleton-text"></span></div>
|
||
<div class="day-meals">
|
||
<div class="meal-slot"><span class="meal-label">午餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
<div class="meal-slot"><span class="meal-label">晚餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
</div>
|
||
</div>
|
||
<div class="weekly-day-card skeleton-loading" data-day="4" role="listitem" aria-hidden="true">
|
||
<div class="day-header"><span class="day-name skeleton-text"></span><span class="day-date skeleton-text"></span></div>
|
||
<div class="day-meals">
|
||
<div class="meal-slot"><span class="meal-label">午餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
<div class="meal-slot"><span class="meal-label">晚餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
</div>
|
||
</div>
|
||
<div class="weekly-day-card skeleton-loading" data-day="5" role="listitem" aria-hidden="true">
|
||
<div class="day-header"><span class="day-name skeleton-text"></span><span class="day-date skeleton-text"></span></div>
|
||
<div class="day-meals">
|
||
<div class="meal-slot"><span class="meal-label">午餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
<div class="meal-slot"><span class="meal-label">晚餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
</div>
|
||
</div>
|
||
<div class="weekly-day-card skeleton-loading" data-day="6" role="listitem" aria-hidden="true">
|
||
<div class="day-header"><span class="day-name skeleton-text"></span><span class="day-date skeleton-text"></span></div>
|
||
<div class="day-meals">
|
||
<div class="meal-slot"><span class="meal-label">午餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
<div class="meal-slot"><span class="meal-label">晚餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
</div>
|
||
</div>
|
||
<div class="weekly-day-card skeleton-loading" data-day="0" role="listitem" aria-hidden="true">
|
||
<div class="day-header"><span class="day-name skeleton-text"></span><span class="day-date skeleton-text"></span></div>
|
||
<div class="day-meals">
|
||
<div class="meal-slot"><span class="meal-label">午餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
<div class="meal-slot"><span class="meal-label">晚餐</span><span class="meal-dish skeleton-text"></span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ==================== 区域5:买菜清单区 ==================== -->
|
||
<section id="section-grocery" class="section section-grocery" aria-labelledby="grocery-heading" hidden>
|
||
<div class="section-header">
|
||
<h2 id="grocery-heading" class="section-title">
|
||
<svg class="section-icon" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
|
||
<path fill="currentColor" d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM7 7h10v2H7zm0 4h7v2H7z"/>
|
||
</svg>
|
||
买菜清单
|
||
</h2>
|
||
<div class="grocery-actions">
|
||
<button class="btn btn-secondary btn-sm" id="btn-clear-grocery" aria-label="清空清单">
|
||
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
|
||
<path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
|
||
</svg>
|
||
清空
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 按分类聚合的食材清单 -->
|
||
<div class="grocery-list" id="grocery-list" role="list" aria-label="购物清单">
|
||
<!-- 空状态 -->
|
||
<div class="empty-state" id="grocery-empty">
|
||
<svg viewBox="0 0 24 24" width="64" height="64" aria-hidden="true">
|
||
<path fill="#BDBDBD" d="M17 18c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM7 18c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM7 2l-.01.07L4.27 4H1v2h2l3.6 7.59-1.35 2.45C5.09 16.37 5.29 17 6 17h12v-2H6.72l.94-1.72L18.84 12H20V8H7.82l-.94-2H20V4H7z"/>
|
||
</svg>
|
||
<p class="empty-text">买菜清单还是空的</p>
|
||
<p class="empty-hint">生成周菜单后,点击「生成买菜清单」自动汇总食材</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ==================== 区域6:收藏/忌口管理区 ==================== -->
|
||
<section id="section-favorites" class="section section-favorites" aria-labelledby="fav-heading" hidden>
|
||
<div class="section-header">
|
||
<h2 id="fav-heading" class="section-title">
|
||
<svg class="section-icon" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
|
||
<path fill="currentColor" d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
|
||
</svg>
|
||
我的收藏
|
||
<span class="result-count" id="fav-count">(0道)</span>
|
||
</h2>
|
||
</div>
|
||
<div class="dishes-grid" id="fav-grid" role="list" aria-label="收藏菜品列表">
|
||
<div class="empty-state" id="fav-empty">
|
||
<svg viewBox="0 0 24 24" width="64" height="64" aria-hidden="true">
|
||
<path fill="#BDBDBD" d="M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"/>
|
||
</svg>
|
||
<p class="empty-text">还没有收藏菜品</p>
|
||
<p class="empty-hint">浏览菜品时点击 ♡ 即可收藏</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 忌口管理 -->
|
||
<div class="section-header" style="margin-top: 2rem;">
|
||
<h2 class="section-title">
|
||
<svg class="section-icon" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
|
||
<path fill="currentColor" d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
|
||
</svg>
|
||
忌口管理
|
||
</h2>
|
||
</div>
|
||
<div class="dietary-manager" aria-label="忌口食材管理">
|
||
<p class="dietary-hint">添加你不吃的食材,推荐和筛选时会自动排除</p>
|
||
<div class="dietary-input-row">
|
||
<label for="dietary-input" class="sr-only">添加忌口食材</label>
|
||
<input type="text" id="dietary-input" class="input-text" placeholder="输入食材名称,如:香菜、肥肉...">
|
||
<button class="btn btn-primary btn-sm" id="btn-add-dietary" aria-label="添加忌口">添加</button>
|
||
</div>
|
||
<div class="dietary-tags" id="dietary-tags" role="list" aria-label="已添加的忌口食材">
|
||
<!-- JS 动态填充 -->
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
<!-- ==================== 菜品详情弹窗 ==================== -->
|
||
<div class="modal-overlay" id="modal-overlay" hidden role="dialog" aria-modal="true" aria-labelledby="modal-title">
|
||
<div class="modal-content">
|
||
<button class="modal-close" id="modal-close" aria-label="关闭详情">
|
||
<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
|
||
<path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
||
</svg>
|
||
</button>
|
||
<div class="modal-body" id="modal-body">
|
||
<!-- 骨架屏 -->
|
||
<div class="modal-img-placeholder skeleton-pulse"></div>
|
||
<h2 id="modal-title" class="modal-dish-name skeleton-text skeleton-text-lg"></h2>
|
||
<div class="modal-tags skeleton-text"></div>
|
||
<div class="modal-ingredients">
|
||
<h3>食材清单</h3>
|
||
<ul id="modal-ingredients-list"></ul>
|
||
</div>
|
||
<div class="modal-steps">
|
||
<h3>烹饪步骤</h3>
|
||
<ol id="modal-steps-list"></ol>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-fav" id="btn-modal-fav" aria-label="收藏菜品">
|
||
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
|
||
<path fill="currentColor" d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
|
||
</svg>
|
||
收藏
|
||
</button>
|
||
<button class="btn btn-primary" id="btn-modal-add-weekly" aria-label="加入周菜单">加入周菜单</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== Toast 提示 ==================== -->
|
||
<div class="toast-container" id="toast-container" aria-live="polite" aria-atomic="true"></div>
|
||
|
||
<!-- JS 脚本 -->
|
||
<script src="app.js" defer></script>
|
||
<!-- PWA 注册 -->
|
||
<script>
|
||
if ('serviceWorker' in navigator) {
|
||
window.addEventListener('load', function() {
|
||
navigator.serviceWorker.register('/sw.js').then(function(reg) {
|
||
console.log('[PWA] Service Worker 已注册:', reg.scope);
|
||
}).catch(function(err) {
|
||
console.log('[PWA] Service Worker 注册失败:', err);
|
||
});
|
||
});
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|