1
2
3
4
5
6
7
8
9
10
11
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
<template>
  <div class="app-container">
    <el-tabs v-model="activeTab">
      <el-tab-pane label="设备台账" name="ledger" />
      <el-tab-pane label="远程控制" name="control" />
      <el-tab-pane label="异常报警" name="alarm">
        <template #label>
          <span>异常报警<el-badge v-if="unhandledCount > 0" :value="unhandledCount" style="margin-left: 6px" /></span>
        </template>
      </el-tab-pane>
    </el-tabs>
 
    <!-- 远程控制 -->
    <div v-if="activeTab === 'ledger'">
      <LedgerPage />
    </div>
 
    <div v-else-if="activeTab === 'control'">
      <div class="section-title">设备列表(远程启停)</div>
      <el-table :data="deviceList" border v-loading="deviceLoading" height="300">
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="设备名称" prop="deviceName" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="deviceModel" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" width="80" />
        <el-table-column fixed="right" label="远程控制" width="180" align="center">
          <template #default="scope">
            <el-button link type="success" size="small" @click="handleSend(scope.row, 'start')">启动</el-button>
            <el-button link type="danger" size="small" @click="handleSend(scope.row, 'stop')">停止</el-button>
          </template>
        </el-table-column>
      </el-table>
 
      <div class="section-title" style="margin-top: 16px">指令记录</div>
      <el-table :data="commandList" border v-loading="commandLoading" height="300">
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="设备名称" prop="deviceName" show-overflow-tooltip />
        <el-table-column label="指令" prop="commandType" width="90" align="center">
          <template #default="scope">
            <el-tag :type="scope.row.commandType === 'start' ? 'success' : 'danger'">
              {{ scope.row.commandType === 'start' ? '启动' : '停止' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="状态" prop="commandStatus" width="100" align="center">
          <template #default="scope">
            <el-tag :type="scope.row.commandStatus === 'success' ? 'success' : (scope.row.commandStatus === 'failed' ? 'danger' : 'info')">
              {{ scope.row.commandStatus === 'success' ? '成功' : (scope.row.commandStatus === 'failed' ? '失败' : '下发中') }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作人" prop="operator" width="110" />
        <el-table-column label="下发时间" prop="commandTime" width="170" />
        <el-table-column label="执行结果" prop="resultMsg" show-overflow-tooltip />
      </el-table>
      <pagination v-show="commandPage.total > 0" :total="commandPage.total" layout="total, prev, pager, next"
        :page="commandPage.current" :limit="commandPage.size" @pagination="commandPageChange" />
    </div>
 
    <!-- 异常报警 -->
    <div v-else>
      <el-alert type="info" :closable="false" show-icon style="margin-bottom: 10px"
        title="系统每 60 秒自动检查一次新报警,发现未处理报警会弹窗提醒(站内推送);报警来源为「设备台账 → 设备故障」登记的故障记录,故障未消除即视为未处理报警" />
      <el-table :data="alarmList" border v-loading="alarmLoading" height="calc(100vh - 16em)">
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="设备名称" prop="deviceName" show-overflow-tooltip width="220">
          <template #default="scope">{{ scope.row.deviceName || '-' }}</template>
        </el-table-column>
        <el-table-column label="故障描述" prop="defectDescription" show-overflow-tooltip />
        <el-table-column label="设备状态" prop="status" width="120" />
        <el-table-column label="是否处理" width="110" align="center">
          <template #default="scope">
            <el-tag :type="scope.row.handled ? 'success' : 'danger'">{{ scope.row.handled ? '已消除' : '未处理' }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="发生时间" prop="createTime" width="170" />
        <el-table-column label="消除时间" prop="eliminateTime" width="170" />
      </el-table>
    </div>
  </div>
</template>
 
<script setup>
import { ref, onMounted, onUnmounted, computed } from "vue";
import { ElNotification, ElMessageBox, ElMessage } from "element-plus";
import pagination from "@/components/PIMTable/Pagination.vue";
import LedgerPage from "@/views/equipmentManagement/ledger/index.vue";
import { getDeviceList, getAlarmList, getCommandPage, sendCommand } from "@/api/environmentAccess/deviceCommand.js";
 
defineOptions({ name: "设备远程监控" });
 
const activeTab = ref("ledger");
const deviceList = ref([]);
const deviceLoading = ref(false);
const commandList = ref([]);
const commandLoading = ref(false);
const commandPage = ref({ current: 1, size: 10, total: 0 });
const alarmList = ref([]);
const alarmLoading = ref(false);
 
const unhandledCount = computed(() => alarmList.value.filter((a) => !a.handled).length);
 
/* ---------- 远程控制 ---------- */
const loadDevices = () => {
  deviceLoading.value = true;
  getDeviceList().then((res) => {
    deviceList.value = res.data || [];
  }).finally(() => { deviceLoading.value = false; });
};
 
const loadCommands = () => {
  commandLoading.value = true;
  getCommandPage({ current: commandPage.value.current, size: commandPage.value.size }).then((res) => {
    commandList.value = (res.data && res.data.records) || [];
    commandPage.value.total = (res.data && res.data.total) || 0;
  }).finally(() => { commandLoading.value = false; });
};
 
const commandPageChange = (obj) => {
  commandPage.value.current = obj.page;
  commandPage.value.size = obj.limit;
  loadCommands();
};
 
const handleSend = (row, type) => {
  const label = type === "start" ? "启动" : "停止";
  ElMessageBox.confirm(`确认对「${row.deviceName || '该设备'}」下发${label}指令?`, "远程控制", { type: "warning" })
    .then(() => {
      sendCommand({ deviceLedgerId: row.id, deviceName: row.deviceName, commandType: type }).then((res) => {
        ElMessage.success(res.msg || `${label}指令已下发`);
        commandPage.value.current = 1;
        loadCommands();
      });
    })
    .catch(() => {});
};
 
/* ---------- 异常报警(含站内弹窗提醒) ---------- */
let alarmTimer = null;
let lastMaxAlarmId = null;
 
const loadAlarms = (notify) => {
  alarmLoading.value = true;
  return getAlarmList().then((res) => {
    const list = res.data || [];
    alarmList.value = list;
    const maxId = list.reduce((m, a) => Math.max(m, Number(a.id) || 0), 0);
    if (notify && lastMaxAlarmId !== null && maxId > lastMaxAlarmId) {
      const fresh = list.filter((a) => Number(a.id) > lastMaxAlarmId && !a.handled);
      fresh.slice(0, 3).forEach((a) => {
        ElNotification({
          title: "设备异常报警",
          message: `【${a.deviceName || '未知设备'}】${a.defectDescription || '发生设备故障'},请及时处理`,
          type: "error",
          duration: 15000,
          position: "bottom-right",
        });
      });
    }
    lastMaxAlarmId = maxId;
  }).finally(() => { alarmLoading.value = false; });
};
 
const startAlarmPolling = () => {
  loadAlarms(false);
  alarmTimer = setInterval(() => loadAlarms(true), 60000);
};
 
onMounted(() => {
  loadDevices();
  loadCommands();
  startAlarmPolling();
});
 
onUnmounted(() => {
  if (alarmTimer) {
    clearInterval(alarmTimer);
    alarmTimer = null;
  }
});
</script>
 
<style scoped lang="scss">
.section-title {
  font-size: 14px;
  font-weight: 600;
  color: #303133;
  margin-bottom: 8px;
}
</style>