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> |