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

定制化开发案例

本指南通过实际的高级定制化案例,展示如何实现复杂的业务需求和深度定制功能。

案例1:动态定价系统

业务需求

  • 基于用户等级的动态定价
  • 批量购买折扣
  • 地区性定价差异
  • 促销活动定价

实现方案

<!-- snippets/dynamic-pricing.liquid --> {% comment %} 动态定价组件 参数: - product: 产品对象 - customer: 客户对象 - quantity: 购买数量 (默认: 1) {% endcomment %} {%- liquid assign base_price = product.price assign current_customer = customer assign buy_quantity = quantity | default: 1 # 获取客户等级 assign customer_tier = 'guest' if current_customer for tag in current_customer.tags if tag contains 'tier:' assign customer_tier = tag | remove: 'tier:' break endif endfor endif # 计算等级折扣 assign tier_discount = 0 case customer_tier when 'vip' assign tier_discount = 0.15 when 'premium' assign tier_discount = 0.10 when 'regular' assign tier_discount = 0.05 endcase # 计算批量折扣 assign bulk_discount = 0 if buy_quantity >= 100 assign bulk_discount = 0.20 elsif buy_quantity >= 50 assign bulk_discount = 0.15 elsif buy_quantity >= 20 assign bulk_discount = 0.10 elsif buy_quantity >= 10 assign bulk_discount = 0.05 endif # 检查地区定价 assign region_multiplier = 1.0 if current_customer.default_address assign country = current_customer.default_address.country_code case country when 'US' assign region_multiplier = 1.0 when 'CA' assign region_multiplier = 1.1 when 'AU' assign region_multiplier = 1.15 when 'CN' assign region_multiplier = 0.85 endcase endif # 检查活动定价 assign promo_discount = 0 assign current_time = 'now' | date: '%s' | plus: 0 # 黑色星期五促销 assign black_friday_start = '2024-11-29' | date: '%s' assign black_friday_end = '2024-12-02' | date: '%s' if current_time >= black_friday_start and current_time <= black_friday_end if product.tags contains 'black-friday' assign promo_discount = 0.30 endif endif # 计算最终价格 assign final_discount = tier_discount | plus: bulk_discount | plus: promo_discount if final_discount > 0.5 assign final_discount = 0.5 endif assign discounted_price = base_price | times: region_multiplier | times: 1 | minus: final_discount assign total_price = discounted_price | times: buy_quantity assign savings = base_price | minus: discounted_price | times: buy_quantity -%} <div class="dynamic-pricing" data-dynamic-pricing> <div class="pricing-display"> <div class="current-price"> {% if discounted_price < base_price %} <span class="price--sale">{{ discounted_price | money }}</span> <span class="price--original">{{ base_price | money }}</span> {% else %} <span class="price--regular">{{ discounted_price | money }}</span> {% endif %} </div> {% if savings > 0 %} <div class="savings-info"> <span class="savings-amount">节省 {{ savings | money }}</span> <span class="savings-percentage">({{ final_discount | times: 100 | round }}% 折扣)</span> </div> {% endif %} </div> <!-- 定价详情 --> <div class="pricing-breakdown" data-pricing-details style="display: none;"> <h4>定价详情</h4> <div class="pricing-item"> <span>基础价格:</span> <span>{{ base_price | money }}</span> </div> {% if tier_discount > 0 %} <div class="pricing-item"> <span>{{ customer_tier | capitalize }} 会员折扣 ({{ tier_discount | times: 100 }}%):</span> <span>-{{ base_price | times: tier_discount | money }}</span> </div> {% endif %} {% if bulk_discount > 0 %} <div class="pricing-item"> <span>批量购买折扣 ({{ bulk_discount | times: 100 }}%):</span> <span>-{{ base_price | times: bulk_discount | money }}</span> </div> {% endif %} {% if promo_discount > 0 %} <div class="pricing-item"> <span>促销活动折扣 ({{ promo_discount | times: 100 }}%):</span> <span>-{{ base_price | times: promo_discount | money }}</span> </div> {% endif %} {% if region_multiplier != 1.0 %} <div class="pricing-item"> <span>地区调整:</span> <span>×{{ region_multiplier }}</span> </div> {% endif %} <div class="pricing-item total"> <span>单价:</span> <span>{{ discounted_price | money }}</span> </div> {% if buy_quantity > 1 %} <div class="pricing-item total"> <span>总计 ({{ buy_quantity }} 件):</span> <span>{{ total_price | money }}</span> </div> {% endif %} </div> <button class="pricing-toggle" data-pricing-toggle> 查看定价详情 </button> </div> <script> class DynamicPricing { constructor(element) { this.container = element; this.toggleBtn = element.querySelector('[data-pricing-toggle]'); this.details = element.querySelector('[data-pricing-details]'); this.init(); } init() { this.bindEvents(); } bindEvents() { this.toggleBtn?.addEventListener('click', this.toggleDetails.bind(this)); } toggleDetails() { const isVisible = this.details.style.display !== 'none'; this.details.style.display = isVisible ? 'none' : 'block'; this.toggleBtn.textContent = isVisible ? '查看定价详情' : '隐藏定价详情'; } // 更新数量时重新计算价格 updateQuantity(newQuantity) { // 这里可以通过AJAX重新获取定价信息 fetch(`/products/${this.productId}/pricing?quantity=${newQuantity}`, { headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { this.updatePricingDisplay(data); }); } updatePricingDisplay(pricingData) { // 更新价格显示 this.container.innerHTML = pricingData.html; } } // 初始化动态定价 document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('[data-dynamic-pricing]').forEach(pricing => { new DynamicPricing(pricing); }); }); </script>

案例2:高级库存管理

业务需求

  • 多仓库库存显示
  • 预售功能
  • 库存预警
  • 自动补货通知

实现方案

<!-- snippets/advanced-inventory.liquid --> {% comment %} 高级库存管理组件 参数: - variant: 产品变体 - show_warehouses: 是否显示仓库详情 {% endcomment %} {%- liquid assign current_variant = variant assign total_inventory = current_variant.inventory_quantity assign inventory_policy = current_variant.inventory_policy assign inventory_management = current_variant.inventory_management assign show_warehouses = show_warehouses | default: false # 获取仓库库存信息 (通过metafields) assign warehouse_stock = current_variant.metafields.inventory.warehouses # 设置库存状态 assign stock_status = 'in_stock' assign stock_message = '有库存' assign stock_class = 'stock-good' if total_inventory <= 0 if inventory_policy == 'continue' assign stock_status = 'preorder' assign stock_message = '可预订' assign stock_class = 'stock-preorder' else assign stock_status = 'out_of_stock' assign stock_message = '缺货' assign stock_class = 'stock-out' endif elsif total_inventory <= 5 assign stock_status = 'low_stock' assign stock_message = '库存紧张' assign stock_class = 'stock-low' endif # 预计到货时间 assign estimated_delivery = current_variant.metafields.inventory.estimated_delivery assign lead_time_days = current_variant.metafields.inventory.lead_time | default: 7 -%} <div class="advanced-inventory" data-inventory-manager data-variant-id="{{ current_variant.id }}"> <div class="stock-status {{ stock_class }}"> <span class="stock-indicator"></span> <span class="stock-text">{{ stock_message }}</span> {% if total_inventory > 0 and total_inventory <= 10 %} <span class="stock-count">(仅剩 {{ total_inventory }} 件)</span> {% endif %} </div> <!-- 仓库库存详情 --> {% if show_warehouses and warehouse_stock %} <div class="warehouse-stock" data-warehouse-details> <h4>库存分布</h4> {% assign warehouses = warehouse_stock | split: ',' %} {% for warehouse_info in warehouses %} {% assign info_parts = warehouse_info | split: ':' %} {% assign warehouse_name = info_parts[0] %} {% assign warehouse_stock_count = info_parts[1] | plus: 0 %} <div class="warehouse-item"> <span class="warehouse-name">{{ warehouse_name }}</span> <span class="warehouse-stock">{{ warehouse_stock_count }} 件</span> </div> {% endfor %} </div> {% endif %} <!-- 预售信息 --> {% if stock_status == 'preorder' %} <div class="preorder-info"> <h4>预售信息</h4> {% if estimated_delivery %} <p>预计到货: {{ estimated_delivery | date: '%Y年%m月%d日' }}</p> {% else %} <p>预计 {{ lead_time_days }} 个工作日内发货</p> {% endif %} </div> {% endif %} <!-- 库存通知 --> {% if stock_status == 'out_of_stock' %} <div class="stock-notification"> <h4>到货通知</h4> <form class="notify-form" data-notify-form> <input type="hidden" name="variant_id" value="{{ current_variant.id }}"> <input type="email" name="email" placeholder="输入邮箱地址" required class="notify-email"> <button type="submit" class="notify-btn">到货通知我</button> </form> <p class="notify-text">商品到货后我们会第一时间通知您</p> </div> {% endif %} <!-- 库存图表 --> <div class="stock-chart" data-stock-chart> <div class="chart-container"> <canvas id="stock-chart-{{ current_variant.id }}"></canvas> </div> </div> </div> <script> class AdvancedInventory { constructor(element) { this.container = element; this.variantId = element.dataset.variantId; this.notifyForm = element.querySelector('[data-notify-form]'); this.chartCanvas = element.querySelector(`#stock-chart-${this.variantId}`); this.init(); } init() { this.bindEvents(); this.loadStockHistory(); this.setupRealTimeUpdates(); } bindEvents() { // 到货通知表单 this.notifyForm?.addEventListener('submit', this.handleNotifySubmit.bind(this)); // 库存变化监听 document.addEventListener('variant:changed', (e) => { if (e.detail.variantId === this.variantId) { this.updateInventoryDisplay(e.detail.inventory); } }); } async handleNotifySubmit(event) { event.preventDefault(); const formData = new FormData(this.notifyForm); const email = formData.get('email'); try { const response = await fetch('/api/stock-notifications', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ variant_id: this.variantId, email: email }) }); if (response.ok) { this.showNotification('通知设置成功!', 'success'); this.notifyForm.reset(); } else { throw new Error('设置失败'); } } catch (error) { this.showNotification('设置失败,请稍后重试', 'error'); } } async loadStockHistory() { try { const response = await fetch(`/api/variants/${this.variantId}/stock-history`); const stockData = await response.json(); this.renderStockChart(stockData); } catch (error) { console.warn('Failed to load stock history:', error); } } renderStockChart(stockData) { if (!this.chartCanvas || !window.Chart) return; const ctx = this.chartCanvas.getContext('2d'); new Chart(ctx, { type: 'line', data: { labels: stockData.dates, datasets: [{ label: '库存数量', data: stockData.quantities, borderColor: '#1a73e8', backgroundColor: 'rgba(26, 115, 232, 0.1)', tension: 0.4 }] }, options: { responsive: true, plugins: { title: { display: true, text: '库存变化趋势' }, legend: { display: false } }, scales: { y: { beginAtZero: true, title: { display: true, text: '库存数量' } }, x: { title: { display: true, text: '日期' } } } } }); } setupRealTimeUpdates() { // 使用 WebSocket 或 Server-Sent Events 实现实时库存更新 if (window.EventSource) { const eventSource = new EventSource(`/api/variants/${this.variantId}/stock-updates`); eventSource.onmessage = (event) => { const stockUpdate = JSON.parse(event.data); this.updateInventoryDisplay(stockUpdate); }; eventSource.onerror = () => { console.warn('Stock update connection lost, falling back to polling'); this.setupPolling(); }; } else { this.setupPolling(); } } setupPolling() { // 降级到轮询方式 setInterval(async () => { try { const response = await fetch(`/api/variants/${this.variantId}/stock`); const stockData = await response.json(); this.updateInventoryDisplay(stockData); } catch (error) { console.warn('Failed to poll stock data:', error); } }, 30000); // 30秒轮询一次 } updateInventoryDisplay(inventoryData) { const stockStatus = this.container.querySelector('.stock-status'); const stockText = stockStatus.querySelector('.stock-text'); const stockCount = stockStatus.querySelector('.stock-count'); // 更新库存状态 stockStatus.className = `stock-status ${inventoryData.stock_class}`; stockText.textContent = inventoryData.stock_message; if (stockCount) { if (inventoryData.quantity > 0 && inventoryData.quantity <= 10) { stockCount.textContent = `(仅剩 ${inventoryData.quantity} 件)`; stockCount.style.display = 'inline'; } else { stockCount.style.display = 'none'; } } // 触发库存变化事件 this.container.dispatchEvent(new CustomEvent('inventory:updated', { detail: inventoryData })); } showNotification(message, type) { const notification = document.createElement('div'); notification.className = `notification notification--${type}`; notification.textContent = message; this.container.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); } } // 初始化高级库存管理 document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('[data-inventory-manager]').forEach(inventory => { new AdvancedInventory(inventory); }); }); </script>

案例3:个性化推荐引擎

业务需求

  • 基于用户行为的商品推荐
  • 协同过滤推荐
  • 跨品类推荐
  • A/B测试支持

实现方案

<!-- sections/personalized-recommendations.liquid --> <div class="personalized-recommendations" data-recommendations data-customer-id="{{ customer.id | default: 'guest' }}" data-current-product="{{ product.id | default: '' }}"> <div class="recommendations-header"> <h2>{{ section.settings.heading | default: '为您推荐' }}</h2> </div> <div class="recommendations-container"> <!-- 加载状态 --> <div class="recommendations-loading" data-loading> <div class="loading-spinner"></div> <p>正在为您精选商品...</p> </div> <!-- 推荐商品 --> <div class="recommendations-grid" data-recommendations-grid style="display: none;"> <!-- 动态加载的推荐商品 --> </div> <!-- 无推荐时的降级内容 --> <div class="recommendations-fallback" data-fallback style="display: none;"> <h3>精选商品</h3> <div class="fallback-products"> {% for product in collections.featured.products limit: 4 %} {% render 'product-card', product: product %} {% endfor %} </div> </div> </div> </div> <script> class PersonalizedRecommendations { constructor(element) { this.container = element; this.customerId = element.dataset.customerId; this.currentProduct = element.dataset.currentProduct; this.loading = element.querySelector('[data-loading]'); this.grid = element.querySelector('[data-recommendations-grid]'); this.fallback = element.querySelector('[data-fallback]'); this.recommendationTypes = [ 'viewed_together', // 一起浏览的商品 'bought_together', // 一起购买的商品 'similar_products', // 相似商品 'user_behavior', // 基于用户行为 'trending', // 热门商品 'category_based' // 基于分类 ]; this.init(); } async init() { await this.loadRecommendations(); this.trackUserBehavior(); } async loadRecommendations() { try { const recommendations = await this.fetchRecommendations(); if (recommendations && recommendations.length > 0) { this.renderRecommendations(recommendations); } else { this.showFallback(); } } catch (error) { console.error('Failed to load recommendations:', error); this.showFallback(); } } async fetchRecommendations() { const params = new URLSearchParams({ customer_id: this.customerId, current_product: this.currentProduct, limit: 8, types: this.recommendationTypes.join(',') }); const response = await fetch(`/api/recommendations?${params}`); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return response.json(); } renderRecommendations(recommendations) { let html = ''; recommendations.forEach((item, index) => { html += this.createProductHTML(item, index); }); this.grid.innerHTML = html; this.hideLoading(); this.grid.style.display = 'grid'; // 懒加载图片 this.setupLazyLoading(); // 跟踪推荐展示 this.trackRecommendationViews(recommendations); } createProductHTML(product, index) { const reasonText = this.getRecommendationReason(product.reason); return ` <div class="recommendation-item" data-product-id="${product.id}" data-index="${index}"> <div class="recommendation-reason"> <span class="reason-badge">${reasonText}</span> </div> <div class="product-image"> <img data-src="${product.featured_image}&width=300" alt="${product.title}" class="lazy-image"> </div> <div class="product-info"> <h4 class="product-title"> <a href="${product.url}">${product.title}</a> </h4> <div class="product-price"> ${product.compare_at_price > product.price ? `<span class="price--sale">${this.formatMoney(product.price)}</span> <span class="price--compare">${this.formatMoney(product.compare_at_price)}</span>` : `<span class="price--regular">${this.formatMoney(product.price)}</span>` } </div> <div class="product-rating" data-rating="${product.rating}"> ${this.createStarRating(product.rating)} <span class="rating-count">(${product.review_count})</span> </div> <button class="quick-add-btn" data-product-id="${product.id}" data-variant-id="${product.default_variant_id}"> 快速加入购物车 </button> </div> <!-- A/B测试标记 --> <div class="ab-test-marker" data-test-variant="${product.test_variant}" style="display: none;"></div> </div> `; } getRecommendationReason(reason) { const reasons = { 'viewed_together': '一起浏览', 'bought_together': '一起购买', 'similar_products': '相似商品', 'user_behavior': '为您推荐', 'trending': '热门商品', 'category_based': '同类推荐' }; return reasons[reason] || '推荐'; } createStarRating(rating) { const fullStars = Math.floor(rating); const hasHalfStar = rating % 1 !== 0; const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0); let html = ''; // 满星 for (let i = 0; i < fullStars; i++) { html += '<span class="star star--full">★</span>'; } // 半星 if (hasHalfStar) { html += '<span class="star star--half">☆</span>'; } // 空星 for (let i = 0; i < emptyStars; i++) { html += '<span class="star star--empty">☆</span>'; } return html; } formatMoney(cents) { return new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(cents / 100); } setupLazyLoading() { const images = this.grid.querySelectorAll('.lazy-image'); if ('IntersectionObserver' in window) { const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.remove('lazy-image'); imageObserver.unobserve(img); } }); }); images.forEach(img => imageObserver.observe(img)); } else { // 降级处理 images.forEach(img => { img.src = img.dataset.src; }); } } trackRecommendationViews(recommendations) { // 跟踪推荐商品的展示 const viewData = { customer_id: this.customerId, recommendations: recommendations.map((item, index) => ({ product_id: item.id, position: index, reason: item.reason, test_variant: item.test_variant })), context: { page: window.location.pathname, current_product: this.currentProduct } }; fetch('/api/analytics/recommendation-views', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(viewData) }).catch(error => { console.warn('Failed to track recommendation views:', error); }); } trackUserBehavior() { // 跟踪用户行为以改善推荐 document.addEventListener('click', (e) => { const productCard = e.target.closest('.recommendation-item'); if (productCard) { const productId = productCard.dataset.productId; const index = productCard.dataset.index; const testVariant = productCard.querySelector('[data-test-variant]')?.dataset.testVariant; this.trackClick(productId, index, testVariant); } }); // 跟踪滚动行为 let scrollTimeout; window.addEventListener('scroll', () => { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { this.trackScrollBehavior(); }, 500); }); } trackClick(productId, position, testVariant) { const clickData = { customer_id: this.customerId, product_id: productId, position: parseInt(position), test_variant: testVariant, timestamp: Date.now() }; fetch('/api/analytics/recommendation-clicks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(clickData) }).catch(error => { console.warn('Failed to track recommendation click:', error); }); } trackScrollBehavior() { const visibleItems = this.getVisibleRecommendations(); if (visibleItems.length > 0) { const scrollData = { customer_id: this.customerId, visible_products: visibleItems, scroll_position: window.scrollY, timestamp: Date.now() }; fetch('/api/analytics/scroll-behavior', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(scrollData) }).catch(error => { console.warn('Failed to track scroll behavior:', error); }); } } getVisibleRecommendations() { const items = this.grid.querySelectorAll('.recommendation-item'); const visibleItems = []; items.forEach(item => { const rect = item.getBoundingClientRect(); const isVisible = rect.top < window.innerHeight && rect.bottom > 0; if (isVisible) { visibleItems.push({ product_id: item.dataset.productId, position: item.dataset.index, visibility_ratio: this.calculateVisibilityRatio(rect) }); } }); return visibleItems; } calculateVisibilityRatio(rect) { const windowHeight = window.innerHeight; const elementHeight = rect.height; let visibleHeight = elementHeight; if (rect.top < 0) { visibleHeight += rect.top; } if (rect.bottom > windowHeight) { visibleHeight -= (rect.bottom - windowHeight); } return Math.max(0, visibleHeight / elementHeight); } hideLoading() { this.loading.style.display = 'none'; } showFallback() { this.hideLoading(); this.fallback.style.display = 'block'; } } // 初始化个性化推荐 document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('[data-recommendations]').forEach(recommendations => { new PersonalizedRecommendations(recommendations); }); }); </script> {% schema %} { "name": "个性化推荐", "settings": [ { "type": "text", "id": "heading", "label": "标题", "default": "为您推荐" }, { "type": "range", "id": "product_limit", "label": "推荐商品数量", "min": 4, "max": 12, "step": 2, "default": 8 }, { "type": "checkbox", "id": "enable_ab_testing", "label": "启用A/B测试", "default": true } ], "presets": [ { "name": "个性化推荐", "category": "产品" } ] } {% endschema %}

通过这些高级定制化案例,您可以实现复杂的业务逻辑和个性化功能!

下一步学习

掌握定制化开发后,建议继续学习:

  1. 开发工具推荐 - 提升开发效率的工具
  2. 开发资源汇总 - 学习资源和社区
最后更新时间: