feat: 数据总览图表开发

main
JINGYJ 3 months ago
parent fda2cbcb31
commit 1a8c712487

@ -13,6 +13,7 @@
"echarts": "^5.6.0",
"postcss-scss": "^4.0.9",
"sass": "^1.85.1",
"swiper": "^11.2.5",
"unplugin-auto-import": "^19.1.1",
"vue": "^3.5.13"
},

@ -17,6 +17,9 @@ importers:
sass:
specifier: ^1.85.1
version: 1.85.1
swiper:
specifier: ^11.2.5
version: 11.2.5
unplugin-auto-import:
specifier: ^19.1.1
version: 19.1.1(@vueuse/core@9.13.0(vue@3.5.13(typescript@5.7.3)))
@ -1583,6 +1586,10 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
swiper@11.2.5:
resolution: {integrity: sha512-nG0kbIyBfeE2BPFt9nPUX03qUBF75o6+enzjIT/DfCmbh8ORlwhc4eZz1+4H/yseAgb3H+OoEYzmb64i0tYNnQ==}
engines: {node: '>= 4.7.0'}
tailwindcss@3.4.17:
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
engines: {node: '>=14.0.0'}
@ -3152,6 +3159,8 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
swiper@11.2.5: {}
tailwindcss@3.4.17:
dependencies:
'@alloc/quick-lru': 5.2.0

@ -0,0 +1,10 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame">
<path id="Vector" d="M12 22C14.7614 22 17.2614 20.8807 19.0711 19.0711C20.8807 17.2614 22 14.7614 22 12C22 9.2386 20.8807 6.7386 19.0711 4.92893C17.2614 3.11929 14.7614 2 12 2C9.2386 2 6.7386 3.11929 4.92893 4.92893C3.11929 6.7386 2 9.2386 2 12C2 14.7614 3.11929 17.2614 4.92893 19.0711C6.7386 20.8807 9.2386 22 12 22Z" fill="white"/>
<path id="Vector_2" d="M13 17.25C13 17.8023 12.5523 18.25 12 18.25C11.4477 18.25 11 17.8023 11 17.25C11 16.6977 11.4477 16.25 12 16.25C12.5523 16.25 13 16.6977 13 17.25Z" fill="#E80D0D" stroke="#E80D0D" stroke-width="0.5"/>
<g id="Vector_3">
<path d="M12 6V14V6Z" fill="#E80D0D"/>
<path d="M12 6V14" stroke="#E80D0D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 846 B

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame">
<path id="Vector" d="M12 22C14.7614 22 17.2614 20.8807 19.0711 19.0711C20.8807 17.2614 22 14.7614 22 12C22 9.2386 20.8807 6.7386 19.0711 4.92893C17.2614 3.11929 14.7614 2 12 2C9.2386 2 6.7386 3.11929 4.92893 4.92893C3.11929 6.7386 2 9.2386 2 12C2 14.7614 3.11929 17.2614 4.92893 19.0711C6.7386 20.8807 9.2386 22 12 22Z" fill="white"/>
<path id="Vector_2" d="M8 12L11 15L17 9" stroke="#52C41A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 582 B

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Vector" d="M10 0C4.47713 0 0 4.47715 0 10C0 15.5229 4.47713 20 10 20C15.5229 20 20 15.5229 20 10C20 4.47715 15.5229 0 10 0ZM15.3891 14.2784L14.2784 15.3891L10 11.1106L5.72156 15.3891L4.61094 14.2784L8.88937 10L4.61094 5.72156L5.72156 4.61094L10 8.88937L14.2785 4.61094L15.3891 5.72156L11.1106 10L15.3891 14.2784Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 442 B

@ -1,69 +1,143 @@
<template>
<div class="appearance-monitor-warp">
<div class="appearance-monitor-left w-[56%] h-[100%]">
<div class="monitor-left-top">
<img src="https://picsum.photos/300/200?random=1" alt="监控画面">
</div>
<div class="monitor-left-bottom">
<img src="https://picsum.photos/300/200?random=2" alt="监控画面">
</div>
</div>
<div class="appearance-monitor-right w-[44%] h-[100%]">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_5">
</div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<!-- 表格区域 -->
<el-table
:data="tableData"
stripe
style="width: 100%; margin-top: 20px"
class="custom-table"
>
<el-table-column prop="carNo" label="车号" width="80"></el-table-column>
<el-table-column prop="carType" label="车型" width="60"></el-table-column>
<el-table-column prop="carriageNo" label="车厢号" width="80"></el-table-column>
<el-table-column prop="warnType" label="告警类型" width="80"></el-table-column>
<el-table-column prop="faultType" label="故障类型" width="120"></el-table-column>
<el-table-column prop="level" label="等级" width="60"></el-table-column>
<el-table-column prop="review" label="复核" width="60"></el-table-column>
<el-table-column prop="time" label="时间" width="120"></el-table-column>
</el-table>
<div class="appearance-monitor-warp">
<div class="appearance-monitor-left w-[56%] h-[100%]">
<div class="monitor-left-top">
<img src="https://picsum.photos/300/200?random=1" alt="监控画面" />
</div>
<div class="monitor-left-bottom">
<swiper
:modules="modules"
:slides-per-view="4"
:space-between="10"
navigation
:pagination="{ type: 'custom',
el: '.swiper-pagination',
renderCustom: (swiper, current, total) => '' }"
:scrollbar="{ draggable: false }"
:centered-slides="false"
:observer="true"
:observeParents="true"
@swiper="onSwiper"
@slideChange="onSlideChange"
>
<swiper-slide>
<img src="https://picsum.photos/300/200?random=2" alt="监控画面" />
</swiper-slide>
<swiper-slide>
<img src="https://picsum.photos/300/200?random=2" alt="监控画面" />
</swiper-slide>
<swiper-slide>
<img src="https://picsum.photos/300/200?random=2" alt="监控画面" />
</swiper-slide>
<swiper-slide>
<img src="https://picsum.photos/300/200?random=2" alt="监控画面" />
</swiper-slide>
<swiper-slide>
<img src="https://picsum.photos/300/200?random=2" alt="监控画面" />
</swiper-slide>
</swiper>
</div>
</div>
<div class="appearance-monitor-right w-[44%] h-[100%]">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_5"></div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<!-- 表格区域 -->
<el-table
:data="tableData"
stripe
style="width: 100%; margin-top: 20px"
class="custom-table"
>
<el-table-column prop="carNo" label="车号" width="80"></el-table-column>
<el-table-column
prop="carType"
label="车型"
width="60"
></el-table-column>
<el-table-column
prop="carriageNo"
label="车厢号"
width="80"
></el-table-column>
<el-table-column
prop="warnType"
label="告警类型"
width="80"
></el-table-column>
<el-table-column
prop="faultType"
label="故障类型"
width="120"
></el-table-column>
<el-table-column prop="level" label="等级" width="60"></el-table-column>
<el-table-column
prop="review"
label="复核"
width="60"
></el-table-column>
<el-table-column prop="time" label="时间" width="120"></el-table-column>
</el-table>
<!-- 分页区域 -->
<!-- 分页区域 -->
<div class="pagination-container">
<el-pagination
:page-sizes="[100, 200, 300, 400]"
layout="total, sizes, prev, pager, next, jumper"
:total="400"
/>
</div>
</div>
<div class="pagination-container">
<el-pagination
:page-sizes="[100, 200, 300, 400]"
layout="total, sizes, prev, pager, next, jumper"
:total="400"
/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import ContentHeader from '@/components/ContentHeader.vue';
import { ref } from 'vue';
import ContentHeader from "@/components/ContentHeader.vue";
import { ref } from "vue";
import { Swiper, SwiperSlide } from "swiper/vue";
import { Navigation, Pagination, Scrollbar } from "swiper/modules";
import "swiper/css";
import 'swiper/scss';
import 'swiper/scss/navigation';
import 'swiper/scss/pagination';
const modules = [Navigation, Pagination, Scrollbar];
//
const searchForm = ref({
trainNo: 'all',
carriageNo: 'all',
faultType: 'all'
trainNo: "all",
carriageNo: "all",
faultType: "all",
});
//
const tableData = ref([
{ carNo: 'CA1001', carType: 'C64', carriageNo: '101', warnType: '车辆损坏', faultType: '搭扣未搭', level: '1', review: '是', time: '2025-02-05 14:33' },
{ carNo: 'CA1002', carType: 'C64', carriageNo: '101', warnType: '车辆损坏', faultType: '搭扣未搭', level: '1', review: '是', time: '2025-02-12 15:33' },
{
carNo: "CA1001",
carType: "C64",
carriageNo: "101",
warnType: "车辆损坏",
faultType: "搭扣未搭",
level: "1",
review: "是",
time: "2025-02-05 14:33",
},
{
carNo: "CA1002",
carType: "C64",
carriageNo: "101",
warnType: "车辆损坏",
faultType: "搭扣未搭",
level: "1",
review: "是",
time: "2025-02-12 15:33",
},
// ...
]);
@ -71,60 +145,92 @@ const total = ref(66);
//
const handleSearch = () => {
console.log('搜索参数', searchForm.value);
console.log("搜索参数", searchForm.value);
};
//
const handleReset = () => {
searchForm.value = {
trainNo: 'all',
carriageNo: 'all',
faultType: 'all'
trainNo: "all",
carriageNo: "all",
faultType: "all",
};
};
//
const handlePageChange = (page) => {
console.log('当前页码', page);
console.log("当前页码", page);
};
const onSwiper = (swiper) => {
console.log(swiper);
};
const onSlideChange = () => {
console.log("slide change");
};
onMounted(() => {
onSwiper();
});
</script>
<style scoped lang="scss">
.appearance-monitor-warp {
height: 100%;
height: 100%;
display: flex;
justify-content: center;
// align-items: center;
// .appearance-monitor-right {
// display: flex;
// }
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: center;
// align-items: center;
// .appearance-monitor-right {
// display: flex;
// }
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
:deep(.el-pagination__total) {
color: #606266;
margin-right: 10px;
}
:deep(.el-pagination__sizes .el-select__inner) {
background: #002a5c;
color: white;
border: 1px solid #409eff;
}
justify-content: flex-end;
}
:deep(.el-pagination__total) {
color: #606266;
margin-right: 10px;
}
:deep(.el-pagination__item),
:deep(.el-pagination__button) {
background: #002a5c;
color: white;
border: 1px solid #409eff;
margin-right: 5px;
}
:deep(.el-pagination__sizes .el-select__inner) {
background: #002a5c;
color: white;
border: 1px solid #409eff;
}
:deep(.el-pagination__item.is-active) {
background: #409eff;
color: white;
:deep(.el-pagination__item),
:deep(.el-pagination__button) {
background: #002a5c;
color: white;
border: 1px solid #409eff;
margin-right: 5px;
}
:deep(.el-pagination__item.is-active) {
background: #409eff;
color: white;
}
.appearance-monitor-left {
.monitor-left-top {
min-height: 600px;
img {
width: 100%;
height: 600px;
}
}
.monitor-left-bottom {
width: 100%;
overflow: visible;
.swiper {
width: 100%;
height: 100%;
.swiper-slide {
width: 20%;
img {
width: 100%;
height: 100%;
}
}
}
}
}
}
</style>
</style>

@ -21,6 +21,7 @@
margin-bottom: 15px;
}
.chart-container {
width: 100%;
height: 300px;
}
}
@ -29,6 +30,7 @@
.total-device {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 15px;
.device-icon {

@ -1,195 +1,167 @@
<template>
<div class="data-overview-wrap">
<!-- 检测总量汇总 -->
<div class="grid-container">
<div class="grid-item">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_0">
</div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<div class="chart-container" id="totalChart"></div>
<div class="data-overview-wrap">
<!-- 检测总量汇总 -->
<div class="grid-container">
<div class="grid-item">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_0"></div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<div class="chart-container">
<BarChart
:xData="xData"
:legendArr="legendArr"
:datas="datas"
:colorArr="colorArr"
/>
</div>
</div>
<!-- 设备信息 -->
<div class="grid-item">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_1"></div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<div class="device-info">
<div class="total-device">
<div class="device-icon"></div>
<div class="device-count">
<div>设备总数</div>
<div class="count-number">37</div>
</div>
<!-- 设备信息 -->
<div class="grid-item">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_1">
</div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<div class="device-info">
<div class="total-device">
<div class="device-icon"></div>
<div class="device-count">
<div>设备总数</div>
<div class="count-number">37</div>
</div>
</div>
<div class="device-list">
<div class="device-card">
<div>车体检测设备: 12</div>
<div class="status-bar">
<el-progress type="line" :percentage="(8 / 12) * 100" status="success"></el-progress>
<el-progress type="line" :percentage="(2 / 12) * 100" status="warning"></el-progress>
<el-progress type="line" :percentage="(2 / 12) * 100" status="danger"></el-progress>
</div>
</div>
<div class="device-card">
<div>撑杆检测设备: 10</div>
<div class="status-bar">
<el-progress type="line" :percentage="(9 / 10) * 100" status="success"></el-progress>
<el-progress type="line" :percentage="(1 / 10) * 100" status="warning"></el-progress>
<el-progress type="line" :percentage="(0 / 10) * 100" status="danger"></el-progress>
</div>
</div>
<div class="device-card">
<div>钩机检测设备: 15</div>
<div class="status-bar">
<el-progress type="line" :percentage="(8 / 15) * 100" status="success"></el-progress>
<el-progress type="line" :percentage="(2 / 15) * 100" status="warning"></el-progress>
<el-progress type="line" :percentage="(2 / 15) * 100" status="danger"></el-progress>
</div>
</div>
</div>
</div>
</div>
<div class="device-list">
<div class="device-card">
<div class="mb-3">车体检测设备: 12</div>
<DeviceStatus :deviceStatus="deviceStatus" />
</div>
</div>
<!-- 中部检测模块 -->
<div class="grid-container">
<ul class="flex grid-item">
<li>
<div class="module-header">
<ContentHeader bgLayout="450">
<template #title>
<div class="w-[200px] bg_title bg_title_2">
</div>
</template>
<template #extra>
<div>
</div>
</template>
</ContentHeader>
</div>
<div class="chart-container" id="bodyChart"></div>
</li>
<li>
<div class="module-header">
<ContentHeader bgLayout="450">
<template #title>
<div class="w-[200px] bg_title bg_title_3">
</div>
</template>
<template #extra>
</template>
</ContentHeader>
</div>
<div class="chart-container" id="poleChart"></div>
</li>
</ul>
<div class="grid-item">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_4">
</div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<div class="monitor-images">
<img src="https://picsum.photos/300/200?random=1" alt="监控画面">
<img src="https://picsum.photos/300/200?random=2" alt="监控画面">
</div>
<div class="device-card">
<div class="mb-3">撑杆检测设备: 10</div>
<DeviceStatus :deviceStatus="deviceStatus" />
</div>
<div class="device-card">
<div class="mb-3">钩机检测设备: 15</div>
<DeviceStatus :deviceStatus="deviceStatus" />
</div>
</div>
</div>
</div>
</div>
<!-- 中部检测模块 -->
<div class="grid-container">
<ul class="flex grid-item">
<li>
<div class="module-header">
<ContentHeader bgLayout="450">
<template #title>
<div class="w-[200px] bg_title bg_title_2"></div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<div class="chart-container">
<PieChart
:data="[
{ value: 26, name: '搭扣未搭' },
{ value: 35, name: '下侧门缺失' },
{ value: 15, name: '小门搭扣丢失' },
{ value: 15, name: '门折页脱落' },
{ value: 10, name: '小门外翘' },
{ value: 5, name: '小窗裂纹' },
]"
:colors="[
'#4CAF50',
'#8BC34A',
'#CDDC39',
'#FFEB3B',
'#FFC107',
'#FF5722',
]"
/>
</div>
</li>
<li>
<div class="module-header">
<ContentHeader bgLayout="450">
<template #title>
<div class="w-[200px] bg_title bg_title_3"></div>
</template>
<template #extra> </template>
</ContentHeader>
</div>
<div class="chart-container">
<PieChartSmall
:data="[
{ value: 65, name: '撑杆断裂' },
{ value: 35, name: '撑杆弯曲' },
]"
:colors="['#2196F3', '#9C27B0']"
/>
</div>
</li>
</ul>
<div class="grid-item">
<div class="module-header">
<ContentHeader bgLayout="918">
<template #title>
<div class="w-[200px] bg_title bg_title_4"></div>
</template>
<template #extra>
<div></div>
</template>
</ContentHeader>
</div>
<div class="monitor-images">
<img src="https://picsum.photos/300/200?random=1" alt="监控画面" />
<img src="https://picsum.photos/300/200?random=2" alt="监控画面" />
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import * as echarts from 'echarts';
import ContentHeader from '@/components/ContentHeader.vue';
import * as echarts from "echarts";
import ContentHeader from "@/components/ContentHeader.vue";
import BarChart from "./components/BarChart.vue";
import PieChart from "./components/PieChart.vue";
import PieChartSmall from "./components/PieChartSmall.vue";
import DeviceStatus from "./components/DeviceStatus.vue";
const xData = ref(["1月", "2月", "3月", "4月", "5月"]);
const legendArr = ["车体检测", "撑杆检测"];
const datas = [
[1528, 1266.02, 2468.39, 2982.67, 3165.91],
[2844.44, 6505.07, 8016.12, 6350.87, 1474.61],
];
const colorArr = [
["#3B9FFE", "#5070F2"],
["#FFDA8D", "#FFAC06"],
];
const deviceStatus = ref({
onlineCount: 50,
errorCount: 10,
outlineCount: 10
});
onMounted(() => {
//
const totalChart = echarts.init(document.getElementById('totalChart') as HTMLDivElement);
totalChart.setOption({
xAxis: {
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {},
series: [
{
name: '车体检测',
type: 'bar',
data: [200, 200, 150, 150, 120, 220]
},
{
name: '撑杆检测',
type: 'bar',
data: [80, 150, 80, 100, 180, 80]
}
]
});
//
const bodyChart = echarts.init(document.getElementById('bodyChart') as HTMLDivElement);
bodyChart.setOption({
series: [
{
name: '车体检测',
type: 'pie',
radius: '70%',
data: [
{ value: 26, name: '搭扣未搭' },
{ value: 35, name: '下侧门缺失' },
{ value: 15, name: '小门搭扣丢失' },
{ value: 15, name: '门折页脱落' },
{ value: 10, name: '小门外翘' },
{ value: 5, name: '小窗裂纹' }
]
}
]
});
//
const poleChart = echarts.init(document.getElementById('poleChart') as HTMLDivElement);
poleChart.setOption({
series: [
{
name: '撑杆检测',
type: 'pie',
radius: ['50%', '70%'],
data: [
{ value: 65, name: '撑杆断裂' },
{ value: 35, name: '撑杆弯曲' }
]
}
]
});
});
</script>
<style scoped lang="scss">
@import url('./DataOverview.scss');
</style>
@import url("./DataOverview.scss");
</style>

@ -0,0 +1,320 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch, nextTick } from "vue";
import * as echarts from "echarts";
// props
const props = defineProps({
xData: {
type: Array as PropType<Array<string>>,
default: () => [],
},
legendArr: {
type: Array as PropType<Array<string>>,
default: () => [],
},
datas: {
type: Array as PropType<Array<Array<number>>>,
default: () => [],
},
colorArr: {
type: Array as PropType<Array<Array<string>>>,
default: () => [],
},
});
const chartContainer = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
// 3D
const CubeLeft = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const { xAxisPoint } = shape;
const [c0, c1, c2, c3] = [
[shape.x, shape.y],
[shape.x - 12, shape.y - 6],
[xAxisPoint[0] - 12, xAxisPoint[1] - 6],
[xAxisPoint[0], xAxisPoint[1]],
];
ctx
.moveTo(c0[0], c0[1])
.lineTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.closePath();
},
});
const CubeRight = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const { xAxisPoint } = shape;
const [c1, c2, c3, c4] = [
[shape.x, shape.y],
[xAxisPoint[0], xAxisPoint[1]],
[xAxisPoint[0] + 12, xAxisPoint[1] - 6],
[shape.x + 12, shape.y - 6],
];
ctx
.moveTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.lineTo(c4[0], c4[1])
.closePath();
},
});
const CubeTop = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const [c1, c2, c3, c4] = [
[shape.x, shape.y],
[shape.x + 12, shape.y - 6],
[shape.x, shape.y - 12],
[shape.x - 12, shape.y - 6],
];
ctx
.moveTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.lineTo(c4[0], c4[1])
.closePath();
},
});
//
echarts.graphic.registerShape("CubeLeft", CubeLeft);
echarts.graphic.registerShape("CubeRight", CubeRight);
echarts.graphic.registerShape("CubeTop", CubeTop);
//
const initChart = () => {
if (!chartContainer.value) return;
chartInstance = echarts.init(chartContainer.value);
setTimeout(() => {
chartInstance?.resize();
updateChart();
}, 500);
};
//
const updateChart = () => {
if (!chartInstance) return;
const series = props.datas
.map((item, index) => [
{
type: "custom",
name: props.legendArr[index],
renderItem: (params, api) => ({
type: "group",
x: (index - props.datas.length / 2) * 30 + 15,
children: [
{
type: "CubeLeft",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][0],
},
]),
},
},
{
type: "CubeRight",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][0],
},
]),
},
},
{
type: "CubeTop",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][1],
},
]),
},
},
],
}),
data: item,
},
{
type: "bar",
name: props.legendArr[index],
barWidth: 25,
// label: {
// normal: {
// show: true,
// position: "top",
// fontSize: 16,
// color: "#fff",
// offset: [0, -10],
// },
// },
itemStyle: { color: "transparent" },
data: item,
yAxisIndex: 0, // 使y
},
{
type: "bar",
name: props.legendArr[index],
barWidth: 25,
itemStyle: { color: "transparent" },
data: item,
yAxisIndex: 1, // 使y
},
])
.flat();
chartInstance.setOption({
tooltip: {
trigger: "axis",
borderWidth: 0,
backgroundColor: "rgba(8,36,68,.9)",
color: "#fff",
textStyle: { color: "#fff" },
formatter: (params) => {
let str = params[0].name + "</br>";
params.forEach((item, index) => {
if (item.seriesType === "custom") {
str += `
<div style='display:flex;justify-content:space-between;align-items:center'>
<div style='margin-right:20px;'>
<span style="display:inline-block;width:10px;height:10px;border-radius:5px;background-color:${
props.colorArr[index % props.colorArr.length][0]
}"></span>
&nbsp;${item.seriesName}
</div>
<span>&nbsp;${item.value ? item.value : "-"}</span>
</div>`;
}
});
return str;
},
},
grid: {
left: "5%",
right: "5%",
top: "5%",
bottom: "10%",
containLabel: true,
},
legend: {
left: "center",
top: "90%",
itemWidth: 12, //
itemHeight: 8,
textStyle: { color: "#fff", fontSize: 12 },
data: props.legendArr.map((name, index) => ({
name,
textStyle: { color: "#fff", fontSize: 12 },
itemStyle: { color: props.colorArr[index % props.colorArr.length][1] },
})),
},
xAxis: {
type: "category",
data: props.xData,
axisLine: { lineStyle: { color: "rgba(239, 247, 253, .1)" } },
axisLabel: { fontSize: 12, color: "#fff", margin: 12 },
},
yAxis: [
{
// name: "kWh",
// nameTextStyle: { color: "#fff", fontSize: 12 },
splitLine: {
lineStyle: { type: "dashed", color: "rgba(80,112,242,0.3)" },
},
axisLabel: { textStyle: { color: "#8C8C8C" }, fontSize: 12 },
// axisLine: { lineStyle: { color: "#8C8C8C" } },
// scale: true, //
},
{
// name: "kWh",
// nameTextStyle: { color: "#fff", fontSize: 12 },
splitLine: { lineStyle: { color: "transparent" } },
axisLabel: { textStyle: { color: "#8C8C8C" }, fontSize: 12 },
// axisLine: { lineStyle: { color: "#8C8C8C" } },
position: "right",
// scale: true, //
},
],
series,
});
};
const handleResize = () => {
chartInstance?.resize();
};
//
onMounted(async () => {
await nextTick();
initChart();
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
chartInstance?.resize();
}
});
//
watch(
() => props,
async () => {
await nextTick();
updateChart();
// delay(600).then(() => resize());
},
{
deep: true,
immediate: true,
}
);
</script>
<template>
<div ref="chartContainer" style="width: 100%; height: 100%" />
</template>

@ -0,0 +1,103 @@
<script setup lang="ts">
import { ref } from "vue";
// import deviceStatusOnline from "@/assets/svg/deviceStatus/online.svg?component";
// import deviceStatusError from "@/assets/svg/deviceStatus/error.svg?component";
// import deviceStatusOutline from "@/assets/svg/deviceStatus/outline.svg?component";
defineOptions({
name: "DeviceStatus"
});
const props = defineProps({
deviceStatus: {
type: Object,
default: () => {}
}
});
const deviceStatusData = ref({ ...props.deviceStatus });
const deviceStatusOptions = ref<Record<string, any>[]>([
{
label: "在线",
color: "#52C41A",
bgColor: "#52C41A",
valueKey: "onlineCount" // 线
},
{
label: "离线",
color: "#ccc",
bgColor: "#999999",
valueKey: "outlineCount" //
},
{
label: "故障",
color: "#E80D0D",
bgColor: "#E80D0D",
valueKey: "errorCount" //
}
]);
</script>
<template>
<ul class="w-full text-sm deviceStatus_box">
<li
class="flex items-center justify-between w-full"
:style="{
marginBottom: '16px'
}"
v-for="(v, k) in deviceStatusOptions"
:key="k"
>
<div
class="flex items-center justify-center"
:style="{
backgroundColor: v.bgColor,
width: '40px',
height: '40px',
borderRadius: '4px',
marginRight: '12px'
}"
>
<div v-if="v.valueKey === 'onlineCount'" class="deviceStatusOnline"></div>
<div v-if="v.valueKey === 'errorCount'" class="deviceStatusError"></div>
<div v-if="v.valueKey === 'outlineCount'" class="deviceStatusOutline"></div>
</div>
<div class="flex flex-col flex-1">
<div class="flex justify-between" style="margin-bottom: 4px">
<span>{{ v.label }}</span>
<span>{{ deviceStatusData[v.valueKey] }}</span>
</div>
<div class="w-full">
<el-progress
:show-text="false"
:stroke-width="8"
:percentage="deviceStatusData[v.valueKey]"
:color="v.color"
/>
</div>
</div>
</li>
</ul>
</template>
<style lang="scss" scoped>
.deviceStatus_box {
li {
div {
.deviceStatusOnline {
width: 20px;
height: 20px;
background: url('@/assets/svg/deviceStatus/online.svg') no-repeat center center;
}
.deviceStatusError {
width: 20px;
height: 20px;
background: url('@/assets/svg/deviceStatus/error.svg') no-repeat center center;
}
.deviceStatusOutline {
width: 20px;
height: 20px;
background: url('@/assets/svg/deviceStatus/outline.svg') no-repeat center center;
}
}
}
}
</style>

@ -0,0 +1,92 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from "vue";
import * as echarts from "echarts";
const props = defineProps({
data: {
type: Array as PropType<Array<{ value: number; name: string }>>,
required: true,
},
colors: { type: Array as PropType<Array<string>>, default: () => [] },
});
const chartContainer = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
//
const initChart = () => {
if (!chartContainer.value) return;
chartInstance = echarts.init(chartContainer.value);
setTimeout(() => {
chartInstance?.resize();
updateChart();
}, 500);
};
//
const updateChart = () => {
if (!chartInstance) return;
chartInstance.setOption({
legend: {
type: "scroll",
orient: "vertical",
left: "70%",
align: "left",
top: "middle",
itemWidth: 16, //
itemHeight: 8,
textStyle: { color: "#8C8C8C" },
},
series: [
{
type: "pie",
radius: ["30%", "80%"],
center: ["35%", "50%"],
label: { show: false },
itemStyle: {
color: (params) =>
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: props.colors[params.dataIndex] },
{ offset: 1, color: "#FFFFFF" },
]),
},
data: props.data,
},
],
});
};
//
onMounted(() => {
initChart();
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
//
watch(
() => props.data,
async () => {
await nextTick();
updateChart();
}
);
//
// onMounted(() => {
// window.addEventListener("resize", () => chartInstance?.resize());
// });
// onUnmounted(() => {
// window.removeEventListener("resize", () => chartInstance?.resize());
// });
</script>
<template>
<div ref="chartContainer" style="width: 100%; height: 100%" />
</template>

@ -0,0 +1,92 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from "vue";
import * as echarts from "echarts";
const props = defineProps({
data: {
type: Array as PropType<Array<{ value: number; name: string }>>,
required: true,
},
colors: { type: Array as PropType<Array<string>>, default: () => [] },
});
const chartContainer = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
//
const initChart = () => {
if (!chartContainer.value) return;
chartInstance = echarts.init(chartContainer.value);
setTimeout(() => {
chartInstance?.resize();
updateChart();
}, 500);
};
//
const updateChart = () => {
if (!chartInstance) return;
chartInstance.setOption({
legend: {
type: "scroll",
orient: "vertical",
left: "70%",
align: "left",
top: "middle",
itemWidth: 16, //
itemHeight: 8,
textStyle: { color: "#8C8C8C" },
},
series: [
{
type: "pie",
radius: ["40%", "80%"],
center: ["35%", "50%"],
label: { show: false },
itemStyle: {
color: (params) =>
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: props.colors[params.dataIndex] },
{ offset: 1, color: "#FFFFFF" },
]),
},
data: props.data,
},
],
});
};
//
onMounted(() => {
initChart();
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
});
//
watch(
() => props.data,
async () => {
await nextTick();
updateChart();
}
);
//
// onMounted(() => {
// window.addEventListener("resize", () => chartInstance?.resize());
// });
// onUnmounted(() => {
// window.removeEventListener("resize", () => chartInstance?.resize());
// });
</script>
<template>
<div ref="chartContainer" style="width: 100%; height: 100%" />
</template>
Loading…
Cancel
Save