商品详情与组合购买
先同步型号与变体事实,再处理媒体、配件和规格信息。
型号、变体与购买表单
由选中的真实变体决定价格、可售状态和加购 ID。先提供可刷新的变体地址,再增强无刷新选择;图片、SKU 和网址都随同一状态更新。
数据与依赖
- product 和 selected_or_first_available_variant。
- 提交的是 variant.id,不是 product.id。
交互逻辑
- GET 选择先刷新当前变体,再使用购买表单。
- 缺货仍可查看,购买按钮按实际状态禁用。
示例代码 · snippets/ww-variant-fallback.liquid
{% assign current_variant = product.selected_or_first_available_variant %}
<h1>{{ product.title | escape }}</h1>
<p>{{ current_variant.title | escape }} — {{ current_variant.price | money_with_currency }}</p>
<form action="{{ product.url }}" method="get">
<label for="Variant-{{ section.id }}">Variant</label>
<select id="Variant-{{ section.id }}" name="variant">
{% for variant in product.variants %}
<option value="{{ variant.id }}"{% if variant.id == current_variant.id %} selected{% endif %}>
{{ variant.title | escape }}{% unless variant.available %} — Sold out{% endunless %}
</option>
{% endfor %}
</select>
<button type="submit">View this variant</button>
</form>
{% assign purchase_form_id = 'Purchase-' | append: section.id %}
{% form 'product', product, id: purchase_form_id %}
<input type="hidden" name="id" value="{{ current_variant.id }}">
<button type="submit"{% unless current_variant.available %} disabled{% endunless %}>
{% if current_variant.available %}Add to cart{% else %}Sold out{% endif %}
</button>
{% endform %}
验收检查
- 打开带 variant 参数的链接,核价格与表单 ID。
- 核缺货、选择变化和 JavaScript 禁用。
这是少量变体的无脚本基线;高变体商品沿当前主题的选项与按需渲染方案实现,不把此循环当全量变体架构。
缩略图、主图与演示视频
媒体列表先按实际资源输出;增强缩略图时保持当前媒体与变体关系。信息图的关键参数另用 HTML 说明,视频不阻塞商品文字和购买入口。
数据与依赖
- product.media 与资源 media_type。
- 说明性 alt 来自该型号图片资料。
交互逻辑
- 图片可读,视频由用户控制。
- 切换变体时定位其关联媒体,并保留无图回退。
示例代码 · snippets/ww-media.liquid
<ol aria-label="Product media">
{% for media in product.media %}
<li id="Media-{{ section.id }}-{{ media.id }}">
{% case media.media_type %}
{% when 'image' %}
{{ media | image_url: width: 1400 | image_tag: widths: '480, 800, 1400', sizes: '(max-width: 749px) 100vw, 60vw', loading: 'lazy' }}
{% when 'video' %}
{{ media | video_tag: controls: true, autoplay: false, preload: 'none' }}
{% else %}
<a href="{{ product.url | escape }}">View media on the product page</a>
{% endcase %}
</li>
{% endfor %}
</ol>
验收检查
- 核缩略图选中、键盘、触摸与主图。
- 首图不延迟;下方媒体可按需加载。
示例是媒体列表;首屏图片需根据实际位置改用 eager,轮播和焦点管理接现有媒体组件。
适配配件与明确加购
兼容关系来自审核过的型号资料,先列可购买的配件入口。组合加购时重新确认主变体、配件变体和数量,用真实服务端结果更新购物车。
数据与依赖
- custom.compatible_accessories:list.product_reference。
- 精确到变体的适配关系需要单独记录。
交互逻辑
- 切换主型号后重新核配件选择。
- 提交中防重复;失败不自动再次加购。
示例代码 · snippets/ww-accessories.liquid
{% assign accessories = product.metafields.custom.compatible_accessories.value %}
{% if accessories != blank %}
<section aria-labelledby="Accessories-{{ section.id }}">
<h2 id="Accessories-{{ section.id }}">Accessories for this model</h2>
<ul>
{% for accessory in accessories %}
<li>
<a href="{{ accessory.url | escape }}">{{ accessory.title | escape }}</a>
<span>{{ accessory.price | money_with_currency }}</span>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
示例代码 · assets/ww-add-selection.js
export async function addSelection(root, items) {
if (!root.startsWith('/') || root.startsWith('//')) throw new Error('Invalid store route');
if (!items.length || items.some(item => !Number.isSafeInteger(item.id) || item.id <= 0 ||
!Number.isSafeInteger(item.quantity) || item.quantity <= 0)) {
throw new Error('Select valid variants and quantities');
}
const response = await fetch(`${root}cart/add.js`, {
method: 'POST',
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
body: JSON.stringify({items})
});
const result = await response.json();
if (!response.ok) throw new Error(result.description || 'The selection could not be added');
return result;
}
// Pass window.Shopify.routes.root from a Shopify theme.
// Disable repeat submission while pending, then refresh actual cart state.
// A network error must not trigger an automatic second add request.
验收检查
- 核不兼容、缺货及多个配件数量。
- 成功或异常后读取购物车确认实际行项目。
商品推荐不等于兼容证明。列表片段不直接加购;Ajax 函数须接已有状态反馈与实际路由。
规格 TAB、包装与政策入口
把规格、包装、兼容和政策放进有序分区,默认使用可键盘展开的原生内容。若增强为 TAB,所有正文仍在服务器 HTML 中,并保持同一字段和政策版本。
数据与依赖
- 规格行依赖 ww-specifications。
- custom.compatibility:单行文本;政策引用现行页面。
交互逻辑
- 缺失内容不生成空承诺。
- 展开后保留标题和可点击出处。
示例代码 · snippets/ww-product-details.liquid
<details>
<summary>Specifications and compatibility</summary>
{% render 'ww-specifications', product: product %}
{% assign compatibility = product.metafields.custom.compatibility.value %}
{% if compatibility != blank %}<p>{{ compatibility | escape }}</p>{% endif %}
</details>
<details>
<summary>Shipping and returns</summary>
{% if shop.shipping_policy != blank %}<a href="{{ shop.shipping_policy.url | escape }}">Shipping policy</a>{% endif %}
{% if shop.refund_policy != blank %}<a href="{{ shop.refund_policy.url | escape }}">Returns policy</a>{% endif %}
</details>
示例代码 · snippets/ww-specifications.liquid
{% assign rows = product.metafields.custom.specification_rows.value %}
{% if rows != blank %}
<table>
<caption>{{ product.title | escape }} — specifications</caption>
<tbody>
{% for row in rows %}
{% if row.label.value != blank and row.spec_value.value != blank %}
<tr>
<th scope="row">{{ row.label.value | escape }}</th>
<td>{{ row.spec_value.value | escape }} {{ row.unit.value | escape }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% endif %}
验收检查
- 关掉脚本仍能读规格与政策。
- 核手机表格、长参数及实际条款目标。
展示政策入口不代表已适配所有市场;地区例外由政策模块继续判断。
先理解,再整合,最后验证
- 选择模块,确认数据和输入输出。
- 将片段接入当前主题或框架。
- 用真实变体、市场和失败恢复场景验收。
示例按 Shopify Online Store 2.0 主题编写;Headless 使用单独的数据与版本边界。代码片段需接实际字段、主题和语言文件。
架构参考
示例由 WESWOO 编写,结合 Shopify 官方文档和固定版本的仓库参考。采用仓库代码时应保留适用许可。
Shopify/dawn · 许可commit 3e9ee2281dc4 · Shopify 集成主题的限定许可;参考架构,不复制仓库实现。
Shopify/hydrogen · 许可commit 9e358a2462ba · MIT;采用实质代码时保留版权与许可。
依据核对 2026-11-12