文章目录
-
- 在现代办公环境中,会议室和各种共享资源(如投影仪、车辆、设备等)的高效管理是企业运营的重要环节。传统的人工预约方式不仅效率低下,还容易引发时间冲突和资源浪费。随着远程办公和混合工作模式的普及,一个数字化的资源调度系统变得尤为重要。 WordPress作为全球最流行的内容管理系统,不仅适用于博客和网站建设,通过代码二次开发,完全可以实现会议室预订与资源调度这样的专业功能。本教程将详细指导您如何从零开始,在WordPress平台上开发一个功能完善的会议室预订与资源调度管理系统。
-
- 在开始开发之前,我们需要明确系统应具备的核心功能: 会议室管理:添加、编辑、删除会议室,设置容量、设备配置等信息 资源管理:管理可预订资源(投影仪、白板、视频会议设备等) 预订功能:用户可查看可用时间段并进行预订 冲突检测:自动检测时间冲突,避免重复预订 日历视图:直观展示会议室和资源的占用情况 用户权限管理:不同用户角色拥有不同权限 通知系统:预订确认、提醒、变更通知 报表统计:使用频率统计、资源利用率分析
- 我们将采用以下技术架构: 前端:HTML5、CSS3、JavaScript(jQuery)、FullCalendar.js 后端:PHP(WordPress核心)、MySQL数据库 通信:AJAX实现无刷新操作 安全性:WordPress非ces、数据验证、权限检查
- 我们需要创建以下自定义数据表: wp_meeting_rooms:存储会议室信息 wp_resources:存储可预订资源信息 wp_bookings:存储预订记录 wp_booking_resources:预订与资源的关联表
-
- 首先确保您具备以下环境: WordPress 5.0+ 安装 PHP 7.4+ 版本 MySQL 5.6+ 数据库 代码编辑器(VS Code、Sublime Text等)
- 我们将创建一个独立的插件来实现所有功能,确保与主题分离,便于维护和迁移。 在WordPress的wp-content/plugins/目录下创建新文件夹meeting-room-booking-system,并在其中创建主插件文件: <?php /** * Plugin Name: 会议室预订与资源调度管理系统 * Plugin URI: https://yourwebsite.com/ * Description: 一个功能完整的会议室与资源预订管理系统 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: mr-booking */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('MRB_PLUGIN_PATH', plugin_dir_path(__FILE__)); define('MRB_PLUGIN_URL', plugin_dir_url(__FILE__)); define('MRB_VERSION', '1.0.0'); // 初始化插件 require_once MRB_PLUGIN_PATH . 'includes/class-init.php';
- 在插件初始化类中,添加创建数据库表的代码: class MRB_Init { public function __construct() { // 激活插件时创建表 register_activation_hook(__FILE__, array($this, 'create_tables')); // 加载其他组件 $this->load_dependencies(); } public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 会议室表 $rooms_table = $wpdb->prefix . 'meeting_rooms'; $rooms_sql = "CREATE TABLE IF NOT EXISTS $rooms_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, description text, capacity smallint NOT NULL DEFAULT 10, location varchar(200), amenities text, status tinyint(1) DEFAULT 1, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; // 资源表 $resources_table = $wpdb->prefix . 'resources'; $resources_sql = "CREATE TABLE IF NOT EXISTS $resources_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, type varchar(50) NOT NULL, description text, quantity smallint NOT NULL DEFAULT 1, status tinyint(1) DEFAULT 1, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; // 预订表 $bookings_table = $wpdb->prefix . 'bookings'; $bookings_sql = "CREATE TABLE IF NOT EXISTS $bookings_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, room_id mediumint(9) NOT NULL, user_id bigint(20) NOT NULL, title varchar(200) NOT NULL, description text, start_time datetime NOT NULL, end_time datetime NOT NULL, attendees smallint DEFAULT 1, status varchar(20) DEFAULT 'confirmed', created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY room_id (room_id), KEY user_id (user_id), KEY start_time (start_time), KEY end_time (end_time) ) $charset_collate;"; // 预订资源关联表 $booking_resources_table = $wpdb->prefix . 'booking_resources'; $booking_resources_sql = "CREATE TABLE IF NOT EXISTS $booking_resources_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, booking_id mediumint(9) NOT NULL, resource_id mediumint(9) NOT NULL, quantity smallint NOT NULL DEFAULT 1, PRIMARY KEY (id), KEY booking_id (booking_id), KEY resource_id (resource_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($rooms_sql); dbDelta($resources_sql); dbDelta($bookings_sql); dbDelta($booking_resources_sql); } private function load_dependencies() { // 加载其他类文件 require_once MRB_PLUGIN_PATH . 'includes/class-admin.php'; require_once MRB_PLUGIN_PATH . 'includes/class-frontend.php'; require_once MRB_PLUGIN_PATH . 'includes/class-ajax.php'; require_once MRB_PLUGIN_PATH . 'includes/class-shortcodes.php'; } } new MRB_Init();
-
- 在class-admin.php中,添加管理菜单功能: class MRB_Admin { public function __construct() { add_action('admin_menu', array($this, 'add_admin_menus')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); } public function add_admin_menus() { // 主菜单 add_menu_page( '会议室预订系统', '会议室预订', 'manage_options', 'mrb-dashboard', array($this, 'dashboard_page'), 'dashicons-calendar-alt', 30 ); // 子菜单 add_submenu_page( 'mrb-dashboard', '会议室管理', '会议室', 'manage_options', 'mrb-rooms', array($this, 'rooms_page') ); add_submenu_page( 'mrb-dashboard', '资源管理', '资源', 'manage_options', 'mrb-resources', array($this, 'resources_page') ); add_submenu_page( 'mrb-dashboard', '预订管理', '预订', 'manage_options', 'mrb-bookings', array($this, 'bookings_page') ); add_submenu_page( 'mrb-dashboard', '系统设置', '设置', 'manage_options', 'mrb-settings', array($this, 'settings_page') ); } public function enqueue_admin_scripts($hook) { // 仅在我们的插件页面加载脚本 if (strpos($hook, 'mrb-') !== false) { wp_enqueue_style('mrb-admin-style', MRB_PLUGIN_URL . 'assets/css/admin.css', array(), MRB_VERSION); wp_enqueue_script('mrb-admin-script', MRB_PLUGIN_URL . 'assets/js/admin.js', array('jquery'), MRB_VERSION, true); // 本地化脚本,传递数据到JS wp_localize_script('mrb-admin-script', 'mrb_admin', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('mrb_admin_nonce') )); } } public function dashboard_page() { include MRB_PLUGIN_PATH . 'templates/admin/dashboard.php'; } public function rooms_page() { include MRB_PLUGIN_PATH . 'templates/admin/rooms.php'; } public function resources_page() { include MRB_PLUGIN_PATH . 'templates/admin/resources.php'; } public function bookings_page() { include MRB_PLUGIN_PATH . 'templates/admin/bookings.php'; } public function settings_page() { include MRB_PLUGIN_PATH . 'templates/admin/settings.php'; } }
- 创建templates/admin/rooms.php文件: <div class="wrap mrb-admin-wrap"> <h1 class="wp-heading-inline">会议室管理</h1> <a href="#" class="page-title-action" id="mrb-add-room">添加新会议室</a> <hr class="wp-header-end"> <!-- 添加/编辑会议室表单 (默认隐藏) --> <div id="mrb-room-form-container" style="display:none;"> <h2 id="mrb-form-title">添加会议室</h2> <form id="mrb-room-form" method="post"> <?php wp_nonce_field('mrb_save_room', 'mrb_room_nonce'); ?> <input type="hidden" id="room_id" name="room_id" value="0"> <table class="form-table"> <tr> <th scope="row"><label for="room_name">会议室名称</label></th> <td><input type="text" id="room_name" name="room_name" class="regular-text" required></td> </tr> <tr> <th scope="row"><label for="room_capacity">容量</label></th> <td><input type="number" id="room_capacity" name="room_capacity" min="1" max="500" value="10" required></td> </tr> <tr> <th scope="row"><label for="room_location">位置</label></th> <td><input type="text" id="room_location" name="room_location" class="regular-text"></td> </tr> <tr> <th scope="row"><label for="room_description">描述</label></th> <td><textarea id="room_description" name="room_description" rows="5" class="large-text"></textarea></td> </tr> <tr> <th scope="row"><label for="room_amenities">设备配置</label></th> <td> <textarea id="room_amenities" name="room_amenities" rows="3" class="large-text" placeholder="例如:投影仪、白板、视频会议系统"></textarea> <p class="description">每行一个设备,或使用逗号分隔</p> </td> </tr> <tr> <th scope="row"><label for="room_status">状态</label></th> <td> <select id="room_status" name="room_status"> <option value="1">可用</option> <option value="0">不可用</option> </select> </td> </tr> </table> <p class="submit"> <button type="submit" class="button button-primary">保存会议室</button> <button type="button" class="button" id="mrb-cancel-form">取消</button> </p> </form> </div> <!-- 会议室列表 --> <div id="mrb-rooms-list"> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th scope="col" width="5%">ID</th> <th scope="col" width="20%">名称</th> <th scope="col" width="10%">容量</th> <th scope="col" width="20%">位置</th> <th scope="col" width="25%">设备配置</th> <th scope="col" width="10%">状态</th> <th scope="col" width="10%">操作</th> </tr> </thead> <tbody id="mrb-rooms-table-body"> <!-- 通过AJAX加载数据 --> <tr> <td colspan="7">加载中...</td> </tr> </tbody> </table> </div> </div>
-
- 在class-shortcodes.php中,创建用于前端显示的短代码: class MRB_Shortcodes { public function __construct() { add_shortcode('meeting_room_booking', array($this, 'booking_calendar_shortcode')); add_shortcode('meeting_room_list', array($this, 'room_list_shortcode')); add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_scripts')); } public function enqueue_frontend_scripts() { global $post; // 仅在包含我们短代码的页面加载脚本 if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'meeting_room_booking')) { // FullCalendar库 wp_enqueue_style('fullcalendar-css', 'https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.css', array(), '5.10.1'); wp_enqueue_script('fullcalendar-js', 'https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.js', array('jquery'), '5.10.1', true); // 本地化日历 wp_enqueue_script('fullcalendar-locale', 'https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/locales/zh-cn.js', array('fullcalendar-js'), '5.10.1', true); // 插件前端样式和脚本 wp_enqueue_style('mrb-frontend-style', MRB_PLUGIN_URL . 'assets/css/frontend.css', array(), MRB_VERSION); wp_enqueue_script('mrb-frontend-script', MRB_PLUGIN_URL . 'assets/js/frontend.js', array('jquery', 'fullcalendar-js'), MRB_VERSION, true); // 传递数据到前端JS wp_localize_script('mrb-frontend-script', 'mrb_frontend', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('mrb_frontend_nonce'), 'current_user_id' => get_current_user_id(), 'calendar_locale' => get_locale(), 'time_format' => get_option('time_format', 'H:i'), 'date_format' => get_option('date_format', 'Y-m-d') )); } } public function booking_calendar_shortcode($atts) { // 检查用户是否登录 if (!is_user_logged_in()) { return '<div class="mrb-login-required">请先登录系统以预订会议室。</div>'; } ob_start(); include MRB_PLUGIN_PATH . 'templates/frontend/booking-calendar.php'; return ob_get_clean(); } public function room_list_shortcode($atts) { ob_start(); include MRB_PLUGIN_PATH . 'templates/frontend/room-list.php'; return ob_get_clean(); } }
- 创建templates/frontend/booking-calendar.php: <div class="mrb-booking-container"> <div class="mrb-booking-header"> <h2>会议室预订系统</h2> <div class="mrb-user-info"> 欢迎,<?php echo wp_get_current_user()->display_name; ?>! </div> </div> <div class="mrb-booking-main"> <div class="mrb-sidebar"> <div class="mrb-room-filter"> <h3>筛选会议室</h3> <div class="mrb-filter-section"> <label for="mrb-filter-capacity">最小容量:</label> <select id="mrb-filter-capacity"> <option value="0">不限</option> <option value="5">5人以上</option> <option value="10">10人以上</option> <option value="20">20人以上</option> <option value="30">30人以上</option> <option value="50">50人以上</option> </select> </div> <div class="mrb-filter-section"> <label for="mrb-filter-equipment">设备要求:</label> <div class="mrb-equipment-checkboxes"> <label><input type="checkbox" value="projector"> 投影仪</label> <label><input type="checkbox" value="whiteboard"> 白板</label> <label><input type="checkbox" value="videoconf"> 视频会议</label> <label><input type="checkbox" value="phone"> 电话会议</label> </div> </div> <button id="mrb-apply-filter" class="button">应用筛选</button> <button id="mrb-reset-filter" class="button button-secondary">重置筛选</button> </div> <div class="mrb-room-list"> <h3>可用会议室</h3> <div id="mrb-rooms-container"> <!-- 通过AJAX加载会议室列表 --> <div class="mrb-loading">加载中...</div> </div> </div> <div class="mrb-resource-list"> <h3>可预订资源</h3> <div id="mrb-resources-container"> <!-- 通过AJAX加载资源列表 --> <div class="mrb-loading">加载中...</div> </div> </div> </div> <div class="mrb-calendar-section"> <div class="mrb-calendar-controls"> <button id="mrb-prev-week" class="button">< 上周</button> <button id="mrb-today" class="button">今天</button> <button id="mrb-next-week" class="button">下周 ></button> <span id="mrb-current-week" class="mrb-week-display"></span> <div class="mrb-view-toggle"> <button class="button active" data-view="week">周视图</button> <button class="button" data-view="day">日视图</button> <button class="button" data-view="month">月视图</button> </div> </div> <div id="mrb-booking-calendar"></div> <div class="mrb-legend"> <div class="mrb-legend-item"> <span class="mrb-legend-color available"></span> <span>可预订</span> </div> <div class="mrb-legend-item"> <span class="mrb-legend-color booked"></span> <span>已预订</span> </div> <div class="mrb-legend-item"> <span class="mrb-legend-color your-booking"></span> <span>您的预订</span> </div> <div class="mrb-legend-item"> <span class="mrb-legend-color unavailable"></span> <span>不可用</span> </div> </div> </div> </div> </div> <!-- 预订模态框 --><div id="mrb-booking-modal" class="mrb-modal" style="display:none;"> <div class="mrb-modal-content"> <div class="mrb-modal-header"> <h3>新建预订</h3> <span class="mrb-modal-close">×</span> </div> <div class="mrb-modal-body"> <form id="mrb-booking-form"> <?php wp_nonce_field('mrb_create_booking', 'mrb_booking_nonce'); ?> <input type="hidden" id="booking_room_id" name="room_id"> <input type="hidden" id="booking_start" name="start_time"> <input type="hidden" id="booking_end" name="end_time"> <div class="mrb-form-group"> <label for="booking_title">会议主题 *</label> <input type="text" id="booking_title" name="title" required> </div> <div class="mrb-form-group"> <label for="booking_description">会议描述</label> <textarea id="booking_description" name="description" rows="3"></textarea> </div> <div class="mrb-form-row"> <div class="mrb-form-group"> <label for="booking_date">日期</label> <input type="text" id="booking_date" name="date" readonly> </div> <div class="mrb-form-group"> <label for="booking_start_time">开始时间</label> <select id="booking_start_time" name="start_time_select"> <!-- 通过JS动态生成时间选项 --> </select> </div> <div class="mrb-form-group"> <label for="booking_end_time">结束时间</label> <select id="booking_end_time" name="end_time_select"> <!-- 通过JS动态生成时间选项 --> </select> </div> </div> <div class="mrb-form-group"> <label for="booking_attendees">参会人数 *</label> <input type="number" id="booking_attendees" name="attendees" min="1" value="1" required> <span id="booking_capacity_info" class="mrb-hint"></span> </div> <div class="mrb-form-group"> <label>预订资源</label> <div id="mrb-booking-resources"> <!-- 通过JS动态生成资源选项 --> </div> </div> <div class="mrb-form-group"> <label for="booking_recurring">重复预订</label> <select id="booking_recurring" name="recurring"> <option value="none">不重复</option> <option value="daily">每天</option> <option value="weekly">每周</option> <option value="monthly">每月</option> </select> <div id="mrb-recurring-options" style="display:none;"> <label for="booking_recurring_count">重复次数:</label> <input type="number" id="booking_recurring_count" name="recurring_count" min="1" max="12" value="1"> </div> </div> <div class="mrb-form-actions"> <button type="submit" class="button button-primary">确认预订</button> <button type="button" class="button mrb-modal-cancel">取消</button> </div> </form> </div> </div> </div> ## 第五部分:AJAX处理与数据交互 ### 5.1 AJAX处理类 在`class-ajax.php`中,处理所有前端和后端的AJAX请求: class MRB_Ajax { public function __construct() { // 前端AJAX动作 add_action('wp_ajax_mrb_get_rooms', array($this, 'get_rooms')); add_action('wp_ajax_nopriv_mrb_get_rooms', array($this, 'get_rooms')); add_action('wp_ajax_mrb_get_resources', array($this, 'get_resources')); add_action('wp_ajax_nopriv_mrb_get_resources', array($this, 'get_resources')); add_action('wp_ajax_mrb_get_bookings', array($this, 'get_bookings')); add_action('wp_ajax_nopriv_mrb_get_bookings', array($this, 'get_bookings')); add_action('wp_ajax_mrb_create_booking', array($this, 'create_booking')); add_action('wp_ajax_mrb_cancel_booking', array($this, 'cancel_booking')); // 后台AJAX动作 add_action('wp_ajax_mrb_admin_save_room', array($this, 'admin_save_room')); add_action('wp_ajax_mrb_admin_delete_room', array($this, 'admin_delete_room')); add_action('wp_ajax_mrb_admin_get_rooms', array($this, 'admin_get_rooms')); } /** * 获取会议室列表 */ public function get_rooms() { // 验证nonce if (!check_ajax_referer('mrb_frontend_nonce', 'nonce', false)) { wp_die('安全验证失败', 403); } global $wpdb; $table_name = $wpdb->prefix . 'meeting_rooms'; // 获取筛选参数 $capacity = isset($_POST['capacity']) ? intval($_POST['capacity']) : 0; $equipment = isset($_POST['equipment']) ? $_POST['equipment'] : array(); // 构建查询 $where = array('status = 1'); $params = array(); if ($capacity > 0) { $where[] = 'capacity >= %d'; $params[] = $capacity; } $where_sql = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : ''; // 如果有设备筛选,需要进一步处理 if (!empty($equipment) && is_array($equipment)) { $query = "SELECT * FROM $table_name $where_sql"; if (!empty($params)) { $query = $wpdb->prepare($query, $params); } $rooms = $wpdb->get_results($query); // 过滤设备 $filtered_rooms = array(); foreach ($rooms as $room) { $amenities = explode(',', $room->amenities); $amenities = array_map('trim', $amenities); $match = true; foreach ($equipment as $eq) { if (!in_array($eq, $amenities)) { $match = false; break; } } if ($match) { $filtered_rooms[] = $room; } } $rooms = $filtered_rooms; } else { $query = "SELECT * FROM $table_name $where_sql ORDER BY name ASC"; if (!empty($params)) { $query = $wpdb->prepare($query, $params); } $rooms = $wpdb->get_results($query); } // 格式化返回数据 $formatted_rooms = array(); foreach ($rooms as $room) { $formatted_rooms[] = array( 'id' => $room->id, 'name' => $room->name, 'capacity' => $room->capacity, 'location' => $room->location, 'amenities' => $room->amenities, 'description' => $room->description ); } wp_send_json_success($formatted_rooms); } /** * 获取资源列表 */ public function get_resources() { if (!check_ajax_referer('mrb_frontend_nonce', 'nonce', false)) { wp_die('安全验证失败', 403); } global $wpdb; $table_name = $wpdb->prefix . 'resources'; $resources = $wpdb->get_results( "SELECT * FROM $table_name WHERE status = 1 ORDER BY type, name ASC" ); $formatted_resources = array(); foreach ($resources as $resource) { $formatted_resources[] = array( 'id' => $resource->id, 'name' => $resource->name, 'type' => $resource->type, 'description' => $resource->description, 'quantity' => $resource->quantity, 'available' => $resource->quantity // 简化处理,实际需要计算已预订数量 ); } wp_send_json_success($formatted_resources); } /** * 获取预订数据 */ public function get_bookings() { if (!check_ajax_referer('mrb_frontend_nonce', 'nonce', false)) { wp_die('安全验证失败', 403); } global $wpdb; $start = isset($_POST['start']) ? sanitize_text_field($_POST['start']) : date('Y-m-d'); $end = isset($_POST['end']) ? sanitize_text_field($_POST['end']) : date('Y-m-d', strtotime('+1 month')); $room_id = isset($_POST['room_id']) ? intval($_POST['room_id']) : 0; $bookings_table = $wpdb->prefix . 'bookings'; $rooms_table = $wpdb->prefix . 'meeting_rooms'; // 构建查询 $where = array("b.start_time >= %s", "b.end_time <= %s", "b.status != 'cancelled'"); $params = array($start, $end); if ($room_id > 0) { $where[] = "b.room_id = %d"; $params[] = $room_id; } $where_sql = implode(' AND ', $where); $query = $wpdb->prepare( "SELECT b.*, r.name as room_name, r.color as room_color FROM $bookings_table b LEFT JOIN $rooms_table r ON b.room_id = r.id WHERE $where_sql ORDER BY b.start_time ASC", $params ); $bookings = $wpdb->get_results($query); // 格式化FullCalendar事件数据 $events = array(); $current_user_id = get_current_user_id(); foreach ($bookings as $booking) { $color = '#3788d8'; // 默认蓝色 if ($booking->user_id == $current_user_id) { $color = '#28a745'; // 用户自己的预订,绿色 } elseif ($booking->status == 'pending') { $color = '#ffc107'; // 待审核,黄色 } $events[] = array( 'id' => $booking->id, 'title' => $booking->title . ' (' . $booking->room_name . ')', 'start' => $booking->start_time, 'end' => $booking->end_time, 'color' => $color, 'extendedProps' => array( 'room_id' => $booking->room_id, 'room_name' => $booking->room_name, 'description' => $booking->description, 'attendees' => $booking->attendees, 'status' => $booking->status, 'user_id' => $booking->user_id ) ); } wp_send_json_success($events); } /** * 创建新预订 */ public function create_booking() { if (!check_ajax_referer('mrb_frontend_nonce', 'nonce', false)) { wp_die('安全验证失败', 403); } // 验证用户登录 if (!is_user_logged_in()) { wp_send_json_error('请先登录'); } // 验证输入数据 $required_fields = array('room_id', 'title', 'start_time', 'end_time', 'attendees'); foreach ($required_fields as $field) { if (empty($_POST[$field])) { wp_send_json_error('缺少必要字段: ' . $field); } } global $wpdb; $room_id = intval($_POST['room_id']); $user_id = get_current_user_id(); $title = sanitize_text_field($_POST['title']); $description = isset($_POST['description']) ? sanitize_textarea_field($_POST['description']) : ''; $start_time = sanitize_text_field($_POST['start_time']); $end_time = sanitize_text_field($_POST['end_time']); $attendees = intval($_POST['attendees']); $resources = isset($_POST['resources']) ? $_POST['resources'] : array(); // 检查会议室是否存在且可用 $room = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}meeting_rooms WHERE id = %d AND status = 1", $room_id )); if (!$room) { wp_send_json_error('会议室不存在或不可用'); } // 检查容量 if ($attendees > $room->capacity) { wp_send_json_error('参会人数超过会议室容量'); } // 检查时间冲突 $conflict = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}bookings WHERE room_id = %d AND status != 'cancelled' AND ( (start_time < %s AND end_time > %s) OR (start_time >= %s AND start_time < %s) OR (end_time > %s AND end_time <= %s) )", $room_id, $end_time, $start_time, $start_time, $end_time, $start_time, $end_time )); if ($conflict > 0) { wp_send_json_error('该时间段已被预订'); } // 检查资源可用性 $resource_errors = array(); if (is_array($resources) && !empty($resources)) { foreach ($resources as $resource_id => $quantity) { $resource_id = intval($resource_id); $quantity = intval($quantity); if ($quantity > 0) { // 检查资源是否存在 $resource = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}resources WHERE id = %d AND status = 1", $resource_id )); if (!$resource) { $resource_errors[] = "资源ID {$resource_id} 不存在"; continue
在现代办公环境中,会议室和各种共享资源(如投影仪、车辆、设备等)的高效管理是企业运营的重要环节。传统的人工预约方式不仅效率低下,还容易引发时间冲突和资源浪费。随着远程办公和混合工作模式的普及,一个数字化的资源调度系统变得尤为重要。
WordPress作为全球最流行的内容管理系统,不仅适用于博客和网站建设,通过代码二次开发,完全可以实现会议室预订与资源调度这样的专业功能。本教程将详细指导您如何从零开始,在WordPress平台上开发一个功能完善的会议室预订与资源调度管理系统。
在开始开发之前,我们需要明确系统应具备的核心功能:
- 会议室管理:添加、编辑、删除会议室,设置容量、设备配置等信息
- 资源管理:管理可预订资源(投影仪、白板、视频会议设备等)
- 预订功能:用户可查看可用时间段并进行预订
- 冲突检测:自动检测时间冲突,避免重复预订
- 日历视图:直观展示会议室和资源的占用情况
- 用户权限管理:不同用户角色拥有不同权限
- 通知系统:预订确认、提醒、变更通知
- 报表统计:使用频率统计、资源利用率分析
我们将采用以下技术架构:
- 前端:HTML5、CSS3、JavaScript(jQuery)、FullCalendar.js
- 后端:PHP(WordPress核心)、MySQL数据库
- 通信:AJAX实现无刷新操作
- 安全性:WordPress非ces、数据验证、权限检查
我们需要创建以下自定义数据表:
wp_meeting_rooms:存储会议室信息wp_resources:存储可预订资源信息wp_bookings:存储预订记录wp_booking_resources:预订与资源的关联表
首先确保您具备以下环境:
- WordPress 5.0+ 安装
- PHP 7.4+ 版本
- MySQL 5.6+ 数据库
- 代码编辑器(VS Code、Sublime Text等)
我们将创建一个独立的插件来实现所有功能,确保与主题分离,便于维护和迁移。
在WordPress的wp-content/plugins/目录下创建新文件夹meeting-room-booking-system,并在其中创建主插件文件:
<?php
/**
* Plugin Name: 会议室预订与资源调度管理系统
* Plugin URI: https://yourwebsite.com/
* Description: 一个功能完整的会议室与资源预订管理系统
* Version: 1.0.0
* Author: 您的名称
* License: GPL v2 or later
* Text Domain: mr-booking
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('MRB_PLUGIN_PATH', plugin_dir_path(__FILE__));
define('MRB_PLUGIN_URL', plugin_dir_url(__FILE__));
define('MRB_VERSION', '1.0.0');
// 初始化插件
require_once MRB_PLUGIN_PATH . 'includes/class-init.php';
在插件初始化类中,添加创建数据库表的代码:
class MRB_Init {
public function __construct() {
// 激活插件时创建表
register_activation_hook(__FILE__, array($this, 'create_tables'));
// 加载其他组件
$this->load_dependencies();
}
public function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// 会议室表
$rooms_table = $wpdb->prefix . 'meeting_rooms';
$rooms_sql = "CREATE TABLE IF NOT EXISTS $rooms_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL,
description text,
capacity smallint NOT NULL DEFAULT 10,
location varchar(200),
amenities text,
status tinyint(1) DEFAULT 1,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
// 资源表
$resources_table = $wpdb->prefix . 'resources';
$resources_sql = "CREATE TABLE IF NOT EXISTS $resources_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL,
type varchar(50) NOT NULL,
description text,
quantity smallint NOT NULL DEFAULT 1,
status tinyint(1) DEFAULT 1,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
// 预订表
$bookings_table = $wpdb->prefix . 'bookings';
$bookings_sql = "CREATE TABLE IF NOT EXISTS $bookings_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
room_id mediumint(9) NOT NULL,
user_id bigint(20) NOT NULL,
title varchar(200) NOT NULL,
description text,
start_time datetime NOT NULL,
end_time datetime NOT NULL,
attendees smallint DEFAULT 1,
status varchar(20) DEFAULT 'confirmed',
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY room_id (room_id),
KEY user_id (user_id),
KEY start_time (start_time),
KEY end_time (end_time)
) $charset_collate;";
// 预订资源关联表
$booking_resources_table = $wpdb->prefix . 'booking_resources';
$booking_resources_sql = "CREATE TABLE IF NOT EXISTS $booking_resources_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
booking_id mediumint(9) NOT NULL,
resource_id mediumint(9) NOT NULL,
quantity smallint NOT NULL DEFAULT 1,
PRIMARY KEY (id),
KEY booking_id (booking_id),
KEY resource_id (resource_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($rooms_sql);
dbDelta($resources_sql);
dbDelta($bookings_sql);
dbDelta($booking_resources_sql);
}
private function load_dependencies() {
// 加载其他类文件
require_once MRB_PLUGIN_PATH . 'includes/class-admin.php';
require_once MRB_PLUGIN_PATH . 'includes/class-frontend.php';
require_once MRB_PLUGIN_PATH . 'includes/class-ajax.php';
require_once MRB_PLUGIN_PATH . 'includes/class-shortcodes.php';
}
}
new MRB_Init();
在class-admin.php中,添加管理菜单功能:
class MRB_Admin {
public function __construct() {
add_action('admin_menu', array($this, 'add_admin_menus'));
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
}
public function add_admin_menus() {
// 主菜单
add_menu_page(
'会议室预订系统',
'会议室预订',
'manage_options',
'mrb-dashboard',
array($this, 'dashboard_page'),
'dashicons-calendar-alt',
30
);
// 子菜单
add_submenu_page(
'mrb-dashboard',
'会议室管理',
'会议室',
'manage_options',
'mrb-rooms',
array($this, 'rooms_page')
);
add_submenu_page(
'mrb-dashboard',
'资源管理',
'资源',
'manage_options',
'mrb-resources',
array($this, 'resources_page')
);
add_submenu_page(
'mrb-dashboard',
'预订管理',
'预订',
'manage_options',
'mrb-bookings',
array($this, 'bookings_page')
);
add_submenu_page(
'mrb-dashboard',
'系统设置',
'设置',
'manage_options',
'mrb-settings',
array($this, 'settings_page')
);
}
public function enqueue_admin_scripts($hook) {
// 仅在我们的插件页面加载脚本
if (strpos($hook, 'mrb-') !== false) {
wp_enqueue_style('mrb-admin-style', MRB_PLUGIN_URL . 'assets/css/admin.css', array(), MRB_VERSION);
wp_enqueue_script('mrb-admin-script', MRB_PLUGIN_URL . 'assets/js/admin.js', array('jquery'), MRB_VERSION, true);
// 本地化脚本,传递数据到JS
wp_localize_script('mrb-admin-script', 'mrb_admin', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('mrb_admin_nonce')
));
}
}
public function dashboard_page() {
include MRB_PLUGIN_PATH . 'templates/admin/dashboard.php';
}
public function rooms_page() {
include MRB_PLUGIN_PATH . 'templates/admin/rooms.php';
}
public function resources_page() {
include MRB_PLUGIN_PATH . 'templates/admin/resources.php';
}
public function bookings_page() {
include MRB_PLUGIN_PATH . 'templates/admin/bookings.php';
}
public function settings_page() {
include MRB_PLUGIN_PATH . 'templates/admin/settings.php';
}
}
创建templates/admin/rooms.php文件:
<div class="wrap mrb-admin-wrap">
<h1 class="wp-heading-inline">会议室管理</h1>
<a href="#" class="page-title-action" id="mrb-add-room">添加新会议室</a>
<hr class="wp-header-end">
<!-- 添加/编辑会议室表单 (默认隐藏) -->
<div id="mrb-room-form-container" style="display:none;">
<h2 id="mrb-form-title">添加会议室</h2>
<form id="mrb-room-form" method="post">
<?php wp_nonce_field('mrb_save_room', 'mrb_room_nonce'); ?>
<input type="hidden" id="room_id" name="room_id" value="0">
<table class="form-table">
<tr>
<th scope="row"><label for="room_name">会议室名称</label></th>
<td><input type="text" id="room_name" name="room_name" class="regular-text" required></td>
</tr>
<tr>
<th scope="row"><label for="room_capacity">容量</label></th>
<td><input type="number" id="room_capacity" name="room_capacity" min="1" max="500" value="10" required></td>
</tr>
<tr>
<th scope="row"><label for="room_location">位置</label></th>
<td><input type="text" id="room_location" name="room_location" class="regular-text"></td>
</tr>
<tr>
<th scope="row"><label for="room_description">描述</label></th>
<td><textarea id="room_description" name="room_description" rows="5" class="large-text"></textarea></td>
</tr>
<tr>
<th scope="row"><label for="room_amenities">设备配置</label></th>
<td>
<textarea id="room_amenities" name="room_amenities" rows="3" class="large-text" placeholder="例如:投影仪、白板、视频会议系统"></textarea>
<p class="description">每行一个设备,或使用逗号分隔</p>
</td>
</tr>
<tr>
<th scope="row"><label for="room_status">状态</label></th>
<td>
<select id="room_status" name="room_status">
<option value="1">可用</option>
<option value="0">不可用</option>
</select>
</td>
</tr>
</table>
<p class="submit">
<button type="submit" class="button button-primary">保存会议室</button>
<button type="button" class="button" id="mrb-cancel-form">取消</button>
</p>
</form>
</div>
<!-- 会议室列表 -->
<div id="mrb-rooms-list">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th scope="col" width="5%">ID</th>
<th scope="col" width="20%">名称</th>
<th scope="col" width="10%">容量</th>
<th scope="col" width="20%">位置</th>
<th scope="col" width="25%">设备配置</th>
<th scope="col" width="10%">状态</th>
<th scope="col" width="10%">操作</th>
</tr>
</thead>
<tbody id="mrb-rooms-table-body">
<!-- 通过AJAX加载数据 -->
<tr>
<td colspan="7">加载中...</td>
</tr>
</tbody>
</table>
</div>
</div>
在class-shortcodes.php中,创建用于前端显示的短代码:
class MRB_Shortcodes {
public function __construct() {
add_shortcode('meeting_room_booking', array($this, 'booking_calendar_shortcode'));
add_shortcode('meeting_room_list', array($this, 'room_list_shortcode'));
add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_scripts'));
}
public function enqueue_frontend_scripts() {
global $post;
// 仅在包含我们短代码的页面加载脚本
if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'meeting_room_booking')) {
// FullCalendar库
wp_enqueue_style('fullcalendar-css', 'https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.css', array(), '5.10.1');
wp_enqueue_script('fullcalendar-js', 'https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.js', array('jquery'), '5.10.1', true);
// 本地化日历
wp_enqueue_script('fullcalendar-locale', 'https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/locales/zh-cn.js', array('fullcalendar-js'), '5.10.1', true);
// 插件前端样式和脚本
wp_enqueue_style('mrb-frontend-style', MRB_PLUGIN_URL . 'assets/css/frontend.css', array(), MRB_VERSION);
wp_enqueue_script('mrb-frontend-script', MRB_PLUGIN_URL . 'assets/js/frontend.js', array('jquery', 'fullcalendar-js'), MRB_VERSION, true);
// 传递数据到前端JS
wp_localize_script('mrb-frontend-script', 'mrb_frontend', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('mrb_frontend_nonce'),
'current_user_id' => get_current_user_id(),
'calendar_locale' => get_locale(),
'time_format' => get_option('time_format', 'H:i'),
'date_format' => get_option('date_format', 'Y-m-d')
));
}
}
public function booking_calendar_shortcode($atts) {
// 检查用户是否登录
if (!is_user_logged_in()) {
return '<div class="mrb-login-required">请先登录系统以预订会议室。</div>';
}
ob_start();
include MRB_PLUGIN_PATH . 'templates/frontend/booking-calendar.php';
return ob_get_clean();
}
public function room_list_shortcode($atts) {
ob_start();
include MRB_PLUGIN_PATH . 'templates/frontend/room-list.php';
return ob_get_clean();
}
}
创建templates/frontend/booking-calendar.php:
<div class="mrb-booking-container">
<div class="mrb-booking-header">
<h2>会议室预订系统</h2>
<div class="mrb-user-info">
欢迎,<?php echo wp_get_current_user()->display_name; ?>!
</div>
</div>
<div class="mrb-booking-main">
<div class="mrb-sidebar">
<div class="mrb-room-filter">
<h3>筛选会议室</h3>
<div class="mrb-filter-section">
<label for="mrb-filter-capacity">最小容量:</label>
<select id="mrb-filter-capacity">
<option value="0">不限</option>
<option value="5">5人以上</option>
<option value="10">10人以上</option>
<option value="20">20人以上</option>
<option value="30">30人以上</option>
<option value="50">50人以上</option>
</select>
</div>
<div class="mrb-filter-section">
<label for="mrb-filter-equipment">设备要求:</label>
<div class="mrb-equipment-checkboxes">
<label><input type="checkbox" value="projector"> 投影仪</label>
<label><input type="checkbox" value="whiteboard"> 白板</label>
<label><input type="checkbox" value="videoconf"> 视频会议</label>
<label><input type="checkbox" value="phone"> 电话会议</label>
</div>
</div>
<button id="mrb-apply-filter" class="button">应用筛选</button>
<button id="mrb-reset-filter" class="button button-secondary">重置筛选</button>
</div>
<div class="mrb-room-list">
<h3>可用会议室</h3>
<div id="mrb-rooms-container">
<!-- 通过AJAX加载会议室列表 -->
<div class="mrb-loading">加载中...</div>
</div>
</div>
<div class="mrb-resource-list">
<h3>可预订资源</h3>
<div id="mrb-resources-container">
<!-- 通过AJAX加载资源列表 -->
<div class="mrb-loading">加载中...</div>
</div>
</div>
</div>
<div class="mrb-calendar-section">
<div class="mrb-calendar-controls">
<button id="mrb-prev-week" class="button">< 上周</button>
<button id="mrb-today" class="button">今天</button>
<button id="mrb-next-week" class="button">下周 ></button>
<span id="mrb-current-week" class="mrb-week-display"></span>
<div class="mrb-view-toggle">
<button class="button active" data-view="week">周视图</button>
<button class="button" data-view="day">日视图</button>
<button class="button" data-view="month">月视图</button>
</div>
</div>
<div id="mrb-booking-calendar"></div>
<div class="mrb-legend">
<div class="mrb-legend-item">
<span class="mrb-legend-color available"></span>
<span>可预订</span>
</div>
<div class="mrb-legend-item">
<span class="mrb-legend-color booked"></span>
<span>已预订</span>
</div>
<div class="mrb-legend-item">
<span class="mrb-legend-color your-booking"></span>
<span>您的预订</span>
</div>
<div class="mrb-legend-item">
<span class="mrb-legend-color unavailable"></span>
<span>不可用</span>
</div>
</div>
</div>
</div>
</div>
<!-- 预订模态框 -->
<div id="mrb-booking-modal" class="mrb-modal" style="display:none;">
<div class="mrb-modal-content">
<div class="mrb-modal-header">
<h3>新建预订</h3>
<span class="mrb-modal-close">×</span>
</div>
<div class="mrb-modal-body">
<form id="mrb-booking-form">
<?php wp_nonce_field('mrb_create_booking', 'mrb_booking_nonce'); ?>
<input type="hidden" id="booking_room_id" name="room_id">
<input type="hidden" id="booking_start" name="start_time">
<input type="hidden" id="booking_end" name="end_time">
<div class="mrb-form-group">
<label for="booking_title">会议主题 *</label>
<input type="text" id="booking_title" name="title" required>
</div>
<div class="mrb-form-group">
<label for="booking_description">会议描述</label>
<textarea id="booking_description" name="description" rows="3"></textarea>
</div>
<div class="mrb-form-row">
<div class="mrb-form-group">
<label for="booking_date">日期</label>
<input type="text" id="booking_date" name="date" readonly>
</div>
<div class="mrb-form-group">
<label for="booking_start_time">开始时间</label>
<select id="booking_start_time" name="start_time_select">
<!-- 通过JS动态生成时间选项 -->
</select>
</div>
<div class="mrb-form-group">
<label for="booking_end_time">结束时间</label>
<select id="booking_end_time" name="end_time_select">
<!-- 通过JS动态生成时间选项 -->
</select>
</div>
</div>
<div class="mrb-form-group">
<label for="booking_attendees">参会人数 *</label>
<input type="number" id="booking_attendees" name="attendees" min="1" value="1" required>
<span id="booking_capacity_info" class="mrb-hint"></span>
</div>
<div class="mrb-form-group">
<label>预订资源</label>
<div id="mrb-booking-resources">
<!-- 通过JS动态生成资源选项 -->
</div>
</div>
<div class="mrb-form-group">
<label for="booking_recurring">重复预订</label>
<select id="booking_recurring" name="recurring">
<option value="none">不重复</option>
<option value="daily">每天</option>
<option value="weekly">每周</option>
<option value="monthly">每月</option>
</select>
<div id="mrb-recurring-options" style="display:none;">
<label for="booking_recurring_count">重复次数:</label>
<input type="number" id="booking_recurring_count" name="recurring_count" min="1" max="12" value="1">
</div>
</div>
<div class="mrb-form-actions">
<button type="submit" class="button button-primary">确认预订</button>
<button type="button" class="button mrb-modal-cancel">取消</button>
</div>
</form>
</div>
</div>
</div>
## 第五部分:AJAX处理与数据交互
### 5.1 AJAX处理类
在`class-ajax.php`中,处理所有前端和后端的AJAX请求:
class MRB_Ajax {
public function __construct() {
// 前端AJAX动作
add_action('wp_ajax_mrb_get_rooms', array($this, 'get_rooms'));
add_action('wp_ajax_nopriv_mrb_get_rooms', array($this, 'get_rooms'));
add_action('wp_ajax_mrb_get_resources', array($this, 'get_resources'));
add_action('wp_ajax_nopriv_mrb_get_resources', array($this, 'get_resources'));
add_action('wp_ajax_mrb_get_bookings', array($this, 'get_bookings'));
add_action('wp_ajax_nopriv_mrb_get_bookings', array($this, 'get_bookings'));
add_action('wp_ajax_mrb_create_booking', array($this, 'create_booking'));
add_action('wp_ajax_mrb_cancel_booking', array($this, 'cancel_booking'));
// 后台AJAX动作
add_action('wp_ajax_mrb_admin_save_room', array($this, 'admin_save_room'));
add_action('wp_ajax_mrb_admin_delete_room', array($this, 'admin_delete_room'));
add_action('wp_ajax_mrb_admin_get_rooms', array($this, 'admin_get_rooms'));
}
/**
* 获取会议室列表
*/
public function get_rooms() {
// 验证nonce
if (!check_ajax_referer('mrb_frontend_nonce', 'nonce', false)) {
wp_die('安全验证失败', 403);
}
global $wpdb;
$table_name = $wpdb->prefix . 'meeting_rooms';
// 获取筛选参数
$capacity = isset($_POST['capacity']) ? intval($_POST['capacity']) : 0;
$equipment = isset($_POST['equipment']) ? $_POST['equipment'] : array();
// 构建查询
$where = array('status = 1');
$params = array();
if ($capacity > 0) {
$where[] = 'capacity >= %d';
$params[] = $capacity;
}
$where_sql = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
// 如果有设备筛选,需要进一步处理
if (!empty($equipment) && is_array($equipment)) {
$query = "SELECT * FROM $table_name $where_sql";
if (!empty($params)) {
$query = $wpdb->prepare($query, $params);
}
$rooms = $wpdb->get_results($query);
// 过滤设备
$filtered_rooms = array();
foreach ($rooms as $room) {
$amenities = explode(',', $room->amenities);
$amenities = array_map('trim', $amenities);
$match = true;
foreach ($equipment as $eq) {
if (!in_array($eq, $amenities)) {
$match = false;
break;
}
}
if ($match) {
$filtered_rooms[] = $room;
}
}
$rooms = $filtered_rooms;
} else {
$query = "SELECT * FROM $table_name $where_sql ORDER BY name ASC";
if (!empty($params)) {
$query = $wpdb->prepare($query, $params);
}
$rooms = $wpdb->get_results($query);
}
// 格式化返回数据
$formatted_rooms = array();
foreach ($rooms as $room) {
$formatted_rooms[] = array(
'id' => $room->id,
'name' => $room->name,
'capacity' => $room->capacity,
'location' => $room->location,
'amenities' => $room->amenities,
'description' => $room->description
);
}
wp_send_json_success($formatted_rooms);
}
/**
* 获取资源列表
*/
public function get_resources() {
if (!check_ajax_referer('mrb_frontend_nonce', 'nonce', false)) {
wp_die('安全验证失败', 403);
}
global $wpdb;
$table_name = $wpdb->prefix . 'resources';
$resources = $wpdb->get_results(
"SELECT * FROM $table_name WHERE status = 1 ORDER BY type, name ASC"
);
$formatted_resources = array();
foreach ($resources as $resource) {
$formatted_resources[] = array(
'id' => $resource->id,
'name' => $resource->name,
'type' => $resource->type,
'description' => $resource->description,
'quantity' => $resource->quantity,
'available' => $resource->quantity // 简化处理,实际需要计算已预订数量
);
}
wp_send_json_success($formatted_resources);
}
/**
* 获取预订数据
*/
public function get_bookings() {
if (!check_ajax_referer('mrb_frontend_nonce', 'nonce', false)) {
wp_die('安全验证失败', 403);
}
global $wpdb;
$start = isset($_POST['start']) ? sanitize_text_field($_POST['start']) : date('Y-m-d');
$end = isset($_POST['end']) ? sanitize_text_field($_POST['end']) : date('Y-m-d', strtotime('+1 month'));
$room_id = isset($_POST['room_id']) ? intval($_POST['room_id']) : 0;
$bookings_table = $wpdb->prefix . 'bookings';
$rooms_table = $wpdb->prefix . 'meeting_rooms';
// 构建查询
$where = array("b.start_time >= %s", "b.end_time <= %s", "b.status != 'cancelled'");
$params = array($start, $end);
if ($room_id > 0) {
$where[] = "b.room_id = %d";
$params[] = $room_id;
}
$where_sql = implode(' AND ', $where);
$query = $wpdb->prepare(
"SELECT b.*, r.name as room_name, r.color as room_color
FROM $bookings_table b
LEFT JOIN $rooms_table r ON b.room_id = r.id
WHERE $where_sql
ORDER BY b.start_time ASC",
$params
);
$bookings = $wpdb->get_results($query);
// 格式化FullCalendar事件数据
$events = array();
$current_user_id = get_current_user_id();
foreach ($bookings as $booking) {
$color = '#3788d8'; // 默认蓝色
if ($booking->user_id == $current_user_id) {
$color = '#28a745'; // 用户自己的预订,绿色
} elseif ($booking->status == 'pending') {
$color = '#ffc107'; // 待审核,黄色
}
$events[] = array(
'id' => $booking->id,
'title' => $booking->title . ' (' . $booking->room_name . ')',
'start' => $booking->start_time,
'end' => $booking->end_time,
'color' => $color,
'extendedProps' => array(
'room_id' => $booking->room_id,
'room_name' => $booking->room_name,
'description' => $booking->description,
'attendees' => $booking->attendees,
'status' => $booking->status,
'user_id' => $booking->user_id
)
);
}
wp_send_json_success($events);
}
/**
* 创建新预订
*/
public function create_booking() {
if (!check_ajax_referer('mrb_frontend_nonce', 'nonce', false)) {
wp_die('安全验证失败', 403);
}
// 验证用户登录
if (!is_user_logged_in()) {
wp_send_json_error('请先登录');
}
// 验证输入数据
$required_fields = array('room_id', 'title', 'start_time', 'end_time', 'attendees');
foreach ($required_fields as $field) {
if (empty($_POST[$field])) {
wp_send_json_error('缺少必要字段: ' . $field);
}
}
global $wpdb;
$room_id = intval($_POST['room_id']);
$user_id = get_current_user_id();
$title = sanitize_text_field($_POST['title']);
$description = isset($_POST['description']) ? sanitize_textarea_field($_POST['description']) : '';
$start_time = sanitize_text_field($_POST['start_time']);
$end_time = sanitize_text_field($_POST['end_time']);
$attendees = intval($_POST['attendees']);
$resources = isset($_POST['resources']) ? $_POST['resources'] : array();
// 检查会议室是否存在且可用
$room = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}meeting_rooms WHERE id = %d AND status = 1",
$room_id
));
if (!$room) {
wp_send_json_error('会议室不存在或不可用');
}
// 检查容量
if ($attendees > $room->capacity) {
wp_send_json_error('参会人数超过会议室容量');
}
// 检查时间冲突
$conflict = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}bookings
WHERE room_id = %d
AND status != 'cancelled'
AND (
(start_time < %s AND end_time > %s) OR
(start_time >= %s AND start_time < %s) OR
(end_time > %s AND end_time <= %s)
)",
$room_id,
$end_time, $start_time,
$start_time, $end_time,
$start_time, $end_time
));
if ($conflict > 0) {
wp_send_json_error('该时间段已被预订');
}
// 检查资源可用性
$resource_errors = array();
if (is_array($resources) && !empty($resources)) {
foreach ($resources as $resource_id => $quantity) {
$resource_id = intval($resource_id);
$quantity = intval($quantity);
if ($quantity > 0) {
// 检查资源是否存在
$resource = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}resources WHERE id = %d AND status = 1",
$resource_id
));
if (!$resource) {
$resource_errors[] = "资源ID {$resource_id} 不存在";
continue


