Merge remote-tracking branch 'origin/main' into multi-tenant

This commit is contained in:
TsMask
2025-01-15 17:22:15 +08:00
6 changed files with 345 additions and 155 deletions

View File

@@ -707,6 +707,26 @@ export default {
licenseTip2: '2. Clicking [Finish] will end the installation process.', licenseTip2: '2. Clicking [Finish] will end the installation process.',
}, },
}, },
neData: {
baseStation: {
list: "List",
topology: "Topology",
nbName: "Equipment Name",
topologyTitle: "Radio State Graph",
name: "Radio Name",
namePlease: "text content length 0~64",
position: "Radio Address",
positionPlease: "location description. Prohibition of spaces, length of text content 0-64",
address: "IP Address",
addressPlease: "text content length 0~64",
state: "Radio State",
online: "Online",
offline: "Offline",
time: "Change Time",
addRadio: "Add Radio Info",
editRadio: "Edit Radio Info",
},
},
neUser: { neUser: {
auth: { auth: {
authInfo:' Authentication Info', authInfo:' Authentication Info',
@@ -965,10 +985,10 @@ export default {
}, },
kpiOverView:{ kpiOverView:{
"kpiName":"NE Metrics Name", "kpiName":"NE Metrics Name",
"maxValue":"Max Value", "maxValue":"Max",
"minValue":"Min Value", "minValue":"Min",
"avgValue":"Average Value", "avgValue":"Avg",
"totalValue":"Worth Value", "totalValue":"Total",
"kpiChartTitle":"Overview of NE metrics", "kpiChartTitle":"Overview of NE metrics",
"changeLine":"Change to Line Charts", "changeLine":"Change to Line Charts",
"changeBar":"Change to Bar Charts", "changeBar":"Change to Bar Charts",

View File

@@ -707,6 +707,26 @@ export default {
licenseTip2: '2. 点击【结束】将结束安装过程', licenseTip2: '2. 点击【结束】将结束安装过程',
}, },
}, },
neData: {
baseStation: {
list: "列表",
topology: "拓扑图",
nbName: "设备名称",
topologyTitle: "基站状态关系图",
name: "基站名称",
namePlease: "文本内容长度0~64",
position: "基站位置",
positionPlease: "位置描述。禁止空格文本内容长度0-64",
address: "IP地址",
addressPlease: "文本内容长度0~64",
state: "基站状态",
online: "在线",
offline: "离线",
time: "变更时间",
addRadio: "添加基站信息",
editRadio: "更新基站信息",
},
},
neUser: { neUser: {
auth: { auth: {
authInfo:'鉴权信息', authInfo:'鉴权信息',

View File

@@ -20,13 +20,13 @@ const route = useRoute();
const nbState = ref<DictType[]>([ const nbState = ref<DictType[]>([
{ {
value: 'ON', value: 'ON',
label: 'Online', label: t('views.neData.baseStation.online'),
tagType: 'green', tagType: 'green',
tagClass: '', tagClass: '',
}, },
{ {
value: 'OFF', value: 'OFF',
label: 'Offline', label: t('views.neData.baseStation.offline'),
tagType: 'red', tagType: 'red',
tagClass: '', tagClass: '',
}, },
@@ -75,48 +75,55 @@ let tableState: TabeStateType = reactive({
/**表格字段列 */ /**表格字段列 */
let tableColumns = ref<ColumnsType>([ let tableColumns = ref<ColumnsType>([
{ {
title: 'Index', title: t('common.rowId'),
dataIndex: 'index', dataIndex: 'index',
align: 'left', align: 'left',
width: 80, width: 80,
}, },
{ {
title: 'Name', title: t('views.neData.baseStation.name'),
dataIndex: 'name', dataIndex: 'name',
align: 'left', align: 'left',
width: 150, width: 150,
ellipsis: true, ellipsis: true,
}, },
{ {
title: 'Position', title: t('views.neData.baseStation.position'),
dataIndex: 'position', dataIndex: 'position',
align: 'left', align: 'left',
width: 150, width: 150,
ellipsis: true, ellipsis: true,
}, },
{ {
title: 'Address', title: t('views.neData.baseStation.address'),
dataIndex: 'address', dataIndex: 'address',
align: 'left', align: 'left',
width: 100, width: 100,
}, },
{ {
title: 'State', title: t('views.neData.baseStation.nbName'),
dataIndex: 'nbName',
align: 'left',
width: 80,
},
{
title: t('views.neData.baseStation.state'),
dataIndex: 'state', dataIndex: 'state',
key: 'state', key: 'state',
align: 'left', align: 'left',
width: 80, width: 80,
}, },
{ {
title: 'Time', title: t('views.neData.baseStation.time'),
align: 'left', align: 'left',
width: 150, width: 150,
customRender(opt) { customRender(opt) {
const record = opt.value; const record = opt.value;
console.log(opt)
if (record.state === 'OFF') { if (record.state === 'OFF') {
return record.offTime; return record.offTime || '-';
} }
return record.onTime; return record.onTime || '-';
}, },
}, },
]); ]);
@@ -277,13 +284,14 @@ let modalState: ModalStateType = reactive({
const modalStateFrom = Form.useForm( const modalStateFrom = Form.useForm(
modalState.from, modalState.from,
reactive({ reactive({
address: [{ required: true, message: `text content length 0~64` }], address: [
name: [{ required: true, message: `text content length 0~64` }], { required: true, message: t('views.neData.baseStation.addressPlease') },
],
name: [
{ required: true, message: t('views.neData.baseStation.namePlease') },
],
position: [ position: [
{ { required: true, message: t('views.neData.baseStation.positionPlease') },
required: true,
message: `location description. Prohibition of spaces, length of text content 0-64`,
},
], ],
}) })
); );
@@ -295,7 +303,7 @@ const modalStateFrom = Form.useForm(
function fnModalVisibleByEdit(edit?: string | number) { function fnModalVisibleByEdit(edit?: string | number) {
if (!edit) { if (!edit) {
modalStateFrom.resetFields(); //重置表单 modalStateFrom.resetFields(); //重置表单
modalState.title = 'Add Radio Info'; modalState.title = t('views.neData.baseStation.addRadio');
modalState.openByEdit = true; modalState.openByEdit = true;
// 获取最大index // 获取最大index
if (tableState.data.length <= 0) { if (tableState.data.length <= 0) {
@@ -312,7 +320,7 @@ function fnModalVisibleByEdit(edit?: string | number) {
}); });
modalStateFrom.resetFields(); //重置表单 modalStateFrom.resetFields(); //重置表单
Object.assign(modalState.from, row); Object.assign(modalState.from, row);
modalState.title = 'Edit Radio Info'; modalState.title = t('views.neData.baseStation.editRadio');
modalState.openByEdit = true; modalState.openByEdit = true;
} }
} }
@@ -427,8 +435,11 @@ onMounted(() => {
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="4" :md="6" :xs="24">
<a-form-item label="State" name="state"> <a-form-item
:label="t('views.neData.baseStation.state')"
name="state"
>
<a-select <a-select
v-model:value="queryParams.state" v-model:value="queryParams.state"
:options="nbState" :options="nbState"
@@ -546,7 +557,7 @@ onMounted(() => {
:labelWrap="true" :labelWrap="true"
> >
<a-form-item <a-form-item
label="Name" :label="t('views.neData.baseStation.name')"
name="name" name="name"
v-bind="modalStateFrom.validateInfos.name" v-bind="modalStateFrom.validateInfos.name"
> >
@@ -558,24 +569,24 @@ onMounted(() => {
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item <a-form-item
label="Address" :label="t('views.neData.baseStation.position')"
name="address" name="position"
v-bind="modalStateFrom.validateInfos.address" v-bind="modalStateFrom.validateInfos.position"
> >
<a-input <a-input
v-model:value="modalState.from.address" v-model:value="modalState.from.position"
allow-clear allow-clear
:maxlength="64" :maxlength="64"
> >
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item <a-form-item
label="Position" :label="t('views.neData.baseStation.address')"
name="position" name="address"
v-bind="modalStateFrom.validateInfos.position" v-bind="modalStateFrom.validateInfos.address"
> >
<a-input <a-input
v-model:value="modalState.from.position" v-model:value="modalState.from.address"
allow-clear allow-clear
:maxlength="64" :maxlength="64"
> >

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted, ref, onBeforeUnmount, useTemplateRef } from 'vue'; import { reactive, onMounted, ref, onBeforeUnmount, useTemplateRef } from 'vue';
import { Graph, GraphData, Menu, Tooltip } from '@antv/g6'; import { Graph, GraphData, Menu, Tooltip, Util } from '@antv/g6';
import { listAMFNbStatelist } from '@/api/neData/amf'; import { listAMFNbStatelist } from '@/api/neData/amf';
import { parseBasePath } from '@/plugins/file-static-url'; import { parseBasePath } from '@/plugins/file-static-url';
import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge'; import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge';
@@ -80,6 +80,22 @@ const graphNodeMenu = new Menu({
getContent(evt) { getContent(evt) {
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo'); if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
const { id, label, nType, nInfo }: any = evt.item?.getModel(); const { id, label, nType, nInfo }: any = evt.item?.getModel();
if (['GNB', 'ENB'].includes(nType)) {
return `
<div
style="
display: flex;
flex-direction: column;
width: 140px;
"
>
<span>
${t('views.neData.baseStation.name')}:
${label ?? '--'}
</span>
</div>
`;
}
return ` return `
<div <div
style=" style="
@@ -110,29 +126,29 @@ const graphNodeTooltip = new Tooltip({
style=" style="
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 228px; width: 256px;
" "
> >
<div><strong>${t('views.monitor.topology.state')}</strong><span> <div><strong>${t('views.neData.baseStation.state')}</strong><span>
${ ${
nInfo.state === 'ON' nInfo.state === 'ON'
? t('views.monitor.topology.normalcy') ? t('views.neData.baseStation.online')
: t('views.monitor.topology.exceptions') : t('views.neData.baseStation.offline')
} }
</span></div> </span></div>
<div><strong>OnTime</strong><span> <div><strong>${t('views.neData.baseStation.time')}</strong><span>
${nInfo.onTime ?? '--'} ${nInfo.state === 'ON' ? nInfo.onTime ?? '--' : nInfo.offTime ?? '--'}
</span></div> </span></div>
<div><strong>OffTime</strong><span> <div>==============================</div>
${nInfo.offTime ?? '--'}
</span></div>
<div>===========================</div>
<div><strong>ID</strong><span>${nInfo.index}</span></div> <div><strong>ID</strong><span>${nInfo.index}</span></div>
<div><strong>${t('views.monitor.topology.name')}</strong><span> <div><strong>${t('views.neData.baseStation.address')}</strong><span>
${nInfo.address}</span></div>
<div><strong>${t('views.neData.baseStation.name')}</strong><span>
${nInfo.name ?? '--'} ${nInfo.name ?? '--'}
</span></div> </span></div>
<div><strong>Address</strong><span>${nInfo.address}</span></div> <div><strong>${t(
<div><strong>Position</strong><span style="word-wrap: break-word;"> 'views.neData.baseStation.position'
)}</strong><span style="word-wrap: break-word;">
${nInfo.position} ${nInfo.position}
</span></div> </span></div>
</div> </div>
@@ -185,6 +201,33 @@ function registerEdgeNode() {
nodeImageAnimateState(); nodeImageAnimateState();
} }
/**
* format the string
* @param {string} str The origin string
* @param {number} maxWidth max width
* @param {number} fontSize font size
* @return {string} the processed result
*/
function fittingString(str: string, maxWidth: number, fontSize: number) {
let currentWidth = 0;
let res = str;
const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
str.split('').forEach((letter, i) => {
if (currentWidth > maxWidth) return;
if (pattern.test(letter)) {
// Chinese charactors
currentWidth += fontSize;
} else {
// get the width of single letter according to the fontSize
currentWidth += Util.getLetterWidth(letter, fontSize);
}
if (currentWidth > maxWidth) {
res = `${str.substring(0, i)}\n${str.substring(i)}`;
}
});
return res;
}
/**图事件 */ /**图事件 */
function graphEvent(graph: Graph) { function graphEvent(graph: Graph) {
graph.on('edge:mouseenter', evt => { graph.on('edge:mouseenter', evt => {
@@ -236,8 +279,8 @@ function handleRanderGraph(container: HTMLElement | null, data: GraphData) {
plugins: [graphNodeMenu, graphNodeTooltip], plugins: [graphNodeMenu, graphNodeTooltip],
layout: { layout: {
type: 'dagre', type: 'dagre',
rankdir: 'BT', // 布局的方向TB从上到下BT从下到上LR从左到右RL从右到左 rankdir: 'TB', // 布局的方向TB从上到下BT从下到上LR从左到右RL从右到左
align: 'UL', // 节点对齐方式 UL、UR、DL、DR //align: 'UL', // 节点对齐方式 UL、UR、DL、DR
controlPoints: true, controlPoints: true,
nodesep: 20, nodesep: 20,
ranksep: 40, ranksep: 40,
@@ -344,7 +387,7 @@ async function fnGraphDataBase() {
const node = { const node = {
id: 'OMC', id: 'OMC',
label: omcInfo.neName, label: omcInfo.neName,
img: parseBasePath('/svg/service.svg'), img: parseBasePath('/svg/service_db.svg'),
nInfo: { online: false, neId: omcInfo.neId, neType: omcInfo.neType }, nInfo: { online: false, neId: omcInfo.neId, neType: omcInfo.neType },
nType: 'OMC', nType: 'OMC',
}; };
@@ -393,7 +436,7 @@ async function fnGraphDataNb(data: GraphData) {
const id = `${item.id}_${nb.index}`; const id = `${item.id}_${nb.index}`;
data.nodes?.push({ data.nodes?.push({
id: id, id: id,
label: `${nb.name}`, label: fittingString(`${nb.name}`, 80, 14),
img: parseBasePath('/svg/base5G.svg'), img: parseBasePath('/svg/base5G.svg'),
nInfo: nb, nInfo: nb,
nType: 'GNB', nType: 'GNB',
@@ -531,7 +574,9 @@ onBeforeUnmount(() => {
<a-card :bordered="false" :body-style="{ padding: '0' }" ref="viewportDom"> <a-card :bordered="false" :body-style="{ padding: '0' }" ref="viewportDom">
<!-- 插槽-卡片左侧侧 --> <!-- 插槽-卡片左侧侧 -->
<template #title> <template #title>
<a-space :size="8" align="center"> Radio State Graph </a-space> <a-space :size="8" align="center">
{{ t('views.neData.baseStation.topologyTitle') }}
</a-space>
</template> </template>
<!-- 插槽-卡片右侧 --> <!-- 插槽-卡片右侧 -->
<template #extra> <template #extra>
@@ -540,7 +585,7 @@ onBeforeUnmount(() => {
<FullscreenExitOutlined v-if="isFullscreen" /> <FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else /> <FullscreenOutlined v-else />
</template> </template>
Full Screen {{ t('loayouts.rightContent.fullscreen') }}
</a-button> </a-button>
</template> </template>

View File

@@ -4,11 +4,16 @@ import {
type Component, type Component,
defineAsyncComponent, defineAsyncComponent,
shallowRef, shallowRef,
ref,
} from 'vue'; } from 'vue';
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const defineComponent = shallowRef<Component | null>(null); const defineComponent = shallowRef<Component | null>(null);
function fnSwitch(name: string) { const value = ref<string>('list');
function fnSwitch(evt: any) {
const name = evt.target?.value;
if (name === 'topology') { if (name === 'topology') {
defineComponent.value = defineAsyncComponent( defineComponent.value = defineAsyncComponent(
() => import('@/views/ne-data/base-station/components/topology.vue') () => import('@/views/ne-data/base-station/components/topology.vue')
@@ -22,15 +27,25 @@ function fnSwitch(name: string) {
} }
onMounted(() => { onMounted(() => {
fnSwitch('topology'); fnSwitch({ target: { value: 'list' } });
}); });
</script> </script>
<template> <template>
<PageContainer> <PageContainer>
<template #extra> <template #extra>
<a-button @click="fnSwitch('list')">List</a-button> <a-radio-group
<a-button type="primary" @click="fnSwitch('topology')">Topology</a-button> v-model:value="value"
button-style="solid"
@change="fnSwitch"
>
<a-radio-button value="list">
{{ t('views.neData.baseStation.list') }}
</a-radio-button>
<a-radio-button value="topology">
{{ t('views.neData.baseStation.topology') }}
</a-radio-button>
</a-radio-group>
</template> </template>
<component :is="defineComponent" /> <component :is="defineComponent" />

View File

@@ -23,7 +23,7 @@ import {
nextTick, nextTick,
onBeforeUnmount, onBeforeUnmount,
h, h,
watch watch,
} from 'vue'; } from 'vue';
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { message, Modal, TableColumnType } from 'ant-design-vue/es'; import { message, Modal, TableColumnType } from 'ant-design-vue/es';
@@ -410,12 +410,13 @@ function fnGetList() {
}); });
// 计算总值 // 计算总值
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;
kpiStats.value.push({ kpiStats.value.push({
kpiId: columns.key, kpiId: columns.key,
@@ -423,7 +424,7 @@ function fnGetList() {
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, avg: avg,
total: total total: total,
}); });
} }
} }
@@ -584,29 +585,31 @@ function fnRanderChartData() {
type: 'category', type: 'category',
boundaryGap: false, boundaryGap: false,
axisLabel: { axisLabel: {
color: document.documentElement.getAttribute('data-theme') === 'dark' color:
? '#CACADA' document.documentElement.getAttribute('data-theme') === 'dark'
: '#333' ? '#CACADA'
: '#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:
? '#CACADA' document.documentElement.getAttribute('data-theme') === 'dark'
: '#333' ? '#CACADA'
: '#333',
}, },
splitLine: { splitLine: {
show: true, show: true,
lineStyle: { lineStyle: {
color: getSplitLineColor() color: getSplitLineColor(),
} },
}, },
}, },
series: chartDataYSeriesData, series: chartDataYSeriesData,
@@ -754,59 +757,49 @@ function getSplitLineColor() {
// 监听主题变化 // 监听主题变化
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:
backgroundColor: newValue === 'dark' 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' textStyle: {
: '#ddd', color: newValue === 'dark' ? '#CACADA' : '#333',
textStyle: { },
color: newValue === 'dark' },
? '#CACADA' xAxis: {
: '#333' axisLabel: {
color: newValue === 'dark' ? '#CACADA' : '#333',
},
splitLine: {
show: true,
lineStyle: {
color: splitLineColor,
}, },
}, },
xAxis: { },
axisLabel: { yAxis: {
color: newValue === 'dark' axisLabel: {
? '#CACADA' color: newValue === 'dark' ? '#CACADA' : '#333',
: '#333'
},
splitLine: {
show: true,
lineStyle: {
color: splitLineColor
}
}
}, },
yAxis: { splitLine: {
axisLabel: { show: true,
color: newValue === 'dark' lineStyle: {
? '#CACADA' color: splitLineColor,
: '#333'
}, },
splitLine: { },
show: true, },
lineStyle: { });
color: splitLineColor
}
}
}
}
);
} }
} }
); );
@@ -878,39 +871,67 @@ 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" 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"
style="width: 100%"
></a-range-picker>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="4" :md="12" :xs="24"> <a-col :lg="4" :md="12" :xs="24">
<a-form-item :label="t('views.perfManage.goldTarget.interval')" name="interval"> <a-form-item
<a-select v-model:value="queryParams.interval" :placeholder="t('common.selectPlease')" :options="[ :label="t('views.perfManage.goldTarget.interval')"
{ label: '5S', value: 5 }, name="interval"
{ label: '1M', value: 60 }, >
{ label: '5M', value: 300 }, <a-select
{ label: '15M', value: 900 }, v-model:value="queryParams.interval"
{ label: '30M', value: 1800 }, :placeholder="t('common.selectPlease')"
{ label: '60M', value: 3600 }, :options="[
]" /> { label: '5S', value: 5 },
{ label: '1M', value: 60 },
{ label: '5M', value: 300 },
{ label: '15M', value: 900 },
{ label: '30M', value: 1800 },
{ label: '60M', value: 3600 },
]"
/>
</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>
@@ -927,7 +948,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>
@@ -937,8 +962,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>
@@ -958,8 +987,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">
@@ -969,7 +1002,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>
@@ -998,19 +1034,37 @@ 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"
>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'timeGroup'"> <template v-if="column.key === 'timeGroup'">
{{ parseDateToStr(+record.timeGroup) }} {{ parseDateToStr(+record.timeGroup) }}
@@ -1020,15 +1074,40 @@ onBeforeUnmount(() => {
<!-- 图表 --> <!-- 图表 -->
<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"
onClick: () => handleRowClick(record), :data-source="kpiStats"
class: selectedRow.includes(record.kpiId) ? 'selected-row' : '' :pagination="false"
}) :scroll="{ y: 250 }"
" /> size="small"
:custom-row="
record => ({
onClick: () => handleRowClick(record),
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>
</a-table>
</div> </div>
</div> </div>
</a-card> </a-card>