fix:【iot 物联网】linter 报错

This commit is contained in:
YunaiV
2025-10-10 20:26:17 +08:00
parent b6fee5c05b
commit f740461c2a
107 changed files with 7161 additions and 5905 deletions

View File

@@ -1,9 +1,11 @@
/**
* 设备数量饼图配置
*/
export function getDeviceCountChartOptions(productCategoryDeviceCounts: Record<string, number>): any {
export function getDeviceCountChartOptions(
productCategoryDeviceCounts: Record<string, number>,
): any {
const data = Object.entries(productCategoryDeviceCounts).map(
([name, value]) => ({ name, value })
([name, value]) => ({ name, value }),
);
return {
@@ -22,7 +24,7 @@ export function getDeviceCountChartOptions(productCategoryDeviceCounts: Record<s
type: 'pie',
radius: ['50%', '80%'],
center: ['30%', '50%'],
data: data,
data,
emphasis: {
itemStyle: {
shadowBlur: 10,
@@ -42,7 +44,12 @@ export function getDeviceCountChartOptions(productCategoryDeviceCounts: Record<s
/**
* 仪表盘图表配置
*/
export function getGaugeChartOptions(value: number, max: number, color: string, title: string): any {
export function getGaugeChartOptions(
value: number,
max: number,
color: string,
title: string,
): any {
return {
series: [
{
@@ -50,14 +57,14 @@ export function getGaugeChartOptions(value: number, max: number, color: string,
startAngle: 180,
endAngle: 0,
min: 0,
max: max,
max,
center: ['50%', '70%'],
radius: '120%',
progress: {
show: true,
width: 12,
itemStyle: {
color: color,
color,
},
},
axisLine: {
@@ -74,7 +81,7 @@ export function getGaugeChartOptions(value: number, max: number, color: string,
valueAnimation: true,
fontSize: 24,
fontWeight: 'bold',
color: color,
color,
offsetCenter: [0, '-20%'],
formatter: '{value}',
},
@@ -84,10 +91,8 @@ export function getGaugeChartOptions(value: number, max: number, color: string,
fontSize: 14,
color: '#666',
},
data: [{ value: value, name: title }],
data: [{ value, name: title }],
},
],
};
}

View File

@@ -1,16 +1,16 @@
/**
* IoT 首页数据配置文件
*
*
* 该文件封装了 IoT 首页所需的:
* - 统计数据接口定义
* - 业务逻辑函数
* - 工具函数
*/
import { ref, onMounted } from 'vue';
import type { IotStatisticsApi } from '#/api/iot/statistics';
import { onMounted, ref } from 'vue';
import { getStatisticsSummary } from '#/api/iot/statistics';
/** 统计数据接口 - 使用 API 定义的类型 */
@@ -43,13 +43,13 @@ export async function loadStatisticsData(): Promise<StatsData> {
} catch (error) {
console.error('获取统计数据出错:', error);
console.warn('使用 Mock 数据,请检查后端接口是否已实现');
// 返回 Mock 数据用于开发调试
return {
productCategoryCount: 12,
productCount: 45,
deviceCount: 328,
deviceMessageCount: 15678,
deviceMessageCount: 15_678,
productCategoryTodayCount: 2,
productTodayCount: 5,
deviceTodayCount: 23,
@@ -58,10 +58,10 @@ export async function loadStatisticsData(): Promise<StatsData> {
deviceOfflineCount: 48,
deviceInactiveCount: 24,
productCategoryDeviceCounts: {
'智能家居': 120,
'工业设备': 98,
'环境监测': 65,
'智能穿戴': 45,
智能家居: 120,
工业设备: 98,
环境监测: 65,
智能穿戴: 45,
},
};
}
@@ -103,12 +103,11 @@ export function useIotHome() {
/** 格式化数字 - 大数字显示为 K/M */
export const formatNumber = (num: number): string => {
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M';
if (num >= 1_000_000) {
return `${(num / 1_000_000).toFixed(1)}M`;
}
if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
return `${(num / 1000).toFixed(1)}K`;
}
return num.toString();
};

View File

@@ -1,16 +1,16 @@
<script setup lang="ts">
import { Row, Col } from 'ant-design-vue';
import { Page } from '@vben/common-ui';
import { Col, Row } from 'ant-design-vue';
// 导入业务逻辑
import { useIotHome } from './data';
// 导入组件
import ComparisonCard from './modules/ComparisonCard.vue';
import DeviceCountCard from './modules/DeviceCountCard.vue';
import DeviceStateCountCard from './modules/DeviceStateCountCard.vue';
import MessageTrendCard from './modules/MessageTrendCard.vue';
// 导入业务逻辑
import { useIotHome } from './data';
defineOptions({ name: 'IoTHome' });
// 使用业务逻辑 Hook
@@ -25,9 +25,9 @@ const { loading, statsData } = useIotHome();
<ComparisonCard
title="分类数量"
:value="statsData.productCategoryCount"
:todayCount="statsData.productCategoryTodayCount"
:today-count="statsData.productCategoryTodayCount"
icon="menu"
iconColor="text-blue-500"
icon-color="text-blue-500"
:loading="loading"
/>
</Col>
@@ -35,9 +35,9 @@ const { loading, statsData } = useIotHome();
<ComparisonCard
title="产品数量"
:value="statsData.productCount"
:todayCount="statsData.productTodayCount"
:today-count="statsData.productTodayCount"
icon="box"
iconColor="text-orange-500"
icon-color="text-orange-500"
:loading="loading"
/>
</Col>
@@ -45,9 +45,9 @@ const { loading, statsData } = useIotHome();
<ComparisonCard
title="设备数量"
:value="statsData.deviceCount"
:todayCount="statsData.deviceTodayCount"
:today-count="statsData.deviceTodayCount"
icon="cpu"
iconColor="text-purple-500"
icon-color="text-purple-500"
:loading="loading"
/>
</Col>
@@ -55,9 +55,9 @@ const { loading, statsData } = useIotHome();
<ComparisonCard
title="设备消息数"
:value="statsData.deviceMessageCount"
:todayCount="statsData.deviceMessageTodayCount"
:today-count="statsData.deviceMessageTodayCount"
icon="message"
iconColor="text-teal-500"
icon-color="text-teal-500"
:loading="loading"
/>
</Col>
@@ -66,10 +66,10 @@ const { loading, statsData } = useIotHome();
<!-- 第二行图表 -->
<Row :gutter="16" class="mb-4">
<Col :span="12">
<DeviceCountCard :statsData="statsData" :loading="loading" />
<DeviceCountCard :stats-data="statsData" :loading="loading" />
</Col>
<Col :span="12">
<DeviceStateCountCard :statsData="statsData" :loading="loading" />
<DeviceStateCountCard :stats-data="statsData" :loading="loading" />
</Col>
</Row>

View File

@@ -1,9 +1,40 @@
<script setup lang="ts">
import { computed } from 'vue';
import { CountTo } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { Card } from 'ant-design-vue';
defineOptions({ name: 'ComparisonCard' });
const props = defineProps<{
icon: string;
iconColor?: string;
loading?: boolean;
title: string;
todayCount: number;
value: number;
}>();
const iconMap: Record<string, any> = {
menu: createIconifyIcon('ant-design:appstore-outlined'),
box: createIconifyIcon('ant-design:box-plot-outlined'),
cpu: createIconifyIcon('ant-design:cluster-outlined'),
message: createIconifyIcon('ant-design:message-outlined'),
};
const IconComponent = computed(() => iconMap[props.icon] || iconMap.menu);
</script>
<template>
<Card class="stat-card" :loading="loading">
<div class="flex flex-col h-full">
<div class="flex justify-between items-start mb-4">
<div class="flex flex-col flex-1">
<span class="text-gray-500 text-sm font-medium mb-2">{{ title }}</span>
<div class="flex h-full flex-col">
<div class="mb-4 flex items-start justify-between">
<div class="flex flex-1 flex-col">
<span class="mb-2 text-sm font-medium text-gray-500">{{
title
}}</span>
<span class="text-3xl font-bold text-gray-800">
<span v-if="value === -1">--</span>
<CountTo v-else :end-val="value" :duration="1000" />
@@ -13,45 +44,20 @@
<IconComponent />
</div>
</div>
<div class="mt-auto pt-3 border-t border-gray-100">
<div class="flex justify-between items-center text-sm">
<div class="mt-auto border-t border-gray-100 pt-3">
<div class="flex items-center justify-between text-sm">
<span class="text-gray-400">今日新增</span>
<span v-if="todayCount === -1" class="text-gray-400">--</span>
<span v-else class="text-green-500 font-medium">+{{ todayCount }}</span>
<span v-else class="font-medium text-green-500"
>+{{ todayCount }}</span
>
</div>
</div>
</div>
</Card>
</template>
<script setup lang="ts">
import { Card } from 'ant-design-vue';
import { CountTo } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { computed } from 'vue';
defineOptions({ name: 'ComparisonCard' });
const props = defineProps<{
title: string;
value: number;
todayCount: number;
icon: string;
iconColor?: string;
loading?: boolean;
}>();
const iconMap: Record<string, any> = {
'menu': createIconifyIcon('ant-design:appstore-outlined'),
'box': createIconifyIcon('ant-design:box-plot-outlined'),
'cpu': createIconifyIcon('ant-design:cluster-outlined'),
'message': createIconifyIcon('ant-design:message-outlined'),
};
const IconComponent = computed(() => iconMap[props.icon] || iconMap.menu);
</script>
<style scoped>
.stat-card {
height: 160px;

View File

@@ -1,28 +1,17 @@
<template>
<Card title="设备数量统计" :loading="loading" class="chart-card">
<div v-if="loading && !hasData" class="h-[300px] flex justify-center items-center">
<Empty description="加载中..." />
</div>
<div v-else-if="!hasData" class="h-[300px] flex justify-center items-center">
<Empty description="暂无数据" />
</div>
<div v-else>
<EchartsUI ref="deviceCountChartRef" class="h-[400px] w-full" />
</div>
</Card>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick } from 'vue';
import { Card, Empty } from 'ant-design-vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import type { IotStatisticsApi } from '#/api/iot/statistics';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { Card, Empty } from 'ant-design-vue';
defineOptions({ name: 'DeviceCountCard' });
const props = defineProps<{
statsData: IotStatisticsApi.StatisticsSummary;
loading?: boolean;
statsData: IotStatisticsApi.StatisticsSummary;
}>();
const deviceCountChartRef = ref();
@@ -31,18 +20,20 @@ const { renderEcharts } = useEcharts(deviceCountChartRef);
/** 是否有数据 */
const hasData = computed(() => {
if (!props.statsData) return false;
const categories = Object.entries(props.statsData.productCategoryDeviceCounts || {});
const categories = Object.entries(
props.statsData.productCategoryDeviceCounts || {},
);
return categories.length > 0 && props.statsData.deviceCount !== 0;
});
/** 初始化图表 */
const initChart = () => {
if (!hasData.value) return;
nextTick(() => {
const data = Object.entries(props.statsData.productCategoryDeviceCounts).map(
([name, value]) => ({ name, value })
);
const data = Object.entries(
props.statsData.productCategoryDeviceCounts,
).map(([name, value]) => ({ name, value }));
renderEcharts({
tooltip: {
@@ -98,7 +89,7 @@ const initChart = () => {
labelLine: {
show: false,
},
data: data,
data,
},
],
});
@@ -111,7 +102,7 @@ watch(
() => {
initChart();
},
{ deep: true }
{ deep: true },
);
/** 组件挂载时初始化图表 */
@@ -120,6 +111,26 @@ onMounted(() => {
});
</script>
<template>
<Card title="设备数量统计" :loading="loading" class="chart-card">
<div
v-if="loading && !hasData"
class="flex h-[300px] items-center justify-center"
>
<Empty description="加载中..." />
</div>
<div
v-else-if="!hasData"
class="flex h-[300px] items-center justify-center"
>
<Empty description="暂无数据" />
</div>
<div v-else>
<EchartsUI ref="deviceCountChartRef" class="h-[400px] w-full" />
</div>
</Card>
</template>
<style scoped>
.chart-card {
height: 100%;

View File

@@ -1,36 +1,17 @@
<template>
<Card title="设备状态统计" :loading="loading" class="chart-card">
<div v-if="loading && !hasData" class="h-[300px] flex justify-center items-center">
<Empty description="加载中..." />
</div>
<div v-else-if="!hasData" class="h-[300px] flex justify-center items-center">
<Empty description="暂无数据" />
</div>
<Row v-else class="h-[280px]">
<Col :span="8" class="flex items-center justify-center">
<EchartsUI ref="deviceOnlineChartRef" class="h-[250px] w-full" />
</Col>
<Col :span="8" class="flex items-center justify-center">
<EchartsUI ref="deviceOfflineChartRef" class="h-[250px] w-full" />
</Col>
<Col :span="8" class="flex items-center justify-center">
<EchartsUI ref="deviceInactiveChartRef" class="h-[250px] w-full" />
</Col>
</Row>
</Card>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick } from 'vue';
import { Card, Empty, Row, Col } from 'ant-design-vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import type { IotStatisticsApi } from '#/api/iot/statistics';
import { computed, nextTick, onMounted, ref, watch } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { Card, Col, Empty, Row } from 'ant-design-vue';
defineOptions({ name: 'DeviceStateCountCard' });
const props = defineProps<{
statsData: IotStatisticsApi.StatisticsSummary;
loading?: boolean;
statsData: IotStatisticsApi.StatisticsSummary;
}>();
const deviceOnlineChartRef = ref();
@@ -39,7 +20,9 @@ const deviceInactiveChartRef = ref();
const { renderEcharts: renderOnlineChart } = useEcharts(deviceOnlineChartRef);
const { renderEcharts: renderOfflineChart } = useEcharts(deviceOfflineChartRef);
const { renderEcharts: renderInactiveChart } = useEcharts(deviceInactiveChartRef);
const { renderEcharts: renderInactiveChart } = useEcharts(
deviceInactiveChartRef,
);
/** 是否有数据 */
const hasData = computed(() => {
@@ -63,7 +46,7 @@ const getGaugeOption = (value: number, color: string, title: string): any => {
show: true,
width: 12,
itemStyle: {
color: color,
color,
},
},
axisLine: {
@@ -86,11 +69,11 @@ const getGaugeOption = (value: number, color: string, title: string): any => {
valueAnimation: true,
fontSize: 32,
fontWeight: 'bold',
color: color,
color,
offsetCenter: [0, '10%'],
formatter: (val: number) => `${val}`,
},
data: [{ value: value, name: title }],
data: [{ value, name: title }],
},
],
};
@@ -103,15 +86,19 @@ const initCharts = () => {
nextTick(() => {
// 在线设备
renderOnlineChart(
getGaugeOption(props.statsData.deviceOnlineCount, '#52c41a', '在线设备')
getGaugeOption(props.statsData.deviceOnlineCount, '#52c41a', '在线设备'),
);
// 离线设备
renderOfflineChart(
getGaugeOption(props.statsData.deviceOfflineCount, '#ff4d4f', '离线设备')
getGaugeOption(props.statsData.deviceOfflineCount, '#ff4d4f', '离线设备'),
);
// 待激活设备
renderInactiveChart(
getGaugeOption(props.statsData.deviceInactiveCount, '#1890ff', '待激活设备')
getGaugeOption(
props.statsData.deviceInactiveCount,
'#1890ff',
'待激活设备',
),
);
});
};
@@ -122,7 +109,7 @@ watch(
() => {
initCharts();
},
{ deep: true }
{ deep: true },
);
/** 组件挂载时初始化图表 */
@@ -131,6 +118,34 @@ onMounted(() => {
});
</script>
<template>
<Card title="设备状态统计" :loading="loading" class="chart-card">
<div
v-if="loading && !hasData"
class="flex h-[300px] items-center justify-center"
>
<Empty description="加载中..." />
</div>
<div
v-else-if="!hasData"
class="flex h-[300px] items-center justify-center"
>
<Empty description="暂无数据" />
</div>
<Row v-else class="h-[280px]">
<Col :span="8" class="flex items-center justify-center">
<EchartsUI ref="deviceOnlineChartRef" class="h-[250px] w-full" />
</Col>
<Col :span="8" class="flex items-center justify-center">
<EchartsUI ref="deviceOfflineChartRef" class="h-[250px] w-full" />
</Col>
<Col :span="8" class="flex items-center justify-center">
<EchartsUI ref="deviceInactiveChartRef" class="h-[250px] w-full" />
</Col>
</Row>
</Card>
</template>
<style scoped>
.chart-card {
height: 100%;

View File

@@ -1,70 +1,24 @@
<template>
<Card class="chart-card" :loading="loading">
<template #title>
<div class="flex items-center justify-between flex-wrap gap-2">
<span class="text-base font-medium">上下行消息量统计</span>
<Space :size="8">
<Button
:type="activeTimeRange === '1h' ? 'primary' : 'default'"
size="small"
@click="setTimeRange('1h')"
>
最近1小时
</Button>
<Button
:type="activeTimeRange === '24h' ? 'primary' : 'default'"
size="small"
@click="setTimeRange('24h')"
>
最近24小时
</Button>
<Button
:type="activeTimeRange === '7d' ? 'primary' : 'default'"
size="small"
@click="setTimeRange('7d')"
>
近一周
</Button>
<RangePicker
v-model:value="dateRange"
format="YYYY-MM-DD"
:placeholder="['开始时间', '结束时间']"
@change="handleDateChange"
size="small"
style="width: 240px"
/>
</Space>
</div>
</template>
<div v-if="loading" class="h-[350px] flex justify-center items-center">
<Empty description="加载中..." />
</div>
<div v-else-if="!hasData" class="h-[350px] flex justify-center items-center">
<Empty description="暂无数据" />
</div>
<div v-else>
<EchartsUI ref="messageChartRef" class="h-[350px] w-full" />
</div>
</Card>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, nextTick } from 'vue';
import { Card, Empty, Space, DatePicker, Button } from 'ant-design-vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import {
StatisticsApi,
type IotStatisticsDeviceMessageSummaryByDateRespVO,
type IotStatisticsDeviceMessageReqVO,
import type {
IotStatisticsDeviceMessageReqVO,
IotStatisticsDeviceMessageSummaryByDateRespVO,
} from '#/api/iot/statistics';
const { RangePicker } = DatePicker;
import { computed, nextTick, onMounted, reactive, ref } from 'vue';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import { Button, Card, DatePicker, Empty, Space } from 'ant-design-vue';
import dayjs from 'dayjs';
import { StatisticsApi } from '#/api/iot/statistics';
defineOptions({ name: 'MessageTrendCard' });
const { RangePicker } = DatePicker;
const messageChartRef = ref();
const { renderEcharts } = useEcharts(messageChartRef);
@@ -87,33 +41,37 @@ const hasData = computed(() => {
const setTimeRange = (range: string) => {
activeTimeRange.value = range;
dateRange.value = undefined; // 清空自定义时间选择
let start: Dayjs;
let end = dayjs();
const end = dayjs();
switch (range) {
case '1h':
case '1h': {
start = dayjs().subtract(1, 'hour');
queryParams.interval = 1; // 按分钟
break;
case '24h':
start = dayjs().subtract(24, 'hour');
queryParams.interval = 1; // 按小时
break;
case '7d':
}
case '7d': {
start = dayjs().subtract(7, 'day');
queryParams.interval = 1; // 按天
break;
default:
}
case '24h': {
start = dayjs().subtract(24, 'hour');
queryParams.interval = 1; // 按小时
break;
}
default: {
start = dayjs().subtract(7, 'day');
queryParams.interval = 1;
}
}
queryParams.times = [
start.format('YYYY-MM-DD HH:mm:ss'),
end.format('YYYY-MM-DD HH:mm:ss'),
];
fetchMessageData();
};
@@ -133,10 +91,11 @@ const handleDateChange = () => {
// 获取消息统计数据
const fetchMessageData = async () => {
if (!queryParams.times || queryParams.times.length !== 2) return;
loading.value = true;
try {
messageData.value = await StatisticsApi.getDeviceMessageSummaryByDate(queryParams);
messageData.value =
await StatisticsApi.getDeviceMessageSummaryByDate(queryParams);
await nextTick();
initChart();
} catch (error) {
@@ -230,6 +189,60 @@ onMounted(() => {
});
</script>
<template>
<Card class="chart-card" :loading="loading">
<template #title>
<div class="flex flex-wrap items-center justify-between gap-2">
<span class="text-base font-medium">上下行消息量统计</span>
<Space :size="8">
<Button
:type="activeTimeRange === '1h' ? 'primary' : 'default'"
size="small"
@click="setTimeRange('1h')"
>
最近1小时
</Button>
<Button
:type="activeTimeRange === '24h' ? 'primary' : 'default'"
size="small"
@click="setTimeRange('24h')"
>
最近24小时
</Button>
<Button
:type="activeTimeRange === '7d' ? 'primary' : 'default'"
size="small"
@click="setTimeRange('7d')"
>
近一周
</Button>
<RangePicker
v-model:value="dateRange"
format="YYYY-MM-DD"
:placeholder="['开始时间', '结束时间']"
@change="handleDateChange"
size="small"
style="width: 240px"
/>
</Space>
</div>
</template>
<div v-if="loading" class="flex h-[350px] items-center justify-center">
<Empty description="加载中..." />
</div>
<div
v-else-if="!hasData"
class="flex h-[350px] items-center justify-center"
>
<Empty description="暂无数据" />
</div>
<div v-else>
<EchartsUI ref="messageChartRef" class="h-[350px] w-full" />
</div>
</Card>
</template>
<style scoped>
.chart-card {
height: 100%;