615 lines
22 KiB
JavaScript
615 lines
22 KiB
JavaScript
// 商品列表页面逻辑 - 使用真实API
|
||
|
||
// 商品列表管理器
|
||
const productList = {
|
||
// 当前筛选和排序状态
|
||
filters: {
|
||
availability: [],
|
||
priceMin: null,
|
||
priceMax: null,
|
||
category: []
|
||
},
|
||
sortBy: 'featured',
|
||
currentPage: 1,
|
||
itemsPerPage: 20,
|
||
|
||
// 所有商品数据
|
||
totalCount: 0,
|
||
|
||
// 分类数据
|
||
categories: [],
|
||
|
||
// 初始化
|
||
init() {
|
||
// 绑定事件
|
||
this.bindEvents();
|
||
|
||
// 加载分类列表
|
||
this.loadCategories();
|
||
|
||
// 加载商品列表
|
||
this.loadProducts();
|
||
|
||
// 加载购物车数量
|
||
this.loadCartCount();
|
||
},
|
||
|
||
// 加载分类列表
|
||
loadCategories() {
|
||
console.log('加载分类列表');
|
||
|
||
API.get('/products/categories', { platform: 'web' })
|
||
.then(data => {
|
||
console.log('分类数据:', data);
|
||
this.categories = data || [];
|
||
this.renderCategories();
|
||
})
|
||
.catch(error => {
|
||
console.error('加载分类失败:', error);
|
||
// 失败时使用空数组,不影响页面其他功能
|
||
this.categories = [];
|
||
});
|
||
},
|
||
|
||
// 渲染分类筛选器(京东风格 - 悬浮窗式)
|
||
renderCategories() {
|
||
if (this.categories.length === 0) {
|
||
console.warn('分类数据为空');
|
||
return;
|
||
}
|
||
|
||
console.log('开始渲染分类,总数:', this.categories.length);
|
||
|
||
const lang = i18n.currentLang;
|
||
const $categoryContainer = $('.filter-group:has(h4[data-i18n="filter_category"]) .filter-options');
|
||
|
||
let categoryHtml = '';
|
||
|
||
// 遍历一级分类
|
||
this.categories.forEach((category, index) => {
|
||
console.log(`渲染一级分类 [${index}]:`, category.id, category.name);
|
||
|
||
const categoryName = this.getCategoryName(category, lang);
|
||
const isSelected = this.filters.category.includes(category.id.toString());
|
||
|
||
// 一级分类
|
||
categoryHtml += `
|
||
<div class="category-group">
|
||
<div class="category-parent ${isSelected ? 'active' : ''}" data-category-id="${category.id}">
|
||
<span class="category-name">${categoryName}</span>
|
||
<span class="category-arrow">›</span>
|
||
</div>
|
||
`;
|
||
|
||
// 如果有二级分类,创建悬浮窗
|
||
if (category.children && category.children.length > 0) {
|
||
console.log(` 二级分类数量: ${category.children.length}`);
|
||
categoryHtml += '<div class="category-popup">';
|
||
categoryHtml += `<div class="category-popup-title">${categoryName}</div>`;
|
||
categoryHtml += '<div class="category-popup-content">';
|
||
|
||
category.children.forEach((child, childIndex) => {
|
||
console.log(` 渲染二级分类 [${childIndex}]:`, child.id, child.name);
|
||
|
||
const childName = this.getCategoryName(child, lang);
|
||
const isChildSelected = this.filters.category.includes(child.id.toString());
|
||
categoryHtml += `
|
||
<span class="category-tag ${isChildSelected ? 'active' : ''}" data-category-id="${child.id}" data-parent="${category.id}">
|
||
${childName}
|
||
</span>
|
||
`;
|
||
});
|
||
|
||
categoryHtml += '</div></div>';
|
||
}
|
||
|
||
categoryHtml += '</div>';
|
||
});
|
||
|
||
console.log('分类 HTML 生成完成');
|
||
$categoryContainer.html(categoryHtml);
|
||
|
||
// 绑定分类点击事件
|
||
this.bindCategoryClick();
|
||
|
||
// 验证渲染结果
|
||
console.log('渲染后的分类元素数量:', $('.category-parent').length);
|
||
$('.category-parent').each(function(idx) {
|
||
const id = $(this).data('category-id');
|
||
const name = $(this).find('.category-name').text();
|
||
console.log(` 分类 [${idx}]: ID=${id}, Name=${name}`);
|
||
});
|
||
},
|
||
|
||
// 绑定分类点击事件
|
||
bindCategoryClick() {
|
||
let hideTimeout = null;
|
||
|
||
// 鼠标进入分类组时,隐藏所有其他悬浮窗,只显示当前的
|
||
$('.category-group').on('mouseenter', function() {
|
||
clearTimeout(hideTimeout);
|
||
|
||
// 隐藏所有悬浮窗
|
||
$('.category-popup').removeClass('show');
|
||
|
||
// 计算当前分类的位置
|
||
const $parent = $(this).find('.category-parent');
|
||
const parentRect = $parent[0].getBoundingClientRect();
|
||
|
||
// 只显示当前的悬浫窗
|
||
const $popup = $(this).find('.category-popup');
|
||
|
||
// 设置悬浮窗位置(在一级分类右侧)
|
||
$popup.css({
|
||
left: parentRect.right + 12 + 'px', // 分类右侧 + 12px 间隙
|
||
top: parentRect.top + 'px' // 与分类顶部对齐
|
||
});
|
||
|
||
$popup.addClass('show');
|
||
});
|
||
|
||
// 鼠标离开分类组时,延迟隐藏悬浮窗
|
||
$('.category-group').on('mouseleave', function(e) {
|
||
const $popup = $(this).find('.category-popup');
|
||
|
||
hideTimeout = setTimeout(() => {
|
||
// 检查鼠标是否在悬浮窗内
|
||
if (!$popup.is(':hover')) {
|
||
$popup.removeClass('show');
|
||
}
|
||
}, 150); // 150ms 延迟
|
||
});
|
||
|
||
// 鼠标进入悬浮窗时,取消隐藏
|
||
$(document).on('mouseenter', '.category-popup', function() {
|
||
clearTimeout(hideTimeout);
|
||
$(this).addClass('show');
|
||
});
|
||
|
||
// 鼠标离开悬浮窗时,隐藏
|
||
$(document).on('mouseleave', '.category-popup', function() {
|
||
$(this).removeClass('show');
|
||
});
|
||
|
||
// 点击一级分类进行筛选(选择该分类下所有商品)
|
||
$(document).on('click', '.category-parent', function(e) {
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
|
||
const categoryId = $(this).data('category-id');
|
||
console.log('点击一级分类,ID:', categoryId);
|
||
|
||
if (!categoryId) {
|
||
console.error('分类 ID 不存在');
|
||
return;
|
||
}
|
||
|
||
const categoryIdStr = categoryId.toString();
|
||
const isActive = $(this).hasClass('active');
|
||
|
||
console.log('当前分类状态:', isActive ? '已选中' : '未选中');
|
||
|
||
// 移除所有选中状态
|
||
$('.category-tag').removeClass('active');
|
||
$('.category-parent').removeClass('active');
|
||
|
||
if (isActive) {
|
||
// 如果已选中,则取消选中(查看全部)
|
||
productList.filters.category = [];
|
||
console.log('取消选中,查看全部商品');
|
||
} else {
|
||
// 选中当前一级分类
|
||
$(this).addClass('active');
|
||
productList.filters.category = [categoryIdStr];
|
||
console.log('选中一级分类:', categoryIdStr);
|
||
}
|
||
|
||
// 重新加载商品
|
||
productList.currentPage = 1;
|
||
productList.loadProducts();
|
||
});
|
||
|
||
// 点击二级分类进行筛选
|
||
$(document).on('click', '.category-tag', function(e) {
|
||
e.stopPropagation();
|
||
e.preventDefault();
|
||
|
||
const categoryId = $(this).data('category-id');
|
||
console.log('点击二级分类,ID:', categoryId);
|
||
|
||
if (!categoryId) {
|
||
console.error('分类 ID 不存在');
|
||
return;
|
||
}
|
||
|
||
const categoryIdStr = categoryId.toString();
|
||
const isActive = $(this).hasClass('active');
|
||
|
||
console.log('当前分类状态:', isActive ? '已选中' : '未选中');
|
||
|
||
// 移除所有选中状态
|
||
$('.category-tag').removeClass('active');
|
||
$('.category-parent').removeClass('active');
|
||
|
||
if (isActive) {
|
||
// 如果已选中,则取消选中(查看全部)
|
||
productList.filters.category = [];
|
||
console.log('取消选中,查看全部商品');
|
||
} else {
|
||
// 选中当前分类
|
||
$(this).addClass('active');
|
||
productList.filters.category = [categoryIdStr];
|
||
console.log('选中二级分类:', categoryIdStr);
|
||
}
|
||
|
||
// 隐藏悬浮窗
|
||
$('.category-popup').removeClass('show');
|
||
|
||
// 重新加载商品
|
||
productList.currentPage = 1;
|
||
productList.loadProducts();
|
||
});
|
||
},
|
||
|
||
// 获取分类名称(支持多语言)
|
||
getCategoryName(category, lang) {
|
||
// 如果有多语言数据
|
||
if (category.name_i18n && category.name_i18n[lang]) {
|
||
return category.name_i18n[lang];
|
||
}
|
||
// 降级使用默认名称
|
||
return category.name || '未知分类';
|
||
},
|
||
|
||
// 加载商品列表
|
||
loadProducts() {
|
||
const params = {
|
||
page: this.currentPage,
|
||
page_size: this.itemsPerPage
|
||
};
|
||
|
||
// 添加价格筛选参数(元转分)
|
||
if (this.filters.priceMin !== null && this.filters.priceMin > 0) {
|
||
params.min_price = Math.round(this.filters.priceMin * 100);
|
||
}
|
||
if (this.filters.priceMax !== null && this.filters.priceMax > 0) {
|
||
params.max_price = Math.round(this.filters.priceMax * 100);
|
||
}
|
||
|
||
// 添加分类筛选参数
|
||
if (this.filters.category && this.filters.category.length > 0) {
|
||
// 如果后端支持多个分类,使用数组
|
||
params.category_ids = this.filters.category.join(',');
|
||
}
|
||
|
||
// 添加库存筛选参数
|
||
if (this.filters.availability && this.filters.availability.length > 0) {
|
||
if (this.filters.availability.includes('in_stock') && !this.filters.availability.includes('out_of_stock')) {
|
||
params.in_stock = true;
|
||
} else if (!this.filters.availability.includes('in_stock') && this.filters.availability.includes('out_of_stock')) {
|
||
params.in_stock = false;
|
||
}
|
||
// 如果同时选中或都未选中,不添加该参数
|
||
}
|
||
|
||
// 添加排序参数
|
||
if (this.sortBy === 'price_asc') {
|
||
params.sort = 'price';
|
||
params.sortType = 0;
|
||
} else if (this.sortBy === 'price_desc') {
|
||
params.sort = 'price';
|
||
params.sortType = 1;
|
||
} else if (this.sortBy === 'date_new') {
|
||
params.sort = 'created_at';
|
||
params.sortType = 1;
|
||
}
|
||
|
||
console.log('加载商品列表,参数:', params);
|
||
|
||
// 显示加载状态
|
||
$('#productGrid').html('<div class="loading">加载中...</div>');
|
||
|
||
// 调用API
|
||
API.get('/products', params)
|
||
.then(data => {
|
||
console.log('商品列表数据:', data);
|
||
const products = data.list || [];
|
||
this.totalCount = data.total || 0;
|
||
|
||
this.renderProducts(products);
|
||
this.updateProductCount();
|
||
this.renderPagination();
|
||
})
|
||
.catch(error => {
|
||
console.error('加载商品列表失败:', error);
|
||
$('#productGrid').html('<div class="error">加载失败,请稍后重试</div>');
|
||
Toast.error(error.message || '加载商品失败');
|
||
});
|
||
},
|
||
|
||
// 渲染商品列表
|
||
renderProducts(products) {
|
||
const lang = i18n.currentLang;
|
||
|
||
if (products.length === 0) {
|
||
// 美化的空状态
|
||
$('#productGrid').html(`
|
||
<div class="empty-state">
|
||
<div class="empty-icon">
|
||
<svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<circle cx="60" cy="60" r="50" fill="#F5F5F5"/>
|
||
<path d="M40 50 L50 60 L40 70 M80 50 L70 60 L80 70 M55 45 L65 75" stroke="#CCCCCC" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||
</svg>
|
||
</div>
|
||
<h3 class="empty-title">暂无商品</h3>
|
||
<p class="empty-desc">请尝试调整筛选条件或清除筛选</p>
|
||
<button class="btn btn-primary empty-btn" onclick="productList.clearFilters()">清除筛选</button>
|
||
</div>
|
||
`);
|
||
return;
|
||
}
|
||
|
||
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;
|
||
const isInStock = product.stock > 0;
|
||
|
||
// 计算折扣
|
||
let discount = 0;
|
||
if (originalPrice && originalPrice > price) {
|
||
discount = Math.round((1 - price / originalPrice) * 100);
|
||
}
|
||
|
||
return `
|
||
<div class="product-card" data-product-id="${product.id}">
|
||
<div class="product-image">
|
||
<a href="product-detail.html?id=${product.id}">
|
||
<img src="${productImage}" alt="${productName}" loading="lazy">
|
||
</a>
|
||
${!isInStock ? '<span class="badge badge-sold-out">已售罄</span>' : ''}
|
||
${discount > 0 ? `<span class="badge badge-sale">-${discount}%</span>` : ''}
|
||
</div>
|
||
<div class="product-info">
|
||
<h3 class="product-title">
|
||
<a href="product-detail.html?id=${product.id}">${productName}</a>
|
||
</h3>
|
||
<div class="product-price">
|
||
<span class="price-current">¥${price.toFixed(2)}</span>
|
||
${originalPrice ? `<span class="price-original">¥${originalPrice.toFixed(2)}</span>` : ''}
|
||
</div>
|
||
<button class="btn btn-primary add-to-cart-btn" data-product-id="${product.id}" ${!isInStock ? 'disabled' : ''}>
|
||
${isInStock ? '<span>加入购物车</span>' : '<span>已售罄</span>'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
$('#productGrid').html(productsHtml);
|
||
},
|
||
|
||
// 更新商品计数
|
||
updateProductCount() {
|
||
$('#productCount').text(this.totalCount);
|
||
},
|
||
|
||
// 渲染分页
|
||
renderPagination() {
|
||
const totalPages = Math.ceil(this.totalCount / this.itemsPerPage);
|
||
|
||
if (totalPages <= 1) {
|
||
$('.pagination').hide();
|
||
return;
|
||
}
|
||
|
||
$('.pagination').show();
|
||
|
||
// 更新上一页按钮状态
|
||
$('.page-btn.prev').prop('disabled', this.currentPage === 1);
|
||
|
||
// 更新下一页按钮状态
|
||
$('.page-btn.next').prop('disabled', this.currentPage === totalPages);
|
||
|
||
// 渲染页码
|
||
let pagesHtml = '';
|
||
const maxPages = 7;
|
||
let startPage = Math.max(1, this.currentPage - Math.floor(maxPages / 2));
|
||
let endPage = Math.min(totalPages, startPage + maxPages - 1);
|
||
|
||
if (endPage - startPage < maxPages - 1) {
|
||
startPage = Math.max(1, endPage - maxPages + 1);
|
||
}
|
||
|
||
if (startPage > 1) {
|
||
pagesHtml += `<button class="page-num" data-page="1">1</button>`;
|
||
if (startPage > 2) {
|
||
pagesHtml += `<span class="page-dots">...</span>`;
|
||
}
|
||
}
|
||
|
||
for (let i = startPage; i <= endPage; i++) {
|
||
pagesHtml += `<button class="page-num ${i === this.currentPage ? 'active' : ''}" data-page="${i}">${i}</button>`;
|
||
}
|
||
|
||
if (endPage < totalPages) {
|
||
if (endPage < totalPages - 1) {
|
||
pagesHtml += `<span class="page-dots">...</span>`;
|
||
}
|
||
pagesHtml += `<button class="page-num" data-page="${totalPages}">${totalPages}</button>`;
|
||
}
|
||
|
||
$('.page-numbers').html(pagesHtml);
|
||
},
|
||
|
||
// 绑定事件
|
||
bindEvents() {
|
||
// 筛选器事件
|
||
$('.filter-options input[type="checkbox"]').on('change', () => {
|
||
this.updateFilters();
|
||
});
|
||
|
||
$('#priceMin, #priceMax').on('input', utils.debounce(() => {
|
||
this.updateFilters();
|
||
}, 500));
|
||
|
||
$('.apply-filter').on('click', () => {
|
||
this.updateFilters();
|
||
});
|
||
|
||
$('.clear-filter').on('click', () => {
|
||
this.clearFilters();
|
||
});
|
||
|
||
// 排序事件
|
||
$('#sortSelect').on('change', (e) => {
|
||
this.sortBy = $(e.target).val();
|
||
this.currentPage = 1;
|
||
this.loadProducts();
|
||
});
|
||
|
||
// 分页事件
|
||
$('.page-btn.prev').on('click', () => {
|
||
if (this.currentPage > 1) {
|
||
this.currentPage--;
|
||
this.loadProducts();
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
}
|
||
});
|
||
|
||
$('.page-btn.next').on('click', () => {
|
||
const totalPages = Math.ceil(this.totalCount / this.itemsPerPage);
|
||
if (this.currentPage < totalPages) {
|
||
this.currentPage++;
|
||
this.loadProducts();
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
}
|
||
});
|
||
|
||
$(document).on('click', '.page-num', (e) => {
|
||
const page = parseInt($(e.target).data('page'));
|
||
if (page && page !== this.currentPage) {
|
||
this.currentPage = page;
|
||
this.loadProducts();
|
||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||
}
|
||
});
|
||
|
||
// 加入购物车事件
|
||
$(document).on('click', '.add-to-cart-btn', (e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
const productId = $(e.currentTarget).data('product-id');
|
||
this.addToCart(productId);
|
||
});
|
||
|
||
// 监听语言切换
|
||
$(document).on('languageChanged', () => {
|
||
// 重新渲染分类(支持多语言)
|
||
this.renderCategories();
|
||
// 重新加载商品
|
||
this.loadProducts();
|
||
});
|
||
},
|
||
|
||
// 更新筛选条件
|
||
updateFilters() {
|
||
// 获取库存状态
|
||
this.filters.availability = [];
|
||
$('input[name="availability"]:checked').each((index, el) => {
|
||
this.filters.availability.push($(el).val());
|
||
});
|
||
|
||
// 获取价格区间
|
||
this.filters.priceMin = parseFloat($('#priceMin').val()) || null;
|
||
this.filters.priceMax = parseFloat($('#priceMax').val()) || null;
|
||
|
||
// 分类由点击事件处理,不需要在这里更新
|
||
|
||
// 重置到第一页
|
||
this.currentPage = 1;
|
||
|
||
// 重新加载商品
|
||
this.loadProducts();
|
||
},
|
||
|
||
// 清除筛选
|
||
clearFilters() {
|
||
$('input[type="checkbox"]').prop('checked', false);
|
||
$('#priceMin, #priceMax').val('');
|
||
|
||
// 清除分类选中状态
|
||
$('.category-tag').removeClass('active');
|
||
$('.category-parent').removeClass('active');
|
||
|
||
this.filters = {
|
||
availability: [],
|
||
priceMin: null,
|
||
priceMax: null,
|
||
category: []
|
||
};
|
||
this.currentPage = 1;
|
||
this.loadProducts();
|
||
},
|
||
|
||
// 加入购物车
|
||
addToCart(productId) {
|
||
console.log('加入购物车:', productId);
|
||
|
||
API.post('/cart', {
|
||
product_id: parseInt(productId),
|
||
quantity: 1
|
||
})
|
||
.then(() => {
|
||
Toast.success('已添加到购物车');
|
||
this.loadCartCount();
|
||
|
||
// 打开购物车抽屉(如果有)
|
||
if (typeof window.openCartDrawer === 'function') {
|
||
window.openCartDrawer();
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('添加到购物车失败:', error);
|
||
Toast.error(error.message || '添加失败');
|
||
});
|
||
},
|
||
|
||
// 加载购物车数量
|
||
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);
|
||
});
|
||
}
|
||
};
|
||
|
||
// 页面加载完成后初始化
|
||
$(document).ready(function() {
|
||
productList.init();
|
||
});
|