Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a714f0a526 | |||
| b963c2b513 | |||
| a672e1d7bc | |||
| 1f0cf4acaa | |||
| 26dd84bea2 | |||
| a372464299 |
@@ -0,0 +1,170 @@
|
||||
# 张老师撸车工作室 - 洗车预约管理系统
|
||||
|
||||
## 版本功能汇总
|
||||
|
||||
### 1. 核心预约管理系统
|
||||
|
||||
**功能特点:**
|
||||
- **普通客户/VIP客户区分预约**:支持选择普通客户或VIP客户进行预约
|
||||
- **套餐选择**:可选择预设的洗车套餐
|
||||
- **时间冲突检测**:自动检查并避免预约时间冲突
|
||||
- **预约状态管理**:支持待确认、已确认、进行中、已完成、已取消等状态
|
||||
- **预约记录查询与筛选**:可按状态筛选和搜索预约记录
|
||||
- **预约时间更新**:支持修改已预约的时间
|
||||
- **付款状态管理**:可标记预约的付款状态(已付款/未付款)
|
||||
|
||||
**技术实现:**
|
||||
- 基于PHP和MySQL开发
|
||||
- 使用PDO进行数据库操作
|
||||
- 时间冲突算法确保资源合理分配
|
||||
- 表单验证和错误处理机制
|
||||
|
||||
### 2. 套餐管理系统
|
||||
|
||||
**功能特点:**
|
||||
- **套餐CRUD操作**:支持添加、编辑、删除和查看套餐
|
||||
- **套餐内容管理**:可定义套餐名称、描述、基础时长、价格和包含的服务项目
|
||||
- **套餐状态控制**:可启用/禁用套餐
|
||||
- **服务项目管理**:支持添加多个服务项目到套餐中
|
||||
- **可视化套餐列表**:卡片式展示,包含套餐详细信息
|
||||
|
||||
**界面优化:**
|
||||
- 现代化卡片式设计
|
||||
- 服务项目标签展示
|
||||
- 悬停效果和微动画
|
||||
- 响应式设计支持移动端
|
||||
|
||||
### 3. VIP客户管理系统
|
||||
|
||||
**功能特点:**
|
||||
- **VIP客户信息录入**:支持添加VIP客户基本信息
|
||||
- **VIP客户查询与管理**:可查看、编辑和删除VIP客户
|
||||
- **VIP客户识别**:基于手机号和车牌号组合唯一识别
|
||||
- **VIP客户预约**:支持快速选择VIP客户进行预约
|
||||
- **数据验证**:防止重复录入VIP客户
|
||||
|
||||
**技术实现:**
|
||||
- 复合唯一索引确保数据完整性
|
||||
- 表单验证和错误处理
|
||||
- VIP客户信息自动填充功能
|
||||
|
||||
### 4. 待预约处理系统(WPS表单集成)
|
||||
|
||||
**功能特点:**
|
||||
- **WPS表单数据同步**:自动获取WPS表单提交的预约请求
|
||||
- **预约转换功能**:可将WPS表单提交转换为正式预约
|
||||
- **套餐选择与价格计算**:支持为WPS表单提交选择套餐和计算价格
|
||||
- **时间选择与冲突检测**:转换时自动检测时间冲突
|
||||
- **状态管理**:标记已处理的表单提交
|
||||
|
||||
**技术实现:**
|
||||
- 数据库表设计支持WPS表单数据存储
|
||||
- 预约转换逻辑确保数据完整性
|
||||
- 错误处理和日志记录
|
||||
|
||||
### 5. 公告与待办列表系统
|
||||
|
||||
**功能特点:**
|
||||
- **今日预约概览**:展示当天所有预约记录
|
||||
- **状态统计**:显示待处理和已完成的预约数量
|
||||
- **日期选择**:支持查看不同日期的预约记录
|
||||
- **预约详情展示**:包含客户信息、套餐、时间和服务项目
|
||||
- **待办事项管理**:直观展示需要处理的预约
|
||||
|
||||
**界面优化:**
|
||||
- 响应式设计支持移动端
|
||||
- 统计卡片展示关键指标
|
||||
- 时间线式预约列表
|
||||
- 简洁明了的状态标识
|
||||
|
||||
### 6. 系统架构与技术栈
|
||||
|
||||
**技术栈:**
|
||||
- **后端语言**:PHP 7+
|
||||
- **数据库**:MySQL
|
||||
- **前端技术**:HTML5, CSS3, JavaScript, jQuery
|
||||
- **数据库连接**:PDO (PHP Data Objects)
|
||||
- **版本控制**:Git
|
||||
|
||||
**系统特点:**
|
||||
- 模块化设计,功能分离清晰
|
||||
- 数据库设计合理,关系明确
|
||||
- 安全性考虑(输入验证、SQL注入防护)
|
||||
- 响应式设计,支持桌面和移动设备
|
||||
- 用户友好的界面和交互体验
|
||||
|
||||
### 7. 界面与用户体验优化
|
||||
|
||||
**优化内容:**
|
||||
- **现代化UI设计**:卡片式布局、渐变效果、阴影处理
|
||||
- **响应式设计**:适配不同屏幕尺寸
|
||||
- **动画效果**:页面加载淡入、元素过渡动画
|
||||
- **表单体验**:统一的表单样式、输入验证、占位符提示
|
||||
- **交互反馈**:按钮悬停效果、操作成功/失败提示
|
||||
|
||||
**移动端适配:**
|
||||
- 响应式布局调整
|
||||
- 触摸友好的元素大小
|
||||
- 优化的移动端表单
|
||||
- 适配不同移动设备
|
||||
|
||||
## 版本信息
|
||||
|
||||
**当前版本:** v1.0.0
|
||||
|
||||
**发布日期:** 2024年
|
||||
|
||||
**主要改进:**
|
||||
1. 完整的预约管理流程
|
||||
2. 套餐管理功能
|
||||
3. VIP客户管理系统
|
||||
4. WPS表单集成
|
||||
5. 响应式界面设计
|
||||
6. 用户体验优化
|
||||
|
||||
## 系统要求
|
||||
|
||||
- PHP 7.0+
|
||||
- MySQL 5.6+
|
||||
- Web服务器(Apache/Nginx)
|
||||
- 支持PDO的PHP环境
|
||||
- 浏览器支持:Chrome、Firefox、Safari、Edge (最新版本)
|
||||
|
||||
## 安装与配置
|
||||
|
||||
1. 导入数据库脚本 `merged_db.sql`
|
||||
2. 配置数据库连接信息 `config.php`
|
||||
3. 将项目部署到Web服务器
|
||||
4. 访问系统首页开始使用
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. **添加套餐**:在套餐管理页面添加洗车套餐
|
||||
2. **管理VIP客户**:录入和管理VIP客户信息
|
||||
3. **创建预约**:在首页选择客户类型和套餐,填写预约信息
|
||||
4. **处理待预约**:在待预约页面处理WPS表单提交
|
||||
5. **查看公告**:在公告页面查看今日待办和统计信息
|
||||
|
||||
## 功能流程图
|
||||
|
||||
1. **预约流程**:选择客户类型 → 填写/选择客户信息 → 选择套餐 → 选择时间 → 确认预约
|
||||
2. **套餐管理**:添加套餐 → 编辑套餐 → 启用/禁用套餐 → 查看套餐列表
|
||||
3. **VIP管理**:录入VIP客户 → 查询/编辑VIP信息 → 使用VIP快速预约
|
||||
4. **WPS表单处理**:接收表单提交 → 转换为预约 → 标记为已处理
|
||||
|
||||
## 数据统计与分析
|
||||
|
||||
系统支持通过数据库查询获取以下统计数据:
|
||||
- 每日/每周/每月预约量
|
||||
- 不同套餐的销售情况
|
||||
- VIP客户数量和活跃度
|
||||
- 客户来源分析
|
||||
|
||||
## 未来计划
|
||||
|
||||
1. 增加支付功能集成
|
||||
2. 实现短信通知系统
|
||||
3. 添加数据可视化图表
|
||||
4. 完善会员积分系统
|
||||
5. 支持多门店管理
|
||||
6. 移动端App开发
|
||||
+52
-22
@@ -245,6 +245,29 @@ try {
|
||||
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;
|
||||
@@ -278,7 +301,7 @@ try {
|
||||
<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="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>
|
||||
@@ -353,9 +376,6 @@ try {
|
||||
if (!empty($booking['services'])) {
|
||||
$services = explode(',', $booking['services']);
|
||||
}
|
||||
if (!empty($booking['custom_services'])) {
|
||||
$services[] = $booking['custom_services'];
|
||||
}
|
||||
?>
|
||||
<div class="booking-card <?php echo $card_class; ?>">
|
||||
<div class="booking-time">
|
||||
@@ -410,11 +430,19 @@ try {
|
||||
</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-label">客户备注</span>
|
||||
<span class="detail-value"><?php echo htmlspecialchars($booking['notes']); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@@ -430,18 +458,19 @@ try {
|
||||
// 移动端优化脚本
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 为按钮添加触摸反馈
|
||||
const buttons = document.querySelectorAll('.btn');
|
||||
buttons.forEach(btn => {
|
||||
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)';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 检测设备类型
|
||||
const isMobile = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
var isMobile = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
if (isMobile) {
|
||||
document.body.classList.add('mobile-device');
|
||||
}
|
||||
@@ -452,8 +481,8 @@ try {
|
||||
}, 5 * 60 * 1000);
|
||||
|
||||
// 将倒计时添加到页面标题旁边
|
||||
const pageHeader = document.querySelector('.page-header');
|
||||
const refreshInfo = document.createElement('div');
|
||||
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) {
|
||||
@@ -466,28 +495,29 @@ try {
|
||||
|
||||
// 显示北京时间及距离下次刷新倒计时
|
||||
function updateBeijingTime() {
|
||||
const now = new Date();
|
||||
const beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000); // 转为北京时间
|
||||
const hours = String(beijingTime.getUTCHours()).padStart(2, '0');
|
||||
const minutes = String(beijingTime.getUTCMinutes()).padStart(2, '0');
|
||||
const seconds = String(beijingTime.getUTCSeconds()).padStart(2, '0');
|
||||
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');
|
||||
|
||||
// 计算距离下次刷新的剩余时间
|
||||
const refreshInterval = 5 * 60 * 1000; // 5分钟
|
||||
const elapsed = now.getTime() % refreshInterval;
|
||||
const remaining = refreshInterval - elapsed;
|
||||
const remMinutes = Math.floor(remaining / 60000);
|
||||
const remSeconds = Math.floor((remaining % 60000) / 1000);
|
||||
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}秒`;
|
||||
refreshInfo.textContent = '北京时间 ' + hours + ':' + minutes + ':' + seconds + ' | 下次刷新 ' + remMinutes + '分' + remSeconds + '秒';
|
||||
}
|
||||
}
|
||||
|
||||
// 每秒更新一次
|
||||
setInterval(updateBeijingTime, 1000);
|
||||
updateBeijingTime(); // 初始化显示
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/**
|
||||
* 预约信息模板文件
|
||||
* 此文件用于定义预约成功后发送给客户的信息模板
|
||||
* 用户可以根据自己的需求修改此模板
|
||||
*/
|
||||
|
||||
// 预约成功信息模板配置
|
||||
$booking_templates = [
|
||||
// 基本模板
|
||||
'basic' => [
|
||||
'title' => '预约成功!',
|
||||
'message' => "预约成功!\n\n" .
|
||||
"客户:{customer_name}\n" .
|
||||
"手机号:{phone}\n" .
|
||||
"车牌号:{car_number}\n" .
|
||||
"车型:{car_model}\n" .
|
||||
"\n" .
|
||||
"预约时间:{start_time}\n" .
|
||||
"服务项目:{package_name}\n" .
|
||||
"服务时长:{duration}分钟\n" .
|
||||
"总价:{total_price}元\n" .
|
||||
"\n" .
|
||||
"感谢您的预约!"
|
||||
],
|
||||
// 详细模板
|
||||
'detailed' => [
|
||||
'title' => '【张老师撸车工作室】预约确认',
|
||||
'message' => "尊敬的 {customer_name} 先生/女士:\n\n" .
|
||||
"恭喜您,您的洗车预约已成功确认!\n\n" .
|
||||
"🔹 客户信息:\n" .
|
||||
" - 姓名:{customer_name}\n" .
|
||||
" - 手机号:{phone}\n" .
|
||||
" - 车牌号:{car_number}\n" .
|
||||
" - 车型:{car_model}\n" .
|
||||
" - 会员类型:{member_type}\n" .
|
||||
"\n" .
|
||||
"🔹 预约详情:\n" .
|
||||
" - 预约时间:{date} {time_range}\n" .
|
||||
" - 服务项目:{package_name}\n" .
|
||||
" - 自定义服务:{custom_services}\n" .
|
||||
" - 服务时长:{duration}分钟\n" .
|
||||
" - 总价:¥{total_price}\n" .
|
||||
" - 支付状态:{payment_status}\n" .
|
||||
"\n" .
|
||||
"🔹 备注信息:\n" .
|
||||
" {notes}\n" .
|
||||
"\n" .
|
||||
"🔹 温馨提示:\n" .
|
||||
" 1. 请提前10分钟到达洗车地点\n" .
|
||||
" 2. 如需改期或取消,请至少提前2小时联系我们\n" .
|
||||
" 3. 联系电话:138-0013-8000\n" .
|
||||
"\n" .
|
||||
"感谢您选择张老师撸车工作室!\n" .
|
||||
"祝您用车愉快!🚗✨"
|
||||
],
|
||||
// 简洁模板
|
||||
'simple' => [
|
||||
'title' => '预约确认',
|
||||
'message' => "{customer_name},您的洗车预约已确认:\n" .
|
||||
"时间:{start_time}\n" .
|
||||
"项目:{package_name}\n" .
|
||||
"总价:{total_price}元\n" .
|
||||
"如需调整请联系我们。"
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* 生成预约信息
|
||||
* @param array $booking_data 预约数据数组
|
||||
* @param string $template_name 使用的模板名称
|
||||
* @return string 生成的预约信息
|
||||
*/
|
||||
function generateBookingMessage($booking_data, $template_name = 'basic') {
|
||||
global $booking_templates;
|
||||
|
||||
// 检查模板是否存在
|
||||
if (!isset($booking_templates[$template_name])) {
|
||||
$template_name = 'basic'; // 默认使用基本模板
|
||||
}
|
||||
|
||||
$template = $booking_templates[$template_name]['message'];
|
||||
|
||||
// 定义替换字段
|
||||
$replacements = [
|
||||
'{customer_name}' => $booking_data['customer_name'] ?? '未知客户',
|
||||
'{phone}' => $booking_data['phone'] ?? '未提供',
|
||||
'{car_model}' => $booking_data['car_model'] ?? '未提供',
|
||||
'{car_number}' => $booking_data['car_number'] ?? '未提供',
|
||||
'{member_type}' => $booking_data['member_type'] ?? '普通客户',
|
||||
'{package_name}' => $booking_data['package_name'] ?? '未选择套餐',
|
||||
'{custom_services}' => $booking_data['custom_services'] ?? '无',
|
||||
'{start_time}' => $booking_data['start_time'] ?? '未设置',
|
||||
'{end_time}' => $booking_data['end_time'] ?? '未设置',
|
||||
'{date}' => $booking_data['date'] ?? (isset($booking_data['start_time']) ? date('Y年m月d日', strtotime($booking_data['start_time'])) : '未设置'),
|
||||
'{time_range}' => $booking_data['time_range'] ?? (isset($booking_data['start_time'], $booking_data['end_time']) ? date('H:i', strtotime($booking_data['start_time'])) . ' - ' . date('H:i', strtotime($booking_data['end_time'])) : '未设置'),
|
||||
'{duration}' => $booking_data['duration'] ?? '未设置',
|
||||
'{total_price}' => $booking_data['total_price'] ?? '0.00',
|
||||
'{notes}' => $booking_data['notes'] ?? '无',
|
||||
'{status}' => $booking_data['status'] ?? '未确认',
|
||||
'{payment_status}' => $booking_data['payment_status'] ?? '未支付',
|
||||
'{source}' => $booking_data['source'] ?? '未知',
|
||||
'{created_at}' => $booking_data['created_at'] ?? date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
// 替换模板中的占位符
|
||||
$message = str_replace(array_keys($replacements), array_values($replacements), $template);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定模板的标题
|
||||
* @param string $template_name 模板名称
|
||||
* @return string 模板标题
|
||||
*/
|
||||
function getTemplateTitle($template_name = 'basic') {
|
||||
global $booking_templates;
|
||||
|
||||
if (isset($booking_templates[$template_name])) {
|
||||
return $booking_templates[$template_name]['title'];
|
||||
}
|
||||
|
||||
return $booking_templates['basic']['title'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用的模板名称
|
||||
* @return array 模板名称数组
|
||||
*/
|
||||
function getAvailableTemplates() {
|
||||
global $booking_templates;
|
||||
return array_keys($booking_templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例用法:
|
||||
* 1. 包含此文件:require_once 'booking_template.php';
|
||||
* 2. 准备预约数据:
|
||||
* $booking_data = [
|
||||
* 'customer_name' => '张三',
|
||||
* 'phone' => '138-0013-8000',
|
||||
* 'car_model' => '特斯拉 Model 3',
|
||||
* 'car_number' => '京A12345',
|
||||
* 'member_type' => 'VIP会员',
|
||||
* 'package_name' => '精致洗车套餐',
|
||||
* 'start_time' => '2023-10-01 10:00:00',
|
||||
* 'end_time' => '2023-10-01 11:00:00',
|
||||
* 'duration' => 60,
|
||||
* 'total_price' => '128.00',
|
||||
* 'notes' => '车顶上有行李架,请小心清洗',
|
||||
* 'status' => '已确认',
|
||||
* 'payment_status' => '已付款',
|
||||
* 'source' => '微信'
|
||||
* ];
|
||||
* 3. 生成预约信息:
|
||||
* $message = generateBookingMessage($booking_data, 'detailed');
|
||||
* 4. 输出或使用生成的信息:
|
||||
* echo $message;
|
||||
*/
|
||||
?>
|
||||
+140
-7
@@ -25,6 +25,13 @@ if (isset($_POST['action']) && isset($_POST['booking_id'])) {
|
||||
$stmt = $pdo->prepare("UPDATE bookings SET start_time = ?, end_time = ? WHERE id = ?");
|
||||
$stmt->execute([$new_start_time, $new_end_time, $booking_id]);
|
||||
$success_message = '预约时间更新成功!';
|
||||
} elseif ($action == 'update_notes' && isset($_POST['notes_content'])) {
|
||||
// 更新客服备注
|
||||
$notes_content = $_POST['notes_content'];
|
||||
$stmt = $conn->prepare("UPDATE bookings SET custom_services = ? WHERE id = ?");
|
||||
$stmt->execute([$notes_content, $booking_id]);
|
||||
echo 'success';
|
||||
exit();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$error_message = '更新失败:' . $e->getMessage();
|
||||
@@ -88,7 +95,7 @@ try {
|
||||
<nav class="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="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>
|
||||
@@ -214,17 +221,31 @@ try {
|
||||
|
||||
<?php if ($booking['notes']): ?>
|
||||
<div class="package-description">
|
||||
<span class="detail-label">备注:</span>
|
||||
<span class="detail-label">客户备注:</span>
|
||||
<span><?php echo htmlspecialchars($booking['notes']); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($booking['custom_services']): ?>
|
||||
<div class="package-description">
|
||||
<span class="detail-label">客服备注:</span>
|
||||
<span><?php echo htmlspecialchars($booking['custom_services']); ?></span>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="openEditNotesModal(<?php echo $booking['id']; ?>, '<?php echo addslashes(htmlspecialchars($booking['custom_services'])); ?>')" style="margin-left: 10px;">修改</button>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="package-description">
|
||||
<span class="detail-label">客服备注:</span>
|
||||
<span style="color: #999;">无</span>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="openEditNotesModal(<?php echo $booking['id']; ?>, '')" style="margin-left: 10px;">添加</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="package-meta">
|
||||
<span>预约时间:<?php echo $booking['created_at']; ?></span>
|
||||
</div>
|
||||
|
||||
<?php if ($booking['status'] !== '已完成' && $booking['status'] !== '已取消'): ?>
|
||||
<div class="package-actions">
|
||||
<div class="package-actions">
|
||||
<?php if ($booking['status'] !== '已完成' && $booking['status'] !== '已取消'): ?>
|
||||
<!-- 状态更新按钮 - 使用AJAX -->
|
||||
<div>
|
||||
<button type="button" class="btn btn-sm btn-success" onclick="updateStatus(<?php echo $booking['id']; ?>, '已确认')">确认</button>
|
||||
@@ -240,8 +261,19 @@ try {
|
||||
<br>
|
||||
<!-- 修改预约时间按钮 -->
|
||||
<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>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<br>
|
||||
<?php endif; ?>
|
||||
<!-- 复制预约信息按钮 -->
|
||||
<?php
|
||||
// 计算服务时长(分钟)
|
||||
$start = strtotime($booking['start_time']);
|
||||
$end = strtotime($booking['end_time']);
|
||||
$duration = round(($end - $start) / 60);
|
||||
// 获取车友备注(如果不存在则为空字符串)
|
||||
$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>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
@@ -251,6 +283,27 @@ try {
|
||||
<a href="index.php" class="btn">返回预约页面</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑客服备注模态框 -->
|
||||
<div id="editNotesModal" style="display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4);">
|
||||
<div style="background-color: #fefefe; margin: 15% auto; padding: 20px; border-radius: 8px; width: 90%; max-width: 500px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);">
|
||||
<div class="modal-header">
|
||||
<h3>编辑客服备注</h3>
|
||||
<span class="close-modal" onclick="closeEditNotesModal()">×</span>
|
||||
</div>
|
||||
<form id="editNotesForm">
|
||||
<input type="hidden" id="editNotesBookingId" name="booking_id">
|
||||
<div class="form-group">
|
||||
<label for="editNotesContent">客服备注内容:</label>
|
||||
<textarea id="editNotesContent" name="notes_content" rows="5" placeholder="请输入客服备注内容..." style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;"></textarea>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn btn-sm" onclick="closeEditNotesModal()">取消</button>
|
||||
<button type="submit" class="btn btn-sm btn-primary">保存</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 修改预约时间模态框 -->
|
||||
<div id="editTimeModal" class="modal">
|
||||
@@ -462,15 +515,56 @@ try {
|
||||
document.getElementById('editTimeModal').style.display = 'none';
|
||||
}
|
||||
|
||||
// 编辑客服备注相关函数
|
||||
function openEditNotesModal(bookingId, notesContent) {
|
||||
document.getElementById('editNotesBookingId').value = bookingId;
|
||||
document.getElementById('editNotesContent').value = notesContent;
|
||||
document.getElementById('editNotesModal').style.display = 'block';
|
||||
}
|
||||
|
||||
function closeEditNotesModal() {
|
||||
document.getElementById('editNotesModal').style.display = 'none';
|
||||
}
|
||||
|
||||
// 点击模态框外部关闭模态框
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('editTimeModal');
|
||||
const notesModal = document.getElementById('editNotesModal');
|
||||
if (event.target == modal) {
|
||||
closeEditModal();
|
||||
} else if (event.target == notesModal) {
|
||||
closeEditNotesModal();
|
||||
}
|
||||
}
|
||||
|
||||
// 表单提交处理 - 使用AJAX
|
||||
// 编辑客服备注表单提交处理 - 使用AJAX
|
||||
document.getElementById('editNotesForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const bookingId = document.getElementById('editNotesBookingId').value;
|
||||
const notesContent = document.getElementById('editNotesContent').value;
|
||||
|
||||
// 创建AJAX请求
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', 'bookings.php', true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
// 刷新页面以显示更新后的备注
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('保存失败,请重试');
|
||||
}
|
||||
};
|
||||
xhr.onerror = function() {
|
||||
alert('网络错误,请重试');
|
||||
};
|
||||
|
||||
// 发送请求
|
||||
xhr.send('action=update_notes&booking_id=' + bookingId + '¬es_content=' + encodeURIComponent(notesContent));
|
||||
});
|
||||
|
||||
// 修改预约时间表单提交处理 - 使用AJAX
|
||||
document.getElementById('editTimeForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -601,6 +695,45 @@ 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
|
||||
|
||||
\n工作室地点:
|
||||
山西省太原市晋源区义井街道西中环充电站内
|
||||
也可高德/百度/腾讯地图软件内搜索‘张老师撸车’
|
||||
|
||||
感谢您选择张老师撸车工作室!
|
||||
祝您用车愉快!
|
||||
|
||||
`;
|
||||
|
||||
// 复制到剪贴板
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = message;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
|
||||
// 显示复制成功提示
|
||||
alert('预约信息已复制到剪贴板!');
|
||||
}
|
||||
|
||||
// 移动端优化脚本
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 为按钮添加触摸反馈
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
/**
|
||||
* 预约信息模板使用示例
|
||||
* 此文件展示如何使用 booking_template.php 来生成预约信息
|
||||
*/
|
||||
|
||||
// 包含模板文件
|
||||
require_once 'booking_template.php';
|
||||
|
||||
// 示例1:使用基本模板
|
||||
function example_basic_template() {
|
||||
echo "=== 示例1:使用基本模板 ===\n\n";
|
||||
|
||||
// 准备预约数据
|
||||
$booking_data = [
|
||||
'customer_name' => '张三',
|
||||
'phone' => '138-0013-8000',
|
||||
'car_number' => '京A12345',
|
||||
'car_model' => '特斯拉 Model 3',
|
||||
'start_time' => '2023-10-01 10:00:00',
|
||||
'package_name' => '精致洗车套餐',
|
||||
'duration' => 60,
|
||||
'total_price' => '128.00'
|
||||
];
|
||||
|
||||
// 生成预约信息
|
||||
$message = generateBookingMessage($booking_data, 'basic');
|
||||
|
||||
// 输出结果
|
||||
echo "模板标题:" . getTemplateTitle('basic') . "\n";
|
||||
echo "模板内容:\n" . $message . "\n\n";
|
||||
}
|
||||
|
||||
// 示例2:使用详细模板
|
||||
function example_detailed_template() {
|
||||
echo "=== 示例2:使用详细模板 ===\n\n";
|
||||
|
||||
// 准备完整的预约数据
|
||||
$booking_data = [
|
||||
'customer_name' => '李四',
|
||||
'phone' => '139-0013-9000',
|
||||
'car_model' => '宝马 5系',
|
||||
'car_number' => '沪B67890',
|
||||
'member_type' => 'VIP会员',
|
||||
'package_name' => '深度清洁套餐',
|
||||
'custom_services' => '内饰消毒 + 轮胎养护',
|
||||
'start_time' => '2023-10-02 14:30:00',
|
||||
'end_time' => '2023-10-02 16:00:00',
|
||||
'duration' => 90,
|
||||
'total_price' => '298.00',
|
||||
'notes' => '车辆右前门有轻微划痕,清洗时请注意',
|
||||
'status' => '已确认',
|
||||
'payment_status' => '已付款',
|
||||
'source' => '抖音'
|
||||
];
|
||||
|
||||
// 生成预约信息
|
||||
$message = generateBookingMessage($booking_data, 'detailed');
|
||||
|
||||
// 输出结果
|
||||
echo "模板标题:" . getTemplateTitle('detailed') . "\n";
|
||||
echo "模板内容:\n" . $message . "\n\n";
|
||||
}
|
||||
|
||||
// 示例3:使用简洁模板
|
||||
function example_simple_template() {
|
||||
echo "=== 示例3:使用简洁模板 ===\n\n";
|
||||
|
||||
// 准备简约的预约数据
|
||||
$booking_data = [
|
||||
'customer_name' => '王五',
|
||||
'phone' => '137-0013-7000',
|
||||
'car_number' => '粤C54321',
|
||||
'car_model' => '本田 CR-V',
|
||||
'start_time' => '2023-10-03 09:30:00',
|
||||
'package_name' => '快速洗车',
|
||||
'duration' => 30,
|
||||
'total_price' => '38.00'
|
||||
];
|
||||
|
||||
// 生成预约信息
|
||||
$message = generateBookingMessage($booking_data, 'simple');
|
||||
|
||||
// 输出结果
|
||||
echo "模板标题:" . getTemplateTitle('simple') . "\n";
|
||||
echo "模板内容:\n" . $message . "\n\n";
|
||||
}
|
||||
|
||||
// 示例4:自定义模板数据
|
||||
function example_custom_data() {
|
||||
echo "=== 示例4:自定义模板数据 ===\n\n";
|
||||
|
||||
// 从数据库获取的示例预约数据
|
||||
$db_booking_data = [
|
||||
'id' => 123,
|
||||
'customer_name' => '赵六',
|
||||
'phone' => '136-0013-6000',
|
||||
'car_model' => '丰田 凯美瑞',
|
||||
'car_number' => '苏D98765',
|
||||
'member_type' => '普通会员',
|
||||
'package_id' => 2,
|
||||
'package_name' => '打蜡套餐',
|
||||
'custom_services' => '',
|
||||
'start_time' => '2023-10-04 16:00:00',
|
||||
'end_time' => '2023-10-04 17:30:00',
|
||||
'duration' => 90,
|
||||
'total_price' => '198.00',
|
||||
'notes' => '',
|
||||
'status' => '已确认',
|
||||
'payment_status' => '未付款',
|
||||
'source' => '朋友介绍',
|
||||
'created_at' => '2023-09-25 14:20:00',
|
||||
'updated_at' => '2023-09-25 14:20:00'
|
||||
];
|
||||
|
||||
// 生成预约信息
|
||||
$message = generateBookingMessage($db_booking_data, 'detailed');
|
||||
|
||||
// 输出结果
|
||||
echo "模板标题:" . getTemplateTitle('detailed') . "\n";
|
||||
echo "模板内容:\n" . $message . "\n\n";
|
||||
}
|
||||
|
||||
// 示例5:将模板集成到现有系统中
|
||||
function example_integration() {
|
||||
echo "=== 示例5:将模板集成到现有系统中 ===\n\n";
|
||||
|
||||
// 模拟从数据库获取预约数据
|
||||
function getBookingFromDatabase($booking_id) {
|
||||
// 这里应该是实际的数据库查询
|
||||
return [
|
||||
'id' => $booking_id,
|
||||
'customer_name' => '孙七',
|
||||
'phone' => '135-0013-5000',
|
||||
'car_model' => '大众 帕萨特',
|
||||
'car_number' => '浙E34567',
|
||||
'member_type' => '普通客户',
|
||||
'package_name' => '普通洗车',
|
||||
'start_time' => '2023-10-05 11:00:00',
|
||||
'end_time' => '2023-10-05 11:30:00',
|
||||
'duration' => 30,
|
||||
'total_price' => '58.00',
|
||||
'notes' => '',
|
||||
'status' => '已确认',
|
||||
'payment_status' => '已付款',
|
||||
'source' => '其他'
|
||||
];
|
||||
}
|
||||
|
||||
// 模拟发送预约确认信息
|
||||
function sendBookingConfirmation($booking_id) {
|
||||
// 获取预约数据
|
||||
$booking = getBookingFromDatabase($booking_id);
|
||||
|
||||
// 生成预约信息
|
||||
$template_name = $booking['member_type'] == 'VIP会员' ? 'detailed' : 'basic';
|
||||
$message = generateBookingMessage($booking, $template_name);
|
||||
$title = getTemplateTitle($template_name);
|
||||
|
||||
// 这里应该是实际的发送逻辑(短信、微信等)
|
||||
echo "向 {$booking['phone']} 发送信息:\n";
|
||||
echo "标题:{$title}\n";
|
||||
echo "内容:\n{$message}\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
$booking_id = 456;
|
||||
sendBookingConfirmation($booking_id);
|
||||
}
|
||||
|
||||
// 运行所有示例
|
||||
example_basic_template();
|
||||
example_detailed_template();
|
||||
example_simple_template();
|
||||
example_custom_data();
|
||||
example_integration();
|
||||
|
||||
// 显示可用模板
|
||||
echo "\n=== 可用模板列表 ===\n";
|
||||
echo "当前系统中可用的预约信息模板:\n";
|
||||
$templates = getAvailableTemplates();
|
||||
foreach ($templates as $template) {
|
||||
echo "- {$template} (标题:" . getTemplateTitle($template) . ")\n";
|
||||
}
|
||||
|
||||
// 如何自定义模板的说明
|
||||
echo "\n=== 如何自定义模板 ===\n";
|
||||
echo "1. 打开 booking_template.php 文件\n";
|
||||
echo "2. 在 \$booking_templates 数组中添加新的模板或修改现有模板\n";
|
||||
echo "3. 每个模板包含 title 和 message 两个字段\n";
|
||||
echo "4. 使用 {字段名} 作为占位符,系统会自动替换为实际数据\n";
|
||||
echo "5. 可使用的占位符包括:\n";
|
||||
echo " - {customer_name}:客户姓名\n";
|
||||
echo " - {phone}:手机号\n";
|
||||
echo " - {car_model}:车型\n";
|
||||
echo " - {car_number}:车牌号\n";
|
||||
echo " - {member_type}:会员类型\n";
|
||||
echo " - {package_name}:服务项目\n";
|
||||
echo " - {custom_services}:自定义服务\n";
|
||||
echo " - {start_time}:预约开始时间\n";
|
||||
echo " - {end_time}:预约结束时间\n";
|
||||
echo " - {date}:预约日期\n";
|
||||
echo " - {time_range}:时间范围\n";
|
||||
echo " - {duration}:服务时长\n";
|
||||
echo " - {total_price}:总价\n";
|
||||
echo " - {notes}:备注\n";
|
||||
echo " - {status}:预约状态\n";
|
||||
echo " - {payment_status}:支付状态\n";
|
||||
echo " - {source}:来源渠道\n";
|
||||
echo " - {created_at}:创建时间\n";
|
||||
?>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
// 引入数据库连接文件
|
||||
require 'db_connect.php';
|
||||
|
||||
// 获取请求参数
|
||||
$date = $_GET['date'];
|
||||
|
||||
// 验证日期格式
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
|
||||
echo json_encode(['error' => 'Invalid date format']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 查询指定日期的总预约时长
|
||||
$sql = "SELECT SUM(duration) as total_duration FROM bookings WHERE DATE(start_time) = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$date]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
// 获取总时长,如果没有预约则返回0
|
||||
$total_duration = $row['total_duration'] ? $row['total_duration'] : 0;
|
||||
|
||||
// 返回结果
|
||||
echo json_encode(['total_duration' => $total_duration]);
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(['error' => 'Database query failed: ' . $e->getMessage()]);
|
||||
}
|
||||
@@ -96,13 +96,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
// 插入预约记录
|
||||
// 对于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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
start_time, end_time, duration, total_price, notes, member_type, source, payment_status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
|
||||
$stmt->execute([$customer_name, $phone, $car_model, $car_number, $package_id, $custom_services,
|
||||
$start_time, $end_time, $duration, $total_price, $notes, $member_type, $source]);
|
||||
$start_time, $end_time, $duration, $total_price, $notes, $member_type, $source, $payment_status]);
|
||||
|
||||
$success_message = "预约提交成功!";
|
||||
|
||||
@@ -315,7 +318,7 @@ $packages_json = json_encode(array_map(function($package) {
|
||||
<nav class="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="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>
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ $packages = $stmt->fetchAll();
|
||||
<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="pending_bookings.php" class="nav-link">待处理预约</a>
|
||||
<a href="packages.php" class="nav-link active">套餐管理</a>
|
||||
<a href="vip.php" class="nav-link">VIP管理</a>
|
||||
<a href="announcement.php" class="nav-link">今日待办</a>
|
||||
|
||||
+228
-62
@@ -42,14 +42,17 @@ if (isset($_POST['action']) && $_POST['action'] == 'convert_to_booking' && isset
|
||||
$end_time = date('Y-m-d H:i:s', strtotime($start_time) + $duration * 60);
|
||||
|
||||
// 将数据插入到正式预约表
|
||||
// 对于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, status,
|
||||
member_type, source
|
||||
member_type, source, payment_status
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?, ?,
|
||||
?, ?
|
||||
?, ?, ?
|
||||
)");
|
||||
|
||||
$stmt->execute([
|
||||
@@ -66,7 +69,8 @@ if (isset($_POST['action']) && $_POST['action'] == 'convert_to_booking' && isset
|
||||
$submission['remarks'],
|
||||
'已确认', // 默认设置为已确认
|
||||
'普通客户', // 默认普通客户,可根据需要调整
|
||||
'其他' // 默认来源,可根据需要调整
|
||||
'其他', // 默认来源,可根据需要调整
|
||||
$payment_status // 根据总价自动设置付款状态
|
||||
]);
|
||||
|
||||
$booking_id = $pdo->lastInsertId();
|
||||
@@ -120,8 +124,8 @@ $booking_schedule = [];
|
||||
$bookings_by_date = [];
|
||||
|
||||
try {
|
||||
// 获取所有未来的预约
|
||||
$stmt = $pdo->prepare("SELECT * FROM bookings WHERE end_time > NOW() ORDER BY start_time ASC");
|
||||
// 获取所有未来的预约(排除已取消的订单)
|
||||
$stmt = $pdo->prepare("SELECT * FROM bookings WHERE end_time > NOW() AND status != '已取消' ORDER BY start_time ASC");
|
||||
$stmt->execute();
|
||||
$all_bookings = $stmt->fetchAll();
|
||||
|
||||
@@ -201,6 +205,12 @@ try {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pending-card.selected-card {
|
||||
background: #e3f2fd;
|
||||
border: 2px solid #2196f3;
|
||||
box-shadow: 0 4px 15px rgba(33, 150, 243, 0.3);
|
||||
}
|
||||
|
||||
.pending-header {
|
||||
background: #f5f5f5;
|
||||
padding: 15px 20px;
|
||||
@@ -288,26 +298,6 @@ try {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.copy-message {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 5px 10px;
|
||||
border-radius: 12px;
|
||||
@@ -320,6 +310,50 @@ try {
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
/* 预约记录选择器样式 */
|
||||
.submission-selector {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.submission-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.submission-item {
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.submission-item:hover {
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.submission-item.selected-item {
|
||||
background: #e3f2fd;
|
||||
border-color: #2196f3;
|
||||
box-shadow: 0 4px 15px rgba(33, 150, 243, 0.3);
|
||||
}
|
||||
|
||||
.submission-info h4 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.submission-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 日历和时间选择样式 */
|
||||
.calendar-container {
|
||||
margin-bottom: 20px;
|
||||
@@ -400,6 +434,32 @@ try {
|
||||
background: #c3e6cb;
|
||||
}
|
||||
|
||||
/* 预约时长提示样式 */
|
||||
.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 {
|
||||
background: #f8f9fa;
|
||||
@@ -538,17 +598,7 @@ try {
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 预约成功信息显示 -->
|
||||
<?php if (isset($_SESSION['booking_success_msg'])): ?>
|
||||
<div class="booking-success">
|
||||
<h3>预约成功信息已生成</h3>
|
||||
<div class="copy-message" id="successMessage">
|
||||
<?php echo htmlspecialchars($_SESSION['booking_success_msg']); ?>
|
||||
</div>
|
||||
<button class="copy-btn" onclick="copyMessage()">复制信息</button>
|
||||
</div>
|
||||
<?php unset($_SESSION['booking_success_msg']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<div class="card">
|
||||
<h2>待处理预约 (共 <?php echo count($pending_submissions); ?> 条)</h2>
|
||||
@@ -556,8 +606,31 @@ try {
|
||||
<?php if (empty($pending_submissions)): ?>
|
||||
<div class="empty-message">暂无待处理的预约请求</div>
|
||||
<?php else: ?>
|
||||
<!-- 无论记录数量多少,都显示选择列表 -->
|
||||
<?php if (true): ?>
|
||||
<div class="submission-selector">
|
||||
<h3>选择要处理的预约记录</h3>
|
||||
<div class="submission-list">
|
||||
<?php foreach ($pending_submissions as $submission): ?>
|
||||
<div class="submission-item" onclick="selectSubmission(<?php echo $submission['id']; ?>)">
|
||||
<div class="submission-info">
|
||||
<h4><?php echo htmlspecialchars($submission['name']); ?></h4>
|
||||
<div class="submission-meta">
|
||||
<span>手机号:<?php echo htmlspecialchars($submission['mobile']); ?></span>
|
||||
<span>车牌号:<?php echo htmlspecialchars($submission['license_plate']); ?></span>
|
||||
<span>提交时间:<?php echo htmlspecialchars($submission['create_time']); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="status-badge status-pending">待处理</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- 显示选中的预约记录的处理表单 -->
|
||||
<?php foreach ($pending_submissions as $submission): ?>
|
||||
<div class="pending-card">
|
||||
<div class="pending-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>
|
||||
@@ -680,6 +753,11 @@ try {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预约时长提示区域 -->
|
||||
<div class="duration-alert" id="durationAlert_<?php echo $submission['id']; ?>">
|
||||
<!-- 提示信息将通过JavaScript动态生成 -->
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="customDuration_<?php echo $submission['id']; ?>">服务时长(分钟)</label>
|
||||
@@ -715,18 +793,36 @@ try {
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 复制预约成功信息到剪贴板
|
||||
function copyMessage() {
|
||||
const message = document.getElementById('successMessage');
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = message.textContent;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
|
||||
|
||||
// 选择预约记录函数(提前定义,确保在调用前可用)
|
||||
function selectSubmission(submissionId) {
|
||||
// 隐藏所有待处理预约卡片并移除高亮
|
||||
document.querySelectorAll('.pending-card').forEach(card => {
|
||||
card.style.display = 'none';
|
||||
card.classList.remove('selected-card');
|
||||
});
|
||||
|
||||
// 显示复制成功提示
|
||||
alert('预约信息已复制到剪贴板!');
|
||||
// 移除选择列表中所有项的高亮
|
||||
document.querySelectorAll('.submission-item').forEach(item => {
|
||||
item.classList.remove('selected-item');
|
||||
});
|
||||
|
||||
// 显示选中的预约卡片并添加高亮
|
||||
const selectedCard = document.getElementById(`submission_${submissionId}`);
|
||||
if (selectedCard) {
|
||||
selectedCard.style.display = 'block';
|
||||
selectedCard.classList.add('selected-card');
|
||||
}
|
||||
|
||||
// 为选择列表中对应的项添加高亮
|
||||
const submissionItems = document.querySelectorAll('.submission-item');
|
||||
submissionItems.forEach(item => {
|
||||
// 查找包含当前submissionId的点击事件
|
||||
if (item.onclick && item.onclick.toString().includes(`selectSubmission(${submissionId})`)) {
|
||||
item.classList.add('selected-item');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 工作时间设置
|
||||
@@ -741,24 +837,34 @@ try {
|
||||
|
||||
// 页面加载时初始化所有预约的日期、时间和套餐信息
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
<?php $firstSubmissionId = null; ?>
|
||||
<?php foreach ($pending_submissions as $submission): ?>
|
||||
<?php if ($firstSubmissionId === null) $firstSubmissionId = $submission['id']; ?>
|
||||
// 初始化日期
|
||||
selectDate(<?php echo $submission['id']; ?>, '<?php echo date('Y-m-d'); ?>');
|
||||
|
||||
// 初始化默认套餐信息(如果有默认选择的话)
|
||||
const packageSelect = document.getElementById('selected_package_<?php echo $submission['id']; ?>');
|
||||
if (packageSelect.value) {
|
||||
updatePackageInfo(<?php echo $submission['id']; ?>);
|
||||
} else if (packageSelect.options.length > 1) {
|
||||
// 默认选择第一个套餐
|
||||
packageSelect.selectedIndex = 1;
|
||||
updatePackageInfo(<?php echo $submission['id']; ?>);
|
||||
}
|
||||
(function() {
|
||||
let packageSelectElem = document.getElementById('selected_package_<?php echo $submission['id']; ?>');
|
||||
if (packageSelectElem.value) {
|
||||
updatePackageInfo(<?php echo $submission['id']; ?>);
|
||||
} else if (packageSelectElem.options.length > 1) {
|
||||
// 默认选择第一个套餐
|
||||
packageSelectElem.selectedIndex = 1;
|
||||
updatePackageInfo(<?php echo $submission['id']; ?>);
|
||||
}
|
||||
})();
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
// 默认显示并高亮第一条记录
|
||||
<?php if ($firstSubmissionId !== null): ?>
|
||||
selectSubmission(<?php echo $firstSubmissionId; ?>);
|
||||
<?php endif; ?>
|
||||
});
|
||||
|
||||
// 选择日期
|
||||
function selectDate(submissionId, date) {
|
||||
|
||||
// 选择日期
|
||||
function selectDate(submissionId, date) {
|
||||
const calendarGrid = document.getElementById('calendarGrid_' + submissionId);
|
||||
const timeGrid = document.getElementById('timeGrid_' + submissionId);
|
||||
const selectedDateInput = document.getElementById('selected_date_' + submissionId);
|
||||
@@ -781,6 +887,9 @@ try {
|
||||
|
||||
// 生成时间段
|
||||
generateTimeSlots(submissionId, date);
|
||||
|
||||
// 显示时长提示
|
||||
showDurationAlert(submissionId);
|
||||
}
|
||||
|
||||
// 生成时间段
|
||||
@@ -863,6 +972,9 @@ try {
|
||||
if (slotElement) {
|
||||
slotElement.classList.add('selected');
|
||||
}
|
||||
|
||||
// 显示时长提示
|
||||
showDurationAlert(submissionId);
|
||||
}
|
||||
|
||||
// 更新套餐信息
|
||||
@@ -996,9 +1108,9 @@ try {
|
||||
let form = null;
|
||||
|
||||
// 方式1:通过套餐选择元素获取表单
|
||||
const packageSelect = document.getElementById('selected_package_' + submissionId);
|
||||
if (packageSelect) {
|
||||
form = packageSelect.closest('form');
|
||||
const packageSelectForm = document.getElementById('selected_package_' + submissionId);
|
||||
if (packageSelectForm) {
|
||||
form = packageSelectForm.closest('form');
|
||||
console.log('Form found via packageSelect:', form ? 'Yes' : 'No');
|
||||
}
|
||||
|
||||
@@ -1066,6 +1178,9 @@ try {
|
||||
if (!buttonFound) {
|
||||
console.warn('⚠️ No matching duration button found for:', minutes, 'minutes');
|
||||
}
|
||||
|
||||
// 显示时长提示
|
||||
showDurationAlert(submissionId);
|
||||
}
|
||||
|
||||
// 应用自定义时长
|
||||
@@ -1088,6 +1203,57 @@ 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>
|
||||
|
||||
@@ -151,7 +151,7 @@ try {
|
||||
<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="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>
|
||||
|
||||
Reference in New Issue
Block a user