Skip to Content
🎉 探索 Shopify 的无限可能 结构化知识 + 实战案例,持续更新中...
Liquid 开发主题开发实践

主题开发实战

本指南将通过实际项目案例,深入介绍 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>

下一步学习

掌握了主题开发实战后,建议继续学习:

  1. 自定义分区开发 - 创建灵活的页面分区
  2. 自定义代码片段 - 开发可复用组件
  3. 主题设置配置 - 主题自定义选项
  4. 响应式设计实现 - 移动优先设计
  5. 电商功能实现 - 高级电商功能
  6. 第三方集成 - 外部服务集成

通过实战项目的锻炼,您将能够开发出专业级别的 Shopify 主题!

最后更新时间: