feat:iot messge

pull/764/head
alwayssuper 2025-04-14 15:25:12 +08:00
parent fc3018bfb2
commit feab54c11e
3 changed files with 241 additions and 246 deletions

View File

@ -1397,42 +1397,36 @@ packages:
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.0':
resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.0':
resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.0':
resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.0':
resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.0':
resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.5.0':
resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==}
@ -1536,55 +1530,46 @@ packages:
resolution: {integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.27.4':
resolution: {integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.27.4':
resolution: {integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.27.4':
resolution: {integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-powerpc64le-gnu@4.27.4':
resolution: {integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.27.4':
resolution: {integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.27.4':
resolution: {integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.27.4':
resolution: {integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.27.4':
resolution: {integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.27.4':
resolution: {integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==}
@ -1630,28 +1615,24 @@ packages:
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@swc/core-linux-arm64-musl@1.9.3':
resolution: {integrity: sha512-tzVH480RY6RbMl/QRgh5HK3zn1ZTFsThuxDGo6Iuk1MdwIbdFYUY034heWUTI4u3Db97ArKh0hNL0xhO3+PZdg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@swc/core-linux-x64-gnu@1.9.3':
resolution: {integrity: sha512-ivXXBRDXDc9k4cdv10R21ccBmGebVOwKXT/UdH1PhxUn9m/h8erAWjz5pcELwjiMf27WokqPgaWVfaclDbgE+w==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-musl@1.9.3':
resolution: {integrity: sha512-ILsGMgfnOz1HwdDz+ZgEuomIwkP1PHT6maigZxaCIuC6OPEhKE8uYna22uU63XvYcLQvZYDzpR3ms47WQPuNEg==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@swc/core-win32-arm64-msvc@1.9.3':
resolution: {integrity: sha512-e+XmltDVIHieUnNJHtspn6B+PCcFOMYXNJB1GqoCcyinkEIQNwC8KtWgMqUucUbEWJkPc35NHy9k8aCXRmw9Kg==}

View File

@ -0,0 +1,232 @@
<template>
<el-card class="chart-card" shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="text-base font-medium text-gray-600">上下行消息量统计</span>
<div class="flex items-center space-x-2">
<el-radio-group v-model="timeRange" @change="handleTimeRangeChange">
<el-radio-button label="8h">最近8小时</el-radio-button>
<el-radio-button label="24h">最近24小时</el-radio-button>
<el-radio-button label="7d">近一周</el-radio-button>
</el-radio-group>
<el-date-picker
v-model="dateRange"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
@change="handleDateRangeChange"
/>
</div>
</div>
</template>
<div ref="messageChartRef" class="h-[300px]"></div>
</el-card>
</template>
<script lang="ts" setup>
import * as echarts from 'echarts/core'
import { LineChart } from 'echarts/charts'
import { CanvasRenderer } from 'echarts/renderers'
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import { UniversalTransition } from 'echarts/features'
import { IotStatisticsDeviceMessageSummaryRespVO } from '@/api/iot/statistics'
import { formatDate } from '@/utils/formatTime'
import type { PropType } from 'vue'
/** 消息趋势统计卡片 */
defineOptions({ name: 'MessageTrendCard' })
const props = defineProps({
messageStats: {
type: Object as PropType<IotStatisticsDeviceMessageSummaryRespVO>,
required: true
}
})
const emit = defineEmits(['timeRangeChange'])
const timeRange = ref('7d')
const dateRange = ref<[Date, Date] | null>(null)
const messageChartRef = ref()
//
const handleTimeRangeChange = (range: string) => {
const now = Date.now()
let startTime: number
switch (range) {
case '8h':
startTime = now - 8 * 60 * 60 * 1000
break
case '24h':
startTime = now - 24 * 60 * 60 * 1000
break
case '7d':
startTime = now - 7 * 24 * 60 * 60 * 1000
break
default:
return
}
dateRange.value = null
emit('timeRangeChange', { startTime, endTime: now })
}
//
const handleDateRangeChange = (value: [Date, Date] | null) => {
if (value) {
timeRange.value = ''
emit('timeRangeChange', {
startTime: value[0].getTime(),
endTime: value[1].getTime()
})
}
}
//
const initChart = () => {
echarts.use([
LineChart,
CanvasRenderer,
GridComponent,
LegendComponent,
TooltipComponent,
UniversalTransition
])
const timestamps = Array.from(
new Set([
...props.messageStats.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
...props.messageStats.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
])
).sort((a, b) => a - b) //
//
const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
const upData = timestamps.map((ts) => {
const item = props.messageStats.upstreamCounts.find(
(count) => Number(Object.keys(count)[0]) === ts
)
return item ? Object.values(item)[0] : 0
})
const downData = timestamps.map((ts) => {
const item = props.messageStats.downstreamCounts.find(
(count) => Number(Object.keys(count)[0]) === ts
)
return item ? Object.values(item)[0] : 0
})
//
console.log(xdata, upData, downData)
//
const chart = echarts.init(messageChartRef.value)
chart.setOption({
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderColor: '#E5E7EB',
textStyle: {
color: '#374151'
}
},
legend: {
data: ['上行消息量', '下行消息量'],
textStyle: {
color: '#374151',
fontWeight: 500
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xdata,
axisLine: {
lineStyle: {
color: '#E5E7EB'
}
},
axisLabel: {
color: '#6B7280'
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: {
color: '#E5E7EB'
}
},
axisLabel: {
color: '#6B7280'
},
splitLine: {
lineStyle: {
color: '#F3F4F6'
}
}
},
series: [
{
name: '上行消息量',
type: 'line',
smooth: true,
data: upData,
itemStyle: {
color: '#3B82F6'
},
lineStyle: {
width: 2
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
{ offset: 1, color: 'rgba(59, 130, 246, 0)' }
])
}
},
{
name: '下行消息量',
type: 'line',
smooth: true,
data: downData,
itemStyle: {
color: '#10B981'
},
lineStyle: {
width: 2
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
{ offset: 1, color: 'rgba(16, 185, 129, 0)' }
])
}
}
]
})
}
//
watch(
() => props.messageStats,
() => {
initChart()
},
{ deep: true }
)
//
onMounted(() => {
initChart()
})
</script>

View File

@ -52,30 +52,10 @@
<!-- 第三行消息统计行 -->
<el-row>
<el-col :span="24">
<el-card class="chart-card" shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span class="text-base font-medium text-gray-600">上下行消息量统计</span>
<div class="flex items-center space-x-2">
<el-radio-group v-model="timeRange" @change="handleTimeRangeChange">
<el-radio-button label="1h">最近1小时</el-radio-button>
<el-radio-button label="24h">最近24小时</el-radio-button>
<el-radio-button label="7d">近一周</el-radio-button>
</el-radio-group>
<el-date-picker
v-model="dateRange"
type="datetimerange"
range-separator="至"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
@change="handleDateRangeChange"
/>
</div>
</div>
</template>
<div ref="deviceMessageCountChartRef" class="h-[300px]"></div>
</el-card>
<MessageTrendCard
:messageStats="messageStats"
@time-range-change="handleTimeRangeChange"
/>
</el-col>
</el-row>
@ -83,17 +63,6 @@
</template>
<script setup lang="ts" name="Index">
import * as echarts from 'echarts/core'
import {
GridComponent,
LegendComponent,
TitleComponent,
ToolboxComponent,
TooltipComponent
} from 'echarts/components'
import { GaugeChart, LineChart, PieChart } from 'echarts/charts'
import { LabelLayout, UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'
import {
IotStatisticsDeviceMessageSummaryRespVO,
IotStatisticsSummaryRespVO,
@ -103,26 +72,12 @@ import { formatDate } from '@/utils/formatTime'
import ComparisonCard from './components/ComparisonCard.vue'
import DeviceCountCard from './components/DeviceCountCard.vue'
import DeviceStateCountCard from './components/DeviceStateCountCard.vue'
// TODO @super /Users/yunai/Java/yudao-ui-admin-vue3/src/views/mall/home/index.vue
import MessageTrendCard from './components/MessageTrendCard.vue'
/** IoT 首页 */
defineOptions({ name: 'IoTHome' })
// TODO @super使 Echart yudao-ui-admin-vue3/src/views/mall/home/components/TradeTrendCard.vue
echarts.use([
TooltipComponent,
LegendComponent,
PieChart,
CanvasRenderer,
LabelLayout,
TitleComponent,
ToolboxComponent,
GridComponent,
LineChart,
UniversalTransition,
GaugeChart
])
const timeRange = ref('7d') //
const dateRange = ref<[Date, Date] | null>(null)
@ -132,11 +87,6 @@ const queryParams = reactive({
endTime: Date.now() //
})
const deviceCountChartRef = ref() //
const deviceOnlineCountChartRef = ref() // 线
const deviceOfflineChartRef = ref() // 线
const deviceActiveChartRef = ref() //
const deviceMessageCountChartRef = ref() //
//
// TODO @super -1 cursor
@ -161,187 +111,19 @@ const messageStats = ref<IotStatisticsDeviceMessageSummaryRespVO>({
downstreamCounts: {}
})
/** 处理快捷时间范围选择 */
const handleTimeRangeChange = (timeRange: string) => {
const now = Date.now()
let startTime: number
// TODO @super dayjs 1h24h7d utils/formatTime.ts
switch (timeRange) {
case '1h':
startTime = now - 60 * 60 * 1000
break
case '24h':
startTime = now - 24 * 60 * 60 * 1000
break
case '7d':
startTime = now - 7 * 24 * 60 * 60 * 1000
break
default:
return
}
//
dateRange.value = null
//
queryParams.startTime = startTime
queryParams.endTime = now
//
/** 处理时间范围变化 */
const handleTimeRangeChange = (params: { startTime: number; endTime: number }) => {
queryParams.startTime = params.startTime
queryParams.endTime = params.endTime
getStats()
}
/** 处理自定义日期范围选择 */
const handleDateRangeChange = (value: [Date, Date] | null) => {
if (value) {
//
timeRange.value = ''
//
queryParams.startTime = value[0].getTime()
queryParams.endTime = value[1].getTime()
//
getStats()
}
}
/** 获取统计数据 */
const getStats = async () => {
//
statsData.value = await ProductCategoryApi.getIotStatisticsSummary()
//
messageStats.value = await ProductCategoryApi.getIotStatisticsDeviceMessageSummary(queryParams)
//
initCharts()
}
/** 初始化图表 */
const initCharts = () => {
//
initMessageChart()
}
/** 初始化消息统计图表 */
const initMessageChart = () => {
//
// TODO @super idea
const timestamps = Array.from(
new Set([
...messageStats.value.upstreamCounts.map((item) => Number(Object.keys(item)[0])),
...messageStats.value.downstreamCounts.map((item) => Number(Object.keys(item)[0]))
])
).sort((a, b) => a - b) //
//
const xdata = timestamps.map((ts) => formatDate(ts, 'YYYY-MM-DD HH:mm'))
const upData = timestamps.map((ts) => {
const item = messageStats.value.upstreamCounts.find(
(count) => Number(Object.keys(count)[0]) === ts
)
return item ? Object.values(item)[0] : 0
})
const downData = timestamps.map((ts) => {
const item = messageStats.value.downstreamCounts.find(
(count) => Number(Object.keys(count)[0]) === ts
)
return item ? Object.values(item)[0] : 0
})
//
echarts.init(deviceMessageCountChartRef.value).setOption({
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(255, 255, 255, 0.9)',
borderColor: '#E5E7EB',
textStyle: {
color: '#374151'
}
},
legend: {
data: ['上行消息量', '下行消息量'],
textStyle: {
color: '#374151',
fontWeight: 500
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xdata,
axisLine: {
lineStyle: {
color: '#E5E7EB'
}
},
axisLabel: {
color: '#6B7280'
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: {
color: '#E5E7EB'
}
},
axisLabel: {
color: '#6B7280'
},
splitLine: {
lineStyle: {
color: '#F3F4F6'
}
}
},
series: [
{
name: '上行消息量',
type: 'line',
smooth: true, // 线
data: upData,
itemStyle: {
color: '#3B82F6'
},
lineStyle: {
width: 2
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(59, 130, 246, 0.2)' },
{ offset: 1, color: 'rgba(59, 130, 246, 0)' }
])
}
},
{
name: '下行消息量',
type: 'line',
smooth: true, // 线
data: downData,
itemStyle: {
color: '#10B981'
},
lineStyle: {
width: 2
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(16, 185, 129, 0.2)' },
{ offset: 1, color: 'rgba(16, 185, 129, 0)' }
])
}
}
]
})
}
/** 初始化 */