c64651d6c7
refactor(预约统计): 修改查询逻辑只计算有效预约
542 lines
21 KiB
PHP
542 lines
21 KiB
PHP
<?php
|
|
// 公告页面 - 显示今日待处理的洗车预约
|
|
|
|
// 包含数据库连接
|
|
require_once 'db_connect.php';
|
|
|
|
// 设置页面标题
|
|
$page_title = '待办列表 - 洗车预约系统';
|
|
|
|
// 设置默认时区为北京时间
|
|
date_default_timezone_set('Asia/Shanghai');
|
|
|
|
// 获取选择的日期,默认为当前日期
|
|
$selected_date = isset($_GET['selected_date']) ? $_GET['selected_date'] : date('Y-m-d');
|
|
|
|
// 验证日期格式
|
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $selected_date)) {
|
|
$selected_date = date('Y-m-d');
|
|
}
|
|
|
|
// 获取选择日期的开始和结束时间
|
|
$day_start = $selected_date . ' 00:00:00';
|
|
$day_end = $selected_date . ' 23:59:59';
|
|
|
|
// 查询选择日期的预约(不包括已取消的)
|
|
try {
|
|
$stmt = $pdo->prepare("SELECT b.*, p.package_name, p.services
|
|
FROM bookings b
|
|
LEFT JOIN packages p ON b.package_id = p.id
|
|
WHERE b.start_time BETWEEN :day_start AND :day_end
|
|
AND b.status != '已取消'
|
|
ORDER BY b.start_time ASC");
|
|
$stmt->execute(['day_start' => $day_start, 'day_end' => $day_end]);
|
|
$today_bookings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// 计算今日待处理和已完成的数量
|
|
$pending_count = 0;
|
|
$completed_count = 0;
|
|
foreach ($today_bookings as $booking) {
|
|
if ($booking['status'] == '已完成') {
|
|
$completed_count++;
|
|
} else {
|
|
$pending_count++;
|
|
}
|
|
}
|
|
} catch (PDOException $e) {
|
|
$error_message = '获取今日预约失败: ' . $e->getMessage();
|
|
$today_bookings = [];
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<meta name="format-detection" content="telephone=no">
|
|
<meta name="description" content="今日洗车待办列表,直观展示今日需要处理的洗车预约">
|
|
<meta name="keywords" content="公告,今日待办,预约列表,洗车管理">
|
|
<title>张老师撸车(私家车库)工作室</title>
|
|
<link rel="stylesheet" href="style.css">
|
|
<script src="mobile-nav.js" defer></script>
|
|
<style>
|
|
/* 公告页面特有样式 */
|
|
.announcement-container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
|
|
.page-header {
|
|
background: linear-gradient(135deg, #4ECDC4 0%, #44A08D 100%);
|
|
color: white;
|
|
padding: 30px;
|
|
border-radius: 10px;
|
|
margin-bottom: 30px;
|
|
text-align: center;
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.page-header h1 {
|
|
margin: 0;
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.page-header .subtitle {
|
|
margin-top: 10px;
|
|
font-size: 1.2rem;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.stats-card {
|
|
background: white;
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
margin-bottom: 30px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
|
|
display: flex;
|
|
justify-content: space-around;
|
|
flex-wrap: wrap;
|
|
gap: 20px;
|
|
}
|
|
|
|
.stat-item {
|
|
text-align: center;
|
|
flex: 1;
|
|
min-width: 120px;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
background: #f8f9fa;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.stat-item:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.stat-pending .stat-number {
|
|
color: #ff9800;
|
|
}
|
|
|
|
.stat-completed .stat-number {
|
|
color: #4caf50;
|
|
}
|
|
|
|
.stat-total .stat-number {
|
|
color: #2196f3;
|
|
}
|
|
|
|
.booking-list {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
|
gap: 20px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.booking-card {
|
|
background: white;
|
|
border-radius: 10px;
|
|
padding: 25px;
|
|
box-shadow: 0 2px 15px rgba(0,0,0,0.08);
|
|
transition: all 0.3s ease;
|
|
border-left: 5px solid #2196f3;
|
|
}
|
|
|
|
.booking-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 8px 25px rgba(0,0,0,0.12);
|
|
}
|
|
|
|
.booking-card.urgent {
|
|
border-left-color: #ff9800;
|
|
}
|
|
|
|
.booking-card.completed {
|
|
border-left-color: #4caf50;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.booking-time {
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin-bottom: 15px;
|
|
padding-bottom: 10px;
|
|
border-bottom: 2px solid #f0f0f0;
|
|
}
|
|
|
|
.booking-details {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.detail-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
padding: 5px 0;
|
|
}
|
|
|
|
.detail-label {
|
|
font-weight: 600;
|
|
color: #666;
|
|
min-width: 80px;
|
|
}
|
|
|
|
.detail-value {
|
|
color: #333;
|
|
text-align: right;
|
|
flex: 1;
|
|
}
|
|
|
|
.booking-status {
|
|
display: inline-block;
|
|
padding: 5px 15px;
|
|
border-radius: 20px;
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.status-pending {
|
|
background: #fff3cd;
|
|
color: #856404;
|
|
}
|
|
|
|
.status-confirmed {
|
|
background: #d1ecf1;
|
|
color: #0c5460;
|
|
}
|
|
|
|
.status-processing {
|
|
background: #cce7ff;
|
|
color: #004085;
|
|
}
|
|
|
|
.status-completed {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
}
|
|
|
|
.services-list {
|
|
background: #f8f9fa;
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.services-list h4 {
|
|
margin: 0 0 8px 0;
|
|
font-size: 0.9rem;
|
|
color: #666;
|
|
}
|
|
|
|
.services-list ul {
|
|
margin: 0;
|
|
padding-left: 20px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* 客服备注样式 */
|
|
.service-notes {
|
|
margin-top: 12px;
|
|
padding: 12px;
|
|
background-color: #f8f9fa;
|
|
border-left: 4px solid #007bff;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.service-notes h4 {
|
|
margin-top: 0;
|
|
margin-bottom: 8px;
|
|
font-size: 14px;
|
|
color: #007bff;
|
|
}
|
|
|
|
.service-notes p {
|
|
margin: 0;
|
|
font-size: 13px;
|
|
color: #495057;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.no-bookings {
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
background: white;
|
|
border-radius: 10px;
|
|
color: #666;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.booking-list {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.detail-row {
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
}
|
|
|
|
.detail-value {
|
|
text-align: left;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<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>
|
|
<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>
|
|
</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">
|
|
<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;">
|
|
<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;">
|
|
<button type="submit" class="btn btn-primary" style="padding: 10px 25px; font-size: 1rem;">查看预约</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- 统计信息卡片 -->
|
|
<div class="stats-card">
|
|
<div class="stat-item stat-pending">
|
|
<div class="stat-label">待处理</div>
|
|
<div class="stat-number"><?php echo $pending_count; ?></div>
|
|
</div>
|
|
<div class="stat-item stat-completed">
|
|
<div class="stat-label">已完成</div>
|
|
<div class="stat-number"><?php echo $completed_count; ?></div>
|
|
</div>
|
|
<div class="stat-item stat-total">
|
|
<div class="stat-label">总计</div>
|
|
<div class="stat-number"><?php echo count($today_bookings); ?></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 错误提示 -->
|
|
<?php if (isset($error_message)): ?>
|
|
<div class="error-message"><?php echo $error_message; ?></div>
|
|
<?php endif; ?>
|
|
|
|
<!-- 预约列表 -->
|
|
<?php if (empty($today_bookings)): ?>
|
|
<div class="no-bookings">
|
|
<div style="font-size: 3rem; margin-bottom: 20px;">🎉</div>
|
|
<div>今日暂无预约</div>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="booking-list">
|
|
<?php foreach ($today_bookings as $booking): ?>
|
|
<?php
|
|
// 确定卡片类型
|
|
$card_class = '';
|
|
if ($booking['status'] == '已完成') {
|
|
$card_class = 'completed';
|
|
} else {
|
|
// 检查是否紧急(15分钟内开始的预约)
|
|
$now = new DateTime();
|
|
$start_time = new DateTime($booking['start_time']);
|
|
$interval = $now->diff($start_time);
|
|
$minutes_diff = $interval->days * 24 * 60 + $interval->h * 60 + $interval->i;
|
|
|
|
if ($minutes_diff >= 0 && $minutes_diff <= 15 && $booking['status'] != '进行中') {
|
|
$card_class = 'urgent';
|
|
}
|
|
}
|
|
|
|
// 格式化服务项目列表
|
|
$services = [];
|
|
if (!empty($booking['services'])) {
|
|
$services = explode(',', $booking['services']);
|
|
}
|
|
?>
|
|
<div class="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']); ?>">
|
|
<?php echo $booking['status']; ?>
|
|
</span>
|
|
</div>
|
|
|
|
<div class="booking-details">
|
|
<div class="detail-row">
|
|
<span class="detail-label">客户姓名</span>
|
|
<span class="detail-value"><?php echo htmlspecialchars($booking['customer_name']); ?></span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">联系电话</span>
|
|
<span class="detail-value"><?php echo htmlspecialchars($booking['phone']); ?></span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">车型</span>
|
|
<span class="detail-value"><?php echo htmlspecialchars($booking['car_model']); ?></span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">车牌号</span>
|
|
<span class="detail-value"><?php echo htmlspecialchars($booking['car_number']); ?></span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">套餐</span>
|
|
<span class="detail-value"><?php echo htmlspecialchars($booking['package_name'] ?: '自定义套餐'); ?></span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">时长</span>
|
|
<span class="detail-value"><?php echo $booking['duration']; ?> 分钟</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">价格</span>
|
|
<span class="detail-value">¥<?php echo $booking['total_price']; ?></span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">会员类型</span>
|
|
<span class="detail-value"><?php echo $booking['member_type']; ?></span>
|
|
</div>
|
|
|
|
<!-- 服务项目列表 -->
|
|
<?php if (!empty($services)): ?>
|
|
<div class="services-list">
|
|
<h4>服务项目:</h4>
|
|
<ul>
|
|
<?php foreach ($services as $service): ?>
|
|
<li><?php echo trim(htmlspecialchars($service)); ?></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- 客服备注 -->
|
|
<?php if (!empty($booking['custom_services'])): ?>
|
|
<div class="service-notes">
|
|
<h4>客服备注:</h4>
|
|
<p><?php echo htmlspecialchars($booking['custom_services']); ?></p>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- 备注信息 -->
|
|
<?php if (!empty($booking['notes'])): ?>
|
|
<div class="detail-row">
|
|
<span class="detail-label">客户备注</span>
|
|
<span class="detail-value"><?php echo htmlspecialchars($booking['notes']); ?></span>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 移动端优化脚本
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// 为按钮添加触摸反馈
|
|
var buttons = document.querySelectorAll('.btn');
|
|
for (var i = 0; i < buttons.length; i++) {
|
|
var btn = buttons[i];
|
|
btn.addEventListener('touchstart', function() {
|
|
this.style.transform = 'translateY(1px)';
|
|
});
|
|
btn.addEventListener('touchend', function() {
|
|
this.style.transform = 'translateY(-2px)';
|
|
});
|
|
}
|
|
|
|
// 检测设备类型
|
|
var isMobile = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
if (isMobile) {
|
|
document.body.classList.add('mobile-device');
|
|
}
|
|
|
|
// 自动刷新功能(默认开启,每5分钟刷新一次)
|
|
setInterval(function() {
|
|
window.location.reload();
|
|
}, 5 * 60 * 1000);
|
|
|
|
// 将倒计时添加到页面标题旁边
|
|
var pageHeader = document.querySelector('.page-header');
|
|
var refreshInfo = document.createElement('div');
|
|
refreshInfo.style.cssText = 'font-size:0.9rem;color:#666;margin-top:5px;';
|
|
|
|
if (pageHeader) {
|
|
pageHeader.appendChild(refreshInfo);
|
|
} else {
|
|
// 如果找不到header,就添加到body
|
|
refreshInfo.style.cssText = 'font-size:0.9rem;color:#333;background:#fff;padding:8px 12px;border-radius:4px;box-shadow:0 2px 6px rgba(0,0,0,0.2);border:1px solid #ddd;margin:20px;';
|
|
document.body.appendChild(refreshInfo);
|
|
}
|
|
|
|
// 显示北京时间及距离下次刷新倒计时
|
|
function updateBeijingTime() {
|
|
var now = new Date();
|
|
var beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000); // 转为北京时间
|
|
var hours = String(beijingTime.getUTCHours()).padStart(2, '0');
|
|
var minutes = String(beijingTime.getUTCMinutes()).padStart(2, '0');
|
|
var seconds = String(beijingTime.getUTCSeconds()).padStart(2, '0');
|
|
|
|
// 计算距离下次刷新的剩余时间
|
|
var refreshInterval = 5 * 60 * 1000; // 5分钟
|
|
var elapsed = now.getTime() % refreshInterval;
|
|
var remaining = refreshInterval - elapsed;
|
|
var remMinutes = Math.floor(remaining / 60000);
|
|
var remSeconds = Math.floor((remaining % 60000) / 1000);
|
|
|
|
// 更新显示
|
|
if (refreshInfo) {
|
|
refreshInfo.textContent = '北京时间 ' + hours + ':' + minutes + ':' + seconds + ' | 下次刷新 ' + remMinutes + '分' + remSeconds + '秒';
|
|
}
|
|
}
|
|
|
|
// 每秒更新一次
|
|
setInterval(updateBeijingTime, 1000);
|
|
updateBeijingTime(); // 初始化显示
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|