Merge remote-tracking branch 'origin/main' into lichang

This commit is contained in:
TsMask
2024-11-08 17:40:18 +08:00
7 changed files with 337 additions and 193 deletions

View File

@@ -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",

View File

@@ -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: '平均负载',

View File

@@ -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"

View File

@@ -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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -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)">