EN
Shopify 知识库 · 指南

Horizon 吸底加购与可售状态:库存文案、取货与加购反馈

基于 Horizon 4.2.0 静态源码,说明商品页吸底加购栏的显示条件、购买区可售与库存文案、门店取货可用性、加购成功与失败反馈,并对照通用契约列出缺口。
历史资料
请结合文中的适用版本和来源阅读。

本文基于 Horizon 4.2.0,源码固定到 5acd1b6(核验于 2026-10-07)。结论限于此提交,不表示其他版本行为相同;本篇为静态源码分析,未完成店铺运行验证。Dawn 上游已发布 v16.0.0(2026-08-10,GitHub Releases 核对),本库 Dawn 正文统一以 v15.3.0 为基线,升级前需重新核对;本库 Horizon 正文统一以 4.2.0 为基线(固定提交中 config/settings_schema.json 的 theme_version 为 4.2.0)。

对应哪些通用模式

映射购买区与库存状态与到货通知两个模块,展现形式为 Sticky Bar 与 Toast。此版本有吸底加购栏、库存文案与门店取货;在固定提交的全仓库搜索中未找到到货通知与 Toast(见文末搜索口径)。数量、加购提交与变体队列已在购买流程说明,变体切换见变体选择,吸底栏的存在与页面组合见商品页,本篇只补它们没写的显示条件、状态词与反馈链。

入口与文件

文件职责
sections/product-information.liquid输出吸底栏标记与样式,schema 设置 enable_sticky_add_to_cart(默认 true)
assets/sticky-add-to-cart.js显示/隐藏、变体同步、代点击主按钮
blocks/buy-buttons.liquid可售判断与按钮文案、错误位、liveRegion、门店取货
blocks/product-inventory.liquid、product-inventory.js库存状态文案(low / in_stock / out_of_stock)
assets/product-form.js、add-to-cart-button加购请求、错误与朗读、按钮动画
assets/local-pickup.js取货可用性随变体刷新
assets/header-actions.js、cart-drawer.js购物车数量朗读、可选的抽屉自动打开

数据从哪来

  • 按钮可售(buy-buttons.liquid):取 selected_or_first_available_variant;variant.available 为真显示 products.product.add_to_cart;否则在「inventory_management == 'shopify'、库存 ≤ 0、策略 deny」或 quantity_rule.min 大于库存时显示 sold_out(该 Liquid 条件混用 and / or,实际求值顺序未核验),其余为 unavailable(含无变体)。不可售时按钮 disabled,加速结账容器 hidden。
  • 库存块:product-inventory 只在 inventory_management == 'shopify' 时读 inventory_quantity。库存 > 0 且 ≤ inventory_threshold(默认 10)为低库存,show_inventory_quantity(默认 true)决定显示「N left」还是「Low stock」;库存 ≤ 0 且策略 continue 显示 In stock;未被追踪的变体只要存在即 In stock。默认 templates/product.json 不含该块,需商家添加。
  • 取货:variant.store_availabilities | where: 'pick_up_enabled', true;摘要只读取列表第一项,抽屉(theme-drawer 内的 dialog)列出全部地点。切换变体后 local-pickup.js 请求 商品地址?variant=&section_id= 并 morph。设置 show_pickup_availability 默认 true。
  • 加购请求:product-form.js 向 routes.cart_add_url + '.js' 提交 FormData,附带页面上 cart-items-component 的分区 ID;响应含 status 即视为失败。

交互怎样运行

  • 吸底栏:sticky-add-to-cart.js 用 IntersectionObserver 观察 .buy-buttons-block:主购买区滚出视口上方才显示;页脚与视口相交时隐藏(观察器 rootMargin 为 200px 0px 0px 0px);shopify-chat 已注册且页面有该元素时不显示;找不到 .buy-buttons-block 或 footer 时不初始化观察器。data-stuck 切换 transform / opacity。价格、标题、变体名来自服务端渲染,变体切换后从返回的 HTML morph 整条栏并同步 data-variant-available,数量变化时按钮文字追加「(n)」。
  • 吸底栏点击:给主表单的加购按钮设 data-puppet='true' 后调用 .click(),走同一条 handleSubmit;随后立即给自身设 data-added 并创建 fly-to-cart 元素,不等请求结果。
  • 反馈链:点击时 handleClick 即启动按钮的对勾动画(data-added,约 800 ms 后复位;add_to_cart_animation 开启且非快速购买弹窗时还有飞入购物车动画),此时请求尚未返回。成功后,隐藏的 role="status" 区域写入 Added(5 秒后清空),头部另有区域朗读 Total items in cart: N;auto_open_cart_drawer 开启(默认 false,schema 中仅在 cart_type == 'drawer' 时可见)且不在 /cart 页时,抽屉自动打开。失败后(含超上限的预检与 422 响应)把消息(预检为 quantity_error_max,422 为 response.message)写入购买按钮组上方的 .product-form-text__error 与同一朗读区,10 秒后隐藏。网络异常只 console.error 并派发 code 为 SERVICE_UNAVAILABLE 的 CartErrorEvent;已读到的监听者中,礼品卡表单只处理 INVALID,吸底栏只复位 data-puppet,均不向用户显示。

与通用契约的一致与差距

一致

  • 吸底栏与主购买区同源:价格、变体名、可售状态都来自同一次商品渲染与变体切换结果;栏本身有 role="region" 与名称(Quick add to cart bar);过渡放在 prefers-reduced-motion: no-preference 内,底边内距用 --safe-area-inset-bottom;页脚附近与聊天入口出现时收起,与 Sticky Bar 的避让要求对应。无 JS 时该栏保持屏外,页内原位按钮仍在。
  • 不可售选项在选择器里带 aria-disabled="true" 与「- Sold out」,下拉项带「- Unavailable」,不只靠颜色。
  • 库存追踪关闭时不显示数量;库存数字来自渲染时的 inventory_quantity,变体切换后随服务端结果替换。
  • 数量超限在提交前拦截并显示 quantity_error_max;422 的平台 message 原样显示,没有改成成功。

差距

  • 不可售时吸底栏整条隐藏(display: none),而不是显示同样的售罄状态,与 Sticky Bar「固定栏显示同样状态」不同;标记里的 Sold out 文案因此看不到。
  • 收起的栏仍可聚焦:栏收起只靠 opacity: 0 与屏外位移,未见 visibility、inert 或 aria-hidden,按静态阅读,按钮仍在 Tab 顺序与可访问性树中。窄屏(≤749px)按钮文字被 display: none,图标 SVG 为 aria-hidden,按钮没有 aria-label,可访问名称按代码为空。
  • 未见 scroll-padding:固定栏与固定页眉都没有为焦点滚动预留偏移,2.4.11 需实测。
  • 成功反馈先于结果:对勾与飞入动画在点击时启动,422 失败时已播放;吸底栏还会无条件播放;fly-to-cart.js 与其样式中未见 prefers-reduced-motion 判断。
  • 错误不常驻:错误 10 秒后自动隐藏,与 Toast「错误就近常驻」不同;只显示 message,不显示 description;吸底栏触发的失败出现在主购买区,而栏只在主区滚出视口后显示,用户可能看不到,只有朗读区会播报。
  • 朗读细节:成功文案只有 Added,不含商品与变体;朗读区同时写 role="status" 与 aria-live="assertive",读屏实际优先级未核验;product-inventory 的 role="status" 同时带 aria-label,是否覆盖内容未核验;无「加载中」状态,变体请求返回前继续显示上一变体的库存文字。
  • 状态词不全:库存 ≤ 0 但继续销售时显示 In stock,没有「缺货预订」;未使用 incoming、next_incoming_date;没有预售文案;也没有对买家所在地区不可配送的处理(未见对应错误码分支)。
  • 销售计划:selling_plan 只出现在 quick-add.liquid(有销售计划的商品一律转商品页)与购物车行显示,商品页购买区没有计划选择器与 selling_plan 输入,需应用块提供,未核验。
  • 取货摘要只读第一个地点,可能与抽屉内其他地点的可用性不一致。
  • 到货通知与 Toast:在固定提交的全仓库搜索中未找到到货通知与 Toast(搜索词见文末);cart_type 只有 page 与 drawer,没有弹出通知。email-signup 是通用 form 'customer' 表单,与变体无关。

定制入口与风险

编辑器可改(schema 设置名):enable_sticky_add_to_cart(商品信息 section);inventory_threshold、show_inventory_quantity(库存块);show_pickup_availability、stacking、gift_card_form(购买按钮块);主题设置 add_to_cart_animation、cart_type、auto_open_cart_drawer。文案在 products.product.* 与 content.inventory_* 等 locale 键。耦合点:吸底栏依赖 .buy-buttons-block、product-form-component[data-product-id]、[ref="addToCartButton"] 与页脚元素;data-puppet 由 product-form 与吸底栏共同读写;修改按钮标记后须同时核对 morph 与状态复位。featured-product-information 中未见吸底栏设置。

建议的验证范围

均未执行:在有货、低库存、售罄、库存 ≤ 0 继续销售、未追踪四种变体上检查库存块、按钮与吸底栏;不存在的选项组合下吸底栏是否隐藏;超上限与 422 失败时对勾动画、错误位与朗读的先后;从吸底栏触发失败后错误是否可见;窄屏用读屏检查吸底按钮名称;Tab 是否进入屏外收起的栏;禁用 JS 与开启减少动态偏好;多取货地点下摘要与抽屉是否一致。

搜索口径:对固定提交整个检出(含 sections、snippets、blocks、assets、templates、locales、config,含隐藏文件)用 rg -uui 搜索 notify、back in stock、back_in_stock、restock、waitlist、coming soon、backorder、toast、snackbar,均无命中;notification 仅命中 header-actions.js 的注释;selling_plan 命中见上。

源码基线