ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Node.js与Vue的餐厅点餐管理系统设计与实现

基于Node.js与Vue的餐厅点餐管理系统设计与实现 1. 项目到底要做什么需求拆解与功能清单我做了这么多年全栈开发接过的管理系统项目少说也有几十个了但每次看到“点餐系统”这类题目还是会有兴趣。原因很简单点餐系统麻雀虽小五脏俱全前后端交互、权限管理、数据流转、移动端适配全都能练到特别适合拿来入门真实项目开发也适合作为毕业设计或者个人作品集里的主打项目。回到这个标题——nodejs基于Vue的餐厅美食点餐信息管理系统设计。拆开来看核心词有三个Node.js、Vue、点餐信息管理。前两个确定了技术栈第三个确定业务方向。这个组合在目前的开发环境下非常主流前端的活交给Vue后端的接口服务用Node.js来提供前后端通过HTTP请求完成数据交互整个系统敏捷又轻量。1.1 用户点餐端要解决的真实问题先想一个最实际的问题一个普通的餐厅没上系统前是怎么点餐的服务员拿纸质菜单给你你看完勾选服务员再录入收银机或者后厨。高峰期的时候人手不够客人等得着急服务员来回跑也辛苦后厨接单还可能出错。这套流程的痛点很明确效率低、易出错、管理数据一团乱麻。所以用户端也就是顾客用H5或者桌面端操作的那一侧必须具备以下几个核心功能菜品浏览按分类展示菜品包括菜品图片、名称、价格、描述、月销量、剩余库存/是否售罄。菜品搜索关键词模糊搜索方便顾客快速定位想吃的菜。购物车管理加菜、减菜、清空、计算总价购物车数据在刷新后依然保留。提交订单选桌号或者扫码自动带出桌号、填写备注、提交订单。订单状态查看已提交、制作中、已完成等状态让顾客心里有数。1.2 后台管理端不可少的功能模块后台管理端是给餐厅老板和服务员用的这一侧的核心目标是高效管理门店的日常运营数据。我通常是这么拆分模块的登录鉴权管理员账号密码登录后端生成Token前端通过路由守卫拦截未登录的请求。菜品管理菜品的增删改查、上下架、图片上传、库存管理。分类管理热菜、凉菜、主食、饮品、甜品等分类的维护。订单管理订单列表、订单详情、订单状态流转待接单→制作中→已完成/已取消、按时间段筛选营业额。桌台管理桌号、座位数、扫码对应的桌台编号。数据概览今日营收、订单量、热门菜品排行这部分可以做成简单的ECharts图表也可以做纯表格数据。1.3 为什么选Node.js Vue这套组合有人可能会问高校里最常见的毕设后端不是Spring Boot吗为什么这里用Node.js客观来说两者没有绝对的好坏关键看项目自身的定位。Node.js Vue这套组合最大的优势在于语言栈统一前后端都用JavaScript思维模式不需要频繁切换。对于个人开发者或者小团队来说从建模到开发再到部署整个链条的调试成本都更低。另外Node.js生态中的Express框架非常轻量写几个CRUD接口几乎不需要什么配置开箱即用。而Vue作为渐进式框架既可以只做视图层也可以配合Vue Router和Pinia/Vuex把整个单页应用撑起来学习曲线相对平缓实际陡峭程度取决于你做到多深。再加上Node.js的npm生态极其庞大很多能力封装得非常成熟做一个小型管理系统绰绰有余。不过也要说个前提如果你的项目需要对接复杂的事务处理、分布式场景或者团队本身就是Java技术栈那Spring Boot当然是更好的选择。这个项目选择Node.jsVue恰恰是在“快速交付、团队配置轻、场景不算重”的前提下做出的合理决策。2. 环境准备从零搭好开发地基说干就干第一步就是把开发环境拉起来。环境配置这个环节看着简单但我见过太多人在这一步被卡住尤其是Windows环境下npm各种报错能把新手劝退。所以这里有值得一提的细节。2.1 Node.js安装及环境配置踩坑预警Node.js的安装本身不算难去官网下载LTS版本建议用LTS别追最新的稳字当头然后一直下一步就行。但我实际操作中碰到过几个高频问题这里提前给你们排雷第一个坑npm下载慢到怀疑人生。解决方法是配置国内镜像源。我用的是nrm这个工具来管理npm源切换起来非常方便。安装方式npm install -g nrm nrm use taobao如果不想装工具也可以直接改npm配置npm config set registry https://registry.npmmirror.com第二个坑Windows PowerShell执行脚本报错。很多人在装完依赖想跑npm命令时会遇到这个经典报错npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本这是因为PowerShell的执行策略默认是Restricted不允许执行脚本。解决办法很简单用管理员身份打开PowerShell执行Set-ExecutionPolicy RemoteSigned然后选择Y或者A确认即可。注意是“RemoteSigned”意思是本地脚本可以运行远程下载的需要签名这个级别比较平衡不会把系统安全机制全关掉。第三个坑安装时缺C编译环境。部分npm包比如node-sass这类老包或者bcrypt需要本地编译Windows上如果没有安装Visual Studio Build Tools和Python2/3编译阶段就会报错。现在的方案是尽量选纯JS实现的替代包比如bcryptjs代替bcryptnode-sass一律换成sassdart-sass。新版Node.js搭配对应的依赖版本一般不会再踩这个坑了但如果你用的是老项目的依赖锁定文件就可能遇到。2.2 用Vue CLI快速创建前端项目环境就绪后创建Vue项目。虽然Vite已经越来越流行但考虑到Vue CLI在生态兼容性和文档完善度上更成熟而且很多教程、插件默认都支持它我依然建议初学者先用Vue CLI。安装命令是npm install -g vue/cli vue create restaurant-frontend创建过程中会让你选预设我习惯选Manually select features然后勾选这几个必要的Babel把ES6转成浏览器能识别的ES5。Router前端路由点餐和管理页面之间的跳转全靠它。Vuex或者现在推荐用Pinia全局状态管理购物车数据放这里最合适。CSS Pre-processors我选Less写嵌套样式会舒服很多。选完之后等依赖安装完成就行。如果中途因为网络问题报错重新执行npm install多试几次或者检查registry是否已经切换到镜像源。补充一句创建完项目后建议立刻跑一遍npm run serve能正常打开欢迎页再继续往下开发。千万不要攒了一堆代码再启动到那时候报错都不知道是环境问题还是代码问题。2.3 前后端项目结构规划开发之前先把目录结构理清楚。我这个系统的目录分成了两个大端彼此独立restaurant-system/ ├── frontend/ # Vue前端工程 │ ├── public/ │ ├── src/ │ │ ├── api/ # 接口请求封装 │ │ ├── assets/ # 静态资源 │ │ ├── components/ # 公共组件 │ │ ├── router/ # 路由配置 │ │ ├── store/ # 状态管理 │ │ ├── views/ # 页面组件 │ │ ├── App.vue │ │ └── main.js │ ├── package.json │ └── vue.config.js └── backend/ # Node.js后端工程 ├── config/ # 数据库配置 ├── routes/ # 路由注册 ├── controllers/ # 控制器业务处理 ├── models/ # 数据模型 ├── middleware/ # 中间件 ├── public/ # 上传的图片等静态资源 ├── app.js # 入口文件 └── package.json这样划分有个好处前后端可以并行开发我只需要约定好接口文档前端拿Mock数据先跑页面后端专注写接口逻辑最后联调时再统一对接。3. 后端设计与实现用Node.js把接口撑起来3.1 技术选型说明后端的技术栈我选的是Express MySQL这两个组合在Node.js后端开发里属于稳如老狗级别的方案。ExpressNode.js最经典的Web框架中间件机制设计得很优雅写RESTful API体验非常好。MySQL开源关系型数据库在Web开发领域使用率最高腰杆硬。如果你本机装MySQL觉得麻烦也可以先用SQLite顶上表结构几乎不用改但后期部署到服务器后我还是推荐切回MySQL。SequelizeORM框架建表、增删改查都用模型定义来操作可以有效防止SQL注入后续维护重构也方便。如果不想上ORM也可以直接用mysql2这个驱动写原生的SQL语句看个人习惯。3.2 数据库设计菜品、订单、桌台与分类的关系数据库表是整个系统的地基表结构设计得不好后面写代码的时候会疯狂后悔。我花了很长时间把表结构理清楚这里直接给出我的设计方案。管理员表admin字段类型说明idint主键自增usernamevarchar(50)登录账号唯一passwordvarchar(255)密码bcrypt加密nicknamevarchar(50)显示昵称avatarvarchar(255)头像地址created_atdatetime创建时间菜品分类表category字段类型说明idint主键namevarchar(50)分类名如热菜、凉菜sortint排序权重数字越小越靠前statustinyint是否启用0禁用1启用菜品表dish字段类型说明idint主键category_idint所属分类外键关联category表namevarchar(100)菜品名称imagevarchar(255)菜品图片URLdescriptionvarchar(500)菜品描述pricedecimal(10,2)价格stockint库存-1表示不限salesint销量statustinyint上下架状态0下架1上架created_atdatetime创建时间桌台表table_info字段类型说明idint主键table_novarchar(20)桌号如A01seat_countint座位数qr_codevarchar(255)对应的二维码内容/URLstatustinyint0空闲 1占用订单表orders字段类型说明idint主键order_novarchar(32)订单编号全局唯一table_idint桌台IDtotal_amountdecimal(10,2)订单总金额statustinyint0待接单 1制作中 2已完成 3已取消remarkvarchar(255)顾客备注create_timedatetime下单时间update_timedatetime状态更新时间订单明细表order_item字段类型说明idint主键order_idint订单IDdish_idint菜品IDdish_namevarchar(100)菜名冗余存储防止菜品修改后影响历史订单dish_imagevarchar(255)菜品图片pricedecimal(10,2)下单时的单价快照quantityint数量subtotaldecimal(10,2)小计几个设计上的小心机我解释一下为什么这么做订单明细里面冗余了dish_name和dish_image。很多人会问为什么不直接关联菜品表等菜品下架或者改名之后你回头看历史订单会发现全是空数据这里冗余存储就是为了防止历史订单被影响。order_no的生成规则。我用的规则是时间戳 随机数 桌台号比如1696320000000 8371 A01拼接起来。这样在分布式环境下基本不会重复。decimal而不是float存价格。float有精度问题结算时0.10.2不等于0.3的悲剧会直接砸在点餐系统的营收数据上。3.3 Express路由划分与接口规范接口设计遵循RESTful风格我把它整理成以下清单。这份清单也是前后端联调时的核心契约。POST /api/admin/login管理员登录返回TokenGET /api/category/list获取启用的菜品分类POST /api/category/add新增分类管理员PUT /api/category/update修改分类管理员DELETE /api/category/:id删除分类管理员GET /api/dish/list获取菜品列表支持按分类筛选、按关键词搜索GET /api/dish/:id获取菜品详情POST /api/dish/add新增菜品管理员PUT /api/dish/update修改菜品管理员DELETE /api/dish/:id删除菜品管理员POST /api/dish/upload上传菜品图片POST /api/order/submit提交订单GET /api/order/list获取订单列表按桌台/状态筛选GET /api/order/detail/:orderNo订单详情PUT /api/order/status更新订单状态GET /api/stats/overview统计数据今日营收、订单量等GET /api/stats/hot-dishes热门菜品排行服务端返回的数据格式必须统一这对前端处理非常友好{ code: 200, message: success, data: { } }当出现业务异常时code返回非200比如401登录过期或未登录403没有操作权限404资源不存在500服务器内部错误这个规范看似简单但我看到过太多项目里接口返回体五花八门前端每个接口都要单独判断一次数据结构联调效率大打折扣。3.4 登录鉴权的具体实现管理端接口必须做鉴权不然谁都能调接口把菜品给删了。我用的是**JWTJSON Web Token**方案实现起来干净利落。// 引入jsonwebtoken const jwt require(jsonwebtoken); // 登录成功后签发Token有效期2小时 const token jwt.sign( { id: admin.id, username: admin.username, role: admin }, your_secret_key, // 生产环境务必换成环境变量别写死在代码里 { expiresIn: 2h } );然后在路由层加一个中间件凡是需要管理员权限的接口都要过这一关function verifyToken(req, res, next) { const authHeader req.headers[authorization]; const token authHeader authHeader.split( )[1]; // Bearer TOKEN if (!token) { return res.status(401).json({ code: 401, message: 未登录请先登录 }); } try { const decoded jwt.verify(token, your_secret_key); req.admin decoded; // 把解析出的用户信息挂到req上 next(); } catch (err) { return res.status(401).json({ code: 401, message: 登录状态已失效请重新登录 }); } }前端拿到Token之后存储到localStorage在Axios拦截器里统一带上Authorization: Bearer ${token}这样每个请求都会自动携带凭证。3.5 菜品图片上传功能点餐系统没有菜品图片是没法看的。图片上传这块我用的是Multer中间件存储位置放在后端的public/uploads目录下上传完返回一个可访问的URL。const multer require(multer); const path require(path); const storage multer.diskStorage({ destination: function (req, file, cb) { cb(null, path.join(__dirname, ../public/uploads/)); }, filename: function (req, file, cb) { const uniqueName Date.now() - Math.round(Math.random() * 1e9); const ext path.extname(file.originalname); cb(null, uniqueName ext); } }); const upload multer({ storage: storage, limits: { fileSize: 5 * 1024 * 1024 }, // 限制5MB fileFilter: function (req, file, cb) { const allowedTypes /jpeg|jpg|png|gif|webp/; const extname allowedTypes.test(path.extname(file.originalname).toLowerCase()); const mimetype allowedTypes.test(file.mimetype); if (mimetype extname) { return cb(null, true); } else { cb(new Error(只支持图片格式上传)); } } });注意事项文件名一定要重新生成不要用用户上传的原始文件名原因有两个一是避免文件名重复导致覆盖二是在Linux环境下特殊字符可能导致安全问题。4. 前端核心交互Vue从页面到状态管理4.1 用Vue Router做页面路由设计前端页面我分了两个入口区点餐端和管理端。从路由设计上看const routes [ // 点餐端 { path: /, name: Home, component: HomeView }, { path: /menu, name: Menu, component: MenuView }, { path: /cart, name: Cart, component: CartView }, { path: /order/:orderNo, name: OrderDetail, component: OrderDetailView }, // 管理端 { path: /admin/login, name: AdminLogin, component: AdminLoginView }, { path: /admin, component: AdminLayout, meta: { requiresAuth: true }, children: [ { path: dashboard, name: Dashboard, component: DashboardView }, { path: dishes, name: DishManage, component: DishManageView }, { path: categories, name: CategoryManage, component: CategoryManageView }, { path: orders, name: OrderManage, component: OrderManageView }, { path: tables, name: TableManage, component: TableManageView }, ]} ];路由守卫这块值得多说一句。管理端的每一个页面都应该加meta: { requiresAuth: true }然后在全局前置守卫里判断router.beforeEach((to, from, next) { const token localStorage.getItem(admin_token); if (to.meta.requiresAuth !token) { next(/admin/login); } else { next(); } });也就是说没登录的人想直接访问/admin/dashboard会被拦下来踢回登录页。这个环节对管理系统的安全至关重要别偷懒省略。4.2 购物车与订单提交流程的设计用户点餐的核心链路是浏览菜单 - 加购 - 查看购物车 - 填写桌号 - 提交订单 - 查看状态。购物车的数据状态我放在Vuex里管理。你可能会问为什么不直接存在组件里因为购物车的状态在菜单页、购物车页、订单确认页都可能用到如果每个页面各存一份数据同步就会非常酸爽。放全局状态里所有组件都从store读取加购、减购只操作store数据源唯一不会乱。购物车的state结构长这样state: { cartList: [], // [{ dishId, dishName, price, image, quantity, stock }] activeTableId: null }, getters: { totalQuantity: (state) { return state.cartList.reduce((total, item) total item.quantity, 0); }, totalAmount: (state) { return state.cartList.reduce((total, item) total item.price * item.quantity, 0); } }, mutations: { addToCart(state, dish) { const existing state.cartList.find(item item.dishId dish.id); if (existing) { if (existing.quantity dish.stock) existing.quantity; } else { state.cartList.push({ ...dish, quantity: 1 }); } }, reduceFromCart(state, dishId) { const index state.cartList.findIndex(item item.dishId dishId); if (index ! -1) { state.cartList[index].quantity--; if (state.cartList[index].quantity 0) { state.cartList.splice(index, 1); } } }, clearCart(state) { state.cartList []; } }提交订单时先判断购物车是否为空、桌台是否已选择然后把订单数据和明细一起发给后端。这里我用的是事务操作防止出现订单主表创建成功但明细插入失败的情况——不然这单就等于白下但顾客的钱已经付出去了。4.3 菜品展示与管理表格的实现小事菜单页的布局我做的是左侧分类、右侧菜品列表的方式类似外卖App的样式。点分类右边滚动到对应区块用el-scrollbar做滚动体验还行。菜品卡片用el-card包裹左侧是图片右侧是名称、月售、价格右下角有一个加号按钮点击直接加购。管理端的菜品表格我用的是Element Plus的el-table配合el-dialog做新增/编辑的浮窗表单。这里有几个细节要留意菜品图片用el-upload组件上传成功后把返回的URL赋值到表单的image字段。价格的输入框要做精度校验避免用户输入3.999这样的值。校验规则用/^\d(\.\d{1,2})?$/。上下架操作直接用el-switch绑定状态改变立刻调接口不用等提交按钮体验会好很多。我在实际做这个项目的时候还发现一个高频问题上传图片后刷新页面图片404。排查到最后基本是路径问题——上传返回的路径是相对路径部署到服务器后域名和端口变了图片就找不到了。我建议保存完整的绝对URL或者在响应时动态拼接req.protocol :// req.get(host) 路径这样才不会因为环境变化导致图片丢失。5. 前后端联调跨域、Axios封装与问题排查5.1 Axios统一封装的艺术前端所有HTTP请求我都走一个统一封装的Axios实例好处是拦截器只需写一遍所有接口都自动生效。直接贴核心代码import axios from axios; import { ElMessage } from element-plus; import router from /router; const service axios.create({ baseURL: process.env.VUE_APP_BASE_API || http://localhost:3000/api, timeout: 15000 }); // 请求拦截器自动带上Token service.interceptors.request.use( config { const token localStorage.getItem(admin_token); if (token) { config.headers[Authorization] Bearer token; } return config; }, error Promise.reject(error) ); // 响应拦截器统一处理错误提示 service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { ElMessage.error(res.message || 请求异常); if (res.code 401) { localStorage.removeItem(admin_token); router.push(/admin/login); } return Promise.reject(new Error(res.message || Error)); } return res; }, error { ElMessage.error(error.message || 服务器内部错误); return Promise.reject(error); } ); export default service;有了这个封装页面组件里写接口就非常优雅了import request from /utils/request; export function getDishList(params) { return request({ url: /dish/list, method: get, params }); }5.2 跨域问题为什么前端请求总是失败前后端分离项目最常见的一个问题就是跨域。你在Vue开发服务器上跑前端默认端口8080后端Node跑在3000端口浏览器发现端口不同就会触发同源策略拦截。打开浏览器F12看到这类报错Access to XMLHttpRequest at http://localhost:3000/api/dish/list from origin http://localhost:8080 has been blocked by CORS policy基本就是跨域了。解决方式有两种后端开启CORS推荐。在Express里加一个中间件app.use(function(req, res, next) { res.header(Access-Control-Allow-Origin, *); res.header(Access-Control-Allow-Headers, Content-Type, Authorization); res.header(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); if (req.method OPTIONS) { return res.sendStatus(200); } next(); });前端用代理。在vue.config.js里配置devServermodule.exports { devServer: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } };这两种方式各有适用场景。开发阶段用代理速度最快也不用动后端代码但上线后前后端往往不在同一个端口甚至同一台机器上所以后端CORS是一个必须学会的兜底方案。5.3 联调时最容易踩的坑我把联调阶段的高频问题整理成了一张速查表这些问题我在做这个系统时几乎全碰过问题现象原因分析解决方案接口返回404前端请求路径和后端路由不匹配检查baseURL和path拼接重点看/api前缀是否一致列表数据能加载但图片全裂图片路径是相对路径域对不上后端返回拼接完整URL登录后刷新页面又跳到登录页页面刷新后Vuex数据丢失结合localStorage持久化token提交订单成功但订单列表查不到事务没提交或订单状态默认值不对检查orders表的status默认值查看数据库实际记录前端改了代码但界面没变浏览器缓存了旧的JS清理浏览器缓存或用无痕模式接口请求特别慢可能是N1查询问题Sequelize开启日志检查SQL执行次数菜品删除失败有订单明细还在引用这个菜品改成软删除逻辑删除或删除前先校验5.4 联调时独家的几个建议联调阶段最容易忽略的一件事是协商好日期时间的格式。后端存的是datetime类型返回给前端是JavaScript Date对象还能接受但一旦变成字符串比如2024-10-06T12:30:00.000Z前端展示时如果不做格式化用户看到的就是一串看不懂的UTC时间。我通常在后端就直接格式化好YYYY-MM-DD HH:mm:ss或者前端用dayjs统一处理。不要指望MySQL自己转化你用的是Sequelize它默认返回的Date对象序列化后是ISO字符串前端展示前必须转换。联调的另一件事是统一HTTP状态码和业务状态码。有的后端把401直接返回HTTP 401有的返回HTTP 200 code 401这两种风格会导致前端拦截器写法完全不同。我建议别用HTTP状态码来表达业务语义统一用HTTP 200 body里code字段来表达这样一个普普通通的HTTP状态码200不代表业务成功必须看body里的code。这样前端处理逻辑非常统一很难出错。6. 打包部署与上线流程6.1 前端打包与访问路径配置开发完进入部署阶段。前端执行打包命令npm run build打包完成后会生成dist目录里面是纯静态文件HTML/CSS/JS。这里容易踩的坑是如果你的项目部署在服务器的二级路径下比如http://yourdomain.com/restaurant/那必须改两个地方在vue.config.js里设置publicPath: /restaurant/。在router的createWebHistory处改为createWebHistory(/restaurant/)。如果不改你会发现打包后页面白屏控制台报资源加载404。很多初学者在这个问题上卡很久其实根源只是资源路径的问题。6.2 后端部署到服务器Node后端部署相对简单把代码传到服务器上执行npm install --production然后用PM2守护进程npm install -g pm2 pm2 start app.js --name restaurant-backend pm2 save pm2 startup # 设置开机自启PM2的好处是进程崩溃后会自动重启服务器重启后也能自动拉起服务省心很多。日志输出用pm2 logs查看排错就靠它了。6.3 用Nginx做反向代理和静态文件服务部署架构通常是这样一台服务器上Nginx占据80端口负责两件事托管前端dist目录的静态文件。把以/api开头的请求反向代理到Node服务监听3000端口。Nginx配置示例server { listen 80; server_name your-domain.com; # 前端静态文件 root /var/www/restaurant/dist; index index.html; # 防止刷新404所有非静态资源请求都返回到index.html location / { try_files $uri $uri/ /index.html; } # API反向代理 location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }注意try_files $uri $uri/ /index.html;这一行如果漏掉刷新子页面会直接404因为Vue是单页应用路由是前端控制的Nginx层面根本没有对应文件。6.4 上线前的最后一轮安全检查部署完先别急着宣传还有几件事必须做改掉默认密钥。JWT的secret、数据库密码全部从代码里挪到环境变量不要硬编码在业务代码里。数据库备份。写一个简单的crontab定时任务每天凌晨备份一次MySQL数据。HTTPS。现在免费的证书申请很简单能上HTTPS就别用HTTP裸奔涉及到登录密码的传输必须加密。我用的是certbot一条命令搞定证书申请和Nginx配置整个过程不到五分钟。7. 常见问题与排查技巧实录最后总结一下整个开发过程中最常遇到的几个疑难杂症。这些问题有些是环境问题有些是代码层面逻辑疏忽我按照实际踩坑频率排个序分享出来希望能帮大家省些调试时间。7.1 npm安装依赖时各种报错最典型的是安装卡在idealTree或者下载失败这通常和网络环境有关。可以先检查npm源是否配置好再尝试清缓存npm cache clean --force rm -rf node_modules # Windows上是删除node_modules文件夹 npm install --registryhttps://registry.npmmirror.com还有一类报错是ERESOLVE这是npm 7版本依赖冲突检测更严格导致的如果你觉得冲突并不影响使用可以在安装时加--legacy-peer-deps参数绕过。但注意这只是权宜之计如果项目能升级依赖版本还是尽量从根源上解决。7.2 Vue打包后布局异常热搜词里有一个vue 打包后 布局异常这个我太有发言权了。开发环境一切正常一打包上线发现页面错位、样式全丢了。大部分原因是样式文件加载顺序或者CSS的路径问题。解决方法就是前面提到的publicPath配置。如果用了多级路由和懒加载还要检查打包产物里是否有chunk文件名重复的情况这会导致浏览器加载到错误的代码块。另一个原因是Element Plus的样式引用了图标字体字体文件需要正确被拷贝到dist目录。这个通常在Vue CLI的配置里是自动处理的但如果手动改了public/index.html引入方式就可能出问题。7.3 订单状态不同步问题点餐系统里前端页面显示的订单状态和后端实际状态不一致这是个业务逻辑bug高发区。我排查这类问题的思路是先看数据库表里的实际状态是什么。再看后端的更新逻辑是否执行成功。最后看前端缓存的数据是不是没刷新。如果是多端同时操作比如管理端改了订单状态顾客端页面还停留在旧状态最简单的方案是前端定时轮询接口比如每30秒拉一次最新状态。进阶做法是用WebSocket做实时推送但这个系统的场景里轮询完全够用没必要增加复杂度。7.4 Node.js服务莫名其妙的崩溃服务刚上线时连续崩过几次排查后发现几个常见原因未捕获的promise异常。比如数据库连接暂时断掉查询的promise直接reject又没写catch进程就挂了。解决方案是全局加一个process.on(unhandledRejection)兜底。内存泄漏。在某些版本的Express里如果配置了不合理的session存储或者缓存机制内存占用会缓慢上涨最终OOM。用pm2 restart暂时缓解长期方案是排查清楚哪个变量被常驻引用。8. 项目后续还能怎么升级系统做完之后如果你不满足于此有几条很自然的升级路径微服务化拆解。登录鉴权、菜品管理、订单模块完全可以拆成独立服务用Docker做容器化编排。这是从单体往分布式方向迈进的必修课。接入微信小程序端。餐厅点餐最自然的场景其实是扫码进小程序点餐这个需求和H5端几乎没有差别只需要把Vue的代码迁移一部分到小程序框架uni-app / Taro后端接口完全不用动。如果我把这个系统继续做下去小程序端一定是第一步。数据可视化增强。管理端目前只有基础数据统计如果接入ECharts做营收趋势图、热销菜品饼图、客流情况折线图整个系统的专业度会立刻上一个台阶。订单消息实时推送。用Socket.IO实现服务端和客户端的长连接顾客下单后后厨大屏实时弹出新订单提醒也不用人工刷新了。这些升级方向结合你实际的项目基础和时间精力选一两个做得深入一点含金量会更高。回到最初这个项目本身Node.js Vue这套组合是我个人非常推荐的全栈入门路线。你不需要学习两套完全不同的语言体系JavaScript一统前后端调试链路上少了很多心智负担。做这个点餐系统的过程中我踩过的每一个坑每一次查资料每一次调通一个接口都是实打实的能力提升。这类项目的乐趣就在于此——它不是一个只会在简历上出现的名词而是一个你真真切切能跑通、能上线、能被真实用户使用的完整系统。后面如果你想深入交流某个模块的细节尤其是数据库设计或者部署踩坑的部分随时可以在评论区聊。
返回列表