Commit 568600c8 by 吴春元

对接:设备接入数量、系统分类统计、设备分类占比、设备告警统计、近 7 天告警统计、已部署系统、下级单位设备统计

parent 0768b0b5
...@@ -10,13 +10,24 @@ declare module 'vue' { ...@@ -10,13 +10,24 @@ declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AsyncMap3D: typeof import('./src/components/ThreeMap/AsyncMap3D.vue')['default'] AsyncMap3D: typeof import('./src/components/ThreeMap/AsyncMap3D.vue')['default']
ContainerWrap: typeof import('./src/components/ContainerWrap/index.vue')['default'] ContainerWrap: typeof import('./src/components/ContainerWrap/index.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCascader: typeof import('element-plus/es')['ElCascader'] ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon'] ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElOption: typeof import('element-plus/es')['ElOption'] ElOption: typeof import('element-plus/es')['ElOption']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow'] ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default'] HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
ItemWrap: typeof import('./src/components/ItemWrap/index.vue')['default'] ItemWrap: typeof import('./src/components/ItemWrap/index.vue')['default']
ItemWrap2: typeof import('./src/components/ItemWrap2/index.vue')['default'] ItemWrap2: typeof import('./src/components/ItemWrap2/index.vue')['default']
......
...@@ -35,6 +35,12 @@ ...@@ -35,6 +35,12 @@
<script type="text/javascript" <script type="text/javascript"
src="https://api.map.baidu.com/api?v=1.0&&type=webgl&ak=i8RgXE4a6vzGgbFs5s4ynlyUh6YnUQIB"> src="https://api.map.baidu.com/api?v=1.0&&type=webgl&ak=i8RgXE4a6vzGgbFs5s4ynlyUh6YnUQIB">
</script> </script>
<script type="text/javascript"
src="https://webapi.amap.com/maps?v=2.0&key=fee919a8e608c39d1d528ec662a2ca17&plugin=AMap.DistrictSearch"></script>
<script src="https://a.amap.com/jsapi_demos/static/demo-center/js/demoutils.js"></script>
<!-- <link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css" /> -->
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -510,9 +510,10 @@ import { ...@@ -510,9 +510,10 @@ import {
virtualizedScrollbarProps, virtualizedScrollbarProps,
watermarkProps, watermarkProps,
zIndexContextKey zIndexContextKey
} from "./chunk-QOFRX7VY.js"; } from "./chunk-IQCPA24U.js";
import "./chunk-S4WGN4LU.js";
import "./chunk-TKM7ANES.js"; import "./chunk-TKM7ANES.js";
import "./chunk-S4WGN4LU.js";
import "./chunk-3EK6V4TN.js";
import "./chunk-JDSL2H4L.js"; import "./chunk-JDSL2H4L.js";
import "./chunk-G3PMV62Z.js"; import "./chunk-G3PMV62Z.js";
var export_dayjs = import_dayjs.default; var export_dayjs = import_dayjs.default;
......
import request from "@/utils/request";
// 获取截止时间
export function nowTime() {
return request({
url: "/system/dept/getNow",
method: "get",
});
}
// 获取设备接入数量(在线、离线)
export function allDevice(deptId: number) {
return request({
url: "/device/baseDevice/getAllDevice?deptId=" + deptId,
method: "get",
});
}
// 获取设备分类占比
export function deviceCountByType(deptId: number, grade: String) {
return request({
url:
"/device/baseDevice/getDeviceCountByType?deptId=" +
deptId +
"&grade=" +
grade,
method: "get",
});
}
// 获取设备接入数量(在线、离线)
export function deviceCountByChild(deptId: number) {
return request({
url: "/device/baseDevice/getDeviceCountByChild?deptId=" + deptId,
method: "get",
});
}
// 设备告警统计
export function alarmNumCount(deptId: number) {
return request({
url: "/device/alarmInfo/alarmNumCount?deptId=" + deptId,
method: "get",
});
}
// 近7天告警统计
export function countByDay(data: any) {
return request({
url: "/device/alarmInfo/countByDay",
method: "post",
data: data,
});
}
// 已部署系统
export function countBySysType(deptId: number) {
return request({
url: "/system/dept/countBySysType?deptId=" + deptId,
method: "get",
});
}
...@@ -71,6 +71,7 @@ $blue-hover: #40a9ff; // 悬停蓝色 ...@@ -71,6 +71,7 @@ $blue-hover: #40a9ff; // 悬停蓝色
.el-input__inner { .el-input__inner {
color: #{$white-text}; color: #{$white-text};
} }
} }
......
<template> <template>
<div class="user flex items-center cursor-pointer"> <div class="user flex cursor-pointer mt-4 mr-3" @click="logout">
<el-icon color="#fff" size="24"><SwitchButton /></el-icon> <el-icon color="#fff" size="24"><SwitchButton /></el-icon>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from "vue";
import { SwitchButton } from '@element-plus/icons-vue' import { SwitchButton } from "@element-plus/icons-vue";
import useUserStore from "@/store/modules/user";
import { ElMessageBox } from "element-plus";
const user = ref({ function logout() {
name: '张三', ElMessageBox.confirm("确定注销并退出系统吗?", "提示", {
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', confirmButtonText: "确定",
}) cancelButtonText: "取消",
type: "warning",
})
.then(() => {
useUserStore()
.logOut()
.then(() => {
location.href = "/";
});
})
.catch(() => {});
}
</script> </script>
...@@ -45,7 +45,9 @@ ...@@ -45,7 +45,9 @@
</el-select> </el-select>
</div> </div>
</div> </div>
<User class=""></User>
</div> </div>
<div class="content relative"> <div class="content relative">
<router-view></router-view> <router-view></router-view>
</div> </div>
...@@ -55,6 +57,7 @@ ...@@ -55,6 +57,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { AppSetting } from "@/setting"; import { AppSetting } from "@/setting";
import Menu from "./components/Menu.vue"; import Menu from "./components/Menu.vue";
import User from "./components/User.vue";
import { ref } from "vue"; import { ref } from "vue";
import { eventBus } from "@/eventBus"; import { eventBus } from "@/eventBus";
......
...@@ -4,18 +4,18 @@ ...@@ -4,18 +4,18 @@
<!-- 第一列(3个均匀分布的框) --> <!-- 第一列(3个均匀分布的框) -->
<el-col :span="7" class="column"> <el-col :span="7" class="column">
<div class="grid-content tall-box"> <div class="grid-content tall-box">
<ContainerWrap title="设备接入数量" endDate="2025-10-18"> <ContainerWrap title="设备接入数量" :endDate="nowTimes">
<DeviceNumber /> <DeviceNumber :dept-id="selectedOffice[0]" />
</ContainerWrap> </ContainerWrap>
</div> </div>
<div class="grid-content tall-box"> <div class="grid-content tall-box">
<ContainerWrap title="系统分类统计" endDate="2025-10-18"> <ContainerWrap title="系统分类统计" :endDate="nowTimes">
<SysClassification /> <SysClassification :dept-id="selectedOffice[0]" />
</ContainerWrap> </ContainerWrap>
</div> </div>
<div class="grid-content tall-box"> <div class="grid-content tall-box">
<ContainerWrap title="设备分类占比" endDate="2025-10-18"> <ContainerWrap title="设备分类占比" :endDate="nowTimes">
<DeviceClassification /> <DeviceClassification :dept-id="selectedOffice[0]" />
</ContainerWrap> </ContainerWrap>
</div> </div>
</el-col> </el-col>
...@@ -23,13 +23,13 @@ ...@@ -23,13 +23,13 @@
<!-- 第二列(第一个框占2/3,第二个框占1/3) --> <!-- 第二列(第一个框占2/3,第二个框占1/3) -->
<el-col :span="10" class="column"> <el-col :span="10" class="column">
<div class="grid-content wide-box" style="flex: 2"> <div class="grid-content wide-box" style="flex: 2">
<ContainerWrap title="设备分布图" endDate="2025-10-18"> <ContainerWrap title="设备分布图" :endDate="nowTimes">
<MapEcharts /> <MapEcharts :dept-id="selectedOffice[0]" />
</ContainerWrap> </ContainerWrap>
</div> </div>
<div class="grid-content wide-box" style="flex: 1"> <div class="grid-content wide-box" style="flex: 1">
<ContainerWrap title="各分子公司设备统计" endDate="2025-10-18"> <ContainerWrap title="下级单位设备统计" :endDate="nowTimes">
<SubsidiaryCompanyEquipment /> <SubsidiaryCompanyEquipment :dept-id="selectedOffice[0]" />
</ContainerWrap> </ContainerWrap>
</div> </div>
</el-col> </el-col>
...@@ -37,18 +37,18 @@ ...@@ -37,18 +37,18 @@
<!-- 第三列(3个均匀分布的框) --> <!-- 第三列(3个均匀分布的框) -->
<el-col :span="7" class="column"> <el-col :span="7" class="column">
<div class="grid-content tall-box"> <div class="grid-content tall-box">
<ContainerWrap title="设备告警统计" endDate="2025-10-18"> <ContainerWrap title="设备告警统计" :endDate="nowTimes">
<EquipmentAlarm /> <EquipmentAlarm :dept-id="selectedOffice[0]" />
</ContainerWrap> </ContainerWrap>
</div> </div>
<div class="grid-content tall-box"> <div class="grid-content tall-box">
<ContainerWrap title="近7天告警统计" endDate="2025-10-18"> <ContainerWrap title="近7天告警统计" :endDate="nowTimes">
<Alert7Days /> <Alert7Days :dept-id="selectedOffice[0]" />
</ContainerWrap> </ContainerWrap>
</div> </div>
<div class="grid-content tall-box"> <div class="grid-content tall-box">
<ContainerWrap title="已部署系统" endDate="2025-10-18"> <ContainerWrap title="已部署系统" :endDate="nowTimes">
<DeployProject /> <DeployProject :dept-id="selectedOffice[0]" />
</ContainerWrap> </ContainerWrap>
</div> </div>
</el-col> </el-col>
...@@ -126,7 +126,7 @@ import SysClassification from "./components/SysClassification.vue"; ...@@ -126,7 +126,7 @@ import SysClassification from "./components/SysClassification.vue";
import DeviceClassification from "./components/DeviceClassification.vue"; import DeviceClassification from "./components/DeviceClassification.vue";
import EquipmentAlarm from "./components/EquipmentAlarm.vue"; import EquipmentAlarm from "./components/EquipmentAlarm.vue";
import Alert7Days from "./components/Alert7Days.vue"; import Alert7Days from "./components/Alert7Days.vue";
import MapEcharts from "./components/MapEcharts.vue"; import MapEcharts from "./components/Map1.vue";
import DeployProject from "./components/DeployProject.vue"; import DeployProject from "./components/DeployProject.vue";
import SubsidiaryCompanyEquipment from "./components/SubsidiaryCompanyEquipment.vue"; import SubsidiaryCompanyEquipment from "./components/SubsidiaryCompanyEquipment.vue";
import ContainerWrap from "@/components/ContainerWrap/index.vue"; import ContainerWrap from "@/components/ContainerWrap/index.vue";
...@@ -134,8 +134,10 @@ import ContainerWrap from "@/components/ContainerWrap/index.vue"; ...@@ -134,8 +134,10 @@ import ContainerWrap from "@/components/ContainerWrap/index.vue";
import { ref, onMounted, onUnmounted } from "vue"; import { ref, onMounted, onUnmounted } from "vue";
import { eventBus } from "@/eventBus"; import { eventBus } from "@/eventBus";
import { nowTime } from "@/api/iot/home";
const nowTimes = ref("");
const dialogVisible = ref(false); const dialogVisible = ref(false);
const selectedOffice = ref([]); const selectedOffice = ref([100]);
const officeTree = ref([ const officeTree = ref([
{ {
value: "1", value: "1",
...@@ -251,12 +253,26 @@ onMounted(() => { ...@@ -251,12 +253,26 @@ onMounted(() => {
}); });
// 监听机构变化事件 // 监听机构变化事件
eventBus.on("officeChange", (office) => {}); eventBus.on("officeChange", (office) => {
selectedOffice.value = office;
});
}); });
onUnmounted(() => { onUnmounted(() => {
eventBus.off("sceneChange"); // 避免内存泄漏 eventBus.off("sceneChange"); // 避免内存泄漏
}); });
//获取截止时间
function getNowTime() {
nowTime().then((res: any) => {
if (res.code === 200) {
//将res.data时间戳转换为时间格式
nowTimes.value = new Date(res.data).toLocaleString();
}
});
}
getNowTime();
</script> </script>
<style scoped> <style scoped>
......
...@@ -6,21 +6,70 @@ ...@@ -6,21 +6,70 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from "vue"; import { ref, onMounted, onBeforeUnmount } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { countByDay } from "@/api/iot/home";
import { de } from "element-plus/es/locales.mjs";
const chartRef = ref<HTMLDivElement>(); const chartRef = ref<HTMLDivElement>();
const chartContainer = ref<HTMLDivElement>(); const chartContainer = ref<HTMLDivElement>();
const chartInstance = ref<echarts.ECharts | null>(null); const chartInstance = ref<echarts.ECharts | null>(null);
let resizeObserver: ResizeObserver | null = null; let resizeObserver: ResizeObserver | null = null;
const yData = ref([2, 1, 1, 1, 3, 2, 2]); const xData = ref<any[]>([
const wData = ref([0, 0, 0, 1, 1, 1, 0]); {
time: "",
timestamp: 0,
haveHandle: 0, //已处理
notHandle: 0, //未处理
haveIgnore: 0, //已忽略
},
{
time: "",
timestamp: 0,
haveHandle: 0, //已处理
notHandle: 0, //未处理
haveIgnore: 0, //已忽略
},
{
time: "",
timestamp: 0,
haveHandle: 0, //已处理
notHandle: 0, //未处理
haveIgnore: 0, //已忽略
},
{
time: "",
timestamp: 0,
haveHandle: 0, //已处理
notHandle: 0, //未处理
haveIgnore: 0, //已忽略
},
{
time: "",
timestamp: 0,
haveHandle: 0, //已处理
notHandle: 0, //未处理
haveIgnore: 0, //已忽略
},
{
time: "",
timestamp: 0,
haveHandle: 0, //已处理
notHandle: 0, //未处理
haveIgnore: 0, //已忽略
},
{
time: "",
timestamp: 0,
haveHandle: 0, //已处理
notHandle: 0, //未处理
haveIgnore: 0, //已忽略
},
]);
const initChart = () => { const initChart = () => {
if (!chartRef.value) return; if (!chartRef.value) return;
// 销毁旧实例 // 销毁旧实例
chartInstance.value?.dispose(); chartInstance.value?.dispose();
chartInstance.value = echarts.init(chartRef.value); chartInstance.value = echarts.init(chartRef.value);
const option = { const option = {
...@@ -31,7 +80,7 @@ const initChart = () => { ...@@ -31,7 +80,7 @@ const initChart = () => {
}, },
}, },
legend: { legend: {
data: ["已处理", "未处理"], data: ["已处理", "未处理", "已忽略"],
top: 0, top: 0,
right: 7, right: 7,
itemWidth: 15, itemWidth: 15,
...@@ -42,18 +91,11 @@ const initChart = () => { ...@@ -42,18 +91,11 @@ const initChart = () => {
}, },
}, },
xAxis: { xAxis: {
data: [ data: xData.value.map((item) => item.time.substring(0, 10)),
"2025-10-13",
"2025-10-14",
"2025-10-15",
"2025-10-16",
"2025-10-17",
"2025-10-18",
"2025-10-19",
],
axisLabel: { axisLabel: {
color: "#66FFFF", color: "#66FFFF",
fontSize: 11, fontSize: 11,
rotate: 15,
}, },
}, },
yAxis: { yAxis: {
...@@ -73,7 +115,7 @@ const initChart = () => { ...@@ -73,7 +115,7 @@ const initChart = () => {
{ {
name: "已处理", name: "已处理",
type: "bar", type: "bar",
barWidth: 16, barWidth: 13,
label: { label: {
show: true, show: true,
position: "top", position: "top",
...@@ -88,14 +130,14 @@ const initChart = () => { ...@@ -88,14 +130,14 @@ const initChart = () => {
{ offset: 1, color: "rgba(146, 225, 255, 1)" }, { offset: 1, color: "rgba(146, 225, 255, 1)" },
]), ]),
}, },
data: yData.value, data: xData.value.map((item) => item.haveHandle),
z: 10, z: 10,
zlevel: 0, zlevel: 0,
}, },
{ {
name: "未处理", name: "未处理",
type: "bar", type: "bar",
barWidth: 16, barWidth: 13,
label: { label: {
show: true, show: true,
position: "top", position: "top",
...@@ -110,7 +152,31 @@ const initChart = () => { ...@@ -110,7 +152,31 @@ const initChart = () => {
{ offset: 1, color: "rgba(250, 76, 102, 1)" }, { offset: 1, color: "rgba(250, 76, 102, 1)" },
]), ]),
}, },
data: wData.value, data: xData.value.map((item) => item.notHandle),
z: 10,
zlevel: 0,
},
{
name: "已忽略",
type: "bar",
barWidth: 13,
label: {
show: true,
position: "top",
textStyle: {
color: "#fff",
fontSize: 10,
},
},
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
// 已忽略灰色
// 已忽略灰色
{ offset: 1, color: "rgba(128, 128, 128, 1)" },
{ offset: 0, color: "rgba(255, 255, 255, 1)" },
]),
},
data: xData.value.map((item) => item.haveIgnore),
z: 10, z: 10,
zlevel: 0, zlevel: 0,
}, },
...@@ -124,10 +190,10 @@ const initChart = () => { ...@@ -124,10 +190,10 @@ const initChart = () => {
symbolMargin: 2, symbolMargin: 2,
symbol: "rect", symbol: "rect",
symbolClip: true, symbolClip: true,
symbolSize: [18, 2], symbolSize: [14, 2],
symbolPosition: "start", symbolPosition: "start",
symbolOffset: [-9, 1], symbolOffset: [-14, 1],
data: yData.value, data: xData.value.map((item) => item.haveHandle),
width: 2, width: 2,
z: 0, z: 0,
zlevel: 1, zlevel: 1,
...@@ -142,20 +208,38 @@ const initChart = () => { ...@@ -142,20 +208,38 @@ const initChart = () => {
symbolMargin: 2, symbolMargin: 2,
symbol: "rect", symbol: "rect",
symbolClip: true, symbolClip: true,
symbolSize: [18, 2], symbolSize: [13, 2],
symbolPosition: "start", symbolPosition: "start",
symbolOffset: [9, 1], symbolOffset: [0, 1],
data: wData.value, data: xData.value.map((item) => item.notHandle),
width: 2,
z: 0,
zlevel: 1,
},
{
// 已忽略分隔
type: "pictorialBar",
itemStyle: {
color: "#0F375F",
},
symbolRepeat: "fixed",
symbolMargin: 2,
symbol: "rect",
symbolClip: true,
symbolSize: [14, 2],
symbolPosition: "start",
symbolOffset: [14, 1],
data: xData.value.map((item) => item.haveIgnore),
width: 2, width: 2,
z: 0, z: 0,
zlevel: 1, zlevel: 1,
}, },
], ],
color: ["#4C9FFF", "#FF6E76"], color: ["#4C9FFF", "#FF6E76", "#FF0000"],
barWidth: "20%", barWidth: "20%",
grid: { grid: {
left: "4%", left: "7%",
right: "5%", right: "3%",
bottom: "5%", bottom: "5%",
top: "20%", top: "20%",
containLabel: true, containLabel: true,
...@@ -176,7 +260,69 @@ onMounted(() => { ...@@ -176,7 +260,69 @@ onMounted(() => {
chartContainer.value.style.height = "100%"; chartContainer.value.style.height = "100%";
} }
// 构建请求参数 beginTime(年月日时分秒)7天前 endTime(年月日时分秒)今天
const data = {
beginTime:
new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000)
.toISOString()
.substring(0, 10) + " 00:00:00",
endTime: new Date().toISOString().substring(0, 10) + " 23:59:59",
};
// 获取近7天告警统计
countByDay(data).then((res) => {
const haveHandle = res.data.haveHandle; //已处理
const notHandle = res.data.notHandle; //未处理
const haveIgnore = res.data.haveIgnore; //已忽略
// 将近7天的日期添加到xData.time中,格式为yyyy-MM-dd 00:00:00
xData.value.forEach((item, index) => {
item.time = `${new Date(
new Date().getTime() - (6 - index) * 24 * 60 * 60 * 1000
).getFullYear()}-${
new Date(
new Date().getTime() - (6 - index) * 24 * 60 * 60 * 1000
).getMonth() + 1
}-${new Date(
new Date().getTime() - (6 - index) * 24 * 60 * 60 * 1000
).getDate()} 00:00:00`;
});
// 将近7天的时间戳添加到xData.timestamp中
xData.value.forEach((item, index) => {
item.timestamp = new Date(item.time).getTime();
});
if (haveHandle) {
//将haveHandle的 key 和 xData.timestamp 进行匹配,将对应的值添加到xData.haveHandle中
for (let key in haveHandle) {
for (let item of xData.value) {
if (item.timestamp === Number(key)) {
item.haveHandle = haveHandle[key];
}
}
}
}
if (notHandle) {
//将notHandle的 key 和 xData.timestamp 进行匹配,将对应的值添加到xData.notHandle中
for (let key in notHandle) {
for (let item of xData.value) {
if (item.timestamp === Number(key)) {
item.notHandle = notHandle[key];
}
}
}
}
if (haveIgnore) {
//将haveIgnore的 key 和 xData.timestamp 进行匹配,将对应的值添加到xData.haveIgnore中
for (let key in haveIgnore) {
for (let item of xData.value) {
if (item.timestamp === Number(key)) {
item.haveIgnore = haveIgnore[key];
}
}
}
}
initChart(); initChart();
});
// 使用 ResizeObserver 监听容器尺寸变化 // 使用 ResizeObserver 监听容器尺寸变化
if (chartRef.value) { if (chartRef.value) {
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
class="flex flex-1 w-full h-full" class="flex flex-1 w-full h-full"
> >
<div class="flex flex-col justify-center"> <div class="flex flex-col justify-center">
<img :src="item.imageUrl" width="90px" /> <!-- <img :src="item.imageUrl" width="90px" /> -->
<div class="bg-container flex flex-col items-center"> <div class="bg-container flex flex-col items-center">
<div class="text-[#66FFFF] text-[14px]"> <div class="text-[#66FFFF] text-[14px]">
{{ item.name }} {{ item.name }}
...@@ -29,28 +29,68 @@ import sysBg1 from "@/assets/imgs/sys-bg1.png"; ...@@ -29,28 +29,68 @@ import sysBg1 from "@/assets/imgs/sys-bg1.png";
import sysBg2 from "@/assets/imgs/sys-bg2.png"; import sysBg2 from "@/assets/imgs/sys-bg2.png";
import sysBg3 from "@/assets/imgs/sys-bg3.png"; import sysBg3 from "@/assets/imgs/sys-bg3.png";
import sysBg4 from "@/assets/imgs/sys-bg4.png"; import sysBg4 from "@/assets/imgs/sys-bg4.png";
import { countBySysType } from "@/api/iot/home";
const props = defineProps({
deptId: {
type: Number,
default: 0,
},
});
const projectList = ref([ const projectList = ref([
{ {
type: 1,
name: "智慧隧道", name: "智慧隧道",
imageUrl: sysBg1, imageUrl: sysBg1,
num: "3", num: 0,
}, },
{ {
type: 2,
name: "幸福小镇", name: "幸福小镇",
imageUrl: sysBg2, imageUrl: sysBg2,
num: "2", num: 0,
},
{
type: 3,
name: "智慧园区",
imageUrl: sysBg3,
num: 0,
}, },
{ {
type: 4,
name: "拌合站",
imageUrl: sysBg3,
num: 0,
},
{
type: 5,
name: "视频监控", name: "视频监控",
imageUrl: sysBg3, imageUrl: sysBg3,
num: "5", num: 0,
}, },
{ {
name: "智慧园区", type: 6,
name: "钢筋场",
imageUrl: sysBg4, imageUrl: sysBg4,
num: "7", num: 0,
}, },
]); ]);
function getCountBySysType() {
countBySysType(props.deptId).then((res: any) => {
if (res.code === 200) {
res.data.map((item: any) => {
projectList.value.map((i: any) => {
if (i.type === item.type) {
i.num = item.count || 0;
}
});
});
}
});
}
getCountBySysType();
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.bg-container { .bg-container {
......
...@@ -7,11 +7,22 @@ ...@@ -7,11 +7,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from "vue"; import { ref, onMounted, onBeforeUnmount } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { deviceCountByType } from "@/api/iot/home";
const chartContainer = ref<HTMLElement | null>(null); const chartContainer = ref<HTMLElement | null>(null);
const chartInstance = ref<echarts.ECharts | null>(null); const chartInstance = ref<echarts.ECharts | null>(null);
let resizeObserver: ResizeObserver | null = null; let resizeObserver: ResizeObserver | null = null;
const props = defineProps({
deptId: {
type: Number,
default: 0,
},
});
const datas = ref([]);
const totalCount = ref(0);
const initChart = () => { const initChart = () => {
if (!chartContainer.value) return; if (!chartContainer.value) return;
// 销毁旧实例 // 销毁旧实例
...@@ -19,16 +30,6 @@ const initChart = () => { ...@@ -19,16 +30,6 @@ const initChart = () => {
// 初始化图表 // 初始化图表
chartInstance.value = echarts.init(chartContainer.value); chartInstance.value = echarts.init(chartContainer.value);
const datas = [
{ value: 1048, name: "视频监控" },
{ value: 735, name: "人车识别" },
{ value: 580, name: "水电监测" },
{ value: 484, name: "消防监测" },
{ value: 300, name: "人员定位监测" },
{ value: 300, name: "环境气体监测" },
{ value: 300, name: "车辆定位监测" },
];
const chartPieColors = [ const chartPieColors = [
[ [
{ offset: 0, color: "#F5C815" }, { offset: 0, color: "#F5C815" },
...@@ -64,7 +65,7 @@ const initChart = () => { ...@@ -64,7 +65,7 @@ const initChart = () => {
], ],
]; ];
const seriesData = datas.map((item, index) => ({ const seriesData = datas.value.map((item: any, index: number) => ({
value: item.value, value: item.value,
name: item.name, name: item.name,
itemStyle: { itemStyle: {
...@@ -115,18 +116,19 @@ const initChart = () => { ...@@ -115,18 +116,19 @@ const initChart = () => {
right: 150, right: 150,
radius: ["50%", "70%"], radius: ["50%", "70%"],
center: ["53%", "48%"], center: ["53%", "48%"],
avoidLabelOverlap: false, avoidLabelOverlap: true,
label: { label: {
show: false, show: false,
position: "outside", position: "outside",
formatter: "{a|{b}:{d}%}\n{hr|}", formatter: "{a|{b}:{d}%}\n{hr|}",
rich: { rich: {
hr: { hr: {
backgroundColor: "t", backgroundColor: "t",
borderRadius: 3, borderRadius: 3,
width: 3, width: 1,
height: 3, height: 1,
padding: [3, 3, 0, -12], padding: [2, 1, 0, -0],
}, },
a: { a: {
padding: [-30, 15, -20, 15], padding: [-30, 15, -20, 15],
...@@ -161,9 +163,11 @@ const initChart = () => { ...@@ -161,9 +163,11 @@ const initChart = () => {
label: { label: {
show: true, show: true,
position: "center", position: "center",
formatter: "37800", //item.value的设备总数
formatter: "" + totalCount.value,
// formatter: "",
color: "#FEBC22", color: "#FEBC22",
fontSize: 23, fontSize: 22,
fontWeight: "bold", fontWeight: "bold",
fontFamily: "Arial", fontFamily: "Arial",
}, },
...@@ -178,7 +182,6 @@ const initChart = () => { ...@@ -178,7 +182,6 @@ const initChart = () => {
}, },
], ],
}; };
chartInstance.value.setOption(option); chartInstance.value.setOption(option);
}; };
...@@ -187,7 +190,21 @@ const handleResize = () => { ...@@ -187,7 +190,21 @@ const handleResize = () => {
}; };
onMounted(() => { onMounted(() => {
deviceCountByType(props.deptId, "1").then((res: any) => {
if (res.code === 200) {
//将res.dcbt中的数据赋值给datas.value
datas.value = res.dcbt.map((item: any) => ({
value: item.count,
name: item.deviceType.name,
}));
// 计算总设备数量
totalCount.value = res.dcbt.reduce(
(acc: number, item: any) => acc + item.count,
0
);
initChart(); initChart();
}
});
// 使用 ResizeObserver 监听容器大小变化 // 使用 ResizeObserver 监听容器大小变化
if (chartContainer.value) { if (chartContainer.value) {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<!-- 数量 --> <!-- 数量 -->
<div class="gap-2 flex"> <div class="gap-2 flex">
<div <div
v-for="(digit, index) in digitsNumber" v-for="(digit, index) in sumCount"
:key="index" :key="index"
class="card-container flex-1" class="card-container flex-1"
> >
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
在线数量 在线数量
<text <text
class="text-[#66FFFF] xl:text-[14px] 2xl:text-[18px] pl-1 font-[YouSheBiaoTiHei]" class="text-[#66FFFF] xl:text-[14px] 2xl:text-[18px] pl-1 font-[YouSheBiaoTiHei]"
>{{ onlineNumber }}</text >{{ onLineCount }}</text
> >
</div> </div>
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
> >
<text <text
class="text-[#F01111] xl:text-[14px] 2xl:text-[18px] pr-1 font-[YouSheBiaoTiHei]" class="text-[#F01111] xl:text-[14px] 2xl:text-[18px] pr-1 font-[YouSheBiaoTiHei]"
>{{ offlineNumber }}</text >{{ noLineCount }}</text
> >
离线数量 离线数量
</div> </div>
...@@ -61,27 +61,51 @@ ...@@ -61,27 +61,51 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { computed } from "vue"; import { computed } from "vue";
import { allDevice } from "@/api/iot/home";
var originalNumber = ref("1610"); const props = defineProps({
//将数字转换为字符串并补零到9位,并将其拆分为单个数字数组 deptId: {
var digitsNumber = originalNumber.value.padStart(9, "0").split(""); type: Number,
default: 0,
},
});
// 设备接入数量(在线、离线)
var sumCount = ref([]);
//在线数量 //在线数量
var onlineNumber = ref(1500); var onLineCount = ref(0);
//离线数量 //离线数量
var offlineNumber = ref(110); var noLineCount = ref(0);
//计算在线和离线百分比 //计算在线和离线百分比
const totalDevices = computed(() => onlineNumber.value + offlineNumber.value); const totalDevices = computed(() => onLineCount.value + noLineCount.value);
const onlinePercent = computed(() => { const onlinePercent = computed(() => {
return totalDevices.value > 0 return totalDevices.value > 0
? Math.round((onlineNumber.value / totalDevices.value) * 100) ? Math.round((onLineCount.value / totalDevices.value) * 100)
: 0; : 0;
}); });
const offlinePercent = computed(() => { const offlinePercent = computed(() => {
return totalDevices.value > 0 return totalDevices.value > 0
? Math.round((offlineNumber.value / totalDevices.value) * 100) ? Math.round((noLineCount.value / totalDevices.value) * 100)
: 0; : 0;
}); });
// 获取设备接入数量(在线、离线)
function getAllDevice() {
allDevice(props.deptId).then((res: any) => {
if (res.code === 200) {
// 将数字转换为字符串并补零到9位,并将其拆分为单个数字数组
sumCount.value = (res.numMap?.sumCount?.toString() || "0")
.padStart(9, "0")
.split("");
// 在线数量
onLineCount.value = res.numMap?.onLineCount || 0;
// 离线数量
noLineCount.value = res.numMap?.noLineCount || 0;
}
});
}
getAllDevice();
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<div <div
class="text-center font-bold text-[34px] text-[#66FFFF] font-[YouSheBiaoTiHei]" class="text-center font-bold text-[34px] text-[#66FFFF] font-[YouSheBiaoTiHei]"
> >
10 {{ alarmTotal }}
</div> </div>
<div class="text-center text-[12px] text-[#66FFFF] mt-0">告警总数</div> <div class="text-center text-[12px] text-[#66FFFF] mt-0">告警总数</div>
</div> </div>
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<div <div
class="font-medium text-[28px] text-[#66FFFF] font-[YouSheBiaoTiHei]" class="font-medium text-[28px] text-[#66FFFF] font-[YouSheBiaoTiHei]"
> >
0 {{ unprocessedCount }}
</div> </div>
<img <img
src="@/assets/imgs/equ-right-icon.png" src="@/assets/imgs/equ-right-icon.png"
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
<div <div
class="font-medium text-[28px] text-[#66FFFF] font-[YouSheBiaoTiHei]" class="font-medium text-[28px] text-[#66FFFF] font-[YouSheBiaoTiHei]"
> >
8 {{ processedCount }}
</div> </div>
<img <img
src="@/assets/imgs/equ-right-icon.png" src="@/assets/imgs/equ-right-icon.png"
...@@ -40,12 +40,57 @@ ...@@ -40,12 +40,57 @@
/> />
<div class="text-[12px] text-white mt-1">已处理</div> <div class="text-[12px] text-white mt-1">已处理</div>
</div> </div>
<!-- 已处理统计 -->
<div class="flex flex-col items-center">
<div
class="font-medium text-[28px] text-[#66FFFF] font-[YouSheBiaoTiHei]"
>
{{ processedCount }}
</div>
<img
src="@/assets/imgs/equ-right-icon.png"
class="w-[45px] h-[50px] my-1"
alt="已处理图标"
/>
<div class="text-[12px] text-white mt-1">已忽略</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// 可以保留空的script setup,或者移除如果不需要 import { alarmNumCount } from "@/api/iot/home";
import { onMounted, ref } from "vue";
// 告警总数
let alarmTotal = ref(0);
// 未处理统计
let unprocessedCount = ref(0);
// 已处理统计
let processedCount = ref(0);
// 已忽略统计
let ignoredCount = ref(0);
const props = defineProps({
deptId: {
type: Number,
default: 0,
},
});
// 获取设备告警统计
function getAlarmNumCount() {
alarmNumCount(props.deptId).then((res: any) => {
if (res.code === 200) {
alarmTotal.value = res.data.count;
unprocessedCount.value = res.data.notHandle;
processedCount.value = res.data.haveHandle;
ignoredCount.value = res.data.haveIgnore;
}
});
}
getAlarmNumCount();
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
......
<template>
<div id="map-container" ref="mapContainer" class="w-full h-full"></div>
</template>
<script>
import { ref, onMounted, onBeforeUnmount } from "vue";
onMounted(() => {
initMap();
});
function initMap() {
//创建地图对象
this.map = new AMap.Map("map", {
center: [113.280637, 23.125178],
resizeEnable: true,
zoom: 7,
});
//获取边界坐标点
AMap.plugin("AMap.DistrictSearch", () => {
var districtSearch = new AMap.DistrictSearch({
// 关键字对应的行政区级别,共有5种级别
level: "province",
// 是否显示下级行政区级数,1表示返回下一级行政区
subdistrict: 0,
// 返回行政区边界坐标点
extensions: "all",
});
// 搜索所有省/直辖市信息
districtSearch.search("广东", (status, result) => {
// 查询成功时,result即为对应的行政区信息
this.handlePolygon(result);
});
});
}
// function handlePolygon(result) {
// let bounds = result.districtList[0].boundaries
// if (bounds) {
// for (let i = 0, l = bounds.length; i < l; i++) {
// //生成行政区划polygon
// let polygon = new AMap.Polygon({
// map: this.map, // 指定地图对象
// strokeWeight: 1, // 轮廓线宽度
// path: bounds[i], //轮廓线的节点坐标数组
// fillOpacity: 0.15, //透明度
// fillColor: '#256edc', //填充颜色
// strokeColor: '#256edc', //线条颜色
// })
// polygon.on('click', (e) => {
// // 点击绘制的区域时执行其他交互
// // ......
// })
// }
// // 地图自适应
// this.map.setFitView()
// }
</script>
<style scoped></style>
...@@ -7,13 +7,21 @@ ...@@ -7,13 +7,21 @@
import { ref, onMounted, onBeforeUnmount } from "vue"; import { ref, onMounted, onBeforeUnmount } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { debounce } from "lodash-es"; // 或使用自定义防抖函数 import { debounce } from "lodash-es"; // 或使用自定义防抖函数
import { deviceCountByChild } from "@/api/iot/home";
import { de } from "element-plus/es/locales.mjs";
const chartRef = ref<HTMLDivElement>(); const chartRef = ref<HTMLDivElement>();
const chart = ref<echarts.ECharts | null>(null); const chart = ref<echarts.ECharts | null>(null);
let resizeObserver: ResizeObserver | null = null; let resizeObserver: ResizeObserver | null = null;
const yData = ref<any[]>([]);
const xData = ref<any[]>([]);
const yData = ref([120, 200, 150, 190, 170, 110, 130, 130, 130, 130, 130]); const props = defineProps({
deptId: {
type: Number,
default: 0,
},
});
// 防抖的重绘函数 // 防抖的重绘函数
const handleResize = debounce(() => { const handleResize = debounce(() => {
chart.value?.resize(); chart.value?.resize();
...@@ -22,12 +30,9 @@ const handleResize = debounce(() => { ...@@ -22,12 +30,9 @@ const handleResize = debounce(() => {
// 初始化图表 // 初始化图表
const initChart = () => { const initChart = () => {
if (!chartRef.value) return; if (!chartRef.value) return;
// 如果已有图表实例,先销毁 // 如果已有图表实例,先销毁
chart.value?.dispose(); chart.value?.dispose();
chart.value = echarts.init(chartRef.value); chart.value = echarts.init(chartRef.value);
const option = { const option = {
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
...@@ -39,19 +44,7 @@ const initChart = () => { ...@@ -39,19 +44,7 @@ const initChart = () => {
show: false, show: false,
}, },
xAxis: { xAxis: {
data: [ data: xData.value,
"路桥一公司",
"路桥二公司",
"路桥三公司",
"路桥四公司",
"路桥五公司",
"路桥七公司",
"路桥八公司",
"路桥九公司",
"浙江分公司",
"市政公司",
"特种分公司",
],
axisLabel: { axisLabel: {
color: "#66FFFF", color: "#66FFFF",
fontSize: 11, fontSize: 11,
...@@ -108,8 +101,24 @@ const initChart = () => { ...@@ -108,8 +101,24 @@ const initChart = () => {
}; };
onMounted(() => { onMounted(() => {
deviceCountByChild(props.deptId).then((res: any) => {
if (res.code === 200) {
const childDeptList = res.result?.childDeptList || [];
const numMap = res.result?.numMap || [];
//numMap中的 key 和childDeptList.deptId 进行匹配
Object.entries(numMap).forEach(([key, value]) => {
console.log(`Key: ${key}, Value: ${value}`);
yData.value.push(value);
// 匹配到后,将对应的部门名称添加到 xData 中
for (let i = 0; i < childDeptList.length; i++) {
if (childDeptList[i].deptId === Number(key)) {
xData.value.push(childDeptList[i].deptName);
}
}
});
initChart(); initChart();
}
});
// 使用 ResizeObserver 监听容器尺寸变化 // 使用 ResizeObserver 监听容器尺寸变化
if (chartRef.value) { if (chartRef.value) {
resizeObserver = new ResizeObserver(() => { resizeObserver = new ResizeObserver(() => {
...@@ -117,7 +126,6 @@ onMounted(() => { ...@@ -117,7 +126,6 @@ onMounted(() => {
}); });
resizeObserver.observe(chartRef.value); resizeObserver.observe(chartRef.value);
} }
// 仍然监听窗口大小变化作为后备 // 仍然监听窗口大小变化作为后备
window.addEventListener("resize", handleResize); window.addEventListener("resize", handleResize);
}); });
...@@ -128,7 +136,6 @@ onBeforeUnmount(() => { ...@@ -128,7 +136,6 @@ onBeforeUnmount(() => {
resizeObserver.disconnect(); resizeObserver.disconnect();
resizeObserver = null; resizeObserver = null;
} }
window.removeEventListener("resize", handleResize); window.removeEventListener("resize", handleResize);
chart.value?.dispose(); chart.value?.dispose();
chart.value = null; chart.value = null;
......
<template> <template>
<div class="login flex-col pb-10"> <div class="login flex-col pb-10">
<h3 class="text-5xl font-bold text-center mb-15"> <h3 class="text-6xl font-bold text-center mb-15 text-[#fff]">
{{ AppSetting.projectName }} {{ AppSetting.projectName }}
</h3> </h3>
...@@ -81,7 +81,10 @@ ...@@ -81,7 +81,10 @@
</el-form> </el-form>
<!-- 底部 --> <!-- 底部 -->
<div class="el-login-footer"> <div class="el-login-footer">
<span>Copyright © 2018-2024 ruoyi.vip All Rights Reserved.</span> <span
>Copyright © 2025 {{ AppSetting.projectName }} All Rights
Reserved.</span
>
</div> </div>
</div> </div>
</template> </template>
...@@ -267,23 +270,22 @@ getCookie(); ...@@ -267,23 +270,22 @@ getCookie();
// display: flex; // display: flex;
// // --font-size: 14px; // // --font-size: 14px;
// margin-bottom: 13px; // margin-bottom: 13px;
// } // }
:deep(.el-input .el-input__inner) { // :deep(.el-input .el-input__inner) {
color: #fff; // color: #fff;
// background: yellow !important; // background-color: #163054;
// border: 0px solid rgba(255, 255, 255, 0.3); // border: 0px solid rgba(255, 255, 255, 0.3);
} // }
:deep(.el-input--large .el-input__wrapper) { // :deep(.el-input--large .el-input__wrapper) {
// padding: 1px 15px; // // padding: 1px 15px;
background-color: #163054; // background-color: #163054;
// box-shadow: 0 0 0 1px #2261ba inset !important; /* 可选:修改边框颜色 */ // // box-shadow: 0 0 0 1px #2261ba inset !important; /* 可选:修改边框颜色 */
box-shadow: 0px 2px 4px rgba(79, 151, 214, 0.33); // box-shadow: 0px 2px 4px rgba(79, 151, 214, 0.33);
// border: 0px solid rgba(255, 255, 255, 0.3); // // border: 0px solid rgba(255, 255, 255, 0.3);
} // }
:deep(.el-input__inner) { // :deep(.el-input__inner) {
// background-color: rgba(22, 48, 84, 0.6); // background-color: rgba(22, 48, 84, 0.6);
} // }
</style> </style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment