ERP/frontend/js/components/export.js

472 lines
18 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.

const Export = (() => {
async function render() {
setTimeout(bindEvents, 0);
return `
<div class="card">
<div style="font-weight:600;margin-bottom:16px;font-size:16px">📤 数据导出</div>
<div class="field">
<label>选择要导出的数据类型</label>
<select id="export-type" class="input">
<option value="stats">良/不良统计</option>
<option value="mac">MAC与批次</option>
<option value="repairs">返修记录</option>
<option value="defects">不良明细</option>
<option value="shipments">发货记录</option>
<option value="devices">设备状态</option>
<option value="environment">环境参数</option>
<option value="personnel">人员信息</option>
<option value="qa">质检报告</option>
<option value="production">时间记录</option>
</select>
</div>
<div class="field">
<label>导出格式</label>
<div style="display:flex;gap:12px;margin-top:8px">
<button class="btn" id="export-excel" style="flex:1">
<span>📊</span>
<span>导出为 Excel</span>
</button>
<button class="btn btn-secondary" id="export-pdf" style="flex:1">
<span>📄</span>
<span>导出为 PDF</span>
</button>
</div>
</div>
<div id="export-status" style="margin-top:16px;padding:12px;border-radius:8px;display:none"></div>
</div>
<div class="card" style="margin-top:12px">
<div style="font-weight:600;margin-bottom:12px">📋 数据预览</div>
<div id="export-preview" style="max-height:400px;overflow-y:auto">
<div style="color:var(--text-2);text-align:center;padding:40px">请选择数据类型查看预览</div>
</div>
</div>
`;
}
function bindEvents() {
const typeSelect = document.getElementById('export-type');
const excelBtn = document.getElementById('export-excel');
const pdfBtn = document.getElementById('export-pdf');
// 类型改变时更新预览
if (typeSelect) {
typeSelect.addEventListener('change', updatePreview);
// 初始加载预览
updatePreview();
}
// Excel导出
if (excelBtn) {
excelBtn.addEventListener('click', async () => {
const type = typeSelect.value;
await exportData(type, 'excel');
});
}
// PDF导出
if (pdfBtn) {
pdfBtn.addEventListener('click', async () => {
const type = typeSelect.value;
await exportData(type, 'pdf');
});
}
}
async function updatePreview() {
const type = document.getElementById('export-type').value;
const preview = document.getElementById('export-preview');
try {
preview.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-2)">加载中...</div>';
let data;
switch(type) {
case 'stats':
data = await API.listStats();
renderStatsPreview(data.list || []);
break;
case 'mac':
data = await API.listMac();
renderMacPreview(data.list || []);
break;
case 'repairs':
data = await API.listRepairs();
renderRepairsPreview(data.list || []);
break;
case 'defects':
data = await API.listDefects();
renderDefectsPreview(data.list || []);
break;
case 'shipments':
data = await API.listShipments();
renderShipmentsPreview(data.list || []);
break;
case 'devices':
data = await API.devices();
renderDevicesPreview(data.list || []);
break;
case 'environment':
data = await API.environment();
renderEnvironmentPreview(data);
break;
case 'personnel':
data = await API.personnel();
renderPersonnelPreview(data.list || []);
break;
case 'qa':
data = await API.qa();
renderQaPreview(data.list || []);
break;
case 'production':
data = await API.production();
renderProductionPreview(data.list || []);
break;
}
} catch (e) {
preview.innerHTML = '<div style="text-align:center;padding:20px;color:var(--danger)">加载失败</div>';
}
}
function renderStatsPreview(list) {
const preview = document.getElementById('export-preview');
if (list.length === 0) {
preview.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-2)">暂无数据</div>';
return;
}
preview.innerHTML = `
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:var(--surface);border-bottom:2px solid var(--border)">
<th style="padding:8px;text-align:left">直通良品数</th>
<th style="padding:8px;text-align:left">良品数</th>
<th style="padding:8px;text-align:left">不良品数</th>
<th style="padding:8px;text-align:left">时间</th>
</tr>
</thead>
<tbody>
${list.slice(0, 50).map(item => `
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:8px">${item.fpy_good || 0}</td>
<td style="padding:8px">${item.good}</td>
<td style="padding:8px">${item.bad}</td>
<td style="padding:8px;font-size:12px;color:var(--text-2)">${new Date(item.ts).toLocaleString('zh-CN')}</td>
</tr>
`).join('')}
</tbody>
</table>
${list.length > 50 ? `<div style="text-align:center;padding:12px;color:var(--text-2);font-size:13px">仅显示前50条导出时将包含全部 ${list.length} 条数据</div>` : ''}
`;
}
function renderMacPreview(list) {
const preview = document.getElementById('export-preview');
if (list.length === 0) {
preview.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-2)">暂无数据</div>';
return;
}
preview.innerHTML = `
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:var(--surface);border-bottom:2px solid var(--border)">
<th style="padding:8px;text-align:left">MAC地址</th>
<th style="padding:8px;text-align:left">批次号</th>
<th style="padding:8px;text-align:left">时间</th>
</tr>
</thead>
<tbody>
${list.slice(0, 50).map(item => `
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:8px;font-family:monospace">${item.mac}</td>
<td style="padding:8px">${item.batch}</td>
<td style="padding:8px;font-size:12px;color:var(--text-2)">${new Date(item.ts).toLocaleString('zh-CN')}</td>
</tr>
`).join('')}
</tbody>
</table>
${list.length > 50 ? `<div style="text-align:center;padding:12px;color:var(--text-2);font-size:13px">仅显示前50条导出时将包含全部 ${list.length} 条数据</div>` : ''}
`;
}
function renderRepairsPreview(list) {
const preview = document.getElementById('export-preview');
if (list.length === 0) {
preview.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-2)">暂无数据</div>';
return;
}
preview.innerHTML = `
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:var(--surface);border-bottom:2px solid var(--border)">
<th style="padding:8px;text-align:left">返修数量</th>
<th style="padding:8px;text-align:left">备注</th>
<th style="padding:8px;text-align:left">时间</th>
</tr>
</thead>
<tbody>
${list.slice(0, 50).map(item => `
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:8px">${item.qty}</td>
<td style="padding:8px">${item.note || '无'}</td>
<td style="padding:8px;font-size:12px;color:var(--text-2)">${new Date(item.ts).toLocaleString('zh-CN')}</td>
</tr>
`).join('')}
</tbody>
</table>
${list.length > 50 ? `<div style="text-align:center;padding:12px;color:var(--text-2);font-size:13px">仅显示前50条导出时将包含全部 ${list.length} 条数据</div>` : ''}
`;
}
function renderDefectsPreview(list) {
const preview = document.getElementById('export-preview');
if (list.length === 0) {
preview.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-2)">暂无数据</div>';
return;
}
preview.innerHTML = `
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:var(--surface);border-bottom:2px solid var(--border)">
<th style="padding:8px;text-align:left">MAC地址</th>
<th style="padding:8px;text-align:left">批次号</th>
<th style="padding:8px;text-align:left">时间</th>
</tr>
</thead>
<tbody>
${list.slice(0, 50).map(item => `
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:8px;font-family:monospace">${item.mac}</td>
<td style="padding:8px">${item.batch}</td>
<td style="padding:8px;font-size:12px;color:var(--text-2)">${new Date(item.ts).toLocaleString('zh-CN')}</td>
</tr>
`).join('')}
</tbody>
</table>
${list.length > 50 ? `<div style="text-align:center;padding:12px;color:var(--text-2);font-size:13px">仅显示前50条导出时将包含全部 ${list.length} 条数据</div>` : ''}
`;
}
function renderShipmentsPreview(list) {
const preview = document.getElementById('export-preview');
if (list.length === 0) {
preview.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-2)">暂无数据</div>';
return;
}
preview.innerHTML = `
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:var(--surface);border-bottom:2px solid var(--border)">
<th style="padding:8px;text-align:left">日期</th>
<th style="padding:8px;text-align:left">数量</th>
<th style="padding:8px;text-align:left">收货方</th>
<th style="padding:8px;text-align:left">时间</th>
</tr>
</thead>
<tbody>
${list.slice(0, 50).map(item => `
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:8px">${item.date}</td>
<td style="padding:8px">${item.qty}</td>
<td style="padding:8px">${item.receiver}</td>
<td style="padding:8px;font-size:12px;color:var(--text-2)">${new Date(item.ts).toLocaleString('zh-CN')}</td>
</tr>
`).join('')}
</tbody>
</table>
${list.length > 50 ? `<div style="text-align:center;padding:12px;color:var(--text-2);font-size:13px">仅显示前50条导出时将包含全部 ${list.length} 条数据</div>` : ''}
`;
}
function renderDevicesPreview(list) {
const preview = document.getElementById('export-preview');
if (list.length === 0) {
preview.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-2)">暂无数据</div>';
return;
}
preview.innerHTML = `
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:var(--surface);border-bottom:2px solid var(--border)">
<th style="padding:8px;text-align:left">设备名称</th>
<th style="padding:8px;text-align:left">状态</th>
</tr>
</thead>
<tbody>
${list.map(item => `
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:8px">${item.name}</td>
<td style="padding:8px">${item.status}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
function renderEnvironmentPreview(data) {
const preview = document.getElementById('export-preview');
preview.innerHTML = `
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:var(--surface);border-bottom:2px solid var(--border)">
<th style="padding:8px;text-align:left">参数</th>
<th style="padding:8px;text-align:left">值</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:8px">温度</td>
<td style="padding:8px">${data.temp || '—'}</td>
</tr>
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:8px">湿度</td>
<td style="padding:8px">${data.hum || '—'}</td>
</tr>
</tbody>
</table>
`;
}
function renderPersonnelPreview(list) {
const preview = document.getElementById('export-preview');
if (list.length === 0) {
preview.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-2)">暂无数据</div>';
return;
}
preview.innerHTML = `
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:var(--surface);border-bottom:2px solid var(--border)">
<th style="padding:8px;text-align:left">姓名</th>
<th style="padding:8px;text-align:left">角色</th>
</tr>
</thead>
<tbody>
${list.map(item => `
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:8px">${item.name}</td>
<td style="padding:8px">${item.role || '—'}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
function renderQaPreview(list) {
const preview = document.getElementById('export-preview');
if (list.length === 0) {
preview.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-2)">暂无数据</div>';
return;
}
preview.innerHTML = `
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:var(--surface);border-bottom:2px solid var(--border)">
<th style="padding:8px;text-align:left">标题</th>
<th style="padding:8px;text-align:left">日期</th>
</tr>
</thead>
<tbody>
${list.map(item => `
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:8px">${item.title}</td>
<td style="padding:8px">${item.date}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
function renderProductionPreview(list) {
const preview = document.getElementById('export-preview');
if (list.length === 0) {
preview.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-2)">暂无数据</div>';
return;
}
preview.innerHTML = `
<table style="width:100%;border-collapse:collapse">
<thead>
<tr style="background:var(--surface);border-bottom:2px solid var(--border)">
<th style="padding:8px;text-align:left">批次</th>
<th style="padding:8px;text-align:left">时长</th>
</tr>
</thead>
<tbody>
${list.map(item => `
<tr style="border-bottom:1px solid var(--border)">
<td style="padding:8px">${item.batch}</td>
<td style="padding:8px">${item.duration}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
async function exportData(type, format) {
const statusEl = document.getElementById('export-status');
try {
statusEl.style.display = 'block';
statusEl.style.background = 'rgba(79,140,255,0.1)';
statusEl.style.color = 'var(--primary)';
statusEl.textContent = `正在导出${format.toUpperCase()}...`;
// 调用导出API并下载文件
const endpoint = format === 'excel' ? '/api/export/excel' : '/api/export/pdf';
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify({ type })
});
if (!response.ok) {
throw new Error('导出失败');
}
// 获取文件名
const contentDisposition = response.headers.get('Content-Disposition');
let filename = `export_${type}_${Date.now()}.${format === 'excel' ? 'xlsx' : 'pdf'}`;
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
if (filenameMatch && filenameMatch[1]) {
filename = filenameMatch[1].replace(/['"]/g, '');
}
}
// 下载文件
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
statusEl.style.background = 'rgba(34,197,94,0.1)';
statusEl.style.color = 'var(--success)';
statusEl.textContent = `${format.toUpperCase()}导出成功!文件已下载`;
setTimeout(() => {
statusEl.style.display = 'none';
}, 3000);
} catch (e) {
statusEl.style.background = 'rgba(239,68,68,0.1)';
statusEl.style.color = 'var(--danger)';
statusEl.textContent = `✗ 导出失败: ${e.message}`;
}
}
Router.register('/export', render);
})();