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

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