047438968b
添加VIP客户搜索功能,支持姓名和手机号模糊搜索 增加手机号自动检测VIP功能并提供切换按钮 优化VIP客户选择界面样式和交互流程
1012 lines
43 KiB
PHP
1012 lines
43 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) {
|
||
$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>
|