259 lines
7.8 KiB
JavaScript
259 lines
7.8 KiB
JavaScript
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
|
|
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,
|
|
})
|
|
})
|
|
this.missionState = 'running'
|
|
},
|
|
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') |