ARTICLE DETAIL

资讯详情

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

从零散代码到完整系统:基于Vue与Node.js的超市管理系统重构实战

从零散代码到完整系统:基于Vue与Node.js的超市管理系统重构实战 简介本资源是一套基于HTML技术实现的轻量级超市管理系统前端工程面向Web开发初学者与小型零售场景信息化实践者聚焦产品管理、员工信息维护等核心业务的网页化呈现与交互。压缩包共468个文件含19个HTML页面构成系统主界面框架16个CSS文件如Admin.css、skin.css等负责多模块样式定制11个JavaScript脚本支撑表单验证与动态交互7个数据库文件.db提供本地数据存储基础辅以大量图片资源377个GIF、13个JPG等增强可视化效果整体仅1.85MB便于快速部署与学习。已有77人下载学习资源结构清晰涵盖登录页、商品列表、库存查询、员工信息表等典型模块CSS命名规范如CssAdmin.css、静态资源分类明确适合作为HTMLCSSJS综合实训案例帮助开发者理解超市管理系统的前端组织逻辑与常见UI模式。1. 项目概述从一份压缩包到一套可用的超市管理系统最近在整理旧硬盘时翻到了一个名为chaoshi.rar的压缩包。解压一看里面是一堆零散的 HTML、CSS、JavaScript 文件还有一个简单的数据库脚本。这显然是一个多年前的“超市管理系统”前端项目但代码结构混乱功能不完整更像是一个课程设计或练手作品的半成品。对于很多刚入行的前端开发者或者需要快速搭建一个内部管理工具的小型超市经营者来说手头可能就有类似的“遗产代码”。它们有价值但直接使用又问题重重。这个项目就是要把这个“压缩包遗产”变成一个结构清晰、功能完整、真正能跑起来的超市管理后台。它适合有一定 HTML/CSS/JS 基础想通过一个完整项目提升工程化能力和全栈思维的朋友也适合需要低成本信息化解决方案的个体户参考。核心价值在于我们不从零开始造轮子而是学习如何“修复”、“重构”和“增强”一个现有项目这是工作中更常遇到的真实场景。2. 系统核心需求与功能模块拆解一个可用的超市管理系统远不止是商品列表的增删改查。我们需要从业务出发倒推技术实现。基于chaoshi.rar中零散文件透露的信息和超市实际运营我们可以梳理出以下几大核心模块。2.1 商品管理进销存的基石这是系统的核心。原始代码可能只有一个简单的表格展示商品名称和价格。我们需要扩展为完整的商品档案包括唯一商品编码条码、商品名称、分类如食品、日用品、规格、单位、进货价、零售价、库存数量、库存预警下限、供应商信息、商品图片等。其中商品编码的管理是关键它关系到后续的采购、销售和盘点。功能上需实现商品信息的增、删、改、查、批量导入通过Excel、以及根据库存预警自动提示补货。2.2 采购与入库管理控制成本源头当商品库存低于预警线或需要引进新品时就涉及采购。这个模块需要管理采购订单包含订单号、供应商、采购员、采购日期、采购商品明细商品、数量、单价、金额、订单总金额、状态待审核、已入库、已取消。采购订单审核通过后触发入库操作此时系统需要自动更新对应商品的库存数量。原始压缩包里很可能缺少这个完整的流程我们需要设计前后端交互来模拟“创建订单-审核-入库”的状态流转。2.3 销售与收银管理前台业务核心这是直接产生效益的环节。一个基本的销售界面收银台需要快速商品录入支持扫码枪输入商品编码或手工选择、实时计算合计金额、选择支付方式现金、移动支付、会员卡、打印小票前端生成打印格式、完成销售并更新库存。销售数据应自动生成销售单记录流水号、销售时间、收银员、商品明细、实收金额、找零等信息。原始代码可能只有一个简单的购物车计算我们需要将其强化为一个完整的销售流程。2.4 库存与盘点管理保障账实相符除了随采购入库和销售出库自动更新库存外还需要独立的库存盘点功能。因为可能存在商品损耗、盗窃或录入错误导致系统库存与实际库存不符。盘点功能允许管理员或仓管员按货架或分类生成盘点任务实地清点后将实际数量录入系统系统自动计算盈亏并生成盘点报告经确认后调整系统库存。这是一个典型的“计划-执行-核对”流程。2.5 会员与促销管理提升客户粘性对于超市会员体系很重要。需要会员管理功能注册、发卡关联会员号、积分累计与查询、积分兑换、会员等级折扣等。促销管理则可以设置限时折扣、满减活动、组合优惠等。这些规则需要在销售收银时实时生效。原始项目大概率没有这部分这是重要的增值功能点。2.6 数据统计与报表辅助经营决策管理者需要看数据。基础报表包括日/月销售报表按时间、按商品、按收银员、商品销售排行榜、库存分析报表哪些商品滞销、哪些快断货、毛利分析报表等。前端需要提供丰富的图表如ECharts来可视化这些数据后端则需要编写复杂的查询语句来汇总数据。3. 技术栈选型与前后端架构设计面对一堆原始的 HTML/JS 文件直接在上面修修补补会陷入泥潭。正确的做法是基于现代前端工程化思想进行重构并为其搭配一个轻量级但健壮的后端。3.1 前端重构从散装文件到工程化项目原始文件通常是index.html,goods.html,style.css,main.js这种平铺直叙的结构。我们将对其进行彻底改造。技术栈选择Vue.js Element Plus Axios Vue Router为什么选Vue.js对于从传统 jQuery/原生JS项目重构的场景Vue的渐进式特性非常友好。我们可以先将一个页面如商品列表用Vue组件化逐步替换平滑迁移。其数据驱动视图的模式能极大简化商品列表渲染、表单绑定等复杂操作。为什么选Element Plus作为一套成熟的UI组件库它提供了表格、表单、对话框、消息提示等后台管理系统必需的组件能让我们快速搭建出美观且交互一致的界面把精力集中在业务逻辑而非样式调试上。Axios用于处理所有HTTP请求与后端API通信。Vue Router管理前端路由实现单页面应用SPA体验让不同功能模块商品、销售、报表之间的切换无需刷新页面。项目结构重构chaoshi-management/ ├── public/ # 静态资源旧项目图片等可放这里 ├── src/ │ ├── api/ # 所有后端API接口封装如 goods.js, order.js │ ├── assets/ # 样式、图片等资源 │ ├── components/ # 公共组件如SearchBar, Pagination │ ├── router/ # 路由配置 │ ├── store/ # Vuex/Pinia状态管理管理用户登录态等 │ ├── utils/ # 工具函数如日期格式化、金额计算 │ ├── views/ # 页面级组件 │ │ ├── Goods/ # 商品管理页 │ │ ├── Purchase/ # 采购管理页 │ │ ├── Sale/ # 销售收银页 │ │ └── Report/ # 报表页 │ ├── App.vue │ └── main.js ├── package.json └── vite.config.js # 构建工具配置推荐Vite速度快通过这样的结构我们将原始散乱的文件按功能模块组织起来清晰且易于维护。3.2 后端搭建提供数据与业务逻辑支撑前端需要数据我们需要一个后端。考虑到快速开发和与前端JS的亲和度Node.js是绝佳选择。技术栈选择Express.js Sequelize MySQLExpress.js:Node.js最流行的Web框架轻量且灵活能快速搭建RESTful API。Sequelize:一个强大的ORM对象关系映射库它允许我们使用JavaScript类和对象来操作数据库无需编写复杂的SQL语句提高开发效率和代码可读性。MySQL:关系型数据库适合存储结构化的商品、订单、会员数据。相比原始项目中可能使用的简单文件存储或LocalStorageMySQL在数据一致性、并发处理和查询能力上有着不可比拟的优势。核心API设计示例RESTful风格GET /api/goods- 获取商品列表支持分页、筛选、排序POST /api/goods- 创建新商品PUT /api/goods/:id- 更新商品信息DELETE /api/goods/:id- 删除商品GET /api/purchase-orders- 获取采购单列表POST /api/purchase-orders- 创建采购单POST /api/purchase-orders/:id/receive- 执行采购单入库POST /api/sales- 创建销售单收银GET /api/reports/daily-sales- 获取日销售报表3.3 数据库设计规划表结构根据功能模块我们需要设计以下几张核心表商品表 (goods):id, barcode, name, category_id, unit, purchase_price, retail_price, stock_quantity, alert_quantity, supplier, image_url, ...商品分类表 (categories):id, name, parent_id支持多级分类供应商表 (suppliers):id, name, contact, phone, ...采购订单表 (purchase_orders):id, order_no, supplier_id, operator_id, total_amount, status, created_at采购订单明细表 (purchase_order_items):id, order_id, goods_id, quantity, unit_price, subtotal销售单表 (sales):id, sale_no, operator_id, total_amount, payment_method, received_amount, change, created_at销售单明细表 (sale_items):id, sale_id, goods_id, quantity, unit_price, subtotal库存流水表 (inventory_logs):id, goods_id, change_quantity, type (purchase/sale/adjust), related_id, created_at用于追溯每一次库存变动会员表 (members):id, card_no, name, phone, points, ...用户表 (users):id, username, password_hash, role (admin/cashier/warehouse), ...注意库存数量不应只存在于goods表。任何库存变动采购入库、销售出库、盘点调整都应通过事务操作goods.stock_quantity并记录一条inventory_logs。这是保证数据一致性的关键避免直接UPDATE导致数据丢失或错误。4. 核心功能模块的详细实现与避坑指南有了架构和设计我们来深入几个核心模块看看具体如何实现以及会遇到哪些“坑”。4.1 商品管理模块的实现细节前端Goods.vue组件主要包含一个基于 Element Plusel-table的商品列表以及添加、编辑、删除商品的对话框。列表与查询// 在 Vue 组件中 import { getGoodsList } from /api/goods; data() { return { goodsList: [], queryParams: { name: , categoryId: undefined, page: 1, size: 10 }, total: 0 }; }, methods: { async fetchData() { const res await getGoodsList(this.queryParams); this.goodsList res.data.items; this.total res.data.total; } }后端对应的API需要处理复杂的查询// Express 路由处理函数 async function getGoodsList(req, res) { const { name, categoryId, page 1, size 10 } req.query; const whereCondition {}; if (name) whereCondition.name { [Op.like]: %${name}% }; // 模糊查询 if (categoryId) whereCondition.categoryId categoryId; const { count, rows } await Goods.findAndCountAll({ where: whereCondition, limit: parseInt(size), offset: (parseInt(page) - 1) * parseInt(size), include: [{ model: Category }], // 关联查询分类名称 order: [[createdAt, DESC]] }); res.json({ success: true, data: { items: rows, total: count } }); }避坑指南1分页与性能坑当商品数据量很大时findAndCountAll中的count操作会扫描全表可能很慢。解决对于百万级数据可以考虑更复杂的分页策略如“游标分页”或者对常用的筛选条件如分类建立索引。在初期确保where条件中的字段如category_id有数据库索引是性价比最高的优化。避坑指南2商品删除的逻辑删除坑直接物理删除商品记录会导致历史采购单、销售单中的商品信息丢失无法对账。解决在商品表中增加一个is_deleted字段布尔值执行删除操作时只是标记删除而非真正删除记录。前端查询列表时默认过滤掉已标记删除的商品。这就是“逻辑删除”。4.2 销售收银模块的实现与并发控制收银界面是交互最复杂的部分。我们需要一个实时响应的“购物车”。前端购物车状态管理使用 Vuex 或 Pinia 来管理全局的购物车状态是合适的因为收银数据可能在多个子组件间共享如商品列表、购物车清单、金额汇总。// 使用 Pinia store export const useCartStore defineStore(cart, { state: () ({ items: [], // 每一项{ id, barcode, name, price, quantity, subtotal } total: 0 }), actions: { addItem(goods) { const existing this.items.find(item item.id goods.id); if (existing) { existing.quantity 1; existing.subtotal existing.price * existing.quantity; } else { this.items.push({ ...goods, quantity: 1, subtotal: goods.price }); } this.calculateTotal(); }, calculateTotal() { this.total this.items.reduce((sum, item) sum item.subtotal, 0); }, async checkout(paymentInfo) { // 提交销售数据到后端 const saleData { items: this.items, total: this.total, ...paymentInfo }; await createSale(saleData); // 调用API // 清空购物车 this.$reset(); } } });后端销售API与库存扣减的原子性这是整个系统最关键的并发安全点。想象两个收银台同时卖出最后一瓶可乐。// 错误的做法先查询再计算最后更新 const goods await Goods.findByPk(item.goodsId); if (goods.stock_quantity item.quantity) { throw new Error(库存不足); } goods.stock_quantity - item.quantity; await goods.save(); // 在高并发下两个请求可能同时读到相同的stock_quantity都判断为足够导致超卖。 // 正确的做法使用数据库事务和原子操作 const t await sequelize.transaction(); // 开启事务 try { for (const item of saleItems) { // 使用原子操作直接递减库存并在递减时检查 const [affectedRows] await Goods.update( { stock_quantity: sequelize.literal(stock_quantity - ${item.quantity}) }, { where: { id: item.goodsId, stock_quantity: { [Op.gte]: item.quantity } // 保证库存充足才更新 }, transaction: t } ); if (affectedRows 0) { // 更新失败说明库存不足或商品不存在 throw new Error(商品 ${item.goodsId} 库存不足或不存在); } // 记录库存流水... } // 创建销售单记录... await t.commit(); // 所有操作成功提交事务 } catch (error) { await t.rollback(); // 任何一步失败回滚所有操作 throw error; }核心要点必须使用数据库事务来确保“扣减库存”和“创建销售单”要么一起成功要么一起失败。并且扣减库存必须在一条SQL语句中完成原子操作利用where条件中的stock_quantity ?来防止超卖。这是实现高并发下数据一致性的黄金法则。4.3 报表模块的数据聚合与性能报表查询往往涉及大量数据的聚合SUM,GROUP BY直接在前端或通过ORM进行多次查询拼接性能会非常差。优化策略后端聚合前端展示所有复杂的统计逻辑都在后端完成前端只负责接收格式化好的数据和渲染图表。编写原生SQL或复杂查询对于多表关联和分组统计Sequelize提供的简单方法可能不够优化。此时可以直接编写原生SQL查询或者使用Sequelize的query方法。建立数据库视图或定时任务对于实时性要求不高的日报、月报可以建立数据库视图或者通过定时任务在凌晨计算好结果存入“报表汇总表”前端查询时直接读汇总表速度极快。合理使用索引在sales.created_at,goods_id,category_id等常用于筛选和分组的字段上建立索引能极大提升报表查询速度。示例日销售报表API// 后端使用原生SQL进行高效聚合 const sql SELECT DATE(s.created_at) as sale_date, SUM(si.quantity) as total_quantity, SUM(si.subtotal) as total_amount, COUNT(DISTINCT s.id) as order_count FROM sales s JOIN sale_items si ON s.id si.sale_id WHERE s.created_at BETWEEN ? AND ? GROUP BY DATE(s.created_at) ORDER BY sale_date DESC ; const [results] await sequelize.query(sql, { replacements: [startDate, endDate], type: sequelize.QueryTypes.SELECT }); res.json({ success: true, data: results });5. 项目部署与后期维护建议将开发好的系统真正用起来还需要最后几步。5.1 前端构建与部署使用Vite或Webpack将Vue项目打包成静态文件dist目录。npm run build生成的dist文件夹里就是所有HTML、CSS、JS文件。你可以放到静态服务器如Nginx、Apache。只需将服务器根目录指向dist并配置一个简单的重写规则让所有前端路由都回退到index.html用于支持Vue Router的history模式。与后端服务集成在Express后端可以设置一个静态文件中间件来托管dist目录。const express require(express); const path require(path); const app express(); // 先托管静态文件 app.use(express.static(path.join(__dirname, dist))); // 然后是你的API路由 app.use(/api, apiRouter); // 最后所有未匹配的请求都返回前端入口文件由前端路由处理 app.get(*, (req, res) { res.sendFile(path.join(__dirname, dist, index.html)); });5.2 后端服务部署推荐使用PM2来管理Node.js进程它提供了进程守护、日志管理、监控等功能。# 全局安装PM2 npm install pm2 -g # 在项目根目录启动你的Express应用 pm2 start app.js --name chaoshi-backend # 设置开机自启 pm2 startup pm2 save将你的服务部署在云服务器上并配置好域名和HTTPS可以使用Let‘s Encrypt免费证书。5.3 数据备份与安全数据库定期备份使用mysqldump命令或云数据库的自动备份功能每天备份数据到远程存储。API安全身份验证使用JWTJSON Web Token。用户登录后后端签发一个有时效的Token前端将其存储在localStorage或Cookie中并在后续请求的Header中携带Authorization: Bearer token。权限控制根据用户角色role进行接口访问控制。例如只有admin角色才能访问用户管理、报表导出等接口。输入验证对所有用户输入进行严格的验证和清理防止SQL注入和XSS攻击。可以使用joi或express-validator库。环境变量数据库密码、JWT密钥等敏感信息绝不能硬编码在代码里。使用.env文件管理并通过dotenv包加载。从解压一个陈旧的chaoshi.rar开始到部署一个完整的、可用的超市管理系统这个过程远比从零开始写一个“玩具项目”更有挑战也更有价值。它强迫你去思考数据库设计、API规范、状态管理、并发安全和性能优化这些工程实际问题。最大的体会是业务逻辑的严谨性往往比炫技的技术更重要。比如库存扣减的那个事务看似简单却是保证系统不“错乱”的基石。另一个心得是不要试图在第一个版本就做出完美系统。先基于核心流程商品、销售跑通闭环再迭代加入采购、会员、报表等模块边用边改才是可持续的开发节奏。最后给旧项目“换新装”时重构的勇气和保留核心价值的判断力需要反复权衡。本文还有配套的精品资源点击获取
返回列表