1
HuBingJie
2025-12-05 28110912ca4803e5793f181517d7bf2d7a5ea2ad
´úÂë¹ÜÀí/WCS/WCS/WIDESEAWCS_Client/src/views/Home.vue
@@ -1,24 +1,1958 @@
<template>
  <div class="title"></div>
  <div class="rgv-monitor">
    <div class="monitor-header">
      <div class="title">设备状态监控</div>
      <div class="control-panel">
        <!-- ç›‘控类型切换 -->
        <div class="monitor-type-switch">
          <el-button-group>
            <el-button :type="currentMonitorType === 'inbound' ? 'primary' : ''" @click="switchMonitorType('inbound')">
              å…¥åº“监控
            </el-button>
            <el-button :type="currentMonitorType === 'outbound' ? 'primary' : ''"
              @click="switchMonitorType('outbound')">
              å‡ºåº“监控
            </el-button>
            <el-button :type="currentMonitorType === 'safetydoor' ? 'primary' : ''"
              @click="switchMonitorType('safetydoor')">
              å®‰å…¨é—¨ç›‘控
            </el-button>
            <el-button :type="currentMonitorType === 'platform' ? 'primary' : ''"
              @click="switchMonitorType('platform')">
              ç«™å°ç›‘控
            </el-button>
          </el-button-group>
        </div>
        <!-- ä¸€é”®æ“ä½œæŒ‰é’®ï¼ˆå®‰å…¨é—¨å’Œç«™å°ç›‘控时不显示) -->
        <div class="one-click-operations"
          v-if="currentMonitorType !== 'safetydoor' && currentMonitorType !== 'platform'">
          <el-button-group>
            <el-button type="primary"
              @mousedown="startOneClickInitHold"
              @mouseup="cancelOneClickInitHold"
              @mouseleave="cancelOneClickInitHold"
              @touchstart.prevent="startOneClickInitHold"
              @touchend="cancelOneClickInitHold"
              @touchcancel="cancelOneClickInitHold"
              :loading="oneClickLoading.init">
              ä¸€é”®åˆå§‹åŒ–
            </el-button>
            <el-button type="warning" @click="handleOneClickOperation('reset')" :loading="oneClickLoading.reset">
              ä¸€é”®å¤ä½
            </el-button>
            <el-button type="success" @click="handleOneClickOperation('start')" :loading="oneClickLoading.start">
              ä¸€é”®å¯åЍ
            </el-button>
            <el-button type="danger" @click="handleOneClickOperation('stop')" :loading="oneClickLoading.stop">
              ä¸€é”®æš‚停
            </el-button>
          </el-button-group>
        </div>
        <el-button :type="isMonitoring ? 'danger' : 'primary'" @click="toggleMonitoring">
          {{ isMonitoring ? '停止监控' : '启动监控' }}
        </el-button>
        <el-button type="warning" @click="refreshData">刷新数据</el-button>
      </div>
    </div>
    <!-- ç›‘控状态显示 -->
    <div class="monitor-status">
      <el-alert :title="getMonitorStatusTitle()" :type="isMonitoring ? 'success' : 'info'" :closable="false"
        show-icon />
    </div>
    <div class="devices-container">
      <!-- åŠ¨æ€æ¸²æŸ“è®¾å¤‡ -->
      <div class="device-card" v-for="(deviceData, deviceKey) in currentDeviceData" :key="deviceKey">
        <div class="device-header">
          <h3>{{ getDeviceDisplayName(deviceKey) }}</h3>
          <div class="status-indicator" :class="getStatusClass(deviceData)"></div>
        </div>
        <div class="device-content">
          <template v-if="deviceData && Object.keys(deviceData).length > 0">
            <!-- RGV设备显示 -->
            <template v-if="currentMonitorType !== 'safetydoor' && currentMonitorType !== 'platform'">
              <!-- åˆå§‹åŒ–状态显示 -->
              <div class="data-row" v-if="deviceData['初始化未完成标志位'] !== undefined">
                <span class="label">初始化状态:</span>
                <span class="value" :class="deviceData['初始化未完成标志位'] === 1 ? 'fault-text' : ''">
                  {{ deviceData['初始化未完成标志位'] === 0 ? '已完成' : '未完成' }}
                </span>
              </div>
              <div class="data-row" v-for="(value, key) in deviceData" :key="key" v-if="key !== '初始化未完成标志位'">
                <span class="label">{{ getFieldDisplayName(key) }}:</span>
                <span class="value" :class="getValueClass(key, value, deviceKey)">
                  {{ getFormattedValue(key, value, deviceKey) }}
                  <!-- ä¸ºä¸Šå‡ä¿¡å·å’Œä¸‹é™ä¿¡å·æ·»åŠ æŒ‡ç¤ºç¯ -->
                  <span v-if="key === '上升信号到位'" class="signal-indicator" :class="getSignalClass(value)"></span>
                  <span v-if="key === '下降信号到位'" class="signal-indicator" :class="getSignalClass(value)"></span>
                </span>
              </div>
            </template>
            <!-- å®‰å…¨é—¨è®¾å¤‡æ˜¾ç¤º -->
            <template v-else-if="currentMonitorType === 'safetydoor'">
              <div class="data-row" v-for="(value, key) in deviceData" :key="key">
                <span class="label">{{ getFieldDisplayName(key) }}:</span>
                <span class="value" :class="getSafetyDoorValueClass(key, value)">
                  {{ getSafetyDoorFormattedValue(key, value) }}
                  <!-- å®‰å…¨é—¨çŠ¶æ€æŒ‡ç¤ºç¯ -->
                  <span v-if="key === '安全门指示灯状态'" class="safety-light" :class="getSafetyLightClass(value)"></span>
                  <span v-if="key === '安全门急停状态'" class="emergency-stop" :class="getEmergencyStopClass(value)"></span>
                </span>
              </div>
            </template>
            <!-- ç«™å°è®¾å¤‡æ˜¾ç¤º -->
            <template v-else-if="currentMonitorType === 'platform'">
              <div class="data-row" v-for="(value, key) in deviceData" :key="key">
                <span class="label">{{ getFieldDisplayName(key) }}:</span>
                <span class="value" :class="getPlatformValueClass(key, value)">
                  {{ getPlatformFormattedValue(key, value) }}
                  <!-- ç«™å°å…‰ç”µä¿¡å·æŒ‡ç¤ºç¯ -->
                  <span v-if="key === '光电信号'" class="signal-indicator" :class="getSignalClass(value)"></span>
                </span>
              </div>
            </template>
          </template>
          <div v-else class="no-data">
            è®¾å¤‡æœªè¿žæŽ¥æˆ–暂无数据
          </div>
          <!-- æ“ä½œæŒ‰é’®åŒºåŸŸï¼ˆå®‰å…¨é—¨å’Œç«™å°ç›‘控时不显示) -->
          <div class="operation-buttons"
            v-if="currentMonitorType !== 'safetydoor' && currentMonitorType !== 'platform'">
            <!-- å­è½¦æ“ä½œæŒ‰é’® -->
            <template v-if="getDeviceType(deviceKey) === 'child'">
              <el-button type="primary" size="small"
                @mousedown="startInitHold(deviceKey)"
                @mouseup="cancelInitHold(deviceKey)"
                @mouseleave="cancelInitHold(deviceKey)"
                @touchstart.prevent="startInitHold(deviceKey)"
                @touchend="cancelInitHold(deviceKey)"
                @touchcancel="cancelInitHold(deviceKey)"
                :loading="loadingStates[deviceKey]?.cs">
                {{ getInitializationButtonText(deviceData) }}
              </el-button>
              <el-button :type="getModeButtonType(deviceData)" size="small" @click="handleModeToggle(deviceKey)"
                :loading="loadingStates[deviceKey]?.modeToggle">
                {{ getCurrentModeText(deviceData) }}
              </el-button>
              <el-button type="warning" size="small" @click="handleOperation(deviceKey, 'fw')"
                :loading="loadingStates[deviceKey]?.fw">
                å¤ä½
              </el-button>
              <!-- é•¿æŒ‰ï¼šä¸Šå‡/下降(按下=1,松开=0) -->
              <el-button type="success" size="small"
                @pointerdown.prevent="handleLiftPress(deviceKey, 'ss')"
                @pointerup="handleLiftRelease(deviceKey, 'ss')"
                @pointercancel="handleLiftRelease(deviceKey, 'ss')"
                :disabled="!isManualMode(deviceData) || deviceData['上升信号到位'] === 1"
                :loading="activeLift && activeLift.deviceKey === deviceKey && activeLift.operationType === 'ss'">
                ä¸Šå‡
              </el-button>
              <el-button type="info" size="small"
                @pointerdown.prevent="handleLiftPress(deviceKey, 'xj')"
                @pointerup="handleLiftRelease(deviceKey, 'xj')"
                @pointercancel="handleLiftRelease(deviceKey, 'xj')"
                :disabled="!isManualMode(deviceData) || deviceData['下降信号到位'] === 1"
                :loading="activeLift && activeLift.deviceKey === deviceKey && activeLift.operationType === 'xj'">
                ä¸‹é™
              </el-button>
              <!-- ä¿éšœï¼šæ‰‹åŠ¨åœæ­¢æŒ‰é’®ï¼ˆç‚¹å‡»å³å‘0) -->
              <el-button type="warning" size="small"
                @click="stopLift(deviceKey, 'ss')"
                :disabled="!isManualMode(deviceData)">
                åœæ­¢ä¸Šå‡
              </el-button>
              <el-button type="warning" size="small"
                @click="stopLift(deviceKey, 'xj')"
                :disabled="!isManualMode(deviceData)">
                åœæ­¢ä¸‹é™
              </el-button>
            </template>
            <!-- æ¯è½¦æ“ä½œæŒ‰é’® -->
            <template v-else-if="getDeviceType(deviceKey) === 'mother'">
              <el-button type="primary" size="small"
                @mousedown="startInitHold(deviceKey)"
                @mouseup="cancelInitHold(deviceKey)"
                @mouseleave="cancelInitHold(deviceKey)"
                @touchstart.prevent="startInitHold(deviceKey)"
                @touchend="cancelInitHold(deviceKey)"
                @touchcancel="cancelInitHold(deviceKey)"
                :loading="loadingStates[deviceKey]?.cs">
                {{ getInitializationButtonText(deviceData) }}
              </el-button>
              <el-button :type="getModeButtonType(deviceData)" size="small" @click="handleModeToggle(deviceKey)"
                :loading="loadingStates[deviceKey]?.modeToggle">
                {{ getCurrentModeText(deviceData) }}
              </el-button>
              <el-button type="warning" size="small" @click="handleOperation(deviceKey, 'fw')"
                :loading="loadingStates[deviceKey]?.fw">
                å¤ä½
              </el-button>
              <!-- é›·è¾¾å¼€å…³æŒ‰é’® -->
              <el-button :type="getRadarButtonType(deviceData)" size="small" @click="handleRadarToggle(deviceKey)"
                :loading="loadingStates[deviceKey]?.radarToggle" :disabled="!isManualMode(deviceData)">
                {{ getRadarButtonText(deviceData) }}
              </el-button>
            </template>
            <!-- åŽŸæ–™è½¦æ“ä½œæŒ‰é’® -->
            <template v-else-if="getDeviceType(deviceKey) === 'material'">
              <el-button type="primary" size="small"
                @mousedown="startInitHold(deviceKey)"
                @mouseup="cancelInitHold(deviceKey)"
                @mouseleave="cancelInitHold(deviceKey)"
                @touchstart.prevent="startInitHold(deviceKey)"
                @touchend="cancelInitHold(deviceKey)"
                @touchcancel="cancelInitHold(deviceKey)"
                :loading="loadingStates[deviceKey]?.cs">
                {{ getInitializationButtonText(deviceData) }}
              </el-button>
              <el-button :type="getModeButtonType(deviceData)" size="small" @click="handleModeToggle(deviceKey)"
                :loading="loadingStates[deviceKey]?.modeToggle">
                {{ getCurrentModeText(deviceData) }}
              </el-button>
              <el-button type="warning" size="small" @click="handleOperation(deviceKey, 'fw')"
                :loading="loadingStates[deviceKey]?.fw">
                å¤ä½
              </el-button>
              <!-- é›·è¾¾å¼€å…³æŒ‰é’® -->
              <el-button :type="getRadarButtonType(deviceData)" size="small" @click="handleRadarToggle(deviceKey)"
                :loading="loadingStates[deviceKey]?.radarToggle" :disabled="!isManualMode(deviceData)">
                {{ getRadarButtonText(deviceData) }}
              </el-button>
              <!-- é•¿æŒ‰ï¼šä¸Šå‡/下降(按下=1,松开=0) -->
              <el-button type="success" size="small"
                @pointerdown.prevent="handleLiftPress(deviceKey, 'ss')"
                @pointerup="handleLiftRelease(deviceKey, 'ss')"
                @mousedown.prevent="handleLiftPress(deviceKey, 'ss')"
                @mouseup="handleLiftRelease(deviceKey, 'ss')"
                @touchstart.prevent="handleLiftPress(deviceKey, 'ss')"
                @touchend="handleLiftRelease(deviceKey, 'ss')"
                @touchcancel="handleLiftRelease(deviceKey, 'ss')"
                :disabled="!isManualMode(deviceData) || deviceData['上升信号到位'] === 1"
                :loading="activeLift && activeLift.deviceKey === deviceKey && activeLift.operationType === 'ss'">
                ä¸Šå‡
              </el-button>
              <el-button type="info" size="small"
                @pointerdown.prevent="handleLiftPress(deviceKey, 'xj')"
                @pointerup="handleLiftRelease(deviceKey, 'xj')"
                @pointercancel="handleLiftRelease(deviceKey, 'xj')"
                :disabled="!isManualMode(deviceData) || deviceData['下降信号到位'] === 1"
                :loading="activeLift && activeLift.deviceKey === deviceKey && activeLift.operationType === 'xj'">
                ä¸‹é™
              </el-button>
              <!-- ä¿éšœï¼šæ‰‹åŠ¨åœæ­¢æŒ‰é’®ï¼ˆç‚¹å‡»å³å‘0) -->
              <el-button type="warning" size="small"
                @click="stopLift(deviceKey, 'ss')"
                :disabled="!isManualMode(deviceData)">
                åœæ­¢ä¸Šå‡
              </el-button>
              <el-button type="warning" size="small"
                @click="stopLift(deviceKey, 'xj')"
                :disabled="!isManualMode(deviceData)">
                åœæ­¢ä¸‹é™
              </el-button>
            </template>
          </div>
          <!-- åœ°å€æ“ä½œåŒºåŸŸï¼ˆå®‰å…¨é—¨å’Œç«™å°ç›‘控时不显示) -->
          <div class="address-operation"
            v-if="currentMonitorType !== 'safetydoor' && currentMonitorType !== 'platform'">
            <div class="address-title">地址操作</div>
            <div class="address-input-group">
              <el-select v-model="addressValues[deviceKey]" placeholder="选择目标地址" size="small"
                style="width: 200px; margin-right: 10px;">
                <el-option v-for="address in getDeviceAddresses(deviceKey)" :key="address.value" :label="address.label"
                  :value="address.value" />
              </el-select>
              <el-button type="info" size="small" @click="handleAddressOperation(deviceKey)"
                :loading="loadingStates[deviceKey]?.dz" :disabled="!addressValues[deviceKey]">
                å‰å¾€åœ°å€
              </el-button>
            </div>
          </div>
        </div>
      </div>
    </div>
    <!-- å…¨å±€æ“ä½œé¢æ¿ï¼ˆå®‰å…¨é—¨å’Œç«™å°ç›‘控时不显示) -->
    <div class="operation-panel" v-if="currentMonitorType !== 'safetydoor' && currentMonitorType !== 'platform'">
      <!-- <el-button type="primary" @click="showOperationDialog = true">高级操作</el-button> -->
    </div>
    <!-- æ“ä½œå¯¹è¯æ¡† -->
    <el-dialog v-model="showOperationDialog" title="设备高级操作" width="600px">
      <div>设备初始化、写入参数等高级操作界面</div>
    </el-dialog>
  </div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
  setup() {
  name: 'RgvMonitor',
  data() {
    return {
      currentMonitorType: 'inbound', // 'inbound' æˆ– 'outbound' æˆ– 'safetydoor' æˆ– 'platform'
      isMonitoring: false,
      monitoringLoading: false,
      showOperationDialog: false,
      pollingTimer: null,
      loadingStates: {},
      addressValues: {},
      // é•¿æŒ‰åˆå§‹åŒ–相关
      holdTimers: {}, // è®¾å¤‡çº§åˆå§‹åŒ–长按定时器
      oneClickInitTimer: null, // ä¸€é”®åˆå§‹åŒ–长按定时器
      holdDuration: 3000, // æŒ‰ä½3秒触发
      activeLift: null, // { deviceKey, operationType } å½“前按下中的升降操作
      monitoringPaused: false, // é•¿æŒ‰æœŸé—´æš‚停监控刷新
      liftPressPromises: {}, // key: `${deviceKey}-${operationType}` -> Promise for press(1)
      signalPollingTimer: null, // å‡é™é•¿æŒ‰æœŸé—´çš„轻量信号轮询
      signalPollInterval: 1200, // è½»é‡ä¿¡å·è½®è¯¢é—´éš”(ms)
      lastLiftPressAt: {}, // è®°å½•每个(deviceKey-op)最后按下的时间戳
      liftReleaseInProgress: {}, // æ­£åœ¨å‘送释放(0)的标记,防重复
      // ç‚¹å‡»å¼å‡é™æŽ§åˆ¶çŠ¶æ€
      activeLiftStates: {}, // { [deviceKey]: { ss: boolean, xj: boolean } }
      liftAutoStopTimeouts: {}, // { [deviceKey-op]: timeoutId }
      liftCommandTimeoutMs: 10000, // å‡é™æŒ‡ä»¤æœ€å¤§æŒç»­æ—¶é—´ï¼Œè¶…时自动停止
      // ä¸€é”®æ“ä½œåŠ è½½çŠ¶æ€
      oneClickLoading: {
        init: false,
        reset: false,
        start: false,
        stop: false
      },
      // è®¾å¤‡æ•°æ®
      inboundDeviceData: {
        rgV101: null,
        rgV103: null,
        rgV104: null,
        rgV105: null,
        rgV107: null,
        rgV108: null,
        rgV109: null,
      },
      outboundDeviceData: {
        rgV116: null,
        rgV115: null,
        rgV111: null,
        rgV112: null,
        rgV110: null,
        rgV114: null,
        rgV118: null
      },
      safetyDoorDeviceData: {
        aqm001: null,
        aqm002: null,
        aqm003: null
      },
      platformDeviceData: {
        '1001': null,
        '1002': null,
        '2016': null,
        '2017': null,
        '2018': null,
        '2019': null,
        '1021': null,
        '1061': null,
        '1131': null,
        '1171': null
      },
      // å­—段显示名称映射
      fieldDisplayNames: {
        '工作模式': '工作模式',
        '当前位置': '当前位置',
        '有货状态': '有货状态',
        '目标地址': '目标地址',
        '任务状态': '任务状态',
        'rgV任务编号': '任务编号',
        '故障代码': '故障代码',
        '上升信号到位': '上升信号',
        '下降信号到位': '下降信号',
        '初始化未完成标志位': '初始化状态',
        '雷达状态': '雷达状态',
        '货叉状态': '货叉状态',
        // å®‰å…¨é—¨å­—段
        '安全门指示灯状态': '指示灯状态',
        '安全门请求开门': '请求开门',
        '安全门断电状态': '断电状态',
        '安全门急停状态': '急停状态',
        '安全门锁状态': '门锁状态',
        '安全门复位状态': '复位状态',
        '报警信息': '报警信息',
        '开门信息': '开门信息',
        // ç«™å°å­—段
        '光电信号': '光电信号',
        '任务id': '任务ID'
      },
      // è®¾å¤‡æ˜¾ç¤ºåç§°æ˜ å°„
      deviceDisplayNames: {
        // å…¥åº“设备
        'rgV101': 'RGV101 - åŽŸæ–™å…¥åº“è½¦',
        'rgV103': 'RGV103 - æ¯è½¦',
        'rgV104': 'RGV104 - å­è½¦',
        'rgV105': 'RGV105 - æ¯è½¦',
        'rgV107': 'RGV107 - å­è½¦',
        'rgV108': 'RGV108 - æ¯è½¦',
        'rgV109': 'RGV109 - æ¯è½¦',
        // å‡ºåº“设备
        'rgV110': 'RGV110 - æ¯è½¦',
        'rgV111': 'RGV111 - å­è½¦',
        'rgV112': 'RGV112 - æ¯è½¦',
        'rgV114': 'RGV114 - æ¯è½¦',
        'rgV115': 'RGV115 - æ¯è½¦',
        'rgV116': 'RGV116 - å­è½¦',
        'rgV118': 'RGV118 - åŽŸæ–™å‡ºåº“è½¦',
        // å®‰å…¨é—¨è®¾å¤‡
        'aqm001': 'AQM001 - 1#安全门',
        'aqm002': 'AQM002 - 2#安全门',
        'aqm003': 'AQM003 - 3#安全门',
        // ç«™å°è®¾å¤‡
        '1001': '1001 - 1#站台',
        '1002': '1002 - 2#站台',
        '2016': '2016 - 3#站台',
        '2017': '2017 - 4#站台',
        '2018': '2018 - 5#站台',
        '2019': '2019 - 6#站台',
        '1021': '1021 - 7#站台',
        '1061': '1061 - 8#站台',
        '1131': '1131 - 9#站台',
        '1171': '1171 - 10#站台'
      },
      // æ“ä½œç±»åž‹æ˜ å°„
      operationTypes: {
        'cs': '初始化',
        'sd': '切换到手动模式',
        'zd': '切换到自动模式',
        'dz': '前往地址',
        'kld': '开雷达',
        'gld': '关雷达',
        'fw': '复位',
        'ss': '上升',
        'xj': '下降',
      },
      // æŠ¥è­¦ä»£ç æ˜ å°„
      motherCarAlarmCodes: {
        0: '无报警',
        1: 'RGV小车急停被按下',
        2: '前进限位报警',
        3: '后退限位报警',
        4: 'PLC模块故障',
        5: 'PLC扩展模块故障',
        6: 'RGV长时间空转故障',
        7: '目的地不等于实际位置故障',
        8: '与总控通讯故障',
        9: '行走变频器故障',
        10: '取货时自身有货物报警',
        11: '放货时自身无货物报警',
        12: '停止时位置过冲报警'
      },
      childCarAlarmCodes: {
        0: '无报警',
        1: 'RGV小车急停被按下',
        2: '前进限位报警',
        3: '后退限位报警',
        4: 'PLC模块故障',
        5: 'PLC扩展模块故障',
        6: '扫码定位故障',
        7: 'RGV长时间空转故障',
        8: '目的地不等于实际位置故障',
        9: '与总控通讯故障',
        10: '行走变频器故障',
        11: '液压单元过载保护故障',
        12: '液压上升超时报警',
        13: '液压下降超时报警',
        14: '取货时自身有货物报警',
        15: '放货时自身无货物报警',
        16: '取货检测不到货物报警'
      },
      // åœ¨ data() ä¸­ä¿®æ”¹ materialCarAlarmCodes
      materialCarAlarmCodes: {
        0: '无报警',
        1: 'RGV小车急停被按下',
        2: '正转雷达报警',
        3: '反转雷达报警',
        4: '前进限位报警',
        5: '后退限位报警',
        6: '',
        7: 'PLC模块故障',
        8: 'PLC扩展模块故障',
        9: '称重模块故障',
        10: '扫码定位故障',
        11: 'RGV长时间空转故障',
        12: '目的地不等于实际位置故障',
        13: '与总控通讯故障',
        14: '前雷达屏蔽警告',
        15: '后雷达屏蔽警告',
        16: '行走变频器故障',
        17: '伸缩叉变频器故障',
        18: '液压单元过载保护故障',
        19: '液压上升超时报警',
        20: '液压下降超时报警',
        21: '伸缩叉伸出超时报警',
        22: '伸缩叉缩回超时报警',
        23: '外形检测报警',
        24: '称重超重报警',
        25: '货叉伸出极限限位报警',
        26: '货叉缩回极限限位报警',
        27: '取货时自身有货物报警',
        28: '放货时自身无货物报警',
        29: '货叉未回到初始位报警',
        30: '触发仅移动命令时货叉不在初始位报警',
        31: '货叉到达初始位但中位传感器未检测到报警',
        32: '行走轴没到位禁止货叉伸出',
        33: '取货异常报警',
        34: '放货异常报警',
        35: '外型检测-前超出报警',
        36: '外型检测-后超出报警',
        37: '外型检测-左超出报警',
        38: '外型检测-右超出报警',
        39: '外型检测-上超出报警'
      },
      // å®‰å…¨é—¨æŒ‡ç¤ºç¯çŠ¶æ€æ˜ å°„
      safetyDoorLightCodes: {
        0: '无输出',
        1: '红灯常亮+蜂鸣',
        2: '绿灯常亮',
        3: '黄灯闪烁(2HZ)',
        4: '黄灯常亮'
      },
      // è®¾å¤‡ç±»åž‹åˆ†ç±»
      deviceTypes: {
        // å…¥åº“设备
        motherCars: ['rgV103', 'rgV105', 'rgV108', 'rgV109'],
        childCars: ['rgV104', 'rgV107'],
        materialCars: ['rgV101'],
        // å‡ºåº“设备
        outboundMotherCars: ['rgV110', 'rgV112', 'rgV114', 'rgV115'],
        outboundChildCars: ['rgV111', 'rgV116'],
        outboundMaterialCars: ['rgV118'],
        // å®‰å…¨é—¨è®¾å¤‡
        safetyDoors: ['aqm001', 'aqm002', 'aqm003'],
        // ç«™å°è®¾å¤‡
        platforms: ['1001', '1002', '2016', '2017', '2018', '2019', '1021', '1061', '1131', '1171']
      },
      // è®¾å¤‡åœ°å€æ˜ å°„
      deviceAddresses: {
        // å…¥åº“设备地址
        'rgV101': [
          { value: '1001', label: '1001 - åŽŸæ–™å…¥åº“ä½' },
          { value: '1002', label: '1002 - åŽŸæ–™å‡ºåº“ä½' },
          { value: '1021', label: '1021 - ä¸­è½¬ä½' }
        ],
        'rgV103': [
          { value: '1031', label: '1031 - æ¯è½¦åœè½¦ä½1' },
          { value: '1032', label: '1032 - æ¯è½¦åœè½¦ä½2' }
        ],
        'rgV104': [
          { value: '3', label: '停靠点1' },
          { value: '5', label: '停靠点2' },
          { value: '1031', label: '1031 - å­è½¦å–货位' },
          { value: '1051', label: '1051 - å­è½¦å–货位' }
        ],
        'rgV105': [
          { value: '1051', label: '1051 - æ¯è½¦åœè½¦ä½1' },
          { value: '1052', label: '1052 - æ¯è½¦åœè½¦ä½2' }
        ],
        'rgV107': [
          { value: '3', label: '停靠点1' },
          { value: '5', label: '停靠点2' },
          { value: '1081', label: '1081 - å­è½¦å–货位' },
          { value: '1091', label: '1091 - å­è½¦å–货位' }
        ],
        'rgV108': [
          { value: '1081', label: '1081 - æ¯è½¦åœè½¦ä½1' },
          { value: '1082', label: '1082 - æ¯è½¦åœè½¦ä½2' }
        ],
        'rgV109': [
          { value: '1091', label: '1091 - æ¯è½¦åœè½¦ä½1' },
          { value: '1092', label: '1092 - æ¯è½¦åœè½¦ä½2' }
        ],
        // å‡ºåº“设备地址
        'rgV110': [
          { value: '1101', label: '1101 - æ¯è½¦åœè½¦ä½1' },
          { value: '1102', label: '1102 - æ¯è½¦åœè½¦ä½2' }
        ],
        'rgV111': [
          { value: '3', label: '停靠点1' },
          { value: '5', label: '停靠点2' },
          { value: '1101', label: '1101 - å­è½¦å–货位' },
          { value: '1121', label: '1121 - å­è½¦å–货位' }
        ],
        'rgV112': [
          { value: '1121', label: '1121 - æ¯è½¦åœè½¦ä½1' },
          { value: '1122', label: '1122 - æ¯è½¦åœè½¦ä½2' }
        ],
        'rgV114': [
          { value: '1141', label: '1141 - æ¯è½¦åœè½¦ä½1' },
          { value: '1142', label: '1142 - æ¯è½¦åœè½¦ä½2' },
        ],
        'rgV115': [
          { value: '1151', label: '1151 - æ¯è½¦åœè½¦ä½1' },
          { value: '1152', label: '1152 - æ¯è½¦åœè½¦ä½2' }
        ],
        'rgV116': [
          { value: '3', label: '停靠点1' },
          { value: '5', label: '停靠点2' },
          { value: '1151', label: '1151 - å­è½¦å–货位' },
          { value: '1141', label: '1141 - å­è½¦å–货位' }
        ],
        'rgV118': [
          { value: '2016', label: '2016 - åŽŸæ–™å¤„ç†ä½1' },
          { value: '2017', label: '2017 - åŽŸæ–™å¤„ç†ä½2' },
          { value: '2018', label: '2018 - åŽŸæ–™å¤„ç†ä½3' },
          { value: '2019', label: '2019 - åŽŸæ–™å¤„ç†ä½4' }
        ]
      },
      // ç‰¹æ®Šå­—段的值格式化
      valueFormatters: {
        '工作模式': (value) => {
          const modes = { 0: '手动模式', 1: '自动模式' }
          return modes[value] || `未知模式(${value})`
        },
        '有货状态': (value) => value === 0 ? '无货' : '有货',
        '任务状态': (value) => {
          const statusMap = { 0: '空闲', 1: '执行中', 2: '完成' }
          return statusMap[value] || `未知状态(${value})`
        },
        '故障代码': (value, deviceKey) => {
          return this.getAlarmText(value, deviceKey)
        },
        '上升信号到位': (value) => value === 0 ? '未到位' : '已到位',
        '下降信号到位': (value) => value === 0 ? '未到位' : '已到位',
        '初始化未完成标志位': (value) => value === 0 ? '已完成' : '未完成',
        '雷达状态': (value) => value === 0 ? '关闭' : '开启',
        '货叉状态': (value) => value === 0 ? '缩回' : '伸出'
      },
      // å®‰å…¨é—¨å­—段值格式化
      safetyDoorValueFormatters: {
        '安全门指示灯状态': (value) => {
          return this.safetyDoorLightCodes[value] || `未知状态(${value})`
        },
        '安全门请求开门': (value) => value === 0 ? '正常' : '请求开门',
        '安全门断电状态': (value) => value === 0 ? '断电' : '上电',
        '安全门急停状态': (value) => value === 0 ? '急停' : '正常',
        '安全门锁状态': (value) => value === 0 ? '开门' : '关门',
        '安全门复位状态': (value) => value === 0 ? '正常' : '复位中',
        '报警信息': (value) => value === 0 ? '正常' : '报警',
        '开门信息': (value) => value === 0 ? '关门' : '开门'
      },
      // ç«™å°å­—段值格式化
      platformValueFormatters: {
        '光电信号': (value) => value === 0 ? '无信号' : '有信号',
        '任务id': (value) => value === 0 ? '无任务' : `任务${value}`
      }
    }
  },
  computed: {
    // å½“前显示的设备数据
    currentDeviceData() {
      switch (this.currentMonitorType) {
        case 'inbound':
          return this.inboundDeviceData
        case 'outbound':
          return this.outboundDeviceData
        case 'safetydoor':
          return this.safetyDoorDeviceData
        case 'platform':
          return this.platformDeviceData
        default:
          return this.inboundDeviceData
      }
    }
  },
  methods: {
    // æš‚停/恢复监控轮询
    pauseMonitoring() {
      this.monitoringPaused = true
      if (this.pollingTimer) {
        clearTimeout(this.pollingTimer)
        this.pollingTimer = null
      }
    },
    resumeMonitoring() {
      // ä»…当没有任何长按/升降在进行时才恢复
      if (!this.activeLift && !this.isHoldActive()) {
        this.monitoringPaused = false
        if (this.isMonitoring && !this.pollingTimer) {
          this.startPolling()
        }
      }
    },
    // å‡é™é•¿æŒ‰æœŸé—´ä»…轮询当前设备的"上/下到位"信号,避免全量刷新(实例方法)
    startLiftSignalPolling(deviceKey) {
      this.stopLiftSignalPolling()
      this.signalPollingTimer = setInterval(async () => {
        // ä»…在当前仍处于该设备的升降长按且监控开启时有效,否则自动停止
        if (!this.isMonitoring || !this.activeLift || this.activeLift.deviceKey !== deviceKey) {
          this.stopLiftSignalPolling()
          return
        }
        try {
          const response = await this.http.post('api/Rgvoperainform/GetDeviceStatusDto', {
            off: 1,
            monitorType: this.currentMonitorType
          })
          if (response && response.status && response.data) {
            const keys = Object.keys(response.data)
            const respKey = keys.find(k => k.toLowerCase() === String(deviceKey).toLowerCase())
            const dev = respKey ? response.data[respKey] : null
            if (!dev) return
            const updateMap = (mapObj) => {
              if (!mapObj) return null
              const devOld = mapObj[deviceKey] || {}
              const patch = { ...devOld }
              const riseVal = Object.prototype.hasOwnProperty.call(dev, '上升信号到位') ? dev['上升信号到位']
                : (Object.prototype.hasOwnProperty.call(dev, 'RGV_Risingsignalplace') ? dev['RGV_Risingsignalplace']
                : (Object.prototype.hasOwnProperty.call(dev, 'RiseArrived') ? dev['RiseArrived'] : undefined))
              const downVal = Object.prototype.hasOwnProperty.call(dev, '下降信号到位') ? dev['下降信号到位']
                : (Object.prototype.hasOwnProperty.call(dev, 'RGV_Descentsignal') ? dev['RGV_Descentsignal']
                : (Object.prototype.hasOwnProperty.call(dev, 'DescendArrived') ? dev['DescendArrived'] : undefined))
              if (riseVal !== undefined) patch['上升信号到位'] = riseVal
              if (downVal !== undefined) patch['下降信号到位'] = downVal
              return { ...mapObj, [deviceKey]: patch }
            }
            let patched = false
            const tryPatch = (getter, setter) => {
              const next = updateMap(getter)
              if (next) {
                setter(next)
                patched = true
              }
            }
            if (this.currentMonitorType === 'inbound') tryPatch(this.inboundDeviceData, v => this.inboundDeviceData = v)
            if (this.currentMonitorType === 'outbound') tryPatch(this.outboundDeviceData, v => this.outboundDeviceData = v)
            if (this.currentMonitorType === 'safetydoor') tryPatch(this.safetyDoorDeviceData, v => this.safetyDoorDeviceData = v)
            if (this.currentMonitorType === 'platform') tryPatch(this.platformDeviceData, v => this.platformDeviceData = v)
            if (!patched) {
              tryPatch(this.inboundDeviceData, v => this.inboundDeviceData = v)
              tryPatch(this.outboundDeviceData, v => this.outboundDeviceData = v)
              tryPatch(this.safetyDoorDeviceData, v => this.safetyDoorDeviceData = v)
              tryPatch(this.platformDeviceData, v => this.platformDeviceData = v)
            }
            if (typeof this.$forceUpdate === 'function') this.$forceUpdate()
          }
        } catch (e) {
          // å¿½ç•¥è½»é‡è½®è¯¢é”™è¯¯
        }
      }, this.signalPollInterval)
    },
    stopLiftSignalPolling() {
      if (this.signalPollingTimer) {
        clearInterval(this.signalPollingTimer)
        this.signalPollingTimer = null
      }
    },
    // æ˜¯å¦å­˜åœ¨é•¿æŒ‰ä¸­çš„初始化(设备级或一键)
    isHoldActive() {
      if (this.oneClickInitTimer) return true
      // holdTimers ä¸­æœ‰ä»»ä½•一个有效定时器即视为长按中
      return Object.values(this.holdTimers || {}).some(t => !!t)
    },
    // å¼€å§‹è®¾å¤‡çº§åˆå§‹åŒ–长按
    startInitHold(deviceKey) {
      this.pauseMonitoring()
      if (this.holdTimers[deviceKey]) {
        clearTimeout(this.holdTimers[deviceKey])
      }
      this.holdTimers[deviceKey] = setTimeout(() => {
        this.handleOperation(deviceKey, 'cs')
        this.holdTimers[deviceKey] = null
        // åˆå§‹åŒ–触发完成后,恢复监控并确保无残留轻量轮询
        this.stopLiftSignalPolling()
        this.resumeMonitoring()
      }, this.holdDuration)
    },
    // å–消设备级初始化长按
    cancelInitHold(deviceKey) {
      const t = this.holdTimers[deviceKey]
      if (t) {
        clearTimeout(t)
        this.holdTimers[deviceKey] = null
        // å–消初始化长按,恢复监控并停止轻量轮询
        this.stopLiftSignalPolling()
        this.resumeMonitoring()
      }
    },
    // å¼€å§‹ä¸€é”®åˆå§‹åŒ–长按
    startOneClickInitHold() {
      this.pauseMonitoring()
      if (this.oneClickInitTimer) {
        clearTimeout(this.oneClickInitTimer)
      }
      this.oneClickInitTimer = setTimeout(() => {
        this.handleOneClickOperation('init')
        this.oneClickInitTimer = null
        this.resumeMonitoring()
      }, this.holdDuration)
    },
    // å–消一键初始化长按
    cancelOneClickInitHold() {
      if (this.oneClickInitTimer) {
        clearTimeout(this.oneClickInitTimer)
        this.oneClickInitTimer = null
        this.resumeMonitoring()
      }
    },
    // æŒ‰ä¸‹å‡é™ï¼šå‘送参数1
    async handleLiftPress(deviceKey, operationType) {
      // operationType: 'ss' or 'xj'
      console.log('[LIFT][PRESS] event received', { deviceKey, operationType, ts: Date.now() })
      if (this.activeLift && this.activeLift.deviceKey === deviceKey && this.activeLift.operationType === operationType) {
        console.log('[LIFT][PRESS] skipped because same activeLift exists')
        return
      }
      this.activeLift = { deviceKey, operationType }
      // ä»…当已开启监控时,才暂停全局轮询并开启轻量信号轮询
      if (this.isMonitoring) {
        this.pauseMonitoring()
        this.startLiftSignalPolling(deviceKey)
      }
      // è®°å½•按下时间戳
      this.lastLiftPressAt[`${deviceKey}-${operationType}`] = Date.now()
      // ä¸ç­‰å¾…,序列化存储Promise,保证release时按顺序发送
      const key = `${deviceKey}-${operationType}`
      console.log('[LIFT][PRESS] sending 1', { key, DelKeys: [deviceKey, operationType, 1] })
      this.liftPressPromises[key] = this.http.post('api/Rgvoperainform/DeviceOperation', {
        DelKeys: [deviceKey, operationType, 1],
        Extra: true
      }).then((response) => {
        console.log('[LIFT][PRESS] response', { key, status: response && response.status, message: response && response.message })
        if (!response.status) {
          this.$message.error(response.message || `${this.operationTypes[operationType]}失败`)
        }
      }).catch((error) => {
        console.error('[LIFT][PRESS] error', { key, error })
        this.$message.error(`${this.operationTypes[operationType]}请求失败: ` + error.message)
      })
    },
    // æ¾å¼€å‡é™ï¼šå‘送参数0
    async handleLiftRelease(deviceKey, operationType) {
      console.log('[LIFT][RELEASE] event received', { deviceKey, operationType, ts: Date.now() })
      if (!this.activeLift || this.activeLift.deviceKey !== deviceKey || this.activeLift.operationType !== operationType) {
        console.log('[LIFT][RELEASE] skipped because activeLift not match', { activeLift: this.activeLift })
        return
      }
      const key = `${deviceKey}-${operationType}`
      if (this.liftReleaseInProgress[key]) {
        // å·²æœ‰é‡Šæ”¾è¯·æ±‚在进行,避免重复发送0(可能来自元素事件+全局事件的双触发)
        console.log('[LIFT][RELEASE] skipped because release in progress', { key })
        return
      }
      this.liftReleaseInProgress[key] = true
      // é‡Šæ”¾ä¸€å¼€å§‹å°±åœæ­¢è½»é‡è½®è¯¢ï¼Œé¿å…ç»§ç»­å ç”¨ç½‘络导致0发送延迟
      this.stopLiftSignalPolling()
      // é˜²æ­¢æŒ‰ä¸‹åŽæžçŸ­æ—¶é—´å†…就触发释放导致0先于1或"看起来只发了0"
      const pressedAt = this.lastLiftPressAt[key] || 0
      const elapsed = Date.now() - pressedAt
      console.log('[LIFT][RELEASE] timing', { key, pressedAt, elapsed })
      if (elapsed < 120) {
        console.log('[LIFT][RELEASE] delaying to ensure press first', { delayMs: 120 - elapsed })
        await new Promise(r => setTimeout(r, 120 - elapsed))
      }
      try {
        const pressPromise = this.liftPressPromises[key]
        if (pressPromise) {
          console.log('[LIFT][RELEASE] awaiting press promise', { key })
          // åŠ è¶…æ—¶ä¿æŠ¤ï¼Œè‹¥press长时间未返回,也不要无限等,最多等300ms
          const timeout = new Promise((_, rej) => setTimeout(() => rej(new Error('press wait timeout')), 300))
          await Promise.race([pressPromise, timeout]).catch(err => {
            console.warn('[LIFT][RELEASE] press await timeout or error, continue to send 0', err && err.message)
          })
        }
        console.log('[LIFT][RELEASE] sending 0', { key, DelKeys: [deviceKey, operationType, 0] })
        const response = await this.http.post('api/Rgvoperainform/DeviceOperation', {
          DelKeys: [deviceKey, operationType, 0],
          Extra: true
        })
        console.log('[LIFT][RELEASE] response', { key, status: response && response.status, message: response && response.message })
        if (!response.status) {
          this.$message.error(response.message || `${this.operationTypes[operationType]}停止失败`)
        }
      } catch (error) {
        console.error('[LIFT][RELEASE] error', { key, error })
        this.$message.error(`${this.operationTypes[operationType]}停止请求失败: ` + error.message)
      } finally {
        console.log('[LIFT][RELEASE] finalize cleanup', { key })
        this.activeLift = null
        delete this.liftPressPromises[key]
        delete this.lastLiftPressAt[key]
        delete this.liftReleaseInProgress[key]
        this.stopLiftSignalPolling()
        // æ¢å¤å…¨å±€2s轮询
        this.resumeMonitoring()
      }
    },
    // å…¥åº“继续任务
    async handleInNormal(deviceKey) {
      this.setLoadingState(deviceKey, 'inNormal', true);
      try {
        const response = await this.http.post("api/RgvOperation/WriteInNormal", {}, "数据处理中...");
        if (response.status) {
          this.$message.success('入库继续任务成功');
          if (this.isMonitoring) this.refreshData();
        } else {
          this.$message.error(response.message || '入库继续任务失败');
        }
      } catch (error) {
        this.$message.error('入库继续任务请求失败: ' + error.message);
      } finally {
        this.setLoadingState(deviceKey, 'inNormal', false);
      }
    },
    // å…¥åº“异常排除
    async handleInAbnormal(deviceKey) {
      this.setLoadingState(deviceKey, 'inAbnormal', true);
      try {
        const response = await this.http.post("api/RgvOperation/WriteInAbnormal", {}, "数据处理中...");
        if (response.status) {
          this.$message.success('入库异常排除成功');
          if (this.isMonitoring) this.refreshData();
        } else {
          this.$message.error(response.message || '入库异常排除失败');
        }
      } catch (error) {
        this.$message.error('入库异常排除请求失败: ' + error.message);
      } finally {
        this.setLoadingState(deviceKey, 'inAbnormal', false);
      }
    },
    // èŽ·å–ç›‘æŽ§çŠ¶æ€æ ‡é¢˜
    getMonitorStatusTitle() {
      const typeNames = {
        'inbound': '入库',
        'outbound': '出库',
        'safetydoor': '安全门',
        'platform': '站台'
      }
      const status = this.isMonitoring ? '运行中' : '已停止'
      return `${typeNames[this.currentMonitorType]}监控状态: ${status}`
    },
    // å®‰å…¨é—¨å­—段值格式化
    getSafetyDoorFormattedValue(fieldKey, value) {
      const formatter = this.safetyDoorValueFormatters[fieldKey]
      return formatter ? formatter(value) : value
    },
    // å®‰å…¨é—¨å­—段值样式
    getSafetyDoorValueClass(fieldKey, value) {
      if (fieldKey === '安全门急停状态' && value === 0) {
        return 'fault-text'
      }
      if (fieldKey === '安全门断电状态' && value === 0) {
        return 'fault-text'
      }
      if (fieldKey === '报警信息' && value === 1) {
        return 'fault-text'
      }
      return ''
    },
    // ç«™å°å­—段值格式化
    getPlatformFormattedValue(fieldKey, value) {
      const formatter = this.platformValueFormatters[fieldKey]
      return formatter ? formatter(value) : value
    },
    // ç«™å°å­—段值样式
    getPlatformValueClass(fieldKey, value) {
      if (fieldKey === '光电信号' && value === 1) {
        return 'normal-text'
      }
      return ''
    },
    // å®‰å…¨é—¨æŒ‡ç¤ºç¯æ ·å¼
    getSafetyLightClass(value) {
      const classMap = {
        0: 'light-off',
        1: 'light-red',
        2: 'light-green',
        3: 'light-yellow-blink',
        4: 'light-yellow'
      }
      return classMap[value] || 'light-off'
    },
    // æ€¥åœçŠ¶æ€æ ·å¼
    getEmergencyStopClass(value) {
      return value === 0 ? 'emergency-stop-active' : 'emergency-stop-normal'
    },
    // ä¸€é”®æ“ä½œå¤„理
    async handleOneClickOperation(operationType) {
      // æ ¹æ®å½“前监控类型设置参数
      const monitorType = this.currentMonitorType === 'inbound' ? 'Inbound' : 'Outbound'
      this.oneClickLoading[operationType] = true
      try {
        const operationNames = {
          init: '一键初始化',
          reset: '一键复位',
          start: '一键启动',
          stop: '一键暂停'
        }
        const response = await this.http.post('api/Rgvoperainform/OneClickOperation', {
          operationType: operationType,
          monitorType: monitorType
        }, `${operationNames[operationType]}中...`)
        if (response.status) {
          this.$message.success(`${operationNames[operationType]} ${monitorType}端设备成功`)
          this.refreshData()
        } else {
          this.$message.error(response.message || `${operationNames[operationType]}失败`)
        }
      } catch (error) {
        this.$message.error(`${operationNames[operationType]}请求失败: ` + error.message)
      } finally {
        this.oneClickLoading[operationType] = false
      }
    },
    // èŽ·å–åˆå§‹åŒ–æŒ‰é’®æ˜¾ç¤ºæ–‡æœ¬
    getInitializationButtonText(deviceData) {
      if (!deviceData) return '初始化'
      return deviceData['初始化未完成标志位'] === 0 ? '已初始化' : '初始化'
    },
    // ç‚¹å‡»å¼å‡é™ï¼šçŠ¶æ€æŸ¥è¯¢
    isLifting(deviceKey) {
      const st = this.activeLiftStates[deviceKey]
      return !!(st && st.ss === true)
    },
    isLowering(deviceKey) {
      const st = this.activeLiftStates[deviceKey]
      return !!(st && st.xj === true)
    },
    // å¯åŠ¨å‡é™ï¼ˆç‚¹å‡»ä¸€æ¬¡å‘1)
    async startLift(deviceKey, operationType) {
      // operationType: 'ss' | 'xj'
      const isManual = this.isManualMode && this.isManualMode(this.currentMonitorDataMap()?.[deviceKey])
      if (!isManual) {
        this.$message.warning('请切换为手动模式后再操作')
        return
      }
      // äº’斥与防重
      const st = this.activeLiftStates[deviceKey] || { ss: false, xj: false }
      if ((operationType === 'ss' && (st.ss || st.xj)) || (operationType === 'xj' && (st.xj || st.ss))) {
        return
      }
      // è®¾ç½®å¯åŠ¨æ€
      this.activeLiftStates = { ...this.activeLiftStates, [deviceKey]: { ...st, [operationType]: true } }
      try {
        const resp = await this.http.post('api/Rgvoperainform/DeviceOperation', {
          DelKeys: [deviceKey, operationType, 1],
          Extra: true
        })
        if (!resp.status) {
          this.$message.error((this.operationTypes && this.operationTypes[operationType]) ? `${this.operationTypes[operationType]}失败` : '指令发送失败')
          // å›žæ»šçŠ¶æ€
          const cur = this.activeLiftStates[deviceKey] || {}
          this.activeLiftStates = { ...this.activeLiftStates, [deviceKey]: { ...cur, [operationType]: false } }
          return
        }
        // æˆåŠŸåŽå¯åŠ¨è¶…æ—¶è‡ªåŠ¨åœæ­¢
        const key = `${deviceKey}-${operationType}`
        this.clearLiftTimeout(key)
        this.liftAutoStopTimeouts[key] = setTimeout(() => {
          // è¶…时自动发送0
          this.stopLift(deviceKey, operationType, true)
          this.$message.warning('升降动作超时,已自动停止')
        }, this.liftCommandTimeoutMs)
      } catch (e) {
        this.$message.error('请求失败: ' + e.message)
        // å›žæ»šçŠ¶æ€
        const cur = this.activeLiftStates[deviceKey] || {}
        this.activeLiftStates = { ...this.activeLiftStates, [deviceKey]: { ...cur, [operationType]: false } }
      }
    },
    // åœæ­¢å‡é™ï¼ˆç‚¹å‡»ä¸€æ¬¡å‘0)
    async stopLift(deviceKey, operationType, fromTimeout = false) {
      // å³ä½¿æœªè®°å½•为启动态,也允许发送0,保证停止按钮在手动模式下始终可用
      const st = this.activeLiftStates[deviceKey] || { ss: false, xj: false }
      try {
        const resp = await this.http.post('api/Rgvoperainform/DeviceOperation', {
          DelKeys: [deviceKey, operationType, 0],
          Extra: true
        })
        if (!resp.status && !fromTimeout) {
          this.$message.error((this.operationTypes && this.operationTypes[operationType]) ? `${this.operationTypes[operationType]}停止失败` : '停止指令失败')
          return
        }
      } catch (e) {
        if (!fromTimeout) this.$message.error('停止请求失败: ' + e.message)
      } finally {
        // ç½®ä¸ºåœæ­¢æ€å¹¶æ¸…理超时定时器
        const cur = this.activeLiftStates[deviceKey] || {}
        this.activeLiftStates = { ...this.activeLiftStates, [deviceKey]: { ...cur, [operationType]: false } }
        const key = `${deviceKey}-${operationType}`
        this.clearLiftTimeout(key)
      }
    },
    clearLiftTimeout(key) {
      if (this.liftAutoStopTimeouts[key]) {
        clearTimeout(this.liftAutoStopTimeouts[key])
        delete this.liftAutoStopTimeouts[key]
      }
    },
    // åˆ‡æ¢ç›‘控类型
    switchMonitorType(type) {
      if (this.currentMonitorType !== type) {
        // åœæ­¢å½“前监控
        if (this.isMonitoring) {
          this.stopMonitoring()
        }
        this.currentMonitorType = type
        // å¦‚果之前是监控状态,自动启动新类型的监控
        if (this.isMonitoring) {
          this.startMonitoring()
        }
      }
    },
    // èŽ·å–ä¿¡å·æŒ‡ç¤ºç¯ç±»å
    getSignalClass(value) {
      return value === 1 ? 'signal-active' : 'signal-inactive'
    },
    // ç›‘控控制方法
    async toggleMonitoring() {
      if (this.monitoringLoading) return
      if (this.isMonitoring) {
        this.stopMonitoring()
      } else {
        await this.startMonitoring()
      }
    },
    async startMonitoring() {
      try {
        this.monitoringLoading = true
        const param = {
          off: 1,
          monitorType: this.currentMonitorType
        }
        const response = await this.http.post('api/Rgvoperainform/GetDeviceStatusDto', param)
        if (response.status) {
          this.isMonitoring = true
          // æ ¹æ®ç›‘控类型更新对应的设备数据
          switch (this.currentMonitorType) {
            case 'inbound':
              this.inboundDeviceData = response.data
              break
            case 'outbound':
              this.outboundDeviceData = response.data
              break
            case 'safetydoor':
              this.safetyDoorDeviceData = response.data
              break
            case 'platform':
              this.platformDeviceData = response.data
              break
          }
          this.startPolling()
          this.$message.success(`${this.getMonitorStatusTitle()}已启动`)
        } else {
          this.$message.error(response.message || `启动${this.currentMonitorType}监控失败`)
        }
      } catch (error) {
        this.$message.error('请求失败: ' + error.message)
      } finally {
        this.monitoringLoading = false
      }
    },
    stopMonitoring() {
      this.isMonitoring = false
      if (this.pollingTimer) {
        clearTimeout(this.pollingTimer)
        this.pollingTimer = null
      }
      this.http.post('api/Rgvoperainform/GetDeviceStatusDto', {
        off: 0,
        monitorType: this.currentMonitorType
      })
      this.$message.info(`${this.getMonitorStatusTitle()}已停止`)
    },
    startPolling() {
      this.pollingTimer = setTimeout(() => {
        if (this.isMonitoring && !this.monitoringPaused) {
          this.refreshData()
          this.startPolling()
        }
      }, 2000)
    },
    async refreshData() {
      if (!this.isMonitoring) return
      if (this.monitoringPaused) return
      try {
        const response = await this.http.post('api/Rgvoperainform/GetDeviceStatusDto', {
          off: 1,
          monitorType: this.currentMonitorType
        })
        if (response.status) {
          // åœ¨å‡é™é•¿æŒ‰æ—¶ï¼Œä»…合并当前设备的到位信号,避免大面积重绘造成打断
          if (this.activeLift) {
            const { deviceKey } = this.activeLift
            const respKeys = Object.keys(response.data || {})
            const respKey = respKeys.find(k => k.toLowerCase() === String(deviceKey).toLowerCase())
            const dev = respKey ? response.data[respKey] : null
            const mergeSignals = (mapObj, setter) => {
              if (!mapObj) return false
              const old = mapObj[deviceKey]
              if (!old) return false
              const patch = { ...old }
              const riseVal = Object.prototype.hasOwnProperty.call(dev || {}, '上升信号到位') ? dev['上升信号到位']
                : (Object.prototype.hasOwnProperty.call(dev || {}, 'RGV_Risingsignalplace') ? dev['RGV_Risingsignalplace']
                : (Object.prototype.hasOwnProperty.call(dev || {}, 'RiseArrived') ? dev['RiseArrived'] : undefined))
              const downVal = Object.prototype.hasOwnProperty.call(dev || {}, '下降信号到位') ? dev['下降信号到位']
                : (Object.prototype.hasOwnProperty.call(dev || {}, 'RGV_Descentsignal') ? dev['RGV_Descentsignal']
                : (Object.prototype.hasOwnProperty.call(dev || {}, 'DescendArrived') ? dev['DescendArrived'] : undefined))
              if (riseVal !== undefined) patch['上升信号到位'] = riseVal
              if (downVal !== undefined) patch['下降信号到位'] = downVal
              setter({ ...mapObj, [deviceKey]: patch })
              if (typeof this.$forceUpdate === 'function') this.$forceUpdate()
              return true
            }
            let merged = false
            if (this.currentMonitorType === 'inbound') merged = mergeSignals(this.inboundDeviceData, v => this.inboundDeviceData = v)
            if (this.currentMonitorType === 'outbound') merged = mergeSignals(this.outboundDeviceData, v => this.outboundDeviceData = v)
            if (this.currentMonitorType === 'safetydoor') merged = mergeSignals(this.safetyDoorDeviceData, v => this.safetyDoorDeviceData = v)
            if (this.currentMonitorType === 'platform') merged = mergeSignals(this.platformDeviceData, v => this.platformDeviceData = v)
            if (!merged) {
              merged = mergeSignals(this.inboundDeviceData, v => this.inboundDeviceData = v) ||
                mergeSignals(this.outboundDeviceData, v => this.outboundDeviceData = v) ||
                mergeSignals(this.safetyDoorDeviceData, v => this.safetyDoorDeviceData = v) ||
                mergeSignals(this.platformDeviceData, v => this.platformDeviceData = v)
            }
          } else {
            // éžé•¿æŒ‰ï¼Œæ­£å¸¸æ›¿æ¢æ•°æ®é›†
            switch (this.currentMonitorType) {
              case 'inbound':
                this.inboundDeviceData = response.data
                break
              case 'outbound':
                this.outboundDeviceData = response.data
                break
              case 'safetydoor':
                this.safetyDoorDeviceData = response.data
                break
              case 'platform':
                this.platformDeviceData = response.data
                break
            }
            // ç‚¹å‡»å¼ï¼šæ£€æµ‹åˆ°ä½åŽè‡ªåŠ¨åœæ­¢å¯¹åº”åŠ¨ä½œ
            this.autoStopLiftIfArrived()
          }
        }
      } catch (error) {
        console.error('刷新数据失败:', error)
      }
    },
    // è‹¥è®¾å¤‡åœ¨ç‚¹å‡»å¼å‡é™ä¸­ï¼Œæ£€æµ‹åˆ°ä¸Š/下到位则自动发送停止
    autoStopLiftIfArrived() {
      const dataMap = this.currentMonitorDataMap()
      if (!dataMap) return
      Object.keys(this.activeLiftStates).forEach(deviceKey => {
        const state = this.activeLiftStates[deviceKey] || {}
        const dev = dataMap[deviceKey]
        if (!dev) return
        if (state.ss && (dev['上升信号到位'] === 1 || dev['RGV_Risingsignalplace'] === 1 || dev['RiseArrived'] === 1)) {
          this.stopLift(deviceKey, 'ss')
        }
        if (state.xj && (dev['下降信号到位'] === 1 || dev['RGV_Descentsignal'] === 1 || dev['DescendArrived'] === 1)) {
          this.stopLift(deviceKey, 'xj')
        }
      })
    },
    // è¿”回当前监控类型的数据映射
    currentMonitorDataMap() {
      switch (this.currentMonitorType) {
        case 'inbound': return this.inboundDeviceData
        case 'outbound': return this.outboundDeviceData
        case 'safetydoor': return this.safetyDoorDeviceData
        case 'platform': return this.platformDeviceData
        default: return null
      }
    },
    // å…¶ä»–原有方法保持不变
    getStatusClass(device) {
      if (!device || Object.keys(device).length === 0) return 'offline'
      // å®‰å…¨é—¨çŠ¶æ€åˆ¤æ–­
      if (this.currentMonitorType === 'safetydoor') {
        if (device['安全门急停状态'] === 0) return 'fault'
        if (device['安全门断电状态'] === 0) return 'offline'
        if (device['报警信息'] === 1) return 'fault'
        return 'normal'
      }
      // ç«™å°çŠ¶æ€åˆ¤æ–­
      if (this.currentMonitorType === 'platform') {
        if (device['光电信号'] === 1) return 'running'
        return 'normal'
      }
      // RGV设备状态判断
      if (device['故障代码'] !== 0) return 'fault'
      if (device['任务状态'] === 1) return 'running'
      if (device['工作模式'] === 0) return 'manual'
      return 'normal'
    },
    getDeviceDisplayName(deviceKey) {
      return this.deviceDisplayNames[deviceKey] || deviceKey.toUpperCase()
    },
    getFieldDisplayName(fieldKey) {
      return this.fieldDisplayNames[fieldKey] || fieldKey
    },
    getFormattedValue(fieldKey, value, deviceKey) {
      const formatter = this.valueFormatters[fieldKey]
      return formatter ? formatter(value, deviceKey) : value
    },
    getValueClass(fieldKey, value, deviceKey) {
      if (fieldKey === '故障代码' && value !== 0) {
        return 'fault-text'
      }
      return ''
    },
    // èŽ·å–è®¾å¤‡ç±»åž‹
    getDeviceType(deviceKey) {
      if (this.deviceTypes.motherCars.includes(deviceKey) ||
        this.deviceTypes.outboundMotherCars.includes(deviceKey)) {
        return 'mother'
      } else if (this.deviceTypes.childCars.includes(deviceKey) ||
        this.deviceTypes.outboundChildCars.includes(deviceKey)) {
        return 'child'
      } else if (this.deviceTypes.materialCars.includes(deviceKey) ||
        this.deviceTypes.outboundMaterialCars.includes(deviceKey)) {
        return 'material'
      } else if (this.deviceTypes.safetyDoors.includes(deviceKey)) {
        return 'safetydoor'
      } else if (this.deviceTypes.platforms.includes(deviceKey)) {
        return 'platform'
      }
      return 'unknown'
    },
    // èŽ·å–è®¾å¤‡å¯ç”¨çš„ç›®æ ‡åœ°å€
    getDeviceAddresses(deviceKey) {
      return this.deviceAddresses[deviceKey] || []
    },
    // èŽ·å–æŠ¥è­¦æ–‡æœ¬
    getAlarmText(alarmCode, deviceKey) {
      if (alarmCode === 0) return '无报警'
      const deviceType = this.getDeviceType(deviceKey)
      let alarmMap = {}
      switch (deviceType) {
        case 'mother':
          alarmMap = this.motherCarAlarmCodes
          break
        case 'child':
          alarmMap = this.childCarAlarmCodes
          break
        case 'material':
          alarmMap = this.materialCarAlarmCodes
          break
        default:
          alarmMap = this.motherCarAlarmCodes
      }
      return alarmMap[alarmCode] || `未连接(${alarmCode})`
    },
    // èŽ·å–å½“å‰æ¨¡å¼æ˜¾ç¤ºæ–‡æœ¬
    getCurrentModeText(deviceData) {
      if (!deviceData || !deviceData['工作模式']) return '点击切换自动'
      return deviceData['工作模式'] === 1 ? '点击切换手动' : '点击切换自动'
    },
    // èŽ·å–æ¨¡å¼æŒ‰é’®ç±»åž‹ï¼ˆé¢œè‰²ï¼‰
    getModeButtonType(deviceData) {
      if (!deviceData || !deviceData['工作模式']) return 'success'
      return deviceData['工作模式'] === 1 ? 'warning' : 'success'
    },
    // åˆ¤æ–­æ˜¯å¦ä¸ºæ‰‹åŠ¨æ¨¡å¼ï¼ˆä»…å½“æ˜Žç¡®ä¸º1=自动时视为非手动;undefined/0都按手动处理)
    isManualMode(deviceData) {
      if (!deviceData) return false
      return deviceData['工作模式'] !== 1
    },
    // èŽ·å–é›·è¾¾æŒ‰é’®æ–‡æœ¬
    getRadarButtonText(deviceData) {
      if (!deviceData) return '开启雷达'
      return deviceData['雷达状态'] === 1 ? '关闭雷达' : '开启雷达'
    },
    // èŽ·å–é›·è¾¾æŒ‰é’®ç±»åž‹
    getRadarButtonType(deviceData) {
      if (!deviceData) return 'info'
      return deviceData['雷达状态'] === 1 ? 'warning' : 'info'
    },
    // è®¾ç½®åŠ è½½çŠ¶æ€
    setLoadingState(deviceKey, operationType, loading) {
      if (!this.loadingStates[deviceKey]) {
        this.loadingStates[deviceKey] = {}
      }
      this.loadingStates[deviceKey][operationType] = loading
      this.loadingStates = { ...this.loadingStates }
    },
    // æ‰‹åЍ/自动模式切换
    async handleModeToggle(deviceKey) {
      this.setLoadingState(deviceKey, 'modeToggle', true)
      try {
        const deviceData = this.currentDeviceData[deviceKey]
        const currentMode = deviceData?.['工作模式'] || 0
        const operationType = currentMode === 0 ? 'zd' : 'sd'
        const response = await this.http.post('api/Rgvoperainform/DeviceOperation', {
          DelKeys: [deviceKey, operationType],
          Extra: true
        }, `${this.operationTypes[operationType]}中...`)
        if (response.status) {
          this.$message.success(`${this.getDeviceDisplayName(deviceKey)} ${this.operationTypes[operationType]}成功`)
          if (this.isMonitoring) this.refreshData()
        } else {
          this.$message.error(response.message || `${this.operationTypes[operationType]}失败`)
        }
      } catch (error) {
        this.$message.error(`模式切换请求失败: ` + error.message)
      } finally {
        this.setLoadingState(deviceKey, 'modeToggle', false)
      }
    },
    // é›·è¾¾å¼€å…³åˆ‡æ¢
    async handleRadarToggle(deviceKey) {
      this.setLoadingState(deviceKey, 'radarToggle', true)
      try {
        const deviceData = this.currentDeviceData[deviceKey]
        const currentRadarState = deviceData?.['雷达状态'] || 0
        const operationType = currentRadarState === 0 ? 'kld' : 'gld'
        const response = await this.http.post('api/Rgvoperainform/DeviceOperation', {
          DelKeys: [deviceKey, operationType],
          Extra: true
        }, `${this.operationTypes[operationType]}中...`)
        if (response.status) {
          this.$message.success(`${this.getDeviceDisplayName(deviceKey)} ${this.operationTypes[operationType]}成功`)
          this.refreshData()
        } else {
          this.$message.error(response.message || `${this.operationTypes[operationType]}失败`)
        }
      } catch (error) {
        this.$message.error(`雷达操作请求失败: ` + error.message)
      } finally {
        this.setLoadingState(deviceKey, 'radarToggle', false)
      }
    },
    // åœ°å€æ“ä½œ
    async handleAddressOperation(deviceKey) {
      const address = this.addressValues[deviceKey]
      if (!address) {
        this.$message.warning('请选择目标地址')
        return
      }
      this.setLoadingState(deviceKey, 'dz', true)
      try {
        const response = await this.http.post('api/Rgvoperainform/DeviceOperation', {
          DelKeys: [deviceKey, 'dz', address],
          Extra: true
        }, '前往地址中...')
        if (response.status) {
          this.$message.success(`${this.getDeviceDisplayName(deviceKey)} å‰å¾€åœ°å€ ${address} æˆåŠŸ`)
          if (this.isMonitoring) this.refreshData()
        } else {
          this.$message.error(response.message || '前往地址失败')
        }
      } catch (error) {
        this.$message.error('前往地址请求失败: ' + error.message)
      } finally {
        this.setLoadingState(deviceKey, 'dz', false)
      }
    },
    // ç»Ÿä¸€æ“ä½œå¤„理函数
    async handleOperation(deviceKey, operationType) {
      this.setLoadingState(deviceKey, operationType, true)
      try {
        // ä¸Šå‡/下降需要第三个参数置位
        const delKeys = (operationType === 'ss' || operationType === 'xj')
          ? [deviceKey, operationType, 1]
          : [deviceKey, operationType]
        const response = await this.http.post('api/Rgvoperainform/DeviceOperation', {
          DelKeys: delKeys,
          Extra: true
        }, `${this.operationTypes[operationType]}中...`)
        if (response.status) {
          const operationName = this.operationTypes[operationType]
          this.$message.success(`${this.getDeviceDisplayName(deviceKey)} ${operationName}操作成功`)
          if (this.isMonitoring) this.refreshData()
        } else {
          this.$message.error(response.message || `${this.operationTypes[operationType]}操作失败`)
        }
      } catch (error) {
        this.$message.error(`${this.operationTypes[operationType]}请求失败: ` + error.message)
      } finally {
        this.setLoadingState(deviceKey, operationType, false)
      }
    }
  },
  mounted() {
    // å…¨å±€é‡Šæ”¾å…œåº•,防止指针移出按钮区域或窗口导致无法发送 0
    this._liftGlobalRelease = () => {
      if (this.activeLift) {
        const { deviceKey, operationType } = this.activeLift
        this.handleLiftRelease(deviceKey, operationType)
      }
    }
    window.addEventListener('mouseup', this._liftGlobalRelease)
    window.addEventListener('touchend', this._liftGlobalRelease, { passive: true })
    window.addEventListener('blur', this._liftGlobalRelease)
    document.addEventListener('visibilitychange', () => {
      if (document.hidden) this._liftGlobalRelease()
    })
  },
  beforeUnmount() {
    this.stopMonitoring()
    if (this._liftGlobalRelease) {
      window.removeEventListener('mouseup', this._liftGlobalRelease)
      window.removeEventListener('touchend', this._liftGlobalRelease)
      window.removeEventListener('blur', this._liftGlobalRelease)
    }
  }
}
</script>
<style scoped>
/* æ·»åŠ ç«™å°æ­£å¸¸æ–‡æœ¬æ ·å¼ */
.normal-text {
  color: #67C23A;
  font-weight: bold;
}
/* åŽŸæœ‰çš„æ ·å¼ä¿æŒä¸å˜ï¼Œæ·»åŠ å®‰å…¨é—¨ç›¸å…³æ ·å¼ */
/* å®‰å…¨é—¨æŒ‡ç¤ºç¯æ ·å¼ */
.safety-light {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: inline-block;
  margin-left: 8px;
  border: 1px solid #ccc;
}
.safety-light.light-off {
  background-color: #909399;
}
.safety-light.light-red {
  background-color: #F56C6C;
  box-shadow: 0 0 6px #F56C6C;
  animation: blink 1s infinite;
}
.safety-light.light-green {
  background-color: #67C23A;
  box-shadow: 0 0 6px #67C23A;
}
.safety-light.light-yellow {
  background-color: #E6A23C;
  box-shadow: 0 0 6px #E6A23C;
}
.safety-light.light-yellow-blink {
  background-color: #E6A23C;
  box-shadow: 0 0 6px #E6A23C;
  animation: blink 0.5s infinite;
}
/* æ€¥åœçŠ¶æ€æŒ‡ç¤º */
.emergency-stop {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
  margin-left: 8px;
}
.emergency-stop.emergency-stop-normal {
  background-color: #67C23A;
  box-shadow: 0 0 4px #67C23A;
}
.emergency-stop.emergency-stop-active {
  background-color: #F56C6C;
  box-shadow: 0 0 4px #F56C6C;
  animation: blink 0.8s infinite;
}
/* ç›‘控类型切换样式 */
.monitor-type-switch {
  margin-right: 20px;
}
.one-click-operations {
  margin-right: 20px;
}
.control-panel {
  display: flex;
  gap: 10px;
  align-items: center;
}
/* ä¿¡å·æŒ‡ç¤ºç¯æ ·å¼ */
.signal-indicator {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
  margin-left: 8px;
}
.signal-indicator.signal-active {
  background-color: #67C23A;
  box-shadow: 0 0 4px #67C23A;
}
.signal-indicator.signal-inactive {
  background-color: #DCDFE6;
  border: 1px solid #C0C4CC;
}
/* ä¿®æ”¹value样式以容纳指示灯 */
.value {
  color: #303133;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* å…¶ä»–原有样式保持不变 */
.rgv-monitor {
  padding: 20px;
  background-color: #f5f7fa;
  min-height: 100vh;
}
.monitor-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding: 0 10px;
}
.title {
  line-height: 70vh;
  font-size: 24px;
  font-weight: bold;
  color: #409EFF;
}
.monitor-status {
  margin-bottom: 20px;
}
.devices-container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
  gap: 20px;
  margin-bottom: 20px;
}
.device-card {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  overflow: hidden;
  transition: all 0.3s ease;
}
.device-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.15);
}
.device-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px 20px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
}
.device-header h3 {
  margin: 0;
  font-size: 16px;
}
.status-indicator {
  width: 12px;
  height: 12px;
  border-radius: 50%;
}
.status-indicator.normal {
  background-color: #67C23A;
  box-shadow: 0 0 8px #67C23A;
}
.status-indicator.running {
  background-color: #409EFF;
  box-shadow: 0 0 8px #409EFF;
  animation: pulse 1.5s infinite;
}
.status-indicator.fault {
  background-color: #F56C6C;
  box-shadow: 0 0 8px #F56C6C;
  animation: blink 1s infinite;
}
.status-indicator.manual {
  background-color: #E6A23C;
  box-shadow: 0 0 8px #E6A23C;
  animation: pulse 1.5s infinite;
}
.status-indicator.offline {
  background-color: #909399;
}
.device-content {
  padding: 20px;
}
.data-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid #ebeef5;
}
.data-row:last-child {
  border-bottom: none;
}
.label {
  font-weight: 500;
  color: #606266;
}
.fault-text {
  color: #F56C6C;
  font-weight: bold;
}
.no-data {
  text-align: center;
  font-size: 28px;
  color: orange;
  color: #909399;
  padding: 40px 0;
  font-style: italic;
}
.operation-buttons {
  margin-top: 15px;
  padding-top: 15px;
  border-top: 1px dashed #ebeef5;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
}
.address-operation {
  margin-top: 15px;
  padding-top: 15px;
  border-top: 1px dashed #ebeef5;
}
.address-title {
  font-weight: 500;
  color: #606266;
  margin-bottom: 10px;
  text-align: center;
}
.address-input-group {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
}
.operation-panel {
  text-align: center;
  padding: 20px;
}
@keyframes pulse {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
  100% {
    opacity: 1;
  }
}
@keyframes blink {
  0%,
  50% {
    opacity: 1;
  }
  51%,
  100% {
    opacity: 0.3;
  }
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .devices-container {
    grid-template-columns: 1fr;
  }
  .monitor-header {
    flex-direction: column;
    gap: 15px;
    align-items: flex-start;
  }
  .control-panel {
    flex-direction: column;
    align-items: flex-start;
    width: 100%;
  }
  .monitor-type-switch,
  .one-click-operations {
    margin-right: 0;
    margin-bottom: 10px;
    width: 100%;
  }
  .monitor-type-switch .el-button-group,
  .one-click-operations .el-button-group {
    width: 100%;
  }
  .monitor-type-switch .el-button,
  .one-click-operations .el-button {
    flex: 1;
  }
  .operation-buttons {
    flex-direction: column;
    align-items: stretch;
  }
  .operation-buttons .el-button {
    width: 100%;
  }
  .address-input-group {
    flex-direction: column;
    align-items: stretch;
  }
  .address-input-group .el-select,
  .address-input-group .el-button {
    width: 100%;
  }
}
</style>