diff --git a/agv_app/__pycache__/app.cpython-312.pyc b/agv_app/__pycache__/app.cpython-312.pyc index 3f184e9..08361e9 100644 Binary files a/agv_app/__pycache__/app.cpython-312.pyc and b/agv_app/__pycache__/app.cpython-312.pyc differ diff --git a/agv_app/app.py b/agv_app/app.py index fc9f86c..dd22d3f 100644 --- a/agv_app/app.py +++ b/agv_app/app.py @@ -1376,6 +1376,23 @@ def api_mission_state(): if r < len(grid) and c < len(grid[r]) and grid[r][c]: path.append((r, c)) + # 网格级别任务数据 + result["rows"] = rows + result["cols"] = cols + result["grid"] = grid + result["point_status"] = {f"{pr}_{c}": "pending" for pr in range(rows+1) for c in range(cols)} + result["machine_status"] = {} + for r in range(rows): + for c in range(cols): + if r < len(grid) and c < len(grid[r]) and grid[r][c]: + result["machine_status"][f"{r}_{c}"] = { + "has_machine": True, + "qr": "pending", "qr_val": None, + "front": "pending", "front_cnt": 0, + "back": "pending", "back_cnt": 0, + "status": "pending", "step": "等待", + } + # 保留旧的 tasks 列表(兼容) tlist = [] for (r, c) in path: tlist.append({ @@ -1390,15 +1407,26 @@ def api_mission_state(): }) result["tasks"] = tlist except Exception: + result["rows"] = 1 + result["cols"] = 1 + result["grid"] = [] + result["point_status"] = {} + result["machine_status"] = {} result["tasks"] = [] - # 错误弹窗状态 + # 错误弹窗状态和实时网格状态 if hasattr(MissionExecutorV3, "_instance") and MissionExecutorV3._instance: ex = MissionExecutorV3._instance st = ex.get_status() result["error_msg"] = st.get("error", "") result["waiting_step"] = (st.get("status") == "waiting_step") result["waiting_error"] = (st.get("status") == "waiting_error") + # 从 executor.report 读取实时点/机器状态 + rpt = ex.report + if rpt.get("point_status"): + result["point_status"] = rpt["point_status"] + if rpt.get("machine_status"): + result["machine_status"] = rpt["machine_status"] else: result["error_msg"] = "" result["waiting_step"] = False diff --git a/agv_app/static/css/style.css b/agv_app/static/css/style.css index a28ea53..d2e3b69 100644 --- a/agv_app/static/css/style.css +++ b/agv_app/static/css/style.css @@ -1043,3 +1043,36 @@ a:hover { text-decoration: underline; } font-size: 12px; color: #666; } + +/* ===== 运行页网格任务状态 ===== */ +/* 点位单元格 - 导航状态 */ +.point-cell { min-height: 52px; } +.cell-nav-icon { font-size: 18px; text-align: center; } +.cell-nav-label { font-size: 11px; color: #8899aa; text-align: center; margin-top: 2px; } +.point-cell.nav-pending { background: #141e28; border-color: #2a3a4a; } +.point-cell.nav-active { background: #1a3020; border-color: #4caf50; animation: navPulse 1.5s infinite; } +.point-cell.nav-done { background: #152522; border-color: #2e7d32; } +.point-cell.nav-done .cell-nav-icon { color: #4caf50; } +.point-cell.nav-skipped { background: #141e28; border-color: #2a3a4a; opacity: 0.5; } +@keyframes navPulse { 0%,100% { border-color: #4caf50; } 50% { border-color: #1b5e20; } } + +/* 机器单元格状态 */ +.machine-cell { min-height: 62px; padding: 6px 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; } +.machine-label { font-size: 12px; font-weight: 600; color: #ccc; } +.machine-steps-mini { display: flex; gap: 8px; font-size: 13px; } +.machine-qr-mini { font-size: 10px; color: #4fc3f7; max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.empty-cell { color: #445566; font-size: 12px; } + +/* 步骤圆点 */ +.step-dot { opacity: 0.4; transition: opacity 0.3s; } +.step-dot.dot-done { opacity: 1; } +.step-dot.dot-scanning, +.step-dot.dot-shooting { opacity: 0.8; animation: dotPulse 0.8s infinite; } +@keyframes dotPulse { 0%,100% { opacity: 0.4; } 50% { opacity: 1; } } +.step-dot.dot-manual { opacity: 0.7; } +.step-dot.dot-skipped { opacity: 0.3; } + +/* 机器状态背景 */ +.machine-cell.mstatus-pending { background: #141e28; border-color: #2a3a4a; } +.machine-cell.mstatus-active { background: #1a2535; border-color: #4fc3f7; } +.machine-cell.mstatus-completed { background: #152522; border-color: #2e7d32; } diff --git a/agv_app/static/js/running.js b/agv_app/static/js/running.js index e3e1852..7757013 100644 --- a/agv_app/static/js/running.js +++ b/agv_app/static/js/running.js @@ -2,8 +2,6 @@ const { createApp } = Vue const API = '' createApp({ - delimiters: ['[[', ']]'], - data() { return { missionState: 'idle', @@ -15,7 +13,14 @@ createApp({ polling: null, logs: [], showQrModal: false, + qrSubmitting: false, qrValue: '', + // 网格任务显示 + missionRows: 0, + missionCols: 0, + missionGrid: [], + pointStatus: {}, + machineStatus: {}, // 错误弹窗 / 单步执行 waitingError: false, errorMsg: '', @@ -68,6 +73,13 @@ createApp({ this.progress = data.progress || 0 if (data.tasks) this.tasks = data.tasks + // 网格数据 + if (data.rows) this.missionRows = data.rows + if (data.cols) this.missionCols = data.cols + if (data.grid) this.missionGrid = data.grid + if (data.point_status) this.pointStatus = data.point_status + if (data.machine_status) this.machineStatus = data.machine_status + // 错误弹窗 if (data.waiting_error) { this.waitingError = true @@ -84,8 +96,11 @@ createApp({ this.waitingStep = false } - // QR 弹窗 - if (this.missionState === 'waiting_qr' && !this.showQrModal) { + // QR 弹窗(防止提交后重复弹出) + if (this.missionState !== 'waiting_qr') { + this.qrSubmitting = false + } + if (this.missionState === 'waiting_qr' && !this.showQrModal && !this.qrSubmitting) { this.showQrModal = true this.qrValue = '' } @@ -190,10 +205,12 @@ createApp({ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ qr: val || ' ' }) }) + this.qrSubmitting = true this.showQrModal = false this.qrValue = '' }, cancelQr() { + this.qrSubmitting = true this.showQrModal = false this.qrValue = '' fetch(API + '/api/mission/manual-qr', { @@ -207,6 +224,36 @@ createApp({ }, onArmPreviewError(e) { e.target.style.display = 'none' + }, + // ===== 网格任务显示方法 ===== + getPointStatus(pr, c) { + return (this.pointStatus && this.pointStatus[pr + '_' + c]) || 'pending' + }, + navIcon(s) { + const m = { pending: '⏳', active: '🔄', done: '✅', skipped: '⏭️' } + return m[s] || '⏳' + }, + navLabel(s) { + const m = { pending: '等待', active: '导航中', done: '到达', skipped: '空位' } + return m[s] || '等待' + }, + hasMachine(r, c) { + const key = r + '_' + c + const ms = this.machineStatus && this.machineStatus[key] + return ms && ms.has_machine + }, + getMachineClass(r, c) { + const key = r + '_' + c + const ms = this.machineStatus && this.machineStatus[key] + if (!ms) return '' + const s = ms.status || 'pending' + return 'mstatus-' + s + }, + getMachineField(r, c, field) { + const key = r + '_' + c + const ms = this.machineStatus && this.machineStatus[key] + if (!ms) return '' + return ms[field] || '' } } }).mount('#app') \ No newline at end of file diff --git a/agv_app/templates/running.html b/agv_app/templates/running.html index 294122c..56e2644 100644 --- a/agv_app/templates/running.html +++ b/agv_app/templates/running.html @@ -4,7 +4,7 @@