3d33e595dd
- 修改预约数据处理逻辑以支持跨天预约情况 - 在时间选择界面添加12小时预约按钮 - 增加自定义时长上限为12小时的验证 - 优化预约详情显示,标记跨天预约延续时段
1305 lines
57 KiB
PHP
1305 lines
57 KiB
PHP
<?php
|
||
session_start();
|
||
require_once 'db_connect.php';
|
||
|
||
$message = '';
|
||
$success_message = '';
|
||
|
||
// 处理表单提交
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
try {
|
||
$customer_type = $_POST['customer_type'];
|
||
$vip_id = isset($_POST['vip_id']) ? (int)$_POST['vip_id'] : 0;
|
||
|
||
// 如果选择VIP客户,从VIP表获取信息
|
||
if ($customer_type === 'vip' && $vip_id > 0) {
|
||
$stmt = $pdo->prepare("SELECT * FROM vip_customers WHERE id = ? AND is_active = 1");
|
||
$stmt->execute([$vip_id]);
|
||
$vip_customer = $stmt->fetch();
|
||
|
||
if (!$vip_customer) {
|
||
throw new Exception('选择的VIP客户无效');
|
||
}
|
||
|
||
// 使用VIP客户信息
|
||
$customer_name = $vip_customer['customer_name'];
|
||
$phone = $vip_customer['phone'];
|
||
$car_model = $vip_customer['car_model'] ?: $car_model; // 允许覆盖
|
||
$car_number = $vip_customer['car_number'] ?: $car_number; // 允许覆盖
|
||
$member_type = 'VIP会员';
|
||
} else {
|
||
// 新客户录入
|
||
$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'] ?? '');
|
||
$member_type = $_POST['member_type'];
|
||
$source = $_POST['source'];
|
||
|
||
// 验证必填字段
|
||
if (empty($customer_name) || empty($phone) || empty($car_model) ||
|
||
empty($car_number) || empty($appointment_date) || empty($appointment_time)) {
|
||
throw new Exception('请填写所有必填字段');
|
||
}
|
||
|
||
// 验证VIP客户或新客户的必填字段
|
||
if ($customer_type === 'vip') {
|
||
if (empty($vip_id)) {
|
||
throw new Exception('请选择一个VIP客户');
|
||
}
|
||
} else {
|
||
if (empty($customer_name) || empty($phone)) {
|
||
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, member_type, source)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||
|
||
$stmt->execute([$customer_name, $phone, $car_model, $car_number, $package_id, $custom_services,
|
||
$start_time, $end_time, $duration, $total_price, $notes, $member_type, $source]);
|
||
|
||
$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 NOT IN ('已完成', '已取消')
|
||
GROUP BY DATE(start_time)
|
||
ORDER BY date");
|
||
$stmt->execute([$start_date, $end_date]);
|
||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
// 将结果转换为键值对格式(日期 => 预约数量)
|
||
$booking_schedule = [];
|
||
$booking_details = []; // 存储详细的预约信息
|
||
foreach ($results as $row) {
|
||
$booking_schedule[$row['date']] = $row['booking_count'];
|
||
$booking_details[$row['date']] = $row['bookings'];
|
||
}
|
||
|
||
// 获取具体的时间段预约信息供JavaScript使用
|
||
$stmt2 = $pdo->prepare("SELECT DATE(start_time) as date,
|
||
TIME_FORMAT(start_time, '%H:%i') as start_time,
|
||
TIME_FORMAT(end_time, '%H:%i') as end_time,
|
||
status,
|
||
customer_name,
|
||
car_model,
|
||
car_number
|
||
FROM bookings
|
||
WHERE DATE(start_time) BETWEEN ? AND ?
|
||
AND status NOT IN ('已完成', '已取消')
|
||
ORDER BY date, start_time");
|
||
$stmt2->execute([$start_date, $end_date]);
|
||
$all_bookings = $stmt2->fetchAll(PDO::FETCH_ASSOC);
|
||
|
||
// 按日期组织预约数据,处理跨天预约情况
|
||
$bookings_by_date = [];
|
||
foreach ($all_bookings as $booking) {
|
||
$start_date = $booking['date'];
|
||
$start_time = $booking['start_time'];
|
||
$end_time = $booking['end_time'];
|
||
|
||
// 将预约添加到开始日期
|
||
$bookings_by_date[$start_date][] = $booking;
|
||
|
||
// 检查是否是跨天预约(结束时间早于开始时间)
|
||
if (strtotime($end_time) < strtotime($start_time)) {
|
||
// 计算第二天的日期
|
||
$next_date = date('Y-m-d', strtotime($start_date . ' +1 day'));
|
||
|
||
// 创建第二天的预约记录副本
|
||
$next_day_booking = $booking;
|
||
$next_day_booking['is_cross_day'] = true; // 标记为跨天预约
|
||
|
||
// 将预约添加到第二天
|
||
$bookings_by_date[$next_date][] = $next_day_booking;
|
||
}
|
||
}
|
||
|
||
// 获取套餐信息用于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">
|
||
|
||
<style>
|
||
/* VIP搜索结果样式 */
|
||
.vip-search-results {
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 0;
|
||
right: 0;
|
||
background: white;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||
z-index: 1000;
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.vip-search-item {
|
||
padding: 12px 16px;
|
||
cursor: pointer;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
transition: background-color 0.2s ease;
|
||
}
|
||
|
||
.vip-search-item:hover {
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
.vip-search-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.vip-search-item .customer-name {
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.vip-search-item .customer-phone {
|
||
color: #666;
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
.vip-search-item .customer-car {
|
||
color: #888;
|
||
font-size: 0.85em;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.vip-search-item.selected {
|
||
background-color: #e3f2fd;
|
||
}
|
||
|
||
#vip_search {
|
||
position: relative;
|
||
}
|
||
|
||
.form-group {
|
||
position: relative;
|
||
}
|
||
|
||
.form-group #vip_search {
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.search-tips {
|
||
font-size: 0.85em;
|
||
color: #666;
|
||
margin-top: 5px;
|
||
}
|
||
|
||
.no-results {
|
||
text-align: center;
|
||
color: #999;
|
||
padding: 20px;
|
||
font-style: italic;
|
||
}
|
||
|
||
.vip-search-results mark {
|
||
background-color: #ffeb3b;
|
||
color: #333;
|
||
padding: 1px 2px;
|
||
border-radius: 2px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.phone-vip-tip {
|
||
font-size: 0.85em;
|
||
margin-top: 5px;
|
||
padding: 8px 12px;
|
||
border-radius: 6px;
|
||
border: 1px solid #ddd;
|
||
}
|
||
|
||
.phone-vip-tip.vip-detected {
|
||
background-color: #e8f5e8;
|
||
border-color: #4caf50;
|
||
color: #2e7d32;
|
||
}
|
||
|
||
.phone-vip-tip.suggestion {
|
||
background-color: #fff3e0;
|
||
border-color: #ff9800;
|
||
color: #ef6c00;
|
||
}
|
||
</style>
|
||
</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>
|
||
<a href="vip.php" class="nav-link">VIP管理</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 ?>"
|
||
data-booking-count="<?= $booking_count ?>"
|
||
onclick="showDateDetails('<?= $date ?>')">
|
||
<div class="day-number"><?= $date_display ?></div>
|
||
<div class="day-week">周<?= $weekday ?></div>
|
||
<div class="day-status"><?= $status_text ?></div>
|
||
<div class="booking-count"><?= $booking_count ?>个预约</div>
|
||
</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>
|
||
<button type="button" class="duration-btn" onclick="selectDuration(720)">12小时</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 id="bookingDetails" class="booking-details" style="display: none;">
|
||
<h3 id="detailsDateTitle"></h3>
|
||
<div id="bookingDetailsContent"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="booking-form-section">
|
||
<h2>📋 预约信息</h2>
|
||
<form method="POST" class="form" id="bookingForm">
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="customer_type">客户类型 *</label>
|
||
<select id="customer_type" name="customer_type" required onchange="handleCustomerTypeChange()">
|
||
<option value="new">新客户</option>
|
||
<option value="vip">VIP客户</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group" id="vip_select_group" style="display: none;">
|
||
<label for="vip_search">搜索VIP客户</label>
|
||
<input type="text" id="vip_search" placeholder="输入姓名或手机号搜索"
|
||
oninput="searchVIPCustomers()" autocomplete="off"
|
||
onfocus="clearSearchResults()">
|
||
<div class="search-tips">💡 支持模糊搜索,输入姓名或手机号即可快速定位</div>
|
||
|
||
<label for="vip_id" style="margin-top: 10px;">选择VIP客户</label>
|
||
<select id="vip_id" name="vip_id" onchange="loadVIPInfo()">
|
||
<option value="">请选择VIP客户</option>
|
||
</select>
|
||
<div id="vip_search_results" class="vip-search-results" style="display: none;"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row" id="new_customer_fields">
|
||
<div class="form-group">
|
||
<label for="customer_name">客户姓名 *</label>
|
||
<input type="text" id="customer_name" name="customer_name" placeholder="请输入客户姓名">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="phone">联系电话 *</label>
|
||
<input type="tel" id="phone" name="phone" placeholder="请输入联系电话"
|
||
oninput="checkPhoneForVIP()">
|
||
<div id="phone_vip_tip" class="phone-vip-tip" style="display: none;"></div>
|
||
</div>
|
||
</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 class="form-group" style="margin-top: 15px;">
|
||
<label for="total_price">最终价格 (¥)</label>
|
||
<input type="number" id="total_price" name="total_price" min="0" step="0.01" value="0" placeholder="输入最终价格" required>
|
||
<input type="hidden" id="package_price" value="0">
|
||
</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-row">
|
||
<div class="form-group">
|
||
<label for="member_type">会员类型 *</label>
|
||
<select id="member_type" name="member_type" required>
|
||
<option value="普通客户">普通客户</option>
|
||
<option value="VIP会员">VIP会员</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="source">客户来源 *</label>
|
||
<select id="source" name="source" required>
|
||
<option value="抖音">抖音</option>
|
||
<option value="微信">微信</option>
|
||
<option value="快手">快手</option>
|
||
<option value="朋友介绍">朋友介绍</option>
|
||
<option value="其他">其他</option>
|
||
</select>
|
||
</div>
|
||
</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>
|
||
|
||
<script>
|
||
const packages = <?= $packages_json ?>;
|
||
const bookingSchedule = <?= json_encode($booking_schedule) ?>;
|
||
const bookingsByDate = <?= json_encode($bookings_by_date) ?>;
|
||
let selectedDate = null;
|
||
let selectedTime = null;
|
||
let selectedDuration = 60;
|
||
|
||
// 工作时间设置 - 改为24小时制
|
||
const workingHours = {
|
||
start: 0, // 00:00
|
||
end: 24, // 24:00
|
||
slotDuration: 30 // 30分钟一个时段
|
||
};
|
||
|
||
// 页面加载完成时初始化
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// 初始化时间选择
|
||
generateTimeSlots();
|
||
|
||
// 处理当前时间
|
||
const now = new Date();
|
||
const todayStr = now.getFullYear() + '-' +
|
||
String(now.getMonth() + 1).padStart(2, '0') + '-' +
|
||
String(now.getDate()).padStart(2, '0');
|
||
document.getElementById('appointment_date').value = todayStr;
|
||
|
||
// 检查数据库连接
|
||
checkDatabaseConnection();
|
||
|
||
// 加载VIP客户(使用异步加载确保数据完整性)
|
||
console.log('开始初始化VIP客户数据...');
|
||
loadVIPCustomers()
|
||
.then(data => {
|
||
console.log('VIP客户数据初始化成功,共', data.length, '条记录');
|
||
})
|
||
.catch(error => {
|
||
console.error('VIP客户数据初始化失败:', error);
|
||
// 即使VIP数据加载失败,也要继续页面初始化
|
||
});
|
||
|
||
// 调试信息
|
||
console.log('页面初始化完成');
|
||
});
|
||
|
||
window.allVIPCustomers = []; // 改为window全局变量,确保在所有函数中可访问
|
||
|
||
// 加载VIP客户列表 - 返回Promise以支持异步调用
|
||
function loadVIPCustomers() {
|
||
console.log('开始加载VIP客户列表...');
|
||
|
||
return new Promise((resolve, reject) => {
|
||
fetch('get_vip_customers.php')
|
||
.then(response => {
|
||
console.log('VIP客户API响应状态:', response.status);
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
return response.text();
|
||
})
|
||
.then(text => {
|
||
console.log('VIP客户API原始响应:', text);
|
||
|
||
try {
|
||
// 先检查是否为空响应
|
||
if (!text || text.trim() === '') {
|
||
throw new Error('API返回空响应');
|
||
}
|
||
|
||
const data = JSON.parse(text);
|
||
|
||
// 检查是否是错误响应
|
||
if (data.error) {
|
||
throw new Error(data.message || 'API返回错误');
|
||
}
|
||
|
||
console.log('VIP客户数据解析成功:', data);
|
||
|
||
// 确保数据是数组格式
|
||
if (!Array.isArray(data)) {
|
||
throw new Error('API返回数据不是数组格式');
|
||
}
|
||
|
||
window.allVIPCustomers = data; // 存储所有VIP客户数据
|
||
updateVIPSelect(data); // 更新下拉列表
|
||
|
||
console.log('VIP客户列表加载完成,共', data.length, '条记录');
|
||
|
||
resolve(data); // 成功完成Promise
|
||
|
||
} catch(e) {
|
||
console.error('VIP客户数据解析失败:', e);
|
||
window.allVIPCustomers = [];
|
||
reject(e); // 拒绝Promise
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('加载VIP客户列表失败:', error);
|
||
window.allVIPCustomers = [];
|
||
reject(error); // 拒绝Promise
|
||
});
|
||
});
|
||
}
|
||
|
||
// 测试搜索功能
|
||
function testSearchFunction() {
|
||
if (window.allVIPCustomers.length > 0) {
|
||
console.log('当前VIP客户:', window.allVIPCustomers);
|
||
|
||
// 模拟搜索测试
|
||
const testTerm = window.allVIPCustomers[0].customer_name.substring(0, 1);
|
||
const results = window.allVIPCustomers.filter(vip =>
|
||
vip.customer_name.includes(testTerm) ||
|
||
vip.phone.includes(testTerm)
|
||
);
|
||
|
||
console.log('搜索测试:', testTerm, '->', results.length, '个结果');
|
||
}
|
||
}
|
||
|
||
// 更新VIP客户下拉列表
|
||
function updateVIPSelect(customers) {
|
||
const vipSelect = document.getElementById('vip_id');
|
||
vipSelect.innerHTML = '<option value="">请选择VIP客户</option>';
|
||
customers.forEach(vip => {
|
||
const option = document.createElement('option');
|
||
option.value = vip.id;
|
||
option.textContent = `${vip.customer_name} (${vip.phone})`;
|
||
vipSelect.appendChild(option);
|
||
});
|
||
}
|
||
|
||
// VIP客户搜索功能 - 修复版本
|
||
function searchVIPCustomers() {
|
||
console.log('开始搜索VIP客户...');
|
||
|
||
const searchInput = document.getElementById('vip_search');
|
||
const searchResultsDiv = document.getElementById('vip_search_results');
|
||
const vipSelect = document.getElementById('vip_id');
|
||
|
||
if (!searchInput || !searchResultsDiv || !vipSelect) {
|
||
console.error('找不到必要的DOM元素');
|
||
return;
|
||
}
|
||
|
||
const searchTerm = searchInput.value.trim();
|
||
console.log('搜索关键词:', searchTerm);
|
||
|
||
// 检查数据加载状态
|
||
console.log('VIP客户总数:', window.allVIPCustomers.length);
|
||
|
||
// 始终显示搜索结果容器
|
||
searchResultsDiv.style.display = 'block';
|
||
|
||
// 如果数据还没加载完成,先加载数据
|
||
if (window.allVIPCustomers.length === 0) {
|
||
console.log('VIP客户数据尚未加载,先加载数据...');
|
||
searchResultsDiv.innerHTML = '<div class="loading">正在加载VIP客户数据...</div>';
|
||
|
||
loadVIPCustomers().then(() => {
|
||
console.log('数据加载完成,重新执行搜索');
|
||
// 数据加载后执行搜索
|
||
performSearch(searchTerm);
|
||
}).catch(error => {
|
||
console.error('加载VIP数据失败:', error);
|
||
searchResultsDiv.innerHTML = '<div class="search-error">加载VIP客户数据失败,请重试</div>';
|
||
});
|
||
} else {
|
||
// 直接执行搜索
|
||
performSearch(searchTerm);
|
||
}
|
||
|
||
function performSearch(term) {
|
||
if (term === '') {
|
||
searchResultsDiv.innerHTML = '';
|
||
searchResultsDiv.style.display = 'none';
|
||
vipSelect.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
// 模糊搜索:支持姓名和手机号搜索
|
||
const filteredCustomers = window.allVIPCustomers.filter(vip => {
|
||
const name = (vip.customer_name || '').toLowerCase();
|
||
const phone = (vip.phone || '').toLowerCase();
|
||
const searchLower = term.toLowerCase();
|
||
|
||
return name.includes(searchLower) || phone.includes(searchLower);
|
||
});
|
||
|
||
console.log('搜索结果数量:', filteredCustomers.length);
|
||
|
||
// 显示搜索结果
|
||
if (filteredCustomers.length === 0) {
|
||
searchResultsDiv.innerHTML = '<div class="no-results">未找到匹配的VIP客户</div>';
|
||
} else {
|
||
let html = '';
|
||
filteredCustomers.forEach(vip => {
|
||
const name = vip.customer_name || '';
|
||
const phone = vip.phone || '';
|
||
const id = vip.id || '';
|
||
|
||
// 高亮处理
|
||
const highlightedName = name.replace(new RegExp(`(${term})`, 'gi'), '<span class="highlight">$1</span>');
|
||
const highlightedPhone = phone.replace(new RegExp(`(${term})`, 'gi'), '<span class="highlight">$1</span>');
|
||
|
||
html += `
|
||
<div class="vip-search-item" onclick="selectVIPCustomer('${id}', '${name}', '${phone}')">
|
||
<div class="customer-name">${highlightedName}</div>
|
||
<div class="customer-phone">${highlightedPhone}</div>
|
||
${vip.car_model || vip.car_number ? `<div class="customer-car">${vip.car_model || ''} ${vip.car_number || ''}</div>` : ''}
|
||
</div>
|
||
`;
|
||
});
|
||
searchResultsDiv.innerHTML = html;
|
||
}
|
||
|
||
// 隐藏下拉列表
|
||
vipSelect.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// 显示搜索结果
|
||
function displaySearchResults(results, searchTerm) {
|
||
console.log('显示搜索结果...', results.length, '个结果');
|
||
|
||
const searchResultsDiv = document.getElementById('vip_search_results');
|
||
|
||
if (!searchResultsDiv) {
|
||
console.error('找不到VIP搜索结果容器');
|
||
return;
|
||
}
|
||
|
||
if (results.length === 0) {
|
||
searchResultsDiv.innerHTML = '<div class="no-results">未找到匹配的VIP客户</div>';
|
||
searchResultsDiv.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
let html = '';
|
||
results.forEach(vip => {
|
||
// 安全地处理数据
|
||
const name = vip.customer_name || '';
|
||
const phone = vip.phone || '';
|
||
const id = vip.id || '';
|
||
|
||
const highlightedName = highlightSearchTerm(name, searchTerm);
|
||
const highlightedPhone = highlightSearchTerm(phone, searchTerm);
|
||
|
||
html += `
|
||
<div class="vip-search-item" onclick="selectVIPCustomer('${id}', '${name}', '${phone}')">
|
||
<div class="customer-name">${highlightedName}</div>
|
||
<div class="customer-phone">${highlightedPhone}</div>
|
||
${vip.car_model || vip.car_number ? `<div class="customer-car">${vip.car_model || ''} ${vip.car_number || ''}</div>` : ''}
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
searchResultsDiv.innerHTML = html;
|
||
searchResultsDiv.style.display = 'block';
|
||
console.log('搜索结果已显示');
|
||
}
|
||
|
||
// 高亮搜索关键词
|
||
function highlightSearchTerm(text, searchTerm) {
|
||
if (!text || !searchTerm) return text || '';
|
||
|
||
console.log('高亮处理:', text, '关键词:', searchTerm);
|
||
|
||
const regex = new RegExp(`(${searchTerm})`, 'gi');
|
||
const result = text.replace(regex, '<span class="highlight">$1</span>');
|
||
|
||
console.log('高亮结果:', result);
|
||
return result;
|
||
}
|
||
|
||
// 清除搜索结果
|
||
function clearSearchResults() {
|
||
console.log('清除搜索结果');
|
||
|
||
const searchResults = document.getElementById('vip_search_results');
|
||
if (searchResults) {
|
||
searchResults.innerHTML = '';
|
||
searchResults.style.display = 'none';
|
||
}
|
||
|
||
const searchInput = document.getElementById('vip_search');
|
||
if (searchInput) {
|
||
searchInput.value = '';
|
||
}
|
||
}
|
||
|
||
// 选择VIP客户 - 修复版本
|
||
function selectVIPCustomer(customerId, customerName = '', customerPhone = '') {
|
||
console.log('选择VIP客户:', customerId, customerName, customerPhone);
|
||
|
||
// 设置下拉选择值
|
||
const vipSelect = document.getElementById('vip_id');
|
||
const searchResultsDiv = document.getElementById('vip_search_results');
|
||
const searchInput = document.getElementById('vip_search');
|
||
|
||
if (vipSelect) {
|
||
vipSelect.value = customerId;
|
||
vipSelect.style.display = 'block';
|
||
console.log('已设置VIP选择值:', customerId);
|
||
} else {
|
||
console.error('找不到VIP选择元素');
|
||
}
|
||
|
||
// 清除搜索状态
|
||
if (searchResultsDiv) {
|
||
searchResultsDiv.innerHTML = '';
|
||
searchResultsDiv.style.display = 'none';
|
||
}
|
||
|
||
if (searchInput) {
|
||
searchInput.value = '';
|
||
}
|
||
|
||
// 加载VIP客户信息
|
||
loadVIPInfo();
|
||
|
||
// 显示选择成功的提示
|
||
if (customerName) {
|
||
console.log('成功选择VIP客户:', customerName);
|
||
}
|
||
}
|
||
|
||
// 检查手机号是否为VIP客户
|
||
function checkPhoneForVIP() {
|
||
const phoneInput = document.getElementById('phone');
|
||
const tipDiv = document.getElementById('phone_vip_tip');
|
||
const phone = phoneInput.value.trim();
|
||
|
||
// 清除提示
|
||
tipDiv.style.display = 'none';
|
||
tipDiv.className = 'phone-vip-tip';
|
||
|
||
// 如果手机号为空或过短,不进行检查
|
||
if (phone.length < 3) {
|
||
return;
|
||
}
|
||
|
||
// 在VIP客户中搜索匹配的手机号
|
||
const matchedVIP = allVIPCustomers.find(vip => vip.phone.includes(phone));
|
||
|
||
if (matchedVIP) {
|
||
// 显示VIP客户提示
|
||
tipDiv.innerHTML = `
|
||
<strong>👑 检测到VIP客户!</strong><br>
|
||
该手机号属于 VIP客户:${matchedVIP.customer_name}<br>
|
||
<button type="button" onclick="switchToVIPMode(${matchedVIP.id})" class="switch-to-vip-btn">
|
||
切换到VIP客户模式
|
||
</button>
|
||
`;
|
||
tipDiv.className = 'phone-vip-tip vip-detected';
|
||
tipDiv.style.display = 'block';
|
||
} else if (phone.length >= 8) {
|
||
// 显示建议提示(当手机号长度足够时)
|
||
tipDiv.innerHTML = `
|
||
💡 <strong>提示:</strong>未找到该手机号的VIP客户<br>
|
||
如果此客户是VIP客户,请检查手机号是否正确
|
||
`;
|
||
tipDiv.className = 'phone-vip-tip suggestion';
|
||
tipDiv.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
// 切换到VIP模式
|
||
function switchToVIPMode(vipId = null) {
|
||
// 切换到VIP客户模式
|
||
document.getElementById('customer_type').value = 'vip';
|
||
handleCustomerTypeChange();
|
||
|
||
// 如果提供了VIP ID,选择该VIP客户
|
||
if (vipId) {
|
||
// 先等待VIP客户列表加载完成
|
||
setTimeout(() => {
|
||
document.getElementById('vip_id').value = vipId;
|
||
loadVIPInfo();
|
||
}, 500);
|
||
}
|
||
|
||
// 隐藏提示
|
||
document.getElementById('phone_vip_tip').style.display = 'none';
|
||
}
|
||
|
||
// 处理客户类型变更
|
||
function handleCustomerTypeChange() {
|
||
const customerType = document.getElementById('customer_type').value;
|
||
const vipSelectGroup = document.getElementById('vip_select_group');
|
||
const newCustomerFields = document.getElementById('new_customer_fields');
|
||
|
||
console.log('切换客户类型:', customerType);
|
||
|
||
if (customerType === 'vip') {
|
||
vipSelectGroup.style.display = 'block';
|
||
newCustomerFields.style.display = 'none';
|
||
// 隐藏新客户字段的必填属性
|
||
document.getElementById('customer_name').required = false;
|
||
document.getElementById('phone').required = false;
|
||
|
||
// 确保VIP客户数据已加载
|
||
if (window.allVIPCustomers.length === 0) {
|
||
console.log('VIP客户数据未加载,开始加载...');
|
||
loadVIPCustomers().then(() => {
|
||
console.log('VIP客户数据加载完成,可进行搜索和选择');
|
||
}).catch(error => {
|
||
console.error('加载VIP数据失败:', error);
|
||
});
|
||
}
|
||
} else {
|
||
vipSelectGroup.style.display = 'none';
|
||
newCustomerFields.style.display = 'flex';
|
||
// 显示新客户字段的必填属性
|
||
document.getElementById('customer_name').required = true;
|
||
document.getElementById('phone').required = true;
|
||
|
||
// 清除VIP选择
|
||
const vipSelect = document.getElementById('vip_id');
|
||
if (vipSelect) {
|
||
vipSelect.value = '';
|
||
}
|
||
|
||
// 清除搜索结果
|
||
const searchResultsDiv = document.getElementById('vip_search_results');
|
||
const searchInput = document.getElementById('vip_search');
|
||
if (searchResultsDiv) {
|
||
searchResultsDiv.innerHTML = '';
|
||
searchResultsDiv.style.display = 'none';
|
||
}
|
||
if (searchInput) {
|
||
searchInput.value = '';
|
||
}
|
||
}
|
||
|
||
updateSubmitButton();
|
||
}
|
||
|
||
// 加载VIP客户信息
|
||
function loadVIPInfo() {
|
||
const vipId = document.getElementById('vip_id').value;
|
||
if (vipId) {
|
||
// 这里将从数据库获取VIP客户详细信息
|
||
fetch(`get_vip_customer.php?id=${vipId}`)
|
||
.then(response => response.json())
|
||
.then(vip => {
|
||
if (vip.car_model) {
|
||
document.getElementById('car_model').value = vip.car_model;
|
||
}
|
||
if (vip.car_number) {
|
||
document.getElementById('car_number').value = vip.car_number;
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.log('加载VIP信息失败:', error);
|
||
});
|
||
}
|
||
updateSubmitButton();
|
||
}
|
||
|
||
function showDateDetails(date) {
|
||
// 获取预约详情容器
|
||
const detailsDiv = document.getElementById('bookingDetails');
|
||
const detailsDateTitle = document.getElementById('detailsDateTitle');
|
||
const detailsContent = document.getElementById('bookingDetailsContent');
|
||
|
||
// 设置日期标题
|
||
const dateObj = new Date(date);
|
||
const dateStr = dateObj.toLocaleDateString('zh-CN', {
|
||
year: 'numeric',
|
||
month: 'long',
|
||
day: 'numeric',
|
||
weekday: 'long'
|
||
});
|
||
detailsDateTitle.textContent = dateStr + ' - 预约详情';
|
||
|
||
// 获取并显示预约详情
|
||
const details = getBookingDetailsForDate(date);
|
||
detailsContent.innerHTML = details;
|
||
|
||
// 显示预约详情区域
|
||
detailsDiv.style.display = 'block';
|
||
|
||
// 同时选择该日期用于预约
|
||
selectDate(date);
|
||
|
||
// 滚动到预约详情区域
|
||
setTimeout(() => {
|
||
detailsDiv.scrollIntoView({ behavior: 'smooth' });
|
||
}, 100);
|
||
}
|
||
|
||
function hideDateDetails() {
|
||
document.getElementById('bookingDetails').style.display = 'none';
|
||
}
|
||
|
||
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) {
|
||
// 检查指定时间段是否已被预约
|
||
const bookings = bookingsByDate[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 getBookingDetailsForDate(date) {
|
||
// 获取指定日期的所有预约详情
|
||
const bookings = bookingsByDate[date] || [];
|
||
let details = '';
|
||
|
||
if (bookings.length === 0) {
|
||
return '<p>该日期暂无预约</p>';
|
||
}
|
||
|
||
details += `<h4>当日预约情况 (共${bookings.length}个预约):</h4>`;
|
||
bookings.forEach((booking, index) => {
|
||
// 处理跨天预约的显示
|
||
let displayStartTime = booking.start_time;
|
||
let displayEndTime = booking.end_time;
|
||
let crossDayLabel = '';
|
||
|
||
if (booking.is_cross_day) {
|
||
// 对于跨天预约的第二天记录,显示为从00:00开始
|
||
displayStartTime = '00:00';
|
||
crossDayLabel = ' <small style="color: #ff6b6b;">(跨天预约延续)</small>';
|
||
}
|
||
|
||
details += `
|
||
<div class="booking-detail-item">
|
||
<div class="booking-time">${displayStartTime} - ${displayEndTime}${crossDayLabel}</div>
|
||
<div class="booking-info">
|
||
<strong>客户:</strong> ${booking.customer_name} |
|
||
<strong>车辆:</strong> ${booking.car_model} (${booking.car_number}) |
|
||
<strong>状态:</strong> <span class="status-${booking.status}">${booking.status}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
return details;
|
||
}
|
||
|
||
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);
|
||
const maxDuration = 720; // 最大时长为12小时(720分钟)
|
||
if (customDuration < 30) {
|
||
alert('自定义时长不能少于30分钟');
|
||
} else if (customDuration > maxDuration) {
|
||
alert('自定义时长不能超过12小时');
|
||
} else {
|
||
selectDuration(customDuration);
|
||
}
|
||
}
|
||
|
||
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);
|
||
|
||
// 设置价格输入字段的值为套餐价格
|
||
document.getElementById('package_price').value = package.price;
|
||
document.getElementById('total_price').value = package.price;
|
||
|
||
console.log('已设置价格:', package.price);
|
||
}
|
||
} else {
|
||
packageInfoDiv.style.display = 'none';
|
||
document.getElementById('package_price').value = 0;
|
||
document.getElementById('total_price').value = 0;
|
||
}
|
||
|
||
updateSubmitButton();
|
||
}
|
||
|
||
function updateSubmitButton() {
|
||
const submitBtn = document.getElementById('submitBtn');
|
||
const customerType = document.getElementById('customer_type').value;
|
||
|
||
// 根据客户类型进行不同的验证
|
||
let isFormValid;
|
||
|
||
if (customerType === 'vip') {
|
||
// VIP模式:验证VIP选择和其他必要字段,但不验证新客户字段
|
||
isFormValid = selectedDate && selectedTime &&
|
||
document.getElementById('vip_id').value &&
|
||
document.getElementById('car_model').value &&
|
||
document.getElementById('car_number').value &&
|
||
document.getElementById('package_id').value &&
|
||
document.getElementById('total_price').value;
|
||
} else {
|
||
// 新客户模式:验证所有必填字段
|
||
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 &&
|
||
document.getElementById('total_price').value;
|
||
}
|
||
|
||
submitBtn.disabled = !isFormValid;
|
||
console.log('更新提交按钮状态:', 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>
|