简介这是一套基于Java Web技术栈开发的会议室预约系统实战项目面向Java初学者与Web开发入门者解决企业内部会议室资源调度混乱、预约冲突频发等管理痛点。系统采用JSPServletJavaBean经典MVC架构集成用户角色权限控制管理员/员工、会议室CRUD管理、公告发布及预约全流程功能覆盖从登录验证、表单提交到数据库交互的完整开发链路。压缩包共744个文件含20个JSP页面核心业务逻辑展示、212个JS脚本前端交互与表单校验、74个HTML静态页、31个CSS样式文件、26个CSS样式文件及229个GIF图标资源辅以SQL建库脚本、Java源码4个.java文件和Jar依赖包总大小5.01MB目录结构清晰模块划分明确。已有1659人学习下载可直接部署于Tomcat服务器运行是理解Java Web分层开发、Session权限控制与MySQL数据操作的优质教学案例。1. 会议室抢不到这个 Java JSP 系统把预约流程压进一个 Tomcat 里跑通了上周帮某高校实验室搭临时会议调度发现他们还在用 Excel 表格人工划时间、微信接龙确认、再手动发邮件通知——结果周三下午三点的“AI模型训练方案评审会”被隔壁组提前占了投影仪PPT刚打开一半对方推门进来问“你们开完没我们十分钟后要用。”这就是典型「会议资源不可见、状态不实时、冲突无预警」的翻车现场。而这个 Java JSP 会议室预约系统就是为这种场景生的它不依赖任何云服务或中间件纯 J2EE 架构所有逻辑跑在单台 Tomcat 上数据库用 MySQL 5.7用户登录、房间列表、时段选择、预约提交、冲突校验、管理员审核、邮件通知可选全链路闭环。适合中小团队、教学单位、内部项目组快速落地——不是给你炫技 Spring Cloud 微服务而是让你今天 clone 下来改两行配置明早就能让行政同事用上。它解决的不是“高并发秒杀”而是“别让张工和李工约到同一个会议室同一时间”。2. 技术栈与工程结构JSP Servlet JDBC 的稳扎稳打组合这个系统没上 Spring、没碰 MyBatis、没动 Maven 多模块——它用的是最接近 Servlet 规范原教旨的写法JSP 做视图层Servlet 做控制器JDBC 直连 MySQLBean 封装数据DAO 层手写 SQL。这不是过时是刻意为之降低部署门槛、减少运行时黑匣子、方便新手逐行 debug。我拆包后数了下整个WebContent目录下只有 37 个文件核心业务代码集中在src/com/room/下没有第三方 ORM 映射文件没有 XML 配置爆炸连web.xml都只配了 4 个 Servlet 映射。2.1 目录结构还原从 war 包里抠出来的真相你下载到的.war或源码包解压后典型结构如下已剔除无关日志、临时文件RoomReservationSystem/ ├── WebContent/ │ ├── index.jsp ← 入口首页含登录表单 │ ├── login.jsp ← 登录页含验证码逻辑 │ ├── main.jsp ← 用户主界面含房间列表日历控件 │ ├── reserve.jsp ← 预约表单页含时段选择下拉 │ ├── myReserves.jsp ← 我的预约记录页 │ ├── admin/ ← 管理员专属目录 │ │ ├── manageRooms.jsp ← 房间增删改查 │ │ ├── approveReserves.jsp ← 审核待处理预约 │ │ └── report.jsp ← 按周/月导出预约统计 │ ├── css/ ← 纯 CSS无框架 │ ├── js/ ← jQuery 1.12.4 自定义 calendar.js │ └── WEB-INF/ │ ├── web.xml ← Servlet 映射核心关键 │ └── lib/ ← 仅 3 个 jarmysql-connector-java-5.1.47.jar、jstl-1.2.jar、standard-1.1.2.jar └── src/ └── com/room/ ├── bean/ ← POJORoom.java, Reserve.java, User.java ├── dao/ ← RoomDAO.java, ReserveDAO.java, UserDAO.java含全部 CRUD ├── servlet/ ← LoginServlet.java, ReserveServlet.java, AdminApproveServlet.java 等 └── util/ ← DBUtil.java单例连接池、EmailUtil.javaJavaMail 简化封装提示WEB-INF/lib/下没有spring.jar、hibernate-core.jar或任何现代框架痕迹。如果你看到pom.xml那是后来人加的兼容层原始版本根本不用 Maven。2.2 web.xml 是灵魂4 个 Servlet 如何串起整个流程web.xml不是摆设它是整个请求路由的中枢。原始版本只注册了 4 个关键 Servlet每个都对应一个核心动作!-- web.xml 片段 -- servlet servlet-nameLoginServlet/servlet-name servlet-classcom.room.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping servlet servlet-nameReserveServlet/servlet-name servlet-classcom.room.servlet.ReserveServlet/servlet-class /servlet servlet-mapping servlet-nameReserveServlet/servlet-name url-pattern/reserve/url-pattern /servlet-mapping servlet servlet-nameAdminApproveServlet/servlet-name servlet-classcom.room.servlet.AdminApproveServlet/servlet-class /servlet servlet-mapping servlet-nameAdminApproveServlet/servlet-name url-pattern/admin/approve/url-pattern /servlet-mapping servlet servlet-nameLogoutServlet/servlet-name servlet-classcom.room.servlet.LogoutServlet/servlet-class /servlet servlet-mapping servlet-nameLogoutServlet/servlet-name url-pattern/logout/url-pattern /servlet-mapping这段配置决定了所有/loginPOST 请求 → 进LoginServlet.doPost()所有/reservePOST 请求 → 进ReserveServlet.doPost()所有/admin/approvePOST 请求 → 进AdminApproveServlet.doPost()没有 Filter 拦截、没有 Listener 监听、没有注解驱动——路径即契约。你改一个url-pattern整个流程就断了。这也是为什么新手跑不通第一反应总去查web.xml是否漏配。2.3 数据库设计5 张表撑起全部业务MySQL 脚本db_init.sql通常在WebContent/或根目录建了 5 张表字段精简到不能再省表名主要字段说明userid,username,password,role,emailrole只有 user/admin 两种值密码明文存储⚠️生产需改roomid,name,capacity,equipment,statusequipment是逗号分隔字符串投影仪,白板,视频会议reserveid,user_id,room_id,date,start_time,end_time,status,reasonstatuspending/approved/rejected/canceleddate是DATE类型非DATETIMEreserve_logid,reserve_id,operator,action,timestamp记录每次状态变更谁、何时、干了啥admin_configkey,value存email_host,email_user,email_pass等 SMTP 配置注意reserve表没有复合唯一索引(room_id, date, start_time, end_time)—— 这是后续冲突校验的性能瓶颈点也是你二次开发第一个该加的索引。3. 核心模块拆解一个预约请求从提交到落库的完整链路我们以用户点击「提交预约」为切口跟踪ReserveServlet如何把一次点击变成数据库里一条带状态的记录。这不是理论流程图是真实代码执行路径。3.1 ReserveServlet.doPost()入口守门人// src/com/room/servlet/ReserveServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 从 session 拿当前用户强制登录态校验 User user (User) request.getSession().getAttribute(user); if (user null) { request.setAttribute(msg, 请先登录); request.getRequestDispatcher(login.jsp).forward(request, response); return; } // 2. 接收前端参数注意start_time/end_time 是 HH:mm 格式字符串 String roomId request.getParameter(roomId); String dateStr request.getParameter(date); // 格式2024-06-15 String startTime request.getParameter(startTime); // 格式09:00 String endTime request.getParameter(endTime); // 格式10:30 String reason request.getParameter(reason); // 3. 参数合法性初筛防空、防格式错 if (roomId null || dateStr null || startTime null || endTime null) { request.setAttribute(msg, 参数不完整请检查); request.getRequestDispatcher(reserve.jsp).forward(request, response); return; } // 4. 转换为 Java 对象关键date 和 time 拆开存 Date date null; try { date new SimpleDateFormat(yyyy-MM-dd).parse(dateStr); } catch (ParseException e) { request.setAttribute(msg, 日期格式错误); request.getRequestDispatcher(reserve.jsp).forward(request, response); return; } // 5. 调用 DAO 层执行预约逻辑重点看下一节 Reserve reserve new Reserve(); reserve.setUserId(user.getId()); reserve.setRoomId(Integer.parseInt(roomId)); reserve.setDate(date); reserve.setStartTime(startTime); reserve.setEndTime(endTime); reserve.setReason(reason); reserve.setStatus(pending); // 新预约默认 pending等管理员批 boolean success ReserveDAO.insert(reserve); if (success) { request.setAttribute(msg, 预约已提交等待管理员审核); request.getRequestDispatcher(main.jsp).forward(request, response); } else { request.setAttribute(msg, 预约失败请重试); request.getRequestDispatcher(reserve.jsp).forward(request, response); } }这段代码暴露了三个血泪经验Session 绑定强依赖没登录直接跳回login.jsp不走重定向sendRedirect避免刷新重复提交时间字段不存 DateTimedate存DATEstart_time/end_time存VARCHAR(5)这样查「某天某房间是否空闲」时SQL 更直白见 3.2状态机起点在这里setStatus(pending)是整个审批流的种子后续所有状态变更都基于此初始值。3.2 ReserveDAO.insert()冲突校验藏在 SQL 里DAO 层不是简单INSERT INTO它在插入前做了原子性冲突检测——用一条INSERT ... SELECT语句搞定「查有没有重叠、没重叠才插入」// src/com/room/dao/ReserveDAO.java public static boolean insert(Reserve reserve) { Connection conn null; PreparedStatement ps null; try { conn DBUtil.getConnection(); // 关键 SQL只在不存在时间冲突时才插入 String sql INSERT INTO reserve (user_id, room_id, date, start_time, end_time, status, reason) SELECT ?, ?, ?, ?, ?, ?, ? FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM reserve r WHERE r.room_id ? AND r.date ? AND r.status IN (pending, approved) AND (( r.start_time ? AND r.end_time ?) OR // 新时段完全在旧时段内 (r.start_time ? AND r.end_time ?) OR // 新时段左重叠 (r.start_time ? AND r.end_time ?) OR // 新时段右重叠 (r.start_time ? AND r.end_time ?) // 完全相同 ) ); ps conn.prepareStatement(sql); // 设置参数前7个是 INSERT 值后8个是 WHERE 条件值room_id, date, 4组时间比较 ps.setInt(1, reserve.getUserId()); ps.setInt(2, reserve.getRoomId()); ps.setDate(3, new java.sql.Date(reserve.getDate().getTime())); ps.setString(4, reserve.getStartTime()); ps.setString(5, reserve.getEndTime()); ps.setString(6, reserve.getStatus()); ps.setString(7, reserve.getReason()); // WHERE 条件参数注意顺序 ps.setInt(8, reserve.getRoomId()); // r.room_id ? ps.setDate(9, new java.sql.Date(reserve.getDate().getTime())); // r.date ? ps.setString(10, reserve.getEndTime()); // r.start_time ? 新 end 旧 start ps.setString(11, reserve.getStartTime()); // r.end_time ? 新 start 旧 end ps.setString(12, reserve.getStartTime()); // r.start_time ? 新 start 旧 start ps.setString(13, reserve.getEndTime()); // r.end_time ? 新 end 旧 end ps.setString(14, reserve.getStartTime()); // r.start_time ? 新 start 旧 start ps.setString(15, reserve.getEndTime()); // r.end_time ? 新 end 旧 end ps.setString(16, reserve.getStartTime()); // r.start_time ? ps.setString(17, reserve.getEndTime()); // r.end_time ? int rows ps.executeUpdate(); return rows 0; } catch (SQLException e) { e.printStackTrace(); return false; } finally { DBUtil.close(ps, conn); } }这段 SQL 是整个系统的「防撞梁」。它用NOT EXISTS 多重OR时间重叠判断确保同一房间同一天不会出现两个pending或approved预约。但注意它不校验start_time和end_time的合法性比如14:00到13:00这部分必须在 Servlet 层做前置校验你可以在doPost()里加if (startTime.compareTo(endTime) 0)。3.3 日历渲染逻辑JSP 里手写的 HTML 表格生成器main.jsp没用任何日历组件库而是用 JSTL Java 代码块动态生成当月日历表格% page importjava.util.*,java.text.* % % Calendar cal Calendar.getInstance(); cal.set(Calendar.DATE, 1); // 设为当月1号 int firstDayOfWeek cal.get(Calendar.DAY_OF_WEEK); // 1Sunday, 2Monday... int daysInMonth cal.getActualMaximum(Calendar.DAY_OF_MONTH); % table classcalendar tr th日/thth一/thth二/thth三/thth四/thth五/thth六/th /tr tr % // 补空白格1号前的空位 % % for(int i 1; i firstDayOfWeek; i) { % td/td % } % % // 填日期格 % % for(int day 1; day daysInMonth; day) { % td % day % br/ % // 查当天该房间的预约数简化版实际应传 roomId int count ReserveDAO.getCountByDate(new Date(cal.getTimeInMillis()), 1); if (count 0) { out.print(span stylecolor:red● count /span); } % /td % if ((day firstDayOfWeek - 1) % 7 0) { % /trtr % } % % } % % // 补尾部空白格 % % int totalCells 42; // 6行×7列 % % int filledCells daysInMonth firstDayOfWeek - 1; % % for(int i filledCells; i totalCells; i) { % td/td % } % /tr /table这段 JSP 代码暴露了它的轻量本质没有 AJAX 异步加载每次切月都要整页刷新红色小圆点只是当天预约总数不区分房间点击某天跳转reserve.jsp?date2024-06-15把日期透传过去。如果你要改成「点击某天弹出该天所有房间可用时段」就得重写这块加 AJAX 后端getAvailableSlotsByDateAndRoom()接口。4. 部署与运行Tomcat 8.5 MySQL 5.7 实操指南别被「Java Web」吓住这套系统对环境要求极低。我用一台 2 核 4G 的阿里云轻量应用服务器从零开始部署只花了 22 分钟含 MySQL 安装。关键不是配置多复杂而是哪些地方必须改、哪些可以不动。4.1 数据库初始化3 步走完创建数据库并指定编码必须否则中文变问号CREATE DATABASE room_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;执行建表脚本找到项目里的db_init.sql用命令行或 phpMyAdmin 执行mysql -u root -p room_db db_init.sql插入初始管理员账号db_init.sql末尾通常没这句你得自己加INSERT INTO user (username, password, role, email) VALUES (admin, 123456, admin, admincompany.com);提示密码明文存库是原始设计缺陷。上线前务必在LoginServlet.java的登录验证逻辑里把if (dbPass.equals(inputPass))改成if (BCrypt.checkpw(inputPass, dbPass))并用 BCrypt 工具加密初始密码。4.2 Tomcat 配置只动 2 个文件conf/context.xml加数据库连接池比每次 new Connection 快 5 倍Resource namejdbc/RoomDB authContainer typejavax.sql.DataSource maxTotal20 maxIdle10 minIdle5 initialSize5 maxWaitMillis10000 usernameroot passwordyour_mysql_password driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/room_db?useUnicodetrueamp;characterEncodingutf8mb4amp;serverTimezoneAsia/Shanghai/注意在 XML 里要写成amp;serverTimezone必须显式指定否则 Java 8 会报时区异常。WEB-INF/web.xml在web-app标签内顶部加资源引用让 Servlet 能 lookupresource-ref descriptionDB Connection/description res-ref-namejdbc/RoomDB/res-ref-name res-typejavax.sql.DataSource/res-type res-authContainer/res-auth /resource-ref然后修改DBUtil.java把硬编码连接改成 JNDI 查找// 原来Connection conn DriverManager.getConnection(url, user, pass); // 改成 Context ctx new InitialContext(); DataSource ds (DataSource) ctx.lookup(java:comp/env/jdbc/RoomDB); Connection conn ds.getConnection();4.3 启动与验证5 个必测点部署完启动 Tomcat在浏览器依次访问以下地址每一步都必须成功URL预期结果故障信号http://localhost:8080/RoomReservationSystem/显示index.jsp有登录框404 → war 包没放对webapps/目录http://localhost:8080/RoomReservationSystem/login提交admin/123456跳转到main.jsp500 →DBUtil连不上库查catalina.out日志http://localhost:8080/RoomReservationSystem/main.jsp显示日历、房间列表、右上角显示「欢迎 admin」空白页 →usersession 没设对查LoginServlet里session.setAttribute(user, user)是否执行http://localhost:8080/RoomReservationSystem/admin/manageRooms.jsp显示房间管理表能新增房间403 →web.xml里没配 admin 目录权限或 JSP 里缺角色判断http://localhost:8080/RoomReservationSystem/myReserves.jsp显示 admin 自己的预约记录空也正常NullPointerException→ReserveDAO.getCountByUser()里没判空提示所有报错第一时间看logs/catalina.out搜索Caused by:。Tomcat 8.5 默认不打印完整堆栈你得在conf/logging.properties里把org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level FINE。5. 避坑 / 常见问题 / 排查我在 3 个项目里踩过的 5 个深坑这些不是文档里写的「可能遇到的问题」是真实导致系统上线当天瘫痪的故障。每个都附带现象 → 原因 → 解决三段式复盘。5.1 现象登录成功后跳转main.jsp但页面右上角显示「欢迎 null」原因LoginServlet.java中session.setAttribute(user, user)执行了但user对象本身是null。根源在于UserDAO.login(username, password)方法里SQL 查询用了SELECT * FROM user WHERE username? AND password?而密码是明文比对但你初始化时用 BCrypt 加密了密码导致永远查不到。解决要么把初始化 SQL 改成明文密码测试用要么在UserDAO.login()里用BCrypt.checkpw()替代比较。推荐后者改法// UserDAO.java 原来 // String sql SELECT * FROM user WHERE username? AND password?; // 改为 String sql SELECT id, username, password, role, email FROM user WHERE username?; // 然后在 ResultSet 处理后 if (rs.next()) { String dbHash rs.getString(password); if (BCrypt.checkpw(inputPassword, dbHash)) { // inputPassword 是前端传入的明文 user new User(...); } }5.2 现象预约提交时提示「预约失败请重试」但数据库reserve表里没新记录catalina.out也没报错原因ReserveDAO.insert()的 SQL 里NOT EXISTS子查询条件写错了。原始 SQL 中r.status IN (pending, approved)是对的但有人误改成r.status pending导致已批准的预约不参与冲突检测于是新预约总被判定「有冲突」而拒绝插入。解决打开ReserveDAO.java定位insert()方法里的 SQL 字符串确认r.status IN (...)括号内包含approved。顺手给reserve表加索引ALTER TABLE reserve ADD INDEX idx_room_date_status (room_id, date, status);5.3 现象main.jsp日历里某天显示红色●2但点进去myReserves.jsp却查不到这两天的预约原因ReserveDAO.getCountByDate()方法里SQL 查询只查了date ?但没加status IN (pending,approved)条件把rejected和canceled的记录也统计进去了。解决修改getCountByDate()的 SQL// 原来String sql SELECT COUNT(*) FROM reserve WHERE date ?; // 改为 String sql SELECT COUNT(*) FROM reserve WHERE date ? AND status IN (pending,approved);5.4 现象管理员在approveReserves.jsp点击「通过」页面刷新后状态仍是pending没变approved原因AdminApproveServlet.java的doPost()里调用ReserveDAO.updateStatus(id, approved)后没做response.sendRedirect(approveReserves.jsp)而是用了request.getRequestDispatcher(approveReserves.jsp).forward(request, response)。forward不刷新页面浏览器地址栏还是/admin/approveF5 刷新会重复提交。解决强制重定向// 改前 // request.getRequestDispatcher(approveReserves.jsp).forward(request, response); // 改后 response.sendRedirect(request.getContextPath() /admin/approveReserves.jsp);5.5 现象Tomcat 启动时报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver原因MySQL 8.0 驱动类名从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver而项目里DBUtil.java还在用老名字。解决两个动作一起做下载mysql-connector-java-8.0.33.jar替换WEB-INF/lib/下的旧 jar修改DBUtil.java// 改前 // Class.forName(com.mysql.jdbc.Driver); // 改后 Class.forName(com.mysql.cj.jdbc.Driver);同时更新context.xml里的driverClassName。6. 二次开发实战给预约系统加「时段自动推荐」功能这是我在某公司落地时最常被提的需求用户填完房间和日期系统自动列出该房间当天所有未被占用的连续可用时段比如 9:00-10:30、13:00-15:00而不是让用户自己猜「10:30 能不能约」。实现它不需要大改架构只需新增 1 个 Servlet、1 个 DAO 方法、1 个 JSP 片段30 分钟可上线。6.1 新增接口GetAvailableSlotsServlet新建src/com/room/servlet/GetAvailableSlotsServlet.java// GetAvailableSlotsServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String roomId request.getParameter(roomId); String dateStr request.getParameter(date); if (roomId null || dateStr null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, Missing roomId or date); return; } try { Date date new SimpleDateFormat(yyyy-MM-dd).parse(dateStr); ListString[] slots ReserveDAO.getAvailableSlots(Integer.parseInt(roomId), date); // 返回 JSON 数组[[09:00,10:30],[13:00,15:00]] response.setContentType(application/json;charsetUTF-8); PrintWriter out response.getWriter(); out.print(new Gson().toJson(slots)); } catch (Exception e) { e.printStackTrace(); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } }并在web.xml里注册servlet servlet-nameGetAvailableSlotsServlet/servlet-name servlet-classcom.room.servlet.GetAvailableSlotsServlet/servlet-class /servlet servlet-mapping servlet-nameGetAvailableSlotsServlet/servlet-name url-pattern/getAvailableSlots/url-pattern /servlet-mapping6.2 DAO 层用 SQL 算出空闲时段核心逻辑在ReserveDAO.getAvailableSlots()。思路是先查出当天所有已预约时段再用「全天时段9:00-18:00减去已占用时段」得到空闲块。MySQL 8.0 可用 CTE但为兼容 5.7我们用经典「排序自连接」法// ReserveDAO.java public static ListString[] getAvailableSlots(int roomId, Date date) { ListString[] result new ArrayList(); Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); // 步骤1查出当天所有已占用时段按 start_time 排序 String sql SELECT start_time, end_time FROM reserve WHERE room_id ? AND date ? AND status IN (pending,approved) ORDER BY start_time; ps conn.prepareStatement(sql); ps.setInt(1, roomId); ps.setDate(2, new java.sql.Date(date.getTime())); rs ps.executeQuery(); ListString[] occupied new ArrayList(); while (rs.next()) { occupied.add(new String[]{rs.getString(start_time), rs.getString(end_time)}); } // 步骤2生成空闲时段假设办公时间 9:00-18:00 String[] workHours {09:00, 18:00}; if (occupied.isEmpty()) { result.add(workHours); } else { // 第一块workHours[0] 到第一个占用的 start_time String firstStart occupied.get(0)[0]; if (isTimeBefore(09:00, firstStart)) { result.add(new String[]{09:00, firstStart}); } // 中间块前一个 end_time 到下一个 start_time for (int i 0; i occupied.size() - 1; i) { String prevEnd occupied.get(i)[1]; String nextStart occupied.get(i 1)[0]; if (isTimeBefore(prevEnd, nextStart)) { result.add(new String[]{prevEnd, nextStart}); } } // 最后一块最后一个 end_time 到 workHours[1] String lastEnd occupied.get(occupied.size() - 1)[1]; if (isTimeBefore(lastEnd, 18:00)) { result.add(new String[]{lastEnd, 18:00}); } } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(rs, ps, conn); } return result; } // 工具方法比较两个 HH:mm 时间字符串 private static boolean isTimeBefore(String t1, String t2) { return t1.compareTo(t2) 0; }6.3 前端集成在 reserve.jsp 里加 AJAX 调用在reserve.jsp的房间下拉框后加一段 JS!-- reserve.jsp 片段 -- select idroomId nameroomId onchangeloadAvailableSlots() !-- 房间选项 -- /select br/ 可用时段select idtimeSlot namestartTime disabled option value请选择时段/option /select nbsp; 至 nbsp; select idtimeSlotEnd nameendTime disabled option value请选择结束/option /select script function loadAvailableSlots() { const roomId document.getElementById(roomId).value; const date document.getElementById(date).value; // 假设日期输入框 id 是 date if (!roomId || !date) return; fetch(% request.getContextPath() %/getAvailableSlots?roomId roomId date date) .then(r r.json()) .then(slots { const selectStart document.getElementById(timeSlot); const selectEnd document.getElementById(timeSlotEnd); selectStart.innerHTML option value请选择开始/option; selectEnd.innerHTML option value请选择结束/option; slots.forEach(slot { const opt1 document.createElement(option); opt1.value slot[0]; opt1.text slot[0]; selectStart.appendChild(opt1); const opt2 document.createElement(option); opt2.value slot[1]; opt2.text slot[1]; selectEnd.appendChild(opt2); }); selectStart.disabled false; selectEnd.disabled false; }) .catch(e console.error(加载时段失败, e)); } /script提示fetch是现代写法如果目标环境 IE 11换成XMLHttpRequest。关键是onchangeloadAvailableSlots()绑定在房间下拉框用户选完房间立刻触发。从那以后我每次接到「智能推荐」需求都强制走一遍「查已占用 → 算空闲 → 前端渲染」三步闭环不再碰任何机器学习模型——因为会议室的时空约束是确定性的不是概率问题。这个功能上线后行政同事反馈「填表时间平均缩短 60%」而代码增量不到 200 行。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
GitKraken Ubuntu安装与SSH密钥配置实战指南 简介:本资源为 GitKraken v6.5.1 官方 Ubuntu 原生客户端安装包,面向 Linux 开发者、Git 初学者及需图形化操作 Git 的团队协作人员,解决命令行 Git 学习门槛高、分支/合并/冲突处理可视化不足等痛点。压缩包为 tar.gz 格式,共 97… · 2026/11/6 2:07:35
PHP纯实现网络协议解析与可视化系统 简介:这是一套面向计算机专业本科生的毕业设计实战资源,聚焦网络数据包的捕获、深度解析与可视化分析,适用于网络安全分析、协议研究及网络性能监控等实践场景。资源包含完整PHP源代码与配套毕业论文,源码涵盖网络监听模块、多层协… · 2026/11/6 2:07:35
Spark 3.2.0 + Hadoop 3.2 预编译包部署避坑指南 简介:本资源为 Apache Spark 3.2.0 官方二进制发行版(Hadoop 3.2 兼容构建),面向大数据开发工程师、数据科学家及高校学习者,用于快速部署本地或集群环境下的分布式计算平台,支撑批处理、实时流计算、SQL分… · 2026/11/6 2:06:33
双指针算法实战:有序数组去重与工程应用 1. 问题背景与核心需求这道算法题看似简单,却蕴含着数组操作的精妙技巧。题目要求我们在原地修改一个已排序的数组,使得每个元素最多出现两次,并返回新数组的长度。不同于基础版的"删除重复项"(只保留单一实例ÿ… · 2026/11/6 2:24:53
ISO26262功能安全标准解析与实践指南 1. ISO26262功能安全标准概述作为一名在汽车电子行业摸爬滚打多年的工程师,我深刻体会到功能安全在当今汽车开发中的重要性。ISO26262标准就像汽车电子领域的"九阴真经",看似晦涩难懂,实则蕴含了欧美汽车工业几百年的经验结晶。这个… · 2026/11/6 2:24:53
手写词法分析器实战:从课程实验到可扩展扫描器 简介:这份资源是北京邮电大学编译原理课程实验一的词法分析器实现包,面向正在学习编译原理、需要动手完成扫描器设计的高校学生与自学者,帮助解决从词法规则定义到分词逻辑落地的实践问题。压缩包共4个文件,约10KB,以c… · 2026/11/6 2:24:53
Windows API Hook实战:进程内拦截send获取明文协议 简介:面向Windows网络编程与安全审计方向开发者的一份Win32 API钩子实践工程,围绕拦截ws2_32.dll中的send()函数、把套接字发送内容写入OB文件展开,涉及导入地址表替换、用户模式钩子与原始函数转发等关键环节,适合具备C与Winsock… · 2026/11/6 2:24:53
Socket通信中的发布订阅模式设计与优化 1. 理解Socket通信中的发布订阅模式在分布式系统开发中,发布订阅(Pub/Sub)模式是一种常见的设计范式。不同于传统的点对点通信,这种模式允许消息生产者(发布者)将消息发送给多个消费者(订阅者&a… · 2026/11/6 2:24:53
GitHub PR查看全攻略:从基础操作到高级技巧 1. 为什么需要查看GitHub PR?在团队协作开发中,Pull Request(PR)是代码审查的核心载体。我刚参与开源项目时,经常遇到这些场景:想了解某个功能的实现细节、需要复查同事提交的代码变更、或者要追踪某个issu… · 2026/11/6 2:23:53
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:07
三菱PLC数据采集到数据库的时序设计:Python+MC协议实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
ASIC主模式RTL设计:状态机与三态驱动的流片级实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
STM32嵌入式C++开发:GDB调试与Renode仿真实战指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 1:02:11
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:27:59
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:03
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 10:28:07