const { createApp } = Vue const API = '' createApp({ data() { return { missionState: 'idle', progress: 0, tasks: [], report: null, agvPreviewUrl: API + '/api/camera/preview', armPreviewUrl: API + '/api/camera/arm_refresh', polling: null, logs: [], showQrModal: false, qrSubmitting: false, qrValue: '', // 网格任务显示 missionRows: 0, missionCols: 0, missionGrid: [], pointStatus: {}, machineStatus: {}, // 错误弹窗 / 单步执行 waitingError: false, errorMsg: '', waitingStep: false, stepLabel: '', // 任务步骤控制开关(机械臂初始化并入AGV移动) agvMoveEnabled: true, qrScanEnabled: true, frontPhotoEnabled: true, backPhotoEnabled: true, // 速度控制 agvSpeed: 0.5, armSpeed: 500, } }, computed: { missionStateText() { const map = { idle: '空闲', running: '任务运行中', paused: '已暂停', completed: '已完成', waiting_qr: '等待输入二维码', waiting_error: '⚠️ 执行错误', waiting_step: '🦶 等待步骤确认', } return map[this.missionState] || '未知' }, }, mounted() { this.poll() }, beforeUnmount() { if (this.polling) clearInterval(this.polling) }, methods: { poll() { this.refresh() this.pollLogs() this.polling = setInterval(() => { this.refresh() this.pollLogs() }, 2000) }, async refresh() { try { const res = await fetch(API + '/api/mission/state') const data = await res.json() this.missionState = data.status || 'idle' 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 this.errorMsg = data.error_msg || '任务执行出错' } else { this.waitingError = false } // 步骤确认弹窗 if (data.waiting_step) { this.waitingStep = true this.stepLabel = data.step_label || '' } else { this.waitingStep = false } // QR 弹窗(防止提交后重复弹出) if (this.missionState !== 'waiting_qr') { this.qrSubmitting = false } if (this.missionState === 'waiting_qr' && !this.showQrModal && !this.qrSubmitting) { this.showQrModal = true this.qrValue = '' } // 完成后获取报告 if (this.missionState === 'idle' && this.tasks.length > 0) { const reportRes = await fetch(API + '/api/mission/report') const reportData = await reportRes.json() this.report = reportData.report } } catch (e) {} }, async pollLogs() { if (this.missionState !== 'running' && this.missionState !== 'waiting_qr' && this.missionState !== 'waiting_error' && this.missionState !== 'waiting_step') return try { const res = await fetch(API + '/api/mission/log') const data = await res.json() if (data.log) this.logs = data.log if (data.progress != null) this.progress = data.progress if (data.tasks) this.tasks = data.tasks // 自动滚到底 this.$nextTick(() => { const box = this.$refs.logBox if (box) box.scrollTop = box.scrollHeight }) } catch (e) {} }, async startMission() { if (this.missionState !== 'idle') return this.logs = [] this.progress = 0 this.report = null this.showQrModal = false try { const res = await fetch(API + '/api/mission/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ agv_move: this.agvMoveEnabled, qr_scan: this.qrScanEnabled, front_photo: this.frontPhotoEnabled, back_photo: this.backPhotoEnabled, agv_speed: this.agvSpeed, arm_speed: this.armSpeed, }) }) const data = await res.json() if (!data.ok) { alert('❌ 启动失败: ' + (data.error || '未知错误')) return } this.missionState = 'running' } catch (e) { alert('❌ 启动请求失败: ' + e.message) } }, async startSingleStep() { if (this.missionState !== 'idle') return this.logs = [] this.progress = 0 this.report = null this.showQrModal = false await fetch(API + '/api/mission/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ single_step: true }) }) if (this.polling) clearInterval(this.polling) this.poll() }, async skipError() { await fetch(API + '/api/mission/error-skip', { method: 'POST' }) this.waitingError = false }, async abortError() { await fetch(API + '/api/mission/error-abort', { method: 'POST' }) this.waitingError = false }, async confirmStep() { await fetch(API + '/api/mission/singlestep/confirm', { method: 'POST' }) this.waitingStep = false }, async retryStep() { await fetch(API + '/api/mission/singlestep/retry', { method: 'POST' }) this.waitingStep = false }, async abortStep() { await fetch(API + '/api/mission/singlestep/abort', { method: 'POST' }) this.waitingStep = false }, async pauseMission() { await fetch(API + '/api/mission/pause', { method: 'POST' }) this.missionState = 'paused' }, async resumeMission() { await fetch(API + '/api/mission/resume', { method: 'POST' }) this.missionState = 'running' this.showQrModal = false }, async stopMission() { await fetch(API + '/api/mission/stop', { method: 'POST' }) this.missionState = 'idle' this.showQrModal = false this.waitingError = false this.waitingStep = false }, async submitQr() { const val = this.qrValue.trim() await fetch(API + '/api/mission/manual-qr', { method: 'POST', 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', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ qr: 'SKIP' }) }) }, onAgvPreviewError(e) { e.target.style.display = 'none' }, 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')