关于 b2c-page-designer
用于开发 Salesforce B2C Commerce Page Designer 页面类型和可复用组件类型的技能,介绍文件结构、元数据、属性、渲染及故障排查,并建议依据官方文档核实版本相关细节。
主要功能
- 01页面与组件类型结构
- 02JSON 元数据及脚本模式
- 03属性和区域配置
- 04页面与组件渲染指引
- 05编辑器不可见及架构问题排查
- 06核实版本相关文档
适用场景
- 01创建可复用的 Page Designer 组件
- 02定义页面区域和组件属性
- 03排查组件未显示在编辑器中的原因
- 04核对特定平台版本的 API 和架构细节
MCP 相关
该技能建议通过 MCP 使用 `docs_search` 和 `docs_read` 核实 Page Designer 的版本相关文档,但没有说明它提供 MCP 服务。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ salesforcecommercecloud/ b2c-developer-tooling --skill b2c-page-designer安装后在 Claude Code 中输入/b2c-page-designer确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/trigger-evals.jsonreferences/ATTRIBUTE-TYPES.mdMETA-DEFINITIONS.md详细介绍
页面设计器技能
本技能将指导你为 Salesforce B2C Commerce 创建自定义页面设计器页面类型和组件类型。
范围与依据
本技能涵盖 B2C Commerce 页面设计器(dw.experience 包),并提供与版本无关的模式、JSON 架构结构和代码示例。此处展示的 JSON 架构和 Script API 签名仅供参考,可能会随平台版本变化。在回答涉及特定框架或版本的问题前,或在生成用户将要运行的代码前,请通过 docs_search/docs_read(MCP)或 b2c docs search/b2c docs read(CLI)对照文档确认。对于此处未涵盖的 Script API 方法、属性架构要求和特定版本行为,应以 B2C Commerce 文档为权威依据。
权威文档:
dw.experience.PageMgr- 渲染页面和区域dw.experience.PageScriptContext- 页面脚本上下文 APIdw.experience.ComponentScriptContext- 组件脚本上下文 APIdw.experience.Page- 页面对象方法dw.experience.Component- 组件对象方法dw.experience.Region- 区域对象方法dw.experience.image.Image- 图像属性类型dw.experience- 包概述
概述
页面设计器允许商家通过可视化编辑器创建和管理内容页面。开发人员创建:
- 页面类型 - 定义包含区域的页面结构
- 组件类型 - 可配置属性的可复用内容块
文件结构
页面设计器文件位于 cartridge 的 experience 目录中:
/my-cartridge
/cartridge
/experience
/pages
homepage.json # Page type meta definition
homepage.js # Page type script
/components
banner.json # Component type meta definition
banner.js # Component type script
/templates
/default
/experience
/pages
homepage.isml # Page template
/components
banner.isml # Component template
命名: .json 和 .js 文件必须使用相同的名称。experience/pages 或 experience/components 下的文件名和子目录名称只能包含字母数字字符或下划线。
子文件夹中的组件类型: 你可以将组件元数据和脚本放在子目录中(例如 experience/components/assets/)。此时,组件类型 ID 为以点分隔的路径:assets.hero_image_block。templates/default/ 下的模板路径必须与该路径一致(例如 templates/default/experience/components/assets/hero_image_block.isml),脚本则必须调用 Template('experience/components/assets/hero_image_block'),以确保路径匹配。存储的 ID 为 component.{component_type_id};总长度不得超过 256 个字符。
页面类型
元数据定义(pages/homepage.json)
JSON 架构仅供参考;请根据官方平台文档确认特定版本的页面类型架构要求。
{
"name": "Home Page",
"description": "Landing page with hero and content regions",
"region_definitions": [
{
"id": "hero",
"name": "Hero Section",
"max_components": 1
},
{
"id": "content",
"name": "Main Content"
},
{
"id": "footer",
"name": "Footer Section",
"component_type_exclusions": [
{ "type_id": "video" }
]
}
]
}
页面脚本(pages/homepage.js)
'use strict';
var Template = require('dw/util/Template');
var HashMap = require('dw/util/HashMap');
var PageRenderHelper = require('*/cartridge/experience/utilities/PageRenderHelper.js');
module.exports.render = function (context) {
var model = new HashMap();
var page = context.page;
model.page = page;
model.regions = PageRenderHelper.getRegionModelRegistry(page);
return new Template('experience/pages/homepage').render(model).text;
};
页面模板(templates/default/experience/pages/homepage.isml)
此处遵循 SFRA 的区域模型模式:页面脚本通过
PageRenderHelper.getRegionModelRegistry(page)构建pdict.regions,每次调用RegionModel.render()时都会通过 SFRA 的渲染设置委托平台的PageMgr.renderRegion()。
<isdecorate template="common/layout/page">
<div class="homepage">
<div class="hero-region">
<isprint value="${pdict.regions.hero.render()}" encoding="off"/>
</div>
<div class="content-region">
<isprint value="${pdict.regions.content.render()}" encoding="off"/>
</div>
<div class="footer-region">
<isprint value="${pdict.regions.footer.render()}" encoding="off"/>
</div>
</div>
</isdecorate>
组件类型
元数据定义(components/banner.json)
JSON 架构仅供参考;请根据官方平台文档确认特定版本的组件类型架构要求。
{
"name": "Banner",
"description": "Promotional banner with image and CTA",
"group": "content",
"region_definitions": [],
"attribute_definition_groups": [
{
"id": "image",
"name": "Image Settings",
"attribute_definitions": [
{
"id": "image",
"name": "Banner Image",
"type": "image",
"required": true
},
{
"id": "alt",
"name": "Alt Text",
"type": "string",
"required": true
}
]
},
{
"id": "content",
"name": "Content",
"attribute_definitions": [
{
"id": "headline",
"name": "Headline",
"type": "string",
"required": true
},
{
"id": "body",
"name": "Body Text",
"type": "markup"
},
{
"id": "ctaUrl",
"name": "CTA Link",
"type": "url"
},
{
"id": "ctaText",
"name": "CTA Button Text",
"type": "string"
}
]
},
{
"id": "layout",
"name": "Layout Options",
"attribute_definitions": [
{
"id": "alignment",
"name": "Text Alignment",
"type": "enum",
"values": ["left", "center", "right"],
"default_value": "center"
},
{
"id": "fullWidth",
"name": "Full Width",
"type": "boolean",
"default_value": false
}
]
}
]
}
组件元数据: 始终包含 region_definitions。当组件没有嵌套区域(即没有用于放置其他组件的插槽)时,请使用 []。
组件脚本(components/banner.js)
'use strict';
var Template = require('dw/util/Template');
var HashMap = require('dw/util/HashMap');
var URLUtils = require('dw/web/URLUtils');
module.exports.render = function (context) {
var model = new HashMap();
var content = context.content;
// Access merchant-configured attributes
model.put('image', content.image); // Image object
model.put('alt', content.alt); // String
model.put('headline', content.headline); // String
model.put('body', content.body); // Markup string
model.put('ctaUrl', content.ctaUrl); // URL object
model.put('ctaText', content.ctaText); // String
model.put('alignment', content.alignment || 'center');
model.put('fullWidth', content.fullWidth);
return new Template('experience/components/banner').render(model).text;
};
模板路径: 传递给 Template(...) 的路径必须与 templates/default/ 下的模板路径一致。如果组件位于子文件夹中(例如 experience/components/assets/hero_image_block),请使用 Template('experience/components/assets/hero_image_block'),并将 ISML 放在 templates/default/experience/components/assets/hero_image_block.isml。
处理颜色(字符串或颜色选择器对象): 如果属性可以是十六进制字符串或颜色选择器对象 { color: "#hex" },请使用一个小型辅助函数,让脚本同时兼容这两种格式:
function getColor(colorAttr) {
if (!colorAttr) return '';
if (typeof colorAttr === 'string' && colorAttr.trim()) return colorAttr.trim();
if (colorAttr.color) return colorAttr.color;
return '';
}
组件模板(templates/experience/components/banner.isml)
<div class="banner ${pdict.fullWidth ? 'banner--full-width' : ''}"
style="text-align: ${pdict.alignment}">
<isif condition="${pdict.image}">
<img src="${pdict.image.file.absURL}"
alt="${pdict.alt}"
class="banner__image"/>
</isif>
<div class="banner__content">
<h2 class="banner__headline">${pdict.headline}</h2>
<isif condition="${pdict.body}">
<div class="banner__body">
<isprint value="${pdict.body}" encoding="off"/>
</div>
</isif>
<isif condition="${pdict.ctaUrl && pdict.ctaText}">
<a href="${pdict.ctaUrl}" class="banner__cta btn btn-primary">
${pdict.ctaText}
</a>
</isif>
</div>
</div>
属性类型
属性架构示例;具体版本的
attribute_definition语法请以官方平台文档为准。
| 类型 | 描述 | 返回值 |
|---|---|---|
string | 文本输入 | 字符串 |
text | 多行文本 | 字符串 |
markup | 富文本编辑器 | 标记语言字符串(使用 encoding="off") |
boolean | 复选框 | 布尔值 |
integer | 数字输入 | 整数 |
enum | 单选下拉列表 | 字符串或整数(取决于值) |
image | 图片选择器 | 包含 file.absURL 的图片对象 |
file | 文件选择器 | 文件对象 |
url | URL 选择器 | URL 字符串 |
category | 类别选择器 | 类别对象 |
product | 商品选择器 | 商品对象 |
page | 页面选择器 | 页面对象 |
custom | JSON 对象或自定义编辑器 | 对象(或取决于编辑器) |
Enum — 对组件可见性至关重要: values 必须使用字符串数组:"values": ["left", "center", "right"]。不要使用 { "value": "x", "display_value": "X" } 这类对象;这种格式可能导致组件类型被拒绝,且不会出现在 Page Designer 组件列表中。如果 values 是字符串,Enum 属性会返回字符串;如果是数字,则会返回整数。
自定义类型和颜色: type: "custom" 搭配例如 editor_definition.type: "styling.colorPicker",需要有一个能在 Business Manager 站点 cartridge 路径中提供该编辑器的 cartridge。如果组件未在编辑器中显示,请将颜色属性的类型改为 type: "string"(由商家输入十六进制值)。在脚本中同时兼容这两种形式:接受字符串或 { color: "#hex" } 这类对象(例如编写一个小型 getColor(attr) 辅助函数,返回字符串)。
default_value: 仅用于店面渲染;在 Page Designer 可视化编辑器中不会显示为预选值。
区域定义
{
"region_definitions": [
{
"id": "main",
"name": "Main Content",
"max_components": 10,
"component_type_exclusions": [
{ "type_id": "heavy-component" }
],
"component_type_inclusions": [
{ "type_id": "text-block" },
{ "type_id": "image-block" }
]
}
]
}
| 属性 | 描述 |
|---|---|
id | 唯一的区域标识符 |
name | 编辑器中的显示名称 |
max_components | 组件数量上限(可选) |
component_type_exclusions | 不允许使用的组件 |
component_type_inclusions | 仅允许使用的组件 |
渲染页面
在控制器中
var PageMgr = require('dw/experience/PageMgr');
server.get('Show', function (req, res, next) {
var page = PageMgr.getPage(req.querystring.cid);
if (page && page.isVisible()) {
res.page(page.ID);
} else {
res.setStatusCode(404);
res.render('error/notfound');
}
next();
});
在 ISML 中
<isscript>
var PageMgr = require('dw/experience/PageMgr');
var page = PageMgr.getPage('homepage-id');
</isscript>
<isif condition="${page && page.isVisible()}">
<isprint value="${PageMgr.renderPage(page.ID)}" encoding="off"/>
</isif>
组件分组
在编辑器侧边栏中整理组件:
{
"name": "Product Card",
"group": "products"
}
常见分组:content、products、navigation、layout、media
如果组件未出现在 Page Designer 中
- 枚举: 确保所有
enum属性都使用"values": ["a", "b"](字符串数组),而不是包含value/display_value的对象。 - 自定义编辑器: 对有问题的属性,将
type: "custom"(例如颜色选择器)替换为type: "string"并重新部署;如果组件随后出现,问题可能出在自定义编辑器或 BM cartridge 路径上。 - 命名: 文件名和子目录名只能包含字母数字字符或下划线。
- region_definitions: 组件 meta 必须包含
region_definitions(如果没有嵌套区域,请使用[])。 - 模板路径: 脚本中的
Template('...')路径必须与templates/default/下的模板路径一致(包括experience/components/assets/...这类子目录)。 - 日志: 在 Business Manager 中,打开 Page Designer 时,请检查 Administration > Site Development > Development Setup 中的错误日志,查找与组件类型 ID 相关的脚本或 meta 错误。
- 代码版本: 将 cartridge 部署到正确的代码版本;在非生产环境中,meta 可能会缓存几秒钟,可尝试切换代码版本或稍等片刻。
最佳实践
- 使用
dw.util.Template进行渲染(不要使用dw.template.ISML) - 让组件保持自包含 —— 避免组件间相互依赖
- 为可选属性提供默认值(这些默认值适用于渲染;在编辑器中不会显示为预选值)
- 在
attribute_definition_groups中对相关属性进行分组 - 使用有意义的 ID —— 它们会以编程方式使用
- 商家创建内容后,不要更改类型 ID 或属性类型(这会导致存储的数据不一致);如有需要,可添加新的组件类型并弃用旧类型
- 枚举优先使用字符串数组,颜色优先使用字符串,除非你能控制 BM cartridge 路径和自定义编辑器
详细参考
如需完整的属性文档: