WordPress开发教程:集成网站自动化数据采集与信息聚合工具,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:WordPress的无限可能 在当今数字化时代,网站已不仅仅是信息展示的窗口,更是功能集成与服务的平台。WordPress作为全球最受欢迎的内容管理系统,占据了互联网上超过43%的网站份额。然而,许多用户仅将其视为简单的博客或内容发布工具,未能充分挖掘其作为强大开发平台的潜力。 实际上,通过代码二次开发,WordPress可以转型为功能丰富的应用平台,集成自动化数据采集、信息聚合以及各种实用小工具。本教程将深入探讨如何通过WordPress开发,实现网站自动化数据采集与信息聚合,并集成常用互联网小工具功能,帮助您将普通网站升级为智能化的多功能平台。 第一部分:WordPress开发环境搭建与基础知识 1.1 开发环境配置 在开始WordPress二次开发之前,首先需要搭建合适的开发环境。推荐使用本地开发环境如XAMPP、MAMP或Local by Flywheel,这些工具提供了完整的PHP、MySQL和Web服务器环境。 对于代码编辑器,Visual Studio Code是目前最受欢迎的选择,配合以下扩展插件可极大提升开发效率: PHP Intelephense(PHP代码智能提示) WordPress Snippet(WordPress代码片段) GitLens(Git版本控制集成) 此外,建议安装调试工具如Query Monitor和Debug Bar,这些插件能帮助您在开发过程中实时监控数据库查询、PHP错误和性能数据。 1.2 WordPress主题与插件架构理解 要有效进行WordPress二次开发,必须深入理解其核心架构: 主题系统:WordPress主题控制网站的外观和显示方式。子主题开发是自定义功能而不影响父主题更新的最佳实践。创建子主题只需在wp-content/themes目录下建立新文件夹,包含style.css和functions.php文件。 插件系统:插件用于扩展WordPress功能,独立于主题。良好的插件应遵循单一职责原则,专注于特定功能的实现。 钩子机制:动作钩子(Action Hooks)和过滤器钩子(Filter Hooks)是WordPress扩展性的核心。动作钩子允许在特定点执行自定义代码,而过滤器钩子允许修改数据。 // 动作钩子示例 add_action('wp_head', 'custom_head_code'); function custom_head_code() { echo '<meta name="custom-tag" content="value">'; } // 过滤器钩子示例 add_filter('the_title', 'custom_title_format'); function custom_title_format($title) { return '📌 ' . $title; } 1.3 自定义文章类型与字段 对于数据采集和聚合,自定义文章类型(CPT)和字段是基础。CPT允许您创建不同于标准文章和页面的内容类型,如“新闻”、“产品”或“数据条目”。 // 注册自定义文章类型 function register_data_collection_cpt() { $labels = array( 'name' => '采集数据', 'singular_name' => '数据条目' ); $args = array( 'labels' => $labels, 'public' => true, 'has_archive' => true, 'supports' => array('title', 'editor', 'custom-fields'), 'show_in_rest' => true, // 启用Gutenberg编辑器支持 ); register_post_type('collected_data', $args); } add_action('init', 'register_data_collection_cpt'); 对于更复杂的字段需求,推荐使用Advanced Custom Fields(ACF)插件或Meta Box框架,它们提供了直观的字段管理界面。 第二部分:自动化数据采集系统开发 2.1 数据采集策略与规划 在开发数据采集功能前,需要明确采集目标、数据源和更新频率。常见的数据源包括: RSS/Atom订阅源 公开API接口 网页抓取(需遵守robots.txt和法律法规) 社交媒体平台 公开数据库 设计数据采集系统时,应考虑以下因素: 数据源稳定性与可用性 采集频率与服务器负载平衡 数据去重与更新机制 错误处理与日志记录 版权与法律合规性 2.2 WordPress定时任务系统 WordPress提供了内置的定时任务系统WP-Cron,可用于定期执行数据采集任务。但需要注意的是,WP-Cron基于页面访问触发,对于精确的定时任务可能不够可靠。对于高要求的采集任务,建议使用系统级的Cron任务。 // 注册定时采集任务 function register_data_collection_cron() { // 确保事件未已安排 if (!wp_next_scheduled('hourly_data_collection')) { // 安排每小时执行一次 wp_schedule_event(time(), 'hourly', 'hourly_data_collection'); } } add_action('wp', 'register_data_collection_cron'); // 定义采集函数 function perform_data_collection() { // 数据采集逻辑 $data_sources = get_option('data_collection_sources', array()); foreach ($data_sources as $source) { $collected_data = fetch_data_from_source($source); process_and_store_data($collected_data); } // 记录采集日志 update_option('last_collection_time', current_time('mysql')); } add_action('hourly_data_collection', 'perform_data_collection'); // 添加自定义时间间隔 function add_custom_cron_intervals($schedules) { $schedules['every_10_minutes'] = array( 'interval' => 600, 'display' => __('每10分钟') ); return $schedules; } add_filter('cron_schedules', 'add_custom_cron_intervals'); 2.3 数据采集方法实现 RSS/Atom订阅采集: function fetch_rss_feed($feed_url) { include_once(ABSPATH . WPINC . '/feed.php'); // 获取RSS订阅 $rss = fetch_feed($feed_url); if (is_wp_error($rss)) { error_log('RSS采集错误: ' . $rss->get_error_message()); return false; } $max_items = $rss->get_item_quantity(10); // 获取最新10条 $rss_items = $rss->get_items(0, $max_items); $collected_data = array(); foreach ($rss_items as $item) { $data_entry = array( 'title' => $item->get_title(), 'content' => $item->get_content(), 'excerpt' => $item->get_description(), 'source_url' => $item->get_permalink(), 'publish_date' => $item->get_date('Y-m-d H:i:s'), 'author' => $item->get_author() ? $item->get_author()->get_name() : '', 'categories' => array() ); // 获取分类 $categories = $item->get_categories(); if ($categories) { foreach ($categories as $category) { $data_entry['categories'][] = $category->get_label(); } } $collected_data[] = $data_entry; } return $collected_data; } API数据采集: function fetch_api_data($api_url, $api_key = '') { $args = array( 'timeout' => 30, 'headers' => $api_key ? array('Authorization' => 'Bearer ' . $api_key) : array() ); $response = wp_remote_get($api_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; } 网页内容抓取:对于网页抓取,建议使用WordPress内置的HTTP API配合DOM解析: function scrape_website_content($url, $selector) { // 获取网页内容 $response = wp_remote_get($url, array('timeout' => 30)); if (is_wp_error($response)) { return false; } $html = wp_remote_retrieve_body($response); // 使用DOMDocument解析HTML $dom = new DOMDocument(); @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); $xpath = new DOMXPath($dom); $elements = $xpath->query($selector); $results = array(); foreach ($elements as $element) { $results[] = $dom->saveHTML($element); } return $results; } 注意:网页抓取应遵守robots.txt规则,尊重版权,并控制请求频率以避免对目标服务器造成负担。 2.4 数据存储与处理 采集到的数据需要有效存储和处理。WordPress提供了多种存储选项: 自定义数据库表:对于大量结构化数据,创建自定义数据库表可能更高效: function create_data_collection_table() { global $wpdb; $table_name = $wpdb->prefix . 'collected_data'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, source_id varchar(100) NOT NULL, title text NOT NULL, content longtext, source_url varchar(500), collected_date datetime DEFAULT CURRENT_TIMESTAMP, processed tinyint(1) DEFAULT 0, PRIMARY KEY (id), KEY source_id (source_id), KEY processed (processed) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } register_activation_hook(__FILE__, 'create_data_collection_table'); 使用自定义文章类型存储:对于大多数情况,使用自定义文章类型配合自定义字段是更简单的方法: function store_collected_data_as_post($data) { // 检查是否已存在相同内容(避免重复) $existing_post = get_posts(array( 'post_type' => 'collected_data', 'meta_query' => array( array( 'key' => 'source_url', 'value' => $data['source_url'] ) ), 'posts_per_page' => 1 )); if (!empty($existing_post)) { // 更新现有文章 $post_id = $existing_post[0]->ID; $post_data = array( 'ID' => $post_id, 'post_title' => $data['title'], 'post_content' => $data['content'], 'post_excerpt' => $data['excerpt'], 'post_status' => 'publish' ); wp_update_post($post_data); } else { // 创建新文章 $post_data = array( 'post_title' => $data['title'], 'post_content' => $data['content'], 'post_excerpt' => $data['excerpt'], 'post_status' => 'publish', 'post_type' => 'collected_data', 'post_date' => $data['publish_date'] ?: current_time('mysql') ); $post_id = wp_insert_post($post_data); } // 保存元数据 if ($post_id && !is_wp_error($post_id)) { update_post_meta($post_id, 'source_url', $data['source_url']); update_post_meta($post_id, 'original_author', $data['author']); update_post_meta($post_id, 'collected_date', current_time('mysql')); // 保存分类 if (!empty($data['categories'])) { $category_ids = array(); foreach ($data['categories'] as $category_name) { $term = term_exists($category_name, 'category'); if (!$term) { $term = wp_insert_term($category_name, 'category'); } if (!is_wp_error($term)) { $category_ids[] = (int)$term['term_id']; } } wp_set_post_terms($post_id, $category_ids, 'category'); } } return $post_id; } 2.5 数据清洗与去重 采集的数据通常需要清洗和去重处理: function clean_and_deduplicate_data($data_array) { $unique_data = array(); $content_hashes = array(); foreach ($data_array as $data) { // 内容清洗 $data['title'] = sanitize_text_field($data['title']); $data['content'] = wp_kses_post($data['content']); // 过滤允许的HTML // 去除HTML标签获取纯文本用于去重 $content_text = wp_strip_all_tags($data['content']); $content_hash = md5($content_text); // 检查是否重复 if (!in_array($content_hash, $content_hashes)) { $content_hashes[] = $content_hash; $unique_data[] = $data; } } return $unique_data; } 第三部分:信息聚合与展示系统 3.1 数据聚合策略 信息聚合不仅仅是收集数据,更是将多源数据整合为有价值的信息流。常见的聚合策略包括: 时间线聚合:按时间顺序展示多源数据 主题聚合:按主题或分类组织相关内容 来源聚合:按数据源分类展示 混合聚合:结合多种维度展示数据 3.2 创建聚合页面模板 在WordPress主题中创建专门的聚合页面模板: <?php /* Template Name: 数据聚合页面 */ get_header(); // 获取聚合配置 $sources = get_field('aggregation_sources'); // 假设使用ACF字段 $layout = get_field('aggregation_layout', 'grid'); // 网格或列表布局 $items_per_page = get_field('items_per_page', 20); $current_page = max(1, get_query_var('paged')); ?> <div class="aggregation-container"> <header class="aggregation-header"> <h1><?php the_title(); ?></h1> <div class="aggregation-filters"> <select id="source-filter"> <option value="all">所有来源</option> <?php foreach ($sources as $source): ?> <option value="<?php echo esc_attr($source['value']); ?>"> <?php echo esc_html($source['label']); ?> </option> <?php endforeach; ?> </select> <select id="date-filter"> <option value="all">全部时间</option> <option value="today">今天</option> <option value="week">本周</option> <option value="month">本月</option> </select> </div> </header> <div class="aggregation-content" id="aggregation-results"> <?php // 查询聚合数据 $args = array( 'post_type' => 'collected_data', 'posts_per_page' => $items_per_page, 'paged' => $current_page, 'orderby' => 'date', 'order' => 'DESC' ); // 添加源过滤 if (isset($_GET['source']) && $_GET['source'] !== 'all') { $args['meta_query'] = array( array( 'key' => 'data_source', 'value' => sanitize_text_field($_GET['source']), 'compare' => '=' ) ); } // 添加日期过滤 if (isset($_GET['date_filter'])) { $date_filter = sanitize_text_field($_GET['date_filter']); $date_query = array(); switch ($date_filter) { case 'today': $date_query = array( 'after' => 'today midnight', 'inclusive' => true ); break; case 'week': $date_query = array( 'after' => '1 week ago' ); break; case 'month': $date_query = array( 'after' => '1 month ago' ); break; } if (!empty($date_query)) { $args['date_query'] = $date_query; } } $aggregation_query = new WP_Query($args); if ($aggregation_query->have_posts()): echo $layout === 'grid' ? '<div class="aggregation-grid">' : '<div class="aggregation-list">'; while ($aggregation_query->have_posts()): $aggregation_query->the_post(); include(locate_template('template-parts/aggregation-item.php')); endwhile; echo '</div>'; // 分页 echo '<div class="aggregation-pagination">'; echo paginate_links(array( 'total' => $aggregation_query->max_num_pages, 'current' => $current_page, 'prev_text' => '« 上一页', 'next_text' => '下一页 »' )); echo '</div>'; wp_reset_postdata(); else: echo '<p class="no-results">暂无聚合数据</p>'; endif; ?> </div> </div> <script> // AJAX过滤功能 jQuery(document).ready(function($) { $('#source-filter, #date-filter').on('change', function() { var source = $('#source-filter').val(); var dateFilter = $('#date-filter').val(); $.ajax({ url: '<?php echo admin_url("admin-ajax.php"); ?>', type: 'POST', data: { data', source: source, date_filter: dateFilter, page: 1 }, beforeSend: function() { $('#aggregation-results').html('<div class="loading">加载中...</div>'); }, success: function(response) { $('#aggregation-results').html(response); } }); }); });</script> <?phpget_footer(); #### 3.3 实时数据聚合与更新 对于需要实时展示的数据,可以结合AJAX和WebSocket技术: // 实时数据推送端点function realtime_aggregation_endpoint() { register_rest_route('aggregation/v1', '/realtime', array( 'methods' => 'GET', 'callback' => 'get_realtime_aggregation_data', 'permission_callback' => '__return_true' )); }add_action('rest_api_init', 'realtime_aggregation_endpoint'); function get_realtime_aggregation_data($request) { $last_id = $request->get_param('last_id'); $category = $request->get_param('category'); $args = array( 'post_type' => 'collected_data', 'posts_per_page' => 10, 'orderby' => 'date', 'order' => 'DESC' ); if ($last_id) { $args['date_query'] = array( 'after' => get_the_date('Y-m-d H:i:s', $last_id) ); } if ($category && $category !== 'all') { $args['tax_query'] = array( array( 'taxonomy' => 'category', 'field' => 'slug', 'terms' => $category ) ); } $query = new WP_Query($args); $data = array(); if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); $data[] = array( 'id' => get_the_ID(), 'title' => get_the_title(), 'excerpt' => get_the_excerpt(), 'date' => get_the_date('Y-m-d H:i:s'), 'source' => get_post_meta(get_the_ID(), 'data_source', true), 'url' => get_permalink() ); } wp_reset_postdata(); } return rest_ensure_response(array( 'success' => true, 'data' => $data, 'timestamp' => current_time('timestamp') )); } 前端实时更新实现: // 前端实时数据监听class RealtimeAggregation { constructor(options) { this.options = Object.assign({ endpoint: '/wp-json/aggregation/v1/realtime', interval: 30000, // 30秒 container: '#realtime-feed', lastId: 0 }, options); this.init(); } init() { this.container = document.querySelector(this.options.container); if (!this.container) return; this.loadInitialData(); this.startPolling(); } async loadInitialData() { try { const response = await fetch(`${this.options.endpoint}?last_id=0`); const data = await response.json(); if (data.success && data.data.length > 0) { this.renderData(data.data); this.options.lastId = data.data[0].id; } } catch (error) { console.error('加载数据失败:', error); } } startPolling() { setInterval(() => { this.checkForUpdates(); }, this.options.interval); } async checkForUpdates() { try { const response = await fetch( `${this.options.endpoint}?last_id=${this.options.lastId}` ); const data = await response.json(); if (data.success && data.data.length > 0) { this.prependData(data.data); this.options.lastId = data.data[0].id; // 显示新数据通知 this.showNewItemsNotification(data.data.length); } } catch (error) { console.error('检查更新失败:', error); } } renderData(items) { items.forEach(item => { const itemElement = this.createItemElement(item); this.container.appendChild(itemElement); }); } prependData(items) { items.reverse().forEach(item => { const itemElement = this.createItemElement(item); this.container.insertBefore(itemElement, this.container.firstChild); }); } createItemElement(item) { const div = document.createElement('div'); div.className = 'realtime-item'; div.innerHTML = ` <div class="item-header"> <span class="source-badge">${this.escapeHtml(item.source)}</span> <span class="item-time">${this.formatTime(item.date)}</span> </div> <h3 class="item-title"> <a href="${this.escapeHtml(item.url)}">${this.escapeHtml(item.title)}</a> </h3> <p class="item-excerpt">${this.escapeHtml(item.excerpt)}</p> `; return div; } showNewItemsNotification(count) { // 实现新数据通知逻辑 const notification = document.createElement('div'); notification.className = 'new-items-notification'; notification.innerHTML = ` 有${count}条新内容,<a href="#" class="show-new">点击查看</a> `; notification.querySelector('.show-new').addEventListener('click', (e) => { e.preventDefault(); notification.remove(); }); document.body.appendChild(notification); setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 5000); } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } formatTime(dateString) { const date = new Date(dateString); const now = new Date(); const diff = Math.floor((now - date) / 1000); // 秒 if (diff < 60) return '刚刚'; if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`; if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`; return date.toLocaleDateString(); } } // 初始化实时聚合document.addEventListener('DOMContentLoaded', () => { new RealtimeAggregation({ container: '#realtime-feed', interval: 15000 // 15秒 }); }); #### 3.4 智能推荐与个性化聚合 基于用户行为实现个性化内容推荐: class PersonalizedAggregation { private $user_id; private $preferences; public function __construct($user_id = null) { $this->user_id = $user_id ?: get_current_user_id(); $this->load_user_preferences(); } private function load_user_preferences() { if ($this->user_id) { $this->preferences = get_user_meta($this->user_id, 'aggregation_preferences', true); } if (empty($this->preferences)) { $this->preferences = array( 'preferred_categories' => array(), 'preferred_sources' => array(), 'reading_history' => array(), 'click_pattern' => array() ); } } public function track_user_interaction($post_id, $interaction_type = 'view') { if (!$this->user_id) return; $post_categories = wp_get_post_categories($post_id); $post_source = get_post_meta($post_id, 'data_source', true); // 更新阅读历史 $history = $this->preferences['reading_history']; array_unshift($history, array( 'post_id' => $post_id, 'timestamp' => current_time('timestamp'), 'type' => $interaction_type )); // 保持最近100条记录 $this->preferences['reading_history'] = array_slice($history, 0, 100); // 更新分类偏好 foreach ($post_categories as $cat_id) { if (!isset($this->preferences['preferred_categories'][$cat_id])) { $this->preferences['preferred_categories'][$cat_id] = 0; } $this->preferences['preferred_categories'][$cat_id] += 1; } // 更新来源偏好 if ($post_source) { if (!isset($this->preferences['preferred_sources'][$post_source])) { $this->preferences['preferred_sources'][$post_source] = 0; } $this->preferences['preferred_sources'][$post_source] += 1; } $this->save_preferences(); } public function get_personalized_feed($limit = 20) { $args = array( 'post_type' => 'collected_data', 'posts_per_page' => $limit, 'orderby' => 'relevance', 'meta_query' => array() ); // 基于用户偏好调整查询 if (!empty($this->preferences['preferred_categories'])) { arsort($this->preferences['preferred_categories']); $top_categories = array_slice( array_keys($this->preferences['preferred_categories']), 0, 3 ); $args['category__in'] = $top_categories; } if (!empty($this->preferences['preferred_sources'])) { arsort($this->preferences['preferred_sources']); $top_sources = array_slice( array_keys($this->preferences['preferred_sources']), 0, 2 ); $args['meta_query'][] = array( 'key' => 'data_source', 'value' => $top_sources, 'compare' => 'IN' ); } // 排除已读内容 if (!empty($this->preferences['reading_history'])) { $read_posts = array_column($this->preferences['reading_history'], 'post_id'); $args['post__not_in'] = array_unique($read_posts); } // 添加相关性评分 add_filter('posts_where', array($this, 'add_relevance_scoring')); $query = new WP_Query($args); remove_filter('posts_where', array($this, 'add_relevance_scoring')); return $query; } public function add_relevance_scoring($where) { // 基于用户偏好计算相关性得分的复杂逻辑 // 这里简化实现,实际应用中可能需要更复杂的算法 global $wpdb; if (!empty($this->preferences['preferred_categories'])) { // 为偏好的分类添加权重 $category_weights = array(); foreach ($this->preferences['preferred_categories'] as $cat_id => $count) { $weight = min(10, $count / 10); // 计算权重 $category_weights[$cat_id] = $weight; } // 这里可以添加更复杂的SQL逻辑来计算相关性 } return $where; } private function save_preferences() { if ($this->user_id) { update_user_meta( $this->user_id, 'aggregation_preferences', $this->preferences ); } } public function get_recommendations_based_on_similarity($post_id, $limit = 5) { $post_categories = wp_get_post_categories($post_id); $post_tags = wp_get_post_tags($post_id, array('fields' => 'ids')); $post_source = get_post_meta($post_id, 'data_source', true); $args = array( 'post_type' => 'collected_data', 'posts_per_page' => $limit, 'post__not_in' => array($post_id), 'orderby' => 'relevance' ); // 基于内容相似性查找相关文章 $tax_query = array('relation' => 'OR'); if (!empty($post_categories)) { $tax_query[] = array( 'taxonomy' => 'category', 'field' => 'term_id', 'terms' => $post_categories ); } if (!empty($post_tags)) { $tax_query[] = array( 'taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => $post_tags ); } if (!empty($tax_query)) { $args['tax_query'] = $tax_query; } if ($post_source) { $args['meta_query'] = array( array( 'key' => 'data_source', 'value' => $post_source, 'compare' => '=' ) ); } return new WP_Query($args); } } // 使用示例add_action('wp', function() { if (is_singular('collected_data')) { $personalizer = new PersonalizedAggregation(); $personalizer->track_user_interaction(get_the_ID()); } }); ### 第四部分:常用互联网小工具集成 #### 4.1 工具类插件架构设计 创建可扩展的小工具系统: // 小工具管理器类class ToolManager { private static $instance = null; private $tools = array(); private function __construct() { $this->load_tools(); add_action('init', array($this, 'register_tools')); } public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function load_tools() { // 加载内置工具 $this->register_tool('unit_converter', array( 'name' => '单位换算器', 'description' => '常用单位换算工具', 'callback' => array($this, 'render_unit_converter'), 'icon' => 'dashicons-calculator', 'category' => 'utility' )); $this->register_tool('color_picker', array( 'name' => '颜色选择器', 'description' => 'RGB/HEX颜色选择与转换', 'callback' => array($this, 'render_color_picker'), 'icon' => 'dashicons-art', 'category' => 'design' )); $this->register_tool('qrcode_generator', array( 'name' => '二维码生成器', 'description' => '生成自定义二维码', 'callback' => array($this, 'render_qrcode_generator'), 'icon' => 'dashicons-format-image', 'category' => 'generator' )); // 允许其他插件注册工具 do_action('tool_manager_register_tools', $this); } public function register_tool($slug, $args) { $defaults = array( 'name' => '', 'description' => '', 'callback' => null, 'icon' => 'dashicons-admin-tools', 'category' => 'general', 'settings' => array() ); $this->tools[$slug] = wp_parse_args($args, $defaults); } public function register_tools() { // 注册短代码 foreach ($this->tools as $slug => $tool) { add_shortcode('tool_' . $slug, $tool['callback']); } // 注册Gutenberg块 if (function_exists('register_block_type')) { $this->register_tool_blocks(); } } private function register_tool_blocks() { wp_register_script( 'tool-blocks', plugins_url('js/tool-blocks.js', __FILE__), array('wp-blocks', 'wp-element', 'wp-editor', 'wp-components'), '1.0.0', true ); register_block_type('tool-manager/tool', array( 'editor_script' => 'tool-blocks', 'render_callback' => array($this, 'render_tool_block') )); } public function render_tool_block($attributes) { $slug = $attributes['toolSlug'] ?? ''; if (isset($this->tools[$slug]) && is_callable($this->tools[$slug]['callback'])) { ob_start(); call_user_func($this->tools[$slug]['callback'], $attributes); return ob_get_clean(); } return '<p>工具未找到</p>'; } public function get_tools_by_category($category = '') { if (empty($category)) { return $this->tools; } return array_filter($this->tools, function($tool) use ($category) { return $tool['category'] === $category; }); } public function render_tool_selector() { $categories = array(); foreach ($this->tools as $tool) { if (!isset($categories[$tool['category']])) { $categories[$tool['category']] = array(); } $categories[$tool['category']][] = $tool; } ob_start(); ?> <div class="tool-selector"> <?php foreach ($categories as $category_name => $category_tools): ?> <div class="tool-category"> <h3><?php echo esc_html($this->get_category_label($category_name)); ?></h3> <div class="tool-grid"> <?php foreach ($category_tools as $slug => $tool): ?> <div class="tool-item" data-tool="<?php echo esc_attr($slug); ?>"> <div class="tool-icon"> <span class="dashicons <?php echo esc_attr($tool['icon']); ?>
发表评论分类: 应用软件
实战教学:为WordPress网站添加在线虚拟试衣间与产品3D展示功能 引言:拥抱Web 3.0时代的交互式购物体验 在电子商务竞争日益激烈的今天,静态图片和文字描述已难以满足消费者的购物需求。据统计,提供3D产品展示的电商网站转化率平均提升40%,退货率降低25%。对于时尚电商而言,虚拟试衣间功能更是能将用户参与度提升300%以上。 本教程将深入讲解如何通过WordPress代码二次开发,为您的网站添加在线虚拟试衣间和产品3D展示功能。无论您是WordPress开发者、电商店主还是技术爱好者,都能通过本文学会如何将这些前沿技术整合到您的网站中。 第一部分:技术选型与环境准备 1.1 核心技术与工具评估 在开始开发前,我们需要评估几种主流技术方案: 3D展示技术选项: Three.js:最流行的WebGL库,功能强大,社区活跃 Babylon.js:微软开发的3D引擎,对商业应用友好 Model-Viewer:Google推出的Web组件,简单易用 A-Frame:基于Three.js的VR框架,适合沉浸式体验 虚拟试衣间技术方案: 2D图像融合:使用Canvas API处理服装与用户图像的合成 3D人体建模:基于参数化人体模型调整服装 AR试穿:通过摄像头实现增强现实试穿效果 开发环境要求: WordPress 5.0+版本 PHP 7.4+环境 支持HTML5和WebGL的现代浏览器 基本的JavaScript和PHP知识 1.2 创建开发环境 首先,我们需要设置一个安全的开发环境: // 创建专用插件目录结构 /* - virtual-fitting-room/ - virtual-fitting-room.php (主插件文件) - includes/ - class-3d-viewer.php - class-virtual-fitting.php - class-asset-manager.php - assets/ - js/ - three.min.js - fitting-room.js - css/ - fitting-room.css - models/ (3D模型文件) - templates/ (前端模板) - admin/ (后台管理界面) */ 在主插件文件中添加基础结构: <?php /** * Plugin Name: 虚拟试衣间与3D展示 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress产品添加虚拟试衣间和3D展示功能 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('VFR_VERSION', '1.0.0'); define('VFR_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('VFR_PLUGIN_URL', plugin_dir_url(__FILE__)); // 自动加载类文件 spl_autoload_register(function ($class) { $prefix = 'VFR_'; $base_dir = VFR_PLUGIN_DIR . 'includes/'; $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) { return; } $relative_class = substr($class, $len); $file = $base_dir . 'class-' . str_replace('_', '-', strtolower($relative_class)) . '.php'; if (file_exists($file)) { require $file; } }); // 初始化插件 function vfr_init() { // 检查WordPress版本 if (version_compare(get_bloginfo('version'), '5.0', '<')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>虚拟试衣间插件需要WordPress 5.0或更高版本。</p></div>'; }); return; } // 初始化核心类 $vfr_3d_viewer = new VFR_3D_Viewer(); $vfr_virtual_fitting = new VFR_Virtual_Fitting(); $vfr_asset_manager = new VFR_Asset_Manager(); } add_action('plugins_loaded', 'vfr_init'); 第二部分:实现3D产品展示功能 2.1 集成Three.js 3D引擎 首先,我们需要将Three.js集成到WordPress中: // includes/class-asset-manager.php class VFR_Asset_Manager { public function __construct() { add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); } public function enqueue_frontend_assets() { // 加载Three.js库 wp_enqueue_script( 'three-js', VFR_PLUGIN_URL . 'assets/js/three.min.js', array(), 'r128', true ); // 加载OrbitControls(相机控制) wp_enqueue_script( 'three-orbit-controls', VFR_PLUGIN_URL . 'assets/js/OrbitControls.js', array('three-js'), '1.0', true ); // 加载GLTFLoader(3D模型加载器) wp_enqueue_script( 'three-gltf-loader', VFR_PLUGIN_URL . 'assets/js/GLTFLoader.js', array('three-js'), '1.0', true ); // 加载自定义3D查看器脚本 wp_enqueue_script( 'vfr-3d-viewer', VFR_PLUGIN_URL . 'assets/js/3d-viewer.js', array('three-js', 'three-orbit-controls', 'three-gltf-loader'), VFR_VERSION, true ); // 加载样式 wp_enqueue_style( 'vfr-frontend-style', VFR_PLUGIN_URL . 'assets/css/frontend.css', array(), VFR_VERSION ); // 传递数据到JavaScript wp_localize_script('vfr-3d-viewer', 'vfr_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('vfr_nonce') )); } } 2.2 创建3D查看器核心功能 // assets/js/3d-viewer.js class Product3DViewer { constructor(containerId, modelPath, options = {}) { this.container = document.getElementById(containerId); this.modelPath = modelPath; this.options = Object.assign({ backgroundColor: 0xf0f0f0, showControls: true, autoRotate: true, enableZoom: true }, options); this.scene = null; this.camera = null; this.renderer = null; this.controls = null; this.model = null; this.init(); } init() { // 创建场景 this.scene = new THREE.Scene(); this.scene.background = new THREE.Color(this.options.backgroundColor); // 创建相机 this.camera = new THREE.PerspectiveCamera( 45, this.container.clientWidth / this.container.clientHeight, 0.1, 1000 ); this.camera.position.set(5, 5, 5); // 创建渲染器 this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.renderer.setSize(this.container.clientWidth, this.container.clientHeight); this.renderer.setPixelRatio(window.devicePixelRatio); this.container.appendChild(this.renderer.domElement); // 添加光源 const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); this.scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 20, 5); this.scene.add(directionalLight); // 添加轨道控制 if (this.options.showControls) { this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); this.controls.enableZoom = this.options.enableZoom; this.controls.autoRotate = this.options.autoRotate; } // 加载3D模型 this.loadModel(); // 开始动画循环 this.animate(); // 处理窗口大小变化 window.addEventListener('resize', () => this.onWindowResize()); } loadModel() { const loader = new THREE.GLTFLoader(); loader.load( this.modelPath, (gltf) => { this.model = gltf.scene; this.scene.add(this.model); // 调整模型位置和大小 const box = new THREE.Box3().setFromObject(this.model); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); // 居中模型 this.model.position.x += (this.model.position.x - center.x); this.model.position.y += (this.model.position.y - center.y); this.model.position.z += (this.model.position.z - center.z); // 缩放模型到合适大小 const maxDim = Math.max(size.x, size.y, size.z); const scale = 5 / maxDim; this.model.scale.multiplyScalar(scale); console.log('3D模型加载成功'); }, (progress) => { // 加载进度回调 const percent = (progress.loaded / progress.total * 100).toFixed(2); console.log(`模型加载进度: ${percent}%`); }, (error) => { console.error('3D模型加载失败:', error); } ); } animate() { requestAnimationFrame(() => this.animate()); if (this.controls && this.options.autoRotate) { this.controls.update(); } this.renderer.render(this.scene, this.camera); } onWindowResize() { this.camera.aspect = this.container.clientWidth / this.container.clientHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(this.container.clientWidth, this.container.clientHeight); } // 公共方法:更换模型 changeModel(newModelPath) { if (this.model) { this.scene.remove(this.model); } this.modelPath = newModelPath; this.loadModel(); } // 公共方法:更改背景颜色 setBackgroundColor(color) { this.scene.background = new THREE.Color(color); } } // WordPress集成 document.addEventListener('DOMContentLoaded', function() { // 查找所有3D查看器容器 const viewers = document.querySelectorAll('.vfr-3d-container'); viewers.forEach(container => { const modelPath = container.getAttribute('data-model'); const options = { backgroundColor: container.getAttribute('data-bg-color') || 0xf0f0f0, autoRotate: container.getAttribute('data-auto-rotate') !== 'false', enableZoom: container.getAttribute('data-enable-zoom') !== 'false' }; new Product3DViewer(container.id, modelPath, options); }); }); 2.3 创建WordPress短代码和Gutenberg块 // includes/class-3d-viewer.php class VFR_3D_Viewer { public function __construct() { // 注册短代码 add_shortcode('3d_product_viewer', array($this, 'render_3d_viewer_shortcode')); // 注册Gutenberg块 add_action('init', array($this, 'register_gutenberg_block')); // 添加产品编辑页面元框 add_action('add_meta_boxes', array($this, 'add_3d_model_meta_box')); add_action('save_post_product', array($this, 'save_3d_model_meta')); } // 短代码渲染 public function render_3d_viewer_shortcode($atts) { $atts = shortcode_atts(array( 'model' => '', 'width' => '100%', 'height' => '500px', 'bg_color' => '#f0f0f0', 'auto_rotate' => 'true', 'enable_zoom' => 'true' ), $atts, '3d_product_viewer'); // 生成唯一ID $viewer_id = 'vfr-3d-viewer-' . uniqid(); // 构建HTML $output = '<div class="vfr-3d-container" id="' . esc_attr($viewer_id) . '" '; $output .= 'data-model="' . esc_url($atts['model']) . '" '; $output .= 'data-bg-color="' . esc_attr($atts['bg_color']) . '" '; $output .= 'data-auto-rotate="' . esc_attr($atts['auto_rotate']) . '" '; $output .= 'data-enable-zoom="' . esc_attr($atts['enable_zoom']) . '" '; $output .= 'style="width:' . esc_attr($atts['width']) . ';height:' . esc_attr($atts['height']) . ';"></div>'; return $output; } // 注册Gutenberg块 public function register_gutenberg_block() { if (!function_exists('register_block_type')) { return; } register_block_type('vfr/3d-viewer', array( 'editor_script' => 'vfr-gutenberg-editor', 'render_callback' => array($this, 'render_gutenberg_3d_viewer'), 'attributes' => array( 'modelUrl' => array('type' => 'string'), 'width' => array('type' => 'string', 'default' => '100%'), 'height' => array('type' => 'string', 'default' => '500px'), 'backgroundColor' => array('type' => 'string', 'default' => '#f0f0f0'), 'autoRotate' => array('type' => 'boolean', 'default' => true), 'enableZoom' => array('type' => 'boolean', 'default' => true) ) )); } // 渲染Gutenberg块 public function render_gutenberg_3d_viewer($attributes) { return $this->render_3d_viewer_shortcode(array( 'model' => $attributes['modelUrl'], 'width' => $attributes['width'], 'height' => $attributes['height'], 'bg_color' => $attributes['backgroundColor'], 'auto_rotate' => $attributes['autoRotate'] ? 'true' : 'false', 'enable_zoom' => $attributes['enableZoom'] ? 'true' : 'false' )); } // 添加3D模型元框 public function add_3d_model_meta_box() { add_meta_box( 'vfr_3d_model', '3D模型设置', array($this, 'render_3d_model_meta_box'), 'product', 'side', 'default' ); } // 渲染元框内容 public function render_3d_model_meta_box($post) { wp_nonce_field('vfr_3d_model_nonce', 'vfr_3d_model_nonce_field'); $model_url = get_post_meta($post->ID, '_vfr_3d_model_url', true); ?> <div class="vfr-meta-box"> <p> <label for="vfr_3d_model_url">3D模型URL (GLTF/GLB格式):</label> <input type="url" id="vfr_3d_model_url" name="vfr_3d_model_url" value="<?php echo esc_url($model_url); ?>" style="width:100%; margin-top:5px;"> </p> <p class="description"> 上传GLTF或GLB格式的3D模型文件,然后在此处粘贴URL。 </p> <button type="button" class="button vfr-upload-model" style="width:100%;"> 上传/选择3D模型 </button> </div> <script> jQuery(document).ready(function($) { $('.vfr-upload-model').click(function(e) { e.preventDefault(); var frame = wp.media({ title: '选择3D模型文件', button: { text: '使用此文件' }, multiple: false, library: { type: ['application/octet-stream', 'model/gltf-binary'] } }); frame.on('select', function() { var attachment = frame.state().get('selection').first().toJSON(); $('#vfr_3d_model_url').val(attachment.url); }); frame.open(); }); }); </script> <?php } // 保存元数据 public function save_3d_model_meta($post_id) { // 安全检查 if (!isset($_POST['vfr_3d_model_nonce_field']) || !wp_verify_nonce($_POST['vfr_3d_model_nonce_field'], 'vfr_3d_model_nonce')) { return; } if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (!current_user_can('edit_post', $post_id)) { return; } // 保存3D模型URL if (isset($_POST['vfr_3d_model_url'])) { vfr_3d_model_url'])); } } } ## 第三部分:实现虚拟试衣间功能 ### 3.1 虚拟试衣间核心架构设计 虚拟试衣间需要处理用户图像与服装图像的智能合成。我们将采用以下技术路径: 1. **用户身体测量**:通过上传照片或输入尺寸获取用户体型数据 2. **服装变形算法**:根据用户体型调整服装图像 3. **图像合成**:将调整后的服装与用户图像自然融合 // includes/class-virtual-fitting.phpclass VFR_Virtual_Fitting { private $upload_dir; public function __construct() { $this->upload_dir = wp_upload_dir(); // 注册AJAX处理 add_action('wp_ajax_vfr_process_fitting', array($this, 'process_fitting')); add_action('wp_ajax_nopriv_vfr_process_fitting', array($this, 'process_fitting')); // 注册短代码 add_shortcode('virtual_fitting_room', array($this, 'render_fitting_room')); // 添加服装尺寸管理 add_action('init', array($this, 'register_clothing_size_taxonomy')); } // 注册服装尺寸分类法 public function register_clothing_size_taxonomy() { $labels = array( 'name' => '服装尺寸', 'singular_name' => '尺寸', 'search_items' => '搜索尺寸', 'all_items' => '所有尺寸', 'edit_item' => '编辑尺寸', 'update_item' => '更新尺寸', 'add_new_item' => '添加新尺寸', 'new_item_name' => '新尺寸名称', 'menu_name' => '尺寸' ); register_taxonomy('clothing_size', 'product', array( 'hierarchical' => true, 'labels' => $labels, 'show_ui' => true, 'show_admin_column' => true, 'query_var' => true, 'rewrite' => array('slug' => 'size'), )); } // 渲染虚拟试衣间界面 public function render_fitting_room($atts) { $atts = shortcode_atts(array( 'product_id' => 0, 'width' => '800px', 'height' => '600px' ), $atts, 'virtual_fitting_room'); // 获取当前用户ID $user_id = get_current_user_id(); // 获取用户保存的身体尺寸 $user_measurements = $user_id ? get_user_meta($user_id, 'vfr_body_measurements', true) : array(); // 获取产品信息 $product = $atts['product_id'] ? wc_get_product($atts['product_id']) : null; ob_start(); ?> <div class="vfr-fitting-room-container" style="width:<?php echo esc_attr($atts['width']); ?>; max-width:100%;"> <!-- 用户控制面板 --> <div class="vfr-control-panel"> <div class="vfr-tabs"> <button class="vfr-tab active" data-tab="upload">上传照片</button> <button class="vfr-tab" data-tab="measurements">身体尺寸</button> <button class="vfr-tab" data-tab="clothing">选择服装</button> <button class="vfr-tab" data-tab="result">试穿效果</button> </div> <!-- 上传照片标签页 --> <div class="vfr-tab-content active" id="tab-upload"> <div class="vfr-upload-area" id="vfr-upload-area"> <div class="vfr-upload-instructions"> <i class="dashicons dashicons-format-image" style="font-size:48px;color:#ccc;"></i> <p>点击或拖拽上传全身照片</p> <p class="vfr-upload-hint">建议:正面站立,背景简单,光线充足</p> </div> <input type="file" id="vfr-user-photo" accept="image/*" style="display:none;"> </div> <div class="vfr-preview-container" id="vfr-user-preview" style="display:none;"> <img id="vfr-user-image" src="" alt="用户照片"> <button type="button" class="vfr-remove-image">重新上传</button> </div> </div> <!-- 身体尺寸标签页 --> <div class="vfr-tab-content" id="tab-measurements"> <form id="vfr-measurements-form"> <div class="vfr-measurement-row"> <label>身高 (cm):</label> <input type="number" name="height" value="<?php echo esc_attr($user_measurements['height'] ?? ''); ?>" min="100" max="250"> </div> <div class="vfr-measurement-row"> <label>胸围 (cm):</label> <input type="number" name="chest" value="<?php echo esc_attr($user_measurements['chest'] ?? ''); ?>" min="50" max="150"> </div> <div class="vfr-measurement-row"> <label>腰围 (cm):</label> <input type="number" name="waist" value="<?php echo esc_attr($user_measurements['waist'] ?? ''); ?>" min="40" max="150"> </div> <div class="vfr-measurement-row"> <label>臀围 (cm):</label> <input type="number" name="hips" value="<?php echo esc_attr($user_measurements['hips'] ?? ''); ?>" min="50" max="150"> </div> <div class="vfr-measurement-row"> <label>肩宽 (cm):</label> <input type="number" name="shoulders" value="<?php echo esc_attr($user_measurements['shoulders'] ?? ''); ?>" min="30" max="80"> </div> <button type="button" id="vfr-save-measurements" class="button button-primary">保存尺寸</button> <button type="button" id="vfr-auto-detect" class="button">自动检测</button> </form> </div> <!-- 选择服装标签页 --> <div class="vfr-tab-content" id="tab-clothing"> <?php if ($product): ?> <div class="vfr-product-selection"> <h4>当前产品: <?php echo esc_html($product->get_name()); ?></h4> <div class="vfr-clothing-options"> <div class="vfr-color-options"> <label>颜色:</label> <?php if ($product->is_type('variable')) { $colors = $product->get_available_variations(); foreach ($colors as $color) { echo '<button type="button" class="vfr-color-option" data-color="' . esc_attr($color['attributes']['attribute_color']) . '">' . esc_html($color['attributes']['attribute_color']) . '</button>'; } } ?> </div> <div class="vfr-size-options"> <label>尺寸:</label> <?php $sizes = get_terms(array('taxonomy' => 'clothing_size', 'hide_empty' => false)); foreach ($sizes as $size) { echo '<button type="button" class="vfr-size-option" data-size="' . esc_attr($size->slug) . '">' . esc_html($size->name) . '</button>'; } ?> </div> </div> </div> <?php else: ?> <p>请选择要试穿的产品</p> <?php endif; ?> </div> <!-- 试穿效果标签页 --> <div class="vfr-tab-content" id="tab-result"> <div class="vfr-result-container"> <canvas id="vfr-fitting-canvas" width="400" height="600"></canvas> <div class="vfr-result-controls"> <button type="button" id="vfr-download-result" class="button">下载图片</button> <button type="button" id="vfr-share-result" class="button">分享</button> <button type="button" id="vfr-try-another" class="button">试穿其他</button> </div> </div> </div> </div> <!-- 试衣预览区域 --> <div class="vfr-preview-area"> <div class="vfr-preview-wrapper"> <div class="vfr-original-preview"> <h4>原始照片</h4> <div id="vfr-original-container"></div> </div> <div class="vfr-fitted-preview"> <h4>试穿效果</h4> <div id="vfr-fitted-container"></div> </div> </div> <div class="vfr-loading" id="vfr-loading" style="display:none;"> <div class="vfr-spinner"></div> <p>正在处理中...</p> </div> </div> </div> <script> // 虚拟试衣间JavaScript逻辑将在下面实现 </script> <?php return ob_get_clean(); } // 处理试衣请求 public function process_fitting() { // 验证nonce if (!check_ajax_referer('vfr_nonce', 'nonce', false)) { wp_die('安全验证失败', 403); } // 获取POST数据 $user_image = $_POST['user_image'] ?? ''; $product_id = intval($_POST['product_id'] ?? 0); $measurements = $_POST['measurements'] ?? array(); $color = sanitize_text_field($_POST['color'] ?? ''); $size = sanitize_text_field($_POST['size'] ?? ''); // 验证数据 if (empty($user_image) || $product_id <= 0) { wp_send_json_error('缺少必要数据'); } // 解码base64图像 $user_image = str_replace('data:image/png;base64,', '', $user_image); $user_image = str_replace(' ', '+', $user_image); $user_image_data = base64_decode($user_image); // 保存临时文件 $temp_dir = $this->upload_dir['basedir'] . '/vfr_temp/'; if (!file_exists($temp_dir)) { wp_mkdir_p($temp_dir); } $user_image_path = $temp_dir . uniqid('user_') . '.png'; file_put_contents($user_image_path, $user_image_data); // 获取产品图像 $product = wc_get_product($product_id); $product_image_id = $product->get_image_id(); $product_image_path = get_attached_file($product_image_id); // 调用图像处理函数 $result = $this->process_clothing_fitting($user_image_path, $product_image_path, $measurements); if ($result['success']) { // 将结果图像转换为base64 $result_image = base64_encode(file_get_contents($result['path'])); // 清理临时文件 unlink($user_image_path); unlink($result['path']); // 保存用户尺寸(如果用户已登录) if (is_user_logged_in() && !empty($measurements)) { update_user_meta(get_current_user_id(), 'vfr_body_measurements', $measurements); } wp_send_json_success(array( 'image' => 'data:image/png;base64,' . $result_image, 'message' => '试穿处理完成' )); } else { wp_send_json_error($result['message']); } } // 服装试穿处理核心算法 private function process_clothing_fitting($user_image_path, $clothing_image_path, $measurements) { // 这里实现图像处理算法 // 实际项目中可能需要使用OpenCV、ImageMagick或深度学习模型 // 简化版实现:使用PHP GD库进行基本图像处理 $user_image = imagecreatefrompng($user_image_path); $clothing_image = imagecreatefrompng($clothing_image_path); if (!$user_image || !$clothing_image) { return array('success' => false, 'message' => '图像加载失败'); } // 获取图像尺寸 $user_width = imagesx($user_image); $user_height = imagesy($user_image); // 根据用户尺寸调整服装图像 $scale_factor = $this->calculate_scale_factor($measurements, $user_height); $clothing_width = imagesx($clothing_image) * $scale_factor; $clothing_height = imagesy($clothing_image) * $scale_factor; // 创建新图像 $result_image = imagecreatetruecolor($user_width, $user_height); // 复制用户图像 imagecopy($result_image, $user_image, 0, 0, 0, 0, $user_width, $user_height); // 调整服装图像大小 $scaled_clothing = imagescale($clothing_image, $clothing_width, $clothing_height); // 计算服装位置(简化版:居中放置) $x_position = ($user_width - $clothing_width) / 2; $y_position = $user_height * 0.3; // 假设服装从30%高度开始 // 合并图像(使用alpha混合) $this->image_alpha_merge($result_image, $scaled_clothing, $x_position, $y_position, 0, 0, $clothing_width, $clothing_height, 100); // 保存结果 $result_path = $this->upload_dir['basedir'] . '/vfr_temp/result_' . uniqid() . '.png'; imagepng($result_image, $result_path); // 释放内存 imagedestroy($user_image); imagedestroy($clothing_image); imagedestroy($result_image); imagedestroy($scaled_clothing); return array('success' => true, 'path' => $result_path); } // 计算缩放因子 private function calculate_scale_factor($measurements, $user_height) { // 简化算法:根据身高比例缩放 $standard_height = 170; // 标准身高170cm $height = $measurements['height'] ?? $user_height; return $height / $standard_height; } // Alpha通道图像合并 private function image_alpha_merge($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) { // 创建临时图像 $tmp_im = imagecreatetruecolor($src_w, $src_h); // 复制源图像到临时图像 imagecopy($tmp_im, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h); imagecopy($tmp_im, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h); imagecopymerge($dst_im, $tmp_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct); // 释放临时图像 imagedestroy($tmp_im); } } ### 3.2 虚拟试衣间前端交互实现 // assets/js/fitting-room.jsclass VirtualFittingRoom { constructor() { this.userImage = null; this.measurements = {}; this.selectedProduct = null; this.selectedColor = null; this.selectedSize = null; this.init(); } init() { this.setupEventListeners(); this.setupDragAndDrop(); this.loadSavedMeasurements(); } setupEventListeners() { // 标签页切换 document.querySelectorAll('.vfr-tab').forEach(tab => { tab.addEventListener('click', (e) => { this.switchTab(e.target.dataset.tab); }); }); // 上传区域点击 document.getElementById('vfr-upload-area').addEventListener('click', () => { document.getElementById('vfr-user-photo').click(); }); // 文件选择 document.getElementById('vfr-user-photo').addEventListener('change', (e) => { this.handleImageUpload(e.target.files[0]); }); // 保存尺寸 document.getElementById('vfr-save-measurements').addEventListener('click', () => { this.saveMeasurements(); }); // 自动检测 document.getElementById('vfr-auto-detect').addEventListener('click', () => { this.autoDetectMeasurements(); }); // 颜色选择 document.querySelectorAll('.vfr-color-option').forEach(option => { option.addEventListener('click', (e) => { this.selectColor(e.target.dataset.color); }); }); // 尺寸选择 document.querySelectorAll('.vfr-size-option').forEach(option => { option.addEventListener('click', (e) => { this.selectSize(e.target.dataset.size); }); }); // 处理试衣 document.getElementById('vfr-process-fitting').addEventListener('click', () => { this.processFitting(); }); } setupDragAndDrop() { const uploadArea = document.getElementById
发表评论手把手教程:在WordPress中集成多平台社交媒体内容聚合与展示 引言:为什么需要社交媒体内容聚合? 在当今数字营销时代,社交媒体已成为品牌建设、用户互动和内容传播的核心渠道。然而,随着品牌在多平台(如微博、微信公众号、Twitter、Facebook、Instagram、LinkedIn等)的布局,内容分散管理的问题日益凸显。WordPress作为全球最流行的内容管理系统,通过代码二次开发实现社交媒体内容聚合与展示,能够帮助网站管理员: 提升内容展示效率:自动聚合多平台内容,减少手动更新工作量 增强用户参与度:集中展示社交媒体动态,鼓励用户跨平台互动 强化品牌一致性:统一设计和展示风格,提升品牌专业形象 优化SEO效果:新鲜、多样化的内容有助于提升搜索引擎排名 本教程将详细指导您通过WordPress代码二次开发,实现一个功能完善的多平台社交媒体内容聚合系统。 第一部分:准备工作与环境配置 1.1 开发环境搭建 在开始开发前,确保您已具备以下环境: 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel WordPress安装:最新稳定版本(建议5.8以上) 代码编辑器:VS Code、Sublime Text或PHPStorm 浏览器开发者工具:用于调试前端代码 1.2 创建自定义插件框架 为避免主题更新导致代码丢失,我们将创建一个独立插件: <?php /** * Plugin Name: 多平台社交媒体聚合器 * Plugin URI: https://yourwebsite.com/ * Description: 聚合并展示多平台社交媒体内容 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SMA_VERSION', '1.0.0'); define('SMA_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SMA_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 add_action('plugins_loaded', 'sma_init'); function sma_init() { // 检查必要扩展 if (!extension_loaded('curl')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>社交媒体聚合器需要cURL扩展支持,请联系主机提供商启用。</p></div>'; }); return; } // 加载核心类 require_once SMA_PLUGIN_DIR . 'includes/class-social-aggregator.php'; require_once SMA_PLUGIN_DIR . 'includes/class-api-manager.php'; require_once SMA_PLUGIN_DIR . 'includes/class-display-engine.php'; // 初始化主类 $social_aggregator = new Social_Media_Aggregator(); $social_aggregator->init(); } 1.3 数据库表设计 我们需要创建数据表来存储聚合的内容: // 在插件激活时创建表 register_activation_hook(__FILE__, 'sma_create_tables'); function sma_create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'social_media_posts'; $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_avatar varchar(500), content text, media_urls text, post_url varchar(500), likes_count int(11) DEFAULT 0, shares_count int(11) DEFAULT 0, comments_count int(11) DEFAULT 0, post_time datetime NOT NULL, fetched_time datetime NOT NULL, is_active tinyint(1) DEFAULT 1, PRIMARY KEY (id), UNIQUE KEY platform_post (platform, post_id), KEY post_time (post_time), KEY platform (platform) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 创建缓存表 $cache_table = $wpdb->prefix . 'social_media_cache'; $cache_sql = "CREATE TABLE IF NOT EXISTS $cache_table ( cache_key varchar(255) NOT NULL, cache_value longtext, expiration datetime NOT NULL, PRIMARY KEY (cache_key) ) $charset_collate;"; dbDelta($cache_sql); } 第二部分:多平台API集成 2.1 API管理器设计 创建一个统一的API管理器类,处理不同平台的认证和请求: <?php // includes/class-api-manager.php class Social_Media_API_Manager { private $platforms = []; private $cache_time = 3600; // 缓存1小时 public function __construct() { $this->platforms = [ 'weibo' => [ 'name' => '微博', 'api_base' => 'https://api.weibo.com/2/', 'auth_type' => 'oauth2' ], 'wechat' => [ 'name' => '微信公众号', 'api_base' => 'https://api.weixin.qq.com/cgi-bin/', 'auth_type' => 'oauth2' ], 'twitter' => [ 'name' => 'Twitter', 'api_base' => 'https://api.twitter.com/2/', 'auth_type' => 'oauth2' ], 'facebook' => [ 'name' => 'Facebook', 'api_base' => 'https://graph.facebook.com/v12.0/', 'auth_type' => 'oauth2' ], 'instagram' => [ 'name' => 'Instagram', 'api_base' => 'https://graph.facebook.com/v12.0/', 'auth_type' => 'oauth2' ] ]; } /** * 获取平台数据 */ public function fetch_posts($platform, $params = []) { // 检查缓存 $cache_key = 'sma_' . $platform . '_' . md5(serialize($params)); $cached = $this->get_cache($cache_key); if ($cached !== false) { return $cached; } // 根据平台调用不同的API方法 $method_name = 'fetch_' . $platform . '_posts'; if (method_exists($this, $method_name)) { $posts = $this->$method_name($params); // 缓存结果 $this->set_cache($cache_key, $posts); return $posts; } return new WP_Error('unsupported_platform', '不支持的社交媒体平台'); } /** * 获取微博内容 */ private function fetch_weibo_posts($params) { $defaults = [ 'count' => 20, 'screen_name' => get_option('sma_weibo_username'), 'access_token' => get_option('sma_weibo_access_token') ]; $params = wp_parse_args($params, $defaults); $url = add_query_arg([ 'screen_name' => $params['screen_name'], 'count' => $params['count'], 'access_token' => $params['access_token'] ], 'https://api.weibo.com/2/statuses/user_timeline.json'); $response = wp_remote_get($url, [ 'timeout' => 15, 'sslverify' => false ]); if (is_wp_error($response)) { return $response; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (isset($data['error'])) { return new WP_Error('weibo_api_error', $data['error']); } return $this->parse_weibo_posts($data); } /** * 解析微博数据 */ private function parse_weibo_posts($data) { $posts = []; if (!isset($data['statuses']) || empty($data['statuses'])) { return $posts; } foreach ($data['statuses'] as $status) { $post = [ 'platform' => 'weibo', 'post_id' => $status['id'], 'author_name' => $status['user']['screen_name'], 'author_avatar' => $status['user']['profile_image_url'], 'content' => $this->format_weibo_content($status['text']), 'media_urls' => [], 'post_url' => 'https://weibo.com/' . $status['user']['id'] . '/' . $status['bid'], 'likes_count' => $status['attitudes_count'], 'shares_count' => $status['reposts_count'], 'comments_count' => $status['comments_count'], 'post_time' => date('Y-m-d H:i:s', strtotime($status['created_at'])) ]; // 处理图片 if (isset($status['pic_urls']) && !empty($status['pic_urls'])) { foreach ($status['pic_urls'] as $pic) { $post['media_urls'][] = $pic['thumbnail_pic']; } } // 处理视频 if (isset($status['page_info']) && $status['page_info']['type'] == 'video') { $post['media_urls'][] = $status['page_info']['page_url']; } $posts[] = $post; } return $posts; } /** * 格式化微博内容(处理链接、话题、@用户) */ private function format_weibo_content($text) { // 处理链接 $text = preg_replace( '/(https?://[^s]+)/', '<a href="$1" target="_blank" rel="nofollow">$1</a>', $text ); // 处理话题 $text = preg_replace( '/#([^#]+)#/', '<a href="https://s.weibo.com/weibo?q=$1" target="_blank">#$1#</a>', $text ); // 处理@用户 $text = preg_replace( '/@([wx{4e00}-x{9fa5}-]+)/u', '<a href="https://weibo.com/n/$1" target="_blank">@$1</a>', $text ); return $text; } /** * 获取微信公众号文章 */ private function fetch_wechat_posts($params) { // 微信公众号API实现 // 注意:微信公众号API需要服务号并认证,这里仅展示框架 $access_token = $this->get_wechat_access_token(); if (is_wp_error($access_token)) { return $access_token; } // 获取素材列表 $url = add_query_arg([ 'access_token' => $access_token, 'type' => 'news', 'offset' => 0, 'count' => 20 ], 'https://api.weixin.qq.com/cgi-bin/material/batchget_material'); $response = wp_remote_post($url, [ 'timeout' => 15, 'sslverify' => false ]); // 解析响应... return []; } /** * 缓存管理方法 */ private function get_cache($key) { global $wpdb; $table = $wpdb->prefix . 'social_media_cache'; $result = $wpdb->get_row($wpdb->prepare( "SELECT cache_value FROM $table WHERE cache_key = %s AND expiration > %s", $key, current_time('mysql') )); if ($result) { return maybe_unserialize($result->cache_value); } return false; } private function set_cache($key, $value) { global $wpdb; $table = $wpdb->prefix . 'social_media_cache'; $expiration = date('Y-m-d H:i:s', time() + $this->cache_time); $wpdb->replace($table, [ 'cache_key' => $key, 'cache_value' => maybe_serialize($value), 'expiration' => $expiration ]); } } 2.2 OAuth认证集成 为安全地存储API密钥,创建设置页面: // includes/class-settings.php class Social_Media_Aggregator_Settings { public function __construct() { add_action('admin_menu', [$this, 'add_admin_menu']); add_action('admin_init', [$this, 'register_settings']); } public function add_admin_menu() { add_options_page( '社交媒体聚合设置', '社交聚合', 'manage_options', 'social-media-aggregator', [$this, 'render_settings_page'] ); } public function register_settings() { // 注册平台设置 $platforms = ['weibo', 'wechat', 'twitter', 'facebook', 'instagram']; foreach ($platforms as $platform) { register_setting('sma_settings', 'sma_' . $platform . '_enabled'); register_setting('sma_settings', 'sma_' . $platform . '_username'); register_setting('sma_settings', 'sma_' . $platform . '_access_token'); register_setting('sma_settings', 'sma_' . $platform . '_app_id'); register_setting('sma_settings', 'sma_' . $platform . '_app_secret'); } // 显示设置 register_setting('sma_settings', 'sma_display_limit'); register_setting('sma_settings', 'sma_update_interval'); register_setting('sma_settings', 'sma_display_style'); } public function render_settings_page() { ?> <div class="wrap"> <h1>社交媒体聚合设置</h1> <form method="post" action="options.php"> <?php settings_fields('sma_settings'); ?> <h2 class="title">平台配置</h2> <table class="form-table"> <tr> <th scope="row">微博配置</th> <td> <label> <input type="checkbox" name="sma_weibo_enabled" value="1" <?php checked(1, get_option('sma_weibo_enabled')); ?>> 启用微博聚合 </label> <p class="description">需要微博开放平台Access Token</p> <p> <label>用户名:<br> <input type="text" name="sma_weibo_username" value="<?php echo esc_attr(get_option('sma_weibo_username')); ?>" class="regular-text"> </label> </p> <p> <label>Access Token:<br> <input type="password" name="sma_weibo_access_token" value="<?php echo esc_attr(get_option('sma_weibo_access_token')); ?>" class="regular-text"> </label> </p> </td> </tr> <!-- 其他平台配置类似 --> </table> <h2 class="title">显示设置</h2> <table class="form-table"> <tr> <th scope="row">显示数量</th> <td> <input type="number" name="sma_display_limit" value="<?php echo esc_attr(get_option('sma_display_limit', 10)); ?>" min="1" max="50"> <p class="description">每页显示的内容数量</p> </td> </tr> <tr> <th scope="row">更新频率</th> <td> <select name="sma_update_interval"> <option value="1800" <?php selected(get_option('sma_update_interval'), 1800); ?>>30分钟</option> <option value="3600" <?php selected(get_option('sma_update_interval'), 3600); ?>>1小时</option> <option value="7200" <?php selected(get_option('sma_update_interval'), 7200); ?>>2小时</option> <option value="21600" <?php selected(get_option('sma_update_interval'), 21600); ?>>6小时</option> </select> </td> </tr> </table> <?php submit_button(); ?> </form> <div class="card"> <h3>API配置指南</h3> <ol> <li>微博:访问 <a href="https://open.weibo.com/" target="_blank">微博开放平台</a> 创建应用获取Access Token</li> <li>微信公众号:需要认证服务号,在 <a href="https://mp.weixin.qq.com/" target="_blank">微信公众平台</a> 获取凭证</li> <li>Twitter:访问 <a href="https://developer.twitter.com/" target="_blank">Twitter开发者平台</a></li> </ol> </div> </div> <?php } } 第三部分:内容展示引擎开发 3.1 短代码系统实现 创建短代码以便在文章和页面中插入社交媒体内容: // includes/class-display-engine.php class Social_Media_Display_Engine { private $api_manager; public function __construct($api_manager) { $this->api_manager = $api_manager; $this->init_hooks(); } private function init_hooks() { // 注册短代码 add_shortcode('social_media_feed', [$this, 'render_feed_shortcode']); // 注册小工具 add_action('widgets_init', function() { register_widget('Social_Media_Feed_Widget'); }); // 注册Gutenberg块 add_action('init', [$this, 'register_gutenberg_block']); // AJAX加载更多 add_action('wp_ajax_sma_load_more', [$this, 'ajax_load_more']); add_action('wp_ajax_nopriv_sma_load_more', [$this, 'ajax_load_more']); } /** * 短代码渲染 */ public function render_feed_shortcode($atts) { $atts = shortcode_atts([ 'platform' => 'all', // all, weibo, wechat, twitter等 'limit' => get_option('sma_display_limit', 10), 'layout' => 'grid', // grid, list, masonry 'show_avatars' => 'yes', 'show_metrics' => 'yes', 'auto_refresh' => 'no', 'filter' => '' // 过滤关键词 ], $atts, 'social_media_feed'); // 获取数据 $posts = $this->get_posts_for_display($atts); if (empty($posts)) { return '<div class="sma-no-posts">暂无社交媒体内容</div>'; } // 渲染输出 ob_start(); ?> <div class="social-media-aggregator" data-platform="<?php echo esc_attr($atts['platform']); ?>" data-limit="<?php echo esc_attr($atts['limit']); ?>" data-layout="<?php echo esc_attr($atts['layout']); ?>" data-page="1"> <?php if ($atts['filter']): ?> <div class="sma-feed-filter"> <input type="text" class="sma-filter-input" placeholder="搜索内容..." data-filter="<?php echo esc_attr($atts['filter']); ?>"> <button class="sma-filter-btn">搜索</button> </div> <?php endif; ?> <div class="sma-feed-container sma-layout-<?php echo esc_attr($atts['layout']); ?>"> <?php foreach ($posts as $post): ?> <?php $this->render_post_card($post, $atts); ?> <?php endforeach; ?> </div> <?php if (count($posts) >= $atts['limit']): ?> <div class="sma-load-more-container"> <button class="sma-load-more-btn">加载更多</button> <div class="sma-loading" style="display:none;"> <div class="sma-spinner"></div> 加载中... </div> </div> <?php endif; ?> </div> <?php if ($atts['auto_refresh'] === 'yes'): ?> <script> jQuery(document).ready(function($) { // 每5分钟自动刷新 setInterval(function() { var container = $('.social-media-aggregator'); var platform = container.data('platform'); var limit = container.data('limit'); $.ajax({ url: '<?php echo admin_url('admin-ajax.php'); ?>', type: 'POST', data: { action: 'sma_refresh_feed', platform: platform, limit: limit, nonce: '<?php echo wp_create_nonce('sma_refresh'); ?>' }, success: function(response) { if (response.success) { container.find('.sma-feed-container').html(response.data.html); } } }); }, 300000); // 5分钟 }); </script> <?php endif; return ob_get_clean(); } /** * 渲染单个内容卡片 */ private function render_post_card($post, $atts) { $platform_class = 'sma-platform-' . $post['platform']; $media_html = ''; // 处理媒体内容 if (!empty($post['media_urls'])) { $media_html = $this->render_media_content($post['media_urls'], $post['platform']); } // 格式化时间 $time_diff = human_time_diff(strtotime($post['post_time']), current_time('timestamp')); ?> <div class="sma-post-card <?php echo esc_attr($platform_class); ?>" data-post-id="<?php echo esc_attr($post['post_id']); ?>" data-platform="<?php echo esc_attr($post['platform']); ?>"> <div class="sma-post-header"> <?php if ($atts['show_avatars'] === 'yes' && !empty($post['author_avatar'])): ?> <div class="sma-author-avatar"> <img src="<?php echo esc_url($post['author_avatar']); ?>" alt="<?php echo esc_attr($post['author_name']); ?>" onerror="this.src='<?php echo SMA_PLUGIN_URL; ?>assets/default-avatar.png'"> </div> <?php endif; ?> <div class="sma-author-info"> <div class="sma-author-name"> <?php echo esc_html($post['author_name']); ?> <span class="sma-platform-badge"><?php echo $this->get_platform_name($post['platform']); ?></span> </div> <div class="sma-post-time" title="<?php echo esc_attr($post['post_time']); ?>"> <?php echo $time_diff; ?>前 </div> </div> <div class="sma-platform-icon"> <?php echo $this->get_platform_icon($post['platform']); ?> </div> </div> <div class="sma-post-content"> <?php echo wp_kses_post($post['content']); ?> </div> <?php if ($media_html): ?> <div class="sma-post-media"> <?php echo $media_html; ?> </div> <?php endif; ?> <?php if ($atts['show_metrics'] === 'yes'): ?> <div class="sma-post-metrics"> <span class="sma-metric sma-likes" title="点赞"> <i class="sma-icon-heart"></i> <?php echo $this->format_number($post['likes_count']); ?> </span> <span class="sma-metric sma-shares" title="分享"> <i class="sma-icon-share"></i> <?php echo $this->format_number($post['shares_count']); ?> </span> <span class="sma-metric sma-comments" title="评论"> <i class="sma-icon-comment"></i> <?php echo $this->format_number($post['comments_count']); ?> </span> <a href="<?php echo esc_url($post['post_url']); ?>" target="_blank" rel="nofollow noopener" class="sma-original-link"> 查看原文 </a> </div> <?php endif; ?> <div class="sma-post-actions"> <button class="sma-action-btn sma-like-btn" data-post-id="<?php echo esc_attr($post['post_id']); ?>"> <i class="sma-icon-heart"></i> 点赞 </button> <button class="sma-action-btn sma-share-btn" data-post-url="<?php echo esc_url($post['post_url']); ?>"> <i class="sma-icon-share"></i> 分享 </button> <button class="sma-action-btn sma-embed-btn" data-embed-code="<?php echo esc_attr($this->generate_embed_code($post)); ?>"> <i class="sma-icon-embed"></i> 嵌入 </button> </div> </div> <?php } /** * 渲染媒体内容 */ private function render_media_content($media_urls, $platform) { if (empty($media_urls)) return ''; ob_start(); // 判断媒体类型 $first_media = $media_urls[0]; $is_video = preg_match('/(.mp4|.mov|.avi|.webm|youtube.com|vimeo.com)/i', $first_media); if ($is_video) { // 视频内容 ?> <div class="sma-video-container"> <video controls preload="metadata" poster="<?php echo SMA_PLUGIN_URL; ?>assets/video-poster.jpg"> <source src="<?php echo esc_url($first_media); ?>" type="video/mp4"> 您的浏览器不支持视频播放 </video> </div> <?php } else { // 图片内容 $total_images = count($media_urls); ?> <div class="sma-image-gallery" data-total="<?php echo $total_images; ?>"> <?php foreach ($media_urls as $index => $image_url): ?> <?php if ($index < 4): // 最多显示4张 ?> <div class="sma-image-item <?php echo $total_images > 1 ? 'sma-multiple' : 'sma-single'; ?>" style="background-image: url('<?php echo esc_url($image_url); ?>')" data-index="<?php echo $index; ?>"> <?php if ($index === 3 && $total_images > 4): ?> <div class="sma-image-more">+<?php echo $total_images - 4; ?></div> <?php endif; ?> <img src="<?php echo esc_url($image_url); ?>" alt="社交媒体图片" loading="lazy"> </div> <?php endif; ?> <?php endforeach; ?> </div> <?php if ($total_images > 1): ?> <div class="sma-gallery-lightbox" style="display:none;"> <div class="sma-lightbox-content"> <button class="sma-lightbox-close">×</button> <div class="sma-lightbox-slider"></div> <div class="sma-lightbox-caption"></div> <button class="sma-lightbox-prev">‹</button> <button class="sma-lightbox-next">›</button> </div> </div> <?php endif; } return ob_get_clean(); } /** * AJAX加载更多 */ public function ajax_load_more() { check_ajax_referer('sma_ajax', 'nonce'); $page = intval($_POST['page']); $platform = sanitize_text_field($_POST['platform']); $limit = intval($_POST['limit']); $layout = sanitize_text_field($_POST['layout']); $offset = ($page - 1) * $limit; // 获取更多数据 $posts = $this->get_posts_for_display([ 'platform' => $platform, 'limit' => $limit, 'offset' => $offset ]); if (empty($posts)) { wp_send_json_error(['message' => '没有更多内容']); } // 渲染HTML ob_start(); foreach ($posts as $post) { $this->render_post_card($post, [ 'show_avatars' => 'yes', 'show_metrics' => 'yes', 'layout' => $layout ]); } $html = ob_get_clean(); wp_send_json_success([ 'html' => $html, 'has_more' => count($posts) >= $limit ]); } /** * 获取展示用的数据 */ private function get_posts_for_display($args) { global $wpdb; $table_name = $wpdb->prefix . 'social_media_posts'; $defaults = [ 'platform' => 'all', 'limit' => 10, 'offset' => 0, 'filter' => '' ]; $args = wp_parse_args($args, $defaults); // 构建查询 $where = ['is_active = 1']; $params = []; if ($args['platform'] !== 'all') { $where[] = 'platform = %s'; $params[] = $args['platform']; } if (!empty($args['filter'])) { $where[] = '(content LIKE %s OR author_name LIKE %s)'; $search_term = '%' . $wpdb->esc_like($args['filter']) . '%'; $params[] = $search_term; $params[] = $search_term; } $where_sql = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : ''; $query = $wpdb->prepare( "SELECT * FROM $table_name $where_sql ORDER BY post_time DESC LIMIT %d OFFSET %d", array_merge($params, [$args['limit'], $args['offset']]) ); return $wpdb->get_results($query, ARRAY_A); } /** * 获取平台名称 */ private function get_platform_name($platform) { $names = [ 'weibo' => '微博', 'wechat' => '微信', 'twitter' => 'Twitter', 'facebook' => 'Facebook', 'instagram' => 'Instagram' ]; return $names[$platform] ?? $platform; } /** * 获取平台图标 */ private function get_platform_icon($platform) { $icons = [ 'weibo' => '<svg viewBox="0 0 24 24"><path d="M20.194 3.46c-1.802.81-3.73 1.36-5.758 1.61.022.16.033.322.033.486 0 2.33-.94 4.43-2.46 5.94-1.52 1.51-3.61 2.45-5.94 2.45-.164 0-.326-.01-.486-.03C4.9 15.444 4.35 17.372 3.54 19.174c1.8-.81 3.73-1.36 5.76-1.61-.02-.16-.03-.322-.03-.486 0-2.33.94-4.43 2.46-5.94 1.52-1.51 3.61-2.45 5.94-2.45.164 0 .326.01.486.03.25-1.986.8-3.914 1.61-5.716z"/></svg>',
发表评论详细教程:为网站打造会员专属社区与论坛互动系统,通过WordPress代码二次开发实现常用互联网小工具功能 引言:为什么需要会员专属社区与论坛系统? 在当今互联网环境中,用户参与度和社区粘性已成为网站成功的关键因素。一个功能完善的会员专属社区与论坛系统不仅能提升用户体验,还能有效增加用户留存时间、促进内容生成和增强品牌忠诚度。对于内容创作者、在线教育平台、企业官网或任何希望建立稳定用户群体的网站来说,会员社区都是不可或缺的组成部分。 WordPress作为全球最流行的内容管理系统,拥有强大的扩展性和灵活性。通过代码二次开发,我们可以在WordPress平台上构建出功能丰富、高度定制化的会员社区系统,同时集成各种实用的互联网小工具,为用户提供全方位的互动体验。 本教程将详细指导您如何从零开始,通过WordPress代码二次开发,打造一个功能完整的会员专属社区与论坛互动系统,并集成常用互联网小工具功能。 第一部分:前期准备与环境搭建 1.1 选择合适的WordPress主题 在开始开发之前,选择一个适合社区建设的WordPress主题至关重要。建议选择以下类型的主题: 响应式设计:确保社区在所有设备上都能良好显示 轻量级框架:避免过多不必要的功能影响性能 良好的扩展性:支持自定义开发和无冲突的代码结构 社区友好型:内置或兼容论坛、用户资料等社区功能 推荐主题:Astra、GeneratePress、OceanWP等轻量级多功能主题。 1.2 必备插件安装与配置 虽然我们将主要通过代码开发实现功能,但一些基础插件可以节省开发时间: BuddyPress:提供基础的社交网络功能框架 bbPress:轻量级论坛系统,与WordPress完美集成 MemberPress或WooCommerce Memberships:会员管理系统 Advanced Custom Fields:自定义字段管理,便于扩展用户资料 User Role Editor:用户角色和权限管理 1.3 开发环境配置 为了安全地进行代码开发,建议配置本地开发环境: 安装Local by Flywheel或XAMPP作为本地服务器环境 设置调试模式:在wp-config.php中添加以下代码: define('WP_DEBUG', true); define('WP_DEBUG_LOG', true); define('WP_DEBUG_DISPLAY', false); 安装代码编辑器:VS Code、Sublime Text或PHPStorm 配置版本控制系统(Git)以便代码管理 第二部分:会员系统核心功能开发 2.1 自定义用户角色与权限系统 WordPress默认的用户角色可能无法满足社区需求,我们需要创建更精细的角色体系: // 在主题的functions.php或自定义插件中添加 function create_community_user_roles() { // 添加青铜会员角色 add_role('bronze_member', '青铜会员', array( 'read' => true, 'edit_posts' => true, 'delete_posts' => false, 'publish_posts' => false, 'upload_files' => true, 'participate_in_forum' => true, 'access_basic_content' => true )); // 添加白银会员角色 add_role('silver_member', '白银会员', array( 'read' => true, 'edit_posts' => true, 'delete_posts' => true, 'publish_posts' => true, 'upload_files' => true, 'participate_in_forum' => true, 'access_premium_content' => true, 'create_topics' => true )); // 添加黄金会员角色 add_role('gold_member', '黄金会员', array( 'read' => true, 'edit_posts' => true, 'edit_others_posts' => true, 'delete_posts' => true, 'delete_others_posts' => false, 'publish_posts' => true, 'upload_files' => true, 'participate_in_forum' => true, 'access_all_content' => true, 'create_topics' => true, 'moderate_comments' => true, 'access_exclusive_areas' => true )); } add_action('init', 'create_community_user_roles'); 2.2 扩展用户资料字段 使用Advanced Custom Fields插件或自定义代码扩展用户资料: // 自定义用户资料字段 function add_custom_user_profile_fields($user) { if(!current_user_can('edit_user', $user->ID)) { return false; } ?> <h3>社区资料</h3> <table class="form-table"> <tr> <th><label for="user_location">所在地</label></th> <td> <input type="text" name="user_location" id="user_location" value="<?php echo esc_attr(get_the_author_meta('user_location', $user->ID)); ?>" class="regular-text" /> </td> </tr> <tr> <th><label for="user_occupation">职业</label></th> <td> <input type="text" name="user_occupation" id="user_occupation" value="<?php echo esc_attr(get_the_author_meta('user_occupation', $user->ID)); ?>" class="regular-text" /> </td> </tr> <tr> <th><label for="user_interests">兴趣标签</label></th> <td> <input type="text" name="user_interests" id="user_interests" value="<?php echo esc_attr(get_the_author_meta('user_interests', $user->ID)); ?>" class="regular-text" /> <p class="description">用逗号分隔多个兴趣标签</p> </td> </tr> <tr> <th><label for="user_signature">个人签名</label></th> <td> <textarea name="user_signature" id="user_signature" rows="3" cols="30"><?php echo esc_textarea(get_the_author_meta('user_signature', $user->ID)); ?></textarea> </td> </tr> </table> <?php } add_action('show_user_profile', 'add_custom_user_profile_fields'); add_action('edit_user_profile', 'add_custom_user_profile_fields'); // 保存自定义用户字段 function save_custom_user_profile_fields($user_id) { if(!current_user_can('edit_user', $user_id)) { return false; } update_user_meta($user_id, 'user_location', sanitize_text_field($_POST['user_location'])); update_user_meta($user_id, 'user_occupation', sanitize_text_field($_POST['user_occupation'])); update_user_meta($user_id, 'user_interests', sanitize_text_field($_POST['user_interests'])); update_user_meta($user_id, 'user_signature', sanitize_textarea_field($_POST['user_signature'])); } add_action('personal_options_update', 'save_custom_user_profile_fields'); add_action('edit_user_profile_update', 'save_custom_user_profile_fields'); 2.3 会员等级与权限控制 实现基于会员等级的权限控制系统: // 检查用户是否有特定权限 function community_user_can($user_id, $capability) { $user = get_user_by('id', $user_id); if(!$user) { return false; } // 获取用户角色 $user_roles = $user->roles; // 定义各角色权限映射 $role_capabilities = array( 'bronze_member' => array( 'access_basic_content', 'participate_in_forum', 'view_profiles' ), 'silver_member' => array( 'access_basic_content', 'access_premium_content', 'participate_in_forum', 'create_topics', 'view_profiles', 'send_private_messages' ), 'gold_member' => array( 'access_basic_content', 'access_premium_content', 'access_all_content', 'participate_in_forum', 'create_topics', 'moderate_comments', 'view_profiles', 'send_private_messages', 'access_exclusive_areas', 'download_resources' ), 'administrator' => array('all') // 管理员拥有所有权限 ); // 检查用户是否有权限 foreach($user_roles as $role) { if(array_key_exists($role, $role_capabilities)) { if(in_array('all', $role_capabilities[$role]) || in_array($capability, $role_capabilities[$role])) { return true; } } } return false; } // 使用示例:在模板中检查权限 function display_premium_content() { $user_id = get_current_user_id(); if(community_user_can($user_id, 'access_premium_content')) { // 显示高级内容 echo '<div class="premium-content">这里是高级会员专属内容</div>'; } else { // 显示升级提示 echo '<div class="upgrade-prompt">升级为高级会员以查看此内容</div>'; } } 第三部分:论坛系统开发与集成 3.1 基于bbPress的论坛定制 bbPress是一个轻量级论坛插件,我们可以通过代码进行深度定制: // 自定义论坛主题模板 function custom_bbpress_templates() { // 重写bbPress模板路径 return get_stylesheet_directory() . '/bbpress/'; } add_filter('bbp_get_template_stack', 'custom_bbpress_templates', 10, 1); // 创建自定义论坛分类 function create_custom_forum_structure() { // 技术讨论区 $tech_forum_id = wp_insert_post(array( 'post_title' => '技术讨论区', 'post_content' => '关于网站开发、编程技术的讨论', 'post_status' => 'publish', 'post_type' => 'forum', 'post_author' => 1 )); // 在技术讨论区下创建子论坛 if($tech_forum_id) { wp_insert_post(array( 'post_title' => '前端开发', 'post_content' => 'HTML、CSS、JavaScript等前端技术讨论', 'post_status' => 'publish', 'post_type' => 'forum', 'post_parent' => $tech_forum_id, 'post_author' => 1 )); wp_insert_post(array( 'post_title' => '后端开发', 'post_content' => 'PHP、Python、数据库等后端技术讨论', 'post_status' => 'publish', 'post_type' => 'forum', 'post_parent' => $tech_forum_id, 'post_author' => 1 )); } // 创建其他论坛分类... } // 注释掉这行,除非您确实需要创建论坛结构 // add_action('init', 'create_custom_forum_structure'); 3.2 论坛权限与访问控制 // 限制特定论坛的访问权限 function restrict_forum_access($capabilities, $forum_id) { // 获取当前用户 $user_id = get_current_user_id(); // 获取论坛元数据 $forum_access_level = get_post_meta($forum_id, '_forum_access_level', true); // 如果没有设置访问级别,使用默认权限 if(empty($forum_access_level)) { return $capabilities; } // 检查用户是否有访问权限 if(!community_user_can($user_id, 'access_' . $forum_access_level . '_content')) { // 如果没有权限,移除阅读能力 $capabilities['read_others_topics'] = false; $capabilities['view_forum'] = false; } return $capabilities; } add_filter('bbp_get_forum_caps', 'restrict_forum_access', 10, 2); // 在论坛编辑页面添加访问级别选择 function add_forum_access_level_metabox() { add_meta_box( 'forum_access_level', '论坛访问级别', 'render_forum_access_level_metabox', 'forum', 'side', 'high' ); } add_action('add_meta_boxes', 'add_forum_access_level_metabox'); function render_forum_access_level_metabox($post) { wp_nonce_field('save_forum_access_level', 'forum_access_level_nonce'); $current_value = get_post_meta($post->ID, '_forum_access_level', true); $options = array( 'basic' => '基础会员', 'premium' => '高级会员', 'all' => '所有会员' ); echo '<select name="forum_access_level" id="forum_access_level">'; foreach($options as $value => $label) { echo '<option value="' . esc_attr($value) . '" ' . selected($current_value, $value, false) . '>' . esc_html($label) . '</option>'; } echo '</select>'; echo '<p class="description">设置可以访问此论坛的最低会员级别</p>'; } function save_forum_access_level($post_id) { if(!isset($_POST['forum_access_level_nonce']) || !wp_verify_nonce($_POST['forum_access_level_nonce'], 'save_forum_access_level')) { return; } if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if(!current_user_can('edit_post', $post_id)) { return; } if(isset($_POST['forum_access_level'])) { update_post_meta($post_id, '_forum_access_level', sanitize_text_field($_POST['forum_access_level'])); } } add_action('save_post_forum', 'save_forum_access_level'); 3.3 论坛功能增强 // 添加点赞功能 function add_topic_like_system() { // 在前端添加点赞按钮 add_action('bbp_theme_after_topic_content', 'display_like_button'); // 处理AJAX点赞请求 add_action('wp_ajax_topic_like', 'handle_topic_like'); add_action('wp_ajax_nopriv_topic_like', 'require_login_for_like'); } add_action('init', 'add_topic_like_system'); function display_like_button() { if(!is_singular('topic')) { return; } $topic_id = get_the_ID(); $user_id = get_current_user_id(); $like_count = get_post_meta($topic_id, '_topic_likes', true) ?: 0; $user_liked = $user_id ? get_user_meta($user_id, '_liked_topic_' . $topic_id, true) : false; ?> <div class="topic-like-system"> <button class="like-button <?php echo $user_liked ? 'liked' : ''; ?>" data-topic-id="<?php echo $topic_id; ?>"> <span class="like-icon">👍</span> <span class="like-count"><?php echo $like_count; ?></span> </button> </div> <script> jQuery(document).ready(function($) { $('.like-button').on('click', function() { var button = $(this); var topicId = button.data('topic-id'); $.ajax({ url: '<?php echo admin_url('admin-ajax.php'); ?>', type: 'POST', data: { action: 'topic_like', topic_id: topicId, nonce: '<?php echo wp_create_nonce('topic_like_nonce'); ?>' }, success: function(response) { if(response.success) { button.toggleClass('liked'); button.find('.like-count').text(response.data.like_count); } else { alert(response.data.message); } } }); }); }); </script> <?php } function handle_topic_like() { // 验证nonce if(!wp_verify_nonce($_POST['nonce'], 'topic_like_nonce')) { wp_die('安全验证失败'); } $topic_id = intval($_POST['topic_id']); $user_id = get_current_user_id(); if(!$user_id) { wp_send_json_error(array('message' => '请先登录')); } $user_liked = get_user_meta($user_id, '_liked_topic_' . $topic_id, true); $like_count = get_post_meta($topic_id, '_topic_likes', true) ?: 0; if($user_liked) { // 取消点赞 delete_user_meta($user_id, '_liked_topic_' . $topic_id); $like_count = max(0, $like_count - 1); update_post_meta($topic_id, '_topic_likes', $like_count); } else { // 点赞 update_user_meta($user_id, '_liked_topic_' . $topic_id, true); $like_count++; update_post_meta($topic_id, '_topic_likes', $like_count); } wp_send_json_success(array('like_count' => $like_count)); } function require_login_for_like() { wp_send_json_error(array('message' => '请先登录')); } 第四部分:集成常用互联网小工具 4.1 实时通知系统 // 创建通知系统 class Community_Notification_System { private static $instance = null; public static function get_instance() { if(null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { 4.1 实时通知系统(续) add_action('wp_ajax_get_notifications', array($this, 'ajax_get_notifications')); add_action('wp_ajax_mark_notification_read', array($this, 'ajax_mark_notification_read')); add_action('wp_enqueue_scripts', array($this, 'enqueue_notification_scripts')); add_action('admin_bar_menu', array($this, 'add_notification_to_admin_bar'), 100); } // 创建新通知 public static function create_notification($user_id, $type, $message, $link = '', $sender_id = 0) { $notifications = get_user_meta($user_id, '_community_notifications', true); if(!is_array($notifications)) { $notifications = array(); } $notification = array( 'id' => uniqid('notif_'), 'type' => $type, 'message' => $message, 'link' => $link, 'sender_id' => $sender_id, 'timestamp' => current_time('timestamp'), 'read' => false ); array_unshift($notifications, $notification); // 只保留最近的50条通知 if(count($notifications) > 50) { $notifications = array_slice($notifications, 0, 50); } update_user_meta($user_id, '_community_notifications', $notifications); // 更新未读计数 self::update_unread_count($user_id); return $notification['id']; } // 更新未读计数 private static function update_unread_count($user_id) { $notifications = get_user_meta($user_id, '_community_notifications', true); $unread_count = 0; if(is_array($notifications)) { foreach($notifications as $notification) { if(!$notification['read']) { $unread_count++; } } } update_user_meta($user_id, '_community_unread_notifications', $unread_count); return $unread_count; } // 获取用户通知 public static function get_user_notifications($user_id, $limit = 20) { $notifications = get_user_meta($user_id, '_community_notifications', true); if(!is_array($notifications)) { return array(); } if($limit > 0) { $notifications = array_slice($notifications, 0, $limit); } return $notifications; } // 标记通知为已读 public static function mark_as_read($user_id, $notification_id = 'all') { $notifications = get_user_meta($user_id, '_community_notifications', true); if(!is_array($notifications)) { return false; } $updated = false; foreach($notifications as &$notification) { if($notification_id === 'all' || $notification['id'] === $notification_id) { if(!$notification['read']) { $notification['read'] = true; $updated = true; } } } if($updated) { update_user_meta($user_id, '_community_notifications', $notifications); self::update_unread_count($user_id); } return $updated; } // AJAX获取通知 public function ajax_get_notifications() { if(!is_user_logged_in()) { wp_die('请先登录'); } $user_id = get_current_user_id(); $notifications = self::get_user_notifications($user_id, 10); wp_send_json_success(array( 'notifications' => $notifications, 'unread_count' => get_user_meta($user_id, '_community_unread_notifications', true) ?: 0 )); } // AJAX标记通知为已读 public function ajax_mark_notification_read() { if(!is_user_logged_in()) { wp_die('请先登录'); } $user_id = get_current_user_id(); $notification_id = isset($_POST['notification_id']) ? sanitize_text_field($_POST['notification_id']) : 'all'; self::mark_as_read($user_id, $notification_id); wp_send_json_success(); } // 加载通知脚本 public function enqueue_notification_scripts() { if(!is_user_logged_in()) { return; } wp_enqueue_script('community-notifications', get_template_directory_uri() . '/js/notifications.js', array('jquery'), '1.0', true); wp_localize_script('community-notifications', 'communityNotifications', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('notification_nonce'), 'poll_interval' => 30000 // 30秒轮询一次 )); } // 在管理栏添加通知 public function add_notification_to_admin_bar($admin_bar) { if(!is_user_logged_in()) { return; } $user_id = get_current_user_id(); $unread_count = get_user_meta($user_id, '_community_unread_notifications', true) ?: 0; $admin_bar->add_node(array( 'id' => 'community-notifications', 'title' => '通知' . ($unread_count > 0 ? ' <span class="notification-count">' . $unread_count . '</span>' : ''), 'href' => '#', 'meta' => array( 'class' => 'community-notifications-menu', 'html' => $this->get_notification_dropdown_html() ) )); } // 获取通知下拉菜单HTML private function get_notification_dropdown_html() { if(!is_user_logged_in()) { return ''; } $user_id = get_current_user_id(); $notifications = self::get_user_notifications($user_id, 5); ob_start(); ?> <div class="notification-dropdown" style="display: none;"> <div class="notification-header"> <h3>通知</h3> <?php if(!empty($notifications)): ?> <a href="#" class="mark-all-read">全部标记为已读</a> <?php endif; ?> </div> <div class="notification-list"> <?php if(empty($notifications)): ?> <div class="notification-item no-notifications"> 暂无新通知 </div> <?php else: ?> <?php foreach($notifications as $notification): ?> <div class="notification-item <?php echo $notification['read'] ? 'read' : 'unread'; ?>" data-notification-id="<?php echo esc_attr($notification['id']); ?>"> <div class="notification-content"> <p><?php echo esc_html($notification['message']); ?></p> <span class="notification-time"><?php echo human_time_diff($notification['timestamp'], current_time('timestamp')) . '前'; ?></span> </div> <?php if(!empty($notification['link'])): ?> <a href="<?php echo esc_url($notification['link']); ?>" class="notification-link"></a> <?php endif; ?> </div> <?php endforeach; ?> <?php endif; ?> </div> <div class="notification-footer"> <a href="<?php echo esc_url(home_url('/notifications/')); ?>">查看所有通知</a> </div> </div> <?php return ob_get_clean(); } } // 初始化通知系统 Community_Notification_System::get_instance(); // 创建通知触发器 function trigger_community_notifications() { // 当用户收到私信时 add_action('bp_messages_message_sent', function($message) { $recipients = $message->get_recipients(); foreach($recipients as $recipient) { if($recipient->user_id != $message->sender_id) { $sender = get_userdata($message->sender_id); Community_Notification_System::create_notification( $recipient->user_id, 'message', $sender->display_name . ' 给您发送了一条私信', bp_core_get_user_domain($message->sender_id) . 'messages/view/' . $message->thread_id . '/', $message->sender_id ); } } }); // 当用户的话题收到回复时 add_action('bbp_new_reply', function($reply_id, $topic_id, $forum_id, $anonymous_data, $reply_author) { $topic_author = get_post_field('post_author', $topic_id); if($topic_author != $reply_author) { $reply_author_name = get_the_author_meta('display_name', $reply_author); Community_Notification_System::create_notification( $topic_author, 'reply', $reply_author_name . ' 回复了您的话题', get_permalink($reply_id), $reply_author ); } }, 10, 5); } add_action('init', 'trigger_community_notifications'); 4.2 私信系统 // 增强私信功能 class Enhanced_Private_Messaging { public function __construct() { // 检查BuddyPress是否存在 if(!function_exists('bp_is_active')) { return; } // 添加消息类型 add_filter('bp_messages_allowed_tags', array($this, 'allow_additional_message_tags')); // 添加消息附件功能 add_action('bp_after_messages_compose_content', array($this, 'add_attachment_field')); add_action('messages_message_sent', array($this, 'handle_message_attachment'), 10, 1); // 添加消息搜索 add_action('bp_messages_directory_search_form', array($this, 'add_message_search')); // 添加消息分类标签 add_action('bp_before_message_compose_input', array($this, 'add_message_labels')); } // 允许更多HTML标签 public function allow_additional_message_tags($allowed_tags) { $allowed_tags['img'] = array( 'src' => true, 'alt' => true, 'width' => true, 'height' => true, 'class' => true ); $allowed_tags['a']['target'] = true; $allowed_tags['code'] = array(); $allowed_tags['pre'] = array(); return $allowed_tags; } // 添加附件字段 public function add_attachment_field() { if(!community_user_can(get_current_user_id(), 'send_private_messages')) { return; } ?> <div class="message-attachment-field"> <label for="message-attachment">添加附件</label> <input type="file" name="message_attachment" id="message-attachment" accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.txt"> <p class="description">支持图片、文档文件,最大2MB</p> </div> <script> jQuery(document).ready(function($) { $('#send_message_form').on('submit', function(e) { var fileInput = $('#message-attachment')[0]; if(fileInput.files.length > 0) { var file = fileInput.files[0]; var maxSize = 2 * 1024 * 1024; // 2MB if(file.size > maxSize) { alert('文件大小不能超过2MB'); e.preventDefault(); return false; } // 创建FormData对象 var formData = new FormData(this); formData.append('message_attachment', file); // 使用AJAX提交表单 e.preventDefault(); $.ajax({ url: $(this).attr('action'), type: 'POST', data: formData, processData: false, contentType: false, success: function(response) { // 处理成功响应 window.location.reload(); } }); } }); }); </script> <?php } // 处理消息附件 public function handle_message_attachment($message) { if(!empty($_FILES['message_attachment']['name'])) { require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); $upload = wp_handle_upload($_FILES['message_attachment'], array( 'test_form' => false, 'action' => 'bp_message_attachment' )); if(!isset($upload['error'])) { // 保存附件信息到消息元数据 bp_messages_update_meta($message->id, 'attachment_url', $upload['url']); bp_messages_update_meta($message->id, 'attachment_type', $_FILES['message_attachment']['type']); bp_messages_update_meta($message->id, 'attachment_name', $_FILES['message_attachment']['name']); } } } // 显示消息附件 public static function display_message_attachment($message_id) { $attachment_url = bp_messages_get_meta($message_id, 'attachment_url', true); if($attachment_url) { $attachment_type = bp_messages_get_meta($message_id, 'attachment_type', true); $attachment_name = bp_messages_get_meta($message_id, 'attachment_name', true); if(strpos($attachment_type, 'image/') === 0) { echo '<div class="message-attachment image-attachment">'; echo '<a href="' . esc_url($attachment_url) . '" target="_blank">'; echo '<img src="' . esc_url($attachment_url) . '" alt="' . esc_attr($attachment_name) . '" style="max-width: 300px;">'; echo '</a>'; echo '</div>'; } else { echo '<div class="message-attachment file-attachment">'; echo '<a href="' . esc_url($attachment_url) . '" target="_blank" class="attachment-link">'; echo '<span class="attachment-icon">📎</span>'; echo '<span class="attachment-name">' . esc_html($attachment_name) . '</span>'; echo '</a>'; echo '</div>'; } } } // 添加消息搜索 public function add_message_search() { ?> <div class="message-search-form"> <form action="<?php echo bp_get_messages_directory_permalink(); ?>" method="get"> <input type="text" name="s" placeholder="搜索消息..." value="<?php echo isset($_GET['s']) ? esc_attr($_GET['s']) : ''; ?>"> <button type="submit">搜索</button> </form> </div> <?php } // 添加消息标签 public function add_message_labels() { ?> <div class="message-labels"> <label>消息标签:</label> <select name="message_label" id="message-label"> <option value="">无标签</option> <option value="important">重要</option> <option value="work">工作</option> <option value="personal">个人</option> <option value="question">问题</option> </select> </div> <?php } } // 初始化增强私信系统 new Enhanced_Private_Messaging(); // 在消息显示时添加附件 add_action('bp_after_message_content', function() { global $messages_template; if(isset($messages_template->message->id)) { Enhanced_Private_Messaging::display_message_attachment($messages_template->message->id); } }); 4.3 积分与成就系统 // 积分与成就系统 class Points_and_Achievements_System { private $achievements = array(); public function __construct() { $this->define_achievements(); add_action('init', array($this, 'register_points_post_type')); add_action('user_register', array($this, 'award_registration_points')); add_action('bbp_new_topic', array($this, 'award_topic_creation_points'), 10, 4); add_action('bbp_new_reply', array($this, 'award_reply_points'), 10, 5); add_action('bp_activity_add', array($this, 'check_activity_achievements')); add_action('wp_ajax_get_user_points', array($this, 'ajax_get_user_points')); add_action('wp_enqueue_scripts', array($this, 'enqueue_points_scripts')); // 添加积分显示到用户资料 add_action('bp_profile_header_meta', array($this, 'display_user_points_in_profile')); } // 定义成就 private function define_achievements() { $this->achievements = array( 'first_post' => array( 'name' => '初来乍到', 'description' => '发表第一个帖子', 'points' => 100, 'badge' => '🥇' ), 'active_member' => array( 'name' => '活跃会员', 'description' => '连续7天登录', 'points' => 200, 'badge' => '🔥' ), 'helpful_member' => array( 'name' => '热心助人', 'description' => '获得10个感谢', 'points' => 300, 'badge' => '❤️' ), 'expert' => array( 'name' => '社区专家', 'description' => '发表50个高质量回复', 'points' => 500, 'badge' => '👑' ), 'veteran' => array(
发表评论WordPress高级教程:开发集成AI的智能内容创作与辅助写作工具 引言:WordPress在AI时代的新机遇 WordPress作为全球最流行的内容管理系统,占据了互联网超过43%的网站份额。随着人工智能技术的飞速发展,将AI能力集成到WordPress平台已成为提升内容创作效率和质量的重要趋势。本教程将深入探讨如何通过WordPress代码二次开发,构建集成AI的智能内容创作与辅助写作工具,同时实现常用互联网小工具功能,为网站管理员和内容创作者提供全方位的智能解决方案。 在当今信息爆炸的时代,内容创作者面临着日益增长的创作压力和质量要求。AI技术的引入不仅可以自动化部分创作流程,还能提供智能建议、优化内容结构、增强可读性,甚至生成个性化内容。通过将AI能力与WordPress的强大扩展性相结合,我们可以打造出真正智能化的内容创作生态系统。 第一部分:WordPress开发环境搭建与AI技术选型 1.1 WordPress开发环境配置 要开始WordPress高级开发,首先需要搭建专业的开发环境。推荐使用以下配置: 本地开发环境:使用Local by Flywheel、XAMPP或Docker容器化环境 代码编辑器:VS Code配合PHP Intelephense、WordPress代码片段等扩展 调试工具:安装Query Monitor、Debug Bar等WordPress调试插件 版本控制:Git配合GitHub或GitLab进行代码管理 对于AI集成开发,还需要确保服务器环境满足以下要求: PHP 7.4或更高版本 至少256MB内存限制(建议512MB以上) 支持cURL扩展 可选的GPU加速支持(用于本地AI模型运行) 1.2 AI技术选型与集成策略 选择合适的AI技术是项目成功的关键。根据功能需求和资源情况,可以考虑以下方案: 云端AI服务集成: OpenAI GPT系列API:用于内容生成、改写、摘要 Google Cloud Natural Language:用于情感分析、实体识别 IBM Watson:用于语气分析、内容优化 国内可选:百度文心、阿里通义、腾讯混元等 本地AI模型部署: 使用Transformers库部署轻量级模型 利用ONNX Runtime优化推理性能 考虑使用GPT-2、BERT等开源模型 混合方案: 核心功能使用云端API保证质量 辅助功能使用本地模型降低成本 敏感数据处理使用本地模型确保隐私 第二部分:WordPress插件架构设计与AI集成 2.1 智能内容创作插件架构设计 创建一个名为"AI Content Creator Pro"的WordPress插件,采用模块化设计: /* Plugin Name: AI Content Creator Pro Plugin URI: https://yourwebsite.com/ai-content-creator Description: 集成AI的智能内容创作与辅助写作工具 Version: 1.0.0 Author: Your Name License: GPL v2 or later */ // 主插件类 class AI_Content_Creator_Pro { private static $instance = null; private $ai_services = []; private $tools_manager; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->define_constants(); $this->init_hooks(); $this->load_dependencies(); } private function define_constants() { define('AICC_VERSION', '1.0.0'); define('AICC_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('AICC_PLUGIN_URL', plugin_dir_url(__FILE__)); define('AICC_AI_CACHE_EXPIRY', 24 * HOUR_IN_SECONDS); } private function init_hooks() { add_action('init', [$this, 'init_plugin']); add_action('admin_menu', [$this, 'add_admin_menu']); add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']); add_action('add_meta_boxes', [$this, 'add_content_ai_metabox']); } private function load_dependencies() { // 加载核心模块 require_once AICC_PLUGIN_DIR . 'includes/class-ai-service-manager.php'; require_once AICC_PLUGIN_DIR . 'includes/class-content-generator.php'; require_once AICC_PLUGIN_DIR . 'includes/class-writing-assistant.php'; require_once AICC_PLUGIN_DIR . 'includes/class-tools-manager.php'; require_once AICC_PLUGIN_DIR . 'includes/class-utilities.php'; // 加载AI服务适配器 require_once AICC_PLUGIN_DIR . 'includes/ai-services/class-openai-adapter.php'; require_once AICC_PLUGIN_DIR . 'includes/ai-services/class-local-ai-adapter.php'; // 加载工具模块 require_once AICC_PLUGIN_DIR . 'includes/tools/class-seo-analyzer.php'; require_once AICC_PLUGIN_DIR . 'includes/tools/class-readability-checker.php'; require_once AICC_PLUGIN_DIR . 'includes/tools/class-plagiarism-checker.php'; } public function init_plugin() { $this->ai_services = AI_Service_Manager::get_available_services(); $this->tools_manager = new Tools_Manager(); // 初始化短代码 $this->init_shortcodes(); } // ... 其他方法 } 2.2 AI服务管理器实现 AI服务管理器负责统一接口调用不同的AI服务: class AI_Service_Manager { private static $services = []; public static function register_service($service_id, $class_name, $config = []) { self::$services[$service_id] = [ 'class' => $class_name, 'config' => $config, 'instance' => null ]; } public static function get_service($service_id = 'default') { if (!isset(self::$services[$service_id])) { return false; } if (null === self::$services[$service_id]['instance']) { $class_name = self::$services[$service_id]['class']; self::$services[$service_id]['instance'] = new $class_name( self::$services[$service_id]['config'] ); } return self::$services[$service_id]['instance']; } public static function get_available_services() { $services = []; // 检查OpenAI配置 $openai_key = get_option('aicc_openai_api_key', ''); if (!empty($openai_key)) { self::register_service('openai', 'OpenAI_Adapter', [ 'api_key' => $openai_key, 'model' => get_option('aicc_openai_model', 'gpt-3.5-turbo'), 'max_tokens' => get_option('aicc_openai_max_tokens', 1000) ]); $services['openai'] = 'OpenAI GPT'; } // 检查本地AI服务 if (self::is_local_ai_available()) { self::register_service('local', 'Local_AI_Adapter', [ 'model_path' => get_option('aicc_local_model_path', ''), 'use_gpu' => get_option('aicc_use_gpu', false) ]); $services['local'] = '本地AI模型'; } return $services; } private static function is_local_ai_available() { // 检查本地AI环境是否可用 $model_path = get_option('aicc_local_model_path', ''); return !empty($model_path) && file_exists($model_path); } public static function generate_content($prompt, $options = [], $service_id = 'default') { $service = self::get_service($service_id); if (!$service) { return new WP_Error('service_unavailable', 'AI服务不可用'); } // 检查缓存 $cache_key = 'aicc_content_' . md5($prompt . serialize($options) . $service_id); $cached = get_transient($cache_key); if ($cached !== false) { return $cached; } // 调用AI服务 $result = $service->generate($prompt, $options); // 缓存结果 if (!is_wp_error($result)) { set_transient($cache_key, $result, AICC_AI_CACHE_EXPIRY); } return $result; } } 第三部分:核心功能实现 - 智能内容创作 3.1 AI内容生成器实现 内容生成器是插件的核心功能,支持多种内容类型生成: class Content_Generator { private $ai_service; public function __construct($service_id = 'default') { $this->ai_service = AI_Service_Manager::get_service($service_id); } public function generate_blog_post($topic, $options = []) { $default_options = [ 'tone' => 'informative', 'length' => 'medium', 'target_audience' => 'general', 'include_seo_keywords' => true, 'structure' => 'introduction-body-conclusion' ]; $options = wp_parse_args($options, $default_options); // 构建AI提示词 $prompt = $this->build_blog_post_prompt($topic, $options); // 调用AI生成内容 $content = AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => $this->get_token_length($options['length']), 'temperature' => 0.7, 'top_p' => 0.9 ]); if (is_wp_error($content)) { return $content; } // 后处理内容 $processed_content = $this->post_process_content($content, $options); return [ 'title' => $this->extract_title($processed_content), 'content' => $processed_content, 'excerpt' => $this->generate_excerpt($processed_content), 'meta_description' => $this->generate_meta_description($processed_content), 'tags' => $this->extract_tags($processed_content, $topic) ]; } private function build_blog_post_prompt($topic, $options) { $prompt_templates = [ 'informative' => "请以专业、 informative 的语气撰写一篇关于 {$topic} 的博客文章。", 'conversational' => "请以对话式、友好的语气撰写一篇关于 {$topic} 的博客文章。", 'persuasive' => "请以有说服力的语气撰写一篇关于 {$topic} 的博客文章,旨在说服读者采取行动。" ]; $tone = isset($prompt_templates[$options['tone']]) ? $prompt_templates[$options['tone']] : $prompt_templates['informative']; $length_instructions = [ 'short' => '文章长度约500字左右,内容简洁明了。', 'medium' => '文章长度约1000-1500字,内容详实。', 'long' => '文章长度约2000字以上,内容全面深入。' ]; $length = isset($length_instructions[$options['length']]) ? $length_instructions[$options['length']] : $length_instructions['medium']; $structure_guide = "文章结构应包括:引言、主体内容(分几个小节阐述)和结论。"; $seo_instruction = $options['include_seo_keywords'] ? "请自然地包含相关关键词,但不要堆砌。" : ""; $audience_instruction = "目标读者是{$options['target_audience']},请使用他们容易理解的语言。"; return "{$tone} {$length} {$structure_guide} {$seo_instruction} {$audience_instruction}"; } private function get_token_length($length) { $lengths = [ 'short' => 800, 'medium' => 1500, 'long' => 3000 ]; return isset($lengths[$length]) ? $lengths[$length] : $lengths['medium']; } public function generate_content_ideas($seed_topic, $count = 5) { $prompt = "基于主题'{$seed_topic}',生成{$count}个相关的博客文章创意。每个创意包括标题和简要描述。"; $ideas = AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 800, 'temperature' => 0.8 ]); if (is_wp_error($ideas)) { return $ideas; } return $this->parse_content_ideas($ideas); } public function rewrite_content($original_content, $options = []) { $default_options = [ 'purpose' => 'improve_clarity', 'tone' => null, 'simplify' => false ]; $options = wp_parse_args($options, $default_options); $purpose_instructions = [ 'improve_clarity' => '提高内容清晰度和可读性', 'seo_optimization' => '优化SEO,自然地加入关键词', 'change_tone' => '改变文章语气为' . ($options['tone'] ?: '专业'), 'summarize' => '总结内容,保留核心信息' ]; $instruction = isset($purpose_instructions[$options['purpose']]) ? $purpose_instructions[$options['purpose']] : '改进内容'; $prompt = "请重新改写以下内容,要求:{$instruction}。" . ($options['simplify'] ? "请简化语言,使其更易理解。" : "") . "nn原文:n{$original_content}"; return AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => strlen($original_content) * 1.5, 'temperature' => 0.6 ]); } // ... 其他内容生成方法 } 3.2 智能写作助手实现 写作助手在编辑器中提供实时AI辅助: class Writing_Assistant { public function __construct() { add_action('admin_init', [$this, 'init_writing_assistant']); } public function init_writing_assistant() { // 添加编辑器按钮 add_filter('mce_buttons', [$this, 'add_tinymce_buttons']); add_filter('mce_external_plugins', [$this, 'add_tinymce_plugins']); // 添加Gutenberg块 if (function_exists('register_block_type')) { add_action('init', [$this, 'register_gutenberg_blocks']); } // 添加AJAX处理 add_action('wp_ajax_aicc_writing_assist', [$this, 'handle_ajax_request']); } public function add_tinymce_buttons($buttons) { array_push($buttons, 'aicc_assistant'); return $buttons; } public function add_tinymce_plugins($plugins) { $plugins['aicc_assistant'] = AICC_PLUGIN_URL . 'assets/js/tinymce-plugin.js'; return $plugins; } public function register_gutenberg_blocks() { register_block_type('aicc/writing-assistant', [ 'editor_script' => 'aicc-gutenberg-blocks', 'render_callback' => [$this, 'render_writing_assistant_block'] ]); } public function handle_ajax_request() { check_ajax_referer('aicc_writing_assist_nonce', 'nonce'); $action = sanitize_text_field($_POST['action_type']); $content = wp_kses_post($_POST['content']); $options = isset($_POST['options']) ? $_POST['options'] : []; switch ($action) { case 'expand_idea': $result = $this->expand_idea($content, $options); break; case 'improve_sentence': $result = $this->improve_sentence($content, $options); break; case 'generate_headline': $result = $this->generate_headline($content, $options); break; case 'check_grammar': $result = $this->check_grammar($content); break; default: $result = new WP_Error('invalid_action', '无效的操作类型'); } if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } else { wp_send_json_success($result); } } public function expand_idea($idea, $options = []) { $prompt = "请扩展以下想法,形成完整的段落:nn{$idea}"; if (!empty($options['tone'])) { $prompt .= "nn使用{$options['tone']}的语气。"; } return AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 500, 'temperature' => 0.7 ]); } public function improve_sentence($sentence, $options = []) { $improvement_type = $options['type'] ?? 'clarity'; $instructions = [ 'clarity' => '提高清晰度和可读性', 'conciseness' => '使表达更简洁', 'formality' => '使语气更正式', 'creativity' => '增加创意和表现力' ]; $instruction = isset($instructions[$improvement_type]) ? $instructions[$improvement_type] : '改进句子'; $prompt = "请{$instruction}:nn{$sentence}nn提供3个改进版本。"; 3.3 内容优化与SEO增强功能 class SEO_Analyzer { public function analyze_content($content, $focus_keyword = '') { $analysis = [ 'score' => 0, 'checks' => [], 'suggestions' => [] ]; // 基础分析 $analysis['checks']['word_count'] = $this->check_word_count($content); $analysis['checks']['headings'] = $this->check_headings($content); $analysis['checks']['paragraph_length'] = $this->check_paragraph_length($content); $analysis['checks']['link_count'] = $this->check_link_count($content); // SEO特定分析 if (!empty($focus_keyword)) { $analysis['checks']['keyword_density'] = $this->check_keyword_density($content, $focus_keyword); $analysis['checks']['keyword_in_title'] = $this->check_keyword_in_title($content, $focus_keyword); $analysis['checks']['keyword_in_meta'] = $this->check_keyword_in_meta($content, $focus_keyword); $analysis['checks']['keyword_in_first_paragraph'] = $this->check_keyword_in_first_paragraph($content, $focus_keyword); } // 可读性分析 $analysis['checks']['readability'] = $this->check_readability($content); // 计算总分 $analysis['score'] = $this->calculate_total_score($analysis['checks']); // 生成建议 $analysis['suggestions'] = $this->generate_suggestions($analysis['checks']); return $analysis; } private function check_word_count($content) { $word_count = str_word_count(strip_tags($content)); $result = [ 'passed' => false, 'value' => $word_count, 'message' => '' ]; if ($word_count >= 300) { $result['passed'] = true; $result['message'] = "文章长度合适({$word_count}字)"; } else { $result['message'] = "文章可能过短({$word_count}字),建议至少300字"; } return $result; } private function check_keyword_density($content, $keyword) { $clean_content = strip_tags(strtolower($content)); $keyword_lower = strtolower($keyword); $total_words = str_word_count($clean_content); $keyword_count = substr_count($clean_content, $keyword_lower); $density = $total_words > 0 ? ($keyword_count / $total_words) * 100 : 0; $result = [ 'passed' => false, 'value' => round($density, 2), 'message' => '' ]; if ($density >= 0.5 && $density <= 2.5) { $result['passed'] = true; $result['message'] = "关键词密度合适({$result['value']}%)"; } elseif ($density < 0.5) { $result['message'] = "关键词密度偏低({$result['value']}%),建议适当增加"; } else { $result['message'] = "关键词密度偏高({$result['value']}%),可能被判定为关键词堆砌"; } return $result; } public function generate_seo_title($content, $keyword = '') { $prompt = "为以下内容生成一个SEO友好的标题"; if (!empty($keyword)) { $prompt .= ",需要包含关键词'{$keyword}'"; } $prompt .= ":nn" . substr(strip_tags($content), 0, 500); $titles = AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 200, 'temperature' => 0.7 ]); if (is_wp_error($titles)) { return $titles; } return $this->extract_titles_from_response($titles); } public function generate_meta_description($content, $keyword = '') { $prompt = "为以下内容生成一个吸引点击的meta描述(150-160字符)"; if (!empty($keyword)) { $prompt .= ",自然地包含关键词'{$keyword}'"; } $prompt .= ":nn" . substr(strip_tags($content), 0, 300); $description = AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 100, 'temperature' => 0.6 ]); if (is_wp_error($description)) { return $description; } // 清理和截断描述 $clean_description = preg_replace('/s+/', ' ', strip_tags($description)); return substr($clean_description, 0, 160); } public function suggest_lsi_keywords($content, $main_keyword) { $prompt = "基于以下内容和主关键词'{$main_keyword}',生成5个相关的LSI(潜在语义索引)关键词:nn" . substr(strip_tags($content), 0, 1000); $keywords = AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 150, 'temperature' => 0.5 ]); if (is_wp_error($keywords)) { return $keywords; } return $this->extract_keywords_from_response($keywords); } } 第四部分:常用互联网小工具集成 4.1 多功能工具管理器 class Tools_Manager { private $tools = []; public function __construct() { $this->register_default_tools(); add_action('init', [$this, 'init_tools']); } private function register_default_tools() { $this->register_tool('seo_analyzer', [ 'name' => 'SEO分析工具', 'description' => '分析内容SEO表现并提供优化建议', 'class' => 'SEO_Analyzer', 'ajax_action' => 'aicc_analyze_seo' ]); $this->register_tool('readability_checker', [ 'name' => '可读性检查', 'description' => '检查内容可读性水平', 'class' => 'Readability_Checker', 'ajax_action' => 'aicc_check_readability' ]); $this->register_tool('plagiarism_checker', [ 'name' => '原创性检查', 'description' => '检查内容原创性', 'class' => 'Plagiarism_Checker', 'ajax_action' => 'aicc_check_plagiarism' ]); $this->register_tool('text_summarizer', [ 'name' => '文本摘要', 'description' => '自动生成内容摘要', 'class' => 'Text_Summarizer', 'ajax_action' => 'aicc_summarize_text' ]); $this->register_tool('image_alt_generator', [ 'name' => '图片ALT文本生成', 'description' => '为图片生成SEO友好的ALT文本', 'class' => 'Image_Alt_Generator', 'ajax_action' => 'aicc_generate_alt_text' ]); } public function register_tool($tool_id, $config) { $this->tools[$tool_id] = wp_parse_args($config, [ 'name' => $tool_id, 'description' => '', 'class' => '', 'ajax_action' => '', 'enabled' => true, 'settings' => [] ]); } public function init_tools() { foreach ($this->tools as $tool_id => $tool) { if ($tool['enabled'] && !empty($tool['ajax_action'])) { add_action("wp_ajax_{$tool['ajax_action']}", [$this, "handle_{$tool_id}_request"]); if (!empty($tool['class']) && class_exists($tool['class'])) { $this->tools[$tool_id]['instance'] = new $tool['class'](); } } } // 注册短代码 $this->register_tool_shortcodes(); } public function register_tool_shortcodes() { add_shortcode('aicc_tool', [$this, 'render_tool_shortcode']); add_shortcode('aicc_seo_analyzer', [$this, 'render_seo_analyzer']); add_shortcode('aicc_readability_check', [$this, 'render_readability_checker']); add_shortcode('aicc_text_summarizer', [$this, 'render_text_summarizer']); } public function render_tool_shortcode($atts) { $atts = shortcode_atts([ 'tool' => 'seo_analyzer', 'title' => '', 'width' => '100%', 'height' => '400px' ], $atts); $tool_id = sanitize_text_field($atts['tool']); if (!isset($this->tools[$tool_id]) || !$this->tools[$tool_id]['enabled']) { return '<p>工具不可用</p>'; } ob_start(); ?> <div class="aicc-tool-container" data-tool="<?php echo esc_attr($tool_id); ?>"> <?php if (!empty($atts['title'])): ?> <h3><?php echo esc_html($atts['title']); ?></h3> <?php endif; ?> <div class="aicc-tool-content"> <?php $this->render_tool_interface($tool_id); ?> </div> </div> <style> .aicc-tool-container { width: <?php echo esc_attr($atts['width']); ?>; height: <?php echo esc_attr($atts['height']); ?>; border: 1px solid #ddd; border-radius: 8px; padding: 20px; margin: 20px 0; background: #f9f9f9; } </style> <?php return ob_get_clean(); } private function render_tool_interface($tool_id) { switch ($tool_id) { case 'seo_analyzer': $this->render_seo_analyzer_interface(); break; case 'readability_checker': $this->render_readability_checker_interface(); break; case 'text_summarizer': $this->render_text_summarizer_interface(); break; default: echo '<p>该工具界面正在开发中</p>'; } } private function render_seo_analyzer_interface() { ?> <div class="aicc-seo-analyzer"> <textarea id="aicc-seo-content" placeholder="请输入要分析的内容..." rows="8" style="width:100%;"></textarea> <input type="text" id="aicc-seo-keyword" placeholder="主关键词(可选)" style="width:100%; margin:10px 0; padding:8px;"> <div style="margin:15px 0;"> <label> <input type="checkbox" id="aicc-check-readability" checked> 检查可读性 </label> <label style="margin-left:15px;"> <input type="checkbox" id="aicc-check-structure" checked> 检查结构 </label> </div> <button id="aicc-analyze-seo-btn" class="button button-primary">分析SEO</button> <div id="aicc-seo-results" style="margin-top:20px; display:none;"> <div class="aicc-score-circle" style="width:100px; height:100px; border-radius:50%; border:5px solid #ddd; display:flex; align-items:center; justify-content:center; margin:0 auto;"> <span id="aicc-seo-score" style="font-size:24px; font-weight:bold;">0</span> </div> <div id="aicc-seo-checks" style="margin-top:20px;"></div> <div id="aicc-seo-suggestions" style="margin-top:20px; padding:15px; background:#fff; border-radius:5px;"></div> </div> </div> <script> jQuery(document).ready(function($) { $('#aicc-analyze-seo-btn').on('click', function() { var content = $('#aicc-seo-content').val(); var keyword = $('#aicc-seo-keyword').val(); if (!content.trim()) { alert('请输入要分析的内容'); return; } $(this).prop('disabled', true).text('分析中...'); $.ajax({ url: '<?php echo admin_url('admin-ajax.php'); ?>', type: 'POST', data: { action: 'aicc_analyze_seo', content: content, keyword: keyword, nonce: '<?php echo wp_create_nonce("aicc_seo_analysis"); ?>' }, success: function(response) { if (response.success) { var data = response.data; $('#aicc-seo-score').text(data.score); $('#aicc-seo-results').show(); // 更新检查结果 var checksHtml = '<h4>检查结果:</h4><ul>'; for (var check in data.checks) { var checkData = data.checks[check]; var statusIcon = checkData.passed ? '✅' : '❌'; checksHtml += '<li>' + statusIcon + ' ' + checkData.message + '</li>'; } checksHtml += '</ul>'; $('#aicc-seo-checks').html(checksHtml); // 更新建议 if (data.suggestions && data.suggestions.length > 0) { var suggestionsHtml = '<h4>优化建议:</h4><ul>'; data.suggestions.forEach(function(suggestion) { suggestionsHtml += '<li>💡 ' + suggestion + '</li>'; }); suggestionsHtml += '</ul>'; $('#aicc-seo-suggestions').html(suggestionsHtml); } // 更新分数圆环颜色 var scoreCircle = $('.aicc-score-circle'); var score = parseInt(data.score); var color; if (score >= 80) color = '#4CAF50'; else if (score >= 60) color = '#FFC107'; else color = '#F44336'; scoreCircle.css('border-color', color); } else { alert('分析失败:' + response.data); } }, error: function() { alert('请求失败,请重试'); }, complete: function() { $('#aicc-analyze-seo-btn').prop('disabled', false).text('分析SEO'); } }); }); }); </script> <?php } } 4.2 实用小工具集合实现 class Text_Summarizer { public function summarize($text, $options = []) { $default_options = [ 'length' => 'medium', // short, medium, long 'format' => 'paragraph', // paragraph, bullet_points 'focus' => 'main_points' // main_points, key_facts, conclusions ]; $options = wp_parse_args($options, $default_options); $length_instructions = [ 'short' => '生成一个非常简短的摘要(约50字)', 'medium' => '生成一个中等长度的摘要(约100-150字)', 'long' => '生成一个详细的摘要(约200-300字)' ]; $format_instructions = [ 'paragraph' => '以段落形式呈现', 'bullet_points' => '以要点列表形式呈现' ]; $focus_instructions = [ 'main_points' => '重点关注主要观点', 'key_facts' => '重点关注关键事实和数据', 'conclusions' => '重点关注结论和建议' ]; $instruction = $length_instructions[$options['length']] . ',' . $format_instructions[$options['format']] . ',' . $focus_instructions[$options['focus']]; $prompt = "请为以下文本{$instruction}:nn{$text}"; return AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => $this->get_token_limit($options['length']), 'temperature' => 0.5 ]); } public function generate_tldr($text) { $prompt = "为以下文本生成一个'太长不看版'(TL;DR)摘要,要求简洁明了:nn{$text}"; return AI_Service_Manager::generate_content($prompt, [ 'max_tokens' => 100, 'temperature' => 0.4 ]); } } class Readability_Checker { public function analyze($text) { $analysis = [ 'flesch_reading_ease' => $this->calculate_flesch_reading_ease($text), 'flesch_kincaid_grade' => $this->calculate_flesch_kincaid_grade($text), 'gunning_fog' => $this->calculate_gunning_fog_index($text), 'smog_index' => $this->calculate_smog_index($text), 'automated_readability' => $this->calculate_automated_readability($text), 'coleman_liau' => $this->calculate_coleman_liau_index($text) ]; $analysis['overall_level'] = $this->determine_readability_level($analysis);
发表评论一步步教你,为WordPress网站添加访客行为热力图与录屏分析,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:为什么你的WordPress网站需要行为分析工具? 在当今竞争激烈的数字环境中,仅仅拥有一个美观的WordPress网站已经远远不够。了解访客如何与你的网站互动,哪些内容吸引他们,哪些元素导致他们离开,这些洞察对于优化用户体验、提高转化率至关重要。传统的数据分析工具如Google Analytics提供了页面浏览量和用户数量的宏观数据,但却无法回答一些关键问题:用户究竟在哪里点击?他们是否看到了重要的行动号召按钮?他们在离开前在页面上做了什么? 这就是热力图和录屏分析的价值所在。热力图通过色彩可视化展示用户在页面上的点击、滚动和注意力分布,而录屏分析则像“数字监控摄像头”一样,记录真实用户在网站上的完整操作过程。本文将详细指导你如何通过WordPress代码二次开发,为你的网站集成这两项强大的分析工具,无需依赖昂贵的第三方服务。 第一部分:理解热力图与录屏分析的技术原理 1.1 热力图的三种类型及其实现原理 热力图主要分为三种类型:点击热力图、滚动热力图和注意力热力图。点击热力图记录用户在页面上的所有点击位置,包括链接、按钮甚至非链接区域。实现原理是通过JavaScript事件监听器捕获点击事件,记录点击元素的坐标、尺寸和页面URL,然后将这些数据发送到服务器进行处理和可视化。 滚动热力图显示用户在不同页面深度的停留比例,揭示有多少用户滚动到页面特定位置。这通过监听滚动事件,记录视窗位置和页面高度比例来实现。注意力热力图则基于视线追踪研究,假设用户的注意力与鼠标移动轨迹相关,通过跟踪鼠标移动和停留时间来模拟实现。 1.2 录屏分析的技术实现方式 录屏分析(也称为会话回放)的实现比热力图更为复杂。现代实现通常采用以下技术组合: DOM序列化:记录页面初始的DOM状态,然后监听所有可能改变DOM的事件(如点击、输入、滚动等) 突变观察器(MutationObserver):监控DOM元素的变化 事件监听:捕获用户交互事件 时间戳同步:确保所有事件按正确顺序和时序记录 数据压缩:将记录的数据高效压缩后发送到服务器 录屏数据通常以增量方式记录,而不是完整视频,这大大减少了数据量。回放时,通过重建DOM状态和应用记录的事件来“重现”用户会话。 1.3 数据收集与隐私保护的平衡 实现这些工具时,必须考虑隐私保护。敏感数据(如密码字段、个人身份信息)应在客户端就被过滤或遮蔽。GDPR、CCPA等法规要求对用户数据进行适当处理,因此你的实现应包含: 明确的隐私政策和使用告知 敏感数据自动遮蔽功能 用户选择退出机制 数据匿名化处理 第二部分:准备工作与开发环境搭建 2.1 开发环境配置 在开始编码前,确保你已准备好以下环境: 本地WordPress开发环境:可以使用Local by Flywheel、XAMPP或Docker配置 代码编辑器:VS Code、Sublime Text或PHPStorm 浏览器开发者工具:熟悉Chrome DevTools或Firefox开发者工具 版本控制系统:Git初始化你的插件目录 测试网站:不要在正式网站直接开发,使用测试网站或本地环境 2.2 创建自定义WordPress插件框架 我们将创建一个独立的WordPress插件来实现功能,而不是直接修改主题文件。这样做的好处是: 功能与主题分离,更换主题不影响分析工具 便于更新和维护 可以轻松在其他网站上复用 创建插件基本结构: wp-content/plugins/visitor-analytics-tool/ ├── visitor-analytics-tool.php ├── includes/ │ ├── class-data-collector.php │ ├── class-heatmap-generator.php │ ├── class-session-recorder.php │ └── class-data-viewer.php ├── assets/ │ ├── js/ │ │ ├── frontend.js │ │ └── admin.js │ └── css/ │ ├── frontend.css │ └── admin.css ├── admin/ │ └── admin-pages.php └── uninstall.php 2.3 插件主文件基本结构 在visitor-analytics-tool.php中,设置插件基本信息: <?php /** * Plugin Name: Visitor Analytics Tool * Plugin URI: https://yourwebsite.com/visitor-analytics * Description: 添加访客行为热力图与录屏分析功能 * Version: 1.0.0 * Author: Your Name * License: GPL v2 or later * Text Domain: visitor-analytics */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('VAT_VERSION', '1.0.0'); define('VAT_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('VAT_PLUGIN_URL', plugin_dir_url(__FILE__)); // 包含必要文件 require_once VAT_PLUGIN_DIR . 'includes/class-data-collector.php'; require_once VAT_PLUGIN_DIR . 'includes/class-heatmap-generator.php'; require_once VAT_PLUGIN_DIR . 'includes/class-session-recorder.php'; require_once VAT_PLUGIN_DIR . 'includes/class-data-viewer.php'; // 初始化插件 function vat_initialize_plugin() { // 检查用户权限 if (current_user_can('manage_options')) { // 初始化各个组件 $data_collector = new VAT_Data_Collector(); $heatmap_generator = new VAT_Heatmap_Generator(); $session_recorder = new VAT_Session_Recorder(); $data_viewer = new VAT_Data_Viewer(); // 注册激活/停用钩子 register_activation_hook(__FILE__, array($data_collector, 'activate')); register_deactivation_hook(__FILE__, array($data_collector, 'deactivate')); } } add_action('plugins_loaded', 'vat_initialize_plugin'); 第三部分:实现前端数据收集系统 3.1 设计高效的数据收集JavaScript 创建assets/js/frontend.js文件,负责收集用户行为数据: (function() { 'use strict'; // 配置参数 const config = { sampleRate: 0.3, // 30%的用户被记录 heatmap: { enabled: true, click: true, scroll: true, move: true }, sessionRecord: { enabled: false, // 默认关闭,需要时开启 maxDuration: 300000, // 5分钟 events: ['click', 'input', 'scroll', 'resize'] }, privacy: { maskText: true, excludeFields: ['password', 'credit-card', 'ssn'] } }; // 检查是否应该记录当前会话 function shouldRecordSession() { if (!Math.floor(Math.random() * 100) < (config.sampleRate * 100)) { return false; } // 检查排除条件(如管理员、特定页面等) const excludedPaths = ['/wp-admin', '/wp-login']; const currentPath = window.location.pathname; return !excludedPaths.some(path => currentPath.startsWith(path)); } // 数据收集器类 class DataCollector { constructor() { this.sessionId = this.generateSessionId(); this.pageId = window.location.pathname + window.location.search; this.startTime = Date.now(); this.events = []; this.isRecording = false; // 初始化 this.init(); } generateSessionId() { return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } init() { if (!shouldRecordSession()) return; // 设置数据收集 if (config.heatmap.enabled) { this.setupHeatmapCollection(); } if (config.sessionRecord.enabled) { this.setupSessionRecording(); } // 发送初始数据 this.sendInitialData(); this.isRecording = true; } setupHeatmapCollection() { // 点击事件收集 if (config.heatmap.click) { document.addEventListener('click', (e) => { this.recordClick(e); }, { capture: true }); } // 滚动事件收集 if (config.heatmap.scroll) { let scrollTimeout; window.addEventListener('scroll', () => { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { this.recordScroll(); }, 100); }); } // 鼠标移动收集(用于注意力热力图) if (config.heatmap.move) { let moveTimeout; document.addEventListener('mousemove', (e) => { clearTimeout(moveTimeout); moveTimeout = setTimeout(() => { this.recordMousePosition(e); }, 50); }); } } recordClick(event) { const target = event.target; const rect = target.getBoundingClientRect(); const clickData = { type: 'click', x: event.clientX, y: event.clientY, element: { tag: target.tagName, id: target.id, classes: target.className, text: this.maskText(target.textContent.trim().substring(0, 100)) }, pageX: event.pageX, pageY: event.pageY, viewport: { width: window.innerWidth, height: window.innerHeight }, timestamp: Date.now() - this.startTime, url: window.location.href }; this.events.push(clickData); this.sendDataIfNeeded(); } recordScroll() { const scrollData = { type: 'scroll', position: { x: window.scrollX, y: window.scrollY }, maxScroll: { x: document.documentElement.scrollWidth - window.innerWidth, y: document.documentElement.scrollHeight - window.innerHeight }, percentage: (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100, timestamp: Date.now() - this.startTime, url: window.location.href }; this.events.push(scrollData); this.sendDataIfNeeded(); } recordMousePosition(event) { const moveData = { type: 'move', x: event.clientX, y: event.clientY, timestamp: Date.now() - this.startTime, url: window.location.href }; this.events.push(moveData); // 鼠标移动数据较多,单独处理发送逻辑 this.sendMoveData(); } maskText(text) { if (!config.privacy.maskText) return text; // 简单文本遮蔽,实际应用中需要更复杂的逻辑 if (text.length > 5) { return text.substring(0, 2) + '***' + text.substring(text.length - 2); } return '***'; } sendDataIfNeeded() { // 每10个事件或每5秒发送一次数据 if (this.events.length >= 10) { this.sendBatchData(); } } sendMoveData() { // 鼠标移动数据单独处理,避免数据量过大 if (this.moveEvents && this.moveEvents.length >= 20) { const moveBatch = { sessionId: this.sessionId, type: 'move_batch', events: this.moveEvents, url: window.location.href }; this.sendToServer(moveBatch); this.moveEvents = []; } } sendBatchData() { if (this.events.length === 0) return; const batch = { sessionId: this.sessionId, events: [...this.events], url: window.location.href, timestamp: Date.now() }; this.sendToServer(batch); this.events = []; } sendToServer(data) { // 使用navigator.sendBeacon确保页面卸载时也能发送数据 const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); if (navigator.sendBeacon) { navigator.sendBeacon(vat_ajax.ajax_url + '?action=vat_track', blob); } else { // 回退方案 const xhr = new XMLHttpRequest(); xhr.open('POST', vat_ajax.ajax_url, false); xhr.setRequestHeader('Content-Type', 'application/json'); xhr.send(JSON.stringify(data)); } } sendInitialData() { const initialData = { sessionId: this.sessionId, type: 'session_start', url: window.location.href, referrer: document.referrer, viewport: { width: window.innerWidth, height: window.innerHeight }, device: { userAgent: navigator.userAgent, platform: navigator.platform }, timestamp: Date.now() }; this.sendToServer(initialData); } setupSessionRecording() { // 录屏功能需要更复杂的实现 console.log('Session recording setup would go here'); // 实际实现需要DOM序列化、突变观察器等 } } // 初始化数据收集器 document.addEventListener('DOMContentLoaded', () => { window.vatCollector = new DataCollector(); }); // 页面卸载前发送剩余数据 window.addEventListener('beforeunload', () => { if (window.vatCollector && window.vatCollector.isRecording) { window.vatCollector.sendBatchData(); } }); })(); 3.2 创建PHP后端数据处理类 在includes/class-data-collector.php中创建数据处理后端: <?php class VAT_Data_Collector { private $table_events; private $table_sessions; public function __construct() { global $wpdb; $this->table_events = $wpdb->prefix . 'vat_events'; $this->table_sessions = $wpdb->prefix . 'vat_sessions'; // 注册AJAX处理 add_action('wp_ajax_vat_track', array($this, 'handle_tracking_data')); add_action('wp_ajax_nopriv_vat_track', array($this, 'handle_tracking_data')); // 注册前端脚本 add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_scripts')); } public function activate() { $this->create_tables(); $this->set_default_options(); } private function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 创建事件表 $sql_events = "CREATE TABLE IF NOT EXISTS {$this->table_events} ( id bigint(20) NOT NULL AUTO_INCREMENT, session_id varchar(100) NOT NULL, event_type varchar(50) NOT NULL, event_data longtext NOT NULL, page_url varchar(500) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY (id), KEY session_id (session_id), KEY event_type (event_type), KEY page_url (page_url(200)), KEY created_at (created_at) ) $charset_collate;"; // 创建会话表 $sql_sessions = "CREATE TABLE IF NOT EXISTS {$this->table_sessions} ( session_id varchar(100) NOT NULL, start_time datetime NOT NULL, end_time datetime, page_views int(11) DEFAULT 1, duration int(11), referrer varchar(500), device_info text, user_id bigint(20), PRIMARY KEY (session_id), KEY start_time (start_time), KEY user_id (user_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql_events); dbDelta($sql_sessions); } private function set_default_options() { if (!get_option('vat_settings')) { $default_settings = array( 'sample_rate' => 0.3, 'heatmap_enabled' => true, 'session_record_enabled' => false, 'data_retention_days' => 30, 'exclude_roles' => array('administrator'), 'privacy_mask' => true ); update_option('vat_settings', $default_settings); } } public function enqueue_frontend_scripts() { // 只有在前端页面加载 if (is_admin()) return; // 获取设置 $settings = get_option('vat_settings', array()); // 检查是否应该加载跟踪脚本 if (!$this->should_track_current_user()) return; // 计算采样率 $sample_rate = isset($settings['sample_rate']) ? floatval($settings['sample_rate']) : 0.3; // 只有部分用户加载跟踪脚本 if (mt_rand(1, 100) > ($sample_rate * 100)) return; wp_enqueue_script( 'vat-frontend', VAT_PLUGIN_URL . 'assets/js/frontend.js', array(), VAT_VERSION, true ); // 传递AJAX URL给前端脚本 wp_localize_script('vat-frontend', 'vat_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'sample_rate' => $sample_rate )); } 第三部分:实现前端数据收集系统(续) 3.2 创建PHP后端数据处理类(续) private function should_track_current_user() { $settings = get_option('vat_settings', array()); $exclude_roles = isset($settings['exclude_roles']) ? $settings['exclude_roles'] : array(); // 如果用户已登录,检查其角色 if (is_user_logged_in()) { $user = wp_get_current_user(); $user_roles = $user->roles; // 检查用户角色是否在排除列表中 foreach ($user_roles as $role) { if (in_array($role, $exclude_roles)) { return false; } } } // 排除特定页面 $excluded_pages = array('/wp-admin', '/wp-login.php', '/cart', '/checkout'); $current_path = $_SERVER['REQUEST_URI']; foreach ($excluded_pages as $page) { if (strpos($current_path, $page) === 0) { return false; } } return true; } public function handle_tracking_data() { // 验证请求 if ($_SERVER['REQUEST_METHOD'] !== 'POST') { wp_die('Invalid request method', 400); } // 获取原始POST数据 $raw_data = file_get_contents('php://input'); $data = json_decode($raw_data, true); if (!$data || !isset($data['sessionId'])) { wp_die('Invalid data format', 400); } // 处理不同类型的数据 if (isset($data['type']) && $data['type'] === 'session_start') { $this->record_session_start($data); } elseif (isset($data['type']) && $data['type'] === 'move_batch') { $this->record_move_batch($data); } elseif (isset($data['events'])) { $this->record_events($data); } // 返回成功响应 wp_send_json_success(array('message' => 'Data recorded')); } private function record_session_start($data) { global $wpdb; $session_data = array( 'session_id' => sanitize_text_field($data['sessionId']), 'start_time' => current_time('mysql'), 'referrer' => isset($data['referrer']) ? sanitize_text_field($data['referrer']) : '', 'device_info' => isset($data['device']) ? json_encode($data['device']) : '', 'user_id' => is_user_logged_in() ? get_current_user_id() : 0 ); $wpdb->insert($this->table_sessions, $session_data); } private function record_events($data) { global $wpdb; $session_id = sanitize_text_field($data['sessionId']); $events = $data['events']; foreach ($events as $event) { $event_type = isset($event['type']) ? sanitize_text_field($event['type']) : 'unknown'; $event_data = array( 'session_id' => $session_id, 'event_type' => $event_type, 'event_data' => json_encode($event), 'page_url' => isset($event['url']) ? sanitize_text_field($event['url']) : '' ); $wpdb->insert($this->table_events, $event_data); } // 更新会话的最后活动时间 $this->update_session_activity($session_id); } private function record_move_batch($data) { global $wpdb; $session_id = sanitize_text_field($data['sessionId']); $events = $data['events']; // 鼠标移动数据较多,进行聚合处理 $aggregated_data = array( 'session_id' => $session_id, 'event_type' => 'move_aggregated', 'event_data' => json_encode(array( 'count' => count($events), 'positions' => array_map(function($e) { return array($e['x'], $e['y']); }, $events) )), 'page_url' => isset($data['url']) ? sanitize_text_field($data['url']) : '' ); $wpdb->insert($this->table_events, $aggregated_data); } private function update_session_activity($session_id) { global $wpdb; $wpdb->update( $this->table_sessions, array('end_time' => current_time('mysql')), array('session_id' => $session_id) ); } public function cleanup_old_data() { global $wpdb; $settings = get_option('vat_settings', array()); $retention_days = isset($settings['data_retention_days']) ? intval($settings['data_retention_days']) : 30; $cutoff_date = date('Y-m-d H:i:s', strtotime("-$retention_days days")); // 删除旧事件数据 $wpdb->query($wpdb->prepare( "DELETE FROM {$this->table_events} WHERE created_at < %s", $cutoff_date )); // 删除旧会话数据 $wpdb->query($wpdb->prepare( "DELETE FROM {$this->table_sessions} WHERE start_time < %s", $cutoff_date )); } } 第四部分:构建热力图生成与可视化系统 4.1 创建热力图数据处理类 在includes/class-heatmap-generator.php中: <?php class VAT_Heatmap_Generator { private $data_collector; public function __construct() { global $wpdb; $this->table_events = $wpdb->prefix . 'vat_events'; // 注册管理页面 add_action('admin_menu', array($this, 'add_admin_menu')); // 注册AJAX端点 add_action('wp_ajax_vat_get_heatmap_data', array($this, 'get_heatmap_data')); } public function add_admin_menu() { add_menu_page( '访客行为分析', '行为分析', 'manage_options', 'vat-analytics', array($this, 'render_admin_page'), 'dashicons-chart-area', 30 ); add_submenu_page( 'vat-analytics', '热力图分析', '热力图', 'manage_options', 'vat-heatmaps', array($this, 'render_heatmap_page') ); } public function render_admin_page() { ?> <div class="wrap"> <h1>访客行为分析仪表板</h1> <div class="vat-dashboard"> <div class="vat-stats-grid"> <div class="vat-stat-card"> <h3>今日会话</h3> <p class="vat-stat-number"><?php echo $this->get_today_sessions(); ?></p> </div> <div class="vat-stat-card"> <h3>平均点击次数</h3> <p class="vat-stat-number"><?php echo $this->get_average_clicks(); ?></p> </div> <div class="vat-stat-card"> <h3>热门页面</h3> <p class="vat-stat-number"><?php echo $this->get_top_page(); ?></p> </div> </div> </div> </div> <?php } public function render_heatmap_page() { $pages = $this->get_pages_with_data(); ?> <div class="wrap"> <h1>热力图分析</h1> <div class="vat-heatmap-controls"> <div class="vat-control-group"> <label for="vat-page-select">选择页面:</label> <select id="vat-page-select" class="vat-select"> <option value="">选择页面...</option> <?php foreach ($pages as $page): ?> <option value="<?php echo esc_attr($page->page_url); ?>"> <?php echo esc_html($this->truncate_url($page->page_url)); ?> (<?php echo $page->event_count; ?> 事件) </option> <?php endforeach; ?> </select> </div> <div class="vat-control-group"> <label for="vat-date-range">日期范围:</label> <select id="vat-date-range" class="vat-select"> <option value="today">今天</option> <option value="yesterday">昨天</option> <option value="7days" selected>最近7天</option> <option value="30days">最近30天</option> <option value="custom">自定义</option> </select> <div id="vat-custom-dates" style="display: none; margin-top: 10px;"> <input type="date" id="vat-date-from" class="vat-date-input"> <span>至</span> <input type="date" id="vat-date-to" class="vat-date-input"> </div> </div> <div class="vat-control-group"> <label for="vat-heatmap-type">热力图类型:</label> <select id="vat-heatmap-type" class="vat-select"> <option value="click">点击热力图</option> <option value="scroll">滚动热力图</option> <option value="attention">注意力热力图</option> </select> </div> <button id="vat-generate-heatmap" class="button button-primary">生成热力图</button> </div> <div class="vat-heatmap-container"> <div id="vat-page-preview" style="border: 1px solid #ddd; margin: 20px 0; position: relative; overflow: hidden;"> <!-- 页面预览将在这里显示 --> </div> <div id="vat-heatmap-canvas" style="position: absolute; top: 0; left: 0; pointer-events: none;"> <!-- 热力图将在这里绘制 --> </div> <div class="vat-heatmap-legend"> <div class="vat-legend-title">热力强度</div> <div class="vat-legend-gradient"> <span>低</span> <div class="vat-gradient-bar"></div> <span>高</span> </div> </div> </div> <div class="vat-heatmap-stats"> <h3>页面分析数据</h3> <div id="vat-page-stats"> <!-- 统计数据将动态加载 --> </div> </div> </div> <script> jQuery(document).ready(function($) { // 初始化热力图页面 vatHeatmap.init(); }); </script> <?php } private function get_pages_with_data() { global $wpdb; return $wpdb->get_results($wpdb->prepare( "SELECT page_url, COUNT(*) as event_count FROM {$this->table_events} WHERE event_type IN ('click', 'scroll', 'move_aggregated') AND created_at >= %s GROUP BY page_url ORDER BY event_count DESC LIMIT 50", date('Y-m-d H:i:s', strtotime('-30 days')) )); } public function get_heatmap_data() { // 验证权限 if (!current_user_can('manage_options')) { wp_die('权限不足', 403); } $page_url = isset($_GET['page_url']) ? urldecode($_GET['page_url']) : ''; $date_range = isset($_GET['date_range']) ? $_GET['date_range'] : '7days'; $heatmap_type = isset($_GET['heatmap_type']) ? $_GET['heatmap_type'] : 'click'; if (empty($page_url)) { wp_send_json_error('请选择页面'); } // 计算日期范围 $date_conditions = $this->get_date_condition($date_range); // 根据热力图类型获取数据 switch ($heatmap_type) { case 'click': $data = $this->get_click_data($page_url, $date_conditions); break; case 'scroll': $data = $this->get_scroll_data($page_url, $date_conditions); break; case 'attention': $data = $this->get_attention_data($page_url, $date_conditions); break; default: $data = array(); } // 获取页面统计数据 $stats = $this->get_page_stats($page_url, $date_conditions); wp_send_json_success(array( 'data' => $data, 'stats' => $stats, 'page_url' => $page_url )); } private function get_click_data($page_url, $date_conditions) { global $wpdb; $events = $wpdb->get_results($wpdb->prepare( "SELECT event_data FROM {$this->table_events} WHERE page_url = %s AND event_type = 'click' AND created_at >= %s ORDER BY created_at DESC LIMIT 1000", $page_url, $date_conditions['start_date'] )); $clicks = array(); foreach ($events as $event) { $data = json_decode($event->event_data, true); if (isset($data['x']) && isset($data['y'])) { $clicks[] = array( 'x' => intval($data['x']), 'y' => intval($data['y']), 'viewport_width' => isset($data['viewport']['width']) ? intval($data['viewport']['width']) : 1920, 'viewport_height' => isset($data['viewport']['height']) ? intval($data['viewport']['height']) : 1080 ); } } return $clicks; } private function get_scroll_data($page_url, $date_conditions) { global $wpdb; $events = $wpdb->get_results($wpdb->prepare( "SELECT event_data FROM {$this->table_events} WHERE page_url = %s AND event_type = 'scroll' AND created_at >= %s ORDER BY created_at DESC LIMIT 5000", $page_url, $date_conditions['start_date'] )); $scroll_depths = array(); foreach ($events as $event) { $data = json_decode($event->event_data, true); if (isset($data['percentage'])) { $scroll_depths[] = floatval($data['percentage']); } } // 计算滚动深度分布 $distribution = array_fill(0, 10, 0); // 10个区间:0-10%, 10-20%, ..., 90-100% foreach ($scroll_depths as $depth) { $index = min(floor($depth / 10), 9); $distribution[$index]++; } return $distribution; } private function get_attention_data($page_url, $date_conditions) { global $wpdb; $events = $wpdb->get_results($wpdb->prepare( "SELECT event_data FROM {$this->table_events} WHERE page_url = %s AND event_type = 'move_aggregated' AND created_at >= %s ORDER BY created_at DESC LIMIT 100", $page_url, $date_conditions['start_date'] )); $positions = array(); foreach ($events as $event) { $data = json_decode($event->event_data, true); if (isset($data['positions'])) { foreach ($data['positions'] as $pos) { $positions[] = array( 'x' => $pos[0], 'y' => $pos[1] ); } } } return $positions; } private function get_page_stats($page_url, $date_conditions) { global $wpdb; $stats = array(); // 获取总点击次数 $stats['total_clicks'] = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_events} WHERE page_url = %s AND event_type = 'click' AND created_at >= %s", $page_url, $date_conditions['start_date'] )); // 获取独立访客数 $stats['unique_visitors'] = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(DISTINCT session_id) FROM {$this->table_events} WHERE page_url = %s AND created_at >= %s", $page_url, $date_conditions['start_date'] )); // 获取平均滚动深度 $scroll_data = $wpdb->get_results($wpdb->prepare( "SELECT event_data FROM {$this->table_events} WHERE page_url = %s AND event_type = 'scroll' AND created_at >= %s", $page_url, $date_conditions['start_date'] )); $total_depth = 0; $count = 0; foreach ($scroll_data as $event) { $data = json_decode($event->event_data, true); if (isset($data['percentage'])) { $total_depth += floatval($data['percentage']); $count++; } }
发表评论实战教程:集成网站实时协同编辑文档与电子表格应用,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:为什么需要实时协同编辑功能? 在当今数字化工作环境中,实时协同编辑已成为提高团队效率的关键工具。无论是远程团队协作、在线教育还是客户服务,能够多人同时编辑文档和电子表格的功能都大大提升了工作效率和沟通效果。然而,对于许多中小型企业和个人网站所有者来说,集成专业的协同编辑工具往往面临高昂的成本和技术门槛。 WordPress作为全球最流行的内容管理系统,拥有超过40%的网站市场份额,其强大的可扩展性为我们提供了一个理想的平台。通过代码二次开发,我们可以在WordPress网站上集成实时协同编辑功能,同时实现其他常用互联网小工具,从而打造一个功能全面、成本可控的协作平台。 本教程将详细指导您如何通过WordPress代码二次开发,实现文档和电子表格的实时协同编辑功能,并集成其他实用工具,最终打造一个功能丰富的在线协作环境。 第一部分:准备工作与环境搭建 1.1 开发环境要求 在开始开发之前,我们需要确保具备以下环境: WordPress 5.8或更高版本 PHP 7.4或更高版本(建议8.0+) MySQL 5.6或更高版本 支持WebSocket的服务器环境(Nginx或Apache) SSL证书(HTTPS协议对于实时通信至关重要) 代码编辑器(如VS Code、Sublime Text等) 1.2 创建自定义插件框架 首先,我们需要创建一个自定义插件来容纳所有功能代码: 在WordPress的wp-content/plugins/目录下创建新文件夹real-time-collab-tools 在该文件夹中创建主插件文件real-time-collab-tools.php 添加插件基本信息: <?php /** * Plugin Name: 实时协同编辑工具套件 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress网站添加实时协同编辑文档和电子表格功能,集成常用互联网小工具 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: rt-collab-tools */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('RT_COLLAB_VERSION', '1.0.0'); define('RT_COLLAB_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('RT_COLLAB_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-init.php'; 创建初始化类文件includes/class-init.php: <?php class RT_Collab_Init { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->init_hooks(); $this->load_dependencies(); } private function init_hooks() { // 注册激活和停用钩子 register_activation_hook(__FILE__, array($this, 'activate')); register_deactivation_hook(__FILE__, array($this, 'deactivate')); // 初始化插件 add_action('plugins_loaded', array($this, 'init_plugin')); } private function load_dependencies() { // 加载依赖文件 require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-websocket-server.php'; require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-document-editor.php'; require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-spreadsheet-editor.php'; require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-tools-manager.php'; require_once RT_COLLAB_PLUGIN_DIR . 'includes/class-database.php'; } public function activate() { // 创建必要的数据库表 RT_Collab_Database::create_tables(); // 设置默认选项 update_option('rt_collab_version', RT_COLLAB_VERSION); } public function deactivate() { // 清理临时数据 // 注意:不删除用户数据 } public function init_plugin() { // 初始化各个组件 RT_WebSocket_Server::get_instance(); RT_Document_Editor::get_instance(); RT_Spreadsheet_Editor::get_instance(); RT_Tools_Manager::get_instance(); // 加载文本域 load_plugin_textdomain('rt-collab-tools', false, dirname(plugin_basename(__FILE__)) . '/languages'); } } // 初始化插件 RT_Collab_Init::get_instance(); 第二部分:实现WebSocket实时通信服务器 2.1 WebSocket服务器基础架构 实时协同编辑的核心是WebSocket通信。我们将使用PHP的Ratchet库来创建WebSocket服务器: 首先通过Composer安装Ratchet库: cd /path/to/your/plugin composer require cboden/ratchet 创建WebSocket服务器类includes/class-websocket-server.php: <?php require_once RT_COLLAB_PLUGIN_DIR . 'vendor/autoload.php'; use RatchetMessageComponentInterface; use RatchetConnectionInterface; use RatchetServerIoServer; use RatchetHttpHttpServer; use RatchetWebSocketWsServer; class RT_WebSocket_Server implements MessageComponentInterface { protected $clients; protected $documentSessions; protected $spreadsheetSessions; private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } public function __construct() { $this->clients = new SplObjectStorage; $this->documentSessions = []; $this->spreadsheetSessions = []; // 启动WebSocket服务器 $this->start_server(); } private function start_server() { // 仅在特定条件下启动服务器(如通过WP-CLI) if (defined('WP_CLI') && WP_CLI) { $port = get_option('rt_collab_websocket_port', 8080); $server = IoServer::factory( new HttpServer( new WsServer($this) ), $port ); // 在后台运行服务器 add_action('init', function() use ($server) { if (current_user_can('manage_options')) { $server->run(); } }); } } public function onOpen(ConnectionInterface $conn) { // 存储新连接 $this->clients->attach($conn); echo "新连接: {$conn->resourceId}n"; } public function onMessage(ConnectionInterface $from, $msg) { // 解析消息 $data = json_decode($msg, true); if (!$data || !isset($data['type'])) { return; } // 根据消息类型处理 switch ($data['type']) { case 'join_document': $this->handleJoinDocument($from, $data); break; case 'document_edit': $this->handleDocumentEdit($from, $data); break; case 'join_spreadsheet': $this->handleJoinSpreadsheet($from, $data); break; case 'spreadsheet_edit': $this->handleSpreadsheetEdit($from, $data); break; case 'cursor_move': $this->handleCursorMove($from, $data); break; } } private function handleJoinDocument($conn, $data) { $docId = $data['document_id']; $userId = $data['user_id']; if (!isset($this->documentSessions[$docId])) { $this->documentSessions[$docId] = [ 'clients' => [], 'content' => '', 'revision' => 0 ]; } // 添加客户端到会话 $this->documentSessions[$docId]['clients'][$conn->resourceId] = [ 'connection' => $conn, 'user_id' => $userId, 'cursor_position' => 0 ]; // 发送当前文档内容给新用户 $conn->send(json_encode([ 'type' => 'document_content', 'content' => $this->documentSessions[$docId]['content'], 'revision' => $this->documentSessions[$docId]['revision'] ])); // 通知其他用户有新成员加入 $this->broadcastToDocument($docId, $conn->resourceId, [ 'type' => 'user_joined', 'user_id' => $userId, 'total_users' => count($this->documentSessions[$docId]['clients']) ]); } private function handleDocumentEdit($conn, $data) { $docId = $data['document_id']; if (!isset($this->documentSessions[$docId])) { return; } // 应用操作转换(OT)算法处理并发编辑 $transformedOperation = $this->transformOperation( $data['operation'], $this->documentSessions[$docId]['revision'] ); // 更新文档内容 $this->documentSessions[$docId]['content'] = $this->applyOperation( $this->documentSessions[$docId]['content'], $transformedOperation ); $this->documentSessions[$docId]['revision']++; // 广播编辑操作给其他用户 $this->broadcastToDocument($docId, $conn->resourceId, [ 'type' => 'document_update', 'operation' => $transformedOperation, 'revision' => $this->documentSessions[$docId]['revision'], 'user_id' => $data['user_id'] ]); } private function broadcastToDocument($docId, $excludeClientId, $message) { if (!isset($this->documentSessions[$docId])) { return; } foreach ($this->documentSessions[$docId]['clients'] as $clientId => $client) { if ($clientId != $excludeClientId) { $client['connection']->send(json_encode($message)); } } } public function onClose(ConnectionInterface $conn) { // 从所有会话中移除客户端 foreach ($this->documentSessions as $docId => $session) { if (isset($session['clients'][$conn->resourceId])) { unset($this->documentSessions[$docId]['clients'][$conn->resourceId]); // 通知其他用户 $this->broadcastToDocument($docId, $conn->resourceId, [ 'type' => 'user_left', 'user_id' => $session['clients'][$conn->resourceId]['user_id'], 'total_users' => count($this->documentSessions[$docId]['clients']) ]); } } $this->clients->detach($conn); echo "连接关闭: {$conn->resourceId}n"; } public function onError(ConnectionInterface $conn, Exception $e) { echo "错误: {$e->getMessage()}n"; $conn->close(); } // 简化版的OT操作转换(实际项目应使用完整OT算法) private function transformOperation($operation, $revision) { // 这里实现操作转换逻辑 // 由于篇幅限制,这里返回原始操作 return $operation; } private function applyOperation($content, $operation) { // 应用操作到内容 // 简化实现 if ($operation['type'] == 'insert') { return substr($content, 0, $operation['position']) . $operation['text'] . substr($content, $operation['position']); } return $content; } } 2.2 数据库设计与数据持久化 创建数据库表来存储文档和电子表格数据: <?php class RT_Collab_Database { public static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 文档表 $documents_table = $wpdb->prefix . 'rt_collab_documents'; $sql_documents = "CREATE TABLE IF NOT EXISTS $documents_table ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, content longtext NOT NULL, owner_id bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, revision int(11) DEFAULT 0, permissions varchar(20) DEFAULT 'private', PRIMARY KEY (id), KEY owner_id (owner_id) ) $charset_collate;"; // 电子表格表 $spreadsheets_table = $wpdb->prefix . 'rt_collab_spreadsheets'; $sql_spreadsheets = "CREATE TABLE IF NOT EXISTS $spreadsheets_table ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, data longtext NOT NULL, owner_id bigint(20) NOT NULL, rows int(11) DEFAULT 100, columns int(11) DEFAULT 26, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, permissions varchar(20) DEFAULT 'private', PRIMARY KEY (id), KEY owner_id (owner_id) ) $charset_collate;"; // 文档访问记录表 $document_access_table = $wpdb->prefix . 'rt_collab_document_access'; $sql_document_access = "CREATE TABLE IF NOT EXISTS $document_access_table ( id bigint(20) NOT NULL AUTO_INCREMENT, document_id bigint(20) NOT NULL, user_id bigint(20) NOT NULL, last_access datetime DEFAULT CURRENT_TIMESTAMP, permission_level varchar(20) DEFAULT 'view', PRIMARY KEY (id), UNIQUE KEY document_user (document_id, user_id), KEY user_id (user_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql_documents); dbDelta($sql_spreadsheets); dbDelta($sql_document_access); } } 第三部分:实现文档协同编辑器 3.1 前端编辑器界面 创建文档编辑器前端界面: <?php class RT_Document_Editor { 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('wp_enqueue_scripts', array($this, 'enqueue_scripts')); add_shortcode('collab_document', array($this, 'document_shortcode')); add_action('wp_ajax_save_document', array($this, 'save_document')); add_action('wp_ajax_nopriv_save_document', array($this, 'save_document')); } public function enqueue_scripts() { // 仅在有需要的页面加载 if ($this->is_editor_page()) { // 加载CodeMirror编辑器 wp_enqueue_style('codemirror-css', 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.0/codemirror.min.css'); wp_enqueue_script('codemirror-js', 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.0/codemirror.min.js', array(), null, true); // 加载协同编辑前端脚本 wp_enqueue_script('rt-collab-document', RT_COLLAB_PLUGIN_URL . 'assets/js/document-editor.js', array('jquery', 'codemirror-js'), RT_COLLAB_VERSION, true); // 本地化脚本 wp_localize_script('rt-collab-document', 'rtCollabConfig', array( 'ajax_url' => admin_url('admin-ajax.php'), 'websocket_url' => $this->get_websocket_url(), 'current_user' => get_current_user_id(), 'nonce' => wp_create_nonce('rt_collab_nonce') )); } } public function document_shortcode($atts) { $atts = shortcode_atts(array( 'id' => 0, 'title' => '协同文档', 'height' => '500px' ), $atts, 'collab_document'); if (!$atts['id']) { return '<p>请指定文档ID</p>'; } // 检查用户权限 if (!$this->check_document_access($atts['id'])) { return '<p>您没有权限访问此文档</p>'; } ob_start(); ?> <div class="rt-collab-document-editor" data-document-id="<?php echo esc_attr($atts['id']); ?>"> <div class="document-header"> <h3><?php echo esc_html($atts['title']); ?></h3> <div class="document-toolbar"> <button class="btn-save">保存</button> <button class="btn-share">分享</button> <span class="user-count">在线用户: <span class="count">1</span></span> </div> </div> attr($atts['height']); ?>"> <textarea id="document-editor-<?php echo esc_attr($atts['id']); ?>" class="document-editor"></textarea> </div> <div class="document-users"> <h4>当前在线用户</h4> <ul class="user-list"> <!-- 用户列表将通过JavaScript动态生成 --> </ul> </div> <div class="document-revision"> 版本: <span class="revision-number">0</span> </div> </div> <?php return ob_get_clean(); } private function check_document_access($document_id) { // 简化权限检查,实际应更复杂 if (is_user_logged_in()) { return true; } // 检查是否为公开文档 global $wpdb; $table_name = $wpdb->prefix . 'rt_collab_documents'; $document = $wpdb->get_row($wpdb->prepare( "SELECT permissions FROM $table_name WHERE id = %d", $document_id )); return $document && $document->permissions === 'public'; } private function get_websocket_url() { $protocol = is_ssl() ? 'wss://' : 'ws://'; $host = $_SERVER['HTTP_HOST']; $port = get_option('rt_collab_websocket_port', 8080); return $protocol . $host . ':' . $port; } private function is_editor_page() { global $post; if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'collab_document')) { return true; } return false; } public function save_document() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'rt_collab_nonce')) { wp_die('安全验证失败'); } $document_id = intval($_POST['document_id']); $content = wp_kses_post($_POST['content']); $user_id = get_current_user_id(); // 保存到数据库 global $wpdb; $table_name = $wpdb->prefix . 'rt_collab_documents'; $result = $wpdb->update( $table_name, array( 'content' => $content, 'updated_at' => current_time('mysql') ), array('id' => $document_id), array('%s', '%s'), array('%d') ); if ($result !== false) { wp_send_json_success(array('message' => '文档保存成功')); } else { wp_send_json_error(array('message' => '保存失败')); } } } ### 3.2 前端JavaScript协同逻辑 创建`assets/js/document-editor.js`: (function($) { 'use strict'; class DocumentEditor { constructor(element) { this.element = element; this.documentId = element.data('document-id'); this.editor = null; this.websocket = null; this.userId = rtCollabConfig.current_user; this.revision = 0; this.pendingOperations = []; this.isApplyingRemote = false; this.init(); } init() { this.initEditor(); this.initWebSocket(); this.bindEvents(); this.loadDocument(); } initEditor() { // 初始化CodeMirror编辑器 this.editor = CodeMirror.fromTextArea( this.element.find('.document-editor')[0], { lineNumbers: true, mode: 'text/html', theme: 'default', lineWrapping: true, autofocus: true } ); // 监听本地编辑 this.editor.on('change', (instance, changeObj) => { this.handleLocalEdit(changeObj); }); // 监听光标移动 this.editor.on('cursorActivity', (instance) => { this.sendCursorPosition(); }); } initWebSocket() { this.websocket = new WebSocket(rtCollabConfig.websocket_url); this.websocket.onopen = () => { console.log('WebSocket连接已建立'); this.joinDocument(); }; this.websocket.onmessage = (event) => { this.handleWebSocketMessage(JSON.parse(event.data)); }; this.websocket.onerror = (error) => { console.error('WebSocket错误:', error); }; this.websocket.onclose = () => { console.log('WebSocket连接已关闭'); // 尝试重新连接 setTimeout(() => this.initWebSocket(), 3000); }; } joinDocument() { this.websocket.send(JSON.stringify({ type: 'join_document', document_id: this.documentId, user_id: this.userId })); } handleLocalEdit(changeObj) { if (this.isApplyingRemote) { return; } const operation = { type: 'edit', revision: this.revision, changes: [{ from: changeObj.from, to: changeObj.to, text: changeObj.text, removed: changeObj.removed }] }; // 发送到服务器 this.websocket.send(JSON.stringify({ type: 'document_edit', document_id: this.documentId, user_id: this.userId, operation: operation })); // 添加到待处理队列 this.pendingOperations.push(operation); } handleWebSocketMessage(message) { switch (message.type) { case 'document_content': this.loadContent(message.content); this.revision = message.revision; break; case 'document_update': this.applyRemoteEdit(message.operation, message.user_id); this.revision = message.revision; break; case 'user_joined': this.updateUserList(message); break; case 'user_left': this.updateUserList(message); break; case 'cursor_position': this.showRemoteCursor(message); break; } } loadContent(content) { this.isApplyingRemote = true; this.editor.setValue(content); this.isApplyingRemote = false; } applyRemoteEdit(operation, userId) { if (userId === this.userId) { // 自己的操作,从待处理队列中移除 this.pendingOperations = this.pendingOperations.filter(op => op.revision !== operation.revision ); return; } this.isApplyingRemote = true; // 应用远程编辑 operation.changes.forEach(change => { this.editor.replaceRange( change.text, change.from, change.to ); }); this.isApplyingRemote = false; // 更新待处理操作 this.pendingOperations = this.pendingOperations.map(op => this.transformOperation(op, operation) ); } transformOperation(localOp, remoteOp) { // 简化版操作转换 // 实际应实现完整的OT算法 return localOp; } sendCursorPosition() { const cursor = this.editor.getCursor(); this.websocket.send(JSON.stringify({ type: 'cursor_move', document_id: this.documentId, user_id: this.userId, position: cursor })); } showRemoteCursor(message) { // 显示其他用户的光标位置 // 实现光标可视化 } updateUserList(message) { const userList = this.element.find('.user-list'); const countElement = this.element.find('.user-count .count'); countElement.text(message.total_users); // 更新用户列表显示 // 实际实现中应从服务器获取完整用户列表 } loadDocument() { // 从服务器加载文档 $.ajax({ url: rtCollabConfig.ajax_url, method: 'POST', data: { action: 'get_document', document_id: this.documentId, nonce: rtCollabConfig.nonce }, success: (response) => { if (response.success) { this.loadContent(response.data.content); } } }); } bindEvents() { // 保存按钮 this.element.find('.btn-save').on('click', () => { this.saveDocument(); }); // 分享按钮 this.element.find('.btn-share').on('click', () => { this.shareDocument(); }); } saveDocument() { const content = this.editor.getValue(); $.ajax({ url: rtCollabConfig.ajax_url, method: 'POST', data: { action: 'save_document', document_id: this.documentId, content: content, nonce: rtCollabConfig.nonce }, success: (response) => { if (response.success) { alert('文档保存成功'); } else { alert('保存失败: ' + response.data.message); } } }); } shareDocument() { // 生成分享链接 const shareUrl = window.location.origin + '/?document=' + this.documentId; // 复制到剪贴板 navigator.clipboard.writeText(shareUrl) .then(() => alert('分享链接已复制到剪贴板')) .catch(() => prompt('请手动复制链接:', shareUrl)); } } // 初始化所有文档编辑器 $(document).ready(function() { $('.rt-collab-document-editor').each(function() { new DocumentEditor($(this)); }); }); })(jQuery); ## 第四部分:实现电子表格协同编辑器 ### 4.1 电子表格后端处理 创建`includes/class-spreadsheet-editor.php`: <?phpclass RT_Spreadsheet_Editor { 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('wp_enqueue_scripts', array($this, 'enqueue_scripts')); add_shortcode('collab_spreadsheet', array($this, 'spreadsheet_shortcode')); add_action('wp_ajax_save_spreadsheet', array($this, 'save_spreadsheet')); add_action('wp_ajax_get_spreadsheet', array($this, 'get_spreadsheet')); } public function enqueue_scripts() { if ($this->is_spreadsheet_page()) { // 加载Handsontable电子表格库 wp_enqueue_style('handsontable-css', 'https://cdn.jsdelivr.net/npm/handsontable@latest/dist/handsontable.full.min.css'); wp_enqueue_script('handsontable-js', 'https://cdn.jsdelivr.net/npm/handsontable@latest/dist/handsontable.full.min.js', array(), null, true); // 加载协同电子表格脚本 wp_enqueue_script('rt-collab-spreadsheet', RT_COLLAB_PLUGIN_URL . 'assets/js/spreadsheet-editor.js', array('jquery', 'handsontable-js'), RT_COLLAB_VERSION, true); wp_localize_script('rt-collab-spreadsheet', 'rtSpreadsheetConfig', array( 'ajax_url' => admin_url('admin-ajax.php'), 'websocket_url' => $this->get_websocket_url(), 'current_user' => get_current_user_id(), 'nonce' => wp_create_nonce('rt_spreadsheet_nonce') )); } } public function spreadsheet_shortcode($atts) { $atts = shortcode_atts(array( 'id' => 0, 'title' => '协同电子表格', 'rows' => 100, 'cols' => 26, 'height' => '500px' ), $atts, 'collab_spreadsheet'); if (!$atts['id']) { // 创建新电子表格 $atts['id'] = $this->create_new_spreadsheet($atts); } if (!$this->check_spreadsheet_access($atts['id'])) { return '<p>您没有权限访问此电子表格</p>'; } ob_start(); ?> <div class="rt-collab-spreadsheet-editor" data-spreadsheet-id="<?php echo esc_attr($atts['id']); ?>" data-rows="<?php echo esc_attr($atts['rows']); ?>" data-cols="<?php echo esc_attr($atts['cols']); ?>"> <div class="spreadsheet-header"> <h3><?php echo esc_html($atts['title']); ?></h3> <div class="spreadsheet-toolbar"> <button class="btn-save-sheet">保存</button> <button class="btn-export">导出Excel</button> <button class="btn-add-sheet">添加工作表</button> <span class="user-count">在线用户: <span class="count">1</span></span> </div> </div> <div class="spreadsheet-container" style="height: <?php echo esc_attr($atts['height']); ?>"> <div id="spreadsheet-<?php echo esc_attr($atts['id']); ?>" class="hot"></div> </div> <div class="spreadsheet-info"> <div class="sheet-tabs"> <!-- 工作表标签将通过JavaScript生成 --> </div> <div class="cell-info"> 选中: <span class="selected-range">A1</span> </div> </div> </div> <?php return ob_get_clean(); } private function create_new_spreadsheet($atts) { global $wpdb; $table_name = $wpdb->prefix . 'rt_collab_spreadsheets'; $default_data = array( 'sheets' => array( 'Sheet1' => array( 'data' => array(), 'rows' => intval($atts['rows']), 'cols' => intval($atts['cols']) ) ), 'activeSheet' => 'Sheet1' ); $wpdb->insert( $table_name, array( 'title' => $atts['title'], 'data' => json_encode($default_data), 'owner_id' => get_current_user_id(), 'rows' => $atts['rows'], 'columns' => $atts['cols'] ), array('%s', '%s', '%d', '%d', '%d') ); return $wpdb->insert_id; } private function check_spreadsheet_access($spreadsheet_id) { // 权限检查逻辑 return true; } public function save_spreadsheet() { if (!wp_verify_nonce($_POST['nonce'], 'rt_spreadsheet_nonce')) { wp_die('安全验证失败'); } $spreadsheet_id = intval($_POST['spreadsheet_id']); $data = json_decode(stripslashes($_POST['data']), true); global $wpdb; $table_name = $wpdb->prefix . 'rt_collab_spreadsheets'; $result = $wpdb->update( $table_name, array( 'data' => json_encode($data), 'updated_at' => current_time('mysql') ), array('id' => $spreadsheet_id), array('%s', '%s'), array('%d') ); if ($result !== false) { wp_send_json_success(array('message' => '保存成功')); } else { wp_send_json_error(array('message' => '保存失败')); } } public function get_spreadsheet() { $spreadsheet_id = intval($_GET['spreadsheet_id']); global $wpdb; $table_name = $wpdb->prefix . 'rt_collab_spreadsheets'; $spreadsheet = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $spreadsheet_id )); if ($spreadsheet) { $data = json_decode($spreadsheet->data, true); wp_send_json_success(array( 'data' => $data, 'title' => $spreadsheet->title, 'rows' => $spreadsheet->rows, 'columns' => $spreadsheet->columns )); } else { wp_send_json_error(array('message' => '电子表格不存在')); } } private function is_spreadsheet_page() { global $post; return is_a($post, 'WP_Post') && (has_shortcode($post->post_content, 'collab_spreadsheet') || has_shortcode($post->post_content, 'collab_document')); } } ### 4.2 电子表格前端协同逻辑 创建`assets/js/spreadsheet-editor.js`: (function($) { 'use strict'; class SpreadsheetEditor { constructor(element) { this.element = element; this.spreadsheetId = element.data('spreadsheet-id'); this.hot = null; this.websocket = null; this.userId = rtSpreadsheetConfig.current_user; this.data = null; this.pendingChanges = []; this.init(); } init() { this.loadSpreadsheet().then(() => { this.initHandsontable(); this.initWebSocket(); this.bindEvents(); }); } async loadSpreadsheet() { try { const response = await $.ajax({ url: rtSpreadsheet
发表评论详细指南:在WordPress中开发内嵌式项目管理与甘特图工具 摘要 本文提供了一份完整的指南,介绍如何在WordPress平台中通过代码二次开发,实现内嵌式项目管理与甘特图工具。我们将从需求分析开始,逐步讲解数据库设计、功能模块开发、甘特图集成、用户界面设计以及性能优化等关键环节,帮助开发者将常用互联网小工具功能无缝集成到WordPress系统中。 目录 项目概述与需求分析 WordPress开发环境搭建 数据库设计与数据模型 项目管理核心功能开发 甘特图集成与可视化 用户权限与团队协作 前端界面与用户体验优化 数据安全与性能优化 测试与部署指南 扩展与维护建议 1. 项目概述与需求分析 1.1 项目背景 随着远程工作和团队协作的普及,项目管理工具成为企业日常运营的重要组成部分。许多中小型企业使用WordPress作为其官方网站或内容管理系统,但缺乏集成的项目管理功能。通过开发内嵌式项目管理与甘特图工具,用户可以在熟悉的WordPress环境中管理项目,无需切换多个平台,提高工作效率。 1.2 功能需求 项目管理:创建、编辑、删除项目,设置项目基本信息 任务管理:任务创建、分配、优先级设置、状态跟踪 甘特图可视化:直观展示项目时间线、任务依赖关系 团队协作:用户角色分配、任务评论、文件附件 进度跟踪:完成百分比、里程碑标记、时间跟踪 报告与分析:项目进度报告、团队绩效统计 1.3 技术选型 核心框架:WordPress插件架构 前端技术:React/Vue.js(可选)、jQuery、HTML5、CSS3 图表库:DHTMLX Gantt、Frappe Gantt或自定义SVG实现 数据库:WordPress默认MySQL数据库 通信方式:REST API + AJAX 2. WordPress开发环境搭建 2.1 本地开发环境配置 # 使用Local by Flywheel或Docker配置WordPress环境 # 安装必要工具 npm install -g @wordpress/env wp-env start # 或使用传统方法 # 1. 安装XAMPP/MAMP/WAMP # 2. 下载最新WordPress # 3. 配置数据库 2.2 插件基础结构 创建插件主文件 project-management-gantt.php: <?php /** * Plugin Name: 项目管理与甘特图工具 * Plugin URI: https://yourwebsite.com/ * Description: 在WordPress中集成项目管理与甘特图功能 * Version: 1.0.0 * Author: 开发者名称 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('PMG_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('PMG_PLUGIN_URL', plugin_dir_url(__FILE__)); define('PMG_VERSION', '1.0.0'); // 初始化插件 require_once PMG_PLUGIN_DIR . 'includes/class-pmg-init.php'; PMG_Init::register(); 2.3 插件目录结构 project-management-gantt/ ├── project-management-gantt.php # 主插件文件 ├── includes/ # 核心类文件 │ ├── class-pmg-init.php # 初始化类 │ ├── class-pmg-database.php # 数据库处理 │ ├── class-pmg-projects.php # 项目管理类 │ ├── class-pmg-tasks.php # 任务管理类 │ └── class-pmg-gantt.php # 甘特图处理类 ├── admin/ # 后台管理文件 │ ├── css/ # 管理端CSS │ ├── js/ # 管理端JavaScript │ └── views/ # 管理端视图 ├── public/ # 前端文件 │ ├── css/ # 前端CSS │ ├── js/ # 前端JavaScript │ └── views/ # 前端视图 ├── assets/ # 静态资源 │ ├── lib/ # 第三方库 │ └── images/ # 图片资源 └── templates/ # 模板文件 3. 数据库设计与数据模型 3.1 自定义数据表设计 为了避免与WordPress核心表冲突,我们创建独立的数据表: // includes/class-pmg-database.php class PMG_Database { public static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 项目表 $projects_table = $wpdb->prefix . 'pmg_projects'; $projects_sql = "CREATE TABLE IF NOT EXISTS $projects_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, name varchar(255) NOT NULL, description text, status varchar(50) DEFAULT 'active', start_date date, end_date date, progress tinyint(3) DEFAULT 0, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; // 任务表 $tasks_table = $wpdb->prefix . 'pmg_tasks'; $tasks_sql = "CREATE TABLE IF NOT EXISTS $tasks_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, project_id mediumint(9) NOT NULL, parent_id mediumint(9) DEFAULT 0, title varchar(255) NOT NULL, description text, start_date date, end_date date, duration int(11) DEFAULT 1, progress tinyint(3) DEFAULT 0, priority varchar(20) DEFAULT 'medium', status varchar(50) DEFAULT 'pending', assigned_to bigint(20), sort_order int(11) DEFAULT 0, dependencies text, created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY project_id (project_id) ) $charset_collate;"; // 项目成员表 $members_table = $wpdb->prefix . 'pmg_project_members'; $members_sql = "CREATE TABLE IF NOT EXISTS $members_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, project_id mediumint(9) NOT NULL, user_id bigint(20) NOT NULL, role varchar(50) DEFAULT 'member', joined_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY project_user (project_id, user_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($projects_sql); dbDelta($tasks_sql); dbDelta($members_sql); } } 3.2 数据关系模型 一个项目包含多个任务 一个任务可以有子任务(通过parent_id实现层级结构) 一个项目可以有多个成员 一个用户可以参与多个项目 3.3 数据表优化考虑 添加适当的索引以提高查询性能 考虑大数据量下的分表策略 定期清理历史数据 4. 项目管理核心功能开发 4.1 项目CRUD操作 // includes/class-pmg-projects.php class PMG_Projects { // 创建新项目 public static function create_project($data) { global $wpdb; $table = $wpdb->prefix . 'pmg_projects'; $defaults = array( 'name' => '', 'description' => '', 'status' => 'active', 'start_date' => current_time('mysql'), 'end_date' => null, 'progress' => 0, 'created_by' => get_current_user_id() ); $data = wp_parse_args($data, $defaults); $wpdb->insert($table, $data); return $wpdb->insert_id; } // 获取项目列表 public static function get_projects($args = array()) { global $wpdb; $table = $wpdb->prefix . 'pmg_projects'; $defaults = array( 'status' => 'active', 'user_id' => 0, 'per_page' => 10, 'page' => 1 ); $args = wp_parse_args($args, $defaults); $where = "WHERE status = '" . esc_sql($args['status']) . "'"; if ($args['user_id'] > 0) { $members_table = $wpdb->prefix . 'pmg_project_members'; $where .= " AND id IN (SELECT project_id FROM $members_table WHERE user_id = " . intval($args['user_id']) . ")"; } $offset = ($args['page'] - 1) * $args['per_page']; $query = "SELECT * FROM $table $where ORDER BY created_at DESC LIMIT %d OFFSET %d"; return $wpdb->get_results($wpdb->prepare($query, $args['per_page'], $offset)); } // 更新项目 public static function update_project($project_id, $data) { global $wpdb; $table = $wpdb->prefix . 'pmg_projects'; return $wpdb->update($table, $data, array('id' => $project_id)); } // 删除项目 public static function delete_project($project_id) { global $wpdb; $table = $wpdb->prefix . 'pmg_projects'; // 同时删除相关任务和成员 $tasks_table = $wpdb->prefix . 'pmg_tasks'; $members_table = $wpdb->prefix . 'pmg_project_members'; $wpdb->delete($tasks_table, array('project_id' => $project_id)); $wpdb->delete($members_table, array('project_id' => $project_id)); return $wpdb->delete($table, array('id' => $project_id)); } } 4.2 任务管理功能 // includes/class-pmg-tasks.php class PMG_Tasks { // 创建任务 public static function create_task($data) { global $wpdb; $table = $wpdb->prefix . 'pmg_tasks'; $defaults = array( 'project_id' => 0, 'parent_id' => 0, 'title' => '', 'description' => '', 'start_date' => current_time('mysql'), 'end_date' => null, 'duration' => 1, 'progress' => 0, 'priority' => 'medium', 'status' => 'pending', 'assigned_to' => null, 'sort_order' => 0, 'dependencies' => '', 'created_by' => get_current_user_id() ); $data = wp_parse_args($data, $defaults); // 自动计算结束日期 if (empty($data['end_date']) && !empty($data['start_date']) && $data['duration'] > 0) { $start_date = new DateTime($data['start_date']); $start_date->modify('+' . ($data['duration'] - 1) . ' days'); $data['end_date'] = $start_date->format('Y-m-d'); } $wpdb->insert($table, $data); return $wpdb->insert_id; } // 获取项目任务树 public static function get_project_tasks($project_id, $flat = false) { global $wpdb; $table = $wpdb->prefix . 'pmg_tasks'; $tasks = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table WHERE project_id = %d ORDER BY parent_id, sort_order ASC", $project_id )); if ($flat) { return $tasks; } // 构建层级结构 return self::build_task_tree($tasks); } // 构建任务树 private static function build_task_tree($tasks, $parent_id = 0) { $tree = array(); foreach ($tasks as $task) { if ($task->parent_id == $parent_id) { $children = self::build_task_tree($tasks, $task->id); if ($children) { $task->children = $children; } $tree[] = $task; } } return $tree; } // 更新任务进度 public static function update_task_progress($task_id, $progress) { global $wpdb; $table = $wpdb->prefix . 'pmg_tasks'; $result = $wpdb->update($table, array('progress' => $progress, 'updated_at' => current_time('mysql')), array('id' => $task_id) ); // 更新父任务和项目进度 if ($result) { self::update_parent_progress($task_id); } return $result; } // 递归更新父任务进度 private static function update_parent_progress($task_id) { global $wpdb; $table = $wpdb->prefix . 'pmg_tasks'; $task = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $task_id)); if ($task && $task->parent_id > 0) { // 计算父任务下所有子任务的平均进度 $children = $wpdb->get_results($wpdb->prepare( "SELECT progress FROM $table WHERE parent_id = %d", $task->parent_id )); if ($children) { $total_progress = 0; foreach ($children as $child) { $total_progress += $child->progress; } $avg_progress = round($total_progress / count($children)); $wpdb->update($table, array('progress' => $avg_progress), array('id' => $task->parent_id) ); // 递归更新 self::update_parent_progress($task->parent_id); } } // 更新项目进度 if ($task) { self::update_project_progress($task->project_id); } } // 更新项目进度 private static function update_project_progress($project_id) { global $wpdb; $tasks_table = $wpdb->prefix . 'pmg_tasks'; $projects_table = $wpdb->prefix . 'pmg_projects'; // 计算项目下所有根任务的平均进度 $root_tasks = $wpdb->get_results($wpdb->prepare( "SELECT progress FROM $tasks_table WHERE project_id = %d AND parent_id = 0", $project_id )); if ($root_tasks) { $total_progress = 0; foreach ($root_tasks as $task) { $total_progress += $task->progress; } $avg_progress = round($total_progress / count($root_tasks)); $wpdb->update($projects_table, array('progress' => $avg_progress), array('id' => $project_id) ); } } } 4.3 REST API端点 // 注册REST API路由 add_action('rest_api_init', function() { // 项目相关端点 register_rest_route('pmg/v1', '/projects', array( array( 'methods' => 'GET', 'callback' => 'pmg_rest_get_projects', 'permission_callback' => function() { return current_user_can('read'); } ), array( 'methods' => 'POST', 'callback' => 'pmg_rest_create_project', 'permission_callback' => function() { return current_user_can('edit_posts'); } ) )); // 任务相关端点 register_rest_route('pmg/v1', '/projects/(?P<project_id>d+)/tasks', array( 'methods' => 'GET', 'callback' => 'pmg_rest_get_tasks', 'permission_callback' => function($request) { return pmg_check_project_access($request['project_id']); } )); register_rest_route('pmg/v1', '/tasks/(?P<task_id>d+)', array( array( 'methods' => 'PUT', 'callback' => 'pmg_rest_update_task', 'permission_callback' => function($request) { return pmg_check_task_access($request['task_id']); } ), array( 'methods' => 'DELETE', 'callback' => 'pmg_rest_delete_task', 'permission_callback' => function($request) { return current_user_can('delete_posts'); } ) )); }); // REST API回调函数示例 function pmg_rest_get_projects(WP_REST_Request $request) { $args = array( 'status' => $request->get_param('status') ?: 'active', 'user_id' => get_current_user_id(), 'page' => $request->get_param('page') ?: 1 ); $projects = PMG_Projects::get_projects($args); return new WP_REST_Response($projects, 200); } function pmg_rest_create_project(WP_REST_Request $request) { $data = $request->get_json_params(); $project_id = PMG_Projects::create_project($data); if ($project_id) { // 自动将创建者添加为项目管理员 PMG_Projects::add_project_member($project_id, get_current_user_id(), 'admin'); return new WP_REST_Response(array( 'id' => $project_id, 'message' => '项目创建成功' ), 201); } return new WP_REST_Response(array( 'error' => '项目创建失败' ), 500); } 5. 甘特图集成与可视化 5.1 甘特图库选择与集成 5.1.1 选择适合的甘特图库 DHTMLX Gantt:功能强大,商业使用需授权 Frappe Gantt:开源免费,轻量级 Gantt-elastic:基于SVG,响应式设计 自定义实现:完全控制,但开发成本高 5.2 使用Frappe Gantt实现 <!-- public/views/gantt-view.php --> <div class="pmg-gantt-container"> <div class="pmg-gantt-toolbar"> <button class="pmg-btn pmg-btn-zoom-in">放大</button> <button class="pmg-btn pmg-btn-zoom-out">缩小</button> <button class="pmg-btn pmg-btn-today">今天</button> <select class="pmg-select-view"> <option value="Day">日视图</option> <option value="Week">周视图</option> <option value="Month">月视图</option> </select> </div> <div id="pmg-gantt-chart"></div> </div> // public/js/gantt-chart.js (function($) { 'use strict'; class PMG_GanttChart { constructor(containerId, projectId) { this.container = document.getElementById(containerId); this.projectId = projectId; this.gantt = null; this.init(); } init() { // 加载Frappe Gantt库 this.loadGanttLibrary().then(() => { this.setupGantt(); this.loadProjectData(); this.bindEvents(); }); } loadGanttLibrary() { return new Promise((resolve) => { if (typeof Gantt !== 'undefined') { resolve(); return; } // 动态加载CSS和JS const cssLink = document.createElement('link'); cssLink.rel = 'stylesheet'; cssLink.href = PMG_Gantt.pluginUrl + 'assets/lib/frappe-gantt/frappe-gantt.css'; document.head.appendChild(cssLink); const script = document.createElement('script'); script.src = PMG_Gantt.pluginUrl + 'assets/lib/frappe-gantt/frappe-gantt.min.js'; script.onload = resolve; document.head.appendChild(script); }); } setupGantt() { this.gantt = new Gantt(this.container, [], { header_height: 50, column_width: 30, step: 24, view_modes: ['Day', 'Week', 'Month'], bar_height: 20, bar_corner_radius: 3, arrow_curve: 5, padding: 18, view_mode: 'Week', date_format: 'YYYY-MM-DD', custom_popup_html: null, on_click: (task) => this.onTaskClick(task), on_date_change: (task, start, end) => this.onDateChange(task, start, end), on_progress_change: (task, progress) => this.onProgressChange(task, progress), on_view_change: (mode) => this.onViewChange(mode) }); } loadProjectData() { $.ajax({ url: PMG_Gantt.restUrl + 'pmg/v1/projects/' + this.projectId + '/gantt-data', method: 'GET', beforeSend: (xhr) => { xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce); }, success: (response) => { this.transformAndLoadData(response); }, error: (error) => { console.error('加载甘特图数据失败:', error); } }); } transformAndLoadData(data) { // 转换数据为甘特图所需格式 const ganttTasks = data.tasks.map(task => ({ id: task.id.toString(), name: task.title, start: task.start_date, end: task.end_date, progress: task.progress, dependencies: task.dependencies ? task.dependencies.split(',') : [], custom_class: task.priority + '-priority' })); this.gantt.refresh(ganttTasks); } onTaskClick(task) { // 打开任务详情模态框 this.openTaskModal(task.id); } onDateChange(task, start, end) { // 更新任务日期 $.ajax({ url: PMG_Gantt.restUrl + 'pmg/v1/tasks/' + task.id, method: 'PUT', beforeSend: (xhr) => { xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce); }, data: JSON.stringify({ start_date: start, end_date: end }), contentType: 'application/json', success: () => { console.log('任务日期更新成功'); }, error: (error) => { console.error('更新失败:', error); // 恢复原始日期 this.loadProjectData(); } }); } onProgressChange(task, progress) { // 更新任务进度 $.ajax({ url: PMG_Gantt.restUrl + 'pmg/v1/tasks/' + task.id + '/progress', method: 'PUT', beforeSend: (xhr) => { xhr.setRequestHeader('X-WP-Nonce', PMG_Gantt.nonce); }, data: JSON.stringify({ progress: progress }), contentType: 'application/json', success: () => { console.log('任务进度更新成功'); } }); } bindEvents() { // 工具栏事件绑定 $('.pmg-btn-zoom-in').on('click', () => this.zoomIn()); $('.pmg-btn-zoom-out').on('click', () => this.zoomOut()); $('.pmg-btn-today').on('click', () => this.scrollToToday()); $('.pmg-select-view').on('change', (e) => this.changeView(e.target.value)); } zoomIn() { const currentWidth = this.gantt.options.column_width; this.gantt.change_view_mode({ ...this.gantt.options, column_width: Math.min(currentWidth + 10, 100) }); } zoomOut() { const currentWidth = this.gantt.options.column_width; this.gantt.change_view_mode({ ...this.gantt.options, column_width: Math.max(currentWidth - 10, 10) }); } scrollToToday() { const today = new Date(); this.gantt.scroll_to(today); } changeView(mode) { this.gantt.change_view_mode(mode); } openTaskModal(taskId) { // 实现任务详情模态框 console.log('打开任务详情:', taskId); } } // 初始化甘特图 $(document).ready(function() { if ($('#pmg-gantt-chart').length) { const projectId = $('#pmg-gantt-chart').data('project-id'); window.pmgGantt = new PMG_GanttChart('pmg-gantt-chart', projectId); } }); })(jQuery); 5.3 甘特图数据API端点 // 添加甘特图数据端点 add_action('rest_api_init', function() { register_rest_route('pmg/v1', '/projects/(?P<project_id>d+)/gantt-data', array( 'methods' => 'GET', 'callback' => 'pmg_rest_get_gantt_data', 'permission_callback' => function($request) { return pmg_check_project_access($request['project_id']); } )); }); function pmg_rest_get_gantt_data(WP_REST_Request $request) { $project_id = $request->get_param('project_id'); // 获取项目任务 $tasks = PMG_Tasks::get_project_tasks($project_id, true); // 格式化任务依赖关系 $formatted_tasks = array(); foreach ($tasks as $task) { $formatted_task = array( 'id' => $task->id, 'title' => $task->title, 'start_date' => $task->start_date, 'end_date' => $task->end_date, 'progress' => $task->progress, 'priority' => $task->priority, 'dependencies' => $task->dependencies ); $formatted_tasks[] = $formatted_task; } // 获取项目信息 $project = PMG_Projects::get_project($project_id); return new WP_REST_Response(array( 'project' => $project, 'tasks' => $formatted_tasks ), 200); } 5.4 甘特图样式定制 /* public/css/gantt-styles.css */ .pmg-gantt-container { background: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; margin: 20px 0; } .pmg-gantt-toolbar { padding: 15px; background: #f8f9fa; border-bottom: 1px solid #e9ecef; display: flex; gap: 10px; align-items: center; } .pmg-btn { padding: 8px 16px; background: #007cba; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background 0.3s; } .pmg-btn:hover { background: #005a87; } .pmg-select-view { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; background: white; } /* 甘特图任务样式 */ .gantt .bar { rx: 4; ry: 4; } .gantt .bar-wrapper { cursor: pointer; } .gantt .bar-progress { fill: #4CAF50; } /* 优先级颜色 */ .high-priority .bar { fill: #ff6b6b; } .medium-priority .bar { fill: #4d96ff; } .low-priority .bar { fill: #6bcf7f; } /* 里程碑样式 */ .milestone .bar { fill: #ffd166; width: 10px; rx: 10; ry: 10; } /* 依赖线样式 */ .gantt .arrow { stroke: #666; stroke-width: 2; fill: none; } 6. 用户权限与团队协作 6.1 用户角色系统 // includes/class-pmg-permissions.php class PMG_Permissions { // 定义项目角色 const ROLES = array( 'admin' => array( 'name' => '管理员', 'capabilities' => array( 'edit_project', 'delete_project', 'manage_members', 'create_tasks', 'edit_all_tasks', 'delete_tasks', 'assign_tasks' ) ), 'manager' => array( 'name' => '经理', 'capabilities' => array( 'edit_project', 'create_tasks', 'edit_all_tasks', 'assign_tasks' ) ), 'member' => array( 'name' => '成员', 'capabilities' => array( 'view_project', 'create_tasks', 'edit_own_tasks' ) ), 'viewer' => array( 'name' => '观察者', 'capabilities' => array( 'view_project' ) ) ); // 检查用户权限 public static function user_can($user_id, $project_id, $capability) { $user_role = self::get_user_role($user_id, $project_id); if (!$user_role) { return false; } // 管理员拥有所有权限 if ($user_role === 'admin') { return true; } // 检查角色权限 if (isset(self::ROLES[$user_role])) { return in_array($capability, self::ROLES[$user_role]['capabilities']); } return false; } // 获取用户在项目中的角色 public static function get_user_role($user_id, $project_id) { global $wpdb; $table = $wpdb->prefix . 'pmg_project_members'; $role = $wpdb->get_var($wpdb->prepare( "SELECT role FROM $table WHERE user_id = %d AND project_id = %d", $user_id, $project_id )); return $role ?: false; } // 添加项目成员 public static function add_project_member($project_id, $user_id, $role = 'member') { global $wpdb; $table = $wpdb->prefix . 'pmg_project_members'; // 检查是否已是成员 $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table WHERE project_id = %d AND user_id = %d", $project_id, $user_id )); if ($existing) { return $wpdb->update($table, array('role' => $role), array('id' => $existing) ); } return $wpdb->insert($table, array( 'project_id' => $project_id, 'user_id' => $user_id, 'role' => $role )); } // 获取项目成员列表 public static function get_project_members($project_id) { global $wpdb; $members_table = $wpdb->prefix . 'pmg_project_members'; $users_table = $wpdb->users; return $wpdb->get_results($wpdb->prepare( "SELECT m.*, u.display_name, u.user_email FROM $members_table m LEFT JOIN $users_table u ON m.user_id = u.ID WHERE m.project_id = %d ORDER BY m.joined_at ASC", $project_id )); } } 6.2 团队协作功能 // includes/class-pmg-collaboration.php class PMG_Collaboration { // 添加任务评论 public static function add_task_comment($task_id, $user_id, $content) { global $wpdb; $table = $wpdb->prefix . 'pmg_task_comments'; $comment_id = $wpdb->insert($table, array( 'task_id' => $task_id, 'user_id' => $user_id, 'content' => wp_kses_post($content), 'created_at' => current_time('mysql') )); if ($comment_id) { // 发送通知 self::notify_task_comment($task_id, $user_id, $content); } return $comment_id; } // 获取任务评论 public static function get_task_comments($task_id) { global $wpdb; $comments_table = $wpdb->prefix . 'pmg_task_comments'; $users_table = $wpdb->users; return $wpdb->get_results($wpdb->prepare( "SELECT c.*, u.display_name, u.user_email FROM $comments_table c LEFT JOIN $users_table u ON c.user_id = u.ID WHERE c.task_id = %d ORDER BY c.created_at ASC", $task_id )); } // 添加文件附件 public static function add_task_attachment($task_id, $user_id, $file_data) { // 使用WordPress媒体库上传文件 require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/media.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); $upload = wp_handle_upload($file_data, array('test_form' => false)); if (isset($upload['error'])) { return new WP_Error('upload_error', $upload['error']); } // 创建附件记录 global $wpdb; $table = $wpdb->prefix . 'pmg_task_attachments'; $attachment_id = $wpdb->insert($table, array( 'task_id' => $task_id,
发表评论手把手教学:为你的网站集成智能合同模板与在线签署流程 引言:数字时代的企业合同管理变革 在当今数字化商业环境中,纸质合同的局限性日益凸显。传统合同签署流程不仅耗时耗力,还存在存储不便、安全性低、管理困难等问题。随着远程办公和电子商务的蓬勃发展,企业对高效、安全、便捷的在线合同签署解决方案的需求日益增长。 WordPress作为全球最流行的内容管理系统,为中小企业和自由职业者提供了强大的网站建设平台。通过代码二次开发,我们可以为WordPress网站集成智能合同模板与在线签署流程,将原本复杂的法律文件处理转化为简单、自动化的在线操作。 本教程将带领您一步步实现这一功能,无需昂贵的第三方服务,通过自主开发打造完全符合您业务需求的智能合同系统。 第一部分:准备工作与环境搭建 1.1 系统需求分析 在开始开发之前,我们需要明确系统的基本功能需求: 合同模板管理:创建、编辑、存储可重复使用的合同模板 变量替换系统:在模板中插入动态字段(如客户姓名、日期、金额等) 在线签署功能:支持多方电子签名,具备法律效力 合同状态跟踪:实时监控合同创建、发送、签署、完成等状态 安全存储与备份:加密存储已签署合同,防止篡改 通知系统:自动邮件通知合同相关方 1.2 开发环境配置 确保您的WordPress环境满足以下条件: WordPress 5.0或更高版本 PHP 7.4或更高版本(推荐8.0+) MySQL 5.6或更高版本 启用HTTPS(电子签名必需) 安装并激活代码编辑器插件(如Code Snippets) 1.3 创建自定义插件 为了避免主题更新导致代码丢失,我们将创建一个独立的插件: <?php /** * Plugin Name: 智能合同管理系统 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress网站添加智能合同模板与在线签署功能 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SCM_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SCM_PLUGIN_URL', plugin_dir_url(__FILE__)); define('SCM_VERSION', '1.0.0'); // 初始化插件 require_once SCM_PLUGIN_DIR . 'includes/class-contract-manager.php'; function scm_init() { $contract_manager = new Contract_Manager(); $contract_manager->init(); } add_action('plugins_loaded', 'scm_init'); 第二部分:数据库设计与合同模板系统 2.1 创建数据库表 我们需要创建几个核心数据库表来存储合同相关数据: // 在Contract_Manager类中添加数据库创建方法 public function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 合同模板表 $templates_table = $wpdb->prefix . 'scm_templates'; $sql1 = "CREATE TABLE IF NOT EXISTS $templates_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL, content longtext NOT NULL, variables text, 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', PRIMARY KEY (id) ) $charset_collate;"; // 合同实例表 $contracts_table = $wpdb->prefix . 'scm_contracts'; $sql2 = "CREATE TABLE IF NOT EXISTS $contracts_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, template_id mediumint(9) NOT NULL, contract_number varchar(100) NOT NULL, title varchar(200) NOT NULL, content longtext NOT NULL, parties text NOT NULL, variables_values text, status varchar(50) DEFAULT 'draft', created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, expires_at datetime, completed_at datetime, PRIMARY KEY (id), UNIQUE KEY contract_number (contract_number) ) $charset_collate;"; // 签署记录表 $signatures_table = $wpdb->prefix . 'scm_signatures'; $sql3 = "CREATE TABLE IF NOT EXISTS $signatures_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, contract_id mediumint(9) NOT NULL, party_email varchar(200) NOT NULL, party_name varchar(200) NOT NULL, signature_data text, signed_at datetime, ip_address varchar(45), user_agent text, verification_hash varchar(255), PRIMARY KEY (id), KEY contract_id (contract_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql1); dbDelta($sql2); dbDelta($sql3); } 2.2 合同模板编辑器 创建合同模板管理界面,允许用户通过可视化编辑器创建模板: // 添加模板编辑器短代码 public function add_template_editor_shortcode() { add_shortcode('contract_template_editor', array($this, 'render_template_editor')); } public function render_template_editor() { // 检查用户权限 if (!current_user_can('manage_options')) { return '<p>您没有权限访问此功能。</p>'; } ob_start(); ?> <div class="scm-template-editor"> <h2>智能合同模板编辑器</h2> <div class="scm-editor-container"> <div class="scm-sidebar"> <h3>可用变量</h3> <div class="variable-list"> <div class="variable-item" data-variable="{client_name}"> <span class="variable-label">客户姓名</span> <code>{client_name}</code> </div> <div class="variable-item" data-variable="{client_email}"> <span class="variable-label">客户邮箱</span> <code>{client_email}</code> </div> <div class="variable-item" data-variable="{contract_date}"> <span class="variable-label">合同日期</span> <code>{contract_date}</code> </div> <div class="variable-item" data-variable="{amount}"> <span class="variable-label">金额</span> <code>{amount}</code> </div> <div class="variable-item" data-variable="{service_description}"> <span class="variable-label">服务描述</span> <code>{service_description}</code> </div> <div class="variable-item" data-variable="{terms}"> <span class="variable-label">条款</span> <code>{terms}</code> </div> </div> <button type="button" class="button button-primary" id="add-custom-variable"> 添加自定义变量 </button> </div> <div class="scm-editor-main"> <div class="form-group"> <label for="template-title">模板标题</label> <input type="text" id="template-title" class="widefat" placeholder="输入模板标题"> </div> <div class="form-group"> <label for="template-content">模板内容</label> <?php // 使用WordPress编辑器 wp_editor('', 'template-content', array( 'textarea_name' => 'template_content', 'editor_height' => 400, 'media_buttons' => false, 'tinymce' => array( 'toolbar1' => 'formatselect,bold,italic,bullist,numlist,blockquote,alignleft,aligncenter,alignright,link,unlink,undo,redo' ) )); ?> </div> <div class="form-actions"> <button type="button" class="button button-primary" id="save-template"> 保存模板 </button> <button type="button" class="button" id="preview-template"> 预览合同 </button> </div> </div> </div> <div id="template-preview-modal" class="scm-modal" style="display:none;"> <div class="scm-modal-content"> <span class="scm-close-modal">×</span> <h3>合同预览</h3> <div id="preview-content"></div> </div> </div> </div> <style> .scm-template-editor { max-width: 1200px; margin: 20px auto; background: #fff; padding: 20px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .scm-editor-container { display: flex; gap: 20px; margin-top: 20px; } .scm-sidebar { width: 300px; background: #f5f5f5; padding: 15px; border-radius: 5px; } .scm-editor-main { flex: 1; } .variable-list { margin-bottom: 20px; } .variable-item { background: #fff; padding: 10px; margin-bottom: 8px; border-radius: 4px; border-left: 4px solid #0073aa; cursor: pointer; transition: all 0.3s; } .variable-item:hover { background: #e3f2fd; transform: translateX(5px); } .variable-item code { display: block; margin-top: 5px; padding: 3px 6px; background: #f1f1f1; border-radius: 3px; font-size: 12px; } .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 5px; font-weight: bold; } .scm-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 9999; display: flex; align-items: center; justify-content: center; } .scm-modal-content { background: #fff; width: 80%; max-width: 900px; max-height: 80vh; overflow-y: auto; padding: 20px; border-radius: 5px; position: relative; } .scm-close-modal { position: absolute; top: 15px; right: 20px; font-size: 24px; cursor: pointer; } </style> <script> jQuery(document).ready(function($) { // 插入变量到编辑器 $('.variable-item').click(function() { var variable = $(this).data('variable'); var editor = tinyMCE.get('template-content'); if (editor) { editor.insertContent(variable); } else { var textarea = $('#template-content'); var currentVal = textarea.val(); var cursorPos = textarea.prop('selectionStart'); var newVal = currentVal.substring(0, cursorPos) + variable + currentVal.substring(cursorPos); textarea.val(newVal); } }); // 保存模板 $('#save-template').click(function() { var title = $('#template-title').val(); var content = ''; if (tinyMCE.get('template-content')) { content = tinyMCE.get('template-content').getContent(); } else { content = $('#template-content').val(); } if (!title || !content) { alert('请填写模板标题和内容'); return; } // 提取变量 var variables = []; var regex = /{([^}]+)}/g; var match; while ((match = regex.exec(content)) !== null) { if (variables.indexOf(match[1]) === -1) { variables.push(match[1]); } } $.ajax({ url: '<?php echo admin_url("admin-ajax.php"); ?>', type: 'POST', data: { action: 'scm_save_template', title: title, content: content, variables: JSON.stringify(variables), nonce: '<?php echo wp_create_nonce("scm_save_template_nonce"); ?>' }, success: function(response) { if (response.success) { alert('模板保存成功!'); window.location.reload(); } else { alert('保存失败:' + response.data); } } }); }); // 预览合同 $('#preview-template').click(function() { var content = ''; if (tinyMCE.get('template-content')) { content = tinyMCE.get('template-content').getContent(); } else { content = $('#template-content').val(); } if (!content) { alert('请先输入模板内容'); return; } // 替换变量为示例值 var previewContent = content .replace(/{client_name}/g, '<span class="variable-example">张三</span>') .replace(/{client_email}/g, '<span class="variable-example">zhangsan@example.com</span>') .replace(/{contract_date}/g, '<span class="variable-example">' + new Date().toLocaleDateString() + '</span>') .replace(/{amount}/g, '<span class="variable-example">¥5,000.00</span>') .replace(/{service_description}/g, '<span class="variable-example">网站设计与开发服务</span>') .replace(/{terms}/g, '<span class="variable-example">30天内完成所有工作</span>'); $('#preview-content').html(previewContent); $('#template-preview-modal').show(); }); // 关闭模态框 $('.scm-close-modal').click(function() { $('#template-preview-modal').hide(); }); // 点击模态框外部关闭 $(window).click(function(event) { if ($(event.target).hasClass('scm-modal')) { $('.scm-modal').hide(); } }); }); </script> <?php return ob_get_clean(); } 第三部分:合同创建与变量替换系统 3.1 合同创建界面 创建用户友好的合同创建界面,允许用户选择模板并填写变量值: // 添加合同创建短代码 public function add_contract_creator_shortcode() { add_shortcode('create_contract', array($this, 'render_contract_creator')); } public function render_contract_creator() { global $wpdb; // 获取可用模板 $templates_table = $wpdb->prefix . 'scm_templates'; $templates = $wpdb->get_results( "SELECT id, title FROM $templates_table WHERE status = 'active' ORDER BY title" ); ob_start(); ?> <div class="scm-contract-creator"> <h2>创建新合同</h2> <div class="scm-form-container"> <div class="form-step" id="step1"> <h3>步骤1:选择合同模板</h3> <div class="template-grid"> <?php if ($templates): ?> <?php foreach ($templates as $template): ?> <div class="template-card" data-template-id="<?php echo $template->id; ?>"> <h4><?php echo esc_html($template->title); ?></h4> <button type="button" class="button select-template" data-template-id="<?php echo $template->id; ?>"> 选择此模板 </button> </div> <?php endforeach; ?> <?php else: ?> <p>暂无可用模板。请先创建合同模板。</p> <?php endif; ?> </div> </div> <div class="form-step" id="step2" style="display:none;"> <h3>步骤2:填写合同信息</h3> <form id="contract-details-form"> <div id="dynamic-fields-container"> <!-- 动态字段将在这里生成 --> </div> <div class="form-section"> <h4>签署方信息</h4> <div class="party-section"> <h5>甲方(我方)</h5> <div class="form-row"> <div class="form-group"> <label>公司/个人名称</label> <input type="text" name="party_a_name" required value="<?php echo get_bloginfo('name'); ?>"> </div> <div class="form-group"> <label>电子邮箱</label> <input type="email" name="party_a_email" required value="<?php echo get_option('admin_email'); ?>"> </div> </div> </div> <div class="party-section"> <h5>乙方(客户)</h5> <div class="form-row"> <div class="form-group"> <label>客户姓名</label> <input type="text" name="party_b_name" required> </div> <div class="form-group"> </div> <div class="form-group"> <label>客户邮箱</label> <input type="email" name="party_b_email" required> </div> </div> <button type="button" class="button button-secondary" id="add-party"> 添加更多签署方 </button> </div> </div> <div class="form-actions"> <button type="button" class="button" id="back-to-step1"> 返回上一步 </button> <button type="submit" class="button button-primary"> 生成合同并发送 </button> </div> </form> </div> <div class="form-step" id="step3" style="display:none;"> <h3>合同创建成功!</h3> <div class="success-message"> <p>合同已成功创建并发送给相关签署方。</p> <p>合同编号:<strong id="contract-number"></strong></p> <p>您可以在 <a href="#" id="contract-management-link">合同管理页面</a> 跟踪签署状态。</p> </div> </div> </div> </div> <style> .scm-contract-creator { max-width: 1000px; margin: 20px auto; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } .template-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; margin-top: 20px; } .template-card { background: #f8f9fa; border: 2px solid #e9ecef; border-radius: 8px; padding: 20px; text-align: center; transition: all 0.3s ease; cursor: pointer; } .template-card:hover { border-color: #0073aa; transform: translateY(-5px); box-shadow: 0 5px 15px rgba(0,115,170,0.1); } .template-card h4 { margin-top: 0; color: #333; min-height: 60px; display: flex; align-items: center; justify-content: center; } .form-step { animation: fadeIn 0.5s ease; } @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .form-section { background: #f8f9fa; padding: 20px; border-radius: 6px; margin: 25px 0; } .form-section h4 { margin-top: 0; color: #0073aa; border-bottom: 2px solid #dee2e6; padding-bottom: 10px; } .party-section { margin-bottom: 25px; } .party-section h5 { color: #495057; margin-bottom: 15px; font-size: 16px; } .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; font-weight: 600; color: #495057; } .form-group input, .form-group textarea, .form-group select { width: 100%; padding: 10px; border: 1px solid #ced4da; border-radius: 4px; font-size: 14px; transition: border-color 0.3s; } .form-group input:focus, .form-group textarea:focus, .form-group select:focus { border-color: #0073aa; outline: none; box-shadow: 0 0 0 3px rgba(0,115,170,0.1); } .form-actions { display: flex; justify-content: space-between; margin-top: 30px; padding-top: 20px; border-top: 1px solid #dee2e6; } .success-message { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; padding: 20px; border-radius: 6px; text-align: center; } .success-message p { margin: 10px 0; font-size: 16px; } .dynamic-field-group { background: #fff; border: 1px solid #dee2e6; border-radius: 6px; padding: 15px; margin-bottom: 15px; } .dynamic-field-group label { display: block; margin-bottom: 8px; font-weight: 600; color: #495057; } .dynamic-field-group .field-description { font-size: 12px; color: #6c757d; margin-top: 5px; font-style: italic; } </style> <script> jQuery(document).ready(function($) { var selectedTemplateId = null; var templateVariables = []; // 选择模板 $('.select-template').click(function() { selectedTemplateId = $(this).data('template-id'); // 获取模板变量 $.ajax({ url: '<?php echo admin_url("admin-ajax.php"); ?>', type: 'POST', data: { action: 'scm_get_template_variables', template_id: selectedTemplateId, nonce: '<?php echo wp_create_nonce("scm_get_variables_nonce"); ?>' }, success: function(response) { if (response.success) { templateVariables = response.data.variables; renderDynamicFields(templateVariables); $('#step1').hide(); $('#step2').show(); } else { alert('获取模板信息失败'); } } }); }); // 渲染动态字段 function renderDynamicFields(variables) { var container = $('#dynamic-fields-container'); container.empty(); if (variables.length === 0) { container.html('<p>此模板没有需要填写的变量。</p>'); return; } container.html('<h4>合同变量填写</h4>'); variables.forEach(function(variable) { var fieldHtml = ` <div class="dynamic-field-group"> <label for="var_${variable}">${formatVariableName(variable)}</label> `; // 根据变量名猜测字段类型 if (variable.includes('date') || variable.includes('Date')) { fieldHtml += ` <input type="date" id="var_${variable}" name="variables[${variable}]" required> <div class="field-description">请选择日期</div> `; } else if (variable.includes('amount') || variable.includes('price') || variable.includes('金额') || variable.includes('价格')) { fieldHtml += ` <input type="number" id="var_${variable}" name="variables[${variable}]" step="0.01" min="0" required> <div class="field-description">请输入金额(单位:元)</div> `; } else if (variable.includes('description') || variable.includes('content') || variable.includes('描述') || variable.includes('内容')) { fieldHtml += ` <textarea id="var_${variable}" name="variables[${variable}]" rows="3" required></textarea> <div class="field-description">请详细描述</div> `; } else if (variable.includes('email') || variable.includes('邮箱')) { fieldHtml += ` <input type="email" id="var_${variable}" name="variables[${variable}]" required> <div class="field-description">请输入有效的邮箱地址</div> `; } else { fieldHtml += ` <input type="text" id="var_${variable}" name="variables[${variable}]" required> <div class="field-description">请填写${formatVariableName(variable)}</div> `; } fieldHtml += '</div>'; container.append(fieldHtml); }); } // 格式化变量名显示 function formatVariableName(variable) { // 移除花括号 var name = variable.replace(/[{}]/g, ''); // 将下划线或连字符转换为空格 name = name.replace(/[_-]/g, ' '); // 首字母大写 return name.replace(/bw/g, function(l) { return l.toUpperCase(); }); } // 返回上一步 $('#back-to-step1').click(function() { $('#step2').hide(); $('#step1').show(); }); // 添加更多签署方 $('#add-party').click(function() { var partyCount = $('.party-section').length - 2; // 减去甲方和乙方 if (partyCount >= 3) { alert('最多支持5个签署方'); return; } var partyLetter = String.fromCharCode(67 + partyCount); // C, D, E... var partyHtml = ` <div class="party-section additional-party"> <h5>${partyLetter}方</h5> <div class="form-row"> <div class="form-group"> <label>签署方名称</label> <input type="text" name="party_${partyLetter.toLowerCase()}_name" required> </div> <div class="form-group"> <label>签署方邮箱</label> <input type="email" name="party_${partyLetter.toLowerCase()}_email" required> </div> </div> <button type="button" class="button button-small remove-party"> 移除此签署方 </button> </div> `; $(this).before(partyHtml); }); // 移除签署方 $(document).on('click', '.remove-party', function() { $(this).closest('.party-section').remove(); }); // 提交合同表单 $('#contract-details-form').submit(function(e) { e.preventDefault(); // 收集表单数据 var formData = $(this).serializeArray(); var variables = {}; // 提取变量数据 formData.forEach(function(item) { if (item.name.startsWith('variables[')) { var varName = item.name.match(/[(.*?)]/)[1]; variables[varName] = item.value; } }); // 收集签署方信息 var parties = []; $('input[name^="party_"]').each(function() { var name = $(this).attr('name'); var value = $(this).val(); if (name.endsWith('_name')) { var partyKey = name.replace('_name', ''); var partyIndex = parties.findIndex(p => p.key === partyKey); if (partyIndex === -1) { parties.push({ key: partyKey, name: value, email: '' }); } else { parties[partyIndex].name = value; } } else if (name.endsWith('_email')) { var partyKey = name.replace('_email', ''); var partyIndex = parties.findIndex(p => p.key === partyKey); if (partyIndex === -1) { parties.push({ key: partyKey, name: '', email: value }); } else { parties[partyIndex].email = value; } } }); // 发送AJAX请求创建合同 $.ajax({ url: '<?php echo admin_url("admin-ajax.php"); ?>', type: 'POST', data: { action: 'scm_create_contract', template_id: selectedTemplateId, variables: JSON.stringify(variables), parties: JSON.stringify(parties), nonce: '<?php echo wp_create_nonce("scm_create_contract_nonce"); ?>' }, beforeSend: function() { $('.form-actions button').prop('disabled', true).text('处理中...'); }, success: function(response) { if (response.success) { $('#contract-number').text(response.data.contract_number); $('#contract-management-link').attr('href', response.data.management_url); $('#step2').hide(); $('#step3').show(); } else { alert('创建合同失败:' + response.data); $('.form-actions button').prop('disabled', false).text('生成合同并发送'); } }, error: function() { alert('网络错误,请重试'); $('.form-actions button').prop('disabled', false).text('生成合同并发送'); } }); }); }); </script> <?php return ob_get_clean(); } ### 3.2 变量替换与合同生成逻辑 实现合同内容的变量替换和最终合同生成: // 处理AJAX请求:创建合同public function ajax_create_contract() { check_ajax_referer('scm_create_contract_nonce', 'nonce'); if (!current_user_can('edit_posts')) { wp_die('权限不足'); } global $wpdb; $template_id = intval($_POST['template_id']); $variables = json_decode(stripslashes($_POST['variables']), true); $parties = json_decode(stripslashes($_POST['parties']), true); // 获取模板内容 $templates_table = $wpdb->prefix . 'scm_templates'; $template = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $templates_table WHERE id = %d", $template_id )); if (!$template) { wp_send_json_error('模板不存在'); } // 生成合同编号 $contract_number = 'CONTRACT-' . date('Ymd') . '-' . strtoupper(wp_generate_password(6, false)); // 替换变量 $contract_content = $template->content; foreach ($variables as $key => $value) { $placeholder = '{' . $key . '}'; $contract_content = str_replace($placeholder, $value, $contract_content); } // 添加标准条款 $standard_terms = $this->get_standard_terms(); $contract_content .= $standard_terms; // 保存合同到数据库 $contracts_table = $wpdb->prefix . 'scm_contracts'; $wpdb->insert( $contracts_table, array( 'template_id' => $template_id, 'contract_number' => $contract_number, 'title' => $template->title, 'content' => $contract_content, 'parties' => json_encode($parties, JSON_UNESCAPED_UNICODE), 'variables_values' => json_encode($variables, JSON_UNESCAPED_UNICODE), 'status' => 'pending', 'created_by' => get_current_user_id(), 'expires_at' => date('Y-m-d H:i:s', strtotime('+30 days')) ), array('%d', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s') ); $contract_id = $wpdb->insert_id; // 创建签署记录 $signatures_table = $wpdb->prefix . 'scm_signatures'; foreach ($parties as $party) { $wpdb->insert( $signatures_table, array( 'contract_id' => $contract_id, 'party_email' => $party['email'], 'party_name' => $party['name'], 'verification_hash' => wp_hash($contract_id . $party['email'] . time()) ), array('%d', '%s', '%s', '%s') ); } // 发送签署邀请邮件 $this->send_signature_invitations($contract_id); // 返回成功响应 wp_send_json_success(array( 'contract_number' => $contract_number, 'contract_id' => $contract_id, 'management_url' => home_url('/contract-management/') )); } // 获取标准条款private function get_standard_terms() { return ' <div class="standard-terms"> <h3>标准条款与条件</h3> <ol> <li><strong>电子签名有效性</strong>:本文件使用电子签名,根据《中华人民共和国电子签名法》规定,可靠的电子签名与手写签名或者盖章具有同等的法律效力。</li> <li><strong>合同生效</strong>:本合同自所有签署方完成电子签名之日起生效。</li> <li><strong>合同修改</strong>:任何对本合同的修改必须以书面形式(包括电子形式)进行,并经所有签署方同意。</li> <li><strong>争议解决</strong>:因本合同引起的或与本合同有关的任何争议,双方应友好协商解决;协商不成的,任何一方均有权向合同签订地有管辖权的人民法院提起诉讼。</li> <li><strong>完整性</strong>:本合同构成双方就本合同标的达成的完整协议,取代所有先前口头或书面的沟通、陈述或协议。</li> </ol> <div class="signature-disclaimer"> <p><
发表评论WordPress插件开发教程:实现网站自动化营销漏斗与客户旅程追踪 引言:WordPress插件开发的无限可能 在当今数字化营销时代,网站不仅仅是企业的线上名片,更是潜在客户转化的重要渠道。WordPress作为全球最受欢迎的内容管理系统,其强大的可扩展性为开发者提供了无限可能。通过自定义插件开发,我们可以将WordPress从一个简单的博客平台转变为功能强大的营销自动化工具。 本教程将深入探讨如何通过WordPress插件开发,实现网站自动化营销漏斗与客户旅程追踪功能。我们将从基础概念讲起,逐步构建一个完整的营销自动化插件,帮助您更好地理解客户行为,优化转化路径,并最终提升业务成果。 第一部分:理解营销漏斗与客户旅程 1.1 营销漏斗的基本概念 营销漏斗是描述潜在客户从认知到购买决策过程的模型。传统营销漏斗通常包括以下阶段: 认知阶段:用户首次接触品牌 兴趣阶段:用户对产品或服务产生兴趣 考虑阶段:用户评估不同选项 意向阶段:用户表现出购买意愿 购买阶段:用户完成交易 忠诚阶段:用户成为忠实客户并推荐他人 1.2 客户旅程追踪的重要性 客户旅程追踪是指记录和分析用户与品牌互动的全过程。通过追踪客户旅程,企业可以: 识别转化路径中的瓶颈 个性化用户体验 优化营销策略 提高客户留存率 增加客户生命周期价值 1.3 WordPress在营销自动化中的优势 WordPress作为开发平台具有以下优势: 庞大的用户基础和成熟的生态系统 灵活的插件架构和丰富的API 开源特性,允许深度定制 与各种第三方服务的良好兼容性 强大的社区支持和丰富的学习资源 第二部分:WordPress插件开发基础 2.1 插件开发环境搭建 在开始开发之前,我们需要准备以下环境: 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel 代码编辑器:VS Code、PHPStorm或Sublime Text WordPress安装:最新版本的WordPress 调试工具:安装Query Monitor、Debug Bar等调试插件 2.2 创建第一个WordPress插件 每个WordPress插件至少需要一个主文件,通常以插件名称命名。以下是创建基础插件结构的步骤: 在wp-content/plugins/目录下创建新文件夹,命名为marketing-automation-funnel 在该文件夹中创建主文件marketing-automation-funnel.php 添加插件头部信息: <?php /** * Plugin Name: 营销自动化漏斗与客户旅程追踪 * Plugin URI: https://yourwebsite.com/marketing-automation-funnel * Description: 实现网站自动化营销漏斗与客户旅程追踪功能 * Version: 1.0.0 * Author: 您的姓名 * Author URI: https://yourwebsite.com * License: GPL v2 or later * Text Domain: marketing-automation-funnel */ 2.3 WordPress插件架构基础 了解WordPress插件的基本架构是开发的关键: 钩子系统:动作钩子(Actions)和过滤器钩子(Filters) 短代码系统:允许在文章和页面中嵌入动态内容 自定义数据库表:存储插件特定数据 管理界面:为插件创建设置页面 AJAX处理:实现前端与后端的异步通信 第三部分:构建客户旅程追踪系统 3.1 设计数据存储结构 客户旅程数据需要专门的数据库表来存储。我们将创建两个主要表: 客户表:存储客户基本信息 互动事件表:记录客户的每一次互动 以下是创建数据库表的代码示例: // 在插件激活时创建数据库表 register_activation_hook(__FILE__, 'maf_create_tables'); function maf_create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 客户表 $table_name_customers = $wpdb->prefix . 'maf_customers'; $sql_customers = "CREATE TABLE IF NOT EXISTS $table_name_customers ( id INT(11) NOT NULL AUTO_INCREMENT, email VARCHAR(100) NOT NULL, first_name VARCHAR(50), last_name VARCHAR(50), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_seen DATETIME, funnel_stage VARCHAR(50) DEFAULT 'awareness', PRIMARY KEY (id), UNIQUE KEY email (email) ) $charset_collate;"; // 互动事件表 $table_name_events = $wpdb->prefix . 'maf_events'; $sql_events = "CREATE TABLE IF NOT EXISTS $table_name_events ( id INT(11) NOT NULL AUTO_INCREMENT, customer_id INT(11) NOT NULL, event_type VARCHAR(50) NOT NULL, event_data TEXT, page_url VARCHAR(500), referrer VARCHAR(500), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY customer_id (customer_id), KEY event_type (event_type) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql_customers); dbDelta($sql_events); } 3.2 实现客户识别与追踪 客户识别是旅程追踪的基础。我们将通过以下方式识别客户: Cookie追踪:为匿名用户设置唯一标识符 用户登录识别:对已登录用户直接识别 电子邮件识别:通过表单提交获取电子邮件 // 设置客户Cookie function maf_set_customer_cookie() { if (!isset($_COOKIE['maf_customer_id'])) { $customer_id = uniqid('maf_', true); setcookie('maf_customer_id', $customer_id, time() + (365 * 24 * 60 * 60), '/'); } } add_action('init', 'maf_set_customer_cookie'); // 识别当前客户 function maf_identify_customer() { global $wpdb; $customer_id = null; // 检查是否已登录 if (is_user_logged_in()) { $user_id = get_current_user_id(); $user_info = get_userdata($user_id); // 检查用户是否已存在于客户表中 $table_name = $wpdb->prefix . 'maf_customers'; $existing_customer = $wpdb->get_row( $wpdb->prepare( "SELECT id FROM $table_name WHERE email = %s", $user_info->user_email ) ); if ($existing_customer) { $customer_id = $existing_customer->id; } else { // 创建新客户记录 $wpdb->insert( $table_name, array( 'email' => $user_info->user_email, 'first_name' => $user_info->first_name, 'last_name' => $user_info->last_name, 'created_at' => current_time('mysql') ) ); $customer_id = $wpdb->insert_id; } } // 检查Cookie elseif (isset($_COOKIE['maf_customer_id'])) { $cookie_id = sanitize_text_field($_COOKIE['maf_customer_id']); // 查找基于Cookie的客户记录 $table_name = $wpdb->prefix . 'maf_customers'; $existing_customer = $wpdb->get_row( $wpdb->prepare( "SELECT id FROM $table_name WHERE email = %s", $cookie_id . '@anonymous.com' ) ); if ($existing_customer) { $customer_id = $existing_customer->id; } } return $customer_id; } 3.3 记录客户互动事件 客户与网站的每一次互动都应该被记录: // 记录客户事件 function maf_track_event($event_type, $event_data = array()) { global $wpdb; $customer_id = maf_identify_customer(); if (!$customer_id) { // 为匿名用户创建临时记录 $table_name = $wpdb->prefix . 'maf_customers'; $cookie_id = isset($_COOKIE['maf_customer_id']) ? sanitize_text_field($_COOKIE['maf_customer_id']) : uniqid('maf_', true); $wpdb->insert( $table_name, array( 'email' => $cookie_id . '@anonymous.com', 'created_at' => current_time('mysql'), 'last_seen' => current_time('mysql') ) ); $customer_id = $wpdb->insert_id; } // 更新客户最后访问时间 $wpdb->update( $wpdb->prefix . 'maf_customers', array('last_seen' => current_time('mysql')), array('id' => $customer_id) ); // 记录事件 $table_name = $wpdb->prefix . 'maf_events'; $wpdb->insert( $table_name, array( 'customer_id' => $customer_id, 'event_type' => sanitize_text_field($event_type), 'event_data' => maybe_serialize($event_data), 'page_url' => isset($_SERVER['REQUEST_URI']) ? esc_url_raw($_SERVER['REQUEST_URI']) : '', 'referrer' => isset($_SERVER['HTTP_REFERER']) ? esc_url_raw($_SERVER['HTTP_REFERER']) : '', 'created_at' => current_time('mysql') ) ); return $wpdb->insert_id; } // 自动追踪页面访问 function maf_track_page_view() { if (is_admin()) { return; } $post_id = get_the_ID(); $post_type = get_post_type(); $event_data = array( 'post_id' => $post_id, 'post_title' => get_the_title(), 'post_type' => $post_type ); maf_track_event('page_view', $event_data); } add_action('wp', 'maf_track_page_view'); 第四部分:构建自动化营销漏斗 4.1 定义漏斗阶段与规则 营销漏斗需要明确的阶段定义和转换规则: // 定义漏斗阶段 function maf_get_funnel_stages() { return array( 'awareness' => array( 'name' => '认知阶段', 'description' => '用户首次接触品牌', 'next_stages' => array('interest'), 'triggers' => array('page_view', 'social_click') ), 'interest' => array( 'name' => '兴趣阶段', 'description' => '用户对产品或服务产生兴趣', 'next_stages' => array('consideration'), 'triggers' => array('form_submission', 'content_download') ), 'consideration' => array( 'name' => '考虑阶段', 'description' => '用户评估不同选项', 'next_stages' => array('intent'), 'triggers' => array('product_view', 'pricing_view') ), 'intent' => array( 'name' => '意向阶段', 'description' => '用户表现出购买意愿', 'next_stages' => array('purchase'), 'triggers' => array('cart_add', 'demo_request') ), 'purchase' => array( 'name' => '购买阶段', 'description' => '用户完成交易', 'next_stages' => array('loyalty'), 'triggers' => array('order_complete') ), 'loyalty' => array( 'name' => '忠诚阶段', 'description' => '用户成为忠实客户并推荐他人', 'next_stages' => array(), 'triggers' => array('repeat_purchase', 'referral') ) ); } // 检查并更新客户漏斗阶段 function maf_update_customer_funnel_stage($customer_id, $event_type) { global $wpdb; $funnel_stages = maf_get_funnel_stages(); $table_name = $wpdb->prefix . 'maf_customers'; // 获取客户当前阶段 $current_stage = $wpdb->get_var( $wpdb->prepare( "SELECT funnel_stage FROM $table_name WHERE id = %d", $customer_id ) ); // 检查事件是否触发阶段转换 foreach ($funnel_stages as $stage_id => $stage) { if ($stage_id === $current_stage && in_array($event_type, $stage['triggers'])) { // 移动到下一个阶段 if (!empty($stage['next_stages'])) { $next_stage = $stage['next_stages'][0]; // 简单实现:取第一个下一个阶段 $wpdb->update( $table_name, array('funnel_stage' => $next_stage), array('id' => $customer_id) ); // 记录阶段转换事件 maf_track_event('funnel_stage_change', array( 'from_stage' => $current_stage, 'to_stage' => $next_stage, 'trigger_event' => $event_type )); // 触发阶段转换动作 do_action('maf_funnel_stage_changed', $customer_id, $current_stage, $next_stage); } break; } } } 4.2 实现自动化营销动作 根据客户所在的漏斗阶段,触发相应的营销动作: // 根据漏斗阶段执行自动化动作 function maf_execute_funnel_actions($customer_id, $from_stage, $to_stage) { $actions = array(); switch ($to_stage) { case 'interest': $actions[] = array( 'type' => 'email', 'action' => 'send_welcome_series', 'delay' => 0 ); $actions[] = array( 'type' => 'internal', 'action' => 'tag_customer', 'tag' => 'interested' ); break; case 'consideration': $actions[] = array( 'type' => 'email', 'action' => 'send_case_studies', 'delay' => 1 // 1天后发送 ); $actions[] = array( 'type' => 'internal', 'action' => 'tag_customer', 'tag' => 'considering' ); break; case 'intent': $actions[] = array( 'type' => 'email', 'action' => 'send_demo_offer', 'delay' => 0 ); $actions[] = array( 'type' => 'task', 'action' => 'notify_sales_team', 'delay' => 0 ); break; case 'purchase': $actions[] = array( 'type' => 'email', 'action' => 'send_thank_you', 'delay' => 0 ); $actions[] = array( 'type' => 'email', 'action' => 'send_upsell_offer', 'delay' => 7 // 7天后发送 ); break; } // 执行动作 foreach ($actions as $action) { if ($action['delay'] > 0) { // 计划延迟执行 wp_schedule_single_event( time() + ($action['delay'] * DAY_IN_SECONDS), 'maf_scheduled_action', array($customer_id, $action) ); } else { // 立即执行 maf_execute_single_action($customer_id, $action); } } } add_action('maf_funnel_stage_changed', 'maf_execute_funnel_actions', 10, 3); // 执行单个动作 function maf_execute_single_action($customer_id, $action) { global $wpdb; $table_name = $wpdb->prefix . 'maf_customers'; $customer = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d", $customer_id ) ); if (!$customer) { return; } switch ($action['type']) { case 'email': maf_send_automated_email($customer, $action['action']); break; case 'internal': if ($action['action'] === 'tag_customer') { // 为客户添加标签 $tags = get_user_meta($customer_id, 'maf_tags', true); if (!is_array($tags)) { $tags = array(); } if (!in_array($action['tag'], $tags)) { $tags[] = $action['tag']; update_user_meta($customer_id, 'maf_tags', $tags); } } break; case 'task': if ($action['action'] === 'notify_sales_team') { // 通知销售团队 $admin_email = get_option('admin_email'); $subject = '新销售线索:客户进入意向阶段'; $message = "客户 {$customer->email} 已进入意向阶段,请及时跟进。"; wp_mail($admin_email, $subject, $message); } break; } // 记录动作执行 maf_track_event('automation_action', array( 'action_type' => $action['type'], 'action_name' => $action['action'], 'customer_id' => $customer_id )); } 第五部分:创建管理界面与 第五部分:创建管理界面与数据分析面板 5.1 构建插件管理菜单 一个直观的管理界面对于营销自动化插件至关重要。我们将创建多级管理菜单,让用户可以轻松访问所有功能: // 添加管理菜单 function maf_add_admin_menu() { // 主菜单 add_menu_page( '营销自动化漏斗', '营销自动化', 'manage_options', 'marketing-automation-funnel', 'maf_dashboard_page', 'dashicons-chart-line', 30 ); // 子菜单项 add_submenu_page( 'marketing-automation-funnel', '仪表板', '仪表板', 'manage_options', 'marketing-automation-funnel', 'maf_dashboard_page' ); add_submenu_page( 'marketing-automation-funnel', '客户旅程', '客户旅程', 'manage_options', 'maf-customer-journeys', 'maf_customer_journeys_page' ); add_submenu_page( 'marketing-automation-funnel', '营销漏斗', '营销漏斗', 'manage_options', 'maf-marketing-funnel', 'maf_marketing_funnel_page' ); add_submenu_page( 'marketing-automation-funnel', '自动化规则', '自动化规则', 'manage_options', 'maf-automation-rules', 'maf_automation_rules_page' ); add_submenu_page( 'marketing-automation-funnel', '设置', '设置', 'manage_options', 'maf-settings', 'maf_settings_page' ); } add_action('admin_menu', 'maf_add_admin_menu'); // 仪表板页面 function maf_dashboard_page() { if (!current_user_can('manage_options')) { wp_die('您没有权限访问此页面'); } ?> <div class="wrap maf-dashboard"> <h1 class="wp-heading-inline">营销自动化仪表板</h1> <div class="maf-stats-container"> <div class="maf-stat-card"> <h3>总客户数</h3> <div class="maf-stat-number"><?php echo maf_get_total_customers(); ?></div> <div class="maf-stat-trend">+12% 较上月</div> </div> <div class="maf-stat-card"> <h3>今日互动</h3> <div class="maf-stat-number"><?php echo maf_get_today_interactions(); ?></div> <div class="maf-stat-trend">+5% 较昨日</div> </div> <div class="maf-stat-card"> <h3>转化率</h3> <div class="maf-stat-number"><?php echo maf_get_conversion_rate(); ?>%</div> <div class="maf-stat-trend">+2.3% 较上周</div> </div> <div class="maf-stat-card"> <h3>平均停留阶段</h3> <div class="maf-stat-number"><?php echo maf_get_avg_stage_duration(); ?>天</div> <div class="maf-stat-trend">-1.2天 较上月</div> </div> </div> <div class="maf-charts-container"> <div class="maf-chart-card"> <h3>漏斗阶段分布</h3> <div class="maf-chart" id="funnel-stage-chart"></div> </div> <div class="maf-chart-card"> <h3>客户增长趋势</h3> <div class="maf-chart" id="customer-growth-chart"></div> </div> </div> <div class="maf-recent-activity"> <h3>最近活动</h3> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>时间</th> <th>客户</th> <th>事件</th> <th>页面</th> <th>阶段变化</th> </tr> </thead> <tbody> <?php echo maf_get_recent_activities_html(); ?> </tbody> </table> </div> </div> <script> // 使用Chart.js渲染图表 jQuery(document).ready(function($) { // 漏斗阶段分布图 var funnelCtx = document.getElementById('funnel-stage-chart').getContext('2d'); var funnelChart = new Chart(funnelCtx, { type: 'doughnut', data: { labels: <?php echo json_encode(maf_get_funnel_stage_labels()); ?>, datasets: [{ data: <?php echo json_encode(maf_get_funnel_stage_counts()); ?>, backgroundColor: [ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40' ] }] }, options: { responsive: true, maintainAspectRatio: false } }); // 客户增长趋势图 var growthCtx = document.getElementById('customer-growth-chart').getContext('2d'); var growthChart = new Chart(growthCtx, { type: 'line', data: { labels: <?php echo json_encode(maf_get_last_30_days_labels()); ?>, datasets: [{ label: '新客户', data: <?php echo json_encode(maf_get_new_customers_last_30_days()); ?>, borderColor: '#36A2EB', fill: false }, { label: '总客户', data: <?php echo json_encode(maf_get_total_customers_last_30_days()); ?>, borderColor: '#FF6384', fill: false }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } } } }); }); </script> <?php } 5.2 客户旅程可视化界面 创建详细的客户旅程查看界面,让营销人员能够深入了解每个客户的行为路径: // 客户旅程页面 function maf_customer_journeys_page() { if (!current_user_can('manage_options')) { wp_die('您没有权限访问此页面'); } global $wpdb; // 分页参数 $per_page = 20; $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1; $offset = ($current_page - 1) * $per_page; // 搜索功能 $search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : ''; $where_clause = ''; if (!empty($search)) { $where_clause = $wpdb->prepare( " WHERE email LIKE %s OR first_name LIKE %s OR last_name LIKE %s", '%' . $wpdb->esc_like($search) . '%', '%' . $wpdb->esc_like($search) . '%', '%' . $wpdb->esc_like($search) . '%' ); } // 获取客户总数 $table_name = $wpdb->prefix . 'maf_customers'; $total_customers = $wpdb->get_var("SELECT COUNT(*) FROM $table_name $where_clause"); $total_pages = ceil($total_customers / $per_page); // 获取客户列表 $customers = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_name $where_clause ORDER BY last_seen DESC LIMIT %d OFFSET %d", $per_page, $offset ) ); ?> <div class="wrap"> <h1 class="wp-heading-inline">客户旅程</h1> <form method="get" class="search-form"> <input type="hidden" name="page" value="maf-customer-journeys"> <p class="search-box"> <label class="screen-reader-text" for="customer-search">搜索客户:</label> <input type="search" id="customer-search" name="s" value="<?php echo esc_attr($search); ?>"> <input type="submit" id="search-submit" class="button" value="搜索客户"> </p> </form> <div class="tablenav top"> <div class="tablenav-pages"> <span class="displaying-num"><?php echo $total_customers; ?> 个客户</span> <?php if ($total_pages > 1): ?> <span class="pagination-links"> <?php echo paginate_links(array( 'base' => add_query_arg('paged', '%#%'), 'format' => '', 'prev_text' => '«', 'next_text' => '»', 'total' => $total_pages, 'current' => $current_page )); ?> </span> <?php endif; ?> </div> </div> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>ID</th> <th>邮箱</th> <th>姓名</th> <th>当前阶段</th> <th>首次访问</th> <th>最后活动</th> <th>总互动数</th> <th>操作</th> </tr> </thead> <tbody> <?php foreach ($customers as $customer): ?> <tr> <td><?php echo $customer->id; ?></td> <td> <strong><?php echo esc_html($customer->email); ?></strong> <?php if (strpos($customer->email, '@anonymous.com') !== false): ?> <span class="dashicons dashicons-admin-users" title="匿名用户"></span> <?php endif; ?> </td> <td><?php echo esc_html($customer->first_name . ' ' . $customer->last_name); ?></td> <td> <?php $stage_info = maf_get_funnel_stage_info($customer->funnel_stage); echo '<span class="maf-stage-badge maf-stage-' . $customer->funnel_stage . '">' . esc_html($stage_info['name']) . '</span>'; ?> </td> <td><?php echo date('Y-m-d H:i', strtotime($customer->created_at)); ?></td> <td><?php echo $customer->last_seen ? date('Y-m-d H:i', strtotime($customer->last_seen)) : '从未'; ?></td> <td><?php echo maf_get_customer_interaction_count($customer->id); ?></td> <td> <a href="<?php echo admin_url('admin.php?page=maf-customer-journeys&view=customer&id=' . $customer->id); ?>" class="button button-small">查看旅程</a> <button class="button button-small maf-send-message" data-customer-id="<?php echo $customer->id; ?>" data-customer-email="<?php echo esc_attr($customer->email); ?>"> 发送消息 </button> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> <!-- 发送消息模态框 --> <div id="maf-message-modal" class="maf-modal" style="display:none;"> <div class="maf-modal-content"> <div class="maf-modal-header"> <h3>发送消息给客户</h3> <span class="maf-modal-close">×</span> </div> <div class="maf-modal-body"> <form id="maf-message-form"> <input type="hidden" id="maf-message-customer-id" name="customer_id"> <div class="maf-form-group"> <label for="maf-message-subject">主题:</label> <input type="text" id="maf-message-subject" name="subject" class="regular-text" required> </div> <div class="maf-form-group"> <label for="maf-message-content">内容:</label> <textarea id="maf-message-content" name="content" rows="6" class="large-text" required></textarea> </div> <div class="maf-form-group"> <label for="maf-message-type">消息类型:</label> <select id="maf-message-type" name="message_type"> <option value="email">电子邮件</option> <option value="notification">站内通知</option> <option value="sms">短信</option> </select> </div> <div class="maf-form-actions"> <button type="submit" class="button button-primary">发送</button> <button type="button" class="button maf-modal-cancel">取消</button> </div> </form> </div> </div> </div> <script> jQuery(document).ready(function($) { // 发送消息模态框 $('.maf-send-message').click(function() { var customerId = $(this).data('customer-id'); var customerEmail = $(this).data('customer-email'); $('#maf-message-customer-id').val(customerId); $('#maf-message-subject').val('来自 ' + '<?php echo get_bloginfo("name"); ?> 的消息'); $('#maf-message-modal').show(); }); $('.maf-modal-close, .maf-modal-cancel').click(function() { $('#maf-message-modal').hide(); }); // 发送消息表单提交 $('#maf-message-form').submit(function(e) { e.preventDefault(); var formData = $(this).serialize(); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'maf_send_customer_message', nonce: '<?php echo wp_create_nonce("maf_send_message"); ?>', form_data: formData }, beforeSend: function() { $('.maf-form-actions button').prop('disabled', true).text('发送中...'); }, success: function(response) { if (response.success) { alert('消息发送成功!'); $('#maf-message-modal').hide(); $('#maf-message-form')[0].reset(); } else { alert('发送失败: ' + response.data); } }, complete: function() { $('.maf-form-actions button').prop('disabled', false).text('发送'); } }); }); }); </script> <?php } 5.3 营销漏斗分析页面 创建可视化漏斗分析页面,帮助用户理解转化路径和瓶颈: // 营销漏斗分析页面 function maf_marketing_funnel_page() { if (!current_user_can('manage_options')) { wp_die('您没有权限访问此页面'); } global $wpdb; // 获取时间段参数 $time_range = isset($_GET['time_range']) ? sanitize_text_field($_GET['time_range']) : '30days'; $start_date = maf_calculate_start_date($time_range); // 获取漏斗数据 $funnel_data = maf_get_funnel_analysis_data($start_date); ?> <div class="wrap"> <h1 class="wp-heading-inline">营销漏斗分析</h1> <div class="maf-funnel-controls"> <form method="get" class="maf-time-range-form"> <input type="hidden" name="page" value="maf-marketing-funnel"> <label for="time-range">时间范围:</label> <select name="time_range" id="time-range" onchange="this.form.submit()"> <option value="7days" <?php selected($time_range, '7days'); ?>>最近7天</option> <option value="30days" <?php selected($time_range, '30days'); ?>>最近30天</option> <option value="90days" <?php selected($time_range, '90days'); ?>>最近90天</option> <option value="custom" <?php selected($time_range, 'custom'); ?>>自定义</option> </select> <?php if ($time_range === 'custom'): ?> <label for="start-date">开始日期:</label> <input type="date" name="start_date" id="start-date" value="<?php echo isset($_GET['start_date']) ? esc_attr($_GET['start_date']) : ''; ?>"> <label for="end-date">结束日期:</label> <input type="date" name="end_date" id="end-date" value="<?php echo isset($_GET['end_date']) ? esc_attr($_GET['end_date']) : ''; ?>"> <input type="submit" class="button" value="应用"> <?php endif; ?> </form> </div> <div class="maf-funnel-visualization"> <h3>漏斗可视化</h3> <div class="maf-funnel-chart-container"> <div class="maf-funnel-chart"> <?php foreach ($funnel_data['stages'] as $stage): ?>
发表评论