Compare commits
26 Commits
47b272441c
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5144bc9a7d | ||
|
|
2d2f46b6f1 | ||
|
|
e4d014cf8c | ||
|
|
80a360fa0e | ||
|
|
7d2c70a19f | ||
|
|
a248e3b234 | ||
|
|
70ab453d94 | ||
|
|
36f0cedc62 | ||
|
|
df52a3e343 | ||
|
|
8d27df05d6 | ||
|
|
e47b8d354e | ||
|
|
ced12d5805 | ||
|
|
dd54ce489e | ||
|
|
00efe08873 | ||
|
|
c3420640a7 | ||
|
|
4c882ac309 | ||
|
|
334787f2cf | ||
|
|
de48277991 | ||
|
|
753f84ec26 | ||
|
|
3d58c73a88 | ||
|
|
457d0c3888 | ||
|
|
a8ea3746f4 | ||
|
|
55f0cd2192 | ||
|
|
2492b8ff5a | ||
|
|
13b00ac8de | ||
|
|
89d0dd2a1d |
@@ -5,10 +5,6 @@
|
|||||||
"ComputedRef": true,
|
"ComputedRef": true,
|
||||||
"DirectiveBinding": true,
|
"DirectiveBinding": true,
|
||||||
"EffectScope": true,
|
"EffectScope": true,
|
||||||
"ElLoading": true,
|
|
||||||
"ElMessage": true,
|
|
||||||
"ElMessageBox": true,
|
|
||||||
"ElNotification": true,
|
|
||||||
"ExtractDefaultPropTypes": true,
|
"ExtractDefaultPropTypes": true,
|
||||||
"ExtractPropTypes": true,
|
"ExtractPropTypes": true,
|
||||||
"ExtractPublicPropTypes": true,
|
"ExtractPublicPropTypes": true,
|
||||||
|
|||||||
@@ -37,9 +37,16 @@
|
|||||||
<!-- 消息 -->
|
<!-- 消息 -->
|
||||||
<el-tooltip content="警报" effect="dark" placement="bottom">
|
<el-tooltip content="警报" effect="dark" placement="bottom">
|
||||||
<div style="display: flex; align-items: center" class="mr-[30px]">
|
<div style="display: flex; align-items: center" class="mr-[30px]">
|
||||||
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="400" :persistent="false">
|
<el-popover
|
||||||
|
placement="bottom"
|
||||||
|
transition="el-zoom-in-top"
|
||||||
|
:width="400"
|
||||||
|
:persistent="false"
|
||||||
|
trigger="click"
|
||||||
|
v-model:visible="popoverVisible"
|
||||||
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-badge :value="warningListStore.warningListTotal > 0 ? warningListStore.warningListTotal : ''" :max="999">
|
<el-badge :value="infoData" :max="999">
|
||||||
<div class="right-menu-item hover-effect" @click="goToWarningList">
|
<div class="right-menu-item hover-effect" @click="goToWarningList">
|
||||||
<el-icon><BellFilled /></el-icon>
|
<el-icon><BellFilled /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,6 +130,9 @@ const noticeStore = storeToRefs(useNoticeStore());
|
|||||||
const newNotice = ref(<number>0);
|
const newNotice = ref(<number>0);
|
||||||
const { warningList, warningListTotal } = storeToRefs(warningListStore);
|
const { warningList, warningListTotal } = storeToRefs(warningListStore);
|
||||||
|
|
||||||
|
const infoData = ref(warningListStore.warningListTotal ?? 0);
|
||||||
|
const popoverVisible = ref(false);
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
|
||||||
const userId = ref(userStore.userId);
|
const userId = ref(userStore.userId);
|
||||||
@@ -213,8 +223,14 @@ const handleCommand = (command: string) => {
|
|||||||
//用深度监听 消息
|
//用深度监听 消息
|
||||||
watch(
|
watch(
|
||||||
() => noticeStore.state.value.notices,
|
() => noticeStore.state.value.notices,
|
||||||
(newVal) => {
|
async (newVal) => {
|
||||||
newNotice.value = newVal.filter((item: any) => !item.read).length;
|
newNotice.value = newVal.filter((item: any) => !item.read).length;
|
||||||
|
//监听到消息变化,刷新告警列表
|
||||||
|
await warningListStore.getWarningList({
|
||||||
|
status: 0,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 300
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
);
|
);
|
||||||
@@ -239,8 +255,38 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
warningListStore.getWarningList();
|
warningListStore.getWarningList({
|
||||||
|
status: 0,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 300
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//监听数据变化
|
||||||
|
watch(
|
||||||
|
() => warningListStore.warningListTotal,
|
||||||
|
(newVal) => {
|
||||||
|
infoData.value = newVal;
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
// 告警数据变化时自动弹出弹窗
|
||||||
|
// 当 isNotice 变为 true 时弹出弹窗,并重置为 false
|
||||||
|
// let noticeFirstLoad = true; //解决初始化时弹出弹窗问题
|
||||||
|
watch(
|
||||||
|
() => noticeStore.state.value.isNotice,
|
||||||
|
(newVal) => {
|
||||||
|
// if (noticeFirstLoad) {
|
||||||
|
// noticeFirstLoad = false;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
if (newVal) {
|
||||||
|
noticeStore.state.value.isNotice = false;
|
||||||
|
popoverVisible.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -95,21 +95,25 @@ const isLastView = () => {
|
|||||||
};
|
};
|
||||||
const filterAffixTags = (routes: RouteRecordRaw[], basePath = '') => {
|
const filterAffixTags = (routes: RouteRecordRaw[], basePath = '') => {
|
||||||
let tags: RouteLocationNormalized[] = [];
|
let tags: RouteLocationNormalized[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
|
||||||
routes.forEach((route) => {
|
routes.forEach((route) => {
|
||||||
if (route.meta && route.meta.affix) {
|
if (route.meta && route.meta.affix) {
|
||||||
const tagPath = getNormalPath(basePath + '/' + route.path);
|
const tagPath = getNormalPath(basePath + '/' + route.path);
|
||||||
tags.push({
|
if (!seen.has(tagPath)) {
|
||||||
hash: '',
|
seen.add(tagPath);
|
||||||
matched: [],
|
tags.push({
|
||||||
params: undefined,
|
hash: '',
|
||||||
query: undefined,
|
matched: [],
|
||||||
redirectedFrom: undefined,
|
params: undefined,
|
||||||
fullPath: tagPath,
|
query: undefined,
|
||||||
path: tagPath,
|
redirectedFrom: undefined,
|
||||||
name: route.name as string,
|
fullPath: tagPath,
|
||||||
meta: { ...route.meta }
|
path: tagPath,
|
||||||
});
|
name: route.name as string,
|
||||||
|
meta: { ...route.meta }
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
const tempTags = filterAffixTags(route.children, route.path);
|
const tempTags = filterAffixTags(route.children, route.path);
|
||||||
@@ -124,7 +128,6 @@ const initTags = () => {
|
|||||||
const res = filterAffixTags(routes.value);
|
const res = filterAffixTags(routes.value);
|
||||||
affixTags.value = res;
|
affixTags.value = res;
|
||||||
for (const tag of res) {
|
for (const tag of res) {
|
||||||
// Must have tag name
|
|
||||||
if (tag.name) {
|
if (tag.name) {
|
||||||
useTagsViewStore().addVisitedView(tag);
|
useTagsViewStore().addVisitedView(tag);
|
||||||
}
|
}
|
||||||
@@ -136,6 +139,10 @@ const addTags = () => {
|
|||||||
route.meta.title = route.query.title as string;
|
route.meta.title = route.query.title as string;
|
||||||
}
|
}
|
||||||
if (name) {
|
if (name) {
|
||||||
|
// 如果 visitedViews 中已有同 title 的固定标签(affix),不再添加普通标签
|
||||||
|
if (visitedViews.value.some((v) => v.meta?.affix && v.title === route.meta?.title)) return;
|
||||||
|
// 常规去重:已有同 path 或 fullPath 的标签则跳过
|
||||||
|
if (visitedViews.value.some((v) => v.path === route.path || v.fullPath === route.fullPath)) return;
|
||||||
useTagsViewStore().addView(route as any);
|
useTagsViewStore().addView(route as any);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
|
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
|
||||||
<span class="truncate">实时监控</span>
|
<span class="truncate">实时监控-{{ monitoringArea }}监控</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="monitor-video-wrap">
|
<div class="monitor-video-wrap">
|
||||||
@@ -59,10 +59,11 @@ const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import { useVideosStore } from '@/store/modules/video';
|
import { useVideosStore } from '@/store/modules/video';
|
||||||
const videosStore = useVideosStore();
|
const videosStore = useVideosStore();
|
||||||
|
const monitoringArea = ref<string>('');
|
||||||
|
|
||||||
const warningListStore = useWarningListStore();
|
const warningListStore = useWarningListStore();
|
||||||
const noticeStore = useNoticeStore();
|
const noticeStore = useNoticeStore();
|
||||||
const { getWarningList, hanldeAction } = warningListStore;
|
const { getWarningList, handleAction } = warningListStore;
|
||||||
const { warningListTotal, warningList } = storeToRefs(warningListStore);
|
const { warningListTotal, warningList } = storeToRefs(warningListStore);
|
||||||
|
|
||||||
// const props = defineProps<{
|
// const props = defineProps<{
|
||||||
@@ -72,6 +73,18 @@ const warningListRows = computed(() => warningList.value?.rows || []);
|
|||||||
const { readAll } = useNoticeStore();
|
const { readAll } = useNoticeStore();
|
||||||
const monitorVideoRef = ref<HTMLVideoElement>();
|
const monitorVideoRef = ref<HTMLVideoElement>();
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
try {
|
||||||
|
await getWarningList({
|
||||||
|
status: 0,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 300
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取消息列表失败', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 定义变量内容
|
// 定义变量内容
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
loading: false
|
loading: false
|
||||||
@@ -105,10 +118,12 @@ const handleSee = async (row: any) => {
|
|||||||
}
|
}
|
||||||
await videosStore.getVideos(videoId);
|
await videosStore.getVideos(videoId);
|
||||||
monitorVideoUrl.value = videosStore.hisUrl;
|
monitorVideoUrl.value = videosStore.hisUrl;
|
||||||
|
monitoringArea.value = row.monitoringArea;
|
||||||
dialog.visible = true;
|
dialog.visible = true;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
initHls(monitorVideoUrl, monitorVideoRef);
|
initHls(monitorVideoUrl, monitorVideoRef);
|
||||||
await hanldeAction(row.id);
|
await handleAction(row.id);
|
||||||
|
await getList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMonitorDialogClose = async (done: () => void) => {
|
const handleMonitorDialogClose = async (done: () => void) => {
|
||||||
@@ -196,8 +211,9 @@ onBeforeRouteLeave(() => {
|
|||||||
dialog.visible = false;
|
dialog.visible = false;
|
||||||
monitorVideoUrl.value = '';
|
monitorVideoUrl.value = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getWarningList();
|
getList();
|
||||||
console.log('111111111111111111111_warningList.value', warningList.value);
|
console.log('111111111111111111111_warningList.value', warningList.value);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ interface NoticeItem {
|
|||||||
|
|
||||||
export const useNoticeStore = defineStore('notice', () => {
|
export const useNoticeStore = defineStore('notice', () => {
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
notices: [] as NoticeItem[]
|
notices: [] as NoticeItem[],
|
||||||
|
isNotice: false as boolean
|
||||||
});
|
});
|
||||||
|
|
||||||
const addNotice = (notice: NoticeItem) => {
|
const addNotice = (notice: NoticeItem) => {
|
||||||
|
|||||||
@@ -37,7 +37,12 @@ export const useTagsViewStore = defineStore('tagsView', () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
const addVisitedView = (view: RouteLocationNormalized): void => {
|
const addVisitedView = (view: RouteLocationNormalized): void => {
|
||||||
if (visitedViews.value.some((v: RouteLocationNormalized) => v.path === view.path)) return;
|
// 同时通过 path、fullPath、name 去重
|
||||||
|
if (visitedViews.value.some((v: RouteLocationNormalized) =>
|
||||||
|
v.path === view.path ||
|
||||||
|
v.fullPath === view.fullPath ||
|
||||||
|
(v.name && view.name && v.name === view.name)
|
||||||
|
)) return;
|
||||||
visitedViews.value.push(
|
visitedViews.value.push(
|
||||||
Object.assign({}, view, {
|
Object.assign({}, view, {
|
||||||
title: view.meta?.title || 'no-name'
|
title: view.meta?.title || 'no-name'
|
||||||
|
|||||||
@@ -1,36 +1,45 @@
|
|||||||
|
/** 此数据源跟表列不一致,(后端接口重复使用因逻辑复杂, 经商定不动后端接口, 设置此数据源) 适用于消息 */
|
||||||
|
/** 抽取公用代码更加复杂模糊-暂存数据源 */
|
||||||
import { listDemo, updateDemo } from '@/api/WarningList/index';
|
import { listDemo, updateDemo } from '@/api/WarningList/index';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
|
import { any } from 'vue-types';
|
||||||
|
|
||||||
export const useWarningListStore = defineStore('warningListStore', () => {
|
export const useWarningListStore = defineStore('warningListStore', () => {
|
||||||
// --- State (状态) ---
|
// --- State (状态) ---
|
||||||
// 相当于 Vuex 中的 state,用 ref 或 reactive 定义
|
const warningList = ref();
|
||||||
const warningList = ref([]);
|
|
||||||
const warningListTotal = ref(0);
|
const warningListTotal = ref(0);
|
||||||
// --- Getters (计算属性) ---
|
// --- Getters (计算属性) ---
|
||||||
// 相当于 Vuex 中的 getters,直接用 computed 定义
|
|
||||||
|
|
||||||
// --- Actions (动作) ---
|
// --- Actions (动作) ---
|
||||||
// 相当于 Vuex 中的 actions/mutations,直接写同步或异步函数
|
async function getWarningList(query?: any) {
|
||||||
async function getWarningList(deviceId?: string | number) {
|
|
||||||
try {
|
try {
|
||||||
const response = await listDemo({
|
const response = await listDemo({
|
||||||
deviceId,
|
...query
|
||||||
status: 0,
|
// deviceId,
|
||||||
pageNum: 1,
|
// status: 0,
|
||||||
pageSize: 300
|
// pageNum: 1,
|
||||||
|
// pageSize: 10
|
||||||
});
|
});
|
||||||
warningList.value = response.data || [];
|
warningList.value = response.data;
|
||||||
warningListTotal.value = response.data.total || 0;
|
warningListTotal.value = response.data.total || 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取消息列表失败', error);
|
console.error('获取消息列表失败', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function handleAction(ids: string | number | Array<string | number>) {
|
||||||
|
try {
|
||||||
|
await updateDemo(ids);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('处理告警失败', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 核心点:必须把需要暴露出去的变量和方法 return 出去!
|
// 核心点:必须把需要暴露出去的变量和方法 return 出去!
|
||||||
return {
|
return {
|
||||||
warningList,
|
warningList,
|
||||||
warningListTotal,
|
warningListTotal,
|
||||||
getWarningList
|
getWarningList,
|
||||||
|
handleAction
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { getToken } from '@/utils/auth';
|
import { getToken } from '@/utils/auth';
|
||||||
import { ElNotification } from 'element-plus';
|
import { ElNotification } from 'element-plus';
|
||||||
import { useNoticeStore } from '@/store/modules/notice';
|
import { useNoticeStore } from '@/store/modules/notice';
|
||||||
|
import { toRefs } from 'vue';
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
export const initSSE = (url: any) => {
|
export const initSSE = (url: any) => {
|
||||||
|
const noticeStore = useNoticeStore();
|
||||||
|
const { isNotice, notices } = toRefs(noticeStore.state);
|
||||||
if (import.meta.env.VITE_APP_SSE === 'false') {
|
if (import.meta.env.VITE_APP_SSE === 'false') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -40,12 +43,13 @@ export const initSSE = (url: any) => {
|
|||||||
time: new Date().toLocaleString()
|
time: new Date().toLocaleString()
|
||||||
});
|
});
|
||||||
const displayMessage = data.value.split('|')[0].trim();
|
const displayMessage = data.value.split('|')[0].trim();
|
||||||
ElNotification({
|
// ElNotification({
|
||||||
title: '消息',
|
// title: '消息',
|
||||||
message: displayMessage,
|
// message: displayMessage,
|
||||||
type: 'success',
|
// type: 'success',
|
||||||
duration: 3000
|
// duration: 3000
|
||||||
});
|
// });
|
||||||
|
noticeStore.state.isNotice = true;
|
||||||
data.value = null;
|
data.value = null;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,19 +39,18 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<el-row :gutter="10" class="flex justify-end mb8">
|
<el-row :gutter="10" class="flex justify-end mb8">
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button v-hasPermi="['demo:demo:edit']" type="success" plain icon="Edit" :disabled="multiple" @click="handleBatchProcess"
|
<el-button type="success" plain icon="Edit" :disabled="multiple" @click="handleBatchProcess">批量处理</el-button>
|
||||||
>批量处理</el-button
|
|
||||||
>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<el-table v-loading="loading" border :data="demoList" @selection-change="handleSelectionChange">
|
<el-table v-loading="loading" border :data="demoList" @selection-change="handleSelectionChange">
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" :selectable="checkSelectable" />
|
||||||
<el-table-column label="报警编号" align="center" prop="alarmCode" width="180" />
|
<el-table-column label="报警编号" align="center" prop="alarmCode" width="180" />
|
||||||
<el-table-column label="报警时间" align="center" prop="alarmTime" min-width="170" />
|
<el-table-column label="报警时间" align="center" prop="alarmTime" min-width="170" />
|
||||||
<el-table-column label="所属设备" align="center" prop="deviceName" min-width="120" show-overflow-tooltip />
|
<el-table-column label="所属设备" align="center" prop="deviceName" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column label="报警区域" align="center" prop="monitoringArea" min-width="120" show-overflow-tooltip />
|
<el-table-column label="报警区域" align="center" prop="monitoringArea" min-width="120" show-overflow-tooltip />
|
||||||
|
<!-- pay content -->
|
||||||
<el-table-column label="报警图片" align="center" prop="alarmImage" min-width="120">
|
<el-table-column label="报警图片" align="center" prop="alarmImage" min-width="120">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-image
|
<el-image
|
||||||
@@ -136,7 +135,7 @@ import { storeToRefs } from 'pinia';
|
|||||||
import { useWarningListStore } from '@/store/modules/warnningList';
|
import { useWarningListStore } from '@/store/modules/warnningList';
|
||||||
|
|
||||||
const warningListStore = useWarningListStore();
|
const warningListStore = useWarningListStore();
|
||||||
const { getWarningList, hanldeAction } = warningListStore;
|
const { getWarningList, handleAction } = warningListStore;
|
||||||
const { warningListTotal, warningList } = storeToRefs(warningListStore);
|
const { warningListTotal, warningList } = storeToRefs(warningListStore);
|
||||||
|
|
||||||
const deviceStore = useDeviceStore();
|
const deviceStore = useDeviceStore();
|
||||||
@@ -173,6 +172,7 @@ const initFormData: any = {
|
|||||||
processingTime: undefined
|
processingTime: undefined
|
||||||
};
|
};
|
||||||
/* ?id=&deviceId=&pageSize=1&pageNum=1&beginTime=&endTime= */
|
/* ?id=&deviceId=&pageSize=1&pageNum=1&beginTime=&endTime= */
|
||||||
|
/** 查询参数 */
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
form: { ...initFormData },
|
form: { ...initFormData },
|
||||||
queryParams: {
|
queryParams: {
|
||||||
@@ -193,27 +193,35 @@ const data = reactive({
|
|||||||
|
|
||||||
const { queryParams, form, rules, queryTime } = toRefs(data);
|
const { queryParams, form, rules, queryTime } = toRefs(data);
|
||||||
|
|
||||||
|
/** 获取列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await listDemo(queryParams.value);
|
const res = await listDemo(queryParams.value);
|
||||||
demoList.value = res.data.rows;
|
demoList.value = res.data.rows;
|
||||||
total.value = res.data.total;
|
total.value = res.data.total;
|
||||||
|
await getWarningList({
|
||||||
|
status: 0,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 300
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 取消按钮 */
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
reset();
|
reset();
|
||||||
dialog.visible = false;
|
dialog.visible = false;
|
||||||
};
|
};
|
||||||
|
/** 重置按钮 */
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
form.value = { ...initFormData };
|
form.value = { ...initFormData };
|
||||||
demoFormRef.value?.resetFields();
|
demoFormRef.value?.resetFields();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 查询按钮 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.value.pageNum = 1;
|
queryParams.value.pageNum = 1;
|
||||||
queryParams.value.beginTime = queryTime.value[0];
|
queryParams.value.beginTime = queryTime.value[0];
|
||||||
@@ -221,12 +229,13 @@ const handleQuery = () => {
|
|||||||
getList();
|
getList();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 重置按钮 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value?.resetFields();
|
queryFormRef.value?.resetFields();
|
||||||
queryTime.value = [];
|
queryTime.value = [];
|
||||||
handleQuery();
|
handleQuery();
|
||||||
};
|
};
|
||||||
|
/** 多选框选中数据 */
|
||||||
const handleSelectionChange = (selection: any) => {
|
const handleSelectionChange = (selection: any) => {
|
||||||
ids.value = selection.map((item) => item.id as string | number);
|
ids.value = selection.map((item) => item.id as string | number);
|
||||||
multiple.value = !selection.length;
|
multiple.value = !selection.length;
|
||||||
@@ -238,7 +247,7 @@ const handleUpdate = async (row: any) => {
|
|||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
await updateDemo(row.id);
|
await handleAction(row.id);
|
||||||
proxy?.$modal.msgSuccess('处理成功');
|
proxy?.$modal.msgSuccess('处理成功');
|
||||||
await getList();
|
await getList();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -246,6 +255,11 @@ const handleUpdate = async (row: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 检查行是否可选(已处理不可选) */
|
||||||
|
const checkSelectable = (row: any) => {
|
||||||
|
return row.status !== 1;
|
||||||
|
};
|
||||||
|
|
||||||
/** 批量:确认后直接提交 */
|
/** 批量:确认后直接提交 */
|
||||||
const handleBatchProcess = async () => {
|
const handleBatchProcess = async () => {
|
||||||
if (!ids.value.length) {
|
if (!ids.value.length) {
|
||||||
@@ -255,7 +269,7 @@ const handleBatchProcess = async () => {
|
|||||||
await proxy?.$modal.confirm('是否确认批量处理已选中的报警?');
|
await proxy?.$modal.confirm('是否确认批量处理已选中的报警?');
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
await updateDemo(ids.value);
|
await handleAction(ids.value);
|
||||||
proxy?.$modal.msgSuccess('处理成功');
|
proxy?.$modal.msgSuccess('处理成功');
|
||||||
await getList();
|
await getList();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -263,6 +277,7 @@ const handleBatchProcess = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 提交按钮 */
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
if (form.value.id == null) {
|
if (form.value.id == null) {
|
||||||
proxy?.$modal.msgWarning('数据异常');
|
proxy?.$modal.msgWarning('数据异常');
|
||||||
@@ -296,4 +311,11 @@ onMounted(() => {
|
|||||||
getList();
|
getList();
|
||||||
getDeviceName();
|
getDeviceName();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//监听数据源变化,自动刷新列表
|
||||||
|
watch(warningListTotal, async (newVal) => {
|
||||||
|
//console.log('监听到报警列表变化,自动刷新列表', newVal);
|
||||||
|
//数据源不同, 接口复用,请求多了一遍(不属于bug)
|
||||||
|
await getList();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -65,10 +65,19 @@
|
|||||||
<el-tag v-else>{{ scope.row.status ?? '-' }}</el-tag>
|
<el-tag v-else>{{ scope.row.status ?? '-' }}</el-tag>
|
||||||
</template> -->
|
</template> -->
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="电池电量" align="center" prop="batteryLevel">
|
<el-table-column label="电池电量" align="center" prop="batteryLevel1" width="150">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.batteryLevel">{{ scope.row.batteryLevel + '%' }}</el-tag>
|
<!-- <el-button type="primary" v-if="scope.row.batteryLevel1" plain size="small">{{ scope.row.batteryLevel1 + '%' }}</el-button>
|
||||||
<el-tag v-else type="info"> 未获取 </el-tag>
|
<el-button type="primary" v-if="scope.row.batteryLevel2" plain size="small">
|
||||||
|
{{ scope.row.batteryLevel2 + '%' }}
|
||||||
|
</el-button> -->
|
||||||
|
<!-- <el-button type="primary" v-else plain size="small"> 未获取 </el-button> -->
|
||||||
|
<el-tag v-if="scope.row.batteryLevel1">{{ scope.row.batteryLevel1 + '%' }}</el-tag>
|
||||||
|
<el-tag v-if="scope.row.batteryLevel2" :style="scope.row.batteryLevel1 && scope.row.batteryLevel2 ? 'margin-left: 10px' : ''">
|
||||||
|
{{ scope.row.batteryLevel2 + '%' }}
|
||||||
|
</el-tag>
|
||||||
|
<!-- <el-tag v-else type="info"> 未获取 </el-tag> -->
|
||||||
|
<el-tag v-if="!scope.row.batteryLevel1 && !scope.row.batteryLevel2"> 未获取 </el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="设备型号" align="center" prop="model" />
|
<el-table-column label="设备型号" align="center" prop="model" />
|
||||||
@@ -123,7 +132,7 @@
|
|||||||
<el-dialog v-model="dialog1.visible" class="monitor-dialog" :before-close="handleMonitorDialogClose" fullscreen destroy-on-close>
|
<el-dialog v-model="dialog1.visible" class="monitor-dialog" :before-close="handleMonitorDialogClose" fullscreen destroy-on-close>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
|
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
|
||||||
<span class="truncate">实时监控</span>
|
<span class="truncate">{{ videoName }}-{{ monitoringArea }}监控</span>
|
||||||
<div>
|
<div>
|
||||||
<el-button v-if="!seeSee" type="primary" class="shrink-0" @click="drawImage()">绘制区域</el-button>
|
<el-button v-if="!seeSee" type="primary" class="shrink-0" @click="drawImage()">绘制区域</el-button>
|
||||||
<el-button v-if="seeSee" type="primary" class="shrink-0" @click="clearImage()">清空区域</el-button>
|
<el-button v-if="seeSee" type="primary" class="shrink-0" @click="clearImage()">清空区域</el-button>
|
||||||
@@ -165,6 +174,7 @@ const multiple = ref(true);
|
|||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const seeSee = ref(0);
|
const seeSee = ref(0);
|
||||||
const haveCode = ref(false);
|
const haveCode = ref(false);
|
||||||
|
const videoName = ref<string>('实时监控');
|
||||||
|
|
||||||
const queryFormRef = ref<ElFormInstance>();
|
const queryFormRef = ref<ElFormInstance>();
|
||||||
const demoFormRef = ref<ElFormInstance>();
|
const demoFormRef = ref<ElFormInstance>();
|
||||||
@@ -181,6 +191,8 @@ const dialog1 = reactive<DialogOption>({
|
|||||||
const monitorVideoUrl = ref('');
|
const monitorVideoUrl = ref('');
|
||||||
const monitorVideoRef = ref<HTMLVideoElement>();
|
const monitorVideoRef = ref<HTMLVideoElement>();
|
||||||
let hlsInstance: Hls | null = null;
|
let hlsInstance: Hls | null = null;
|
||||||
|
let visibilityHandler: (() => void) | null = null;
|
||||||
|
const monitoringArea = ref<string>('');
|
||||||
|
|
||||||
const initHls = () => {
|
const initHls = () => {
|
||||||
const url = monitorVideoUrl.value;
|
const url = monitorVideoUrl.value;
|
||||||
@@ -400,6 +412,7 @@ const handleSee = async (row?: any) => {
|
|||||||
reset();
|
reset();
|
||||||
resetMonitorDraw();
|
resetMonitorDraw();
|
||||||
seeSee.value = row?.isDrawArea === 1 ? 1 : 0;
|
seeSee.value = row?.isDrawArea === 1 ? 1 : 0;
|
||||||
|
monitoringArea.value = row?.monitoringArea;
|
||||||
const _id = row?.id || ids.value[0];
|
const _id = row?.id || ids.value[0];
|
||||||
const res = await getDeviceInfo(_id);
|
const res = await getDeviceInfo(_id);
|
||||||
Object.assign(form.value, res.data);
|
Object.assign(form.value, res.data);
|
||||||
@@ -454,6 +467,18 @@ const handleSee = async (row?: any) => {
|
|||||||
console.error('[查看监控] 解析 drawArea 失败:', error);
|
console.error('[查看监控] 解析 drawArea 失败:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 后台运行的时候恢复播放
|
||||||
|
cleanupVisibilityChange();
|
||||||
|
const handleVisibilityChange = () => {
|
||||||
|
if (!document.hidden) {
|
||||||
|
const video = monitorVideoRef.value;
|
||||||
|
if (video && video.paused) {
|
||||||
|
video.play().catch((e) => console.warn('恢复播放失败:', e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
visibilityHandler = handleVisibilityChange;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 提取显示报警区域的逻辑为独立函数
|
// 提取显示报警区域的逻辑为独立函数
|
||||||
@@ -530,6 +555,13 @@ const destroyVideoResizeObserver = () => {
|
|||||||
videoResizeObserver.value = null;
|
videoResizeObserver.value = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// 清理 visibilitychange 事件监听
|
||||||
|
const cleanupVisibilityChange = () => {
|
||||||
|
if (visibilityHandler) {
|
||||||
|
document.removeEventListener('visibilitychange', visibilityHandler);
|
||||||
|
visibilityHandler = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleMonitorDialogClose = (done: () => void) => {
|
const handleMonitorDialogClose = (done: () => void) => {
|
||||||
// 卸载 Hls 与清理 video
|
// 卸载 Hls 与清理 video
|
||||||
@@ -537,6 +569,7 @@ const handleMonitorDialogClose = (done: () => void) => {
|
|||||||
monitorVideoUrl.value = '';
|
monitorVideoUrl.value = '';
|
||||||
destroyVideoResizeObserver();
|
destroyVideoResizeObserver();
|
||||||
resetMonitorDraw();
|
resetMonitorDraw();
|
||||||
|
cleanupVisibilityChange();
|
||||||
done();
|
done();
|
||||||
//刷新列表
|
//刷新列表
|
||||||
getList();
|
getList();
|
||||||
@@ -580,6 +613,7 @@ const payload = ref<AlarmAreaData[]>([]);
|
|||||||
const drawImage = () => {
|
const drawImage = () => {
|
||||||
resetMonitorDraw();
|
resetMonitorDraw();
|
||||||
monitorDrawCollecting.value = true;
|
monitorDrawCollecting.value = true;
|
||||||
|
videoName.value = '绘制区域';
|
||||||
proxy?.$modal.msgSuccess('请在视频画面上连续点击两个点,生成报警区域');
|
proxy?.$modal.msgSuccess('请在视频画面上连续点击两个点,生成报警区域');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -706,6 +740,13 @@ const handleExport = () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList();
|
getList();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => seeSee.value,
|
||||||
|
(newValue) => {
|
||||||
|
videoName.value = newValue === 1 ? '绘制区域' : '实时监控';
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -790,4 +831,19 @@ onMounted(() => {
|
|||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 隐藏视频进度条,保留其他控制按钮 */
|
||||||
|
.monitor-video::-webkit-media-controls-timeline {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.monitor-video::-webkit-media-controls-current-time-display,
|
||||||
|
.monitor-video::-webkit-media-controls-time-remaining-display {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.monitor-video::-moz-range-track {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.monitor-video::-moz-progress-bar {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" class="monitor-dialog" fullscreen destroy-on-close>
|
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" class="monitor-dialog" fullscreen destroy-on-close>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
|
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
|
||||||
<span class="truncate">实时监控-北门出入门监控</span>
|
<span class="truncate">播放-{{ videoName }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="monitor-video-wrap">
|
<div class="monitor-video-wrap">
|
||||||
@@ -109,6 +109,7 @@ const multiple = ref(true);
|
|||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const monitorVideoUrl = ref();
|
const monitorVideoUrl = ref();
|
||||||
const downloadUrl = ref();
|
const downloadUrl = ref();
|
||||||
|
const videoName = ref<string>('录像文件');
|
||||||
|
|
||||||
const queryFormRef = ref<ElFormInstance>();
|
const queryFormRef = ref<ElFormInstance>();
|
||||||
const demoFormRef = ref<ElFormInstance>();
|
const demoFormRef = ref<ElFormInstance>();
|
||||||
@@ -218,6 +219,7 @@ const handlePlay = async (row?: any) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
monitorVideoUrl.value = row.videoUrl;
|
monitorVideoUrl.value = row.videoUrl;
|
||||||
|
videoName.value = row.videoName;
|
||||||
dialog.visible = true;
|
dialog.visible = true;
|
||||||
};
|
};
|
||||||
const handleDown = async (row?: any) => {
|
const handleDown = async (row?: any) => {
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ export default defineConfig(({ mode, command }) => {
|
|||||||
open: true,
|
open: true,
|
||||||
proxy: {
|
proxy: {
|
||||||
[env.VITE_APP_BASE_API]: {
|
[env.VITE_APP_BASE_API]: {
|
||||||
target: 'http://47.104.208.94:8089',
|
// target: 'http://47.104.208.94:8089',
|
||||||
|
target: 'http://192.168.0.214:8089',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
ws: true,
|
ws: true,
|
||||||
rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
|
rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
|
||||||
|
|||||||
Reference in New Issue
Block a user