跳到主要内容

2 篇博文 含有标签「Block Theme」

查看所有标签

修复 WooCommerce FSE Cart Block 空车白屏与商品无图塌陷

· 阅读需 4 分钟

在为客户开发 WooCommerce FSE Block Theme 时遇到这两个问题:Cart Block 空车时页面白屏、商品无特色图片时卡片高度塌陷。记录根因与解法。

TL;DR

  1. Cart Block 必须显式声明 filled-cart-blockempty-cart-block 子块,否则空车时无任何内容输出。
  2. 商品无特色图片时,FSE 的 post-featured-image 块渲染为空字符串,导致卡片高度塌陷。通过 post_thumbnail_html filter 补上 WooCommerce 占位图。

问题一:Cart Block 空车白屏

问题现象

购物车页面在有商品时正常显示,清空购物车后整个页面内容区变成空白 -- 没有提示文案,没有"继续购物"按钮,用户无法自助返回。

根因

WooCommerce Cart Block (wp:woocommerce/cart) 的设计要求开发者显式声明两个子块:

  • wp:woocommerce/filled-cart-block -- 有商品时显示
  • wp:woocommerce/empty-cart-block -- 空车时显示

如果只写了 Cart Block 本体而没有嵌套这两个子块,WooCommerce 在空车状态下不知道该渲染什么,输出为空。

这个问题在经典主题中不存在,因为经典主题使用 PHP 模板 cart.php,其中已经内置了空车处理逻辑。但 FSE 的 HTML 模板是声明式的,必须完整声明所有状态。

解决方案

cart.html 模板的正确结构:

<!-- wp:woocommerce/cart {"className":"cclee-cart"} -->
<div class="wp-block-woocommerce-cart alignwide is-loading">

<!-- wp:woocommerce/filled-cart-block -->
<div class="wp-block-woocommerce-filled-cart-block">
<!-- 有商品时的完整布局:商品列表 + 汇总 -->
<!-- wp:columns -->
<div class="wp-block-columns">
<!-- wp:column {"width":"65%"} -->
<div class="wp-block-column" style="flex-basis:65%">
<!-- wp:woocommerce/cart-items-block -->
<div class="wp-block-woocommerce-cart-items-block"></div>
<!-- /wp:woocommerce/cart-items-block -->
</div>
<!-- /wp:column -->
<!-- wp:column {"width":"35%"} -->
<div class="wp-block-column" style="flex-basis:35%">
<!-- wp:woocommerce/cart-totals-block -->
<div class="wp-block-woocommerce-cart-totals-block">
<!-- wp:woocommerce/cart-order-summary-block /-->
<!-- wp:woocommerce/proceed-to-checkout-block /-->
</div>
<!-- /wp:woocommerce/cart-totals-block -->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->
</div>
<!-- /wp:woocommerce/filled-cart-block -->

<!-- wp:woocommerce/empty-cart-block -->
<div class="wp-block-woocommerce-empty-cart-block">
<!-- 空车提示 + 继续购物按钮 -->
<!-- wp:paragraph {"align":"center","textColor":"neutral-500"} -->
<p class="has-text-align-center has-neutral-500-color has-text-color">Your cart is currently empty.</p>
<!-- /wp:paragraph -->
<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"center"}} -->
<div class="wp-block-buttons">
<!-- wp:button {"backgroundColor":"accent","textColor":"base"} -->
<div class="wp-block-button"><a href="/shop/" class="wp-block-button__link has-base-color has-accent-background-color has-text-color has-background wp-element-button">Browse Products</a></div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
</div>
<!-- /wp:woocommerce/empty-cart-block -->

</div>
<!-- /wp:woocommerce/cart -->

关键点filled-cart-blockempty-cart-block 必须是 wp:woocommerce/cart 的直接子块,WooCommerce 通过这两个子块实现有货/空车的条件渲染。

问题二:商品无特色图片时卡片塌陷

问题现象

在商品列表页(archive-product)中,没有设置特色图片的商品卡片只显示文字部分,没有图片区域。与有图卡片混排时,布局高度不统一,视觉上非常突兀。

根因

FSE 的 wp:post-featured-image 块在文章/商品没有缩略图时直接渲染为空字符串,不会输出占位图。经典主题中通常在 PHP 模板里手动处理这种情况(has_post_thumbnail() 判断),但 FSE HTML 模板无法嵌入这种条件逻辑。

解决方案

在主题的 functions.php 或 WooCommerce 集成文件中添加 filter:

/**
* Product placeholder image when no featured image is set.
*
* FSE post-featured-image block renders empty when no thumbnail,
* causing card height collapse. This filter injects the WooCommerce
* placeholder image for product post type.
*
* @param string $html The post thumbnail HTML.
* @param int $post_id The post ID.
* @return string
*/
add_filter( 'post_thumbnail_html', function ( $html, $post_id ) {
// Already has image or not a product -- skip.
if ( $html || get_post_type( $post_id ) !== 'product' ) {
return $html;
}

// Use WooCommerce's built-in placeholder.
$src = function_exists( 'wc_placeholder_img_src' )
? wc_placeholder_img_src()
: '';

if ( ! $src ) {
return '';
}

return sprintf(
'<img src="%s" alt="%s" loading="lazy" decoding="async" style="width:100%%;height:100%%;object-fit:cover;">',
esc_url( $src ),
esc_attr( get_the_title( $post_id ) )
);
}, 10, 2 );

注意

  • wc_placeholder_img_src() 依赖 WooCommerce 插件激活,用 function_exists() 做防护
  • object-fit: cover 确保占位图和正常特色图片的裁剪行为一致
  • 只针对 product 类型生效,不影响博客文章等其他 post type

对类似需求感兴趣?联系合作

WordPress FSE Block Validation Failed: JSON 引号缺失的隐蔽根因

· 阅读需 4 分钟

TL;DR

WordPress FSE 主题的 Pattern/Template 文件中,HTML 注释里的 JSON 属性如果某个字符串值缺少闭合引号 ",花括号数量仍然平衡,但 parse_blocks() 会静默将该块的 attrs 置为 null。Gutenberg 的 save 函数因此不输出 inline style,触发 Block validation failed。用 json.loads() 验证 JSON 合法性即可定位。

问题现象

WordPress Site Editor 打开 wishlist 模板时控制台报错:

Block validation: Block validation failed for `core/group`

Content generated by `save` function:
<div class="wp-block-group has-border-color has-neutral-200-border-color"></div>

Content retrieved from post body:
<div class="wp-block-group has-border-color has-neutral-200-border-color"
style="border-style:solid;border-width:1px;border-radius:var(--wp--custom--border--radius--lg);
padding-top:var(--wp--preset--spacing--40);...">

save 函数输出了正确的 CSS class,但完全丢失了 inline style,且内容为空。而文件中 HTML 明明包含 style 属性和子块。

根因

问题出在模板文件中 core/group 块的 HTML 注释 JSON 属性:

<!-- 原始(有误) -->
<!-- wp:group {"style":{"spacing":{"padding":{"top":"var(--wp--preset--spacing--40)",
"right":"var(--wp--preset--spacing--40)",
"bottom":"var(--wp--preset--spacing--40)",
"left":"var(--wp--preset--spacing--40)"}}, <!-- 此处缺少闭合引号 -->
"border":{"radius":"var(--wp--custom--border--radius--lg)","width":"1px","style":"solid"}},
"borderColor":"neutral-200","layout":{"type":"constrained"}} -->

注意 "left" 的值 "var(--wp--preset--spacing--40)" 缺少闭合引号 ",实际写成了 "var(--wp--preset--spacing--40)

为什么花括号检查发现不了:

正确:{ "left": "value" }  → 引号成对,花括号平衡
错误:{ "left": "value } → 引号不成对,但花括号仍然平衡

} 在 JSON 解析器眼中是字符串内容的一部分(因为引号没闭合),所以花括号计数不变。parse_blocks() 解析失败后不会报错,而是将该块的 attrs 直接置为 null

// parse_blocks 返回结果
[
'blockName' => 'core/group',
'attrs' => null, // 整个属性对象被丢弃
'innerHTML' => '<div ...>', // 原始 HTML 仍在
]

Gutenberg 拿到 null attrs 调用 save() 函数,自然不会输出任何 inline style,与文件中的 HTML 不匹配,触发 Block validation failed。

这个问题的隐蔽性在于:

  • 不会白屏,页面仍能渲染(使用 innerHTML 作为 fallback)
  • 花括号数量平衡,肉眼审查容易遗漏
  • Site Editor 中表现为"块需要恢复"的提示,容易被忽略
  • 审计脚本通常只检查花括号平衡和属性对应,不验证 JSON 合法性

解决方案

1. 定位问题

用 Python 验证 JSON 合法性:

python3 -c "
import json
with open('templates/wishlist.html') as f:
content = f.read()
# 提取 JSON 注释
marker = 'wp:group {'
start = content.index(marker) + len(marker) - 1
end = content.index(' -->', start)
json_str = content[start:end]
try:
json.loads(json_str)
print('JSON OK')
except json.JSONDecodeError as e:
print(f'Error at position {e.pos}: {e.msg}')
print(f'Context: ...{json_str[max(0,e.pos-20):e.pos+20]}...')
"

输出会精确定位错误:

Error at position 196: Expecting ',' delimiter
Context: ...g--40)}},"border":{"...

2. 修复 JSON

"left" 的值后面补上缺失的闭合引号:

<!-- 修复后 -->
<!-- wp:group {"style":{"spacing":{"padding":{"top":"var(--wp--preset--spacing--40)",
"right":"var(--wp--preset--spacing--40)",
"bottom":"var(--wp--preset--spacing--40)",
"left":"var(--wp--preset--spacing--40)"}}, <!-- 闭合引号已补上 -->
"border":{"radius":"var(--wp--custom--border--radius--lg)","width":"1px","style":"solid"}},
"borderColor":"neutral-200","layout":{"type":"constrained"}} -->

3. 验证修复

# 用 WP-CLI 验证 parse_blocks 正确解析
docker exec wp_cli wp eval '
$blocks = parse_blocks(file_get_contents(get_stylesheet_directory() . "/templates/wishlist.html"));
// 导航到目标块,检查 attrs 非 null
echo $blocks[...]["attrs"]["style"]["border"]["radius"];
' --allow-root

4. 预防措施

在 CI 中加入 JSON 注释合法性检查:

import json, re, sys

def check_block_json(filepath):
with open(filepath) as f:
content = f.read()
# 匹配所有 <!-- wp:xxx {...} --> 注释中的 JSON
for m in re.finditer(r'<!-- wp:\w+ (\{.*?\}) -->', content):
try:
json.loads(m.group(1))
except json.JSONDecodeError as e:
print(f"{filepath}: JSON error at comment position {m.start()}: {e}")
sys.exit(1)

check_block_json(sys.argv[1])

对类似需求感兴趣?联系合作