1586 lines
61 KiB
HTML
1586 lines
61 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||
<title>今天吃什么</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
:root {
|
||
--primary-color: #007AFF;
|
||
--bg-color: #F2F2F7;
|
||
--card-bg: #FFFFFF;
|
||
--text-primary: #1C1C1E;
|
||
--text-secondary: #8E8E93;
|
||
--border-radius: 16px;
|
||
--shadow: 0 2px 20px rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', sans-serif;
|
||
background: var(--bg-color);
|
||
color: var(--text-primary);
|
||
min-height: 100vh;
|
||
padding: 20px;
|
||
}
|
||
|
||
.container {
|
||
max-width: 480px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.header {
|
||
text-align: center;
|
||
padding: 30px 0;
|
||
}
|
||
|
||
.header h1 {
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.header p {
|
||
color: var(--text-secondary);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.mode-selector {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.mode-btn {
|
||
flex: 1;
|
||
padding: 14px;
|
||
border: none;
|
||
border-radius: 12px;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
background: var(--card-bg);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.mode-btn.active {
|
||
background: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.filter-panel {
|
||
background: var(--card-bg);
|
||
border-radius: var(--border-radius);
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
display: none;
|
||
}
|
||
|
||
.filter-panel.show {
|
||
display: block;
|
||
}
|
||
|
||
.filter-section {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.filter-section:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.filter-label {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--text-secondary);
|
||
margin-bottom: 8px;
|
||
display: block;
|
||
}
|
||
|
||
.filter-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.filter-header .filter-label {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.refresh-btn {
|
||
background: var(--bg-color);
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 4px 10px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.refresh-btn:hover {
|
||
background: var(--primary-color);
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
.refresh-btn:active {
|
||
transform: rotate(360deg);
|
||
}
|
||
|
||
.filter-tag.expandable {
|
||
background: linear-gradient(135deg, var(--primary-color), #5856D6);
|
||
color: white;
|
||
}
|
||
|
||
.filter-tag.expandable.active {
|
||
box-shadow: 0 0 0 2px var(--primary-color);
|
||
}
|
||
|
||
.subcategory-container {
|
||
margin-top: 12px;
|
||
padding: 12px;
|
||
background: var(--bg-color);
|
||
border-radius: 12px;
|
||
border: 1px solid rgba(0, 122, 255, 0.2);
|
||
}
|
||
|
||
.subcategory-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.subcategory-title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.subcategory-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.subcategory-tag {
|
||
padding: 6px 12px;
|
||
border-radius: 16px;
|
||
font-size: 12px;
|
||
background: var(--card-bg);
|
||
border: 1px solid var(--primary-color);
|
||
color: var(--primary-color);
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.subcategory-tag:hover {
|
||
background: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.subcategory-tag.selected {
|
||
background: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.filter-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.filter-tag {
|
||
padding: 8px 14px;
|
||
border-radius: 20px;
|
||
font-size: 13px;
|
||
background: var(--bg-color);
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
border: 1px solid transparent;
|
||
}
|
||
|
||
.filter-tag.selected {
|
||
background: rgba(0, 122, 255, 0.1);
|
||
color: var(--primary-color);
|
||
border-color: var(--primary-color);
|
||
}
|
||
|
||
.wheel-container {
|
||
background: var(--card-bg);
|
||
border-radius: var(--border-radius);
|
||
padding: 40px 20px;
|
||
text-align: center;
|
||
margin-bottom: 20px;
|
||
box-shadow: var(--shadow);
|
||
}
|
||
|
||
.wheel {
|
||
width: 200px;
|
||
height: 200px;
|
||
margin: 0 auto 30px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, #FF6B6B, #FF8E53, #FFCD56, #4BC0C0, #36A2EB, #9966FF);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
transition: transform 0.1s ease;
|
||
}
|
||
|
||
.wheel-inner {
|
||
width: 160px;
|
||
height: 160px;
|
||
background: var(--card-bg);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 60px;
|
||
}
|
||
|
||
.wheel.spinning {
|
||
animation: spin 0.1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
from { transform: rotate(0deg); }
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
.start-btn {
|
||
padding: 16px 48px;
|
||
border: none;
|
||
border-radius: 25px;
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
background: var(--primary-color);
|
||
color: white;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.start-btn:active {
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.start-btn:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.speed-selector {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.speed-btn {
|
||
padding: 8px 16px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
background: var(--bg-color);
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.speed-btn.active {
|
||
background: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.result-card {
|
||
background: var(--card-bg);
|
||
border-radius: var(--border-radius);
|
||
overflow: hidden;
|
||
margin-bottom: 20px;
|
||
box-shadow: var(--shadow);
|
||
display: none;
|
||
}
|
||
|
||
.result-card.show {
|
||
display: block;
|
||
animation: slideUp 0.5s ease;
|
||
}
|
||
|
||
.match-info {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 12px 16px;
|
||
background: linear-gradient(135deg, #E8F5E9, #E3F2FD);
|
||
border-bottom: 1px solid rgba(0, 122, 255, 0.1);
|
||
}
|
||
|
||
.match-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.match-icon {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.match-label {
|
||
font-size: 13px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.match-value {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.match-unit {
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.match-divider {
|
||
width: 1px;
|
||
height: 24px;
|
||
background: rgba(0, 122, 255, 0.2);
|
||
margin: 0 20px;
|
||
}
|
||
|
||
.candidates-container {
|
||
padding: 16px;
|
||
background: var(--bg-color);
|
||
}
|
||
|
||
.candidates-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||
gap: 12px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.candidate-card {
|
||
background: var(--card-bg);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.candidate-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.15);
|
||
}
|
||
|
||
.candidate-cover {
|
||
width: 100%;
|
||
height: 100px;
|
||
object-fit: cover;
|
||
background: var(--bg-color);
|
||
}
|
||
|
||
.candidate-info {
|
||
padding: 10px;
|
||
}
|
||
|
||
.candidate-title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.candidate-meta {
|
||
font-size: 11px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.candidates-actions {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding-top: 12px;
|
||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.refresh-candidates-btn {
|
||
padding: 10px 20px;
|
||
background: var(--primary-color);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 20px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.refresh-candidates-btn:hover {
|
||
background: #0056CC;
|
||
transform: scale(1.02);
|
||
}
|
||
|
||
.candidates-hint {
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
@keyframes slideUp {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.result-cover {
|
||
width: 100%;
|
||
height: 200px;
|
||
object-fit: cover;
|
||
background: var(--bg-color);
|
||
}
|
||
|
||
.result-content {
|
||
padding: 20px;
|
||
}
|
||
|
||
.result-title {
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.result-intro {
|
||
color: var(--text-secondary);
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.result-meta {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin-bottom: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.meta-tag {
|
||
padding: 6px 12px;
|
||
background: var(--bg-color);
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.result-section {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.result-section h3 {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.ingredient-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.ingredient-item {
|
||
padding: 10px;
|
||
background: var(--bg-color);
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.ingredient-item.has-detail {
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.ingredient-item.has-detail:hover {
|
||
background: rgba(0, 122, 255, 0.1);
|
||
}
|
||
|
||
.ingredient-item .expand-icon {
|
||
float: right;
|
||
color: var(--primary-color);
|
||
font-size: 12px;
|
||
transition: transform 0.2s ease;
|
||
}
|
||
|
||
.ingredient-item.expanded .expand-icon {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
.ingredient-detail {
|
||
margin-top: 8px;
|
||
padding: 12px;
|
||
background: white;
|
||
border-radius: 8px;
|
||
border: 1px solid rgba(0, 122, 255, 0.1);
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.detail-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.detail-row {
|
||
padding: 4px 0;
|
||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.detail-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.detail-label {
|
||
font-weight: 600;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.ingredient-item .name {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.ingredient-item .amount {
|
||
color: var(--text-secondary);
|
||
margin-left: 4px;
|
||
}
|
||
|
||
.nutrition-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.nutrition-item {
|
||
text-align: center;
|
||
padding: 10px;
|
||
background: var(--bg-color);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.nutrition-item .value {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.nutrition-item .label {
|
||
font-size: 11px;
|
||
color: var(--text-secondary);
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.result-stats {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 30px;
|
||
margin-bottom: 16px;
|
||
padding: 16px;
|
||
background: var(--bg-color);
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.stat-item {
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-item .icon {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.stat-item .value {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.stat-item .label {
|
||
font-size: 12px;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.result-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-top: 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.action-btn {
|
||
flex: 1;
|
||
min-width: 80px;
|
||
padding: 14px;
|
||
border: none;
|
||
border-radius: 12px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.action-btn.primary {
|
||
background: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.action-btn.secondary {
|
||
background: var(--bg-color);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.loading {
|
||
display: none;
|
||
text-align: center;
|
||
padding: 20px;
|
||
}
|
||
|
||
.loading.show {
|
||
display: block;
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 3px solid var(--bg-color);
|
||
border-top-color: var(--primary-color);
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin: 0 auto 10px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>🎯 今天吃什么?</h1>
|
||
<p>让命运来决定你的下一餐</p>
|
||
</div>
|
||
|
||
<div class="mode-selector">
|
||
<button class="mode-btn active" data-mode="random">🎲 完全随机</button>
|
||
<button class="mode-btn" data-mode="smart">🎯 智能推荐</button>
|
||
</div>
|
||
|
||
<div class="filter-panel" id="filterPanel">
|
||
<div class="filter-info" id="recipeCount" style="display: none; padding: 12px; background: rgba(0, 122, 255, 0.1); border-radius: 8px; margin-bottom: 12px; text-align: center; font-weight: 600; color: var(--primary-color);">
|
||
符合条件: 0 个菜谱
|
||
</div>
|
||
|
||
<div class="filter-section">
|
||
<div class="filter-header">
|
||
<label class="filter-label">屏蔽过敏原</label>
|
||
<button class="refresh-btn" data-type="allergen" title="刷新">🔄</button>
|
||
</div>
|
||
<div class="filter-tags" id="allergenTags"></div>
|
||
</div>
|
||
|
||
<div class="filter-section">
|
||
<div class="filter-header">
|
||
<label class="filter-label">需要的分类</label>
|
||
<button class="refresh-btn" data-type="category" title="刷新">🔄</button>
|
||
</div>
|
||
<div class="filter-tags" id="categoryTags"></div>
|
||
</div>
|
||
|
||
<div class="filter-section">
|
||
<div class="filter-header">
|
||
<label class="filter-label">👅 口味</label>
|
||
<button class="refresh-btn" data-type="taste" title="刷新">🔄</button>
|
||
</div>
|
||
<div class="filter-tags" id="tasteTags"></div>
|
||
</div>
|
||
|
||
<div class="filter-section">
|
||
<div class="filter-header">
|
||
<label class="filter-label">👨🍳 工艺</label>
|
||
<button class="refresh-btn" data-type="craft" title="刷新">🔄</button>
|
||
</div>
|
||
<div class="filter-tags" id="craftTags"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="wheel-container">
|
||
<div class="wheel" id="wheel">
|
||
<div class="wheel-inner">🍽️</div>
|
||
</div>
|
||
<button class="start-btn" id="startBtn">开始选择</button>
|
||
<div class="speed-selector">
|
||
<button class="speed-btn" data-speed="fast">快</button>
|
||
<button class="speed-btn active" data-speed="medium">中</button>
|
||
<button class="speed-btn" data-speed="slow">慢</button>
|
||
<button class="speed-btn" data-speed="skip">跳过</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="loading" id="loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>正在为你选择...</p>
|
||
</div>
|
||
|
||
<div class="result-card" id="resultCard">
|
||
<div class="match-info" id="matchInfo" style="display: none;">
|
||
<div class="match-item">
|
||
<span class="match-icon">🎯</span>
|
||
<span class="match-label">符合条件</span>
|
||
<span class="match-value" id="candidatesCount">0</span>
|
||
<span class="match-unit">道</span>
|
||
</div>
|
||
<div class="match-divider"></div>
|
||
<div class="match-item">
|
||
<span class="match-icon">⭐</span>
|
||
<span class="match-label">最佳匹配</span>
|
||
<span class="match-value" id="bestMatchCount">0</span>
|
||
<span class="match-unit">道</span>
|
||
</div>
|
||
</div>
|
||
<img class="result-cover" id="resultCover" src="" alt="">
|
||
<div class="result-content">
|
||
<h2 class="result-title" id="resultTitle"></h2>
|
||
<p class="result-intro" id="resultIntro"></p>
|
||
<div class="result-meta" id="resultMeta"></div>
|
||
|
||
<div class="result-section">
|
||
<h3>🥬 食材清单</h3>
|
||
<div class="ingredient-list" id="ingredientList"></div>
|
||
</div>
|
||
|
||
<div class="result-section" id="nutritionSection" style="display:none;">
|
||
<h3>📊 营养成分</h3>
|
||
<div class="nutrition-grid" id="nutritionGrid"></div>
|
||
</div>
|
||
|
||
<div class="result-stats" id="resultStats">
|
||
<div class="stat-item">
|
||
<div class="icon">👁️</div>
|
||
<div class="value" id="statViews">0</div>
|
||
<div class="label">浏览</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="icon">❤️</div>
|
||
<div class="value" id="statLikes">0</div>
|
||
<div class="label">点赞</div>
|
||
</div>
|
||
<div class="stat-item">
|
||
<div class="icon">⭐</div>
|
||
<div class="value" id="statRecommends">0</div>
|
||
<div class="label">推荐</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="result-actions">
|
||
<button class="action-btn secondary" id="likeBtn">❤️ 点赞</button>
|
||
<button class="action-btn secondary" id="recommendBtn">⭐ 推荐</button>
|
||
<button class="action-btn secondary" id="retryBtn">🔄 再来</button>
|
||
<button class="action-btn primary" id="detailBtn">📖 详情</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="candidates-container" id="candidatesContainer" style="display: none;"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let configData = null;
|
||
let currentMode = 'random';
|
||
let currentSpeed = 'medium';
|
||
let selectedFilters = {
|
||
allergens: [],
|
||
categories: [],
|
||
tags: []
|
||
};
|
||
let currentCandidates = [];
|
||
let candidatesCount = 0;
|
||
let bestMatchCount = 0;
|
||
|
||
const API_BASE = '';
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
loadConfig();
|
||
bindEvents();
|
||
});
|
||
|
||
async function loadConfig() {
|
||
try {
|
||
const response = await fetch(API_BASE + 'api_what_to_eat.php?act=config');
|
||
const data = await response.json();
|
||
console.log('配置数据:', data);
|
||
if (data.code === 200) {
|
||
configData = data.data;
|
||
renderFilters();
|
||
} else {
|
||
console.error('配置加载失败:', data.message);
|
||
}
|
||
} catch (error) {
|
||
console.error('加载配置失败:', error);
|
||
}
|
||
}
|
||
|
||
function renderFilters() {
|
||
if (!configData) {
|
||
console.warn('configData 为空');
|
||
return;
|
||
}
|
||
|
||
console.log('渲染过滤器, categories:', configData.categories);
|
||
renderAllergens();
|
||
renderCategories();
|
||
renderTasteTags();
|
||
renderCraftTags();
|
||
}
|
||
|
||
function shuffleArray(array) {
|
||
const shuffled = [...array];
|
||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||
const j = Math.floor(Math.random() * (i + 1));
|
||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||
}
|
||
return shuffled;
|
||
}
|
||
|
||
function renderAllergens() {
|
||
const allergenContainer = document.getElementById('allergenTags');
|
||
if (configData.allergen_types && configData.allergen_types.length > 0) {
|
||
const items = shuffleArray(configData.allergen_types).slice(0, 20);
|
||
let html = '';
|
||
items.forEach(item => {
|
||
html += '<span class="filter-tag" data-type="allergen" data-value="' + item.type + '">';
|
||
html += item.icon + ' ' + item.name;
|
||
html += '</span>';
|
||
});
|
||
allergenContainer.innerHTML = html;
|
||
} else {
|
||
allergenContainer.innerHTML = '<span style="color: var(--text-secondary);">暂无过敏原选项</span>';
|
||
}
|
||
}
|
||
|
||
function renderCategories() {
|
||
const categoryContainer = document.getElementById('categoryTags');
|
||
if (configData.categories && configData.categories.length > 0) {
|
||
const recipeCategories = shuffleArray(configData.categories.filter(c => c.parent_id === 11)).slice(0, 10);
|
||
const ingredientCategories = shuffleArray(configData.categories.filter(c => c.parent_id === 1000)).slice(0, 10);
|
||
|
||
let categoryHtml = '';
|
||
|
||
if (recipeCategories.length > 0) {
|
||
categoryHtml += '<div style="margin-bottom: 8px; font-size: 12px; color: var(--text-secondary);">📖 菜谱分类</div>';
|
||
recipeCategories.forEach(item => {
|
||
categoryHtml += '<span class="filter-tag expandable" data-type="category" data-value="' + item.id + '" data-name="' + item.name + '" onclick="toggleSubcategories(this, ' + item.id + ')">';
|
||
categoryHtml += item.name + ' ▾';
|
||
categoryHtml += '</span>';
|
||
});
|
||
}
|
||
|
||
if (ingredientCategories.length > 0) {
|
||
categoryHtml += '<div style="margin: 12px 0 8px 0; font-size: 12px; color: var(--text-secondary);">🥬 食材分类</div>';
|
||
ingredientCategories.forEach(item => {
|
||
categoryHtml += '<span class="filter-tag expandable" data-type="category" data-value="' + item.id + '" data-name="' + item.name + '" onclick="toggleSubcategories(this, ' + item.id + ')">';
|
||
categoryHtml += item.name + ' ▾';
|
||
categoryHtml += '</span>';
|
||
});
|
||
}
|
||
|
||
categoryHtml += '<div id="subcategoryContainer" class="subcategory-container" style="display: none;"></div>';
|
||
|
||
categoryContainer.innerHTML = categoryHtml || '<span style="color: var(--text-secondary);">暂无分类</span>';
|
||
} else {
|
||
categoryContainer.innerHTML = '<span style="color: var(--text-secondary);">暂无分类</span>';
|
||
}
|
||
}
|
||
|
||
function renderTasteTags() {
|
||
const tasteContainer = document.getElementById('tasteTags');
|
||
if (configData.tags && configData.tags.taste && configData.tags.taste.length > 0) {
|
||
const items = shuffleArray(configData.tags.taste).slice(0, 20);
|
||
let html = '';
|
||
items.forEach(item => {
|
||
html += '<span class="filter-tag" data-type="tag" data-value="' + item.id + '">';
|
||
html += item.name;
|
||
html += '</span>';
|
||
});
|
||
tasteContainer.innerHTML = html;
|
||
} else {
|
||
tasteContainer.innerHTML = '<span style="color: var(--text-secondary);">暂无口味选项</span>';
|
||
}
|
||
}
|
||
|
||
function renderCraftTags() {
|
||
const craftContainer = document.getElementById('craftTags');
|
||
if (configData.tags && configData.tags.craft && configData.tags.craft.length > 0) {
|
||
const items = shuffleArray(configData.tags.craft).slice(0, 20);
|
||
let html = '';
|
||
items.forEach(item => {
|
||
html += '<span class="filter-tag" data-type="tag" data-value="' + item.id + '">';
|
||
html += item.name;
|
||
html += '</span>';
|
||
});
|
||
craftContainer.innerHTML = html;
|
||
} else {
|
||
craftContainer.innerHTML = '<span style="color: var(--text-secondary);">暂无工艺选项</span>';
|
||
}
|
||
}
|
||
|
||
let currentExpandedCategory = null;
|
||
|
||
async function toggleSubcategories(element, parentId) {
|
||
const container = document.getElementById('subcategoryContainer');
|
||
const categoryName = element.dataset.name;
|
||
|
||
document.querySelectorAll('.filter-tag.expandable').forEach(tag => {
|
||
tag.classList.remove('active');
|
||
});
|
||
|
||
if (currentExpandedCategory === parentId) {
|
||
container.style.display = 'none';
|
||
currentExpandedCategory = null;
|
||
return;
|
||
}
|
||
|
||
element.classList.add('active');
|
||
currentExpandedCategory = parentId;
|
||
|
||
container.innerHTML = '<div class="subcategory-header">' +
|
||
'<span class="subcategory-title">📁 ' + categoryName + ' 子分类</span>' +
|
||
'<button class="refresh-btn" onclick="loadSubcategories(' + parentId + ', \'' + categoryName + '\')">🔄 刷新</button>' +
|
||
'</div>' +
|
||
'<div class="subcategory-tags" id="subcategoryTags">' +
|
||
'<span style="color: var(--text-secondary);">加载中...</span>' +
|
||
'</div>';
|
||
container.style.display = 'block';
|
||
|
||
await loadSubcategories(parentId, categoryName);
|
||
}
|
||
|
||
async function loadSubcategories(parentId, categoryName) {
|
||
const tagsContainer = document.getElementById('subcategoryTags');
|
||
|
||
try {
|
||
const selectedTags = selectedFilters.tags.join(',');
|
||
|
||
if (selectedTags) {
|
||
const url = API_BASE + 'api_what_to_eat.php?act=available_filters&parent_category_id=' + parentId + '&selected_tags=' + selectedTags;
|
||
const response = await fetch(url);
|
||
const data = await response.json();
|
||
|
||
if (data.code === 200 && data.data.available_subcategories && data.data.available_subcategories.length > 0) {
|
||
let html = '';
|
||
data.data.available_subcategories.forEach(item => {
|
||
const isSelected = selectedFilters.categories.includes(String(item.id)) ? 'selected' : '';
|
||
html += '<span class="subcategory-tag ' + isSelected + '" ';
|
||
html += 'data-type="category" data-value="' + item.id + '" ';
|
||
html += 'onclick="selectSubcategory(this)">';
|
||
html += item.name + ' (' + item.count + ')';
|
||
html += '</span>';
|
||
});
|
||
tagsContainer.innerHTML = html;
|
||
} else {
|
||
tagsContainer.innerHTML = '<span style="color: var(--text-secondary);">暂无符合条件的子分类</span>';
|
||
}
|
||
} else {
|
||
const response = await fetch(API_BASE + 'api_what_to_eat.php?act=subcategories&parent_id=' + parentId);
|
||
const data = await response.json();
|
||
|
||
if (data.code === 200 && data.data.subcategories.length > 0) {
|
||
let html = '';
|
||
data.data.subcategories.forEach(item => {
|
||
const isSelected = selectedFilters.categories.includes(String(item.id)) ? 'selected' : '';
|
||
html += '<span class="subcategory-tag ' + isSelected + '" ';
|
||
html += 'data-type="category" data-value="' + item.id + '" ';
|
||
html += 'onclick="selectSubcategory(this)">';
|
||
html += item.name;
|
||
html += '</span>';
|
||
});
|
||
tagsContainer.innerHTML = html;
|
||
} else {
|
||
tagsContainer.innerHTML = '<span style="color: var(--text-secondary);">暂无子分类</span>';
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('加载子分类失败:', error);
|
||
tagsContainer.innerHTML = '<span style="color: red;">加载失败</span>';
|
||
}
|
||
}
|
||
|
||
function selectSubcategory(element) {
|
||
element.classList.toggle('selected');
|
||
const value = element.dataset.value;
|
||
|
||
if (element.classList.contains('selected')) {
|
||
if (!selectedFilters.categories.includes(value)) {
|
||
selectedFilters.categories.push(value);
|
||
}
|
||
} else {
|
||
const index = selectedFilters.categories.indexOf(value);
|
||
if (index > -1) {
|
||
selectedFilters.categories.splice(index, 1);
|
||
}
|
||
}
|
||
|
||
updateAvailableFilters();
|
||
}
|
||
|
||
async function updateAvailableFilters() {
|
||
const selectedCategories = selectedFilters.categories.join(',');
|
||
const selectedTags = selectedFilters.tags.join(',');
|
||
const parentCategoryId = currentExpandedCategory || 0;
|
||
|
||
if (!selectedCategories && !selectedTags && !parentCategoryId) {
|
||
return;
|
||
}
|
||
|
||
let url = API_BASE + 'api_what_to_eat.php?act=available_filters';
|
||
if (selectedCategories) {
|
||
url += '&selected_categories=' + selectedCategories;
|
||
}
|
||
if (selectedTags) {
|
||
url += '&selected_tags=' + selectedTags;
|
||
}
|
||
if (parentCategoryId) {
|
||
url += '&parent_category_id=' + parentCategoryId;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(url);
|
||
const data = await response.json();
|
||
|
||
if (data.code === 200) {
|
||
updateFilterUI(data.data);
|
||
}
|
||
} catch (error) {
|
||
console.error('更新筛选选项失败:', error);
|
||
}
|
||
}
|
||
|
||
function updateFilterUI(data) {
|
||
const recipeCountEl = document.getElementById('recipeCount');
|
||
if (recipeCountEl) {
|
||
recipeCountEl.textContent = '符合条件: ' + data.total_recipes + ' 个菜谱';
|
||
recipeCountEl.style.display = 'block';
|
||
}
|
||
|
||
if (data.available_subcategories && data.available_subcategories.length > 0) {
|
||
const tagsContainer = document.getElementById('subcategoryTags');
|
||
if (tagsContainer) {
|
||
let html = '';
|
||
data.available_subcategories.forEach(item => {
|
||
const isSelected = selectedFilters.categories.includes(String(item.id)) ? 'selected' : '';
|
||
html += '<span class="subcategory-tag ' + isSelected + '" ';
|
||
html += 'data-type="category" data-value="' + item.id + '" ';
|
||
html += 'onclick="selectSubcategory(this)">';
|
||
html += item.name + ' (' + item.count + ')';
|
||
html += '</span>';
|
||
});
|
||
tagsContainer.innerHTML = html;
|
||
}
|
||
}
|
||
|
||
if (data.available_tags) {
|
||
if (data.available_tags.taste && data.available_tags.taste.length > 0) {
|
||
const tasteContainer = document.getElementById('tasteTags');
|
||
if (tasteContainer) {
|
||
let html = '';
|
||
data.available_tags.taste.forEach(item => {
|
||
const isSelected = selectedFilters.tags.includes(String(item.id)) ? 'selected' : '';
|
||
html += '<span class="filter-tag ' + isSelected + '" ';
|
||
html += 'data-type="tag" data-value="' + item.id + '">';
|
||
html += item.name + ' (' + item.count + ')';
|
||
html += '</span>';
|
||
});
|
||
tasteContainer.innerHTML = html;
|
||
}
|
||
}
|
||
|
||
if (data.available_tags.craft && data.available_tags.craft.length > 0) {
|
||
const craftContainer = document.getElementById('craftTags');
|
||
if (craftContainer) {
|
||
let html = '';
|
||
data.available_tags.craft.forEach(item => {
|
||
const isSelected = selectedFilters.tags.includes(String(item.id)) ? 'selected' : '';
|
||
html += '<span class="filter-tag ' + isSelected + '" ';
|
||
html += 'data-type="tag" data-value="' + item.id + '">';
|
||
html += item.name + ' (' + item.count + ')';
|
||
html += '</span>';
|
||
});
|
||
craftContainer.innerHTML = html;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function toggleIngredientDetail(index) {
|
||
const detailEl = document.getElementById('ingredientDetail' + index);
|
||
const itemEl = detailEl.previousElementSibling;
|
||
|
||
if (detailEl.style.display === 'none') {
|
||
detailEl.style.display = 'block';
|
||
itemEl.classList.add('expanded');
|
||
} else {
|
||
detailEl.style.display = 'none';
|
||
itemEl.classList.remove('expanded');
|
||
}
|
||
}
|
||
|
||
function bindEvents() {
|
||
document.querySelectorAll('.mode-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
|
||
this.classList.add('active');
|
||
currentMode = this.dataset.mode;
|
||
document.getElementById('filterPanel').classList.toggle('show', currentMode === 'smart');
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('.speed-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
document.querySelectorAll('.speed-btn').forEach(b => b.classList.remove('active'));
|
||
this.classList.add('active');
|
||
currentSpeed = this.dataset.speed;
|
||
});
|
||
});
|
||
|
||
// 刷新按钮事件
|
||
document.querySelectorAll('.refresh-btn').forEach(btn => {
|
||
btn.addEventListener('click', function() {
|
||
const type = this.dataset.type;
|
||
switch(type) {
|
||
case 'allergen':
|
||
renderAllergens();
|
||
break;
|
||
case 'category':
|
||
renderCategories();
|
||
break;
|
||
case 'taste':
|
||
renderTasteTags();
|
||
break;
|
||
case 'craft':
|
||
renderCraftTags();
|
||
break;
|
||
}
|
||
});
|
||
});
|
||
|
||
document.addEventListener('click', function(e) {
|
||
if (e.target.classList.contains('filter-tag')) {
|
||
e.target.classList.toggle('selected');
|
||
const type = e.target.dataset.type;
|
||
const value = e.target.dataset.value;
|
||
|
||
if (e.target.classList.contains('selected')) {
|
||
selectedFilters[type + 's'].push(value);
|
||
} else {
|
||
const index = selectedFilters[type + 's'].indexOf(value);
|
||
if (index > -1) {
|
||
selectedFilters[type + 's'].splice(index, 1);
|
||
}
|
||
}
|
||
|
||
updateAvailableFilters();
|
||
}
|
||
});
|
||
|
||
document.getElementById('startBtn').addEventListener('click', startSelection);
|
||
document.getElementById('retryBtn').addEventListener('click', function() {
|
||
const resultCard = document.getElementById('resultCard');
|
||
const candidatesContainer = document.getElementById('candidatesContainer');
|
||
|
||
if (currentCandidates.length > 0) {
|
||
document.querySelector('.result-content').style.display = 'none';
|
||
candidatesContainer.style.display = 'block';
|
||
showCandidatesList();
|
||
} else {
|
||
startSelection();
|
||
}
|
||
});
|
||
|
||
document.getElementById('likeBtn').addEventListener('click', function() {
|
||
const recipeId = this.dataset.id;
|
||
if (recipeId) {
|
||
doAction('like', recipeId);
|
||
}
|
||
});
|
||
|
||
document.getElementById('recommendBtn').addEventListener('click', function() {
|
||
const recipeId = this.dataset.id;
|
||
if (recipeId) {
|
||
doAction('recommend', recipeId);
|
||
}
|
||
});
|
||
|
||
document.getElementById('detailBtn').addEventListener('click', function() {
|
||
const recipeId = this.dataset.id;
|
||
if (recipeId) {
|
||
window.open(API_BASE + 'api.php?act=detail&id=' + recipeId, '_blank');
|
||
}
|
||
});
|
||
}
|
||
|
||
async function startSelection() {
|
||
console.log('===== startSelection 开始 =====');
|
||
const startBtn = document.getElementById('startBtn');
|
||
const wheel = document.getElementById('wheel');
|
||
const loading = document.getElementById('loading');
|
||
const resultCard = document.getElementById('resultCard');
|
||
|
||
console.log('startBtn:', startBtn);
|
||
console.log('wheel:', wheel);
|
||
console.log('loading:', loading);
|
||
console.log('resultCard:', resultCard);
|
||
|
||
startBtn.disabled = true;
|
||
resultCard.classList.remove('show');
|
||
|
||
loading.classList.add('show');
|
||
|
||
if (currentSpeed !== 'skip') {
|
||
wheel.classList.add('spinning');
|
||
}
|
||
|
||
try {
|
||
let url = API_BASE + 'api_what_to_eat.php?act=' + currentMode;
|
||
|
||
if (currentMode === 'smart') {
|
||
if (selectedFilters.allergens.length > 0) {
|
||
url += '&exclude_allergens=' + selectedFilters.allergens.join(',');
|
||
}
|
||
if (selectedFilters.categories.length > 0) {
|
||
url += '&include_categories=' + selectedFilters.categories.join(',');
|
||
}
|
||
if (selectedFilters.tags.length > 0) {
|
||
url += '&include_tags=' + selectedFilters.tags.join(',');
|
||
}
|
||
}
|
||
|
||
console.log('请求URL:', url);
|
||
|
||
const response = await fetch(url);
|
||
console.log('Response:', response);
|
||
|
||
const data = await response.json();
|
||
console.log('API返回数据:', data);
|
||
console.log('data.code:', data.code);
|
||
console.log('data.data:', data.data);
|
||
|
||
const durations = { fast: 2000, medium: 5000, slow: 8000, skip: 0 };
|
||
const duration = durations[currentSpeed];
|
||
|
||
if (currentSpeed !== 'skip') {
|
||
await new Promise(resolve => setTimeout(resolve, duration));
|
||
wheel.classList.remove('spinning');
|
||
}
|
||
|
||
loading.classList.remove('show');
|
||
|
||
if (data.code === 200) {
|
||
console.log('data.data.candidates:', data.data?.candidates);
|
||
currentCandidates = data.data?.candidates || [];
|
||
candidatesCount = data.data?.candidates_count || 0;
|
||
bestMatchCount = data.data?.best_match_count || 0;
|
||
console.log('currentCandidates:', currentCandidates);
|
||
console.log('candidatesCount:', candidatesCount);
|
||
console.log('调用 showCandidatesList');
|
||
showCandidatesList();
|
||
} else {
|
||
alert(data.message || '选择失败,请重试');
|
||
}
|
||
} catch (error) {
|
||
console.error('请求失败:', error);
|
||
loading.classList.remove('show');
|
||
wheel.classList.remove('spinning');
|
||
alert('网络错误,请重试');
|
||
} finally {
|
||
startBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
function showCandidatesList() {
|
||
console.log('===== showCandidatesList 开始 =====');
|
||
console.log('currentCandidates:', currentCandidates);
|
||
console.log('currentCandidates.length:', currentCandidates.length);
|
||
|
||
const matchInfo = document.getElementById('matchInfo');
|
||
const candidatesContainer = document.getElementById('candidatesContainer');
|
||
const resultContent = document.querySelector('.result-content');
|
||
|
||
console.log('matchInfo:', matchInfo);
|
||
console.log('candidatesContainer:', candidatesContainer);
|
||
console.log('resultContent:', resultContent);
|
||
|
||
resultContent.style.display = 'none';
|
||
|
||
document.getElementById('candidatesCount').textContent = candidatesCount;
|
||
document.getElementById('bestMatchCount').textContent = bestMatchCount;
|
||
matchInfo.style.display = 'flex';
|
||
|
||
if (currentCandidates.length === 0) {
|
||
console.log('没有候选菜品');
|
||
candidatesContainer.innerHTML = '<p style="text-align: center; color: var(--text-secondary);">暂无候选菜品</p>';
|
||
return;
|
||
}
|
||
|
||
console.log('开始渲染候选菜品');
|
||
let html = '<div class="candidates-grid">';
|
||
currentCandidates.forEach((recipe, index) => {
|
||
console.log('渲染菜谱 ' + index + ':', recipe);
|
||
const coverSrc = recipe.cover || 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect fill="%23f2f2f7" width="100" height="100"/><text x="50" y="55" text-anchor="middle" font-size="30">🍽️</text></svg>';
|
||
const recipeTitle = recipe.title || '未知菜谱';
|
||
const categoryName = (recipe.category && recipe.category.name) ? recipe.category.name : '未分类';
|
||
|
||
html += '<div class="candidate-card" onclick="selectCandidate(' + index + ')">';
|
||
html += '<img class="candidate-cover" src="' + coverSrc + '" alt="">';
|
||
html += '<div class="candidate-info">';
|
||
html += '<div class="candidate-title">' + recipeTitle + '</div>';
|
||
html += '<div class="candidate-meta">' + categoryName + '</div>';
|
||
html += '</div></div>';
|
||
});
|
||
html += '</div>';
|
||
|
||
html += '<div class="candidates-actions">';
|
||
html += '<button class="refresh-candidates-btn" onclick="refreshCandidates()">🔄 换一批</button>';
|
||
html += '<span class="candidates-hint">点击菜品查看详情</span>';
|
||
html += '</div>';
|
||
|
||
console.log('HTML生成完成,长度:', html.length);
|
||
candidatesContainer.innerHTML = html;
|
||
candidatesContainer.style.display = 'block';
|
||
console.log('===== showCandidatesList 完成 =====');
|
||
}
|
||
|
||
function selectCandidate(index) {
|
||
if (index >= 0 && index < currentCandidates.length) {
|
||
showResult(currentCandidates[index]);
|
||
}
|
||
}
|
||
|
||
async function refreshCandidates() {
|
||
const candidatesContainer = document.getElementById('candidatesContainer');
|
||
candidatesContainer.innerHTML = '<p style="text-align: center; padding: 20px;">🔄 加载中...</p>';
|
||
await startSelection();
|
||
}
|
||
|
||
function showResult(recipe) {
|
||
console.log('显示结果:', recipe);
|
||
const resultCard = document.getElementById('resultCard');
|
||
const candidatesContainer = document.getElementById('candidatesContainer');
|
||
const resultContent = document.querySelector('.result-content');
|
||
|
||
candidatesContainer.style.display = 'none';
|
||
resultContent.style.display = 'block';
|
||
|
||
document.getElementById('resultCover').src = recipe.cover || 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect fill="%23f2f2f7" width="100" height="100"/><text x="50" y="55" text-anchor="middle" font-size="40">🍽️</text></svg>';
|
||
|
||
document.getElementById('resultTitle').textContent = recipe.title || '未知菜谱';
|
||
|
||
document.getElementById('resultIntro').textContent = recipe.intro || '暂无简介';
|
||
|
||
const categoryName = (recipe.category && recipe.category.name) ? recipe.category.name : '未分类';
|
||
const tags = recipe.tags || [];
|
||
let metaHtml = '<span class="meta-tag">' + categoryName + '</span>';
|
||
tags.forEach(t => {
|
||
metaHtml += '<span class="meta-tag">' + (t.name || '') + '</span>';
|
||
});
|
||
document.getElementById('resultMeta').innerHTML = metaHtml;
|
||
|
||
const ingredients = recipe.ingredients || {};
|
||
const mainIngredients = ingredients.main || [];
|
||
const auxiliaryIngredients = ingredients.auxiliary || [];
|
||
const seasoningIngredients = ingredients.seasoning || [];
|
||
|
||
let ingredientHtml = '';
|
||
|
||
function renderIngredientItem(ing, index) {
|
||
const hasDetail = ing.detail && ing.detail.id;
|
||
let onclickAttr = '';
|
||
if (hasDetail) {
|
||
onclickAttr = ' onclick="toggleIngredientDetail(' + index + ')"';
|
||
}
|
||
let html = '<div class="ingredient-item' + (hasDetail ? ' has-detail' : '') + '"' + onclickAttr + '>';
|
||
html += '<span class="name">' + (ing.name || '') + '</span>';
|
||
html += '<span class="amount">' + (ing.amount || '') + '</span>';
|
||
if (hasDetail) {
|
||
html += '<span class="expand-icon">▾</span>';
|
||
}
|
||
html += '</div>';
|
||
|
||
if (hasDetail) {
|
||
html += '<div class="ingredient-detail" id="ingredientDetail' + index + '" style="display: none;">';
|
||
html += '<div class="detail-section">';
|
||
|
||
if (ing.detail.alias && ing.detail.alias.length > 0) {
|
||
html += '<div class="detail-row"><span class="detail-label">别名:</span>' + ing.detail.alias.join('、') + '</div>';
|
||
}
|
||
|
||
if (ing.detail.intro) {
|
||
html += '<div class="detail-row"><span class="detail-label">简介:</span>' + (ing.detail.intro.substring(0, 100) || '') + '...</div>';
|
||
}
|
||
|
||
if (ing.detail.efficacy) {
|
||
html += '<div class="detail-row"><span class="detail-label">功效:</span>' + (ing.detail.efficacy.substring(0, 100) || '') + '...</div>';
|
||
}
|
||
|
||
if (ing.detail.suitable_crowd && ing.detail.suitable_crowd.length > 0) {
|
||
html += '<div class="detail-row"><span class="detail-label" style="color: #34C759;">✅ 适宜:</span>' + ing.detail.suitable_crowd.join(';') + '</div>';
|
||
}
|
||
|
||
if (ing.detail.unsuitable_crowd && ing.detail.unsuitable_crowd.length > 0) {
|
||
html += '<div class="detail-row"><span class="detail-label" style="color: #FF3B30;">❌ 不宜:</span>' + ing.detail.unsuitable_crowd.join(';') + '</div>';
|
||
}
|
||
|
||
if (ing.detail.allergen_type && ing.detail.allergen_type.length > 0) {
|
||
html += '<div class="detail-row"><span class="detail-label" style="color: #FF9500;">⚠️ 过敏原:</span>' + ing.detail.allergen_type.join('、') + '</div>';
|
||
}
|
||
|
||
if (ing.detail.category_names && ing.detail.category_names.length > 0) {
|
||
html += '<div class="detail-row"><span class="detail-label">分类:</span>' + ing.detail.category_names.join('、') + '</div>';
|
||
}
|
||
|
||
html += '</div></div>';
|
||
}
|
||
|
||
return html;
|
||
}
|
||
|
||
let ingredientIndex = 0;
|
||
|
||
if (mainIngredients.length > 0) {
|
||
ingredientHtml += '<div style="margin-bottom: 12px;"><strong style="color: var(--primary-color);">🍖 主料</strong></div>';
|
||
ingredientHtml += '<div class="ingredient-list" style="margin-bottom: 16px;">';
|
||
mainIngredients.forEach(ing => {
|
||
ingredientHtml += renderIngredientItem(ing, ingredientIndex++);
|
||
});
|
||
ingredientHtml += '</div>';
|
||
}
|
||
|
||
if (auxiliaryIngredients.length > 0) {
|
||
ingredientHtml += '<div style="margin-bottom: 12px;"><strong style="color: var(--primary-color);">🥬 辅料</strong></div>';
|
||
ingredientHtml += '<div class="ingredient-list" style="margin-bottom: 16px;">';
|
||
auxiliaryIngredients.forEach(ing => {
|
||
ingredientHtml += renderIngredientItem(ing, ingredientIndex++);
|
||
});
|
||
ingredientHtml += '</div>';
|
||
}
|
||
|
||
if (seasoningIngredients.length > 0) {
|
||
ingredientHtml += '<div style="margin-bottom: 12px;"><strong style="color: var(--primary-color);">🧂 调料</strong></div>';
|
||
ingredientHtml += '<div class="ingredient-list">';
|
||
seasoningIngredients.forEach(ing => {
|
||
ingredientHtml += renderIngredientItem(ing, ingredientIndex++);
|
||
});
|
||
ingredientHtml += '</div>';
|
||
}
|
||
|
||
document.getElementById('ingredientList').innerHTML = ingredientHtml || '<p>暂无食材信息</p>';
|
||
|
||
if (recipe.nutrition && (recipe.nutrition.calories || recipe.nutrition.protein || (recipe.nutrition.all && recipe.nutrition.all.length > 0))) {
|
||
document.getElementById('nutritionSection').style.display = 'block';
|
||
|
||
let nutritionHtml = '';
|
||
|
||
if (recipe.nutrition.calories) {
|
||
nutritionHtml += '<div class="nutrition-item">';
|
||
nutritionHtml += '<div class="value">' + recipe.nutrition.calories + '</div>';
|
||
nutritionHtml += '<div class="label">能量</div>';
|
||
nutritionHtml += '</div>';
|
||
}
|
||
if (recipe.nutrition.protein) {
|
||
nutritionHtml += '<div class="nutrition-item">';
|
||
nutritionHtml += '<div class="value">' + recipe.nutrition.protein + '</div>';
|
||
nutritionHtml += '<div class="label">蛋白质</div>';
|
||
nutritionHtml += '</div>';
|
||
}
|
||
if (recipe.nutrition.fat) {
|
||
nutritionHtml += '<div class="nutrition-item">';
|
||
nutritionHtml += '<div class="value">' + recipe.nutrition.fat + '</div>';
|
||
nutritionHtml += '<div class="label">脂肪</div>';
|
||
nutritionHtml += '</div>';
|
||
}
|
||
if (recipe.nutrition.carbs) {
|
||
nutritionHtml += '<div class="nutrition-item">';
|
||
nutritionHtml += '<div class="value">' + recipe.nutrition.carbs + '</div>';
|
||
nutritionHtml += '<div class="label">碳水</div>';
|
||
nutritionHtml += '</div>';
|
||
}
|
||
if (recipe.nutrition.fiber) {
|
||
nutritionHtml += '<div class="nutrition-item">';
|
||
nutritionHtml += '<div class="value">' + recipe.nutrition.fiber + '</div>';
|
||
nutritionHtml += '<div class="label">纤维</div>';
|
||
nutritionHtml += '</div>';
|
||
}
|
||
if (recipe.nutrition.sodium) {
|
||
nutritionHtml += '<div class="nutrition-item">';
|
||
nutritionHtml += '<div class="value">' + recipe.nutrition.sodium + '</div>';
|
||
nutritionHtml += '<div class="label">钠</div>';
|
||
nutritionHtml += '</div>';
|
||
}
|
||
if (recipe.nutrition.cholesterol) {
|
||
nutritionHtml += '<div class="nutrition-item">';
|
||
nutritionHtml += '<div class="value">' + recipe.nutrition.cholesterol + '</div>';
|
||
nutritionHtml += '<div class="label">胆固醇</div>';
|
||
nutritionHtml += '</div>';
|
||
}
|
||
|
||
if (recipe.nutrition.all && recipe.nutrition.all.length > 0) {
|
||
const mainNutrients = ['能量', '热量', '蛋白质', '脂肪', '碳水', '纤维', '钠', '胆固醇'];
|
||
const otherNutrients = recipe.nutrition.all.filter(n => !mainNutrients.some(m => n.name.includes(m)));
|
||
otherNutrients.slice(0, 4).forEach(n => {
|
||
nutritionHtml += '<div class="nutrition-item">' +
|
||
'<div class="value">' + n.value + n.unit + '</div>' +
|
||
'<div class="label">' + n.name + '</div>' +
|
||
'</div>';
|
||
});
|
||
}
|
||
|
||
document.getElementById('nutritionGrid').innerHTML = nutritionHtml || '<p style="color: var(--text-secondary);">暂无营养信息</p>';
|
||
} else {
|
||
document.getElementById('nutritionSection').style.display = 'none';
|
||
}
|
||
|
||
document.getElementById('detailBtn').dataset.id = recipe.id;
|
||
document.getElementById('likeBtn').dataset.id = recipe.id;
|
||
document.getElementById('recommendBtn').dataset.id = recipe.id;
|
||
|
||
const stats = recipe.statistics || {};
|
||
document.getElementById('statViews').textContent = stats.view_count || 0;
|
||
document.getElementById('statLikes').textContent = stats.like_count || 0;
|
||
document.getElementById('statRecommends').textContent = stats.recommend_count || 0;
|
||
|
||
resultCard.classList.add('show');
|
||
}
|
||
|
||
async function doAction(action, recipeId) {
|
||
try {
|
||
const response = await fetch(API_BASE + 'api_what_to_eat.php?act=' + action + '&id=' + recipeId);
|
||
const data = await response.json();
|
||
|
||
if (data.code === 200) {
|
||
if (action === 'like') {
|
||
document.getElementById('statLikes').textContent = data.data.like_count;
|
||
} else if (action === 'recommend') {
|
||
document.getElementById('statRecommends').textContent = data.data.recommend_count;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('操作失败:', error);
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|