如何无需整页刷新动态更新WooCommerce变体面板?
动态刷新WooCommerce产品编辑页的变体与属性面板(无需整页刷新)
问题背景
通过AJAX成功创建产品属性和变体后,WooCommerce产品编辑页的「变体」和「属性」面板无法即时显示新增内容,必须整页刷新才能看到;直接点击「更新」按钮会导致新增数据丢失。需要实现无刷新动态更新面板。
解决方案
WooCommerce后台的属性与变体面板基于Backbone.js构建,前端数据与后端通过模型/集合同步。要解决问题,需同步前端模型数据并触发面板重新渲染,同时确保隐藏表单字段的内容更新,避免「更新」时丢失数据。
1. 优化AJAX后端响应,返回关键数据
修改PHP的AJAX处理函数,在成功响应中返回新增的变体ID、属性分类及产品属性数据,方便前端同步:
function create_drop_print_variations() { // ... 原有验证和初始化代码 ... $added_variation_ids = []; foreach ($variations as $variation_data) { $sku = sanitize_title($variation_data['sku']); if (!term_exists($sku, $taxonomy)) { $term = wp_insert_term($sku, $taxonomy); if (is_wp_error($term)) { drop_print_log("Error creating term $sku: " . $term->get_error_message()); continue; } } $term_slug = get_term_by('name', $sku, $taxonomy)->slug; if (!isset($product_attributes[$taxonomy])) { $product_attributes[$taxonomy] = new WC_Product_Attribute(); $product_attributes[$taxonomy]->set_name($taxonomy); $product_attributes[$taxonomy]->set_options([$term_slug]); $product_attributes[$taxonomy]->set_visible(1); $product_attributes[$taxonomy]->set_variation(1); $updated_attributes = true; } elseif (!in_array($term_slug, $product_attributes[$taxonomy]->get_options())) { $options = $product_attributes[$taxonomy]->get_options(); $options[] = $term_slug; $product_attributes[$taxonomy]->set_options($options); $updated_attributes = true; } $variation_id = wp_insert_post([ 'post_title' => $product->get_name() . ' - ' . $sku, 'post_name' => 'product-' . $product_id . '-variation-' . $sku, 'post_status' => 'publish', 'post_parent' => $product_id, 'post_type' => 'product_variation', 'guid' => $product->get_permalink(), ]); if ($variation_id) { $added_variation_ids[] = $variation_id; $variation = new WC_Product_Variation($variation_id); update_post_meta($variation_id, 'attribute_' . $taxonomy, $term_slug); $variation->set_regular_price($variation_data['regular_price']); $variation->set_price($variation_data['regular_price']); $variation->save(); } else { drop_print_log("Error creating variation for $sku."); } } if ($updated_attributes) { $product->set_attributes($product_attributes); $product->save(); } wc_delete_product_transients($product_id); wc_delete_shop_order_transients(); wp_cache_flush(); // 返回包含新增数据的成功响应 wp_send_json_success([ 'message' => 'Variations created successfully.', 'added_variations' => $added_variation_ids, 'attribute_taxonomy' => $taxonomy, 'product_attributes' => $product->get_attributes() ]); }
2. 修复属性分类注册问题
确保属性分类同时关联product和product_variation类型,否则属性不会在产品编辑页显示:
$taxonomy = 'pa_print_dimensions'; if (!taxonomy_exists($taxonomy)) { register_taxonomy( $taxonomy, array('product', 'product_variation'), [ 'hierarchical' => false, 'label' => 'Print Dimensions', 'query_var' => true, 'rewrite' => ['slug' => sanitize_title('print_dimensions')], 'show_in_rest' => true, 'meta_box_cb' => 'post_categories_meta_box' ] ); }
3. 前端实现面板刷新逻辑
在AJAX请求的成功回调中,添加代码同步前端模型、渲染面板并更新隐藏字段:
.then(data => { console.log('AJAX Response:', data); if (data.success) { // 刷新属性面板 if (window.wc_product_attributes) { window.wc_product_attributes.fetch_attributes(); window.wc_product_attributes.render(); } // 刷新变体面板 if (window.wc_variations_admin) { window.wc_variations_admin.variations.reset(); window.wc_variations_admin.variations.fetch({ success: function() { window.wc_variations_admin.render(); jQuery(document).trigger('woocommerce_variations_loaded'); } }); } // 更新产品属性隐藏字段,避免点击更新时丢失数据 const productId = document.querySelector('#post_ID').value; jQuery.post(ajaxurl, { action: 'woocommerce_get_product_data', post_id: productId, security: wc_admin_meta_boxes_variations_nonce }, function(response) { if (response.success) { jQuery('#_product_attributes').val(JSON.stringify(response.data.product_attributes)); } }); alert('Variations created successfully!'); } else { alert('Error: ' + data.data.message); } })
完整修改后的插件代码
<?php /** * Plugin Name: Minimal WooCommerce Attribute Button with AJAX * Description: Adds a button to the edit product screen to log attribute data and trigger an AJAX handler. * Version: 1.1.2 * Author: Callum Alden */ if (!function_exists('drop_print_log')) { function drop_print_log($message) { error_log(date('Y-m-d H:i:s') . " " . $message . "\n", 3, WP_CONTENT_DIR . '/minimal-attribute-button-plugin-log.txt'); } } function create_drop_print_variations() { $nonce_result = check_ajax_referer('drop_print_variation_nonce', 'nonce', false); if (false === $nonce_result) { wp_send_json_error(['error' => "Invalid nonce"]); wp_die(); } drop_print_log("Nonce verified successfully."); if (!isset($_POST['product_id']) || !isset($_POST['variations'])) { wp_send_json_error(['message' => 'Missing product ID or variations data.']); return; } $product_id = intval($_POST['product_id']); $variations = json_decode(stripslashes($_POST['variations']), true); if (!is_array($variations)) { wp_send_json_error(['message' => 'Invalid variations data.']); return; } $product = wc_get_product($product_id); if (!$product || $product->get_type() !== 'variable') { wp_send_json_error(['message' => 'Product is not a variable product.']); return; } $taxonomy = 'pa_print_dimensions'; if (!taxonomy_exists($taxonomy)) { register_taxonomy( $taxonomy, array('product', 'product_variation'), [ 'hierarchical' => false, 'label' => 'Print Dimensions', 'query_var' => true, 'rewrite' => ['slug' => sanitize_title('print_dimensions')], 'show_in_rest' => true, 'meta_box_cb' => 'post_categories_meta_box' ] ); } $product_attributes = $product->get_attributes(); $updated_attributes = false; $added_variation_ids = []; foreach ($variations as $variation_data) { $sku = sanitize_title($variation_data['sku']); if (!term_exists($sku, $taxonomy)) { $term = wp_insert_term($sku, $taxonomy); if (is_wp_error($term)) { drop_print_log("Error creating term $sku: " . $term->get_error_message()); continue; // Skip this variation } } $term_slug = get_term_by('name', $sku, $taxonomy)->slug; if (!isset($product_attributes[$taxonomy])) { $product_attributes[$taxonomy] = new WC_Product_Attribute(); $product_attributes[$taxonomy]->set_name($taxonomy); $product_attributes[$taxonomy]->set_options([$term_slug]); $product_attributes[$taxonomy]->set_visible(1); $product_attributes[$taxonomy]->set_variation(1); $updated_attributes = true; } elseif (!in_array($term_slug, $product_attributes[$taxonomy]->get_options())) { $options = $product_attributes[$taxonomy]->get_options(); $options[] = $term_slug; $product_attributes[$taxonomy]->set_options($options); $updated_attributes = true; } $variation_id = wp_insert_post([ 'post_title' => $product->get_name() . ' - ' . $sku, 'post_name' => 'product-' . $product_id . '-variation-' . $sku, 'post_status' => 'publish', 'post_parent' => $product_id, 'post_type' => 'product_variation', 'guid' => $product->get_permalink(), ]); if ($variation_id) { $added_variation_ids[] = $variation_id; $variation = new WC_Product_Variation($variation_id); update_post_meta($variation_id, 'attribute_' . $taxonomy, $term_slug); $variation->set_regular_price($variation_data['regular_price']); $variation->set_price($variation_data['regular_price']); $variation->save(); } else { drop_print_log("Error creating variation for $sku."); } } if ($updated_attributes) { $product->set_attributes($product_attributes); $product->save(); } wc_delete_product_transients($product_id); wc_delete_shop_order_transients(); wp_cache_flush(); wp_send_json_success([ 'message' => 'Variations created successfully.', 'added_variations' => $added_variation_ids, 'attribute_taxonomy' => $taxonomy, 'product_attributes' => $product->get_attributes() ]); } add_action('wp_ajax_create_drop_print_variations', 'create_drop_print_variations'); function minimal_attribute_button_ajax() { $screen = get_current_screen(); if ($screen && $screen->id === 'product') { $nonce = wp_create_nonce('drop_print_variation_nonce'); ?> <script type="text/javascript"> document.addEventListener('DOMContentLoaded', function() { let button = document.createElement('button'); button.textContent = 'Make Attributes AJAX'; button.addEventListener('click', function(event) { event.preventDefault(); let selectedProducts = []; let productId = document.querySelector('#post_ID').value; selectedProducts.push({ title: 'title_goes_here', regular_price: 9.99, sku: 'sku_goes_here', }); let data = { 'action': 'create_drop_print_variations', 'nonce': '<?php echo $nonce; ?>', 'product_id': productId, 'variations': JSON.stringify(selectedProducts) }; fetch(ajaxurl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams(data) }) .then(response => response.json()) .then(data => { console.log('AJAX Response:', data); if (data.success) { // 刷新属性面板 if (window.wc_product_attributes) { window.wc_product_attributes.fetch_attributes(); window.wc_product_attributes.render(); } // 刷新变体面板 if (window.wc_variations_admin) { window.wc_variations_admin.variations.reset(); window.wc_variations_admin.variations.fetch({ success: function() { window.wc_variations_admin.render(); jQuery(document).trigger('woocommerce_variations_loaded'); } }); } // 更新产品属性隐藏字段,避免丢失数据 jQuery.post(ajaxurl, { action: 'woocommerce_get_product_data', post_id: productId, security: wc_admin_meta_boxes_variations_nonce }, function(response) { if (response.success) { jQuery('#_product_attributes').val(JSON.stringify(response.data.product_attributes)); } }); alert('Variations created successfully!'); } else { alert('Error: ' + data.data.message); } }) .catch(error => { console.error('AJAX Error:', error); alert('An error occurred during AJAX request.'); }); }); let productTitle = document.querySelector('#title'); if (productTitle && productTitle.parentNode) { productTitle.parentNode.appendChild(button); } }); </script> <?php } } add_action('admin_footer', 'minimal_attribute_button_ajax'); ?>
内容的提问来源于stack exchange,提问作者Callum
相关产品推荐
相关产品推荐

