finish_preview
This commit is contained in:
parent
5d73776a71
commit
d1b5e8c5cf
@ -5,6 +5,125 @@
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
|
||||
/* 规格选择样式 */
|
||||
.specs-section {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.spec-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.spec-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.spec-group .form-label {
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.spec-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.spec-option {
|
||||
min-width: 60px;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
background-color: #fff;
|
||||
color: #495057;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.spec-option:hover {
|
||||
border-color: #007bff;
|
||||
background-color: #e3f2fd;
|
||||
}
|
||||
|
||||
.spec-option.btn-primary {
|
||||
background-color: #007bff;
|
||||
border-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.spec-option.btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
border-color: #0056b3;
|
||||
}
|
||||
|
||||
.spec-option:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
/* 库存信息样式 */
|
||||
.stock-section {
|
||||
padding: 15px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
#stockCount {
|
||||
font-weight: 600;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
/* 数量选择样式 */
|
||||
.quantity-section .input-group {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.quantity-section .form-control {
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-buttons .btn {
|
||||
padding: 12px 24px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.action-buttons .btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 服务承诺样式 */
|
||||
.service-promises {
|
||||
background-color: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.service-promises h6 {
|
||||
color: #495057;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.service-promises li {
|
||||
margin-bottom: 5px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.service-promises .bi-check-circle {
|
||||
margin-right: 8px;
|
||||
}
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
@ -9,6 +9,23 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (inventoryData.length === 1) {
|
||||
currentSku = inventoryData[0];
|
||||
updateStockInfo();
|
||||
} else if (inventoryData.length > 1) {
|
||||
// 自动选择默认规格
|
||||
const defaultSku = inventoryData.find(sku => sku.is_default);
|
||||
if (defaultSku && defaultSku.spec_combination) {
|
||||
// 自动选择默认规格
|
||||
for (const [specName, specValue] of Object.entries(defaultSku.spec_combination)) {
|
||||
selectedSpecs[specName] = specValue;
|
||||
// 更新按钮状态
|
||||
const button = document.querySelector(`[data-spec-name="${specName}"][data-spec-value="${specValue}"]`);
|
||||
if (button) {
|
||||
button.classList.remove('btn-outline-secondary');
|
||||
button.classList.add('btn-primary');
|
||||
}
|
||||
}
|
||||
currentSku = defaultSku;
|
||||
updateStockInfo();
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定规格选择事件
|
||||
@ -107,16 +124,43 @@ function selectSpec(button) {
|
||||
|
||||
// 查找匹配的SKU
|
||||
function findMatchingSku() {
|
||||
// 获取所有需要选择的规格类型
|
||||
const allSpecNames = new Set();
|
||||
inventoryData.forEach(sku => {
|
||||
if (sku.spec_combination) {
|
||||
Object.keys(sku.spec_combination).forEach(specName => {
|
||||
allSpecNames.add(specName);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 检查是否选择了所有必需的规格
|
||||
const selectedSpecNames = Object.keys(selectedSpecs);
|
||||
if (selectedSpecNames.length < allSpecNames.size) {
|
||||
currentSku = null;
|
||||
updateStockInfo();
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找完全匹配的SKU
|
||||
for (let sku of inventoryData) {
|
||||
if (sku.spec_combination) {
|
||||
let isMatch = true;
|
||||
|
||||
// 检查规格数量是否匹配
|
||||
if (Object.keys(sku.spec_combination).length !== selectedSpecNames.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查每个规格是否匹配
|
||||
for (let [specName, specValue] of Object.entries(selectedSpecs)) {
|
||||
if (sku.spec_combination[specName] !== specValue) {
|
||||
isMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isMatch && Object.keys(selectedSpecs).length === Object.keys(sku.spec_combination).length) {
|
||||
|
||||
if (isMatch) {
|
||||
currentSku = sku;
|
||||
updateStockInfo();
|
||||
return;
|
||||
@ -138,24 +182,38 @@ function updateStockInfo() {
|
||||
const quantityInput = document.getElementById('quantity');
|
||||
|
||||
if (currentSku) {
|
||||
// 有选中的SKU
|
||||
stockElement.textContent = currentSku.stock;
|
||||
stockElement.className = currentSku.stock > 0 ? 'text-success' : 'text-danger';
|
||||
priceElement.textContent = currentSku.final_price.toFixed(2);
|
||||
|
||||
if (currentSku.stock > 0) {
|
||||
stockElement.className = 'text-success';
|
||||
addToCartBtn.disabled = false;
|
||||
buyNowBtn.disabled = false;
|
||||
quantityInput.max = currentSku.stock;
|
||||
quantityInput.value = Math.min(parseInt(quantityInput.value), currentSku.stock);
|
||||
} else {
|
||||
stockElement.className = 'text-danger';
|
||||
addToCartBtn.disabled = true;
|
||||
buyNowBtn.disabled = true;
|
||||
quantityInput.max = 0;
|
||||
quantityInput.value = 1;
|
||||
}
|
||||
|
||||
// 更新价格
|
||||
priceElement.textContent = currentSku.final_price.toFixed(2);
|
||||
} else if (inventoryData.length > 1) {
|
||||
// 多规格商品但未完全选择
|
||||
stockElement.textContent = '请选择规格';
|
||||
stockElement.className = 'text-warning';
|
||||
addToCartBtn.disabled = true;
|
||||
buyNowBtn.disabled = true;
|
||||
quantityInput.max = 999;
|
||||
} else {
|
||||
// 无库存或其他错误
|
||||
stockElement.textContent = '暂无库存';
|
||||
stockElement.className = 'text-danger';
|
||||
addToCartBtn.disabled = true;
|
||||
buyNowBtn.disabled = true;
|
||||
quantityInput.max = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,6 +247,15 @@ function loadCartCount() {
|
||||
});
|
||||
}
|
||||
|
||||
// 更新购物车徽章
|
||||
function updateCartBadge(count) {
|
||||
const badge = document.querySelector('.cart-badge');
|
||||
if (badge) {
|
||||
badge.textContent = count;
|
||||
badge.style.display = count > 0 ? 'inline' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 加入购物车
|
||||
function addToCart() {
|
||||
if (!currentSku) {
|
||||
@ -207,6 +274,10 @@ function addToCart() {
|
||||
addToCartBtn.disabled = true;
|
||||
addToCartBtn.innerHTML = '<i class="bi bi-hourglass-split"></i> 添加中...';
|
||||
|
||||
// 准备规格组合数据
|
||||
const specCombination = Object.keys(selectedSpecs).length > 0 ?
|
||||
Object.entries(selectedSpecs).map(([key, value]) => `${key}:${value}`).join(', ') : '';
|
||||
|
||||
// 提交到购物车
|
||||
fetch('/cart/add', {
|
||||
method: 'POST',
|
||||
@ -216,7 +287,7 @@ function addToCart() {
|
||||
body: JSON.stringify({
|
||||
product_id: window.productId,
|
||||
sku_code: currentSku.sku_code,
|
||||
spec_combination: Object.keys(selectedSpecs).length > 0 ? JSON.stringify(selectedSpecs) : '',
|
||||
spec_combination: specCombination,
|
||||
quantity: quantity
|
||||
})
|
||||
})
|
||||
@ -260,6 +331,15 @@ function buyNow() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 禁用按钮,防止重复点击
|
||||
const buyNowBtn = document.getElementById('buyNowBtn');
|
||||
buyNowBtn.disabled = true;
|
||||
buyNowBtn.innerHTML = '<i class="bi bi-hourglass-split"></i> 处理中...';
|
||||
|
||||
// 准备规格组合数据
|
||||
const specCombination = Object.keys(selectedSpecs).length > 0 ?
|
||||
Object.entries(selectedSpecs).map(([key, value]) => `${key}:${value}`).join(', ') : '';
|
||||
|
||||
// 先添加到购物车,然后跳转到结算页面
|
||||
fetch('/cart/add', {
|
||||
method: 'POST',
|
||||
@ -269,7 +349,7 @@ function buyNow() {
|
||||
body: JSON.stringify({
|
||||
product_id: window.productId,
|
||||
sku_code: currentSku.sku_code,
|
||||
spec_combination: Object.keys(selectedSpecs).length > 0 ? JSON.stringify(selectedSpecs) : '',
|
||||
spec_combination: specCombination,
|
||||
quantity: quantity
|
||||
})
|
||||
})
|
||||
@ -285,6 +365,11 @@ function buyNow() {
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('购买失败,请稍后再试');
|
||||
})
|
||||
.finally(() => {
|
||||
// 恢复按钮状态
|
||||
buyNowBtn.disabled = false;
|
||||
buyNowBtn.innerHTML = '<i class="bi bi-lightning-fill"></i> 立即购买';
|
||||
});
|
||||
}
|
||||
|
||||
@ -304,8 +389,6 @@ function addToFavorites() {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('收藏商品ID:', productId); // 调试信息
|
||||
|
||||
const favoriteBtn = document.querySelector('[onclick="addToFavorites()"]');
|
||||
const isFavorited = favoriteBtn && favoriteBtn.innerHTML.includes('已收藏');
|
||||
|
||||
|
@ -141,7 +141,11 @@
|
||||
<strong>库存:</strong>
|
||||
<span id="stockCount" class="text-success">
|
||||
{% if inventory_list %}
|
||||
{{ inventory_list[0].stock if inventory_list|length == 1 else '请选择规格' }}
|
||||
{% if inventory_list|length == 1 %}
|
||||
{{ inventory_list[0].stock }}
|
||||
{% else %}
|
||||
请选择规格
|
||||
{% endif %}
|
||||
{% else %}
|
||||
暂无库存
|
||||
{% endif %}
|
||||
|
114
create_test_product_with_specs.py
Normal file
114
create_test_product_with_specs.py
Normal file
@ -0,0 +1,114 @@
|
||||
"""
|
||||
创建带规格的测试商品
|
||||
"""
|
||||
from app.models.product import Product, Category, ProductInventory, SpecName, SpecValue
|
||||
from config.database import db
|
||||
import json
|
||||
|
||||
def create_test_product():
|
||||
"""创建测试商品"""
|
||||
try:
|
||||
# 创建或获取测试分类
|
||||
category = Category.query.filter_by(name='测试分类').first()
|
||||
if not category:
|
||||
category = Category(
|
||||
name='测试分类',
|
||||
parent_id=0,
|
||||
level=1,
|
||||
sort_order=1,
|
||||
is_active=1
|
||||
)
|
||||
db.session.add(category)
|
||||
db.session.flush()
|
||||
|
||||
# 创建测试商品
|
||||
product = Product(
|
||||
name='测试商品 - 多规格T恤',
|
||||
category_id=category.id,
|
||||
brand='测试品牌',
|
||||
price=99.00,
|
||||
original_price=129.00,
|
||||
description='这是一件多规格测试商品,支持颜色和尺寸选择',
|
||||
has_specs=1,
|
||||
status=1,
|
||||
weight=0.3
|
||||
)
|
||||
db.session.add(product)
|
||||
db.session.flush()
|
||||
|
||||
# 创建规格名称(如果不存在)
|
||||
color_spec = SpecName.query.filter_by(name='颜色').first()
|
||||
if not color_spec:
|
||||
color_spec = SpecName(name='颜色', sort_order=1)
|
||||
db.session.add(color_spec)
|
||||
db.session.flush()
|
||||
|
||||
size_spec = SpecName.query.filter_by(name='尺寸').first()
|
||||
if not size_spec:
|
||||
size_spec = SpecName(name='尺寸', sort_order=2)
|
||||
db.session.add(size_spec)
|
||||
db.session.flush()
|
||||
|
||||
# 创建规格值(如果不存在)
|
||||
colors = ['红色', '蓝色', '黑色']
|
||||
sizes = ['S', 'M', 'L', 'XL']
|
||||
|
||||
for color in colors:
|
||||
color_value = SpecValue.query.filter_by(spec_name_id=color_spec.id, value=color).first()
|
||||
if not color_value:
|
||||
color_value = SpecValue(spec_name_id=color_spec.id, value=color)
|
||||
db.session.add(color_value)
|
||||
|
||||
for size in sizes:
|
||||
size_value = SpecValue.query.filter_by(spec_name_id=size_spec.id, value=size).first()
|
||||
if not size_value:
|
||||
size_value = SpecValue(spec_name_id=size_spec.id, value=size)
|
||||
db.session.add(size_value)
|
||||
|
||||
db.session.flush()
|
||||
|
||||
# 创建商品库存(多个SKU)
|
||||
sku_data = [
|
||||
{'color': '红色', 'size': 'S', 'stock': 10, 'price_adj': 0, 'is_default': 1},
|
||||
{'color': '红色', 'size': 'M', 'stock': 15, 'price_adj': 0, 'is_default': 0},
|
||||
{'color': '红色', 'size': 'L', 'stock': 12, 'price_adj': 0, 'is_default': 0},
|
||||
{'color': '蓝色', 'size': 'S', 'stock': 8, 'price_adj': 5, 'is_default': 0},
|
||||
{'color': '蓝色', 'size': 'M', 'stock': 20, 'price_adj': 5, 'is_default': 0},
|
||||
{'color': '蓝色', 'size': 'L', 'stock': 18, 'price_adj': 5, 'is_default': 0},
|
||||
{'color': '黑色', 'size': 'M', 'stock': 25, 'price_adj': -5, 'is_default': 0},
|
||||
{'color': '黑色', 'size': 'L', 'stock': 22, 'price_adj': -5, 'is_default': 0},
|
||||
{'color': '黑色', 'size': 'XL', 'stock': 15, 'price_adj': 10, 'is_default': 0},
|
||||
]
|
||||
|
||||
for i, sku_info in enumerate(sku_data):
|
||||
spec_combination = {'颜色': sku_info['color'], '尺寸': sku_info['size']}
|
||||
sku_code = f"TST-{sku_info['color'][:1]}{sku_info['size']}-{product.id:03d}"
|
||||
|
||||
inventory = ProductInventory(
|
||||
product_id=product.id,
|
||||
sku_code=sku_code,
|
||||
spec_combination=spec_combination,
|
||||
price_adjustment=sku_info['price_adj'],
|
||||
stock=sku_info['stock'],
|
||||
warning_stock=5,
|
||||
is_default=sku_info['is_default'],
|
||||
status=1
|
||||
)
|
||||
db.session.add(inventory)
|
||||
|
||||
db.session.commit()
|
||||
print(f"成功创建测试商品: {product.name} (ID: {product.id})")
|
||||
print(f"创建了 {len(sku_data)} 个SKU")
|
||||
return product.id
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"创建失败: {str(e)}")
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
from app import create_app
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
create_test_product()
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user