2213 lines
61 KiB
Vue
2213 lines
61 KiB
Vue
<!-- eslint-disable vue/max-attributes-per-line -->
|
||
<template>
|
||
<div
|
||
id="screen"
|
||
class="qa-screen"
|
||
:style="{
|
||
width: `${style.width}px`,
|
||
height: `${style.height}px`,
|
||
transform: style.transform
|
||
}"
|
||
>
|
||
<div class="title-png">
|
||
<div class="left-control-bar">
|
||
<img class="brand-logo" src="/png/logo.png" alt="庆安" />
|
||
<div class="mode-button-group">
|
||
<button type="button" class="mode-button" :class="{ 'mode-button--active': currentMode === 'DT' }" @click="changeDtMode">孪生模式</button>
|
||
<button type="button" class="mode-button" :class="{ 'mode-button--active': currentMode === 'SM' }" @click="changeSmMode">仿真模式</button>
|
||
</div>
|
||
</div>
|
||
<span class="title-text">{{ titleText }}</span>
|
||
<div class="right-control-bar">
|
||
<div class="df-header__timebar">
|
||
<span class="df-header__year">{{ headerDateCn }}</span>
|
||
<span class="df-header__time">{{ headerTime }}</span>
|
||
</div>
|
||
<div class="view-switch">
|
||
<button
|
||
v-for="item in viewButtons"
|
||
:key="item.value"
|
||
type="button"
|
||
class="view-switch__btn"
|
||
:class="{ 'view-switch__btn--active': activeView === item.value }"
|
||
@click="changeView(item.value)"
|
||
>
|
||
{{ item.label }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="leftLine"></div>
|
||
<div class="rightLine"></div>
|
||
|
||
<div class="main-content">
|
||
<iframe id="dmtIframe" v-if="mode === 'DT' && srcDt" allowtransparency="true" class="model-bg-frame" :src="srcDt" title="数字孪生模型" @load="handleIframeLoad"></iframe>
|
||
<iframe id="dmtIframe" v-if="mode === 'SM' && srcSm" allowtransparency="true" class="model-bg-frame" :src="srcSm" title="仿真模型" @load="handleIframeLoad"></iframe>
|
||
<div v-if="!mode || (!srcDt && !srcSm)" class="model-placeholder">等待配置三维模型地址</div>
|
||
|
||
<div class="left-box">
|
||
<section class="qa-panel qa-panel--combined">
|
||
<div class="left-title-header">
|
||
<span class="left-title-text">线体任务联动状态</span>
|
||
</div>
|
||
<div class="qa-panel-body line-monitor-body">
|
||
<div class="line-monitor-scroll-box">
|
||
<scroll class="line-monitor-scroll" :data="lineMonitorScrollList" :class-option="lineMonitorScrollOption">
|
||
<div class="line-monitor-content">
|
||
<div v-for="(row, index) in lineMonitorScrollList" :key="row.name + index" class="monitor-card">
|
||
<div class="monitor-head">
|
||
<div class="monitor-title">
|
||
<strong>{{ row.name }}</strong>
|
||
<span>{{ row.taskNo }}</span>
|
||
</div>
|
||
<span class="line-status" :class="`line-status--${row.type}`">{{ row.status }}</span>
|
||
</div>
|
||
|
||
<div class="task-summary">
|
||
<div class="task-route">
|
||
<span>起止位置</span>
|
||
<strong>{{ row.startPos }} -> {{ row.endPos }}</strong>
|
||
</div>
|
||
<div v-for="item in row.taskInfo" :key="item.label" class="task-field">
|
||
<span>{{ item.label }}</span>
|
||
<strong>{{ item.value }}</strong>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="signal-section">
|
||
<span class="signal-section__title">线体状态</span>
|
||
<div class="signal-grid signal-grid--line">
|
||
<div v-for="signal in row.lineSignals" :key="signal.label" class="signal-chip" :class="`signal-chip--${signal.state}`">
|
||
<i></i>
|
||
<span>{{ signal.label }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="signal-section">
|
||
<span class="signal-section__title">任务握手</span>
|
||
<div class="signal-grid signal-grid--handshake">
|
||
<div v-for="signal in row.handshakes" :key="signal.label" class="signal-chip" :class="`signal-chip--${signal.state}`">
|
||
<i></i>
|
||
<span>{{ signal.label }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</scroll>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<div class="right-box">
|
||
<section class="qa-panel">
|
||
<div class="right-title-header">
|
||
<span class="right-title-text">加工设备状态</span>
|
||
</div>
|
||
<div class="qa-panel-body equipment-body">
|
||
<div class="equipment-head">
|
||
<span>设备</span>
|
||
<span>产量</span>
|
||
<span>联动信号</span>
|
||
<span>状态</span>
|
||
</div>
|
||
<div class="seamless-box">
|
||
<scroll class="seamless-scroll" :data="equipmentScrollList" :class-option="equipmentScrollOption">
|
||
<div class="seamless-content">
|
||
<div
|
||
v-for="(item, index) in equipmentScrollList"
|
||
:key="item.name + index"
|
||
class="equipment-row"
|
||
:class="{ 'equipment-row--alarm': item.type === 'alarm' }"
|
||
>
|
||
<strong class="equipment-name">{{ item.name }}</strong>
|
||
<span class="equipment-count">{{ item.actual }}</span>
|
||
<div class="equipment-tags">
|
||
<span v-for="tag in item.tags" :key="tag.text" class="status-pill status-pill--compact" :class="`status-pill--${tag.type}`">
|
||
{{ tag.text }}
|
||
</span>
|
||
</div>
|
||
<span class="status-pill equipment-status" :class="`status-pill--${item.type}`">{{ item.status }}</span>
|
||
</div>
|
||
</div>
|
||
</scroll>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="qa-panel">
|
||
<div class="right-title-header">
|
||
<span class="right-title-text">辅助设备状态</span>
|
||
</div>
|
||
<div class="qa-panel-body assist-body">
|
||
<div class="assist-head">
|
||
<span>设备</span>
|
||
<span>联动信号</span>
|
||
<span>状态</span>
|
||
</div>
|
||
<div class="assist-list">
|
||
<div v-for="item in assistList" :key="item.name" class="assist-status-row">
|
||
<strong class="assist-name">{{ item.name }}</strong>
|
||
<div class="assist-tags">
|
||
<span v-for="tag in item.tags" :key="tag.text" class="status-pill status-pill--compact" :class="`status-pill--${tag.type}`">
|
||
{{ tag.text }}
|
||
</span>
|
||
</div>
|
||
<span class="status-pill assist-status" :class="`status-pill--${item.type}`">{{ item.status }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<div class="bottom-box">
|
||
<section class="qa-panel">
|
||
<div class="center-title-header">
|
||
<span>物料库位矩阵</span>
|
||
</div>
|
||
<div class="qa-panel-body qa-panel-body--storage">
|
||
<div class="storage-groups">
|
||
<div v-for="group in storageGroups" :key="group.name" class="storage-card">
|
||
<div class="storage-head">
|
||
<strong>{{ group.name }}</strong>
|
||
<span>{{ group.filled }} / {{ group.total }}</span>
|
||
</div>
|
||
<div class="slots">
|
||
<span v-for="slot in group.slots" :key="slot.name" class="slot" :class="{ 'slot--filled': slot.filled }">
|
||
{{ slot.name }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="qa-panel">
|
||
<div class="center-title-header">
|
||
<span>报警与异常事件</span>
|
||
</div>
|
||
<div class="qa-panel-body event-body">
|
||
<div ref="alarmPieChart" class="alarm-bar-chart"></div>
|
||
<div class="event-head">
|
||
<span>时间</span>
|
||
<span>设备</span>
|
||
<span>事件内容</span>
|
||
</div>
|
||
<div class="seamless-box">
|
||
<scroll class="seamless-scroll" :data="alarmScrollList" :class-option="alarmScrollOption">
|
||
<div class="seamless-content">
|
||
<div v-for="(event, index) in alarmScrollList" :key="event.time + event.device + index" class="event-row">
|
||
<span>{{ event.time }}</span>
|
||
<strong>{{ event.device }}</strong>
|
||
<p>{{ event.message }}</p>
|
||
</div>
|
||
</div>
|
||
</scroll>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="qa-panel">
|
||
<div class="center-title-header">
|
||
<span>设备运行履历</span>
|
||
</div>
|
||
<div class="qa-panel-body history-body">
|
||
<div class="history-legend">
|
||
<span v-for="item in historyLegend" :key="item.label" class="history-legend-item">
|
||
<i :class="`history-legend-dot history-legend-dot--${item.type}`"></i>
|
||
{{ item.label }}
|
||
</span>
|
||
</div>
|
||
<div class="history-head">
|
||
<span>设备</span>
|
||
<div class="history-scale">
|
||
<span v-for="tick in historyTicks" :key="tick">{{ tick }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="seamless-box">
|
||
<scroll class="seamless-scroll" :data="deviceHistoryScrollList" :class-option="historyScrollOption">
|
||
<div class="seamless-content">
|
||
<div v-for="(item, index) in deviceHistoryScrollList" :key="item.name + index" class="history-row">
|
||
<div class="history-device">
|
||
<strong>{{ item.name }}</strong>
|
||
</div>
|
||
<div class="history-track">
|
||
<span
|
||
v-for="segment in item.segments"
|
||
:key="segment.label + segment.start"
|
||
class="history-segment"
|
||
:class="`history-segment--${segment.type}`"
|
||
:style="getHistorySegmentStyle(segment)"
|
||
></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</scroll>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import * as echarts from 'echarts'
|
||
import scroll from 'vue-seamless-scroll'
|
||
|
||
export default {
|
||
name: 'QingAn',
|
||
components: { scroll },
|
||
data() {
|
||
return {
|
||
style: {
|
||
width: 1920,
|
||
height: 1080,
|
||
transform: 'scaleY(1) scaleX(1) translate(-50%, -50%)'
|
||
},
|
||
titleText: '',
|
||
headerDateCn: '',
|
||
headerTime: '',
|
||
iframeAddress: '',
|
||
mode: 'DT',
|
||
currentMode: 'DT',
|
||
srcDt: 'http://localhost:10056/',
|
||
srcSm: 'http://localhost:10059/',
|
||
autoPlayCraftCode: '',
|
||
modeSwitchTimer: null,
|
||
cameraSyncTimer: null,
|
||
iframeReadyTimer: null,
|
||
dmtIframeReady: false,
|
||
clockTimer: null,
|
||
resizeHandler: null,
|
||
alarmPieChart: null,
|
||
activeView: 'longRail',
|
||
viewButtons: [
|
||
{ label: '加工中心单元视角', value: 'longRail' },
|
||
{ label: '车床加工单元视角', value: 'shortRail' },
|
||
{ label: '插齿机单元视角', value: 'joint' },
|
||
{ label: '磨床加工单元视角', value: 'grinder' },
|
||
{ label: '全揽视角', value: 'overview' }
|
||
],
|
||
cameraViews: {
|
||
longRail: {
|
||
target: { x: -5.725619423653396, y: 1.559219531820475, z: -5.23360272959924 },
|
||
position: { x: -18.7530206850084, y: 1.6376732214652037, z: 15.344064598520557 },
|
||
zoom0: 1
|
||
},
|
||
shortRail: {
|
||
target: { x: -5.814177085428968, y: 16.255194514605996, z: -5.3456965650308055 },
|
||
position: { x: -17.571406723801864, y: 16.32599896951042, z: 13.22564819859711 },
|
||
zoom0: 1
|
||
},
|
||
joint: {
|
||
target: { x: 2.7622940494890424, y: 15.83905572715569, z: 0.08552052688719117 },
|
||
position: { x: -4.6476929900921515, y: 15.883680193065107, z: 11.790099601955712 },
|
||
zoom0: 1
|
||
},
|
||
grinder: {
|
||
target: { x: -10.128808700176803, y: -15.480679803858136, z: 1.9651555238025413 },
|
||
position: { x: -21.43448244172167, y: -15.531783203240701, z: 12.347383997813267 },
|
||
zoom0: 1
|
||
},
|
||
overview: {
|
||
target: { x: -2.4540886878602937, y: -1.7690793306726371, z: -0.104103172990748 },
|
||
position: { x: -44.67609815729721, y: -1.5148097061598855, z: 38.669527626788486 },
|
||
zoom0: 1
|
||
}
|
||
},
|
||
lineMonitorRows: [
|
||
{
|
||
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' }
|
||
]
|
||
},
|
||
{
|
||
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' }
|
||
]
|
||
},
|
||
{
|
||
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' }
|
||
]
|
||
},
|
||
{
|
||
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: [
|
||
{
|
||
name: '加工中心单元库区',
|
||
total: 9,
|
||
filled: 6,
|
||
slots: this.createSlots(['S1', 'S2', 'S4', 'S6', 'S7', 'S9'], 9)
|
||
},
|
||
{
|
||
name: '车床加工单元库区',
|
||
total: 9,
|
||
filled: 6,
|
||
slots: this.createSlots(['S1', 'S3', 'S4', 'S6', 'S8', 'S9'], 9)
|
||
},
|
||
{
|
||
name: '插齿机单元库区',
|
||
total: 6,
|
||
filled: 4,
|
||
slots: this.createSlots(['S1', 'S3', 'S5', 'S6'], 6)
|
||
},
|
||
{
|
||
name: '磨床加工单元库区',
|
||
total: 6,
|
||
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 }
|
||
],
|
||
historyTicks: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'],
|
||
historyLegend: [
|
||
{ label: '关机', type: 'off' },
|
||
{ label: '运行', type: 'run' },
|
||
{ 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 }] }
|
||
]
|
||
}
|
||
},
|
||
computed: {
|
||
lineMonitorScrollList() {
|
||
return [...this.lineMonitorRows, ...this.lineMonitorRows]
|
||
},
|
||
equipmentScrollList() {
|
||
return [...this.equipmentList, ...this.equipmentList]
|
||
},
|
||
alarmScrollList() {
|
||
return [...this.alarmEvents, ...this.alarmEvents]
|
||
},
|
||
deviceHistoryScrollList() {
|
||
return [...this.deviceHistoryList, ...this.deviceHistoryList]
|
||
},
|
||
lineMonitorScrollOption() {
|
||
return {
|
||
step: 0.38,
|
||
limitMoveNum: 3,
|
||
hoverStop: true,
|
||
direction: 1,
|
||
openTouch: false,
|
||
singleHeight: 168,
|
||
waitTime: 1200,
|
||
autoPlay: true
|
||
}
|
||
},
|
||
equipmentScrollOption() {
|
||
return {
|
||
step: 0.45,
|
||
limitMoveNum: 4,
|
||
hoverStop: true,
|
||
direction: 1,
|
||
openTouch: false,
|
||
singleHeight: 44,
|
||
waitTime: 1200,
|
||
autoPlay: true
|
||
}
|
||
},
|
||
alarmScrollOption() {
|
||
return {
|
||
step: 0.4,
|
||
limitMoveNum: 4,
|
||
hoverStop: true,
|
||
direction: 1,
|
||
openTouch: false,
|
||
singleHeight: 36,
|
||
waitTime: 1200,
|
||
autoPlay: true
|
||
}
|
||
},
|
||
historyScrollOption() {
|
||
return {
|
||
step: 0.45,
|
||
limitMoveNum: 5,
|
||
hoverStop: true,
|
||
direction: 1,
|
||
openTouch: false,
|
||
singleHeight: 32,
|
||
waitTime: 1000,
|
||
autoPlay: true
|
||
}
|
||
}
|
||
},
|
||
mounted() {
|
||
const config = window.dt_Config || {}
|
||
this.iframeAddress = config.iframeAddress ? config.iframeAddress : ''
|
||
this.autoPlayCraftCode = config.autoPlayCraftCode || 'E8077755-9B85-46A1-9B8A-85083C00DF26'
|
||
this.srcDt = this.buildIframeUrl(config.dtIframe || this.srcDt, this.autoPlayCraftCode)
|
||
this.srcSm = this.buildIframeUrl(config.smIframe || this.srcSm, this.autoPlayCraftCode)
|
||
this.titleText = config.titleText
|
||
this.updateTime()
|
||
this.setScale()
|
||
this.$nextTick(this.initAlarmPieChart)
|
||
this.clockTimer = setInterval(this.updateTime, 1000)
|
||
this.resizeHandler = () => {
|
||
this.setScale()
|
||
if (this.alarmPieChart) {
|
||
this.alarmPieChart.resize()
|
||
}
|
||
}
|
||
window.addEventListener('resize', this.resizeHandler)
|
||
window.addEventListener('message', this.handleDmtMessage)
|
||
},
|
||
beforeDestroy() {
|
||
if (this.clockTimer) {
|
||
clearInterval(this.clockTimer)
|
||
this.clockTimer = null
|
||
}
|
||
if (this.resizeHandler) {
|
||
window.removeEventListener('resize', this.resizeHandler)
|
||
this.resizeHandler = null
|
||
}
|
||
window.removeEventListener('message', this.handleDmtMessage)
|
||
if (this.modeSwitchTimer) {
|
||
clearTimeout(this.modeSwitchTimer)
|
||
this.modeSwitchTimer = null
|
||
}
|
||
if (this.cameraSyncTimer) {
|
||
clearTimeout(this.cameraSyncTimer)
|
||
this.cameraSyncTimer = null
|
||
}
|
||
if (this.iframeReadyTimer) {
|
||
clearTimeout(this.iframeReadyTimer)
|
||
this.iframeReadyTimer = null
|
||
}
|
||
if (this.alarmPieChart) {
|
||
this.alarmPieChart.dispose()
|
||
this.alarmPieChart = null
|
||
}
|
||
},
|
||
methods: {
|
||
createSlots(filledNames, total) {
|
||
return Array.from({ length: total }, (_, index) => {
|
||
const name = `S${index + 1}`
|
||
return {
|
||
name,
|
||
filled: filledNames.includes(name)
|
||
}
|
||
})
|
||
},
|
||
getHistorySegmentStyle(segment) {
|
||
const start = Math.max(0, Math.min(24, segment.start))
|
||
const end = Math.max(start, Math.min(24, segment.end))
|
||
return {
|
||
left: `${(start / 24) * 100}%`,
|
||
width: `${((end - start) / 24) * 100}%`
|
||
}
|
||
},
|
||
changeView(value) {
|
||
this.activeView = value
|
||
if (this.dmtIframeReady) {
|
||
this.syncActiveCamera()
|
||
}
|
||
},
|
||
buildIframeUrl(baseUrl, craftCode) {
|
||
if (!baseUrl) {
|
||
return ''
|
||
}
|
||
let nextUrl = baseUrl
|
||
if (craftCode && nextUrl.includes('autoPlayCraftCode=') && !nextUrl.includes(craftCode)) {
|
||
const separator = nextUrl.endsWith('=') || nextUrl.endsWith('/') ? '' : '/'
|
||
nextUrl = `${nextUrl}${separator}${craftCode}`
|
||
}
|
||
return this.withTransparentParam(nextUrl)
|
||
},
|
||
withTransparentParam(url) {
|
||
if (!url || url.includes('transparent=true')) {
|
||
return url
|
||
}
|
||
const hashIndex = url.indexOf('#')
|
||
if (hashIndex > -1) {
|
||
const separator = url.slice(hashIndex).includes('?') ? '&' : '?'
|
||
return `${url}${separator}transparent=true`
|
||
}
|
||
return `${url}${url.endsWith('/') ? '' : '/'}#?transparent=true`
|
||
},
|
||
handleIframeLoad() {
|
||
this.dmtIframeReady = false
|
||
if (this.iframeReadyTimer) {
|
||
clearTimeout(this.iframeReadyTimer)
|
||
this.iframeReadyTimer = null
|
||
}
|
||
},
|
||
handleDmtMessage(event) {
|
||
if (!event || !event.data || event.data.type !== 'dmt_load_finish') {
|
||
return
|
||
}
|
||
const iframe = document.getElementById('dmtIframe')
|
||
if (!iframe || event.source !== iframe.contentWindow) {
|
||
return
|
||
}
|
||
this.markDmtIframeReady()
|
||
},
|
||
markDmtIframeReady() {
|
||
this.dmtIframeReady = true
|
||
if (this.iframeReadyTimer) {
|
||
clearTimeout(this.iframeReadyTimer)
|
||
this.iframeReadyTimer = null
|
||
}
|
||
this.syncActiveCamera()
|
||
},
|
||
syncActiveCamera() {
|
||
const camera = this.cameraViews[this.activeView]
|
||
if (!camera) return
|
||
if (!this.dmtIframeReady) return
|
||
if (this.cameraSyncTimer) {
|
||
clearTimeout(this.cameraSyncTimer)
|
||
}
|
||
this.cameraSyncTimer = setTimeout(() => {
|
||
const iframe = document.getElementById('dmtIframe')
|
||
if (!iframe || !iframe.contentWindow) return
|
||
iframe.contentWindow.postMessage({
|
||
type: 'set_camera',
|
||
data: {
|
||
target: camera.target,
|
||
position: camera.position,
|
||
zoom0: camera.zoom0
|
||
}
|
||
}, '*')
|
||
this.cameraSyncTimer = null
|
||
}, 300)
|
||
},
|
||
switchMode(mode) {
|
||
this.currentMode = mode
|
||
this.mode = ''
|
||
this.dmtIframeReady = false
|
||
if (this.iframeReadyTimer) {
|
||
clearTimeout(this.iframeReadyTimer)
|
||
this.iframeReadyTimer = null
|
||
}
|
||
if (this.modeSwitchTimer) {
|
||
clearTimeout(this.modeSwitchTimer)
|
||
}
|
||
this.modeSwitchTimer = setTimeout(() => {
|
||
this.mode = mode
|
||
this.modeSwitchTimer = null
|
||
}, 600)
|
||
},
|
||
changeDtMode() {
|
||
this.switchMode('DT')
|
||
},
|
||
changeSmMode() {
|
||
this.switchMode('SM')
|
||
},
|
||
initAlarmPieChart() {
|
||
if (!this.$refs.alarmPieChart) {
|
||
return
|
||
}
|
||
if (this.alarmPieChart) {
|
||
this.alarmPieChart.dispose()
|
||
}
|
||
this.alarmPieChart = echarts.init(this.$refs.alarmPieChart)
|
||
const names = this.alarmFrequencyTop5.map(item => item.name)
|
||
const values = this.alarmFrequencyTop5.map(item => item.value)
|
||
const maxValue = Math.max(...values, 1)
|
||
const yAxisMax = Math.max(2, Math.ceil(maxValue / 2) * 2)
|
||
this.alarmPieChart.setOption({
|
||
grid: {
|
||
left: 34,
|
||
right: 12,
|
||
top: 22,
|
||
bottom: 24
|
||
},
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
axisPointer: {
|
||
type: 'shadow',
|
||
shadowStyle: {
|
||
color: 'rgba(114, 206, 255, 0.08)'
|
||
}
|
||
},
|
||
formatter(params) {
|
||
const item = params && params[0]
|
||
return item ? `${item.name}<br/>报警频次:${item.value} 次` : ''
|
||
}
|
||
},
|
||
xAxis: {
|
||
type: 'category',
|
||
data: names,
|
||
axisTick: {
|
||
show: false
|
||
},
|
||
axisLine: {
|
||
lineStyle: {
|
||
color: 'rgba(114, 206, 255, 0.28)'
|
||
}
|
||
},
|
||
axisLabel: {
|
||
color: '#b8d3e6',
|
||
fontSize: 10,
|
||
interval: 0
|
||
}
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
min: 0,
|
||
max: yAxisMax,
|
||
interval: yAxisMax / 2,
|
||
splitNumber: 2,
|
||
minInterval: 1,
|
||
axisLine: {
|
||
show: false
|
||
},
|
||
axisTick: {
|
||
show: false
|
||
},
|
||
axisLabel: {
|
||
color: '#8fb8d3',
|
||
fontSize: 10,
|
||
formatter: value => `${Math.round(value)}`
|
||
},
|
||
splitLine: {
|
||
lineStyle: {
|
||
color: 'rgba(114, 206, 255, 0.12)',
|
||
type: 'dashed'
|
||
}
|
||
}
|
||
},
|
||
series: [
|
||
{
|
||
name: '报警频次',
|
||
type: 'bar',
|
||
data: values,
|
||
barWidth: 18,
|
||
label: {
|
||
show: true,
|
||
position: 'top',
|
||
color: '#e8f7ff',
|
||
fontSize: 10,
|
||
formatter: '{c}'
|
||
},
|
||
itemStyle: {
|
||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{ offset: 0, color: '#8df6ff' },
|
||
{ offset: 0.42, color: '#36b8ff' },
|
||
{ offset: 1, color: '#135cae' }
|
||
]),
|
||
borderRadius: 0,
|
||
shadowBlur: 10,
|
||
shadowColor: 'rgba(80, 237, 255, 0.24)'
|
||
}
|
||
}
|
||
]
|
||
})
|
||
},
|
||
setScale() {
|
||
const scaleX = window.innerWidth / this.style.width
|
||
const scaleY = window.innerHeight / this.style.height
|
||
this.style.transform = `scaleY(${scaleY}) scaleX(${scaleX}) translate(-50%, -50%)`
|
||
},
|
||
updateTime() {
|
||
const now = new Date()
|
||
const pad = value => String(value).padStart(2, '0')
|
||
this.headerDateCn = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
|
||
this.headerTime = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
html,
|
||
body,
|
||
#app {
|
||
margin: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
background: #050b14;
|
||
}
|
||
</style>
|
||
|
||
<style scoped>
|
||
.qa-screen {
|
||
--scada-panel: rgba(8, 20, 34, 0.84);
|
||
--scada-panel-deep: rgba(3, 10, 20, 0.92);
|
||
--scada-border: rgba(91, 213, 255, 0.34);
|
||
--scada-border-strong: rgba(128, 234, 255, 0.68);
|
||
--scada-cyan: #72e8ff;
|
||
--scada-blue: #2c8fff;
|
||
--scada-green: #24d88b;
|
||
--scada-yellow: #f5c84c;
|
||
--scada-red: #ff5266;
|
||
--scada-text: #f1f8ff;
|
||
--scada-muted: #91b5cd;
|
||
position: fixed;
|
||
left: 50%;
|
||
top: 50%;
|
||
transform-origin: 0 0;
|
||
overflow: hidden;
|
||
color: var(--scada-text);
|
||
font-family: "Microsoft YaHei", "PingFang SC", "Segoe UI", Arial, sans-serif;
|
||
background: #06101f url("/png/背景底图.png") no-repeat center center;
|
||
background-size: cover;
|
||
background-color: #050b14;
|
||
box-shadow: inset 0 0 140px rgba(0, 0, 0, 0.72);
|
||
}
|
||
|
||
.qa-screen::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
radial-gradient(circle at 25% 22%, rgba(42, 151, 255, 0.18), transparent 28%),
|
||
radial-gradient(circle at 74% 26%, rgba(39, 217, 128, 0.08), transparent 26%),
|
||
linear-gradient(180deg, rgba(2, 9, 22, 0.35), rgba(3, 13, 31, 0.66));
|
||
pointer-events: none;
|
||
}
|
||
|
||
.qa-screen::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 0;
|
||
background:
|
||
radial-gradient(circle at 50% 48%, rgba(44, 143, 255, 0.14), transparent 34%);
|
||
opacity: 0.42;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.title-png {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
z-index: 40;
|
||
width: 100%;
|
||
height: 100px;
|
||
background-image: url("/png/top1.png");
|
||
background-size: cover;
|
||
background-repeat: no-repeat;
|
||
background-position: center;
|
||
pointer-events: none;
|
||
display: flex;
|
||
justify-content: center;
|
||
box-shadow: 0 10px 22px rgba(0, 0, 0, 0.32);
|
||
}
|
||
|
||
.view-switch {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||
gap: 6px;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.view-switch__btn {
|
||
height: 30px;
|
||
padding: 0;
|
||
color: #b9d8ec;
|
||
border: 1px solid rgba(111, 224, 255, 0.34);
|
||
background:
|
||
linear-gradient(180deg, rgba(24, 52, 82, 0.82), rgba(5, 17, 30, 0.9)),
|
||
rgba(6, 25, 53, 0.72);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.12),
|
||
inset 0 -10px 18px rgba(0, 0, 0, 0.18),
|
||
0 6px 14px rgba(0, 0, 0, 0.28);
|
||
font-family: inherit;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
line-height: 28px;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.view-switch__btn--active {
|
||
color: #ffffff;
|
||
border-color: rgba(127, 239, 255, 0.95);
|
||
background:
|
||
linear-gradient(180deg, rgba(82, 207, 255, 0.74), rgba(16, 89, 142, 0.92)),
|
||
rgba(13, 70, 118, 0.78);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.34),
|
||
inset 0 0 14px rgba(101, 221, 255, 0.34),
|
||
0 0 16px rgba(49, 166, 255, 0.46);
|
||
}
|
||
|
||
.view-switch__btn:focus {
|
||
outline: none;
|
||
}
|
||
|
||
.mode-button {
|
||
position: relative;
|
||
flex: 1;
|
||
height: 30px;
|
||
padding: 0;
|
||
color: #e9fbff;
|
||
border: 1px solid rgba(75, 245, 209, 0.48);
|
||
background:
|
||
linear-gradient(90deg, rgba(8, 41, 57, 0.92), rgba(10, 67, 87, 0.78), rgba(8, 41, 57, 0.92)),
|
||
rgba(4, 20, 35, 0.78);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.14),
|
||
inset 0 0 14px rgba(75, 245, 209, 0.12),
|
||
0 0 14px rgba(75, 245, 209, 0.16);
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
letter-spacing: 0;
|
||
cursor: pointer;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.mode-button::before,
|
||
.mode-button::after {
|
||
content: "";
|
||
position: absolute;
|
||
top: 6px;
|
||
width: 7px;
|
||
height: 16px;
|
||
border-top: 1px solid rgba(128, 255, 230, 0.74);
|
||
border-bottom: 1px solid rgba(128, 255, 230, 0.48);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.mode-button::before {
|
||
left: 6px;
|
||
border-left: 1px solid rgba(128, 255, 230, 0.74);
|
||
}
|
||
|
||
.mode-button::after {
|
||
right: 6px;
|
||
border-right: 1px solid rgba(128, 255, 230, 0.74);
|
||
}
|
||
|
||
.mode-button:hover {
|
||
color: #ffffff;
|
||
border-color: rgba(128, 255, 230, 0.82);
|
||
background:
|
||
linear-gradient(90deg, rgba(12, 55, 73, 0.94), rgba(15, 91, 113, 0.84), rgba(12, 55, 73, 0.94)),
|
||
rgba(4, 20, 35, 0.82);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.22),
|
||
inset 0 0 18px rgba(75, 245, 209, 0.18),
|
||
0 0 18px rgba(75, 245, 209, 0.22);
|
||
}
|
||
|
||
.mode-button--active {
|
||
color: #ffffff;
|
||
border-color: rgba(128, 255, 230, 0.95);
|
||
background:
|
||
linear-gradient(90deg, rgba(12, 62, 78, 0.96), rgba(26, 122, 132, 0.88), rgba(12, 62, 78, 0.96)),
|
||
rgba(4, 20, 35, 0.86);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.26),
|
||
inset 0 0 20px rgba(75, 245, 209, 0.24),
|
||
0 0 20px rgba(75, 245, 209, 0.28);
|
||
}
|
||
|
||
.mode-button:focus {
|
||
outline: none;
|
||
}
|
||
|
||
.brand-logo {
|
||
display: block;
|
||
width: 300px;
|
||
height: 50px;
|
||
box-sizing: border-box;
|
||
padding: 6px 12px;
|
||
object-fit: contain;
|
||
object-position: left center;
|
||
pointer-events: none;
|
||
border: 1px solid rgba(255, 255, 255, 0.72);
|
||
background:
|
||
linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(231, 242, 248, 0.9)),
|
||
rgba(255, 255, 255, 0.92);
|
||
box-shadow:
|
||
inset 0 0 0 1px rgba(114, 206, 255, 0.14),
|
||
0 0 16px rgba(114, 232, 255, 0.18),
|
||
0 8px 20px rgba(0, 0, 0, 0.18);
|
||
}
|
||
|
||
.mode-button-group {
|
||
display: flex;
|
||
width: 300px;
|
||
height: 30px;
|
||
gap: 10px;
|
||
}
|
||
|
||
.title-text {
|
||
margin-right: 37px;
|
||
color: #fff;
|
||
font-size: 37px;
|
||
font-weight: 800;
|
||
line-height: 70px;
|
||
text-align: center;
|
||
text-shadow:
|
||
0 0 10px rgba(114, 232, 255, 0.56),
|
||
0 0 26px rgba(44, 143, 255, 0.42),
|
||
0 3px 0 rgba(0, 0, 0, 0.34);
|
||
}
|
||
|
||
.df-header__timebar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 12px;
|
||
color: #ffffff;
|
||
height: 26px;
|
||
padding: 0 8px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.df-header__year {
|
||
font-size: 13px;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.df-header__time {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #dffcff;
|
||
text-shadow: 0 0 12px rgba(114, 232, 255, 0.5);
|
||
}
|
||
|
||
.leftLine,
|
||
.rightLine {
|
||
position: absolute;
|
||
top: 50px;
|
||
z-index: 30;
|
||
width: 54px;
|
||
height: 1020px;
|
||
background-size: 100% 100%;
|
||
background-position: center;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.leftLine {
|
||
left: 10px;
|
||
background-image: url("/png/左边条.png");
|
||
}
|
||
|
||
.rightLine {
|
||
right: 10px;
|
||
background-image: url("/png/右边条.png");
|
||
}
|
||
|
||
.main-content {
|
||
position: absolute;
|
||
z-index: 2;
|
||
left: 64px;
|
||
right: 64px;
|
||
top: 100px;
|
||
bottom: 26px;
|
||
}
|
||
|
||
.left-control-bar,
|
||
.right-control-bar {
|
||
position: absolute;
|
||
width: 520px;
|
||
z-index: 20;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.left-control-bar {
|
||
top: 18px;
|
||
left: 80px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
height: 66px;
|
||
gap: 7px;
|
||
}
|
||
|
||
.right-control-bar {
|
||
top: 24px;
|
||
right: 80px;
|
||
height: 58px;
|
||
display: grid;
|
||
grid-template-rows: 24px 30px;
|
||
justify-content: end;
|
||
gap: 4px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.right-control-bar .df-header__timebar {
|
||
width: 236px;
|
||
justify-self: end;
|
||
}
|
||
|
||
.right-control-bar .view-switch {
|
||
width: 520px;
|
||
justify-self: end;
|
||
}
|
||
|
||
.model-bg-frame {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
border: 0;
|
||
background: transparent;
|
||
z-index: 1;
|
||
}
|
||
|
||
.model-placeholder {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #77d9ff;
|
||
font-size: 24px;
|
||
background: radial-gradient(circle at center, rgba(37, 111, 184, 0.24), rgba(4, 12, 28, 0.92));
|
||
}
|
||
|
||
.left-box {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 360px;
|
||
bottom: 260px;
|
||
z-index: 10;
|
||
overflow: hidden;
|
||
scrollbar-width: none;
|
||
-ms-overflow-style: none;
|
||
}
|
||
|
||
.left-box::-webkit-scrollbar,
|
||
.line-monitor-body::-webkit-scrollbar {
|
||
width: 0;
|
||
height: 0;
|
||
display: none;
|
||
}
|
||
|
||
.right-box {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
width: 360px;
|
||
bottom: 260px;
|
||
z-index: 10;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.bottom-box {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
height: 248px;
|
||
z-index: 10;
|
||
display: grid;
|
||
grid-template-columns: 1.2fr 1fr 1fr;
|
||
gap: 12px;
|
||
}
|
||
|
||
.left-box .qa-panel {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.line-monitor-body {
|
||
padding: 8px 12px;
|
||
overflow: hidden;
|
||
scrollbar-width: none;
|
||
-ms-overflow-style: none;
|
||
}
|
||
|
||
.line-monitor-scroll-box {
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.line-monitor-scroll,
|
||
.line-monitor-content {
|
||
min-height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.line-monitor-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.right-box .qa-panel:first-child {
|
||
flex: 1;
|
||
min-height: 0;
|
||
}
|
||
|
||
.right-box .qa-panel:last-child {
|
||
flex-shrink: 0;
|
||
height: 236px;
|
||
}
|
||
|
||
.right-box .qa-scroll {
|
||
overflow: hidden;
|
||
scrollbar-width: none;
|
||
}
|
||
|
||
.right-box .qa-scroll::-webkit-scrollbar {
|
||
width: 0;
|
||
height: 0;
|
||
display: none;
|
||
}
|
||
|
||
.qa-panel {
|
||
position: relative;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
border: 1px solid var(--scada-border);
|
||
background:
|
||
linear-gradient(180deg, rgba(18, 44, 68, 0.72) 0%, rgba(5, 15, 29, 0.9) 100%),
|
||
var(--scada-panel);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.08),
|
||
inset 0 0 28px rgba(44, 143, 255, 0.18),
|
||
0 14px 30px rgba(0, 0, 0, 0.38),
|
||
0 0 18px rgba(44, 143, 255, 0.08);
|
||
}
|
||
|
||
.qa-panel::before,
|
||
.qa-panel::after {
|
||
content: "";
|
||
position: absolute;
|
||
z-index: 1;
|
||
width: 28px;
|
||
height: 28px;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.qa-panel::before {
|
||
top: 0;
|
||
left: 0;
|
||
border-top: 2px solid rgba(128, 234, 255, 0.82);
|
||
border-left: 2px solid rgba(128, 234, 255, 0.72);
|
||
box-shadow: -1px -1px 10px rgba(114, 232, 255, 0.2);
|
||
}
|
||
|
||
.qa-panel::after {
|
||
right: 0;
|
||
bottom: 0;
|
||
border-right: 2px solid rgba(128, 234, 255, 0.58);
|
||
border-bottom: 2px solid rgba(128, 234, 255, 0.5);
|
||
box-shadow: 1px 1px 10px rgba(114, 232, 255, 0.14);
|
||
}
|
||
|
||
.qa-panel-body {
|
||
height: calc(100% - 42px);
|
||
padding: 10px 12px 12px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.qa-panel-body--storage {
|
||
padding: 8px 10px 10px;
|
||
display: flex;
|
||
min-height: 0;
|
||
}
|
||
|
||
.qa-scroll {
|
||
overflow: auto;
|
||
}
|
||
|
||
.qa-scroll::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
|
||
.qa-scroll::-webkit-scrollbar-thumb {
|
||
background: rgba(111, 212, 255, 0.22);
|
||
}
|
||
|
||
.left-title-header,
|
||
.right-title-header,
|
||
.center-title-header {
|
||
height: 42px;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 18px;
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
|
||
.left-title-header {
|
||
justify-content: flex-start;
|
||
padding-left: 18px;
|
||
background:
|
||
linear-gradient(90deg, rgba(25, 92, 145, 0.5), rgba(8, 24, 46, 0.12)),
|
||
linear-gradient(180deg, rgba(255, 255, 255, 0.07), transparent);
|
||
}
|
||
|
||
.right-title-header {
|
||
justify-content: flex-start;
|
||
padding-left: 18px;
|
||
padding-right: 18px;
|
||
background:
|
||
linear-gradient(90deg, rgba(25, 92, 145, 0.5), rgba(8, 24, 46, 0.12)),
|
||
linear-gradient(180deg, rgba(255, 255, 255, 0.07), transparent);
|
||
}
|
||
|
||
.center-title-header {
|
||
border-bottom: 1px solid rgba(114, 206, 255, 0.22);
|
||
background:
|
||
linear-gradient(90deg, rgba(25, 92, 145, 0.5), rgba(8, 24, 46, 0.12)),
|
||
linear-gradient(180deg, rgba(255, 255, 255, 0.07), transparent);
|
||
}
|
||
|
||
.left-title-text,
|
||
.right-title-text,
|
||
.center-title-header span {
|
||
color: #ffffff;
|
||
font-size: 17px;
|
||
font-weight: 800;
|
||
text-shadow: 0 0 12px rgba(114, 232, 255, 0.34);
|
||
}
|
||
|
||
.center-title-header::after,
|
||
.left-title-header::after,
|
||
.right-title-header::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 16px;
|
||
right: 16px;
|
||
bottom: 0;
|
||
height: 1px;
|
||
background: linear-gradient(90deg, transparent, rgba(114, 232, 255, 0.46), transparent);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.right-title-text {
|
||
text-align: left;
|
||
}
|
||
|
||
.status-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 18px;
|
||
padding: 2px 9px;
|
||
border-radius: 0;
|
||
color: #b8ccdc;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
background:
|
||
linear-gradient(180deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.03)),
|
||
rgba(40, 55, 72, 0.42);
|
||
font-size: 12px;
|
||
white-space: nowrap;
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.08),
|
||
0 0 10px rgba(255, 255, 255, 0.04);
|
||
text-shadow: none;
|
||
}
|
||
|
||
.status-pill--compact {
|
||
padding: 2px 7px;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.status-pill--ok {
|
||
color: #d9ffea;
|
||
border-color: rgba(36, 216, 139, 0.38);
|
||
background:
|
||
linear-gradient(180deg, rgba(36, 216, 139, 0.34), rgba(11, 73, 49, 0.56)),
|
||
rgba(14, 92, 61, 0.45);
|
||
box-shadow: 0 0 10px rgba(36, 216, 139, 0.18);
|
||
}
|
||
|
||
.status-pill--on {
|
||
color: #e0fbff;
|
||
border-color: rgba(114, 232, 255, 0.42);
|
||
background:
|
||
linear-gradient(180deg, rgba(44, 143, 255, 0.4), rgba(14, 58, 98, 0.58)),
|
||
rgba(14, 58, 98, 0.48);
|
||
box-shadow: 0 0 14px rgba(44, 143, 255, 0.2);
|
||
}
|
||
|
||
.status-pill--warn {
|
||
color: #fff3bf;
|
||
border-color: rgba(245, 200, 76, 0.42);
|
||
background:
|
||
linear-gradient(180deg, rgba(245, 200, 76, 0.38), rgba(101, 73, 14, 0.58)),
|
||
rgba(101, 73, 14, 0.44);
|
||
box-shadow: 0 0 14px rgba(245, 200, 76, 0.2);
|
||
}
|
||
|
||
.status-pill--alarm {
|
||
color: #ffe3e6;
|
||
border-color: rgba(255, 82, 102, 0.5);
|
||
background:
|
||
linear-gradient(180deg, rgba(255, 82, 102, 0.42), rgba(101, 18, 32, 0.62)),
|
||
rgba(101, 18, 32, 0.48);
|
||
box-shadow: 0 0 12px rgba(255, 82, 102, 0.22);
|
||
}
|
||
|
||
.status-pill--idle {
|
||
color: #c5d4df;
|
||
border-color: rgba(160, 180, 194, 0.24);
|
||
background:
|
||
linear-gradient(180deg, rgba(100, 121, 139, 0.28), rgba(36, 48, 60, 0.54)),
|
||
rgba(36, 48, 60, 0.42);
|
||
}
|
||
|
||
.line-status {
|
||
flex: 0 0 auto;
|
||
min-width: 74px;
|
||
padding: 4px 10px;
|
||
border-radius: 0;
|
||
color: #f3fbff;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
text-align: center;
|
||
letter-spacing: 0;
|
||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||
text-shadow: none;
|
||
}
|
||
|
||
.line-status--ok {
|
||
color: #dfffee;
|
||
border-color: rgba(36, 216, 139, 0.52);
|
||
background:
|
||
linear-gradient(180deg, rgba(36, 216, 139, 0.44), rgba(14, 92, 61, 0.68)),
|
||
rgba(14, 92, 61, 0.45);
|
||
box-shadow: 0 0 18px rgba(36, 216, 139, 0.26);
|
||
}
|
||
|
||
.line-status--on {
|
||
color: #e7f9ff;
|
||
border-color: rgba(114, 232, 255, 0.52);
|
||
background:
|
||
linear-gradient(180deg, rgba(44, 143, 255, 0.5), rgba(16, 79, 126, 0.72)),
|
||
rgba(16, 79, 126, 0.48);
|
||
box-shadow: 0 0 18px rgba(44, 143, 255, 0.3);
|
||
}
|
||
|
||
.line-status--alarm {
|
||
color: #fff2f4;
|
||
border-color: rgba(255, 82, 102, 0.58);
|
||
background:
|
||
linear-gradient(180deg, rgba(255, 82, 102, 0.52), rgba(118, 22, 38, 0.72)),
|
||
rgba(118, 22, 38, 0.5);
|
||
box-shadow: 0 0 18px rgba(255, 82, 102, 0.34);
|
||
}
|
||
|
||
.monitor-card,
|
||
.equipment-row,
|
||
.storage-card,
|
||
.event-row {
|
||
position: relative;
|
||
border: 1px solid rgba(114, 206, 255, 0.18);
|
||
background:
|
||
linear-gradient(180deg, rgba(20, 42, 62, 0.62), rgba(4, 14, 27, 0.74)),
|
||
rgba(5, 20, 44, 0.66);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.05),
|
||
inset 0 0 18px rgba(42, 133, 205, 0.11);
|
||
}
|
||
|
||
.monitor-card {
|
||
flex: 0 0 168px;
|
||
min-height: 168px;
|
||
margin-bottom: 0;
|
||
padding: 8px 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.monitor-card::before,
|
||
.equipment-row::before,
|
||
.event-row::before,
|
||
.history-row::before,
|
||
.assist-status-row::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 2px;
|
||
background: linear-gradient(180deg, transparent, rgba(114, 232, 255, 0.42), transparent);
|
||
opacity: 0.7;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.monitor-head,
|
||
.assist-title,
|
||
.storage-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
}
|
||
|
||
.monitor-title strong {
|
||
display: block;
|
||
color: #fff;
|
||
font-size: 18px;
|
||
text-shadow: 0 0 10px rgba(114, 232, 255, 0.28);
|
||
}
|
||
|
||
.monitor-title span {
|
||
display: block;
|
||
margin-top: 2px;
|
||
color: var(--scada-cyan);
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.task-summary {
|
||
margin-top: 5px;
|
||
padding: 6px 8px;
|
||
border: 1px solid rgba(114, 206, 255, 0.08);
|
||
background:
|
||
linear-gradient(90deg, rgba(44, 143, 255, 0.1), transparent 55%),
|
||
rgba(0, 0, 0, 0.26);
|
||
display: grid;
|
||
grid-template-columns: 1.2fr 1fr 1fr;
|
||
gap: 6px;
|
||
}
|
||
|
||
.task-route,
|
||
.task-field {
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.task-route span,
|
||
.task-field span,
|
||
.equipment-program span,
|
||
.storage-head span {
|
||
color: #9fc3dc;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.task-route strong,
|
||
.task-field strong {
|
||
color: #fff;
|
||
font-size: 13px;
|
||
line-height: 1.2;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.signal-section {
|
||
margin-top: 6px;
|
||
display: grid;
|
||
grid-template-columns: 50px minmax(0, 1fr);
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.signal-section__title {
|
||
color: #9fc3dc;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.signal-grid {
|
||
min-width: 0;
|
||
display: grid;
|
||
gap: 5px;
|
||
}
|
||
|
||
.signal-grid--line {
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
}
|
||
|
||
.signal-grid--handshake {
|
||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||
}
|
||
|
||
.signal-chip {
|
||
min-width: 0;
|
||
height: 22px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 4px;
|
||
color: #b8ccdc;
|
||
border: 1px solid rgba(160, 180, 194, 0.16);
|
||
background: rgba(36, 48, 60, 0.38);
|
||
font-size: 10px;
|
||
line-height: 1;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.signal-chip i {
|
||
flex: 0 0 auto;
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background: #5f7182;
|
||
}
|
||
|
||
.signal-chip span {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.signal-chip--on {
|
||
color: #d9ffea;
|
||
border-color: rgba(36, 216, 139, 0.34);
|
||
background: rgba(12, 80, 54, 0.52);
|
||
}
|
||
|
||
.signal-chip--on i {
|
||
background: var(--scada-green);
|
||
box-shadow: 0 0 8px rgba(36, 216, 139, 0.64);
|
||
}
|
||
|
||
.signal-chip--warn {
|
||
color: #fff3bf;
|
||
border-color: rgba(245, 200, 76, 0.36);
|
||
background: rgba(101, 73, 14, 0.48);
|
||
}
|
||
|
||
.signal-chip--warn i {
|
||
background: var(--scada-yellow);
|
||
box-shadow: 0 0 8px rgba(245, 200, 76, 0.58);
|
||
}
|
||
|
||
.signal-chip--alarm {
|
||
color: #ffe3e6;
|
||
border-color: rgba(255, 82, 102, 0.44);
|
||
background: rgba(101, 18, 32, 0.52);
|
||
}
|
||
|
||
.signal-chip--alarm i {
|
||
background: var(--scada-red);
|
||
box-shadow: 0 0 8px rgba(255, 82, 102, 0.62);
|
||
}
|
||
|
||
.signal-chip--off {
|
||
color: #9fb0bd;
|
||
}
|
||
|
||
.signal-chip--off i {
|
||
background: #5f7182;
|
||
}
|
||
|
||
.dot {
|
||
flex: 0 0 auto;
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 50%;
|
||
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.04);
|
||
}
|
||
|
||
.dot--green { background: var(--scada-green); box-shadow: 0 0 0 4px rgba(36, 216, 139, 0.08), 0 0 10px rgba(36, 216, 139, 0.5); }
|
||
.dot--blue { background: var(--scada-cyan); box-shadow: 0 0 0 4px rgba(114, 232, 255, 0.08), 0 0 10px rgba(114, 232, 255, 0.46); }
|
||
.dot--yellow { background: var(--scada-yellow); box-shadow: 0 0 0 4px rgba(245, 200, 76, 0.08), 0 0 10px rgba(245, 200, 76, 0.45); }
|
||
.dot--red { background: var(--scada-red); box-shadow: 0 0 0 4px rgba(255, 82, 102, 0.08), 0 0 10px rgba(255, 82, 102, 0.48); }
|
||
.dot--gray { background: #5f7182; }
|
||
|
||
.monitor-stage {
|
||
border-left: 3px solid rgba(53, 178, 255, 0.8);
|
||
}
|
||
|
||
.monitor-stage--ok {
|
||
border-left-color: #2be085;
|
||
}
|
||
|
||
.monitor-stage--alarm {
|
||
border-left-color: #ff5d6c;
|
||
background: rgba(64, 8, 20, 0.38);
|
||
}
|
||
|
||
.equipment-body,
|
||
.event-body,
|
||
.history-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.alarm-bar-chart {
|
||
flex-shrink: 0;
|
||
height: 98px;
|
||
margin: -2px 0 5px;
|
||
border: 1px solid rgba(114, 206, 255, 0.12);
|
||
background:
|
||
linear-gradient(180deg, rgba(24, 66, 104, 0.28), rgba(7, 20, 34, 0.12)),
|
||
rgba(0, 0, 0, 0.18);
|
||
}
|
||
|
||
.seamless-box {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.seamless-scroll,
|
||
.seamless-content {
|
||
min-height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.equipment-head,
|
||
.equipment-row {
|
||
display: grid;
|
||
grid-template-columns: 68px 56px minmax(104px, 1fr) 50px;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.equipment-head {
|
||
flex-shrink: 0;
|
||
min-height: 28px;
|
||
margin-bottom: 6px;
|
||
padding: 0 8px;
|
||
color: #a9cce0;
|
||
border: 1px solid rgba(114, 206, 255, 0.18);
|
||
background:
|
||
linear-gradient(180deg, rgba(38, 83, 122, 0.42), rgba(12, 34, 56, 0.42)),
|
||
rgba(21, 58, 94, 0.34);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.equipment-row {
|
||
min-height: 44px;
|
||
margin-bottom: 6px;
|
||
padding: 8px;
|
||
}
|
||
|
||
.equipment-row--alarm {
|
||
border-color: rgba(255, 82, 102, 0.52);
|
||
background:
|
||
linear-gradient(90deg, rgba(255, 82, 102, 0.22), rgba(64, 8, 20, 0.44)),
|
||
rgba(64, 8, 20, 0.48);
|
||
}
|
||
|
||
.equipment-name {
|
||
min-width: 0;
|
||
color: #fff;
|
||
font-size: 13px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.equipment-program {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.equipment-count {
|
||
color: #fff;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.equipment-program b {
|
||
margin-top: 4px;
|
||
color: #fff;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.equipment-tags,
|
||
.assist-flow {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.equipment-status {
|
||
justify-self: end;
|
||
min-width: 40px;
|
||
}
|
||
|
||
.assist-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 8px 12px 10px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.assist-head,
|
||
.assist-status-row {
|
||
display: grid;
|
||
grid-template-columns: 118px minmax(108px, 1fr) 50px;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.assist-head {
|
||
flex-shrink: 0;
|
||
min-height: 26px;
|
||
margin-bottom: 5px;
|
||
padding: 0 8px;
|
||
color: #a9cce0;
|
||
border: 1px solid rgba(114, 206, 255, 0.18);
|
||
background:
|
||
linear-gradient(180deg, rgba(38, 83, 122, 0.42), rgba(12, 34, 56, 0.42)),
|
||
rgba(21, 58, 94, 0.34);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.assist-list {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: grid;
|
||
grid-template-rows: repeat(5, minmax(0, 1fr));
|
||
gap: 4px;
|
||
}
|
||
|
||
.assist-status-row {
|
||
position: relative;
|
||
min-height: 0;
|
||
padding: 0 8px;
|
||
border: 1px solid rgba(114, 206, 255, 0.18);
|
||
background:
|
||
linear-gradient(180deg, rgba(20, 42, 62, 0.58), rgba(4, 14, 27, 0.72)),
|
||
rgba(5, 20, 44, 0.66);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.05),
|
||
inset 0 0 14px rgba(42, 133, 205, 0.12);
|
||
}
|
||
|
||
.assist-name {
|
||
min-width: 0;
|
||
color: #fff;
|
||
font-size: 13px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.assist-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.assist-status {
|
||
justify-self: end;
|
||
min-width: 40px;
|
||
}
|
||
|
||
.storage-groups {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 8px;
|
||
align-items: stretch;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.storage-card {
|
||
min-width: 0;
|
||
padding: 9px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.storage-head {
|
||
margin-bottom: 8px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.slots {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
grid-auto-rows: minmax(0, 1fr);
|
||
gap: 6px;
|
||
}
|
||
|
||
.slot {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 30px;
|
||
color: rgba(184, 202, 214, 0.72);
|
||
border: 1px solid rgba(127, 154, 174, 0.24);
|
||
background:
|
||
linear-gradient(180deg, rgba(84, 99, 114, 0.24), rgba(28, 39, 51, 0.42)),
|
||
rgba(56, 68, 82, 0.22);
|
||
box-shadow:
|
||
inset 0 0 10px rgba(120, 145, 160, 0.08),
|
||
inset 0 0 0 1px rgba(255, 255, 255, 0.03);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.slot::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 5px;
|
||
top: 5px;
|
||
width: 5px;
|
||
height: 5px;
|
||
border-radius: 50%;
|
||
background: rgba(91, 109, 129, 0.75);
|
||
box-shadow: 0 0 6px rgba(91, 109, 129, 0.2);
|
||
}
|
||
|
||
.slot--filled {
|
||
color: #f1fcff;
|
||
font-weight: 700;
|
||
border-color: rgba(36, 216, 139, 0.68);
|
||
background:
|
||
linear-gradient(180deg, rgba(44, 143, 255, 0.34), rgba(36, 216, 139, 0.38)),
|
||
rgba(8, 46, 50, 0.38);
|
||
box-shadow:
|
||
inset 0 0 0 1px rgba(126, 233, 255, 0.28),
|
||
inset 0 0 16px rgba(36, 216, 139, 0.22),
|
||
0 0 12px rgba(36, 216, 139, 0.18);
|
||
}
|
||
|
||
.slot--filled::before {
|
||
background: var(--scada-green);
|
||
box-shadow: 0 0 10px rgba(36, 216, 139, 0.72);
|
||
}
|
||
|
||
.event-head,
|
||
.event-row {
|
||
display: grid;
|
||
grid-template-columns: 52px 90px 1fr;
|
||
align-items: center;
|
||
gap: 8px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.event-head {
|
||
flex-shrink: 0;
|
||
height: 22px;
|
||
min-height: 22px;
|
||
margin-bottom: 3px;
|
||
padding: 0 10px;
|
||
color: #a9cce0;
|
||
border: 1px solid rgba(114, 206, 255, 0.18);
|
||
background:
|
||
linear-gradient(180deg, rgba(38, 83, 122, 0.42), rgba(12, 34, 56, 0.42)),
|
||
rgba(21, 58, 94, 0.34);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.event-row {
|
||
min-height: 30px;
|
||
margin-bottom: 3px;
|
||
padding: 5px 10px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.event-row p,
|
||
.event-row strong {
|
||
margin: 0;
|
||
color: #d5edf8;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.event-row p {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
line-height: 1.25;
|
||
}
|
||
|
||
.event-row > span:first-child {
|
||
color: var(--scada-cyan);
|
||
font-weight: 700;
|
||
text-shadow: 0 0 8px rgba(114, 232, 255, 0.24);
|
||
}
|
||
|
||
.history-legend {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
height: 18px;
|
||
margin-bottom: 3px;
|
||
padding: 0 10px;
|
||
color: #b8d3e6;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.history-legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.history-legend-dot {
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.history-legend-dot--run {
|
||
background: rgba(36, 216, 139, 0.9);
|
||
box-shadow: 0 0 8px rgba(36, 216, 139, 0.42);
|
||
}
|
||
|
||
.history-legend-dot--standby {
|
||
background: rgba(243, 198, 78, 0.9);
|
||
box-shadow: 0 0 8px rgba(243, 198, 78, 0.36);
|
||
}
|
||
|
||
.history-legend-dot--off {
|
||
background: rgba(92, 111, 130, 0.9);
|
||
}
|
||
|
||
.history-legend-dot--alarm {
|
||
background: rgba(255, 82, 102, 0.9);
|
||
box-shadow: 0 0 8px rgba(255, 82, 102, 0.38);
|
||
}
|
||
|
||
.history-head,
|
||
.history-row {
|
||
display: grid;
|
||
grid-template-columns: 132px 1fr;
|
||
align-items: center;
|
||
gap: 10px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.history-head {
|
||
flex-shrink: 0;
|
||
height: 22px;
|
||
margin-bottom: 3px;
|
||
padding: 0 10px;
|
||
color: #a9cce0;
|
||
border: 1px solid rgba(114, 206, 255, 0.18);
|
||
background:
|
||
linear-gradient(180deg, rgba(38, 83, 122, 0.42), rgba(12, 34, 56, 0.42)),
|
||
rgba(21, 58, 94, 0.34);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.history-row {
|
||
min-height: 30px;
|
||
margin-bottom: 3px;
|
||
padding: 4px 10px;
|
||
border: 1px solid rgba(114, 206, 255, 0.18);
|
||
background:
|
||
linear-gradient(180deg, rgba(20, 42, 62, 0.56), rgba(4, 14, 27, 0.72)),
|
||
rgba(5, 20, 44, 0.66);
|
||
box-shadow: inset 0 0 14px rgba(42, 133, 205, 0.12);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.history-row::before {
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 2px;
|
||
background: linear-gradient(180deg, transparent, rgba(114, 232, 255, 0.42), transparent);
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.history-device {
|
||
min-width: 0;
|
||
}
|
||
|
||
.history-device strong {
|
||
color: #fff;
|
||
font-size: 13px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.history-scale {
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
color: #8fb8d3;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.history-scale span {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.history-track {
|
||
position: relative;
|
||
height: 16px;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
border: 1px solid rgba(114, 206, 255, 0.13);
|
||
background:
|
||
repeating-linear-gradient(
|
||
to right,
|
||
rgba(114, 206, 255, 0.26) 0,
|
||
rgba(114, 206, 255, 0.26) 1px,
|
||
transparent 1px,
|
||
transparent calc(100% / 6)
|
||
),
|
||
rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.history-segment {
|
||
position: absolute;
|
||
top: 2px;
|
||
bottom: 2px;
|
||
min-width: 4px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.history-segment--run {
|
||
color: #eafff2;
|
||
background: rgba(36, 216, 139, 0.72);
|
||
}
|
||
|
||
.history-segment--standby {
|
||
color: #fff2b0;
|
||
background: rgba(243, 198, 78, 0.58);
|
||
}
|
||
|
||
.history-segment--off {
|
||
color: #b5c8d5;
|
||
background: rgba(92, 111, 130, 0.5);
|
||
}
|
||
|
||
.history-segment--alarm {
|
||
color: #fff0f1;
|
||
background: rgba(255, 82, 102, 0.72);
|
||
}
|
||
|
||
</style>
|