feat:有限空间饼图加接口以及请求

This commit is contained in:
Free-sss 2025-08-27 19:00:00 +08:00
parent f0075ba4c0
commit 5a15829710
5 changed files with 214 additions and 84 deletions

View File

@ -3,11 +3,16 @@ import { PieCircleCommenConfig } from './index'
import { CreateComponentType } from '@/packages/index.d'
import cloneDeep from 'lodash/cloneDeep'
import dataJson from './data.json'
import { pad, padEnd } from 'lodash'
import { calcPadding } from '@visactor/vchart/esm/util'
export const includes = ['legend']
export const selectStyleOption = {
showAngle: false,
color: '#1B9FCC',
backgroundColor: '#0F4557',
borderTop: '1px solid #1CA4D2',
cursor: 'pointer',
borderRadius: '2px',
}
const otherConfig = {
// 轮播动画
isCarousel: false,
@ -16,9 +21,26 @@ const otherConfig = {
show: true,
selectValue: 'day',
dataset: [
{ label: '今日', value: 'day' },
{ label: '本周', value: 'week' },
{ label: '本月', value: 'month' }
{
label: '当天',
value: 'day'
},
{
label: '本周',
value: 'week'
},
{
label: '当月',
value: 'month'
},
{
label: '本季度',
value: 'quarter'
},
{
label: '当年',
value: 'year'
}
]
},
titleText: '今日数据',
@ -31,9 +53,11 @@ const otherConfig = {
},
headerOption: {
paddingLeft: 30,
paddingRight:0,
paddingRight: 0,
paddingTop: 0,
paddingBottom: 0
}, selectStyleOption: {
}
}

View File

@ -1,26 +1,26 @@
{
"dimensions": [
"name",
"value",
"alarmLevel",
"count",
"itemColor",
"borderColor"
],
"source": [
{
"name": "分类一",
"value": 94
"alarmLevel": "分类一",
"count": 94
},
{
"name": "分类二",
"value": 82
"alarmLevel": "分类3",
"count": 4
},
{
"name": "分类三",
"value": 78
"alarmLevel": "分类2",
"count": 30
},
{
"name": "分类四",
"value": 60
"alarmLevel": "分类4",
"count": 40
}
]
}

View File

@ -1,7 +1,9 @@
<template>
<div class="go-border-box">
<SmallBaorder01 :titleText="option.titleText" :title-option="option.titleOption"
:select-option="option.selectOption" :header-option="option.headerOption" @change="handleBorderSelectChange">
:select-option="option.selectOption" :header-option="option.headerOption" @change="handleBorderSelectChange"
:selectStyleConfig="selectStyleOption
">
<v-chart class=" chart" ref="vChartRef" :theme="themeColor" :init-options="initOptions" :option="option"
autoresize> </v-chart>
</SmallBaorder01>
@ -16,7 +18,7 @@ import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { PieChart } from 'echarts/charts'
import { mergeTheme } from '@/packages/public/chart'
import config, { includes } from './config'
import config, { includes, selectStyleOption } from './config'
import { useChartDataFetch } from '@/hooks'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
import SmallBaorder01 from '../SmallBorder01Co/index.vue'
@ -28,6 +30,7 @@ import {
TitleComponent, GraphicComponent
} from 'echarts/components'
import axiosInstance from '@/api/axios'
const props = defineProps({
@ -56,9 +59,44 @@ use([
TitleComponent,
GraphicComponent
])
// const option = computed(() => {
// return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
// })
const option = computed(() => {
return mergeTheme(props.chartConfig.option, props.themeSetting, includes)
})
const merged = mergeTheme(props.chartConfig.option, props.themeSetting, includes);
const dataset = merged.dataset || {};
const total = calculateTotal(dataset);
merged.title.text = total;
// formatter
merged.legend = {
...merged.legend,
formatter: (name: string) => {
const item = dataset.source?.find((it: any) => it[dataset.dimensions?.[0]] === name);
if (!item) return name;
const value = item[dataset.dimensions?.[1]] ?? 0;
const percentage = calculatePercentage(value, total);
return `{title|${name}}\n{detail|${value}\t${percentage}}`;
}
};
// tooltip formatter
merged.tooltip = {
...merged.tooltip,
formatter: (params: any) => {
const name = params.name;
const item = dataset.source?.find((it: any) => it[dataset.dimensions?.[0]] === name);
if (!item) return `${name}: 0(0.0%)`;
const value = item[dataset.dimensions?.[1]] ?? 0;
const percentage = calculatePercentage(value, total);
return `${name}: ${value}(${percentage})`;
}
};
return merged;
});
//
const calculateTotal = (data: any) => {
if (!data || !data.source) return 0
@ -76,48 +114,104 @@ const calculatePercentage = (value: number, total: number) => {
//
const dataHandle = (newData: any) => {
if (!newData) return
const total = calculateTotal(newData)
//
props.chartConfig.option.title.text = `${total}`
// 使
props.chartConfig.option.legend.formatter = (name: string) => {
const item = newData.source.find((it: any) => it[newData.dimensions[0]] === name)
if (!item) return name;
const value = item[newData.dimensions[1]] ?? 0
const percentage = calculatePercentage(value, total)
// const value = item[newData.dimensions[1]];
// const percentage = calculatePercentage(value, total);
return `{title|${name}}\n{detail|${value}\t${percentage}}`;
};
// tooplip
props.chartConfig.option.tooltip.formatter = (params: any) => {
//
const name = params.name;
const item = newData.source.find((it: any) => it[newData.dimensions[0]] === name);
if (!item) return `${name}: 0(0.0%)`;
const value = item[newData.dimensions[1]] ?? 0;
const percentage = calculatePercentage(value, total);
return `${name}: ${value}(${percentage})`;
};
props.chartConfig.option.dataset = newData;
}
// YYYY-MM-DD HH:mm:ss
function formatDateTime(date: Date): string {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0'); // 0+1
const day = date.getDate().toString().padStart(2, '0');
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
//
function generateTimeRange(period: string): { startTime: string; endTime: string } {
const now = new Date();
let startTime: Date;
let endTime: Date;
const handleBorderSelectChange = (newValue: string) => {
props.chartConfig.option.selectOption.selectValue = newValue // <---
console.log('选择器值已更改为:', newValue)
//
endTime = new Date(now);
endTime.setDate(endTime.getDate() + 1); //
endTime.setHours(0, 0, 0, 0); // 00:00:00
switch (period) {
case 'day':
startTime = new Date(now);
startTime.setHours(0, 0, 0, 0);
break;
case 'week':
// 7 00:00:00
startTime = new Date(now);
startTime.setDate(startTime.getDate() - 7);
startTime.setHours(0, 0, 0, 0);
break;
case 'month':
// 00:00:00
startTime = new Date(now.getFullYear(), now.getMonth(), 1);
startTime.setHours(0, 0, 0, 0);
break;
case 'quarter':
// 00:00:00
const currentMonth = now.getMonth();
const quarterStartMonth = Math.floor(currentMonth / 3) * 3; // (0, 3, 6, 9)
startTime = new Date(now.getFullYear(), quarterStartMonth, 1);
startTime.setHours(0, 0, 0, 0);
break;
case 'year':
// 00:00:00
startTime = new Date(now.getFullYear(), 0, 1); // 0
startTime.setHours(0, 0, 0, 0);
break;
default:
// 使 'day'
console.warn(`未知周期类型: ${period},默认使用 'day'`);
startTime = new Date(now);
startTime.setHours(0, 0, 0, 0);
break;
}
return {
startTime: formatDateTime(startTime),
endTime: formatDateTime(endTime),
};
}
const fetchAlarmData = async ({ startTime, endTime }: { startTime: string; endTime: string }) => {
try {
const res = await axiosInstance({
method: 'GET',
url: `/space/getNumberByAlarmLevel`,
params: { startTime, endTime },
responseType: 'json',
});
if (res && (res as any).value && Array.isArray((res as any).value)) {
return {
dimensions: props.chartConfig.option.dataset.dimensions, //
source: (res as any).value,
};
} else {
console.warn('API returned unexpected data structure:', res);
return undefined;
}
} catch (error) {
console.error('Error fetching alarm data:', error);
return undefined; // undefined
}
};
const handleBorderSelectChange = async (newValue: string) => {
props.chartConfig.option.selectOption.selectValue = newValue;
const { startTime, endTime } = generateTimeRange(newValue);
const fetchedData = await fetchAlarmData({ startTime, endTime });
if (fetchedData) {
dataHandle(fetchedData);
} else {
dataHandle(undefined); //
}
};
//
watch(
() => props.chartConfig.option.dataset,
(newData) => {
@ -129,7 +223,7 @@ watch(
},
{
immediate: true,
deep: false
deep: true
}
)
@ -137,8 +231,10 @@ const { vChartRef } = useChartDataFetch(props.chartConfig, useChartEditStore, (n
props.chartConfig.option.dataset = newData
dataHandle(newData)
})
onMounted(() => {
dataHandle(props.chartConfig.option.dataset)
onMounted(async () => {
const timeRange = generateTimeRange(option.value.selectOption.selectValue);
const fetchedData = await fetchAlarmData(timeRange);
dataHandle(fetchedData);
})
</script>

View File

@ -222,8 +222,9 @@
{{ props.titleText }}
</span>
<span class="right-select">
<ConsumSelect v-show="props.selectOption.show" :options="props.selectOption.dataset"
:selectedValue="props.selectOption.selectValue" @change="handleSelectChange" />
<ConsumSelect v-show="selectOption.show" :options="selectOption.dataset"
:selectedValue="selectOption.selectValue" @change="handleSelectChange"
:select-style-config="selectStyleConfig" />
</span>
</div>
<div class="body">
@ -282,6 +283,11 @@ const props = defineProps({
}
]
})
}, selectStyleConfig: {
default: () => ({
showAngle: false
}),
required: false
}
})
const emits = defineEmits(['change'])
@ -296,7 +302,7 @@ const handleSelectChange = (value: any) => {
height: 100%;
// --- ---
$border-outside-padding: 5px; //
$bg-top-offset: 40px; // SVGtop
$bg-top-offset: 0px; // SVGtop
padding: $border-outside-padding;
box-sizing: border-box;
@ -329,6 +335,7 @@ const handleSelectChange = (value: any) => {
// = 100% - top
height: calc(100% - #{$bg-top-offset});
object-fit: scale-down;
z-index: -2;
}
}
@ -345,7 +352,7 @@ const handleSelectChange = (value: any) => {
// background-color: antiquewhite;
.header {
margin-top: 8px;
margin-top: 5px;
margin-bottom: 0px;
padding: 0 20px;
display: flex;
@ -362,7 +369,7 @@ const handleSelectChange = (value: any) => {
}
.body {
margin-top: 10px;
margin-top: 0px;
flex: 1;
min-height: 0;
overflow: hidden;

View File

@ -1,17 +1,15 @@
<template>
<div class="custom-select" @click="toggleDropdown">
<div class="select-display">
<div class="select-display" :style="selectStyleConfig">
<span class="select-text">{{ getSelectedLabel() }}</span>
<span class="select-arrow" :class="{ 'arrow-up': isDropdownOpen }"></span>
<span v-if="selectStyleConfig.showAngle" class="select-arrow"
:class="{ 'arrow-up': isDropdownOpen }"></span>
</div>
<div class="select-dropdown" v-show="isDropdownOpen">
<div
v-for="item in options"
:key="item.value"
class="select-option"
:class="{ 'selected': item.value === selectedValue }"
@click.stop="selectOption(item)"
>
<div class="select-dropdown" v-show="isDropdownOpen"
:style="{ backgroundColor: selectStyleConfig.backgroundColor }">
<div v-for="item in options" :key="item.value"
:style="{ backgroundColor: selectStyleConfig.backgroundColor }" class="select-option"
:class="{ 'selected': item.value === selectedValue }" @click.stop="selectOption(item)">
{{ item.label }}
</div>
</div>
@ -24,7 +22,8 @@ import { ref, onMounted, onUnmounted } from 'vue'
// props
const props = defineProps<{
options: Array<{ label: string; value: any }>
selectedValue: any
selectedValue: any,
selectStyleConfig: any
}>()
// emits
@ -107,7 +106,7 @@ onUnmounted(() => {
margin-left: 8px;
font-size: 10px;
transition: transform 0.3s ease;
&.arrow-up {
transform: rotate(180deg);
}
@ -117,7 +116,8 @@ onUnmounted(() => {
position: absolute;
top: 100%;
left: 0;
right: 0;
width: fit-content; //
min-width: 100%; // .select-display
background-color: rgb(48, 110, 100);
border-radius: 6px;
margin-top: 2px;
@ -131,16 +131,18 @@ onUnmounted(() => {
color: #fff;
cursor: pointer;
transition: background-color 0.2s ease;
width: 100%;
text-wrap: nowrap;
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
&.selected {
background-color: rgba(255, 255, 255, 0.2);
font-weight: bold;
}
&:not(:last-child) {
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
@ -151,9 +153,10 @@ onUnmounted(() => {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
</style>