From fd75d299ec095c9e80dfc129bc8c168f216881f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=9A=E7=9A=93?= <1736436516@qq.com> Date: Thu, 14 Nov 2024 16:07:42 +0800 Subject: [PATCH] =?UTF-8?q?DataStatistics=E6=95=B0=E6=8D=AE=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E9=A1=B5=E9=9D=A2---test.vue=E4=B8=AD=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E5=85=A8=E5=B1=80=E5=BC=B9=E7=AA=97=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 59 +++ package.json | 5 + src/App.vue | 94 +++- src/bk/DataStatics新数据统计.vue | 0 src/bk/DataStatistics原始版.vue | 127 +++++ src/bk/告警数量分布问题代码.vue | 410 +++++++++++++++ src/bk/时间段告警数量分布.vue | 514 +++++++++++++++++++ src/bk/点播页面原版.vue | 411 +++++++++++++++ src/components/AlertChart.vue | 626 +++++++---------------- src/components/Channel.vue | 350 +++++++++++++ src/components/Max/CenterBottom.vue | 388 +++++---------- src/components/Max/LeftBottom.vue | 9 +- src/components/Max/LeftTop.vue | 10 +- src/components/Max/RightTop.vue | 8 +- src/components/Settings.vue | 33 +- src/html/AlertManagement.vue | 167 +++++-- src/html/DataStatistics.vue | 740 +++++++++++++++++++++++++--- src/html/Home.vue | 684 ++++++++++++------------- src/html/Test.vue | 489 ++++++------------ src/main.ts | 4 + src/utils/boxApi.ts | 22 + src/utils/eventBus.ts | 5 + src/utils/useGlobalWebSocket.ts | 54 +- tsconfig.json | 10 +- 24 files changed, 3637 insertions(+), 1582 deletions(-) create mode 100644 src/bk/DataStatics新数据统计.vue create mode 100644 src/bk/DataStatistics原始版.vue create mode 100644 src/bk/告警数量分布问题代码.vue create mode 100644 src/bk/时间段告警数量分布.vue create mode 100644 src/bk/点播页面原版.vue create mode 100644 src/components/Channel.vue create mode 100644 src/utils/eventBus.ts diff --git a/package-lock.json b/package-lock.json index 1fa7778..5300fa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,12 @@ "dayjs": "^1.11.13", "echarts": "^5.5.1", "element-plus": "^2.7.8", + "file-saver": "^2.0.5", "jsmpeg": "^1.0.0", + "json2csv": "^5.0.7", + "lodash": "^4.17.21", + "mitt": "^3.0.1", + "papaparse": "^5.4.1", "vue": "^3.4.29", "vue-router": "^4.4.0" }, @@ -1189,6 +1194,14 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "engines": { + "node": ">= 6" + } + }, "node_modules/computeds": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/computeds/-/computeds-0.0.1.tgz", @@ -1351,6 +1364,11 @@ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -1465,6 +1483,32 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/json2csv": { + "version": "5.0.7", + "resolved": "https://registry.npmmirror.com/json2csv/-/json2csv-5.0.7.tgz", + "integrity": "sha512-YRZbUnyaJZLZUJSRi2G/MqahCyRv9n/ds+4oIetjDF3jWQA7AG7iSeKTiZiCNqtMZM7HDyt0e/W6lEnoGEmMGA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dependencies": { + "commander": "^6.1.0", + "jsonparse": "^1.3.1", + "lodash.get": "^4.4.2" + }, + "bin": { + "json2csv": "bin/json2csv.js" + }, + "engines": { + "node": ">= 10", + "npm": ">= 6.13.0" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "engines": [ + "node >= 0.2.0" + ] + }, "node_modules/less": { "version": "4.2.0", "resolved": "https://registry.npmmirror.com/less/-/less-4.2.0.tgz", @@ -1537,6 +1581,11 @@ "lodash-es": "*" } }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmmirror.com/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "node_modules/magic-string": { "version": "0.30.11", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.11.tgz", @@ -1630,6 +1679,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, "node_modules/muggle-string": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", @@ -1709,6 +1763,11 @@ "npm": ">= 8" } }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmmirror.com/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "node_modules/parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", diff --git a/package.json b/package.json index 3d8e8db..284f717 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,12 @@ "dayjs": "^1.11.13", "echarts": "^5.5.1", "element-plus": "^2.7.8", + "file-saver": "^2.0.5", "jsmpeg": "^1.0.0", + "json2csv": "^5.0.7", + "lodash": "^4.17.21", + "mitt": "^3.0.1", + "papaparse": "^5.4.1", "vue": "^3.4.29", "vue-router": "^4.4.0" }, diff --git a/src/App.vue b/src/App.vue index bc7bc45..5166ceb 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,26 +1,102 @@ \ No newline at end of file + diff --git a/src/bk/DataStatics新数据统计.vue b/src/bk/DataStatics新数据统计.vue new file mode 100644 index 0000000..e69de29 diff --git a/src/bk/DataStatistics原始版.vue b/src/bk/DataStatistics原始版.vue new file mode 100644 index 0000000..98d6208 --- /dev/null +++ b/src/bk/DataStatistics原始版.vue @@ -0,0 +1,127 @@ + + + + + + + + \ No newline at end of file diff --git a/src/bk/告警数量分布问题代码.vue b/src/bk/告警数量分布问题代码.vue new file mode 100644 index 0000000..c5f3665 --- /dev/null +++ b/src/bk/告警数量分布问题代码.vue @@ -0,0 +1,410 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/bk/时间段告警数量分布.vue b/src/bk/时间段告警数量分布.vue new file mode 100644 index 0000000..7597a84 --- /dev/null +++ b/src/bk/时间段告警数量分布.vue @@ -0,0 +1,514 @@ + + + + + + \ No newline at end of file diff --git a/src/bk/点播页面原版.vue b/src/bk/点播页面原版.vue new file mode 100644 index 0000000..6365a5b --- /dev/null +++ b/src/bk/点播页面原版.vue @@ -0,0 +1,411 @@ + + + + + + \ No newline at end of file diff --git a/src/components/AlertChart.vue b/src/components/AlertChart.vue index 1335681..58f215e 100644 --- a/src/components/AlertChart.vue +++ b/src/components/AlertChart.vue @@ -1,513 +1,263 @@ + + + + diff --git a/src/components/Channel.vue b/src/components/Channel.vue new file mode 100644 index 0000000..4efba4e --- /dev/null +++ b/src/components/Channel.vue @@ -0,0 +1,350 @@ + + + + + + \ No newline at end of file diff --git a/src/components/Max/CenterBottom.vue b/src/components/Max/CenterBottom.vue index 1a51348..966afb3 100644 --- a/src/components/Max/CenterBottom.vue +++ b/src/components/Max/CenterBottom.vue @@ -35,6 +35,7 @@ import dayjs from 'dayjs'; import { debounce } from 'lodash'; + const activeName = ref('first'); const hourlyCounts = ref(Array(24).fill(0)); const dailyCounts = ref(Array(7).fill(0)); @@ -44,38 +45,128 @@ const weekLineChartDom = ref(null); const monthLineChartDom = ref(null); - const chartInstanceToday = ref(null); - const chartInstanceWeek = ref(null); - const chartInstanceMonth = ref(null); + const chartInstance = ref(null); const weekDates = ref([]); const monthDates = ref([]); - let isTodayChartInitialized = false; - let isWeekChartInitialized = false; - let isMonthChartInitialized = false; - const apiInstance = new BoxApi(); - const allEvents = ref([]); - // 日期格式化函数 - const formatDateTime = (date) => dayjs(date).format('YYYY-MM-DD HH:mm:ss'); - - // 获取时间参数 - const getDayParams = () => { - const today = dayjs(); - return { timeAfter: today.startOf('day').format(), timeBefore: today.endOf('day').format() }; - }; - const getWeeklyParams = () => { - const today = dayjs(); - return { timeAfter: today.subtract(7, 'day').format(), timeBefore: today.endOf('day').format() }; - }; - const getMonthlyParams = () => { - const today = dayjs(); - return { timeAfter: today.subtract(30, 'day').format(), timeBefore: today.endOf('day').format() }; + // 设置时间范围获取计数 + const getEventCount = async (timeBefore, timeAfter) => { + try { + const token = localStorage.getItem('alertToken'); + const response = await apiInstance.getEventsByParams(token, 1, 1, timeBefore, timeAfter); + return response.count; + } catch (error) { + console.error("Error fetching event count:", error); + return 0; + } }; - // 计算日期 + // 生成图表的配置选项 + const createChartOption = (title, xAxisData, seriesData) => ({ + // title: { + // text: title, + // left: 'center', + // top: '0%', + // textStyle: { + // color: '#ffffff', + // fontSize: 16, + // fontWeight: 'bold', + // fontFamily: 'Arial, sans-serif', + // }, + // }, + tooltip: { + trigger: 'item', + axisPointer: { type: 'line' }, + formatter: (params) => { + const date = params.name; + const value = params.value; + return `时间:${date}
告警数量: ${value}`; + }, + backgroundColor: 'rgba(50, 50, 50, 0.3)', + borderWidth: 1, + textStyle: { + color: '#fff', + fontStyle: 'italic', + fontWeight: 'bold', + fontFamily: 'Arial, sans-serif', + fontSize: 12 + }, + padding: 10 + }, + grid: { + top: '10%', + left: '5%', + right: '5%', + bottom: '20%', + }, + xAxis: { + type: 'category', + data: xAxisData, + axisLabel: { color: '#fff' }, + }, + yAxis: { + type: 'value', minInterval: 1, axisLabel: { color: '#fff' }, + splitLine: { + show: false, + lineStyle: { color: '#cccccc', width: 1, type: 'solid' } + }, + }, + series: [ + { + name: '告警数量', + type: 'line', + data: seriesData, + smooth: true, + areaStyle: { color: 'rgba(74, 172, 178, 0.3)' }, + lineStyle: { color: 'rgb(60,178,239)', width: 2 }, + }, + ], + }); + + // 更新计数数据 + const updateCounts = async (range) => { + let timeAfter, timeBefore = dayjs().format(); + + if (range === 'day') { + // 获取“今日”每小时的事件数量 + for (let i = 0; i < 24; i++) { + timeAfter = dayjs().startOf('day').add(i, 'hour').format(); + timeBefore = dayjs().startOf('day').add(i + 1, 'hour').format(); + hourlyCounts.value[i] = await getEventCount(timeBefore, timeAfter); + console.log(`Hour ${i}: ${hourlyCounts.value[i]}`); + } + delayedInitChart(todayLineChartDom, createChartOption('今日告警趋势', Array.from({ length: 24 }, (_, i) => `${i}:00`), hourlyCounts.value)); + + } else if (range === 'week') { + // 获取“本周”每天的事件数量 + for (let i = 0; i < 7; i++) { + timeAfter = dayjs().subtract(i + 1, 'day').endOf('day').format(); + timeBefore = dayjs().subtract(i, 'day').endOf('day').format(); + dailyCounts.value[6 - i] = await getEventCount(timeBefore, timeAfter); + } + delayedInitChart(weekLineChartDom, createChartOption('本周告警趋势', weekDates.value, dailyCounts.value)); + + } else if (range === 'month') { + // 获取“最近30天”每天的事件数量 + for (let i = 0; i < 30; i++) { + timeAfter = dayjs().subtract(i + 1, 'day').endOf('day').format(); + timeBefore = dayjs().subtract(i, 'day').endOf('day').format(); + monthlyCounts.value[29 - i] = await getEventCount(timeBefore, timeAfter); + } + delayedInitChart(monthLineChartDom, createChartOption('近30天告警趋势', monthDates.value, monthlyCounts.value)); + } + }; + + // 处理选项卡切换 + const handleClick = async (tab) => { + const range = tab.props.name === 'first' ? 'day' : tab.props.name === 'second' ? 'week' : 'month'; + await updateCounts(range); + }; + + // 初始化日期 const calculateWeekDates = () => { weekDates.value = Array.from({ length: 7 }, (_, i) => dayjs().subtract(i, 'day').format('YYYY-MM-DD')).reverse(); }; @@ -83,8 +174,8 @@ monthDates.value = Array.from({ length: 30 }, (_, i) => dayjs().subtract(i, 'day').format('YYYY-MM-DD')).reverse(); }; - // 延迟初始化图表函数 - const delayedInitChart = debounce((chartInstance, domRef, option) => { + // 图表延迟初始化 + const delayedInitChart = debounce((domRef, option) => { nextTick(() => { if (domRef.value) { chartInstance.value = echarts.init(domRef.value); @@ -93,253 +184,14 @@ }); }, 300); - // 初始化图表选项配置 - const createTodayChartOption = () => ({ - tooltip: { - trigger: 'item', - axisPointer: { type: 'line' }, - formatter: (params) => { - console.log('Tooltip params:', params); - const date = params.name; - const value = params.value; - return `时间:${date}
告警数量: ${value}`; // 使用 标签作为小标题 - }, - backgroundColor: 'rgba(50, 50, 50, 0.3)', - borderWidth: 1, - textStyle: { - color: '#fff', - fontStyle: 'italic', // 字体样式,可以是 'normal', 'italic', 'oblique' - fontWeight: 'bold', - fontFamily: 'Arial, sans-serif', // 字体 - fontSize: 12 - }, - padding: 10 - }, - grid: { - top: '10%', - left: '5%', - right: '5%', - bottom: '20%', - }, - xAxis: { - type: 'category', - data: Array.from({ length: 24 }, (_, i) => `${i}:00`), - axisLabel: { color: '#fff' }, - }, - yAxis: { - type: 'value', minInterval: 1, axisLabel: { color: '#fff' }, - splitLine: { - show: false, - lineStyle: { - color: '#cccccc', - width: 1, - type: 'solid', - } - }, - }, - series: [ - { - name: '告警数量', - type: 'line', - data: hourlyCounts.value, - smooth: true, - areaStyle: { color: 'rgba(74, 172, 178, 0.3)' }, - lineStyle: { color: 'rgb(60,178,239)', width: 2 }, - }, - ], - }); - - const createWeekChartOption = () => ({ - tooltip: { - trigger: 'item', - axisPointer: { type: 'line' }, - formatter: (params) => { - console.log('Tooltip params:', params); - const date = params.name; - const value = params.value; - return `时间:${date}
告警数量: ${value}`; // 使用 标签作为小标题 - }, - backgroundColor: 'rgba(50, 50, 50, 0.3)', - borderWidth: 1, - textStyle: { - color: '#fff', - fontStyle: 'italic', // 字体样式,可以是 'normal', 'italic', 'oblique' - fontWeight: 'bold', - fontFamily: 'Arial, sans-serif', // 字体 - fontSize: 12 - }, - padding: 10 - }, - grid: { - top: '10%', - left: '5%', - right: '5%', - bottom: '20%', - }, - xAxis: { - type: 'category', - data: weekDates.value, - axisLabel: { color: '#fff' }, - }, - yAxis: { - type: 'value', minInterval: 1, axisLabel: { color: '#fff' }, - splitLine: { - show: false, - lineStyle: { - color: '#cccccc', - width: 1, - type: 'solid', - } - }, - }, - series: [ - { - name: '告警数量', - type: 'line', - data: dailyCounts.value, - smooth: true, - areaStyle: { color: 'rgba(74, 172, 178, 0.3)' }, - lineStyle: { color: 'rgb(60,178,239)', width: 2 }, - }, - ], - }); - - const createMonthChartOption = () => ({ - tooltip: { - trigger: 'item', - axisPointer: { type: 'line' }, - formatter: (params) => { - console.log('Tooltip params:', params); - const date = params.name; - const value = params.value; - return `时间:${date}
告警数量: ${value}`; // 使用 标签作为小标题 - }, - backgroundColor: 'rgba(50, 50, 50, 0.3)', - borderWidth: 1, - textStyle: { - color: '#fff', - fontStyle: 'italic', // 字体样式,可以是 'normal', 'italic', 'oblique' - fontWeight: 'bold', - fontFamily: 'Arial, sans-serif', // 字体 - fontSize: 12 - }, - padding: 10 - }, - grid: { - top: '10%', - left: '5%', - right: '5%', - bottom: '20%', - }, - xAxis: { - type: 'category', - data: monthDates.value, - axisLabel: { color: '#fff' }, - }, - yAxis: { - type: 'value', minInterval: 1, axisLabel: { color: '#fff' }, - splitLine: { - show: false, - lineStyle: { - color: '#cccccc', - width: 1, - type: 'solid', - } - }, - }, - series: [ - { - name: '告警数量', - type: 'line', - data: monthlyCounts.value, - smooth: true, - areaStyle: { color: 'rgba(74, 172, 178, 0.3)' }, - lineStyle: { color: 'rgb(60,178,239)', width: 2 }, - }, - ], - }); - - // 获取和处理事件数据 - const fetchAndProcessEvents = async (timeParams) => { - try { - let currentPage = 1; - const pageSize = 1000; - const token = localStorage.getItem('alertToken'); - allEvents.value = []; - - const firstResponse = await apiInstance.getEventsByParams(token, pageSize, currentPage, timeParams.timeBefore, timeParams.timeAfter); - const totalItems = firstResponse.count; - allEvents.value.push(...firstResponse.results); - - const totalPages = Math.ceil(totalItems / pageSize); - while (currentPage < totalPages) { - currentPage++; - const response = await apiInstance.getEventsByParams(token, pageSize, currentPage, timeParams.timeBefore, timeParams.timeAfter); - allEvents.value.push(...response.results); - } - - processEventData(allEvents.value); - } catch (error) { - console.error("Error fetching events:", error); - } - }; - - // 处理事件数据 - const processEventData = (events) => { - hourlyCounts.value.fill(0); - dailyCounts.value.fill(0); - monthlyCounts.value.fill(0); - events.forEach((event) => { - const hour = dayjs(event.ended_at).hour(); - const dayDiff = dayjs().diff(dayjs(event.ended_at), 'day'); - if (dayDiff < 1) hourlyCounts.value[hour] += 1; - if (dayDiff < 7) dailyCounts.value[6 - dayDiff] += 1; - if (dayDiff < 30) monthlyCounts.value[29 - dayDiff] += 1; - }); - updateCharts(); - }; - - // 更新图表 - const updateCharts = () => { - if (chartInstanceToday.value) chartInstanceToday.value.setOption({ series: [{ data: hourlyCounts.value }] }); - if (chartInstanceWeek.value) chartInstanceWeek.value.setOption({ series: [{ data: dailyCounts.value }] }); - if (chartInstanceMonth.value) chartInstanceMonth.value.setOption({ series: [{ data: monthlyCounts.value }] }); - }; - - // 处理选项卡切换 - const handleClick = async (tab) => { - let timeParams; - if (tab.props.name === 'first' && !isTodayChartInitialized) { - timeParams = getDayParams(); - await fetchAndProcessEvents(timeParams); - delayedInitChart(chartInstanceToday, todayLineChartDom, createTodayChartOption()); - isTodayChartInitialized = true; - } else if (tab.props.name === 'second' && !isWeekChartInitialized) { - timeParams = getWeeklyParams(); - await fetchAndProcessEvents(timeParams); - delayedInitChart(chartInstanceWeek, weekLineChartDom, createWeekChartOption()); - isWeekChartInitialized = true; - } else if (tab.props.name === 'third' && !isMonthChartInitialized) { - timeParams = getMonthlyParams(); - await fetchAndProcessEvents(timeParams); - delayedInitChart(chartInstanceMonth, monthLineChartDom, createMonthChartOption()); - isMonthChartInitialized = true; - } - }; - - // 窗口调整时重新渲染图表 - const resizeCharts = debounce(() => { - if (chartInstanceToday.value) chartInstanceToday.value.resize(); - if (chartInstanceWeek.value) chartInstanceWeek.value.resize(); - if (chartInstanceMonth.value) chartInstanceMonth.value.resize(); - }, 300); - // 组件挂载时调用 onMounted(async () => { calculateWeekDates(); calculateMonthDates(); await handleClick({ props: { name: 'first' } }); - window.addEventListener('resize', resizeCharts); + window.addEventListener('resize', debounce(() => { + if (chartInstance.value) chartInstance.value.resize(); + }, 300)); }); diff --git a/src/components/Max/LeftBottom.vue b/src/components/Max/LeftBottom.vue index b16e44d..d9194c5 100644 --- a/src/components/Max/LeftBottom.vue +++ b/src/components/Max/LeftBottom.vue @@ -146,6 +146,11 @@ const originalWidths = [97, 150, 160]; // 默认宽度 const adjustedWidths = ref([...originalWidths]); const baseWidth = 2150; +const formatDateTimeToISO = (datetime) => { + return new Date(datetime).toISOString().replace('.000', ''); +}; + + const adjustColumnWidths = () => { const currentWidth = window.innerWidth; // console.log(">>>>>>>>>>", currentWidth); @@ -192,8 +197,8 @@ const fetchEvents = async () => { const { endOfToday, startOfToday } = getDateParams(); - const timeBefore = formatDateTime(endOfToday); - const timeAfter = formatDateTime(startOfToday); + const timeBefore = formatDateTimeToISO(endOfToday); + const timeAfter = formatDateTimeToISO(startOfToday); // console.log("Start of today:", timeAfter); // const firstResponse = await apiInstance.getEventsByParams(token, limit, currentPage); const firstResponse = await apiInstance.getEventsByParams(token, limit, currentPage,timeBefore, timeAfter); diff --git a/src/components/Max/LeftTop.vue b/src/components/Max/LeftTop.vue index 987c932..960a2f6 100644 --- a/src/components/Max/LeftTop.vue +++ b/src/components/Max/LeftTop.vue @@ -141,7 +141,7 @@ - {{ pendintingEventCount }} + {{ pendingEventCount }} @@ -169,7 +169,7 @@ const cameraOfflineCount = ref(0); const cameraOnlineCount = ref(0); const eventCount = ref(0); - const pendintingEventCount = ref(0); + const pendingEventCount = ref(0); const closedEventCount = ref(0); const activeTab = ref('all'); @@ -227,7 +227,7 @@ const firstResponse = await apiInstance.getCameras(limit, offset, token); cameraCount.value = firstResponse.count; - console.log("总数》》》》》》》》》》》》》", cameraCount.value) + // console.log("总数》》》》》》》》》》》》》", cameraCount.value) allCameras = firstResponse.results; @@ -261,7 +261,7 @@ // eventCount.value = totalResponse.count; // const pendingResponse = await apiInstance.getEventsByParams(token, 1, 1, timeBefore, timeAfter, null, null, 'pending'); - // pendintingEventCount.value = pendingResponse.count; + // pendingEventCount.value = pendingResponse.count; // const closedResponse = await apiInstance.getEventsByParams(token, 1, 1, timeBefore, timeAfter, null, null, 'closed'); // closedEventCount.value = closedResponse.count; @@ -292,7 +292,7 @@ // 获取状态为 pending 的告警数量 const pendingResponse = await apiInstance.getEventsByParams(token, 1, 1, timeRange.timeBefore, timeRange.timeAfter, null, null, 'pending'); - pendintingEventCount.value = pendingResponse.count; + pendingEventCount.value = pendingResponse.count; // 获取状态为 closed 的告警数量 const closedResponse = await apiInstance.getEventsByParams(token, 1, 1, timeRange.timeBefore, timeRange.timeAfter, null, null, 'closed'); diff --git a/src/components/Max/RightTop.vue b/src/components/Max/RightTop.vue index 9416630..5e2ff9c 100644 --- a/src/components/Max/RightTop.vue +++ b/src/components/Max/RightTop.vue @@ -38,6 +38,10 @@ import * as echarts from 'echarts'; const apiInstance = new BoxApi(); const typeMapping = reactive({}); const typeCounts = reactive({}); // 存储每种类型的数量 +const formatDateTimeToISO = (datetime) => { + return new Date(datetime).toISOString().replace('.000', ''); +}; + const filterParams = reactive({ timeAfter: null, @@ -195,8 +199,8 @@ const fetchTypeCounts = async (timeAfter = null, timeBefore = null) => { // 点击查询按钮时,添加时间条件并重新统计 const handleFilter = () => { - const timeAfter = filterParams.timeAfter ? formatDateTime(new Date(filterParams.timeAfter)) : null; - const timeBefore = filterParams.timeBefore ? formatDateTime(new Date(filterParams.timeBefore)) : null; + const timeAfter = filterParams.timeAfter ? formatDateTimeToISO(new Date(filterParams.timeAfter)) : null; + const timeBefore = filterParams.timeBefore ? formatDateTimeToISO(new Date(filterParams.timeBefore)) : null; fetchTypeCounts(timeAfter, timeBefore); // 重新统计数量,添加时间条件 }; diff --git a/src/components/Settings.vue b/src/components/Settings.vue index 9669600..954990e 100644 --- a/src/components/Settings.vue +++ b/src/components/Settings.vue @@ -1,17 +1,23 @@ diff --git a/src/html/Home.vue b/src/html/Home.vue index 9e535bb..fb15516 100644 --- a/src/html/Home.vue +++ b/src/html/Home.vue @@ -1,411 +1,345 @@ - \ No newline at end of file + diff --git a/src/html/Test.vue b/src/html/Test.vue index b224b4c..d2c3ccc 100644 --- a/src/html/Test.vue +++ b/src/html/Test.vue @@ -1,365 +1,180 @@ + diff --git a/src/main.ts b/src/main.ts index 8e55a98..77fc159 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,6 +10,7 @@ import '@/assets/global.css' import { useGlobalWebSocket } from './utils/useGlobalWebSocket'; import DataVVue3 from '@kjgl77/datav-vue3'; import '@kjgl77/datav-vue3/dist/style.css'; +import mitt from 'mitt'; const app = createApp(App) @@ -74,4 +75,7 @@ onBeforeUnmount(() => { window.removeEventListener('resize', updateDimensions); }); +const eventBus = mitt(); +app.config.globalProperties.$eventBus = eventBus; + app.mount('#app') diff --git a/src/utils/boxApi.ts b/src/utils/boxApi.ts index fbc3eee..e288d38 100644 --- a/src/utils/boxApi.ts +++ b/src/utils/boxApi.ts @@ -16,6 +16,7 @@ class BoxApi { private readonly apiResetPassword: string = "/auth/reset_password"; private readonly getMinCamerasApi: string = "/camera/cameras/get_all"; private readonly getMinCamera: string = "/camera/cameras"; + private readonly getEventByIdUrl: string = "/event/events/retrieves"; private readonly loginConfig: object = { headers: { @@ -313,6 +314,7 @@ class BoxApi { } } + public async getEventsByParams( token: string | null = null, pageSize: number = 20, @@ -366,6 +368,26 @@ class BoxApi { throw error; } } + + public async getEventById(id: number, token: string | null = null): Promise { + try { + const url = `${this.getEventByIdUrl}`; + const params = { id }; + const res = await this.axios.get(url, { + ...this._authHeader(token), + params: params + }); + + if (res.data.err.ec === 0) { + // return res.data.ret.objects[0]; + return res.data.ret.objects[0]; + } else { + throw new Error(res.data.err.dm); + } + } catch (error) { + throw error; + } + } // public async getOneEvent(token: string | null = null): Promise { // try { // return await this.getEvents(1, 0, token); diff --git a/src/utils/eventBus.ts b/src/utils/eventBus.ts new file mode 100644 index 0000000..6227fcf --- /dev/null +++ b/src/utils/eventBus.ts @@ -0,0 +1,5 @@ + +import mitt from 'mitt'; +// 创建事件总线实例 +const eventBus = mitt(); +export default eventBus; diff --git a/src/utils/useGlobalWebSocket.ts b/src/utils/useGlobalWebSocket.ts index 636971b..a2a0fb1 100644 --- a/src/utils/useGlobalWebSocket.ts +++ b/src/utils/useGlobalWebSocket.ts @@ -1,22 +1,23 @@ -// useGlobalWebSocket.ts import { ref } from 'vue'; -import { ElMessage } from 'element-plus'; +import { ElMessage, ElNotification } from 'element-plus'; +import eventBus from '@/utils/eventBus'; const websocket = ref(null); const isWebSocketConnected = ref(false); let heartbeatInterval: number | null = null; -const rememberedAddress = localStorage.getItem('rememberedAddress') || '127.0.0.1'; // 连接 WebSocket const connectWebSocket = () => { - websocket.value = new WebSocket(`ws://${rememberedAddress}:8080/ws/event`); + websocket.value = new WebSocket(`ws://192.168.28.33:8080/ws/event`); websocket.value.onopen = () => { ElMessage.success('全局 WebSocket 连接成功'); isWebSocketConnected.value = true; - startHeartbeat(); }; websocket.value.onmessage = (event) => { - showNotification(event.data); + const data = JSON.parse(event.data); + + // 显示通知,并在点击时通过 eventBus 触发全局弹窗事件 + showNotification(data); }; websocket.value.onclose = handleClose; websocket.value.onerror = handleError; @@ -32,7 +33,7 @@ const closeWebSocket = () => { } }; -// 心跳检测 +// 启动心跳 const startHeartbeat = () => { if (heartbeatInterval) return; heartbeatInterval = window.setInterval(() => { @@ -42,6 +43,7 @@ const startHeartbeat = () => { }, 5000); }; +// 停止心跳 const stopHeartbeat = () => { if (heartbeatInterval) { clearInterval(heartbeatInterval); @@ -54,32 +56,34 @@ const handleClose = () => { ElMessage.warning('WebSocket 连接已关闭'); isWebSocketConnected.value = false; stopHeartbeat(); - localStorage.setItem('isPopupEnabled', 'False'); }; +// 处理错误 const handleError = () => { ElMessage.error('WebSocket 连接出错'); isWebSocketConnected.value = false; stopHeartbeat(); }; -// 显示通知 -const showNotification = (message: string) => { - if (Notification.permission === 'granted') { - new Notification('新消息', { - body: message, - icon: '/path/to/icon.png', - }); - } else if (Notification.permission !== 'denied') { - Notification.requestPermission().then((permission) => { - if (permission === 'granted') { - new Notification('新消息', { - body: message, - icon: '/path/to/icon.png', - }); - } - }); - } +// 显示自定义通知 +const showNotification = (data: any) => { + ElNotification({ + title: '新告警', + message: ` +
+

告警编号:${data.id || '未知'}

+

摄像头编号:${data.camera_id || '未知'}

+

摄像头名称:${data.camera.name || '未知'}

+
+ `, + dangerouslyUseHTMLString: true, + duration: 5000, + customClass: 'custom-notification', + onClick: () => { + // 触发全局事件总线的 showDialog 事件 + eventBus.emit('showDialog', data); + } + }); }; // 导出类型和方法 diff --git a/tsconfig.json b/tsconfig.json index 66b5e57..7c6c941 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,5 +7,13 @@ { "path": "./tsconfig.app.json" } - ] + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + } + + }