超市收银+库存管理双模块系统:SpringBoot后端与Vue前端完整实现(含MySQL建库脚本)

该文章已生成可运行项目,

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这个资源包提供一套开箱即用的超市业务管理系统,前后端分离设计,后端基于SpringBoot(Java语言),前端采用Vue.js框架,数据库使用MySQL。系统支持管理员、会计、销售员、库管员四类角色,各自拥有对应操作权限:管理员可维护员工、商品、会员信息,处理订单、退货、积分折扣及销售数据统计;会计能查看财务汇总和报表;销售员完成日常扫码收银、小票打印、会员结算;库管员负责商品入库、出库、库存查询与低库存预警。配套SQL脚本db_chaoshi_manger.sql已写好,导入即可运行。开发环境适配IntelliJ IDEA或Eclipse(后端)、VS Code或WebStorm(前端),构建工具为Maven(含mvnw),前端配置通过vue.config.js和babel.config.js完成。项目结构清晰,包含独立的chaoshi_server(后端模块)和chaoshi_admin(前端模块),附带详细README说明文档和API接口文档(api.md),适合本科毕业设计、课程实训或快速二次开发学习。

1. 项目概述:为什么一个“超市收银+库存管理”系统值得从零拆解?

你手头这份资源包,表面看是个毕业设计模板,但背后藏着一套真实商业场景下最基础、也最容易被低估的数字化骨架——它不是玩具,而是超市每天开门营业时,收银台后那台嗡嗡作响的电脑、库房里扫码枪扫过的每一箱货、财务室月底对账时反复核对的Excel表格,这三者在数字世界里的统一映射。我带过十几届学生做这类系统,也给三四家社区连锁超市做过轻量级定制,最深的体会是:90%的“系统崩溃”,从来不是因为代码写错了,而是因为开发者没真正站在收银员、库管、会计的工位上,摸过他们手里的扫码枪、翻过他们皱巴巴的入库单、听过他们抱怨“这个报表导出来日期又不对”。 这套系统之所以能“开箱即用”,核心不在于用了SpringBoot或Vue这些热门框架,而在于它的每一个模块、每一张数据库表、甚至每一个按钮的命名,都对应着一个真实的、有温度的操作动作。

比如,“销售员”角色下的“扫码收银”,它背后要串联起:前端扫码组件识别商品条码 → 调用后端/api/sale/scan接口实时查询商品信息(含库存余量、会员价、积分规则)→ 系统自动校验当前库存是否充足(不足则弹窗阻断)→ 用户确认支付方式(现金/微信/会员卡)→ 后端生成唯一订单号、扣减库存、记录销售流水、同步更新会员积分 → 最后触发小票打印机指令。这一连串动作,必须在3秒内完成,否则排队顾客就会皱眉。而“库管员”的“入库操作”,看似只是填个数量,实则要校验供应商资质、批次号、保质期、入库单与采购单是否匹配、新入库商品是否触发低库存预警阈值……这些细节,才是区分一个“能跑起来的Demo”和一个“能扛住早市高峰的生产系统”的分水岭。

关键词里提到的“超市收银系统”和“库存管理”,绝不是两个并列的模块,而是同一枚硬币的两面。收银是库存的“出口”,入库是库存的“入口”,而所有销售数据,最终都要沉淀为库存周转率、毛利率、畅销榜这些老板真正关心的经营指标。所以,当你看到db_chaoshi_manger.sql这个脚本时,别急着导入,先打开它,数一数goods(商品)、inventory(库存)、sale_order(销售订单)、purchase_order(采购订单)这四张表之间有多少个外键关联、多少个索引字段——这些设计,就是整个业务逻辑的DNA。这套系统最大的价值,不在于教你如何写一个@RestController,而在于它用最朴素的代码,把“超市怎么赚钱”这件事,翻译成了计算机能听懂的语言。如果你正为毕设发愁,或者想快速掌握企业级Java+Vue项目的落地逻辑,那么接下来的每一步拆解,都是你绕不开的实战课。

2. 整体架构与设计思路:前后端分离不是口号,而是责任切分

2.1 为什么必须前后端完全分离?——从“改一个按钮”说起

很多初学者会疑惑:既然都是Java写的,为什么后端不用Thymeleaf直接渲染页面,非得搞个Vue前端?答案藏在一个再普通不过的需求变更里:某天老板说,“收银界面那个‘会员结算’按钮,颜色太浅了,顾客老找不到,给我改成醒目的红色!” 如果是传统服务端渲染,你得改Java Controller里的跳转逻辑、改HTML模板里的CSS类名、改JS里的点击事件绑定,然后重启整个SpringBoot应用——这意味着,哪怕只是换了个颜色,整个超市的收银系统都要暂停几秒钟。而在这套分离架构里,你只需要打开chaoshi_admin/src/views/cashier/index.vue,找到<el-button type="primary">这一行,把type="primary"改成type="danger",保存,前端开发服务器自动刷新,收银员那边毫无感知。这就是分离带来的第一重红利:前端UI的迭代,与后端业务逻辑的稳定性,彻底解耦。

更深层的考量在于职责清晰。SpringBoot后端的核心使命只有一个:做最可靠的“数据守门人”和“业务裁判员”。它不关心按钮长什么样,只关心“这笔销售是否合法”:商品是否存在?库存是否足够?会员卡是否有效?折扣是否在活动期内?支付金额是否等于应付总额?所有这些判断,必须由后端在SaleService里用事务(@Transactional)兜底,确保哪怕服务器在扣库存的瞬间宕机,数据库也不会出现“钱收了但库存没扣”的灾难性错误。而Vue前端,则是纯粹的“用户体验工程师”,它负责把后端返回的JSON数据,用最直观的方式呈现给不同角色:给销售员一个大大的扫码框和清晰的支付按钮;给库管员一个带搜索和批量导入的表格;给会计一个能钻取到每一天每一笔明细的图表。这种分工,让团队协作变得高效——前端同学可以专注优化扫码识别率和小票打印样式,后端同学可以深耕库存预警算法和财务对账逻辑,互不干扰。

2.2 四角色权限模型:RBAC不是理论,而是业务流程的镜像

系统定义的管理员、会计、销售员、库管员四个角色,其本质是超市内部管理流程的数字化投射。我们来看chaoshi_server模块中SysRoleSysUserRole这两张表的设计:

-- 角色表
CREATE TABLE `sys_role` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `role_name` varchar(50) NOT NULL COMMENT '角色名称,如:管理员、销售员',
  `role_code` varchar(50) NOT NULL COMMENT '角色编码,唯一标识,如:ADMIN, SALE_CLERK',
  PRIMARY KEY (`id`)
);

-- 用户-角色关联表
CREATE TABLE `sys_user_role` (
  `user_id` bigint NOT NULL,
  `role_id` bigint NOT NULL,
  PRIMARY KEY (`user_id`,`role_id`)
);

这里的role_code(角色编码)是关键。它不是随便起的名字,而是整个权限控制的“开关”。在后端SecurityConfig配置类里,你会看到类似这样的代码:

// 配置不同角色的访问路径
http.authorizeRequests()
    .antMatchers("/api/admin/**").hasRole("ADMIN")        // 管理员专属API
    .antMatchers("/api/account/**").hasRole("ACCOUNTANT")  // 会计专属API
    .antMatchers("/api/cashier/**").hasRole("SALE_CLERK")  // 销售员专属API
    .antMatchers("/api/warehouse/**").hasRole("WAREHOUSE") // 库管员专属API
    .anyRequest().authenticated();

而前端chaoshi_admin的路由守卫(router.beforeEach)则会读取登录后返回的role_code,动态加载对应角色的菜单:

// router/index.js 中的路由守卫
router.beforeEach((to, from, next) => {
  const role = store.getters.role; // 从Vuex store获取用户角色编码
  if (to.meta.roles && !to.meta.roles.includes(role)) {
    next({ path: '/403' }); // 无权限,跳转403页
  } else {
    next();
  }
});

你看,一个role_code,从前端菜单渲染,到后端API拦截,再到数据库表结构,形成了一个闭环。这解释了为什么“管理员能维护员工信息”而“销售员不能”——不是前端把按钮隐藏了,而是当销售员试图调用/api/admin/user/list这个接口时,后端的Spring Security会直接返回403 Forbidden,前端连数据都拿不到。这种基于角色的访问控制(RBAC),让权限管理变得可审计、可追溯,也避免了前端“藏而不禁”的安全漏洞。

2.3 数据库设计哲学:一张表就是一个业务实体,一个字段就是一次业务决策

打开db_chaoshi_manger.sql,你会发现goods(商品)表里有bar_code(条形码)、unit_price(单价)、cost_price(成本价)、is_on_sale(是否上架)等字段,这很常规。但真正体现设计功力的,是inventory(库存)表:

CREATE TABLE `inventory` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `goods_id` bigint NOT NULL COMMENT '关联商品ID',
  `warehouse_id` bigint DEFAULT NULL COMMENT '仓库ID,支持多仓',
  `stock_quantity` int NOT NULL DEFAULT '0' COMMENT '当前库存数量',
  `min_stock_level` int NOT NULL DEFAULT '0' COMMENT '最低库存预警阈值',
  `last_update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_goods_warehouse` (`goods_id`,`warehouse_id`) -- 关键!支持多仓
);

这里有两个精妙之处:第一,warehouse_id字段允许为空,意味着系统默认支持“总仓”概念,同时也为未来扩展“分店仓”、“区域仓”预留了空间;第二,UNIQUE KEY uk_goods_warehouse这个联合唯一索引,强制保证了“同一个商品在同一个仓库里,只能有一条库存记录”,这直接杜绝了因并发入库导致的库存数量错乱问题。再看sale_order(销售订单)表:

CREATE TABLE `sale_order` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `order_no` varchar(50) NOT NULL COMMENT '订单号,全局唯一',
  `member_id` bigint DEFAULT NULL COMMENT '会员ID,可为空(现金顾客)',
  `total_amount` decimal(10,2) NOT NULL COMMENT '应付总额',
  `actual_amount` decimal(10,2) NOT NULL COMMENT '实收金额',
  `discount_amount` decimal(10,2) DEFAULT '0.00' COMMENT '优惠金额',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-待支付,2-已支付,3-已退货',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_no` (`order_no`)
);

order_no(订单号)被设计为业务主键,而非数据库自增ID。这是因为,在分布式或高并发场景下,自增ID可能暴露业务量,且不利于日志追踪。而一个格式如SO202405200001(SO+年月日+流水号)的订单号,既能保证全局唯一,又能让人一眼看出订单生成时间,方便客服查单。这些设计,没有一行是凭空想象出来的,它们全是从无数次“顾客投诉买的东西没出库”、“财务对不上账”、“库管说系统显示有货但货架上找不到”这些真实问题里,一点点打磨出来的。

3. 核心模块深度解析:从扫码收银到库存预警的完整链路

3.1 收银模块:3秒成交背后的并发与一致性保障

销售员日常最核心的动作,就是“扫码-确认-收款-打印”。这个看似简单的流程,后端需要处理至少5个关键环节:

  1. 扫码识别与商品查询:前端调用GET /api/cashier/goods/by-barcode/{barcode}。后端GoodsController接收到请求后,不是简单地查goods表,而是通过GoodsService执行一个复合查询:
    java // GoodsService.java public GoodsVO getGoodsByBarcode(String barcode) { Goods goods = goodsMapper.selectByBarCode(barcode); // 主表查询 if (goods == null) { throw new BusinessException("商品不存在"); } // 关联查询库存,这是关键! Inventory inventory = inventoryMapper.selectByGoodsId(goods.getId()); // 关联查询会员价(如果当前登录用户是会员) BigDecimal memberPrice = memberPriceMapper.selectByGoodsIdAndMemberId(goods.getId(), currentMemberId); return convertToVO(goods, inventory, memberPrice); }
    这里,inventoryMapper.selectByGoodsId必须是原子操作,确保返回的stock_quantity是最新、最准确的。如果此时库存刚好为0,前端会立刻提示“缺货”,避免顾客排队到一半才发现买不了。

  2. 购物车构建与实时计算:用户扫码后,商品信息(含当前库存、会员价)被加入前端Vuex Store的购物车数组。每次添加,都会触发一个计算函数:
    javascript // store/modules/cashier.js mutations: { ADD_TO_CART(state, goods) { const exist = state.cart.find(item => item.id === goods.id); if (exist) { exist.quantity += 1; } else { state.cart.push({ ...goods, quantity: 1, totalPrice: goods.memberPrice || goods.unitPrice // 优先用会员价 }); } // 实时计算总价、优惠、实付 state.totalAmount = state.cart.reduce((sum, item) => sum + item.totalPrice * item.quantity, 0); } }
    注意totalPrice的赋值逻辑:goods.memberPrice || goods.unitPrice。这体现了业务规则——有会员卡就用会员价,没有就用标价。这个逻辑如果放在后端计算,每次加购都要调一次API,网络延迟会让体验变卡;放在前端计算,则要求后端必须把memberPrice这个字段随商品信息一起返回,这就是前后端契约的体现。

  3. 下单与库存扣减的强一致性:当用户点击“结算”按钮,前端将整个购物车数组(含商品ID、数量、支付方式)POST到POST /api/cashier/order/create。这才是真正的“临门一脚”。后端SaleOrderController的处理逻辑必须用@Transactional包裹,并采用“先查后扣”的悲观锁策略:
    java @Transactional public SaleOrderVO createOrder(@RequestBody SaleOrderDTO dto) { // 1. 对购物车中每个商品,查询并锁定其库存记录(for update) for (SaleOrderItemDTO item : dto.getItems()) { Inventory inventory = inventoryMapper.selectForUpdateByGoodsId(item.getGoodsId()); if (inventory.getStockQuantity() < item.getQuantity()) { throw new BusinessException("商品【" + item.getGoodsName() + "】库存不足"); } } // 2. 扣减库存(注意:是循环扣减,不是一次性SQL) for (SaleOrderItemDTO item : dto.getItems()) { inventoryMapper.decreaseStock(item.getGoodsId(), item.getQuantity()); } // 3. 创建销售订单主表和明细表 SaleOrder order = buildOrder(dto); saleOrderMapper.insert(order); // 4. 记录会员积分(如果适用) if (dto.getMemberId() != null) { memberPointService.addPoints(dto.getMemberId(), calculatePoints(dto)); } return convertToVO(order); }
    关键点在于selectForUpdateByGoodsId。这个方法对应的SQL是SELECT * FROM inventory WHERE goods_id = ? FOR UPDATE,它会在数据库层面给这条库存记录加行锁,阻止其他并发请求同时修改它。这样,即使10个销售员在同一秒扫描同一个爆款商品,系统也能保证库存只被正确扣减10次,而不是因为并发读写导致“超卖”。

  4. 小票打印的本地化适配:前端调用/api/cashier/print/receipt接口后,后端并不直接驱动打印机,而是返回一个标准格式的JSON小票数据(包含抬头、商品列表、金额、时间戳)。前端printReceipt()方法则根据浏览器环境选择方案:在Electron打包的桌面版中,调用Node.js的printer模块;在普通Chrome浏览器中,则生成一个隐藏的<iframe>,注入HTML小票模板,调用window.print()。这种设计,让系统能无缝适配超市不同的硬件环境。

提示:db_chaoshi_manger.sqlsale_order_item(销售订单明细)表的price字段,存储的是下单那一刻的快照价格,而非实时查询goods表的价格。这是为了保证财务对账的准确性——哪怕明天商品涨价了,昨天的销售小票金额也绝不能变。

3.2 库存管理模块:从“入库单”到“预警通知”的闭环

库管员的工作,是让“账实相符”这四个字真正落地。系统为此设计了一套完整的“单据流”驱动机制。

入库流程(Purchase Order):
1. 库管员在/warehouse/purchase页面填写采购单:选择供应商、录入采购商品(可扫码或搜索)、填写采购数量、采购单价、预计到货日期。
2. 提交后,后端PurchaseOrderController创建purchase_order主表记录,并为每一项商品创建purchase_order_item明细记录。
3. 关键动作:入库确认。当货物实际到达,库管员在系统中找到这张采购单,点击“确认入库”。此时,后端WarehouseService执行:
java @Transactional public void confirmPurchase(Long purchaseOrderId) { // 1. 查询采购单及其所有明细 PurchaseOrder purchaseOrder = purchaseOrderMapper.selectById(purchaseOrderId); List<PurchaseOrderItem> items = purchaseOrderItemMapper.selectByOrderId(purchaseOrderId); // 2. 循环处理每一项商品 for (PurchaseOrderItem item : items) { // 3. 查询该商品当前的库存记录(按商品ID和仓库ID) Inventory inventory = inventoryMapper.selectByGoodsIdAndWarehouseId( item.getGoodsId(), purchaseOrder.getWarehouseId()); if (inventory == null) { // 如果是新商品,初始化一条库存记录 inventory = new Inventory(); inventory.setGoodsId(item.getGoodsId()); inventory.setWarehouseId(purchaseOrder.getWarehouseId()); inventory.setStockQuantity(0); inventory.setMinStockLevel(0); // 默认预警阈值为0,需库管手动设置 inventoryMapper.insert(inventory); } // 4. 增加库存数量 inventory.setStockQuantity(inventory.getStockQuantity() + item.getQuantity()); inventoryMapper.updateById(inventory); } // 5. 更新采购单状态为“已入库” purchaseOrder.setStatus(2); purchaseOrderMapper.updateById(purchaseOrder); }
这个流程确保了每一次实物入库,都必然伴随着库存数字的精确增加。

库存预警与主动通知:
低库存预警不是等库管员自己去查报表,而是系统主动出击。db_chaoshi_manger.sqlinventory表的min_stock_level字段,就是预警阈值。系统通过一个定时任务(@Scheduled(cron = "0 0 9 * * ?"),每天上午9点执行)来扫描:

@Scheduled(cron = "0 0 9 * * ?")
public void checkLowStock() {
    // 查询所有库存数量 <= 预警阈值的商品
    List<Inventory> lowStockList = inventoryMapper.selectLowStockItems();
    if (!lowStockList.isEmpty()) {
        // 构建预警消息
        String content = "以下商品库存低于预警线,请及时补货:\n";
        for (Inventory inv : lowStockList) {
            Goods goods = goodsMapper.selectById(inv.getGoodsId());
            content += String.format("- %s (当前:%d,预警:%d)\n", 
                goods.getName(), inv.getStockQuantity(), inv.getMinStockLevel());
        }
        // 发送站内信给所有库管员角色的用户
        messageService.sendToRole("WAREHOUSE", "库存预警", content);
        // 可选:发送邮件或短信(需集成第三方服务)
    }
}

这个设计的价值在于,它把“被动查询”变成了“主动提醒”,把库管员从海量数据中解放出来,让他们能聚焦于最关键的补货决策。

注意:inventory表中的last_update_time字段,被设计为ON UPDATE CURRENT_TIMESTAMP。这意味着,只要库存数量发生任何变化(无论是入库、出库还是盘点调整),这个时间戳就会自动更新。在/warehouse/inventory页面的“库存流水”查询中,前端可以按此时间排序,快速定位最近一次变动,极大提升了问题排查效率。

4. 实操部署与环境配置:从源码到可运行系统的完整路径

4.1 后端(chaoshi_server)启动指南:Maven与IDE的协同

拿到源码后,第一步是让后端服务跑起来。chaoshi_server目录下的pom.xml是整个Java世界的“说明书”,而mvnw(Maven Wrapper)则是确保你无需提前安装Maven的“便携式引擎”。

步骤1:环境检查与准备
- JDK版本:必须是JDK 8或JDK 11。在命令行输入java -version,确认输出类似openjdk version "11.0.20"。如果版本不符,IntelliJ IDEA中可在File > Project Structure > Project Settings > Project里切换SDK。
- MySQL服务:确保本地MySQL 5.7+服务已启动。推荐使用Docker快速拉起一个干净的实例:
bash docker run -d --name mysql-chao -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=chaoshi_manager -v $(pwd)/mysql-data:/var/lib/mysql -d mysql:5.7
这条命令会创建一个名为mysql-chao的容器,root密码为123456,并自动创建数据库chaoshi_manager

步骤2:数据库初始化
- 将资源包中的db_chaoshi_manger.sql文件,用MySQL客户端(如Navicat、DBeaver或命令行)连接到刚启动的MySQL,执行该SQL脚本。脚本会创建所有表结构并插入初始数据(如管理员账号、测试商品)。
- 检查application.yml文件(位于src/main/resources/下),确认数据库连接配置是否正确:
yaml spring: datasource: url: jdbc:mysql://localhost:3306/chaoshi_manager?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: 123456

步骤3:IDEA中启动项目
- 在IntelliJ IDEA中,File > Open,选择chaoshi_server目录。
- IDEA会自动识别这是一个Maven项目,并开始下载依赖(pom.xml中定义的spring-boot-starter-web, mybatis-spring-boot-starter等)。
- 项目加载完成后,找到ChaoshiServerApplication.java这个主类(通常在com.example.chaoshi包下),右键Run 'ChaoshiServerApplication'
- 控制台输出Started ChaoshiServerApplication in X seconds,即表示启动成功。此时,后端API已在http://localhost:8080监听。

实操心得:如果启动报错Failed to configure a DataSource,90%的可能是application.yml里的数据库密码或URL写错了。一个快速验证方法是,把url里的localhost换成127.0.0.1,有时hosts文件的解析会导致localhost无法连接。另外,mvnw.cmd(Windows)和mvnw(Mac/Linux)是同一种工具的不同版本,IDEA会自动选择,无需手动干预。

4.2 前端(chaoshi_admin)配置与运行:Vue CLI的魔法

前端chaoshi_admin是一个标准的Vue CLI 4.x项目,其灵魂在于vue.config.jsbabel.config.js这两个配置文件。

步骤1:Node.js环境与依赖安装
- 确保已安装Node.js(>= 14.0.0)和npm。命令行输入node -vnpm -v验证。
- 进入chaoshi_admin目录,执行npm install。这会根据package.json中的dependenciesdevDependencies,安装所有前端依赖(Vue、Element UI、Axios等)。国内用户建议先配置淘宝镜像源:npm config set registry https://registry.npmmirror.com

步骤2:代理配置(解决跨域)
- 开发时,前端运行在http://localhost:8080,而后端在http://localhost:8080,这必然导致浏览器跨域。vue.config.js中的devServer.proxy就是为此而生:
javascript module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', // 后端地址 changeOrigin: true, // 修改请求头中的host为target pathRewrite: { '^/api': '/api' // 将前端请求的/api前缀,转发给后端的/api } } } } }
这意味着,你在前端代码里写axios.get('/api/cashier/goods'),开发服务器会自动把它转发到http://localhost:8080/api/cashier/goods,前端代码完全感觉不到跨域的存在。

步骤3:运行与调试
- 在chaoshi_admin目录下,执行npm run serve
- 等待Webpack编译完成,控制台会输出App running at: http://localhost:8080
- 打开浏览器,访问http://localhost:8080,你应该能看到登录页面。使用SQL脚本中预置的管理员账号(如admin/admin123)登录,即可进入后台。

实操心得:babel.config.js的作用是让Vue项目能兼容老版本浏览器(如IE11)。如果你确定只在现代浏览器中使用,可以安全地忽略它。另一个容易被忽视的文件是.browserslistrc,它定义了项目需要兼容的浏览器范围,vue-cli-service build会根据它来决定哪些ES6+语法需要被Babel转译。对于超市内部系统,通常只需支持Chrome、Firefox、Edge的最新两个版本,可以将其内容简化为> 1%, last 2 versions, not dead

4.3 前后端联调与常见问题速查

当后端和前端都启动后,真正的考验才开始——它们能否顺畅对话?以下是我在教学中遇到的最高频问题及解决方案:

问题现象可能原因排查与解决方法
登录失败,控制台报401 Unauthorized1. 后端application.yml中JWT密钥jwt.secret与前端utils/request.js中配置的不一致。
2. 登录请求未携带Content-Type: application/json头。
检查前后端JWT配置是否完全相同(包括空格)。在前端login方法中,确保axios.post的第二个参数是对象,第三个参数是{ headers: { 'Content-Type': 'application/json' } }
收银页面扫码无反应,控制台报Network Error1. 前端vue.config.jsproxy配置未生效,请求被发往了错误的地址。
2. 后端服务未启动,或端口被占用。
在浏览器开发者工具的Network标签页,查看扫码请求的实际URL。如果是http://localhost:8080/api/cashier/goods/...,说明代理失效,检查vue.config.js语法是否有误(如逗号缺失)。用netstat -ano \| findstr :8080(Windows)或lsof -i :8080(Mac)检查端口占用。
库存列表为空,但数据库里有数据1. 前端路由守卫(router.beforeEach)拦截了请求,因为用户角色权限不足。
2. 后端InventoryController@PreAuthorize("hasRole('WAREHOUSE')")注解限制了访问。
登录后,在浏览器控制台输入localStorage.getItem('userInfo'),查看返回的JSON中role字段是否为WAREHOUSE。如果不是,说明登录的不是库管员账号。检查db_chaoshi_manger.sqlsys_user_role表的数据,确认库管员用户ID是否关联了正确的角色ID。
小票打印出来是空白页1. 后端返回的小票JSON数据格式有误,前端模板无法解析。
2. 浏览器打印设置中,“背景图形”选项被关闭。
在Network标签页中,找到/api/cashier/print/receipt请求,查看Response内容是否为有效的JSON。在Chrome打印预览界面,点击左下角“更多设置”,勾选“背景图形”。

5. 二次开发与能力拓展:从“能用”到“好用”的进阶之路

5.1 为销售员增加“快速结账”模式

目前的收银流程,要求销售员为每一笔交易都手动输入或扫码。但对于烟酒、饮料等高频、单价固定的品类,可以增加一个“快捷键结账”功能。这需要前后端协同改造:

  • 后端:在SaleOrderController中新增一个接口POST /api/cashier/order/quick-create,它接受一个简化的DTO:
    java public class QuickOrderDTO { private Long goodsId; // 商品ID private Integer quantity; // 数量,默认为1 private String paymentType; // 支付方式:CASH, WECHAT, ALIPAY }
    逻辑与createOrder类似,但省去了购物车构建步骤,直接针对单个商品创建订单。

  • 前端:在/cashier/index.vue页面底部,增加一个“快捷结账区”:
    ```html

    快捷结账

    红牛 (¥6.0) 中华 (¥50.0) 农夫山泉 (¥2.0)

``quickPay(goodsId)方法会调用新的/api/cashier/order/quick-create`接口,并在成功后自动清空当前屏幕,准备下一笔。

这个改动,能让销售员在早市高峰期,将单笔交易时间从8秒压缩到3秒以内,是提升用户体验最直接的手段。

5.2 为库存管理增加“盘点差异报告”

系统能管住“账”,但“实”是否与“账”一致,需要定期盘点。可以在/warehouse/stocktake页面增加一个“生成盘点报告”按钮:

  • 后端:创建StocktakeService,其核心方法是对比inventory表(系统账)与stocktake_record表(盘点实绩):
    java public List<StocktakeDiffVO> generateDiffReport(Long warehouseId) { // 1. 获取系统当前所有库存 List<Inventory> systemStock = inventoryMapper.selectByWarehouseId(warehouseId); // 2. 获取最后一次盘点的所有记录 List<StocktakeRecord> actualStock = stocktakeRecordMapper.selectLatestByWarehouse(warehouseId); // 3. 以商品ID为key,进行Map合并,计算差异 Map<Long, StocktakeDiffVO> diffMap = new HashMap<>(); for (Inventory sys : systemStock) { StocktakeDiffVO vo = new StocktakeDiffVO(); vo.setGoodsId(sys.getGoodsId()); vo.setSystemQuantity(sys.getStockQuantity()); vo.setActualQuantity(0); // 默认实盘为0 diffMap.put(sys.getGoodsId(), vo); } for (StocktakeRecord act : actualStock) { StocktakeDiffVO vo = diffMap.get(act.getGoodsId()); if (vo != null) { vo.setActualQuantity(act.getQuantity()); vo.setDifference(vo.getSystemQuantity() - vo.getActualQuantity()); } } return new ArrayList<>(diffMap.values()); }
    报告会清晰列出每一项商品的“系统数量”、“实盘数量”、“差异”,并高亮显示差异大于0(盘亏)或小于0(盘盈)的条目,方便库管员快速定位问题。

5.3 为会计模块增加“毛利分析”图表

api.md文档中定义了GET /api/account/profit/summary接口,但目前只返回一个总毛利数字。我们可以用ECharts为它赋能:

  • 后端:扩展该接口,使其能按“日”、“周”、“月”维度返回聚合数据:
    json { "date": ["2024-05-01", "2024-05-02", "2024-05-03"], "grossProfit": [1250.50, 1380.20, 1190.75], "salesAmount": [5200.00, 5680.50, 4950.20] }
  • 前端:在/account/profit.vue中,使用<v-chart>组件(基于ECharts封装)绘制双Y轴折线图:
    html <v-chart :options="chartOptions" />
    javascript data() { return { chartOptions: { tooltip: { trigger: 'axis' }, legend: { data: ['销售额', '毛利润'] }, xAxis: { type: 'category', data: [] }, yAxis: [ { type: 'value', name: '销售额' }, { type: 'value', name: '毛利润', position: 'right' } ], series: [ { name: '销售额', type: 'line', data: [], yAxisIndex: 0 }, { name: '毛利润', type: 'line', data: [], yAxisIndex: 1 } ] } } }
    这样的图表,能让会计一眼看出“哪天生意最好”、“毛利最高的品类是什么”,把枯燥的数字,变成可行动的洞察。

最后分享一个小技巧:如果你想快速验证某个API接口是否正常,不必每次都写前端代码。直接用curl命令行工具:
bash curl -X GET "http://localhost:8080/api/cashier/goods/by-barcode/6923450654321" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Bearer后面那个长字符串,替换成你登录后拿到的真实token。这种方式,比启动整个前端项目要快得多,是后端开发者最常用的“秒级”调试法。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这个资源包提供一套开箱即用的超市业务管理系统,前后端分离设计,后端基于SpringBoot(Java语言),前端采用Vue.js框架,数据库使用MySQL。系统支持管理员、会计、销售员、库管员四类角色,各自拥有对应操作权限:管理员可维护员工、商品、会员信息,处理订单、退货、积分折扣及销售数据统计;会计能查看财务汇总和报表;销售员完成日常扫码收银、小票打印、会员结算;库管员负责商品入库、出库、库存查询与低库存预警。配套SQL脚本db_chaoshi_manger.sql已写好,导入即可运行。开发环境适配IntelliJ IDEA或Eclipse(后端)、VS Code或WebStorm(前端),构建工具为Maven(含mvnw),前端配置通过vue.config.js和babel.config.js完成。项目结构清晰,包含独立的chaoshi_server(后端模块)和chaoshi_admin(前端模块),附带详细README说明文档和API接口文档(api.md),适合本科毕业设计、课程实训或快速二次开发学习。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值