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 `
`;
}
function filePicker(id,label,accept){
return `${label}
`;
}
function numberInput(id,label){return `${label}
`}
function textarea(id,label,placeholder=''){return `${label}
`}
async function renderMac(){
return section('MAC与批次(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(async ()=>{
// 从服务器获取最新10条记录
const listEl=document.getElementById('mac-list');
try {
const data = await API.listMac();
if(listEl && data.list && data.list.length > 0){
listEl.innerHTML = data.list.slice(0, 10).map(r=>`${r.mac} ${r.batch} `).join('');
} else if(listEl) {
listEl.innerHTML = '暂无数据 ';
}
} catch(e) {
if(listEl) listEl.innerHTML = '加载失败 ';
}
},0);
return html;
});
Router.register('/upload/stats', async () => {
const html = await renderStats();
setTimeout(bindStatsEvents,0);
setTimeout(async ()=>{
// 从服务器获取最新10条记录
const listEl=document.getElementById('stats-list');
try {
const data = await API.listStats();
if(listEl && data.list && data.list.length > 0){
listEl.innerHTML = data.list.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} `;
html += ' ';
return html;
}).join('');
} else if(listEl) {
listEl.innerHTML = '暂无数据 ';
}
} catch(e) {
console.error('加载历史记录失败:', e);
if(listEl) listEl.innerHTML = '加载失败 ';
}
},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', async ()=>{
const listEl = document.getElementById('mac-list');
try {
const data = await API.listMac();
if(listEl){
if(data.list && data.list.length > 0){
listEl.innerHTML = data.list.map(r=>`${r.mac} ${r.batch} ${new Date(r.ts).toLocaleString('zh-CN')} `).join('');
API.toast(`显示全部 ${data.list.length} 条历史记录`);
} else {
listEl.innerHTML = '暂无历史记录 ';
}
}
} catch(e) {
API.toast('加载历史记录失败');
if(listEl) 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', async ()=>{
const listEl = document.getElementById('stats-list');
try {
const data = await API.listStats();
if(listEl){
if(data.list && data.list.length > 0){
listEl.innerHTML = data.list.map(r=>{
const platformName = {pdd: '拼多多', yt: '圆通', tx: '兔喜'}[r.platform] || '';
const platformText = platformName ? `${platformName} - ` : '';
let html = `${platformText}直通良:${r.fpy_good||0} 良:${r.good} 不良:${r.bad} `;
html += `${new Date(r.ts).toLocaleString('zh-CN')} `;
return html;
}).join('');
API.toast(`显示全部 ${data.list.length} 条历史记录`);
} else {
listEl.innerHTML = '暂无历史记录 ';
}
}
} catch(e) {
API.toast('加载历史记录失败');
if(listEl) 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;
}
});
}
})();