主题开发实战
本指南将通过实际项目案例,深入介绍 Shopify 主题开发的最佳实践、架构设计和具体实现技巧。
项目架构设计
1. 主题文件结构规划
theme/
├── assets/                     # 静态资源
│   ├── styles/
│   │   ├── base/              # 基础样式
│   │   │   ├── reset.css
│   │   │   ├── variables.css
│   │   │   └── typography.css
│   │   ├── components/        # 组件样式
│   │   │   ├── buttons.css
│   │   │   ├── cards.css
│   │   │   └── forms.css
│   │   ├── layout/           # 布局样式
│   │   │   ├── header.css
│   │   │   ├── footer.css
│   │   │   └── grid.css
│   │   └── pages/            # 页面特定样式
│   │       ├── product.css
│   │       ├── collection.css
│   │       └── cart.css
│   ├── scripts/
│   │   ├── modules/          # 功能模块
│   │   │   ├── cart.js
│   │   │   ├── product.js
│   │   │   └── search.js
│   │   ├── utilities/        # 工具函数
│   │   │   ├── dom.js
│   │   │   ├── api.js
│   │   │   └── helpers.js
│   │   └── main.js          # 主入口文件
│   └── images/              # 图片资源
├── config/                  # 配置文件
├── layout/                  # 布局模板
├── sections/               # 分区文件
│   ├── header.liquid
│   ├── footer.liquid
│   ├── product-hero.liquid
│   ├── product-recommendations.liquid
│   ├── collection-filters.liquid
│   └── newsletter-signup.liquid
├── snippets/               # 代码片段
│   ├── components/         # UI组件
│   │   ├── product-card.liquid
│   │   ├── price-display.liquid
│   │   ├── quantity-selector.liquid
│   │   └── variant-selector.liquid
│   ├── utilities/          # 工具片段
│   │   ├── image-responsive.liquid
│   │   ├── social-sharing.liquid
│   │   └── breadcrumbs.liquid
│   └── icons/             # 图标片段
│       ├── icon-cart.liquid
│       ├── icon-search.liquid
│       └── icon-arrow.liquid
└── templates/             # 模板文件2. 组件化开发策略
<!-- snippets/components/product-card.liquid -->
{% comment %}
  产品卡片组件
  
  参数:
  - product: 产品对象 (必需)
  - image_size: 图片尺寸 (可选, 默认: '300x300')
  - show_vendor: 是否显示品牌 (可选, 默认: false)
  - show_price: 是否显示价格 (可选, 默认: true)
  - css_class: 自定义CSS类 (可选)
  - lazy_load: 是否懒加载图片 (可选, 默认: true)
{% endcomment %}
 
{% assign image_size = image_size | default: '300x300' %}
{% assign show_vendor = show_vendor | default: false %}
{% assign show_price = show_price | default: true %}
{% assign lazy_load = lazy_load | default: true %}
 
<article class="product-card {{ css_class }}" data-product-id="{{ product.id }}">
  <!-- 产品图片 -->
  <div class="product-card__image">
    <a href="{{ product.url }}" aria-label="{{ product.title }}">
      {% if product.featured_image %}
        {% if lazy_load %}
          <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
               data-src="{{ product.featured_image | img_url: image_size }}"
               alt="{{ product.featured_image.alt | default: product.title }}"
               class="product-card__img lazyload">
        {% else %}
          <img src="{{ product.featured_image | img_url: image_size }}"
               alt="{{ product.featured_image.alt | default: product.title }}"
               class="product-card__img">
        {% endif %}
        
        <!-- 鼠标悬停显示第二张图片 -->
        {% if product.images[1] %}
          <img src="{{ product.images[1] | img_url: image_size }}"
               alt="{{ product.images[1].alt | default: product.title }}"
               class="product-card__img product-card__img--hover">
        {% endif %}
      {% else %}
        <div class="product-card__placeholder">
          {% render 'icon-image-placeholder' %}
        </div>
      {% endif %}
      
      <!-- 产品标签 -->
      {% if product.tags contains 'new' %}
        <span class="product-card__badge product-card__badge--new">新品</span>
      {% elsif product.compare_at_price > product.price %}
        {% assign discount_percent = product.compare_at_price | minus: product.price | times: 100 | divided_by: product.compare_at_price | round %}
        <span class="product-card__badge product-card__badge--sale">-{{ discount_percent }}%</span>
      {% endif %}
      
      <!-- 缺货标识 -->
      {% unless product.available %}
        <span class="product-card__badge product-card__badge--soldout">缺货</span>
      {% endunless %}
    </a>
    
    <!-- 快速添加到购物车 -->
    {% if product.available and product.variants.size == 1 %}
      <button type="button" 
              class="product-card__quick-add"
              data-variant-id="{{ product.first_available_variant.id }}"
              aria-label="快速添加 {{ product.title }} 到购物车">
        {% render 'icon-cart' %}
      </button>
    {% endif %}
  </div>
  
  <!-- 产品信息 -->
  <div class="product-card__content">
    {% if show_vendor and product.vendor %}
      <p class="product-card__vendor">{{ product.vendor }}</p>
    {% endif %}
    
    <h3 class="product-card__title">
      <a href="{{ product.url }}">{{ product.title }}</a>
    </h3>
    
    {% if show_price %}
      <div class="product-card__price">
        {% render 'price-display', product: product %}
      </div>
    {% endif %}
    
    <!-- 产品评分 -->
    {% if product.metafields.reviews.rating %}
      <div class="product-card__rating">
        {% render 'star-rating', rating: product.metafields.reviews.rating %}
      </div>
    {% endif %}
    
    <!-- 颜色选项 -->
    {% assign color_options = product.options_with_values | where: 'name', 'Color' | first %}
    {% if color_options.values.size > 1 %}
      <div class="product-card__colors">
        {% for color in color_options.values limit: 5 %}
          <span class="product-card__color" 
                style="background-color: {{ color | handleize }}"
                title="{{ color }}"></span>
        {% endfor %}
        {% if color_options.values.size > 5 %}
          <span class="product-card__color-more">+{{ color_options.values.size | minus: 5 }}</span>
        {% endif %}
      </div>
    {% endif %}
  </div>
</article>3. 响应式布局系统
<!-- snippets/utilities/responsive-grid.liquid -->
{% comment %}
  响应式网格组件
  
  参数:
  - items: 项目数组
  - columns_mobile: 移动端列数 (默认: 1)
  - columns_tablet: 平板端列数 (默认: 2)  
  - columns_desktop: 桌面端列数 (默认: 4)
  - gap: 间距 (默认: 'medium')
  - item_snippet: 项目渲染片段 (默认: 'grid-item')
{% endcomment %}
 
{% assign columns_mobile = columns_mobile | default: 1 %}
{% assign columns_tablet = columns_tablet | default: 2 %}
{% assign columns_desktop = columns_desktop | default: 4 %}
{% assign gap = gap | default: 'medium' %}
{% assign item_snippet = item_snippet | default: 'grid-item' %}
 
<div class="responsive-grid responsive-grid--{{ gap }}"
     style="--columns-mobile: {{ columns_mobile }};
            --columns-tablet: {{ columns_tablet }};
            --columns-desktop: {{ columns_desktop }};">
  {% for item in items %}
    <div class="responsive-grid__item">
      {% render item_snippet, item: item, index: forloop.index %}
    </div>
  {% endfor %}
</div>
 
<!-- 对应的CSS -->
<style>
.responsive-grid {
  display: grid;
  grid-template-columns: repeat(var(--columns-mobile), 1fr);
  gap: var(--gap-small);
}
 
.responsive-grid--small { --gap-small: 0.5rem; --gap-medium: 1rem; --gap-large: 1.5rem; }
.responsive-grid--medium { --gap-small: 1rem; --gap-medium: 1.5rem; --gap-large: 2rem; }
.responsive-grid--large { --gap-small: 1.5rem; --gap-medium: 2rem; --gap-large: 2.5rem; }
 
@media (min-width: 768px) {
  .responsive-grid {
    grid-template-columns: repeat(var(--columns-tablet), 1fr);
    gap: var(--gap-medium);
  }
}
 
@media (min-width: 1024px) {
  .responsive-grid {
    grid-template-columns: repeat(var(--columns-desktop), 1fr);
    gap: var(--gap-large);
  }
}
</style>高级功能实现
1. 智能搜索和过滤
<!-- sections/collection-filters.liquid -->
<div class="collection-filters" data-collection="{{ collection.handle }}">
  <!-- 搜索框 -->
  <div class="filter-group">
    <label for="search-input" class="filter-label">搜索</label>
    <div class="search-input-wrapper">
      <input type="text" 
             id="search-input"
             class="search-input"
             placeholder="搜索商品..."
             data-search-input>
      {% render 'icon-search' %}
    </div>
  </div>
  
  <!-- 价格范围 -->
  <div class="filter-group">
    <label class="filter-label">价格范围</label>
    <div class="price-range">
      <input type="range" 
             id="price-min" 
             class="price-slider"
             min="0" 
             max="{{ collection.products | map: 'price' | sort | last }}"
             data-price-min>
      <input type="range" 
             id="price-max" 
             class="price-slider"
             min="0" 
             max="{{ collection.products | map: 'price' | sort | last }}"
             data-price-max>
      <div class="price-display">
        <span data-price-min-display>¥0</span> - 
        <span data-price-max-display>¥{{ collection.products | map: 'price' | sort | last | money_without_currency }}</span>
      </div>
    </div>
  </div>
  
  <!-- 品牌过滤 -->
  {% assign vendors = collection.products | map: 'vendor' | uniq | sort %}
  {% if vendors.size > 1 %}
    <div class="filter-group">
      <label class="filter-label">品牌</label>
      <div class="filter-options" data-filter="vendor">
        {% for vendor in vendors %}
          <label class="filter-option">
            <input type="checkbox" value="{{ vendor | handleize }}" data-filter-checkbox>
            <span class="filter-option-text">{{ vendor }}</span>
            <span class="filter-option-count">({{ collection.products | where: 'vendor', vendor | size }})</span>
          </label>
        {% endfor %}
      </div>
    </div>
  {% endif %}
  
  <!-- 产品类型过滤 -->
  {% assign product_types = collection.products | map: 'type' | uniq | sort %}
  {% if product_types.size > 1 %}
    <div class="filter-group">
      <label class="filter-label">类型</label>
      <div class="filter-options" data-filter="type">
        {% for type in product_types %}
          <label class="filter-option">
            <input type="checkbox" value="{{ type | handleize }}" data-filter-checkbox>
            <span class="filter-option-text">{{ type }}</span>
            <span class="filter-option-count">({{ collection.products | where: 'type', type | size }})</span>
          </label>
        {% endfor %}
      </div>
    </div>
  {% endif %}
  
  <!-- 标签过滤 -->
  {% assign all_tags = "" %}
  {% for product in collection.products %}
    {% for tag in product.tags %}
      {% unless all_tags contains tag %}
        {% if all_tags == "" %}
          {% assign all_tags = tag %}
        {% else %}
          {% assign all_tags = all_tags | append: "," | append: tag %}
        {% endif %}
      {% endunless %}
    {% endfor %}
  {% endfor %}
  {% assign unique_tags = all_tags | split: "," | sort %}
  
  {% if unique_tags.size > 0 %}
    <div class="filter-group">
      <label class="filter-label">标签</label>
      <div class="filter-options" data-filter="tags">
        {% for tag in unique_tags %}
          <label class="filter-option">
            <input type="checkbox" value="{{ tag | handleize }}" data-filter-checkbox>
            <span class="filter-option-text">{{ tag }}</span>
          </label>
        {% endfor %}
      </div>
    </div>
  {% endif %}
  
  <!-- 库存状态 -->
  <div class="filter-group">
    <label class="filter-label">库存状态</label>
    <div class="filter-options" data-filter="availability">
      <label class="filter-option">
        <input type="checkbox" value="available" data-filter-checkbox>
        <span class="filter-option-text">有库存</span>
      </label>
    </div>
  </div>
  
  <!-- 清除所有过滤器 -->
  <button type="button" class="filter-clear" data-clear-filters>
    清除所有过滤器
  </button>
</div>
 
<!-- 产品网格 -->
<div class="products-grid" data-products-container>
  {% for product in collection.products %}
    <div class="product-item" 
         data-product-item
         data-vendor="{{ product.vendor | handleize }}"
         data-type="{{ product.type | handleize }}"
         data-price="{{ product.price }}"
         data-available="{{ product.available }}"
         data-tags="{{ product.tags | join: ',' | handleize }}">
      {% render 'product-card', product: product %}
    </div>
  {% endfor %}
</div>
 
<script>
// 过滤器JavaScript实现
class CollectionFilters {
  constructor() {
    this.container = document.querySelector('[data-products-container]')
    this.items = document.querySelectorAll('[data-product-item]')
    this.searchInput = document.querySelector('[data-search-input]')
    this.priceMin = document.querySelector('[data-price-min]')
    this.priceMax = document.querySelector('[data-price-max]')
    this.checkboxes = document.querySelectorAll('[data-filter-checkbox]')
    this.clearBtn = document.querySelector('[data-clear-filters]')
    
    this.init()
  }
  
  init() {
    this.searchInput?.addEventListener('input', this.handleSearch.bind(this))
    this.priceMin?.addEventListener('input', this.handlePriceFilter.bind(this))
    this.priceMax?.addEventListener('input', this.handlePriceFilter.bind(this))
    this.checkboxes.forEach(cb => {
      cb.addEventListener('change', this.handleFilter.bind(this))
    })
    this.clearBtn?.addEventListener('click', this.clearAllFilters.bind(this))
  }
  
  handleSearch(e) {
    const query = e.target.value.toLowerCase()
    this.filterProducts()
  }
  
  handlePriceFilter() {
    this.filterProducts()
  }
  
  handleFilter() {
    this.filterProducts()
  }
  
  filterProducts() {
    const searchQuery = this.searchInput?.value.toLowerCase() || ''
    const minPrice = parseInt(this.priceMin?.value) || 0
    const maxPrice = parseInt(this.priceMax?.value) || Infinity
    
    const activeFilters = {
      vendor: [],
      type: [],
      tags: [],
      availability: []
    }
    
    // 收集活跃的过滤器
    this.checkboxes.forEach(cb => {
      if (cb.checked) {
        const filterType = cb.closest('[data-filter]').dataset.filter
        activeFilters[filterType].push(cb.value)
      }
    })
    
    // 过滤产品
    this.items.forEach(item => {
      let show = true
      
      // 搜索过滤
      if (searchQuery) {
        const productText = item.textContent.toLowerCase()
        if (!productText.includes(searchQuery)) {
          show = false
        }
      }
      
      // 价格过滤
      const price = parseInt(item.dataset.price)
      if (price < minPrice || price > maxPrice) {
        show = false
      }
      
      // 品牌过滤
      if (activeFilters.vendor.length > 0) {
        if (!activeFilters.vendor.includes(item.dataset.vendor)) {
          show = false
        }
      }
      
      // 类型过滤
      if (activeFilters.type.length > 0) {
        if (!activeFilters.type.includes(item.dataset.type)) {
          show = false
        }
      }
      
      // 标签过滤
      if (activeFilters.tags.length > 0) {
        const productTags = item.dataset.tags.split(',')
        const hasMatchingTag = activeFilters.tags.some(tag => 
          productTags.includes(tag)
        )
        if (!hasMatchingTag) {
          show = false
        }
      }
      
      // 库存过滤
      if (activeFilters.availability.includes('available')) {
        if (item.dataset.available !== 'true') {
          show = false
        }
      }
      
      // 显示/隐藏产品
      item.style.display = show ? 'block' : 'none'
    })
    
    this.updateResultsCount()
  }
  
  updateResultsCount() {
    const visibleItems = Array.from(this.items).filter(item => 
      item.style.display !== 'none'
    )
    
    // 更新结果计数显示
    const countElement = document.querySelector('[data-results-count]')
    if (countElement) {
      countElement.textContent = `显示 ${visibleItems.length} 个商品`
    }
  }
  
  clearAllFilters() {
    this.searchInput.value = ''
    this.priceMin.value = this.priceMin.min
    this.priceMax.value = this.priceMax.max
    this.checkboxes.forEach(cb => cb.checked = false)
    
    this.items.forEach(item => {
      item.style.display = 'block'
    })
    
    this.updateResultsCount()
  }
}
 
// 初始化过滤器
document.addEventListener('DOMContentLoaded', () => {
  new CollectionFilters()
})
</script>2. 动态购物车系统
<!-- snippets/cart-drawer.liquid -->
<div class="cart-drawer" data-cart-drawer>
  <div class="cart-drawer__overlay" data-cart-overlay></div>
  
  <div class="cart-drawer__content">
    <!-- 购物车头部 -->
    <div class="cart-drawer__header">
      <h2 class="cart-drawer__title">购物车 (<span data-cart-count>{{ cart.item_count }}</span>)</h2>
      <button type="button" class="cart-drawer__close" data-cart-close>
        {% render 'icon-close' %}
      </button>
    </div>
    
    <!-- 购物车内容 -->
    <div class="cart-drawer__body" data-cart-items>
      {% if cart.item_count > 0 %}
        {% for item in cart.items %}
          <div class="cart-item" data-cart-item="{{ item.key }}">
            <div class="cart-item__image">
              <img src="{{ item.image | img_url: '80x80' }}" 
                   alt="{{ item.title }}"
                   loading="lazy">
            </div>
            
            <div class="cart-item__details">
              <h4 class="cart-item__title">{{ item.product.title }}</h4>
              {% if item.variant.title != 'Default Title' %}
                <p class="cart-item__variant">{{ item.variant.title }}</p>
              {% endif %}
              
              <!-- 商品属性 -->
              {% unless item.properties == empty %}
                <div class="cart-item__properties">
                  {% for property in item.properties %}
                    {% unless property.last == blank %}
                      <p class="cart-item__property">
                        <strong>{{ property.first }}:</strong> {{ property.last }}
                      </p>
                    {% endunless %}
                  {% endfor %}
                </div>
              {% endunless %}
              
              <!-- 价格和数量 -->
              <div class="cart-item__price-quantity">
                <div class="cart-item__quantity">
                  <button type="button" 
                          class="quantity-btn quantity-btn--minus"
                          data-quantity-change="{{ item.key }}"
                          data-quantity="decrease">
                    {% render 'icon-minus' %}
                  </button>
                  <input type="number" 
                         class="quantity-input"
                         value="{{ item.quantity }}"
                         min="0"
                         data-quantity-input="{{ item.key }}">
                  <button type="button" 
                          class="quantity-btn quantity-btn--plus"
                          data-quantity-change="{{ item.key }}"
                          data-quantity="increase">
                    {% render 'icon-plus' %}
                  </button>
                </div>
                
                <div class="cart-item__price">
                  <span class="cart-item__unit-price">{{ item.price | money }}</span>
                  {% if item.quantity > 1 %}
                    <span class="cart-item__total-price">总计: {{ item.line_price | money }}</span>
                  {% endif %}
                </div>
              </div>
              
              <!-- 折扣信息 -->
              {% if item.line_level_discount_allocations.size > 0 %}
                <div class="cart-item__discounts">
                  {% for discount in item.line_level_discount_allocations %}
                    <p class="cart-item__discount">
                      {% render 'icon-discount' %}
                      {{ discount.discount_application.title }} (-{{ discount.amount | money }})
                    </p>
                  {% endfor %}
                </div>
              {% endif %}
            </div>
            
            <!-- 删除按钮 -->
            <button type="button" 
                    class="cart-item__remove"
                    data-cart-remove="{{ item.key }}"
                    aria-label="移除 {{ item.title }}">
              {% render 'icon-trash' %}
            </button>
          </div>
        {% endfor %}
      {% else %}
        <div class="cart-empty" data-cart-empty>
          <div class="cart-empty__icon">
            {% render 'icon-cart-empty' %}
          </div>
          <h3 class="cart-empty__title">购物车是空的</h3>
          <p class="cart-empty__text">添加一些商品开始购物吧</p>
          <a href="/collections" class="btn btn--primary">继续购物</a>
        </div>
      {% endif %}
    </div>
    
    <!-- 购物车底部 -->
    {% if cart.item_count > 0 %}
      <div class="cart-drawer__footer">
        <!-- 小计 -->
        <div class="cart-summary">
          <div class="cart-summary__line">
            <span>小计:</span>
            <span data-cart-subtotal>{{ cart.total_price | money }}</span>
          </div>
          
          <!-- 配送信息 -->
          <div class="cart-summary__shipping">
            {% if cart.total_price >= settings.free_shipping_threshold %}
              <p class="shipping-message shipping-message--free">
                {% render 'icon-truck' %}
                恭喜!您已获得免费配送
              </p>
            {% else %}
              {% assign remaining = settings.free_shipping_threshold | minus: cart.total_price %}
              <p class="shipping-message">
                {% render 'icon-truck' %}
                再购买 {{ remaining | money }} 即可获得免费配送
              </p>
              <div class="shipping-progress">
                {% assign progress = cart.total_price | times: 100 | divided_by: settings.free_shipping_threshold %}
                <div class="shipping-progress__bar" style="width: {{ progress }}%"></div>
              </div>
            {% endif %}
          </div>
          
          <!-- 购物车折扣 -->
          {% if cart.cart_level_discount_applications.size > 0 %}
            <div class="cart-discounts">
              {% for discount in cart.cart_level_discount_applications %}
                <div class="cart-discount">
                  {% render 'icon-discount' %}
                  <span>{{ discount.title }}</span>
                  <span>-{{ discount.total_allocated_amount | money }}</span>
                </div>
              {% endfor %}
            </div>
          {% endif %}
        </div>
        
        <!-- 操作按钮 -->
        <div class="cart-actions">
          <button type="button" class="btn btn--secondary btn--full" data-cart-close>
            继续购物
          </button>
          <a href="/cart" class="btn btn--outline btn--full">
            查看购物车
          </a>
          <button type="submit" 
                  class="btn btn--primary btn--full"
                  name="add" 
                  data-cart-checkout>
            立即结账
          </button>
        </div>
        
        <!-- 推荐商品 -->
        {% if settings.show_cart_recommendations %}
          <div class="cart-recommendations">
            <h4 class="cart-recommendations__title">您可能还喜欢</h4>
            <div class="cart-recommendations__grid">
              {% assign recommended_products = collections.recommendations.products | limit: 4 %}
              {% for product in recommended_products %}
                {% render 'product-card-mini', product: product %}
              {% endfor %}
            </div>
          </div>
        {% endif %}
      </div>
    {% endif %}
  </div>
</div>
 
<script>
// 购物车抽屉功能
class CartDrawer {
  constructor() {
    this.drawer = document.querySelector('[data-cart-drawer]')
    this.overlay = document.querySelector('[data-cart-overlay]')
    this.closeBtn = document.querySelector('[data-cart-close]')
    this.cartTriggers = document.querySelectorAll('[data-cart-open]')
    
    this.init()
  }
  
  init() {
    // 绑定事件
    this.cartTriggers.forEach(trigger => {
      trigger.addEventListener('click', () => this.open())
    })
    
    this.closeBtn?.addEventListener('click', () => this.close())
    this.overlay?.addEventListener('click', () => this.close())
    
    // 数量变更
    document.addEventListener('change', this.handleQuantityChange.bind(this))
    document.addEventListener('click', this.handleCartActions.bind(this))
    
    // 键盘事件
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && this.drawer.classList.contains('is-open')) {
        this.close()
      }
    })
  }
  
  open() {
    this.drawer.classList.add('is-open')
    document.body.classList.add('cart-drawer-open')
    
    // 焦点管理
    const firstFocusable = this.drawer.querySelector('button, input, a')
    firstFocusable?.focus()
  }
  
  close() {
    this.drawer.classList.remove('is-open')
    document.body.classList.remove('cart-drawer-open')
  }
  
  async handleQuantityChange(e) {
    if (!e.target.matches('[data-quantity-input]')) return
    
    const key = e.target.dataset.quantityInput
    const quantity = parseInt(e.target.value)
    
    if (quantity === 0) {
      await this.removeItem(key)
    } else {
      await this.updateQuantity(key, quantity)
    }
  }
  
  async handleCartActions(e) {
    // 数量增减按钮
    if (e.target.matches('[data-quantity-change]')) {
      e.preventDefault()
      
      const key = e.target.dataset.quantityChange
      const action = e.target.dataset.quantity
      const input = document.querySelector(`[data-quantity-input="${key}"]`)
      const currentQuantity = parseInt(input.value)
      
      let newQuantity = currentQuantity
      if (action === 'increase') {
        newQuantity = currentQuantity + 1
      } else if (action === 'decrease') {
        newQuantity = Math.max(0, currentQuantity - 1)
      }
      
      input.value = newQuantity
      
      if (newQuantity === 0) {
        await this.removeItem(key)
      } else {
        await this.updateQuantity(key, newQuantity)
      }
    }
    
    // 删除商品
    if (e.target.matches('[data-cart-remove]')) {
      e.preventDefault()
      const key = e.target.dataset.cartRemove
      await this.removeItem(key)
    }
    
    // 结账
    if (e.target.matches('[data-cart-checkout]')) {
      window.location.href = '/checkout'
    }
  }
  
  async updateQuantity(key, quantity) {
    try {
      this.setLoading(true)
      
      const response = await fetch('/cart/change.js', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          id: key,
          quantity: quantity
        })
      })
      
      const cart = await response.json()
      this.updateCartUI(cart)
      
    } catch (error) {
      console.error('更新购物车失败:', error)
      this.showError('更新失败,请重试')
    } finally {
      this.setLoading(false)
    }
  }
  
  async removeItem(key) {
    try {
      this.setLoading(true)
      
      const response = await fetch('/cart/change.js', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          id: key,
          quantity: 0
        })
      })
      
      const cart = await response.json()
      this.updateCartUI(cart)
      
    } catch (error) {
      console.error('删除商品失败:', error)
      this.showError('删除失败,请重试')
    } finally {
      this.setLoading(false)
    }
  }
  
  updateCartUI(cart) {
    // 更新购物车计数
    document.querySelectorAll('[data-cart-count]').forEach(el => {
      el.textContent = cart.item_count
    })
    
    // 更新小计
    document.querySelectorAll('[data-cart-subtotal]').forEach(el => {
      el.textContent = this.formatMoney(cart.total_price)
    })
    
    // 如果购物车为空,刷新页面显示空状态
    if (cart.item_count === 0) {
      location.reload()
    }
  }
  
  setLoading(loading) {
    this.drawer.classList.toggle('is-loading', loading)
  }
  
  showError(message) {
    // 显示错误提示
    const errorEl = document.createElement('div')
    errorEl.className = 'cart-error'
    errorEl.textContent = message
    
    this.drawer.appendChild(errorEl)
    
    setTimeout(() => {
      errorEl.remove()
    }, 3000)
  }
  
  formatMoney(cents) {
    return (cents / 100).toLocaleString('zh-CN', {
      style: 'currency',
      currency: 'CNY'
    })
  }
}
 
// 初始化购物车抽屉
document.addEventListener('DOMContentLoaded', () => {
  new CartDrawer()
})
</script>3. 高级产品功能
<!-- templates/product.liquid -->
<div class="product-page" data-product-id="{{ product.id }}">
  <!-- 产品面包屑 -->
  <nav class="breadcrumbs">
    {% render 'breadcrumbs' %}
  </nav>
  
  <!-- 产品主要内容 -->
  <div class="product-main">
    <!-- 产品图片画廊 -->
    <div class="product-gallery">
      {% render 'product-gallery', product: product %}
    </div>
    
    <!-- 产品信息 -->
    <div class="product-info">
      <!-- 产品基本信息 -->
      <div class="product-header">
        {% if product.vendor %}
          <p class="product-vendor">{{ product.vendor }}</p>
        {% endif %}
        <h1 class="product-title">{{ product.title }}</h1>
        
        <!-- 产品评分 -->
        {% if product.metafields.reviews.rating %}
          <div class="product-rating">
            {% render 'star-rating', rating: product.metafields.reviews.rating %}
            <span class="rating-count">({{ product.metafields.reviews.count }} 条评价)</span>
          </div>
        {% endif %}
      </div>
      
      <!-- 产品价格 -->
      <div class="product-price">
        {% render 'price-display', product: product, size: 'large' %}
      </div>
      
      <!-- 产品表单 -->
      {% form 'product', product, class: 'product-form', data-product-form: '' %}
        <!-- 变体选择器 -->
        {% if product.variants.size > 1 %}
          <div class="product-variants">
            {% for option in product.options_with_values %}
              <div class="variant-option" data-option-position="{{ option.position }}">
                <label class="variant-label">{{ option.name }}</label>
                
                {% case option.name %}
                  {% when 'Color' or '颜色' %}
                    {% render 'variant-color-selector', option: option, product: product %}
                  {% when 'Size' or '尺寸' %}
                    {% render 'variant-size-selector', option: option, product: product %}
                  {% else %}
                    {% render 'variant-dropdown-selector', option: option, product: product %}
                {% endcase %}
              </div>
            {% endfor %}
          </div>
        {% endif %}
        
        <!-- 数量选择器 -->
        <div class="quantity-selector">
          <label for="quantity" class="quantity-label">数量</label>
          <div class="quantity-input-wrapper">
            <button type="button" class="quantity-btn" data-quantity="decrease">
              {% render 'icon-minus' %}
            </button>
            <input type="number" 
                   id="quantity" 
                   name="quantity" 
                   value="1" 
                   min="1"
                   class="quantity-input"
                   data-quantity-input>
            <button type="button" class="quantity-btn" data-quantity="increase">
              {% render 'icon-plus' %}
            </button>
          </div>
        </div>
        
        <!-- 购买按钮 -->
        <div class="product-actions">
          <button type="submit" 
                  class="btn btn--primary btn--large btn--full product-form__cart-submit"
                  data-add-to-cart>
            <span data-add-to-cart-text>
              {% if product.available %}
                加入购物车
              {% else %}
                缺货
              {% endif %}
            </span>
            <div class="loading-spinner" data-loading-spinner></div>
          </button>
          
          <!-- 立即购买 -->
          {% if product.available %}
            <button type="button" 
                    class="btn btn--secondary btn--large btn--full"
                    data-buy-now>
              立即购买
            </button>
          {% endif %}
          
          <!-- 愿望清单 -->
          <button type="button" 
                  class="btn btn--outline wishlist-btn"
                  data-wishlist-toggle="{{ product.id }}">
            {% render 'icon-heart' %}
            <span>收藏</span>
          </button>
        </div>
        
        <!-- 库存状态 -->
        <div class="stock-status" data-stock-status>
          {% assign selected_variant = product.selected_or_first_available_variant %}
          {% if selected_variant.available %}
            {% if selected_variant.inventory_quantity <= 5 and selected_variant.inventory_policy == 'deny' %}
              <span class="stock-low">仅剩 {{ selected_variant.inventory_quantity }} 件</span>
            {% else %}
              <span class="stock-available">有库存</span>
            {% endif %}
          {% else %}
            <span class="stock-unavailable">缺货</span>
          {% endif %}
        </div>
        
        <!-- 产品描述 -->
        {% if product.description %}
          <div class="product-description">
            <h3>产品描述</h3>
            <div class="description-content">
              {{ product.description }}
            </div>
          </div>
        {% endif %}
        
        <!-- 产品详情标签页 -->
        <div class="product-tabs">
          <div class="tab-nav">
            <button class="tab-btn active" data-tab="description">详细信息</button>
            {% if product.metafields.specifications %}
              <button class="tab-btn" data-tab="specifications">产品规格</button>
            {% endif %}
            <button class="tab-btn" data-tab="shipping">配送信息</button>
            <button class="tab-btn" data-tab="reviews">用户评价</button>
          </div>
          
          <div class="tab-content">
            <div class="tab-panel active" data-panel="description">
              {{ product.description }}
            </div>
            
            {% if product.metafields.specifications %}
              <div class="tab-panel" data-panel="specifications">
                {{ product.metafields.specifications | metafield_tag }}
              </div>
            {% endif %}
            
            <div class="tab-panel" data-panel="shipping">
              {% render 'shipping-information' %}
            </div>
            
            <div class="tab-panel" data-panel="reviews">
              {% render 'product-reviews', product: product %}
            </div>
          </div>
        </div>
        
        <!-- 隐藏的变体ID输入 -->
        <input type="hidden" name="id" data-variant-id value="{{ product.selected_or_first_available_variant.id }}">
        
      {% endform %}
    </div>
  </div>
  
  <!-- 推荐商品 -->
  <div class="product-recommendations">
    {% render 'product-recommendations', product: product %}
  </div>
  
  <!-- 最近浏览的商品 -->
  <div class="recently-viewed">
    {% render 'recently-viewed-products' %}
  </div>
</div>
 
<!-- 产品数据 -->
<script>
  window.productData = {{ product | json }};
  window.variantData = {{ product.variants | json }};
</script>下一步学习
掌握了主题开发实战后,建议继续学习:
- 自定义分区开发 - 创建灵活的页面分区
 - 自定义代码片段 - 开发可复用组件
 - 主题设置配置 - 主题自定义选项
 - 响应式设计实现 - 移动优先设计
 - 电商功能实现 - 高级电商功能
 - 第三方集成 - 外部服务集成
 
通过实战项目的锻炼,您将能够开发出专业级别的 Shopify 主题!
最后更新时间: