如何获取select选中值传递给PHP执行查询返回对应可用预订时段
同文件下AJAX传参失败排查+房间预订时段联动实现方案
常见传值失败原因排查
按优先级从高到低检查:
- 缺少POST请求头:原生XMLHttpRequest发POST请求时,必须手动添加
Content-Type: application/x-www-form-urlencoded请求头,否则PHP不会将请求体中的数据解析到$_POST数组,这是同文件传参丢失的最高发原因 - PHP处理逻辑位置错误:同文件处理AJAX请求的代码必须放在所有HTML输出之前,处理完成后必须加
exit终止执行,否则会把整页HTML作为响应返回,既拿不到正确参数,也无法解析JSON结果 - 请求路径错误:同文件请求直接填当前文件路径或者
location.pathname即可,不要写错路径触发服务器重定向,重定向会丢失POST参数 - 参数格式错误:原生XHR的
send()方法不能直接传入JS对象,需要拼接成key1=val1&key2=val2的urlencoded格式,特殊字符要做编码 - 请求拦截:如果服务器开了CSRF防护、WAF拦截规则,未携带合法校验参数的POST请求会被直接拦截,导致后端拿不到参数
可直接运行的home.php完整实现
代码逻辑分层:文件最顶部是PHP处理逻辑,分为AJAX接口响应、页面基础数据查询两部分;之后是HTML结构和原生JS联动逻辑,无需引入第三方库。
顶部PHP逻辑
<?php // 所有代码放在文件最顶部,所有HTML输出前执行 // 1. 处理AJAX请求:返回指定房间当日已预订时段 if (isset($_POST['action']) && $_POST['action'] === 'get_booked_slots') { header('Content-Type: application/json'); // 参数校验 $room_id = isset($_POST['room_id']) ? intval($_POST['room_id']) : 0; if ($room_id <= 0) { echo json_encode(['code' => 400, 'msg' => '房间参数无效', 'data' => []]); exit; } // 数据库连接 替换成你自己的连接信息 $pdo = new PDO('mysql:host=localhost;dbname=你的数据库名;charset=utf8mb4', '数据库用户名', '数据库密码'); $today = date('Y-m-d'); // 查询当日该房间的已预订记录 $stmt = $pdo->prepare("SELECT start_time, end_time FROM booking_records WHERE room_id = ? AND booking_date = ?"); $stmt->execute([$room_id, $today]); $records = $stmt->fetchAll(PDO::FETCH_ASSOC); // 整理已被占用的时间点 以30分钟为粒度 可根据自己的业务调整 $bookedPoints = []; foreach ($records as $row) { $startTs = strtotime($row['start_time']); $endTs = strtotime($row['end_time']); for ($ts = $startTs; $ts < $endTs; $ts += 1800) { $bookedPoints[] = date('H:i', $ts); } } echo json_encode(['code' => 200, 'data' => $bookedPoints]); exit; // 必须终止 避免返回后续页面HTML } // 2. 正常页面渲染逻辑:查询所有可用房间供下拉框选择 $pdo = new PDO('mysql:host=localhost;dbname=你的数据库名;charset=utf8mb4', '数据库用户名', '数据库密码'); $roomList = $pdo->query("SELECT id, room_name FROM rooms WHERE is_available = 1")->fetchAll(PDO::FETCH_ASSOC); ?>
页面结构与JS逻辑
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>房间预订</title> </head> <body> <div style="margin: 20px 0;"> <label>选择房间:</label> <select id="room"> <option value="">请选择房间</option> <?php foreach ($roomList as $room): ?> <option value="<?= $room['id'] ?>"><?= htmlspecialchars($room['room_name']) ?></option> <?php endforeach; ?> </select> </div> <div style="margin: 20px 0;"> <label>开始时间:</label> <select id="start_time" disabled> <option value="">请先选择房间</option> </select> </div> <div style="margin: 20px 0;"> <label>结束时间:</label> <select id="end_time" disabled> <option value="">请先选择开始时间</option> </select> </div> <script> const roomSel = document.getElementById('room'); const startSel = document.getElementById('start_time'); const endSel = document.getElementById('end_time'); let cachedBooked = []; // 缓存当前房间已占用时段 // 生成时间选项 可配置起始时间、时间粒度 function renderStartOptions(bookedPoints) { cachedBooked = bookedPoints; startSel.innerHTML = '<option value="">请选择开始时间</option>'; const openMinute = 8 * 60; // 每日8点开放预约 const closeMinute = 22 * 60; // 每日22点停止预约 const step = 30; // 每30分钟一个时段 for (let m = openMinute; m < closeMinute; m += step) { const h = Math.floor(m/60).toString().padStart(2, '0'); const min = (m%60).toString().padStart(2, '0'); const timeVal = `${h}:${min}`; const opt = document.createElement('option'); opt.value = timeVal; opt.textContent = timeVal; if (bookedPoints.includes(timeVal)) opt.disabled = true; startSel.appendChild(opt); } startSel.disabled = false; } // 房间选择变更事件 roomSel.addEventListener('change', function() { const roomId = this.value.trim(); // 重置时间选择框 startSel.innerHTML = '<option value="">加载中...</option>'; startSel.disabled = true; endSel.innerHTML = '<option value="">请先选择开始时间</option>'; endSel.disabled = true; if (!roomId) { startSel.innerHTML = '<option value="">请先选择房间</option>'; return; } // 发送AJAX请求 const xhr = new XMLHttpRequest(); xhr.open('POST', location.pathname); // 必须添加该请求头 否则$_POST无法接收参数 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onload = function() { if (xhr.status !== 200) { alert('网络请求失败,请重试'); return; } try { const res = JSON.parse(xhr.responseText); if (res.code !== 200) { alert(res.msg); return; } renderStartOptions(res.data); } catch (err) { alert('数据解析失败,请检查PHP逻辑是否加了exit终止'); console.log('响应原始内容:', xhr.responseText); } } // 拼接参数 注意编码 xhr.send(`action=get_booked_slots&room_id=${encodeURIComponent(roomId)}`); }); // 开始时间选择后 渲染可选结束时间 startSel.addEventListener('change', function() { const startTime = this.value; endSel.innerHTML = '<option value="">请选择结束时间</option>'; endSel.disabled = true; if (!startTime) return; // 从开始时间往后生成结束时间选项 跳过已占用时段 逻辑可根据业务补全 const [sh, sm] = startTime.split(':').map(Number); const startM = sh*60 + sm; const closeMinute = 22 * 60; const step = 30; for (let m = startM + step; m <= closeMinute; m += step) { const h = Math.floor(m/60).toString().padStart(2, '0'); const min = (m%60).toString().padStart(2, '0'); const timeVal = `${h}:${min}`; const opt = document.createElement('option'); opt.value = timeVal; opt.textContent = timeVal; if (cachedBooked.includes(timeVal)) break; // 碰到已占用时段就终止 不能跨已约时段选 endSel.appendChild(opt); } endSel.disabled = false; }); </script> </body> </html>
关键注意点
- AJAX处理逻辑执行完必须加
exit,否则会把后续的页面HTML一并返回,导致前端JSON解析失败 - 数据库查询部分一定要用预处理语句,不要直接拼接SQL,避免SQL注入风险
- 如果站点开启了CSRF令牌校验,需要在POST参数中额外携带CSRF令牌参数,否则请求会被中间件拦截
- 前端渲染时段时,提交表单前后端必须二次校验时段是否真的可用,不能只靠前端禁用选项做拦截,避免用户篡改请求参数提交无效预订
内容的提问来源于stack exchange,提问作者Ram
相关产品推荐
相关产品推荐

