查验
This commit is contained in:
@@ -59,6 +59,49 @@ a:hover { text-decoration: underline; }
|
||||
.status-item.paused { background: #3a2a1a; color: #ff9800; }
|
||||
.status-item.idle { background: #2a2a2a; color: #9aa0a6; }
|
||||
|
||||
/* ========== 环境切换开关 ========== */
|
||||
.env-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.env-label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
min-width: 48px;
|
||||
text-align: right;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.env-label.test { color: #ff9800; }
|
||||
.env-label.prod { color: #4fc3f7; }
|
||||
.toggle-switch {
|
||||
width: 40px;
|
||||
height: 22px;
|
||||
background: #3a3a3a;
|
||||
border-radius: 11px;
|
||||
position: relative;
|
||||
transition: background 0.25s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.toggle-switch.active {
|
||||
background: #ff9800;
|
||||
}
|
||||
.toggle-knob {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: left 0.25s;
|
||||
}
|
||||
.toggle-switch.active .toggle-knob {
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
/* ========== 卡片 ========== */
|
||||
.card {
|
||||
background: #1a2332;
|
||||
@@ -466,7 +509,7 @@ a:hover { text-decoration: underline; }
|
||||
object-fit: cover;
|
||||
}
|
||||
.camera-img.arm {
|
||||
transform: rotate(180deg);
|
||||
/* no flip */
|
||||
}
|
||||
|
||||
.camera-placeholder {
|
||||
@@ -1191,3 +1234,74 @@ a:hover { text-decoration: underline; }
|
||||
justify-content: center;
|
||||
padding: 16px 0 8px;
|
||||
}
|
||||
|
||||
/* ===== 查验进度 ===== */
|
||||
.inspection-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.inspection-item {
|
||||
background: rgba(26, 26, 46, 0.7);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
border: 1px solid #2a2a3e;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.inspection-item.insp-done {
|
||||
border-color: #4caf50;
|
||||
background: rgba(76, 175, 80, 0.08);
|
||||
}
|
||||
.inspection-item.insp-active {
|
||||
border-color: #ff9800;
|
||||
background: rgba(255, 152, 0, 0.08);
|
||||
}
|
||||
.insp-name {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
.insp-code {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: #8899aa;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.insp-spec {
|
||||
font-size: 11px;
|
||||
color: #667788;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.insp-count {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
}
|
||||
.insp-num {
|
||||
color: #4fc3f7;
|
||||
}
|
||||
.insp-sep {
|
||||
color: #667788;
|
||||
font-size: 14px;
|
||||
}
|
||||
.insp-total {
|
||||
color: #8899aa;
|
||||
font-size: 14px;
|
||||
}
|
||||
.insp-bar {
|
||||
height: 4px;
|
||||
background: #0a0a14;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.insp-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #4fc3f7, #4caf50);
|
||||
border-radius: 2px;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ createApp({
|
||||
agvCameraError: false,
|
||||
hasAgvCamera: false, // AGV 车体是否有可用相机
|
||||
armCameraError: false,
|
||||
reconnectingDevice: null
|
||||
reconnectingDevice: null,
|
||||
// 环境切换
|
||||
testMode: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -38,6 +40,7 @@ createApp({
|
||||
mounted() {
|
||||
this.refresh()
|
||||
this.refreshCameraCapabilities()
|
||||
this.loadEnvMode()
|
||||
setInterval(this.refreshStatus, 3000)
|
||||
this.refreshCams()
|
||||
setInterval(() => this.refreshCams(), 2000)
|
||||
@@ -133,6 +136,36 @@ createApp({
|
||||
} else {
|
||||
window.location.href = '/running'
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadEnvMode() {
|
||||
try {
|
||||
const res = await fetch(API + '/api/config/mode')
|
||||
const data = await res.json()
|
||||
if (data.ok) {
|
||||
this.testMode = data.test_mode
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载环境配置失败:', e)
|
||||
}
|
||||
},
|
||||
async toggleEnvMode() {
|
||||
const newMode = !this.testMode
|
||||
try {
|
||||
const res = await fetch(API + '/api/config/mode', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({test_mode: newMode})
|
||||
})
|
||||
const data = await res.json()
|
||||
if (data.ok) {
|
||||
this.testMode = data.test_mode
|
||||
alert('已切换至: ' + data.label)
|
||||
} else {
|
||||
alert('切换失败: ' + (data.error || '未知错误'))
|
||||
}
|
||||
} catch (e) {
|
||||
alert('切换请求失败: ' + e.message)
|
||||
}
|
||||
},
|
||||
}
|
||||
}).mount('#app')
|
||||
|
||||
@@ -28,6 +28,7 @@ createApp({
|
||||
errorMsg: '',
|
||||
waitingStep: false,
|
||||
stepLabel: '',
|
||||
qrMessage: '所有姿态均未识别到二维码,请手动输入:',
|
||||
// 任务步骤控制开关(机械臂初始化并入AGV移动)
|
||||
agvMoveEnabled: true,
|
||||
qrScanEnabled: true,
|
||||
@@ -36,6 +37,8 @@ createApp({
|
||||
// 速度控制
|
||||
agvSpeed: 1.0,
|
||||
armSpeed: 1000,
|
||||
// 查验
|
||||
inspection: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -51,6 +54,14 @@ createApp({
|
||||
}
|
||||
return map[this.missionState] || '未知'
|
||||
},
|
||||
inspectionTotal() {
|
||||
if (!this.inspection || !this.inspection.items) return 0
|
||||
return this.inspection.items.reduce((s, i) => s + (i.inspected || 0), 0)
|
||||
},
|
||||
inspectionTarget() {
|
||||
if (!this.inspection || !this.inspection.items) return 0
|
||||
return this.inspection.items.reduce((s, i) => s + (i.quantify || 0), 0)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.poll()
|
||||
@@ -101,6 +112,7 @@ createApp({
|
||||
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.inspection) this.inspection = data.inspection
|
||||
this.armCameraOpened = data.arm_camera_opened
|
||||
if (this.armCameraOpened && !this.armPreviewUrl) {
|
||||
this.armPreviewUrl = API + '/api/camera/arm_preview'
|
||||
@@ -122,6 +134,11 @@ createApp({
|
||||
this.waitingStep = false
|
||||
}
|
||||
|
||||
// QR 弹窗消息
|
||||
if (data.qr_message) {
|
||||
this.qrMessage = data.qr_message
|
||||
}
|
||||
|
||||
// QR 弹窗(防止提交后重复弹出)
|
||||
if (this.missionState !== 'waiting_qr') {
|
||||
this.qrSubmitting = false
|
||||
@@ -129,6 +146,9 @@ createApp({
|
||||
if (this.missionState === 'waiting_qr' && !this.showQrModal && !this.qrSubmitting) {
|
||||
this.showQrModal = true
|
||||
this.qrValue = ''
|
||||
if (!this.qrMessage) {
|
||||
this.qrMessage = '所有姿态均未识别到二维码,请手动输入:'
|
||||
}
|
||||
}
|
||||
|
||||
// 完成后获取报告
|
||||
@@ -156,6 +176,11 @@ createApp({
|
||||
},
|
||||
async startMission() {
|
||||
if (this.missionState !== 'idle') return
|
||||
// 没有设置报关单时阻止启动(后端也会校验,这里提前友好提示)
|
||||
if (!this.inspection) {
|
||||
alert('⚠️ 请先在「设置→报关单」中选择报关单并点击「开始查验」')
|
||||
return
|
||||
}
|
||||
this.logs = []
|
||||
this.progress = 0
|
||||
this.report = null
|
||||
@@ -186,6 +211,11 @@ createApp({
|
||||
},
|
||||
async startSingleStep() {
|
||||
if (this.missionState !== 'idle') return
|
||||
// 没有设置报关单时阻止启动(后端会校验,这里提前友好提示)
|
||||
if (!this.inspection) {
|
||||
alert('⚠️ 请先在「设置→报关单」中选择报关单并点击「开始查验」')
|
||||
return
|
||||
}
|
||||
this.logs = []
|
||||
this.progress = 0
|
||||
this.report = null
|
||||
|
||||
+113
-55
@@ -82,8 +82,9 @@ createApp({
|
||||
this.refreshAngles()
|
||||
this.loadQrConfigs()
|
||||
this.nav2Timer = setInterval(this.refreshNavStatus, 3000)
|
||||
this.armCameraUrl = API + '/api/camera/arm_preview?t=' + Date.now()
|
||||
},
|
||||
this.armSnapshotUrl = ""; this.armCameraUrl = API + '/api/camera/arm_preview?t=' + Date.now()
|
||||
this.armSnapshotUrl = ""; this.armCameraUrl = API + "/api/camera/arm_preview?t=" + Date.now()
|
||||
},
|
||||
computed: {
|
||||
customsTotalPages() {
|
||||
return Math.max(1, Math.ceil(this.customsTotal / this.customsPageSize))
|
||||
@@ -1203,64 +1204,121 @@ createApp({
|
||||
alert('❌ 复位请求失败: ' + e.message)
|
||||
}
|
||||
},
|
||||
},
|
||||
// ===== 报关单方法 =====
|
||||
async loadCustomsList() {
|
||||
this.customsLoading = true
|
||||
try {
|
||||
const url = API + '/api/customs/list?pageNum=' + this.customsPage + '&pageSize=' + this.customsPageSize
|
||||
const res = await fetch(url)
|
||||
const d = await res.json()
|
||||
if (d.ok && d.data) {
|
||||
const raw = d.data
|
||||
let list = []
|
||||
let total = 0
|
||||
if (raw.rows) { list = raw.rows; total = raw.total || list.length }
|
||||
else if (raw.records) { list = raw.records; total = raw.total || list.length }
|
||||
else if (Array.isArray(raw)) { list = raw; total = list.length }
|
||||
else if (raw.data && raw.data.rows) { list = raw.data.rows; total = raw.data.total || list.length }
|
||||
else if (raw.data && raw.data.records) { list = raw.data.records; total = raw.data.total || list.length }
|
||||
else if (raw.data && Array.isArray(raw.data)) { list = raw.data; total = list.length }
|
||||
this.customsList = list
|
||||
this.customsTotal = total || list.length
|
||||
} else {
|
||||
// ===== 报关单方法 =====
|
||||
async loadCustomsList() {
|
||||
this.customsLoading = true
|
||||
try {
|
||||
const url = API + '/api/customs/list?pageNum=' + this.customsPage + '&pageSize=' + this.customsPageSize + '&customsName=' + encodeURIComponent(this.customsName) + '&customsNo=' + encodeURIComponent(this.customsNo)
|
||||
const res = await fetch(url)
|
||||
const d = await res.json()
|
||||
if (d.ok && d.data) {
|
||||
const raw = d.data
|
||||
let list = []
|
||||
let total = 0
|
||||
if (raw.rows) { list = raw.rows; total = raw.total || list.length }
|
||||
else if (raw.records) { list = raw.records; total = raw.total || list.length }
|
||||
else if (Array.isArray(raw)) { list = raw; total = list.length }
|
||||
else if (raw.data && raw.data.rows) { list = raw.data.rows; total = raw.data.total || list.length }
|
||||
else if (raw.data && raw.data.records) { list = raw.data.records; total = raw.data.total || list.length }
|
||||
else if (raw.data && Array.isArray(raw.data)) { list = raw.data; total = list.length }
|
||||
this.customsList = list
|
||||
this.customsTotal = total || list.length
|
||||
} else {
|
||||
this.customsList = []
|
||||
this.customsTotal = 0
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载报关单列表失败', e)
|
||||
this.customsList = []
|
||||
this.customsTotal = 0
|
||||
} finally {
|
||||
this.customsLoading = false
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载报关单列表失败', e)
|
||||
this.customsList = []
|
||||
this.customsTotal = 0
|
||||
} finally {
|
||||
this.customsLoading = false
|
||||
}
|
||||
},
|
||||
async selectCustomsRow(item) {
|
||||
const id = item.id || item.customsId || item.customs_id || ''
|
||||
if (!id) return
|
||||
this.selectedCustomsId = id
|
||||
this.selectedCustomsName = item.customsNo || item.customs_no || item.name || item.customsName || item.customs_name || id
|
||||
this.customsMachines = []
|
||||
try {
|
||||
const res = await fetch(API + '/api/customs/machines?customsId=' + encodeURIComponent(id))
|
||||
const d = await res.json()
|
||||
if (d.ok && d.data) {
|
||||
const raw = d.data
|
||||
let machines = []
|
||||
if (raw.rows) { machines = raw.rows }
|
||||
else if (raw.records) { machines = raw.records }
|
||||
else if (raw.data && Array.isArray(raw.data)) { machines = raw.data }
|
||||
else if (Array.isArray(raw)) { machines = raw }
|
||||
else if (Array.isArray(raw.data)) { machines = raw.data }
|
||||
this.customsMachines = machines
|
||||
} else {
|
||||
},
|
||||
async selectCustomsRow(item) {
|
||||
// 新数据结构: { customs:{id,orderId,..}, orderCode, drawCode }
|
||||
const id = (item.customs && item.customs.id) || item.id || item.customsId || item.customs_id || ''
|
||||
if (!id) return
|
||||
this.selectedCustomsId = id
|
||||
this.selectedCustomsName = (item.customs && item.customs.customsCode) || item.orderCode || item.drawCode || id
|
||||
this.customsMachines = []
|
||||
try {
|
||||
const url = API + '/api/customs/machines?customsId=' + encodeURIComponent(id)
|
||||
const res = await fetch(url)
|
||||
const d = await res.json()
|
||||
if (d.ok && d.data) {
|
||||
const raw = d.data
|
||||
let machines = []
|
||||
// customsMachines 返回格式: {"code":"0","data":[{serialNumber,inventoryName,...}]}
|
||||
if (raw.rows) { machines = raw.rows }
|
||||
else if (raw.records) { machines = raw.records }
|
||||
else if (raw.data && Array.isArray(raw.data)) { machines = raw.data }
|
||||
else if (Array.isArray(raw)) { machines = raw }
|
||||
else if (Array.isArray(raw.data)) { machines = raw.data }
|
||||
this.customsMachines = machines
|
||||
} else {
|
||||
this.customsMachines = []
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载机器列表失败', e)
|
||||
this.customsMachines = []
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载机器列表失败', e)
|
||||
this.customsMachines = []
|
||||
},
|
||||
async startInspection(item) {
|
||||
const id = (item.customs && item.customs.id) || item.id || item.customsId || item.customs_id || ''
|
||||
const name = (item.customs && item.customs.customsCode) || item.orderCode || item.drawCode || id
|
||||
if (!id) return
|
||||
if (!confirm(`确定要对报关单「${name}」开始查验吗?\n点击确定后,运行页将以该报关单的机器进行查验。`)) return
|
||||
try {
|
||||
const res = await fetch(API + '/api/customs/inspection/start', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ customsId: id, customsName: name })
|
||||
})
|
||||
const d = await res.json()
|
||||
if (d.ok) {
|
||||
alert(`✅ 查验已开始!\n报关单: ${name}\n机型: ${d.inspection.items.length} 种\n总数: ${d.inspection.items.reduce((s,i)=>s+i.quantify,0)} 台\n\n请前往「运行」页执行任务。`)
|
||||
// 同时选中该报关单,显示机器列表
|
||||
this.selectedCustomsId = id
|
||||
this.selectedCustomsName = name
|
||||
// 用 inspection items 填充 customsMachines 显示(聚合后)
|
||||
this.customsMachines = d.inspection.items.map(it => ({
|
||||
inventoryCode: it.inventoryCode,
|
||||
inventoryName: it.inventoryName,
|
||||
inventorySpecification: it.spec,
|
||||
serialNumber: '',
|
||||
quantify: it.quantify,
|
||||
inspectionCount: it.inspected,
|
||||
}))
|
||||
} else {
|
||||
alert('❌ 开始查验失败: ' + (d.error || '未知错误'))
|
||||
}
|
||||
} catch (e) {
|
||||
alert('❌ 请求失败: ' + e.message)
|
||||
}
|
||||
},
|
||||
async loadInspectionCounts() {
|
||||
// 轮询查验计数,更新 customsMachines 的 inspectionCount
|
||||
try {
|
||||
const res = await fetch(API + '/api/customs/inspection')
|
||||
const d = await res.json()
|
||||
if (d.ok && d.inspection && this.customsMachines.length) {
|
||||
for (const item of d.inspection.items) {
|
||||
const match = this.customsMachines.find(m => m.inventoryCode === item.inventoryCode)
|
||||
if (match) {
|
||||
match.inspectionCount = item.inspected
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
tab(newVal) {
|
||||
if (newVal === 'customs' && this.customsMachines.length > 0) {
|
||||
// 切换到报关单 tab 时刷新查验计数
|
||||
this.loadInspectionCounts()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
}).mount('#app')
|
||||
|
||||
Reference in New Issue
Block a user