【HTML】美食大转盘 - 点餐助手
增加导入txt文本功能,增加一键清空功能,将所有代码进行注释,方便大家修改,瞎弄的,博大家一笑!!!
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>美食大转盘 - 点餐助手</title>
<style>
/*
============================================
样式表说明
============================================
给维护者:
- 使用CSS变量确保颜色一致性
- 采用响应式设计,移动端优先
- 使用backdrop-filter实现毛玻璃效果
给学习者:
- 使用clamp()实现响应式字体
- aspect-ratio保持元素比例
- CSS Grid创建两栏布局
============================================
*/
:root {
--primary-color: #f97316;
--secondary-color: #db2777;
--accent-color: #7c3aed;
--success-color: #10b981;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--text-light: rgba(255, 255, 255, 0.9);
--text-lighter: rgba(255, 255, 255, 0.7);
--bg-overlay: rgba(255, 255, 255, 0.1);
--bg-overlay-dark: rgba(0, 0, 0, 0.2);
--border-light: rgba(255, 255, 255, 0.2);
--shadow-color: rgba(0, 0, 0, 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
min-height: 100vh;
/* NOTE: 美食主题渐变背景色 */
background: linear-gradient(135deg,
#ff6b6b 0%,
#ff9a3c 25%,
#f6d365 50%,
#84fab0 75%,
#8fd3f4 100%);
background-attachment: fixed;
padding: 1rem;
color: white;
position: relative;
overflow-x: hidden;
}
/* 背景装饰元素 */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(255, 107, 107, 0.15) 0%, transparent 50%),
radial-gradient(circle at 60% 60%, rgba(255, 154, 60, 0.15) 0%, transparent 50%);
z-index: -1;
}
.container {
max-width: 1200px;
margin: 0 auto;
position: relative;
}
.header {
text-align: center;
margin-bottom: 2rem;
position: relative;
}
.header::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 150px;
height: 4px;
background: linear-gradient(90deg, transparent, var(--secondary-color), transparent);
border-radius: 2px;
}
.header h1 {
/* OPTIMIZE: clamp()实现平滑的字体响应式变化 */
font-size: clamp(1.8rem, 6vw, 3rem);
font-weight: 900;
color: #fff;
margin: 0.5rem 0;
text-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
letter-spacing: 1px;
background: linear-gradient(135deg, #fff 0%, #fcd34d 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header p {
color: var(--text-light);
font-size: 1.2rem;
font-weight: 500;
margin-top: 0.5rem;
max-width: 600px;
margin-left: auto;
margin-right: auto;
line-height: 1.6;
}
.header .emoji-bounce {
display: inline-block;
animation: bounce 2s infinite;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
}
.subtitle {
font-size: 1rem;
color: var(--text-lighter);
margin-top: 0.5rem;
font-style: italic;
}
.main-grid {
display: grid;
/* NOTE: 两栏布局,左侧固定宽度,右侧自适应 */
grid-template-columns: 350px 1fr;
gap: 2rem;
align-items: start;
}
/* WARN: 移动端适配 - 900px以下变为单栏布局 */
@media (max-width: 900px) {
.main-grid {
grid-template-columns: 1fr;
}
.list-panel { order: 2; }
.wheel-panel { order: 1; }
}
.panel {
/* NOTE: 毛玻璃效果面板 */
background: var(--bg-overlay);
backdrop-filter: blur(20px) saturate(180%);
border-radius: 1.5rem;
padding: 1.8rem;
border: 1px solid var(--border-light);
box-shadow:
0 8px 32px var(--shadow-color),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
position: relative;
overflow: hidden;
}
.panel::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color), var(--accent-color));
border-radius: 1.5rem 1.5rem 0 0;
}
.panel:hover {
transform: translateY(-5px);
box-shadow:
0 12px 40px rgba(0, 0, 0, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
margin-bottom: 1.8rem;
padding-bottom: 1.2rem;
border-bottom: 1px solid var(--border-light);
}
.panel-header h2 {
font-size: 1.6rem;
font-weight: 800;
color: #fff;
display: flex;
align-items: center;
gap: 0.75rem;
}
.icon {
color: #fcd34d;
font-size: 2rem;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
}
.count-badge {
font-size: 0.9rem;
font-weight: 700;
color: #fff;
background: linear-gradient(135deg, var(--success-color), #0d9668);
padding: 0.4rem 0.8rem;
border-radius: 2rem;
box-shadow: 0 4px 8px rgba(16, 185, 129, 0.3);
}
.input-group {
margin-bottom: 1.5rem;
}
.input-label {
color: var(--text-light);
margin-bottom: 0.75rem;
display: block;
font-weight: 600;
font-size: 1.1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.input-label .label-icon {
font-size: 1.2rem;
}
.input-row {
display: flex;
gap: 0.75rem;
}
input, textarea {
background: rgba(255, 255, 255, 0.15);
border: 2px solid var(--border-light);
border-radius: 0.75rem;
padding: 1rem 1.25rem;
color: white;
outline: none;
transition: all 0.3s;
width: 100%;
font-size: 1rem;
font-weight: 500;
}
input::placeholder, textarea::placeholder {
color: rgba(255, 255, 255, 0.5);
font-weight: 400;
}
input:focus, textarea:focus {
border-color: #fcd34d;
background: rgba(255, 255, 255, 0.25);
box-shadow: 0 0 0 4px rgba(252, 211, 77, 0.2);
}
.input-row input {
flex: 1;
}
.btn {
padding: 1rem 1.5rem;
border: none;
border-radius: 0.75rem;
font-weight: 700;
cursor: pointer;
transition: all 0.3s;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
text-decoration: none;
user-select: none;
white-space: nowrap;
font-size: 1rem;
letter-spacing: 0.5px;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.btn:hover::before {
left: 100%;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none !important;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), #ea580c);
color: white;
box-shadow: 0 6px 16px rgba(249, 115, 22, 0.4);
}
.btn-primary:hover:not(:disabled) {
background: linear-gradient(135deg, #ea580c, #c2410c);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(249, 115, 22, 0.5);
}
.btn-success {
background: linear-gradient(135deg, var(--success-color), #0d9668);
color: white;
box-shadow: 0 6px 16px rgba(16, 185, 129, 0.4);
}
.btn-success:hover:not(:disabled) {
background: linear-gradient(135deg, #0d9668, #047857);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.5);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 2px solid var(--border-light);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.btn-secondary:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
}
.btn-danger {
background: linear-gradient(135deg, var(--danger-color), #b91c1c);
color: white;
box-shadow: 0 6px 16px rgba(239, 68, 68, 0.4);
}
.btn-danger:hover:not(:disabled) {
background: linear-gradient(135deg, #b91c1c, #991b1b);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(239, 68, 68, 0.5);
}
.btn-warning {
background: linear-gradient(135deg, var(--warning-color), #d97706);
color: white;
box-shadow: 0 6px 16px rgba(245, 158, 11, 0.4);
}
.btn-warning:hover:not(:disabled) {
background: linear-gradient(135deg, #d97706, #b45309);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(245, 158, 11, 0.5);
}
.punishment-list {
max-height: 400px;
overflow-y: auto;
padding-right: 5px;
margin-bottom: 1.5rem;
}
.punishment-list::-webkit-scrollbar {
width: 8px;
}
.punishment-list::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
}
.punishment-list::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border-radius: 4px;
}
.punishment-item {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
padding: 1.25rem;
border-radius: 1rem;
margin-bottom: 0.75rem;
display: flex;
justify-content: space-between;
align-items: center;
transition: all 0.3s;
color: white;
border-left: 4px solid transparent;
border: 1px solid var(--border-light);
position: relative;
overflow: hidden;
}
.punishment-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: linear-gradient(180deg, var(--primary-color), var(--secondary-color));
opacity: 0;
transition: opacity 0.3s;
}
.punishment-item:hover {
background: linear-gradient(135deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.1));
transform: translateX(8px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
}
.punishment-item:hover::before {
opacity: 1;
}
.punishment-item.active {
background: linear-gradient(135deg, rgba(252, 211, 77, 0.25), rgba(245, 158, 11, 0.15));
border-left-color: #fcd34d;
font-weight: bold;
box-shadow: 0 4px 12px rgba(252, 211, 77, 0.2);
}
.punishment-item .food-icon {
font-size: 1.5rem;
margin-right: 0.75rem;
}
.punishment-item .remove-btn {
background: rgba(0, 0, 0, 0.2);
border: none;
color: #fca5a5;
cursor: pointer;
font-size: 1.3rem;
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
flex-shrink: 0;
}
.punishment-item .remove-btn:hover {
background: #ef4444;
color: white;
transform: rotate(90deg);
}
.wheel-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
}
.wheel-wrapper {
position: relative;
margin-bottom: 2.5rem;
filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.3));
width: 100%;
/* OPTIMIZE: 使用aspect-ratio保持正方形 */
max-width: 500px;
aspect-ratio: 1/1;
}
.wheel-pointer {
position: absolute;
top: -15px;
left: 50%;
transform: translateX(-50%);
z-index: 20;
width: 50px;
height: 50px;
background: linear-gradient(135deg, var(--danger-color), #b91c1c);
border: 5px solid #fff;
border-radius: 50%;
box-shadow: 0 8px 20px rgba(0,0,0,0.4);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
animation: pulsePointer 2s infinite;
}
.wheel-pointer::after {
content: '👇';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
font-size: 1.8rem;
filter: drop-shadow(0 4px 6px rgba(0,0,0,0.3));
}
.wheel-svg {
width: 100%;
height: 100%;
transition-property: transform;
/* NOTE: 自定义贝塞尔曲线实现"缓慢停止"效果 */
transition-timing-function: cubic-bezier(0.15, 0, 0.20, 1);
}
.control-buttons {
display: flex;
gap: 1.5rem;
margin-bottom: 2.5rem;
flex-wrap: wrap;
justify-content: center;
}
.result-display {
background: linear-gradient(135deg, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.5));
backdrop-filter: blur(20px);
border: 3px solid #fcd34d;
color: #fcd34d;
padding: 2rem 3rem;
border-radius: 1.5rem;
text-align: center;
font-weight: bold;
box-shadow:
0 15px 35px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
min-width: 320px;
transform: scale(0.9);
opacity: 0;
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
}
.result-display::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--primary-color), #fcd34d, var(--secondary-color));
}
.result-display.show {
transform: scale(1);
opacity: 1;
}
.result-display h3 {
font-size: 1.6rem;
margin-bottom: 1rem;
color: white;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.result-display .punishment-text {
font-size: 2rem;
font-weight: 900;
word-break: break-word;
line-height: 1.4;
background: linear-gradient(135deg, #fcd34d, #f59e0b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.result-emoji {
font-size: 3rem;
margin-bottom: 1rem;
display: block;
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
}
.celebration-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(10px);
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.celebration-content {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border-radius: 2rem;
padding: 3rem;
max-width: 36rem;
width: 100%;
text-align: center;
position: relative;
overflow: hidden;
box-shadow:
0 30px 60px rgba(0, 0, 0, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
border: 4px solid rgba(255,255,255,0.2);
}
.celebration-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.2) 0%, transparent 50%),
radial-gradient(circle at 70% 70%, rgba(255,255,255,0.15) 0%, transparent 50%);
animation: pulseBg 3s infinite;
}
.celebration-close {
position: absolute;
top: 1.5rem;
right: 1.5rem;
width: 3rem;
height: 3rem;
background: rgba(0, 0, 0, 0.3);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.8rem;
transition: all 0.3s;
font-weight: bold;
}
.celebration-close:hover {
background: rgba(255, 255, 255, 0.2);
transform: rotate(90deg);
}
.celebration-inner {
position: relative;
z-index: 10;
}
.celebration-emoji {
font-size: 6rem;
margin-bottom: 1rem;
display: block;
filter: drop-shadow(0 6px 12px rgba(0,0,0,0.4));
animation: bounce 2s infinite;
}
.celebration-title {
font-size: 2.8rem;
font-weight: 900;
color: #fff;
margin-bottom: 1.5rem;
text-transform: uppercase;
letter-spacing: 2px;
text-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
.celebration-result-box {
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
border-radius: 1.5rem;
padding: 2.5rem;
margin-bottom: 2.5rem;
border: 2px dashed rgba(255,255,255,0.3);
}
.celebration-result-label {
font-size: 1.4rem;
color: rgba(255,255,255,0.9);
margin-bottom: 1rem;
font-weight: 600;
}
.celebration-result-text {
font-size: 2.4rem;
font-weight: 900;
color: #fcd34d;
line-height: 1.3;
text-shadow: 0 3px 6px rgba(0,0,0,0.3);
}
.celebration-btn {
background: white;
color: var(--secondary-color);
border: none;
padding: 1.25rem 4rem;
border-radius: 3rem;
font-weight: 900;
font-size: 1.4rem;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
position: relative;
overflow: hidden;
}
.celebration-btn:hover {
background: #f3f4f6;
transform: translateY(-3px) scale(1.05);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
}
.empty-state {
padding: 2.5rem;
border-radius: 1.5rem;
background: rgba(255, 255, 255, 0.05);
color: rgba(255, 255, 255, 0.6);
text-align: center;
font-style: italic;
border: 2px dashed rgba(255, 255, 255, 0.2);
font-size: 1.1rem;
}
.file-import-note {
color: var(--text-lighter);
font-size: 0.9rem;
margin-top: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.clear-all-section {
display: flex;
justify-content: flex-end;
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border-light);
}
/* NOTE: 一键清空确认弹窗样式 */
.confirm-clear-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(10px);
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.confirm-clear-content {
background: linear-gradient(135deg, var(--danger-color), #b91c1c);
border-radius: 2rem;
padding: 3rem;
max-width: 32rem;
width: 100%;
text-align: center;
position: relative;
overflow: hidden;
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5);
border: 4px solid rgba(255,255,255,0.2);
}
.confirm-clear-icon {
font-size: 4.5rem;
margin-bottom: 1.5rem;
display: block;
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
animation: shake 0.5s ease-in-out;
}
.confirm-clear-title {
font-size: 2rem;
font-weight: 900;
color: #fff;
margin-bottom: 1.5rem;
text-shadow: 0 3px 6px rgba(0,0,0,0.3);
}
.confirm-clear-message {
font-size: 1.2rem;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 2.5rem;
line-height: 1.6;
}
.confirm-clear-count {
font-weight: 900;
color: #fcd34d;
font-size: 1.5rem;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
.confirm-clear-buttons {
display: flex;
gap: 1.5rem;
justify-content: center;
}
.confirm-clear-btn {
padding: 1.2rem 2.5rem;
border: none;
border-radius: 1rem;
font-weight: 800;
font-size: 1.2rem;
cursor: pointer;
transition: all 0.3s;
min-width: 140px;
position: relative;
overflow: hidden;
}
.confirm-clear-btn.confirm {
background: #10b981;
color: white;
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4);
}
.confirm-clear-btn.confirm:hover {
background: #0d9668;
transform: translateY(-3px);
box-shadow: 0 12px 25px rgba(16, 185, 129, 0.5);
}
.confirm-clear-btn.cancel {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 2px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
}
.confirm-clear-btn.cancel:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-3px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@keyframes pulsePointer {
0%, 100% { transform: translateX(-50%) scale(1); }
50% { transform: translateX(-50%) scale(1.1); }
}
@keyframes pulseBg {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
@keyframes bounce {
0%, 20%, 53%, 80%, 100% { transform: translateY(0); }
40%, 43% { transform: translateY(-20px); }
70% { transform: translateY(-10px); }
90% { transform: translateY(-5px); }
}
@keyframes modalPop {
0% { transform: scale(0.8) translateY(50px); opacity: 0; }
70% { transform: scale(1.05); }
100% { transform: scale(1) translateY(0); opacity: 1; }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-8px); }
20%, 40%, 60%, 80% { transform: translateX(8px); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
.hidden {
display: none;
}
.modal-enter {
animation: modalPop 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}
.shake {
animation: shake 0.5s ease-in-out;
}
/* TODO: 添加暗色模式支持 */
/* OPTIMIZE: 将动画关键帧提取到单独部分以便维护 */
/* 美食图标样式 */
.food-emoji {
display: inline-block;
animation: float 3s ease-in-out infinite;
}
/* 响应式调整 */
@media (max-width: 768px) {
.panel {
padding: 1.5rem;
}
.header h1 {
font-size: clamp(1.5rem, 5vw, 2.5rem);
}
.header p {
font-size: 1rem;
}
.control-buttons {
flex-direction: column;
align-items: center;
}
.btn {
width: 100%;
max-width: 300px;
}
.celebration-content {
padding: 2rem;
}
.celebration-title {
font-size: 2rem;
}
.celebration-result-text {
font-size: 1.8rem;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 头部标题 -->
<div class="header">
<div style="display: flex; align-items: center; justify-content: center; gap: 1.5rem; margin-bottom: 1rem;">
<span class="emoji-bounce food-emoji">🍕</span>
<h1>美食大转盘 - 点餐助手</h1>
<span class="emoji-bounce food-emoji">🍔</span>
</div>
<p>还在为"吃什么"发愁吗?让转盘帮你决定今天的美食!</p>
<div class="subtitle">添加你喜欢的菜品,让选择变得简单有趣</div>
</div>
<div class="main-grid">
<!--
============================================
菜品项目库面板
给使用者:
- 在此添加/删除菜品项目
- 支持导入txt文件批量添加
- 项目列表会保存在浏览器本地
============================================
-->
<div class="panel list-panel">
<div class="panel-header">
<div style="display: flex; align-items: center; gap: 0.75rem;">
<span class="icon">🍽️</span>
<h2>我的美食菜单</h2>
<div class="count-badge" id="punishmentCount">0项</div>
</div>
<button class="btn btn-danger" id="clearAllPunishmentsBtn" style="padding: 0.8rem 1.2rem;">
<span>🗑️</span>
一键清空
</button>
</div>
<div class="input-group">
<label class="input-label">
<span class="label-icon">➕</span>
添加新菜品:
</label>
<div class="input-row">
<input type="text" id="punishmentInput" placeholder="例如:麻辣香锅、披萨、寿司..." autocomplete="off">
<button class="btn btn-success" id="addPunishmentBtn">
<span>🍴</span>
添加
</button>
</div>
</div>
<!-- NOTE: 文件导入功能 -->
<div class="input-group">
<label class="input-label">
<span class="label-icon">📁</span>
导入菜品列表(TXT文件):
</label>
<div class="input-row">
<input type="file" id="importFile" accept=".txt" style="flex: 1; padding: 0.75rem;">
<button class="btn btn-secondary" id="importBtn">
<span>📥</span>
导入
</button>
</div>
<div class="file-import-note">
<span>📝</span>
每行一个菜品,支持批量导入
</div>
</div>
<div class="punishment-list" id="punishmentList">
<!-- 菜品项目动态加载 -->
</div>
<div class="clear-all-section">
<button class="btn btn-warning" id="resetToDefaultBtn" style="padding: 0.8rem 1.5rem;">
<span>🔄</span>
恢复默认菜单
</button>
</div>
</div>
<!--
============================================
转盘控制面板
给使用者:
- 点击"开始点餐"旋转转盘
- 点击"重置"将转盘归零
- 结果会在下方和弹窗显示
============================================
-->
<div class="panel wheel-panel" style="display: flex; flex-direction: column; align-items: center; justify-content: center;">
<div class="wheel-container">
<div class="wheel-wrapper">
<!-- 转盘指针 -->
<div class="wheel-pointer">👇</div>
<!-- SVG转盘 -->
<svg id="wheelSvg" class="wheel-svg" viewBox="-250 -250 500 500">
<defs>
<radialGradient id="centerGradient">
<stop offset="0%" stop-color="#fcd34d" />
<stop offset="100%" stop-color="#f59e0b" />
</radialGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="0" stdDeviation="8" flood-color="rgba(0,0,0,0.4)"/>
</filter>
<linearGradient id="wheelGradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#ff6b6b;stop-opacity:1" />
<stop offset="25%" style="stop-color:#ff9a3c;stop-opacity:1" />
<stop offset="50%" style="stop-color:#f6d365;stop-opacity:1" />
<stop offset="75%" style="stop-color:#84fab0;stop-opacity:1" />
<stop offset="100%" style="stop-color:#8fd3f4;stop-opacity:1" />
</linearGradient>
</defs>
<!-- 转盘扇形区域(动态生成) -->
<g id="wheelSections"></g>
<!-- 转盘中心装饰 -->
<circle cx="0" cy="0" r="50" fill="url(#centerGradient)" stroke="#fff" stroke-width="6" filter="url(#shadow)"/>
<circle cx="0" cy="0" r="35" fill="none" stroke="#fff" stroke-width="3" stroke-dasharray="4 4"/>
<text x="0" y="5" fill="#fff" font-size="18" font-weight="bold" text-anchor="middle" dominant-baseline="middle">点餐</text>
</svg>
</div>
<!-- 控制按钮区域 -->
<div class="control-buttons">
<button class="btn btn-primary" id="startBtn" style="min-width: 180px; font-size: 1.3rem; padding: 1.2rem 2rem;">
<span>🎯</span>
<span id="startBtnText">开始点餐</span>
</button>
<button class="btn btn-secondary" id="resetBtn" style="padding: 1.2rem 2rem;">
<span>🔄</span>
重置转盘
</button>
</div>
<!-- 结果展示区域 -->
<div id="resultDisplay" class="result-display">
<div class="result-emoji">🤔</div>
<h3>今日推荐</h3>
<div class="punishment-text" id="winnerPunishment">等待选择...</div>
</div>
</div>
</div>
</div>
</div>
<!--
============================================
庆祝弹窗
给使用者:
- 抽取结果后会显示此弹窗
- 点击"确定"或关闭按钮关闭弹窗
============================================
-->
<div id="celebrationModal" class="celebration-modal hidden">
<div class="celebration-content">
<div class="celebration-bg"></div>
<button class="celebration-close" id="closeCelebrationBtn">×</button>
<div class="celebration-inner">
<span class="celebration-emoji">🎉</span>
<h2 class="celebration-title">今日美食已选定!</h2>
<div class="celebration-result-box">
<div class="celebration-result-label">转盘推荐:</div>
<div class="celebration-result-text" id="celebrationPunishment"></div>
</div>
<button class="celebration-btn" id="confirmCelebrationBtn">
<span>😋</span>
太棒了,就吃这个!
</button>
</div>
</div>
</div>
<!--
============================================
一键清空确认弹窗
给使用者:
- 确认是否清空所有菜品项目
- 提供取消选项防止误操作
============================================
-->
<div id="confirmClearModal" class="confirm-clear-modal hidden">
<div class="confirm-clear-content modal-enter">
<span class="confirm-clear-icon">⚠️</span>
<h2 class="confirm-clear-title">确认清空菜单</h2>
<div class="confirm-clear-message">
您确定要清空所有 <span class="confirm-clear-count" id="clearCount">0</span> 个菜品吗?<br>
<strong>此操作无法撤销!</strong> 所有自定义和导入的菜品将被永久删除。
</div>
<div class="confirm-clear-buttons">
<button class="confirm-clear-btn cancel" id="cancelClearBtn">
<span>❌</span>
取消
</button>
<button class="confirm-clear-btn confirm" id="confirmClearBtn">
<span>✅</span>
确定清空
</button>
</div>
</div>
</div>
<script>
/*
============================================
美食大转盘 - JavaScript核心逻辑
给维护者:
- 使用LocalStorage持久化数据
- 转盘算法基于角度计算
- 事件委托处理动态列表
给学习者:
- 使用SVG动态生成转盘
- 三角函数计算扇形区域
- 贝塞尔曲线实现缓动效果
============================================
*/
// ========== 全局变量声明 ==========
// NOTE: 默认菜品列表 - 当本地存储为空时使用
const defaultPunishments = [
'🍕 披萨',
'🍔 汉堡',
'🍜 拉面',
'🍣 寿司',
'🥘 麻辣香锅',
'🍲 火锅',
'🥗 沙拉',
'🍛 咖喱饭',
'🍱 便当',
'🥪 三明治',
'🍝 意大利面',
'🌮 墨西哥卷饼',
'🥟 饺子',
'🍤 炸虾',
'🍦 冰淇淋'
];
// WARN: 从LocalStorage加载数据,失败时使用默认值
let punishments = JSON.parse(localStorage.getItem('wheelPunishments')) || [...defaultPunishments];
// 转盘状态控制变量
let isSpinning = false; // 是否正在旋转
let currentRotation = 0; // 当前旋转角度
let currentPunishment = '';// 当前选中的菜品
// NOTE: 转盘扇形颜色列表 - 美食主题渐变色
const colors = [
'#FF6B6B', '#FF9A3C', '#F6D365', '#84FAB0', '#8FD3F4',
'#A78BFA', '#F472B6', '#60A5FA', '#34D399', '#FBBF24',
'#EF4444', '#F59E0B', '#10B981', '#3B82F6', '#8B5CF6'
];
// ========== 工具函数 ==========
/**
* 更新菜品项目计数显示
*/
function updatePunishmentCount() {
const countElement = document.getElementById('punishmentCount');
const count = punishments.length;
countElement.textContent = `${count}项`;
// 根据数量调整颜色
if (count === 0) {
countElement.style.background = 'linear-gradient(135deg, #ef4444, #b91c1c)';
} else if (count <= 5) {
countElement.style.background = 'linear-gradient(135deg, #f59e0b, #d97706)';
} else if (count <= 10) {
countElement.style.background = 'linear-gradient(135deg, #10b981, #0d9668)';
} else {
countElement.style.background = 'linear-gradient(135deg, #8b5cf6, #7c3aed)';
}
}
/**
* 保存菜品列表到LocalStorage
* @description 持久化数据,页面刷新后不丢失
*/
function savePunishments() {
localStorage.setItem('wheelPunishments', JSON.stringify(punishments));
}
/**
* 格式化日期时间
* @Param {Date} date - 日期对象
* @returns {string} 格式化后的日期字符串
*/
function formatDateTime(date) {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
}
/**
* 截断文本,添加省略号
* @param {string} text - 原始文本
* @param {number} maxLength - 最大长度
* @returns {string} 截断后的文本
*/
function truncateText(text, maxLength = 100) {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength) + '...';
}
/**
* 显示一键清空确认弹窗
*/
function showClearConfirmation() {
if (isSpinning) {
alert('转盘旋转时不能清空菜品!');
return;
}
if (punishments.length === 0) {
alert('当前没有菜品可清空!');
return;
}
// 更新清空数量显示
document.getElementById('clearCount').textContent = punishments.length;
// 显示确认弹窗
const modal = document.getElementById('confirmClearModal');
modal.classList.remove('hidden');
// 添加抖动效果以强调危险操作
const content = modal.querySelector('.confirm-clear-content');
content.classList.remove('shake');
void content.offsetWidth; // 触发重排
content.classList.add('shake');
}
/**
* 隐藏一键清空确认弹窗
*/
function hideClearConfirmation() {
document.getElementById('confirmClearModal').classList.add('hidden');
}
/**
* 执行一键清空操作
* @description 清空所有菜品项目
*/
function clearAllPunishments() {
if (isSpinning) {
alert('转盘旋转时不能清空菜品!');
return;
}
// 记录清空前的数量
const previousCount = punishments.length;
// 清空菜品列表
punishments = [];
// 更新UI
updatePunishmentList();
updatePunishmentCount();
updateWheel();
// 保存到LocalStorage
savePunishments();
// 重置转盘状态
currentRotation = 0;
currentPunishment = '';
const wheelSvg = document.getElementById('wheelSvg');
wheelSvg.style.transitionDuration = '0ms';
wheelSvg.style.transform = 'rotate(0deg)';
// 隐藏结果
const resultDisplay = document.getElementById('resultDisplay');
resultDisplay.classList.remove('show');
// 隐藏庆祝弹窗
closeCelebration();
// 显示操作反馈
alert(`已清空 ${previousCount} 个菜品!`);
// 隐藏确认弹窗
hideClearConfirmation();
}
/**
* 恢复默认菜单
*/
function resetToDefaultMenu() {
if (isSpinning) {
alert('转盘旋转时不能恢复默认菜单!');
return;
}
if (!confirm('确定要恢复默认菜单吗?当前所有菜品将被替换为默认菜品。')) {
return;
}
// 恢复默认菜品列表
punishments = [...defaultPunishments];
// 更新UI
updatePunishmentList();
updatePunishmentCount();
updateWheel();
// 保存到LocalStorage
savePunishments();
alert('已恢复默认菜单!');
}
/**
* 导入TXT文件并解析菜品列表
* @description 每行一个菜品,自动过滤空行和重复项
* @param {File} file - 用户选择的TXT文件
*/
function importPunishmentsFromTxt(file) {
const reader = new FileReader();
reader.onload = function(e) {
try {
const content = e.target.result;
// 按换行符分割,过滤空行和首尾空格
const newPunishments = content.split(/\r?\n/)
.map(item => item.trim())
.filter(item => item.length > 0);
if (newPunishments.length === 0) {
alert('文件为空或格式不正确!');
return;
}
// 合并并去重
const uniqueNewPunishments = newPunishments.filter(
item => !punishments.includes(item)
);
if (uniqueNewPunishments.length === 0) {
alert('所有菜品已存在!');
return;
}
// 添加到现有列表
punishments.push(...uniqueNewPunishments);
// 更新UI并保存
updatePunishmentList();
updatePunishmentCount();
updateWheel();
savePunishments();
alert(`成功导入 ${uniqueNewPunishments.length} 个新菜品!${newPunishments.length - uniqueNewPunishments.length > 0 ? `有 ${newPunishments.length - uniqueNewPunishments.length} 个重复菜品已跳过。` : ''}`);
} catch (error) {
console.error('导入文件时出错:', error);
alert('文件解析失败,请检查文件格式!');
}
};
reader.onerror = function() {
alert('读取文件失败!');
};
reader.readAsText(file);
}
// ========== DOM加载完成事件 ==========
document.addEventListener('DOMContentLoaded', function() {
// 初始化UI
updatePunishmentList();
updatePunishmentCount();
updateWheel();
// 输入框回车事件
const input = document.getElementById('punishmentInput');
input.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
addPunishment();
}
});
// 绑定按钮事件
document.getElementById('addPunishmentBtn').addEventListener('click', addPunishment);
document.getElementById('importBtn').addEventListener('click', function() {
const fileInput = document.getElementById('importFile');
if (fileInput.files.length > 0) {
importPunishmentsFromTxt(fileInput.files);
fileInput.value = ''; // 清空文件选择
} else {
alert('请先选择文件!');
}
});
document.getElementById('startBtn').addEventListener('click', startGame);
document.getElementById('resetBtn').addEventListener('click', resetWheel);
document.getElementById('resetToDefaultBtn').addEventListener('click', resetToDefaultMenu);
document.getElementById('closeCelebrationBtn').addEventListener('click', closeCelebration);
document.getElementById('confirmCelebrationBtn').addEventListener('click', closeCelebration);
// 一键清空相关事件
document.getElementById('clearAllPunishmentsBtn').addEventListener('click', showClearConfirmation);
document.getElementById('cancelClearBtn').addEventListener('click', hideClearConfirmation);
document.getElementById('confirmClearBtn').addEventListener('click', clearAllPunishments);
// 点击确认弹窗外部关闭弹窗
document.getElementById('confirmClearModal').addEventListener('click', function(e) {
if (e.target === this) {
hideClearConfirmation();
}
});
// 使用事件委托处理动态生成的删除按钮
document.getElementById('punishmentList').addEventListener('click', function(e) {
if (e.target.classList.contains('remove-btn')) {
const item = e.target.closest('.punishment-item');
const index = Array.from(this.children).indexOf(item);
if (index !== -1) {
removePunishment(index);
}
}
});
});
// ========== 核心功能函数 ==========
/**
* 添加新菜品项目
* @description 验证输入并添加到列表,过滤重复项
*/
function addPunishment() {
const input = document.getElementById('punishmentInput');
const name = input.value.trim();
// 输入验证
if (!name) {
alert('请输入菜品名称!');
return;
}
if (name.length > 30) {
alert('菜品名称过长,请控制在30字以内!');
return;
}
if (!punishments.includes(name)) {
punishments.push(name);
input.value = '';
input.focus();
updatePunishmentList();
updatePunishmentCount();
updateWheel();
savePunishments();
} else {
alert('该菜品已存在!');
}
}
/**
* 删除菜品项目
* @param {number} index - 要删除的项目索引
* @description 转盘旋转时禁止删除
*/
function removePunishment(index) {
if (isSpinning) {
alert('转盘旋转时不能删除菜品!');
return;
}
if (confirm('确定要删除这个菜品吗?')) {
punishments.splice(index, 1);
updatePunishmentList();
updatePunishmentCount();
updateWheel();
savePunishments();
}
}
/**
* 更新菜品列表UI
* @description 动态生成列表项,处理空状态
*/
function updatePunishmentList() {
const list = document.getElementById('punishmentList');
// 处理空列表状态
if (punishments.length === 0) {
list.innerHTML = '<div class="empty-state">菜品菜单是空的,请先添加菜品!</div>';
return;
}
// 生成列表HTML
list.innerHTML = punishments.map((punishment, index) => {
// 提取emoji和文本
const emojiMatch = punishment.match(/^([\u{1F300}-\u{1F9FF}]|\u{1F1E6}-\u{1F1FF}|\u{2600}-\u{26FF}|\u{2700}-\u{27BF})/u);
const emoji = emojiMatch ? emojiMatch : '🍽️';
const text = emojiMatch ? punishment.substring(emojiMatch.length).trim() : punishment;
return `
<div class="punishment-item ${currentPunishment === punishment ? 'active' : ''}">
<div style="display: flex; align-items: center;">
<span class="food-emoji" style="font-size: 1.8rem; margin-right: 0.75rem;">${emoji}</span>
<span>${text}</span>
</div>
<button class="remove-btn" title="删除此项">×</button>
</div>
`;
}).join('');
}
/**
* 更新转盘SVG图形
* @description 动态计算扇形区域,处理文本显示
* @算法说明:
* 1. 计算每个扇形的角度
* 2. 使用极坐标计算弧线路径
* 3. 根据项目数量调整字体大小
*/
function updateWheel() {
const sectionsGroup = document.getElementById('wheelSections');
sectionsGroup.innerHTML = '';
// 空转盘状态
if (punishments.length === 0) {
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', 'M 0 0 L 240 0 A 240 240 0 1 1 -240 0 Z');
path.setAttribute('fill', 'rgba(255,255,255,0.2)');
path.setAttribute('stroke', '#fff');
path.setAttribute('stroke-width', '4');
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
text.setAttribute('x', '0');
text.setAttribute('y', '0');
text.setAttribute('fill', '#fff');
text.setAttribute('font-size', '32');
text.setAttribute('font-weight', 'bold');
text.setAttribute('text-anchor', 'middle');
text.setAttribute('dominant-baseline', 'middle');
text.textContent = '请添加菜品';
sectionsGroup.appendChild(path);
sectionsGroup.appendChild(text);
return;
}
// 计算每个扇形的角度
const sectionAngle = 360 / punishments.length;
const radius = 240; // 转盘半径
// 生成每个扇形
punishments.forEach((punishment, index) => {
// 计算起始和结束角度(弧度制)
const startAngle = index * sectionAngle;
const endAngle = (index + 1) * sectionAngle;
const startAngleRad = (startAngle * Math.PI) / 180;
const endAngleRad = (endAngle * Math.PI) / 180;
// 判断是否为大圆弧(角度大于180度)
const largeArcFlag = sectionAngle > 180 ? 1 : 0;
// 计算弧线起点和终点坐标
const x1 = Math.cos(startAngleRad) * radius;
const y1 = Math.sin(startAngleRad) * radius;
const x2 = Math.cos(endAngleRad) * radius;
const y2 = Math.sin(endAngleRad) * radius;
// 构建扇形路径数据
const pathData = `M 0 0 L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2} Z`;
// 创建扇形路径元素
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', pathData);
path.setAttribute('fill', colors);
path.setAttribute('stroke', '#fff');
path.setAttribute('stroke-width', '4');
path.setAttribute('filter', 'url(#shadow)');
// 计算文本位置(扇形中间)
const midAngle = (startAngle + endAngle) / 2;
const textRadius = radius * 0.65; // 文本位置半径
const textX = Math.cos((midAngle * Math.PI) / 180) * textRadius;
const textY = Math.sin((midAngle * Math.PI) / 180) * textRadius;
// 根据项目数量调整字体大小
let fontSize = 22;
if (punishments.length > 15) fontSize = 14;
else if (punishments.length > 10) fontSize = 16;
else if (punishments.length > 8) fontSize = 18;
// 提取emoji和文本
const emojiMatch = punishment.match(/^([\u{1F300}-\u{1F9FF}]|\u{1F1E6}-\u{1F1FF}|\u{2600}-\u{26FF}|\u{2700}-\u{27BF})/u);
const emoji = emojiMatch ? emojiMatch : '';
const text = emojiMatch ? punishment.substring(emojiMatch.length).trim() : punishment;
// 创建文本元素
const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
textElement.setAttribute('x', textX);
textElement.setAttribute('y', textY);
textElement.setAttribute('fill', '#fff');
textElement.setAttribute('font-size', fontSize);
textElement.setAttribute('font-weight', 'bold');
textElement.setAttribute('text-anchor', 'middle');
textElement.setAttribute('dominant-baseline', 'middle');
textElement.setAttribute('transform', `rotate(${midAngle}, ${textX}, ${textY})`);
// 文本截断处理(长文本显示省略号)
const maxChars = 6;
let displayText = text;
if (text.length > maxChars) {
displayText = text.substring(0, maxChars) + '..';
}
// 如果有emoji,添加到文本前面
const displayContent = emoji ? `${emoji} ${displayText}` : displayText;
textElement.textContent = displayContent;
// 添加到SVG
sectionsGroup.appendChild(path);
sectionsGroup.appendChild(textElement);
});
}
/**
* 开始转盘游戏
* @description 控制转盘旋转并计算最终结果
* @算法说明:
* 1. 生成随机旋转角度和时间
* 2. 使用CSS transition实现动画
* 3. 根据指针角度计算选中扇形
*/
function startGame() {
// 前置检查
if (punishments.length === 0) {
alert('请先添加菜品!');
return;
}
if (isSpinning) return;
// 设置旋转状态
isSpinning = true;
currentPunishment = '';
// 更新按钮状态
const startBtn = document.getElementById('startBtn');
const startBtnText = document.getElementById('startBtnText');
startBtn.disabled = true;
startBtnText.textContent = '点餐中...';
// 隐藏上一个结果
const resultDisplay = document.getElementById('resultDisplay');
resultDisplay.classList.remove('show');
// 生成随机动画参数
const randomDuration = 4000 + Math.random() * 4000; // 4-8秒
// 计算最终旋转角度(多圈旋转 + 随机偏移)
const randomDegreeOffset = Math.floor(Math.random() * 360);
const randomSpins = 5 + Math.floor(Math.random() * 5); // 5-10圈
const finalRotation = currentRotation + (randomSpins * 360) + randomDegreeOffset;
// 更新当前角度
currentRotation = finalRotation;
// 应用旋转动画
const wheelSvg = document.getElementById('wheelSvg');
wheelSvg.style.transitionDuration = `${randomDuration}ms`;
wheelSvg.style.transform = `rotate(${currentRotation}deg)`;
// 动画结束后计算结果
setTimeout(() => {
isSpinning = false;
// 转盘指针固定角度(正上方为270度)
const POINTER_ANGLE = 270;
// 计算实际旋转角度(归一化到0-360度)
const actualRotation = currentRotation % 360;
// 计算指针指向的角度
let winningAngle = (POINTER_ANGLE - actualRotation);
// 角度归一化处理
winningAngle = winningAngle % 360;
if (winningAngle < 0) {
winningAngle += 360;
}
// 根据角度计算选中扇形的索引
const sectionAngle = 360 / punishments.length;
const winningIndex = Math.floor(winningAngle / sectionAngle);
// 获取选中的菜品(边界情况处理)
currentPunishment = punishments || punishments;
// 恢复按钮状态
startBtn.disabled = false;
startBtnText.textContent = '再次点餐';
// 显示结果
const resultEmoji = document.querySelector('#resultDisplay .result-emoji');
resultEmoji.textContent = '🎉';
document.getElementById('winnerPunishment').textContent = currentPunishment;
resultDisplay.classList.add('show');
// 更新列表高亮
updatePunishmentList();
// 显示庆祝弹窗
showCelebration();
}, randomDuration);
}
/**
* 重置转盘
* @description 将转盘归零,清除结果
*/
function resetWheel() {
if (isSpinning) {
alert('转盘旋转时不能重置!');
return;
}
if (!confirm('确定要重置转盘吗?')) return;
currentRotation = 0;
currentPunishment = '';
// 立即重置转盘(无动画)
const wheelSvg = document.getElementById('wheelSvg');
wheelSvg.style.transitionDuration = '0ms';
wheelSvg.style.transform = 'rotate(0deg)';
// 隐藏结果
const resultDisplay = document.getElementById('resultDisplay');
const resultEmoji = document.querySelector('#resultDisplay .result-emoji');
resultEmoji.textContent = '🤔';
resultDisplay.classList.remove('show');
// 更新UI
updatePunishmentList();
closeCelebration();
}
/**
* 显示庆祝弹窗
* @description 展示选中的菜品,带有动画效果
*/
function showCelebration() {
const modal = document.getElementById('celebrationModal');
document.getElementById('celebrationPunishment').textContent = currentPunishment;
// 显示并添加动画
modal.classList.remove('hidden');
const content = modal.querySelector('.celebration-content');
content.classList.remove('modal-enter');
void content.offsetWidth; // 触发重排以重新播放动画
content.classList.add('modal-enter');
}
/**
* 关闭庆祝弹窗
*/
function closeCelebration() {
document.getElementById('celebrationModal').classList.add('hidden');
}
// TODO: 添加导出菜品列表功能
// TODO: 添加转盘音效支持
// TODO: 添加点餐历史记录功能
// OPTIMIZE: 考虑使用Vue/React重构以提高可维护性
</script>
</body>
</html>
源码在哪?
页:
[1]