|
|
|
@ -2326,7 +2326,7 @@ async function trHandleFileImport(e) {
|
|
|
|
const res = await fetch(`${TR_API_BASE}/import`, { method: 'POST', body: formData });
|
|
|
|
const res = await fetch(`${TR_API_BASE}/import`, { method: 'POST', body: formData });
|
|
|
|
const json = await res.json();
|
|
|
|
const json = await res.json();
|
|
|
|
if (json.success) {
|
|
|
|
if (json.success) {
|
|
|
|
alert(json.message);
|
|
|
|
trShowImportResult(json.message, json.skipped_details || []);
|
|
|
|
trLoadAllData();
|
|
|
|
trLoadAllData();
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
alert('导入失败: ' + (json.message || '未知错误'));
|
|
|
|
alert('导入失败: ' + (json.message || '未知错误'));
|
|
|
|
@ -2339,6 +2339,53 @@ async function trHandleFileImport(e) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 显示导入结果(含去重详情)
|
|
|
|
|
|
|
|
function trShowImportResult(message, skippedDetails) {
|
|
|
|
|
|
|
|
let html = `<div style="padding:16px;max-width:500px;">
|
|
|
|
|
|
|
|
<div style="font-size:14px;margin-bottom:12px;">${message}</div>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (skippedDetails && skippedDetails.length > 0) {
|
|
|
|
|
|
|
|
html += `<div style="margin-top:12px;border-top:1px solid #eee;padding-top:12px;">
|
|
|
|
|
|
|
|
<div style="font-weight:600;font-size:13px;margin-bottom:8px;color:#666;">
|
|
|
|
|
|
|
|
被过滤的重复记录(${skippedDetails.length} 条):
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style="max-height:200px;overflow-y:auto;font-size:12px;background:#f9f9f9;border-radius:4px;padding:8px;">`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 按类型分组显示
|
|
|
|
|
|
|
|
const grouped = {};
|
|
|
|
|
|
|
|
skippedDetails.forEach(d => {
|
|
|
|
|
|
|
|
const key = d.type || '未知';
|
|
|
|
|
|
|
|
if (!grouped[key]) grouped[key] = [];
|
|
|
|
|
|
|
|
grouped[key].push(d);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const [type, items] of Object.entries(grouped)) {
|
|
|
|
|
|
|
|
html += `<div style="margin-bottom:6px;"><strong>${type}</strong>(${items.length} 条):</div>`;
|
|
|
|
|
|
|
|
items.slice(0, 10).forEach(d => {
|
|
|
|
|
|
|
|
html += `<div style="padding:2px 0;color:#555;">• ${d.variety} ${d.date} ${d.time} @ ${d.price}</div>`;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
if (items.length > 10) {
|
|
|
|
|
|
|
|
html += `<div style="color:#999;">... 还有 ${items.length - 10} 条</div>`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
html += `</div></div>`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
html += `</div>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 使用自定义弹窗
|
|
|
|
|
|
|
|
const modal = document.createElement('div');
|
|
|
|
|
|
|
|
modal.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:10000;display:flex;align-items:center;justify-content:center;';
|
|
|
|
|
|
|
|
modal.innerHTML = `<div style="background:white;border-radius:8px;max-width:600px;max-height:80vh;overflow-y:auto;">
|
|
|
|
|
|
|
|
${html}
|
|
|
|
|
|
|
|
<div style="padding:12px 16px;border-top:1px solid #eee;text-align:right;">
|
|
|
|
|
|
|
|
<button onclick="this.closest('div[style*=fixed]').remove()" style="padding:6px 16px;background:#007aff;color:white;border:none;border-radius:4px;cursor:pointer;">确定</button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>`;
|
|
|
|
|
|
|
|
document.body.appendChild(modal);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function trHandleBatchImport(e) {
|
|
|
|
async function trHandleBatchImport(e) {
|
|
|
|
const files = e.target.files;
|
|
|
|
const files = e.target.files;
|
|
|
|
if (!files || files.length === 0) return;
|
|
|
|
if (!files || files.length === 0) return;
|
|
|
|
@ -2375,9 +2422,27 @@ function trShowBatchImportResult(json) {
|
|
|
|
summaryEl.textContent = json.message;
|
|
|
|
summaryEl.textContent = json.message;
|
|
|
|
detailsEl.innerHTML = data.details.map(d => {
|
|
|
|
detailsEl.innerHTML = data.details.map(d => {
|
|
|
|
let icon = d.success ? (d.new_count > 0 ? '✅' : '⚠️') : '❌';
|
|
|
|
let icon = d.success ? (d.new_count > 0 ? '✅' : '⚠️') : '❌';
|
|
|
|
|
|
|
|
let skippedHtml = '';
|
|
|
|
|
|
|
|
if (d.skipped_details && d.skipped_details.length > 0) {
|
|
|
|
|
|
|
|
const grouped = {};
|
|
|
|
|
|
|
|
d.skipped_details.forEach(item => {
|
|
|
|
|
|
|
|
const key = item.type || '未知';
|
|
|
|
|
|
|
|
if (!grouped[key]) grouped[key] = [];
|
|
|
|
|
|
|
|
grouped[key].push(item);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
skippedHtml = `<div style="margin-top:6px;font-size:11px;color:#888;max-height:80px;overflow-y:auto;">`;
|
|
|
|
|
|
|
|
for (const [type, items] of Object.entries(grouped)) {
|
|
|
|
|
|
|
|
skippedHtml += `<div><strong>${type}</strong> ${items.length}条: `;
|
|
|
|
|
|
|
|
skippedHtml += items.slice(0, 5).map(item => `${item.variety}@${item.price}`).join(', ');
|
|
|
|
|
|
|
|
if (items.length > 5) skippedHtml += ` ...`;
|
|
|
|
|
|
|
|
skippedHtml += `</div>`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
skippedHtml += `</div>`;
|
|
|
|
|
|
|
|
}
|
|
|
|
return `<div style="padding:10px 12px;margin-bottom:8px;border-radius:6px;background:#F5F5F7;border-left:3px solid ${d.success ? '#34C759' : '#FF3B30'};">
|
|
|
|
return `<div style="padding:10px 12px;margin-bottom:8px;border-radius:6px;background:#F5F5F7;border-left:3px solid ${d.success ? '#34C759' : '#FF3B30'};">
|
|
|
|
<div style="font-weight:600;font-size:13px;">${icon} ${d.filename}</div>
|
|
|
|
<div style="font-weight:600;font-size:13px;">${icon} ${d.filename}</div>
|
|
|
|
<div style="font-size:12px;color:var(--text-tertiary);margin-top:4px;">${d.message}</div>
|
|
|
|
<div style="font-size:12px;color:var(--text-tertiary);margin-top:4px;">${d.message}</div>
|
|
|
|
|
|
|
|
${skippedHtml}
|
|
|
|
</div>`;
|
|
|
|
</div>`;
|
|
|
|
}).join('');
|
|
|
|
}).join('');
|
|
|
|
modal.classList.add('show');
|
|
|
|
modal.classList.add('show');
|
|
|
|
|