style: KPI数据表格头提示信息
This commit is contained in:
@@ -53,8 +53,6 @@ const route = useRoute();
|
|||||||
const { t, currentLocale } = useI18n();
|
const { t, currentLocale } = useI18n();
|
||||||
const ws = new WS();
|
const ws = new WS();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
echarts.use([
|
echarts.use([
|
||||||
TooltipComponent,
|
TooltipComponent,
|
||||||
GridComponent,
|
GridComponent,
|
||||||
@@ -449,20 +447,19 @@ function fnGetList() {
|
|||||||
return item[columns.key] ? Number(item[columns.key]) : 0;
|
return item[columns.key] ? Number(item[columns.key]) : 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// 计算总值
|
// 计算总值
|
||||||
const total = Number(values.reduce((sum, val) => sum + val, 0).toFixed(2));
|
const total = Number(
|
||||||
|
values.reduce((sum, val) => sum + val, 0).toFixed(2)
|
||||||
|
);
|
||||||
|
|
||||||
// 计算平均值
|
// 计算平均值
|
||||||
const avg = values.length > 0
|
const avg = values.length > 0 ? Number((total / values.length).toFixed(2)) : 0;
|
||||||
? Number((total / values.length).toFixed(2))
|
|
||||||
: 0;
|
|
||||||
kpiStats.value.push({
|
kpiStats.value.push({
|
||||||
kpiId: columns.key,
|
kpiId: columns.key,
|
||||||
title: columns.title,
|
title: columns.title,
|
||||||
max: values.length > 0 ? Math.max(...values) : 0,
|
max: values.length > 0 ? Math.max(...values) : 0,
|
||||||
min: values.length > 0 ? Math.min(...values) : 0,
|
min: values.length > 0 ? Math.min(...values) : 0,
|
||||||
avg
|
avg,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -487,16 +484,19 @@ function fnRanderChart() {
|
|||||||
return [pt[0], '10%'];
|
return [pt[0], '10%'];
|
||||||
},
|
},
|
||||||
confine: true, // 限制 tooltip 显示范围
|
confine: true, // 限制 tooltip 显示范围
|
||||||
backgroundColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
backgroundColor:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? 'rgba(48, 48, 48, 0.8)'
|
? 'rgba(48, 48, 48, 0.8)'
|
||||||
: 'rgba(255, 255, 255, 0.9)',
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
borderColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
borderColor:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#555'
|
? '#555'
|
||||||
: '#ddd',
|
: '#ddd',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
@@ -633,29 +633,31 @@ function fnRanderChartData() {
|
|||||||
type: 'category',
|
type: 'category',
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: getSplitLineColor()
|
color: getSplitLineColor(),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
data: chartDataXAxisData,
|
data: chartDataXAxisData,
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: getSplitLineColor()
|
color: getSplitLineColor(),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
series: chartDataYSeriesData,
|
series: chartDataYSeriesData,
|
||||||
@@ -784,59 +786,49 @@ function handleRowClick(record: any) {
|
|||||||
// 监听主题变化
|
// 监听主题变化
|
||||||
watch(
|
watch(
|
||||||
() => layoutStore.proConfig.theme, // 监听的值
|
() => layoutStore.proConfig.theme, // 监听的值
|
||||||
(newValue) => {
|
newValue => {
|
||||||
if (kpiChart.value) {
|
if (kpiChart.value) {
|
||||||
const splitLineColor = getSplitLineColor();
|
const splitLineColor = getSplitLineColor();
|
||||||
// 绘制图数据
|
// 绘制图数据
|
||||||
kpiChart.value.setOption(
|
kpiChart.value.setOption({
|
||||||
{
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
position: function (pt: any) {
|
position: function (pt: any) {
|
||||||
return [pt[0], '10%'];
|
return [pt[0], '10%'];
|
||||||
},
|
},
|
||||||
confine: true, // 限制 tooltip 显示范围
|
confine: true, // 限制 tooltip 显示范围
|
||||||
backgroundColor: newValue === 'dark'
|
backgroundColor:
|
||||||
|
newValue === 'dark'
|
||||||
? 'rgba(48, 48, 48, 0.8)'
|
? 'rgba(48, 48, 48, 0.8)'
|
||||||
: 'rgba(255, 255, 255, 0.9)',
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
borderColor: newValue === 'dark'
|
borderColor: newValue === 'dark' ? '#555' : '#ddd',
|
||||||
? '#555'
|
|
||||||
: '#ddd',
|
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: newValue === 'dark'
|
color: newValue === 'dark' ? '#CACADA' : '#333',
|
||||||
? '#CACADA'
|
|
||||||
: '#333'
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: newValue === 'dark'
|
color: newValue === 'dark' ? '#CACADA' : '#333',
|
||||||
? '#CACADA'
|
|
||||||
: '#333'
|
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: splitLineColor
|
color: splitLineColor,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: newValue === 'dark'
|
color: newValue === 'dark' ? '#CACADA' : '#333',
|
||||||
? '#CACADA'
|
|
||||||
: '#333'
|
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: splitLineColor
|
color: splitLineColor,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -924,28 +916,50 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<a-card v-show="tableState.seached" :bordered="false" :body-style="{ marginBottom: '24px', paddingBottom: 0 }">
|
<a-card
|
||||||
|
v-show="tableState.seached"
|
||||||
|
:bordered="false"
|
||||||
|
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||||
|
>
|
||||||
<!-- 表格搜索栏 -->
|
<!-- 表格搜索栏 -->
|
||||||
<a-form :model="queryParams" name="queryParamsFrom" layout="horizontal">
|
<a-form :model="queryParams" name="queryParamsFrom" layout="horizontal">
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :lg="6" :md="12" :xs="24">
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
<a-form-item name="neType" :label="t('views.ne.common.neType')">
|
<a-form-item name="neType" :label="t('views.ne.common.neType')">
|
||||||
<a-cascader v-model:value="state.neType" :options="neCascaderOptions" :allow-clear="false"
|
<a-cascader
|
||||||
:placeholder="t('common.selectPlease')" />
|
v-model:value="state.neType"
|
||||||
|
:options="neCascaderOptions"
|
||||||
|
:allow-clear="false"
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="10" :md="12" :xs="24">
|
<a-col :lg="10" :md="12" :xs="24">
|
||||||
<a-form-item :label="t('views.perfManage.goldTarget.timeFrame')" name="timeFrame">
|
<a-form-item
|
||||||
<a-range-picker v-model:value="queryRangePicker" bordered :allow-clear="false"
|
:label="t('views.perfManage.goldTarget.timeFrame')"
|
||||||
:show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" value-format="x" :presets="ranges"
|
name="timeFrame"
|
||||||
style="width: 100%"></a-range-picker>
|
>
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="queryRangePicker"
|
||||||
|
bordered
|
||||||
|
:allow-clear="false"
|
||||||
|
:show-time="{ format: 'HH:mm:ss' }"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="x"
|
||||||
|
:presets="ranges"
|
||||||
|
style="width: 100%"
|
||||||
|
></a-range-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
||||||
<a-col :lg="2" :md="12" :xs="24">
|
<a-col :lg="2" :md="12" :xs="24">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-space :size="8">
|
<a-space :size="8">
|
||||||
<a-button type="primary" :loading="tableState.loading" @click.prevent="fnGetListTitle()">
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
@click.prevent="fnGetListTitle()"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<SearchOutlined />
|
<SearchOutlined />
|
||||||
</template>
|
</template>
|
||||||
@@ -962,7 +976,11 @@ onBeforeUnmount(() => {
|
|||||||
<!-- 插槽-卡片左侧侧 -->
|
<!-- 插槽-卡片左侧侧 -->
|
||||||
<template #title>
|
<template #title>
|
||||||
<a-space :size="8" align="center">
|
<a-space :size="8" align="center">
|
||||||
<a-button type="primary" :loading="tableState.loading" @click.prevent="fnChangShowType()">
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
@click.prevent="fnChangShowType()"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<AreaChartOutlined />
|
<AreaChartOutlined />
|
||||||
</template>
|
</template>
|
||||||
@@ -972,8 +990,12 @@ onBeforeUnmount(() => {
|
|||||||
: t('views.perfManage.goldTarget.kpiTableTitle')
|
: t('views.perfManage.goldTarget.kpiTableTitle')
|
||||||
}}
|
}}
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="dashed" :loading="tableState.loading" @click.prevent="fnRecordExport()"
|
<a-button
|
||||||
v-show="tableState.showTable">
|
type="dashed"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
@click.prevent="fnRecordExport()"
|
||||||
|
v-show="tableState.showTable"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<ExportOutlined />
|
<ExportOutlined />
|
||||||
</template>
|
</template>
|
||||||
@@ -993,8 +1015,12 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<TableColumnsDnd v-if="tableColumns.length > 0" :cache-id="`kpiTarget_${state.neType[0]}`"
|
<TableColumnsDnd
|
||||||
:columns="tableColumns" v-model:columns-dnd="tableColumnsDnd"></TableColumnsDnd>
|
v-if="tableColumns.length > 0"
|
||||||
|
:cache-id="`kpiTarget_${state.neType[0]}`"
|
||||||
|
:columns="tableColumns"
|
||||||
|
v-model:columns-dnd="tableColumnsDnd"
|
||||||
|
></TableColumnsDnd>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template #title>{{ t('common.sizeText') }}</template>
|
<template #title>{{ t('common.sizeText') }}</template>
|
||||||
<a-dropdown trigger="click" placement="bottomRight">
|
<a-dropdown trigger="click" placement="bottomRight">
|
||||||
@@ -1004,7 +1030,10 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<a-menu :selected-keys="[tableState.size as string]" @click="fnTableSize">
|
<a-menu
|
||||||
|
:selected-keys="[tableState.size as string]"
|
||||||
|
@click="fnTableSize"
|
||||||
|
>
|
||||||
<a-menu-item key="default">
|
<a-menu-item key="default">
|
||||||
{{ t('common.size.default') }}
|
{{ t('common.size.default') }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@@ -1033,32 +1062,108 @@ onBeforeUnmount(() => {
|
|||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</a-form-item> -->
|
</a-form-item> -->
|
||||||
<a-form-item :label="t('views.perfManage.goldTarget.realTimeData')" name="chartRealTime">
|
<a-form-item
|
||||||
<a-switch :disabled="tableState.loading" v-model:checked="state.chartRealTime"
|
:label="t('views.perfManage.goldTarget.realTimeData')"
|
||||||
:checked-children="t('common.switch.open')" :un-checked-children="t('common.switch.shut')"
|
name="chartRealTime"
|
||||||
@change="fnRealTimeSwitch" size="small" />
|
>
|
||||||
|
<a-switch
|
||||||
|
:disabled="tableState.loading"
|
||||||
|
v-model:checked="state.chartRealTime"
|
||||||
|
:checked-children="t('common.switch.open')"
|
||||||
|
:un-checked-children="t('common.switch.shut')"
|
||||||
|
@change="fnRealTimeSwitch"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 表格列表 -->
|
<!-- 表格列表 -->
|
||||||
<a-table v-show="tableState.showTable" class="table" row-key="id" :columns="tableColumnsDnd"
|
<a-table
|
||||||
:loading="tableState.loading" :data-source="tableState.data" :size="tableState.size"
|
v-show="tableState.showTable"
|
||||||
:pagination="tablePagination" :scroll="{ x: tableColumnsDnd.length * 200, y: 'calc(100vh - 480px)' }"
|
class="table"
|
||||||
@resizeColumn="(w: number, col: any) => (col.width = w)" :show-expand-column="false" @change="fnTableChange">
|
row-key="id"
|
||||||
|
:columns="tableColumnsDnd"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
:data-source="tableState.data"
|
||||||
|
:size="tableState.size"
|
||||||
|
:pagination="tablePagination"
|
||||||
|
:scroll="{ x: tableColumnsDnd.length * 200, y: 'calc(100vh - 480px)' }"
|
||||||
|
@resizeColumn="(w: number, col: any) => (col.width = w)"
|
||||||
|
:show-expand-column="false"
|
||||||
|
@change="fnTableChange"
|
||||||
|
>
|
||||||
</a-table>
|
</a-table>
|
||||||
|
|
||||||
<!-- 图表 -->
|
<!-- 图表 -->
|
||||||
<div style="padding: 24px" v-show="!tableState.showTable">
|
<div style="padding: 24px" v-show="!tableState.showTable">
|
||||||
<div ref="kpiChartDom" class="chart-container" style="height: 450px; width: 100%"></div>
|
<div
|
||||||
|
ref="kpiChartDom"
|
||||||
|
class="chart-container"
|
||||||
|
style="height: 450px; width: 100%"
|
||||||
|
></div>
|
||||||
|
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<a-table :columns="statsColumns" :data-source="kpiStats" :pagination="false" :scroll="{ y: 250 }" size="small"
|
<a-table
|
||||||
:custom-row="record => ({
|
:columns="statsColumns"
|
||||||
|
:data-source="kpiStats"
|
||||||
|
:pagination="false"
|
||||||
|
:scroll="{ y: 250 }"
|
||||||
|
size="small"
|
||||||
|
:custom-row="
|
||||||
|
record => ({
|
||||||
onClick: () => handleRowClick(record),
|
onClick: () => handleRowClick(record),
|
||||||
class: selectedRow.includes(record.kpiId) ? 'selected-row' : ''
|
class: selectedRow.includes(record.kpiId) ? 'selected-row' : '',
|
||||||
})
|
})
|
||||||
" />
|
"
|
||||||
|
>
|
||||||
|
<template #headerCell="{ column }">
|
||||||
|
<template v-if="column.key === 'total'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.totalValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Sum within Time Range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'avg'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.avgValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Average value over the time range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'max'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.maxValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Maximum value in time range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'min'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.minValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Minimum value in the time range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
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, watch, h } from 'vue';
|
import {
|
||||||
import { RESULT_CODE_ERROR, RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
onMounted,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
markRaw,
|
||||||
|
nextTick,
|
||||||
|
onUnmounted,
|
||||||
|
watch,
|
||||||
|
h,
|
||||||
|
} from 'vue';
|
||||||
|
import {
|
||||||
|
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';
|
||||||
@@ -23,12 +35,19 @@ const { t, currentLocale } = useI18n();
|
|||||||
const neInfoStore = useNeInfoStore();
|
const neInfoStore = useNeInfoStore();
|
||||||
//日期快捷选择
|
//日期快捷选择
|
||||||
const ranges = ref([
|
const ranges = ref([
|
||||||
{label:t('views.perfManage.customTarget.sixHoursAgo'),value:[dayjs().subtract(6, 'hours'),
|
{
|
||||||
dayjs(),]},
|
label: t('views.perfManage.customTarget.sixHoursAgo'),
|
||||||
{label:t('views.perfManage.customTarget.threeHoursAgo'),value:[dayjs().subtract(3, 'hours'),
|
value: [dayjs().subtract(6, 'hours'), dayjs()],
|
||||||
dayjs(),]},
|
},
|
||||||
{label:t('views.monitor.monitor.today'),value:[dayjs().startOf('day'), dayjs()]},
|
{
|
||||||
])
|
label: t('views.perfManage.customTarget.threeHoursAgo'),
|
||||||
|
value: [dayjs().subtract(3, 'hours'), dayjs()],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('views.monitor.monitor.today'),
|
||||||
|
value: [dayjs().startOf('day'), dayjs()],
|
||||||
|
},
|
||||||
|
]);
|
||||||
//WebSocket连接
|
//WebSocket连接
|
||||||
const ws = ref<WS | null>(null);
|
const ws = ref<WS | null>(null);
|
||||||
|
|
||||||
@@ -40,13 +59,25 @@ const handleRealTimeSwitch = (checked: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 定义所有网元类型
|
// 定义所有网元类型
|
||||||
const ALL_NE_TYPES = ['ims', 'amf', 'udm', 'upf','smf', 'pcf', 'mme', 'mocngw', 'smsc', 'cbc', 'ausf'] as const;
|
const ALL_NE_TYPES = [
|
||||||
|
'ims',
|
||||||
|
'amf',
|
||||||
|
'udm',
|
||||||
|
'upf',
|
||||||
|
'smf',
|
||||||
|
'pcf',
|
||||||
|
'mme',
|
||||||
|
'mocngw',
|
||||||
|
'smsc',
|
||||||
|
'cbc',
|
||||||
|
'ausf',
|
||||||
|
] as const;
|
||||||
type AllChartType = (typeof ALL_NE_TYPES)[number] & string;
|
type AllChartType = (typeof ALL_NE_TYPES)[number] & string;
|
||||||
|
|
||||||
// 在 ALL_NE_TYPES 定义之后添加 小写转大写
|
// 在 ALL_NE_TYPES 定义之后添加 小写转大写
|
||||||
const neTypeOptions = ALL_NE_TYPES.map(type => ({
|
const neTypeOptions = ALL_NE_TYPES.map(type => ({
|
||||||
label: type.toUpperCase(),
|
label: type.toUpperCase(),
|
||||||
value: type
|
value: type,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 使用 ref 来使 networkElementTypes 变为响应式,并使用 ALL_NE_TYPES 初始化
|
// 使用 ref 来使 networkElementTypes 变为响应式,并使用 ALL_NE_TYPES 初始化
|
||||||
@@ -59,7 +90,9 @@ const selectedNeTypes = ref<AllChartType[]>([]);
|
|||||||
const latestSelectedTypes = ref<AllChartType[]>([]);
|
const latestSelectedTypes = ref<AllChartType[]>([]);
|
||||||
|
|
||||||
// watch 监控函数
|
// watch 监控函数
|
||||||
watch(selectedNeTypes, async (newTypes, oldTypes) => {
|
watch(
|
||||||
|
selectedNeTypes,
|
||||||
|
async (newTypes, oldTypes) => {
|
||||||
networkElementTypes.value = [...newTypes];
|
networkElementTypes.value = [...newTypes];
|
||||||
latestSelectedTypes.value = [...newTypes];
|
latestSelectedTypes.value = [...newTypes];
|
||||||
|
|
||||||
@@ -96,7 +129,8 @@ watch(selectedNeTypes, async (newTypes, oldTypes) => {
|
|||||||
for (const type of newTypes) {
|
for (const type of newTypes) {
|
||||||
try {
|
try {
|
||||||
// 只有当图表不存在或者是新增的类型时才初始化
|
// 只有当图表不存在或者是新增的类型时才初始化
|
||||||
const needsInit = !chartStates[type].chart.value || addedTypes.includes(type);
|
const needsInit =
|
||||||
|
!chartStates[type].chart.value || addedTypes.includes(type);
|
||||||
|
|
||||||
if (needsInit) {
|
if (needsInit) {
|
||||||
await fetchKPITitle(type);
|
await fetchKPITitle(type);
|
||||||
@@ -125,7 +159,9 @@ watch(selectedNeTypes, async (newTypes, oldTypes) => {
|
|||||||
const options: OptionsType = {
|
const options: OptionsType = {
|
||||||
url: '/ws',
|
url: '/ws',
|
||||||
params: {
|
params: {
|
||||||
subGroupID: newTypes.map(type => `10_${type.toUpperCase()}_001`).join(','),
|
subGroupID: newTypes
|
||||||
|
.map(type => `10_${type.toUpperCase()}_001`)
|
||||||
|
.join(','),
|
||||||
},
|
},
|
||||||
onmessage: wsMessage,
|
onmessage: wsMessage,
|
||||||
onerror: wsError,
|
onerror: wsError,
|
||||||
@@ -135,19 +171,24 @@ watch(selectedNeTypes, async (newTypes, oldTypes) => {
|
|||||||
|
|
||||||
// 保存选中的网元类型到本地存储
|
// 保存选中的网元类型到本地存储
|
||||||
localStorage.setItem('selectedNeTypes', JSON.stringify(newTypes));
|
localStorage.setItem('selectedNeTypes', JSON.stringify(newTypes));
|
||||||
}, { deep: true });
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
// 防抖函数
|
// 防抖函数
|
||||||
useDebounceFn(() => {
|
useDebounceFn(() => {
|
||||||
// 比较当前选择和最新选择
|
// 比较当前选择和最新选择
|
||||||
if (JSON.stringify(latestSelectedTypes.value) !== JSON.stringify(selectedNeTypes.value)) {
|
if (
|
||||||
|
JSON.stringify(latestSelectedTypes.value) !==
|
||||||
|
JSON.stringify(selectedNeTypes.value)
|
||||||
|
) {
|
||||||
// 如果不一致,以最新选择为准
|
// 如果不一致,以最新选择为准
|
||||||
selectedNeTypes.value = latestSelectedTypes.value;
|
selectedNeTypes.value = latestSelectedTypes.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTypes = selectedNeTypes.value;
|
const newTypes = selectedNeTypes.value;
|
||||||
// 确保 chartStates 包含新的网元类型
|
// 确保 chartStates 包含新的网元类型
|
||||||
newTypes.forEach((type) => {
|
newTypes.forEach(type => {
|
||||||
if (!chartStates[type]) {
|
if (!chartStates[type]) {
|
||||||
chartStates[type] = createChartState(type);
|
chartStates[type] = createChartState(type);
|
||||||
}
|
}
|
||||||
@@ -168,7 +209,7 @@ const initCharts = async () => {
|
|||||||
const savedChartStates = new Map(Object.entries(chartStates));
|
const savedChartStates = new Map(Object.entries(chartStates));
|
||||||
|
|
||||||
// 清除不再需要的图表
|
// 清除不再需要的图表
|
||||||
Object.keys(chartStates).forEach((key) => {
|
Object.keys(chartStates).forEach(key => {
|
||||||
if (!networkElementTypes.value.includes(key as AllChartType)) {
|
if (!networkElementTypes.value.includes(key as AllChartType)) {
|
||||||
const state = chartStates[key as AllChartType];
|
const state = chartStates[key as AllChartType];
|
||||||
if (state.chart.value) {
|
if (state.chart.value) {
|
||||||
@@ -226,7 +267,9 @@ const createChartState = (neType: AllChartType) => {
|
|||||||
const chartDom = ref<HTMLElement | null>(null);
|
const chartDom = ref<HTMLElement | null>(null);
|
||||||
const chart = ref<echarts.ECharts | null>(null);
|
const chart = ref<echarts.ECharts | null>(null);
|
||||||
const observer = ref<ResizeObserver | null>(null);
|
const observer = ref<ResizeObserver | null>(null);
|
||||||
const currentTheme = ref<string | null>(document.documentElement.getAttribute('data-theme'));
|
const currentTheme = ref<string | null>(
|
||||||
|
document.documentElement.getAttribute('data-theme')
|
||||||
|
);
|
||||||
const themeObserver = ref<MutationObserver | null>(null);
|
const themeObserver = ref<MutationObserver | null>(null);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -252,7 +295,10 @@ const createChartState = (neType: AllChartType) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 图表类型状态
|
// 图表类型状态
|
||||||
const chartStates: Record<AllChartType, ReturnType<typeof createChartState>> = Object.fromEntries(
|
const chartStates: Record<
|
||||||
|
AllChartType,
|
||||||
|
ReturnType<typeof createChartState>
|
||||||
|
> = Object.fromEntries(
|
||||||
networkElementTypes.value.map(type => [type, createChartState(type)])
|
networkElementTypes.value.map(type => [type, createChartState(type)])
|
||||||
) as Record<AllChartType, ReturnType<typeof createChartState>>;
|
) as Record<AllChartType, ReturnType<typeof createChartState>>;
|
||||||
|
|
||||||
@@ -263,14 +309,19 @@ interface RangePicker extends Record<AllChartType, [string, string]> {
|
|||||||
|
|
||||||
// 日期选择器状态
|
// 日期选择器状态
|
||||||
const rangePicker = reactive<RangePicker>({
|
const rangePicker = reactive<RangePicker>({
|
||||||
...Object.fromEntries(networkElementTypes.value.map(type => [
|
...(Object.fromEntries(
|
||||||
|
networkElementTypes.value.map(type => [
|
||||||
type,
|
type,
|
||||||
[
|
[
|
||||||
dayjs().subtract(1, 'hour').startOf('hour').valueOf().toString(), // 上一小时开始
|
dayjs().subtract(1, 'hour').startOf('hour').valueOf().toString(), // 上一小时开始
|
||||||
dayjs().startOf('hour').add(1, 'hour').valueOf().toString(), // 当前小时结束
|
dayjs().startOf('hour').add(1, 'hour').valueOf().toString(), // 当前小时结束
|
||||||
]
|
],
|
||||||
])) as Record<AllChartType, [string, string]>,
|
])
|
||||||
placeholder: [t('views.monitor.monitor.startTime'), t('views.monitor.monitor.endTime')] as [string, string],
|
) as Record<AllChartType, [string, string]>),
|
||||||
|
placeholder: [
|
||||||
|
t('views.monitor.monitor.startTime'),
|
||||||
|
t('views.monitor.monitor.endTime'),
|
||||||
|
] as [string, string],
|
||||||
});
|
});
|
||||||
|
|
||||||
// 可复用的图表初始化函数
|
// 可复用的图表初始化函数
|
||||||
@@ -284,7 +335,8 @@ const initChart = (type: AllChartType) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新当前主题
|
// 更新当前主题
|
||||||
state.currentTheme.value = document.documentElement.getAttribute('data-theme');
|
state.currentTheme.value =
|
||||||
|
document.documentElement.getAttribute('data-theme');
|
||||||
|
|
||||||
// 等待 DOM 更新
|
// 等待 DOM 更新
|
||||||
await nextTick();
|
await nextTick();
|
||||||
@@ -292,10 +344,14 @@ const initChart = (type: AllChartType) => {
|
|||||||
const container = state.chartDom.value;
|
const container = state.chartDom.value;
|
||||||
if (!container) {
|
if (!container) {
|
||||||
if (retries > 0) {
|
if (retries > 0) {
|
||||||
console.warn(`Chart container for ${type} not found, retrying... (${retries} attempts left)`);
|
console.warn(
|
||||||
|
`Chart container for ${type} not found, retrying... (${retries} attempts left)`
|
||||||
|
);
|
||||||
setTimeout(() => tryInit(retries - 1), 100);
|
setTimeout(() => tryInit(retries - 1), 100);
|
||||||
} else {
|
} else {
|
||||||
console.error(`Chart container for ${type} not found after multiple attempts`);
|
console.error(
|
||||||
|
`Chart container for ${type} not found after multiple attempts`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -314,12 +370,19 @@ const initChart = (type: AllChartType) => {
|
|||||||
if (!state.chart.value) return;
|
if (!state.chart.value) return;
|
||||||
|
|
||||||
const splitLineColor = getSplitLineColor();
|
const splitLineColor = getSplitLineColor();
|
||||||
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
const isDark =
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark';
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
position: function (point: number[], _params: any, _dom: HTMLElement, _rect: any, size: { viewSize: number[], contentSize: number[] }) {
|
position: function (
|
||||||
|
point: number[],
|
||||||
|
_params: any,
|
||||||
|
_dom: HTMLElement,
|
||||||
|
_rect: any,
|
||||||
|
size: { viewSize: number[]; contentSize: number[] }
|
||||||
|
) {
|
||||||
const [x] = point;
|
const [x] = point;
|
||||||
const [viewWidth] = size.viewSize;
|
const [viewWidth] = size.viewSize;
|
||||||
const [tooltipWidth] = size.contentSize;
|
const [tooltipWidth] = size.contentSize;
|
||||||
@@ -338,17 +401,19 @@ const initChart = (type: AllChartType) => {
|
|||||||
},
|
},
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
z: 0
|
z: 0,
|
||||||
},
|
},
|
||||||
className: `chart-tooltip-${type}`,
|
className: `chart-tooltip-${type}`,
|
||||||
z: 1000,
|
z: 1000,
|
||||||
extraCssText: 'z-index: 1000; pointer-events: none;',
|
extraCssText: 'z-index: 1000; pointer-events: none;',
|
||||||
confine: true,
|
confine: true,
|
||||||
backgroundColor: isDark ? 'rgba(48, 48, 48, 0.8)' : 'rgba(255, 255, 255, 0.9)',
|
backgroundColor: isDark
|
||||||
|
? 'rgba(48, 48, 48, 0.8)'
|
||||||
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
borderColor: isDark ? '#555' : '#ddd',
|
borderColor: isDark ? '#555' : '#ddd',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: isDark ? '#CACADA' : '#333'
|
color: isDark ? '#CACADA' : '#333',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
@@ -356,30 +421,30 @@ const initChart = (type: AllChartType) => {
|
|||||||
data: state.chartDataXAxisData,
|
data: state.chartDataXAxisData,
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: '{value}',
|
formatter: '{value}',
|
||||||
color: isDark ? '#CACADA' : '#333'
|
color: isDark ? '#CACADA' : '#333',
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: splitLineColor
|
color: splitLineColor,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
boundaryGap: [0, '100%'],
|
boundaryGap: [0, '100%'],
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: '{value}',
|
formatter: '{value}',
|
||||||
color: isDark ? '#CACADA' : '#333'
|
color: isDark ? '#CACADA' : '#333',
|
||||||
},
|
},
|
||||||
splitNumber: 5,
|
splitNumber: 5,
|
||||||
scale: true,
|
scale: true,
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: splitLineColor
|
color: splitLineColor,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
type: 'scroll',
|
type: 'scroll',
|
||||||
@@ -392,7 +457,9 @@ const initChart = (type: AllChartType) => {
|
|||||||
show: false,
|
show: false,
|
||||||
icon: 'circle',
|
icon: 'circle',
|
||||||
selected: state.chartLegendSelected,
|
selected: state.chartLegendSelected,
|
||||||
backgroundColor: isDark ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.9)',
|
backgroundColor: isDark
|
||||||
|
? 'rgba(0, 0, 0, 0.2)'
|
||||||
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
borderColor: isDark ? '#555' : '#ddd',
|
borderColor: isDark ? '#555' : '#ddd',
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
@@ -401,7 +468,9 @@ const initChart = (type: AllChartType) => {
|
|||||||
bottom: '15%',
|
bottom: '15%',
|
||||||
},
|
},
|
||||||
series: state.chartDataYSeriesData.map(series => {
|
series: state.chartDataYSeriesData.map(series => {
|
||||||
const color = state.seriesColors.get(series.customKey as string) || generateColorRGBA();
|
const color =
|
||||||
|
state.seriesColors.get(series.customKey as string) ||
|
||||||
|
generateColorRGBA();
|
||||||
return {
|
return {
|
||||||
...series,
|
...series,
|
||||||
itemStyle: { color },
|
itemStyle: { color },
|
||||||
@@ -428,13 +497,14 @@ const initChart = (type: AllChartType) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
state.currentTheme.value = document.documentElement.getAttribute('data-theme');
|
state.currentTheme.value =
|
||||||
|
document.documentElement.getAttribute('data-theme');
|
||||||
updateChartTheme(type);
|
updateChartTheme(type);
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe(document.documentElement, {
|
observer.observe(document.documentElement, {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
attributeFilter: ['data-theme']
|
attributeFilter: ['data-theme'],
|
||||||
});
|
});
|
||||||
|
|
||||||
// 正确地设置 ref 的值
|
// 正确地设置 ref 的值
|
||||||
@@ -505,7 +575,7 @@ function fnRealTimeSwitch(bool: boolean) {
|
|||||||
if (state.chart.value) {
|
if (state.chart.value) {
|
||||||
state.chart.value.setOption({
|
state.chart.value.setOption({
|
||||||
xAxis: { data: [] },
|
xAxis: { data: [] },
|
||||||
series: state.chartDataYSeriesData
|
series: state.chartDataYSeriesData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -513,7 +583,9 @@ function fnRealTimeSwitch(bool: boolean) {
|
|||||||
const options: OptionsType = {
|
const options: OptionsType = {
|
||||||
url: '/ws',
|
url: '/ws',
|
||||||
params: {
|
params: {
|
||||||
subGroupID: selectedNeTypes.value.map(type => `10_${type.toUpperCase()}_001`).join(','),
|
subGroupID: selectedNeTypes.value
|
||||||
|
.map(type => `10_${type.toUpperCase()}_001`)
|
||||||
|
.join(','),
|
||||||
},
|
},
|
||||||
onmessage: wsMessage,
|
onmessage: wsMessage,
|
||||||
onerror: wsError,
|
onerror: wsError,
|
||||||
@@ -534,7 +606,6 @@ function fnRealTimeSwitch(bool: boolean) {
|
|||||||
|
|
||||||
// 接收数据后错误回调
|
// 接收数据后错误回调
|
||||||
function wsError() {
|
function wsError() {
|
||||||
|
|
||||||
message.error(t('common.websocketError'));
|
message.error(t('common.websocketError'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,7 +630,9 @@ function wsMessage(res: Record<string, any>) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTime = parseDateToStr(kpiEvent.timeGroup ? +kpiEvent.timeGroup : Date.now());
|
const newTime = parseDateToStr(
|
||||||
|
kpiEvent.timeGroup ? +kpiEvent.timeGroup : Date.now()
|
||||||
|
);
|
||||||
|
|
||||||
// 只有在实时数据模式下才更新图表
|
// 只有在实时数据模式下才更新图表
|
||||||
if (realTimeEnabled.value) {
|
if (realTimeEnabled.value) {
|
||||||
@@ -596,7 +669,7 @@ function wsMessage(res: Record<string, any>) {
|
|||||||
if (state.chart.value) {
|
if (state.chart.value) {
|
||||||
state.chart.value.setOption({
|
state.chart.value.setOption({
|
||||||
xAxis: { data: state.chartDataXAxisData },
|
xAxis: { data: state.chartDataXAxisData },
|
||||||
series: state.chartDataYSeriesData as SeriesOption[]
|
series: state.chartDataYSeriesData as SeriesOption[],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,7 +702,8 @@ const renderChart = async (type: AllChartType) => {
|
|||||||
|
|
||||||
// 处理数据
|
// 处理数据
|
||||||
for (const column of state.tableColumns.value) {
|
for (const column of state.tableColumns.value) {
|
||||||
if (['neName', 'startIndex', 'timeGroup'].includes(column.key as string)) continue;
|
if (['neName', 'startIndex', 'timeGroup'].includes(column.key as string))
|
||||||
|
continue;
|
||||||
|
|
||||||
// 检查是否已有该指标的颜色,如果没有才生成新颜色
|
// 检查是否已有该指标的颜色,如果没有才生成新颜色
|
||||||
let color = state.seriesColors.get(column.key as string);
|
let color = state.seriesColors.get(column.key as string);
|
||||||
@@ -672,11 +746,14 @@ const renderChart = async (type: AllChartType) => {
|
|||||||
// 更新图表时考虑当前选中状态
|
// 更新图表时考虑当前选中状态
|
||||||
const legendSelected: Record<string, boolean> = {};
|
const legendSelected: Record<string, boolean> = {};
|
||||||
state.chartDataYSeriesData.forEach(series => {
|
state.chartDataYSeriesData.forEach(series => {
|
||||||
const title = state.tableColumns.value.find(col => col.key === series.customKey)?.title || series.customKey;
|
const title =
|
||||||
|
state.tableColumns.value.find(col => col.key === series.customKey)
|
||||||
|
?.title || series.customKey;
|
||||||
if (typeof title === 'string') {
|
if (typeof title === 'string') {
|
||||||
// 如果没有选中的指标或选中列表为空,显示所有指标
|
// 如果没有选中的指标或选中列表为空,显示所有指标
|
||||||
// 否则只显示选中的指标
|
// 否则只显示选中的指标
|
||||||
legendSelected[title] = !currentSelectedKpiIds.length ||
|
legendSelected[title] =
|
||||||
|
!currentSelectedKpiIds.length ||
|
||||||
currentSelectedKpiIds.includes(series.customKey as string);
|
currentSelectedKpiIds.includes(series.customKey as string);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -687,14 +764,17 @@ const renderChart = async (type: AllChartType) => {
|
|||||||
legend: {
|
legend: {
|
||||||
selected: legendSelected,
|
selected: legendSelected,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#646A73',
|
: '#646A73',
|
||||||
},
|
},
|
||||||
backgroundColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
backgroundColor:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? 'rgba(0, 0, 0, 0.2)'
|
? 'rgba(0, 0, 0, 0.2)'
|
||||||
: 'rgba(255, 255, 255, 0.9)',
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
borderColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
borderColor:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#555'
|
? '#555'
|
||||||
: '#ddd',
|
: '#ddd',
|
||||||
},
|
},
|
||||||
@@ -704,16 +784,17 @@ const renderChart = async (type: AllChartType) => {
|
|||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: '{value}',
|
formatter: '{value}',
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: getSplitLineColor()
|
color: getSplitLineColor(),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
series: state.chartDataYSeriesData as SeriesOption[],
|
series: state.chartDataYSeriesData as SeriesOption[],
|
||||||
},
|
},
|
||||||
@@ -721,10 +802,12 @@ const renderChart = async (type: AllChartType) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 获网元指标据
|
// 获网元指标据
|
||||||
const fetchKPITitle = async (type: AllChartType) => {
|
const fetchKPITitle = async (type: AllChartType) => {
|
||||||
const language = currentLocale.value.split('_')[0] === 'zh' ? 'cn' : currentLocale.value.split('_')[0];
|
const language =
|
||||||
|
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)) {
|
||||||
@@ -742,7 +825,7 @@ const fetchKPITitle = async (type: AllChartType) => {
|
|||||||
fixed: 'left',
|
fixed: 'left',
|
||||||
customRender: ({ text }: { text: string }) => {
|
customRender: ({ text }: { text: string }) => {
|
||||||
return dayjs(Number(text)).format('YYYY-MM-DD HH:mm:ss');
|
return dayjs(Number(text)).format('YYYY-MM-DD HH:mm:ss');
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
...res.data.map(item => {
|
...res.data.map(item => {
|
||||||
const kpiId = item.kpiId;
|
const kpiId = item.kpiId;
|
||||||
@@ -761,7 +844,7 @@ const fetchKPITitle = async (type: AllChartType) => {
|
|||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
maxWidth: 300,
|
maxWidth: 300,
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
// 更新颜色映射
|
// 更新颜色映射
|
||||||
@@ -805,11 +888,14 @@ const themeObserver = new MutationObserver(() => {
|
|||||||
// 重新计算图例选中状态
|
// 重新计算图例选中状态
|
||||||
const legendSelected: Record<string, boolean> = {};
|
const legendSelected: Record<string, boolean> = {};
|
||||||
state.chartDataYSeriesData.forEach(series => {
|
state.chartDataYSeriesData.forEach(series => {
|
||||||
const title = state.tableColumns.value.find(col => col.key === series.customKey)?.title || series.customKey;
|
const title =
|
||||||
|
state.tableColumns.value.find(col => col.key === series.customKey)
|
||||||
|
?.title || series.customKey;
|
||||||
if (typeof title === 'string') {
|
if (typeof title === 'string') {
|
||||||
// 如果没有选中的指标或选中列表为空,显示所有指标
|
// 如果没有选中的指标或选中列表为空,显示所有指标
|
||||||
// 否则只显示选中的指标
|
// 否则只显示选中的指标
|
||||||
legendSelected[title] = !currentSelectedKpiIds.length ||
|
legendSelected[title] =
|
||||||
|
!currentSelectedKpiIds.length ||
|
||||||
currentSelectedKpiIds.includes(series.customKey as string);
|
currentSelectedKpiIds.includes(series.customKey as string);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -817,7 +903,13 @@ const themeObserver = new MutationObserver(() => {
|
|||||||
const option = {
|
const option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
position: function (point: number[], _params: any, _dom: HTMLElement, _rect: any, size: { viewSize: number[], contentSize: number[] }) {
|
position: function (
|
||||||
|
point: number[],
|
||||||
|
_params: any,
|
||||||
|
_dom: HTMLElement,
|
||||||
|
_rect: any,
|
||||||
|
size: { viewSize: number[]; contentSize: number[] }
|
||||||
|
) {
|
||||||
const [x] = point;
|
const [x] = point;
|
||||||
const [viewWidth] = size.viewSize;
|
const [viewWidth] = size.viewSize;
|
||||||
const [tooltipWidth] = size.contentSize;
|
const [tooltipWidth] = size.contentSize;
|
||||||
@@ -836,58 +928,64 @@ const themeObserver = new MutationObserver(() => {
|
|||||||
},
|
},
|
||||||
axisPointer: {
|
axisPointer: {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
z: 0
|
z: 0,
|
||||||
},
|
},
|
||||||
className: `chart-tooltip-${state.neType}`,
|
className: `chart-tooltip-${state.neType}`,
|
||||||
z: 1000,
|
z: 1000,
|
||||||
extraCssText: 'z-index: 1000; pointer-events: none;',
|
extraCssText: 'z-index: 1000; pointer-events: none;',
|
||||||
confine: true,
|
confine: true,
|
||||||
backgroundColor: isDark ? 'rgba(48, 48, 48, 0.8)' : 'rgba(255, 255, 255, 0.9)',
|
backgroundColor: isDark
|
||||||
|
? 'rgba(48, 48, 48, 0.8)'
|
||||||
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
borderColor: isDark ? '#555' : '#ddd',
|
borderColor: isDark ? '#555' : '#ddd',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: isDark ? '#CACADA' : '#333'
|
color: isDark ? '#CACADA' : '#333',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: state.chartDataXAxisData,
|
data: state.chartDataXAxisData,
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: isDark ? '#CACADA' : '#333'
|
color: isDark ? '#CACADA' : '#333',
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: splitLineColor
|
color: splitLineColor,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
boundaryGap: [0, '100%'],
|
boundaryGap: [0, '100%'],
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: isDark ? '#CACADA' : '#333'
|
color: isDark ? '#CACADA' : '#333',
|
||||||
},
|
},
|
||||||
splitNumber: 5,
|
splitNumber: 5,
|
||||||
scale: true,
|
scale: true,
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: splitLineColor
|
color: splitLineColor,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
show: false,
|
show: false,
|
||||||
selected: legendSelected,
|
selected: legendSelected,
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: isDark ? '#CACADA' : '#646A73'
|
color: isDark ? '#CACADA' : '#646A73',
|
||||||
},
|
},
|
||||||
backgroundColor: isDark ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.9)',
|
backgroundColor: isDark
|
||||||
|
? 'rgba(0, 0, 0, 0.2)'
|
||||||
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
borderColor: isDark ? '#555' : '#ddd',
|
borderColor: isDark ? '#555' : '#ddd',
|
||||||
},
|
},
|
||||||
series: state.chartDataYSeriesData.map(series => {
|
series: state.chartDataYSeriesData.map(series => {
|
||||||
const color = state.seriesColors.get(series.customKey as string) as string;
|
const color = state.seriesColors.get(
|
||||||
|
series.customKey as string
|
||||||
|
) as string;
|
||||||
return {
|
return {
|
||||||
...series,
|
...series,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
@@ -902,14 +1000,14 @@ const themeObserver = new MutationObserver(() => {
|
|||||||
{ offset: 1, color: color.replace(')', ',0.3)') },
|
{ offset: 1, color: color.replace(')', ',0.3)') },
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
data: series.data
|
data: series.data,
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
chart.setOption(option, {
|
chart.setOption(option, {
|
||||||
notMerge: false,
|
notMerge: false,
|
||||||
lazyUpdate: false
|
lazyUpdate: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 强制重新渲染
|
// 强制重新渲染
|
||||||
@@ -945,7 +1043,9 @@ onMounted(async () => {
|
|||||||
// 从本地存储中读取选中的网元类型
|
// 从本地存储中读取选中的网元类型
|
||||||
const savedSelectedNeTypes = localStorage.getItem('selectedNeTypes');
|
const savedSelectedNeTypes = localStorage.getItem('selectedNeTypes');
|
||||||
if (savedSelectedNeTypes) {
|
if (savedSelectedNeTypes) {
|
||||||
const parsedSelectedNeTypes = JSON.parse(savedSelectedNeTypes) as AllChartType[];
|
const parsedSelectedNeTypes = JSON.parse(
|
||||||
|
savedSelectedNeTypes
|
||||||
|
) as AllChartType[];
|
||||||
selectedNeTypes.value = parsedSelectedNeTypes;
|
selectedNeTypes.value = parsedSelectedNeTypes;
|
||||||
networkElementTypes.value = parsedSelectedNeTypes;
|
networkElementTypes.value = parsedSelectedNeTypes;
|
||||||
} else {
|
} else {
|
||||||
@@ -957,7 +1057,7 @@ onMounted(async () => {
|
|||||||
// 添加主题观察器
|
// 添加主题观察器
|
||||||
themeObserver.observe(document.documentElement, {
|
themeObserver.observe(document.documentElement, {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
attributeFilter: ['data-theme']
|
attributeFilter: ['data-theme'],
|
||||||
});
|
});
|
||||||
|
|
||||||
await initCharts();
|
await initCharts();
|
||||||
@@ -968,7 +1068,7 @@ 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();
|
||||||
}
|
}
|
||||||
@@ -996,7 +1096,8 @@ const getKpiStats = (type: AllChartType) => {
|
|||||||
const state = chartStates[type];
|
const state = chartStates[type];
|
||||||
if (!state?.chartDataYSeriesData) return [];
|
if (!state?.chartDataYSeriesData) return [];
|
||||||
|
|
||||||
return state.chartDataYSeriesData.map(series => {
|
return state.chartDataYSeriesData
|
||||||
|
.map(series => {
|
||||||
const kpiId = series.customKey as string;
|
const kpiId = series.customKey as string;
|
||||||
const column = state.tableColumns.value.find(col => col.key === kpiId);
|
const column = state.tableColumns.value.find(col => col.key === kpiId);
|
||||||
if (!column) return null;
|
if (!column) return null;
|
||||||
@@ -1009,13 +1110,16 @@ const getKpiStats = (type: AllChartType) => {
|
|||||||
title: column.title as string,
|
title: column.title as string,
|
||||||
max: values.length > 0 ? Math.max(...values) : 0,
|
max: values.length > 0 ? Math.max(...values) : 0,
|
||||||
min: values.length > 0 ? Math.min(...values) : 0,
|
min: values.length > 0 ? Math.min(...values) : 0,
|
||||||
neType: type
|
neType: type,
|
||||||
};
|
};
|
||||||
}).filter((item): item is KPIStats => item !== null);
|
})
|
||||||
|
.filter((item): item is KPIStats => item !== null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 修改选中行的状态,改为数组存储个选中的指标ID
|
// 修改选中行的状态,改为数组存储个选中的指标ID
|
||||||
const selectedRows = ref<Record<AllChartType, string[]>>({} as Record<AllChartType, string[]>);
|
const selectedRows = ref<Record<AllChartType, string[]>>(
|
||||||
|
{} as Record<AllChartType, string[]>
|
||||||
|
);
|
||||||
|
|
||||||
// 修改处理行点击的方法,支持多选
|
// 修改处理行点击的方法,支持多选
|
||||||
const handleRowClick = (record: KPIStats, type: AllChartType) => {
|
const handleRowClick = (record: KPIStats, type: AllChartType) => {
|
||||||
@@ -1041,7 +1145,10 @@ const handleRowClick = (record: KPIStats, type: AllChartType) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 修改更新图表图例选中的方法,支持多选
|
// 修改更新图表图例选中的方法,支持多选
|
||||||
const updateChartLegendSelect = (type: AllChartType, selectedKpiIds?: string[]) => {
|
const updateChartLegendSelect = (
|
||||||
|
type: AllChartType,
|
||||||
|
selectedKpiIds?: string[]
|
||||||
|
) => {
|
||||||
const state = chartStates[type];
|
const state = chartStates[type];
|
||||||
const chart = state?.chart?.value;
|
const chart = state?.chart?.value;
|
||||||
if (!chart) return;
|
if (!chart) return;
|
||||||
@@ -1053,11 +1160,15 @@ const updateChartLegendSelect = (type: AllChartType, selectedKpiIds?: string[])
|
|||||||
|
|
||||||
const legendSelected: Record<string, boolean> = {};
|
const legendSelected: Record<string, boolean> = {};
|
||||||
state.chartDataYSeriesData.forEach(series => {
|
state.chartDataYSeriesData.forEach(series => {
|
||||||
const title = state.tableColumns.value.find(col => col.key === series.customKey)?.title || series.customKey;
|
const title =
|
||||||
|
state.tableColumns.value.find(col => col.key === series.customKey)
|
||||||
|
?.title || series.customKey;
|
||||||
if (typeof title === 'string') {
|
if (typeof title === 'string') {
|
||||||
// 如果没有选中的指标或选中列表为空,显示所有指标
|
// 如果没有选中的指标或选中列表为空,显示所有指标
|
||||||
// 否则只显示选中的指标
|
// 否则只显示选中的指标
|
||||||
legendSelected[title] = !selectedKpiIds?.length || selectedKpiIds.includes(series.customKey as string);
|
legendSelected[title] =
|
||||||
|
!selectedKpiIds?.length ||
|
||||||
|
selectedKpiIds.includes(series.customKey as string);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1070,9 +1181,11 @@ const updateChartLegendSelect = (type: AllChartType, selectedKpiIds?: string[])
|
|||||||
textStyle: {
|
textStyle: {
|
||||||
color: isDark ? '#CACADA' : '#646A73',
|
color: isDark ? '#CACADA' : '#646A73',
|
||||||
},
|
},
|
||||||
backgroundColor: isDark ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.9)',
|
backgroundColor: isDark
|
||||||
|
? 'rgba(0, 0, 0, 0.2)'
|
||||||
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
borderColor: isDark ? '#555' : '#ddd',
|
borderColor: isDark ? '#555' : '#ddd',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1092,9 +1205,9 @@ const statsColumns: TableColumnType<KPIStats>[] = [
|
|||||||
color: color || '#000',
|
color: color || '#000',
|
||||||
fontSize: '40px',
|
fontSize: '40px',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
} as Record<string, string>
|
} as Record<string, string>,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.perfManage.kpiOverView.kpiName'),
|
title: t('views.perfManage.kpiOverView.kpiName'),
|
||||||
@@ -1117,7 +1230,7 @@ const statsColumns: TableColumnType<KPIStats>[] = [
|
|||||||
width: '30%',
|
width: '30%',
|
||||||
sorter: (a: KPIStats, b: KPIStats) => a.min - b.min,
|
sorter: (a: KPIStats, b: KPIStats) => a.min - b.min,
|
||||||
sortDirections: ['ascend', 'descend'] as ('ascend' | 'descend')[],
|
sortDirections: ['ascend', 'descend'] as ('ascend' | 'descend')[],
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 1. 添加 tab 切换处理函数
|
// 1. 添加 tab 切换处理函数
|
||||||
@@ -1142,7 +1255,13 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => {
|
|||||||
<a-form layout="horizontal">
|
<a-form layout="horizontal">
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :lg="4" :md="24" :xs="24">
|
<a-col :lg="4" :md="24" :xs="24">
|
||||||
<a-form-item :label="realTimeEnabled ? t('views.dashboard.cdr.realTimeDataStop'):t('views.dashboard.cdr.realTimeDataStart')">
|
<a-form-item
|
||||||
|
:label="
|
||||||
|
realTimeEnabled
|
||||||
|
? t('views.dashboard.cdr.realTimeDataStop')
|
||||||
|
: t('views.dashboard.cdr.realTimeDataStart')
|
||||||
|
"
|
||||||
|
>
|
||||||
<a-switch
|
<a-switch
|
||||||
v-model:checked="realTimeEnabled"
|
v-model:checked="realTimeEnabled"
|
||||||
@change="handleRealTimeSwitch"
|
@change="handleRealTimeSwitch"
|
||||||
@@ -1150,7 +1269,10 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => {
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="20" :md="24" :xs="24">
|
<a-col :lg="20" :md="24" :xs="24">
|
||||||
<a-form-item :label="t('views.ne.common.neType')" class="ne-type-select">
|
<a-form-item
|
||||||
|
:label="t('views.ne.common.neType')"
|
||||||
|
class="ne-type-select"
|
||||||
|
>
|
||||||
<a-checkbox-group
|
<a-checkbox-group
|
||||||
v-model:value="selectedNeTypes"
|
v-model:value="selectedNeTypes"
|
||||||
:options="neTypeOptions"
|
:options="neTypeOptions"
|
||||||
@@ -1192,11 +1314,19 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => {
|
|||||||
</template>
|
</template>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<div :ref="el => { if (el && chartStates[type]) chartStates[type].chartDom.value = el as HTMLElement }"></div>
|
<div
|
||||||
|
:ref="el => { if (el && chartStates[type]) chartStates[type].chartDom.value = el as HTMLElement }"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<a-tabs default-active-key="stats" @change="(key:any) => handleTabChange(key, type)">
|
<a-tabs
|
||||||
<a-tab-pane key="stats" :tab="t('views.perfManage.kpiKeyTarget.statistics')">
|
default-active-key="stats"
|
||||||
|
@change="(key:any) => handleTabChange(key, type)"
|
||||||
|
>
|
||||||
|
<a-tab-pane
|
||||||
|
key="stats"
|
||||||
|
:tab="t('views.perfManage.kpiKeyTarget.statistics')"
|
||||||
|
>
|
||||||
<a-table
|
<a-table
|
||||||
:columns="statsColumns"
|
:columns="statsColumns"
|
||||||
:data-source="getKpiStats(type)"
|
:data-source="getKpiStats(type)"
|
||||||
@@ -1204,13 +1334,67 @@ const handleTabChange = async (activeKey: string, type: AllChartType) => {
|
|||||||
size="small"
|
size="small"
|
||||||
:scroll="{ y: 'true' }"
|
:scroll="{ y: 'true' }"
|
||||||
:loading="chartStates[type].tableState.loading"
|
:loading="chartStates[type].tableState.loading"
|
||||||
:custom-row="(record) => ({
|
:custom-row="
|
||||||
|
record => ({
|
||||||
onClick: () => handleRowClick(record, type),
|
onClick: () => handleRowClick(record, type),
|
||||||
class: selectedRows[type]?.includes(record.kpiId) ? 'selected-row' : ''
|
class: selectedRows[type]?.includes(record.kpiId)
|
||||||
})"
|
? 'selected-row'
|
||||||
/>
|
: '',
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #headerCell="{ column }">
|
||||||
|
<template v-if="column.key === 'total'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.totalValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Sum within Time Range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'avg'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.avgValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Average value over the time range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'max'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.maxValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Maximum value in time range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'min'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.minValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Minimum value in the time range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="raw" :tab="t('views.perfManage.kpiKeyTarget.rawData')">
|
<a-tab-pane
|
||||||
|
key="raw"
|
||||||
|
:tab="t('views.perfManage.kpiKeyTarget.rawData')"
|
||||||
|
>
|
||||||
<a-table
|
<a-table
|
||||||
:columns="chartStates[type].tableColumns.value"
|
:columns="chartStates[type].tableColumns.value"
|
||||||
:data-source="chartStates[type].tableState.data"
|
:data-source="chartStates[type].tableState.data"
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, onUnmounted, nextTick, computed, h } from 'vue';
|
import { ref, onMounted, onUnmounted, nextTick, computed, h } from 'vue';
|
||||||
import * as echarts from 'echarts/core';
|
import * as echarts from 'echarts/core';
|
||||||
import { GridComponent, TooltipComponent, TitleComponent,LegendComponent } from 'echarts/components';
|
import {
|
||||||
|
GridComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
TitleComponent,
|
||||||
|
LegendComponent,
|
||||||
|
} from 'echarts/components';
|
||||||
import { LineChart } from 'echarts/charts';
|
import { LineChart } from 'echarts/charts';
|
||||||
import { CanvasRenderer } from 'echarts/renderers';
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
import { getKPITitle, listKPIData } from '@/api/perfManage/goldTarget';
|
import { getKPITitle, listKPIData } from '@/api/perfManage/goldTarget';
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
import { message,} from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { RESULT_CODE_ERROR, RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
import {
|
||||||
|
RESULT_CODE_ERROR,
|
||||||
|
RESULT_CODE_SUCCESS,
|
||||||
|
} from '@/constants/result-constants';
|
||||||
import type { Dayjs } from 'dayjs';
|
import type { Dayjs } from 'dayjs';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
@@ -35,7 +43,7 @@ const tableLoading = ref(false);
|
|||||||
const rangeLoading = ref(false);
|
const rangeLoading = ref(false);
|
||||||
//网元类型定义
|
//网元类型定义
|
||||||
const ALL_NE_TYPES = ['AMF', 'SMF', 'UPF', 'MME', 'IMS', 'SMSC'] as const;
|
const ALL_NE_TYPES = ['AMF', 'SMF', 'UPF', 'MME', 'IMS', 'SMSC'] as const;
|
||||||
type NeType= typeof ALL_NE_TYPES[number];
|
type NeType = (typeof ALL_NE_TYPES)[number];
|
||||||
|
|
||||||
echarts.use([
|
echarts.use([
|
||||||
LineChart,
|
LineChart,
|
||||||
@@ -61,12 +69,19 @@ const ws = ref<WS | null>(null);
|
|||||||
// [t('views.monitor.monitor.today')]: [dayjs().startOf('day'), dayjs()],
|
// [t('views.monitor.monitor.today')]: [dayjs().startOf('day'), dayjs()],
|
||||||
// });
|
// });
|
||||||
const ranges = ref([
|
const ranges = ref([
|
||||||
{label:t('views.perfManage.customTarget.sixHoursAgo'),value:[dayjs().subtract(6, 'hours'),
|
{
|
||||||
dayjs(),]},
|
label: t('views.perfManage.customTarget.sixHoursAgo'),
|
||||||
{label:t('views.perfManage.customTarget.threeHoursAgo'),value:[dayjs().subtract(3, 'hours'),
|
value: [dayjs().subtract(6, 'hours'), dayjs()],
|
||||||
dayjs(),]},
|
},
|
||||||
{label:t('views.monitor.monitor.today'),value:[dayjs().startOf('day'), dayjs()]},
|
{
|
||||||
])
|
label: t('views.perfManage.customTarget.threeHoursAgo'),
|
||||||
|
value: [dayjs().subtract(3, 'hours'), dayjs()],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('views.monitor.monitor.today'),
|
||||||
|
value: [dayjs().startOf('day'), dayjs()],
|
||||||
|
},
|
||||||
|
]);
|
||||||
//日期范围响应式变量
|
//日期范围响应式变量
|
||||||
const dateRange = ref<[string, string]>([
|
const dateRange = ref<[string, string]>([
|
||||||
dayjs().subtract(1, 'hour').startOf('hour').valueOf().toString(), // 上一小时开始
|
dayjs().subtract(1, 'hour').startOf('hour').valueOf().toString(), // 上一小时开始
|
||||||
@@ -181,18 +196,20 @@ const wsMessage = (res:Record<string,any>)=>{
|
|||||||
const processChartData = (rawData: any[]) => {
|
const processChartData = (rawData: any[]) => {
|
||||||
const groupedData = new Map<string, any>(); //数据按时间分组
|
const groupedData = new Map<string, any>(); //数据按时间分组
|
||||||
|
|
||||||
rawData.forEach(item => {//合并相同时间点的数据
|
rawData.forEach(item => {
|
||||||
|
//合并相同时间点的数据
|
||||||
const timeKey = item.timeGroup;
|
const timeKey = item.timeGroup;
|
||||||
if (!groupedData.has(timeKey)) {//按时间排序
|
if (!groupedData.has(timeKey)) {
|
||||||
|
//按时间排序
|
||||||
groupedData.set(timeKey, { timeGroup: timeKey });
|
groupedData.set(timeKey, { timeGroup: timeKey });
|
||||||
}
|
}
|
||||||
Object.assign(groupedData.get(timeKey), item);
|
Object.assign(groupedData.get(timeKey), item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return Array.from(groupedData.values())
|
return Array.from(groupedData.values())
|
||||||
.sort((a, b) => Number(a.timeGroup) - Number(b.timeGroup))
|
.sort((a, b) => Number(a.timeGroup) - Number(b.timeGroup))
|
||||||
.map(item => {//转换成图表需要的格式
|
.map(item => {
|
||||||
|
//转换成图表需要的格式
|
||||||
const dataItem: ChartDataItem = { date: item.timeGroup.toString() };
|
const dataItem: ChartDataItem = { date: item.timeGroup.toString() };
|
||||||
selectedKPIs.value.forEach(kpiId => {
|
selectedKPIs.value.forEach(kpiId => {
|
||||||
dataItem[kpiId] = Number(item[kpiId]) || 0;
|
dataItem[kpiId] = Number(item[kpiId]) || 0;
|
||||||
@@ -216,7 +233,7 @@ const fetchChartData = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建并行请求数组
|
// 创建并行请求数组
|
||||||
const requests = ALL_NE_TYPES.map(async (neType) => {
|
const requests = ALL_NE_TYPES.map(async neType => {
|
||||||
const params = {
|
const params = {
|
||||||
neType,
|
neType,
|
||||||
neId: '001',
|
neId: '001',
|
||||||
@@ -289,7 +306,8 @@ const themeObserver = new MutationObserver(() => {
|
|||||||
kpiColors.set(kpiId, color);
|
kpiColors.set(kpiId, color);
|
||||||
});
|
});
|
||||||
// 使用存储的颜色更新图表系列
|
// 使用存储的颜色更新图表系列
|
||||||
const series = selectedKPIs.value.map(kpiId => {
|
const series = selectedKPIs.value
|
||||||
|
.map(kpiId => {
|
||||||
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||||
if (!kpi) return null;
|
if (!kpi) return null;
|
||||||
|
|
||||||
@@ -300,16 +318,18 @@ const themeObserver = new MutationObserver(() => {
|
|||||||
itemStyle: { color: kpiColors.get(kpiId) },
|
itemStyle: { color: kpiColors.get(kpiId) },
|
||||||
...getSeriesConfig(),
|
...getSeriesConfig(),
|
||||||
};
|
};
|
||||||
}).filter(Boolean);
|
})
|
||||||
|
.filter(Boolean);
|
||||||
const option = {
|
const option = {
|
||||||
title: {
|
title: {
|
||||||
text: t('views.perfManage.kpiOverView.kpiChartTitle'),
|
text: t('views.perfManage.kpiOverView.kpiChartTitle'),
|
||||||
left: 'center',
|
left: 'center',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
// 保持现有的 xAxis 配置
|
// 保持现有的 xAxis 配置
|
||||||
@@ -319,61 +339,68 @@ const themeObserver = new MutationObserver(() => {
|
|||||||
dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss')
|
dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss')
|
||||||
),
|
),
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: splitLineColor
|
color: splitLineColor,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: '{value}',
|
formatter: '{value}',
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
},
|
},
|
||||||
splitNumber: 5,
|
splitNumber: 5,
|
||||||
scale: true,
|
scale: true,
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: splitLineColor
|
color: splitLineColor,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
show: false,
|
show: false,
|
||||||
selected: Object.fromEntries(
|
selected: Object.fromEntries(
|
||||||
selectedKPIs.value.map(kpiId => [
|
selectedKPIs.value.map(kpiId => [
|
||||||
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId,
|
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId,
|
||||||
selectedRows.value.length === 0 ? true : selectedRows.value.includes(kpiId)
|
selectedRows.value.length === 0
|
||||||
|
? true
|
||||||
|
: selectedRows.value.includes(kpiId),
|
||||||
])
|
])
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
confine: true, // 限制 tooltip 显示范围
|
confine: true, // 限制 tooltip 显示范围
|
||||||
backgroundColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
backgroundColor:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? 'rgba(48, 48, 48, 0.8)'
|
? 'rgba(48, 48, 48, 0.8)'
|
||||||
: 'rgba(255, 255, 255, 0.9)',
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
borderColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
borderColor:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#555'
|
? '#555'
|
||||||
: '#ddd',
|
: '#ddd',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
},
|
},
|
||||||
extraCssText: 'box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);'
|
extraCssText: 'box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);',
|
||||||
},
|
},
|
||||||
// 重新设置系列数据
|
// 重新设置系列数据
|
||||||
series
|
series,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 使用新的配置更新图表
|
// 使用新的配置更新图表
|
||||||
@@ -393,7 +420,8 @@ const updateChart = () => {
|
|||||||
//获取图表配置
|
//获取图表配置
|
||||||
const commonConfig = getSeriesConfig();
|
const commonConfig = getSeriesConfig();
|
||||||
//构建数据系列
|
//构建数据系列
|
||||||
const series = selectedKPIs.value.map(kpiId => {
|
const series = selectedKPIs.value
|
||||||
|
.map(kpiId => {
|
||||||
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||||
if (!kpi) return null;
|
if (!kpi) return null;
|
||||||
//为每个KPI分配临时的固定颜色
|
//为每个KPI分配临时的固定颜色
|
||||||
@@ -407,7 +435,8 @@ const updateChart = () => {
|
|||||||
itemStyle: { color },
|
itemStyle: { color },
|
||||||
...commonConfig,
|
...commonConfig,
|
||||||
};
|
};
|
||||||
}).filter(Boolean);
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
title: {
|
title: {
|
||||||
@@ -415,29 +444,34 @@ const updateChart = () => {
|
|||||||
left: 'center',
|
left: 'center',
|
||||||
// 添加文字颜色配置,根据主题切换
|
// 添加文字颜色配置,根据主题切换
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
confine: true, // 限制 tooltip 显示范围
|
confine: true, // 限制 tooltip 显示范围
|
||||||
backgroundColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
backgroundColor:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? 'rgba(48, 48, 48, 0.8)'
|
? 'rgba(48, 48, 48, 0.8)'
|
||||||
: 'rgba(255, 255, 255, 0.9)',
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
borderColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
borderColor:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#555'
|
? '#555'
|
||||||
: '#ddd',
|
: '#ddd',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
},
|
},
|
||||||
extraCssText: 'box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);'
|
extraCssText: 'box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);',
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
data: selectedKPIs.value.map(kpiId =>
|
data: selectedKPIs.value.map(
|
||||||
|
kpiId =>
|
||||||
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId
|
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId
|
||||||
),
|
),
|
||||||
type: 'scroll',
|
type: 'scroll',
|
||||||
@@ -449,7 +483,9 @@ const updateChart = () => {
|
|||||||
selected: Object.fromEntries(
|
selected: Object.fromEntries(
|
||||||
selectedKPIs.value.map(kpiId => [
|
selectedKPIs.value.map(kpiId => [
|
||||||
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId,
|
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId,
|
||||||
selectedRows.value.length === 0 ? true : selectedRows.value.includes(kpiId)
|
selectedRows.value.length === 0
|
||||||
|
? true
|
||||||
|
: selectedRows.value.includes(kpiId),
|
||||||
])
|
])
|
||||||
),
|
),
|
||||||
show: false,
|
show: false,
|
||||||
@@ -470,15 +506,16 @@ const updateChart = () => {
|
|||||||
type: 'category',
|
type: 'category',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: '{value}',
|
formatter: '{value}',
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: getSplitLineColor()
|
color: getSplitLineColor(),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
//控制坐标轴两边留白
|
//控制坐标轴两边留白
|
||||||
// 当为折线图时(isLine为true)时不留白,柱状图时留白
|
// 当为折线图时(isLine为true)时不留白,柱状图时留白
|
||||||
@@ -519,9 +556,10 @@ const updateChart = () => {
|
|||||||
type: 'value',
|
type: 'value',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: '{value}',
|
formatter: '{value}',
|
||||||
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
color:
|
||||||
|
document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
? '#CACADA'
|
? '#CACADA'
|
||||||
: '#333'
|
: '#333',
|
||||||
},
|
},
|
||||||
// 添加自计算的分割段数
|
// 添加自计算的分割段数
|
||||||
splitNumber: 5,
|
splitNumber: 5,
|
||||||
@@ -530,9 +568,9 @@ const updateChart = () => {
|
|||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: getSplitLineColor()
|
color: getSplitLineColor(),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
series: series, //配置数据
|
series: series, //配置数据
|
||||||
};
|
};
|
||||||
@@ -561,8 +599,6 @@ const updateChart = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//钩子函数
|
//钩子函数
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -588,7 +624,7 @@ onMounted(async () => {
|
|||||||
// 添加主题观察器
|
// 添加主题观察器
|
||||||
themeObserver.observe(document.documentElement, {
|
themeObserver.observe(document.documentElement, {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
attributeFilter: ['data-theme']
|
attributeFilter: ['data-theme'],
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize:', error);
|
console.error('Failed to initialize:', error);
|
||||||
@@ -603,7 +639,8 @@ const selectedKPIs = ref<string[]>(Object.values(TARGET_KPI_IDS).flat());
|
|||||||
|
|
||||||
// 获取网元指标
|
// 获取网元指标
|
||||||
const fetchSpecificKPI = async () => {
|
const fetchSpecificKPI = async () => {
|
||||||
const language = currentLocale.value.split('_')[0] === 'zh'
|
const language =
|
||||||
|
currentLocale.value.split('_')[0] === 'zh'
|
||||||
? 'cn'
|
? 'cn'
|
||||||
: currentLocale.value.split('_')[0];
|
: currentLocale.value.split('_')[0];
|
||||||
|
|
||||||
@@ -668,7 +705,8 @@ const updateChartData = (newData: ChartDataItem) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chartData.value.push(newData);
|
chartData.value.push(newData);
|
||||||
if (chartData.value.length > 100) {//100改为50
|
if (chartData.value.length > 100) {
|
||||||
|
//100改为50
|
||||||
chartData.value.shift(); //大于100条时删除最早的数据
|
chartData.value.shift(); //大于100条时删除最早的数据
|
||||||
}
|
}
|
||||||
//使用try-catch包裹图表更新逻辑
|
//使用try-catch包裹图表更新逻辑
|
||||||
@@ -697,7 +735,6 @@ const updateChartData = (newData: ChartDataItem) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 添加一个接口定义指标统计数据的类型
|
// 添加一个接口定义指标统计数据的类型
|
||||||
interface KPIStats {
|
interface KPIStats {
|
||||||
kpiId: string;
|
kpiId: string;
|
||||||
@@ -718,7 +755,8 @@ const updateKpiStats = () => {
|
|||||||
kpiStats.value = [];
|
kpiStats.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
kpiStats.value = selectedKPIs.value.map(kpiId => {
|
kpiStats.value = selectedKPIs.value
|
||||||
|
.map(kpiId => {
|
||||||
// 找到对应的KPI标题
|
// 找到对应的KPI标题
|
||||||
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||||
if (!kpi) return null;
|
if (!kpi) return null;
|
||||||
@@ -727,12 +765,13 @@ const updateKpiStats = () => {
|
|||||||
const values = chartData.value.map(item => Number(item[kpiId]) || 0);
|
const values = chartData.value.map(item => Number(item[kpiId]) || 0);
|
||||||
|
|
||||||
// 计算总值
|
// 计算总值
|
||||||
const total = Number(values.reduce((sum, val) => sum + val, 0).toFixed(2));
|
const total = Number(
|
||||||
|
values.reduce((sum, val) => sum + val, 0).toFixed(2)
|
||||||
|
);
|
||||||
|
|
||||||
// 计算平均值
|
// 计算平均值
|
||||||
const avg = values.length > 0
|
const avg =
|
||||||
? Number((total / values.length).toFixed(2))
|
values.length > 0 ? Number((total / values.length).toFixed(2)) : 0;
|
||||||
: 0;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kpiId: kpiId,
|
kpiId: kpiId,
|
||||||
@@ -740,12 +779,12 @@ const updateKpiStats = () => {
|
|||||||
max: Math.max(...values),
|
max: Math.max(...values),
|
||||||
min: Math.min(...values),
|
min: Math.min(...values),
|
||||||
avg: avg,
|
avg: avg,
|
||||||
total: total
|
total: total,
|
||||||
};
|
};
|
||||||
}).filter((item): item is KPIStats => item !== null);
|
})
|
||||||
|
.filter((item): item is KPIStats => item !== null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 添加表列定义
|
// 添加表列定义
|
||||||
const statsColumns: TableColumnType<KPIStats>[] = [
|
const statsColumns: TableColumnType<KPIStats>[] = [
|
||||||
{
|
{
|
||||||
@@ -761,9 +800,9 @@ const statsColumns: TableColumnType<KPIStats>[] = [
|
|||||||
color: color || '#000', // 使用与折线图相同的颜色
|
color: color || '#000', // 使用与折线图相同的颜色
|
||||||
fontSize: '30px', // 增大图标尺寸到30px
|
fontSize: '30px', // 增大图标尺寸到30px
|
||||||
fontWeight: 'bold', // 加粗
|
fontWeight: 'bold', // 加粗
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.perfManage.kpiOverView.kpiName'),
|
title: t('views.perfManage.kpiOverView.kpiName'),
|
||||||
@@ -829,14 +868,16 @@ const updateChartLegendSelect = () => {
|
|||||||
const legendSelected = Object.fromEntries(
|
const legendSelected = Object.fromEntries(
|
||||||
selectedKPIs.value.map(kpiId => [
|
selectedKPIs.value.map(kpiId => [
|
||||||
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId,
|
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId,
|
||||||
selectedRows.value.length === 0 ? true : selectedRows.value.includes(kpiId)
|
selectedRows.value.length === 0
|
||||||
|
? true
|
||||||
|
: selectedRows.value.includes(kpiId),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
chart.setOption({
|
chart.setOption({
|
||||||
legend: {
|
legend: {
|
||||||
selected: legendSelected
|
selected: legendSelected,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -844,7 +885,7 @@ const updateChartLegendSelect = () => {
|
|||||||
const tableRowConfig = computed(() => {
|
const tableRowConfig = computed(() => {
|
||||||
return (record: KPIStats) => ({
|
return (record: KPIStats) => ({
|
||||||
onClick: () => handleRowClick(record),
|
onClick: () => handleRowClick(record),
|
||||||
class: selectedRows.value.includes(record.kpiId) ? 'selected-row' : ''
|
class: selectedRows.value.includes(record.kpiId) ? 'selected-row' : '',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -882,7 +923,54 @@ const tableRowConfig = computed(() => {
|
|||||||
size="small"
|
size="small"
|
||||||
:loading="tableLoading"
|
:loading="tableLoading"
|
||||||
:custom-row="tableRowConfig"
|
:custom-row="tableRowConfig"
|
||||||
/>
|
>
|
||||||
|
<template #headerCell="{ column }">
|
||||||
|
<template v-if="column.key === 'total'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.totalValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Sum within Time Range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'avg'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.avgValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Average value over the time range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'max'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.maxValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Maximum value in time range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'min'">
|
||||||
|
<span>
|
||||||
|
{{ t('views.perfManage.kpiOverView.minValue') }}
|
||||||
|
<a-tooltip placement="bottom">
|
||||||
|
<template #title>
|
||||||
|
<span>Minimum value in the time range</span>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user