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 047438968b feat(预约系统): 添加VIP客户搜索功能并优化用户体验
添加VIP客户搜索功能,支持姓名和手机号模糊搜索
增加手机号自动检测VIP功能并提供切换按钮
优化VIP客户选择界面样式和交互流程
2025-11-19 01:57:46 +08:00

1012 lines
43 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_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) {
$bookings_by_date[$booking['date']][] = $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>
<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 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;
// 工作时间设置
const workingHours = {
start: 8, // 8:00
end: 18, // 18: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('service_date').value = todayStr;
// 检查数据库连接
checkDatabaseConnection();
// 加载VIP客户(必须在页面加载时完成)
loadVIPCustomers();
});
let allVIPCustomers = []; // 全局变量存储所有VIP客户数据
// 加载VIP客户列表
function loadVIPCustomers() {
fetch('get_vip_customers.php')
.then(response => response.json())
.then(data => {
allVIPCustomers = data; // 存储所有VIP客户数据
updateVIPSelect(data); // 更新下拉列表
})
.catch(error => {
console.log('加载VIP客户列表失败:', error);
allVIPCustomers = [];
});
}
// 更新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() {
const searchTerm = document.getElementById('vip_search').value.trim();
const searchResultsDiv = document.getElementById('vip_search_results');
if (searchTerm === '') {
clearSearchResults();
updateVIPSelect(allVIPCustomers);
return;
}
// 模糊搜索:支持姓名和手机号搜索
const filteredCustomers = allVIPCustomers.filter(vip =>
vip.customer_name.includes(searchTerm) ||
vip.phone.includes(searchTerm)
);
// 显示搜索结果
displaySearchResults(filteredCustomers, searchTerm);
// 隐藏下拉列表
document.getElementById('vip_id').style.display = 'none';
}
// 显示搜索结果
function displaySearchResults(customers, searchTerm) {
const searchResultsDiv = document.getElementById('vip_search_results');
if (customers.length === 0) {
searchResultsDiv.innerHTML = '<div class="no-results">没有找到匹配的客户</div>';
} else {
searchResultsDiv.innerHTML = customers.map(vip => `
<div class="vip-search-item" onclick="selectVIPCustomer(${vip.id})"
data-customer-id="${vip.id}">
<div class="customer-name">${highlightSearchTerm(vip.customer_name, searchTerm)}</div>
<div class="customer-phone">${highlightSearchTerm(vip.phone, searchTerm)}</div>
<div class="customer-car">${vip.car_model || '未知车型'} - ${vip.car_number || '未知车牌'}</div>
</div>
`).join('');
}
searchResultsDiv.style.display = 'block';
}
// 高亮搜索关键词
function highlightSearchTerm(text, searchTerm) {
if (!searchTerm) return text;
const regex = new RegExp(`(${searchTerm})`, 'gi');
return text.replace(regex, '<mark>$1</mark>');
}
// 清除搜索结果
function clearSearchResults() {
document.getElementById('vip_search_results').style.display = 'none';
document.getElementById('vip_id').style.display = 'block';
// 清除搜索输入
const searchInput = document.getElementById('vip_search');
if (searchInput && searchInput.value !== '') {
searchInput.value = '';
updateVIPSelect(allVIPCustomers);
}
}
// 选择VIP客户
function selectVIPCustomer(customerId) {
// 设置下拉选择值
document.getElementById('vip_id').value = customerId;
// 清除搜索状态
clearSearchResults();
// 加载VIP客户信息
loadVIPInfo();
}
// 检查手机号是否为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');
if (customerType === 'vip') {
vipSelectGroup.style.display = 'block';
newCustomerFields.style.display = 'none';
// 隐藏新客户字段的必填属性
document.getElementById('customer_name').required = false;
document.getElementById('phone').required = false;
} else {
vipSelectGroup.style.display = 'none';
newCustomerFields.style.display = 'flex';
// 显示新客户字段的必填属性
document.getElementById('customer_name').required = true;
document.getElementById('phone').required = true;
}
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 (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) => {
details += `
<div class="booking-detail-item">
<div class="booking-time">${booking.start_time} - ${booking.end_time}</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);
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>