1651 lines
70 KiB
HTML
1651 lines
70 KiB
HTML
|
|
<html lang="zh-CN">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>今天吃什么 - 随机菜单抽取器</title>
|
|||
|
|
<!-- Tailwind CSS -->
|
|||
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|||
|
|
<!-- Font Awesome -->
|
|||
|
|
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
|||
|
|
|
|||
|
|
<!-- Tailwind 配置 -->
|
|||
|
|
<script>
|
|||
|
|
tailwind.config = {
|
|||
|
|
theme: {
|
|||
|
|
extend: {
|
|||
|
|
colors: {
|
|||
|
|
primary: '#FF6B6B',
|
|||
|
|
secondary: '#4ECDC4',
|
|||
|
|
accent: '#FFE66D',
|
|||
|
|
light: '#F7FFF7',
|
|||
|
|
dark: '#1A535C'
|
|||
|
|
},
|
|||
|
|
fontFamily: {
|
|||
|
|
sans: ['Inter', 'system-ui', 'sans-serif'],
|
|||
|
|
},
|
|||
|
|
animation: {
|
|||
|
|
'spin-slow': 'spin 3s linear infinite',
|
|||
|
|
'bounce-slow': 'bounce 2s infinite',
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style type="text/tailwindcss">
|
|||
|
|
@layer utilities {
|
|||
|
|
.text-shadow {
|
|||
|
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
|||
|
|
}
|
|||
|
|
.card-hover {
|
|||
|
|
@apply transition-all duration-300 hover:shadow-lg hover:-translate-y-1;
|
|||
|
|
}
|
|||
|
|
.btn-primary {
|
|||
|
|
@apply bg-primary text-white font-bold py-3 px-6 rounded-lg shadow-md hover:bg-opacity-90 transition-all duration-300 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-opacity-50;
|
|||
|
|
}
|
|||
|
|
.btn-secondary {
|
|||
|
|
@apply bg-secondary text-white font-bold py-2 px-4 rounded-lg shadow-md hover:bg-opacity-90 transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-secondary focus:ring-opacity-50;
|
|||
|
|
}
|
|||
|
|
.input-field {
|
|||
|
|
@apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent;
|
|||
|
|
}
|
|||
|
|
.badge {
|
|||
|
|
@apply inline-block px-2 py-1 text-xs font-semibold rounded-full;
|
|||
|
|
}
|
|||
|
|
.badge-primary {
|
|||
|
|
@apply bg-primary bg-opacity-20 text-primary;
|
|||
|
|
}
|
|||
|
|
.badge-secondary {
|
|||
|
|
@apply bg-secondary bg-opacity-20 text-secondary;
|
|||
|
|
}
|
|||
|
|
.badge-accent {
|
|||
|
|
@apply bg-accent bg-opacity-20 text-dark;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 自定义动画 */
|
|||
|
|
@keyframes spin-wheel {
|
|||
|
|
from { transform: rotate(0deg); }
|
|||
|
|
to { transform: rotate(360deg); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.spin-wheel-animation {
|
|||
|
|
animation: spin-wheel 3s cubic-bezier(0.5, 0, 0.5, 1) forwards;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 转盘样式 */
|
|||
|
|
.wheel-container {
|
|||
|
|
position: relative;
|
|||
|
|
width: 300px;
|
|||
|
|
height: 300px;
|
|||
|
|
margin: 0 auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.wheel {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
position: relative;
|
|||
|
|
overflow: hidden;
|
|||
|
|
transition: transform 3s cubic-bezier(0.5, 0, 0.5, 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.wheel-center {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 50%;
|
|||
|
|
left: 50%;
|
|||
|
|
transform: translate(-50%, -50%);
|
|||
|
|
width: 60px;
|
|||
|
|
height: 60px;
|
|||
|
|
background-color: white;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
z-index: 10;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.wheel-pointer {
|
|||
|
|
position: absolute;
|
|||
|
|
top: -10px;
|
|||
|
|
left: 50%;
|
|||
|
|
transform: translateX(-50%);
|
|||
|
|
width: 0;
|
|||
|
|
height: 0;
|
|||
|
|
border-left: 15px solid transparent;
|
|||
|
|
border-right: 15px solid transparent;
|
|||
|
|
border-bottom: 25px solid #FF6B6B;
|
|||
|
|
z-index: 10;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 菜品卡片样式 */
|
|||
|
|
.meal-card {
|
|||
|
|
@apply bg-white rounded-xl shadow-md overflow-hidden card-hover;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 加载动画 */
|
|||
|
|
.loading {
|
|||
|
|
@apply inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-primary;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body class="bg-gray-50 min-h-screen">
|
|||
|
|
<!-- 导航栏 -->
|
|||
|
|
<nav class="bg-white shadow-md">
|
|||
|
|
<div class="container mx-auto px-4 py-3 flex justify-between items-center">
|
|||
|
|
<div class="flex items-center space-x-2">
|
|||
|
|
<img src="https://p3-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/rc/pc/super_tool/7bededf640e748eb98cc85f945aa5f41~tplv-a9rns2rl98-image.image?rcl=20251122135701E64BCA52CB770CFFFE61&rk3s=8e244e95&rrcfp=f06b921b&x-expires=1766383039&x-signature=jbkwsTYX7ACM3iPbPDTWLdmr7Dw%3D" alt="Logo" class="w-10 h-10">
|
|||
|
|
<h1 class="text-2xl font-bold text-dark">今天吃什么</h1>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<button id="helpBtn" class="btn-secondary flex items-center">
|
|||
|
|
<i class="fa fa-question-circle mr-2"></i> 使用帮助
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</nav>
|
|||
|
|
|
|||
|
|
<!-- 主内容区 -->
|
|||
|
|
<main class="container mx-auto px-4 py-8">
|
|||
|
|
<!-- 加载状态提示 -->
|
|||
|
|
<div id="loadingIndicator" class="mb-6 flex justify-center items-center hidden">
|
|||
|
|
<div class="loading mr-2"></div>
|
|||
|
|
<span>正在加载菜单数据...</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- JSON文件加载状态提示 -->
|
|||
|
|
<div id="jsonLoadStatus" class="mb-6 hidden">
|
|||
|
|
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative" role="alert">
|
|||
|
|
<strong class="font-bold">成功!</strong>
|
|||
|
|
<span class="block sm:inline" id="jsonLoadMessage">已从menu.json加载菜品数据</span>
|
|||
|
|
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
|
|||
|
|
<button onclick="document.getElementById('jsonLoadStatus').classList.add('hidden')" class="text-green-700 hover:text-green-900"><i class="fa fa-times"></i></button>
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 菜品管理区域 -->
|
|||
|
|
<section class="mb-10">
|
|||
|
|
<div class="bg-white rounded-xl shadow-md p-6">
|
|||
|
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4 gap-4">
|
|||
|
|
<h2 class="text-xl font-bold text-dark flex items-center">
|
|||
|
|
<i class="fa fa-cutlery text-primary mr-2"></i> 菜品管理
|
|||
|
|
</h2>
|
|||
|
|
<div class="flex flex-wrap gap-2">
|
|||
|
|
<button id="toggleAddFormBtn" class="btn-primary flex items-center">
|
|||
|
|
<i class="fa fa-plus mr-2"></i> 添加菜品
|
|||
|
|
</button>
|
|||
|
|
<button id="batchAddBtn" class="btn-secondary flex items-center">
|
|||
|
|
<i class="fa fa-list-ol mr-2"></i> 批量添加
|
|||
|
|
</button>
|
|||
|
|
<label for="jsonFileInput" class="btn-secondary flex items-center cursor-pointer">
|
|||
|
|
<i class="fa fa-upload mr-2"></i> 导入JSON
|
|||
|
|
</label>
|
|||
|
|
<input type="file" id="jsonFileInput" accept=".json" class="hidden">
|
|||
|
|
<button id="clearBtn" class="bg-gray-200 text-gray-700 font-bold py-2 px-4 rounded-lg shadow-sm hover:bg-gray-300 transition-all duration-300 focus:outline-none">
|
|||
|
|
<i class="fa fa-trash mr-2"></i> 清空菜单
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 添加菜品表单 -->
|
|||
|
|
<div id="addMealForm" class="mb-6 p-4 bg-gray-50 rounded-lg hidden">
|
|||
|
|
<h3 class="font-bold mb-3 text-dark">添加菜品</h3>
|
|||
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">菜品名称 <span class="text-red-500">*</span></label>
|
|||
|
|
<input type="text" id="mealName" class="input-field" placeholder="请输入菜品名称">
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">分类 <span class="text-red-500">*</span></label>
|
|||
|
|
<select id="mealCategory" class="input-field">
|
|||
|
|
<option value="主食">主食</option>
|
|||
|
|
<option value="荤菜">荤菜</option>
|
|||
|
|
<option value="素菜">素菜</option>
|
|||
|
|
<option value="汤品">汤品</option>
|
|||
|
|
<option value="其他">其他</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">备注</label>
|
|||
|
|
<input type="text" id="mealNote" class="input-field" placeholder="可选">
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="mt-4 flex justify-end space-x-2">
|
|||
|
|
<button id="cancelAddBtn" class="bg-gray-200 text-gray-700 font-bold py-2 px-4 rounded-lg shadow-sm hover:bg-gray-300 transition-all duration-300 focus:outline-none">
|
|||
|
|
取消
|
|||
|
|
</button>
|
|||
|
|
<button id="confirmAddBtn" class="btn-secondary">
|
|||
|
|
确认添加
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 批量添加菜品表单 -->
|
|||
|
|
<div id="batchAddForm" class="mb-6 p-4 bg-gray-50 rounded-lg hidden">
|
|||
|
|
<h3 class="font-bold mb-3 text-dark">批量添加菜品</h3>
|
|||
|
|
<div class="mb-4">
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">菜品列表 <span class="text-red-500">*</span></label>
|
|||
|
|
<textarea id="batchMealNames" class="input-field" rows="6" placeholder="请输入菜品名称,每行一个"></textarea>
|
|||
|
|
</div>
|
|||
|
|
<div class="mb-4">
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">分类 <span class="text-red-500">*</span></label>
|
|||
|
|
<select id="batchMealCategory" class="input-field">
|
|||
|
|
<option value="主食">主食</option>
|
|||
|
|
<option value="荤菜">荤菜</option>
|
|||
|
|
<option value="素菜">素菜</option>
|
|||
|
|
<option value="汤品">汤品</option>
|
|||
|
|
<option value="其他">其他</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
<div class="mt-4 flex justify-end space-x-2">
|
|||
|
|
<button id="cancelBatchAddBtn" class="bg-gray-200 text-gray-700 font-bold py-2 px-4 rounded-lg shadow-sm hover:bg-gray-300 transition-all duration-300 focus:outline-none">
|
|||
|
|
取消
|
|||
|
|
</button>
|
|||
|
|
<button id="confirmBatchAddBtn" class="btn-secondary">
|
|||
|
|
确认添加
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<!-- 菜单列表区域 -->
|
|||
|
|
<section class="mb-10">
|
|||
|
|
<div class="bg-white rounded-xl shadow-md p-6">
|
|||
|
|
<div class="flex justify-between items-center mb-4">
|
|||
|
|
<h2 class="text-xl font-bold text-dark flex items-center">
|
|||
|
|
<i class="fa fa-list text-primary mr-2"></i> 菜单列表
|
|||
|
|
</h2>
|
|||
|
|
<div class="flex items-center space-x-2">
|
|||
|
|
<div class="relative">
|
|||
|
|
<input type="text" id="searchInput" placeholder="搜索菜品..." class="input-field pr-10">
|
|||
|
|
<i class="fa fa-search absolute right-3 top-3 text-gray-400"></i>
|
|||
|
|
</div>
|
|||
|
|
<select id="categoryFilter" class="input-field">
|
|||
|
|
<option value="all">全部分类</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div id="menuList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 max-h-96 overflow-y-auto p-2">
|
|||
|
|
<!-- 菜单列表将通过 JavaScript 动态生成 -->
|
|||
|
|
<div class="col-span-full text-center py-10 text-gray-500" id="emptyMenuMsg">
|
|||
|
|
<i class="fa fa-info-circle text-3xl mb-3"></i>
|
|||
|
|
<p>暂无菜单数据,请点击上方"添加菜品"按钮添加</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="mt-4 flex justify-end items-center">
|
|||
|
|
<div class="text-sm text-gray-500">
|
|||
|
|
共 <span id="totalMeals">0</span> 个菜品
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<!-- 随机抽取区域 -->
|
|||
|
|
<section class="mb-10">
|
|||
|
|
<div class="bg-white rounded-xl shadow-md p-6">
|
|||
|
|
<h2 class="text-xl font-bold mb-4 text-dark flex items-center">
|
|||
|
|
<i class="fa fa-random text-primary mr-2"></i> 随机抽取
|
|||
|
|
</h2>
|
|||
|
|
|
|||
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|||
|
|
<!-- 左侧:转盘 -->
|
|||
|
|
<div class="flex flex-col items-center justify-center">
|
|||
|
|
<div class="wheel-container">
|
|||
|
|
<div class="wheel-pointer"></div>
|
|||
|
|
<div id="wheel" class="wheel">
|
|||
|
|
<!-- 转盘内容将通过 JavaScript 动态生成 -->
|
|||
|
|
<img src="https://p3-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/rc/pc/super_tool/32352db51cfe4b978936ee83db986dc1~tplv-a9rns2rl98-image.image?rcl=20251122135701E64BCA52CB770CFFFE61&rk3s=8e244e95&rrcfp=f06b921b&x-expires=1766383052&x-signature=eeKdGn2fh3DIiXJ%2B%2FWpgP9UFJPE%3D" alt="转盘" class="w-full h-full object-cover opacity-50" id="wheelPlaceholder">
|
|||
|
|
</div>
|
|||
|
|
<div class="wheel-center">
|
|||
|
|
<i class="fa fa-cutlery text-2xl text-primary"></i>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="mt-6 w-full max-w-xs">
|
|||
|
|
<div class="mb-4">
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">抽取数量</label>
|
|||
|
|
<div class="flex items-center">
|
|||
|
|
<button id="decreaseCount" class="bg-gray-200 text-gray-700 w-10 h-10 rounded-l-lg flex items-center justify-center hover:bg-gray-300 transition-all duration-300">
|
|||
|
|
<i class="fa fa-minus"></i>
|
|||
|
|
</button>
|
|||
|
|
<input type="number" id="drawCount" min="1" value="1" class="input-field text-center border-l-0 border-r-0 rounded-none">
|
|||
|
|
<button id="increaseCount" class="bg-gray-200 text-gray-700 w-10 h-10 rounded-r-lg flex items-center justify-center hover:bg-gray-300 transition-all duration-300">
|
|||
|
|
<i class="fa fa-plus"></i>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="mb-4">
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">抽取方式</label>
|
|||
|
|
<div class="flex space-x-2">
|
|||
|
|
<button id="normalDrawBtn" class="btn-primary flex-1">
|
|||
|
|
<i class="fa fa-random mr-2"></i> 随机抽取
|
|||
|
|
</button>
|
|||
|
|
<button id="smartDrawBtn" class="btn-secondary flex-1">
|
|||
|
|
<i class="fa fa-lightbulb-o mr-2"></i> 智能推荐
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 右侧:结果展示 -->
|
|||
|
|
<div>
|
|||
|
|
<div class="bg-gray-50 rounded-lg p-6 min-h-[300px] flex flex-col">
|
|||
|
|
<h3 class="font-bold mb-4 text-dark">抽取结果</h3>
|
|||
|
|
|
|||
|
|
<div id="resultContainer" class="flex-1">
|
|||
|
|
<!-- 未抽取时的提示 -->
|
|||
|
|
<div id="noResultMsg" class="flex flex-col items-center justify-center h-full text-gray-500">
|
|||
|
|
<i class="fa fa-hand-pointer-o text-4xl mb-3"></i>
|
|||
|
|
<p>点击上方按钮开始抽取</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 抽取结果列表 -->
|
|||
|
|
<div id="resultList" class="hidden">
|
|||
|
|
<!-- 结果将通过 JavaScript 动态生成 -->
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="mt-4 flex justify-between">
|
|||
|
|
<button id="saveResultBtn" class="btn-secondary flex items-center hidden">
|
|||
|
|
<i class="fa fa-save mr-2"></i> 保存结果
|
|||
|
|
</button>
|
|||
|
|
<button id="shareResultBtn" class="btn-secondary flex items-center hidden">
|
|||
|
|
<i class="fa fa-share-alt mr-2"></i> 分享结果
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 历史记录 -->
|
|||
|
|
<div class="mt-6">
|
|||
|
|
<h3 class="font-bold mb-3 text-dark flex items-center">
|
|||
|
|
<i class="fa fa-history text-primary mr-2"></i> 历史记录
|
|||
|
|
</h3>
|
|||
|
|
<div id="historyList" class="bg-gray-50 rounded-lg p-4 max-h-40 overflow-y-auto">
|
|||
|
|
<!-- 历史记录将通过 JavaScript 动态生成 -->
|
|||
|
|
<div class="text-center py-3 text-gray-500 text-sm">
|
|||
|
|
暂无历史记录
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
</main>
|
|||
|
|
|
|||
|
|
<!-- 页脚 -->
|
|||
|
|
<footer class="bg-dark text-white py-6">
|
|||
|
|
<div class="container mx-auto px-4 text-center">
|
|||
|
|
<p>© 2025 今天吃什么 - 随机菜单抽取器</p>
|
|||
|
|
<p class="text-sm text-gray-400 mt-2">解决您的选择困难症</p>
|
|||
|
|
</div>
|
|||
|
|
</footer>
|
|||
|
|
|
|||
|
|
<!-- 帮助模态框 -->
|
|||
|
|
<div id="helpModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
|||
|
|
<div class="bg-white rounded-xl shadow-xl max-w-2xl w-full max-h-[80vh] overflow-y-auto">
|
|||
|
|
<div class="p-6">
|
|||
|
|
<div class="flex justify-between items-center mb-4">
|
|||
|
|
<h2 class="text-xl font-bold text-dark">使用帮助</h2>
|
|||
|
|
<button id="closeHelpBtn" class="text-gray-500 hover:text-gray-700">
|
|||
|
|
<i class="fa fa-times text-xl"></i>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="space-y-4">
|
|||
|
|
<div>
|
|||
|
|
<h3 class="font-bold text-primary mb-2">1. 添加菜品</h3>
|
|||
|
|
<p>您可以通过以下三种方式添加菜品:</p>
|
|||
|
|
<ul class="list-disc pl-5 mt-2">
|
|||
|
|
<li><strong>单个添加</strong>:点击"添加菜品"按钮,填写菜品名称、分类和备注信息</li>
|
|||
|
|
<li><strong>批量添加</strong>:点击"批量添加"按钮,在文本框中每行输入一个菜品名称</li>
|
|||
|
|
<li><strong>导入JSON</strong>:点击"导入JSON"按钮,选择包含菜品数据的JSON文件</li>
|
|||
|
|
<li><strong>自动加载</strong>:页面会自动加载同目录下的menu.json文件</li>
|
|||
|
|
</ul>
|
|||
|
|
<p class="mt-2">JSON文件支持两种格式:</p>
|
|||
|
|
<ul class="list-disc pl-5 mt-2">
|
|||
|
|
<li>简单格式:<code>["菜品1", "菜品2", "菜品3"]</code></li>
|
|||
|
|
<li>对象格式:<code>[{"name": "菜品1", "category": "分类", "note": "备注"}, ...]</code></li>
|
|||
|
|
</ul>
|
|||
|
|
<p class="mt-2">对象格式支持的字段包括:name/title/dish/food(名称)、category/type/kind(分类)、note/remark/description(备注)</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<h3 class="font-bold text-primary mb-2">2. 菜单管理</h3>
|
|||
|
|
<p>导入菜单后,您可以:</p>
|
|||
|
|
<ul class="list-disc pl-5 mt-2">
|
|||
|
|
<li>使用搜索框搜索菜品</li>
|
|||
|
|
<li>使用分类筛选器筛选菜品</li>
|
|||
|
|
<li>点击"添加菜品"按钮手动添加菜品</li>
|
|||
|
|
<li>点击菜品卡片上的编辑或删除按钮修改或删除菜品</li>
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<h3 class="font-bold text-primary mb-2">3. 随机抽取</h3>
|
|||
|
|
<p>设置抽取数量后,点击以下按钮之一进行抽取:</p>
|
|||
|
|
<ul class="list-disc pl-5 mt-2">
|
|||
|
|
<li><strong>随机抽取</strong>:完全随机地从菜单中抽取指定数量的菜品</li>
|
|||
|
|
<li><strong>智能推荐</strong>:基于历史抽取记录,优先推荐较少出现的菜品</li>
|
|||
|
|
</ul>
|
|||
|
|
<p class="mt-2">抽取结果将显示在右侧的结果区域,您可以保存或分享结果。</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<h3 class="font-bold text-primary mb-2">4. 数据存储</h3>
|
|||
|
|
<p>本应用使用浏览器的本地存储功能保存您的菜单数据和历史记录,数据仅存储在您的设备上,不会上传到服务器。</p>
|
|||
|
|
<p class="mt-2">清除浏览器缓存或使用隐私模式可能会导致数据丢失,请定期备份您的 Excel 文件。</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 编辑菜品模态框 -->
|
|||
|
|
<div id="editModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
|||
|
|
<div class="bg-white rounded-xl shadow-xl max-w-md w-full">
|
|||
|
|
<div class="p-6">
|
|||
|
|
<div class="flex justify-between items-center mb-4">
|
|||
|
|
<h2 class="text-xl font-bold text-dark">编辑菜品</h2>
|
|||
|
|
<button id="closeEditBtn" class="text-gray-500 hover:text-gray-700">
|
|||
|
|
<i class="fa fa-times text-xl"></i>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="space-y-4">
|
|||
|
|
<div>
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">菜品名称</label>
|
|||
|
|
<input type="text" id="editMealName" class="input-field">
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">分类</label>
|
|||
|
|
<select id="editMealCategory" class="input-field">
|
|||
|
|
<option value="主食">主食</option>
|
|||
|
|
<option value="荤菜">荤菜</option>
|
|||
|
|
<option value="素菜">素菜</option>
|
|||
|
|
<option value="汤品">汤品</option>
|
|||
|
|
<option value="其他">其他</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">备注</label>
|
|||
|
|
<input type="text" id="editMealNote" class="input-field">
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flex justify-end space-x-2 mt-6">
|
|||
|
|
<button id="cancelEditBtn" class="bg-gray-200 text-gray-700 font-bold py-2 px-4 rounded-lg shadow-sm hover:bg-gray-300 transition-all duration-300 focus:outline-none">
|
|||
|
|
取消
|
|||
|
|
</button>
|
|||
|
|
<button id="confirmEditBtn" class="btn-secondary">
|
|||
|
|
确认修改
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 分享结果模态框 -->
|
|||
|
|
<div id="shareModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
|
|||
|
|
<div class="bg-white rounded-xl shadow-xl max-w-md w-full">
|
|||
|
|
<div class="p-6">
|
|||
|
|
<div class="flex justify-between items-center mb-4">
|
|||
|
|
<h2 class="text-xl font-bold text-dark">分享结果</h2>
|
|||
|
|
<button id="closeShareBtn" class="text-gray-500 hover:text-gray-700">
|
|||
|
|
<i class="fa fa-times text-xl"></i>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="space-y-4">
|
|||
|
|
<div>
|
|||
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">分享文本</label>
|
|||
|
|
<textarea id="shareText" class="input-field" rows="4" readonly=""></textarea>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="flex justify-between space-x-2 mt-6">
|
|||
|
|
<button id="copyShareBtn" class="btn-secondary flex-1">
|
|||
|
|
<i class="fa fa-copy mr-2"></i> 复制文本
|
|||
|
|
</button>
|
|||
|
|
<button id="shareWechatBtn" class="btn-secondary flex-1">
|
|||
|
|
<i class="fa fa-wechat mr-2"></i> 分享到微信
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
// 全局变量
|
|||
|
|
let mealData = [];
|
|||
|
|
let filteredMeals = [];
|
|||
|
|
let currentEditIndex = -1;
|
|||
|
|
let drawHistory = [];
|
|||
|
|
let categories = ['主食', '荤菜', '素菜', '汤品', '其他'];
|
|||
|
|
|
|||
|
|
// DOM 元素
|
|||
|
|
const clearBtn = document.getElementById('clearBtn');
|
|||
|
|
const menuList = document.getElementById('menuList');
|
|||
|
|
const emptyMenuMsg = document.getElementById('emptyMenuMsg');
|
|||
|
|
const totalMeals = document.getElementById('totalMeals');
|
|||
|
|
const searchInput = document.getElementById('searchInput');
|
|||
|
|
const categoryFilter = document.getElementById('categoryFilter');
|
|||
|
|
const toggleAddFormBtn = document.getElementById('toggleAddFormBtn');
|
|||
|
|
const addMealForm = document.getElementById('addMealForm');
|
|||
|
|
const cancelAddBtn = document.getElementById('cancelAddBtn');
|
|||
|
|
const confirmAddBtn = document.getElementById('confirmAddBtn');
|
|||
|
|
const mealName = document.getElementById('mealName');
|
|||
|
|
const mealCategory = document.getElementById('mealCategory');
|
|||
|
|
const mealNote = document.getElementById('mealNote');
|
|||
|
|
const decreaseCount = document.getElementById('decreaseCount');
|
|||
|
|
const increaseCount = document.getElementById('increaseCount');
|
|||
|
|
const drawCount = document.getElementById('drawCount');
|
|||
|
|
const normalDrawBtn = document.getElementById('normalDrawBtn');
|
|||
|
|
const smartDrawBtn = document.getElementById('smartDrawBtn');
|
|||
|
|
const resultContainer = document.getElementById('resultContainer');
|
|||
|
|
const noResultMsg = document.getElementById('noResultMsg');
|
|||
|
|
const resultList = document.getElementById('resultList');
|
|||
|
|
const saveResultBtn = document.getElementById('saveResultBtn');
|
|||
|
|
const shareResultBtn = document.getElementById('shareResultBtn');
|
|||
|
|
const historyList = document.getElementById('historyList');
|
|||
|
|
const helpBtn = document.getElementById('helpBtn');
|
|||
|
|
const helpModal = document.getElementById('helpModal');
|
|||
|
|
const closeHelpBtn = document.getElementById('closeHelpBtn');
|
|||
|
|
const editModal = document.getElementById('editModal');
|
|||
|
|
const closeEditBtn = document.getElementById('closeEditBtn');
|
|||
|
|
const cancelEditBtn = document.getElementById('cancelEditBtn');
|
|||
|
|
const confirmEditBtn = document.getElementById('confirmEditBtn');
|
|||
|
|
const editMealName = document.getElementById('editMealName');
|
|||
|
|
const editMealCategory = document.getElementById('editMealCategory');
|
|||
|
|
const editMealNote = document.getElementById('editMealNote');
|
|||
|
|
const shareModal = document.getElementById('shareModal');
|
|||
|
|
const closeShareBtn = document.getElementById('closeShareBtn');
|
|||
|
|
const copyShareBtn = document.getElementById('copyShareBtn');
|
|||
|
|
const shareWechatBtn = document.getElementById('shareWechatBtn');
|
|||
|
|
const shareText = document.getElementById('shareText');
|
|||
|
|
const wheel = document.getElementById('wheel');
|
|||
|
|
const wheelPlaceholder = document.getElementById('wheelPlaceholder');
|
|||
|
|
const loadingIndicator = document.getElementById('loadingIndicator');
|
|||
|
|
const jsonLoadStatus = document.getElementById('jsonLoadStatus');
|
|||
|
|
const jsonLoadMessage = document.getElementById('jsonLoadMessage');
|
|||
|
|
|
|||
|
|
// 初始化
|
|||
|
|
function init() {
|
|||
|
|
console.log('初始化应用');
|
|||
|
|
|
|||
|
|
// 显示加载状态
|
|||
|
|
loadingIndicator.classList.remove('hidden');
|
|||
|
|
|
|||
|
|
// 加载本地存储的数据
|
|||
|
|
loadFromLocalStorage();
|
|||
|
|
|
|||
|
|
// 优先读取本地JSON文件
|
|||
|
|
loadMealsFromJsonFile()
|
|||
|
|
.then(() => {
|
|||
|
|
// 隐藏加载状态
|
|||
|
|
loadingIndicator.classList.add('hidden');
|
|||
|
|
|
|||
|
|
// 更新菜单列表
|
|||
|
|
updateMenuList();
|
|||
|
|
|
|||
|
|
// 更新分类筛选器
|
|||
|
|
updateCategoryFilter();
|
|||
|
|
|
|||
|
|
// 更新历史记录
|
|||
|
|
updateHistoryList();
|
|||
|
|
|
|||
|
|
// 更新转盘
|
|||
|
|
updateWheel();
|
|||
|
|
})
|
|||
|
|
.catch(error => {
|
|||
|
|
console.log('JSON文件加载失败:', error);
|
|||
|
|
// 隐藏加载状态
|
|||
|
|
loadingIndicator.classList.add('hidden');
|
|||
|
|
|
|||
|
|
// 即使JSON加载失败,也要初始化界面
|
|||
|
|
updateMenuList();
|
|||
|
|
updateCategoryFilter();
|
|||
|
|
updateHistoryList();
|
|||
|
|
updateWheel();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 事件监听
|
|||
|
|
clearBtn.addEventListener('click', clearMenu);
|
|||
|
|
searchInput.addEventListener('input', handleSearch);
|
|||
|
|
categoryFilter.addEventListener('change', handleCategoryFilter);
|
|||
|
|
toggleAddFormBtn.addEventListener('click', toggleAddForm);
|
|||
|
|
cancelAddBtn.addEventListener('click', toggleAddForm);
|
|||
|
|
confirmAddBtn.addEventListener('click', addMeal);
|
|||
|
|
|
|||
|
|
// 批量添加相关事件
|
|||
|
|
const batchAddBtn = document.getElementById('batchAddBtn');
|
|||
|
|
const cancelBatchAddBtn = document.getElementById('cancelBatchAddBtn');
|
|||
|
|
const confirmBatchAddBtn = document.getElementById('confirmBatchAddBtn');
|
|||
|
|
|
|||
|
|
batchAddBtn.addEventListener('click', toggleBatchAddForm);
|
|||
|
|
cancelBatchAddBtn.addEventListener('click', toggleBatchAddForm);
|
|||
|
|
confirmBatchAddBtn.addEventListener('click', batchAddMeals);
|
|||
|
|
|
|||
|
|
// JSON导入相关事件
|
|||
|
|
const jsonFileInput = document.getElementById('jsonFileInput');
|
|||
|
|
jsonFileInput.addEventListener('change', handleJsonFileImport);
|
|||
|
|
|
|||
|
|
// 抽取相关事件
|
|||
|
|
decreaseCount.addEventListener('click', decreaseDrawCount);
|
|||
|
|
increaseCount.addEventListener('click', increaseDrawCount);
|
|||
|
|
normalDrawBtn.addEventListener('click', () => drawMeals(false));
|
|||
|
|
smartDrawBtn.addEventListener('click', () => drawMeals(true));
|
|||
|
|
|
|||
|
|
// 结果相关事件
|
|||
|
|
saveResultBtn.addEventListener('click', saveResult);
|
|||
|
|
shareResultBtn.addEventListener('click', showShareModal);
|
|||
|
|
|
|||
|
|
// 模态框相关事件
|
|||
|
|
helpBtn.addEventListener('click', showHelpModal);
|
|||
|
|
closeHelpBtn.addEventListener('click', hideHelpModal);
|
|||
|
|
closeEditBtn.addEventListener('click', hideEditModal);
|
|||
|
|
cancelEditBtn.addEventListener('click', hideEditModal);
|
|||
|
|
confirmEditBtn.addEventListener('click', updateMeal);
|
|||
|
|
closeShareBtn.addEventListener('click', hideShareModal);
|
|||
|
|
copyShareBtn.addEventListener('click', copyShareText);
|
|||
|
|
shareWechatBtn.addEventListener('click', shareToWechat);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 批量添加菜品
|
|||
|
|
function batchAddMeals() {
|
|||
|
|
console.log('执行批量添加菜品');
|
|||
|
|
const batchMealNames = document.getElementById('batchMealNames');
|
|||
|
|
const batchMealCategory = document.getElementById('batchMealCategory');
|
|||
|
|
|
|||
|
|
const namesText = batchMealNames.value.trim();
|
|||
|
|
const category = batchMealCategory.value;
|
|||
|
|
|
|||
|
|
console.log('菜品名称文本:', namesText);
|
|||
|
|
console.log('分类:', category);
|
|||
|
|
|
|||
|
|
// 验证输入
|
|||
|
|
if (!namesText) {
|
|||
|
|
console.log('错误: 菜品名称文本为空');
|
|||
|
|
alert('请输入菜品名称');
|
|||
|
|
batchMealNames.focus();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 按行分割
|
|||
|
|
const names = namesText.split('\n').filter(name => name.trim());
|
|||
|
|
|
|||
|
|
console.log('解析后的菜品名称:', names);
|
|||
|
|
|
|||
|
|
if (names.length === 0) {
|
|||
|
|
console.log('错误: 未找到有效的菜品名称');
|
|||
|
|
alert('未找到有效的菜品名称');
|
|||
|
|
batchMealNames.focus();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查重复
|
|||
|
|
const duplicates = [];
|
|||
|
|
names.forEach(name => {
|
|||
|
|
if (mealData.some(meal => meal.name === name.trim())) {
|
|||
|
|
duplicates.push(name.trim());
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log('重复的菜品:', duplicates);
|
|||
|
|
|
|||
|
|
if (duplicates.length > 0) {
|
|||
|
|
const confirmMsg = `以下菜品已存在,是否仍然添加其他菜品?\n${duplicates.join('\n')}`;
|
|||
|
|
if (!confirm(confirmMsg)) {
|
|||
|
|
console.log('用户取消添加');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加菜品
|
|||
|
|
let addedCount = 0;
|
|||
|
|
names.forEach(name => {
|
|||
|
|
const trimmedName = name.trim();
|
|||
|
|
if (trimmedName && !mealData.some(meal => meal.name === trimmedName)) {
|
|||
|
|
const newMeal = {
|
|||
|
|
name: trimmedName,
|
|||
|
|
category: category,
|
|||
|
|
note: '',
|
|||
|
|
count: 0
|
|||
|
|
};
|
|||
|
|
console.log('添加新菜品:', newMeal);
|
|||
|
|
mealData.push(newMeal);
|
|||
|
|
addedCount++;
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log('成功添加的菜品数量:', addedCount);
|
|||
|
|
|
|||
|
|
// 添加新分类
|
|||
|
|
if (!categories.includes(category)) {
|
|||
|
|
console.log('添加新分类:', category);
|
|||
|
|
categories.push(category);
|
|||
|
|
updateCategoryFilter();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新界面
|
|||
|
|
console.log('更新菜单列表');
|
|||
|
|
updateMenuList();
|
|||
|
|
updateWheel();
|
|||
|
|
saveToLocalStorage();
|
|||
|
|
|
|||
|
|
// 显示成功消息
|
|||
|
|
console.log('批量添加完成');
|
|||
|
|
alert(`成功添加 ${addedCount} 个菜品`);
|
|||
|
|
|
|||
|
|
// 隐藏表单
|
|||
|
|
toggleBatchAddForm();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 切换批量添加菜品表单
|
|||
|
|
function toggleBatchAddForm() {
|
|||
|
|
const batchAddForm = document.getElementById('batchAddForm');
|
|||
|
|
const addMealForm = document.getElementById('addMealForm');
|
|||
|
|
|
|||
|
|
// 隐藏单个添加表单
|
|||
|
|
addMealForm.classList.add('hidden');
|
|||
|
|
|
|||
|
|
// 切换批量添加表单
|
|||
|
|
batchAddForm.classList.toggle('hidden');
|
|||
|
|
|
|||
|
|
// 如果显示表单,清空输入
|
|||
|
|
if (!batchAddForm.classList.contains('hidden')) {
|
|||
|
|
document.getElementById('batchMealNames').value = '';
|
|||
|
|
document.getElementById('batchMealNames').focus();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清空菜单
|
|||
|
|
function clearMenu() {
|
|||
|
|
if (mealData.length === 0) return;
|
|||
|
|
|
|||
|
|
if (confirm('确定要清空所有菜单数据吗?此操作不可撤销。')) {
|
|||
|
|
mealData = [];
|
|||
|
|
filteredMeals = [];
|
|||
|
|
updateMenuList();
|
|||
|
|
updateCategoryFilter();
|
|||
|
|
updateWheel();
|
|||
|
|
saveToLocalStorage();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新菜单列表
|
|||
|
|
function updateMenuList() {
|
|||
|
|
// 如果没有数据,显示空消息
|
|||
|
|
if (mealData.length === 0) {
|
|||
|
|
emptyMenuMsg.classList.remove('hidden');
|
|||
|
|
menuList.innerHTML = '';
|
|||
|
|
menuList.appendChild(emptyMenuMsg);
|
|||
|
|
totalMeals.textContent = '0';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 隐藏空消息
|
|||
|
|
emptyMenuMsg.classList.add('hidden');
|
|||
|
|
|
|||
|
|
// 更新总数
|
|||
|
|
totalMeals.textContent = mealData.length;
|
|||
|
|
|
|||
|
|
// 过滤数据
|
|||
|
|
filteredMeals = mealData.filter(meal => {
|
|||
|
|
const matchesSearch = meal.name.toLowerCase().includes(searchInput.value.toLowerCase()) ||
|
|||
|
|
meal.note.toLowerCase().includes(searchInput.value.toLowerCase());
|
|||
|
|
const matchesCategory = categoryFilter.value === 'all' || meal.category === categoryFilter.value;
|
|||
|
|
return matchesSearch && matchesCategory;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 清空列表
|
|||
|
|
menuList.innerHTML = '';
|
|||
|
|
|
|||
|
|
// 添加菜品卡片
|
|||
|
|
filteredMeals.forEach((meal, index) => {
|
|||
|
|
const card = document.createElement('div');
|
|||
|
|
card.className = 'meal-card';
|
|||
|
|
|
|||
|
|
// 获取分类对应的颜色
|
|||
|
|
const categoryColor = getCategoryColor(meal.category);
|
|||
|
|
|
|||
|
|
card.innerHTML = `
|
|||
|
|
<div class="p-4">
|
|||
|
|
<div class="flex justify-between items-start">
|
|||
|
|
<h3 class="font-bold text-lg">${meal.name}</h3>
|
|||
|
|
<span class="badge ${categoryColor}">${meal.category}</span>
|
|||
|
|
</div>
|
|||
|
|
${meal.note ? `<p class="text-gray-600 text-sm mt-1">${meal.note}</p>` : ''}
|
|||
|
|
<div class="flex justify-end mt-3 space-x-2">
|
|||
|
|
<button class="text-gray-500 hover:text-primary" onclick="editMeal(${index})">
|
|||
|
|
<i class="fa fa-pencil"></i>
|
|||
|
|
</button>
|
|||
|
|
<button class="text-gray-500 hover:text-red-500" onclick="deleteMeal(${index})">
|
|||
|
|
<i class="fa fa-trash"></i>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
menuList.appendChild(card);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取分类对应的颜色
|
|||
|
|
function getCategoryColor(category) {
|
|||
|
|
switch (category) {
|
|||
|
|
case '主食': return 'badge-primary';
|
|||
|
|
case '荤菜': return 'badge-secondary';
|
|||
|
|
case '素菜': return 'badge-accent';
|
|||
|
|
case '汤品': return 'badge-primary';
|
|||
|
|
default: return 'badge-secondary';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新分类筛选器
|
|||
|
|
function updateCategoryFilter() {
|
|||
|
|
// 清空现有选项
|
|||
|
|
categoryFilter.innerHTML = '<option value="all">全部分类</option>';
|
|||
|
|
|
|||
|
|
// 添加分类选项
|
|||
|
|
categories.forEach(category => {
|
|||
|
|
const option = document.createElement('option');
|
|||
|
|
option.value = category;
|
|||
|
|
option.textContent = category;
|
|||
|
|
categoryFilter.appendChild(option);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理搜索
|
|||
|
|
function handleSearch() {
|
|||
|
|
updateMenuList();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理分类筛选
|
|||
|
|
function handleCategoryFilter() {
|
|||
|
|
updateMenuList();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 切换添加菜品表单
|
|||
|
|
function toggleAddForm() {
|
|||
|
|
console.log('切换添加菜品表单');
|
|||
|
|
// 隐藏批量添加表单
|
|||
|
|
const batchAddForm = document.getElementById('batchAddForm');
|
|||
|
|
batchAddForm.classList.add('hidden');
|
|||
|
|
|
|||
|
|
// 切换单个添加表单
|
|||
|
|
addMealForm.classList.toggle('hidden');
|
|||
|
|
|
|||
|
|
// 如果显示表单,清空输入并聚焦
|
|||
|
|
if (!addMealForm.classList.contains('hidden')) {
|
|||
|
|
console.log('显示添加菜品表单');
|
|||
|
|
mealName.value = '';
|
|||
|
|
mealNote.value = '';
|
|||
|
|
mealName.focus();
|
|||
|
|
} else {
|
|||
|
|
console.log('隐藏添加菜品表单');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加菜品
|
|||
|
|
function addMeal() {
|
|||
|
|
console.log('执行添加菜品');
|
|||
|
|
const name = mealName.value.trim();
|
|||
|
|
const category = mealCategory.value;
|
|||
|
|
const note = mealNote.value.trim();
|
|||
|
|
|
|||
|
|
console.log('菜品名称:', name);
|
|||
|
|
console.log('分类:', category);
|
|||
|
|
console.log('备注:', note);
|
|||
|
|
|
|||
|
|
// 验证输入
|
|||
|
|
if (!name) {
|
|||
|
|
console.log('错误: 菜品名称为空');
|
|||
|
|
alert('请输入菜品名称');
|
|||
|
|
mealName.focus();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查重复
|
|||
|
|
if (mealData.some(meal => meal.name === name)) {
|
|||
|
|
console.log('错误: 菜品名称已存在');
|
|||
|
|
alert('菜品名称已存在');
|
|||
|
|
mealName.focus();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加菜品
|
|||
|
|
const newMeal = {
|
|||
|
|
name,
|
|||
|
|
category,
|
|||
|
|
note,
|
|||
|
|
count: 0
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
console.log('添加新菜品:', newMeal);
|
|||
|
|
mealData.push(newMeal);
|
|||
|
|
|
|||
|
|
// 添加新分类
|
|||
|
|
if (!categories.includes(category)) {
|
|||
|
|
console.log('添加新分类:', category);
|
|||
|
|
categories.push(category);
|
|||
|
|
updateCategoryFilter();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新界面
|
|||
|
|
console.log('更新菜单列表');
|
|||
|
|
updateMenuList();
|
|||
|
|
updateWheel();
|
|||
|
|
saveToLocalStorage();
|
|||
|
|
|
|||
|
|
// 显示成功消息
|
|||
|
|
console.log('菜品添加成功');
|
|||
|
|
alert(`菜品"${name}"添加成功!`);
|
|||
|
|
|
|||
|
|
// 隐藏表单
|
|||
|
|
toggleAddForm();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 编辑菜品
|
|||
|
|
function editMeal(index) {
|
|||
|
|
currentEditIndex = index;
|
|||
|
|
const meal = filteredMeals[index];
|
|||
|
|
|
|||
|
|
// 填充表单
|
|||
|
|
editMealName.value = meal.name;
|
|||
|
|
editMealCategory.value = meal.category;
|
|||
|
|
editMealNote.value = meal.note;
|
|||
|
|
|
|||
|
|
// 显示模态框
|
|||
|
|
editModal.classList.remove('hidden');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 隐藏编辑模态框
|
|||
|
|
function hideEditModal() {
|
|||
|
|
editModal.classList.add('hidden');
|
|||
|
|
currentEditIndex = -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新菜品
|
|||
|
|
function updateMeal() {
|
|||
|
|
if (currentEditIndex === -1) return;
|
|||
|
|
|
|||
|
|
const name = editMealName.value.trim();
|
|||
|
|
const category = editMealCategory.value;
|
|||
|
|
const note = editMealNote.value.trim();
|
|||
|
|
|
|||
|
|
// 验证输入
|
|||
|
|
if (!name) {
|
|||
|
|
alert('请输入菜品名称');
|
|||
|
|
editMealName.focus();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查重复(排除当前菜品)
|
|||
|
|
const originalIndex = mealData.findIndex(meal => meal === filteredMeals[currentEditIndex]);
|
|||
|
|
if (mealData.some((meal, index) => meal.name === name && index !== originalIndex)) {
|
|||
|
|
alert('菜品名称已存在');
|
|||
|
|
editMealName.focus();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新菜品
|
|||
|
|
const meal = mealData[originalIndex];
|
|||
|
|
meal.name = name;
|
|||
|
|
|
|||
|
|
// 如果分类改变,更新分类列表
|
|||
|
|
if (meal.category !== category) {
|
|||
|
|
meal.category = category;
|
|||
|
|
if (!categories.includes(category)) {
|
|||
|
|
categories.push(category);
|
|||
|
|
updateCategoryFilter();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
meal.note = note;
|
|||
|
|
|
|||
|
|
// 更新界面
|
|||
|
|
updateMenuList();
|
|||
|
|
updateWheel();
|
|||
|
|
saveToLocalStorage();
|
|||
|
|
|
|||
|
|
// 隐藏模态框
|
|||
|
|
hideEditModal();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除菜品
|
|||
|
|
function deleteMeal(index) {
|
|||
|
|
const meal = filteredMeals[index];
|
|||
|
|
|
|||
|
|
if (confirm(`确定要删除菜品"${meal.name}"吗?`)) {
|
|||
|
|
// 找到原始索引
|
|||
|
|
const originalIndex = mealData.findIndex(m => m === meal);
|
|||
|
|
|
|||
|
|
// 删除菜品
|
|||
|
|
mealData.splice(originalIndex, 1);
|
|||
|
|
|
|||
|
|
// 更新界面
|
|||
|
|
updateMenuList();
|
|||
|
|
updateCategoryFilter();
|
|||
|
|
updateWheel();
|
|||
|
|
saveToLocalStorage();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 减少抽取数量
|
|||
|
|
function decreaseDrawCount() {
|
|||
|
|
const count = parseInt(drawCount.value);
|
|||
|
|
if (count > 1) {
|
|||
|
|
drawCount.value = count - 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 增加抽取数量
|
|||
|
|
function increaseDrawCount() {
|
|||
|
|
const count = parseInt(drawCount.value);
|
|||
|
|
if (count < mealData.length) {
|
|||
|
|
drawCount.value = count + 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 抽取菜品
|
|||
|
|
function drawMeals(smart = false) {
|
|||
|
|
// 检查是否有菜品
|
|||
|
|
if (mealData.length === 0) {
|
|||
|
|
alert('请先导入菜单或添加菜品');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const count = parseInt(drawCount.value);
|
|||
|
|
|
|||
|
|
// 检查抽取数量
|
|||
|
|
if (count > mealData.length) {
|
|||
|
|
alert(`菜单中只有 ${mealData.length} 个菜品,无法抽取 ${count} 个`);
|
|||
|
|
drawCount.value = mealData.length;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示转盘动画
|
|||
|
|
showWheelAnimation();
|
|||
|
|
|
|||
|
|
// 延迟显示结果,模拟转盘旋转
|
|||
|
|
setTimeout(() => {
|
|||
|
|
// 抽取菜品
|
|||
|
|
let selectedMeals;
|
|||
|
|
if (smart) {
|
|||
|
|
// 智能推荐:优先选择抽取次数少的菜品
|
|||
|
|
selectedMeals = smartDraw(count);
|
|||
|
|
} else {
|
|||
|
|
// 随机抽取
|
|||
|
|
selectedMeals = randomDraw(count);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新抽取次数
|
|||
|
|
selectedMeals.forEach(meal => {
|
|||
|
|
meal.count++;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 显示结果
|
|||
|
|
showResult(selectedMeals);
|
|||
|
|
|
|||
|
|
// 保存历史记录
|
|||
|
|
saveDrawHistory(selectedMeals);
|
|||
|
|
|
|||
|
|
// 保存到本地存储
|
|||
|
|
saveToLocalStorage();
|
|||
|
|
}, 3000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 随机抽取
|
|||
|
|
function randomDraw(count) {
|
|||
|
|
const result = [];
|
|||
|
|
const availableMeals = [...mealData];
|
|||
|
|
|
|||
|
|
for (let i = 0; i < count; i++) {
|
|||
|
|
if (availableMeals.length === 0) break;
|
|||
|
|
|
|||
|
|
const randomIndex = Math.floor(Math.random() * availableMeals.length);
|
|||
|
|
result.push(availableMeals[randomIndex]);
|
|||
|
|
availableMeals.splice(randomIndex, 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 智能推荐
|
|||
|
|
function smartDraw(count) {
|
|||
|
|
// 按抽取次数排序
|
|||
|
|
const sortedMeals = [...mealData].sort((a, b) => a.count - b.count);
|
|||
|
|
|
|||
|
|
// 选择抽取次数最少的菜品
|
|||
|
|
return sortedMeals.slice(0, count);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示转盘动画
|
|||
|
|
function showWheelAnimation() {
|
|||
|
|
// 如果没有菜品,显示占位图
|
|||
|
|
if (mealData.length === 0) {
|
|||
|
|
wheelPlaceholder.classList.remove('hidden');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 隐藏占位图
|
|||
|
|
wheelPlaceholder.classList.add('hidden');
|
|||
|
|
|
|||
|
|
// 创建转盘分区
|
|||
|
|
createWheelSegments();
|
|||
|
|
|
|||
|
|
// 添加动画类
|
|||
|
|
wheel.classList.add('spin-wheel-animation');
|
|||
|
|
|
|||
|
|
// 动画结束后移除类
|
|||
|
|
setTimeout(() => {
|
|||
|
|
wheel.classList.remove('spin-wheel-animation');
|
|||
|
|
}, 3000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建转盘分区
|
|||
|
|
function createWheelSegments() {
|
|||
|
|
// 清空转盘
|
|||
|
|
wheel.innerHTML = '';
|
|||
|
|
|
|||
|
|
// 如果没有菜品,返回
|
|||
|
|
if (mealData.length === 0) return;
|
|||
|
|
|
|||
|
|
// 计算每个分区的角度
|
|||
|
|
const angle = 360 / mealData.length;
|
|||
|
|
|
|||
|
|
// 创建分区
|
|||
|
|
mealData.forEach((meal, index) => {
|
|||
|
|
const segment = document.createElement('div');
|
|||
|
|
segment.className = 'absolute';
|
|||
|
|
segment.style.width = '50%';
|
|||
|
|
segment.style.height = '50%';
|
|||
|
|
segment.style.transformOrigin = '100% 100%';
|
|||
|
|
segment.style.transform = `rotate(${index * angle}deg)`;
|
|||
|
|
segment.style.backgroundColor = getSegmentColor(index);
|
|||
|
|
segment.style.display = 'flex';
|
|||
|
|
segment.style.alignItems = 'center';
|
|||
|
|
segment.style.justifyContent = 'center';
|
|||
|
|
segment.style.padding = '10px';
|
|||
|
|
segment.style.boxSizing = 'border-box';
|
|||
|
|
|
|||
|
|
// 创建菜品名称
|
|||
|
|
const name = document.createElement('div');
|
|||
|
|
name.className = 'text-white font-bold text-center';
|
|||
|
|
name.style.transform = 'rotate(45deg)';
|
|||
|
|
name.style.width = '100px';
|
|||
|
|
name.style.overflow = 'hidden';
|
|||
|
|
name.style.textOverflow = 'ellipsis';
|
|||
|
|
name.style.whiteSpace = 'nowrap';
|
|||
|
|
name.textContent = meal.name;
|
|||
|
|
|
|||
|
|
segment.appendChild(name);
|
|||
|
|
wheel.appendChild(segment);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取分区颜色
|
|||
|
|
function getSegmentColor(index) {
|
|||
|
|
const colors = [
|
|||
|
|
'#FF6B6B', '#4ECDC4', '#FFE66D', '#1A535C', '#FF9F1C',
|
|||
|
|
'#7B287D', '#00B4D8', '#0077B6', '#2EC4B6', '#E76F51'
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return colors[index % colors.length];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示结果
|
|||
|
|
function showResult(selectedMeals) {
|
|||
|
|
// 隐藏无结果提示
|
|||
|
|
noResultMsg.classList.add('hidden');
|
|||
|
|
|
|||
|
|
// 显示结果列表
|
|||
|
|
resultList.classList.remove('hidden');
|
|||
|
|
|
|||
|
|
// 清空结果列表
|
|||
|
|
resultList.innerHTML = '';
|
|||
|
|
|
|||
|
|
// 添加结果卡片
|
|||
|
|
selectedMeals.forEach((meal, index) => {
|
|||
|
|
const card = document.createElement('div');
|
|||
|
|
card.className = 'bg-white rounded-lg shadow-sm p-4 mb-3 border-l-4 border-primary';
|
|||
|
|
|
|||
|
|
const categoryColor = getCategoryColor(meal.category);
|
|||
|
|
|
|||
|
|
card.innerHTML = `
|
|||
|
|
<div class="flex justify-between items-start">
|
|||
|
|
<div>
|
|||
|
|
<div class="flex items-center">
|
|||
|
|
<span class="text-lg font-bold mr-2">${index + 1}. ${meal.name}</span>
|
|||
|
|
<span class="badge ${categoryColor}">${meal.category}</span>
|
|||
|
|
</div>
|
|||
|
|
${meal.note ? `<p class="text-gray-600 text-sm mt-1">${meal.note}</p>` : ''}
|
|||
|
|
</div>
|
|||
|
|
<span class="text-gray-500 text-sm">已抽取 ${meal.count} 次</span>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
resultList.appendChild(card);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 显示保存和分享按钮
|
|||
|
|
saveResultBtn.classList.remove('hidden');
|
|||
|
|
shareResultBtn.classList.remove('hidden');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存结果
|
|||
|
|
function saveResult() {
|
|||
|
|
// 创建日期字符串
|
|||
|
|
const date = new Date();
|
|||
|
|
const dateStr = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
|
|||
|
|
|
|||
|
|
// 获取当前结果
|
|||
|
|
const resultItems = resultList.querySelectorAll('.bg-white');
|
|||
|
|
let resultText = '菜品名称,分类,备注\n';
|
|||
|
|
|
|||
|
|
resultItems.forEach(item => {
|
|||
|
|
const name = item.querySelector('.font-bold').textContent.split('. ')[1];
|
|||
|
|
const category = item.querySelector('.badge').textContent;
|
|||
|
|
const note = item.querySelector('.text-gray-600')?.textContent || '';
|
|||
|
|
|
|||
|
|
// 转义CSV特殊字符
|
|||
|
|
const escapeCSV = (str) => `"${str.replace(/"/g, '""')}"`;
|
|||
|
|
resultText += `${escapeCSV(name)},${escapeCSV(category)},${escapeCSV(note)}\n`;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 创建Blob对象
|
|||
|
|
const blob = new Blob([resultText], { type: 'text/csv;charset=utf-8;' });
|
|||
|
|
|
|||
|
|
// 创建下载链接
|
|||
|
|
const link = document.createElement('a');
|
|||
|
|
const url = URL.createObjectURL(blob);
|
|||
|
|
|
|||
|
|
link.setAttribute('href', url);
|
|||
|
|
link.setAttribute('download', `抽取结果_${dateStr}.csv`);
|
|||
|
|
link.style.visibility = 'hidden';
|
|||
|
|
|
|||
|
|
document.body.appendChild(link);
|
|||
|
|
link.click();
|
|||
|
|
document.body.removeChild(link);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示分享模态框
|
|||
|
|
function showShareModal() {
|
|||
|
|
// 获取当前结果
|
|||
|
|
const resultItems = resultList.querySelectorAll('.bg-white');
|
|||
|
|
let shareContent = '今天吃什么 - 抽取结果\n\n';
|
|||
|
|
|
|||
|
|
resultItems.forEach((item, index) => {
|
|||
|
|
const name = item.querySelector('.font-bold').textContent;
|
|||
|
|
const category = item.querySelector('.badge').textContent;
|
|||
|
|
shareContent += `${name} [${category}]\n`;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加日期
|
|||
|
|
const date = new Date();
|
|||
|
|
shareContent += `\n抽取时间: ${date.toLocaleString('zh-CN')}`;
|
|||
|
|
|
|||
|
|
// 设置分享文本
|
|||
|
|
shareText.value = shareContent;
|
|||
|
|
|
|||
|
|
// 显示模态框
|
|||
|
|
shareModal.classList.remove('hidden');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 隐藏分享模态框
|
|||
|
|
function hideShareModal() {
|
|||
|
|
shareModal.classList.add('hidden');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 复制分享文本
|
|||
|
|
function copyShareText() {
|
|||
|
|
shareText.select();
|
|||
|
|
document.execCommand('copy');
|
|||
|
|
|
|||
|
|
// 显示提示
|
|||
|
|
alert('文本已复制到剪贴板');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 分享到微信
|
|||
|
|
function shareToWechat() {
|
|||
|
|
alert('请手动复制文本,然后粘贴到微信中分享');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存抽取历史
|
|||
|
|
function saveDrawHistory(selectedMeals) {
|
|||
|
|
// 创建历史记录
|
|||
|
|
const history = {
|
|||
|
|
date: new Date(),
|
|||
|
|
meals: selectedMeals.map(meal => ({
|
|||
|
|
name: meal.name,
|
|||
|
|
category: meal.category
|
|||
|
|
}))
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 添加到历史记录
|
|||
|
|
drawHistory.unshift(history);
|
|||
|
|
|
|||
|
|
// 限制历史记录数量
|
|||
|
|
if (drawHistory.length > 10) {
|
|||
|
|
drawHistory.pop();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新历史记录列表
|
|||
|
|
updateHistoryList();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新历史记录列表
|
|||
|
|
function updateHistoryList() {
|
|||
|
|
// 如果没有历史记录,显示提示
|
|||
|
|
if (drawHistory.length === 0) {
|
|||
|
|
historyList.innerHTML = `
|
|||
|
|
<div class="text-center py-3 text-gray-500 text-sm">
|
|||
|
|
暂无历史记录
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清空历史记录列表
|
|||
|
|
historyList.innerHTML = '';
|
|||
|
|
|
|||
|
|
// 添加历史记录
|
|||
|
|
drawHistory.forEach((history, index) => {
|
|||
|
|
const item = document.createElement('div');
|
|||
|
|
item.className = 'bg-white rounded-lg shadow-sm p-3 mb-2';
|
|||
|
|
|
|||
|
|
// 格式化日期
|
|||
|
|
const dateStr = history.date.toLocaleString('zh-CN');
|
|||
|
|
|
|||
|
|
// 创建菜品列表
|
|||
|
|
const mealList = history.meals.map(meal => meal.name).join('、');
|
|||
|
|
|
|||
|
|
item.innerHTML = `
|
|||
|
|
<div class="flex justify-between items-start">
|
|||
|
|
<div>
|
|||
|
|
<p class="font-medium">${mealList}</p>
|
|||
|
|
<p class="text-xs text-gray-500">${dateStr}</p>
|
|||
|
|
</div>
|
|||
|
|
<button class="text-primary hover:text-primary-dark" onclick="loadHistory(${index})">
|
|||
|
|
<i class="fa fa-refresh"></i>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
historyList.appendChild(item);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加载历史记录
|
|||
|
|
function loadHistory(index) {
|
|||
|
|
const history = drawHistory[index];
|
|||
|
|
|
|||
|
|
// 查找菜品
|
|||
|
|
const selectedMeals = history.meals.map(meal => {
|
|||
|
|
return mealData.find(m => m.name === meal.name) || meal;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 显示结果
|
|||
|
|
showResult(selectedMeals);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示帮助模态框
|
|||
|
|
function showHelpModal() {
|
|||
|
|
helpModal.classList.remove('hidden');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 隐藏帮助模态框
|
|||
|
|
function hideHelpModal() {
|
|||
|
|
helpModal.classList.add('hidden');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存到本地存储
|
|||
|
|
function saveToLocalStorage() {
|
|||
|
|
localStorage.setItem('mealData', JSON.stringify(mealData));
|
|||
|
|
localStorage.setItem('categories', JSON.stringify(categories));
|
|||
|
|
localStorage.setItem('drawHistory', JSON.stringify(drawHistory));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从本地存储加载
|
|||
|
|
function loadFromLocalStorage() {
|
|||
|
|
const savedMealData = localStorage.getItem('mealData');
|
|||
|
|
const savedCategories = localStorage.getItem('categories');
|
|||
|
|
const savedDrawHistory = localStorage.getItem('drawHistory');
|
|||
|
|
|
|||
|
|
if (savedMealData) {
|
|||
|
|
mealData = JSON.parse(savedMealData);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (savedCategories) {
|
|||
|
|
categories = JSON.parse(savedCategories);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (savedDrawHistory) {
|
|||
|
|
drawHistory = JSON.parse(savedDrawHistory);
|
|||
|
|
|
|||
|
|
// 转换日期字符串为 Date 对象
|
|||
|
|
drawHistory.forEach(history => {
|
|||
|
|
history.date = new Date(history.date);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从本地JSON文件加载菜品数据(返回Promise)
|
|||
|
|
async function loadMealsFromJsonFile() {
|
|||
|
|
console.log('尝试从本地JSON文件加载菜品数据');
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 首先尝试加载menu.json
|
|||
|
|
const response = await fetch('menu.json', {
|
|||
|
|
method: 'GET',
|
|||
|
|
cache: 'no-cache'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!response.ok) {
|
|||
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const jsonData = await response.json();
|
|||
|
|
console.log('成功读取menu.json文件');
|
|||
|
|
|
|||
|
|
// 处理JSON数据
|
|||
|
|
const addedCount = processJsonData(jsonData);
|
|||
|
|
|
|||
|
|
// 显示成功提示
|
|||
|
|
if (addedCount > 0) {
|
|||
|
|
jsonLoadMessage.textContent = `已从menu.json加载 ${addedCount} 个菜品`;
|
|||
|
|
jsonLoadStatus.classList.remove('hidden');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.log('无法加载menu.json文件:', error.message);
|
|||
|
|
|
|||
|
|
// 尝试加载其他可能的JSON文件名
|
|||
|
|
const alternativeFiles = ['meals.json', 'dishes.json', 'food.json'];
|
|||
|
|
|
|||
|
|
for (const filename of alternativeFiles) {
|
|||
|
|
try {
|
|||
|
|
console.log(`尝试加载${filename}`);
|
|||
|
|
const response = await fetch(filename, {
|
|||
|
|
method: 'GET',
|
|||
|
|
cache: 'no-cache'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!response.ok) {
|
|||
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const jsonData = await response.json();
|
|||
|
|
console.log(`成功读取${filename}文件`);
|
|||
|
|
|
|||
|
|
// 处理JSON数据
|
|||
|
|
const addedCount = processJsonData(jsonData);
|
|||
|
|
|
|||
|
|
// 显示成功提示
|
|||
|
|
if (addedCount > 0) {
|
|||
|
|
jsonLoadMessage.textContent = `已从${filename}加载 ${addedCount} 个菜品`;
|
|||
|
|
jsonLoadStatus.classList.remove('hidden');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
} catch (altError) {
|
|||
|
|
console.log(`加载${filename}失败:`, altError.message);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果所有文件都加载失败,抛出错误
|
|||
|
|
throw new Error('未找到可用的JSON菜单文件');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理JSON数据(返回添加的菜品数量)
|
|||
|
|
function processJsonData(jsonData) {
|
|||
|
|
console.log('处理JSON数据');
|
|||
|
|
|
|||
|
|
// 验证JSON格式
|
|||
|
|
if (!Array.isArray(jsonData)) {
|
|||
|
|
console.log('错误: JSON格式不正确,应为数组');
|
|||
|
|
alert('JSON文件格式错误,应为数组格式');
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理导入的菜品数据
|
|||
|
|
let addedCount = 0;
|
|||
|
|
let duplicateCount = 0;
|
|||
|
|
|
|||
|
|
jsonData.forEach(item => {
|
|||
|
|
// 支持不同的JSON格式
|
|||
|
|
let name, category, note;
|
|||
|
|
|
|||
|
|
if (typeof item === 'string') {
|
|||
|
|
// 简单格式: ["菜品1", "菜品2", ...]
|
|||
|
|
name = item.trim();
|
|||
|
|
category = '其他'; // 默认分类
|
|||
|
|
note = '';
|
|||
|
|
} else if (typeof item === 'object' && item !== null) {
|
|||
|
|
// 对象格式: [{name: "菜品1", category: "分类", note: "备注"}, ...]
|
|||
|
|
name = item.name || item.title || item.dish || item.food || '';
|
|||
|
|
name = name ? name.trim() : '';
|
|||
|
|
category = item.category || item.type || item.kind || '其他';
|
|||
|
|
note = item.note || item.remark || item.description || '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证菜品名称
|
|||
|
|
if (!name) {
|
|||
|
|
console.log('跳过无效菜品:', item);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查重复
|
|||
|
|
if (mealData.some(meal => meal.name === name)) {
|
|||
|
|
console.log('跳过重复菜品:', name);
|
|||
|
|
duplicateCount++;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加菜品
|
|||
|
|
mealData.push({
|
|||
|
|
name,
|
|||
|
|
category,
|
|||
|
|
note,
|
|||
|
|
count: 0
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加新分类
|
|||
|
|
if (!categories.includes(category)) {
|
|||
|
|
categories.push(category);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
addedCount++;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log(`处理完成: 添加${addedCount}个菜品, 跳过${duplicateCount}个重复菜品`);
|
|||
|
|
|
|||
|
|
// 如果有新菜品添加,保存到本地存储
|
|||
|
|
if (addedCount > 0) {
|
|||
|
|
saveToLocalStorage();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return addedCount;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理JSON文件导入
|
|||
|
|
function handleJsonFileImport(event) {
|
|||
|
|
console.log('处理JSON文件导入');
|
|||
|
|
const file = event.target.files[0];
|
|||
|
|
|
|||
|
|
if (!file) {
|
|||
|
|
console.log('未选择文件');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (file.type !== 'application/json' && !file.name.endsWith('.json')) {
|
|||
|
|
console.log('错误: 不是JSON文件');
|
|||
|
|
alert('请选择JSON格式的文件');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
|
|||
|
|
reader.onload = function(e) {
|
|||
|
|
try {
|
|||
|
|
console.log('读取文件完成');
|
|||
|
|
const jsonData = JSON.parse(e.target.result);
|
|||
|
|
const addedCount = processJsonData(jsonData);
|
|||
|
|
|
|||
|
|
// 更新界面
|
|||
|
|
updateCategoryFilter();
|
|||
|
|
updateMenuList();
|
|||
|
|
updateWheel();
|
|||
|
|
|
|||
|
|
// 显示成功消息
|
|||
|
|
alert(`JSON文件导入成功!共添加 ${addedCount} 个新菜品`);
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('解析JSON文件出错:', error);
|
|||
|
|
alert('解析JSON文件出错,请检查文件格式');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
reader.onerror = function() {
|
|||
|
|
console.error('读取文件出错');
|
|||
|
|
alert('读取文件出错,请重试');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
reader.readAsText(file, 'UTF-8');
|
|||
|
|
|
|||
|
|
// 重置文件输入,允许重新选择相同文件
|
|||
|
|
event.target.value = '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新转盘(空实现,确保函数存在)
|
|||
|
|
function updateWheel() {
|
|||
|
|
// 如果有菜品,创建转盘分区
|
|||
|
|
if (mealData.length > 0) {
|
|||
|
|
wheelPlaceholder.classList.add('hidden');
|
|||
|
|
createWheelSegments();
|
|||
|
|
} else {
|
|||
|
|
wheelPlaceholder.classList.remove('hidden');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 页面加载完成后初始化
|
|||
|
|
document.addEventListener('DOMContentLoaded', init);
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
</body>
|
|||
|
|
</html>
|