120 lines
7.5 KiB
HTML
120 lines
7.5 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="zh-CN">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>版本管理 - 智能排产系统</title>
|
||
|
|
<link rel="stylesheet" href="../css/common.css">
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div id="app">
|
||
|
|
<header class="header" id="header"></header>
|
||
|
|
<div class="body">
|
||
|
|
<aside class="sidebar" id="sidebar"></aside>
|
||
|
|
<main class="main">
|
||
|
|
<h1 class="page-title">版本管理</h1>
|
||
|
|
<div class="toolbar">
|
||
|
|
<button class="btn" onclick="compareModal()">版本对比</button>
|
||
|
|
<button class="btn btn-danger" onclick="rollbackModal()">版本回滚</button>
|
||
|
|
</div>
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-body table-wrap">
|
||
|
|
<table id="versionTable">
|
||
|
|
<thead><tr><th>版本号</th><th>名称</th><th>触发方式</th><th>引擎</th><th>订单/工单</th><th>利用率</th><th>延迟</th><th>冲突</th><th>状态</th><th>操作</th></tr></thead>
|
||
|
|
<tbody></tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</main>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<script src="../js/data.js"></script>
|
||
|
|
<script src="../js/common.js"></script>
|
||
|
|
<script>
|
||
|
|
initPage('versions');
|
||
|
|
function render() {
|
||
|
|
const tbody = document.querySelector('#versionTable tbody');
|
||
|
|
const rows = apsData.scheduleVersions.slice().sort((a, b) => b.id - a.id);
|
||
|
|
tbody.innerHTML = rows.map(v => `
|
||
|
|
<tr>
|
||
|
|
<td>${v.versionNo}</td>
|
||
|
|
<td>${v.versionName}</td>
|
||
|
|
<td>${v.triggerType}</td>
|
||
|
|
<td>${statusBadge(v.engineType, { RULE:'default', CP:'info', GA:'warning', HYBRID:'primary' })}</td>
|
||
|
|
<td>${v.orderCount} / ${v.woCount}</td>
|
||
|
|
<td>${Math.round(v.avgUtilization * 100)}%</td>
|
||
|
|
<td>${Math.round(v.totalTardiness)}h</td>
|
||
|
|
<td>${v.conflictCount}</td>
|
||
|
|
<td>${statusBadge(v.status, { DRAFT:'default', PUBLISHED:'success', ARCHIVED:'warning' })}</td>
|
||
|
|
<td class="actions">
|
||
|
|
<button class="btn btn-sm" onclick="viewVersion(${v.id})">详情</button>
|
||
|
|
${v.status === 'DRAFT' ? `<button class="btn btn-sm btn-success" onclick="publish(${v.id})">发布</button>` : ''}
|
||
|
|
${v.status !== 'ARCHIVED' ? `<button class="btn btn-sm btn-danger" onclick="archive(${v.id})">归档</button>` : ''}
|
||
|
|
</td>
|
||
|
|
</tr>`).join('') || '<tr><td colspan="10" class="text-center text-muted">无版本</td></tr>';
|
||
|
|
}
|
||
|
|
function viewVersion(id) {
|
||
|
|
const v = findById(apsData.scheduleVersions, id);
|
||
|
|
openModal('版本详情', `
|
||
|
|
<div class="form-row"><div class="form-group"><label>版本号</label><input value="${v.versionNo}" readonly></div><div class="form-group"><label>创建时间</label><input value="${v.createdAt}" readonly></div></div>
|
||
|
|
<div class="form-row"><div class="form-group"><label>订单数</label><input value="${v.orderCount}" readonly></div><div class="form-group"><label>工单数</label><input value="${v.woCount}" readonly></div></div>
|
||
|
|
<div class="form-row"><div class="form-group"><label>平均利用率</label><input value="${Math.round(v.avgUtilization*100)}%" readonly></div><div class="form-group"><label>总延迟</label><input value="${Math.round(v.totalTardiness)}h" readonly></div></div>
|
||
|
|
<div class="form-row"><div class="form-group"><label>预估成本</label><input value="¥${Math.round(v.totalCost).toLocaleString()}" readonly></div><div class="form-group"><label>冲突数</label><input value="${v.conflictCount}" readonly></div></div>
|
||
|
|
<p><strong>备注:</strong>${v.note || '无'}</p>
|
||
|
|
`);
|
||
|
|
}
|
||
|
|
function publish(id) {
|
||
|
|
const v = findById(apsData.scheduleVersions, id);
|
||
|
|
v.status = 'PUBLISHED'; v.publishedAt = apsData.$utils.fmtDateTime(new Date());
|
||
|
|
apsData.productionOrders.filter(po => po.schedulingVersionId == id).forEach(po => po.status = 'CONFIRMED');
|
||
|
|
apsData.logs.unshift({ id: apsData.$utils.nextId('log'), type: 'INFO', category: 'SCHEDULE', operationType: 'PUBLISH', targetType: 'SCHEDULE_VERSION', targetId: id, action: '发布版本', description: `版本 ${v.versionNo} 已发布`, operator: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()) });
|
||
|
|
apsData.$utils.save(apsData); showToast('发布成功', v.versionNo); render();
|
||
|
|
}
|
||
|
|
function archive(id) {
|
||
|
|
const v = findById(apsData.scheduleVersions, id); v.status = 'ARCHIVED';
|
||
|
|
apsData.$utils.save(apsData); showToast('归档成功', v.versionNo); render();
|
||
|
|
}
|
||
|
|
function compareModal() {
|
||
|
|
const opts = apsData.scheduleVersions.map(v => `<option value="${v.id}">${v.versionNo}</option>`).join('');
|
||
|
|
openModal('版本对比', `
|
||
|
|
<div class="form-row"><div class="form-group"><label>基准版本</label><select id="baseV">${opts}</select></div><div class="form-group"><label>对比版本</label><select id="compareV">${opts}</select></div></div>
|
||
|
|
`, `
|
||
|
|
<button class="btn" onclick="closeModal()">取消</button>
|
||
|
|
<button class="btn btn-primary" onclick="doCompare()">对比</button>
|
||
|
|
`);
|
||
|
|
}
|
||
|
|
function doCompare() {
|
||
|
|
const base = findById(apsData.scheduleVersions, document.getElementById('baseV').value);
|
||
|
|
const comp = findById(apsData.scheduleVersions, document.getElementById('compareV').value);
|
||
|
|
if (!base || !comp) return;
|
||
|
|
openModal('版本对比结果', `
|
||
|
|
<table>
|
||
|
|
<thead><tr><th>指标</th><th>${base.versionNo}</th><th>${comp.versionNo}</th><th>变化</th></tr></thead>
|
||
|
|
<tbody>
|
||
|
|
<tr><td>工单数</td><td>${base.woCount}</td><td>${comp.woCount}</td><td>${comp.woCount - base.woCount}</td></tr>
|
||
|
|
<tr><td>平均利用率</td><td>${Math.round(base.avgUtilization*100)}%</td><td>${Math.round(comp.avgUtilization*100)}%</td><td>${Math.round((comp.avgUtilization-base.avgUtilization)*100)}%</td></tr>
|
||
|
|
<tr><td>总延迟</td><td>${Math.round(base.totalTardiness)}h</td><td>${Math.round(comp.totalTardiness)}h</td><td>${Math.round(comp.totalTardiness - base.totalTardiness)}h</td></tr>
|
||
|
|
<tr><td>冲突数</td><td>${base.conflictCount}</td><td>${comp.conflictCount}</td><td>${comp.conflictCount - base.conflictCount}</td></tr>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
`, '', true);
|
||
|
|
}
|
||
|
|
function rollbackModal() {
|
||
|
|
const opts = apsData.scheduleVersions.filter(v => v.status !== 'ARCHIVED').map(v => `<option value="${v.id}">${v.versionNo}</option>`).join('');
|
||
|
|
openModal('版本回滚', `<p>选择要回滚到的版本(当前未下发的工单将恢复为该版本状态)。</p><select id="rollbackV" class="w-full" style="padding:8px;border:1px solid #d1d5db;border-radius:4px">${opts}</select>`, `
|
||
|
|
<button class="btn" onclick="closeModal()">取消</button>
|
||
|
|
<button class="btn btn-danger" onclick="doRollback()">确认回滚</button>
|
||
|
|
`);
|
||
|
|
}
|
||
|
|
function doRollback() {
|
||
|
|
const v = findById(apsData.scheduleVersions, document.getElementById('rollbackV').value);
|
||
|
|
apsData.scheduleVersions.forEach(sv => { if (sv.id !== v.id && sv.status === 'PUBLISHED') sv.status = 'ARCHIVED'; });
|
||
|
|
v.status = 'PUBLISHED';
|
||
|
|
apsData.logs.unshift({ id: apsData.$utils.nextId('log'), type: 'WARNING', category: 'SCHEDULE', operationType: 'ROLLBACK', targetType: 'SCHEDULE_VERSION', targetId: v.id, action: '版本回滚', description: `已回滚至 ${v.versionNo}`, operator: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()) });
|
||
|
|
apsData.$utils.save(apsData); closeModal(); showToast('回滚成功', v.versionNo); render();
|
||
|
|
}
|
||
|
|
render();
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|