SpringBoot+MyBatis+Thymeleaf超市后台快速落地指南

发布时间:2026/10/30 5:54:03
SpringBoot+MyBatis+Thymeleaf超市后台快速落地指南 简介这是一套基于Spring Boot开发的超市信息管理系统完整源码资源面向Java初学者与Web开发入门者解决中小型商超日常经营中商品采购、销售管理、库存盘点及财务分析等核心业务场景的信息化需求。资源包大小为7.92MB为RAR压缩格式包含可直接运行的后端工程、配套数据库脚本及基础配置文件支持远程调试与本地部署结构清晰、注释规范便于理解MVC分层设计与RESTful接口实践。已有918人学习下载适合用于课程设计、毕业设计或Spring Boot实战训练。读者可获得完整可运行系统含登录、商品录入、库存管理、销售模块等核心功能、MySQL建表语句与初始化数据、前后端交互逻辑说明以及论文中对应的技术实现思路梳理有助于快速掌握企业级Java Web项目开发全流程。1. 一个能当天跑通、次日就敢往里填真实商品数据的超市后台SpringBoot MyBatis Thymeleaf 三件套落地实录上周帮某高校实验室搭实训环境导师甩来一个「超市信息管理系统」压缩包说“学生交作业用别整太复杂”。我点开一看没 README.md没 application.yml 示例连数据库建表语句都藏在 resources/sql/ 下面一个叫 init.sql 的文件里——但双击 jar 包居然真弹出了登录页。输入 admin/123456货架、员工、进货单全在眼前。更关键的是它没用任何云服务或外部依赖H2 内存库开箱即用MySQL 切换只要改两行配置Thymeleaf 模板全在 templates/ 下改个商品列表页不用重启连远程调试参数都写死在 pom.xml 里——-agentlib:jdwptransportdt_socket,servery,suspendn,address*:5005。这不是教学 Demo是能直接塞进小超市老板手机相册里、让他指着屏幕说“这个价格栏我要加个红色警示”的生产级骨架。适合 Java 入门者练手 SpringBoot 全流程也适合老手拿它当基座三天内接入扫码枪或微信支付。2. 项目结构与技术栈解剖从 pom.xml 到 templates 目录的每一层意图2.1 为什么选 SpringBoot 而不是 SpringMVC 原生——pom.xml 里的 5 个关键依赖真相打开pom.xml最核心的不是spring-boot-starter-web而是这 5 行dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.2.2/version /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency提示spring-boot-starter-jdbc是关键伏笔——它让 MyBatis 不需要 XML 配置就能自动绑定 DataSource而 H2 和 MySQL 同时存在意味着项目默认走 H2内存库启动快但只要把application.yml里spring.datasource.url换成 MySQL 地址再删掉h2依赖整个系统立刻切到真实数据库零 SQL 重写。常见误区是以为mybatis-spring-boot-starter会自动扫描 Mapper 接口——其实它只负责注入 SqlSessionFactory真正的扫描靠MapperScan(com.example.supermarket.mapper)注解这个注解在SupermarketApplication.java主类上写着。如果你删了它启动时会报Could not find mapper interface但错误日志里根本不会提“扫描路径”只会说BeanCreationException——这是新手翻车第一现场。2.2 目录结构即设计语言templates/ 与 static/ 的分工铁律项目前端部分严格遵循 SpringBoot 默认约定src/main/resources/ ├── application.yml # 主配置含数据库、服务器端口、Thymeleaf 开关 ├── sql/ │ └── init.sql # H2 初始化脚本建库、建表、插 3 条测试数据 └── static/ ├── css/ │ └── style.css # 全局样式仅覆盖基础排版无 Bootstrap └── js/ └── main.js # 商品搜索的 debounce 实现防连续请求 src/main/resources/templates/ ├── index.html # 首页商品总览卡片 最近 5 条进货记录 ├── goods/ │ ├── list.html # 商品列表页分页 搜索框 “新增”按钮 │ └── edit.html # 商品编辑页form 表单methodpostaction/goods/save ├── employees/ │ └── list.html # 员工列表带“启用/禁用”开关AJAX PATCH └── layout.html # Thymeleaf 布局模板定义 header/footer用 th:fragmentcontent 插入子页注意static/和templates/的本质区别static/css/style.css是纯静态资源浏览器直取不经过 Spring MVC 拦截器所以不能用Value(${app.version})注入变量templates/goods/list.html是 Thymeleaf 模板渲染时可调用serviceBean、读取Controller传来的 Model 数据比如${goods}就是 Controller 里model.addAttribute(goods, goodsList)放进去的 List。layout.html的写法值得抄作业!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head title th:text${title} ?: 超市管理系统默认标题/title /head body header th:fragmentheader h1超市信息管理系统 v1.0/h1 nava href/首页/a | a href/goods/list商品管理/a/nav /header main th:fragmentcontent !-- 子页面通过 th:replace~{::content} 注入此处 -- /main footer th:fragmentfooter pcopy; 2023 超市后台 · 仅供学习交流/p /footer /body /html子页goods/list.html只需写!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org xmlns:layouthttp://www.ultraq.net.nz/thymeleaf/layout layout:decorate~{layout} head title商品列表/title /head body div layout:fragmentcontent h2商品管理/h2 table tr th:eachg : ${goods} td th:text${g.name}商品名/td td th:text${g.price} 元价格/td tda th:href{/goods/edit(id${g.id})}编辑/a/td /tr /table /div /body /html这种结构让 UI 修改和业务逻辑彻底解耦改样式动static/css/改页面结构动templates/改数据逻辑动controller/mapper/service——三者互不影响。2.3 数据库设计init.sql 里藏着的 4 张表与 2 个设计权衡resources/sql/init.sql文件虽小但暴露了作者对小型超市业务的理解深度。它建了 4 张表-- 商品表price 用 DECIMAL(10,2) 而非 FLOAT避免 0.10.20.30000000000000004 CREATE TABLE goods ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, stock INT NOT NULL DEFAULT 0, category VARCHAR(50), created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 员工表status 字段用 TINYINT(1) 代替 ENUM兼容 MySQL 5.7 且方便 JDBC 映射 CREATE TABLE employees ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, phone VARCHAR(20), status TINYINT(1) DEFAULT 1 -- 1启用, 0禁用 ); -- 进货单主表关联员工记录谁在什么时候进了多少货 CREATE TABLE purchase_order ( id BIGINT AUTO_INCREMENT PRIMARY KEY, employee_id BIGINT NOT NULL, order_date DATE NOT NULL, total_amount DECIMAL(12,2) DEFAULT 0.00 ); -- 进货单明细表一对多解决“一单进多种商品”问题 CREATE TABLE purchase_order_item ( id BIGINT AUTO_INCREMENT PRIMARY KEY, order_id BIGINT NOT NULL, goods_id BIGINT NOT NULL, quantity INT NOT NULL, unit_price DECIMAL(10,2) NOT NULL );两个关键设计权衡点没做库存扣减事务purchase_order_item插入后并不自动更新goods.stock。这是故意为之——作者在PurchaseOrderService.java里留了 TODO 注释“TODO: 库存同步需加事务控制当前为简化演示”。这意味着你如果直接改数据库库存数可能和实际不符但代码层面没锁表适合教学场景快速验证流程不适合高并发入库。员工状态用 TINYINT 而非布尔Java 里映射为Integer避免某些 MySQL 驱动把TINYINT(1)当 Boolean 导致true/false误判尤其在旧版驱动中。这是血泪经验某次升级 MySQL 驱动后所有禁用员工突然变启用查了 3 小时才发现是类型映射 bug。3. 启动与调试全流程从双击 jar 到远程断点的 7 步实操3.1 本地一键启动H2 内存库模式的 3 个隐藏开关项目默认使用 H2 内存数据库无需安装 MySQL 即可运行。启动只需一步java -jar supermarket-system-1.0.jar但必须确保application.yml中这 3 项配置处于激活状态spring: datasource: url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY-1;DB_CLOSE_ON_EXITFALSE driver-class-name: org.h2.Driver username: sa password: h2: console: enabled: true # 关键开启 H2 控制台 path: /h2-console # 访问 http://localhost:8080/h2-console 即可查库 thymeleaf: cache: false # 关键关闭模板缓存改 HTML 不用重启注意DB_CLOSE_DELAY-1表示 JVM 进程不退出时 H2 不关闭连接DB_CLOSE_ON_EXITFALSE防止应用关闭时清空内存库——否则每次重启商品数据全丢。这两个参数是 H2 内存库能“持久化”到本次运行周期的关键。访问http://localhost:8080/h2-console后填入JDBC URL:jdbc:h2:mem:testdbUser Name:saPassword: 空点 Connect就能看到GOODS,EMPLOYEES等表甚至执行UPDATE GOODS SET PRICE 9.9 SET NAME 矿泉水 WHERE ID 1实时改价——这是比改 Java 代码更快的调试方式。3.2 切换 MySQL 真实库5 行配置 1 个 SQL 文件的最小改动要换成 MySQL只需 5 步在 MySQL 中新建数据库CREATE DATABASE supermarket DEFAULT CHARSET utf8mb4;修改application.ymlspring: datasource: url: jdbc:mysql://localhost:3306/supermarket?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_mysql_password # 替换为你的真实密码 driver-class-name: com.mysql.cj.jdbc.Driver # 删除或注释掉 h2.console 配置 # thymeleaf.cache: true # 生产环境建议开启删除pom.xml中 H2 依赖保留 MySQL 依赖!-- dependency groupIdcom.h2database/groupId artifactIdh2/artifactId scoperuntime/scope /dependency --把resources/sql/init.sql中的 H2 语法转为 MySQLAUTO_INCREMENT保持不变MySQL 原生支持TIMESTAMP DEFAULT CURRENT_TIMESTAMP改为TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMPMySQL 5.7 兼容执行该 SQL 到 MySQL 数据库中重启应用java -jar supermarket-system-1.0.jar此时所有数据将持久化到 MySQL。提示serverTimezoneAsia/Shanghai是必加参数否则 MySQL 8.0 会报The server time zone value XXX is unrecognized错误——这是国内开发者踩坑率最高的 JDBC 连接参数。3.3 远程调试实战IDEA 里 3 分钟配好 5005 端口断点项目pom.xml已预埋远程调试参数但需手动触发plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration jvmArguments -agentlib:jdwptransportdt_socket,servery,suspendn,address*:5005 /jvmArguments /configuration /plugin在 IDEA 中配置 Remote JVM Debug点击右上角Add Configuration→→Remote JVM Debug填写Host:localhostPort:5005Module classpath: 选择本项目模块启动 jar 时加-agentlib参数或直接用 Maven 命令mvn spring-boot:run -Dspring-boot.run.jvmArguments-agentlib:jdwptransportdt_socket,servery,suspendn,address*:5005在GoodsController.java的list()方法第一行打断点点击 Debug 按钮访问http://localhost:8080/goods/listIDEA 自动停在断点此时你能看到Model model对象里goodsList 的真实 size甚至右键goods.get(0)→Evaluate Expression查看商品详情——这才是调试的正确姿势比 System.out.println() 快 10 倍。4. 核心模块代码逐行解析从商品列表到库存扣减的完整链路4.1 GoodsControllerRESTful 风格路由与 Model 传递的 4 个关键点com.example.supermarket.controller.GoodsController.java是流量入口其设计体现 SpringBoot 最佳实践Controller RequestMapping(/goods) public class GoodsController { Autowired private GoodsService goodsService; // 1. GET /goods/list → 返回商品列表页Thymeleaf 渲染 GetMapping(/list) public String list(Model model) { ListGoods goodsList goodsService.findAll(); // 调用 service 层 model.addAttribute(goods, goodsList); // 绑定到模板变量 return goods/list; // 返回模板名不带 .html } // 2. GET /goods/edit?id1 → 返回编辑页查单条商品 GetMapping(/edit) public String edit(RequestParam Long id, Model model) { Goods goods goodsService.findById(id); model.addAttribute(goods, goods); return goods/edit; } // 3. POST /goods/save → 处理表单提交Valid 启用校验 PostMapping(/save) public String save(Valid ModelAttribute Goods goods, BindingResult result, RedirectAttributes redirectAttributes) { if (result.hasErrors()) { return goods/edit; // 校验失败返回编辑页错误信息自动注入 } goodsService.save(goods); redirectAttributes.addFlashAttribute(message, 保存成功); return redirect:/goods/list; // 重定向防重复提交 } // 4. POST /goods/delete?id1 → 删除商品返回 JSON前后端分离预备 PostMapping(/delete) ResponseBody public MapString, Object delete(RequestParam Long id) { goodsService.delete(id); MapString, Object response new HashMap(); response.put(success, true); return response; } }关键点解析ModelAttribute的妙用save()方法参数Goods goods自动绑定表单字段name, price, stock无需RequestParam逐个写——前提是表单 input 的name属性与 Goods 类字段名一致如input namename→goods.setName()。BindingResult必须紧跟Valid参数之后Spring 规定校验结果对象必须紧挨被校验对象否则result.hasErrors()永远为 false。这是新手写校验时 90% 的失败原因。RedirectAttributes.addFlashAttribute()让message在重定向后仍能被下个请求读取Thymeleaf 中用${message}取出这是实现“操作成功提示”的标准方案。ResponseBody与Controller共存同一 Controller 既服务页面Thymeleaf又提供 APIJSON为后续接入 Vue/React 预留接口——不需要新建RestController。4.2 GoodsMapper 与 MyBatis XML动态 SQL 与 resultMap 的精准控制MyBatis 配置分散在两处接口GoodsMapper.java和 XMLmapper/GoodsMapper.xml。这种分离让 SQL 与 Java 逻辑解耦GoodsMapper.javaMapper public interface GoodsMapper { ListGoods findAll(); Goods findById(Param(id) Long id); void insert(Goods goods); void update(Goods goods); void delete(Param(id) Long id); }对应mapper/GoodsMapper.xml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.supermarket.mapper.GoodsMapper !-- 1. resultMap 定义字段映射解决数据库下划线转驼峰 -- resultMap idGoodsResultMap typecom.example.supermarket.entity.Goods id propertyid columnid/ result propertyname columnname/ result propertyprice columnprice/ result propertystock columnstock/ result propertycategory columncategory/ result propertycreatedTime columncreated_time/ !-- 数据库字段 created_time → Java 字段 createdTime -- /resultMap !-- 2. findAll 使用 resultMap避免手写 AS 别名 -- select idfindAll resultMapGoodsResultMap SELECT * FROM goods ORDER BY created_time DESC /select !-- 3. 动态 SQLupdate 语句只更新非 null 字段 -- update idupdate parameterTypecom.example.supermarket.entity.Goods UPDATE goods set if testname ! null and name ! name #{name},/if if testprice ! nullprice #{price},/if if teststock ! nullstock #{stock},/if if testcategory ! null and category ! category #{category},/if /set WHERE id #{id} /update /mapper提示set标签会自动删除末尾逗号if标签实现“有值才更新”避免把空字符串写进数据库。这是 MyBatis 动态 SQL 的核心价值——比手拼 SQL 字符串安全 10 倍。4.3 GoodsService事务边界与库存扣减的伪原子性实现GoodsService.java是业务逻辑中枢其Transactional注解定义了事务范围Service Transactional public class GoodsService { Autowired private GoodsMapper goodsMapper; Autowired private PurchaseOrderItemMapper orderItemMapper; public ListGoods findAll() { return goodsMapper.findAll(); } public Goods findById(Long id) { return goodsMapper.findById(id); } public void save(Goods goods) { if (goods.getId() null) { goodsMapper.insert(goods); } else { goodsMapper.update(goods); } } // 伪原子性扣减先查库存再判断再更新 public boolean reduceStock(Long goodsId, Integer quantity) { Goods goods goodsMapper.findById(goodsId); if (goods null || goods.getStock() quantity) { return false; // 库存不足 } goods.setStock(goods.getStock() - quantity); goodsMapper.update(goods); return true; } }这里有个重要事实reduceStock()方法没有加Transactional它继承了类级别的注解但它的“查-判-改”三步并非真正原子——在高并发下两个线程同时查到stock10都判断105成立然后都执行stock5最终库存变成5而非0。这是典型的“超卖”漏洞。修复方案不改架构的前提下// 在 GoodsMapper.xml 中加一条乐观锁更新 update idreduceStockWithLock parameterTypemap UPDATE goods SET stock stock - #{quantity} WHERE id #{goodsId} AND stock #{quantity} /update然后在 Service 中调用public boolean reduceStock(Long goodsId, Integer quantity) { int updated goodsMapper.reduceStockWithLock(goodsId, quantity); return updated 0; // updated1 表示扣减成功0 表示库存不足 }这种基于 WHERE 条件的更新由数据库保证原子性是中小型系统最经济的库存控制方案。5. 避坑指南上线前必须验证的 4 类典型故障5.1 现象启动时报Failed to configure a DataSource原因application.yml中spring.datasource配置缺失或格式错误例如忘了缩进YAML 对空格敏感url:前多了空格driver-class-name写成driverClassNameSpring Boot 2.4 强制要求带连字符H2 的 JDBC URL 写成jdbc:h2:mem:testdb但没加DB_CLOSE_DELAY-1导致连接被提前关闭解决用 YAML 校验网站如 https://yamlchecker.com/粘贴配置检查语法确保spring.datasource.url以jdbc:开头且驱动类名与依赖版本匹配H2 用org.h2.DriverMySQL 8.0 用com.mysql.cj.jdbc.Driver在application.yml顶部加spring.profiles.active: dev并在application-dev.yml中单独写数据库配置避免主配置混乱5.2 现象Thymeleaf 页面显示${goods.name}而非真实值原因Thymeleaf 模板未被正确解析常见于templates/目录不在src/main/resources/下应为src/main/resources/templates/application.yml中spring.thymeleaf.enabled设为false默认 true但有人会手动关Controller 返回goods/list时实际文件是list.html但放在templates/goods/外层解决检查项目结构是否符合 SpringBoot 默认约定IDEA 中src/main/resources必须标为 Resources Root在application.yml中显式开启spring: thymeleaf: enabled: true cache: false在 Controller 中临时加日志System.out.println(goods size: goodsList.size());确认数据已查出排除 service 层问题5.3 现象MySQL 中文乱码商品名显示???原因MySQL 服务端、数据库、表、连接四层编码不统一。即使建库时指定了utf8mb4JDBC 连接参数也可能覆盖它。解决登录 MySQL 执行SHOW VARIABLES LIKE character_set%; SHOW VARIABLES LIKE collation%;确保character_set_server和collation_server为utf8mb4修改application.yml的 JDBC URL强制指定编码spring: datasource: url: jdbc:mysql://localhost:3306/supermarket?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai对已有表执行ALTER TABLE goods CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;5.4 现象远程调试连不上 5005 端口提示Connection refused原因pom.xml中spring-boot-maven-plugin的jvmArguments未生效Maven 版本过低或插件配置错位防火墙拦截了 5005 端口Windows Defender 或公司网络策略IDEA 的 Remote JVM Debug 配置中 Host 写成了127.0.0.1但 jar 是用address*:5005启动的*表示监听所有网卡127.0.0.1只监听本地回环解决改用命令行启动并确认参数生效java -agentlib:jdwptransportdt_socket,servery,suspendn,address*:5005 -jar supermarket-system-1.0.jarWindows 下临时关闭防火墙或添加入站规则放行 TCP 5005IDEA 中 Remote JVM Debug 的 Host 改为localhost与address*:5005兼容6. 二次开发实战给商品列表页加 Excel 导出功能的 3 种实现路径6.1 路径一Apache POI —— 零依赖、纯 Java、适合小数据量这是最轻量的方案不引入新依赖直接用 JDK 写.xlsExcel 97-2003 格式在GoodsController.java中新增导出方法GetMapping(/export) public void export(HttpServletResponse response) throws IOException { ListGoods goodsList goodsService.findAll(); // 创建工作簿 HSSFWorkbook workbook new HSSFWorkbook(); HSSFSheet sheet workbook.createSheet(商品列表); // 表头 HSSFRow headerRow sheet.createRow(0); String[] headers {ID, 名称, 价格, 库存, 分类, 创建时间}; for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } // 数据行 int rowNum 1; for (Goods g : goodsList) { HSSFRow row sheet.createRow(rowNum); row.createCell(0).setCellValue(g.getId()); row.createCell(1).setCellValue(g.getName()); row.createCell(2).setCellValue(g.getPrice().doubleValue()); row.createCell(3).setCellValue(g.getStock()); row.createCell(4).setCellValue(g.getCategory() ! null ? g.getCategory() : ); row.createCell(5).setCellValue(new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(g.getCreatedTime())); } // 设置响应头 response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment; filenamegoods_export.xls); workbook.write(response.getOutputStream()); }在goods/list.html中加导出按钮a href/goods/export classbtn btn-primary导出 Excel/a注意POI 3.17 对应HSSFWorkbook.xls若要支持.xlsxExcel 2007需升级 POI 到 4.x 并用XSSFWorkbook但会增大 jar 包体积。对于超市系统.xls完全够用且兼容性更好。6.2 路径二EasyExcel —— 自动映射、支持大文件、一行代码搞定如果数据量超过 1 万行POI 内存占用会飙升。EasyExcel 是阿里巴巴开源的流式 Excel 处理库内存友好加依赖替换pom.xml中的 POIdependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.2/version /dependency新建 Excel 实体类GoodsExportData.javaData public class GoodsExportData { ExcelProperty(商品ID) private Long id; ExcelProperty(商品名称) private String name; ExcelProperty(价格元) private BigDecimal price; ExcelProperty(库存数量) private Integer stock; ExcelProperty(商品分类) private String category; ExcelProperty(创建时间) DateTimeFormat(yyyy-MM-dd HH:mm:ss) private Date createdTime; }Controller 中改写导出方法GetMapping(/export-easy) public void exportEasy(HttpServletResponse response) throws IOException { ListGoods goodsList goodsService.findAll(); ListGoodsExportData exportList goodsList.stream() .map(g - { GoodsExportData data new GoodsExportData(); data.setId(g.getId()); data.setName(g.getName()); data.setPrice(g.getPrice()); data.setStock(g.getStock()); data.setCategory(g.getCategory()); data.setCreatedTime(g.getCreatedTime()); return data; }) .collect(Collectors.toList()); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setCharacterEncoding(utf-8); String fileName URLEncoder.encode(商品导出, UTF-8).replaceAll(\\, %20); response.setHeader(Content-disposition, attachment;filename*utf-8 fileName .xlsx); EasyExcel.write(response.getOutputStream(), GoodsExportData.class) .sheet(商品列表) .doWrite(exportList); }6.3 路径三前端 SheetJS —— 浏览器内生成不走后端零服务器压力如果只是导出当前页数据非全部用前端 JS 更高效在templates/layout.html的head中引入 SheetJSscript srchttps://cdn.jsdelivr.net/npm/xlsx0.18.5/dist/xlsx.full.min.js/script在goods/list.html底部加 JSscript function exportToExcel() { // 构造二维数组第一行是表头后面是数据 const data [[ID, 名称, 价格, 库存, 分类, 创建时间]]; document.querySelectorAll(#goods-table tbody tr).forEach(row { const rowData []; row.querySelectorAll(td:not(:last-child), th:not(:last-child)).forEach(cell { rowData.push(cell.textContent.trim()); }); data.push(rowData); }); // 创建工作簿并导出 const ws XLSX.utils.aoa_to_sheet(data); const wb XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 商品列表); XLSX.writeFile(wb, goods_current_page.xlsx); } /script button onclickexportToExcel() classbtn btn-success导出当前页/button三种路径对比方案适用场景优点缺点Apache POI数据量 5000 行无额外依赖需求纯 Java学习成本低内存占用高不支持大文件EasyExcel数据量 1 万行需导出全量内存友好自动映射社区活跃需引入新依赖SheetJS只导出当前页服务器零压力前端完成响应极快无法导出分页后的全部数据从那以后我每次加导出功能都强制走一遍这三步验证先用 POI 快速验证流程再用 EasyExcel 压测大数据最后用 SheetJS 给用户加个“当前页快捷导出”按钮——三个方案不是替代关系而是互补组合。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询