aps-agent/aps-frontend/pages/scheduling.html

166 lines
10 KiB
HTML
Raw Normal View History

2026-07-21 11:05:57 +08:00
<!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="row">
<div class="col-4">
<div class="card">
<div class="card-header">排产参数</div>
<div class="card-body">
<div class="form-group"><label>排产引擎</label><select id="engineType"><option value="RULE">规则引擎(快速)</option><option value="CP">约束规划 CP-SAT</option><option value="GA">遗传算法 GA</option><option value="HYBRID" selected>混合模式</option></select></div>
<div class="form-group"><label>策略模板</label><select id="strategyTemplate"><option value="DELIVERY_FIRST">交期优先</option><option value="CAPACITY_BALANCE">产能均衡</option><option value="COST_FIRST">成本最优</option><option value="FAST_RESPONSE">快速响应</option><option value="COMPREHENSIVE" selected>综合优化</option></select></div>
<div class="form-group"><label>计划展望期(天)</label><input id="horizon" type="number" value="14"></div>
<div class="form-group"><label>排产起始日</label><input id="startDate" type="date"></div>
<div class="form-group"><label>目标订单</label><select id="orderScope"><option value="">全部待排产订单</option><option value="selected">仅已选订单</option></select></div>
<div class="form-group"><label>约束检查</label><div><label style="font-weight:400"><input type="checkbox" id="ckMaterial" checked> 物料齐套</label></div><div><label style="font-weight:400"><input type="checkbox" id="ckEquipment" checked> 设备可用</label></div><div><label style="font-weight:400"><input type="checkbox" id="ckPersonnel" checked> 人员班次</label></div><div><label style="font-weight:400"><input type="checkbox" id="ckChangeover" checked> 换线时间</label></div></div>
<button class="btn btn-primary w-full" onclick="startScheduling()" id="runBtn">开始排产</button>
</div>
</div>
</div>
<div class="col-8">
<div class="card" id="progressCard" style="display:none">
<div class="card-header">排产进度</div>
<div class="card-body">
<div class="flex justify-between mb-2"><span id="phaseText">初始化...</span><span id="progressText">0%</span></div>
<div class="progress"><div class="progress-bar" id="progressBar" style="width:0%"></div></div>
<div class="mt-3" id="engineLog" style="font-family:monospace;font-size:12px;color:#6b7280;max-height:160px;overflow:auto"></div>
</div>
</div>
<div class="card" id="resultCard" style="display:none">
<div class="card-header">排产结果</div>
<div class="card-body">
<div class="row">
<div class="col-3"><div class="kpi-value" id="r_orders">0</div><div class="kpi-label">订单数</div></div>
<div class="col-3"><div class="kpi-value" id="r_pos">0</div><div class="kpi-label">生产订单</div></div>
<div class="col-3"><div class="kpi-value" id="r_wos">0</div><div class="kpi-label">工单数</div></div>
<div class="col-3"><div class="kpi-value" id="r_conflicts">0</div><div class="kpi-label">冲突数</div></div>
</div>
<div class="row mt-3">
<div class="col-4"><div class="kpi-value" id="r_tardiness">0h</div><div class="kpi-label">总延迟</div></div>
<div class="col-4"><div class="kpi-value" id="r_util">0%</div><div class="kpi-label">平均利用率</div></div>
<div class="col-4"><div class="kpi-value" id="r_cost">0</div><div class="kpi-label">预估成本</div></div>
</div>
<div class="mt-3 table-wrap">
<table id="resultTable"><thead><tr><th>生产订单</th><th>产品</th><th>产线</th><th>计划起止</th><th>齐套</th><th>冲突</th></tr></thead><tbody></tbody></table>
</div>
</div>
<div class="card-footer">
<button class="btn" onclick="location.href='calendar.html'">查看日历</button>
<button class="btn btn-success ml-auto" id="publishBtn" onclick="publishVersion()">发布版本</button>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<script src="../js/data.js"></script>
<script src="../js/common.js"></script>
<script>
initPage('scheduling');
const params = new URLSearchParams(location.search);
const selectedOrderIds = params.get('orderIds') ? params.get('orderIds').split(',').map(Number) : [];
if (selectedOrderIds.length) document.getElementById('orderScope').value = 'selected';
document.getElementById('startDate').value = apsData.$utils.fmtDate(apsData.$utils.addMinutes(apsData.$utils.today(), 24 * 60));
let currentVersion = null;
function startScheduling() {
const engine = document.getElementById('engineType').value;
const strategy = document.getElementById('strategyTemplate').value;
const horizon = Number(document.getElementById('horizon').value);
const startDate = document.getElementById('startDate').value;
const scope = document.getElementById('orderScope').value;
const orderIds = scope === 'selected' ? selectedOrderIds : [];
const constraints = {
materialKit: document.getElementById('ckMaterial').checked,
equipment: document.getElementById('ckEquipment').checked,
personnel: document.getElementById('ckPersonnel').checked,
changeover: document.getElementById('ckChangeover').checked
};
document.getElementById('progressCard').style.display = 'block';
document.getElementById('resultCard').style.display = 'none';
document.getElementById('runBtn').disabled = true;
const logEl = document.getElementById('engineLog');
logEl.innerHTML = '';
const phases = [
{ p: 10, t: '加载订单与主数据...' },
{ p: 25, t: '规则引擎生成初始解...' },
{ p: 55, t: 'CP-SAT 局部优化...' },
{ p: 80, t: 'GA 全局优化...' },
{ p: 95, t: '冲突检测与统计...' },
{ p: 100, t: '排产完成' }
];
let idx = 0;
function tick() {
if (idx >= phases.length) return finalize();
const ph = phases[idx++];
document.getElementById('progressBar').style.width = ph.p + '%';
document.getElementById('progressText').textContent = ph.p + '%';
document.getElementById('phaseText').textContent = ph.t;
logEl.innerHTML += `> ${ph.t}<br>`;
logEl.scrollTop = logEl.scrollHeight;
if (engine === 'RULE' && ph.p > 25) return finalize();
setTimeout(tick, 400 + Math.random() * 300);
}
function finalize() {
currentVersion = runScheduling({ orderIds, engineType: engine, strategyTemplate: strategy, planningHorizonDays: horizon, startDate, constraints });
showResult(currentVersion);
document.getElementById('runBtn').disabled = false;
}
tick();
}
function showResult(version) {
document.getElementById('resultCard').style.display = 'block';
document.getElementById('r_orders').textContent = version.orderCount;
document.getElementById('r_pos').textContent = version.poCount;
document.getElementById('r_wos').textContent = version.woCount;
document.getElementById('r_conflicts').textContent = version.conflictCount;
document.getElementById('r_tardiness').textContent = Math.round(version.totalTardiness) + 'h';
document.getElementById('r_util').textContent = Math.round(version.avgUtilization * 100) + '%';
document.getElementById('r_cost').textContent = '¥' + Math.round(version.totalCost).toLocaleString();
const pos = apsData.productionOrders.filter(po => po.schedulingVersionId == version.id);
const tbody = document.querySelector('#resultTable tbody');
tbody.innerHTML = pos.map(po => `
<tr>
<td>${po.orderNo}</td>
<td>${po.productName} × ${po.quantity}</td>
<td>${po.lineName}</td>
<td>${po.plannedStartDate ? po.plannedStartDate.slice(0,16) : ''} ~ ${po.plannedEndDate ? po.plannedEndDate.slice(0,16) : ''}</td>
<td>${statusBadge(po.materialKitStatus, { PASSED:'success', PARTIAL:'warning', FAILED:'danger', PENDING:'default' })}</td>
<td>${po.conflictCount}</td>
</tr>`).join('') || '<tr><td colspan="6" class="text-center text-muted">无数据</td></tr>';
document.getElementById('publishBtn').disabled = version.status === 'PUBLISHED';
}
function publishVersion() {
if (!currentVersion) return;
currentVersion.status = 'PUBLISHED';
currentVersion.publishedAt = apsData.$utils.fmtDateTime(new Date());
apsData.productionOrders.filter(po => po.schedulingVersionId == currentVersion.id).forEach(po => po.status = 'CONFIRMED');
apsData.workOrders.filter(wo => {
const po = apsData.productionOrders.find(p => p.id == wo.productionOrderId);
return po && po.schedulingVersionId == currentVersion.id;
}).forEach(wo => wo.status = 'PENDING');
apsData.logs.unshift({ id: apsData.$utils.nextId('log'), type: 'INFO', category: 'SCHEDULE', operationType: 'PUBLISH', targetType: 'SCHEDULE_VERSION', targetId: currentVersion.id, action: '发布排产版本', description: `版本 ${currentVersion.versionNo} 已发布`, operator: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()) });
apsData.$utils.save(apsData);
showToast('发布成功', `版本 ${currentVersion.versionNo} 已发布为执行基准`);
showResult(currentVersion);
}
</script>
</body>
</html>