You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何无需整页刷新动态更新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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 11:29:52