跳至内容

分类: 网站建设

一步步教你,为WordPress集成多源比价与优惠券聚合展示功能

一步步教你,为WordPress集成多源比价与优惠券聚合展示功能 引言:为什么WordPress需要比价与优惠券功能? 在当今电子商务蓬勃发展的时代,价格比较和优惠券获取已成为消费者购物决策的重要环节。对于内容创作者、博主和电商网站运营者而言,在WordPress网站上集成多源比价与优惠券聚合展示功能,不仅能显著提升用户体验,还能通过联盟营销等方式创造额外收入。 传统的WordPress网站通常只展示单一商品信息或有限的优惠内容,而通过代码二次开发实现多源比价与优惠券聚合功能,可以让您的网站成为用户购物的第一站。本文将详细指导您如何通过WordPress程序代码二次开发,实现这一实用功能,无需依赖昂贵的第三方插件,完全自主控制数据展示和更新逻辑。 第一部分:准备工作与环境搭建 1.1 开发环境要求 在开始开发之前,请确保您的环境满足以下要求: WordPress 5.0及以上版本 PHP 7.4及以上版本(推荐PHP 8.0+) MySQL 5.6及以上或MariaDB 10.1及以上 至少512MB内存(推荐1GB以上) 支持cURL扩展的PHP环境 一个代码编辑器(如VS Code、Sublime Text或PHPStorm) 1.2 创建子主题保护核心文件 为了避免主题更新导致自定义代码丢失,我们首先创建一个子主题: 在WordPress的wp-content/themes/目录下创建新文件夹,命名为my-custom-theme 在该文件夹中创建style.css文件,添加以下内容: /* Theme Name: My Custom Theme Template: your-parent-theme-folder-name Version: 1.0.0 Description: 子主题用于添加比价和优惠券功能 */ 创建functions.php文件,初始内容可以为空 在WordPress后台启用这个子主题 1.3 安全备份策略 在进行任何代码修改前,请确保: 完整备份网站文件和数据库 使用版本控制系统(如Git)跟踪代码变更 在本地或 staging 环境测试后再部署到生产环境 第二部分:设计数据库结构 2.1 创建自定义数据表 我们需要创建几个自定义数据表来存储比价和优惠券信息。在子主题的functions.php中添加以下代码: // 创建自定义数据表 function create_price_comparison_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 商品信息表 $products_table = $wpdb->prefix . 'price_comparison_products'; $products_sql = "CREATE TABLE IF NOT EXISTS $products_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, product_name varchar(255) NOT NULL, description text, category varchar(100), image_url varchar(500), created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; // 价格信息表 $prices_table = $wpdb->prefix . 'price_comparison_prices'; $prices_sql = "CREATE TABLE IF NOT EXISTS $prices_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, product_id mediumint(9) NOT NULL, merchant_name varchar(100) NOT NULL, price decimal(10,2) NOT NULL, original_price decimal(10,2), currency varchar(10) DEFAULT 'CNY', product_url varchar(500), availability tinyint(1) DEFAULT 1, last_updated datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY product_id (product_id), KEY merchant_name (merchant_name) ) $charset_collate;"; // 优惠券信息表 $coupons_table = $wpdb->prefix . 'price_comparison_coupons'; $coupons_sql = "CREATE TABLE IF NOT EXISTS $coupons_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, merchant_name varchar(100) NOT NULL, coupon_code varchar(100), coupon_description text, discount_value varchar(100), discount_type varchar(50), expiry_date date, terms_conditions text, product_category varchar(100), is_active tinyint(1) DEFAULT 1, click_count mediumint(9) DEFAULT 0, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY merchant_name (merchant_name), KEY expiry_date (expiry_date), KEY is_active (is_active) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($products_sql); dbDelta($prices_sql); dbDelta($coupons_sql); } // 在插件激活时创建表 register_activation_hook(__FILE__, 'create_price_comparison_tables'); 2.2 添加数据库更新钩子 为了确保数据库表在主题激活时创建,添加以下代码: // 主题激活时创建表 add_action('after_switch_theme', 'create_price_comparison_tables'); // 添加数据库版本控制 $price_comparison_db_version = '1.0'; function update_price_comparison_tables() { global $price_comparison_db_version; $installed_version = get_option('price_comparison_db_version'); if ($installed_version != $price_comparison_db_version) { create_price_comparison_tables(); update_option('price_comparison_db_version', $price_comparison_db_version); } } add_action('init', 'update_price_comparison_tables'); 第三部分:构建数据采集系统 3.1 设计API接口类 我们需要创建一个类来处理与各大电商平台API的交互。在子主题目录下创建includes/class-price-api-manager.php: <?php if (!defined('ABSPATH')) { exit; } class Price_API_Manager { private $api_keys = array(); public function __construct() { // 从WordPress选项获取API密钥 $this->api_keys = get_option('price_comparison_api_keys', array()); } /** * 从京东获取商品信息 */ public function fetch_jd_product($keyword, $category = '') { $api_url = 'https://api.jd.com/routerjson'; $params = array( 'method' => 'jingdong.ware.search', 'app_key' => isset($this->api_keys['jd_app_key']) ? $this->api_keys['jd_app_key'] : '', 'timestamp' => date('Y-m-d H:i:s'), 'format' => 'json', 'v' => '2.0', 'sign_method' => 'md5', 'param_json' => json_encode(array( 'keyword' => $keyword, 'catId' => $category, 'page' => 1, 'pageSize' => 20 )) ); // 生成签名(简化版,实际需要按照京东API文档实现) $params['sign'] = $this->generate_jd_signature($params); $response = wp_remote_get($api_url . '?' . http_build_query($params)); if (is_wp_error($response)) { return false; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); return $this->parse_jd_response($data); } /** * 从淘宝/天猫获取商品信息 */ public function fetch_taobao_product($keyword, $category = '') { // 使用淘宝开放平台API // 注意:需要申请正式API权限 $api_url = 'https://eco.taobao.com/router/rest'; $params = array( 'method' => 'taobao.tbk.item.get', 'app_key' => isset($this->api_keys['taobao_app_key']) ? $this->api_keys['taobao_app_key'] : '', 'timestamp' => date('Y-m-d H:i:s'), 'format' => 'json', 'v' => '2.0', 'sign_method' => 'md5', 'q' => $keyword, 'cat' => $category, 'page_no' => 1, 'page_size' => 20 ); $params['sign'] = $this->generate_taobao_signature($params); $response = wp_remote_get($api_url . '?' . http_build_query($params)); if (is_wp_error($response)) { return false; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); return $this->parse_taobao_response($data); } /** * 从拼多多获取商品信息 */ public function fetch_pdd_product($keyword) { // 拼多多联盟API $api_url = 'https://gw-api.pinduoduo.com/api/router'; $params = array( 'type' => 'pdd.ddk.goods.search', 'client_id' => isset($this->api_keys['pdd_client_id']) ? $this->api_keys['pdd_client_id'] : '', 'timestamp' => time(), 'data_type' => 'JSON', 'keyword' => $keyword, 'page' => 1, 'page_size' => 20 ); $params['sign'] = $this->generate_pdd_signature($params); $response = wp_remote_post($api_url, array( 'body' => json_encode($params), 'headers' => array('Content-Type' => 'application/json') )); if (is_wp_error($response)) { return false; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); return $this->parse_pdd_response($data); } /** * 获取优惠券信息 */ public function fetch_coupons($merchant = '', $category = '') { $coupons = array(); // 这里可以集成多个优惠券API // 例如:淘宝客优惠券、京东优惠券等 // 示例:从本地数据库获取优惠券 global $wpdb; $table_name = $wpdb->prefix . 'price_comparison_coupons'; $query = "SELECT * FROM $table_name WHERE is_active = 1"; if (!empty($merchant)) { $query .= $wpdb->prepare(" AND merchant_name = %s", $merchant); } if (!empty($category)) { $query .= $wpdb->prepare(" AND product_category = %s", $category); } $query .= " AND (expiry_date IS NULL OR expiry_date >= CURDATE()) ORDER BY created_at DESC LIMIT 50"; $coupons = $wpdb->get_results($query, ARRAY_A); return $coupons; } // 以下为辅助方法,实际开发中需要完整实现 private function generate_jd_signature($params) { // 实现京东签名算法 return md5(implode('', $params) . (isset($this->api_keys['jd_app_secret']) ? $this->api_keys['jd_app_secret'] : '')); } private function generate_taobao_signature($params) { // 实现淘宝签名算法 return md5(implode('', $params) . (isset($this->api_keys['taobao_app_secret']) ? $this->api_keys['taobao_app_secret'] : '')); } private function generate_pdd_signature($params) { // 实现拼多多签名算法 return md5(implode('', $params) . (isset($this->api_keys['pdd_client_secret']) ? $this->api_keys['pdd_client_secret'] : '')); } private function parse_jd_response($data) { // 解析京东API响应 if (!isset($data['code']) || $data['code'] != '0') { return array(); } $products = array(); // 解析逻辑... return $products; } private function parse_taobao_response($data) { // 解析淘宝API响应 if (!isset($data['error_response'])) { return array(); } $products = array(); // 解析逻辑... return $products; } private function parse_pdd_response($data) { // 解析拼多多API响应 if (!isset($data['error_response'])) { return array(); } $products = array(); // 解析逻辑... return $products; } } ?> 3.2 创建数据采集调度器 为了定期更新价格和优惠券信息,我们需要创建一个调度器。创建includes/class-data-collector.php: <?php if (!defined('ABSPATH')) { exit; } class Price_Data_Collector { private $api_manager; public function __construct() { $this->api_manager = new Price_API_Manager(); // 设置定时任务 add_action('price_comparison_daily_cron', array($this, 'daily_data_collection')); add_action('price_comparison_hourly_cron', array($this, 'hourly_price_check')); // 初始化定时任务 $this->schedule_cron_jobs(); } /** * 安排定时任务 */ private function schedule_cron_jobs() { if (!wp_next_scheduled('price_comparison_daily_cron')) { wp_schedule_event(time(), 'daily', 'price_comparison_daily_cron'); } if (!wp_next_scheduled('price_comparison_hourly_cron')) { wp_schedule_event(time(), 'hourly', 'price_comparison_hourly_cron'); } } /** * 每日数据收集 */ public function daily_data_collection() { global $wpdb; // 收集热门商品的价格信息 $popular_products = $this->get_popular_products(); foreach ($popular_products as $product) { $this->update_product_prices($product['product_name'], $product['category']); } // 更新优惠券信息 $this->update_coupon_data(); // 清理过期数据 $this->cleanup_old_data(); } /** * 每小时价格检查 */ public function hourly_price_check() { // 检查价格变动,特别是促销商品 $promotional_products = $this->get_promotional_products(); foreach ($promotional_products as $product) { $this->check_price_changes($product['id']); } } /** * 更新商品价格 */ private function update_product_prices($product_name, $category = '') { // 从多个平台获取价格 $jd_prices = $this->api_manager->fetch_jd_product($product_name, $category); $taobao_prices = $this->api_manager->fetch_taobao_product($product_name, $category); $pdd_prices = $this->api_manager->fetch_pdd_product($product_name); // 合并价格数据 $all_prices = array_merge( $this->format_prices($jd_prices, '京东'), $this->format_prices($taobao_prices, '淘宝'), $this->format_prices($pdd_prices, '拼多多') ); // 保存到数据库 $this->save_prices_to_db($product_name, $all_prices); } /** * 更新优惠券数据 */ private function update_coupon_data() { // 从各平台API获取最新优惠券 $coupons = $this->api_manager->fetch_coupons(); // 保存到数据库 $this->save_coupons_to_db($coupons); } /** * 获取热门商品 */ private function get_popular_products() { global $wpdb; $products_table = $wpdb->prefix . 'price_comparison_products'; // 获取最近30天有搜索或点击的商品 $query = "SELECT * FROM $products_table WHERE updated_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) ORDER BY (SELECT COUNT(*) FROM {$wpdb->prefix}price_comparison_prices WHERE product_id = $products_table.id) DESC LIMIT 50"; return $wpdb->get_results($query, ARRAY_A); } /** * 获取促销商品 */ private function get_promotional_products() { global $wpdb; $prices_table = $wpdb->prefix . 'price_comparison_prices'; // 获取最近有价格变动的商品 $query = "SELECT DISTINCT product_id as id FROM $prices_table WHERE last_updated >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND original_price > price LIMIT 20"; return $wpdb->get_results($query, ARRAY_A); } /** * 检查价格变动 */ private function check_price_changes($product_id) { global $wpdb; $prices_table = $wpdb->prefix . 'price_comparison_prices'; // 获取商品当前价格 $current_prices = $wpdb->get_results( $wpdb->prepare("SELECT * FROM $prices_table WHERE product_id = %d", $product_id), A ); // 重新从API获取最新价格 $product_info = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$wpdb->prefix}price_comparison_products WHERE id = %d", $product_id), ARRAY_A ); if ($product_info) { $new_prices = $this->api_manager->fetch_jd_product($product_info['product_name'], $product_info['category']); // 比较价格变动 foreach ($current_prices as $current) { foreach ($new_prices as $new) { if ($current['merchant_name'] == $new['merchant']) { if ($current['price'] != $new['price']) { // 价格变动,记录并可能发送通知 $this->record_price_change($product_id, $current, $new); } } } } } } /** * 格式化价格数据 */ private function format_prices($prices, $merchant) { $formatted = array(); if (is_array($prices)) { foreach ($prices as $price) { $formatted[] = array( 'merchant' => $merchant, 'price' => $price['price'] ?? 0, 'original_price' => $price['original_price'] ?? $price['price'], 'product_url' => $price['url'] ?? '', 'availability' => $price['availability'] ?? 1 ); } } return $formatted; } /** * 保存价格到数据库 */ private function save_prices_to_db($product_name, $prices) { global $wpdb; $products_table = $wpdb->prefix . 'price_comparison_products'; $prices_table = $wpdb->prefix . 'price_comparison_prices'; // 查找或创建商品记录 $product = $wpdb->get_row( $wpdb->prepare("SELECT id FROM $products_table WHERE product_name = %s", $product_name), ARRAY_A ); if (!$product) { $wpdb->insert($products_table, array( 'product_name' => $product_name, 'created_at' => current_time('mysql') )); $product_id = $wpdb->insert_id; } else { $product_id = $product['id']; } // 保存价格信息 foreach ($prices as $price) { $existing = $wpdb->get_row( $wpdb->prepare( "SELECT id FROM $prices_table WHERE product_id = %d AND merchant_name = %s", $product_id, $price['merchant'] ), ARRAY_A ); if ($existing) { // 更新现有记录 $wpdb->update($prices_table, array( 'price' => $price['price'], 'original_price' => $price['original_price'], 'product_url' => $price['product_url'], 'availability' => $price['availability'], 'last_updated' => current_time('mysql') ), array('id' => $existing['id'])); } else { // 插入新记录 $wpdb->insert($prices_table, array( 'product_id' => $product_id, 'merchant_name' => $price['merchant'], 'price' => $price['price'], 'original_price' => $price['original_price'], 'product_url' => $price['product_url'], 'availability' => $price['availability'], 'last_updated' => current_time('mysql') )); } } } /** * 保存优惠券到数据库 */ private function save_coupons_to_db($coupons) { global $wpdb; $coupons_table = $wpdb->prefix . 'price_comparison_coupons'; foreach ($coupons as $coupon) { // 检查是否已存在相同优惠券 $existing = $wpdb->get_row( $wpdb->prepare( "SELECT id FROM $coupons_table WHERE merchant_name = %s AND coupon_code = %s", $coupon['merchant_name'], $coupon['coupon_code'] ), ARRAY_A ); if (!$existing) { $wpdb->insert($coupons_table, $coupon); } } } /** * 记录价格变动 */ private function record_price_change($product_id, $old_price, $new_price) { global $wpdb; $changes_table = $wpdb->prefix . 'price_comparison_changes'; // 创建价格变动记录表(如果不存在) if ($wpdb->get_var("SHOW TABLES LIKE '$changes_table'") != $changes_table) { $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE $changes_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, product_id mediumint(9) NOT NULL, merchant_name varchar(100) NOT NULL, old_price decimal(10,2) NOT NULL, new_price decimal(10,2) NOT NULL, change_percent decimal(5,2), change_type varchar(20), recorded_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } // 计算变动百分比 $change_percent = (($new_price['price'] - $old_price['price']) / $old_price['price']) * 100; $change_type = $new_price['price'] < $old_price['price'] ? '降价' : '涨价'; // 插入变动记录 $wpdb->insert($changes_table, array( 'product_id' => $product_id, 'merchant_name' => $old_price['merchant_name'], 'old_price' => $old_price['price'], 'new_price' => $new_price['price'], 'change_percent' => $change_percent, 'change_type' => $change_type, 'recorded_at' => current_time('mysql') )); // 可以在这里添加邮件通知或站内通知逻辑 $this->notify_price_change($product_id, $old_price, $new_price, $change_percent, $change_type); } /** * 通知价格变动 */ private function notify_price_change($product_id, $old_price, $new_price, $change_percent, $change_type) { // 获取关注此商品的用户 $followers = $this->get_product_followers($product_id); if (!empty($followers)) { $product_info = $wpdb->get_row( $wpdb->prepare("SELECT product_name FROM {$wpdb->prefix}price_comparison_products WHERE id = %d", $product_id), ARRAY_A ); foreach ($followers as $follower) { // 发送邮件通知 $subject = "价格变动通知:{$product_info['product_name']} {$change_type}了" . abs($change_percent) . "%"; $message = "商品:{$product_info['product_name']}n"; $message .= "商家:{$old_price['merchant_name']}n"; $message .= "原价:{$old_price['price']}元n"; $message .= "现价:{$new_price['price']}元n"; $message .= "变动幅度:{$change_percent}%n"; $message .= "查看详情:" . home_url("/product/{$product_id}"); wp_mail($follower['email'], $subject, $message); } } } /** * 清理旧数据 */ private function cleanup_old_data() { global $wpdb; $prices_table = $wpdb->prefix . 'price_comparison_prices'; $coupons_table = $wpdb->prefix . 'price_comparison_coupons'; // 删除30天前的价格记录(保留最新记录) $wpdb->query( "DELETE FROM $prices_table WHERE last_updated < DATE_SUB(NOW(), INTERVAL 30 DAY) AND id NOT IN ( SELECT id FROM ( SELECT MAX(id) as id FROM $prices_table GROUP BY product_id, merchant_name ) as latest )" ); // 标记过期优惠券为无效 $wpdb->query( "UPDATE $coupons_table SET is_active = 0 WHERE expiry_date < CURDATE() AND is_active = 1" ); } }?> ## 第四部分:创建管理界面 ### 4.1 添加WordPress管理菜单 在`functions.php`中添加管理菜单: // 添加管理菜单function price_comparison_admin_menu() { add_menu_page( '比价与优惠券管理', '比价管理', 'manage_options', 'price-comparison', 'price_comparison_admin_page', 'dashicons-chart-line', 30 ); add_submenu_page( 'price-comparison', '商品管理', '商品管理', 'manage_options', 'price-comparison-products', 'price_comparison_products_page' ); add_submenu_page( 'price-comparison', '价格监控', '价格监控', 'manage_options', 'price-comparison-monitor', 'price_comparison_monitor_page' ); add_submenu_page( 'price-comparison', '优惠券管理', '优惠券管理', 'manage_options', 'price-comparison-coupons', 'price_comparison_coupons_page' ); add_submenu_page( 'price-comparison', 'API设置', 'API设置', 'manage_options', 'price-comparison-settings', 'price_comparison_settings_page' ); }add_action('admin_menu', 'price_comparison_admin_menu'); // 主管理页面function price_comparison_admin_page() { ?> <div class="wrap"> <h1>比价与优惠券管理系统</h1> <div class="price-comparison-dashboard"> <div class="dashboard-card"> <h3>商品总数</h3> <p class="dashboard-number"><?php echo get_product_count(); ?></p> </div> <div class="dashboard-card"> <h3>有效优惠券</h3> <p class="dashboard-number"><?php echo get_active_coupon_count(); ?></p> </div> <div class="dashboard-card"> <h3>今日价格更新</h3> <p class="dashboard-number"><?php echo get_today_price_updates(); ?></p> </div> <div class="dashboard-card"> <h3>用户搜索量</h3> <p class="dashboard-number"><?php echo get_today_searches(); ?></p> </div> </div> <div class="recent-activity"> <h2>最近活动</h2> <?php display_recent_activity(); ?> </div> </div> <style> .price-comparison-dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; } .dashboard-card { background: #fff; padding: 20px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); text-align: center; } .dashboard-number { font-size: 2em; font-weight: bold; color: #0073aa; margin: 10px 0; } </style> <?php } // 商品管理页面function price_comparison_products_page() { global $wpdb; // 处理表单提交 if (isset($_POST['add_product'])) { $product_name = sanitize_text_field($_POST['product_name']); $category = sanitize_text_field($_POST['category']); $wpdb->insert( $wpdb->prefix . 'price_comparison_products', array( 'product_name' => $product_name, 'category' => $category, 'created_at' => current_time('mysql') ) ); echo '<div class="notice notice-success"><p>商品添加成功!</p></div>'; } // 获取商品列表 $products = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}price_comparison_products ORDER BY created_at DESC LIMIT 50", ARRAY_A ); ?> <div class="wrap"> <h1>商品管理</h1> <div class="add-product-form"> <h2>添加新商品</h2> <form method="post"> <table class="form-table"> <tr> <th><label for="product_name">商品名称</label></th> <td><input type="text" id="product_name" name="product_name" class="regular-text" required></td> </tr> <tr> <th><label for="category">商品分类</label></th> <td> <input type="text" id="category" name="category" class="regular-text"> <p class="description">例如:手机、笔记本电脑、家电等</p> </td> </tr> </table> <?php submit_button('添加商品', 'primary', 'add_product'); ?> </form> </div> <div class="product-list"> <h2>商品列表</h2> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>ID</th> <th>商品名称</th> <th>分类</th> <th>创建时间</th> <th>操作</th> </tr> </thead> <tbody> <?php foreach ($products as $product): ?> <tr> <td><?php echo $product['id']; ?></td> <td><?php echo esc_html($product['product_name']); ?></td> <td><?php echo esc_html($product['category']); ?></td> <td><?php echo $product['created_at']; ?></td> <td> <a href="<?php echo admin_url('admin.php?page=price-comparison-monitor&product_id=' . $product['id']); ?>">查看价格</a> | <a href="#" class="delete-product" data-id="<?php echo $product['id']; ?>">删除</a> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> </div> <script> jQuery(document).ready(function($) { $('.delete-product').click(function(e) { e.preventDefault(); if (confirm('确定要删除这个商品吗?')) { var productId = $(this).data('id'); $.post(ajaxurl, { action: 'delete_product', product_id: productId, nonce: '<?php echo wp_create_nonce('delete_product_nonce'); ?>' }, function(response) { if (response.success) { location.reload(); } else { alert('删除失败:' + response.data); } }); } }); }); </script> <?php } // 价格监控页面function price_comparison_monitor_page() { global $wpdb; $product_id = isset($_GET['product_id']) ? intval($_GET['product_id']) : 0; if ($product_id) { // 显示单个商品的价格信息 $product = $wpdb->get_row( $wpdb->prepare("SELECT * FROM {$wpdb->prefix}price_comparison_products WHERE id = %d", $product_id), ARRAY_A ); $prices = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}price_comparison_prices WHERE product_id = %d ORDER BY price ASC", $product_id ), ARRAY_A ); ?> <div class="wrap"> <h1>价格监控:<?php echo esc_html($product['product_name']); ?></h1> <div class="price-comparison-chart"> <canvas id="priceHistoryChart" width="800" height="400"></canvas> </div> <div class="current-prices"> <h2>当前价格对比</h2> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>商家</th> <th>当前价格</th> <th>原价</th> <th>优惠幅度</th> <th>最后更新</th> <th>操作</th> </tr> </thead> <tbody> <?php foreach ($prices as $price): ?> <tr> <td><?php echo esc_html($price['merchant_name']); ?></td> <td><strong style="color: #d63638;"><?php echo $price['price']; ?>元</strong></td> <td><del><?php echo $price['original_price']; ?>元</del></td> <td> <?php if ($price['original_price'] > $price['price']): ?> <span class="discount-badge"> -<?php echo round((($price['original_price'] - $price['price']) /

发表评论

实战教程,开发网站内嵌的在线Logo设计与名片制作工具

实战教程:开发网站内嵌的在线Logo设计与名片制作工具 摘要 在当今数字化时代,企业网站的功能需求日益多样化。本文将详细介绍如何通过WordPress程序的代码二次开发,实现网站内嵌的在线Logo设计与名片制作工具。我们将从需求分析、技术选型、开发流程到最终部署,全面解析这一实用功能的实现过程,帮助网站管理员和技术开发者扩展网站功能,提升用户体验。 一、项目背景与需求分析 1.1 为什么需要内嵌设计工具 随着中小企业数字化转型加速,越来越多的企业主希望在自己的网站上直接完成品牌视觉元素的创建。传统方式下,用户需要离开网站使用第三方设计工具,这不仅打断了用户体验流程,还可能导致潜在客户的流失。 内嵌设计工具的优势: 提升用户停留时间:用户在网站内完成设计,增加互动时长 增强品牌专业性:展示网站功能的全面性 降低使用门槛:无需安装额外软件或注册新平台 数据自主控制:所有设计数据保留在自有服务器 1.2 功能需求定义 基于实际应用场景,我们需要实现以下核心功能: Logo设计工具需求: 基础形状库(圆形、方形、三角形等几何图形) 图标库(分类图标资源) 文本编辑功能(字体、大小、颜色、间距调整) 图层管理系统 颜色选择器与调色板 导出功能(支持PNG、SVG格式) 名片制作工具需求: 标准名片尺寸模板(90×54mm) 企业信息字段(姓名、职位、联系方式等) 双面设计支持 印刷出血线提示 多种设计风格模板 导出打印质量PDF 通用需求: 响应式设计,适配各种设备 用户作品保存与加载 设计分享功能 水印添加选项(免费版) 二、技术架构与开发环境 2.1 技术选型 前端技术栈: HTML5 Canvas:核心绘图引擎 Fabric.js:强大的Canvas操作库 React/Vue.js:可选前端框架(本教程使用原生JavaScript) Bootstrap 5:响应式UI框架 Coloris:轻量级颜色选择器 FileSaver.js:客户端文件保存 后端技术栈: WordPress核心:内容管理系统 PHP 7.4+:服务器端逻辑 MySQL 5.7+:数据存储 WordPress REST API:前后端通信 GD库/ImageMagick:图像处理 2.2 开发环境搭建 本地WordPress安装 # 使用Docker快速搭建WordPress环境 docker-compose up -d 开发工具准备 代码编辑器:VS Code 浏览器开发者工具 Git版本控制 本地测试服务器 WordPress主题结构 /wp-content/themes/your-theme/ ├── assets/ │ ├── css/ │ ├── js/ │ └── images/ ├── template-parts/ ├── functions.php └── style.css 三、Logo设计工具开发实战 3.1 基础HTML结构 首先创建Logo设计工具的前端界面: <!-- logo-designer.php --> <div class="logo-designer-container"> <div class="designer-header"> <h2>在线Logo设计工具</h2> <div class="toolbar"> <button id="save-logo" class="btn btn-primary">保存设计</button> <button id="export-png" class="btn btn-success">导出PNG</button> <button id="export-svg" class="btn btn-info">导出SVG</button> </div> </div> <div class="designer-body"> <!-- 左侧工具栏 --> <div class="sidebar-left"> <div class="tool-section"> <h4>形状</h4> <div class="shape-list"> <button class="shape-btn" data-type="rectangle">矩形</button> <button class="shape-btn" data-type="circle">圆形</button> <button class="shape-btn" data-type="triangle">三角形</button> </div> </div> <div class="tool-section"> <h4>图标库</h4> <div class="icon-grid" id="icon-library"> <!-- 图标将通过AJAX加载 --> </div> </div> </div> <!-- 中央画布区域 --> <div class="canvas-area"> <canvas id="logo-canvas" width="800" height="600"></canvas> <div class="canvas-controls"> <button id="clear-canvas">清空画布</button> <button id="undo-action">撤销</button> <button id="redo-action">重做</button> </div> </div> <!-- 右侧属性面板 --> <div class="sidebar-right"> <div class="properties-panel"> <h4>属性设置</h4> <div class="property-group"> <label>填充颜色</label> <input type="color" id="fill-color" value="#3498db"> </div> <div class="property-group"> <label>边框颜色</label> <input type="color" id="stroke-color" value="#2c3e50"> </div> <div class="property-group"> <label>字体</label> <select id="font-family"> <option value="Arial">Arial</option> <option value="Helvetica">Helvetica</option> <option value="Times New Roman">Times New Roman</option> </select> </div> </div> <div class="layers-panel"> <h4>图层管理</h4> <ul id="layers-list"> <!-- 图层列表动态生成 --> </ul> </div> </div> </div> </div> 3.2 Canvas初始化与Fabric.js集成 // assets/js/logo-designer.js document.addEventListener('DOMContentLoaded', function() { // 初始化Canvas const canvas = new fabric.Canvas('logo-canvas', { backgroundColor: '#ffffff', preserveObjectStacking: true }); // 设置画布尺寸 canvas.setDimensions({ width: 800, height: 600 }); // 形状添加功能 document.querySelectorAll('.shape-btn').forEach(button => { button.addEventListener('click', function() { const shapeType = this.getAttribute('data-type'); addShapeToCanvas(shapeType); }); }); // 添加形状函数 function addShapeToCanvas(type) { let shape; const fillColor = document.getElementById('fill-color').value; const strokeColor = document.getElementById('stroke-color').value; switch(type) { case 'rectangle': shape = new fabric.Rect({ left: 100, top: 100, fill: fillColor, width: 100, height: 100, stroke: strokeColor, strokeWidth: 2 }); break; case 'circle': shape = new fabric.Circle({ left: 100, top: 100, radius: 50, fill: fillColor, stroke: strokeColor, strokeWidth: 2 }); break; case 'triangle': shape = new fabric.Triangle({ left: 100, top: 100, fill: fillColor, width: 100, height: 100, stroke: strokeColor, strokeWidth: 2 }); break; } if(shape) { canvas.add(shape); updateLayersList(); } } // 文本添加功能 document.getElementById('add-text').addEventListener('click', function() { const text = new fabric.IText('编辑文本', { left: 150, top: 150, fontFamily: document.getElementById('font-family').value, fill: document.getElementById('fill-color').value, fontSize: 40 }); canvas.add(text); canvas.setActiveObject(text); updateLayersList(); }); // 更新图层列表 function updateLayersList() { const layersList = document.getElementById('layers-list'); layersList.innerHTML = ''; canvas.getObjects().forEach((obj, index) => { const li = document.createElement('li'); li.className = 'layer-item'; li.innerHTML = ` <span>${obj.type || '对象'}</span> <div class="layer-actions"> <button class="btn-layer-up" data-index="${index}">上移</button> <button class="btn-layer-down" data-index="${index}">下移</button> <button class="btn-layer-delete" data-index="${index}">删除</button> </div> `; layersList.appendChild(li); }); } // 导出PNG功能 document.getElementById('export-png').addEventListener('click', function() { const dataURL = canvas.toDataURL({ format: 'png', quality: 1 }); const link = document.createElement('a'); link.download = 'logo-design.png'; link.href = dataURL; link.click(); }); // 初始化图标库 loadIconLibrary(); function loadIconLibrary() { // 通过AJAX从服务器获取图标 fetch('/wp-json/logo-designer/v1/icons') .then(response => response.json()) .then(icons => { const iconGrid = document.getElementById('icon-library'); icons.forEach(icon => { const iconElement = document.createElement('div'); iconElement.className = 'icon-item'; iconElement.innerHTML = `<i class="${icon.class}"></i>`; iconElement.addEventListener('click', () => addIconToCanvas(icon)); iconGrid.appendChild(iconElement); }); }); } function addIconToCanvas(icon) { // 添加图标到画布的逻辑 // 这里可以使用fabric.Path或fabric.Group来处理SVG图标 } }); 3.3 后端API开发 <?php // functions.php - WordPress主题函数文件 // 注册REST API端点 add_action('rest_api_init', function() { // 获取图标库 register_rest_route('logo-designer/v1', '/icons', array( 'methods' => 'GET', 'callback' => 'get_icon_library', 'permission_callback' => '__return_true' )); // 保存设计 register_rest_route('logo-designer/v1', '/save', array( 'methods' => 'POST', 'callback' => 'save_logo_design', 'permission_callback' => function() { return current_user_can('edit_posts'); } )); }); // 获取图标库函数 function get_icon_library() { $icons = array( array('id' => 1, 'name' => '星星', 'class' => 'fas fa-star'), array('id' => 2, 'name' => '爱心', 'class' => 'fas fa-heart'), array('id' => 3, 'name' => '云朵', 'class' => 'fas fa-cloud'), array('id' => 4, 'name' => '树叶', 'class' => 'fas fa-leaf'), array('id' => 5, 'name' => '火焰', 'class' => 'fas fa-fire'), array('id' => 6, 'name' => '水滴', 'class' => 'fas fa-tint'), ); return rest_ensure_response($icons); } // 保存设计函数 function save_logo_design($request) { $user_id = get_current_user_id(); $design_data = $request->get_param('design_data'); $design_name = $request->get_param('design_name'); if(empty($design_data) || empty($design_name)) { return new WP_Error('invalid_data', '设计数据或名称为空', array('status' => 400)); } // 保存到用户meta $saved_designs = get_user_meta($user_id, 'logo_designs', true); if(empty($saved_designs)) { $saved_designs = array(); } $new_design = array( 'id' => uniqid(), 'name' => sanitize_text_field($design_name), 'data' => $design_data, 'created_at' => current_time('mysql'), 'updated_at' => current_time('mysql') ); $saved_designs[] = $new_design; update_user_meta($user_id, 'logo_designs', $saved_designs); return rest_ensure_response(array( 'success' => true, 'message' => '设计已保存', 'design_id' => $new_design['id'] )); } // 添加快捷码支持 add_shortcode('logo_designer', 'render_logo_designer'); function render_logo_designer($atts) { ob_start(); include get_template_directory() . '/template-parts/logo-designer.php'; return ob_get_clean(); } ?> 四、名片制作工具开发实战 4.1 名片设计器界面 <!-- business-card-designer.php --> <div class="card-designer-container"> <div class="designer-header"> <h2>在线名片设计工具</h2> <div class="template-selector"> <h4>选择模板</h4> <div class="template-grid"> <div class="template-item" data-template="modern"> <div class="template-preview modern-template"></div> <span>现代风格</span> </div> <div class="template-item" data-template="classic"> <div class="template-preview classic-template"></div> <span>经典风格</span> </div> <div class="template-item" data-template="minimal"> <div class="template-preview minimal-template"></div> <span>极简风格</span> </div> </div> </div> </div> <div class="card-design-area"> <!-- 正面设计 --> <div class="card-side front-side"> <div class="card-canvas-container"> <canvas id="card-front-canvas" width="1050" height="600"></canvas> <div class="bleed-marks"></div> </div> <div class="side-label">正面</div> </div> <!-- 背面设计 --> <div class="card-side back-side"> <div class="card-canvas-container"> <canvas id="card-back-canvas" width="1050" height="600"></canvas> <div class="bleed-marks"></div> </div> <div class="side-label">背面</div> </div> </div> <div class="card-controls"> <div class="info-fields"> <h4>企业信息</h4> <div class="field-group"> <input type="text" id="company-name" placeholder="公司名称"> <input type="text" id="person-name" placeholder="姓名"> <input type="text" id="person-title" placeholder="职位"> </div> <div class="field-group"> <input type="tel" id="phone-number" placeholder="电话"> <input type="email" id="email-address" placeholder="邮箱"> <input type="text" id="website-url" placeholder="网址"> </div> <div class="field-group"> <textarea id="company-address" placeholder="公司地址"></textarea> </div> <button id="apply-info" class="btn btn-primary">应用到名片</button> </div> <div class="export-options"> <h4>导出选项</h4> <button id="export-card-pdf" class="btn btn-success">导出打印PDF</button> <button id="export-card-image" class="btn btn-info">导出图片</button> <div class="quality-options"> <label>打印质量:</label> <select id="print-quality"> <option value="standard">标准(300DPI)</option> <option value="high">高质量(600DPI)</option> </select> </div> </div> </div> </div> 4.2 名片设计逻辑实现 // assets/js/business-card-designer.js class BusinessCardDesigner { constructor() { this.frontCanvas = new fabric.Canvas('card-front-canvas'); this.backCanvas = new fabric.Canvas('card-back-canvas'); this.currentTemplate = 'modern'; this.cardSize = { width: 90, height: 54 }; // 毫米 this.bleedSize = 3; // 出血尺寸 this.init(); } init() { this.setupCanvas(); this.bindEvents(); this.loadTemplate(this.currentTemplate); } setupCanvas() { // 设置画布尺寸(考虑出血) const scale = 10; // 放大10倍以便设计 const bleedPx = this.bleedSize * scale; this.frontCanvas.setDimensions({ width: (this.cardSize.width + this.bleedSize * 2) * scale, height: (this.cardSize.height + this.bleedSize * 2) * scale }); this.backCanvas.setDimensions({ width: (this.cardSize.width + this.bleedSize * 2) * scale, }); // 添加出血线标记 this.addBleedMarks(this.frontCanvas); this.addBleedMarks(this.backCanvas); } addBleedMarks(canvas) { const width = canvas.width; const height = canvas.height; const bleedPx = this.bleedSize * 10; // 创建出血线 const bleedLines = new fabric.Group([ // 外框线(出血边界) new fabric.Rect({ left: 0, top: 0, width: width, height: height, fill: 'transparent', stroke: '#ff0000', strokeWidth: 1, strokeDashArray: [5, 5] }), // 内框线(裁切边界) new fabric.Rect({ left: bleedPx, top: bleedPx, width: width - bleedPx * 2, height: height - bleedPx * 2, fill: 'transparent', stroke: '#0000ff', strokeWidth: 1 }) ], { selectable: false, evented: false }); canvas.add(bleedLines); canvas.sendToBack(bleedLines); } bindEvents() { // 模板选择 document.querySelectorAll('.template-item').forEach(item => { item.addEventListener('click', () => { const template = item.getAttribute('data-template'); this.loadTemplate(template); }); }); // 应用企业信息 document.getElementById('apply-info').addEventListener('click', () => { this.applyBusinessInfo(); }); // 导出PDF document.getElementById('export-card-pdf').addEventListener('click', () => { this.exportToPDF(); }); // 导出图片 document.getElementById('export-card-image').addEventListener('click', () => { this.exportToImage(); }); } loadTemplate(templateName) { this.currentTemplate = templateName; // 清空画布但保留出血线 const frontObjects = this.frontCanvas.getObjects(); const backObjects = this.backCanvas.getObjects(); frontObjects.forEach(obj => { if (!obj.isType('group') || !obj.getObjects().some(o => o.stroke === '#ff0000')) { this.frontCanvas.remove(obj); } }); backObjects.forEach(obj => { if (!obj.isType('group') || !obj.getObjects().some(o => o.stroke === '#ff0000')) { this.backCanvas.remove(obj); } }); // 加载模板 switch(templateName) { case 'modern': this.loadModernTemplate(); break; case 'classic': this.loadClassicTemplate(); break; case 'minimal': this.loadMinimalTemplate(); break; } } loadModernTemplate() { // 正面设计 const frontBackground = new fabric.Rect({ left: this.bleedSize * 10, top: this.bleedSize * 10, width: this.cardSize.width * 10, height: this.cardSize.height * 10, fill: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', selectable: false }); const companyName = new fabric.Text('公司名称', { left: 150, top: 100, fontSize: 32, fontFamily: 'Arial', fill: '#ffffff', fontWeight: 'bold' }); this.frontCanvas.add(frontBackground, companyName); // 背面设计 const backBackground = new fabric.Rect({ left: this.bleedSize * 10, top: this.bleedSize * 10, width: this.cardSize.width * 10, height: this.cardSize.height * 10, fill: '#f8f9fa', selectable: false }); const contactInfo = new fabric.Text('联系方式', { left: 150, top: 150, fontSize: 24, fontFamily: 'Arial', fill: '#333333' }); this.backCanvas.add(backBackground, contactInfo); } applyBusinessInfo() { const companyName = document.getElementById('company-name').value; const personName = document.getElementById('person-name').value; const personTitle = document.getElementById('person-title').value; const phoneNumber = document.getElementById('phone-number').value; const emailAddress = document.getElementById('email-address').value; const websiteUrl = document.getElementById('website-url').value; const companyAddress = document.getElementById('company-address').value; // 更新正面信息 this.updateFrontCard(companyName, personName, personTitle); // 更新背面信息 this.updateBackCard(phoneNumber, emailAddress, websiteUrl, companyAddress); } updateFrontCard(company, name, title) { // 查找并更新正面文本元素 const frontObjects = this.frontCanvas.getObjects(); frontObjects.forEach(obj => { if (obj.isType('text')) { if (obj.text === '公司名称') { obj.set('text', company || '公司名称'); } } }); // 添加姓名和职位 const nameText = new fabric.Text(name || '姓名', { left: 150, top: 200, fontSize: 24, fontFamily: 'Arial', fill: '#ffffff' }); const titleText = new fabric.Text(title || '职位', { left: 150, top: 240, fontSize: 18, fontFamily: 'Arial', fill: '#ffffff', fontStyle: 'italic' }); this.frontCanvas.add(nameText, titleText); this.frontCanvas.renderAll(); } updateBackCard(phone, email, website, address) { // 更新背面联系方式 const contactText = `电话: ${phone || '未填写'}n邮箱: ${email || '未填写'}n网址: ${website || '未填写'}n地址: ${address || '未填写'}`; const backObjects = this.backCanvas.getObjects(); backObjects.forEach(obj => { if (obj.isType('text') && obj.text === '联系方式') { obj.set('text', contactText); } }); this.backCanvas.renderAll(); } async exportToPDF() { // 创建PDF文档 const { jsPDF } = window.jspdf; const pdf = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' }); // 获取画布数据 const frontDataURL = this.frontCanvas.toDataURL({ format: 'jpeg', quality: 1 }); const backDataURL = this.backCanvas.toDataURL({ format: 'jpeg', quality: 1 }); // 添加正面到PDF pdf.addImage(frontDataURL, 'JPEG', 10, 10, this.cardSize.width, this.cardSize.height); // 添加背面到PDF(同一页面右侧) pdf.addImage(backDataURL, 'JPEG', 110, 10, this.cardSize.width, this.cardSize.height); // 添加裁切标记 this.addCuttingMarksToPDF(pdf); // 保存PDF pdf.save('business-card.pdf'); } addCuttingMarksToPDF(pdf) { // 添加裁切标记 pdf.setDrawColor(0, 0, 0); pdf.setLineWidth(0.1); // 正面裁切标记 const frontX = 10; const frontY = 10; // 添加角标记 this.drawCuttingMark(pdf, frontX, frontY); this.drawCuttingMark(pdf, frontX + this.cardSize.width, frontY); this.drawCuttingMark(pdf, frontX, frontY + this.cardSize.height); this.drawCuttingMark(pdf, frontX + this.cardSize.width, frontY + this.cardSize.height); // 背面裁切标记 const backX = 110; const backY = 10; this.drawCuttingMark(pdf, backX, backY); this.drawCuttingMark(pdf, backX + this.cardSize.width, backY); this.drawCuttingMark(pdf, backX, backY + this.cardSize.height); this.drawCuttingMark(pdf, backX + this.cardSize.width, backY + this.cardSize.height); } drawCuttingMark(pdf, x, y) { const markLength = 3; // 水平线 pdf.line(x - markLength, y, x + markLength, y); // 垂直线 pdf.line(x, y - markLength, x, y + markLength); } exportToImage() { // 导出为图片 const frontDataURL = this.frontCanvas.toDataURL({ format: 'png', quality: 1 }); const link = document.createElement('a'); link.download = 'business-card-front.png'; link.href = frontDataURL; link.click(); } } // 初始化名片设计器document.addEventListener('DOMContentLoaded', function() { window.cardDesigner = new BusinessCardDesigner(); }); ### 4.3 名片设计器后端支持 <?php// functions.php - 添加名片设计器功能 // 注册名片设计器短代码add_shortcode('business_card_designer', 'render_business_card_designer');function render_business_card_designer($atts) { ob_start(); include get_template_directory() . '/template-parts/business-card-designer.php'; return ob_get_clean(); } // 添加名片设计保存功能add_action('rest_api_init', function() { // 保存名片设计 register_rest_route('card-designer/v1', '/save', array( 'methods' => 'POST', 'callback' => 'save_card_design', 'permission_callback' => function() { return current_user_can('edit_posts'); } )); // 获取用户保存的名片设计 register_rest_route('card-designer/v1', '/designs', array( 'methods' => 'GET', 'callback' => 'get_user_card_designs', 'permission_callback' => function() { return is_user_logged_in(); } )); }); function save_card_design($request) { $user_id = get_current_user_id(); $design_data = $request->get_param('design_data'); $design_name = $request->get_param('design_name'); $card_type = $request->get_param('card_type'); // 'front' or 'back' if(empty($design_data) || empty($design_name)) { return new WP_Error('invalid_data', '设计数据或名称为空', array('status' => 400)); } // 生成预览图 $preview_data = generate_card_preview($design_data); // 保存到数据库 global $wpdb; $table_name = $wpdb->prefix . 'card_designs'; $result = $wpdb->insert( $table_name, array( 'user_id' => $user_id, 'design_name' => sanitize_text_field($design_name), 'design_data' => json_encode($design_data), 'card_type' => $card_type, 'preview_data' => $preview_data, 'created_at' => current_time('mysql') ), array('%d', '%s', '%s', '%s', '%s', '%s') ); if($result === false) { return new WP_Error('db_error', '保存失败', array('status' => 500)); } return rest_ensure_response(array( 'success' => true, 'message' => '名片设计已保存', 'design_id' => $wpdb->insert_id )); } function generate_card_preview($design_data) { // 这里可以添加生成预览图的逻辑 // 可以使用GD库或ImageMagick生成缩略图 return ''; // 返回base64编码的预览图 } // 创建名片设计数据库表register_activation_hook(__FILE__, 'create_card_designs_table');function create_card_designs_table() { global $wpdb; $table_name = $wpdb->prefix . 'card_designs'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, design_name varchar(255) NOT NULL, design_data longtext NOT NULL, card_type varchar(50) NOT NULL, preview_data longtext, created_at datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, PRIMARY KEY (id), KEY user_id (user_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); }?> --- ## 五、样式设计与用户体验优化 ### 5.1 响应式CSS设计 / assets/css/design-tools.css / / Logo设计器样式 /.logo-designer-container { display: flex; flex-direction: column; height: 80vh; background: #f5f7fa; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.1); } .designer-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; } .designer-body { display: flex; flex: 1; overflow: hidden; } .sidebar-left, .sidebar-right { width: 250px; background: white; padding: 15px; overflow-y: auto; border-right: 1px solid #e0e0e0; } .sidebar-right { border-right: none; border-left: 1px solid #e0e0e0; } .canvas-area { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px; background: #f8f9fa; } logo-canvas { background: white; border: 1px solid #ddd; border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); } .canvas-controls { margin-top: 15px; display: flex; gap: 10px; } .tool-section { margin-bottom: 25px; } .tool-section h4 { margin-bottom: 10px; color: #333; font-size: 14px; text-transform: uppercase; letter-spacing: 1px; } .shape-list { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } .shape-btn { padding: 8px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; transition: all 0.3s ease; } .shape-btn:hover { background: #f0f0f0; border-color: #667eea; } .icon-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; } .icon-item { padding: 10px; border: 1px solid #e0e0e0; border-radius: 4px; text-align: center; cursor: pointer; transition: all 0.3s ease; } .icon-item:hover { background: #f0f7ff; border-color: #4dabf7; transform: translateY(-2px); } .icon-item i { font-size: 20px; color: #495057; } .property-group { margin-bottom: 15px; } .property-group label { display: block; margin-bottom: 5px; font-size: 12px; color: #666; font-weight: 500; } .property-group input,.property-group select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } .layers-panel ul { list-style: none; padding: 0; margin: 0; } .layer-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; border-bottom: 1px solid #eee; font-size: 13px; } .layer-item:hover { background: #f8f9fa; } .layer-actions { display: flex; gap: 5px; } .layer-actions button { padding: 2px 6px; font-size: 11px; border: 1px solid #ddd; background: white; border-radius: 3px; cursor: pointer; } / 名片设计器样式 /.card-designer-container { background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.1); } .template-selector { padding: 15px; background: #f8f9fa; border-bottom: 1px solid #e0e0

发表评论

详细指南,在WordPress中集成在线客服会话存档与质检分析系统

详细指南:在WordPress中集成在线客服会话存档与质检分析系统 摘要 随着电子商务和在线服务的快速发展,在线客服系统已成为企业与客户沟通的重要桥梁。然而,仅仅拥有客服系统并不足够,如何有效管理、存档和分析客服会话数据,提升服务质量,已成为企业数字化转型的关键环节。本文将详细介绍如何在WordPress平台中通过代码二次开发,集成在线客服会话存档与质检分析系统,实现这一常用互联网小工具功能。我们将从系统架构设计、数据库规划、前后端开发到数据分析功能实现,提供完整的技术解决方案。 一、系统需求分析与架构设计 1.1 业务需求分析 在开始技术实现之前,我们首先需要明确系统的业务需求: 会话存档功能:自动保存所有在线客服会话记录,包括文字、图片、文件等多媒体内容 质检分析功能:对客服会话进行质量评估,包括响应时间、服务态度、问题解决率等指标 数据可视化:通过图表展示客服绩效、会话趋势、常见问题等数据 权限管理:不同角色的用户(管理员、客服主管、普通客服)拥有不同的数据访问权限 搜索与筛选:支持按时间、客服人员、客户、关键词等多维度检索会话记录 实时监控:实时查看当前在线会话状态和客服工作状态 1.2 技术架构设计 基于WordPress的扩展性,我们设计以下技术架构: 前端展示层:使用WordPress主题模板和自定义页面展示数据 业务逻辑层:通过自定义插件处理会话数据的存储、分析和展示逻辑 数据存储层:扩展WordPress数据库,创建专门的数据表存储会话和质检数据 第三方集成层:与主流在线客服系统(如LiveChat、Zendesk、自定义系统)API对接 数据分析层:集成数据分析工具和算法,实现智能质检功能 1.3 开发环境准备 在开始开发前,确保具备以下环境: WordPress 5.0+ 版本 PHP 7.4+ 版本 MySQL 5.6+ 或 MariaDB 10.1+ 代码编辑器(VS Code、PHPStorm等) 本地开发环境(XAMPP、MAMP、Local等) 二、数据库设计与扩展 2.1 自定义数据表设计 为了存储客服会话和质检数据,我们需要在WordPress数据库中添加以下数据表: -- 客服会话主表 CREATE TABLE wp_chat_sessions ( session_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, session_key VARCHAR(100) NOT NULL, customer_id BIGINT(20) UNSIGNED, customer_name VARCHAR(255), customer_email VARCHAR(255), agent_id BIGINT(20) UNSIGNED, agent_name VARCHAR(255), start_time DATETIME NOT NULL, end_time DATETIME, duration INT(11), status ENUM('active', 'closed', 'transferred', 'timeout') DEFAULT 'active', channel VARCHAR(50), rating TINYINT(1), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (session_id), UNIQUE KEY session_key (session_key), KEY customer_id (customer_id), KEY agent_id (agent_id), KEY start_time (start_time) ); -- 会话消息表 CREATE TABLE wp_chat_messages ( message_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, session_id BIGINT(20) UNSIGNED NOT NULL, sender_type ENUM('customer', 'agent', 'system') NOT NULL, sender_id BIGINT(20) UNSIGNED, message_type ENUM('text', 'image', 'file', 'emoji', 'system') DEFAULT 'text', content LONGTEXT, media_url VARCHAR(500), file_name VARCHAR(255), file_size INT(11), sent_time DATETIME NOT NULL, read_status TINYINT(1) DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (message_id), KEY session_id (session_id), KEY sent_time (sent_time), FOREIGN KEY (session_id) REFERENCES wp_chat_sessions(session_id) ON DELETE CASCADE ); -- 质检分析表 CREATE TABLE wp_chat_quality ( quality_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, session_id BIGINT(20) UNSIGNED NOT NULL, evaluator_id BIGINT(20) UNSIGNED, evaluation_time DATETIME, response_time_score TINYINT(1), professionalism_score TINYINT(1), problem_solving_score TINYINT(1), communication_score TINYINT(1), overall_score DECIMAL(3,2), positive_keywords TEXT, negative_keywords TEXT, tags VARCHAR(500), notes TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (quality_id), UNIQUE KEY session_evaluator (session_id, evaluator_id), KEY session_id (session_id), KEY evaluator_id (evaluator_id), FOREIGN KEY (session_id) REFERENCES wp_chat_sessions(session_id) ON DELETE CASCADE ); -- 客服绩效表 CREATE TABLE wp_agent_performance ( performance_id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, agent_id BIGINT(20) UNSIGNED NOT NULL, date DATE NOT NULL, total_sessions INT(11) DEFAULT 0, avg_response_time DECIMAL(6,2), avg_rating DECIMAL(3,2), avg_quality_score DECIMAL(3,2), first_response_time DECIMAL(6,2), resolution_rate DECIMAL(5,2), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (performance_id), UNIQUE KEY agent_date (agent_id, date), KEY agent_id (agent_id), KEY date (date) ); 2.2 数据库操作类实现 创建数据库操作类,封装所有数据库交互逻辑: <?php /** * 客服会话数据库操作类 */ class ChatSessionDB { private $wpdb; private $sessions_table; private $messages_table; private $quality_table; private $performance_table; public function __construct() { global $wpdb; $this->wpdb = $wpdb; $this->sessions_table = $wpdb->prefix . 'chat_sessions'; $this->messages_table = $wpdb->prefix . 'chat_messages'; $this->quality_table = $wpdb->prefix . 'chat_quality'; $this->performance_table = $wpdb->prefix . 'agent_performance'; } /** * 创建新会话 */ public function create_session($data) { $defaults = array( 'session_key' => uniqid('chat_'), 'start_time' => current_time('mysql'), 'status' => 'active' ); $data = wp_parse_args($data, $defaults); $result = $this->wpdb->insert( $this->sessions_table, $data, array('%s', '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%d', '%s', '%d') ); if ($result) { return $this->wpdb->insert_id; } return false; } /** * 添加会话消息 */ public function add_message($session_id, $sender_type, $content, $message_type = 'text', $media_data = array()) { $data = array( 'session_id' => $session_id, 'sender_type' => $sender_type, 'message_type' => $message_type, 'content' => $content, 'sent_time' => current_time('mysql') ); // 处理媒体文件 if (!empty($media_data)) { $data['media_url'] = isset($media_data['url']) ? $media_data['url'] : ''; $data['file_name'] = isset($media_data['name']) ? $media_data['name'] : ''; $data['file_size'] = isset($media_data['size']) ? $media_data['size'] : 0; } // 如果是客服发送的消息,记录客服ID if ($sender_type === 'agent' && is_user_logged_in()) { $data['sender_id'] = get_current_user_id(); } $result = $this->wpdb->insert( $this->messages_table, $data, array('%d', '%s', '%d', '%s', '%s', '%s', '%s', '%d', '%s') ); return $result ? $this->wpdb->insert_id : false; } /** * 获取会话消息 */ public function get_session_messages($session_id, $limit = 100, $offset = 0) { $query = $this->wpdb->prepare( "SELECT * FROM {$this->messages_table} WHERE session_id = %d ORDER BY sent_time ASC LIMIT %d OFFSET %d", $session_id, $limit, $offset ); return $this->wpdb->get_results($query); } /** * 更新会话状态 */ public function update_session_status($session_id, $status, $end_time = null) { $data = array('status' => $status); if ($end_time) { $data['end_time'] = $end_time; // 计算会话时长 $session = $this->get_session($session_id); if ($session && $session->start_time) { $start = strtotime($session->start_time); $end = strtotime($end_time); $data['duration'] = $end - $start; } } return $this->wpdb->update( $this->sessions_table, $data, array('session_id' => $session_id), array('%s', '%s', '%d'), array('%d') ); } /** * 添加质检记录 */ public function add_quality_evaluation($session_id, $scores, $evaluator_id = null, $notes = '') { // 计算总分 $total = 0; $count = 0; foreach ($scores as $score) { if (!is_null($score)) { $total += $score; $count++; } } $overall_score = $count > 0 ? round($total / $count, 2) : 0; $data = array( 'session_id' => $session_id, 'evaluator_id' => $evaluator_id ?: get_current_user_id(), 'evaluation_time' => current_time('mysql'), 'overall_score' => $overall_score, 'notes' => $notes ); // 添加各项分数 $score_fields = array( 'response_time_score', 'professionalism_score', 'problem_solving_score', 'communication_score' ); foreach ($score_fields as $field) { if (isset($scores[$field])) { $data[$field] = $scores[$field]; } } // 关键词分析(简化版,实际应用中可能需要更复杂的NLP处理) if (isset($scores['positive_keywords'])) { $data['positive_keywords'] = is_array($scores['positive_keywords']) ? implode(',', $scores['positive_keywords']) : $scores['positive_keywords']; } if (isset($scores['negative_keywords'])) { $data['negative_keywords'] = is_array($scores['negative_keywords']) ? implode(',', $scores['negative_keywords']) : $scores['negative_keywords']; } return $this->wpdb->insert( $this->quality_table, $data, array('%d', '%d', '%s', '%d', '%d', '%d', '%d', '%f', '%s', '%s', '%s', '%s') ); } /** * 获取客服绩效数据 */ public function get_agent_performance($agent_id, $start_date, $end_date) { $query = $this->wpdb->prepare( "SELECT * FROM {$this->performance_table} WHERE agent_id = %d AND date BETWEEN %s AND %s ORDER BY date ASC", $agent_id, $start_date, $end_date ); return $this->wpdb->get_results($query); } /** * 统计会话数据 */ public function get_session_stats($filters = array()) { $where = array('1=1'); $params = array(); // 时间筛选 if (!empty($filters['start_date'])) { $where[] = "start_time >= %s"; $params[] = $filters['start_date']; } if (!empty($filters['end_date'])) { $where[] = "start_time <= %s"; $params[] = $filters['end_date'] . ' 23:59:59'; } // 客服筛选 if (!empty($filters['agent_id'])) { $where[] = "agent_id = %d"; $params[] = $filters['agent_id']; } // 状态筛选 if (!empty($filters['status'])) { $where[] = "status = %s"; $params[] = $filters['status']; } $where_clause = implode(' AND ', $where); // 基础统计 $query = $this->wpdb->prepare( "SELECT COUNT(*) as total_sessions, AVG(duration) as avg_duration, AVG(rating) as avg_rating, SUM(CASE WHEN status = 'closed' THEN 1 ELSE 0 END) as closed_sessions, SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active_sessions FROM {$this->sessions_table} WHERE {$where_clause}", $params ); return $this->wpdb->get_row($query); } } ?> 三、WordPress插件开发 3.1 插件基础结构 创建插件主文件 chat-archive-analyzer.php: <?php /** * Plugin Name: 在线客服会话存档与质检分析系统 * Plugin URI: https://yourwebsite.com/ * Description: 在WordPress中集成在线客服会话存档与质检分析功能 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: chat-archive-analyzer */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('CHAT_ARCHIVE_VERSION', '1.0.0'); define('CHAT_ARCHIVE_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('CHAT_ARCHIVE_PLUGIN_URL', plugin_dir_url(__FILE__)); // 自动加载类 spl_autoload_register(function ($class) { $prefix = 'ChatArchive\'; $base_dir = CHAT_ARCHIVE_PLUGIN_DIR . 'includes/'; $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) { return; } $relative_class = substr($class, $len); $file = $base_dir . str_replace('\', '/', $relative_class) . '.php'; if (file_exists($file)) { require $file; } }); // 初始化插件 class ChatArchiveAnalyzer { private static $instance = null; private $db; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->init_hooks(); } 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')); // 管理菜单 add_action('admin_menu', array($this, 'add_admin_menu')); // 脚本和样式 add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_scripts')); // AJAX处理 add_action('wp_ajax_chat_archive_action', array($this, 'handle_ajax_request')); add_action('wp_ajax_nopriv_chat_archive_action', array($this, 'handle_ajax_request')); // 短代码 add_shortcode('chat_session_archive', array($this, 'shortcode_session_archive')); add_shortcode('chat_analytics_dashboard', array($this, 'shortcode_analytics_dashboard')); } public function activate() { require_once CHAT_ARCHIVE_PLUGIN_DIR . 'includes/class-installer.php'; ChatArchiveInstaller::install(); // 创建默认选项 $default_options = array( 'auto_archive' => true, 'retention_days' => 365, 'quality_auto_evaluation' => false, 'enable_realtime_monitoring' => true, 'dashboard_access_roles' => array('administrator', 'editor') ); add_option('chat_archive_settings', $default_options); } public function deactivate() { // 清理临时数据 wp_clear_scheduled_hook('chat_archive_daily_maintenance'); } public function init() { // 初始化数据库类 $this->db = new ChatSessionDB(); // 加载文本域 load_plugin_textdomain('chat-archive-analyzer', false, dirname(plugin_basename(__FILE__)) . '/languages'); // 初始化API if (class_exists('ChatArchiveAPI')) { new ChatArchiveAPI(); } // 初始化第三方集成 $this->init_third_party_integrations(); } private function init_third_party_integrations() { // 检查并加载可用的第三方客服系统集成 $integrations_dir = CHAT_ARCHIVE_PLUGIN_DIR . 'includes/integrations/'; if (file_exists($integrations_dir)) { $integrations = glob($integrations_dir . '*.php'); foreach ($integrations as $integration_file) { $integration_name = basename($integration_file, '.php'); // 检查是否启用了该集成 $settings = get_option('chat_archive_settings', array()); $enabled_integrations = isset($settings['enabled_integrations']) ? $settings['enabled_integrations'] : array(); if (in_array($integration_name, $enabled_integrations) || empty($enabled_integrations)) { require_once $integration_file; $class_name = 'ChatArchive\Integrations\' . ucfirst($integration_name); if (class_exists($class_name)) { new $class_name(); } } } } } public function add_admin_menu() { // 主菜单 add_menu_page( __('客服会话存档', 'chat-archive-analyzer'), __('客服会话', 'chat-archive-analyzer'), 'manage_options', 'chat-archive', array($this, 'render_admin_page'), 'dashicons-format-chat', 30 ); // 子菜单 add_submenu_page( 'chat-archive', __('会话存档', 'chat-archive-analyzer'), __('会话存档', 'chat-archive-analyzer'), 'manage_options', 'chat-archive-sessions', array($this, 'render_sessions_page') ); add_submenu_page( 'chat-archive', __('质检分析', 'chat-archive-analyzer'), __('质检分析', 'chat-archive-analyzer'), 'manage_options', 'chat-archive-quality', array($this, 'render_quality_page') ); add_submenu_page( 'chat-archive', __('数据分析', 'chat-archive-analyzer'), __('数据分析', 'chat-archive-analyzer'), 'manage_options', 'chat-archive-analytics', array($this, 'render_analytics_page') ); add_submenu_page( 'chat-archive', __('实时监控', 'chat-archive-analyzer'), __('实时监控', 'chat-archive-analyzer'), 'manage_options', 'chat-archive-monitor', array($this, 'render_monitor_page') ); add_submenu_page( 'chat-archive', __('设置', 'chat-archive-analyzer'), __('设置', 'chat-archive-analyzer'), 'manage_options', 'chat-archive-settings', array($this, 'render_settings_page') ); } public function render_admin_page() { include CHAT_ARCHIVE_PLUGIN_DIR . 'templates/admin/dashboard.php'; } public function render_sessions_page() { include CHAT_ARCHIVE_PLUGIN_DIR . 'templates/admin/sessions.php'; } public function render_quality_page() { include CHAT_ARCHIVE_PLUGIN_DIR . 'templates/admin/quality.php'; } public function render_analytics_page() { include CHAT_ARCHIVE_PLUGIN_DIR . 'templates/admin/analytics.php'; } public function render_monitor_page() { include CHAT_ARCHIVE_PLUGIN_DIR . 'templates/admin/monitor.php'; } public function render_settings_page() { include CHAT_ARCHIVE_PLUGIN_DIR . 'templates/admin/settings.php'; } public function enqueue_admin_scripts($hook) { // 只在插件页面加载 if (strpos($hook, 'chat-archive') === false) { return; } // CSS wp_enqueue_style( 'chat-archive-admin', CHAT_ARCHIVE_PLUGIN_URL . 'assets/css/admin.css', array(), CHAT_ARCHIVE_VERSION ); // JavaScript wp_enqueue_script( 'chat-archive-admin', CHAT_ARCHIVE_PLUGIN_URL . 'assets/js/admin.js', array('jquery', 'jquery-ui-datepicker', 'chart-js'), CHAT_ARCHIVE_VERSION, true ); // 加载Chart.js wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), '3.7.0', true ); // 本地化脚本 wp_localize_script('chat-archive-admin', 'chatArchive', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('chat_archive_nonce'), 'strings' => array( 'loading' => __('加载中...', 'chat-archive-analyzer'), 'error' => __('发生错误', 'chat-archive-analyzer'), 'confirm_delete' => __('确定要删除这条记录吗?', 'chat-archive-analyzer') ) )); } public function enqueue_frontend_scripts() { // 前端样式和脚本(用于短代码) if (is_page() || is_single()) { global $post; if (has_shortcode($post->post_content, 'chat_session_archive') || has_shortcode($post->post_content, 'chat_analytics_dashboard')) { wp_enqueue_style( 'chat-archive-frontend', CHAT_ARCHIVE_PLUGIN_URL . 'assets/css/frontend.css', array(), CHAT_ARCHIVE_VERSION ); wp_enqueue_script( 'chat-archive-frontend', CHAT_ARCHIVE_PLUGIN_URL . 'assets/js/frontend.js', array('jquery'), CHAT_ARCHIVE_VERSION, true ); } } } public function handle_ajax_request() { // 验证nonce if (!check_ajax_referer('chat_archive_nonce', 'nonce', false)) { wp_die(__('安全验证失败', 'chat-archive-analyzer'), 403); } $action = isset($_POST['action_type']) ? sanitize_text_field($_POST['action_type']) : ''; switch ($action) { case 'get_sessions': $this->ajax_get_sessions(); break; case 'get_session_detail': $this->ajax_get_session_detail(); break; case 'add_quality_evaluation': $this->ajax_add_quality_evaluation(); break; case 'get_analytics_data': $this->ajax_get_analytics_data(); break; case 'export_sessions': $this->ajax_export_sessions(); break; default: wp_send_json_error(array('message' => __('未知操作', 'chat-archive-analyzer'))); } } private function ajax_get_sessions() { // 验证权限 if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => __('权限不足', 'chat-archive-analyzer'))); } $page = isset($_POST['page']) ? intval($_POST['page']) : 1; $per_page = isset($_POST['per_page']) ? intval($_POST['per_page']) : 20; $filters = isset($_POST['filters']) ? $_POST['filters'] : array(); // 清理过滤条件 $clean_filters = array(); if (isset($filters['start_date'])) { $clean_filters['start_date'] = sanitize_text_field($filters['start_date']); } if (isset($filters['end_date'])) { $clean_filters['end_date'] = sanitize_text_field($filters['end_date']); } if (isset($filters['agent_id'])) { $clean_filters['agent_id'] = intval($filters['agent_id']); } if (isset($filters['status'])) { $clean_filters['status'] = sanitize_text_field($filters['status']); } if (isset($filters['search'])) { $clean_filters['search'] = sanitize_text_field($filters['search']); } // 获取会话数据 $sessions = $this->db->get_sessions($clean_filters, $page, $per_page); $total = $this->db->count_sessions($clean_filters); wp_send_json_success(array( 'sessions' => $sessions, 'total' => $total, 'total_pages' => ceil($total / $per_page), 'current_page' => $page )); } public function shortcode_session_archive($atts) { // 检查用户权限 if (!is_user_logged_in()) { return '<p>' . __('请登录后查看会话记录', 'chat-archive-analyzer') . '</p>'; } $atts = shortcode_atts(array( 'agent_id' => get_current_user_id(), 'limit' => 10, 'show_search' => true ), $atts, 'chat_session_archive'); ob_start(); include CHAT_ARCHIVE_PLUGIN_DIR . 'templates/frontend/session-archive.php'; return ob_get_clean(); } public function shortcode_analytics_dashboard($atts) { // 检查用户权限 $user = wp_get_current_user(); $allowed_roles = array('administrator', 'editor', 'shop_manager'); if (!array_intersect($allowed_roles, $user->roles)) { return '<p>' . __('您没有权限查看此页面', 'chat-archive-analyzer') . '</p>'; } $atts = shortcode_atts(array( 'period' => 'month', 'show_agents' => true, 'show_trends' => true ), $atts, 'chat_analytics_dashboard'); ob_start(); include CHAT_ARCHIVE_PLUGIN_DIR . 'templates/frontend/analytics-dashboard.php'; return ob_get_clean(); } } // 启动插件ChatArchiveAnalyzer::get_instance();?> ### 3.2 安装器类实现 创建 `includes/class-installer.php`: <?phpnamespace ChatArchive; class Installer { public static function install() { self::create_tables(); self::create_roles(); self::schedule_events(); } private static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 这里包含之前设计的SQL表创建语句 require_once CHAT_ARCHIVE_PLUGIN_DIR . 'includes/schema.php'; dbDelta($sql); } private static function create_roles() { // 添加客服主管角色 add_role('chat_supervisor', __('客服主管', 'chat-archive-analyzer'), array( 'read' => true, 'edit_posts' => true, 'delete_posts' => true, 'manage_chat_sessions' => true, 'evaluate_chat_quality' => true, 'view_chat_analytics' => true )); // 添加客服角色 add_role('chat_agent', __('客服专员', 'chat-archive-analyzer'), array( 'read' => true, 'edit_posts' => false, 'view_own_chat_sessions' => true, 'view_own_performance' => true )); // 为管理员添加自定义权限 $admin_role = get_role('administrator'); if ($admin_role) { $admin_role->add_cap('manage_chat_sessions'); $admin_role->add_cap('evaluate_chat_quality'); $admin_role->add_cap('view_chat_analytics'); $admin_role->add_cap('configure_chat_system'); } } private static function schedule_events() { if (!wp_next_scheduled('chat_archive_daily_maintenance')) { wp_schedule_event(time(), 'daily', 'chat_archive_daily_maintenance'); } if (!wp_next_scheduled('chat_archive_hourly_processing')) { wp_schedule_event(time(), 'hourly', 'chat_archive_hourly_processing'); } } }?> --- ## 四、前端界面与用户体验 ### 4.1 管理界面模板 创建 `templates/admin/sessions.php`: <div class="wrap chat-archive-wrapper"> <h1><?php _e('客服会话存档', 'chat-archive-analyzer'); ?></h1> <!-- 过滤条件 --> <div class="chat-filter-section"> <form id="chat-session-filter" method="get"> <input type="hidden" name="page" value="chat-archive-sessions"> <div class="filter-row"> <div class="filter-group"> <label for="filter-date-start"><?php _e('开始日期', 'chat-archive-analyzer'); ?></label> <input type="date" id="filter-date-start" name="date_start" class="regular-text"> </div> <div class="filter-group"> <label for="filter-date-end"><?php _e('结束日期', 'chat-archive-analyzer'); ?></label> <input type="date" id="filter-date-end" name="date_end" class="regular-text"> </div> <div class="filter-group"> <label for="filter-agent"><?php _e('客服人员', 'chat-archive-analyzer'); ?></label> <select id="filter-agent" name="agent_id"> <option value=""><?php _e('所有客服', 'chat-archive-analyzer'); ?></option> <?php $agents = get_users(array('role__in' => array('chat_agent', 'administrator', 'editor'))); foreach ($agents as $agent) { echo '<option value="' . $agent->ID . '">' . $agent->display_name . '</option>'; } ?> </select> </div> <div class="filter-group"> <label for="filter-status"><?php _e('会话状态', 'chat-archive-analyzer'); ?></label> <select id="filter-status" name="status"> <option value=""><?php _e('所有状态', 'chat-archive-analyzer'); ?></option> <option value="active"><?php _e('进行中', 'chat-archive-analyzer'); ?></option> <option value="closed"><?php _e('已结束', 'chat-archive-analyzer'); ?></option> <option value="transferred"><?php _e('已转接', 'chat-archive-analyzer'); ?></option> </select> </div> <div class="filter-group"> <label for="filter-search"><?php _e('关键词搜索', 'chat-archive-analyzer'); ?></label> <input type="text" id="filter-search" name="search" placeholder="<?php _e('客户姓名、邮箱或会话内容', 'chat-archive-analyzer'); ?>"> </div> <div class="filter-actions"> <button type="submit" class="button button-primary"><?php _e('筛选', 'chat-archive-analyzer'); ?></button> <button type="button" id="reset-filters" class="button"><?php _e('重置', 'chat-archive-analyzer'); ?></button> </div> </div> </form> </div> <!-- 会话列表 --> <div class="chat-sessions-list"> <div class="tablenav top"> <div class="alignleft actions"> <select id="bulk-action-selector"> <option value=""><?php _e('批量操作', 'chat-archive-analyzer'); ?></option> <option value="export"><?php _e('导出选中', 'chat-archive-analyzer'); ?></option> <option value="delete"><?php _e('删除选中', 'chat-archive-analyzer'); ?></option> </select> <button id="do-bulk-action" class="button"><?php _e('应用', 'chat-archive-analyzer'); ?></button> </div> <div class="tablenav-pages"> <span class="displaying-num"></span> <span class="pagination-links"> <button class="button first-page" disabled><?php _e('首页', 'chat-archive-analyzer'); ?></button> <button class="button prev-page" disabled><?php _e('上一页', 'chat-archive-analyzer'); ?></button> <span class="paging-input"> <input type="text" class="current-page" size="2" value="1"> <?php _e('共', 'chat-archive-analyzer'); ?> <span class="total-pages">1</span> <?php _e('页', 'chat-archive-analyzer'); ?>

发表评论

手把手教学,为你的网站添加智能化的内容自动摘要生成器

手把手教学:为你的WordPress网站添加智能内容自动摘要生成器 引言:为什么你的网站需要智能摘要功能? 在信息爆炸的互联网时代,用户浏览网页的时间越来越碎片化。根据研究,普通用户在决定是否继续阅读一篇文章前,平均只会花费15秒浏览页面内容。一个精心设计的摘要不仅能帮助用户快速了解文章核心内容,还能显著提高页面停留时间和内容转化率。 传统的WordPress摘要功能通常只是简单截取文章开头部分文字,这种方法往往无法准确反映文章的核心观点。而智能摘要生成器通过算法分析文章内容,提取关键信息,生成简洁、准确的摘要,为用户提供更好的阅读体验。 本文将带你从零开始,通过WordPress代码二次开发,为你的网站添加一个智能化的内容自动摘要生成器。无论你是WordPress开发者还是有一定技术基础的网站管理员,都能通过本教程实现这一功能。 第一部分:准备工作与环境配置 1.1 理解WordPress摘要系统的工作原理 在开始开发之前,我们需要了解WordPress默认的摘要系统是如何工作的: WordPress使用the_excerpt()函数显示摘要 默认情况下,如果没有手动设置摘要,WordPress会自动截取文章前55个单词 可以通过excerpt_length过滤器修改摘要长度 可以通过excerpt_more过滤器修改摘要末尾的"阅读更多"文本 1.2 开发环境准备 为了安全地进行代码开发,我们建议采取以下步骤: 备份你的网站:在进行任何代码修改前,请务必备份整个网站和数据库 创建子主题:如果你正在使用主题,建议创建子主题进行修改 启用调试模式:在wp-config.php中添加以下代码以便查看错误信息: define('WP_DEBUG', true); define('WP_DEBUG_LOG', true); define('WP_DEBUG_DISPLAY', false); 安装代码编辑器:推荐使用VS Code、Sublime Text或PHPStorm 1.3 理解我们将要构建的系统架构 我们的智能摘要生成器将包含以下组件: 文本处理模块:清理和预处理文章内容 关键词提取模块:识别文章中的关键术语和概念 摘要生成算法:基于文本分析生成连贯摘要 缓存机制:提高性能,避免重复处理相同内容 管理界面:允许用户配置摘要生成参数 第二部分:构建基础摘要生成功能 2.1 创建插件文件结构 首先,我们创建一个独立的WordPress插件来实现这个功能: 在wp-content/plugins/目录下创建新文件夹smart-excerpt-generator 在该文件夹中创建以下文件: smart-excerpt-generator.php (主插件文件) includes/ (目录) class-text-processor.php class-keyword-extractor.php class-summary-generator.php assets/ (目录) css/admin-style.css js/admin-script.js templates/ (目录) admin-settings.php 2.2 编写插件主文件 打开smart-excerpt-generator.php,添加以下代码: <?php /** * Plugin Name: 智能摘要生成器 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress文章自动生成智能摘要 * Version: 1.0.0 * Author: 你的名字 * License: GPL v2 or later * Text Domain: smart-excerpt */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SEG_VERSION', '1.0.0'); define('SEG_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SEG_PLUGIN_URL', plugin_dir_url(__FILE__)); // 自动加载类文件 spl_autoload_register(function ($class) { $prefix = 'SmartExcerpt\'; $base_dir = SEG_PLUGIN_DIR . 'includes/'; $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) { return; } $relative_class = substr($class, $len); $file = $base_dir . str_replace('\', '/', $relative_class) . '.php'; if (file_exists($file)) { require $file; } }); // 初始化插件 function seg_init_plugin() { // 检查PHP版本 if (version_compare(PHP_VERSION, '7.0.0', '<')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>'; echo '智能摘要生成器需要PHP 7.0或更高版本。当前版本:' . PHP_VERSION; echo '</p></div>'; }); return; } // 实例化主控制器 $plugin = new SmartExcerptMainController(); $plugin->run(); } add_action('plugins_loaded', 'seg_init_plugin'); 2.3 创建文本处理器类 在includes/目录下创建class-text-processor.php: <?php namespace SmartExcerpt; class TextProcessor { /** * 清理HTML内容,提取纯文本 */ public function extract_plain_text($content) { // 移除HTML标签 $text = strip_tags($content); // 解码HTML实体 $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8'); // 移除多余空白字符 $text = preg_replace('/s+/', ' ', $text); // 移除短代码 $text = strip_shortcodes($text); return trim($text); } /** * 将文本分割成句子 */ public function split_into_sentences($text) { // 使用正则表达式分割句子 $sentences = preg_split('/(?<=[.?!。?!])s+/', $text, -1, PREG_SPLIT_NO_EMPTY); // 过滤空句子和过短句子 $sentences = array_filter($sentences, function($sentence) { return strlen(trim($sentence)) > 10; }); return array_values($sentences); } /** * 计算句子权重 */ public function calculate_sentence_score($sentence, $keywords) { $score = 0; // 基于关键词出现频率评分 foreach ($keywords as $keyword => $weight) { $count = substr_count(strtolower($sentence), strtolower($keyword)); $score += $count * $weight; } // 基于句子位置评分(开头和结尾的句子通常更重要) // 这个因素将在调用此方法时通过参数传递 // 基于句子长度评分(中等长度的句子通常包含更多信息) $length = strlen($sentence); if ($length > 50 && $length < 200) { $score += 2; } return $score; } } 2.4 创建关键词提取器类 在includes/目录下创建class-keyword-extractor.php: <?php namespace SmartExcerpt; class KeywordExtractor { private $stop_words; public function __construct() { // 中文停用词列表(部分示例) $this->stop_words = [ '的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这', 'the', 'and', 'a', 'an', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'being' ]; } /** * 从文本中提取关键词 */ public function extract($text, $max_keywords = 10) { // 分词处理(简化版,实际应用中可能需要使用分词库) $words = $this->tokenize($text); // 移除停用词 $words = $this->remove_stop_words($words); // 计算词频 $word_freq = array_count_values($words); // 按频率排序 arsort($word_freq); // 取前N个关键词 $keywords = array_slice($word_freq, 0, $max_keywords, true); // 计算权重(归一化处理) $total = array_sum($keywords); $weighted_keywords = []; foreach ($keywords as $word => $count) { $weighted_keywords[$word] = $count / $total * 100; } return $weighted_keywords; } /** * 简单分词函数(针对中文和英文) */ private function tokenize($text) { // 将文本转换为小写 $text = mb_strtolower($text, 'UTF-8'); // 使用正则表达式分割单词 // 匹配中文词语和英文单词 preg_match_all('/[x{4e00}-x{9fa5}]|[a-zA-Z]+/', $text, $matches); return $matches[0]; } /** * 移除停用词 */ private function remove_stop_words($words) { return array_filter($words, function($word) { return !in_array($word, $this->stop_words) && strlen($word) > 1; }); } } 第三部分:实现智能摘要生成算法 3.1 创建摘要生成器主类 在includes/目录下创建class-summary-generator.php: <?php namespace SmartExcerpt; class SummaryGenerator { private $text_processor; private $keyword_extractor; public function __construct() { $this->text_processor = new TextProcessor(); $this->keyword_extractor = new KeywordExtractor(); } /** * 生成文章摘要 */ public function generate($content, $max_length = 150) { // 检查是否有缓存 $cache_key = 'seg_summary_' . md5($content . $max_length); $cached_summary = get_transient($cache_key); if ($cached_summary !== false) { return $cached_summary; } // 提取纯文本 $plain_text = $this->text_processor->extract_plain_text($content); // 如果文本太短,直接返回 if (strlen($plain_text) < $max_length) { return $plain_text; } // 提取关键词 $keywords = $this->keyword_extractor->extract($plain_text); // 分割成句子 $sentences = $this->text_processor->split_into_sentences($plain_text); // 计算每个句子的得分 $scored_sentences = []; $total_sentences = count($sentences); foreach ($sentences as $index => $sentence) { $score = $this->text_processor->calculate_sentence_score($sentence, $keywords); // 基于句子位置调整分数 $position_score = $this->calculate_position_score($index, $total_sentences); $score += $position_score; $scored_sentences[] = [ 'text' => $sentence, 'score' => $score, 'index' => $index ]; } // 按分数排序 usort($scored_sentences, function($a, $b) { return $b['score'] <=> $a['score']; }); // 选择最佳句子生成摘要 $selected_sentences = []; $current_length = 0; foreach ($scored_sentences as $sentence) { $sentence_length = strlen($sentence['text']); if ($current_length + $sentence_length <= $max_length) { $selected_sentences[] = $sentence; $current_length += $sentence_length; } if ($current_length >= $max_length * 0.8) { break; } } // 按原始顺序排序选中的句子 usort($selected_sentences, function($a, $b) { return $a['index'] <=> $b['index']; }); // 构建摘要 $summary = ''; foreach ($selected_sentences as $sentence) { $summary .= $sentence['text'] . ' '; } $summary = trim($summary); // 如果摘要太短,使用传统方法生成 if (strlen($summary) < $max_length * 0.5) { $summary = $this->generate_fallback_summary($plain_text, $max_length); } // 缓存结果(24小时) set_transient($cache_key, $summary, DAY_IN_SECONDS); return $summary; } /** * 计算句子位置分数 */ private function calculate_position_score($index, $total) { // 开头和结尾的句子通常更重要 $position = $index / max(1, $total - 1); if ($position < 0.1) { // 前10% return 3; } elseif ($position > 0.9) { // 后10% return 2; } elseif ($position < 0.2) { // 前10%-20% return 1; } return 0; } /** * 备用摘要生成方法 */ private function generate_fallback_summary($text, $max_length) { // 简单截取前N个字符,确保在完整单词处结束 if (strlen($text) <= $max_length) { return $text; } $truncated = substr($text, 0, $max_length); $last_space = strrpos($truncated, ' '); if ($last_space !== false) { $truncated = substr($truncated, 0, $last_space); } return $truncated . '...'; } } 3.2 创建主控制器类 在includes/目录下创建class-main-controller.php: <?php namespace SmartExcerpt; class MainController { private $summary_generator; public function __construct() { $this->summary_generator = new SummaryGenerator(); } public function run() { // 初始化钩子 $this->init_hooks(); } private function init_hooks() { // 替换默认的摘要生成函数 add_filter('get_the_excerpt', [$this, 'filter_the_excerpt'], 10, 2); // 添加快捷码支持 add_shortcode('smart_excerpt', [$this, 'shortcode_smart_excerpt']); // 添加RSS摘要支持 add_filter('the_excerpt_rss', [$this, 'filter_rss_excerpt']); // 添加管理菜单 add_action('admin_menu', [$this, 'add_admin_menu']); // 添加设置链接 add_filter('plugin_action_links_' . plugin_basename(SEG_PLUGIN_DIR . 'smart-excerpt-generator.php'), [$this, 'add_settings_link']); } /** * 过滤文章摘要 */ public function filter_the_excerpt($excerpt, $post) { // 如果已经有手动设置的摘要,直接返回 if (!empty($excerpt)) { return $excerpt; } // 获取文章内容 $content = $post->post_content; // 获取摘要长度设置 $length = get_option('seg_excerpt_length', 150); // 生成智能摘要 $smart_excerpt = $this->summary_generator->generate($content, $length); // 添加"阅读更多"链接 $more_text = get_option('seg_excerpt_more', '... 阅读更多'); $smart_excerpt .= ' <a href="' . get_permalink($post->ID) . '" class="read-more">' . $more_text . '</a>'; return $smart_excerpt; } /** * 快捷码函数 */ public function shortcode_smart_excerpt($atts) { $atts = shortcode_atts([ 'length' => 150, 'post_id' => null, 'more_text' => '...' ], $atts); $post_id = $atts['post_id'] ?: get_the_ID(); $post = get_post($post_id); if (!$post) { return ''; } $excerpt = $this->summary_generator->generate($post->post_content, $atts['length']); if (!empty($atts['more_text'])) { $excerpt .= ' <a href="' . get_permalink($post_id) . '">' . $atts['more_text'] . '</a>'; } return '<div class="smart-excerpt">' . $excerpt . '</div>'; } /** * 过滤RSS摘要 */ public function filter_rss_excerpt($excerpt) { global $post; if (get_option('seg_enable_for_rss', false)) { $length = get_option('seg_rss_excerpt_length', 100); return $this->summary_generator->generate($post->post_content, $length); } return $excerpt; } /** * 添加管理菜单 */ public function add_admin_menu() { add_options_page( 第四部分:创建管理界面与设置选项 4.1 完善主控制器类(续) /** * 添加管理菜单 */ public function add_admin_menu() { add_options_page( '智能摘要设置', '智能摘要', 'manage_options', 'smart-excerpt-settings', [$this, 'render_settings_page'] ); } /** * 添加设置链接到插件列表 */ public function add_settings_link($links) { $settings_link = '<a href="' . admin_url('options-general.php?page=smart-excerpt-settings') . '">设置</a>'; array_unshift($links, $settings_link); return $links; } /** * 渲染设置页面 */ public function render_settings_page() { // 检查用户权限 if (!current_user_can('manage_options')) { wp_die('您没有权限访问此页面'); } // 保存设置 if (isset($_POST['submit_settings']) && check_admin_referer('seg_settings_nonce')) { $this->save_settings(); echo '<div class="notice notice-success"><p>设置已保存!</p></div>'; } // 加载设置页面模板 include SEG_PLUGIN_DIR . 'templates/admin-settings.php'; } /** * 保存设置 */ private function save_settings() { // 摘要长度 if (isset($_POST['excerpt_length'])) { $length = intval($_POST['excerpt_length']); if ($length > 0) { update_option('seg_excerpt_length', $length); } } // "阅读更多"文本 if (isset($_POST['excerpt_more'])) { update_option('seg_excerpt_more', sanitize_text_field($_POST['excerpt_more'])); } // 是否启用RSS摘要 $enable_rss = isset($_POST['enable_rss']) ? 1 : 0; update_option('seg_enable_for_rss', $enable_rss); // RSS摘要长度 if (isset($_POST['rss_excerpt_length'])) { $rss_length = intval($_POST['rss_excerpt_length']); if ($rss_length > 0) { update_option('seg_rss_excerpt_length', $rss_length); } } // 是否启用缓存 $enable_cache = isset($_POST['enable_cache']) ? 1 : 0; update_option('seg_enable_cache', $enable_cache); // 缓存过期时间 if (isset($_POST['cache_expiration'])) { $expiration = intval($_POST['cache_expiration']); if ($expiration > 0) { update_option('seg_cache_expiration', $expiration); } } // 是否在文章编辑页面显示摘要预览 $show_preview = isset($_POST['show_preview']) ? 1 : 0; update_option('seg_show_preview', $show_preview); } } 4.2 创建管理设置页面模板 在templates/目录下创建admin-settings.php: <div class="wrap"> <h1>智能摘要生成器设置</h1> <form method="post" action=""> <?php wp_nonce_field('seg_settings_nonce'); ?> <table class="form-table"> <tr> <th scope="row"> <label for="excerpt_length">摘要长度(字符数)</label> </th> <td> <input type="number" id="excerpt_length" name="excerpt_length" value="<?php echo esc_attr(get_option('seg_excerpt_length', 150)); ?>" min="50" max="500" step="10"> <p class="description">摘要的最大字符长度,建议值:150-200</p> </td> </tr> <tr> <th scope="row"> <label for="excerpt_more">"阅读更多"文本</label> </th> <td> <input type="text" id="excerpt_more" name="excerpt_more" value="<?php echo esc_attr(get_option('seg_excerpt_more', '... 阅读更多')); ?>" class="regular-text"> <p class="description">摘要末尾显示的"阅读更多"链接文本</p> </td> </tr> <tr> <th scope="row">RSS摘要设置</th> <td> <label> <input type="checkbox" id="enable_rss" name="enable_rss" value="1" <?php checked(get_option('seg_enable_for_rss', false)); ?>> 为RSS订阅启用智能摘要 </label> <div style="margin-top: 10px;"> <label for="rss_excerpt_length">RSS摘要长度:</label> <input type="number" id="rss_excerpt_length" name="rss_excerpt_length" value="<?php echo esc_attr(get_option('seg_rss_excerpt_length', 100)); ?>" min="50" max="300" step="10"> </div> </td> </tr> <tr> <th scope="row">缓存设置</th> <td> <label> <input type="checkbox" id="enable_cache" name="enable_cache" value="1" <?php checked(get_option('seg_enable_cache', true)); ?>> 启用摘要缓存 </label> <div style="margin-top: 10px;"> <label for="cache_expiration">缓存过期时间(小时):</label> <input type="number" id="cache_expiration" name="cache_expiration" value="<?php echo esc_attr(get_option('seg_cache_expiration', 24)); ?>" min="1" max="720" step="1"> <p class="description">缓存摘要结果以提高性能</p> </div> </td> </tr> <tr> <th scope="row">文章编辑页面</th> <td> <label> <input type="checkbox" id="show_preview" name="show_preview" value="1" <?php checked(get_option('seg_show_preview', true)); ?>> 显示摘要预览 </label> <p class="description">在文章编辑页面显示自动生成的摘要预览</p> </td> </tr> <tr> <th scope="row">摘要生成测试</th> <td> <button type="button" id="test_summary" class="button">测试摘要生成</button> <div id="test_result" style="margin-top: 10px; padding: 10px; background: #f5f5f5; display: none;"></div> </td> </tr> </table> <p class="submit"> <input type="submit" name="submit_settings" class="button-primary" value="保存设置"> <button type="button" id="clear_cache" class="button">清除缓存</button> </p> </form> <div class="card"> <h2>使用说明</h2> <p>1. 智能摘要会自动替换WordPress默认的摘要生成功能</p> <p>2. 如果文章已有手动设置的摘要,将优先使用手动摘要</p> <p>3. 在文章中使用短码:[smart_excerpt length="200" more_text="查看更多"]</p> <p>4. 可以通过过滤器自定义摘要生成行为,详见插件文档</p> </div> </div> <script> jQuery(document).ready(function($) { // 测试摘要生成 $('#test_summary').on('click', function() { var testText = "人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器。该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大。可以设想,未来人工智能带来的科技产品,将会是人类智慧的容器。人工智能可以对人的意识、思维的信息过程的模拟。人工智能不是人的智能,但能像人那样思考,也可能超过人的智能。"; $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'seg_test_summary', text: testText, length: $('#excerpt_length').val() }, success: function(response) { $('#test_result').html('<strong>测试结果:</strong><br>' + response).show(); } }); }); // 清除缓存 $('#clear_cache').on('click', function(e) { e.preventDefault(); if (confirm('确定要清除所有摘要缓存吗?')) { $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'seg_clear_cache' }, success: function(response) { alert(response.message); } }); } }); }); </script> 4.3 添加AJAX处理功能 在主控制器类中添加AJAX处理方法: // 在MainController类的init_hooks方法中添加: add_action('wp_ajax_seg_test_summary', [$this, 'ajax_test_summary']); add_action('wp_ajax_seg_clear_cache', [$this, 'ajax_clear_cache']); // 添加文章编辑页面摘要预览 add_action('add_meta_boxes', [$this, 'add_excerpt_preview_metabox']); // 在MainController类中添加新方法: /** * AJAX测试摘要生成 */ public function ajax_test_summary() { if (!current_user_can('manage_options')) { wp_die('权限不足'); } $text = isset($_POST['text']) ? $_POST['text'] : ''; $length = isset($_POST['length']) ? intval($_POST['length']) : 150; if (empty($text)) { wp_die('请输入测试文本'); } $summary = $this->summary_generator->generate($text, $length); echo wp_kses_post($summary); wp_die(); } /** * AJAX清除缓存 */ public function ajax_clear_cache() { if (!current_user_can('manage_options')) { wp_send_json_error(['message' => '权限不足']); } global $wpdb; // 删除所有智能摘要缓存 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_seg_summary_%' ) ); $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_timeout_seg_summary_%' ) ); wp_send_json_success(['message' => '缓存已清除']); } /** * 添加摘要预览元框 */ public function add_excerpt_preview_metabox() { if (get_option('seg_show_preview', true)) { add_meta_box( 'seg_excerpt_preview', '智能摘要预览', [$this, 'render_excerpt_preview_metabox'], 'post', 'side', 'high' ); } } /** * 渲染摘要预览元框 */ public function render_excerpt_preview_metabox($post) { $content = $post->post_content; $length = get_option('seg_excerpt_length', 150); echo '<div id="seg_preview_container">'; echo '<p>自动生成的摘要预览:</p>'; echo '<div id="seg_preview_content" style="background: #f5f5f5; padding: 10px; border-radius: 3px; min-height: 100px;">'; if (!empty($content)) { $preview = $this->summary_generator->generate($content, $length); echo wp_kses_post($preview); } else { echo '<em>请输入文章内容以查看摘要预览</em>'; } echo '</div>'; echo '<button type="button" id="seg_refresh_preview" class="button button-small" style="margin-top: 10px;">刷新预览</button>'; echo '</div>'; // 添加JavaScript ?> <script> jQuery(document).ready(function($) { // 刷新预览 $('#seg_refresh_preview').on('click', function() { var content = $('#content').val(); var length = <?php echo $length; ?>; $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'seg_update_preview', content: content, length: length, post_id: <?php echo $post->ID; ?>, nonce: '<?php echo wp_create_nonce('seg_preview_nonce'); ?>' }, success: function(response) { if (response.success) { $('#seg_preview_content').html(response.data); } } }); }); // 自动刷新预览(每30秒) setInterval(function() { if ($('#content').is(':focus')) { $('#seg_refresh_preview').click(); } }, 30000); }); </script> <?php } // 添加预览更新AJAX处理 add_action('wp_ajax_seg_update_preview', [$this, 'ajax_update_preview']); /** * AJAX更新预览 */ public function ajax_update_preview() { check_ajax_referer('seg_preview_nonce', 'nonce'); if (!current_user_can('edit_posts')) { wp_send_json_error('权限不足'); } $content = isset($_POST['content']) ? $_POST['content'] : ''; $length = isset($_POST['length']) ? intval($_POST['length']) : 150; if (empty($content)) { wp_send_json_success('<em>请输入文章内容以查看摘要预览</em>'); } $summary = $this->summary_generator->generate($content, $length); wp_send_json_success(wp_kses_post($summary)); } 第五部分:高级功能与优化 5.1 添加自定义过滤器 为了让其他开发者可以自定义摘要生成行为,我们添加一些过滤器: // 在SummaryGenerator类的generate方法开始处添加: $content = apply_filters('seg_before_generate', $content, $max_length); // 在生成摘要后添加: $summary = apply_filters('seg_after_generate', $summary, $content, $max_length); // 在MainController类的filter_the_excerpt方法中添加: $smart_excerpt = apply_filters('seg_custom_excerpt', $smart_excerpt, $post, $length); // 创建新的过滤器类 // 在includes/目录下创建class-custom-filters.php: <?php namespace SmartExcerpt; class CustomFilters { /** * 示例过滤器:为特定分类的文章添加前缀 */ public static function add_category_prefix($excerpt, $post, $length) { $categories = get_the_category($post->ID); if (!empty($categories)) { $category_names = array_map(function($cat) { return $cat->name; }, $categories); $prefix = '【' . implode('、', $category_names) . '】'; $excerpt = $prefix . ' ' . $excerpt; } return $excerpt; } /** * 示例过滤器:移除摘要中的短链接 */ public static function remove_short_urls($summary, $content, $max_length) { // 移除类似bit.ly、t.cn等短链接 $summary = preg_replace('/b(https?://)?(bit.ly|t.cn|goo.gl|tinyurl.com)/S+/i', '', $summary); return trim($summary); } /** * 示例过滤器:为长摘要添加分页 */ public static function add_pagination($excerpt, $post) { $full_content = $post->post_content; $excerpt_length = strlen($excerpt); $full_length = strlen(strip_tags($full_content)); if ($full_length > $excerpt_length * 2) { $excerpt .= ' <span class="summary-pagination">[1/2]</span>'; } return $excerpt; } } // 在MainController类的init_hooks方法中添加过滤器示例: add_filter('seg_custom_excerpt', ['SmartExcerptCustomFilters', 'add_category_prefix'], 10, 3); add_filter('seg_after_generate', ['SmartExcerptCustomFilters', 'remove_short_urls'], 10, 3); 5.2 添加性能优化功能 // 在includes/目录下创建class-performance-optimizer.php: <?php namespace SmartExcerpt; class PerformanceOptimizer { /** * 批量生成摘要缓存 */ public static function batch_generate_cache($post_ids = []) { if (empty($post_ids)) { // 获取最近100篇文章 $args = [ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 100, 'fields' => 'ids' ]; $query = new WP_Query($args); $post_ids = $query->posts; } $summary_generator = new SummaryGenerator(); $results = [ 'total' => count($post_ids),

发表评论

WordPress 插件开发教程,集成网站实时股票行情与财经数据工具

WordPress插件开发教程:集成实时股票行情与财经数据工具 引言:为什么需要自定义财经数据插件 在当今数字化时代,财经数据的实时获取和展示对于许多网站来说至关重要。无论是金融博客、投资社区还是企业官网,实时股票行情和财经数据都能显著提升网站的专业性和用户粘性。虽然市场上有一些现成的财经插件,但它们往往功能单一、定制性差,或者存在样式冲突、加载缓慢等问题。 通过自主开发WordPress插件,我们可以完全掌控数据展示方式、更新频率和用户界面,同时确保与网站主题完美兼容。本教程将引导您从零开始创建一个功能全面的实时股票行情与财经数据工具,并探讨如何通过WordPress代码二次开发实现常用互联网小工具功能。 第一章:WordPress插件开发基础 1.1 插件开发环境搭建 在开始开发之前,我们需要准备合适的开发环境: 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel搭建本地WordPress环境 代码编辑器:Visual Studio Code、PHPStorm或Sublime Text 浏览器开发者工具:用于调试前端代码 版本控制系统:Git用于代码版本管理 1.2 创建插件基本结构 每个WordPress插件都需要一个主文件,通常以插件名称命名。让我们创建插件的初始结构: wp-content/plugins/stock-finance-tool/ │ ├── stock-finance-tool.php # 主插件文件 ├── includes/ # 包含核心功能文件 │ ├── class-stock-api.php # 股票API处理类 │ ├── class-data-cache.php # 数据缓存类 │ └── class-shortcodes.php # 短代码处理类 ├── admin/ # 后台管理文件 │ ├── css/ # 后台样式 │ ├── js/ # 后台脚本 │ └── class-admin-settings.php # 设置页面类 ├── public/ # 前端文件 │ ├── css/ # 前端样式 │ ├── js/ # 前端脚本 │ └── templates/ # 前端模板 ├── assets/ # 静态资源 │ └── images/ # 图片资源 └── uninstall.php # 插件卸载脚本 1.3 插件主文件配置 首先,我们需要在插件主文件中添加标准的WordPress插件头部信息: <?php /** * Plugin Name: 实时股票行情与财经数据工具 * Plugin URI: https://yourwebsite.com/stock-plugin * Description: 在WordPress网站中集成实时股票行情、财经数据和小工具功能 * Version: 1.0.0 * Author: 您的名称 * Author URI: https://yourwebsite.com * License: GPL v2 or later * Text Domain: stock-finance-tool * Domain Path: /languages */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SFT_VERSION', '1.0.0'); define('SFT_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SFT_PLUGIN_URL', plugin_dir_url(__FILE__)); define('SFT_PLUGIN_BASENAME', plugin_basename(__FILE__)); // 插件初始化 add_action('plugins_loaded', 'sft_init_plugin'); function sft_init_plugin() { // 加载文本域用于国际化 load_plugin_textdomain('stock-finance-tool', false, dirname(SFT_PLUGIN_BASENAME) . '/languages'); // 检查依赖 if (!function_exists('curl_init')) { add_action('admin_notices', function() { echo '<div class="notice notice-error"><p>'; echo esc_html__('实时股票行情插件需要cURL扩展支持,请联系您的主机提供商启用cURL。', 'stock-finance-tool'); echo '</p></div>'; }); return; } // 引入核心类文件 require_once SFT_PLUGIN_DIR . 'includes/class-stock-api.php'; require_once SFT_PLUGIN_DIR . 'includes/class-data-cache.php'; require_once SFT_PLUGIN_DIR . 'includes/class-shortcodes.php'; // 初始化核心类 $stock_api = new SFT_Stock_API(); $data_cache = new SFT_Data_Cache(); $shortcodes = new SFT_Shortcodes(); // 根据环境加载管理或前端功能 if (is_admin()) { require_once SFT_PLUGIN_DIR . 'admin/class-admin-settings.php'; new SFT_Admin_Settings(); } else { // 前端初始化 add_action('wp_enqueue_scripts', 'sft_enqueue_frontend_assets'); } } // 注册激活和停用钩子 register_activation_hook(__FILE__, 'sft_activate_plugin'); register_deactivation_hook(__FILE__, 'sft_deactivate_plugin'); function sft_activate_plugin() { // 创建必要的数据库表 global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'sft_stock_data'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, symbol varchar(20) NOT NULL, data longtext NOT NULL, last_updated datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY (id), UNIQUE KEY symbol (symbol) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 设置默认选项 add_option('sft_api_provider', 'alpha_vantage'); add_option('sft_cache_duration', 300); add_option('sft_default_symbols', 'AAPL,MSFT,GOOGL,AMZN,TSLA'); add_option('sft_currency', 'USD'); } function sft_deactivate_plugin() { // 清理定时任务 wp_clear_scheduled_hook('sft_daily_data_update'); } // 前端资源加载 function sft_enqueue_frontend_assets() { wp_enqueue_style( 'sft-frontend-style', SFT_PLUGIN_URL . 'public/css/frontend.css', array(), SFT_VERSION ); wp_enqueue_script( 'sft-frontend-script', SFT_PLUGIN_URL . 'public/js/frontend.js', array('jquery'), SFT_VERSION, true ); // 本地化脚本,传递数据到JavaScript wp_localize_script('sft-frontend-script', 'sft_ajax_object', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('sft_nonce') )); } 第二章:集成股票行情API 2.1 选择合适的财经数据API 市场上有多种财经数据API可供选择,每种都有其优缺点: Alpha Vantage:免费层提供较全面的数据,但调用频率有限制 Yahoo Finance (非官方API):免费但稳定性存疑 Twelve Data:提供免费和付费方案,数据质量较好 Finnhub:免费层提供实时数据,但功能有限 国内选择:新浪财经、腾讯财经等提供免费接口 本教程将以Alpha Vantage为例,但代码设计将支持多种API提供商。 2.2 创建API处理类 <?php // includes/class-stock-api.php class SFT_Stock_API { private $api_key; private $api_provider; private $cache_duration; public function __construct() { $this->api_key = get_option('sft_api_key', ''); $this->api_provider = get_option('sft_api_provider', 'alpha_vantage'); $this->cache_duration = get_option('sft_cache_duration', 300); // 注册AJAX处理 add_action('wp_ajax_sft_get_stock_data', array($this, 'ajax_get_stock_data')); add_action('wp_ajax_nopriv_sft_get_stock_data', array($this, 'ajax_get_stock_data')); } /** * 获取股票数据 */ public function get_stock_data($symbol, $force_refresh = false) { // 检查缓存 if (!$force_refresh) { $cached_data = $this->get_cached_data($symbol); if ($cached_data !== false) { return $cached_data; } } // 根据选择的API提供商调用相应方法 switch ($this->api_provider) { case 'alpha_vantage': $data = $this->get_alpha_vantage_data($symbol); break; case 'twelve_data': $data = $this->get_twelve_data_data($symbol); break; case 'yahoo': $data = $this->get_yahoo_data($symbol); break; default: $data = array('error' => '不支持的API提供商'); } // 缓存数据 if (!isset($data['error'])) { $this->cache_data($symbol, $data); } return $data; } /** * Alpha Vantage API调用 */ private function get_alpha_vantage_data($symbol) { if (empty($this->api_key)) { return array('error' => 'API密钥未配置'); } $url = add_query_arg(array( 'function' => 'GLOBAL_QUOTE', 'symbol' => $symbol, 'apikey' => $this->api_key ), 'https://www.alphavantage.co/query'); $response = wp_remote_get($url, array( 'timeout' => 15, 'user-agent' => 'WordPress Stock Finance Tool/' . SFT_VERSION )); if (is_wp_error($response)) { return array('error' => $response->get_error_message()); } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (isset($data['Global Quote']) && !empty($data['Global Quote'])) { $quote = $data['Global Quote']; return array( 'symbol' => $symbol, 'price' => $quote['05. price'], 'change' => $quote['09. change'], 'change_percent' => $quote['10. change percent'], 'volume' => $quote['06. volume'], 'latest_trading_day' => $quote['07. latest trading day'], 'previous_close' => $quote['08. previous close'], 'open' => $quote['02. open'], 'high' => $quote['03. high'], 'low' => $quote['04. low'] ); } elseif (isset($data['Error Message'])) { return array('error' => $data['Error Message']); } else { return array('error' => '无法解析API响应'); } } /** * 获取缓存数据 */ private function get_cached_data($symbol) { global $wpdb; $table_name = $wpdb->prefix . 'sft_stock_data'; $result = $wpdb->get_row($wpdb->prepare( "SELECT data, last_updated FROM $table_name WHERE symbol = %s", $symbol )); if ($result) { $last_updated = strtotime($result->last_updated); $current_time = current_time('timestamp'); // 检查缓存是否过期 if (($current_time - $last_updated) < $this->cache_duration) { return unserialize($result->data); } } return false; } /** * 缓存数据 */ private function cache_data($symbol, $data) { global $wpdb; $table_name = $wpdb->prefix . 'sft_stock_data'; $wpdb->replace( $table_name, array( 'symbol' => $symbol, 'data' => serialize($data), 'last_updated' => current_time('mysql') ), array('%s', '%s', '%s') ); } /** * AJAX获取股票数据 */ public function ajax_get_stock_data() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'sft_nonce')) { wp_die('安全验证失败'); } $symbol = sanitize_text_field($_POST['symbol']); $data = $this->get_stock_data($symbol); wp_send_json($data); } /** * 批量获取多个股票数据 */ public function get_multiple_stocks($symbols) { $results = array(); $symbol_array = explode(',', $symbols); foreach ($symbol_array as $symbol) { $symbol = trim($symbol); if (!empty($symbol)) { $results[$symbol] = $this->get_stock_data($symbol); } } return $results; } /** * 获取历史数据 */ public function get_historical_data($symbol, $interval = 'daily', $output_size = 'compact') { if (empty($this->api_key)) { return array('error' => 'API密钥未配置'); } $url = add_query_arg(array( 'function' => 'TIME_SERIES_' . strtoupper($interval), 'symbol' => $symbol, 'outputsize' => $output_size, 'apikey' => $this->api_key ), 'https://www.alphavantage.co/query'); $response = wp_remote_get($url); if (is_wp_error($response)) { return array('error' => $response->get_error_message()); } $body = wp_remote_retrieve_body($response); return json_decode($body, true); } } 第三章:创建数据缓存系统 3.1 设计高效缓存机制 为了提高插件性能并减少API调用次数,我们需要实现一个高效的缓存系统: <?php // includes/class-data-cache.php class SFT_Data_Cache { private $cache_group = 'sft_stock_data'; public function __construct() { // 注册清理过期缓存的定时任务 add_action('sft_clean_expired_cache', array($this, 'clean_expired_cache')); if (!wp_next_scheduled('sft_clean_expired_cache')) { wp_schedule_event(time(), 'daily', 'sft_clean_expired_cache'); } } /** * 设置缓存 */ public function set($key, $data, $expiration = 300) { $cache_data = array( 'data' => $data, 'expires' => time() + $expiration ); return wp_cache_set($key, $cache_data, $this->cache_group, $expiration); } /** * 获取缓存 */ public function get($key) { $cached = wp_cache_get($key, $this->cache_group); if ($cached === false) { return false; } // 检查是否过期 if (isset($cached['expires']) && $cached['expires'] < time()) { wp_cache_delete($key, $this->cache_group); return false; } return $cached['data']; } /** * 删除缓存 */ public function delete($key) { return wp_cache_delete($key, $this->cache_group); } /** * 清理所有插件缓存 */ public function flush() { return wp_cache_flush(); } /** * 清理过期缓存 */ public function clean_expired_cache() { global $wpdb; // 清理数据库缓存 $table_name = $wpdb->prefix . 'sft_stock_data'; $expiration_time = date('Y-m-d H:i:s', time() - 86400); // 24小时前的数据 $wpdb->query($wpdb->prepare( "DELETE FROM $table_name WHERE last_updated < %s", $expiration_time )); // 清理对象缓存中的旧数据 // 注意:wp_cache_flush()会清理所有缓存,可能影响其他插件 // 在生产环境中需要更精细的缓存管理 } /** * 获取缓存统计信息 */ public function get_stats() { global $wpdb; $table_name = $wpdb->prefix . 'sft_stock_data'; $total = $wpdb->get_var("SELECT COUNT(*) FROM $table_name"); $oldest = $wpdb->get_var("SELECT MIN(last_updated) FROM $table_name"); $newest = $wpdb->get_var("SELECT MAX(last_updated) FROM $table_name"); return array( 'total_cached' => $total, 'oldest_cache' => $oldest, 'newest_cache' => $newest ); } } 第四章:实现短代码功能 4.1 创建基本短代码 短代码是WordPress中非常强大的功能,允许用户在文章和页面中轻松插入动态内容: <?php // includes/class-shortcodes.php class SFT_Shortcodes { private $api; public function __construct() { $this->api = new SFT_Stock_API(); 4.1 创建基本短代码(续) // 注册短代码 add_shortcode('stock_quote', array($this, 'stock_quote_shortcode')); add_shortcode('stock_ticker', array($this, 'stock_ticker_shortcode')); add_shortcode('financial_data', array($this, 'financial_data_shortcode')); add_shortcode('currency_converter', array($this, 'currency_converter_shortcode')); add_shortcode('crypto_price', array($this, 'crypto_price_shortcode')); // 注册Gutenberg块 add_action('init', array($this, 'register_gutenberg_blocks')); } /** * 股票报价短代码 */ public function stock_quote_shortcode($atts) { $atts = shortcode_atts(array( 'symbol' => 'AAPL', 'show_change' => 'true', 'show_volume' => 'false', 'refresh' => '60', 'style' => 'default', 'currency' => 'USD' ), $atts, 'stock_quote'); // 获取股票数据 $data = $this->api->get_stock_data($atts['symbol']); if (isset($data['error'])) { return '<div class="sft-error">错误: ' . esc_html($data['error']) . '</div>'; } // 生成唯一ID用于JavaScript $widget_id = 'sft-stock-' . uniqid(); // 格式化输出 ob_start(); ?> <div id="<?php echo esc_attr($widget_id); ?>" class="sft-stock-quote sft-style-<?php echo esc_attr($atts['style']); ?>" data-symbol="<?php echo esc_attr($atts['symbol']); ?>" data-refresh="<?php echo esc_attr($atts['refresh']); ?>"> <div class="sft-stock-header"> <h4 class="sft-stock-symbol"><?php echo esc_html($atts['symbol']); ?></h4> <span class="sft-stock-price"><?php echo esc_html($this->format_currency($data['price'], $atts['currency'])); ?></span> </div> <?php if ($atts['show_change'] === 'true') : ?> <div class="sft-stock-change <?php echo (floatval($data['change']) >= 0) ? 'sft-positive' : 'sft-negative'; ?>"> <span class="sft-change-amount"><?php echo esc_html($data['change']); ?></span> <span class="sft-change-percent">(<?php echo esc_html($data['change_percent']); ?>)</span> </div> <?php endif; ?> <?php if ($atts['show_volume'] === 'true') : ?> <div class="sft-stock-volume"> 成交量: <?php echo esc_html($this->format_number($data['volume'])); ?> </div> <?php endif; ?> <div class="sft-stock-footer"> <span class="sft-updated-time">更新于: <?php echo esc_html(date('H:i:s')); ?></span> </div> </div> <script> jQuery(document).ready(function($) { // 自动刷新功能 var refreshInterval = <?php echo intval($atts['refresh']) * 1000; ?>; if (refreshInterval > 0) { setInterval(function() { $.ajax({ url: sft_ajax_object.ajax_url, type: 'POST', data: { action: 'sft_get_stock_data', symbol: '<?php echo esc_js($atts['symbol']); ?>', nonce: sft_ajax_object.nonce }, success: function(response) { if (!response.error) { $('#<?php echo esc_js($widget_id); ?> .sft-stock-price').text(response.price); $('#<?php echo esc_js($widget_id); ?> .sft-change-amount').text(response.change); $('#<?php echo esc_js($widget_id); ?> .sft-change-percent').text('(' + response.change_percent + ')'); $('#<?php echo esc_js($widget_id); ?> .sft-stock-volume').text('成交量: ' + response.volume); $('#<?php echo esc_js($widget_id); ?> .sft-updated-time').text('更新于: ' + new Date().toLocaleTimeString()); // 更新颜色 var changeClass = parseFloat(response.change) >= 0 ? 'sft-positive' : 'sft-negative'; $('#<?php echo esc_js($widget_id); ?> .sft-stock-change') .removeClass('sft-positive sft-negative') .addClass(changeClass); } } }); }, refreshInterval); } }); </script> <?php return ob_get_clean(); } /** * 股票行情滚动条短代码 */ public function stock_ticker_shortcode($atts) { $atts = shortcode_atts(array( 'symbols' => 'AAPL,MSFT,GOOGL,AMZN,TSLA', 'speed' => '50', 'direction' => 'left', 'separator' => '|', 'show_change' => 'true' ), $atts, 'stock_ticker'); $symbols = array_map('trim', explode(',', $atts['symbols'])); $data = $this->api->get_multiple_stocks($atts['symbols']); ob_start(); ?> <div class="sft-stock-ticker" data-speed="<?php echo esc_attr($atts['speed']); ?>" data-direction="<?php echo esc_attr($atts['direction']); ?>"> <div class="sft-ticker-container"> <div class="sft-ticker-content"> <?php foreach ($symbols as $symbol) : if (isset($data[$symbol]) && !isset($data[$symbol]['error'])) : $stock = $data[$symbol]; ?> <div class="sft-ticker-item"> <span class="sft-ticker-symbol"><?php echo esc_html($symbol); ?></span> <span class="sft-ticker-price"><?php echo esc_html($stock['price']); ?></span> <?php if ($atts['show_change'] === 'true') : ?> <span class="sft-ticker-change <?php echo (floatval($stock['change']) >= 0) ? 'sft-positive' : 'sft-negative'; ?>"> <?php echo esc_html($stock['change']); ?> (<?php echo esc_html($stock['change_percent']); ?>) </span> <?php endif; ?> <span class="sft-ticker-separator"><?php echo esc_html($atts['separator']); ?></span> </div> <?php endif; endforeach; ?> </div> </div> </div> <style> .sft-stock-ticker { overflow: hidden; white-space: nowrap; background: #f8f9fa; padding: 10px; border-radius: 4px; } .sft-ticker-container { display: inline-block; animation: sft-ticker-<?php echo esc_attr($atts['direction']); ?> <?php echo esc_attr(100000/$atts['speed']); ?>s linear infinite; } @keyframes sft-ticker-left { 0% { transform: translateX(100%); } 100% { transform: translateX(-100%); } } @keyframes sft-ticker-right { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } </style> <?php return ob_get_clean(); } /** * 货币转换器短代码 */ public function currency_converter_shortcode($atts) { $atts = shortcode_atts(array( 'from' => 'USD', 'to' => 'EUR', 'amount' => '1', 'show_form' => 'true', 'update_frequency' => '3600' ), $atts, 'currency_converter'); $conversion_rate = $this->get_currency_rate($atts['from'], $atts['to']); $converted_amount = floatval($atts['amount']) * $conversion_rate; ob_start(); ?> <div class="sft-currency-converter" data-from="<?php echo esc_attr($atts['from']); ?>" data-to="<?php echo esc_attr($atts['to']); ?>"> <div class="sft-conversion-result"> <p> <?php echo esc_html($atts['amount']); ?> <?php echo esc_html($atts['from']); ?> = <strong><?php echo number_format($converted_amount, 4); ?></strong> <?php echo esc_html($atts['to']); ?> </p> <p class="sft-rate-info"> 汇率: 1 <?php echo esc_html($atts['from']); ?> = <?php echo number_format($conversion_rate, 6); ?> <?php echo esc_html($atts['to']); ?> </p> </div> <?php if ($atts['show_form'] === 'true') : ?> <div class="sft-converter-form"> <div class="sft-form-row"> <input type="number" class="sft-amount" value="<?php echo esc_attr($atts['amount']); ?>" step="0.01" min="0" placeholder="金额"> <select class="sft-from-currency"> <?php echo $this->get_currency_options($atts['from']); ?> </select> <span class="sft-convert-arrow">→</span> <select class="sft-to-currency"> <?php echo $this->get_currency_options($atts['to']); ?> </select> <button type="button" class="sft-convert-button">转换</button> </div> <div class="sft-form-result" style="display:none;"> <p class="sft-result-text"></p> </div> </div> <script> jQuery(document).ready(function($) { $('.sft-convert-button').on('click', function() { var $converter = $(this).closest('.sft-currency-converter'); var amount = $converter.find('.sft-amount').val(); var from = $converter.find('.sft-from-currency').val(); var to = $converter.find('.sft-to-currency').val(); $.ajax({ url: sft_ajax_object.ajax_url, type: 'POST', data: { action: 'sft_convert_currency', amount: amount, from: from, to: to, nonce: sft_ajax_object.nonce }, success: function(response) { if (response.success) { $converter.find('.sft-result-text').html( amount + ' ' + from + ' = <strong>' + response.result + '</strong> ' + to ); $converter.find('.sft-form-result').show(); } } }); }); }); </script> <?php endif; ?> </div> <?php return ob_get_clean(); } /** * 获取货币汇率 */ private function get_currency_rate($from, $to) { $cache_key = 'currency_rate_' . $from . '_' . $to; $cached_rate = get_transient($cache_key); if ($cached_rate !== false) { return $cached_rate; } // 使用免费汇率API $api_url = "https://api.exchangerate-api.com/v4/latest/{$from}"; $response = wp_remote_get($api_url); if (!is_wp_error($response)) { $data = json_decode(wp_remote_retrieve_body($response), true); if (isset($data['rates'][$to])) { $rate = $data['rates'][$to]; set_transient($cache_key, $rate, HOUR_IN_SECONDS); return $rate; } } // 备用汇率(如果API失败) $fallback_rates = array( 'USD_EUR' => 0.85, 'EUR_USD' => 1.18, 'USD_GBP' => 0.73, 'GBP_USD' => 1.37, 'USD_JPY' => 110.5, 'JPY_USD' => 0.0091 ); $key = $from . '_' . $to; return isset($fallback_rates[$key]) ? $fallback_rates[$key] : 1.0; } /** * 获取货币选项 */ private function get_currency_options($selected) { $currencies = array( 'USD' => '美元', 'EUR' => '欧元', 'GBP' => '英镑', 'JPY' => '日元', 'CNY' => '人民币', 'CAD' => '加元', 'AUD' => '澳元', 'CHF' => '瑞士法郎', 'HKD' => '港币' ); $options = ''; foreach ($currencies as $code => $name) { $selected_attr = ($code === $selected) ? ' selected' : ''; $options .= "<option value="{$code}"{$selected_attr}>{$name} ({$code})</option>"; } return $options; } /** * 格式化货币 */ private function format_currency($amount, $currency) { $formats = array( 'USD' => '$%s', 'EUR' => '€%s', 'GBP' => '£%s', 'JPY' => '¥%s', 'CNY' => '¥%s' ); $format = isset($formats[$currency]) ? $formats[$currency] : '%s ' . $currency; return sprintf($format, number_format(floatval($amount), 2)); } /** * 格式化数字(千位分隔符) */ private function format_number($number) { return number_format(floatval($number)); } /** * 注册Gutenberg块 */ public function register_gutenberg_blocks() { // 检查Gutenberg是否可用 if (!function_exists('register_block_type')) { return; } // 注册股票报价块 register_block_type('sft/stock-quote', array( 'editor_script' => 'sft-gutenberg-blocks', 'render_callback' => array($this, 'render_stock_quote_block'), 'attributes' => array( 'symbol' => array( 'type' => 'string', 'default' => 'AAPL' ), 'showChange' => array( 'type' => 'boolean', 'default' => true ), 'refreshInterval' => array( 'type' => 'number', 'default' => 60 ) ) )); } /** * 渲染Gutenberg块 */ public function render_stock_quote_block($attributes) { return $this->stock_quote_shortcode(array( 'symbol' => $attributes['symbol'], 'show_change' => $attributes['showChange'] ? 'true' : 'false', 'refresh' => $attributes['refreshInterval'] )); } } 第五章:创建管理后台界面 5.1 设置页面开发 <?php // admin/class-admin-settings.php class SFT_Admin_Settings { private $options_page; public function __construct() { add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_init', array($this, 'register_settings')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); // 添加插件设置链接 add_filter('plugin_action_links_' . SFT_PLUGIN_BASENAME, array($this, 'add_settings_link')); } /** * 添加管理菜单 */ public function add_admin_menu() { $this->options_page = add_menu_page( '股票财经工具设置', '股票财经工具', 'manage_options', 'sft-settings', array($this, 'render_settings_page'), 'dashicons-chart-line', 80 ); // 添加子菜单 add_submenu_page( 'sft-settings', 'API设置', 'API设置', 'manage_options', 'sft-settings', array($this, 'render_settings_page') ); add_submenu_page( 'sft-settings', '小工具管理', '小工具管理', 'manage_options', 'sft-widgets', array($this, 'render_widgets_page') ); add_submenu_page( 'sft-settings', '数据缓存', '数据缓存', 'manage_options', 'sft-cache', array($this, 'render_cache_page') ); add_submenu_page( 'sft-settings', '使用说明', '使用说明', 'manage_options', 'sft-documentation', array($this, 'render_documentation_page') ); } /** * 渲染设置页面 */ public function render_settings_page() { if (!current_user_can('manage_options')) {

发表评论

手把手教程,为WordPress实现自动化内容翻译与本地化适配工具

手把手教程:为WordPress实现自动化内容翻译与本地化适配工具 引言:全球化时代下的WordPress本地化挑战 在当今互联网全球化浪潮中,网站内容的多语言支持已成为吸引国际用户、拓展全球市场的关键因素。根据W3Techs的数据,超过43%的网站使用WordPress作为内容管理系统,其中许多网站面临着内容本地化的迫切需求。然而,传统的人工翻译方式不仅成本高昂、效率低下,而且难以保持内容更新的同步性。 本教程将深入探讨如何通过WordPress代码二次开发,构建一个自动化内容翻译与本地化适配工具。我们将从基础概念入手,逐步实现一个功能完整的解决方案,帮助您的WordPress网站轻松跨越语言障碍,实现真正的全球化内容管理。 第一部分:理解WordPress本地化与翻译机制 1.1 WordPress国际化(i18n)与本地化(l10n)基础 WordPress从设计之初就考虑了国际化需求,其核心采用gettext框架实现文本翻译。理解这一机制是进行二次开发的基础: // WordPress标准翻译函数示例 $text = __('Hello World', 'text-domain'); $text = _e('Hello World', 'text-domain'); // 直接输出 WordPress使用.po和.mo文件存储翻译字符串,但这种方式主要适用于主题和插件的静态文本翻译,对于动态生成的内容支持有限。 1.2 现有翻译插件的局限性 市场上存在多种WordPress翻译插件,如WPML、Polylang等,它们提供了不错的解决方案,但也存在一些限制: 高昂的许可费用 复杂的配置过程 对自定义内容类型的支持有限 自动化程度不足,仍需大量人工干预 难以与第三方翻译API深度集成 通过自主开发,我们可以创建更贴合特定需求、成本更低且高度自动化的解决方案。 第二部分:系统架构设计与技术选型 2.1 整体架构设计 我们的自动化翻译系统将采用模块化设计,主要包括以下组件: 内容捕获模块:监测新发布或更新的内容 翻译处理模块:调用翻译API进行内容转换 本地化适配模块:处理日期、货币、数字格式等本地化元素 存储管理模块:管理多语言内容之间的关系 前端展示模块:根据用户语言偏好显示相应内容 2.2 技术选型与准备 2.2.1 翻译API选择 我们将使用以下翻译API之一(本教程以Google Cloud Translation API为例): Google Cloud Translation API(质量高,支持100+语言) DeepL API(欧洲语言质量优秀) Microsoft Azure Translator 百度翻译API(中文相关翻译效果好) 2.2.2 开发环境要求 WordPress 5.0+ PHP 7.4+ 基本的WordPress插件开发知识 熟悉REST API和AJAX 获取所选翻译API的访问密钥 第三部分:创建基础插件框架 3.1 初始化插件结构 首先创建插件主文件 auto-translate-localize.php: <?php /** * Plugin Name: 自动化翻译与本地化工具 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress提供自动化内容翻译与本地化适配功能 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: auto-translate-localize */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('ATL_VERSION', '1.0.0'); define('ATL_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('ATL_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 require_once ATL_PLUGIN_DIR . 'includes/class-atl-core.php'; function atl_init() { $plugin = new ATL_Core(); $plugin->run(); } add_action('plugins_loaded', 'atl_init'); 3.2 创建核心类 在 includes/class-atl-core.php 中创建核心类: class ATL_Core { private $loader; private $version; public function __construct() { $this->version = ATL_VERSION; $this->load_dependencies(); $this->define_admin_hooks(); $this->define_public_hooks(); } private function load_dependencies() { require_once ATL_PLUGIN_DIR . 'includes/class-atl-loader.php'; require_once ATL_PLUGIN_DIR . 'includes/class-atl-translator.php'; require_once ATL_PLUGIN_DIR . 'includes/class-atl-localizer.php'; require_once ATL_PLUGIN_DIR . 'includes/class-atl-content-manager.php'; require_once ATL_PLUGIN_DIR . 'admin/class-atl-admin.php'; require_once ATL_PLUGIN_DIR . 'public/class-atl-public.php'; $this->loader = new ATL_Loader(); } private function define_admin_hooks() { $admin = new ATL_Admin($this->version); $this->loader->add_action('admin_menu', $admin, 'add_admin_menu'); $this->loader->add_action('admin_init', $admin, 'register_settings'); $this->loader->add_action('save_post', $admin, 'handle_post_save', 10, 3); } private function define_public_hooks() { $public = new ATL_Public($this->version); $this->loader->add_action('wp_enqueue_scripts', $public, 'enqueue_scripts'); $this->loader->add_filter('the_content', $public, 'filter_content_by_language'); $this->loader->add_action('init', $public, 'register_language_switcher'); } public function run() { $this->loader->run(); } } 第四部分:实现内容捕获与翻译模块 4.1 内容捕获机制 我们需要捕获WordPress中的内容更新事件,自动触发翻译流程: class ATL_Content_Manager { private $supported_post_types = ['post', 'page']; private $translation_languages = []; public function __construct() { $this->translation_languages = get_option('atl_target_languages', ['es', 'fr', 'de']); } /** * 处理文章保存事件 */ public function handle_post_save($post_id, $post, $update) { // 避免无限循环 if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) { return; } // 检查是否支持的文章类型 if (!in_array($post->post_type, $this->supported_post_types)) { return; } // 检查是否需要翻译(避免重复翻译) $already_translated = get_post_meta($post_id, '_atl_translated', true); if (!$already_translated || isset($_POST['force_translate'])) { $this->translate_post($post_id, $post); } } /** * 翻译文章内容 */ private function translate_post($post_id, $post) { $translator = new ATL_Translator(); $source_lang = get_option('atl_source_language', 'en'); foreach ($this->translation_languages as $target_lang) { // 翻译标题 $translated_title = $translator->translate_text( $post->post_title, $source_lang, $target_lang ); // 翻译内容 $translated_content = $translator->translate_text( $post->post_content, $source_lang, $target_lang ); // 创建翻译后的文章 $this->create_translated_post( $post_id, $translated_title, $translated_content, $target_lang ); } // 标记原文已翻译 update_post_meta($post_id, '_atl_translated', true); } } 4.2 集成翻译API 创建翻译器类,集成Google Cloud Translation API: class ATL_Translator { private $api_key; private $api_url = 'https://translation.googleapis.com/language/translate/v2'; public function __construct() { $this->api_key = get_option('atl_google_api_key', ''); if (empty($this->api_key)) { error_log('ATL: Google Translation API key not set'); } } /** * 翻译文本 */ public function translate_text($text, $source_lang, $target_lang) { if (empty($text) || $source_lang === $target_lang) { return $text; } // 检查是否有缓存 $cache_key = 'atl_trans_' . md5($text . $source_lang . $target_lang); $cached = get_transient($cache_key); if ($cached !== false) { return $cached; } // 调用API $response = $this->call_translation_api($text, $source_lang, $target_lang); if ($response && isset($response['data']['translations'][0]['translatedText'])) { $translated_text = $response['data']['translations'][0]['translatedText']; // 缓存结果(24小时) set_transient($cache_key, $translated_text, DAY_IN_SECONDS); return $translated_text; } return $text; // 翻译失败,返回原文 } /** * 调用Google翻译API */ private function call_translation_api($text, $source_lang, $target_lang) { $args = [ 'body' => [ 'q' => $text, 'source' => $source_lang, 'target' => $target_lang, 'format' => 'html', 'key' => $this->api_key ], 'timeout' => 30 ]; $response = wp_remote_post($this->api_url, $args); if (is_wp_error($response)) { error_log('ATL Translation API error: ' . $response->get_error_message()); return false; } return json_decode(wp_remote_retrieve_body($response), true); } /** * 批量翻译(提高效率) */ public function translate_batch($texts, $source_lang, $target_lang) { // 实现批量翻译逻辑 // ... } } 第五部分:实现本地化适配模块 5.1 本地化内容适配 翻译不仅仅是文字转换,还包括本地化元素的适配: class ATL_Localizer { /** * 本地化日期格式 */ public function localize_date($date_string, $target_lang) { $date_formats = [ 'en' => 'F j, Y', 'es' => 'j de F de Y', 'fr' => 'j F Y', 'de' => 'j. F Y', 'ja' => 'Y年m月d日', 'zh-CN' => 'Y年m月d日' ]; $format = isset($date_formats[$target_lang]) ? $date_formats[$target_lang] : $date_formats['en']; $timestamp = strtotime($date_string); return date_i18n($format, $timestamp); } /** * 本地化数字格式 */ public function localize_number($number, $target_lang) { $number_formats = [ 'en' => ['decimal' => '.', 'thousands' => ','], 'es' => ['decimal' => ',', 'thousands' => '.'], 'fr' => ['decimal' => ',', 'thousands' => ' '], 'de' => ['decimal' => ',', 'thousands' => '.'], ]; $format = isset($number_formats[$target_lang]) ? $number_formats[$target_lang] : $number_formats['en']; return number_format( $number, 2, $format['decimal'], $format['thousands'] ); } /** * 本地化货币 */ public function localize_currency($amount, $currency, $target_lang) { $currency_formats = [ 'en' => ['symbol' => '$', 'position' => 'before'], 'es' => ['symbol' => '€', 'position' => 'after'], 'fr' => ['symbol' => '€', 'position' => 'after'], 'de' => ['symbol' => '€', 'position' => 'after'], 'ja' => ['symbol' => '¥', 'position' => 'before'], 'zh-CN' => ['symbol' => '¥', 'position' => 'before'] ]; $format = isset($currency_formats[$target_lang]) ? $currency_formats[$target_lang] : $currency_formats['en']; $localized_amount = $this->localize_number($amount, $target_lang); if ($format['position'] === 'before') { return $format['symbol'] . $localized_amount; } else { return $localized_amount . ' ' . $format['symbol']; } } /** * 处理内容中的本地化元素 */ public function process_content($content, $target_lang) { // 处理日期 $content = preg_replace_callback( '/{date:(.+?)}/', function($matches) use ($target_lang) { return $this->localize_date($matches[1], $target_lang); }, $content ); // 处理货币 $content = preg_replace_callback( '/{currency:(d+.?d*):(w+)}/', function($matches) use ($target_lang) { return $this->localize_currency( $matches[1], $matches[2], $target_lang ); }, $content ); // 处理测量单位转换(如英里到公里) $content = $this->convert_measurements($content, $target_lang); return $content; } } 第六部分:创建管理界面与设置 6.1 管理菜单与设置页面 class ATL_Admin { private $version; public function __construct($version) { $this->version = $version; } /** * 添加管理菜单 */ public function add_admin_menu() { add_menu_page( '自动化翻译设置', '翻译工具', 'manage_options', 'atl-settings', [$this, 'display_settings_page'], 'dashicons-translation', 80 ); add_submenu_page( 'atl-settings', '翻译统计', '统计', 'manage_options', 'atl-stats', [$this, 'display_stats_page'] ); } /** * 显示设置页面 */ public function display_settings_page() { ?> <div class="wrap"> <h1>自动化翻译与本地化设置</h1> <form method="post" action="options.php"> <?php settings_fields('atl_settings_group'); do_settings_sections('atl-settings'); submit_button(); ?> </form> <div class="atl-actions"> <h2>批量操作</h2> <button id="atl-batch-translate" class="button button-primary"> 批量翻译现有内容 </button> <button id="atl-sync-translations" class="button"> 同步翻译内容 </button> </div> </div> <?php } /** * 注册设置 */ public function register_settings() { register_setting('atl_settings_group', 'atl_google_api_key'); register_setting('atl_settings_group', 'atl_source_language'); register_setting('atl_settings_group', 'atl_target_languages'); register_setting('atl_settings_group', 'atl_auto_translate'); register_setting('atl_settings_group', 'atl_post_types'); add_settings_section( 'atl_main_section', 'API配置', null, 'atl-settings' ); add_settings_field( 'atl_google_api_key', 'Google Cloud API密钥', [$this, 'render_api_key_field'], 'atl-settings', 'atl_main_section' ); add_settings_field( 'atl_source_language', '源语言', [$this, 'render_source_language_field'], 'atl-settings', 'atl_main_section' ); add_settings_field( 'atl_target_languages', '目标语言', [$this, 'render_target_languages_field'], 'atl-settings', 'atl_main_section' ); } /** * 渲染API密钥字段 */ public function render_api_key_field() { $value = get_option('atl_google_api_key', ''); ?> <input type="password" name="atl_google_api_key" value="<?php echo esc_attr($value); ?>" class="regular-text"> <p class="description">从Google Cloud Console获取Translation API密钥</p> <?php } /** * 渲染目标语言选择字段 */ public function render_target_languages_field() { $languages = [ 'es' => '西班牙语', 'fr' => '法语', 'de' => '德语', 'ja' => '日语', zh-CN' => '简体中文', 'zh-TW' => '繁体中文', 'ko' => '韩语', 'ru' => '俄语', 'ar' => '阿拉伯语' ]; $selected = get_option('atl_target_languages', ['es', 'fr', 'de']); ?> <select name="atl_target_languages[]" multiple="multiple" class="regular-text" style="height: 150px;"> <?php foreach ($languages as $code => $name): ?> <option value="<?php echo esc_attr($code); ?>" <?php echo in_array($code, $selected) ? 'selected' : ''; ?>> <?php echo esc_html($name . ' (' . $code . ')'); ?> </option> <?php endforeach; ?> </select> <p class="description">按住Ctrl键可多选</p> <?php } } ### 6.2 批量翻译功能 class ATL_Batch_Processor { /** * 批量翻译现有内容 */ public function process_batch_translation() { $post_types = get_option('atl_post_types', ['post', 'page']); $languages = get_option('atl_target_languages', ['es', 'fr', 'de']); $args = [ 'post_type' => $post_types, 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => [ [ 'key' => '_atl_translated', 'compare' => 'NOT EXISTS' ] ] ]; $posts = get_posts($args); $results = [ 'total' => count($posts), 'processed' => 0, 'errors' => [] ]; foreach ($posts as $post) { try { $this->translate_single_post($post->ID, $post, $languages); $results['processed']++; // 更新进度 if ($results['processed'] % 10 === 0) { update_option('atl_batch_progress', [ 'current' => $results['processed'], 'total' => $results['total'] ]); } } catch (Exception $e) { $results['errors'][] = [ 'post_id' => $post->ID, 'error' => $e->getMessage() ]; } } return $results; } /** * AJAX批量翻译处理 */ public function ajax_batch_translate() { check_ajax_referer('atl_batch_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_die('权限不足'); } $page = isset($_POST['page']) ? intval($_POST['page']) : 1; $batch_size = 5; // 每批处理5篇文章 $args = [ 'post_type' => get_option('atl_post_types', ['post', 'page']), 'post_status' => 'publish', 'posts_per_page' => $batch_size, 'paged' => $page, 'meta_query' => [ [ 'key' => '_atl_translated', 'compare' => 'NOT EXISTS' ] ] ]; $posts = get_posts($args); $total_posts = wp_count_posts()->publish; foreach ($posts as $post) { $this->translate_single_post($post->ID, $post); } $progress = ($page * $batch_size) / $total_posts * 100; $progress = min(100, $progress); wp_send_json_success([ 'progress' => $progress, 'message' => sprintf('已处理 %d/%d', $page * $batch_size, $total_posts), 'completed' => $progress >= 100 ]); } } ## 第七部分:前端语言切换与内容展示 ### 7.1 语言切换器实现 class ATL_Public { private $version; private $current_language; public function __construct($version) { $this->version = $version; $this->current_language = $this->get_user_language(); } /** * 获取用户语言偏好 */ private function get_user_language() { // 1. 检查URL参数 if (isset($_GET['lang'])) { $lang = sanitize_text_field($_GET['lang']); setcookie('atl_user_language', $lang, time() + YEAR_IN_SECONDS, '/'); return $lang; } // 2. 检查Cookie if (isset($_COOKIE['atl_user_language'])) { return $_COOKIE['atl_user_language']; } // 3. 检查浏览器语言 $browser_lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); $supported_langs = get_option('atl_target_languages', []); if (in_array($browser_lang, $supported_langs)) { return $browser_lang; } // 4. 默认语言 return get_option('atl_source_language', 'en'); } /** * 注册语言切换器短代码 */ public function register_language_switcher() { add_shortcode('language_switcher', [$this, 'render_language_switcher']); } /** * 渲染语言切换器 */ public function render_language_switcher($atts) { $atts = shortcode_atts([ 'type' => 'dropdown', // dropdown, flags, list 'show_names' => true, 'show_flags' => true ], $atts); $languages = [ 'en' => ['name' => 'English', 'flag' => '🇺🇸'], 'es' => ['name' => 'Español', 'flag' => '🇪🇸'], 'fr' => ['name' => 'Français', 'flag' => '🇫🇷'], 'de' => ['name' => 'Deutsch', 'flag' => '🇩🇪'], 'zh-CN' => ['name' => '简体中文', 'flag' => '🇨🇳'] ]; $current_url = home_url(add_query_arg(null, null)); ob_start(); ?> <div class="atl-language-switcher atl-type-<?php echo esc_attr($atts['type']); ?>"> <?php if ($atts['type'] === 'dropdown'): ?> <select class="atl-lang-select" onchange="window.location.href=this.value"> <?php foreach ($languages as $code => $lang): ?> <option value="<?php echo esc_url(add_query_arg('lang', $code, $current_url)); ?>" <?php echo $code === $this->current_language ? 'selected' : ''; ?>> <?php if ($atts['show_flags']): ?> <?php echo esc_html($lang['flag']); ?> <?php endif; ?> <?php if ($atts['show_names']): ?> <?php echo esc_html($lang['name']); ?> <?php endif; ?> </option> <?php endforeach; ?> </select> <?php else: ?> <ul class="atl-lang-list"> <?php foreach ($languages as $code => $lang): ?> <li class="<?php echo $code === $this->current_language ? 'current' : ''; ?>"> <a href="<?php echo esc_url(add_query_arg('lang', $code, $current_url)); ?>"> <?php if ($atts['show_flags']): ?> <span class="atl-flag"><?php echo esc_html($lang['flag']); ?></span> <?php endif; ?> <?php if ($atts['show_names']): ?> <span class="atl-name"><?php echo esc_html($lang['name']); ?></span> <?php endif; ?> </a> </li> <?php endforeach; ?> </ul> <?php endif; ?> </div> <?php return ob_get_clean(); } /** * 根据语言过滤内容 */ public function filter_content_by_language($content) { global $post; if (is_admin() || !$post) { return $content; } // 如果是源语言,直接返回 if ($this->current_language === get_option('atl_source_language', 'en')) { return $content; } // 查找翻译版本 $translated_post_id = $this->get_translated_post_id($post->ID, $this->current_language); if ($translated_post_id) { $translated_post = get_post($translated_post_id); if ($translated_post && $translated_post->post_status === 'publish') { $localizer = new ATL_Localizer(); $content = $localizer->process_content($translated_post->post_content, $this->current_language); // 应用WordPress过滤器 $content = apply_filters('the_content', $content); } } return $content; } /** * 获取翻译后的文章ID */ private function get_translated_post_id($original_id, $language) { $args = [ 'post_type' => get_post_type($original_id), 'meta_query' => [ [ 'key' => '_atl_original_post', 'value' => $original_id ], [ 'key' => '_atl_language', 'value' => $language ] ], 'posts_per_page' => 1 ]; $posts = get_posts($args); return $posts ? $posts[0]->ID : false; } } ### 7.2 前端样式与脚本 / assets/css/atl-public.css /.atl-language-switcher { margin: 10px 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } .atl-language-switcher select.atl-lang-select { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; background: white; font-size: 14px; min-width: 150px; } .atl-language-switcher ul.atl-lang-list { list-style: none; margin: 0; padding: 0; display: flex; gap: 10px; } .atl-language-switcher ul.atl-lang-list li { margin: 0; } .atl-language-switcher ul.atl-lang-list li a { display: flex; align-items: center; gap: 5px; padding: 5px 10px; text-decoration: none; color: #333; border: 1px solid #ddd; border-radius: 4px; transition: all 0.3s ease; } .atl-language-switcher ul.atl-lang-list li a:hover { background: #f5f5f5; border-color: #0073aa; } .atl-language-switcher ul.atl-lang-list li.current a { background: #0073aa; color: white; border-color: #0073aa; } .atl-flag { font-size: 18px; } .atl-name { font-size: 14px; } // assets/js/atl-public.jsjQuery(document).ready(function($) { // 语言切换器交互 $('.atl-language-switcher').on('change', '.atl-lang-select', function() { window.location.href = $(this).val(); }); // 检测内容更新并提示翻译 if (typeof wp !== 'undefined' && wp.data && wp.data.subscribe) { wp.data.subscribe(function() { var isSavingPost = wp.data.select('core/editor').isSavingPost(); var isAutosavingPost = wp.data.select('core/editor').isAutosavingPost(); if (isSavingPost && !isAutosavingPost) { // 文章保存后显示翻译提示 setTimeout(function() { if (confirm('是否立即翻译这篇文章到其他语言?')) { $('#atl-force-translate').click(); } }, 1000); } }); } }); ## 第八部分:高级功能与优化 ### 8.1 SEO优化与hreflang标签 class ATL_SEO_Optimizer { /** * 添加hreflang标签 */ public function add_hreflang_tags() { if (is_singular()) { global $post; $original_id = get_post_meta($post->ID, '_atl_original_post', true); $original_id = $original_id ?: $post->ID; $translations = $this->get_post_translations($original_id); foreach ($translations as $lang => $url) { echo '<link rel="alternate" hreflang="' . esc_attr($lang) . '" href="' . esc_url($url) . '" />' . "n"; } // 添加x-default $default_url = get_permalink($original_id); echo '<link rel="alternate" hreflang="x-default" href="' . esc_url($default_url) . '" />' . "n"; } } /** * 获取文章的所有翻译版本 */ private function get_post_translations($post_id) { $translations = []; $source_lang = get_option('atl_source_language', 'en'); // 添加源语言 $translations[$source_lang] = get_permalink($post_id); // 查找翻译版本 $args = [ 'post_type' => get_post_type($post_id), 'meta_query' => [ [ 'key' => '_atl_original_post', 'value' => $post_id ] ], 'posts_per_page' => -1 ]; $translated_posts = get_posts($args); foreach ($translated_posts as $translated_post) { $lang = get_post_meta($translated_post->ID, '_atl_language', true); if ($lang) { $translations[$lang] = get_permalink($translated_post->ID); } } return $translations; } /** * 优化翻译内容的SEO元数据 */ public function optimize_seo_metadata($post_id, $language) { $original_id = get_post_meta($post_id, '_atl_original_post', true); if ($original_id) { // 复制SEO数据 $meta_keys = [ '_yoast_wpseo_title', '_yoast_wpseo_metadesc', '_yoast_wpseo_focuskw', '_aioseo_title', '_aioseo_description' ]; foreach ($meta_keys as $meta_key) { $value = get_post_meta($original_id, $meta_key, true); if ($value) { // 翻译SEO数据 $translator = new ATL_Translator(); $translated_value = $translator->translate_text( $value, get_option('atl_source_language', 'en'), $language ); update_post_meta($post_id, $meta_key, $translated_value); } } } } } ### 8.2 缓存优化策略 class ATL_Cache_Manager { /** * 智能缓存策略 */ public function setup_caching() { // 按语言缓存页面 add_action('template_redirect', [$this, 'set_cache_headers']); // 清除翻译缓存 add_action('atl_translation_updated', [$this, 'clear_translation_cache']); // 预缓存热门翻译 $this->precache_popular_content(); } /** * 设置缓存头 */ public function set_cache_headers() { if (!is_admin() && !is_user_logged_in()) { $language = $this->get_current_language(); header('Cache-Control: public, max-age=3600'); header('Vary: Accept-Language, Cookie'); // 为CDN设置缓存键 if (function_exists('wp_cache_add')) { wp_cache_add('atl_language', $language); } } } /** * 清除翻译缓存 */ public function clear_translation_cache($post_id) { // 清除文章缓存 clean_post_cache($post_id); // 清除相关翻译缓存 $translated_posts = $this->get_translated_posts($post_id); foreach ($translated_posts as $translated_post) { clean_post_cache($translated_post->ID); } // 清除API缓存 $this->clear_api_cache($post_id); } /** * 预缓存热门内容 */ private function precache_popular_content() { if (false === ($popular_posts = get_transient('atl_popular_posts'))) { $args = [ 'post_type' => get_option('atl_post_types', ['post', 'page']), 'posts_per_page' => 10, 'meta_key' => 'post_views_count', 'orderby' => 'meta_value_num', 'order' => 'DESC' ]; $popular_posts = get_posts($args); set_transient('atl_popular_posts', $popular_posts, HOUR_IN_SECONDS); } // 预翻译热门文章 foreach ($popular

发表评论

开发指南,打造网站内嵌的在线问卷调查与可视化数据分析报告

开发指南:打造网站内嵌的在线问卷调查与可视化数据分析报告 摘要 在当今数据驱动的互联网时代,网站内嵌的在线问卷调查与可视化数据分析功能已成为企业与用户互动、收集反馈、优化产品的重要工具。本文详细介绍了如何通过WordPress程序的代码二次开发,实现这一常用互联网小工具功能。我们将从需求分析、技术选型、开发流程到最终部署,全面解析打造专业级问卷调查与数据分析系统的全过程。 一、需求分析与功能规划 1.1 市场需求背景 随着互联网技术的普及,数据收集与分析已成为企业决策的重要依据。传统问卷调查方式存在诸多局限性,如数据整理繁琐、分析效率低下、可视化程度不足等。而网站内嵌的在线问卷调查系统能够实时收集用户反馈,结合可视化数据分析报告,为企业提供直观、高效的数据洞察工具。 1.2 核心功能需求 一个完整的网站内嵌问卷调查与数据分析系统应包含以下核心功能: 问卷创建与管理:支持多种题型(单选、多选、文本、评分等),可自定义问卷样式 响应式设计:适配各种设备,确保移动端用户体验 数据收集与存储:安全高效地收集和存储用户提交的数据 实时数据分析:对收集的数据进行实时统计分析 可视化报告:将分析结果以图表形式直观展示 数据导出:支持将数据导出为Excel、CSV等格式 权限管理:不同用户角色拥有不同的数据访问和操作权限 集成能力:能够与WordPress用户系统、邮件系统等无缝集成 1.3 技术可行性分析 WordPress作为全球最流行的内容管理系统,拥有强大的扩展性和丰富的API接口,通过二次开发完全可以实现上述功能需求。其优势包括: 成熟的用户管理系统 丰富的插件生态和开发文档 灵活的数据库结构 强大的主题定制能力 活跃的开发者社区 二、技术架构设计 2.1 系统架构概览 我们将采用分层架构设计,确保系统的可扩展性和可维护性: ┌─────────────────────────────────────────┐ │ 用户界面层 (UI Layer) │ ├─────────────────────────────────────────┤ │ 业务逻辑层 (Logic Layer) │ ├─────────────────────────────────────────┤ │ 数据访问层 (Data Layer) │ ├─────────────────────────────────────────┤ │ WordPress核心系统 │ └─────────────────────────────────────────┘ 2.2 数据库设计 在WordPress默认数据库结构基础上,我们需要新增以下数据表: 问卷表 (wp_surveys):存储问卷基本信息 问题表 (wp_survey_questions):存储问卷中的各个问题 选项表 (wp_survey_options):存储选择题的选项 回答表 (wp_survey_responses):存储用户提交的回答 报告表 (wp_survey_reports):存储生成的分析报告 2.3 技术栈选择 后端:PHP 7.4+,WordPress REST API,MySQL 5.7+ 前端:HTML5,CSS3,JavaScript (ES6+),Chart.js/D3.js(数据可视化) 开发框架:基于WordPress插件架构开发 第三方库:jQuery(兼容性),Bootstrap(响应式布局),PHPSpreadsheet(数据导出) 三、开发环境搭建与配置 3.1 本地开发环境配置 安装本地服务器环境:推荐使用XAMPP、MAMP或Local by Flywheel 安装WordPress:下载最新版WordPress并完成基本配置 创建插件目录:在wp-content/plugins/目录下创建survey-analytics-tool文件夹 配置开发工具:安装代码编辑器(如VS Code),配置Git版本控制 3.2 插件基础结构 创建插件主文件survey-analytics-tool.php: <?php /** * Plugin Name: 问卷调查与数据分析工具 * Plugin URI: https://yourwebsite.com/ * Description: 网站内嵌的在线问卷调查与可视化数据分析报告系统 * Version: 1.0.0 * Author: 开发者名称 * License: GPL v2 or later * Text Domain: survey-analytics-tool */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('SAT_VERSION', '1.0.0'); define('SAT_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('SAT_PLUGIN_URL', plugin_dir_url(__FILE__)); // 初始化插件 require_once SAT_PLUGIN_DIR . 'includes/class-survey-analytics-tool.php'; // 实例化主类 function sat_init() { $plugin = new Survey_Analytics_Tool(); $plugin->run(); } add_action('plugins_loaded', 'sat_init'); 3.3 数据库表创建 创建数据库表安装脚本: // 在includes/class-install.php中 class SAT_Install { public static function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 问卷表 $surveys_table = $wpdb->prefix . 'surveys'; $sql1 = "CREATE TABLE IF NOT EXISTS $surveys_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, title varchar(255) NOT NULL, description text, status varchar(20) DEFAULT 'draft', created_by bigint(20) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, settings text, PRIMARY KEY (id) ) $charset_collate;"; // 问题表 $questions_table = $wpdb->prefix . 'survey_questions'; $sql2 = "CREATE TABLE IF NOT EXISTS $questions_table ( id mediumint(9) NOT NULL AUTO_INCREMENT, survey_id mediumint(9) NOT NULL, question_text text NOT NULL, question_type varchar(50) NOT NULL, is_required tinyint(1) DEFAULT 0, sort_order int(11) DEFAULT 0, options text, PRIMARY KEY (id), KEY survey_id (survey_id) ) $charset_collate;"; // 更多表创建语句... require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql1); dbDelta($sql2); // 执行其他表的创建... } } 四、核心功能模块开发 4.1 问卷创建与管理模块 4.1.1 后台管理界面 创建问卷管理后台页面: // 在admin/class-admin-survey.php中 class SAT_Admin_Survey { public function add_admin_menu() { add_menu_page( '问卷调查管理', '问卷调查', 'manage_options', 'survey-analytics', array($this, 'render_survey_list'), 'dashicons-feedback', 30 ); add_submenu_page( 'survey-analytics', '创建新问卷', '创建问卷', 'manage_options', 'survey-analytics-new', array($this, 'render_survey_editor') ); } public function render_survey_list() { // 显示问卷列表 include SAT_PLUGIN_DIR . 'admin/views/survey-list.php'; } public function render_survey_editor() { // 显示问卷编辑器 include SAT_PLUGIN_DIR . 'admin/views/survey-editor.php'; } } 4.1.2 问卷编辑器前端实现 创建交互式问卷编辑器: <!-- admin/views/survey-editor.php --> <div class="wrap"> <h1>问卷编辑器</h1> <div id="survey-builder"> <!-- 问卷基本信息 --> <div class="survey-basic-info"> <input type="text" id="survey-title" placeholder="问卷标题"> <textarea id="survey-description" placeholder="问卷描述"></textarea> </div> <!-- 问题列表 --> <div id="questions-container"> <!-- 问题将通过JavaScript动态添加 --> </div> <!-- 添加问题按钮 --> <button id="add-question" class="button button-primary">添加问题</button> <!-- 保存问卷 --> <button id="save-survey" class="button button-primary">保存问卷</button> </div> </div> <script> // 问题类型模板 const questionTemplates = { 'single_choice': ` <div class="question-item" data-type="single_choice"> <div class="question-header"> <input type="text" class="question-text" placeholder="请输入问题"> <select class="question-required"> <option value="0">非必填</option> <option value="1">必填</option> </select> <button class="remove-question">删除</button> </div> <div class="options-container"> <div class="option-item"> <input type="radio" disabled> <input type="text" class="option-text" placeholder="选项文本"> <button class="remove-option">删除</button> </div> </div> <button class="add-option">添加选项</button> </div> `, // 其他问题类型模板... }; // 初始化问卷编辑器 jQuery(document).ready(function($) { // 添加问题 $('#add-question').on('click', function() { const questionType = 'single_choice'; // 默认单选,实际中可以让用户选择 $('#questions-container').append(questionTemplates[questionType]); }); // 保存问卷 $('#save-survey').on('click', function() { const surveyData = { title: $('#survey-title').val(), description: $('#survey-description').val(), questions: [] }; // 收集所有问题数据 $('.question-item').each(function() { const question = { text: $(this).find('.question-text').val(), type: $(this).data('type'), required: $(this).find('.question-required').val(), options: [] }; // 收集选项数据 $(this).find('.option-item').each(function() { question.options.push($(this).find('.option-text').val()); }); surveyData.questions.push(question); }); // 发送到服务器保存 $.ajax({ url: sat_ajax.ajax_url, type: 'POST', data: { action: 'save_survey', nonce: sat_ajax.nonce, survey: surveyData }, success: function(response) { if (response.success) { alert('问卷保存成功!'); } else { alert('保存失败:' + response.data.message); } } }); }); }); </script> 4.2 前端问卷展示与数据收集 4.2.1 创建短代码显示问卷 // 在includes/class-shortcodes.php中 class SAT_Shortcodes { public function register_shortcodes() { add_shortcode('survey', array($this, 'render_survey')); } public function render_survey($atts) { $atts = shortcode_atts(array( 'id' => 0, 'title' => '问卷调查' ), $atts); $survey_id = intval($atts['id']); if (!$survey_id) { return '<p>请指定有效的问卷ID</p>'; } // 获取问卷数据 $survey = $this->get_survey_data($survey_id); if (!$survey) { return '<p>问卷不存在或已删除</p>'; } // 渲染问卷HTML ob_start(); include SAT_PLUGIN_DIR . 'public/views/survey-form.php'; return ob_get_clean(); } private function get_survey_data($survey_id) { global $wpdb; $table = $wpdb->prefix . 'surveys'; $survey = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table WHERE id = %d AND status = 'published'", $survey_id )); if ($survey) { // 获取问题数据 $questions_table = $wpdb->prefix . 'survey_questions'; $survey->questions = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $questions_table WHERE survey_id = %d ORDER BY sort_order ASC", $survey_id )); // 解析问题选项 foreach ($survey->questions as &$question) { if ($question->options) { $question->options = maybe_unserialize($question->options); } } } return $survey; } } 4.2.2 问卷表单前端实现 <!-- public/views/survey-form.php --> <div class="sat-survey-container" data-survey-id="<?php echo $survey->id; ?>"> <h2 class="survey-title"><?php echo esc_html($survey->title); ?></h2> <?php if ($survey->description): ?> <div class="survey-description"> <?php echo wp_kses_post($survey->description); ?> </div> <?php endif; ?> <form id="sat-survey-form-<?php echo $survey->id; ?>" class="sat-survey-form"> <?php foreach ($survey->questions as $index => $question): ?> <div class="survey-question" data-question-id="<?php echo $question->id; ?>"> <div class="question-text"> <label> <?php echo ($index + 1) . '. ' . esc_html($question->question_text); ?> <?php if ($question->is_required): ?> <span class="required">*</span> <?php endif; ?> </label> </div> <div class="question-input"> <?php switch($question->question_type): case 'single_choice': ?> <?php foreach ($question->options as $option_index => $option): ?> <div class="option-item"> <input type="radio" id="q<?php echo $question->id; ?>_o<?php echo $option_index; ?>" name="question_<?php echo $question->id; ?>" value="<?php echo esc_attr($option); ?>" <?php echo $question->is_required ? 'required' : ''; ?>> <label for="q<?php echo $question->id; ?>_o<?php echo $option_index; ?>"> <?php echo esc_html($option); ?> </label> </div> <?php endforeach; ?> <?php break; case 'multiple_choice': ?> <!-- 多选实现 --> <?php break; case 'text': ?> <textarea name="question_<?php echo $question->id; ?>" <?php echo $question->is_required ? 'required' : ''; ?> rows="3"></textarea> <?php break; case 'rating': ?> <!-- 评分题实现 --> <?php break; endswitch; ?> </div> </div> <?php endforeach; ?> <div class="survey-submit"> <button type="submit" class="submit-button">提交问卷</button> </div> </form> <div id="survey-thankyou" style="display:none;"> <p>感谢您参与本次问卷调查!</p> </div> </div> <script> jQuery(document).ready(function($) { $('#sat-survey-form-<?php echo $survey->id; ?>').on('submit', function(e) { e.preventDefault(); const formData = $(this).serializeArray(); const surveyId = $(this).closest('.sat-survey-container').data('survey-id'); // 添加survey_id到数据中 formData.push({name: 'survey_id', value: surveyId}); $.ajax({ url: sat_public.ajax_url, type: 'POST', data: { action: 'submit_survey', nonce: sat_public.nonce, form_data: formData }, success: function(response) { if (response.success) { // 显示感谢信息 $('#sat-survey-form-<?php echo $survey->id; ?>').hide(); $('#survey-thankyou').show(); } else { alert('提交失败:' + response.data.message); } } }); }); }); </script> 4.3 数据存储与处理模块 4.3.1 数据提交处理 // 在includes/class-data-handler.php中 class SAT_Data_Handler { public function handle_survey_submission() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'sat_public_nonce')) { wp_die('安全验证失败'); } $survey_id = intval($_POST['survey_id']); $form_data = $_POST['form_data']; // 验证问卷是否存在且处于发布状态 $survey = $this->validate_survey($survey_id); if (!$survey) { wp_send_json_error(array('message' => '问卷不存在或已关闭')); } // 处理表单数据 $processed_data = array(); foreach ($form_data as $item) { if (strpos($item['name'], 'question_') === 0) { $question_id = str_replace('question_', '', $item['name']); $processed_data[$question_id] = $item['value']; } 4.3.2 数据验证与存储 // 验证必填问题 foreach ($survey->questions as $question) { if ($question->is_required && empty($processed_data[$question->id])) { wp_send_json_error(array( 'message' => '请填写所有必填问题', 'question_id' => $question->id )); } } // 存储回答数据 $response_id = $this->store_response($survey_id, $processed_data); if ($response_id) { // 触发数据收集完成钩子 do_action('sat_survey_completed', $survey_id, $response_id, $processed_data); wp_send_json_success(array( 'message' => '提交成功', 'response_id' => $response_id )); } else { wp_send_json_error(array('message' => '数据存储失败')); } } private function store_response($survey_id, $data) { global $wpdb; // 插入主回答记录 $responses_table = $wpdb->prefix . 'survey_responses'; $wpdb->insert( $responses_table, array( 'survey_id' => $survey_id, 'user_id' => get_current_user_id(), 'ip_address' => $this->get_client_ip(), 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'created_at' => current_time('mysql') ), array('%d', '%d', '%s', '%s', '%s') ); $response_id = $wpdb->insert_id; if (!$response_id) { return false; } // 存储每个问题的回答 $answers_table = $wpdb->prefix . 'survey_answers'; foreach ($data as $question_id => $answer_value) { $wpdb->insert( $answers_table, array( 'response_id' => $response_id, 'question_id' => $question_id, 'answer_value' => is_array($answer_value) ? serialize($answer_value) : $answer_value, 'created_at' => current_time('mysql') ), array('%d', '%d', '%s', '%s') ); } return $response_id; } private function get_client_ip() { $ipaddress = ''; if (isset($_SERVER['HTTP_CLIENT_IP'])) $ipaddress = $_SERVER['HTTP_CLIENT_IP']; else if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR']; else if(isset($_SERVER['HTTP_X_FORWARDED'])) $ipaddress = $_SERVER['HTTP_X_FORWARDED']; else if(isset($_SERVER['HTTP_FORWARDED_FOR'])) $ipaddress = $_SERVER['HTTP_FORWARDED_FOR']; else if(isset($_SERVER['HTTP_FORWARDED'])) $ipaddress = $_SERVER['HTTP_FORWARDED']; else if(isset($_SERVER['REMOTE_ADDR'])) $ipaddress = $_SERVER['REMOTE_ADDR']; else $ipaddress = 'UNKNOWN'; return $ipaddress; } } 4.4 数据分析与统计模块 4.4.1 数据统计功能 // 在includes/class-analytics.php中 class SAT_Analytics { public function get_survey_statistics($survey_id) { global $wpdb; $statistics = array( 'basic' => $this->get_basic_stats($survey_id), 'questions' => array(), 'trends' => $this->get_response_trends($survey_id) ); // 获取所有问题 $questions_table = $wpdb->prefix . 'survey_questions'; $questions = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $questions_table WHERE survey_id = %d ORDER BY sort_order ASC", $survey_id )); // 为每个问题生成统计 foreach ($questions as $question) { $statistics['questions'][$question->id] = $this->analyze_question($question); } return $statistics; } private function get_basic_stats($survey_id) { global $wpdb; $responses_table = $wpdb->prefix . 'survey_responses'; // 总回答数 $total_responses = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $responses_table WHERE survey_id = %d", $survey_id )); // 今日回答数 $today_responses = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $responses_table WHERE survey_id = %d AND DATE(created_at) = CURDATE()", $survey_id )); // 平均完成时间(如果有记录时间的话) $avg_completion_time = null; return array( 'total_responses' => (int)$total_responses, 'today_responses' => (int)$today_responses, 'completion_rate' => $this->calculate_completion_rate($survey_id), 'avg_completion_time' => $avg_completion_time ); } private function analyze_question($question) { global $wpdb; $answers_table = $wpdb->prefix . 'survey_answers'; $responses_table = $wpdb->prefix . 'survey_responses'; $analysis = array( 'question_id' => $question->id, 'question_text' => $question->question_text, 'question_type' => $question->question_type, 'total_answers' => 0, 'data' => array() ); switch ($question->question_type) { case 'single_choice': case 'multiple_choice': $analysis = $this->analyze_choice_question($question, $analysis); break; case 'rating': $analysis = $this->analyze_rating_question($question, $analysis); break; case 'text': $analysis = $this->analyze_text_question($question, $analysis); break; } return $analysis; } private function analyze_choice_question($question, $analysis) { global $wpdb; $answers_table = $wpdb->prefix . 'survey_answers'; $options = maybe_unserialize($question->options); // 获取每个选项的选择次数 $option_counts = array(); foreach ($options as $index => $option) { $count = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $answers_table WHERE question_id = %d AND answer_value LIKE %s", $question->id, '%' . $wpdb->esc_like($option) . '%' )); $option_counts[$option] = (int)$count; } // 计算百分比 $total = array_sum($option_counts); $analysis['total_answers'] = $total; foreach ($option_counts as $option => $count) { $percentage = $total > 0 ? round(($count / $total) * 100, 2) : 0; $analysis['data'][] = array( 'option' => $option, 'count' => $count, 'percentage' => $percentage ); } return $analysis; } private function get_response_trends($survey_id) { global $wpdb; $responses_table = $wpdb->prefix . 'survey_responses'; // 获取最近30天的回答趋势 $trends = $wpdb->get_results($wpdb->prepare( "SELECT DATE(created_at) as date, COUNT(*) as count FROM $responses_table WHERE survey_id = %d AND created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) GROUP BY DATE(created_at) ORDER BY date ASC", $survey_id )); return $trends; } } 4.5 可视化报告生成模块 4.5.1 图表生成与展示 <!-- admin/views/analytics-dashboard.php --> <div class="wrap"> <h1>问卷数据分析报告</h1> <div class="sat-analytics-dashboard"> <!-- 基本统计 --> <div class="basic-stats"> <div class="stat-card"> <h3>总回答数</h3> <div class="stat-value" id="total-responses">0</div> </div> <div class="stat-card"> <h3>今日回答</h3> <div class="stat-value" id="today-responses">0</div> </div> <div class="stat-card"> <h3>完成率</h3> <div class="stat-value" id="completion-rate">0%</div> </div> </div> <!-- 回答趋势图 --> <div class="chart-container"> <h3>回答趋势</h3> <canvas id="response-trend-chart" width="800" height="300"></canvas> </div> <!-- 问题分析 --> <div id="question-analytics"> <!-- 每个问题的分析将通过JavaScript动态加载 --> </div> <!-- 数据导出 --> <div class="export-options"> <button id="export-csv" class="button">导出CSV</button> <button id="export-excel" class="button">导出Excel</button> <button id="export-pdf" class="button">导出PDF报告</button> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script> jQuery(document).ready(function($) { const surveyId = <?php echo $survey_id; ?>; // 加载统计数据 function loadAnalytics() { $.ajax({ url: sat_admin.ajax_url, type: 'GET', data: { action: 'get_survey_analytics', survey_id: surveyId, nonce: sat_admin.nonce }, success: function(response) { if (response.success) { renderAnalytics(response.data); } } }); } function renderAnalytics(data) { // 更新基本统计 $('#total-responses').text(data.basic.total_responses); $('#today-responses').text(data.basic.today_responses); $('#completion-rate').text(data.basic.completion_rate + '%'); // 渲染趋势图 renderTrendChart(data.trends); // 渲染问题分析 renderQuestionAnalytics(data.questions); } function renderTrendChart(trends) { const dates = trends.map(item => item.date); const counts = trends.map(item => parseInt(item.count)); const ctx = document.getElementById('response-trend-chart').getContext('2d'); new Chart(ctx, { type: 'line', data: { labels: dates, datasets: [{ label: '回答数量', data: counts, borderColor: 'rgb(75, 192, 192)', tension: 0.1, fill: true, backgroundColor: 'rgba(75, 192, 192, 0.2)' }] }, options: { responsive: true, plugins: { legend: { position: 'top', }, title: { display: true, text: '回答趋势图' } } } }); } function renderQuestionAnalytics(questions) { const container = $('#question-analytics'); container.empty(); Object.values(questions).forEach(question => { const questionHtml = ` <div class="question-analysis" data-question-id="${question.question_id}"> <h4>${question.question_text}</h4> <div class="chart-container"> <canvas id="chart-${question.question_id}" width="400" height="200"></canvas> </div> <div class="analysis-table"> <table> <thead> <tr> <th>选项</th> <th>数量</th> <th>百分比</th> </tr> </thead> <tbody> ${question.data.map(item => ` <tr> <td>${item.option}</td> <td>${item.count}</td> <td>${item.percentage}%</td> </tr> `).join('')} </tbody> </table> </div> </div> `; container.append(questionHtml); // 为每个问题渲染图表 renderQuestionChart(question); }); } function renderQuestionChart(question) { const ctx = document.getElementById(`chart-${question.question_id}`).getContext('2d'); const labels = question.data.map(item => item.option); const data = question.data.map(item => item.count); const backgroundColors = generateColors(data.length); new Chart(ctx, { type: question.question_type === 'rating' ? 'bar' : 'pie', data: { labels: labels, datasets: [{ data: data, backgroundColor: backgroundColors, borderWidth: 1 }] }, options: { responsive: true, plugins: { legend: { position: 'right', }, title: { display: true, text: question.question_text.substring(0, 50) + '...' } } } }); } function generateColors(count) { const colors = []; for (let i = 0; i < count; i++) { const hue = (i * 360 / count) % 360; colors.push(`hsl(${hue}, 70%, 60%)`); } return colors; } // 初始化加载 loadAnalytics(); // 设置自动刷新 setInterval(loadAnalytics, 300000); // 每5分钟刷新一次 }); </script> 4.5.2 数据导出功能 // 在includes/class-export.php中 class SAT_Export { public function export_csv($survey_id) { global $wpdb; // 获取问卷数据 $survey = $this->get_survey_with_responses($survey_id); if (!$survey) { return false; } // 设置CSV头部 $filename = 'survey_' . $survey_id . '_' . date('Ymd_His') . '.csv'; header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $filename . '"'); $output = fopen('php://output', 'w'); // 写入标题行 $headers = array('回答ID', '提交时间', '用户ID', 'IP地址'); foreach ($survey->questions as $question) { $headers[] = $question->question_text; } fputcsv($output, $headers); // 写入数据行 foreach ($survey->responses as $response) { $row = array( $response->id, $response->created_at, $response->user_id, $response->ip_address ); foreach ($survey->questions as $question) { $answer = $this->find_answer($response->answers, $question->id); $row[] = $answer ? $answer->answer_value : ''; } fputcsv($output, $row); } fclose($output); exit; } public function export_pdf_report($survey_id) { // 使用TCPDF或DomPDF生成PDF报告 require_once SAT_PLUGIN_DIR . 'vendor/autoload.php'; $analytics = SAT_Analytics::get_survey_statistics($survey_id); $survey = $this->get_survey_data($survey_id); // 创建PDF $pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); // 设置文档信息 $pdf->SetCreator('Survey Analytics Tool'); $pdf->SetAuthor('WordPress'); $pdf->SetTitle('问卷分析报告 - ' . $survey->title); // 添加页面 $pdf->AddPage(); // 添加标题 $pdf->SetFont('stsongstdlight', 'B', 16); $pdf->Cell(0, 10, $survey->title, 0, 1, 'C'); // 添加基本统计 $pdf->SetFont('stsongstdlight', '', 12); $pdf->Ln(10); $pdf->Cell(0, 10, '基本统计:', 0, 1); $pdf->Cell(0, 10, '总回答数: ' . $analytics['basic']['total_responses'], 0, 1); $pdf->Cell(0, 10, '今日回答: ' . $analytics['basic']['today_responses'], 0, 1); // 添加问题分析 foreach ($analytics['questions'] as $question) { $pdf->AddPage(); $pdf->SetFont('stsongstdlight', 'B', 14); $pdf->Cell(0, 10, $question['question_text'], 0, 1); $pdf->SetFont('stsongstdlight', '', 12); // 添加图表或表格... } // 输出PDF

发表评论

WordPress集成教程,连接智能硬件并展示物联网设备数据面板

WordPress集成教程:连接智能硬件并展示物联网设备数据面板,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:当WordPress遇见物联网 在当今万物互联的时代,物联网设备正以前所未有的速度渗透到我们生活的方方面面。从智能家居的温度传感器到工业环境的监控设备,海量数据正源源不断地产生。然而,这些数据的价值往往受限于展示和分析的平台。与此同时,WordPress作为全球最流行的内容管理系统,已经超越了简单的博客平台,发展成为功能强大的网站构建工具。本教程将深入探讨如何将这两者结合——通过WordPress的代码二次开发,连接智能硬件并创建专业的物联网设备数据面板,同时实现多种常用互联网小工具功能。 第一部分:准备工作与环境搭建 1.1 WordPress开发环境配置 在开始集成物联网设备之前,我们需要建立一个适合开发的WordPress环境。建议使用本地开发环境如XAMPP、MAMP或Local by Flywheel,这些工具可以快速搭建包含Apache、MySQL和PHP的完整环境。 对于物联网开发,我们需要确保WordPress安装满足以下条件: WordPress 5.0及以上版本 PHP 7.4及以上(建议8.0+以获得更好性能) 启用REST API功能 安装并激活调试插件,如Query Monitor和Debug Bar 1.2 物联网硬件连接基础 物联网设备与WordPress的通信通常通过以下几种方式实现: HTTP/HTTPS请求:设备直接向WordPress REST API端点发送数据 MQTT协议:轻量级的发布/订阅消息传输协议,适合低带宽环境 WebSocket连接:实现实时双向通信 第三方物联网平台中转:通过如ThingsBoard、AWS IoT等平台收集数据,再同步到WordPress 在本教程中,我们将主要使用HTTP REST API方式,因为它与WordPress的集成最为直接,且大多数智能硬件都支持HTTP客户端库。 1.3 必要的开发工具和库 Postman或Insomnia:用于测试API端点 Arduino IDE或PlatformIO:用于嵌入式设备编程 Node-RED(可选):可视化物联网编程工具,可用于数据预处理 Chart.js或D3.js:用于数据可视化 Vue.js或React(可选):用于构建交互式前端面板 第二部分:创建WordPress REST API端点接收物联网数据 2.1 理解WordPress REST API架构 WordPress REST API提供了一套标准的、易于使用的接口,允许外部应用与WordPress进行数据交互。默认情况下,WordPress已经为文章、页面、用户等核心内容类型提供了API端点。 对于物联网数据,我们需要创建自定义端点来接收和存储设备数据。这可以通过两种方式实现: 使用register_rest_route函数创建全新端点 扩展现有端点(如文章类型)来存储设备数据 2.2 创建自定义REST API端点 下面是一个完整的插件示例,用于创建接收物联网设备数据的API端点: <?php /** * Plugin Name: IoT Device Data Manager * Description: 接收和展示物联网设备数据的WordPress插件 * Version: 1.0.0 * Author: Your Name */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } class IoT_Device_Manager { private $table_name; public function __construct() { global $wpdb; $this->table_name = $wpdb->prefix . 'iot_device_data'; // 注册激活钩子 register_activation_hook(__FILE__, array($this, 'create_database_table')); // 注册REST API路由 add_action('rest_api_init', array($this, 'register_rest_routes')); // 注册管理菜单 add_action('admin_menu', array($this, 'add_admin_menu')); // 注册短代码 add_shortcode('iot_dashboard', array($this, 'iot_dashboard_shortcode')); } // 创建数据库表 public function create_database_table() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS {$this->table_name} ( id mediumint(9) NOT NULL AUTO_INCREMENT, device_id varchar(100) NOT NULL, device_type varchar(50) NOT NULL, temperature decimal(5,2), humidity decimal(5,2), pressure decimal(7,2), pm25 int(11), voltage decimal(6,3), status varchar(20) DEFAULT 'active', created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), INDEX device_index (device_id), INDEX time_index (created_at) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } // 注册REST API路由 public function register_rest_routes() { // 接收设备数据的端点 register_rest_route('iot/v1', '/data', array( 'methods' => 'POST', 'callback' => array($this, 'handle_device_data'), 'permission_callback' => array($this, 'verify_device_token'), 'args' => array( 'device_id' => array( 'required' => true, 'validate_callback' => function($param) { return is_string($param) && strlen($param) <= 100; } ), 'temperature' => array( 'required' => false, 'validate_callback' => function($param) { return is_numeric($param) && $param >= -50 && $param <= 100; } ), 'humidity' => array( 'required' => false, 'validate_callback' => function($param) { return is_numeric($param) && $param >= 0 && $param <= 100; } ), // 其他参数... ) )); // 获取设备数据的端点 register_rest_route('iot/v1', '/data/(?P<device_id>[a-zA-Z0-9_-]+)', array( 'methods' => 'GET', 'callback' => array($this, 'get_device_data'), 'permission_callback' => function() { return current_user_can('edit_posts'); } )); } // 处理设备数据 public function handle_device_data($request) { global $wpdb; $parameters = $request->get_params(); // 验证必要参数 if (empty($parameters['device_id'])) { return new WP_Error('missing_device_id', '设备ID是必需的', array('status' => 400)); } // 准备插入数据 $data = array( 'device_id' => sanitize_text_field($parameters['device_id']), 'device_type' => sanitize_text_field($parameters['device_type'] ?? 'unknown'), 'temperature' => isset($parameters['temperature']) ? floatval($parameters['temperature']) : null, 'humidity' => isset($parameters['humidity']) ? floatval($parameters['humidity']) : null, 'pressure' => isset($parameters['pressure']) ? floatval($parameters['pressure']) : null, 'pm25' => isset($parameters['pm25']) ? intval($parameters['pm25']) : null, 'voltage' => isset($parameters['voltage']) ? floatval($parameters['voltage']) : null, 'status' => 'active' ); // 插入数据库 $result = $wpdb->insert($this->table_name, $data); if ($result === false) { return new WP_Error('db_insert_error', '数据插入失败', array('status' => 500)); } // 触发数据接收动作,供其他插件使用 do_action('iot_device_data_received', $data, $wpdb->insert_id); return rest_ensure_response(array( 'success' => true, 'message' => '数据接收成功', 'data_id' => $wpdb->insert_id, 'timestamp' => current_time('mysql') )); } // 验证设备令牌 public function verify_device_token($request) { // 从请求头获取API密钥 $api_key = $request->get_header('X-IoT-API-Key'); if (empty($api_key)) { return false; } // 验证API密钥(这里应该从数据库或配置中获取有效密钥) $valid_keys = get_option('iot_valid_api_keys', array()); return in_array($api_key, $valid_keys); } // 获取设备数据 public function get_device_data($request) { global $wpdb; $device_id = $request->get_param('device_id'); $limit = $request->get_param('limit') ? intval($request->get_param('limit')) : 100; $query = $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE device_id = %s ORDER BY created_at DESC LIMIT %d", $device_id, $limit ); $results = $wpdb->get_results($query, ARRAY_A); if (empty($results)) { return rest_ensure_response(array( 'success' => false, 'message' => '未找到设备数据' )); } return rest_ensure_response(array( 'success' => true, 'data' => $results, 'count' => count($results) )); } // 添加管理菜单 public function add_admin_menu() { add_menu_page( '物联网设备管理', 'IoT设备', 'manage_options', 'iot-device-manager', array($this, 'display_admin_page'), 'dashicons-admin-generic', 30 ); add_submenu_page( 'iot-device-manager', '设备数据', '数据查看', 'manage_options', 'iot-device-data', array($this, 'display_device_data_page') ); } // 显示管理页面 public function display_admin_page() { ?> <div class="wrap"> <h1>物联网设备管理</h1> <div class="card"> <h2>API端点信息</h2> <p><strong>数据提交URL:</strong> <?php echo rest_url('iot/v1/data'); ?></p> <p><strong>请求方法:</strong> POST</p> <p><strong>内容类型:</strong> application/json</p> <h3>请求示例</h3> <pre> { "device_id": "sensor_001", "device_type": "environment", "temperature": 23.5, "humidity": 65.2, "pressure": 1013.25, "pm25": 35 } </pre> </div> </div> <?php } // 短代码显示仪表板 public function iot_dashboard_shortcode($atts) { $atts = shortcode_atts(array( 'device_id' => '', 'title' => '物联网设备数据面板', 'show_charts' => 'true' ), $atts, 'iot_dashboard'); // 生成唯一ID用于JavaScript $dashboard_id = 'iot-dashboard-' . uniqid(); ob_start(); ?> <div id="<?php echo esc_attr($dashboard_id); ?>" class="iot-dashboard"> <h3><?php echo esc_html($atts['title']); ?></h3> <div class="iot-stats-grid"> <div class="iot-stat-card"> <div class="stat-label">当前温度</div> <div class="stat-value temperature">--</div> <div class="stat-unit">°C</div> </div> <div class="iot-stat-card"> <div class="stat-label">当前湿度</div> <div class="stat-value humidity">--</div> <div class="stat-unit">%</div> </div> <div class="iot-stat-card"> <div class="stat-label">空气质量</div> <div class="stat-value pm25">--</div> <div class="stat-unit">PM2.5</div> </div> <div class="iot-stat-card"> <div class="stat-label">设备状态</div> <div class="stat-value status">--</div> <div class="stat-unit"></div> </div> </div> <?php if ($atts['show_charts'] === 'true') : ?> <div class="iot-charts-container"> <div class="chart-container"> <canvas id="<?php echo esc_attr($dashboard_id); ?>-temp-chart"></canvas> </div> <div class="chart-container"> <canvas id="<?php echo esc_attr($dashboard_id); ?>-humidity-chart"></canvas> </div> </div> <?php endif; ?> <div class="iot-data-table"> <table> <thead> <tr> <th>时间</th> <th>温度(°C)</th> <th>湿度(%)</th> <th>气压(hPa)</th> <th>PM2.5</th> </tr> </thead> <tbody> <!-- 数据将通过JavaScript动态加载 --> </tbody> </table> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // 初始化物联网仪表板 initIoTDashboard('<?php echo esc_js($dashboard_id); ?>', '<?php echo esc_js($atts['device_id']); ?>'); }); </script> <?php return ob_get_clean(); } } // 初始化插件 new IoT_Device_Manager(); // 添加前端样式和脚本 add_action('wp_enqueue_scripts', function() { if (has_shortcode(get_post()->post_content, 'iot_dashboard')) { // 引入Chart.js用于数据可视化 wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js', array(), '3.7.0', true); // 引入自定义脚本 wp_enqueue_script('iot-dashboard', plugin_dir_url(__FILE__) . 'js/iot-dashboard.js', array('jquery', 'chart-js'), '1.0.0', true); // 传递AJAX URL到前端 wp_localize_script('iot-dashboard', 'iot_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'rest_url' => rest_url('iot/v1/data/'), 'nonce' => wp_create_nonce('iot_nonce') )); // 添加样式 wp_enqueue_style('iot-dashboard-style', plugin_dir_url(__FILE__) . 'css/iot-dashboard.css'); } }); // 添加AJAX处理 add_action('wp_ajax_get_iot_data', 'handle_iot_data_ajax'); add_action('wp_ajax_nopriv_get_iot_data', 'handle_iot_data_ajax'); function handle_iot_data_ajax() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'iot_nonce')) { wp_die('权限验证失败'); } global $wpdb; $table_name = $wpdb->prefix . 'iot_device_data'; $device_id = sanitize_text_field($_POST['device_id']); $limit = intval($_POST['limit'] ?? 50); $data = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$table_name} WHERE device_id = %s ORDER BY created_at DESC LIMIT %d", $device_id, $limit ), ARRAY_A); wp_send_json_success($data); } 2.3 前端JavaScript代码 (iot-dashboard.js) // iot-dashboard.js function initIoTDashboard(dashboardId, deviceId) { const dashboard = document.getElementById(dashboardId); if (!dashboard) return; // 初始化图表 let tempChart = null; let humidityChart = null; // 获取数据并更新仪表板 function updateDashboard() { if (!deviceId) { console.error('设备ID未指定'); return; } // 使用REST API获取数据 fetch(`${iot_ajax.rest_url}${deviceId}?limit=50`) .then(response => response.json()) .then(data => { if (data.success && data.data.length > 0) { updateStats(data.data[0]); // 更新最新数据 updateCharts(data.data); // 更新图表 updateTable(data.data); // 更新表格 } }) .catch(error => console.error('获取数据失败:', error)); } // 更新统计数据 function updateStats(latestData) { const tempElement = dashboard.querySelector('.temperature'); const humidityElement = dashboard.querySelector('.humidity'); const pm25Element = dashboard.querySelector('.pm25'); const statusElement = dashboard.querySelector('.status'); if (tempElement && latestData.temperature !== null) { tempElement.textContent = latestData.temperature.toFixed(1); // 根据温度设置颜色 tempElement.style.color = getTemperatureColor(latestData.temperature); } if (humidityElement && latestData.humidity !== null) { humidityElement.textContent = latestData.humidity.toFixed(1); } if (pm25Element && latestData.pm25 !== null) { pm25Element.textContent = latestData.pm25; // 根据PM2.5设置颜色 pm25Element.style.color = getPM25Color(latestData.pm25); } statusElement && latestData.status) { statusElement.textContent = latestData.status; statusElement.className = `status status-${latestData.status}`; } } // 更新图表 function updateCharts(data) { // 反转数据,使时间从旧到新 const reversedData = [...data].reverse(); // 准备图表数据 const labels = reversedData.map(item => { const date = new Date(item.created_at); return `${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`; }); const temperatures = reversedData.map(item => item.temperature); const humidities = reversedData.map(item => item.humidity); // 温度图表 const tempCanvas = document.getElementById(`${dashboardId}-temp-chart`); if (tempCanvas) { if (tempChart) { tempChart.destroy(); } tempChart = new Chart(tempCanvas.getContext('2d'), { type: 'line', data: { labels: labels, datasets: [{ label: '温度 (°C)', data: temperatures, borderColor: 'rgb(255, 99, 132)', backgroundColor: 'rgba(255, 99, 132, 0.1)', tension: 0.4, fill: true }] }, options: { responsive: true, plugins: { legend: { position: 'top', }, title: { display: true, text: '温度变化趋势' } }, scales: { y: { beginAtZero: false, title: { display: true, text: '温度 (°C)' } } } } }); } // 湿度图表 const humidityCanvas = document.getElementById(`${dashboardId}-humidity-chart`); if (humidityCanvas) { if (humidityChart) { humidityChart.destroy(); } humidityChart = new Chart(humidityCanvas.getContext('2d'), { type: 'line', data: { labels: labels, datasets: [{ label: '湿度 (%)', data: humidities, borderColor: 'rgb(54, 162, 235)', backgroundColor: 'rgba(54, 162, 235, 0.1)', tension: 0.4, fill: true }] }, options: { responsive: true, plugins: { legend: { position: 'top', }, title: { display: true, text: '湿度变化趋势' } }, scales: { y: { beginAtZero: false, min: 0, max: 100, title: { display: true, text: '湿度 (%)' } } } } }); } } // 更新数据表格 function updateTable(data) { const tbody = dashboard.querySelector('.iot-data-table tbody'); if (!tbody) return; tbody.innerHTML = ''; data.forEach(item => { const row = document.createElement('tr'); const date = new Date(item.created_at); const timeString = `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; row.innerHTML = ` <td>${timeString}</td> <td>${item.temperature !== null ? item.temperature.toFixed(1) : '--'}</td> <td>${item.humidity !== null ? item.humidity.toFixed(1) : '--'}</td> <td>${item.pressure !== null ? item.pressure.toFixed(1) : '--'}</td> <td>${item.pm25 !== null ? item.pm25 : '--'}</td> `; tbody.appendChild(row); }); } // 辅助函数:根据温度获取颜色 function getTemperatureColor(temp) { if (temp < 10) return '#3498db'; // 冷 - 蓝色 if (temp < 20) return '#2ecc71'; // 凉爽 - 绿色 if (temp < 28) return '#f1c40f'; // 舒适 - 黄色 if (temp < 35) return '#e67e22'; // 温暖 - 橙色 return '#e74c3c'; // 热 - 红色 } // 辅助函数:根据PM2.5获取颜色 function getPM25Color(pm25) { if (pm25 <= 35) return '#2ecc71'; // 优 - 绿色 if (pm25 <= 75) return '#f1c40f'; // 良 - 黄色 if (pm25 <= 115) return '#e67e22'; // 轻度污染 - 橙色 if (pm25 <= 150) return '#e74c3c'; // 中度污染 - 红色 return '#8e44ad'; // 重度污染 - 紫色 } // 初始加载数据 updateDashboard(); // 设置定时刷新(每30秒) setInterval(updateDashboard, 30000); // 添加手动刷新按钮 const refreshButton = document.createElement('button'); refreshButton.textContent = '刷新数据'; refreshButton.className = 'iot-refresh-btn'; refreshButton.onclick = updateDashboard; const header = dashboard.querySelector('h3'); if (header) { header.appendChild(refreshButton); } } #### 2.4 前端CSS样式 (iot-dashboard.css) / iot-dashboard.css /.iot-dashboard { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; padding: 20px; background: #f8f9fa; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .iot-dashboard h3 { margin-top: 0; color: #2c3e50; display: flex; justify-content: space-between; align-items: center; } .iot-stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; } .iot-stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); text-align: center; transition: transform 0.3s ease; } .iot-stat-card:hover { transform: translateY(-5px); box-shadow: 0 5px 15px rgba(0,0,0,0.1); } .stat-label { font-size: 14px; color: #7f8c8d; margin-bottom: 10px; text-transform: uppercase; letter-spacing: 1px; } .stat-value { font-size: 36px; font-weight: bold; color: #2c3e50; margin: 10px 0; transition: color 0.3s ease; } .stat-unit { font-size: 14px; color: #95a5a6; } .iot-charts-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin: 30px 0; } .chart-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); } .iot-data-table { margin-top: 30px; overflow-x: auto; } .iot-data-table table { width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 5px rgba(0,0,0,0.05); } .iot-data-table th { background: #34495e; color: white; padding: 15px; text-align: left; font-weight: 600; } .iot-data-table td { padding: 12px 15px; border-bottom: 1px solid #ecf0f1; } .iot-data-table tr:hover { background: #f8f9fa; } .iot-refresh-btn { background: #3498db; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background 0.3s ease; } .iot-refresh-btn:hover { background: #2980b9; } / 状态指示器 /.status-active { color: #27ae60; } .status-inactive { color: #e74c3c; } .status-warning { color: #f39c12; } / 响应式设计 /@media (max-width: 768px) { .iot-stats-grid { grid-template-columns: repeat(2, 1fr); } .iot-charts-container { grid-template-columns: 1fr; } } @media (max-width: 480px) { .iot-stats-grid { grid-template-columns: 1fr; } .iot-dashboard { padding: 10px; } } ### 第三部分:智能硬件端代码实现 #### 3.1 Arduino ESP8266/ESP32示例代码 // iot_sensor_to_wordpress.ino include <ESP8266WiFi.h> // 对于ESP8266 // #include <WiFi.h> // 对于ESP32 include <ArduinoJson.h> include <DHT.h> // 温湿度传感器库 // WiFi配置const char* ssid = "你的WiFi名称";const char* password = "你的WiFi密码"; // WordPress站点配置const char* wordpress_host = "你的网站域名";const int wordpress_port = 443; // HTTPS端口const char* api_endpoint = "/wp-json/iot/v1/data";const char* api_key = "你的API密钥"; // 传感器配置 define DHTPIN D4 // DHT传感器数据引脚 define DHTTYPE DHT22 // DHT22传感器类型 DHT dht(DHTPIN, DHTTYPE); // 设备ID(每个设备唯一)const String device_id = "sensor_001";const String device_type = "environment"; // 定时器unsigned long previousMillis = 0;const long interval = 30000; // 30秒发送一次数据 void setup() { Serial.begin(115200); delay(1000); Serial.println("物联网设备启动中..."); // 初始化传感器 dht.begin(); // 连接WiFi connectToWiFi(); Serial.println("设备准备就绪");} void loop() { unsigned long currentMillis = millis(); // 定时发送数据 if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; // 读取传感器数据 float temperature = dht.readTemperature(); float humidity = dht.readHumidity(); // 检查读取是否成功 if (isnan(temperature) || isnan(humidity)) { Serial.println("读取传感器数据失败"); return; } // 发送数据到WordPress sendToWordPress(temperature, humidity); } // 其他任务... delay(1000);} void connectToWiFi() { Serial.print("连接WiFi: "); Serial.println(ssid); WiFi.begin(ssid, password); int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 20) { delay(500); Serial.print("."); attempts++; } if (WiFi.status() == WL_CONNECTED) { Serial.println("nWiFi连接成功!"); Serial.print("IP地址: "); Serial.println(WiFi.localIP()); } else { Serial.println("nWiFi连接失败"); }} void sendToWordPress(float temperature, float humidity) { Serial.println("准备发送数据到WordPress..."); // 创建JSON数据 DynamicJsonDocument doc(256); doc["device_id"] = device_id; doc["device_type"] = device_type; doc["temperature"] = temperature; doc["humidity"] = humidity; doc["voltage"] = getBatteryVoltage(); // 假设有电池电压读取函数 String jsonData; serializeJson(doc, jsonData); // 使用WiFiClientSecure进行HTTPS连接 WiFiClientSecure client; client.setInsecure(); // 跳过证书验证(生产环境应使用证书验证) if (!client.connect(wordpress_host, wordpress_port)) { Serial.println("连接WordPress失败"); return; } // 构建HTTP请求 String request = String("POST ") + api_endpoint + " HTTP/1.1rn" + "Host: " + wordpress_host + "rn" + "User-Agent: IoT-Device/1.0rn" + "X-IoT-API-Key: " + api_key + "rn" + "Content-Type: application/jsonrn" + "Content-Length: " + jsonData.length() + "rn" + "Connection: closernrn" + jsonData; // 发送请求 client.print(request); // 等待响应 unsigned long timeout = millis(); while (client.available() == 0) { if (millis() - timeout > 5000) { Serial.println("请求超时"); client.stop(); return; } } // 读取响应 while (client.available()) { String line = client.readStringUntil('r'); Serial.print(line); } Serial.println("n数据发送完成"); client.stop();} float getBatteryVoltage() { // 模拟读取电池电压(实际实现取决于硬件) return 3.7; // 示例值} #### 3.2 Raspberry Pi Python示例代码 wordpress_iot_client.py import requestsimport jsonimport timeimport loggingfrom datetime import datetimeimport boardimport adafruit_dht # 对于DHT传感器import psutil # 用于系统监控 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' )logger = logging.getLogger(__name__) class WordPressIoTClient: def __init__(self, config): self.config = config self.device_id = config['device_id'] self.api_url = f"https://{config['wordpress_host']}/wp-json/iot/v1/data" self.headers = { 'X-IoT-API-Key': config['api_key'], 'Content-Type': 'application/json' } # 初始化传感器 self.init_sensors() def init_sensors(self): """初始化传感器""" try: # DHT22温湿度传感器 self.dht_device = adafruit_dht.DHT22(board.D4) logger.info("DHT22传感器初始化成功") except Exception as e: logger.error(f"传感器初始化失败: {e}") self.dht_device = None def read_sensor_data(self): """读取传感器数据""" data = { 'device_id': self.device_id, 'device_type': 'raspberry_pi', 'timestamp': datetime.now().isoformat() } # 读取温湿度 if self.dht_device: try: temperature = self.dht_device.temperature humidity = self.dht_device.humidity if temperature is not None and humidity is not None: data['temperature'] = round(temperature, 2) data['humidity'] = round(humidity, 2) else: logger.warning("读取传感器数据失败") except RuntimeError as e: logger.error(f"读取传感器错误: {e}") # 读取系统信息 data['cpu_usage'] = psutil.cpu_percent() data['memory_usage'] = psutil.virtual_memory().percent data['disk_usage'] = psutil.disk_usage('/').percent # 读取GPIO状态(示例) data['gpio_status'] = self.read_gpio_status() return data def read_gpio_status(self): """读取GPIO状态""" # 这里可以添加实际的GPIO读取逻辑 return { 'pin_17': 1, 'pin_18': 0, 'pin_27': 1 } def send_data(self, data): """发送数据到WordPress""" try: response = requests.post( self.api_url, headers=self.headers, json=data, timeout=10 ) if response.status_code == 200: logger.info(f"数据发送成功: {response.json()}") return True else: logger.error(f"数据发送失败: {response.status_code} - {response.text}") return False except requests.exceptions.RequestException as e: logger.error(f"请求异常: {e}") return False def run(self, interval=30): """主循环""" logger

发表评论

详细教程,为WordPress网站开发活动抽奖与互动游戏化组件

WordPress网站活动抽奖与互动游戏化组件开发详细教程 引言:为什么WordPress网站需要游戏化组件 在当今互联网环境中,用户参与度和互动性已成为网站成功的关键指标。无论是电商平台、内容网站还是企业官网,通过游戏化元素如抽奖、积分系统、进度条等互动组件,可以显著提升用户粘性、延长停留时间并促进转化。WordPress作为全球最流行的内容管理系统,拥有强大的扩展能力,通过代码二次开发,我们可以为网站添加各种互联网常用的小工具功能。 本教程将详细指导您如何为WordPress网站开发活动抽奖与互动游戏化组件,从环境搭建到功能实现,涵盖完整的技术流程。无论您是WordPress开发者还是有一定技术基础的网站管理员,都能通过本教程掌握相关技能。 第一章:开发环境准备与基础架构 1.1 开发环境配置 在开始开发之前,我们需要准备合适的开发环境: 本地开发环境:推荐使用XAMPP、MAMP或Local by Flywheel WordPress安装:最新版本的WordPress(建议5.8+) 代码编辑器:VS Code、Sublime Text或PHPStorm 浏览器开发者工具:Chrome DevTools或Firefox Developer Tools 1.2 创建自定义插件 为了避免主题更新导致功能丢失,我们将创建一个独立的插件来实现所有功能: <?php /** * Plugin Name: 互动游戏化组件套件 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress网站添加抽奖、游戏化互动功能 * Version: 1.0.0 * Author: Your Name * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('GAMIFICATION_PLUGIN_PATH', plugin_dir_path(__FILE__)); define('GAMIFICATION_PLUGIN_URL', plugin_dir_url(__FILE__)); define('GAMIFICATION_VERSION', '1.0.0'); // 初始化插件 require_once GAMIFICATION_PLUGIN_PATH . 'includes/class-gamification-core.php'; function run_gamification_plugin() { $plugin = new Gamification_Core(); $plugin->run(); } run_gamification_plugin(); 1.3 数据库表设计 我们需要创建几个数据库表来存储游戏化组件的数据: // 在插件激活时创建数据库表 register_activation_hook(__FILE__, 'gamification_create_tables'); function gamification_create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 抽奖活动表 $table_name = $wpdb->prefix . 'gamification_lotteries'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL, description text, start_date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, end_date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, status varchar(20) DEFAULT 'draft', settings longtext, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; // 抽奖奖品表 $table_name_prizes = $wpdb->prefix . 'gamification_prizes'; $sql2 = "CREATE TABLE IF NOT EXISTS $table_name_prizes ( id mediumint(9) NOT NULL AUTO_INCREMENT, lottery_id mediumint(9) NOT NULL, name varchar(200) NOT NULL, description text, image_url varchar(500), quantity int NOT NULL DEFAULT 1, probability decimal(5,4) NOT NULL DEFAULT 0.0, remaining int NOT NULL DEFAULT 0, type varchar(50) DEFAULT 'physical', PRIMARY KEY (id), KEY lottery_id (lottery_id) ) $charset_collate;"; // 用户参与记录表 $table_name_participants = $wpdb->prefix . 'gamification_participants'; $sql3 = "CREATE TABLE IF NOT EXISTS $table_name_participants ( id mediumint(9) NOT NULL AUTO_INCREMENT, user_id bigint(20) unsigned, lottery_id mediumint(9) NOT NULL, prize_id mediumint(9), win_status varchar(20) DEFAULT 'pending', participant_data longtext, ip_address varchar(45), created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY user_id (user_id), KEY lottery_id (lottery_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); dbDelta($sql2); dbDelta($sql3); } 第二章:抽奖系统核心功能开发 2.1 抽奖活动管理后台 首先创建抽奖活动的管理界面: // 在includes/class-gamification-admin.php中 class Gamification_Admin { public function __construct() { add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts')); } public function add_admin_menu() { add_menu_page( '游戏化组件', '游戏化组件', 'manage_options', 'gamification', array($this, 'display_main_page'), 'dashicons-games', 30 ); add_submenu_page( 'gamification', '抽奖活动', '抽奖活动', 'manage_options', 'gamification-lotteries', array($this, 'display_lotteries_page') ); add_submenu_page( 'gamification', '添加新抽奖', '添加新抽奖', 'manage_options', 'gamification-add-lottery', array($this, 'display_add_lottery_page') ); } public function display_lotteries_page() { include GAMIFICATION_PLUGIN_PATH . 'admin/views/lotteries-list.php'; } public function display_add_lottery_page() { include GAMIFICATION_PLUGIN_PATH . 'admin/views/lottery-edit.php'; } public function enqueue_admin_scripts($hook) { if (strpos($hook, 'gamification') !== false) { wp_enqueue_style('gamification-admin', GAMIFICATION_PLUGIN_URL . 'assets/css/admin.css', array(), GAMIFICATION_VERSION); wp_enqueue_script('gamification-admin', GAMIFICATION_PLUGIN_URL . 'assets/js/admin.js', array('jquery', 'jquery-ui-sortable'), GAMIFICATION_VERSION, true); // 本地化脚本 wp_localize_script('gamification-admin', 'gamification_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('gamification_nonce') )); } } } 2.2 抽奖活动前端展示 创建前端抽奖界面: // 短代码处理 add_shortcode('lottery_wheel', 'gamification_lottery_wheel_shortcode'); function gamification_lottery_wheel_shortcode($atts) { $atts = shortcode_atts(array( 'id' => 0, 'title' => '幸运大转盘', 'width' => '500', 'height' => '500' ), $atts, 'lottery_wheel'); if (!$atts['id']) { return '<p>请指定抽奖活动ID</p>'; } // 获取抽奖活动数据 global $wpdb; $table_name = $wpdb->prefix . 'gamification_lotteries'; $lottery = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d AND status = 'active'", $atts['id'] )); if (!$lottery) { return '<p>抽奖活动不存在或已结束</p>'; } // 获取奖品数据 $table_name_prizes = $wpdb->prefix . 'gamification_prizes'; $prizes = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name_prizes WHERE lottery_id = %d ORDER BY id ASC", $atts['id'] )); if (empty($prizes)) { return '<p>抽奖活动暂无奖品</p>'; } // 检查用户是否已参与 $user_id = get_current_user_id(); $table_name_participants = $wpdb->prefix . 'gamification_participants'; $has_participated = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_name_participants WHERE lottery_id = %d AND user_id = %d", $atts['id'], $user_id )); ob_start(); ?> <div class="lottery-container" data-lottery-id="<?php echo esc_attr($atts['id']); ?>"> <h3 class="lottery-title"><?php echo esc_html($lottery->title); ?></h3> <div class="lottery-description"><?php echo wp_kses_post($lottery->description); ?></div> <div class="lottery-wheel-container" style="width: <?php echo esc_attr($atts['width']); ?>px; height: <?php echo esc_attr($atts['height']); ?>px;"> <canvas id="lottery-wheel-<?php echo esc_attr($atts['id']); ?>" width="<?php echo esc_attr($atts['width']); ?>" height="<?php echo esc_attr($atts['height']); ?>"></canvas> <div class="wheel-pointer"></div> </div> <div class="lottery-controls"> <?php if ($has_participated): ?> <button class="lottery-button" disabled>您已参与过本次抽奖</button> <?php else: ?> <button class="lottery-button" id="spin-button-<?php echo esc_attr($atts['id']); ?>">开始抽奖</button> <?php endif; ?> </div> <div class="lottery-prizes"> <h4>奖品列表</h4> <ul> <?php foreach ($prizes as $prize): ?> <li> <span class="prize-name"><?php echo esc_html($prize->name); ?></span> <span class="prize-probability">中奖概率: <?php echo esc_html($prize->probability * 100); ?>%</span> <span class="prize-remaining">剩余: <?php echo esc_html($prize->remaining); ?>份</span> </li> <?php endforeach; ?> </ul> </div> </div> <script type="text/javascript"> var lotteryData_<?php echo esc_attr($atts['id']); ?> = { prizes: <?php echo json_encode($prizes); ?>, lotteryId: <?php echo esc_attr($atts['id']); ?>, userId: <?php echo $user_id ?: 0; ?>, ajaxUrl: '<?php echo admin_url('admin-ajax.php'); ?>', nonce: '<?php echo wp_create_nonce('lottery_spin_' . $atts['id']); ?>' }; </script> <?php return ob_get_clean(); } 2.3 抽奖转盘Canvas绘制与动画 创建转盘的JavaScript绘制逻辑: // assets/js/lottery-wheel.js class LotteryWheel { constructor(canvasId, options) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.options = options; this.prizes = options.prizes; this.colors = ['#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40', '#FF6384', '#C9CBCF']; this.isSpinning = false; this.currentRotation = 0; this.spinDuration = 4000; // 旋转持续时间(毫秒) this.init(); } init() { this.drawWheel(); this.drawPointer(); } drawWheel() { const ctx = this.ctx; const width = this.canvas.width; const height = this.canvas.height; const centerX = width / 2; const centerY = height / 2; const radius = Math.min(width, height) / 2 - 10; // 清空画布 ctx.clearRect(0, 0, width, height); // 绘制转盘背景 ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); ctx.fillStyle = '#f0f0f0'; ctx.fill(); ctx.strokeStyle = '#ddd'; ctx.lineWidth = 2; ctx.stroke(); // 计算每个扇形的角度 const prizeCount = this.prizes.length; const anglePerPrize = (2 * Math.PI) / prizeCount; // 绘制每个扇形 for (let i = 0; i < prizeCount; i++) { const startAngle = i * anglePerPrize + this.currentRotation; const endAngle = (i + 1) * anglePerPrize + this.currentRotation; // 绘制扇形 ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.arc(centerX, centerY, radius, startAngle, endAngle); ctx.closePath(); ctx.fillStyle = this.colors[i % this.colors.length]; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.stroke(); // 绘制奖品文字 ctx.save(); ctx.translate(centerX, centerY); ctx.rotate(startAngle + anglePerPrize / 2); ctx.textAlign = 'right'; ctx.fillStyle = '#fff'; ctx.font = 'bold 14px Arial'; ctx.fillText(this.prizes[i].name, radius - 20, 5); ctx.restore(); } // 绘制中心圆 ctx.beginPath(); ctx.arc(centerX, centerY, 20, 0, 2 * Math.PI); ctx.fillStyle = '#fff'; ctx.fill(); ctx.strokeStyle = '#ddd'; ctx.lineWidth = 3; ctx.stroke(); } drawPointer() { const ctx = this.ctx; const width = this.canvas.width; const height = this.canvas.height; const centerX = width / 2; const centerY = height / 2; // 绘制指针三角形 ctx.beginPath(); ctx.moveTo(centerX, 10); ctx.lineTo(centerX - 15, 40); ctx.lineTo(centerX + 15, 40); ctx.closePath(); ctx.fillStyle = '#ff4444'; ctx.fill(); ctx.strokeStyle = '#cc0000'; ctx.lineWidth = 2; ctx.stroke(); } spin(prizeIndex, callback) { if (this.isSpinning) return; this.isSpinning = true; // 计算目标旋转角度(确保旋转多圈后停在指定奖品) const prizeCount = this.prizes.length; const anglePerPrize = (2 * Math.PI) / prizeCount; const targetPrizeAngle = prizeIndex * anglePerPrize; // 计算总旋转角度(多转几圈) const fullRotations = 5; // 完整旋转圈数 const targetRotation = fullRotations * 2 * Math.PI + targetPrizeAngle; // 动画开始时间 const startTime = Date.now(); // 缓动函数 const easeOut = (t) => 1 - Math.pow(1 - t, 3); // 动画函数 const animate = () => { const currentTime = Date.now(); const elapsed = currentTime - startTime; const progress = Math.min(elapsed / this.spinDuration, 1); // 应用缓动函数 const easedProgress = easeOut(progress); // 计算当前旋转角度 this.currentRotation = easedProgress * targetRotation; // 重绘转盘 this.drawWheel(); this.drawPointer(); if (progress < 1) { requestAnimationFrame(animate); } else { this.isSpinning = false; if (callback) callback(); } }; // 开始动画 animate(); } } 第三章:AJAX交互与抽奖逻辑处理 3.1 抽奖逻辑与概率算法 // includes/class-lottery-engine.php class Lottery_Engine { public function process_spin($lottery_id, $user_id) { global $wpdb; // 验证抽奖活动 $lottery = $this->get_lottery($lottery_id); if (!$lottery || $lottery->status !== 'active') { return array('success' => false, 'message' => '抽奖活动不存在或已结束'); } // 检查活动时间 $current_time = current_time('mysql'); if ($current_time < $lottery->start_date || $current_time > $lottery->end_date) { return array('success' => false, 'message' => '抽奖活动未开始或已结束'); } 用户参与次数 $participation_count = $this->get_user_participation_count($lottery_id, $user_id); $settings = maybe_unserialize($lottery->settings); $max_participations = isset($settings['max_participations']) ? intval($settings['max_participations']) : 1; if ($participation_count >= $max_participations) { return array('success' => false, 'message' => '您已达到最大参与次数'); } // 获取奖品列表 $prizes = $this->get_prizes($lottery_id); if (empty($prizes)) { return array('success' => false, 'message' => '抽奖活动暂无奖品'); } // 执行抽奖算法 $winning_prize = $this->calculate_winner($prizes); if (!$winning_prize) { return array('success' => false, 'message' => '抽奖失败,请重试'); } // 减少奖品库存 $this->decrease_prize_stock($winning_prize->id); // 记录用户参与 $participation_id = $this->record_participation($lottery_id, $user_id, $winning_prize->id); return array( 'success' => true, 'prize' => $winning_prize, 'participation_id' => $participation_id, 'message' => '恭喜您中奖了!' ); } private function calculate_winner($prizes) { // 计算总概率 $total_probability = 0; $available_prizes = array(); foreach ($prizes as $prize) { if ($prize->remaining > 0) { $total_probability += floatval($prize->probability); $available_prizes[] = $prize; } } if (empty($available_prizes)) { return null; } // 如果总概率小于1,添加"未中奖"选项 if ($total_probability < 1) { $no_prize = new stdClass(); $no_prize->id = 0; $no_prize->name = '未中奖'; $no_prize->probability = 1 - $total_probability; $available_prizes[] = $no_prize; $total_probability = 1; } // 生成随机数 $random = mt_rand() / mt_getrandmax() * $total_probability; // 确定中奖奖品 $cumulative_probability = 0; foreach ($available_prizes as $prize) { $cumulative_probability += floatval($prize->probability); if ($random <= $cumulative_probability) { return $prize->id == 0 ? null : $prize; } } return null; } private function record_participation($lottery_id, $user_id, $prize_id) { global $wpdb; $table_name = $wpdb->prefix . 'gamification_participants'; $user_ip = $_SERVER['REMOTE_ADDR']; $wpdb->insert( $table_name, array( 'user_id' => $user_id, 'lottery_id' => $lottery_id, 'prize_id' => $prize_id, 'win_status' => $prize_id ? 'won' : 'lost', 'ip_address' => $user_ip, 'created_at' => current_time('mysql') ), array('%d', '%d', '%d', '%s', '%s', '%s') ); return $wpdb->insert_id; } } ### 3.2 AJAX处理程序 // 添加AJAX处理add_action('wp_ajax_gamification_spin_wheel', 'handle_spin_wheel_ajax');add_action('wp_ajax_nopriv_gamification_spin_wheel', 'handle_spin_wheel_ajax'); function handle_spin_wheel_ajax() { // 验证nonce if (!check_ajax_referer('lottery_spin_' . $_POST['lottery_id'], 'nonce', false)) { wp_die(json_encode(array( 'success' => false, 'message' => '安全验证失败' ))); } $lottery_id = intval($_POST['lottery_id']); $user_id = get_current_user_id(); // 如果用户未登录,可以创建临时用户或使用IP限制 if (!$user_id) { // 可以根据需要处理未登录用户 wp_die(json_encode(array( 'success' => false, 'message' => '请先登录' ))); } $engine = new Lottery_Engine(); $result = $engine->process_spin($lottery_id, $user_id); wp_die(json_encode($result)); } ### 3.3 前端AJAX调用 // assets/js/lottery-frontend.jsjQuery(document).ready(function($) { $('.lottery-button').on('click', function() { var button = $(this); var container = button.closest('.lottery-container'); var lotteryId = container.data('lottery-id'); var canvasId = 'lottery-wheel-' + lotteryId; // 禁用按钮防止重复点击 button.prop('disabled', true).text('抽奖中...'); // 获取转盘实例 var wheel = window['lotteryWheel_' + lotteryId]; // 发送AJAX请求 $.ajax({ url: gamification_ajax.ajax_url, type: 'POST', data: { action: 'gamification_spin_wheel', lottery_id: lotteryId, nonce: gamification_ajax.nonce }, dataType: 'json', success: function(response) { if (response.success) { // 计算奖品在转盘上的位置 var prizeIndex = 0; if (response.prize) { // 这里需要根据实际奖品列表计算索引 prizeIndex = calculatePrizeIndex(lotteryId, response.prize.id); } else { // 未中奖的位置 prizeIndex = Math.floor(Math.random() * 8); // 随机位置 } // 开始转盘动画 wheel.spin(prizeIndex, function() { // 动画完成后显示结果 showPrizeResult(response); button.text('已参与'); }); } else { alert(response.message); button.prop('disabled', false).text('开始抽奖'); } }, error: function() { alert('网络错误,请重试'); button.prop('disabled', false).text('开始抽奖'); } }); }); function calculatePrizeIndex(lotteryId, prizeId) { // 根据lotteryData计算奖品索引 var lotteryData = window['lotteryData_' + lotteryId]; if (lotteryData && lotteryData.prizes) { for (var i = 0; i < lotteryData.prizes.length; i++) { if (lotteryData.prizes[i].id == prizeId) { return i; } } } return 0; } function showPrizeResult(response) { var message = response.message; var prizeName = response.prize ? response.prize.name : '未中奖'; // 使用SweetAlert或自定义模态框显示结果 if (typeof Swal !== 'undefined') { Swal.fire({ title: prizeName, text: message, icon: response.prize ? 'success' : 'info', confirmButtonText: '确定' }); } else { // 简单的alert显示 alert(prizeName + ' - ' + message); } } }); ## 第四章:积分系统与用户进度追踪 ### 4.1 积分系统数据库设计 // 扩展数据库表function gamification_create_points_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 用户积分表 $table_name_points = $wpdb->prefix . 'gamification_user_points'; $sql = "CREATE TABLE IF NOT EXISTS $table_name_points ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) unsigned NOT NULL, points int NOT NULL DEFAULT 0, level int NOT NULL DEFAULT 1, total_earned int NOT NULL DEFAULT 0, last_updated datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY user_id (user_id), KEY points (points) ) $charset_collate;"; // 积分记录表 $table_name_points_log = $wpdb->prefix . 'gamification_points_log'; $sql2 = "CREATE TABLE IF NOT EXISTS $table_name_points_log ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) unsigned NOT NULL, points_change int NOT NULL, action_type varchar(100) NOT NULL, action_id bigint(20), description text, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY user_id (user_id), KEY action_type (action_type), KEY created_at (created_at) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); dbDelta($sql2); } // 在插件激活时调用register_activation_hook(__FILE__, 'gamification_create_points_tables'); ### 4.2 积分管理类 // includes/class-points-manager.phpclass Points_Manager { public static function add_points($user_id, $points, $action_type, $action_id = null, $description = '') { global $wpdb; if (!$user_id || $points == 0) { return false; } // 记录积分变更 $table_log = $wpdb->prefix . 'gamification_points_log'; $wpdb->insert( $table_log, array( 'user_id' => $user_id, 'points_change' => $points, 'action_type' => $action_type, 'action_id' => $action_id, 'description' => $description, 'created_at' => current_time('mysql') ), array('%d', '%d', '%s', '%d', '%s', '%s') ); // 更新用户总积分 $table_points = $wpdb->prefix . 'gamification_user_points'; // 检查用户是否已有积分记录 $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_points WHERE user_id = %d", $user_id )); if ($existing) { // 更新现有记录 $wpdb->query($wpdb->prepare( "UPDATE $table_points SET points = points + %d, total_earned = total_earned + %d, level = FLOOR(SQRT(total_earned + %d) / 10) + 1 WHERE user_id = %d", $points, max($points, 0), max($points, 0), $user_id )); } else { // 创建新记录 $wpdb->insert( $table_points, array( 'user_id' => $user_id, 'points' => max($points, 0), 'total_earned' => max($points, 0), 'level' => 1, 'last_updated' => current_time('mysql') ), array('%d', '%d', '%d', '%d', '%s') ); } // 检查是否达到升级条件 self::check_level_up($user_id); return true; } public static function get_user_points($user_id) { global $wpdb; $table_points = $wpdb->prefix . 'gamification_user_points'; $result = $wpdb->get_row($wpdb->prepare( "SELECT points, level, total_earned FROM $table_points WHERE user_id = %d", $user_id )); if (!$result) { return array( 'points' => 0, 'level' => 1, 'total_earned' => 0 ); } return array( 'points' => intval($result->points), 'level' => intval($result->level), 'total_earned' => intval($result->total_earned) ); } private static function check_level_up($user_id) { $user_points = self::get_user_points($user_id); $old_level = $user_points['level']; // 计算新等级(每1000积分升一级) $new_level = floor($user_points['total_earned'] / 1000) + 1; if ($new_level > $old_level) { // 触发等级提升事件 do_action('gamification_level_up', $user_id, $old_level, $new_level); // 发送通知 self::send_level_up_notification($user_id, $old_level, $new_level); } } private static function send_level_up_notification($user_id, $old_level, $new_level) { $user = get_user_by('id', $user_id); if (!$user) return; $subject = sprintf('恭喜您升级到 %d 级!', $new_level); $message = sprintf( '亲爱的 %s,恭喜您从 %d 级升级到 %d 级!继续努力获取更多积分吧!', $user->display_name, $old_level, $new_level ); wp_mail($user->user_email, $subject, $message); // 也可以添加站内通知 if (function_exists('bp_notifications_add_notification')) { // BuddyPress 集成 bp_notifications_add_notification(array( 'user_id' => $user_id, 'item_id' => $new_level, 'component_name' => 'gamification', 'component_action' => 'level_up', 'date_notified' => bp_core_current_time(), 'is_new' => 1, )); } } } ### 4.3 积分获取规则 // 定义积分获取规则class Points_Rules { private static $rules = array( 'daily_login' => array( 'points' => 10, 'limit' => 'daily', 'description' => '每日登录奖励' ), 'post_comment' => array( 'points' => 5, 'limit' => 'none', 'description' => '发表评论' ), 'lottery_participation' => array( 'points' => 2, 'limit' => 'daily', 'description' => '参与抽奖' ), 'lottery_win' => array( 'points' => 20, 'limit' => 'none', 'description' => '抽奖中奖' ), 'share_content' => array( 'points' => 15, 'limit' => 'daily', 'description' => '分享内容' ), 'complete_profile' => array( 'points' => 50, 'limit' => 'once', 'description' => '完善个人资料' ) ); public static function apply_rule($user_id, $rule_name, $action_id = null) { if (!isset(self::$rules[$rule_name])) { return false; } $rule = self::$rules[$rule_name]; // 检查限制条件 if (!self::check_limit($user_id, $rule_name, $rule['limit'])) { return false; } // 添加积分 Points_Manager::add_points( $user_id, $rule['points'], $rule_name, $action_id, $rule['description'] ); return true; } private static function check_limit($user_id, $rule_name, $limit_type) { global $wpdb; $table_log = $wpdb->prefix . 'gamification_points_log'; switch ($limit_type) { case 'daily': // 检查今天是否已经获得过 $today = date('Y-m-d'); $count = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_log WHERE user_id = %d AND action_type = %s AND DATE(created_at) = %s", $user_id, $rule_name, $today )); return $count == 0; case 'once': // 检查是否已经获得过 $count = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM $table_log WHERE user_id = %d AND action_type = %s", $user_id, $rule_name )); return $count == 0; case 'none': default: return true; } } } ## 第五章:进度条与成就系统 ### 5.1 进度条组件 // 进度条短代码add_shortcode('progress_bar', 'gamification_progress_bar_shortcode'); function gamification_progress_bar_shortcode($atts) { $atts = shortcode_atts(array( 'type' => 'points', // points, level, custom 'target' => 1000, 'current' => '', 'height' => '20

发表评论

一步步教你,集成在线设计工具与模板编辑器到你的网站

一步步教你,集成在线设计工具与模板编辑器到你的网站,通过WordPress程序的代码二次开发实现常用互联网小工具功能 引言:为什么你的网站需要集成设计工具与编辑器? 在当今数字化时代,用户体验已成为网站成功的关键因素。无论是电商平台、内容发布网站还是企业官网,用户都期望获得更加个性化、互动性强的在线体验。集成在线设计工具与模板编辑器,可以让你的网站从“静态展示”转变为“动态创作平台”,从而显著提升用户参与度、延长停留时间并提高转化率。 想象一下,一个服装电商网站允许用户自定义T恤图案;一个营销公司网站让客户在线设计宣传海报;一个博客平台提供个性化的文章模板编辑器——这些功能不仅提升了网站的实用价值,也创造了独特的竞争优势。通过WordPress这一全球最流行的内容管理系统,我们可以通过代码二次开发,相对高效地实现这些高级功能。 本文将详细指导你如何一步步在WordPress网站中集成在线设计工具与模板编辑器,并实现一系列常用互联网小工具功能。无论你是WordPress开发者、网站管理员还是有一定技术基础的企业主,都能从本指南中获得实用的解决方案。 第一部分:前期准备与环境搭建 1.1 理解核心概念:什么是在线设计工具与模板编辑器? 在线设计工具是一种基于Web的应用程序,允许用户在浏览器中直接创建和编辑视觉内容,无需安装专业设计软件。常见的功能包括:拖放元素、调整尺寸、更改颜色、添加文字和上传图片等。而模板编辑器则是预设了布局和样式的设计工具,用户可以在模板基础上进行个性化修改,大大降低了设计门槛。 在WordPress中集成这类工具,意味着将这些功能无缝嵌入到你的网站页面中,让访问者能够直接使用。这通常需要结合前端界面库、后端处理逻辑和数据库存储等技术组件。 1.2 开发环境准备 在开始编码之前,你需要搭建合适的开发环境: 本地开发环境:建议使用Local by Flywheel、XAMPP或MAMP等工具,在本地计算机上搭建WordPress运行环境。这样可以避免在线上网站直接调试可能带来的风险。 代码编辑器:选择一款强大的代码编辑器,如Visual Studio Code、PHPStorm或Sublime Text。确保安装相关插件,如PHP智能提示、CSS自动补全和Git集成等。 浏览器开发者工具:熟悉Chrome或Firefox的开发者工具,这对前端调试至关重要。 版本控制系统:初始化Git仓库来管理代码变更,这是专业开发的基本实践。 备份方案:确保你有完整的网站备份,包括文件和数据库,以防开发过程中出现不可逆的错误。 1.3 创建子主题或自定义插件 为了避免主题更新覆盖你的修改,最佳实践是创建一个子主题或自定义插件: 创建子主题的方法: 在/wp-content/themes/目录下创建新文件夹,如my-custom-design-tool 创建style.css文件,添加主题信息头: /* Theme Name: 我的设计工具子主题 Template: parent-theme-folder-name */ 创建functions.php文件,用于添加自定义功能 创建自定义插件的方法: 在/wp-content/plugins/目录下创建新文件夹,如design-tool-integration 创建主插件文件design-tool-integration.php: <?php /** * Plugin Name: 设计工具集成插件 * Description: 为WordPress网站集成在线设计工具和模板编辑器 */ 第二部分:集成基础在线设计工具 2.1 选择合适的前端设计库 集成设计工具的第一步是选择合适的前端库。以下是几个优秀的选择: Fabric.js:一个强大的Canvas库,专门用于创建交互式设计工具。它提供了丰富的对象模型、序列化功能和交互控制。 Konva.js:另一个基于Canvas的2D绘图库,性能优异,支持桌面和移动设备。 tldraw:开源的绘图库,提供流畅的绘图体验和丰富的图形工具。 PixiJS:专注于性能的2D渲染引擎,适合需要处理大量图形元素的复杂设计工具。 对于大多数应用场景,Fabric.js提供了最全面的功能集和活跃的社区支持。我们将以Fabric.js为例进行后续讲解。 2.2 在WordPress中引入Fabric.js 有几种方法可以将Fabric.js引入WordPress: 方法一:使用CDN链接(最简单)在主题或插件的PHP文件中添加: function enqueue_design_tool_scripts() { wp_enqueue_script('fabric-js', 'https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.5.0/fabric.min.js', array(), '4.5.0', true); wp_enqueue_script('design-tool-main', get_stylesheet_directory_uri() . '/js/design-tool.js', array('fabric-js', 'jquery'), '1.0.0', true); wp_enqueue_style('design-tool-style', get_stylesheet_directory_uri() . '/css/design-tool.css'); } add_action('wp_enqueue_scripts', 'enqueue_design_tool_scripts'); 方法二:本地托管(推荐,更稳定) 从Fabric.js官网下载最新版本 将文件放入主题的/js/目录 修改上述代码中的CDN链接为本地路径 2.3 创建基础画布编辑器 接下来,我们创建一个基础的设计画布。首先,在页面模板中添加画布容器: // 在主题模板文件中添加 function render_design_tool() { ob_start(); ?> <div class="design-tool-container"> <div class="tool-header"> <h2>在线设计工具</h2> </div> <div class="tool-body"> <div class="tool-sidebar"> <!-- 工具选项将在这里添加 --> </div> <div class="tool-main"> <canvas id="design-canvas" width="800" height="600"></canvas> </div> </div> <div class="tool-footer"> <button id="save-design" class="btn btn-primary">保存设计</button> <button id="export-png" class="btn btn-secondary">导出PNG</button> </div> </div> <?php return ob_get_clean(); } add_shortcode('design_tool', 'render_design_tool'); 然后,创建JavaScript文件初始化画布: // js/design-tool.js jQuery(document).ready(function($) { // 初始化画布 var canvas = new fabric.Canvas('design-canvas', { backgroundColor: '#ffffff', preserveObjectStacking: true }); // 设置画布尺寸 canvas.setDimensions({ width: 800, height: 600 }, { cssOnly: false }); // 添加矩形工具 $('#add-rectangle').on('click', function() { var rect = new fabric.Rect({ left: 100, top: 100, fill: '#ff0000', width: 100, height: 100 }); canvas.add(rect); canvas.setActiveObject(rect); }); // 添加文字工具 $('#add-text').on('click', function() { var text = new fabric.Textbox('双击编辑文字', { left: 50, top: 50, width: 200, fontSize: 20, fill: '#000000' }); canvas.add(text); canvas.setActiveObject(text); }); // 保存设计到服务器 $('#save-design').on('click', function() { var designData = JSON.stringify(canvas.toJSON()); $.ajax({ url: designToolAjax.ajax_url, type: 'POST', data: { action: 'save_design', design_data: designData, nonce: designToolAjax.nonce }, success: function(response) { if(response.success) { alert('设计已保存!'); } else { alert('保存失败:' + response.data); } } }); }); // 导出为PNG $('#export-png').on('click', function() { var dataURL = canvas.toDataURL({ format: 'png', quality: 1 }); // 创建下载链接 var link = document.createElement('a'); link.download = 'my-design.png'; link.href = dataURL; document.body.appendChild(link); link.click(); document.body.removeChild(link); }); }); 2.4 实现后端保存功能 设计数据需要保存到服务器。在WordPress中,我们使用AJAX处理: // 在functions.php或插件文件中添加 function save_design_callback() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'design_tool_nonce')) { wp_die('安全验证失败'); } $design_data = json_decode(stripslashes($_POST['design_data']), true); // 获取当前用户ID $user_id = get_current_user_id(); // 创建或更新设计 $design_id = wp_insert_post(array( 'post_type' => 'design', 'post_title' => '用户设计 ' . current_time('mysql'), 'post_status' => 'publish', 'post_author' => $user_id, 'meta_input' => array( '_design_data' => $design_data, '_design_preview' => generate_design_preview($design_data) // 生成预览图 ) )); if ($design_id) { wp_send_json_success(array( 'design_id' => $design_id, 'message' => '设计保存成功' )); } else { wp_send_json_error('保存失败'); } } add_action('wp_ajax_save_design', 'save_design_callback'); add_action('wp_ajax_nopriv_save_design', 'save_design_callback'); // 如果允许未登录用户保存 // 注册设计自定义文章类型 function register_design_post_type() { register_post_type('design', array( 'labels' => array( 'name' => __('设计作品'), 'singular_name' => __('设计') ), 'public' => true, 'has_archive' => true, 'supports' => array('title', 'thumbnail'), 'show_in_rest' => true // 启用Gutenberg编辑器支持 ) ); } add_action('init', 'register_design_post_type'); 第三部分:构建模板编辑器系统 3.1 设计模板数据结构 模板编辑器需要一种结构化的方式来定义模板。我们使用JSON格式存储模板配置: // 示例模板数据结构 $template_structure = array( 'name' => '社交媒体海报模板', 'width' => 1200, 'height' => 630, 'backgroundColor' => '#ffffff', 'objects' => array( array( 'type' => 'image', 'src' => '/templates/images/placeholder.jpg', 'left' => 100, 'top' => 100, 'width' => 400, 'height' => 300, 'editable' => true, 'properties' => array( 'replaceable' => true, 'minWidth' => 100, 'minHeight' => 100 ) ), array( 'type' => 'textbox', 'text' => '点击编辑标题', 'left' => 550, 'top' => 150, 'fontSize' => 48, 'fontFamily' => 'Arial', 'fill' => '#333333', 'editable' => true, 'properties' => array( 'maxLength' => 100, 'textAlign' => 'center' ) ) ) ); 3.2 创建模板管理界面 我们需要一个管理界面来创建和管理模板。使用WordPress的Admin API创建管理页面: // 添加模板管理菜单 function add_template_admin_menu() { add_menu_page( '设计模板管理', '设计模板', 'manage_options', 'design-templates', 'render_template_admin_page', 'dashicons-layout', 30 ); add_submenu_page( 'design-templates', '添加新模板', '添加模板', 'manage_options', 'add-design-template', 'render_add_template_page' ); } add_action('admin_menu', 'add_template_admin_menu'); // 渲染模板管理页面 function render_template_admin_page() { ?> <div class="wrap"> <h1 class="wp-heading-inline">设计模板管理</h1> <a href="<?php echo admin_url('admin.php?page=add-design-template'); ?>" class="page-title-action">添加新模板</a> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>ID</th> <th>模板名称</th> <th>尺寸</th> <th>创建时间</th> <th>操作</th> </tr> </thead> <tbody> <?php $templates = get_posts(array( 'post_type' => 'design_template', 'posts_per_page' => -1 )); foreach ($templates as $template) { $template_data = get_post_meta($template->ID, '_template_data', true); ?> <tr> <td><?php echo $template->ID; ?></td> <td><?php echo $template->post_title; ?></td> <td><?php echo isset($template_data['width']) ? $template_data['width'] . '×' . $template_data['height'] : '未设置'; ?></td> <td><?php echo $template->post_date; ?></td> <td> <a href="<?php echo admin_url('admin.php?page=add-design-template&template_id=' . $template->ID); ?>">编辑</a> | <a href="#" class="delete-template" data-id="<?php echo $template->ID; ?>">删除</a> | <a href="#" class="preview-template" data-id="<?php echo $template->ID; ?>">预览</a> </td> </tr> <?php } ?> </tbody> </table> </div> <?php } 3.3 实现模板加载与应用 在前端,我们需要从服务器加载模板并应用到画布: // 加载模板功能 function loadTemplate(templateId) { $.ajax({ url: designToolAjax.ajax_url, type: 'GET', data: { action: 'load_template', template_id: templateId }, success: function(response) { if(response.success) { // 清空当前画布 canvas.clear(); // 设置画布尺寸 canvas.setWidth(response.data.width); canvas.setHeight(response.data.height); // 设置背景色 canvas.setBackgroundColor(response.data.backgroundColor, canvas.renderAll.bind(canvas)); // 加载模板对象 fabric.util.enlivenObjects(response.data.objects, function(objects) { objects.forEach(function(obj) { canvas.add(obj); }); canvas.renderAll(); }); } } }); } // 模板选择器UI function renderTemplateSelector() { $.ajax({ url: designToolAjax.ajax_url, type: 'GET', data: { action: 'get_templates' }, success: function(response) { if(response.success) { var $selector = $('#template-selector'); $selector.empty(); response.data.forEach(function(template) { $selector.append( '<div class="template-thumbnail" data-id="' + template.id + '">' + '<img src="' + template.thumbnail + '" alt="' + template.name + '">' + '<div class="template-name">' + template.name + '</div>' + '</div>' ); }); // 绑定点击事件 $('.template-thumbnail').on('click', function() { var templateId = $(this).data('id'); loadTemplate(templateId); }); } } }); } 第四部分:集成常用互联网小工具功能 4.1 图像处理工具集成 现代设计工具离不开图像处理功能。我们可以集成一些常见的图像处理工具: // 图像处理功能后端 function process_image_callback() { $action = $_POST['action_type']; $image_data = $_POST['image_data']; // 解码base64图像 $image_data = str_replace('data:image/png;base64,', '', $image_data); $image_data = str_replace(' ', '+', $image_data); $image_binary = base64_decode($image_data); // 使用GD库处理图像 $image = imagecreatefromstring($image_binary); switch($action) { case 'grayscale': imagefilter($image, IMG_FILTER_GRAYSCALE); break; case 'sepia': // 实现棕褐色滤镜 imagefilter($image, IMG_FILTER_GRAYSCALE); imagefilter($image, IMG_FILTER_COLORIZE, 112, 66, 20); break; case 'brightness': $level = intval($_POST['level']); imagefilter($image, IMG_FILTER_BRIGHTNESS, $level); break; case 'crop': $x = intval($_POST['x']); $y = intval($_POST['y']); $width = intval($_POST['width']); $height = intval($_POST['height']); $image = imagecrop($image, ['x' => $x, 'y' => $y, 'width' => $width, 'height' => $height]); 4.2 字体管理与Google Fonts集成 设计工具中字体选择是核心功能。我们可以集成Google Fonts API,提供丰富的字体选择: // 字体管理功能 class Font_Manager { private $google_fonts_api_key = 'YOUR_GOOGLE_API_KEY'; public function __construct() { add_action('wp_ajax_get_google_fonts', array($this, 'get_google_fonts_callback')); add_action('wp_ajax_load_font_to_canvas', array($this, 'load_font_to_canvas_callback')); } // 获取Google Fonts列表 public function get_google_fonts_callback() { $transient_key = 'google_fonts_list'; $fonts = get_transient($transient_key); if (false === $fonts) { $response = wp_remote_get( 'https://www.googleapis.com/webfonts/v1/webfonts?key=' . $this->google_fonts_api_key . '&sort=popularity' ); if (!is_wp_error($response)) { $body = wp_remote_retrieve_body($response); $fonts_data = json_decode($body, true); $fonts = array(); foreach ($fonts_data['items'] as $font) { $fonts[] = array( 'family' => $font['family'], 'variants' => $font['variants'], 'category' => $font['category'] ); } // 缓存24小时 set_transient($transient_key, $fonts, DAY_IN_SECONDS); } } wp_send_json_success($fonts); } // 动态加载字体到画布 public function load_font_to_canvas_callback() { $font_family = sanitize_text_field($_POST['font_family']); $font_variant = sanitize_text_field($_POST['font_variant']); // 生成字体CSS URL $font_url = $this->generate_font_css_url($font_family, $font_variant); // 将字体添加到页面 $font_face_css = " @font-face { font-family: '{$font_family}'; src: url('{$font_url}'); }"; wp_send_json_success(array( 'font_css' => $font_face_css, 'font_family' => $font_family )); } private function generate_font_css_url($family, $variant) { $family_encoded = str_replace(' ', '+', $family); return "https://fonts.googleapis.com/css2?family={$family_encoded}:wght@{$variant}&display=swap"; } } new Font_Manager(); 前端字体选择器实现: // 字体选择器组件 class FontSelector { constructor(canvas) { this.canvas = canvas; this.fonts = []; this.init(); } async init() { await this.loadFonts(); this.renderFontSelector(); } async loadFonts() { try { const response = await $.ajax({ url: designToolAjax.ajax_url, type: 'GET', data: { action: 'get_google_fonts' } }); if (response.success) { this.fonts = response.data; this.populateFontSelect(); } } catch (error) { console.error('加载字体失败:', error); } } populateFontSelect() { const $fontSelect = $('#font-family-select'); $fontSelect.empty(); // 添加系统字体 const systemFonts = ['Arial', 'Helvetica', 'Times New Roman', 'Georgia', 'Courier New']; systemFonts.forEach(font => { $fontSelect.append(`<option value="${font}">${font}</option>`); }); // 添加Google Fonts this.fonts.slice(0, 50).forEach(font => { // 限制显示数量 $fontSelect.append(`<option value="${font.family}">${font.family}</option>`); }); } renderFontSelector() { const fontControls = ` <div class="font-controls"> <div class="font-control-group"> <label>字体:</label> <select id="font-family-select" class="font-select"> <option value="Arial">Arial</option> </select> </div> <div class="font-control-group"> <label>字号:</label> <input type="range" id="font-size-slider" min="8" max="120" value="24"> <input type="number" id="font-size-input" value="24" min="8" max="120"> </div> <div class="font-control-group"> <label>颜色:</label> <input type="color" id="font-color-picker" value="#000000"> </div> <div class="font-control-group"> <button id="bold-btn" class="font-style-btn">B</button> <button id="italic-btn" class="font-style-btn">I</button> <button id="underline-btn" class="font-style-btn">U</button> </div> </div>`; $('.tool-sidebar').append(fontControls); this.bindEvents(); } bindEvents() { // 字体选择变化 $('#font-family-select').on('change', async (e) => { const fontFamily = $(e.target).val(); const activeObject = this.canvas.getActiveObject(); if (activeObject && activeObject.type === 'textbox') { // 如果是Google Font,先加载 if (this.isGoogleFont(fontFamily)) { await this.loadGoogleFont(fontFamily, '400'); } activeObject.set('fontFamily', fontFamily); this.canvas.renderAll(); } }); // 字号调整 $('#font-size-slider, #font-size-input').on('input change', (e) => { const fontSize = $(e.target).val(); const activeObject = this.canvas.getActiveObject(); if (activeObject && activeObject.type === 'textbox') { activeObject.set('fontSize', parseInt(fontSize)); this.canvas.renderAll(); } // 同步两个输入框的值 if (e.target.id === 'font-size-slider') { $('#font-size-input').val(fontSize); } else { $('#font-size-slider').val(fontSize); } }); } isGoogleFont(fontFamily) { return this.fonts.some(font => font.family === fontFamily); } async loadGoogleFont(fontFamily, variant) { try { const response = await $.ajax({ url: designToolAjax.ajax_url, type: 'POST', data: { action: 'load_font_to_canvas', font_family: fontFamily, font_variant: variant } }); if (response.success) { // 动态添加字体CSS到页面 $('head').append(`<style>${response.data.font_css}</style>`); return true; } } catch (error) { console.error('加载Google字体失败:', error); } return false; } } // 初始化字体选择器 const fontSelector = new FontSelector(canvas); 4.3 素材库与资源管理器 创建可搜索的素材库,包含图标、背景、贴纸等资源: // 素材库管理 class Asset_Library { private $asset_categories = array( 'icons' => '图标', 'backgrounds' => '背景', 'stickers' => '贴纸', 'shapes' => '形状', 'frames' => '边框' ); public function __construct() { add_action('init', array($this, 'register_asset_post_type')); add_action('wp_ajax_search_assets', array($this, 'search_assets_callback')); add_action('wp_ajax_upload_asset', array($this, 'upload_asset_callback')); } public function register_asset_post_type() { register_post_type('design_asset', array( 'labels' => array( 'name' => __('设计素材'), 'singular_name' => __('素材') ), 'public' => true, 'has_archive' => false, 'show_in_menu' => true, 'menu_position' => 31, 'menu_icon' => 'dashicons-images-alt2', 'supports' => array('title', 'thumbnail'), 'taxonomies' => array('asset_category'), 'show_in_rest' => true ) ); // 注册素材分类 register_taxonomy('asset_category', 'design_asset', array( 'labels' => array( 'name' => __('素材分类'), 'singular_name' => __('分类') ), 'hierarchical' => true, 'show_in_rest' => true ) ); } public function search_assets_callback() { $search_term = sanitize_text_field($_GET['search']); $category = sanitize_text_field($_GET['category']); $page = intval($_GET['page']) ?: 1; $per_page = 20; $args = array( 'post_type' => 'design_asset', 'posts_per_page' => $per_page, 'paged' => $page, 'post_status' => 'publish' ); if (!empty($search_term)) { $args['s'] = $search_term; } if (!empty($category) && $category !== 'all') { $args['tax_query'] = array( array( 'taxonomy' => 'asset_category', 'field' => 'slug', 'terms' => $category ) ); } $query = new WP_Query($args); $assets = array(); while ($query->have_posts()) { $query->the_post(); $post_id = get_the_ID(); $assets[] = array( 'id' => $post_id, 'title' => get_the_title(), 'thumbnail' => get_the_post_thumbnail_url($post_id, 'medium'), 'full_url' => wp_get_attachment_url(get_post_thumbnail_id($post_id)), 'type' => $this->get_asset_type($post_id), 'category' => wp_get_post_terms($post_id, 'asset_category', array('fields' => 'names')) ); } wp_reset_postdata(); wp_send_json_success(array( 'assets' => $assets, 'total_pages' => $query->max_num_pages, 'current_page' => $page )); } private function get_asset_type($post_id) { $mime_type = get_post_mime_type(get_post_thumbnail_id($post_id)); if (strpos($mime_type, 'image/svg') !== false) { return 'svg'; } elseif (strpos($mime_type, 'image/') !== false) { return 'image'; } else { return 'other'; } } public function upload_asset_callback() { // 检查用户权限 if (!current_user_can('upload_files')) { wp_send_json_error('没有上传权限'); } // 处理文件上传 if (!function_exists('wp_handle_upload')) { require_once(ABSPATH . 'wp-admin/includes/file.php'); } $uploadedfile = $_FILES['asset_file']; $category = sanitize_text_field($_POST['category']); $upload_overrides = array('test_form' => false); $movefile = wp_handle_upload($uploadedfile, $upload_overrides); if ($movefile && !isset($movefile['error'])) { // 创建附件 $attachment = array( 'post_mime_type' => $movefile['type'], 'post_title' => sanitize_file_name($_POST['title']), 'post_content' => '', 'post_status' => 'inherit' ); $attach_id = wp_insert_attachment($attachment, $movefile['file']); // 生成缩略图 require_once(ABSPATH . 'wp-admin/includes/image.php'); $attach_data = wp_generate_attachment_metadata($attach_id, $movefile['file']); wp_update_attachment_metadata($attach_id, $attach_data); // 创建设计素材文章 $asset_post_id = wp_insert_post(array( 'post_type' => 'design_asset', 'post_title' => sanitize_text_field($_POST['title']), 'post_status' => 'publish', 'meta_input' => array( '_asset_file_id' => $attach_id ) )); // 设置特色图片 set_post_thumbnail($asset_post_id, $attach_id); // 设置分类 if (!empty($category)) { wp_set_post_terms($asset_post_id, array($category), 'asset_category'); } wp_send_json_success(array( 'message' => '素材上传成功', 'asset_id' => $asset_post_id, 'url' => $movefile['url'] )); } else { wp_send_json_error($movefile['error']); } } } new Asset_Library(); 前端素材库界面: // 素材库组件 class AssetLibrary { constructor(canvas) { this.canvas = canvas; this.currentPage = 1; this.totalPages = 1; this.currentCategory = 'all'; this.searchTerm = ''; this.init(); } init() { this.renderLibraryUI(); this.loadAssets(); } renderLibraryUI() { const libraryHTML = ` <div class="asset-library"> <div class="library-header"> <h3>素材库</h3> <div class="library-controls"> <input type="text" id="asset-search" placeholder="搜索素材..."> <select id="asset-category"> <option value="all">所有分类</option> <option value="icons">图标</option> <option value="backgrounds">背景</option> <option value="stickers">贴纸</option> <option value="shapes">形状</option> <option value="frames">边框</option> </select> <button id="upload-asset-btn" class="btn-small">上传素材</button> </div> </div> <div class="library-content" id="asset-grid"> <!-- 素材将在这里显示 --> </div> <div class="library-footer"> <div class="pagination"> <button id="prev-page" disabled>上一页</button> <span id="page-info">第 1 页 / 共 1 页</span> <button id="next-page" disabled>下一页</button> </div> </div> </div>`; $('.tool-sidebar').append(libraryHTML); this.bindEvents(); } bindEvents() { // 搜索功能 $('#asset-search').on('keyup', debounce(() => { this.searchTerm = $('#asset-search').val(); this.currentPage = 1; this.loadAssets(); }, 500)); // 分类筛选 $('#asset-category').on('change', () => { this.currentCategory = $('#asset-category').val(); this.currentPage = 1; this.loadAssets(); }); // 分页 $('#prev-page').on('click', () => { if (this.currentPage > 1) { this.currentPage--; this.loadAssets(); } }); $('#next-page').on('click', () => { if (this.currentPage < this.totalPages) { this.currentPage++; this.loadAssets(); } }); // 上传素材 $('#upload-asset-btn').on('click', () => { this.showUploadModal(); }); } async loadAssets() { try { const response = await $.ajax({ url: designToolAjax.ajax_url, type: 'GET', data: { action: 'search_assets', search: this.searchTerm, category: this.currentCategory, page: this.currentPage } }); if (response.success) { this.renderAssets(response.data.assets); this.updatePagination(response.data); } } catch (error) { console.error('加载素材失败:', error); } } renderAssets(assets) { const $assetGrid = $('#asset-grid'); $assetGrid.empty(); if (assets.length === 0) { $assetGrid.html('<div class="no-assets">未找到素材</div>'); return; } assets.forEach(asset => { const assetItem = ` <div class="asset-item" data-id="${asset.id}" data-url="${asset.full_url}" data-type="${asset.type}"> <div class="asset-thumbnail"> <img src="${asset.thumbnail}" alt="${asset.title}" loading="lazy"> </div> <div class="asset-info"> <div class="asset-title">${asset.title}</div> <div class="asset-category">${asset.category.join(', ')}</div> </div> </div>`; $assetGrid.append(assetItem); }); // 绑定点击事件 $('.asset-item').on('click', (e) => { const $item = $(e.currentTarget); this.addAssetToCanvas( $item.data('url'), $item.data('type') ); }); } addAssetToCanvas(url, type) {

发表评论