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

166 lines
10 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>