ERP/frontend/js/components/shipments.js

434 lines
16 KiB
JavaScript
Raw Normal View History

Router.register('/upload/shipments', async () => {
setTimeout(() => {
const manualStatus = document.getElementById('ship-manual-status');
2026-05-11 05:41:05 +00:00
const defaultPlatformNameMap = {pdd: '拼多多', yt: '圆通', tx: '兔喜', mt: '美团', drf: '大润发', std: '标准版'};
const renderPlatformOptions = (selectEl, list, selectedValue = '') => {
if (!selectEl) return;
const optsHtml = [
'<option value="">请选择机种</option>',
...(list || []).map(o => `<option value="${o.value}">${o.label}</option>`)
].join('');
selectEl.innerHTML = optsHtml;
if (selectedValue) selectEl.value = selectedValue;
};
const loadPlatforms = async (selectedManual = '', selectedImport = '') => {
try {
const res = await API.listShipmentPlatforms();
const list = (res && res.list) ? res.list : [];
renderPlatformOptions(document.getElementById('ship-manual-platform'), list, selectedManual);
renderPlatformOptions(document.getElementById('ship-platform'), list, selectedImport);
} catch (e) {
// 失败时不阻塞页面,保持当前 options可能是旧的静态内容
console.warn('加载机种列表失败:', e);
}
};
const showManageModal = async () => {
let list = [];
try {
const res = await API.listShipmentPlatforms();
list = (res && res.list) ? res.list : [];
} catch (e) {
API.toast('加载机种失败');
return;
}
const custom = list.filter(x => !x.is_default);
const modal = document.createElement('div');
modal.id = 'ship-platform-manage-modal';
modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:1000;display:flex;align-items:center;justify-content:center;padding:20px';
modal.innerHTML = `
<div style="background:var(--surface);border-radius:12px;width:90%;max-width:520px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 10px 30px rgba(0,0,0,0.2)">
<div style="padding:14px 18px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center">
<div style="font-weight:700">机种管理仅自定义可删除</div>
<button id="ship-platform-manage-close" class="btn btn-secondary" style="padding:4px 10px">关闭</button>
</div>
<div style="padding:16px;overflow:auto;flex:1">
${custom.length ? `
<div style="font-size:12px;color:var(--text-2);margin-bottom:10px"> ${custom.length} 个自定义机种</div>
<ul class="list" style="margin:0">
${custom.map(x => `
<li style="display:flex;justify-content:space-between;align-items:center;gap:10px">
<div style="min-width:0">
<div style="font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${x.label}</div>
<div style="font-size:11px;color:var(--text-2);font-family:monospace">${x.value}</div>
</div>
<button class="btn btn-secondary ship-platform-delete" data-code="${x.value}" style="background:var(--danger);color:white">删除</button>
</li>
`).join('')}
</ul>
` : `
<div style="text-align:center;padding:30px 0;color:var(--text-2)">暂无自定义机种</div>
`}
</div>
<div style="padding:12px 16px;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:10px">
<button class="btn" id="ship-platform-manage-add">新增机种</button>
</div>
</div>
`;
document.body.appendChild(modal);
const close = () => modal.remove();
modal.addEventListener('click', (e) => { if (e.target === modal) close(); });
modal.querySelector('#ship-platform-manage-close')?.addEventListener('click', close);
modal.querySelector('#ship-platform-manage-add')?.addEventListener('click', async () => {
const name = (prompt('请输入自定义机种名称') || '').trim();
if (!name) return;
try {
const added = await API.addShipmentPlatform(name);
if (added && added.ok) {
API.toast('已添加机种:' + (added.label || name));
close();
await loadPlatforms(added.value, added.value);
}
} catch (e) {}
});
modal.querySelectorAll('.ship-platform-delete').forEach(btn => {
btn.addEventListener('click', async () => {
const code = btn.dataset.code;
const label = btn.closest('li')?.querySelector('div div')?.textContent || code;
if (!confirm(`确定删除自定义机种:${label}\n\n删除后下拉将不再显示该机种。`)) return;
try {
const r = await API.deleteShipmentPlatform(code);
if (r && r.ok) {
API.toast('删除成功');
close();
await loadPlatforms();
}
} catch (e) {}
});
});
};
// 设置默认日期为今天
const dateInput = document.getElementById('ship-date');
if (dateInput && !dateInput.value) {
const today = new Date().toISOString().split('T')[0];
dateInput.value = today;
}
2026-05-11 05:41:05 +00:00
// 动态加载机种下拉,并支持自定义新增
loadPlatforms();
document.getElementById('ship-manage-platforms')?.addEventListener('click', showManageModal);
// 手动录入提交
const btn = document.getElementById('ship-upload');
btn?.addEventListener('click', async () => {
const date = document.getElementById('ship-date').value;
const qty = parseInt(document.getElementById('ship-qty').value || '0', 10);
const to = document.getElementById('ship-to').value;
const platform = document.getElementById('ship-manual-platform').value;
const boxNo = document.getElementById('ship-box-no').value.trim();
// 验证必填字段
if (!date) {
manualStatus.textContent = '✗ 请选择发货日期';
manualStatus.className = 'error';
return;
}
if (!platform) {
manualStatus.textContent = '✗ 请选择机种类型';
manualStatus.className = 'error';
return;
}
if (!to) {
manualStatus.textContent = '✗ 请输入接收方';
manualStatus.className = 'error';
return;
}
if (qty <= 0) {
manualStatus.textContent = '✗ 数量必须大于0';
manualStatus.className = 'error';
return;
}
try {
manualStatus.textContent = '提交中...';
manualStatus.className = '';
const payload = { date, qty, to, platform };
if (boxNo) {
payload.box_no = boxNo;
}
await API.uploadShipments(payload);
2026-05-11 05:41:05 +00:00
const platformName = defaultPlatformNameMap[platform] || platform;
manualStatus.textContent = `✓ 录入成功!机种:${platformName},数量:${qty}`;
manualStatus.className = 'success';
// 清空表单(保留日期和机种)
document.getElementById('ship-qty').value = '';
document.getElementById('ship-to').value = '';
document.getElementById('ship-box-no').value = '';
} catch(e) {
manualStatus.textContent = '✗ 录入失败:' + (e.message || '未知错误');
manualStatus.className = 'error';
}
});
const fileInput = document.getElementById('ship-file');
const validateBtn = document.getElementById('ship-validate');
const uploadFileBtn = document.getElementById('ship-upload-file');
const fileStatus = document.getElementById('ship-file-status');
fileInput?.addEventListener('change', () => {
fileStatus.textContent = '';
fileStatus.className = '';
});
validateBtn?.addEventListener('click', async () => {
const file = fileInput?.files?.[0];
if (!file) {
fileStatus.textContent = '请先选择文件';
fileStatus.className = 'error';
return;
}
const formData = new FormData();
formData.append('file', file);
try {
const res = await fetch('/api/validate/shipments-file', {
method: 'POST',
body: formData,
credentials: 'include'
});
if (!res.ok) {
const text = await res.text();
throw new Error(`HTTP ${res.status}: ${text}`);
}
const data = await res.json();
if (data.valid) {
fileStatus.textContent = '✓ ' + data.message;
fileStatus.className = 'success';
} else {
fileStatus.textContent = '✗ ' + data.message;
fileStatus.className = 'error';
}
} catch (e) {
fileStatus.textContent = '验证失败:' + e.message;
fileStatus.className = 'error';
}
});
uploadFileBtn?.addEventListener('click', async () => {
const file = fileInput?.files?.[0];
const platform = document.getElementById('ship-platform')?.value;
if (!platform) {
fileStatus.textContent = '✗ 请选择机种类型';
fileStatus.className = 'error';
return;
}
if (!file) {
fileStatus.textContent = '✗ 请先选择文件';
fileStatus.className = 'error';
return;
}
const formData = new FormData();
formData.append('file', file);
formData.append('platform', platform);
try {
fileStatus.textContent = '上传中...';
fileStatus.className = '';
const res = await fetch('/api/upload/shipments-file', {
method: 'POST',
body: formData,
credentials: 'include'
});
if (!res.ok) {
const text = await res.text();
throw new Error(`HTTP ${res.status}: ${text}`);
}
const data = await res.json();
if (data.ok) {
2026-05-11 05:41:05 +00:00
const platformName = defaultPlatformNameMap[platform] || platform;
fileStatus.textContent = `✓ 上传成功!机种:${platformName},共导入${data.count}个箱次,${data.total_qty}个SN`;
fileStatus.className = 'success';
fileInput.value = '';
document.getElementById('ship-platform').value = '';
} else {
fileStatus.textContent = '✗ ' + (data.error || '上传失败');
fileStatus.className = 'error';
}
} catch (e) {
fileStatus.textContent = '上传失败:' + e.message;
fileStatus.className = 'error';
}
});
}, 0);
2026-03-26 02:16:19 +00:00
return `
<style>
#shipments-page {
padding: 20px;
background: var(--bg);
}
#shipments-page .page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
#shipments-page h1 {
margin: 0;
font-size: 24px;
color: var(--text);
}
#shipments-page .content-area {
background: var(--surface);
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
#shipments-page .section {
padding: 20px;
}
#shipments-page .section + .section {
border-top: 1px solid var(--border);
}
#shipments-page .section-title {
font-weight: 600;
font-size: 16px;
margin-bottom: 12px;
color: var(--text);
}
#shipments-page .section-desc {
font-size: 13px;
color: var(--text-2);
margin-bottom: 16px;
}
#shipments-page .format-requirements {
background: rgba(79,140,255,0.08);
border: 1px solid rgba(79,140,255,0.2);
border-radius: 8px;
padding: 12px;
margin-bottom: 16px;
font-size: 13px;
line-height: 1.6;
}
#shipments-page .row {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 16px;
}
@media (max-width: 768px) {
#shipments-page .row {
grid-template-columns: 1fr;
}
}
</style>
2026-03-26 02:16:19 +00:00
<div id="shipments-page">
<div class="page-header">
<h1>发货记录</h1>
2026-05-11 05:41:05 +00:00
<div class="page-actions">
<button class="btn btn-secondary" id="ship-manage-platforms" type="button">机种管理</button>
</div>
</div>
2026-03-26 02:16:19 +00:00
<div class="content-area">
<div class="section">
<div class="section-title">手动录入</div>
<div class="section-desc">用于快速录入发货汇总信息不含详细SN</div>
<div class="row">
<div class="field">
<label>发货日期 <span style="color:var(--danger)">*</span></label>
<input id="ship-date" type="date" class="input" />
</div>
<div class="field">
<label>机种类型 <span style="color:var(--danger)">*</span></label>
<select id="ship-manual-platform" class="input">
<option value="">请选择机种</option>
<option value="pdd">拼多多</option>
<option value="yt">圆通</option>
<option value="tx">兔喜</option>
<option value="mt">美团</option>
<option value="drf">大润发</option>
<option value="std">标准版</option>
</select>
</div>
</div>
<div class="row">
<div class="field">
<label>接收方 <span style="color:var(--danger)">*</span></label>
<input id="ship-to" class="input" placeholder="客户名称" />
</div>
<div class="field">
<label>数量 <span style="color:var(--danger)">*</span></label>
<input id="ship-qty" type="number" min="1" class="input" placeholder="发货数量" />
</div>
</div>
<div class="field" style="margin-bottom:16px">
<label>箱号可选</label>
<input id="ship-box-no" class="input" placeholder="例如BOX001" />
</div>
<div id="ship-manual-status" style="margin:8px 0;font-size:13px"></div>
<div class="actions">
<button class="btn btn-primary" id="ship-upload">提交录入</button>
</div>
</div>
2026-03-26 02:16:19 +00:00
<div class="section">
<div class="section-title">详细记录批量导入</div>
<div class="format-requirements">
<div style="font-weight:600;margin-bottom:6px;color:var(--primary)">📋 文件格式要求</div>
<div style="color:var(--text)">
<div> 必需列出货日期箱号SN1SN2...SN20</div>
<div> 出货日期列支持合并单元格</div>
<div> 支持格式Excel (.xlsx, .xls) CSV</div>
</div>
</div>
<div class="field" style="margin-bottom:16px">
<label>机种类型 <span style="color:var(--danger)">*</span></label>
2026-03-26 02:16:19 +00:00
<select id="ship-platform" class="input">
<option value="">请选择机种</option>
<option value="pdd">拼多多</option>
<option value="yt">圆通</option>
<option value="tx">兔喜</option>
2025-12-15 01:38:42 +00:00
<option value="mt">美团</option>
<option value="drf">大润发</option>
<option value="std">标准版</option>
</select>
</div>
2026-03-26 02:16:19 +00:00
<div class="field" style="margin-bottom:16px">
<label>选择文件</label>
<input type="file" id="ship-file" accept=".xlsx,.xls,.csv" class="input" style="padding:6px" />
</div>
2026-03-26 02:16:19 +00:00
<div id="ship-file-status" style="margin:8px 0;font-size:13px"></div>
<div class="actions">
<button class="btn btn-secondary" id="ship-validate">验证文件</button>
<button class="btn btn-primary" id="ship-upload-file">导入数据</button>
</div>
</div>
</div>
</div>
2026-03-26 02:16:19 +00:00
`;
});