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

143 lines
9.8 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="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>