首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >「中午吃什么」小程序:从纠结中解放的自助小助手

「中午吃什么」小程序:从纠结中解放的自助小助手

原创
作者头像
繁依Fanyi
发布2025-05-15 23:37:29
发布2025-05-15 23:37:29
2940
举报

最近不知道怎么回事,一到中午我就开始“选择困难症”发作。外卖平台翻了十几分钟,结果最后还是点了昨天的那家麻辣烫。其实我也知道附近就那几家店,但每次都想“换点别的”,却又拿不定主意。于是我突发奇想,干脆写一个“随机午饭推荐器”,每天一点,点开一看,吃啥它说了算。

这个想法并不复杂,但当我真正动手时,还是有不少细节让我玩得挺开心的。尤其是为了让它看起来不像“土味网页”,我还花了些心思做了一个现代 UI,甚至引入了轮盘动画和打赏彩蛋。整个过程就像是把一个日常小痛点包装成一份实用又好玩的数字工具。


🧠 项目构思:功能划分与基本目标

在正式开工前,我先列出了这个小程序要实现的几个核心功能:

  1. 随机选择功能:点击按钮即可随机从预设的“午饭菜单”中选出一个结果;
  2. 菜单可自定义:用户可以添加、删除、修改自己的推荐列表;
  3. 结果展示动画:不仅是文本提示,最好能有个小动画增强互动感;
  4. 历史记录:记录今天中午吃了啥,下次不要连着吃三天同一个;
  5. UI 响应式:桌面和移动端都要适配好,手感要舒适;
  6. 本地存储:所有数据保存在浏览器本地,不依赖后台服务。

为了更清晰地掌握流程,我画了一张用户操作流程图:

在这里插入图片描述
在这里插入图片描述

这个小流程图帮助我在编码前就理清了事件顺序和状态管理的关键路径。


💡 页面布局与 UI 设计

我想做一个不太“工程化”的页面,也就是说,虽然用的是原生 HTML/CSS/JS,但 UI 要尽可能简洁、有质感。于是我采用了卡片式布局 + 渐变背景 + 图标加持的设计方案。

界面主要分成几个模块:

  • 顶部标题栏:展示程序名称、图标、当前日期;
  • 菜单展示区:滚动显示当前菜单,可编辑;
  • 操作按钮区:包含“随机选择”、“添加菜单项”、“重置”等按钮;
  • 结果展示卡片:动画区域与最终结果呈现;
  • 历史记录区:一个小弹窗展示历史吃过的项目。

配色上我采用了粉橘 + 白灰的组合,既有食欲感又不会显得杂乱。按钮和卡片都带有轻微阴影与圆角,营造一种“轻盈”的触感。

我还引入了 Google Fonts 的 Poppins 字体和 Font Awesome 图标库,整体效果现代而灵动。


🧱 HTML 页面结构

代码语言:html
复制
<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 风格设计

我使用了 CSS 变量定义了主题色和基础尺寸:

代码语言:css
复制
:root {
  --main-color: #ff8c42;
  --bg-color: #fff3e0;
  --text-color: #333;
  --radius: 12px;
}

背景使用渐变提升氛围感:

代码语言:css
复制
body {
  background: linear-gradient(145deg, #ffe0b2, #fff3e0);
  font-family: 'Poppins', sans-serif;
  color: var(--text-color);
}

所有按钮带有轻微放大与 hover 效果:

代码语言:css
复制
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);
}

卡片结果区域也做了动效准备:

代码语言:css
复制
#roulette {
  font-size: 2rem;
  animation: spin 1s infinite;
}
@keyframes spin {
  0% { transform: translateX(0); }
  50% { transform: translateX(-10px); }
  100% { transform: translateX(10px); }
}

所有样式都是围绕“温暖+简洁+互动性强”的目标来实现的,用户体验上能让人产生信赖和轻松感。


🧠 主逻辑脚本设计

我将主要逻辑拆分成几部分:菜单管理、随机选择动画、结果展示、本地存储封装、历史记录。这让代码更模块化、方便维护,也更易于将来扩展功能,比如接入后端推荐、添加打赏接口等。

下面是 JavaScript 初始化结构:

代码语言:js
复制
document.addEventListener('DOMContentLoaded', () => {
  initMenu()
  bindEvents()
  updateDate()
})

🧾 初始化菜单逻辑

我用 localStorage 来持久化保存菜单列表和历史记录,这样用户刷新页面后也不会丢失数据。

代码语言:js
复制
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 标签,并附带删除按钮:

代码语言:js
复制
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)
  })
}

🔁 添加 / 删除菜单项

这里绑定了按钮和回车事件双触发添加功能:

代码语言:js
复制
function bindEvents() {
  document.getElementById('add-btn').addEventListener('click', addMenuItem)
  document.getElementById('new-item').addEventListener('keydown', (e) => {
    if (e.key === 'Enter') addMenuItem()
  })
}

添加函数本身逻辑也很简单:

代码语言:js
复制
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 后重新渲染:

代码语言:js
复制
function removeMenuItem(index) {
  const menu = JSON.parse(localStorage.getItem('menu'))
  menu.splice(index, 1)
  localStorage.setItem('menu', JSON.stringify(menu))
  renderMenu(menu)
}

🎲 随机抽选 + 动画模拟

最有意思的部分来了 —— 随机选择功能。我希望点击“来一个吧”按钮之后不是直接蹦出结果,而是有个轮盘或跳动效果,给人“选择中”的感觉。

先定义核心逻辑:

代码语言:js
复制
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 模拟“滚动选择”,让用户感受到一种“机器正在思考”的动画氛围。

按钮绑定也很简单:

代码语言:js
复制
document.getElementById('random-btn').addEventListener('click', randomPick)

📜 历史记录模块

为了避免用户连续几天吃重复,我添加了历史记录功能。点击底部按钮弹出 modal 查看最近吃过什么。

保存历史:

代码语言:js
复制
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))
}

渲染历史:

代码语言:js
复制
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()

代码语言:js
复制
function updateDate() {
  const date = new Date()
  const str = date.toLocaleDateString('zh-CN', { weekday: 'long', month: 'short', day: 'numeric' })
  document.getElementById('date').textContent = str
}

而“重置菜单”按钮其实就是清空并重新写入默认值:

代码语言:js
复制
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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🧠 项目构思:功能划分与基本目标
  • 💡 页面布局与 UI 设计
  • 🧱 HTML 页面结构
  • 🎨 CSS 风格设计
  • 🧠 主逻辑脚本设计
    • 🧾 初始化菜单逻辑
    • 🔁 添加 / 删除菜单项
    • 🎲 随机抽选 + 动画模拟
    • 📜 历史记录模块
  • 🧩 其他细节与优化
  • 🧪 测试体验与使用反馈
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档