首页 / 中文教程 / 教程

WooCommerce 产品循环模板覆盖:用 Bricks 模板替换默认产品循环

Bricks 里的查询循环(query loop)确实强大又方便,但你也肯定不想把自定义的 WooCommerce 产品循环复制粘贴到每个页面里吧?有时候你用 Bricks 把 WooCommerce 设计得挺漂亮,可产…

Ray Chan·2026-08-12·约 5 分钟

Bricks 里的查询循环(query loop)确实强大又方便,但你也肯定不想把自定义的 WooCommerce 产品循环复制粘贴到每个页面里吧?有时候你用 Bricks 把 WooCommerce 设计得挺漂亮,可产品元素(Products element)还是套用 WooCommerce 默认样式,又得花时间去重新设计。

为什么不干脆做一个标准的产品循环模板,让它覆盖 WooCommerce 默认的产品循环呢?这篇教程就带你这么做。和往常一样,我不只给你最终能跑的代码,还会解释过程中可能遇到的各种问题——先泡杯咖啡,希望你喜欢。

⚠️ 注意:一旦你决定覆盖 WooCommerce 产品循环,部分 WooCommerce 钩子(hooks)就不能再用了。不过没关系——你只需要用 Bricks 模板来控制一切。

别吐槽我简陋的模板设计 :)

教程环境

  • Bricks theme v1.5 beta(bricksbuilder.io
  • WordPress 6.0.1
  • PHP 7.4
  • Open LiteSpeed Server
  • 教程里的所有自定义代码都放在子主题functions.php 里,另外还有子主题的 woocommerce/content-product.php(后面会讲到)

第 1 步:创建自定义产品循环模板

这一步很简单——新建一个 section 类型的模板,按你的喜好设计。当然数据必须是动态的。比如你想显示产品价格,就用一个基础文本元素把内容设为 {woo_product_price};想显示图片就用图片元素、数据源选特色图片,等等(在小闪电图标里找合适的动态数据)。

我还把 div 设成了可点击链接,这样访客点击整个区块就能进入对应的产品详情页。

在构建器(builder)模式下内容没有填充也别担心,这是正常的——因为 Bricks 暂时还不知道怎么填充这些动态数据。

第 2 步:在子主题创建新的 PHP 文件

要替换 WooCommerce 默认的产品循环,直接用 WooCommerce 的模板覆盖机制就行(参考文档):

  • 在子主题里新建一个 woocommerce 文件夹(你应该用子主题,可以从 Bricks Account 页面下载)
  • 在 woocommerce 文件夹里新建文件 content-product.php

把下面的代码复制进新建的 content-product.php,记得保存:

<?php

defined( 'ABSPATH' ) || exit;

global $product;

// Ensure visibility.
if ( empty( $product ) || ! $product->is_visible() ) {
	return;
}

// Replace 856 with your product loop template ID.
$template_id = 856;

// Just to check if the template exists
$template_data = \Bricks\Database::get_data( $template_id, 'content' );
$template_post_status = get_post_status( $template_id );

// Only use our template if the template is published and exists.
if ( $template_data && $template_post_status === 'publish' ) :

	$ori['post_id'] = \Bricks\Database::$page_data['post_id'];
	$ori['original_post_id']= \Bricks\Database::$page_data['original_post_id'];
	$ori['preview_or_post_id'] = \Bricks\Database::$page_data['preview_or_post_id'];
	
	do_action( 'itchy/template_output/before', $template_id, $product->ID, $ori );

	// This is the product loop template.
	echo do_shortcode( '[bricks_template id="' . $template_id . '"]' );
	
	do_action( 'itchy/template_output/after', $template_id, $product->ID, $ori );

else:

// Below code is from WooCommerce default template

?>
<li <?php wc_product_class( '', $product ); ?>>
	<?php
	/**
	 * Hook: woocommerce_before_shop_loop_item.
	 *
	 * @hooked woocommerce_template_loop_product_link_open - 10
	 */
	do_action( 'woocommerce_before_shop_loop_item' );

	/**
	 * Hook: woocommerce_before_shop_loop_item_title.
	 *
	 * @hooked woocommerce_show_product_loop_sale_flash - 10
	 * @hooked woocommerce_template_loop_product_thumbnail - 10
	 */
	do_action( 'woocommerce_before_shop_loop_item_title' );

	/**
	 * Hook: woocommerce_shop_loop_item_title.
	 *
	 * @hooked woocommerce_template_loop_product_title - 10
	 */
	do_action( 'woocommerce_shop_loop_item_title' );

	/**
	 * Hook: woocommerce_after_shop_loop_item_title.
	 *
	 * @hooked woocommerce_template_loop_rating - 5
	 * @hooked woocommerce_template_loop_price - 10
	 */
	do_action( 'woocommerce_after_shop_loop_item_title' );

	/**
	 * Hook: woocommerce_after_shop_loop_item.
	 *
	 * @hooked woocommerce_template_loop_product_link_close - 5
	 * @hooked woocommerce_template_loop_add_to_cart - 10
	 */
	do_action( 'woocommerce_after_shop_loop_item' );
	?>
</li>

<?php
endif;
?>

记得把 $template_id 换成你自己的模板 ID。现在先忽略那些 $ori 数组和 do_action,后面会解释。上面代码里的「主角」是 do_shortcode( '[bricks_template id="' . $template_id . '"]' )——这行代码让 Bricks 通过短代码渲染我们的模板。

去 WooCommerce 商城页(Shop page),或者任何用到产品循环的地方(如相关产品 Related Products、追加销售 Upsell 等)看看效果。

但是等等——为什么显示成这样?!Jenn,你在逗我吗?

Bricks 模板确实生效了,但图片、产品标题和价格呢?

我们来修好它!

第 3 步:理解问题所在

到底发生了什么?动态数据无法正确填充,是因为模板里的 Bricks 元素在执行动态函数时用错了 post ID。证据就是:标题元素渲染出来的是「Shop」而不是每个产品的标题——它把商城页当成了当前文章。

为了验证这一点,可以用 bricks/element/settings 过滤器钩子来测试。把下面的代码复制到 functions.php,把 $element->id 换成你自己的元素 ID:

add_filter( 'bricks/element/settings', function( $settings, $element ) {
	
	// My heading element Id is cafvof, only target this element
	if ( $element->id !== 'cafvof' ) return $settings;
	
	// Let's check what $post_id is used for populating dynamic data here
	var_dump( $element->post_id );

	// Let's check what is the global $product ID too
	global $product;
	var_dump( $product->get_id() );
	
	return $settings;
	
}, 10, 2);

看到了吧——产品循环里每个元素的 $element->post_id 都是 171,也就是我的商城页的 post ID。现在明白为什么不出产品特色图、不出产品价格了吧?因为商城页压根没有特色图,也没有产品价格!

这是 bug 吗?不是,Bricks 的行为是正确的。想象一下:你正在一个 post_id = 100 的页面里设计和用 Bricks 元素,那么后台执行动态数据时,这个页面里的每个元素当然都应该用 100 作为 post_id。当然,在查询循环、AJAX 或 REST 调用等场景下,Bricks 会自动动态改变 $element->post_id

所以,我们需要写一段代码,让产品循环里的 $element->post_id 也动态改变。

第 4 步:动态改变元素的 Post ID

如果你读过我博客上的 Bricks Builder Useful Functions & Tips 一文,应该记得我提到过可以用 bricks/builder/data_post_id 来改变 Bricks 元素的 post ID,那篇文章里也介绍了其他改 post ID 的方法。

\Bricks\Database:$page_data 里关键的数组键值对是 post_idoriginal_post_idpreview_or_post_id

我的计划是这样的:

  • 渲染产品循环模板前,把原始数据存进临时变量
  • \Bricks\Database:$page_data 里 post_id 相关的值改成 $product->get_id()
  • 渲染产品循环模板——现在它会用 WooCommerce 产品 ID 作为元素的 post ID
  • 单个循环渲染完后,把原始数据恢复到 \Bricks\Database:$page_data

你可能会问:为什么还要恢复原始数据?反正下一个产品循环也会覆盖它啊。我的回答很简单:每次离开前我都喜欢把自己的烂摊子收拾干净——而且我希望我的代码不会引发其他未知的副作用。

现在明白 content-product.php 里为什么有 $ori 数组和 do_action 了吧?

// Previous codes from child-theme/woocommerce/content-product.php
// Only use our template if the template is published and exists.
if ( $template_data && $template_post_status === 'publish' ) :

	// Save some original data into $ori array
	$ori['post_id'] = \Bricks\Database::$page_data['post_id'];
	$ori['original_post_id']= \Bricks\Database::$page_data['original_post_id'];
	$ori['preview_or_post_id'] = \Bricks\Database::$page_data['preview_or_post_id'];
	
	// Create a custom action hook and so I can use in functions.php
	// I may use this same action hook in other woocommerce template in future too!
	do_action( 'itchy/template_output/before', $template_id, $product->ID, $ori );

    // This is the product loop template.
	echo do_shortcode( '[bricks_template id="' . $template_id . '"]' );
	
	// Another custom action hook to some cleanup
	do_action( 'itchy/template_output/after', $template_id, $product->ID, $ori );

else:

对,加了些注释,希望你能看懂。

好了,现在在 functions.php 里挂上我们的自定义动作钩子:

// 2) Change the \Bricks\Database:$page_data post_id related values to $product->get_id()
add_action( 'itchy/template_output/before', 'itchy_change_data_post_id', 10, 3 );

function itchy_change_data_post_id( $template_id, $id, $ori ) {

	// Only target my product loop template, in future you might have more different templates to be hooked into
	if ( $template_id !== 856 ) return;

	// Use filter hook to change the $post_id used for populating dynamic data
	// I use anonymous function to do this as it is easier to pass the $id and $ori to the function
	// I use $id from content-product.php, which is the product ID
	// I use priority 34 to make sure it is unique, this is a little tricky, I will use 34 priority to remove my filter hook later
	add_filter( 'bricks/builder/data_post_id', function( $element_post_id ) use ( $id ) {
		return $id;
	}, 34 );

	// Different elements use different way to set the post_id
	// I feel more safe to manually set product ID here too, sorry if I am wrong, because there are many different logic in Bricks codebase
	// Without doing this, some dynamic data will not be populated correctly too
	\Bricks\Database::$page_data['original_post_id'] = $id;
	\Bricks\Database::$page_data['preview_or_post_id'] = $id;
	\Bricks\Database::$page_data['post_id'] = $id;

}

// 4) Once finished 1 single loop, restore the original data back to \Bricks\Database:$page_data
add_action( 'itchy/template_output/after', 'itchy_revert_data_post_id', 10, 3 );

function itchy_revert_data_post_id( $template_id, $id, $ori ) {

	// Only target my product loop template, in future you might have more different templates to be hooked into
	if ( $template_id !== 856 ) return;

	// I want to remove my anonymous filter function
	global $wp_filter;

	// callbacks[34] is the anonymous function I added above, use unset to remove the filter
	if ( isset( $wp_filter[ 'bricks/builder/data_post_id' ]->callbacks[34] ) ) {
		unset(  $wp_filter[ 'bricks/builder/data_post_id' ]->callbacks[34] );
	}

	// I feel more safe to manually set original values here
	\Bricks\Database::$page_data['original_post_id'] = $ori['original_post_id'];
	\Bricks\Database::$page_data['preview_or_post_id'] = $ori['preview_or_post_id'];
	\Bricks\Database::$page_data['post_id'] = $ori['post_id'];

}

请仔细读一遍我写的注释。

去前端看看吧。铛铛铛~!

不过,如果你再看 $element->post_id,会发现它显示的是空字符串。老实说,我也不明白为什么显示空字符串,希望有人能给我解释一下 :)

如何禁用我的产品循环模板?

把模板状态改成 draft(草稿)即可,这样就会重新用回 WooCommerce 默认的产品循环。或者直接把 content-product.php 删除或改名,比如改成 backup_content-product.php

为什么 Bricks Products 元素不用我的自定义产品循环模板?

如果你发现 products 元素没有应用自定义产品循环模板,请检查并确保 Bricks products 元素里的字段都是空的。这样它才会回退到 WooCommerce 模板,从而触发我们的模板覆盖机制。

结论

Bricks 是一个极其灵活强大的主题,只要你会一些 tweak 和自定义代码,绝大多数功能都能实现!感谢 Bricks 团队的努力,以后我也很想好好读读、理解他们的代码,写出更多教程。

⚠️ 生产环境务必删除第 3 步里的调试代码!

⚠️ 记得把产品循环模板做成响应式的——建议用 div,并设置 align-self: stretch。

需要帮忙?

用 Bricks 建站?我接客户项目。

从快速营销站到完整的 Bricks 建站,再到从 Elementor 迁移——我都做过。本站每篇教程都来自真实项目经验。告诉我你的需求,一个工作日内回复。

  • Bricks 建站与改版
  • Elementor / Divi → Bricks 迁移
  • Bricks → Astro / headless 性能升级
  • 速度优化,PageSpeed 95+ 目标