finish_preview

This commit is contained in:
superlishunqin 2025-07-09 05:33:23 +08:00
parent 5d73776a71
commit d1b5e8c5cf
5 changed files with 13833 additions and 3669 deletions

View File

@ -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);
}

View File

@ -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('已收藏');

View File

@ -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 %}

View 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()