Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c0e7cbb5f | |||
| 2c5ac2b943 | |||
| c64651d6c7 | |||
| 9cd2b31648 | |||
| 89a22c7b11 | |||
| 5438b944b8 | |||
| ae557aa5c2 | |||
| 905bbc5934 | |||
| 58fbb9f1e1 | |||
| 31899dfea8 |
+32
-15
@@ -60,10 +60,11 @@ try {
|
||||
<meta name="keywords" content="公告,今日待办,预约列表,洗车管理">
|
||||
<title>张老师撸车(私家车库)工作室</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="mobile-nav.js" defer></script>
|
||||
<style>
|
||||
/* 公告页面特有样式 */
|
||||
.announcement-container {
|
||||
max-width: 1200px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -297,6 +298,11 @@ try {
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<button class="mobile-menu-toggle" onclick="toggleMobileMenu()">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
<h1>🚗 张老师撸车工作室 - 今日待办</h1>
|
||||
<nav class="nav">
|
||||
<a href="index.php" class="nav-link">预约洗车</a>
|
||||
@@ -307,16 +313,27 @@ try {
|
||||
<a href="announcement.php" class="nav-link active">今日待办</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- 移动端导航菜单 -->
|
||||
<div class="mobile-nav-overlay" onclick="closeMobileMenu()"></div>
|
||||
<nav class="mobile-nav">
|
||||
<a href="index.php" class="nav-link">预约洗车</a>
|
||||
<a href="bookings.php" class="nav-link">预约管理</a>
|
||||
<a href="pending_bookings.php" class="nav-link">待处理预约</a>
|
||||
<a href="packages.php" class="nav-link">套餐管理</a>
|
||||
<a href="vip.php" class="nav-link">VIP管理</a>
|
||||
<a href="announcement.php" class="nav-link active">今日待办</a>
|
||||
</nav>
|
||||
|
||||
<div class="announcement-container">
|
||||
<!-- 页面标题区 -->
|
||||
<div class="page-header">
|
||||
<div class="page-header enhanced-card">
|
||||
<h1>📋 待办列表</h1>
|
||||
<div class="subtitle"><?php echo date('Y年m月d日', strtotime($selected_date)); ?> - <?php echo date('l', strtotime($selected_date)); ?></div>
|
||||
</div>
|
||||
|
||||
<!-- 日期选择器 -->
|
||||
<div style="background: white; border-radius: 10px; padding: 20px; margin-bottom: 30px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); text-align: center;">
|
||||
<div class="enhanced-card" style="text-align: center; padding: 20px; margin-bottom: 30px;">
|
||||
<form method="GET" style="display: flex; justify-content: center; align-items: center; gap: 15px; flex-wrap: wrap;">
|
||||
<label for="selected_date" style="font-weight: 600; color: #333;">选择日期:</label>
|
||||
<input type="date" id="selected_date" name="selected_date" value="<?php echo $selected_date; ?>" style="padding: 10px 15px; border: 2px solid #e0e0e0; border-radius: 5px; font-size: 1rem; width: 200px; transition: border-color 0.3s ease;">
|
||||
@@ -325,7 +342,7 @@ try {
|
||||
</div>
|
||||
|
||||
<!-- 统计信息卡片 -->
|
||||
<div class="stats-card">
|
||||
<div class="stats-card enhanced-card">
|
||||
<div class="stat-item stat-pending">
|
||||
<div class="stat-label">待处理</div>
|
||||
<div class="stat-number"><?php echo $pending_count; ?></div>
|
||||
@@ -377,44 +394,44 @@ try {
|
||||
$services = explode(',', $booking['services']);
|
||||
}
|
||||
?>
|
||||
<div class="booking-card <?php echo $card_class; ?>">
|
||||
<div class="enhanced-card booking-card <?php echo $card_class; ?>">
|
||||
<div class="booking-time">
|
||||
🕒 <?php echo date('H:i', strtotime($booking['start_time'])); ?> - <?php echo date('H:i', strtotime($booking['end_time'])); ?>
|
||||
<span class="booking-status status-<?php echo strtolower($booking['status']); ?>">
|
||||
<span class="status-badge status-<?php echo strtolower($booking['status']); ?>">
|
||||
<?php echo $booking['status']; ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="booking-details">
|
||||
<div class="detail-row">
|
||||
<div class="booking-details details-grid">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">客户姓名</span>
|
||||
<span class="detail-value"><?php echo htmlspecialchars($booking['customer_name']); ?></span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">联系电话</span>
|
||||
<span class="detail-value"><?php echo htmlspecialchars($booking['phone']); ?></span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">车型</span>
|
||||
<span class="detail-value"><?php echo htmlspecialchars($booking['car_model']); ?></span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">车牌号</span>
|
||||
<span class="detail-value"><?php echo htmlspecialchars($booking['car_number']); ?></span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">套餐</span>
|
||||
<span class="detail-value"><?php echo htmlspecialchars($booking['package_name'] ?: '自定义套餐'); ?></span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">时长</span>
|
||||
<span class="detail-value"><?php echo $booking['duration']; ?> 分钟</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">价格</span>
|
||||
<span class="detail-value">¥<?php echo $booking['total_price']; ?></span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">会员类型</span>
|
||||
<span class="detail-value"><?php echo $booking['member_type']; ?></span>
|
||||
</div>
|
||||
|
||||
+102
-37
@@ -41,7 +41,7 @@ if (isset($_POST['action']) && isset($_POST['booking_id'])) {
|
||||
// 获取所有预约,支持状态筛选和搜索功能
|
||||
try {
|
||||
// 构建查询,支持筛选和搜索
|
||||
$query = "SELECT b.*, p.package_name FROM bookings b LEFT JOIN packages p ON b.package_id = p.id WHERE 1=1 ";
|
||||
$query = "SELECT b.*, p.package_name, p.package_reminder FROM bookings b LEFT JOIN packages p ON b.package_id = p.id WHERE 1=1 ";
|
||||
$params = [];
|
||||
|
||||
// 状态筛选
|
||||
@@ -84,6 +84,7 @@ try {
|
||||
<title>张老师撸车(私家车库)工作室</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="mobile-nav.js" defer></script>
|
||||
|
||||
<!-- Favicon for mobile devices -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🚗</text></svg>">
|
||||
@@ -91,6 +92,11 @@ try {
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<button class="mobile-menu-toggle" onclick="toggleMobileMenu()" aria-label="菜单">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
<h1>🚗 张老师撸车工作室 - 预约管理</h1>
|
||||
<nav class="nav">
|
||||
<a href="index.php" class="nav-link">预约洗车</a>
|
||||
@@ -101,6 +107,17 @@ try {
|
||||
<a href="announcement.php" class="nav-link">今日待办</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- 移动端导航菜单 -->
|
||||
<div class="mobile-nav-overlay" onclick="closeMobileMenu()"></div>
|
||||
<nav class="mobile-nav">
|
||||
<a href="index.php" class="nav-link">预约洗车</a>
|
||||
<a href="bookings.php" class="nav-link active">预约管理</a>
|
||||
<a href="pending_bookings.php" class="nav-link">待处理预约</a>
|
||||
<a href="packages.php" class="nav-link">套餐管理</a>
|
||||
<a href="vip.php" class="nav-link">VIP管理</a>
|
||||
<a href="announcement.php" class="nav-link">今日待办</a>
|
||||
</nav>
|
||||
|
||||
<?php if (isset($success_message)): ?>
|
||||
<div class="success-message"><?php echo $success_message; ?></div>
|
||||
@@ -112,11 +129,11 @@ try {
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card">
|
||||
<div class="card enhanced-card">
|
||||
<h2>所有预约 (共 <?php echo count($bookings); ?> 条)</h2>
|
||||
|
||||
<!-- 筛选和搜索区域 -->
|
||||
<div class="filter-search-area">
|
||||
<div class="filter-search-area enhanced-card">
|
||||
<form method="GET" class="filter-form">
|
||||
<div class="filter-group">
|
||||
<label for="status_filter">状态筛选:</label>
|
||||
@@ -144,7 +161,7 @@ try {
|
||||
<div class="empty-message">暂无预约记录</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($bookings as $booking): ?>
|
||||
<div class="package-card">
|
||||
<div class="package-card enhanced-card">
|
||||
<div class="package-header">
|
||||
<h3><?php echo htmlspecialchars($booking['customer_name']); ?> 的预约</h3>
|
||||
<div class="package-status">
|
||||
@@ -263,6 +280,24 @@ try {
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="openEditModal(<?php echo $booking['id']; ?>, '<?php echo date('Y-m-d\TH:i', strtotime($booking['start_time'])); ?>', '<?php echo date('Y-m-d\TH:i', strtotime($booking['end_time'])); ?>')">修改预约时间</button>
|
||||
<br>
|
||||
<?php endif; ?>
|
||||
<!-- 转换为VIP客户按钮 -->
|
||||
<?php
|
||||
// 检查该客户是否已经是VIP
|
||||
$stmt_check_vip = $pdo->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;
|
||||
?>
|
||||
<?php if (!$is_vip && $booking['member_type'] === '普通客户'): ?>
|
||||
<form method="POST" action="vip.php" style="display: inline; margin-top: 10px;">
|
||||
<input type="hidden" name="action" value="convert_from_booking">
|
||||
<input type="hidden" name="booking_id" value="<?php echo $booking['id']; ?>">
|
||||
<button type="submit" class="btn btn-sm" style="background-color: #ffd700; color: #333; font-weight: bold;"
|
||||
onclick="return confirm('确定要将该客户转换为VIP客户吗?\n\n客户:<?php echo htmlspecialchars($booking['customer_name']); ?>\n手机号:<?php echo htmlspecialchars($booking['phone']); ?>\n车牌号:<?php echo htmlspecialchars($booking['car_number']); ?>')">
|
||||
👑 转换为VIP客户
|
||||
</button>
|
||||
</form>
|
||||
<br>
|
||||
<?php endif; ?>
|
||||
<!-- 复制预约信息按钮 -->
|
||||
<?php
|
||||
// 计算服务时长(分钟)
|
||||
@@ -272,7 +307,19 @@ try {
|
||||
// 获取车友备注(如果不存在则为空字符串)
|
||||
$notes = $booking['notes'] ?? '';
|
||||
?>
|
||||
<button type="button" class="btn btn-sm btn-copy" onclick="copyMessage(<?php echo $booking['id']; ?>, '<?php echo htmlspecialchars($booking['customer_name']); ?>', '<?php echo htmlspecialchars($booking['phone']); ?>', '<?php echo htmlspecialchars($booking['car_model']); ?>', '<?php echo htmlspecialchars($booking['car_number']); ?>', '<?php echo htmlspecialchars($booking['package_name'] ?? '未选择套餐'); ?>', '<?php echo date('Y-m-d', strtotime($booking['start_time'])); ?>', '<?php echo date('H:i', strtotime($booking['start_time'])); ?> - <?php echo date('H:i', strtotime($booking['end_time'])); ?>', <?php echo $duration; ?>, '<?php echo htmlspecialchars($notes); ?>')">复制预约信息</button>
|
||||
<button type="button" class="btn btn-sm btn-copy"
|
||||
data-id="<?php echo $booking['id']; ?>"
|
||||
data-customer-name="<?php echo htmlspecialchars($booking['customer_name']); ?>"
|
||||
data-phone="<?php echo htmlspecialchars($booking['phone']); ?>"
|
||||
data-car-model="<?php echo htmlspecialchars($booking['car_model']); ?>"
|
||||
data-car-number="<?php echo htmlspecialchars($booking['car_number']); ?>"
|
||||
data-package-name="<?php echo htmlspecialchars($booking['package_name'] ?? '未选择套餐'); ?>"
|
||||
data-date="<?php echo date('Y-m-d', strtotime($booking['start_time'])); ?>"
|
||||
data-time="<?php echo date('H:i', strtotime($booking['start_time'])) . ' - ' . date('H:i', strtotime($booking['end_time'])); ?>"
|
||||
data-duration="<?php echo $duration; ?>"
|
||||
data-notes="<?php echo htmlspecialchars($notes); ?>"
|
||||
data-package-reminder="<?php echo htmlspecialchars($booking['package_reminder'] ?? ''); ?>"
|
||||
onclick="copyMessage(this)">复制预约信息</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
@@ -695,44 +742,62 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
// 复制预约信息到剪贴板
|
||||
function copyMessage(id, customer_name, phone, car_model, car_number, package_name, date, time, duration, notes) {
|
||||
// 构建预约信息字符串
|
||||
const message = `【张老师撸车工作室】预约确认\n
|
||||
客户姓名:${customer_name}
|
||||
联系方式:${phone}
|
||||
车型:${car_model}
|
||||
车牌号:${car_number}
|
||||
预约日期:${date}
|
||||
套餐:${package_name}
|
||||
预约时间:${time}
|
||||
预计服务时长:${duration}分钟
|
||||
车友备注:${notes}
|
||||
\n温馨提示:
|
||||
1. 请提前20分钟到达工作室
|
||||
2. 如需改期或取消,请至少提前2小时联系我们
|
||||
3. 联系电话:186-0345-3500
|
||||
</script>
|
||||
|
||||
<!-- 单独的JavaScript块用于复制功能 -->
|
||||
<!-- 复制预约信息功能 -->
|
||||
<script>
|
||||
function copyMessage(button) {
|
||||
try {
|
||||
// 从按钮的data属性获取预约信息
|
||||
var id = button.dataset.id;
|
||||
var customer_name = button.dataset.customerName;
|
||||
var phone = button.dataset.phone;
|
||||
var car_model = button.dataset.carModel;
|
||||
var car_number = button.dataset.carNumber;
|
||||
var package_name = button.dataset.packageName;
|
||||
var date = button.dataset.date;
|
||||
var time = button.dataset.time;
|
||||
var duration = button.dataset.duration;
|
||||
var notes = button.dataset.notes;
|
||||
var package_reminder = button.dataset.packageReminder;
|
||||
|
||||
\n工作室地点:
|
||||
山西省太原市晋源区义井街道西中环充电站内
|
||||
也可高德/百度/腾讯地图软件内搜索‘张老师撸车’
|
||||
var msg = "【张老师撸车工作室】预约确认\n\n";
|
||||
msg += "客户姓名:" + customer_name + "\n";
|
||||
msg += "联系方式:" + phone + "\n";
|
||||
msg += "车型:" + car_model + "\n";
|
||||
msg += "车牌号:" + car_number + "\n";
|
||||
msg += "预约日期:" + date + "\n";
|
||||
msg += "套餐:" + package_name + "\n";
|
||||
msg += "预约时间:" + time + "\n";
|
||||
msg += "预计服务时长:" + duration + "分钟\n";
|
||||
msg += "车友备注:" + notes + "\n";
|
||||
msg += "" + (package_reminder || "") + "\n\n";
|
||||
|
||||
感谢您选择张老师撸车工作室!
|
||||
祝您用车愉快!
|
||||
|
||||
`;
|
||||
|
||||
// 复制到剪贴板
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = message;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
var ta = document.createElement('textarea');
|
||||
ta.value = msg;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.left = '-9999px';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
|
||||
// 显示复制成功提示
|
||||
alert('预约信息已复制到剪贴板!');
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
alert('预约信息已复制到剪贴板!');
|
||||
} catch (err) {
|
||||
alert('复制失败,请手动复制!');
|
||||
}
|
||||
|
||||
document.body.removeChild(ta);
|
||||
} catch (e) {
|
||||
alert('复制过程中发生错误,请稍后重试!');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
// 移动端优化脚本
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
+71
-74
@@ -1,12 +1,9 @@
|
||||
<?php
|
||||
// 配置数据库连接参数
|
||||
$servername = "localhost";
|
||||
$username = "root";
|
||||
$password = "";
|
||||
$dbname = "car_wash_db";
|
||||
// 加载数据库配置
|
||||
require_once 'db_connect.php';
|
||||
|
||||
// 设置响应头为JSON
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 获取请求参数
|
||||
$phone = $_GET['phone'] ?? '';
|
||||
@@ -20,82 +17,82 @@ if (empty($phone)) {
|
||||
'pending' => 0,
|
||||
'completed' => 0,
|
||||
'bookings' => []
|
||||
]);
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 创建数据库连接
|
||||
$conn = new mysqli($servername, $username, $password, $dbname);
|
||||
try {
|
||||
// 准备SQL查询 - 查询用户的预约记录(使用新的数据库结构)
|
||||
$sql = "SELECT b.*, p.package_name
|
||||
FROM bookings b
|
||||
LEFT JOIN packages p ON b.package_id = p.id
|
||||
WHERE b.phone = ?
|
||||
ORDER BY b.start_time DESC, b.id DESC";
|
||||
|
||||
// 检查连接
|
||||
if ($conn->connect_error) {
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$phone]);
|
||||
$results = $stmt->fetchAll();
|
||||
|
||||
$bookings = [];
|
||||
$pending = 0;
|
||||
$completed = 0;
|
||||
|
||||
// 处理查询结果
|
||||
foreach ($results as $row) {
|
||||
// 格式化日期和时间
|
||||
$booking_date = $row['start_time'] ? date('Y-m-d', strtotime($row['start_time'])) : '';
|
||||
$time_slot = '';
|
||||
if ($row['start_time'] && $row['end_time']) {
|
||||
$start_time = date('H:i', strtotime($row['start_time']));
|
||||
$end_time = date('H:i', strtotime($row['end_time']));
|
||||
$time_slot = $start_time . '-' . $end_time;
|
||||
}
|
||||
|
||||
$booking = [
|
||||
'id' => $row['id'],
|
||||
'booking_date' => $booking_date,
|
||||
'phone' => $row['phone'],
|
||||
'car_number' => $row['car_number'] ?? '',
|
||||
'car_model' => $row['car_model'] ?? '',
|
||||
'time_slot' => $time_slot,
|
||||
'status' => $row['status'] ?? '未知',
|
||||
'package_name' => $row['package_name'] ?? '',
|
||||
'notes' => $row['notes'] ?? ''
|
||||
];
|
||||
|
||||
// 统计不同状态的预约数量
|
||||
if ($booking['status'] === '待确认' || $booking['status'] === '已确认' || $booking['status'] === '进行中') {
|
||||
$pending++;
|
||||
} elseif ($booking['status'] === '已完成') {
|
||||
$completed++;
|
||||
}
|
||||
|
||||
$bookings[] = $booking;
|
||||
}
|
||||
|
||||
// 计算总预约数
|
||||
$total = count($bookings);
|
||||
|
||||
// 构建响应数据
|
||||
$response = [
|
||||
'success' => true,
|
||||
'total' => $total,
|
||||
'pending' => $pending,
|
||||
'completed' => $completed,
|
||||
'bookings' => $bookings
|
||||
];
|
||||
|
||||
// 返回JSON数据
|
||||
echo json_encode($response, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '数据库连接失败',
|
||||
'message' => '查询失败:' . $e->getMessage(),
|
||||
'total' => 0,
|
||||
'pending' => 0,
|
||||
'completed' => 0,
|
||||
'bookings' => []
|
||||
]);
|
||||
exit;
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
// 准备SQL查询 - 查询用户的预约记录
|
||||
// 假设预约表名为bookings,套餐表名为packages
|
||||
$sql = "SELECT b.*, p.package_name
|
||||
FROM bookings b
|
||||
LEFT JOIN packages p ON b.package_id = p.id
|
||||
WHERE b.phone = ?
|
||||
ORDER BY b.booking_date DESC, b.id DESC";
|
||||
|
||||
$stmt = $conn->prepare($sql);
|
||||
$stmt->bind_param("s", $phone);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$bookings = [];
|
||||
$pending = 0;
|
||||
$completed = 0;
|
||||
|
||||
// 处理查询结果
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$booking = [
|
||||
'id' => $row['id'],
|
||||
'booking_date' => $row['booking_date'],
|
||||
'phone' => $row['phone'],
|
||||
'car_number' => $row['car_number'] ?? '',
|
||||
'car_model' => $row['car_model'] ?? '',
|
||||
'time_slot' => $row['time_slot'] ?? '',
|
||||
'status' => $row['status'] ?? '未知',
|
||||
'package_name' => $row['package_name'] ?? '',
|
||||
'notes' => $row['notes'] ?? ''
|
||||
];
|
||||
|
||||
// 统计不同状态的预约数量
|
||||
if ($booking['status'] === '待服务') {
|
||||
$pending++;
|
||||
} elseif ($booking['status'] === '已完成') {
|
||||
$completed++;
|
||||
}
|
||||
|
||||
$bookings[] = $booking;
|
||||
}
|
||||
|
||||
// 计算总预约数
|
||||
$total = count($bookings);
|
||||
|
||||
// 构建响应数据
|
||||
$response = [
|
||||
'success' => true,
|
||||
'total' => $total,
|
||||
'pending' => $pending,
|
||||
'completed' => $completed,
|
||||
'bookings' => $bookings
|
||||
];
|
||||
|
||||
// 返回JSON数据
|
||||
echo json_encode($response);
|
||||
|
||||
// 关闭数据库连接
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
?>
|
||||
|
||||
@@ -12,8 +12,8 @@ if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
|
||||
}
|
||||
|
||||
try {
|
||||
// 查询指定日期的总预约时长
|
||||
$sql = "SELECT SUM(duration) as total_duration FROM bookings WHERE DATE(start_time) = ?";
|
||||
// 查询指定日期的总预约时长,只计算有效预约
|
||||
$sql = "SELECT SUM(duration) as total_duration FROM bookings WHERE DATE(start_time) = ? AND status NOT IN ('已完成', '已取消')";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$date]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
require_once 'db_connect.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(['error' => '无效的套餐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);
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -9,7 +9,12 @@ $success_message = '';
|
||||
// 处理表单提交
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
$customer_type = $_POST['customer_type'];
|
||||
// 验证并获取客户类型
|
||||
$customer_type = isset($_POST['customer_type']) ? $_POST['customer_type'] : '';
|
||||
if (!in_array($customer_type, ['vip', 'new'])) {
|
||||
throw new Exception('无效的客户类型');
|
||||
}
|
||||
|
||||
$vip_id = isset($_POST['vip_id']) ? (int)$_POST['vip_id'] : 0;
|
||||
|
||||
// 如果选择VIP客户,从VIP表获取信息
|
||||
@@ -25,25 +30,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// 使用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; // 允许覆盖
|
||||
// #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']);
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
// VIP客户信息优先,但允许通过POST覆盖(如果用户想修改)
|
||||
$car_model = isset($_POST['car_model']) && trim($_POST['car_model']) ? trim($_POST['car_model']) : (isset($vip_customer['car_model']) && $vip_customer['car_model'] ? $vip_customer['car_model'] : '');
|
||||
$car_number = isset($_POST['car_number']) && trim($_POST['car_number']) ? trim($_POST['car_number']) : (isset($vip_customer['car_number']) && $vip_customer['car_number'] ? $vip_customer['car_number'] : '');
|
||||
$member_type = 'VIP会员';
|
||||
} else {
|
||||
// 新客户录入
|
||||
$customer_name = trim($_POST['customer_name']);
|
||||
$phone = trim($_POST['phone']);
|
||||
$customer_name = trim($_POST['customer_name'] ?? '');
|
||||
$phone = trim($_POST['phone'] ?? '');
|
||||
$car_model = trim($_POST['car_model'] ?? '');
|
||||
$car_number = trim($_POST['car_number'] ?? '');
|
||||
}
|
||||
|
||||
$car_model = trim($_POST['car_model']);
|
||||
$car_number = trim($_POST['car_number']);
|
||||
$package_id = (int)$_POST['package_id'];
|
||||
$package_id = (int)($_POST['package_id'] ?? 0);
|
||||
$custom_services = trim($_POST['custom_services'] ?? '');
|
||||
$appointment_date = $_POST['appointment_date'];
|
||||
$appointment_time = $_POST['appointment_time'];
|
||||
$duration = (int)$_POST['duration'];
|
||||
$appointment_date = $_POST['appointment_date'] ?? '';
|
||||
$appointment_time = $_POST['appointment_time'] ?? '';
|
||||
$duration = (int)($_POST['duration'] ?? 60);
|
||||
$notes = trim($_POST['notes'] ?? '');
|
||||
$member_type = $_POST['member_type'];
|
||||
$source = $_POST['source'];
|
||||
|
||||
// 验证member_type和source
|
||||
$allowed_member_types = ['普通客户', 'VIP会员'];
|
||||
$member_type = isset($_POST['member_type']) && in_array($_POST['member_type'], $allowed_member_types) ? $_POST['member_type'] : '普通客户';
|
||||
|
||||
$allowed_sources = ['抖音', '微信', '快手', '朋友介绍', '其他'];
|
||||
$source = isset($_POST['source']) && in_array($_POST['source'], $allowed_sources) ? $_POST['source'] : '其他';
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($customer_name) || empty($phone) || empty($car_model) ||
|
||||
@@ -77,24 +91,68 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
throw new Exception('请选择一个套餐');
|
||||
}
|
||||
|
||||
// 验证日期和时间格式
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $appointment_date)) {
|
||||
throw new Exception('预约日期格式不正确');
|
||||
}
|
||||
if (!preg_match('/^\d{2}:\d{2}$/', $appointment_time)) {
|
||||
throw new Exception('预约时间格式不正确');
|
||||
}
|
||||
|
||||
// 计算预约时间范围
|
||||
$start_time = $appointment_date . ' ' . $appointment_time . ':00';
|
||||
$end_time = date('Y-m-d H:i:s', strtotime($start_time . " +{$duration} minutes"));
|
||||
$start_timestamp = strtotime($start_time);
|
||||
if ($start_timestamp === false) {
|
||||
throw new Exception('预约时间无效,请检查日期和时间');
|
||||
}
|
||||
$end_time = date('Y-m-d H:i:s', $start_timestamp + $duration * 60);
|
||||
|
||||
// 验证结束时间是否有效
|
||||
if ($end_time === false) {
|
||||
throw new Exception('计算结束时间失败');
|
||||
}
|
||||
|
||||
// 检查时间冲突
|
||||
// 两个时间段重叠的条件:现有预约的开始时间 < 新预约的结束时间 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']);
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
$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]);
|
||||
AND start_time < ?
|
||||
AND end_time > ?");
|
||||
$stmt->execute([$end_time, $start_time]);
|
||||
$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']);
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
|
||||
if ($stmt->fetchColumn() > 0) {
|
||||
if ($conflict_count > 0) {
|
||||
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) ? '已付款' : '未付款';
|
||||
@@ -164,17 +222,26 @@ $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'];
|
||||
$booking_date = $booking['date']; // 使用不同的变量名避免覆盖外层$start_date
|
||||
$booking_start_time = $booking['start_time'];
|
||||
$booking_end_time = $booking['end_time'];
|
||||
|
||||
// 将预约添加到开始日期
|
||||
$bookings_by_date[$start_date][] = $booking;
|
||||
$bookings_by_date[$booking_date][] = $booking;
|
||||
|
||||
// 检查是否是跨天预约(结束时间早于开始时间)
|
||||
if (strtotime($end_time) < strtotime($start_time)) {
|
||||
// 检查是否是跨天预约(结束时间早于开始时间,表示跨天)
|
||||
// 注意:这里比较的是时间字符串(HH:MM格式),需要转换为可比较的格式
|
||||
$start_timestamp = strtotime($booking_start_time);
|
||||
$end_timestamp = strtotime($booking_end_time);
|
||||
|
||||
// #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']);
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
|
||||
if ($start_timestamp !== false && $end_timestamp !== false && $end_timestamp < $start_timestamp) {
|
||||
// 计算第二天的日期
|
||||
$next_date = date('Y-m-d', strtotime($start_date . ' +1 day'));
|
||||
$next_date = date('Y-m-d', strtotime($booking_date . ' +1 day'));
|
||||
|
||||
// 创建第二天的预约记录副本
|
||||
$next_day_booking = $booking;
|
||||
@@ -204,6 +271,7 @@ $packages_json = json_encode(array_map(function($package) {
|
||||
<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">
|
||||
<script src="mobile-nav.js" defer></script>
|
||||
|
||||
<style>
|
||||
/* VIP搜索结果样式 */
|
||||
@@ -314,6 +382,11 @@ $packages_json = json_encode(array_map(function($package) {
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<button class="mobile-menu-toggle" onclick="toggleMobileMenu()" aria-label="菜单">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
<h1>🚗 张老师撸车工作室</h1>
|
||||
<nav class="nav">
|
||||
<a href="index.php" class="nav-link active">预约洗车</a>
|
||||
@@ -324,6 +397,17 @@ $packages_json = json_encode(array_map(function($package) {
|
||||
<a href="announcement.php" class="nav-link">今日待办</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- 移动端导航菜单 -->
|
||||
<div class="mobile-nav-overlay" onclick="closeMobileMenu()"></div>
|
||||
<nav class="mobile-nav">
|
||||
<a href="index.php" class="nav-link active">预约洗车</a>
|
||||
<a href="bookings.php" class="nav-link">预约管理</a>
|
||||
<a href="pending_bookings.php" class="nav-link">待处理预约</a>
|
||||
<a href="packages.php" class="nav-link">套餐管理</a>
|
||||
<a href="vip.php" class="nav-link">VIP管理</a>
|
||||
<a href="announcement.php" class="nav-link">今日待办</a>
|
||||
</nav>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="message error-message" style="background-color: #fee; color: #c33; border-color: #fcc;">
|
||||
@@ -403,7 +487,7 @@ $packages_json = json_encode(array_map(function($package) {
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="calendar" id="calendarContainer">
|
||||
<div class="calendar enhanced-card" id="calendarContainer">
|
||||
<!-- 日期将通过JavaScript动态生成 -->
|
||||
</div>
|
||||
|
||||
@@ -583,7 +667,7 @@ $packages_json = json_encode(array_map(function($package) {
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="time-slots" id="timeSlots" style="display: none;">
|
||||
<div class="time-slots enhanced-card" id="timeSlots" style="display: none;">
|
||||
<h3>🕐 选择时间段</h3>
|
||||
<div class="quick-duration">
|
||||
<span>快捷时长:</span>
|
||||
@@ -604,13 +688,13 @@ $packages_json = json_encode(array_map(function($package) {
|
||||
</div>
|
||||
|
||||
<!-- 预约详情显示区域 -->
|
||||
<div id="bookingDetails" class="booking-details" style="display: none;">
|
||||
<div id="bookingDetails" class="booking-details enhanced-card" style="display: none;">
|
||||
<h3 id="detailsDateTitle"></h3>
|
||||
<div id="bookingDetailsContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="booking-form-section">
|
||||
<div class="booking-form-section enhanced-card">
|
||||
<h2>📋 预约信息</h2>
|
||||
<form method="POST" class="form" id="bookingForm">
|
||||
<div class="form-row">
|
||||
@@ -1193,20 +1277,35 @@ $packages_json = json_encode(array_map(function($package) {
|
||||
const carModel = vip.car_model || '';
|
||||
const carNumber = vip.car_number || '';
|
||||
|
||||
// 转义引号,防止JS语法错误
|
||||
const escapedId = id.toString().replace(/'/g, "\\'");
|
||||
const escapedName = name.replace(/'/g, "\\'");
|
||||
const escapedPhone = phone.replace(/'/g, "\\'");
|
||||
// 转义HTML和引号,防止XSS和JS语法错误
|
||||
const escapeHtml = (str) => {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
};
|
||||
const escapeJs = (str) => {
|
||||
return str.replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\\/g, '\\\\');
|
||||
};
|
||||
|
||||
// 高亮处理
|
||||
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>');
|
||||
const escapedId = escapeJs(id.toString());
|
||||
const escapedName = escapeJs(name);
|
||||
const escapedPhone = escapeJs(phone);
|
||||
|
||||
// 高亮处理(先转义HTML,再进行高亮)
|
||||
const escapedNameHtml = escapeHtml(name);
|
||||
const escapedPhoneHtml = escapeHtml(phone);
|
||||
const escapedTerm = escapeHtml(term);
|
||||
const highlightedName = escapedNameHtml.replace(new RegExp(`(${escapedTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'), '<span class="highlight">$1</span>');
|
||||
const highlightedPhone = escapedPhoneHtml.replace(new RegExp(`(${escapedTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'), '<span class="highlight">$1</span>');
|
||||
|
||||
const escapedCarModel = escapeHtml(carModel);
|
||||
const escapedCarNumber = escapeHtml(carNumber);
|
||||
|
||||
html += `
|
||||
<div class="vip-search-item" onclick="selectVIPCustomer('${escapedId}', '${escapedName}', '${escapedPhone}')">
|
||||
<div class="customer-name">${highlightedName}</div>
|
||||
<div class="customer-phone">${highlightedPhone}</div>
|
||||
${carModel || carNumber ? `<div class="customer-car">${carModel} ${carNumber}</div>` : ''}
|
||||
${carModel || carNumber ? `<div class="customer-car">${escapedCarModel} ${escapedCarNumber}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// 移动端导航菜单控制
|
||||
function toggleMobileMenu() {
|
||||
const toggle = document.querySelector('.mobile-menu-toggle');
|
||||
const overlay = document.querySelector('.mobile-nav-overlay');
|
||||
const nav = document.querySelector('.mobile-nav');
|
||||
|
||||
if (!toggle || !overlay || !nav) return;
|
||||
|
||||
toggle.classList.toggle('active');
|
||||
overlay.classList.toggle('active');
|
||||
nav.classList.toggle('active');
|
||||
document.body.style.overflow = nav.classList.contains('active') ? 'hidden' : '';
|
||||
}
|
||||
|
||||
function closeMobileMenu() {
|
||||
const toggle = document.querySelector('.mobile-menu-toggle');
|
||||
const overlay = document.querySelector('.mobile-nav-overlay');
|
||||
const nav = document.querySelector('.mobile-nav');
|
||||
|
||||
if (!toggle || !overlay || !nav) return;
|
||||
|
||||
toggle.classList.remove('active');
|
||||
overlay.classList.remove('active');
|
||||
nav.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 点击导航链接后关闭菜单
|
||||
const mobileNavLinks = document.querySelectorAll('.mobile-nav .nav-link');
|
||||
mobileNavLinks.forEach(link => {
|
||||
link.addEventListener('click', closeMobileMenu);
|
||||
});
|
||||
|
||||
// 点击遮罩层关闭菜单
|
||||
const overlay = document.querySelector('.mobile-nav-overlay');
|
||||
if (overlay) {
|
||||
overlay.addEventListener('click', closeMobileMenu);
|
||||
}
|
||||
|
||||
// ESC键关闭菜单
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeMobileMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// 防止双击缩放(iOS Safari)
|
||||
let lastTouchEnd = 0;
|
||||
document.addEventListener('touchend', function(event) {
|
||||
const now = Date.now();
|
||||
if (now - lastTouchEnd <= 300) {
|
||||
event.preventDefault();
|
||||
}
|
||||
lastTouchEnd = now;
|
||||
}, false);
|
||||
|
||||
// 优化触摸反馈
|
||||
document.addEventListener('touchstart', function(e) {
|
||||
const target = e.target;
|
||||
if (target.classList.contains('btn') ||
|
||||
target.classList.contains('time-slot') ||
|
||||
target.classList.contains('calendar-day') ||
|
||||
target.classList.contains('nav-link')) {
|
||||
target.style.transition = 'transform 0.1s';
|
||||
target.style.transform = 'scale(0.97)';
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener('touchend', function(e) {
|
||||
const target = e.target;
|
||||
if (target.classList.contains('btn') ||
|
||||
target.classList.contains('time-slot') ||
|
||||
target.classList.contains('calendar-day') ||
|
||||
target.classList.contains('nav-link')) {
|
||||
setTimeout(() => {
|
||||
target.style.transform = '';
|
||||
}, 100);
|
||||
}
|
||||
}, { passive: true });
|
||||
});
|
||||
|
||||
+1166
-183
File diff suppressed because it is too large
Load Diff
+34
-91
@@ -186,13 +186,22 @@ try {
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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="待处理预约页面,处理WPS表单数据">
|
||||
<meta name="keywords" content="待处理预约,预约管理,洗车预约">
|
||||
<title>张老师撸车(私家车库)工作室</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="mobile-nav.js" defer></script>
|
||||
|
||||
<!-- Favicon for mobile devices -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🚗</text></svg>">
|
||||
<style>
|
||||
/* 待预约页面特定样式 */
|
||||
.pending-bookings-container {
|
||||
max-width: 1200px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -435,30 +444,7 @@ try {
|
||||
}
|
||||
|
||||
/* 预约时长提示样式 */
|
||||
.duration-alert {
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.duration-alert .alert {
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.duration-alert .alert-warning {
|
||||
background-color: #fff3cd;
|
||||
border-color: #ffeaa7;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.duration-alert .alert-info {
|
||||
background-color: #d1ecf1;
|
||||
border-color: #bee5eb;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
|
||||
/* 套餐信息样式 */
|
||||
.package-info {
|
||||
@@ -577,6 +563,11 @@ try {
|
||||
<body>
|
||||
<div class="pending-bookings-container">
|
||||
<header class="header">
|
||||
<button class="mobile-menu-toggle" onclick="toggleMobileMenu()" aria-label="菜单">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
<h1>🚗 张老师撸车工作室 - 待处理预约</h1>
|
||||
<nav class="nav">
|
||||
<a href="index.php" class="nav-link">预约洗车</a>
|
||||
@@ -588,6 +579,17 @@ try {
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- 移动端导航菜单 -->
|
||||
<div class="mobile-nav-overlay" onclick="closeMobileMenu()"></div>
|
||||
<nav class="mobile-nav">
|
||||
<a href="index.php" class="nav-link">预约洗车</a>
|
||||
<a href="bookings.php" class="nav-link">预约管理</a>
|
||||
<a href="pending_bookings.php" class="nav-link active">待处理预约</a>
|
||||
<a href="packages.php" class="nav-link">套餐管理</a>
|
||||
<a href="vip.php" class="nav-link">VIP管理</a>
|
||||
<a href="announcement.php" class="nav-link">今日待办</a>
|
||||
</nav>
|
||||
|
||||
<?php if (isset($success_message)): ?>
|
||||
<div class="success-message"><?php echo $success_message; ?></div>
|
||||
<?php endif; ?>
|
||||
@@ -600,7 +602,7 @@ try {
|
||||
|
||||
|
||||
|
||||
<div class="card">
|
||||
<div class="card enhanced-card">
|
||||
<h2>待处理预约 (共 <?php echo count($pending_submissions); ?> 条)</h2>
|
||||
|
||||
<?php if (empty($pending_submissions)): ?>
|
||||
@@ -608,7 +610,7 @@ try {
|
||||
<?php else: ?>
|
||||
<!-- 无论记录数量多少,都显示选择列表 -->
|
||||
<?php if (true): ?>
|
||||
<div class="submission-selector">
|
||||
<div class="submission-selector enhanced-card">
|
||||
<h3>选择要处理的预约记录</h3>
|
||||
<div class="submission-list">
|
||||
<?php foreach ($pending_submissions as $submission): ?>
|
||||
@@ -630,7 +632,7 @@ try {
|
||||
|
||||
<!-- 显示选中的预约记录的处理表单 -->
|
||||
<?php foreach ($pending_submissions as $submission): ?>
|
||||
<div class="pending-card" id="submission_<?php echo $submission['id']; ?>" style="display: none;">
|
||||
<div class="pending-card enhanced-card" id="submission_<?php echo $submission['id']; ?>" style="display: none;">
|
||||
<div class="pending-header">
|
||||
<h3><?php echo htmlspecialchars($submission['name']); ?> 的预约请求</h3>
|
||||
<span class="status-badge status-pending">待处理</span>
|
||||
@@ -709,7 +711,7 @@ try {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="package-info" id="packageInfo_<?php echo $submission['id']; ?>">
|
||||
<div class="package-info enhanced-card" id="packageInfo_<?php echo $submission['id']; ?>">
|
||||
<div class="package-details">
|
||||
<h4 id="packageName_<?php echo $submission['id']; ?>"></h4>
|
||||
<div class="package-meta">
|
||||
@@ -754,8 +756,7 @@ try {
|
||||
</div>
|
||||
|
||||
<!-- 预约时长提示区域 -->
|
||||
<div class="duration-alert" id="durationAlert_<?php echo $submission['id']; ?>">
|
||||
<!-- 提示信息将通过JavaScript动态生成 -->
|
||||
<!-- 提示信息将通过JavaScript动态生成 -->
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
@@ -887,9 +888,6 @@ try {
|
||||
|
||||
// 生成时间段
|
||||
generateTimeSlots(submissionId, date);
|
||||
|
||||
// 显示时长提示
|
||||
showDurationAlert(submissionId);
|
||||
}
|
||||
|
||||
// 生成时间段
|
||||
@@ -972,9 +970,6 @@ try {
|
||||
if (slotElement) {
|
||||
slotElement.classList.add('selected');
|
||||
}
|
||||
|
||||
// 显示时长提示
|
||||
showDurationAlert(submissionId);
|
||||
}
|
||||
|
||||
// 更新套餐信息
|
||||
@@ -1178,9 +1173,6 @@ try {
|
||||
if (!buttonFound) {
|
||||
console.warn('⚠️ No matching duration button found for:', minutes, 'minutes');
|
||||
}
|
||||
|
||||
// 显示时长提示
|
||||
showDurationAlert(submissionId);
|
||||
}
|
||||
|
||||
// 应用自定义时长
|
||||
@@ -1203,58 +1195,9 @@ try {
|
||||
customDurationInput.value = roundedDuration;
|
||||
selectDuration(submissionId, roundedDuration);
|
||||
}
|
||||
|
||||
// 显示时长提示
|
||||
showDurationAlert(submissionId);
|
||||
}
|
||||
|
||||
// 计算当天总预约时长(异步函数)
|
||||
async function calculateDailyTotalDuration(submissionId) {
|
||||
const selectedDate = document.getElementById('selected_date_' + submissionId).value;
|
||||
const currentDuration = parseInt(document.getElementById('customDuration_' + submissionId).value);
|
||||
|
||||
try {
|
||||
// 通过AJAX请求获取当天已有的预约时长
|
||||
const response = await fetch(`get_daily_booking_duration.php?date=${selectedDate}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
console.error('获取当天预约时长失败:', data.error);
|
||||
return currentDuration; // 如果获取失败,只返回当前预约时长
|
||||
}
|
||||
|
||||
const existingDuration = data.total_duration || 0;
|
||||
return existingDuration + currentDuration;
|
||||
} catch (error) {
|
||||
console.error('获取当天预约时长出错:', error);
|
||||
return currentDuration; // 如果出错,只返回当前预约时长
|
||||
}
|
||||
}
|
||||
|
||||
// 显示时长提示
|
||||
async function showDurationAlert(submissionId) {
|
||||
const alertContainer = document.getElementById('durationAlert_' + submissionId);
|
||||
|
||||
// 清空现有提示
|
||||
alertContainer.innerHTML = '<div class="alert alert-info">加载中...</div>';
|
||||
|
||||
try {
|
||||
const totalDuration = await calculateDailyTotalDuration(submissionId);
|
||||
|
||||
// 清空现有提示
|
||||
alertContainer.innerHTML = '';
|
||||
|
||||
// 根据总时长显示不同的提示
|
||||
if (totalDuration >= 720) { // 大于等于12小时
|
||||
alertContainer.innerHTML = '<div class="alert alert-warning">⚠️ 当天总预约时长已达到12小时,你要当超人啊,别约了!</div>';
|
||||
} else if (totalDuration >= 360) { // 大于等于6小时
|
||||
alertContainer.innerHTML = '<div class="alert alert-info">💡 当天总预约时长已达到6小时,注意休息!</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('计算总预约时长出错:', error);
|
||||
alertContainer.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+205
-111
@@ -1,111 +1,207 @@
|
||||
<?php
|
||||
// 配置数据库连接参数
|
||||
$servername = "localhost";
|
||||
$username = "root";
|
||||
$password = "";
|
||||
$dbname = "car_wash_db";
|
||||
// 加载数据库配置
|
||||
require_once 'db_connect.php';
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'process_booking.php:3', 'message' => 'Database connection check', 'data' => ['has_pdo' => isset($pdo), 'using_db_connect' => true], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'D']);
|
||||
// 确保日志目录存在
|
||||
$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
|
||||
|
||||
// 检查表单提交
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// 获取表单数据
|
||||
$customer_name = $_POST['customer_name'] ?? '';
|
||||
$phone = $_POST['phone'] ?? '';
|
||||
$car_model = $_POST['car_model'] ?? '';
|
||||
$car_number = $_POST['car_number'] ?? '';
|
||||
$booking_date = $_POST['booking_date'] ?? '';
|
||||
$time_slot = $_POST['time_slot'] ?? '';
|
||||
$package_id = $_POST['package_id'] ?? '';
|
||||
$notes = $_POST['notes'] ?? '';
|
||||
$source = $_POST['source'] ?? ''; // 来源标识,用于确定返回页面
|
||||
|
||||
// 验证必填字段
|
||||
$errors = [];
|
||||
|
||||
if (empty($customer_name)) {
|
||||
$errors[] = '请输入客户姓名';
|
||||
}
|
||||
|
||||
if (empty($phone)) {
|
||||
$errors[] = '请输入手机号码';
|
||||
} elseif (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||||
$errors[] = '请输入正确的手机号码';
|
||||
}
|
||||
|
||||
if (empty($car_number)) {
|
||||
$errors[] = '请输入车牌号';
|
||||
}
|
||||
|
||||
if (empty($booking_date)) {
|
||||
$errors[] = '请选择预约日期';
|
||||
}
|
||||
|
||||
if (empty($time_slot)) {
|
||||
$errors[] = '请选择预约时间';
|
||||
}
|
||||
|
||||
if (empty($package_id)) {
|
||||
$errors[] = '请选择洗车套餐';
|
||||
}
|
||||
|
||||
// 验证日期是否为过去
|
||||
$current_date = date('Y-m-d');
|
||||
if ($booking_date < $current_date) {
|
||||
$errors[] = '不能选择过去的日期';
|
||||
}
|
||||
|
||||
// 如果有错误,返回错误信息
|
||||
if (!empty($errors)) {
|
||||
$error_message = implode('\n', $errors);
|
||||
echo "<script>
|
||||
alert('$error_message');
|
||||
window.history.back();
|
||||
</script>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// 创建数据库连接
|
||||
$conn = new mysqli($servername, $username, $password, $dbname);
|
||||
|
||||
// 检查连接
|
||||
if ($conn->connect_error) {
|
||||
echo "<script>
|
||||
alert('数据库连接失败,请稍后重试');
|
||||
window.history.back();
|
||||
</script>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// 检查是否已经存在相同的预约(同一天、同一时间段、同一车牌号)
|
||||
$check_sql = "SELECT * FROM bookings
|
||||
WHERE booking_date = ? AND time_slot = ? AND car_number = ? AND status != '已取消'";
|
||||
$check_stmt = $conn->prepare($check_sql);
|
||||
$check_stmt->bind_param("sss", $booking_date, $time_slot, $car_number);
|
||||
$check_stmt->execute();
|
||||
$check_result = $check_stmt->get_result();
|
||||
|
||||
if ($check_result->num_rows > 0) {
|
||||
echo "<script>
|
||||
alert('该时间段已存在相同车牌号的预约');
|
||||
window.history.back();
|
||||
</script>";
|
||||
$check_stmt->close();
|
||||
$conn->close();
|
||||
exit;
|
||||
}
|
||||
$check_stmt->close();
|
||||
|
||||
// 插入预约记录
|
||||
$status = '待服务'; // 默认为待服务状态
|
||||
$create_time = date('Y-m-d H:i:s');
|
||||
|
||||
$insert_sql = "INSERT INTO bookings
|
||||
(customer_name, phone, car_model, car_number, booking_date, time_slot, package_id, notes, status, create_time)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
$stmt = $conn->prepare($insert_sql);
|
||||
$stmt->bind_param("ssssssssss", $customer_name, $phone, $car_model, $car_number, $booking_date, $time_slot, $package_id, $notes, $status, $create_time);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
try {
|
||||
// 获取表单数据
|
||||
$customer_name = $_POST['customer_name'] ?? '';
|
||||
$phone = $_POST['phone'] ?? '';
|
||||
$car_model = $_POST['car_model'] ?? '';
|
||||
$car_number = $_POST['car_number'] ?? '';
|
||||
$booking_date = $_POST['booking_date'] ?? '';
|
||||
$time_slot = $_POST['time_slot'] ?? '';
|
||||
$package_id = $_POST['package_id'] ?? '';
|
||||
$notes = $_POST['notes'] ?? '';
|
||||
$source = $_POST['source'] ?? '其他'; // 来源标识,用于确定返回页面
|
||||
$duration = isset($_POST['duration']) ? (int)$_POST['duration'] : 60; // 默认60分钟
|
||||
$total_price = isset($_POST['total_price']) ? (float)$_POST['total_price'] : 0;
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($customer_name)) {
|
||||
throw new Exception('请输入客户姓名');
|
||||
}
|
||||
|
||||
if (empty($phone)) {
|
||||
throw new Exception('请输入手机号码');
|
||||
} elseif (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||||
throw new Exception('请输入正确的手机号码');
|
||||
}
|
||||
|
||||
if (empty($car_number)) {
|
||||
throw new Exception('请输入车牌号');
|
||||
}
|
||||
|
||||
if (empty($booking_date)) {
|
||||
throw new Exception('请选择预约日期');
|
||||
}
|
||||
|
||||
if (empty($time_slot)) {
|
||||
throw new Exception('请选择预约时间');
|
||||
}
|
||||
|
||||
if (empty($package_id)) {
|
||||
throw new Exception('请选择洗车套餐');
|
||||
}
|
||||
|
||||
// 验证日期是否为过去
|
||||
$current_date = date('Y-m-d');
|
||||
if ($booking_date < $current_date) {
|
||||
throw new Exception('不能选择过去的日期');
|
||||
}
|
||||
|
||||
// 解析时间段,转换为start_time和end_time
|
||||
// time_slot格式可能是 "09:00-10:00" 或 "09:00"
|
||||
$start_time_str = '';
|
||||
$end_time_str = '';
|
||||
|
||||
// 验证日期格式
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $booking_date)) {
|
||||
throw new Exception('预约日期格式不正确');
|
||||
}
|
||||
|
||||
if (strpos($time_slot, '-') !== false) {
|
||||
// 格式:09:00-10:00
|
||||
list($start_time_str, $end_time_str) = explode('-', $time_slot);
|
||||
$start_time_str = trim($start_time_str);
|
||||
$end_time_str = trim($end_time_str);
|
||||
|
||||
// 验证时间格式
|
||||
if (!preg_match('/^\d{2}:\d{2}$/', $start_time_str) || !preg_match('/^\d{2}:\d{2}$/', $end_time_str)) {
|
||||
throw new Exception('时间段格式不正确');
|
||||
}
|
||||
|
||||
$start_time = $booking_date . ' ' . $start_time_str . ':00';
|
||||
$end_time = $booking_date . ' ' . $end_time_str . ':00';
|
||||
|
||||
// 验证时间有效性
|
||||
$start_timestamp = strtotime($start_time);
|
||||
$end_timestamp = strtotime($end_time);
|
||||
if ($start_timestamp === false || $end_timestamp === false) {
|
||||
throw new Exception('时间段无效');
|
||||
}
|
||||
if ($end_timestamp <= $start_timestamp) {
|
||||
throw new Exception('结束时间必须晚于开始时间');
|
||||
}
|
||||
} else {
|
||||
// 格式:09:00,使用默认时长
|
||||
$time_slot = trim($time_slot);
|
||||
if (!preg_match('/^\d{2}:\d{2}$/', $time_slot)) {
|
||||
throw new Exception('时间格式不正确');
|
||||
}
|
||||
|
||||
$start_time = $booking_date . ' ' . $time_slot . ':00';
|
||||
$start_timestamp = strtotime($start_time);
|
||||
if ($start_timestamp === false) {
|
||||
throw new Exception('开始时间无效');
|
||||
}
|
||||
$end_time = date('Y-m-d H:i:s', $start_timestamp + $duration * 60);
|
||||
if ($end_time === false) {
|
||||
throw new Exception('计算结束时间失败');
|
||||
}
|
||||
}
|
||||
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'process_booking.php:70', 'message' => 'Time conversion', 'data' => ['booking_date' => $booking_date, 'time_slot' => $time_slot, 'start_time' => $start_time, 'end_time' => $end_time, 'duration' => $duration], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'E']);
|
||||
// 确保日志目录存在
|
||||
$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 * FROM packages WHERE id = ? AND is_active = 1");
|
||||
$stmt->execute([$package_id]);
|
||||
$package = $stmt->fetch();
|
||||
|
||||
if (!$package) {
|
||||
throw new Exception('选择的套餐无效');
|
||||
}
|
||||
|
||||
// 如果未提供价格,使用套餐价格
|
||||
if ($total_price <= 0) {
|
||||
$total_price = $package['price'];
|
||||
}
|
||||
|
||||
// 如果未提供时长,使用套餐默认时长
|
||||
if ($duration <= 0) {
|
||||
$duration = $package['base_duration'];
|
||||
// 重新计算结束时间
|
||||
$end_time = date('Y-m-d H:i:s', strtotime($start_time) + $duration * 60);
|
||||
}
|
||||
|
||||
// 检查时间冲突
|
||||
// 两个时间段重叠的条件:现有预约的开始时间 < 新预约的结束时间 AND 现有预约的结束时间 > 新预约的开始时间
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'process_booking.php:100', 'message' => 'Checking time conflict', 'data' => ['start_time' => $start_time, 'end_time' => $end_time], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'H']);
|
||||
// 确保日志目录存在
|
||||
$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
|
||||
WHERE status != '已取消'
|
||||
AND start_time < ?
|
||||
AND end_time > ?");
|
||||
$stmt->execute([$end_time, $start_time]);
|
||||
$conflict_count = $stmt->fetchColumn();
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'process_booking.php:110', 'message' => 'Time conflict check result', 'data' => ['conflict_count' => $conflict_count], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'H']);
|
||||
// 确保日志目录存在
|
||||
$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
|
||||
|
||||
if ($conflict_count > 0) {
|
||||
throw new Exception('该时间段已被预约,请选择其他时间');
|
||||
}
|
||||
|
||||
// 对于0元订单,自动标记为已付款
|
||||
$payment_status = ($total_price <= 0) ? '已付款' : '未付款';
|
||||
|
||||
// 插入预约记录(使用新的数据库结构)
|
||||
$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, payment_status, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
|
||||
$member_type = '普通客户'; // 默认普通客户,VIP客户应该通过index.php添加
|
||||
|
||||
$stmt->execute([
|
||||
$customer_name,
|
||||
$phone,
|
||||
$car_model,
|
||||
$car_number,
|
||||
$package_id,
|
||||
'', // custom_services
|
||||
$start_time,
|
||||
$end_time,
|
||||
$duration,
|
||||
$total_price,
|
||||
$notes,
|
||||
$member_type,
|
||||
$source,
|
||||
$payment_status,
|
||||
'待确认' // status
|
||||
]);
|
||||
|
||||
// 预约成功
|
||||
$success_message = '预约添加成功!';
|
||||
|
||||
@@ -116,19 +212,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
alert('$success_message');
|
||||
window.location.href = '$redirect_url';
|
||||
</script>";
|
||||
} else {
|
||||
// 预约失败
|
||||
|
||||
} catch (Exception $e) {
|
||||
$error_message = $e->getMessage();
|
||||
echo "<script>
|
||||
alert('预约添加失败,请稍后重试');
|
||||
alert('$error_message');
|
||||
window.history.back();
|
||||
</script>";
|
||||
}
|
||||
|
||||
// 关闭数据库连接
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
} else {
|
||||
// 不是POST请求,重定向到首页
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -61,7 +61,7 @@ body {
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: var(--el-spacing-large);
|
||||
}
|
||||
@@ -132,6 +132,47 @@ body {
|
||||
margin-bottom: var(--el-spacing-large);
|
||||
}
|
||||
|
||||
/* 增强型卡片样式(参考套餐管理页面) */
|
||||
.enhanced-card {
|
||||
background: var(--el-bg-color);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.enhanced-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--el-primary-color), var(--el-success-color));
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.enhanced-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
.enhanced-card:hover::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.enhanced-card.inactive {
|
||||
opacity: 0.7;
|
||||
background: var(--el-bg-color-page);
|
||||
}
|
||||
|
||||
.enhanced-card.inactive::before {
|
||||
background: linear-gradient(90deg, var(--el-info-color), var(--el-border-color));
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.form-group {
|
||||
margin-bottom: var(--el-spacing-large);
|
||||
@@ -280,6 +321,32 @@ body {
|
||||
font-size: var(--el-font-size-large);
|
||||
}
|
||||
|
||||
/* 切换状态按钮(参考套餐管理页面) */
|
||||
.btn-toggle-status {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: var(--el-border-radius-base);
|
||||
font-size: var(--el-font-size-small);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.btn-toggle-status.active {
|
||||
background: var(--el-success-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-toggle-status.inactive {
|
||||
background: var(--el-danger-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-toggle-status:hover {
|
||||
opacity: 0.9;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* 消息提示样式 */
|
||||
.message {
|
||||
padding: var(--el-spacing-base);
|
||||
@@ -345,6 +412,35 @@ body {
|
||||
color: var(--el-danger-color);
|
||||
}
|
||||
|
||||
/* 增强型状态标签(参考套餐管理页面) */
|
||||
.status-badge {
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: var(--el-font-size-extra-small);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-badge.active {
|
||||
background: rgba(103, 194, 58, 0.1);
|
||||
color: var(--el-success-color);
|
||||
}
|
||||
|
||||
.status-badge.inactive {
|
||||
background: rgba(245, 108, 108, 0.1);
|
||||
color: var(--el-danger-color);
|
||||
}
|
||||
|
||||
.status-badge.info {
|
||||
background: rgba(64, 158, 255, 0.1);
|
||||
color: var(--el-primary-color);
|
||||
}
|
||||
|
||||
.status-badge.warning {
|
||||
background: rgba(230, 162, 60, 0.1);
|
||||
color: var(--el-warning-color);
|
||||
}
|
||||
|
||||
/* 布局样式 */
|
||||
.booking-container {
|
||||
display: grid;
|
||||
@@ -369,6 +465,99 @@ body {
|
||||
border-bottom: 1px solid var(--el-border-lighter);
|
||||
}
|
||||
|
||||
/* 详情项布局(参考套餐管理页面) */
|
||||
.details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: var(--el-spacing-large);
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
background: var(--el-bg-color-light);
|
||||
border-radius: var(--el-border-radius-base);
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-size: var(--el-font-size-extra-small);
|
||||
color: var(--el-text-secondary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-size: var(--el-font-size-large);
|
||||
font-weight: bold;
|
||||
color: var(--el-text-primary);
|
||||
}
|
||||
|
||||
.detail-value.price {
|
||||
color: var(--el-danger-color);
|
||||
}
|
||||
|
||||
/* 标签样式(参考套餐管理页面) */
|
||||
.tags-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
background: rgba(64, 158, 255, 0.1);
|
||||
color: var(--el-primary-color);
|
||||
border-radius: 16px;
|
||||
font-size: var(--el-font-size-extra-small);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 提醒框样式(参考套餐管理页面) */
|
||||
.reminder-box {
|
||||
margin-top: var(--el-spacing-base);
|
||||
padding: var(--el-spacing-base);
|
||||
background: var(--el-bg-color-light);
|
||||
border-radius: var(--el-border-radius-base);
|
||||
border-left: 4px solid var(--el-primary-color);
|
||||
}
|
||||
|
||||
.reminder-editor {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: var(--el-border-radius-small);
|
||||
font-size: var(--el-font-size-base);
|
||||
line-height: 1.5;
|
||||
min-height: 80px;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.reminder-actions {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* 操作按钮组样式(参考套餐管理页面) */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: var(--el-spacing-base);
|
||||
padding-top: var(--el-spacing-base);
|
||||
border-top: 1px solid var(--el-border-lighter);
|
||||
}
|
||||
|
||||
.action-buttons button,
|
||||
.action-buttons form {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 日历样式 */
|
||||
.calendar {
|
||||
display: grid;
|
||||
@@ -752,6 +941,98 @@ body {
|
||||
font-size: var(--el-font-size-base);
|
||||
}
|
||||
|
||||
/* 移动端导航菜单(汉堡菜单) */
|
||||
.mobile-menu-toggle {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
padding: 8px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.mobile-menu-toggle span {
|
||||
display: block;
|
||||
width: 25px;
|
||||
height: 3px;
|
||||
background: var(--el-text-primary);
|
||||
border-radius: 2px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.mobile-menu-toggle.active span:nth-child(1) {
|
||||
transform: rotate(45deg) translate(8px, 8px);
|
||||
}
|
||||
|
||||
.mobile-menu-toggle.active span:nth-child(2) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.mobile-menu-toggle.active span:nth-child(3) {
|
||||
transform: rotate(-45deg) translate(7px, -7px);
|
||||
}
|
||||
|
||||
.mobile-nav-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.mobile-nav-overlay.active {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 280px;
|
||||
max-width: 85vw;
|
||||
height: 100vh;
|
||||
background: white;
|
||||
box-shadow: 2px 0 10px rgba(0,0,0,0.1);
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
transition: left 0.3s ease;
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.mobile-nav.active {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.mobile-nav .nav-link {
|
||||
display: block;
|
||||
padding: 16px 20px;
|
||||
border: none;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-radius: 0;
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
min-height: 52px;
|
||||
}
|
||||
|
||||
.mobile-nav .nav-link:first-child {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.mobile-nav .nav-link.active {
|
||||
background: rgba(64, 158, 255, 0.1);
|
||||
border-left: 4px solid var(--el-primary-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
|
||||
/* 平板设备 */
|
||||
@@ -760,10 +1041,36 @@ body {
|
||||
--el-spacing-base: 12px;
|
||||
--el-spacing-large: 16px;
|
||||
--el-spacing-extra-large: 24px;
|
||||
--el-font-size-base: 15px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: var(--el-spacing-base);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
padding: 12px 16px;
|
||||
justify-content: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 18px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.mobile-menu-toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.booking-container {
|
||||
@@ -771,16 +1078,6 @@ body {
|
||||
gap: var(--el-spacing-base);
|
||||
}
|
||||
|
||||
.header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
gap: var(--el-spacing-base);
|
||||
@@ -788,6 +1085,7 @@ body {
|
||||
|
||||
.form-row .form-group {
|
||||
flex: none;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.calendar {
|
||||
@@ -798,6 +1096,13 @@ body {
|
||||
.quick-duration {
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.duration-btn {
|
||||
min-width: 60px;
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.time-grid {
|
||||
@@ -805,25 +1110,84 @@ body {
|
||||
gap: var(--el-spacing-small);
|
||||
}
|
||||
|
||||
.time-slot {
|
||||
padding: 12px 8px;
|
||||
font-size: 13px;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.package-meta {
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* 卡片优化 */
|
||||
.card {
|
||||
padding: var(--el-spacing-base);
|
||||
margin-bottom: var(--el-spacing-base);
|
||||
}
|
||||
|
||||
/* 表格优化 */
|
||||
.table {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 8px 6px;
|
||||
}
|
||||
|
||||
/* VIP搜索优化 */
|
||||
.vip-search-results {
|
||||
max-height: 300px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.vip-search-item {
|
||||
padding: 14px 16px;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.stat-card .stat-value {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机设备 */
|
||||
@media (max-width: 480px) {
|
||||
:root {
|
||||
--el-font-size-base: 14px;
|
||||
--el-font-size-small: 13px;
|
||||
--el-font-size-large: 16px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: var(--el-spacing-small);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: var(--el-spacing-base);
|
||||
padding: 10px 12px;
|
||||
border-radius: 0;
|
||||
margin-bottom: var(--el-spacing-base);
|
||||
margin-bottom: 12px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: var(--el-font-size-large);
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.section {
|
||||
@@ -831,52 +1195,277 @@ body {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid var(--el-border-lighter);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.calendar {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
padding: var(--el-spacing-small);
|
||||
padding: 10px 6px;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.day-number {
|
||||
font-size: var(--el-font-size-base);
|
||||
}
|
||||
|
||||
.nav {
|
||||
flex-direction: column;
|
||||
gap: var(--el-spacing-small);
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
flex-direction: column;
|
||||
gap: var(--el-spacing-small);
|
||||
gap: 10px;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
background: white;
|
||||
padding: 12px;
|
||||
margin: 0 -12px -12px;
|
||||
box-shadow: 0 -2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
min-height: 48px;
|
||||
font-size: 16px;
|
||||
padding: 14px 20px;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
min-height: 44px;
|
||||
padding: 10px 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.package-meta {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
padding: var(--el-spacing-small);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
margin: 0;
|
||||
max-height: calc(100vh - 20px);
|
||||
max-height: 100vh;
|
||||
border-radius: 0;
|
||||
padding: 20px 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 表单优化 */
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
font-size: 16px; /* 防止iOS自动缩放 */
|
||||
padding: 12px 14px;
|
||||
min-height: 48px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
/* 输入组优化 */
|
||||
.input-group {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.input-group .form-control {
|
||||
border-radius: var(--el-border-radius-base);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
border-radius: var(--el-border-radius-base);
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/* 卡片优化 */
|
||||
.card {
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 表格优化 - 移动端横向滚动 */
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.table {
|
||||
min-width: 600px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
padding: 10px 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* VIP卡片优化 */
|
||||
.vip-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vip-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.vip-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vip-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 套餐卡片优化 */
|
||||
.packages-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.package-card {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.package-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.package-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.package-details {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.package-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.package-actions button,
|
||||
.package-actions form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 搜索框优化 */
|
||||
.search-box input {
|
||||
font-size: 16px;
|
||||
padding: 12px 16px 12px 40px;
|
||||
}
|
||||
|
||||
/* 筛选区域优化 */
|
||||
.filter-section {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.filter-group select {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
/* 统计卡片优化 */
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.stat-card .stat-value {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.stat-card .stat-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 时间选择优化 */
|
||||
.time-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.time-slot {
|
||||
padding: 14px 8px;
|
||||
font-size: 14px;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
/* 快速时长按钮优化 */
|
||||
.quick-duration {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.duration-btn {
|
||||
flex: 1;
|
||||
min-width: calc(50% - 4px);
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
/* 消息提示优化 */
|
||||
.message {
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 空状态优化 */
|
||||
.empty-state {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.empty-submessage {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 套餐管理页面移动端优化 */
|
||||
@@ -961,8 +1550,12 @@ body {
|
||||
|
||||
/* 小屏幕手机 */
|
||||
@media (max-width: 360px) {
|
||||
.header h1 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.calendar {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.time-grid {
|
||||
@@ -974,27 +1567,99 @@ body {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.duration-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
font-size: 16px; /* 防止iOS缩放 */
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
width: 260px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 触摸设备优化 */
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
/* 增加触摸目标大小 */
|
||||
.btn {
|
||||
min-height: 44px;
|
||||
padding: 12px 20px;
|
||||
-webkit-tap-highlight-color: rgba(64, 158, 255, 0.2);
|
||||
tap-highlight-color: rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: scale(0.98);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
min-height: 44px;
|
||||
-webkit-tap-highlight-color: rgba(64, 158, 255, 0.1);
|
||||
tap-highlight-color: rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
min-height: 44px;
|
||||
padding: 12px 16px;
|
||||
-webkit-tap-highlight-color: rgba(64, 158, 255, 0.2);
|
||||
tap-highlight-color: rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.mobile-nav .nav-link {
|
||||
min-height: 52px;
|
||||
}
|
||||
|
||||
.time-slot {
|
||||
min-height: 44px;
|
||||
-webkit-tap-highlight-color: rgba(64, 158, 255, 0.2);
|
||||
tap-highlight-color: rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.time-slot:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
min-height: 60px;
|
||||
-webkit-tap-highlight-color: rgba(64, 158, 255, 0.2);
|
||||
tap-highlight-color: rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 禁用hover效果 */
|
||||
.btn:hover:not(:disabled) {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* 优化滚动 */
|
||||
* {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* 优化文本选择 */
|
||||
body {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-1
@@ -62,7 +62,26 @@ try {
|
||||
}
|
||||
|
||||
// 计算新的持续时间(分钟)
|
||||
$duration = $start_dt->diff($end_dt)->i + ($start_dt->diff($end_dt)->h * 60);
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'update_booking.php:65', 'message' => 'Calculating duration', 'data' => ['start_time' => $new_start_time, 'end_time' => $new_end_time], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'C']);
|
||||
// 确保日志目录存在
|
||||
$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
|
||||
$interval = $start_dt->diff($end_dt);
|
||||
$duration = ($interval->days * 24 * 60) + ($interval->h * 60) + $interval->i;
|
||||
// #region agent log
|
||||
$log_data = json_encode(['location' => 'update_booking.php:67', 'message' => 'Duration calculated', 'data' => ['duration' => $duration, 'days' => $interval->days, 'hours' => $interval->h, 'minutes' => $interval->i], 'timestamp' => time() * 1000, 'sessionId' => 'debug-session', 'runId' => 'run1', 'hypothesisId' => 'C']);
|
||||
// 确保日志目录存在
|
||||
$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("UPDATE bookings SET start_time = ?, end_time = ?, duration = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?");
|
||||
|
||||
@@ -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) {
|
||||
@@ -143,10 +193,16 @@ try {
|
||||
<meta name="keywords" content="VIP管理,客户管理,会员管理">
|
||||
<title>张老师撸车(私家车库)工作室</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="mobile-nav.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<button class="mobile-menu-toggle" onclick="toggleMobileMenu()" aria-label="菜单">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
<h1>🚗 张老师撸车工作室 - VIP管理</h1>
|
||||
<nav class="nav">
|
||||
<a href="index.php" class="nav-link">预约洗车</a>
|
||||
@@ -157,6 +213,17 @@ try {
|
||||
<a href="announcement.php" class="nav-link">今日待办</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- 移动端导航菜单 -->
|
||||
<div class="mobile-nav-overlay" onclick="closeMobileMenu()"></div>
|
||||
<nav class="mobile-nav">
|
||||
<a href="index.php" class="nav-link">预约洗车</a>
|
||||
<a href="bookings.php" class="nav-link">预约管理</a>
|
||||
<a href="pending_bookings.php" class="nav-link">待处理预约</a>
|
||||
<a href="packages.php" class="nav-link">套餐管理</a>
|
||||
<a href="vip.php" class="nav-link active">VIP管理</a>
|
||||
<a href="announcement.php" class="nav-link">今日待办</a>
|
||||
</nav>
|
||||
|
||||
<?php if (isset($success_message)): ?>
|
||||
<div class="success-message"><?php echo $success_message; ?></div>
|
||||
@@ -174,7 +241,7 @@ try {
|
||||
|
||||
<div class="vip-management">
|
||||
<!-- VIP搜索表单 -->
|
||||
<div class="card search-container">
|
||||
<div class="card search-container enhanced-card">
|
||||
<h2>🔍 VIP客户搜索</h2>
|
||||
<form method="GET" class="form search-form">
|
||||
<div class="form-row">
|
||||
@@ -195,7 +262,7 @@ try {
|
||||
</div>
|
||||
|
||||
<!-- VIP录入表单 -->
|
||||
<div class="card">
|
||||
<div class="card enhanced-card">
|
||||
<h2>➕ 录入新VIP客户</h2>
|
||||
<form method="POST" class="form">
|
||||
<input type="hidden" name="action" value="add_vip">
|
||||
@@ -248,8 +315,66 @@ try {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 从预约转换为VIP -->
|
||||
<div class="card enhanced-card">
|
||||
<h2>📋 从预约记录转换为VIP客户</h2>
|
||||
<p style="color: #666; margin-bottom: 15px;">选择预约记录,快速将普通客户转换为VIP客户</p>
|
||||
<?php
|
||||
// 获取可以转换为VIP的普通客户预约记录(最近30天,且不是VIP客户)
|
||||
try {
|
||||
$stmt = $pdo->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 = [];
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if (empty($convertible_bookings)): ?>
|
||||
<div class="empty-message">暂无可转换的普通客户</div>
|
||||
<?php else: ?>
|
||||
<div style="max-height: 400px; overflow-y: auto;">
|
||||
<?php foreach ($convertible_bookings as $booking): ?>
|
||||
<div style="padding: 12px; border: 1px solid #e0e0e0; border-radius: 6px; margin-bottom: 10px; background: #f9f9f9;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="flex: 1;">
|
||||
<strong><?php echo htmlspecialchars($booking['customer_name']); ?></strong><br>
|
||||
<span style="color: #666; font-size: 0.9em;">
|
||||
手机号:<?php echo htmlspecialchars($booking['phone']); ?> |
|
||||
车牌号:<?php echo htmlspecialchars($booking['car_number']); ?><br>
|
||||
<?php if ($booking['car_model']): ?>
|
||||
车型:<?php echo htmlspecialchars($booking['car_model']); ?> |
|
||||
<?php endif; ?>
|
||||
预约次数:<?php echo $booking['booking_count']; ?>次
|
||||
</span>
|
||||
</div>
|
||||
<form method="POST" style="margin-left: 15px;">
|
||||
<input type="hidden" name="action" value="convert_from_booking">
|
||||
<input type="hidden" name="booking_id" value="<?php echo $booking['id']; ?>">
|
||||
<button type="submit" class="btn btn-sm" style="background-color: #ffd700; color: #333; font-weight: bold;"
|
||||
onclick="return confirm('确定要将 <?php echo htmlspecialchars($booking['customer_name']); ?> 转换为VIP客户吗?')">
|
||||
👑 转为VIP
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- VIP客户列表 -->
|
||||
<div class="card">
|
||||
<div class="card enhanced-card">
|
||||
<h2>👑 VIP客户列表 (共 <?php echo count($vip_customers); ?> 位)</h2>
|
||||
|
||||
<?php if (empty($vip_customers)): ?>
|
||||
@@ -257,7 +382,7 @@ try {
|
||||
<?php else: ?>
|
||||
<div class="vip-grid">
|
||||
<?php foreach ($vip_customers as $vip): ?>
|
||||
<div class="vip-card">
|
||||
<div class="vip-card enhanced-card">
|
||||
<div class="vip-header">
|
||||
<h3><?php echo htmlspecialchars($vip['customer_name']); ?></h3>
|
||||
<div class="vip-status">👑 VIP</div>
|
||||
@@ -414,13 +539,19 @@ try {
|
||||
<option value="">请选择套餐</option>
|
||||
<?php
|
||||
// 查询所有可用的洗车套餐
|
||||
$package_result = $conn->query("SELECT id, package_name, price FROM packages ORDER BY price ASC");
|
||||
if ($package_result && $package_result->num_rows > 0) {
|
||||
while ($package = $package_result->fetch_assoc()) {
|
||||
// #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']);
|
||||
file_put_contents('.cursor/debug.log', $log_data . "\n", FILE_APPEND);
|
||||
// #endregion
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT id, package_name, price FROM packages WHERE is_active = 1 ORDER BY price ASC");
|
||||
$packages = $stmt->fetchAll();
|
||||
foreach ($packages as $package) {
|
||||
echo "<option value='{$package['id']}'>{$package['package_name']} - ¥{$package['price']}</option>";
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "<option value=''>加载套餐失败</option>";
|
||||
}
|
||||
$package_result->free_result();
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user