文章目录
-
- 在文创产品销售领域,柔性预售模式越来越受欢迎。这种模式允许商家根据预售情况调整生产数量,降低库存风险。本教程将带领大家开发一个完整的WordPress文创产品柔性限时预售插件。 核心功能需求: 为产品添加预售状态和预售时间设置 显示预售倒计时和预售进度 支持预售数量限制和柔性调整 预售结束后自动转为正常销售或下架 后台管理界面和销售数据统计
- 首先创建插件的基本目录结构和主文件: <?php /** * Plugin Name: 文创产品柔性限时预售插件 * Plugin URI: https://yourwebsite.com/ * Description: 为WordPress文创产品添加柔性限时预售功能 * Version: 1.0.0 * Author: 你的名字 * License: GPL v2 or later * Text Domain: flexible-presale */ // 防止直接访问 if (!defined('ABSPATH')) { exit; } // 定义插件常量 define('FLEXIBLE_PRESALE_VERSION', '1.0.0'); define('FLEXIBLE_PRESALE_PATH', plugin_dir_path(__FILE__)); define('FLEXIBLE_PRESALE_URL', plugin_dir_url(__FILE__)); // 初始化插件 class Flexible_Presale_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('plugins_loaded', array($this, 'init')); } public function activate() { // 创建必要的数据库表 $this->create_tables(); // 设置默认选项 update_option('flexible_presale_version', FLEXIBLE_PRESALE_VERSION); } public function deactivate() { // 清理临时数据 wp_clear_scheduled_hook('flexible_presale_daily_check'); } public function init() { // 加载文本域 load_plugin_textdomain('flexible-presale', false, dirname(plugin_basename(__FILE__)) . '/languages'); // 包含必要文件 $this->includes(); // 初始化组件 $this->init_components(); } private function includes() { // 包含后台管理类 require_once FLEXIBLE_PRESALE_PATH . 'includes/class-admin.php'; // 包含产品管理类 require_once FLEXIBLE_PRESALE_PATH . 'includes/class-product.php'; // 包含前端显示类 require_once FLEXIBLE_PRESALE_PATH . 'includes/class-frontend.php'; // 包含AJAX处理类 require_once FLEXIBLE_PRESALE_PATH . 'includes/class-ajax.php'; } private function init_components() { // 初始化各个组件 Flexible_Presale_Admin::init(); Flexible_Presale_Product::init(); Flexible_Presale_Frontend::init(); Flexible_Presale_Ajax::init(); } private function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); $table_name = $wpdb->prefix . 'flexible_presale_data'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, product_id bigint(20) NOT NULL, presale_start datetime NOT NULL, presale_end datetime NOT NULL, target_quantity int(11) NOT NULL DEFAULT 0, current_quantity int(11) NOT NULL DEFAULT 0, is_flexible tinyint(1) NOT NULL DEFAULT 1, status varchar(20) NOT NULL DEFAULT 'active', created_at datetime DEFAULT CURRENT_TIMESTAMP, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY product_id (product_id), KEY presale_end (presale_end) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } } // 启动插件 Flexible_Presale_Plugin::get_instance(); ?>
- 接下来创建产品管理类,用于处理产品的预售设置: <?php /** * 产品预售管理类 */ class Flexible_Presale_Product { public static function init() { // 添加产品预售设置字段 add_action('woocommerce_product_options_general_product_data', array(__CLASS__, 'add_presale_fields')); // 保存产品预售设置 add_action('woocommerce_process_product_meta', array(__CLASS__, 'save_presale_fields')); // 添加预售状态到产品列表 add_filter('manage_product_posts_columns', array(__CLASS__, 'add_presale_column')); add_action('manage_product_posts_custom_column', array(__CLASS__, 'show_presale_column'), 10, 2); } /** * 在产品编辑页面添加预售设置字段 */ public static function add_presale_fields() { global $post; echo '<div class="options_group presale-options">'; // 预售开关 woocommerce_wp_checkbox(array( 'id' => '_is_presale', 'label' => __('启用预售', 'flexible-presale'), 'description' => __('启用此产品的预售功能', 'flexible-presale') )); // 预售开始时间 woocommerce_wp_text_input(array( 'id' => '_presale_start_date', 'label' => __('预售开始时间', 'flexible-presale'), 'type' => 'datetime-local', 'desc_tip' => true, 'description' => __('预售开始的具体日期和时间', 'flexible-presale') )); // 预售结束时间 woocommerce_wp_text_input(array( 'id' => '_presale_end_date', 'label' => __('预售结束时间', 'flexible-presale'), 'type' => 'datetime-local', 'desc_tip' => true, 'description' => __('预售结束的具体日期和时间', 'flexible-presale') )); // 目标预售数量 woocommerce_wp_text_input(array( 'id' => '_presale_target_qty', 'label' => __('目标预售数量', 'flexible-presale'), 'type' => 'number', 'custom_attributes' => array( 'min' => '0', 'step' => '1' ), 'desc_tip' => true, 'description' => __('期望达到的预售数量', 'flexible-presale') )); // 是否启用柔性调整 woocommerce_wp_checkbox(array( 'id' => '_is_flexible_presale', 'label' => __('柔性预售', 'flexible-presale'), 'description' => __('根据预售情况自动调整生产数量', 'flexible-presale') )); // 最低生产数量 woocommerce_wp_text_input(array( 'id' => '_min_production_qty', 'label' => __('最低生产数量', 'flexible-presale'), 'type' => 'number', 'custom_attributes' => array( 'min' => '0', 'step' => '1' ), 'desc_tip' => true, 'description' => __('无论预售情况如何都会生产的最低数量', 'flexible-presale') )); echo '</div>'; // 添加JavaScript处理日期时间选择器 self::add_admin_scripts(); } /** * 保存产品预售设置 */ public static function save_presale_fields($post_id) { // 验证nonce if (!isset($_POST['woocommerce_meta_nonce']) || !wp_verify_nonce($_POST['woocommerce_meta_nonce'], 'woocommerce_save_data')) { return; } // 保存预售设置 $is_presale = isset($_POST['_is_presale']) ? 'yes' : 'no'; update_post_meta($post_id, '_is_presale', $is_presale); if ($is_presale === 'yes') { // 保存预售开始时间 if (!empty($_POST['_presale_start_date'])) { $start_date = sanitize_text_field($_POST['_presale_start_date']); update_post_meta($post_id, '_presale_start_date', $start_date); } // 保存预售结束时间 if (!empty($_POST['_presale_end_date'])) { $end_date = sanitize_text_field($_POST['_presale_end_date']); update_post_meta($post_id, '_presale_end_date', $end_date); } // 保存目标数量 if (!empty($_POST['_presale_target_qty'])) { $target_qty = intval($_POST['_presale_target_qty']); update_post_meta($post_id, '_presale_target_qty', $target_qty); } // 保存柔性预售设置 $is_flexible = isset($_POST['_is_flexible_presale']) ? 'yes' : 'no'; update_post_meta($post_id, '_is_flexible_presale', $is_flexible); // 保存最低生产数量 if (!empty($_POST['_min_production_qty'])) { $min_qty = intval($_POST['_min_production_qty']); update_post_meta($post_id, '_min_production_qty', $min_qty); } // 更新预售数据到自定义表 self::update_presale_data($post_id); } } /** * 更新预售数据到数据库 */ private static function update_presale_data($product_id) { global $wpdb; $table_name = $wpdb->prefix . 'flexible_presale_data'; $presale_data = array( 'product_id' => $product_id, 'presale_start' => get_post_meta($product_id, '_presale_start_date', true), 'presale_end' => get_post_meta($product_id, '_presale_end_date', true), 'target_quantity' => get_post_meta($product_id, '_presale_target_qty', true), 'is_flexible' => get_post_meta($product_id, '_is_flexible_presale', true) === 'yes' ? 1 : 0, 'status' => 'active' ); // 检查是否已存在记录 $existing = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_name WHERE product_id = %d", $product_id )); if ($existing) { $wpdb->update($table_name, $presale_data, array('product_id' => $product_id)); } else { $wpdb->insert($table_name, $presale_data); } } /** * 在产品列表添加预售状态列 */ public static function add_presale_column($columns) { $columns['presale_status'] = __('预售状态', 'flexible-presale'); return $columns; } /** * 显示预售状态列内容 */ public static function show_presale_column($column, $post_id) { if ($column === 'presale_status') { $is_presale = get_post_meta($post_id, '_is_presale', true); if ($is_presale === 'yes') { $end_date = get_post_meta($post_id, '_presale_end_date', true); $now = current_time('mysql'); if ($end_date > $now) { echo '<span class="presale-active">' . __('预售中', 'flexible-presale') . '</span>'; } else { echo '<span class="presale-ended">' . __('预售结束', 'flexible-presale') . '</span>'; } } else { echo '<span class="presale-inactive">' . __('未预售', 'flexible-presale') . '</span>'; } } } /** * 添加管理后台脚本 */ private static function add_admin_scripts() { ?> <script type="text/javascript"> jQuery(document).ready(function($) { // 动态显示/隐藏预售字段 $('#_is_presale').change(function() { if ($(this).is(':checked')) { $('.presale-options').show(); } else { $('.presale-options').hide(); } }).trigger('change'); // 设置日期时间选择器的最小值 var now = new Date(); var today = now.toISOString().slice(0, 16); $('#_presale_start_date').attr('min', today); $('#_presale_end_date').attr('min', today); }); </script> <?php } } ?>
- 创建前端显示类,处理产品页面的预售信息展示: <?php /** * 前端预售展示类 */ class Flexible_Presale_Frontend { public static function init() { // 在产品页面显示预售信息 add_action('woocommerce_before_add_to_cart_form', array(__CLASS__, 'show_presale_info'), 10); // 在商品列表显示预售标签 add_action('woocommerce_before_shop_loop_item_title', array(__CLASS__, 'add_presale_badge'), 5); // 修改预售产品的价格显示 add_filter('woocommerce_get_price_html', array(__CLASS__, 'modify_price_display'), 10, 2); // 添加预售倒计时脚本 add_action('wp_enqueue_scripts', array(__CLASS__, 'enqueue_frontend_scripts')); } /** * 在产品页面显示预售信息 */ public static function show_presale_info() { global $product; if (!self::is_presale_product($product->get_id())) { return; } $product_id = $product->get_id(); $presale_data = self::get_presale_data($product_id); if (!$presale_data) { return; } ?> <div class="flexible-presale-info"> <div class="presale-header"> <h3><?php _e('🎉 限时预售中', 'flexible-presale'); ?></h3> <p class="presale-description"> <?php _e('参与预售,享受优先发货和特别优惠!', 'flexible-presale'); ?> </p> </div> <div class="presale-countdown"> <h4><?php _e('预售结束倒计时:', 'flexible-presale'); ?></h4> <div id="presale-countdown-<?php echo $product_id; ?>" class="countdown-timer"></div> </div> <div class="presale-progress"> <h4><?php _e('预售进度:', 'flexible-presale'); ?></h4> <?php $current_qty = $presale_data->current_quantity; $target_qty = $presale_data->target_quantity; $percentage = $target_qty > 0 ? min(100, ($current_qty / $target_qty) * 100) : 0; ?> <div class="progress-bar"> <div class="progress-fill" style="width: <?php echo $percentage; ?>%"></div> </div> <p class="progress-text"> <?php printf(__('已预售 %d / 目标 %d (%.1f%%)', 'flexible-presale'), $current_qty, $target_qty, $percentage); ?> </p> </div> <div class="presale-notice"> <p> <?php if ($presale_data->is_flexible) : ?> <?php _e('✅ 此为柔性预售:最终生产数量将根据预售情况调整', 'flexible-presale'); ?> <?php else : ?> <?php _e('📦 此为定量预售:达到目标数量即开始生产', 'flexible-presale'); ?> <?php endif; ?> </p> <p> <?php _e('⏰ 预售结束后将根据订单安排生产,预计发货时间将另行通知', 'flexible-presale'); ?> </p> </div> </div> <script type="text/javascript"> jQuery(document).ready(function($) { // 初始化倒计时 initializeCountdown(<?php echo $product_id; ?>, '<?php echo $presale_data->presale_end; ?>'); }); </script> <?php } /** * 在商品列表显示预售标签 */ public static function add_presale_badge() { global $product; if (self::is_presale_product($product->get_id())) { echo '<span class="presale-badge">' . __('预售', 'flexible-presale') . '</span>'; } } /** * 修改预售产品的价格显示 */ public static function modify_price_display($price, $product) { )) { $presale_data = self::get_presale_data($product->get_id()); if ($presale_data) { $original_price = $product->get_regular_price(); $presale_price = $product->get_sale_price() ?: $original_price; $price_html = '<div class="presale-price">'; $price_html .= '<span class="original-price">' . wc_price($original_price) . '</span>'; $price_html .= '<span class="presale-discount">' . wc_price($presale_price) . '</span>'; $price_html .= '<span class="presale-label">' . __('预售特价', 'flexible-presale') . '</span>'; $price_html .= '</div>'; return $price_html; } } return $price; } /** * 加载前端脚本和样式 */ public static function enqueue_frontend_scripts() { if (is_product() || is_shop() || is_product_category()) { wp_enqueue_style( 'flexible-presale-frontend', FLEXIBLE_PRESALE_URL . 'assets/css/frontend.css', array(), FLEXIBLE_PRESALE_VERSION ); wp_enqueue_script( 'flexible-presale-countdown', FLEXIBLE_PRESALE_URL . 'assets/js/countdown.js', array('jquery'), FLEXIBLE_PRESALE_VERSION, true ); wp_localize_script('flexible-presale-countdown', 'presale_vars', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('flexible_presale_nonce') )); } } /** * 检查产品是否为预售产品 */ private static function is_presale_product($product_id) { $is_presale = get_post_meta($product_id, '_is_presale', true); $end_date = get_post_meta($product_id, '_presale_end_date', true); $now = current_time('mysql'); return ($is_presale === 'yes' && $end_date > $now); } /** * 获取预售数据 */ private static function get_presale_data($product_id) { global $wpdb; $table_name = $wpdb->prefix . 'flexible_presale_data'; return $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE product_id = %d AND status = 'active'", $product_id )); } }?> ## 五、倒计时与预售进度JavaScript实现 创建倒计时JavaScript文件: /** 预售倒计时功能 */ (function($) { 'use strict'; // 倒计时计时器集合 var countdownTimers = {}; /** * 初始化倒计时 */ window.initializeCountdown = function(productId, endDateStr) { var endDate = new Date(endDateStr); var countdownElement = $('#presale-countdown-' + productId); if (!countdownElement.length || isNaN(endDate.getTime())) { return; } // 更新倒计时显示 function updateCountdown() { var now = new Date(); var timeLeft = endDate - now; if (timeLeft <= 0) { clearInterval(countdownTimers[productId]); countdownElement.html('<div class="countdown-ended">预售已结束</div>'); updatePresaleStatus(productId); return; } var days = Math.floor(timeLeft / (1000 * 60 * 60 * 24)); var hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); var minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60)); var seconds = Math.floor((timeLeft % (1000 * 60)) / 1000); var countdownHtml = '<div class="countdown-digits">'; countdownHtml += '<div class="countdown-unit"><span class="digit">' + days + '</span><span class="label">天</span></div>'; countdownHtml += '<div class="countdown-unit"><span class="digit">' + hours + '</span><span class="label">时</span></div>'; countdownHtml += '<div class="countdown-unit"><span class="digit">' + minutes + '</span><span class="label">分</span></div>'; countdownHtml += '<div class="countdown-unit"><span class="digit">' + seconds + '</span><span class="label">秒</span></div>'; countdownHtml += '</div>'; countdownElement.html(countdownHtml); } // 立即更新一次 updateCountdown(); // 每秒更新一次 countdownTimers[productId] = setInterval(updateCountdown, 1000); }; /** * 更新预售状态 */ function updatePresaleStatus(productId) { $.ajax({ url: presale_vars.ajax_url, type: 'POST', data: { action: 'update_presale_status', product_id: productId, nonce: presale_vars.nonce }, success: function(response) { if (response.success) { // 刷新页面或更新UI location.reload(); } } }); } /** * 更新预售进度 */ window.updatePresaleProgress = function(productId, quantity) { $.ajax({ url: presale_vars.ajax_url, type: 'POST', data: { action: 'update_presale_progress', product_id: productId, quantity: quantity, nonce: presale_vars.nonce }, success: function(response) { if (response.success) { // 更新进度条显示 var percentage = response.data.percentage; $('.progress-fill').css('width', percentage + '%'); $('.progress-text').text('已预售 ' + response.data.current + ' / 目标 ' + response.data.target + ' (' + percentage.toFixed(1) + '%)'); } } }); }; /** * 页面加载完成后初始化所有倒计时 */ $(document).ready(function() { $('[id^="presale-countdown-"]').each(function() { var id = $(this).attr('id'); var productId = id.replace('presale-countdown-', ''); var endDate = $(this).data('end-date'); if (productId && endDate) { initializeCountdown(productId, endDate); } }); // 监听加入购物车事件 $(document).on('added_to_cart', function(e, fragments, cart_hash, $button) { var productId = $button.data('product_id'); var quantity = $button.data('quantity') || 1; if (productId) { updatePresaleProgress(productId, quantity); } }); }); })(jQuery); ## 六、AJAX处理与后台管理 创建AJAX处理类: <?php/** AJAX处理类 */ class Flexible_Presale_Ajax { public static function init() { // 更新预售状态 add_action('wp_ajax_update_presale_status', array(__CLASS__, 'update_presale_status')); add_action('wp_ajax_nopriv_update_presale_status', array(__CLASS__, 'update_presale_status')); // 更新预售进度 add_action('wp_ajax_update_presale_progress', array(__CLASS__, 'update_presale_progress')); add_action('wp_ajax_nopriv_update_presale_progress', array(__CLASS__, 'update_presale_progress')); // 获取预售统计数据 add_action('wp_ajax_get_presale_stats', array(__CLASS__, 'get_presale_stats')); // 每日检查预售状态 add_action('flexible_presale_daily_check', array(__CLASS__, 'daily_status_check')); } /** * 更新预售状态 */ public static function update_presale_status() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'flexible_presale_nonce')) { wp_die('权限验证失败'); } $product_id = intval($_POST['product_id']); if ($product_id) { // 更新产品预售状态 update_post_meta($product_id, '_is_presale', 'no'); // 更新数据库状态 global $wpdb; $table_name = $wpdb->prefix . 'flexible_presale_data'; $wpdb->update( $table_name, array('status' => 'ended'), array('product_id' => $product_id) ); // 发送邮件通知 self::send_presale_end_notification($product_id); wp_send_json_success(array( 'message' => '预售状态已更新' )); } wp_send_json_error(array( 'message' => '更新失败' )); } /** * 更新预售进度 */ public static function update_presale_progress() { // 验证nonce if (!wp_verify_nonce($_POST['nonce'], 'flexible_presale_nonce')) { wp_die('权限验证失败'); } $product_id = intval($_POST['product_id']); $quantity = intval($_POST['quantity']); if ($product_id && $quantity > 0) { global $wpdb; $table_name = $wpdb->prefix . 'flexible_presale_data'; // 获取当前数据 $current_data = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE product_id = %d AND status = 'active'", $product_id )); if ($current_data) { // 更新预售数量 $new_quantity = $current_data->current_quantity + $quantity; $wpdb->update( $table_name, array('current_quantity' => $new_quantity), array('id' => $current_data->id) ); // 计算百分比 $percentage = $current_data->target_quantity > 0 ? min(100, ($new_quantity / $current_data->target_quantity) * 100) : 0; // 检查是否达到目标 if ($new_quantity >= $current_data->target_quantity && $current_data->target_quantity > 0) { self::handle_target_reached($product_id, $current_data); } wp_send_json_success(array( 'current' => $new_quantity, 'target' => $current_data->target_quantity, 'percentage' => $percentage )); } } wp_send_json_error(array( 'message' => '更新失败' )); } /** * 处理达到预售目标 */ private static function handle_target_reached($product_id, $presale_data) { // 发送达到目标通知 $admin_email = get_option('admin_email'); $subject = sprintf(__('产品 #%d 已达到预售目标', 'flexible-presale'), $product_id); $message = sprintf(__( "产品 #%d 已达到预售目标数量 %d。n" . "当前预售数量:%dn" . "预售结束时间:%sn" . "请及时安排生产。", 'flexible-presale' ), $product_id, $presale_data->target_quantity, $presale_data->current_quantity, $presale_data->presale_end); wp_mail($admin_email, $subject, $message); // 如果是柔性预售,计算建议生产数量 if ($presale_data->is_flexible) { $min_qty = get_post_meta($product_id, '_min_production_qty', true) ?: 0; $suggested_qty = max($min_qty, $presale_data->current_quantity); // 保存建议生产数量 update_post_meta($product_id, '_suggested_production_qty', $suggested_qty); // 发送生产建议 $subject = sprintf(__('产品 #%d 生产数量建议', 'flexible-presale'), $product_id); $message = sprintf(__( "根据柔性预售结果,建议生产数量:%dn" . "最低生产数量:%dn" . "实际预售数量:%d", 'flexible-presale' ), $suggested_qty, $min_qty, $presale_data->current_quantity); wp_mail($admin_email, $subject, $message); } } /** * 发送预售结束通知 */ private static function send_presale_end_notification($product_id) { global $wpdb; $table_name = $wpdb->prefix . 'flexible_presale_data'; $presale_data = $wpdb->get_row($wpdb->prepare( "SELECT * FROM $table_name WHERE product_id = %d", $product_id )); if ($presale_data) { $admin_email = get_option('admin_email'); $product = wc_get_product($product_id); $product_name = $product ? $product->get_name() : '产品 #' . $product_id; $subject = sprintf(__('产品"%s"预售已结束', 'flexible-presale'), $product_name); $message = sprintf(__( "产品:%sn" . "预售开始时间:%sn" . "预售结束时间:%sn" . "目标预售数量:%dn" . "实际预售数量:%dn" . "完成度:%.1f%%nn" . "请登录后台查看详细数据并安排后续工作。", 'flexible-presale' ), $product_name, $presale_data->presale_start, $presale_data->presale_end, $presale_data->target_quantity, $presale_data->current_quantity, ($presale_data->target_quantity > 0 ? ($presale_data->current_quantity / $presale_data->target_quantity * 100) : 0)); wp_mail($admin_email, $subject, $message); } } /** * 每日检查预售状态 */ public static function daily_status_check() { global $wpdb; $table_name = $wpdb->prefix . 'flexible_presale_data'; $now = current_time('mysql'); // 查找已结束但状态未更新的预售 $ended_presales = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name WHERE presale_end < %s AND status = 'active'", $now )); foreach ($ended_presales as $presale) { // 更新状态 $wpdb->update( $table_name, array('status' => 'ended'), array('id' => $presale->id) ); // 更新产品元数据 update_post_meta($presale->product_id, '_is_presale', 'no'); // 发送通知 self::send_presale_end_notification($presale->product_id); } // 记录检查日志 error_log('柔性预售插件每日检查完成,处理了 ' . count($ended_presales) . ' 个已结束的预售'); } }?> ## 七、CSS样式设计与优化 创建前端样式文件: / 预售插件前端样式 /.flexible-presale-info { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 10px; padding: 25px; margin-bottom: 30px; color: white; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); } .presale-header h3 { margin: 0 0 10px 0; font-size: 24px; font-weight: bold; } .presale-description { margin: 0 0 20px 0; opacity: 0.9; font-size: 14px; } / 倒计时样式 /.presale-countdown { margin: 25px 0; padding: 20px; background: rgba(255, 255, 255, 0.1); border-radius: 8px; } .presale-countdown h4 { margin: 0 0 15px 0; font-size: 16px; } .countdown-timer { display: flex; justify-content: center; gap: 15px; } .countdown-unit { text-align: center; min-width: 70px; } .countdown-unit .digit { display: block; font-size: 32px; font-weight: bold; background: rgba(255, 255, 255, 0.2); padding: 10px; border-radius: 5px; margin-bottom: 5px; } .countdown-unit .label { font-size: 12px; opacity: 0.8; text-transform: uppercase; } .countdown-ended { text-align: center; font-size: 18px; font-weight: bold; padding: 15px; background: rgba(255, 255, 255, 0.2); border-radius: 5px; } /* 进度条样式
在文创产品销售领域,柔性预售模式越来越受欢迎。这种模式允许商家根据预售情况调整生产数量,降低库存风险。本教程将带领大家开发一个完整的WordPress文创产品柔性限时预售插件。
核心功能需求:
- 为产品添加预售状态和预售时间设置
- 显示预售倒计时和预售进度
- 支持预售数量限制和柔性调整
- 预售结束后自动转为正常销售或下架
- 后台管理界面和销售数据统计
首先创建插件的基本目录结构和主文件:
<?php
/**
* Plugin Name: 文创产品柔性限时预售插件
* Plugin URI: https://yourwebsite.com/
* Description: 为WordPress文创产品添加柔性限时预售功能
* Version: 1.0.0
* Author: 你的名字
* License: GPL v2 or later
* Text Domain: flexible-presale
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义插件常量
define('FLEXIBLE_PRESALE_VERSION', '1.0.0');
define('FLEXIBLE_PRESALE_PATH', plugin_dir_path(__FILE__));
define('FLEXIBLE_PRESALE_URL', plugin_dir_url(__FILE__));
// 初始化插件
class Flexible_Presale_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('plugins_loaded', array($this, 'init'));
}
public function activate() {
// 创建必要的数据库表
$this->create_tables();
// 设置默认选项
update_option('flexible_presale_version', FLEXIBLE_PRESALE_VERSION);
}
public function deactivate() {
// 清理临时数据
wp_clear_scheduled_hook('flexible_presale_daily_check');
}
public function init() {
// 加载文本域
load_plugin_textdomain('flexible-presale', false, dirname(plugin_basename(__FILE__)) . '/languages');
// 包含必要文件
$this->includes();
// 初始化组件
$this->init_components();
}
private function includes() {
// 包含后台管理类
require_once FLEXIBLE_PRESALE_PATH . 'includes/class-admin.php';
// 包含产品管理类
require_once FLEXIBLE_PRESALE_PATH . 'includes/class-product.php';
// 包含前端显示类
require_once FLEXIBLE_PRESALE_PATH . 'includes/class-frontend.php';
// 包含AJAX处理类
require_once FLEXIBLE_PRESALE_PATH . 'includes/class-ajax.php';
}
private function init_components() {
// 初始化各个组件
Flexible_Presale_Admin::init();
Flexible_Presale_Product::init();
Flexible_Presale_Frontend::init();
Flexible_Presale_Ajax::init();
}
private function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$table_name = $wpdb->prefix . 'flexible_presale_data';
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
product_id bigint(20) NOT NULL,
presale_start datetime NOT NULL,
presale_end datetime NOT NULL,
target_quantity int(11) NOT NULL DEFAULT 0,
current_quantity int(11) NOT NULL DEFAULT 0,
is_flexible tinyint(1) NOT NULL DEFAULT 1,
status varchar(20) NOT NULL DEFAULT 'active',
created_at datetime DEFAULT CURRENT_TIMESTAMP,
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY product_id (product_id),
KEY presale_end (presale_end)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
}
// 启动插件
Flexible_Presale_Plugin::get_instance();
?>
接下来创建产品管理类,用于处理产品的预售设置:
<?php
/**
* 产品预售管理类
*/
class Flexible_Presale_Product {
public static function init() {
// 添加产品预售设置字段
add_action('woocommerce_product_options_general_product_data', array(__CLASS__, 'add_presale_fields'));
// 保存产品预售设置
add_action('woocommerce_process_product_meta', array(__CLASS__, 'save_presale_fields'));
// 添加预售状态到产品列表
add_filter('manage_product_posts_columns', array(__CLASS__, 'add_presale_column'));
add_action('manage_product_posts_custom_column', array(__CLASS__, 'show_presale_column'), 10, 2);
}
/**
* 在产品编辑页面添加预售设置字段
*/
public static function add_presale_fields() {
global $post;
echo '<div class="options_group presale-options">';
// 预售开关
woocommerce_wp_checkbox(array(
'id' => '_is_presale',
'label' => __('启用预售', 'flexible-presale'),
'description' => __('启用此产品的预售功能', 'flexible-presale')
));
// 预售开始时间
woocommerce_wp_text_input(array(
'id' => '_presale_start_date',
'label' => __('预售开始时间', 'flexible-presale'),
'type' => 'datetime-local',
'desc_tip' => true,
'description' => __('预售开始的具体日期和时间', 'flexible-presale')
));
// 预售结束时间
woocommerce_wp_text_input(array(
'id' => '_presale_end_date',
'label' => __('预售结束时间', 'flexible-presale'),
'type' => 'datetime-local',
'desc_tip' => true,
'description' => __('预售结束的具体日期和时间', 'flexible-presale')
));
// 目标预售数量
woocommerce_wp_text_input(array(
'id' => '_presale_target_qty',
'label' => __('目标预售数量', 'flexible-presale'),
'type' => 'number',
'custom_attributes' => array(
'min' => '0',
'step' => '1'
),
'desc_tip' => true,
'description' => __('期望达到的预售数量', 'flexible-presale')
));
// 是否启用柔性调整
woocommerce_wp_checkbox(array(
'id' => '_is_flexible_presale',
'label' => __('柔性预售', 'flexible-presale'),
'description' => __('根据预售情况自动调整生产数量', 'flexible-presale')
));
// 最低生产数量
woocommerce_wp_text_input(array(
'id' => '_min_production_qty',
'label' => __('最低生产数量', 'flexible-presale'),
'type' => 'number',
'custom_attributes' => array(
'min' => '0',
'step' => '1'
),
'desc_tip' => true,
'description' => __('无论预售情况如何都会生产的最低数量', 'flexible-presale')
));
echo '</div>';
// 添加JavaScript处理日期时间选择器
self::add_admin_scripts();
}
/**
* 保存产品预售设置
*/
public static function save_presale_fields($post_id) {
// 验证nonce
if (!isset($_POST['woocommerce_meta_nonce']) ||
!wp_verify_nonce($_POST['woocommerce_meta_nonce'], 'woocommerce_save_data')) {
return;
}
// 保存预售设置
$is_presale = isset($_POST['_is_presale']) ? 'yes' : 'no';
update_post_meta($post_id, '_is_presale', $is_presale);
if ($is_presale === 'yes') {
// 保存预售开始时间
if (!empty($_POST['_presale_start_date'])) {
$start_date = sanitize_text_field($_POST['_presale_start_date']);
update_post_meta($post_id, '_presale_start_date', $start_date);
}
// 保存预售结束时间
if (!empty($_POST['_presale_end_date'])) {
$end_date = sanitize_text_field($_POST['_presale_end_date']);
update_post_meta($post_id, '_presale_end_date', $end_date);
}
// 保存目标数量
if (!empty($_POST['_presale_target_qty'])) {
$target_qty = intval($_POST['_presale_target_qty']);
update_post_meta($post_id, '_presale_target_qty', $target_qty);
}
// 保存柔性预售设置
$is_flexible = isset($_POST['_is_flexible_presale']) ? 'yes' : 'no';
update_post_meta($post_id, '_is_flexible_presale', $is_flexible);
// 保存最低生产数量
if (!empty($_POST['_min_production_qty'])) {
$min_qty = intval($_POST['_min_production_qty']);
update_post_meta($post_id, '_min_production_qty', $min_qty);
}
// 更新预售数据到自定义表
self::update_presale_data($post_id);
}
}
/**
* 更新预售数据到数据库
*/
private static function update_presale_data($product_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'flexible_presale_data';
$presale_data = array(
'product_id' => $product_id,
'presale_start' => get_post_meta($product_id, '_presale_start_date', true),
'presale_end' => get_post_meta($product_id, '_presale_end_date', true),
'target_quantity' => get_post_meta($product_id, '_presale_target_qty', true),
'is_flexible' => get_post_meta($product_id, '_is_flexible_presale', true) === 'yes' ? 1 : 0,
'status' => 'active'
);
// 检查是否已存在记录
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM $table_name WHERE product_id = %d",
$product_id
));
if ($existing) {
$wpdb->update($table_name, $presale_data, array('product_id' => $product_id));
} else {
$wpdb->insert($table_name, $presale_data);
}
}
/**
* 在产品列表添加预售状态列
*/
public static function add_presale_column($columns) {
$columns['presale_status'] = __('预售状态', 'flexible-presale');
return $columns;
}
/**
* 显示预售状态列内容
*/
public static function show_presale_column($column, $post_id) {
if ($column === 'presale_status') {
$is_presale = get_post_meta($post_id, '_is_presale', true);
if ($is_presale === 'yes') {
$end_date = get_post_meta($post_id, '_presale_end_date', true);
$now = current_time('mysql');
if ($end_date > $now) {
echo '<span class="presale-active">' . __('预售中', 'flexible-presale') . '</span>';
} else {
echo '<span class="presale-ended">' . __('预售结束', 'flexible-presale') . '</span>';
}
} else {
echo '<span class="presale-inactive">' . __('未预售', 'flexible-presale') . '</span>';
}
}
}
/**
* 添加管理后台脚本
*/
private static function add_admin_scripts() {
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
// 动态显示/隐藏预售字段
$('#_is_presale').change(function() {
if ($(this).is(':checked')) {
$('.presale-options').show();
} else {
$('.presale-options').hide();
}
}).trigger('change');
// 设置日期时间选择器的最小值
var now = new Date();
var today = now.toISOString().slice(0, 16);
$('#_presale_start_date').attr('min', today);
$('#_presale_end_date').attr('min', today);
});
</script>
<?php
}
}
?>
创建前端显示类,处理产品页面的预售信息展示:
<?php
/**
* 前端预售展示类
*/
class Flexible_Presale_Frontend {
public static function init() {
// 在产品页面显示预售信息
add_action('woocommerce_before_add_to_cart_form', array(__CLASS__, 'show_presale_info'), 10);
// 在商品列表显示预售标签
add_action('woocommerce_before_shop_loop_item_title', array(__CLASS__, 'add_presale_badge'), 5);
// 修改预售产品的价格显示
add_filter('woocommerce_get_price_html', array(__CLASS__, 'modify_price_display'), 10, 2);
// 添加预售倒计时脚本
add_action('wp_enqueue_scripts', array(__CLASS__, 'enqueue_frontend_scripts'));
}
/**
* 在产品页面显示预售信息
*/
public static function show_presale_info() {
global $product;
if (!self::is_presale_product($product->get_id())) {
return;
}
$product_id = $product->get_id();
$presale_data = self::get_presale_data($product_id);
if (!$presale_data) {
return;
}
?>
<div class="flexible-presale-info">
<div class="presale-header">
<h3><?php _e('🎉 限时预售中', 'flexible-presale'); ?></h3>
<p class="presale-description">
<?php _e('参与预售,享受优先发货和特别优惠!', 'flexible-presale'); ?>
</p>
</div>
<div class="presale-countdown">
<h4><?php _e('预售结束倒计时:', 'flexible-presale'); ?></h4>
<div id="presale-countdown-<?php echo $product_id; ?>" class="countdown-timer"></div>
</div>
<div class="presale-progress">
<h4><?php _e('预售进度:', 'flexible-presale'); ?></h4>
<?php
$current_qty = $presale_data->current_quantity;
$target_qty = $presale_data->target_quantity;
$percentage = $target_qty > 0 ? min(100, ($current_qty / $target_qty) * 100) : 0;
?>
<div class="progress-bar">
<div class="progress-fill" style="width: <?php echo $percentage; ?>%"></div>
</div>
<p class="progress-text">
<?php printf(__('已预售 %d / 目标 %d (%.1f%%)', 'flexible-presale'),
$current_qty, $target_qty, $percentage); ?>
</p>
</div>
<div class="presale-notice">
<p>
<?php if ($presale_data->is_flexible) : ?>
<?php _e('✅ 此为柔性预售:最终生产数量将根据预售情况调整', 'flexible-presale'); ?>
<?php else : ?>
<?php _e('📦 此为定量预售:达到目标数量即开始生产', 'flexible-presale'); ?>
<?php endif; ?>
</p>
<p>
<?php _e('⏰ 预售结束后将根据订单安排生产,预计发货时间将另行通知', 'flexible-presale'); ?>
</p>
</div>
</div>
<script type="text/javascript">
jQuery(document).ready(function($) {
// 初始化倒计时
initializeCountdown(<?php echo $product_id; ?>, '<?php echo $presale_data->presale_end; ?>');
});
</script>
<?php
}
/**
* 在商品列表显示预售标签
*/
public static function add_presale_badge() {
global $product;
if (self::is_presale_product($product->get_id())) {
echo '<span class="presale-badge">' . __('预售', 'flexible-presale') . '</span>';
}
}
/**
* 修改预售产品的价格显示
*/
public static function modify_price_display($price, $product) {
)) {
$presale_data = self::get_presale_data($product->get_id());
if ($presale_data) {
$original_price = $product->get_regular_price();
$presale_price = $product->get_sale_price() ?: $original_price;
$price_html = '<div class="presale-price">';
$price_html .= '<span class="original-price">' . wc_price($original_price) . '</span>';
$price_html .= '<span class="presale-discount">' . wc_price($presale_price) . '</span>';
$price_html .= '<span class="presale-label">' . __('预售特价', 'flexible-presale') . '</span>';
$price_html .= '</div>';
return $price_html;
}
}
return $price;
}
/**
* 加载前端脚本和样式
*/
public static function enqueue_frontend_scripts() {
if (is_product() || is_shop() || is_product_category()) {
wp_enqueue_style(
'flexible-presale-frontend',
FLEXIBLE_PRESALE_URL . 'assets/css/frontend.css',
array(),
FLEXIBLE_PRESALE_VERSION
);
wp_enqueue_script(
'flexible-presale-countdown',
FLEXIBLE_PRESALE_URL . 'assets/js/countdown.js',
array('jquery'),
FLEXIBLE_PRESALE_VERSION,
true
);
wp_localize_script('flexible-presale-countdown', 'presale_vars', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('flexible_presale_nonce')
));
}
}
/**
* 检查产品是否为预售产品
*/
private static function is_presale_product($product_id) {
$is_presale = get_post_meta($product_id, '_is_presale', true);
$end_date = get_post_meta($product_id, '_presale_end_date', true);
$now = current_time('mysql');
return ($is_presale === 'yes' && $end_date > $now);
}
/**
* 获取预售数据
*/
private static function get_presale_data($product_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'flexible_presale_data';
return $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE product_id = %d AND status = 'active'",
$product_id
));
}
}
?>
## 五、倒计时与预售进度JavaScript实现
创建倒计时JavaScript文件:
/**
- 预售倒计时功能
*/
(function($) {
'use strict';
// 倒计时计时器集合
var countdownTimers = {};
/**
* 初始化倒计时
*/
window.initializeCountdown = function(productId, endDateStr) {
var endDate = new Date(endDateStr);
var countdownElement = $('#presale-countdown-' + productId);
if (!countdownElement.length || isNaN(endDate.getTime())) {
return;
}
// 更新倒计时显示
function updateCountdown() {
var now = new Date();
var timeLeft = endDate - now;
if (timeLeft <= 0) {
clearInterval(countdownTimers[productId]);
countdownElement.html('<div class="countdown-ended">预售已结束</div>');
updatePresaleStatus(productId);
return;
}
var days = Math.floor(timeLeft / (1000 * 60 * 60 * 24));
var hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
var countdownHtml = '<div class="countdown-digits">';
countdownHtml += '<div class="countdown-unit"><span class="digit">' + days + '</span><span class="label">天</span></div>';
countdownHtml += '<div class="countdown-unit"><span class="digit">' + hours + '</span><span class="label">时</span></div>';
countdownHtml += '<div class="countdown-unit"><span class="digit">' + minutes + '</span><span class="label">分</span></div>';
countdownHtml += '<div class="countdown-unit"><span class="digit">' + seconds + '</span><span class="label">秒</span></div>';
countdownHtml += '</div>';
countdownElement.html(countdownHtml);
}
// 立即更新一次
updateCountdown();
// 每秒更新一次
countdownTimers[productId] = setInterval(updateCountdown, 1000);
};
/**
* 更新预售状态
*/
function updatePresaleStatus(productId) {
$.ajax({
url: presale_vars.ajax_url,
type: 'POST',
data: {
action: 'update_presale_status',
product_id: productId,
nonce: presale_vars.nonce
},
success: function(response) {
if (response.success) {
// 刷新页面或更新UI
location.reload();
}
}
});
}
/**
* 更新预售进度
*/
window.updatePresaleProgress = function(productId, quantity) {
$.ajax({
url: presale_vars.ajax_url,
type: 'POST',
data: {
action: 'update_presale_progress',
product_id: productId,
quantity: quantity,
nonce: presale_vars.nonce
},
success: function(response) {
if (response.success) {
// 更新进度条显示
var percentage = response.data.percentage;
$('.progress-fill').css('width', percentage + '%');
$('.progress-text').text('已预售 ' + response.data.current + ' / 目标 ' + response.data.target + ' (' + percentage.toFixed(1) + '%)');
}
}
});
};
/**
* 页面加载完成后初始化所有倒计时
*/
$(document).ready(function() {
$('[id^="presale-countdown-"]').each(function() {
var id = $(this).attr('id');
var productId = id.replace('presale-countdown-', '');
var endDate = $(this).data('end-date');
if (productId && endDate) {
initializeCountdown(productId, endDate);
}
});
// 监听加入购物车事件
$(document).on('added_to_cart', function(e, fragments, cart_hash, $button) {
var productId = $button.data('product_id');
var quantity = $button.data('quantity') || 1;
if (productId) {
updatePresaleProgress(productId, quantity);
}
});
});
})(jQuery);
## 六、AJAX处理与后台管理
创建AJAX处理类:
<?php
/**
- AJAX处理类
*/
class Flexible_Presale_Ajax {
public static function init() {
// 更新预售状态
add_action('wp_ajax_update_presale_status', array(__CLASS__, 'update_presale_status'));
add_action('wp_ajax_nopriv_update_presale_status', array(__CLASS__, 'update_presale_status'));
// 更新预售进度
add_action('wp_ajax_update_presale_progress', array(__CLASS__, 'update_presale_progress'));
add_action('wp_ajax_nopriv_update_presale_progress', array(__CLASS__, 'update_presale_progress'));
// 获取预售统计数据
add_action('wp_ajax_get_presale_stats', array(__CLASS__, 'get_presale_stats'));
// 每日检查预售状态
add_action('flexible_presale_daily_check', array(__CLASS__, 'daily_status_check'));
}
/**
* 更新预售状态
*/
public static function update_presale_status() {
// 验证nonce
if (!wp_verify_nonce($_POST['nonce'], 'flexible_presale_nonce')) {
wp_die('权限验证失败');
}
$product_id = intval($_POST['product_id']);
if ($product_id) {
// 更新产品预售状态
update_post_meta($product_id, '_is_presale', 'no');
// 更新数据库状态
global $wpdb;
$table_name = $wpdb->prefix . 'flexible_presale_data';
$wpdb->update(
$table_name,
array('status' => 'ended'),
array('product_id' => $product_id)
);
// 发送邮件通知
self::send_presale_end_notification($product_id);
wp_send_json_success(array(
'message' => '预售状态已更新'
));
}
wp_send_json_error(array(
'message' => '更新失败'
));
}
/**
* 更新预售进度
*/
public static function update_presale_progress() {
// 验证nonce
if (!wp_verify_nonce($_POST['nonce'], 'flexible_presale_nonce')) {
wp_die('权限验证失败');
}
$product_id = intval($_POST['product_id']);
$quantity = intval($_POST['quantity']);
if ($product_id && $quantity > 0) {
global $wpdb;
$table_name = $wpdb->prefix . 'flexible_presale_data';
// 获取当前数据
$current_data = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE product_id = %d AND status = 'active'",
$product_id
));
if ($current_data) {
// 更新预售数量
$new_quantity = $current_data->current_quantity + $quantity;
$wpdb->update(
$table_name,
array('current_quantity' => $new_quantity),
array('id' => $current_data->id)
);
// 计算百分比
$percentage = $current_data->target_quantity > 0
? min(100, ($new_quantity / $current_data->target_quantity) * 100)
: 0;
// 检查是否达到目标
if ($new_quantity >= $current_data->target_quantity && $current_data->target_quantity > 0) {
self::handle_target_reached($product_id, $current_data);
}
wp_send_json_success(array(
'current' => $new_quantity,
'target' => $current_data->target_quantity,
'percentage' => $percentage
));
}
}
wp_send_json_error(array(
'message' => '更新失败'
));
}
/**
* 处理达到预售目标
*/
private static function handle_target_reached($product_id, $presale_data) {
// 发送达到目标通知
$admin_email = get_option('admin_email');
$subject = sprintf(__('产品 #%d 已达到预售目标', 'flexible-presale'), $product_id);
$message = sprintf(__(
"产品 #%d 已达到预售目标数量 %d。n" .
"当前预售数量:%dn" .
"预售结束时间:%sn" .
"请及时安排生产。",
'flexible-presale'
), $product_id, $presale_data->target_quantity,
$presale_data->current_quantity, $presale_data->presale_end);
wp_mail($admin_email, $subject, $message);
// 如果是柔性预售,计算建议生产数量
if ($presale_data->is_flexible) {
$min_qty = get_post_meta($product_id, '_min_production_qty', true) ?: 0;
$suggested_qty = max($min_qty, $presale_data->current_quantity);
// 保存建议生产数量
update_post_meta($product_id, '_suggested_production_qty', $suggested_qty);
// 发送生产建议
$subject = sprintf(__('产品 #%d 生产数量建议', 'flexible-presale'), $product_id);
$message = sprintf(__(
"根据柔性预售结果,建议生产数量:%dn" .
"最低生产数量:%dn" .
"实际预售数量:%d",
'flexible-presale'
), $suggested_qty, $min_qty, $presale_data->current_quantity);
wp_mail($admin_email, $subject, $message);
}
}
/**
* 发送预售结束通知
*/
private static function send_presale_end_notification($product_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'flexible_presale_data';
$presale_data = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM $table_name WHERE product_id = %d",
$product_id
));
if ($presale_data) {
$admin_email = get_option('admin_email');
$product = wc_get_product($product_id);
$product_name = $product ? $product->get_name() : '产品 #' . $product_id;
$subject = sprintf(__('产品"%s"预售已结束', 'flexible-presale'), $product_name);
$message = sprintf(__(
"产品:%sn" .
"预售开始时间:%sn" .
"预售结束时间:%sn" .
"目标预售数量:%dn" .
"实际预售数量:%dn" .
"完成度:%.1f%%nn" .
"请登录后台查看详细数据并安排后续工作。",
'flexible-presale'
), $product_name, $presale_data->presale_start,
$presale_data->presale_end, $presale_data->target_quantity,
$presale_data->current_quantity,
($presale_data->target_quantity > 0 ?
($presale_data->current_quantity / $presale_data->target_quantity * 100) : 0));
wp_mail($admin_email, $subject, $message);
}
}
/**
* 每日检查预售状态
*/
public static function daily_status_check() {
global $wpdb;
$table_name = $wpdb->prefix . 'flexible_presale_data';
$now = current_time('mysql');
// 查找已结束但状态未更新的预售
$ended_presales = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $table_name WHERE presale_end < %s AND status = 'active'",
$now
));
foreach ($ended_presales as $presale) {
// 更新状态
$wpdb->update(
$table_name,
array('status' => 'ended'),
array('id' => $presale->id)
);
// 更新产品元数据
update_post_meta($presale->product_id, '_is_presale', 'no');
// 发送通知
self::send_presale_end_notification($presale->product_id);
}
// 记录检查日志
error_log('柔性预售插件每日检查完成,处理了 ' . count($ended_presales) . ' 个已结束的预售');
}
}
?>
## 七、CSS样式设计与优化
创建前端样式文件:
/ 预售插件前端样式 /
.flexible-presale-info {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 10px;
padding: 25px;
margin-bottom: 30px;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.presale-header h3 {
margin: 0 0 10px 0;
font-size: 24px;
font-weight: bold;
}
.presale-description {
margin: 0 0 20px 0;
opacity: 0.9;
font-size: 14px;
}
/ 倒计时样式 /
.presale-countdown {
margin: 25px 0;
padding: 20px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
}
.presale-countdown h4 {
margin: 0 0 15px 0;
font-size: 16px;
}
.countdown-timer {
display: flex;
justify-content: center;
gap: 15px;
}
.countdown-unit {
text-align: center;
min-width: 70px;
}
.countdown-unit .digit {
display: block;
font-size: 32px;
font-weight: bold;
background: rgba(255, 255, 255, 0.2);
padding: 10px;
border-radius: 5px;
margin-bottom: 5px;
}
.countdown-unit .label {
font-size: 12px;
opacity: 0.8;
text-transform: uppercase;
}
.countdown-ended {
text-align: center;
font-size: 18px;
font-weight: bold;
padding: 15px;
background: rgba(255, 255, 255, 0.2);
border-radius: 5px;
}
/* 进度条样式


