定制化开发案例
本指南通过实际的高级定制化案例,展示如何实现复杂的业务需求和深度定制功能。
案例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 %}
通过这些高级定制化案例,您可以实现复杂的业务逻辑和个性化功能!
下一步学习
掌握定制化开发后,建议继续学习:
最后更新时间: