Files
smart-inspection/agv_app/static/js/running.js
T
2026-06-13 14:07:19 +08:00

313 lines
9.7 KiB
JavaScript

const { createApp } = Vue
const API = ''
createApp({
data() {
return {
missionState: 'idle',
progress: 0,
tasks: [],
report: null,
armCameraOpened: false,
hasAgvCamera: false,
agvPreviewUrl: API + '/api/camera/preview',
armPreviewUrl: '',
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: 1.0,
armSpeed: 1000,
}
},
computed: {
missionStateText() {
const map = {
idle: '空闲',
running: '任务运行中',
paused: '已暂停',
completed: '已完成',
waiting_qr: '等待输入二维码',
waiting_error: '⚠️ 执行错误',
waiting_step: '🦶 等待步骤确认',
}
return map[this.missionState] || '未知'
},
},
mounted() {
this.poll()
this.checkArmCamera()
},
beforeUnmount() {
if (this.polling) clearInterval(this.polling)
},
methods: {
async checkArmCamera() {
try {
const res = await fetch(API + '/api/status')
const data = await res.json()
const opened = data.arm_camera_opened
if (opened !== this.armCameraOpened || (opened && !this.armPreviewUrl)) {
this.armCameraOpened = opened
this.armPreviewUrl = opened ? API + '/api/camera/arm_preview' : ''
}
} catch (e) {}
},
async checkAgvCameraCapabilities() {
try {
const res = await fetch(API + '/api/camera/capabilities')
const data = await res.json()
this.hasAgvCamera = data.has_agv_camera
} catch (e) { this.hasAgvCamera = false }
},
poll() {
this.refresh()
this.pollLogs()
this.polling = setInterval(() => {
this.checkArmCamera()
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
this.armCameraOpened = data.arm_camera_opened
if (this.armCameraOpened && !this.armPreviewUrl) {
this.armPreviewUrl = API + '/api/camera/arm_preview'
}
// 错误弹窗
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({
arm_init: this.qrScanEnabled || this.frontPhotoEnabled || this.backPhotoEnabled,
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' })
})
},
async rescanQr() {
this.showQrModal = false
this.qrValue = ''
this.qrSubmitting = true
await fetch(API + '/api/mission/manual-qr', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ qr: 'RESCAN' })
})
// 4秒后允许弹窗重新出现(后端重试扫码约3秒)
setTimeout(() => { this.qrSubmitting = false }, 4000)
},
onAgvPreviewError(e) {
e.target.style.display = 'none'
},
onArmPreviewError(e) {
// 重新加载:加时间戳避免缓存
const url = this.armPreviewUrl
if (url) {
const sep = url.includes('?') ? '&' : '?'
this.armPreviewUrl = url + sep + '_t=' + Date.now()
}
},
// ===== 网格任务显示方法 =====
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')