Files
kitchen/preview/food_compatibility.html
Developer 3a056ca676 feat: 新增菜品对比和食物相生相克功能
- 新增菜品对比功能,支持1v1左右和1vN上下布局切换
- 新增食物相生相克查询工具,包含API服务和详情页面
- 优化平台工具类,移除冗余的鸿蒙系统检测逻辑
- 更新版本号至1.5.1,修改更新日志和版本说明
- 修复多个页面列表分隔符构建器参数警告
- 新增雷达图组件,用于展示多维度对比数据
- 新增可编辑数值组件,支持双击编辑和范围验证
- 优化导出按钮,增加对比功能入口
- 新增搜索引擎枚举工具类
- 更新应用路由配置,添加对比和相生相克相关页面
2026-05-01 09:13:27 +08:00

1445 lines
38 KiB
HTML
Raw Permalink 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">
<title>食物相生相克 - 预览</title>
<style>
:root {
--primary: #007AFF;
--primary-light: rgba(0,122,255,0.12);
--secondary: #FF9500;
--background: #F2F2F7;
--card: #FFFFFF;
--text1: #1C1C1E;
--text2: #8E8E93;
--text3: #C7C7CC;
--green: #34C759;
--red: #FF3B30;
--orange: #FF9500;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 20px;
--radius-xl: 28px;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-7: 32px;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
--shadow-lg: 0 8px 24px rgba(0,0,0,0.1);
--font-xs: 11px;
--font-sm: 12px;
--font-md: 14px;
--font-lg: 16px;
--font-xl: 18px;
--font-xxl: 22px;
--glass-bg: rgba(255,255,255,0.72);
--glass-blur: 20px;
--glass-border: rgba(255,255,255,0.5);
}
* { margin:0; padding:0; box-sizing:border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', sans-serif;
background: var(--background);
color: var(--text1);
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
.app-container {
max-width: 480px;
margin: 0 auto;
min-height: 100vh;
background: var(--background);
position: relative;
overflow-x: hidden;
}
/* Header */
.header {
position: sticky;
top: 0;
z-index: 100;
background: rgba(242,242,247,0.72);
backdrop-filter: blur(var(--glass-blur));
-webkit-backdrop-filter: blur(var(--glass-blur));
border-bottom: 0.5px solid rgba(0,0,0,0.06);
padding: var(--space-3) var(--space-4) var(--space-3);
}
.header-top {
display: flex;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-3);
}
.header-icon {
width: 44px;
height: 44px;
border-radius: var(--radius-md);
background: linear-gradient(135deg, #34C759, #30D158);
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
box-shadow: 0 4px 12px rgba(52,199,89,0.3);
flex-shrink: 0;
}
.header-info { flex:1; min-width:0; }
.header-title {
font-size: var(--font-xxl);
font-weight: 700;
letter-spacing: -0.5px;
}
.header-subtitle {
font-size: var(--font-sm);
color: var(--text2);
margin-top: 2px;
}
.header-badge {
padding: var(--space-1) var(--space-2);
background: var(--primary-light);
border-radius: var(--radius-sm);
font-size: var(--font-xs);
font-weight: 600;
color: var(--primary);
white-space: nowrap;
}
/* Search Bar */
.search-bar {
display: flex;
align-items: center;
height: 40px;
background: rgba(0,0,0,0.04);
border-radius: var(--radius-md);
padding: 0 var(--space-3);
gap: var(--space-2);
}
.search-bar svg { flex-shrink:0; color: var(--text3); }
.search-bar input {
flex: 1;
border: none;
background: none;
font-size: var(--font-md);
color: var(--text1);
outline: none;
}
.search-bar input::placeholder { color: var(--text3); }
.search-clear {
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--text3);
color: white;
display: none;
align-items: center;
justify-content: center;
font-size: 10px;
cursor: pointer;
flex-shrink: 0;
}
.search-clear.show { display: flex; }
/* Hot Tags */
.hot-tags {
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
padding: var(--space-2) var(--space-4) 0;
}
.hot-tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
background: var(--card);
border-radius: 16px;
font-size: var(--font-sm);
font-weight: 500;
color: var(--text2);
cursor: pointer;
transition: all 0.2s ease;
box-shadow: var(--shadow-sm);
white-space: nowrap;
border: 1px solid rgba(0,0,0,0.03);
}
.hot-tag:hover {
background: var(--primary-light);
color: var(--primary);
transform: scale(1.04);
}
.hot-tag:active {
transform: scale(0.96);
}
.hot-tag-emoji { font-size: 14px; }
.hot-tag.active {
background: linear-gradient(135deg, #007AFF, #5856D6);
color: white;
box-shadow: 0 3px 10px rgba(0,122,255,0.3);
}
/* Category Tabs */
.category-tabs {
display: flex;
gap: var(--space-2);
padding: var(--space-3) var(--space-4);
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.category-tabs::-webkit-scrollbar { display: none; }
.category-tab {
padding: var(--space-2) var(--space-4);
border-radius: 20px;
font-size: var(--font-md);
font-weight: 500;
white-space: nowrap;
cursor: pointer;
transition: all 0.25s ease;
border: 1.5px solid transparent;
background: var(--card);
color: var(--text2);
box-shadow: var(--shadow-sm);
}
.category-tab:hover { transform: scale(1.03); }
.category-tab.active {
background: linear-gradient(135deg, #34C759, #30D158);
color: white;
box-shadow: 0 4px 12px rgba(52,199,89,0.3);
}
.category-tab.active-xiangke {
background: linear-gradient(135deg, #FF3B30, #FF6B6B);
box-shadow: 0 4px 12px rgba(255,59,48,0.3);
}
.category-tab .tab-icon { margin-right: 4px; }
/* Hot Section */
.section {
padding: var(--space-2) var(--space-4);
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-3);
}
.section-title {
font-size: var(--font-lg);
font-weight: 700;
display: flex;
align-items: center;
gap: var(--space-2);
}
.section-action {
font-size: var(--font-sm);
color: var(--primary);
cursor: pointer;
}
/* Hot Cards Horizontal Scroll */
.hot-scroll {
display: flex;
gap: var(--space-3);
overflow-x: auto;
padding-bottom: var(--space-2);
-webkit-overflow-scrolling: touch;
scroll-snap-type: x mandatory;
}
.hot-scroll::-webkit-scrollbar { display: none; }
.hot-card {
min-width: 160px;
max-width: 160px;
background: var(--card);
border-radius: var(--radius-lg);
padding: var(--space-4);
box-shadow: var(--shadow-md);
scroll-snap-align: start;
cursor: pointer;
transition: transform 0.2s ease;
}
.hot-card:hover { transform: translateY(-2px); }
.hot-card-icon {
width: 48px;
height: 48px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
margin-bottom: var(--space-3);
}
.hot-card-icon.xiangsheng {
background: linear-gradient(135deg, rgba(52,199,89,0.15), rgba(48,209,88,0.08));
}
.hot-card-icon.xiangke {
background: linear-gradient(135deg, rgba(255,59,48,0.15), rgba(255,107,107,0.08));
}
.hot-card-name {
font-size: var(--font-md);
font-weight: 600;
margin-bottom: 4px;
}
.hot-card-desc {
font-size: var(--font-xs);
color: var(--text2);
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.hot-card-views {
margin-top: var(--space-2);
font-size: var(--font-xs);
color: var(--text3);
display: flex;
align-items: center;
gap: 4px;
}
/* Food List */
.food-list {
padding: 0 var(--space-4);
display: flex;
flex-direction: column;
gap: var(--space-3);
padding-bottom: 100px;
}
.food-card {
background: var(--card);
border-radius: var(--radius-lg);
padding: var(--space-4);
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
.food-card:hover { box-shadow: var(--shadow-md); }
.food-card::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
border-radius: 0 4px 4px 0;
}
.food-card.xiangsheng::before {
background: linear-gradient(180deg, #34C759, #30D158);
}
.food-card.xiangke::before {
background: linear-gradient(180deg, #FF3B30, #FF6B6B);
}
.food-card-top {
display: flex;
align-items: flex-start;
gap: var(--space-3);
}
.food-card-emoji {
width: 52px;
height: 52px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
flex-shrink: 0;
}
.food-card-emoji.xiangsheng {
background: linear-gradient(135deg, rgba(52,199,89,0.15), rgba(48,209,88,0.08));
}
.food-card-emoji.xiangke {
background: linear-gradient(135deg, rgba(255,59,48,0.15), rgba(255,107,107,0.08));
}
.food-card-content { flex:1; min-width:0; }
.food-card-name {
font-size: var(--font-lg);
font-weight: 600;
margin-bottom: 4px;
}
.food-card-tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 10px;
font-size: var(--font-xs);
font-weight: 600;
margin-bottom: var(--space-2);
}
.food-card-tag.xiangsheng {
background: rgba(52,199,89,0.12);
color: #34C759;
}
.food-card-tag.xiangke {
background: rgba(255,59,48,0.12);
color: #FF3B30;
}
.food-card-desc {
font-size: var(--font-sm);
color: var(--text2);
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.food-card-bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: var(--space-3);
padding-top: var(--space-3);
border-top: 0.5px solid rgba(0,0,0,0.04);
}
.food-card-views {
font-size: var(--font-xs);
color: var(--text3);
display: flex;
align-items: center;
gap: 4px;
}
.food-card-arrow {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--primary-light);
display: flex;
align-items: center;
justify-content: center;
color: var(--primary);
font-size: 14px;
}
/* Detail Modal */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.4);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 200;
display: none;
align-items: flex-end;
justify-content: center;
}
.modal-overlay.show { display: flex; }
.modal-sheet {
width: 100%;
max-width: 480px;
max-height: 85vh;
background: var(--card);
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
overflow: hidden;
animation: slideUp 0.35s cubic-bezier(0.32,0.72,0,1);
}
@keyframes slideUp {
from { transform: translateY(100%); }
to { transform: translateY(0); }
}
.modal-handle {
width: 36px;
height: 5px;
background: var(--text3);
border-radius: 3px;
margin: var(--space-2) auto;
}
.modal-content {
padding: var(--space-2) var(--space-5) var(--space-7);
overflow-y: auto;
max-height: calc(85vh - 30px);
}
.modal-header {
display: flex;
align-items: center;
gap: var(--space-4);
margin-bottom: var(--space-5);
}
.modal-icon {
width: 64px;
height: 64px;
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
flex-shrink: 0;
}
.modal-icon.xiangsheng {
background: linear-gradient(135deg, rgba(52,199,89,0.2), rgba(48,209,88,0.1));
}
.modal-icon.xiangke {
background: linear-gradient(135deg, rgba(255,59,48,0.2), rgba(255,107,107,0.1));
}
.modal-title {
font-size: var(--font-xxl);
font-weight: 700;
}
.modal-tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 12px;
font-size: var(--font-sm);
font-weight: 600;
margin-top: 4px;
}
.modal-tag.xiangsheng { background: rgba(52,199,89,0.12); color: #34C759; }
.modal-tag.xiangke { background: rgba(255,59,48,0.12); color: #FF3B30; }
.modal-section-title {
font-size: var(--font-md);
font-weight: 600;
color: var(--text2);
margin-bottom: var(--space-3);
margin-top: var(--space-5);
}
.modal-desc {
font-size: var(--font-md);
line-height: 1.7;
color: var(--text1);
padding: var(--space-4);
background: var(--background);
border-radius: var(--radius-md);
}
.modal-related {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.modal-related-item {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3);
background: var(--background);
border-radius: var(--radius-md);
cursor: pointer;
transition: background 0.2s;
}
.modal-related-item:hover { background: rgba(0,0,0,0.04); }
.modal-related-icon {
width: 36px;
height: 36px;
border-radius: var(--radius-sm);
background: var(--primary-light);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
.modal-related-name { font-size: var(--font-md); font-weight: 500; }
.modal-related-arrow { margin-left: auto; color: var(--text3); font-size: 14px; }
.modal-nav {
display: flex;
gap: var(--space-3);
margin-top: var(--space-5);
}
.modal-nav-btn {
flex: 1;
padding: var(--space-3);
border-radius: var(--radius-md);
border: 1.5px solid rgba(0,0,0,0.06);
background: var(--card);
font-size: var(--font-md);
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
transition: all 0.2s;
}
.modal-nav-btn:hover { background: var(--background); }
.modal-actions {
display: flex;
gap: var(--space-3);
margin-top: var(--space-4);
}
.modal-action-btn {
flex: 1;
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-md);
border: none;
font-size: var(--font-md);
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
transition: all 0.2s;
}
.modal-action-btn.copy {
background: var(--background);
color: var(--text1);
border: 1.5px solid rgba(0,0,0,0.06);
}
.modal-action-btn.copy:hover { background: rgba(0,0,0,0.06); }
.modal-action-btn.search {
background: linear-gradient(135deg, #007AFF, #5856D6);
color: white;
box-shadow: 0 4px 12px rgba(0,122,255,0.3);
}
.modal-action-btn.search:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(0,122,255,0.4); }
.modal-action-btn.copied {
background: rgba(52,199,89,0.12);
color: #34C759;
border-color: rgba(52,199,89,0.2);
}
.search-engine-menu {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.4);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 300;
display: none;
align-items: flex-end;
justify-content: center;
}
.search-engine-menu.show { display: flex; }
.search-engine-sheet {
width: 100%;
max-width: 480px;
background: var(--card);
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
padding: var(--space-2) 0 var(--space-7);
animation: slideUp 0.3s cubic-bezier(0.32,0.72,0,1);
}
.search-engine-handle {
width: 36px;
height: 5px;
background: var(--text3);
border-radius: 3px;
margin: var(--space-2) auto var(--space-3);
}
.search-engine-title {
font-size: var(--font-lg);
font-weight: 600;
text-align: center;
margin-bottom: var(--space-4);
padding: 0 var(--space-4);
}
.search-engine-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-3);
padding: 0 var(--space-4);
}
.search-engine-card {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-4);
background: var(--background);
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s;
border: 1.5px solid transparent;
}
.search-engine-card:hover { border-color: var(--primary); transform: scale(1.02); }
.search-engine-card-icon {
width: 40px;
height: 40px;
border-radius: var(--radius-sm);
background: var(--primary-light);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.search-engine-card-name { font-size: var(--font-md); font-weight: 600; }
.search-engine-card-url { font-size: var(--font-xs); color: var(--text3); }
/* Loading */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--space-7);
gap: var(--space-3);
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid var(--text3);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loading-text { font-size: var(--font-sm); color: var(--text2); }
/* Empty State */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--space-7);
gap: var(--space-3);
}
.empty-icon {
width: 80px;
height: 80px;
border-radius: var(--radius-xl);
background: var(--primary-light);
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
}
.empty-title { font-size: var(--font-lg); font-weight: 600; }
.empty-desc { font-size: var(--font-sm); color: var(--text2); }
/* Random Button */
.random-fab {
position: fixed;
bottom: var(--space-6);
right: calc(50% - 240px + var(--space-5));
width: 56px;
height: 56px;
border-radius: 50%;
background: linear-gradient(135deg, #FF9500, #FF6B35);
color: white;
border: none;
font-size: 24px;
cursor: pointer;
box-shadow: 0 6px 20px rgba(255,149,0,0.4);
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s;
z-index: 50;
}
.random-fab:hover { transform: scale(1.1); }
@media (max-width: 480px) {
.random-fab { right: var(--space-5); }
}
/* Stats Bar */
.stats-bar {
display: flex;
gap: var(--space-2);
padding: var(--space-2) var(--space-4);
overflow-x: auto;
}
.stats-bar::-webkit-scrollbar { display: none; }
.stat-chip {
display: flex;
align-items: center;
gap: 6px;
padding: var(--space-2) var(--space-3);
background: var(--card);
border-radius: var(--radius-md);
box-shadow: var(--shadow-sm);
white-space: nowrap;
font-size: var(--font-sm);
}
.stat-chip-value { font-weight: 700; color: var(--primary); }
.stat-chip-label { color: var(--text2); }
/* Pull to refresh indicator */
.refresh-indicator {
text-align: center;
padding: var(--space-3);
font-size: var(--font-sm);
color: var(--text2);
display: none;
}
.refresh-indicator.show { display: block; }
/* Pagination */
.load-more {
text-align: center;
padding: var(--space-4);
}
.load-more-btn {
padding: var(--space-3) var(--space-6);
background: var(--card);
border: 1.5px solid rgba(0,0,0,0.06);
border-radius: 20px;
font-size: var(--font-md);
font-weight: 500;
color: var(--primary);
cursor: pointer;
transition: all 0.2s;
}
.load-more-btn:hover { background: var(--primary-light); }
/* Dark mode support */
@media (prefers-color-scheme: dark) {
:root {
--background: #000000;
--card: #1C1C1E;
--text1: #FFFFFF;
--text2: #EBEBF5;
--text3: #8E8E93;
--primary-light: rgba(10,132,255,0.18);
--glass-bg: rgba(28,28,30,0.72);
--shadow-sm: 0 1px 3px rgba(0,0,0,0.2);
--shadow-md: 0 4px 12px rgba(0,0,0,0.3);
}
.header { background: rgba(0,0,0,0.72); border-bottom-color: rgba(255,255,255,0.06); }
.search-bar { background: rgba(255,255,255,0.06); }
.modal-sheet { background: #1C1C1E; }
.modal-desc { background: #2C2C2E; }
.modal-related-item { background: #2C2C2E; }
.modal-related-item:hover { background: #3A3A3C; }
.food-card { box-shadow: var(--shadow-sm); }
.food-card-bottom { border-top-color: rgba(255,255,255,0.06); }
.hot-tag { background: #2C2C2E; color: var(--text2); border-color: rgba(255,255,255,0.06); }
.hot-tag:hover { background: var(--primary-light); }
.search-engine-sheet { background: #1C1C1E; }
.search-engine-card { background: #2C2C2E; }
.modal-action-btn.copy { background: #2C2C2E; border-color: rgba(255,255,255,0.06); color: var(--text1); }
.modal-action-btn.copy:hover { background: #3A3A3C; }
}
</style>
</head>
<body>
<div class="app-container" id="app">
<!-- Header -->
<div class="header">
<div class="header-top">
<div class="header-icon">🥗</div>
<div class="header-info">
<div class="header-title">食物相生相克</div>
<div class="header-subtitle">了解食物搭配的宜忌</div>
</div>
<div class="header-badge" id="totalCount">加载中...</div>
</div>
<div class="search-bar">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<input type="text" id="searchInput" placeholder="搜索食物名称..." />
<div class="search-clear" id="searchClear" onclick="clearSearch()"></div>
</div>
<div class="hot-tags" id="hotTags">
<div class="hot-tag" onclick="quickSearch('苹果')"><span class="hot-tag-emoji">🍎</span>苹果</div>
<div class="hot-tag" onclick="quickSearch('牛奶')"><span class="hot-tag-emoji">🥛</span>牛奶</div>
<div class="hot-tag" onclick="quickSearch('鸡蛋')"><span class="hot-tag-emoji">🥚</span>鸡蛋</div>
<div class="hot-tag" onclick="quickSearch('蜂蜜')"><span class="hot-tag-emoji">🍯</span>蜂蜜</div>
<div class="hot-tag" onclick="quickSearch('香蕉')"><span class="hot-tag-emoji">🍌</span>香蕉</div>
<div class="hot-tag" onclick="quickSearch('虾')"><span class="hot-tag-emoji">🦐</span></div>
<div class="hot-tag" onclick="quickSearch('豆腐')"><span class="hot-tag-emoji">🧈</span>豆腐</div>
<div class="hot-tag" onclick="quickSearch('菠菜')"><span class="hot-tag-emoji">🥬</span>菠菜</div>
<div class="hot-tag" onclick="quickSearch('土豆')"><span class="hot-tag-emoji">🥔</span>土豆</div>
<div class="hot-tag" onclick="quickSearch('螃蟹')"><span class="hot-tag-emoji">🦀</span>螃蟹</div>
</div>
</div>
<!-- Category Tabs -->
<div class="category-tabs" id="categoryTabs">
<div class="category-tab active" data-category="all" onclick="selectCategory(this, 'all')">
<span class="tab-icon">📋</span>全部
</div>
</div>
<!-- Stats Bar -->
<div class="stats-bar" id="statsBar">
<div class="stat-chip">
<span class="stat-chip-value" id="statTotal">-</span>
<span class="stat-chip-label">条记录</span>
</div>
<div class="stat-chip">
<span class="stat-chip-value" id="statSheng">-</span>
<span class="stat-chip-label">🟢 相生</span>
</div>
<div class="stat-chip">
<span class="stat-chip-value" id="statKe">-</span>
<span class="stat-chip-label">🔴 相克</span>
</div>
</div>
<!-- Hot Section -->
<div class="section" id="hotSection" style="display:none">
<div class="section-header">
<div class="section-title">🔥 热门食物</div>
<div class="section-action" onclick="loadHot()">换一批</div>
</div>
<div class="hot-scroll" id="hotList"></div>
</div>
<!-- Food List -->
<div class="food-list" id="foodList">
<div class="loading-container">
<div class="loading-spinner"></div>
<div class="loading-text">加载中...</div>
</div>
</div>
<!-- Load More -->
<div class="load-more" id="loadMore" style="display:none">
<button class="load-more-btn" onclick="loadMore()">加载更多</button>
</div>
<!-- Random FAB -->
<button class="random-fab" onclick="loadRandom()" title="随机推荐">🎲</button>
</div>
<!-- Detail Modal -->
<div class="modal-overlay" id="detailModal" onclick="closeModalOutside(event)">
<div class="modal-sheet">
<div class="modal-handle"></div>
<div class="modal-content" id="modalContent"></div>
</div>
</div>
<!-- Search Engine Menu -->
<div class="search-engine-menu" id="searchEngineMenu" onclick="closeSearchEngineOutside(event)">
<div class="search-engine-sheet">
<div class="search-engine-handle"></div>
<div class="search-engine-title">🔍 选择搜索引擎</div>
<div class="search-engine-grid">
<div class="search-engine-card" onclick="launchSearch('baidu')">
<div class="search-engine-card-icon">🔍</div>
<div>
<div class="search-engine-card-name">百度</div>
<div class="search-engine-card-url">baidu.com</div>
</div>
</div>
<div class="search-engine-card" onclick="launchSearch('bing')">
<div class="search-engine-card-icon">🟦</div>
<div>
<div class="search-engine-card-name">Bing</div>
<div class="search-engine-card-url">bing.com</div>
</div>
</div>
<div class="search-engine-card" onclick="launchSearch('google')">
<div class="search-engine-card-icon">🌐</div>
<div>
<div class="search-engine-card-name">谷歌</div>
<div class="search-engine-card-url">google.com</div>
</div>
</div>
<div class="search-engine-card" onclick="launchSearch('sogou')">
<div class="search-engine-card-icon">🐕</div>
<div>
<div class="search-engine-card-name">搜狗</div>
<div class="search-engine-card-url">sogou.com</div>
</div>
</div>
</div>
</div>
</div>
<script>
const API_BASE = 'https://tools.wktyl.com/api/shiwu';
let currentPage = 1;
let currentCategoryId = null;
let isLoading = false;
let hasMore = true;
let categories = [];
let searchTimer = null;
const foodEmojis = {
'苹果': '🍎', '香蕉': '🍌', '葡萄': '🍇', '草莓': '🍓', '西瓜': '🍉',
'橙子': '🍊', '柠檬': '🍋', '桃子': '🍑', '梨': '🍐', '芒果': '🥭',
'菠萝': '🍍', '樱桃': '🍒', '石榴': '🫐', '柿子': '🟠', '荔枝': '🔴',
'牛奶': '🥛', '鸡蛋': '🥚', '蜂蜜': '🍯', '豆腐': '🧈', '萝卜': '🥕',
'菠菜': '🥬', '番茄': '🍅', '土豆': '🥔', '黄瓜': '🥒', '茄子': '🍆',
'辣椒': '🌶️', '大蒜': '🧄', '洋葱': '🧅', '蘑菇': '🍄', '芹菜': '🥦',
'白菜': '🥬', '花生': '🥜', '核桃': '🌰', '红枣': '🫐', '枸杞': '🔴',
'绿茶': '🍵', '咖啡': '☕', '啤酒': '🍺', '白酒': '🍶', '红酒': '🍷',
'虾': '🦐', '蟹': '🦀', '鱼': '🐟', '鸡肉': '🍗', '牛肉': '🥩',
'猪肉': '🥓', '羊肉': '🍖', '鸭肉': '🦆', '狗肉': '🐕', '兔肉': '🐇',
'豆浆': '🥛', '酸奶': '🥛', '奶酪': '🧀', '巧克力': '🍫', '冰淇淋': '🍦',
'醋': '🫗', '酱油': '🫗', '生姜': '🫚', '葱': '🧅', '香菜': '🌿',
'红薯': '🍠', '山药': '🥔', '莲藕': '🪷', '冬瓜': '🟢', '南瓜': '🎃',
'丝瓜': '🥒', '苦瓜': '🥒', '木耳': '🍄', '银耳': '🍄', '海带': '🌊',
'紫菜': '🌊', '芝麻': '🫘', '栗子': '🌰', '桂圆': '🔴', '山楂': '🔴',
'木瓜': '🟡', '榴莲': '🟡', '椰子': '🥥', '甘蔗': '🎋', '杨梅': '🔴',
'枇杷': '🟡', '杨桃': '⭐', '猕猴桃': '🥝', '火龙果': '🐉', '百香果': '🟣',
};
function getEmoji(name) {
for (const [key, val] of Object.entries(foodEmojis)) {
if (name.includes(key)) return val;
}
return '🍽️';
}
function getCategoryClass(categoryName) {
return categoryName === '相生' ? 'xiangsheng' : 'xiangke';
}
function getCategoryIcon(categoryName) {
return categoryName === '相生' ? '✅' : '⚠️';
}
async function fetchAPI(endpoint, params = {}) {
const url = new URL(`${API_BASE}/${endpoint}`);
Object.entries(params).forEach(([k, v]) => {
if (v !== null && v !== undefined && v !== '') url.searchParams.set(k, v);
});
try {
const resp = await fetch(url.toString());
const data = await resp.json();
if (data.code === 1) return data.data;
return null;
} catch (e) {
console.error('API Error:', e);
return null;
}
}
async function loadCategories() {
const data = await fetchAPI('category');
if (!data) return;
categories = data.list || [];
const tabsEl = document.getElementById('categoryTabs');
categories.forEach(cat => {
const tab = document.createElement('div');
tab.className = 'category-tab';
tab.dataset.category = cat.id;
const icon = cat.name === '相生' ? '✅' : '⚠️';
tab.innerHTML = `<span class="tab-icon">${icon}</span>${cat.name}`;
tab.onclick = function() { selectCategory(this, cat.id); };
tabsEl.appendChild(tab);
});
const statSheng = categories.find(c => c.name === '相生');
const statKe = categories.find(c => c.name === '相克');
if (statSheng) document.getElementById('statSheng').textContent = statSheng.count || '-';
if (statKe) document.getElementById('statKe').textContent = statKe.count || '-';
}
async function loadFoodList(reset = false) {
if (isLoading) return;
if (reset) {
currentPage = 1;
hasMore = true;
document.getElementById('foodList').innerHTML = '';
}
if (!hasMore) return;
isLoading = true;
const params = { page: currentPage, limit: 20 };
if (currentCategoryId) params.category_id = currentCategoryId;
const data = await fetchAPI('list', params);
isLoading = false;
if (!data) {
document.getElementById('foodList').innerHTML = `
<div class="empty-state">
<div class="empty-icon">😔</div>
<div class="empty-title">加载失败</div>
<div class="empty-desc">请检查网络后重试</div>
</div>`;
return;
}
document.getElementById('statTotal').textContent = data.total || '-';
document.getElementById('totalCount').textContent = `${data.total || 0}`;
const list = data.list || [];
if (list.length === 0 && currentPage === 1) {
document.getElementById('foodList').innerHTML = `
<div class="empty-state">
<div class="empty-icon">🔍</div>
<div class="empty-title">暂无数据</div>
<div class="empty-desc">试试其他分类或搜索</div>
</div>`;
return;
}
hasMore = currentPage < (data.last_page || 1);
const container = document.getElementById('foodList');
list.forEach(item => container.appendChild(createFoodCard(item)));
document.getElementById('loadMore').style.display = hasMore ? 'block' : 'none';
}
async function loadHot() {
const data = await fetchAPI('hot', { limit: 10 });
if (!data) return;
const section = document.getElementById('hotSection');
const list = document.getElementById('hotList');
section.style.display = 'block';
list.innerHTML = '';
(data.list || []).forEach(item => {
const catClass = getCategoryClass(item.category_name);
const card = document.createElement('div');
card.className = 'hot-card';
card.onclick = () => openDetail(item.hash_id);
card.innerHTML = `
<div class="hot-card-icon ${catClass}">${getEmoji(item.sw)}</div>
<div class="hot-card-name">${item.sw}</div>
<div class="hot-card-desc">${item.yh || ''}</div>
<div class="hot-card-views">👁 ${item.views || 0}</div>`;
list.appendChild(card);
});
}
async function loadRandom() {
const data = await fetchAPI('random', { limit: 5 });
if (!data || !data.list || data.list.length === 0) return;
openDetail(data.list[0].hash_id);
}
async function searchFood(keyword) {
if (!keyword.trim()) {
loadFoodList(true);
return;
}
isLoading = true;
document.getElementById('foodList').innerHTML = `
<div class="loading-container">
<div class="loading-spinner"></div>
<div class="loading-text">搜索中...</div>
</div>`;
document.getElementById('loadMore').style.display = 'none';
const data = await fetchAPI('search', { keyword: keyword.trim(), limit: 20 });
isLoading = false;
if (!data) {
document.getElementById('foodList').innerHTML = `
<div class="empty-state">
<div class="empty-icon">😔</div>
<div class="empty-title">搜索失败</div>
</div>`;
return;
}
const list = data.list || [];
if (list.length === 0) {
document.getElementById('foodList').innerHTML = `
<div class="empty-state">
<div class="empty-icon">🔍</div>
<div class="empty-title">未找到"${keyword}"</div>
<div class="empty-desc">试试其他关键词</div>
</div>`;
return;
}
document.getElementById('foodList').innerHTML = '';
const container = document.getElementById('foodList');
list.forEach(item => container.appendChild(createFoodCard(item)));
}
async function openDetail(id) {
const modal = document.getElementById('detailModal');
const content = document.getElementById('modalContent');
content.innerHTML = `
<div class="loading-container">
<div class="loading-spinner"></div>
<div class="loading-text">加载详情...</div>
</div>`;
modal.classList.add('show');
const data = await fetchAPI('detail', { id });
if (!data) {
content.innerHTML = `
<div class="empty-state">
<div class="empty-icon">😔</div>
<div class="empty-title">加载失败</div>
</div>`;
return;
}
const catClass = getCategoryClass(data.category_name);
content.innerHTML = `
<div class="modal-header">
<div class="modal-icon ${catClass}">${getEmoji(data.sw)}</div>
<div>
<div class="modal-title">${data.sw}</div>
<div class="modal-tag ${catClass}">${getCategoryIcon(data.category_name)} ${data.category_name}</div>
</div>
</div>
<div class="modal-section-title">📝 搭配说明</div>
<div class="modal-desc">${data.yh || '暂无说明'}</div>
<div class="modal-section-title">👁 浏览量</div>
<div class="modal-desc">${data.views || 0} 次</div>
${data.related && data.related.length > 0 ? `
<div class="modal-section-title">🔗 相关推荐</div>
<div class="modal-related">
${data.related.map(r => `
<div class="modal-related-item" onclick="openDetail('${r.hash_id}')">
<div class="modal-related-icon">${getEmoji(r.sw)}</div>
<div class="modal-related-name">${r.sw}</div>
<div class="modal-related-arrow"></div>
</div>
`).join('')}
</div>
` : ''}
<div class="modal-nav">
${data.prev ? `<button class="modal-nav-btn" onclick="openDetail('${data.prev.hash_id}')">← ${data.prev.sw}</button>` : '<div></div>'}
${data.next ? `<button class="modal-nav-btn" onclick="openDetail('${data.next.hash_id}')">${data.next.sw} →</button>` : '<div></div>'}
</div>
<div class="modal-actions">
<button class="modal-action-btn copy" id="copyBtn" onclick="copyFoodInfo('${data.sw}', \`${(data.yh || '').replace(/`/g, '\\`')}\`)">
📋 复制信息
</button>
<button class="modal-action-btn search" onclick="showSearchEngine('${data.sw}')">
🔍 去搜索
</button>
</div>
`;
}
function closeModalOutside(e) {
if (e.target === document.getElementById('detailModal')) {
document.getElementById('detailModal').classList.remove('show');
}
}
function copyFoodInfo(name, desc) {
const text = `${name}】食物相生相克\n${desc}`;
navigator.clipboard.writeText(text).then(() => {
const btn = document.getElementById('copyBtn');
if (btn) {
btn.classList.add('copied');
btn.innerHTML = '✅ 已复制';
setTimeout(() => {
btn.classList.remove('copied');
btn.innerHTML = '📋 复制信息';
}, 2000);
}
}).catch(() => {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
const btn = document.getElementById('copyBtn');
if (btn) {
btn.classList.add('copied');
btn.innerHTML = '✅ 已复制';
setTimeout(() => {
btn.classList.remove('copied');
btn.innerHTML = '📋 复制信息';
}, 2000);
}
});
}
let currentSearchKeyword = '';
function showSearchEngine(keyword) {
currentSearchKeyword = keyword;
document.getElementById('searchEngineMenu').classList.add('show');
}
function closeSearchEngineOutside(e) {
if (e.target === document.getElementById('searchEngineMenu')) {
document.getElementById('searchEngineMenu').classList.remove('show');
}
}
function launchSearch(engine) {
const keyword = currentSearchKeyword;
const encoded = encodeURIComponent(keyword + ' 相生相克');
const urls = {
baidu: `https://www.baidu.com/s?wd=${encoded}`,
bing: `https://www.bing.com/search?q=${encoded}`,
google: `https://www.google.com/search?q=${encoded}`,
sogou: `https://www.sogou.com/web?query=${encoded}`,
};
const url = urls[engine] || urls.baidu;
document.getElementById('searchEngineMenu').classList.remove('show');
window.open(url, '_blank');
}
function createFoodCard(item) {
const catClass = getCategoryClass(item.category_name);
const card = document.createElement('div');
card.className = `food-card ${catClass}`;
card.onclick = () => openDetail(item.hash_id);
card.innerHTML = `
<div class="food-card-top">
<div class="food-card-emoji ${catClass}">${getEmoji(item.sw)}</div>
<div class="food-card-content">
<div class="food-card-name">${item.sw}</div>
<div class="food-card-tag ${catClass}">${getCategoryIcon(item.category_name)} ${item.category_name}</div>
<div class="food-card-desc">${item.yh || ''}</div>
</div>
</div>
<div class="food-card-bottom">
<div class="food-card-views">👁 ${item.views || 0} 次浏览</div>
<div class="food-card-arrow"></div>
</div>`;
return card;
}
function selectCategory(el, categoryId) {
document.querySelectorAll('.category-tab').forEach(t => {
t.classList.remove('active', 'active-xiangke');
});
el.classList.add('active');
if (categoryId !== 'all') {
const cat = categories.find(c => c.id == categoryId);
if (cat && cat.name === '相克') el.classList.add('active-xiangke');
}
currentCategoryId = categoryId === 'all' ? null : categoryId;
loadFoodList(true);
}
function loadMore() {
currentPage++;
loadFoodList();
}
function clearSearch() {
document.getElementById('searchInput').value = '';
document.getElementById('searchClear').classList.remove('show');
document.querySelectorAll('.hot-tag').forEach(t => t.classList.remove('active'));
loadFoodList(true);
}
function quickSearch(keyword) {
const input = document.getElementById('searchInput');
input.value = keyword;
document.getElementById('searchClear').classList.add('show');
document.querySelectorAll('.hot-tag').forEach(t => {
t.classList.toggle('active', t.textContent.trim().replace(/^.{1,2}/, '') === keyword || t.textContent.includes(keyword));
});
searchFood(keyword);
}
document.getElementById('searchInput').addEventListener('input', function() {
const val = this.value;
document.getElementById('searchClear').classList.toggle('show', val.length > 0);
clearTimeout(searchTimer);
searchTimer = setTimeout(() => searchFood(val), 400);
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.getElementById('detailModal').classList.remove('show');
}
});
async function init() {
await loadCategories();
await loadFoodList(true);
await loadHot();
const urlParams = new URLSearchParams(window.location.search);
const keyword = urlParams.get('keyword');
const foodId = urlParams.get('id');
if (keyword) {
quickSearch(keyword);
} else if (foodId) {
openDetail(foodId);
}
}
init();
</script>
</body>
</html>