This repository has been archived on 2026-06-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
carwash_order/packages.php
T
wsh5485 58fbb9f1e1 feat(预约系统): 添加套餐专属预约信息功能并优化复制逻辑
- 在SQL查询中添加package_reminder字段
- 重构复制按钮使用data属性存储预约信息
- 实现套餐专属预约信息的单独保存功能
- 优化复制功能以包含套餐专属提醒信息
- 添加套餐专属预约信息的实时编辑功能
2025-12-12 01:06:36 +08:00

430 lines
27 KiB
PHP

<?php
require_once 'db_connect.php';
$message = '';
// 处理套餐操作
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
try {
if ($action === 'add') {
$package_name = trim($_POST['package_name']);
$description = trim($_POST['description']);
$base_duration = (int)$_POST['base_duration'];
$price = (float)$_POST['price'];
$services = implode(',', array_filter(array_map('trim', $_POST['services'] ?? [])));
$package_reminder = trim($_POST['package_reminder']);
$stmt = $pdo->prepare("INSERT INTO packages (package_name, description, base_duration, price, services, package_reminder) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$package_name, $description, $base_duration, $price, $services, $package_reminder]);
$message = "套餐添加成功!";
} elseif ($action === 'update') {
$id = (int)$_POST['id'];
$package_name = trim($_POST['package_name']);
$description = trim($_POST['description']);
$base_duration = (int)$_POST['base_duration'];
$price = (float)$_POST['price'];
$services = implode(',', array_filter(array_map('trim', $_POST['services'] ?? [])));
$is_active = isset($_POST['is_active']) ? 1 : 0;
// 获取当前套餐的专属预约信息,避免更新时丢失
$stmt = $pdo->prepare("SELECT package_reminder FROM packages WHERE id = ?");
$stmt->execute([$id]);
$current_package = $stmt->fetch();
$package_reminder = $current_package['package_reminder'] ?? '';
// 更新套餐信息,保留现有的package_reminder
$stmt = $pdo->prepare("UPDATE packages SET package_name = ?, description = ?, base_duration = ?, price = ?, services = ?, package_reminder = ?, is_active = ? WHERE id = ?");
$stmt->execute([$package_name, $description, $base_duration, $price, $services, $package_reminder, $is_active, $id]);
$message = "套餐更新成功!";
} elseif ($action === 'delete') {
$id = (int)$_POST['id'];
$stmt = $pdo->prepare("DELETE FROM packages WHERE id = ?");
$stmt->execute([$id]);
$message = "套餐删除成功!";
} elseif ($action === 'update_reminder') {
// 处理套餐专属预约信息的单独更新
$id = (int)$_POST['id'];
$package_reminder = trim($_POST['package_reminder']);
$stmt = $pdo->prepare("UPDATE packages SET package_reminder = ? WHERE id = ?");
$stmt->execute([$package_reminder, $id]);
// 返回JSON响应
header('Content-Type: application/json');
echo json_encode(['success' => true, 'message' => '套餐专属预约信息更新成功!']);
exit;
}
} catch (Exception $e) {
$message = "操作失败:" . $e->getMessage();
}
}
// 获取套餐列表
$stmt = $pdo->query("SELECT * FROM packages ORDER BY created_at DESC");
$packages = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>张老师撸车(私家车库)工作室</title>
<link rel="stylesheet" href="style.css">
<style>
/* 套餐管理页面特定样式 */
.pending-bookings-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
/* 加载动画 */
.loading { display: none; }
/* 页面淡入效果 */
body {
animation: fadeIn 0.5s ease;
}
</style>
</head>
<body>
<div class="pending-bookings-container">
<header class="header">
<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 active">套餐管理</a>
<a href="vip.php" class="nav-link">VIP管理</a>
<a href="announcement.php" class="nav-link">今日待办</a>
</nav>
</header>
<?php if ($message): ?>
<div class="message <?= strpos($message, '成功') !== false ? 'success-message' : 'error-message' ?>"
style="<?= strpos($message, '成功') !== false ? '' : 'background-color: #fee; color: #c33; border-color: #fcc;' ?>">
<?= htmlspecialchars($message) ?>
</div>
<?php endif; ?>
<div class="package-form-container">
<h2>📋 添加新套餐</h2>
<form method="POST" class="package-form" style="background: white; border-radius: 8px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);">
<input type="hidden" name="action" value="add">
<div class="form-group" style="margin-bottom: 16px;">
<label for="package_name" class="required" style="display: block; font-weight: 600; margin-bottom: 6px; color: #333;">套餐名称</label>
<input type="text" id="package_name" name="package_name" required placeholder="如:标准洗车套餐" class="form-control" style="width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
</div>
<div class="form-group" style="margin-bottom: 16px;">
<label for="description" style="display: block; font-weight: 600; margin-bottom: 6px; color: #333;">套餐描述</label>
<textarea id="description" name="description" rows="3" placeholder="详细描述套餐内容和特点" class="form-control" style="width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; resize: vertical;"></textarea>
</div>
<div class="form-row" style="display: flex; gap: 16px; margin-bottom: 16px;">
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<label for="base_duration" class="required" style="display: block; font-weight: 600; margin-bottom: 6px; color: #333;">基础时长</label>
<div class="input-group" style="display: flex; align-items: center;">
<input type="number" id="base_duration" name="base_duration" min="15" step="15" value="60" required class="form-control" style="flex: 1; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; font-size: 14px; border-right: none;">
<span class="input-group-addon" style="padding: 10px 12px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 0 4px 4px 0; font-size: 14px; color: #666;">分钟</span>
</div>
</div>
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<label for="price" class="required" style="display: block; font-weight: 600; margin-bottom: 6px; color: #333;">价格</label>
<div class="input-group" style="display: flex; align-items: center;">
<span class="input-group-addon" style="padding: 10px 12px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px 0 0 4px; font-size: 14px; color: #666;">¥</span>
<input type="number" id="price" name="price" min="0" step="0.01" required placeholder="0.00" class="form-control" style="flex: 1; padding: 10px 12px; border: 1px solid #ddd; border-radius: 0 4px 4px 0; font-size: 14px; border-left: none;">
</div>
</div>
</div>
<div class="form-group" style="margin-bottom: 16px;">
<label style="display: block; font-weight: 600; margin-bottom: 6px; color: #333;">服务项目</label>
<div class="services-container" style="margin-bottom: 8px;">
<div class="service-item" style="display: flex; gap: 8px; margin-bottom: 8px; align-items: center;">
<input type="text" name="services[]" placeholder="如:外观清洗" value="外观清洗" class="form-control" style="flex: 1; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<button type="button" class="btn-danger btn-sm" onclick="removeService(this)" style="padding: 6px 12px; background: #ff4d4f; color: white; border: none; border-radius: 4px; font-size: 12px; cursor: pointer;"><span>删除</span></button>
</div>
<div class="service-item" style="display: flex; gap: 8px; margin-bottom: 8px; align-items: center;">
<input type="text" name="services[]" placeholder="如:内饰清洁" value="内饰清洁" class="form-control" style="flex: 1; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<button type="button" class="btn-danger btn-sm" onclick="removeService(this)" style="padding: 6px 12px; background: #ff4d4f; color: white; border: none; border-radius: 4px; font-size: 12px; cursor: pointer;"><span>删除</span></button>
</div>
</div>
<button type="button" class="btn-outline btn-sm" onclick="addService()" style="padding: 6px 12px; background: white; color: #1890ff; border: 1px solid #1890ff; border-radius: 4px; font-size: 12px; cursor: pointer;"><span>+ 添加服务项目</span></button>
</div>
<div class="form-group" style="margin-bottom: 16px;">
<label for="package_reminder" style="display: block; font-weight: 600; margin-bottom: 6px; color: #333;">套餐专属预约信息</label>
<textarea id="package_reminder" name="package_reminder" rows="4" placeholder="输入此套餐的专属预约信息,将在预约确认时显示" class="form-control" style="width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; resize: vertical;"></textarea>
</div>
<div class="form-actions" style="margin-top: 24px; display: flex; justify-content: flex-end;">
<button type="submit" class="btn-primary btn-lg" style="padding: 10px 24px; background: #1890ff; color: white; border: none; border-radius: 4px; font-size: 14px; font-weight: 600; cursor: pointer; transition: background 0.3s;">
<span>✨ 添加套餐</span>
</button>
</div>
</form>
</div>
<div class="packages-list">
<h2>📊 套餐列表</h2>
<?php if (empty($packages)): ?>
<div class="empty-state">
<div class="empty-icon">📦</div>
<p class="empty-message">暂无套餐数据</p>
<p class="empty-submessage">点击上方"添加套餐"按钮创建第一个套餐吧!</p>
</div>
<?php else: ?>
<div class="packages-grid">
<?php foreach ($packages as $package): ?>
<div class="package-card <?= $package['is_active'] ? '' : 'inactive' ?>" data-package-id="<?= $package['id'] ?>">
<div class="package-header">
<h3 class="package-title"><?= htmlspecialchars($package['package_name']) ?></h3>
<div class="package-status">
<span class="status-badge <?= $package['is_active'] ? 'active' : 'inactive' ?>">
<?= $package['is_active'] ? '启用' : '禁用' ?>
</span>
</div>
</div>
<?php if ($package['description']): ?>
<p class="package-description"><?= htmlspecialchars($package['description']) ?></p>
<?php endif; ?>
<div class="package-details">
<div class="detail-item">
<span class="detail-label">时长</span>
<span class="detail-value"><strong><?= $package['base_duration'] ?></strong>分钟</span>
</div>
<div class="detail-item">
<span class="detail-label">价格</span>
<span class="detail-value price"><strong>¥<?= number_format($package['price'], 2) ?></strong></span>
</div>
</div>
<?php
$services = explode(',', $package['services']);
if ($services && !empty(trim($services[0]))):
?>
<div class="package-services">
<span class="detail-label">包含服务</span>
<div class="services-tags">
<?php foreach ($services as $service): ?>
<?php if (trim($service)): ?>
<span class="service-tag"><?= htmlspecialchars(trim($service)) ?></span>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<div class="package-reminder" style="margin-top: 12px; padding: 12px; background: #f8f9fa; border-radius: 4px; border-left: 4px solid #1890ff;">
<div class="detail-label" style="font-weight: 600; margin-bottom: 8px; color: #333;">套餐专属预约信息</div>
<textarea class="reminder-editor" data-package-id="<?= $package['id'] ?>" oninput="autoResizeTextarea(this)" style="width: 100%; padding: 10px; background: white; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; line-height: 1.5; min-height: 100px; resize: none; overflow: hidden;" placeholder="输入此套餐的专属预约信息,将在预约确认时显示"><?= htmlspecialchars($package['package_reminder'] ?? '') ?></textarea>
<div class="reminder-actions" style="margin-top: 8px; text-align: right;">
<button class="btn-primary btn-sm" onclick="saveReminder(this)">保存</button>
<button class="btn-outline btn-sm" onclick="cancelEdit(this)">取消</button>
</div>
</div>
<div class="package-actions">
<button class="btn-primary btn-sm" onclick="editPackage(<?= $package['id'] ?>)"><span>✏️ 编辑</span></button>
<form method="POST" style="display: inline;" onsubmit="return confirmDelete()">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $package['id'] ?>">
<button type="submit" class="btn-danger btn-sm"><span>🗑️ 删除</span></button>
</form>
</div>
<!-- 编辑表单 -->
<form method="POST" class="edit-form" id="edit-form-<?= $package['id'] ?>" style="display: none; background: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-top: 16px;">
<input type="hidden" name="action" value="update">
<input type="hidden" name="id" value="<?= $package['id'] ?>">
<h4 style="margin-top: 0; margin-bottom: 16px; font-size: 16px; font-weight: 600; color: #333;">✏️ 编辑套餐</h4>
<div class="form-group" style="margin-bottom: 16px;">
<label class="required" style="display: block; font-weight: 600; margin-bottom: 6px; color: #333;">套餐名称</label>
<input type="text" name="package_name" value="<?= htmlspecialchars($package['package_name']) ?>" required placeholder="请输入套餐名称" class="form-control" style="width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
</div>
<div class="form-group" style="margin-bottom: 16px;">
<label style="display: block; font-weight: 600; margin-bottom: 6px; color: #333;">套餐描述</label>
<textarea name="description" rows="2" placeholder="请输入套餐描述" class="form-control" style="width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; resize: vertical;"><?= htmlspecialchars($package['description']) ?></textarea>
</div>
<div class="form-row" style="display: flex; gap: 16px; margin-bottom: 16px;">
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<label class="required" style="display: block; font-weight: 600; margin-bottom: 6px; color: #333;">基础时长</label>
<div class="input-group" style="display: flex; align-items: center;">
<input type="number" name="base_duration" min="15" step="15" value="<?= $package['base_duration'] ?>" required class="form-control" style="flex: 1; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px 0 0 4px; font-size: 14px; border-right: none;">
<span class="input-group-addon" style="padding: 10px 12px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 0 4px 4px 0; font-size: 14px; color: #666;">分钟</span>
</div>
</div>
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<label class="required" style="display: block; font-weight: 600; margin-bottom: 6px; color: #333;">价格</label>
<div class="input-group" style="display: flex; align-items: center;">
<span class="input-group-addon" style="padding: 10px 12px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px 0 0 4px; font-size: 14px; color: #666;">¥</span>
<input type="number" name="price" min="0" step="0.01" value="<?= $package['price'] ?>" required placeholder="0.00" class="form-control" style="flex: 1; padding: 10px 12px; border: 1px solid #ddd; border-radius: 0 4px 4px 0; font-size: 14px; border-left: none;">
</div>
</div>
</div>
<div class="form-group" style="margin-bottom: 16px;">
<label style="display: block; font-weight: 600; margin-bottom: 6px; color: #333;">服务项目</label>
<div class="services-container" style="margin-bottom: 8px;">
<?php
$services = explode(',', $package['services']);
foreach ($services as $service):
if (trim($service)):
?>
<div class="service-item" style="display: flex; gap: 8px; margin-bottom: 8px; align-items: center;">
<input type="text" name="services[]" value="<?= htmlspecialchars(trim($service)) ?>" placeholder="请输入服务项目" class="form-control" style="flex: 1; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
<button type="button" class="btn-danger btn-sm" onclick="removeService(this)" style="padding: 6px 12px; background: #ff4d4f; color: white; border: none; border-radius: 4px; font-size: 12px; cursor: pointer;"><span>删除</span></button>
</div>
<?php
endif;
endforeach;
?>
</div>
<button type="button" class="btn-outline btn-sm" onclick="addService()" style="padding: 6px 12px; background: white; color: #1890ff; border: 1px solid #1890ff; border-radius: 4px; font-size: 12px; cursor: pointer;"><span>+ 添加服务项目</span></button>
</div>
<!-- 套餐专属预约信息编辑已移至套餐列表直接编辑 -->
<div class="form-group" style="margin-bottom: 16px;">
<label class="checkbox-label" style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" name="is_active" <?= $package['is_active'] ? 'checked' : '' ?> style="margin-right: 8px;">
<span class="checkbox-text" style="font-weight: normal; color: #333;">启用此套餐</span>
</label>
</div>
<div class="form-actions" style="display: flex; gap: 12px; justify-content: flex-end;">
<button type="submit" class="btn-primary" style="padding: 8px 16px; background: #1890ff; color: white; border: none; border-radius: 4px; font-size: 14px; cursor: pointer; transition: background 0.3s;">
<span>💾 保存更改</span>
</button>
<button type="button" class="btn-outline" onclick="cancelEdit(<?= $package['id'] ?>)" style="padding: 8px 16px; background: white; color: #666; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; cursor: pointer; transition: all 0.3s;">
<span>❌ 取消</span>
</button>
</div>
</form>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<script>
function addService() {
const container = document.querySelector('.services-container');
const serviceItem = document.createElement('div');
serviceItem.className = 'service-item';
serviceItem.style.display = 'flex';
serviceItem.style.gap = '8px';
serviceItem.style.marginBottom = '8px';
serviceItem.style.alignItems = 'center';
serviceItem.innerHTML = `
<input type="text" name="services[]" placeholder="如:外观清洗" class="form-control" style="flex: 1; padding: 10px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;" required>
<button type="button" class="btn-danger btn-sm" onclick="removeService(this)" style="padding: 6px 12px; background: #ff4d4f; color: white; border: none; border-radius: 4px; font-size: 12px; cursor: pointer;"><span>删除</span></button>
`;
container.appendChild(serviceItem);
}
function removeService(button) {
const container = button.closest('.services-container');
const items = container.querySelectorAll('.service-item');
if (items.length > 1) {
button.parentElement.remove();
} else {
alert('至少需要保留一个服务项目');
}
}
function editPackage(id) {
// 隐藏所有编辑表单
document.querySelectorAll('.edit-form').forEach(form => {
form.style.display = 'none';
});
// 显示当前编辑表单
const editForm = document.getElementById(`edit-form-${id}`);
if (editForm) {
editForm.style.display = 'block';
editForm.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}
function cancelEdit(id) {
const editForm = document.getElementById(`edit-form-${id}`);
if (editForm) {
editForm.style.display = 'none';
}
}
function confirmDelete() {
return confirm('确定要删除这个套餐吗?此操作不可恢复。');
}
// 移动端优化
if (/Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) {
document.body.classList.add('mobile-device');
}
</script>
<script>
// 自适应文本框高度
function autoResizeTextarea(textarea) {
// 重置高度以获取正确的滚动高度
textarea.style.height = 'auto';
// 设置新高度(加上一些内边距的补偿)
textarea.style.height = Math.min(textarea.scrollHeight + 10, 500) + 'px';
}
// 页面加载时初始化所有文本框高度
window.addEventListener('DOMContentLoaded', function() {
const textareas = document.querySelectorAll('.reminder-editor');
textareas.forEach(autoResizeTextarea);
});
// 保存套餐专属预约信息
function saveReminder(button) {
const textarea = button.closest('.package-reminder').querySelector('.reminder-editor');
const packageId = textarea.dataset.packageId;
const reminderText = textarea.value.trim();
// 创建保存请求
const xhr = new XMLHttpRequest();
xhr.open('POST', 'packages.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
alert('保存成功!');
// 可以选择刷新页面或更新UI
} else {
alert('保存失败,请重试!');
}
}
};
xhr.send(`action=update_reminder&id=${packageId}&package_reminder=${encodeURIComponent(reminderText)}`);
}
// 取消编辑
function cancelEdit(button) {
const textarea = button.closest('.package-reminder').querySelector('.reminder-editor');
const packageId = textarea.dataset.packageId;
// 可以选择恢复原始值或不做处理
textarea.blur();
}
</script>
</body>
</html>