文章目录[隐藏]
详细教程:为网站开发活动报名与电子票务管理工具——通过WordPress代码二次开发实现常用互联网小工具功能
引言:为什么需要自定义活动报名与电子票务系统
在当今数字化时代,活动组织者面临着管理参与者、票务销售和活动执行的复杂挑战。虽然市场上有许多现成的活动管理解决方案,但它们往往缺乏灵活性、定制性,或者价格昂贵。对于许多中小型组织、社区团体或个人活动策划者来说,一个能够完全控制、成本可控且功能定制的解决方案显得尤为重要。
WordPress作为全球最流行的内容管理系统,拥有强大的扩展性和灵活性。通过代码二次开发,我们可以在WordPress平台上构建一个功能完善、符合特定需求的活动报名与电子票务管理工具。本教程将详细指导您如何从零开始,通过WordPress代码二次开发实现这一目标。
第一部分:准备工作与环境搭建
1.1 开发环境配置
在开始开发之前,我们需要搭建一个合适的开发环境:
- 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel等工具搭建本地WordPress环境
- 代码编辑器:选择Visual Studio Code、PHPStorm或Sublime Text等专业编辑器
- 版本控制:初始化Git仓库,便于代码管理和版本控制
- 浏览器开发者工具:熟悉Chrome或Firefox的开发者工具,用于调试前端代码
1.2 WordPress安装与基础配置
- 下载最新版WordPress并安装到本地环境
- 配置数据库连接信息
- 设置管理员账户和网站基本信息
-
安装必要的开发插件:
- Query Monitor:用于调试数据库查询和性能
- Show Current Template:显示当前使用的模板文件
- Advanced Custom Fields:用于创建自定义字段(可选)
1.3 创建自定义插件结构
为了避免主题更新导致功能丢失,我们将创建一个独立插件来实现所有功能。在wp-content/plugins/目录下创建新文件夹event-ticket-manager,并创建以下基础文件结构:
event-ticket-manager/
├── event-ticket-manager.php # 主插件文件
├── includes/ # 核心功能文件目录
│ ├── class-database.php # 数据库操作类
│ ├── class-event.php # 活动管理类
│ ├── class-ticket.php # 票务管理类
│ ├── class-payment.php # 支付处理类
│ └── class-email.php # 邮件通知类
├── admin/ # 后台管理文件
│ ├── css/ # 后台样式
│ ├── js/ # 后台脚本
│ └── pages/ # 后台页面
├── public/ # 前端文件
│ ├── css/ # 前端样式
│ ├── js/ # 前端脚本
│ └── templates/ # 前端模板
├── assets/ # 静态资源
└── vendor/ # 第三方库(如果需要)
第二部分:数据库设计与数据模型
2.1 设计数据库表结构
活动报名与电子票务系统需要多个数据表来存储不同类型的信息。我们将在插件激活时创建这些表:
// includes/class-database.php
class ETM_Database {
public function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// 活动表
$events_table = $wpdb->prefix . 'etm_events';
$events_sql = "CREATE TABLE IF NOT EXISTS $events_table (
id int(11) NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
description text,
start_date datetime NOT NULL,
end_date datetime NOT NULL,
location varchar(500),
max_participants int(11) DEFAULT 0,
current_participants int(11) DEFAULT 0,
status varchar(50) DEFAULT 'draft',
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
// 票务类型表
$ticket_types_table = $wpdb->prefix . 'etm_ticket_types';
$ticket_types_sql = "CREATE TABLE IF NOT EXISTS $ticket_types_table (
id int(11) NOT NULL AUTO_INCREMENT,
event_id int(11) NOT NULL,
name varchar(255) NOT NULL,
description text,
price decimal(10,2) DEFAULT 0.00,
quantity int(11) NOT NULL,
sold int(11) DEFAULT 0,
sale_start datetime,
sale_end datetime,
status varchar(50) DEFAULT 'active',
PRIMARY KEY (id),
FOREIGN KEY (event_id) REFERENCES $events_table(id) ON DELETE CASCADE
) $charset_collate;";
// 订单表
$orders_table = $wpdb->prefix . 'etm_orders';
$orders_sql = "CREATE TABLE IF NOT EXISTS $orders_table (
id int(11) NOT NULL AUTO_INCREMENT,
order_number varchar(100) NOT NULL UNIQUE,
event_id int(11) NOT NULL,
customer_name varchar(255) NOT NULL,
customer_email varchar(255) NOT NULL,
customer_phone varchar(50),
total_amount decimal(10,2) NOT NULL,
payment_status varchar(50) DEFAULT 'pending',
payment_method varchar(100),
payment_id varchar(255),
order_status varchar(50) DEFAULT 'pending',
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
FOREIGN KEY (event_id) REFERENCES $events_table(id)
) $charset_collate;";
// 订单详情表
$order_items_table = $wpdb->prefix . 'etm_order_items';
$order_items_sql = "CREATE TABLE IF NOT EXISTS $order_items_table (
id int(11) NOT NULL AUTO_INCREMENT,
order_id int(11) NOT NULL,
ticket_type_id int(11) NOT NULL,
quantity int(11) NOT NULL,
unit_price decimal(10,2) NOT NULL,
subtotal decimal(10,2) NOT NULL,
ticket_code varchar(100) NOT NULL UNIQUE,
checked_in tinyint(1) DEFAULT 0,
checked_in_at datetime,
PRIMARY KEY (id),
FOREIGN KEY (order_id) REFERENCES $orders_table(id) ON DELETE CASCADE,
FOREIGN KEY (ticket_type_id) REFERENCES $ticket_types_table(id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($events_sql);
dbDelta($ticket_types_sql);
dbDelta($orders_sql);
dbDelta($order_items_sql);
}
}
2.2 实现数据模型类
为每个主要实体创建对应的PHP类,封装数据库操作:
// includes/class-event.php
class ETM_Event {
private $db;
private $table_name;
public function __construct() {
global $wpdb;
$this->db = $wpdb;
$this->table_name = $wpdb->prefix . 'etm_events';
}
public function create($data) {
$defaults = array(
'status' => 'draft',
'current_participants' => 0,
'created_at' => current_time('mysql'),
'updated_at' => current_time('mysql')
);
$data = wp_parse_args($data, $defaults);
$result = $this->db->insert(
$this->table_name,
$data
);
if ($result) {
return $this->db->insert_id;
}
return false;
}
public function get($id) {
$query = $this->db->prepare(
"SELECT * FROM $this->table_name WHERE id = %d",
$id
);
return $this->db->get_row($query);
}
public function update($id, $data) {
$data['updated_at'] = current_time('mysql');
$where = array('id' => $id);
return $this->db->update(
$this->table_name,
$data,
$where
);
}
public function delete($id) {
return $this->db->delete(
$this->table_name,
array('id' => $id)
);
}
public function get_all($args = array()) {
$defaults = array(
'status' => 'publish',
'orderby' => 'start_date',
'order' => 'ASC',
'limit' => 10,
'offset' => 0
);
$args = wp_parse_args($args, $defaults);
$where = array();
if (!empty($args['status'])) {
$where[] = $this->db->prepare("status = %s", $args['status']);
}
if (!empty($args['search'])) {
$where[] = $this->db->prepare("(title LIKE %s OR description LIKE %s)",
'%' . $args['search'] . '%',
'%' . $args['search'] . '%'
);
}
$where_clause = '';
if (!empty($where)) {
$where_clause = 'WHERE ' . implode(' AND ', $where);
}
$orderby = esc_sql($args['orderby']);
$order = esc_sql($args['order']);
$query = "SELECT * FROM $this->table_name
$where_clause
ORDER BY $orderby $order
LIMIT %d OFFSET %d";
$query = $this->db->prepare(
$query,
$args['limit'],
$args['offset']
);
return $this->db->get_results($query);
}
public function get_upcoming($limit = 5) {
$current_time = current_time('mysql');
$query = $this->db->prepare(
"SELECT * FROM $this->table_name
WHERE status = 'publish'
AND start_date > %s
ORDER BY start_date ASC
LIMIT %d",
$current_time,
$limit
);
return $this->db->get_results($query);
}
}
第三部分:后台管理界面开发
3.1 创建管理菜单和页面
在WordPress后台添加自定义管理菜单,用于管理活动、票务和订单:
// admin/class-admin-menu.php
class ETM_Admin_Menu {
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',
'etm-events',
array($this, 'render_events_page'),
'dashicons-tickets-alt',
30
);
// 子菜单
add_submenu_page(
'etm-events',
'活动管理',
'所有活动',
'manage_options',
'etm-events',
array($this, 'render_events_page')
);
add_submenu_page(
'etm-events',
'添加新活动',
'添加活动',
'manage_options',
'etm-add-event',
array($this, 'render_add_event_page')
);
add_submenu_page(
'etm-events',
'票务类型',
'票务类型',
'manage_options',
'etm-ticket-types',
array($this, 'render_ticket_types_page')
);
add_submenu_page(
'etm-events',
'订单管理',
'订单',
'manage_options',
'etm-orders',
array($this, 'render_orders_page')
);
add_submenu_page(
'etm-events',
'签到管理',
'签到',
'manage_options',
'etm-checkin',
array($this, 'render_checkin_page')
);
add_submenu_page(
'etm-events',
'报表统计',
'报表',
'manage_options',
'etm-reports',
array($this, 'render_reports_page')
);
add_submenu_page(
'etm-events',
'系统设置',
'设置',
'manage_options',
'etm-settings',
array($this, 'render_settings_page')
);
}
public function enqueue_admin_scripts($hook) {
// 只在插件页面加载脚本和样式
if (strpos($hook, 'etm-') === false) {
return;
}
wp_enqueue_style(
'etm-admin-style',
plugins_url('admin/css/admin-style.css', dirname(__FILE__)),
array(),
'1.0.0'
);
wp_enqueue_script(
'etm-admin-script',
plugins_url('admin/js/admin-script.js', dirname(__FILE__)),
array('jquery', 'jquery-ui-datepicker'),
'1.0.0',
true
);
// 本地化脚本,传递数据到JavaScript
wp_localize_script('etm-admin-script', 'etm_admin', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('etm_admin_nonce'),
'confirm_delete' => '确定要删除这个项目吗?此操作不可撤销。'
));
// 加载jQuery UI日期选择器样式
wp_enqueue_style('jquery-ui-style', 'https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css');
}
public function render_events_page() {
include plugin_dir_path(__FILE__) . 'pages/events.php';
}
public function render_add_event_page() {
include plugin_dir_path(__FILE__) . 'pages/add-event.php';
}
// 其他页面渲染方法...
}
3.2 实现活动管理界面
创建活动列表和编辑界面:
// admin/pages/events.php
<div class="wrap etm-admin-wrap">
<h1 class="wp-heading-inline">活动管理</h1>
<a href="<?php echo admin_url('admin.php?page=etm-add-event'); ?>" class="page-title-action">添加新活动</a>
<hr class="wp-header-end">
<div class="etm-admin-container">
<div class="etm-admin-header">
<div class="tablenav top">
<div class="alignleft actions">
<select name="filter_status" id="filter-status">
<option value="">所有状态</option>
<option value="draft">草稿</option>
<option value="publish">已发布</option>
<option value="cancelled">已取消</option>
<option value="completed">已结束</option>
</select>
<input type="text" name="search" id="event-search" placeholder="搜索活动..." value="<?php echo isset($_GET['s']) ? esc_attr($_GET['s']) : ''; ?>">
<button type="button" class="button" id="search-events">搜索</button>
</div>
<div class="tablenav-pages">
<span class="displaying-num"><?php echo $total_events; ?> 个活动</span>
<span class="pagination-links">
<?php echo $pagination; ?>
</span>
</div>
</div>
</div>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th scope="col" class="column-primary">活动名称</th>
<th scope="col">日期</th>
<th scope="col">地点</th>
<th scope="col">参与人数</th>
<th scope="col">状态</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
<?php if (!empty($events)) : ?>
<?php foreach ($events as $event) : ?>
<tr>
<td class="column-primary">
<strong><?php echo esc_html($event->title); ?></strong>
<div class="row-actions">
<span class="edit">
<a href="<?php echo admin_url('admin.php?page=etm-add-event&id=' . $event->id); ?>">编辑</a> |
</span>
<span class="view">
<a href="<?php echo get_permalink($event->post_id); ?>" target="_blank">查看</a> |
</span>
<span class="duplicate">
<a href="#" class="duplicate-event" data-id="<?php echo $event->id; ?>">复制</a> |
</span>
<span class="trash">
<a href="#" class="delete-event" data-id="<?php echo $event->id; ?>">删除</a>
</span>
</div>
<button type="button" class="toggle-row">
<span class="screen-reader-text">显示详情</span>
</button>
</td>
<td>
<?php
$start_date = date_i18n('Y年m月d日 H:i', strtotime($event->start_date));
$end_date = date_i18n('Y年m月d日 H:i', strtotime($event->end_date));
echo $start_date . '<br>至<br>' . $end_date;
?>
</td>
<td><?php echo esc_html($event->location); ?></td>
<td>
<?php echo $event->max_participants > 0 ? $event->max_participants : '不限'; ?>
</td>
<td>
<span class="event-status status-<?php echo esc_attr($event->status); ?>">
<?php
$status_labels = array(
'draft' => '草稿',
'publish' => '已发布',
'cancelled' => '已取消',
'completed' => '已结束'
);
echo isset($status_labels[$event->status]) ? $status_labels[$event->status] : $event->status;
?>
</span>
</td>
<td>
<div class="action-buttons">
<a href="<?php echo admin_url('admin.php?page=etm-ticket-types&event_id=' . $event->id); ?>" class="button button-small">管理票务</a>
<a href="<?php echo admin_url('admin.php?page=etm-orders&event_id=' . $event->id); ?>" class="button button-small">查看订单</a>
<?php if ($event->status == 'publish') : ?>
<a href="#" class="button button-small button-primary copy-registration-link" data-link="<?php echo home_url('/event-registration/?event_id=' . $event->id); ?>">复制报名链接</a>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php else : ?>
<tr>
<td colspan="6" class="no-items">暂无活动,<a href="<?php echo admin_url('admin.php?page=etm-add-event'); ?>">创建第一个活动</a></td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
## 第四部分:前端报名与票务购买功能
### 4.1 创建前端报名页面模板
// public/templates/registration-form.php
<div class="etm-registration-container">
<div class="etm-registration-header">
<h1><?php echo esc_html($event->title); ?></h1>
<div class="event-meta">
<div class="meta-item">
<span class="dashicons dashicons-calendar"></span>
<span class="meta-label">时间:</span>
<span class="meta-value"><?php echo date_i18n('Y年m月d日 H:i', strtotime($event->start_date)); ?> - <?php echo date_i18n('Y年m月d日 H:i', strtotime($event->end_date)); ?></span>
</div>
<div class="meta-item">
<span class="dashicons dashicons-location"></span>
<span class="meta-label">地点:</span>
<span class="meta-value"><?php echo esc_html($event->location); ?></span>
</div>
<div class="meta-item">
<span class="dashicons dashicons-groups"></span>
<span class="meta-label">已报名:</span>
<span class="meta-value"><?php echo $event->current_participants; ?>/<?php echo $event->max_participants > 0 ? $event->max_participants : '不限'; ?></span>
</div>
</div>
</div>
<?php if ($event->status !== 'publish') : ?>
<div class="etm-alert etm-alert-warning">
此活动当前不可报名。
</div>
<?php elseif ($event->max_participants > 0 && $event->current_participants >= $event->max_participants) : ?>
<div class="etm-alert etm-alert-warning">
抱歉,此活动报名人数已满。
</div>
<?php else : ?>
<div class="etm-registration-content">
<div class="etm-event-description">
<h2>活动介绍</h2>
<div class="description-content">
<?php echo wpautop($event->description); ?>
</div>
</div>
<div class="etm-registration-form-wrapper">
<h2>报名信息</h2>
<form id="etm-registration-form" method="post" action="<?php echo admin_url('admin-ajax.php'); ?>">
<?php wp_nonce_field('etm_registration_nonce', 'registration_nonce'); ?>
<input type="hidden" name="action" value="etm_process_registration">
<input type="hidden" name="event_id" value="<?php echo $event->id; ?>">
<div class="form-section">
<h3>选择票种</h3>
<div class="ticket-selection">
<?php if (!empty($ticket_types)) : ?>
<?php foreach ($ticket_types as $ticket) : ?>
<?php if ($ticket->status == 'active' && $ticket->sold < $ticket->quantity) : ?>
<div class="ticket-option">
<div class="ticket-header">
<input type="radio"
name="ticket_type_id"
id="ticket_<?php echo $ticket->id; ?>"
value="<?php echo $ticket->id; ?>"
data-price="<?php echo $ticket->price; ?>"
required>
<label for="ticket_<?php echo $ticket->id; ?>">
<span class="ticket-name"><?php echo esc_html($ticket->name); ?></span>
<span class="ticket-price">
<?php if ($ticket->price > 0) : ?>
¥<?php echo number_format($ticket->price, 2); ?>
<?php else : ?>
免费
<?php endif; ?>
</span>
</label>
</div>
<div class="ticket-details">
<p><?php echo esc_html($ticket->description); ?></p>
<div class="ticket-meta">
<span class="available">剩余:<?php echo $ticket->quantity - $ticket->sold; ?>张</span>
<?php if ($ticket->sale_start && $ticket->sale_end) : ?>
<span class="sale-period">
销售时间:<?php echo date_i18n('m月d日 H:i', strtotime($ticket->sale_start)); ?> - <?php echo date_i18n('m月d日 H:i', strtotime($ticket->sale_end)); ?>
</span>
<?php endif; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php endforeach; ?>
<?php else : ?>
<p class="no-tickets">暂无可用票种</p>
<?php endif; ?>
</div>
</div>
<div class="form-section">
<h3>购票数量</h3>
<div class="quantity-selection">
<div class="quantity-control">
<button type="button" class="quantity-minus" disabled>-</button>
<input type="number"
name="ticket_quantity"
id="ticket_quantity"
value="1"
min="1"
max="10"
class="quantity-input">
<button type="button" class="quantity-plus">+</button>
</div>
<div class="quantity-note">
<p>每人最多可购买10张票</p>
</div>
</div>
</div>
<div class="form-section">
<h3>填写个人信息</h3>
<div class="personal-info">
<div class="form-row">
<div class="form-group">
<label for="customer_name">姓名 <span class="required">*</span></label>
<input type="text"
id="customer_name"
name="customer_name"
required
placeholder="请输入真实姓名">
</div>
<div class="form-group">
<label for="customer_email">邮箱 <span class="required">*</span></label>
<input type="email"
id="customer_email"
name="customer_email"
required
placeholder="用于接收电子票和通知">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="customer_phone">手机号 <span class="required">*</span></label>
<input type="tel"
id="customer_phone"
name="customer_phone"
required
placeholder="用于活动通知">
</div>
<div class="form-group">
<label for="customer_company">公司/机构</label>
<input type="text"
id="customer_company"
name="customer_company"
placeholder="选填">
</div>
</div>
<div class="form-row">
<div class="form-group full-width">
<label for="special_requirements">特殊需求</label>
<textarea id="special_requirements"
name="special_requirements"
rows="3"
placeholder="如有饮食禁忌、特殊需求等请在此说明"></textarea>
</div>
</div>
</div>
</div>
<div class="form-section">
<h3>订单总览</h3>
<div class="order-summary">
<div class="summary-row">
<span class="summary-label">票种:</span>
<span class="summary-value" id="summary_ticket_name">请选择票种</span>
</div>
<div class="summary-row">
<span class="summary-label">单价:</span>
<span class="summary-value" id="summary_ticket_price">¥0.00</span>
</div>
<div class="summary-row">
<span class="summary-label">数量:</span>
<span class="summary-value" id="summary_quantity">1</span>
</div>
<div class="summary-row total">
<span class="summary-label">总计:</span>
<span class="summary-value" id="summary_total">¥0.00</span>
</div>
</div>
</div>
<div class="form-section">
<div class="form-submit">
<button type="submit" class="etm-submit-button" id="submit-registration">
<span class="button-text">提交报名</span>
<span class="loading-spinner" style="display:none;"></span>
</button>
<p class="form-notice">点击提交即表示您同意我们的<a href="<?php echo get_privacy_policy_url(); ?>" target="_blank">隐私政策</a>和<a href="<?php echo home_url('/terms'); ?>" target="_blank">服务条款</a></p>
</div>
</div>
</form>
</div>
</div>
<?php endif; ?>
</div>
<script>
jQuery(document).ready(function($) {
// 更新订单总览
function updateOrderSummary() {
var selectedTicket = $('input[name="ticket_type_id"]:checked');
var quantity = parseInt($('#ticket_quantity').val());
if (selectedTicket.length > 0) {
var ticketName = selectedTicket.siblings('label').find('.ticket-name').text();
var ticketPrice = parseFloat(selectedTicket.data('price'));
var total = ticketPrice * quantity;
$('#summary_ticket_name').text(ticketName);
$('#summary_ticket_price').text('¥' + ticketPrice.toFixed(2));
$('#summary_quantity').text(quantity);
$('#summary_total').text('¥' + total.toFixed(2));
}
}
// 票种选择事件
$('input[name="ticket_type_id"]').on('change', updateOrderSummary);
// 数量控制
$('.quantity-minus').on('click', function() {
var input = $('#ticket_quantity');
var currentVal = parseInt(input.val());
if (currentVal > 1) {
input.val(currentVal - 1);
updateOrderSummary();
}
updateQuantityButtons();
});
$('.quantity-plus').on('click', function() {
var input = $('#ticket_quantity');
var currentVal = parseInt(input.val());
var maxVal = parseInt(input.attr('max'));
if (currentVal < maxVal) {
input.val(currentVal + 1);
updateOrderSummary();
}
updateQuantityButtons();
});
$('#ticket_quantity').on('change', function() {
var val = parseInt($(this).val());
var maxVal = parseInt($(this).attr('max'));
var minVal = parseInt($(this).attr('min'));
if (val > maxVal) val = maxVal;
if (val < minVal) val = minVal;
$(this).val(val);
updateOrderSummary();
updateQuantityButtons();
});
function updateQuantityButtons() {
var currentVal = parseInt($('#ticket_quantity').val());
var maxVal = parseInt($('#ticket_quantity').attr('max'));
var minVal = parseInt($('#ticket_quantity').attr('min'));
$('.quantity-minus').prop('disabled', currentVal <= minVal);
$('.quantity-plus').prop('disabled', currentVal >= maxVal);
}
// 表单提交
$('#etm-registration-form').on('submit', function(e) {
e.preventDefault();
var form = $(this);
var submitButton = $('#submit-registration');
var buttonText = submitButton.find('.button-text');
var spinner = submitButton.find('.loading-spinner');
// 验证表单
if (!form[0].checkValidity()) {
form[0].reportValidity();
return;
}
// 显示加载状态
buttonText.text('处理中...');
spinner.show();
submitButton.prop('disabled', true);
// 发送AJAX请求
$.ajax({
url: form.attr('action'),
type: 'POST',
data: form.serialize(),
dataType: 'json',
success: function(response) {
if (response.success) {
// 跳转到支付页面或成功页面
if (response.data.payment_required) {
window.location.href = response.data.payment_url;
} else {
window.location.href = response.data.success_url;
}
} else {
// 显示错误信息
alert(response.data.message || '提交失败,请重试');
buttonText.text('提交报名');
spinner.hide();
submitButton.prop('disabled', false);
}
},
error: function() {
alert('网络错误,请重试');
buttonText.text('提交报名');
spinner.hide();
submitButton.prop('disabled', false);
}
});
});
// 初始化
updateQuantityButtons();
$('input[name="ticket_type_id"]:first').prop('checked', true).trigger('change');
});
</script>
### 4.2 实现AJAX表单处理
// includes/class-ajax-handler.php
class ETM_Ajax_Handler {
public function __construct() {
// 前端AJAX处理
add_action('wp_ajax_etm_process_registration', array($this, 'process_registration'));
add_action('wp_ajax_nopriv_etm_process_registration', array($this, 'process_registration'));
// 后台AJAX处理
add_action('wp_ajax_etm_admin_actions', array($this, 'handle_admin_actions'));
}
public function process_registration() {
// 验证nonce
if (!isset($_POST['registration_nonce']) ||
!wp_verify_nonce($_POST['registration_nonce'], 'etm_registration_nonce')) {
wp_send_json_error(array('message' => '安全验证失败'));
}
// 验证必填字段
$required_fields = array(
'event_id',
'ticket_type_id',
'ticket_quantity',
'customer_name',
'customer_email',
'customer_phone'
);
foreach ($required_fields as $field) {
if (empty($_POST[$field])) {
wp_send_json_error(array('message' => '请填写所有必填字段'));
}
}
$event_id = intval($_POST['event_id']);
$ticket_type_id = intval($_POST['ticket_type_id']);
$quantity = intval($_POST['ticket_quantity']);
// 验证活动状态
$event_model = new ETM_Event();
$event = $event_model->get($event_id);
if (!$event || $event->status !== 'publish') {
wp_send_json_error(array('message' => '活动不可用'));
}
// 验证票种
$ticket_model = new ETM_Ticket();
$ticket_type = $ticket_model->get_type($ticket_type_id);
if (!$ticket_type || $ticket_type->event_id != $event_id) {
wp_send_json_error(array('message' => '票种无效'));
}
if ($ticket_type->status !== 'active') {
wp_send_json_error(array('message' => '该票种已停售'));
}
// 检查库存
if ($ticket_type->sold + $quantity > $ticket_type->quantity) {
wp_send_json_error(array('message' => '票数不足,仅剩' . ($ticket_type->quantity - $ticket_type->sold) . '张'));
}
// 检查活动人数限制
if ($event->max_participants > 0 &&
$event->current_participants + $quantity > $event->max_participants) {
wp_send_json_error(array('
