(() => {
const categories = [
{ id: 'cost', name: '自家成本' },
{ id: 'retail', name: '售价价格' },
{ id: 'dealer', name: '经销商价格' }
];
const token = () => window.__manyiToken || '';
const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (character) => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
}[character]));
const api = (url, options = {}) => fetch(url, {
...options,
headers: { Authorization: `Bearer ${token()}`, ...(options.headers || {}) }
}).then(async (response) => {
const body = await response.json();
if (!response.ok) throw new Error(body.error || '操作失败');
return body;
});
const init = () => {
const card = document.querySelector('#files .card');
if (!card || document.getElementById('priceLibrary')) return;
const isAdmin = window.__manyiUser?.role === '管理员';
const visibleCategories = isAdmin ? categories : categories.filter((item) => item.id === 'retail');
const box = document.createElement('section');
box.id = 'priceLibrary';
box.innerHTML = `
价格资料
各类价格独立保存和索引,敏感价格仅管理员可见。
${visibleCategories.map((item, index) => ``).join('')}
`;
card.appendChild(box);
const priceStyle = document.createElement('style');
priceStyle.textContent = '#priceLibrary .price-files{margin-top:12px}#priceLibrary .price-file{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 0;border-bottom:1px solid #edf1f5}#priceLibrary .price-file>span{min-width:0;word-break:break-word}#priceLibrary .price-delete{width:32px;height:32px;flex:none;padding:0;font-size:20px;line-height:1}.order-query-files{margin-top:26px;padding-top:18px;border-top:1px solid #e1e7ee}.order-query-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin:12px 0}.order-query-toolbar .input{margin:0;max-width:360px}.order-query-upload-status{font-size:12px}.order-query-folder{margin:16px 0}.order-query-folder summary{display:flex;justify-content:space-between;align-items:center;cursor:pointer;padding:8px 0;color:#506275;font-weight:700;border-bottom:1px solid #dfe7ee}.order-query-folder summary small{color:#8794a1;font-weight:400}.order-query-file{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 0;border-bottom:1px solid #edf1f5}.order-query-file small{display:block;color:#788696;margin-top:3px}.order-query-file a{color:#2f78df;text-decoration:none;white-space:nowrap}.order-query-file-actions{display:flex;align-items:center;gap:10px;white-space:nowrap}.order-query-delete{border:0;background:transparent;color:#b94343;font-size:20px;line-height:1;cursor:pointer;padding:0}';
document.head.appendChild(priceStyle);
const panel = box.querySelector('#pricePanel');
const orderFileList = box.querySelector('.order-query-file-list');
const loadOrderFiles = async (query = '') => {
try {
const result = await api(`/api/order-query/files?q=${encodeURIComponent(query)}`);
const folders = result.folders || [{ date: null, items: result.items || [] }];
const renderFile = (file) => `${escapeHtml(file.fileName)}${file.records} 条记录 · ${escapeHtml(file.sheetSummary || file.sheets.join('、'))} · ${formatFileSize(file.size)} · ${new Date(file.modifiedAt).toLocaleString()}打开${isAdmin ? `` : ''} `;
orderFileList.innerHTML = result.items.length ? folders.map((folder, index) => `${escapeHtml(folder.date || '日期未记录')}${folder.items.length} 个文件
${folder.items.map(renderFile).join('')} `).join('') : '暂无单号查询员保存的文件
';
orderFileList.querySelectorAll('[data-order-delete]').forEach((button) => button.onclick = async () => {
if (!confirm(`确定删除“${button.dataset.orderFileName}”?本地文件和查询索引会同步删除。`)) return;
try { await api(`/api/order-query/files/${button.dataset.orderDelete}`, { method: 'DELETE' }); await loadOrderFiles(box.querySelector('.order-query-search').value.trim()); }
catch (error) { alert(error.message); }
});
} catch (error) { orderFileList.innerHTML = `${escapeHtml(error.message)}
`; }
};
const formatFileSize = (bytes) => Number(bytes || 0) < 1048576 ? `${(Number(bytes || 0) / 1024).toFixed(1)} KB` : `${(Number(bytes || 0) / 1048576).toFixed(1)} MB`;
let active = visibleCategories[0]?.id || 'retail';
let uploadStatus = '';
const defaultStatus = '可一次选择多个文件,逐个建立价格索引';
const render = async () => {
const category = visibleCategories.find((item) => item.id === active);
const data = await api(`/api/price-files?category=${active}`);
const adminControls = isAdmin
? `${uploadStatus || defaultStatus}
`
: '';
const files = data.items.map((file) => {
const summary = file.status === 'failed'
? `录入失败${file.error ? `:${escapeHtml(file.error)}` : ''}`
: `${file.recordCount} 条记录`;
return `${escapeHtml(file.fileName)} · ${summary}${isAdmin ? `` : ''}
`;
}).join('');
panel.innerHTML = `${escapeHtml(category.name)}
${adminControls}`;
const input = panel.querySelector('.price-input');
if (input) input.onchange = async (event) => {
const filesToUpload = Array.from(event.target.files || []);
if (!filesToUpload.length) return;
const results = [];
const status = panel.querySelector('.price-status');
for (const file of filesToUpload) {
uploadStatus = `正在处理 ${escapeHtml(file.name)}…`;
if (status) status.innerHTML = uploadStatus;
try {
const response = await fetch('/api/price-files', {
method: 'POST',
headers: {
Authorization: `Bearer ${token()}`,
'X-Price-Category': active,
'X-File-Name-Encoded': encodeURIComponent(file.name)
},
body: await file.arrayBuffer()
});
const result = await response.json();
if (!response.ok) throw new Error(result.error || '价格资料录入失败');
results.push(`✓ ${escapeHtml(file.name)}:${result.file.recordCount} 条`);
} catch (error) {
results.push(`✗ ${escapeHtml(file.name)}:${escapeHtml(error.message)}`);
}
}
uploadStatus = results.join('
');
await render();
event.target.value = '';
};
panel.querySelectorAll('.price-delete').forEach((button) => {
button.onclick = async () => {
const file = data.items.find((item) => item.id === button.dataset.id);
if (!file || !confirm(`确定删除“${category.name}”中的 ${file.fileName}?本地文件也会同步删除。`)) return;
try {
await api(`/api/price-files/${file.id}?category=${active}`, { method: 'DELETE' });
await render();
} catch (error) {
alert(error.message);
}
};
});
};
box.querySelectorAll('.price-tab').forEach((button) => button.onclick = async () => {
active = button.dataset.category;
box.querySelectorAll('.price-tab').forEach((item) => item.classList.toggle('secondary', item !== button));
uploadStatus = '';
await render();
});
box.querySelector('.order-query-search-button').onclick = () => loadOrderFiles(box.querySelector('.order-query-search').value.trim());
box.querySelector('.order-query-search').onkeydown = (event) => { if (event.key === 'Enter') loadOrderFiles(event.target.value.trim()); };
const orderInput = box.querySelector('.order-query-input');
if (orderInput) orderInput.onchange = async (event) => {
const files = Array.from(event.target.files || []);
if (!files.length) return;
const status = box.querySelector('.order-query-upload-status'); const results = [];
for (const file of files) {
if (!/\.(xls|xlsx|csv|tsv)$/i.test(file.name)) { results.push(`✗ ${escapeHtml(file.name)}:不支持的表格格式`); continue; }
if (status) status.textContent = `正在归档 ${file.name}…`;
try {
const response = await fetch('/api/order-query/files', { method: 'POST', headers: { Authorization: `Bearer ${token()}`, 'X-File-Name-Encoded': encodeURIComponent(file.name) }, body: await file.arrayBuffer() });
const result = await response.json(); if (!response.ok) throw new Error(result.error || '表格录入失败');
results.push(`✓ ${escapeHtml(file.name)}:已归档到 ${escapeHtml(result.day)}`);
} catch (error) { results.push(`✗ ${escapeHtml(file.name)}:${escapeHtml(error.message)}`); }
}
if (status) status.innerHTML = results.join('
');
event.target.value = '';
await loadOrderFiles(box.querySelector('.order-query-search').value.trim());
};
render().catch((error) => { panel.textContent = error.message; });
loadOrderFiles();
};
document.querySelector('[data-view="files"]')?.addEventListener('click', () => setTimeout(init, 80));
})();