跳至内容

分类: 应用软件

WordPress开发教程,集成网站自动化社交媒体舆情监控与警报

WordPress开发教程:集成网站自动化社交媒体舆情监控与警报,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:WordPress的无限可能 在当今数字化时代,网站已不仅仅是信息展示的平台,更是企业与用户互动、品牌传播和业务拓展的核心阵地。作为全球最受欢迎的内容管理系统,WordPress以其开源特性、灵活的可扩展性和庞大的开发者社区,占据了互联网超过43%的网站市场份额。然而,许多WordPress用户仅停留在使用现成主题和插件的层面,未能充分挖掘其深层潜力。 本教程将深入探讨如何通过WordPress代码二次开发,将您的网站从一个被动的内容发布平台,转变为一个集成了自动化社交媒体舆情监控与警报系统的智能工具。我们将逐步引导您实现这一复杂功能,同时在这个过程中,掌握如何通过自定义开发为WordPress添加各种实用的小工具功能,从而大幅提升网站的价值和效率。 第一部分:WordPress开发基础与环境配置 1.1 WordPress开发环境搭建 在开始任何WordPress开发项目之前,建立一个合适的开发环境至关重要。我们推荐使用本地开发环境,如Local by Flywheel、XAMPP或MAMP,这些工具可以快速在本地计算机上搭建WordPress运行所需的PHP、MySQL和Web服务器环境。 对于本教程涉及的开发工作,您需要确保环境满足以下要求: PHP 7.4或更高版本(建议8.0+) MySQL 5.6或更高版本或MariaDB 10.1+ WordPress 5.8或更高版本 代码编辑器(如VS Code、PHPStorm等) Git版本控制系统 1.2 子主题创建与最佳实践 为了避免直接修改主题文件导致更新时丢失自定义代码,我们始终建议使用子主题进行开发。创建子主题只需在wp-content/themes目录下新建一个文件夹,并包含以下基本文件: style.css - 子主题样式表,必须包含特定的头部信息: /* Theme Name: 我的自定义子主题 Template: parent-theme-folder-name Version: 1.0.0 */ functions.php - 子主题功能文件,用于添加自定义代码: <?php // 子主题functions.php add_action('wp_enqueue_scripts', 'my_child_theme_scripts'); function my_child_theme_scripts() { // 加载父主题样式 wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css'); // 加载子主题样式 wp_enqueue_style('child-style', get_stylesheet_directory_uri() . '/style.css', array('parent-style')); } 1.3 WordPress钩子(Hooks)系统理解 WordPress的钩子系统是扩展其功能的核心机制,分为两种类型: 动作(Actions):在特定时刻执行自定义代码 过滤器(Filters):修改数据后再返回 理解并熟练使用钩子是高级WordPress开发的基础。例如,我们将在舆情监控系统中使用wp_cron钩子来定期执行监控任务。 第二部分:社交媒体API集成基础 2.1 社交媒体API概览与申请 要实现社交媒体舆情监控,首先需要获取各大社交平台的API访问权限。以下是主要平台的API申请要点: Twitter API(现为X平台): 访问developer.twitter.com申请开发者账户 创建项目和应用获取API密钥和访问令牌 注意:Twitter API v2有严格的访问限制和费用结构 Facebook Graph API: 通过Facebook开发者平台创建应用 需要应用审核才能访问某些接口 获取长期访问令牌以实现自动化 Instagram Basic Display API: 只能访问用户自己的内容 需要用户授权流程 适用于监控品牌自己的Instagram账户 YouTube Data API: 通过Google Cloud Console启用 每日有免费配额限制 可以搜索视频和评论 Reddit API: 相对宽松的访问政策 需要遵守API使用规则 可以访问公开的帖子和评论 2.2 WordPress中安全存储API密钥 绝对不要在代码中硬编码API密钥。WordPress提供了安全存储敏感数据的方法: // 在主题或插件中安全存储和获取API密钥 function save_social_api_keys() { if (isset($_POST['twitter_api_key'])) { update_option('twitter_api_key', sanitize_text_field($_POST['twitter_api_key'])); } } function get_twitter_api_key() { return get_option('twitter_api_key', ''); } // 或者使用更安全的wp-config.php常量定义 // define('TWITTER_API_KEY', 'your_actual_key_here'); 2.3 API请求处理与错误处理 在WordPress中发起API请求时,应使用内置的HTTP函数: function make_api_request($url, $args = array()) { $defaults = array( 'timeout' => 30, 'headers' => array( 'Authorization' => 'Bearer ' . get_option('twitter_bearer_token') ) ); $args = wp_parse_args($args, $defaults); $response = wp_remote_get($url, $args); 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 (json_last_error() !== JSON_ERROR_NONE) { error_log('JSON解析错误: ' . json_last_error_msg()); return false; } return $data; } 第三部分:构建舆情监控系统核心 3.1 数据库设计与数据模型 我们需要创建自定义数据库表来存储监控到的社交媒体内容: function create_social_monitoring_tables() { global $wpdb; $table_name = $wpdb->prefix . 'social_mentions'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, platform varchar(50) NOT NULL, post_id varchar(255) NOT NULL, author_name varchar(255), author_username varchar(255), content text NOT NULL, url varchar(500), sentiment_score float DEFAULT 0, engagement_count int DEFAULT 0, mention_date datetime DEFAULT CURRENT_TIMESTAMP, processed tinyint(1) DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY post_platform (post_id, platform), KEY platform_index (platform), KEY sentiment_index (sentiment_score), KEY date_index (mention_date) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } add_action('after_setup_theme', 'create_social_monitoring_tables'); 3.2 多平台数据采集引擎 创建一个统一的数据采集引擎,支持多个社交平台: class SocialMediaMonitor { private $platforms = array(); public function __construct() { $this->platforms = array( 'twitter' => new TwitterMonitor(), 'facebook' => new FacebookMonitor(), // 可以轻松扩展其他平台 ); } public function collect_mentions($keywords, $hours = 24) { $all_mentions = array(); foreach ($this->platforms as $platform => $monitor) { if ($monitor->is_enabled()) { $mentions = $monitor->search_mentions($keywords, $hours); $all_mentions = array_merge($all_mentions, $mentions); // 存储到数据库 $this->store_mentions($mentions, $platform); } } return $all_mentions; } private function store_mentions($mentions, $platform) { global $wpdb; $table_name = $wpdb->prefix . 'social_mentions'; foreach ($mentions as $mention) { $wpdb->replace( $table_name, array( 'platform' => $platform, 'post_id' => $mention['id'], 'author_name' => $mention['author_name'], 'author_username' => $mention['author_username'], 'content' => $mention['content'], 'url' => $mention['url'], 'mention_date' => $mention['created_at'], 'engagement_count' => $mention['engagement'] ), array('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d') ); } } } 3.3 情感分析与关键词识别 集成情感分析功能,自动判断提及内容的情感倾向: class SentimentAnalyzer { public function analyze($text) { // 使用简单的词典方法进行情感分析 // 实际项目中可以考虑使用机器学习API如Google Natural Language API $positive_words = array('好', '优秀', '推荐', '喜欢', '满意', '棒', '赞'); $negative_words = array('差', '糟糕', '讨厌', '失望', '垃圾', '差评', '投诉'); $score = 0; $words = $this->segment_text($text); // 中文需要分词 foreach ($words as $word) { if (in_array($word, $positive_words)) { $score += 1; } elseif (in_array($word, $negative_words)) { $score -= 1; } } // 归一化到-1到1之间 $normalized_score = tanh($score / 10); return array( 'score' => $normalized_score, 'sentiment' => $this->get_sentiment_label($normalized_score) ); } private function get_sentiment_label($score) { if ($score > 0.3) return '积极'; if ($score < -0.3) return '消极'; return '中性'; } private function segment_text($text) { // 简单的中文分词,实际项目应使用专业分词库如jieba-php return preg_split('/s+/', $text); } } 第四部分:实时警报系统实现 4.1 警报规则引擎设计 创建一个灵活的警报规则系统,允许用户自定义触发条件: class AlertEngine { private $rules = array(); public function __construct() { $this->load_rules(); } public function check_mention($mention) { $alerts_triggered = array(); foreach ($this->rules as $rule) { if ($this->evaluate_rule($rule, $mention)) { $alerts_triggered[] = $rule['id']; $this->trigger_alert($rule, $mention); } } return $alerts_triggered; } private function evaluate_rule($rule, $mention) { $conditions_met = 0; foreach ($rule['conditions'] as $condition) { if ($this->check_condition($condition, $mention)) { $conditions_met++; } } // 根据规则类型判断是否触发 if ($rule['type'] === 'all' && $conditions_met === count($rule['conditions'])) { return true; } elseif ($rule['type'] === 'any' && $conditions_met > 0) { return true; } return false; } private function check_condition($condition, $mention) { switch ($condition['field']) { case 'sentiment': return $this->compare_sentiment($mention['sentiment_score'], $condition['operator'], $condition['value']); case 'engagement': return $this->compare_number($mention['engagement_count'], $condition['operator'], $condition['value']); case 'keyword': return $this->check_keyword($mention['content'], $condition['value']); default: return false; } } } 4.2 多渠道通知系统 实现通过多种渠道发送警报通知: class NotificationSystem { public function send_alert($alert_data, $channels) { foreach ($channels as $channel) { switch ($channel) { case 'email': $this->send_email_alert($alert_data); break; case 'slack': $this->send_slack_alert($alert_data); break; case 'webhook': $this->send_webhook_alert($alert_data); break; case 'sms': $this->send_sms_alert($alert_data); break; } } } private function send_email_alert($alert_data) { $to = get_option('alert_email_recipient', get_option('admin_email')); $subject = '社交媒体警报: ' . $alert_data['title']; $message = $this->build_email_template($alert_data); $headers = array('Content-Type: text/html; charset=UTF-8'); wp_mail($to, $subject, $message, $headers); } private function send_slack_alert($alert_data) { $webhook_url = get_option('slack_webhook_url'); if (!$webhook_url) return; $payload = array( 'text' => $alert_data['title'], 'blocks' => array( array( 'type' => 'section', 'text' => array( 'type' => 'mrkdwn', 'text' => "*新警报:* " . $alert_data['title'] ) ), array( 'type' => 'section', 'text' => array( 'type' => 'mrkdwn', 'text' => $alert_data['content'] ) ) ) ); wp_remote_post($webhook_url, array( 'body' => json_encode($payload), 'headers' => array('Content-Type' => 'application/json') )); } } 4.3 WordPress Cron定时任务集成 使用WordPress内置的Cron系统定期执行监控任务: class MonitoringScheduler { public function __construct() { add_action('social_monitoring_cron', array($this, 'run_monitoring')); add_filter('cron_schedules', array($this, 'add_custom_schedules')); } public function activate() { if (!wp_next_scheduled('social_monitoring_cron')) { wp_schedule_event(time(), 'every_15_minutes', 'social_monitoring_cron'); } } public function deactivate() { wp_clear_scheduled_hook('social_monitoring_cron'); } public function add_custom_schedules($schedules) { $schedules['every_15_minutes'] = array( 'interval' => 15 * 60, 'display' => __('每15分钟') ); $schedules['every_hour'] = array( 'interval' => 60 * 60, 'display' => __('每小时') ); return $schedules; } public function run_monitoring() { $monitor = new SocialMediaMonitor(); $keywords = get_option('monitoring_keywords', array()); if (empty($keywords)) { error_log('未设置监控关键词'); return; } $mentions = $monitor->collect_mentions($keywords, 1); // 监控最近1小时的内容 $alert_engine = new AlertEngine(); foreach ($mentions as $mention) { $alert_engine->check_mention($mention); } // 记录执行日志 $this->log_execution(count($mentions)); } } 第五部分:管理界面与可视化仪表板 5.1 WordPress管理菜单集成 创建用户友好的管理界面: class MonitoringAdmin { 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_options', 'social-monitoring', array($this, 'render_dashboard'), 'dashicons-share', 30 ); add_submenu_page( 'social-monitoring', '监控仪表板', '仪表板', 'manage_options', 'social-monitoring', array($this, 'render_dashboard') ); add_submenu_page( 'social-monitoring', '警报规则', '警报规则', 'manage_options', 'social-monitoring-rules', array($this, 'render_rules_page') ); add_submenu_page( 'social-monitoring', '设置', '设置', 'manage_options', 'social-monitoring-settings', array($this, 'render_settings_page') ); } public function render_dashboard() { include plugin_dir_path(__FILE__) . 'templates/dashboard.php'; } } 5.2 数据可视化与图表 使用Chart.js或ECharts创建交互式数据可视化: public function enqueue_admin_scripts($hook) { if (strpos($hook, 'social-monitoring') === false) { return; } // 加载Chart.js wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js@3.7.0/dist/chart.min.js', array(), '3.7.0', true ); // 加载自定义仪表板脚本 wp_enqueue_script( 'monitoring-dashboard', plugin_dir_url(__FILE__) . 'js/dashboard.js', array('jquery', 'chart-js'), '1.0.0', ); // 传递数据到前端 wp_localize_script('monitoring-dashboard', 'monitoringData', array( 'sentimentData' => $this->get_sentiment_chart_data(), 'platformData' => $this->get_platform_distribution_data(), 'timelineData' => $this->get_mentions_timeline_data() )); } private function get_sentiment_chart_data() { global $wpdb; $table_name = $wpdb->prefix . 'social_mentions'; $results = $wpdb->get_results(" SELECT CASE WHEN sentiment_score > 0.3 THEN '积极' WHEN sentiment_score < -0.3 THEN '消极' ELSE '中性' END as sentiment, COUNT(*) as count FROM $table_name WHERE mention_date >= DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY CASE WHEN sentiment_score > 0.3 THEN '积极' WHEN sentiment_score < -0.3 THEN '消极' ELSE '中性' END "); $data = array( 'labels' => array('积极', '中性', '消极'), 'datasets' => array( array( 'data' => array(0, 0, 0), 'backgroundColor' => array('#4CAF50', '#2196F3', '#F44336') ) ) ); foreach ($results as $row) { $index = array_search($row->sentiment, $data['labels']); if ($index !== false) { $data['datasets'][0]['data'][$index] = (int)$row->count; } } return $data; } #### 5.3 实时数据更新与AJAX集成 实现无需刷新页面的实时数据更新: class RealTimeUpdater { public function __construct() { add_action('wp_ajax_get_recent_mentions', array($this, 'ajax_get_recent_mentions')); add_action('wp_ajax_nopriv_get_recent_mentions', array($this, 'ajax_no_permission')); add_action('wp_ajax_update_alert_status', array($this, 'ajax_update_alert_status')); } public function ajax_get_recent_mentions() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'monitoring_ajax_nonce')) { wp_die('权限验证失败'); } global $wpdb; $table_name = $wpdb->prefix . 'social_mentions'; $limit = intval($_POST['limit'] ?? 10); $offset = intval($_POST['offset'] ?? 0); $mentions = $wpdb->get_results($wpdb->prepare(" SELECT * FROM $table_name ORDER BY mention_date DESC LIMIT %d OFFSET %d ", $limit, $offset)); // 格式化数据 $formatted_mentions = array(); foreach ($mentions as $mention) { $formatted_mentions[] = array( 'id' => $mention->id, 'platform' => $mention->platform, 'author' => $mention->author_name ?: $mention->author_username, 'content' => wp_trim_words($mention->content, 20), 'sentiment' => $this->get_sentiment_label($mention->sentiment_score), 'sentiment_score' => $mention->sentiment_score, 'engagement' => $mention->engagement_count, 'time' => human_time_diff(strtotime($mention->mention_date), current_time('timestamp')), 'url' => $mention->url ); } wp_send_json_success(array( 'mentions' => $formatted_mentions, 'total' => $wpdb->get_var("SELECT COUNT(*) FROM $table_name") )); } public function ajax_update_alert_status() { if (!current_user_can('manage_options')) { wp_send_json_error('权限不足'); } $alert_id = intval($_POST['alert_id']); $status = sanitize_text_field($_POST['status']); // 更新警报状态逻辑 $result = $this->update_alert_in_database($alert_id, $status); if ($result) { wp_send_json_success('状态更新成功'); } else { wp_send_json_error('更新失败'); } } } ### 第六部分:实用小工具功能扩展 #### 6.1 短代码(Shortcode)系统开发 创建灵活的短代码系统,让用户可以在文章或页面中嵌入监控数据: class MonitoringShortcodes { public function __construct() { add_shortcode('social_mentions', array($this, 'render_mentions_shortcode')); add_shortcode('sentiment_chart', array($this, 'render_sentiment_chart')); add_shortcode('top_influencers', array($this, 'render_influencers_list')); } public function render_mentions_shortcode($atts) { $atts = shortcode_atts(array( 'limit' => 5, 'platform' => 'all', 'sentiment' => 'all', 'days' => 7 ), $atts); global $wpdb; $table_name = $wpdb->prefix . 'social_mentions'; $where_clauses = array("mention_date >= DATE_SUB(NOW(), INTERVAL %d DAY)"); $where_values = array(intval($atts['days'])); if ($atts['platform'] !== 'all') { $where_clauses[] = "platform = %s"; $where_values[] = sanitize_text_field($atts['platform']); } if ($atts['sentiment'] !== 'all') { $sentiment_map = array( 'positive' => 'sentiment_score > 0.3', 'negative' => 'sentiment_score < -0.3', 'neutral' => 'sentiment_score BETWEEN -0.3 AND 0.3' ); if (isset($sentiment_map[$atts['sentiment']])) { $where_clauses[] = $sentiment_map[$atts['sentiment']]; } } $where_sql = implode(' AND ', $where_clauses); $mentions = $wpdb->get_results($wpdb->prepare(" SELECT * FROM $table_name WHERE $where_sql ORDER BY engagement_count DESC LIMIT %d ", array_merge($where_values, array(intval($atts['limit']))))); ob_start(); ?> <div class="social-mentions-widget"> <h3>最新社交媒体提及</h3> <div class="mentions-list"> <?php foreach ($mentions as $mention): ?> <div class="mention-item"> <div class="mention-platform platform-<?php echo esc_attr($mention->platform); ?>"> <?php echo esc_html(ucfirst($mention->platform)); ?> </div> <div class="mention-content"> <?php echo esc_html(wp_trim_words($mention->content, 15)); ?> </div> <div class="mention-meta"> <span class="mention-author">@<?php echo esc_html($mention->author_username); ?></span> <span class="mention-time"><?php echo human_time_diff(strtotime($mention->mention_date), current_time('timestamp')); ?>前</span> </div> </div> <?php endforeach; ?> </div> </div> <style> .social-mentions-widget { border: 1px solid #ddd; padding: 15px; border-radius: 5px; } .mention-item { border-bottom: 1px solid #eee; padding: 10px 0; } .mention-platform { display: inline-block; padding: 2px 8px; border-radius: 3px; font-size: 12px; color: white; } .platform-twitter { background: #1DA1F2; } .platform-facebook { background: #4267B2; } .mention-meta { font-size: 12px; color: #666; margin-top: 5px; } </style> <?php return ob_get_clean(); } } #### 6.2 WordPress小工具(Widget)开发 创建可拖拽的侧边栏小工具: class SocialMonitoringWidget extends WP_Widget { public function __construct() { parent::__construct( 'social_monitoring_widget', '社交媒体监控', array('description' => '显示最新的社交媒体提及和情感分析') ); } public function widget($args, $instance) { echo $args['before_widget']; $title = apply_filters('widget_title', $instance['title']); if (!empty($title)) { echo $args['before_title'] . $title . $args['after_title']; } // 获取数据 $data = $this->get_widget_data($instance); // 渲染小工具内容 $this->render_widget_content($data, $instance); echo $args['after_widget']; } public function form($instance) { $title = $instance['title'] ?? '社交媒体监控'; $limit = $instance['limit'] ?? 5; $show_chart = $instance['show_chart'] ?? true; ?> <p> <label for="<?php echo $this->get_field_id('title'); ?>">标题:</label> <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>"> </p> <p> <label for="<?php echo $this->get_field_id('limit'); ?>">显示数量:</label> <input class="tiny-text" id="<?php echo $this->get_field_id('limit'); ?>" name="<?php echo $this->get_field_name('limit'); ?>" type="number" value="<?php echo esc_attr($limit); ?>" min="1" max="20"> </p> <p> <input class="checkbox" type="checkbox" id="<?php echo $this->get_field_id('show_chart'); ?>" name="<?php echo $this->get_field_name('show_chart'); ?>" <?php checked($show_chart); ?>> <label for="<?php echo $this->get_field_id('show_chart'); ?>">显示情感图表</label> </p> <?php } public function update($new_instance, $old_instance) { $instance = array(); $instance['title'] = sanitize_text_field($new_instance['title'] ?? ''); $instance['limit'] = intval($new_instance['limit'] ?? 5); $instance['show_chart'] = isset($new_instance['show_chart']); return $instance; } } // 注册小工具add_action('widgets_init', function() { register_widget('SocialMonitoringWidget'); }); #### 6.3 REST API端点创建 为监控系统创建REST API,支持与其他系统集成: class MonitoringRESTAPI { public function __construct() { add_action('rest_api_init', array($this, 'register_routes')); } public function register_routes() { register_rest_route('social-monitoring/v1', '/mentions', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array($this, 'get_mentions'), 'permission_callback' => array($this, 'check_api_permission'), 'args' => $this->get_mentions_args() ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array($this, 'create_mention'), 'permission_callback' => array($this, 'check_api_permission') ) )); register_rest_route('social-monitoring/v1', '/analytics', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array($this, 'get_analytics'), 'permission_callback' => array($this, 'check_api_permission') )); register_rest_route('social-monitoring/v1', '/alerts', array( 'methods' => WP_REST_Server::READABLE, 'callback' => array($this, 'get_alerts'), 'permission_callback' => array($this, 'check_api_permission') )); } public function get_mentions(WP_REST_Request $request) { $params = $request->get_params(); global $wpdb; $table_name = $wpdb->prefix . 'social_mentions'; $page = max(1, intval($params['page'] ?? 1)); $per_page = min(100, intval($params['per_page'] ?? 20)); $offset = ($page - 1) * $per_page; // 构建查询条件 $where = array('1=1'); $query_params = array(); if (!empty($params['platform'])) { $where[] = 'platform = %s'; $query_params[] = sanitize_text_field($params['platform']); } if (!empty($params['start_date'])) { $where[] = 'mention_date >= %s'; $query_params[] = sanitize_text_field($params['start_date']); } if (!empty($params['end_date'])) { $where[] = 'mention_date <= %s'; $query_params[] = sanitize_text_field($params['end_date']); } if (!empty($params['sentiment'])) { if ($params['sentiment'] === 'positive') { $where[] = 'sentiment_score > 0.3'; } elseif ($params['sentiment'] === 'negative') { $where[] = 'sentiment_score < -0.3'; } else { $where[] = 'sentiment_score BETWEEN -0.3 AND 0.3'; } } $where_sql = implode(' AND ', $where); // 获取总数 $count_query = "SELECT COUNT(*) FROM $table_name WHERE $where_sql"; if (!empty($query_params)) { $count_query = $wpdb->prepare($count_query, $query_params); } $total = $wpdb->get_var($count_query); // 获取数据 $data_query = "SELECT * FROM $table_name WHERE $where_sql ORDER BY mention_date DESC LIMIT %d OFFSET %d"; $query_params[] = $per_page; $query_params[] = $offset; $data = $wpdb->get_results($wpdb->prepare($data_query, $query_params)); // 格式化响应 $formatted_data = array(); foreach ($data as $item) { $formatted_data[] = array( 'id' => $item->id, 'platform' => $item->platform, 'author' => array( 'name' => $item->author_name, 'username' => $item->author_username ), 'content' => $item->content, 'url' => $item->url, 'sentiment' => array( 'score' => floatval($item->sentiment_score), 'label' => $this->get_sentiment_label(floatval($item->sentiment_score)) ), 'engagement' => intval($item->engagement_count), 'date' => $item->mention_date ); } return new WP_REST_Response(array( 'data' => $formatted_data, 'pagination' => array( 'page' => $page, 'per_page' => $per_page, 'total' => intval($total), 'total_pages' => ceil($total / $per_page) ) ), 200); } } ### 第七部分:性能优化与安全加固 #### 7.1 数据库查询优化 优化监控系统的数据库性能: class DatabaseOptimizer { public function optimize_tables() { global $wpdb; // 定期清理旧数据 $retention_days = get_option('data_retention_days', 90); $table_name = $wpdb->prefix . 'social_mentions'; $wpdb->query($wpdb->prepare(" DELETE FROM $table_name WHERE mention_date < DATE_SUB(NOW(), INTERVAL %d DAY) ", $retention_days)); // 优化表 $wpdb->query("OPTIMIZE TABLE $table_name"); // 创建和维护索引 $this->maintain_indexes(); } private function maintain_indexes() { global $wpdb; $table_name = $wpdb->prefix . 'social_mentions'; // 检查并添加缺失的索引 $indexes = $wpdb->get_results("SHOW INDEX FROM $table_name"); $existing_indexes = array(); foreach ($indexes as $index) { $existing_indexes[] = $index->Key_name; } // 添加常用查询的复合索引 if (!in_array('idx_platform_date', $existing_indexes)) { $wpdb->query("CREATE INDEX idx_platform_date ON $table_name (platform, mention_date)"); } if (!in_array('idx_sentiment_engagement', $existing_indexes)) { $wpdb->query("CREATE INDEX idx_sentiment_engagement ON $table_name (sentiment_score, engagement_count)"); } } public function add_query_cache() { // 使用WordPress瞬

发表评论

实战教学,为你的网站添加在线迷你游戏以提升用户互动与留存

实战教学:为你的WordPress网站添加在线迷你游戏以提升用户互动与留存 引言:为什么网站需要迷你游戏? 在当今互联网环境中,用户注意力已成为最稀缺的资源之一。网站运营者面临着一个共同的挑战:如何让访客停留更长时间,提高用户参与度,并最终实现转化率的提升?传统的内容展示方式已难以满足现代用户的需求,而互动元素的加入正成为解决这一问题的有效途径。 在线迷你游戏作为一种轻量级互动形式,具有以下优势: 提升用户停留时间:有趣的游戏体验能有效延长用户在网站的停留时间 增强品牌记忆:通过游戏化体验加深用户对品牌的印象 促进社交分享:用户乐于分享游戏成绩和体验,带来自然流量 收集用户数据:游戏过程中可以收集有价值的用户行为数据 提高转化率:游戏化元素可以作为引导用户完成特定动作的有效手段 本文将详细介绍如何通过WordPress代码二次开发,为你的网站添加实用的在线迷你游戏和小工具功能,从而显著提升用户互动与留存率。 第一部分:准备工作与环境搭建 1.1 选择合适的开发环境 在开始开发之前,确保你拥有以下环境: 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel WordPress安装:最新版本的WordPress(建议5.8以上) 代码编辑器:VS Code、Sublime Text或PHPStorm 浏览器开发者工具:用于调试JavaScript和CSS 1.2 创建子主题保护核心文件 为了避免主题更新导致自定义代码丢失,我们首先创建一个子主题: /* Theme Name: 我的游戏化子主题 Template: twentytwentythree Version: 1.0 Description: 为网站添加迷你游戏功能的子主题 */ // 引入父主题样式表 add_action('wp_enqueue_scripts', 'my_gamification_theme_enqueue_styles'); function my_gamification_theme_enqueue_styles() { wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css'); wp_enqueue_style('child-style', get_stylesheet_directory_uri() . '/style.css', array('parent-style')); } 1.3 创建必要的目录结构 在你的子主题目录中创建以下文件夹结构: /my-gamification-theme/ ├── games/ │ ├── js/ │ ├── css/ │ └── assets/ ├── includes/ ├── templates/ └── functions.php 第二部分:实现经典记忆配对游戏 2.1 游戏设计与功能规划 记忆配对游戏是一种简单但有效的互动游戏,适合各种类型的网站。我们将实现以下功能: 可配置的卡片数量(4x4、4x5、5x6等) 计时器和步数计数器 得分系统 社交分享功能 保存最高分记录 2.2 创建游戏短代码 在functions.php中添加短代码,使游戏可以轻松插入到任何文章或页面中: // 注册记忆游戏短代码 add_shortcode('memory_game', 'memory_game_shortcode'); function memory_game_shortcode($atts) { // 短代码属性 $atts = shortcode_atts( array( 'columns' => 4, 'rows' => 4, 'theme' => 'default' ), $atts, 'memory_game' ); // 生成唯一游戏ID $game_id = 'memory_game_' . uniqid(); // 输出游戏容器 ob_start(); ?> <div id="<?php echo esc_attr($game_id); ?>" class="memory-game-container" data-columns="<?php echo esc_attr($atts['columns']); ?>" data-rows="<?php echo esc_attr($atts['rows']); ?>" data-theme="<?php echo esc_attr($atts['theme']); ?>"> <div class="game-controls"> <div class="game-stats"> <span class="timer">时间: <span class="time-value">00:00</span></span> <span class="moves">步数: <span class="moves-value">0</span></span> <span class="score">得分: <span class="score-value">0</span></span> </div> <div class="game-buttons"> <button class="restart-game">重新开始</button> <button class="pause-game">暂停</button> </div> </div> <div class="game-board"></div> <div class="game-result" style="display:none;"> <h3>游戏结束!</h3> <p>你的得分: <span class="final-score">0</span></p> <p>用时: <span class="final-time">00:00</span></p> <p>步数: <span class="final-moves">0</span></p> <button class="play-again">再玩一次</button> <button class="share-score">分享成绩</button> </div> </div> <?php return ob_get_clean(); } 2.3 实现游戏JavaScript逻辑 创建 /games/js/memory-game.js 文件: class MemoryGame { constructor(containerId) { this.container = document.getElementById(containerId); this.columns = parseInt(this.container.dataset.columns) || 4; this.rows = parseInt(this.container.dataset.rows) || 4; this.theme = this.container.dataset.theme || 'default'; this.totalPairs = (this.columns * this.rows) / 2; this.cards = []; this.flippedCards = []; this.matchedPairs = 0; this.moves = 0; this.score = 0; this.gameStarted = false; this.gamePaused = false; this.startTime = null; this.timerInterval = null; this.elapsedTime = 0; this.init(); } init() { this.createCards(); this.renderBoard(); this.setupEventListeners(); this.updateStats(); } createCards() { // 创建卡片对 const symbols = ['★', '❤', '♦', '♠', '♣', '☀', '☁', '☂', '☃', '♫', '⚓', '✈']; const usedSymbols = symbols.slice(0, this.totalPairs); // 每对卡片重复一次 let cardValues = [...usedSymbols, ...usedSymbols]; // 随机排序 cardValues = this.shuffleArray(cardValues); // 创建卡片对象 this.cards = cardValues.map((value, index) => ({ id: index, value: value, flipped: false, matched: false })); } shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } renderBoard() { const board = this.container.querySelector('.game-board'); board.innerHTML = ''; // 设置网格布局 board.style.gridTemplateColumns = `repeat(${this.columns}, 1fr)`; board.style.gridTemplateRows = `repeat(${this.rows}, 1fr)`; // 创建卡片元素 this.cards.forEach(card => { const cardElement = document.createElement('div'); cardElement.className = 'memory-card'; cardElement.dataset.id = card.id; const frontFace = document.createElement('div'); frontFace.className = 'card-front'; frontFace.textContent = card.value; const backFace = document.createElement('div'); backFace.className = 'card-back'; backFace.textContent = '?'; cardElement.appendChild(frontFace); cardElement.appendChild(backFace); board.appendChild(cardElement); }); } setupEventListeners() { // 卡片点击事件 this.container.addEventListener('click', (e) => { const cardElement = e.target.closest('.memory-card'); if (!cardElement || this.gamePaused) return; const cardId = parseInt(cardElement.dataset.id); this.flipCard(cardId); }); // 重新开始按钮 const restartBtn = this.container.querySelector('.restart-game'); restartBtn.addEventListener('click', () => this.restartGame()); // 暂停按钮 const pauseBtn = this.container.querySelector('.pause-game'); pauseBtn.addEventListener('click', () => this.togglePause()); // 再玩一次按钮 const playAgainBtn = this.container.querySelector('.play-again'); if (playAgainBtn) { playAgainBtn.addEventListener('click', () => this.restartGame()); } // 分享按钮 const shareBtn = this.container.querySelector('.share-score'); if (shareBtn) { shareBtn.addEventListener('click', () => this.shareScore()); } } flipCard(cardId) { // 如果游戏未开始,开始计时 if (!this.gameStarted) { this.startGame(); } const card = this.cards.find(c => c.id === cardId); // 如果卡片已匹配或已翻转,忽略点击 if (card.matched || card.flipped || this.flippedCards.length >= 2) { return; } // 翻转卡片 card.flipped = true; this.flippedCards.push(card); // 更新UI this.updateCardUI(cardId); // 如果翻转了两张卡片,检查是否匹配 if (this.flippedCards.length === 2) { this.moves++; this.updateStats(); const [card1, card2] = this.flippedCards; if (card1.value === card2.value) { // 匹配成功 card1.matched = true; card2.matched = true; this.matchedPairs++; this.score += 100; // 更新分数 this.updateStats(); // 清空翻转卡片数组 this.flippedCards = []; // 检查游戏是否结束 if (this.matchedPairs === this.totalPairs) { this.endGame(); } } else { // 不匹配,稍后翻转回来 setTimeout(() => { card1.flipped = false; card2.flipped = false; this.flippedCards = []; this.updateCardUI(card1.id); this.updateCardUI(card2.id); }, 1000); } } } updateCardUI(cardId) { const cardElement = this.container.querySelector(`[data-id="${cardId}"]`); const card = this.cards.find(c => c.id === cardId); if (card.flipped || card.matched) { cardElement.classList.add('flipped'); } else { cardElement.classList.remove('flipped'); } } startGame() { this.gameStarted = true; this.startTime = Date.now(); // 开始计时器 this.timerInterval = setInterval(() => { if (!this.gamePaused) { this.elapsedTime = Date.now() - this.startTime; this.updateStats(); } }, 1000); } togglePause() { this.gamePaused = !this.gamePaused; const pauseBtn = this.container.querySelector('.pause-game'); if (this.gamePaused) { pauseBtn.textContent = '继续'; } else { pauseBtn.textContent = '暂停'; // 如果游戏暂停后继续,调整开始时间 if (this.gameStarted) { this.startTime = Date.now() - this.elapsedTime; } } } updateStats() { // 更新时间显示 const timeElement = this.container.querySelector('.time-value'); const minutes = Math.floor(this.elapsedTime / 60000); const seconds = Math.floor((this.elapsedTime % 60000) / 1000); timeElement.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; // 更新步数 const movesElement = this.container.querySelector('.moves-value'); movesElement.textContent = this.moves; // 更新分数 const scoreElement = this.container.querySelector('.score-value'); scoreElement.textContent = this.score; } endGame() { clearInterval(this.timerInterval); // 计算最终得分(考虑时间和步数) const timeBonus = Math.max(0, 300 - Math.floor(this.elapsedTime / 1000)) * 10; const movesBonus = Math.max(0, 50 - this.moves) * 5; this.score += timeBonus + movesBonus; // 显示结果 const resultElement = this.container.querySelector('.game-result'); resultElement.querySelector('.final-score').textContent = this.score; resultElement.querySelector('.final-time').textContent = this.container.querySelector('.time-value').textContent; resultElement.querySelector('.final-moves').textContent = this.moves; resultElement.style.display = 'block'; // 保存最高分到本地存储 this.saveHighScore(); } saveHighScore() { const highScores = JSON.parse(localStorage.getItem('memoryGameHighScores') || '[]'); highScores.push({ score: this.score, time: this.elapsedTime, moves: this.moves, date: new Date().toISOString(), grid: `${this.columns}x${this.rows}` }); // 按分数排序,只保留前10名 highScores.sort((a, b) => b.score - a.score); const topScores = highScores.slice(0, 10); localStorage.setItem('memoryGameHighScores', JSON.stringify(topScores)); } shareScore() { const text = `我在记忆配对游戏中获得了${this.score}分!用时${this.container.querySelector('.time-value').textContent},用了${this.moves}步。`; if (navigator.share) { navigator.share({ title: '我的游戏成绩', text: text, url: window.location.href }); } else { // 备用方案:复制到剪贴板 navigator.clipboard.writeText(text).then(() => { alert('成绩已复制到剪贴板,快去分享吧!'); }); } } restartGame() { // 重置游戏状态 this.matchedPairs = 0; this.moves = 0; this.score = 0; this.gameStarted = false; this.gamePaused = false; this.flippedCards = []; this.elapsedTime = 0; clearInterval(this.timerInterval); // 重新创建卡片 this.createCards(); this.renderBoard(); // 隐藏结果 const resultElement = this.container.querySelector('.game-result'); resultElement.style.display = 'none'; // 更新统计 this.updateStats(); } } // 初始化所有记忆游戏实例 document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.memory-game-container').forEach(container => { new MemoryGame(container.id); }); }); 2.4 添加游戏样式 创建 /games/css/memory-game.css 文件: .memory-game-container { max-width: 800px; margin: 20px auto; padding: 20px; background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); border-radius: 15px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); color: white; font-family: 'Arial', sans-serif; } .game-controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 15px; background: rgba(255, 255, 255, 0.1); border-radius: 10px; flex-wrap: wrap; } .game-stats { display: flex; gap: 20px; font-size: 18px; font-weight: bold; } .game-stats span { background: rgba(0, 0, 0, 0.2); padding: 8px 15px; border-radius: 5px; } .game-buttons { display: flex; gap: 10px; } .game-buttons button { padding: 10px 20px; border: none; border-radius: 5px; background: #4CAF50; color: white; font-weight: bold; cursor: pointer; transition: all 0.3s ease; } .game-buttons button:hover { background: #45a049; transform: translateY(-2px); } .game-buttons .pause-game { background: #ff9800; } .game-buttons .pause-game:hover { background: #e68900; } .game-board { display: grid; gap: 10px; margin: 20px 0; perspective: 1000px; } .memory-card { height: 100px; position: relative; transform-style: preserve-3d; transition: transform 0.6s; cursor: pointer; border-radius: 10px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } .memory-card.flipped { transform: rotateY(180deg); } .memory-card .card-front, .memory-card .card-back { position: absolute; width: 100%; height: 100%; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 2em; font-weight: bold; } .memory-card .card-back { background: linear-gradient(45deg, #2196F3, #21CBF3); color: white; transform: rotateY(0deg); } .memory-card .card-front { background: linear-gradient(45deg, #FF9800, #FFC107); color: white; transform: rotateY(180deg); } .memory-card.matched .card-front { background: linear-gradient(45deg, #4CAF50, #8BC34A); } .game-result { text-align: center; padding: 30px; background: rgba(255, 255, 255, 0.1); border-radius: 10px; margin-top: 20px; animation: fadeIn 0.5s ease; } .game-result h3 { font-size: 28px; margin-bottom: 20px; color: #FFEB3B; } .game-result p { font-size: 18px; margin: 10px 0; } .game-result button { margin: 10px; padding: 12px 25px; border: none; border-radius: 5px; font-size: 16px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; } .game-result .play-again { background: #4CAF50; color: white; } .game-result .share-score { background: #2196F3; color: white; } .game-result button:hover { transform: translateY(-3px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); } @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } /* 响应式设计 */ @media (max-width: 768px) { .game-controls { flex-direction: column; gap: 15px; } .game-stats { flex-wrap: wrap; justify-content: center; } .memory-card { height: 80px; } } @media (max-width: 480px) { .memory-card { height: 60px; } .memory-card .card-front, .memory-card .card-back { font-size: 1.5em; } } 2.5 在WordPress中注册游戏资源 在functions.php中添加以下代码,确保游戏脚本和样式正确加载: // 注册并加载记忆游戏资源 add_action('wp_enqueue_scripts', 'register_memory_game_assets'); function register_memory_game_assets() { // 只在需要时加载游戏资源 global $post; if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'memory_game')) { // 游戏样式 wp_enqueue_style( 'memory-game-css', get_stylesheet_directory_uri() . '/games/css/memory-game.css', array(), '1.0.0' ); // 游戏脚本 wp_enqueue_script( 'memory-game-js', get_stylesheet_directory_uri() . '/games/js/memory-game.js', array(), '1.0.0', true ); } } 第三部分:创建简易抽奖转盘游戏 3.1 转盘游戏设计与实现 抽奖转盘是另一种受欢迎的互动形式,特别适合电商网站和营销活动。我们将创建一个可配置的转盘游戏: // 注册转盘游戏短代码 add_shortcode('wheel_of_fortune', 'wheel_of_fortune_shortcode'); function wheel_of_fortune_shortcode($atts) { $atts = shortcode_atts( array( 'segments' => '优惠券10%,谢谢参与,优惠券20%,再来一次,折扣30%,幸运奖,优惠券15%,大奖', 'colors' => '#FF6384,#36A2EB,#FFCE56,#4BC0C0,#9966FF,#FF9F40,#FF6384,#36A2EB', 'prize_text' => '恭喜您获得:', 'button_text' => '开始抽奖' ), $atts, 'wheel_of_fortune' ); $game_id = 'wheel_game_' . uniqid(); $segments = explode(',', $atts['segments']); $colors = explode(',', $atts['colors']); ob_start(); ?> <div id="<?php echo esc_attr($game_id); ?>" class="wheel-game-container"> <div class="wheel-header"> <h3>幸运大转盘</h3> <p>试试你的运气,赢取惊喜奖励!</p> </div> <div class="wheel-content"> <div class="wheel-wrapper"> <canvas id="<?php echo esc_attr($game_id); ?>_canvas" class="wheel-canvas" width="400" height="400"></canvas> <div class="wheel-pointer"></div> </div> <div class="wheel-controls"> <div class="wheel-stats"> <p>剩余抽奖次数: <span class="spins-left">3</span></p> <p class="prize-result"></p> </div> <button class="spin-button"><?php echo esc_html($atts['button_text']); ?></button> <button class="reset-spins">重置次数</button> <div class="wheel-segments"> <h4>奖项设置:</h4> <ul> <?php foreach ($segments as $index => $segment): ?> <li> <span class="segment-color" style="background-color: <?php echo esc_attr($colors[$index % count($colors)]); ?>"></span> <?php echo esc_html($segment); ?> </li> <?php endforeach; ?> </ul> </div> </div> </div> <div class="wheel-history"> <h4>中奖记录</h4> <ul class="history-list"></ul> </div> </div> <script type="text/javascript"> document.addEventListener('DOMContentLoaded', function() { new WheelOfFortune( '<?php echo esc_js($game_id); ?>', <?php echo json_encode($segments); ?>, <?php echo json_encode($colors); ?>, '<?php echo esc_js($atts['prize_text']); ?>' ); }); </script> <?php return ob_get_clean(); } 3.2 转盘游戏JavaScript实现 创建 /games/js/wheel-game.js 文件: class WheelOfFortune { constructor(containerId, segments, colors, prizeText) { this.container = document.getElementById(containerId); this.canvas = this.container.querySelector('.wheel-canvas'); this.ctx = this.canvas.getContext('2d'); this.spinButton = this.container.querySelector('.spin-button'); this.resetButton = this.container.querySelector('.reset-spins'); this.prizeResult = this.container.querySelector('.prize-result'); this.spinsLeftElement = this.container.querySelector('.spins-left'); this.historyList = this.container.querySelector('.history-list'); this.segments = segments; this.colors = colors; this.prizeText = prizeText; // 游戏状态 this.spinsLeft = 3; this.isSpinning = false; this.currentRotation = 0; this.segmentAngle = (2 * Math.PI) / this.segments.length; this.init(); } init() { this.drawWheel(); this.setupEventListeners(); this.updateSpinsDisplay(); this.loadHistory(); } drawWheel() { const centerX = this.canvas.width / 2; const centerY = this.canvas.height / 2; const radius = Math.min(centerX, centerY) - 10; // 清除画布 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // 绘制每个扇形 for (let i = 0; i < this.segments.length; i++) { const startAngle = this.currentRotation + (i * this.segmentAngle); const endAngle = startAngle + this.segmentAngle; // 绘制扇形 this.ctx.beginPath(); this.ctx.moveTo(centerX, centerY); this.ctx.arc(centerX, centerY, radius, startAngle, endAngle); this.ctx.closePath(); // 填充颜色 this.ctx.fillStyle = this.colors[i % this.colors.length]; this.ctx.fill(); // 绘制边框 this.ctx.strokeStyle = '#FFFFFF'; this.ctx.lineWidth = 2; this.ctx.stroke(); // 绘制文本 this.ctx.save(); this.ctx.translate(centerX, centerY); this.ctx.rotate(startAngle + this.segmentAngle / 2); this.ctx.textAlign = 'right'; this.ctx.fillStyle = '#FFFFFF'; this.ctx.font = 'bold 14px Arial'; this.ctx.fillText(this.segments[i], radius - 20, 5); this.ctx.restore(); } // 绘制中心圆 this.ctx.beginPath(); this.ctx.arc(centerX, centerY, 20, 0, 2 * Math.PI); this.ctx.fillStyle = '#333333'; this.ctx.fill(); this.ctx.strokeStyle = '#FFFFFF'; this.ctx.lineWidth = 3; this.ctx.stroke(); } setupEventListeners() { this.spinButton.addEventListener('click', () => this.spinWheel()); this.resetButton.addEventListener('click', () => this.resetSpins()); } spinWheel() { if (this.isSpinning || this.spinsLeft <= 0) return; this.isSpinning = true; this.spinsLeft--; this.updateSpinsDisplay(); // 随机决定停止位置 const spinDuration = 3000 + Math.random() * 2000; // 3-5秒 const extraRotation = 5 + Math.random() * 5; // 额外旋转5-10圈 const totalRotation = (extraRotation * 2 * Math.PI) + (Math.random() * this.segmentAngle); // 动画开始时间 const startTime = Date.now(); const animate = () => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / spinDuration, 1); // 缓动函数:先快后慢 const easeOut = 1 - Math.pow(1 - progress, 3); // 更新旋转角度 this.currentRotation = easeOut * totalRotation; this.drawWheel(); if (progress < 1) { requestAnimationFrame(animate); } else { // 动画结束 this.isSpinning = false; this.determinePrize(); } }; animate(); } determinePrize() { // 计算指针指向的扇形 const normalizedRotation = this.currentRotation % (2 * Math.PI); const segmentIndex = Math.floor( ((2 * Math.PI - normalizedRotation) % (2 * Math.PI)) / this.segmentAngle ); const prize = this.segments[segmentIndex]; // 显示结果 this.prizeResult.textContent = `${this.prizeText} ${prize}`; this.prizeResult.style.color = this.colors[segmentIndex % this.colors.length]; // 添加到历史记录 this.addToHistory(prize); // 保存到本地存储 this.saveHistory(prize); // 如果是"再来一次",增加一次抽奖机会 if (prize === '再来一次') { this.spinsLeft++; this.updateSpinsDisplay(); } } addToHistory(prize) { const now = new Date(); const timeString = now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); const historyItem = document.createElement('li'); historyItem.innerHTML = ` <span class="history-time">${timeString}</span> <span class="history-prize">${prize}</span> `; this.historyList.insertBefore(historyItem, this.historyList.firstChild); // 限制历史记录数量 if (this.historyList.children.length > 10) { this.historyList.removeChild(this.historyList.lastChild); } } saveHistory(prize) { const history = JSON.parse(localStorage.getItem('wheelGameHistory') || '[]'); history.unshift({ prize: prize, timestamp: new Date().toISOString() }); // 只保留最近20条记录 const recentHistory = history.slice(0, 20); localStorage.setItem('wheelGameHistory', JSON.stringify(recentHistory)); } loadHistory() { const history = JSON.parse(localStorage.getItem('wheelGameHistory') || '[]'); history.forEach(item => { const date = new Date(item.timestamp); const timeString = date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); const historyItem = document.createElement('li'); historyItem.innerHTML = ` <span class="history-time">${timeString}</span> <span class="history-prize">${item.prize}</span> `; this.historyList.appendChild(historyItem); }); } updateSpinsDisplay() { this.spinsLeftElement.textContent = this.spinsLeft; if (this.spinsLeft <= 0) { this.spinButton.disabled = true; this.spinButton.textContent = '次数已用完'; } else { this.spinButton.disabled = false; this.spinButton.textContent = '开始抽奖'; } } resetSpins() { this.spinsLeft = 3; this.updateSpinsDisplay(); this.prizeResult.textContent = ''; } } 3.3 转盘游戏样式设计 创建 /games/css/wheel-game.css 文件: .wheel-game-container { max-width: 900px; margin: 30px auto; padding: 25px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2); color: white; font-family: 'Arial', sans-serif; } .wheel-header { text-align: center; margin-bottom: 30px; } .wheel-header h3 { font-size: 32px; margin-bottom: 10px; color: #FFD700; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); } .wheel-header p { font-size: 18px; opacity: 0.9; } .wheel-content { display: flex; flex-wrap: wrap; gap: 40px; align-items: center; justify-content: center; } .wheel-wrapper { position: relative; flex: 0 0 auto; } .wheel-canvas { background: white; border-radius: 50%; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); border: 8px solid #FFD700; } .wheel-pointer { position: absolute; top: -20px; left: 50%; transform: translateX(-50%); width: 0; height: 0; border-left: 20px solid transparent; border-right: 20px solid transparent; border-top: 40px solid #FF0000; filter: drop-shadow(0 5px 5px rgba(0, 0, 0, 0.3)); z-index: 10; } .wheel-controls { flex: 1; min-width: 300px; background: rgba(255, 255, 255, 0.1); padding: 25px; border-radius: 15px; backdrop-filter: blur(10px); } .wheel-stats { margin-bottom: 25px; padding: 15px; background: rgba(0, 0, 0, 0.2); border-radius: 10px; } .wheel-stats p { font-size: 18px; margin: 10px 0; } .prize-result { font-size: 22px !important; font-weight: bold; color: #FFD700 !important; min-height: 30px; margin-top: 15px !important; } .wheel-controls button { display: block; width: 100%; padding: 15px; margin: 10px 0; border: none; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; } .spin-button { background: linear-gradient(45deg, #FF416C, #FF4B2B); color: white; } .spin-button:hover:not(:disabled) { transform: translateY(-3px);

发表评论

手把手教程,在WordPress中集成网站Cookie合规管理与用户同意横幅

手把手教程:在WordPress中集成网站Cookie合规管理与用户同意横幅,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:为什么Cookie合规管理如此重要? 在当今数字时代,数据隐私已成为全球关注的焦点。随着欧盟《通用数据保护条例》(GDPR)、加州消费者隐私法案(CCPA)以及中国《个人信息保护法》等法规的实施,网站所有者必须确保其在线平台符合数据保护要求。Cookie作为网站跟踪用户行为、存储偏好设置的关键工具,其使用必须透明且获得用户明确同意。 WordPress作为全球最流行的内容管理系统,驱动着超过40%的网站。然而,许多WordPress网站所有者并未充分意识到Cookie合规的重要性,或不知道如何正确实施合规解决方案。本教程将手把手指导您通过代码二次开发,在WordPress中集成完整的Cookie合规管理系统,包括用户同意横幅、偏好设置中心和常用互联网小工具功能。 第一部分:理解Cookie合规的基本要求 1.1 主要数据保护法规概述 在开始技术实施之前,了解相关法规的基本要求至关重要: GDPR(欧盟通用数据保护条例):要求网站在使用非必要Cookie前获得用户明确、知情的同意 CCPA(加州消费者隐私法案):赋予加州居民了解其个人信息被收集、拒绝出售个人信息的权利 ePrivacy指令:专门规范电子通信隐私,包括Cookie使用 中国《个人信息保护法》:规定个人信息处理应取得个人同意,并遵循最小必要原则 1.2 Cookie分类与合规要求 根据功能,Cookie通常分为以下几类: 必要Cookie:确保网站基本功能运行,无需用户同意 偏好Cookie:记住用户选择(如语言、地区),需要用户同意 统计Cookie:收集匿名数据用于分析,需要用户同意 营销Cookie:跟踪用户行为用于广告定向,需要明确同意 1.3 WordPress网站Cookie合规现状 大多数WordPress网站通过以下方式使用Cookie: 核心WordPress:使用登录认证Cookie 主题和插件:添加各种功能性和跟踪Cookie 第三方服务:如Google Analytics、Facebook像素等 第二部分:规划Cookie合规解决方案架构 2.1 系统需求分析 一个完整的Cookie合规管理系统应包含: 可定制的同意横幅:清晰说明Cookie使用目的 同意管理平台:允许用户查看和修改偏好设置 Cookie分类拦截:在获得同意前阻止非必要脚本 同意记录:存储用户同意状态和偏好 定期重新同意:根据法规要求定期更新同意 2.2 技术架构设计 我们将构建一个轻量级但功能完整的解决方案: 前端组件:使用HTML、CSS和JavaScript创建响应式横幅和设置面板 后端处理:使用PHP处理同意状态存储和脚本管理 数据库设计:存储用户同意偏好和日志 集成机制:与WordPress核心和第三方插件无缝集成 第三部分:创建WordPress插件基础结构 3.1 初始化插件文件 首先,在wp-content/plugins目录下创建新文件夹"cookie-compliance-manager",然后创建主插件文件: <?php /** * Plugin Name: Cookie Compliance Manager * Plugin URI: https://yourwebsite.com/ * Description: 完整的Cookie合规管理与用户同意解决方案 * Version: 1.0.0 * Author: Your Name * License: GPL v2 or later * Text Domain: cookie-compliance-manager */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('CCM_VERSION', '1.0.0'); define('CCM_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('CCM_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 require_once CCM_PLUGIN_DIR . 'includes/class-cookie-compliance-manager.php'; function run_cookie_compliance_manager() { $plugin = new Cookie_Compliance_Manager(); $plugin->run(); } run_cookie_compliance_manager(); 3.2 创建主管理类 在includes目录下创建主类文件: <?php class Cookie_Compliance_Manager { private $loader; public function __construct() { $this->load_dependencies(); $this->define_admin_hooks(); $this->define_public_hooks(); } private function load_dependencies() { require_once CCM_PLUGIN_DIR . 'includes/class-ccm-loader.php'; require_once CCM_PLUGIN_DIR . 'includes/class-ccm-i18n.php'; require_once CCM_PLUGIN_DIR . 'admin/class-ccm-admin.php'; require_once CCM_PLUGIN_DIR . 'public/class-ccm-public.php'; $this->loader = new CCM_Loader(); } private function define_admin_hooks() { $plugin_admin = new CCM_Admin(); $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_styles'); $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts'); $this->loader->add_action('admin_menu', $plugin_admin, 'add_admin_menu'); $this->loader->add_action('admin_init', $plugin_admin, 'register_settings'); } private function define_public_hooks() { $plugin_public = new CCM_Public(); $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_styles'); $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_scripts'); $this->loader->add_action('wp_head', $plugin_public, 'insert_cookie_consent_banner'); $this->loader->add_action('wp_footer', $plugin_public, 'insert_cookie_settings_modal'); $this->loader->add_action('init', $plugin_public, 'handle_ajax_requests'); } public function run() { $this->loader->run(); } } 第四部分:构建Cookie同意横幅前端界面 4.1 设计响应式Cookie横幅 创建public/css/ccm-public.css文件: /* Cookie同意横幅基础样式 */ .ccm-cookie-banner { position: fixed; bottom: 0; left: 0; right: 0; background: #2c3e50; color: #ecf0f1; padding: 20px; z-index: 999999; box-shadow: 0 -2px 10px rgba(0,0,0,0.1); display: none; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; } .ccm-banner-content { max-width: 1200px; margin: 0 auto; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; } .ccm-banner-text { flex: 1; min-width: 300px; margin-right: 20px; } .ccm-banner-text h3 { margin: 0 0 10px 0; color: #3498db; font-size: 1.2em; } .ccm-banner-text p { margin: 0 0 15px 0; line-height: 1.5; font-size: 14px; } .ccm-banner-text a { color: #3498db; text-decoration: underline; } .ccm-banner-actions { display: flex; gap: 10px; flex-wrap: wrap; } .ccm-btn { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-weight: 600; transition: all 0.3s ease; font-size: 14px; } .ccm-btn-accept-all { background: #27ae60; color: white; } .ccm-btn-accept-all:hover { background: #219653; } .ccm-btn-settings { background: #3498db; color: white; } .ccm-btn-settings:hover { background: #2980b9; } .ccm-btn-reject-all { background: #e74c3c; color: white; } .ccm-btn-reject-all:hover { background: #c0392b; } /* 响应式设计 */ @media (max-width: 768px) { .ccm-banner-content { flex-direction: column; text-align: center; } .ccm-banner-text { margin-right: 0; margin-bottom: 15px; } .ccm-banner-actions { justify-content: center; } } 4.2 创建Cookie设置模态窗口 添加设置面板的HTML结构和CSS: /* Cookie设置模态窗口 */ .ccm-settings-modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 1000000; overflow-y: auto; } .ccm-modal-content { background: white; margin: 50px auto; max-width: 800px; border-radius: 8px; box-shadow: 0 5px 30px rgba(0,0,0,0.3); color: #333; } .ccm-modal-header { padding: 20px 30px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; } .ccm-modal-header h2 { margin: 0; color: #2c3e50; } .ccm-close-modal { background: none; border: none; font-size: 24px; cursor: pointer; color: #7f8c8d; } .ccm-modal-body { padding: 30px; } .ccm-cookie-category { margin-bottom: 25px; padding: 20px; border: 1px solid #ddd; border-radius: 6px; background: #f9f9f9; } .ccm-category-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .ccm-category-header h3 { margin: 0; color: #2c3e50; } .ccm-category-toggle { position: relative; display: inline-block; width: 50px; height: 24px; } .ccm-category-toggle input { opacity: 0; width: 0; height: 0; } .ccm-toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 24px; } .ccm-toggle-slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .ccm-toggle-slider { background-color: #27ae60; } input:checked + .ccm-toggle-slider:before { transform: translateX(26px); } .ccm-category-description { color: #666; font-size: 14px; line-height: 1.5; } .ccm-cookie-list { margin-top: 15px; font-size: 13px; } .ccm-cookie-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eee; } .ccm-modal-footer { padding: 20px 30px; border-top: 1px solid #eee; text-align: right; background: #f9f9f9; border-radius: 0 0 8px 8px; } 第五部分:实现JavaScript交互功能 5.1 创建主JavaScript文件 在public/js/ccm-public.js中实现核心交互逻辑: (function($) { 'use strict'; var CCM = { // 初始化 init: function() { this.bindEvents(); this.checkConsent(); this.loadBlockedScripts(); }, // 绑定事件 bindEvents: function() { // 接受所有Cookie $(document).on('click', '.ccm-btn-accept-all', function(e) { e.preventDefault(); CCM.saveConsent('all'); CCM.hideBanner(); }); // 拒绝所有非必要Cookie $(document).on('click', '.ccm-btn-reject-all', function(e) { e.preventDefault(); CCM.saveConsent('necessary'); CCM.hideBanner(); }); // 打开设置 $(document).on('click', '.ccm-btn-settings', function(e) { e.preventDefault(); CCM.showSettingsModal(); }); // 关闭模态窗口 $(document).on('click', '.ccm-close-modal', function() { CCM.hideSettingsModal(); }); // 保存设置 $(document).on('click', '.ccm-save-settings', function() { CCM.saveCustomConsent(); }); // 阻止模态窗口外部点击关闭 $(document).on('click', '.ccm-settings-modal', function(e) { if ($(e.target).hasClass('ccm-settings-modal')) { CCM.hideSettingsModal(); } }); }, // 显示Cookie横幅 showBanner: function() { $('.ccm-cookie-banner').fadeIn(300); $('body').addClass('ccm-banner-visible'); }, // 隐藏Cookie横幅 hideBanner: function() { $('.ccm-cookie-banner').fadeOut(300); $('body').removeClass('ccm-banner-visible'); }, // 显示设置模态窗口 showSettingsModal: function() { $('.ccm-settings-modal').fadeIn(300); $('body').addClass('ccm-modal-visible'); this.hideBanner(); }, // 隐藏设置模态窗口 hideSettingsModal: function() { $('.ccm-settings-modal').fadeOut(300); $('body').removeClass('ccm-modal-visible'); }, // 检查同意状态 checkConsent: function() { var consent = this.getConsent(); if (!consent || consent.status === 'pending') { this.showBanner(); } else { this.hideBanner(); } }, // 获取同意状态 getConsent: function() { var consentCookie = this.getCookie('ccm_consent'); if (consentCookie) { try { return JSON.parse(decodeURIComponent(consentCookie)); } catch (e) { return null; } } return null; }, // 保存同意设置 saveConsent: function(type) { var consent = { version: '1.0', date: new Date().toISOString(), status: 'given' }; switch(type) { case 'all': consent.categories = { necessary: true, preferences: true, statistics: true, marketing: true }; break; case 'necessary': consent.categories = { necessary: true, preferences: false, statistics: false, marketing: false }; break; default: // 自定义设置 consent.categories = this.getCategorySelections(); } // 设置Cookie(365天过期) this.setCookie('ccm_consent', JSON.stringify(consent), 365); // 触发同意更改事件 $(document).trigger('ccm_consent_changed', [consent]); // 重新加载被阻止的脚本 this.loadBlockedScripts(); }, // 保存自定义同意设置 saveCustomConsent: function() { this.saveConsent('custom'); this.hideSettingsModal(); this.showNotification('设置已保存成功!'); }, // 获取类别选择 getCategorySelections: function() { return { necessary: true, // 必要Cookie始终启用 preferences: $('#ccm-category-preferences').is(':checked'), statistics: $('#ccm-category-statistics').is(':checked'), marketing: $('#ccm-category-marketing').is(':checked') }; }, // 加载被阻止的脚本 loadBlockedScripts: function() { var consent = this.getConsent(); if (!consent || !consent.categories) { return; } // 根据同意类别加载脚本 if (consent.categories.statistics) { this.loadStatisticsScripts(); } if (consent.categories.marketing) { this.loadMarketingScripts(); } }, // 加载统计脚本 loadStatisticsScripts: function() { // 加载Google Analytics if (window.ccmConfig.gaTrackingId) { this.loadGoogleAnalytics(window.ccmConfig.gaTrackingId); } // 这里可以添加其他统计脚本 }, // 加载Google Analytics loadGoogleAnalytics: function(trackingId) { window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('config', trackingId, { 'anonymize_ip': true }); var script = document.createElement('script'); script.async = true; script.src = 'https://www.googletagmanager.com/gtag/js?id=' + trackingId; document.head.appendChild(script); }, // 加载营销脚本 loadMarketingScripts: function() { // 加载Facebook像素 if (window.ccmConfig.fbPixelId) { this.loadFacebookPixel(window.ccmConfig.fbPixelId); } // 这里可以添加其他营销脚本 }, // 加载Facebook像素 loadFacebookPixel: function(pixelId) { !function(f,b,e,v,n,t,s) {if(f.fbq)return;n=f.fbq=function(){n.callMethod? n.callMethod.apply(n,arguments):n.queue.push(arguments)}; if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; n.queue=[];t=b.createElement(e);t.async=!0; t.src=v;s=b.getElementsByTagName(e)[0]; s.parentNode.insertBefore(t,s)}(window, document,'script', 'https://connect.facebook.net/en_US/fbevents.js'); fbq('init', pixelId); fbq('track', 'PageView'); }, // Cookie操作辅助函数 setCookie: function(name, value, days) { var expires = ""; if (days) { var date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); expires = "; expires=" + date.toUTCString(); } document.cookie = name + "=" + encodeURIComponent(value) + expires + "; path=/; SameSite=Lax"; }, getCookie: function(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i = 0; i < ca.length; i++) { var c = ca[i]; while (c.charAt(0) == ' ') c = c.substring(1, c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length); } return null; }, // 显示通知 showNotification: function(message) { var notification = $('<div class="ccm-notification">' + message + '</div>'); $('body').append(notification); notification.css({ position: 'fixed', top: '20px', right: '20px', background: '#27ae60', color: 'white', padding: '15px 20px', borderRadius: '4px', zIndex: '1000001', boxShadow: '0 2px 10px rgba(0,0,0,0.2)' }); setTimeout(function() { notification.fadeOut(300, function() { $(this).remove(); }); }, 3000); } }; // 文档加载完成后初始化 $(document).ready(function() { CCM.init(); }); // 暴露到全局作用域 window.CookieComplianceManager = CCM; })(jQuery); ### 第六部分:实现PHP后端处理逻辑 #### 6.1 创建公共功能类 在public/class-ccm-public.php中实现后端逻辑: <?phpclass CCM_Public { private $plugin_name; private $version; public function __construct() { $this->plugin_name = 'cookie-compliance-manager'; $this->version = CCM_VERSION; } // 注册前端样式和脚本 public function enqueue_styles() { wp_enqueue_style( $this->plugin_name, CCM_PLUGIN_URL . 'public/css/ccm-public.css', array(), $this->version, 'all' ); } public function enqueue_scripts() { wp_enqueue_script( 'jquery' ); wp_enqueue_script( $this->plugin_name, CCM_PLUGIN_URL . 'public/js/ccm-public.js', array('jquery'), $this->version, true ); // 传递配置到JavaScript wp_localize_script($this->plugin_name, 'ccmConfig', array( 'ajax_url' => admin_url('admin-ajax.php'), 'gaTrackingId' => get_option('ccm_ga_tracking_id', ''), 'fbPixelId' => get_option('ccm_fb_pixel_id', ''), 'nonce' => wp_create_nonce('ccm_nonce') )); } // 插入Cookie同意横幅 public function insert_cookie_consent_banner() { if ($this->should_show_banner()) { ?> <div class="ccm-cookie-banner" id="ccm-cookie-banner"> <div class="ccm-banner-content"> <div class="ccm-banner-text"> <h3>Cookie设置</h3> <p>我们使用Cookie来提升您的浏览体验、分析网站流量并个性化内容。点击"接受所有"即表示您同意我们使用所有Cookie。您可以通过"Cookie设置"管理您的偏好。了解更多,请查看我们的<a href="<?php echo get_privacy_policy_url(); ?>">隐私政策</a>。</p> </div> <div class="ccm-banner-actions"> <button type="button" class="ccm-btn ccm-btn-accept-all">接受所有</button> <button type="button" class="ccm-btn ccm-btn-settings">Cookie设置</button> <button type="button" class="ccm-btn ccm-btn-reject-all">拒绝非必要</button> </div> </div> </div> <?php } } // 插入Cookie设置模态窗口 public function insert_cookie_settings_modal() { ?> <div class="ccm-settings-modal" id="ccm-settings-modal"> <div class="ccm-modal-content"> <div class="ccm-modal-header"> <h2>Cookie偏好设置</h2> <button type="button" class="ccm-close-modal">&times;</button> </div> <div class="ccm-modal-body"> <p>您可以选择接受或拒绝不同类型的Cookie。必要Cookie对于网站基本功能是必需的,无法被拒绝。</p> <div class="ccm-cookie-category"> <div class="ccm-category-header"> <h3>必要Cookie</h3> <label class="ccm-category-toggle"> <input type="checkbox" id="ccm-category-necessary" checked disabled> <span class="ccm-toggle-slider"></span> </label> </div> <div class="ccm-category-description"> <p>这些Cookie对于网站的基本功能是必需的,无法被禁用。它们通常仅针对您所做的操作(例如设置隐私偏好、登录或填写表单)而设置。</p> </div> </div> <div class="ccm-cookie-category"> <div class="ccm-category-header"> <h3>偏好Cookie</h3> <label class="ccm-category-toggle"> <input type="checkbox" id="ccm-category-preferences"> <span class="ccm-toggle-slider"></span> </label> </div> <div class="ccm-category-description"> <p>这些Cookie使网站能够记住您所做的选择(例如用户名、语言或地区),并提供增强的个性化功能。</p> </div> </div> <div class="ccm-cookie-category"> <div class="ccm-category-header"> <h3>统计Cookie</h3> <label class="ccm-category-toggle"> <input type="checkbox" id="ccm-category-statistics"> <span class="ccm-toggle-slider"></span> </label> </div> <div class="ccm-category-description"> <p>这些Cookie帮助我们了解访问者如何与网站互动,收集匿名信息用于改进网站功能。</p> <div class="ccm-cookie-list"> <div class="ccm-cookie-item"> <span>Google Analytics</span> <span>分析用户行为</span> </div> </div> </div> </div> <div class="ccm-cookie-category"> <div class="ccm-category-header"> <h3>营销Cookie</h3> <label class="ccm-category-toggle"> <input type="checkbox" id="ccm-category-marketing"> <span class="ccm-toggle-slider"></span> </label> </div> <div class="ccm-category-description"> <p>这些Cookie用于跟踪访问者跨网站的浏览习惯,以显示更相关的广告。</p> <div class="ccm-cookie-list"> <div class="ccm-cookie-item"> <span>Facebook Pixel</span> <span>广告效果跟踪</span> </div> </div> </div> </div> </div> <div class="ccm-modal-footer"> <button type="button" class="ccm-btn ccm-btn-accept-all">接受所有</button> <button type="button" class="ccm-btn ccm-btn-save-settings">保存设置</button> <button type="button" class="ccm-btn ccm-btn-reject-all">仅接受必要</button> </div> </div> </div> <?php } // 处理AJAX请求 public function handle_ajax_requests() { add_action('wp_ajax_ccm_save_consent', array($this, 'ajax_save_consent')); add_action('wp_ajax_nopriv_ccm_save_consent', array($this, 'ajax_save_consent')); add_action('wp_ajax_ccm_get_consent', array($this, 'ajax_get_consent')); add_action('wp_ajax_nopriv_ccm_get_consent', array($this, 'ajax_get_consent')); } // AJAX保存同意设置 public function ajax_save_consent() { check_ajax_referer('ccm_nonce', 'nonce'); $consent_data = array( 'version' => '1.0', 'date' => current_time('mysql'), 'ip_address' => $this->get_user_ip(), 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'categories' => array( 'necessary' => true, 'preferences' => isset($_POST['preferences']) ? filter_var($_POST['preferences'], FILTER_VALIDATE_BOOLEAN) : false, 'statistics' => isset($_POST['statistics']) ? filter_var($_POST['statistics'], FILTER_VALIDATE_BOOLEAN) : false, 'marketing' => isset($_POST['marketing']) ? filter_var($_POST['marketing'], FILTER_VALIDATE_BOOLEAN) : false ) ); // 保存到数据库 $this->save_consent_to_db($consent_data); // 设置Cookie $this->set_consent_cookie($consent_data); wp_send_json_success(array( 'message' => '同意设置已保存', 'consent' => $consent_data )); } // AJAX获取同意设置 public function ajax_get_consent() { $consent = $this->get_user_consent(); if ($consent) { wp_send_json_success($consent); } else { wp_send_json_error('未找到同意记录'); } } // 保存同意到数据库 private function save_consent_to_db($consent_data) { global $wpdb; $table_name = $wpdb->prefix . 'ccm_consents'; $data = array( 'user_id' => get_current_user_id(), 'ip_address' => $consent_data['ip_address'], 'user_agent' => $consent_data['user_agent'], 'consent_data' => json_encode($consent_data), 'created_at' => current_time('mysql') ); $wpdb->insert($table_name, $data); } // 设置同意Cookie private function set_consent_cookie($consent_data) { $cookie_value = json_encode($consent_data); $expiry = time() + (365 * 24 * 60 * 60); // 1年 setcookie('ccm_consent', $cookie_value, $expiry, '/', '', is_ssl(), true); } // 获取用户同意状态 private function get_user_consent() { if (isset($_COOKIE['ccm_consent'])) { return json_decode(stripslashes($_COOKIE['ccm_consent']), true); } return null; } // 检查是否应该显示横幅 private function should_show_banner() { // 如果用户已经做出选择,不显示横幅 $consent = $this->get_user_consent(); if ($consent && isset($consent['date'])) { return false; } // 检查是否在管理页面 if (is_admin()) { return false; } // 检查是否在特定页面排除 $excluded_pages = get_option('ccm_excluded_pages', array()); if (is_page($excluded_pages)) { return false; } return true; } // 获取用户IP地址 private function get_user_ip() { $ip_keys = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'); foreach ($ip_keys as $key) { if (array_key_exists($key, $_SERVER) === true) { foreach (explode(',', $_SERVER[$key]) as $ip) { $ip = trim($ip); if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) { return $ip; } } } } return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; } } ### 第七部分:创建管理后台界面 #### 7.1 构建管理设置页面 在admin/class-ccm-admin.php中创建管理界面: <?phpclass CCM_Admin { private $plugin_name; private $version; public function __construct() { $this->plugin_name = 'cookie-compliance-manager'; $this->version = CCM_VERSION; } // 注册管理菜单 public function add_admin_menu() { add_menu_page( 'Cookie合规管理', 'Cookie合规', 'manage_options', 'cookie-compliance-manager', array($this, 'display_admin_page'), 'dashicons-shield', 80 ); add_submenu_page( 'cookie-compliance-manager', '设置', '设置', 'manage_options', 'cookie-compliance-settings', array($this, 'display_settings_page') ); add_submenu_page( 'cookie-compliance-manager', '同意记录', '同意记录', 'manage_options', 'cookie-compliance-consents', array($this, 'display_consents_page') ); } // 显示主管理页面 public function display_admin_page() { ?> <div class="wrap"> <h1>Cookie合规管理</h1> <div class="ccm-admin-container"> <div class="ccm-admin-header"> <div class="ccm-stats-cards"> <div class="ccm-stat-card"> <h3>总同意数</h3> <p class="ccm-stat-number"><?php echo $this->get_total_consents(); ?></p> </div> <div class="ccm-stat-card"> <h3>今日同意</h3> <p class="ccm-stat-number"><?php echo $this->get_today_consents(); ?></p> </div> <div class="ccm-stat-card"> <h3>接受率</h3> <p class="ccm-stat-number"><?php echo $this->get_acceptance_rate(); ?>%</p> </div> </div> </div> <div class="ccm-admin-content"> <div class="ccm-welcome-panel"> <h2>欢迎使用Cookie合规管理器</h2> <p>确保您的网站符合GDPR、CCPA等数据保护法规要求。</p> <div class="ccm-quick-links"> <a href="?page=cookie-compliance-settings" class="button button-primary">配置设置</a> <a href="?page=cookie-compliance-consents" class="button">查看同意记录</a> <a href="#" class="button">生成隐私政策</a> </div> </div> <div class="ccm-compliance-status"> <h3>合规状态检查</h3> <ul class="ccm-status-list"> <li class="ccm-status-item <?php echo $this->check_banner_status() ? 'ccm-status-ok' : 'ccm-status-error'; ?>"> <span

发表评论

详细教程,为网站打造内嵌的在线视频剪辑与简易制作工具

详细教程:为网站打造内嵌的在线视频剪辑与简易制作工具,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:为什么网站需要内嵌视频编辑工具? 在当今数字内容为王的时代,视频已成为最受欢迎的内容形式之一。据统计,全球互联网用户每天观看的视频时长超过10亿小时,而超过85%的企业使用视频作为营销工具。然而,对于许多网站运营者来说,视频制作一直是个门槛——用户需要离开网站,使用专业软件编辑视频,再上传回网站,这一流程既繁琐又影响用户体验。 想象一下,如果您的WordPress网站能够直接提供在线视频剪辑功能,让用户无需离开页面即可完成视频裁剪、合并、添加字幕和特效等操作,这将是多么强大的竞争优势!无论是教育平台让学生编辑课程录像,电商网站让卖家制作产品展示视频,还是社交平台让用户创作内容,内嵌视频工具都能显著提升用户参与度和内容产出效率。 本教程将详细指导您如何通过WordPress代码二次开发,为您的网站打造一个功能完整的在线视频剪辑与制作工具。我们将从基础原理讲起,逐步实现核心功能,最终整合成一个可直接使用的解决方案。 第一部分:准备工作与环境搭建 1.1 理解WordPress插件开发基础 在开始之前,我们需要了解WordPress插件的基本结构。WordPress插件是独立的代码模块,可以扩展WordPress的功能而不修改核心代码。一个基本的插件至少包含: 主PHP文件(包含插件头信息) 必要的JavaScript和CSS文件 可选的资源文件(如图像、字体等) 插件头信息示例: <?php /** * Plugin Name: 在线视频剪辑工具 * Plugin URI: https://yourwebsite.com/video-editor * Description: 为WordPress网站添加在线视频剪辑功能 * Version: 1.0.0 * Author: 您的名字 * License: GPL v2 or later */ 1.2 开发环境配置 本地开发环境:建议使用Local by Flywheel、XAMPP或MAMP搭建本地WordPress环境 代码编辑器:推荐VS Code、PHPStorm或Sublime Text 浏览器开发者工具:Chrome或Firefox的开发者工具是调试前端代码的必备 版本控制:初始化Git仓库管理代码版本 1.3 关键技术栈选择 为了实现在线视频编辑,我们需要选择合适的技术方案: 前端视频处理:FFmpeg.js或WebAssembly版本的FFmpeg 前端框架:React或Vue.js(本教程使用纯JavaScript以降低复杂度) 视频播放器:Video.js或plyr.js UI组件:自定义CSS或使用轻量级UI库 后端处理:PHP + WordPress REST API 1.4 创建插件基本结构 在wp-content/plugins目录下创建"video-editor-tool"文件夹,并建立以下结构: video-editor-tool/ ├── video-editor.php # 主插件文件 ├── includes/ # PHP类文件 │ ├── class-video-processor.php │ ├── class-video-library.php │ └── class-ajax-handler.php ├── admin/ # 后台相关文件 │ ├── css/ │ ├── js/ │ └── admin-page.php ├── public/ # 前端相关文件 │ ├── css/ │ ├── js/ │ ├── libs/ # 第三方库 │ └── templates/ # 前端模板 ├── assets/ # 静态资源 │ ├── images/ │ └── fonts/ └── vendor/ # 第三方PHP库 第二部分:核心视频处理功能实现 2.1 集成FFmpeg.js进行客户端视频处理 FFmpeg.js是FFmpeg的JavaScript端口,允许在浏览器中直接处理视频文件。由于视频处理是计算密集型任务,我们将在客户端进行基本操作以减少服务器压力。 步骤1:引入FFmpeg.js <!-- 在编辑器页面添加 --> <script src="<?php echo plugin_dir_url(__FILE__); ?>public/libs/ffmpeg/ffmpeg.min.js"></script> 步骤2:创建视频处理管理器 // public/js/video-processor.js class VideoProcessor { constructor() { this.ffmpeg = null; this.videoElements = []; this.isFFmpegLoaded = false; this.initFFmpeg(); } async initFFmpeg() { try { // 加载FFmpeg const { createFFmpeg, fetchFile } = FFmpeg; this.ffmpeg = createFFmpeg({ log: true }); await this.ffmpeg.load(); this.isFFmpegLoaded = true; console.log('FFmpeg加载成功'); } catch (error) { console.error('FFmpeg加载失败:', error); } } // 裁剪视频 async trimVideo(inputFile, startTime, endTime) { if (!this.isFFmpegLoaded) { throw new Error('FFmpeg未加载完成'); } // 将视频文件写入FFmpeg文件系统 this.ffmpeg.FS('writeFile', 'input.mp4', await fetchFile(inputFile)); // 执行裁剪命令 await this.ffmpeg.run( '-i', 'input.mp4', '-ss', startTime.toString(), '-to', endTime.toString(), '-c', 'copy', 'output.mp4' ); // 读取输出文件 const data = this.ffmpeg.FS('readFile', 'output.mp4'); return new Blob([data.buffer], { type: 'video/mp4' }); } // 合并多个视频 async mergeVideos(videoFiles) { // 创建文件列表 const fileList = videoFiles.map((file, index) => { const filename = `input${index}.mp4`; this.ffmpeg.FS('writeFile', filename, await fetchFile(file)); return `file '${filename}'`; }).join('n'); // 写入文件列表到FFmpeg文件系统 this.ffmpeg.FS('writeFile', 'filelist.txt', fileList); // 执行合并命令 await this.ffmpeg.run( '-f', 'concat', '-safe', '0', '-i', 'filelist.txt', '-c', 'copy', 'output.mp4' ); const data = this.ffmpeg.FS('readFile', 'output.mp4'); return new Blob([data.buffer], { type: 'video/mp4' }); } // 提取音频 async extractAudio(videoFile) { this.ffmpeg.FS('writeFile', 'input.mp4', await fetchFile(videoFile)); await this.ffmpeg.run( '-i', 'input.mp4', '-vn', '-acodec', 'libmp3lame', 'output.mp3' ); const data = this.ffmpeg.FS('readFile', 'output.mp3'); return new Blob([data.buffer], { type: 'audio/mpeg' }); } // 添加水印 async addWatermark(videoFile, watermarkImage, position = 'bottom-right') { // 实现水印添加逻辑 // 注意:这需要更复杂的FFmpeg命令 } } 2.2 视频时间轴与预览组件 时间轴是视频编辑器的核心组件,允许用户可视化地操作视频。 // public/js/timeline-editor.js class TimelineEditor { constructor(containerId, options = {}) { this.container = document.getElementById(containerId); this.videoElement = null; this.timelineCanvas = null; this.duration = 0; this.currentTime = 0; this.videoClips = []; this.isDragging = false; this.init(options); } init(options) { // 创建时间轴HTML结构 this.container.innerHTML = ` <div class="video-preview-container"> <video id="preview-video" controls></video> </div> <div class="timeline-container"> <canvas id="timeline-canvas"></canvas> <div class="timeline-controls"> <div class="playhead" id="playhead"></div> <div class="trim-handle left-handle" id="trim-left"></div> <div class="trim-handle right-handle" id="trim-right"></div> </div> </div> <div class="timeline-toolbar"> <button class="btn-cut" title="剪切">✂️</button> <button class="btn-split" title="分割">🔪</button> <button class="btn-delete" title="删除">🗑️</button> <button class="btn-add-text" title="添加文字">T</button> <button class="btn-add-transition" title="添加转场">✨</button> </div> `; this.videoElement = document.getElementById('preview-video'); this.timelineCanvas = document.getElementById('timeline-canvas'); this.playhead = document.getElementById('playhead'); this.setupEventListeners(); this.setupCanvas(); } setupCanvas() { const ctx = this.timelineCanvas.getContext('2d'); const width = this.container.offsetWidth; const height = 120; this.timelineCanvas.width = width; this.timelineCanvas.height = height; // 绘制时间轴背景 this.drawTimeline(ctx, width, height); } drawTimeline(ctx, width, height) { // 绘制背景 ctx.fillStyle = '#2d2d2d'; ctx.fillRect(0, 0, width, height); // 绘制时间刻度 const seconds = this.duration; const pixelsPerSecond = width / seconds; ctx.strokeStyle = '#555'; ctx.lineWidth = 1; ctx.fillStyle = '#888'; ctx.font = '10px Arial'; for (let i = 0; i <= seconds; i++) { const x = i * pixelsPerSecond; // 主刻度(每秒) ctx.beginPath(); ctx.moveTo(x, height - 20); ctx.lineTo(x, height); ctx.stroke(); // 时间标签 if (i % 5 === 0) { const timeText = this.formatTime(i); ctx.fillText(timeText, x - 10, height - 25); } // 次刻度(每0.5秒) if (i < seconds) { const midX = x + pixelsPerSecond / 2; ctx.beginPath(); ctx.moveTo(midX, height - 15); ctx.lineTo(midX, height); ctx.stroke(); } } // 绘制视频片段 this.videoClips.forEach((clip, index) => { this.drawVideoClip(ctx, clip, index, pixelsPerSecond, height); }); } drawVideoClip(ctx, clip, index, pixelsPerSecond, height) { const startX = clip.startTime * pixelsPerSecond; const clipWidth = (clip.endTime - clip.startTime) * pixelsPerSecond; // 绘制片段背景 ctx.fillStyle = index % 2 === 0 ? '#4a9eff' : '#6bb7ff'; ctx.fillRect(startX, 10, clipWidth, height - 40); // 绘制片段边框 ctx.strokeStyle = '#2a7fff'; ctx.lineWidth = 2; ctx.strokeRect(startX, 10, clipWidth, height - 40); // 绘制片段标签 ctx.fillStyle = 'white'; ctx.font = '12px Arial'; ctx.fillText(`片段 ${index + 1}`, startX + 5, 30); // 绘制时长 const durationText = this.formatTime(clip.endTime - clip.startTime); ctx.fillText(durationText, startX + 5, 50); } formatTime(seconds) { const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } setupEventListeners() { // 视频时间更新事件 this.videoElement.addEventListener('timeupdate', () => { this.updatePlayheadPosition(); }); // 时间轴点击事件 this.timelineCanvas.addEventListener('click', (e) => { const rect = this.timelineCanvas.getBoundingClientRect(); const x = e.clientX - rect.left; const time = (x / this.timelineCanvas.width) * this.duration; this.videoElement.currentTime = time; this.updatePlayheadPosition(); }); // 拖拽事件 this.setupDragEvents(); } updatePlayheadPosition() { if (!this.duration) return; const percentage = (this.videoElement.currentTime / this.duration) * 100; this.playhead.style.left = `${percentage}%`; } setupDragEvents() { // 实现拖拽逻辑 // 包括播放头拖拽、片段拖拽、裁剪手柄拖拽等 } loadVideo(videoFile) { return new Promise((resolve, reject) => { const url = URL.createObjectURL(videoFile); this.videoElement.src = url; this.videoElement.onloadedmetadata = () => { this.duration = this.videoElement.duration; this.videoClips = [{ startTime: 0, endTime: this.duration, file: videoFile }]; this.setupCanvas(); resolve(); }; this.videoElement.onerror = reject; }); } } 2.3 文字与特效添加功能 // public/js/text-effects-editor.js class TextEffectsEditor { constructor() { this.textTracks = []; this.effects = []; this.currentText = null; } // 添加文字轨道 addTextTrack(text, options = {}) { const textTrack = { id: Date.now(), text: text, startTime: options.startTime || 0, duration: options.duration || 5, style: { fontSize: options.fontSize || '24px', fontFamily: options.fontFamily || 'Arial', color: options.color || '#ffffff', backgroundColor: options.backgroundColor || 'rgba(0,0,0,0.5)', position: options.position || 'bottom-center', animation: options.animation || 'fade' } }; this.textTracks.push(textTrack); return textTrack; } // 渲染文字到视频 renderTextToVideo(videoElement, textTrack) { const overlay = document.createElement('div'); overlay.className = 'text-overlay'; Object.assign(overlay.style, { position: 'absolute', color: textTrack.style.color, fontSize: textTrack.style.fontSize, fontFamily: textTrack.style.fontFamily, backgroundColor: textTrack.style.backgroundColor, padding: '10px', borderRadius: '5px', ...this.getPositionStyle(textTrack.style.position) }); overlay.textContent = textTrack.text; // 添加动画类 overlay.classList.add(`text-animation-${textTrack.style.animation}`); // 添加到视频容器 const videoContainer = videoElement.parentElement; videoContainer.style.position = 'relative'; videoContainer.appendChild(overlay); // 设置显示时间 setTimeout(() => { overlay.style.display = 'block'; }, textTrack.startTime * 1000); setTimeout(() => { overlay.style.display = 'none'; overlay.remove(); }, (textTrack.startTime + textTrack.duration) * 1000); } getPositionStyle(position) { const positions = { 'top-left': { top: '10px', left: '10px' }, 'top-center': { top: '10px', left: '50%', transform: 'translateX(-50%)' }, 'top-right': { top: '10px', right: '10px' }, 'middle-left': { top: '50%', left: '10px', transform: 'translateY(-50%)' }, 'middle-center': { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }, 'middle-right': { top: '50%', right: '10px', transform: 'translateY(-50%)' }, 'bottom-left': { bottom: '10px', left: '10px' }, 'bottom-center': { bottom: '10px', left: '50%', transform: 'translateX(-50%)' }, 'bottom-right': { bottom: '10px', right: '10px' } }; return positions[position] || positions['bottom-center']; } // 添加转场效果 addTransitionEffect(videoClips, transitionType = 'fade') { const transitions = { 'fade': { duration: 1, type: 'fade' }, 'slide': { duration: 1, type: 'slide', direction: 'right' }, 'zoom': { duration: 1, type: 'zoom' }, 'rotate': { duration: 1, type: 'rotate' } }; this.effects.push({ type: 'transition', transition: transitions[transitionType], position: videoClips.length > 0 ? videoClips.length - 1 : 0 }); } } 第三部分:WordPress后端集成 3.1 创建视频处理API端点 <?php // includes/class-rest-api.php class Video_Editor_REST_API { private $namespace = 'video-editor/v1'; public function __construct() { add_action('rest_api_init', [$this, 'register_routes']); } public function register_routes() { // 上传视频端点 register_rest_route($this->namespace, '/upload', [ 'methods' => 'POST', 'callback' => [$this, 'handle_video_upload'], 'permission_callback' => [$this, 'check_permission'], 'args' => [ 'file' => [ 'required' => true, 'validate_callback' => function($file) { return !empty($file); } ], 'title' => [ 'required' => false, 'sanitize_callback' => 'sanitize_text_field' ] ] ]); // 保存项目端点 register_rest_route($this->namespace, '/project/save', [ 'methods' => 'POST', 'callback' => [$this, 'save_project'], 'permission_callback' => [$this, 'check_permission'] ]); // 获取项目列表 register_rest_route($this->namespace, '/projects', [ 'methods' => 'GET', 'callback' => [$this, 'get_projects'], 'permission_callback' => [$this, 'check_permission'] ]); // 服务器端视频处理(用于复杂操作) register_rest_route($this->namespace, '/process', [ 'methods' => 'POST', 'callback' => [$this, 'process_video'], 'permission_callback' => [$this, 'check_permission'] ]); } public function handle_video_upload($request) { $files = $request->get_file_params(); if (empty($files['video_file'])) { return new WP_Error('no_file', '没有上传文件', ['status' => 400]); } $file = $files['video_file']; // 检查文件类型 $allowed_types = ['video/mp4', 'video/webm', 'video/ogg', 'video/quicktime']; if (!in_array($file['type'], $allowed_types)) { return new WP_Error('invalid_type', '不支持的文件格式', ['status' => 400]); } // 检查文件大小(限制为500MB) $max_size = 500 * 1024 * 1024; if ($file['size'] > $max_size) { return new WP_Error('file_too_large', '文件太大,最大支持500MB', ['status' => 400]); } // 创建上传目录 $upload_dir = wp_upload_dir(); $video_dir = $upload_dir['basedir'] . '/video-editor'; if (!file_exists($video_dir)) { wp_mkdir_p($video_dir); } // 生成唯一文件名 $filename = wp_unique_filename($video_dir, $file['name']); $filepath = $video_dir . '/' . $filename; // 移动文件 if (move_uploaded_file($file['tmp_name'], $filepath)) { // 保存到媒体库 $attachment = [ 'post_mime_type' => $file['type'], 'post_title' => $request->get_param('title') ?: preg_replace('/.[^.]+$/', '', $file['name']), 'post_content' => '', 'post_status' => 'inherit', 'guid' => $upload_dir['baseurl'] . '/video-editor/' . $filename ]; $attach_id = wp_insert_attachment($attachment, $filepath); // 生成视频元数据 require_once(ABSPATH . 'wp-admin/includes/image.php'); $attach_data = wp_generate_attachment_metadata($attach_id, $filepath); wp_update_attachment_metadata($attach_id, $attach_data); // 获取视频信息 $video_info = $this->get_video_info($filepath); return [ 'success' => true, 'id' => $attach_id, 'url' => wp_get_attachment_url($attach_id), 'thumbnail' => $this->generate_thumbnail($attach_id, $filepath), 'duration' => $video_info['duration'] ?? 0, 'dimensions' => $video_info['dimensions'] ?? ['width' => 0, 'height' => 0] ]; } return new WP_Error('upload_failed', '文件上传失败', ['status' => 500]); } private function get_video_info($filepath) { if (!function_exists('exec')) { return ['duration' => 0, 'dimensions' => ['width' => 0, 'height' => 0]]; } // 使用FFmpeg获取视频信息 $cmd = "ffprobe -v error -show_entries format=duration -show_entries stream=width,height -of json " . escapeshellarg($filepath); @exec($cmd, $output, $return_var); if ($return_var === 0 && !empty($output)) { $data = json_decode(implode('', $output), true); return [ 'duration' => $data['format']['duration'] ?? 0, 'dimensions' => [ 'width' => $data['streams'][0]['width'] ?? 0, 'height' => $data['streams'][0]['height'] ?? 0 ] ]; } return ['duration' => 0, 'dimensions' => ['width' => 0, 'height' => 0]]; } private function generate_thumbnail($attach_id, $filepath) { $upload_dir = wp_upload_dir(); $thumb_dir = $upload_dir['basedir'] . '/video-editor/thumbs'; if (!file_exists($thumb_dir)) { wp_mkdir_p($thumb_dir); } $thumb_name = 'thumb-' . $attach_id . '.jpg'; $thumb_path = $thumb_dir . '/' . $thumb_name; // 使用FFmpeg生成缩略图 $cmd = "ffmpeg -i " . escapeshellarg($filepath) . " -ss 00:00:01 -vframes 1 -q:v 2 " . escapeshellarg($thumb_path); @exec($cmd, $output, $return_var); if ($return_var === 0 && file_exists($thumb_path)) { return $upload_dir['baseurl'] . '/video-editor/thumbs/' . $thumb_name; } // 如果FFmpeg失败,使用默认缩略图 return plugin_dir_url(__FILE__) . '../assets/images/default-thumb.jpg'; } public function save_project($request) { $user_id = get_current_user_id(); $project_data = $request->get_json_params(); if (!$user_id) { return new WP_Error('unauthorized', '用户未登录', ['status' => 401]); } $project_id = wp_insert_post([ 'post_title' => $project_data['title'] ?: '未命名项目', 'post_content' => wp_json_encode($project_data['content']), 'post_status' => 'draft', 'post_type' => 'video_project', 'post_author' => $user_id, 'meta_input' => [ '_video_project_data' => $project_data['timeline'], '_video_duration' => $project_data['duration'], '_video_thumbnail' => $project_data['thumbnail'] ] ]); if (is_wp_error($project_id)) { return $project_id; } return [ 'success' => true, 'project_id' => $project_id, 'message' => '项目保存成功' ]; } public function get_projects($request) { $user_id = get_current_user_id(); if (!$user_id) { return new WP_Error('unauthorized', '用户未登录', ['status' => 401]); } $args = [ 'post_type' => 'video_project', 'author' => $user_id, 'posts_per_page' => 20, 'post_status' => ['draft', 'publish'] ]; $projects = get_posts($args); $formatted_projects = []; foreach ($projects as $project) { $formatted_projects[] = [ 'id' => $project->ID, 'title' => $project->post_title, 'created' => $project->post_date, 'modified' => $project->post_modified, 'thumbnail' => get_post_meta($project->ID, '_video_thumbnail', true), 'duration' => get_post_meta($project->ID, '_video_duration', true) ]; } return $formatted_projects; } public function process_video($request) { // 服务器端复杂视频处理 $data = $request->get_json_params(); $operation = $data['operation'] ?? ''; switch ($operation) { case 'concat': return $this->concatenate_videos($data['videos']); case 'compress': return $this->compress_video($data['video'], $data['options']); case 'add_watermark': return $this->add_watermark_server($data['video'], $data['watermark']); default: return new WP_Error('invalid_operation', '不支持的操作', ['status' => 400]); } } private function concatenate_videos($video_urls) { // 实现服务器端视频合并 // 注意:这需要服务器安装FFmpeg } public function check_permission($request) { // 检查用户权限 return current_user_can('edit_posts') || apply_filters('video_editor_allow_anonymous', false); } } 3.2 创建自定义文章类型存储视频项目 <?php // includes/class-custom-post-type.php class Video_Project_Post_Type { public function __construct() { add_action('init', [$this, 'register_post_type']); add_action('add_meta_boxes', [$this, 'add_meta_boxes']); add_action('save_post_video_project', [$this, 'save_meta_data']); } public function register_post_type() { $labels = [ '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 = [ 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => ['slug' => 'video-project'], 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => 20, 'menu_icon' => 'dashicons-video-alt3', 'supports' => ['title', 'editor', 'author', 'thumbnail'], 'show_in_rest' => true ]; register_post_type('video_project', $args); } public function add_meta_boxes() { add_meta_box( 'video_project_meta', '项目详情', [$this, 'render_meta_box'], 'video_project', 'normal', 'high' ); add_meta_box( 'video_project_preview', '视频预览', [$this, 'render_preview_box'], 'video_project', 'side', 'high' ); } public function render_meta_box($post) { wp_nonce_field('video_project_meta', 'video_project_nonce'); $project_data = get_post_meta($post->ID, '_video_project_data', true); $duration = get_post_meta($post->ID, '_video_duration', true); $video_url = get_post_meta($post->ID, '_video_final_url', true); ?> <div class="video-project-meta"> <p> <label for="video_duration">视频时长:</label> <input type="text" id="video_duration" name="video_duration" value="<?php echo esc_attr($duration); ?>" readonly> <span>秒</span> </p> <p> <label for="video_url">最终视频URL:</label> <input type="url" id="video_url" name="video_url" value="<?php echo esc_url($video_url); ?>" class="widefat"> </p> <p> <label for="project_status">项目状态:</label> <select id="project_status" name="project_status"> <option value="draft" <?php selected(get_post_status($post->ID), 'draft'); ?>>草稿</option> <option value="publish" <?php selected(get_post_status($post->ID), 'publish'); ?>>发布</option> <option value="processing" <?php selected(get_post_meta($post->ID, '_project_status', true), 'processing'); ?>>处理中</option> <option value="completed" <?php selected(get_post_meta($post->ID, '_project_status', true), 'completed'); ?>>已完成</option> </select> </p> <div class="project-data"> <h4>项目数据(JSON格式)</h4> <textarea name="project_data" rows="10" class="widefat" readonly><?php echo esc_textarea($project_data); ?></textarea> </div> </div> <style> .video-project-meta p { margin: 15px 0; } .video-project-meta label { display: inline-block; width: 120px; font-weight: bold; } .project-data textarea { font-family: monospace; font-size: 12px; } </style> <?php } public function render_preview_box($post) { $thumbnail = get_post_meta($post->ID, '_video_thumbnail', true); $video_url = get_post_meta($post->ID, '_video_final_url', true); ?> <div class="video-preview-sidebar"> <?php if ($thumbnail): ?> <div class="video-thumbnail"> <img src="<?php echo esc_url($thumbnail); ?>" alt="视频缩略图" style="max-width:100%; height:auto;"> </div> <?php endif; ?> <?php if ($video_url): ?> <div class="video-preview"> <video controls style="width:100%; max-height:200px;"> <source src="<?php echo esc_url($video_url); ?>" type="video/mp4"> 您的浏览器不支持视频播放 </video> </div> <p style="text-align:center; margin-top:10px;"> <a href="<?php echo esc_url($video_url); ?>" class="button button-primary" target="_blank"> <span class="dashicons dashicons-external"></span> 查看完整视频 </a> </p> <?php else: ?> <p class="description">视频尚未生成</p> <button type="button" id="generate_video" class="button button-secondary"> <span class="dashicons dashicons-video-alt3"></span> 生成最终视频 </button> <?php endif; ?> </div> <script> jQuery(document).ready(function($) { $('#generate_video').on('click', function() { var button = $(this); var postId = <?php echo $post->ID; ?>; button.prop('disabled', true).text('生成中...'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'generate_final_video', post_id: postId, nonce: '<?php echo wp_create_nonce('generate_video_' . $post->ID); ?>' }, success: function(response) { if (response.success) { alert('视频生成成功!'); location.reload(); } else { alert('生成失败:' + response.data); button.prop('disabled', false).text('生成最终视频'); } }, error: function() { alert('请求失败,请重试'); button.prop('disabled', false).text('生成最终视频'); } }); }); }); </script> <?php } public function save_meta_data($post_id) { // 验证nonce if (!isset($_POST['video_project_nonce']) || !wp_verify_nonce($_POST['video_project_nonce'], 'video_project_meta')) { return; } // 检查自动保存 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } // 检查权限 if (!current_user_can('edit_post', $post_id)) { return; } // 保存元数据

发表评论

WordPress高级教程,开发集成在线问卷调查与自动报告生成器

WordPress高级教程:开发集成在线问卷调查与自动报告生成器 引言:WordPress作为企业级应用开发平台 WordPress早已超越了简单的博客系统范畴,成为全球最受欢迎的内容管理系统(CMS),驱动着超过40%的网站。其强大的插件架构、丰富的API接口和庞大的开发者社区,使得WordPress能够胜任各种复杂的企业级应用开发。本教程将深入探讨如何通过WordPress代码二次开发,实现一个集在线问卷调查与自动报告生成器于一体的高级功能模块。 在当今数据驱动的商业环境中,问卷调查和数据分析工具已成为企业决策的重要支撑。传统上,企业可能需要购买昂贵的专业调查工具或委托定制开发,而通过WordPress二次开发,我们可以以更低的成本、更高的集成度实现这一功能,同时充分利用WordPress的用户管理、权限控制和内容展示能力。 系统架构设计 功能需求分析 在开始开发之前,我们需要明确系统的核心需求: 问卷调查功能: 支持多种题型(单选、多选、文本输入、评分等) 问题逻辑跳转 问卷分页与进度保存 响应式设计,适配移动设备 报告生成功能: 基于问卷结果的自动分析 可视化图表生成 可定制的报告模板 多种格式导出(PDF、Word、HTML) 管理功能: 问卷创建与管理界面 数据收集与统计分析 用户权限与访问控制 报告模板管理 技术架构设计 我们将采用分层架构设计,确保系统的可维护性和扩展性: 数据层:使用WordPress自定义数据表存储问卷、回答和报告数据 业务逻辑层:处理问卷逻辑、数据分析和报告生成 表示层:前端界面和用户交互 集成层:与WordPress核心功能(用户、权限、主题)的集成 数据库设计与实现 自定义数据表设计 虽然WordPress提供了自定义文章类型(CPT)和元数据存储机制,但对于问卷调查这种结构化数据,我们建议创建专门的数据表以提高查询效率。 -- 问卷主表 CREATE TABLE wp_surveys ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, description TEXT, status ENUM('draft', 'published', 'closed') DEFAULT 'draft', created_by INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, settings TEXT, -- JSON格式存储问卷设置 FOREIGN KEY (created_by) REFERENCES wp_users(ID) ); -- 问题表 CREATE TABLE wp_survey_questions ( id INT AUTO_INCREMENT PRIMARY KEY, survey_id INT NOT NULL, question_text TEXT NOT NULL, question_type ENUM('single_choice', 'multiple_choice', 'text', 'rating', 'matrix') NOT NULL, options TEXT, -- JSON格式存储选项 is_required BOOLEAN DEFAULT FALSE, sort_order INT DEFAULT 0, logic_rules TEXT, -- JSON格式存储逻辑跳转规则 FOREIGN KEY (survey_id) REFERENCES wp_surveys(id) ON DELETE CASCADE ); -- 回答表 CREATE TABLE wp_survey_responses ( id INT AUTO_INCREMENT PRIMARY KEY, survey_id INT NOT NULL, user_id INT, session_id VARCHAR(100), started_at DATETIME, completed_at DATETIME, ip_address VARCHAR(45), user_agent TEXT, FOREIGN KEY (survey_id) REFERENCES wp_surveys(id), FOREIGN KEY (user_id) REFERENCES wp_users(ID) ); -- 答案详情表 CREATE TABLE wp_survey_answers ( id INT AUTO_INCREMENT PRIMARY KEY, response_id INT NOT NULL, question_id INT NOT NULL, answer_value TEXT, FOREIGN KEY (response_id) REFERENCES wp_survey_responses(id) ON DELETE CASCADE, FOREIGN KEY (question_id) REFERENCES wp_survey_questions(id) ); -- 报告表 CREATE TABLE wp_survey_reports ( id INT AUTO_INCREMENT PRIMARY KEY, survey_id INT NOT NULL, title VARCHAR(255) NOT NULL, report_type ENUM('summary', 'individual', 'comparative') NOT NULL, template_id INT, generated_at DATETIME DEFAULT CURRENT_TIMESTAMP, content LONGTEXT, -- 报告内容,可以是HTML或序列化数据 file_path VARCHAR(500), -- 导出文件路径 FOREIGN KEY (survey_id) REFERENCES wp_surveys(id) ); 数据库操作类封装 为了与WordPress数据库操作保持一致,我们创建一个专门的数据库操作类: <?php /** * 问卷调查数据库操作类 */ class Survey_DB { private $wpdb; private $charset_collate; public function __construct() { global $wpdb; $this->wpdb = $wpdb; $this->charset_collate = $wpdb->get_charset_collate(); } /** * 创建数据表 */ public function create_tables() { require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); // 问卷主表 $sql = "CREATE TABLE {$this->wpdb->prefix}surveys ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, description TEXT, status ENUM('draft', 'published', 'closed') DEFAULT 'draft', created_by INT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, settings TEXT, FOREIGN KEY (created_by) REFERENCES {$this->wpdb->prefix}users(ID) ) {$this->charset_collate};"; dbDelta($sql); // 其他表的创建代码类似... } /** * 获取问卷列表 */ public function get_surveys($args = array()) { $defaults = array( 'status' => 'published', 'per_page' => 10, 'page' => 1, 'orderby' => 'created_at', 'order' => 'DESC' ); $args = wp_parse_args($args, $defaults); $offset = ($args['page'] - 1) * $args['per_page']; $sql = $this->wpdb->prepare( "SELECT * FROM {$this->wpdb->prefix}surveys WHERE status = %s ORDER BY {$args['orderby']} {$args['order']} LIMIT %d OFFSET %d", $args['status'], $args['per_page'], $offset ); return $this->wpdb->get_results($sql); } /** * 保存问卷回答 */ public function save_response($survey_id, $user_id, $answers) { // 开启事务 $this->wpdb->query('START TRANSACTION'); try { // 保存回答主记录 $response_data = array( 'survey_id' => $survey_id, 'user_id' => $user_id, 'completed_at' => current_time('mysql'), 'ip_address' => $this->get_client_ip(), 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ); $this->wpdb->insert( "{$this->wpdb->prefix}survey_responses", $response_data ); $response_id = $this->wpdb->insert_id; // 保存每个问题的答案 foreach ($answers as $question_id => $answer) { $answer_data = array( 'response_id' => $response_id, 'question_id' => $question_id, 'answer_value' => is_array($answer) ? json_encode($answer) : $answer ); $this->wpdb->insert( "{$this->wpdb->prefix}survey_answers", $answer_data ); } // 提交事务 $this->wpdb->query('COMMIT'); return $response_id; } catch (Exception $e) { // 回滚事务 $this->wpdb->query('ROLLBACK'); return false; } } /** * 获取客户端IP */ private function get_client_ip() { $ipaddress = ''; if (isset($_SERVER['HTTP_CLIENT_IP'])) $ipaddress = $_SERVER['HTTP_CLIENT_IP']; else if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR']; else if(isset($_SERVER['HTTP_X_FORWARDED'])) $ipaddress = $_SERVER['HTTP_X_FORWARDED']; else if(isset($_SERVER['HTTP_FORWARDED_FOR'])) $ipaddress = $_SERVER['HTTP_FORWARDED_FOR']; else if(isset($_SERVER['HTTP_FORWARDED'])) $ipaddress = $_SERVER['HTTP_FORWARDED']; else if(isset($_SERVER['REMOTE_ADDR'])) $ipaddress = $_SERVER['REMOTE_ADDR']; else $ipaddress = 'UNKNOWN'; return $ipaddress; } } ?> 问卷创建与管理模块 后台管理界面开发 我们将创建一个完整的后台管理界面,用于问卷的创建、编辑和管理: <?php /** * 问卷调查管理类 */ class Survey_Admin { private $db; public function __construct() { $this->db = new Survey_DB(); // 添加管理菜单 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_options', 'survey-manager', array($this, 'render_main_page'), 'dashicons-feedback', 30 ); // 子菜单 add_submenu_page( 'survey-manager', '所有问卷', '所有问卷', 'manage_options', 'survey-manager', array($this, 'render_main_page') ); add_submenu_page( 'survey-manager', '新建问卷', '新建问卷', 'manage_options', 'survey-new', array($this, 'render_new_survey_page') ); add_submenu_page( 'survey-manager', '报告生成', '报告生成', 'manage_options', 'survey-reports', array($this, 'render_reports_page') ); add_submenu_page( 'survey-manager', '设置', '设置', 'manage_options', 'survey-settings', array($this, 'render_settings_page') ); } /** * 渲染主页面 */ public function render_main_page() { // 获取问卷列表 $surveys = $this->db->get_surveys(); // 加载模板 include SURVEY_PLUGIN_DIR . 'templates/admin/survey-list.php'; } /** * 渲染新建问卷页面 */ public function render_new_survey_page() { // 处理表单提交 if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_survey'])) { $this->save_survey($_POST); } // 加载模板 include SURVEY_PLUGIN_DIR . 'templates/admin/survey-editor.php'; } /** * 保存问卷 */ private function save_survey($data) { // 验证和清理数据 $survey_data = array( 'title' => sanitize_text_field($data['title']), 'description' => wp_kses_post($data['description']), 'status' => sanitize_text_field($data['status']), 'created_by' => get_current_user_id(), 'settings' => json_encode(array( 'require_login' => isset($data['require_login']) ? 1 : 0, 'limit_responses' => isset($data['limit_responses']) ? intval($data['limit_responses']) : 0, 'show_progress' => isset($data['show_progress']) ? 1 : 0, 'randomize_questions' => isset($data['randomize_questions']) ? 1 : 0 )) ); // 保存到数据库 global $wpdb; if (isset($data['survey_id']) && $data['survey_id']) { // 更新现有问卷 $wpdb->update( "{$wpdb->prefix}surveys", $survey_data, array('id' => intval($data['survey_id'])) ); $survey_id = $data['survey_id']; } else { // 创建新问卷 $wpdb->insert( "{$wpdb->prefix}surveys", $survey_data ); $survey_id = $wpdb->insert_id; } // 保存问题 if (isset($data['questions']) && is_array($data['questions'])) { $this->save_questions($survey_id, $data['questions']); } // 重定向到编辑页面 wp_redirect(admin_url('admin.php?page=survey-new&survey_id=' . $survey_id . '&saved=1')); exit; } /** * 注册管理页面脚本 */ public function enqueue_admin_scripts($hook) { // 只在我们的管理页面加载脚本 if (strpos($hook, 'survey-') === false) { return; } // 加载Vue.js用于动态界面 wp_enqueue_script('vue', 'https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js', array(), '2.6.14'); // 加载Element UI组件库 wp_enqueue_style('element-ui', 'https://unpkg.com/element-ui/lib/theme-chalk/index.css'); wp_enqueue_script('element-ui', 'https://unpkg.com/element-ui/lib/index.js', array('vue'), '2.15.6'); // 加载自定义脚本 wp_enqueue_script( 'survey-admin', SURVEY_PLUGIN_URL . 'assets/js/admin.js', array('vue', 'element-ui', 'jquery'), '1.0.0', true ); // 加载自定义样式 wp_enqueue_style( 'survey-admin', SURVEY_PLUGIN_URL . 'assets/css/admin.css', array('element-ui'), '1.0.0' ); // 传递数据到JavaScript wp_localize_script('survey-admin', 'survey_admin_data', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('survey_admin_nonce') )); } } ?> 问卷编辑器前端实现 使用Vue.js和Element UI创建动态问卷编辑器: // assets/js/admin.js (function($) { 'use strict'; // 创建Vue应用 new Vue({ el: '#survey-editor', data: { survey: { title: '', description: '', status: 'draft', settings: { require_login: false, limit_responses: 0, show_progress: true, randomize_questions: false } }, questions: [], questionTypes: [ { value: 'single_choice', label: '单选题' }, { value: 'multiple_choice', label: '多选题' }, { value: 'text', label: '文本题' }, { value: 'rating', label: '评分题' }, { value: 'matrix', label: '矩阵题' } ], activeQuestionIndex: 0 }, methods: { // 添加新问题 addQuestion: function(type) { const newQuestion = { id: Date.now(), // 临时ID question_text: '', question_type: type, options: type === 'rating' ? [{value: 1, label: '1'}, {value: 2, label: '2'}, {value: 3, label: '3'}, {value: 4, label: '4'}, {value: 5, label: '5'}] : type === 'single_choice' || type === 'multiple_choice' ? [{value: 'option1', label: '选项1'}, {value: 'option2', label: '选项2'}] : [], is_required: false, sort_order: this.questions.length, logic_rules: [] }; this.questions.push(newQuestion); this.activeQuestionIndex = this.questions.length - 1; }, // 删除问题 removeQuestion: function(index) { this.questions.splice(index, 1); if (this.activeQuestionIndex >= index && this.activeQuestionIndex > 0) { this.activeQuestionIndex--; } }, // 上移问题 moveQuestionUp: function(index) { if (index > 0) { const temp = this.questions[index]; this.$set(this.questions, index, this.questions[index - 1]); this.$set(this.questions, index - 1, temp); this.activeQuestionIndex = index - 1; } }, // 下移问题 moveQuestionDown: function(index) { if (index < this.questions.length - 1) { const temp = this.questions[index]; this.$set(this.questions, index, this.questions[index + 1]); this.activeQuestionIndex = index + 1; } }, // 添加选项 addOption: function(questionIndex) { if (!this.questions[questionIndex].options) { this.$set(this.questions[questionIndex], 'options', []); } const optionCount = this.questions[questionIndex].options.length + 1; this.questions[questionIndex].options.push({ value: 'option' + optionCount, label: '选项' + optionCount }); }, // 删除选项 removeOption: function(questionIndex, optionIndex) { this.questions[questionIndex].options.splice(optionIndex, 1); }, // 保存问卷 saveSurvey: function() { const formData = new FormData(); formData.append('action', 'save_survey'); formData.append('nonce', survey_admin_data.nonce); formData.append('survey', JSON.stringify(this.survey)); formData.append('questions', JSON.stringify(this.questions)); $.ajax({ url: survey_admin_data.ajax_url, type: 'POST', data: formData, processData: false, contentType: false, success: function(response) { if (response.success) { this.$message.success('问卷保存成功'); if (response.data.redirect) { window.location.href = response.data.redirect; } } else { this.$message.error('保存失败:' + response.data.message); } }.bind(this), error: function() { this.$message.error('网络错误,请重试'); }.bind(this) }); }, // 预览问卷 previewSurvey: function() { // 在新窗口打开预览 const previewUrl = survey_admin_data.site_url + '?survey_preview=' + (this.survey.id || 'new'); window.open(previewUrl, '_blank'); } }, mounted: function() { // 如果有现有问卷数据,加载它 if (window.surveyData) { this.survey = window.surveyData.survey; this.questions = window.surveyData.questions; } } }); })(jQuery); 前端问卷展示与交互 短代码系统实现 为了让用户能够轻松地在文章或页面中插入问卷,我们实现一个短代码系统: <?php /** * 问卷调查短代码类 */ class Survey_Shortcode { private $db; public function __construct() { $this->db = new Survey_DB(); // 注册短代码 add_shortcode('survey', array($this, 'render_survey')); // 注册AJAX处理 add_action('wp_ajax_submit_survey', array($this, 'handle_survey_submission')); add_action('wp_ajax_nopriv_submit_survey', array($this, 'handle_survey_submission')); } /** * 渲染问卷短代码 */ public function render_survey($atts) { $atts = shortcode_atts(array( 'id' => 0, 'title' => '', 'width' => '100%', 'height' => 'auto' ), $atts, 'survey'); $survey_id = intval($atts['id']); if (!$survey_id) { return '<div class="survey-error">请指定问卷ID</div>'; } // 获取问卷数据 global $wpdb; $survey = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}surveys WHERE id = %d AND status = 'published'", $survey_id )); if (!$survey) { return '<div class="survey-error">问卷不存在或未发布</div>'; } // 检查访问权限 $settings = json_decode($survey->settings, true); if ($settings['require_login'] && !is_user_logged_in()) { return '<div class="survey-login-required">请先登录后再填写问卷</div>'; } // 检查回答限制 if ($settings['limit_responses'] > 0) { $response_count = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}survey_responses WHERE survey_id = %d", $survey_id )); if ($response_count >= $settings['limit_responses']) { return '<div class="survey-closed">本问卷已收集足够数据,感谢参与</div>'; } } // 获取问题列表 $questions = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}survey_questions WHERE survey_id = %d ORDER BY sort_order ASC", $survey_id )); // 解析问题选项 foreach ($questions as &$question) { if ($question->options) { $question->options = json_decode($question->options, true); } if ($question->logic_rules) { $question->logic_rules = json_decode($question->logic_rules, true); } } // 生成唯一会话ID $session_id = $this->generate_session_id(); // 输出问卷HTML ob_start(); include SURVEY_PLUGIN_DIR . 'templates/frontend/survey-form.php'; return ob_get_clean(); } /** * 处理问卷提交 */ public function handle_survey_submission() { // 验证nonce if (!check_ajax_referer('survey_submission', 'nonce', false)) { wp_die(json_encode(array( 'success' => false, 'message' => '安全验证失败' ))); } $survey_id = intval($_POST['survey_id']); $answers = $_POST['answers']; $session_id = sanitize_text_field($_POST['session_id']); // 验证问卷是否存在且开放 global $wpdb; $survey = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}surveys WHERE id = %d AND status = 'published'", $survey_id )); if (!$survey) { wp_die(json_encode(array( 'success' => false, 'message' => '问卷不存在' ))); } // 检查是否已经提交过 $settings = json_decode($survey->settings, true); if ($settings['prevent_duplicate']) { $user_id = get_current_user_id(); $check_field = $user_id ? 'user_id' : 'session_id'; $check_value = $user_id ? $user_id : $session_id; $existing = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}survey_responses WHERE survey_id = %d AND {$check_field} = %s", $survey_id, $check_value )); if ($existing > 0) { wp_die(json_encode(array( 'success' => false, 'message' => '您已经提交过本问卷' ))); } } // 保存回答 $user_id = get_current_user_id(); $response_id = $this->db->save_response($survey_id, $user_id, $answers); if ($response_id) { // 触发完成动作 do_action('survey_completed', $survey_id, $response_id, $user_id); wp_die(json_encode(array( 'success' => true, 'message' => '提交成功', 'response_id' => $response_id ))); } else { wp_die(json_encode(array( 'success' => false, 'message' => '提交失败,请重试' ))); } } /** * 生成会话ID */ private function generate_session_id() { if (isset($_COOKIE['survey_session_id'])) { return $_COOKIE['survey_session_id']; } $session_id = wp_generate_uuid4(); setcookie('survey_session_id', $session_id, time() + 3600 * 24 * 30, '/'); return $session_id; } } ?> 前端问卷样式与交互 /* assets/css/frontend.css */ .survey-container { max-width: 800px; margin: 0 auto; padding: 30px; background: #fff; border-radius: 10px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; } .survey-header { margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #f0f0f0; } .survey-title { font-size: 28px; font-weight: 600; color: #333; margin-bottom: 10px; } .survey-description { font-size: 16px; color: #666; line-height: 1.6; } .survey-progress { margin-bottom: 30px; } .progress-bar { height: 8px; background: #f0f0f0; border-radius: 4px; overflow: hidden; } .progress-fill { height: 100%; background: linear-gradient(90deg, #4CAF50, #8BC34A); transition: width 0.3s ease; } .progress-text { text-align: right; font-size: 14px; color: #666; margin-top: 5px; } .survey-questions { margin-bottom: 40px; } .question-item { margin-bottom: 30px; padding: 25px; background: #f9f9f9; border-radius: 8px; border-left: 4px solid #4CAF50; transition: all 0.3s ease; } .question-item.required { border-left-color: #F44336; } .question-item.required .question-text::after { content: " *"; color: #F44336; } .question-text { font-size: 18px; font-weight: 500; color: #333; margin-bottom: 20px; line-height: 1.5; } .question-hint { font-size: 14px; color: #888; margin-top: 5px; font-style: italic; } .options-container { display: flex; flex-direction: column; gap: 12px; } .option-item { display: flex; align-items: center; padding: 12px 15px; background: #fff; border: 2px solid #e0e0e0; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; } .option-item:hover { border-color: #4CAF50; background: #f8fff8; } .option-item.selected { border-color: #4CAF50; background: #f0f9f0; } .option-radio, .option-checkbox { margin-right: 12px; width: 20px; height: 20px; } .option-label { font-size: 16px; color: #333; flex: 1; } .text-input { width: 100%; padding: 12px 15px; font-size: 16px; border: 2px solid #e0e0e0; border-radius: 6px; transition: border-color 0.2s ease; } .text-input:focus { outline: none; border-color: #4CAF50; } .rating-container { display: flex; gap: 10px; justify-content: center; } .rating-star { font-size: 32px; color: #ddd; cursor: pointer; transition: color 0.2s ease; } .rating-star.active { color: #FFC107; } .rating-star:hover { color: #FFC107; } .matrix-table { width: 100%; border-collapse: collapse; margin-top: 10px; } .matrix-table th, .matrix-table td { padding: 12px; text-align: center; border: 1px solid #e0e0e0; } .matrix-table th { background: #f5f5f5; font-weight: 500; } .matrix-option { display: inline-block; margin: 0 5px; } .survey-navigation { display: flex; justify-content: space-between; align-items: center; padding-top: 30px; border-top: 2px solid #f0f0f0; } .nav-button { padding: 12px 30px; font-size: 16px; font-weight: 500; border: none; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; } .nav-button.prev { background: #f5f5f5; color: #666; } .nav-button.prev:hover { background: #e0e0e0; } .nav-button.next { background: #4CAF50; color: white; } .nav-button.next:hover { background: #45a049; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3); } .nav-button.submit { background: #2196F3; color: white; } .nav-button.submit:hover { background: #1976D2; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3); } .survey-footer { margin-top: 30px; text-align: center; font-size: 14px; color: #888; } /* 响应式设计 */ @media (max-width: 768px) { .survey-container { padding: 20px; margin: 10px; } .survey-title { font-size: 24px; } .question-item { padding: 20px; } .question-text { font-size: 16px; } .nav-button { padding: 10px 20px; font-size: 14px; } .matrix-table { font-size: 14px; } .matrix-table th, .matrix-table td { padding: 8px; } } /* 动画效果 */ @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .question-item { animation: fadeIn 0.5s ease forwards; } .question-item:nth-child(odd) { animation-delay: 0.1s; } .question-item:nth-child(even) { animation-delay: 0.2s; } 数据统计与分析模块 数据可视化组件 <?php /** * 数据统计与分析类 */ class Survey_Analytics { private $db; public function __construct() { $this->db = new Survey_DB(); // 注册AJAX端点 add_action('wp_ajax_get_survey_stats', array($this, 'get_survey_statistics')); } /** * 获取问卷统计数据 */ public function get_survey_statistics($survey_id) { global $wpdb; $stats = array( 'summary' => array(), 'questions' => array(), 'demographics' => array(), 'timeline' => array() ); // 基础统计 $stats['summary'] = array( 'total_responses' => $this->get_total_responses($survey_id), 'completion_rate' => $this->get_completion_rate($survey_id), 'average_time' => $this->get_average_completion_time($survey_id), 'daily_average' => $this->get_daily_average($survey_id) ); // 问题分析 $questions = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}survey_questions WHERE survey_id = %d ORDER BY sort_order", $survey_id )); foreach ($questions as $question) { $question_stats = $this->analyze_question($question->id, $question->question_type); $stats['questions'][] = array( 'id' => $question->id, 'text' => $question->question_text, 'type' => $question->question_type, 'stats' => $question_stats ); } // 用户画像(如果收集了用户信息) $stats['demographics'] = $this->analyze_demographics($survey_id); // 时间线数据 $stats['timeline'] = $this->get_response_timeline($survey_id); return $stats; } /** * 分析单个问题 */ private function analyze_question($question_id, $question_type) { global $wpdb; $stats = array(); switch ($question_type) { case 'single_choice': case 'multiple_choice': // 获取选项统计 $answers = $wpdb->get_results($wpdb->prepare( "SELECT answer_value, COUNT(*) as count

发表评论

一步步教你,在WordPress中添加网站暗黑模式切换与个性化主题工具

一步步教你,在WordPress中添加网站暗黑模式切换与个性化主题工具 引言:为什么网站需要暗黑模式与个性化主题工具? 在当今互联网时代,用户体验已成为网站成功的关键因素之一。随着用户对个性化体验需求的增加,以及健康用眼意识的提升,暗黑模式已成为现代网站的标配功能。根据2023年的一项调查显示,超过82%的用户表示更喜欢使用支持暗黑模式的网站,尤其是在夜间浏览时。 WordPress作为全球最流行的内容管理系统,拥有超过40%的市场份额,但其默认主题往往缺乏现代化的暗黑模式切换功能。通过代码二次开发,我们不仅可以为WordPress网站添加暗黑模式,还能创建一套完整的个性化主题工具,让用户根据自己的偏好调整网站外观。 本文将详细介绍如何通过WordPress代码二次开发,实现暗黑模式切换与个性化主题工具,让你的网站在众多WordPress站点中脱颖而出。 第一部分:准备工作与环境配置 1.1 创建子主题 在进行任何WordPress代码修改之前,最佳实践是创建一个子主题。这样可以确保你的修改不会在主题更新时丢失。 /* Theme Name: 我的个性化主题 Template: twentytwentythree Version: 1.0.0 Description: 添加暗黑模式与个性化工具的WordPress子主题 */ 在WordPress的wp-content/themes目录下创建新文件夹"my-custom-theme",并创建style.css文件,添加上述代码。然后创建functions.php文件,用于添加功能代码。 1.2 设置开发环境 确保你的开发环境满足以下要求: WordPress 5.0或更高版本 PHP 7.4或更高版本 支持JavaScript的现代浏览器 代码编辑器(如VS Code、Sublime Text等) 1.3 备份原始文件 在进行任何代码修改前,请务必备份以下文件: 当前主题的functions.php 当前主题的style.css 当前主题的JavaScript文件 第二部分:实现暗黑模式切换功能 2.1 理解暗黑模式的实现原理 暗黑模式的核心是通过CSS变量或类名切换来改变网站的颜色方案。我们将采用以下两种方法结合的方式: CSS变量方法:定义两套颜色变量(亮色和暗色) 类名切换方法:通过JavaScript在body标签上添加或移除"dark-mode"类 2.2 创建CSS颜色变量系统 在子主题的style.css文件中,添加CSS变量定义: :root { /* 亮色主题变量 */ --primary-color: #3498db; --secondary-color: #2ecc71; --background-color: #ffffff; --text-color: #333333; --header-bg: #f8f9fa; --border-color: #e0e0e0; --card-bg: #ffffff; --shadow-color: rgba(0, 0, 0, 0.1); } .dark-mode { /* 暗色主题变量 */ --primary-color: #5dade2; --secondary-color: #58d68d; --background-color: #121212; --text-color: #e0e0e0; --header-bg: #1e1e1e; --border-color: #333333; --card-bg: #1e1e1e; --shadow-color: rgba(0, 0, 0, 0.3); } /* 应用CSS变量到网站元素 */ body { background-color: var(--background-color); color: var(--text-color); transition: background-color 0.3s ease, color 0.3s ease; } header { background-color: var(--header-bg); border-bottom: 1px solid var(--border-color); } .card { background-color: var(--card-bg); box-shadow: 0 2px 10px var(--shadow-color); border-radius: 8px; padding: 20px; margin-bottom: 20px; } 2.3 创建暗黑模式切换开关 在主题的合适位置(通常是页眉或页脚)添加暗黑模式切换按钮。首先,在functions.php中添加切换按钮的HTML代码: function add_dark_mode_switch() { echo '<button id="dark-mode-toggle" class="dark-mode-toggle" aria-label="切换暗黑模式"> <span class="toggle-icon light-icon">☀️</span> <span class="toggle-icon dark-icon">🌙</span> </button>'; } add_action('wp_body_open', 'add_dark_mode_switch'); 2.4 添加切换功能的JavaScript 创建新的JavaScript文件(如dark-mode.js)并添加到主题中: // dark-mode.js document.addEventListener('DOMContentLoaded', function() { const darkModeToggle = document.getElementById('dark-mode-toggle'); const body = document.body; // 检查用户之前的偏好设置 const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); const savedTheme = localStorage.getItem('theme'); // 初始化主题 function initTheme() { if (savedTheme === 'dark' || (!savedTheme && prefersDarkScheme.matches)) { body.classList.add('dark-mode'); updateToggleButton(true); } else { body.classList.remove('dark-mode'); updateToggleButton(false); } } // 更新切换按钮状态 function updateToggleButton(isDarkMode) { if (isDarkMode) { darkModeToggle.setAttribute('aria-label', '切换到亮色模式'); darkModeToggle.classList.add('active'); } else { darkModeToggle.setAttribute('aria-label', '切换到暗黑模式'); darkModeToggle.classList.remove('active'); } } // 切换主题 function toggleTheme() { if (body.classList.contains('dark-mode')) { body.classList.remove('dark-mode'); localStorage.setItem('theme', 'light'); updateToggleButton(false); } else { body.classList.add('dark-mode'); localStorage.setItem('theme', 'dark'); updateToggleButton(true); } } // 监听系统主题变化 prefersDarkScheme.addEventListener('change', function(e) { if (!localStorage.getItem('theme')) { if (e.matches) { body.classList.add('dark-mode'); updateToggleButton(true); } else { body.classList.remove('dark-mode'); updateToggleButton(false); } } }); // 绑定点击事件 darkModeToggle.addEventListener('click', toggleTheme); // 初始化 initTheme(); }); 在functions.php中注册并加载这个JavaScript文件: function enqueue_dark_mode_scripts() { wp_enqueue_script( 'dark-mode-script', get_stylesheet_directory_uri() . '/js/dark-mode.js', array(), '1.0.0', true ); } add_action('wp_enqueue_scripts', 'enqueue_dark_mode_scripts'); 2.5 添加切换按钮样式 为暗黑模式切换按钮添加CSS样式: .dark-mode-toggle { position: fixed; bottom: 30px; right: 30px; width: 60px; height: 60px; border-radius: 50%; background-color: var(--primary-color); border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 12px var(--shadow-color); z-index: 9999; transition: all 0.3s ease; } .dark-mode-toggle:hover { transform: scale(1.1); box-shadow: 0 6px 16px var(--shadow-color); } .toggle-icon { font-size: 24px; position: absolute; transition: opacity 0.3s ease, transform 0.3s ease; } .dark-icon { opacity: 0; transform: rotate(90deg); } .light-icon { opacity: 1; transform: rotate(0); } .dark-mode-toggle.active .dark-icon { opacity: 1; transform: rotate(0); } .dark-mode-toggle.active .light-icon { opacity: 0; transform: rotate(-90deg); } 第三部分:创建个性化主题工具面板 3.1 设计个性化工具面板界面 我们将创建一个滑出式面板,用户可以通过它调整网站的各种视觉设置。首先,在functions.php中添加面板HTML结构: function add_theme_customizer_panel() { ?> <div id="theme-customizer" class="theme-customizer"> <button id="customizer-toggle" class="customizer-toggle" aria-label="打开主题定制面板"> <span class="customizer-icon">⚙️</span> </button> <div class="customizer-panel"> <div class="customizer-header"> <h3>主题定制</h3> <button id="close-customizer" class="close-customizer" aria-label="关闭面板">×</button> </div> <div class="customizer-body"> <div class="customizer-section"> <h4>颜色主题</h4> <div class="color-option"> <label for="primary-color">主色调</label> <input type="color" id="primary-color" value="#3498db"> </div> <div class="color-option"> <label for="secondary-color">辅助色</label> <input type="color" id="secondary-color" value="#2ecc71"> </div> <div class="color-option"> <label for="background-color">背景色</label> <input type="color" id="background-color" value="#ffffff"> </div> </div> <div class="customizer-section"> <h4>字体设置</h4> <div class="font-option"> <label for="font-family">字体家族</label> <select id="font-family"> <option value="'Helvetica Neue', Arial, sans-serif">系统默认</option> <option value="'Roboto', sans-serif">Roboto</option> <option value="'Open Sans', sans-serif">Open Sans</option> <option value="'Montserrat', sans-serif">Montserrat</option> <option value="'Georgia', serif">Georgia</option> </select> </div> <div class="font-option"> <label for="font-size">基础字号</label> <input type="range" id="font-size" min="12" max="20" value="16"> <span id="font-size-value">16px</span> </div> </div> <div class="customizer-section"> <h4>布局设置</h4> <div class="layout-option"> <label for="layout-width">内容宽度</label> <input type="range" id="layout-width" min="800" max="1400" value="1200"> <span id="layout-width-value">1200px</span> </div> <div class="layout-option"> <label> <input type="checkbox" id="rounded-corners" checked> 圆角元素 </label> </div> </div> <div class="customizer-actions"> <button id="reset-customizations" class="button-secondary">重置设置</button> <button id="save-customizations" class="button-primary">保存设置</button> </div> </div> </div> </div> <?php } add_action('wp_footer', 'add_theme_customizer_panel'); 3.2 添加个性化工具面板样式 为个性化工具面板添加CSS样式: .theme-customizer { position: fixed; top: 0; right: 0; z-index: 10000; } .customizer-toggle { position: fixed; top: 50%; right: 0; transform: translateY(-50%); background-color: var(--primary-color); color: white; border: none; border-radius: 5px 0 0 5px; padding: 15px 10px; cursor: pointer; font-size: 20px; transition: all 0.3s ease; box-shadow: -2px 0 10px var(--shadow-color); } .customizer-toggle:hover { padding-right: 15px; } .customizer-panel { position: fixed; top: 0; right: -400px; width: 380px; height: 100vh; background-color: var(--card-bg); box-shadow: -5px 0 15px var(--shadow-color); transition: right 0.3s ease; overflow-y: auto; } .customizer-panel.open { right: 0; } .customizer-header { display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid var(--border-color); } .customizer-header h3 { margin: 0; color: var(--text-color); } .close-customizer { background: none; border: none; font-size: 28px; cursor: pointer; color: var(--text-color); line-height: 1; } .customizer-body { padding: 20px; } .customizer-section { margin-bottom: 30px; padding-bottom: 20px; border-bottom: 1px solid var(--border-color); } .customizer-section h4 { margin-top: 0; margin-bottom: 15px; color: var(--text-color); } .color-option, .font-option, .layout-option { margin-bottom: 15px; display: flex; align-items: center; justify-content: space-between; } .color-option label, .font-option label, .layout-option label { color: var(--text-color); margin-right: 15px; } .color-option input[type="color"] { width: 50px; height: 40px; border: 2px solid var(--border-color); border-radius: 4px; cursor: pointer; } .font-option select, .font-option input[type="range"], .layout-option input[type="range"] { flex-grow: 1; max-width: 200px; } .customizer-actions { display: flex; justify-content: space-between; margin-top: 30px; } .button-primary, .button-secondary { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; transition: all 0.2s ease; } .button-primary { background-color: var(--primary-color); color: white; } .button-secondary { background-color: var(--border-color); color: var(--text-color); } .button-primary:hover, .button-secondary:hover { transform: translateY(-2px); box-shadow: 0 4px 8px var(--shadow-color); } 3.3 实现个性化设置功能 创建customizer.js文件,实现个性化设置功能: // customizer.js document.addEventListener('DOMContentLoaded', function() { const customizerToggle = document.getElementById('customizer-toggle'); const closeCustomizer = document.getElementById('close-customizer'); const customizerPanel = document.querySelector('.customizer-panel'); const saveButton = document.getElementById('save-customizations'); const resetButton = document.getElementById('reset-customizations'); // 获取所有可定制的选项 const primaryColorInput = document.getElementById('primary-color'); const secondaryColorInput = document.getElementById('secondary-color'); const backgroundColorInput = document.getElementById('background-color'); const fontFamilySelect = document.getElementById('font-family'); const fontSizeInput = document.getElementById('font-size'); const fontSizeValue = document.getElementById('font-size-value'); const layoutWidthInput = document.getElementById('layout-width'); const layoutWidthValue = document.getElementById('layout-width-value'); const roundedCornersCheckbox = document.getElementById('rounded-corners'); // 初始化面板状态 function initCustomizer() { // 从localStorage加载保存的设置 const savedSettings = JSON.parse(localStorage.getItem('themeSettings')) || {}; // 设置输入控件的值 if (savedSettings.primaryColor) { primaryColorInput.value = savedSettings.primaryColor; updateCSSVariable('--primary-color', savedSettings.primaryColor); } if (savedSettings.secondaryColor) { secondaryColorInput.value = savedSettings.secondaryColor; updateCSSVariable('--secondary-color', savedSettings.secondaryColor); } if (savedSettings.backgroundColor) { backgroundColorInput.value = savedSettings.backgroundColor; updateCSSVariable('--background-color', savedSettings.backgroundColor); } if (savedSettings.fontFamily) { fontFamilySelect.value = savedSettings.fontFamily; updateCSSVariable('--font-family', savedSettings.fontFamily); } if (savedSettings.fontSize) { fontSizeInput.value = savedSettings.fontSize; fontSizeValue.textContent = savedSettings.fontSize + 'px'; updateCSSVariable('--base-font-size', savedSettings.fontSize + 'px'); } if (savedSettings.layoutWidth) { layoutWidthInput.value = savedSettings.layoutWidth; layoutWidthValue.textContent = savedSettings.layoutWidth + 'px'; updateCSSVariable('--container-width', savedSettings.layoutWidth + 'px'); } if (savedSettings.roundedCorners !== undefined) { roundedCornersCheckbox.checked = savedSettings.roundedCorners; toggleRoundedCorners(savedSettings.roundedCorners); } } // 更新CSS变量 function updateCSSVariable(variable, value) { document.documentElement.style.setProperty(variable, value); } // 切换圆角样式 function toggleRoundedCorners(enabled) { document.body.classList.add('rounded-elements'); } else { document.body.classList.remove('rounded-elements'); } } // 保存设置到localStorage function saveSettings() { const settings = { primaryColor: primaryColorInput.value, secondaryColor: secondaryColorInput.value, backgroundColor: backgroundColorInput.value, fontFamily: fontFamilySelect.value, fontSize: parseInt(fontSizeInput.value), layoutWidth: parseInt(layoutWidthInput.value), roundedCorners: roundedCornersCheckbox.checked }; localStorage.setItem('themeSettings', JSON.stringify(settings)); // 显示保存成功的提示 showNotification('设置已保存!', 'success'); } // 重置所有设置 function resetSettings() { // 清除localStorage中的设置 localStorage.removeItem('themeSettings'); // 重置CSS变量为默认值 const root = document.documentElement; root.style.removeProperty('--primary-color'); root.style.removeProperty('--secondary-color'); root.style.removeProperty('--background-color'); root.style.removeProperty('--font-family'); root.style.removeProperty('--base-font-size'); root.style.removeProperty('--container-width'); // 重置输入控件 primaryColorInput.value = '#3498db'; secondaryColorInput.value = '#2ecc71'; backgroundColorInput.value = '#ffffff'; fontFamilySelect.value = "'Helvetica Neue', Arial, sans-serif"; fontSizeInput.value = 16; fontSizeValue.textContent = '16px'; layoutWidthInput.value = 1200; layoutWidthValue.textContent = '1200px'; roundedCornersCheckbox.checked = true; toggleRoundedCorners(true); // 显示重置成功的提示 showNotification('设置已重置为默认值!', 'info'); } // 显示通知 function showNotification(message, type) { // 移除已存在的通知 const existingNotification = document.querySelector('.customizer-notification'); if (existingNotification) { existingNotification.remove(); } // 创建新通知 const notification = document.createElement('div'); notification.className = `customizer-notification ${type}`; notification.textContent = message; // 添加到页面 document.body.appendChild(notification); // 3秒后自动移除 setTimeout(() => { notification.classList.add('fade-out'); setTimeout(() => notification.remove(), 300); }, 3000); } // 事件监听器 customizerToggle.addEventListener('click', () => { customizerPanel.classList.add('open'); }); closeCustomizer.addEventListener('click', () => { customizerPanel.classList.remove('open'); }); // 实时更新颜色预览 primaryColorInput.addEventListener('input', (e) => { updateCSSVariable('--primary-color', e.target.value); }); secondaryColorInput.addEventListener('input', (e) => { updateCSSVariable('--secondary-color', e.target.value); }); backgroundColorInput.addEventListener('input', (e) => { updateCSSVariable('--background-color', e.target.value); }); // 实时更新字体设置 fontFamilySelect.addEventListener('change', (e) => { updateCSSVariable('--font-family', e.target.value); }); fontSizeInput.addEventListener('input', (e) => { const value = e.target.value; fontSizeValue.textContent = value + 'px'; updateCSSVariable('--base-font-size', value + 'px'); }); // 实时更新布局设置 layoutWidthInput.addEventListener('input', (e) => { const value = e.target.value; layoutWidthValue.textContent = value + 'px'; updateCSSVariable('--container-width', value + 'px'); }); roundedCornersCheckbox.addEventListener('change', (e) => { toggleRoundedCorners(e.target.checked); }); // 保存和重置按钮 saveButton.addEventListener('click', saveSettings); resetButton.addEventListener('click', resetSettings); // 初始化定制器 initCustomizer(); }); 在functions.php中注册并加载这个JavaScript文件: function enqueue_customizer_scripts() { wp_enqueue_script( 'customizer-script', get_stylesheet_directory_uri() . '/js/customizer.js', array(), '1.0.0', true ); } add_action('wp_enqueue_scripts', 'enqueue_customizer_scripts'); 3.4 添加通知样式 为个性化工具面板的通知功能添加CSS样式: .customizer-notification { position: fixed; bottom: 100px; right: 30px; padding: 15px 25px; border-radius: 8px; color: white; font-weight: bold; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 10001; animation: slideIn 0.3s ease; max-width: 300px; } .customizer-notification.success { background-color: #2ecc71; } .customizer-notification.info { background-color: #3498db; } .customizer-notification.error { background-color: #e74c3c; } .customizer-notification.fade-out { animation: fadeOut 0.3s ease forwards; } @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes fadeOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } 第四部分:集成常用互联网小工具 4.1 添加阅读进度条 阅读进度条是许多现代网站的实用功能,可以显示用户在页面上的阅读进度。 在functions.php中添加阅读进度条: function add_reading_progress_bar() { echo '<div id="reading-progress" class="reading-progress"> <div class="reading-progress-bar"></div> </div>'; } add_action('wp_body_open', 'add_reading_progress_bar'); 添加阅读进度条样式: .reading-progress { position: fixed; top: 0; left: 0; width: 100%; height: 4px; background-color: transparent; z-index: 9998; } .reading-progress-bar { height: 100%; background-color: var(--primary-color); width: 0%; transition: width 0.2s ease; } 添加阅读进度条JavaScript功能: // reading-progress.js document.addEventListener('DOMContentLoaded', function() { const progressBar = document.querySelector('.reading-progress-bar'); if (!progressBar) return; function updateReadingProgress() { const windowHeight = window.innerHeight; const documentHeight = document.documentElement.scrollHeight - windowHeight; const scrolled = window.scrollY; const progress = (scrolled / documentHeight) * 100; progressBar.style.width = progress + '%'; } // 监听滚动事件 window.addEventListener('scroll', updateReadingProgress); // 初始化 updateReadingProgress(); }); 4.2 添加回到顶部按钮 回到顶部按钮是长页面中非常实用的功能。 在functions.php中添加回到顶部按钮: function add_back_to_top_button() { echo '<button id="back-to-top" class="back-to-top" aria-label="回到顶部">↑</button>'; } add_action('wp_footer', 'add_back_to_top_button'); 添加回到顶部按钮样式: .back-to-top { position: fixed; bottom: 100px; right: 30px; width: 50px; height: 50px; border-radius: 50%; background-color: var(--primary-color); color: white; border: none; cursor: pointer; display: none; align-items: center; justify-content: center; font-size: 20px; box-shadow: 0 4px 12px var(--shadow-color); z-index: 9997; transition: all 0.3s ease; } .back-to-top.visible { display: flex; } .back-to-top:hover { transform: translateY(-5px); box-shadow: 0 6px 16px var(--shadow-color); } 添加回到顶部按钮JavaScript功能: // back-to-top.js document.addEventListener('DOMContentLoaded', function() { const backToTopButton = document.getElementById('back-to-top'); if (!backToTopButton) return; function toggleBackToTopButton() { if (window.scrollY > 300) { backToTopButton.classList.add('visible'); } else { backToTopButton.classList.remove('visible'); } } function scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); } // 监听滚动事件 window.addEventListener('scroll', toggleBackToTopButton); // 点击事件 backToTopButton.addEventListener('click', scrollToTop); // 初始化 toggleBackToTopButton(); }); 4.3 添加代码高亮功能 对于技术博客或教程网站,代码高亮是必不可少的功能。 首先,在functions.php中集成Prism.js代码高亮库: function enqueue_prism_assets() { // Prism.js核心CSS wp_enqueue_style( 'prism-css', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css', array(), '1.29.0' ); // Prism.js核心JS wp_enqueue_script( 'prism-js', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js', array(), '1.29.0', true ); // 添加常用语言支持 wp_enqueue_script( 'prism-js-php', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-php.min.js', array('prism-js'), '1.29.0', true ); wp_enqueue_script( 'prism-js-javascript', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js', array('prism-js'), '1.29.0', true ); wp_enqueue_script( 'prism-js-css', 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-css.min.js', array('prism-js'), '1.29.0', true ); } add_action('wp_enqueue_scripts', 'enqueue_prism_assets'); 4.4 添加复制代码按钮 为代码块添加复制功能,提升用户体验。 添加复制代码按钮的JavaScript功能: // copy-code.js document.addEventListener('DOMContentLoaded', function() { // 为所有代码块添加复制按钮 document.querySelectorAll('pre code').forEach(function(codeBlock) { // 创建复制按钮 const copyButton = document.createElement('button'); copyButton.className = 'copy-code-button'; copyButton.textContent = '复制'; copyButton.setAttribute('aria-label', '复制代码'); // 将按钮添加到代码块容器 const pre = codeBlock.parentNode; if (pre.tagName === 'PRE') { pre.style.position = 'relative'; pre.appendChild(copyButton); } // 复制功能 copyButton.addEventListener('click', function() { const textToCopy = codeBlock.textContent; navigator.clipboard.writeText(textToCopy).then(function() { // 复制成功反馈 copyButton.textContent = '已复制!'; copyButton.classList.add('copied'); setTimeout(function() { copyButton.textContent = '复制'; copyButton.classList.remove('copied'); }, 2000); }).catch(function(err) { console.error('复制失败: ', err); copyButton.textContent = '复制失败'; }); }); }); }); 添加复制按钮样式: .copy-code-button { position: absolute; top: 10px; right: 10px; padding: 5px 10px; background-color: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; opacity: 0; transition: opacity 0.3s ease; } pre:hover .copy-code-button { opacity: 1; } .copy-code-button.copied { background-color: #2ecc71; } 第五部分:优化与高级功能 5.1 添加主题设置导入/导出功能 允许用户导出自己的主题设置,并在其他设备上导入。 在个性化工具面板中添加导入/导出功能: // 在customizer-body部分添加以下代码 <div class="customizer-section"> <h4>导入/导出设置</h4> <div class="import-export-option"> <button id="export-settings" class="button-secondary">导出设置</button> <button id="import-settings" class="button-secondary">导入设置</button> <input type="file" id="import-file" accept=".json" style="display: none;"> </div> <div id="export-result" class="export-result" style="display: none;"> <textarea id="export-data" readonly rows="4"></textarea> <button id="copy-export" class="button-secondary">复制JSON</button> </div> </div> 添加导入/导出功能的JavaScript: // import-export.js document.addEventListener('DOMContentLoaded', function() { const exportButton = document.getElementById('export-settings'); const importButton = document.getElementById('import-settings'); const importFileInput = document.getElementById('import-file'); const exportResult = document.getElementById('export-result'); const exportData = document.getElementById('export-data'); const copyExportButton = document.getElementById('copy-export'); // 导出设置 exportButton.addEventListener('click', function() { const settings = JSON.parse(localStorage.getItem('themeSettings')) || {}; const settingsJSON = JSON.stringify(settings, null, 2); exportData.value = settingsJSON; exportResult.style.display = 'block'; // 自动滚动到导出结果 exportResult.scrollIntoView({ behavior: 'smooth' }); }); // 复制导出数据 copyExportButton.addEventListener('click', function() { exportData.select(); document.execCommand('copy'); // 显示复制成功提示 showNotification('设置已复制到剪贴板!', 'success'); }); // 导入设置 importButton.addEventListener('click', function() { importFileInput.click(); }); importFileInput.addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(event) { try { const settings = JSON.parse(event.target.result); // 验证设置格式 if (typeof settings === 'object' && settings !== null) { // 保存设置到localStorage localStorage.setItem('themeSettings', JSON.stringify(settings)); // 应用新设置 applySettings(settings); // 显示成功提示 showNotification('设置导入成功!', 'success'); } else { throw new Error('无效的设置文件格式'); } } catch (error) { showNotification('导入失败:' + error.message, 'error'); } }; reader.readAsText(file); // 重置文件输入 importFileInput.value = ''; }); // 应用设置函数 function applySettings(settings) { // 更新所有输入控件 if (settings.primaryColor) { primaryColorInput.value = settings.primaryColor; updateCSSVariable('--primary-color', settings.primaryColor); } if (settings.secondaryColor) { secondaryColorInput.value = settings.secondaryColor; updateCSSVariable('--secondary-color', settings.secondaryColor); } if (settings.backgroundColor) { backgroundColorInput.value = settings.backgroundColor; updateCSSVariable('--background-color', settings.backgroundColor); } if (settings.fontFamily) { fontFamilySelect.value = settings.fontFamily; updateCSSVariable('--font-family', settings.fontFamily); } if (settings.fontSize) { fontSizeInput.value = settings.fontSize; fontSizeValue.textContent = settings.fontSize + 'px'; updateCSSVariable('--base-font-size', settings.fontSize + 'px'); } if (settings.layoutWidth) { layoutWidthInput.value = settings.layoutWidth; layoutWidthValue.textContent = settings.layoutWidth + 'px'; updateCSSVariable('--container-width', settings.layoutWidth + 'px'); } if (settings.roundedCorners !== undefined) { roundedCornersCheckbox.checked = settings.roundedCorners; toggleRoundedCorners(settings.roundedCorners); } } }); 5.2 添加键盘快捷键支持 为高级用户添加键盘快捷键,提升操作效率。 添加键盘快捷键功能: // keyboard-shortcuts.js document.addEventListener('DOMContentLoaded', function() { // 键盘快捷键映射 const shortcuts = { // 切换暗黑模式: Ctrl+Shift+D 'toggleDarkMode': { key: 'D', ctrlKey: true, shiftKey: true, action: function() { const darkModeToggle = document.getElementById('dark-mode-toggle'); if (darkModeToggle) darkModeToggle.click(); } }, // 打开/关闭定制面板: Ctrl+Shift+C 'toggleCustomizer': { key: 'C', ctrlKey: true,

发表评论

实战教程,为网站集成智能化的内容相似度检测与防抄袭系统

实战教程:为WordPress网站集成智能化的内容相似度检测与防抄袭系统 引言:内容原创性在数字时代的重要性 在当今信息爆炸的互联网环境中,内容创作已成为网站运营的核心。然而,随着内容数量的急剧增加,抄袭和内容重复问题也日益严重。对于WordPress网站管理员和内容创作者而言,保护原创内容不仅是维护品牌声誉的需要,更是提升搜索引擎排名、吸引忠实读者的关键。 传统的防抄袭方法主要依赖人工检查或基础文本比对,效率低下且难以应对海量内容。本教程将指导您通过WordPress代码二次开发,集成智能化的内容相似度检测与防抄袭系统,将您的网站提升到一个新的智能化水平。 第一部分:系统架构设计与技术选型 1.1 系统核心功能需求分析 在开始开发之前,我们需要明确系统应具备的核心功能: 实时内容检测:在文章发布时自动检测内容相似度 批量历史内容扫描:对网站现有内容进行全面检查 智能相似度算法:采用先进的文本相似度计算方法 外部资源比对:能够与互联网上的公开内容进行比对 可视化报告系统:直观展示检测结果和相似度分析 自动化处理机制:根据预设规则自动处理疑似抄袭内容 1.2 技术栈选择与原理 我们将采用以下技术方案: WordPress钩子机制:利用save_post、publish_post等动作钩子实现自动化检测 PHP文本处理库:使用PHP内置函数和扩展进行文本预处理 相似度算法:实现余弦相似度、Jaccard相似系数和编辑距离算法 外部API集成:通过第三方原创检测API增强检测能力 数据库优化:合理设计数据表结构,确保系统性能 前端展示:使用AJAX和Chart.js实现交互式报告界面 1.3 系统架构图 用户发布内容 → WordPress钩子触发 → 文本预处理 → 特征提取 → 相似度计算 → 结果评估 → 数据库存储 → 报告生成 → 用户通知 第二部分:开发环境搭建与基础配置 2.1 开发环境准备 首先,确保您的开发环境满足以下要求: WordPress 5.0或更高版本 PHP 7.3或更高版本(支持mbstring、curl扩展) MySQL 5.6或更高版本 至少100MB的可用磁盘空间(用于存储文本指纹和缓存) 2.2 创建自定义插件 我们将创建一个独立的WordPress插件来实现所有功能: 在wp-content/plugins/目录下创建新文件夹smart-content-checker 创建主插件文件smart-content-checker.php: <?php /** * Plugin Name: 智能内容相似度检测与防抄袭系统 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress网站提供智能化的内容相似度检测与防抄袭功能 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: smart-content-checker */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SCC_VERSION', '1.0.0'); define('SCC_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SCC_PLUGIN_URL', plugin_dir_url(__FILE__)); define('SCC_CACHE_TIME', 3600); // 缓存时间1小时 // 初始化插件 require_once SCC_PLUGIN_DIR . 'includes/class-core.php'; require_once SCC_PLUGIN_DIR . 'includes/class-text-processor.php'; require_once SCC_PLUGIN_DIR . 'includes/class-similarity-checker.php'; require_once SCC_PLUGIN_DIR . 'includes/class-database.php'; require_once SCC_PLUGIN_DIR . 'includes/class-admin-interface.php'; // 启动插件 function scc_init_plugin() { $core = new SCC_Core(); $core->init(); } add_action('plugins_loaded', 'scc_init_plugin'); 第三部分:核心文本处理与特征提取模块 3.1 文本预处理类实现 创建includes/class-text-processor.php文件: <?php class SCC_Text_Processor { /** * 文本清洗和标准化 */ public function clean_text($text) { // 移除HTML标签 $text = strip_tags($text); // 转换所有字符为小写 $text = mb_strtolower($text, 'UTF-8'); // 移除特殊字符和标点符号,保留中文、英文和数字 $text = preg_replace('/[^p{L}p{N}s]/u', ' ', $text); // 移除多余空格 $text = preg_replace('/s+/', ' ', $text); return trim($text); } /** * 中文文本分词 * 注意:需要服务器安装中文分词扩展,这里提供简单实现 */ public function chinese_segmentation($text) { // 如果服务器安装了scws或jieba分词,可以调用相关函数 // 这里提供一个简单的按字符分割的方法(适用于基础需求) if (function_exists('scws_new')) { // 使用scws分词 $so = scws_new(); $so->set_charset('utf8'); $so->send_text($text); $words = array(); while ($tmp = $so->get_result()) { foreach ($tmp as $word) { if (strlen($word['word']) > 1) { $words[] = $word['word']; } } } $so->close(); return $words; } else { // 简单分词:按空格和标点分割 return preg_split('/s+/', $text); } } /** * 提取文本特征(词频向量) */ public function extract_features($text, $max_features = 100) { $cleaned_text = $this->clean_text($text); // 分词 $words = $this->chinese_segmentation($cleaned_text); // 计算词频 $word_freq = array_count_values($words); // 移除停用词 $word_freq = $this->remove_stopwords($word_freq); // 按词频排序并取前N个特征 arsort($word_freq); $features = array_slice($word_freq, 0, $max_features, true); return $features; } /** * 移除停用词 */ private function remove_stopwords($word_freq) { // 中文停用词列表(部分示例) $stopwords = array( '的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这', '那', '他', '她', '它' ); // 英文停用词 $english_stopwords = array( 'a', 'an', 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing' ); $all_stopwords = array_merge($stopwords, $english_stopwords); foreach ($all_stopwords as $stopword) { if (isset($word_freq[$stopword])) { unset($word_freq[$stopword]); } } return $word_freq; } /** * 生成文本指纹(Simhash算法简化版) */ public function generate_simhash($text) { $features = $this->extract_features($text, 64); $vector = array_fill(0, 64, 0); foreach ($features as $word => $weight) { $hash = crc32($word); for ($i = 0; $i < 64; $i++) { $bit = ($hash >> $i) & 1; if ($bit == 1) { $vector[$i] += $weight; } else { $vector[$i] -= $weight; } } } // 生成64位指纹 $fingerprint = 0; for ($i = 0; $i < 64; $i++) { if ($vector[$i] > 0) { $fingerprint |= (1 << $i); } } return $fingerprint; } } 3.2 相似度计算算法实现 创建includes/class-similarity-checker.php文件: <?php class SCC_Similarity_Checker { private $text_processor; public function __construct() { $this->text_processor = new SCC_Text_Processor(); } /** * 计算余弦相似度 */ public function cosine_similarity($text1, $text2) { $features1 = $this->text_processor->extract_features($text1); $features2 = $this->text_processor->extract_features($text2); // 获取所有特征的并集 $all_features = array_unique(array_merge( array_keys($features1), array_keys($features2) )); // 创建向量 $vector1 = array(); $vector2 = array(); foreach ($all_features as $feature) { $vector1[] = isset($features1[$feature]) ? $features1[$feature] : 0; $vector2[] = isset($features2[$feature]) ? $features2[$feature] : 0; } // 计算点积 $dot_product = 0; for ($i = 0; $i < count($vector1); $i++) { $dot_product += $vector1[$i] * $vector2[$i]; } // 计算模长 $magnitude1 = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $vector1))); $magnitude2 = sqrt(array_sum(array_map(function($x) { return $x * $x; }, $vector2))); // 避免除以零 if ($magnitude1 == 0 || $magnitude2 == 0) { return 0; } return $dot_product / ($magnitude1 * $magnitude2); } /** * 计算Jaccard相似系数 */ public function jaccard_similarity($text1, $text2) { $features1 = $this->text_processor->extract_features($text1); $features2 = $this->text_processor->extract_features($text2); $set1 = array_keys($features1); $set2 = array_keys($features2); $intersection = array_intersect($set1, $set2); $union = array_unique(array_merge($set1, $set2)); if (count($union) == 0) { return 0; } return count($intersection) / count($union); } /** * 计算Simhash海明距离 */ public function simhash_distance($text1, $text2) { $hash1 = $this->text_processor->generate_simhash($text1); $hash2 = $this->text_processor->generate_simhash($text2); // 计算海明距离 $xor = $hash1 ^ $hash2; $distance = 0; while ($xor) { $distance += $xor & 1; $xor >>= 1; } return $distance; } /** * 综合相似度评估 */ public function comprehensive_similarity($text1, $text2) { $cosine = $this->cosine_similarity($text1, $text2); $jaccard = $this->jaccard_similarity($text1, $text2); $simhash_distance = $this->simhash_distance($text1, $text2); // 将Simhash距离转换为相似度(距离越小,相似度越高) $simhash_similarity = max(0, 1 - ($simhash_distance / 64)); // 加权平均 $weights = array( 'cosine' => 0.5, 'jaccard' => 0.3, 'simhash' => 0.2 ); $similarity = ($cosine * $weights['cosine']) + ($jaccard * $weights['jaccard']) + ($simhash_similarity * $weights['simhash']); return round($similarity, 4); } /** * 与外部API集成进行深度检测 */ public function external_api_check($text, $api_type = 'copyscape') { // 这里以Copyscape API为例 $api_key = get_option('scc_copyscape_api_key', ''); if (empty($api_key)) { return array( 'success' => false, 'message' => 'API密钥未配置' ); } $encoded_text = urlencode($text); $url = "https://www.copyscape.com/api/?o=search&k={$api_key}&t={$encoded_text}&f=xml"; $response = wp_remote_get($url, array( 'timeout' => 30, 'sslverify' => false )); if (is_wp_error($response)) { return array( 'success' => false, 'message' => $response->get_error_message() ); } $body = wp_remote_retrieve_body($response); // 解析XML响应 $xml = simplexml_load_string($body); if (!$xml) { return array( 'success' => false, 'message' => 'API响应解析失败' ); } $results = array(); if (isset($xml->result)) { foreach ($xml->result as $result) { $results[] = array( 'url' => (string)$result->url, 'title' => (string)$result->title, 'similarity' => (float)$result->minwordsmatched / 100 ); } } return array( 'success' => true, 'results' => $results ); } } 第四部分:数据库设计与数据管理 4.1 数据库表结构设计 创建includes/class-database.php文件: <?php class SCC_Database { /** * 创建必要的数据库表 */ public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'scc_content_fingerprints'; $results_table = $wpdb->prefix . 'scc_scan_results'; // 内容指纹表 $sql1 = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, post_id bigint(20) NOT NULL, fingerprint_64 bigint(20) UNSIGNED NOT NULL, fingerprint_128 varchar(255) DEFAULT NULL, content_hash varchar(64) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY post_id (post_id), KEY fingerprint_64 (fingerprint_64), KEY content_hash (content_hash) ) $charset_collate;"; // 扫描结果表 $sql2 = "CREATE TABLE IF NOT EXISTS $results_table ( id bigint(20) NOT NULL AUTO_INCREMENT, post_id bigint(20) NOT NULL, scan_type varchar(50) NOT NULL, similarity_score float NOT NULL, matched_urls text, details text, scan_date datetime DEFAULT CURRENT_TIMESTAMP, status varchar(20) DEFAULT 'pending', PRIMARY KEY (id), KEY post_id (post_id), KEY scan_date (scan_date), KEY status (status) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql1); dbDelta($sql2); } /** * 保存内容指纹 */ public function save_fingerprint($post_id, $fingerprint_64, $content_hash, $fingerprint_128 = null) { global $wpdb; $table_name = $wpdb->prefix . 'scc_content_fingerprints'; // 检查是否已存在 $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_name WHERE post_id = %d", $post_id )); if ($existing) { // 更新现有记录 $wpdb->update( $table_name, array( 'fingerprint_64' => $fingerprint_64, 'fingerprint_128' => $fingerprint_128, 'content_hash' => $content_hash, 'updated_at' => current_time('mysql') ), ), array('%d', '%s', '%s', '%s'), array('%d') ); } else { // 插入新记录 $wpdb->insert( $table_name, array( 'post_id' => $post_id, 'fingerprint_64' => $fingerprint_64, 'fingerprint_128' => $fingerprint_128, 'content_hash' => $content_hash ), array('%d', '%d', '%s', '%s') ); } return $wpdb->insert_id; } /** * 查找相似内容 */ public function find_similar_content($fingerprint_64, $threshold = 5, $exclude_post_id = 0) { global $wpdb; $table_name = $wpdb->prefix . 'scc_content_fingerprints'; // 查找海明距离小于阈值的指纹 $query = $wpdb->prepare( "SELECT p1.post_id, BIT_COUNT(p1.fingerprint_64 ^ %d) as hamming_distance, p.post_title, p.post_date FROM $table_name p1 INNER JOIN {$wpdb->posts} p ON p1.post_id = p.ID WHERE BIT_COUNT(p1.fingerprint_64 ^ %d) <= %d AND p1.post_id != %d AND p.post_status = 'publish' ORDER BY hamming_distance ASC LIMIT 10", $fingerprint_64, $fingerprint_64, $threshold, $exclude_post_id ); return $wpdb->get_results($query, ARRAY_A); } /** * 保存扫描结果 */ public function save_scan_result($post_id, $scan_type, $similarity_score, $matched_urls = '', $details = '') { global $wpdb; $table_name = $wpdb->prefix . 'scc_scan_results'; $wpdb->insert( $table_name, array( 'post_id' => $post_id, 'scan_type' => $scan_type, 'similarity_score' => $similarity_score, 'matched_urls' => is_array($matched_urls) ? json_encode($matched_urls) : $matched_urls, 'details' => is_array($details) ? json_encode($details) : $details, 'status' => $similarity_score > 0.7 ? 'high_risk' : ($similarity_score > 0.3 ? 'medium_risk' : 'low_risk') ), array('%d', '%s', '%f', '%s', '%s', '%s') ); return $wpdb->insert_id; } /** * 获取文章的扫描历史 */ public function get_scan_history($post_id, $limit = 10) { global $wpdb; $table_name = $wpdb->prefix . 'scc_scan_results'; return $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name WHERE post_id = %d ORDER BY scan_date DESC LIMIT %d", $post_id, $limit ), ARRAY_A); } /** * 获取高风险内容统计 */ public function get_risk_statistics($days = 30) { global $wpdb; $table_name = $wpdb->prefix . 'scc_scan_results'; $query = $wpdb->prepare( "SELECT COUNT(CASE WHEN status = 'high_risk' THEN 1 END) as high_risk_count, COUNT(CASE WHEN status = 'medium_risk' THEN 1 END) as medium_risk_count, COUNT(CASE WHEN status = 'low_risk' THEN 1 END) as low_risk_count, DATE(scan_date) as scan_date FROM $table_name WHERE scan_date >= DATE_SUB(NOW(), INTERVAL %d DAY) GROUP BY DATE(scan_date) ORDER BY scan_date DESC", $days ); return $wpdb->get_results($query, ARRAY_A); } } 第五部分:WordPress集成与自动化检测 5.1 核心插件类实现 创建includes/class-core.php文件: <?php class SCC_Core { private $db; private $text_processor; private $similarity_checker; public function __construct() { $this->db = new SCC_Database(); $this->text_processor = new SCC_Text_Processor(); $this->similarity_checker = new SCC_Similarity_Checker(); } /** * 初始化插件 */ public function init() { // 创建数据库表 register_activation_hook(__FILE__, array($this->db, 'create_tables')); // 添加WordPress钩子 add_action('save_post', array($this, 'on_post_save'), 10, 3); add_action('publish_post', array($this, 'on_post_publish'), 10, 2); // 添加管理菜单 add_action('admin_menu', array($this, 'add_admin_menu')); // 添加AJAX处理 add_action('wp_ajax_scc_manual_scan', array($this, 'ajax_manual_scan')); add_action('wp_ajax_scc_bulk_scan', array($this, 'ajax_bulk_scan')); // 添加文章列表列 add_filter('manage_posts_columns', array($this, 'add_post_columns')); add_action('manage_posts_custom_column', array($this, 'render_post_columns'), 10, 2); // 添加文章编辑页面元框 add_action('add_meta_boxes', array($this, 'add_meta_boxes')); } /** * 文章保存时触发 */ public function on_post_save($post_id, $post, $update) { // 跳过自动保存和修订 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (wp_is_post_revision($post_id)) { return; } // 只处理特定文章类型 $allowed_types = array('post', 'page'); if (!in_array($post->post_type, $allowed_types)) { return; } // 获取文章内容 $content = $post->post_content; // 生成内容哈希 $content_hash = md5($content); // 生成指纹 $fingerprint_64 = $this->text_processor->generate_simhash($content); // 保存指纹 $this->db->save_fingerprint($post_id, $fingerprint_64, $content_hash); // 如果是更新操作,检查与历史版本的相似度 if ($update) { $this->check_self_similarity($post_id, $content); } } /** * 文章发布时触发 */ public function on_post_publish($post_id, $post) { // 执行相似度检测 $this->perform_similarity_check($post_id, $post->post_content); } /** * 执行相似度检测 */ private function perform_similarity_check($post_id, $content) { // 1. 内部相似度检测 $fingerprint_64 = $this->text_processor->generate_simhash($content); $similar_posts = $this->db->find_similar_content($fingerprint_64, 5, $post_id); $internal_similarity = 0; $matched_posts = array(); if (!empty($similar_posts)) { foreach ($similar_posts as $similar_post) { $similar_post_content = get_post_field('post_content', $similar_post['post_id']); $similarity = $this->similarity_checker->comprehensive_similarity($content, $similar_post_content); if ($similarity > $internal_similarity) { $internal_similarity = $similarity; } if ($similarity > 0.3) { $matched_posts[] = array( 'post_id' => $similar_post['post_id'], 'title' => $similar_post['post_title'], 'similarity' => $similarity, 'url' => get_permalink($similar_post['post_id']) ); } } } // 2. 外部API检测(可选) $external_results = array(); if (get_option('scc_enable_external_check', false)) { $external_check = $this->similarity_checker->external_api_check($content); if ($external_check['success']) { $external_results = $external_check['results']; } } // 计算综合相似度 $total_similarity = $internal_similarity; if (!empty($external_results)) { $external_similarity = max(array_column($external_results, 'similarity')); $total_similarity = max($total_similarity, $external_similarity); } // 保存结果 $this->db->save_scan_result( $post_id, 'auto', $total_similarity, array_merge($matched_posts, $external_results), array( 'internal_similarity' => $internal_similarity, 'matched_posts' => $matched_posts, 'external_results' => $external_results ) ); // 发送通知 if ($total_similarity > get_option('scc_notification_threshold', 0.7)) { $this->send_notification($post_id, $total_similarity); } return $total_similarity; } /** * 检查与历史版本的相似度 */ private function check_self_similarity($post_id, $current_content) { $revisions = wp_get_post_revisions($post_id); if (empty($revisions)) { return; } // 获取最新修订版 $latest_revision = reset($revisions); $revision_content = $latest_revision->post_content; $similarity = $this->similarity_checker->comprehensive_similarity($current_content, $revision_content); // 如果相似度低于阈值,记录重大修改 if ($similarity < 0.5) { $this->db->save_scan_result( $post_id, 'revision_check', $similarity, array(), array( 'message' => '检测到文章内容发生重大修改', 'revision_id' => $latest_revision->ID, 'similarity_with_revision' => $similarity ) ); } } /** * 发送通知 */ private function send_notification($post_id, $similarity) { $post = get_post($post_id); $author = get_userdata($post->post_author); $admin_email = get_option('admin_email'); $subject = sprintf('【内容相似度警报】文章 "%s" 检测到高相似度内容', $post->post_title); $message = sprintf( "文章标题:%sn" . "文章ID:%dn" . "作者:%sn" . "检测相似度:%.2f%%n" . "文章链接:%sn" . "编辑链接:%snn" . "请及时审核该文章内容。", $post->post_title, $post_id, $author->display_name, $similarity * 100, get_permalink($post_id), admin_url('post.php?post=' . $post_id . '&action=edit') ); // 发送给管理员 wp_mail($admin_email, $subject, $message); // 如果设置了作者通知,也发送给作者 if (get_option('scc_notify_author', false)) { wp_mail($author->user_email, $subject, $message); } } } 5.2 管理界面实现 创建includes/class-admin-interface.php文件: <?php class SCC_Admin_Interface { private $db; public function __construct() { $this->db = new SCC_Database(); } /** * 添加管理菜单 */ public function add_admin_menu() { // 主菜单 add_menu_page( '内容相似度检测', '内容检测', 'manage_options', 'scc-dashboard', array($this, 'render_dashboard_page'), 'dashicons-search', 30 ); // 子菜单 add_submenu_page( 'scc-dashboard', '批量检测', '批量检测', 'manage_options', 'scc-bulk-scan', array($this, 'render_bulk_scan_page') ); add_submenu_page( 'scc-dashboard', '检测设置', '设置', 'manage_options', 'scc-settings', array($this, 'render_settings_page') ); add_submenu_page( 'scc-dashboard', '检测报告', '报告统计', 'manage_options', 'scc-reports', array($this, 'render_reports_page') ); } /** * 渲染仪表盘页面 */ public function render_dashboard_page() { ?> <div class="wrap scc-dashboard"> <h1><?php echo esc_html(get_admin_page_title()); ?></h1> <div class="scc-stats-container"> <div class="scc-stat-card"> <h3>今日检测</h3> <div class="stat-number"><?php echo $this->get_today_scan_count(); ?></div> </div> <div class="scc-stat-card"> <h3>高风险内容</h3> <div class="stat-number" style="color: #dc3232;"><?php echo $this->get_high_risk_count(); ?></div> </div> <div class="scc-stat-card"> <h3>平均相似度</h3> <div class="stat-number"><?php echo $this->get_average_similarity(); ?>%</div> </div> <div class="scc-stat-card"> <h3>已保护文章</h3> <div class="stat-number"><?php echo $this->get_protected_post_count(); ?></div> </div> </div> <div class="scc-quick-actions"> <h2>快速操作</h2> <button class="button button-primary" onclick="sccQuickScan()">快速扫描最新文章</button> <button class="button" onclick="window.location.href='?page=scc-bulk-scan'">批量检测</button> <button class="button" onclick="window.location.href='?page=scc-reports'">查看完整报告</button> </div> <div class="scc-recent-scans"> <h2>最近检测记录</h2> <?php $this->render_recent_scans_table(); ?> </div> <script> function sccQuickScan() { jQuery.post(ajaxurl, { action: 'scc_manual_scan', post_ids: 'recent', nonce: '<?php echo wp_create_nonce('scc_manual_scan'); ?>' }, function(response) { if (response.success) { alert('扫描完成!检测到 ' + response.data.high_risk + ' 篇高风险文章'); location.reload(); } else { alert('扫描失败:' + response.data); } }); } </script> <style> .scc-stats-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; } .scc-stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center; } .scc-stat-card h3 { margin-top: 0; color: #666; } .scc-stat-card .stat-number { font-size: 2.5em; font-weight: bold; color: #0073aa; } .scc-quick-actions { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: 20px 0; } .scc-quick-actions button { margin-right: 10px; margin-bottom: 10px; } </style> </div> <?php } /** * 渲染批量检测页面 */ public function render_bulk_scan_page() { ?> <div class="wrap"> <h1>批量内容检测</h1> <div class="card"> <h2>选择检测范围</h2> <form id="scc-bulk-scan-form"> <table class="form-table"> <tr> <th scope="row">文章类型</th> <td> <select name="post_type" id="post_type"> <option value="post">文章</option> <option value="page">页面</option> <option value="all">所有内容</option>

发表评论

详细指南,在WordPress中开发集成在线简历生成与职位推荐工具

详细指南:在WordPress中开发集成在线简历生成与职位推荐工具 摘要 本文提供了一份全面的技术指南,详细介绍了如何在WordPress平台中通过代码二次开发,创建一个集在线简历生成与智能职位推荐功能于一体的专业工具。我们将从系统架构设计开始,逐步深入到具体实现步骤,包括数据库设计、前端界面开发、核心功能实现以及系统优化等方面。本指南适合有一定WordPress开发经验的中级开发者,旨在帮助您构建一个功能完善、用户体验优秀的职业服务平台。 一、项目概述与需求分析 1.1 项目背景与目标 在当今数字化招聘市场中,求职者需要一个能够快速创建专业简历并获取个性化职位推荐的平台。而招聘方则希望找到与职位要求高度匹配的候选人。通过WordPress这一广泛使用的内容管理系统,我们可以开发一个集成这两大功能的工具,为用户提供一站式职业发展服务。 本项目的主要目标包括: 开发一个用户友好的在线简历创建和编辑系统 实现基于用户技能和经验的智能职位推荐算法 创建可自定义的简历模板系统 确保数据安全性和用户隐私保护 提供响应式设计,支持多设备访问 1.2 功能需求详细说明 简历生成模块需求: 用户注册与个人资料管理 分步骤简历创建向导(个人信息、教育背景、工作经历、技能等) 多种专业简历模板选择 实时预览功能 导出为PDF/Word格式 简历分享链接生成 职位推荐模块需求: 职位数据库管理 基于用户简历内容的智能匹配算法 个性化推荐排序 职位收藏与申请跟踪 推荐职位邮件通知 1.3 技术栈选择 核心平台:WordPress 5.8+ 开发语言:PHP 7.4+,JavaScript (ES6+) 前端框架:React/Vue.js(可选,用于复杂交互) 数据库:MySQL 5.7+ PDF生成:TCPDF或Dompdf库 缓存机制:Redis或Memcached(可选) 安全性:WordPress Nonce,数据验证与清理 二、系统架构设计 2.1 整体架构图 用户层 (前端界面) ↓ 表示层 (WordPress主题 + 自定义页面模板) ↓ 应用层 (自定义插件 + REST API) ↓ 数据层 (WordPress数据库 + 自定义表) ↓ 服务层 (第三方API集成:职位数据源) 2.2 数据库设计 2.2.1 核心数据表结构 -- 简历主表 CREATE TABLE wp_resume_builder_resumes ( resume_id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) UNSIGNED NOT NULL, title VARCHAR(255) NOT NULL, template_id INT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, is_public TINYINT(1) DEFAULT 0, FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE ); -- 简历内容表(JSON格式存储,便于扩展) CREATE TABLE wp_resume_builder_content ( content_id INT AUTO_INCREMENT PRIMARY KEY, resume_id INT NOT NULL, section_type ENUM('personal', 'education', 'experience', 'skills', 'projects', 'certifications') NOT NULL, content_data JSON NOT NULL, display_order INT DEFAULT 0, FOREIGN KEY (resume_id) REFERENCES wp_resume_builder_resumes(resume_id) ON DELETE CASCADE ); -- 职位信息表 CREATE TABLE wp_resume_builder_jobs ( job_id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, company VARCHAR(255) NOT NULL, description TEXT, requirements JSON, location VARCHAR(255), salary_range VARCHAR(100), job_type ENUM('full-time', 'part-time', 'contract', 'remote', 'hybrid') DEFAULT 'full-time', posted_date DATETIME DEFAULT CURRENT_TIMESTAMP, expiry_date DATETIME, is_active TINYINT(1) DEFAULT 1 ); -- 用户技能标签表 CREATE TABLE wp_resume_builder_skills ( skill_id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) UNSIGNED NOT NULL, skill_name VARCHAR(100) NOT NULL, proficiency_level ENUM('beginner', 'intermediate', 'advanced', 'expert') DEFAULT 'intermediate', years_of_experience DECIMAL(3,1), FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE ); -- 职位推荐记录表 CREATE TABLE wp_resume_builder_recommendations ( recommendation_id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) UNSIGNED NOT NULL, job_id INT NOT NULL, match_score DECIMAL(5,2), viewed TINYINT(1) DEFAULT 0, applied TINYINT(1) DEFAULT 0, recommended_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES wp_users(ID) ON DELETE CASCADE, FOREIGN KEY (job_id) REFERENCES wp_resume_builder_jobs(job_id) ON DELETE CASCADE ); 2.2.2 数据库优化策略 为频繁查询的字段添加索引 使用InnoDB引擎支持事务和外键 定期归档历史数据 实施查询缓存机制 2.3 插件架构设计 resume-builder-plugin/ ├── resume-builder.php # 主插件文件 ├── includes/ │ ├── class-database-handler.php # 数据库操作类 │ ├── class-resume-builder.php # 简历构建核心类 │ ├── class-job-matcher.php # 职位匹配算法类 │ ├── class-pdf-generator.php # PDF生成类 │ └── class-api-handler.php # REST API处理类 ├── admin/ │ ├── class-admin-menu.php # 管理菜单 │ ├── class-settings-page.php # 设置页面 │ └── class-job-manager.php # 职位管理界面 ├── public/ │ ├── css/ # 前端样式 │ ├── js/ # 前端脚本 │ └── templates/ # 前端模板 ├── assets/ # 静态资源 ├── templates/ # 简历模板 └── vendor/ # 第三方库 三、开发环境搭建与基础配置 3.1 本地开发环境配置 安装本地服务器环境: 使用XAMPP、MAMP或Local by Flywheel 确保PHP版本≥7.4,MySQL≥5.7 设置WordPress开发环境: # 创建插件目录 mkdir -p wp-content/plugins/resume-builder # 启用WordPress调试模式 # 在wp-config.php中添加 define('WP_DEBUG', true); define('WP_DEBUG_LOG', true); define('WP_DEBUG_DISPLAY', false); 创建主插件文件: <?php /** * Plugin Name: 简历生成与职位推荐工具 * Plugin URI: https://yourwebsite.com/ * Description: 一个集在线简历生成与智能职位推荐功能的WordPress插件 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: resume-builder */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('RB_PLUGIN_VERSION', '1.0.0'); define('RB_PLUGIN_PATH', plugin_dir_path(__FILE__)); define('RB_PLUGIN_URL', plugin_dir_url(__FILE__)); define('RB_PLUGIN_BASENAME', plugin_basename(__FILE__)); // 初始化插件 require_once RB_PLUGIN_PATH . 'includes/class-resume-builder-init.php'; // 激活/停用钩子 register_activation_hook(__FILE__, ['Resume_Builder_Init', 'activate']); register_deactivation_hook(__FILE__, ['Resume_Builder_Init', 'deactivate']); // 初始化插件 add_action('plugins_loaded', ['Resume_Builder_Init', 'get_instance']); 3.2 数据库表创建 创建数据库初始化类: // includes/class-database-handler.php class Resume_Builder_Database { private static $instance = null; private $charset_collate; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { global $wpdb; $this->charset_collate = $wpdb->get_charset_collate(); } public function create_tables() { require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); $sql = []; // 简历主表 $sql[] = "CREATE TABLE {$wpdb->prefix}resume_builder_resumes ( resume_id INT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT(20) UNSIGNED NOT NULL, title VARCHAR(255) NOT NULL, template_id INT DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, is_public TINYINT(1) DEFAULT 0, FOREIGN KEY (user_id) REFERENCES {$wpdb->prefix}users(ID) ON DELETE CASCADE ) {$this->charset_collate};"; // 其他表创建语句... foreach ($sql as $query) { dbDelta($query); } // 添加默认数据 $this->add_default_data(); } private function add_default_data() { // 添加默认简历模板 $default_templates = [ [ 'name' => '经典专业', 'description' => '简洁专业的简历模板', 'thumbnail' => RB_PLUGIN_URL . 'assets/templates/classic.png', 'file_path' => RB_PLUGIN_PATH . 'templates/classic.php' ], // 更多模板... ]; // 保存到选项表 update_option('rb_default_templates', $default_templates); } public function update_tables() { // 数据库升级逻辑 } } 四、简历生成模块开发 4.1 用户界面设计 4.1.1 创建简历构建器短代码 // includes/class-resume-builder.php class Resume_Builder_Frontend { public function init() { // 注册短代码 add_shortcode('resume_builder', [$this, 'render_resume_builder']); // 注册前端脚本和样式 add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_assets']); } public function enqueue_frontend_assets() { // 仅在有短代码的页面加载 global $post; if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'resume_builder')) { // 样式文件 wp_enqueue_style( 'rb-frontend-style', RB_PLUGIN_URL . 'public/css/frontend.css', [], RB_PLUGIN_VERSION ); // JavaScript文件 wp_enqueue_script( 'rb-frontend-script', RB_PLUGIN_URL . 'public/js/frontend.js', ['jquery', 'wp-api'], RB_PLUGIN_VERSION, true ); // 本地化脚本 wp_localize_script('rb-frontend-script', 'rb_ajax', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('rb_ajax_nonce'), 'user_id' => get_current_user_id(), 'i18n' => [ 'save_success' => __('保存成功', 'resume-builder'), 'save_error' => __('保存失败,请重试', 'resume-builder'), // 更多翻译字符串... ] ]); } } public function render_resume_builder($atts) { // 检查用户是否登录 if (!is_user_logged_in()) { return $this->render_login_prompt(); } ob_start(); ?> <div id="resume-builder-app" class="resume-builder-container"> <!-- 简历构建器界面 --> <div class="rb-stepper"> <!-- 步骤指示器 --> <div class="stepper-header"> <div class="step active" data-step="1">个人信息</div> <div class="step" data-step="2">教育背景</div> <div class="step" data-step="3">工作经历</div> <div class="step" data-step="4">技能专长</div> <div class="step" data-step="5">预览与导出</div> </div> <!-- 步骤内容 --> <div class="stepper-content"> <!-- 步骤1: 个人信息 --> <div class="step-content active" id="step-1"> <h3>个人信息</h3> <form id="personal-info-form"> <div class="form-group"> <label for="full_name">全名</label> <input type="text" id="full_name" name="full_name" required> </div> <!-- 更多表单字段... --> </form> </div> <!-- 其他步骤内容... --> </div> <!-- 导航按钮 --> <div class="stepper-nav"> <button class="btn btn-secondary" id="prev-step">上一步</button> <button class="btn btn-primary" id="next-step">下一步</button> <button class="btn btn-success" id="save-resume">保存简历</button> </div> </div> <!-- 实时预览面板 --> <div class="resume-preview"> <h3>简历预览</h3> <div id="resume-preview-content"> <!-- 通过JavaScript动态加载预览 --> </div> <div class="preview-actions"> <button class="btn btn-outline" id="change-template">更换模板</button> <button class="btn btn-primary" id="export-pdf">导出PDF</button> <button class="btn btn-secondary" id="share-resume">分享链接</button> </div> </div> </div> <?php return ob_get_clean(); } private function render_login_prompt() { ob_start(); ?> <div class="rb-login-prompt"> <h3>请登录以创建简历</h3> <p>登录后您可以创建、编辑和管理您的专业简历</p> <div class="login-actions"> <a href="<?php echo wp_login_url(get_permalink()); ?>" class="btn btn-primary"> 登录 </a> <a href="<?php echo wp_registration_url(); ?>" class="btn btn-secondary"> 注册 </a> </div> </div> <?php return ob_get_clean(); } } 4.1.2 前端JavaScript交互 // public/js/frontend.js (function($) { 'use strict'; class ResumeBuilder { constructor() { this.currentStep = 1; this.totalSteps = 5; this.resumeData = {}; this.init(); } init() { this.bindEvents(); this.loadUserData(); } bindEvents() { // 步骤导航 $('#next-step').on('click', () => this.nextStep()); $('#prev-step').on('click', () => this.prevStep()); // 表单保存 $('.step-content form').on('change', 'input, textarea, select', (e) => { this.saveStepData($(e.target).closest('form')); }); // 导出功能 $('#export-pdf').on('click', () => this.exportToPDF()); $('#share-resume').on('click', () => this.generateShareLink()); // 模板切换 $('#change-template').on('click', () => this.showTemplateSelector()); } nextStep() { if (this.currentStep < this.totalSteps) { this.validateCurrentStep(); this.currentStep++; this.updateStepper(); } } prevStep() { if (this.currentStep > 1) { this.currentStep--; this.updateStepper(); } } updateStepper() { // 更新步骤指示器 $('.step').removeClass('active'); $(`.step[data-step="${this.currentStep}"]`).addClass('active'); // 更新内容显示 $('.step-content').removeClass('active'); $(`#step-${this.currentStep}`).addClass('active'); // 更新按钮状态 $('#prev-step').toggle(this.currentStep > 1); $('#next-step').toggle(this.currentStep < this.totalSteps); $('#save-resume').toggle(this.currentStep === this.totalSteps); // 更新预览 this.updatePreview(); } saveStepData(form) { const formData = new FormData(form[0]); const stepData = {}; for (let [key, value] of formData.entries()) { stepData[key] = value; } resumeData[step${this.currentStep}] = stepData; // 自动保存到服务器 this.autoSaveToServer(); } autoSaveToServer() { $.ajax({ url: rb_ajax.ajax_url, type: 'POST', data: { action: 'save_resume_data', nonce: rb_ajax.nonce, user_id: rb_ajax.user_id, resume_data: this.resumeData }, success: (response) => { if (response.success) { console.log('自动保存成功'); } } }); } updatePreview() { // 使用AJAX获取HTML预览 $.ajax({ url: rb_ajax.ajax_url, type: 'POST', data: { action: 'get_resume_preview', nonce: rb_ajax.nonce, resume_data: this.resumeData, template_id: this.currentTemplate }, success: (response) => { if (response.success) { $('#resume-preview-content').html(response.data.preview); } } }); } exportToPDF() { // 生成PDF window.open( `${rb_ajax.ajax_url}?action=generate_pdf&nonce=${rb_ajax.nonce}&resume_id=${this.currentResumeId}`, '_blank' ); } generateShareLink() { $.ajax({ url: rb_ajax.ajax_url, type: 'POST', data: { action: 'generate_share_link', nonce: rb_ajax.nonce, resume_id: this.currentResumeId }, success: (response) => { if (response.success) { this.showShareModal(response.data.share_url); } } }); } showShareModal(shareUrl) { // 创建分享模态框 const modalHtml = ` <div class="rb-modal" id="share-modal"> <div class="modal-content"> <h3>分享您的简历</h3> <div class="share-url"> <input type="text" value="${shareUrl}" readonly> <button class="btn-copy" data-clipboard-text="${shareUrl}">复制</button> </div> <div class="share-options"> <button class="share-option" data-platform="linkedin">LinkedIn</button> <button class="share-option" data-platform="email">电子邮件</button> <button class="share-option" data-platform="whatsapp">WhatsApp</button> </div> </div> </div> `; $('body').append(modalHtml); this.initClipboard(); } initClipboard() { // 初始化剪贴板功能 new ClipboardJS('.btn-copy'); } } // 初始化简历构建器 $(document).ready(function() { if ($('#resume-builder-app').length) { window.resumeBuilder = new ResumeBuilder(); } }); })(jQuery); ### 4.2 简历模板系统 #### 4.2.1 模板引擎设计 // includes/class-template-engine.phpclass Resume_Builder_Template_Engine { private $templates = []; private $current_template = null; public function __construct() { $this->load_templates(); } private function load_templates() { $template_dir = RB_PLUGIN_PATH . 'templates/'; $template_files = glob($template_dir . '*.php'); foreach ($template_files as $file) { $template_name = basename($file, '.php'); $template_data = $this->get_template_metadata($file); $this->templates[$template_name] = [ 'name' => $template_data['name'] ?? ucfirst($template_name), 'description' => $template_data['description'] ?? '', 'thumbnail' => $template_data['thumbnail'] ?? '', 'file_path' => $file, 'version' => $template_data['version'] ?? '1.0', 'category' => $template_data['category'] ?? 'general' ]; } } private function get_template_metadata($file) { $metadata = [ 'name' => '', 'description' => '', 'thumbnail' => '', 'version' => '1.0', 'category' => 'general' ]; $content = file_get_contents($file); // 解析模板头部注释 if (preg_match('//**s*n(.*?)*//s', $content, $matches)) { $comment = $matches[1]; if (preg_match('/Template Name:s*(.+)/', $comment, $name_match)) { $metadata['name'] = trim($name_match[1]); } if (preg_match('/Description:s*(.+)/', $comment, $desc_match)) { $metadata['description'] = trim($desc_match[1]); } } return $metadata; } public function render_template($template_name, $resume_data) { if (!isset($this->templates[$template_name])) { $template_name = 'classic'; // 默认模板 } $template_file = $this->templates[$template_name]['file_path']; // 提取数据 $data = $this->prepare_template_data($resume_data); // 开始输出缓冲 ob_start(); // 包含模板文件 include $template_file; // 获取缓冲内容 $html = ob_get_clean(); // 应用CSS样式 $html = $this->apply_template_styles($template_name, $html); return $html; } private function prepare_template_data($resume_data) { $data = [ 'personal' => $resume_data['step1'] ?? [], 'education' => $this->format_education_data($resume_data['step2'] ?? []), 'experience' => $this->format_experience_data($resume_data['step3'] ?? []), 'skills' => $this->format_skills_data($resume_data['step4'] ?? []), 'summary' => $resume_data['step5']['summary'] ?? '' ]; return $data; } private function format_education_data($education_data) { // 格式化教育背景数据 $formatted = []; if (isset($education_data['institutions'])) { foreach ($education_data['institutions'] as $edu) { $formatted[] = [ 'institution' => $edu['school'] ?? '', 'degree' => $edu['degree'] ?? '', 'field' => $edu['field'] ?? '', 'period' => $this->format_period($edu['start_date'] ?? '', $edu['end_date'] ?? ''), 'description' => $edu['description'] ?? '', 'gpa' => $edu['gpa'] ?? '' ]; } } // 按时间倒序排列 usort($formatted, function($a, $b) { return strtotime($b['period']) - strtotime($a['period']); }); return $formatted; } private function format_experience_data($experience_data) { // 格式化工作经历数据 $formatted = []; if (isset($experience_data['positions'])) { foreach ($experience_data['positions'] as $exp) { $formatted[] = [ 'company' => $exp['company'] ?? '', 'position' => $exp['position'] ?? '', 'period' => $this->format_period($exp['start_date'] ?? '', $exp['end_date'] ?? ''), 'description' => $exp['description'] ?? '', 'achievements' => isset($exp['achievements']) ? explode("n", $exp['achievements']) : [], 'skills_used' => isset($exp['skills']) ? explode(',', $exp['skills']) : [] ]; } } // 按时间倒序排列 usort($formatted, function($a, $b) { return strtotime($b['period']) - strtotime($a['period']); }); return $formatted; } private function format_skills_data($skills_data) { // 格式化技能数据 $formatted = [ 'technical' => [], 'professional' => [], 'languages' => [] ]; if (isset($skills_data['technical_skills'])) { foreach ($skills_data['technical_skills'] as $skill) { $formatted['technical'][] = [ 'name' => $skill['name'] ?? '', 'level' => $skill['level'] ?? 'intermediate', 'years' => $skill['years'] ?? 0 ]; } } // 类似处理其他技能类型... return $formatted; } private function format_period($start_date, $end_date) { $start = date('M Y', strtotime($start_date)); $end = $end_date ? date('M Y', strtotime($end_date)) : '至今'; return "$start - $end"; } private function apply_template_styles($template_name, $html) { $css_file = RB_PLUGIN_PATH . "templates/css/{$template_name}.css"; if (file_exists($css_file)) { $css = file_get_contents($css_file); $html = "<style>{$css}</style>" . $html; } return $html; } public function get_available_templates() { return $this->templates; } } #### 4.2.2 示例简历模板 <!-- templates/classic.php --><?php/** Template Name: 经典专业 Description: 简洁专业的简历模板,适合传统行业 Version: 1.0 Category: professional */ ?><!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title><?php echo esc_html($data['personal']['full_name'] ?? '个人简历'); ?></title> </head><body class="rb-resume-template classic"> <div class="resume-container"> <!-- 头部信息 --> <header class="resume-header"> <div class="personal-info"> <h1 class="name"><?php echo esc_html($data['personal']['full_name'] ?? ''); ?></h1> <div class="contact-info"> <?php if (!empty($data['personal']['email'])): ?> <span class="contact-item"> <i class="icon-email"></i> <?php echo esc_html($data['personal']['email']); ?> </span> <?php endif; ?> <?php if (!empty($data['personal']['phone'])): ?> <span class="contact-item"> <i class="icon-phone"></i> <?php echo esc_html($data['personal']['phone']); ?> </span> <?php endif; ?> <?php if (!empty($data['personal']['location'])): ?> <span class="contact-item"> <i class="icon-location"></i> <?php echo esc_html($data['personal']['location']); ?> </span> <?php endif; ?> <?php if (!empty($data['personal']['linkedin'])): ?> <span class="contact-item"> <i class="icon-linkedin"></i> <?php echo esc_html($data['personal']['linkedin']); ?> </span> <?php endif; ?> </div> </div> <?php if (!empty($data['personal']['title'])): ?> <div class="professional-title"> <h2><?php echo esc_html($data['personal']['title']); ?></h2> </div> <?php endif; ?> </header> <!-- 个人简介 --> <?php if (!empty($data['summary'])): ?> <section class="resume-section summary"> <h3 class="section-title">个人简介</h3> <div class="section-content"> <p><?php echo nl2br(esc_html($data['summary'])); ?></p> </div> </section> <?php endif; ?> <!-- 工作经历 --> <?php if (!empty($data['experience'])): ?> <section class="resume-section experience"> <h3 class="section-title">工作经历</h3> <div class="section-content"> <?php foreach ($data['experience'] as $job): ?> <div class="experience-item"> <div class="job-header"> <h4 class="job-title"><?php echo esc_html($job['position']); ?></h4> <span class="company"><?php echo esc_html($job['company']); ?></span> <span class="period"><?php echo esc_html($job['period']); ?></span> </div> <?php if (!empty($job['description'])): ?> <div class="job-description"> <p><?php echo nl2br(esc_html($job['description'])); ?></p> </div> <?php endif; ?> <?php if (!empty($job['achievements'])): ?> <ul class="achievements"> <?php foreach ($job['achievements'] as $achievement): ?> <?php if (!empty(trim($achievement))): ?> <li><?php echo esc_html(trim($achievement)); ?></li> <?php endif; ?> <?php endforeach; ?> </ul> <?php endif; ?> </div> <?php endforeach; ?> </div> </section> <?php endif; ?> <!-- 教育背景 --> <?php if (!empty($data['education'])): ?> <section class="resume-section education"> <h3 class="section-title">教育背景</h3> <div class="section-content"> <?php foreach ($data['education'] as $edu): ?> <div class="education-item"> <h4 class="degree"><?php echo esc_html($edu['degree']); ?></h4> <span class="institution"><?php echo esc_html($edu['institution']); ?></span> <span class="period"><?php echo esc_html($edu['period']); ?></span> <?php if (!empty($edu['field'])): ?> <div class="field-of-study">专业:<?php echo esc_html($edu['field']); ?></div> <?php endif; ?> <?php if (!empty($edu['gpa'])): ?> <div class="gpa">GPA:<?php echo esc_html($edu['gpa']); ?></div> <?php endif; ?> </div> <?php endforeach; ?> </div> </section> <?php endif; ?> <!-- 技能专长 --> <?php if (!empty($data['skills']['technical'])): ?> <section class="resume-section skills"> <h3 class="section-title">技能专长</h3> <div class="section-content"> <div class="skills-grid"> <?php foreach ($data['skills']['technical'] as $skill): ?> <div class="skill-item"> <span class="skill-name"><?php echo esc_html($skill['name']); ?></span> <div class="skill-level"> <?php for ($i = 1; $i <= 5; $i++): ?> <span class="level-dot <?php echo $i <= $this->get_level_number($skill['level']) ? 'filled' : ''; ?>"></span> <?php endfor; ?> </div> </div> <?php endforeach; ?> </div> </div> </section> <?php endif; ?> </div> </body></html> ### 4.3 PDF导出功能 // includes/class-pdf-generator.phpclass Resume_Builder_PDF_Generator { private $pdf; private $template_engine; public function __construct() { // 引入TCPDF库 require_once RB_PLUGIN_PATH . 'vendor/tcpdf/tcpdf.php'; $this->template_engine = new Resume_Builder_Template_Engine(); } public function generate_pdf($resume_id, $template_name = 'classic') { // 获取简历数据 $resume_data = $this->get_resume_data($resume_id); if (!$resume_data) { return false; } // 创建PDF实例 $this->pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); // 设置文档信息 $this->pdf->SetCreator('WordPress Resume Builder'); $this->pdf->SetAuthor($resume_data['personal']['full_name'] ?? ''); $this->pdf->SetTitle('简历 - ' . ($resume_data['personal']['full_name'] ?? '')); $this->pdf->SetSubject('个人简历'); // 设置页眉页脚 $this->pdf->setHeaderFont([PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN]); $this->pdf->setFooterFont([PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA]); // 设置默认等宽字体 $this->pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED); // 设置边距 $this->pdf->SetMargins(15, 20, 15); $this->pdf->SetHeaderMargin(5); $this->pdf->SetFooterMargin(10); // 设置自动分页 $this->pdf->SetAutoPageBreak(TRUE, 15

发表评论

手把手教学,为你的网站添加在线白板与团队头脑风暴功能

手把手教学:为你的WordPress网站添加在线白板与团队头脑风暴功能 引言:为什么你的网站需要协作工具? 在当今数字化工作环境中,远程协作已成为常态。无论是教育机构、创意团队还是企业项目组,都需要高效的在线协作工具来促进沟通和创意产出。然而,许多专业协作工具价格昂贵,且难以与现有网站无缝集成。 本文将指导你通过WordPress代码二次开发,为你的网站添加在线白板和团队头脑风暴功能。这不仅能为你的用户提供价值,还能显著提升网站的互动性和专业性。我们将从零开始,逐步构建一个功能完整的协作系统。 第一部分:准备工作与环境搭建 1.1 理解WordPress开发基础 在开始之前,你需要具备以下基础知识: 基本的PHP编程能力 HTML/CSS/JavaScript前端知识 WordPress主题或插件开发经验 对REST API的基本了解 如果你已经熟悉这些技术,可以直接进入下一部分。如果你是初学者,建议先学习WordPress官方开发文档。 1.2 开发环境配置 首先,确保你有一个本地开发环境: 安装XAMPP、MAMP或Local by Flywheel 下载最新版WordPress并安装 启用调试模式(在wp-config.php中添加define('WP_DEBUG', true);) 安装代码编辑器(如VS Code、Sublime Text或PHPStorm) 1.3 创建自定义插件 我们将创建一个独立插件来实现功能,而不是修改主题文件,这样可以确保功能独立且易于维护。 创建插件目录结构: wp-content/plugins/team-whiteboard/ ├── team-whiteboard.php ├── includes/ │ ├── class-database.php │ ├── class-whiteboard.php │ └── class-brainstorm.php ├── assets/ │ ├── css/ │ ├── js/ │ └── images/ ├── templates/ └── vendor/ 第二部分:构建在线白板核心功能 2.1 数据库设计与实现 首先,我们需要设计数据库表来存储白板数据。在includes/class-database.php中: <?php class TeamWhiteboard_Database { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_action('init', array($this, 'create_tables')); } public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'team_whiteboards'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, content longtext, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, status varchar(20) DEFAULT 'active', settings text, PRIMARY KEY (id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 创建白板元素表 $elements_table = $wpdb->prefix . 'whiteboard_elements'; $sql_elements = "CREATE TABLE IF NOT EXISTS $elements_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, whiteboard_id mediumint(9) NOT NULL, element_type varchar(50) NOT NULL, element_data text NOT NULL, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, position_x float DEFAULT 0, position_y float DEFAULT 0, z_index int DEFAULT 0, PRIMARY KEY (id), FOREIGN KEY (whiteboard_id) REFERENCES $table_name(id) ON DELETE CASCADE ) $charset_collate;"; dbDelta($sql_elements); } } ?> 2.2 白板前端界面开发 接下来,我们创建白板的HTML结构和CSS样式。在templates/whiteboard.php中: <div class="team-whiteboard-container"> <div class="whiteboard-header"> <h2 id="whiteboard-title">新白板</h2> <div class="whiteboard-actions"> <button id="save-whiteboard" class="btn btn-primary">保存</button> <button id="clear-whiteboard" class="btn btn-secondary">清空</button> <button id="export-whiteboard" class="btn btn-success">导出</button> </div> </div> <div class="whiteboard-toolbar"> <div class="tool-group"> <button class="tool-btn active" data-tool="select" title="选择工具"> <i class="fas fa-mouse-pointer"></i> </button> <button class="tool-btn" data-tool="pen" title="画笔"> <i class="fas fa-pen"></i> </button> <button class="tool-btn" data-tool="line" title="直线"> <i class="fas fa-minus"></i> </button> <button class="tool-btn" data-tool="rectangle" title="矩形"> <i class="fas fa-square"></i> </button> <button class="tool-btn" data-tool="circle" title="圆形"> <i class="fas fa-circle"></i> </button> <button class="tool-btn" data-tool="text" title="文本"> <i class="fas fa-font"></i> </button> </div> <div class="tool-group"> <input type="color" id="color-picker" value="#000000"> <input type="range" id="brush-size" min="1" max="50" value="3"> <span id="brush-size-value">3px</span> </div> <div class="tool-group"> <button class="tool-btn" data-action="undo" title="撤销"> <i class="fas fa-undo"></i> </button> <button class="tool-btn" data-action="redo" title="重做"> <i class="fas fa-redo"></i> </button> <button class="tool-btn" data-action="delete" title="删除选中"> <i class="fas fa-trash"></i> </button> </div> </div> <div class="whiteboard-wrapper"> <canvas id="whiteboard-canvas"></canvas> <div class="whiteboard-grid"></div> </div> <div class="whiteboard-sidebar"> <div class="sidebar-section"> <h4>参与者</h4> <ul id="participants-list"> <!-- 参与者列表将通过JavaScript动态生成 --> </ul> </div> <div class="sidebar-section"> <h4>聊天</h4> <div id="chat-messages"></div> <div class="chat-input"> <input type="text" id="chat-input" placeholder="输入消息..."> <button id="send-chat">发送</button> </div> </div> </div> </div> 2.3 白板画布与绘图功能实现 现在,我们使用JavaScript和Canvas API实现白板的绘图功能。在assets/js/whiteboard.js中: class Whiteboard { constructor(canvasId) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.isDrawing = false; this.currentTool = 'pen'; this.currentColor = '#000000'; this.brushSize = 3; this.lastX = 0; this.lastY = 0; this.history = []; this.historyIndex = -1; this.elements = []; this.selectedElement = null; this.initCanvas(); this.setupEventListeners(); this.setupWebSocket(); } initCanvas() { // 设置画布尺寸 this.resizeCanvas(); window.addEventListener('resize', () => this.resizeCanvas()); // 设置初始样式 this.ctx.lineCap = 'round'; this.ctx.lineJoin = 'round'; this.ctx.strokeStyle = this.currentColor; this.ctx.lineWidth = this.brushSize; // 绘制网格背景 this.drawGrid(); } resizeCanvas() { const container = this.canvas.parentElement; this.canvas.width = container.clientWidth; this.canvas.height = container.clientHeight; this.drawGrid(); this.redraw(); } drawGrid() { const gridSize = 20; const width = this.canvas.width; const height = this.canvas.height; this.ctx.save(); this.ctx.strokeStyle = '#f0f0f0'; this.ctx.lineWidth = 1; // 绘制垂直线 for (let x = 0; x <= width; x += gridSize) { this.ctx.beginPath(); this.ctx.moveTo(x, 0); this.ctx.lineTo(x, height); this.ctx.stroke(); } // 绘制水平线 for (let y = 0; y <= height; y += gridSize) { this.ctx.beginPath(); this.ctx.moveTo(0, y); this.ctx.lineTo(width, y); this.ctx.stroke(); } this.ctx.restore(); } setupEventListeners() { // 鼠标事件 this.canvas.addEventListener('mousedown', (e) => this.startDrawing(e)); this.canvas.addEventListener('mousemove', (e) => this.draw(e)); this.canvas.addEventListener('mouseup', () => this.stopDrawing()); this.canvas.addEventListener('mouseout', () => this.stopDrawing()); // 触摸事件(移动设备支持) this.canvas.addEventListener('touchstart', (e) => { e.preventDefault(); const touch = e.touches[0]; this.startDrawing(touch); }); this.canvas.addEventListener('touchmove', (e) => { e.preventDefault(); const touch = e.touches[0]; this.draw(touch); }); this.canvas.addEventListener('touchend', () => this.stopDrawing()); // 工具选择 document.querySelectorAll('.tool-btn[data-tool]').forEach(btn => { btn.addEventListener('click', (e) => { document.querySelectorAll('.tool-btn[data-tool]').forEach(b => b.classList.remove('active')); e.target.classList.add('active'); this.currentTool = e.target.dataset.tool; }); }); // 颜色选择 document.getElementById('color-picker').addEventListener('change', (e) => { this.currentColor = e.target.value; this.ctx.strokeStyle = this.currentColor; }); // 笔刷大小 document.getElementById('brush-size').addEventListener('input', (e) => { this.brushSize = e.target.value; document.getElementById('brush-size-value').textContent = `${this.brushSize}px`; this.ctx.lineWidth = this.brushSize; }); // 动作按钮 document.querySelector('[data-action="undo"]').addEventListener('click', () => this.undo()); document.querySelector('[data-action="redo"]').addEventListener('click', () => this.redo()); document.querySelector('[data-action="delete"]').addEventListener('click', () => this.deleteSelected()); } startDrawing(e) { this.isDrawing = true; const rect = this.canvas.getBoundingClientRect(); this.lastX = e.clientX - rect.left; this.lastY = e.clientY - rect.top; // 保存当前状态到历史记录 this.saveState(); // 根据工具类型执行不同操作 if (this.currentTool === 'text') { this.addTextElement(this.lastX, this.lastY); } } draw(e) { if (!this.isDrawing) return; const rect = this.canvas.getBoundingClientRect(); const currentX = e.clientX - rect.left; const currentY = e.clientY - rect.top; switch (this.currentTool) { case 'pen': this.drawFreehand(currentX, currentY); break; case 'line': this.drawLine(currentX, currentY); break; case 'rectangle': this.drawRectangle(currentX, currentY); break; case 'circle': this.drawCircle(currentX, currentY); break; } this.lastX = currentX; this.lastY = currentY; } drawFreehand(x, y) { this.ctx.beginPath(); this.ctx.moveTo(this.lastX, this.lastY); this.ctx.lineTo(x, y); this.ctx.stroke(); // 发送绘图数据到服务器 this.sendDrawingData({ type: 'freehand', from: {x: this.lastX, y: this.lastY}, to: {x, y}, color: this.currentColor, size: this.brushSize }); } stopDrawing() { this.isDrawing = false; this.ctx.beginPath(); } saveState() { // 只保留最近50个状态 if (this.historyIndex < this.history.length - 1) { this.history = this.history.slice(0, this.historyIndex + 1); } const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); this.history.push(imageData); this.historyIndex++; // 限制历史记录数量 if (this.history.length > 50) { this.history.shift(); this.historyIndex--; } } undo() { if (this.historyIndex > 0) { this.historyIndex--; const imageData = this.history[this.historyIndex]; this.ctx.putImageData(imageData, 0, 0); } } redo() { if (this.historyIndex < this.history.length - 1) { this.historyIndex++; const imageData = this.history[this.historyIndex]; this.ctx.putImageData(imageData, 0, 0); } } setupWebSocket() { // 这里将实现WebSocket连接,用于实时协作 // 实际实现需要服务器端WebSocket支持 console.log('WebSocket连接将在服务器端配置后实现'); } sendDrawingData(data) { // 发送绘图数据到服务器,以便其他参与者可以看到 if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify({ type: 'drawing', data: data, timestamp: Date.now(), userId: window.userId || 'anonymous' })); } } redraw() { // 重绘画布上的所有元素 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.drawGrid(); // 重绘所有保存的元素 this.elements.forEach(element => { this.drawElement(element); }); } drawElement(element) { // 根据元素类型绘制 switch (element.type) { case 'freehand': this.ctx.beginPath(); this.ctx.moveTo(element.from.x, element.from.y); this.ctx.lineTo(element.to.x, element.to.y); this.ctx.strokeStyle = element.color; this.ctx.lineWidth = element.size; this.ctx.stroke(); break; // 其他元素类型的绘制逻辑 } } } // 初始化白板 document.addEventListener('DOMContentLoaded', () => { const whiteboard = new Whiteboard('whiteboard-canvas'); }); 第三部分:实现团队头脑风暴功能 3.1 头脑风暴数据库设计 在includes/class-brainstorm.php中,我们扩展数据库设计: <?php class TeamWhiteboard_Brainstorm { public function create_brainstorm_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 头脑风暴会话表 $sessions_table = $wpdb->prefix . 'brainstorm_sessions'; $sql_sessions = "CREATE TABLE IF NOT EXISTS $sessions_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, description text, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, status varchar(20) DEFAULT 'active', settings text, PRIMARY KEY (id) ) $charset_collate;"; // 想法/便签表 $ideas_table = $wpdb->prefix . 'brainstorm_ideas'; $sql_ideas = "CREATE TABLE IF NOT EXISTS $ideas_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, session_id mediumint(9) NOT NULL, content text NOT NULL, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, category varchar(50), votes int DEFAULT 0, position_x float DEFAULT 0, position_y float DEFAULT 0, color varchar(20) DEFAULT '#FFFF99', PRIMARY KEY (id), FOREIGN KEY (session_id) REFERENCES $sessions_table(id) ON DELETE CASCADE ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql_sessions); dbDelta($sql_ideas); } } ?> 3.2 头脑风暴前端界面 创建templates/brainstorm.php: <div class="brainstorm-container"> <div class="brainstorm-header"> 手把手教学:为你的WordPress网站添加在线白板与团队头脑风暴功能(续) 第三部分:实现团队头脑风暴功能(续) 3.2 头脑风暴前端界面(续) <div class="brainstorm-container"> <div class="brainstorm-header"> <h2 id="session-title">头脑风暴会议</h2> <div class="session-info"> <span id="participant-count">0 参与者</span> <span id="idea-count">0 个想法</span> </div> <div class="session-controls"> <button id="new-idea-btn" class="btn btn-primary"> <i class="fas fa-plus"></i> 添加想法 </button> <button id="timer-toggle" class="btn btn-secondary"> <i class="fas fa-clock"></i> 计时器 </button> <button id="export-ideas" class="btn btn-success"> <i class="fas fa-download"></i> 导出 </button> </div> </div> <div class="brainstorm-main"> <!-- 左侧工具栏 --> <div class="brainstorm-sidebar"> <div class="sidebar-section"> <h4>分类</h4> <div class="category-list"> <div class="category-item active" data-category="all"> <span class="category-color" style="background:#f0f0f0"></span> <span>全部想法</span> </div> <div class="category-item" data-category="feature"> <span class="category-color" style="background:#FF9999"></span> <span>功能建议</span> </div> <div class="category-item" data-category="improvement"> <span class="category-color" style="background:#99FF99"></span> <span>改进建议</span> </div> <div class="category-item" data-category="question"> <span class="category-color" style="background:#9999FF"></span> <span>问题反馈</span> </div> </div> <div class="add-category"> <input type="text" id="new-category-name" placeholder="新分类名称"> <input type="color" id="new-category-color" value="#CCCCCC"> <button id="add-category-btn">添加</button> </div> </div> <div class="sidebar-section"> <h4>投票设置</h4> <div class="voting-settings"> <label>每人票数:</label> <input type="number" id="votes-per-user" min="1" max="20" value="5"> <div class="voting-status"> <p>已用票数:<span id="used-votes">0</span>/<span id="total-votes">5</span></p> </div> </div> </div> <div class="sidebar-section"> <h4>计时器</h4> <div class="timer-container"> <div class="timer-display" id="timer-display">10:00</div> <div class="timer-controls"> <button id="start-timer">开始</button> <button id="pause-timer">暂停</button> <button id="reset-timer">重置</button> </div> <div class="timer-presets"> <button class="timer-preset" data-time="300">5分钟</button> <button class="timer-preset" data-time="600">10分钟</button> <button class="timer-preset" data-time="900">15分钟</button> </div> </div> </div> </div> <!-- 主工作区 --> <div class="brainstorm-workspace"> <div class="workspace-tools"> <button class="workspace-tool" data-action="arrange" title="自动排列"> <i class="fas fa-th"></i> </button> <button class="workspace-tool" data-action="cluster" title="按分类分组"> <i class="fas fa-object-group"></i> </button> <button class="workspace-tool" data-action="clear" title="清空工作区"> <i class="fas fa-broom"></i> </button> </div> <div class="ideas-container" id="ideas-container"> <!-- 想法卡片将通过JavaScript动态生成 --> </div> </div> <!-- 右侧聊天和参与者面板 --> <div class="brainstorm-participants"> <div class="participants-section"> <h4>参与者 (<span id="active-participants">0</span>)</h4> <ul id="participants-list"> <!-- 参与者列表 --> </ul> </div> <div class="chat-section"> <h4>讨论区</h4> <div class="chat-messages" id="brainstorm-chat"> <!-- 聊天消息 --> </div> <div class="chat-input"> <input type="text" id="brainstorm-chat-input" placeholder="输入消息..."> <button id="send-brainstorm-chat"> <i class="fas fa-paper-plane"></i> </button> </div> </div> </div> </div> </div> 3.3 头脑风暴JavaScript实现 创建assets/js/brainstorm.js: class BrainstormSession { constructor(sessionId) { this.sessionId = sessionId; this.ideas = []; this.categories = []; this.participants = []; this.currentUser = null; this.votesUsed = 0; this.votesTotal = 5; this.timer = null; this.timerSeconds = 600; // 默认10分钟 this.isTimerRunning = false; this.init(); this.loadSessionData(); this.setupEventListeners(); this.connectWebSocket(); } init() { // 初始化用户信息 this.currentUser = { id: window.userId || 'user_' + Math.random().toString(36).substr(2, 9), name: window.userName || '匿名用户', color: this.getRandomColor() }; // 初始化默认分类 this.categories = [ { id: 'all', name: '全部想法', color: '#f0f0f0' }, { id: 'feature', name: '功能建议', color: '#FF9999' }, { id: 'improvement', name: '改进建议', color: '#99FF99' }, { id: 'question', name: '问题反馈', color: '#9999FF' } ]; // 更新UI this.updateParticipantCount(); this.renderCategories(); } loadSessionData() { // 从服务器加载会话数据 fetch(`/wp-json/team-whiteboard/v1/brainstorm/${this.sessionId}`) .then(response => response.json()) .then(data => { this.ideas = data.ideas || []; this.categories = data.categories || this.categories; this.participants = data.participants || []; this.renderIdeas(); this.renderParticipants(); }) .catch(error => { console.error('加载会话数据失败:', error); }); } setupEventListeners() { // 添加想法按钮 document.getElementById('new-idea-btn').addEventListener('click', () => { this.showIdeaForm(); }); // 分类点击事件 document.querySelectorAll('.category-item').forEach(item => { item.addEventListener('click', (e) => { const category = e.currentTarget.dataset.category; this.filterIdeasByCategory(category); // 更新活动状态 document.querySelectorAll('.category-item').forEach(i => { i.classList.remove('active'); }); e.currentTarget.classList.add('active'); }); }); // 添加分类 document.getElementById('add-category-btn').addEventListener('click', () => { this.addCategory(); }); // 投票设置 document.getElementById('votes-per-user').addEventListener('change', (e) => { this.votesTotal = parseInt(e.target.value); document.getElementById('total-votes').textContent = this.votesTotal; }); // 计时器控制 document.getElementById('start-timer').addEventListener('click', () => { this.startTimer(); }); document.getElementById('pause-timer').addEventListener('click', () => { this.pauseTimer(); }); document.getElementById('reset-timer').addEventListener('click', () => { this.resetTimer(); }); // 预设时间按钮 document.querySelectorAll('.timer-preset').forEach(btn => { btn.addEventListener('click', (e) => { const seconds = parseInt(e.currentTarget.dataset.time); this.setTimer(seconds); }); }); // 工作区工具 document.querySelectorAll('.workspace-tool').forEach(tool => { tool.addEventListener('click', (e) => { const action = e.currentTarget.dataset.action; this.handleWorkspaceAction(action); }); }); // 聊天发送 document.getElementById('send-brainstorm-chat').addEventListener('click', () => { this.sendChatMessage(); }); document.getElementById('brainstorm-chat-input').addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.sendChatMessage(); } }); } showIdeaForm() { // 创建想法表单模态框 const modal = document.createElement('div'); modal.className = 'idea-form-modal'; modal.innerHTML = ` <div class="modal-content"> <h3>添加新想法</h3> <div class="form-group"> <label>想法内容</label> <textarea id="idea-content" rows="4" placeholder="输入你的想法..."></textarea> </div> <div class="form-group"> <label>分类</label> <select id="idea-category"> ${this.categories.filter(c => c.id !== 'all').map(c => `<option value="${c.id}">${c.name}</option>` ).join('')} </select> </div> <div class="form-group"> <label>颜色</label> <input type="color" id="idea-color" value="#FFFF99"> </div> <div class="modal-actions"> <button id="cancel-idea" class="btn btn-secondary">取消</button> <button id="submit-idea" class="btn btn-primary">提交</button> </div> </div> `; document.body.appendChild(modal); // 事件监听 document.getElementById('cancel-idea').addEventListener('click', () => { document.body.removeChild(modal); }); document.getElementById('submit-idea').addEventListener('click', () => { this.submitIdea(); document.body.removeChild(modal); }); } submitIdea() { const content = document.getElementById('idea-content').value; const category = document.getElementById('idea-category').value; const color = document.getElementById('idea-color').value; if (!content.trim()) { alert('请输入想法内容'); return; } const idea = { id: 'idea_' + Date.now(), content: content, category: category, color: color, createdBy: this.currentUser, createdAt: new Date().toISOString(), votes: 0, position: { x: Math.random() * 600, y: Math.random() * 400 } }; this.ideas.push(idea); this.renderIdea(idea); this.updateIdeaCount(); // 发送到服务器 this.saveIdea(idea); // 广播给其他参与者 this.broadcast({ type: 'new_idea', data: idea }); } renderIdeas() { const container = document.getElementById('ideas-container'); container.innerHTML = ''; this.ideas.forEach(idea => { this.renderIdea(idea); }); this.updateIdeaCount(); } renderIdea(idea) { const container = document.getElementById('ideas-container'); const ideaElement = document.createElement('div'); ideaElement.className = 'idea-card'; ideaElement.dataset.id = idea.id; ideaElement.dataset.category = idea.category; ideaElement.style.left = idea.position.x + 'px'; ideaElement.style.top = idea.position.y + 'px'; ideaElement.style.backgroundColor = idea.color; ideaElement.innerHTML = ` <div class="idea-header"> <span class="idea-author">${idea.createdBy.name}</span> <span class="idea-time">${this.formatTime(idea.createdAt)}</span> </div> <div class="idea-content">${this.escapeHtml(idea.content)}</div> <div class="idea-footer"> <button class="vote-btn" data-id="${idea.id}"> <i class="fas fa-thumbs-up"></i> <span class="vote-count">${idea.votes}</span> </button> <button class="delete-btn" data-id="${idea.id}"> <i class="fas fa-trash"></i> </button> </div> `; container.appendChild(ideaElement); // 添加拖拽功能 this.makeDraggable(ideaElement, idea); // 投票按钮事件 ideaElement.querySelector('.vote-btn').addEventListener('click', (e) => { e.stopPropagation(); this.voteForIdea(idea.id); }); // 删除按钮事件 ideaElement.querySelector('.delete-btn').addEventListener('click', (e) => { e.stopPropagation(); if (confirm('确定要删除这个想法吗?')) { this.deleteIdea(idea.id); } }); } makeDraggable(element, idea) { let isDragging = false; let offsetX, offsetY; element.addEventListener('mousedown', startDrag); element.addEventListener('touchstart', startDrag); function startDrag(e) { isDragging = true; if (e.type === 'mousedown') { offsetX = e.clientX - element.offsetLeft; offsetY = e.clientY - element.offsetTop; document.addEventListener('mousemove', drag); document.addEventListener('mouseup', stopDrag); } else { const touch = e.touches[0]; offsetX = touch.clientX - element.offsetLeft; offsetY = touch.clientY - element.offsetTop; document.addEventListener('touchmove', drag); document.addEventListener('touchend', stopDrag); } e.preventDefault(); } const drag = (e) => { if (!isDragging) return; let clientX, clientY; if (e.type === 'mousemove') { clientX = e.clientX; clientY = e.clientY; } else { clientX = e.touches[0].clientX; clientY = e.touches[0].clientY; } const container = document.getElementById('ideas-container'); const maxX = container.clientWidth - element.offsetWidth; const maxY = container.clientHeight - element.offsetHeight; let x = clientX - offsetX; let y = clientY - offsetY; // 限制在容器内 x = Math.max(0, Math.min(x, maxX)); y = Math.max(0, Math.min(y, maxY)); element.style.left = x + 'px'; element.style.top = y + 'px'; // 更新想法位置 idea.position.x = x; idea.position.y = y; // 广播位置更新 this.broadcast({ type: 'move_idea', data: { id: idea.id, position: idea.position } }); }.bind(this); function stopDrag() { isDragging = false; document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', stopDrag); document.removeEventListener('touchmove', drag); document.removeEventListener('touchend', stopDrag); } } voteForIdea(ideaId) { if (this.votesUsed >= this.votesTotal) { alert('你的投票次数已用完!'); return; } const idea = this.ideas.find(i => i.id === ideaId); if (!idea) return; idea.votes++; this.votesUsed++; // 更新UI const voteBtn = document.querySelector(`.vote-btn[data-id="${ideaId}"] .vote-count`); if (voteBtn) { voteBtn.textContent = idea.votes; } document.getElementById('used-votes').textContent = this.votesUsed; // 发送到服务器 this.updateIdeaVotes(ideaId, idea.votes); // 广播投票 this.broadcast({ type: 'vote', data: { ideaId: ideaId, votes: idea.votes } }); } filterIdeasByCategory(category) { const ideas = document.querySelectorAll('.idea-card'); ideas.forEach(idea => { if (category === 'all' || idea.dataset.category === category) { idea.style.display = 'block'; } else { idea.style.display = 'none'; } }); } addCategory() { const nameInput = document.getElementById('new-category-name'); const colorInput = document.getElementById('new-category-color'); const name = nameInput.value.trim(); const color = colorInput.value; if (!name) { alert('请输入分类名称'); return; }

发表评论

WordPress 插件开发教程,集成网站实时翻译与多语言聊天工具

WordPress插件开发教程:集成网站实时翻译与多语言聊天工具 引言:WordPress插件开发的无限可能 在当今全球化的互联网环境中,多语言支持和实时互动功能已成为网站提升用户体验的关键要素。WordPress作为全球最流行的内容管理系统,其强大的插件架构为开发者提供了无限的可能性。本教程将深入探讨如何通过WordPress插件开发,集成网站实时翻译与多语言聊天工具,实现常用互联网小工具功能。 WordPress插件开发不仅仅是简单的功能添加,更是对现有系统进行二次开发,创造独特价值的过程。通过本教程,您将学习到如何从零开始构建一个功能全面的插件,将实时翻译和聊天工具无缝集成到您的WordPress网站中,从而提升网站的国际化水平和用户互动体验。 第一章:WordPress插件开发基础 1.1 WordPress插件架构概述 WordPress插件系统基于PHP语言构建,采用事件驱动的钩子(Hooks)机制。插件通过动作(Actions)和过滤器(Filters)与WordPress核心进行交互,这种设计模式使得开发者可以在不修改核心代码的情况下扩展功能。 每个WordPress插件至少需要一个主文件,其中包含插件头部信息,用于向WordPress系统标识插件: <?php /** * Plugin Name: 多语言实时工具套件 * Plugin URI: https://yourwebsite.com/multilingual-tools * Description: 集成实时翻译与多语言聊天功能的WordPress插件 * Version: 1.0.0 * Author: 您的名字 * License: GPL v2 or later * Text Domain: multilingual-tools */ 1.2 开发环境搭建 在开始插件开发前,需要搭建合适的开发环境: 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel 代码编辑器:VS Code、PHPStorm或Sublime Text 调试工具:安装Query Monitor、Debug Bar等调试插件 版本控制:使用Git进行代码版本管理 1.3 插件文件结构规划 合理的文件结构是插件可维护性的基础: multilingual-tools/ ├── multilingual-tools.php # 主插件文件 ├── includes/ # 核心功能文件 │ ├── class-translation-engine.php │ ├── class-chat-system.php │ └── class-admin-settings.php ├── assets/ # 静态资源 │ ├── css/ │ ├── js/ │ └── images/ ├── languages/ # 国际化文件 ├── templates/ # 前端模板 └── vendor/ # 第三方库 第二章:实时翻译引擎集成 2.1 翻译API选择与比较 集成实时翻译功能首先需要选择合适的翻译API。目前市场上有多种选择: Google Cloud Translation API:准确度高,支持100多种语言 Microsoft Azure Translator:企业级解决方案,稳定性好 DeepL API:欧洲语言翻译质量优秀 百度翻译API:中文相关翻译效果较好 本教程将以Google Cloud Translation API为例,但代码设计将保持灵活性,便于切换不同服务商。 2.2 翻译功能类设计 创建一个翻译引擎类,封装所有翻译相关功能: class MLT_Translation_Engine { private $api_key; private $api_endpoint = 'https://translation.googleapis.com/language/translate/v2'; public function __construct($api_key) { $this->api_key = $api_key; } /** * 检测文本语言 */ public function detect_language($text) { $response = wp_remote_post($this->api_endpoint . '/detect', [ 'body' => [ 'q' => $text, 'key' => $this->api_key ] ]); if (is_wp_error($response)) { return false; } $body = json_decode(wp_remote_retrieve_body($response), true); return isset($body['data']['detections'][0][0]['language']) ? $body['data']['detections'][0][0]['language'] : false; } /** * 执行翻译 */ public function translate($text, $target_language, $source_language = null) { $args = [ 'q' => $text, 'target' => $target_language, 'key' => $this->api_key ]; if ($source_language) { $args['source'] = $source_language; } $response = wp_remote_post($this->api_endpoint, [ 'body' => $args ]); if (is_wp_error($response)) { return $text; // 翻译失败时返回原文 } $body = json_decode(wp_remote_retrieve_body($response), true); return isset($body['data']['translations'][0]['translatedText']) ? $body['data']['translations'][0]['translatedText'] : $text; } /** * 批量翻译 */ public function translate_batch($texts, $target_language, $source_language = null) { $results = []; // 免费API通常有频率限制,这里添加延迟避免超限 foreach ($texts as $index => $text) { $results[$index] = $this->translate($text, $target_language, $source_language); // 每翻译5个文本暂停1秒 if ($index % 5 === 0 && $index > 0) { sleep(1); } } return $results; } } 2.3 前端翻译界面实现 在前端添加翻译控件,让用户可以选择翻译页面内容: class MLT_Frontend_Translator { public function __construct() { add_action('wp_footer', [$this, 'add_translation_widget']); add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']); add_action('wp_ajax_mlt_translate_content', [$this, 'ajax_translate_content']); add_action('wp_ajax_nopriv_mlt_translate_content', [$this, 'ajax_translate_content']); } public function enqueue_scripts() { wp_enqueue_style( 'mlt-frontend-style', plugin_dir_url(__FILE__) . '../assets/css/frontend.css', [], '1.0.0' ); wp_enqueue_script( 'mlt-frontend-script', plugin_dir_url(__FILE__) . '../assets/js/frontend.js', ['jquery'], '1.0.0', true ); wp_localize_script('mlt-frontend-script', 'mlt_ajax', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('mlt_translate_nonce') ]); } public function add_translation_widget() { if (!is_singular()) return; $languages = [ 'en' => 'English', 'es' => 'Español', 'fr' => 'Français', 'de' => 'Deutsch', 'zh-CN' => '中文(简体)', 'ja' => '日本語' ]; include plugin_dir_path(__FILE__) . '../templates/translation-widget.php'; } public function ajax_translate_content() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'mlt_translate_nonce')) { wp_die('安全验证失败'); } $post_id = intval($_POST['post_id']); $target_lang = sanitize_text_field($_POST['target_lang']); $post = get_post($post_id); if (!$post) { wp_send_json_error('文章不存在'); } // 获取翻译引擎实例 $api_key = get_option('mlt_google_api_key'); $translator = new MLT_Translation_Engine($api_key); // 翻译标题和内容 $translated_title = $translator->translate($post->post_title, $target_lang); $translated_content = $translator->translate($post->post_content, $target_lang); wp_send_json_success([ 'title' => $translated_title, 'content' => apply_filters('the_content', $translated_content) ]); } } 第三章:多语言聊天系统开发 3.1 聊天系统架构设计 多语言聊天系统需要处理实时通信、消息存储和语言转换。我们将采用以下架构: 前端界面:使用WebSocket或AJAX轮询实现实时通信 消息处理:PHP处理消息接收、存储和转发 数据库设计:创建自定义表存储聊天记录 翻译集成:在消息发送/接收时自动翻译 3.2 数据库设计与消息存储 创建聊天消息数据库表: class MLT_Chat_Database { public static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'mlt_chat_messages'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, session_id varchar(100) NOT NULL, sender_id bigint(20) DEFAULT 0, sender_type enum('user','admin') DEFAULT 'user', message_text text NOT NULL, original_language varchar(10) DEFAULT '', target_language varchar(10) DEFAULT '', translated_text text, is_translated tinyint(1) DEFAULT 0, timestamp datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY session_id (session_id), KEY timestamp (timestamp) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } public static function save_message($data) { global $wpdb; $table_name = $wpdb->prefix . 'mlt_chat_messages'; return $wpdb->insert($table_name, $data); } public static function get_chat_history($session_id, $limit = 50) { global $wpdb; $table_name = $wpdb->prefix . 'mlt_chat_messages'; return $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name WHERE session_id = %s ORDER BY timestamp ASC LIMIT %d", $session_id, $limit )); } } 3.3 实时通信实现 使用AJAX轮询实现实时聊天功能(为简化示例,生产环境建议使用WebSocket): class MLT_Chat_System { private $translation_engine; public function __construct() { $api_key = get_option('mlt_google_api_key'); $this->translation_engine = new MLT_Translation_Engine($api_key); add_action('wp_ajax_mlt_send_message', [$this, 'handle_send_message']); add_action('wp_ajax_nopriv_mlt_send_message', [$this, 'handle_send_message']); add_action('wp_ajax_mlt_get_messages', [$this, 'handle_get_messages']); add_action('wp_ajax_nopriv_mlt_get_messages', [$this, 'handle_get_messages']); } public function handle_send_message() { // 验证nonce和安全检查 if (!wp_verify_nonce($_POST['nonce'], 'mlt_chat_nonce')) { wp_send_json_error('安全验证失败'); } $session_id = sanitize_text_field($_POST['session_id']); $message = sanitize_textarea_field($_POST['message']); $user_lang = sanitize_text_field($_POST['user_lang']); $target_lang = sanitize_text_field($_POST['target_lang']); // 检测消息语言 $detected_lang = $this->translation_engine->detect_language($message); // 如果需要翻译,则翻译消息 $translated_message = $message; if ($detected_lang && $detected_lang !== $target_lang) { $translated_message = $this->translation_engine->translate( $message, $target_lang, $detected_lang ); } // 保存消息到数据库 $message_id = MLT_Chat_Database::save_message([ 'session_id' => $session_id, 'sender_type' => 'user', 'message_text' => $message, 'original_language' => $detected_lang, 'target_language' => $target_lang, 'translated_text' => $translated_message, 'is_translated' => ($detected_lang !== $target_lang) ? 1 : 0 ]); if ($message_id) { // 这里可以添加通知管理员新消息的逻辑 wp_send_json_success([ 'message_id' => $message_id, 'translated_message' => $translated_message, 'timestamp' => current_time('mysql') ]); } else { wp_send_json_error('消息发送失败'); } } public function handle_get_messages() { $session_id = sanitize_text_field($_POST['session_id']); $last_message_id = intval($_POST['last_message_id']); global $wpdb; $table_name = $wpdb->prefix . 'mlt_chat_messages'; // 获取新消息 $new_messages = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name WHERE session_id = %s AND id > %d ORDER BY timestamp ASC", $session_id, $last_message_id )); wp_send_json_success([ 'messages' => $new_messages, 'count' => count($new_messages) ]); } } 3.4 前端聊天界面 创建美观的聊天界面: <!-- templates/chat-widget.php --> <div id="mlt-chat-widget" class="mlt-chat-container"> <div class="mlt-chat-header"> <h3>多语言在线客服</h3> <div class="mlt-language-selector"> <select id="mlt-chat-language"> <option value="auto">自动检测</option> <option value="zh-CN">中文</option> <option value="en">English</option> <option value="es">Español</option> <option value="fr">Français</option> <option value="de">Deutsch</option> <option value="ja">日本語</option> </select> </div> <button class="mlt-chat-close">&times;</button> </div> <div class="mlt-chat-messages"> <!-- 消息将在这里动态加载 --> <div class="mlt-welcome-message"> <p>您好!我是多语言客服助手。请选择您的语言开始聊天。</p> </div> </div> <div class="mlt-chat-input-area"> <textarea id="mlt-chat-input" placeholder="输入消息... (按Enter发送,Shift+Enter换行)" rows="2" ></textarea> <button id="mlt-send-button">发送</button> </div> </div> <button id="mlt-chat-toggle" class="mlt-chat-toggle-button"> <span class="mlt-chat-icon">💬</span> <span class="mlt-chat-label">在线聊天</span> </button> 第四章:管理后台与设置界面 4.1 插件设置页面 创建完整的插件设置页面,让管理员可以配置API密钥和其他选项: class MLT_Admin_Settings { public function __construct() { add_action('admin_menu', [$this, 'add_admin_menu']); add_action('admin_init', [$this, 'register_settings']); add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']); } public function add_admin_menu() { add_menu_page( '多语言工具设置', '多语言工具', 'manage_options', 'mlt-settings', [$this, 'render_settings_page'], 'dashicons-translation', 80 ); add_submenu_page( 'mlt-settings', '聊天记录', '聊天记录', 'manage_options', 'mlt-chat-logs', [$this, 'render_chat_logs_page'] ); add_submenu_page( 'mlt-settings', '翻译统计', '翻译统计', 'manage_options', 'mlt-translation-stats', [$this, 'render_stats_page'] ); } public function register_settings() { register_setting('mlt_settings_group', 'mlt_google_api_key'); register_setting('mlt_settings_group', 'mlt_default_language'); register_setting('mlt_settings_group', 'mlt_chat_enabled'); register_setting('mlt_settings_group', 'mlt_translation_enabled'); register_setting('mlt_settings_group', 'mlt_supported_languages'); add_settings_section( 'mlt_api_section', 'API设置', [$this, 'render_api_section'], 'mlt-settings' ); add_settings_field( 'mlt_google_api_key', 'Google翻译API密钥', [$this, 'render_api_key_field'], 'mlt-settings', 'mlt_api_section' ); 4.2 设置字段与选项 public function render_api_section() { echo '<p>配置翻译和聊天功能所需的API密钥和基本设置。</p>'; } public function render_api_key_field() { $api_key = get_option('mlt_google_api_key', ''); echo '<input type="password" id="mlt_google_api_key" name="mlt_google_api_key" value="' . esc_attr($api_key) . '" class="regular-text" /> <p class="description">获取Google Cloud Translation API密钥:<a href="https://cloud.google.com/translate/docs/setup" target="_blank">点击这里</a></p>'; } public function render_settings_page() { if (!current_user_can('manage_options')) { return; } // 检查API密钥是否有效 $api_key = get_option('mlt_google_api_key'); $api_status = $this->check_api_status($api_key); ?> <div class="wrap"> <h1><?php echo esc_html(get_admin_page_title()); ?></h1> <?php if ($api_status['valid'] === false): ?> <div class="notice notice-error"> <p><?php echo esc_html($api_status['message']); ?></p> </div> <?php endif; ?> <form action="options.php" method="post"> <?php settings_fields('mlt_settings_group'); do_settings_sections('mlt-settings'); submit_button('保存设置'); ?> </form> <div class="mlt-settings-extra"> <h2>功能测试</h2> <div class="mlt-test-area"> <h3>翻译功能测试</h3> <textarea id="mlt-test-text" rows="3" class="large-text" placeholder="输入要测试翻译的文本..."></textarea> <select id="mlt-test-target-lang"> <option value="en">英语</option> <option value="es">西班牙语</option> <option value="fr">法语</option> <option value="zh-CN">中文</option> </select> <button id="mlt-test-translate" class="button button-secondary">测试翻译</button> <div id="mlt-test-result" class="mlt-test-result"></div> </div> </div> </div> <?php } private function check_api_status($api_key) { if (empty($api_key)) { return ['valid' => false, 'message' => 'API密钥未设置']; } // 简单的API测试 $test_url = 'https://translation.googleapis.com/language/translate/v2/languages?key=' . $api_key; $response = wp_remote_get($test_url); if (is_wp_error($response)) { return ['valid' => false, 'message' => '网络连接错误: ' . $response->get_error_message()]; } $status_code = wp_remote_retrieve_response_code($response); if ($status_code === 200) { return ['valid' => true, 'message' => 'API连接正常']; } elseif ($status_code === 403) { return ['valid' => false, 'message' => 'API密钥无效或未启用翻译API服务']; } else { return ['valid' => false, 'message' => 'API连接失败,状态码: ' . $status_code]; } } public function render_chat_logs_page() { if (!current_user_can('manage_options')) { return; } global $wpdb; $table_name = $wpdb->prefix . 'mlt_chat_messages'; // 分页参数 $per_page = 20; $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1; $offset = ($current_page - 1) * $per_page; // 获取聊天会话列表 $sessions = $wpdb->get_results( "SELECT session_id, COUNT(*) as message_count, MIN(timestamp) as start_time, MAX(timestamp) as end_time FROM $table_name GROUP BY session_id ORDER BY end_time DESC LIMIT $offset, $per_page" ); $total_sessions = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name"); $total_pages = ceil($total_sessions / $per_page); ?> <div class="wrap"> <h1>聊天记录管理</h1> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>会话ID</th> <th>消息数量</th> <th>开始时间</th> <th>最后活动</th> <th>操作</th> </tr> </thead> <tbody> <?php if (empty($sessions)): ?> <tr> <td colspan="5">暂无聊天记录</td> </tr> <?php else: ?> <?php foreach ($sessions as $session): ?> <tr> <td><?php echo esc_html($session->session_id); ?></td> <td><?php echo intval($session->message_count); ?></td> <td><?php echo esc_html($session->start_time); ?></td> <td><?php echo esc_html($session->end_time); ?></td> <td> <a href="#" class="button button-small view-chat-details" data-session="<?php echo esc_attr($session->session_id); ?>"> 查看详情 </a> <a href="<?php echo wp_nonce_url( admin_url('admin.php?page=mlt-chat-logs&action=delete_session&session_id=' . $session->session_id), 'delete_chat_session' ); ?>" class="button button-small button-link-delete" onclick="return confirm('确定删除此会话的所有记录吗?')"> 删除 </a> </td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> <?php if ($total_pages > 1): ?> <div class="tablenav bottom"> <div class="tablenav-pages"> <?php echo paginate_links([ 'base' => add_query_arg('paged', '%#%'), 'format' => '', 'prev_text' => '&laquo;', 'next_text' => '&raquo;', 'total' => $total_pages, 'current' => $current_page ]); ?> </div> </div> <?php endif; ?> <!-- 聊天详情模态框 --> <div id="mlt-chat-details-modal" class="mlt-modal" style="display:none;"> <div class="mlt-modal-content"> <div class="mlt-modal-header"> <h3>聊天详情 - <span id="modal-session-id"></span></h3> <span class="mlt-modal-close">&times;</span> </div> <div class="mlt-modal-body"> <div id="mlt-chat-details-content"></div> </div> </div> </div> </div> <?php } } 第五章:高级功能与优化 5.1 缓存机制实现 为了减少API调用次数和提高性能,实现翻译缓存: class MLT_Translation_Cache { private $cache_group = 'mlt_translations'; private $cache_expiry = WEEK_IN_SECONDS; // 缓存一周 public function get_cached_translation($text, $target_lang, $source_lang = null) { $cache_key = $this->generate_cache_key($text, $target_lang, $source_lang); $cached = wp_cache_get($cache_key, $this->cache_group); if ($cached !== false) { return $cached; } // 检查数据库缓存 global $wpdb; $table_name = $wpdb->prefix . 'mlt_translation_cache'; $result = $wpdb->get_row($wpdb->prepare( "SELECT translated_text FROM $table_name WHERE original_text_hash = %s AND target_language = %s AND (source_language = %s OR source_language IS NULL) AND expiry_time > NOW()", md5($text), $target_lang, $source_lang )); if ($result) { wp_cache_set($cache_key, $result->translated_text, $this->cache_group, $this->cache_expiry); return $result->translated_text; } return false; } public function cache_translation($text, $translated_text, $target_lang, $source_lang = null) { $cache_key = $this->generate_cache_key($text, $target_lang, $source_lang); // 设置内存缓存 wp_cache_set($cache_key, $translated_text, $this->cache_group, $this->cache_expiry); // 存储到数据库 global $wpdb; $table_name = $wpdb->prefix . 'mlt_translation_cache'; $wpdb->replace($table_name, [ 'original_text_hash' => md5($text), 'original_text' => $text, 'translated_text' => $translated_text, 'source_language' => $source_lang, 'target_language' => $target_lang, 'expiry_time' => date('Y-m-d H:i:s', time() + $this->cache_expiry), 'created_at' => current_time('mysql') ]); } private function generate_cache_key($text, $target_lang, $source_lang) { return 'trans_' . md5($text . $target_lang . $source_lang); } public static function create_cache_table() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'mlt_translation_cache'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, original_text_hash varchar(32) NOT NULL, original_text text NOT NULL, translated_text text NOT NULL, source_language varchar(10), target_language varchar(10) NOT NULL, expiry_time datetime NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY text_hash_lang (original_text_hash, target_language, source_language), KEY expiry_time (expiry_time) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } } 5.2 性能优化与异步处理 对于大量翻译请求,使用异步处理避免阻塞: class MLT_Async_Processor { private $queue_table; public function __construct() { global $wpdb; $this->queue_table = $wpdb->prefix . 'mlt_async_queue'; add_action('mlt_process_queue', [$this, 'process_queue']); add_action('wp_ajax_nopriv_mlt_async_translate', [$this, 'handle_async_translate']); } public function add_translation_job($data) { global $wpdb; return $wpdb->insert($this->queue_table, [ 'job_type' => 'translation', 'job_data' => json_encode($data), 'status' => 'pending', 'created_at' => current_time('mysql') ]); } public function process_queue() { global $wpdb; // 获取待处理的任务 $jobs = $wpdb->get_results( "SELECT * FROM {$this->queue_table} WHERE status = 'pending' ORDER BY created_at ASC LIMIT 10" ); foreach ($jobs as $job) { $this->process_job($job); } } private function process_job($job) { global $wpdb; $job_data = json_decode($job->job_data, true); try { // 更新状态为处理中 $wpdb->update( $this->queue_table, ['status' => 'processing'], ['id' => $job->id] ); // 执行翻译 $api_key = get_option('mlt_google_api_key'); $translator = new MLT_Translation_Engine($api_key); $result = $translator->translate( $job_data['text'], $job_data['target_lang'], $job_data['source_lang'] ?? null ); // 更新状态为完成 $wpdb->update( $this->queue_table, [ 'status' => 'completed', 'result_data' => json_encode(['translated_text' => $result]), 'completed_at' => current_time('mysql') ], ['id' => $job->id] ); } catch (Exception $e) { $wpdb->update( $this->queue_table, [ 'status' => 'failed', 'error_message' => $e->getMessage(), 'completed_at' => current_time('mysql') ], ['id' => $job->id] ); } } public static function create_queue_table() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'mlt_async_queue'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, job_type varchar(50) NOT NULL, job_data text NOT NULL, status varchar(20) DEFAULT 'pending', result_data text, error_message text, created_at datetime DEFAULT CURRENT_TIMESTAMP, completed_at datetime, PRIMARY KEY (id), KEY status (status), KEY job_type (job_type) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } } 5.3 安全增强措施 class MLT_Security_Manager { public static function sanitize_chat_input($input) { // 移除危险标签但保留基本格式 $allowed_tags = [ 'b' => [], 'i' => [], 'u' => [], 'em' => [], 'strong' => [], 'br' => [], 'p' => [], 'span' => ['class' => []] ]; $sanitized = wp_kses($input, $allowed_tags); // 限制长度 if (strlen($sanitized) > 1000) { $sanitized = substr($sanitized, 0, 1000); } return $sanitized; } public static function validate_language_code($lang_code) { $supported_languages = [ 'en', 'es', 'fr', 'de', 'zh-CN', 'zh-TW', 'ja', 'ko', 'ru', 'ar', 'pt', 'it', 'nl', 'pl', 'tr', 'th', 'vi' ]; return in_array($lang_code, $supported_languages) ? $lang_code : 'en'; } public static function prevent_flood_attack($session_id, $limit = 10, $time_window = 60) { $transient_key = 'mlt_chat_flood_' . md5($session_id); $request_count = get_transient($transient_key); if ($request_count === false) { set_transient($transient_key, 1, $time_window); return true; } if ($request_count >= $limit) { return false; } set_transient($transient_key, $request_count + 1, $time_window); return true; } public static function encrypt_sensitive_data($data) { if (!extension_loaded('openssl')) { return $data; // 回退到基本编码 } $method = 'AES-256-CBC'; $key = defined('MLT_ENCRYPTION_KEY') ? MLT_ENCRYPTION_KEY : wp_salt(); $iv_length = openssl_cipher_iv_length($method); $iv = openssl_random_pseudo_bytes($iv_length); $encrypted = openssl_encrypt($data, $method, $key, 0, $iv); return base64_encode($iv . $encrypted); } } 第六章:插件部署与维护 6.1 插件激活与卸载处理 class MLT_Plugin_Manager { public static function activate() { // 创建数据库表 MLT_Chat_Database::create_tables(); MLT_Translation_Cache::create_cache_table(); MLT_Async_Processor::create_queue_table(); // 设置默认选项 add_option('mlt_chat_enabled', '1'); add_option('mlt_translation_enabled', '1'); add_option('mlt_default_language', 'zh-CN'); add_option('mlt_supported_languages', ['en', 'zh-CN', 'es', 'fr', 'de']); // 创建定时任务 if (!wp_next_scheduled('mlt_process_queue')) { wp_schedule_event(time(), 'hourly', 'mlt_process_queue'); } // 创建必要的目录

发表评论