文章目录[隐藏]
详细指南:开发网站内嵌的在线预约课程与虚拟教室系统
摘要
随着在线教育的蓬勃发展,越来越多的教育机构和个人教师需要在自己的网站上集成在线预约课程与虚拟教室功能。本指南将详细介绍如何通过WordPress程序的代码二次开发,实现一个功能完整的在线教育系统。我们将从系统设计、功能规划、技术实现到测试部署,全面解析开发过程,帮助您打造一个集课程预约、支付管理、虚拟教室和学员管理于一体的专业在线教育平台。
一、项目概述与需求分析
1.1 在线教育市场趋势
近年来,全球在线教育市场呈现爆发式增长。根据市场研究数据显示,到2025年,全球在线教育市场规模预计将达到3500亿美元。这一趋势推动了教育机构和个人教师对在线教学平台的需求,特别是能够与现有网站无缝集成的解决方案。
1.2 系统核心需求
我们的在线预约课程与虚拟教室系统需要满足以下核心需求:
- 课程展示与管理:教师可以创建、编辑和发布课程信息
- 在线预约系统:学员可以浏览课程并预约上课时间
- 支付集成:支持多种支付方式完成课程费用支付
- 虚拟教室:集成视频会议功能,支持实时互动教学
- 学员管理:教师可以管理学员信息、跟踪学习进度
- 通知系统:自动发送预约确认、上课提醒等通知
- 评价与反馈:学员可以对课程和教师进行评价
1.3 WordPress作为开发平台的优势
WordPress作为全球最流行的内容管理系统,具有以下优势:
- 庞大的插件生态系统和主题资源
- 成熟的用户管理和权限控制系统
- 强大的扩展性和自定义能力
- 活跃的开发者社区和丰富的文档资源
- 良好的SEO基础和移动端适配
二、系统架构设计
2.1 整体架构设计
我们的系统将采用分层架构设计:
前端展示层 (WordPress主题)
↓
业务逻辑层 (自定义插件)
↓
数据访问层 (WordPress数据库)
↓
第三方服务层 (支付、视频会议API)
2.2 数据库设计
我们需要扩展WordPress的数据库结构,添加以下自定义表:
- 课程表 (wp_courses):存储课程基本信息
- 课程安排表 (wp_course_schedules):存储课程的具体时间安排
- 预约记录表 (wp_bookings):存储学员的预约记录
- 支付记录表 (wp_payments):存储支付相关信息
- 虚拟教室表 (wp_virtual_classes):存储虚拟教室的会议信息
- 学员进度表 (wp_student_progress):跟踪学员学习进度
2.3 技术栈选择
- 后端:PHP 7.4+,WordPress 5.8+
- 前端:HTML5,CSS3,JavaScript (ES6+),jQuery
- 数据库:MySQL 5.7+ 或 MariaDB 10.3+
- 视频会议:集成Zoom API或Jitsi Meet
- 支付网关:Stripe,PayPal或国内支付平台API
- 实时通信:WebSocket或Socket.io for实时通知
三、开发环境搭建与准备工作
3.1 本地开发环境配置
-
安装本地服务器环境:
- 使用XAMPP、MAMP或Local by Flywheel
- 确保PHP版本≥7.4,MySQL≥5.7
-
安装WordPress:
# 下载最新版WordPress wget https://wordpress.org/latest.zip unzip latest.zip # 配置数据库连接信息 -
配置开发工具:
- 代码编辑器:VS Code或PHPStorm
- 版本控制:Git
- 调试工具:Query Monitor、Debug Bar插件
3.2 创建自定义插件结构
在wp-content/plugins/目录下创建插件文件夹结构:
online-education-system/
├── online-education-system.php # 主插件文件
├── includes/ # 核心功能文件
│ ├── class-database.php # 数据库操作类
│ ├── class-course-manager.php # 课程管理类
│ ├── class-booking-system.php # 预约系统类
│ ├── class-payment-handler.php # 支付处理类
│ ├── class-virtual-classroom.php # 虚拟教室类
│ └── class-notification.php # 通知系统类
├── admin/ # 后台管理文件
│ ├── css/
│ ├── js/
│ └── partials/
├── public/ # 前端文件
│ ├── css/
│ ├── js/
│ └── templates/
├── assets/ # 静态资源
└── vendor/ # 第三方库
3.3 插件主文件配置
创建主插件文件online-education-system.php:
<?php
/**
* Plugin Name: 在线教育系统
* Plugin URI: https://yourwebsite.com/
* Description: 在线预约课程与虚拟教室系统
* Version: 1.0.0
* Author: 您的名称
* License: GPL v2 or later
* Text Domain: online-education
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('OES_VERSION', '1.0.0');
define('OES_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('OES_PLUGIN_URL', plugin_dir_url(__FILE__));
// 自动加载类文件
spl_autoload_register(function ($class_name) {
if (strpos($class_name, 'OES_') === 0) {
$file = OES_PLUGIN_DIR . 'includes/class-' . strtolower(str_replace('_', '-', $class_name)) . '.php';
if (file_exists($file)) {
require_once $file;
}
}
});
// 初始化插件
function oes_init() {
// 检查依赖
if (!function_exists('get_bloginfo')) {
wp_die('此插件需要WordPress环境。');
}
// 初始化核心类
if (class_exists('OES_Database')) {
$database = new OES_Database();
$database->create_tables();
}
// 加载文本域
load_plugin_textdomain('online-education', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
add_action('plugins_loaded', 'oes_init');
// 激活插件时执行的操作
register_activation_hook(__FILE__, function() {
require_once OES_PLUGIN_DIR . 'includes/class-database.php';
$database = new OES_Database();
$database->create_tables();
// 创建必要的页面
oes_create_pages();
// 设置默认选项
update_option('oes_version', OES_VERSION);
update_option('oes_currency', 'CNY');
update_option('oes_timezone', 'Asia/Shanghai');
});
// 停用插件时执行的操作
register_deactivation_hook(__FILE__, function() {
// 清理临时数据
wp_clear_scheduled_hook('oes_daily_maintenance');
});
// 创建系统必要页面
function oes_create_pages() {
$pages = [
'course-list' => [
'title' => '课程列表',
'content' => '[oes_course_list]'
],
'booking' => [
'title' => '课程预约',
'content' => '[oes_booking_form]'
],
'student-dashboard' => [
'title' => '学员中心',
'content' => '[oes_student_dashboard]'
],
'teacher-dashboard' => [
'title' => '教师中心',
'content' => '[oes_teacher_dashboard]'
]
];
foreach ($pages as $slug => $page) {
if (!get_page_by_path($slug)) {
wp_insert_post([
'post_title' => $page['title'],
'post_content' => $page['content'],
'post_status' => 'publish',
'post_type' => 'page',
'post_name' => $slug
]);
}
}
}
四、核心功能模块开发
4.1 课程管理系统
4.1.1 数据库表结构
创建课程相关数据库表:
// includes/class-database.php
class OES_Database {
public function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// 课程表
$courses_table = $wpdb->prefix . 'oes_courses';
$sql = "CREATE TABLE IF NOT EXISTS $courses_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
title varchar(200) NOT NULL,
description text,
teacher_id bigint(20) NOT NULL,
category varchar(100),
price decimal(10,2) DEFAULT 0.00,
duration int DEFAULT 60,
max_students int DEFAULT 10,
thumbnail_url varchar(500),
status varchar(20) DEFAULT 'draft',
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY teacher_id (teacher_id),
KEY status (status)
) $charset_collate;";
// 课程安排表
$schedules_table = $wpdb->prefix . 'oes_course_schedules';
$sql .= "CREATE TABLE IF NOT EXISTS $schedules_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
course_id mediumint(9) NOT NULL,
start_time datetime NOT NULL,
end_time datetime NOT NULL,
recurring_type varchar(20) DEFAULT 'once',
recurring_days varchar(100),
max_capacity int DEFAULT 10,
booked_count int DEFAULT 0,
status varchar(20) DEFAULT 'available',
meeting_id varchar(200),
meeting_password varchar(100),
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY course_id (course_id),
KEY start_time (start_time),
KEY status (status)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
}
4.1.2 课程管理后台界面
创建课程管理后台界面:
// admin/partials/course-management.php
class OES_Course_Manager {
public function add_admin_menu() {
add_menu_page(
'课程管理',
'在线教育',
'manage_options',
'oes-courses',
[$this, 'render_course_list'],
'dashicons-welcome-learn-more',
30
);
add_submenu_page(
'oes-courses',
'添加新课程',
'添加课程',
'manage_options',
'oes-add-course',
[$this, 'render_add_course']
);
add_submenu_page(
'oes-courses',
'课程安排',
'课程安排',
'manage_options',
'oes-schedules',
[$this, 'render_schedule_management']
);
}
public function render_course_list() {
?>
<div class="wrap">
<h1 class="wp-heading-inline">课程管理</h1>
<a href="<?php echo admin_url('admin.php?page=oes-add-course'); ?>" class="page-title-action">添加新课程</a>
<div class="oes-course-list">
<?php
global $wpdb;
$courses_table = $wpdb->prefix . 'oes_courses';
$courses = $wpdb->get_results("SELECT * FROM $courses_table ORDER BY created_at DESC");
if ($courses) {
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr>
<th>ID</th>
<th>课程名称</th>
<th>教师</th>
<th>价格</th>
<th>状态</th>
<th>操作</th>
</tr></thead>';
echo '<tbody>';
foreach ($courses as $course) {
$teacher = get_userdata($course->teacher_id);
echo '<tr>
<td>' . $course->id . '</td>
<td><strong>' . esc_html($course->title) . '</strong></td>
<td>' . ($teacher ? $teacher->display_name : '未知') . '</td>
<td>' . number_format($course->price, 2) . ' ' . get_option('oes_currency', 'CNY') . '</td>
<td><span class="oes-status ' . $course->status . '">' . $this->get_status_text($course->status) . '</span></td>
<td>
<a href="' . admin_url('admin.php?page=oes-add-course&id=' . $course->id) . '">编辑</a> |
<a href="#" class="oes-delete-course" data-id="' . $course->id . '">删除</a>
</td>
</tr>';
}
echo '</tbody></table>';
} else {
echo '<div class="notice notice-info"><p>暂无课程,请添加第一个课程。</p></div>';
}
?>
</div>
</div>
<?php
}
public function render_add_course() {
$course_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
$course = null;
if ($course_id) {
global $wpdb;
$courses_table = $wpdb->prefix . 'oes_courses';
$course = $wpdb->get_row($wpdb->prepare("SELECT * FROM $courses_table WHERE id = %d", $course_id));
}
?>
<div class="wrap">
<h1><?php echo $course_id ? '编辑课程' : '添加新课程'; ?></h1>
<form method="post" action="" id="oes-course-form">
<?php wp_nonce_field('oes_save_course', 'oes_course_nonce'); ?>
<input type="hidden" name="course_id" value="<?php echo $course_id; ?>">
<table class="form-table">
<tr>
<th scope="row"><label for="course_title">课程标题</label></th>
<td><input type="text" id="course_title" name="course_title" class="regular-text"
value="<?php echo $course ? esc_attr($course->title) : ''; ?>" required></td>
</tr>
<tr>
<th scope="row"><label for="course_description">课程描述</label></th>
<td>
<?php
$description = $course ? $course->description : '';
wp_editor($description, 'course_description', [
'textarea_name' => 'course_description',
'media_buttons' => true,
'textarea_rows' => 10
]);
?>
</td>
</tr>
<tr>
<th scope="row"><label for="course_teacher">授课教师</label></th>
<td>
<select id="course_teacher" name="course_teacher" required>
<option value="">选择教师</option>
<?php
$teachers = get_users(['role__in' => ['administrator', 'teacher']]);
foreach ($teachers as $teacher) {
$selected = $course && $course->teacher_id == $teacher->ID ? 'selected' : '';
echo '<option value="' . $teacher->ID . '" ' . $selected . '>' . $teacher->display_name . '</option>';
}
?>
</select>
</td>
</tr>
<tr>
<th scope="row"><label for="course_price">课程价格</label></th>
<td>
<input type="number" id="course_price" name="course_price" step="0.01" min="0"
value="<?php echo $course ? number_format($course->price, 2) : '0.00'; ?>" required>
<span class="description"><?php echo get_option('oes_currency', 'CNY'); ?></span>
</td>
</tr>
<tr>
<th scope="row"><label for="course_duration">课程时长</label></th>
<td>
<input type="number" id="course_duration" name="course_duration" min="15" max="480"
value="<?php echo $course ? $course->duration : 60; ?>" required>
<span class="description">分钟</span>
</td>
</tr>
<tr>
<th scope="row"><label for="max_students">最大学员数</label></th>
<td>
<input type="number" id="max_students" name="max_students" min="1" max="100"
value="<?php echo $course ? $course->max_students : 10; ?>" required>
</td>
</tr>
<tr>
<th scope="row"><label for="course_status">课程状态</label></th>
<td>
<select id="course_status" name="course_status">
<option value="draft" <?php echo $course && $course->status == 'draft' ? 'selected' : ''; ?>>草稿</option>
<option value="published" <?php echo $course && $course->status == 'published' ? 'selected' : ''; ?>>已发布</option>
<option value="archived" <?php echo $course && $course->status == 'archived' ? 'selected' : ''; ?>>已归档</option>
</select>
</td>
</tr>
</table>
<p class="submit">
type="submit" name="submit_course" class="button button-primary" value="保存课程">
</p>
</form>
</div>
<?php
}
private function get_status_text($status) {
$statuses = [
'draft' => '草稿',
'published' => '已发布',
'archived' => '已归档'
];
return isset($statuses[$status]) ? $statuses[$status] : $status;
}
}
### 4.2 在线预约系统
#### 4.2.1 预约数据库表设计
// 在class-database.php中继续添加
class OES_Database {
public function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// 预约记录表
$bookings_table = $wpdb->prefix . 'oes_bookings';
$sql = "CREATE TABLE IF NOT EXISTS $bookings_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
schedule_id mediumint(9) NOT NULL,
student_id bigint(20) NOT NULL,
booking_time datetime DEFAULT CURRENT_TIMESTAMP,
status varchar(20) DEFAULT 'pending',
payment_status varchar(20) DEFAULT 'unpaid',
payment_id varchar(100),
notes text,
attended tinyint(1) DEFAULT 0,
rating int DEFAULT 0,
review text,
PRIMARY KEY (id),
KEY schedule_id (schedule_id),
KEY student_id (student_id),
KEY status (status),
KEY payment_status (payment_status)
) $charset_collate;";
// 支付记录表
$payments_table = $wpdb->prefix . 'oes_payments';
$sql .= "CREATE TABLE IF NOT EXISTS $payments_table (
id mediumint(9) NOT NULL AUTO_INCREMENT,
booking_id mediumint(9) NOT NULL,
amount decimal(10,2) NOT NULL,
currency varchar(10) DEFAULT 'CNY',
payment_method varchar(50),
transaction_id varchar(100),
status varchar(20) DEFAULT 'pending',
payment_time datetime,
refund_amount decimal(10,2) DEFAULT 0.00,
refund_time datetime,
PRIMARY KEY (id),
KEY booking_id (booking_id),
KEY transaction_id (transaction_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
}
#### 4.2.2 前端预约界面
创建前端预约短代码和模板:
// includes/class-booking-system.php
class OES_Booking_System {
public function register_shortcodes() {
add_shortcode('oes_course_list', [$this, 'render_course_list']);
add_shortcode('oes_booking_form', [$this, 'render_booking_form']);
add_shortcode('oes_student_dashboard', [$this, 'render_student_dashboard']);
}
public function render_course_list($atts) {
global $wpdb;
$atts = shortcode_atts([
'category' => '',
'teacher' => '',
'limit' => 10
], $atts);
$courses_table = $wpdb->prefix . 'oes_courses';
$where = "WHERE status = 'published'";
if (!empty($atts['category'])) {
$where .= $wpdb->prepare(" AND category = %s", $atts['category']);
}
if (!empty($atts['teacher'])) {
$where .= $wpdb->prepare(" AND teacher_id = %d", $atts['teacher']);
}
$courses = $wpdb->get_results("SELECT * FROM $courses_table $where ORDER BY created_at DESC LIMIT " . intval($atts['limit']));
ob_start();
?>
<div class="oes-course-list-container">
<div class="oes-course-filters">
<form method="get" class="oes-filter-form">
<input type="text" name="search" placeholder="搜索课程..." value="<?php echo isset($_GET['search']) ? esc_attr($_GET['search']) : ''; ?>">
<select name="category">
<option value="">所有分类</option>
<?php
$categories = $wpdb->get_col("SELECT DISTINCT category FROM $courses_table WHERE category IS NOT NULL AND category != ''");
foreach ($categories as $category) {
$selected = isset($_GET['category']) && $_GET['category'] == $category ? 'selected' : '';
echo '<option value="' . esc_attr($category) . '" ' . $selected . '>' . esc_html($category) . '</option>';
}
?>
</select>
<button type="submit" class="oes-filter-button">筛选</button>
</form>
</div>
<div class="oes-courses-grid">
<?php if ($courses): ?>
<?php foreach ($courses as $course): ?>
<?php
$teacher = get_userdata($course->teacher_id);
$schedules_table = $wpdb->prefix . 'oes_course_schedules';
$next_schedule = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $schedules_table WHERE course_id = %d AND start_time > NOW() AND status = 'available' ORDER BY start_time ASC LIMIT 1",
$course->id
));
?>
<div class="oes-course-card">
<div class="oes-course-thumbnail">
<?php if ($course->thumbnail_url): ?>
<img src="<?php echo esc_url($course->thumbnail_url); ?>" alt="<?php echo esc_attr($course->title); ?>">
<?php else: ?>
<div class="oes-course-thumbnail-placeholder">
<span class="dashicons dashicons-welcome-learn-more"></span>
</div>
<?php endif; ?>
</div>
<div class="oes-course-content">
<h3 class="oes-course-title"><?php echo esc_html($course->title); ?></h3>
<div class="oes-course-meta">
<span class="oes-course-teacher">
<span class="dashicons dashicons-businessperson"></span>
<?php echo $teacher ? esc_html($teacher->display_name) : '未知教师'; ?>
</span>
<span class="oes-course-duration">
<span class="dashicons dashicons-clock"></span>
<?php echo intval($course->duration); ?>分钟
</span>
<span class="oes-course-price">
<span class="dashicons dashicons-money-alt"></span>
<?php echo number_format($course->price, 2); ?> <?php echo get_option('oes_currency', 'CNY'); ?>
</span>
</div>
<div class="oes-course-excerpt">
<?php echo wp_trim_words(strip_tags($course->description), 30); ?>
</div>
<div class="oes-course-actions">
<?php if ($next_schedule): ?>
<a href="<?php echo add_query_arg(['course_id' => $course->id], get_permalink(get_page_by_path('booking'))); ?>"
class="oes-book-button button">
立即预约
</a>
<?php else: ?>
<button class="oes-book-button button" disabled>暂无排课</button>
<?php endif; ?>
<a href="<?php echo add_query_arg(['course_id' => $course->id], get_permalink()); ?>"
class="oes-details-button button">
查看详情
</a>
</div>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="oes-no-courses">
<p>暂无可用课程。</p>
</div>
<?php endif; ?>
</div>
</div>
<?php
return ob_get_clean();
}
public function render_booking_form() {
if (!is_user_logged_in()) {
return '<div class="oes-login-required"><p>请先<a href="' . wp_login_url(get_permalink()) . '">登录</a>以预约课程。</p></div>';
}
$course_id = isset($_GET['course_id']) ? intval($_GET['course_id']) : 0;
if (!$course_id) {
return '<div class="oes-booking-error"><p>请选择要预约的课程。</p></div>';
}
global $wpdb;
$courses_table = $wpdb->prefix . 'oes_courses';
$course = $wpdb->get_row($wpdb->prepare("SELECT * FROM $courses_table WHERE id = %d AND status = 'published'", $course_id));
if (!$course) {
return '<div class="oes-booking-error"><p>课程不存在或不可用。</p></div>';
}
$schedules_table = $wpdb->prefix . 'oes_course_schedules';
$schedules = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $schedules_table WHERE course_id = %d AND start_time > NOW() AND status = 'available' ORDER BY start_time ASC",
$course_id
));
ob_start();
?>
<div class="oes-booking-container">
<div class="oes-booking-header">
<h2>预约课程: <?php echo esc_html($course->title); ?></h2>
<div class="oes-course-info">
<p><strong>教师:</strong> <?php echo get_userdata($course->teacher_id)->display_name; ?></p>
<p><strong>价格:</strong> <?php echo number_format($course->price, 2); ?> <?php echo get_option('oes_currency', 'CNY'); ?></p>
<p><strong>时长:</strong> <?php echo intval($course->duration); ?> 分钟</p>
</div>
</div>
<?php if ($schedules): ?>
<form id="oes-booking-form" method="post" action="<?php echo admin_url('admin-ajax.php'); ?>">
<?php wp_nonce_field('oes_process_booking', 'oes_booking_nonce'); ?>
<input type="hidden" name="action" value="oes_process_booking">
<input type="hidden" name="course_id" value="<?php echo $course_id; ?>">
<div class="oes-schedule-selection">
<h3>选择上课时间</h3>
<div class="oes-schedules-list">
<?php foreach ($schedules as $schedule): ?>
<?php
$available_seats = $schedule->max_capacity - $schedule->booked_count;
$start_time = strtotime($schedule->start_time);
$end_time = strtotime($schedule->end_time);
?>
<div class="oes-schedule-item">
<input type="radio"
id="schedule_<?php echo $schedule->id; ?>"
name="schedule_id"
value="<?php echo $schedule->id; ?>"
<?php echo $available_seats <= 0 ? 'disabled' : ''; ?>
required>
<label for="schedule_<?php echo $schedule->id; ?>">
<div class="oes-schedule-date">
<?php echo date('Y年m月d日', $start_time); ?>
</div>
<div class="oes-schedule-time">
<?php echo date('H:i', $start_time); ?> - <?php echo date('H:i', $end_time); ?>
</div>
<div class="oes-schedule-availability">
<?php if ($available_seats > 0): ?>
<span class="oes-available">剩余 <?php echo $available_seats; ?> 个名额</span>
<?php else: ?>
<span class="oes-full">已满额</span>
<?php endif; ?>
</div>
</label>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="oes-booking-notes">
<h3>备注信息 (可选)</h3>
<textarea name="booking_notes" rows="3" placeholder="如有特殊要求,请在此说明..."></textarea>
</div>
<div class="oes-payment-method">
<h3>选择支付方式</h3>
<div class="oes-payment-options">
<label class="oes-payment-option">
<input type="radio" name="payment_method" value="alipay" checked>
<span class="oes-payment-icon">支付宝</span>
</label>
<label class="oes-payment-option">
<input type="radio" name="payment_method" value="wechat">
<span class="oes-payment-icon">微信支付</span>
</label>
<label class="oes-payment-option">
<input type="radio" name="payment_method" value="paypal">
<span class="oes-payment-icon">PayPal</span>
</label>
</div>
</div>
<div class="oes-booking-summary">
<h3>订单摘要</h3>
<table class="oes-summary-table">
<tr>
<td>课程费用:</td>
<td class="oes-price"><?php echo number_format($course->price, 2); ?> <?php echo get_option('oes_currency', 'CNY'); ?></td>
</tr>
<tr>
<td>服务费:</td>
<td class="oes-price">0.00 <?php echo get_option('oes_currency', 'CNY'); ?></td>
</tr>
<tr class="oes-total">
<td>总计:</td>
<td class="oes-price"><?php echo number_format($course->price, 2); ?> <?php echo get_option('oes_currency', 'CNY'); ?></td>
</tr>
</table>
</div>
<div class="oes-booking-submit">
<button type="submit" class="oes-submit-button button button-primary">
确认预约并支付
</button>
<p class="oes-terms-notice">
点击确认即表示您同意我们的<a href="<?php echo get_permalink(get_page_by_path('terms')); ?>">服务条款</a>
</p>
</div>
</form>
<div id="oes-booking-result" style="display:none;"></div>
<?php else: ?>
<div class="oes-no-schedules">
<p>该课程暂无可用时间安排。</p>
<a href="<?php echo get_permalink(get_page_by_path('course-list')); ?>" class="button">
返回课程列表
</a>
</div>
<?php endif; ?>
</div>
<?php
return ob_get_clean();
}
}
### 4.3 虚拟教室集成
#### 4.3.1 Zoom API集成
// includes/class-virtual-classroom.php
class OES_Virtual_Classroom {
private $zoom_api_key;
private $zoom_api_secret;
private $zoom_account_id;
public function __construct() {
$this->zoom_api_key = get_option('oes_zoom_api_key', '');
$this->zoom_api_secret = get_option('oes_zoom_api_secret', '');
$this->zoom_account_id = get_option('oes_zoom_account_id', '');
add_action('wp_ajax_oes_create_meeting', [$this, 'create_meeting']);
add_action('wp_ajax_oes_get_meeting_info', [$this, 'get_meeting_info']);
}
public function create_meeting_for_schedule($schedule_id) {
global $wpdb;
$schedules_table = $wpdb->prefix . 'oes_course_schedules';
$courses_table = $wpdb->prefix . 'oes_courses';
$schedule = $wpdb->get_row($wpdb->prepare(
"SELECT s.*, c.title as course_title, c.teacher_id
FROM $schedules_table s
LEFT JOIN $courses_table c ON s.course_id = c.id
WHERE s.id = %d",
$schedule_id
));
if (!$schedule) {
return false;
}
$teacher = get_userdata($schedule->teacher_id);
$start_time = new DateTime($schedule->start_time, new DateTimeZone('UTC'));
$meeting_data = [
'topic' => $schedule->course_title . ' - ' . $start_time->format('Y-m-d H:i'),
'type' => 2, // 预定会议
'start_time' => $start_time->format('Y-m-dTH:i:sZ'),
'duration' => intval($schedule->end_time) - intval($schedule->start_time),
'timezone' => get_option('timezone_string', 'Asia/Shanghai'),
'password' => wp_generate_password(6, false),
'settings' => [
'host_video' => true,
'participant_video' => true,
'join_before_host' => false,
'mute_upon_entry' => true,
'waiting_room' => true,
'auto_recording' => 'cloud'
]
];
$response = $this->call_zoom_api('users/me/meetings', 'POST', $meeting_data);
if ($response && isset($response['id'])) {
$wpdb->update(
$schedules_table,
[
'meeting_id' => $response['id'],
'meeting_password' => $response['password']
],
['id' => $schedule_id]
);
return [
'meeting_id' => $response['id'],
'join_url' => $response['join_url'],
'password' => $response['password']
];
}
return false;
}
private function call_zoom_api
