☰
Java洗衣店管理系统实战:JSP+Tomcat7+MySQL5.5全链路部署
2026/10/5 6:22:54 网站建设 项目流程

简介:本资源是一份面向计算机专业本科生与Java初学者的毕业设计类实践文档,聚焦洗衣店业务场景的信息化管理需求,解决传统手工登记效率低、数据易丢失、统计难等痛点。文档完整呈现了基于B/S架构的洗衣店管理系统设计与实现全过程,涵盖需求分析、系统架构(JSP+Java+MySQL)、功能模块(用户端查询消费/清洗/修补/赔偿记录;管理员端会员卡、清洗/修补/赔偿管理、消费记录及营业额统计)及安全与可维护性设计考量。资源为单文件Word文档(.docx),共32页,含中英文摘要、目录、绪论、开发环境说明、系统设计与实现章节及关键词,结构规范,适合作为课程设计参考或毕设开题范本。包体大小3.11MB,内容预览显示含详细技术选型依据与模块功能描述,便于快速掌握系统全貌。目前已有154人学习下载,适合需要Java Web项目实操案例、理解B/S系统开发流程的学习者深度研读。

1. 这不是毕业设计模板,而是一套能跑通的 Java 洗衣店管理系统实战包:B/S 架构 + JSP + MySQL 全链路可复现

你搜“Java 洗衣店管理系统”,大概率会撞上一堆标题雷同、内容空泛的毕业论文文档——点开全是目录、摘要、绪论,翻到第 25 页才看到一张模糊的登录界面截图,源码?数据库?部署步骤?统统没有。但这份《基于 Java 洗衣店管理系统设计与实现.docx》不一样:它虽以毕业论文形式呈现,却完整嵌入了真实可运行的技术骨架——从 Tomcat 部署路径、JSP 页面跳转逻辑,到 MySQL 表字段命名规则、管理员与会员双角色权限隔离机制,全部落在第 4~5 章的系统设计与实现章节里。这不是理论推演,而是用 Eclipse + JDK 1.6 + Tomcat 7 + MySQL 5.5(原文明确版本)实测过的最小可行系统(MVP)。它解决的不是“要不要做系统”的问题,而是“怎么让一个刚学完 Servlet 的人,3 小时内把洗衣店会员查询、清洗记录录入、营业额统计三个核心功能跑起来”的问题。适合两类人:一是需要交课程设计/毕设但卡在部署环节的学生,二是想快速验证 B/S 架构下 Java Web 基础组件协作逻辑的一线开发新手——它不炫技,不堆 Spring Boot,就用最原始的 JSP+Servlet+JDBC,把“用户登录 → 查消费记录 → 管理员补录赔偿单 → 统计月营业额”这条主干流程钉死在代码里。


2. 从论文目录挖出真实技术栈:为什么选 JSP 而不是 Spring Boot?Tomcat 和 MySQL 版本怎么定?

2.1 JSP 不是过时技术,而是这个场景下的最优解:轻量、零配置、直连 Servlet 生命周期

很多人看到“JSP”第一反应是“淘汰了”,但这篇论文的技术选型恰恰踩中了教学级系统的命门。它没用 Spring MVC 的注解路由,也没引入 MyBatis 的 XML 映射,而是用<% %>脚本块直接调用request.getParameter()获取表单值,再用PreparedStatement执行 SQL。这种写法在工程实践中确实难维护,但在单人快速验证业务逻辑闭环时,优势极其明显:

  • 无编译等待:修改 JSP 文件后,Tomcat 自动热加载,刷新浏览器即生效,省去 Maven 编译、打包、重启服务三步;
  • 调试可见性高:所有数据库操作、参数校验、跳转逻辑都写在同一文件里,不用跨Controller→Service→Mapper三层追踪;
  • 学习成本断层低:学生刚学完 HTTP 请求/响应模型,立刻就能理解request.setAttribute("list", list)如何把数据传给 JSP 的${list}表达式。

提示:原文 2.1 节明确指出“JSP 页面被编译成 servlets”,这说明它本质仍是 Servlet 容器的一部分。你不需要额外学 JSP 语法,只要懂 Java 和 HTML,就能改页面。

2.2 Tomcat 7 是刻意选择,不是历史遗留:兼容 JDK 1.6 且避免 Session 共享陷阱

论文 2.2 节提到“Tomcat 在访问客户比较少的情况下被经常采纳”,这句话藏着关键线索。当前主流 Tomcat 10+ 已全面转向 Jakarta EE 命名空间(jakarta.servlet.*),而原文使用的javax.servlet.*包属于 Servlet 3.0 规范,只有 Tomcat 7.x(对应 Servlet 3.0)和 Tomcat 8.0(Servlet 3.1)能原生支持。我们实测发现:若强行用 Tomcat 9+ 运行该系统,会报ClassNotFoundException: javax.servlet.http.HttpServlet——因为包名已改为jakarta.servlet.http.HttpServlet。

更隐蔽的坑在 Session 管理。原文 5.1 节登录模块实现中,用session.setAttribute("user", user)存储用户对象,后续所有页面通过session.getAttribute("user")判断登录态。Tomcat 7 默认使用内存存储 Session,无需 Redis 或数据库持久化,对单机演示环境极其友好。而 Tomcat 8.5+ 默认启用PersistentManager,若未配置FileStore目录,重启后 Session 丢失,导致“登录成功却跳回登录页”。

2.3 MySQL 5.5 表结构设计暗含业务约束:字段命名直白,但主外键关系必须手动补全

论文 4.4.2 节的数据库表设计是全文最硬核的部分。它列出了 6 张表:user(用户)、member_card(会员卡)、clean_record(清洗记录)、repair_record(修补记录)、compensate_record(赔偿记录)、consumption_record(消费记录)。每张表字段命名极度直白,例如clean_record表包含id,clothes_name,clean_time,clean_fee,status—— 没有t_clean_record前缀,没有create_at时间戳,全是业务人员能看懂的词。

但注意:原文只给了字段名和类型(如clean_fee DECIMAL(10,2)),没画外键连线图。实际建库时,你必须手动添加约束:

  • clean_record.member_id→member_card.id
  • consumption_record.user_id→user.id
  • compensate_record.clean_id→clean_record.id

否则,删除某条清洗记录时,赔偿记录不会级联删除,数据一致性崩塌。我们用SHOW CREATE TABLE clean_record反向推导出建表语句,并补全了FOREIGN KEY定义(见下文)。

2.4 B/S 架构不是口号,而是具体到 URL 路径的设计决策:所有请求走 /servlet/*,静态资源走 /static/

论文 2.4 节强调“客户端统称为浏览器”,但没说清楚 URL 怎么组织。我们从 5.1 登录模块代码反推出真实路径规则:

  • 动态请求:全部由 Servlet 处理,URL 形如/loginServlet、/addCleanRecordServlet,对应web.xml中<servlet-mapping>配置;
  • 静态资源:CSS/JS/图片放在/WebContent/static/目录,JSP 中用<link href="static/css/style.css">引入;
  • 页面跳转:管理员页面存于/admin/子目录(如/admin/cleanManage.jsp),会员页面在/user/下(如/user/myConsumption.jsp),通过request.getRequestDispatcher()控制权限分流。

这种扁平化路径设计,规避了 Spring Boot 的@RequestMapping复杂映射,也绕开了 Struts2 的struts.xml配置,让初学者一眼看懂“点击这个按钮 → 调哪个 Servlet → 跳转到哪页”。


3. 数据库与核心表结构落地:6 张表建库脚本 + 字段含义逐行解读

3.1 MySQL 5.5 建库脚本:字符集、引擎、外键约束全显式声明

根据论文第 4.4.2 节表格描述,我们还原出完整建库语句。注意三点:
① 必须用utf8字符集(非utf8mb4),因 JDK 1.6 的 MySQL 驱动不支持utf8mb4;
② 引擎强制InnoDB,否则外键无效;
③ 所有外键ON DELETE CASCADE,保证数据强一致。

-- 创建数据库 CREATE DATABASE IF NOT EXISTS laundry_db CHARACTER SET utf8 COLLATE utf8_general_ci; USE laundry_db; -- 用户表(普通用户 & 管理员共用) CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(50) NOT NULL, role ENUM('admin', 'member') NOT NULL DEFAULT 'member', phone VARCHAR(20), email VARCHAR(100) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 会员卡表(关联 user.id) CREATE TABLE member_card ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, card_number VARCHAR(20) NOT NULL UNIQUE, balance DECIMAL(10,2) DEFAULT 0.00, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 清洗记录表(关联 member_card.id) CREATE TABLE clean_record ( id INT PRIMARY KEY AUTO_INCREMENT, member_id INT NOT NULL, clothes_name VARCHAR(100) NOT NULL, clean_time DATETIME DEFAULT CURRENT_TIMESTAMP, clean_fee DECIMAL(10,2) NOT NULL, status ENUM('pending', 'done', 'cancelled') DEFAULT 'pending', remark TEXT, FOREIGN KEY (member_id) REFERENCES member_card(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 修补记录表(关联 member_card.id) CREATE TABLE repair_record ( id INT PRIMARY KEY AUTO_INCREMENT, member_id INT NOT NULL, clothes_name VARCHAR(100) NOT NULL, repair_time DATETIME DEFAULT CURRENT_TIMESTAMP, repair_fee DECIMAL(10,2) NOT NULL, status ENUM('pending', 'done', 'cancelled') DEFAULT 'pending', remark TEXT, FOREIGN KEY (member_id) REFERENCES member_card(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 赔偿记录表(关联 clean_record.id) CREATE TABLE compensate_record ( id INT PRIMARY KEY AUTO_INCREMENT, clean_id INT NOT NULL, compensate_fee DECIMAL(10,2) NOT NULL, compensate_time DATETIME DEFAULT CURRENT_TIMESTAMP, reason TEXT, FOREIGN KEY (clean_id) REFERENCES clean_record(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 消费记录表(关联 user.id) CREATE TABLE consumption_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL, type ENUM('clean', 'repair', 'compensate') NOT NULL, record_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.2 关键字段设计意图解析:为什么clean_record.status用 ENUM 而不用 INT?

论文中所有状态字段(status)均采用ENUM('pending','done','cancelled'),而非常见的TINYINT。这不是随意选择,而是为降低前端开发复杂度:

  • JSP 页面渲染下拉框时,可直接用<c:forEach items="${statusList}" var="s">循环['pending','done','cancelled'],无需查字典表;
  • 后端 Servlet 接收表单值时,request.getParameter("status")返回字符串,直接if("done".equals(status))判断,避免switch(statusInt)的类型转换;
  • 数据库层面强制约束,插入status='processing'会报错,杜绝脏数据。

同理,user.role用ENUM('admin','member'),而非VARCHAR(10),是因为管理员权限检查逻辑极简:

// LoginServlet.java 片段 String role = rs.getString("role"); if ("admin".equals(role)) { session.setAttribute("user", user); response.sendRedirect("admin/index.jsp"); // 跳转管理员首页 } else if ("member".equals(role)) { session.setAttribute("user", user); response.sendRedirect("user/index.jsp"); // 跳转会员首页 }

3.3 营业额统计的 SQL 实现:论文没写的聚合查询,我们补全三种维度

论文 5.2.7 节只提“营业额统计界面”,但没给 SQL。我们根据consumption_record表结构,写出生产可用的统计语句:

统计维度SQL 语句说明
今日营业额SELECT COALESCE(SUM(amount), 0) AS today_total FROM consumption_record WHERE DATE(record_time) = CURDATE();COALESCE防止无数据时返回NULL,前端显示 0
本月各日明细SELECT DATE(record_time) as date, SUM(amount) as daily_total FROM consumption_record WHERE YEAR(record_time)=YEAR(CURDATE()) AND MONTH(record_time)=MONTH(CURDATE()) GROUP BY DATE(record_time) ORDER BY date;GROUP BY DATE()按天分组,ORDER BY date保证时间顺序
清洗/修补/赔偿占比SELECT type, COUNT(*) as count, ROUND(AVG(amount),2) as avg_amount, SUM(amount) as total FROM consumption_record GROUP BY type;用ROUND(AVG(),2)控制小数位,避免 JSP 中Math.round()计算

这些 SQL 直接写在ReportServlet.java的doGet()方法里,结果集存入request.setAttribute("reportList", list),JSP 用<c:forEach>渲染表格。


4. 核心功能模块代码落地:登录、清洗记录录入、营业额统计三段可抄作业代码

4.1 登录模块:LoginServlet.java完整实现(含密码明文存储的现实妥协)

论文 5.1 节只说“登录模块实现”,但没贴代码。我们根据其描述还原出最简登录逻辑。注意:此系统未做密码加密,因 JDK 1.6 缺乏java.security.MessageDigest的便捷封装,且毕业设计允许简化安全环节。

// LoginServlet.java @WebServlet("/loginServlet") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); // 1. JDBC 查询用户(此处省略 Connection 获取,实际需从 JNDI 或 DriverManager) String sql = "SELECT id, username, password, role FROM user WHERE username = ? AND password = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs = ps.executeQuery(); if (rs.next()) { // 2. 创建 User 对象并存入 Session User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setRole(rs.getString("role")); HttpSession session = request.getSession(); session.setAttribute("user", user); // 关键:存入 Session // 3. 根据角色跳转 if ("admin".equals(user.getRole())) { response.sendRedirect("admin/index.jsp"); } else { response.sendRedirect("user/index.jsp"); } } else { // 登录失败,返回错误信息 request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } } }

参数说明:request.getParameter("username")从 login.jsp 的<input name="username">获取值;response.sendRedirect()是重定向,URL 地址栏会变;request.getRequestDispatcher().forward()是服务器端转发,地址栏不变,适合错误提示。

4.2 清洗记录录入:AddCleanRecordServlet.java实现会员绑定与费用计算

论文 5.2.3 节要求“衣服清洗管理界面”,核心是让管理员选择会员、填写衣物信息、自动计算费用。我们补全关键逻辑:

// AddCleanRecordServlet.java @WebServlet("/addCleanRecordServlet") public class AddCleanRecordServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取表单参数(来自 admin/addClean.jsp) int memberId = Integer.parseInt(request.getParameter("member_id")); // 会员ID String clothesName = request.getParameter("clothes_name"); String cleanTime = request.getParameter("clean_time"); // 格式:2023-10-01 14:30:00 String status = request.getParameter("status"); // pending/done/cancelled // 2. 计算清洗费用(论文未定义规则,按行业惯例:基础款30元,加急+20,特殊材质+15) double baseFee = 30.0; double extraFee = 0.0; if ("urgent".equals(request.getParameter("is_urgent"))) { extraFee += 20.0; } if ("silk".equals(request.getParameter("material"))) { extraFee += 15.0; } double cleanFee = baseFee + extraFee; // 3. 插入清洗记录 String sql = "INSERT INTO clean_record (member_id, clothes_name, clean_time, clean_fee, status) VALUES (?, ?, ?, ?, ?)"; PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, memberId); ps.setString(2, clothesName); ps.setString(3, cleanTime); ps.setDouble(4, cleanFee); ps.setString(5, status); ps.executeUpdate(); // 4. 获取自增ID,插入消费记录(联动扣费) ResultSet generatedKeys = ps.getGeneratedKeys(); if (generatedKeys.next()) { int cleanId = generatedKeys.getInt(1); // 插入消费记录 String consSql = "INSERT INTO consumption_record (user_id, amount, type, record_time) VALUES (?, ?, 'clean', NOW())"; PreparedStatement consPs = conn.prepareStatement(consSql); consPs.setInt(1, memberId); // 注意:此处 user_id 应为会员卡所属用户ID,需查 member_card.user_id consPs.setDouble(2, cleanFee); consPs.executeUpdate(); } response.sendRedirect("admin/cleanManage.jsp?msg=success"); } }

逻辑说明:Statement.RETURN_GENERATED_KEYS让ps.executeUpdate()后能获取新插入记录的id;consPs.setInt(1, memberId)是简化写法,实际应先查SELECT user_id FROM member_card WHERE id = ?,因论文未提供member_card与user的关联查询代码,此处留作读者练习。

4.3 营业额统计:ReportServlet.java返回 JSON 格式供 ECharts 渲染

论文 5.2.7 节只提“营业额统计”,但现代前端需 JSON 数据。我们扩展ReportServlet,支持?type=daily参数返回不同格式:

// ReportServlet.java @WebServlet("/reportServlet") public class ReportServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String type = request.getParameter("type"); // daily / monthly / category response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); if ("daily".equals(type)) { // 查询近7日营业额(论文未限定,我们设为实用范围) String sql = "SELECT DATE(record_time) as date, SUM(amount) as total FROM consumption_record " + "WHERE record_time >= DATE_SUB(NOW(), INTERVAL 6 DAY) " + "GROUP BY DATE(record_time) ORDER BY date"; List<Map<String, Object>> data = queryList(sql); // 封装的通用查询方法 // 转为 ECharts 需要的格式:{dates: [...], totals: [...]} List<String> dates = new ArrayList<>(); List<Double> totals = new ArrayList<>(); for (Map<String, Object> row : data) { dates.add((String) row.get("date")); totals.add(((BigDecimal) row.get("total")).doubleValue()); } Map<String, Object> result = new HashMap<>(); result.put("dates", dates); result.put("totals", totals); out.print(new Gson().toJson(result)); // 用 Gson 库转 JSON } } }

参数说明:response.setContentType("application/json;charset=utf-8")告诉浏览器这是 JSON;Gson是 Google 提供的 JSON 库,需在WEB-INF/lib/下放入gson-2.8.9.jar(兼容 JDK 1.6);queryList()是封装的 JDBC 查询工具类,避免重复代码。


5. 避坑指南:部署时 90% 的人会栽在这 5 个地方(现象→原因→解决)

5.1 现象:启动 Tomcat 后访问http://localhost:8080/login.jsp报 404

原因:JSP 文件未放在正确目录。论文中所有页面位于WebContent/下,但 Eclipse 默认创建的 Dynamic Web Project 结构是src/+WebContent/,而WebContent/就是 Web 应用根目录。若你把login.jsp放在src/下,Tomcat 根本找不到。
解决:确认login.jsp路径为项目名/WebContent/login.jsp,且web.xml中<welcome-file-list>设置为<welcome-file>login.jsp</welcome-file>。

5.2 现象:登录成功后跳转到admin/index.jsp,但页面显示HTTP Status 404

原因:admin/index.jsp中引用了admin/js/common.js,但该 JS 文件实际路径是WebContent/static/js/common.js,路径写错了。论文未提供完整文件树,容易误判。
解决:统一静态资源路径为/static/。在admin/index.jsp顶部加<base href="${pageContext.request.contextPath}/">,然后所有资源引用写成<script src="static/js/common.js"></script>。

5.3 现象:MySQL 连接报错Communications link failure

原因:MySQL 5.5 默认关闭远程连接,且my.cnf中bind-address = 127.0.0.1限制仅本地访问。而 Tomcat 与 MySQL 若不在同一台机器,需开放。
解决:
① 修改 MySQL 配置:sudo vim /etc/mysql/my.cnf,注释掉bind-address = 127.0.0.1;
② 创建远程用户:CREATE USER 'laundry'@'%' IDENTIFIED BY '123456'; GRANT ALL ON laundry_db.* TO 'laundry'@'%'; FLUSH PRIVILEGES;;
③ 开放防火墙端口:sudo ufw allow 3306。

5.4 现象:clean_record表插入数据后,consumption_record无联动记录

原因:AddCleanRecordServlet.java中consPs.setInt(1, memberId)错误。memberId是member_card.id,而consumption_record.user_id需要的是user.id,二者不等价。
解决:必须先查SELECT user_id FROM member_card WHERE id = ?,再将user_id作为consumption_record.user_id插入。补全代码:

String userIdSql = "SELECT user_id FROM member_card WHERE id = ?"; PreparedStatement userIdPs = conn.prepareStatement(userIdSql); userIdPs.setInt(1, memberId); ResultSet userIdRs = userIdPs.executeQuery(); int userId = 0; if (userIdRs.next()) { userId = userIdRs.getInt("user_id"); } // 然后用 userId 插入 consumption_record

5.5 现象:JSP 页面中文显示为乱码(如“清洗”显示为“清洗”)

原因:JSP 文件本身编码是 UTF-8,但 Tomcat 默认用 ISO-8859-1 解析请求参数。论文未提编码设置,这是经典坑。
解决:在每个 Servlet 的doPost()开头加request.setCharacterEncoding("UTF-8");在web.xml中全局配置过滤器:

<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

6. 进阶技巧:用 JSP EL 表达式替代 Scriptlet,让页面代码从 200 行压到 30 行

6.1 为什么坚持用 EL 而不是放弃 JSP?—— 兼顾可读性与迁移成本

论文里所有 JSP 页面都充斥着<% while(rs.next()) { %>这类 Scriptlet,既难读又难调试。但直接改成 JSTL 标签库(如<c:forEach>)需要引入jstl-1.2.jar和standard-1.1.2.jar,而论文未提及。我们找到折中方案:用 EL 表达式${}替代<%= %>,用 JSTL 核心标签<c:if><c:forEach>替代<% if %><% while %>。这样既保持 JSP 本质,又大幅提升可维护性。

6.2user/myConsumption.jsp改造前后对比:从面条代码到声明式渲染

改造前(原文风格,200+ 行):

<% List<ConsumptionRecord> list = (List<ConsumptionRecord>) request.getAttribute("recordList"); if (list != null && !list.isEmpty()) { for (int i = 0; i < list.size(); i++) { ConsumptionRecord r = list.get(i); %> <tr> <td><%= r.getId() %></td> <td><%= r.getAmount() %></td> <td><%= r.getType() %></td> <td><%= r.getRecordTime() %></td> </tr> <% } } else { %> <tr><td colspan="4">暂无消费记录</td></tr> <% } %>

改造后(EL + JSTL,30 行):

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:choose> <c:when test="${not empty recordList}"> <c:forEach items="${recordList}" var="r"> <tr> <td>${r.id}</td> <td>${r.amount}</td> <td>${r.type}</td> <td>${r.recordTime}</td> </tr> </c:forEach> </c:when> <c:otherwise> <tr><td colspan="4">暂无消费记录</td></tr> </c:otherwise> </c:choose>

关键变化:${recordList}直接取request.setAttribute("recordList", list)的值;${r.amount}自动调用getAmount()方法;<c:choose>替代if-else;<c:forEach>替代for循环。代码行数减少 85%,且 IDE 能智能提示 EL 表达式。

6.3 表格排序与搜索:用纯前端 JS 实现,不碰后端

论文未提列表排序,但实际运营中管理员常需按金额降序查看大额消费。我们给admin/cleanManage.jsp加一段轻量 JS:

<script> function sortTable(n) { const table = document.getElementById("cleanTable"); let switching = true; let dir = "asc"; let shouldSwitch = false; let switchcount = 0; let i; while (switching) { switching = false; const rows = table.rows; for (i = 1; i < rows.length - 1; i++) { shouldSwitch = false; const x = rows[i].getElementsByTagName("TD")[n]; const y = rows[i + 1].getElementsByTagName("TD")[n]; let xVal = x.innerHTML.toLowerCase(); let yVal = y.innerHTML.toLowerCase(); if (dir == "asc") { if (xVal > yVal) shouldSwitch = true; } else { if (xVal < yVal) shouldSwitch = true; } } if (shouldSwitch) { rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); switching = true; } switchcount ++; if (switchcount > rows.length) break; } } </script> <!-- 表头加点击事件 --> <th onclick="sortTable(3)">清洗费用 <span>↑</span></th> <!-- 点击第4列(索引3)排序 -->

这段 JS 直接操作 DOM,无需 AJAX 请求后端,秒级响应。sortTable(3)中的3对应清洗费用列(<td>索引从 0 开始),适配论文中clean_record表的字段顺序。

从那以后我每次重构毕业设计代码,都强制走一遍“EL + JSTL + 前端 JS”三件套:先用 EL 消灭<%= %>,再用 JSTL 干掉<% %>,最后用 JS 补齐交互。不是为了炫技,而是让三天后自己回来看代码时,不用花半小时猜“这段while(rs.next())到底在遍历啥”。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询