Compare commits

...

3 Commits

Author SHA1 Message Date
bunny cf1a91f38f 社区完成 2025-05-16 21:55:58 +08:00
bunny 077d0fb8bb 社区面板 完成 2025-05-16 21:55:22 +08:00
bunny dafb9b9db7 大数据可视化 2025-05-15 23:10:31 +08:00
34 changed files with 1325 additions and 224 deletions

View File

@ -4,9 +4,9 @@ import vueJsx from '@vitejs/plugin-vue-jsx';
import { presetIcons } from 'unocss';
import UnoCSS from 'unocss/vite';
import type { PluginOption } from 'vite';
// @ts-ignore
import { vitePluginFakeServer } from 'vite-plugin-fake-server';
import removeConsole from 'vite-plugin-remove-console';
import vueDevTools from 'vite-plugin-vue-devtools';
import Inspector from 'vite-plugin-vue-inspector';
import { useCDN } from './cdn';
@ -20,6 +20,7 @@ export const plugins = (mode: string): PluginOption[] => {
Inspector(),
report(),
removeConsole(),
vueDevTools(),
useCDN(mode),
viteConsoleLog(mode),
UnoCSS({

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,

132
mock/bid-data.ts Normal file
View File

@ -0,0 +1,132 @@
import { defineFakeRoute } from 'vite-plugin-fake-server';
const BASE_URL = '/mock/big-data';
const randomNumber = (range: number = 100) => {
return parseInt((Math.random() * range).toFixed(0));
};
export default defineFakeRoute([
// 规模效益
{
url: `${BASE_URL}/scale-benfit`,
method: 'GET',
response: () => ({
code: 200,
data: {
total: randomNumber(200),
income: randomNumber(999999999),
incomeChain: randomNumber(150),
expend: randomNumber(999999999),
expendChain: randomNumber(150),
},
message: '操作成功',
}),
},
// 经营收入
{
url: `${BASE_URL}/year-income`,
method: 'GET',
response: () => ({
code: 200,
data: {
list: Array(4)
.fill(0)
.map((_, index) => ({
title: `经营总收入-${index + 1}`,
amount: randomNumber(9999999),
percent: randomNumber(),
})),
endTime: new Date().getTime(),
},
message: '操作成功',
}),
},
// 园区规划
{
url: `${BASE_URL}/chart-plan`,
method: 'GET',
response: () => ({
code: 200,
data: [
Array(12)
.fill(0)
.map(() => {
const num = randomNumber().toFixed(2);
return parseInt(num);
}),
Array(12)
.fill(0)
.map(() => {
const num = randomNumber().toFixed(2);
return parseInt(num);
}),
],
message: '操作成功',
}),
},
// 税收概览
{
url: `${BASE_URL}/revenue-overview`,
method: 'GET',
response: () => ({
code: 200,
data: Array(12)
.fill(0)
.map(() => randomNumber(9999)),
message: '操作成功',
}),
},
// 园区进出口额
{
url: `${BASE_URL}/parks/import-export`,
method: 'GET',
response: () => ({
code: 200,
data: {
amount: randomNumber(99999999),
import: randomNumber(99999),
export: randomNumber(99999),
},
message: '操作成功',
}),
},
// 企业信息
{
url: `${BASE_URL}/enterprise-info`,
method: 'GET',
response: () => ({
code: 200,
data: {
// 报税金额
taxAmount: randomNumber(999999),
// 税收总金额
taxTotalAmount: randomNumber(99999),
// 企业数量
enterpriseCount: randomNumber(9999),
// 国营企业(强调国家控股)
stateOwnedEnterprise: randomNumber(9999),
// 私营企业
privateEnterprise: randomNumber(9999),
// 投资总金额
investmentTotal: randomNumber(10000),
},
message: '操作成功',
}),
},
// 园区规划
{
url: `${BASE_URL}/parks/areas`,
method: 'GET',
response: () => ({
code: 200,
data: Array(9)
.fill(0)
.map(() => ({
title: randomNumber(99999.99),
summary: '建成投产面积',
})),
message: '操作成功',
}),
},
]);

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

@ -8,6 +8,8 @@
"build": "vite build",
"preview": "vite preview",
"report": "rimraf dist && vite build",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache-location node_modules/.cache/stylelint/",
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint"
},
@ -57,7 +59,7 @@
"vite-plugin-fake-server": "^2.2.0",
"vite-plugin-remove-console": "^2.2.0",
"vite-plugin-vue-inspector": "^5.3.1",
"vue": "^3.5.13",
"vue": "^3.5.14",
"vue-demi": "^0.14.10",
"vue-eslint-parser": "^9.4.3",
"vue-router": "^4.4.3",
@ -70,9 +72,11 @@
"@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",
"vite-plugin-vue-devtools": "^7.7.2",
"vue-tsc": "^2.2.0"
},
"engines": {

File diff suppressed because it is too large Load Diff

5
push.sh Normal file
View File

@ -0,0 +1,5 @@
git checkout master
git merge dev
git push --all
git push --tags
git checkout dev

36
src/api/bigData.ts Normal file
View File

@ -0,0 +1,36 @@
import request from '@/api/server/requestMock';
/* 规模效益 */
export const getScaleProfit = (data: any) => {
return request.get('big-data/scale-benfit', { params: data });
};
/* 规模效益 */
export const getYearIncome = () => {
return request.get('big-data/year-income');
};
/* 园区规划 */
export const getParkPlan = () => {
return request.get('big-data/chart-plan');
};
/* 税收概览 */
export const getRevenueOverView = () => {
return request.get('big-data/revenue-overview');
};
/* 园区进出口额 */
export const getParkImportExportData = (params: any) => {
return request.get('big-data/parks/import-export', { params });
};
/* 企业信息 */
export const getEnterpriseInfo = () => {
return request.get('big-data/enterprise-info');
};
/* 园区规划 */
export const getParkAreas = () => {
return request.get('big-data/parks/areas');
};

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

@ -12,10 +12,11 @@ export default defineComponent({
default: false,
},
},
setup(props) {
setup(props, { slots }) {
return () => (
<div className={'mb-[20px]'}>
{(props.separator ? formatter(props.money) : props.money.toString())
{slots.default?.()}
{(props.separator ? formatter(props.money ?? 0) : props.money.toString())
.split(/(\d,)/g)
.filter((item) => item !== '')
.map((item) => (!item.includes(',') ? item.split('') : item))
@ -23,7 +24,6 @@ export default defineComponent({
.map((item, index) => (
<span key={index}>{item}</span>
))}
{/* {moneyStringList.value?.map((item, index) => <span key={index}>{item}</span>)} */}
</div>
);
},

View File

@ -3,11 +3,21 @@ import { TimeSelectType } from '@/components/TimeSelect/type';
defineProps({
timeList: Array<TimeSelectType>,
modelValue: String,
});
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;
}>();
const handleChange = (e: Event) => {
const value = (e.target as HTMLSelectElement).value;
emit('update:modelValue', value); // v-model
};
</script>
<template>
<select class="time-select" name="timeSelect">
<select class="time-select" :value="modelValue" @change="handleChange">
<option v-for="(item, index) in timeList" :key="index" :value="item.value">
{{ item.label }}
</option>

View File

@ -1,4 +1,4 @@
export interface TimeSelectType {
value: string | number;
label: string | number;
value: string;
label: string;
}

View File

@ -0,0 +1,85 @@
import { defineStore } from 'pinia';
import {
getEnterpriseInfo,
getParkAreas,
getParkImportExportData,
getParkPlan,
getRevenueOverView,
getScaleProfit,
getYearIncome,
} from '@/api/bigData';
export const useBigDataStore = defineStore('bidDataStore', {
state: () => ({
// 规模效益
scaleProfit: {
total: undefined,
income: undefined,
incomeChain: undefined,
expend: undefined,
expendChain: undefined,
},
// 本年经营收入
incomeList: { list: [], endTime: undefined },
// 园区规划
parkPlan: [],
// 税收概览
revenueOverview: [],
// 园区进出口额
parkImportExportData: { amount: undefined, import: undefined, export: undefined },
// 企业信息
enterpriseInfo: {
// 报税金额
taxAmount: undefined,
// 税收总金额
taxTotalAmount: undefined,
// 企业数量
enterpriseCount: undefined,
// 国营企业(强调国家控股)
stateOwnedEnterprise: undefined,
// 私营企业
privateEnterprise: undefined,
// 投资总金额
investmentTotal: undefined,
},
// 园区规划
parkAreas: [],
}),
actions: {
/* 规模效益 */
async fetchScaleProfit(data: any) {
this.scaleProfit = await getScaleProfit(data);
},
/* 本年经营收入 */
async fetchYearIncome() {
this.incomeList = await getYearIncome();
},
/* 园区规划 */
async fetchParkPlan() {
this.parkPlan = await getParkPlan();
},
/* 税收概览 */
async fetchRevenueOverview() {
this.revenueOverview = await getRevenueOverView();
},
/* 园区进出口额 */
async fetchParkImportExportData(data) {
this.parkImportExportData = await getParkImportExportData(data);
},
/* 企业信息 */
async fetchEnterpriseInfo() {
this.enterpriseInfo = await getEnterpriseInfo();
},
/* 园区规划 */
async fetchParkAreas() {
this.parkAreas = await getParkAreas();
},
},
});

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

@ -13,7 +13,7 @@ export const debounceChart = (myChart: echarts.ECharts | undefined) => {
/** 数字格式化 */
export const formatter = (number: any) => {
const numbers = number.toString().split('').reverse();
const numbers = number?.toString().split('').reverse();
const segs = [];
while (numbers.length) segs.push(numbers.splice(0, 3).join(''));

View File

@ -78,7 +78,7 @@ const option = {
};
/** 渲染图表 */
export const renderFooterChart = (element: Ref<HTMLDivElement>) => {
export const renderBigDateContentFooterChart = (element: Ref<HTMLDivElement>) => {
myChart = echarts.init(element.value, null, {
renderer: 'canvas',
devicePixelRatio: window.devicePixelRatio,
@ -88,3 +88,12 @@ export const renderFooterChart = (element: Ref<HTMLDivElement>) => {
myChart.setOption(option);
};
/** 更新图标数据 */
export const updateBigDateContentFooterChart = (props: { data: Array<number> }) => {
const series = myChart.getOption()?.series;
series[0].data = props.data;
series[1].data = props.data;
myChart.setOption({ series });
};

View File

@ -6,6 +6,8 @@ import { type Ref, ref } from 'vue';
import echarts from '@/plugins/echarts';
import { debounceChart } from '@/utils/chart';
let myChart = null;
const option = ref<EChartsOption>();
option.value = {
tooltip: {
@ -44,8 +46,8 @@ option.value = {
],
};
export const renderEcharts = (element: Ref<HTMLDivElement>) => {
const myChart: any = echarts.init(element.value, null, {
export const renderBigDataLeftTopEcharts = (element: Ref<HTMLDivElement>) => {
myChart = echarts.init(element.value, null, {
renderer: 'svg',
devicePixelRatio: window.devicePixelRatio,
});
@ -54,3 +56,15 @@ export const renderEcharts = (element: Ref<HTMLDivElement>) => {
myChart.setOption(option.value);
};
/** 更新图标数据 */
export const updateBigDataLeftTopEcharts = (props: {
import: Array<number>;
export: Array<number>;
}) => {
const series = myChart.getOption()?.series;
series[0].data[0] = props.import;
series[1].data[0] = -props.export;
myChart.setOption({ series });
};

View File

@ -1,17 +1,25 @@
<script lang="ts" setup>
import { useIntervalFn } from '@vueuse/core';
import { storeToRefs } from 'pinia';
import { onMounted, ref } from 'vue';
import { useBigDataStore } from '@/store/modules/bigData';
import { getImage } from '@/utils/image';
import { renderFooterChart } from '@/views/big-data/charts/content-footer';
import {
renderBigDateContentFooterChart,
updateBigDateContentFooterChart,
} from '@/views/big-data/charts/content-footer';
const headerList = [
const HEADER_LIST = [
{ title: '员工', img: '/images/big-data/bg-content-top-1.png' },
{ title: '智慧大楼', img: '/images/big-data/bg-content-top-2.png' },
{ title: '智慧设备', img: '/images/big-data/bg-content-top-3.png' },
{ title: '数据报表', img: '/images/big-data/bg-content-top-4.png' },
];
const bidDataStore = useBigDataStore();
const { revenueOverview } = storeToRefs(bidDataStore);
const isActive = ref(true);
const footerChartRef = ref<HTMLDivElement>();
@ -22,9 +30,25 @@ const changeMoveState = () => {
}, 2000);
};
const initAppData = async () => {
await bidDataStore.fetchRevenueOverview();
updateBigDateContentFooterChart({ data: revenueOverview.value });
};
onMounted(() => {
//
changeMoveState();
renderFooterChart(footerChartRef);
//
renderBigDateContentFooterChart(footerChartRef);
//
initAppData();
//
useIntervalFn(() => {
initAppData();
}, 1000);
});
</script>
@ -32,7 +56,7 @@ onMounted(() => {
<div class="big-data__content">
<header class="big-data__header">
<ul class="big-data__stats-list">
<li v-for="(item, index) in headerList" :key="index">
<li v-for="(item, index) in HEADER_LIST" :key="index">
<img :src="getImage(item.img)" alt="" />
<h2>{{ item.title }}</h2>
</li>

View File

@ -1,70 +1,60 @@
<script lang="ts" setup>
import { useIntervalFn } from '@vueuse/core';
import { onMounted, ref } from 'vue';
import dayjs from 'dayjs';
import { storeToRefs } from 'pinia';
import { onMounted, reactive, ref } from 'vue';
import { displayContent } from '@/components/DigitalNumber/DigitalCurrency';
import DigitalNumber from '@/components/DigitalNumber/DigitalNumber';
import TimeSelect from '@/components/TimeSelect/index.vue';
import { TimeSelectType } from '@/components/TimeSelect/type';
import { useBigDataStore } from '@/store/modules/bigData';
import { formatter } from '@/utils/chart';
import { ChartProgress } from '@/views/big-data/charts/left-body';
import { renderFooterChart, updateFooterChart } from '@/views/big-data/charts/left-footer';
const timeList = ref<TimeSelectType[]>([
{ label: '2020.09', value: '2021' },
{ label: '2020.09', value: '2021' },
{ label: '2020.09', value: '2021' },
const TIME_LIST = ref<TimeSelectType[]>([
{ label: '2020.09', value: '2020-09' },
{ label: '2021.09', value: '2021-09' },
{ label: '2022.09', value: '2022-08' },
{ label: '2023.09', value: '2023-08' },
{ label: '2025.09', value: '2025-08' },
]);
/* body数据---模拟数据 */
const bodyList = ref([]);
const initBodyList = () => {
const randomNumber = (range: number = 100) => {
return parseInt((Math.random() * range).toFixed(0));
};
bodyList.value = [
{ title: '经营总收入', amount: randomNumber(9999999), percent: randomNumber() },
{ title: '经营总收入', amount: randomNumber(9999999), percent: randomNumber() },
{ title: '经营总收入', amount: randomNumber(9999999), percent: randomNumber() },
{ title: '经营总收入', amount: randomNumber(9999999), percent: randomNumber() },
];
};
const bigdataStore = useBigDataStore();
const { scaleProfit, incomeList, parkPlan } = storeToRefs(bigdataStore);
const form = reactive({
date: TIME_LIST.value[0].value,
});
/* 底部图表---模拟数据 */
const footerChartRef = ref<HTMLDivElement>();
const mockFooterChart = () => {
function random() {
return Array(12)
.fill(0)
.map(() => {
const num = (Math.random() * 100).toFixed(2);
return parseInt(num);
});
}
const data: Array<Array<number>> = [random(), random()];
updateFooterChart(data);
/* 初始化数据 */
const initAppData = () => {
bigdataStore.fetchScaleProfit({ date: form.date });
bigdataStore.fetchYearIncome();
bigdataStore.fetchParkPlan();
updateFooterChart(parkPlan.value);
};
onMounted(() => {
//
renderFooterChart(footerChartRef);
initBodyList();
initAppData();
//
useIntervalFn(() => {
mockFooterChart();
initBodyList();
initAppData();
}, 1000);
});
</script>
<template>
<div class="big-data__sidebar">
<div class="big-data__header">
<header class="big-data__header">
<div class="flex-x-between">
<h1 class="big-data__sidebar-title">规模效益</h1>
<TimeSelect :time-list="timeList" />
<TimeSelect v-model="form.date" :time-list="TIME_LIST" />
</div>
<ul>
@ -74,16 +64,16 @@ onMounted(() => {
<div>
<span>
总值增幅
<em>+123%</em>
<em>+{{ scaleProfit.total }}%</em>
</span>
<span>
超越第二名
<em>+22.3%</em>
<em>+{{ scaleProfit.incomeChain }}%</em>
</span>
</div>
</div>
<div class="money-digit">
<component :is="displayContent('8888888')" />
<DigitalNumber :money="scaleProfit.income" :separator="true" />
</div>
</li>
@ -93,47 +83,50 @@ onMounted(() => {
<div>
<span>
环比变化
<em>+123%</em>
<em>+{{ scaleProfit.expendChain }}%</em>
</span>
</div>
</div>
<div class="money-digit">
<component :is="displayContent('888888')" />
<DigitalNumber :money="scaleProfit.expend" :separator="true" />
</div>
</li>
</ul>
</div>
</header>
<div class="big-data__body h-[389px]">
<main class="big-data__body h-[389px]">
<div class="flex-x-between">
<h1 class="big-data__sidebar-title">本年经营收入</h1>
<span class="big-data__sidebar-title-describe">截止时间至2021年6月</span>
<span class="big-data__sidebar-title-describe">
截止时间至 {{ dayjs(incomeList.endTime).format('YYYY-MM-DD') }}
</span>
</div>
<ul>
<li v-for="(item, index) in bodyList" :key="index">
<li v-for="(item, index) in incomeList.list" :key="index">
<div>
<h1>{{ item.title }}</h1>
<em>¥ {{ formatter(item.amount) }}}</em>
<em>¥ {{ formatter(item.amount) }}</em>
</div>
<ChartProgress :percent="item.percent" />
</li>
</ul>
</div>
</main>
<div class="big-data__footer">
<footer class="big-data__footer">
<div class="flex-x-between">
<div class="big-data__sidebar-title">
<h1>园区规划</h1>
</div>
</div>
<div ref="footerChartRef" class="big-data__footer-chart-container" />
</div>
</footer>
</div>
</template>
<style lang="scss" scoped>
.big-data__header {
width: 100%;
height: 274px;
ul {

View File

@ -1,15 +1,24 @@
<script lang="ts" setup>
const list = [
{ title: '134.5㎡', summary: '建成投产面积' },
{ title: '38000㎡', summary: '保税仓库面积' },
{ title: '327.3㎡', summary: '物流场站' },
{ title: '327.3㎡', summary: '物流场站' },
{ title: '327.3㎡', summary: '物流场站' },
{ title: '327.3㎡', summary: '物流场站' },
{ title: '327.3㎡', summary: '物流场站' },
{ title: '327.3㎡', summary: '物流场站' },
{ title: '327.3㎡', summary: '物流场站' },
];
import { useIntervalFn } from '@vueuse/core';
import { storeToRefs } from 'pinia';
import { onMounted } from 'vue';
import { useBigDataStore } from '@/store/modules/bigData';
const bidDataStore = useBigDataStore();
const { parkAreas } = storeToRefs(bidDataStore);
const initAppData = () => {
bidDataStore.fetchParkAreas();
};
onMounted(() => {
initAppData();
useIntervalFn(() => {
initAppData();
}, 1000);
});
</script>
<template>
@ -19,8 +28,8 @@ const list = [
</div>
<ul class="big-data__sidebar-card">
<li v-for="(item, index) in list" :key="index">
<h1>{{ item.title }}</h1>
<li v-for="(item, index) in parkAreas" :key="index">
<h1>{{ item.title }} </h1>
<p>{{ item.summary }}</p>
</li>
</ul>

View File

@ -1,12 +1,28 @@
<script lang="ts" setup>
import { useIntervalFn } from '@vueuse/core';
import { storeToRefs } from 'pinia';
import { onMounted, ref } from 'vue';
import { useBigDataStore } from '@/store/modules/bigData';
import { renderEcharts } from '@/views/business-supervision/charts/leftSidebarMiddle';
const chartPie = ref<HTMLDivElement>();
const bidDataStore = useBigDataStore();
const { enterpriseInfo } = storeToRefs(bidDataStore);
const initAppData = () => {
bidDataStore.fetchEnterpriseInfo();
};
onMounted(() => {
renderEcharts(chartPie);
initAppData();
useIntervalFn(() => {
initAppData();
}, 1000);
});
</script>
@ -20,24 +36,24 @@ onMounted(() => {
<ul class="big-data__sidebar-card">
<li>
<h1>报税金额</h1>
<p class="c-warning-secondary">¥1551154545</p>
<p class="c-warning-secondary">{{ enterpriseInfo.taxAmount }}</p>
</li>
<li>
<div class="flex-x-between">
<div>
<h1>企业数量</h1>
<p class="c-primary-secondary">783</p>
<p class="c-primary-secondary">{{ enterpriseInfo.enterpriseCount }}</p>
</div>
<div ref="chartPie" class="big-data__sidebar-card-chart-pie" />
</div>
<div class="big-data__sidebar-card-enterprise-type">
<span class="mr-[13px]">
<span class="mr-[4px]">
<i class="bg-primary-secondary" />
国营企业 345
国营企业 {{ enterpriseInfo.stateOwnedEnterprise }}
</span>
<span>
<i class="bg-warning-secondary" />
私营企业 345
私营企业 {{ enterpriseInfo.privateEnterprise }}
</span>
</div>
</li>
@ -47,7 +63,7 @@ onMounted(() => {
<span class="font-size-[12px]">环比变化</span>
<span class="font-size-[12px]">+123%</span>
</div>
<p class="c-primary-secondary">¥1551154545</p>
<p class="c-primary-secondary">¥{{ enterpriseInfo.investmentTotal }}</p>
</li>
<li>
<div class="flex-x-between">
@ -55,7 +71,7 @@ onMounted(() => {
<span class="font-size-[12px]">环比变化</span>
<span class="font-size-[12px]">-123%</span>
</div>
<p class="c-primary-secondary">¥1551154545</p>
<p class="c-primary-secondary">¥{{ enterpriseInfo.taxTotalAmount }}</p>
</li>
</ul>
</div>

View File

@ -1,50 +1,85 @@
<script lang="tsx" setup>
import { onMounted, ref } from 'vue';
import { useIntervalFn } from '@vueuse/core';
import { storeToRefs } from 'pinia';
import { onMounted, reactive, ref } from 'vue';
import { displayContent } from '@/components/DigitalNumber/DigitalCurrency';
import DigitalNumber from '@/components/DigitalNumber/DigitalNumber';
import TimeSelect from '@/components/TimeSelect/index.vue';
import { TimeSelectType } from '@/components/TimeSelect/type';
import { renderEcharts } from '@/views/big-data/charts/right-header';
import { useBigDataStore } from '@/store/modules/bigData';
import {
renderBigDataLeftTopEcharts,
updateBigDataLeftTopEcharts,
} from '@/views/big-data/charts/right-header';
const chartProgress = ref<HTMLDivElement>();
const money = '1386114';
const timeList = ref<TimeSelectType[]>([
{ label: '2020.09', value: '2021' },
{ label: '2020.09', value: '2021' },
{ label: '2020.09', value: '2021' },
const TIME_LIST = ref<TimeSelectType[]>([
{ label: '2020.09', value: '2020-09' },
{ label: '2021.09', value: '2021-09' },
{ label: '2022.09', value: '2022-08' },
{ label: '2023.09', value: '2023-08' },
{ label: '2025.09', value: '2025-08' },
]);
const bidDataStore = useBigDataStore();
const { parkImportExportData } = storeToRefs(bidDataStore);
//
const chartProgressRef = ref<HTMLDivElement>();
//
const form = reactive({
date: TIME_LIST.value[0].value,
});
/* 初始化数据 */
const initAppData = () => {
bidDataStore.fetchParkImportExportData({ date: form.date });
updateBigDataLeftTopEcharts({
import: parkImportExportData.value.import,
export: parkImportExportData.value.export,
});
};
onMounted(() => {
renderEcharts(chartProgress);
//
renderBigDataLeftTopEcharts(chartProgressRef);
//
initAppData();
//
useIntervalFn(() => {
initAppData();
}, 1000);
});
</script>
<template>
<div class="big-data__header h-[226px]">
<div class="flex-x-between">
<h1 class="big-data__header-title">园区进出口额</h1>
<h1 class="big-data__sidebar-title">园区进出口额</h1>
<div>
<span class="big-data__header-tag">总数据</span>
<TimeSelect :time-list="timeList" />
<TimeSelect v-model="form.date" :time-list="TIME_LIST" />
</div>
</div>
<div class="money-digit">
<component :is="displayContent(money)" />
<DigitalNumber :money="parkImportExportData.amount" :separator="true">
<span></span>
</DigitalNumber>
</div>
<div>
<div ref="chartProgress" class="big-data__header-progress" />
<div ref="chartProgressRef" class="big-data__header-progress" />
<ul class="big-data__header-value">
<li>
进口额
<i>¥1551154545</i>
<i>¥ {{ parkImportExportData.import }}</i>
</li>
<li class="thin-line h-[20px]" />
<li>
<i>¥1551154545</i>
<i>¥ {{ parkImportExportData.export }}</i>
出口额
</li>
</ul>
@ -57,7 +92,7 @@ onMounted(() => {
margin: 14px 0 0 0;
width: 100%;
span {
:deep(span) {
height: 69px;
}
}

View File

@ -26,7 +26,7 @@ onBeforeMount(async () => {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 9px 0 9px;
padding: 0 14px;
width: 100%;
height: 100%;

View File

@ -7,7 +7,6 @@ import { debounceChart } from '@/utils/chart';
let myChart = null;
/* 随机颜色 */
const colors = ['#3D7FFF', '#00FFFF', '#FF1190', '#FEDB65'];
const option = ref<EChartsOption>({
@ -30,7 +29,6 @@ const option = ref<EChartsOption>({
barWidth: 20,
barGap: 0,
coordinateSystem: 'polar',
itemStyle: {},
label: { show: true, position: 'middle' },
},
});
@ -50,13 +48,14 @@ export const renderBodyChart = (element: Ref<HTMLDivElement>) => {
/** 更新图标数据 */
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 total = props.list?.reduce((old, item) => old + item.value, 0);
const title = myChart?.getOption()?.title;
title[0].text = total;

View File

@ -1,10 +1,9 @@
import 'echarts/lib/component/dataZoom';
import type { EChartsOption, EChartsType } from 'echarts';
import type { EChartsOption } 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: {
@ -69,13 +68,11 @@ export const renderLeftHeaderEcharts: any = (
devicePixelRatio: window.devicePixelRatio,
});
myChart.value!.setOption(option.value!);
debounceChart(myChart.value);
myChart.value?.setOption(option.value);
};
/** 更新图标数据 */
const updateChart = (myChart: Ref<EChartsType | undefined>, props: any) => {
const updateChart = (myChart: Ref<echarts.ECharts | undefined>, props: any) => {
const series = myChart.value.getOption().series;
series[0].data[0] = props.dataLeft;
series[1].data[0] = props.dataRight;
@ -87,18 +84,18 @@ const LeftHeaderChart = defineComponent({
name: 'LeftHeaderChart',
props: { dataLeft: { type: Number }, dataRight: { type: Number } },
setup(props) {
const myChartRef = ref<EChartsType>();
const myChartRef = ref<echarts.ECharts>();
const chartRef = ref<HTMLDivElement>();
onMounted(() => {
renderLeftHeaderEcharts(myChartRef, chartRef);
updateChart(myChartRef, props);
watch(
() => [props.dataLeft, () => props.dataRight],
() => {
updateChart(myChartRef, props);
},
{ immediate: true }
}
);
});

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

@ -21,7 +21,7 @@ defineProps({
.panel {
width: 430px;
height: 440px;
background: url('../images/bg-common-panel.png') no-repeat;
background: url('../images/bg-common-panel.png') no-repeat center;
background-size: cover;
h1 {

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"
>
@ -103,7 +103,7 @@ onMounted(() => {
.community__stat-card {
position: relative;
width: 209px;
width: 215px;
height: 204px;
color: #fff;
@ -111,6 +111,8 @@ onMounted(() => {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.community__stat-content {
@ -145,7 +147,7 @@ onMounted(() => {
margin: 40px 0 0 0;
width: 100%;
height: 660px;
background: url('../images/bg-body.png') no-repeat;
background: url('../images/bg-body.png') no-repeat center;
background-size: contain;
/* 数据图片内容 */
@ -186,7 +188,8 @@ onMounted(() => {
align-items: center;
width: 105px;
height: 170px;
background: url('../images/bg-body-card.png');
background: url('../images/bg-body-card.png') no-repeat center;
background-size: cover;
h1 {
font-size: 40px;
@ -206,7 +209,8 @@ onMounted(() => {
width: 286px;
height: 169px;
color: #fff;
background: url('../images/bg-body-instrument-panel.png');
background: url('../images/bg-body-instrument-panel.png') no-repeat center;
background-size: cover;
}
}
}

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>