Files
ai_dianshang/web/assets/js/product-detail.js

758 lines
25 KiB
JavaScript
Raw Permalink Normal View History

2025-11-28 15:18:10 +08:00
// 商品详情页功能 - 使用真实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);
}