尧图精选

基于Electron+Vue3+IndexedDB构建单机版超市管理系统实战

🕒 发布时间:2026/9/5 12:37:04 📁 来源:尧图网络
简介本资源是一套基于HTML技术构建的轻量级超市管理系统前端实现方案面向Web开发初学者、高职院校信息管理专业学生及小型商超信息化改造需求者聚焦产品录入、库存展示与员工信息管理等核心业务场景。压缩包共468个文件含19个HTML页面构成系统主界面框架16个CSS文件如Admin.css、skin.css等负责多主题样式控制11个JavaScript脚本实现表单验证与交互逻辑另有377个GIF动图用于界面提示与加载反馈以及7个数据库文件支撑本地数据模拟整体体积仅1.85MB便于快速部署与学习调试。目前已有77人下载学习资源结构清晰涵盖登录页、商品管理页、员工信息表单、库存统计视图等完整模块配套CSS与JS已按功能分层组织适合用于理解前后端分离雏形、练习HTMLCSSJS协同开发或作为课程设计原型快速二次开发。1. 项目缘起一个被遗忘的“超市管理”压缩包前几天在整理一个老旧的移动硬盘时我翻到了一个名为chaoshi.rar的压缩文件。文件名直白得有点可爱——“超市”后缀还带着一个“管理”。解压开来里面是一个典型的、十几年前风格的静态HTML网站项目。没有数据库没有后端所有的商品信息、订单记录都硬编码在HTML和零散的JavaScript里。这瞬间把我拉回了Web开发的“上古时代”那时候做个管理系统可能真的就是一个前端页面加一堆写死的表格数据。这个项目本身的技术栈在今天看来已经过时但它背后所代表的“小型实体超市信息化管理”的需求却从未消失。现在依然有大量的小超市、便利店、夫妻店他们不需要复杂的ERP负担不起SaaS的年费但确实需要一个简单、直观、能管理商品、收银和库存的工具。chaoshi.rar就像是一个时间胶囊封装了那个时代最朴素的需求和最直接的解决方案。我决定以这个“考古发现”为引子结合现代最轻量、最易上手的技术栈重新设计和实现一个真正能为小微超市所用的单机版管理系统。我们的目标不是做一个功能大而全的“航母”而是打造一把锋利、趁手的“瑞士军刀”——零部署、打开即用、数据本地存储、界面直观。2. 技术选型为什么是Electron Vue3 IndexedDB面对这个需求技术选型是第一步也是最关键的一步。我们需要一个桌面应用来保证数据的安全性和操作的独立性避免网络依赖同时又要具备现代Web开发的效率和体验。经过权衡我选择了Electron Vue3 IndexedDB这个组合理由如下2.1 为什么选择Electron作为应用外壳核心需求是“单机版”和“打开即用”。传统的Web应用需要服务器和浏览器而传统的桌面应用开发如C#、Java对于前端开发者来说门槛较高。Electron完美地解决了这个问题。它允许我们使用熟悉的HTML、CSS和JavaScript来构建跨平台的桌面应用程序。最终打包出来的就是一个.exeWindows或.dmgMac文件用户双击即可运行无需安装运行环境体验上与普通软件无异。这比要求用户打开浏览器、输入本地服务器地址要友好得多。2.2 为什么选择Vue3作为前端框架Vue3的组合式API和响应式系统对于开发数据密集型的后台管理系统简直是绝配。商品列表、库存变动、订单生成这些都需要界面和数据保持实时同步。Vue3的ref、reactive以及computed属性让状态管理变得异常清晰。同时基于Vue3的UI组件库如Element Plus、Naive UI提供了丰富、美观且成熟的表格、表单、弹窗组件能极大提升开发效率让我们专注于业务逻辑而非样式调整。2.3 为什么选择IndexedDB作为本地数据库这是与传统chaoshi.rar项目最大的区别也是现代方案的核心优势。过去的数据是写死在HTML里的无法持久化、无法查询。我们需要一个能在浏览器环境Electron内置了Chromium中运行的、真正的数据库。对比其他方案LocalStorage/SessionStorage 容量太小通常5-10MB且仅支持字符串键值对不适合存储结构化的商品和订单数据。Web SQL 已被W3C废弃不再推荐。SQLite通过Node.js集成 功能强大但需要在Electron中集成Node原生模块增加了打包复杂度且对于纯前端开发者有一定学习成本。IndexedDB是一个内置在浏览器中的、事务型的、NoSQL数据库。它支持存储大量结构化数据远大于LocalStorage提供索引以实现高性能查询完全异步操作不阻塞界面。对于一个小型超市管理系统存储几万条商品和订单记录绰绰有余。最重要的是它无需任何额外依赖纯前端API即可操作学习曲线平缓。注意 IndexedDB的API是出了名的“丑陋”和基于事件的。在实际开发中强烈推荐使用一个封装库如Dexie.js。它提供了类似ORM的、Promise风格的API能让操作IndexedDB的代码变得简洁易懂。3. 核心功能模块设计与实现拆解一个可用的超市管理系统至少需要四大核心模块商品管理、库存管理、收银结算、数据统计。下面我们逐一拆解其设计与关键实现。3.1 商品管理模块CRUD是基础分类与条码是灵魂商品管理是所有操作的基础。我们需要一个清晰的表单来添加商品一个强大的表格来展示和搜索商品。数据库表设计IndexedDB ObjectStore// 使用Dexie.js定义数据库 db.version(1).stores({ goods: id, barcode, name, category, purchasePrice, retailPrice, stock, alertStock, createdAt, // 表示唯一索引 categories: id, name, // ... 其他表 });id: 自增主键。barcode: 商品条码建立唯一索引这是防止商品重复录入的关键。category: 关联分类ID。purchasePrice/retailPrice: 进价和售价用于计算毛利。alertStock: 库存预警值当stock低于此值时在界面上高亮提示。前端组件关键点商品表单 使用Element Plus的ElForm组件对barcode条码字段添加异步校验在用户输入时查询数据库是否已存在并给出友好提示。商品列表表格 使用ElTable并开启筛选和排序。核心的搜索功能应支持多字段模糊查询名称、条码、分类。这里可以利用Dexie的where()和filter()方法或者更简单在Vue的computed属性中对获取的全量商品数组进行JavaScript过滤。批量操作 实现“批量修改分类”、“批量更新价格”等功能。前端通过表格的多选功能获取商品ID数组然后调用Dexie的bulkUpdate方法。3.2 库存管理模块流水记录才是王道库存不能只是一个数字每一次变动都必须有迹可循。我们需要建立“库存流水”的概念。数据库表设计db.version(1).stores({ stock_logs: id, goodsId, type, quantity, relatedOrderId, operator, createdAt, // 类型入库、出库销售、盘点调整、报损 // ... goods表 });type: 枚举类型定义库存变动原因。purchase采购入库、sale销售出库、adjust盘点调整、loss报损。relatedOrderId: 关联订单ID如果是销售出库。operator: 操作员。业务逻辑采购入库 在“入库”界面选择商品、输入数量、进价。点击保存时执行两个原子操作1) 更新goods表中该商品的stock字段增加。2) 在stock_logs表中插入一条typepurchase的记录。销售出库 在收银时自动触发见3.3节。盘点调整 实际盘点后输入商品当前实际库存系统自动计算与账面库存的差值生成一条typeadjust的流水并更新商品库存。库存预警 在首页或商品列表通过计算goods.stock goods.alertStock来筛选出需要补货的商品并用红色标签显示。3.3 收银结算模块模拟真实收银流程这是系统的核心交互界面设计目标是快、准、稳。界面布局设计采用左右或上下分栏布局。左侧商品添加区 一个大大的条码输入框自动聚焦支持扫码枪输入和键盘输入。下方是一个实时搜索框支持拼音首字母、商品名模糊搜索点击即可添加商品到右侧清单。右侧购物清单区 一个表格展示已添加的商品名称、单价、数量、小计。最重要的交互是直接修改数量单元格。用户可以直接在表格里按数字键修改数量按-键减少按键或直接输入数字增加。底部汇总与操作区 显示商品总数、应收总计。提供“会员折扣”、“整单抹零”等快捷按钮。一个显眼的“结算”按钮。业务逻辑与数据流转添加商品 输入条码或搜索商品后检查购物清单中是否已存在。若存在则将其数量1若不存在则向清单数组cartItems中push一个新项。实时计算 利用Vue3的computed属性根据cartItems实时计算商品总数和总价。totalPrice cartItems.reduce((sum, item) sum (item.retailPrice * item.quantity), 0)。结算弹出结算确认框显示应收金额输入实收金额自动计算找零。点击确认后开始一个数据库事务。这是保证数据一致性的关键。await db.transaction(rw, db.goods, db.stock_logs, db.orders, db.order_details, async () { // 1. 插入主订单记录 (orders表) const orderId await db.orders.add({ total: totalPrice, cash: receivedCash, change: change, createdAt: new Date() }); // 2. 遍历购物车插入订单明细 (order_details表)并更新库存 for (const item of cartItems) { await db.order_details.add({ orderId, goodsId: item.id, quantity: item.quantity, price: item.retailPrice }); // 更新商品库存 await db.goods.update(item.id, { stock: item.stock - item.quantity }); // 插入库存流水出库 await db.stock_logs.add({ goodsId: item.id, type: sale, quantity: -item.quantity, relatedOrderId: orderId, createdAt: new Date() }); } });事务确保以上所有操作要么全部成功要么全部失败回滚防止出现“订单生了库存没减”的脏数据。打印小票可选 Electron可以通过node-thermal-printer或调用系统打印对话框连接热敏小票打印机。小票模板可以使用HTML生成内容包含订单号、商品清单、金额、时间等。3.4 数据统计与报表模块让数据说话数据沉淀下来价值在于分析。这个模块不需要太复杂但几个核心报表必不可少。实现思路所有报表都基于IndexedDB中已有的orders、order_details、goods、stock_logs表进行关联查询和聚合计算。由于IndexedDB不支持SQL我们需要用Dexie的组合查询来实现。销售统计日/月/年// 获取今日销售额 const startOfDay new Date(); startOfDay.setHours(0,0,0,0); const endOfDay new Date(); endOfDay.setHours(23,59,59,999); const todaySales await db.orders .where(createdAt).between(startOfDay, endOfDay) .toArray(); const total todaySales.reduce((sum, order) sum order.total, 0);按月、按年统计只需改变时间区间。可以配合ECharts或Chart.js生成柱状图或折线图直观展示销售趋势。商品销售排行// 这是一个相对复杂的聚合查询Dexie需要一些技巧 const saleDetails await db.order_details.toArray(); const goodsMap {}; saleDetails.forEach(detail { if (!goodsMap[detail.goodsId]) { goodsMap[detail.goodsId] { quantity: 0, amount: 0 }; } goodsMap[detail.goodsId].quantity detail.quantity; goodsMap[detail.goodsId].amount detail.quantity * detail.price; }); // 然后根据goodsId去goods表查询商品名称再排序这个查询在数据量很大时可能会有效率问题。对于生产环境可以考虑定期在Web Worker中计算并缓存排行结果。库存资金占用分析const allGoods await db.goods.toArray(); const totalCapital allGoods.reduce((sum, goods) sum (goods.purchasePrice * goods.stock), 0);这个简单的计算能让你知道有多少钱“躺”在仓库里对于优化采购计划很有帮助。4. 从零到一的详细搭建步骤理论讲完我们进入实战。假设你已有Node.js和npm环境。4.1 初始化Electron-Vue项目我推荐使用社区维护的electron-vite模板它集成了Vue3和Electron配置简单。# 使用 npm create 快速创建 npm create quick-start/electron my-supermarket-app # 根据提示选择框架这里选择 vue cd my-supermarket-app npm install项目结构会清晰地区分src/renderer前端Vue代码和src/mainElectron主进程代码。4.2 引入核心依赖进入项目根目录安装UI库和数据库库。npm install element-plus dexie # 图标库可选 npm install element-plus/icons-vue在src/renderer/src/main.js中引入Element Plusimport { createApp } from vue import App from ./App.vue import ElementPlus from element-plus import element-plus/dist/index.css const app createApp(App) app.use(ElementPlus) app.mount(#app)4.3 封装IndexedDB操作核心在src/renderer/src下创建utils/db.js文件使用Dexie初始化数据库并定义表结构。import Dexie from dexie; class SupermarketDB extends Dexie { constructor() { super(SupermarketDB); this.version(1).stores({ goods: id, barcode, name, category, *tags, purchasePrice, retailPrice, stock, alertStock, unit, supplier, createdAt, updatedAt, categories: id, name, description, suppliers: id, name, contact, phone, stock_logs: id, goodsId, type, quantity, beforeStock, afterStock, relatedId, operator, note, createdAt, orders: id, orderNo, totalAmount, paymentMethod, cashReceived, change, memberId, status, operator, createdAt, order_details: id, orderId, goodsId, quantity, unitPrice, subtotal, members: id, cardNo, name, phone, points, balance, createdAt }); // 定义表实例方便使用 this.goods this.table(goods); this.categories this.table(categories); // ... 其他表 } } // 创建并导出数据库实例 export const db new SupermarketDB(); // 可选添加一些数据库操作助手函数 export const goodsAPI { async addGoods(item) { item.createdAt new Date(); item.updatedAt new Date(); return await db.goods.add(item); }, async searchGoods(keyword) { // 实现多字段模糊搜索 const allGoods await db.goods.toArray(); return allGoods.filter(g g.name.includes(keyword) || g.barcode.includes(keyword) || (g.pinyin g.pinyin.includes(keyword.toLowerCase())) ); }, // ... 更多CRUD方法 };4.4 构建商品管理页面在src/renderer/src/components下创建GoodsManagement.vue。模板部分 包含一个搜索框、一个“新增”按钮和一个ElTable。表格列包括选择框多选、条码、名称、分类、库存、售价、操作编辑、删除。脚本部分import { ref, onMounted, computed } from vue; import { ElMessage, ElMessageBox } from element-plus; import { db, goodsAPI } from /utils/db; import GoodsForm from ./GoodsForm.vue; // 假设有一个独立的表单组件 export default { components: { GoodsForm }, setup() { const goodsList ref([]); const searchKeyword ref(); const selectedGoodsIds ref([]); // 计算属性过滤后的商品列表 const filteredGoods computed(() { if (!searchKeyword.value) return goodsList.value; const kw searchKeyword.value.toLowerCase(); return goodsList.value.filter(g g.name.toLowerCase().includes(kw) || g.barcode.includes(kw) || (g.category g.category.name g.category.name.includes(kw)) ); }); // 加载商品数据关联分类名称 const loadGoods async () { // 这里演示一个简单的关联查询先拿到所有商品再根据categoryId去查分类名 const allGoods await db.goods.toArray(); const allCates await db.categories.toArray(); const cateMap new Map(allCates.map(c [c.id, c])); goodsList.value allGoods.map(g ({ ...g, categoryName: cateMap.get(g.category)?.name || 未分类 })); }; // 批量删除 const handleBatchDelete async () { if (selectedGoodsIds.value.length 0) { ElMessage.warning(请先选择商品); return; } try { await ElMessageBox.confirm(确定删除选中的商品吗, 提示); await db.goods.bulkDelete(selectedGoodsIds.value); ElMessage.success(删除成功); loadGoods(); selectedGoodsIds.value []; } catch (err) { // 用户取消 } }; onMounted(() { loadGoods(); }); return { filteredGoods, searchKeyword, selectedGoodsIds, handleBatchDelete, loadGoods }; } };4.5 构建收银台页面创建Pos.vue。这是最复杂的组件重点在于购物车 (cartItems) 的状态管理和结算事务。状态设计const cartItems ref([]); // 购物车商品数组每个对象包含 id, name, retailPrice, quantity const inputBarcode ref(); // 条码输入框绑定值 const payment reactive({ total: 0, received: 0, change: 0 }); // 支付信息添加商品函数const addToCart async (goods) { const existingItem cartItems.value.find(item item.id goods.id); if (existingItem) { existingItem.quantity 1; } else { cartItems.value.push({ id: goods.id, name: goods.name, retailPrice: goods.retailPrice, quantity: 1, stock: goods.stock // 记录当前库存用于后续校验 }); } // 清空条码输入框并聚焦 inputBarcode.value ; nextTick(() { document.getElementById(barcode-input).focus(); }); calculateTotal(); };结算函数事务 如3.3节所示是系统的核心务必处理好异常。const handleCheckout async () { // 1. 校验库存 for (const item of cartItems.value) { const goodsInDB await db.goods.get(item.id); if (goodsInDB.stock item.quantity) { ElMessage.error(商品“${item.name}”库存不足当前库存${goodsInDB.stock}); return; } } // 2. 弹出支付对话框获取实收金额... // 3. 执行数据库事务 try { await db.transaction(rw, db.goods, db.stock_logs, db.orders, db.order_details, async () { // ... 事务内操作见3.3节 }); ElMessage.success(结算成功); // 清空购物车打印小票... cartItems.value []; } catch (error) { ElMessage.error(结算失败${error.message}); console.error(Transaction failed:, error); } };4.6 打包与分发开发完成后使用electron-vite或electron-builder进行打包。# 使用 electron-vite 打包 npm run build这会在dist目录下生成可执行文件。你可以使用electron-builder进行更专业的配置如生成安装程序、设置应用图标、签名等。5. 开发中的坑与实战经验总结做完这个项目我踩过不少坑也积累了一些让应用更健壮、更易用的经验。5.1 IndexedDB的版本升级与数据迁移这是第一个大坑。当你发布第一版后发现需要增加一个字段比如商品需要添加“规格”字段怎么办直接修改db.version(1)的定义是没用的因为用户本地的数据库已经以版本1创建了。你需要进行版本升级。// 在 db.js 中 this.version(2).stores({ goods: id, barcode, name, category, purchasePrice, retailPrice, stock, alertStock, spec, createdAt // 增加了 spec }).upgrade(tx { // 这是一个从版本1升级到版本2的迁移函数 return tx.table(goods).toCollection().modify(goods { goods.spec ; // 为所有已有记录添加 spec 字段并设默认值 }); });关键点upgrade函数中的操作必须是幂等的且要处理好可能的数据转换。务必在开发初期就规划好核心表结构减少频繁升级。5.2 异步操作与UI响应的平衡IndexedDB的所有操作都是异步的。如果在for循环里频繁调用db.goods.update可能会导致界面卡顿。对于批量操作务必使用Dexie提供的bulkPut、bulkUpdate、bulkDelete方法它们在一个事务内完成效率高得多。// 差循环内多次异步操作 for (const id of selectedIds) { await db.goods.update(id, { retailPrice: newPrice }); } // 好使用批量操作 const updates selectedIds.map(id ({ key: id, changes: { retailPrice: newPrice } })); await db.goods.bulkUpdate(updates);5.3 数据备份与恢复功能数据存在用户本地电脑如果硬盘损坏或误删所有数据就没了。必须提供数据备份功能。 实现很简单提供一个按钮调用db.export()Dexie提供的方法将整个数据库导出为一个JSON字符串然后让用户保存为文件。恢复时读取文件内容使用db.import()导入即可。这个功能应该放在系统设置里并用醒目的文字提示用户定期备份。5.4 扫码枪与键盘输入的兼容性处理收银台的核心是条码输入。普通扫码枪模拟键盘输入会在输入条码后自动加一个“回车”。我们的输入框监听keyup或input事件即可。但要注意防抖 防止用户手动输入时过快触发查询。const handleBarcodeInput _.debounce(async (value) { if (value.length 3) return; // 忽略太短的输入 const goods await goodsAPI.findByBarcode(value); if (goods) addToCart(goods); }, 300);自动清空与聚焦 成功添加商品后必须立即清空输入框并将焦点设回去准备接收下一个条码。这是收银效率的关键。5.5 应对突然断电或程序崩溃Electron应用毕竟不是真正的“服务”用户可能直接点窗口关闭或者程序意外崩溃。如果崩溃时正在进行一个复杂的结算事务可能导致数据不一致。虽然IndexedDB事务有原子性保证但为了更安全可以在结算开始时在本地存储LocalStorage设置一个isTransactionInProgress标志结算完成后清除。下次应用启动时检查这个标志如果存在则提示用户上次结算可能未完成并引导至一个“异常订单处理”页面进行人工核对。这是一个“尽力而为”的增强体验措施。从那个古老的chaoshi.rar出发到构建出一个具备现代交互、可靠数据管理的桌面应用整个过程就像一次技术考古与重建。最终做出的工具虽然界面简单但每一行代码都针对着小超市老板的真实操作习惯快、稳、直观。它可能没有联网功能没有华丽的报表但它解决了最核心的“管货”和“收钱”问题并且数据完全掌握在店主自己手里。对于想要入门桌面应用开发、理解本地数据库操作的前端开发者来说这也是一个绝佳的练手项目。你可以从这里开始逐步添加会员管理、供应商管理、多店铺支持等功能让它不断进化。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →