You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 13:06:30