文章目录
-
- 在当今电子商务快速发展的时代,为在线商店提供虚拟试用功能已成为提升用户体验和转化率的重要手段。本教程将详细介绍如何为WordPress开发一个小批量定制插件,集成虚拟试用功能,让用户能够在购买前预览产品效果。
- 首先,我们需要创建一个基础的WordPress插件结构。以下是插件的基本文件组织: <?php /** * Plugin Name: 虚拟试用定制插件 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress产品提供虚拟试用功能的小批量定制插件 * Version: 1.0.0 * Author: 你的名字 * License: GPL v2 or later */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('VT_PLUGIN_PATH', plugin_dir_path(__FILE__)); define('VT_PLUGIN_URL', plugin_dir_url(__FILE__)); define('VT_VERSION', '1.0.0'); // 初始化插件 class Virtual_Try_On_Plugin { private static $instance = null; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->init_hooks(); } private function init_hooks() { // 激活/停用插件时的操作 register_activation_hook(__FILE__, array($this, 'activate')); register_deactivation_hook(__FILE__, array($this, 'deactivate')); // 初始化 add_action('init', array($this, 'init')); // 管理界面 add_action('admin_menu', array($this, 'add_admin_menu')); // 前端资源 add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets')); } public function activate() { // 创建必要的数据库表 $this->create_database_tables(); // 设置默认选项 update_option('vt_plugin_version', VT_VERSION); update_option('vt_enable_virtual_try', 'yes'); update_option('vt_max_upload_size', 5); // MB } public function deactivate() { // 清理临时数据 // 注意:这里不删除用户数据,仅清理临时文件 $this->cleanup_temp_files(); } public function init() { // 初始化代码 $this->register_shortcodes(); } // 其他方法将在下面逐步实现 } // 启动插件 Virtual_Try_On_Plugin::get_instance(); ?>
- 虚拟试用功能需要存储用户上传的图像、试用记录等数据。以下是数据库表创建和模型类的实现: <?php // 在Virtual_Try_On_Plugin类中添加以下方法 private function create_database_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // 虚拟试用记录表 $table_name = $wpdb->prefix . 'virtual_try_records'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, user_id bigint(20) DEFAULT NULL, product_id bigint(20) NOT NULL, original_image varchar(255) NOT NULL, processed_image varchar(255) DEFAULT NULL, try_on_data longtext, created_at datetime DEFAULT CURRENT_TIMESTAMP, status varchar(20) DEFAULT 'pending', PRIMARY KEY (id), KEY user_id (user_id), KEY product_id (product_id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // 产品虚拟试用设置表 $table_name = $wpdb->prefix . 'product_virtual_try_settings'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, product_id bigint(20) NOT NULL, enable_virtual_try tinyint(1) DEFAULT 1, try_on_type varchar(50) DEFAULT 'overlay', anchor_points longtext, overlay_image varchar(255), created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY product_id (product_id) ) $charset_collate;"; dbDelta($sql); } // 模型类 class VT_Try_Record { public $id; public $user_id; public $product_id; public $original_image; public $processed_image; public $try_on_data; public $created_at; public $status; public static function create($data) { global $wpdb; $table_name = $wpdb->prefix . 'virtual_try_records'; $wpdb->insert( $table_name, $data ); return $wpdb->insert_id; } public static function get_by_id($id) { global $wpdb; $table_name = $wpdb->prefix . 'virtual_try_records'; $result = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $id) ); return $result; } public static function get_user_records($user_id, $limit = 10) { global $wpdb; $table_name = $wpdb->prefix . 'virtual_try_records'; $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $table_name WHERE user_id = %d ORDER BY created_at DESC LIMIT %d", $user_id, $limit ) ); return $results; } } ?>
- 接下来,我们创建前端虚拟试用界面,允许用户上传图片并预览试用效果: <?php // 在插件目录中创建 frontend/try-on-interface.php // 短代码注册方法 private function register_shortcodes() { add_shortcode('virtual_try_on', array($this, 'render_try_on_interface')); } public function render_try_on_interface($atts) { $atts = shortcode_atts(array( 'product_id' => 0, 'width' => '800px', 'height' => '600px' ), $atts); if (!$atts['product_id']) { return '<p>错误:未指定产品ID</p>'; } ob_start(); ?> <div class="virtual-try-on-container" data-product-id="<?php echo esc_attr($atts['product_id']); ?>"> <div class="vt-controls"> <h3>虚拟试用</h3> <div class="upload-section"> <label for="vt-upload-image">上传您的照片:</label> <input type="file" id="vt-upload-image" accept="image/*"> <button id="vt-upload-btn" class="button">上传</button> <div class="upload-progress" style="display:none;"> <div class="progress-bar"></div> <span class="progress-text">0%</span> </div> </div> <div class="adjustment-controls" style="display:none;"> <h4>调整设置</h4> <div class="control-group"> <label>大小:</label> <input type="range" id="vt-size-slider" min="0.5" max="2" step="0.1" value="1"> <span id="vt-size-value">100%</span> </div> <div class="control-group"> <label>旋转:</label> <input type="range" id="vt-rotate-slider" min="-180" max="180" step="1" value="0"> <span id="vt-rotate-value">0°</span> </div> <div class="control-group"> <label>透明度:</label> <input type="range" id="vt-opacity-slider" min="0" max="1" step="0.1" value="1"> <span id="vt-opacity-value">100%</span> </div> <button id="vt-reset-btn" class="button">重置</button> <button id="vt-save-btn" class="button button-primary">保存试用效果</button> </div> </div> <div class="vt-preview-area"> <div class="original-image-container"> <h4>原始图像</h4> <canvas id="vt-original-canvas"></canvas> </div> <div class="result-container"> <h4>试用效果</h4> <canvas id="vt-result-canvas"></canvas> </div> </div> <div class="vt-history" style="display:none;"> <h4>历史记录</h4> <div class="history-items"></div> </div> </div> <?php return ob_get_clean(); } ?>
- 虚拟试用的核心是JavaScript图像处理功能。以下是前端交互逻辑: // 在插件目录中创建 assets/js/virtual-try-on.js (function($) { 'use strict'; class VirtualTryOn { constructor(container) { this.container = container; this.productId = container.data('product-id'); this.originalCanvas = document.getElementById('vt-original-canvas'); this.resultCanvas = document.getElementById('vt-result-canvas'); this.originalCtx = this.originalCanvas.getContext('2d'); this.resultCtx = this.resultCanvas.getContext('2d'); this.originalImage = null; this.overlayImage = null; this.currentSettings = { scale: 1.0, rotation: 0, opacity: 1.0, position: { x: 0, y: 0 } }; this.isDragging = false; this.dragStart = { x: 0, y: 0 }; this.init(); } init() { this.setupEventListeners(); this.loadProductOverlay(); this.setupCanvas(); } setupEventListeners() { // 上传按钮事件 $('#vt-upload-btn').on('click', () => this.handleImageUpload()); // 滑块控制事件 $('#vt-size-slider').on('input', (e) => this.updateScale(e.target.value)); $('#vt-rotate-slider').on('input', (e) => this.updateRotation(e.target.value)); $('#vt-opacity-slider').on('input', (e) => this.updateOpacity(e.target.value)); // 按钮事件 $('#vt-reset-btn').on('click', () => this.resetSettings()); $('#vt-save-btn').on('click', () => this.saveTryOn()); // 画布拖拽事件 this.resultCanvas.addEventListener('mousedown', (e) => this.startDrag(e)); this.resultCanvas.addEventListener('mousemove', (e) => this.drag(e)); this.resultCanvas.addEventListener('mouseup', () => this.endDrag()); this.resultCanvas.addEventListener('mouseleave', () => this.endDrag()); } setupCanvas() { // 设置画布尺寸 const containerWidth = this.container.width(); this.originalCanvas.width = containerWidth / 2 - 20; this.originalCanvas.height = 400; this.resultCanvas.width = containerWidth / 2 - 20; this.resultCanvas.height = 400; } async loadProductOverlay() { try { const response = await $.ajax({ url: vt_ajax.ajax_url, method: 'POST', data: { action: 'vt_get_overlay_image', product_id: this.productId, nonce: vt_ajax.nonce } }); if (response.success && response.data.overlay_url) { this.overlayImage = new Image(); this.overlayImage.src = response.data.overlay_url; this.overlayImage.onload = () => { console.log('Overlay image loaded'); }; } } catch (error) { console.error('Failed to load overlay image:', error); } } handleImageUpload() { const fileInput = document.getElementById('vt-upload-image'); const file = fileInput.files[0]; if (!file) { alert('请选择一张图片'); return; } // 显示上传进度 $('.upload-progress').show(); // 创建FormData对象 const formData = new FormData(); formData.append('action', 'vt_upload_image'); formData.append('product_id', this.productId); formData.append('nonce', vt_ajax.nonce); formData.append('image', file); // 使用XMLHttpRequest来获取上传进度 const xhr = new XMLHttpRequest(); xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { const percentComplete = (e.loaded / e.total) * 100; $('.progress-bar').css('width', percentComplete + '%'); $('.progress-text').text(Math.round(percentComplete) + '%'); } }); xhr.onload = () => { if (xhr.status === 200) { const response = JSON.parse(xhr.responseText); if (response.success) { this.loadOriginalImage(response.data.image_url); $('.adjustment-controls').show(); $('.vt-history').show(); } else { alert('上传失败: ' + response.data.message); } } $('.upload-progress').hide(); }; xhr.open('POST', vt_ajax.ajax_url); xhr.send(formData); } loadOriginalImage(imageUrl) { this.originalImage = new Image(); this.originalImage.src = imageUrl; this.originalImage.onload = () => { // 绘制原始图像 this.drawOriginalImage(); // 更新试用效果 this.updateTryOn(); }; } drawOriginalImage() { const canvas = this.originalCanvas; const ctx = this.originalCtx; // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 计算图像尺寸以适应画布 const scale = Math.min( canvas.width / this.originalImage.width, canvas.height / this.originalImage.height ); const width = this.originalImage.width * scale; const height = this.originalImage.height * scale; const x = (canvas.width - width) / 2; const y = (canvas.height - height) / 2; // 绘制图像 ctx.drawImage(this.originalImage, x, y, width, height); } updateTryOn() { if (!this.originalImage || !this.overlayImage) return; const canvas = this.resultCanvas; const ctx = this.resultCtx; // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 绘制原始图像(作为背景) this.drawOriginalImageOnResult(); // 保存当前上下文状态 ctx.save(); // 移动到中心点 ctx.translate(canvas.width / 2, canvas.height / 2); // 应用旋转 ctx.rotate(this.currentSettings.rotation * Math.PI / 180); // 应用缩放 ctx.scale(this.currentSettings.scale, this.currentSettings.scale); // 应用位置偏移 ctx.translate(this.currentSettings.position.x, this.currentSettings.position.y); // 设置透明度 ctx.globalAlpha = this.currentSettings.opacity; // 绘制叠加图像 const overlayX = -this.overlayImage.width / 2; const overlayY = -this.overlayImage.height / 2; ctx.drawImage(this.overlayImage, overlayX, overlayY); // 恢复上下文状态 ctx.restore(); } drawOriginalImageOnResult() { const canvas = this.resultCanvas; const ctx = this.resultCtx; // 计算图像尺寸以适应画布 const scale = Math.min( canvas.width / this.originalImage.width, canvas.height / this.originalImage.height ); const width = this.originalImage.width * scale; const height = this.originalImage.height * scale; const x = (canvas.width - width) / 2; const y = (canvas.height - height) / 2; // 绘制图像 ctx.drawImage(this.originalImage, x, y, width, height); } updateScale(value) { this.currentSettings.scale = parseFloat(value); $('#vt-size-value').text(Math.round(value * 100) + '%'); this.updateTryOn(); } updateRotation(value) { this.currentSettings.rotation = parseInt(value); $('#vt-rotate-value').text(value + '°'); this.updateTryOn(); } updateOpacity(value) { this.currentSettings.opacity = parseFloat(value); $('#vt-opacity-value').text(Math.round(value * 100) + '%'); this.updateTryOn(); } startDrag(e) { const rect = this.resultCanvas.getBoundingClientRect(); this.dragStart = { x: e.clientX - rect.left, y: e.clientY - rect.top }; this.isDragging = true; } drag(e) { if (!this.isDragging) return; const rect = this.resultCanvas.getBoundingClientRect(); const currentX = e.clientX - rect.left; const currentY = e.clientY - rect.top; // 计算拖拽距离 const deltaX = currentX - this.dragStart.x; const deltaY = currentY - this.dragStart.y; // 更新位置 this.currentSettings.position.x += deltaX / this.currentSettings.scale; this.currentSettings.position.y += deltaY / this.currentSettings.scale; // 更新起始点 this.dragStart = { x: currentX, y: currentY }; // 更新试用效果 this.updateTryOn(); } endDrag() { this.isDragging = false; } resetSettings() { this.currentSettings = { scale: 1.0, rotation: 0, opacity: 1.0, position: { x: 0, y: 0 } }; // 重置滑块 $('#vt-size-slider').val(1); $('#vt-rotate-slider').val(0); $('#vt-opacity-slider').val(1); // 更新显示值 $('#vt-size-value').text('100%'); $('#vt-rotate-value').text('0°'); $('#vt-opacity-value').text('100%'); // 更新试用效果 this.updateTryOn(); } async saveTryOn() { try { // 将结果画布转换为DataURL const imageData = this.resultCanvas.toDataURL('image/jpeg', 0.9); const response = await $.ajax({ url: vt_ajax.ajax_url, method: 'POST', data: { action: 'vt_save_try_on', product_id: this.productId, image_data: imageData, settings: JSON.stringify(this.currentSettings), nonce: vt_ajax.nonce } }); if (response.success) { alert('试用效果已保存!'); this.loadHistory(); } else { alert('保存失败: ' + response.data.message); } } catch (error) { console.error('保存失败:', error); alert('保存失败,请重试'); } } async loadHistory() { try { const response = await $.ajax({ url: vt_ajax.ajax_url, method: 'POST', data: { action: 'vt_get_history', product_id: this.productId, nonce: vt_ajax.nonce } }); if (response.success) { this.displayHistory(response.data.history); } } catch (error) { console.error('加载历史记录失败:', error); } } displayHistory(history) { const container = $('.history-items'); container.empty(); if (history.length === 0) { container.html('<p>暂无历史记录</p>'); return; } history.forEach(item => { const itemHtml = ` <div class="history-item" data-id="${item.id}"> <img src="${item.processed_image}" alt="试用效果" style="width: 100px; height: 100px;"> <p>${new Date(item.created_at).toLocaleDateString()}</p> <button class="button small load-history-btn">加载</button> </div> `; container.append(itemHtml); }); // 绑定加载历史记录事件 $('.load-history-btn').on('click', (e) => { const itemId = $(e.target).closest('.history-item').data('id'); this.loadHistoryItem(itemId); }); } async loadHistoryItem(itemId) { try { const response = await $.ajax({ url: vt_ajax.ajax_url, method: 'POST', data: { action: 'vt_load_history_item', item_id: itemId, nonce: vt_ajax.nonce } }); if (response.success) { const settings = response.data.settings; // 更新当前设置 this.currentSettings = settings; // 更新滑块 $('#vt-size-slider').val(settings.scale); $('#vt-rotate-slider').val(settings.rotation); $('#vt-opacity-slider').val(settings.opacity); // 更新显示值 $('#vt-size-value').text(Math.round(settings.scale * 100) + '%'); $('#vt-rotate-value').text(settings.rotation + '°'); $('#vt-opacity-value').text(Math.round(settings.opacity * 100) + '%'); // 更新试用效果 this.updateTryOn(); alert('历史记录已加载'); } } catch (error) { console.error('加载历史记录失败:', error); } } } // 初始化插件 $(document).ready(function() { $('.virtual-try-on-container').each(function() { new VirtualTryOn($(this)); }); }); })(jQuery);
- 现在我们需要创建PHP端处理AJAX请求: <?php // 在Virtual_Try_On_Plugin类中添加AJAX处理方法 private function register_ajax_handlers() { // 前端AJAX处理 add_action('wp_ajax_vt_upload_image', array($this, 'handle_image_upload')); add_action('wp_ajax_nopriv_vt_upload_image', array($this, 'handle_image_upload')); add_action('wp_ajax_vt_get_overlay_image', array($this, 'get_overlay_image')); add_action('wp_ajax_nopriv_vt_get_overlay_image', array($this, 'get_overlay_image')); add_action('wp_ajax_vt_save_try_on', array($this, 'save_try_on')); add_action('wp_ajax_nopriv_vt_save_try_on', array($this, 'save_try_on')); add_action('wp_ajax_vt_get_history', array($this, 'get_try_on_history')); add_action('wp_ajax_nopriv_vt_get_history', array($this, 'get_try_on_history')); add_action('wp_ajax_vt_load_history_item', array($this, 'load_history_item')); add_action('wp_ajax_nopriv_vt_load_history_item', array($this, 'load_history_item')); } public function handle_image_upload() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'vt_ajax_nonce')) { wp_send_json_error(array('message' => '安全验证失败')); return; } // 检查文件上传 if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) { wp_send_json_error(array('message' => '文件上传失败')); return; } // 检查文件类型 $allowed_types = array('image/jpeg', 'image/png', 'image/gif'); $file_type = wp_check_filetype($_FILES['image']['name']); if (!in_array($file_type['type'], $allowed_types)) { wp_send_json_error(array('message' => '不支持的文件类型')); return; } // 检查文件大小 $max_size = get_option('vt_max_upload_size', 5) * 1024 * 1024; // 转换为字节 if ($_FILES['image']['size'] > $max_size) { wp_send_json_error(array('message' => '文件大小超过限制')); return; } // 处理文件上传 require_once(ABSPATH . 'wp-admin/includes/file.php'); $upload_overrides = array('test_form' => false); $movefile = wp_handle_upload($_FILES['image'], $upload_overrides); if ($movefile && !isset($movefile['error'])) { // 创建缩略图 $image_path = $movefile['file']; $image_url = $movefile['url']; // 保存到数据库 $user_id = get_current_user_id(); $product_id = intval($_POST['product_id']); $record_id = VT_Try_Record::create(array( 'user_id' => $user_id ?: null, 'product_id' => $product_id, 'original_image' => $image_url, 'status' => 'uploaded' )); wp_send_json_success(array( 'image_url' => $image_url, 'record_id' => $record_id )); } else { wp_send_json_error(array('message' => $movefile['error'])); } } public function get_overlay_image() { if (!wp_verify_nonce($_POST['nonce'], 'vt_ajax_nonce')) { wp_send_json_error(array('message' => '安全验证失败')); return; } $product_id = intval($_POST['product_id']); // 获取产品叠加图像 global $wpdb; $table_name = $wpdb->prefix . 'product_virtual_try_settings'; $settings = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE product_id = %d", $product_id )); if ($settings && $settings->overlay_image) { wp_send_json_success(array( 'overlay_url' => $settings->overlay_image )); } else { // 返回默认叠加图像 wp_send_json_success(array( 'overlay_url' => VT_PLUGIN_URL . 'assets/images/default-overlay.png' )); } } public function save_try_on() { if (!wp_verify_nonce($_POST['nonce'], 'vt_ajax_nonce')) { wp_send_json_error(array('message' => '安全验证失败')); return; } $product_id = intval($_POST['product_id']); $image_data = $_POST['image_data']; $settings = json_decode(stripslashes($_POST['settings']), true); // 处理Base64图像数据 $image_data = str_replace('data:image/jpeg;base64,', '', $image_data); $image_data = str_replace(' ', '+', $image_data); $image_binary = base64_decode($image_data); // 保存图像文件 $upload_dir = wp_upload_dir(); $filename = 'virtual-try-on-' . time() . '-' . wp_generate_password(8, false) . '.jpg'; $filepath = $upload_dir['path'] . '/' . $filename; if (file_put_contents($filepath, $image_binary)) { $image_url = $upload_dir['url'] . '/' . $filename; // 更新数据库记录 global $wpdb; $table_name = $wpdb->prefix . 'virtual_try_records'; // 获取最新的用户记录 $user_id = get_current_user_id(); $latest_record = $wpdb->get_row($wpdb->prepare( "SELECT id FROM $table_name WHERE user_id = %d AND product_id = %d ORDER BY created_at DESC LIMIT 1", $user_id ?: null, $product_id )); if ($latest_record) { $wpdb->update( $table_name, array( 'processed_image' => $image_url, 'try_on_data' => json_encode($settings), 'status' => 'completed' ), array('id' => $latest_record->id) ); wp_send_json_success(array( 'message' => '保存成功', 'image_url' => $image_url )); } else { wp_send_json_error(array('message' => '未找到原始记录')); } } else { wp_send_json_error(array('message' => '保存图像失败')); } } public function get_try_on_history() { if (!wp_verify_nonce($_POST['nonce'], 'vt_ajax_nonce')) { wp_send_json_error(array('message' => '安全验证失败')); return; } $product_id = intval($_POST['product_id']); $user_id = get_current_user_id(); $history = VT_Try_Record::get_user_records($user_id, 10); // 过滤当前产品的记录 $filtered_history = array_filter($history, function($record) use ($product_id) { return $record->product_id == $product_id && $record->processed_image; }); wp_send_json_success(array( 'history' => array_values($filtered_history) )); } public function load_history_item() { if (!wp_verify_nonce($_POST['nonce'], 'vt_ajax_nonce')) { wp_send_json_error(array('message' => '安全验证失败')); return; } $item_id = intval($_POST['item_id']); $record = VT_Try_Record::get_by_id($item_id); if ($record && $record->try_on_data) { wp_send_json_success(array( 'settings' => json_decode($record->try_on_data) )); } else { wp_send_json_error(array('message' => '未找到记录')); } } ?>
- 为管理员提供产品虚拟试用设置界面: <?php // 在插件目录中创建 admin/product-settings.php public function add_admin_menu() { add_menu_page( '虚拟试用设置', '虚拟试用', 'manage_options', 'virtual-try-on', array($this, 'render_admin_page'), 'dashicons-camera', 30 ); add_submenu_page( 'virtual-try-on', '产品设置', '产品设置', 'manage_options', 'vt-product-settings', array($this, 'render_product_settings_page') ); add_submenu_page( 'virtual-try-on', '试用记录', '试用记录', 'manage_options', 'vt-try-records', array($this, 'render_try_records_page') ); } public function render_product_settings_page() { global $wpdb; // 获取所有产品 $products = get_posts(array( 'post_type' => 'product', 'posts_per_page' => -1, 'post_status' => 'publish' )); // 处理表单提交 if (isset($_POST['submit_settings'])) { $this->save_product_settings($_POST); echo '<div class="notice notice-success"><p>设置已保存!</p></div>'; } ?> <div class="wrap"> <h1>产品虚拟试用设置</h1> <form method="post" action=""> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>产品名称</th> <th>启用虚拟试用</th> <th>试用类型</th> <th>叠加图像</th> <th>锚点设置</th> </tr> </thead> <tbody> <?php foreach ($products as $product): $settings = $this->get_product_settings($product->ID); ?> <tr> <td> <strong><?php echo esc_html($product->post_title); ?></strong> <input type="hidden" name="product_ids[]" value="<?php echo $product->ID; ?>"> </td> <td> <input type="checkbox" name="enable_virtual_try[<?php echo $product->ID; ?>]" value="1" <?php checked($settings->enable_virtual_try, 1); ?>> </td> <td> <select name="try_on_type[<?php echo $product->ID; ?>]"> <option value="overlay" <?php selected($settings->try_on_type, 'overlay'); ?>>叠加模式</option> <option value="replace" <?php selected($settings->try_on_type, 'replace'); ?>>替换模式</option> <option value="ar" <?php selected($settings->try_on_type, 'ar'); ?>>AR模式</option> </select> </td> <td> <div class="upload-container"> <?php if ($settings->overlay_image): ?> <img src="<?php echo esc_url($settings->overlay_image); ?>" style="max-width: 100px; max-height: 100px; display: block; margin-bottom: 5px;"> <?php endif; ?> <input type="text" name="overlay_image[<?php echo $product->ID; ?>]" value="<?php echo esc_url($settings->overlay_image); ?>" class="regular-text"> <button type="button" class="button upload-image-btn" data-target="overlay_image[<?php echo $product->ID; ?>]"> 选择图像 </button> </div> </td> <td> <textarea name="anchor_points[<?php echo $product->ID; ?>]" rows="3" cols="30" placeholder="JSON格式的锚点数据"><?php echo esc_textarea($settings->anchor_points); ?></textarea> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php submit_button('保存设置', 'primary', 'submit_settings'); ?> </form> <script> jQuery(document).ready(function($) { // 媒体上传器 $('.upload-image-btn').on('click', function(e) { e.preventDefault(); var targetInput = $(this).data('target'); var button = $(this); var frame = wp.media({ title: '选择叠加图像', multiple: false, library: { type: 'image' }, button: {
在当今电子商务快速发展的时代,为在线商店提供虚拟试用功能已成为提升用户体验和转化率的重要手段。本教程将详细介绍如何为WordPress开发一个小批量定制插件,集成虚拟试用功能,让用户能够在购买前预览产品效果。
首先,我们需要创建一个基础的WordPress插件结构。以下是插件的基本文件组织:
<?php
/**
* Plugin Name: 虚拟试用定制插件
* Plugin URI: https://yourwebsite.com/
* Description: 为WordPress产品提供虚拟试用功能的小批量定制插件
* Version: 1.0.0
* Author: 你的名字
* License: GPL v2 or later
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('VT_PLUGIN_PATH', plugin_dir_path(__FILE__));
define('VT_PLUGIN_URL', plugin_dir_url(__FILE__));
define('VT_VERSION', '1.0.0');
// 初始化插件
class Virtual_Try_On_Plugin {
private static $instance = null;
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init_hooks();
}
private function init_hooks() {
// 激活/停用插件时的操作
register_activation_hook(__FILE__, array($this, 'activate'));
register_deactivation_hook(__FILE__, array($this, 'deactivate'));
// 初始化
add_action('init', array($this, 'init'));
// 管理界面
add_action('admin_menu', array($this, 'add_admin_menu'));
// 前端资源
add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets'));
}
public function activate() {
// 创建必要的数据库表
$this->create_database_tables();
// 设置默认选项
update_option('vt_plugin_version', VT_VERSION);
update_option('vt_enable_virtual_try', 'yes');
update_option('vt_max_upload_size', 5); // MB
}
public function deactivate() {
// 清理临时数据
// 注意:这里不删除用户数据,仅清理临时文件
$this->cleanup_temp_files();
}
public function init() {
// 初始化代码
$this->register_shortcodes();
}
// 其他方法将在下面逐步实现
}
// 启动插件
Virtual_Try_On_Plugin::get_instance();
?>
虚拟试用功能需要存储用户上传的图像、试用记录等数据。以下是数据库表创建和模型类的实现:
<?php
// 在Virtual_Try_On_Plugin类中添加以下方法
private function create_database_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// 虚拟试用记录表
$table_name = $wpdb->prefix . 'virtual_try_records';
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
user_id bigint(20) DEFAULT NULL,
product_id bigint(20) NOT NULL,
original_image varchar(255) NOT NULL,
processed_image varchar(255) DEFAULT NULL,
try_on_data longtext,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
status varchar(20) DEFAULT 'pending',
PRIMARY KEY (id),
KEY user_id (user_id),
KEY product_id (product_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
// 产品虚拟试用设置表
$table_name = $wpdb->prefix . 'product_virtual_try_settings';
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
product_id bigint(20) NOT NULL,
enable_virtual_try tinyint(1) DEFAULT 1,
try_on_type varchar(50) DEFAULT 'overlay',
anchor_points longtext,
overlay_image varchar(255),
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY product_id (product_id)
) $charset_collate;";
dbDelta($sql);
}
// 模型类
class VT_Try_Record {
public $id;
public $user_id;
public $product_id;
public $original_image;
public $processed_image;
public $try_on_data;
public $created_at;
public $status;
public static function create($data) {
global $wpdb;
$table_name = $wpdb->prefix . 'virtual_try_records';
$wpdb->insert(
$table_name,
$data
);
return $wpdb->insert_id;
}
public static function get_by_id($id) {
global $wpdb;
$table_name = $wpdb->prefix . 'virtual_try_records';
$result = $wpdb->get_row(
$wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $id)
);
return $result;
}
public static function get_user_records($user_id, $limit = 10) {
global $wpdb;
$table_name = $wpdb->prefix . 'virtual_try_records';
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM $table_name WHERE user_id = %d ORDER BY created_at DESC LIMIT %d",
$user_id,
$limit
)
);
return $results;
}
}
?>
接下来,我们创建前端虚拟试用界面,允许用户上传图片并预览试用效果:
<?php
// 在插件目录中创建 frontend/try-on-interface.php
// 短代码注册方法
private function register_shortcodes() {
add_shortcode('virtual_try_on', array($this, 'render_try_on_interface'));
}
public function render_try_on_interface($atts) {
$atts = shortcode_atts(array(
'product_id' => 0,
'width' => '800px',
'height' => '600px'
), $atts);
if (!$atts['product_id']) {
return '<p>错误:未指定产品ID</p>';
}
ob_start();
?>
<div class="virtual-try-on-container" data-product-id="<?php echo esc_attr($atts['product_id']); ?>">
<div class="vt-controls">
<h3>虚拟试用</h3>
<div class="upload-section">
<label for="vt-upload-image">上传您的照片:</label>
<input type="file" id="vt-upload-image" accept="image/*">
<button id="vt-upload-btn" class="button">上传</button>
<div class="upload-progress" style="display:none;">
<div class="progress-bar"></div>
<span class="progress-text">0%</span>
</div>
</div>
<div class="adjustment-controls" style="display:none;">
<h4>调整设置</h4>
<div class="control-group">
<label>大小:</label>
<input type="range" id="vt-size-slider" min="0.5" max="2" step="0.1" value="1">
<span id="vt-size-value">100%</span>
</div>
<div class="control-group">
<label>旋转:</label>
<input type="range" id="vt-rotate-slider" min="-180" max="180" step="1" value="0">
<span id="vt-rotate-value">0°</span>
</div>
<div class="control-group">
<label>透明度:</label>
<input type="range" id="vt-opacity-slider" min="0" max="1" step="0.1" value="1">
<span id="vt-opacity-value">100%</span>
</div>
<button id="vt-reset-btn" class="button">重置</button>
<button id="vt-save-btn" class="button button-primary">保存试用效果</button>
</div>
</div>
<div class="vt-preview-area">
<div class="original-image-container">
<h4>原始图像</h4>
<canvas id="vt-original-canvas"></canvas>
</div>
<div class="result-container">
<h4>试用效果</h4>
<canvas id="vt-result-canvas"></canvas>
</div>
</div>
<div class="vt-history" style="display:none;">
<h4>历史记录</h4>
<div class="history-items"></div>
</div>
</div>
<?php
return ob_get_clean();
}
?>
虚拟试用的核心是JavaScript图像处理功能。以下是前端交互逻辑:
// 在插件目录中创建 assets/js/virtual-try-on.js
(function($) {
'use strict';
class VirtualTryOn {
constructor(container) {
this.container = container;
this.productId = container.data('product-id');
this.originalCanvas = document.getElementById('vt-original-canvas');
this.resultCanvas = document.getElementById('vt-result-canvas');
this.originalCtx = this.originalCanvas.getContext('2d');
this.resultCtx = this.resultCanvas.getContext('2d');
this.originalImage = null;
this.overlayImage = null;
this.currentSettings = {
scale: 1.0,
rotation: 0,
opacity: 1.0,
position: { x: 0, y: 0 }
};
this.isDragging = false;
this.dragStart = { x: 0, y: 0 };
this.init();
}
init() {
this.setupEventListeners();
this.loadProductOverlay();
this.setupCanvas();
}
setupEventListeners() {
// 上传按钮事件
$('#vt-upload-btn').on('click', () => this.handleImageUpload());
// 滑块控制事件
$('#vt-size-slider').on('input', (e) => this.updateScale(e.target.value));
$('#vt-rotate-slider').on('input', (e) => this.updateRotation(e.target.value));
$('#vt-opacity-slider').on('input', (e) => this.updateOpacity(e.target.value));
// 按钮事件
$('#vt-reset-btn').on('click', () => this.resetSettings());
$('#vt-save-btn').on('click', () => this.saveTryOn());
// 画布拖拽事件
this.resultCanvas.addEventListener('mousedown', (e) => this.startDrag(e));
this.resultCanvas.addEventListener('mousemove', (e) => this.drag(e));
this.resultCanvas.addEventListener('mouseup', () => this.endDrag());
this.resultCanvas.addEventListener('mouseleave', () => this.endDrag());
}
setupCanvas() {
// 设置画布尺寸
const containerWidth = this.container.width();
this.originalCanvas.width = containerWidth / 2 - 20;
this.originalCanvas.height = 400;
this.resultCanvas.width = containerWidth / 2 - 20;
this.resultCanvas.height = 400;
}
async loadProductOverlay() {
try {
const response = await $.ajax({
url: vt_ajax.ajax_url,
method: 'POST',
data: {
action: 'vt_get_overlay_image',
product_id: this.productId,
nonce: vt_ajax.nonce
}
});
if (response.success && response.data.overlay_url) {
this.overlayImage = new Image();
this.overlayImage.src = response.data.overlay_url;
this.overlayImage.onload = () => {
console.log('Overlay image loaded');
};
}
} catch (error) {
console.error('Failed to load overlay image:', error);
}
}
handleImageUpload() {
const fileInput = document.getElementById('vt-upload-image');
const file = fileInput.files[0];
if (!file) {
alert('请选择一张图片');
return;
}
// 显示上传进度
$('.upload-progress').show();
// 创建FormData对象
const formData = new FormData();
formData.append('action', 'vt_upload_image');
formData.append('product_id', this.productId);
formData.append('nonce', vt_ajax.nonce);
formData.append('image', file);
// 使用XMLHttpRequest来获取上传进度
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
$('.progress-bar').css('width', percentComplete + '%');
$('.progress-text').text(Math.round(percentComplete) + '%');
}
});
xhr.onload = () => {
if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
if (response.success) {
this.loadOriginalImage(response.data.image_url);
$('.adjustment-controls').show();
$('.vt-history').show();
} else {
alert('上传失败: ' + response.data.message);
}
}
$('.upload-progress').hide();
};
xhr.open('POST', vt_ajax.ajax_url);
xhr.send(formData);
}
loadOriginalImage(imageUrl) {
this.originalImage = new Image();
this.originalImage.src = imageUrl;
this.originalImage.onload = () => {
// 绘制原始图像
this.drawOriginalImage();
// 更新试用效果
this.updateTryOn();
};
}
drawOriginalImage() {
const canvas = this.originalCanvas;
const ctx = this.originalCtx;
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 计算图像尺寸以适应画布
const scale = Math.min(
canvas.width / this.originalImage.width,
canvas.height / this.originalImage.height
);
const width = this.originalImage.width * scale;
const height = this.originalImage.height * scale;
const x = (canvas.width - width) / 2;
const y = (canvas.height - height) / 2;
// 绘制图像
ctx.drawImage(this.originalImage, x, y, width, height);
}
updateTryOn() {
if (!this.originalImage || !this.overlayImage) return;
const canvas = this.resultCanvas;
const ctx = this.resultCtx;
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制原始图像(作为背景)
this.drawOriginalImageOnResult();
// 保存当前上下文状态
ctx.save();
// 移动到中心点
ctx.translate(canvas.width / 2, canvas.height / 2);
// 应用旋转
ctx.rotate(this.currentSettings.rotation * Math.PI / 180);
// 应用缩放
ctx.scale(this.currentSettings.scale, this.currentSettings.scale);
// 应用位置偏移
ctx.translate(this.currentSettings.position.x, this.currentSettings.position.y);
// 设置透明度
ctx.globalAlpha = this.currentSettings.opacity;
// 绘制叠加图像
const overlayX = -this.overlayImage.width / 2;
const overlayY = -this.overlayImage.height / 2;
ctx.drawImage(this.overlayImage, overlayX, overlayY);
// 恢复上下文状态
ctx.restore();
}
drawOriginalImageOnResult() {
const canvas = this.resultCanvas;
const ctx = this.resultCtx;
// 计算图像尺寸以适应画布
const scale = Math.min(
canvas.width / this.originalImage.width,
canvas.height / this.originalImage.height
);
const width = this.originalImage.width * scale;
const height = this.originalImage.height * scale;
const x = (canvas.width - width) / 2;
const y = (canvas.height - height) / 2;
// 绘制图像
ctx.drawImage(this.originalImage, x, y, width, height);
}
updateScale(value) {
this.currentSettings.scale = parseFloat(value);
$('#vt-size-value').text(Math.round(value * 100) + '%');
this.updateTryOn();
}
updateRotation(value) {
this.currentSettings.rotation = parseInt(value);
$('#vt-rotate-value').text(value + '°');
this.updateTryOn();
}
updateOpacity(value) {
this.currentSettings.opacity = parseFloat(value);
$('#vt-opacity-value').text(Math.round(value * 100) + '%');
this.updateTryOn();
}
startDrag(e) {
const rect = this.resultCanvas.getBoundingClientRect();
this.dragStart = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
this.isDragging = true;
}
drag(e) {
if (!this.isDragging) return;
const rect = this.resultCanvas.getBoundingClientRect();
const currentX = e.clientX - rect.left;
const currentY = e.clientY - rect.top;
// 计算拖拽距离
const deltaX = currentX - this.dragStart.x;
const deltaY = currentY - this.dragStart.y;
// 更新位置
this.currentSettings.position.x += deltaX / this.currentSettings.scale;
this.currentSettings.position.y += deltaY / this.currentSettings.scale;
// 更新起始点
this.dragStart = { x: currentX, y: currentY };
// 更新试用效果
this.updateTryOn();
}
endDrag() {
this.isDragging = false;
}
resetSettings() {
this.currentSettings = {
scale: 1.0,
rotation: 0,
opacity: 1.0,
position: { x: 0, y: 0 }
};
// 重置滑块
$('#vt-size-slider').val(1);
$('#vt-rotate-slider').val(0);
$('#vt-opacity-slider').val(1);
// 更新显示值
$('#vt-size-value').text('100%');
$('#vt-rotate-value').text('0°');
$('#vt-opacity-value').text('100%');
// 更新试用效果
this.updateTryOn();
}
async saveTryOn() {
try {
// 将结果画布转换为DataURL
const imageData = this.resultCanvas.toDataURL('image/jpeg', 0.9);
const response = await $.ajax({
url: vt_ajax.ajax_url,
method: 'POST',
data: {
action: 'vt_save_try_on',
product_id: this.productId,
image_data: imageData,
settings: JSON.stringify(this.currentSettings),
nonce: vt_ajax.nonce
}
});
if (response.success) {
alert('试用效果已保存!');
this.loadHistory();
} else {
alert('保存失败: ' + response.data.message);
}
} catch (error) {
console.error('保存失败:', error);
alert('保存失败,请重试');
}
}
async loadHistory() {
try {
const response = await $.ajax({
url: vt_ajax.ajax_url,
method: 'POST',
data: {
action: 'vt_get_history',
product_id: this.productId,
nonce: vt_ajax.nonce
}
});
if (response.success) {
this.displayHistory(response.data.history);
}
} catch (error) {
console.error('加载历史记录失败:', error);
}
}
displayHistory(history) {
const container = $('.history-items');
container.empty();
if (history.length === 0) {
container.html('<p>暂无历史记录</p>');
return;
}
history.forEach(item => {
const itemHtml = `
<div class="history-item" data-id="${item.id}">
<img src="${item.processed_image}" alt="试用效果" style="width: 100px; height: 100px;">
<p>${new Date(item.created_at).toLocaleDateString()}</p>
<button class="button small load-history-btn">加载</button>
</div>
`;
container.append(itemHtml);
});
// 绑定加载历史记录事件
$('.load-history-btn').on('click', (e) => {
const itemId = $(e.target).closest('.history-item').data('id');
this.loadHistoryItem(itemId);
});
}
async loadHistoryItem(itemId) {
try {
const response = await $.ajax({
url: vt_ajax.ajax_url,
method: 'POST',
data: {
action: 'vt_load_history_item',
item_id: itemId,
nonce: vt_ajax.nonce
}
});
if (response.success) {
const settings = response.data.settings;
// 更新当前设置
this.currentSettings = settings;
// 更新滑块
$('#vt-size-slider').val(settings.scale);
$('#vt-rotate-slider').val(settings.rotation);
$('#vt-opacity-slider').val(settings.opacity);
// 更新显示值
$('#vt-size-value').text(Math.round(settings.scale * 100) + '%');
$('#vt-rotate-value').text(settings.rotation + '°');
$('#vt-opacity-value').text(Math.round(settings.opacity * 100) + '%');
// 更新试用效果
this.updateTryOn();
alert('历史记录已加载');
}
} catch (error) {
console.error('加载历史记录失败:', error);
}
}
}
// 初始化插件
$(document).ready(function() {
$('.virtual-try-on-container').each(function() {
new VirtualTryOn($(this));
});
});
})(jQuery);
现在我们需要创建PHP端处理AJAX请求:
<?php
// 在Virtual_Try_On_Plugin类中添加AJAX处理方法
private function register_ajax_handlers() {
// 前端AJAX处理
add_action('wp_ajax_vt_upload_image', array($this, 'handle_image_upload'));
add_action('wp_ajax_nopriv_vt_upload_image', array($this, 'handle_image_upload'));
add_action('wp_ajax_vt_get_overlay_image', array($this, 'get_overlay_image'));
add_action('wp_ajax_nopriv_vt_get_overlay_image', array($this, 'get_overlay_image'));
add_action('wp_ajax_vt_save_try_on', array($this, 'save_try_on'));
add_action('wp_ajax_nopriv_vt_save_try_on', array($this, 'save_try_on'));
add_action('wp_ajax_vt_get_history', array($this, 'get_try_on_history'));
add_action('wp_ajax_nopriv_vt_get_history', array($this, 'get_try_on_history'));
add_action('wp_ajax_vt_load_history_item', array($this, 'load_history_item'));
add_action('wp_ajax_nopriv_vt_load_history_item', array($this, 'load_history_item'));
}
public function handle_image_upload() {
// 验证nonce
if (!wp_verify_nonce($_POST['nonce'], 'vt_ajax_nonce')) {
wp_send_json_error(array('message' => '安全验证失败'));
return;
}
// 检查文件上传
if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
wp_send_json_error(array('message' => '文件上传失败'));
return;
}
// 检查文件类型
$allowed_types = array('image/jpeg', 'image/png', 'image/gif');
$file_type = wp_check_filetype($_FILES['image']['name']);
if (!in_array($file_type['type'], $allowed_types)) {
wp_send_json_error(array('message' => '不支持的文件类型'));
return;
}
// 检查文件大小
$max_size = get_option('vt_max_upload_size', 5) * 1024 * 1024; // 转换为字节
if ($_FILES['image']['size'] > $max_size) {
wp_send_json_error(array('message' => '文件大小超过限制'));
return;
}
// 处理文件上传
require_once(ABSPATH . 'wp-admin/includes/file.php');
$upload_overrides = array('test_form' => false);
$movefile = wp_handle_upload($_FILES['image'], $upload_overrides);
if ($movefile && !isset($movefile['error'])) {
// 创建缩略图
$image_path = $movefile['file'];
$image_url = $movefile['url'];
// 保存到数据库
$user_id = get_current_user_id();
$product_id = intval($_POST['product_id']);
$record_id = VT_Try_Record::create(array(
'user_id' => $user_id ?: null,
'product_id' => $product_id,
'original_image' => $image_url,
'status' => 'uploaded'
));
wp_send_json_success(array(
'image_url' => $image_url,
'record_id' => $record_id
));
} else {
wp_send_json_error(array('message' => $movefile['error']));
}
}
public function get_overlay_image() {
if (!wp_verify_nonce($_POST['nonce'], 'vt_ajax_nonce')) {
wp_send_json_error(array('message' => '安全验证失败'));
return;
}
$product_id = intval($_POST['product_id']);
// 获取产品叠加图像
global $wpdb;
$table_name = $wpdb->prefix . 'product_virtual_try_settings';
$settings = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE product_id = %d",
$product_id
));
if ($settings && $settings->overlay_image) {
wp_send_json_success(array(
'overlay_url' => $settings->overlay_image
));
} else {
// 返回默认叠加图像
wp_send_json_success(array(
'overlay_url' => VT_PLUGIN_URL . 'assets/images/default-overlay.png'
));
}
}
public function save_try_on() {
if (!wp_verify_nonce($_POST['nonce'], 'vt_ajax_nonce')) {
wp_send_json_error(array('message' => '安全验证失败'));
return;
}
$product_id = intval($_POST['product_id']);
$image_data = $_POST['image_data'];
$settings = json_decode(stripslashes($_POST['settings']), true);
// 处理Base64图像数据
$image_data = str_replace('data:image/jpeg;base64,', '', $image_data);
$image_data = str_replace(' ', '+', $image_data);
$image_binary = base64_decode($image_data);
// 保存图像文件
$upload_dir = wp_upload_dir();
$filename = 'virtual-try-on-' . time() . '-' . wp_generate_password(8, false) . '.jpg';
$filepath = $upload_dir['path'] . '/' . $filename;
if (file_put_contents($filepath, $image_binary)) {
$image_url = $upload_dir['url'] . '/' . $filename;
// 更新数据库记录
global $wpdb;
$table_name = $wpdb->prefix . 'virtual_try_records';
// 获取最新的用户记录
$user_id = get_current_user_id();
$latest_record = $wpdb->get_row($wpdb->prepare(
"SELECT id FROM $table_name
WHERE user_id = %d AND product_id = %d
ORDER BY created_at DESC LIMIT 1",
$user_id ?: null,
$product_id
));
if ($latest_record) {
$wpdb->update(
$table_name,
array(
'processed_image' => $image_url,
'try_on_data' => json_encode($settings),
'status' => 'completed'
),
array('id' => $latest_record->id)
);
wp_send_json_success(array(
'message' => '保存成功',
'image_url' => $image_url
));
} else {
wp_send_json_error(array('message' => '未找到原始记录'));
}
} else {
wp_send_json_error(array('message' => '保存图像失败'));
}
}
public function get_try_on_history() {
if (!wp_verify_nonce($_POST['nonce'], 'vt_ajax_nonce')) {
wp_send_json_error(array('message' => '安全验证失败'));
return;
}
$product_id = intval($_POST['product_id']);
$user_id = get_current_user_id();
$history = VT_Try_Record::get_user_records($user_id, 10);
// 过滤当前产品的记录
$filtered_history = array_filter($history, function($record) use ($product_id) {
return $record->product_id == $product_id && $record->processed_image;
});
wp_send_json_success(array(
'history' => array_values($filtered_history)
));
}
public function load_history_item() {
if (!wp_verify_nonce($_POST['nonce'], 'vt_ajax_nonce')) {
wp_send_json_error(array('message' => '安全验证失败'));
return;
}
$item_id = intval($_POST['item_id']);
$record = VT_Try_Record::get_by_id($item_id);
if ($record && $record->try_on_data) {
wp_send_json_success(array(
'settings' => json_decode($record->try_on_data)
));
} else {
wp_send_json_error(array('message' => '未找到记录'));
}
}
?>
为管理员提供产品虚拟试用设置界面:
<?php
// 在插件目录中创建 admin/product-settings.php
public function add_admin_menu() {
add_menu_page(
'虚拟试用设置',
'虚拟试用',
'manage_options',
'virtual-try-on',
array($this, 'render_admin_page'),
'dashicons-camera',
30
);
add_submenu_page(
'virtual-try-on',
'产品设置',
'产品设置',
'manage_options',
'vt-product-settings',
array($this, 'render_product_settings_page')
);
add_submenu_page(
'virtual-try-on',
'试用记录',
'试用记录',
'manage_options',
'vt-try-records',
array($this, 'render_try_records_page')
);
}
public function render_product_settings_page() {
global $wpdb;
// 获取所有产品
$products = get_posts(array(
'post_type' => 'product',
'posts_per_page' => -1,
'post_status' => 'publish'
));
// 处理表单提交
if (isset($_POST['submit_settings'])) {
$this->save_product_settings($_POST);
echo '<div class="notice notice-success"><p>设置已保存!</p></div>';
}
?>
<div class="wrap">
<h1>产品虚拟试用设置</h1>
<form method="post" action="">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>产品名称</th>
<th>启用虚拟试用</th>
<th>试用类型</th>
<th>叠加图像</th>
<th>锚点设置</th>
</tr>
</thead>
<tbody>
<?php foreach ($products as $product):
$settings = $this->get_product_settings($product->ID);
?>
<tr>
<td>
<strong><?php echo esc_html($product->post_title); ?></strong>
<input type="hidden" name="product_ids[]" value="<?php echo $product->ID; ?>">
</td>
<td>
<input type="checkbox"
name="enable_virtual_try[<?php echo $product->ID; ?>]"
value="1"
<?php checked($settings->enable_virtual_try, 1); ?>>
</td>
<td>
<select name="try_on_type[<?php echo $product->ID; ?>]">
<option value="overlay" <?php selected($settings->try_on_type, 'overlay'); ?>>叠加模式</option>
<option value="replace" <?php selected($settings->try_on_type, 'replace'); ?>>替换模式</option>
<option value="ar" <?php selected($settings->try_on_type, 'ar'); ?>>AR模式</option>
</select>
</td>
<td>
<div class="upload-container">
<?php if ($settings->overlay_image): ?>
<img src="<?php echo esc_url($settings->overlay_image); ?>"
style="max-width: 100px; max-height: 100px; display: block; margin-bottom: 5px;">
<?php endif; ?>
<input type="text"
name="overlay_image[<?php echo $product->ID; ?>]"
value="<?php echo esc_url($settings->overlay_image); ?>"
class="regular-text">
<button type="button" class="button upload-image-btn" data-target="overlay_image[<?php echo $product->ID; ?>]">
选择图像
</button>
</div>
</td>
<td>
<textarea name="anchor_points[<?php echo $product->ID; ?>]"
rows="3"
cols="30"
placeholder="JSON格式的锚点数据"><?php echo esc_textarea($settings->anchor_points); ?></textarea>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php submit_button('保存设置', 'primary', 'submit_settings'); ?>
</form>
<script>
jQuery(document).ready(function($) {
// 媒体上传器
$('.upload-image-btn').on('click', function(e) {
e.preventDefault();
var targetInput = $(this).data('target');
var button = $(this);
var frame = wp.media({
title: '选择叠加图像',
multiple: false,
library: {
type: 'image'
},
button: {


