aps-agent/aps-frontend/pages/work-orders.html

143 lines
9.8 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="toolbar">
<button class="btn btn-primary" id="batchIssueBtn" onclick="batchIssue()" disabled>批量下发</button>
<select id="statusFilter" class="btn" style="padding:7px 10px" onchange="render()">
<option value="">全部状态</option>
<option value="PENDING">待下发</option>
<option value="ISSUED">已下发</option>
<option value="IN_PROGRESS">执行中</option>
<option value="PAUSED">已暂停</option>
<option value="COMPLETED">已完成</option>
<option value="CANCELLED">已取消</option>
</select>
<div class="search-box"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg><input id="searchInput" placeholder="搜索工单号/产品/工序" oninput="render()"></div>
</div>
<div class="card">
<div class="card-body table-wrap">
<table id="woTable">
<thead><tr><th><input type="checkbox" id="checkAll" onclick="toggleAll()"></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 class="card-footer flex justify-between">
<span id="totalInfo" class="text-muted"></span>
<div id="pagination" class="pagination"></div>
</div>
</div>
</main>
</div>
</div>
<script src="../js/data.js"></script>
<script src="../js/common.js"></script>
<script>
initPage('work-orders');
let page = 1, pageSize = 12, selectedIds = [];
function render(pg) {
if (pg) page = pg;
const st = document.getElementById('statusFilter').value;
const kw = document.getElementById('searchInput').value.toLowerCase();
let rows = apsData.workOrders.filter(wo => {
if (st && wo.status !== st) return false;
const text = (wo.orderNo + ' ' + wo.productName + ' ' + wo.operationName + ' ' + wo.lineName).toLowerCase();
return !kw || text.includes(kw);
}).sort((a, b) => apsData.$utils.parseDate(a.plannedStartTime) - apsData.$utils.parseDate(b.plannedStartTime));
const total = rows.length;
rows = rows.slice((page - 1) * pageSize, page * pageSize);
const tbody = document.querySelector('#woTable tbody');
tbody.innerHTML = rows.map(wo => `
<tr>
<td><input type="checkbox" class="row-check" value="${wo.id}" ${selectedIds.includes(wo.id)?'checked':''} onchange="toggleRow(${wo.id})"></td>
<td>${wo.orderNo}</td>
<td>${wo.operationName}</td>
<td>${wo.productName} × ${wo.quantity}</td>
<td>${wo.lineName} / ${wo.workstationName}</td>
<td>${wo.plannedStartTime.slice(0,16)}<br><span class="text-muted">${wo.plannedEndTime.slice(0,16)}</span></td>
<td><div class="progress"><div class="progress-bar ${wo.progressPercent>=100?'success':(wo.progressPercent>0?'warning':'')}" style="width:${wo.progressPercent}%"></div></div>${wo.progressPercent}%</td>
<td>${statusBadge(wo.kitStatus, { PASSED:'success', PARTIAL:'warning', FAILED:'danger', PENDING:'default' })}</td>
<td>${statusBadge(wo.status, { PENDING:'default', ISSUED:'primary', IN_PROGRESS:'warning', PAUSED:'warning', COMPLETED:'success', CANCELLED:'danger' })}</td>
<td class="actions">
<button class="btn btn-sm" onclick="viewWo(${wo.id})">详情</button>
${wo.status === 'PENDING' ? `<button class="btn btn-sm btn-success" onclick="issueWo(${wo.id})">下发</button>` : ''}
${wo.status === 'IN_PROGRESS' || wo.status === 'ISSUED' ? `<button class="btn btn-sm" onclick="feedbackWo(${wo.id})">反馈</button>` : ''}
${wo.status === 'IN_PROGRESS' ? `<button class="btn btn-sm btn-warning" onclick="pauseWo(${wo.id})">暂停</button>` : ''}
${wo.status === 'PAUSED' ? `<button class="btn btn-sm btn-success" onclick="resumeWo(${wo.id})">恢复</button>` : ''}
</td>
</tr>`).join('') || '<tr><td colspan="10" class="text-center text-muted">无数据</td></tr>';
document.getElementById('totalInfo').textContent = `共 ${total} 条`;
document.getElementById('batchIssueBtn').disabled = !selectedIds.length;
renderPagination('pagination', page, total, pageSize, 'render');
}
function toggleRow(id) { selectedIds = selectedIds.includes(id) ? selectedIds.filter(x=>x!==id) : [...selectedIds, id]; render(); }
function toggleAll() { const checked = document.getElementById('checkAll').checked; selectedIds = checked ? $$('.row-check').map(cb=>Number(cb.value)) : []; render(); }
function viewWo(id) {
const wo = findById(apsData.workOrders, id);
const matList = wo.requiredMaterials.map(m => `<li>${m.name}: 需求 ${m.required}, 可用 ${m.available}, ${statusBadge(m.status, { PASSED:'success', PARTIAL:'warning', FAILED:'danger' })}</li>`).join('') || '<li class="text-muted">无物料需求</li>';
openModal('工单详情', `
<div class="form-row"><div class="form-group"><label>工单号</label><input value="${wo.orderNo}" readonly></div><div class="form-group"><label>工序</label><input value="${wo.operationName}" readonly></div></div>
<div class="form-row"><div class="form-group"><label>产品</label><input value="${wo.productName} × ${wo.quantity}" readonly></div><div class="form-group"><label>工位</label><input value="${wo.workstationName}" readonly></div></div>
<div class="form-row"><div class="form-group"><label>计划开始</label><input value="${wo.plannedStartTime}" readonly></div><div class="form-group"><label>计划结束</label><input value="${wo.plannedEndTime}" readonly></div></div>
<p><strong>物料齐套:</strong></p><ul>${matList}</ul>
`, '', true);
}
function issueWo(id) {
const wo = findById(apsData.workOrders, id);
if (wo.kitStatus === 'FAILED') return showToast('无法下发', '物料不齐套', 'error');
wo.status = 'ISSUED'; wo.isFrozen = true;
apsData.logs.unshift({ id: apsData.$utils.nextId('log'), type: 'INFO', category: 'WO', operationType: 'ISSUE', targetType: 'WORK_ORDER', targetId: id, action: '下发工单', description: `工单 ${wo.orderNo} 已下发至 MES`, operator: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()) });
apsData.$utils.save(apsData); showToast('下发成功', wo.orderNo); render();
}
function batchIssue() {
let count = 0;
selectedIds.forEach(id => {
const wo = findById(apsData.workOrders, id);
if (wo.status === 'PENDING' && wo.kitStatus !== 'FAILED') { wo.status = 'ISSUED'; wo.isFrozen = true; count++; }
});
apsData.$utils.save(apsData); selectedIds = []; render();
showToast('批量下发完成', `成功下发 ${count} 个工单`);
}
function feedbackWo(id) {
const wo = findById(apsData.workOrders, id);
openModal('工单执行反馈', `
<div class="form-row"><div class="form-group"><label>累计完成数量</label><input id="f_qty" type="number" value="${wo.completedQuantity || 0}"></div><div class="form-group"><label>报废数量</label><input id="f_scrap" type="number" value="0"></div></div>
<div class="form-row"><div class="form-group"><label>反馈类型</label><select id="f_type"><option value="PROGRESS">进度更新</option><option value="COMPLETION">完工</option><option value="EXCEPTION">异常</option></select></div></div>
<div class="form-row"><div class="form-group"><label>异常说明</label><textarea id="f_exc" rows="2" placeholder="选异常时填写"></textarea></div></div>
`, `
<button class="btn" onclick="closeModal()">取消</button>
<button class="btn btn-primary" onclick="doFeedback(${id})">提交</button>
`);
}
function doFeedback(id) {
const wo = findById(apsData.workOrders, id);
const qty = Number(document.getElementById('f_qty').value);
const scrap = Number(document.getElementById('f_scrap').value);
const type = document.getElementById('f_type').value;
wo.completedQuantity = qty;
wo.progressPercent = Math.min(100, Math.round(qty / wo.quantity * 100));
if (type === 'COMPLETION' || wo.progressPercent >= 100) { wo.status = 'COMPLETED'; wo.actualEndTime = apsData.$utils.fmtDateTime(new Date()); }
else if (type === 'EXCEPTION') { wo.status = 'PAUSED'; }
else if (wo.status === 'ISSUED' && qty > 0) wo.status = 'IN_PROGRESS';
apsData.logs.unshift({ id: apsData.$utils.nextId('log'), type: type==='EXCEPTION'?'ERROR':'INFO', category: 'WO', operationType: 'FEEDBACK', targetType: 'WORK_ORDER', targetId: id, action: '工单反馈', description: `${wo.orderNo} 进度 ${wo.progressPercent}%`, operator: 'admin', createdAt: apsData.$utils.fmtDateTime(new Date()) });
apsData.$utils.save(apsData); closeModal(); showToast('反馈已提交', wo.orderNo); render();
}
function pauseWo(id) { const wo = findById(apsData.workOrders, id); wo.status='PAUSED'; apsData.$utils.save(apsData); showToast('已暂停', wo.orderNo); render(); }
function resumeWo(id) { const wo = findById(apsData.workOrders, id); wo.status='IN_PROGRESS'; apsData.$utils.save(apsData); showToast('已恢复', wo.orderNo); render(); }
render();
</script>
</body>
</html>