aps-agent/aps-frontend/index.html

146 lines
7.8 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="row">
<div class="col-3">
<div class="card kpi-card">
<div class="kpi-icon" style="background:#ffedd5;color:#c2410c"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 17v-2H4.5A2.5 2.5 0 0 1 2 12.5v-9A2.5 2.5 0 0 1 4.5 1h9A2.5 2.5 0 0 1 16 3.5V9h-2V3.5a.5.5 0 0 0-.5-.5h-9a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5H9z"/><path d="M15 17h6v2h-6z"/><path d="M15 21h6v2h-6z"/><path d="M20 11V7h-2v4h-4v2h4v4h2v-4h4v-2h-4z"/></svg></div>
<div><div class="kpi-value" id="kpiOrders">0</div><div class="kpi-label">待排产订单</div></div>
</div>
</div>
<div class="col-3">
<div class="card kpi-card">
<div class="kpi-icon" style="background:#dcfce7;color:#15803d"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg></div>
<div><div class="kpi-value" id="kpoCount">0</div><div class="kpi-label">生产订单</div></div>
</div>
</div>
<div class="col-3">
<div class="card kpi-card">
<div class="kpi-icon" style="background:#e0f2fe;color:#0369a1"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div>
<div><div class="kpi-value" id="kpiDelay">0h</div><div class="kpi-label">总延迟</div></div>
</div>
</div>
<div class="col-3">
<div class="card kpi-card">
<div class="kpi-icon" style="background:#fef9c3;color:#a16207"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg></div>
<div><div class="kpi-value" id="kpiUtil">0%</div><div class="kpi-label">平均利用率</div></div>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="card">
<div class="card-header">产线负荷趋势</div>
<div class="card-body"><canvas id="loadChart" style="width:100%;height:220px"></canvas></div>
</div>
</div>
<div class="col-3">
<div class="card">
<div class="card-header">订单状态分布</div>
<div class="card-body"><canvas id="statusPie" style="width:100%;height:220px"></canvas></div>
</div>
</div>
<div class="col-3">
<div class="card">
<div class="card-header">快捷操作</div>
<div class="card-body">
<button class="btn btn-primary w-full mb-2" onclick="location.href='pages/scheduling.html'">执行排产</button>
<button class="btn w-full mb-2" onclick="location.href='pages/orders.html'">录入订单</button>
<button class="btn w-full mb-2" onclick="location.href='pages/calendar.html'">查看日历</button>
<button class="btn w-full" onclick="location.href='pages/conflicts.html'">处理冲突</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-6">
<div class="card">
<div class="card-header">最近排产版本</div>
<div class="card-body table-wrap"><table id="versionTable"><thead><tr><th>版本号</th><th>引擎</th><th>订单/工单</th><th>利用率</th><th>冲突</th><th>状态</th></tr></thead><tbody></tbody></table></div>
</div>
</div>
<div class="col-6">
<div class="card">
<div class="card-header">待处理冲突</div>
<div class="card-body table-wrap"><table id="conflictTable"><thead><tr><th>类型</th><th>级别</th><th>描述</th><th>建议</th></tr></thead><tbody></tbody></table></div>
</div>
</div>
</div>
</main>
</div>
</div>
<script src="js/data.js"></script>
<script src="js/common.js"></script>
<script>
initPage('index');
function render() {
const pending = apsData.salesOrders.filter(o => o.status === 'CONFIRMED').length;
document.getElementById('kpiOrders').textContent = pending;
document.getElementById('kpoCount').textContent = apsData.productionOrders.length;
const latest = apsData.scheduleVersions[apsData.scheduleVersions.length - 1];
document.getElementById('kpiDelay').textContent = latest ? Math.round(latest.totalTardiness) + 'h' : '0h';
document.getElementById('kpiUtil').textContent = latest ? Math.round(latest.avgUtilization * 100) + '%' : '0%';
// 负荷趋势
const labels = [];
const d1 = [], d2 = [], d3 = [];
for (let i = 0; i < 7; i++) {
const d = apsData.$utils.fmtDate(apsData.$utils.addMinutes(apsData.$utils.today(), i * 24 * 60));
labels.push(d.slice(5));
d1.push(Math.round(Math.random() * 30 + 60));
d2.push(Math.round(Math.random() * 30 + 50));
d3.push(Math.round(Math.random() * 30 + 40));
}
drawLineChart('loadChart', labels, [d1, d2, d3]);
// 订单状态饼图
const statusCounts = { CONFIRMED: 0, IN_PRODUCTION: 0, COMPLETED: 0, DRAFT: 0, CANCELLED: 0 };
apsData.salesOrders.forEach(o => statusCounts[o.status] = (statusCounts[o.status] || 0) + 1);
drawPieChart('statusPie', [
{ label: '已确认', value: statusCounts.CONFIRMED || 0, color: '#f97316' },
{ label: '生产中', value: statusCounts.IN_PRODUCTION || 0, color: '#16a34a' },
{ label: '已完成', value: statusCounts.COMPLETED || 0, color: '#0ea5e9' },
{ label: '草稿', value: statusCounts.DRAFT || 0, color: '#6b7280' },
{ label: '已取消', value: statusCounts.CANCELLED || 0, color: '#dc2626' }
]);
// 版本表
const vt = document.querySelector('#versionTable tbody');
vt.innerHTML = apsData.scheduleVersions.slice(-5).reverse().map(v => `
<tr>
<td><a href="pages/versions.html?id=${v.id}">${v.versionNo}</a></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>${v.conflictCount}</td>
<td>${statusBadge(v.status, { DRAFT:'default', PUBLISHED:'success', ARCHIVED:'warning' })}</td>
</tr>`).join('') || '<tr><td colspan="6" class="text-center text-muted">暂无版本</td></tr>';
// 冲突表
const ct = document.querySelector('#conflictTable tbody');
const unresolved = apsData.conflicts.filter(c => !c.isResolved).slice(0, 5);
ct.innerHTML = unresolved.map(c => `
<tr>
<td>${c.conflictType}</td>
<td>${statusBadge(c.severity, { CRITICAL:'danger', MAJOR:'warning', MINOR:'info' })}</td>
<td>${c.description}</td>
<td class="text-muted">${c.suggestedSolution}</td>
</tr>`).join('') || '<tr><td colspan="4" class="text-center text-muted">暂无冲突</td></tr>';
}
render();
</script>
</body>
</html>