const Upload = (() => { // 使用localStorage保存上传记录 const STORAGE_KEY = 'mac_upload_history'; // 事件监听器清理 const eventListeners = []; const addListener = (element, event, handler) => { if(element){ element.addEventListener(event, handler); eventListeners.push({element, event, handler}); } }; const cleanupListeners = () => { eventListeners.forEach(({element, event, handler}) => { element.removeEventListener(event, handler); }); eventListeners.length = 0; }; Router.onBeforeEach((path) => { if(!path.startsWith('/upload')){ cleanupListeners(); } }); function getHistory() { try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); } catch { return []; } } function saveToHistory(records) { try { const history = getHistory(); const newRecords = records.map(r => ({ ...r, timestamp: new Date().toISOString() })); const updated = [...newRecords, ...history].slice(0, 100); // 保留最近100条 localStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); } catch (e) { console.error('保存历史记录失败:', e); } } function clearHistory() { localStorage.removeItem(STORAGE_KEY); } function section(title, inner) { return `
${title}
${inner}
`; } function filePicker(id,label,accept){ return `
`; } function numberInput(id,label){return `
`} function textarea(id,label,placeholder=''){return `
`} async function renderMac(){ return section('MAC与批次',`
${filePicker('mac-file','批量导入(Excel)','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel')}
最新记录
`); } async function renderStats(){ return section('良/不良统计',`
📊 数据说明
直通良品数:一次检测就通过的产品数量
良品数:最终通过检测的产品总数(包含直通良品 + 返修后通过的产品)
不良品数:最终未通过检测的产品数量(报废或待返修)
💡 计算公式:
直通良品率 = 直通良品数 / (良品数 + 不良品数) × 100%
总良品率 = 良品数 / (良品数 + 不良品数) × 100%
${numberInput('fpy-good-count','直通良品数量(一次检测通过)')} ${numberInput('good-count','良品数量(最终通过检测的总数)')} ${numberInput('bad-count','不良品数量(最终未通过)')}
最新记录
`); } async function renderRepairs(){ return section('返修记录',` ${numberInput('repair-qty','返修完成数量')} ${textarea('repair-note','备注(可选)','例如:批次号、问题描述等')}
最新记录
`); } async function renderDefects(){ return section('不良明细',` ${filePicker('defects-file','批量导入不良MAC与批次(Excel/CSV)','text/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel')} ${textarea('defects-manual','手动输入','AA:BB:...,BATCH-xyz; ...')}
最新记录
`); } Router.register('/upload/mac', async () => { const html = await renderMac(); setTimeout(bindMacEvents,0); setTimeout(()=>{ // 显示历史记录中的最新10条 const history = getHistory(); const listEl=document.getElementById('mac-list'); if(listEl && history.length > 0){ listEl.innerHTML = history.slice(0, 10).map(r=>`
  • ${r.mac}${r.batch}
  • `).join(''); } else if(listEl) { listEl.innerHTML = '
  • 暂无数据
  • '; } },0); return html; }); Router.register('/upload/stats', async () => { const html = await renderStats(); setTimeout(bindStatsEvents,0); setTimeout(()=>{ // 显示历史记录中的最新10条 const STATS_STORAGE_KEY = 'stats_upload_history'; try { const history = JSON.parse(localStorage.getItem(STATS_STORAGE_KEY) || '[]'); const listEl=document.getElementById('stats-list'); if(listEl && history.length > 0){ listEl.innerHTML = history.slice(0, 10).map(r=>{ const platformName = {pdd: '拼多多', yt: '圆通', tx: '兔喜'}[r.platform] || ''; const platformText = platformName ? `${platformName} - ` : ''; let html = `
  • ${platformText}直通良:${r.fpy_good||0} 良:${r.good} 不良:${r.bad}`; if(r.details && r.details.length > 0){ html += `${r.details.length}条明细`; } html += '
  • '; return html; }).join(''); } else if(listEl) { listEl.innerHTML = '
  • 暂无数据
  • '; } } catch(e) { console.error('加载历史记录失败:', e); } },0); return html; }); Router.register('/upload/repairs', async () => { const html = await renderRepairs(); setTimeout(bindRepairsEvents,0); setTimeout(async ()=>{ const listEl=document.getElementById('repairs-list'); const data=await API.listRepairs().catch(()=>({list:[]})); listEl.innerHTML=(data.list||[]).slice(0,10).map(r=>{ const ts = new Date(r.ts).toLocaleString('zh-CN'); return `
  • 数量: ${r.qty}${r.note||'无备注'}${ts}
  • `; }).join('')||'
  • 暂无数据
  • '; },0); return html; }); Router.register('/upload/defects', async () => { const html = await renderDefects(); setTimeout(bindDefectsEvents,0); setTimeout(async ()=>{ const listEl=document.getElementById('defects-list'); const data=await API.listDefects().catch(()=>({list:[]})); listEl.innerHTML=(data.list||[]).slice(0,10).map(r=>`
  • ${r.mac}${r.batch}
  • `).join('')||'
  • 暂无数据
  • '; },0); return html; }); function readText(file){return new Promise((resolve,reject)=>{const r=new FileReader();r.onload=()=>resolve(r.result);r.onerror=reject;r.readAsText(file)})} function parseManual(text){ return text.split(/\n+/).map(l=>l.trim()).filter(Boolean).map(l=>{ const [mac,batch]=l.split(','); return { mac, batch }; }); } async function bindMacEvents(){ const fileEl=document.getElementById('mac-file'); const btn = document.getElementById('mac-upload'); const showHistoryBtn = document.getElementById('mac-show-history'); const clearDisplayBtn = document.getElementById('mac-clear-display'); // 文件选择后立即验证 addListener(fileEl, 'change', async ()=>{ const file = fileEl.files[0]; if(!file) return; try{ const formData = new FormData(); formData.append('file', file); const res = await fetch('/api/validate/mac-file', { method: 'POST', body: formData }); const result = await res.json(); if(!result.valid){ API.toast(result.message || '文件格式不正确'); fileEl.value = ''; return; } API.toast(result.message || '文件验证通过'); }catch(e){ API.toast('文件验证失败'); fileEl.value = ''; } }); // 查看历史按钮 addListener(showHistoryBtn, 'click', ()=>{ const history = getHistory(); const listEl = document.getElementById('mac-list'); if(listEl){ if(history.length > 0){ listEl.innerHTML = history.map(r=>`
  • ${r.mac}${r.batch}${new Date(r.timestamp).toLocaleString('zh-CN')}
  • `).join(''); API.toast(`显示全部 ${history.length} 条历史记录`); } else { listEl.innerHTML = '
  • 暂无历史记录
  • '; } } }); // 清空显示按钮 addListener(clearDisplayBtn, 'click', ()=>{ const listEl = document.getElementById('mac-list'); if(listEl){ listEl.innerHTML = '
  • 已清空显示
  • '; API.toast('已清空显示(历史记录仍保留)'); } }); addListener(btn, 'click', async ()=>{ const file = fileEl.files[0]; if(!file){ API.toast('请选择文件'); return; } const typeEl = document.getElementById('mac-type'); const uploadType = typeEl ? typeEl.value : 'pdd'; const logContainer = document.getElementById('upload-log'); const logPre = logContainer ? logContainer.querySelector('pre') : null; try{ btn.disabled = true; if(logContainer) logContainer.style.display = 'block'; if(logPre) logPre.textContent = '正在上传文件...\n'; const formData = new FormData(); formData.append('file', file); formData.append('type', uploadType); const res = await fetch('/api/upload/mac-file', { method: 'POST', body: formData }); const result = await res.json(); if(logPre){ logPre.textContent = result.output || '上传完成'; } if(result.ok){ API.toast('上传成功'); // 解析并显示成功上传的记录 const output = result.output || ''; const jsonMatch = output.match(/=== 成功导入的数据 ===\n([\s\S]*?)\n=== 数据输出结束 ===/); if(jsonMatch && jsonMatch[1]){ try{ const records = JSON.parse(jsonMatch[1].trim()); if(records.length > 0){ // 保存到历史记录 saveToHistory(records); // 显示最新记录 const listEl = document.getElementById('mac-list'); if(listEl){ listEl.innerHTML = records.map(r=>`
  • ${r.mac}${r.batch}
  • `).join(''); } } }catch(e){ console.error('解析上传记录失败:', e); } } } else { API.toast(result.error || '上传失败'); } if(fileEl) fileEl.value = ''; }catch(e){ API.toast('上传失败: ' + e.message); if(logPre) logPre.textContent += '\n错误: ' + e.message; } finally { if (btn) btn.disabled = false; } }); } async function bindStatsEvents(){ const STATS_STORAGE_KEY = 'stats_upload_history'; const getStatsHistory = () => { try { return JSON.parse(localStorage.getItem(STATS_STORAGE_KEY) || '[]'); } catch { return []; } }; const saveToStatsHistory = (record) => { try { const history = getStatsHistory(); const newRecord = { ...record, timestamp: new Date().toISOString() }; const updated = [newRecord, ...history].slice(0, 100); localStorage.setItem(STATS_STORAGE_KEY, JSON.stringify(updated)); } catch (e) { console.error('保存历史记录失败:', e); } }; const btn = document.getElementById('stats-upload'); const showHistoryBtn = document.getElementById('stats-show-history'); const clearDisplayBtn = document.getElementById('stats-clear-display'); // 查看历史按钮 addListener(showHistoryBtn, 'click', ()=>{ const history = getStatsHistory(); const listEl = document.getElementById('stats-list'); if(listEl){ if(history.length > 0){ listEl.innerHTML = history.map(r=>{ const platformName = {pdd: '拼多多', yt: '圆通', tx: '兔喜'}[r.platform] || ''; const platformText = platformName ? `${platformName} - ` : ''; let html = `
  • ${platformText}直通良:${r.fpy_good||0} 良:${r.good} 不良:${r.bad}`; if(r.details && r.details.length > 0){ html += `${r.details.length}条明细`; } html += `${new Date(r.timestamp).toLocaleString('zh-CN')}
  • `; return html; }).join(''); API.toast(`显示全部 ${history.length} 条历史记录`); } else { listEl.innerHTML = '
  • 暂无历史记录
  • '; } } }); // 清空显示按钮 addListener(clearDisplayBtn, 'click', ()=>{ const listEl = document.getElementById('stats-list'); if(listEl){ listEl.innerHTML = '
  • 已清空显示
  • '; API.toast('已清空显示(历史记录仍保留)'); } }); addListener(btn, 'click', async ()=>{ const platform = document.getElementById('stats-platform').value; const fpyGood=parseInt(document.getElementById('fpy-good-count').value||'0',10); const good=parseInt(document.getElementById('good-count').value||'0',10); const bad=parseInt(document.getElementById('bad-count').value||'0',10); const detailsText = document.getElementById('bad-details')?.value.trim() || ''; if(fpyGood<0||good<0||bad<0){return API.toast('数量不能为负数')} // 解析不良明细 const details = []; if(detailsText){ const lines = detailsText.split('\n').filter(l => l.trim()); for(const line of lines){ const [mac, batch] = line.split(',').map(s => s.trim()); if(mac && batch){ details.push({mac, batch}); } } } btn.disabled = true; try{ await API.uploadStats({platform, fpy_good: fpyGood, good, bad, details}); API.toast('上传成功'); // 保存到历史记录 saveToStatsHistory({platform, fpy_good: fpyGood, good, bad, details}); // 显示最新记录 const listEl=document.getElementById('stats-list'); if(listEl){ const platformName = {pdd: '拼多多', yt: '圆通', tx: '兔喜'}[platform] || platform; let html = `
  • ${platformName} - 直通良:${fpyGood} 良:${good} 不良:${bad}`; if(details.length > 0){ html += `${details.length}条明细`; } html += '
  • '; listEl.innerHTML = html; } // 清空输入 document.getElementById('fpy-good-count').value = ''; document.getElementById('good-count').value = ''; document.getElementById('bad-count').value = ''; document.getElementById('bad-details').value = ''; }catch(e){ API.toast('上传失败'); } finally { if (btn) btn.disabled = false; } }); } async function bindRepairsEvents(){ const btn = document.getElementById('repairs-upload'); const showAllBtn = document.getElementById('repairs-show-all'); // 查看全部按钮 addListener(showAllBtn, 'click', async ()=>{ const listEl = document.getElementById('repairs-list'); const data = await API.listRepairs().catch(()=>({list:[]})); if(listEl){ if(data.list && data.list.length > 0){ listEl.innerHTML = data.list.map(r=>{ const ts = new Date(r.ts).toLocaleString('zh-CN'); return `
  • 数量: ${r.qty}${r.note||'无备注'}${ts}
  • `; }).join(''); API.toast(`显示全部 ${data.list.length} 条记录`); } else { listEl.innerHTML = '
  • 暂无记录
  • '; } } }); addListener(btn, 'click', async ()=>{ const qty = parseInt(document.getElementById('repair-qty').value||'0', 10); const note = document.getElementById('repair-note')?.value.trim() || ''; if(qty <= 0){ return API.toast('请输入有效的返修数量'); } btn.disabled = true; try{ await API.uploadRepairs({qty, note}); API.toast('上传成功'); // 刷新列表 const listEl = document.getElementById('repairs-list'); const data = await API.listRepairs().catch(()=>({list:[]})); if(listEl){ listEl.innerHTML = (data.list||[]).slice(0,10).map(r=>{ const ts = new Date(r.ts).toLocaleString('zh-CN'); return `
  • 数量: ${r.qty}${r.note||'无备注'}${ts}
  • `; }).join('')||'
  • 暂无数据
  • '; } // 清空输入 document.getElementById('repair-qty').value = ''; document.getElementById('repair-note').value = ''; }catch(e){ API.toast('上传失败'); } finally { if (btn) btn.disabled = false; } }); } async function bindDefectsEvents(){ const btn = document.getElementById('defects-upload'); addListener(btn, 'click', async ()=>{ try{ const fileEl=document.getElementById('defects-file'); const manual=document.getElementById('defects-manual').value.trim(); let rows=[]; if(fileEl.files[0]){ const text=await readText(fileEl.files[0]); rows=text.split(/\n+/).map(l=>l.split(',')); rows=rows.map(([mac,batch])=>({mac,batch})); }else if(manual){ rows=parseManual(manual); } if(!rows.length){throw new Error('请提供文件或手动输入')} btn.disabled = true; await API.uploadDefects({rows}); API.toast('上传成功'); const listEl=document.getElementById('defects-list'); const data=await API.listDefects().catch(()=>({list:[]})); if(listEl) listEl.innerHTML=(data.list||[]).slice(0,10).map(r=>`
  • ${r.mac}${r.batch}
  • `).join('')||'
  • 暂无数据
  • '; }catch(e){ API.toast('上传失败'); } finally { if (btn) btn.disabled = false; } }); } })();