社区完成

This commit is contained in:
bunny 2025-05-16 21:55:58 +08:00
parent 077d0fb8bb
commit cf1a91f38f
17 changed files with 323 additions and 36 deletions

View File

@ -16,7 +16,7 @@ export const server = (mode: string) => {
'/api': {
target: VITE_APP_URL,
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/admin/, '/api'),
rewrite: (path: string) => path.replace(/^\/api/, '/api'),
},
'/mock': {
target: VITE_APP_URL,

View File

@ -1,6 +1,6 @@
import { defineFakeRoute } from 'vite-plugin-fake-server';
const BASE_URL = '/api/big-data';
const BASE_URL = '/mock/big-data';
const randomNumber = (range: number = 100) => {
return parseInt((Math.random() * range).toFixed(0));

View File

@ -1,5 +1,7 @@
import { defineFakeRoute } from 'vite-plugin-fake-server/client';
const BASE_URL = '/mock/community';
const randomNumber = (range: number = 100) => {
return parseInt((Math.random() * range).toFixed(0));
};
@ -7,7 +9,7 @@ const randomNumber = (range: number = 100) => {
export default defineFakeRoute([
// 设备总数
{
url: '/api/community/devices-amount',
url: `${BASE_URL}/devices-amount`,
method: 'GET',
response: () => ({
code: 200,
@ -25,7 +27,7 @@ export default defineFakeRoute([
},
// 预警概览
{
url: '/api/community/alarms-overview',
url: `${BASE_URL}/alarms-overview`,
method: 'GET',
response: () => ({
code: 200,
@ -40,7 +42,7 @@ export default defineFakeRoute([
},
// 中间顶部区域
{
url: '/api/community/community-statistics',
url: `${BASE_URL}/community-statistics`,
method: 'GET',
response: () => ({
code: 200,
@ -58,12 +60,12 @@ export default defineFakeRoute([
},
// 中间区域设备运行状态
{
url: '/api/community/devices-status',
url: `${BASE_URL}/devices-status`,
method: 'GET',
response: () => ({
code: 200,
data: {
devcies: [
devices: [
{
title: '正常运行总数',
total: randomNumber(100),
@ -86,4 +88,37 @@ export default defineFakeRoute([
message: '操作成功',
}),
},
// 数据统计
{
url: `${BASE_URL}/data-statistics`,
method: 'GET',
response: () => ({
code: 200,
data: [
{ name: '园区面积', value: randomNumber(9999) },
{ name: '绿化面积', value: randomNumber(9999) },
{ name: '道路面积', value: randomNumber(9999) },
{ name: '新能源车', value: randomNumber(9999) },
{ name: '安防在线率', value: randomNumber(9999) },
{ name: '安防在线率', value: randomNumber(9999) },
],
message: '操作成功',
}),
},
// 服务项目
{
url: `${BASE_URL}/server-project`,
method: 'GET',
response: () => ({
code: 200,
data: Array(15)
.fill(0)
.map((_, index) => ({
name: `服务项目-${index}`,
left: randomNumber(),
right: randomNumber(),
})),
message: '操作成功',
}),
},
]);

View File

@ -1,5 +1,7 @@
import { defineFakeRoute } from 'vite-plugin-fake-server';
const BASE_URL = '/mock/smart-park';
const randomNumber = (range: number = 100) => {
return parseInt((Math.random() * range).toFixed(0));
};
@ -14,7 +16,7 @@ const mockRoadStatus = () => {
export default defineFakeRoute([
// 道路状况
{
url: '/api/smart-park/road-status',
url: `${BASE_URL}/road-status`,
method: 'GET',
response: () => ({
code: 200,
@ -50,7 +52,7 @@ export default defineFakeRoute([
},
// 车辆监控
{
url: '/api/smart-park/monitor',
url: `${BASE_URL}/monitor`,
method: 'GET',
response: () => ({
code: 200,
@ -65,7 +67,7 @@ export default defineFakeRoute([
},
// 车流量
{
url: '/api/smart-park/flow-rate',
url: `${BASE_URL}/flow-rate`,
method: 'GET',
response: () => ({
code: 200,

View File

@ -72,12 +72,12 @@
"@vitejs/plugin-vue": "^5.2.1",
"@vue/tsconfig": "^0.7.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"jiti": "^2.4.2",
"typescript": "~5.7.2",
"vite": "6.2.6",
"vite-plugin-compression": "^0.5.1",
"vue-tsc": "^2.2.0",
"vite-plugin-vue-devtools": "^7.7.2",
"jiti": "^2.4.2"
"vue-tsc": "^2.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0",

View File

@ -1,4 +1,4 @@
import request from '@/api/server/request';
import request from '@/api/server/requestMock';
/* 规模效益 */
export const getScaleProfit = (data: any) => {

View File

@ -1,4 +1,4 @@
import request from '@/api/server/request';
import request from '@/api/server/requestMock';
/* 设备总数 */
export const getCommunityDevicesAmount = () => {
@ -15,6 +15,16 @@ export const getCommunityStatistics = () => {
};
/* 设备运行状态 */
export const getCommityDeicesStatus = () => {
export const getCommunityDeicesStatus = () => {
return request.get('/community/devices-status');
};
/* 数据统计 */
export const getDataStatistics = () => {
return request.get('community/data-statistics');
};
/* 右侧底部服务项目 */
export const getServerProject = () => {
return request.get('community/server-project');
};

View File

@ -1,7 +1,7 @@
import request from '@/api/server/request';
import request from '@/api/server/requestMock';
/* 实时道路 */
export const gethRoadStatus = () => {
export const getRoadStatus = () => {
return request.get('/smart-park/road-status');
};

View File

@ -2,9 +2,11 @@ import { defineStore } from 'pinia';
import {
getAlarmsOverview,
getCommityDeicesStatus,
getCommunityDeicesStatus,
getCommunityDevicesAmount,
getCommunityStatistics,
getDataStatistics,
getServerProject,
} from '@/api/community';
export const useCommunityStore = defineStore('communityStore', {
@ -16,28 +18,36 @@ export const useCommunityStore = defineStore('communityStore', {
// 统计列表
statisticsList: [],
// 设备状态
deviceStatus: { devcies: [], security: 0 },
deviceStatus: { devices: [], security: 0 },
// 数据统计
dataStatistics: [],
// 右侧底部服务项目
serverProject: [],
}),
actions: {
/* 设备总数 */
async fetchCommunityDevicesAmount() {
const result = await getCommunityDevicesAmount();
this.devicesList = result;
this.devicesList = await getCommunityDevicesAmount();
},
/* 预警概览 */
async fetchAlarmsOverview() {
const result = await getAlarmsOverview();
this.alarmOverviewList = result;
this.alarmOverviewList = await getAlarmsOverview();
},
/* 社区统计 */
async fetchCommunityStatisticsList() {
this.statisticsList = await getCommunityStatistics();
},
/* 设备状态 */
async fetchCommityDeicesStatus() {
const result = await getCommityDeicesStatus();
this.deviceStatus = result;
async fetchCommunityDeicesStatus() {
this.deviceStatus = await getCommunityDeicesStatus();
},
/* 数据统计 */
async fetchDataStatistics() {
this.dataStatistics = await getDataStatistics();
},
/* 右侧底部服务项目 */
async fetchServerProject() {
this.serverProject = await getServerProject();
},
},
});

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia';
import { gethRoadStatus, getTollgateMonitoringData, getTrafficStatistics } from '@/api/smartPark';
import { getRoadStatus, getTollgateMonitoringData, getTrafficStatistics } from '@/api/smartPark';
export const useSmartPark = defineStore('smartparkStore', {
state: () => ({
@ -16,7 +16,7 @@ export const useSmartPark = defineStore('smartparkStore', {
actions: {
/* 道路情况 */
async fetchRoadStatus() {
const result: any = await gethRoadStatus();
const result: any = await getRoadStatus();
this.roadStatus = result.entrances;
this.roadStatusSuggest = result.suggest;
},

View File

@ -7,7 +7,6 @@ import { debounceChart } from '@/utils/chart';
let myChart = null;
/* 随机颜色 */
const colors = ['#3D7FFF', '#00FFFF', '#FF1190', '#FEDB65'];
const option = ref<EChartsOption>({

View File

@ -0,0 +1,115 @@
import 'echarts/lib/component/dataZoom';
import type { EChartsOption } from 'echarts';
import { defineComponent, onMounted, type Ref, ref, watch } from 'vue';
import echarts from '@/plugins/echarts';
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: true, formatter: ({ value }) => `${value}%`, fontWeight: 1000, fontSize: 14 },
emphasis: {
focus: 'series',
},
itemStyle: {
borderRadius: [4],
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: true, formatter: ({ value }) => `${value}%`, fontWeight: 1000, fontSize: 14 },
itemStyle: {
borderRadius: [4],
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);
};
/** 更新图标数据 */
const updateChart = (myChart: Ref<echarts.ECharts | undefined>, props: any) => {
const series = myChart.value.getOption().series;
const sum = props.dataLeft + props.dataRight;
const right = +(props.dataRight / sum).toFixed(2);
const left = +(props.dataLeft / sum).toFixed(2);
series[0].data[0] = Math.round(left * 100);
series[1].data[0] = Math.round(right * 100);
myChart.value?.setOption({ series });
};
const RightFooterChart = defineComponent({
name: 'RightFooterChart',
props: { dataLeft: { type: Number }, dataRight: { type: Number }, title: { type: String } },
setup(props) {
const myChartRef = ref<echarts.ECharts>();
const chartRef = ref<HTMLDivElement>();
onMounted(() => {
renderLeftHeaderEcharts(myChartRef, chartRef);
updateChart(myChartRef, props);
watch(
() => [props.dataLeft, () => props.dataRight],
() => {
updateChart(myChartRef, props);
}
);
});
return () => (
<div>
{props.title}
<div ref={chartRef} className="progress"></div>
</div>
);
},
});
export default RightFooterChart;

View File

@ -0,0 +1,56 @@
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', '#FE9B45'];
const option = ref<EChartsOption>({
backgroundColor: 'transparent',
grid: { top: 0, right: 0, bottom: 0, left: 0, containLabel: true },
tooltip: { trigger: 'item' },
series: [
{
name: '名称',
type: 'pie',
radius: [14, 100],
center: ['50%', '50%'],
roseType: 'area',
itemStyle: { borderRadius: 4 },
data: [],
label: { distanceToLabelLine: 4, position: 'insideBottom' },
labelLine: { show: true, length: 1, length2: 0, smooth: true },
},
],
});
/** 渲染图表 */
export const renderRightHeaderChart = (element: Ref<HTMLDivElement>) => {
myChart = echarts.init(element.value, null, {
renderer: 'canvas',
devicePixelRatio: window.devicePixelRatio,
});
debounceChart(myChart);
myChart.setOption(option.value);
};
/** 更新图标数据 */
export const updateRightHeaderChart = (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 title = myChart?.getOption()?.title;
myChart.setOption({ series, title });
};

View File

@ -18,7 +18,7 @@ const securityCharRef = ref();
/* 初始化数据 */
const initData = async () => {
await communityStore.fetchCommunityStatisticsList();
await communityStore.fetchCommityDeicesStatus();
await communityStore.fetchCommunityDeicesStatus();
updateBodyChart({ data: deviceStatus.value.security });
};
@ -63,7 +63,7 @@ onMounted(() => {
<div class="community__metrics">
<div
v-for="(item, index) in deviceStatus?.devcies?.slice(0, 2)"
v-for="(item, index) in deviceStatus?.devices?.slice(0, 2)"
:key="index"
class="community__metric-card"
>
@ -76,7 +76,7 @@ onMounted(() => {
</div>
<div ref="securityCharRef" class="community__instrument-panel" />
<div
v-for="(item, index) in deviceStatus?.devcies?.slice(2, 4)"
v-for="(item, index) in deviceStatus?.devices?.slice(2, 4)"
:key="index"
class="community__metric-card"
>

View File

@ -5,7 +5,7 @@ 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 { renderBodyChart, updateBodyChart } from '@/views/community/charts/left-footer-chart';
import LeftHeaderChart from '@/views/community/charts/left-header-chart';
import CommonPanel from '@/views/community/components/CommonPanel.vue';

View File

@ -1,20 +1,80 @@
<script lang="ts" setup>
import { useIntervalFn } from '@vueuse/core';
import { storeToRefs } from 'pinia';
import { onMounted, ref } from 'vue';
import { useCommunityStore } from '@/store/modules/community';
import RightFooterChart from '@/views/community/charts/right-footer';
import CommonPanel from '@/views/community/components/CommonPanel.vue';
import { renderRightHeaderChart, updateRightHeaderChart } from '../charts/right-header';
const communityStore = useCommunityStore();
const { serverProject, dataStatistics } = storeToRefs(communityStore);
const rightHeaderChart = ref();
/* 初始化加载 */
const initAppData = async () => {
await communityStore.fetchDataStatistics();
communityStore.fetchServerProject();
updateRightHeaderChart({ list: dataStatistics.value });
};
onMounted(() => {
renderRightHeaderChart(rightHeaderChart);
initAppData();
useIntervalFn(() => {
initAppData();
}, 1000);
});
</script>
<template>
<div class="community__sidebar">
<div class="community__sidebar-item">
<CommonPanel title="标题标题" />
<CommonPanel title="标题标题">
<div ref="rightHeaderChart" class="w-full h-full" />
</CommonPanel>
</div>
<div class="community__sidebar-item">
<CommonPanel title="标题标题" />
<CommonPanel title="标题标题">
<div class="community__panel-chart">
<RightFooterChart
v-for="(chart, index) in serverProject"
:key="index"
:title="chart.name"
:data-left="chart.left"
:data-right="chart.right"
/>
</div>
</CommonPanel>
</div>
</div>
</template>
<style lang="scss" scoped>
.community__sidebar {
:deep(.progress) {
width: 100%;
height: 20px;
}
.community__panel-chart {
position: relative;
display: flex;
justify-content: space-between;
flex-direction: column;
flex-wrap: nowrap;
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
&::-webkit-scrollbar {
display: none;
}
}
}
</style>