跳至内容

分类: 网站建设

针对小批量定制的WordPress柔性供应链插件开发详解

本文详细解析了面向小批量定制需求的WordPress柔性供应链插件开发。该插件旨在解决传统电商供应链难以适应多品种、快响应定制生产的痛点,通过供应商管理、订单智能分配、生产进度跟踪等核心模块,实现生产流程的动态优化与实时监控。文章涵盖插件架构设计、数据库规划、核心功能实现及与WooCommerce的集成,为开发者提供完整的代码方案与系统构建指南。

发表评论

WordPress文创产品柔性供应链插件的配置与使用教程

本文详细介绍了WordPress文创产品柔性供应链插件的配置与使用。该插件专为文创电商设计,支持多供应商协同管理、动态库存调整、智能订单分配及全流程进度跟踪。教程涵盖插件安装环境要求、基础配置步骤、库存与订单管理操作,并深入讲解高级功能、故障排除及优化建议,帮助用户高效搭建并维护柔性供应链系统,提升文创产品电商运营效率。

发表评论

实现网络传媒柔性供应的WordPress插件开发教程

本教程概述了开发实现网络传媒柔性供应的WordPress插件。柔性供应系统帮助传媒机构动态调整内容生产与资源分配,以灵活应对市场需求波动。教程将指导搭建插件基础结构,并开发核心功能模块:内容自动调度、资源管理以及多渠道发布。开发前需准备WordPress 5.0+、PHP 7.2+、MySQL 5.6+及相应的本地开发环境与代码编辑器。通过本教程,您将能够创建一个支持内容高效调度与分发的柔性供应插件。

发表评论

WordPress软件开发中集成柔性供应链的实战指南

本文探讨了在WordPress电商开发中集成柔性供应链系统的实战策略。面对供应链中断与需求波动等挑战,柔性供应链通过实时库存管理、动态定价引擎等核心组件,提升企业响应速度与运营效率。文章详细解析了微服务与事件驱动的集成架构,并提供了实时数据同步、智能缓存及错误监控的代码示例,帮助开发者构建更具弹性与竞争力的电商解决方案。

发表评论

WordPress柔性供应链系统搭建的详细教程

WordPress柔性供应链系统搭建详细教程 引言:为什么需要柔性供应链系统 在当今快速变化的市场环境中,企业需要一个能够灵活响应需求波动的供应链系统。传统的供应链管理系统往往僵化且成本高昂,而基于WordPress搭建的柔性供应链系统则提供了经济、灵活且可扩展的解决方案。本教程将指导您从零开始构建一个功能完善的柔性供应链系统。 系统架构设计 核心功能模块规划 我们的柔性供应链系统将包含以下核心模块: 供应商管理模块 库存管理模块 订单处理模块 物流跟踪模块 数据分析与报告模块 技术栈选择 WordPress 6.0+ WooCommerce插件 自定义PHP开发 MySQL数据库 REST API接口 环境准备与基础配置 WordPress安装与基础设置 首先确保您已安装最新版本的WordPress,并完成基本配置。建议使用支持PHP 7.4+和MySQL 5.7+的主机环境。 必要插件安装 在WordPress后台安装以下核心插件: WooCommerce - 电子商务基础 Advanced Custom Fields - 自定义字段管理 WP REST API - API接口支持 数据库设计与创建 自定义数据表结构 我们需要创建几个核心数据表来支持供应链系统: <?php /** * 创建供应链系统所需的数据表 * 这段代码应放在插件激活钩子中执行 */ function create_supply_chain_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 供应商表 $suppliers_table = $wpdb->prefix . 'supply_chain_suppliers'; $suppliers_sql = "CREATE TABLE IF NOT EXISTS $suppliers_table ( supplier_id INT(11) NOT NULL AUTO_INCREMENT, supplier_name VARCHAR(255) NOT NULL, contact_person VARCHAR(100), email VARCHAR(100), phone VARCHAR(50), address TEXT, rating DECIMAL(3,2) DEFAULT 0.00, lead_time_days INT(5) DEFAULT 7, is_active TINYINT(1) DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (supplier_id) ) $charset_collate;"; // 库存表 $inventory_table = $wpdb->prefix . 'supply_chain_inventory'; $inventory_sql = "CREATE TABLE IF NOT EXISTS $inventory_table ( inventory_id INT(11) NOT NULL AUTO_INCREMENT, product_id INT(11) NOT NULL, supplier_id INT(11), quantity INT(11) DEFAULT 0, reorder_level INT(11) DEFAULT 10, location VARCHAR(100), last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (inventory_id), KEY product_id (product_id), KEY supplier_id (supplier_id) ) $charset_collate;"; // 采购订单表 $purchase_orders_table = $wpdb->prefix . 'supply_chain_purchase_orders'; $purchase_orders_sql = "CREATE TABLE IF NOT EXISTS $purchase_orders_table ( order_id INT(11) NOT NULL AUTO_INCREMENT, supplier_id INT(11) NOT NULL, order_date DATE NOT NULL, expected_delivery DATE, status ENUM('pending', 'confirmed', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending', total_amount DECIMAL(10,2), notes TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($suppliers_sql); dbDelta($inventory_sql); dbDelta($purchase_orders_sql); } ?> 供应商管理模块开发 供应商自定义帖子类型 <?php /** * 注册供应商自定义帖子类型 */ function register_supplier_post_type() { $labels = array( 'name' => '供应商', 'singular_name' => '供应商', 'menu_name' => '供应链管理', 'add_new' => '添加供应商', 'add_new_item' => '添加新供应商', 'edit_item' => '编辑供应商', 'new_item' => '新供应商', 'view_item' => '查看供应商', 'search_items' => '搜索供应商', 'not_found' => '未找到供应商', 'not_found_in_trash' => '回收站中无供应商' ); $args = array( 'labels' => $labels, 'public' => false, 'publicly_queryable' => false, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => array('slug' => 'supplier'), 'capability_type' => 'post', 'has_archive' => false, 'hierarchical' => false, 'menu_position' => 30, 'menu_icon' => 'dashicons-truck', 'supports' => array('title', 'editor', 'custom-fields') ); register_post_type('supplier', $args); } add_action('init', 'register_supplier_post_type'); ?> 供应商管理界面 创建供应商管理后台界面,包含添加、编辑、删除和查看供应商详细信息的功能。 库存管理模块实现 实时库存监控 <?php /** * 库存管理类 * 处理库存相关操作 */ class InventoryManager { /** * 更新库存数量 * @param int $product_id 产品ID * @param int $quantity_change 数量变化(正数为增加,负数为减少) * @return bool 操作是否成功 */ public static function update_inventory($product_id, $quantity_change) { global $wpdb; $table_name = $wpdb->prefix . 'supply_chain_inventory'; // 检查库存记录是否存在 $existing = $wpdb->get_var($wpdb->prepare( "SELECT quantity FROM $table_name WHERE product_id = %d", $product_id )); if ($existing === null) { // 创建新的库存记录 $result = $wpdb->insert( $table_name, array( 'product_id' => $product_id, 'quantity' => max(0, $quantity_change), 'last_updated' => current_time('mysql') ), array('%d', '%d', '%s') ); } else { // 更新现有库存 $new_quantity = max(0, $existing + $quantity_change); $result = $wpdb->update( $table_name, array( 'quantity' => $new_quantity, 'last_updated' => current_time('mysql') ), array('product_id' => $product_id), array('%d', '%s'), array('%d') ); } // 检查是否需要重新订货 if ($result !== false) { self::check_reorder_level($product_id); } return $result !== false; } /** * 检查库存是否低于重新订货水平 * @param int $product_id 产品ID */ private static function check_reorder_level($product_id) { global $wpdb; $table_name = $wpdb->prefix . 'supply_chain_inventory'; $inventory = $wpdb->get_row($wpdb->prepare( "SELECT quantity, reorder_level FROM $table_name WHERE product_id = %d", $product_id )); if ($inventory && $inventory->quantity <= $inventory->reorder_level) { // 触发重新订货通知 self::send_reorder_alert($product_id, $inventory->quantity); } } /** * 发送重新订货警报 * @param int $product_id 产品ID * @param int $current_quantity 当前库存数量 */ private static function send_reorder_alert($product_id, $current_quantity) { // 获取产品信息 $product = wc_get_product($product_id); if ($product) { $admin_email = get_option('admin_email'); $subject = '库存警报:产品需要重新订货'; $message = sprintf( "产品:%s (ID: %d)n当前库存:%dn请及时联系供应商补充库存。", $product->get_name(), $product_id, $current_quantity ); wp_mail($admin_email, $subject, $message); } } } ?> 订单处理与物流集成 自动化订单处理 集成WooCommerce订单系统,实现订单自动处理和供应商分配。 物流跟踪API集成 <?php /** * 物流跟踪管理器 * 集成第三方物流API */ class LogisticsTracker { private $api_key; private $api_url = 'https://api.logistics.example.com/v1/'; public function __construct($api_key) { $this->api_key = $api_key; } /** * 创建物流跟踪 * @param array $shipment_data 货运数据 * @return array|bool API响应或false */ public function create_tracking($shipment_data) { $endpoint = $this->api_url . 'tracking/create'; $headers = array( 'Authorization' => 'Bearer ' . $this->api_key, 'Content-Type' => 'application/json' ); $body = wp_json_encode($shipment_data); $response = wp_remote_post($endpoint, array( 'headers' => $headers, 'body' => $body, 'timeout' => 30 )); if (is_wp_error($response)) { error_log('物流API错误: ' . $response->get_error_message()); return false; } $response_code = wp_remote_retrieve_response_code($response); $response_body = wp_remote_retrieve_body($response); if ($response_code === 200) { return json_decode($response_body, true); } else { error_log('物流API错误,状态码: ' . $response_code); return false; } } /** * 获取跟踪信息 * @param string $tracking_number 跟踪号码 * @return array|bool 跟踪信息或false */ public function get_tracking_info($tracking_number) { $endpoint = $this->api_url . 'tracking/' . urlencode($tracking_number); $headers = array( 'Authorization' => 'Bearer ' . $this->api_key ); $response = wp_remote_get($endpoint, array( 'headers' => $headers, 'timeout' => 15 )); if (is_wp_error($response)) { return false; } $response_code = wp_remote_retrieve_response_code($response); $response_body = wp_remote_retrieve_body($response); if ($response_code === 200) { return json_decode($response_body, true); } return false; } } ?> 数据分析与报告系统 供应链KPI计算 <?php /** * 供应链分析报告生成器 */ class SupplyChainAnalytics { /** * 计算关键绩效指标 * @param string $period 时间段:'daily', 'weekly', 'monthly' * @return array KPI数据 */ public static function calculate_kpis($period = 'monthly') { global $wpdb; $kpis = array(); // 计算订单履行率 $orders_table = $wpdb->prefix . 'posts'; $order_items_table = $wpdb->prefix . 'woocommerce_order_items'; $date_filter = self::get_date_filter($period); // 总订单数 $total_orders = $wpdb->get_var(" SELECT COUNT(*) FROM $orders_table WHERE post_type = 'shop_order' AND post_status IN ('wc-completed', 'wc-processing') AND post_date >= '$date_filter' "); // 已完成的订单数 $fulfilled_orders = $wpdb->get_var(" SELECT COUNT(*) FROM $orders_table WHERE post_type = 'shop_order' AND post_status = 'wc-completed' AND post_date >= '$date_filter' "); $kpis['order_fulfillment_rate'] = $total_orders > 0 ? round(($fulfilled_orders / $total_orders) * 100, 2) : 0; // 计算平均订单处理时间 $processing_time = $wpdb->get_var(" SELECT AVG(TIMESTAMPDIFF(HOUR, MIN(CASE WHEN meta_key = '_paid_date' THEN meta_value END), MAX(CASE WHEN meta_key = '_completed_date' THEN meta_value END) )) FROM $orders_table o LEFT JOIN {$wpdb->prefix}postmeta pm ON o.ID = pm.post_id WHERE o.post_type = 'shop_order' AND o.post_status = 'wc-completed' AND o.post_date >= '$date_filter' GROUP BY o.ID "); $kpis['avg_order_processing_hours'] = round($processing_time ?: 0, 1); // 库存周转率计算 $inventory_table = $wpdb->prefix . 'supply_chain_inventory'; $inventory_data = $wpdb->get_results(" SELECT product_id, quantity, last_updated FROM $inventory_table WHERE last_updated >= '$date_filter' "); // 这里可以添加更复杂的库存周转计算逻辑 $kpis['inventory_turnover'] = self::calculate_inventory_turnover($inventory_data); return $kpis; } /** * 根据时间段获取日期过滤器 */ private static function get_date_filter($period) { $date = new DateTime(); switch ($period) { case 'daily': $date->modify('-1 day'); break; case 'weekly': $date->modify('-1 week'); break; case 'monthly': default: $date->modify('-1 month'); break; } return $date->format('Y-m-d H:i:s'); } /** * 计算库存周转率 */ private static function calculate_inventory_turnover($inventory_data) { // 简化的库存周转计算 // 实际应用中可能需要更复杂的逻辑 if (empty($inventory_data)) { return 0; } $total_value = 0; $total_quantity = 0; foreach ($inventory_data as $item) { $product = wc_get_product($item->product_id); if ($product) { $total_value += $product->get_price() * $item->quantity; $total_quantity += $item->quantity; } } // 这里使用简化的周转率计算 // 实际应基于销售成本和平均库存价值 return $total_quantity > 0 ? round($total_value / $total_quantity, 2) : 0; } } ?> 系统优化与安全考虑 性能优化建议 使用WordPress对象缓存减少数据库查询 对大数据表添加适当索引 实现分页加载和延迟加载 使用CDN加速静态资源 安全措施实施 <?php /** * 供应链系统安全加固 */ class SupplyChainSecurity { /** * 验证用户权限 * @param string $capability 所需权限 * @return bool 是否有权限 */ public static function check_capability($capability = 'manage_woocommerce') { if (!current_user_can($capability)) { wp_die('您没有足够的权限访问此页面。'); return false; } return true; } /** * 数据输入清理 * @param mixed $data 输入数据 * @return mixed 清理后的数据 */ public static function sanitize_input($data) { if (is_array($data)) { foreach ($data as $key => $value) { $data[$key] = self::sanitize_input($value); } return $data; } // 移除危险标签和脚本 $data = wp_strip_all_tags($data); $data = esc_sql($data); return $data; } /** * 防止SQL注入 * @param string $query SQL查询 * @param array $params 参数数组 * @return string 安全的查询 */ public static function prepare_sql_query($query, $params = array()) { global $wpdb; if (empty($params)) { return $query; } return $wpdb->prepare($query, $params); } /** * 记录安全事件 * @param string $event 事件描述 * @param string $severity 严重程度:low, medium, high */ public static function log_security_event($event, $severity = 'medium') { $log_entry = sprintf( "[%s] [%s] %sn", current_time('mysql'), strtoupper($severity), $event ); $log_file = WP_CONTENT_DIR . '/supply-chain-security.log'; // 限制日志文件大小 if (file_exists($log_file) && filesize($log_file) > 10485760) { // 10MB $backup_file = $log_file . '.' . date('Y-m-d'); rename($log_file, $backup_file); } error_log($log_entry, 3, $log_file); } } ?> REST API接口开发 创建供应链API端点 <?php /** * 供应链系统REST API */ class SupplyChainAPI { public function register_routes() { register_rest_route('supply-chain/v1', '/inventory/(?P<id>d+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array($this, 'get_inventory'), 'permission_callback' => array($this, 'check_api_permission'), 'args' => array( 'id' => array( 'validate_callback' => function($param) { return is_numeric($param); } ), ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array($this, 'update_inventory'), 'permission_callback' => array($this, 'check_api_permission'), ), )); register_rest_route('supply-chain/v1', '/suppliers', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array($this, 'get_suppliers'), 'permission_callback' => array($this, 'check_api_permission'), )); register_rest_route('supply-chain/v1', '/analytics/kpi', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array($this, 'get_kpi_data'), 'permission_callback' => array($this, 'check_api_permission'), )); } /** * 获取库存信息 */ public function get_inventory($request) { $product_id = $request['id']; global $wpdb; $table_name = $wpdb->prefix . 'supply_chain_inventory'; $inventory = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE product_id = %d", $product_id )); if (!$inventory) { return new WP_Error('not_found', '库存记录不存在', array('status' => 404)); } return rest_ensure_response($inventory); } /** * 获取供应商列表 */ public function get_suppliers($request) { $args = array( 'post_type' => 'supplier', 'posts_per_page' => -1, 'post_status' => 'publish', ); if (isset($request['active_only']) && $request['active_only']) { $args['meta_query'] = array( array( 'key' => 'is_active', 'value' => '1', 'compare' => '=' ) ); } $suppliers = get_posts($args); $formatted_suppliers = array(); foreach ($suppliers as $supplier) { $formatted_suppliers[] = array( 'id' => $supplier->ID, 'name' => $supplier->post_title, 'contact' => get_field('contact_person', $supplier->ID), 'email' => get_field('email', $supplier->ID), 'rating' => get_field('rating', $supplier->ID), 'lead_time' => get_field('lead_time_days', $supplier->ID), ); } return rest_ensure_response($formatted_suppliers); } /** * 获取KPI数据 */ public function get_kpi_data($request) { $period = $request->get_param('period') ?: 'monthly'; $kpis = SupplyChainAnalytics::calculate_kpis($period); return rest_ensure_response(array( 'period' => $period, 'kpis' => $kpis, 'timestamp' => current_time('mysql'), )); } /** * 检查API权限 */ public function check_api_permission($request) { // 使用API密钥验证或用户权限验证 $api_key = $request->get_header('X-API-Key'); if ($api_key) { return $this->validate_api_key($api_key); } // 或者检查用户权限 return current_user_can('manage_woocommerce'); } private function validate_api_key($api_key) { $valid_keys = get_option('supply_chain_api_keys', array()); return in_array(hash('sha256', $api_key), $valid_keys); } } // 初始化API add_action('rest_api_init', function() { $api = new SupplyChainAPI(); $api->register_routes(); }); ?> 前端界面与用户体验 管理后台界面优化 <?php /** * 供应链管理后台界面 */ class SupplyChainAdminUI { public function __construct() { add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); } /** * 添加管理菜单 */ public function add_admin_menu() { add_menu_page( '供应链仪表板', '供应链', 'manage_woocommerce', 'supply-chain-dashboard', array($this, 'render_dashboard'), 'dashicons-networking', 58 ); add_submenu_page( 'supply-chain-dashboard', '库存管理', '库存管理', 'manage_woocommerce', 'supply-chain-inventory', array($this, 'render_inventory_page') ); add_submenu_page( 'supply-chain-dashboard', '供应商管理', '供应商管理', 'manage_woocommerce', 'supply-chain-suppliers', array($this, 'render_suppliers_page') ); add_submenu_page( 'supply-chain-dashboard', '分析报告', '分析报告', 'manage_woocommerce', 'supply-chain-analytics', array($this, 'render_analytics_page') ); } /** * 加载管理界面脚本和样式 */ public function enqueue_admin_scripts($hook) { if (strpos($hook, 'supply-chain') === false) { return; } wp_enqueue_style( 'supply-chain-admin', plugin_dir_url(__FILE__) . 'css/admin.css', array(), '1.0.0' ); wp_enqueue_script( 'supply-chain-admin', plugin_dir_url(__FILE__) . 'js/admin.js', array('jquery', 'chartjs'), '1.0.0', true ); // 本地化脚本数据 wp_localize_script('supply-chain-admin', 'supplyChainData', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('supply_chain_nonce'), 'api_endpoint' => rest_url('supply-chain/v1/'), )); } /** * 渲染仪表板 */ public function render_dashboard() { ?> <div class="wrap supply-chain-dashboard"> <h1>供应链仪表板</h1> <div class="dashboard-widgets"> <div class="widget kpi-widget"> <h3>关键绩效指标</h3> <div class="kpi-grid"> <div class="kpi-item"> <span class="kpi-value" id="fulfillment-rate">--%</span> <span class="kpi-label">订单履行率</span> </div> <div class="kpi-item"> <span class="kpi-value" id="processing-time">--h</span> <span class="kpi-label">平均处理时间</span> </div> <div class="kpi-item"> <span class="kpi-value" id="inventory-turnover">--</span> <span class="kpi-label">库存周转率</span> </div> </div> </div> <div class="widget inventory-alerts"> <h3>库存警报</h3> <div id="inventory-alerts-list"> <!-- 通过AJAX动态加载 --> </div> </div> <div class="widget recent-orders"> <h3>最近采购订单</h3> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>订单号</th> <th>供应商</th> <th>金额</th> <th>状态</th> <th>预计交付</th> </tr> </thead> <tbody id="recent-orders-list"> <!-- 通过AJAX动态加载 --> </tbody> </table> </div> </div> <div class="dashboard-chart"> <canvas id="supply-chain-chart" width="800" height="300"></canvas> </div> </div> <script> jQuery(document).ready(function($) { // 加载KPI数据 $.ajax({ url: supplyChainData.api_endpoint + 'analytics/kpi', method: 'GET', beforeSend: function(xhr) { xhr.setRequestHeader('X-WP-Nonce', supplyChainData.nonce); }, success: function(response) { if (response.kpis) { $('#fulfillment-rate').text(response.kpis.order_fulfillment_rate + '%'); $('#processing-time').text(response.kpis.avg_order_processing_hours + 'h'); $('#inventory-turnover').text(response.kpis.inventory_turnover); } } }); // 加载库存警报 loadInventoryAlerts(); // 加载最近订单 loadRecentOrders(); // 初始化图表 initSupplyChainChart(); }); </script> <?php } /** * 渲染库存管理页面 */ public function render_inventory_page() { ?> <div class="wrap"> <h1>库存管理</h1> <div class="inventory-controls"> <input type="text" id="inventory-search" placeholder="搜索产品..." /> <select id="inventory-filter"> <option value="all">所有库存</option> <option value="low">低库存</option> <option value="out">缺货</option> </select> <button id="export-inventory" class="button">导出CSV</button> </div> <table id="inventory-table" class="wp-list-table widefat fixed striped"> <thead> <tr> <th>产品ID</th> <th>产品名称</th> <th>当前库存</th> <th>重新订货水平</th> <th>供应商</th> <th>最后更新</th> <th>操作</th> </tr> </thead> <tbody> <!-- 通过AJAX动态加载 --> </tbody> </table> <div id="inventory-pagination"></div> </div> <?php } } ?> 系统测试与部署 单元测试示例 <?php /** * 供应链系统单元测试 */ class SupplyChainTests extends WP_UnitTestCase { /** * 测试库存更新功能 */ public function test_inventory_update() { // 创建测试产品 $product_id = $this->factory->post->create(array( 'post_type' => 'product' )); // 初始库存应为0 $this->assertTrue(InventoryManager::update_inventory($product_id, 100)); global $wpdb; $table_name = $wpdb->prefix . 'supply_chain_inventory'; $inventory = $wpdb->get_row($wpdb->prepare( "SELECT quantity FROM $table_name WHERE product_id = %d", $product_id )); $this->assertEquals(100, $inventory->quantity); // 测试减少库存 $this->assertTrue(InventoryManager::update_inventory($product_id, -30)); $inventory = $wpdb->get_row($wpdb->prepare( "SELECT quantity FROM $table_name WHERE product_id = %d", $product_id )); $this->assertEquals(70, $inventory->quantity); // 测试库存不会低于0 $this->assertTrue(InventoryManager::update_inventory($product_id, -100)); $inventory = $wpdb->get_row($wpdb->prepare( "SELECT quantity FROM $table_name WHERE product_id = %d", $product_id )); $this->assertEquals(0, $inventory->quantity); } /** * 测试供应商自定义帖子类型 */ public function test_supplier_post_type() { $post_types = get_post_types(); $this->assertArrayHasKey('supplier', $post_types); // 创建测试供应商 $supplier_id = $this->factory->post->create(array( 'post_type' => 'supplier', 'post_title' => '测试供应商' )); $supplier = get_post($supplier_id); $this->assertEquals('supplier', $supplier->post_type); $this->assertEquals('测试供应商', $supplier->post_title); } /** * 测试API权限验证 */ public function test_api_permission() { $api = new SupplyChainAPI(); // 测试无权限情况 $request = new WP_REST_Request(); $this->assertFalse($api->check_api_permission($request)); // 创建有权限的用户 $user_id = $this->factory->user->create(array( 'role' => 'administrator' )); wp_set_current_user($user_id); $this->assertTrue($api->check_api_permission($request)); } } /** * 集成测试:完整订单流程 */ function test_complete_order_workflow() { // 1. 创建供应商 // 2. 添加产品库存 // 3. 创建客户订单 // 4. 验证库存扣减 // 5. 生成采购订单 // 6. 验证整个流程 } ?> 部署清单 环境检查 PHP版本 ≥ 7.4 MySQL版本 ≥ 5.7 WordPress版本 ≥ 6.0 WooCommerce插件已安装 数据库备份 -- 备份现有数据 mysqldump -u username -p database_name > backup.sql -- 创建新表 source supply_chain_tables.sql 文件部署 /wp-content/plugins/supply-chain-system/ ├── supply-chain.php # 主插件文件 ├── includes/ # 包含文件 │ ├── class-inventory.php │ ├── class-suppliers.php │ ├── class-api.php │ └── class-analytics.php ├── assets/ # 静态资源 │ ├── css/ │ ├── js/ │ └── images/ ├── templates/ # 模板文件 └── uninstall.php # 卸载脚本 配置步骤 激活插件 运行数据库安装脚本 配置API密钥 设置初始供应商 配置库存参数 维护与扩展 定期维护任务 数据库优化 -- 每周执行一次 OPTIMIZE TABLE wp_supply_chain_inventory; ANALYZE TABLE wp_supply_chain_purchase_orders; -- 清理旧日志 DELETE FROM wp_supply_chain_logs WHERE log_date < DATE_SUB(NOW(), INTERVAL 90 DAY); 性能监控 <?php /** * 系统性能监控 */ class PerformanceMonitor { public static function log_performance() { $memory_usage = memory_get_usage(true) / 1024 / 1024; // MB $load_time = timer_stop(0, 3); // 秒 if ($memory_usage > 256 || $load_time > 3) { error_log(sprintf( "性能警报: 内存使用 %.2fMB, 加载时间 %.3fs", $memory_usage, $load_time )); } } } add_action('shutdown', array('PerformanceMonitor', 'log_performance')); ?> 系统扩展建议 多仓库支持 // 扩展库存表支持多仓库 ALTER TABLE wp_supply_chain_inventory ADD warehouse_id INT(11) AFTER product_id, ADD INDEX (warehouse_id); 预测分析模块 class DemandForecaster { public function predict_demand($product_id, $period = 'monthly') { // 使用历史销售数据进行需求预测 // 可集成机器学习算法 } } 移动端应用 // 使用React Native或Flutter开发移动应用

发表评论

手把手教程,为WordPress实现基于用户分层的差异化内容访问控制工具

手把手教程:为WordPress实现基于用户分层的差异化内容访问控制工具 引言:为什么WordPress需要用户分层内容控制? 在当今互联网环境中,个性化体验已成为网站成功的关键因素之一。根据Monetate的研究,93%的企业表示个性化策略显著提高了收入。对于WordPress网站而言,实现基于用户分层的差异化内容访问控制不仅能提升用户体验,还能有效提高转化率、用户粘性和内容价值。 传统的WordPress用户角色系统虽然提供了基本的权限管理,但在面对复杂的内容访问策略时显得力不从心。许多网站管理员需要根据用户等级、订阅状态、积分体系或购买历史等因素,为不同用户群体展示不同的内容。本教程将手把手指导您通过代码二次开发,实现一个功能完善的用户分层内容访问控制工具。 第一部分:理解WordPress用户系统与权限架构 1.1 WordPress默认用户角色与能力 WordPress内置了五种基本用户角色:订阅者、投稿者、作者、编辑和管理员。每个角色都有一组特定的"能力"(capabilities),这些能力决定了用户可以执行哪些操作。 // 查看WordPress默认能力 global $wp_roles; print_r($wp_roles->roles); 然而,这些默认角色和能力主要针对内容管理,而非内容访问控制。要实现基于用户分层的差异化访问,我们需要扩展这一系统。 1.2 用户分层策略设计 在开始编码之前,我们需要设计合理的用户分层策略。常见的分层维度包括: 会员等级:免费用户、基础会员、高级会员、VIP 订阅状态:试用期用户、活跃订阅用户、过期用户 行为积分:根据互动行为积累的积分等级 购买历史:基于消费金额或频率的分层 用户标签:基于兴趣、职业等特征的手动标签 在本教程中,我们将以会员等级为核心,创建一个四级分层系统: 等级0:未注册游客 等级1:免费注册用户 等级2:基础会员 等级3:高级会员 第二部分:构建用户分层管理系统 2.1 创建用户等级数据库字段 首先,我们需要在用户数据表中添加等级字段。虽然WordPress提供了wp_usermeta表存储用户元数据,但为了性能考虑,我们将使用专门的数据表来管理用户等级信息。 // 创建用户等级数据表 function create_user_level_table() { global $wpdb; $table_name = $wpdb->prefix . 'user_levels'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, user_level int(2) NOT NULL DEFAULT 1, level_expiry datetime DEFAULT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY user_id (user_id), KEY user_level (user_level) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } register_activation_hook(__FILE__, 'create_user_level_table'); 2.2 用户等级管理后台界面 接下来,我们创建一个用户等级管理后台页面,让管理员可以轻松调整用户等级。 // 添加用户等级管理菜单 function add_user_level_admin_menu() { add_users_page( '用户等级管理', '用户等级', 'manage_options', 'user-level-manager', 'render_user_level_admin_page' ); } add_action('admin_menu', 'add_user_level_admin_menu'); // 渲染用户等级管理页面 function render_user_level_admin_page() { global $wpdb; $table_name = $wpdb->prefix . 'user_levels'; // 处理表单提交 if (isset($_POST['update_user_level'])) { $user_id = intval($_POST['user_id']); $new_level = intval($_POST['user_level']); $expiry_date = sanitize_text_field($_POST['level_expiry']); update_user_level($user_id, $new_level, $expiry_date); echo '<div class="notice notice-success"><p>用户等级已更新</p></div>'; } // 分页逻辑 $per_page = 20; $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1; $offset = ($current_page - 1) * $per_page; // 获取用户数据 $users = $wpdb->get_results( $wpdb->prepare( "SELECT u.ID, u.user_login, u.user_email, ul.user_level, ul.level_expiry FROM {$wpdb->users} u LEFT JOIN $table_name ul ON u.ID = ul.user_id ORDER BY u.ID DESC LIMIT %d OFFSET %d", $per_page, $offset ) ); // 计算总页数 $total_users = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->users}"); $total_pages = ceil($total_users / $per_page); // 渲染页面HTML ?> <div class="wrap"> <h1 class="wp-heading-inline">用户等级管理</h1> <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 foreach ($users as $user): ?> <tr> <td><?php echo $user->ID; ?></td> <td><?php echo esc_html($user->user_login); ?></td> <td><?php echo esc_html($user->user_email); ?></td> <td> <select id="level_<?php echo $user->ID; ?>"> <option value="0" <?php selected($user->user_level, 0); ?>>游客</option> <option value="1" <?php selected($user->user_level, 1); ?>>免费用户</option> <option value="2" <?php selected($user->user_level, 2); ?>>基础会员</option> <option value="3" <?php selected($user->user_level, 3); ?>>高级会员</option> </select> </td> <td> <input type="date" id="expiry_<?php echo $user->ID; ?>" value="<?php echo $user->level_expiry ? date('Y-m-d', strtotime($user->level_expiry)) : ''; ?>"> </td> <td> <button class="button button-primary update-level-btn" data-user-id="<?php echo $user->ID; ?>">更新</button> </td> </tr> <?php endforeach; ?> </tbody> </table> <!-- 分页导航 --> <div class="tablenav bottom"> <div class="tablenav-pages"> <?php echo paginate_links(array( 'base' => add_query_arg('paged', '%#%'), 'format' => '', 'prev_text' => '&laquo;', 'next_text' => '&raquo;', 'total' => $total_pages, 'current' => $current_page )); ?> </div> </div> </div> <script> jQuery(document).ready(function($) { $('.update-level-btn').click(function() { var userId = $(this).data('user-id'); var newLevel = $('#level_' + userId).val(); var expiryDate = $('#expiry_' + userId).val(); $.post(ajaxurl, { action: 'update_user_level', user_id: userId, user_level: newLevel, level_expiry: expiryDate, nonce: '<?php echo wp_create_nonce("update_user_level_nonce"); ?>' }, function(response) { if (response.success) { alert('用户等级已更新'); } else { alert('更新失败: ' + response.data); } }); }); }); </script> <?php } 2.3 用户等级API函数 创建核心的用户等级管理函数,这些函数将在整个插件中使用。 // 获取用户等级 function get_user_level($user_id = null) { if (!$user_id) { $user_id = get_current_user_id(); } if (!$user_id) { return 0; // 游客 } global $wpdb; $table_name = $wpdb->prefix . 'user_levels'; $result = $wpdb->get_row($wpdb->prepare( "SELECT user_level, level_expiry FROM $table_name WHERE user_id = %d", $user_id )); // 检查等级是否过期 if ($result && $result->level_expiry) { $expiry_time = strtotime($result->level_expiry); if (time() > $expiry_time) { // 等级已过期,降级为免费用户 update_user_level($user_id, 1, null); return 1; } } return $result ? $result->user_level : 1; // 默认等级为1(免费用户) } // 更新用户等级 function update_user_level($user_id, $level, $expiry_date = null) { global $wpdb; $table_name = $wpdb->prefix . 'user_levels'; // 验证用户是否存在 $user = get_user_by('ID', $user_id); if (!$user) { return new WP_Error('invalid_user', '用户不存在'); } // 验证等级值 if (!in_array($level, [0, 1, 2, 3])) { return new WP_Error('invalid_level', '无效的用户等级'); } // 检查是否已有记录 $existing = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE user_id = %d", $user_id )); if ($existing) { // 更新现有记录 $wpdb->update( $table_name, array( 'user_level' => $level, 'level_expiry' => $expiry_date, 'updated_at' => current_time('mysql') ), array('user_id' => $user_id) ); } else { // 插入新记录 $wpdb->insert( $table_name, array( 'user_id' => $user_id, 'user_level' => $level, 'level_expiry' => $expiry_date, 'created_at' => current_time('mysql'), 'updated_at' => current_time('mysql') ) ); } // 清除用户缓存 clean_user_cache($user_id); // 触发等级变更钩子 do_action('user_level_updated', $user_id, $level, $expiry_date); return true; } // AJAX处理用户等级更新 add_action('wp_ajax_update_user_level', 'handle_ajax_update_user_level'); function handle_ajax_update_user_level() { // 验证nonce if (!check_ajax_referer('update_user_level_nonce', 'nonce', false)) { wp_die('安全验证失败', 403); } // 验证权限 if (!current_user_can('manage_options')) { wp_die('权限不足', 403); } $user_id = intval($_POST['user_id']); $user_level = intval($_POST['user_level']); $level_expiry = sanitize_text_field($_POST['level_expiry']); $result = update_user_level($user_id, $user_level, $level_expiry); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } else { wp_send_json_success(); } } 第三部分:实现内容访问控制功能 3.1 短代码实现内容保护 短代码是WordPress中最灵活的内容控制方式之一。我们将创建几个短代码,用于根据用户等级显示或隐藏内容。 // 等级保护短代码 function user_level_protected_shortcode($atts, $content = null) { // 解析短代码属性 $atts = shortcode_atts(array( 'min_level' => 1, 'max_level' => 3, 'message' => '您没有权限查看此内容', 'show_to' => '' // 特定用户ID,用逗号分隔 ), $atts, 'user_level_content'); $current_user_id = get_current_user_id(); $current_user_level = get_user_level($current_user_id); // 检查特定用户权限 if (!empty($atts['show_to'])) { $allowed_users = array_map('intval', explode(',', $atts['show_to'])); if (in_array($current_user_id, $allowed_users)) { return do_shortcode($content); } } // 检查等级权限 if ($current_user_level >= $atts['min_level'] && $current_user_level <= $atts['max_level']) { return do_shortcode($content); } // 没有权限,显示提示信息 $message = '<div class="user-level-restricted-message">'; $message .= '<p>' . esc_html($atts['message']) . '</p>'; // 如果用户未登录,显示登录链接 if (!$current_user_id) { $message .= '<p>请<a href="' . wp_login_url(get_permalink()) . '">登录</a>或'; $message .= '<a href="' . wp_registration_url() . '">注册</a></p>'; } // 如果用户等级不够,显示升级提示 elseif ($current_user_level < $atts['min_level']) { $message .= '<p>您需要升级到等级' . $atts['min_level'] . '才能查看此内容</p>'; $message .= '<a href="/membership" class="button">立即升级</a>'; } $message .= '</div>'; return $message; } add_shortcode('user_level_content', 'user_level_protected_shortcode'); // 使用示例: // [user_level_content min_level="2"]仅限基础会员及以上查看的内容[/user_level_content] // [user_level_content min_level="3" message="这是VIP专属内容"]VIP内容[/user_level_content] 3.2 文章/页面级别的内容保护 除了短代码,我们还需要在文章/页面级别实现内容保护。这可以通过自定义元框和内容过滤器来实现。 // 添加文章/页面等级保护元框 function add_user_level_metabox() { $post_types = get_post_types(array('public' => true)); foreach ($post_types as $post_type) { add_meta_box( 'user_level_protection', '用户等级访问控制', 'render_user_level_metabox', $post_type, 'side', 'high' ); } } add_action('add_meta_boxes', 'add_user_level_metabox'); // 渲染元框内容 function render_user_level_metabox($post) { wp_nonce_field('save_user_level_protection', 'user_level_protection_nonce'); $min_level = get_post_meta($post->ID, '_user_level_min', true); $max_level = get_post_meta($post->ID, '_user_level_max', true); $custom_message = get_post_meta($post->ID, '_user_level_message', true); ?> <p> <label for="user_level_min">最低访问等级:</label> <select name="user_level_min" id="user_level_min"> <option value="0" <?php selected($min_level, '0'); ?>>所有人(包括游客)</option> <option value="1" <?php selected($min_level, '1'); ?>>免费用户及以上</option> <option value="2" <?php selected($min_level, '2'); ?>>基础会员及以上</option> <option value="3" <?php selected($min_level, '3'); ?>>高级会员及以上</option> </select> </p> <p> <label for="user_level_max">最高访问等级:</label> <select name="user_level_max" id="user_level_max"> <option value="0" <?php selected($max_level, '0'); ?>>不限</option> <option value="1" <?php selected($max_level, '1'); ?>>仅免费用户</option> <option value="2" <?php selected($max_level, '2'); ?>>仅基础会员</option> <option value="3" <?php selected($max_level, '3'); ?>>仅高级会员</option> </select> </p> <p> <label for="user_level_message">无权限提示信息:</label> <textarea name="user_level_message" id="user_level_message" rows="3" style="width:100%;"><?php echo esc_textarea($custom_message); ?></textarea> </p> <p class="description"> 设置此文章/页面的访问等级限制。如果用户等级不符合要求,将看到提示信息。 </p> <?php } // 保存元框数据 function save_user_level_protection($post_id) { // 检查nonce if (!isset($_POST['user_level_protection_nonce']) || nono'], 'save_user_level_protection')) { return; } // 检查自动保存 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } // 检查用户权限 if (!current_user_can('edit_post', $post_id)) { return; } // 保存数据 if (isset($_POST['user_level_min'])) { update_post_meta($post_id, '_user_level_min', intval($_POST['user_level_min'])); } if (isset($_POST['user_level_max'])) { update_post_meta($post_id, '_user_level_max', intval($_POST['user_level_max'])); } if (isset($_POST['user_level_message'])) { update_post_meta($post_id, '_user_level_message', sanitize_textarea_field($_POST['user_level_message'])); } }add_action('save_post', 'save_user_level_protection'); // 内容访问控制过滤器function filter_protected_content($content) { global $post; // 只在单篇文章/页面应用 if (!is_singular() || !isset($post->ID)) { return $content; } // 获取当前用户等级 $current_user_id = get_current_user_id(); $current_user_level = get_user_level($current_user_id); // 获取文章保护设置 $min_level = get_post_meta($post->ID, '_user_level_min', true); $max_level = get_post_meta($post->ID, '_user_level_max', true); $custom_message = get_post_meta($post->ID, '_user_level_message', true); // 如果没有设置保护,直接返回内容 if (empty($min_level) && empty($max_level)) { return $content; } // 设置默认值 $min_level = empty($min_level) ? 0 : intval($min_level); $max_level = empty($max_level) ? 3 : intval($max_level); // 检查用户权限 $has_access = true; if ($current_user_level < $min_level) { $has_access = false; $reason = 'level_too_low'; } if ($max_level > 0 && $current_user_level > $max_level) { $has_access = false; $reason = 'level_too_high'; } // 如果有权限,返回完整内容 if ($has_access) { return $content; } // 没有权限,返回提示信息 $message = '<div class="user-level-restricted-content">'; if (!empty($custom_message)) { $message .= '<p>' . esc_html($custom_message) . '</p>'; } else { $message .= '<h3>内容访问受限</h3>'; if ($reason === 'level_too_low') { $level_names = ['游客', '免费用户', '基础会员', '高级会员']; $message .= '<p>此内容需要' . $level_names[$min_level] . '及以上等级才能访问</p>'; $message .= '<p>您的当前等级:' . $level_names[$current_user_level] . '</p>'; } else { $message .= '<p>此内容仅限特定等级用户访问</p>'; } } // 添加操作按钮 if (!$current_user_id) { $message .= '<div class="restricted-actions">'; $message .= '<a href="' . wp_login_url(get_permalink()) . '" class="button button-primary">登录</a>'; $message .= '<a href="' . wp_registration_url() . '" class="button">注册</a>'; $message .= '</div>'; } elseif ($current_user_level < $min_level) { $message .= '<div class="restricted-actions">'; $message .= '<a href="/membership" class="button button-primary">升级会员</a>'; $message .= '</div>'; } $message .= '</div>'; return $message; }add_filter('the_content', 'filter_protected_content', 10); ### 3.3 分类和标签级别的访问控制 除了文章级别的控制,我们还可以实现分类和标签级别的访问控制。 // 为分类添加等级保护字段function add_user_level_to_category($term) { ?> <div class="form-field"> <label for="user_level_min">最低访问等级</label> <select name="user_level_min" id="user_level_min"> <option value="0">所有人(包括游客)</option> <option value="1">免费用户及以上</option> <option value="2">基础会员及以上</option> <option value="3">高级会员及以上</option> </select> <p>设置访问此分类的最低用户等级</p> </div> <?php }add_action('category_add_form_fields', 'add_user_level_to_category'); // 编辑分类时的字段function edit_user_level_for_category($term) { $min_level = get_term_meta($term->term_id, 'user_level_min', true); ?> <tr class="form-field"> <th scope="row"><label for="user_level_min">最低访问等级</label></th> <td> <select name="user_level_min" id="user_level_min"> <option value="0" <?php selected($min_level, '0'); ?>>所有人(包括游客)</option> <option value="1" <?php selected($min_level, '1'); ?>>免费用户及以上</option> <option value="2" <?php selected($min_level, '2'); ?>>基础会员及以上</option> <option value="3" <?php selected($min_level, '3'); ?>>高级会员及以上</option> </select> <p class="description">设置访问此分类的最低用户等级</p> </td> </tr> <?php }add_action('category_edit_form_fields', 'edit_user_level_for_category'); // 保存分类等级设置function save_category_user_level($term_id) { if (isset($_POST['user_level_min'])) { update_term_meta($term_id, 'user_level_min', intval($_POST['user_level_min'])); } }add_action('created_category', 'save_category_user_level');add_action('edited_category', 'save_category_user_level'); // 分类页面访问控制function filter_category_archive($query) { if (!is_admin() && $query->is_main_query() && $query->is_category()) { $current_user_level = get_user_level(); $category_id = $query->get_queried_object_id(); $category_min_level = get_term_meta($category_id, 'user_level_min', true); if (!empty($category_min_level) && $current_user_level < intval($category_min_level)) { // 用户等级不够,重定向或显示错误 if (!is_user_logged_in()) { wp_redirect(wp_login_url(get_category_link($category_id))); exit; } else { wp_die('您没有权限访问此分类', '访问受限', 403); } } } }add_action('pre_get_posts', 'filter_category_archive'); ## 第四部分:用户界面与体验优化 ### 4.1 前端用户等级显示 让用户清楚了解自己的等级状态非常重要。我们将在用户资料页和前端显示用户等级信息。 // 在用户资料页显示等级信息function show_user_level_in_profile($user) { $user_level = get_user_level($user->ID); $level_names = ['游客', '免费用户', '基础会员', '高级会员']; $level_colors = ['#95a5a6', '#3498db', '#2ecc71', '#e74c3c']; // 获取等级有效期 global $wpdb; $table_name = $wpdb->prefix . 'user_levels'; $expiry = $wpdb->get_var($wpdb->prepare( "SELECT level_expiry FROM $table_name WHERE user_id = %d", $user->ID )); ?> <h3>用户等级信息</h3> <table class="form-table"> <tr> <th><label>当前等级</label></th> <td> <div style="display: flex; align-items: center; gap: 10px;"> <span style="display: inline-block; width: 20px; height: 20px; border-radius: 50%; background-color: <?php echo $level_colors[$user_level]; ?>;"></span> <strong style="color: <?php echo $level_colors[$user_level]; ?>;"> <?php echo $level_names[$user_level]; ?> (等级 <?php echo $user_level; ?>) </strong> </div> </td> </tr> <?php if ($expiry): ?> <tr> <th><label>等级有效期</label></th> <td><?php echo date('Y年m月d日', strtotime($expiry)); ?></td> </tr> <?php endif; ?> <tr> <th><label>等级权限</label></th> <td> <ul style="margin: 0;"> <?php $permissions = [ 0 => '只能浏览公开内容', 1 => '可以浏览基础内容,参与评论', 2 => '可以访问会员专属内容,下载资源', 3 => '可以访问所有内容,享受优先支持' ]; foreach ($permissions as $level => $permission) { $checked = $user_level >= $level ? '✓' : '✗'; $color = $user_level >= $level ? '#2ecc71' : '#e74c3c'; echo '<li style="color: ' . $color . ';">' . $checked . ' ' . $permission . '</li>'; } ?> </ul> </td> </tr> </table> <?php }add_action('show_user_profile', 'show_user_level_in_profile');add_action('edit_user_profile', 'show_user_level_in_profile'); // 在前端显示用户等级徽章function display_user_level_badge() { if (!is_user_logged_in()) { return; } $user_id = get_current_user_id(); $user_level = get_user_level($user_id); $level_names = ['游客', '免费用户', '基础会员', '高级会员']; $level_colors = ['#95a5a6', '#3498db', '#2ecc71', '#e74c3c']; // 获取用户信息 $user = wp_get_current_user(); $display_name = $user->display_name; ob_start(); ?> <div class="user-level-badge" style=" display: inline-flex; align-items: center; gap: 8px; padding: 6px 12px; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); border-radius: 20px; border: 2px solid <?php echo $level_colors[$user_level]; ?>; font-size: 14px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); "> <span style=" display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: <?php echo $level_colors[$user_level]; ?>; "></span> <span style="font-weight: bold; color: #2c3e50;"><?php echo esc_html($display_name); ?></span> <span style=" padding: 2px 8px; background-color: <?php echo $level_colors[$user_level]; ?>; color: white; border-radius: 10px; font-size: 12px; font-weight: bold; "> <?php echo $level_names[$user_level]; ?> </span> </div> <?php return ob_get_clean(); }add_shortcode('user_level_badge', 'display_user_level_badge'); // 自动在导航栏添加用户等级信息function add_user_level_to_nav($items, $args) { if (!is_user_logged_in() || $args->theme_location != 'primary') { return $items; } $badge = display_user_level_badge(); $items .= '<li class="menu-item user-level-menu-item">' . $badge . '</li>'; return $items; }add_filter('wp_nav_menu_items', 'add_user_level_to_nav', 10, 2); ### 4.2 等级升级提示和引导 当用户尝试访问受限内容时,提供清晰的升级路径非常重要。 // 创建升级提示小工具class User_Level_Upgrade_Widget extends WP_Widget { public function __construct() { parent::__construct( 'user_level_upgrade_widget', '用户等级升级提示', array('description' => '根据用户当前等级显示升级提示') ); } public function widget($args, $instance) { if (!is_user_logged_in()) { return; } $current_user_level = get_user_level(); // 如果已经是最高等级,不显示 if ($current_user_level >= 3) { return; } $next_level = $current_user_level + 1; $level_names = ['免费用户', '基础会员', '高级会员', 'VIP']; $level_prices = ['免费', '99元/年', '299元/年', '999元/年']; $level_features = [ 1 => ['基础内容访问', '参与评论', '社区互动'], 2 => ['会员专属内容', '资源下载', '优先支持'], 3 => ['所有内容访问', '专属客服', '活动邀请', '定制服务'] ]; echo $args['before_widget']; echo $args['before_title'] . '升级到' . $level_names[$next_level] . $args['after_title']; ?> <div class="upgrade-widget-content"> <div class="current-level" style=" background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 15px; border-radius: 8px; margin-bottom: 15px; "> <h4 style="margin: 0 0 5px 0;">当前等级</h4> <div style="font-size: 24px; font-weight: bold;"><?php echo $level_names[$current_user_level]; ?></div> </div> <div class="next-level" style=" background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 15px; border-radius: 8px; margin-bottom: 15px; "> <h4 style="margin: 0 0 5px 0;">下一等级</h4> <div style="font-size: 24px; font-weight: bold;"><?php echo $level_names[$next_level]; ?></div> <div style="font-size: 18px; margin-top: 5px;"><?php echo $level_prices[$next_level]; ?></div> </div> <div class="features" style="margin-bottom: 15px;"> <h4 style="margin-bottom: 10px;">升级后将获得:</h4> <ul style="margin: 0; padding-left: 20px;"> <?php foreach ($level_features[$next_level] as $feature): ?> <li style="margin-bottom: 5px;">✓ <?php echo $feature; ?></li> <?php endforeach; ?> </ul> </div> <a href="/upgrade?level=<?php echo $next_level; ?>" class="button button-primary" style=" display: block; text-align: center; padding: 12px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; text-decoration: none; border-radius: 6px; font-weight: bold; transition: transform 0.3s; " onmouseover="this.style.transform='translateY(-2px)'" onmouseout="this.style.transform='translateY(0)'"> 立即升级 </a> </div> <?php echo $args['after_widget']; } } // 注册小工具function register_user_level_widget() { register_widget('User_Level_Upgrade_Widget'); }add_action('widgets_init', 'register_user_level_widget'); ## 第五部分:高级功能与集成 ### 5.1 与WooCommerce集成 如果网站使用WooCommerce,我们可以将用户等级与购买行为关联起来。 // WooCommerce购买后自动升级用户等级function upgrade_user_level_on_purchase($order_id) { $order = wc_get_order($order_id); $user_id = $order->get_user_id(); if (!$user_id) { return; } // 获取订单总金额 $order_total = $order->get_total(); // 根据订单金额确定等级 $new_level = 1; // 默认免费用户 if ($order_total >= 999) { $new_level = 3; // 高级会员 $expiry_date = date('Y-m-d H:i:s', strtotime('+1 year')); } elseif ($order_total >= 99) { $new_level = 2; // 基础会员 $expiry_date = date('Y-m-d H:i:s', strtotime('+1 year')); } // 更新用户等级 if ($new_level >

发表评论

开发指南,打造网站内嵌的在线视频速度控制与章节标记工具

开发指南:打造网站内嵌的在线视频速度控制与章节标记工具 摘要 随着在线教育、知识分享和内容创作行业的蓬勃发展,视频内容已成为网站不可或缺的组成部分。然而,大多数网站的视频播放功能相对基础,缺乏个性化控制选项。本文将详细介绍如何通过WordPress代码二次开发,为网站视频播放器添加速度控制与章节标记功能,从而提升用户体验和内容互动性。我们将从需求分析、技术选型、代码实现到测试部署,全面解析这一实用工具的构建过程。 一、需求分析与功能规划 1.1 当前视频播放的局限性 大多数WordPress网站使用默认的视频播放器或第三方插件,这些方案通常存在以下局限性: 播放速度固定,无法根据用户需求调整 缺乏章节标记功能,长视频导航困难 用户无法自定义播放体验 互动性差,用户参与度低 1.2 目标功能定义 我们的开发目标是为WordPress网站视频播放器添加以下核心功能: 多级播放速度控制:提供0.5x到3.0x之间的多档速度选择 智能章节标记系统:允许内容创作者为视频添加章节标记 用户友好的交互界面:直观的控制面板,不影响观看体验 数据持久化:记住用户的播放偏好设置 响应式设计:适配各种设备和屏幕尺寸 1.3 技术可行性分析 WordPress作为开源CMS系统,提供了丰富的API和钩子机制,使我们能够: 通过JavaScript操作HTML5视频元素 使用PHP扩展视频元数据处理 利用WordPress数据库存储章节信息 通过AJAX实现前后端数据交互 二、技术架构与开发环境搭建 2.1 开发环境配置 在开始开发前,需要准备以下环境: 本地开发环境:XAMPP/MAMP或Local by Flywheel WordPress安装:最新版本的WordPress(5.8+) 代码编辑器:VS Code、Sublime Text或PHPStorm 浏览器开发者工具:用于调试JavaScript和CSS 版本控制系统:Git(可选但推荐) 2.2 项目结构设计 我们将创建一个独立的WordPress插件来管理所有功能代码: wp-content/plugins/video-enhancer-tool/ ├── video-enhancer.php # 主插件文件 ├── includes/ │ ├── class-video-processor.php # 视频处理类 │ ├── class-chapter-manager.php # 章节管理类 │ └── class-settings-handler.php # 设置处理类 ├── assets/ │ ├── css/ │ │ ├── video-controls.css # 控制界面样式 │ │ └── admin-styles.css # 后台管理样式 │ ├── js/ │ │ ├── video-controls.js # 前端控制逻辑 │ │ ├── chapter-editor.js # 章节编辑器 │ │ └── admin-scripts.js # 后台脚本 │ └── images/ # 图标和图片资源 ├── templates/ # 前端模板文件 │ └── video-player-enhanced.php └── languages/ # 国际化文件 2.3 核心技术与API 我们将使用以下关键技术: HTML5 Video API:控制视频播放的核心JavaScript接口 WordPress REST API:处理章节数据的存储和检索 jQuery:简化DOM操作和事件处理(WordPress已内置) LocalStorage:存储用户偏好设置 CSS3 Flexbox/Grid:构建响应式控制界面 三、播放速度控制功能实现 3.1 HTML5视频播放器基础集成 首先,我们需要确保网站的视频使用HTML5播放器,这是实现高级控制的基础: // 在video-enhancer.php中 function vet_replace_video_shortcode($output, $tag) { if ('video' !== $tag) { return $output; } // 为视频元素添加ID和类名以便JavaScript操作 $output = preg_replace('/<video/', '<video data-vet-enhanced="true"', $output); return $output; } add_filter('do_shortcode_tag', 'vet_replace_video_shortcode', 10, 2); 3.2 速度控制界面设计 创建直观的速度控制界面,包含按钮和下拉菜单: /* assets/css/video-controls.css */ .vet-controls-container { position: relative; display: flex; align-items: center; justify-content: space-between; background: rgba(0, 0, 0, 0.7); padding: 10px 15px; border-radius: 5px; margin-top: 10px; color: white; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; } .vet-speed-control { display: flex; align-items: center; } .vet-speed-label { margin-right: 10px; font-size: 14px; opacity: 0.9; } .vet-speed-buttons { display: flex; gap: 5px; } .vet-speed-btn { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.3); color: white; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 13px; transition: all 0.2s ease; } .vet-speed-btn:hover { background: rgba(255, 255, 255, 0.2); } .vet-speed-btn.active { background: #0073aa; border-color: #0073aa; } .vet-speed-dropdown { position: relative; display: inline-block; } .vet-speed-dropdown-btn { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.3); color: white; padding: 5px 15px 5px 10px; border-radius: 3px; cursor: pointer; font-size: 13px; position: relative; } .vet-speed-dropdown-btn:after { content: "▼"; font-size: 10px; margin-left: 5px; } .vet-speed-dropdown-content { display: none; position: absolute; bottom: 100%; left: 0; background: rgba(0, 0, 0, 0.9); min-width: 80px; border-radius: 3px; z-index: 1000; margin-bottom: 5px; } .vet-speed-dropdown-content.show { display: block; } .vet-speed-option { color: white; padding: 8px 12px; text-decoration: none; display: block; cursor: pointer; font-size: 13px; } .vet-speed-option:hover { background: rgba(255, 255, 255, 0.1); } 3.3 JavaScript速度控制逻辑 实现播放速度控制的核心JavaScript代码: // assets/js/video-controls.js (function($) { 'use strict'; // 视频增强工具主对象 var VideoEnhancer = { // 初始化 init: function() { this.setupVideoPlayers(); this.bindEvents(); this.loadUserPreferences(); }, // 查找页面上的视频元素并添加控制 setupVideoPlayers: function() { $('video[data-vet-enhanced="true"]').each(function() { var $video = $(this); // 确保视频有ID if (!$video.attr('id')) { $video.attr('id', 'vet-video-' + Math.random().toString(36).substr(2, 9)); } // 添加控制容器 VideoEnhancer.addControls($video); }); }, // 为视频添加控制界面 addControls: function($video) { var videoId = $video.attr('id'); var controlsHtml = ` <div class="vet-controls-container" data-video-id="${videoId}"> <div class="vet-speed-control"> <span class="vet-speed-label">播放速度:</span> <div class="vet-speed-buttons"> <button class="vet-speed-btn" data-speed="0.5">0.5x</button> <button class="vet-speed-btn" data-speed="0.75">0.75x</button> <button class="vet-speed-btn active" data-speed="1">1x</button> <button class="vet-speed-btn" data-speed="1.25">1.25x</button> <button class="vet-speed-btn" data-speed="1.5">1.5x</button> <button class="vet-speed-btn" data-speed="2">2x</button> </div> <div class="vet-speed-dropdown"> <button class="vet-speed-dropdown-btn">更多速度</button> <div class="vet-speed-dropdown-content"> <div class="vet-speed-option" data-speed="0.25">0.25x</div> <div class="vet-speed-option" data-speed="0.5">0.5x</div> <div class="vet-speed-option" data-speed="0.75">0.75x</div> <div class="vet-speed-option" data-speed="1">1x</div> <div class="vet-speed-option" data-speed="1.25">1.25x</div> <div class="vet-speed-option" data-speed="1.5">1.5x</div> <div class="vet-speed-option" data-speed="1.75">1.75x</div> <div class="vet-speed-option" data-speed="2">2x</div> <div class="vet-speed-option" data-speed="2.5">2.5x</div> <div class="vet-speed-option" data-speed="3">3x</div> </div> </div> </div> <div class="vet-chapter-control"> <button class="vet-chapters-toggle-btn">章节</button> <div class="vet-chapters-panel"> <div class="vet-chapters-list"></div> </div> </div> </div> `; // 将控制容器插入到视频后面 $video.after(controlsHtml); // 初始化章节功能 VideoEnhancer.loadChaptersForVideo(videoId); }, // 绑定事件处理 bindEvents: function() { // 速度按钮点击事件 $(document).on('click', '.vet-speed-btn', function() { var speed = $(this).data('speed'); VideoEnhancer.setPlaybackSpeed(speed, $(this)); }); // 下拉速度选项点击事件 $(document).on('click', '.vet-speed-option', function() { var speed = $(this).data('speed'); VideoEnhancer.setPlaybackSpeed(speed, $(this)); }); // 下拉菜单显示/隐藏 $(document).on('click', '.vet-speed-dropdown-btn', function(e) { e.stopPropagation(); $(this).siblings('.vet-speed-dropdown-content').toggleClass('show'); }); // 点击其他地方关闭下拉菜单 $(document).on('click', function() { $('.vet-speed-dropdown-content').removeClass('show'); }); // 章节切换按钮 $(document).on('click', '.vet-chapters-toggle-btn', function() { $(this).siblings('.vet-chapters-panel').toggleClass('show'); }); }, // 设置播放速度 setPlaybackSpeed: function(speed, $element) { // 获取对应的视频元素 var videoId = $element.closest('.vet-controls-container').data('video-id'); var video = document.getElementById(videoId); if (video) { // 设置播放速度 video.playbackRate = speed; // 更新按钮状态 $('.vet-speed-btn').removeClass('active'); $('.vet-speed-btn[data-speed="' + speed + '"]').addClass('active'); // 更新下拉按钮文本 var $dropdownBtn = $element.closest('.vet-controls-container').find('.vet-speed-dropdown-btn'); $dropdownBtn.text(speed + 'x'); // 保存用户偏好 VideoEnhancer.saveUserPreference('playbackSpeed', speed); // 显示速度变化提示 VideoEnhancer.showSpeedNotification(speed); } }, // 显示速度变化提示 showSpeedNotification: function(speed) { // 创建或获取通知元素 var $notification = $('.vet-speed-notification'); if ($notification.length === 0) { $notification = $('<div class="vet-speed-notification"></div>'); $('body').append($notification); } // 设置内容和显示 $notification.text('播放速度: ' + speed + 'x').addClass('show'); // 2秒后隐藏 setTimeout(function() { $notification.removeClass('show'); }, 2000); }, // 加载用户偏好设置 loadUserPreferences: function() { var savedSpeed = localStorage.getItem('vet_playback_speed'); if (savedSpeed) { // 页面加载后应用保存的速度 $(document).ready(function() { setTimeout(function() { $('.vet-speed-btn[data-speed="' + savedSpeed + '"]').click(); }, 500); }); } }, // 保存用户偏好 saveUserPreference: function(key, value) { if (key === 'playbackSpeed') { localStorage.setItem('vet_playback_speed', value); } }, // 加载视频章节 loadChaptersForVideo: function(videoId) { // 通过AJAX获取章节数据 $.ajax({ url: vet_ajax.ajax_url, type: 'POST', data: { action: 'vet_get_chapters', video_id: videoId, nonce: vet_ajax.nonce }, success: function(response) { if (response.success && response.data.chapters) { VideoEnhancer.renderChapters(videoId, response.data.chapters); } } }); }, // 渲染章节列表 renderChapters: function(videoId, chapters) { var $chaptersList = $('.vet-controls-container[data-video-id="' + videoId + '"] .vet-chapters-list'); var html = ''; if (chapters.length > 0) { html += '<div class="vet-chapters-header">视频章节</div>'; chapters.forEach(function(chapter) { html += ` <div class="vet-chapter-item" data-timestamp="${chapter.timestamp}"> <div class="vet-chapter-time">${VideoEnhancer.formatTime(chapter.timestamp)}</div> <div class="vet-chapter-title">${chapter.title}</div> </div> `; }); // 绑定章节点击事件 $chaptersList.html(html); $chaptersList.find('.vet-chapter-item').on('click', function() { var timestamp = $(this).data('timestamp'); var video = document.getElementById(videoId); if (video) { video.currentTime = timestamp; video.play(); } }); } else { $chaptersList.html('<div class="vet-no-chapters">暂无章节标记</div>'); } }, // 格式化时间显示 formatTime: function(seconds) { var hrs = Math.floor(seconds / 3600); var mins = Math.floor((seconds % 3600) / 60); var secs = Math.floor(seconds % 60); if (hrs > 0) { return hrs + ':' + (mins < 10 ? '0' : '') + mins + ':' + (secs < 10 ? '0' : '') + secs; } else { return mins + ':' + (secs < 10 ? '0' : '') + secs; } } }; // 初始化 $(document).ready(function() { VideoEnhancer.init(); }); })(jQuery); 3.4 后端支持代码 创建PHP类来处理速度控制相关的后端逻辑: // includes/class-video-processor.php class Video_Enhancer_Processor { // 初始化 public function __construct() { add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); add_action('wp_ajax_vet_get_chapters', array($this, 'ajax_get_chapters')); add_action('wp_ajax_nopriv_vet_get_chapters', array($this, 'ajax_get_chapters')); add_action('wp_ajax_vet_save_chapters', array($this, 'ajax_save_chapters')); } // 加载前端资源 public function enqueue_frontend_assets() { // 加载CSS wp_enqueue_style( 'video-enhancer-controls', plugin_dir_url(__FILE__) . '../assets/css/video-controls.css', array(), '1.0.0' ); // 加载JavaScript wp_enqueue_script( 'video-enhancer-controls', plugin_dir_url(__FILE__) . '../assets/js/video-controls.js', array('jquery'), '1.0.0', true ); // 传递AJAX参数到前端 wp_localize_script('video-enhancer-controls', 'vet_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('vet_ajax_nonce') )); } // 加载后台资源 public function enqueue_admin_assets($hook) { // 只在文章编辑页面加载 if ('post.php' !== $hook && 'post-new.php' !== $hook) { return; } wp_enqueue_style( 'video-enhancer-admin', plugin_dir_url(__FILE__) . '../assets/css/admin-styles.css', array(), '1.0.0' ); wp_enqueue_script( 'video-enhancer-admin', plugin_dir_url(__FILE__) . '../assets/js/admin-scripts.js', array('jquery', 'jquery-ui-sortable'), '1.0.0', true ); } // AJAX获取章节数据 public function ajax_get_chapters() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'vet_ajax_nonce')) { wp_die('安全验证失败'); } $video_id = sanitize_text_field($_POST['video_id']); $post_id = get_the_ID(); // 获取章节数据 $chapters = $this->get_chapters_for_video($post_id, $video_id); wp_send_json_success(array( 'chapters' => $chapters )); } // 获取视频章节 private function get_chapters_for_video($post_id, $video_id) { $chapters = get_post_meta($post_id, '_vet_video_chapters_' . $video_id, true); if (empty($chapters) || !is_array($chapters)) { return array(); } // 按时间戳排序 usort($chapters, function($a, $b) { return $a['timestamp'] - $b['timestamp']; }); return $chapters; } } 四、章节标记系统实现 4.1 章节数据结构设计 章节数据需要包含以下信息: 时间戳(秒) 章节标题 章节描述(可选) 缩略图(可选) // includes/class-chapter-manager.php class Video_Enhancer_Chapter_Manager { // 初始化 public function __construct() { add_action('add_meta_boxes', array($this, 'add_chapter_meta_box')); add_action('save_post', array($this, 'save_chapter_data')); } // 添加章节管理元框 public function add_chapter_meta_box() { $post_types = apply_filters('vet_supported_post_types', array('post', 'page')); foreach ($post_types as $post_type) { add_meta_box( 'vet-chapter-manager', '视频章节管理', array($this, 'render_chapter_meta_box'), $post_type, 'normal', 'high' ); } } // 渲染章节管理界面 public function render_chapter_meta_box($post) { // 获取文章中的视频 $videos = $this->extract_videos_from_content($post->post_content); if (empty($videos)) { echo '<p>本文中没有找到视频。请先添加视频到内容中。</p>'; return; } // 添加非ce字段 wp_nonce_field('vet_save_chapters', 'vet_chapter_nonce'); echo '<div class="vet-chapter-manager">'; foreach ($videos as $index => $video) { $video_id = $video['id']; $chapters = $this->get_chapters_for_video($post->ID, $video_id); echo '<div class="vet-video-section">'; echo '<h3>视频 #' . ($index + 1) . '</h3>'; echo '<div class="vet-video-preview">' . $video['html'] . '</div>'; // 章节管理界面 $this->render_chapter_editor($video_id, $chapters); echo '</div>'; } echo '</div>'; } // 渲染章节编辑器 private function render_chapter_editor($video_id, $chapters) { ?> <div class="vet-chapter-editor" data-video-id="<?php echo esc_attr($video_id); ?>"> <div class="vet-chapter-header"> <h4>章节管理</h4> <button type="button" class="button vet-add-chapter">添加章节</button> </div> <div class="vet-chapters-list"> <?php if (!empty($chapters)): ?> <?php foreach ($chapters as $chapter): ?> <div class="vet-chapter-item" data-timestamp="<?php echo esc_attr($chapter['timestamp']); ?>"> <input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][timestamp][]" value="<?php echo esc_attr($chapter['timestamp']); ?>"> <input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][title][]" value="<?php echo esc_attr($chapter['title']); ?>"> <div class="vet-chapter-preview"> <span class="vet-chapter-time"><?php echo $this->format_time($chapter['timestamp']); ?></span> <span class="vet-chapter-title"><?php echo esc_html($chapter['title']); ?></span> </div> <div class="vet-chapter-actions"> <button type="button" class="button vet-edit-chapter">编辑</button> <button type="button" class="button button-link vet-remove-chapter">删除</button> </div> </div> <?php endforeach; ?> <?php else: ?> <p class="vet-no-chapters">暂无章节,点击"添加章节"按钮创建</p> <?php endif; ?> </div> <!-- 章节编辑模板 --> <div class="vet-chapter-template" style="display: none;"> <div class="vet-chapter-item"> <input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][timestamp][]" value=""> <input type="hidden" name="vet_chapters[<?php echo esc_attr($video_id); ?>][title][]" value=""> <div class="vet-chapter-preview"> <span class="vet-chapter-time">00:00</span> <span class="vet-chapter-title">新章节</span> </div> <div class="vet-chapter-actions"> <button type="button" class="button vet-edit-chapter">编辑</button> <button type="button" class="button button-link vet-remove-chapter">删除</button> </div> </div> </div> </div> <?php } // 从内容中提取视频 private function extract_videos_from_content($content) { $videos = array(); // 匹配视频短代码 preg_match_all('/]*]/', $content, $shortcode_matches); foreach ($shortcode_matches[0] as $shortcode) { $video_id = 'video-' . md5($shortcode); $videos[] = array( 'id' => $video_id, 'html' => do_shortcode($shortcode) ); } // 匹配HTML5视频标签 preg_match_all('/<video[^>]*>/', $content, $html_matches); foreach ($html_matches[0] as $index => $video_tag) { $video_id = 'html5-video-' . $index; $videos[] = array( 'id' => $video_id, 'html' => $video_tag . '</video>' ); } return $videos; } // 保存章节数据 public function save_chapter_data($post_id) { // 检查nonce if (!isset($_POST['vet_chapter_nonce']) || !wp_verify_nonce($_POST['vet_chapter_nonce'], 'vet_save_chapters')) { return; } // 检查权限 if (!current_user_can('edit_post', $post_id)) { return; } // 保存章节数据 if (isset($_POST['vet_chapters']) && is_array($_POST['vet_chapters'])) { foreach ($_POST['vet_chapters'] as $video_id => $chapters_data) { $chapters = array(); if (isset($chapters_data['timestamp']) && isset($chapters_data['title'])) { $timestamps = $chapters_data['timestamp']; $titles = $chapters_data['title']; for ($i = 0; $i < count($timestamps); $i++) { if (!empty($timestamps[$i]) && !empty($titles[$i])) { $chapters[] = array( 'timestamp' => floatval($timestamps[$i]), 'title' => sanitize_text_field($titles[$i]) ); } } } // 按时间戳排序 usort($chapters, function($a, $b) { return $a['timestamp'] - $b['timestamp']; }); // 保存到post meta update_post_meta($post_id, '_vet_video_chapters_' . sanitize_key($video_id), $chapters); } } } // 格式化时间显示 private function format_time($seconds) { $mins = floor($seconds / 60); $secs = floor($seconds % 60); return sprintf('%02d:%02d', $mins, $secs); } } 4.2 章节编辑器前端交互 // assets/js/admin-scripts.js (function($) { 'use strict'; // 章节编辑器对象 var ChapterEditor = { init: function() { this.bindEvents(); this.initSortable(); }, bindEvents: function() { // 添加章节按钮 $(document).on('click', '.vet-add-chapter', function() { ChapterEditor.addNewChapter($(this).closest('.vet-chapter-editor')); }); // 编辑章节按钮 $(document).on('click', '.vet-edit-chapter', function() { ChapterEditor.editChapter($(this).closest('.vet-chapter-item')); }); // 删除章节按钮 $(document).on('click', '.vet-remove-chapter', function() { ChapterEditor.removeChapter($(this).closest('.vet-chapter-item')); }); // 视频时间点击事件 $(document).on('click', '.vet-video-preview video', function() { var $video = $(this); var currentTime = $video[0].currentTime; var $editor = $video.closest('.vet-video-section').find('.vet-chapter-editor'); // 在章节编辑器中显示当前时间 ChapterEditor.showCurrentTime($editor, currentTime); }); }, // 初始化可排序 initSortable: function() { $('.vet-chapters-list').sortable({ handle: '.vet-chapter-preview', update: function() { ChapterEditor.updateChapterOrder($(this)); } }); }, // 添加新章节 addNewChapter: function($editor) { var $template = $editor.find('.vet-chapter-template .vet-chapter-item').clone(); var $list = $editor.find('.vet-chapters-list'); // 移除"暂无章节"提示 $list.find('.vet-no-chapters').remove(); // 添加到列表 $list.append($template); // 编辑新章节 ChapterEditor.editChapter($template); }, // 编辑章节 editChapter: function($chapterItem) { var $preview = $chapterItem.find('.vet-chapter-preview'); var $timeSpan = $preview.find('.vet-chapter-time'); var $titleSpan = $preview.find('.vet-chapter-title'); var currentTime = $timeSpan.text(); var currentTitle = $titleSpan.text(); // 创建编辑表单 var $form = $('<div class="vet-chapter-edit-form"></div>'); $form.html(` <div class="vet-edit-fields"> <div class="vet-field"> <label>时间戳 (秒):</label> <input type="number" class="vet-time-input" step="0.1" min="0" value="${ChapterEditor.timeToSeconds(currentTime)}"> </div> <div class="vet-field"> <label>章节标题:</label> <input type="text" class="vet-title-input" value="${currentTitle}"> </div> <div class="vet-edit-actions"> <button type="button" class="button button-primary vet-save-chapter">保存</button> <button type="button" class="button vet-cancel-edit">取消</button> </div> </div> `); // 替换预览为编辑表单 $preview.hide(); $chapterItem.find('.vet-chapter-actions').hide(); $chapterItem.append($form); // 绑定保存事件 $form.find('.vet-save-chapter').on('click', function() { var timeInput = $form.find('.vet-time-input').val(); var titleInput = $form.find('.vet-title-input').val(); if (timeInput && titleInput) { // 更新时间戳和标题 $chapterItem.find('input[name*="timestamp"]').val(timeInput); $chapterItem.find('input[name*="title"]').val(titleInput); // 更新预览显示 $timeSpan.text(ChapterEditor.secondsToTime(timeInput)); $titleSpan.text(titleInput); } // 恢复显示 $form.remove(); $preview.show(); $chapterItem.find('.vet-chapter-actions').show(); }); // 绑定取消事件 $form.find('.vet-cancel-edit').on('click', function() { $form.remove(); $preview.show(); $chapterItem.find('.vet-chapter-actions').show(); }); }, // 删除章节 removeChapter: function($chapterItem) { if (confirm('确定要删除这个章节吗?')) { $chapterItem.remove(); // 如果没有章节了,显示提示 var $list = $chapterItem.closest('.vet-chapters-list'); if ($list.children('.vet-chapter-item').length === 0) { $list.html('<p class="vet-no-chapters">暂无章节,点击"添加章节"按钮创建</p>'); } } }, // 显示当前时间 showCurrentTime: function($editor, currentTime) { // 创建或更新时间提示 var $timeHint = $editor.find('.vet-current-time-hint'); if ($timeHint.length === 0) { $timeHint = $('<div class="vet-current-time-hint"></div>'); $editor.find('.vet-chapter-header').after($timeHint); } var timeStr = ChapterEditor.secondsToTime(currentTime); $timeHint.html('当前视频时间: <strong>' + timeStr + '</strong> (点击视频可获取当前时间)'); // 3秒后淡出 $timeHint.show(); setTimeout(function() { $timeHint.fadeOut(); }, 3000); }, // 更新章节顺序 updateChapterOrder: function($list) { // 重新排序后,可以在这里添加额外的处理逻辑 console.log('章节顺序已更新'); }, // 时间字符串转秒数 timeToSeconds: function(timeStr) { var parts = timeStr.split(':'); if (parts.length === 2) { return parseInt(parts[0]) * 60 + parseInt(parts[1]); } return 0; }, // 秒数转时间字符串 secondsToTime: function(seconds) { var mins = Math.floor(seconds / 60); var secs = Math.floor(seconds % 60); return (mins < 10 ? '0' : '') + mins + ':' + (secs < 10 ? '0' : '') + secs; } }; // 初始化 $(document).ready(function() { ChapterEditor.init(); }); })(jQuery); 4.3 章节标记样式设计 /* assets/css/admin-styles.css */ /* 章节管理器样式 */ .vet-chapter-manager { padding: 15px; background: #f8f9fa; border-radius: 5px; } .vet-video-section { margin-bottom: 30px; padding: 20px; background: white; border: 1px solid #ddd; border-radius: 5px; } .vet-video-section h3 { margin-top: 0; color: #23282d; border-bottom: 2px solid #0073aa; padding-bottom: 10px; } .vet-video-preview { margin: 15px 0; text-align: center; } .vet-video-preview video {

发表评论

WordPress集成教程,连接酒店预订API实现房源查询与在线预订功能

WordPress集成教程:连接酒店预订API实现房源查询与在线预订功能 引言:WordPress作为多功能开发平台的潜力 在当今数字化时代,酒店和住宿行业正经历着前所未有的转型。随着在线预订成为主流,拥有一个功能齐全、用户友好的网站对于酒店经营者至关重要。WordPress作为全球最受欢迎的内容管理系统,早已超越了简单的博客平台定位,通过其强大的可扩展性,可以转变为功能丰富的业务管理工具。 本教程将深入探讨如何通过WordPress代码二次开发,连接酒店预订API,实现专业的房源查询与在线预订功能。我们将从基础概念讲起,逐步深入到实际代码实现,最终打造一个完整的酒店预订系统。无论您是WordPress开发者、酒店经营者还是对网站集成感兴趣的技术爱好者,本教程都将为您提供实用的指导和解决方案。 第一部分:准备工作与环境搭建 1.1 理解酒店预订API的基本原理 酒店预订API(应用程序编程接口)是连接您的网站与酒店库存管理系统之间的桥梁。它允许您的WordPress网站实时访问房型信息、价格、可用性,并处理预订请求。常见的酒店API提供商包括Booking.com、Expedia、Hotelbeds等,也有许多专门为中小型酒店设计的API服务。 API通常通过RESTful架构或SOAP协议工作,使用JSON或XML格式传输数据。在开始集成前,您需要: 注册并获取API密钥和访问令牌 了解API的端点(Endpoints)、请求方法和参数 熟悉API的响应数据结构和错误处理机制 1.2 WordPress开发环境配置 为了顺利进行二次开发,您需要搭建合适的开发环境: 本地开发环境:使用XAMPP、MAMP或Local by Flywheel搭建本地WordPress环境 代码编辑器:推荐使用VS Code、PHPStorm或Sublime Text 必备插件: Advanced Custom Fields(用于自定义字段管理) Custom Post Type UI(创建自定义文章类型) Query Monitor(调试数据库查询和API请求) 启用WordPress调试模式:在wp-config.php中添加以下代码: define('WP_DEBUG', true); define('WP_DEBUG_LOG', true); define('WP_DEBUG_DISPLAY', false); 1.3 创建自定义文章类型管理房源 在WordPress中,我们可以使用自定义文章类型(Custom Post Type)来管理酒店房源: // 在主题的functions.php文件中添加以下代码 function register_hotel_room_post_type() { $labels = array( 'name' => '酒店房源', 'singular_name' => '房源', 'menu_name' => '酒店管理', 'name_admin_bar' => '房源', 'add_new' => '添加新房源', 'add_new_item' => '添加新房源', 'new_item' => '新房源', 'edit_item' => '编辑房源', 'view_item' => '查看房源', 'all_items' => '所有房源', 'search_items' => '搜索房源', 'parent_item_colon' => '父房源:', 'not_found' => '未找到房源', 'not_found_in_trash' => '回收站中未找到房源' ); $args = array( 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => array('slug' => 'hotel-room'), 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => 5, 'menu_icon' => 'dashicons-building', 'supports' => array('title', 'editor', 'thumbnail', 'excerpt') ); register_post_type('hotel_room', $args); } add_action('init', 'register_hotel_room_post_type'); 第二部分:酒店预订API集成核心实现 2.1 API连接类设计与实现 创建一个专门的类来处理与酒店预订API的通信: class Hotel_API_Connector { private $api_key; private $api_secret; private $base_url; private $access_token; private $token_expiry; public function __construct($api_key, $api_secret, $base_url) { $this->api_key = $api_key; $this->api_secret = $api_secret; $this->base_url = $base_url; $this->access_token = $this->get_stored_token(); } // 获取访问令牌 private function authenticate() { $response = wp_remote_post($this->base_url . '/auth', array( 'body' => json_encode(array( 'api_key' => $this->api_key, 'api_secret' => $this->api_secret )), 'headers' => array('Content-Type' => 'application/json'), 'timeout' => 30 )); if (is_wp_error($response)) { error_log('酒店API认证失败: ' . $response->get_error_message()); return false; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (isset($data['access_token'])) { $this->access_token = $data['access_token']; $this->token_expiry = time() + $data['expires_in']; // 存储令牌到数据库 update_option('hotel_api_token', array( 'token' => $this->access_token, 'expiry' => $this->token_expiry )); return true; } return false; } // 获取存储的令牌 private function get_stored_token() { $token_data = get_option('hotel_api_token', array()); if (!empty($token_data) && isset($token_data['expiry']) && $token_data['expiry'] > time()) { return $token_data['token']; } return null; } // 发送API请求 public function make_request($endpoint, $method = 'GET', $params = array()) { // 检查令牌有效性 if (!$this->access_token || $this->token_expiry <= time()) { if (!$this->authenticate()) { return new WP_Error('auth_failed', 'API认证失败'); } } $url = $this->base_url . $endpoint; $args = array( 'method' => $method, 'headers' => array( 'Authorization' => 'Bearer ' . $this->access_token, 'Content-Type' => 'application/json' ), 'timeout' => 30 ); if (!empty($params)) { if ($method === 'GET') { $url = add_query_arg($params, $url); } else { $args['body'] = json_encode($params); } } $response = wp_remote_request($url, $args); if (is_wp_error($response)) { error_log('酒店API请求失败: ' . $response->get_error_message()); return $response; } $response_code = wp_remote_retrieve_response_code($response); $response_body = wp_remote_retrieve_body($response); if ($response_code >= 400) { error_log('酒店API错误响应: ' . $response_body); return new WP_Error('api_error', 'API返回错误: ' . $response_code, $response_body); } return json_decode($response_body, true); } // 搜索可用房源 public function search_rooms($check_in, $check_out, $guests = 2, $rooms = 1) { $endpoint = '/api/v1/rooms/search'; $params = array( 'check_in' => $check_in, 'check_out' => $check_out, 'guests' => $guests, 'rooms' => $rooms ); return $this->make_request($endpoint, 'GET', $params); } // 获取房源详情 public function get_room_details($room_id) { $endpoint = '/api/v1/rooms/' . $room_id; return $this->make_request($endpoint); } // 创建预订 public function create_booking($room_id, $check_in, $check_out, $guest_info, $payment_info) { $endpoint = '/api/v1/bookings'; $params = array( 'room_id' => $room_id, 'check_in' => $check_in, 'check_out' => $check_out, 'guest_info' => $guest_info, 'payment_info' => $payment_info ); return $this->make_request($endpoint, 'POST', $params); } // 取消预订 public function cancel_booking($booking_id) { $endpoint = '/api/v1/bookings/' . $booking_id . '/cancel'; return $this->make_request($endpoint, 'POST'); } } 2.2 房源数据同步机制 为了确保WordPress中的房源信息与API数据同步,我们需要建立定期同步机制: class Hotel_Room_Sync { private $api_connector; public function __construct($api_connector) { $this->api_connector = $api_connector; $this->init_hooks(); } private function init_hooks() { // 每天凌晨同步房源数据 if (!wp_next_scheduled('daily_hotel_room_sync')) { wp_schedule_event(strtotime('02:00:00'), 'daily', 'daily_hotel_room_sync'); } add_action('daily_hotel_room_sync', array($this, 'sync_all_rooms')); // 手动同步触发器 add_action('admin_post_sync_hotel_rooms', array($this, 'handle_manual_sync')); } // 同步所有房源 public function sync_all_rooms() { // 获取API中的所有房源 $api_rooms = $this->api_connector->make_request('/api/v1/rooms'); if (is_wp_error($api_rooms)) { error_log('同步房源失败: ' . $api_rooms->get_error_message()); return false; } $synced_count = 0; $updated_count = 0; foreach ($api_rooms as $api_room) { $room_id = $this->sync_single_room($api_room); if ($room_id) { if (get_post_meta($room_id, '_last_api_sync', true) == $api_room['updated_at']) { $synced_count++; } else { $updated_count++; } } } // 记录同步结果 update_option('last_room_sync', array( 'time' => current_time('mysql'), 'synced' => $synced_count, 'updated' => $updated_count, 'total' => count($api_rooms) )); return true; } // 同步单个房源 private function sync_single_room($api_room) { // 检查是否已存在 $existing_posts = get_posts(array( 'post_type' => 'hotel_room', 'meta_key' => '_api_room_id', 'meta_value' => $api_room['id'], 'posts_per_page' => 1 )); $post_data = array( 'post_title' => $api_room['name'], 'post_content' => $api_room['description'], 'post_type' => 'hotel_room', 'post_status' => 'publish' ); if (!empty($existing_posts)) { $post_data['ID'] = $existing_posts[0]->ID; $post_id = wp_update_post($post_data); } else { $post_id = wp_insert_post($post_data); } if (is_wp_error($post_id) || $post_id == 0) { error_log('创建/更新房源失败: ' . print_r($post_id, true)); return false; } // 更新自定义字段 update_post_meta($post_id, '_api_room_id', $api_room['id']); update_post_meta($post_id, '_room_type', $api_room['type']); update_post_meta($post_id, '_max_guests', $api_room['max_guests']); update_post_meta($post_id, '_base_price', $api_room['base_price']); update_post_meta($post_id, '_amenities', $api_room['amenities']); update_post_meta($post_id, '_last_api_sync', $api_room['updated_at']); // 处理房源图片 if (!empty($api_room['images'])) { $this->sync_room_images($post_id, $api_room['images']); } return $post_id; } // 同步房源图片 private function sync_room_images($post_id, $images) { $attachment_ids = array(); foreach ($images as $index => $image_url) { // 下载并设置特色图片 $attachment_id = $this->upload_image_from_url($image_url, $post_id); if ($attachment_id && $index === 0) { set_post_thumbnail($post_id, $attachment_id); } if ($attachment_id) { $attachment_ids[] = $attachment_id; } } // 保存到图库 if (!empty($attachment_ids)) { update_post_meta($post_id, '_room_gallery', $attachment_ids); } } // 从URL上传图片 private function upload_image_from_url($image_url, $post_id) { require_once(ABSPATH . 'wp-admin/includes/media.php'); require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); // 检查是否已存在相同图片 $existing_attachment = $this->get_attachment_by_url($image_url); if ($existing_attachment) { return $existing_attachment; } // 下载图片 $tmp = download_url($image_url); if (is_wp_error($tmp)) { error_log('下载图片失败: ' . $tmp->get_error_message()); return false; } // 准备文件数据 $file_array = array( 'name' => basename($image_url), 'tmp_name' => $tmp ); // 上传到媒体库 $attachment_id = media_handle_sideload($file_array, $post_id); if (is_wp_error($attachment_id)) { @unlink($file_array['tmp_name']); error_log('上传图片失败: ' . $attachment_id->get_error_message()); return false; } // 保存原始URL以便后续识别 update_post_meta($attachment_id, '_original_image_url', $image_url); return $attachment_id; } // 根据URL获取已存在的附件 private function get_attachment_by_url($url) { global $wpdb; $query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_original_image_url' AND meta_value = %s", $url ); $attachment_id = $wpdb->get_var($query); return $attachment_id ? intval($attachment_id) : false; } } 第三部分:前端功能实现与用户界面 3.1 房源搜索与筛选功能 创建一个短代码来显示房源搜索表单: // 搜索表单短代码 function hotel_search_form_shortcode($atts) { ob_start(); ?> <div class="hotel-search-form"> <form id="hotel-search-form" method="GET" action="<?php echo esc_url(get_permalink(get_option('hotel_search_results_page'))); ?>"> <div class="search-row"> <div class="form-group"> <label for="check-in">入住日期</label> <input type="date" id="check-in" name="check_in" value="<?php echo isset($_GET['check_in']) ? esc_attr($_GET['check_in']) : date('Y-m-d'); ?>" min="<?php echo date('Y-m-d'); ?>" required> </div> <div class="form-group"> <label for="check-out">退房日期</label> <input type="date" id="check-out" name="check_out" value="<?php echo isset($_GET['check_out']) ? esc_attr($_GET['check_out']) : date('Y-m-d', strtotime('+1 day')); ?>" min="<?php echo date('Y-m-d', strtotime('+1 day')); ?>" required> </div> <div class="form-group"> <label for="guests">入住人数</label> <select id="guests" name="guests"> <?php for ($i = 1; $i <= 10; $i++): ?> <option value="<?php echo $i; ?>" <?php selected(isset($_GET['guests']) ? $_GET['guests'] : 2, $i); ?>> <?php echo $i; ?> 人 </option> <?php endfor; ?> </select> </div> <div class="form-group"> <label for="rooms">房间数</label> <select id="rooms" name="rooms"> <?php for ($i = 1; $i <= 5; $i++): ?> <option value="<?php echo $i; ?>" <?php selected(isset($_GET['rooms']) ? $_GET['rooms'] : 1, $i); ?>> <?php echo $i; ?> 间 </option> <?php endfor; ?> </select> </div> </div> <div class="search-row"> <div class="form-group"> <label for="room-type">房型</label> <select id="room-type" name="room_type"> <option value="">所有房型</option> <option value="single" <?php selected(isset($_GET['room_type']) ? $_GET['room_type'] : '', 'single'); ?>>单人间</option> <option value="double" <?php selected(isset($_GET['room_type']) ? $_GET['room_type'] : '', 'double'); ?>>双人间</option> <option value="suite" <?php selected(isset($_GET['room_type']) ? $_GET['room_type'] : '', 'suite'); ?>>套房</option> <option value="family" <?php selected(isset($_GET['room_type']) ? $_GET['room_type'] : '', 'family'); ?>>家庭房</option> </select> </div> <div class="form-group"> <label for="price-min">价格范围</label> <div class="price-range"> <input type="number" id="price-min" name="price_min" placeholder="最低价" value="<?php echo isset($_GET['price_min']) ? esc_attr($_GET['price_min']) : ''; ?>"> <span>至</span> <input type="number" id="price-max" name="price_max" placeholder="最高价" value="<?php echo isset($_GET['price_max']) ? esc_attr($_GET['price_max']) : ''; ?>"> </div> </div> <div class="form-group"> <label for="amenities">设施</label> <select id="amenities" name="amenities[]" multiple class="multi-select"> <option value="wifi" <?php selected(in_array('wifi', isset($_GET['amenities']) ? (array)$_GET['amenities'] : array()), true); ?>>WiFi</option> <option value="breakfast" <?php selected(in_array('breakfast', isset($_GET['amenities']) ? (array)$_GET['amenities'] : array()), true); ?>>早餐</option> <option value="parking" <?php selected(in_array('parking', isset($_GET['amenities']) ? (array)$_GET['amenities'] : array()), true); ?>>停车</option> <option value="pool" <?php selected(in_array('pool', isset($_GET['amenities']) ? (array)$_GET['amenities'] : array()), true); ?>>泳池</option> <option value="gym" <?php selected(in_array('gym', isset($_GET['amenities']) ? (array)$_GET['amenities'] : array()), true); ?>>健身房</option> </select> </div> </div> <div class="form-submit"> <button type="submit" class="search-button">搜索房源</button> <button type="button" class="reset-button" onclick="resetSearchForm()">重置条件</button> </div> </form> </div> <script> function resetSearchForm() { document.getElementById('hotel-search-form').reset(); // 重置日期为默认值 document.getElementById('check-in').value = '<?php echo date("Y-m-d"); ?>'; document.getElementById('check-out').value = '<?php echo date("Y-m-d", strtotime("+1 day")); ?>'; } // 日期验证 document.getElementById('check-in').addEventListener('change', function() { var checkIn = new Date(this.value); var checkOut = new Date(document.getElementById('check-out').value); if (checkIn >= checkOut) { var nextDay = new Date(checkIn); nextDay.setDate(nextDay.getDate() + 1); document.getElementById('check-out').value = nextDay.toISOString().split('T')[0]; } // 设置退房日期的最小值 document.getElementById('check-out').min = this.value; }); </script> <style> .hotel-search-form { background: #f8f9fa; padding: 25px; border-radius: 10px; box-shadow: 0 2px 15px rgba(0,0,0,0.1); margin: 20px 0; } .search-row { display: flex; flex-wrap: wrap; gap: 20px; margin-bottom: 20px; } .form-group { flex: 1; min-width: 200px; } .form-group label { display: block; margin-bottom: 8px; font-weight: 600; color: #333; } .form-group input, .form-group select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; } .price-range { display: flex; align-items: center; gap: 10px; } .price-range input { flex: 1; } .multi-select { height: 100px; } .form-submit { text-align: center; margin-top: 20px; } .search-button, .reset-button { padding: 12px 30px; border: none; border-radius: 5px; font-size: 16px; cursor: pointer; transition: all 0.3s ease; } .search-button { background: #007cba; color: white; margin-right: 10px; } .search-button:hover { background: #005a87; } .reset-button { background: #6c757d; color: white; } .reset-button:hover { background: #545b62; } @media (max-width: 768px) { .search-row { flex-direction: column; } .form-group { min-width: 100%; } } </style> <?php return ob_get_clean(); } add_shortcode('hotel_search', 'hotel_search_form_shortcode'); 3.2 房源搜索结果展示 创建搜索结果页面模板: // 搜索结果处理函数 function display_hotel_search_results() { if (!isset($_GET['check_in']) || !isset($_GET['check_out'])) { return '<div class="hotel-search-message">请先选择入住和退房日期</div>'; } $check_in = sanitize_text_field($_GET['check_in']); $check_out = sanitize_text_field($_GET['check_out']); $guests = isset($_GET['guests']) ? intval($_GET['guests']) : 2; $rooms = isset($_GET['rooms']) ? intval($_GET['rooms']) : 1; // 获取API连接实例 $api_connector = new Hotel_API_Connector( get_option('hotel_api_key'), get_option('hotel_api_secret'), get_option('hotel_api_base_url') ); // 搜索可用房源 $search_params = array( 'check_in' => $check_in, 'check_out' => $check_out, 'guests' => $guests, 'rooms' => $rooms ); // 添加筛选条件 if (!empty($_GET['room_type'])) { $search_params['room_type'] = sanitize_text_field($_GET['room_type']); } if (!empty($_GET['price_min'])) { $search_params['price_min'] = floatval($_GET['price_min']); } if (!empty($_GET['price_max'])) { $search_params['price_max'] = floatval($_GET['price_max']); } if (!empty($_GET['amenities'])) { $search_params['amenities'] = array_map('sanitize_text_field', (array)$_GET['amenities']); } $available_rooms = $api_connector->search_rooms($check_in, $check_out, $guests, $rooms); if (is_wp_error($available_rooms)) { return '<div class="hotel-search-error">搜索失败:' . esc_html($available_rooms->get_error_message()) . '</div>'; } if (empty($available_rooms)) { return '<div class="hotel-search-empty">未找到符合条件的房源,请尝试调整搜索条件。</div>'; } ob_start(); ?> <div class="hotel-search-results"> <div class="search-summary"> <h3>搜索结果</h3> <p>入住:<?php echo date('Y年m月d日', strtotime($check_in)); ?> - 退房:<?php echo date('Y年m月d日', strtotime($check_out)); ?> | 共 <?php echo count($available_rooms); ?> 个房源可选</p> </div> <div class="results-controls"> <div class="sort-options"> <label>排序:</label> <select id="sort-results" onchange="sortRooms(this.value)"> <option value="price_asc">价格从低到高</option> <option value="price_desc">价格从高到低</option> <option value="rating_desc">评分从高到低</option> <option value="name_asc">名称A-Z</option> </select> </div> <div class="view-options"> <button class="view-btn active" onclick="changeView('grid')" title="网格视图"> <span class="dashicons dashicons-grid-view"></span> </button> <button class="view-btn" onclick="changeView('list')" title="列表视图"> <span class="dashicons dashicons-list-view"></span> </button> </div> </div> <div class="rooms-container grid-view"> <?php foreach ($available_rooms as $room): ?> <div class="room-card" data-price="<?php echo $room['price_per_night']; ?>" data-rating="<?php echo $room['rating'] ?? 0; ?>" data-name="<?php echo esc_attr($room['name']); ?>"> <div class="room-image"> <?php if (!empty($room['image'])): ?> <img src="<?php echo esc_url($room['image']); ?>" alt="<?php echo esc_attr($room['name']); ?>"> <?php else: ?> <div class="no-image">暂无图片</div> <?php endif; ?> <?php if (isset($room['discount']) && $room['discount'] > 0): ?> <span class="discount-badge">-<?php echo $room['discount']; ?>%</span> <?php endif; ?> </div> <div class="room-info"> <h3 class="room-title"> <a href="<?php echo get_permalink(); ?>?room_id=<?php echo $room['id']; ?>&check_in=<?php echo $check_in; ?>&check_out=<?php echo $check_out; ?>"> <?php echo esc_html($room['name']); ?> </a> </h3> <div class="room-meta"> <span class="room-type"><?php echo esc_html($room['type']); ?></span> <span class="room-guests">最多 <?php echo $room['max_guests']; ?> 人</span> <span class="room-size"><?php echo $room['size'] ?? '--'; ?> ㎡</span> </div> <div class="room-amenities"> <?php if (!empty($room['amenities'])): ?> <?php $amenities_display = array_slice($room['amenities'], 0, 3); foreach ($amenities_display as $amenity): ?> <span class="amenity-tag"><?php echo esc_html($amenity); ?></span> <?php endforeach; ?> <?php if (count($room['amenities']) > 3): ?> <span class="amenity-more">+<?php echo count($room['amenities']) - 3; ?> 更多</span> <?php endif; ?> <?php endif; ?> </div> <div class="room-rating"> <?php if (isset($room['rating'])): ?> <div class="stars"> <?php $full_stars = floor($room['rating']); $half_star = ($room['rating'] - $full_stars) >= 0.5; for ($i = 1; $i <= 5; $i++): if ($i <= $full_stars): ?> <span class="dashicons dashicons-star-filled"></span> <?php elseif ($half_star && $i == $full_stars + 1): ?> <span class="dashicons dashicons-star-half"></span> <?php else: ?> <span class="dashicons dashicons-star-empty"></span> <?php endif; endfor; ?> </div> <span class="rating-score"><?php echo number_format($room['rating'], 1); ?></span> <?php endif; ?> </div> <div class="room-price"> <div class="price-main"> <span class="currency">¥</span> <span class="amount"><?php echo number_format($room['price_per_night'], 0); ?></span> <span class="period">/晚</span> </div> <?php if (isset($room['original_price']) && $room['original_price'] > $room['price_per_night']): ?> <div class="price-original"> ¥<?php echo number_format($room['original_price'], 0); ?> </div> <?php endif; ?> <div class="price-total"> 总计:¥<?php echo number_format($room['total_price'], 0); ?> </div> </div> <div class="room-actions"> <a href="<?php echo get_permalink(); ?>?room_id=<?php echo $room['id']; ?>&check_in=<?php echo $check_in; ?>&check_out=<?php echo $check_out; ?>&action=book" class="book-button"> 立即预订 </a> <button class="view-details" onclick="showRoomDetails('<?php echo $room['id']; ?>')"> 查看详情 </button> </div> </div> </div> <?php endforeach; ?> </div> <!-- 分页 --> <?php if (count($available_rooms) > 12): ?> <div class="pagination"> <button class="page-btn prev" disabled>上一页</button> <span class="page-numbers">1 / <?php echo ceil(count($available_rooms) / 12); ?></span> <button class="page-btn next">下一页</button> </div> <?php endif; ?> </div> <!-- 房源详情模态框 --> <div id="room-details-modal" class="modal"> <div class="modal-content"> <span class="close-modal">&times;</span> <div id="room-details-content"></div> </div> </div> <script> // 排序功能 function sortRooms(sortBy) { const container = document.querySelector('.rooms-container'); const rooms = Array.from(container.querySelectorAll('.room-card')); rooms.sort((a, b) => { let aValue, bValue; switch(sortBy) { case 'price_asc': aValue = parseFloat(a.dataset.price); bValue = parseFloat(b.dataset.price); return aValue - bValue; case 'price_desc': aValue = parseFloat(a.dataset.price); bValue = parseFloat(b.dataset.price); return bValue - aValue; case 'rating_desc': aValue = parseFloat(a.dataset.rating); bValue = parseFloat(b.dataset.rating); return bValue - aValue; case 'name_asc': aValue = a.dataset.name.toLowerCase(); bValue = b.dataset.name.toLowerCase(); return aValue.localeCompare(bValue); default: return 0; } }); // 重新排列DOM元素 rooms.forEach(room => container.appendChild(room)); } // 切换视图 function changeView(viewType) { const container = document.querySelector('.rooms-container'); const buttons = document.querySelectorAll('.view-btn'); buttons.forEach(btn => btn.classList.remove('active')); event.target.classList.add('active'); container.classList.remove('grid-view', 'list-view'); container.classList.add(viewType + '-view'); } // 显示房源详情 function showRoomDetails(roomId) { // 使用AJAX获取房源详情 fetch('<?php echo admin_url('admin-ajax.php'); ?>', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: 'action=get_room_details&room_id=' + roomId + '&check_in=<?php echo $check_in; ?>' +

发表评论

详细教程,为WordPress网站开发线上研讨会预约与虚拟会议室管理

详细教程:为WordPress网站开发线上研讨会预约与虚拟会议室管理系统 引言:线上活动管理的数字化需求 在数字化时代,线上研讨会已成为企业、教育机构和组织进行知识分享、产品发布和团队协作的重要方式。然而,许多WordPress网站所有者面临一个共同挑战:如何高效管理线上活动的预约流程和虚拟会议室?虽然市场上有一些插件可用,但它们往往功能有限、价格昂贵或不符合特定需求。 本教程将指导您通过WordPress代码二次开发,创建一个完整的线上研讨会预约与虚拟会议室管理系统。这个解决方案不仅成本效益高,而且完全可定制,能够满足您的特定需求。我们将从系统设计开始,逐步实现预约管理、虚拟会议室集成、自动通知和数据分析等功能。 第一部分:系统架构设计与环境准备 1.1 需求分析与功能规划 在开始编码之前,我们需要明确系统需求: 预约管理功能:用户可查看 upcoming 研讨会,选择并注册参加 虚拟会议室集成:自动生成或连接Zoom、Google Meet等平台会议室 自动化工作流:注册确认、提醒邮件、会议链接发送 管理后台:活动创建、参与者管理、数据分析 用户界面:直观的前端展示和响应式设计 1.2 开发环境配置 首先确保您的开发环境满足以下要求: WordPress 5.0以上版本 PHP 7.4以上版本 MySQL 5.6以上或MariaDB 10.0以上 代码编辑器(如VS Code、Sublime Text) 本地开发环境(如XAMPP、MAMP或Local by Flywheel) 1.3 创建自定义插件 为了避免主题更新导致代码丢失,我们将创建一个独立插件: 在wp-content/plugins/目录下创建新文件夹virtual-seminar-manager 创建主插件文件virtual-seminar-manager.php: <?php /** * Plugin Name: Virtual Seminar Manager * Plugin URI: https://yourwebsite.com/ * Description: 完整的线上研讨会预约与虚拟会议室管理系统 * Version: 1.0.0 * Author: Your Name * License: GPL v2 or later * Text Domain: virtual-seminar */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('VSM_VERSION', '1.0.0'); define('VSM_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('VSM_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 require_once VSM_PLUGIN_DIR . 'includes/class-init.php'; VSM_Init::register_services(); 第二部分:数据库设计与自定义帖子类型 2.1 创建数据库表 我们需要两个自定义表来存储研讨会和预约数据。在插件初始化类中添加以下代码: // 在includes/class-init.php中 class VSM_Init { public static function register_services() { // 注册激活钩子 register_activation_hook(__FILE__, [self::class, 'activate']); // 注册其他服务 add_action('init', [self::class, 'register_post_types']); add_action('init', [self::class, 'register_taxonomies']); } public static function activate() { self::create_tables(); flush_rewrite_rules(); } private static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 研讨会表 $seminars_table = $wpdb->prefix . 'vsm_seminars'; $sql1 = "CREATE TABLE IF NOT EXISTS $seminars_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, post_id bigint(20) NOT NULL, start_time datetime NOT NULL, end_time datetime NOT NULL, max_participants int(11) DEFAULT 100, current_participants int(11) DEFAULT 0, meeting_platform varchar(50) DEFAULT 'zoom', meeting_id varchar(255), meeting_password varchar(100), meeting_url text, status varchar(20) DEFAULT 'scheduled', created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY post_id (post_id), KEY start_time (start_time) ) $charset_collate;"; // 预约表 $bookings_table = $wpdb->prefix . 'vsm_bookings'; $sql2 = "CREATE TABLE IF NOT EXISTS $bookings_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, seminar_id mediumint(9) NOT NULL, user_id bigint(20), user_email varchar(100) NOT NULL, user_name varchar(100) NOT NULL, booking_time datetime DEFAULT CURRENT_TIMESTAMP, confirmation_code varchar(32), attendance_status varchar(20) DEFAULT 'registered', join_time datetime, leave_time datetime, notes text, PRIMARY KEY (id), KEY seminar_id (seminar_id), KEY user_email (user_email), KEY confirmation_code (confirmation_code) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql1); dbDelta($sql2); } } 2.2 注册自定义帖子类型 创建研讨会自定义帖子类型,使其在WordPress后台可管理: // 在includes/class-post-types.php中 class VSM_Post_Types { public static function register() { // 注册研讨会帖子类型 $labels = array( 'name' => __('研讨会', 'virtual-seminar'), 'singular_name' => __('研讨会', 'virtual-seminar'), 'menu_name' => __('研讨会管理', 'virtual-seminar'), 'add_new' => __('添加新研讨会', 'virtual-seminar'), 'add_new_item' => __('添加新研讨会', 'virtual-seminar'), 'edit_item' => __('编辑研讨会', 'virtual-seminar'), 'new_item' => __('新研讨会', 'virtual-seminar'), 'view_item' => __('查看研讨会', 'virtual-seminar'), 'search_items' => __('搜索研讨会', 'virtual-seminar'), 'not_found' => __('未找到研讨会', 'virtual-seminar'), 'not_found_in_trash' => __('回收站中未找到研讨会', 'virtual-seminar') ); $args = array( 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => array('slug' => 'seminar'), 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => 25, 'menu_icon' => 'dashicons-video-alt3', 'supports' => array('title', 'editor', 'thumbnail', 'excerpt'), 'show_in_rest' => true ); register_post_type('seminar', $args); } } 第三部分:前端预约界面开发 3.1 创建预约短代码 为了让用户可以在任何页面添加预约界面,我们创建一个短代码: // 在includes/class-shortcodes.php中 class VSM_Shortcodes { public static function init() { add_shortcode('seminar_booking', [self::class, 'seminar_booking_shortcode']); add_shortcode('seminar_list', [self::class, 'seminar_list_shortcode']); } public static function seminar_booking_shortcode($atts) { // 解析短代码属性 $atts = shortcode_atts(array( 'seminar_id' => 0, 'show_title' => 'yes' ), $atts, 'seminar_booking'); // 如果没有指定研讨会ID,尝试从当前页面获取 $seminar_id = $atts['seminar_id']; if (!$seminar_id && get_post_type() === 'seminar') { $seminar_id = get_the_ID(); } if (!$seminar_id) { return '<p class="vsm-error">' . __('未指定研讨会', 'virtual-seminar') . '</p>'; } // 检查研讨会是否存在且可预约 $seminar_data = self::get_seminar_data($seminar_id); if (!$seminar_data) { return '<p class="vsm-error">' . __('研讨会不存在或已结束', 'virtual-seminar') . '</p>'; } // 输出预约表单 ob_start(); include VSM_PLUGIN_DIR . 'templates/booking-form.php'; return ob_get_clean(); } private static function get_seminar_data($post_id) { global $wpdb; $table_name = $wpdb->prefix . 'vsm_seminars'; $seminar = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE post_id = %d AND status = 'scheduled'", $post_id )); if (!$seminar) { return false; } // 检查是否还有空位 if ($seminar->current_participants >= $seminar->max_participants) { return false; } return $seminar; } } 3.2 设计预约表单模板 创建templates/booking-form.php模板文件: <?php /** * 研讨会预约表单模板 */ // 确保从短代码中调用 if (!isset($seminar_data)) { return; } // 获取帖子信息 $post = get_post($seminar_data->post_id); $title = $post->post_title; $excerpt = $post->post_excerpt; $thumbnail = get_the_post_thumbnail($post->ID, 'medium'); // 格式化时间 $start_time = date_i18n('Y年m月d日 H:i', strtotime($seminar_data->start_time)); $end_time = date_i18n('H:i', strtotime($seminar_data->end_time)); $remaining = $seminar_data->max_participants - $seminar_data->current_participants; // 处理表单提交 $submitted = false; $error = ''; $success = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['vsm_booking_nonce'])) { if (wp_verify_nonce($_POST['vsm_booking_nonce'], 'vsm_booking_action')) { $result = VSM_Booking_Handler::process_booking($_POST, $seminar_data); if ($result['success']) { $success = $result['message']; $submitted = true; } else { $error = $result['message']; } } } ?> <div class="vsm-booking-container"> <?php if ($atts['show_title'] === 'yes') : ?> <div class="vsm-seminar-header"> <?php if ($thumbnail) : ?> <div class="vsm-thumbnail"> <?php echo $thumbnail; ?> </div> <?php endif; ?> <h2 class="vsm-title"><?php echo esc_html($title); ?></h2> <?php if ($excerpt) : ?> <div class="vsm-excerpt"> <?php echo wpautop($excerpt); ?> </div> <?php endif; ?> <div class="vsm-meta"> <div class="vsm-meta-item"> <span class="dashicons dashicons-calendar-alt"></span> <span><?php echo $start_time . ' - ' . $end_time; ?></span> </div> <div class="vsm-meta-item"> <span class="dashicons dashicons-groups"></span> <span><?php printf(__('剩余席位: %d/%d', 'virtual-seminar'), $remaining, $seminar_data->max_participants); ?></span> </div> </div> </div> <?php endif; ?> <?php if ($success) : ?> <div class="vsm-alert vsm-success"> <?php echo $success; ?> </div> <?php elseif (!$submitted) : ?> <?php if ($error) : ?> <div class="vsm-alert vsm-error"> <?php echo $error; ?> </div> <?php endif; ?> <form id="vsm-booking-form" method="post" class="vsm-booking-form"> <?php wp_nonce_field('vsm_booking_action', 'vsm_booking_nonce'); ?> <input type="hidden" name="seminar_id" value="<?php echo $seminar_data->id; ?>"> <input type="hidden" name="post_id" value="<?php echo $seminar_data->post_id; ?>"> <div class="vsm-form-group"> <label for="vsm_user_name"><?php _e('姓名', 'virtual-seminar'); ?> *</label> <input type="text" id="vsm_user_name" name="user_name" required placeholder="<?php _e('请输入您的姓名', 'virtual-seminar'); ?>"> </div> <div class="vsm-form-group"> <label for="vsm_user_email"><?php _e('电子邮箱', 'virtual-seminar'); ?> *</label> <input type="email" id="vsm_user_email" name="user_email" required placeholder="<?php _e('请输入您的邮箱地址', 'virtual-seminar'); ?>"> <small class="vsm-help-text"><?php _e('会议链接将发送到此邮箱', 'virtual-seminar'); ?></small> </div> <div class="vsm-form-group"> <label for="vsm_user_phone"><?php _e('手机号码', 'virtual-seminar'); ?></label> <input type="tel" id="vsm_user_phone" name="user_phone" placeholder="<?php _e('请输入您的手机号码', 'virtual-seminar'); ?>"> </div> <div class="vsm-form-group"> <label for="vsm_user_company"><?php _e('公司/机构', 'virtual-seminar'); ?></label> <input type="text" id="vsm_user_company" name="user_company" placeholder="<?php _e('请输入您的公司或机构名称', 'virtual-seminar'); ?>"> </div> <div class="vsm-form-group"> <label for="vsm_notes"><?php _e('备注或问题', 'virtual-seminar'); ?></label> <textarea id="vsm_notes" name="notes" rows="3" placeholder="<?php _e('如有特殊需求或问题,请在此说明', 'virtual-seminar'); ?>"></textarea> </div> <div class="vsm-form-group vsm-consent"> <input type="checkbox" id="vsm_consent" name="consent" required> <label for="vsm_consent"> <?php _e('我同意接收关于此研讨会的通知和后续信息', 'virtual-seminar'); ?> </label> </div> <div class="vsm-form-submit"> <button type="submit" class="vsm-submit-btn"> <?php _e('立即预约', 'virtual-seminar'); ?> </button> </div> </form> <?php endif; ?> </div> 第四部分:虚拟会议室集成 4.1 Zoom API集成 我们将集成Zoom API来自动创建会议室。首先创建Zoom API处理类: // 在includes/class-zoom-integration.php中 class VSM_Zoom_Integration { private $api_key; private $api_secret; private $account_id; // JWT应用需要 private $client_id; // OAuth应用需要 private $client_secret; // OAuth应用需要 private $access_token; public function __construct() { $options = get_option('vsm_zoom_settings'); $this->api_key = $options['api_key'] ?? ''; $this->api_secret = $options['api_secret'] ?? ''; $this->account_id = $options['account_id'] ?? ''; $this->client_id = $options['client_id'] ?? ''; $this->client_secret = $options['client_secret'] ?? ''; // 获取或刷新访问令牌 $this->access_token = $this->get_access_token(); } /** * 获取OAuth访问令牌 */ private function get_access_token() { $token_data = get_transient('vsm_zoom_access_token'); if (!$token_data) { // 使用OAuth 2.0获取新令牌 $response = wp_remote_post('https://zoom.us/oauth/token', [ 'headers' => [ 'Authorization' => 'Basic ' . base64_encode($this->client_id . ':' . $this->client_secret), 'Content-Type' => 'application/x-www-form-urlencoded' ], 'body' => [ 'grant_type' => 'account_credentials', 'account_id' => $this->account_id ] ]); if (!is_wp_error($response)) { $body = json_decode(wp_remote_retrieve_body($response)); if (isset($body->access_token)) { $token_data = $body->access_token; // 令牌通常有效1小时,我们设置55分钟过期 set_transient('vsm_zoom_access_token', $token_data, 55 * MINUTE_IN_SECONDS); } } } return $token_data; } /** * 创建Zoom会议 */ public function create_meeting($seminar_data) { /v2/users/me/meetings', [ 'headers' => [ 'Authorization' => 'Bearer ' . $this->access_token, 'Content-Type' => 'application/json' ], 'body' => json_encode([ 'topic' => $seminar_data['title'], 'type' => 2, // 预定会议 'start_time' => $seminar_data['start_time'], 'duration' => $seminar_data['duration'], 'timezone' => wp_timezone_string(), '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' // 可选:none, local, cloud ] ]) ]); if (is_wp_error($response)) { return [ 'success' => false, 'error' => $response->get_error_message() ]; } $body = json_decode(wp_remote_retrieve_body($response), true); if (isset($body['code'])) { return [ 'success' => false, 'error' => $body['message'] ]; } return [ 'success' => true, 'meeting_id' => $body['id'], 'meeting_password' => $body['password'], 'join_url' => $body['join_url'], 'start_url' => $body['start_url'] // 主持人链接 ]; } /** * 更新Zoom会议 */ public function update_meeting($meeting_id, $data) { $response = wp_remote_request("https://api.zoom.us/v2/meetings/{$meeting_id}", [ 'method' => 'PATCH', 'headers' => [ 'Authorization' => 'Bearer ' . $this->access_token, 'Content-Type' => 'application/json' ], 'body' => json_encode($data) ]); return !is_wp_error($response); } /** * 删除Zoom会议 */ public function delete_meeting($meeting_id) { $response = wp_remote_request("https://api.zoom.us/v2/meetings/{$meeting_id}", [ 'method' => 'DELETE', 'headers' => [ 'Authorization' => 'Bearer ' . $this->access_token ] ]); return !is_wp_error($response); } } ### 4.2 Google Meet集成(替代方案) // 在includes/class-google-meet-integration.php中class VSM_Google_Meet_Integration { private $client; private $service; public function __construct() { $this->init_client(); } private function init_client() { require_once VSM_PLUGIN_DIR . 'vendor/autoload.php'; // 需要Google API客户端库 $options = get_option('vsm_google_settings'); $credentials = $options['credentials'] ?? ''; if (!$credentials) { return false; } try { $this->client = new Google_Client(); $this->client->setApplicationName('Virtual Seminar Manager'); $this->client->setAuthConfig(json_decode($credentials, true)); $this->client->addScope(Google_Service_Calendar::CALENDAR); // 检查是否有存储的令牌 $token_path = VSM_PLUGIN_DIR . 'tokens/google-token.json'; if (file_exists($token_path)) { $access_token = json_decode(file_get_contents($token_path), true); $this->client->setAccessToken($access_token); } // 如果令牌过期,刷新它 if ($this->client->isAccessTokenExpired()) { if ($this->client->getRefreshToken()) { $this->client->fetchAccessTokenWithRefreshToken(); file_put_contents($token_path, json_encode($this->client->getAccessToken())); } } $this->service = new Google_Service_Calendar($this->client); return true; } catch (Exception $e) { error_log('Google Meet初始化失败: ' . $e->getMessage()); return false; } } /** * 创建Google Calendar事件并生成Meet链接 */ public function create_meeting($seminar_data) { if (!$this->service) { return [ 'success' => false, 'error' => 'Google服务未初始化' ]; } try { // 创建日历事件 $event = new Google_Service_Calendar_Event([ 'summary' => $seminar_data['title'], 'description' => $seminar_data['description'], 'start' => [ 'dateTime' => $seminar_data['start_time'], 'timeZone' => wp_timezone_string(), ], 'end' => [ 'dateTime' => $seminar_data['end_time'], 'timeZone' => wp_timezone_string(), ], 'conferenceData' => [ 'createRequest' => [ 'requestId' => uniqid(), 'conferenceSolutionKey' => [ 'type' => 'hangoutsMeet' ] ] ], 'attendees' => [ ['email' => $seminar_data['organizer_email']] ], 'reminders' => [ 'useDefault' => false, 'overrides' => [ ['method' => 'email', 'minutes' => 24 * 60], // 提前1天 ['method' => 'popup', 'minutes' => 30] // 提前30分钟 ] ] ]); $created_event = $this->service->events->insert('primary', $event, [ 'conferenceDataVersion' => 1 ]); return [ 'success' => true, 'meeting_id' => $created_event->getId(), 'join_url' => $created_event->getHangoutLink(), 'html_link' => $created_event->getHtmlLink() ]; } catch (Exception $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } } } ## 第五部分:预约处理与数据库操作 ### 5.1 预约处理类 // 在includes/class-booking-handler.php中class VSM_Booking_Handler { public static function process_booking($data, $seminar_data) { global $wpdb; // 验证数据 $validation = self::validate_booking_data($data); if (!$validation['valid']) { return [ 'success' => false, 'message' => $validation['message'] ]; } // 检查是否已预约 $bookings_table = $wpdb->prefix . 'vsm_bookings'; $existing_booking = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $bookings_table WHERE seminar_id = %d AND user_email = %s", $seminar_data->id, sanitize_email($data['user_email']) )); if ($existing_booking > 0) { return [ 'success' => false, 'message' => __('您已经预约了此研讨会', 'virtual-seminar') ]; } // 检查是否还有空位 if ($seminar_data->current_participants >= $seminar_data->max_participants) { return [ 'success' => false, 'message' => __('研讨会名额已满', 'virtual-seminar') ]; } // 生成确认码 $confirmation_code = wp_generate_password(12, false); // 插入预约记录 $insert_result = $wpdb->insert( $bookings_table, [ 'seminar_id' => $seminar_data->id, 'user_id' => is_user_logged_in() ? get_current_user_id() : 0, 'user_email' => sanitize_email($data['user_email']), 'user_name' => sanitize_text_field($data['user_name']), 'confirmation_code' => $confirmation_code, 'notes' => sanitize_textarea_field($data['notes'] ?? '') ], ['%d', '%d', '%s', '%s', '%s', '%s'] ); if (!$insert_result) { return [ 'success' => false, 'message' => __('预约失败,请稍后重试', 'virtual-seminar') ]; } $booking_id = $wpdb->insert_id; // 更新研讨会参与人数 $seminars_table = $wpdb->prefix . 'vsm_seminars'; $wpdb->update( $seminars_table, ['current_participants' => $seminar_data->current_participants + 1], ['id' => $seminar_data->id], ['%d'], ['%d'] ); // 发送确认邮件 self::send_confirmation_email($booking_id, $confirmation_code, $seminar_data, $data); // 发送给管理员的通知 self::send_admin_notification($booking_id, $seminar_data, $data); return [ 'success' => true, 'message' => __('预约成功!确认邮件已发送到您的邮箱。', 'virtual-seminar'), 'booking_id' => $booking_id, 'confirmation_code' => $confirmation_code ]; } private static function validate_booking_data($data) { // 验证必填字段 $required_fields = ['user_name', 'user_email', 'seminar_id']; foreach ($required_fields as $field) { if (empty($data[$field])) { return [ 'valid' => false, 'message' => __('请填写所有必填字段', 'virtual-seminar') ]; } } // 验证邮箱格式 if (!is_email($data['user_email'])) { return [ 'valid' => false, 'message' => __('请输入有效的邮箱地址', 'virtual-seminar') ]; } // 验证同意条款 if (!isset($data['consent']) || $data['consent'] !== 'on') { return [ 'valid' => false, 'message' => __('请同意接收研讨会相关信息', 'virtual-seminar') ]; } return ['valid' => true]; } private static function send_confirmation_email($booking_id, $confirmation_code, $seminar_data, $user_data) { $post = get_post($seminar_data->post_id); $start_time = date_i18n('Y年m月d日 H:i', strtotime($seminar_data->start_time)); $to = $user_data['user_email']; $subject = sprintf(__('研讨会预约确认: %s', 'virtual-seminar'), $post->post_title); // 构建邮件内容 $message = sprintf(__('尊敬的 %s,', 'virtual-seminar'), $user_data['user_name']) . "nn"; $message .= sprintf(__('您已成功预约研讨会: %s', 'virtual-seminar'), $post->post_title) . "n"; $message .= sprintf(__('时间: %s', 'virtual-seminar'), $start_time) . "nn"; // 如果有会议链接,添加到邮件中 if (!empty($seminar_data->meeting_url)) { $message .= __('会议链接: ', 'virtual-seminar') . $seminar_data->meeting_url . "n"; if (!empty($seminar_data->meeting_password)) { $message .= __('会议密码: ', 'virtual-seminar') . $seminar_data->meeting_password . "n"; } } else { $message .= __('会议链接将在研讨会开始前发送给您。', 'virtual-seminar') . "n"; } $message .= "n" . __('您的预约确认码: ', 'virtual-seminar') . $confirmation_code . "n"; $message .= __('如需取消预约,请访问: ', 'virtual-seminar') . home_url('/cancel-booking/?code=' . $confirmation_code) . "nn"; $message .= __('感谢您的参与!', 'virtual-seminar') . "n"; $message .= get_bloginfo('name'); // 设置邮件头 $headers = ['Content-Type: text/plain; charset=UTF-8']; // 发送邮件 wp_mail($to, $subject, $message, $headers); // 记录邮件发送日志 self::log_email_sent($booking_id, 'confirmation', $to); } private static function send_admin_notification($booking_id, $seminar_data, $user_data) { $admin_email = get_option('admin_email'); $post = get_post($seminar_data->post_id); $subject = sprintf(__('新研讨会预约: %s', 'virtual-seminar'), $post->post_title); $message = sprintf(__('有新的研讨会预约:', 'virtual-seminar')) . "nn"; $message .= sprintf(__('研讨会: %s', 'virtual-seminar'), $post->post_title) . "n"; $message .= sprintf(__('预约人: %s', 'virtual-seminar'), $user_data['user_name']) . "n"; $message .= sprintf(__('邮箱: %s', 'virtual-seminar'), $user_data['user_email']) . "n"; $message .= sprintf(__('预约时间: %s', 'virtual-seminar'), current_time('mysql')) . "n"; $message .= sprintf(__('预约ID: %d', 'virtual-seminar'), $booking_id) . "nn"; if (!empty($user_data['user_phone'])) { $message .= sprintf(__('电话: %s', 'virtual-seminar'), $user_data['user_phone']) . "n"; } if (!empty($user_data['user_company'])) { $message .= sprintf(__('公司: %s', 'virtual-seminar'), $user_data['user_company']) . "n"; } if (!empty($user_data['notes'])) { $message .= sprintf(__('备注: %s', 'virtual-seminar'), $user_data['notes']) . "n"; } $message .= "n" . __('查看所有预约: ', 'virtual-seminar') . admin_url('admin.php?page=vsm-bookings&seminar_id=' . $seminar_data->id); wp_mail($admin_email, $subject, $message); } private static function log_email_sent($booking_id, $type, $recipient) { global $wpdb; $table_name = $wpdb->prefix . 'vsm_email_logs'; $wpdb->insert( $table_name, [ 'booking_id' => $booking_id, 'email_type' => $type, 'recipient' => $recipient, 'sent_at' => current_time('mysql') ], ['%d', '%s', '%s', '%s'] ); } } ## 第六部分:后台管理界面 ### 6.1 创建管理菜单 // 在includes/class-admin.php中class VSM_Admin { public static function init() { add_action('admin_menu', [self::class, 'add_admin_menu']); add_action('admin_enqueue_scripts', [self::class, 'enqueue_admin_scripts']); add_action('add_meta_boxes', [self::class, 'add_seminar_meta_boxes']); add_action('save_post_seminar', [self::class, 'save_seminar_meta'], 10, 2); } public static function add_admin_menu() { // 主菜单 add_menu_page( __('研讨会管理', 'virtual-seminar'), __('研讨会管理', 'virtual-seminar'), 'manage_options', 'vsm-dashboard', [self::class, 'render_dashboard'], 'dashicons-video-alt3', 30 ); // 子菜单 add_submenu_page( 'vsm-dashboard', __('所有预约', 'virtual-seminar'), __('所有预约', 'virtual-seminar'), 'manage_options', 'vsm-bookings', [self::class, 'render_bookings_page'] ); add_submenu_page( 'vsm-dashboard', __('设置', 'virtual-seminar'), __('设置', 'virtual-seminar'), 'manage_options', 'vsm-settings', [self::class, 'render_settings_page'] ); add_submenu_page( 'vsm-dashboard', __('报告', 'virtual-seminar'), __('报告', 'virtual-seminar'), 'manage_options', 'vsm-reports', [self::class, 'render_reports_page'] ); } public static function render_dashboard() { global $wpdb; // 获取统计数据 $seminars_table = $wpdb->prefix . 'vsm_seminars'; $bookings_table = $wpdb->prefix . 'vsm_bookings'; $total_seminars = $wpdb->get_var("SELECT COUNT(*) FROM $seminars_table"); $upcoming_seminars = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $seminars_table WHERE start_time > %s", current_time('mysql') )); $total_bookings = $wpdb->get_var("SELECT COUNT(*) FROM $bookings_table"); $today_bookings = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $bookings_table WHERE DATE(booking_time) = %s", current_time('Y-m-d') ));

发表评论

一步步教你,集成在线简易信息图表与数据报告生成工具到网站

一步步教你,集成在线简易信息图表与数据报告生成工具到网站,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:为什么你的网站需要信息图表与数据报告生成工具? 在当今数据驱动的互联网时代,可视化数据呈现已成为网站吸引用户、提升专业形象的关键手段。信息图表(Infographics)能够将复杂数据转化为直观、易懂的视觉形式,而数据报告生成工具则允许用户根据自身需求创建个性化内容。对于企业网站、博客、教育平台或任何需要展示数据的在线平台而言,集成这些功能可以显著提升用户体验和网站价值。 WordPress作为全球最流行的内容管理系统,拥有强大的扩展性和灵活性。通过代码二次开发,我们可以在WordPress网站上集成在线简易信息图表与数据报告生成工具,实现专业级的数据可视化功能,而无需依赖昂贵的外部服务或复杂的第三方插件。 本文将详细介绍如何通过WordPress代码二次开发,一步步实现这些功能,让你的网站具备创建和展示信息图表、生成数据报告的能力。 第一部分:准备工作与环境搭建 1.1 理解WordPress开发基础 在开始之前,我们需要了解WordPress开发的基本概念: 主题与插件结构:WordPress功能扩展主要通过主题和插件实现 钩子(Hooks)系统:动作钩子(Action Hooks)和过滤器钩子(Filter Hooks)是WordPress扩展的核心机制 短代码(Shortcodes):允许在文章和页面中嵌入动态内容 自定义文章类型(Custom Post Types):用于创建特殊类型的内容 REST API:WordPress提供的API接口,用于前后端数据交互 1.2 开发环境配置 为了安全地进行代码开发,建议搭建本地开发环境: 本地服务器环境:使用XAMPP、MAMP或Local by Flywheel等工具 代码编辑器:推荐VS Code、PHPStorm或Sublime Text 版本控制:安装Git用于代码版本管理 浏览器开发者工具:熟悉Chrome或Firefox的开发者工具 1.3 创建开发用子主题或插件 为了避免直接修改主题文件导致更新时丢失更改,我们创建一个专用插件: <?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('INFOGRAPHIC_TOOL_VERSION', '1.0.0'); define('INFOGRAPHIC_TOOL_PATH', plugin_dir_path(__FILE__)); define('INFOGRAPHIC_TOOL_URL', plugin_dir_url(__FILE__)); 第二部分:创建信息图表生成器核心功能 2.1 设计数据库结构 我们需要存储用户创建的信息图表模板和数据。在插件激活时创建必要的数据库表: // 激活插件时创建数据库表 register_activation_hook(__FILE__, 'infographic_tool_create_tables'); function infographic_tool_create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'infographic_templates'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, template_data longtext NOT NULL, template_type varchar(50) DEFAULT 'chart', created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 创建图表数据表 $data_table = $wpdb->prefix . 'infographic_charts'; $sql2 = "CREATE TABLE IF NOT EXISTS $data_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, template_id mediumint(9) NOT NULL, chart_data longtext NOT NULL, chart_options longtext NOT NULL, user_id bigint(20), created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), FOREIGN KEY (template_id) REFERENCES {$table_name}(id) ON DELETE CASCADE ) $charset_collate;"; dbDelta($sql2); } 2.2 创建信息图表编辑器界面 使用HTML、CSS和JavaScript创建前端编辑器: // 添加信息图表编辑器短代码 add_shortcode('infographic_editor', 'infographic_tool_editor_shortcode'); function infographic_tool_editor_shortcode($atts) { // 只有登录用户可以使用编辑器 if (!is_user_logged_in()) { return '<p>请先登录以使用信息图表编辑器。</p>'; } // 加载必要资源 wp_enqueue_style('infographic-editor-style', INFOGRAPHIC_TOOL_URL . 'assets/css/editor.css'); wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), '3.7.0', true); wp_enqueue_script('infographic-editor-script', INFOGRAPHIC_TOOL_URL . 'assets/js/editor.js', array('jquery', 'chart-js'), INFOGRAPHIC_TOOL_VERSION, true); // 传递数据到JavaScript wp_localize_script('infographic-editor-script', 'infographicTool', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('infographic_tool_nonce'), 'user_id' => get_current_user_id() )); ob_start(); ?> <div class="infographic-editor-container"> <div class="editor-header"> <h2>信息图表编辑器</h2> <div class="editor-actions"> <button id="save-template" class="button button-primary">保存模板</button> <button id="load-template" class="button">加载模板</button> <button id="export-chart" class="button">导出图表</button> </div> </div> <div class="editor-main"> <div class="sidebar"> <div class="sidebar-section"> <h3>图表类型</h3> <select id="chart-type" class="chart-type-select"> <option value="bar">柱状图</option> <option value="line">折线图</option> <option value="pie">饼图</option> <option value="doughnut">环形图</option> <option value="radar">雷达图</option> </select> </div> <div class="sidebar-section"> <h3>数据输入</h3> <div class="data-input"> <textarea id="chart-data" rows="10" placeholder="输入数据,格式:标签1,值1 标签2,值2 ...">第一季度,120 第二季度,200 第三季度,150 第四季度,180</textarea> <button id="update-chart" class="button">更新图表</button> </div> </div> <div class="sidebar-section"> <h3>图表选项</h3> <div class="chart-options"> <label>标题: <input type="text" id="chart-title" value="年度销售数据"></label> <label>背景颜色: <input type="color" id="bg-color" value="#ffffff"></label> <label>显示图例: <input type="checkbox" id="show-legend" checked></label> </div> </div> </div> <div class="chart-container"> <div class="chart-wrapper"> <canvas id="infographic-chart" width="800" height="500"></canvas> </div> <div class="chart-actions"> <button id="download-png" class="button">下载PNG</button> <button id="download-pdf" class="button">下载PDF</button> <button id="embed-code" class="button">获取嵌入代码</button> </div> </div> </div> <div class="templates-modal" id="templates-modal"> <div class="modal-content"> <span class="close-modal">&times;</span> <h3>选择模板</h3> <div class="templates-list" id="templates-list"> <!-- 模板将通过AJAX加载 --> </div> </div> </div> </div> <?php return ob_get_clean(); } 2.3 实现图表生成与保存功能 创建JavaScript文件处理图表生成和用户交互: // assets/js/editor.js jQuery(document).ready(function($) { let currentChart = null; let chartData = { labels: ['第一季度', '第二季度', '第三季度', '第四季度'], datasets: [{ label: '销售额', data: [120, 200, 150, 180], backgroundColor: [ 'rgba(255, 99, 132, 0.6)', 'rgba(54, 162, 235, 0.6)', 'rgba(255, 206, 86, 0.6)', 'rgba(75, 192, 192, 0.6)' ], borderColor: [ 'rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)' ], borderWidth: 1 }] }; // 初始化图表 function initChart() { const ctx = document.getElementById('infographic-chart').getContext('2d'); const chartType = $('#chart-type').val(); if (currentChart) { currentChart.destroy(); } currentChart = new Chart(ctx, { type: chartType, data: chartData, options: { responsive: false, plugins: { legend: { display: $('#show-legend').is(':checked'), position: 'top' }, title: { display: true, text: $('#chart-title').val() } } } }); } // 更新图表数据 $('#update-chart').on('click', function() { const dataText = $('#chart-data').val(); const lines = dataText.split('n'); const labels = []; const data = []; lines.forEach(line => { if (line.trim()) { const parts = line.split(','); if (parts.length >= 2) { labels.push(parts[0].trim()); data.push(parseFloat(parts[1].trim()) || 0); } } }); chartData.labels = labels; chartData.datasets[0].data = data; chartData.datasets[0].label = $('#chart-title').val() || '数据'; initChart(); }); // 更改图表类型 $('#chart-type').on('change', initChart); // 保存模板 $('#save-template').on('click', function() { const templateName = prompt('请输入模板名称:'); if (!templateName) return; const templateData = { chartType: $('#chart-type').val(), chartData: chartData, options: { title: $('#chart-title').val(), showLegend: $('#show-legend').is(':checked'), bgColor: $('#bg-color').val() } }; $.ajax({ url: infographicTool.ajax_url, type: 'POST', data: { action: 'save_infographic_template', nonce: infographicTool.nonce, title: templateName, data: JSON.stringify(templateData), user_id: infographicTool.user_id }, success: function(response) { if (response.success) { alert('模板保存成功!'); } else { alert('保存失败: ' + response.data); } } }); }); // 加载模板 $('#load-template').on('click', function() { $.ajax({ url: infographicTool.ajax_url, type: 'POST', data: { action: 'get_infographic_templates', nonce: infographicTool.nonce, user_id: infographicTool.user_id }, success: function(response) { if (response.success) { $('#templates-list').html(''); response.data.forEach(template => { $('#templates-list').append( `<div class="template-item" data-id="${template.id}"> <h4>${template.title}</h4> <button class="load-template-btn button">加载</button> <button class="delete-template-btn button">删除</button> </div>` ); }); $('#templates-modal').show(); } } }); }); // 初始化图表 initChart(); }); 第三部分:实现数据报告生成功能 3.1 设计报告生成系统架构 数据报告生成器需要更复杂的结构,包括: 报告模板系统:预定义报告结构和样式 数据源连接:支持数据库、API或手动输入数据 报告生成引擎:将数据填充到模板中生成报告 导出功能:支持PDF、Word、Excel等格式 3.2 创建报告生成器短代码 // 添加报告生成器短代码 add_shortcode('report_generator', 'report_generator_shortcode'); function report_generator_shortcode($atts) { $atts = shortcode_atts(array( 'type' => 'simple', 'template' => 'default' ), $atts); // 加载资源 wp_enqueue_style('report-generator-style', INFOGRAPHIC_TOOL_URL . 'assets/css/report-generator.css'); wp_enqueue_script('report-generator-script', INFOGRAPHIC_TOOL_URL . 'assets/js/report-generator.js', array('jquery'), INFOGRAPHIC_TOOL_VERSION, true); wp_enqueue_script('html2pdf', 'https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js', array(), '0.10.1', true); ob_start(); ?> <div class="report-generator-container"> <div class="report-header"> <h2>数据报告生成器</h2> <div class="report-actions"> <select id="report-template"> <option value="business">业务报告</option> <option value="financial">财务报告</option> <option value="marketing">营销报告</option> <option value="custom">自定义报告</option> </select> <button id="generate-report" class="button button-primary">生成报告</button> <button id="export-pdf" class="button">导出PDF</button> </div> </div> <div class="report-builder"> <div class="data-sources"> <h3>数据源</h3> <div class="source-options"> <label><input type="radio" name="data-source" value="manual" checked> 手动输入</label> <label><input type="radio" name="data-source" value="csv"> 上传CSV</label> <label><input type="radio" name="data-source" value="api"> API连接</label> </div> <div class="source-input manual-input"> <h4>输入报告数据</h4> <div class="data-table-editor"> <table id="data-table"> <thead> <tr> <th>指标</th> <th>Q1</th> <th>Q2</th> <th>Q3</th> <th>Q4</th> </tr> </thead> <tbody> <tr> <td><input type="text" value="销售额" class="metric-name"></td> <td><input type="number" value="120000"></td> <td><input type="number" value="150000"></td> <td><input type="number" value="180000"></td> <td><input type="number" value="210000"></td> </tr> <!-- 更多行可以通过JavaScript添加 --> </tbody> </table> <button id="add-row" class="button">添加行</button> </div> </div> </div> <div class="report-preview"> <h3>报告预览</h3> <div class="preview-container" id="report-preview"> <!-- 报告预览将通过JavaScript生成 --> </div> </div> </div> </div> <?php return ob_get_clean(); } 3.3 实现报告生成逻辑 创建PHP处理类来处理报告生成: // 创建报告生成类 class Report_Generator { private $data; private $template; private $output_format; public function __construct($data, $template = 'default', $format = 'html') { $this->data = $data; $this->template = $template; $this->output_format = $format; } public function generate() { // 根据模板类型选择生成方法 switch ($this->template) { case 'business': return $this->generate_business_report(); case 'financial': return $this->generate_financial_report(); case 'marketing': return $this->generate_marketing_report(); default: return $this->generate_custom_report(); } } private function generate_business_report() { $html = '<div class="business-report">'; $html .= '<h1 class="report-title">业务分析报告</h1>'; $html .= '<div class="report-meta">'; $html .= '<p>生成日期: ' . date('Y年m月d日') . '</p>'; $html .= '<p>报告周期: ' . ($this->data['period'] ?? '最近季度') . '</p>'; $html .= '</div>'; // 关键指标摘要 if (!empty($this->data['metrics'])) { $html .= '<div class="key-metrics">'; $html .= '<h2>关键绩效指标</h2>'; $html .= '<div class="metrics-grid">'; foreach ($this->data['metrics'] as $metric) { $html .= '<div class="metric-card">'; $html .= '<h3>' . esc_html($metric['name']) . '</h3>'; $html .= '<div class="metric-value">' . esc_html($metric['value']) . '</div>'; if (isset($metric['change'])) { $change_class = $metric['change'] >= 0 ? 'positive' : 'negative'; $html .= '<div class="metric-change ' . $change_class . '">'; $html .= ($metric['change'] >= 0 ? '+' : '') . esc_html($metric['change']) . '%'; $html .= '</div>'; } $html .= '</div>'; } $html .= '</div></div>'; } // 数据表格 if (!empty($this->data['table_data'])) { $html .= '<div class="data-table-section">'; $html .= '<h2>详细数据</h2>'; $html .= '<table class="report-table">'; $html .= '<thead><tr>'; // 表头 foreach ($this->data['table_data']['headers'] as $header) { $html .= '<th>' . esc_html($header) . '</th>'; } $html .= '</tr></thead><tbody>'; // 表格内容 foreach ($this->data['table_data']['rows'] as $row) { $html .= '<tr>'; foreach ($row as $cell) { $html .= '<td>' . esc_html($cell) . '</td>'; } $html .= '</tr>'; } $html .= '</tbody></table></div>'; } // 分析与建议 $html .= '<div class="analysis-section">'; $html .= '<h2>分析与建议</h2>'; $html .= '<div class="analysis-content">'; $html .= '<p>' . ($this->data['analysis'] ?? '基于以上数据,建议关注关键指标的变化趋势,优化资源配置。') . '</p>'; $html .= '</div>'; $html .= '</div>'; $html .= '</div>'; return $this->format_output($html); } private function format_output($html) { if ($this->output_format === 'pdf') { // 这里可以集成PDF生成库,如TCPDF或mPDF return $this->generate_pdf($html); } return $html; } private function generate_pdf($html) { // 简化示例,实际应使用PDF生成库 return array( 'type' => 'pdf', 'content' => 'PDF生成功能需要集成第三方库', 'html_template' => $html ); } } // AJAX处理报告生成add_action('wp_ajax_generate_data_report', 'handle_report_generation');add_action('wp_ajax_nopriv_generate_data_report', 'handle_report_generation'); function handle_report_generation() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'infographic_tool_nonce')) { wp_die('安全验证失败'); } $data = json_decode(stripslashes($_POST['data']), true); $template = sanitize_text_field($_POST['template']); $format = sanitize_text_field($_POST['format'] ?? 'html'); $generator = new Report_Generator($data, $template, $format); $report = $generator->generate(); wp_send_json_success(array( 'report' => $report, 'download_url' => $format === 'pdf' ? generate_pdf_download_link($report) : null )); } ## 第四部分:创建管理界面与高级功能 ### 4.1 构建WordPress管理界面 // 添加管理菜单add_action('admin_menu', 'infographic_tool_admin_menu'); function infographic_tool_admin_menu() { add_menu_page( '信息图表工具', '图表工具', 'manage_options', 'infographic-tool', 'infographic_tool_admin_page', 'dashicons-chart-area', 30 ); add_submenu_page( 'infographic-tool', '图表模板', '模板管理', 'manage_options', 'infographic-templates', 'infographic_templates_page' ); add_submenu_page( 'infographic-tool', '报告设置', '报告设置', 'manage_options', 'infographic-reports', 'infographic_reports_page' ); add_submenu_page( 'infographic-tool', '数据源配置', '数据源', 'manage_options', 'infographic-data-sources', 'infographic_data_sources_page' ); } function infographic_tool_admin_page() { ?> <div class="wrap"> <h1>信息图表与数据报告工具</h1> <div class="dashboard-cards"> <div class="card"> <h3>使用统计</h3> <div class="card-content"> <?php global $wpdb; $templates_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}infographic_templates"); $charts_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}infographic_charts"); ?> <p>模板数量: <strong><?php echo $templates_count; ?></strong></p> <p>生成图表: <strong><?php echo $charts_count; ?></strong></p> </div> </div> <div class="card"> <h3>快速操作</h3> <div class="card-content"> <p> <a href="<?php echo admin_url('admin.php?page=infographic-templates'); ?>" class="button">管理模板</a> <a href="<?php echo admin_url('admin.php?page=infographic-reports'); ?>" class="button">报告设置</a> </p> <p>短代码示例:</p> <code>[infographic_editor]</code><br> <code>[report_generator]</code> </div> </div> </div> <div class="settings-section"> <h2>全局设置</h2> <form method="post" action="options.php"> <?php settings_fields('infographic_tool_settings'); do_settings_sections('infographic_tool_settings'); ?> <table class="form-table"> <tr> <th>默认图表尺寸</th> <td> <input type="number" name="default_chart_width" value="<?php echo get_option('default_chart_width', 800); ?>" placeholder="宽度"> × <input type="number" name="default_chart_height" value="<?php echo get_option('default_chart_height', 500); ?>" placeholder="高度"> 像素 </td> </tr> <tr> <th>允许的用户角色</th> <td> <?php $roles = get_editable_roles(); $allowed_roles = get_option('infographic_allowed_roles', array('administrator', 'editor')); foreach ($roles as $role_name => $role_info) { $checked = in_array($role_name, $allowed_roles) ? 'checked' : ''; echo '<label style="display: block; margin-bottom: 5px;">'; echo '<input type="checkbox" name="allowed_roles[]" value="' . $role_name . '" ' . $checked . '> '; echo $role_info['name']; echo '</label>'; } ?> </td> </tr> <tr> <th>启用数据导出</th> <td> <label> <input type="checkbox" name="enable_data_export" value="1" <?php checked(get_option('enable_data_export', 1)); ?>> 允许用户导出图表数据 </label> </td> </tr> </table> <?php submit_button(); ?> </form> </div> </div> <?php } // 注册设置add_action('admin_init', 'infographic_tool_register_settings'); function infographic_tool_register_settings() { register_setting('infographic_tool_settings', 'default_chart_width'); register_setting('infographic_tool_settings', 'default_chart_height'); register_setting('infographic_tool_settings', 'infographic_allowed_roles'); register_setting('infographic_tool_settings', 'enable_data_export'); } ### 4.2 实现模板管理系统 function infographic_templates_page() { global $wpdb; // 处理模板操作 if (isset($_GET['action'])) { if ($_GET['action'] === 'delete' && isset($_GET['template_id'])) { $template_id = intval($_GET['template_id']); $wpdb->delete( $wpdb->prefix . 'infographic_templates', array('id' => $template_id) ); echo '<div class="notice notice-success"><p>模板已删除</p></div>'; } } // 获取所有模板 $templates = $wpdb->get_results(" SELECT t.*, u.user_login as author FROM {$wpdb->prefix}infographic_templates t LEFT JOIN {$wpdb->users} u ON t.created_by = u.ID ORDER BY t.created_at DESC "); ?> <div class="wrap"> <h1>图表模板管理</h1> <div class="tablenav top"> <div class="alignleft actions"> <a href="<?php echo admin_url('admin.php?page=infographic-tool&action=add_template'); ?>" class="button button-primary">添加新模板</a> </div> </div> <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($templates)): ?> <tr> <td colspan="6" style="text-align: center;">暂无模板</td> </tr> <?php else: ?> <?php foreach ($templates as $template): ?> <tr> <td><?php echo $template->id; ?></td> <td><?php echo esc_html($template->title); ?></td> <td><?php echo esc_html($template->template_type); ?></td> <td><?php echo esc_html($template->author); ?></td> <td><?php echo date('Y-m-d H:i', strtotime($template->created_at)); ?></td> <td> <a href="<?php echo admin_url("admin.php?page=infographic-tool&action=edit_template&id={$template->id}"); ?>" class="button button-small">编辑</a> <a href="<?php echo admin_url("admin.php?page=infographic-templates&action=delete&template_id={$template->id}"); ?>" class="button button-small button-link-delete" onclick="return confirm('确定删除此模板?')">删除</a> <button class="button button-small preview-template" data-id="<?php echo $template->id; ?>">预览</button> </td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> </div> <!-- 模板预览模态框 --> <div id="template-preview-modal" class="modal" style="display:none;"> <div class="modal-content"> <span class="close-modal">&times;</span> <div id="template-preview-content"></div> </div> </div> <script> jQuery(document).ready(function($) { $('.preview-template').on('click', function() { var templateId = $(this).data('id'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'preview_infographic_template', template_id: templateId, nonce: '<?php echo wp_create_nonce('infographic_preview_nonce'); ?>' }, success: function(response) { if (response.success) { $('#template-preview-content').html(response.data.preview); $('#template-preview-modal').show(); } } }); }); $('.close-modal').on('click', function() { $('#template-preview-modal').hide(); }); }); </script> <?php } ### 4.3 添加REST API端点 // 注册REST API端点add_action('rest_api_init', 'register_infographic_rest_routes'); function register_infographic_rest_routes() { // 获取图表数据 register_rest_route('infographic-tool/v1', '/charts/(?P<id>d+)', array( 'methods' => 'GET', 'callback' => 'get_chart_data_api', 'permission_callback' => function() { return current_user_can('read'); } )); // 创建新图表 register_rest_route('infographic-tool/v1', '/charts', array( 'methods' => 'POST', 'callback' => 'create_chart_api', 'permission_callback' => function() { return current_user_can('edit_posts'); } )); // 获取报告模板 register_rest_route('infographic-tool/v1', '/report-templates', array( 'methods' => 'GET', 'callback' => 'get_report_templates_api', 'permission_callback' => function() { return current_user_can('read'); } )); } function get_chart_data_api($request) { $chart_id = $request['id']; global $wpdb; $chart = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}infographic_charts WHERE id = %d", $chart_id )); if (!$chart) { return new WP_Error('not_found', '图表未找到', array('status' => 404)); } return array( 'id' => $chart->id, 'data' => json_decode($chart->chart_data), 'options' => json_decode($chart->chart_options), 'created_at' => $chart->created_at ); } function create_chart_api($request) { $parameters = $request->get_json_params(); if (empty($parameters['data']) || empty($parameters['template_id'])) { return new WP_Error('invalid_data', '缺少必要数据', array('status' => 400)); } global $wpdb; $result = $wpdb->insert( $wpdb->prefix . 'infographic_charts', array( 'template_id' => intval($parameters['template_id']), 'chart_data' => json_encode($parameters['data']), 'chart_options' => json_encode($parameters['options'] ?? array()), 'user_id' => get_current_user_id() ), array('%d', '%s', '%s', '%d') ); if ($result === false) { return new WP_Error('db_error', '数据库错误', array('status' => 500)); } return array( 'id' => $wpdb->insert_id, 'message' => '图表创建成功', 'embed_code' => generate_chart_embed_code($wpdb->insert_id) ); } function generate_chart_embed_code($chart_id) { $embed_url = home_url("/infographic-embed/{$chart_id}/"); return '<iframe src="' . esc_url($embed_url) . '" width="800" height="500" frameborder="0"></iframe>'; } ## 第五部分:优化与安全增强 ### 5.1 数据安全与验证 // 增强数据验证class Data_Validator { public static function validate_chart_data($data) { $errors = array(); // 验证数据格式 if (!is_array($data)) { $errors[] = '数据格式不正确'; return $errors; } // 验证标签 if (empty($data['labels']) || !is_array($data['labels'])) { $errors[] = '标签数据不能为空'; } // 验证数据集 if (empty($data['datasets']) || !is_array($data['datasets'])) { $errors[] = '数据集不能为空'; } else { foreach ($data['datasets'] as $index => $dataset) { if (empty($dataset['data']) || !is_array($dataset['data'])) { $errors[] = "数据集 {$index} 的数据不能为空"; } // 验证数据值是否为数字

发表评论