+
未查询到数据
+
{{ item.label }}
-
+
设备
-
{{ tick }}
+
-
+
+
@@ -289,11 +377,18 @@ export default {
height: 1080,
transform: 'scaleY(1) scaleX(1) translate(-50%, -50%)'
},
+ detailTooltip: {
+ visible: false,
+ type: '',
+ x: 0,
+ y: 0,
+ data: null
+ },
titleText: '',
headerDateCn: '',
headerTime: '',
iframeAddress: '',
- mode: 'DT',
+ mode: '',
currentMode: 'DT',
srcDt: 'http://localhost:10056/',
srcSm: 'http://localhost:10059/',
@@ -355,178 +450,12 @@ export default {
zoom0: 1
}
},
- lineMonitorRows: [
- {
- unitKey: 'longRail',
- name: '加工中心单元',
- status: '执行中',
- type: 'on',
- taskNo: 'TASK 1001',
- startPos: '01',
- endPos: '06',
- taskInfo: [
- { label: '配方号', value: 'RCP-01' },
- { label: '托盘', value: 'Pallet-A' }
- ],
- lineSignals: [
- { label: 'PCS运行', state: 'on' },
- { label: '允许接收', state: 'on' },
- { label: '自动模式', state: 'on' },
- { label: '急停', state: 'off' }
- ],
- handshakes: [
- { label: '启动', state: 'on' },
- { label: '准备好', state: 'on' },
- { label: '忙碌', state: 'on' },
- { label: '错误', state: 'off' },
- { label: '完成', state: 'off' }
- ]
- },
- {
- unitKey: 'shortRail',
- name: '车床加工单元',
- status: '准备就绪',
- type: 'ok',
- taskNo: 'TASK 1002',
- startPos: '03',
- endPos: '09',
- taskInfo: [
- { label: '配方号', value: 'RCP-09' },
- { label: '托盘', value: 'Pallet-C' }
- ],
- lineSignals: [
- { label: 'PCS运行', state: 'on' },
- { label: '允许接收', state: 'on' },
- { label: '自动模式', state: 'on' },
- { label: '急停', state: 'off' }
- ],
- handshakes: [
- { label: '启动', state: 'off' },
- { label: '准备好', state: 'on' },
- { label: '忙碌', state: 'off' },
- { label: '错误', state: 'off' },
- { label: '完成', state: 'off' }
- ]
- },
- {
- unitKey: 'joint',
- name: '插齿机单元',
- status: '异常中断',
- type: 'alarm',
- taskNo: 'TASK 1003',
- startPos: '02',
- endPos: '04',
- taskInfo: [
- { label: '配方号', value: 'RCP-11' },
- { label: '托盘', value: 'Pallet-B' }
- ],
- lineSignals: [
- { label: 'PCS运行', state: 'on' },
- { label: '允许接收', state: 'off' },
- { label: '自动模式', state: 'warn' },
- { label: '急停', state: 'off' }
- ],
- handshakes: [
- { label: '启动', state: 'on' },
- { label: '准备好', state: 'on' },
- { label: '忙碌', state: 'alarm' },
- { label: '错误', state: 'alarm' },
- { label: '完成', state: 'off' }
- ]
- },
- {
- unitKey: 'grinder',
- name: '磨床加工单元',
- status: '预制待命',
- type: 'ok',
- taskNo: 'TASK 1004',
- startPos: '05',
- endPos: '10',
- taskInfo: [
- { label: '配方号', value: 'RCP-18' },
- { label: '托盘', value: 'Pallet-D' }
- ],
- lineSignals: [
- { label: 'PCS运行', state: 'on' },
- { label: '允许接收', state: 'on' },
- { label: '自动模式', state: 'warn' },
- { label: '急停', state: 'off' }
- ],
- handshakes: [
- { label: '启动', state: 'off' },
- { label: '准备好', state: 'on' },
- { label: '忙碌', state: 'off' },
- { label: '错误', state: 'off' },
- { label: '完成', state: 'on' }
- ]
- }
- ],
- equipmentList: [
- { name: 'F500', target: '1202 件', actual: '1202 件', status: '运行中', type: 'on', tags: [{ text: '上料完成', type: 'ok' }, { text: '下料完成', type: 'ok' }] },
- { name: 'L2000LSY', target: '2206 件', actual: '2206 件', status: '待机', type: 'idle', tags: [{ text: '上料完成', type: 'ok' }, { text: '下料完成', type: 'ok' }] },
- { name: 'L2100SY', target: '3301 件', actual: '3298 件', status: '运行中', type: 'on', tags: [{ text: '上料完成', type: 'ok' }, { text: '下料完成', type: 'ok' }] },
- { name: '新日', target: '4108 件', actual: '4108 件', status: '运行中', type: 'on', tags: [{ text: '上料就绪', type: 'ok' }, { text: '下料就绪', type: 'ok' }] },
- { name: 'MAZAK', target: '-', actual: '-', status: '报警', type: 'alarm', tags: [{ text: '上料就绪', type: 'ok' }, { text: '下料就绪', type: 'ok' }] },
- { name: '磨床单元', target: '860 件', actual: '856 件', status: '待机', type: 'idle', tags: [{ text: '上料就绪', type: 'ok' }, { text: '砂轮就绪', type: 'ok' }] },
- { name: 'YKD5132H', target: '5104 件', actual: '5104 件', status: '待机', type: 'idle', tags: [{ text: '上料就绪', type: 'ok' }, { text: '下料就绪', type: 'ok' }] }
- ],
- assistList: [
- { name: '加工打标机', status: '待机', type: 'idle', tags: [{ text: '待机中', type: 'idle' }] },
- { name: '车床打标机', status: '打标中', type: 'on', tags: [{ text: '开机中', type: 'ok' }, { text: '打标中', type: 'on' }] },
- { name: '插齿打标机', status: '待机', type: 'idle', tags: [{ text: '开机中', type: 'ok' }, { text: '待机中', type: 'idle' }] },
- { name: '清洗机', status: '运行中', type: 'on', tags: [{ text: '上料就绪', type: 'ok' }, { text: '运行中', type: 'on' }] },
- { name: '测量仪', status: '报警', type: 'alarm', tags: [{ text: '错误', type: 'alarm' }, { text: '下料就绪', type: 'ok' }] }
- ],
- storageGroups: [
- {
- unitKey: 'longRail',
- name: '加工中心单元库区',
- total: 9,
- columns: 3,
- filled: 6,
- slots: this.createSlots(['S1', 'S2', 'S4', 'S6', 'S7', 'S9'], 9)
- },
- {
- unitKey: 'shortRail',
- name: '车床加工单元库区',
- total: 9,
- columns: 3,
- filled: 6,
- slots: this.createSlots(['S1', 'S3', 'S4', 'S6', 'S8', 'S9'], 9)
- },
- {
- unitKey: 'joint',
- name: '插齿机单元库区',
- total: 6,
- columns: 2,
- filled: 4,
- slots: this.createSlots(['S1', 'S3', 'S5', 'S6'], 6)
- },
- {
- unitKey: 'grinder',
- name: '磨床加工单元库区',
- total: 6,
- columns: 2,
- filled: 3,
- slots: this.createSlots(['S2', 'S4', 'S6'], 6)
- }
- ],
- alarmEvents: [
- { time: '10:26', device: 'MAZAK', level: '严重', type: 'alarm', message: 'ALARM=1,建议定位机床侧运行条件。' },
- { time: '10:22', device: '测量仪', level: '严重', type: 'alarm', message: '测量流程中断,等待复测确认。' },
- { time: '10:18', device: 'L2100SY', level: '警告', type: 'warn', message: '目标程序与实际程序不一致。' },
- { time: '10:12', device: '插齿机单元机器人', level: '警告', type: 'warn', message: '自动模式待恢复,需要人工确认。' },
- { time: '10:08', device: '清洗机', level: '警告', type: 'warn', message: '下料等待时间超过设定阈值。' },
- { time: '10:03', device: '车床加工单元', level: '提示', type: 'idle', message: '任务已接收,等待上游放行。' },
- { time: '09:58', device: '磨床加工单元', level: '提示', type: 'idle', message: '预制单元待接入,当前按模拟节拍展示。' }
- ],
- alarmFrequencyTop5: [
- { name: 'MAZAK', value: 12 },
- { name: '测量仪', value: 9 },
- { name: '插齿机单元', value: 7 },
- { name: 'L2100SY', value: 5 },
- { name: '磨床加工单元', value: 4 }
- ],
+ lineMonitorRows: this.getReservedLineMonitorRows(),
+ equipmentList: this.getReservedEquipmentRows(),
+ assistList: [],
+ storageGroups: this.getReservedStorageGroups(),
+ alarmEvents: this.getReservedAlarmEvents(),
+ alarmFrequencyTop5: this.getReservedAlarmStats(),
historyTicks: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
historyLegend: [
{ label: '关机', type: 'off' },
@@ -534,26 +463,35 @@ export default {
{ label: '待机', type: 'standby' },
{ label: '报警', type: 'alarm' }
],
- deviceHistoryList: [
- { name: '加工中心单元机器人', segments: [{ label: '待机', type: 'standby', start: 0, end: 3.4 }, { label: '运行', type: 'run', start: 3.4, end: 10.8 }, { label: '待机', type: 'standby', start: 10.8, end: 12.1 }, { label: '运行', type: 'run', start: 12.1, end: 20.4 }, { label: '关机', type: 'off', start: 20.4, end: 24 }] },
- { name: '车床加工单元机器人', segments: [{ label: '运行', type: 'run', start: 0, end: 5.2 }, { label: '待机', type: 'standby', start: 5.2, end: 8.8 }, { label: '运行', type: 'run', start: 8.8, end: 17.6 }, { label: '待机', type: 'standby', start: 17.6, end: 18.7 }, { label: '运行', type: 'run', start: 18.7, end: 24 }] },
- { name: '插齿机单元机器人', segments: [{ label: '关机', type: 'off', start: 0, end: 2.5 }, { label: '运行', type: 'run', start: 2.5, end: 9.4 }, { label: '报警', type: 'alarm', start: 9.4, end: 10.3 }, { label: '待机', type: 'standby', start: 10.3, end: 13.1 }, { label: '运行', type: 'run', start: 13.1, end: 24 }] },
- { name: '磨床加工单元', segments: [{ label: '关机', type: 'off', start: 0, end: 3.2 }, { label: '待机', type: 'standby', start: 3.2, end: 6.4 }, { label: '运行', type: 'run', start: 6.4, end: 15.8 }, { label: '待机', type: 'standby', start: 15.8, end: 17.2 }, { label: '运行', type: 'run', start: 17.2, end: 24 }] },
- { name: 'F500', segments: [{ label: '运行', type: 'run', start: 0, end: 7.8 }, { label: '待机', type: 'standby', start: 7.8, end: 9.2 }, { label: '运行', type: 'run', start: 9.2, end: 18.6 }, { label: '待机', type: 'standby', start: 18.6, end: 19.4 }, { label: '运行', type: 'run', start: 19.4, end: 24 }] },
- { name: 'L2100SY', segments: [{ label: '关机', type: 'off', start: 0, end: 4.1 }, { label: '运行', type: 'run', start: 4.1, end: 11.6 }, { label: '报警', type: 'alarm', start: 11.6, end: 12.8 }, { label: '运行', type: 'run', start: 12.8, end: 24 }] },
- { name: 'L2000LSY', segments: [{ label: '运行', type: 'run', start: 0, end: 6.6 }, { label: '待机', type: 'standby', start: 6.6, end: 7.8 }, { label: '运行', type: 'run', start: 7.8, end: 16.2 }, { label: '待机', type: 'standby', start: 16.2, end: 17.1 }, { label: '运行', type: 'run', start: 17.1, end: 24 }] },
- { name: '新日', segments: [{ label: '关机', type: 'off', start: 0, end: 3.8 }, { label: '运行', type: 'run', start: 3.8, end: 14.6 }, { label: '待机', type: 'standby', start: 14.6, end: 15.4 }, { label: '运行', type: 'run', start: 15.4, end: 24 }] },
- { name: 'MAZAK', segments: [{ label: '运行', type: 'run', start: 0, end: 8.7 }, { label: '报警', type: 'alarm', start: 8.7, end: 10.1 }, { label: '待机', type: 'standby', start: 10.1, end: 11.4 }, { label: '运行', type: 'run', start: 11.4, end: 24 }] },
- { name: 'YKD5132H', segments: [{ label: '关机', type: 'off', start: 0, end: 2.8 }, { label: '运行', type: 'run', start: 2.8, end: 12.2 }, { label: '待机', type: 'standby', start: 12.2, end: 13.5 }, { label: '运行', type: 'run', start: 13.5, end: 24 }] },
- { name: '清洗机', segments: [{ label: '关机', type: 'off', start: 0, end: 4.4 }, { label: '运行', type: 'run', start: 4.4, end: 9.7 }, { label: '待机', type: 'standby', start: 9.7, end: 13.6 }, { label: '运行', type: 'run', start: 13.6, end: 18.8 }, { label: '关机', type: 'off', start: 18.8, end: 24 }] },
- { name: '测量仪', segments: [{ label: '待机', type: 'standby', start: 0, end: 5.1 }, { label: '运行', type: 'run', start: 5.1, end: 13.3 }, { label: '待机', type: 'standby', start: 13.3, end: 14.2 }, { label: '运行', type: 'run', start: 14.2, end: 24 }] },
- { name: '加工中心单元打标机', segments: [{ label: '待机', type: 'standby', start: 0, end: 6.5 }, { label: '运行', type: 'run', start: 6.5, end: 11.2 }, { label: '待机', type: 'standby', start: 11.2, end: 15.8 }, { label: '运行', type: 'run', start: 15.8, end: 24 }] },
- { name: '车床加工单元打标机', segments: [{ label: '待机', type: 'standby', start: 0, end: 4.9 }, { label: '运行', type: 'run', start: 4.9, end: 12.6 }, { label: '待机', type: 'standby', start: 12.6, end: 13.4 }, { label: '运行', type: 'run', start: 13.4, end: 24 }] },
- { name: '插齿机单元打标机', segments: [{ label: '待机', type: 'standby', start: 0, end: 7.1 }, { label: '运行', type: 'run', start: 7.1, end: 10.9 }, { label: '待机', type: 'standby', start: 10.9, end: 16.5 }, { label: '运行', type: 'run', start: 16.5, end: 24 }] }
- ]
+ deviceHistoryList: this.getReservedDeviceHistoryRows()
}
},
computed: {
+ isSimulationMode() {
+ return this.currentMode === 'SM' || this.mode === 'SM'
+ },
+ showDashboardModules() {
+ return !this.isSimulationMode
+ },
+ detailTooltipStyle() {
+ const width = 430
+ const maxHeight = 470
+ const padding = 14
+ const left = Math.min(
+ Math.max(this.detailTooltip.x + 18, padding),
+ this.style.width - width - padding
+ )
+ const top = Math.min(
+ Math.max(this.detailTooltip.y + 18, padding),
+ this.style.height - maxHeight - padding
+ )
+ return {
+ left: `${left}px`,
+ top: `${top}px`,
+ width: `${width}px`,
+ maxHeight: `${maxHeight}px`
+ }
+ },
lineMonitorScrollList() {
return [...this.lineMonitorRows, ...this.lineMonitorRows]
},
@@ -625,6 +563,7 @@ export default {
this.srcDt = this.buildIframeUrl(config.dtIframe || this.srcDt, this.autoPlayCraftCode)
this.srcSm = this.buildIframeUrl(this.smIframeBase, this.autoPlayCraftCode, this.selectedAutoPlayRate)
this.titleText = config.titleText
+ this.switchMode('DT')
this.updateTime()
this.setScale()
this.$nextTick(this.initAlarmPieChart)
@@ -706,6 +645,40 @@ export default {
}
},
methods: {
+ showLineTooltip(row, event) {
+ this.detailTooltip.visible = true
+ this.detailTooltip.type = 'line'
+ this.detailTooltip.data = row
+ this.updateDetailTooltipPosition(event)
+ },
+ moveDetailTooltip(event) {
+ if (!this.detailTooltip.visible) return
+ this.updateDetailTooltipPosition(event)
+ },
+ hideDetailTooltip() {
+ if (!this.detailTooltip.visible) return
+ this.detailTooltip.visible = false
+ this.detailTooltip.data = null
+ },
+ updateDetailTooltipPosition(event) {
+ if (!event || !this.$el) return
+ const rect = this.$el.getBoundingClientRect()
+ const scaleX = rect.width ? this.style.width / rect.width : 1
+ const scaleY = rect.height ? this.style.height / rect.height : 1
+ this.detailTooltip.x = (event.clientX - rect.left) * scaleX
+ this.detailTooltip.y = (event.clientY - rect.top) * scaleY
+ },
+ getSignalStateText(state) {
+ const stateMap = {
+ on: '已满足',
+ ok: '正常',
+ warn: '待确认',
+ alarm: '异常',
+ idle: '待机',
+ off: '未触发'
+ }
+ return stateMap[state] || '--'
+ },
createSlots(filledNames, total) {
return Array.from({ length: total }, (_, index) => {
const name = `S${index + 1}`
@@ -716,6 +689,151 @@ export default {
}
})
},
+ getReservedLineMonitorRows() {
+ return [
+ {
+ unitKey: 'grinder',
+ name: '磨床加工单元',
+ status: '预留待机',
+ type: 'ok',
+ taskNo: 'TASK 预留',
+ startPos: 'S2',
+ endPos: 'S6',
+ isReserved: true,
+ taskInfo: [
+ { label: '配方号', value: 'RCP-GR-01' },
+ { label: '托盘类型', value: 'Pallet-G' }
+ ],
+ lineSignals: [
+ { label: 'PCS运行', state: 'on' },
+ { label: '允许接收', state: 'on' },
+ { label: '自动模式', state: 'warn' },
+ { label: '急停', state: 'off' }
+ ],
+ handshakes: [
+ { label: '启动', state: 'off' },
+ { label: '准备好', state: 'on' },
+ { label: '忙碌', state: 'off' },
+ { label: '错误', state: 'off' },
+ { label: '完成', state: 'on' }
+ ]
+ }
+ ]
+ },
+ getReservedEquipmentRows() {
+ return [
+ {
+ key: 'grinder',
+ name: '磨床加工单元',
+ status: '预留待机',
+ type: 'idle',
+ isReserved: true,
+ actual: '--',
+ tags: [
+ { text: '点位预留', type: 'warn' },
+ { text: '模拟展示', type: 'idle' }
+ ]
+ }
+ ]
+ },
+ getReservedStorageGroups() {
+ return [
+ {
+ unitKey: 'grinder',
+ name: '磨床加工单元库区',
+ total: 6,
+ columns: 2,
+ filled: 3,
+ isReserved: true,
+ slots: this.createSlots(['S2', 'S4', 'S6'], 6)
+ }
+ ]
+ },
+ getReservedAlarmEvents() {
+ return [
+ {
+ time: '--:--',
+ device: '磨床加工单元',
+ level: '预留',
+ type: 'warn',
+ isReserved: true,
+ message: '磨床报警点位预留,当前为模拟展示'
+ }
+ ]
+ },
+ getReservedAlarmStats() {
+ return [
+ { name: '磨床加工单元', value: 1, isReserved: true }
+ ]
+ },
+ getReservedDeviceHistoryRows() {
+ return [
+ {
+ key: 'grinder',
+ name: '磨床加工单元',
+ isReserved: true,
+ segments: [
+ { label: '关机', type: 'off', start: 0, end: 3.2 },
+ { label: '待机', type: 'standby', start: 3.2, end: 6.4 },
+ { label: '运行', type: 'run', start: 6.4, end: 15.8 },
+ { label: '待机', type: 'standby', start: 15.8, end: 17.2 },
+ { label: '运行', type: 'run', start: 17.2, end: 24 }
+ ]
+ }
+ ]
+ },
+ isNoDataResult(list) {
+ return !Array.isArray(list) ||
+ list.length === 0 ||
+ (list.length === 1 && list[0] && (list[0].result === '0' || list[0].result === 0))
+ },
+ isGrinderRecord(record) {
+ const text = [
+ record && record.unitKey,
+ record && record.key,
+ record && record.name,
+ record && record.device,
+ record && record.UnitKey,
+ record && record.DeviceKey,
+ record && record.UnitName,
+ record && record.DeviceName
+ ].filter(Boolean).join('|').toLowerCase()
+ return text.includes('grinder') || text.includes('磨床')
+ },
+ isReverseStatusDevice(record) {
+ const text = [
+ record && record.key,
+ record && record.name,
+ record && record.device,
+ record && record.DeviceKey,
+ record && record.DeviceName
+ ].filter(Boolean).join('|').toUpperCase()
+ return text.includes('新日') || text.includes('MAZAK') || text.includes('XINRI')
+ },
+ getReversedEquipmentStatus(record, status, type) {
+ if (!this.isReverseStatusDevice(record)) {
+ return { status, type }
+ }
+ if (type === 'alarm' || status === '报警') {
+ return { status: '待机', type: 'idle' }
+ }
+ if (type === 'idle' || status === '待机') {
+ return { status: '报警', type: 'alarm' }
+ }
+ return { status, type }
+ },
+ getReversedHistoryStatus(record, label, type) {
+ if (!this.isReverseStatusDevice(record)) {
+ return { label, type }
+ }
+ if (type === 'alarm' || label === '报警') {
+ return { label: '待机', type: 'standby' }
+ }
+ if (type === 'standby' || label === '待机') {
+ return { label: '报警', type: 'alarm' }
+ }
+ return { label, type }
+ },
getStorageSlotGridStyle(group) {
const columns = Number(group && group.columns) || 3
return {
@@ -729,11 +847,14 @@ export default {
this.ExecDatabase(data)
.then((response) => {
const list = response && response.data ? response.data : null
- if (!Array.isArray(list) || list.length === 0) return
- if (list.length === 1 && list[0] && (list[0].result === '0' || list[0].result === 0)) return
+ if (this.isNoDataResult(list)) {
+ this.storageGroups = this.getReservedStorageGroups()
+ return
+ }
this.applyStorageMatrixData(list)
})
.catch((error) => {
+ this.storageGroups = this.getReservedStorageGroups()
console.error('TV_物料库位矩阵_查询失败', error)
})
.then(() => {
@@ -751,6 +872,7 @@ export default {
name: this.formatStorageValue(item.UnitName || item.unitName),
total: Number(item.Total || item.total) || 0,
columns: Number(item.Columns || item.columns) || 3,
+ sortNo: Object.keys(groupMap).length + 1,
slots: []
}
}
@@ -770,11 +892,12 @@ export default {
groupMap[key].total = groupMap[key].total || groupMap[key].slots.length
groupMap[key].filled = groupMap[key].slots.filter(slot => slot.filled).length
})
- this.storageGroups = this.storageGroups.map((group) => {
- const nextGroup = groupMap[group.unitKey]
- if (!nextGroup) return group
- return Object.assign({}, group, nextGroup)
- })
+ const groups = Object.keys(groupMap)
+ .map(key => groupMap[key])
+ .sort((a, b) => a.sortNo - b.sortNo)
+ this.storageGroups = groups.some(group => this.isGrinderRecord(group))
+ ? groups
+ : groups.concat(this.getReservedStorageGroups())
},
formatStorageValue(value) {
return String(value == null ? '' : value).trim()
@@ -795,11 +918,18 @@ export default {
this.ExecDatabase(data)
.then((response) => {
const list = response && response.data ? response.data : null
- if (!Array.isArray(list) || list.length === 0) return
- if (list.length === 1 && list[0] && (list[0].result === '0' || list[0].result === 0)) return
+ if (this.isNoDataResult(list)) {
+ this.alarmEvents = this.getReservedAlarmEvents()
+ this.alarmFrequencyTop5 = this.getReservedAlarmStats()
+ this.$nextTick(this.initAlarmPieChart)
+ return
+ }
this.applyAlarmEventData(list)
})
.catch((error) => {
+ this.alarmEvents = this.getReservedAlarmEvents()
+ this.alarmFrequencyTop5 = this.getReservedAlarmStats()
+ this.$nextTick(this.initAlarmPieChart)
console.error('TV_报警异常事件_查询失败', error)
})
.then(() => {
@@ -822,13 +952,13 @@ export default {
events.push(this.createAlarmEventRow(item))
}
})
- if (events.length) {
- this.alarmEvents = events
- }
- if (stats.length) {
- this.alarmFrequencyTop5 = stats
- this.$nextTick(this.initAlarmPieChart)
- }
+ this.alarmEvents = events.some(event => this.isGrinderRecord(event))
+ ? events
+ : events.concat(this.getReservedAlarmEvents())
+ this.alarmFrequencyTop5 = stats.some(stat => this.isGrinderRecord(stat))
+ ? stats
+ : stats.slice(0, 4).concat(this.getReservedAlarmStats())
+ this.$nextTick(this.initAlarmPieChart)
},
createAlarmEventRow(item) {
const type = this.normalizeAlarmEventType(this.pickRecordValue(item, ['EventType', 'eventType']))
@@ -876,11 +1006,16 @@ export default {
this.ExecDatabase(data)
.then((response) => {
const list = response && response.data ? response.data : null
- if (!Array.isArray(list) || list.length === 0) return
- if (list.length === 1 && list[0] && (list[0].result === '0' || list[0].result === 0)) return
+ if (this.isNoDataResult(list)) {
+ this.equipmentList = this.getReservedEquipmentRows()
+ this.assistList = []
+ return
+ }
this.applyEquipmentStatusData(list)
})
.catch((error) => {
+ this.equipmentList = this.getReservedEquipmentRows()
+ this.assistList = []
console.error('TV_设备实时状态_查询失败', error)
})
.then(() => {
@@ -902,16 +1037,19 @@ export default {
})
processRows.sort((a, b) => a.sortNo - b.sortNo)
assistRows.sort((a, b) => a.sortNo - b.sortNo)
- if (processRows.length) {
- this.equipmentList = processRows
- }
- if (assistRows.length) {
- this.assistList = assistRows
- }
+ this.equipmentList = processRows.some(row => this.isGrinderRecord(row))
+ ? processRows
+ : processRows.concat(this.getReservedEquipmentRows())
+ this.assistList = assistRows
},
createEquipmentStatusRow(item) {
const tag1Text = this.formatDisplayValue(this.pickRecordValue(item, ['Tag1Text', 'tag1Text']))
const tag2Text = this.formatDisplayValue(this.pickRecordValue(item, ['Tag2Text', 'tag2Text']))
+ const key = this.formatDisplayValue(this.pickRecordValue(item, ['DeviceKey', 'deviceKey']))
+ const name = this.formatDisplayValue(this.pickRecordValue(item, ['DeviceName', 'deviceName']))
+ const statusText = this.formatDisplayValue(this.pickRecordValue(item, ['StatusText', 'statusText']))
+ const statusType = this.normalizeStatusPillType(this.pickRecordValue(item, ['StatusType', 'statusType']))
+ const displayStatus = this.getReversedEquipmentStatus({ key, name }, statusText, statusType)
const tags = [
{
text: tag1Text,
@@ -923,11 +1061,11 @@ export default {
}
].filter(tag => tag.text && tag.text !== '--')
return {
- key: this.formatDisplayValue(this.pickRecordValue(item, ['DeviceKey', 'deviceKey'])),
- name: this.formatDisplayValue(this.pickRecordValue(item, ['DeviceName', 'deviceName'])),
+ key,
+ name,
actual: this.formatDisplayValue(this.pickRecordValue(item, ['OutputText', 'outputText'])),
- status: this.formatDisplayValue(this.pickRecordValue(item, ['StatusText', 'statusText'])),
- type: this.normalizeStatusPillType(this.pickRecordValue(item, ['StatusType', 'statusType'])),
+ status: displayStatus.status,
+ type: displayStatus.type,
sortNo: Number(this.pickRecordValue(item, ['SortNo', 'sortNo'])) || 999,
updateTime: this.formatDisplayValue(this.pickRecordValue(item, ['UpdateTime', 'updateTime'])),
tags
@@ -944,11 +1082,14 @@ export default {
this.ExecDatabase(data)
.then((response) => {
const list = response && response.data ? response.data : null
- if (!Array.isArray(list) || list.length === 0) return
- if (list.length === 1 && list[0] && (list[0].result === '0' || list[0].result === 0)) return
+ if (this.isNoDataResult(list)) {
+ this.deviceHistoryList = this.getReservedDeviceHistoryRows()
+ return
+ }
this.applyDeviceHistoryData(list)
})
.catch((error) => {
+ this.deviceHistoryList = this.getReservedDeviceHistoryRows()
console.error('TV_设备运行履历_查询失败', error)
})
.then(() => {
@@ -968,7 +1109,7 @@ export default {
segments: []
}
}
- const segment = this.createDeviceHistorySegment(item)
+ const segment = this.createDeviceHistorySegment(item, rowMap[deviceKey])
if (segment.end > segment.start) {
rowMap[deviceKey].segments.push(segment)
}
@@ -980,16 +1121,19 @@ export default {
rows.forEach((row) => {
row.segments.sort((a, b) => a.start - b.start)
})
- if (rows.length) {
- this.deviceHistoryList = rows
- }
+ this.deviceHistoryList = rows.some(row => this.isGrinderRecord(row))
+ ? rows
+ : rows.concat(this.getReservedDeviceHistoryRows())
},
- createDeviceHistorySegment(item) {
+ createDeviceHistorySegment(item, device) {
const start = this.clampHistoryHour(this.pickRecordValue(item, ['StartHour', 'startHour']))
const end = this.clampHistoryHour(this.pickRecordValue(item, ['EndHour', 'endHour']))
+ const label = this.formatDisplayValue(this.pickRecordValue(item, ['StatusText', 'statusText']))
+ const type = this.normalizeHistoryStatusType(this.pickRecordValue(item, ['StatusType', 'statusType']))
+ const displayStatus = this.getReversedHistoryStatus(device, label, type)
return {
- label: this.formatDisplayValue(this.pickRecordValue(item, ['StatusText', 'statusText'])),
- type: this.normalizeHistoryStatusType(this.pickRecordValue(item, ['StatusType', 'statusType'])),
+ label: displayStatus.label,
+ type: displayStatus.type,
start,
end
}
@@ -1010,11 +1154,14 @@ export default {
this.ExecDatabase(data)
.then((response) => {
const list = response && response.data ? response.data : null
- if (!Array.isArray(list) || list.length === 0) return
- if (list.length === 1 && list[0] && (list[0].result === '0' || list[0].result === 0)) return
+ if (this.isNoDataResult(list)) {
+ this.lineMonitorRows = this.getReservedLineMonitorRows()
+ return
+ }
this.applyLineMonitorData(list)
})
.catch((error) => {
+ this.lineMonitorRows = this.getReservedLineMonitorRows()
console.error('TV_线体任务联动状态_查询失败', error)
})
.then(() => {
@@ -1029,12 +1176,18 @@ export default {
rowMap[row.unitKey] = row
}
})
- this.lineMonitorRows = this.lineMonitorRows.map((row) => {
- if (row.unitKey && rowMap[row.unitKey]) {
- return Object.assign({}, row, rowMap[row.unitKey])
- }
- return row
- })
+ const orderMap = {
+ longRail: 1,
+ shortRail: 2,
+ joint: 3,
+ grinder: 4
+ }
+ const rows = Object.keys(rowMap)
+ .map(key => rowMap[key])
+ .sort((a, b) => (orderMap[a.unitKey] || 99) - (orderMap[b.unitKey] || 99))
+ this.lineMonitorRows = rows.some(row => this.isGrinderRecord(row))
+ ? rows
+ : rows.concat(this.getReservedLineMonitorRows())
},
createLineMonitorRow(item) {
const getValue = (...keys) => {
@@ -1094,6 +1247,9 @@ export default {
if (!text || text.toUpperCase() === 'NULL') {
return '--'
}
+ if (text.replace(/\s/g, '') === 'PCS离线') {
+ return '加工未启动'
+ }
return text
},
formatTaskNo(value) {
@@ -1224,6 +1380,13 @@ export default {
},
switchMode(mode) {
this.currentMode = mode
+ if (mode === 'SM') {
+ this.hideDetailTooltip()
+ this.disposeAlarmPieChart()
+ }
+ if (mode === 'DT') {
+ this.$nextTick(this.initAlarmPieChart)
+ }
this.mode = ''
this.dmtIframeReady = false
if (this.iframeReadyTimer) {
@@ -1260,8 +1423,15 @@ export default {
this.simulationRateDialogVisible = false
this.switchMode('SM')
},
+ disposeAlarmPieChart() {
+ if (this.alarmPieChart) {
+ this.alarmPieChart.dispose()
+ this.alarmPieChart = null
+ }
+ },
initAlarmPieChart() {
- if (!this.$refs.alarmPieChart) {
+ if (!this.alarmFrequencyTop5.length || !this.$refs.alarmPieChart) {
+ this.disposeAlarmPieChart()
return
}
if (!this.alarmPieChart) {
@@ -1832,6 +2002,17 @@ body,
background: radial-gradient(circle at center, rgba(37, 111, 184, 0.24), rgba(4, 12, 28, 0.92));
}
+.qa-screen--simulation .leftLine,
+.qa-screen--simulation .rightLine {
+ display: none;
+}
+
+.main-content--simulation {
+ left: 0;
+ right: 0;
+ bottom: 0;
+}
+
.left-box {
position: absolute;
top: 0;
@@ -1994,6 +2175,299 @@ body,
background: rgba(111, 212, 255, 0.22);
}
+.empty-state {
+ flex: 1;
+ min-height: 0;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ color: rgba(180, 211, 230, 0.78);
+ border: 1px dashed rgba(114, 206, 255, 0.2);
+ background:
+ linear-gradient(180deg, rgba(24, 66, 104, 0.16), rgba(7, 20, 34, 0.24)),
+ rgba(2, 9, 18, 0.24);
+ font-size: 15px;
+ font-weight: 700;
+ letter-spacing: 0;
+}
+
+.reserved-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: max-content;
+ min-height: 16px;
+ padding: 1px 6px;
+ box-sizing: border-box;
+ color: #ffe8a8;
+ border: 1px solid rgba(245, 200, 76, 0.38);
+ background: rgba(101, 73, 14, 0.28);
+ box-shadow: 0 0 10px rgba(245, 200, 76, 0.14);
+ font-size: 10px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 1.2;
+ white-space: nowrap;
+}
+
+.reserved-badge--inline {
+ margin-left: 6px;
+ vertical-align: middle;
+}
+
+.detail-tooltip {
+ position: absolute;
+ z-index: 90;
+ padding: 12px;
+ box-sizing: border-box;
+ color: #eaf8ff;
+ border: 1px solid rgba(114, 232, 255, 0.46);
+ background:
+ linear-gradient(180deg, rgba(18, 48, 74, 0.94), rgba(4, 14, 27, 0.96)),
+ rgba(3, 13, 31, 0.94);
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.1),
+ inset 0 0 24px rgba(44, 143, 255, 0.18),
+ 0 14px 30px rgba(0, 0, 0, 0.48),
+ 0 0 24px rgba(80, 237, 255, 0.18);
+ pointer-events: none;
+ overflow: hidden;
+}
+
+.detail-tooltip::before,
+.detail-tooltip::after {
+ content: "";
+ position: absolute;
+ width: 22px;
+ height: 22px;
+ pointer-events: none;
+}
+
+.detail-tooltip::before {
+ left: 0;
+ top: 0;
+ border-left: 2px solid rgba(128, 234, 255, 0.86);
+ border-top: 2px solid rgba(128, 234, 255, 0.86);
+}
+
+.detail-tooltip::after {
+ right: 0;
+ bottom: 0;
+ border-right: 2px solid rgba(128, 234, 255, 0.58);
+ border-bottom: 2px solid rgba(128, 234, 255, 0.58);
+}
+
+.detail-tooltip__head {
+ min-width: 0;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 12px;
+ padding-bottom: 9px;
+ border-bottom: 1px solid rgba(114, 206, 255, 0.18);
+}
+
+.detail-tooltip__head > strong {
+ min-width: 0;
+ color: #fff;
+ font-size: 16px;
+ line-height: 1.35;
+ text-shadow: 0 0 12px rgba(114, 232, 255, 0.28);
+ word-break: break-all;
+}
+
+.detail-tooltip__status {
+ min-width: 76px;
+ transform: scale(0.94);
+ transform-origin: right top;
+}
+
+.detail-tooltip__grid {
+ margin-top: 10px;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 7px;
+}
+
+.detail-tooltip__kv {
+ min-width: 0;
+ padding: 7px 8px;
+ border: 1px solid rgba(114, 206, 255, 0.12);
+ background:
+ linear-gradient(90deg, rgba(44, 143, 255, 0.1), transparent 68%),
+ rgba(0, 0, 0, 0.24);
+}
+
+.detail-tooltip__kv--wide {
+ grid-column: span 3;
+}
+
+.detail-tooltip__kv span,
+.detail-tooltip__section > span {
+ display: block;
+ color: #91b5cd;
+ font-size: 11px;
+ line-height: 1.2;
+}
+
+.detail-tooltip__kv strong {
+ display: block;
+ margin-top: 4px;
+ color: #ffffff;
+ font-size: 13px;
+ line-height: 1.35;
+ word-break: break-all;
+}
+
+.detail-tooltip__section {
+ margin-top: 10px;
+}
+
+.detail-tooltip__signals {
+ margin-top: 6px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 6px;
+}
+
+.detail-tooltip__signals--handshake {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+
+.detail-tooltip__signal {
+ min-width: 0;
+ min-height: 26px;
+ display: grid;
+ grid-template-columns: 8px minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 7px;
+ box-sizing: border-box;
+ border: 1px solid rgba(160, 180, 194, 0.16);
+ background: rgba(36, 48, 60, 0.38);
+}
+
+.detail-tooltip__signal i {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: #5f7182;
+}
+
+.detail-tooltip__signal strong,
+.detail-tooltip__signal em {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: 11px;
+ font-style: normal;
+}
+
+.detail-tooltip__signal strong {
+ color: #d7eefb;
+}
+
+.detail-tooltip__signal em {
+ color: #9fc3dc;
+}
+
+.detail-tooltip__signal--on,
+.detail-tooltip__signal--ok {
+ color: #d9ffea;
+ border-color: rgba(36, 216, 139, 0.34);
+ background: rgba(12, 80, 54, 0.5);
+}
+
+.detail-tooltip__signal--on i,
+.detail-tooltip__signal--ok i {
+ background: var(--scada-green);
+ box-shadow: 0 0 8px rgba(36, 216, 139, 0.64);
+}
+
+.detail-tooltip__signal--warn {
+ color: #fff3bf;
+ border-color: rgba(245, 200, 76, 0.36);
+ background: rgba(101, 73, 14, 0.48);
+}
+
+.detail-tooltip__signal--warn i {
+ background: var(--scada-yellow);
+ box-shadow: 0 0 8px rgba(245, 200, 76, 0.58);
+}
+
+.detail-tooltip__signal--alarm {
+ color: #ffe3e6;
+ border-color: rgba(255, 82, 102, 0.44);
+ background: rgba(101, 18, 32, 0.52);
+}
+
+.detail-tooltip__signal--alarm i {
+ background: var(--scada-red);
+ box-shadow: 0 0 8px rgba(255, 82, 102, 0.62);
+}
+
+.detail-tooltip__timeline {
+ margin-top: 7px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 6px;
+}
+
+.detail-tooltip__time-row {
+ min-width: 0;
+ min-height: 28px;
+ display: grid;
+ grid-template-columns: 1fr 54px;
+ align-items: center;
+ gap: 8px;
+ padding: 5px 7px;
+ box-sizing: border-box;
+ border: 1px solid rgba(114, 206, 255, 0.14);
+ background:
+ linear-gradient(90deg, rgba(44, 143, 255, 0.08), transparent 62%),
+ rgba(0, 0, 0, 0.24);
+}
+
+.detail-tooltip__time-row > span {
+ color: #c9e7f5;
+ font-size: 12px;
+ white-space: nowrap;
+}
+
+.detail-tooltip__history-state {
+ justify-self: end;
+ min-width: 42px;
+ padding: 2px 5px;
+ box-sizing: border-box;
+ color: #c5d4df;
+ border: 1px solid rgba(160, 180, 194, 0.24);
+ background: rgba(36, 48, 60, 0.42);
+ font-size: 11px;
+ text-align: center;
+ white-space: nowrap;
+}
+
+.detail-tooltip__history-state--run {
+ color: #d9ffea;
+ border-color: rgba(36, 216, 139, 0.42);
+ background: rgba(12, 80, 54, 0.5);
+}
+
+.detail-tooltip__history-state--standby {
+ color: #fff3bf;
+ border-color: rgba(245, 200, 76, 0.38);
+ background: rgba(101, 73, 14, 0.48);
+}
+
+.detail-tooltip__history-state--alarm {
+ color: #ffe3e6;
+ border-color: rgba(255, 82, 102, 0.48);
+ background: rgba(101, 18, 32, 0.52);
+}
+
.left-title-header,
.right-title-header,
.center-title-header {
@@ -2189,6 +2663,7 @@ body,
flex-direction: column;
justify-content: space-between;
overflow: hidden;
+ cursor: help;
}
.monitor-card::before,
@@ -2214,6 +2689,7 @@ body,
align-items: flex-start;
justify-content: space-between;
gap: 10px;
+ font-size: 12px;
}
.monitor-title strong {
@@ -2231,6 +2707,10 @@ body,
font-weight: 700;
}
+.monitor-title .reserved-badge {
+ margin-top: 4px;
+}
+
.task-summary {
margin-top: 5px;
padding: 6px 8px;
@@ -2466,6 +2946,10 @@ body,
white-space: nowrap;
}
+.equipment-name .reserved-badge--inline {
+ transform: translateY(-1px);
+}
+
.equipment-program {
display: flex;
flex-direction: column;
@@ -2604,6 +3088,16 @@ body,
flex-shrink: 0;
}
+.storage-head strong {
+ min-width: 0;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
.slots {
flex: 1;
min-height: 0;
@@ -2692,6 +3186,13 @@ body,
font-size: 12px;
}
+.event-row--reserved {
+ border-color: rgba(245, 200, 76, 0.28);
+ background:
+ linear-gradient(90deg, rgba(245, 200, 76, 0.12), rgba(4, 14, 27, 0.72)),
+ rgba(5, 20, 44, 0.66);
+}
+
.event-row p,
.event-row strong {
margin: 0;
@@ -2699,6 +3200,12 @@ body,
line-height: 1.35;
}
+.event-row strong .reserved-badge--inline {
+ display: flex;
+ margin-left: 0;
+ margin-top: 1px;
+}
+
.event-row p {
overflow: hidden;
text-overflow: ellipsis;
@@ -2811,6 +3318,13 @@ body,
white-space: nowrap;
}
+.history-device .reserved-badge--inline {
+ margin-left: 0;
+ margin-top: 2px;
+ transform: scale(0.92);
+ transform-origin: left center;
+}
+
.history-scale {
height: 100%;
display: flex;
diff --git a/work/庆安数字孪生可视化看板说明书.docx b/work/庆安数字孪生可视化看板说明书.docx
new file mode 100644
index 0000000..8eec772
Binary files /dev/null and b/work/庆安数字孪生可视化看板说明书.docx differ
- {{ item.name }}
+
+ {{ item.name }}
+ 预留数据
+
+
+
+ {{ detailTooltip.data.name }}
+ {{ detailTooltip.data.status }}
+
+
+
+
+ 任务编号
+ {{ detailTooltip.data.taskNo }}
+
+
+ 起始位置
+ {{ detailTooltip.data.startPos }}
+
+
+ 结束位置
+ {{ detailTooltip.data.endPos }}
+
+
+ {{ item.label }}
+ {{ item.value }}
+
+
+ 线体状态
+
+
+
+
+
+ {{ signal.label }}
+ {{ getSignalStateText(signal.state) }}
+
+
+ 任务握手
+
+
+
+
+
+
+ {{ signal.label }}
+ {{ getSignalStateText(signal.state) }}
+
+