This repository has been archived on 2026-06-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
carwash_order/index.php
T
wsh5485 3b3e1c577a fix: 修复未定义数组键导致的潜在错误
检查$booking_schedule[$date]是否存在后再进行判断,避免未定义数组键导致的PHP警告
2025-11-19 01:16:58 +08:00

514 lines
24 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
session_start();
require_once 'db_connect.php';
$message = '';
$success_message = '';
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
$customer_name = trim($_POST['customer_name']);
$phone = trim($_POST['phone']);
$car_model = trim($_POST['car_model']);
$car_number = trim($_POST['car_number']);
$package_id = (int)$_POST['package_id'];
$custom_services = trim($_POST['custom_services'] ?? '');
$appointment_date = $_POST['appointment_date'];
$appointment_time = $_POST['appointment_time'];
$duration = (int)$_POST['duration'];
$notes = trim($_POST['notes'] ?? '');
// 验证必填字段
if (empty($customer_name) || empty($phone) || empty($car_model) ||
empty($car_number) || empty($appointment_date) || empty($appointment_time)) {
throw new Exception('请填写所有必填字段');
}
// 验证套餐
if ($package_id) {
$stmt = $pdo->prepare("SELECT * FROM packages WHERE id = ? AND is_active = 1");
$stmt->execute([$package_id]);
$package = $stmt->fetch();
if (!$package) {
throw new Exception('选择的套餐无效');
}
$total_price = $package['price'];
} else {
throw new Exception('请选择一个套餐');
}
// 计算预约时间范围
$start_time = $appointment_date . ' ' . $appointment_time . ':00';
$end_time = date('Y-m-d H:i:s', strtotime($start_time . " +{$duration} minutes"));
// 检查时间冲突
$stmt = $pdo->prepare("SELECT COUNT(*) FROM bookings
WHERE status != '已取消'
AND (
(start_time <= ? AND end_time > ?)
OR (start_time < ? AND end_time >= ?)
OR (start_time >= ? AND end_time <= ?)
)");
$stmt->execute([$start_time, $start_time, $end_time, $end_time, $start_time, $end_time]);
if ($stmt->fetchColumn() > 0) {
throw new Exception('该时间段已被预约,请选择其他时间');
}
// 插入预约记录
$stmt = $pdo->prepare("INSERT INTO bookings
(customer_name, phone, car_model, car_number, package_id, custom_services,
start_time, end_time, duration, total_price, notes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$customer_name, $phone, $car_model, $car_number, $package_id, $custom_services,
$start_time, $end_time, $duration, $total_price, $notes]);
$success_message = "预约提交成功!我们会尽快联系您确认。";
} catch (Exception $e) {
$message = $e->getMessage();
}
}
// 获取套餐列表
$stmt = $pdo->query("SELECT * FROM packages WHERE is_active = 1 ORDER BY price");
$packages = $stmt->fetchAll();
// 获取一周内的预约数据用于日历显示
$start_date = date('Y-m-d');
$end_date = date('Y-m-d', strtotime('+7 days'));
$stmt = $pdo->prepare("SELECT DATE(start_time) as date,
COUNT(*) as booking_count,
GROUP_CONCAT(
CONCAT(
TIME_FORMAT(start_time, '%H:%i'), '-',
TIME_FORMAT(end_time, '%H:%i'),
'(', status, ')'
) ORDER BY start_time SEPARATOR '<br>'
) as bookings
FROM bookings
WHERE DATE(start_time) BETWEEN ? AND ?
AND status != '已取消'
GROUP BY DATE(start_time)
ORDER BY date");
$stmt->execute([$start_date, $end_date]);
$booking_schedule = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
// 获取套餐信息用于JavaScript
$packages_json = json_encode(array_map(function($package) {
$package['services'] = array_filter(array_map('trim', explode(',', $package['services'])));
return $package;
}, $packages));
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="format-detection" content="telephone=no">
<meta name="description" content="洗车预约系统 - 在线预约洗车服务">
<meta name="keywords" content="洗车,预约,在线预约,汽车美容">
<link rel="apple-touch-icon" href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTkyIiBoZWlnaHQ9IjE5MiIgdmlld0JveD0iMCAwIDE5MiAxOTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxOTIiIGhlaWdodD0iMTkyIiByeD0iMjQiIGZpbGw9IiMzMDk1RjQiLz4KPHN2ZyB4PSI0OCIgeT0iNDgiIHdpZHRoPSI5NiIgaGVpZ2h0PSI5NiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJ3aGl0ZSI+CjxwYXRoIGQ9Ik0yMS4yIDQuNEMyMS42IDMuNiAyMi4xIDMuMSAyMi41IDIuN0MyMy40IDIuMSAyNC41IDIuMSAyNS4yIDIuN0MyNS44IDMuMSAyNi4zIDMuNiAyNi43IDQuNEMyNy4xIDUuMSAyNy4xIDYuMiAyNi43IDcuMUMyNi4zIDcuOCAyNS44IDguMyAyNS4yIDguN0MyNC43IDkuMSAyMy42IDkuMSAyMi45IDguN0MyMi4zIDguMyAyMS44IDcuOCAyMS40IDcuMUMyMS4wIDYuMiAyMS4wIDUuMSAyMS4yIDQuNFoiLz4KPHN2ZyB4PSIyMCIgeT0iMTIiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJ3aGl0ZSI+CjxwYXRoIGQ9Ik0yMSAyMy41QzIwLjUgMjMuNSAyMCAyMyAyMCAyMi41VjEyQzIwIDExLjUgMjAuNSAxMSAyMSAxMUg5QzguNSAxMSAxMi41IDEwLjUgMTIgMTBIMjBWMTBCMjAgMTAuNSAyMC41IDExIDIxIDExVjIzLjVaIi8+CjxwYXRoIGQ9Ik0xOCAyMFYxN0gxNFY4SDVWMTNIMTlWMTVIMTlWMjBaIi8+CjxwYXRoIGQ9Ik04IDEwSDVWN0g4VjEwWiIvPgo8L3N2Zz4KPC9zdmc+">
<title>洗车预约系统</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<header class="header">
<h1>🚗 洗车预约系统</h1>
<nav class="nav">
<a href="index.php" class="nav-link active">预约系统</a>
<a href="bookings.php" class="nav-link">预约管理</a>
<a href="packages.php" class="nav-link">套餐管理</a>
</nav>
</header>
<?php if ($message): ?>
<div class="message error-message" style="background-color: #fee; color: #c33; border-color: #fcc;">
<?= htmlspecialchars($message) ?>
</div>
<?php endif; ?>
<?php if ($success_message): ?>
<div class="message success-message">
<?= htmlspecialchars($success_message) ?>
</div>
<?php endif; ?>
<div class="booking-container">
<div class="calendar-section">
<h2>📅 选择预约日期</h2>
<div class="calendar">
<?php for ($i = 0; $i < 7; $i++):
$date = date('Y-m-d', strtotime("+{$i} days"));
$date_display = date('m/d', strtotime("+{$i} days"));
$weekday = ['日', '一', '二', '三', '四', '五', '六'][date('w', strtotime("+{$i} days"))];
$booking_count = $booking_schedule[$date] ?? 0;
$is_today = $i === 0;
$is_full = $booking_count >= 8; // 假设每天最多8个时段
$status_class = $is_full ? 'full' : ($booking_count > 0 ? 'busy' : 'available');
$status_text = $is_full ? '已满' : ($booking_count > 0 ? '繁忙' : '可预约');
?>
<div class="calendar-day <?= $status_class ?> <?= $is_today ? 'today' : '' ?>"
data-date="<?= $date ?>" onclick="selectDate('<?= $date ?>')">
<div class="day-number"><?= $date_display ?></div>
<div class="day-week">周<?= $weekday ?></div>
<div class="day-status"><?= $status_text ?></div>
<?php if (isset($booking_schedule[$date]) && $booking_schedule[$date]): ?>
<div class="booking-count"><?= $booking_count ?>个预约</div>
<?php endif; ?>
</div>
<?php endfor; ?>
</div>
<div class="time-slots" id="timeSlots" style="display: none;">
<h3>🕐 选择时间段</h3>
<div class="quick-duration">
<span>快捷时长:</span>
<button type="button" class="duration-btn" onclick="selectDuration(60)">1小时</button>
<button type="button" class="duration-btn" onclick="selectDuration(90)">1.5小时</button>
<button type="button" class="duration-btn" onclick="selectDuration(120)">2小时</button>
<button type="button" class="duration-btn" onclick="selectDuration(180)">3小时</button>
<button type="button" class="duration-btn" onclick="selectDuration(240)">4小时</button>
<input type="number" id="customDuration" min="30" step="30" value="60" style="width: 80px; margin-left: 10px;">
<button type="button" class="btn btn-sm" onclick="applyCustomDuration()">确定</button>
</div>
<div class="time-grid" id="timeGrid">
<!-- 时间段将通过JavaScript动态生成 -->
</div>
</div>
</div>
<div class="booking-form-section">
<h2>📋 预约信息</h2>
<form method="POST" class="form" id="bookingForm">
<div class="form-group">
<label for="customer_name">客户姓名 *</label>
<input type="text" id="customer_name" name="customer_name" required>
</div>
<div class="form-group">
<label for="phone">联系电话 *</label>
<input type="tel" id="phone" name="phone" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="car_model">车型 *</label>
<input type="text" id="car_model" name="car_model" placeholder="如:大众朗逸" required>
</div>
<div class="form-group">
<label for="car_number">车牌号 *</label>
<input type="text" id="car_number" name="car_number" placeholder="如:京A12345" required>
</div>
</div>
<div class="form-group">
<label for="package_id">选择套餐 *</label>
<select id="package_id" name="package_id" required onchange="updatePackageInfo()">
<option value="">请选择套餐</option>
<?php foreach ($packages as $package): ?>
<option value="<?= $package['id'] ?>"
data-duration="<?= $package['base_duration'] ?>"
data-price="<?= $package['price'] ?>"
data-services='<?= htmlspecialchars($package['services']) ?>'>
<?= htmlspecialchars($package['package_name']) ?> - ¥<?= number_format($package['price'], 2) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="package-info" id="packageInfo" style="display: none;">
<div class="package-details">
<h4 id="packageName"></h4>
<p id="packageDescription"></p>
<div class="package-meta">
<span id="packageDuration"></span>
<span id="packagePrice"></span>
</div>
<div id="packageServices"></div>
</div>
</div>
<div class="form-group">
<label for="custom_services">自定义服务需求</label>
<textarea id="custom_services" name="custom_services" rows="3"
placeholder="如有特殊需求,请在此说明..."></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label for="appointment_date">预约日期 *</label>
<input type="date" id="appointment_date" name="appointment_date" required readonly>
</div>
<div class="form-group">
<label for="appointment_time">预约时间 *</label>
<input type="time" id="appointment_time" name="appointment_time" required readonly>
</div>
</div>
<div class="form-group">
<label for="duration">服务时长(分钟) *</label>
<input type="number" id="duration" name="duration" min="30" step="30" value="60" required>
</div>
<div class="form-group">
<label for="notes">备注</label>
<textarea id="notes" name="notes" rows="3" placeholder="其他备注信息..."></textarea>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="submitBtn" disabled>提交预约</button>
<button type="reset" class="btn" onclick="resetForm()">重置</button>
</div>
</form>
</div>
</div>
<!-- 日期详情弹窗 -->
<div id="dateModal" class="modal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalDate"></h3>
<span class="close" onclick="closeModal()">&times;</span>
</div>
<div class="modal-body" id="modalBody">
<!-- 预约详情将通过JavaScript加载 -->
</div>
</div>
</div>
</div>
<script>
const packages = <?= $packages_json ?>;
const bookingSchedule = <?= json_encode($booking_schedule) ?>;
let selectedDate = null;
let selectedTime = null;
let selectedDuration = 60;
// 工作时间设置
const workingHours = {
start: 8, // 8:00
end: 18, // 18:00
slotDuration: 30 // 30分钟一个时段
};
// 初始化
document.addEventListener('DOMContentLoaded', function() {
const today = new Date();
today.setDate(today.getDate());
document.getElementById('appointment_date').value = today.toISOString().split('T')[0];
selectedDate = today.toISOString().split('T')[0];
// 移动端优化
if (/Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) {
document.body.classList.add('mobile-device');
// 自动聚焦到姓名输入框
setTimeout(() => {
document.getElementById('customer_name').focus();
}, 500);
}
});
function selectDate(date) {
selectedDate = date;
document.getElementById('appointment_date').value = date;
// 更新日历选中状态
document.querySelectorAll('.calendar-day').forEach(day => {
day.classList.remove('selected');
});
document.querySelector(`[data-date="${date}"]`).classList.add('selected');
// 生成时间段
generateTimeSlots(date);
}
function generateTimeSlots(date) {
const timeGrid = document.getElementById('timeGrid');
const timeSlotsDiv = document.getElementById('timeSlots');
timeGrid.innerHTML = '';
// 获取当天已有预约
const dayBookings = bookingSchedule[date];
// 生成时间段
for (let hour = workingHours.start; hour < workingHours.end; hour++) {
for (let minute = 0; minute < 60; minute += workingHours.slotDuration) {
const timeString = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
const slotTime = new Date(`${date} ${timeString}:00`);
const now = new Date();
const isPast = slotTime <= now;
const isBooked = checkTimeSlotBooked(date, timeString);
const slotDiv = document.createElement('div');
slotDiv.className = `time-slot ${isPast ? 'past' : ''} ${isBooked ? 'booked' : 'available'}`;
slotDiv.textContent = timeString;
slotDiv.onclick = () => selectTimeSlot(timeString);
timeGrid.appendChild(slotDiv);
}
}
timeSlotsDiv.style.display = 'block';
selectedTime = null;
updateSubmitButton();
}
function checkTimeSlotBooked(date, time) {
// 这里需要根据实际的预约数据检查时间段是否被占用
// 简化实现,实际应该查询数据库
return false;
}
function selectTimeSlot(time) {
selectedTime = time;
document.getElementById('appointment_time').value = time;
// 更新时间段选中状态
document.querySelectorAll('.time-slot').forEach(slot => {
slot.classList.remove('selected');
});
const slotElement = document.querySelector(`[onclick="selectTimeSlot('${time}')"]`);
if (slotElement) {
slotElement.classList.add('selected');
}
updateSubmitButton();
}
function selectDuration(minutes) {
selectedDuration = minutes;
document.getElementById('duration').value = minutes;
// 更新时长按钮选中状态
document.querySelectorAll('.duration-btn').forEach(btn => {
btn.classList.remove('selected');
});
const btn = document.querySelector(`[onclick="selectDuration(${minutes})"]`);
if (btn) {
btn.classList.add('selected');
}
}
function applyCustomDuration() {
const customDuration = parseInt(document.getElementById('customDuration').value);
if (customDuration >= 30) {
selectDuration(customDuration);
} else {
alert('自定义时长不能少于30分钟');
}
}
function updatePackageInfo() {
const packageSelect = document.getElementById('package_id');
const selectedOption = packageSelect.options[packageSelect.selectedIndex];
const packageInfoDiv = document.getElementById('packageInfo');
if (selectedOption && selectedOption.value) {
const packageId = parseInt(selectedOption.value);
const package = packages.find(p => p.id === packageId);
if (package) {
// 更新套餐信息显示
document.getElementById('packageName').textContent = package.package_name;
document.getElementById('packageDescription').textContent = package.description;
document.getElementById('packageDuration').textContent = `基础时长: ${package.base_duration}分钟`;
document.getElementById('packagePrice').textContent = `价格: ¥${package.price}`;
// 显示服务项目
const servicesContainer = document.getElementById('packageServices');
if (package.services && package.services.length > 0) {
servicesContainer.innerHTML = '<strong>包含服务:</strong><br>' +
package.services.map(service => `• ${service}`).join('<br>');
}
packageInfoDiv.style.display = 'block';
// 自动设置基础时长
document.getElementById('duration').value = package.base_duration;
selectDuration(package.base_duration);
}
} else {
packageInfoDiv.style.display = 'none';
}
updateSubmitButton();
}
function updateSubmitButton() {
const submitBtn = document.getElementById('submitBtn');
const isFormValid = selectedDate && selectedTime &&
document.getElementById('customer_name').value &&
document.getElementById('phone').value &&
document.getElementById('car_model').value &&
document.getElementById('car_number').value &&
document.getElementById('package_id').value;
submitBtn.disabled = !isFormValid;
}
function resetForm() {
selectedDate = null;
selectedTime = null;
selectedDuration = 60;
document.getElementById('appointment_date').value = '';
document.getElementById('appointment_time').value = '';
document.getElementById('duration').value = 60;
document.getElementById('packageInfo').style.display = 'none';
document.getElementById('timeSlots').style.display = 'none';
// 重置所有选中状态
document.querySelectorAll('.calendar-day').forEach(day => {
day.classList.remove('selected');
});
document.querySelectorAll('.time-slot').forEach(slot => {
slot.classList.remove('selected');
});
document.querySelectorAll('.duration-btn').forEach(btn => {
btn.classList.remove('selected');
});
}
// 表单验证
document.getElementById('bookingForm').addEventListener('input', updateSubmitButton);
// 触摸反馈效果
document.addEventListener('touchstart', function(e) {
if (e.target.classList.contains('btn') || e.target.classList.contains('time-slot')) {
e.target.style.transform = 'scale(0.95)';
}
});
document.addEventListener('touchend', function(e) {
if (e.target.classList.contains('btn') || e.target.classList.contains('time-slot')) {
setTimeout(() => {
e.target.style.transform = 'scale(1)';
}, 150);
}
});
// 防止双击缩放(iOS Safari
let lastTouchEnd = 0;
document.addEventListener('touchend', function(event) {
const now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
</script>
</body>
</html>