Files
QingAnVue2TV/src/views/DaLuJiaTV2.vue
DaLuJia Developer 2abd286d42 Initial commit: 大陆架零部件智能生产线数字孪生系统
- 实现TV1和TV2双大屏展示
- 集成ECharts数据可视化
- 实现实时数据刷新机制
- 添加刀具寿命管理模块
- 添加工单加工时长统计模块
- 添加加工中心7日工作时长统计模块
- 添加OEE分析、设备状态监控等功能
- 创建技术文档和使用说明书
2026-05-21 09:19:09 +08:00

2249 lines
65 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- eslint-disable vue/max-attributes-per-line -->
<template>
<div id="screen" class="allShow" :style="{
width: `${style.width}px`,
height: `${style.height}px`,
transform: `${style.transform}`,
}">
<button class="route-switch-btn" @click="switchToTV1">切换TV1</button>
<div class="title-png">
<span class="title-text">大陆架零部件智能生产线数字孪生系统</span>
<div class="df-header__right">
<div class="df-header__timebar">
<span class="df-header__year">{{ headerDateCn }}</span>
<span class="df-header__time">{{ headerTime }}</span>
</div>
</div>
<!-- <img style="
width: 200px;
height: 30px;
position: absolute;
left: 55px;
top: 20px;
" src="/png/logo.png" alt="东方理工大学logo" /> -->
</div>
<div class="leftLine"></div>
<div class="rightLine"></div>
<!-- <button class="button1" @click="togglePanelLayout" :style="buttonStyle"></button> -->
<div class="main-content">
<iframe
id="dmtIframe"
:src="iframeAddress"
style="
width: 100%;
height: 100%;
border: none;
filter: blur(10);
z-index: 100;
" />
<div v-show="!isPanelCollapsed" class="left-box">
<div class="realtimeTask-box">
<div class="left-title-header">
<span class="left-title-text">生产任务</span>
</div>
<div class="realtime-task-grid">
<div class="realtime-task-item">
<span class="realtime-task-label">订单号</span>
<span class="realtime-task-value">{{ realtimeTaskInfo.orderNo }}</span>
</div>
<div class="realtime-task-item">
<span class="realtime-task-label">物料号</span>
<span class="realtime-task-value">{{ realtimeTaskInfo.materialNo }}</span>
</div>
<div class="realtime-task-item">
<span class="realtime-task-label">数量</span>
<span class="realtime-task-value">{{ realtimeTaskInfo.quantity }}</span>
</div>
</div>
</div>
<!-- 工单加工时长统计模块 -->
<div class="component-maintenance-box">
<div class="left-title-header">
<span class="left-title-text">工单加工时长统计</span>
</div>
<div class="component-maintenance-body">
<div id="workOrderChart" class="component-maintenance-chart" />
</div>
</div>
<!-- 设备状态模块 -->
<div class="device-status-box">
<div class="left-title-header">
<span class="left-title-text">设备状态</span>
</div>
<div class="device-status-body">
<div class="device-status-table">
<div class="device-status-row device-status-row--header">
<div class="device-status-cell">工位</div>
<div class="device-status-cell">工位名称</div>
<div class="device-status-cell">手自动</div>
<div class="device-status-cell">状态</div>
</div>
<div v-for="(row, index) in deviceStatusList" :key="index" class="device-status-row">
<div class="device-status-cell">{{ row.station }}</div>
<div class="device-status-cell">{{ row.stationName }}</div>
<div class="device-status-cell">{{ row.mode }}</div>
<div class="device-status-cell">
<span class="device-status-pill" :class="`device-status-pill--${row.statusType}`">
{{ row.statusText }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-show="!isPanelCollapsed" class="right-box">
<!-- 加工中心7日工作时长统计模块 -->
<div class="daily-output-box">
<div class="right-title-header">
<span class="right-title-text">加工中心7日工作时长统计</span>
</div>
<div class="daily-output-body">
<div id="machiningCenterChart" class="daily-output-chart" />
</div>
</div>
<!-- 实时状态模块 -->
<div class="storage-status-box">
<div class="right-title-header">
<span class="right-title-text">实时状态</span>
</div>
<div class="storage-status-body now-status-body">
<div class="now-status-fixed-row">
<div class="now-status-fixed-label">设备状态</div>
<div class="now-status-indicators">
<div v-for="item in nowStatusIndicators" :key="item.label" class="now-status-indicator-item">
<span class="now-status-circle" :class="{ 'now-status-circle--active': item.active }" :style="{
backgroundColor: item.active ? getNowStatusCircleColor(item.label) : '#909399'
}"></span>
<span class="now-status-indicator-label">{{ item.label }}</span>
</div>
</div>
</div>
<div class="now-status-scroll-container">
<scroll
class="now-status-scroll-wrapper"
:data="nowStatusList"
:class-option="nowStatusScrollOption"
>
<div class="now-status-scroll-content">
<div v-for="item in nowStatusList" :key="item.key" class="now-status-row">
<div class="now-status-row-label">{{ item.label }}</div>
<div class="now-status-row-value">{{ item.value }}</div>
</div>
</div>
</scroll>
</div>
</div>
</div>
</div>
<!-- 设备运行履历模块 -->
<div v-show="!isPanelCollapsed" class="status-timeline-box">
<div class="status-timeline-header"></div>
<div class="bb-chart-container bb-chart-container--timeline">
<div id="dashboardDeviceStatus" class="bb-chart bb-chart--status" />
</div>
</div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import 'echarts-gl'
import scroll from 'vue-seamless-scroll'
export default {
name: 'DaLuJiaTV2',
components: { scroll },
data() {
return {
style: {
width: '1920',
height: '1080',
transform: 'scaleY(1) scaleX(1) translate(-50%, -50%)'
},
isPanelCollapsed: false,
workstationStatusLoading: false,
workstationStatusTimer: null,
equipmentRuntimeHistoryLoading: false,
equipmentRuntimeHistoryRefreshTimer: null,
nowStatusLoading: false,
nowStatusRefreshTimer: null,
nowStatusIndicators: [
{ label: '关机', active: false },
{ label: '待机', active: false },
{ label: '运行', active: false },
{ label: '报警', active: false }
],
nowStatusList: [
{ key: 'status', label: 'status', value: '待机' }
],
deviceStatusList: [
{
station: 'OP1010',
stationName: '3D打印机',
mode: '自动',
statusText: '运行',
statusType: 'running'
},
{
station: 'OP1010',
stationName: '加工单元',
mode: '自动',
statusText: '报警',
statusType: 'alarm'
},
{
station: 'OP1010',
stationName: '检测',
mode: '自动',
statusText: '待机',
statusType: 'standby'
},
{
station: 'OP1010',
stationName: '装配',
mode: '自动',
statusText: '运行',
statusType: 'running'
},
{
station: 'OP1010',
stationName: '打标',
mode: '自动',
statusText: '离线',
statusType: 'offline'
}
],
deviceStatusChart: null, // 设备运行状态图表
workOrderChart: null, // 工单加工时长统计图表
machiningCenterChart: null, // 加工中心7日工作时长统计图表
workOrderLoading: false,
workOrderRefreshTimer: null,
realtimeTaskLoading: false,
realtimeTaskRefreshTimer: null,
realtimeTaskInfo: {
orderNo: '--',
materialNo: '--',
quantity: '--'
},
// 加工中心7日工作时长数据
machiningCenterDates: [],
machiningCenterValues: [],
machiningCenterLoading: false,
machiningCenterRefreshTimer: null,
// 工单加工时长数据
workOrderNames: [],
workOrderHours: [],
date: new Date(),
iframe: '',
iframeAddress: '',
handleWindowResize: null,
lastWorkstationStatusSignature: ''
}
},
created() {
this.iframe = document.getElementById('dmtIframe')
this.iframeAddress = window.dt_Config?.iframeAddress
this.loadEquipmentRuntimeHistory() // 历史_工位设备状态每分钟记录_查询
this.loadNowStatusData() // 当前_设备数据_查询
this.loadWorkstationStatus() // 基础_工位_查询
this.setScale()
},
mounted() {
this.timer = setInterval(() => {
this.date = new Date()
}, 1000)
this.initEcharts()
this.startEquipmentRuntimeHistoryRefresh() // 每分钟刷新一次设备运行履历
this.startWorkOrderRefresh() // 每分钟刷新一次工单加工时长统计
this.startMachiningCenterRefresh() // 每分钟刷新一次加工中心7日工作时长统计
this.startWorkstationStatusRefresh() // 每2秒刷新一次设备状态调用存储过程基础_工位_查询
this.startNowStatusRefresh() // 每2秒刷新一次实时状态调用存储过程当前_设备数据_查询
this.startRealtimeTaskRefresh() // 每2秒刷新一次生产任务实时信息
this.setScale()
this.handleWindowResize = () => {
this.setScale()
if (this.machiningCenterChart) this.machiningCenterChart.resize()
if (this.workOrderChart) this.workOrderChart.resize()
if (this.deviceStatusChart) this.deviceStatusChart.resize()
}
window.addEventListener('resize', this.handleWindowResize)
},
beforeDestroy() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
if (this.machiningCenterRefreshTimer) {
clearInterval(this.machiningCenterRefreshTimer)
this.machiningCenterRefreshTimer = null
}
if (this.workstationStatusTimer) {
clearInterval(this.workstationStatusTimer)
this.workstationStatusTimer = null
}
if (this.nowStatusRefreshTimer) {
clearInterval(this.nowStatusRefreshTimer)
this.nowStatusRefreshTimer = null
}
if (this.equipmentRuntimeHistoryRefreshTimer) {
clearInterval(this.equipmentRuntimeHistoryRefreshTimer)
this.equipmentRuntimeHistoryRefreshTimer = null
}
if (this.workOrderRefreshTimer) {
clearInterval(this.workOrderRefreshTimer)
this.workOrderRefreshTimer = null
}
if (this.realtimeTaskRefreshTimer) {
clearInterval(this.realtimeTaskRefreshTimer)
this.realtimeTaskRefreshTimer = null
}
if (this.handleWindowResize) {
window.removeEventListener('resize', this.handleWindowResize)
this.handleWindowResize = null
}
if (this.deviceStatusChart) {
this.deviceStatusChart.dispose()
this.deviceStatusChart = null
}
if (this.workOrderChart) {
this.workOrderChart.dispose()
this.workOrderChart = null
}
if (this.machiningCenterChart) {
this.machiningCenterChart.dispose()
this.machiningCenterChart = null
}
},
computed: {
nowStatusScrollOption() {
return {
step: 0.5,
limitMoveNum: 6,
hoverStop: true,
direction: 1,
openTouch: false,
singleHeight: 40,
waitTime: 1000,
autoPlay: this.nowStatusList.length > 6
}
},
headerTime() {
const d = this.date instanceof Date ? this.date : new Date()
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
const ss = String(d.getSeconds()).padStart(2, '0')
return `${hh}:${mm}:${ss}`
},
headerDate() {
const d = this.date instanceof Date ? this.date : new Date()
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}/${m}/${day}`
},
headerDateCn() {
const d = this.date instanceof Date ? this.date : new Date()
const y = d.getFullYear()
const m = d.getMonth() + 1
const day = d.getDate()
return `${y}-${m}-${day}`
}
},
methods: {
switchToTV1() {
if (this.$route.path !== '/') {
this.$router.push('/')
}
},
getScale() {
const w = window.innerWidth / this.style.width
const h = window.innerHeight / this.style.height
return { x: w, y: h }
},
// 实时状态模块:数据库定时刷新
startNowStatusRefresh() {
if (this.nowStatusRefreshTimer) {
clearInterval(this.nowStatusRefreshTimer)
this.nowStatusRefreshTimer = null
}
this.nowStatusRefreshTimer = setInterval(() => {
this.loadNowStatusData()
}, 2000)
this.loadNowStatusData()
},
getNowStatusCircleColor(label) {
const colorMap = {
关机: 'grey',
待机: 'orange',
运行: 'green',
报警: 'red'
}
return colorMap[label] || 'grey'
},
updateNowStatusIndicator(statusCode) {
this.nowStatusIndicators.forEach((item) => {
item.active = false
})
const statusMap = {
'-1': 0,
0: 1,
1: 2,
2: 3
}
const statusIndex = statusMap[String(statusCode).trim()]
if (statusIndex !== undefined && this.nowStatusIndicators[statusIndex]) {
this.nowStatusIndicators[statusIndex].active = true
}
},
mapNowStatusText(statusCode) {
const textMap = {
'-1': '关机',
0: '待机',
1: '运行',
2: '报警'
}
return textMap[String(statusCode).trim()] || String(statusCode || '--').trim()
},
applyNowStatusData(tagList) {
const list = Array.isArray(tagList) ? tagList : []
const statusRow = list.find((item) => {
const tagName = item && (item.TagName != null ? item.TagName : item['TagName'])
return String(tagName || '').trim() === '设备状态'
})
const statusValue = statusRow && (statusRow.TagValue != null ? statusRow.TagValue : statusRow['TagValue'])
this.updateNowStatusIndicator(statusValue)
this.nowStatusList = list
.filter((item) => {
const tagName = item && (item.TagName != null ? item.TagName : item['TagName'])
return String(tagName || '').trim() !== '设备状态'
})
.map((item, index) => {
const tagName = item && (item.TagName != null ? item.TagName : item['TagName'])
const rawValue = item && (item.TagValue != null ? item.TagValue : item['TagValue'])
return {
key: `${String(tagName || 'tag')}-${index}`,
label: String(tagName || '--'),
value: String(rawValue != null ? rawValue : '--')
}
})
},
loadNowStatusData() {
if (this.nowStatusLoading) return
this.nowStatusLoading = true
const param = []
const data = this.CreateData('11', '当前_设备数据_查询', param)
this.ExecDatabase(data)
.then((response) => {
const list = response && response.data ? response.data : null
if (Array.isArray(list)) {
this.applyNowStatusData(list)
} else {
this.nowStatusList = []
}
})
.catch((err) => {
console.error('实时状态数据获取失败', err)
this.nowStatusList = []
})
.finally(() => {
this.nowStatusLoading = false
})
},
setScale() {
const scale = this.getScale()
this.style.transform =
'scaleY(' + scale.y + ') scaleX(' + scale.x + ') translate(-50%, -50%)'
},
startToolUseTimeRefresh() {
if (this.toolUseTimeRefreshTimer) {
clearInterval(this.toolUseTimeRefreshTimer)
this.toolUseTimeRefreshTimer = null
}
this.toolUseTimeRefreshTimer = setInterval(() => {
this.loadToolUseTimeData()
}, 60 * 1000)
},
startRealtimeTaskRefresh() {
if (this.realtimeTaskRefreshTimer) {
clearInterval(this.realtimeTaskRefreshTimer)
this.realtimeTaskRefreshTimer = null
}
this.realtimeTaskRefreshTimer = setInterval(() => {
this.loadRealtimeTaskData()
}, 2000)
this.loadRealtimeTaskData()
},
loadRealtimeTaskData() {
if (this.realtimeTaskLoading) return
this.realtimeTaskLoading = true
const param = []
const data = this.CreateData('11', '接口_生产任务_实时_查询', param)
this.ExecDatabase(data)
.then((response) => {
const list = response && response.data ? response.data : null
const row = Array.isArray(list) && list.length > 0 ? list[0] : null
this.realtimeTaskInfo = {
orderNo: row && row['计划号'] != null ? String(row['计划号']) : '--',
materialNo: row && row['物料号'] != null ? String(row['物料号']) : '--',
quantity: row && row['数量'] != null ? String(row['数量']) : '--'
}
})
.catch((err) => {
console.error('接口_生产任务_实时_查询失败', err)
this.realtimeTaskInfo = {
orderNo: '--',
materialNo: '--',
quantity: '--'
}
})
.finally(() => {
this.realtimeTaskLoading = false
})
},
togglePanelLayout() {
this.isPanelCollapsed = !this.isPanelCollapsed
},
initEcharts() {
// 设备运行履历甘特图
this.deviceStatusChart = echarts.init(
document.getElementById('dashboardDeviceStatus'),
)
// 工单加工时长统计柱状图
const workOrderEl = document.getElementById('workOrderChart')
if (workOrderEl) {
this.workOrderChart = echarts.init(workOrderEl)
this.loadWorkOrderData()
}
// 加工中心7日工作时长统计柱状图
const machiningCenterEl = document.getElementById('machiningCenterChart')
if (machiningCenterEl) {
this.machiningCenterChart = echarts.init(machiningCenterEl)
this.initMachiningCenterData()
this.drawMachiningCenterChart()
this.loadMachiningCenterHistory()
}
},
openModelInfoPopup({ left, top, ModelId, ModelName }) {
this.modelInfoPopup.visible = true
this.modelInfoPopup.loading = true
this.modelInfoPopup.error = ''
this.modelInfoPopup.base.ModelId = ModelId || ''
this.modelInfoPopup.base.ModelName = ModelName || ''
this.modelInfoPopup.detail = null
this.modelInfoPopup.left = Number(left) || 0
this.modelInfoPopup.top = Number(top) || 0
this.$nextTick(() => {
this.fixModelInfoPopupPosition()
})
this.fetchModelInfoPopupDetail(ModelId)
},
closeModelInfoPopup() {
this.modelInfoPopup.visible = false
this.modelInfoPopup.loading = false
this.modelInfoPopup.error = ''
this.modelInfoPopup.detail = null
},
fixModelInfoPopupPosition() {
const el = this.$refs.modelInfoPopupRef
if (!el) return
const rect = el.getBoundingClientRect()
const margin = 10
let left = this.modelInfoPopup.left
let top = this.modelInfoPopup.top
const maxLeft = window.innerWidth - rect.width - margin
const maxTop = window.innerHeight - rect.height - margin
left = Math.max(margin, Math.min(left, maxLeft))
top = Math.max(margin, Math.min(top, maxTop))
this.modelInfoPopup.left = left
this.modelInfoPopup.top = top
},
// 设备状态模块每秒刷新一次调用存储过程基础_工位_查询
startWorkstationStatusRefresh() {
if (this.workstationStatusTimer) {
clearInterval(this.workstationStatusTimer)
this.workstationStatusTimer = null
}
this.workstationStatusTimer = setInterval(() => {
this.loadWorkstationStatus()
}, 2000)
},
loadWorkstationStatus() {
if (this.workstationStatusLoading) return
this.workstationStatusLoading = true
const param = [] // 无入参
const data = this.CreateData('11', '基础_工位_查询', param)
this.ExecDatabase(data)
.then((response) => {
const list = response && response.data ? response.data : null
if (!Array.isArray(list)) return
const mapStatus = (code) => {
const c = Number(code)
if (c === -1) return { statusText: '离线', statusType: 'offline' }
if (c === 0) return { statusText: '待机', statusType: 'standby' }
if (c === 1) return { statusText: '运行', statusType: 'running' }
if (c === 2) return { statusText: '报警', statusType: 'alarm' }
return { statusText: '未知', statusType: 'offline' }
}
const nextDeviceStatusList = list.map((item) => {
const station =
item && (item['工位号'] != null ? String(item['工位号']) : '')
const stationName =
item &&
(item['工位名称'] != null ? String(item['工位名称']) : '')
const statusCode =
item && (item['状态代码'] != null ? item['状态代码'] : null)
const st = mapStatus(statusCode)
return {
station,
stationName,
mode: '自动',
statusText: st.statusText,
statusType: st.statusType
}
})
const signature = JSON.stringify(nextDeviceStatusList)
if (signature !== this.lastWorkstationStatusSignature) {
this.lastWorkstationStatusSignature = signature
this.deviceStatusList = nextDeviceStatusList
}
})
.catch((err) => {
console.error('基础_工位_查询失败', err)
})
.then(() => {
this.workstationStatusLoading = false
})
},
// 每分钟刷新一次日产量历史数据
startDailyOutputHistoryRefresh() {
if (this.dailyOutputRefreshTimer) {
clearInterval(this.dailyOutputRefreshTimer)
this.dailyOutputRefreshTimer = null
}
this.dailyOutputRefreshTimer = setInterval(() => {
this.loadDailyOutputHistory()
}, 60 * 1000)
},
// 初始化日产量数据(首次为空,由存储过程填充)
initDailyOutputData() {
this.dailyOutputDates = []
this.dailyOutputValues = []
},
// 拉取日产量历史数据调用存储过程历史_过站记录_日产量查询
loadDailyOutputHistory() {
if (this.dailyOutputLoading) return
if (!this.dailyOutputChart) return
this.dailyOutputLoading = true
const param4 = [] // 无入参
const Data4 = this.CreateData('11', '历史_过站记录_日产量查询', param4)
this.ExecDatabase(Data4)
.then((response) => {
const list = response && response.data ? response.data : null
if (!Array.isArray(list) || list.length === 0) {
// 查询不到数据:清空数据,在图表中显示“暂无最近七日产量数据”
this.dailyOutputDates = []
this.dailyOutputValues = []
this.drawDailyOutputChart()
return
}
// 存储过程按“日期 + 工位号”分组,这里忽略工位号,按日期汇总近七日产量
const parseDate = (rawDate) => {
if (rawDate == null) return null
// 优先从字符串中直接截取 YYYY-MM-DD避免时区造成的日期偏移
if (typeof rawDate === 'string') {
const m = rawDate.match(/^(\d{4})[-\/.](\d{1,2})[-\/.](\d{1,2})/)
if (m) {
const y = String(m[1]).padStart(4, '0')
const mo = String(m[2]).padStart(2, '0')
const da = String(m[3]).padStart(2, '0')
const key = `${y}-${mo}-${da}`
return {
key,
md: `${mo}-${da}`,
time: new Date(`${key}T00:00:00`).getTime()
}
}
}
const dt = rawDate instanceof Date ? rawDate : new Date(rawDate)
if (!dt || Number.isNaN(dt.getTime())) return null
const y = String(dt.getFullYear()).padStart(4, '0')
const mo = String(dt.getMonth() + 1).padStart(2, '0')
const da = String(dt.getDate()).padStart(2, '0')
const key = `${y}-${mo}-${da}`
return {
key,
md: `${mo}-${da}`,
time: new Date(`${key}T00:00:00`).getTime()
}
}
const sumByDate = new Map() // key: YYYY-MM-DD -> { md, time, sum }
list.forEach((item) => {
const rawDate =
item && (item['日期'] != null ? item['日期'] : item.日期)
const rawValue =
item && (item['产量'] != null ? item['产量'] : item.产量)
const d = parseDate(rawDate)
if (!d) return
const num = Number(rawValue)
if (!Number.isFinite(num)) return
const prev = sumByDate.get(d.key)
if (prev) {
prev.sum += num
} else {
sumByDate.set(d.key, { md: d.md, time: d.time, sum: num })
}
})
const sorted = Array.from(sumByDate.values())
.sort((a, b) => a.time - b.time)
.slice(-7)
const dates = sorted.map((d) => d.md)
const values = sorted.map((d) => Math.round(Number(d.sum) || 0))
this.dailyOutputDates = dates
this.dailyOutputValues = values
this.drawDailyOutputChart()
})
.catch((err) => {
console.error('日产量历史查询失败', err)
this.dailyOutputDates = []
this.dailyOutputValues = []
this.drawDailyOutputChart()
})
.then(() => {
this.dailyOutputLoading = false
})
},
// 绘制日产量折线图
drawDailyOutputChart() {
if (!this.dailyOutputChart) return
const data = this.dailyOutputValues || []
const dates = this.dailyOutputDates || []
// 先清空,避免上一次的图形或文字残留
this.dailyOutputChart.clear()
// 如果没有任何数据,就只显示“暂无最近七日产量数据”提示
if (!data.length || !dates.length) {
const optionEmpty = {
backgroundColor: 'transparent',
animation: false,
xAxis: { show: false, type: 'category', data: [] },
yAxis: { show: false, type: 'value' },
series: [],
graphic: {
type: 'text',
left: 'center',
top: 'middle',
style: {
text: '暂无最近七日产量数据',
fill: '#fff',
fontSize: 16,
fontFamily: 'Microsoft YaHei'
}
}
}
this.dailyOutputChart.setOption(optionEmpty)
return
}
const maxValue = Math.max.apply(null, data)
const maxIndex = data.indexOf(maxValue)
const lineColor = '#4fc3e8'
const pointColor = '#dff8ff'
const topAreaColor = 'rgba(79, 195, 232, 0.30)'
const option = {
backgroundColor: 'transparent',
animation: false,
grid: {
top: '18%',
bottom: '11%',
left: '8%',
right: '8%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
lineStyle: {
color: 'rgba(79, 195, 232, 0.45)'
}
},
formatter: (params) => {
const p = params && params[0]
if (!p) return ''
return `${p.axisValue}<br/>日产量:${p.value}`
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: dates,
axisLabel: {
color: '#fff',
fontSize: 10
},
axisLine: {
show: true,
lineStyle: { color: '#1B3F66' }
},
axisTick: { show: false }
},
yAxis: {
type: 'value',
axisLabel: {
color: '#fff',
fontSize: 10
},
axisLine: {
show: true,
lineStyle: { color: '#1B3F66' }
},
splitLine: {
lineStyle: { color: 'rgba(27, 63, 102, 0.6)' }
}
},
graphic: [],
series: [
{
type: 'line',
name: '日产量',
smooth: false,
showSymbol: true,
symbol: 'circle',
symbolSize: 5,
data: data,
lineStyle: {
color: lineColor,
width: 1
},
itemStyle: {
color: pointColor,
borderColor: lineColor,
borderWidth: 2
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: topAreaColor },
{ offset: 1, color: 'rgba(79, 195, 232, 0)' }
]
}
},
markPoint: {
symbol: 'circle',
symbolSize: 5,
itemStyle: {
color: pointColor,
borderColor: lineColor,
borderWidth: 2
},
label: {
show: true,
position: 'top',
distance: 10,
color: '#dff8ff',
fontSize: 12,
fontWeight: 'bold',
formatter: 'Max:{c}'
},
data: [
{
coord: [dates[maxIndex], maxValue],
value: maxValue
}
]
},
emphasis: {
focus: 'series'
}
}
]
}
this.dailyOutputChart.setOption(option)
},
getMaintenanceAxisMax(values) {
const maxVal = Math.max(...values, 1)
const roughStep = maxVal / 5
const pow10 = Math.pow(10, Math.floor(Math.log10(roughStep)))
const normalized = roughStep / pow10
let step
if (normalized <= 1) step = 1 * pow10
else if (normalized <= 2) step = 2 * pow10
else if (normalized <= 5) step = 5 * pow10
else step = 10 * pow10
return Math.ceil(maxVal / step) * step
},
// 每分钟刷新一次设备运行履历调用存储过程历史_工位设备状态每分钟记录_查询
startEquipmentRuntimeHistoryRefresh() {
if (this.equipmentRuntimeHistoryRefreshTimer) {
clearInterval(this.equipmentRuntimeHistoryRefreshTimer)
this.equipmentRuntimeHistoryRefreshTimer = null
}
this.equipmentRuntimeHistoryRefreshTimer = setInterval(() => {
this.loadEquipmentRuntimeHistory()
}, 60 * 1000)
},
// 查询设备运行履历调用存储过程历史_工位设备状态每分钟记录_查询过去24小时
loadEquipmentRuntimeHistory() {
if (this.equipmentRuntimeHistoryLoading) return
this.equipmentRuntimeHistoryLoading = true
const endTime = new Date()
const startTime = new Date(endTime.getTime() - 24 * 60 * 60 * 1000) // 近24小时
const fmt = (d) => this.formatDateTime(d)
const param4 = [
['开始时间', fmt(startTime)],
['结束时间', fmt(endTime)],
['记录条数', '2000']
]
const Data4 = this.CreateData(
'11',
'历史_工位设备状态每分钟记录_查询',
param4,
)
this.ExecDatabase(Data4)
.then((response) => {
const list = response.data
if (Array.isArray(list) && list.length > 0) {
this.processEquipmentRuntimeData(list)
} else {
this.deviceStatusTable = []
}
this.drawDeviceStatusChart()
})
.catch((err) => {
console.error('设备运行履历数据获取失败', err)
this.deviceStatusTable = []
this.drawDeviceStatusChart()
})
.finally(() => {
this.equipmentRuntimeHistoryLoading = false
})
},
// 处理设备运行履历数据
processEquipmentRuntimeData(records) {
this.deviceStatusTable = records.map((record) => ({
工位号: record.工位号 || '设备1',
设备状态: record.设备状态,
创建时间: record.创建时间
}))
},
// 格式化日期时间
formatDateTime(date) {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hours = date.getHours().toString().padStart(2, '0')
const minutes = date.getMinutes().toString().padStart(2, '0')
const seconds = date.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
},
drawDeviceStatusChart() {
if (!this.deviceStatusChart) {
return
}
const hasData =
Array.isArray(this.deviceStatusTable) &&
this.deviceStatusTable.length > 0
if (!hasData) {
this.deviceStatusChart.clear()
this.deviceStatusChart.setOption(
{
backgroundColor: 'transparent',
xAxis: { show: false, type: 'time' },
yAxis: { show: false, type: 'category', data: [] },
series: [],
graphic: {
type: 'text',
left: 'center',
top: 'middle',
style: {
text: '未查询到设备运行履历',
fill: '#fff',
fontSize: 16,
fontFamily: 'Microsoft YaHei'
}
}
},
true,
)
return
}
// 设备状态映射
const statusMap = {
'-1': {
name: '离线',
color: '#878787'
},
0: {
name: '待机',
color: '#d49436'
},
1: {
name: '运行',
color: '#2d8f47'
},
2: {
name: '报警',
color: '#c23b48'
}
}
// 处理数据,按工位号分组并按时间排序
const groupedData = {}
this.deviceStatusTable.forEach((item) => {
const workstation = item.工位号 || 'OP10'
if (!groupedData[workstation]) {
groupedData[workstation] = []
}
groupedData[workstation].push(item)
})
// 对每组数据按时间排序
Object.keys(groupedData).forEach((workstation) => {
groupedData[workstation].sort(
(a, b) =>
new Date(a.创建时间).getTime() - new Date(b.创建时间).getTime(),
)
})
// 构建图表数据 - 为每个状态创建一个系列
const seriesData = []
const yAxisData = Object.keys(groupedData)
// 为每个工位和状态创建数据
yAxisData.forEach((workstation, yIndex) => {
const records = groupedData[workstation]
records.forEach((record, index) => {
const statusInfo = statusMap[String(record.设备状态)] || {
name: '未知',
color: '#909399'
}
// 计算结束时间
let endTime
if (index === records.length - 1) {
const startTime = new Date(record.创建时间)
endTime = new Date(startTime.getTime() + 60000) // 默认1分钟
} else {
endTime = new Date(records[index + 1].创建时间)
}
const startTime = new Date(record.创建时间)
// 确保开始时间小于结束时间
if (startTime.getTime() < endTime.getTime()) {
seriesData.push({
name: statusInfo.name,
value: [
workstation, // y轴类别
startTime.getTime(), // 开始时间
endTime.getTime(), // 结束时间
statusInfo.name // 状态名称
],
itemStyle: {
color: statusInfo.color,
shadowBlur: 8,
shadowColor: statusInfo.shadowColor || 'rgba(0, 0, 0, 0.25)',
shadowOffsetX: 0,
shadowOffsetY: 0
}
})
}
})
})
// 创建配置选项
const option = {
backgroundColor: 'transparent',
animation: false,
tooltip: {
trigger: 'item',
formatter: function(params) {
const data = params.value
const startTime = new Date(data[1])
const endTime = new Date(data[2])
const startStr = `${startTime
.getHours()
.toString()
.padStart(2, '0')}:${startTime
.getMinutes()
.toString()
.padStart(2, '0')}`
const endStr = `${endTime
.getHours()
.toString()
.padStart(2, '0')}:${endTime
.getMinutes()
.toString()
.padStart(2, '0')}`
return `${data[3]}<br/>${data[0]}<br/>${startStr} ~ ${endStr}`
},
backgroundColor: 'rgba(0, 0, 0, 0.8)',
textStyle: { color: '#fff' },
borderColor: 'rgba(255, 255, 255, 0.3)',
borderWidth: 1,
confine: true,
position: function(point, params, dom, rect, size) {
const mouseX = point[0]
const mouseY = point[1]
const tooltipWidth = size.contentSize[0]
const tooltipHeight = size.contentSize[1]
const chartWidth = size.viewSize[0]
const chartHeight = size.viewSize[1]
// 默认显示在鼠标上方
let x = mouseX - tooltipWidth / 2
let y = mouseY - tooltipHeight - 10
// 水平边界检查
x = Math.max(5, Math.min(x, chartWidth - tooltipWidth - 5))
// 垂直边界检查 - 确保不超出顶部
if (y < 0) {
// 上方放不下,放到鼠标下方
y = mouseY + 20 // 增加下方偏移量,避免紧贴鼠标
}
// 检查是否超出底部
if (y + tooltipHeight > chartHeight) {
// 超出底部,尝试放回上方
y = mouseY - tooltipHeight - 20
// 如果上方也放不下,就放在图表顶部,留出边距
if (y < 0) {
y = 10 // 固定在顶部留10px边距
}
}
// 最终检查确保整个tooltip都在可视区域内
y = Math.max(5, Math.min(y, chartHeight - tooltipHeight - 5))
return [x, y]
}
},
grid: {
left: '2%',
right: '5%',
bottom: '8%',
top: '7%',
containLabel: true
},
xAxis: {
type: 'time',
axisLine: { show: false, lineStyle: { color: '#fff' }},
axisLabel: {
show: true,
color: '#fff',
fontSize: 15,
formatter: function(value) {
const date = new Date(value)
return `${date.getHours().toString().padStart(2, '0')}:${date
.getMinutes()
.toString()
.padStart(2, '0')}`
}
},
splitLine: { lineStyle: { color: 'rgba(255, 255, 255, 0.1)' }}
},
yAxis: {
type: 'category',
data: yAxisData,
axisLine: { show: false, lineStyle: { color: '#fff' }},
axisLabel: { color: '#fff', fontSize: 15, show: true },
splitLine: { show: false }
},
series: [
{
type: 'custom',
name: '设备状态',
renderItem: function(params, api) {
try {
const categoryIndex = api.value(0)
const start = api.coord([api.value(1), categoryIndex])
const end = api.coord([api.value(2), categoryIndex])
if (!start || !end || start[0] === end[0]) {
return null
}
const height = api.size([0, 1])[1] * 0.6
return {
type: 'rect',
shape: {
x: start[0],
y: start[1] - height / 2,
width: Math.max(end[0] - start[0], 1),
height: height
},
style: api.style()
}
} catch (error) {
return null
}
},
encode: {
x: [1, 2],
y: 0
},
data: seriesData
}
]
}
this.deviceStatusChart.setOption(option, true)
},
// 每分钟刷新一次工单加工时长统计
startWorkOrderRefresh() {
if (this.workOrderRefreshTimer) {
clearInterval(this.workOrderRefreshTimer)
this.workOrderRefreshTimer = null
}
this.workOrderRefreshTimer = setInterval(() => {
this.loadWorkOrderData()
}, 60000)
},
loadWorkOrderData() {
if (this.workOrderLoading) return
if (!this.workOrderChart) return
const param = []
const data = this.CreateData('11', '历史_工位设备状态每分钟记录_工单查询', param)
this.workOrderLoading = true
this.ExecDatabase(data)
.then((response) => {
const list = response && response.data ? response.data : null
if (!Array.isArray(list) || list.length === 0) {
this.workOrderNames = []
this.workOrderHours = []
this.drawWorkOrderChart()
return
}
const names = []
const hours = []
list.forEach((item) => {
const orderNo = item && (item['订单号'] != null ? String(item['订单号']) : '')
const workTime = item && (item['工作时长'] != null ? Number(item['工作时长']) : 0)
if (orderNo) {
names.push(orderNo)
hours.push(Number.isFinite(workTime) ? workTime : 0)
}
})
this.workOrderNames = names
this.workOrderHours = hours
this.drawWorkOrderChart()
})
.catch((error) => {
console.error('工单加工时长统计数据获取失败', error)
this.workOrderNames = []
this.workOrderHours = []
this.drawWorkOrderChart()
})
.finally(() => {
this.workOrderLoading = false
})
},
drawWorkOrderChart() {
if (!this.workOrderChart) return
const names = this.workOrderNames || []
const hours = this.workOrderHours || []
this.workOrderChart.clear()
if (!names.length || !hours.length) {
this.workOrderChart.setOption({
backgroundColor: 'transparent',
xAxis: { show: false, type: 'category', data: [] },
yAxis: { show: false, type: 'value' },
series: [],
graphic: {
type: 'text',
left: 'center',
top: 'middle',
style: {
text: '未查询到工单数据',
fill: '#fff',
fontSize: 16,
fontFamily: 'Microsoft YaHei'
}
}
}, true)
return
}
const maxAxis = this.getMaintenanceAxisMax(hours)
const buildBarGradient = (startColor, middleColor, endColor) => ({
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 0,
colorStops: [
{ offset: 0, color: startColor },
{ offset: 0.55, color: middleColor },
{ offset: 1, color: endColor }
]
})
const option = {
backgroundColor: 'transparent',
animation: false,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
backgroundColor: 'rgba(0, 0, 0, 0.8)',
textStyle: { color: '#fff' },
borderColor: 'rgba(255, 255, 255, 0.3)',
borderWidth: 1,
formatter: function(params) {
const p = params && params[0]
if (!p) return ''
return `${p.name}<br/>工作时长: ${p.value}分钟`
}
},
grid: {
left: '2%',
right: '8%',
bottom: '12%',
top: '8%',
containLabel: true
},
xAxis: {
type: 'value',
max: maxAxis,
interval: maxAxis / 5,
axisLabel: {
color: '#ffffff',
formatter: '{value}m'
},
axisLine: {
show: false
},
splitLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.1)'
}
}
},
yAxis: {
type: 'category',
data: names,
inverse: true,
axisLabel: {
color: '#fff',
fontSize: 12,
interval: 0,
lineHeight: 16,
align: 'right',
formatter: function(value) {
if (!value) return ''
if (value.length > 8) {
const firstLine = value.slice(0, 8)
const secondLine = value.slice(8)
return `${firstLine}\n${secondLine}`
}
return value
}
},
axisLine: {
show: false
},
axisTick: {
alignWithLabel: true
}
},
series: [
{
type: 'bar',
name: '工作时长',
data: hours,
barWidth: 12,
itemStyle: {
borderRadius: [0, 2, 2, 0],
color: buildBarGradient(
'rgba(143, 201, 255, 0.10)',
'#4fc3e8',
'#dff8ff',
)
},
label: {
show: true,
position: 'right',
color: '#dff8ff',
fontSize: 11,
formatter: '{c}min'
}
}
]
}
this.workOrderChart.setOption(option, true)
},
// 每分钟刷新一次加工中心7日工作时长统计
startMachiningCenterRefresh() {
if (this.machiningCenterRefreshTimer) {
clearInterval(this.machiningCenterRefreshTimer)
this.machiningCenterRefreshTimer = null
}
this.machiningCenterRefreshTimer = setInterval(() => {
this.loadMachiningCenterHistory()
}, 60 * 1000)
},
initMachiningCenterData() {
this.machiningCenterDates = []
this.machiningCenterValues = []
},
loadMachiningCenterHistory() {
if (this.machiningCenterLoading) return
if (!this.machiningCenterChart) return
this.machiningCenterLoading = true
const param = []
const data = this.CreateData('11', '历史_工位设备状态每分钟记录_加工中心查询', param)
this.ExecDatabase(data)
.then((response) => {
const list = response && response.data ? response.data : null
if (!Array.isArray(list) || list.length === 0) {
this.machiningCenterDates = []
this.machiningCenterValues = []
this.drawMachiningCenterChart()
return
}
const parseDate = (rawDate) => {
if (rawDate == null) return null
if (typeof rawDate === 'string') {
const m = rawDate.match(/^(\d{4})[-\/.](\d{1,2})[-\/.](\d{1,2})/)
if (m) {
const mo = String(m[2]).padStart(2, '0')
const da = String(m[3]).padStart(2, '0')
return `${mo}-${da}`
}
}
const dt = rawDate instanceof Date ? rawDate : new Date(rawDate)
if (!dt || Number.isNaN(dt.getTime())) return null
const mo = String(dt.getMonth() + 1).padStart(2, '0')
const da = String(dt.getDate()).padStart(2, '0')
return `${mo}-${da}`
}
const dates = []
const values = []
list.forEach((item) => {
const rawDate = item && (item['日期'] != null ? item['日期'] : item.日期)
const rawValue = item && (item['工作时长'] != null ? item['工作时长'] : item.工作时长)
const dateStr = parseDate(rawDate)
if (!dateStr) return
const num = Number(rawValue)
if (!Number.isFinite(num)) return
dates.push(dateStr)
values.push(Math.round(num))
})
this.machiningCenterDates = dates
this.machiningCenterValues = values
this.drawMachiningCenterChart()
})
.catch((err) => {
console.error('加工中心7日工作时长查询失败', err)
this.machiningCenterDates = []
this.machiningCenterValues = []
this.drawMachiningCenterChart()
})
.finally(() => {
this.machiningCenterLoading = false
})
},
drawMachiningCenterChart() {
if (!this.machiningCenterChart) return
const data = this.machiningCenterValues || []
const dates = this.machiningCenterDates || []
this.machiningCenterChart.clear()
if (!data.length || !dates.length) {
const optionEmpty = {
backgroundColor: 'transparent',
animation: false,
xAxis: { show: false, type: 'category', data: [] },
yAxis: { show: false, type: 'value' },
series: [],
graphic: {
type: 'text',
left: 'center',
top: 'middle',
style: {
text: '暂无最近七日工作时长',
fill: '#fff',
fontSize: 16,
fontFamily: 'Microsoft YaHei'
}
}
}
this.machiningCenterChart.setOption(optionEmpty)
return
}
const option = {
backgroundColor: 'transparent',
animation: false,
grid: {
top: '18%',
bottom: '11%',
left: '18%',
right: '8%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: (params) => {
const p = params && params[0]
if (!p) return ''
return `${p.axisValue}<br/>工作时长:${p.value}分钟`
}
},
xAxis: {
type: 'category',
data: dates,
axisLabel: {
color: '#fff',
fontSize: 10
},
axisLine: {
show: true,
lineStyle: { color: '#1B3F66' }
},
axisTick: { show: false }
},
yAxis: {
type: 'value',
axisLabel: {
color: '#fff',
fontSize: 10,
formatter: '{value}分钟'
},
axisLine: {
show: true,
lineStyle: { color: '#1B3F66' }
},
splitLine: {
lineStyle: { color: 'rgba(27, 63, 102, 0.6)' }
}
},
series: [
{
type: 'bar',
name: '工作时长',
data: data,
barWidth: 20,
itemStyle: {
borderRadius: [2, 2, 0, 0],
color: {
type: 'linear',
x: 0,
y: 1,
x2: 0,
y2: 0,
colorStops: [
{ offset: 0, color: 'rgba(79, 195, 232, 0.10)' },
{ offset: 0.55, color: '#4fc3e8' },
{ offset: 1, color: '#dff8ff' }
]
}
},
label: {
show: true,
position: 'top',
color: '#dff8ff',
fontSize: 11,
fontWeight: 'bold',
formatter: '{c}'
}
}
]
}
this.machiningCenterChart.setOption(option)
}
}
}
</script>
<style>
HTML {
overflow: auto;
}
body {
margin: 0;
padding: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
position: relative;
font-family: MiSans Bold, serif;
}
.route-switch-btn {
position: absolute;
left: 0px;
top: 0px;
z-index: 9999;
width: 78px;
height: 30px;
background: rgba(5, 24, 55, 0.72);
color: #dff8ff;
font-size: 13px;
line-height: 28px;
text-align: center;
cursor: pointer;
box-shadow: 0 0 12px rgba(64, 205, 255, 0.35);
}
.route-switch-btn:hover {
background: rgba(18, 80, 130, 0.88);
color: #ffffff;
}
/*css主要部分的样式*/
/*定义滚动条宽高及背景,宽高分别对应横竖滚动条的尺寸*/
::-webkit-scrollbar {
width: 0px;
/*对垂直流动条有效*/
height: 0px;
/*对水平流动条有效*/
}
/*定义滚动条的轨道颜色、内阴影及圆角*/
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: rgb(14, 50, 97);
/*background-color: #ffffff;*/
border-radius: 2px;
}
/*定义滑块颜色、内阴影及圆角*/
::-webkit-scrollbar-thumb {
border-radius: 5px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: rgb(10, 35, 82);
}
/*定义两端按钮的样式*/
::-webkit-scrollbar-button {
display: none;
}
/*定义右下角汇合处的样式*/
::-webkit-scrollbar-corner {
background: #dedede;
}
</style>
<style scoped="scoped">
.allShow {
overflow-y: hidden;
z-index: 100;
transform-origin: 0 0;
position: fixed;
left: 50%;
top: 50%;
transition: 0.3s;
background: url("/png/背景底图.png") no-repeat center center;
background-size: cover;
color: white;
}
.daily-output-box {
width: 100%;
height: 300px;
overflow: hidden;
}
.left-title-header {
width: 100%;
height: 35px;
background: url("/png/left.png") no-repeat center center;
background-size: 100% 100%;
display: flex;
align-items: center;
padding-left: 25px;
box-sizing: border-box;
}
.left-title-text{
font-size: 20px;
font-family: "Microsoft YaHei", "微软雅黑", "PingFang SC", sans-serif;
}
.box-title {
font-size: 25px;
font-weight: 700;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-family: "Microsoft YaHei", "微软雅黑", "PingFang SC", sans-serif;
}
.daily-output-body {
width: 100%;
height: calc(100% - 35px);
box-sizing: border-box;
}
.daily-output-chart {
width: 100%;
height: 100%;
}
.storage-status-box {
height: 600px;
overflow: hidden;
}
.right-title-header {
width: 100%;
height: 35px;
background: url("/png/right.png") no-repeat center center;
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 25px;
box-sizing: border-box;
}
.right-title-text{
font-size: 20px;
font-family: "Microsoft YaHei", "微软雅黑", "PingFang SC", sans-serif;
}
.storage-status-body {
width: 100%;
height: calc(100% - 35px);
box-sizing: border-box;
padding: 10px;
}
.now-status-body {
position: relative;
overflow: hidden;
}
.now-status-scroll-wrapper {
height: 100%;
overflow: hidden;
}
.now-status-fixed-row {
display: grid;
grid-template-columns: 95px 1fr;
align-items: center;
min-height: 42px;
margin-bottom: 8px;
padding: 6px 10px;
box-sizing: border-box;
background: rgba(0, 0, 0, 0.3);
border-radius: 6px;
}
.now-status-fixed-label {
color: #fff;
font-size: 16px;
font-family: "Microsoft YaHei", "微软雅黑", "PingFang SC", sans-serif;
}
.now-status-indicators {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 6px;
}
.now-status-indicator-item {
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
}
.now-status-circle {
width: 15px;
height: 15px;
border-radius: 50%;
display: inline-block;
background: grey;
transition: background-color 0.3s ease;
}
.now-status-circle--active {
box-shadow: 0 0 8px currentColor;
}
.now-status-indicator-label {
margin-left: 5px;
color: #fff;
font-size: 12px;
white-space: nowrap;
font-family: "Microsoft YaHei", "微软雅黑", "PingFang SC", sans-serif;
}
.now-status-scroll-container {
position: relative;
height: calc(100% - 50px);
overflow: hidden;
}
.now-status-scroll-wrapper {
height: 100%;
overflow: hidden;
}
.now-status-scroll-content {
width: 100%;
}
.now-status-row {
display: grid;
grid-template-columns: 110px 1fr;
align-items: center;
height: 40px;
padding: 5px 10px;
box-sizing: border-box;
color: #fff;
font-size: 15px;
border-radius: 4px;
transition: background-color 0.3s;
}
.now-status-row:hover {
background: rgba(0, 112, 192, 0.2);
}
.now-status-row-label,
.now-status-row-value {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: "Microsoft YaHei", "微软雅黑", "PingFang SC", sans-serif;
}
.realtimeTask-box {
width: 100%;
height: 250px;
overflow: hidden;
}
.realtime-task-grid {
width: 100%;
height: calc(100% - 35px);
display: grid;
grid-template-rows: repeat(3, 1fr);
row-gap: 8px;
padding: 10px;
box-sizing: border-box;
}
.realtime-task-item {
min-width: 0;
display: grid;
grid-template-columns: 78px 1fr;
align-items: center;
padding: 0 14px;
box-sizing: border-box;
background: rgba(0, 0, 0, 0.3);
border-radius: 4px;
}
.realtime-task-label {
color: #cfe8ff;
font-size: 16px;
font-family: "Microsoft YaHei", "微软雅黑", "PingFang SC", sans-serif;
}
.realtime-task-value {
color: #ffffff;
font-size: 18px;
font-weight: 700;
text-align: right;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-family: "Microsoft YaHei", "微软雅黑", "PingFang SC", sans-serif;
}
.component-maintenance-box {
width: 100%;
height: 350px;
overflow: hidden;
}
.component-maintenance-body {
width: 100%;
height: calc(100% - 35px);
box-sizing: border-box;
}
.component-maintenance-chart {
width: 100%;
height: 100%;
}
.device-status-box {
width: 100%;
height: 300px;
overflow: hidden;
}
.device-status-body {
width: 100%;
height: calc(100% - 35px);
box-sizing: border-box;
padding: 8px;
}
.device-status-table {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
gap: 3px;
}
.device-status-row {
flex: 1;
display: grid;
grid-template-columns: repeat(4, 1fr);
align-items: center;
background: rgba(255, 255, 255, 0.12);
border-radius: 4px;
box-sizing: border-box;
font-size: 13px;
color: #ffffff;
}
.device-status-row--header {
background: rgba(79, 131, 180, 0.5);
}
.device-status-cell {
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 15px;
color: #fff;
font-family: "Microsoft YaHei", "微软雅黑", "PingFang SC", sans-serif;
}
.device-status-pill {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 2px 13px;
border-radius: 10px;
font-size: 15px;
color: #ffffff;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.35);
}
.device-status-pill--running {
background: radial-gradient(circle at center, rgba(56, 136, 17, 0.3) 0%, rgba(56, 136, 17, 1) 100%);
}
.device-status-pill--alarm {
background: radial-gradient(circle at center, rgba(210, 73, 86, 0.3) 0%, rgba(210, 73, 86, 1) 100%);
}
.device-status-pill--standby {
background: radial-gradient(circle at center, rgba(204, 121, 0, 0.3) 0%, rgba(204, 121, 0, 1) 100%);
}
.device-status-pill--offline {
background: radial-gradient(circle at center, rgba(135, 135, 135, 0.3) 0%, rgba(135, 135, 135, 1) 100%);
}
.df-header__right {
position: absolute;
top: 30px;
right: 30px;
width: 200px;
}
.df-header__timebar {
display: flex;
align-items: center;
gap: 20px;
color: #ffffff;
}
.df-header__time {
order: 1;
/* 确保时间在最左侧 */
}
.df-header__sep {
opacity: 0.85;
order: 2;
/* 分隔符在中间 */
}
.df-header__date-wrapper {
display: flex;
flex-direction: column;
/* 垂直排列 */
align-items: flex-start;
/* 右对齐 */
gap: 2px;
order: 3;
}
.df-header__year {
font-size: 16px;
/* 可以稍小一些 */
line-height: 1.2;
}
.df-header__week {
font-size: 14px;
opacity: 0.85;
}
.other-params-label {
padding: 10px 0;
}
.other-params-value {
color: #ffcb6f;
font-size: 20px;
text-align: center;
}
.background {
width: 1920px;
height: 1080px;
background-color: rgb(8, 31, 72);
color: white;
background-size: 100%;
}
/*表格透明写法*/
::v-deep .el-table,
.el-table__expanded-cell {
background-color: transparent;
}
::v-deep .el-table th {
background-color: transparent !important;
color: #fefefe;
}
::v-deep .el-table tr {
background-color: transparent !important;
}
::v-deep .el-table--enable-row-transition .el-table__body td,
::v-deep.el-table .cell {
background-color: transparent;
color: white;
border: none;
padding-left: 0 !important;
padding-right: 0 !important;
}
::v-deep.el-table th>.cell {
color: rgba(254, 254, 254, 1);
font-weight: 700;
}
::v-deep .el-table td,
.el-table th {
padding: 8px 0;
}
.el-table__body-wrapper::-webkit-scrollbar {
/*width: 0;宽度为0隐藏*/
width: 0;
}
.el-table::before {
/*left: 0;*/
/*bottom: 0;*/
width: 100%;
height: 0;
}
table {
border-right: 1px solid #d7d7d7;
border-bottom: 1px solid #d7d7d7;
text-align: center;
}
table th {
border-left: 1px solid #d7d7d7;
border-top: 1px solid #d7d7d7;
}
table td {
border-left: 1px solid #d7d7d7;
border-top: 1px solid #d7d7d7;
}
/* 设备运行履历样式 */
.bb-chart-container {
width: 100%;
height: 110px;
box-sizing: border-box;
position: relative;
min-height: 110px;
overflow: hidden;
background: rgba(0, 0, 0, 0.3);
}
.bb-chart-container--timeline {
height: calc(100% - 35px);
min-height: 0;
}
.bb-chart {
width: 100% !important;
height: 100% !important;
min-height: 110px;
}
.main-content {
height: 100%;
width: 100%;
position: absolute;
top: 100px;
}
.status-timeline-box {
position: absolute;
left: 526px;
top: 732px;
width: 907px;
height: 202px;
overflow: hidden;
background-image: url("/png/2.png");
background-size: cover;
background-repeat: no-repeat;
}
.status-timeline-header {
width: 36%;
height: 35px;
background: url(/png/-6.png) no-repeat center center;
background-size: 100% 100%;
display: flex;
align-items: center;
box-sizing: border-box;
margin-top: 5px;
margin-left: 16px;
}
.bb-chart--status {
height: 100%;
width: 100%;
min-height: 0;
}
.title-png {
position: absolute;
top: 0;
left: 0;
height: 100px;
width: 100%;
background-image: url("/png/top1.png");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
pointer-events: none;
display: flex;
justify-content: center;
}
.title-text {
color: #fff;
font-size: 37px;
font-weight: bold;
text-align: center;
margin-top: 15px;
margin-right: 37px;
font-family: "Microsoft YaHei", "\5FAE\8F6F\96C5\9ED1", "PingFang SC", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.leftLine {
z-index: 30;
position: absolute;
width: 54px;
height: 1020px;
top: 30px;
left: 10px;
margin-top: 20px;
background-image: url(/png/.png);
background-size: 100% 100%;
background-position: center;
}
.rightLine {
z-index: 30;
position: absolute;
width: 54px;
height: 1020px;
top: 30px;
right: 10px;
margin-top: 20px;
background-image: url(/png/.png);
background-size: 100% 100%;
background-position: center;
}
.left-box {
position: absolute;
top: 0px;
left: 30px;
width: 336px;
background-image: url(/png/1.png);
background-size: cover;
background-repeat: no-repeat;
min-height: 724px;
}
.right-box {
position: absolute;
top: 0px;
right: 30px;
width: 336px;
min-height: 724px;
background-image: url(/png/1.png);
background-size: cover;
background-repeat: no-repeat;
}
.button1 {
position: absolute;
top: 100px;
left: 0;
width: 88px;
height: 35px;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
border: none;
padding: 0;
cursor: pointer;
z-index: 20;
}
.cirle {
pointer-events: none;
background-image: url(/png/.png);
position: absolute;
inset: 0;
top: 20px;
background-size: 95% 95%;
background-repeat: no-repeat;
background-position: center;
border: none;
padding: 0;
z-index: 1;
}
</style>