fix:代码优化-方法封装-拖拽保存-大小自适应

This commit is contained in:
zhongzm
2024-10-14 18:52:47 +08:00
parent ba98b37306
commit bf8d7f2124

View File

@@ -1,44 +1,43 @@
<script setup lang="ts"> <script setup lang="ts">
import draggable from 'vuedraggable'; import draggable from 'vuedraggable';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { onMounted, reactive, ref, markRaw, nextTick, onUnmounted } from 'vue'; import { onMounted, reactive, ref, markRaw, nextTick, onUnmounted} from 'vue';
import { import { RESULT_CODE_ERROR, RESULT_CODE_SUCCESS } from '@/constants/result-constants';
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import { SizeType } from 'ant-design-vue/es/config-provider'; import { SizeType } from 'ant-design-vue/es/config-provider';
import { listKPIData, getKPITitle } from '@/api/perfManage/goldTarget'; import { listKPIData, getKPITitle } from '@/api/perfManage/goldTarget';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr } from '@/utils/date-utils';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import useNeInfoStore from '@/store/modules/neinfo'; import useNeInfoStore from '@/store/modules/neinfo';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { ColumnsType } from 'ant-design-vue/es/table'; import { ColumnsType } from 'ant-design-vue/es/table';
import { generateColorRGBA } from '@/utils/generate-utils'; import { generateColorRGBA } from '@/utils/generate-utils';
import { LineSeriesOption } from 'echarts/charts'; import { LineSeriesOption } from 'echarts/charts';
import { OptionsType, WS } from '@/plugins/ws-websocket'; import { OptionsType, WS } from '@/plugins/ws-websocket';
import { Switch } from 'ant-design-vue'; import { Switch } from 'ant-design-vue';
const { t, currentLocale } = useI18n();
const { t, currentLocale } = useI18n();
const neInfoStore = useNeInfoStore(); const neInfoStore = useNeInfoStore();
//WebSocket连接 //WebSocket连接
//const ws = new WS();
const ws = ref<WS | null>(null); const ws = ref<WS | null>(null);
//实时数据开关
const handleRealTimeSwitch = (checked: any, event: Event) => {
console.log('Switch toggled:', checked);
fnRealTimeSwitch(!!checked);
};
//添加实时数据开关状态 //添加实时数据开关状态
const realTimeEnabled = ref(false); const realTimeEnabled = ref(false);
//实时数据开关
const handleRealTimeSwitch = (checked: any) => {
fnRealTimeSwitch(!!checked);
};
// 网元数组
const networkElementTypes = ['udm', 'upf', 'amf', 'smf', 'ims','ausf'] as const;
// 定义 ChartType
type ChartType = typeof networkElementTypes[number];
// 定义图表类型
type ChartType = 'udm' | 'upf' | 'amf' | 'smf';
//构建响应式数组储存图表类型数据 //构建响应式数组储存图表类型数据
const chartOrder = ref(['udm', 'upf', 'amf', 'smf']); const chartOrder = ref([...networkElementTypes]);
// 定义表格状态类型 // 定义表格状态类型
type TableStateType = { type TableStateType = {
@@ -49,78 +48,59 @@ type TableStateType = {
selectedRowKeys: (string | number)[]; selectedRowKeys: (string | number)[];
}; };
// 创建可复用的状态 // 创建可复用的状态
const createChartState = () => ({ const createChartState = () => {
chartDom: ref<HTMLElement | null>(null), const chartDom = ref<HTMLElement | null>(null);
chart: ref<echarts.ECharts | null>(null), const chart = ref<echarts.ECharts | null>(null);
tableColumns: ref<ColumnsType>([]), const observer = ref<ResizeObserver | null>(null);
tableState: reactive<TableStateType>({
loading: false, return {
size: 'small', chartDom,
seached: true, chart,
data: [], observer,
selectedRowKeys: [], tableColumns: ref<ColumnsType>([]),
}), tableState: reactive<TableStateType>({
chartLegendSelected: {} as Record<string, boolean>, loading: false,
chartDataXAxisData: [] as string[], size: 'small',
chartDataYSeriesData: [] as CustomSeriesOption[], seached: true,
}); data: [],
selectedRowKeys: [],
}),
chartLegendSelected: {} as Record<string, boolean>,
chartDataXAxisData: [] as string[],
chartDataYSeriesData: [] as CustomSeriesOption[],
};
};
// 为每种图表类型创建状态 // 为每种图表类型创建状态
const chartStates: Record<ChartType, ReturnType<typeof createChartState>> = { const chartStates: Record<ChartType, ReturnType<typeof createChartState>> = Object.fromEntries(
udm: createChartState(), networkElementTypes.map(type => [type, createChartState()])
upf: createChartState(), ) as Record<ChartType, ReturnType<typeof createChartState>>;
amf: createChartState(),
smf: createChartState(),
};
//日期选择器 //日期选择器
interface RangePicker { interface RangePicker {
udm: [string, string]; [key: string]: [string, string] | Record<string, [Dayjs, Dayjs]>;
upf: [string, string];
amf: [string, string];
smf: [string, string];
placeholder: [string, string]; placeholder: [string, string];
ranges: Record<string, [Dayjs, Dayjs]>; ranges: Record<string, [Dayjs, Dayjs]>;
} }
// 创建日期选择器状态 // 创建日期选择器状态
const rangePicker = reactive<RangePicker>({ const rangePicker = reactive<RangePicker>({
udm: [ ...Object.fromEntries(networkElementTypes.map(type => [
dayjs('2024-09-20 00:00:00').valueOf().toString(), type,
dayjs('2024-09-20 23:59:59').valueOf().toString(), [
], dayjs('2024-09-20 00:00:00').valueOf().toString(),//模拟数据日期为9月20日数据
upf: [ dayjs('2024-09-20 23:59:59').valueOf().toString()
dayjs('2024-09-20 00:00:00').valueOf().toString(), ]
dayjs('2024-09-20 23:59:59').valueOf().toString(), ])),
], placeholder: [t('views.monitor.monitor.startTime'), t('views.monitor.monitor.endTime')] as [string, string],
amf: [
dayjs('2024-09-20 00:00:00').valueOf().toString(),
dayjs('2024-09-20 23:59:59').valueOf().toString(),
],
smf: [
dayjs('2024-09-20 00:00:00').valueOf().toString(),
dayjs('2024-09-20 23:59:59').valueOf().toString(),
],
placeholder: [
t('views.monitor.monitor.startTime'),
t('views.monitor.monitor.endTime'),
] as [string, string],
ranges: { ranges: {
[t('views.monitor.monitor.yesterday')]: [ [t('views.monitor.monitor.yesterday')]: [dayjs().subtract(1, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day')],
dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
],
[t('views.monitor.monitor.today')]: [dayjs().startOf('day'), dayjs()], [t('views.monitor.monitor.today')]: [dayjs().startOf('day'), dayjs()],
[t('views.monitor.monitor.week')]: [ [t('views.monitor.monitor.week')]: [dayjs().startOf('week'), dayjs().endOf('week')],
dayjs().startOf('week'), [t('views.monitor.monitor.month')]: [dayjs().startOf('month'), dayjs().endOf('month')],
dayjs().endOf('week'), } as Record<string, [Dayjs, Dayjs]>,
],
[t('views.monitor.monitor.month')]: [
dayjs().startOf('month'),
dayjs().endOf('month'),
],
},
}); });
// 创建可复用的图表初始化函数 // 创建可复用的图表初始化函数
@@ -129,11 +109,12 @@ const initChart = (type: ChartType) => {
const state = chartStates[type]; const state = chartStates[type];
const container = state.chartDom.value; const container = state.chartDom.value;
if (!container) return; if (!container) return;
state.chart.value = markRaw(echarts.init(container, 'light')); state.chart.value = markRaw(echarts.init(container, 'light'));
const option: echarts.EChartsOption = { const option: echarts.EChartsOption = {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
position: function (pt: any) { position: function(pt: any) {
return [pt[0], '10%']; return [pt[0], '10%'];
}, },
}, },
@@ -176,29 +157,45 @@ const initChart = (type: ChartType) => {
series: [], series: [],
}; };
state.chart.value.setOption(option); state.chart.value.setOption(option);
// 创建 ResizeObserver 实例
state.observer.value = new ResizeObserver(() => {
if (state.chart.value) {
state.chart.value.resize();
}
});
// 开始观察图表容器
state.observer.value.observe(container);
}); });
}; };
//结束拖拽事件 //结束拖拽事件
const onDragEnd = () => { const onDragEnd = () => {
nextTick(() => { nextTick(() => {
chartOrder.value.forEach(type => { chartOrder.value.forEach((type) => {
const state = chartStates[type as ChartType]; const state = chartStates[type as ChartType];
if (state.chart.value) { if (state.chart.value) {
state.chart.value.dispose(); // 销毁旧的图表实例 state.chart.value.resize(); // 调整图表大小
// 重新设置图表选项,保留原有数据
state.chart.value.setOption({
xAxis: { data: state.chartDataXAxisData },
series: state.chartDataYSeriesData,
});
} }
initChart(type as ChartType);
fetchData(type as ChartType); // 重新获取数据并渲染图表
}); });
}); });
}; };
// 创建可复用的数据获取函数 // 创建可复用的数据获取函数
const fetchData = async (type: ChartType) => { const fetchData = async (type: ChartType) => {
const state = chartStates[type]; const state = chartStates[type];
const neId = '001'; const neId = '001';
state.tableState.loading = true; state.tableState.loading = true;
try { try {
const [startTime, endTime] = rangePicker[type]; const dateRange = rangePicker[type] as [string, string];
const [startTime, endTime] = dateRange;
const res = await listKPIData({ const res = await listKPIData({
neType: type.toUpperCase(), neType: type.toUpperCase(),
neId, neId,
@@ -209,8 +206,9 @@ const fetchData = async (type: ChartType) => {
interval: 5, interval: 5,
}); });
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
state.tableState.data = res.data; state.tableState.data = res.data;
nextTick(() => { nextTick(()=> {
renderChart(type); renderChart(type);
}); });
} }
@@ -224,11 +222,11 @@ const fetchData = async (type: ChartType) => {
//建立实时数据连接 //建立实时数据连接
function fnRealTimeSwitch(bool: boolean) { function fnRealTimeSwitch(bool: boolean) {
console.log('fnRealTimeSwitch called with:', bool);
realTimeEnabled.value = bool; realTimeEnabled.value = bool;
if (bool) { if (bool) {
if (!ws.value) { if(!ws.value){
console.log('Creating new WS instance');
ws.value = new WS(); ws.value = new WS();
} }
Object.values(chartStates).forEach(state => { Object.values(chartStates).forEach(state => {
@@ -238,21 +236,15 @@ function fnRealTimeSwitch(bool: boolean) {
const options: OptionsType = { const options: OptionsType = {
url: '/ws', url: '/ws',
params: { params: {
subGroupID: Object.keys(chartStates) subGroupID: networkElementTypes.map(type => `10_${type.toUpperCase()}_001`).join(','),
.map(type => `10_${type.toUpperCase()}_001`)
.join(','),
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: wsError, onerror: wsError,
onopen: () => {
console.log('WebSocket connection established');
},
}; };
console.log('Attempting to connect with options:', options);
ws.value.connect(options); ws.value.connect(options);
console.log('Connection attempt initiated'); } else if(ws.value){
} else if (ws.value) {
console.log('Closing WebSocket connection');
Object.values(chartStates).forEach(state => { Object.values(chartStates).forEach(state => {
state.tableState.seached = true; state.tableState.seached = true;
}); });
@@ -262,8 +254,8 @@ function fnRealTimeSwitch(bool: boolean) {
} }
// 接收数据后错误回调 // 接收数据后错误回调
function wsError(ev: any) { function wsError() {
console.error('WebSocket error:', ev);
message.error(t('common.websocketError')); message.error(t('common.websocketError'));
} }
@@ -282,9 +274,9 @@ function wsMessage(res: Record<string, any>) {
} }
// 处理四个图表的数据 // 处理四个图表的数据
(Object.keys(chartStates) as ChartType[]).forEach(type => { networkElementTypes.forEach((type) => {
const state = chartStates[type]; const state = chartStates[type];
const kpiEvent = data.data[type.toUpperCase()]; const kpiEvent:any = data.data[type.toUpperCase()];
if (kpiEvent) { if (kpiEvent) {
// 更新 X 轴数据 // 更新 X 轴数据
@@ -326,25 +318,22 @@ interface CustomSeriesOption extends Omit<LineSeriesOption, 'data'> {
} }
// 创建可复用的图表渲染函数 // 创建可复用的图表渲染函数
const renderChart = (type: ChartType) => { const renderChart = (type: ChartType) => {
const state = chartStates[type]; const state = chartStates[type];
if (state.chart.value == null || state.tableState.data.length <= 0) { if (state.chart.value == null ) {
return; return;
} }
// 重置数据 // 重置数据
state.chartLegendSelected = {}; state.chartLegendSelected = {};
state.chartDataXAxisData = []; state.chartDataXAxisData = [];
state.chartDataYSeriesData = []; state.chartDataYSeriesData = [];
// 处理数据 // 处理数据
for (const columns of state.tableColumns.value) { for (const columns of state.tableColumns.value) {
if (['neName', 'startIndex', 'timeGroup'].includes(columns.key as string)) if (['neName', 'startIndex', 'timeGroup'].includes(columns.key as string)) continue;
continue;
const color = generateColorRGBA(); const color = generateColorRGBA();
state.chartDataYSeriesData.push({ state.chartDataYSeriesData.push({
name: columns.title as string, name: columns.title as string,
customKey: columns.key as string, customKey: columns.key as string,
//key: columns.key as string,
type: 'line', type: 'line',
symbol: 'none', symbol: 'none',
sampling: 'lttb', sampling: 'lttb',
@@ -358,8 +347,6 @@ const renderChart = (type: ChartType) => {
data: [], data: [],
} as CustomSeriesOption); } as CustomSeriesOption);
state.chartLegendSelected[columns.title as string] = true; state.chartLegendSelected[columns.title as string] = true;
//});
//state.chartLegendSelected[columns.title as string] = true;
} }
const orgData = [...state.tableState.data].reverse(); const orgData = [...state.tableState.data].reverse();
@@ -370,12 +357,14 @@ const renderChart = (type: ChartType) => {
y.data.push(+item[y.customKey as string]); y.data.push(+item[y.customKey as string]);
} }
} }
// 更新图表 // 更新图表
state.chart.value.setOption( state.chart.value.setOption(
{ {
legend: { selected: state.chartLegendSelected }, legend: { selected: state.chartLegendSelected },
xAxis: { data: state.chartDataXAxisData }, xAxis: { data: state.chartDataXAxisData,
type: 'category',
boundaryGap: false,
},
series: state.chartDataYSeriesData, series: state.chartDataYSeriesData,
dataZoom: [ dataZoom: [
{ {
@@ -393,12 +382,10 @@ const renderChart = (type: ChartType) => {
); );
}; };
// 获取表头数据 // 获取表头数据
const fetchKPITitle = async (type: ChartType) => { const fetchKPITitle = async (type: ChartType) => {
const language = const language = currentLocale.value.split('_')[0] === 'zh' ? 'cn' : currentLocale.value.split('_')[0];
currentLocale.value.split('_')[0] === 'zh'
? 'cn'
: currentLocale.value.split('_')[0];
try { try {
const res = await getKPITitle(type.toUpperCase()); const res = await getKPITitle(type.toUpperCase());
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
@@ -423,22 +410,26 @@ const fetchKPITitle = async (type: ChartType) => {
onMounted(async () => { onMounted(async () => {
ws.value = new WS(); ws.value = new WS();
await neInfoStore.fnNelist(); await neInfoStore.fnNelist();
for (const type of Object.keys(chartStates) as ChartType[]) { for (const type of networkElementTypes) {
await fetchKPITitle(type); await fetchKPITitle(type);
initChart(type); initChart(type);
fetchData(type); fetchData(type);
} }
}); });
// 在组件卸载时销毁图表实例 // 在组件卸载时销毁图表实例
onUnmounted(() => { onUnmounted(() => {
if (ws.value && ws.value.state() === WebSocket.OPEN) { if(ws.value &&ws.value.state()===WebSocket.OPEN) {
ws.value.close(); ws.value.close();
} }
Object.values(chartStates).forEach(state => { Object.values(chartStates).forEach((state) => {
if (state.chart.value) { if (state.chart.value) {
state.chart.value.dispose(); state.chart.value.dispose();
} }
if (state.observer.value) {
state.observer.value.disconnect();
}
}); });
}); });
</script> </script>
@@ -451,9 +442,7 @@ onUnmounted(() => {
v-model:checked="realTimeEnabled" v-model:checked="realTimeEnabled"
@change="handleRealTimeSwitch as any" @change="handleRealTimeSwitch as any"
/> />
<span class="switch-label">{{ <span class="switch-label">{{ realTimeEnabled ? t('views.dashboard.cdr.realTimeDataStart') : t('views.dashboard.cdr.realTimeDataStop') }}</span>
realTimeEnabled ? '实时数据已开启' : '实时数据已关闭'
}}</span>
</div> </div>
</div> </div>
<draggable <draggable
@@ -462,14 +451,12 @@ onUnmounted(() => {
item-key="type" item-key="type"
@end="onDragEnd" @end="onDragEnd"
class="row" class="row"
onscroll="false" onscroll='false'
> >
<template #item="{ element: type }"> <template #item="{element:type}">
<div class="col-lg-6 col-md=-6 col-xs-12"> <div class="col-lg-6 col-md=-6 col-xs-12">
<a-card <a-card :bordered="false" :body-style="{ marginBottom: '24px', padding: '24px'}">
:bordered="false"
:body-style="{ marginBottom: '24px', padding: '24px' }"
>
<template #title>{{ type.toUpperCase() }}</template> <template #title>{{ type.toUpperCase() }}</template>
<template #extra> <template #extra>
<a-range-picker <a-range-picker
@@ -485,14 +472,12 @@ onUnmounted(() => {
@change="() => fetchData(type)" @change="() => fetchData(type)"
></a-range-picker> ></a-range-picker>
</template> </template>
<div class="chart" style="padding: 12px"> <div class='chart' style="padding: 12px">
<div <div :ref="el => { if (el) chartStates[type as ChartType].chartDom.value = el as HTMLElement }" style="height:400px;width:100%"></div>
:ref="el => { if (el) chartStates[type as ChartType].chartDom.value = el as HTMLElement }"
style="height: 400px; width: 100%"
></div>
</div> </div>
</a-card> </a-card>
</div> </div>
</template> </template>
</draggable> </draggable>
</PageContainer> </PageContainer>
@@ -503,6 +488,7 @@ onUnmounted(() => {
width: 100%; width: 100%;
height: 450px; height: 450px;
} }
.sortable-ghost { .sortable-ghost {
opacity: 0.5; opacity: 0.5;
background: #c8ebfb; background: #c8ebfb;
@@ -512,30 +498,19 @@ onUnmounted(() => {
opacity: 0.8; opacity: 0.8;
background: #f4f4f4; background: #f4f4f4;
} }
.row { .row {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
margin-right: -12px; margin: -12px;
margin-left: -12px;
} }
.col-lg-6 { .col-lg-6 {
flex: 0 0 50%; flex: 0 0 50%;
max-width: 50%; max-width: 50%;
padding-right: 12px; padding: 12px;
padding-left: 12px;
}
@media (max-width: 991px) {
.col-md-6 {
flex: 0 0 50%;
max-width: 50%;
}
}
@media (max-width: 575px) {
.col-xs-12 {
flex: 0 0 100%;
max-width: 100%;
}
} }
.control-row { .control-row {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;