在 WooCommerce 商品详情页中,系统默认的交叉销售(cross-sells)依赖于后台手动设置,难以根据商品分类自动推荐互补品。更实用的做法是:获取当前产品所属分类,向上向下递归整棵分类树,然后用 WP_Query 查询不属于该分类树的其他分类产品,实现自动化关联展示。

为什么需要排除当前分类树
很多站点只是排除了产品的直接父分类,但 WooCommerce 商品分类往往是多层结构。例如「服装」下面有「男装」,男装下还有「T恤」。如果当前商品属于「T恤」,仅排除「T恤」或「男装」是不够的,应当把「服装」这一整条祖先链以及其所有分支都视为同一分类树。
若不排除完整分类树,交叉销售区就可能再次出现同系列商品,失去跨品类引流意义。从用户体验看,在买 T 恤时推荐裤子或鞋子,比推荐另一个 T 恤更容易激发加购。因此,代码核心在于「获取分类树全部 ID」。
获取当前产品的完整分类树 ID
我们可以通过 wp_get_post_terms() 拿到直接分类,再用 get_ancestors() 获取祖先,并递归收集子分类。下面函数返回当前商品所属所有相关分类 ID 的数组。
function get_product_category_tree_ids( $product_id ) {
$terms = wp_get_post_terms( $product_id, 'product_cat', array( 'fields' => 'ids' ) );
if ( is_wp_error( $terms ) || empty( $terms ) ) {
return array();
}
$tree_ids = array();
foreach ( $terms as $term_id ) {
// 当前分类
$tree_ids[] = $term_id;
// 祖先分类
$ancestors = get_ancestors( $term_id, 'product_cat' );
if ( ! empty( $ancestors ) ) {
$tree_ids = array_merge( $tree_ids, $ancestors );
}
// 所有子分类
$children = get_term_children( $term_id, 'product_cat' );
if ( ! empty( $children ) && ! is_wp_error( $children ) ) {
$tree_ids = array_merge( $tree_ids, $children );
}
}
return array_unique( array_map( 'intval', $tree_ids ) );
}
该函数先把直接分类放入数组,再利用 get_ancestors() 把父级、祖父级等全部拉入,最后用 get_term_children() 把下属子分类也合并。使用 array_unique() 防止重复 ID 导致查询异常。
要注意 get_term_children() 返回的是子孙分类的扁平 ID 列表,不会包含自身,所以前面必须手动加入当前 $term_id。这样我们就拥有了一棵完整的分类树。
基于分类树查询交叉销售产品
拿到排除用的分类 ID 后,使用 WP_Query 查询不在该数组中的其他分类商品。下面示例在商品页展示 4 个其他分类产品。
function get_cross_sell_by_category( $product_id, $limit = 4 ) {
$exclude_tree = get_product_category_tree_ids( $product_id );
$args = array(
'post_type' => 'product',
'posts_per_page' => $limit,
'post_status' => 'publish',
'post__not_in' => array( $product_id ),
'tax_query' => array(
array(
'taxonomy' => 'product_cat',
'field' => 'term_id',
'terms' => $exclude_tree,
'operator' => 'NOT IN',
),
),
'orderby' => 'rand',
);
$query = new WP_Query( $args );
return $query->posts;
}
上面的 tax_query 使用 NOT IN 操作符,直接过滤掉整棵分类树。配合 post__not_in 排除自身,避免自己推荐自己。排序用随机,可让不同访客看到不同互补品。
这种写法的优点是逻辑清晰、不依赖手动设置;缺点是随机查询在大数据量时略有性能开销,可通过增加 meta_query 限定有库存商品来优化。
在模板中替换默认交叉销售
WooCommerce 提供了 woocommerce_after_single_product_summary 钩子。我们可以移除默认 cross-sells,再挂载自己的函数。
remove_action( 'woocommerce_after_single_product_summary', 'woocommerce_cross_sell_display', 20 );
add_action( 'woocommerce_after_single_product_summary', 'custom_cross_sell_display', 20 );
function custom_cross_sell_display() {
global $product;
if ( ! $product ) {
return;
}
$cross_sells = get_cross_sell_by_category( $product->get_id(), 4 );
if ( empty( $cross_sells ) ) {
return;
}
echo '<section class="cross-sells">';
echo '<h2>相关分类推荐</h2>';
echo '<ul class="products">';
foreach ( $cross_sells as $p ) {
$wc_p = wc_get_product( $p->ID );
echo '<li>' . $wc_p->get_title() . '</li>';
}
echo '</ul>';
echo '</section>';
}
将以上代码放入主题的 functions.php 或自定义插件中即可生效。实际项目中可进一步调用 wc_get_template_part() 复用 WooCommerce 自带卡片样式,保持视觉统一。
如果需要更精细控制,比如只排除祖先而不排除兄弟分类,可调整 get_product_category_tree_ids() 中的子分类收集逻辑。整体方案灵活且易于维护。
常见误区与注意事项
有人会用 wp_get_post_categories() 代替 wp_get_post_terms(),但该产品分类法为 product_cat,并非默认 category,必须用正确的 taxonomy 参数。
另外,缓存查询结果能显著提升性能。可使用 wp_cache_set() 按产品 ID 缓存交叉销售列表,过期时间设为几小时,避免每次页面加载都跑随机查询。
WooCommerce交叉销售分类树排除修改时间:2026-08-07 09:27:15