feat: mml命令操作支持输入多行发送

This commit is contained in:
TsMask
2023-11-22 15:27:13 +08:00
parent a960e77fc6
commit d870f12daf
5 changed files with 747 additions and 309 deletions

View File

@@ -216,7 +216,7 @@ export default {
tabPane1: 'Account password login',
tabPane2: 'Login with phone number',
registerBtn: 'Register an account',
loginBtn: 'Login',
loginBtn: 'Sign In',
loginSuccess: 'Login successful',
loginMethod: 'Other login methods:',
loginMethodWX: 'WeChat Scan Login',
@@ -1252,7 +1252,6 @@ export default {
},
},
mmlManage: {
operationtitle: "Interface Settings",
cmdTitle: "Command Navigator",
cmdConsole: "consoles",
cmdOpTip: "Select the item to be operated in the left command navigation!",
@@ -1265,8 +1264,10 @@ export default {
requireIpv6: "{display} Not a legitimate IPV6 address.",
requireEnum: "{display} is not a reasonable enumeration value.",
requireBool: "{display} is not a reasonable boolean value.",
clearForm: "Clear Forms",
clearLog: "Clearing logs",
cmdQuickEntry: "Command Quick Entry",
cmdParamPanel: "Parameter Panel",
clearForm: "Reset",
clearLog: "Clear Logs",
exec: "Execute",
cmdAwait: "Waiting for a command to be sent",
omcOperate:{
@@ -1276,6 +1277,7 @@ export default {
noUDM: "No UDM network elements",
},
mmlSet: {
operationtitle: "Interface Settings",
saveText: "Save Settings",
ipadd: "Listening to IP addresses",
ipaddPlease: "Please enter the listening IP address",

View File

@@ -1264,7 +1264,9 @@ export default {
requireIpv6: "{display} 不是合法的IPV6地址",
requireEnum: "{display} 不是合理的枚举值",
requireBool: "{display} 不是合理的布尔类型的值",
clearForm: "清除表单",
cmdQuickEntry: "命令快速输入",
cmdParamPanel: "参数面板",
clearForm: "重置",
clearLog: "清除日志",
exec: "执行",
cmdAwait: "等待发送命令",

View File

@@ -11,7 +11,7 @@ import { getMMLByNE, sendMMlByNE } from '@/api/mmlManage/neOperate';
const { t } = useI18n();
/**网元参数 */
let neCascaderOtions = ref<Record<string, any>[]>([]);
let neCascaderOptions = ref<Record<string, any>[]>([]);
/**对象信息状态类型 */
type StateType = {
@@ -25,6 +25,12 @@ type StateType = {
mmlSelect: Record<string, any>;
/**表单数据 */
from: Record<string, any>;
/**自动完成 input */
autoCompleteValue: string;
/**自动完成 options */
autoCompleteData: any[];
/**自动完成 options */
autoCompleteSearch: any[];
/**命令发送日志 */
mmlCmdLog: string;
};
@@ -34,10 +40,19 @@ let state: StateType = reactive({
neType: [],
mmlNeType: '',
mmlTreeData: [],
mmlSelect: {},
mmlSelect: {
title: '',
key: '',
operation: '',
object: '',
param: [],
},
from: {
sendLoading: false,
},
autoCompleteValue: '',
autoCompleteData: [],
autoCompleteSearch: [],
mmlCmdLog: '',
});
@@ -45,6 +60,8 @@ let state: StateType = reactive({
function fnTreeSelect(_: any, info: any) {
state.mmlSelect = info.node.dataRef;
state.from = {};
state.autoCompleteValue =
`${state.mmlSelect.operation} ${state.mmlSelect.object}`.trim();
// state.mmlCmdLog = '';
}
@@ -55,6 +72,13 @@ function fnCleanCmdLog() {
/**清空表单 */
function fnCleanFrom() {
state.mmlSelect = {
title: '',
key: '',
operation: '',
object: '',
param: [],
};
state.from = {};
}
@@ -63,51 +87,64 @@ function fnSendMML() {
if (state.from.sendLoading) {
return;
}
let cmdStr = '';
const operation = state.mmlSelect.operation;
const object = state.mmlSelect.object;
let cmdStr = '';
// 根据参数取值
let argsArr: string[] = [];
const param = toRaw(state.mmlSelect.param) || [];
const from = toRaw(state.from);
for (const item of param) {
const value = from[item.name];
if (operation && Array.isArray(param)) {
const from = toRaw(state.from);
for (const item of param) {
const value = from[item.name];
// 是否必填项且有效值
const notV = value === null || value === undefined || value === '';
if (item.optional === 'false' && notV) {
message.warning(t('views.mmlManage.require', { num: item.display }), 2);
return;
// 是否必填项且有效值
const notV = value === null || value === undefined || value === '';
if (item.optional === 'false' && notV) {
message.warning(t('views.mmlManage.require', { num: item.display }), 2);
return;
}
// 检查是否存在值
if (!Reflect.has(from, item.name) || notV) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(item, from[item.name]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
argsArr.push(`${item.name}=${from[item.name]}`);
}
// 检查是否存在值
if (!Reflect.has(from, item.name) || notV) {
continue;
// 拼装命令
const argsStr = argsArr.join(',');
if (object && argsStr) {
cmdStr = `${operation} ${object} ${argsStr}`;
} else if (object) {
cmdStr = `${operation} ${object}`;
} else {
cmdStr = `${operation} ${argsStr}`;
}
// 检查规则
const [ok, msg] = ruleVerification(item, from[item.name]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
argsArr.push(`${item.name}=${from[item.name]}`);
cmdStr = cmdStr.trim();
}
// 拼装命令
const argsStr = argsArr.join(',');
if (object && argsStr) {
cmdStr = `${operation} ${object} ${argsStr}`;
} else if (object) {
cmdStr = `${operation} ${object}`;
if (cmdStr) {
state.autoCompleteValue = cmdStr;
} else {
cmdStr = `${operation} ${argsStr}`;
let value = state.autoCompleteValue;
if (value.indexOf('\n') !== -1) {
value = value.replace(/(\r\n|\n)/g, ';');
}
cmdStr = value;
}
cmdStr = cmdStr.trim();
// 发送
state.mmlCmdLog += `${cmdStr}\n`;
@@ -215,7 +252,7 @@ function ruleVerification(
break;
default:
return [false, t('views.mmlManage.requireUn', { display })];
console.warn(t('views.mmlManage.requireUn', { display }), type);
}
return result;
}
@@ -224,8 +261,16 @@ function ruleVerification(
function fnNeChange(keys: any, _: any) {
// 不是同类型时需要重新加载
if (state.mmlNeType !== keys[0]) {
state.autoCompleteSearch = [];
state.autoCompleteData = [];
state.mmlTreeData = [];
state.mmlSelect = {};
state.mmlSelect = {
title: '',
key: '',
operation: '',
object: '',
param: {},
};
fnGetList();
}
}
@@ -236,6 +281,8 @@ function fnGetList() {
state.mmlNeType = neType;
getMMLByNE(neType).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// 构建自动完成筛选结构
const autoCompleteArr: Record<string, any>[] = [];
// 构建树结构
const treeArr: Record<string, any>[] = [];
for (const item of res.data) {
@@ -252,8 +299,8 @@ function fnGetList() {
}
// 遍历检查大类
const treeItem = treeArr.find(i => i.key == item['category']);
if (!treeItem) {
const treeItemIndex = treeArr.findIndex(i => i.key == item['category']);
if (treeItemIndex < 0) {
treeArr.push({
title: item['catDisplay'],
key: item['category'],
@@ -262,17 +309,31 @@ function fnGetList() {
{ key: id, title: mmlDisplay, object, operation, param },
],
});
autoCompleteArr.push({
value: item['catDisplay'],
key: item['category'],
selectable: false,
options: [{ key: id, value: mmlDisplay, object, operation, param }],
});
} else {
treeItem.children.push({
treeArr[treeItemIndex].children.push({
key: id,
title: mmlDisplay,
object,
operation,
param,
});
autoCompleteArr[treeItemIndex].options.push({
key: id,
value: mmlDisplay,
object,
operation,
param,
});
}
}
state.mmlTreeData = treeArr;
state.autoCompleteData = autoCompleteArr;
} else {
message.warning({
content: t('views.mmlManage.cmdNoTip', { num: neType }),
@@ -282,6 +343,82 @@ function fnGetList() {
});
}
/**自动完成搜索匹配前缀 */
function fnAutoCompleteSearch(value: string) {
state.autoCompleteSearch = [];
for (const item of state.autoCompleteData) {
const filterOptions = item.options.filter((s: any) => {
return `${s.operation} ${s.object}`.indexOf(value.toLowerCase()) >= 0;
});
if (filterOptions.length > 0) {
state.autoCompleteSearch.push({
value: item.value,
key: item.value,
selectable: false,
options: filterOptions,
});
}
}
}
/**自动完成搜索选择 */
function fnAutoCompleteSelect(_: any, option: any) {
state.mmlSelect = {
title: option.value,
key: option.key,
operation: option.operation,
object: option.object,
param: option.param,
};
state.from = {};
state.autoCompleteValue = `${option.operation} ${option.object}`.trim();
}
/**自动完成搜索选择 */
function fnAutoCompleteChange(value: any, _: any) {
if (value.indexOf(';') !== -1 || value.indexOf('\n') !== -1) {
fnCleanFrom();
return;
}
for (const item of state.autoCompleteData) {
const findItem = item.options.find((s: any) => {
const prefix = `${s.operation} ${s.object}`;
return value.startsWith(prefix);
});
if (findItem) {
state.mmlSelect = {
title: findItem.value,
key: findItem.key,
operation: findItem.operation,
object: findItem.object,
param: findItem.param,
};
state.from = {};
// 截取拆分赋值
const prefix = `${findItem.operation} ${findItem.object} `;
const argsStr = value.replace(prefix, '');
if (argsStr.length > 3) {
const argsArr = argsStr.split(',');
for (const arg of argsArr) {
const kvArr = arg.split('=');
if (kvArr.length >= 2) {
state.from[kvArr[0]] = kvArr[1];
}
}
}
break;
} else {
state.mmlSelect = {
title: '',
key: '',
operation: '',
object: '',
param: [],
};
}
}
}
onMounted(() => {
// 获取网元网元列表
useNeInfoStore()
@@ -290,18 +427,18 @@ onMounted(() => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
// 过滤不可用的网元
neCascaderOtions.value = useNeInfoStore().getNeCascaderOtions.filter(
neCascaderOptions.value = useNeInfoStore().getNeCascaderOtions.filter(
(item: any) => {
return !['OMC'].includes(item.value);
}
);
// 默认选择AMF
const item = neCascaderOtions.value.find(s => s.value === 'AMF');
const item = neCascaderOptions.value.find(s => s.value === 'AMF');
if (item && item.children) {
const info = item.children[0];
state.neType = [info.neType, info.neId];
} else {
const info = neCascaderOtions.value[0].children[0];
const info = neCascaderOptions.value[0].children[0];
state.neType = [info.neType, info.neId];
}
fnGetList();
@@ -330,16 +467,18 @@ onMounted(() => {
<a-form-item name="neType">
<a-cascader
v-model:value="state.neType"
:options="neCascaderOtions"
:options="neCascaderOptions"
@change="fnNeChange"
:allow-clear="false"
:placeholder="t('common.selectPlease')"
/>
</a-form-item>
<a-form-item name="listeningPort">
<a-form-item name="mmlTree" v-if="state.mmlTreeData.length > 0">
<a-directory-tree
:tree-data="state.mmlTreeData"
default-expand-all
@select="fnTreeSelect"
:selectedKeys="[state.mmlSelect.key]"
></a-directory-tree>
</a-form-item>
</a-form>
@@ -347,11 +486,7 @@ onMounted(() => {
</a-col>
<a-col :span="18">
<!-- 命令参数输入 -->
<a-card
size="small"
:bordered="false"
:loading="!state.mmlSelect.title"
>
<a-card size="small" :bordered="false">
<template #title>
<a-typography-text strong v-if="state.mmlSelect.title">
{{ state.mmlSelect.title }}
@@ -377,7 +512,6 @@ onMounted(() => {
<a-button
type="primary"
size="small"
:disabled="!state.mmlSelect.title"
:loading="state.from.sendLoading"
@click.prevent="fnSendMML"
>
@@ -395,58 +529,74 @@ onMounted(() => {
:validate-on-rule-change="false"
:validateTrigger="[]"
>
<a-row :gutter="16">
<a-col
:lg="6"
:md="12"
:xs="24"
v-for="item in state.mmlSelect.param"
<a-form-item :label="t('views.mmlManage.cmdQuickEntry')">
<a-auto-complete
v-model:value="state.autoCompleteValue"
:dropdown-match-select-width="500"
style="width: 100%"
:options="state.autoCompleteSearch"
@search="fnAutoCompleteSearch"
@select="fnAutoCompleteSelect"
@change="fnAutoCompleteChange"
>
<a-form-item
:label="item.display"
:name="item.name"
:required="item.optional === 'false'"
<a-textarea :placeholder="t('common.ipnutPlease')" auto-size />
</a-auto-complete>
</a-form-item>
<template v-if="state.mmlSelect.operation && state.mmlSelect.param">
<a-divider orientation="left">
{{ t('views.mmlManage.cmdParamPanel') }}
</a-divider>
<a-row :gutter="16">
<a-col
:lg="6"
:md="12"
:xs="24"
v-for="item in state.mmlSelect.param"
>
<a-tooltip>
<template #title v-if="item.comment">
{{ item.comment }}
</template>
<a-input
v-if="
['string', 'ipv6', 'ipv4', 'regex'].includes(item.type)
"
v-model:value="state.from[item.name]"
></a-input>
<a-input-number
v-else-if="item.type === 'int'"
v-model:value="state.from[item.name]"
:min="0"
:max="65535"
style="width: 100%"
></a-input-number>
<a-switch
v-else-if="item.type === 'bool'"
v-model:checked="state.from[item.name]"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
></a-switch>
<a-select
v-else-if="item.type === 'enum'"
v-model:value="state.from[item.name]"
:allow-clear="true"
>
<a-select-option
:value="v"
:key="v"
v-for="(k, v) in JSON.parse(item.filter)"
<a-form-item
:label="item.display"
:name="item.name"
:required="item.optional === 'false'"
>
<a-tooltip>
<template #title v-if="item.comment">
{{ item.comment }}
</template>
<a-input-number
v-if="item.type === 'int'"
v-model:value="state.from[item.name]"
style="width: 100%"
></a-input-number>
<a-switch
v-else-if="item.type === 'bool'"
v-model:checked="state.from[item.name]"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
></a-switch>
<a-select
v-else-if="item.type === 'enum'"
v-model:value="state.from[item.name]"
:allow-clear="item.optional === 'true'"
>
{{ k }}
</a-select-option>
</a-select>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
<a-select-option
:value="v"
:key="v"
v-for="(k, v) in JSON.parse(item.filter)"
>
{{ k }}
</a-select-option>
</a-select>
<a-input
v-else
v-model:value="state.from[item.name]"
></a-input>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
</template>
</a-form>
</a-card>
@@ -457,7 +607,7 @@ onMounted(() => {
size="small"
:body-style="{ padding: 0 }"
style="margin-top: 16px"
v-show="state.mmlSelect.title"
v-show="state.mmlCmdLog"
>
<!-- 插槽-卡片右侧 -->
<template #extra>

View File

@@ -11,20 +11,24 @@ import { getMMLByOMC, sendMMlByOMC } from '@/api/mmlManage/omcOperate';
const { t } = useI18n();
/**网元参数 */
let neOtions = ref<Record<string, any>[]>([]);
let neOptions = ref<Record<string, any>[]>([]);
/**对象信息状态类型 */
type StateType = {
/**网元ID */
neId: string;
/**命令数据 loading */
mmlLoading: boolean;
/**命令数据 tree */
mmlTreeData: any[];
/**命令选中 */
mmlSelect: Record<string, any>;
/**表单数据 */
from: Record<string, any>;
/**自动完成 input */
autoCompleteValue: string;
/**自动完成 options */
autoCompleteData: any[];
/**自动完成 options */
autoCompleteSearch: any[];
/**命令发送日志 */
mmlCmdLog: string;
};
@@ -32,12 +36,20 @@ type StateType = {
/**对象信息状态 */
let state: StateType = reactive({
neId: '',
mmlLoading: true,
mmlTreeData: [],
mmlSelect: {},
mmlSelect: {
title: '',
key: '',
operation: '',
object: '',
param: [],
},
from: {
sendLoading: false,
},
autoCompleteValue: '',
autoCompleteData: [],
autoCompleteSearch: [],
mmlCmdLog: '',
});
@@ -45,6 +57,8 @@ let state: StateType = reactive({
function fnTreeSelect(_: any, info: any) {
state.mmlSelect = info.node.dataRef;
state.from = {};
state.autoCompleteValue =
`${state.mmlSelect.operation} ${state.mmlSelect.object}`.trim();
// state.mmlCmdLog = '';
}
@@ -55,6 +69,13 @@ function fnCleanCmdLog() {
/**清空表单 */
function fnCleanFrom() {
state.mmlSelect = {
title: '',
key: '',
operation: '',
object: '',
param: [],
};
state.from = {};
}
@@ -63,51 +84,64 @@ function fnSendMML() {
if (state.from.sendLoading) {
return;
}
let cmdStr = '';
const operation = state.mmlSelect.operation;
const object = state.mmlSelect.object;
let cmdStr = '';
// 根据参数取值
let argsArr: string[] = [];
const param = toRaw(state.mmlSelect.param) || [];
const from = toRaw(state.from);
for (const item of param) {
const value = from[item.name];
if (operation && Array.isArray(param)) {
const from = toRaw(state.from);
for (const item of param) {
const value = from[item.name];
// 是否必填项且有效值
const notV = value === null || value === undefined || value === '';
if (item.optional === 'false' && notV) {
message.warning(t('views.mmlManage.require', { num: item.display }), 2);
return;
// 是否必填项且有效值
const notV = value === null || value === undefined || value === '';
if (item.optional === 'false' && notV) {
message.warning(t('views.mmlManage.require', { num: item.display }), 2);
return;
}
// 检查是否存在值
if (!Reflect.has(from, item.name) || notV) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(item, from[item.name]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
argsArr.push(`${item.name}=${from[item.name]}`);
}
// 检查是否存在值
if (!Reflect.has(from, item.name) || notV) {
continue;
// 拼装命令
const argsStr = argsArr.join(',');
if (object && argsStr) {
cmdStr = `${operation} ${object}:${argsStr}`;
} else if (object) {
cmdStr = `${operation} ${object}`;
} else {
cmdStr = `${operation} ${argsStr}`;
}
// 检查规则
const [ok, msg] = ruleVerification(item, from[item.name]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
argsArr.push(`${item.name}=${from[item.name]}`);
cmdStr = cmdStr.trim();
}
// 拼装命令
const argsStr = argsArr.join(',');
if (object && argsStr) {
cmdStr = `${operation} ${object}:${argsStr}`;
} else if (object) {
cmdStr = `${operation} ${object}`;
if (cmdStr) {
state.autoCompleteValue = cmdStr;
} else {
cmdStr = `${operation} ${argsStr}`;
let value = state.autoCompleteValue;
if (value.indexOf('\n') !== -1) {
value = value.replace(/(\r\n|\n)/g, ';');
}
cmdStr = value;
}
cmdStr = cmdStr.trim();
// 发送
state.mmlCmdLog += `${cmdStr}\n`;
@@ -214,7 +248,7 @@ function ruleVerification(
break;
default:
return [false, t('views.mmlManage.requireUn', { display })];
console.warn(t('views.mmlManage.requireUn', { display }), type);
}
return result;
}
@@ -223,6 +257,8 @@ function ruleVerification(
function fnGetList() {
getMMLByOMC().then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// 构建自动完成筛选结构
const autoCompleteArr: Record<string, any>[] = [];
// 构建树结构
const treeArr: Record<string, any>[] = [];
for (const item of res.data) {
@@ -239,8 +275,8 @@ function fnGetList() {
}
// 遍历检查大类
const treeItem = treeArr.find(i => i.key == item['category']);
if (!treeItem) {
const treeItemIndex = treeArr.findIndex(i => i.key == item['category']);
if (treeItemIndex < 0) {
treeArr.push({
title: item['catDisplay'],
key: item['category'],
@@ -249,22 +285,116 @@ function fnGetList() {
{ key: id, title: mmlDisplay, object, operation, param },
],
});
autoCompleteArr.push({
value: item['catDisplay'],
key: item['category'],
selectable: false,
options: [{ key: id, value: mmlDisplay, object, operation, param }],
});
} else {
treeItem.children.push({
treeArr[treeItemIndex].children.push({
key: id,
title: mmlDisplay,
object,
operation,
param,
});
autoCompleteArr[treeItemIndex].options.push({
key: id,
value: mmlDisplay,
object,
operation,
param,
});
}
}
state.mmlTreeData = treeArr;
state.autoCompleteData = autoCompleteArr;
} else {
message.warning({
content: t('views.mmlManage.cmdNoTip', { num: 'OMC' }),
duration: 2,
});
}
state.mmlLoading = false;
});
}
/**自动完成搜索匹配前缀 */
function fnAutoCompleteSearch(value: string) {
state.autoCompleteSearch = [];
for (const item of state.autoCompleteData) {
const filterOptions = item.options.filter((s: any) => {
return `${s.operation} ${s.object}`.indexOf(value.toLowerCase()) >= 0;
});
if (filterOptions.length > 0) {
state.autoCompleteSearch.push({
value: item.value,
key: item.value,
selectable: false,
options: filterOptions,
});
}
}
}
/**自动完成搜索选择 */
function fnAutoCompleteSelect(_: any, option: any) {
state.mmlSelect = {
title: option.value,
key: option.key,
operation: option.operation,
object: option.object,
param: option.param,
};
state.from = {};
state.autoCompleteValue = `${option.operation} ${option.object}`.trim();
}
/**自动完成搜索选择 */
function fnAutoCompleteChange(value: any, _: any) {
if (value.indexOf(';') !== -1 || value.indexOf('\n') !== -1) {
fnCleanFrom();
return;
}
for (const item of state.autoCompleteData) {
const findItem = item.options.find((s: any) => {
const prefix = `${s.operation} ${s.object}`;
return value.startsWith(prefix);
});
if (findItem) {
state.mmlSelect = {
title: findItem.value,
key: findItem.key,
operation: findItem.operation,
object: findItem.object,
param: findItem.param,
};
state.from = {};
// 截取拆分赋值
const prefix = `${findItem.operation} ${findItem.object}:`;
const argsStr = value.replace(prefix, '');
if (argsStr.length > 3) {
const argsArr = argsStr.split(',');
for (const arg of argsArr) {
const kvArr = arg.split('=');
if (kvArr.length >= 2) {
state.from[kvArr[0]] = kvArr[1];
}
}
}
break;
} else {
state.mmlSelect = {
title: '',
key: '',
operation: '',
object: '',
param: [],
};
}
}
}
onMounted(() => {
// 获取网元网元列表
useNeInfoStore()
@@ -278,7 +408,7 @@ onMounted(() => {
arr.push({ value: i.neId, label: i.neName });
}
});
neOtions.value = arr;
neOptions.value = arr;
if (arr.length > 0) {
state.neId = arr[0].value;
// 获取列表数据
@@ -309,20 +439,21 @@ onMounted(() => {
size="small"
:bordered="false"
:title="t('views.mmlManage.cmdTitle')"
:loading="state.mmlLoading"
>
<a-form layout="vertical" autocomplete="off">
<a-form-item name="neId ">
<a-select
v-model:value="state.neId"
:options="neOtions"
:options="neOptions"
:placeholder="t('common.selectPlease')"
/>
</a-form-item>
<a-form-item name="listeningPort">
<a-form-item name="mmlTree" v-if="state.mmlTreeData.length > 0">
<a-directory-tree
:tree-data="state.mmlTreeData"
default-expand-all
@select="fnTreeSelect"
:selectedKeys="[state.mmlSelect.key]"
></a-directory-tree>
</a-form-item>
</a-form>
@@ -330,11 +461,7 @@ onMounted(() => {
</a-col>
<a-col :span="18">
<!-- 命令参数输入 -->
<a-card
size="small"
:bordered="false"
:loading="!state.mmlSelect.title"
>
<a-card size="small" :bordered="false">
<template #title>
<a-typography-text strong v-if="state.mmlSelect.title">
{{ state.mmlSelect.title }}
@@ -360,7 +487,6 @@ onMounted(() => {
<a-button
type="primary"
size="small"
:disabled="!state.mmlSelect.title"
:loading="state.from.sendLoading"
@click.prevent="fnSendMML"
>
@@ -378,58 +504,74 @@ onMounted(() => {
:validate-on-rule-change="false"
:validateTrigger="[]"
>
<a-row :gutter="16">
<a-col
:lg="6"
:md="12"
:xs="24"
v-for="item in state.mmlSelect.param"
<a-form-item :label="t('views.mmlManage.cmdQuickEntry')">
<a-auto-complete
v-model:value="state.autoCompleteValue"
:dropdown-match-select-width="500"
style="width: 100%"
:options="state.autoCompleteSearch"
@search="fnAutoCompleteSearch"
@select="fnAutoCompleteSelect"
@change="fnAutoCompleteChange"
>
<a-form-item
:label="item.display"
:name="item.name"
:required="item.optional === 'false'"
<a-textarea :placeholder="t('common.ipnutPlease')" auto-size />
</a-auto-complete>
</a-form-item>
<template v-if="state.mmlSelect.operation && state.mmlSelect.param">
<a-divider orientation="left">
{{ t('views.mmlManage.cmdParamPanel') }}
</a-divider>
<a-row :gutter="16">
<a-col
:lg="6"
:md="12"
:xs="24"
v-for="item in state.mmlSelect.param"
>
<a-tooltip>
<template #title v-if="item.comment">
{{ item.comment }}
</template>
<a-input
v-if="
['string', 'ipv6', 'ipv4', 'regex'].includes(item.type)
"
v-model:value="state.from[item.name]"
></a-input>
<a-input-number
v-else-if="item.type === 'int'"
v-model:value="state.from[item.name]"
:min="0"
:max="65535"
style="width: 100%"
></a-input-number>
<a-switch
v-else-if="item.type === 'bool'"
v-model:checked="state.from[item.name]"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
></a-switch>
<a-select
v-else-if="item.type === 'enum'"
v-model:value="state.from[item.name]"
:allow-clear="true"
>
<a-select-option
:value="v"
:key="v"
v-for="(k, v) in JSON.parse(item.filter)"
<a-form-item
:label="item.display"
:name="item.name"
:required="item.optional === 'false'"
>
<a-tooltip>
<template #title v-if="item.comment">
{{ item.comment }}
</template>
<a-input-number
v-if="item.type === 'int'"
v-model:value="state.from[item.name]"
style="width: 100%"
></a-input-number>
<a-switch
v-else-if="item.type === 'bool'"
v-model:checked="state.from[item.name]"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
></a-switch>
<a-select
v-else-if="item.type === 'enum'"
v-model:value="state.from[item.name]"
:allow-clear="item.optional === 'true'"
>
{{ k }}
</a-select-option>
</a-select>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
<a-select-option
:value="v"
:key="v"
v-for="(k, v) in JSON.parse(item.filter)"
>
{{ k }}
</a-select-option>
</a-select>
<a-input
v-else
v-model:value="state.from[item.name]"
></a-input>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
</template>
</a-form>
</a-card>
@@ -440,7 +582,7 @@ onMounted(() => {
size="small"
:body-style="{ padding: 0 }"
style="margin-top: 16px"
v-show="state.mmlSelect.title"
v-show="state.mmlSelect.operation || state.autoCompleteValue"
>
<!-- 插槽-卡片右侧 -->
<template #extra>

View File

@@ -11,20 +11,24 @@ import { getMMLByUDM, sendMMlByUDM } from '@/api/mmlManage/udmOperate';
const { t } = useI18n();
/**网元参数 */
let neOtions = ref<Record<string, any>[]>([]);
let neOptions = ref<Record<string, any>[]>([]);
/**对象信息状态类型 */
type StateType = {
/**网元ID */
neId: string;
/**命令数据 loading */
mmlLoading: boolean;
/**命令数据 tree */
mmlTreeData: any[];
/**命令选中 */
mmlSelect: Record<string, any>;
/**表单数据 */
from: Record<string, any>;
/**自动完成 input */
autoCompleteValue: string;
/**自动完成 options */
autoCompleteData: any[];
/**自动完成 options */
autoCompleteSearch: any[];
/**命令发送日志 */
mmlCmdLog: string;
};
@@ -32,12 +36,20 @@ type StateType = {
/**对象信息状态 */
let state: StateType = reactive({
neId: '',
mmlLoading: true,
mmlTreeData: [],
mmlSelect: {},
mmlSelect: {
title: '',
key: '',
operation: '',
object: '',
param: [],
},
from: {
sendLoading: false,
},
autoCompleteValue: '',
autoCompleteData: [],
autoCompleteSearch: [],
mmlCmdLog: '',
});
@@ -45,6 +57,8 @@ let state: StateType = reactive({
function fnTreeSelect(_: any, info: any) {
state.mmlSelect = info.node.dataRef;
state.from = {};
state.autoCompleteValue =
`${state.mmlSelect.operation} ${state.mmlSelect.object}`.trim();
// state.mmlCmdLog = '';
}
@@ -55,6 +69,13 @@ function fnCleanCmdLog() {
/**清空表单 */
function fnCleanFrom() {
state.mmlSelect = {
title: '',
key: '',
operation: '',
object: '',
param: [],
};
state.from = {};
}
@@ -63,51 +84,64 @@ function fnSendMML() {
if (state.from.sendLoading) {
return;
}
let cmdStr = '';
const operation = state.mmlSelect.operation;
const object = state.mmlSelect.object;
let cmdStr = '';
// 根据参数取值
let argsArr: string[] = [];
const param = toRaw(state.mmlSelect.param) || [];
const from = toRaw(state.from);
for (const item of param) {
const value = from[item.name];
if (operation && Array.isArray(param)) {
const from = toRaw(state.from);
for (const item of param) {
const value = from[item.name];
// 是否必填项且有效值
const notV = value === null || value === undefined || value === '';
if (item.optional === 'false' && notV) {
message.warning(t('views.mmlManage.require', { num: item.display }), 2);
return;
// 是否必填项且有效值
const notV = value === null || value === undefined || value === '';
if (item.optional === 'false' && notV) {
message.warning(t('views.mmlManage.require', { num: item.display }), 2);
return;
}
// 检查是否存在值
if (!Reflect.has(from, item.name) || notV) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(item, from[item.name]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
argsArr.push(`${item.name}=${from[item.name]}`);
}
// 检查是否存在值
if (!Reflect.has(from, item.name) || notV) {
continue;
// 拼装命令
const argsStr = argsArr.join(',');
if (object && argsStr) {
cmdStr = `${operation} ${object}:${argsStr}`;
} else if (object) {
cmdStr = `${operation} ${object}`;
} else {
cmdStr = `${operation} ${argsStr}`;
}
// 检查规则
const [ok, msg] = ruleVerification(item, from[item.name]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
argsArr.push(`${item.name}=${from[item.name]}`);
cmdStr = cmdStr.trim();
}
// 拼装命令
const argsStr = argsArr.join(',');
if (object && argsStr) {
cmdStr = `${operation} ${object}:${argsStr}`;
} else if (object) {
cmdStr = `${operation} ${object}`;
if (cmdStr) {
state.autoCompleteValue = cmdStr;
} else {
cmdStr = `${operation} ${argsStr}`;
let value = state.autoCompleteValue;
if (value.indexOf('\n') !== -1) {
value = value.replace(/(\r\n|\n)/g, ';');
}
cmdStr = value;
}
cmdStr = cmdStr.trim();
// 发送
state.mmlCmdLog += `${cmdStr}\n`;
@@ -214,7 +248,7 @@ function ruleVerification(
break;
default:
return [false, t('views.mmlManage.requireUn', { display })];
console.warn(t('views.mmlManage.requireUn', { display }), type);
}
return result;
}
@@ -223,6 +257,8 @@ function ruleVerification(
function fnGetList() {
getMMLByUDM().then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// 构建自动完成筛选结构
const autoCompleteArr: Record<string, any>[] = [];
// 构建树结构
const treeArr: Record<string, any>[] = [];
for (const item of res.data) {
@@ -239,8 +275,8 @@ function fnGetList() {
}
// 遍历检查大类
const treeItem = treeArr.find(i => i.key == item['category']);
if (!treeItem) {
const treeItemIndex = treeArr.findIndex(i => i.key == item['category']);
if (treeItemIndex < 0) {
treeArr.push({
title: item['catDisplay'],
key: item['category'],
@@ -249,22 +285,116 @@ function fnGetList() {
{ key: id, title: mmlDisplay, object, operation, param },
],
});
autoCompleteArr.push({
value: item['catDisplay'],
key: item['category'],
selectable: false,
options: [{ key: id, value: mmlDisplay, object, operation, param }],
});
} else {
treeItem.children.push({
treeArr[treeItemIndex].children.push({
key: id,
title: mmlDisplay,
object,
operation,
param,
});
autoCompleteArr[treeItemIndex].options.push({
key: id,
value: mmlDisplay,
object,
operation,
param,
});
}
}
state.mmlTreeData = treeArr;
state.autoCompleteData = autoCompleteArr;
} else {
message.warning({
content: t('views.mmlManage.cmdNoTip', { num: 'UDM' }),
duration: 2,
});
}
state.mmlLoading = false;
});
}
/**自动完成搜索匹配前缀 */
function fnAutoCompleteSearch(value: string) {
state.autoCompleteSearch = [];
for (const item of state.autoCompleteData) {
const filterOptions = item.options.filter((s: any) => {
return `${s.operation} ${s.object}`.indexOf(value.toLowerCase()) >= 0;
});
if (filterOptions.length > 0) {
state.autoCompleteSearch.push({
value: item.value,
key: item.value,
selectable: false,
options: filterOptions,
});
}
}
}
/**自动完成搜索选择 */
function fnAutoCompleteSelect(_: any, option: any) {
state.mmlSelect = {
title: option.value,
key: option.key,
operation: option.operation,
object: option.object,
param: option.param,
};
state.from = {};
state.autoCompleteValue = `${option.operation} ${option.object}`.trim();
}
/**自动完成搜索选择 */
function fnAutoCompleteChange(value: any, _: any) {
if (value.indexOf(';') !== -1 || value.indexOf('\n') !== -1) {
fnCleanFrom();
return;
}
for (const item of state.autoCompleteData) {
const findItem = item.options.find((s: any) => {
const prefix = `${s.operation} ${s.object}`;
return value.startsWith(prefix);
});
if (findItem) {
state.mmlSelect = {
title: findItem.value,
key: findItem.key,
operation: findItem.operation,
object: findItem.object,
param: findItem.param,
};
state.from = {};
// 截取拆分赋值
const prefix = `${findItem.operation} ${findItem.object}:`;
const argsStr = value.replace(prefix, '');
if (argsStr.length > 3) {
const argsArr = argsStr.split(',');
for (const arg of argsArr) {
const kvArr = arg.split('=');
if (kvArr.length >= 2) {
state.from[kvArr[0]] = kvArr[1];
}
}
}
break;
} else {
state.mmlSelect = {
title: '',
key: '',
operation: '',
object: '',
param: [],
};
}
}
}
onMounted(() => {
// 获取网元网元列表
useNeInfoStore()
@@ -278,7 +408,7 @@ onMounted(() => {
arr.push({ value: i.neId, label: i.neName });
}
});
neOtions.value = arr;
neOptions.value = arr;
if (arr.length > 0) {
state.neId = arr[0].value;
// 获取列表数据
@@ -309,20 +439,21 @@ onMounted(() => {
size="small"
:bordered="false"
:title="t('views.mmlManage.cmdTitle')"
:loading="state.mmlLoading"
>
<a-form layout="vertical" autocomplete="off">
<a-form-item name="neId ">
<a-select
v-model:value="state.neId"
:options="neOtions"
:options="neOptions"
:placeholder="t('common.selectPlease')"
/>
</a-form-item>
<a-form-item name="listeningPort">
<a-form-item name="mmlTree" v-if="state.mmlTreeData.length > 0">
<a-directory-tree
:tree-data="state.mmlTreeData"
default-expand-all
@select="fnTreeSelect"
:selectedKeys="[state.mmlSelect.key]"
></a-directory-tree>
</a-form-item>
</a-form>
@@ -330,11 +461,7 @@ onMounted(() => {
</a-col>
<a-col :span="18">
<!-- 命令参数输入 -->
<a-card
size="small"
:bordered="false"
:loading="!state.mmlSelect.title"
>
<a-card size="small" :bordered="false">
<template #title>
<a-typography-text strong v-if="state.mmlSelect.title">
{{ state.mmlSelect.title }}
@@ -360,7 +487,6 @@ onMounted(() => {
<a-button
type="primary"
size="small"
:disabled="!state.mmlSelect.title"
:loading="state.from.sendLoading"
@click.prevent="fnSendMML"
>
@@ -378,58 +504,74 @@ onMounted(() => {
:validate-on-rule-change="false"
:validateTrigger="[]"
>
<a-row :gutter="16">
<a-col
:lg="6"
:md="12"
:xs="24"
v-for="item in state.mmlSelect.param"
<a-form-item :label="t('views.mmlManage.cmdQuickEntry')">
<a-auto-complete
v-model:value="state.autoCompleteValue"
:dropdown-match-select-width="500"
style="width: 100%"
:options="state.autoCompleteSearch"
@search="fnAutoCompleteSearch"
@select="fnAutoCompleteSelect"
@change="fnAutoCompleteChange"
>
<a-form-item
:label="item.display"
:name="item.name"
:required="item.optional === 'false'"
<a-textarea :placeholder="t('common.ipnutPlease')" auto-size />
</a-auto-complete>
</a-form-item>
{{ state.from }}
<template v-if="state.mmlSelect.operation && state.mmlSelect.param">
<a-divider orientation="left">
{{ t('views.mmlManage.cmdParamPanel') }}
</a-divider>
<a-row :gutter="16">
<a-col
:lg="6"
:md="12"
:xs="24"
v-for="item in state.mmlSelect.param"
>
<a-tooltip placement="topLeft">
<template #title v-if="item.comment">
{{ item.comment }}
</template>
<a-input
v-if="
['string', 'ipv6', 'ipv4', 'regex'].includes(item.type)
"
v-model:value="state.from[item.name]"
></a-input>
<a-input-number
v-else-if="item.type === 'int'"
v-model:value="state.from[item.name]"
:min="0"
:max="65535"
style="width: 100%"
></a-input-number>
<a-switch
v-else-if="item.type === 'bool'"
v-model:checked="state.from[item.name]"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
></a-switch>
<a-select
v-else-if="item.type === 'enum'"
v-model:value="state.from[item.name]"
:allow-clear="true"
>
<a-select-option
:value="v"
:key="v"
v-for="(k, v) in JSON.parse(item.filter)"
<a-form-item
:label="item.display"
:name="item.name"
:required="item.optional === 'false'"
>
<a-tooltip>
<template #title v-if="item.comment">
{{ item.comment }}
</template>
<a-input-number
v-if="item.type === 'int'"
v-model:value="state.from[item.name]"
style="width: 100%"
></a-input-number>
<a-switch
v-else-if="item.type === 'bool'"
v-model:checked="state.from[item.name]"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
></a-switch>
<a-select
v-else-if="item.type === 'enum'"
v-model:value="state.from[item.name]"
:allow-clear="item.optional === 'true'"
>
{{ k }}
</a-select-option>
</a-select>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
<a-select-option
:value="v"
:key="v"
v-for="(k, v) in JSON.parse(item.filter)"
>
{{ k }}
</a-select-option>
</a-select>
<a-input
v-else
v-model:value="state.from[item.name]"
></a-input>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
</template>
</a-form>
</a-card>
@@ -440,7 +582,7 @@ onMounted(() => {
size="small"
:body-style="{ padding: 0 }"
style="margin-top: 16px"
v-show="state.mmlSelect.title"
v-show="state.mmlCmdLog"
>
<!-- 插槽-卡片右侧 -->
<template #extra>