diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 99bce785..5efa3e7f 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -1065,6 +1065,8 @@ export default { expression:'Expression', description:' Description', kpiSet:' Statistical Settings', + sixHoursAgo:'Six hours ago', + threeHoursAgo:'Three hours ago.', delCustomTip:'Confirm deletion of data item with record number {num}?', delCustom:' Successfully delete record number {num} custom indicator', addCustom:' Add custom indicator', @@ -1077,6 +1079,9 @@ export default { element:'Element', granularity:'Granularity', unit:'Unit', + expressionModal:'Expression Modal', + expressionErrorTip:'Please check the expression, the wrong indicator is {kpiId}', + expressionNoIdTip:'Please check the expression, no valid indicator is found', }, kpiKeyTarget:{ "fullWidthLayout":"Full Width", diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 5c1f0e8f..18cb6da9 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -1065,6 +1065,8 @@ export default { expression:'计算公式', description:'描述', kpiSet:'统计设置', + sixHoursAgo:'6小时前', + threeHoursAgo:'3小时前', delCustomTip:'确认删除记录编号为 {num} 的数据项?', delCustom:'成功删除记录编号为 {num} 自定义指标', addCustom:'添加自定义指标', @@ -1077,6 +1079,9 @@ export default { element:'元素', granularity:'颗粒度', unit:'单位', + expressionModal:'表达式模块', + expressionErrorTip:'请检查表达式,错误的指标为{kpiId}', + expressionNoIdTip:'请检查表达式,没有找到任何有效的指标', }, kpiKeyTarget:{ "fullWidthLayout":"全宽布局", @@ -1337,8 +1342,8 @@ export default { filter: "全局过滤", startTime: '开始时间', endTime: '结束时间', - today: '昨天', - yesterday: '今天', + today: '今天', + yesterday: '昨天', week: '本周', month: '本月', avgLoad: '平均负载', diff --git a/src/views/perfManage/customTarget/index.vue b/src/views/perfManage/customTarget/index.vue index e9daa452..85512ab0 100644 --- a/src/views/perfManage/customTarget/index.vue +++ b/src/views/perfManage/customTarget/index.vue @@ -90,6 +90,11 @@ let tableColumns: ColumnsType = [ dataIndex: 'title', align: 'center', }, + { + title: t('views.perfManage.customTarget.expression'), + dataIndex: 'exprAlias', + align: 'center', + }, { title: t('views.perfManage.customTarget.description'), dataIndex: 'description', @@ -218,6 +223,8 @@ type ModalStateType = { neTypPerformance: Record[]; /**已选择性能测量项 */ selectedPre: string[]; + /** 元素选择*/ + elemSelect: any; /**表单数据 */ from: Record; /**确定按钮 loading */ @@ -232,10 +239,10 @@ let modalState: ModalStateType = reactive({ neType: [], neTypPerformance: [], selectedPre: [], + elemSelect: '', from: { id: undefined, neType: 'UDM', - kpiId: '', title: '', expression: '', status: 'Active', @@ -275,13 +282,6 @@ const modalStateFrom = Form.useForm( t('common.unableNull'), }, ], - kpiId: [ - { - required: true, - message: - t('views.perfManage.customTarget.kpiId') + t('common.unableNull'), - }, - ], title: [ { required: true, @@ -302,6 +302,7 @@ const modalStateFrom = Form.useForm( /**性能测量数据集选择初始 value:neType*/ function fnSelectPerformanceInit(value: any) { modalState.from.expression = ''; + modalState.elemSelect = ''; modalState.neTypPerformance = [ { value: 'granularity', @@ -344,6 +345,7 @@ function fnModalVisibleByEdit(row?: any, id?: any) { } else { fnSelectPerformanceInit(row.neType); modalState.from = Object.assign(modalState.from, row); + modalState.from.expression = modalState.from.exprAlias; modalState.title = t('views.perfManage.customTarget.editCustom'); modalState.openByEdit = true; } @@ -356,7 +358,34 @@ function fnModalVisibleByEdit(row?: any, id?: any) { function fnModalOk() { modalStateFrom .validate() - .then(e => { + .then((e: any) => { + const matches = modalState.from.expression.match(/'([^']+)'/g); // 提取单引号内容 + // 替换为对应的 value + let result = modalState.from.expression; + + if (matches) { + for (const match of matches) { + const valueToReplace = match.slice(1, -1); // 去掉单引号 + const found = modalState.neTypPerformance.find( + (item: any) => item.label === valueToReplace + ); + if (found) { + result = result.replace(match, `'${found.value}'`); // 替换为对应的 value + } else { + message.error( + t('views.perfManage.customTarget.expressionErrorTip', { + kpiId: valueToReplace, + }), + 3 + ); + return; + } + } + } else { + message.error(t('views.perfManage.customTarget.expressionNoIdTip'), 3); + return false; + } + const from = toRaw(modalState.from); //return false; modalState.confirmLoading = true; @@ -405,12 +434,20 @@ function fnModalCancel() { * 选择性能指标,填充进当前计算公式的值 */ function fnSelectPer(s: any, option: any) { - modalState.from.expression += `'${s}'`; + console.log(option); + modalState.from.expression += `'${option.label}'`; } function fnSelectSymbol(s: any) { modalState.from.expression += s; } + +function fnChangeUnit(value: any) { + if (value === '%' && modalState.from.expression) { + modalState.from.expression = `(${modalState.from.expression})*100`; + } +} + /**网元参数 */ let neCascaderOptions = ref[]>([]); onMounted(() => { @@ -634,102 +671,6 @@ onMounted(() => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { + + + + + + + + + {{ + t('views.perfManage.customTarget.expressionModal') + }} + + + + + + + + + + + + + + + + + + + + + + + + + + []>([]); /**记录开始结束时间 */ let queryRangePicker = ref<[string, string]>(['', '']); +/**时间选择 */ +let ranges = ref>({ + [t('views.perfManage.customTarget.sixHoursAgo')]: [ + dayjs().subtract(6, 'hours'), + dayjs(), + ], + [t('views.perfManage.customTarget.threeHoursAgo')]: [ + dayjs().subtract(3, 'hours'), + dayjs(), + ], + [t('views.monitor.monitor.today')]: [dayjs().startOf('day'), dayjs()], +}); + /**表格字段列 */ let tableColumns = ref([]); @@ -260,12 +274,14 @@ function fnGetListTitle() { tableColumns.value = []; tableColumnsDnd.value = []; fnRanderChartData(); - return false; + return false; } tableColumns.value = []; const columns: any[] = []; for (const item of res.data) { - const kpiDisplay = item[`unit`]? item[`title`]+ `(${item['unit']})`:item[`title`]; + const kpiDisplay = item[`unit`] + ? item[`title`] + `(${item['unit']})` + : item[`title`]; const kpiValue = item[`kpiId`]; columns.push({ title: kpiDisplay, @@ -626,9 +642,9 @@ onMounted(() => { const now = new Date(); now.setMinutes(0, 0, 0); - queryRangePicker.value[0] = parseDateToStr(now.getTime()); + queryRangePicker.value[0] = `${now.getTime()}`; now.setMinutes(59, 59, 59); - queryRangePicker.value[1] = parseDateToStr(now.getTime()); + queryRangePicker.value[1] = `${now.getTime()}`; fnGetListTitle(); // 绘图 fnRanderChart(); @@ -658,10 +674,7 @@ onBeforeUnmount(() => { - + { :allow-clear="false" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" - value-format="YYYY-MM-DD HH:mm:ss" + value-format="x" + :ranges="ranges" style="width: 100%" > diff --git a/src/views/perfManage/kpiKeyTarget/index.vue b/src/views/perfManage/kpiKeyTarget/index.vue index 39bae357..58e0a880 100644 --- a/src/views/perfManage/kpiKeyTarget/index.vue +++ b/src/views/perfManage/kpiKeyTarget/index.vue @@ -250,7 +250,6 @@ const chartStates: Record> = O //日期选择器 interface RangePicker extends Record { placeholder: [string, string]; - ranges: Record; } // 日期选择器状态 @@ -258,17 +257,11 @@ const rangePicker = reactive({ ...Object.fromEntries(networkElementTypes.value.map(type => [ type, [ - dayjs().startOf('day').valueOf().toString(), // 0 点 0 分 0 秒 - dayjs().valueOf().toString() // 此时 + dayjs().startOf('hour').valueOf().toString(), // 当前小时内 + dayjs().endOf('hour').valueOf().toString() ] ])) as Record, placeholder: [t('views.monitor.monitor.startTime'), t('views.monitor.monitor.endTime')] as [string, string], - ranges: { - [t('views.monitor.monitor.yesterday')]: [dayjs().subtract(1, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day')], - [t('views.monitor.monitor.today')]: [dayjs().startOf('day'), dayjs()], - [t('views.monitor.monitor.week')]: [dayjs().startOf('week'), dayjs().endOf('week')], - [t('views.monitor.monitor.month')]: [dayjs().startOf('month'), dayjs().endOf('month')], - } as Record, }); // 可复用的图表初始化函数 @@ -896,7 +889,6 @@ const applyTwoColumnLayout = () => { format="YYYY-MM-DD HH:mm:ss" value-format="x" :placeholder="rangePicker.placeholder" - :ranges="rangePicker.ranges" style="width: 360px" @change="() => fetchData(item.i)" class="no-drag" diff --git a/src/views/perfManage/kpiOverView/index.vue b/src/views/perfManage/kpiOverView/index.vue index f5508fde..891ff313 100644 --- a/src/views/perfManage/kpiOverView/index.vue +++ b/src/views/perfManage/kpiOverView/index.vue @@ -19,7 +19,7 @@ interface KPIBase{ kpiId: string; title: string; } - +//继承接口 interface KPIColumn extends KPIBase{ dataIndex: string; key: string; @@ -49,8 +49,8 @@ const ws = ref(null); //日期范围响应式变量 const dateRange = ref<[string, string]>([ - dayjs().startOf('day').valueOf().toString(), - dayjs().valueOf().toString(), + dayjs().startOf('hour').valueOf().toString(), + dayjs().endOf('hour').valueOf().toString(), ]); //实时数据状态 const isRealtime = ref(false); @@ -98,6 +98,10 @@ const TARGET_KPI_IDS: Record = { // 实时数据开关函数 const fnRealTimeSwitch = (bool: boolean) => { if (bool) { + if(!chart){ + isRealtime.value=false; + return; + } if (!ws.value) { ws.value = new WS(); } @@ -141,6 +145,9 @@ const handleWebSocketMessage = (kpiEvent:any)=>{ }; //成功回调 const wsMessage = (res:Record)=>{ + if(!chart){ + return; + } const{code,data}=res; if(code===RESULT_CODE_ERROR||!data?.groupId)return; handleWebSocketMessage(data.data); @@ -304,27 +311,27 @@ const updateChart = () => { }, xAxis: { // 指定x轴类型为类目轴,适用于离散的类目数据 - type: 'category', + //type: 'category', // boundaryGap 控制坐标轴两边留白 // 当为折线图时(isLine为true)时不留白,柱状图时留白 // 这样可以让折线图从原点开始,柱状图有合适的间距 - boundaryGap: isLine, + //boundaryGap: isLine, // 设置x轴的数据 // 将时间戳转换为格式化的时间字符串 data:chartData.value.map(item=> dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss') ), // 设置坐标轴刻度标签的样式 - axisLabel: { - // 格式化函数,这里直接返回原值 - formatter: (value: string) => value, - // 刻度标签旋转角度,0表示不旋转 - rotate: 0, - // 自动计算标签显示的间隔,防止标签重叠 - interval: 'auto', // 自动计算显示间隔 - // 刻度标签对齐方式,右对齐 - align: 'right', - }, + // axisLabel: { + // // 格式化函数,这里直接返回原值 + // formatter: (value: string) => value, + // // 刻度标签旋转角度,0表示不旋转 + // rotate: 0, + // // 自动计算标签显示的间隔,防止标签重叠 + // interval: 'auto', // 自动计算显示间隔 + // // 刻度标签对齐方式,右对齐 + // align: 'right', + // }, }, yAxis: { // y轴配置 @@ -546,14 +553,18 @@ const { t, currentLocale } = useI18n(); // 更新图表数据方法 const updateChartData = (newData: ChartDataItem) => { - chartData.value.push(newData); - if (chartData.value.length > 100) { - chartData.value.shift(); + if(!chart){ + return; } - - if (chart) { + chartData.value.push(newData); + if (chartData.value.length > 50) {//100改为50 + chartData.value.shift();//大于100条时删除最早的数据 + } + //使用try-catch包裹图表更新逻辑 + try { requestAnimationFrame(() => { - chart!.setOption({ + if (!chart) return; + const option = { xAxis: { data: chartData.value.map(item => dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss') @@ -567,11 +578,15 @@ const updateChartData = (newData: ChartDataItem) => { name: kpi?.title || kpiId, }; }), - }); + }; + chart.setOption(option); }); + }catch (error){ + console.error('Failed to update chart:', error); } }; + // groupedKPIs 计算属性,使用 TARGET_KPI_IDS 来分组过滤 const groupedKPIs = computed(() => { const groups: Record = {}; @@ -685,29 +700,30 @@ const handleSecondaryKPIChange = (kpiId: string, checked: boolean) => { {{ neType.toUpperCase() }}