最近不知道怎么回事,一到中午我就开始“选择困难症”发作。外卖平台翻了十几分钟,结果最后还是点了昨天的那家麻辣烫。其实我也知道附近就那几家店,但每次都想“换点别的”,却又拿不定主意。于是我突发奇想,干脆写一个“随机午饭推荐器”,每天一点,点开一看,吃啥它说了算。
这个想法并不复杂,但当我真正动手时,还是有不少细节让我玩得挺开心的。尤其是为了让它看起来不像“土味网页”,我还花了些心思做了一个现代 UI,甚至引入了轮盘动画和打赏彩蛋。整个过程就像是把一个日常小痛点包装成一份实用又好玩的数字工具。
在正式开工前,我先列出了这个小程序要实现的几个核心功能:
为了更清晰地掌握流程,我画了一张用户操作流程图:
这个小流程图帮助我在编码前就理清了事件顺序和状态管理的关键路径。
我想做一个不太“工程化”的页面,也就是说,虽然用的是原生 HTML/CSS/JS,但 UI 要尽可能简洁、有质感。于是我采用了卡片式布局 + 渐变背景 + 图标加持的设计方案。
界面主要分成几个模块:
配色上我采用了粉橘 + 白灰的组合,既有食欲感又不会显得杂乱。按钮和卡片都带有轻微阴影与圆角,营造一种“轻盈”的触感。
我还引入了 Google Fonts 的 Poppins
字体和 Font Awesome
图标库,整体效果现代而灵动。
<body>
<div class="container">
<header>
<h1>中午吃什么?</h1>
<span id="date"></span>
</header>
<section class="menu-section">
<ul id="menu-list"></ul>
<input id="new-item" placeholder="添加新菜品" />
<button id="add-btn">添加</button>
</section>
<section class="action-section">
<button id="random-btn">🎲 来一个吧!</button>
<button id="reset-btn">🔄 重置菜单</button>
</section>
<section class="result-section">
<div id="roulette" class="animating">🍜 🍣 🥟 🍔 🌮 🍱</div>
<h2 id="result-text">今天吃什么?</h2>
</section>
<footer>
<button id="history-btn">📜 查看历史</button>
</footer>
</div>
<div id="history-modal" class="modal hidden">
<div class="modal-content">
<h3>历史记录</h3>
<ul id="history-list"></ul>
<button id="close-history">关闭</button>
</div>
</div>
</body>
整个页面使用原生组件构建,模块化清晰,没有多余嵌套。下一步是通过 CSS 赋予它更鲜明的风格。
我使用了 CSS 变量定义了主题色和基础尺寸:
:root {
--main-color: #ff8c42;
--bg-color: #fff3e0;
--text-color: #333;
--radius: 12px;
}
背景使用渐变提升氛围感:
body {
background: linear-gradient(145deg, #ffe0b2, #fff3e0);
font-family: 'Poppins', sans-serif;
color: var(--text-color);
}
所有按钮带有轻微放大与 hover 效果:
button {
border: none;
padding: 10px 20px;
background-color: var(--main-color);
color: white;
border-radius: var(--radius);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.2s ease;
}
button:hover {
transform: scale(1.05);
}
卡片结果区域也做了动效准备:
#roulette {
font-size: 2rem;
animation: spin 1s infinite;
}
@keyframes spin {
0% { transform: translateX(0); }
50% { transform: translateX(-10px); }
100% { transform: translateX(10px); }
}
所有样式都是围绕“温暖+简洁+互动性强”的目标来实现的,用户体验上能让人产生信赖和轻松感。
我将主要逻辑拆分成几部分:菜单管理、随机选择动画、结果展示、本地存储封装、历史记录。这让代码更模块化、方便维护,也更易于将来扩展功能,比如接入后端推荐、添加打赏接口等。
下面是 JavaScript 初始化结构:
document.addEventListener('DOMContentLoaded', () => {
initMenu()
bindEvents()
updateDate()
})
我用 localStorage
来持久化保存菜单列表和历史记录,这样用户刷新页面后也不会丢失数据。
function initMenu() {
const savedMenu = JSON.parse(localStorage.getItem('menu')) || []
if (savedMenu.length === 0) {
// 默认菜单
const defaultMenu = ['牛肉拉面', '黄焖鸡米饭', '炒河粉', '寿司便当', '麻辣香锅']
localStorage.setItem('menu', JSON.stringify(defaultMenu))
renderMenu(defaultMenu)
} else {
renderMenu(savedMenu)
}
}
菜单渲染函数也非常直观,把菜单渲染为 li
标签,并附带删除按钮:
function renderMenu(menu) {
const list = document.getElementById('menu-list')
list.innerHTML = ''
menu.forEach((item, index) => {
const li = document.createElement('li')
li.textContent = item
const del = document.createElement('button')
del.textContent = '❌'
del.onclick = () => removeMenuItem(index)
li.appendChild(del)
list.appendChild(li)
})
}
这里绑定了按钮和回车事件双触发添加功能:
function bindEvents() {
document.getElementById('add-btn').addEventListener('click', addMenuItem)
document.getElementById('new-item').addEventListener('keydown', (e) => {
if (e.key === 'Enter') addMenuItem()
})
}
添加函数本身逻辑也很简单:
function addMenuItem() {
const input = document.getElementById('new-item')
const val = input.value.trim()
if (!val) return
const menu = JSON.parse(localStorage.getItem('menu'))
menu.push(val)
localStorage.setItem('menu', JSON.stringify(menu))
renderMenu(menu)
input.value = ''
}
删除也是更新 localStorage
后重新渲染:
function removeMenuItem(index) {
const menu = JSON.parse(localStorage.getItem('menu'))
menu.splice(index, 1)
localStorage.setItem('menu', JSON.stringify(menu))
renderMenu(menu)
}
最有意思的部分来了 —— 随机选择功能。我希望点击“来一个吧”按钮之后不是直接蹦出结果,而是有个轮盘或跳动效果,给人“选择中”的感觉。
先定义核心逻辑:
function randomPick() {
const roulette = document.getElementById('roulette')
const resultText = document.getElementById('result-text')
const menu = JSON.parse(localStorage.getItem('menu'))
if (!menu.length) {
alert('菜单为空,请先添加一些菜名吧!')
return
}
roulette.classList.add('animating')
resultText.textContent = '选择中...'
let steps = 20 + Math.floor(Math.random() * 10) // 随机步数
let current = 0
const interval = setInterval(() => {
const idx = Math.floor(Math.random() * menu.length)
roulette.textContent = menu[idx]
current++
if (current >= steps) {
clearInterval(interval)
roulette.classList.remove('animating')
resultText.textContent = `今天吃:${menu[idx]}!`
updateHistory(menu[idx])
}
}, 100)
}
这个函数的重点在于用 setInterval
模拟“滚动选择”,让用户感受到一种“机器正在思考”的动画氛围。
按钮绑定也很简单:
document.getElementById('random-btn').addEventListener('click', randomPick)
为了避免用户连续几天吃重复,我添加了历史记录功能。点击底部按钮弹出 modal 查看最近吃过什么。
保存历史:
function updateHistory(item) {
const history = JSON.parse(localStorage.getItem('history')) || []
const today = new Date().toLocaleDateString()
history.push({ item, date: today })
localStorage.setItem('history', JSON.stringify(history))
}
渲染历史:
document.getElementById('history-btn').addEventListener('click', () => {
const modal = document.getElementById('history-modal')
const list = document.getElementById('history-list')
const history = JSON.parse(localStorage.getItem('history')) || []
list.innerHTML = ''
history.slice(-10).reverse().forEach(entry => {
const li = document.createElement('li')
li.textContent = `${entry.date} - ${entry.item}`
list.appendChild(li)
})
modal.classList.remove('hidden')
})
document.getElementById('close-history').addEventListener('click', () => {
document.getElementById('history-modal').classList.add('hidden')
})
为了让用户每天第一次打开时能看到当前日期,我添加了 updateDate()
:
function updateDate() {
const date = new Date()
const str = date.toLocaleDateString('zh-CN', { weekday: 'long', month: 'short', day: 'numeric' })
document.getElementById('date').textContent = str
}
而“重置菜单”按钮其实就是清空并重新写入默认值:
document.getElementById('reset-btn').addEventListener('click', () => {
if (confirm('确认重置菜单为默认?')) {
const defaultMenu = ['牛肉拉面', '黄焖鸡米饭', '炒河粉', '寿司便当', '麻辣香锅']
localStorage.setItem('menu', JSON.stringify(defaultMenu))
renderMenu(defaultMenu)
}
})
我在开发完成后,把这个小程序发给了几个朋友试玩。有趣的是他们的反馈几乎都一致——“我终于不用为午饭焦虑了!”还有人提了建议,比如希望结果中能出现“今天不吃了节省一顿”的选项,这也启发我未来可能加上“低预算日”、“健康日”等标签系统。
运行效果方面,桌面和手机端都表现不错。由于不依赖任何服务器,完全可以脱机运行,甚至可以打包成 PWA 应用放在桌面上。
这就是我从一个“中午吃什么”的小困扰出发,开发的完整小项目记录。虽然它简单,但我很享受将一个生活中琐碎的痛点转化为一段实用代码的过程。代码之外,更多是从日常中找灵感、在实现中学技术的满足感。
如果你也时常为“吃什么”而烦恼,也许可以试试动手写一个属于你自己的美食决策器。
完结撒花 🎉
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。