案例作品集 浏览精选项目

WESWOO 提供 Shopify Plus 项目开发费抵扣,至多 US$4,800

文章

Shopify 生鲜独立站,顾客能在列表页买齐一篮菜吗?

主题:, , ,

买番茄、鸡蛋、牛奶时,顾客往往一边比较规格,一边往购物篮里添东西。如果每买一件都要进详情页,再返回原来的位置,凑一篮日用品就变成了一串重复操作。

Orbix Studio 的 Freshmart 生鲜设计把两件事放到商品列表里做:用单位价帮助顾客比较规格,用原地步进器选择数量。买完鸡蛋,手指就可以直接滑到下一张商品卡。

对卖咖啡豆、零食、宠粮和日用品的 Shopify 店铺来说,可以沿着这条路径检查自己的货架。先让顾客找得到,再让他算得明白,最后让他看得清这一篮实际加进了什么。

原案的用户需求页把找货困难、浏览比较费力和结账步骤过多分开标示。公开成果页中的 92% 标为「购物体验改善」,页面没有给出结账耗时或结账完成率的测量口径。这里用可见的设计细节来拆解它。

一、先看 Freshmart 的四处设计

1. 森林绿负责常用操作,橙色留给促销

Freshmart 把森林鲜绿用于常见操作和选中状态,让数量步进器容易认出来。橙色面积很小,通常只出现于促销提醒。番茄、鸡蛋和牛奶用浅底卡片承载,商品本身的颜色就能区分货架。

  • 品牌绿 #16664B 用于选中状态和主要操作。
  • 橙色 #FF591F 用于促销提示,灰白 #F7F7F7 用于背景。
  • 原案品牌页标注 Neue Montreal。本文 Section 示例搭配 Newsreader 和 DM Sans。
Shopify 生鲜独立站,顾客能在列表页买齐一篮菜吗?
原案配色页,主绿 #16664B、橙色 #FF591F 和浅灰白 #F7F7F7 · Orbix Studio / Behance

2. 商品卡先让人看懂,再让人原地改数量

原案把番茄和鸡蛋并排放在手机列表上。两张卡都显示供货方与按千克计的单位价。番茄还是加购按钮,鸡蛋已经出现减号、数量和加号。买家能在同一屏比较两件商品,也能直接把鸡蛋改成两份。

商品卡上先写清商品名、当前规格、包价与单位价。以同款咖啡为例,500 克售 29 元,每 100 克是 5.80 元。750 克售 36 元,每 100 克是 4.80 元。两袋都写同一比较单位,买家就能判断哪袋更适合自己。

遇到多规格商品,数量控件跟随当前选中的变体。切换克重时一起更新包价、单位价和可售状态,避免出现卡片看的是一袋,购物车收到的是另一袋。

Shopify 生鲜独立站,顾客能在列表页买齐一篮菜吗?
手机商品列表的番茄卡仍是加购按钮,旁边鸡蛋卡已显示数量步进器 · Orbix Studio / Behance

3. 先按货架找货,再按购物目的筛选

原案的六张分类卡把蔬菜、水果、乳蛋、烘焙、肉鱼和饮品放在一起,卡片上写着商品数。首页还提供当季、新品、闪购、免邮会员和预制餐入口。两组入口分别解决「我要找哪类东西」和「我这次打算怎么买」。

Shopify 做法可以从集合开始。分类名称和数量跟着店内商品更新,换到下一类时,购物篮继续显示已经选过的商品。手机屏幕较窄时,让分类条能横向滑动,并明确当前选中项。

Shopify 生鲜独立站,顾客能在列表页买齐一篮菜吗?
六张货架分类卡分别标出蔬菜、水果、乳蛋、烘焙、肉鱼和饮品的商品数 · Orbix Studio / Behance

4. 履约条件和付款明细各有位置

原案在鸡胸肉详情页单独给出克重、现货提示和门店自提入口。做 Shopify 生鲜站时,可把备货、包装和配送说明放在顾客需要判断的位置。门店、邮编或送达时段会改变履约条件时,按顾客选定的地址或门店显示。

原案的订单摘要把商品、节省金额、运费和合计分行展示。店里可以用类似层级,让顾客在付款前看清优惠是否已计入、运费怎样算,以及实付金额是多少。

Shopify 生鲜独立站,顾客能在列表页买齐一篮菜吗?
鸡胸肉详情页的规格选择、现货提示与门店自提入口 · Orbix Studio / Behance
Shopify 生鲜独立站,顾客能在列表页买齐一篮菜吗?
原案订单摘要局部,商品、节省金额、运费和合计分行排列 · Orbix Studio / Behance

二、在 Shopify 里接入单位计价和凑单

在 Shopify 后台进入「商品 → 商品或变体 → 定价 → 单位价格」,填总净含量和基准计价单位。主题的列表卡拿到当前选中的变体后,可以用 unit_price_with_measurement 输出单位价。

净含量来自计价配置或明确的规格字段。展示现货余量时,先核对库存是否启用追踪,再确认顾客选择的门店或配送范围。variant.available 适合控制是否可加购。店内允许缺货继续销售时,页面还应说明预计履约时间。

页面信息 Shopify 中对应的来源 显示时核对
产地或供应商 product.vendor 或店内单独维护的产地字段 供应商与产地按业务含义分别管理
规格与净含量 变体选项或单位计价里的总净含量 包价与净含量来自同一个变体
每 100 克、每千克或每升的价格 variant.unit_price 和 variant.unit_price_measurement 同类商品用同一个基准量比较
可购买状态 variant.available 变体可售还受库存策略影响
已应用折扣与应付金额 Shopify 购物车及结账回执 区分划线价差、购物车折扣与运费
{%- assign variant = product.selected_or_first_available_variant -%}
{%- if variant.unit_price_measurement -%}
  <span class="fm-unit-price">
    {{ variant.unit_price | unit_price_with_measurement: variant.unit_price_measurement }}
  </span>
{%- endif -%}

数量选择和实际加购的交界

列表页的数量先进入本次待选清单。顾客点汇总按钮时,按有效变体 ID 和数量生成 items,用当前语言路径的 cart/add.js 一次提交。按钮在请求期间保持禁用,避免多点几次把同一批商品加进去。

库存不足时,Shopify 可能先加入可用数量,再返回 422。收到错误后读取 cart.js,对照待选数量和当前购物车,提示买家需要调整哪件商品。用户已有同款时,核对实际购物车行。调整已存在行可按行键用 cart/change.js。

折扣和免邮以店铺规则、配送地址和结账回执为准。底部可以显示暂估金额,实际购物车回执回来后再刷新对应的总价和优惠。

async function addSelection(items) {
  const root = window.Shopify.routes.root;
  let message = '';

  try {
    const response = await fetch(root + 'cart/add.js', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ items })
    });
    if (!response.ok) {
      const error = await response.json().catch(() => null);
      message = error?.description || '有商品未按所选数量加入。';
    }
  } catch {
    message = '未收到加购确认,请查看当前购物车。';
  }

  const currentCart = await fetch(root + 'cart.js');
  if (!currentCart.ok) throw new Error('暂时无法读取购物车');
  return { cart: await currentCart.json(), message };
}

原稿中的完整 Shopify Section

下面保留原稿的完整 Section,用来查看卡片、分类和购物篮的交互结构。示例中的价格、库存说明与免邮阈值来自手填设置。正式店铺应接入选中变体及实际配送规则。原示例的请求完成后会直接跳结账页,接入时按前面的回执处理加购成功、缺货和连接中断。

展开完整代码 · 803 行
{%- comment -%}
  文件:sections/freshmart-aisle-quickshop.liquid
  设计参考:Freshmart Grocery Shopping | Orbix Studio | Web app
  功能:高频快消/食品生鲜快速凑单货架,含带计数的分类胶囊、单位计价外露、原地变形步进器(- 1 +)与实时凑单省钱条
{%- endcomment -%}

<link rel="preconnect" href="https://fonts.googleAPIs.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Newsreader:ital,opsz,wght@0,6..72,500;0,6..72,600;1,6..72,500&display=swap" rel="stylesheet">

<style>
  .fm-sec-{{ section.id }} {
    --fm-green: #16664B;
    --fm-forest: #1E2B23;
    --fm-orange: #FF591F;
    --fm-lime: #B4EE76;
    --fm-bg-lynx: #F7F7F7;
    --fm-bg-cream: #F3F2EC;
    --fm-ink: #000000;
    --fm-bauhaus: #404040;
    --fm-border: #E5E5E0;
    --fm-font-serif: 'Newsreader', Georgia, serif;
    --fm-font-sans: 'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif;

    background-color: #FFFFFF;
    color: var(--fm-ink);
    font-family: var(--fm-font-sans);
    padding: {{ section.settings.padding_top }}px 0 {{ section.settings.padding_bottom }}px;
  }

  /* 原案 1400px 最大网格宽度 */
  .fm-container {
    max-width: 1400px;
    margin: 0 auto;
    padding: 0 24px;
  }

  /* 顶部横幅与标题区 */
  .fm-top-banner {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: flex-end;
    gap: 20px;
    margin-bottom: 32px;
  }
  .fm-promo-tag {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background-color: var(--fm-lime);
    color: var(--fm-forest);
    font-size: 0.75rem;
    font-weight: 700;
    padding: 6px 14px;
    border-radius: 999px;
    margin-bottom: 12px;
  }
  .fm-promo-tag span {
    background-color: var(--fm-orange);
    color: #FFFFFF;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 0.6875rem;
  }
  .fm-heading {
    font-family: var(--fm-font-serif);
    font-size: clamp(2rem, 3.8vw, 3.25rem);
    font-weight: 500;
    line-height: 1.1;
    margin: 0;
    color: var(--fm-ink);
  }
  .fm-heading em {
    font-style: italic;
    color: var(--fm-green);
  }
  .fm-trust-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
  }
  .fm-trust-pill {
    background-color: var(--fm-bg-cream);
    color: var(--fm-bauhaus);
    font-size: 0.8125rem;
    font-weight: 600;
    padding: 8px 14px;
    border-radius: 999px;
  }

  /* 带商品数的货架分类胶囊栏 */
  .fm-aisle-bar {
    display: flex;
    gap: 12px;
    overflow-x: auto;
    padding-bottom: 12px;
    margin-bottom: 32px;
  }
  .fm-aisle-btn {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    background-color: var(--fm-bg-lynx);
    color: var(--fm-ink);
    border: 1.5px solid transparent;
    padding: 12px 18px;
    border-radius: 16px;
    font-family: var(--fm-font-sans);
    font-size: 0.9375rem;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    transition: all 0.2s ease;
  }
  .fm-aisle-btn:hover {
    border-color: var(--fm-green);
  }
  .fm-aisle-btn.is-active {
    background-color: var(--fm-green);
    color: #FFFFFF;
  }
  .fm-aisle-count {
    font-size: 0.75rem;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 999px;
    background-color: #FFFFFF;
    color: var(--fm-green);
  }
  .fm-aisle-btn.is-active .fm-aisle-count {
    background-color: var(--fm-lime);
    color: var(--fm-forest);
  }

  /* 12 列商品货架网格(桌面 4 列,平板 2 列,手机 2 列紧凑模式) */
  .fm-product-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 20px;
  }
  @media screen and (max-width: 1024px) {
    .fm-product-grid {
      grid-template-columns: repeat(3, 1fr);
    }
  }
  @media screen and (max-width: 768px) {
    .fm-product-grid {
      grid-template-columns: repeat(2, 1fr);
      gap: 12px;
    }
  }

  /* 单张生鲜/快消商品卡 */
  .fm-card {
    background-color: var(--fm-bg-lynx);
    border: 1px solid var(--fm-border);
    border-radius: 20px;
    padding: 16px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    position: relative;
    transition: transform 0.2s ease, box-shadow 0.2s ease;
  }
  .fm-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 10px 24px rgba(22, 102, 75, 0.08);
  }
  .fm-card[hidden] {
    display: none !important;
  }

  /* 卡片顶部:折扣橙色标签 + 现货库存余量 */
  .fm-card-top {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
  }
  .fm-sale-badge {
    background-color: var(--fm-orange);
    color: #FFFFFF;
    font-size: 0.6875rem;
    font-weight: 700;
    padding: 3px 9px;
    border-radius: 999px;
  }
  .fm-stock-badge {
    font-size: 0.6875rem;
    font-weight: 600;
    color: var(--fm-green);
    background: rgba(22, 102, 75, 0.1);
    padding: 3px 9px;
    border-radius: 999px;
    margin-left: auto;
  }

  .fm-img-box {
    aspect-ratio: 4 / 3;
    background-color: #FFFFFF;
    border-radius: 14px;
    overflow: hidden;
    margin-bottom: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .fm-img-box img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    padding: 10px;
    box-sizing: border-box;
  }

  .fm-vendor {
    font-size: 0.75rem;
    color: var(--fm-bauhaus);
    margin: 0 0 4px;
  }
  .fm-item-title {
    font-size: 1.0625rem;
    font-weight: 700;
    margin: 0 0 4px;
    color: var(--fm-ink);
  }
  .fm-unit-spec {
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--fm-green);
    margin: 0 0 14px;
  }

  /* 底部价格与原地变形加购步进器 */
  .fm-card-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
    padding-top: 12px;
    border-top: 1px solid var(--fm-border);
  }
  .fm-price-group {
    display: flex;
    flex-direction: column;
  }
  .fm-price-current {
    font-size: 1.125rem;
    font-weight: 700;
    color: var(--fm-ink);
  }
  .fm-price-compare {
    font-size: 0.75rem;
    color: #888888;
    text-decoration: line-through;
  }

  /* 状态 A:未加购时的描边胶囊按钮 (+ Add to Cart) */
  .fm-add-btn {
    background-color: #FFFFFF;
    color: var(--fm-green);
    border: 1.5px solid var(--fm-green);
    padding: 8px 14px;
    border-radius: 999px;
    font-size: 0.8125rem;
    font-weight: 700;
    cursor: pointer;
    transition: all 0.2s ease;
  }
  .fm-add-btn:hover {
    background-color: var(--fm-green);
    color: #FFFFFF;
  }

  /* 状态 B:加购后原地变形为深绿椭圆步进器 (- 1 +) */
  .fm-stepper {
    display: none;
    align-items: center;
    background-color: var(--fm-green);
    color: #FFFFFF;
    border-radius: 999px;
    padding: 4px 6px;
    gap: 10px;
  }
  .fm-card.in-basket .fm-add-btn {
    display: none;
  }
  .fm-card.in-basket .fm-stepper {
    display: inline-flex;
  }
  .fm-step-btn {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: none;
    background: rgba(255, 255, 255, 0.18);
    color: #FFFFFF;
    font-size: 1rem;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .fm-step-btn:hover {
    background: var(--fm-lime);
    color: var(--fm-forest);
  }
  .fm-step-qty {
    font-size: 0.875rem;
    font-weight: 700;
    min-width: 18px;
    text-align: center;
  }

  /* 底部实时凑单与省钱结算栏(Basket full. Worry zero.) */
  .fm-basket-bar {
    margin-top: 32px;
    background-color: var(--fm-forest);
    color: #FFFFFF;
    border-radius: 20px;
    padding: 20px 28px;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 20px;
  }
  .fm-saving-pill {
    display: inline-block;
    background-color: var(--fm-orange);
    color: #FFFFFF;
    font-size: 0.75rem;
    font-weight: 700;
    padding: 4px 10px;
    border-radius: 999px;
    margin-left: 8px;
  }
  .fm-checkout-btn {
    background-color: var(--fm-lime);
    color: var(--fm-forest);
    border: none;
    padding: 14px 28px;
    border-radius: 999px;
    font-size: 0.9375rem;
    font-weight: 700;
    cursor: pointer;
    transition: transform 0.2s ease;
  }
  .fm-checkout-btn:hover {
    transform: translateY(-1px);
  }
</style>

<section class="fm-sec-{{ section.id }}" data-section-id="{{ section.id }}">
  <div class="fm-container">
    <div class="fm-top-banner">
      <div>
        <div class="fm-promo-tag">
          <span>-10% FRESH DEALS</span>
          {{ section.settings.promo_text }}
        </div>
        <h2 class="fm-heading">
          {{ section.settings.heading_main }} <em>{{ section.settings.heading_italic }}</em>
        </h2>
      </div>
      <div class="fm-trust-pills">
        <span class="fm-trust-pill">⚡ Express Delivery &lt; 60 Mins</span>
        <span class="fm-trust-pill">🌱 01 Harvest → 04 Doorstep</span>
        <span class="fm-trust-pill">🚚 Free Delivery Over ${{ section.settings.free_shipping_threshold }}</span>
      </div>
    </div>

    {%- comment -%} 带商品计数的六大货架胶囊导航 {%- endcomment -%}
    <div class="fm-aisle-bar" role="tablist" aria-label="生鲜货架分类">
      <button type="button" class="fm-aisle-btn is-active" data-aisle="all">
        <span>All Fresh Picks</span>
        <span class="fm-aisle-count">{{ section.blocks.size }}</span>
      </button>
      <button type="button" class="fm-aisle-btn" data-aisle="vegetables">
        <span>🥬 Fresh Vegetables</span>
      </button>
      <button type="button" class="fm-aisle-btn" data-aisle="fruits">
        <span>🍓 Seasonal Fruits</span>
      </button>
      <button type="button" class="fm-aisle-btn" data-aisle="dairy">
        <span>🥛 Dairy &amp; Eggs</span>
      </button>
      <button type="button" class="fm-aisle-btn" data-aisle="bakery">
        <span>🥐 Bakery &amp; Pantry</span>
      </button>
    </div>

    <div class="fm-product-grid">
      {%- for block in section.blocks -%}
        {%- assign product = block.settings.product -%}
        {%- assign variant = product.selected_or_first_available_variant -%}
        <article
          class="fm-card {% if block.settings.default_qty > 0 %}in-basket{% endif %}"
          data-aisle-cat="{{ block.settings.aisle_category }}"
          data-variant-id="{{ variant.id }}"
          data-price="{{ block.settings.price_sale }}"
          data-compare="{{ block.settings.price_compare }}"
          data-qty="{{ block.settings.default_qty }}"
          {{ block.shopify_attributes }}
        >
          <div>
            <div class="fm-card-top">
              {%- if block.settings.sale_badge != blank -%}
                <span class="fm-sale-badge">{{ block.settings.sale_badge }}</span>
              {%- endif -%}
              <span class="fm-stock-badge">{{ block.settings.stock_label }}</span>
            </div>

            <div class="fm-img-box">
              {%- if block.settings.custom_image != blank -%}
                <img
                  src="{{ block.settings.custom_image | image_url: width: 600 }}"
                  alt="{{ block.settings.item_title | escape }}"
                  width="600"
                  height="450"
                  loading="lazy"
                >
              {%- elsif product != blank and product.featured_image != blank -%}
                <img
                  src="{{ product.featured_image | image_url: width: 600 }}"
                  alt="{{ product.title | escape }}"
                  width="600"
                  height="450"
                  loading="lazy"
                >
              {%- else -%}
                <span style="font-size:2.5rem;">{{ block.settings.emoji_placeholder }}</span>
              {%- endif -%}
            </div>

            <p class="fm-vendor">{{ block.settings.vendor_name }}</p>
            <h3 class="fm-item-title">{{ block.settings.item_title | default: product.title }}</h3>
            <p class="fm-unit-spec">{{ block.settings.unit_price_label }}</p>
          </div>

          <div class="fm-card-footer">
            <div class="fm-price-group">
              <span class="fm-price-current">${{ block.settings.price_sale }}</span>
              {%- if block.settings.price_compare != blank -%}
                <span class="fm-price-compare">${{ block.settings.price_compare }}</span>
              {%- endif -%}
            </div>

            <button type="button" class="fm-add-btn js-fm-add">+ Add to Cart</button>

            <div class="fm-stepper">
              <button type="button" class="fm-step-btn js-fm-minus" aria-label="减少数量">−</button>
              <span class="fm-step-qty js-fm-qty">{{ block.settings.default_qty }}</span>
              <button type="button" class="fm-step-btn js-fm-plus" aria-label="增加数量">+</button>
            </div>
          </div>
        </article>
      {%- endfor -%}
    </div>

    {%- comment -%} 底部实时凑单与优惠汇总栏 {%- endcomment -%}
    <div class="fm-basket-bar">
      <div>
        <div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:0.08em;color:var(--fm-lime);margin-bottom:4px;">
          BASKET FULL · WORRY ZERO
        </div>
        <div style="font-size:1.125rem;font-weight:700;">
          <span id="FmBasketTotal-{{ section.id }}">已选 0 件 · 合计 $0.00</span>
          <span class="fm-saving-pill" id="FmSavingBadge-{{ section.id }}">Your saving: -$0.00</span>
        </div>
        <div style="font-size:0.8125rem;opacity:0.82;margin-top:4px;" id="FmFreeShipNote-{{ section.id }}"></div>
      </div>

      <button
        type="button"
        class="fm-checkout-btn"
        id="FmSyncCartBtn-{{ section.id }}"
        data-threshold="{{ section.settings.free_shipping_threshold }}"
      >
        Proceed to Fast Checkout →
      </button>
    </div>
  </div>
</section>

<script>
  (function() {
    var sec = document.querySelector('.fm-sec-{{ section.id }}');
    if (!sec) return;

    var aisleBtns = sec.querySelectorAll('[data-aisle]');
    var cards = sec.querySelectorAll('.fm-card');
    var totalEl = sec.querySelector('#FmBasketTotal-{{ section.id }}');
    var savingEl = sec.querySelector('#FmSavingBadge-{{ section.id }}');
    var shipNoteEl = sec.querySelector('#FmFreeShipNote-{{ section.id }}');
    var syncBtn = sec.querySelector('#FmSyncCartBtn-{{ section.id }}');
    var freeThreshold = parseFloat(syncBtn.getAttribute('data-threshold') || '50');

    /* 货架分类筛选 */
    aisleBtns.forEach(function(btn) {
      btn.addEventListener('click', function() {
        var cat = btn.getAttribute('data-aisle');
        aisleBtns.forEach(function(b) { b.classList.toggle('is-active', b === btn); });
        cards.forEach(function(c) {
          if (cat === 'all' || c.getAttribute('data-aisle-cat') === cat) {
            c.removeAttribute('hidden');
          } else {
            c.setAttribute('hidden', 'hidden');
          }
        });
      });
    });

    /* 重新计算购物篮总价、已省金额(Your saving)与免邮差额 */
    function recalcBasket() {
      var itemCount = 0;
      var subtotal = 0;
      var saved = 0;

      cards.forEach(function(card) {
        var qty = parseInt(card.getAttribute('data-qty') || '0', 10);
        var price = parseFloat(card.getAttribute('data-price') || '0');
        var compare = parseFloat(card.getAttribute('data-compare') || '0');
        if (qty > 0) {
          itemCount += qty;
          subtotal += price * qty;
          if (compare > price) {
            saved += (compare - price) * qty;
          }
        }
      });

      totalEl.textContent = '已选 ' + itemCount + ' 件商品 · Subtotal: $' + subtotal.toFixed(2);
      savingEl.textContent = 'Your saving: -$' + saved.toFixed(2);

      if (subtotal >= freeThreshold) {
        shipNoteEl.textContent = '🎉 已满 $' + freeThreshold.toFixed(0) + ',本单享免费极速配送(Free Express Delivery Unlocked)!';
      } else {
        var diff = (freeThreshold - subtotal).toFixed(2);
        shipNoteEl.textContent = '再凑 $' + diff + ' 即可解锁满 $' + freeThreshold.toFixed(0) + ' 免运费';
      }
    }

    /* 绑定每张卡片的原地变形步进器 (+ Add to Cart <-> - 1 +) */
    cards.forEach(function(card) {
      var addBtn = card.querySelector('.js-fm-add');
      var minusBtn = card.querySelector('.js-fm-minus');
      var plusBtn = card.querySelector('.js-fm-plus');
      var qtySpan = card.querySelector('.js-fm-qty');

      function setQty(newQty) {
        var q = Math.max(0, newQty);
        card.setAttribute('data-qty', String(q));
        qtySpan.textContent = String(q);
        card.classList.toggle('in-basket', q > 0);
        recalcBasket();
      }

      addBtn.addEventListener('click', function() { setQty(1); });
      plusBtn.addEventListener('click', function() {
        var cur = parseInt(card.getAttribute('data-qty') || '0', 10);
        setQty(cur + 1);
      });
      minusBtn.addEventListener('click', function() {
        var cur = parseInt(card.getAttribute('data-qty') || '0', 10);
        setQty(cur - 1);
      });
    });

    recalcBasket();

    /* 批量写入 Shopify 原生购物车 */
    syncBtn.addEventListener('click', function() {
      var items = [];
      cards.forEach(function(card) {
        var qty = parseInt(card.getAttribute('data-qty') || '0', 10);
        var variantId = card.getAttribute('data-variant-id');
        if (qty > 0 && variantId) {
          items.push({ id: Number(variantId), quantity: qty });
        }
      });
      if (items.length === 0) {
        window.location.href = '/cart';
        return;
      }
      fetch(window.Shopify.routes.root + 'cart/add.js', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ items: items })
      }).then(function() {
        window.location.href = '/checkout';
      });
    });
  })();
</script>

{% schema %}
{
  "name": "Freshmart 快消步进凑单货架",
  "tag": "section",
  "settings": [
    {
      "type": "text",
      "id": "promo_text",
      "label": "顶部促销条文字",
      "default": "Get our best picks every Thursday · New Here? Enjoy 10% OFF Your First Order"
    },
    {
      "type": "text",
      "id": "heading_main",
      "label": "主标题前段",
      "default": "Everything Fresh,"
    },
    {
      "type": "text",
      "id": "heading_italic",
      "label": "主标题斜体部分",
      "default": "All in One Place."
    },
    {
      "type": "number",
      "id": "free_shipping_threshold",
      "label": "免运费门槛金额 ($)",
      "default": 50
    },
    {
      "type": "range",
      "id": "padding_top",
      "min": 24,
      "max": 120,
      "step": 4,
      "unit": "px",
      "label": "顶部内边距",
      "default": 56
    },
    {
      "type": "range",
      "id": "padding_bottom",
      "min": 24,
      "max": 120,
      "step": 4,
      "unit": "px",
      "label": "底部内边距",
      "default": 56
    }
  ],
  "blocks": [
    {
      "type": "grocery_item",
      "name": "快消/生鲜单品卡",
      "settings": [
        {
          "type": "product",
          "id": "product",
          "label": "关联 Shopify 商品"
        },
        {
          "type": "select",
          "id": "aisle_category",
          "label": "所属货架分类",
          "options": [
            { "value": "vegetables", "label": "Fresh Vegetables(鲜蔬)" },
            { "value": "fruits", "label": "Seasonal Fruits(水果)" },
            { "value": "dairy", "label": "Dairy & Eggs(乳品蛋类)" },
            { "value": "bakery", "label": "Bakery & Pantry(烘焙粮油)" }
          ],
          "default": "vegetables"
        },
        {
          "type": "image_picker",
          "id": "custom_image",
          "label": "自定义商品去底图"
        },
        {
          "type": "text",
          "id": "emoji_placeholder",
          "label": "未传图时的占位图标",
          "default": "🍅"
        },
        {
          "type": "text",
          "id": "sale_badge",
          "label": "左上角橙色促销标签",
          "default": "-20% Deal"
        },
        {
          "type": "text",
          "id": "stock_label",
          "label": "右上角库存余量标签",
          "default": "In stock (100 Kg)"
        },
        {
          "type": "text",
          "id": "vendor_name",
          "label": "货源/农场标签",
          "default": "Local Farmers"
        },
        {
          "type": "text",
          "id": "item_title",
          "label": "商品名称",
          "default": "Organic Red Tomatoes"
        },
        {
          "type": "text",
          "id": "unit_price_label",
          "label": "单位换算单价说明",
          "default": "$1.33 / per kg · 1kg Pack"
        },
        {
          "type": "text",
          "id": "price_sale",
          "label": "当前售价 ($)",
          "default": "16.00"
        },
        {
          "type": "text",
          "id": "price_compare",
          "label": "原价 ($,用于计算 Your saving)",
          "default": "20.00"
        },
        {
          "type": "number",
          "id": "default_qty",
          "label": "默认演示加购数量",
          "default": 1
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Freshmart 快消步进凑单货架",
      "blocks": [
        {
          "type": "grocery_item",
          "settings": {
            "aisle_category": "vegetables",
            "emoji_placeholder": "🍅",
            "sale_badge": "-20% Deal",
            "stock_label": "In stock (100 Kg)",
            "vendor_name": "Local Farmers",
            "item_title": "Organic Red Tomatoes (Vine Pack)",
            "unit_price_label": "$1.33 / per kg · 01 kg",
            "price_sale": "16.00",
            "price_compare": "20.00",
            "default_qty": 1
          }
        },
        {
          "type": "grocery_item",
          "settings": {
            "aisle_category": "dairy",
            "emoji_placeholder": "🥚",
            "sale_badge": "-20% Deal",
            "stock_label": "In stock (80 Boxes)",
            "vendor_name": "Local Farmers",
            "item_title": "Pasture-Raised Farm Eggs",
            "unit_price_label": "$4.80 / per 12 pcs · Family Pack",
            "price_sale": "16.00",
            "price_compare": "20.00",
            "default_qty": 1
          }
        },
        {
          "type": "grocery_item",
          "settings": {
            "aisle_category": "fruits",
            "emoji_placeholder": "🍇",
            "sale_badge": "Peak Quality",
            "stock_label": "In stock (65 Kg)",
            "vendor_name": "Local Farmers",
            "item_title": "Seedless Green Grapes",
            "unit_price_label": "$1.60 / per 100g · 1kg Box",
            "price_sale": "16.00",
            "price_compare": "20.00",
            "default_qty": 1
          }
        },
        {
          "type": "grocery_item",
          "settings": {
            "aisle_category": "dairy",
            "emoji_placeholder": "🥛",
            "sale_badge": "Daily Essential",
            "stock_label": "In stock (42 Bottles)",
            "vendor_name": "Local Farmers",
            "item_title": "Glass-Bottled Whole Milk & Greek Yoghurt",
            "unit_price_label": "$3.20 / per 1L · Chilled Tote",
            "price_sale": "12.00",
            "price_compare": "15.00",
            "default_qty": 0
          }
        }
      ]
    }
  ]
}
{% endschema %}

三、把生鲜货架放回五个案例里看

家居、B2B、潮牌和银饰的列表页,通常需要给顾客更多比较或判断的空间。快消货架的重点是跨分类找货、看懂规格和连续选多件。下表是五个设计案例的交互对照,价格是品类场景示意。

独立站原型与代表案例 客单价与购买频次 核心色彩心理学 商品卡片最关键的 1 个设计细节 Shopify 可借鉴的交互
1. 画廊静奢与 B2B 选品型
NM Art & Design
极高客单价(€1,000+)
低频收藏/工程采购
浅暖灰 #A29A91 + 局部暖铜色 极简留白,隐去促销标签,突出艺术家与材质年份 现货 Add to Bag 与定制款 Enquire 询盘抽屉并存 + 导出 PDF 选品单
2. 硬科技与 B2B 方案型
Volenta
高客单价($500–$5,000+)
理性比选决策
哑光黑 #0B0D0C + 电光青柠绿 #C6FF33 12 列 Bento 数据矩阵(+63%、4X)+ [ + TAG ] 路标 全页唯一高亮青柠绿 CTA(直接加购或预约 15 分钟工程师选型)
3. 潮流视觉与大片驱动型
Born Agency
中高客单价($120–$600)
季度上新/Drop 驱动
暗房炭黑 #1C1C1C + 电光绿/洋红双渐变 相机取景框准星(Viewfinder HUD)标注局部工艺细节 CAROUSEL(大片横滑)与 GALLERY(细线网格)前端一键无刷新切换
4. 重质感工艺配饰型
CODESTAL 男士银饰
中高客单价($150–$900)
重材质分量与身份感
100% 无彩色(#000000 / #1A1A1A / #383838 / #FFFFFF) 外露斜杠档案编号(UGS28\36\2023\1)+ 精确克重(4.10 g) 一级导航设 PRODUCT CARE + 弹窗切换 SPECIFICATIONS / WARRANTY
5. 高频快消与多件凑单型
Freshmart 生鲜食品
低单品价($2–$40)
高频复购、一次选多件
森林鲜绿 #16664B + 活力柑橘橙 #FF591F + 云白 #F7F7F7 外露单位换算价($1.33 / per kg)+ 可售状态 卡片原地变形步进器(+ Add ↔ - 1 +) + 底部汇总本篮金额与配送条件

图源为 Orbix Studio 的 Freshmart 原案。开发参考 Shopify 单位计价、Liquid 单位价过滤器和 Ajax Cart API。

Random Image 608
Random Image 608

WESWOO - 跨境独立站开发专家

助力中国品牌出海,我们为您提供专业的独立站搭建和Shopify Plus集成技术服务。精准的跨境电商解决方案,助力品牌在全球市场成功落地,轻松跨越国界。

  • shopify独立站品牌化
  • 海外UI视觉设计
  • SNS多渠道品牌营销
浏览 WESWOO 案例作品集