1030 lines
38 KiB
JavaScript
1030 lines
38 KiB
JavaScript
// 通用工具函数和全局配置
|
||
|
||
// API配置
|
||
const API_BASE_URL = 'http://localhost:8080/api';
|
||
|
||
// 工具函数
|
||
const utils = {
|
||
// 格式化价格
|
||
// 注:如果price已经是元单位,直接传入;如果是后端返回的分单位,请使用 PriceUtils.formatPrice()
|
||
formatPrice(price, currency = '¥') {
|
||
return `${currency}${parseFloat(price).toFixed(2)}`;
|
||
},
|
||
|
||
// 格式化日期
|
||
formatDate(date) {
|
||
const d = new Date(date);
|
||
const year = d.getFullYear();
|
||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||
const day = String(d.getDate()).padStart(2, '0');
|
||
return `${year}-${month}-${day}`;
|
||
},
|
||
|
||
// 防抖函数
|
||
debounce(func, wait) {
|
||
let timeout;
|
||
return function executedFunction(...args) {
|
||
const later = () => {
|
||
clearTimeout(timeout);
|
||
func(...args);
|
||
};
|
||
clearTimeout(timeout);
|
||
timeout = setTimeout(later, wait);
|
||
};
|
||
},
|
||
|
||
// 节流函数
|
||
throttle(func, limit) {
|
||
let inThrottle;
|
||
return function(...args) {
|
||
if (!inThrottle) {
|
||
func.apply(this, args);
|
||
inThrottle = true;
|
||
setTimeout(() => inThrottle = false, limit);
|
||
}
|
||
};
|
||
},
|
||
|
||
// 显示提示消息
|
||
showMessage(message, type = 'info') {
|
||
// 移除已存在的消息
|
||
$('.message-toast').remove();
|
||
|
||
const colors = {
|
||
success: '#51cf66',
|
||
error: '#ff6b6b',
|
||
warning: '#ffd43b',
|
||
info: '#4ecdc4'
|
||
};
|
||
|
||
const $toast = $(`
|
||
<div class="message-toast" style="
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
padding: 15px 25px;
|
||
background-color: ${colors[type] || colors.info};
|
||
color: white;
|
||
border-radius: 4px;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||
z-index: 9999;
|
||
animation: slideIn 0.3s ease;
|
||
font-size: 14px;
|
||
max-width: 300px;
|
||
">
|
||
${message}
|
||
</div>
|
||
`);
|
||
|
||
$('body').append($toast);
|
||
|
||
setTimeout(() => {
|
||
$toast.fadeOut(300, function() {
|
||
$(this).remove();
|
||
});
|
||
}, 3000);
|
||
},
|
||
|
||
// 加载中状态
|
||
showLoading(container) {
|
||
const $loading = $(`
|
||
<div class="loading-container">
|
||
<div class="loading"></div>
|
||
</div>
|
||
`);
|
||
$(container).html($loading);
|
||
},
|
||
|
||
// 获取URL参数
|
||
getUrlParams() {
|
||
const params = {};
|
||
const searchParams = new URLSearchParams(window.location.search);
|
||
for (const [key, value] of searchParams) {
|
||
params[key] = value;
|
||
}
|
||
return params;
|
||
},
|
||
|
||
// 设置URL参数(不刷新页面)
|
||
setUrlParam(key, value) {
|
||
const url = new URL(window.location);
|
||
if (value === null || value === undefined || value === '') {
|
||
url.searchParams.delete(key);
|
||
} else {
|
||
url.searchParams.set(key, value);
|
||
}
|
||
window.history.pushState({}, '', url);
|
||
}
|
||
};
|
||
|
||
// 购物车管理 - 使用真实API
|
||
const cart = {
|
||
// 检查用户是否登录
|
||
isLoggedIn() {
|
||
const user = localStorage.getItem('currentUser');
|
||
if (!user) return false;
|
||
try {
|
||
const userData = JSON.parse(user);
|
||
return !!userData.token;
|
||
} catch (e) {
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// 加载购物车数据(从后端)
|
||
loadCart() {
|
||
// 未登录时不请求购物车,直接返回空
|
||
if (!this.isLoggedIn()) {
|
||
console.log('[Cart] 用户未登录,跳过购物车查询');
|
||
this.updateCartCount(0);
|
||
return Promise.resolve([]);
|
||
}
|
||
|
||
console.log('[Cart] 用户已登录,请求购物车数据');
|
||
return API.get('/cart')
|
||
.then(data => {
|
||
console.log('[Cart] 购物车数据:', data);
|
||
const items = data.items || [];
|
||
// 后端返回的是 count 字段,不是 total_quantity
|
||
const totalQuantity = data.count || 0;
|
||
console.log('[Cart] 商品总数:', totalQuantity);
|
||
this.updateCartCount(totalQuantity);
|
||
return items;
|
||
})
|
||
.catch(error => {
|
||
console.error('加载购物车失败:', error);
|
||
this.updateCartCount(0);
|
||
return [];
|
||
});
|
||
},
|
||
|
||
// 更新购物车数量显示
|
||
updateCartCount(count) {
|
||
console.log('[Cart] updateCartCount 被调用,count =', count);
|
||
if (count === undefined) {
|
||
// 如果没有传入count,从API获取
|
||
console.log('[Cart] count 未定义,调用 loadCart()');
|
||
this.loadCart();
|
||
} else {
|
||
console.log('[Cart] 更新购物车数量显示:', count);
|
||
$('.cart-count').text(count);
|
||
}
|
||
},
|
||
|
||
// 打开购物车抽屉
|
||
openDrawer() {
|
||
console.log('=== cart.openDrawer() 被调用 ===');
|
||
|
||
// 每次打开时先移除旧的抽屉(如果存在)
|
||
if ($('#cartDrawer').length > 0) {
|
||
console.log('移除旧的购物车抽屉');
|
||
$('#cartDrawer').remove();
|
||
}
|
||
|
||
// 创建新的抽屉
|
||
console.log('创建新的购物车抽屉');
|
||
this.createDrawer();
|
||
|
||
// 加载购物车数据
|
||
console.log('加载购物车数据');
|
||
this.loadCartData();
|
||
|
||
// 显示抽屉(需要小延迟以确保 DOM 渲染完成)
|
||
setTimeout(() => {
|
||
console.log('显示购物车抽屉');
|
||
$('#cartDrawer').addClass('active');
|
||
}, 10);
|
||
|
||
$('body').css('overflow', 'hidden');
|
||
console.log('=== openDrawer() 执行完成 ===');
|
||
},
|
||
|
||
// 关闭购物车抽屉
|
||
closeDrawer() {
|
||
$('#cartDrawer').removeClass('active');
|
||
$('body').css('overflow', '');
|
||
|
||
// 延迟移除遵罩层,等待动画完成
|
||
setTimeout(() => {
|
||
$('#cartDrawer').remove();
|
||
}, 300);
|
||
},
|
||
|
||
// 创建购物车抽屉HTML
|
||
createDrawer() {
|
||
const drawerHtml = `
|
||
<div class="cart-drawer" id="cartDrawer">
|
||
<div class="cart-drawer-overlay"></div>
|
||
<div class="cart-drawer-content">
|
||
<button class="cart-drawer-close" aria-label="Close">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||
</svg>
|
||
</button>
|
||
|
||
<div class="cart-drawer-body">
|
||
<!-- Left Column: Recommendations -->
|
||
<div class="recommendations-column">
|
||
<h4 data-i18n="you_may_also_like">您可能还喜欢</h4>
|
||
<div class="recommendations-list" id="recommendationsList">
|
||
<!-- Recommendations will be loaded here -->
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right Column: Cart Items -->
|
||
<div class="cart-column">
|
||
<div class="cart-header">
|
||
<h3 data-i18n="cart_title">购物车</h3>
|
||
</div>
|
||
|
||
<div class="cart-items-list" id="cartItemsList">
|
||
<!-- Cart items will be loaded here -->
|
||
</div>
|
||
|
||
<div class="cart-footer">
|
||
<div class="cart-total">
|
||
<span data-i18n="total_label">总计:</span>
|
||
<span class="total-amount" id="cartTotalAmount">¥0.00</span>
|
||
</div>
|
||
<button class="btn btn-primary btn-checkout" onclick="window.location.href='checkout.html'">
|
||
<span data-i18n="checkout">结算</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
$('body').append(drawerHtml);
|
||
|
||
// 添加必要的CSS
|
||
if (!$('#cart-drawer-styles').length) {
|
||
$('head').append(`
|
||
<style id="cart-drawer-styles">
|
||
.cart-drawer {
|
||
position: fixed;
|
||
top: 0;
|
||
right: -100%;
|
||
width: 100%;
|
||
max-width: 900px;
|
||
height: 100vh;
|
||
z-index: 10000;
|
||
transition: right 0.3s ease;
|
||
display: none;
|
||
}
|
||
.cart-drawer.active {
|
||
right: 0;
|
||
display: block;
|
||
}
|
||
.cart-drawer-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
z-index: 9999;
|
||
opacity: 0;
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
.cart-drawer.active .cart-drawer-overlay {
|
||
opacity: 1;
|
||
}
|
||
.cart-drawer-content {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: white;
|
||
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
|
||
display: flex;
|
||
flex-direction: column;
|
||
z-index: 10001;
|
||
}
|
||
.cart-drawer-close {
|
||
position: absolute;
|
||
top: 15px;
|
||
right: 20px;
|
||
background: transparent;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 8px;
|
||
z-index: 10;
|
||
color: #666;
|
||
transition: color 0.3s ease;
|
||
}
|
||
.cart-drawer-close:hover {
|
||
color: #000;
|
||
}
|
||
.cart-drawer-body {
|
||
display: flex;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
padding: 0;
|
||
}
|
||
.recommendations-column {
|
||
width: 280px;
|
||
padding: 70px 20px 20px;
|
||
border-right: 1px solid #e5e5e5;
|
||
background-color: #fafafa;
|
||
overflow-y: auto;
|
||
flex-shrink: 0;
|
||
}
|
||
.recommendations-column h4 {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
margin-bottom: 20px;
|
||
color: #333;
|
||
}
|
||
.cart-column {
|
||
flex: 1;
|
||
padding: 70px 20px 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
.cart-header {
|
||
margin-bottom: 20px;
|
||
}
|
||
.cart-header h3 {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
.cart-items-list {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
margin-bottom: 20px;
|
||
}
|
||
.cart-item {
|
||
display: flex;
|
||
gap: 12px;
|
||
padding: 12px;
|
||
margin-bottom: 10px;
|
||
background: #fafafa;
|
||
border-radius: 8px;
|
||
transition: background 0.3s ease;
|
||
}
|
||
.cart-item:hover {
|
||
background: #f0f0f0;
|
||
}
|
||
.cart-item-image {
|
||
width: 80px;
|
||
height: 80px;
|
||
object-fit: cover;
|
||
border-radius: 6px;
|
||
flex-shrink: 0;
|
||
cursor: pointer;
|
||
}
|
||
.cart-item-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
min-width: 0;
|
||
}
|
||
.cart-item-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
margin-bottom: 6px;
|
||
}
|
||
.cart-item-name {
|
||
flex: 1;
|
||
font-weight: 500;
|
||
color: #333;
|
||
font-size: 14px;
|
||
line-height: 1.4;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
word-break: break-word;
|
||
cursor: pointer;
|
||
}
|
||
.cart-item-name:hover {
|
||
color: #ff6b6b;
|
||
}
|
||
.cart-item-spec {
|
||
font-size: 12px;
|
||
color: #999;
|
||
margin-bottom: 6px;
|
||
}
|
||
.cart-item-bottom {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
}
|
||
.cart-item-price {
|
||
font-weight: 600;
|
||
color: #ff6b6b;
|
||
font-size: 16px;
|
||
}
|
||
.cart-item-quantity {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0;
|
||
border: 1px solid #e5e5e5;
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
.quantity-btn {
|
||
width: 28px;
|
||
height: 28px;
|
||
border: none;
|
||
background: #f5f5f5;
|
||
color: #666;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease;
|
||
}
|
||
.quantity-btn:hover {
|
||
background: #e5e5e5;
|
||
color: #333;
|
||
}
|
||
.quantity-btn:active {
|
||
background: #d5d5d5;
|
||
}
|
||
.quantity-input {
|
||
width: 40px;
|
||
height: 28px;
|
||
border: none;
|
||
border-left: 1px solid #e5e5e5;
|
||
border-right: 1px solid #e5e5e5;
|
||
text-align: center;
|
||
font-size: 14px;
|
||
background: white;
|
||
}
|
||
.cart-item-remove {
|
||
width: 28px;
|
||
height: 28px;
|
||
background: transparent;
|
||
border: none;
|
||
color: #999;
|
||
cursor: pointer;
|
||
padding: 4px;
|
||
border-radius: 4px;
|
||
transition: all 0.3s ease;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.cart-item-remove:hover {
|
||
background: #ffebee;
|
||
color: #ff6b6b;
|
||
}
|
||
.cart-item-remove svg {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
.cart-footer {
|
||
padding: 20px;
|
||
border-top: 2px solid #e5e5e5;
|
||
background: white;
|
||
}
|
||
.cart-total {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin-bottom: 20px;
|
||
color: #333;
|
||
}
|
||
.total-amount {
|
||
color: #ff6b6b;
|
||
font-size: 24px;
|
||
}
|
||
.btn-checkout {
|
||
width: 100%;
|
||
padding: 15px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
border: none;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
.recommendation-item {
|
||
margin-bottom: 20px;
|
||
cursor: pointer;
|
||
transition: transform 0.3s ease;
|
||
background: white;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||
}
|
||
.recommendation-item:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
|
||
}
|
||
.recommendation-item img {
|
||
width: 100%;
|
||
height: 180px;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
.recommendation-item-info {
|
||
padding: 12px;
|
||
}
|
||
.recommendation-item-name {
|
||
font-size: 13px;
|
||
margin-bottom: 8px;
|
||
color: #333;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
line-height: 1.4;
|
||
}
|
||
.recommendation-item-price {
|
||
font-weight: 600;
|
||
color: #ff6b6b;
|
||
font-size: 15px;
|
||
}
|
||
.empty-cart {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
color: #999;
|
||
font-size: 14px;
|
||
}
|
||
@media (max-width: 768px) {
|
||
.recommendations-column {
|
||
display: none;
|
||
}
|
||
.cart-column {
|
||
padding-left: 0;
|
||
}
|
||
.cart-drawer {
|
||
max-width: 100%;
|
||
}
|
||
}
|
||
</style>
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
}
|
||
.cart-footer {
|
||
padding: 20px 0;
|
||
border-top: 2px solid #e0e0e0;
|
||
}
|
||
.cart-total {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin-bottom: 15px;
|
||
}
|
||
.recommendation-item {
|
||
margin-bottom: 15px;
|
||
cursor: pointer;
|
||
}
|
||
.recommendation-item img {
|
||
width: 100%;
|
||
border-radius: 4px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.recommendation-item-name {
|
||
font-size: 14px;
|
||
margin-bottom: 5px;
|
||
}
|
||
.recommendation-item-price {
|
||
font-weight: 600;
|
||
color: var(--primary-color);
|
||
}
|
||
@media (max-width: 768px) {
|
||
.recommendations-column {
|
||
display: none;
|
||
}
|
||
.cart-column {
|
||
padding-left: 0;
|
||
}
|
||
}
|
||
</style>
|
||
`);
|
||
}
|
||
|
||
// 绑定事件
|
||
this.bindDrawerEvents();
|
||
},
|
||
|
||
// 绑定抽屉事件
|
||
bindDrawerEvents() {
|
||
// 关闭按钮
|
||
$(document).on('click', '.cart-drawer-close, .cart-drawer-overlay', () => {
|
||
this.closeDrawer();
|
||
});
|
||
|
||
// 删除商品
|
||
$(document).on('click', '.cart-item-remove', (e) => {
|
||
e.stopPropagation();
|
||
const productId = $(e.currentTarget).data('product-id');
|
||
const skuId = $(e.currentTarget).data('sku-id');
|
||
this.removeItem(productId, skuId);
|
||
});
|
||
|
||
// 增加数量
|
||
$(document).on('click', '.quantity-increase', (e) => {
|
||
e.stopPropagation();
|
||
const productId = $(e.currentTarget).data('product-id');
|
||
const skuId = $(e.currentTarget).data('sku-id');
|
||
const $input = $(e.currentTarget).siblings('.quantity-input');
|
||
const currentQty = parseInt($input.val()) || 1;
|
||
this.updateQuantity(productId, skuId, currentQty + 1);
|
||
});
|
||
|
||
// 减少数量
|
||
$(document).on('click', '.quantity-decrease', (e) => {
|
||
e.stopPropagation();
|
||
const productId = $(e.currentTarget).data('product-id');
|
||
const skuId = $(e.currentTarget).data('sku-id');
|
||
const $input = $(e.currentTarget).siblings('.quantity-input');
|
||
const currentQty = parseInt($input.val()) || 1;
|
||
if (currentQty > 1) {
|
||
this.updateQuantity(productId, skuId, currentQty - 1);
|
||
}
|
||
});
|
||
|
||
// 推荐商品点击
|
||
$(document).on('click', '.recommendation-item', (e) => {
|
||
const productId = $(e.currentTarget).data('product-id');
|
||
window.location.href = `product-detail.html?id=${productId}`;
|
||
});
|
||
|
||
// 购物车商品图片和名称点击跳转
|
||
$(document).on('click', '.cart-item-image, .cart-item-name', (e) => {
|
||
e.stopPropagation();
|
||
const productId = $(e.currentTarget).data('product-id');
|
||
if (productId) {
|
||
window.location.href = `product-detail.html?id=${productId}`;
|
||
}
|
||
});
|
||
},
|
||
|
||
// 加载购物车数据到抽屉
|
||
loadCartData() {
|
||
// 未登录时提示需要登录
|
||
if (!this.isLoggedIn()) {
|
||
$('#cartItemsList').html('<div class="empty-cart">请先登录查看购物车</div>');
|
||
return;
|
||
}
|
||
|
||
API.get('/cart')
|
||
.then(data => {
|
||
const items = data.items || [];
|
||
this.renderCartItems(items);
|
||
this.updateCartTotal(items);
|
||
this.loadRecommendations();
|
||
})
|
||
.catch(error => {
|
||
console.error('加载购物车数据失败:', error);
|
||
$('#cartItemsList').html('<div class="empty-cart">加载失败,请重试</div>');
|
||
});
|
||
},
|
||
|
||
// 渲染购物车商品
|
||
renderCartItems(items) {
|
||
if (items.length === 0) {
|
||
$('#cartItemsList').html('<div class="empty-cart">购物车是空的</div>');
|
||
return;
|
||
}
|
||
|
||
const itemsHtml = items.map(item => {
|
||
const product = item.product || {};
|
||
const sku = item.sku || {};
|
||
const itemName = product.name || '未知商品';
|
||
const itemImage = product.main_image || product.image || 'https://picsum.photos/80/80?random=default';
|
||
const itemPrice = PriceUtils.fenToYuan(sku.price || product.price || 0);
|
||
const productId = item.product_id || product.id;
|
||
|
||
// 规格信息
|
||
let specHtml = '';
|
||
if (sku.spec_values && Object.keys(sku.spec_values).length > 0) {
|
||
const specs = Object.entries(sku.spec_values).map(([key, value]) => `${key}: ${value}`).join(', ');
|
||
specHtml = `<div class="cart-item-spec">${specs}</div>`;
|
||
}
|
||
|
||
return `
|
||
<div class="cart-item" data-cart-id="${item.id}" data-product-id="${productId}">
|
||
<img src="${itemImage}" alt="${itemName}" class="cart-item-image" data-product-id="${productId}">
|
||
<div class="cart-item-info">
|
||
<div class="cart-item-header">
|
||
<div class="cart-item-name" data-product-id="${productId}">${itemName}</div>
|
||
<button class="cart-item-remove" data-product-id="${item.product_id}" data-sku-id="${item.sku_id || 0}" title="删除">
|
||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||
<path d="M2.5 1a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1H3v9a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V4h.5a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H10a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1H2.5zm3 4a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5zM8 5a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7A.5.5 0 0 1 8 5zm3 .5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 1 0z"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
${specHtml}
|
||
<div class="cart-item-bottom">
|
||
<div class="cart-item-price">¥${itemPrice.toFixed(2)}</div>
|
||
<div class="cart-item-quantity">
|
||
<button class="quantity-btn quantity-decrease" data-cart-id="${item.id}" data-product-id="${item.product_id}" data-sku-id="${item.sku_id || 0}">-</button>
|
||
<input type="number" class="quantity-input" value="${item.quantity}" min="1" data-cart-id="${item.id}" data-product-id="${item.product_id}" data-sku-id="${item.sku_id || 0}" readonly>
|
||
<button class="quantity-btn quantity-increase" data-cart-id="${item.id}" data-product-id="${item.product_id}" data-sku-id="${item.sku_id || 0}">+</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
$('#cartItemsList').html(itemsHtml);
|
||
},
|
||
|
||
// 更新购物车总计
|
||
updateCartTotal(items) {
|
||
const total = items.reduce((sum, item) => {
|
||
const price = (item.sku && item.sku.price) || (item.product && item.product.price) || 0;
|
||
return sum + (price * item.quantity);
|
||
}, 0);
|
||
|
||
$('#cartTotalAmount').text(`¥${PriceUtils.fenToYuan(total).toFixed(2)}`);
|
||
},
|
||
|
||
// 删除购物车商品
|
||
removeItem(productId, skuId) {
|
||
let url = `/cart/${productId}`;
|
||
if (skuId && skuId != 0) {
|
||
url += `?sku_id=${skuId}`;
|
||
}
|
||
|
||
API.delete(url)
|
||
.then(() => {
|
||
Toast.success('已删除');
|
||
this.loadCartData();
|
||
this.updateCartCount();
|
||
})
|
||
.catch(error => {
|
||
Toast.error(error.message || '删除失败');
|
||
});
|
||
},
|
||
|
||
// 更新商品数量
|
||
updateQuantity(productId, skuId, newQuantity) {
|
||
const data = {
|
||
quantity: newQuantity
|
||
};
|
||
|
||
if (skuId && skuId != 0) {
|
||
data.sku_id = parseInt(skuId);
|
||
}
|
||
|
||
API.put(`/cart/${productId}`, data)
|
||
.then(() => {
|
||
Toast.success('已更新数量');
|
||
this.loadCartData();
|
||
this.updateCartCount();
|
||
})
|
||
.catch(error => {
|
||
Toast.error(error.message || '更新失败');
|
||
this.loadCartData(); // 失败后重新加载恢复原数量
|
||
});
|
||
},
|
||
|
||
// 加载推荐商品
|
||
loadRecommendations() {
|
||
API.get('/products', { page: 1, page_size: 4 })
|
||
.then(data => {
|
||
const products = data.list || [];
|
||
this.renderRecommendations(products.slice(0, 4));
|
||
})
|
||
.catch(error => {
|
||
console.error('加载推荐商品失败:', error);
|
||
});
|
||
},
|
||
|
||
// 渲染推荐商品
|
||
renderRecommendations(products) {
|
||
if (products.length === 0) {
|
||
$('#recommendationsList').html('<div class="empty-cart">暂无推荐</div>');
|
||
return;
|
||
}
|
||
|
||
const recommendationsHtml = products.map(product => {
|
||
const productName = product.name || '未知商品';
|
||
const productImage = product.main_image || 'https://picsum.photos/260/260?random=' + product.id;
|
||
const price = PriceUtils.fenToYuan(product.price || 0);
|
||
|
||
return `
|
||
<div class="recommendation-item" data-product-id="${product.id}">
|
||
<img src="${productImage}" alt="${productName}" loading="lazy">
|
||
<div class="recommendation-item-info">
|
||
<div class="recommendation-item-name">${productName}</div>
|
||
<div class="recommendation-item-price">¥${price.toFixed(2)}</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
$('#recommendationsList').html(recommendationsHtml);
|
||
}
|
||
};
|
||
|
||
// 全局函数供其他页面调用
|
||
window.openCartDrawer = function() {
|
||
cart.openDrawer();
|
||
};
|
||
|
||
// 更新用户图标和显示
|
||
function updateUserIcon() {
|
||
const user = localStorage.getItem('currentUser');
|
||
const $userIcon = $('.user-icon');
|
||
|
||
if (user) {
|
||
try {
|
||
const userData = JSON.parse(user);
|
||
if (userData.token) {
|
||
// 已登录:修改链接和显示用户名
|
||
$userIcon.attr('href', 'user-center.html');
|
||
|
||
// 获取用户名
|
||
const displayName = userData.nickname || userData.email?.split('@')[0] || '用户';
|
||
|
||
// 如果还没有用户名显示,添加一个
|
||
if (!$userIcon.find('.user-name-display').length) {
|
||
$userIcon.append(`<span class="user-name-display">${displayName}</span>`);
|
||
} else {
|
||
$userIcon.find('.user-name-display').text(displayName);
|
||
}
|
||
|
||
// 添加下拉菜单(作为子元素,确保定位正确)
|
||
if (!$userIcon.find('.user-dropdown-menu').length) {
|
||
const dropdownHtml = `
|
||
<div class="user-dropdown-menu">
|
||
<a href="user-center.html" class="dropdown-item">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||
<circle cx="12" cy="7" r="4"></circle>
|
||
</svg>
|
||
<span data-i18n="user_center">个人中心</span>
|
||
</a>
|
||
<a href="#" class="dropdown-item" id="headerLogoutBtn">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
|
||
<polyline points="16 17 21 12 16 7"></polyline>
|
||
<line x1="21" y1="12" x2="9" y2="12"></line>
|
||
</svg>
|
||
<span data-i18n="logout">退出登录</span>
|
||
</a>
|
||
</div>
|
||
`;
|
||
$userIcon.append(dropdownHtml);
|
||
}
|
||
} else {
|
||
// Token无效,清除登录状态
|
||
localStorage.removeItem('currentUser');
|
||
$userIcon.attr('href', 'login.html');
|
||
$userIcon.find('.user-name-display').remove();
|
||
$userIcon.find('.user-dropdown-menu').remove();
|
||
}
|
||
} catch (e) {
|
||
// 解析失败,清除登录状态
|
||
localStorage.removeItem('currentUser');
|
||
$userIcon.attr('href', 'login.html');
|
||
$userIcon.find('.user-name-display').remove();
|
||
$userIcon.find('.user-dropdown-menu').remove();
|
||
}
|
||
} else {
|
||
// 未登录:链接指向登录页
|
||
$userIcon.attr('href', 'login.html');
|
||
$userIcon.find('.user-name-display').remove();
|
||
$userIcon.find('.user-dropdown-menu').remove();
|
||
}
|
||
}
|
||
|
||
// 将 updateUserIcon 暴露为全局函数,供其他页面调用
|
||
window.updateUserIcon = updateUserIcon;
|
||
|
||
// 头部退出登录处理
|
||
function handleHeaderLogout() {
|
||
if (typeof Toast !== 'undefined' && Toast.confirm) {
|
||
Toast.confirm({
|
||
title: i18n?.t('logout_confirm_title') || '退出登录',
|
||
message: i18n?.t('logout_confirm_message') || '确定要退出当前账号吗?',
|
||
confirmText: i18n?.t('confirm') || '确定',
|
||
cancelText: i18n?.t('cancel') || '取消'
|
||
}).then(confirmed => {
|
||
if (confirmed) {
|
||
// 清除登录状态和重定向URL
|
||
localStorage.removeItem('currentUser');
|
||
localStorage.removeItem('redirectUrl');
|
||
|
||
// 更新用户图标显示
|
||
updateUserIcon();
|
||
|
||
Toast.success(i18n?.t('logout_success') || '已退出登录');
|
||
setTimeout(() => {
|
||
window.location.href = 'login.html';
|
||
}, 500);
|
||
}
|
||
});
|
||
} else {
|
||
// 降级处理:使用原生confirm
|
||
if (confirm('确定要退出当前账号吗?')) {
|
||
localStorage.removeItem('currentUser');
|
||
localStorage.removeItem('redirectUrl');
|
||
updateUserIcon();
|
||
window.location.href = 'login.html';
|
||
}
|
||
}
|
||
}
|
||
|
||
// 页面初始化
|
||
$(document).ready(function() {
|
||
// 更新用户图标显示
|
||
updateUserIcon();
|
||
|
||
// 更新购物车计数(已内置登录检查,未登录会自动显示0)
|
||
cart.updateCartCount();
|
||
|
||
// 用户下拉菜单交互
|
||
$(document).on('mouseenter', '.user-icon', function() {
|
||
if ($(this).find('.user-dropdown-menu').length) {
|
||
$(this).find('.user-dropdown-menu').addClass('show');
|
||
}
|
||
});
|
||
|
||
$(document).on('mouseleave', '.user-icon', function() {
|
||
setTimeout(() => {
|
||
if (!$('.user-dropdown-menu:hover').length) {
|
||
$('.user-dropdown-menu').removeClass('show');
|
||
}
|
||
}, 100);
|
||
});
|
||
|
||
$(document).on('mouseenter', '.user-dropdown-menu', function() {
|
||
$(this).addClass('show');
|
||
});
|
||
|
||
$(document).on('mouseleave', '.user-dropdown-menu', function() {
|
||
$(this).removeClass('show');
|
||
});
|
||
|
||
// 头部退出登录按钮
|
||
$(document).on('click', '#headerLogoutBtn', function(e) {
|
||
e.preventDefault();
|
||
handleHeaderLogout();
|
||
});
|
||
|
||
// 购物车按钮点击事件 - 使用事件委托阻止跳转,打开抽屉
|
||
$(document).on('click', '.cart-icon', function(e) {
|
||
console.log('=== 点击了购物车图标 ===');
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
// 打开购物车抽屉
|
||
if (cart && cart.openDrawer) {
|
||
console.log('调用 cart.openDrawer()');
|
||
cart.openDrawer();
|
||
} else {
|
||
console.error('cart.openDrawer 不存在');
|
||
}
|
||
return false;
|
||
});
|
||
|
||
// 搜索功能
|
||
$('.search-btn').on('click', function() {
|
||
const keyword = $('.search-box input').val().trim();
|
||
if (keyword) {
|
||
window.location.href = `search.html?q=${encodeURIComponent(keyword)}`;
|
||
}
|
||
});
|
||
|
||
$('.search-box input').on('keypress', function(e) {
|
||
if (e.which === 13) { // Enter键
|
||
const keyword = $(this).val().trim();
|
||
if (keyword) {
|
||
window.location.href = `search.html?q=${encodeURIComponent(keyword)}`;
|
||
}
|
||
}
|
||
});
|
||
|
||
// 添加CSS动画
|
||
if (!$('#custom-animations').length) {
|
||
$('head').append(`
|
||
<style id="custom-animations">
|
||
@keyframes slideIn {
|
||
from {
|
||
transform: translateX(100%);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
</style>
|
||
`);
|
||
}
|
||
|
||
// 将 cart 对象暴露到全局
|
||
window.cart = cart;
|
||
});
|