主题开发
使用 Liquid 模板语言开发 ShopFaaS 店铺前台主题。
本指南面向主题开发者、二次开发伙伴、AI 主题生成器。以 default/1.0.0 主题为标杆与参照,描述 ShopFaaS 店铺前端的模板体系、上下文变量、自定义标签与过滤器。所有内容均以实际代码为依据。
三层模板机制
ShopFaaS 店铺前端渲染由三层组成,按优先级从高到低:
| 层级 | 角色 | 实现技术 |
|---|---|---|
| L1 主题源码 | 商家可定制的 Liquid 主题包 | Liquid(liquidjs 引擎) |
| L2 运行时存储 | Liquid 引擎实际读取的副本(S3 + Local 双写) | 对象存储 |
| L3 兜底组件 | 主题缺失或模板不存在时的默认实现 | Astro + Solid |
关键点:L3 兜底组件是默认实现,不是简单占位。default/1.0.0 主题的 Liquid 源码与 L3 兜底组件1:1 对齐——每个 Liquid section/snippet 都对应一个 .astro 组件,CSS/JS 也保持同步。开发者二次开发新主题时,可参照 default/1.0.0 的源码作为模板,并以 L3 兜底组件为视觉与交互的”正确性基准”。
主题包结构
主题包根目录为 {themeCode}/{version}/,包含 7 个固定子目录:
my-theme/1.0.0/
├── assets/ # 静态资源(CSS / JS / 图片 / 字体)
│ ├── theme.css # 主题样式
│ └── theme.js # 主题脚本
├── config/ # 主题配置
│ ├── settings_data.json # 主题设置当前值
│ └── settings_schema.json # 主题设置 schema 定义
├── layouts/ # 布局模板
│ └── theme.liquid # 主布局(HTML 骨架 + 全局 section)
├── locales/ # 多语言翻译文件
│ ├── en.default.json # 默认语言(必须)
│ └── zh-CN.json # 中文
├── sections/ # Section 文件(可复用页面区块)
│ ├── header.liquid
│ ├── footer.liquid
│ └── ...
├── snippets/ # Snippet 文件(可被 {% render %} 引用的小片段)
│ ├── card-product.liquid
│ └── ...
└── templates/ # 页面模板(按 PageType 映射)
├── index.liquid # HOME
├── product.liquid # PRODUCT
├── collection.liquid # COLLECTION
└── ...
不存在的目录
以下目录在 ShopFaaS 主题包中不存在:
- ❌
schemas/— section schema 以内联注释形式写在 section 文件首行(见下文”自定义标签”),无独立 schema 目录 - ❌
theme.json— 主题配置统一用config/settings_data.json,不存在根级theme.json文件 - ❌
blocks/— 当前未实现 block 独立目录机制 - ❌
customers/— 客户中心页面模板直接放在templates/下(如customer-login.liquid),无独立子目录
文件命名规范
- 全部使用 kebab-case:
main-checkout.liquid/customer-order-detail.liquid - section 文件名 = section 类型名(
{% section 'main-product' %}对应sections/main-product.liquid) - snippet 文件名 = snippet 名称(
{% render 'cart-drawer' %}对应snippets/cart-drawer.liquid) - 模板文件名 =
PAGE_TYPE_TO_TEMPLATE映射值(见下文)
Liquid 上下文变量
渲染时注入以下全局变量。所有 section / snippet / layout / template 均可直接引用。
核心业务变量
| 变量 | 类型 | 说明 |
|---|---|---|
shop | ShopContext | 店铺信息(id / name / currency / currencyFormat / email / phone / address / domain / timezone / industry / logoUrl) |
customer | CustomerHeaderSummary | null | 当前登录客户摘要(displayName / email,未登录为 null)。注意字段名是 displayName,不是 name |
page | PageData | 当前页面业务数据(联合类型,按 pageType 区分) |
pageType | string | 页面类型枚举值(如 "PRODUCT" / "CUSTOMER_ORDERS"),全大写 |
sections | SectionInstance[] | 当前页面 section 配置列表(snake_case:section_type / section_key / section_config / section_blocks / sort_order) |
collections | CollectionSummary[] | 导航用分类列表(id / handle / title / image / productCount) |
languages | ShopLanguageOption[] | 店铺可用语言列表 |
currencies | ShopCurrencyOption[] | 店铺可用币种列表 |
themeSettings | Record<string, unknown> | 主题设置(扁平字段命名,见下文) |
locale | string | BCP 47 locale(如 en-US / zh-CN) |
currency | string | 当前币种 ISO 代码(如 USD / CNY) |
exchangeRates | ExchangeRateMap | 汇率映射表(key 格式 ${from}_${to},如 USD_CNY) |
chat | ChatContext | 在线客服配置(enabled / welcomeMessage / placeholder / offlineMessage) |
overrides | Record<string, string> | 主题文案 DB 覆盖 |
顶层展开变量
page 数据会展开到顶层(排除 page 和 type 字段),模板内可直接使用展开后的字段:
{{ product.title }}
{{ collection.title }}
{{ cart.items }}
{{ article.title }}
{{ searchQuery }}
CollectionPageData.page(页码数字)单独暴露为 current_page:
{% if current_page > 1 %}
<span>第 {{ current_page }} 页</span>
{% endif %}
请求级变量
| 变量 | 字段 | 说明 |
|---|---|---|
request | locale.iso_code | BCP 47 locale,用于 <html lang="..."> |
request | locale.lang / locale.country | 拆分的语言/国家代码 |
request | page_type | 页面类型小写(如 home / product / cart) |
request | design_mode | 是否主题编辑器预览模式(当前固定 false) |
request | origin | 店铺 origin |
路由变量 routes
完整 28 个路由 URL,供 layout/sections/snippets 中构造链接。所有路由以 / 开头,无尾斜杠。
| 字段 | 值 |
|---|---|
routes.root_url | / |
routes.cart_url | /cart |
routes.cart_add_url | /cart/add |
routes.cart_change_url | /cart/change |
routes.cart_update_url | /cart/update |
routes.cart_count_url | /cart/count |
routes.cart_discount_code_apply_url | /cart/discount/apply |
routes.cart_discount_code_remove_url | /cart/discount/remove |
routes.search_url | /search |
routes.predictive_search_url | /search/suggest |
routes.account_url | /account |
routes.account_login_url | /account/login |
routes.account_logout_url | /account/logout |
routes.account_register_url | /account/register |
routes.account_recover_url | /account/forgot-password |
routes.account_order_list_url | /account/orders |
routes.account_address_new_url | /account/addresses/new |
routes.address_url | /account/addresses |
routes.address_countries_url | /api/countries |
routes.address_country_template_url | /api/countries/template |
routes.address_next_url | /account/addresses/next |
routes.company_account_register_url | /account/company/register |
routes.collections_url | /collections |
routes.all_products_collection_url | /collections/all |
routes.blogs_url | /blogs |
routes.contact_url | /pages/contact |
routes.privacy_policy_url | /policies/privacy |
routes.product_recommendations_url | /recommendations/products |
店铺级设置 settings
固定 3 个字段:
| 字段 | 值 | 说明 |
|---|---|---|
settings.cart_add_type | "drawer" | 加购后打开购物车抽屉 |
settings.predictive_search | true | 启用预测搜索 |
settings.show_sticky_add_to_cart | true | 商品页粘性加购按钮 |
翻译变量
| 变量 | 说明 |
|---|---|
__translations | 当前 locale 完整翻译字典。供 t 过滤器读取。优先级:DB 覆盖 > 主题 locales > 内置字典 > key 本身 |
client_i18n | 客户端 JS 共享字典,由 theme.liquid 注入 window.__STOREFRONT_I18N__ |
__shop_currency | 店铺基础币种(商品存储币种,如 USD),供 money 过滤器读取 |
__currency | 当前用户选择币种(cookie sf_currency 解析结果,如 CNY) |
客户端 JS 共享变量
theme.liquid 应将以下变量注入 window,供 CartDrawer / CartPage / QuickAddModal 等客户端 JS 读取:
window.__STOREFRONT_I18N__ = client_i18n;
window.__STOREFRONT_CURRENCY__ = currency;
window.__STOREFRONT_LOCALE__ = locale;
window.__STOREFRONT_BASE_CURRENCY__ = shop.currency;
window.__STOREFRONT_RATES__ = exchange_rates;
window.__STOREFRONT_LINK_QUERY__ = link_query;
link_query 含义:开发环境为 ?shop_id=xxx(或 ?shop_id=xxx&theme_preview=xxx),生产环境为空字符串。客户端 JS 构造内部链接时需追加此参数。
link_query 使用规范(重要)
link_query 是已含前导 ? 的完整查询串(如 ?shop_id=xxx),追加到 URL 末尾即可。但需注意 URL 是否已有查询参数:
场景 1:URL 无查询参数(最常见)
直接追加 {{ link_query }}:
<a href="/cart{{ link_query }}">购物车</a>
<a href="/products/{{ product.handle }}{{ link_query }}">{{ product.title }}</a>
渲染结果:/cart?shop_id=xxx / /products/premium-hoodie?shop_id=xxx(正确)
场景 2:URL 已有查询参数(如 ?redirect_to=)
必须用 | replace_first: '?', '&' 将前导 ? 转为 &,否则 shop_id 会被并入 redirect_to 的值:
{# ❌ 错误:shop_id 被并入 redirect_to 值 #}
<a href="/account/login?redirect_to={{ '/checkout' | url_encode }}{{ link_query }}">登录</a>
{# 渲染结果:/account/login?redirect_to=%2Fcheckout?shop_id=xxx(redirect_to 值变成 %2Fcheckout?shop_id=xxx)#}
{# ✅ 正确:前导 ? 转 & #}
<a href="/account/login?redirect_to={{ '/checkout' | url_encode }}{{ link_query | replace_first: '?', '&' }}">登录</a>
{# 渲染结果:/account/login?redirect_to=%2Fcheckout&shop_id=xxx #}
场景 3:客户端 JS 构造链接
客户端 JS 通过 window.__STOREFRONT_LINK_QUERY__ 读取(已是 ?shop_id=xxx 或空字符串),直接拼接即可:
var linkQuery = window.__STOREFRONT_LINK_QUERY__ || "";
var productUrl = "/products/" + encodeURIComponent(handle) + linkQuery;
Astro 兜底侧:使用
link()函数(由createLinker创建),内部已自动处理?/&切换,无需手动 replace。Liquid 侧因link_query是固定字符串,需开发者自行处理。
link_query 不要用在哪
- section settings 动态链接:
{{ section.settings.button_link }}是用户在主题编辑器配置的 URL(可能是外链或绝对路径),不应追加link_query - OAuth API 链接:
/api/auth/oauth/*是 API 路由,无需追加查询参数 - 静态资产 URL:
{{ 'theme.css' | asset_url }}等已由asset_url过滤器处理
PageType 与模板映射
完整 PageType 枚举(25 种)
通用页面(12 种):
HOME / PRODUCT / COLLECTION / COLLECTION_LIST / SEARCH / CART / BLOG / BLOG_LIST / PAGE / CHECKOUT / ORDER_CONFIRMATION / NOT_FOUND
客户中心页面(13 种):
CUSTOMER_LOGIN / CUSTOMER_REGISTER / CUSTOMER_FORGOT_PASSWORD / CUSTOMER_RESET_PASSWORD / CUSTOMER_ACCOUNT / CUSTOMER_ADDRESSES / CUSTOMER_ORDERS / CUSTOMER_ORDER_DETAIL / CUSTOMER_WISHLIST / CUSTOMER_BACK_IN_STOCK / CUSTOMER_BROWSING_HISTORY / CUSTOMER_CONSENT / CUSTOMER_SUBSCRIPTIONS
PageType → 模板文件映射
25 个 PageType 全部映射到对应模板文件:
| PageType | 模板文件 |
|---|---|
HOME | templates/index.liquid |
PRODUCT | templates/product.liquid |
COLLECTION | templates/collection.liquid |
COLLECTION_LIST | templates/list-collections.liquid |
SEARCH | templates/search.liquid |
CART | templates/cart.liquid |
BLOG | templates/article.liquid |
BLOG_LIST | templates/blog.liquid |
PAGE | templates/page.liquid |
CHECKOUT | templates/checkout.liquid |
ORDER_CONFIRMATION | templates/order-confirmation.liquid |
NOT_FOUND | templates/404.liquid |
CUSTOMER_LOGIN | templates/customer-login.liquid |
CUSTOMER_REGISTER | templates/customer-register.liquid |
CUSTOMER_FORGOT_PASSWORD | templates/customer-forgot-password.liquid |
CUSTOMER_RESET_PASSWORD | templates/customer-reset-password.liquid |
CUSTOMER_ACCOUNT | templates/customer-account.liquid |
CUSTOMER_ADDRESSES | templates/customer-addresses.liquid |
CUSTOMER_ORDERS | templates/customer-orders.liquid |
CUSTOMER_ORDER_DETAIL | templates/customer-order-detail.liquid |
CUSTOMER_WISHLIST | templates/customer-wishlist.liquid |
CUSTOMER_BACK_IN_STOCK | templates/customer-back-in-stock.liquid |
CUSTOMER_BROWSING_HISTORY | templates/customer-browsing-history.liquid |
CUSTOMER_CONSENT | templates/customer-consent.liquid |
CUSTOMER_SUBSCRIPTIONS | templates/customer-subscriptions.liquid |
自定义标签
{% section 'type' %}
从 sections/{type}.liquid 读取并渲染。
Section schema 内联注释:section schema 以 JSON 注释形式写在文件首行,无独立 schemas/ 目录:
{%- comment -%}{"schema":{"name":"Header"}}{%- endcomment -%}
{%- comment -%}
Header section — 描述文字
上下文变量:shop / locale / currency / languages / currencies / customer / collections
{%- endcomment -%}
<header class="header">
...
</header>
Section 实例数据:{% section %} 标签从上下文 sections 数组按 section_type 匹配实例。匹配的实例数据通过两个变量暴露给 section 模板:
section.settings:section 配置(等价于section_config)section.blocks:section 块数据(等价于section_blocks)
{% render 'snippet' %}
从 snippets/{snippet}.liquid 读取。支持命名参数传递:
{% render 'card-product', product: product, show_vendor: themeSettings.show_vendor %}
{% layout 'name' %}
从 layouts/{name}.liquid 读取布局模板。layout 模板内通过 {{ content_for_layout }} 接收子模板渲染结果。
{# templates/index.liquid #}
{% layout 'theme' %}
<div class="index-template">
{% section 'slideshow' %}
{% section 'featured-collection' %}
</div>
数据模型
核心数据接口
ProductSummary(列表用)
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 商品 ID |
handle | string | URL handle |
title | string | 标题 |
price | number | 价格(单位:分) |
compareAtPrice | number | null | 划线价(分) |
image | string | null | 主图 |
image2 | string | null | 第二张图(hover 切换) |
available | boolean | 是否可售 |
rating | number | 平均评分(1-5,无评论为 0) |
reviewCount | number | 评论总数 |
vendor | string | 品牌/供应商 |
specValues | Record<string, string[]> | 规格值聚合 |
ProductDetail(详情页,extends ProductSummary)
新增字段:description / variants / images / options / ageRestricted / sellingPlans
ProductVariant
| 字段 | 类型 | 说明 |
|---|---|---|
id | string | 变体 ID |
title | string | 变体标题 |
price | number | 价格(分) |
compareAtPrice | number | null | 划线价(分) |
available | boolean | 是否可售 |
optionValues | Record<string, string> | 选项值(如 {"COLOR":"Black","SIZE":"M"}) |
quantity | number | 库存数量 |
sku | string | SKU 编码 |
CartItem
| 字段 | 类型 | 说明 |
|---|---|---|
productId | string | 商品 ID |
variantId | string | 变体 ID |
title | string | 商品标题 |
quantity | number | 数量 |
price | number | 单价(分) |
image | string | null | 商品图片 |
specValues | Record<string, string> | null | 变体规格属性 |
CustomerHeaderSummary(Header 登录态)
| 字段 | 类型 | 说明 |
|---|---|---|
displayName | string | 展示名(优先 fullName,无则邮箱前缀)。不是 name |
email | string | null | 客户邮箱 |
金额单位统一为「分」:所有
price/compareAtPrice/totalAmount等字段均为整数分。使用{{ product.price | money }}过滤器自动除以 100 并格式化。
themeSettings 字段
命名规范
扁平字段命名,使用 snake_case(如 color_primary / heading_font / page_width),不是嵌套结构(如 )。colors.primary
完整字段清单
字段定义在 config/settings_schema.json,当前值在 config/settings_data.json 的 current 字段。
Colors(6 个)
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
color_primary | color | #1a1a1a | 主色 |
color_secondary | color | #6b7280 | 次色 |
color_accent | color | #c9a961 | 强调色 |
color_background | color | #ffffff | 背景色 |
color_text | color | #111827 | 文字色 |
color_border | color | #e5e7eb | 边框色 |
Typography(2 个)
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
heading_font | text | Georgia, serif | 标题字体 CSS font-family |
body_font | text | system-ui, sans-serif | 正文字体 |
Layout(3 个)
| 字段 | 类型 | 默认值 | 范围 | 说明 |
|---|---|---|---|---|
page_width | range | 1280 | 1024-1600 / step 32 | 页面宽度(px) |
container_padding | range | 24 | 8-48 / step 4 | 容器内边距(px) |
section_spacing | range | 48 | 24-96 / step 8 | section 间距(px) |
Header / Footer / Product / Slideshow / Social Media / Social Login
完整字段清单参考 default/1.0.0/config/settings_schema.json。常用字段包括:
- Header:
logo_text/logo_width/sticky_header/show_announcement/mega_menu_show_images/mega_menu_promo_image/mega_menu_promo_title/mega_menu_promo_url - Footer:
show_social/show_payment_icons/footer_text - Product:
show_vendor/show_reviews/quick_add_enabled - Slideshow:
slideshow_image/slideshow_mobile_image/slideshow_overlay_opacity - Social Media:
social_facebook/social_twitter/social_instagram/social_youtube/social_pinterest/social_tiktok - Social Login:
oauth_google_enabled/oauth_facebook_enabled/oauth_apple_enabled
注意:
settings_data.json的current字段可能不包含settings_schema.json中所有字段。模板中读取未设置的字段会得到nil,应使用default过滤器兜底:{{ themeSettings.color_primary | default: '#1a1a1a' }}。
读取方式
主题设置统一从 config/settings_data.json 的 current 字段读取(Shopify 标准,扁平结构)。
自定义过滤器
asset_url
主题静态资产路径 → 访问 URL。
<link rel="stylesheet" href="{{ 'theme.css' | asset_url }}">
<script src="{{ 'theme.js' | asset_url }}" defer></script>
asset_img_url
主题图片资产 → 带尺寸参数的 URL。
<img src="{{ 'slide-1.jpg' | asset_img_url: 1920 }}" alt="...">
image_url
图片路径 → 带尺寸参数的 URL(用于商品图、分类图等业务图片)。
<img src="{{ product.image | image_url: 600 }}" alt="{{ product.title }}">
money
金额(分)→ 格式化字符串(含汇率换算)。
{{ product.price | money }}
关键特性:自动从上下文读取币种,模板无需显式传 currency 参数:
__shop_currency:店铺基础币种(商品存储币种)__currency:当前用户选择币种exchange_rates:汇率映射表
换算逻辑:商品价格以店铺基础币种存储 → 若当前币种 ≠ 店铺基础币种,按汇率换算 → 用 Intl.NumberFormat 格式化(自动除以 100)。
t
多语言翻译。
{{ 'product.addToCart' | t }}
- 从
__translations字典查找 - 不支持
defaultValue参数,未命中翻译时返回 key 本身 - 翻译优先级:DB 覆盖 > 主题 locales 文件 > 内置字典 > key 本身
标准过滤器
liquidjs 内置过滤器全部可用:default / escape / strip_html / truncate / divided_by / times / plus / minus / modulo / json / date / capitalize / downcase / upcase / split / join / slice 等。
注意:标准 Liquid 没有
escape_attr过滤器,仅有escape。
多语言 i18n
双轨制
| 轨道 | 位置 | 用途 | key 格式 |
|---|---|---|---|
| 主题 locales | locales/{locale}.json | Liquid 模板内 t 过滤器 | 嵌套 JSON(如 {"search":{"title":"Search"}},t 过滤器读取时扁平化为 search.title) |
| 内置字典 | 平台内置 | 兜底组件 + 客户端 JS | 扁平化 key(如 search.title) |
locale 文件命名
en.default.json:默认语言(必须存在)zh-CN.json:BCP 47 locale 命名
翻译优先级(最终生效顺序)
- DB 覆盖(店长在后台编辑的自定义翻译)
- 主题 locales 文件
- 内置字典
- key 本身(兜底)
客户端 JS i18n
theme.liquid 应将 client_i18n 注入 window.__STOREFRONT_I18N__:
window.__STOREFRONT_I18N__ = {{ client_i18n | json | default: '{}' }};
主题开发规范
CSS 规范
- 纯手写 CSS + CSS 变量,禁止使用 Tailwind utility class
- CSS 变量在
layouts/theme.liquid的<style>块中定义(:root选择器) - 所有主题样式统一放在
assets/theme.css
JavaScript 规范
- 所有主题脚本统一放在
assets/theme.js - 使用原生 JS(ES2020+),不依赖 jQuery / React / Vue 等框架
- 客户端 JS 通过
window.__STOREFRONT_*全局变量与服务端共享数据 - 事件驱动:CartDrawer / CartNotification / QuickAddModal 等组件通过自定义事件通信(如
storefront:cart-updated)
可访问性(A11y)
- 所有交互元素提供
aria-label/aria-expanded/aria-hidden等属性 - 图标 SVG 添加
aria-hidden="true" focusable="false" - 跳过链接(skip-link)直达正文(WCAG 2.4.1)
- 表单字段使用 WHATWG autocomplete tokens(
given-name/family-name/address-line1/postal-code/address-level2/country-name/tel/email) - 移动端增强:
autocapitalize/autocorrect/spellcheck/inputmode
模板编写约定
- Liquid 输出标签用
{{ }},控制流标签用{% %},去空格用{%- -%} - 多行注释用
{%- comment -%}...{%- endcomment -%} - section 文件首行必须为 schema 内联注释:
{%- comment -%}{"schema":{"name":"Section Name"}}{%- endcomment -%} - 判空用
== blank(检查空字符串/nil),而非unless:{% if query == blank %}
富文本内容渲染规范(强制,2026-07-20 新增)
后台 RichTextEditor(Tiptap 3.27)输出的 HTML 含 h1-h3 / p / ul / ol / li / blockquote / a / img / pre / code / table / iframe / YouTube 16:9 / 任务列表 / 文字对齐 / 高亮标记 / 文字颜色等元素。所有渲染这类富文本的容器必须追加 rich-text-content class,并在 assets/theme.css 中包含完整样式片段,否则元素会以浏览器默认样式渲染(无边框表格、未限制宽图片、无样式引用块等),与后台编辑器视觉严重不一致。
必须追加 rich-text-content class 的位置
| PageType | 模板文件 | 渲染容器(已有 class) |
|---|---|---|
PRODUCT | sections/main-product.liquid | collapsible-content rich-text-content(商品描述折叠面板) |
BLOG | sections/main-article.liquid | article-content rich-text-content(文章正文) |
PAGE | sections/main-page.liquid | static-content rich-text-content(静态页面正文) |
COLLECTION | sections/main-collection.liquid | page-description rich-text-content(分类描述,2 处:有图/无图布局) |
平台主题(hardware/megastore/printing)已有 class 名可能是
rte/product__description rte/article__content rte/collection__description rte/page-content__body rte,追加rich-text-content与原有 class 共存即可,不替换原 class。
CSS 样式片段要求
assets/theme.css 必须包含覆盖以下元素的完整样式(参考 default/1.0.0/assets/theme.css 末尾的 .rich-text-content 片段):
- 容器:
font-size: 16px/line-height: 1.75/color: var(--color-text, ...)/ 首尾子元素 margin 归零 - 标题:
h1-h4字号 / 字重 / margin / line-height - 段落与列表:
p/ul/ol/li(含ul[data-type="taskList"]任务列表 flex 布局) - 引用:
blockquote左边框 + 斜体 - 链接:
a颜色 + 下划线 + hover - 图片:
imgmax-width: 100%/height: auto/ 圆角 - 代码:
pre深色背景 +code内联背景 + 等宽字体 - 表格:
tableborder-collapse/table-layout: fixed/width: 100%/td/th边框与 padding /th表头背景 /.tableWrapper横向滚动 /.selectedCell选中高亮 /.column-resize-handle列宽调整手柄 - 视频:
iframemax-width / YouTube 16:9 响应式容器(.rich-text-youtube/div[data-youtube-video]padding-bottom: 56.25%) - 文字对齐:
[style*="text-align: center/right/justify"] - 高亮:
mark黄色背景 - 响应式:
@media (max-width: 640px)字号略小
避免与首页 section 限宽规则冲突
default/sunset/aurora/ocean 四个 storefront 主题的 theme.css 原有 .rich-text-content { max-width: 720px; margin: 0 auto; } 规则仅用于首页 sections/rich-text.liquid section(居中限宽富文本区块)。新追加的完整样式片段不能覆盖此规则,否则商品描述/文章/页面/分类描述也会被错误限宽到 720px。
正确做法:将原有规则的选择器改为 .rich-text-section .rich-text-content(限定作用域到首页 section),新追加的完整样式片段用 .rich-text-content 通用选择器(不含 max-width/margin)。CSS 同特异性下后者先声明先生效,首页 section 因有更具体的选择器(.rich-text-section .rich-text-content)仍能正确限宽。
新主题开发检查清单
- 4 个
main-*.liquid模板中渲染富文本的容器已追加rich-text-contentclass -
assets/theme.css末尾已包含完整.rich-text-content样式片段 - 若主题含首页
rich-text.liquidsection,其限宽规则已用.rich-text-section .rich-text-content选择器限定作用域 - 在商品详情/文章/页面/分类描述页面渲染富文本时,元素样式与后台 RichTextEditor 视觉一致
共享资源
平台提供跨主题共享的 JS 模块,主题可直接引用:
chat-widget.js(在线客服)
{# 在 theme.liquid 或 section/snippet 中引入 #}
<script src="/shared/1.0.0/assets/chat-widget.js" defer></script>
引入后无需显式调用 init(),页面加载后自动查找 .chat-widget 元素并初始化。主题需提供 DOM 骨架(含 data-chat-* 配置属性),参考 default/1.0.0/snippets/chat-widget.liquid。
参考实现
default/1.0.0 是 ShopFaaS 官方标杆主题,开发者二次开发时应以此为参照:
- 25 个模板:覆盖全部 25 个 PageType(通用 12 + 客户中心 13)
- 38 个 section:含
header/footer/slideshow/main-product/main-collection/main-cart/main-checkout/main-search/main-article/main-blog/main-page/main-customer-*等 - 20 个 snippet:
card-product/cart-drawer/chat-widget/cookie-consent/customer-sidebar/facets/icon/loading-spinner/meta-tags/pagination/predictive-search/price/product-media-gallery/product-variant-picker/quantity-input/quick-add-modal/social-icons/swatch/breadcrumb/buy-buttons - 1 个 layout:
theme.liquid(主布局) - 2 个 config:
settings_data.json+settings_schema.json - 2 个 locales:
en.default.json+zh-CN.json - 2 个 assets:
theme.css+theme.js
常见错误与排查
模板修改不生效
- 主题源码是否已同步到运行时存储?
- theme-resolver L1 缓存是否未过期?(TTL 10 分钟,可用
?theme_preview=default参数绕过)
money 过滤器显示错误价格
- 检查
__shop_currency/__currency/exchange_rates是否正确注入上下文 - 检查汇率表 key 格式是否为
${from}_${to}(如USD_CNY) - 金额单位是否为「分」(整数),而非「元」(浮点)
t 过滤器返回 key 本身
- key 是否在
__translations字典中? - locale 文件是否已同步?
- 主题 locales key 是否已扁平化?
section 不渲染
- section 类型名是否与
{% section 'xxx' %}参数一致?(含大小写) sections/{xxx}.liquid文件是否存在?- 若 section 类型在
sections数组中无配置实例,标签会用空配置渲染一次(section 模板需自行处理空数据兜底)