finish_preview
This commit is contained in:
parent
5d73776a71
commit
d1b5e8c5cf
@ -5,6 +5,125 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.product-card:hover {
|
.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);
|
transform: translateY(-5px);
|
||||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,23 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (inventoryData.length === 1) {
|
if (inventoryData.length === 1) {
|
||||||
currentSku = inventoryData[0];
|
currentSku = inventoryData[0];
|
||||||
updateStockInfo();
|
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
|
// 查找匹配的SKU
|
||||||
function findMatchingSku() {
|
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) {
|
for (let sku of inventoryData) {
|
||||||
if (sku.spec_combination) {
|
if (sku.spec_combination) {
|
||||||
let isMatch = true;
|
let isMatch = true;
|
||||||
|
|
||||||
|
// 检查规格数量是否匹配
|
||||||
|
if (Object.keys(sku.spec_combination).length !== selectedSpecNames.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查每个规格是否匹配
|
||||||
for (let [specName, specValue] of Object.entries(selectedSpecs)) {
|
for (let [specName, specValue] of Object.entries(selectedSpecs)) {
|
||||||
if (sku.spec_combination[specName] !== specValue) {
|
if (sku.spec_combination[specName] !== specValue) {
|
||||||
isMatch = false;
|
isMatch = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isMatch && Object.keys(selectedSpecs).length === Object.keys(sku.spec_combination).length) {
|
|
||||||
|
if (isMatch) {
|
||||||
currentSku = sku;
|
currentSku = sku;
|
||||||
updateStockInfo();
|
updateStockInfo();
|
||||||
return;
|
return;
|
||||||
@ -138,24 +182,38 @@ function updateStockInfo() {
|
|||||||
const quantityInput = document.getElementById('quantity');
|
const quantityInput = document.getElementById('quantity');
|
||||||
|
|
||||||
if (currentSku) {
|
if (currentSku) {
|
||||||
|
// 有选中的SKU
|
||||||
stockElement.textContent = currentSku.stock;
|
stockElement.textContent = currentSku.stock;
|
||||||
stockElement.className = currentSku.stock > 0 ? 'text-success' : 'text-danger';
|
|
||||||
priceElement.textContent = currentSku.final_price.toFixed(2);
|
|
||||||
|
|
||||||
if (currentSku.stock > 0) {
|
if (currentSku.stock > 0) {
|
||||||
|
stockElement.className = 'text-success';
|
||||||
addToCartBtn.disabled = false;
|
addToCartBtn.disabled = false;
|
||||||
buyNowBtn.disabled = false;
|
buyNowBtn.disabled = false;
|
||||||
quantityInput.max = currentSku.stock;
|
quantityInput.max = currentSku.stock;
|
||||||
|
quantityInput.value = Math.min(parseInt(quantityInput.value), currentSku.stock);
|
||||||
} else {
|
} else {
|
||||||
|
stockElement.className = 'text-danger';
|
||||||
addToCartBtn.disabled = true;
|
addToCartBtn.disabled = true;
|
||||||
buyNowBtn.disabled = true;
|
buyNowBtn.disabled = true;
|
||||||
quantityInput.max = 0;
|
quantityInput.max = 0;
|
||||||
|
quantityInput.value = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新价格
|
||||||
|
priceElement.textContent = currentSku.final_price.toFixed(2);
|
||||||
} else if (inventoryData.length > 1) {
|
} else if (inventoryData.length > 1) {
|
||||||
|
// 多规格商品但未完全选择
|
||||||
stockElement.textContent = '请选择规格';
|
stockElement.textContent = '请选择规格';
|
||||||
stockElement.className = 'text-warning';
|
stockElement.className = 'text-warning';
|
||||||
addToCartBtn.disabled = true;
|
addToCartBtn.disabled = true;
|
||||||
buyNowBtn.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() {
|
function addToCart() {
|
||||||
if (!currentSku) {
|
if (!currentSku) {
|
||||||
@ -207,6 +274,10 @@ function addToCart() {
|
|||||||
addToCartBtn.disabled = true;
|
addToCartBtn.disabled = true;
|
||||||
addToCartBtn.innerHTML = '<i class="bi bi-hourglass-split"></i> 添加中...';
|
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', {
|
fetch('/cart/add', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -216,7 +287,7 @@ function addToCart() {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
product_id: window.productId,
|
product_id: window.productId,
|
||||||
sku_code: currentSku.sku_code,
|
sku_code: currentSku.sku_code,
|
||||||
spec_combination: Object.keys(selectedSpecs).length > 0 ? JSON.stringify(selectedSpecs) : '',
|
spec_combination: specCombination,
|
||||||
quantity: quantity
|
quantity: quantity
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -260,6 +331,15 @@ function buyNow() {
|
|||||||
return;
|
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', {
|
fetch('/cart/add', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -269,7 +349,7 @@ function buyNow() {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
product_id: window.productId,
|
product_id: window.productId,
|
||||||
sku_code: currentSku.sku_code,
|
sku_code: currentSku.sku_code,
|
||||||
spec_combination: Object.keys(selectedSpecs).length > 0 ? JSON.stringify(selectedSpecs) : '',
|
spec_combination: specCombination,
|
||||||
quantity: quantity
|
quantity: quantity
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -285,6 +365,11 @@ function buyNow() {
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
alert('购买失败,请稍后再试');
|
alert('购买失败,请稍后再试');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// 恢复按钮状态
|
||||||
|
buyNowBtn.disabled = false;
|
||||||
|
buyNowBtn.innerHTML = '<i class="bi bi-lightning-fill"></i> 立即购买';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,8 +389,6 @@ function addToFavorites() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('收藏商品ID:', productId); // 调试信息
|
|
||||||
|
|
||||||
const favoriteBtn = document.querySelector('[onclick="addToFavorites()"]');
|
const favoriteBtn = document.querySelector('[onclick="addToFavorites()"]');
|
||||||
const isFavorited = favoriteBtn && favoriteBtn.innerHTML.includes('已收藏');
|
const isFavorited = favoriteBtn && favoriteBtn.innerHTML.includes('已收藏');
|
||||||
|
|
||||||
|
@ -141,7 +141,11 @@
|
|||||||
<strong>库存:</strong>
|
<strong>库存:</strong>
|
||||||
<span id="stockCount" class="text-success">
|
<span id="stockCount" class="text-success">
|
||||||
{% if inventory_list %}
|
{% 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 %}
|
{% else %}
|
||||||
暂无库存
|
暂无库存
|
||||||
{% endif %}
|
{% 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