ishop/public/js/main.js
2025-08-10 21:40:33 +08:00

725 lines
24 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.

// 全局变量
let currentProduct = null;
let currentOrder = null;
// DOM 元素
const productPrice = document.getElementById('product-price');
const quantityInput = document.getElementById('quantity');
const totalPriceElement = document.getElementById('total-price');
const orderForm = document.getElementById('orderForm');
const orderModal = document.getElementById('orderModal');
const paymentStatus = document.getElementById('paymentStatus');
// 初始化
document.addEventListener('DOMContentLoaded', function() {
loadProductData();
setupEventListeners();
});
// 加载产品数据
async function loadProductData() {
try {
const response = await fetch('/api/products');
const products = await response.json();
// 使用第一个产品作为展示产品
const productId = Object.keys(products)[0];
currentProduct = { id: productId, ...products[productId] };
// 更新界面
document.getElementById('product-name').textContent = currentProduct.name;
document.getElementById('product-description').textContent = currentProduct.description;
document.getElementById('product-price').textContent = currentProduct.price.toFixed(2);
document.getElementById('unit-price').textContent = currentProduct.price.toFixed(2);
updateTotalPrice();
} catch (error) {
console.error('加载产品数据失败:', error);
alert('加载产品信息失败,请刷新页面重试');
}
}
// 设置事件监听器
function setupEventListeners() {
// 数量控制
document.getElementById('decrease-qty').addEventListener('click', () => {
const current = parseInt(quantityInput.value) || 1;
if (current > 1) {
quantityInput.value = current - 1;
updateTotalPrice();
}
});
document.getElementById('increase-qty').addEventListener('click', () => {
const current = parseInt(quantityInput.value) || 1;
if (current < 999) {
quantityInput.value = current + 1;
updateTotalPrice();
}
});
// 数量输入变化
quantityInput.addEventListener('input', updateTotalPrice);
quantityInput.addEventListener('change', validateQuantity);
// 手机号验证
document.getElementById('customer_phone').addEventListener('blur', validatePhone);
document.getElementById('customer_phone').addEventListener('input', function(e) {
// 只允许输入数字
e.target.value = e.target.value.replace(/[^0-9]/g, '');
// 限制长度为11位
if (e.target.value.length > 11) {
e.target.value = e.target.value.slice(0, 11);
}
});
// 表单提交 - 显示配送信息模态框
orderForm.addEventListener('submit', function(e) {
e.preventDefault();
showShippingInfoModal();
});
// 模态框控制
document.getElementById('closeModal').addEventListener('click', closeOrderModal);
document.getElementById('pay-now-btn').addEventListener('click', handlePayNow);
// 配送信息模态框控制
document.getElementById('closeShippingModal').addEventListener('click', closeShippingInfoModal);
document.getElementById('shippingForm').addEventListener('submit', handleShippingOrderSubmit);
// 手机号验证 - 模态框中的
document.getElementById('modal_customer_phone').addEventListener('blur', function() {
validatePhoneField(this);
});
document.getElementById('modal_customer_phone').addEventListener('input', function(e) {
// 只允许输入数字
e.target.value = e.target.value.replace(/[^0-9]/g, '');
// 限制长度为11位
if (e.target.value.length > 11) {
e.target.value = e.target.value.slice(0, 11);
}
});
// 支付状态检查
document.getElementById('check-status-btn').addEventListener('click', checkPaymentStatus);
// 点击模态框外部关闭
orderModal.addEventListener('click', (e) => {
if (e.target === orderModal) {
closeOrderModal();
}
});
}
// 更新总价
function updateTotalPrice() {
if (!currentProduct) return;
const quantity = parseInt(quantityInput.value) || 1;
const unitPrice = currentProduct.price;
const originalTotal = unitPrice * quantity;
// 计算折扣
let discount = 0;
let discountText = '';
let finalTotal = originalTotal;
if (quantity >= 5) {
discount = 0.1; // 9折优惠10%
discountText = '5个及以上 9折优惠';
finalTotal = originalTotal * 0.9;
} else if (quantity >= 2) {
discount = 0.05; // 9.5折优惠5%
discountText = '2个 9.5折优惠';
finalTotal = originalTotal * 0.95;
}
// 更新显示
const discountLine = document.getElementById('discount-line');
const subtotalLine = document.getElementById('subtotal-line');
const discountTextElement = document.getElementById('discount-text');
const subtotalPriceElement = document.getElementById('subtotal-price');
// 始终显示优惠和小计信息
if (discount > 0) {
// 显示优惠信息
const discountAmount = originalTotal - finalTotal;
discountTextElement.textContent = `-$${discountAmount.toFixed(2)} USDT (${discountText})`;
subtotalPriceElement.textContent = `$${originalTotal.toFixed(2)} USDT`;
discountLine.classList.remove('no-discount');
} else {
// 显示无优惠
discountTextElement.textContent = `$0.00 USDT (无优惠)`;
subtotalPriceElement.textContent = `$${originalTotal.toFixed(2)} USDT`;
discountLine.classList.add('no-discount');
}
discountLine.style.display = 'flex';
subtotalLine.style.display = 'flex';
totalPriceElement.textContent = `$${finalTotal.toFixed(2)} USDT`;
}
// 显示配送信息模态框
function showShippingInfoModal() {
if (!currentProduct) return;
const quantity = parseInt(quantityInput.value) || 1;
const unitPrice = currentProduct.price;
const originalTotal = unitPrice * quantity;
let finalTotal = originalTotal;
let discountText = '';
let hasDiscount = false;
// 计算折扣
if (quantity >= 5) {
finalTotal = originalTotal * 0.9;
discountText = '9折优惠 (-10%)';
hasDiscount = true;
} else if (quantity >= 2) {
finalTotal = originalTotal * 0.95;
discountText = '9.5折优惠 (-5%)';
hasDiscount = true;
}
// 更新订单汇总信息
document.getElementById('summary-product-name').textContent = currentProduct.name;
document.getElementById('summary-product-desc').textContent = currentProduct.description;
document.getElementById('summary-quantity').textContent = quantity;
document.getElementById('summary-unit-price').textContent = `$${unitPrice.toFixed(2)} USDT`;
document.getElementById('summary-total-price').textContent = `$${finalTotal.toFixed(2)} USDT`;
// 更新折扣信息
const discountRow = document.querySelector('.discount-row');
const discountTextElement = document.getElementById('summary-discount-text');
if (hasDiscount) {
const discountAmount = originalTotal - finalTotal;
discountTextElement.textContent = `-$${discountAmount.toFixed(2)} USDT (${discountText})`;
discountRow.classList.remove('no-discount');
} else {
discountTextElement.textContent = `$0.00 USDT (无优惠)`;
discountRow.classList.add('no-discount');
}
document.getElementById('shippingInfoModal').style.display = 'flex';
}
// 关闭配送信息模态框
function closeShippingInfoModal() {
document.getElementById('shippingInfoModal').style.display = 'none';
}
// 验证手机号字段(通用函数)
function validatePhoneField(phoneInput) {
const phone = phoneInput.value.trim();
if (phone && !validatePhoneNumber(phone)) {
phoneInput.style.borderColor = '#ff4757';
showFieldError(phoneInput, '请输入正确的11位手机号码');
} else {
phoneInput.style.borderColor = '#444';
hideFieldError(phoneInput);
}
}
// 验证数量
function validateQuantity() {
const value = parseInt(quantityInput.value);
if (isNaN(value) || value < 1) {
quantityInput.value = 1;
} else if (value > 999) {
quantityInput.value = 999;
}
updateTotalPrice();
}
// 处理订单提交
async function handleOrderSubmit(e) {
e.preventDefault();
if (!currentProduct) {
alert('产品信息加载中,请稍后重试');
return;
}
// 获取表单数据
const formData = new FormData(orderForm);
const quantity = parseInt(formData.get('quantity'));
// 计算最终价格(包含折扣)
const unitPrice = currentProduct.price;
const originalTotal = unitPrice * quantity;
let finalTotal = originalTotal;
if (quantity >= 5) {
finalTotal = originalTotal * 0.9; // 9折
} else if (quantity >= 2) {
finalTotal = originalTotal * 0.95; // 9.5折
}
const orderData = {
product_id: currentProduct.id,
quantity: quantity,
unit_price: unitPrice,
total_amount: finalTotal, // 使用折扣后的价格
customer_name: formData.get('customer_name').trim(),
customer_email: formData.get('customer_email').trim(),
customer_phone: formData.get('customer_phone').trim(),
shipping_address: formData.get('shipping_address').trim()
};
// 验证必填字段
if (!orderData.customer_name || !orderData.customer_phone || !orderData.customer_email || !orderData.shipping_address) {
alert('请填写所有必填信息');
return;
}
// 验证手机号格式
if (!validatePhoneNumber(orderData.customer_phone)) {
alert('请输入正确的11位手机号码');
return;
}
// 验证邮箱格式
if (!validateEmail(orderData.customer_email)) {
alert('请输入正确的邮箱地址');
return;
}
// 禁用提交按钮
const submitBtn = document.getElementById('submit-order');
const btnText = submitBtn.querySelector('.btn-text');
const btnLoading = submitBtn.querySelector('.btn-loading');
submitBtn.disabled = true;
btnText.style.display = 'none';
btnLoading.style.display = 'inline';
try {
// 创建订单
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(orderData)
});
const result = await response.json();
if (result.success) {
currentOrder = {
...orderData,
order_id: result.order_id,
total_amount: result.total_amount
};
showOrderModal();
} else {
throw new Error(result.error || '订单创建失败');
}
} catch (error) {
console.error('创建订单失败:', error);
alert('创建订单失败: ' + error.message);
} finally {
// 恢复提交按钮
submitBtn.disabled = false;
btnText.style.display = 'inline';
btnLoading.style.display = 'none';
}
}
// 显示订单确认模态框
function showOrderModal() {
if (!currentOrder) return;
// 填充订单信息
document.getElementById('modal-order-id').textContent = currentOrder.order_id;
document.getElementById('modal-product-name').textContent = currentProduct.name;
document.getElementById('modal-quantity').textContent = currentOrder.quantity;
document.getElementById('modal-total-price').textContent = `$${currentOrder.total_amount.toFixed(2)} USDT`;
document.getElementById('modal-address').textContent = currentOrder.shipping_address;
orderModal.style.display = 'flex';
}
// 关闭订单模态框
function closeOrderModal() {
orderModal.style.display = 'none';
}
// 处理支付
async function handlePayNow() {
if (!currentOrder) return;
try {
// 创建支付
const response = await fetch('/api/payment/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
order_id: currentOrder.order_id
})
});
const result = await response.json();
if (result.success) {
// 检查是否为手动支付模式
if (result.manual_mode) {
// 关闭模态框,跳转到手动支付页面
closeOrderModal();
window.location.href = result.payment_url;
} else {
// 关闭模态框直接跳转到UPay支付页面
closeOrderModal();
// 显示跳转提示
const jumpTip = document.createElement('div');
jumpTip.innerHTML = `
<div class="jump-tip-overlay">
<div class="jump-tip-content">
<div class="jump-tip-icon">
<div class="loading-spinner-gold"></div>
</div>
<h3>正在跳转到支付页面...</h3>
<p>请在新页面完成USDT支付</p>
</div>
</div>
`;
document.body.appendChild(jumpTip);
// 3秒后跳转到UPay支付页面
setTimeout(() => {
window.location.href = result.payment_url;
}, 3000);
}
} else {
throw new Error(result.error || '创建支付失败');
}
} catch (error) {
console.error('创建支付失败:', error);
alert('创建支付失败: ' + error.message);
}
}
// 显示支付状态页面
function showPaymentStatus() {
if (!currentOrder) return;
document.getElementById('status-order-id').textContent = currentOrder.order_id;
document.getElementById('status-amount').textContent = currentOrder.total_amount.toFixed(2);
paymentStatus.style.display = 'flex';
// 开始监听支付状态
startPaymentStatusCheck();
}
// 开始支付状态检查
function startPaymentStatusCheck() {
// 每10秒检查一次支付状态
const checkInterval = setInterval(async () => {
const status = await checkPaymentStatus();
if (status === 'finished' || status === 'failed') {
clearInterval(checkInterval);
}
}, 10000);
}
// 检查支付状态
async function checkPaymentStatus() {
if (!currentOrder) return;
try {
const response = await fetch(`/api/orders/${currentOrder.order_id}`);
const order = await response.json();
const statusIcon = document.getElementById('status-icon');
const statusTitle = document.getElementById('status-title');
const statusMessage = document.getElementById('status-message');
switch (order.payment_status) {
case 'finished':
statusIcon.innerHTML = '<div class="success-icon">✓</div>';
statusTitle.textContent = '支付成功!';
statusMessage.textContent = '您的订单已确认,我们将尽快处理并发货。';
break;
case 'failed':
case 'expired':
statusIcon.innerHTML = '<div class="error-icon">✗</div>';
statusTitle.textContent = '支付失败';
statusMessage.textContent = '支付未完成或已过期,请重新下单。';
break;
case 'confirming':
statusTitle.textContent = '支付确认中...';
statusMessage.textContent = '我们已收到您的支付,正在等待区块链确认。';
break;
default:
statusTitle.textContent = '等待支付...';
statusMessage.textContent = '请完成USDT支付我们正在等待您的交易。';
}
return order.payment_status;
} catch (error) {
console.error('检查支付状态失败:', error);
return null;
}
}
// 工具函数:验证邮箱
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// 工具函数:验证手机号码
function validatePhoneNumber(phone) {
const phoneRegex = /^1[3-9]\d{9}$/;
return phoneRegex.test(phone);
}
// 手机号失焦验证
function validatePhone() {
const phoneInput = document.getElementById('customer_phone');
const phone = phoneInput.value.trim();
if (phone && !validatePhoneNumber(phone)) {
phoneInput.style.borderColor = '#ff4757';
showFieldError(phoneInput, '请输入正确的11位手机号码');
} else {
phoneInput.style.borderColor = '#444';
hideFieldError(phoneInput);
}
}
// 显示字段错误
function showFieldError(input, message) {
hideFieldError(input); // 先清除已有错误
const errorDiv = document.createElement('div');
errorDiv.className = 'field-error';
errorDiv.textContent = message;
errorDiv.style.cssText = 'color: #ff4757; font-size: 12px; margin-top: 5px; padding-left: 5px;';
input.parentNode.appendChild(errorDiv);
}
// 隐藏字段错误
function hideFieldError(input) {
const existingError = input.parentNode.querySelector('.field-error');
if (existingError) {
existingError.remove();
}
}
// 工具函数:验证电话号码(保留兼容性)
function validatePhone(phone) {
return validatePhoneNumber(phone);
}
// 管理员登录相关函数
function showAdminLogin() {
document.getElementById('adminLoginModal').style.display = 'flex';
document.getElementById('adminPassword').focus();
}
function closeAdminLogin() {
document.getElementById('adminLoginModal').style.display = 'none';
document.getElementById('adminPassword').value = '';
document.getElementById('loginError').style.display = 'none';
}
// 联系我们相关函数
function showContactModal() {
document.getElementById('contactModal').style.display = 'flex';
}
function closeContactModal() {
document.getElementById('contactModal').style.display = 'none';
}
function verifyAdminPassword() {
const password = document.getElementById('adminPassword').value;
const errorDiv = document.getElementById('loginError');
if (password === '223388') {
// 密码正确,跳转到管理页面
window.location.href = '/admin.html';
} else {
// 密码错误
errorDiv.textContent = '密码错误,请重新输入';
errorDiv.style.display = 'block';
document.getElementById('adminPassword').value = '';
document.getElementById('adminPassword').focus();
}
}
// 键盘事件监听
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeAdminLogin();
closeContactModal();
}
});
// 管理员登录密码框回车事件
document.addEventListener('DOMContentLoaded', function() {
const adminPasswordInput = document.getElementById('adminPassword');
if (adminPasswordInput) {
adminPasswordInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
verifyAdminPassword();
}
});
}
});
// 处理配送信息提交并直接下单
async function handleShippingOrderSubmit(e) {
e.preventDefault();
if (!currentProduct) {
alert('产品信息加载中,请稍后重试');
return;
}
// 获取表单数据
const formData = new FormData(e.target);
const quantity = parseInt(quantityInput.value);
// 计算最终价格(包含折扣)
const unitPrice = currentProduct.price;
const originalTotal = unitPrice * quantity;
let finalTotal = originalTotal;
if (quantity >= 5) {
finalTotal = originalTotal * 0.9; // 9折
} else if (quantity >= 2) {
finalTotal = originalTotal * 0.95; // 9.5折
}
const orderData = {
product_id: currentProduct.id,
quantity: quantity,
unit_price: unitPrice,
total_amount: finalTotal, // 使用折扣后的价格
customer_name: formData.get('customer_name').trim(),
customer_email: formData.get('customer_email').trim(),
customer_phone: formData.get('customer_phone').trim(),
shipping_address: formData.get('shipping_address').trim()
};
// 验证必填字段
if (!orderData.customer_name || !orderData.customer_phone || !orderData.customer_email || !orderData.shipping_address) {
alert('请填写所有必填信息');
return;
}
// 验证手机号格式
if (!validatePhoneNumber(orderData.customer_phone)) {
alert('请输入正确的11位手机号码');
return;
}
// 验证邮箱格式
if (!validateEmail(orderData.customer_email)) {
alert('请输入正确的邮箱地址');
return;
}
// 禁用提交按钮
const submitBtn = document.getElementById('submit-shipping-order');
const btnText = submitBtn.querySelector('.btn-text');
const btnLoading = submitBtn.querySelector('.btn-loading');
submitBtn.disabled = true;
btnText.style.display = 'none';
btnLoading.style.display = 'inline';
try {
// 创建订单
const orderResponse = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(orderData)
});
const orderResult = await orderResponse.json();
if (orderResult.success) {
// 创建支付
const paymentResponse = await fetch('/api/payment/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
order_id: orderResult.order_id
})
});
const paymentResult = await paymentResponse.json();
if (paymentResult.success) {
// 关闭配送信息模态框
closeShippingInfoModal();
if (paymentResult.manual_mode) {
// 手动支付模式
window.location.href = paymentResult.payment_url;
} else {
// 显示跳转提示并直接跳转到UPay支付页面
const jumpTip = document.createElement('div');
jumpTip.innerHTML = `
<div class="jump-tip-overlay">
<div class="jump-tip-content">
<div class="jump-tip-icon">
<div class="loading-spinner-gold"></div>
</div>
<h3>正在跳转到支付页面...</h3>
<p>请在新页面完成USDT支付</p>
</div>
</div>
`;
document.body.appendChild(jumpTip);
// 3秒后跳转到UPay支付页面
setTimeout(() => {
window.location.href = paymentResult.payment_url;
}, 3000);
}
} else {
throw new Error(paymentResult.error || '创建支付失败');
}
} else {
throw new Error(orderResult.error || '订单创建失败');
}
} catch (error) {
console.error('下单失败:', error);
alert('下单失败: ' + error.message);
} finally {
// 恢复提交按钮
submitBtn.disabled = false;
btnText.style.display = 'inline';
btnLoading.style.display = 'none';
}
}
// 验证手机号字段(通用函数)
function validatePhoneField(phoneInput) {
const phone = phoneInput.value.trim();
if (phone && !validatePhoneNumber(phone)) {
phoneInput.style.borderColor = '#ff4757';
showFieldError(phoneInput, '请输入正确的11位手机号码');
} else {
phoneInput.style.borderColor = '#444';
hideFieldError(phoneInput);
}
}