Merge remote-tracking branch 'origin/main' into lichang
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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: '平均负载',
|
||||
|
||||
@@ -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<string, any>[];
|
||||
/**已选择性能测量项 */
|
||||
selectedPre: string[];
|
||||
/** 元素选择*/
|
||||
elemSelect: any;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 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<Record<string, any>[]>([]);
|
||||
onMounted(() => {
|
||||
@@ -634,102 +671,6 @@ onMounted(() => {
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.title')"
|
||||
name="title"
|
||||
v-bind="modalStateFrom.validateInfos.title"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.title"
|
||||
:maxlength="255"
|
||||
allow-clear
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.kpiId')"
|
||||
name="kpiId"
|
||||
v-bind="modalStateFrom.validateInfos.kpiId"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.kpiId"
|
||||
:maxlength="16"
|
||||
allow-clear
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.expression')"
|
||||
name="expression"
|
||||
:label-col="{ span: 3 }"
|
||||
v-bind="modalStateFrom.validateInfos.expression"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.expression"
|
||||
:maxlength="1024"
|
||||
allow-clear
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
name="perSelect"
|
||||
:label="t('views.perfManage.customTarget.symbol')"
|
||||
>
|
||||
<a-select
|
||||
placeholder="Please select"
|
||||
:options="modalStateFromOption.symbolJson"
|
||||
@select="fnSelectSymbol"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
name="perSelect"
|
||||
:label="t('views.perfManage.customTarget.element')"
|
||||
>
|
||||
<a-select
|
||||
placeholder="Please select"
|
||||
:options="modalState.neTypPerformance"
|
||||
@select="fnSelectPer"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.unit')"
|
||||
name="expression"
|
||||
v-bind="modalStateFrom.validateInfos.unit"
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from.unit"
|
||||
:options="[
|
||||
{
|
||||
label: 'Mbps',
|
||||
value: 'Mbps',
|
||||
},
|
||||
{
|
||||
label: '%',
|
||||
value: '%',
|
||||
},
|
||||
]"
|
||||
></a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.status')"
|
||||
@@ -755,6 +696,94 @@ onMounted(() => {
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row>
|
||||
<a-col :lg="24" :md="24" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.title')"
|
||||
name="title"
|
||||
:label-col="{ span: 3 }"
|
||||
v-bind="modalStateFrom.validateInfos.title"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.title"
|
||||
:maxlength="255"
|
||||
allow-clear
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider orientation="left">{{
|
||||
t('views.perfManage.customTarget.expressionModal')
|
||||
}}</a-divider>
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.expression')"
|
||||
name="expression"
|
||||
:label-col="{ span: 3 }"
|
||||
v-bind="modalStateFrom.validateInfos.expression"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.expression"
|
||||
:maxlength="1024"
|
||||
autocomplete="off"
|
||||
allow-clear
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
name="elemSelect"
|
||||
:label="t('views.perfManage.customTarget.element')"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.elemSelect"
|
||||
placeholder="Please select"
|
||||
:options="modalState.neTypPerformance"
|
||||
@select="fnSelectPer"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
name="symbol"
|
||||
:label="t('views.perfManage.customTarget.symbol')"
|
||||
>
|
||||
<a-select
|
||||
placeholder="Please select"
|
||||
:options="modalStateFromOption.symbolJson"
|
||||
@select="fnSelectSymbol"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.unit')"
|
||||
name="expression"
|
||||
v-bind="modalStateFrom.validateInfos.unit"
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from.unit"
|
||||
@change="fnChangeUnit"
|
||||
:options="[
|
||||
{
|
||||
label: 'Mbps',
|
||||
value: 'Mbps',
|
||||
},
|
||||
{
|
||||
label: '%',
|
||||
value: '%',
|
||||
},
|
||||
]"
|
||||
></a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider></a-divider>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.description')"
|
||||
name="description"
|
||||
|
||||
@@ -42,6 +42,7 @@ import saveAs from 'file-saver';
|
||||
import { generateColorRGBA } from '@/utils/generate-utils';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import { useRoute } from 'vue-router';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
const neInfoStore = useNeInfoStore();
|
||||
const route = useRoute();
|
||||
const { t, currentLocale } = useI18n();
|
||||
@@ -77,6 +78,19 @@ let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
/**记录开始结束时间 */
|
||||
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
|
||||
/**时间选择 */
|
||||
let ranges = ref<Record<string, [Dayjs, Dayjs]>>({
|
||||
[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<any[]>([]);
|
||||
|
||||
@@ -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(() => {
|
||||
<a-form :model="queryParams" name="queryParamsFrom" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<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"
|
||||
@@ -681,7 +694,8 @@ 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%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
|
||||
@@ -250,7 +250,6 @@ const chartStates: Record<AllChartType, ReturnType<typeof createChartState>> = O
|
||||
//日期选择器
|
||||
interface RangePicker extends Record<AllChartType, [string, string]> {
|
||||
placeholder: [string, string];
|
||||
ranges: Record<string, [Dayjs, Dayjs]>;
|
||||
}
|
||||
|
||||
// 日期选择器状态
|
||||
@@ -258,17 +257,11 @@ const rangePicker = reactive<RangePicker>({
|
||||
...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<AllChartType, [string, string]>,
|
||||
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<string, [Dayjs, Dayjs]>,
|
||||
});
|
||||
|
||||
// 可复用的图表初始化函数
|
||||
@@ -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"
|
||||
|
||||
@@ -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<WS | null>(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<NeType, string[]> = {
|
||||
// 实时数据开关函数
|
||||
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<string,any>)=>{
|
||||
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<string, KPIColumn[]> = {};
|
||||
@@ -685,29 +700,30 @@ const handleSecondaryKPIChange = (kpiId: string, checked: boolean) => {
|
||||
<span class="card-title">{{ neType.toUpperCase() }}</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-dropdown v-if="secondaryKPIs[neType]?.length">
|
||||
<a-dropdown v-if="secondaryKPIs[neType]?.length" :trigger="['click']">
|
||||
<template #overlay>
|
||||
<div class="secondary-kpi-menu" @click.stop>
|
||||
<div
|
||||
v-for="kpi in secondaryKPIs[neType]"
|
||||
:key="kpi.kpiId"
|
||||
class="secondary-kpi-item"
|
||||
@click.stop
|
||||
>
|
||||
<a-checkbox
|
||||
:value="kpi.kpiId"
|
||||
:checked="tempSelectedKPIs.includes(kpi.kpiId)"
|
||||
@change="(e:any) => handleSecondaryKPIChange(kpi.kpiId, e.target.checked)"
|
||||
<div class="secondary-kpi-list">
|
||||
<div
|
||||
v-for="kpi in secondaryKPIs[neType]"
|
||||
:key="kpi.kpiId"
|
||||
class="secondary-kpi-item"
|
||||
@click.stop
|
||||
>
|
||||
{{ kpi.title }}
|
||||
</a-checkbox>
|
||||
<a-checkbox
|
||||
:value="kpi.kpiId"
|
||||
:checked="tempSelectedKPIs.includes(kpi.kpiId)"
|
||||
@change="(e:any) => handleSecondaryKPIChange(kpi.kpiId, e.target.checked)"
|
||||
@click.stop
|
||||
>
|
||||
{{ kpi.title }}
|
||||
</a-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a-button type="link" size="small">
|
||||
<a-button type="link" size="small" class="more-metrics-btn">
|
||||
<more-outlined />
|
||||
<down-outlined />
|
||||
<span class="secondary-count">({{ secondaryKPIs[neType].length }})</span>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
@@ -762,20 +778,81 @@ const handleSecondaryKPIChange = (kpiId: string, checked: boolean) => {
|
||||
}
|
||||
/* 其他指标下拉菜单样式 */
|
||||
.secondary-kpi-menu {
|
||||
background: #fff;
|
||||
padding: 4px 0;
|
||||
border-radius: 2px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 6px 16px 0 rgba(0, 0, 0, 0.08);
|
||||
0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||
0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
min-width: 240px;
|
||||
max-width: 320px;
|
||||
}
|
||||
[data-theme='light'] .secondary-kpi-menu {
|
||||
background: #fff;
|
||||
}
|
||||
[data-theme='dark'] .secondary-kpi-menu {
|
||||
background: #1f1f1f;
|
||||
}
|
||||
|
||||
.secondary-kpi-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
min-width: 200px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.secondary-kpi-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.secondary-kpi-list::-webkit-scrollbar-thumb {
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.secondary-kpi-list::-webkit-scrollbar-track {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.secondary-kpi-item {
|
||||
padding: 8px 12px;
|
||||
transition: background-color 0.3s;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.more-metrics-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: #1890ff;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.more-metrics-btn:hover {
|
||||
color: #40a9ff;
|
||||
background: rgba(24, 144, 255, 0.1);
|
||||
}
|
||||
|
||||
.secondary-count {
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* 优化复选框样式 */
|
||||
:deep(.ant-checkbox-wrapper) {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
:deep(.ant-checkbox-wrapper:hover) {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
:deep(.ant-checkbox-checked .ant-checkbox-inner) {
|
||||
background-color: #1890ff;
|
||||
border-color: #1890ff;
|
||||
}
|
||||
|
||||
/* 组件统一样式 */
|
||||
@@ -798,9 +875,4 @@ const handleSecondaryKPIChange = (kpiId: string, checked: boolean) => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 添加次要指标数量的样式 */
|
||||
.secondary-count {
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,6 +5,7 @@ import useI18n from '@/hooks/useI18n';
|
||||
import { listMenu } from '@/api/system/menu';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { getConfigKey, changeValue } from '@/api/system/config';
|
||||
import { parseDataToTree } from '@/utils/parse-tree-utils';
|
||||
const { t } = useI18n();
|
||||
|
||||
type StateType = {
|
||||
@@ -59,14 +60,21 @@ onMounted(() => {
|
||||
listMenu(toRaw({ status: 1 })).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
// 过滤旧前端菜单以及不是菜单类型以及路径为空
|
||||
res.data = res.data
|
||||
.filter(i => i.perms !== 'page' && i.menuType === 'M' && i.component)
|
||||
.map((item: any) => {
|
||||
state.options.push({
|
||||
label: item.menuName,
|
||||
value: item.component,
|
||||
});
|
||||
});
|
||||
res.data = res.data.filter(i => i.perms !== 'page' && i.menuType !== 'B');
|
||||
state.options = parseDataToTree(res.data, 'menuId');
|
||||
const setDisabledAndComponent = (item:any) => {
|
||||
if (!item.component) {
|
||||
item.disabled = true;
|
||||
item.component = 'Null' + item.menuId;
|
||||
}
|
||||
};
|
||||
|
||||
state.options.forEach((item:any) => {
|
||||
setDisabledAndComponent(item); // 处理父菜单
|
||||
if (item.children && Array.isArray(item.children)) {
|
||||
item.children.forEach(setDisabledAndComponent); // 处理子菜单
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -84,13 +92,22 @@ onMounted(() => {
|
||||
<a-col :lg="12" :md="12" :xs="24" style="margin-bottom: 30px">
|
||||
<template v-if="state.edite">
|
||||
<a-form-item :label="t('views.system.setting.home')">
|
||||
<a-select
|
||||
ref="select"
|
||||
<a-tree-select
|
||||
v-model:value="state.default"
|
||||
show-search
|
||||
style="width: 240px"
|
||||
:disabled="false"
|
||||
:options="state.options"
|
||||
></a-select>
|
||||
:tree-data="state.options"
|
||||
:field-names="{
|
||||
children: 'children',
|
||||
label: 'menuName',
|
||||
value: 'component',
|
||||
}"
|
||||
tree-default-expand-all
|
||||
tree-node-label-prop="menuName"
|
||||
tree-node-filter-prop="menuName"
|
||||
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
></a-tree-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-button type="primary" @click="fnSave">
|
||||
@@ -102,13 +119,23 @@ onMounted(() => {
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form-item :label="t('views.system.setting.home')">
|
||||
<a-select
|
||||
ref="select"
|
||||
<a-tree-select
|
||||
v-model:value="state.default"
|
||||
style="width: 240px"
|
||||
:disabled="true"
|
||||
:options="state.options"
|
||||
></a-select>
|
||||
show-search
|
||||
style="width: 240px"
|
||||
:tree-data="state.options"
|
||||
:field-names="{
|
||||
children: 'children',
|
||||
label: 'menuName',
|
||||
value: 'component',
|
||||
}"
|
||||
tree-default-expand-all
|
||||
tree-node-label-prop="menuName"
|
||||
tree-node-filter-prop="menuName"
|
||||
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
></a-tree-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-button type="dashed" @click="fnEdit(true)">
|
||||
|
||||
Reference in New Issue
Block a user