如何在foreach循环中获取WooCommerce当前变体ID并添加缺货类
问题描述
我基于自定义代码实现了WooCommerce产品变体的单选按钮(未使用变体色板插件),现在需要给缺货的变体选项添加自定义CSS类out-of-stock(而非禁用单选按钮),仅通过CSS实现模糊效果。
当前已移除所有选择框的disabled属性,让所有单选按钮可点击,仅修改文本提示。但尝试通过get_matching_variation_id()获取变体ID时失败,返回0,导致整个表单消失。手动传入有效变体ID时功能正常。
当前使用的PHP代码如下:
function variation_radio_buttons($html, $args) { global $product; $prod_id = $product->get_id(); $args = wp_parse_args(apply_filters('woocommerce_dropdown_variation_attribute_options_args', $args), array( 'options' => false, 'attribute' => false, 'product' => false, 'selected' => false, 'name' => '', 'id' => '', 'class' => '', 'show_option_none' => __('Choose an option', 'woocommerce'), )); if(false === $args['selected'] && $args['attribute'] && $args['product'] instanceof WC_Product) { $selected_key = 'attribute_'.sanitize_title($args['attribute']); $args['selected'] = isset($_REQUEST[$selected_key]) ? wc_clean(wp_unslash($_REQUEST[$selected_key])) : $args['product']->get_variation_default_attribute($args['attribute']); } $options = $args['options']; $product = $args['product']; $attribute = $args['attribute']; $name = $args['name'] ? $args['name'] : 'attribute_'.sanitize_title($attribute); $id = $args['id'] ? $args['id'] : sanitize_title($attribute); $class = $args['class']; $show_option_none = (bool)$args['show_option_none']; $show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __('Choose an option', 'woocommerce'); if(empty($options) && !empty($product) && !empty($attribute)) { $attributes = $product->get_variation_attributes(); $options = $attributes[$attribute]; } $radios = '<div class="variation-radios">'; if(!empty($options)) { if($product && taxonomy_exists($attribute)) { $terms = wc_get_product_terms($product->get_id(), $attribute, array( 'fields' => 'all', )); foreach($terms as $term) { if(in_array($term->slug, $options, true)) { $id = $name.'-'.$term->slug; $radios .= '<div class="attr-container"><div class="radio-span"><label for="'.esc_attr($id). $prod_id.'"><input type="radio" id="'.esc_attr($id). $prod_id .'" name="'.esc_attr($name) .'" value="'.esc_attr($term->slug).'" '.checked(sanitize_title($args['selected']), $term->slug, false).'>'. '<div class="radio-span-label">' . esc_html(apply_filters('woocommerce_variation_option_name', $term->name)). '</div></label></div></div>'; } } } else { foreach($options as $option) { $id = $name.'-'.$option; $checked = sanitize_title($args['selected']) === $args['selected'] ? checked($args['selected'], sanitize_title($option), false) : checked($args['selected'], $option, false); $radios .= '<div class=""><input type="radio" id="'.esc_attr($id). $prod_id .'" name="'.esc_attr($name). '" value="'.esc_attr($option).'" id="'.sanitize_title($option).'" '.$checked.'><label for="'.esc_attr($id). $prod_id.'">'.esc_html(apply_filters('woocommerce_variation_option_name', $option)).'</label></div>'; } } } $radios .= '</div>'; return $html.$radios; } add_filter('woocommerce_dropdown_variation_attribute_options_html', 'variation_radio_buttons', 20, 2);
尝试的代码片段(导致表单消失):
$variation_id = $product->get_matching_variation_id(array($attribute => $term->slug)); $variation = wc_get_product($variation_id); $in_stock_class = $variation && $variation->is_in_stock() ? '' : 'out-of-stock';
解决方案
问题根源
get_matching_variation_id()需要完整的变体属性组合(比如同时指定颜色和尺寸)才能返回有效的变体ID,而当前循环仅处理单个属性项(比如颜色中的“红色”),无法匹配到具体变体,因此返回0,后续调用wc_get_product(0)会出错,导致表单渲染失败。
方法一:后端预加载变体数据,匹配属性项标记库存
在循环前先获取产品所有变体,整理每个属性项对应的库存状态:如果该属性项对应的任意一个变体有库存,则标记为有库存;如果所有包含该属性的变体都缺货,则标记为缺货。
修改后的完整代码:
function variation_radio_buttons($html, $args) { global $product; $prod_id = $product->get_id(); $args = wp_parse_args(apply_filters('woocommerce_dropdown_variation_attribute_options_args', $args), array( 'options' => false, 'attribute' => false, 'product' => false, 'selected' => false, 'name' => '', 'id' => '', 'class' => '', 'show_option_none' => __('Choose an option', 'woocommerce'), )); if(false === $args['selected'] && $args['attribute'] && $args['product'] instanceof WC_Product) { $selected_key = 'attribute_'.sanitize_title($args['attribute']); $args['selected'] = isset($_REQUEST[$selected_key]) ? wc_clean(wp_unslash($_REQUEST[$selected_key])) : $args['product']->get_variation_default_attribute($args['attribute']); } $options = $args['options']; $product = $args['product']; $attribute = $args['attribute']; $name = $args['name'] ? $args['name'] : 'attribute_'.sanitize_title($attribute); $id = $args['id'] ? $args['id'] : sanitize_title($attribute); $class = $args['class']; $show_option_none = (bool)$args['show_option_none']; $show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __('Choose an option', 'woocommerce'); if(empty($options) && !empty($product) && !empty($attribute)) { $attributes = $product->get_variation_attributes(); $options = $attributes[$attribute]; } // 预加载所有变体,整理属性与库存的映射 $attribute_stock_map = array(); foreach ($product->get_available_variations() as $variation) { $variation_product = wc_get_product($variation['variation_id']); if (!$variation_product) continue; foreach ($variation['attributes'] as $attr_key => $attr_value) { $attr_name = str_replace('attribute_', '', $attr_key); // 初始化映射,如果该属性值未记录则设为false(缺货) if (!isset($attribute_stock_map[$attr_name][$attr_value])) { $attribute_stock_map[$attr_name][$attr_value] = false; } // 只要有一个变体有库存,就标记该属性值为有库存 if ($variation_product->is_in_stock()) { $attribute_stock_map[$attr_name][$attr_value] = true; } } } $radios = '<div class="variation-radios">'; if(!empty($options)) { if($product && taxonomy_exists($attribute)) { $terms = wc_get_product_terms($product->get_id(), $attribute, array( 'fields' => 'all', )); foreach($terms as $term) { if(in_array($term->slug, $options, true)) { $id = $name.'-'.$term->slug; // 获取当前属性项的库存状态 $has_stock = isset($attribute_stock_map[$attribute][$term->slug]) ? $attribute_stock_map[$attribute][$term->slug] : false; $in_stock_class = $has_stock ? '' : 'out-of-stock'; $radios .= '<div class="attr-container '.$in_stock_class.'"><div class="radio-span"><label for="'.esc_attr($id). $prod_id.'"><input type="radio" id="'.esc_attr($id). $prod_id .'" name="'.esc_attr($name) .'" value="'.esc_attr($term->slug).'" '.checked(sanitize_title($args['selected']), $term->slug, false).'>'. '<div class="radio-span-label">' . esc_html(apply_filters('woocommerce_variation_option_name', $term->name)). '</div></label></div></div>'; } } } else { foreach($options as $option) { $id = $name.'-'.$option; $checked = sanitize_title($args['selected']) === $args['selected'] ? checked($args['selected'], sanitize_title($option), false) : checked($args['selected'], $option, false); // 获取非分类属性项的库存状态 $has_stock = isset($attribute_stock_map[$attribute][$option]) ? $attribute_stock_map[$attribute][$option] : false; $in_stock_class = $has_stock ? '' : 'out-of-stock'; $radios .= '<div class="'.$in_stock_class.'"><input type="radio" id="'.esc_attr($id). $prod_id .'" name="'.esc_attr($name). '" value="'.esc_attr($option).'" id="'.sanitize_title($option).'" '.$checked.'><label for="'.esc_attr($id). $prod_id.'">'.esc_html(apply_filters('woocommerce_variation_option_name', $option)).'</label></div>'; } } } $radios .= '</div>'; return $html.$radios; } add_filter('woocommerce_dropdown_variation_attribute_options_html', 'variation_radio_buttons', 20, 2);
方法二:前端动态添加类(结合WooCommerce变体事件)
如果后端逻辑复杂,可通过前端JS监听变体切换事件,动态标记缺货选项:
- 后端输出变体库存数据:
function add_variation_stock_data() { global $product; if (!$product || !$product->is_type('variable')) return; $stock_data = array(); foreach ($product->get_available_variations() as $variation) { $variation_product = wc_get_product($variation['variation_id']); $stock_data[$variation['variation_id']] = array( 'attributes' => $variation['attributes'], 'in_stock' => $variation_product->is_in_stock() ); } echo '<script>window.variationStockData = '.json_encode($stock_data).'</script>'; } add_action('woocommerce_single_product_summary', 'add_variation_stock_data', 25);
- 前端JS代码:
jQuery(document).ready(function($) { // 更新缺货类的核心函数 function updateOutOfStockClasses() { $('.variation-radios .attr-container, .variation-radios > div').removeClass('out-of-stock'); // 获取当前选中的所有属性 var selectedAttrs = {}; $('input[type="radio"][name^="attribute_"]:checked').each(function() { var attrName = $(this).attr('name').replace('attribute_', ''); selectedAttrs[attrName] = $(this).val(); }); // 无选中属性时,检查单个属性项的库存 if (Object.keys(selectedAttrs).length === 0) { $('.variation-radios input[type="radio"]').each(function() { var attrName = $(this).attr('name').replace('attribute_', ''); var attrVal = $(this).val(); var hasStock = false; $.each(window.variationStockData, function(id, data) { if (data.attributes['attribute_'+attrName] === attrVal && data.in_stock) { hasStock = true; return false; } }); if (!hasStock) { $(this).closest('.attr-container, div').addClass('out-of-stock'); } }); } else { // 有选中属性时,检查组合后的变体库存 var allOutOfStock = true; $.each(window.variationStockData, function(id, data) { var match = true; $.each(selectedAttrs, function(attr, val) { if (data.attributes['attribute_'+attr] !== val) { match = false; return false; } }); if (match && data.in_stock) { allOutOfStock = false; return false; } }); if (allOutOfStock) { $('input[type="radio"][name^="attribute_"]:checked').closest('.attr-container, div').addClass('out-of-stock'); } } } // 监听单选按钮变化 $(document).on('change', '.variation-radios input[type="radio"]', updateOutOfStockClasses); // 页面加载时初始化 updateOutOfStockClasses(); });
CSS样式示例
.out-of-stock { opacity: 0.5; filter: blur(1px); } .out-of-stock .radio-span-label { text-decoration: line-through; }
内容的提问来源于stack exchange,提问作者libertarian
相关产品推荐
相关产品推荐

