(function(){ "use strict"; const csrf = document.querySelector('meta[name="staff-csrf-token"]').content; const fileInput = document.getElementById('order-file'); const fileName = document.getElementById('file-name'); const form = document.getElementById('order-import-form'); const status = document.getElementById('import-status'); const results = document.getElementById('import-results'); const confirmButton = document.getElementById('confirm-import'); let selectedFile = null; let canConfirm = false; let orderBefore = null; function setStatus(message, isError){ status.textContent = message || ''; status.classList.toggle('is-error', Boolean(isError)); } function escapeText(value){ return String(value == null ? '' : value).replace(/[&<>"']/g, function(character){ return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[character]; }); } function showResponse(data){ results.hidden = false; const summary = data.summary || {}; document.getElementById('import-summary').innerHTML = [ ['文件数据行', summary.total_rows], ['有效行', summary.valid_rows], ['重复保留', summary.duplicate_rows], ['错误行', summary.error_rows] ].map(function(item){ return '
'+escapeText(item[0])+''+escapeText(item[1] || 0)+'
'; }).join(''); document.getElementById('preview-rows').innerHTML = (data.preview || []).map(function(row){ return ''+escapeText(row.row)+''+escapeText(row.tenant_id)+''+escapeText(row.order_no)+''+escapeText(row.status)+''+escapeText(row.inbound_code)+''+(row.duplicate ? '已存在,保留原数据' : '可新增')+''; }).join('') || '没有可预览的有效数据'; document.getElementById('preview-limit-note').hidden = !data.preview_limited; const errors = document.getElementById('import-errors'); errors.innerHTML = (data.errors || []).map(function(item){ return '

第 '+escapeText(item.row)+' 行:'+escapeText(item.message)+'

'; }).join(''); errors.hidden = !data.errors || !data.errors.length; document.getElementById('results-caption').textContent = errors.hidden ? '请确认租户、订单号及重复记录处理方式。' : '发现错误行;请修正文件后重新上传,当前不会写入数据。'; canConfirm = Boolean(data.ok && summary.valid_rows); confirmButton.hidden = !canConfirm; } async function sendFile(path){ const body = new FormData(); body.append('file', selectedFile); const response = await fetch(path, {method:'POST', body:body, credentials:'same-origin', headers:{'X-CSRF-Token':csrf, 'Accept':'application/json'}, signal:AbortSignal.timeout(30000)}); const data = await response.json(); if(!response.ok && !data.summary) throw new Error(data.error || '请求失败,请稍后重试。'); return data; } async function requestJson(path, options){ options = options || {}; options.credentials = 'same-origin'; options.headers = Object.assign({'Accept':'application/json'}, options.headers || {}); const response = await fetch(path, options); const data = await response.json(); if(!response.ok) throw new Error(data.error || '请求失败,请稍后重试。'); return data; } function renderOrders(data, append){ const body = document.getElementById('order-list-rows'); const rows = (data.orders || []).map(function(row){ return ''+escapeText(row.tenant_id)+''+escapeText(row.order_no)+''+escapeText(row.status)+''+escapeText(row.inbound_code)+''+escapeText(row.shipment_id)+''+escapeText(row.country_code)+''+escapeText(row.source_updated_at)+''; }).join(''); if(!append) body.innerHTML = ''; body.insertAdjacentHTML('beforeend', rows || (!append ? '没有找到订单' : '')); orderBefore = data.next_before || null; document.getElementById('load-more-orders').hidden = !orderBefore; document.getElementById('order-list-status').textContent = '共 '+escapeText(data.total || 0)+' 条订单'; } async function loadOrders(append){ const params = new URLSearchParams({q:document.getElementById('order-query').value.trim(), tenant:document.getElementById('order-tenant').value.trim(), status:document.getElementById('order-status').value, limit:'50'}); if(append && orderBefore) params.set('before', orderBefore); const data = await requestJson('/api/admin/orders/list?'+params.toString()); renderOrders(data, append); } async function loadBatches(){ const data = await requestJson('/api/admin/orders/import/batches'); document.getElementById('import-batch-rows').innerHTML = (data.batches || []).map(function(batch){ const active = batch.state === 'active'; const action = active ? '' : '已撤销(删 '+escapeText(batch.deleted_rows)+',留 '+escapeText(batch.preserved_on_undo)+')'; return ''+escapeText(batch.imported_at)+''+escapeText(batch.filename)+''+escapeText(batch.imported_by)+''+escapeText(batch.imported_rows)+''+escapeText(batch.preserved_rows)+''+escapeText(active ? '可撤销' : '已撤销')+''+action+''; }).join('') || '暂无导入记录'; document.querySelectorAll('.batch-undo').forEach(function(button){ button.addEventListener('click', async function(){ if(!await SupportUI.confirm('撤销后只会删除本批次中仍未被修改的新增订单,确定继续吗?',{title:'撤销导入'})) return; button.disabled = true; try{ const result = await requestJson('/api/admin/orders/import/batches/'+encodeURIComponent(button.dataset.batchId)+'/undo',{method:'POST',headers:{'X-CSRF-Token':csrf}}); setStatus('批次已处理:删除 '+result.deleted+' 条,保留 '+result.preserved+' 条。'); await Promise.all([loadBatches(), loadOrders(false)]); }catch(error){ setStatus(error.message, true); button.disabled = false; } }); }); } fileInput.addEventListener('change', function(){ selectedFile = fileInput.files[0] || null; fileName.textContent = selectedFile ? selectedFile.name : '选择订单文件'; results.hidden = true; canConfirm = false; confirmButton.hidden = true; setStatus(''); }); form.addEventListener('submit', async function(event){ event.preventDefault(); if(!selectedFile) return; const button = document.getElementById('preview-import'); button.disabled = true; setStatus('正在读取并校验文件…'); try{ const data = await sendFile('/api/admin/orders/import/preview'); showResponse(data); setStatus(data.ok ? '校验完成,请检查结果。' : '发现错误行,请先修正文件。', !data.ok); }catch(error){ setStatus(error.message || '校验失败,请检查网络后重试。', true); } finally{ button.disabled = false; } }); confirmButton.addEventListener('click', async function(){ if(!canConfirm || !selectedFile) return; confirmButton.disabled = true; setStatus('正在重新校验并导入…'); try{ const data = await sendFile('/api/admin/orders/import'); canConfirm = false; confirmButton.hidden = true; fileInput.value = ''; selectedFile = null; fileName.textContent = '选择订单文件'; setStatus('导入完成:新增 '+data.imported+' 条,重复保留 '+data.preserved+' 条。'); const refreshed = await Promise.allSettled([loadBatches(), loadOrders(false)]); if(refreshed[0].status === 'rejected'){ document.getElementById('import-batch-rows').innerHTML = '导入已完成,但记录刷新失败,请刷新页面重试。'; } if(refreshed[1].status === 'rejected'){ document.getElementById('order-list-status').textContent = '导入已完成,但订单列表刷新失败:'+refreshed[1].reason.message; } if(refreshed.some(function(result){ return result.status === 'rejected'; })){ setStatus('导入已完成,但部分列表刷新失败,请刷新页面查看。', true); } }catch(error){ setStatus(error.message || '导入失败,请检查网络后重试。', true); } finally{ confirmButton.disabled = false; } }); document.getElementById('order-search-form').addEventListener('submit', async function(event){ event.preventDefault(); try { await loadOrders(false); } catch(error) { document.getElementById('order-list-status').textContent = error.message; } }); document.getElementById('load-more-orders').addEventListener('click', async function(){ this.disabled = true; try { await loadOrders(true); } catch(error) { document.getElementById('order-list-status').textContent = error.message; } finally { this.disabled = false; } }); Promise.all([loadOrders(false), loadBatches()]).catch(function(error){ document.getElementById('order-list-status').textContent = error.message; }); document.getElementById('staff-logout').addEventListener('click', async function(){ this.disabled = true; try{ await fetch('/admin/logout', {method:'POST', credentials:'same-origin', headers:{'X-CSRF-Token':csrf}}); }finally{ location.assign('/admin'); } }); })();