feat: 数据总览图表开发
parent
fda2cbcb31
commit
1a8c712487
@ -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,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>
|
||||
${item.seriesName}
|
||||
</div>
|
||||
<span> ${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…
Reference in New Issue