Dawn 商品页购买区:价格、变体、加购按钮与库存状态
本文基于 Dawn v15.3.0,源码固定到 ecb06c3(核验于 2026-09-29)。结论限于此提交,不表示其他版本行为相同;本篇为静态源码分析,未完成店铺运行验证。Dawn 上游已发布 v16.0.0(2026-08-10,GitHub Releases 核对),本库 Dawn 正文统一以 v15.3.0 为基线,升级前需重新核对。
对应哪些通用模式
覆盖购买区模块与库存状态与到货通知模块在 Dawn 商品页上的实现。商品页整体管线见 Dawn 架构,数量控件见数量选择器,加购后的抽屉与通知见购物车篇,本篇不重复。Horizon 的对应内容见 Horizon 购买流程与变体选择,两个主题的结论互不推断。
入口与文件
| 文件 | 职责 / 何时输出 |
|---|---|
| sections/main-product.liquid | 按区块类型输出 price、variant_picker、quantity_selector、inventory、buy_buttons 等;声明各区块的 schema 设置 |
| snippets/price.liquid | 价格、划线价、单位价、促销与售罄徽标(use_variant: true 取所选或首个可售变体) |
| snippets/product-variant-picker.liquid、snippets/product-variant-options.liquid、snippets/swatch-input.liquid | variant-selects:按钮、下拉、色板三种形态;无变体选项的商品(has_only_default_variant)不输出 |
| snippets/buy-buttons.liquid | product-form、隐藏的 id 输入、加购按钮、payment_button、取货可用性容器 |
| sections/pickup-availability.liquid、assets/pickup-availability.js | 取货摘要与门店抽屉,经 variants/<id>/?section_id=pickup-availability 取回 |
| assets/product-form.js | 提交、错误展示、按钮状态 |
| assets/product-info.js、assets/global.js(VariantSelects) | 变体切换后的分区取回与局部更新;variant-selects 派发选项变化 |
| templates/product.json | 默认区块组成:vendor、title、caption、price、variant_picker、quantity_selector、buy_buttons、description、折叠行、share;没有 inventory 区块 |
数据从哪来
- 价格:
price区块渲染price片段,读selected_or_first_available_variant的price、compare_at_price、unit_price;有阶梯价(quantity_price_breaks_configured?)时改为价格区间并输出阶梯价说明。是否显示币种代码取主题设置currency_code_enabled。税费说明按cart.taxes_included、cart.duties_included、shop.shipping_policy;分期入口是另一个form 'product'里的payment_terms。 - 变体:
product.options_with_values与每个值的available、selected、product_url、swatch;所选变体的 JSON 写在data-selected-variant脚本里。 - 购买动作:
buy-buttons输出form 'product',隐藏输入name="id"取selected_or_first_available_variant.id,商品页数量输入框以form属性挂到同一表单;show_dynamic_checkout为真且不是需要收件人的礼品卡时输出form | payment_button生成的动态结账按钮。 - 库存:
inventory区块读inventory_management、inventory_quantity、inventory_policy;取货读store_availabilities中pick_up_enabled的项。 - 请求:加购 POST
routes.cart_add_url;变体切换 GET 同一商品 URL,参数section_id与option_values;取货 GETvariants/<id>/?section_id=pickup-availability。
交互怎样运行
变体切换。 variant-selects 在 change 时发布 optionValueSelectionChange。ProductInfo.handleOptionValueChange 先 resetProductFormState(把提交按钮置为 disabled 并清错误),abort() 上一个请求,再请求带 section_id 与 option_values 的分区。同商品内走 handleUpdateProductInfo:更新取货、选项、URL 的 variant 参数与表单的 id 输入,再逐块替换 price、Sku、Inventory、Volume、Price-Per-Item 与数量规则,最后按响应里按钮是否 disabled 决定提交按钮状态,并发布 variantChange。所选变体为 null(响应里没有对应变体)时进入 setUnavailable:按钮禁用并显示「Unavailable」,隐藏价格、库存、SKU 等块。跨商品的选项(data-product-url 不同)则整体替换 product-info 或 main,并在替换后重新 Shopify.PaymentButton.init()。
加购。 ProductForm 在提交时置 aria-disabled、显示加载图标,POST FormData;响应含 status 视为失败,description 写入按钮上方的 role="alert" 区域(礼品卡收件人表单启用时由 recipient-form.js 接管展示);成功则按 cart_type 处理反馈,见购物车篇。
低库存与库存文案。 inventory 区块仅在 inventory_management == 'shopify' 时输出内容:inventory_quantity > 0 且 <= inventory_threshold 为低库存,show_inventory_quantity 决定是否带数字;大于阈值为有货;数量不大于 0 时,inventory_policy == 'continue' 输出 inventory_out_of_stock_continue_selling(英文包中文案是「In stock」),否则为「Out of stock」。元素带 role="status",变体切换时被就地替换内容;内容为空时被标为 hidden。
取货。 pickup-availability 元素只在所选变体可售且存在开通取货的地点时带 available 属性并发起请求;renderPreview 把摘要放进原位、把门店抽屉追加到 body。请求失败则展示模板里的错误文案与刷新按钮;变体切换时不可售则清空。
与通用契约的一致与差距
| 通用要求 | Dawn 现状 |
|---|---|
| 购买动作落在一个具体变体,立即购买与加购同源 | 已做:payment_button 与加购在同一 form,切换时更新 id 输入;分期表单的 id 输入也同步更新。动态结账按钮如何响应该输入的变化由平台脚本决定,未核验 |
| 未选选项时给出提示,不默认选中 | 与契约不同:Dawn 始终预选 selected_or_first_available_variant(地址的 variant 参数可改变),不存在「未选」状态;只有选项组合无对应变体时才进入 setUnavailable |
| 不可用选项值说明原因 | 部分做到:按钮与色板保持可选,仅加 disabled 类或视觉禁用并附一句视觉隐藏的「Variant sold out or unavailable」;下拉项文字为「X - Unavailable」;不区分售罄与组合不存在 |
| 起价与选定价 | 商品页价格始终是所选或首个可售变体的价格,不显示「起价」(from_price_html 只在商品卡这类非变体目标时出现) |
| 售罄:说明、相邻有货选项、到货通知 | 只有说明:按钮禁用并显示「Sold out」,价格区可带售罄徽标;未做自动切换到相邻有货选项;到货通知见下节 |
| 数量越界提交前拦截 | 未见:表单带 novalidate,product-form.js 无数量校验,最终由服务端返回 422,description 显示在错误区 |
| 变体切换加载态、旧响应不覆盖新选择 | 部分:按钮先禁用,AbortController 取消旧请求;价格区没有加载态。请求失败只 console.error,按钮保持禁用且无用户提示(静态可推,未复现) |
| 价格或库存变化用状态消息 | 已做:价格容器与库存段落都带 role="status",就地更新 |
| 状态不只靠颜色 | 已做:低库存与有货都有文字,图标颜色写在行内样式里 |
| 销售计划选择器 | 未找到:检索 selling_plan、requires_selling_plan,仅购物车、通知与订单页显示计划名称,商品页没有选择器 |
| 无 JavaScript | 商品表单是真实 form,变体选项为 fieldset 带 js 类的单选控件;无 JS 下选项与价格能否联动、动态结账能否使用,未核验 |
| 库存词表:有货、低库存、售罄、缺货预订 | 缺货预订未区分:inventory_policy == 'continue' 且数量不大于 0 时文案与有货相同(英文「In stock」);预售、不可配送在主题中没有对应状态 |
| 低库存阈值来自事先定义的真实规则 | 阈值是 inventory 区块的 inventory_threshold(0 到 100 的滑杆,默认 10),比较用 <=,作用于全部变体,无按商品覆盖;阈值为 0 时低库存分支不可达。规则的书面定义与负责人不在主题内 |
| 追踪关闭时不显示数量 | 已做:inventory_management != 'shopify' 时不输出内容;continue 策略下的数量不大于 0 才显示「有货」文案 |
| 多地点口径 | 主题读 inventory_quantity,不区分地点;取货区按地点列出,口径见 库存与地点 |
| 取货:可用与不可用的地点说明 | 已做:pick_up_available_at_html、pick_up_unavailable_at_html,多地点提供「查看其他门店」;门店抽屉是 role="dialog"、aria-modal,靠 trapFocus 与 Escape,未使用 inert;取货信息加载失败时有重试按钮 |
| 到货通知订阅(邮箱、同意、去重) | 未内置:在固定提交的全仓库搜索中未找到相关实现(搜索词:notify、back in stock、back_in_stock、restock、waitlist、pre-order、preorder、email me,范围为检出内全部 366 个文件,含 sections、snippets、assets、templates、locales、config)。这只说明此版本主题没有内置,不表示平台或应用无法实现 |
补充两点:一是 buy-buttons.liquid 在隐藏 id 输入的 disabled 条件里引用了 quantity_rule_soldout,该变量到第 70 行才赋值,服务端首次渲染时这一项不参与判断;而 ProductForm 构造函数会把该输入的 disabled 设回 false,按钮自身的禁用条件在赋值之后,不受影响。二是 .sold-out-message 只存在于 card-product 的快速加购按钮里,商品页购买区被拒时走的是错误区,没有按钮内的售罄替换。
定制入口与风险
商家可在编辑器改:variant_picker 的 picker_type、swatch_shape;buy_buttons 的 show_dynamic_checkout、show_gift_card_recipient;inventory 的 text_style、inventory_threshold、show_inventory_quantity(需要先把区块加进模板);主题设置里的 sale_badge_color_scheme、sold_out_badge_color_scheme、currency_code_enabled。取货容器由代码固定输出(show_pickup_availability: true),没有区块开关。
耦合点:product-info.js 按 id 前缀(price-、Inventory-、Sku-、Volume-、Price-Per-Item-)在响应与页面间对位替换,改 id 或删块要同步;buy-buttons 与 price、product-variant-picker 同时被 featured-product 区段复用,改动会波及首页的精选商品;快速加购弹窗会移除 pickup-availability 并把含区段 id 的 id 改写为 quickadd- 前缀(assets/quick-add.js)。想增加到货通知或低库存的另一套规则,应新增区块与数据来源,而不是改写 inventory_threshold 的含义。
建议的验证范围
本篇均未执行:
- 多选项商品逐个切换,含不存在的组合与售罄变体,核对价格、库存文案、按钮文字与状态、URL 的
variant;快速连续切换后立刻加购,确认加入的变体。 - 把库存分别设为高于阈值、低于阈值、0,并分别开关「继续销售缺货商品」,核对
inventory区块文案;关闭库存追踪,确认该区块不显示。 - 只让非线上履约地点有货,核对取货摘要与价格区状态;刻意让取货请求失败,观察重试。
- 手工输入越界数量后加购,记录 422 与错误文案。
- 开关
show_dynamic_checkout,切换变体后检查动态结账按钮所购买的变体;礼品卡商品检查收件人表单与错误展示。 - 键盘与读屏软件走完「选项、库存播报、加购、错误」;关闭 JavaScript 核对表单可用性。