文章目录[隐藏]
WordPress小批量定制插件实现供应商在线打样教程
概述:为什么需要在线打样功能
在定制产品行业中,供应商与客户之间的沟通效率直接影响业务成败。传统打样流程通常涉及多次邮件往来、文件传输和实物寄送,耗时耗力。通过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">
