文章目录
-
- 在定制产品行业中,供应商与客户之间的沟通效率直接影响业务成败。传统打样流程通常涉及多次邮件往来、文件传输和实物寄送,耗时耗力。通过WordPress插件实现在线打样功能,可以让客户直接在网站上提交设计需求,供应商实时查看并反馈,大幅缩短打样周期。 本教程将指导您开发一个完整的WordPress小批量定制插件,实现供应商在线打样功能。我们将从环境搭建开始,逐步实现用户提交、供应商审核、在线标注和版本管理等功能。
- 首先,我们需要创建一个标准的WordPress插件。在wp-content/plugins目录下创建新文件夹"custom-sampling"。 <?php /** * Plugin Name: 小批量定制在线打样系统 * Plugin URI: https://yourwebsite.com/ * Description: 实现供应商在线打样功能的WordPress插件 * Version: 1.0.0 * Author: 您的名称 * License: GPL v2 or later * Text Domain: custom-sampling */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('CS_PLUGIN_PATH', plugin_dir_path(__FILE__)); define('CS_PLUGIN_URL', plugin_dir_url(__FILE__)); define('CS_VERSION', '1.0.0'); // 初始化插件 class CustomSamplingPlugin { 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')); // 管理端脚本和样式 add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); } public function activate() { $this->create_tables(); flush_rewrite_rules(); } public function deactivate() { flush_rewrite_rules(); } public function init() { // 加载文本域 load_plugin_textdomain('custom-sampling', false, dirname(plugin_basename(__FILE__)) . '/languages'); } // 创建数据库表 private function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'custom_sampling_requests'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id mediumint(9) NOT NULL AUTO_INCREMENT, customer_id mediumint(9) NOT NULL, product_name varchar(255) NOT NULL, design_files text NOT NULL, specifications text NOT NULL, status varchar(50) DEFAULT 'pending', supplier_notes text, revised_files text, created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } // 添加管理菜单 public function add_admin_menu() { add_menu_page( '在线打样管理', '在线打样', 'manage_options', 'custom-sampling', array($this, 'render_admin_page'), 'dashicons-format-image', 30 ); add_submenu_page( 'custom-sampling', '打样请求', '所有请求', 'manage_options', 'custom-sampling', array($this, 'render_admin_page') ); add_submenu_page( 'custom-sampling', '供应商设置', '供应商设置', 'manage_options', 'custom-sampling-suppliers', array($this, 'render_suppliers_page') ); } public function render_admin_page() { include CS_PLUGIN_PATH . 'templates/admin/main.php'; } public function render_suppliers_page() { include CS_PLUGIN_PATH . 'templates/admin/suppliers.php'; } public function enqueue_frontend_assets() { wp_enqueue_style( 'custom-sampling-frontend', CS_PLUGIN_URL . 'assets/css/frontend.css', array(), CS_VERSION ); wp_enqueue_script( 'custom-sampling-frontend', CS_PLUGIN_URL . 'assets/js/frontend.js', array('jquery'), CS_VERSION, true ); // 本地化脚本 wp_localize_script('custom-sampling-frontend', 'cs_ajax', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('custom_sampling_nonce') )); } public function enqueue_admin_assets($hook) { if (strpos($hook, 'custom-sampling') === false) { return; } wp_enqueue_style( 'custom-sampling-admin', CS_PLUGIN_URL . 'assets/css/admin.css', array(), CS_VERSION ); wp_enqueue_script( 'custom-sampling-admin', CS_PLUGIN_URL . 'assets/js/admin.js', array('jquery', 'wp-color-picker'), CS_VERSION, true ); // 引入图像标注库 wp_enqueue_script( 'fabric-js', 'https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.5.0/fabric.min.js', array(), '4.5.0', true ); } } // 启动插件 CustomSamplingPlugin::get_instance(); ?>
- 在线打样系统的核心是高效的数据管理。我们设计了以下数据表结构来存储打样请求信息: <?php /** * 打样请求数据模型 * 处理所有与打样请求相关的数据库操作 */ class SamplingRequestModel { private $table_name; public function __construct() { global $wpdb; $this->table_name = $wpdb->prefix . 'custom_sampling_requests'; } /** * 创建新的打样请求 * @param array $data 请求数据 * @return int|false 插入ID或false */ public function create_request($data) { global $wpdb; $defaults = array( 'customer_id' => get_current_user_id(), 'product_name' => '', 'design_files' => '', 'specifications' => '', 'status' => 'pending', 'supplier_notes' => '', 'revised_files' => '' ); $data = wp_parse_args($data, $defaults); // 序列化数组字段 if (is_array($data['design_files'])) { $data['design_files'] = serialize($data['design_files']); } if (is_array($data['revised_files'])) { $data['revised_files'] = serialize($data['revised_files']); } $result = $wpdb->insert( $this->table_name, $data, array('%d', '%s', '%s', '%s', '%s', '%s', '%s') ); return $result ? $wpdb->insert_id : false; } /** * 获取打样请求 * @param int $id 请求ID * @return array|null 请求数据或null */ public function get_request($id) { global $wpdb; $query = $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE id = %d", $id ); $request = $wpdb->get_row($query, ARRAY_A); if ($request) { // 反序列化数组字段 $request['design_files'] = maybe_unserialize($request['design_files']); $request['revised_files'] = maybe_unserialize($request['revised_files']); } return $request; } /** * 更新打样请求 * @param int $id 请求ID * @param array $data 更新数据 * @return bool 是否成功 */ public function update_request($id, $data) { global $wpdb; // 序列化数组字段 if (isset($data['design_files']) && is_array($data['design_files'])) { $data['design_files'] = serialize($data['design_files']); } if (isset($data['revised_files']) && is_array($data['revised_files'])) { $data['revised_files'] = serialize($data['revised_files']); } $result = $wpdb->update( $this->table_name, $data, array('id' => $id), array('%s', '%s', '%s', '%s', '%s', '%s'), array('%d') ); return $result !== false; } /** * 获取用户的所有打样请求 * @param int $user_id 用户ID * @param string $status 状态筛选 * @return array 请求列表 */ public function get_user_requests($user_id, $status = '') { global $wpdb; $where = "WHERE customer_id = %d"; $params = array($user_id); if (!empty($status)) { $where .= " AND status = %s"; $params[] = $status; } $query = $wpdb->prepare( "SELECT * FROM {$this->table_name} {$where} ORDER BY created_at DESC", $params ); $requests = $wpdb->get_results($query, ARRAY_A); // 反序列化数组字段 foreach ($requests as &$request) { $request['design_files'] = maybe_unserialize($request['design_files']); $request['revised_files'] = maybe_unserialize($request['revised_files']); } return $requests; } /** * 获取所有打样请求(管理员) * @param array $args 查询参数 * @return array 请求列表 */ public function get_all_requests($args = array()) { global $wpdb; $defaults = array( 'status' => '', 'page' => 1, 'per_page' => 20, 'search' => '' ); $args = wp_parse_args($args, $defaults); $where = "WHERE 1=1"; $params = array(); if (!empty($args['status'])) { $where .= " AND status = %s"; $params[] = $args['status']; } if (!empty($args['search'])) { $where .= " AND (product_name LIKE %s OR specifications LIKE %s)"; $search_term = '%' . $wpdb->esc_like($args['search']) . '%'; $params[] = $search_term; $params[] = $search_term; } $offset = ($args['page'] - 1) * $args['per_page']; $query = $wpdb->prepare( "SELECT SQL_CALC_FOUND_ROWS * FROM {$this->table_name} {$where} ORDER BY created_at DESC LIMIT %d, %d", array_merge($params, array($offset, $args['per_page'])) ); $requests = $wpdb->get_results($query, ARRAY_A); $total = $wpdb->get_var("SELECT FOUND_ROWS()"); // 反序列化数组字段 foreach ($requests as &$request) { $request['design_files'] = maybe_unserialize($request['design_files']); $request['revised_files'] = maybe_unserialize($request['revised_files']); } return array( 'requests' => $requests, 'total' => $total, 'total_pages' => ceil($total / $args['per_page']) ); } } ?>
- 客户提交打样请求的界面需要直观易用。以下是前端表单的实现代码: <?php /** * 前端打样请求表单 * 通过短码嵌入到任何页面 */ class SamplingRequestForm { public function __construct() { add_shortcode('sampling_request_form', array($this, 'render_form')); add_action('wp_ajax_submit_sampling_request', array($this, 'handle_submission')); add_action('wp_ajax_nopriv_submit_sampling_request', array($this, 'handle_submission')); } /** * 渲染提交表单 */ public function render_form() { // 检查用户是否登录 if (!is_user_logged_in()) { return '<div class="cs-alert">请先登录后再提交打样请求。</div>'; } ob_start(); ?> <div class="custom-sampling-form"> <h2>在线打样请求</h2> <form id="sampling-request-form" enctype="multipart/form-data"> <?php wp_nonce_field('submit_sampling_request', 'sampling_nonce'); ?> <div class="form-group"> <label for="product_name">产品名称 *</label> <input type="text" id="product_name" name="product_name" required> </div> <div class="form-group"> <label for="product_category">产品类别</label> <select id="product_category" name="product_category"> <option value="apparel">服装</option> <option value="accessories">配饰</option> <option value="home_decor">家居装饰</option> <option value="promotional">促销品</option> <option value="other">其他</option> </select> </div> <div class="form-group"> <label for="quantity">预计数量</label> <input type="number" id="quantity" name="quantity" min="50" max="10000" value="100"> <small>小批量定制范围:50-10000件</small> </div> <div class="form-group"> <label for="specifications">详细规格要求 *</label> <textarea id="specifications" name="specifications" rows="6" required placeholder="请详细描述您的需求,包括尺寸、材料、颜色、工艺等要求..."></textarea> </div> <div class="form-group"> <label>设计文件上传</label> <div class="file-upload-area" id="file-upload-area"> <div class="upload-instructions"> <p>拖放文件到这里,或点击选择文件</p> <p>支持格式:JPG, PNG, PDF, AI, PSD (最大 20MB)</p> </div> <input type="file" id="design_files" name="design_files[]" multiple accept=".jpg,.jpeg,.png,.pdf,.ai,.psd" style="display: none;"> <div class="file-preview" id="file-preview"></div> </div> <button type="button" class="button" id="select-files-btn">选择文件</button> </div> <div class="form-group"> <label for="deadline">期望完成时间</label> <input type="date" id="deadline" name="deadline" min="<?php echo date('Y-m-d', strtotime('+3 days')); ?>"> </div> <div class="form-group"> <label for="additional_notes">附加说明</label> <textarea id="additional_notes" name="additional_notes" rows="3"></textarea> </div> <div class="form-actions"> <button type="submit" class="button button-primary">提交打样请求</button> <div class="loading-spinner" style="display: none;">提交中...</div> </div> <div id="form-message"></div> </form> </div> <script> jQuery(document).ready(function($) { // 文件上传处理 $('#select-files-btn').on('click', function() { $('#design_files').click(); }); $('#design_files').on('change', function(e) { handleFileSelection(e.target.files); }); // 拖放功能 $('#file-upload-area').on('dragover', function(e) { e.preventDefault(); $(this).addClass('dragover'); }); $('#file-upload-area').on('dragleave', function(e) { e.preventDefault(); $(this).removeClass('dragover'); }); $('#file-upload-area').on('drop', function(e) { e.preventDefault(); $(this).removeClass('dragover'); handleFileSelection(e.originalEvent.dataTransfer.files); }); function handleFileSelection(files) { var preview = $('#file-preview'); preview.empty(); for (var i = 0; i < files.length; i++) { var file = files[i]; var fileName = file.name; var fileSize = (file.size / 1024 / 1024).toFixed(2); // MB var fileHtml = ` <div class="file-info"> <span class="file-name">${fileName}</span> <span class="file-size">${fileSize} MB</span> <button type="button" class="remove-file" data-index="${i}">×</button> </div> `; preview.append(fileHtml); } // 移除文件按钮 $('.remove-file').on('click', function() { $(this).closest('.file-item').remove(); updateFileInput(); }); } function updateFileInput() { // 这里可以添加逻辑来更新实际的文件输入 } // 表单提交 $('#sampling-request-form').on('submit', function(e) { e.preventDefault(); var formData = new FormData(this); var files = $('#design_files')[0].files; // 添加文件到FormData for (var i = 0; i < files.length; i++) { formData.append('design_files_' + i, files[i]); } // 显示加载状态 $('.loading-spinner').show(); $('button[type="submit"]').prop('disabled', true); $.ajax({ url: cs_ajax.ajax_url, type: 'POST', data: formData, processData: false, contentType: false, dataType: 'json', success: function(response) { if (response.success) { $('#form-message').html( '<div class="success-message">' + '打样请求已成功提交!我们将在24小时内联系您。' + '</div>' ); $('#sampling-request-form')[0].reset(); $('#file-preview').empty(); } else { $('#form-message').html( '<div class="error-message">' + response.data.message + '</div>' ); } }, error: function() { $('#form-message').html( '<div class="error-message">提交失败,请稍后重试。</div>' ); }, complete: function() { $('.loading-spinner').hide(); $('button[type="submit"]').prop('disabled', false); } }); }); }); </script> <?php return ob_get_clean(); } /** * 处理表单提交 */ public function handle_submission() { // 验证nonce if (!wp_verify_nonce($_POST['sampling_nonce'], 'submit_sampling_request')) { wp_send_json_error(array('message' => '安全验证失败')); } // 验证用户登录 if (!is_user_logged_in()) { wp_send_json_error(array('message' => '请先登录')); } // 处理文件上传 $uploaded_files = array(); if (!empty($_FILES)) { require_once(ABSPATH . 'wp-admin/includes/file.php'); require_once(ABSPATH . 'wp-admin/includes/image.php'); foreach ($_FILES as $key => $file) { if (strpos($key, 'design_files_') === 0 && $file['error'] === UPLOAD_ERR_OK) { $upload = wp_handle_upload($file, array('test_form' => false)); if (!isset($upload['error'])) { $uploaded_files[] = $upload['url']; // 创建媒体库附件 $attachment = array( 'post_mime_type' => $upload['type'], 'post_title' => sanitize_file_name($file['name']), 'post_content' => '', 'post_status' => 'inherit' ); $attach_id = wp_insert_attachment($attachment, $upload['file']); wp_update_attachment_metadata($attach_id, wp_generate_attachment_metadata($attach_id, $upload['file'])); } } } } // 准备数据 $data = array( 'product_name' => sanitize_text_field($_POST['product_name']), 'specifications' => wp_kses_post($_POST['specifications']), 'design_files' => $uploaded_files, 'additional_data' => array( 'product_category' => sanitize_text_field($_POST['product_category']), 'quantity' => intval($_POST['quantity']), 'deadline' => sanitize_text_field($_POST['deadline']), 'additional_notes' => wp_kses_post($_POST['additional_notes']) ) ); // 保存到数据库 $model = new SamplingRequestModel(); $request_id = $model->create_request($data); if ($request_id) { // 发送通知邮件 $this->send_notification_email($request_id, $data); wp_send_json_success(array( 'message' => '请求提交成功', 'request_id' => $request_id )); } else { wp_send_json_error(array('message' => '保存失败,请稍后重试')); } } /** * 发送通知邮件 */ private function send_notification_email($request_id, $data) { $admin_email = get_option('admin_email'); $customer = wp_get_current_user(); $subject = sprintf('新的打样请求 #%d - %s', $request_id, $data['product_name']); $message = sprintf( "新的打样请求已提交:nn" . "请求编号:%dn" . "产品名称:%sn" . "客户:%s (%s)n" . "预计数量:%dn" . "提交时间:%snn" . "详细规格:n%snn" . "请登录管理后台查看详情:%s", $request_id, $data['product_name'], $customer->display_name, $customer->user_email, $data['additional_data']['quantity'], current_time('mysql'), $data['specifications'], admin_url('admin.php?page=custom-sampling&action=view&id=' . $request_id) ); wp_mail($admin_email, $subject, $message); } } new SamplingRequestForm(); ?>
- 供应商需要一个直观的管理界面来处理打样请求: <?php /** * 供应商管理界面 * 显示所有打样请求并提供处理功能 */ class SupplierAdminInterface { public function __construct() { add_action('admin_init', array($this, 'handle_actions')); } /** * 渲染管理主页面 */ public function render_main_page() { $model = new SamplingRequestModel(); // 获取筛选参数 $status = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : ''; $search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : ''; $paged = isset($_GET['paged']) ? intval($_GET['paged']) : 1; $args = array( 'status' => $status, 'search' => $search, 'page' => $paged, 'per_page' => 20 ); $result = $model->get_all_requests($args); $requests = $result['requests']; $total_pages = $result['total_pages']; ?> <div class="wrap"> <h1 class="wp-heading-inline">打样请求管理</h1> <!-- 筛选表单 --> <div class="cs-filter-box"> <form method="get" action="<?php echo admin_url('admin.php'); ?>"> <input type="hidden" name="page" value="custom-sampling"> <select name="status" onchange="this.form.submit()"> <option value="">所有状态</option> <option value="pending" <?php selected($status, 'pending'); ?>>待处理</option> <option value="reviewing" <?php selected($status, 'reviewing'); ?>>审核中</option> <option value="needs_revision" <?php selected($status, 'needs_revision'); ?>>需要修改</option> <option value="approved" <?php selected($status, 'approved'); ?>>已批准</option> <option value="rejected" <?php selected($status, 'rejected'); ?>>已拒绝</option> <option value="completed" <?php selected($status, 'completed'); ?>>已完成</option> </select> <input type="text" name="s" placeholder="搜索产品名称或规格" value="<?php echo esc_attr($search); ?>"> <button type="submit" class="button">搜索</button> </form> </div> <!-- 请求列表 --> <table class="wp-list-table widefat fixed striped"> <thead> <tr> <th>ID</th> <th>产品名称</th> <th>客户</th> <th>提交时间</th> <th>状态</th> <th>操作</th> </tr> </thead> <tbody> <?php if (empty($requests)): ?> <tr> <td colspan="6" style="text-align: center;">暂无打样请求</td> </tr> <?php else: ?> <?php foreach ($requests as $request): ?> <?php $customer = get_userdata($request['customer_id']); $status_class = 'status-' . $request['status']; ?> <tr> <td>#<?php echo $request['id']; ?></td> <td> <strong><?php echo esc_html($request['product_name']); ?></strong> <?php if (!empty($request['additional_data']['quantity'])): ?> <br><small>数量: <?php echo $request['additional_data']['quantity']; ?>件</small> <?php endif; ?> </td> <td> <?php if ($customer): ?> <?php echo esc_html($customer->display_name); ?><br> <small><?php echo esc_html($customer->user_email); ?></small> <?php else: ?> 用户已删除 <?php endif; ?> </td> <td><?php echo date('Y-m-d H:i', strtotime($request['created_at'])); ?></td> <td> <span class="cs-status-badge <?php echo $status_class; ?>"> <?php echo $this->get_status_label($request['status']); ?> </span> </td> <td> <a href="<?php echo admin_url('admin.php?page=custom-sampling&action=view&id=' . $request['id']); ?>" class="button button-small">查看详情</a> <?php if ($request['status'] === 'pending' || $request['status'] === 'reviewing'): ?> <a href="<?php echo admin_url('admin.php?page=custom-sampling&action=review&id=' . $request['id']); ?>" class="button button-small button-primary">审核</a> <?php endif; ?> </td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> <!-- 分页 --> <?php if ($total_pages > 1): ?> <div class="tablenav bottom"> <div class="tablenav-pages"> <?php echo paginate_links(array( 'base' => add_query_arg('paged', '%#%'), 'format' => '', 'prev_text' => '«', 'next_text' => '»', 'total' => $total_pages, 'current' => $paged )); ?> </div> </div> <?php endif; ?> </div> <style> .cs-status-badge { display: inline-block; padding: 3px 8px; border-radius: 3px; font-size: 12px; font-weight: 600; } .status-pending { background: #f0ad4e; color: white; } .status-reviewing { background: #5bc0de; color: white; } .status-needs_revision { background: #d9534f; color: white; } .status-approved { background: #5cb85c; color: white; } .status-rejected { background: #d9534f; color: white; } .status-completed { background: #5cb85c; color: white; } .cs-filter-box { background: #fff; padding: 15px; margin: 20px 0; border: 1px solid #ccd0d4; } .cs-filter-box select, .cs-filter-box input[type="text"] { margin-right: 10px; } </style> <?php } /** * 获取状态标签 */ private function get_status_label($status) { $labels = array( 'pending' => '待处理', 'reviewing' => '审核中', 'needs_revision' => '需要修改', 'approved' => '已批准', 'rejected' => '已拒绝', 'completed' => '已完成' ); return isset($labels[$status]) ? $labels[$status] : $status; } /** * 处理管理操作 */ public function handle_actions() { if (!isset($_GET['page']) || $_GET['page'] !== 'custom-sampling') { return; } $action = isset($_GET['action']) ? $_GET['action'] : ''; $request_id = isset($_GET['id']) ? intval($_GET['id']) : 0; if (!$request_id) { return; } $model = new SamplingRequestModel(); switch ($action) { case 'update_status': if (isset($_POST['status']) && wp_verify_nonce($_POST['_wpnonce'], 'update_status_' . $request_id)) { $status = sanitize_text_field($_POST['status']); $notes = isset($_POST['supplier_notes']) ? wp_kses_post($_POST['supplier_notes']) : ''; $model->update_request($request_id, array( 'status' => $status, 'supplier_notes' => $notes )); // 发送状态更新通知 $this->send_status_notification($request_id, $status, $notes); wp_redirect(admin_url('admin.php?page=custom-sampling&action=view&id=' . $request_id . '&updated=1')); exit; } break; case 'upload_revision': if (wp_verify_nonce($_POST['_wpnonce'], 'upload_revision_' . $request_id)) { $this->handle_revision_upload($request_id); } break; } } /** * 发送状态更新通知 */ private function send_status_notification($request_id, $status, $notes) { $request = (new SamplingRequestModel())->get_request($request_id); $customer = get_userdata($request['customer_id']); if (!$customer) { return; } $status_labels = array( 'approved' => '已批准', 'rejected' => '已拒绝', 'needs_revision' => '需要修改', 'completed' => '已完成' ); $subject = sprintf('打样请求 #%d 状态更新', $request_id); $message = sprintf( "您的打样请求状态已更新:nn" . "请求编号:%dn" . "产品名称:%sn" . "新状态:%sn" . "供应商备注:%snn" . "请登录网站查看详情:%s", $request_id, $request['product_name'], $status_labels[$status] ?? $status, $notes, home_url('/my-account/sampling-requests/') ); wp_mail($customer->user_email, $subject, $message); } } new SupplierAdminInterface(); ?>
- 供应商需要能够在设计图上直接标注和批注: <?php /** * 在线图像标注系统 * 使用Fabric.js实现 */ class ImageAnnotationSystem { public function __construct() { add_action('admin_enqueue_scripts', array($this, 'enqueue_annotation_scripts')); add_action('wp_ajax_save_annotation', array($this, 'save_annotation')); } /** * 渲染标注界面 */ public function render_annotation_interface($request_id, $image_urls) { ?> <div class="annotation-container"> <div class="annotation-header"> <h2>设计图批注</h2> <div class="annotation-tools"> <button type="button" class="button" id="tool-select">选择</button> <button type="button" class="button" id="tool-rectangle">矩形</button> <button type="button" class="button" id="tool-circle">圆形</button> <button type="button" class="button" id="tool-arrow">箭头</button> <button type="button" class="button" id="tool-text">文字</button> <button type="button" class="button" id="tool-freehand">自由绘制</button> <input type="color" id="color-picker" value="#FF0000">
在定制产品行业中,供应商与客户之间的沟通效率直接影响业务成败。传统打样流程通常涉及多次邮件往来、文件传输和实物寄送,耗时耗力。通过WordPress插件实现在线打样功能,可以让客户直接在网站上提交设计需求,供应商实时查看并反馈,大幅缩短打样周期。
本教程将指导您开发一个完整的WordPress小批量定制插件,实现供应商在线打样功能。我们将从环境搭建开始,逐步实现用户提交、供应商审核、在线标注和版本管理等功能。
首先,我们需要创建一个标准的WordPress插件。在wp-content/plugins目录下创建新文件夹"custom-sampling"。
<?php
/**
* Plugin Name: 小批量定制在线打样系统
* Plugin URI: https://yourwebsite.com/
* Description: 实现供应商在线打样功能的WordPress插件
* Version: 1.0.0
* Author: 您的名称
* License: GPL v2 or later
* Text Domain: custom-sampling
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('CS_PLUGIN_PATH', plugin_dir_path(__FILE__));
define('CS_PLUGIN_URL', plugin_dir_url(__FILE__));
define('CS_VERSION', '1.0.0');
// 初始化插件
class CustomSamplingPlugin {
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'));
// 管理端脚本和样式
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
}
public function activate() {
$this->create_tables();
flush_rewrite_rules();
}
public function deactivate() {
flush_rewrite_rules();
}
public function init() {
// 加载文本域
load_plugin_textdomain('custom-sampling', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
// 创建数据库表
private function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$table_name = $wpdb->prefix . 'custom_sampling_requests';
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
customer_id mediumint(9) NOT NULL,
product_name varchar(255) NOT NULL,
design_files text NOT NULL,
specifications text NOT NULL,
status varchar(50) DEFAULT 'pending',
supplier_notes text,
revised_files text,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
// 添加管理菜单
public function add_admin_menu() {
add_menu_page(
'在线打样管理',
'在线打样',
'manage_options',
'custom-sampling',
array($this, 'render_admin_page'),
'dashicons-format-image',
30
);
add_submenu_page(
'custom-sampling',
'打样请求',
'所有请求',
'manage_options',
'custom-sampling',
array($this, 'render_admin_page')
);
add_submenu_page(
'custom-sampling',
'供应商设置',
'供应商设置',
'manage_options',
'custom-sampling-suppliers',
array($this, 'render_suppliers_page')
);
}
public function render_admin_page() {
include CS_PLUGIN_PATH . 'templates/admin/main.php';
}
public function render_suppliers_page() {
include CS_PLUGIN_PATH . 'templates/admin/suppliers.php';
}
public function enqueue_frontend_assets() {
wp_enqueue_style(
'custom-sampling-frontend',
CS_PLUGIN_URL . 'assets/css/frontend.css',
array(),
CS_VERSION
);
wp_enqueue_script(
'custom-sampling-frontend',
CS_PLUGIN_URL . 'assets/js/frontend.js',
array('jquery'),
CS_VERSION,
true
);
// 本地化脚本
wp_localize_script('custom-sampling-frontend', 'cs_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('custom_sampling_nonce')
));
}
public function enqueue_admin_assets($hook) {
if (strpos($hook, 'custom-sampling') === false) {
return;
}
wp_enqueue_style(
'custom-sampling-admin',
CS_PLUGIN_URL . 'assets/css/admin.css',
array(),
CS_VERSION
);
wp_enqueue_script(
'custom-sampling-admin',
CS_PLUGIN_URL . 'assets/js/admin.js',
array('jquery', 'wp-color-picker'),
CS_VERSION,
true
);
// 引入图像标注库
wp_enqueue_script(
'fabric-js',
'https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.5.0/fabric.min.js',
array(),
'4.5.0',
true
);
}
}
// 启动插件
CustomSamplingPlugin::get_instance();
?>
在线打样系统的核心是高效的数据管理。我们设计了以下数据表结构来存储打样请求信息:
<?php
/**
* 打样请求数据模型
* 处理所有与打样请求相关的数据库操作
*/
class SamplingRequestModel {
private $table_name;
public function __construct() {
global $wpdb;
$this->table_name = $wpdb->prefix . 'custom_sampling_requests';
}
/**
* 创建新的打样请求
* @param array $data 请求数据
* @return int|false 插入ID或false
*/
public function create_request($data) {
global $wpdb;
$defaults = array(
'customer_id' => get_current_user_id(),
'product_name' => '',
'design_files' => '',
'specifications' => '',
'status' => 'pending',
'supplier_notes' => '',
'revised_files' => ''
);
$data = wp_parse_args($data, $defaults);
// 序列化数组字段
if (is_array($data['design_files'])) {
$data['design_files'] = serialize($data['design_files']);
}
if (is_array($data['revised_files'])) {
$data['revised_files'] = serialize($data['revised_files']);
}
$result = $wpdb->insert(
$this->table_name,
$data,
array('%d', '%s', '%s', '%s', '%s', '%s', '%s')
);
return $result ? $wpdb->insert_id : false;
}
/**
* 获取打样请求
* @param int $id 请求ID
* @return array|null 请求数据或null
*/
public function get_request($id) {
global $wpdb;
$query = $wpdb->prepare(
"SELECT * FROM {$this->table_name} WHERE id = %d",
$id
);
$request = $wpdb->get_row($query, ARRAY_A);
if ($request) {
// 反序列化数组字段
$request['design_files'] = maybe_unserialize($request['design_files']);
$request['revised_files'] = maybe_unserialize($request['revised_files']);
}
return $request;
}
/**
* 更新打样请求
* @param int $id 请求ID
* @param array $data 更新数据
* @return bool 是否成功
*/
public function update_request($id, $data) {
global $wpdb;
// 序列化数组字段
if (isset($data['design_files']) && is_array($data['design_files'])) {
$data['design_files'] = serialize($data['design_files']);
}
if (isset($data['revised_files']) && is_array($data['revised_files'])) {
$data['revised_files'] = serialize($data['revised_files']);
}
$result = $wpdb->update(
$this->table_name,
$data,
array('id' => $id),
array('%s', '%s', '%s', '%s', '%s', '%s'),
array('%d')
);
return $result !== false;
}
/**
* 获取用户的所有打样请求
* @param int $user_id 用户ID
* @param string $status 状态筛选
* @return array 请求列表
*/
public function get_user_requests($user_id, $status = '') {
global $wpdb;
$where = "WHERE customer_id = %d";
$params = array($user_id);
if (!empty($status)) {
$where .= " AND status = %s";
$params[] = $status;
}
$query = $wpdb->prepare(
"SELECT * FROM {$this->table_name} {$where} ORDER BY created_at DESC",
$params
);
$requests = $wpdb->get_results($query, ARRAY_A);
// 反序列化数组字段
foreach ($requests as &$request) {
$request['design_files'] = maybe_unserialize($request['design_files']);
$request['revised_files'] = maybe_unserialize($request['revised_files']);
}
return $requests;
}
/**
* 获取所有打样请求(管理员)
* @param array $args 查询参数
* @return array 请求列表
*/
public function get_all_requests($args = array()) {
global $wpdb;
$defaults = array(
'status' => '',
'page' => 1,
'per_page' => 20,
'search' => ''
);
$args = wp_parse_args($args, $defaults);
$where = "WHERE 1=1";
$params = array();
if (!empty($args['status'])) {
$where .= " AND status = %s";
$params[] = $args['status'];
}
if (!empty($args['search'])) {
$where .= " AND (product_name LIKE %s OR specifications LIKE %s)";
$search_term = '%' . $wpdb->esc_like($args['search']) . '%';
$params[] = $search_term;
$params[] = $search_term;
}
$offset = ($args['page'] - 1) * $args['per_page'];
$query = $wpdb->prepare(
"SELECT SQL_CALC_FOUND_ROWS * FROM {$this->table_name}
{$where}
ORDER BY created_at DESC
LIMIT %d, %d",
array_merge($params, array($offset, $args['per_page']))
);
$requests = $wpdb->get_results($query, ARRAY_A);
$total = $wpdb->get_var("SELECT FOUND_ROWS()");
// 反序列化数组字段
foreach ($requests as &$request) {
$request['design_files'] = maybe_unserialize($request['design_files']);
$request['revised_files'] = maybe_unserialize($request['revised_files']);
}
return array(
'requests' => $requests,
'total' => $total,
'total_pages' => ceil($total / $args['per_page'])
);
}
}
?>
客户提交打样请求的界面需要直观易用。以下是前端表单的实现代码:
<?php
/**
* 前端打样请求表单
* 通过短码嵌入到任何页面
*/
class SamplingRequestForm {
public function __construct() {
add_shortcode('sampling_request_form', array($this, 'render_form'));
add_action('wp_ajax_submit_sampling_request', array($this, 'handle_submission'));
add_action('wp_ajax_nopriv_submit_sampling_request', array($this, 'handle_submission'));
}
/**
* 渲染提交表单
*/
public function render_form() {
// 检查用户是否登录
if (!is_user_logged_in()) {
return '<div class="cs-alert">请先登录后再提交打样请求。</div>';
}
ob_start();
?>
<div class="custom-sampling-form">
<h2>在线打样请求</h2>
<form id="sampling-request-form" enctype="multipart/form-data">
<?php wp_nonce_field('submit_sampling_request', 'sampling_nonce'); ?>
<div class="form-group">
<label for="product_name">产品名称 *</label>
<input type="text" id="product_name" name="product_name" required>
</div>
<div class="form-group">
<label for="product_category">产品类别</label>
<select id="product_category" name="product_category">
<option value="apparel">服装</option>
<option value="accessories">配饰</option>
<option value="home_decor">家居装饰</option>
<option value="promotional">促销品</option>
<option value="other">其他</option>
</select>
</div>
<div class="form-group">
<label for="quantity">预计数量</label>
<input type="number" id="quantity" name="quantity" min="50" max="10000" value="100">
<small>小批量定制范围:50-10000件</small>
</div>
<div class="form-group">
<label for="specifications">详细规格要求 *</label>
<textarea id="specifications" name="specifications" rows="6" required
placeholder="请详细描述您的需求,包括尺寸、材料、颜色、工艺等要求..."></textarea>
</div>
<div class="form-group">
<label>设计文件上传</label>
<div class="file-upload-area" id="file-upload-area">
<div class="upload-instructions">
<p>拖放文件到这里,或点击选择文件</p>
<p>支持格式:JPG, PNG, PDF, AI, PSD (最大 20MB)</p>
</div>
<input type="file" id="design_files" name="design_files[]"
multiple accept=".jpg,.jpeg,.png,.pdf,.ai,.psd" style="display: none;">
<div class="file-preview" id="file-preview"></div>
</div>
<button type="button" class="button" id="select-files-btn">选择文件</button>
</div>
<div class="form-group">
<label for="deadline">期望完成时间</label>
<input type="date" id="deadline" name="deadline" min="<?php echo date('Y-m-d', strtotime('+3 days')); ?>">
</div>
<div class="form-group">
<label for="additional_notes">附加说明</label>
<textarea id="additional_notes" name="additional_notes" rows="3"></textarea>
</div>
<div class="form-actions">
<button type="submit" class="button button-primary">提交打样请求</button>
<div class="loading-spinner" style="display: none;">提交中...</div>
</div>
<div id="form-message"></div>
</form>
</div>
<script>
jQuery(document).ready(function($) {
// 文件上传处理
$('#select-files-btn').on('click', function() {
$('#design_files').click();
});
$('#design_files').on('change', function(e) {
handleFileSelection(e.target.files);
});
// 拖放功能
$('#file-upload-area').on('dragover', function(e) {
e.preventDefault();
$(this).addClass('dragover');
});
$('#file-upload-area').on('dragleave', function(e) {
e.preventDefault();
$(this).removeClass('dragover');
});
$('#file-upload-area').on('drop', function(e) {
e.preventDefault();
$(this).removeClass('dragover');
handleFileSelection(e.originalEvent.dataTransfer.files);
});
function handleFileSelection(files) {
var preview = $('#file-preview');
preview.empty();
for (var i = 0; i < files.length; i++) {
var file = files[i];
var fileName = file.name;
var fileSize = (file.size / 1024 / 1024).toFixed(2); // MB
var fileHtml = `
<div class="file-info">
<span class="file-name">${fileName}</span>
<span class="file-size">${fileSize} MB</span>
<button type="button" class="remove-file" data-index="${i}">×</button>
</div>
`;
preview.append(fileHtml);
}
// 移除文件按钮
$('.remove-file').on('click', function() {
$(this).closest('.file-item').remove();
updateFileInput();
});
}
function updateFileInput() {
// 这里可以添加逻辑来更新实际的文件输入
}
// 表单提交
$('#sampling-request-form').on('submit', function(e) {
e.preventDefault();
var formData = new FormData(this);
var files = $('#design_files')[0].files;
// 添加文件到FormData
for (var i = 0; i < files.length; i++) {
formData.append('design_files_' + i, files[i]);
}
// 显示加载状态
$('.loading-spinner').show();
$('button[type="submit"]').prop('disabled', true);
$.ajax({
url: cs_ajax.ajax_url,
type: 'POST',
data: formData,
processData: false,
contentType: false,
dataType: 'json',
success: function(response) {
if (response.success) {
$('#form-message').html(
'<div class="success-message">' +
'打样请求已成功提交!我们将在24小时内联系您。' +
'</div>'
);
$('#sampling-request-form')[0].reset();
$('#file-preview').empty();
} else {
$('#form-message').html(
'<div class="error-message">' +
response.data.message +
'</div>'
);
}
},
error: function() {
$('#form-message').html(
'<div class="error-message">提交失败,请稍后重试。</div>'
);
},
complete: function() {
$('.loading-spinner').hide();
$('button[type="submit"]').prop('disabled', false);
}
});
});
});
</script>
<?php
return ob_get_clean();
}
/**
* 处理表单提交
*/
public function handle_submission() {
// 验证nonce
if (!wp_verify_nonce($_POST['sampling_nonce'], 'submit_sampling_request')) {
wp_send_json_error(array('message' => '安全验证失败'));
}
// 验证用户登录
if (!is_user_logged_in()) {
wp_send_json_error(array('message' => '请先登录'));
}
// 处理文件上传
$uploaded_files = array();
if (!empty($_FILES)) {
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/image.php');
foreach ($_FILES as $key => $file) {
if (strpos($key, 'design_files_') === 0 && $file['error'] === UPLOAD_ERR_OK) {
$upload = wp_handle_upload($file, array('test_form' => false));
if (!isset($upload['error'])) {
$uploaded_files[] = $upload['url'];
// 创建媒体库附件
$attachment = array(
'post_mime_type' => $upload['type'],
'post_title' => sanitize_file_name($file['name']),
'post_content' => '',
'post_status' => 'inherit'
);
$attach_id = wp_insert_attachment($attachment, $upload['file']);
wp_update_attachment_metadata($attach_id, wp_generate_attachment_metadata($attach_id, $upload['file']));
}
}
}
}
// 准备数据
$data = array(
'product_name' => sanitize_text_field($_POST['product_name']),
'specifications' => wp_kses_post($_POST['specifications']),
'design_files' => $uploaded_files,
'additional_data' => array(
'product_category' => sanitize_text_field($_POST['product_category']),
'quantity' => intval($_POST['quantity']),
'deadline' => sanitize_text_field($_POST['deadline']),
'additional_notes' => wp_kses_post($_POST['additional_notes'])
)
);
// 保存到数据库
$model = new SamplingRequestModel();
$request_id = $model->create_request($data);
if ($request_id) {
// 发送通知邮件
$this->send_notification_email($request_id, $data);
wp_send_json_success(array(
'message' => '请求提交成功',
'request_id' => $request_id
));
} else {
wp_send_json_error(array('message' => '保存失败,请稍后重试'));
}
}
/**
* 发送通知邮件
*/
private function send_notification_email($request_id, $data) {
$admin_email = get_option('admin_email');
$customer = wp_get_current_user();
$subject = sprintf('新的打样请求 #%d - %s', $request_id, $data['product_name']);
$message = sprintf(
"新的打样请求已提交:nn" .
"请求编号:%dn" .
"产品名称:%sn" .
"客户:%s (%s)n" .
"预计数量:%dn" .
"提交时间:%snn" .
"详细规格:n%snn" .
"请登录管理后台查看详情:%s",
$request_id,
$data['product_name'],
$customer->display_name,
$customer->user_email,
$data['additional_data']['quantity'],
current_time('mysql'),
$data['specifications'],
admin_url('admin.php?page=custom-sampling&action=view&id=' . $request_id)
);
wp_mail($admin_email, $subject, $message);
}
}
new SamplingRequestForm();
?>
供应商需要一个直观的管理界面来处理打样请求:
<?php
/**
* 供应商管理界面
* 显示所有打样请求并提供处理功能
*/
class SupplierAdminInterface {
public function __construct() {
add_action('admin_init', array($this, 'handle_actions'));
}
/**
* 渲染管理主页面
*/
public function render_main_page() {
$model = new SamplingRequestModel();
// 获取筛选参数
$status = isset($_GET['status']) ? sanitize_text_field($_GET['status']) : '';
$search = isset($_GET['s']) ? sanitize_text_field($_GET['s']) : '';
$paged = isset($_GET['paged']) ? intval($_GET['paged']) : 1;
$args = array(
'status' => $status,
'search' => $search,
'page' => $paged,
'per_page' => 20
);
$result = $model->get_all_requests($args);
$requests = $result['requests'];
$total_pages = $result['total_pages'];
?>
<div class="wrap">
<h1 class="wp-heading-inline">打样请求管理</h1>
<!-- 筛选表单 -->
<div class="cs-filter-box">
<form method="get" action="<?php echo admin_url('admin.php'); ?>">
<input type="hidden" name="page" value="custom-sampling">
<select name="status" onchange="this.form.submit()">
<option value="">所有状态</option>
<option value="pending" <?php selected($status, 'pending'); ?>>待处理</option>
<option value="reviewing" <?php selected($status, 'reviewing'); ?>>审核中</option>
<option value="needs_revision" <?php selected($status, 'needs_revision'); ?>>需要修改</option>
<option value="approved" <?php selected($status, 'approved'); ?>>已批准</option>
<option value="rejected" <?php selected($status, 'rejected'); ?>>已拒绝</option>
<option value="completed" <?php selected($status, 'completed'); ?>>已完成</option>
</select>
<input type="text" name="s" placeholder="搜索产品名称或规格"
value="<?php echo esc_attr($search); ?>">
<button type="submit" class="button">搜索</button>
</form>
</div>
<!-- 请求列表 -->
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th>ID</th>
<th>产品名称</th>
<th>客户</th>
<th>提交时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php if (empty($requests)): ?>
<tr>
<td colspan="6" style="text-align: center;">暂无打样请求</td>
</tr>
<?php else: ?>
<?php foreach ($requests as $request): ?>
<?php
$customer = get_userdata($request['customer_id']);
$status_class = 'status-' . $request['status'];
?>
<tr>
<td>#<?php echo $request['id']; ?></td>
<td>
<strong><?php echo esc_html($request['product_name']); ?></strong>
<?php if (!empty($request['additional_data']['quantity'])): ?>
<br><small>数量: <?php echo $request['additional_data']['quantity']; ?>件</small>
<?php endif; ?>
</td>
<td>
<?php if ($customer): ?>
<?php echo esc_html($customer->display_name); ?><br>
<small><?php echo esc_html($customer->user_email); ?></small>
<?php else: ?>
用户已删除
<?php endif; ?>
</td>
<td><?php echo date('Y-m-d H:i', strtotime($request['created_at'])); ?></td>
<td>
<span class="cs-status-badge <?php echo $status_class; ?>">
<?php echo $this->get_status_label($request['status']); ?>
</span>
</td>
<td>
<a href="<?php echo admin_url('admin.php?page=custom-sampling&action=view&id=' . $request['id']); ?>"
class="button button-small">查看详情</a>
<?php if ($request['status'] === 'pending' || $request['status'] === 'reviewing'): ?>
<a href="<?php echo admin_url('admin.php?page=custom-sampling&action=review&id=' . $request['id']); ?>"
class="button button-small button-primary">审核</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<!-- 分页 -->
<?php if ($total_pages > 1): ?>
<div class="tablenav bottom">
<div class="tablenav-pages">
<?php
echo paginate_links(array(
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => '«',
'next_text' => '»',
'total' => $total_pages,
'current' => $paged
));
?>
</div>
</div>
<?php endif; ?>
</div>
<style>
.cs-status-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: 600;
}
.status-pending { background: #f0ad4e; color: white; }
.status-reviewing { background: #5bc0de; color: white; }
.status-needs_revision { background: #d9534f; color: white; }
.status-approved { background: #5cb85c; color: white; }
.status-rejected { background: #d9534f; color: white; }
.status-completed { background: #5cb85c; color: white; }
.cs-filter-box {
background: #fff;
padding: 15px;
margin: 20px 0;
border: 1px solid #ccd0d4;
}
.cs-filter-box select,
.cs-filter-box input[type="text"] {
margin-right: 10px;
}
</style>
<?php
}
/**
* 获取状态标签
*/
private function get_status_label($status) {
$labels = array(
'pending' => '待处理',
'reviewing' => '审核中',
'needs_revision' => '需要修改',
'approved' => '已批准',
'rejected' => '已拒绝',
'completed' => '已完成'
);
return isset($labels[$status]) ? $labels[$status] : $status;
}
/**
* 处理管理操作
*/
public function handle_actions() {
if (!isset($_GET['page']) || $_GET['page'] !== 'custom-sampling') {
return;
}
$action = isset($_GET['action']) ? $_GET['action'] : '';
$request_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
if (!$request_id) {
return;
}
$model = new SamplingRequestModel();
switch ($action) {
case 'update_status':
if (isset($_POST['status']) && wp_verify_nonce($_POST['_wpnonce'], 'update_status_' . $request_id)) {
$status = sanitize_text_field($_POST['status']);
$notes = isset($_POST['supplier_notes']) ? wp_kses_post($_POST['supplier_notes']) : '';
$model->update_request($request_id, array(
'status' => $status,
'supplier_notes' => $notes
));
// 发送状态更新通知
$this->send_status_notification($request_id, $status, $notes);
wp_redirect(admin_url('admin.php?page=custom-sampling&action=view&id=' . $request_id . '&updated=1'));
exit;
}
break;
case 'upload_revision':
if (wp_verify_nonce($_POST['_wpnonce'], 'upload_revision_' . $request_id)) {
$this->handle_revision_upload($request_id);
}
break;
}
}
/**
* 发送状态更新通知
*/
private function send_status_notification($request_id, $status, $notes) {
$request = (new SamplingRequestModel())->get_request($request_id);
$customer = get_userdata($request['customer_id']);
if (!$customer) {
return;
}
$status_labels = array(
'approved' => '已批准',
'rejected' => '已拒绝',
'needs_revision' => '需要修改',
'completed' => '已完成'
);
$subject = sprintf('打样请求 #%d 状态更新', $request_id);
$message = sprintf(
"您的打样请求状态已更新:nn" .
"请求编号:%dn" .
"产品名称:%sn" .
"新状态:%sn" .
"供应商备注:%snn" .
"请登录网站查看详情:%s",
$request_id,
$request['product_name'],
$status_labels[$status] ?? $status,
$notes,
home_url('/my-account/sampling-requests/')
);
wp_mail($customer->user_email, $subject, $message);
}
}
new SupplierAdminInterface();
?>
供应商需要能够在设计图上直接标注和批注:
<?php
/**
* 在线图像标注系统
* 使用Fabric.js实现
*/
class ImageAnnotationSystem {
public function __construct() {
add_action('admin_enqueue_scripts', array($this, 'enqueue_annotation_scripts'));
add_action('wp_ajax_save_annotation', array($this, 'save_annotation'));
}
/**
* 渲染标注界面
*/
public function render_annotation_interface($request_id, $image_urls) {
?>
<div class="annotation-container">
<div class="annotation-header">
<h2>设计图批注</h2>
<div class="annotation-tools">
<button type="button" class="button" id="tool-select">选择</button>
<button type="button" class="button" id="tool-rectangle">矩形</button>
<button type="button" class="button" id="tool-circle">圆形</button>
<button type="button" class="button" id="tool-arrow">箭头</button>
<button type="button" class="button" id="tool-text">文字</button>
<button type="button" class="button" id="tool-freehand">自由绘制</button>
<input type="color" id="color-picker" value="#FF0000">


