ishop/public/js/admin.js
2025-08-10 12:20:27 +08:00

443 lines
16 KiB
JavaScript

// 订单管理页面脚本
let currentOrders = [];
let currentOrderId = null;
// DOM 元素
const ordersTableBody = document.getElementById('ordersTableBody');
const searchInput = document.getElementById('searchOrder');
const statusFilter = document.getElementById('statusFilter');
const orderDetailModal = document.getElementById('orderDetailModal');
const shippingModal = document.getElementById('shippingModal');
// 页面加载时初始化
document.addEventListener('DOMContentLoaded', function() {
loadOrders();
});
// 加载订单列表
async function loadOrders(search = '', status = '') {
try {
showLoading();
const params = new URLSearchParams();
if (search) params.append('search', search);
if (status) params.append('status', status);
const response = await fetch(`/api/admin/orders?${params}`);
const data = await response.json();
if (response.ok) {
currentOrders = data.orders;
updateOrdersTable(data.orders);
updateStats(data.stats);
} else {
throw new Error(data.error || '加载订单失败');
}
} catch (error) {
console.error('Load orders error:', error);
showError('加载订单失败: ' + error.message);
}
}
// 更新订单表格
function updateOrdersTable(orders) {
if (!orders || orders.length === 0) {
ordersTableBody.innerHTML = '<tr><td colspan="8" class="loading">暂无订单数据</td></tr>';
return;
}
ordersTableBody.innerHTML = orders.map(order => `
<tr>
<td>
<strong>${order.order_id}</strong>
</td>
<td>
<div style="line-height: 1.4;">
<strong>${order.customer_name}</strong>
<button class="copy-btn" onclick="copyOrderInfo('${order.order_id}')" title="复制收货信息" style="margin-left: 5px; font-size: 12px;">📋</button><br>
<small style="color: #666;">${order.customer_phone || '未提供电话'}</small><br>
<small style="color: #666; font-size: 11px; max-width: 200px; display: inline-block; word-wrap: break-word;">${order.shipping_address ? (order.shipping_address.length > 30 ? order.shipping_address.substring(0, 30) + '...' : order.shipping_address) : '未提供地址'}</small>
</div>
</td>
<td>
<div>
${order.product_name}<br>
<small style="color: #888;">数量: ${order.quantity}</small>
</div>
</td>
<td>
<strong style="color: #ffd700;">$${order.total_amount.toFixed(2)}</strong>
</td>
<td>
<span class="status-badge status-${order.payment_status}">
${getStatusText(order.payment_status, 'payment')}
</span>
</td>
<td>
<span class="status-badge status-${order.shipping_status}">
${getStatusText(order.shipping_status, 'shipping')}
</span>
</td>
<td>
${formatDate(order.created_at)}
</td>
<td>
<button class="action-btn btn-ship" onclick="showShippingModal('${order.order_id}')">
📦 发货
</button>
</td>
</tr>
`).join('');
}
// 更新统计数据
function updateStats(stats) {
if (stats) {
const totalElement = document.getElementById('totalOrders');
const pendingElement = document.getElementById('pendingOrders');
const shippedElement = document.getElementById('shippedOrders');
if (totalElement) totalElement.textContent = stats.total || 0;
if (pendingElement) pendingElement.textContent = stats.pending_ship || 0;
if (shippedElement) shippedElement.textContent = stats.shipped || 0;
}
}
// 搜索订单
function searchOrders() {
const search = searchInput.value.trim();
const status = statusFilter.value;
loadOrders(search, status);
}
// 筛选订单
function filterOrders() {
const search = searchInput.value.trim();
const status = statusFilter.value;
loadOrders(search, status);
}
// 显示订单详情
async function showOrderDetail(orderId) {
try {
const response = await fetch(`/api/orders/${orderId}`);
const order = await response.json();
if (response.ok) {
const detailContent = document.getElementById('orderDetailContent');
detailContent.innerHTML = `
<div class="detail-section">
<h4>基本信息</h4>
<div class="detail-row">
<span class="detail-label">订单号:</span>
<span class="detail-value">${order.order_id}</span>
</div>
<div class="detail-row">
<span class="detail-label">支付ID:</span>
<span class="detail-value">${order.payment_id || '未设置'}</span>
</div>
<div class="detail-row">
<span class="detail-label">创建时间:</span>
<span class="detail-value">${formatDate(order.created_at)}</span>
</div>
<div class="detail-row">
<span class="detail-label">更新时间:</span>
<span class="detail-value">${formatDate(order.updated_at)}</span>
</div>
</div>
<div class="detail-section">
<h4>客户信息</h4>
<div class="detail-row">
<span class="detail-label">姓名:</span>
<span class="detail-value">${order.customer_name}</span>
</div>
<div class="detail-row">
<span class="detail-label">邮箱:</span>
<span class="detail-value">${order.customer_email || '未提供'}</span>
</div>
<div class="detail-row">
<span class="detail-label">电话:</span>
<span class="detail-value">${order.customer_phone || '未提供'}</span>
</div>
<div class="detail-row">
<span class="detail-label">收货地址:</span>
<span class="detail-value">
${order.shipping_address}
<button class="copy-btn" onclick="copyText('${order.shipping_address.replace(/'/g, "\\'")}')">📋</button>
</span>
</div>
</div>
<div class="detail-section">
<h4>商品信息</h4>
<div class="detail-row">
<span class="detail-label">产品名称:</span>
<span class="detail-value">${order.product_name}</span>
</div>
<div class="detail-row">
<span class="detail-label">单价:</span>
<span class="detail-value">$${order.unit_price.toFixed(2)}</span>
</div>
<div class="detail-row">
<span class="detail-label">数量:</span>
<span class="detail-value">${order.quantity}</span>
</div>
<div class="detail-row">
<span class="detail-label">总金额:</span>
<span class="detail-value" style="color: #ffd700; font-weight: bold;">$${order.total_amount.toFixed(2)}</span>
</div>
</div>
<div class="detail-section">
<h4>状态信息</h4>
<div class="detail-row">
<span class="detail-label">支付状态:</span>
<span class="detail-value">
<span class="status-badge status-${order.payment_status}">
${getStatusText(order.payment_status, 'payment')}
</span>
</span>
</div>
<div class="detail-row">
<span class="detail-label">发货状态:</span>
<span class="detail-value">
<span class="status-badge status-${order.shipping_status}">
${getStatusText(order.shipping_status, 'shipping')}
</span>
</span>
</div>
${order.tracking_number ? `
<div class="detail-row">
<span class="detail-label">快递单号:</span>
<span class="detail-value">${order.tracking_number}</span>
</div>` : ''}
${order.shipping_notes ? `
<div class="detail-row">
<span class="detail-label">发货备注:</span>
<span class="detail-value">${order.shipping_notes}</span>
</div>` : ''}
</div>
`;
orderDetailModal.style.display = 'flex';
} else {
throw new Error(order.error || '获取订单详情失败');
}
} catch (error) {
console.error('Show order detail error:', error);
alert('获取订单详情失败: ' + error.message);
}
}
// 复制订单收货信息
function copyOrderInfo(orderId) {
const order = currentOrders.find(o => o.order_id === orderId);
if (!order) return;
// 组合收货信息:姓名, 电话, 地址
let infoText = order.customer_name;
if (order.customer_phone) {
infoText += ', ' + order.customer_phone;
}
infoText += ', ' + order.shipping_address;
// 复制到剪贴板
copyText(infoText);
}
// 显示发货模态框
function showShippingModal(orderId) {
currentOrderId = orderId;
const order = currentOrders.find(o => o.order_id === orderId);
if (!order) return;
// 清空表单
document.getElementById('trackingNumber').value = '';
document.getElementById('shippingNotes').value = '';
// 显示模态框
shippingModal.style.display = 'flex';
document.getElementById('trackingNumber').focus();
}
// 关闭发货模态框
function closeShippingModal() {
shippingModal.style.display = 'none';
currentOrderId = null;
}
// 确认发货
async function confirmShipping() {
const trackingNumber = document.getElementById('trackingNumber').value.trim();
const shippingNotes = document.getElementById('shippingNotes').value.trim();
if (!trackingNumber) {
alert('请输入运单号');
document.getElementById('trackingNumber').focus();
return;
}
if (!currentOrderId) {
alert('订单ID错误');
return;
}
try {
const response = await fetch(`/api/admin/orders/${currentOrderId}/shipping`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
shipping_status: 'shipped',
tracking_number: trackingNumber,
shipping_notes: shippingNotes
})
});
const result = await response.json();
if (response.ok) {
alert('订单已成功发货!');
closeShippingModal();
loadOrders(); // 重新加载订单列表
} else {
throw new Error(result.error || '发货失败');
}
} catch (error) {
console.error('Shipping error:', error);
alert('发货失败: ' + error.message);
}
}
// 确认并标记为已发货
function confirmMarkAsShipped(orderId) {
if (confirm('确认标记此订单为已发货吗?')) {
markAsShipped(orderId);
}
}
// 一键标记为已发货
async function markAsShipped(orderId) {
try {
const response = await fetch(`/api/admin/orders/${orderId}/shipping`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
shipping_status: 'shipped'
})
});
const result = await response.json();
if (response.ok) {
alert('订单已标记为发货!');
loadOrders(); // 重新加载订单列表
} else {
throw new Error(result.error || '标记发货失败');
}
} catch (error) {
console.error('Mark as shipped error:', error);
alert('标记发货失败: ' + error.message);
}
}
// 复制文本到剪贴板
async function copyText(text) {
try {
await navigator.clipboard.writeText(text);
// 显示复制成功提示
showCopySuccess();
} catch (err) {
// 降级方案:使用传统方法
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showCopySuccess();
}
}
// 显示复制成功提示
function showCopySuccess() {
// 创建提示元素
const toast = document.createElement('div');
toast.className = 'copy-toast';
toast.textContent = '已复制到剪贴板';
document.body.appendChild(toast);
// 3秒后移除提示
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 3000);
}
// 关闭订单详情模态框
function closeOrderDetail() {
orderDetailModal.style.display = 'none';
}
// 获取状态文本
function getStatusText(status, type) {
if (type === 'payment') {
switch(status) {
case 'pending': return '未支付';
case 'finished': return '已支付';
case 'failed': return '支付失败';
case 'confirming': return '确认中';
default: return status || '未知';
}
} else if (type === 'shipping') {
switch(status) {
case 'pending': return '未发货';
case 'shipped': return '已发货';
default: return status || '未知';
}
}
return status || '未知';
}
// 显示加载状态
function showLoading() {
ordersTableBody.innerHTML = '<tr><td colspan="8" class="loading">加载中...</td></tr>';
}
// 显示错误信息
function showError(message) {
ordersTableBody.innerHTML = `<tr><td colspan="8" class="loading" style="color: #ff4757;">错误: ${message}</td></tr>`;
}
// 格式化日期
function formatDate(dateString) {
if (!dateString) return '未设置';
const date = new Date(dateString);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
// 键盘事件监听
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeOrderDetail();
closeShippingModal();
}
});
// 搜索框回车事件
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
searchOrders();
}
});