EN
Shopify 知识库 · 指南

Dawn 购物车:抽屉、加购通知与购物车页的更新链

基于 Dawn v15.3.0 源码,追踪 cart_type 设置如何在购物车抽屉、加购通知与购物车页之间分叉,梳理 Section Rendering 更新链、空车分支、数量更新与错误处理,并对照通用抽屉与 Toast 契约列出差距。
历史资料
请结合文中的适用版本和来源阅读。

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

对应哪些通用模式

覆盖 Drawer、Toast 与购物车摘要模块在 Dawn 上的实现。全局设置 cart_type(drawer / page / notification,默认 notification)决定加购后的反馈形态;三者共享 Section Rendering 与部分标记,不共享同一套组件。Horizon 的对应实现见 Horizon 购物车,两个主题的结论互不推断。

入口与文件

文件职责 / 何时输出
config/settings_schema.jsonCart 设置组:cart_type、show_vendor、show_cart_note、cart_drawer_collection、cart_color_scheme
layout/theme.liquidcart_type == 'drawer' 时 render 'cart-drawer'、加载抽屉 CSS 与 cart-drawer.js;始终输出 window.routes、window.cartStrings
sections/header.liquid图标链接 #cart-icon-bubble;cart_type == "notification" 时 render 'cart-notification';始终加载 cart-notification.js
snippets/cart-drawer.liquid、snippets/cart-notification.liquid抽屉外壳(条目、备注、小计、结账、空车块);通知面板骨架
sections/cart-drawer.liquid、sections/cart-notification-product.liquid无 schema,只作 Section Rendering 取回目标:前者一行 render 'cart-drawer',后者遍历整个购物车并给每行加上以 item.key 结尾的 id(前缀 cart-notification-product-);cart-notification-button、cart-icon-bubble、cart-live-region-text 同样无 schema
sections/main-cart-items.liquid、sections/main-cart-footer.liquid、templates/cart.json购物车页:cart-items 元素与页脚(区块 subtotal、buttons、@app);模板后接 featured-collection
assets/cart.jsCartItems(页面与抽屉共用基类)、CartRemoveButton、CartNote
assets/cart-drawer.js、assets/cart-notification.js、assets/product-form.js抽屉与通知的开合、内容替换;加购请求

数据从哪来

  • cart 对象:cart.items、item.final_line_price、cart.total_price、cart.cart_level_discount_applications,金额经 money 输出,小计标为 sections.cart.estimated_total;税费与运费说明按 cart.taxes_included、cart.duties_included、shop.shipping_policy 分支。
  • 数量规则:输入框的 data-min、max、step 来自 item.variant.quantity_rule。
  • 端点:window.routes 提供 cart_add_url、cart_change_url、cart_update_url、cart_url。加购与改量请求带 sections 与 sections_url(当前路径),在同一响应里取回分区 HTML;备注只 POST cart_update_url。
  • 购物车页的分区标识来自元素 data-id,即模板区段 key(cart-items、cart-footer;按 JSON 模板区段 id 的约定推断,未运行核验)。

三种入口怎样分叉

维度pagedrawernotification
加购后没有 cart-* 元素,product-form 直接 window.location = routes.cart_urlrenderContents 替换 #CartDrawer 与图标后 open()只取刚加入的一行(按响应 key),更新「查看购物车」文字与徽标后 open()
请求的分区无cart-drawer、cart-icon-bubblecart-notification-product、cart-notification-button、cart-icon-bubble
可改量页内抽屉内(cart-drawer-items 继承 CartItems)不可,需进购物车页
结账#checkout 加 content_for_additional_checkout_buttons(仅 main-cart-footer 使用)#CartDrawer-Checkout,未找到动态结账按钮面板内 form 提交 name="checkout"
图标普通链接点击 preventDefault 开抽屉,补 role="button"、aria-haspopup="dialog"普通链接

product-form.js 用 document.querySelector('cart-notification') || document.querySelector('cart-drawer') 选出 this.cart,所以形态完全取决于哪个元素被输出。

交互怎样运行

加购到展示。 onSubmitHandler 把 this.cart.getSectionsToRender() 的 id 放进 FormData;成功后先 publish(cartUpdate),再 this.cart.renderContents(response),用 response.sections[id] 替换 DOM;快速加购弹窗内则等 modalClosed 后再渲染。

开合。 抽屉 open() 加 animate active,transitionend 后 trapFocus(空车时圈定 .drawer__inner-empty),body 加 overflow-hidden;Escape 走 keyup,遮罩点击关闭,close() 把焦点还给触发者。通知 open() 后 notification.focus() 并 trapFocus,监听 body 点击,点到面板外即关闭;cart-notification.js 没有定时器,不自动消失。

改量与移除(页面、抽屉共用)。 change 冒泡到 CartItems,防抖 300ms 后 validateQuantity:按 data-min、max、step 客户端校验,文案复用 quickOrderListStrings;通过后进入 updateQuantity:给列表加 cart__items--disabled、显示行内加载图标、让焦点元素失焦,POST cart_change_url,按 parsedState.sections 替换分区,再 publish(cartUpdate, { source: 'cart-items' })。移除同样是 updateQuantity(line, 0);页面版移除控件是 url_to_remove 链接,被 JS preventDefault 接管。

其他入口触发的刷新。 CartItems 订阅 cartUpdate,来源非 cart-items 时 onCartUpdate:抽屉请求 cart_url?section_id=cart-drawer 并替换 cart-drawer-items 与 .cart-drawer__footer;页面请求 ?section_id=main-cart-items。因此抽屉模式下一次加购既执行 renderContents,又由订阅者再请求一次分区(静态阅读,未抓包)。

空车分支。 初始由 cart == empty 决定:抽屉输出 drawer__inner-empty(提示、关闭、「继续购物」、未登录的登录提示,以及 cart_drawer_collection 的集合卡片)并给 cart-drawer 加 is-empty;页面给 cart-items 加 is-empty,CSS 隐藏条目与页脚。删除最后一件后,updateQuantity 按 item_count === 0 切换两处 is-empty 并整体替换分区,抽屉聚焦空状态里的第一个 a。

与通用契约的一致与差距

通用要求Dawn 现状
模态抽屉有 role="dialog"、aria-modal、名称已做:.drawer__inner 带三者(名称为 aria-label);通知面板同样标为 dialog
背景真正 inert未做:全仓库搜索 inert(唯一命中是变量名 containerToTrapFocusOn 的子串)、showModal、<dialog 均无使用,背景靠遮罩、overflow-hidden 与手写 Tab 循环
焦点进入、循环、Escape、返还已做(trapFocus、keyup Escape、removeTrapFocus);改量后按 name 找回同一控件,删除后落到空状态链接或首个商品名
减少动态未找到:抽屉与通知的 CSS、JS 中无 prefers-reduced-motion(检索 reduced-motion,其余命中与购物车无关)
RTL 用起始 / 结束侧未见适配:抽屉固定 translateX(100%) 与 flex-end,未核验
Toast 不夺焦点、用 status / alert相反:加购通知是 role="dialog",打开即移动焦点并圈定,「已加入」未写进 role="status" 区域
Toast 可关闭、不因超时丢失不自动消失;关闭方式有按钮、「继续购物」、Escape、点击外部
连续加购合并同类未合并:只展示最新加入的一行,无累计数量
错误就近保留加购被拒(响应含 status)时 description 写入按钮上方 role="alert" 区域,不经通知;改量失败写入该行的 role="alert" 区域
金额、数量规则来自平台已做:金额来自 cart 与分区响应,cart.js、cart-drawer.js 无金额运算;规则输出自 quantity_rule,有规则或阶梯价时提供说明浮层
服务端夹紧后提示已做:响应里的行数量与输入值不同且行数未变时,显示 cart_quantity_error_html
折扣码、免运费进度未找到:检索 discount_code、coupon、free_shipping、shipping_rates,只有行级与购物车级折扣项展示;progress-bar 片段属于快速订购加载条
缺货或失效行标识未找到:条目模板仅在单位价处读 item.variant.available
无 JavaScript头部图标是 /cart 真实链接,页面版移除是链接;抽屉与通知需脚本;表单提交结果由平台决定,未核验

读源码时发现的疑点

均为静态阅读,未在浏览器复现:

  • 抽屉的数量输入框 id 是 Drawer-quantity-N,CartItems.resetQuantityInput 只查 #Quantity-N;客户端校验失败时 setValidity 会调用它,抽屉里预期找不到元素而报空引用。
  • 抽屉备注文本域没有 form 属性,靠 cart-note 防抖 input 事件保存;页面版有 form="cart"。点击结账前是否已写入,属时序问题,未核验。
  • 抽屉的 CartDrawer-LiveRegionText 服务端输出为空,分区列表里没有为它供文字的分区(页面版靠 cart-live-region-text 填充);抽屉 .totals 带 role="status",但位于被整体替换的 .drawer__inner 内,是否被朗读未核验。
  • 加购的 .catch 只 console.error,无面向用户的提示。
  • 相邻文件 quick-add-bulk.js 的 renderSections 对 CartDrawer 元素不判空,并把空车判断写成 items.length.length === 0;不在本文范围,仅提示回归。

定制入口与风险

商家可改 cart_type、供应商、备注开关、空抽屉的推荐集合与抽屉色板;购物车页的边距、配色在两个区段设置里,页脚区块为 subtotal、buttons、@app。耦合点:

  • #cart-icon-bubble 的标记在 header.liquid 与 sections/cart-icon-bubble.liquid 各一份,需同步。
  • getSectionsToRender() 的 id、selector 与 Liquid 元素 id(CartDrawer、cart-notification-product 等)一一对应,重命名会让替换静默失效。
  • 抽屉与页面是两套条目标记(CartDrawer-Item-N 与 CartItem-N,Drawer-quantity-N 与 Quantity-N),改条目内容要改两处。
  • 新增第四种反馈形态,需要实现 getSectionsToRender、renderContents、setActiveElement。

建议的验证范围

本篇均未执行:

  1. 三种 cart_type 下从商品页与快速加购加购,核对图标数量、面板内容、焦点与关闭后的焦点位置。
  2. 抽屉与页面里把数量改到规则之外,再改到超过库存,记录请求、响应、提示与输入值。
  3. 删除最后一件,检查空状态、焦点与两处 is-empty;空抽屉选定集合后核对卡片。
  4. 抓包确认抽屉模式下一次加购的请求数量与顺序。
  5. 键盘与读屏软件走完「打开、改量、删除、关闭」;开启系统减少动态观察动画;关闭 JavaScript 核对链接与结账入口。

源码基线