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

如何在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监听变体切换事件,动态标记缺货选项:

  1. 后端输出变体库存数据:
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);
  1. 前端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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 06:13:12