社区左边和中间完成

This commit is contained in:
bunny 2025-05-13 20:04:27 +08:00
parent c5072da2b1
commit 180bc3c406
14 changed files with 509 additions and 96 deletions

View File

@ -5,41 +5,81 @@ const randomNumber = (range: number = 100) => {
};
export default defineFakeRoute([
// 设备总数
{
url: '/api/community/devices-amount',
method: 'GET',
response: () => ({
code: 200,
data: [
{ name: '设备类型一', outside: randomNumber(), connect: randomNumber() },
{ name: '设备类型二', outside: randomNumber(), connect: randomNumber() },
{ name: '设备类型三', outside: randomNumber(), connect: randomNumber() },
{ name: '设备类型四', outside: randomNumber(), connect: randomNumber() },
],
message: '操作成功',
}),
},
// 预警概览
{
url: '/api/community/alarms-overview',
method: 'GET',
response: () => ({
code: 200,
data: {
total: 500000,
list: Array(4)
.fill(0)
.map((_, index) => ({ name: `name-${index}`, value: randomNumber(9999) })),
},
message: '操作成功',
}),
},
// 中间顶部区域
{
url: '/api/community/community-statistics',
method: 'GET',
response: () => ({
code: 200,
data: [
{
name: '统计人口',
data: Array(4)
.fill(0)
.map((_, index) => ({
name: `统计人口-${index + 1}`,
total: randomNumber(9999),
subtitle: '常驻人口',
subtitle: `常驻人口${index + 1}`,
subPercent: `${randomNumber()}%`,
subTotal: randomNumber(99999),
},
{
name: '统计人口',
total: randomNumber(9999),
subtitle: '常驻人口',
subPercent: `${randomNumber()}%`,
subTotal: randomNumber(99999),
},
{
name: '统计人口',
total: randomNumber(9999),
subtitle: '常驻人口',
subPercent: `${randomNumber()}%`,
subTotal: randomNumber(99999),
},
{
name: '统计人口',
total: randomNumber(9999),
subtitle: '常驻人口',
subPercent: `${randomNumber()}%`,
subTotal: randomNumber(99999),
},
],
})),
message: '操作成功',
}),
},
// 中间区域设备运行状态
{
url: '/api/community/devices-status',
method: 'GET',
response: () => ({
code: 200,
data: {
devcies: [
{
title: '正常运行总数',
total: randomNumber(100),
},
{
title: '故障总数',
total: randomNumber(100),
},
{
title: '故障总数',
total: randomNumber(100),
},
{
title: '故障总数',
total: randomNumber(100),
},
],
security: randomNumber(),
},
message: '操作成功',
}),
},

View File

@ -1,6 +1,20 @@
import request from '@/api/server/request';
/* 设备总数 */
export const getCommunityDevicesAmount = () => {
return request.get('community/devices-amount');
};
export const getAlarmsOverview = () => {
return request.get('community/alarms-overview');
};
/* 社区统计 */
export const getCommunityStatistics = () => {
return request.get('community/community-statistics');
};
/* 设备运行状态 */
export const getCommityDeicesStatus = () => {
return request.get('/community/devices-status');
};

View File

@ -1,16 +1,16 @@
import request from '@/api/server/request';
/* 实时道路 */
export const fetchRoadStatus = () => {
export const gethRoadStatus = () => {
return request.get('/smart-park/road-status');
};
/* 车辆监控 */
export const fetchTollgateMonitoringData = () => {
export const getTollgateMonitoringData = () => {
return request.get('smart-park/monitor');
};
/* 车流量概览 */
export const fetchTrafficStatistics = () => {
export const getTrafficStatistics = () => {
return request.get('/smart-park/flow-rate');
};

View File

@ -21,11 +21,11 @@ export default defineComponent({
.flat();
return () => (
<>
<div className={'mb-[20px]'}>
{moneyStringList.map((item, index) => (
<span key={index}>{item}</span>
))}
</>
</div>
);
},
});

View File

@ -1,15 +1,42 @@
import { defineStore } from 'pinia';
import { getCommunityStatistics } from '@/api/community';
import {
getAlarmsOverview,
getCommityDeicesStatus,
getCommunityDevicesAmount,
getCommunityStatistics,
} from '@/api/community';
export const useCommunityStore = defineStore('communityStore', {
state: () => ({
communityStatisticsList: [],
// 设备总数
devicesList: [],
// 预警概览
alarmOverviewList: [],
// 统计列表
statisticsList: [],
// 设备状态
deviceStatus: { devcies: [], security: undefined },
}),
actions: {
/* 设备总数 */
async fetchCommunityDevicesAmount() {
const result = await getCommunityDevicesAmount();
this.devicesList = result;
},
/* 预警概览 */
async fetchAlarmsOverview() {
const result = await getAlarmsOverview();
this.alarmOverviewList = result;
},
/* 社区统计 */
async loadCommunityStatisticsList() {
this.communityStatisticsList = await getCommunityStatistics();
async fetchCommunityStatisticsList() {
this.statisticsList = await getCommunityStatistics();
},
/* 设备状态 */
async fetchCommityDeicesStatus() {
const result = await getCommityDeicesStatus();
this.deviceStatus = result;
},
},
});

View File

@ -1,10 +1,6 @@
import { defineStore } from 'pinia';
import {
fetchRoadStatus,
fetchTollgateMonitoringData,
fetchTrafficStatistics,
} from '@/api/smartPark';
import { gethRoadStatus, getTollgateMonitoringData, getTrafficStatistics } from '@/api/smartPark';
export const useSmartPark = defineStore('smartparkStore', {
state: () => ({
@ -19,19 +15,19 @@ export const useSmartPark = defineStore('smartparkStore', {
}),
actions: {
/* 道路情况 */
async loadRoadStatus() {
const result: any = await fetchRoadStatus();
async fetchRoadStatus() {
const result: any = await gethRoadStatus();
this.roadStatus = result.entrances;
this.roadStatusSuggest = result.suggest;
},
/* 卡口车辆监控 */
async loadTollgateMonitoringData() {
const result = await fetchTollgateMonitoringData();
async fetchTollgateMonitoringData() {
const result = await getTollgateMonitoringData();
this.tollgateMonitoringData = result;
},
/* 车流量概览 */
async loadFlowRate() {
const result = await fetchTrafficStatistics();
async fetchFlowRate() {
const result = await getTrafficStatistics();
this.trafficStatistics = result;
},
},

View File

@ -51,13 +51,25 @@ const renderEcharts: any = (myChart: Ref<echarts.ECharts>, element: Ref<HTMLDivE
debounceChart(myChart.value);
};
/** 更新图标数据 */
const updateChart = (myChart: Ref<EChartsType | undefined>, props: any) => {
const series = myChart.value.getOption().series;
series[0].data[0].value = props.percent;
series[0].data[0].itemStyle = props.percent >= 20 ? itemStyles[0] : itemStyles[1];
myChart.value?.setOption({ series });
};
/* 封装组件 */
export const ChartProgress = defineComponent({
name: 'ChartProgress',
props: { percent: { type: Number } },
setup(props) {
const chart = ref<HTMLDivElement>();
// 唯一的 Chart
const myChart = ref<EChartsType>();
// 元素
const chart = ref<HTMLDivElement>();
onMounted(() => {
renderEcharts(myChart, chart);
@ -75,12 +87,3 @@ export const ChartProgress = defineComponent({
return () => <div ref={chart} className="progress"></div>;
},
});
/** 更新图标数据 */
const updateChart = (myChart: Ref<EChartsType | undefined>, props: any) => {
const series = myChart.value.getOption().series;
series[0].data[0].value = props.percent;
series[0].data[0].itemStyle = props.percent >= 20 ? itemStyles[0] : itemStyles[1];
myChart.value?.setOption({ series });
};

View File

@ -58,7 +58,7 @@ option.value = {
type: 'pictorialBar',
barWidth: 30,
itemStyle: { color: '#0D3770' },
symbolRepeat: 'true',
symbolRepeat: true,
symbolMargin: 3,
symbol: 'rect',
symbolSize: [30, 4],

View File

@ -0,0 +1,82 @@
import type { EChartsOption } from 'echarts';
import { type Ref, ref } from 'vue';
import echarts from '@/plugins/echarts';
import { debounceChart } from '@/utils/chart';
let myChart = null;
const option = ref<EChartsOption>({
grid: { top: 0, right: 0, bottom: -100, left: 0, containLabel: true },
series: [
{
type: 'gauge',
startAngle: 180,
endAngle: 0,
center: ['50%', '75%'],
radius: '90%',
min: 0,
max: 1,
splitNumber: 8,
axisLine: {
lineStyle: { width: 12, color: [[1, '#05C1D3']] },
},
pointer: {
show: false,
icon: 'path://M12.8,0.7l12,40.1H0.7L12.8,0.7z',
length: '12%',
width: 20,
offsetCenter: [0, '-60%'],
itemStyle: {
color: 'auto',
},
},
axisTick: {
length: 44,
distance: -44,
lineStyle: { color: 'auto', width: 1 },
},
splitLine: {
length: 24,
distance: -24,
lineStyle: { color: 'auto', width: 8, miterLimit: 12 },
},
progress: {
show: true,
width: 14,
itemStyle: { color: '#707070' },
},
axisLabel: { show: false },
detail: {
fontSize: 18,
offsetCenter: [0, '-9%'],
valueAnimation: true,
formatter: function (value) {
return Math.round(value * 100) + '\n安全指数';
},
color: '#05C1D3',
},
data: [0.5],
},
],
});
/** 渲染图表 */
export const renderBodyChart = (element: Ref<HTMLDivElement>) => {
myChart = echarts.init(element.value, null, {
renderer: 'canvas',
devicePixelRatio: window.devicePixelRatio,
});
debounceChart(myChart);
myChart.setOption(option.value);
};
/** 更新图标数据 */
export const updateBodyChart = (props: any) => {
const series = myChart?.getOption()?.series;
series[0].data = [props.data / 100];
myChart.setOption({ series });
};

View File

@ -0,0 +1,67 @@
import type { EChartsOption } from 'echarts';
import { type Ref, ref } from 'vue';
import echarts from '@/plugins/echarts';
import { debounceChart } from '@/utils/chart';
let myChart = null;
/* 随机颜色 */
const colors = ['#3D7FFF', '#00FFFF', '#FF1190', '#FEDB65'];
const option = ref<EChartsOption>({
title: {
text: '34467',
subtext: '历史预警数',
left: 'center',
top: 'center',
textStyle: { color: '#fff', fontSize: 34 },
subtextStyle: { color: '#fff', fontSize: 16 },
},
grid: { containLabel: false },
polar: { radius: [60, '100%'] },
angleAxis: { show: false, startAngle: 90 },
radiusAxis: { show: false, type: 'category' },
tooltip: {},
series: {
type: 'bar',
data: [],
barWidth: 20,
barGap: 0,
coordinateSystem: 'polar',
itemStyle: {},
label: { show: true, position: 'middle' },
},
});
/** 渲染图表 */
export const renderBodyChart = (element: Ref<HTMLDivElement>) => {
myChart = echarts.init(element.value, null, {
renderer: 'canvas',
devicePixelRatio: window.devicePixelRatio,
});
debounceChart(myChart);
myChart.setOption(option.value);
};
/** 更新图标数据 */
export const updateBodyChart = (props: any) => {
const series = myChart?.getOption()?.series;
series[0].data = props.list.map((item, index) => ({
name: item.name,
value: item.value,
itemStyle: { color: colors[index], borderRadius: 10 },
}));
const total = props.list.reduce((old, item) => old + item.value, 0);
// const angleAxis = myChart?.getOption()?.angleAxis;
// angleAxis[0].max = undefined;
const title = myChart?.getOption()?.title;
title[0].text = total;
myChart.setOption({ series, title });
};

View File

@ -0,0 +1,109 @@
import 'echarts/lib/component/dataZoom';
import type { EChartsOption, EChartsType } from 'echarts';
import { defineComponent, onMounted, type Ref, ref, watch } from 'vue';
import echarts from '@/plugins/echarts';
import { debounceChart } from '@/utils/chart';
const option = ref<EChartsOption>({
tooltip: {
trigger: 'axis',
axisPointer: { type: 'line' },
},
grid: {
top: 0,
left: '0',
right: '0',
bottom: '0',
containLabel: false,
},
xAxis: { show: false, type: 'value' },
yAxis: { show: false, type: 'category' },
series: [
{
name: 'Direct',
type: 'bar',
stack: 'total',
label: { show: false },
emphasis: {
focus: 'series',
},
itemStyle: {
borderRadius: [10],
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#17EAFE' },
{ offset: 1, color: '#009CD7' },
]),
},
barWidth: 20,
data: [320],
},
{
type: 'bar',
stack: 'total',
label: { show: false },
itemStyle: {
borderRadius: [10],
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#00FFBC' },
{ offset: 1, color: '#09ADA7' },
]),
},
barWidth: 20,
emphasis: {
focus: 'series',
},
data: [120],
},
],
});
/** 绘制图表 */
export const renderLeftHeaderEcharts: any = (
myChart: Ref<echarts.ECharts>,
element: Ref<HTMLDivElement>
) => {
myChart.value = echarts.init(element.value, null, {
renderer: 'canvas',
devicePixelRatio: window.devicePixelRatio,
});
myChart.value!.setOption(option.value!);
debounceChart(myChart.value);
};
/** 更新图标数据 */
const updateChart = (myChart: Ref<EChartsType | undefined>, props: any) => {
const series = myChart.value.getOption().series;
series[0].data[0] = props.dataLeft;
series[1].data[0] = props.dataRight;
myChart.value?.setOption({ series });
};
const LeftHeaderChart = defineComponent({
name: 'LeftHeaderChart',
props: { dataLeft: { type: Number }, dataRight: { type: Number } },
setup(props) {
const myChartRef = ref<EChartsType>();
const chartRef = ref<HTMLDivElement>();
onMounted(() => {
renderLeftHeaderEcharts(myChartRef, chartRef);
watch(
() => [props.dataLeft, () => props.dataRight],
() => {
updateChart(myChartRef, props);
},
{ immediate: true }
);
});
return () => <div ref={chartRef} className="progress"></div>;
},
});
export default LeftHeaderChart;

View File

@ -1,17 +1,35 @@
<script lang="ts" setup>
import { useIntervalFn } from '@vueuse/core';
import { storeToRefs } from 'pinia';
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import { useCommunityStore } from '@/store/modules/community';
import { getImage } from '@/utils/image';
import { renderBodyChart, updateBodyChart } from '@/views/community/charts/conten-body-chart';
const BODY_CARD_LIST = ['大数据中心', '数字楼宇', '平台设备管理'];
const communityStore = useCommunityStore();
const { communityStatisticsList } = storeToRefs(communityStore);
const { statisticsList, deviceStatus } = storeToRefs(communityStore);
// Ref
const securityCharRef = ref();
/* 初始化数据 */
const initData = async () => {
await communityStore.fetchCommunityStatisticsList();
await communityStore.fetchCommityDeicesStatus();
updateBodyChart({ data: deviceStatus.value.security });
};
onMounted(() => {
communityStore.loadCommunityStatisticsList();
renderBodyChart(securityCharRef);
initData();
useIntervalFn(() => {
initData();
}, 1000);
});
</script>
@ -19,11 +37,7 @@ onMounted(() => {
<div class="community__content">
<header class="community__header">
<ul class="community__stats-list">
<li
v-for="(item, index) in communityStatisticsList"
:key="index"
class="community__stat-card"
>
<li v-for="(item, index) in statisticsList" :key="index" class="community__stat-card">
<div class="community__stat-content">
<h1>{{ item.name }}</h1>
<h2>{{ item.total }}</h2>
@ -48,37 +62,29 @@ onMounted(() => {
</ul>
<div class="community__metrics">
<div class="community__metric-card">
<h1>80</h1>
<div
v-for="(item, index) in deviceStatus?.devcies?.slice(0, 2)"
:key="index"
class="community__metric-card"
>
<h1>{{ item.total }}</h1>
<span>
设备
<br />
正常运行总数
{{ item.title }}
</span>
</div>
<div class="community__metric-card">
<h1>20</h1>
<div ref="securityCharRef" class="community__instrument-panel" />
<div
v-for="(item, index) in deviceStatus?.devcies?.slice(2, 4)"
:key="index"
class="community__metric-card"
>
<h1>{{ item.total }}</h1>
<span>
设备
<br />
故障总数
</span>
</div>
<div class="community__instrument-panel">xxxx</div>
<div class="community__metric-card">
<h1>20</h1>
<span>
设备
<br />
故障总数
</span>
</div>
<div class="community__metric-card">
<h1>98%</h1>
<span>
设备
<br />
故障总数
{{ item.title }}
</span>
</div>
</div>
@ -196,8 +202,10 @@ onMounted(() => {
/* 中间仪表盘内容 */
.community__instrument-panel {
padding: 20px;
width: 286px;
height: 169px;
color: #fff;
background: url('../images/bg-body-instrument-panel.png');
}
}

View File

@ -1,20 +1,67 @@
<script lang="ts" setup>
import { useIntervalFn } from '@vueuse/core';
import { storeToRefs } from 'pinia';
import { onMounted, ref } from 'vue';
import DigitalNumber from '@/components/DigitalNumber/DigitalNumber';
import { useCommunityStore } from '@/store/modules/community';
import { renderBodyChart, updateBodyChart } from '@/views/community/charts/left-body-chart';
import LeftHeaderChart from '@/views/community/charts/left-header-chart';
import CommonPanel from '@/views/community/components/CommonPanel.vue';
const communityStore = useCommunityStore();
const { devicesList, alarmOverviewList } = storeToRefs(communityStore);
//
const alarmOverviewChartRef = ref();
/* 计算接入设备值 */
const calculateDevicePercent = (val1: number, val2: number) => {
const sum = val1 + val2;
const percent = (val2 / sum) * 100;
return parseInt(percent.toFixed(0));
};
/* 初始化数据 */
const initData = async () => {
await communityStore.fetchCommunityDevicesAmount();
await communityStore.fetchAlarmsOverview();
updateBodyChart(alarmOverviewList.value);
};
onMounted(() => {
renderBodyChart(alarmOverviewChartRef);
initData();
useIntervalFn(() => initData(), 1000);
});
</script>
<template>
<div class="community__sidebar">
<div class="community__sidebar-item">
<CommonPanel title="智慧设备总数">
<CommonPanel title="设备总数">
<div class="community__sidebar-digital">
<DigitalNumber :money="1964" />
<div v-for="(item, index) in devicesList" :key="index" class="progress-list">
<LeftHeaderChart
:data-left="calculateDevicePercent(item.connect, item.outside)"
:data-right="calculateDevicePercent(item.outside, item.connect)"
/>
<div class="progress-list-content">
<span>{{ item.name }}</span>
<span>内部设备{{ item.outside }} 接入设备{{ item.connect }}</span>
</div>
</div>
</div>
</CommonPanel>
</div>
<div class="community__sidebar-item">
<CommonPanel title="预警概览" />
<CommonPanel title="预警概览">
<div ref="alarmOverviewChartRef" class="w-[100%] h-[100%]" />
</CommonPanel>
</div>
</div>
</template>
@ -23,10 +70,30 @@ import CommonPanel from '@/views/community/components/CommonPanel.vue';
.community__sidebar {
&-digital {
width: 100%;
height: 100%;
display: flex;
justify-content: flex-end;
flex-direction: column;
justify-content: space-between;
:deep(span) {
.progress-list {
display: flex;
flex-direction: column;
justify-items: center;
margin: 14px 0 0 0;
color: #fff;
:deep(.progress) {
width: 100%;
height: 20px;
}
&-content {
display: flex;
justify-content: space-between;
}
}
:nth-child(1) :deep(span) {
float: left;
margin: 0 11px 0 0;
width: 64px;

View File

@ -15,9 +15,9 @@ const { roadStatus, roadStatusSuggest, tollgateMonitoringData, trafficStatistics
storeToRefs(smartPark);
const initData = () => {
smartPark.loadRoadStatus();
smartPark.loadTollgateMonitoringData();
smartPark.loadFlowRate();
smartPark.fetchRoadStatus();
smartPark.fetchTollgateMonitoringData();
smartPark.fetchFlowRate();
updateChart({
enter: trafficStatistics.value.enter,