This commit is contained in:
ywb
2026-06-13 14:07:19 +08:00
parent 48121b2a05
commit cbc88def27
14 changed files with 626 additions and 80 deletions
+115
View File
@@ -465,6 +465,10 @@ a:hover { text-decoration: underline; }
aspect-ratio: 4/3;
object-fit: cover;
}
.camera-img.arm {
transform: rotate(180deg);
}
.camera-placeholder {
width: 100%;
aspect-ratio: 4/3;
@@ -1076,3 +1080,114 @@ a:hover { text-decoration: underline; }
.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; }
/* ===== 报关单选择 ===== */
.customs-panel {
display: flex;
flex-direction: column;
gap: 4px;
}
.customs-row {
display: flex;
align-items: center;
gap: 8px;
}
.customs-select {
flex: 1;
max-width: 400px;
padding: 10px 12px;
background: #1a2535;
border: 1px solid #2a3a4a;
border-radius: 8px;
color: #e0e0e0;
font-size: 14px;
outline: none;
}
.customs-select:focus {
border-color: #4fc3f7;
}
.customs-select:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.customs-select option {
background: #1a2535;
color: #e0e0e0;
}
.customs-select:disabled option {
opacity: 1;
}
.customs-info {
font-size: 13px;
color: #8899aa;
}
.customs-badge {
font-size: 13px;
}
.customs-actions {
display: flex;
align-items: center;
gap: 4px;
}
/* ===== 数据表格 ===== */
.table-wrapper {
overflow-x: auto;
margin-top: 8px;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.data-table th {
text-align: left;
padding: 10px 12px;
background: #0f1923;
color: #8899aa;
font-weight: 500;
border-bottom: 1px solid #2a3a4a;
white-space: nowrap;
}
.data-table td {
padding: 10px 12px;
border-bottom: 1px solid #1a2a3a;
color: #ccc;
}
.data-table tbody tr:hover {
background: #1a2a3a;
}
.clickable-row {
cursor: pointer;
transition: background 0.15s;
}
.clickable-row:hover {
background: #1a2535 !important;
}
.row-selected {
background: #142a3a !important;
border-left: 3px solid #4fc3f7;
}
/* ===== Badge 状态标签 ===== */
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
}
.badge-unknown { background: #2a3441; color: #8899aa; }
.badge-normal { background: #1a3a2a; color: #4caf50; }
.badge-active { background: #1a3050; color: #4fc3f7; }
.badge-finished { background: #1a3a2a; color: #4caf50; }
.badge-waiting { background: #3a3020; color: #ffc107; }
.badge-error { background: #3a1a1a; color: #f44336; }
/* ===== 分页控件 ===== */
.pagination {
display: flex;
align-items: center;
justify-content: center;
padding: 16px 0 8px;
}
+17
View File
@@ -18,6 +18,7 @@ createApp({
agvCameraSrc: '/api/camera/refresh?t=' + Date.now(),
armCameraSrc: '/api/camera/arm_preview?t=' + Date.now(),
agvCameraError: false,
hasAgvCamera: false, // AGV 车体是否有可用相机
armCameraError: false,
reconnectingDevice: null
}
@@ -36,6 +37,7 @@ createApp({
},
mounted() {
this.refresh()
this.refreshCameraCapabilities()
setInterval(this.refreshStatus, 3000)
this.refreshCams()
setInterval(() => this.refreshCams(), 2000)
@@ -47,6 +49,17 @@ createApp({
this.armCameraSrc = '/api/camera/arm_preview?t=' + Date.now()
}
},
async refreshCameraCapabilities() {
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 }
},
refreshAgvCamera() {
this.agvCameraSrc = '/api/camera/refresh?t=' + Date.now()
this.agvCameraError = false
},
async refresh() {
await this.refreshStatus()
await this.loadPoints()
@@ -58,6 +71,10 @@ createApp({
this.agvConnected = data.agv_connected
this.armConnected = data.arm_connected
this.cameraOpened = data.camera_opened
// 尝试从后端获取摄像头能力,若无字段则保持默认 false
if (data.has_agv_camera !== undefined) {
this.hasAgvCamera = data.has_agv_camera
}
this.armCameraOpened = data.arm_camera_opened
this.mapLoaded = data.map_loaded
this.currentState = data.state || 'idle'
+8
View File
@@ -9,6 +9,7 @@ createApp({
tasks: [],
report: null,
armCameraOpened: false,
hasAgvCamera: false,
agvPreviewUrl: API + '/api/camera/preview',
armPreviewUrl: '',
polling: null,
@@ -70,6 +71,13 @@ createApp({
}
} 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()
+76 -1
View File
@@ -66,6 +66,15 @@ createApp({
armSnapshotLoading: false,
newQrName: '',
armInitialPose: [0, 0, 0, 0, 0, 0],
// 报关单
customsList: [],
customsLoading: false,
customsPage: 1,
customsPageSize: 15,
customsTotal: 0,
selectedCustomsId: '',
selectedCustomsName: '',
customsMachines: [],
}
},
mounted() {
@@ -76,6 +85,13 @@ createApp({
this.armCameraUrl = API + '/api/camera/arm_preview?t=' + Date.now()
},
computed: {
customsTotalPages() {
return Math.max(1, Math.ceil(this.customsTotal / this.customsPageSize))
},
customsPageData() {
// 前端显示 pagination data — 但我们在 API 后端做分页,所以这里只是引用
return this.customsList
},
hasQr() {
return !!(this.selectedMachine && this.selectedMachine.qr)
},
@@ -1187,5 +1203,64 @@ 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 {
this.customsList = []
this.customsTotal = 0
}
} 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 {
this.customsMachines = []
}
} catch (e) {
console.error('加载机器列表失败', e)
this.customsMachines = []
}
},
}
}).mount('#app')