diff --git a/bookings.php b/bookings.php index a241953..63b726b 100644 --- a/bookings.php +++ b/bookings.php @@ -263,6 +263,24 @@ try {
+ + prepare("SELECT id FROM vip_customers WHERE phone = ? AND car_number = ? AND is_active = 1"); + $stmt_check_vip->execute([$booking['phone'], $booking['car_number']]); + $is_vip = $stmt_check_vip->fetch() !== false; + ?> + +
+ + + +
+
+ '无效的套餐ID'], JSON_UNESCAPED_UNICODE); + exit; +} + +try { + $stmt = $pdo->prepare("SELECT * FROM packages WHERE id = ?"); + $stmt->execute([$id]); + $package = $stmt->fetch(); + + if (!$package) { + echo json_encode(['error' => '套餐不存在'], JSON_UNESCAPED_UNICODE); + exit; + } + + echo json_encode($package, JSON_UNESCAPED_UNICODE); +} catch (Exception $e) { + echo json_encode(['error' => '获取套餐信息失败:' . $e->getMessage()], JSON_UNESCAPED_UNICODE); +} +?> + diff --git a/index.php b/index.php index cf9fe8e..16f3696 100644 --- a/index.php +++ b/index.php @@ -32,11 +32,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $phone = $vip_customer['phone']; // #region agent log $log_data = json_encode(['location' => 'index.php:28', 'message' => 'VIP customer data', 'data' => ['vip_id' => $vip_id, 'has_car_model' => isset($vip_customer['car_model']), 'has_car_number' => isset($vip_customer['car_number'])], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'A']); - // 确保日志目录存在 - $log_dir = '.cursor'; - if (!file_exists($log_dir)) { - mkdir($log_dir, 0777, true); - } file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND); // #endregion // VIP客户信息优先,但允许通过POST覆盖(如果用户想修改) @@ -121,11 +116,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // 两个时间段重叠的条件:现有预约的开始时间 < 新预约的结束时间 AND 现有预约的结束时间 > 新预约的开始时间 // #region agent log $log_data = json_encode(['location' => 'index.php:98', 'message' => 'Checking time conflict', 'data' => ['start_time' => $start_time, 'end_time' => $end_time, 'duration' => $duration], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'F']); - // 确保日志目录存在 - $log_dir = '.cursor'; - if (!file_exists($log_dir)) { - mkdir($log_dir, 0777, true); - } file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND); // #endregion $stmt = $pdo->prepare("SELECT COUNT(*) FROM bookings @@ -136,11 +126,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $conflict_count = $stmt->fetchColumn(); // #region agent log $log_data = json_encode(['location' => 'index.php:107', 'message' => 'Time conflict check result', 'data' => ['conflict_count' => $conflict_count], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'F']); - // 确保日志目录存在 - $log_dir = '.cursor'; - if (!file_exists($log_dir)) { - mkdir($log_dir, 0777, true); - } file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND); // #endregion @@ -148,6 +133,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { throw new Exception('该时间段已被预约,请选择其他时间'); } + // 检查客户是否有历史预约记录,如果有则自动转为普通客户 + // #region agent log + $log_data = json_encode(['location' => 'index.php:136', 'message' => 'Checking customer booking history', 'data' => ['phone' => $phone, 'car_number' => $car_number, 'current_member_type' => $member_type], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'I']); + file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND); + // #endregion + // 检查该手机号和车牌号组合是否有历史预约记录 + $stmt = $pdo->prepare("SELECT COUNT(*) FROM bookings + WHERE phone = ? AND car_number = ?"); + $stmt->execute([$phone, $car_number]); + $has_booking_history = $stmt->fetchColumn() > 0; + + // 如果客户有历史预约记录,自动转为普通客户(除非是VIP客户模式) + if ($has_booking_history && $member_type === 'VIP会员' && $customer_type !== 'vip') { + $member_type = '普通客户'; + // #region agent log + $log_data = json_encode(['location' => 'index.php:144', 'message' => 'Auto converted to regular customer', 'data' => ['reason' => 'has_booking_history', 'phone' => $phone, 'car_number' => $car_number], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'I']); + file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND); + // #endregion + } + // 插入预约记录 // 对于0元订单,自动标记为已付款 $payment_status = ($total_price <= 0) ? '已付款' : '未付款'; @@ -231,11 +236,6 @@ foreach ($all_bookings as $booking) { // #region agent log $log_data = json_encode(['location' => 'index.php:196', 'message' => 'Checking cross-day booking', 'data' => ['booking_date' => $booking_date, 'start_time' => $booking_start_time, 'end_time' => $booking_end_time, 'start_ts' => $start_timestamp, 'end_ts' => $end_timestamp], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'G']); - // 确保日志目录存在 - $log_dir = '.cursor'; - if (!file_exists($log_dir)) { - mkdir($log_dir, 0777, true); - } file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND); // #endregion diff --git a/packages.php b/packages.php index d1d4d40..5967a2d 100644 --- a/packages.php +++ b/packages.php @@ -2,6 +2,7 @@ require_once 'db_connect.php'; $message = ''; +$success_message = ''; // 处理套餐操作 if ($_SERVER['REQUEST_METHOD'] === 'POST') { @@ -16,9 +17,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $services = implode(',', array_filter(array_map('trim', $_POST['services'] ?? []))); $package_reminder = trim($_POST['package_reminder']); + if (empty($package_name)) { + throw new Exception('套餐名称不能为空'); + } + if ($base_duration <= 0) { + throw new Exception('基础时长必须大于0'); + } + if ($price < 0) { + throw new Exception('价格不能为负数'); + } + $stmt = $pdo->prepare("INSERT INTO packages (package_name, description, base_duration, price, services, package_reminder) VALUES (?, ?, ?, ?, ?, ?)"); $stmt->execute([$package_name, $description, $base_duration, $price, $services, $package_reminder]); - $message = "套餐添加成功!"; + $success_message = "套餐添加成功!"; } elseif ($action === 'update') { $id = (int)$_POST['id']; @@ -29,6 +40,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $services = implode(',', array_filter(array_map('trim', $_POST['services'] ?? []))); $is_active = isset($_POST['is_active']) ? 1 : 0; + if (empty($package_name)) { + throw new Exception('套餐名称不能为空'); + } + if ($base_duration <= 0) { + throw new Exception('基础时长必须大于0'); + } + if ($price < 0) { + throw new Exception('价格不能为负数'); + } + // 获取当前套餐的专属预约信息,避免更新时丢失 $stmt = $pdo->prepare("SELECT package_reminder FROM packages WHERE id = ?"); $stmt->execute([$id]); @@ -38,13 +59,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // 更新套餐信息,保留现有的package_reminder $stmt = $pdo->prepare("UPDATE packages SET package_name = ?, description = ?, base_duration = ?, price = ?, services = ?, package_reminder = ?, is_active = ? WHERE id = ?"); $stmt->execute([$package_name, $description, $base_duration, $price, $services, $package_reminder, $is_active, $id]); - $message = "套餐更新成功!"; + $success_message = "套餐更新成功!"; } elseif ($action === 'delete') { $id = (int)$_POST['id']; + + // 检查是否有预约使用此套餐 + $stmt = $pdo->prepare("SELECT COUNT(*) FROM bookings WHERE package_id = ?"); + $stmt->execute([$id]); + $booking_count = $stmt->fetchColumn(); + + if ($booking_count > 0) { + throw new Exception("无法删除此套餐,因为有 {$booking_count} 个预约正在使用此套餐。建议禁用此套餐而不是删除。"); + } + $stmt = $pdo->prepare("DELETE FROM packages WHERE id = ?"); $stmt->execute([$id]); - $message = "套餐删除成功!"; + $success_message = "套餐删除成功!"; + } elseif ($action === 'update_reminder') { // 处理套餐专属预约信息的单独更新 $id = (int)$_POST['id']; @@ -55,7 +87,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // 返回JSON响应 header('Content-Type: application/json'); - echo json_encode(['success' => true, 'message' => '套餐专属预约信息更新成功!']); + echo json_encode(['success' => true, 'message' => '套餐专属预约信息更新成功!'], JSON_UNESCAPED_UNICODE); + exit; + } elseif ($action === 'toggle_active') { + // 快速切换启用/禁用状态 + $id = (int)$_POST['id']; + $stmt = $pdo->prepare("UPDATE packages SET is_active = NOT is_active WHERE id = ?"); + $stmt->execute([$id]); + + header('Content-Type: application/json'); + echo json_encode(['success' => true, 'message' => '状态更新成功!'], JSON_UNESCAPED_UNICODE); exit; } } catch (Exception $e) { @@ -63,36 +104,721 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } -// 获取套餐列表 -$stmt = $pdo->query("SELECT * FROM packages ORDER BY created_at DESC"); +// 获取搜索和筛选参数 +$search = isset($_GET['search']) ? trim($_GET['search']) : ''; +$status_filter = isset($_GET['status']) ? $_GET['status'] : 'all'; // all, active, inactive +$sort_by = isset($_GET['sort']) ? $_GET['sort'] : 'created_at'; // created_at, price, duration, name +$sort_order = isset($_GET['order']) ? $_GET['order'] : 'desc'; // asc, desc + +// 构建查询 +$query = "SELECT p.*, + COUNT(b.id) as booking_count, + SUM(CASE WHEN b.status NOT IN ('已完成', '已取消') THEN 1 ELSE 0 END) as active_booking_count + FROM packages p + LEFT JOIN bookings b ON p.id = b.package_id + WHERE 1=1"; +$params = []; + +// 搜索条件 +if (!empty($search)) { + $query .= " AND (p.package_name LIKE ? OR p.description LIKE ?)"; + $search_param = "%{$search}%"; + $params[] = $search_param; + $params[] = $search_param; +} + +// 状态筛选 +if ($status_filter === 'active') { + $query .= " AND p.is_active = 1"; +} elseif ($status_filter === 'inactive') { + $query .= " AND p.is_active = 0"; +} + +$query .= " GROUP BY p.id"; + +// 排序 +$allowed_sorts = ['created_at', 'price', 'base_duration', 'package_name', 'booking_count']; +$sort_by = in_array($sort_by, $allowed_sorts) ? $sort_by : 'created_at'; +$sort_order = in_array($sort_order, ['asc', 'desc']) ? $sort_order : 'desc'; + +if ($sort_by === 'package_name') { + $query .= " ORDER BY p.package_name " . strtoupper($sort_order); +} elseif ($sort_by === 'booking_count') { + $query .= " ORDER BY booking_count " . strtoupper($sort_order) . ", p.created_at DESC"; +} else { + $query .= " ORDER BY p.{$sort_by} " . strtoupper($sort_order); +} + +$stmt = $pdo->prepare($query); +$stmt->execute($params); $packages = $stmt->fetchAll(); + +// 统计信息 +$total_packages = count($packages); +$active_packages = count(array_filter($packages, function($p) { return $p['is_active']; })); +$inactive_packages = $total_packages - $active_packages; +$total_bookings = array_sum(array_column($packages, 'booking_count')); ?> - - 张老师撸车(私家车库)工作室 + + + 张老师撸车(私家车库)工作室 - 套餐管理 -
+

🚗 张老师撸车工作室 - 套餐管理

-
+
-
-

📋 添加新套餐

-
- - -
- - -
+ +
+ +
+ -
- - -
+ +
+
+
总套餐数
+
+
个套餐
+
+
+
启用中
+
+
个套餐
+
+
+
已禁用
+
+
个套餐
+
+
+
总预约数
+
+
次预约
+
+
-
-
- -
- - 分钟 -
-
- -
- -
- ¥ - -
-
+ +
+ + - -
- -
-
- - -
-
- - -
-
- +
+ +
- -
- - +
+ +
- -
- +
+ +
+ + 重置 +
+ +
+
+

📋 添加新套餐

+ +
+
+
+ + +
+ + +
+ +
+ + +
+ +
+
+ +
+ + 分钟 +
+
+ +
+ +
+ ¥ + +
+
+
+ +
+ +
+
+ + +
+
+ + +
+
+ +
+ +
+ + +
+ +
+ + +
+
+
+
+ +
-

📊 套餐列表

+

📊 套餐列表 ( 个)

📦

暂无套餐数据

-

点击上方"添加套餐"按钮创建第一个套餐吧!

+

点击上方"添加新套餐"创建第一个套餐吧!

@@ -188,8 +986,11 @@ $packages = $stmt->fetchAll();

+ 0): ?> + 次预约 + - +
@@ -200,12 +1001,12 @@ $packages = $stmt->fetchAll();
- 时长 - 分钟 + ⏱️ 时长 + 分钟
- 价格 - ¥ + 💰 价格 + ¥
@@ -214,7 +1015,7 @@ $packages = $stmt->fetchAll(); if ($services && !empty(trim($services[0]))): ?>
- 包含服务 + ✨ 包含服务
@@ -225,97 +1026,27 @@ $packages = $stmt->fetchAll();
-
-
套餐专属预约信息
- -
- - +
+
📝 套餐专属预约信息
+ +
+ +
- -
+ + + - +
- - -
@@ -323,22 +1054,97 @@ $packages = $stmt->fetchAll();
+ + + - + // 取消编辑提醒 + function cancelReminderEdit(button) { + const textarea = button.closest('.package-reminder').querySelector('.reminder-editor'); + textarea.blur(); + } + + // 自适应文本框高度 + function autoResizeTextarea(textarea) { + textarea.style.height = 'auto'; + textarea.style.height = Math.min(textarea.scrollHeight + 10, 500) + 'px'; + } + + // 工具函数 + function trim(str) { + return str ? str.trim() : ''; + } + + function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + // 页面加载时初始化 + window.addEventListener('DOMContentLoaded', function() { + const textareas = document.querySelectorAll('.reminder-editor'); + textareas.forEach(autoResizeTextarea); + }); + - \ No newline at end of file + diff --git a/vip.php b/vip.php index 7210fab..984635f 100644 --- a/vip.php +++ b/vip.php @@ -94,6 +94,56 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $stmt = $pdo->prepare("DELETE FROM vip_customers WHERE id = ?"); $stmt->execute([$id]); $success_message = "VIP客户删除成功!"; + + } elseif ($action === 'convert_from_booking') { + // 从预约记录转换为VIP客户 + $booking_id = isset($_POST['booking_id']) ? (int)$_POST['booking_id'] : 0; + + if ($booking_id <= 0) { + throw new Exception('无效的预约ID'); + } + + // 获取预约信息 + $stmt = $pdo->prepare("SELECT customer_name, phone, car_model, car_number FROM bookings WHERE id = ?"); + $stmt->execute([$booking_id]); + $booking = $stmt->fetch(); + + if (!$booking) { + throw new Exception('预约记录不存在'); + } + + // 检查是否已经是VIP客户 + $stmt = $pdo->prepare("SELECT id FROM vip_customers WHERE phone = ? AND car_number = ?"); + $stmt->execute([$booking['phone'], $booking['car_number']]); + if ($stmt->fetch()) { + throw new Exception('该客户已经是VIP客户'); + } + + // 插入VIP客户 + $stmt = $pdo->prepare("INSERT INTO vip_customers + (customer_name, phone, car_model, car_number, notes) + VALUES (?, ?, ?, ?, ?)"); + try { + $notes = '从预约记录 #' . $booking_id . ' 自动转换'; + $stmt->execute([ + $booking['customer_name'], + $booking['phone'], + $booking['car_model'] ?? '', + $booking['car_number'] ?? '', + $notes + ]); + + // 更新该客户的所有预约记录的member_type为VIP会员 + $stmt = $pdo->prepare("UPDATE bookings SET member_type = 'VIP会员' WHERE phone = ? AND car_number = ?"); + $stmt->execute([$booking['phone'], $booking['car_number']]); + + $success_message = "客户已成功转换为VIP客户!"; + } catch (PDOException $e) { + if ($e->errorInfo[1] == 1062) { + throw new Exception('该客户已经是VIP客户'); + } + throw $e; + } } } } catch (Exception $e) { @@ -248,6 +298,64 @@ try {
+ +
+

📋 从预约记录转换为VIP客户

+

选择预约记录,快速将普通客户转换为VIP客户

+ prepare("SELECT DISTINCT b.id, b.customer_name, b.phone, b.car_model, b.car_number, + COUNT(*) as booking_count, + MAX(b.start_time) as last_booking_time + FROM bookings b + LEFT JOIN vip_customers v ON b.phone = v.phone AND b.car_number = v.car_number AND v.is_active = 1 + WHERE b.member_type = '普通客户' + AND v.id IS NULL + AND b.start_time >= DATE_SUB(NOW(), INTERVAL 30 DAY) + GROUP BY b.phone, b.car_number, b.customer_name, b.car_model + ORDER BY last_booking_time DESC + LIMIT 20"); + $stmt->execute(); + $convertible_bookings = $stmt->fetchAll(); + } catch (Exception $e) { + $convertible_bookings = []; + } + ?> + + +
暂无可转换的普通客户
+ +
+ +
+
+
+
+ + 手机号: | + 车牌号:
+ + 车型: | + + 预约次数:次 +
+
+
+ + + +
+
+
+ +
+ +
+

👑 VIP客户列表 (共 位)

@@ -416,11 +524,6 @@ try { // 查询所有可用的洗车套餐 // #region agent log $log_data = json_encode(['location' => 'vip.php:417', 'message' => 'Loading packages', 'data' => ['using_pdo' => isset($pdo), 'using_conn' => isset($conn)], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'B']); - // 确保日志目录存在 - $log_dir = '.cursor'; - if (!file_exists($log_dir)) { - mkdir($log_dir, 0777, true); - } file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND); // #endregion try {