实战教程:为你的网站集成在线预约与日程管理
概述:为什么需要在线预约与日程管理功能
在当今数字化时代,网站已不仅仅是信息展示的平台,更是企业与客户互动的重要渠道。无论是服务行业、咨询机构、医疗机构还是教育机构,在线预约与日程管理功能都已成为提升用户体验、优化运营效率的关键工具。通过集成这些功能,您可以:
- 减少人工沟通成本,实现24/7自助预约
- 避免时间冲突,提高资源利用率
- 提升专业形象,增强客户信任感
- 自动化提醒功能,降低爽约率
本教程将指导您通过WordPress代码二次开发,为您的网站添加完整的在线预约与日程管理系统,无需依赖昂贵的第三方插件,完全自主控制数据与功能。
环境准备与基础配置
在开始编码之前,请确保您的WordPress环境满足以下要求:
- WordPress 5.0或更高版本
- PHP 7.4或更高版本
- MySQL 5.6或更高版本
- 已安装并激活一个支持子主题的WordPress主题
首先,我们需要创建一个自定义插件来承载我们的预约系统。在wp-content/plugins/目录下创建新文件夹custom-booking-system,并在其中创建主插件文件:
<?php
/**
* Plugin Name: 自定义预约与日程管理系统
* Plugin URI: https://yourwebsite.com/
* Description: 为WordPress网站添加完整的在线预约与日程管理功能
* Version: 1.0.0
* Author: 您的名称
* License: GPL v2 or later
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('CBS_VERSION', '1.0.0');
define('CBS_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('CBS_PLUGIN_URL', plugin_dir_url(__FILE__));
// 初始化插件
function cbs_init() {
// 检查必要组件
if (!function_exists('register_post_type')) {
wp_die('此插件需要WordPress 3.0或更高版本。');
}
// 加载文本域(用于国际化)
load_plugin_textdomain('custom-booking-system', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
add_action('plugins_loaded', 'cbs_init');
// 激活插件时执行的操作
function cbs_activate() {
// 创建数据库表
cbs_create_tables();
// 设置默认选项
cbs_set_default_options();
// 刷新重写规则
flush_rewrite_rules();
}
register_activation_hook(__FILE__, 'cbs_activate');
// 停用插件时执行的操作
function cbs_deactivate() {
// 清理临时数据
// 注意:这里我们不删除数据,以便用户重新激活时保留数据
flush_rewrite_rules();
}
register_deactivation_hook(__FILE__, 'cbs_deactivate');
数据库设计与表结构创建
预约系统需要存储预约数据、服务项目、员工信息和时间安排。我们将创建以下数据库表:
// 创建数据库表
function cbs_create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$table_prefix = $wpdb->prefix . 'cbs_';
// 预约表
$bookings_table = $table_prefix . 'bookings';
$services_table = $table_prefix . 'services';
$staff_table = $table_prefix . 'staff';
$schedule_table = $table_prefix . 'schedules';
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
// 创建服务项目表
$sql_services = "CREATE TABLE IF NOT EXISTS $services_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL,
description text,
duration int NOT NULL DEFAULT 60 COMMENT '服务时长(分钟)',
price decimal(10,2) DEFAULT 0.00,
active tinyint(1) DEFAULT 1,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
// 创建员工表
$sql_staff = "CREATE TABLE IF NOT EXISTS $staff_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
user_id bigint(20) unsigned,
name varchar(100) NOT NULL,
email varchar(100),
phone varchar(20),
services text COMMENT '可提供的服务ID,逗号分隔',
work_hours text COMMENT '工作时间安排',
active tinyint(1) DEFAULT 1,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES {$wpdb->users}(ID) ON DELETE SET NULL
) $charset_collate;";
// 创建预约表
$sql_bookings = "CREATE TABLE IF NOT EXISTS $bookings_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
booking_code varchar(20) NOT NULL UNIQUE COMMENT '预约编号',
customer_name varchar(100) NOT NULL,
customer_email varchar(100) NOT NULL,
customer_phone varchar(20),
service_id mediumint(9) NOT NULL,
staff_id mediumint(9),
booking_date date NOT NULL,
start_time time NOT NULL,
end_time time NOT NULL,
status varchar(20) DEFAULT 'pending' COMMENT 'pending, confirmed, cancelled, completed',
notes text,
ip_address varchar(45),
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
FOREIGN KEY (service_id) REFERENCES $services_table(id) ON DELETE CASCADE,
FOREIGN KEY (staff_id) REFERENCES $staff_table(id) ON DELETE SET NULL
) $charset_collate;";
// 创建日程表
$sql_schedules = "CREATE TABLE IF NOT EXISTS $schedule_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
staff_id mediumint(9),
date date NOT NULL,
start_time time NOT NULL,
end_time time NOT NULL,
max_bookings int DEFAULT 1 COMMENT '该时段最大预约数',
booked_count int DEFAULT 0 COMMENT '已预约数',
is_dayoff tinyint(1) DEFAULT 0 COMMENT '是否休息日',
PRIMARY KEY (id),
FOREIGN KEY (staff_id) REFERENCES $staff_table(id) ON DELETE CASCADE,
UNIQUE KEY unique_schedule (staff_id, date, start_time)
) $charset_collate;";
// 执行SQL
dbDelta($sql_services);
dbDelta($sql_staff);
dbDelta($sql_bookings);
dbDelta($sql_schedules);
// 添加示例数据(仅当表为空时)
cbs_add_sample_data($services_table, $staff_table);
}
// 添加示例数据
function cbs_add_sample_data($services_table, $staff_table) {
global $wpdb;
// 检查服务表是否为空
$service_count = $wpdb->get_var("SELECT COUNT(*) FROM $services_table");
if ($service_count == 0) {
$wpdb->insert($services_table, [
'name' => '基础咨询',
'description' => '30分钟的专业咨询服务',
'duration' => 30,
'price' => 50.00
]);
$wpdb->insert($services_table, [
'name' => '深度服务',
'description' => '60分钟的深度服务',
'duration' => 60,
'price' => 100.00
]);
}
// 检查员工表是否为空
$staff_count = $wpdb->get_var("SELECT COUNT(*) FROM $staff_table");
if ($staff_count == 0) {
$wpdb->insert($staff_table, [
'name' => '张顾问',
'email' => 'consultant@example.com',
'phone' => '13800138000',
'services' => '1,2'
]);
}
}
预约表单前端实现
接下来,我们创建前端预约表单,让用户可以轻松选择服务、日期和时间:
// 预约表单短代码
function cbs_booking_form_shortcode($atts) {
// 提取短代码属性
$atts = shortcode_atts([
'service_id' => 0,
'staff_id' => 0,
'title' => '在线预约'
], $atts, 'booking_form');
ob_start(); // 开始输出缓冲
// 加载必要样式和脚本
wp_enqueue_style('cbs-frontend-style', CBS_PLUGIN_URL . 'assets/css/frontend.css');
wp_enqueue_script('cbs-frontend-script', CBS_PLUGIN_URL . 'assets/js/frontend.js', ['jquery', 'jquery-ui-datepicker'], CBS_VERSION, true);
// 本地化脚本,传递数据给JavaScript
wp_localize_script('cbs-frontend-script', 'cbs_ajax', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('cbs_booking_nonce')
]);
// 加载jQuery UI日期选择器样式
wp_enqueue_style('jquery-ui-style', '//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css');
?>
<div class="cbs-booking-container">
<h2><?php echo esc_html($atts['title']); ?></h2>
<div id="cbs-booking-message" class="cbs-message" style="display:none;"></div>
<form id="cbs-booking-form" method="post">
<!-- 客户信息 -->
<div class="cbs-form-section">
<h3>您的信息</h3>
<div class="cbs-form-group">
<label for="cbs-customer-name">姓名 *</label>
<input type="text" id="cbs-customer-name" name="customer_name" required>
</div>
<div class="cbs-form-group">
<label for="cbs-customer-email">邮箱 *</label>
<input type="email" id="cbs-customer-email" name="customer_email" required>
</div>
<div class="cbs-form-group">
<label for="cbs-customer-phone">电话</label>
<input type="tel" id="cbs-customer-phone" name="customer_phone">
</div>
</div>
<!-- 服务选择 -->
<div class="cbs-form-section">
<h3>选择服务</h3>
<div class="cbs-form-group">
<label for="cbs-service">服务项目 *</label>
<select id="cbs-service" name="service_id" required>
<option value="">请选择服务项目</option>
<?php
global $wpdb;
$services = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}cbs_services WHERE active = 1");
foreach ($services as $service) {
$selected = ($service->id == $atts['service_id']) ? 'selected' : '';
echo '<option value="' . esc_attr($service->id) . '" ' . $selected . '>'
. esc_html($service->name) . ' (' . esc_html($service->duration) . '分钟)'
. '</option>';
}
?>
</select>
</div>
</div>
<!-- 日期选择 -->
<div class="cbs-form-section">
<h3>选择日期</h3>
<div class="cbs-form-group">
<label for="cbs-booking-date">预约日期 *</label>
<input type="text" id="cbs-booking-date" name="booking_date" class="cbs-datepicker" required readonly>
<p class="cbs-description">请选择未来30天内的日期</p>
</div>
</div>
<!-- 时间选择 -->
<div class="cbs-form-section">
<h3>选择时间</h3>
<div class="cbs-form-group">
<label>可用时间段</label>
<div id="cbs-time-slots">
<p>请先选择日期以查看可用时间段</p>
</div>
</div>
</div>
<!-- 备注 -->
<div class="cbs-form-section">
<h3>备注信息</h3>
<div class="cbs-form-group">
<label for="cbs-notes">特殊要求或备注</label>
<textarea id="cbs-notes" name="notes" rows="3"></textarea>
</div>
</div>
<!-- 提交按钮 -->
<div class="cbs-form-section">
<input type="hidden" name="action" value="cbs_submit_booking">
<?php wp_nonce_field('cbs_booking_action', 'cbs_booking_nonce'); ?>
<button type="submit" id="cbs-submit-booking" class="cbs-submit-btn">提交预约</button>
</div>
</form>
</div>
<?php
return ob_get_clean(); // 返回缓冲内容
}
add_shortcode('booking_form', 'cbs_booking_form_shortcode');
AJAX处理与后端逻辑
现在我们需要创建AJAX处理函数,用于处理表单提交和获取可用时间:
// 获取可用时间段的AJAX处理
function cbs_get_available_times() {
// 验证nonce
check_ajax_referer('cbs_booking_nonce', 'nonce');
$date = sanitize_text_field($_POST['date']);
$service_id = intval($_POST['service_id']);
if (empty($date) || empty($service_id)) {
wp_send_json_error('缺少必要参数');
}
global $wpdb;
// 获取服务时长
$service = $wpdb->get_row($wpdb->prepare(
"SELECT duration FROM {$wpdb->prefix}cbs_services WHERE id = %d",
$service_id
));
if (!$service) {
wp_send_json_error('服务不存在');
}
$duration = $service->duration;
// 获取所有员工的工作时间
$staff_schedules = $wpdb->get_results($wpdb->prepare(
"SELECT s.*, st.name as staff_name
FROM {$wpdb->prefix}cbs_schedules s
LEFT JOIN {$wpdb->prefix}cbs_staff st ON s.staff_id = st.id
WHERE s.date = %s AND s.is_dayoff = 0",
$date
));
// 获取该日期已有的预约
$existing_bookings = $wpdb->get_results($wpdb->prepare(
"SELECT start_time, end_time, staff_id
FROM {$wpdb->prefix}cbs_bookings
WHERE booking_date = %s AND status IN ('pending', 'confirmed')",
$date
));
// 生成可用时间段
$available_slots = [];
foreach ($staff_schedules as $schedule) {
$start = strtotime($schedule->start_time);
$end = strtotime($schedule->end_time);
// 以30分钟为间隔生成时间段
for ($time = $start; $time < $end; $time += 30 * 60) {
$slot_end = $time + ($duration * 60);
// 检查是否超出工作时间
if ($slot_end > $end) {
continue;
}
// 检查该时间段是否已满
$booked_count = 0;
foreach ($existing_bookings as $booking) {
$booking_start = strtotime($booking->start_time);
$booking_end = strtotime($booking->end_time);
// 检查时间是否冲突
if ($time < $booking_end && $slot_end > $booking_start) {
if ($booking->staff_id == $schedule->staff_id || $booking->staff_id === null) {
$booked_count++;
}
}
}
// 如果未超过最大预约数,则添加为可用时间段
if ($booked_count < $schedule->max_bookings) {
$available_slots[] = [
'time' => date('H:i', $time),
'staff_id' => $schedule->staff_id,
'staff_name' => $schedule->staff_name,
'end_time' => date('H:i', $slot_end)
];
}
}
}
// 按时间排序
usort($available_slots, function($a, $b) {
return strcmp($a['time'], $b['time']);
});
wp_send_json_success($available_slots);
}
add_action('wp_ajax_cbs_get_available_times', 'cbs_get_available_times');
add_action('wp_ajax_nopriv_cbs_get_available_times', 'cbs_get_available_times');
// 处理预约提交
function cbs_submit_booking() {
// 验证nonce
if (!isset($_POST['cbs_booking_nonce']) ||
!wp_verify_nonce($_POST['cbs_booking_nonce'], 'cbs_booking_action')) {
wp_die('安全验证失败');
}
// 验证并清理数据
$customer_name = sanitize_text_field($_POST['customer_name']);
$customer_email = sanitize_email($_POST['customer_email']);
$customer_phone = sanitize_text_field($_POST['customer_phone']);
$service_id = intval($_POST['service_id']);
$booking_date = sanitize_text_field($_POST['booking_date']);
$start_time = sanitize_text_field($_POST['start_time']);
$notes = sanitize_textarea_field($_POST['notes']);
// 基本验证
if (empty($customer_name) || empty($customer_email) || empty($service_id) || empty($booking_date) || empty($start_time)) {
wp_die('请填写所有必填字段');
}
// 验证日期格式
if (!preg_match('/^d{4}-d{2}-d{2}$/', $booking_date)) {
wp_die('日期格式不正确');
}
// 验证时间格式
if (!preg_match('/^d{2}:d{2}$/', $start_time)) {
wp_die('时间格式不正确');
}
global $wpdb;
// 获取服务信息
$service = $wpdb->get_row($wpdb->prepare(
"SELECT duration FROM {$wpdb->prefix}cbs_services WHERE id = %d",
$service_id
));
if (!$service) {
wp_die('选择的服务不存在');
}
// 计算结束时间
$start_timestamp = strtotime($booking_date . ' ' . $start_time);
$end_timestamp = $start_timestamp + ($service->duration * 60);
$end_time = date('H:i', $end_timestamp);
// 检查时间是否可用
$is_available = cbs_check_time_availability($booking_date, $start_time, $end_time, $service_id);
if (!$is_available) {
wp_die('选择的时间段已被预约,请选择其他时间');
}
// 生成预约编号
$booking_code = 'BK' . date('Ymd') . strtoupper(wp_generate_password(6, false));
// 插入预约数据
$insert_data = [
'booking_code' => $booking_code,
'customer_name' => $customer_name,
'customer_email' => $customer_email,
'customer_phone' => $customer_phone,
'service_id' => $service_id,
'booking_date' => $booking_date,
'start_time' => $start_time,
'end_time' => $end_time,
'status' => 'pending',
'notes' => $notes,
'ip_address' => $_SERVER['REMOTE_ADDR']
];
$result = $wpdb->insert(
$wpdb->prefix . 'cbs_bookings',
$insert_data
);
if ($result === false) {
wp_die('预约提交失败,请稍后重试');
}
$booking_id = $wpdb->insert_id;
// 发送确认邮件
cbs_send_confirmation_email($booking_id);
// 返回成功信息
$success_message = sprintf(
'<div class="cbs-success-message">
<h3>预约提交成功!</h3>
<p>您的预约编号:<strong>%s</strong></p>
<p>我们已向您的邮箱发送确认邮件,请注意查收。</p>
<p>您可以在预约管理页面查看预约状态。</p>
</div>',
$booking_code
);
wp_die($success_message);
}
add_action('wp_ajax_cbs_submit_booking', 'cbs_submit_booking');
add_action('wp_ajax_nopriv_cbs_submit_booking', 'cbs_submit_booking');
// 检查时间可用性
function cbs_check_time_availability($date, $start_time, $end_time, $service_id, $exclude_booking_id = 0) {
global $wpdb;
$query = $wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}cbs_bookings
WHERE booking_date = %s
AND status IN ('pending', 'confirmed')
AND id != %d
AND (
(start_time < %s AND end_time > %s) OR
(start_time >= %s AND start_time < %s)
)",
$date,
$exclude_booking_id,
$end_time,
$start_time,
$start_time,
$end_time
);
$conflict_count = $wpdb->get_var($query);
return $conflict_count == 0;
}
## 后台管理界面开发
创建后台管理界面,让管理员可以管理预约、服务和员工:
// 添加管理菜单
function cbs_add_admin_menu() {
// 主菜单
add_menu_page(
'预约管理',
'预约系统',
'manage_options',
'cbs-dashboard',
'cbs_dashboard_page',
'dashicons-calendar-alt',
30
);
// 子菜单
add_submenu_page(
'cbs-dashboard',
'所有预约',
'所有预约',
'manage_options',
'cbs-bookings',
'cbs_bookings_page'
);
add_submenu_page(
'cbs-dashboard',
'服务管理',
'服务管理',
'manage_options',
'cbs-services',
'cbs_services_page'
);
add_submenu_page(
'cbs-dashboard',
'员工管理',
'员工管理',
'manage_options',
'cbs-staff',
'cbs_staff_page'
);
add_submenu_page(
'cbs-dashboard',
'日程设置',
'日程设置',
'manage_options',
'cbs-schedules',
'cbs_schedules_page'
);
add_submenu_page(
'cbs-dashboard',
'系统设置',
'系统设置',
'manage_options',
'cbs-settings',
'cbs_settings_page'
);
}
add_action('admin_menu', 'cbs_add_admin_menu');
// 预约管理页面
function cbs_bookings_page() {
global $wpdb;
// 处理批量操作
if (isset($_POST['action']) && isset($_POST['booking_ids'])) {
cbs_process_bulk_actions();
}
// 处理单个操作
if (isset($_GET['action']) && isset($_GET['id'])) {
cbs_process_single_action();
}
// 获取预约数据
$bookings = $wpdb->get_results("
SELECT b.*, s.name as service_name
FROM {$wpdb->prefix}cbs_bookings b
LEFT JOIN {$wpdb->prefix}cbs_services s ON b.service_id = s.id
ORDER BY b.booking_date DESC, b.start_time DESC
LIMIT 100
");
?>
<div class="wrap">
<h1 class="wp-heading-inline">预约管理</h1>
<a href="<?php echo admin_url('admin.php?page=cbs-dashboard'); ?>" class="page-title-action">返回仪表板</a>
<hr class="wp-header-end">
<form method="post" action="">
<?php wp_nonce_field('cbs_bulk_action', 'cbs_bulk_nonce'); ?>
<div class="tablenav top">
<div class="alignleft actions bulkactions">
<select name="action" id="bulk-action-selector-top">
<option value="-1">批量操作</option>
<option value="confirm">确认预约</option>
<option value="cancel">取消预约</option>
<option value="delete">删除预约</option>
</select>
<input type="submit" id="doaction" class="button action" value="应用">
</div>
<br class="clear">
</div>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<td id="cb" class="manage-column column-cb check-column">
<input type="checkbox" id="cb-select-all-1">
</td>
<th>预约编号</th>
<th>客户姓名</th>
<th>服务项目</th>
<th>预约时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if (empty($bookings)): ?>
<tr>
<td colspan="7">暂无预约记录</td>
</tr>
<?php else: ?>
<?php foreach ($bookings as $booking): ?>
<tr>
<th scope="row" class="check-column">
<input type="checkbox" name="booking_ids[]" value="<?php echo $booking->id; ?>">
</th>
<td><?php echo esc_html($booking->booking_code); ?></td>
<td>
<?php echo esc_html($booking->customer_name); ?><br>
<small><?php echo esc_html($booking->customer_email); ?></small>
</td>
<td><?php echo esc_html($booking->service_name); ?></td>
<td>
<?php echo date('Y-m-d', strtotime($booking->booking_date)); ?><br>
<?php echo $booking->start_time; ?> - <?php echo $booking->end_time; ?>
</td>
<td>
<?php
$status_labels = [
'pending' => '<span class="cbs-status pending">待确认</span>',
'confirmed' => '<span class="cbs-status confirmed">已确认</span>',
'cancelled' => '<span class="cbs-status cancelled">已取消</span>',
'completed' => '<span class="cbs-status completed">已完成</span>'
];
echo $status_labels[$booking->status] ?? $booking->status;
?>
</td>
<td>
<a href="<?php echo admin_url('admin.php?page=cbs-bookings&action=view&id=' . $booking->id); ?>"
class="button button-small">查看</a>
<a href="<?php echo admin_url('admin.php?page=cbs-bookings&action=edit&id=' . $booking->id); ?>"
class="button button-small">编辑</a>
<?php if ($booking->status == 'pending'): ?>
<a href="<?php echo admin_url('admin.php?page=cbs-bookings&action=confirm&id=' . $booking->id); ?>"
class="button button-small button-primary">确认</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</form>
</div>
<style>
.cbs-status {
padding: 3px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
}
.cbs-status.pending { background: #f0ad4e; color: white; }
.cbs-status.confirmed { background: #5cb85c; color: white; }
.cbs-status.cancelled { background: #d9534f; color: white; }
.cbs-status.completed { background: #337ab7; color: white; }
</style>
<?php
}
// 服务管理页面
function cbs_services_page() {
global $wpdb;
// 处理表单提交
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['add_service'])) {
cbs_add_service();
} elseif (isset($_POST['update_service'])) {
cbs_update_service();
}
}
// 处理删除
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['id'])) {
cbs_delete_service(intval($_GET['id']));
}
// 获取所有服务
$services = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}cbs_services ORDER BY id DESC");
?>
<div class="wrap">
<h1>服务管理</h1>
<div class="cbs-admin-container">
<!-- 添加服务表单 -->
<div class="cbs-admin-card">
<h2><?php echo isset($_GET['edit']) ? '编辑服务' : '添加新服务'; ?></h2>
<form method="post" action="">
<?php
if (isset($_GET['edit'])) {
$edit_id = intval($_GET['edit']);
$service = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}cbs_services WHERE id = %d",
$edit_id
));
}
?>
<table class="form-table">
<tr>
<th><label for="service_name">服务名称</label></th>
<td>
<input type="text" id="service_name" name="service_name"
value="<?php echo isset($service) ? esc_attr($service->name) : ''; ?>"
class="regular-text" required>
</td>
</tr>
<tr>
<th><label for="service_description">服务描述</label></th>
<td>
<textarea id="service_description" name="service_description"
rows="3" class="large-text"><?php echo isset($service) ? esc_textarea($service->description) : ''; ?></textarea>
</td>
</tr>
<tr>
<th><label for="service_duration">服务时长(分钟)</label></th>
<td>
<input type="number" id="service_duration" name="service_duration"
value="<?php echo isset($service) ? esc_attr($service->duration) : '60'; ?>"
min="15" step="15" required>
<p class="description">建议设置为15的倍数</p>
</td>
</tr>
<tr>
<th><label for="service_price">价格(元)</label></th>
<td>
<input type="number" id="service_price" name="service_price"
value="<?php echo isset($service) ? esc_attr($service->price) : '0'; ?>"
min="0" step="0.01" class="small-text">
</td>
</tr>
<tr>
<th><label for="service_active">状态</label></th>
<td>
<label>
<input type="checkbox" id="service_active" name="service_active" value="1"
<?php echo (isset($service) && $service->active) || !isset($service) ? 'checked' : ''; ?>>
启用此服务
</label>
</td>
</tr>
</table>
<?php if (isset($_GET['edit'])): ?>
<input type="hidden" name="service_id" value="<?php echo $edit_id; ?>">
<?php wp_nonce_field('cbs_update_service', 'cbs_service_nonce'); ?>
<p class="submit">
<input type="submit" name="update_service" class="button button-primary" value="更新服务">
<a href="<?php echo admin_url('admin.php?page=cbs-services'); ?>" class="button">取消</a>
</p>
<?php else: ?>
<?php wp_nonce_field('cbs_add_service', 'cbs_service_nonce'); ?>
<p class="submit">
<input type="submit" name="add_service" class="button button-primary" value="添加服务">
</p>
<?php endif; ?>
</form>
</div>
<!-- 服务列表 -->
<div class="cbs-admin-card">
<h2>服务列表</h2>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>ID</th>
<th>服务名称</th>
<th>时长</th>
<th>价格</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if (empty($services)): ?>
<tr>
<td colspan="6">暂无服务项目</td>
</tr>
<?php else: ?>
<?php foreach ($services as $service): ?>
<tr>
<td><?php echo $service->id; ?></td>
<td>
<strong><?php echo esc_html($service->name); ?></strong>
<?php if ($service->description): ?>
<p class="description"><?php echo esc_html($service->description); ?></p>
<?php endif; ?>
</td>
<td><?php echo $service->duration; ?>分钟</td>
<td><?php echo $service->price ? '¥' . number_format($service->price, 2) : '免费'; ?></td>
<td>
<?php if ($service->active): ?>
<span class="dashicons dashicons-yes" style="color: #46b450;"></span> 启用
<?php else: ?>
<span class="dashicons dashicons-no" style="color: #dc3232;"></span> 停用
<?php endif; ?>
</td>
<td>
<a href="<?php echo admin_url('admin.php?page=cbs-services&edit=' . $service->id); ?>"
class="button button-small">编辑</a>
<a href="<?php echo admin_url('admin.php?page=cbs-services&action=delete&id=' . $service->id); ?>"
class="button button-small button-link-delete"
onclick="return confirm('确定要删除这个服务吗?');">删除</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<style>
.cbs-admin-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-top: 20px;
}
.cbs-admin-card {
background: white;
padding: 20px;
border: 1px solid #ccd0d4;


