Files
ai_dianshang/web/assets/js/product-detail.js
2025-11-28 15:18:10 +08:00

758 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 商品详情页功能 - 使用真实API
let currentProduct = null;
let selectedSku = null;
let currentCommentPage = 1;
let currentCommentFilter = 'all'; // all, good, medium, bad, image
// 页面初始化
$(document).ready(function() {
const productId = getProductIdFromUrl();
if (productId) {
loadProductDetail(productId);
loadCommentsCount(productId);
loadComments(productId);
} else {
Toast.error('商品不存在');
setTimeout(() => {
window.location.href = 'index.html';
}, 2000);
}
bindEvents();
loadCartCount();
});
// 从URL获取商品ID
function getProductIdFromUrl() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('id');
}
// 加载商品详情
function loadProductDetail(productId) {
console.log('加载商品详情:', productId);
API.get(`/frontend/products/${productId}/detail`)
.then(data => {
console.log('商品详情数据:', data);
currentProduct = data;
renderProductDetail(data);
loadRelatedProducts();
})
.catch(error => {
console.error('加载商品详情失败:', error);
Toast.error(error.message || '加载商品详情失败');
setTimeout(() => {
window.location.href = 'index.html';
}, 2000);
});
}
// 渲染商品详情
function renderProductDetail(product) {
const lang = i18n.currentLang;
// 设置商品名称
const productName = product.title || product.name || '未知商品';
$('#productName').text(productName);
$('#breadcrumbProduct').text(productName);
document.title = `${productName} - vizee为之甄选`;
// 设置价格
const minPrice = PriceUtils.fenToYuan(product.minSalePrice || product.price || 0);
const maxPrice = PriceUtils.fenToYuan(product.maxSalePrice || product.price || 0);
const originPrice = PriceUtils.fenToYuan(product.maxLinePrice || product.originPrice || 0);
if (minPrice === maxPrice) {
$('#currentPrice').text(`¥${minPrice.toFixed(2)}`);
} else {
$('#currentPrice').text(`¥${minPrice.toFixed(2)} - ¥${maxPrice.toFixed(2)}`);
}
if (originPrice > maxPrice) {
$('#originalPrice').text(`¥${originPrice.toFixed(2)}`).show();
const discount = Math.round((1 - maxPrice / originPrice) * 100);
$('#priceSave').text(`${i18n.t('save') || '省'} ${discount}%`).show();
} else {
$('#originalPrice').hide();
$('#priceSave').hide();
}
// 设置库存状态
const spuStock = product.spuStockQuantity ?? product.stock ?? 0;
const isInStock = spuStock > 0;
if (isInStock) {
$('#stockStatus').text('有货').addClass('in-stock').removeClass('out-of-stock');
} else {
$('#stockStatus').text('缺货').addClass('out-of-stock').removeClass('in-stock');
}
// 保存库存状态到currentProduct
currentProduct.isStock = isInStock;
// 设置图片
loadProductImages(product.images || [product.primaryImage]);
// 设置描述
const description = product.descriptionText || product.details || '';
$('#productDescription').text(description);
// 渲染详情图片
if (product.desc && Array.isArray(product.desc) && product.desc.length > 0) {
const detailImagesHtml = product.desc.map(img =>
`<img src="${img}" alt="商品详情" loading="lazy">`
).join('');
$('#productDetailImages').html(detailImagesHtml);
}
// 渲染规格选择器
if (product.specList && product.specList.length > 0) {
renderSpecSelector(product.specList, product.skuList);
} else {
$('#specSelector').hide();
}
// 显示已售数量
if (product.soldNum) {
$('#soldCount').text(`已售 ${product.soldNum}`).show();
}
}
// 加载商品图片
function loadProductImages(images) {
// 显示加载动画
showImageLoading();
if (!images || images.length === 0) {
// 如果没有图片,隐藏加载动画并显示默认图片
hideImageLoading();
images = ['https://picsum.photos/800/800?random=default'];
}
// 主图
const mainImg = new Image();
mainImg.onload = function() {
$('#mainImage').attr('src', images[0]);
hideImageLoading();
};
mainImg.onerror = function() {
// 加载失败,使用占位图
$('#mainImage').attr('src', 'https://picsum.photos/800/800?random=error');
hideImageLoading();
};
mainImg.src = images[0];
// 缩略图
const thumbnailsHtml = images.map((img, index) =>
`<div class="thumbnail ${index === 0 ? 'active' : ''}" data-index="${index}">
<img src="${img}" alt="商品图片 ${index + 1}" loading="lazy">
</div>`
).join('');
$('#thumbnails').html(thumbnailsHtml);
}
// 显示图片加载动画
function showImageLoading() {
const loadingHtml = `
<div class="image-loading" id="imageLoading">
<div class="loading-spinner"></div>
<div class="loading-text">加载中...</div>
</div>
`;
if ($('#imageLoading').length === 0) {
$('.main-image').append(loadingHtml);
}
$('#mainImage').css('opacity', '0');
}
// 隐藏图片加载动画
function hideImageLoading() {
$('#imageLoading').fadeOut(300, function() {
$(this).remove();
});
$('#mainImage').css('opacity', '1');
}
// 渲染规格选择器
function renderSpecSelector(specList, skuList) {
let specHtml = '';
specList.forEach((spec, specIndex) => {
// 优先使用title其次specName最后name
const specName = spec.title || spec.specName || spec.name || `规格${specIndex + 1}`;
const specValues = spec.specValueList || [];
specHtml += `
<div class="spec-group">
<label class="spec-label">${specName}</label>
<div class="spec-options" data-spec-index="${specIndex}">
${specValues.map((value, valueIndex) => {
const valueName = value.specValue || value.name || value;
return `
<button class="spec-option"
data-spec-index="${specIndex}"
data-value-index="${valueIndex}"
data-spec-value="${valueName}">
${valueName}
</button>
`;
}).join('')}
</div>
</div>
`;
});
$('#specSelector').html(specHtml).show();
}
// 加载相关商品
function loadRelatedProducts() {
API.get('/products', { page: 1, page_size: 4 })
.then(data => {
const products = data.list || [];
renderRelatedProducts(products.slice(0, 4));
})
.catch(error => {
console.error('加载相关商品失败:', error);
});
}
// 渲染相关商品
function renderRelatedProducts(products) {
const lang = i18n.currentLang;
const productsHtml = products.map(product => {
const productName = product.name || '未知商品';
const productImage = product.main_image || 'https://picsum.photos/400/400?random=default';
const price = PriceUtils.fenToYuan(product.price || 0);
const originalPrice = product.orig_price ? PriceUtils.fenToYuan(product.orig_price) : null;
return `
<div class="product-card">
<a href="product-detail.html?id=${product.id}">
<div class="product-image">
<img src="${productImage}" alt="${productName}" loading="lazy">
</div>
<div class="product-info">
<h3 class="product-title">${productName}</h3>
<div class="product-price">
<span class="price-current">¥${price.toFixed(2)}</span>
${originalPrice ? `<span class="price-original">¥${originalPrice.toFixed(2)}</span>` : ''}
</div>
</div>
</a>
</div>
`;
}).join('');
$('#relatedProducts').html(productsHtml);
}
// 绑定事件
function bindEvents() {
// 缩略图点击
$(document).on('click', '.thumbnail', function() {
const index = $(this).data('index');
const imgSrc = $(this).find('img').attr('src');
$('#mainImage').attr('src', imgSrc);
$('.thumbnail').removeClass('active');
$(this).addClass('active');
});
// 数量增减
$('.qty-btn.minus').on('click', function() {
const $input = $('#quantityInput');
let qty = parseInt($input.val()) || 1;
if (qty > 1) {
$input.val(qty - 1);
}
});
$('.qty-btn.plus').on('click', function() {
const $input = $('#quantityInput');
let qty = parseInt($input.val()) || 1;
const max = parseInt($input.attr('max')) || 99;
if (qty < max) {
$input.val(qty + 1);
}
});
$('#quantityInput').on('change', function() {
let qty = parseInt($(this).val()) || 1;
const max = parseInt($(this).attr('max')) || 99;
if (qty < 1) qty = 1;
if (qty > max) qty = max;
$(this).val(qty);
});
// 规格选择
$(document).on('click', '.spec-option', function() {
const $option = $(this);
const specIndex = $option.data('spec-index');
// 取消同组其他选项
$(`.spec-option[data-spec-index="${specIndex}"]`).removeClass('selected');
// 选中当前选项
$option.addClass('selected');
// 更新选中的SKU
updateSelectedSku();
});
// 评论标签切换
$(document).on('click', '.comment-tab', function() {
const $tab = $(this);
const filter = $tab.data('filter');
$('.comment-tab').removeClass('active');
$tab.addClass('active');
currentCommentFilter = filter;
currentCommentPage = 1;
const productId = getProductIdFromUrl();
loadComments(productId, 1, filter);
});
// 评论分页
$(document).on('click', '#commentPagination .page-btn', function() {
const page = parseInt($(this).data('page'));
const productId = getProductIdFromUrl();
currentCommentPage = page;
loadComments(productId, page, currentCommentFilter);
// 滚动到评论区域
$('html, body').animate({
scrollTop: $('#reviews').offset().top - 100
}, 300);
});
// Tab切换
$(document).on('click', '.tab-header', function() {
const $tab = $(this);
const tabId = $tab.data('tab');
$('.tab-header').removeClass('active');
$tab.addClass('active');
$('.tab-content').removeClass('active');
$(`#${tabId}`).addClass('active');
});
// 立即购买
$('#buyNowBtn').on('click', function() {
if (!currentProduct || !currentProduct.isStock) {
Toast.error('商品缺货');
return;
}
// 如果有规格,检查是否已选择
if (currentProduct.specList && currentProduct.specList.length > 0) {
const selectedCount = $('.spec-option.selected').length;
if (selectedCount < currentProduct.specList.length) {
// 找到未选择的规格组
const unselectedSpecs = [];
currentProduct.specList.forEach((spec, index) => {
const hasSelected = $(`.spec-option.selected[data-spec-index="${index}"]`).length > 0;
if (!hasSelected) {
const specName = spec.title || spec.specName || spec.name || `规格${index + 1}`;
unselectedSpecs.push(specName);
// 高亮未选择的规格组
$(`.spec-options[data-spec-index="${index}"]`).addClass('spec-required');
setTimeout(() => {
$(`.spec-options[data-spec-index="${index}"]`).removeClass('spec-required');
}, 2000);
}
});
Toast.error(`请选择${unselectedSpecs.join('、')}`);
return;
}
}
const quantity = parseInt($('#quantityInput').val()) || 1;
// 先加入购物车,然后跳转结算
addToCart(quantity, true);
});
// 加入购物车
$('#addToCartBtn').on('click', function() {
if (!currentProduct || !currentProduct.isStock) {
Toast.error('商品缺货');
return;
}
// 如果有规格,检查是否已选择
if (currentProduct.specList && currentProduct.specList.length > 0) {
const selectedCount = $('.spec-option.selected').length;
if (selectedCount < currentProduct.specList.length) {
// 找到未选择的规格组
const unselectedSpecs = [];
currentProduct.specList.forEach((spec, index) => {
const hasSelected = $(`.spec-option.selected[data-spec-index="${index}"]`).length > 0;
if (!hasSelected) {
const specName = spec.title || spec.specName || spec.name || `规格${index + 1}`;
unselectedSpecs.push(specName);
// 高亮未选择的规格组
$(`.spec-options[data-spec-index="${index}"]`).addClass('spec-required');
setTimeout(() => {
$(`.spec-options[data-spec-index="${index}"]`).removeClass('spec-required');
}, 2000);
}
});
Toast.error(`请选择${unselectedSpecs.join('、')}`);
return;
}
}
const quantity = parseInt($('#quantityInput').val()) || 1;
addToCart(quantity, false);
});
}
// 更新选中的SKU
function updateSelectedSku() {
if (!currentProduct || !currentProduct.skuList || currentProduct.skuList.length === 0) {
return;
}
// 获取所有选中的规格值
const selectedSpecs = [];
$('.spec-option.selected').each(function() {
selectedSpecs.push($(this).data('spec-value'));
});
// 查找匹配的SKU
const sku = currentProduct.skuList.find(s => {
if (!s.specInfo || s.specInfo.length !== selectedSpecs.length) {
return false;
}
return s.specInfo.every(spec => selectedSpecs.includes(spec.specValue));
});
if (sku) {
selectedSku = sku;
// 更新价格显示
const price = PriceUtils.fenToYuan(sku.priceInfo && sku.priceInfo[0] ? sku.priceInfo[0].price : 0);
$('#currentPrice').text(`¥${price.toFixed(2)}`);
// 更新库存
const stock = sku.stockQuantity || 0;
if (stock > 0) {
$('#stockStatus').text('有货').addClass('in-stock').removeClass('out-of-stock');
$('#quantity').attr('max', stock);
} else {
$('#stockStatus').text('缺货').addClass('out-of-stock').removeClass('in-stock');
}
}
}
// 加入购物车
function addToCart(quantity, buyNow = false) {
// 如果是立即购买,直接跳转到结算页,不需要加入购物车
if (buyNow) {
const productId = currentProduct.spuId || currentProduct.id;
const skuId = selectedSku ? (selectedSku.skuId || selectedSku.id) : '';
// 构建 URL 参数
const params = new URLSearchParams({
type: 'buynow',
product_id: productId,
quantity: quantity
});
if (skuId) {
params.append('sku_id', skuId);
}
// 直接跳转到结算页
window.location.href = `checkout.html?${params.toString()}`;
return;
}
// 普通加入购物车流程
const data = {
product_id: parseInt(currentProduct.spuId || currentProduct.id),
quantity: quantity
};
// 如果选择了SKU
if (selectedSku) {
data.sku_id = parseInt(selectedSku.skuId || selectedSku.id);
}
console.log('加入购物车:', data);
API.post('/cart', data)
.then(() => {
Toast.success('已添加到购物车');
loadCartCount();
// 打开购物车抽屉
setTimeout(() => {
if (typeof window.openCartDrawer === 'function') {
window.openCartDrawer();
}
}, 300);
})
.catch(error => {
console.error('添加到购物车失败:', error);
Toast.error(error.message || '添加失败');
});
}
// 加载购物车数量
function loadCartCount() {
// 检查是否登录,未登录时不请求
const user = localStorage.getItem('currentUser');
if (!user) {
$('.cart-count').text(0);
return;
}
try {
const userData = JSON.parse(user);
if (!userData.token) {
$('.cart-count').text(0);
return;
}
} catch (e) {
$('.cart-count').text(0);
return;
}
API.get('/cart')
.then(data => {
const totalQuantity = data.total_quantity || 0;
$('.cart-count').text(totalQuantity);
})
.catch(error => {
console.error('加载购物车数量失败:', error);
$('.cart-count').text(0);
});
}
// 加载评论统计
function loadCommentsCount(productId) {
API.get(`/comments/products/${productId}/stats`)
.then(data => {
const totalCount = data.total_count || 0;
const avgRating = data.average_rating || 0;
const goodCount = (data.rating_4_count || 0) + (data.rating_5_count || 0);
const mediumCount = data.rating_3_count || 0;
const badCount = (data.rating_1_count || 0) + (data.rating_2_count || 0);
const imageCount = data.has_images_count || 0;
// 更新评论数量显示
$('#reviewCount').text(`${totalCount} reviews`);
// 更新星级显示
renderStars(avgRating);
// 更新评论标签
updateCommentTabs(totalCount, goodCount, mediumCount, badCount, imageCount);
})
.catch(error => {
console.error('加载评论统计失败:', error);
});
}
// 渲染星级
function renderStars(rating) {
const fullStars = Math.floor(rating);
const hasHalfStar = rating % 1 >= 0.5;
let starsHtml = '';
for (let i = 0; i < 5; i++) {
if (i < fullStars) {
starsHtml += '★';
} else if (i === fullStars && hasHalfStar) {
starsHtml += '☆';
} else {
starsHtml += '☆';
}
}
$('#productStars').html(starsHtml);
}
// 更新评论标签
function updateCommentTabs(total, good, medium, bad, image) {
const tabsHtml = `
<div class="comment-tabs">
<button class="comment-tab active" data-filter="all">全部 (${total})</button>
<button class="comment-tab" data-filter="good">好评 (${good})</button>
<button class="comment-tab" data-filter="medium">中评 (${medium})</button>
<button class="comment-tab" data-filter="bad">差评 (${bad})</button>
<button class="comment-tab" data-filter="image">有图 (${image})</button>
</div>
`;
$('#reviewsList').before(tabsHtml);
}
// 加载评论列表
function loadComments(productId, page = 1, filter = 'all') {
const params = {
page: page,
page_size: 10
};
// 根据筛选条件添加参数
if (filter === 'good') {
params.rating = 4; // 4-5星
} else if (filter === 'medium') {
params.rating = 3; // 3星
} else if (filter === 'bad') {
params.rating = 2; // 1-2星
} else if (filter === 'image') {
params.has_images = true;
}
API.get(`/comments/products/${productId}`, params)
.then(data => {
const comments = data.list || [];
renderComments(comments);
// 更新分页
renderCommentPagination(data.page, data.total, data.page_size);
})
.catch(error => {
console.error('加载评论失败:', error);
$('#reviewsList').html('<div class="empty-reviews">暂无评论</div>');
});
}
// 渲染评论列表
function renderComments(comments) {
if (comments.length === 0) {
$('#reviewsList').html('<div class="empty-reviews">暂无评论</div>');
return;
}
const commentsHtml = comments.map(comment => {
const userName = comment.user_name || '匿名用户';
const userAvatar = comment.user_avatar || 'https://picsum.photos/40/40?random=' + comment.id;
const rating = comment.rating || 5;
const content = comment.content || '';
const images = comment.images || [];
const createdAt = formatDate(comment.created_at);
const productSpec = comment.product_spec || '';
const replyContent = comment.reply_content || '';
// 星级
let starsHtml = '';
for (let i = 0; i < 5; i++) {
starsHtml += i < rating ? '★' : '☆';
}
// 图片
let imagesHtml = '';
if (images.length > 0) {
imagesHtml = `
<div class="comment-images">
${images.map(img => `
<img src="${img}" alt="评论图片" class="comment-image" onclick="previewImage('${img}')">
`).join('')}
</div>
`;
}
// 商家回复
let replyHtml = '';
if (replyContent) {
replyHtml = `
<div class="merchant-reply">
<div class="reply-label">商家回复:</div>
<div class="reply-content">${replyContent}</div>
</div>
`;
}
return `
<div class="review-item">
<div class="review-header">
<div class="review-author-info">
<img src="${userAvatar}" alt="${userName}" class="review-avatar">
<div>
<div class="review-author-name">${userName}</div>
${productSpec ? `<div class="review-spec">${productSpec}</div>` : ''}
</div>
</div>
<div class="review-meta">
<div class="review-rating">${starsHtml}</div>
<div class="review-date">${createdAt}</div>
</div>
</div>
<div class="review-content">
<p class="review-text">${content}</p>
${imagesHtml}
</div>
${replyHtml}
</div>
`;
}).join('');
$('#reviewsList').html(commentsHtml);
}
// 渲染评论分页
function renderCommentPagination(currentPage, total, pageSize) {
const totalPages = Math.ceil(total / pageSize);
if (totalPages <= 1) {
$('#commentPagination').hide();
return;
}
let paginationHtml = `<div class="pagination">`;
// 上一页
if (currentPage > 1) {
paginationHtml += `<button class="page-btn" data-page="${currentPage - 1}">上一页</button>`;
}
// 页码
for (let i = 1; i <= Math.min(totalPages, 5); i++) {
const active = i === currentPage ? 'active' : '';
paginationHtml += `<button class="page-btn ${active}" data-page="${i}">${i}</button>`;
}
// 下一页
if (currentPage < totalPages) {
paginationHtml += `<button class="page-btn" data-page="${currentPage + 1}">下一页</button>`;
}
paginationHtml += `</div>`;
$('#commentPagination').html(paginationHtml).show();
}
// 格式化日期
function formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 图片预览
function previewImage(imgUrl) {
// 创建预览模态框
const modal = `
<div class="image-preview-modal" onclick="this.remove()">
<img src="${imgUrl}" alt="预览图片">
</div>
`;
$('body').append(modal);
}