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

View File

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

View File

@@ -11,7 +11,7 @@ import { getMMLByNE, sendMMlByNE } from '@/api/mmlManage/neOperate';
const { t } = useI18n(); const { t } = useI18n();
/**网元参数 */ /**网元参数 */
let neCascaderOtions = ref<Record<string, any>[]>([]); let neCascaderOptions = ref<Record<string, any>[]>([]);
/**对象信息状态类型 */ /**对象信息状态类型 */
type StateType = { type StateType = {
@@ -25,6 +25,12 @@ type StateType = {
mmlSelect: Record<string, any>; mmlSelect: Record<string, any>;
/**表单数据 */ /**表单数据 */
from: Record<string, any>; from: Record<string, any>;
/**自动完成 input */
autoCompleteValue: string;
/**自动完成 options */
autoCompleteData: any[];
/**自动完成 options */
autoCompleteSearch: any[];
/**命令发送日志 */ /**命令发送日志 */
mmlCmdLog: string; mmlCmdLog: string;
}; };
@@ -34,10 +40,19 @@ let state: StateType = reactive({
neType: [], neType: [],
mmlNeType: '', mmlNeType: '',
mmlTreeData: [], mmlTreeData: [],
mmlSelect: {}, mmlSelect: {
title: '',
key: '',
operation: '',
object: '',
param: [],
},
from: { from: {
sendLoading: false, sendLoading: false,
}, },
autoCompleteValue: '',
autoCompleteData: [],
autoCompleteSearch: [],
mmlCmdLog: '', mmlCmdLog: '',
}); });
@@ -45,6 +60,8 @@ let state: StateType = reactive({
function fnTreeSelect(_: any, info: any) { function fnTreeSelect(_: any, info: any) {
state.mmlSelect = info.node.dataRef; state.mmlSelect = info.node.dataRef;
state.from = {}; state.from = {};
state.autoCompleteValue =
`${state.mmlSelect.operation} ${state.mmlSelect.object}`.trim();
// state.mmlCmdLog = ''; // state.mmlCmdLog = '';
} }
@@ -55,6 +72,13 @@ function fnCleanCmdLog() {
/**清空表单 */ /**清空表单 */
function fnCleanFrom() { function fnCleanFrom() {
state.mmlSelect = {
title: '',
key: '',
operation: '',
object: '',
param: [],
};
state.from = {}; state.from = {};
} }
@@ -63,12 +87,14 @@ function fnSendMML() {
if (state.from.sendLoading) { if (state.from.sendLoading) {
return; return;
} }
let cmdStr = '';
const operation = state.mmlSelect.operation; const operation = state.mmlSelect.operation;
const object = state.mmlSelect.object; const object = state.mmlSelect.object;
let cmdStr = '';
// 根据参数取值 // 根据参数取值
let argsArr: string[] = []; let argsArr: string[] = [];
const param = toRaw(state.mmlSelect.param) || []; const param = toRaw(state.mmlSelect.param) || [];
if (operation && Array.isArray(param)) {
const from = toRaw(state.from); const from = toRaw(state.from);
for (const item of param) { for (const item of param) {
const value = from[item.name]; const value = from[item.name];
@@ -108,6 +134,17 @@ function fnSendMML() {
cmdStr = `${operation} ${argsStr}`; cmdStr = `${operation} ${argsStr}`;
} }
cmdStr = cmdStr.trim(); cmdStr = cmdStr.trim();
}
if (cmdStr) {
state.autoCompleteValue = cmdStr;
} else {
let value = state.autoCompleteValue;
if (value.indexOf('\n') !== -1) {
value = value.replace(/(\r\n|\n)/g, ';');
}
cmdStr = value;
}
// 发送 // 发送
state.mmlCmdLog += `${cmdStr}\n`; state.mmlCmdLog += `${cmdStr}\n`;
@@ -215,7 +252,7 @@ function ruleVerification(
break; break;
default: default:
return [false, t('views.mmlManage.requireUn', { display })]; console.warn(t('views.mmlManage.requireUn', { display }), type);
} }
return result; return result;
} }
@@ -224,8 +261,16 @@ function ruleVerification(
function fnNeChange(keys: any, _: any) { function fnNeChange(keys: any, _: any) {
// 不是同类型时需要重新加载 // 不是同类型时需要重新加载
if (state.mmlNeType !== keys[0]) { if (state.mmlNeType !== keys[0]) {
state.autoCompleteSearch = [];
state.autoCompleteData = [];
state.mmlTreeData = []; state.mmlTreeData = [];
state.mmlSelect = {}; state.mmlSelect = {
title: '',
key: '',
operation: '',
object: '',
param: {},
};
fnGetList(); fnGetList();
} }
} }
@@ -236,6 +281,8 @@ function fnGetList() {
state.mmlNeType = neType; state.mmlNeType = neType;
getMMLByNE(neType).then(res => { getMMLByNE(neType).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// 构建自动完成筛选结构
const autoCompleteArr: Record<string, any>[] = [];
// 构建树结构 // 构建树结构
const treeArr: Record<string, any>[] = []; const treeArr: Record<string, any>[] = [];
for (const item of res.data) { for (const item of res.data) {
@@ -252,8 +299,8 @@ function fnGetList() {
} }
// 遍历检查大类 // 遍历检查大类
const treeItem = treeArr.find(i => i.key == item['category']); const treeItemIndex = treeArr.findIndex(i => i.key == item['category']);
if (!treeItem) { if (treeItemIndex < 0) {
treeArr.push({ treeArr.push({
title: item['catDisplay'], title: item['catDisplay'],
key: item['category'], key: item['category'],
@@ -262,17 +309,31 @@ function fnGetList() {
{ key: id, title: mmlDisplay, object, operation, param }, { 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 { } else {
treeItem.children.push({ treeArr[treeItemIndex].children.push({
key: id, key: id,
title: mmlDisplay, title: mmlDisplay,
object, object,
operation, operation,
param, param,
}); });
autoCompleteArr[treeItemIndex].options.push({
key: id,
value: mmlDisplay,
object,
operation,
param,
});
} }
} }
state.mmlTreeData = treeArr; state.mmlTreeData = treeArr;
state.autoCompleteData = autoCompleteArr;
} else { } else {
message.warning({ message.warning({
content: t('views.mmlManage.cmdNoTip', { num: neType }), 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(() => { onMounted(() => {
// 获取网元网元列表 // 获取网元网元列表
useNeInfoStore() useNeInfoStore()
@@ -290,18 +427,18 @@ onMounted(() => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) { if (res.data.length > 0) {
// 过滤不可用的网元 // 过滤不可用的网元
neCascaderOtions.value = useNeInfoStore().getNeCascaderOtions.filter( neCascaderOptions.value = useNeInfoStore().getNeCascaderOtions.filter(
(item: any) => { (item: any) => {
return !['OMC'].includes(item.value); return !['OMC'].includes(item.value);
} }
); );
// 默认选择AMF // 默认选择AMF
const item = neCascaderOtions.value.find(s => s.value === 'AMF'); const item = neCascaderOptions.value.find(s => s.value === 'AMF');
if (item && item.children) { if (item && item.children) {
const info = item.children[0]; const info = item.children[0];
state.neType = [info.neType, info.neId]; state.neType = [info.neType, info.neId];
} else { } else {
const info = neCascaderOtions.value[0].children[0]; const info = neCascaderOptions.value[0].children[0];
state.neType = [info.neType, info.neId]; state.neType = [info.neType, info.neId];
} }
fnGetList(); fnGetList();
@@ -330,16 +467,18 @@ onMounted(() => {
<a-form-item name="neType"> <a-form-item name="neType">
<a-cascader <a-cascader
v-model:value="state.neType" v-model:value="state.neType"
:options="neCascaderOtions" :options="neCascaderOptions"
@change="fnNeChange" @change="fnNeChange"
:allow-clear="false" :allow-clear="false"
:placeholder="t('common.selectPlease')" :placeholder="t('common.selectPlease')"
/> />
</a-form-item> </a-form-item>
<a-form-item name="listeningPort"> <a-form-item name="mmlTree" v-if="state.mmlTreeData.length > 0">
<a-directory-tree <a-directory-tree
:tree-data="state.mmlTreeData" :tree-data="state.mmlTreeData"
default-expand-all
@select="fnTreeSelect" @select="fnTreeSelect"
:selectedKeys="[state.mmlSelect.key]"
></a-directory-tree> ></a-directory-tree>
</a-form-item> </a-form-item>
</a-form> </a-form>
@@ -347,11 +486,7 @@ onMounted(() => {
</a-col> </a-col>
<a-col :span="18"> <a-col :span="18">
<!-- 命令参数输入 --> <!-- 命令参数输入 -->
<a-card <a-card size="small" :bordered="false">
size="small"
:bordered="false"
:loading="!state.mmlSelect.title"
>
<template #title> <template #title>
<a-typography-text strong v-if="state.mmlSelect.title"> <a-typography-text strong v-if="state.mmlSelect.title">
{{ state.mmlSelect.title }} {{ state.mmlSelect.title }}
@@ -377,7 +512,6 @@ onMounted(() => {
<a-button <a-button
type="primary" type="primary"
size="small" size="small"
:disabled="!state.mmlSelect.title"
:loading="state.from.sendLoading" :loading="state.from.sendLoading"
@click.prevent="fnSendMML" @click.prevent="fnSendMML"
> >
@@ -395,6 +529,24 @@ onMounted(() => {
:validate-on-rule-change="false" :validate-on-rule-change="false"
:validateTrigger="[]" :validateTrigger="[]"
> >
<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-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-row :gutter="16">
<a-col <a-col
:lg="6" :lg="6"
@@ -411,17 +563,10 @@ onMounted(() => {
<template #title v-if="item.comment"> <template #title v-if="item.comment">
{{ item.comment }} {{ item.comment }}
</template> </template>
<a-input
v-if="
['string', 'ipv6', 'ipv4', 'regex'].includes(item.type)
"
v-model:value="state.from[item.name]"
></a-input>
<a-input-number <a-input-number
v-else-if="item.type === 'int'" v-if="item.type === 'int'"
v-model:value="state.from[item.name]" v-model:value="state.from[item.name]"
:min="0"
:max="65535"
style="width: 100%" style="width: 100%"
></a-input-number> ></a-input-number>
<a-switch <a-switch
@@ -433,7 +578,7 @@ onMounted(() => {
<a-select <a-select
v-else-if="item.type === 'enum'" v-else-if="item.type === 'enum'"
v-model:value="state.from[item.name]" v-model:value="state.from[item.name]"
:allow-clear="true" :allow-clear="item.optional === 'true'"
> >
<a-select-option <a-select-option
:value="v" :value="v"
@@ -443,10 +588,15 @@ onMounted(() => {
{{ k }} {{ k }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input
v-else
v-model:value="state.from[item.name]"
></a-input>
</a-tooltip> </a-tooltip>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
</template>
</a-form> </a-form>
</a-card> </a-card>
@@ -457,7 +607,7 @@ onMounted(() => {
size="small" size="small"
:body-style="{ padding: 0 }" :body-style="{ padding: 0 }"
style="margin-top: 16px" style="margin-top: 16px"
v-show="state.mmlSelect.title" v-show="state.mmlCmdLog"
> >
<!-- 插槽-卡片右侧 --> <!-- 插槽-卡片右侧 -->
<template #extra> <template #extra>

View File

@@ -11,20 +11,24 @@ import { getMMLByOMC, sendMMlByOMC } from '@/api/mmlManage/omcOperate';
const { t } = useI18n(); const { t } = useI18n();
/**网元参数 */ /**网元参数 */
let neOtions = ref<Record<string, any>[]>([]); let neOptions = ref<Record<string, any>[]>([]);
/**对象信息状态类型 */ /**对象信息状态类型 */
type StateType = { type StateType = {
/**网元ID */ /**网元ID */
neId: string; neId: string;
/**命令数据 loading */
mmlLoading: boolean;
/**命令数据 tree */ /**命令数据 tree */
mmlTreeData: any[]; mmlTreeData: any[];
/**命令选中 */ /**命令选中 */
mmlSelect: Record<string, any>; mmlSelect: Record<string, any>;
/**表单数据 */ /**表单数据 */
from: Record<string, any>; from: Record<string, any>;
/**自动完成 input */
autoCompleteValue: string;
/**自动完成 options */
autoCompleteData: any[];
/**自动完成 options */
autoCompleteSearch: any[];
/**命令发送日志 */ /**命令发送日志 */
mmlCmdLog: string; mmlCmdLog: string;
}; };
@@ -32,12 +36,20 @@ type StateType = {
/**对象信息状态 */ /**对象信息状态 */
let state: StateType = reactive({ let state: StateType = reactive({
neId: '', neId: '',
mmlLoading: true,
mmlTreeData: [], mmlTreeData: [],
mmlSelect: {}, mmlSelect: {
title: '',
key: '',
operation: '',
object: '',
param: [],
},
from: { from: {
sendLoading: false, sendLoading: false,
}, },
autoCompleteValue: '',
autoCompleteData: [],
autoCompleteSearch: [],
mmlCmdLog: '', mmlCmdLog: '',
}); });
@@ -45,6 +57,8 @@ let state: StateType = reactive({
function fnTreeSelect(_: any, info: any) { function fnTreeSelect(_: any, info: any) {
state.mmlSelect = info.node.dataRef; state.mmlSelect = info.node.dataRef;
state.from = {}; state.from = {};
state.autoCompleteValue =
`${state.mmlSelect.operation} ${state.mmlSelect.object}`.trim();
// state.mmlCmdLog = ''; // state.mmlCmdLog = '';
} }
@@ -55,6 +69,13 @@ function fnCleanCmdLog() {
/**清空表单 */ /**清空表单 */
function fnCleanFrom() { function fnCleanFrom() {
state.mmlSelect = {
title: '',
key: '',
operation: '',
object: '',
param: [],
};
state.from = {}; state.from = {};
} }
@@ -63,12 +84,14 @@ function fnSendMML() {
if (state.from.sendLoading) { if (state.from.sendLoading) {
return; return;
} }
let cmdStr = '';
const operation = state.mmlSelect.operation; const operation = state.mmlSelect.operation;
const object = state.mmlSelect.object; const object = state.mmlSelect.object;
let cmdStr = '';
// 根据参数取值 // 根据参数取值
let argsArr: string[] = []; let argsArr: string[] = [];
const param = toRaw(state.mmlSelect.param) || []; const param = toRaw(state.mmlSelect.param) || [];
if (operation && Array.isArray(param)) {
const from = toRaw(state.from); const from = toRaw(state.from);
for (const item of param) { for (const item of param) {
const value = from[item.name]; const value = from[item.name];
@@ -108,6 +131,17 @@ function fnSendMML() {
cmdStr = `${operation} ${argsStr}`; cmdStr = `${operation} ${argsStr}`;
} }
cmdStr = cmdStr.trim(); cmdStr = cmdStr.trim();
}
if (cmdStr) {
state.autoCompleteValue = cmdStr;
} else {
let value = state.autoCompleteValue;
if (value.indexOf('\n') !== -1) {
value = value.replace(/(\r\n|\n)/g, ';');
}
cmdStr = value;
}
// 发送 // 发送
state.mmlCmdLog += `${cmdStr}\n`; state.mmlCmdLog += `${cmdStr}\n`;
@@ -214,7 +248,7 @@ function ruleVerification(
break; break;
default: default:
return [false, t('views.mmlManage.requireUn', { display })]; console.warn(t('views.mmlManage.requireUn', { display }), type);
} }
return result; return result;
} }
@@ -223,6 +257,8 @@ function ruleVerification(
function fnGetList() { function fnGetList() {
getMMLByOMC().then(res => { getMMLByOMC().then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// 构建自动完成筛选结构
const autoCompleteArr: Record<string, any>[] = [];
// 构建树结构 // 构建树结构
const treeArr: Record<string, any>[] = []; const treeArr: Record<string, any>[] = [];
for (const item of res.data) { for (const item of res.data) {
@@ -239,8 +275,8 @@ function fnGetList() {
} }
// 遍历检查大类 // 遍历检查大类
const treeItem = treeArr.find(i => i.key == item['category']); const treeItemIndex = treeArr.findIndex(i => i.key == item['category']);
if (!treeItem) { if (treeItemIndex < 0) {
treeArr.push({ treeArr.push({
title: item['catDisplay'], title: item['catDisplay'],
key: item['category'], key: item['category'],
@@ -249,20 +285,114 @@ function fnGetList() {
{ key: id, title: mmlDisplay, object, operation, param }, { 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 { } else {
treeItem.children.push({ treeArr[treeItemIndex].children.push({
key: id, key: id,
title: mmlDisplay, title: mmlDisplay,
object, object,
operation, operation,
param, param,
}); });
autoCompleteArr[treeItemIndex].options.push({
key: id,
value: mmlDisplay,
object,
operation,
param,
});
} }
} }
state.mmlTreeData = treeArr; state.mmlTreeData = treeArr;
} state.autoCompleteData = autoCompleteArr;
state.mmlLoading = false; } else {
message.warning({
content: t('views.mmlManage.cmdNoTip', { num: 'OMC' }),
duration: 2,
}); });
}
});
}
/**自动完成搜索匹配前缀 */
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(() => { onMounted(() => {
@@ -278,7 +408,7 @@ onMounted(() => {
arr.push({ value: i.neId, label: i.neName }); arr.push({ value: i.neId, label: i.neName });
} }
}); });
neOtions.value = arr; neOptions.value = arr;
if (arr.length > 0) { if (arr.length > 0) {
state.neId = arr[0].value; state.neId = arr[0].value;
// 获取列表数据 // 获取列表数据
@@ -309,20 +439,21 @@ onMounted(() => {
size="small" size="small"
:bordered="false" :bordered="false"
:title="t('views.mmlManage.cmdTitle')" :title="t('views.mmlManage.cmdTitle')"
:loading="state.mmlLoading"
> >
<a-form layout="vertical" autocomplete="off"> <a-form layout="vertical" autocomplete="off">
<a-form-item name="neId "> <a-form-item name="neId ">
<a-select <a-select
v-model:value="state.neId" v-model:value="state.neId"
:options="neOtions" :options="neOptions"
:placeholder="t('common.selectPlease')" :placeholder="t('common.selectPlease')"
/> />
</a-form-item> </a-form-item>
<a-form-item name="listeningPort"> <a-form-item name="mmlTree" v-if="state.mmlTreeData.length > 0">
<a-directory-tree <a-directory-tree
:tree-data="state.mmlTreeData" :tree-data="state.mmlTreeData"
default-expand-all
@select="fnTreeSelect" @select="fnTreeSelect"
:selectedKeys="[state.mmlSelect.key]"
></a-directory-tree> ></a-directory-tree>
</a-form-item> </a-form-item>
</a-form> </a-form>
@@ -330,11 +461,7 @@ onMounted(() => {
</a-col> </a-col>
<a-col :span="18"> <a-col :span="18">
<!-- 命令参数输入 --> <!-- 命令参数输入 -->
<a-card <a-card size="small" :bordered="false">
size="small"
:bordered="false"
:loading="!state.mmlSelect.title"
>
<template #title> <template #title>
<a-typography-text strong v-if="state.mmlSelect.title"> <a-typography-text strong v-if="state.mmlSelect.title">
{{ state.mmlSelect.title }} {{ state.mmlSelect.title }}
@@ -360,7 +487,6 @@ onMounted(() => {
<a-button <a-button
type="primary" type="primary"
size="small" size="small"
:disabled="!state.mmlSelect.title"
:loading="state.from.sendLoading" :loading="state.from.sendLoading"
@click.prevent="fnSendMML" @click.prevent="fnSendMML"
> >
@@ -378,6 +504,24 @@ onMounted(() => {
:validate-on-rule-change="false" :validate-on-rule-change="false"
:validateTrigger="[]" :validateTrigger="[]"
> >
<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-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-row :gutter="16">
<a-col <a-col
:lg="6" :lg="6"
@@ -394,17 +538,10 @@ onMounted(() => {
<template #title v-if="item.comment"> <template #title v-if="item.comment">
{{ item.comment }} {{ item.comment }}
</template> </template>
<a-input
v-if="
['string', 'ipv6', 'ipv4', 'regex'].includes(item.type)
"
v-model:value="state.from[item.name]"
></a-input>
<a-input-number <a-input-number
v-else-if="item.type === 'int'" v-if="item.type === 'int'"
v-model:value="state.from[item.name]" v-model:value="state.from[item.name]"
:min="0"
:max="65535"
style="width: 100%" style="width: 100%"
></a-input-number> ></a-input-number>
<a-switch <a-switch
@@ -416,7 +553,7 @@ onMounted(() => {
<a-select <a-select
v-else-if="item.type === 'enum'" v-else-if="item.type === 'enum'"
v-model:value="state.from[item.name]" v-model:value="state.from[item.name]"
:allow-clear="true" :allow-clear="item.optional === 'true'"
> >
<a-select-option <a-select-option
:value="v" :value="v"
@@ -426,10 +563,15 @@ onMounted(() => {
{{ k }} {{ k }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input
v-else
v-model:value="state.from[item.name]"
></a-input>
</a-tooltip> </a-tooltip>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
</template>
</a-form> </a-form>
</a-card> </a-card>
@@ -440,7 +582,7 @@ onMounted(() => {
size="small" size="small"
:body-style="{ padding: 0 }" :body-style="{ padding: 0 }"
style="margin-top: 16px" style="margin-top: 16px"
v-show="state.mmlSelect.title" v-show="state.mmlSelect.operation || state.autoCompleteValue"
> >
<!-- 插槽-卡片右侧 --> <!-- 插槽-卡片右侧 -->
<template #extra> <template #extra>

View File

@@ -11,20 +11,24 @@ import { getMMLByUDM, sendMMlByUDM } from '@/api/mmlManage/udmOperate';
const { t } = useI18n(); const { t } = useI18n();
/**网元参数 */ /**网元参数 */
let neOtions = ref<Record<string, any>[]>([]); let neOptions = ref<Record<string, any>[]>([]);
/**对象信息状态类型 */ /**对象信息状态类型 */
type StateType = { type StateType = {
/**网元ID */ /**网元ID */
neId: string; neId: string;
/**命令数据 loading */
mmlLoading: boolean;
/**命令数据 tree */ /**命令数据 tree */
mmlTreeData: any[]; mmlTreeData: any[];
/**命令选中 */ /**命令选中 */
mmlSelect: Record<string, any>; mmlSelect: Record<string, any>;
/**表单数据 */ /**表单数据 */
from: Record<string, any>; from: Record<string, any>;
/**自动完成 input */
autoCompleteValue: string;
/**自动完成 options */
autoCompleteData: any[];
/**自动完成 options */
autoCompleteSearch: any[];
/**命令发送日志 */ /**命令发送日志 */
mmlCmdLog: string; mmlCmdLog: string;
}; };
@@ -32,12 +36,20 @@ type StateType = {
/**对象信息状态 */ /**对象信息状态 */
let state: StateType = reactive({ let state: StateType = reactive({
neId: '', neId: '',
mmlLoading: true,
mmlTreeData: [], mmlTreeData: [],
mmlSelect: {}, mmlSelect: {
title: '',
key: '',
operation: '',
object: '',
param: [],
},
from: { from: {
sendLoading: false, sendLoading: false,
}, },
autoCompleteValue: '',
autoCompleteData: [],
autoCompleteSearch: [],
mmlCmdLog: '', mmlCmdLog: '',
}); });
@@ -45,6 +57,8 @@ let state: StateType = reactive({
function fnTreeSelect(_: any, info: any) { function fnTreeSelect(_: any, info: any) {
state.mmlSelect = info.node.dataRef; state.mmlSelect = info.node.dataRef;
state.from = {}; state.from = {};
state.autoCompleteValue =
`${state.mmlSelect.operation} ${state.mmlSelect.object}`.trim();
// state.mmlCmdLog = ''; // state.mmlCmdLog = '';
} }
@@ -55,6 +69,13 @@ function fnCleanCmdLog() {
/**清空表单 */ /**清空表单 */
function fnCleanFrom() { function fnCleanFrom() {
state.mmlSelect = {
title: '',
key: '',
operation: '',
object: '',
param: [],
};
state.from = {}; state.from = {};
} }
@@ -63,12 +84,14 @@ function fnSendMML() {
if (state.from.sendLoading) { if (state.from.sendLoading) {
return; return;
} }
let cmdStr = '';
const operation = state.mmlSelect.operation; const operation = state.mmlSelect.operation;
const object = state.mmlSelect.object; const object = state.mmlSelect.object;
let cmdStr = '';
// 根据参数取值 // 根据参数取值
let argsArr: string[] = []; let argsArr: string[] = [];
const param = toRaw(state.mmlSelect.param) || []; const param = toRaw(state.mmlSelect.param) || [];
if (operation && Array.isArray(param)) {
const from = toRaw(state.from); const from = toRaw(state.from);
for (const item of param) { for (const item of param) {
const value = from[item.name]; const value = from[item.name];
@@ -108,6 +131,17 @@ function fnSendMML() {
cmdStr = `${operation} ${argsStr}`; cmdStr = `${operation} ${argsStr}`;
} }
cmdStr = cmdStr.trim(); cmdStr = cmdStr.trim();
}
if (cmdStr) {
state.autoCompleteValue = cmdStr;
} else {
let value = state.autoCompleteValue;
if (value.indexOf('\n') !== -1) {
value = value.replace(/(\r\n|\n)/g, ';');
}
cmdStr = value;
}
// 发送 // 发送
state.mmlCmdLog += `${cmdStr}\n`; state.mmlCmdLog += `${cmdStr}\n`;
@@ -214,7 +248,7 @@ function ruleVerification(
break; break;
default: default:
return [false, t('views.mmlManage.requireUn', { display })]; console.warn(t('views.mmlManage.requireUn', { display }), type);
} }
return result; return result;
} }
@@ -223,6 +257,8 @@ function ruleVerification(
function fnGetList() { function fnGetList() {
getMMLByUDM().then(res => { getMMLByUDM().then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// 构建自动完成筛选结构
const autoCompleteArr: Record<string, any>[] = [];
// 构建树结构 // 构建树结构
const treeArr: Record<string, any>[] = []; const treeArr: Record<string, any>[] = [];
for (const item of res.data) { for (const item of res.data) {
@@ -239,8 +275,8 @@ function fnGetList() {
} }
// 遍历检查大类 // 遍历检查大类
const treeItem = treeArr.find(i => i.key == item['category']); const treeItemIndex = treeArr.findIndex(i => i.key == item['category']);
if (!treeItem) { if (treeItemIndex < 0) {
treeArr.push({ treeArr.push({
title: item['catDisplay'], title: item['catDisplay'],
key: item['category'], key: item['category'],
@@ -249,20 +285,114 @@ function fnGetList() {
{ key: id, title: mmlDisplay, object, operation, param }, { 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 { } else {
treeItem.children.push({ treeArr[treeItemIndex].children.push({
key: id, key: id,
title: mmlDisplay, title: mmlDisplay,
object, object,
operation, operation,
param, param,
}); });
autoCompleteArr[treeItemIndex].options.push({
key: id,
value: mmlDisplay,
object,
operation,
param,
});
} }
} }
state.mmlTreeData = treeArr; state.mmlTreeData = treeArr;
} state.autoCompleteData = autoCompleteArr;
state.mmlLoading = false; } else {
message.warning({
content: t('views.mmlManage.cmdNoTip', { num: 'UDM' }),
duration: 2,
}); });
}
});
}
/**自动完成搜索匹配前缀 */
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(() => { onMounted(() => {
@@ -278,7 +408,7 @@ onMounted(() => {
arr.push({ value: i.neId, label: i.neName }); arr.push({ value: i.neId, label: i.neName });
} }
}); });
neOtions.value = arr; neOptions.value = arr;
if (arr.length > 0) { if (arr.length > 0) {
state.neId = arr[0].value; state.neId = arr[0].value;
// 获取列表数据 // 获取列表数据
@@ -309,20 +439,21 @@ onMounted(() => {
size="small" size="small"
:bordered="false" :bordered="false"
:title="t('views.mmlManage.cmdTitle')" :title="t('views.mmlManage.cmdTitle')"
:loading="state.mmlLoading"
> >
<a-form layout="vertical" autocomplete="off"> <a-form layout="vertical" autocomplete="off">
<a-form-item name="neId "> <a-form-item name="neId ">
<a-select <a-select
v-model:value="state.neId" v-model:value="state.neId"
:options="neOtions" :options="neOptions"
:placeholder="t('common.selectPlease')" :placeholder="t('common.selectPlease')"
/> />
</a-form-item> </a-form-item>
<a-form-item name="listeningPort"> <a-form-item name="mmlTree" v-if="state.mmlTreeData.length > 0">
<a-directory-tree <a-directory-tree
:tree-data="state.mmlTreeData" :tree-data="state.mmlTreeData"
default-expand-all
@select="fnTreeSelect" @select="fnTreeSelect"
:selectedKeys="[state.mmlSelect.key]"
></a-directory-tree> ></a-directory-tree>
</a-form-item> </a-form-item>
</a-form> </a-form>
@@ -330,11 +461,7 @@ onMounted(() => {
</a-col> </a-col>
<a-col :span="18"> <a-col :span="18">
<!-- 命令参数输入 --> <!-- 命令参数输入 -->
<a-card <a-card size="small" :bordered="false">
size="small"
:bordered="false"
:loading="!state.mmlSelect.title"
>
<template #title> <template #title>
<a-typography-text strong v-if="state.mmlSelect.title"> <a-typography-text strong v-if="state.mmlSelect.title">
{{ state.mmlSelect.title }} {{ state.mmlSelect.title }}
@@ -360,7 +487,6 @@ onMounted(() => {
<a-button <a-button
type="primary" type="primary"
size="small" size="small"
:disabled="!state.mmlSelect.title"
:loading="state.from.sendLoading" :loading="state.from.sendLoading"
@click.prevent="fnSendMML" @click.prevent="fnSendMML"
> >
@@ -378,6 +504,24 @@ onMounted(() => {
:validate-on-rule-change="false" :validate-on-rule-change="false"
:validateTrigger="[]" :validateTrigger="[]"
> >
<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-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-row :gutter="16">
<a-col <a-col
:lg="6" :lg="6"
@@ -390,21 +534,14 @@ onMounted(() => {
:name="item.name" :name="item.name"
:required="item.optional === 'false'" :required="item.optional === 'false'"
> >
<a-tooltip placement="topLeft"> <a-tooltip>
<template #title v-if="item.comment"> <template #title v-if="item.comment">
{{ item.comment }} {{ item.comment }}
</template> </template>
<a-input
v-if="
['string', 'ipv6', 'ipv4', 'regex'].includes(item.type)
"
v-model:value="state.from[item.name]"
></a-input>
<a-input-number <a-input-number
v-else-if="item.type === 'int'" v-if="item.type === 'int'"
v-model:value="state.from[item.name]" v-model:value="state.from[item.name]"
:min="0"
:max="65535"
style="width: 100%" style="width: 100%"
></a-input-number> ></a-input-number>
<a-switch <a-switch
@@ -416,7 +553,7 @@ onMounted(() => {
<a-select <a-select
v-else-if="item.type === 'enum'" v-else-if="item.type === 'enum'"
v-model:value="state.from[item.name]" v-model:value="state.from[item.name]"
:allow-clear="true" :allow-clear="item.optional === 'true'"
> >
<a-select-option <a-select-option
:value="v" :value="v"
@@ -426,10 +563,15 @@ onMounted(() => {
{{ k }} {{ k }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input
v-else
v-model:value="state.from[item.name]"
></a-input>
</a-tooltip> </a-tooltip>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
</template>
</a-form> </a-form>
</a-card> </a-card>
@@ -440,7 +582,7 @@ onMounted(() => {
size="small" size="small"
:body-style="{ padding: 0 }" :body-style="{ padding: 0 }"
style="margin-top: 16px" style="margin-top: 16px"
v-show="state.mmlSelect.title" v-show="state.mmlCmdLog"
> >
<!-- 插槽-卡片右侧 --> <!-- 插槽-卡片右侧 -->
<template #extra> <template #extra>