Files
renjianbo f2e65a8fbb feat: virtual company module, team projects, PWA dishes app, and startup scripts overhaul
- 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>
2026-07-10 22:37:56 +08:00

478 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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">🐢 &gt;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>