更新待预约页面:添加套餐选择、日历和空闲时间段显示功能
This commit is contained in:
+412
-41
@@ -14,6 +14,8 @@ if (isset($_POST['action']) && $_POST['action'] == 'convert_to_booking' && isset
|
||||
$selected_time = $_POST['selected_time'];
|
||||
$selected_package = $_POST['selected_package'];
|
||||
$custom_notes = $_POST['custom_notes'];
|
||||
$total_price = $_POST['total_price'];
|
||||
$duration = $_POST['duration'];
|
||||
|
||||
try {
|
||||
// 获取WPS表单提交数据
|
||||
@@ -36,7 +38,7 @@ if (isset($_POST['action']) && $_POST['action'] == 'convert_to_booking' && isset
|
||||
|
||||
// 计算开始和结束时间
|
||||
$start_time = $selected_date . ' ' . $selected_time;
|
||||
$end_time = date('Y-m-d H:i:s', strtotime($start_time) + $package['base_duration'] * 60);
|
||||
$end_time = date('Y-m-d H:i:s', strtotime($start_time) + $duration * 60);
|
||||
|
||||
// 将数据插入到正式预约表
|
||||
$stmt = $pdo->prepare("INSERT INTO bookings (
|
||||
@@ -58,8 +60,8 @@ if (isset($_POST['action']) && $_POST['action'] == 'convert_to_booking' && isset
|
||||
$custom_notes,
|
||||
$start_time,
|
||||
$end_time,
|
||||
$package['base_duration'],
|
||||
$package['price'],
|
||||
$duration,
|
||||
$total_price,
|
||||
$submission['remarks'],
|
||||
'已确认', // 默认设置为已确认
|
||||
'普通客户', // 默认普通客户,可根据需要调整
|
||||
@@ -73,12 +75,11 @@ if (isset($_POST['action']) && $_POST['action'] == 'convert_to_booking' && isset
|
||||
$stmt->execute([$submission_id]);
|
||||
|
||||
// 生成预约成功信息
|
||||
$booking_success_msg = "预约成功!\n\n客户:{$submission['name']}\n手机号:{$submission['mobile']}\n车牌号:{$submission['license_plate']}\n车型:{$submission['car_type']}\n\n预约时间:{$start_time}\n服务项目:{$package['package_name']}\n服务时长:{$package['base_duration']}分钟\n总价:{$package['price']}元\n\n感谢您的预约!";
|
||||
$booking_success_msg = "预约成功!\n\n客户:{$submission['name']}\n手机号:{$submission['mobile']}\n车牌号:{$submission['license_plate']}\n车型:{$submission['car_type']}\n\n预约时间:{$start_time}\n服务项目:{$package['package_name']}\n服务时长:{$duration}分钟\n总价:{$total_price}元\n\n感谢您的预约!";
|
||||
|
||||
$success_message = "预约转换成功!预约ID:{$booking_id}";
|
||||
|
||||
// 存储预约成功信息到会话,以便在页面上显示
|
||||
session_start();
|
||||
$_SESSION['booking_success_msg'] = $booking_success_msg;
|
||||
|
||||
} catch (Exception $e) {
|
||||
@@ -105,6 +106,76 @@ try {
|
||||
$error_message = '获取套餐信息失败:' . $e->getMessage();
|
||||
$packages = [];
|
||||
}
|
||||
|
||||
// 获取当前日期及未来7天的日期
|
||||
$current_date = date('Y-m-d');
|
||||
$available_dates = [];
|
||||
for ($i = 0; $i < 7; $i++) {
|
||||
$available_dates[] = date('Y-m-d', strtotime($current_date . " +$i days"));
|
||||
}
|
||||
|
||||
// 获取所有预约数据,用于显示空闲时间段
|
||||
$booking_schedule = [];
|
||||
$bookings_by_date = [];
|
||||
|
||||
try {
|
||||
// 获取所有未来的预约
|
||||
$stmt = $pdo->prepare("SELECT * FROM bookings WHERE end_time > NOW() ORDER BY start_time ASC");
|
||||
$stmt->execute();
|
||||
$all_bookings = $stmt->fetchAll();
|
||||
|
||||
// 处理预约数据
|
||||
foreach ($all_bookings as $booking) {
|
||||
$start_date = date('Y-m-d', strtotime($booking['start_time']));
|
||||
$end_date = date('Y-m-d', strtotime($booking['end_time']));
|
||||
$start_time = date('H:i', strtotime($booking['start_time']));
|
||||
$end_time = date('H:i', strtotime($booking['end_time']));
|
||||
|
||||
// 处理跨天预约
|
||||
$is_cross_day = $start_date != $end_date;
|
||||
|
||||
// 添加到预约日程
|
||||
if (!isset($booking_schedule[$start_date])) {
|
||||
$booking_schedule[$start_date] = [];
|
||||
}
|
||||
|
||||
if (!isset($bookings_by_date[$start_date])) {
|
||||
$bookings_by_date[$start_date] = [];
|
||||
}
|
||||
|
||||
// 主预约记录
|
||||
$bookings_by_date[$start_date][] = [
|
||||
'booking_id' => $booking['id'],
|
||||
'customer_name' => $booking['customer_name'],
|
||||
'car_model' => $booking['car_model'],
|
||||
'car_number' => $booking['car_number'],
|
||||
'status' => $booking['status'],
|
||||
'start_time' => $start_time,
|
||||
'end_time' => $end_time,
|
||||
'is_cross_day' => $is_cross_day
|
||||
];
|
||||
|
||||
// 如果是跨天预约,添加第二天的记录
|
||||
if ($is_cross_day) {
|
||||
if (!isset($bookings_by_date[$end_date])) {
|
||||
$bookings_by_date[$end_date] = [];
|
||||
}
|
||||
|
||||
$bookings_by_date[$end_date][] = [
|
||||
'booking_id' => $booking['id'],
|
||||
'customer_name' => $booking['customer_name'],
|
||||
'car_model' => $booking['car_model'],
|
||||
'car_number' => $booking['car_number'],
|
||||
'status' => $booking['status'],
|
||||
'start_time' => $start_time,
|
||||
'end_time' => $end_time,
|
||||
'is_cross_day' => $is_cross_day
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$error_message = '获取预约数据失败:' . $e->getMessage();
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
@@ -247,6 +318,131 @@ try {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
/* 日历和时间选择样式 */
|
||||
.calendar-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
padding: 15px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.calendar-day:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.calendar-day.selected {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.time-slots-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.time-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.time-slot {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.time-slot.available {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.time-slot.booked {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.time-slot.past {
|
||||
background: #e9ecef;
|
||||
color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.time-slot.selected {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.time-slot:hover.available {
|
||||
background: #c3e6cb;
|
||||
}
|
||||
|
||||
/* 套餐信息样式 */
|
||||
.package-info {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.package-details {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.package-meta {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.booking-detail-item {
|
||||
background: #fff;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.booking-time {
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.status-已确认 {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.status-待服务 {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.status-已完成 {
|
||||
color: #6c757d;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -356,45 +552,76 @@ try {
|
||||
<input type="hidden" name="action" value="convert_to_booking">
|
||||
<input type="hidden" name="submission_id" value="<?php echo $submission['id']; ?>">
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="selected_date_<?php echo $submission['id']; ?>">选择日期:</label>
|
||||
<input type="date" id="selected_date_<?php echo $submission['id']; ?>" name="selected_date"
|
||||
value="<?php echo date('Y-m-d'); ?>" required>
|
||||
<div class="form-group">
|
||||
<label for="selected_package_<?php echo $submission['id']; ?>">选择套餐:</label>
|
||||
<select id="selected_package_<?php echo $submission['id']; ?>" name="selected_package" required onchange="updatePackageInfo(<?php echo $submission['id']; ?>)">
|
||||
<option value="">请选择套餐</option>
|
||||
<?php foreach ($packages as $package): ?>
|
||||
<option value="<?php echo $package['id']; ?>"
|
||||
data-duration="<?php echo $package['base_duration']; ?>"
|
||||
data-price="<?php echo $package['price']; ?>"
|
||||
data-services='<?php echo htmlspecialchars($package['services']); ?>'>
|
||||
<?php echo htmlspecialchars($package['package_name']); ?> - ¥<?php echo number_format($package['price'], 2); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="package-info" id="packageInfo_<?php echo $submission['id']; ?>">
|
||||
<div class="package-details">
|
||||
<h4 id="packageName_<?php echo $submission['id']; ?>"></h4>
|
||||
<div class="package-meta">
|
||||
<span id="packageDuration_<?php echo $submission['id']; ?>"></span>
|
||||
<span id="packagePrice_<?php echo $submission['id']; ?>"></span>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 15px;">
|
||||
<label for="total_price_<?php echo $submission['id']; ?>">最终价格 (¥)</label>
|
||||
<input type="number" id="total_price_<?php echo $submission['id']; ?>" name="total_price" min="0" step="0.01" value="0" placeholder="输入最终价格" required>
|
||||
<input type="hidden" id="package_price_<?php echo $submission['id']; ?>" value="0">
|
||||
</div>
|
||||
<div id="packageServices_<?php echo $submission['id']; ?>"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="selected_time_<?php echo $submission['id']; ?>">选择时间:</label>
|
||||
<select id="selected_time_<?php echo $submission['id']; ?>" name="selected_time" required>
|
||||
<?php
|
||||
// 生成可选时间(9:00-18:00,每30分钟一个时间段)
|
||||
$start_hour = 9;
|
||||
$end_hour = 18;
|
||||
for ($hour = $start_hour; $hour < $end_hour; $hour++) {
|
||||
for ($minute = 0; $minute < 60; $minute += 30) {
|
||||
$time = sprintf('%02d:%02d', $hour, $minute);
|
||||
echo "<option value='$time'>$time</option>";
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="calendar-container">
|
||||
<div class="calendar-header">
|
||||
<h5>选择预约日期</h5>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="selected_package_<?php echo $submission['id']; ?>">选择套餐:</label>
|
||||
<select id="selected_package_<?php echo $submission['id']; ?>" name="selected_package" required>
|
||||
<?php foreach ($packages as $package): ?>
|
||||
<option value="<?php echo $package['id']; ?>">
|
||||
<?php echo htmlspecialchars($package['package_name']); ?> - <?php echo $package['price']; ?>元 (<?php echo $package['base_duration']; ?>分钟)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="calendar-grid" id="calendarGrid_<?php echo $submission['id']; ?>">
|
||||
<?php foreach ($available_dates as $date): ?>
|
||||
<div class="calendar-day"
|
||||
data-date="<?php echo $date; ?>"
|
||||
onclick="selectDate(<?php echo $submission['id']; ?>, '<?php echo $date; ?>')">
|
||||
<div><?php echo date('m月d日', strtotime($date)); ?></div>
|
||||
<div><?php echo date('D', strtotime($date)); ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="custom_notes_<?php echo $submission['id']; ?>">自定义备注:</label>
|
||||
<textarea id="custom_notes_<?php echo $submission['id']; ?>" name="custom_notes" rows="3"></textarea>
|
||||
<input type="hidden" id="selected_date_<?php echo $submission['id']; ?>" name="selected_date" value="<?php echo date('Y-m-d'); ?>">
|
||||
<input type="hidden" id="selected_time_<?php echo $submission['id']; ?>" name="selected_time" value="" required>
|
||||
</div>
|
||||
|
||||
<div class="time-slots-container">
|
||||
<h5>选择预约时间</h5>
|
||||
<div class="time-grid" id="timeGrid_<?php echo $submission['id']; ?>">
|
||||
<!-- 时间格子将通过JavaScript生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="duration_<?php echo $submission['id']; ?>">服务时长(分钟)</label>
|
||||
<input type="number" id="duration_<?php echo $submission['id']; ?>" name="duration" min="30" step="30" value="60" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="custom_notes_<?php echo $submission['id']; ?>">自定义服务需求:</label>
|
||||
<textarea id="custom_notes_<?php echo $submission['id']; ?>" name="custom_notes" rows="3"
|
||||
placeholder="如有特殊需求,请在此说明..."></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">确认预约</button>
|
||||
@@ -421,10 +648,154 @@ try {
|
||||
alert('预约信息已复制到剪贴板!');
|
||||
}
|
||||
|
||||
// 设置最小日期为今天
|
||||
document.querySelectorAll('input[type="date"]').forEach(input => {
|
||||
input.min = new Date().toISOString().split('T')[0];
|
||||
// 工作时间设置
|
||||
const workingHours = {
|
||||
start: 0, // 00:00
|
||||
end: 24, // 24:00
|
||||
slotDuration: 30 // 30分钟一个时段
|
||||
};
|
||||
|
||||
// 预约数据
|
||||
const bookingsByDate_<?php echo $submission['id']; ?> = <?php echo json_encode($bookings_by_date); ?>;
|
||||
|
||||
// 页面加载时初始化第一个预约的日期和时间
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
<?php foreach ($pending_submissions as $submission): ?>
|
||||
// 初始化日期
|
||||
selectDate(<?php echo $submission['id']; ?>, '<?php echo date('Y-m-d'); ?>');
|
||||
<?php endforeach; ?>
|
||||
});
|
||||
|
||||
// 选择日期
|
||||
function selectDate(submissionId, date) {
|
||||
const calendarGrid = document.getElementById('calendarGrid_' + submissionId);
|
||||
const timeGrid = document.getElementById('timeGrid_' + submissionId);
|
||||
const selectedDateInput = document.getElementById('selected_date_' + submissionId);
|
||||
|
||||
// 更新日期输入
|
||||
selectedDateInput.value = date;
|
||||
|
||||
// 更新日历选中状态
|
||||
calendarGrid.querySelectorAll('.calendar-day').forEach(day => {
|
||||
day.classList.remove('selected');
|
||||
});
|
||||
document.querySelector(`[data-date="${date}"]`).classList.add('selected');
|
||||
|
||||
// 生成时间段
|
||||
generateTimeSlots(submissionId, date);
|
||||
}
|
||||
|
||||
// 生成时间段
|
||||
function generateTimeSlots(submissionId, date) {
|
||||
const timeGrid = document.getElementById('timeGrid_' + submissionId);
|
||||
const selectedTimeInput = document.getElementById('selected_time_' + submissionId);
|
||||
|
||||
timeGrid.innerHTML = '';
|
||||
|
||||
// 获取当天已有预约
|
||||
const dayBookings = bookingsByDate_<?php echo $submission['id']; ?>[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, submissionId);
|
||||
|
||||
const slotDiv = document.createElement('div');
|
||||
slotDiv.className = `time-slot ${isPast ? 'past' : ''} ${isBooked ? 'booked' : 'available'}`;
|
||||
slotDiv.textContent = timeString;
|
||||
slotDiv.onclick = () => selectTimeSlot(submissionId, timeString);
|
||||
|
||||
timeGrid.appendChild(slotDiv);
|
||||
}
|
||||
}
|
||||
|
||||
selectedTimeInput.value = '';
|
||||
}
|
||||
|
||||
// 检查时间段是否已被预约
|
||||
function checkTimeSlotBooked(date, time, submissionId) {
|
||||
const bookings = bookingsByDate_<?php echo $submission['id']; ?>[date] || [];
|
||||
|
||||
for (let booking of bookings) {
|
||||
const bookingStart = booking.start_time;
|
||||
const bookingEnd = booking.end_time;
|
||||
|
||||
// 处理跨天预约的情况
|
||||
if (booking.is_cross_day) {
|
||||
// 如果是跨天预约的第二天记录,所有时间都应该是已被预约的(从00:00到结束时间)
|
||||
if (time < bookingEnd) {
|
||||
return true; // 该时间段已被预约
|
||||
}
|
||||
} else {
|
||||
// 正常预约检查
|
||||
if (time >= bookingStart && time < bookingEnd) {
|
||||
return true; // 该时间段已被预约
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // 该时间段可用
|
||||
}
|
||||
|
||||
// 选择时间
|
||||
function selectTimeSlot(submissionId, time) {
|
||||
const selectedTimeInput = document.getElementById('selected_time_' + submissionId);
|
||||
|
||||
// 更新时间输入
|
||||
selectedTimeInput.value = time;
|
||||
|
||||
// 更新时间段选中状态
|
||||
const timeGrid = document.getElementById('timeGrid_' + submissionId);
|
||||
timeGrid.querySelectorAll('.time-slot').forEach(slot => {
|
||||
slot.classList.remove('selected');
|
||||
});
|
||||
|
||||
const slotElement = timeGrid.querySelector(`[onclick="selectTimeSlot(${submissionId}, '${time}')"]`);
|
||||
if (slotElement) {
|
||||
slotElement.classList.add('selected');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新套餐信息
|
||||
function updatePackageInfo(submissionId) {
|
||||
const packageSelect = document.getElementById('selected_package_' + submissionId);
|
||||
const selectedOption = packageSelect.options[packageSelect.selectedIndex];
|
||||
const packageInfoDiv = document.getElementById('packageInfo_' + submissionId);
|
||||
const durationInput = document.getElementById('duration_' + submissionId);
|
||||
const totalPriceInput = document.getElementById('total_price_' + submissionId);
|
||||
|
||||
if (selectedOption && selectedOption.value) {
|
||||
const packageId = parseInt(selectedOption.value);
|
||||
const duration = parseInt(selectedOption.getAttribute('data-duration'));
|
||||
const price = parseFloat(selectedOption.getAttribute('data-price'));
|
||||
const services = JSON.parse(selectedOption.getAttribute('data-services'));
|
||||
|
||||
// 更新套餐信息显示
|
||||
document.getElementById('packageName_' + submissionId).textContent = selectedOption.textContent;
|
||||
document.getElementById('packageDuration_' + submissionId).textContent = `基础时长: ${duration}分钟`;
|
||||
document.getElementById('packagePrice_' + submissionId).textContent = `套餐价格: ¥${price.toFixed(2)}`;
|
||||
|
||||
// 更新服务项目显示
|
||||
const servicesContainer = document.getElementById('packageServices_' + submissionId);
|
||||
if (services && services.length > 0) {
|
||||
servicesContainer.innerHTML = '<strong>包含服务:</strong><br>' +
|
||||
services.map(service => `• ${service}`).join('<br>');
|
||||
} else {
|
||||
servicesContainer.innerHTML = '';
|
||||
}
|
||||
|
||||
// 更新时长和价格
|
||||
durationInput.value = duration;
|
||||
totalPriceInput.value = price.toFixed(2);
|
||||
|
||||
packageInfoDiv.style.display = 'block';
|
||||
} else {
|
||||
packageInfoDiv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user