|
@@ -189,9 +189,9 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
<!--设备详情弹窗-->
|
|
<!--设备详情弹窗-->
|
|
|
- <el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="900px" append-to-body>
|
|
|
|
|
|
|
+ <el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="1000px" append-to-body>
|
|
|
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClickTab">
|
|
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClickTab">
|
|
|
- <el-tab-pane label="状态检测" name="first">
|
|
|
|
|
|
|
+ <el-tab-pane label="状态检测" name="info">
|
|
|
<div>
|
|
<div>
|
|
|
<el-row :gutter="20" style="height: 100%; display: flex">
|
|
<el-row :gutter="20" style="height: 100%; display: flex">
|
|
|
<el-col :span="8">
|
|
<el-col :span="8">
|
|
@@ -207,7 +207,7 @@
|
|
|
</el-row>
|
|
</el-row>
|
|
|
</div>
|
|
</div>
|
|
|
</el-tab-pane>
|
|
</el-tab-pane>
|
|
|
- <el-tab-pane label="远程操作" name="second">
|
|
|
|
|
|
|
+ <el-tab-pane label="远程操作" name="control">
|
|
|
<el-button :loading="buttonLoading" type="primary" @click="handleControl('reboot')">设备重启</el-button>
|
|
<el-button :loading="buttonLoading" type="primary" @click="handleControl('reboot')">设备重启</el-button>
|
|
|
<el-button :loading="buttonLoading" type="primary" @click="handleControl('start')">远程开机</el-button>
|
|
<el-button :loading="buttonLoading" type="primary" @click="handleControl('start')">远程开机</el-button>
|
|
|
<el-button :loading="buttonLoading" type="primary" @click="handleControl('shutdown')">远程关机</el-button>
|
|
<el-button :loading="buttonLoading" type="primary" @click="handleControl('shutdown')">远程关机</el-button>
|
|
@@ -215,7 +215,30 @@
|
|
|
<el-button :loading="buttonLoading" type="primary" @click="handleVoice">音量调节</el-button>
|
|
<el-button :loading="buttonLoading" type="primary" @click="handleVoice">音量调节</el-button>
|
|
|
<el-button :loading="buttonLoading" type="primary" @click="handleBrightness">亮度调节</el-button>
|
|
<el-button :loading="buttonLoading" type="primary" @click="handleBrightness">亮度调节</el-button>
|
|
|
</el-tab-pane>
|
|
</el-tab-pane>
|
|
|
- <el-tab-pane label="报警信息" name="third">报警信息</el-tab-pane>
|
|
|
|
|
|
|
+ <el-tab-pane label="报警信息" name="alarm">
|
|
|
|
|
+ <el-table v-loading="loading" :data="alarmList" row-key="id">
|
|
|
|
|
+ <el-table-column label="主键ID" align="left" prop="id" v-if="true" />
|
|
|
|
|
+ <el-table-column label="设备名称" align="left" prop="deviceName" :show-overflow-tooltip="true" />
|
|
|
|
|
+ <el-table-column label="告警等级" align="center" prop="errorLevel" width="100">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <dict-tag :options="smsb_device_error_level" :value="scope.row.errorLevel" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="告警类型" align="center" prop="errorType" width="100">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <dict-tag :options="smsb_device_error_type" :value="scope.row.errorType" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="创建时间" align="left" prop="createTime" width="160" />
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <pagination
|
|
|
|
|
+ v-show="alarmTotal > 0"
|
|
|
|
|
+ :total="alarmTotal"
|
|
|
|
|
+ v-model:page="dialogQueryParams.pageNum"
|
|
|
|
|
+ v-model:limit="dialogQueryParams.pageSize"
|
|
|
|
|
+ @pagination="getAlarmList"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-tab-pane>
|
|
|
</el-tabs>
|
|
</el-tabs>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
<div class="dialog-footer">
|
|
@@ -223,13 +246,13 @@
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
- <el-dialog :title="watchDialog.title" v-model="watchDialog.visible" width="900px" append-to-body>
|
|
|
|
|
|
|
+ <el-dialog :title="watchDialog.title" v-model="watchDialog.visible" width="900px" append-to-body @closed="onDialogClosed">
|
|
|
<div v-if="watchDialog.visible" style="width: 100%; height: 500px">
|
|
<div v-if="watchDialog.visible" style="width: 100%; height: 500px">
|
|
|
- <video id="flv-player" style="width: 100%; height: 100%" controls></video>
|
|
|
|
|
|
|
+ <video ref="flvPlayerRef" style="width: 100%; height: 100%" controls></video>
|
|
|
</div>
|
|
</div>
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
<div class="dialog-footer">
|
|
|
- <el-button @click="cancel">取 消</el-button>
|
|
|
|
|
|
|
+ <el-button @click="cancelStream">取 消</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
@@ -249,7 +272,8 @@ import {
|
|
|
shutdown,
|
|
shutdown,
|
|
|
startStream,
|
|
startStream,
|
|
|
deviceStatistics,
|
|
deviceStatistics,
|
|
|
- getDeviceRunInfo
|
|
|
|
|
|
|
+ getDeviceRunInfo,
|
|
|
|
|
+ stopStream
|
|
|
} from '@/api/smsb/device/device';
|
|
} from '@/api/smsb/device/device';
|
|
|
import { DeviceVO, DeviceQuery, DeviceForm, DeviceStatisticsVo } from '@/api/smsb/device/device_type';
|
|
import { DeviceVO, DeviceQuery, DeviceForm, DeviceStatisticsVo } from '@/api/smsb/device/device_type';
|
|
|
import { ProductVO } from '@/api/smsb/device/product_types';
|
|
import { ProductVO } from '@/api/smsb/device/product_types';
|
|
@@ -257,11 +281,17 @@ import { listProduct } from '@/api/smsb/device/product';
|
|
|
import { DeviceManufacturerVO } from '@/api/smsb/device/deviceManufacturer_type';
|
|
import { DeviceManufacturerVO } from '@/api/smsb/device/deviceManufacturer_type';
|
|
|
import { listDeviceManufacturer } from '@/api/smsb/device/deviceManufacturer';
|
|
import { listDeviceManufacturer } from '@/api/smsb/device/deviceManufacturer';
|
|
|
import type { TabsPaneContext } from 'element-plus';
|
|
import type { TabsPaneContext } from 'element-plus';
|
|
|
|
|
+import { ref, onBeforeUnmount } from 'vue';
|
|
|
import flvjs from 'flv.js';
|
|
import flvjs from 'flv.js';
|
|
|
import { DeviceRunInfoVO } from '@/api/smsb/device/device_run_type';
|
|
import { DeviceRunInfoVO } from '@/api/smsb/device/device_run_type';
|
|
|
|
|
+import { DeviceErrorRecordQuery, DeviceErrorRecordVO } from '@/api/smsb/device/errorRecord_type';
|
|
|
|
|
+import { listDeviceErrorRecord } from '@/api/smsb/device/errorRecord';
|
|
|
|
|
|
|
|
|
|
+const alarmList = ref<DeviceErrorRecordVO[]>([]);
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
-const { sys_device_online, smsb_yes_no } = toRefs<any>(proxy?.useDict('sys_device_online', 'smsb_yes_no'));
|
|
|
|
|
|
|
+const { sys_device_online, smsb_yes_no, smsb_device_error_level, smsb_device_error_type } = toRefs<any>(
|
|
|
|
|
+ proxy?.useDict('sys_device_online', 'smsb_yes_no', 'smsb_device_error_level', 'smsb_device_error_type')
|
|
|
|
|
+);
|
|
|
const deviceList = ref<DeviceVO[]>([]);
|
|
const deviceList = ref<DeviceVO[]>([]);
|
|
|
const deviceStatisticsVo = ref<DeviceStatisticsVo>();
|
|
const deviceStatisticsVo = ref<DeviceStatisticsVo>();
|
|
|
const productList = ref<ProductVO[]>([]);
|
|
const productList = ref<ProductVO[]>([]);
|
|
@@ -277,8 +307,10 @@ const queryFormRef = ref<ElFormInstance>();
|
|
|
const deviceFormRef = ref<ElFormInstance>();
|
|
const deviceFormRef = ref<ElFormInstance>();
|
|
|
const deviceId = ref<string | number>();
|
|
const deviceId = ref<string | number>();
|
|
|
const totalNum = ref(0);
|
|
const totalNum = ref(0);
|
|
|
|
|
+const alarmTotal = ref(0);
|
|
|
const onlineNum = ref(0);
|
|
const onlineNum = ref(0);
|
|
|
const offlineNum = ref(0);
|
|
const offlineNum = ref(0);
|
|
|
|
|
+const streamDeviceId = ref<string | number>();
|
|
|
const initNum = ref(0);
|
|
const initNum = ref(0);
|
|
|
const deviceRunInfo = reactive<DeviceRunInfoVO>({
|
|
const deviceRunInfo = reactive<DeviceRunInfoVO>({
|
|
|
deviceBase: undefined,
|
|
deviceBase: undefined,
|
|
@@ -294,8 +326,6 @@ const deviceRunInfo = reactive<DeviceRunInfoVO>({
|
|
|
appList: undefined,
|
|
appList: undefined,
|
|
|
createTime: undefined
|
|
createTime: undefined
|
|
|
});
|
|
});
|
|
|
-// 用于保存flv.js播放器实例
|
|
|
|
|
-let flvPlayer: flvjs.Player | null = null;
|
|
|
|
|
const dialog = reactive<DialogOption>({
|
|
const dialog = reactive<DialogOption>({
|
|
|
visible: false,
|
|
visible: false,
|
|
|
title: ''
|
|
title: ''
|
|
@@ -305,11 +335,14 @@ const viewDialog = reactive<DialogOption>({
|
|
|
visible: false,
|
|
visible: false,
|
|
|
title: ''
|
|
title: ''
|
|
|
});
|
|
});
|
|
|
|
|
+// 播放器实例引用
|
|
|
|
|
+const flvPlayer = ref<flvjs.Player | null>(null);
|
|
|
|
|
+const flvPlayerRef = ref<HTMLVideoElement | null>(null);
|
|
|
const watchDialog = reactive<DialogOption>({
|
|
const watchDialog = reactive<DialogOption>({
|
|
|
visible: false,
|
|
visible: false,
|
|
|
title: ''
|
|
title: ''
|
|
|
});
|
|
});
|
|
|
-const activeName = ref('first');
|
|
|
|
|
|
|
+const activeName = ref('info');
|
|
|
|
|
|
|
|
const initFormData: DeviceForm = {
|
|
const initFormData: DeviceForm = {
|
|
|
id: undefined,
|
|
id: undefined,
|
|
@@ -361,8 +394,17 @@ const data = reactive<PageData<DeviceForm, DeviceQuery>>({
|
|
|
]
|
|
]
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
+const dialogData = reactive<DialogPageData<DeviceErrorRecordQuery>>({
|
|
|
|
|
+ dialogQueryParams: {
|
|
|
|
|
+ pageNum: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ deviceId: undefined,
|
|
|
|
|
+ params: {}
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
const { queryParams, form, rules } = toRefs(data);
|
|
const { queryParams, form, rules } = toRefs(data);
|
|
|
|
|
+const { dialogQueryParams } = toRefs(dialogData);
|
|
|
|
|
|
|
|
/** 查询设备列表 */
|
|
/** 查询设备列表 */
|
|
|
const getList = async () => {
|
|
const getList = async () => {
|
|
@@ -419,6 +461,10 @@ const cancel = () => {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+const cancelStream = () => {
|
|
|
|
|
+ stopMonitor();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
/** 表单重置 */
|
|
/** 表单重置 */
|
|
|
const reset = () => {
|
|
const reset = () => {
|
|
|
form.value = { ...initFormData };
|
|
form.value = { ...initFormData };
|
|
@@ -506,6 +552,7 @@ const handleExport = () => {
|
|
|
);
|
|
);
|
|
|
};
|
|
};
|
|
|
const handleInfo = async (row?: DeviceVO) => {
|
|
const handleInfo = async (row?: DeviceVO) => {
|
|
|
|
|
+ activeName.value = 'info';
|
|
|
deviceId.value = row?.id || ids.value[0];
|
|
deviceId.value = row?.id || ids.value[0];
|
|
|
const runInfo = await getDeviceRunInfo(deviceId.value);
|
|
const runInfo = await getDeviceRunInfo(deviceId.value);
|
|
|
Object.assign(deviceRunInfo, runInfo.data);
|
|
Object.assign(deviceRunInfo, runInfo.data);
|
|
@@ -532,30 +579,78 @@ const handleControl = async (type: string) => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const startMonitor = async (row?: DeviceVO) => {
|
|
const startMonitor = async (row?: DeviceVO) => {
|
|
|
- const res = await startStream(ids.value[0]);
|
|
|
|
|
- if (res.data.pushResult) {
|
|
|
|
|
- watchDialog.visible = true;
|
|
|
|
|
- watchDialog.title = '回采画面';
|
|
|
|
|
- // 创建FLV播放器
|
|
|
|
|
- const videoElement = document.getElementById('flv-player') as HTMLVideoElement;
|
|
|
|
|
- const player = flvjs.createPlayer({
|
|
|
|
|
- type: 'flv',
|
|
|
|
|
- url: res.data.viewUrl
|
|
|
|
|
- });
|
|
|
|
|
- // 加载并播放
|
|
|
|
|
- player.attachMediaElement(videoElement);
|
|
|
|
|
- player.load();
|
|
|
|
|
- player.play();
|
|
|
|
|
- flvPlayer = player;
|
|
|
|
|
- } else {
|
|
|
|
|
- proxy?.$modal.msgError('开启推流失败!');
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await startStream(ids.value[0]);
|
|
|
|
|
+ streamDeviceId.value = ids.value[0];
|
|
|
|
|
+
|
|
|
|
|
+ if (res.data.pushResult) {
|
|
|
|
|
+ // 先销毁已有播放器
|
|
|
|
|
+ destroyPlayer();
|
|
|
|
|
+ watchDialog.visible = true;
|
|
|
|
|
+ watchDialog.title = '回采画面';
|
|
|
|
|
+ // 确保DOM已更新
|
|
|
|
|
+ await nextTick();
|
|
|
|
|
+ if (flvPlayerRef.value && res.data.viewUrl) {
|
|
|
|
|
+ flvPlayer.value = flvjs.createPlayer({
|
|
|
|
|
+ type: 'flv',
|
|
|
|
|
+ url: res.data.viewUrl
|
|
|
|
|
+ });
|
|
|
|
|
+ // 错误处理
|
|
|
|
|
+ /*flvPlayer.value.on(flvjs.Events.ERROR, (errType, errDetail) => {
|
|
|
|
|
+ console.error('播放错误:', errType, errDetail);
|
|
|
|
|
+ proxy?.$modal.msgError('视频播放失败,请检查流地址');
|
|
|
|
|
+ destroyPlayer();
|
|
|
|
|
+ });*/
|
|
|
|
|
+ flvPlayer.value.attachMediaElement(flvPlayerRef.value);
|
|
|
|
|
+ flvPlayer.value.load();
|
|
|
|
|
+ // 处理浏览器自动播放策略
|
|
|
|
|
+ flvPlayer.value.play().catch(() => {
|
|
|
|
|
+ proxy?.$modal.msgWarning('请点击视频播放按钮以开始播放');
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ proxy?.$modal.msgError('开启推流失败!');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ proxy?.$modal.msgError('请求推流失败');
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+// 清理播放器
|
|
|
|
|
+const destroyPlayer = () => {
|
|
|
|
|
+ if (flvPlayer.value) {
|
|
|
|
|
+ flvPlayer.value.pause();
|
|
|
|
|
+ flvPlayer.value.unload();
|
|
|
|
|
+ flvPlayer.value.detachMediaElement();
|
|
|
|
|
+ flvPlayer.value.destroy();
|
|
|
|
|
+ flvPlayer.value = null;
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
+// 对话框关闭时清理
|
|
|
|
|
+const onDialogClosed = () => {
|
|
|
|
|
+ destroyPlayer();
|
|
|
|
|
+};
|
|
|
|
|
+const stopMonitor = async () => {
|
|
|
|
|
+ const res = await stopStream(streamDeviceId.value);
|
|
|
|
|
+ watchDialog.visible = false;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+const getAlarmList = async () => {
|
|
|
|
|
+ dialogQueryParams.value.deviceId = deviceId.value;
|
|
|
|
|
+ const res = await listDeviceErrorRecord(dialogQueryParams.value);
|
|
|
|
|
+ alarmList.value = res.rows;
|
|
|
|
|
+ alarmTotal.value = res.total;
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
const handleClickTab = (tab: TabsPaneContext, event: Event) => {
|
|
const handleClickTab = (tab: TabsPaneContext, event: Event) => {
|
|
|
console.log(tab, event);
|
|
console.log(tab, event);
|
|
|
|
|
+ if (tab.props.name === 'alarm') {
|
|
|
|
|
+ getAlarmList();
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
|
|
+// 组件卸载前清理
|
|
|
|
|
+onBeforeUnmount(() => {
|
|
|
|
|
+ destroyPlayer();
|
|
|
|
|
+});
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
getList();
|
|
getList();
|
|
|
getManufacturerList();
|
|
getManufacturerList();
|