news 2026/9/15 13:02:54

纯前端点餐系统DEMO开发实战:从HTML/CSS/JS到H5移动端适配

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
纯前端点餐系统DEMO开发实战:从HTML/CSS/JS到H5移动端适配

简介:这是一份基于HTML5、JavaScript与CSS构建的APP点餐系统前端示例代码,面向Web前端初学者、移动端开发人员及有课程设计需求的学生。系统围绕在线点餐核心流程,覆盖菜单浏览、菜品分类、购物车、订单提交等典型功能,并采用移动优先的响应式布局思路。压缩包共93个文件,以23个HTML页面、7个CSS样式表、7个JS交互脚本为主体,辅以30张png、22张jpg、3张gif等图片资源,整体仅1.23MB,目录结构按首页、菜单、购物车、订单等模块划分,便于对照学习。目前已有778人学习下载。通过源码可重点研究H5语义化结构、JavaScript实现的无刷新异步交互、CSS自适应布局以及触摸滑动组件封装等技巧,无论是用于入门练手还是二次开发,都能提供完整的前端实现参照。

1. 一个纯前端 DEMO 能把点餐做到什么程度

一份菜单、一部手机、一个浏览器,就是点餐系统最小 DEMO 的全部载体。标题里把 HTML、JS、CSS、H5 放在一起,其实点破了这件事:真正可用的点餐系统不一定先要有后端服务,纯静态页面也能把「菜品展示—加购—结算」这条主链路完整跑通。这类 DEMO 最常见的用途有三个:给课程设计交差、给餐馆做移动端菜单预览、给后续接真实接口的前端项目当页面骨架。

需要注意的是,这类代码资源里“DEMO-HTML .5”往往意味着它是半成品向成品过渡的形态——页面结构有了、交互逻辑有了,但数据是写死的、订单不会真正提交到服务器。这个定位决定了它的技术取舍:不追求工程化,不引入构建工具,核心价值在数据结构和 DOM 交互上。后面所有章节都围绕一个目标展开:让这个 DEMO 接近真实点餐 App 的体验,同时保留随时替换成真实数据源的能力。

2. 零依赖的 HTML/CSS 骨架:先把页面结构跑起来

2.1 为什么这个 DEMO 不引入 Vue/React

点餐 DEMO 的典型场景是「打开即看、双击即用」。如果一上来就npm create vue,用户需要先装 Node、再装依赖、再起 dev server,这对一个以展示和练手为目的的代码资源来说是过重的负担。常见的做法是:三个静态文件,浏览器直接打开 index.html,连本地服务器都不需要;后续要部署,丢到 Nginx 或者对象存储里就能访问。

但不用框架不等于没有结构设计。这个 DEMO 仍然需要有清晰的「视图—数据—行为」分层:index.html 只放容器节点,style.css 负责移动端布局和主题变量,app.js 里维护菜单数据和购物车状态。数据层与视图层分离带来的直接好处是,以后从menuData常量切换到fetch('/api/menu')时,渲染函数一行都不用改。这是这类 DEMO 最值得保留的工程习惯。

2.2 目录结构与 HTML 骨架

我一般会把点餐 DEMO 的文件拆成下面这样,尽量让每个文件职责单一:

order-demo/ ├── index.html ├── style.css └── app.js

index.html 只保留必要的 SEO 信息和应用容器。顶部信息栏展示店名和营业状态,中间是菜品列表挂载点,底部是固定操作栏,显示已选数量和合计金额。代码如下:

<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <title>点餐系统 DEMO</title> <link rel="stylesheet" href="style.css"> </head> <body> <div id="order-app"> <header class="shop-bar"> <h1>老灶私房菜</h1> <span class="status-dot">营业中</span> </header> <main id="demo-menu" class="menu-list"> <!-- 菜品卡片由 app.js 渲染 --> </main> <footer class="cart-bar"> <div class="cart-total"> 已选 <span id="cart-count">0</span> 件 <strong>¥<span id="cart-price">0.00</span></strong> </div> <button id="checkout-btn">去结算</button> </footer> </div> <script src="app.js"></script> </body> </html>

这段结构里有几个关键点。viewport-fit=cover是为了在 iPhone 刘海屏下让页面撑满安全区,这在 H5 点餐页面里是刚需。菜品列表容器demo-menu的命名刻意加了demo前缀,这样在控制台里直接getElementById("demo-menu")就能拿到列表节点,调试时省去一层查找。cart-bar使用固定定位,保证用户滚到长菜单底部时也能看到购物车入口,这是点餐产品的基本交互要求。

2.3 CSS 变量与移动端布局

点餐页面的视觉重点有两个:菜品卡片的信息密度、底部操作栏的可用性。卡片要在一屏内尽量多展示菜品,图片、名称、价格、加购按钮必须对齐;底部栏要随时可见,且不能被键盘弹起顶乱。

:root { --primary: #ff6a00; --text-main: #2d2d2d; --text-sub: #888; --card-bg: #ffffff; --radius: 12px; } * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; } body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background: #f5f5f5; color: var(--text-main); } .menu-list { display: flex; flex-direction: column; gap: 12px; padding: 12px 16px calc(76px + env(safe-area-inset-bottom)); } .cart-bar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; padding-bottom: calc(12px + env(safe-area-inset-bottom)); background: #fff; border-top: 1px solid #eee; }

CSS 变量--primary把主题色收敛到一处,后面如果换品牌色只改这里。底部安全区用了env(safe-area-inset-bottom)适配全面屏手势条,同时菜单列表底部预留了76px的留白避免最后一张卡片被固定栏遮挡。这套变量与适配规则,在点餐系统之外的移动端落地页里也能直接沿用。

文件职责上分三块:index.html负责结构、style.css负责视觉和适配、app.js负责数据与交互。需要说明的是,真实项目里图片资源会增加一个 assets 目录,DEMO 阶段用占位色块或 emoji 即可,不影响交互调试。

3. JS 数据模型与购物车:核心交互在这里

3.1 菜品 JSON 与渲染函数

点餐系统的数据模型比普通列表页复杂的地方在于,菜品需要支持分类、标签、起售状态、价格精度这些业务属性。DEMO 阶段我一般直接用数组 + 对象表达:

const menuData = [ { id: 1, name: "宫保鸡丁", price: 32, category: "热菜", tag: "招牌", soldOut: false }, { id: 2, name: "麻婆豆腐", price: 22, category: "热菜", tag: "微辣", soldOut: false }, { id: 3, name: "蒜蓉空心菜", price: 18, category: "素菜", tag: "时令", soldOut: true }, { id: 4, name: "冰镇酸梅汤", price: 8, category: "饮品", tag: "解腻", soldOut: false }, ]; const menuContainer = document.getElementById("demo-menu"); function renderMenu(list) { menuContainer.innerHTML = list.map(item => { const disabled = item.soldOut ? "disabled" : ""; return ` <div class="dish-card ${item.soldOut ? "is-soldout" : ""}">const cart = {}; menuContainer.addEventListener("click", (e) => { const addBtn = e.target.closest(".btn-add"); if (!addBtn || addBtn.disabled) return; const id = Number(addBtn.dataset.id); if (!cart[id]) { cart[id] = 1; } else { cart[id] += 1; } renderCart(); }); function renderCart() { const count = Object.values(cart).reduce((sum, n) => sum + n, 0); document.getElementById("cart-count").textContent = count; updateSummary(); }

closest(".btn-add")是关键容错手段。用户实际点击的位置可能落在<button>内部的文字节点上,直接用e.target.dataset可能拿到undefinedclosest会向上查找直到找到匹配的元素,找不到就返回null。加购成功后只更新购物车区域,不重绘整个菜单列表,避免用户滚动位置被打断。

3.3 购物车合计与结算状态计算

合计计算需要把菜品单价和购物车数量二次关联。这里有两个容易踩的坑:一是购物车里可能存了已经被下架的菜品;二是浮点数累加会产生精度误差。正确的写法是先查菜品数据,再乘数量,统一用整数分计算:

function getCartItems() { return Object.entries(cart) .map(([id, count]) => { const dish = menuData.find(d => d.id === Number(id)); return dish ? { ...dish, count } : null; }) .filter(Boolean); } function updateSummary() { const items = getCartItems(); const totalCents = items.reduce((sum, item) => { return sum + Math.round(item.price * 100) * item.count; }, 0); const priceEl = document.getElementById("cart-price"); priceEl.textContent = (totalCents / 100).toFixed(2); } updateSummary();

价格先乘 100 转成整数分参与累加,最后再除以 100,这一步能规避0.1 + 0.2 === 0.30000000000000004这类经典问题。过滤空值是为了防止menuData后续更新时删掉了某个菜品,导致结算页面出现 undefined。

filter(Boolean)很多新手看不出门道,它实际执行的是把map返回数组里的null全部剔除。这个技巧在「先关联、再过滤」的管线式数据处理里很常用,比写filter(item => item !== null)更简洁。这里getCartItems输出的数组结构,就是将来传给后端下单接口的 payload 雏形,字段层面已经具备了对接条件。

4. H5 适配、本地存储与三个必调细节

4.1 viewport 与 rem 适配策略

点餐页面 90% 的流量来自手机,H5 适配的第一道关是 viewport。width=device-width让布局视口等于设备宽度,initial-scale=1保证不缩放。在此基础上有人会用 rem 做等比缩放:把根字号设置成屏幕宽度的 1/10,所有尺寸改用 rem 标注。对于点餐这种固定 375 设计稿的场景,常见实现是:

html { font-size: calc(100vw / 3.75); }

这样设计稿上的 12px 就写成0.12rem,在 375px 宽的屏幕上渲染出的物理尺寸和设计稿一致。需要注意calc(100vw / 3.75)在横屏时会变得很大,要用媒体查询限制根字号范围:

@media (min-width: 480px) { html { font-size: 20px; } }

实际项目中我更推荐 flex + 百分比布局为主、rem 只用于字体和间距的方案。点餐卡片需要固定宽高比、底部栏需要贴边,这两个场景 flex 都更可控,全站 rem 反而会让卡片边框和阴影在不同屏幕上出现细微的毛刺。适配做到「中等屏幕不局促、大屏幕不拉伸」即可,不必过分追求像素级还原。

4.2 localStorage 持久化购物车

DEMO 的一个常见痛点:用户加了几个菜,手一滑刷新页面,购物车清空了。真实 App 会把购物车状态同步到服务端,DEMO 阶段用localStorage模拟持久化是通行的做法。写入时机选在每次购物车变化之后,读取时机选在页面初始化时:

const CART_KEY = "order-demo-cart-v1"; function saveCart() { localStorage.setItem(CART_KEY, JSON.stringify(cart)); } function loadCart() { try { const raw = localStorage.getItem(CART_KEY); return raw ? JSON.parse(raw) : {}; } catch (err) { console.warn("购物车数据解析失败,已重置", err); return {}; } } // 初始化 Object.assign(cart, loadCart()); renderCart();

key 里带上v1后缀是一种防御性设计。DEMO 阶段数据结构经常调整——比如cart从「id 到数量的映射」改成「数组嵌套对象」——旧数据在JSON.parse后可能结构不匹配,导致渲染异常。用版本号区分后,下次结构变更时直接把 key 改成v2,旧数据自然失效,不需要额外写迁移逻辑。

try-catch必须包住JSON.parse。localStorage 里的数据不可信,用户可能手动改坏、也可能是上一个版本留下的脏数据,解析失败时静默重置比抛异常白屏更好。VSCode 调试时也可以在 Application 面板里手动清掉这个 key 来重置状态。

4.3 移动端点击延迟与 hover 状态

CSS 的:hover是鼠标时代的产物,在触屏上表现是另一回事。移动端浏览器会把第一次点击解释为 hover,导致「点了一下没反应,第二下才触发 click」。点餐场景里加购按钮是最高频操作,这个延迟非常影响体验。现代移动浏览器在设置了width=device-width的 viewport 后会取消 300ms 点击延迟,所以 HTML 里的 viewport meta 本身就在解决这个问题。

另一个隐患是手指触发:hover后状态会粘住。用户点了「加入」按钮,按钮的 hover 背景色会一直保持到点击页面其他地方,看起来像按钮被卡住了。点餐卡片的加购按钮上,我一般会用:active替代:hover做反馈,因为:active只在手指按下时生效,松手即还原:

.btn-add:active { transform: scale(0.96); background: var(--primary); color: #fff; }

transform: scale(0.96)是移动端常用的按压反馈:按钮在手指按下瞬间轻微缩小,松手弹回,视觉上比单纯变色更接近原生控件手感。CSS 里transition: transform 0.1s可以再配合这行代码让缩放更顺滑;0.1 秒是经过验证的值,太短会显得生硬,太长会有拖拽感。如果目标设备包含低端安卓,阴影动画比 transform 更耗性能,缩放在这两类设备上都能稳定 60 帧。

5. 进阶:把 DEMO 改成可交付的演示单页

5.1 hash 切换结算页与搜索筛选

单页应用的雏形可以从 hash change 开始。底部「去结算」按钮点击后把location.hash改成#checkout,监听hashchange事件控制菜单区域和结算区域的显隐。不用引入路由库,十来行代码就能让 DEMO 具备两个页面的基本导航能力。搜索筛选用Array.prototype.includes判断菜品名是否包含关键字,让数据与输入框联动:

window.addEventListener("hashchange", switchView); const searchInput = document.getElementById("search-input"); searchInput.addEventListener("input", (e) => { const kw = e.target.value.trim(); const filtered = menuData.filter(d => d.name.includes(kw)); renderMenu(filtered); }); function switchView() { const isCheckout = location.hash === "#checkout"; document.getElementById("view-menu").classList.toggle("hidden", isCheckout); document.getElementById("view-checkout").classList.toggle("hidden", !isCheckout); }

includes(kw)对空字符串天然返回 true,所以输入框清空时直接显示全部菜品,不需要额外判断。同时把下单按钮换成「确认下单(模拟)」,点击后用alert输出JSON.stringify(getCartItems()),这样演示时观众能看到即将提交的数据结构。

5.2 满减规则与字段预留

真实点餐系统少不了一套优惠规则。DEMO 里可以预置一个规则对象,结算时叠加计算:

const discountRule = { threshold: 50, reduction: 5 }; function calcPayable(totalCents) { const totalYuan = totalCents / 100; if (totalYuan >= discountRule.threshold) { return totalCents - discountRule.reduction * 100; } return totalCents; }

规则对象独立于菜品和购物车之外,意味着后续从fetch('/api/discount')拉取真实规则时,只需替换规则来源,calcPayable的纯函数逻辑一行不用动。这里给数据字段做预留是一种习惯:比如菜品数据里提前加上monthlySold销量字段用于排序,比上线需求来了再改数据模型更从容。

把订单结果输出到控制台,再配合metas字段验证优惠金额与接口文档是否一致,这个做法在演示时尤其加分——演示结束后直接展示console里的完整订单对象,比任何口头说明都有说服力。整个 DEMO 到现在已经具备「展示、筛选、加购、优惠、结算模拟」五个完整链路,足以撑起一个产品原型的交互演示。此时将它嵌入 WebView 或小程序容器,就能完成从静态资源到 App 内页的转变,这也是这类点餐系统代码类资源最实际的一条交付路径。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 13:01:56

基于布莱克曼窗的FIR低通滤波器设计与MATLAB实现

简介&#xff1a;面向数字信号处理初学者和 MATLAB 使用者&#xff0c;这份示例通过布莱克曼窗完成 FIR 低通滤波器设计&#xff0c;主程序直接调用 Blackman() 生成窗函数&#xff0c;并结合 ideal_lp() 理想低通函数与 freqz_m() 频率响应函数&#xff0c;完整演示了从理想低…

作者头像 李华
网站建设 2026/9/15 13:00:33

用 OpenCore Legacy Patcher 给老 Mac 升级 macOS 15 完整指南

用 OpenCore Legacy Patcher 给老 Mac 升级 macOS 15 完整指南 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher OpenCore Legacy Patcher 是一款面向 Intel 老…

作者头像 李华
网站建设 2026/9/15 12:58:51

3个实战案例揭秘wordpress免费导航主题为何没人看

3个实战案例揭秘wordpress免费导航主题为何没人看 网站做好了没人访问,这行话我听了太多年。很多站长花大价钱买了服务器,甚至请了开发,结果上线一个月,后台日志里除了蜘蛛就是404错误。问题往往不出在技术高深,而出在选错了方向。你以为你做的是企业官网,其实用户想看的是资源聚合。这时候,…

作者头像 李华
网站建设 2026/9/15 12:57:14

ASPICE Level 1配置管理实战:从基线建立到评估审计的落地方法

评估前一周&#xff0c;项目经理把配置管理相关的差距清单甩过来&#xff1a;“基线有了&#xff0c;但代码和测试用例对不上号&#xff0c;评估师要我们证明版本怎么控制的。”这种场景&#xff0c;在汽车电子供应链里太常见了。ASPICE&#xff08;Automotive Software Proces…

作者头像 李华