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

1426 lines
49 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.

// Checkout Page JavaScript - 完全重写版本
// 全局状态
const checkoutState = {
cartItems: [], // 购物车商品
addresses: [], // 用户地址列表
selectedAddress: null, // 选中的地址
coupons: [], // 可用优惠券
selectedCoupon: null, // 选中的优惠券
orderNotes: '', // 订单备注
checkoutType: 'cart', // 结算类型:'cart'(购物车) 或 'buynow'(立即购买)
paymentMethod: 'wechat', // 支付方式:'wechat'(微信) 或 'alipay'(支付宝)
paymentTimer: null, // 支付轮询定时器
countdownTimer: null, // 倒计时定时器
};
$(document).ready(function() {
initCheckoutPage();
});
function initCheckoutPage() {
const pageStartTime = Date.now();
console.log('\n=== 结算页面加载开始 ===');
// 检查结算类型
const urlParams = new URLSearchParams(window.location.search);
const type = urlParams.get('type');
const productId = urlParams.get('product_id');
const skuId = urlParams.get('sku_id');
const quantity = urlParams.get('quantity');
// 绑定事件(立即绑定,不等待数据加载)
bindCheckoutEvents();
// 并行加载所有数据,提升页面加载速度
console.log('🚀 开始并行加载所有数据...');
console.log('⏱️ 请求开始时间:', new Date().toLocaleTimeString());
let dataPromise;
if (type === 'buynow' && productId) {
// 立即购买模式
checkoutState.checkoutType = 'buynow';
console.log(' [1/3] 发起商品详情 API 请求...');
dataPromise = loadBuyNowProduct(productId, skuId, quantity);
} else {
// 购物车结算模式
checkoutState.checkoutType = 'cart';
console.log(' [1/3] 发起购物车 API 请求...');
dataPromise = loadCartItems();
}
// 并行加载地址和商品数据
console.log(' [2/3] 发起地址 API 请求...');
const addressPromise = loadAddresses();
console.log(' [3/3] 所有并行请求已发起!');
console.log('⏱️ 请求发起时间:', new Date().toLocaleTimeString());
// 等待主要数据加载完成(地址和商品)
Promise.all([addressPromise, dataPromise])
.then(() => {
const pageLoadTime = Date.now() - pageStartTime;
console.log(`\n✅ 页面主要内容加载完成`);
console.log(` 总耗时: ${pageLoadTime}ms`);
console.log(` 完成时间: ${new Date().toLocaleTimeString()}`);
console.log(` 说明: 由于地址和商品并行加载,总耗时 ≈ max(地址耗时, 商品耗时)\n`);
// 异步加载优惠券(不阻塞页面显示)
// 使用 setTimeout 让页面先渲染,然后再加载优惠券
setTimeout(() => {
loadCoupons();
}, 0);
})
.catch(error => {
const pageLoadTime = Date.now() - pageStartTime;
console.error(`❌ 页面加载失败 (耗时: ${pageLoadTime}ms):`, error);
});
}
// 加载立即购买的商品
function loadBuyNowProduct(productId, skuId, quantity) {
const startTime = Date.now();
console.log(' → 商品详情 API: 请求中...');
// 显示加载状态
$('#goodsList').html(`
<div style="text-align: center; padding: 40px 20px; color: var(--text-light);">
<div class="spinner" style="margin: 0 auto 16px;"></div>
<p>加载中...</p>
</div>
`);
// 返回 Promise
return API.get(`/products/${productId}`)
.then(data => {
const loadTime = Date.now() - startTime;
console.log(` ✓ 商品详情 API: 完成 (耗时: ${loadTime}ms)`);
console.log(' 商品:', data.data?.name || data.name || '未知');
// 支持多种数据格式
const product = data.data || data;
// 构建购买商品数据
const item = {
product_id: parseInt(productId),
product: product,
quantity: parseInt(quantity) || 1,
selected: true
};
// 如果指定了SKU
if (skuId) {
const sku = product.skus?.find(s => s.id == skuId || s.sku_id == skuId);
if (sku) {
item.sku_id = parseInt(skuId);
item.sku = sku;
}
}
checkoutState.cartItems = [item];
renderGoodsList([item]);
updatePriceSummary();
})
.catch(error => {
const loadTime = Date.now() - startTime;
console.error(` ✗ 商品详情 API: 失败 (耗时: ${loadTime}ms)`, error.message);
Toast.error('加载商品失败');
showEmptyCart();
});
}
// 加载购物车商品
function loadCartItems() {
const startTime = Date.now();
console.log(' → 购物车 API: 请求中...');
// 显示加载状态
$('#goodsList').html(`
<div style="text-align: center; padding: 40px 20px; color: var(--text-light);">
<div class="spinner" style="margin: 0 auto 16px;"></div>
<p>加载中...</p>
</div>
`);
// 返回 Promise
return API.get('/cart')
.then(data => {
const loadTime = Date.now() - startTime;
console.log(` ✓ 购物车 API: 完成 (耗时: ${loadTime}ms)`);
console.log(` 商品数: ${data.items?.length || 0}`);
console.log(` 总金额: ${data.total || '0'}`);
// 支持多种数据格式
let items = [];
if (Array.isArray(data)) {
// 直接返回数组
items = data;
} else if (data.data) {
// 数据在 data.data 中
if (Array.isArray(data.data)) {
items = data.data;
} else if (data.data.items) {
items = data.data.items;
} else if (data.data.list) {
items = data.data.list;
}
} else if (data.items) {
// 数据在 data.items 中
items = data.items;
} else if (data.list) {
// 数据在 data.list 中
items = data.list;
}
console.log('解析到的商品列表:', items);
checkoutState.cartItems = items;
if (items.length === 0) {
showEmptyCart();
return;
}
renderGoodsList(items);
updatePriceSummary();
})
.catch(error => {
const loadTime = Date.now() - startTime;
console.error(` ✗ 购物车 API: 失败 (耗时: ${loadTime}ms)`, error.message);
Toast.error('加载商品失败');
showEmptyCart();
});
}
// 显示空购物车
function showEmptyCart() {
$('#goodsList').html(`
<div style="text-align: center; padding: 60px 20px; color: var(--text-light);">
<p style="font-size: 16px; margin-bottom: 20px;">购物车是空的</p>
<a href="index.html" class="btn btn-primary">
去逛逛
</a>
</div>
`);
$('#payNowBtn').prop('disabled', true);
}
// 渲染商品列表
function renderGoodsList(items) {
const goodsHtml = items.map(item => {
// 字段映射 - 支持多种格式(嵌套结构优先)
const productName = item.product_name || item.productName || item.name || item.title ||
item.goods_name ||
(item.product && (item.product.name || item.product.title)) ||
'未知商品';
// SKU 规格名称提取(支持多种格式)
let skuName = item.sku_name || item.skuName || item.specs || item.spec || '';
// 处理 spec_values 对象格式
if (!skuName && item.sku && item.sku.spec_values) {
const specValues = item.sku.spec_values;
if (typeof specValues === 'object' && specValues !== null) {
// 将对象转换为 "key:value" 格式,如 "颜色:红色 尺寸:XL"
skuName = Object.entries(specValues)
.map(([key, value]) => `${key}:${value}`)
.join(' ');
} else if (typeof specValues === 'string') {
skuName = specValues;
}
}
// 价格提取(优先从嵌套的 product 或 sku 对象中获取)
const price = parseInt(
item.price ||
item.sale_price ||
item.salePrice ||
(item.sku && item.sku.price) ||
(item.product && item.product.price) ||
0
);
const quantity = item.quantity || item.num || item.count || 1;
// 图片提取(支持嵌套的 product.main_image
const image = item.image || item.thumb || item.main_image || item.mainImage || item.pic ||
(item.product && item.product.main_image) ||
'https://via.placeholder.com/80';
console.log('商品数据:', {
原始数据: item,
解析结果: { productName, skuName, price, quantity, image }
});
return `
<div class="goods-item">
<img src="${image}" alt="${productName}" class="goods-image">
<div class="goods-info">
<div class="goods-title">${productName}</div>
${skuName ? `<div class="goods-specs">${skuName}</div>` : ''}
</div>
<div class="goods-right">
<div class="goods-price">¥${PriceUtils.fenToYuan(price).toFixed(2)}</div>
<div class="goods-quantity">x${quantity}</div>
</div>
</div>
`;
}).join('');
$('#goodsList').html(goodsHtml);
}
// 加载用户地址
function loadAddresses() {
const startTime = Date.now();
console.log(' → 地址 API: 请求中...');
// 返回 Promise
return API.get('/addresses')
.then(data => {
const loadTime = Date.now() - startTime;
console.log(` ✓ 地址 API: 完成 (耗时: ${loadTime}ms)`);
// 支持多种数据格式
let addresses = [];
if (Array.isArray(data)) {
// 直接返回数组
addresses = data;
} else if (data.data) {
// 数据在 data.data 中
addresses = Array.isArray(data.data) ? data.data : (data.data.list || []);
} else if (data.list) {
// 数据在 data.list 中
addresses = data.list;
}
console.log(' 地址数: ' + addresses.length);
checkoutState.addresses = addresses;
if (addresses.length > 0) {
// 查找默认地址
const defaultAddress = addresses.find(addr => addr.is_default || addr.isDefault);
checkoutState.selectedAddress = defaultAddress || addresses[0];
console.log(' 选中地址:', checkoutState.selectedAddress.name || '无名称');
renderSelectedAddress(checkoutState.selectedAddress);
} else {
console.log(' 没有可用地址');
}
})
.catch(error => {
const loadTime = Date.now() - startTime;
console.error(` ✗ 地址 API: 失败 (耗时: ${loadTime}ms)`, error.message);
// 非登录用户或没有地址时不显示错误
});
}
// 渲染选中的地址
function renderSelectedAddress(address) {
if (!address) {
$('#noAddress').show();
$('#addressInfo').hide();
return;
}
$('#noAddress').hide();
$('#addressInfo').show();
// 字段映射 - 支持多种格式
const name = address.receiver_name || address.name || '';
const phone = address.receiver_phone || address.phone || '';
const province = address.province_name || address.provinceName || address.province || '';
const city = address.city_name || address.cityName || address.city || '';
const district = address.district_name || address.districtName || address.district || '';
const detail = address.detail_address || address.detailAddress || address.detail || '';
const isDefault = address.is_default || address.isDefault;
const tag = address.address_tag || address.addressTag || (isDefault ? '默认' : '');
$('#addressName').text(name);
$('#addressPhone').text(phone);
$('#addressDetail').text(`${province} ${city} ${district} ${detail}`);
if (tag) {
$('#addressTag').text(tag).show();
} else {
$('#addressTag').hide();
}
}
// 加载优惠券
function loadCoupons() {
const startTime = Date.now();
console.log('\n🎫 加载优惠券(异步,不阻塞页面)...');
// 计算总价(支持嵌套结构)
const totalAmount = checkoutState.cartItems.reduce((sum, item) => {
const price = parseInt(
item.price ||
item.sale_price ||
item.salePrice ||
(item.sku && item.sku.price) ||
(item.product && item.product.price) ||
0
);
const quantity = item.quantity || 1;
return sum + (price * quantity);
}, 0);
console.log(' 订单总额(分):', totalAmount);
// 如果订单金额为0不加载优惠券
if (totalAmount <= 0) {
console.log(' 订单金额为0跳过加载优惠券\n');
checkoutState.coupons = [];
updateCouponDisplay();
return;
}
// 使用订单可用优惠券 API添加超时控制
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), 5000); // 5秒超时
});
const apiPromise = API.get('/coupons/order/available', {
order_amount: totalAmount
});
Promise.race([apiPromise, timeoutPromise])
.then(data => {
const loadTime = Date.now() - startTime;
console.log(` ✓ 优惠券 API: 完成 (耗时: ${loadTime}ms)\n`);
// 检查数据是否为空
if (!data) {
console.warn(' ⚠️ 优惠券 API 返回空数据\n');
checkoutState.coupons = [];
updateCouponDisplay();
return;
}
// 支持多种数据格式
let coupons = [];
if (Array.isArray(data)) {
coupons = data;
} else if (data.data) {
coupons = Array.isArray(data.data) ? data.data : (data.data.list || data.data.coupons || []);
} else if (data.list) {
coupons = data.list;
} else if (data.coupons) {
coupons = data.coupons;
}
console.log(' 优惠券数量:', coupons.length, '\n');
checkoutState.coupons = coupons;
// 更新优惠券显示
updateCouponDisplay();
})
.catch(error => {
const loadTime = Date.now() - startTime;
console.error(` ✗ 优惠券 API: 失败 (耗时: ${loadTime}ms)`, error.message);
console.log(' 优惠券加载失败不影响结算,继续使用\n');
// 优惠券加载失败不影响结算流程,只是没有优惠券可用
checkoutState.coupons = [];
updateCouponDisplay();
});
}
// 更新优惠券显示
function updateCouponDisplay() {
const availableCount = checkoutState.coupons.length;
if (checkoutState.selectedCoupon) {
const discount = parseInt(checkoutState.selectedCoupon.discount_amount || 0);
$('#couponText').text(`-¥${PriceUtils.fenToYuan(discount).toFixed(2)}`);
} else if (availableCount > 0) {
$('#couponText').text(`${availableCount}张可用`);
} else {
$('#couponText').text('暂无可用优惠券');
}
}
// 更新价格汇总
function updatePriceSummary() {
// 计算商品总额(支持嵌套结构)
const subtotal = checkoutState.cartItems.reduce((sum, item) => {
const price = parseInt(
item.price ||
item.sale_price ||
item.salePrice ||
(item.sku && item.sku.price) ||
(item.product && item.product.price) ||
0
);
const quantity = item.quantity || 1;
return sum + (price * quantity);
}, 0);
// 运费(满一定金额免运费)
const shippingFee = subtotal >= 5900 ? 0 : 0; // 满 59 元免运费
// 活动优惠(从商品信息中获取)
const promotionAmount = 0; // TODO: 从 API 获取
// 优惠券优惠
const couponAmount = checkoutState.selectedCoupon ?
parseInt(checkoutState.selectedCoupon.discount_amount || 0) : 0;
// 应付总额
const totalAmount = subtotal + shippingFee - promotionAmount - couponAmount;
// 更新显示
$('#subtotalAmount').text(`${PriceUtils.fenToYuan(subtotal).toFixed(2)}`);
if (shippingFee > 0) {
$('#shippingAmount').text(`${PriceUtils.fenToYuan(shippingFee).toFixed(2)}`);
} else {
$('#shippingAmount').text('免运费');
}
if (promotionAmount > 0) {
$('#promotionRow').show();
$('#promotionAmount').text(`-¥${PriceUtils.fenToYuan(promotionAmount).toFixed(2)}`);
} else {
$('#promotionRow').hide();
}
if (couponAmount > 0) {
$('#couponRow').show();
$('#couponAmount').text(`-¥${PriceUtils.fenToYuan(couponAmount).toFixed(2)}`);
} else {
$('#couponRow').hide();
}
$('#totalAmount').text(`${PriceUtils.fenToYuan(totalAmount).toFixed(2)}`);
}
// 绑定事件
function bindCheckoutEvents() {
// 选择地址按钮
$('#selectAddressBtn, #changeAddressBtn').on('click', function() {
showAddressModal();
});
// 编辑选中的地址
$('#editSelectedAddressBtn').on('click', function() {
if (checkoutState.selectedAddress) {
editAddress(checkoutState.selectedAddress);
}
});
// 关闭地址弹窗
$('#closeAddressModal').on('click', function() {
hideAddressModal();
});
// 选择优惠券
$('#couponSelector').on('click', function() {
if (checkoutState.coupons.length === 0) {
Toast.info('暂无可用优惠券');
return;
}
showCouponModal();
});
// 关闭优惠券弹窗
$('#closeCouponModal').on('click', function() {
hideCouponModal();
});
// 确认优惠券
$('#confirmCouponBtn').on('click', function() {
hideCouponModal();
updatePriceSummary();
});
// 新增地址按钮
$('#addAddressBtn').on('click', function() {
hideAddressModal();
showAddAddressModal();
});
// 关闭新增地址弹窗
$('#closeAddAddressModal, #cancelAddAddressBtn').on('click', function() {
hideAddAddressModal();
});
// 保存地址
$('#saveAddressBtn').on('click', function() {
saveNewAddress();
});
// 提交订单
$('#payNowBtn').on('click', function(e) {
e.preventDefault();
submitOrder();
});
// 选择支付方式
$('.payment-method:not(.disabled)').on('click', function() {
$('.payment-method').removeClass('active');
$(this).addClass('active');
checkoutState.paymentMethod = $(this).data('payment');
});
// 关闭支付弹窗
$('#closePaymentModal').on('click', function() {
hidePaymentModal();
});
// 点击弹窗背景关闭
$('.modal').on('click', function(e) {
if ($(e.target).hasClass('modal')) {
$(this).removeClass('show');
}
});
}
// 显示地址选择弹窗
function showAddressModal() {
renderAddressList();
$('#addressModal').addClass('show');
}
function hideAddressModal() {
$('#addressModal').removeClass('show');
}
// 渲染地址列表
function renderAddressList() {
if (checkoutState.addresses.length === 0) {
$('#addressList').html(`
<div style="text-align: center; padding: 40px 20px; color: var(--text-light);">
<p>暂无收货地址</p>
</div>
`);
return;
}
const addressHtml = checkoutState.addresses.map(address => {
// 字段映射 - 支持多种格式
const name = address.receiver_name || address.name || '';
const phone = address.receiver_phone || address.phone || '';
const province = address.province_name || address.provinceName || address.province || '';
const city = address.city_name || address.cityName || address.city || '';
const district = address.district_name || address.districtName || address.district || '';
const detail = address.detail_address || address.detailAddress || address.detail || '';
const isDefault = address.is_default || address.isDefault;
const addressId = address.id || address.address_id || address.addressId;
const isSelected = checkoutState.selectedAddress &&
(checkoutState.selectedAddress.id === addressId ||
checkoutState.selectedAddress.address_id === addressId ||
checkoutState.selectedAddress.addressId === addressId);
return `
<div class="address-item ${isSelected ? 'selected' : ''}" data-address-id="${addressId}">
<div class="address-content">
<div class="address-header">
<div class="address-user">
<span class="user-name">${name}</span>
<span class="user-phone">${phone}</span>
</div>
${isDefault ? '<span class="tag tag-default">默认</span>' : ''}
</div>
<div class="address-detail">${province} ${city} ${district} ${detail}</div>
</div>
<div class="address-operations">
<button class="btn-icon-action btn-edit-address" data-address-id="${addressId}" title="编辑" onclick="event.stopPropagation()">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
</button>
<button class="btn-icon-action btn-delete-address" data-address-id="${addressId}" title="删除" onclick="event.stopPropagation()">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
</button>
</div>
${isSelected ? '<div class="address-selected-icon">✓</div>' : ''}
</div>
`;
}).join('');
$('#addressList').html(addressHtml);
// 绑定地址选择事件
$('.address-item').on('click', function() {
const addressId = $(this).data('address-id');
const address = checkoutState.addresses.find(addr =>
(addr.id == addressId || addr.address_id == addressId || addr.addressId == addressId)
);
if (address) {
checkoutState.selectedAddress = address;
renderSelectedAddress(address);
hideAddressModal();
// 重新加载优惠券(地址可能影响可用优惠券)
loadCoupons();
}
});
// 绑定编辑按钮事件
$('.btn-edit-address').on('click', function(e) {
e.stopPropagation();
const addressId = $(this).data('address-id');
const address = checkoutState.addresses.find(addr =>
(addr.id == addressId || addr.address_id == addressId || addr.addressId == addressId)
);
if (address) {
editAddress(address);
}
});
// 绑定删除按钮事件
$('.btn-delete-address').on('click', function(e) {
e.stopPropagation();
const addressId = $(this).data('address-id');
deleteAddress(addressId);
});
}
// 显示优惠券选择弹窗
function showCouponModal() {
renderCouponList();
$('#couponModal').addClass('show');
}
function hideCouponModal() {
$('#couponModal').removeClass('show');
}
// 渲染优惠券列表
function renderCouponList() {
if (checkoutState.coupons.length === 0) {
$('#couponList').html(`
<div style="text-align: center; padding: 40px 20px; color: var(--text-light);">
<p>暂无可用优惠券</p>
</div>
`);
return;
}
const couponHtml = checkoutState.coupons.map(coupon => {
const name = coupon.coupon_name || coupon.name || '优惠券';
const discount = parseInt(coupon.discount_amount || 0);
const minAmount = parseInt(coupon.min_amount || 0);
const isSelected = checkoutState.selectedCoupon &&
checkoutState.selectedCoupon.coupon_id === coupon.coupon_id;
return `
<div class="coupon-item ${isSelected ? 'selected' : ''}" data-coupon-id="${coupon.coupon_id}">
<div class="coupon-left">
<div class="coupon-discount">¥${PriceUtils.fenToYuan(discount).toFixed(0)}</div>
<div class="coupon-condition">
${minAmount > 0 ? `满¥${PriceUtils.fenToYuan(minAmount).toFixed(0)}可用` : '无门槛'}
</div>
</div>
<div class="coupon-right">
<div class="coupon-name">${name}</div>
<div class="coupon-validity">
${formatCouponDate(coupon.start_time, coupon.end_time)}
</div>
</div>
${isSelected ? '<div class="coupon-selected-icon">✓</div>' : ''}
</div>
`;
}).join('');
// 添加“不使用优惠券”选项
const noCouponHtml = `
<div class="coupon-item ${!checkoutState.selectedCoupon ? 'selected' : ''}" data-coupon-id="">
<div class="coupon-left">
<div class="coupon-discount" style="font-size: 16px;">不使用</div>
</div>
<div class="coupon-right">
<div class="coupon-name">不使用优惠券</div>
</div>
${!checkoutState.selectedCoupon ? '<div class="coupon-selected-icon">✓</div>' : ''}
</div>
`;
$('#couponList').html(noCouponHtml + couponHtml);
// 绑定优惠券选择事件
$('.coupon-item').on('click', function() {
const couponId = $(this).data('coupon-id');
if (couponId) {
const coupon = checkoutState.coupons.find(c => c.coupon_id === couponId);
checkoutState.selectedCoupon = coupon || null;
} else {
checkoutState.selectedCoupon = null;
}
// 重新渲染列表显示选中状态
renderCouponList();
// 更新优惠券显示
updateCouponDisplay();
});
}
// 格式化优惠券日期
function formatCouponDate(startTime, endTime) {
if (!endTime) return '';
const end = new Date(endTime);
return `有效期至 ${end.getFullYear()}-${String(end.getMonth() + 1).padStart(2, '0')}-${String(end.getDate()).padStart(2, '0')}`;
}
// 显示新增地址弹窗
function showAddAddressModal() {
// 清空表单
$('#addAddressForm')[0].reset();
// 初始化省份选择器
initProvinceSelector();
// 禁用城市和区县选择器
$('#newAddressCity').prop('disabled', true).html('<option value="">请选择城市</option>');
$('#newAddressDistrict').prop('disabled', true).html('<option value="">请选择区县</option>');
// 绑定级联事件
bindRegionCascade();
// 绑定字数统计
bindCharCount();
$('#addAddressModal').addClass('show');
}
function hideAddAddressModal() {
$('#addAddressModal').removeClass('show');
}
// 初始化省份选择器
function initProvinceSelector() {
const provinceSelect = $('#newAddressProvince');
provinceSelect.html('<option value="">请选择省份</option>');
ChinaRegions.provinces.forEach(province => {
provinceSelect.append(`<option value="${province.code}" data-name="${province.name}">${province.name}</option>`);
});
}
// 绑定级联事件
function bindRegionCascade() {
// 省份变化
$('#newAddressProvince').off('change').on('change', function() {
const provinceCode = $(this).val();
const citySelect = $('#newAddressCity');
const districtSelect = $('#newAddressDistrict');
if (provinceCode) {
const cities = ChinaRegions.getCities(provinceCode);
citySelect.html('<option value="">请选择城市</option>');
cities.forEach(city => {
citySelect.append(`<option value="${city.code}" data-name="${city.name}">${city.name}</option>`);
});
citySelect.prop('disabled', false);
districtSelect.html('<option value="">请选择区县</option>').prop('disabled', true);
} else {
citySelect.html('<option value="">请选择城市</option>').prop('disabled', true);
districtSelect.html('<option value="">请选择区县</option>').prop('disabled', true);
}
});
// 城市变化
$('#newAddressCity').off('change').on('change', function() {
const cityCode = $(this).val();
const districtSelect = $('#newAddressDistrict');
if (cityCode) {
const districts = ChinaRegions.getDistricts(cityCode);
districtSelect.html('<option value="">请选择区县</option>');
if (districts.length > 0) {
districts.forEach(district => {
districtSelect.append(`<option value="${district.code}" data-name="${district.name}">${district.name}</option>`);
});
districtSelect.prop('disabled', false);
} else {
// 没有区县数据时,也启用选择器,但显示提示
districtSelect.html('<option value="000000">市辖区</option>');
districtSelect.prop('disabled', false);
}
} else {
districtSelect.html('<option value="">请选择区县</option>').prop('disabled', true);
}
});
}
// 绑定字数统计
function bindCharCount() {
$('#newAddressDetail').off('input').on('input', function() {
const length = $(this).val().length;
$('#detailCharCount').text(length);
if (length > 100) {
$(this).val($(this).val().substring(0, 100));
$('#detailCharCount').text(100);
}
});
}
// 保存新地址
function saveNewAddress() {
const name = $('#newAddressName').val().trim();
const phone = $('#newAddressPhone').val().trim();
const provinceSelect = $('#newAddressProvince');
const citySelect = $('#newAddressCity');
const districtSelect = $('#newAddressDistrict');
const provinceCode = provinceSelect.val();
const provinceName = provinceSelect.find('option:selected').data('name') || provinceSelect.find('option:selected').text();
const cityCode = citySelect.val();
const cityName = citySelect.find('option:selected').data('name') || citySelect.find('option:selected').text();
const districtCode = districtSelect.val();
const districtName = districtSelect.find('option:selected').data('name') || districtSelect.find('option:selected').text();
const detail = $('#newAddressDetail').val().trim();
const isDefault = $('#newAddressIsDefault').is(':checked');
// 验证
if (!name) {
Toast.error('请输入收货人姓名');
return;
}
if (!phone) {
Toast.error('请输入联系电话');
return;
}
// 验证手机号格式
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(phone)) {
Toast.error('请输入11位有效的手机号');
return;
}
if (!provinceCode || !cityCode || !districtCode) {
Toast.error('请选择完整的所在地区');
return;
}
if (!detail) {
Toast.error('请输入详细地址');
return;
}
if (detail.length < 5) {
Toast.error('详细地址至少需要5个字符');
return;
}
// 调用 API 保存地址
API.post('/users/addresses', {
name: name,
phone: phone,
province: provinceName,
city: cityName,
district: districtName,
detail: detail,
is_default: isDefault ? 1 : 0
})
.then(data => {
Toast.success('地址保存成功');
hideAddAddressModal();
// 重新加载地址列表
loadAddresses();
})
.catch(error => {
console.error('保存地址失败:', error);
Toast.error(error.message || '保存失败');
});
}
// 提交订单
function submitOrder() {
// 验证
if (!checkoutState.selectedAddress) {
Toast.error('请选择收货地址');
return;
}
if (checkoutState.cartItems.length === 0) {
Toast.error('购物车是空的');
return;
}
// 获取订单备注
checkoutState.orderNotes = $('#orderNotes').val().trim();
// 显示加载状态
const payBtn = $('#payNowBtn');
const originalText = payBtn.html();
payBtn.prop('disabled', true).html('<span>提交中...</span>');
// 获取地址ID - 支持多种字段名,转换为数字类型
const addressId = parseInt(
checkoutState.selectedAddress.id ||
checkoutState.selectedAddress.address_id ||
checkoutState.selectedAddress.addressId
);
// 构造订单数据
const orderData = {
address_id: addressId, // 确保是数字类型
coupon_id: checkoutState.selectedCoupon ? parseInt(checkoutState.selectedCoupon.coupon_id) : null,
remark: checkoutState.orderNotes,
items: checkoutState.cartItems.map(item => ({
product_id: parseInt(item.product_id),
sku_id: item.sku_id ? parseInt(item.sku_id) : null,
quantity: parseInt(item.quantity)
}))
};
console.log('订单数据:', orderData);
console.log('结算类型:', checkoutState.checkoutType);
// 调用创建订单 API
API.post('/orders', orderData)
.then(data => {
console.log('订单创建成功:', data);
const orderId = data.data?.order_id || data.order_id || data.data?.orderId || data.orderId;
Toast.success('订单创建成功!');
// 恢复按钮状态
payBtn.prop('disabled', false).html(originalText);
// 清空购物车(后端已清空)
if (window.cart && window.cart.updateCartCount) {
window.cart.updateCartCount();
}
// 根据支付方式处理
if (checkoutState.paymentMethod === 'wechat' && orderId) {
// 微信支付,展示二维码
showWechatPayment(orderId);
} else {
// 其他支付方式或无订单ID跳转到订单详情
setTimeout(() => {
if (orderId) {
window.location.href = `order-detail.html?id=${orderId}`;
} else {
window.location.href = 'user-center.html#orders';
}
}, 1000);
}
})
.catch(error => {
console.error('订单创建失败:', error);
Toast.error(error.message || '订单创建失败');
// 恢复按钮状态
payBtn.prop('disabled', false).html(originalText);
});
}
// 编辑地址
function editAddress(address) {
// 隐藏地址选择弹窗
hideAddressModal();
// 填充表单数据
const name = address.receiver_name || address.name || '';
const phone = address.receiver_phone || address.phone || '';
const province = address.province_name || address.provinceName || address.province || '';
const city = address.city_name || address.cityName || address.city || '';
const district = address.district_name || address.districtName || address.district || '';
const detail = address.detail_address || address.detailAddress || address.detail || '';
const isDefault = address.is_default || address.isDefault;
const addressId = address.id || address.address_id || address.addressId;
$('#newAddressName').val(name);
$('#newAddressPhone').val(phone);
$('#newAddressDetail').val(detail);
$('#newAddressIsDefault').prop('checked', isDefault);
// 初始化省份选择器
initProvinceSelector();
// 绑定级联事件
bindRegionCascade();
// 绑定字数统计
bindCharCount();
// 更新字数显示
$('#detailCharCount').text(detail.length);
// TODO: 需要根据省市区名称反查 code然后设置选中
// 这里简化处理,直接根据名称匹配
setTimeout(() => {
// 设置省份
const provinceOption = $('#newAddressProvince option').filter(function() {
return $(this).data('name') === province || $(this).text() === province;
});
if (provinceOption.length > 0) {
$('#newAddressProvince').val(provinceOption.val()).trigger('change');
// 等待城市加载后设置
setTimeout(() => {
const cityOption = $('#newAddressCity option').filter(function() {
return $(this).data('name') === city || $(this).text() === city;
});
if (cityOption.length > 0) {
$('#newAddressCity').val(cityOption.val()).trigger('change');
// 等待区县加载后设置
setTimeout(() => {
const districtOption = $('#newAddressDistrict option').filter(function() {
return $(this).data('name') === district || $(this).text() === district;
});
if (districtOption.length > 0) {
$('#newAddressDistrict').val(districtOption.val());
}
}, 100);
}
}, 100);
}
}, 100);
// 修改按钮文字和事件
$('#saveAddressBtn').text('保存修改').off('click').on('click', function() {
updateAddress(addressId);
});
// 显示弹窗
$('#addAddressModal').addClass('show');
}
// 更新地址
function updateAddress(addressId) {
const name = $('#newAddressName').val().trim();
const phone = $('#newAddressPhone').val().trim();
const provinceSelect = $('#newAddressProvince');
const citySelect = $('#newAddressCity');
const districtSelect = $('#newAddressDistrict');
const provinceCode = provinceSelect.val();
const provinceName = provinceSelect.find('option:selected').data('name') || provinceSelect.find('option:selected').text();
const cityCode = citySelect.val();
const cityName = citySelect.find('option:selected').data('name') || citySelect.find('option:selected').text();
const districtCode = districtSelect.val();
const districtName = districtSelect.find('option:selected').data('name') || districtSelect.find('option:selected').text();
const detail = $('#newAddressDetail').val().trim();
const isDefault = $('#newAddressIsDefault').is(':checked');
// 验证
if (!name) {
Toast.error('请输入收货人姓名');
return;
}
if (!phone) {
Toast.error('请输入联系电话');
return;
}
// 验证手机号格式
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(phone)) {
Toast.error('请输入11位有效的手机号');
return;
}
if (!provinceCode || !cityCode || !districtCode) {
Toast.error('请选择完整的所在地区');
return;
}
if (!detail) {
Toast.error('请输入详细地址');
return;
}
if (detail.length < 5) {
Toast.error('详细地址至少需要5个字符');
return;
}
// 调用 API 更新地址
API.put(`/users/addresses/${addressId}`, {
name: name,
phone: phone,
province: provinceName,
city: cityName,
district: districtName,
detail: detail,
is_default: isDefault ? 1 : 0
})
.then(data => {
Toast.success('地址更新成功');
hideAddAddressModal();
// 恢复按钮为新增模式
$('#saveAddressBtn').text('保存').off('click').on('click', function() {
saveNewAddress();
});
// 重新加载地址列表
loadAddresses();
})
.catch(error => {
console.error('更新地址失败:', error);
Toast.error(error.message || '更新失败');
});
}
// 删除地址
function deleteAddress(addressId) {
Toast.confirm({
title: '确认删除',
message: '确定要删除这个地址吗?',
confirmText: '删除',
cancelText: '取消',
confirmColor: '#ff6b6b'
}).then(confirmed => {
if (confirmed) {
API.delete(`/users/addresses/${addressId}`)
.then(() => {
Toast.success('地址删除成功');
// 如果删除的是当前选中的地址,清空选中状态
if (checkoutState.selectedAddress &&
(checkoutState.selectedAddress.id == addressId ||
checkoutState.selectedAddress.address_id == addressId ||
checkoutState.selectedAddress.addressId == addressId)) {
checkoutState.selectedAddress = null;
$('#noAddress').show();
$('#addressInfo').hide();
}
// 重新加载地址列表
loadAddresses();
})
.catch(error => {
console.error('删除地址失败:', error);
Toast.error(error.message || '删除失败');
});
}
});
}
// ===================== 支付相关 =====================
// 显示微信支付弹窗
function showWechatPayment(orderId) {
console.log('开始微信支付流程订单ID:', orderId);
// 计算总金额
const totalAmount = calculateTotalAmount();
// 显示支付金额
$('#paymentAmount').text(`${PriceUtils.fenToYuan(totalAmount).toFixed(2)}`);
// 显示弹窗
$('#paymentModal').addClass('show');
// 调用支付 API 获取二维码
API.post(`/orders/${orderId}/pay`, {
payment_method: 'wechat'
})
.then(data => {
console.log('支付响应:', data);
// 支持多种数据格式
const paymentData = data.data || data;
const qrcodeUrl = paymentData.qrcode_url || paymentData.qrcodeUrl || paymentData.code_url || paymentData.codeUrl;
if (qrcodeUrl) {
// 显示二维码
displayQRCode(qrcodeUrl);
// 开始轮询支付状态
startPaymentPolling(orderId);
// 开始倒计时5分钟
startCountdown(300);
} else {
Toast.error('获取支付二维码失败');
hidePaymentModal();
}
})
.catch(error => {
console.error('创建支付失败:', error);
Toast.error(error.message || '创建支付失败');
hidePaymentModal();
});
}
// 显示二维码
function displayQRCode(url) {
// 替换 loading 为二维码图片
$('#qrcodeContainer').html(`
<img src="${url}" alt="微信支付二维码" />
`);
}
// 开始轮询支付状态
function startPaymentPolling(orderId) {
// 清除之前的定时器
if (checkoutState.paymentTimer) {
clearInterval(checkoutState.paymentTimer);
}
// 每 2 秒查询一次
checkoutState.paymentTimer = setInterval(() => {
checkPaymentStatus(orderId);
}, 2000);
}
// 检查支付状态
function checkPaymentStatus(orderId) {
API.get(`/orders/${orderId}/payment/status`)
.then(data => {
const status = data.data?.status || data.status;
console.log('支付状态:', status);
if (status === 'paid' || status === 'success') {
// 支付成功
handlePaymentSuccess(orderId);
} else if (status === 'failed' || status === 'cancelled') {
// 支付失败
handlePaymentFailure();
}
// 其他状态继续轮询
})
.catch(error => {
console.error('查询支付状态失败:', error);
// 继续轮询,不中断
});
}
// 支付成功处理
function handlePaymentSuccess(orderId) {
// 停止轮询
clearInterval(checkoutState.paymentTimer);
clearInterval(checkoutState.countdownTimer);
// 显示成功消息
Toast.success('支付成功!');
// 关闭弹窗
hidePaymentModal();
// 跳转到订单详情
setTimeout(() => {
window.location.href = `order-detail.html?id=${orderId}`;
}, 1000);
}
// 支付失败处理
function handlePaymentFailure() {
// 停止轮询
clearInterval(checkoutState.paymentTimer);
clearInterval(checkoutState.countdownTimer);
// 显示失败消息
Toast.error('支付失败,请重试');
// 关闭弹窗
hidePaymentModal();
}
// 开始倒计时
function startCountdown(seconds) {
// 清除之前的定时器
if (checkoutState.countdownTimer) {
clearInterval(checkoutState.countdownTimer);
}
let remainingSeconds = seconds;
// 更新显示
updateCountdownDisplay(remainingSeconds);
// 每秒更新
checkoutState.countdownTimer = setInterval(() => {
remainingSeconds--;
if (remainingSeconds <= 0) {
// 倒计时结束
clearInterval(checkoutState.countdownTimer);
clearInterval(checkoutState.paymentTimer);
Toast.warning('二维码已过期,请重新生成');
hidePaymentModal();
} else {
updateCountdownDisplay(remainingSeconds);
}
}, 1000);
}
// 更新倒计时显示
function updateCountdownDisplay(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = seconds % 60;
$('#countdownTime').text(`${minutes}:${String(secs).padStart(2, '0')}`);
}
// 隐藏支付弹窗
function hidePaymentModal() {
// 清除定时器
if (checkoutState.paymentTimer) {
clearInterval(checkoutState.paymentTimer);
checkoutState.paymentTimer = null;
}
if (checkoutState.countdownTimer) {
clearInterval(checkoutState.countdownTimer);
checkoutState.countdownTimer = null;
}
// 关闭弹窗
$('#paymentModal').removeClass('show');
// 重置二维码区域
$('#qrcodeContainer').html(`
<div class="qrcode-loading">
<div class="spinner"></div>
<p>正在生成二维码...</p>
</div>
`);
}
// 计算总金额
function calculateTotalAmount() {
// 计算商品总额
const subtotal = checkoutState.cartItems.reduce((sum, item) => {
const price = parseInt(
item.price ||
item.sale_price ||
item.salePrice ||
(item.sku && item.sku.price) ||
(item.product && item.product.price) ||
0
);
const quantity = item.quantity || 1;
return sum + (price * quantity);
}, 0);
// 运费
const shippingFee = subtotal >= 5900 ? 0 : 0;
// 优惠券优惠
const couponAmount = checkoutState.selectedCoupon ?
parseInt(checkoutState.selectedCoupon.discount_amount || 0) : 0;
// 总金额
return subtotal + shippingFee - couponAmount;
}