Files
fe.ems.vue3/src/views/configManage/configParamTree/index.vue
2023-11-06 16:38:25 +08:00

2074 lines
63 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { reactive, ref, onMounted } from 'vue';
import { PageContainer } from '@ant-design-vue/pro-layout';
import { Modal, message } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import {
getParamConfigTopTab,
getParamConfigInfoTable,
updateParamConfigInfo,
updateNeConfigReload,
delParamConfigInfo,
addParamConfigInfo,
getParamConfigInfoTree,
} from '@/api/configManage/configParam';
import { TabPosition } from 'ant-design-vue/lib/tabs/src/interface';
import useNeInfoStore from '@/store/modules/neinfo';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { toRaw } from 'vue';
import { regExpIPv4, regExpIPv6 } from '@/utils/regular-utils';
import { DataNode, EventDataNode } from 'ant-design-vue/lib/tree';
import { ColumnsType } from 'ant-design-vue/lib/table';
const { t } = useI18n();
/**网元参数 */
let neCascaderOtions = ref<Record<string, any>[]>([]);
/**网元类型选择 type,id */
let neTypeSelect = ref<string[]>(['', '']);
/**tab标签栏类型 */
type TabStateType = {
/**标签方向 */
tabPosition: TabPosition;
/**标签项 */
tabNames: Record<string, any>[];
/**标签激活项 */
tabActiveTopTag: string;
};
/**tab标签栏属性 */
let tabState: TabStateType = reactive({
tabPosition: 'top',
tabNames: [],
tabActiveTopTag: '',
});
/**tab标签栏标签点击监听 */
function fnTabActiveTopTag(key: string | number) {
tableState.loading = true;
if (key !== '#') {
tabState.tabActiveTopTag = key as string;
}
// 获取数据
const neType = neTypeSelect.value[0];
const neId = neTypeSelect.value[1];
const topTag = tabState.tabActiveTopTag;
getParamConfigInfoTable(neType, topTag, neId)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && res.data.type) {
tableState.type = res.data.type;
if (res.data.type === 'list') {
tableState.listData = res.data.data;
tableState.listColumns = res.data.columns;
}
if (res.data.type === 'array') {
tableState.arrayData = res.data.data;
tableState.arrayDataRule = res.data.dataRule;
tableState.arrayColumns = res.data.columns;
tableState.arrayColumns.push({
title: t('common.operate'),
key: 'index',
align: 'left',
fixed: 'right',
width: 5,
});
}
} else {
message.warning({
content: `暂无配置项数据`,
duration: 3,
});
}
})
.finally(() => {
tableState.arrayNewIndex = -1;
tableState.editRecord = {};
tableState.loading = false;
});
}
/**查询配置Tag标签 */
function fnGetParamConfigTopTab() {
const neType = neTypeSelect.value[0];
if (!neType) {
message.warning({
content: `请选择网元类型`,
duration: 3,
});
return;
}
// 获取数据
getParamConfigTopTab(neType).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
tabState.tabNames = res.data;
// 取首个tag
if (res.data.length > 0) {
const topTag = res.data[0].topTag;
fnTabActiveTopTag(topTag);
}
} else {
message.warning({
content: `暂无配置项数据`,
duration: 3,
});
}
});
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**表格数据类型 单列还是多列 */
type: string;
/**单列记录字段 */
listColumns: object[];
/**单列记录数据 */
listData: object[];
/**多列记录字段 */
arrayColumns: object[];
/**多列记录数据 */
arrayData: object[];
/**多列记录规则 */
arrayDataRule: Record<string, any>;
/**新增行记录Index */
arrayNewIndex: number;
/**编辑行记录 */
editRecord: Record<string, any>;
/**多列嵌套列表标题 */
arrayChildTitle: string;
/**多列嵌套层级index */
arrayChildLoc: string;
/**多列嵌套记录字段 */
arrayChildColumns: object[];
/**多列嵌套记录数据 */
arrayChildData: object[];
/**多列嵌套记录规则 */
arrayChildDataRule: Record<string, any>;
/**多列嵌套展开key */
arrayChildExpandKeys: any[];
/**新增嵌套行记录Index */
arrayChildNewIndex: number;
/**编辑嵌套行记录 */
arrayChildEditRecord: Record<string, any>;
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
type: 'list',
listColumns: [],
listData: [],
arrayColumns: [],
arrayData: [],
arrayDataRule: {},
arrayNewIndex: -1,
editRecord: {},
arrayChildTitle: '',
arrayChildLoc: '',
arrayChildColumns: [],
arrayChildData: [],
arrayChildDataRule: {},
arrayChildExpandKeys: [],
arrayChildNewIndex: -1,
arrayChildEditRecord: {},
});
/**单列表编辑 */
function listEdit(row: Record<string, any>) {
tableState.editRecord = Object.assign({}, row);
}
/**单列表编辑关闭 */
function listEditClose() {
tableState.editRecord = {};
}
/**单列表编辑确认 */
function listEditOk() {
const from = toRaw(tableState.editRecord);
// 检查规则
const [ok, msg] = ruleVerification(from);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
// 发送
const hide = message.loading({ content: t('common.loading') });
let data = {
[from['name']]: from['value'],
};
// UPF参数不统一
// if (neTypeSelect.value[0] === 'UPF') {
// data = {
// [parseFirstUpper(from['name'])]: from['value'],
// };
// }
updateParamConfigInfo(
'list',
{
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
topTag: tabState.tabActiveTopTag,
},
data
)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: `${from['display']} 属性值修改成功`,
duration: 3,
});
// 改变表格数据
const item = tableState.listData.filter(
(item: Record<string, any>) => from['name'] === item['name']
)[0];
if (item) {
Object.assign(item, tableState.editRecord);
}
} else {
message.warning({
content: `属性值修改失败`,
duration: 3,
});
}
})
.catch(err => {
console.error(err);
message.error({
content: `非法操作属性值`,
duration: 3,
});
})
.finally(() => {
hide();
tableState.editRecord = {};
});
}
/**多列表编辑 */
function arrayEdit(row: Record<string, any>) {
tableState.editRecord = Object.assign({}, JSON.parse(JSON.stringify(row)));
console.log(tableState.editRecord);
}
/**多列表编辑关闭 */
function arrayEditClose() {
if (tableState.arrayNewIndex !== -1) {
tableState.arrayNewIndex = -1;
tableState.arrayData.pop();
}
tableState.editRecord = {};
}
/**多列表编辑确认 */
function arrayEditOk() {
const from = toRaw(tableState.editRecord);
const loc = from['index']['value'];
const neType = neTypeSelect.value[0];
// 遍历提取属性和值
let data: Record<string, any> = {};
for (const key in from) {
// 子嵌套的不插入
if (from[key]['array']) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(from[key]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
// UPF参数不统一
// if (neType === 'UPF') {
// data[parseFirstUpper(key)] = from[key]['value'];
// } else {
// data[key] = from[key]['value'];
// }
data[key] = from[key]['value'];
}
// 发送
const hide = message.loading({ content: t('common.loading') });
updateParamConfigInfo(
'array',
{
neType: neType,
neId: neTypeSelect.value[1],
topTag: tabState.tabActiveTopTag,
loc,
},
data
)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: `Index 为 ${from['index']['value']} 记录修改成功`,
duration: 3,
});
// 改变表格数据
const item = tableState.arrayData.filter(
(item: Record<string, any>) =>
from['index']['value'] === item['index']['value']
)[0];
if (item) {
Object.assign(item, from);
}
} else {
message.warning({
content: `记录修改失败`,
duration: 3,
});
}
})
.catch(err => {
console.error(err);
message.error({
content: `非法操作记录参数`,
duration: 3,
});
})
.finally(() => {
hide();
tableState.arrayNewIndex = -1;
tableState.editRecord = {};
});
}
/**多列表删除单行 */
function arrayDelete(row: Record<string, any>) {
const from = toRaw(row);
const loc = from['index']['value'];
Modal.confirm({
title: '提示',
content: `确认删除Index为 【${from['index']['value']}】 的数据项?`,
onOk() {
const hide = message.loading({ content: t('common.loading') });
delParamConfigInfo({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
topTag: tabState.tabActiveTopTag,
loc,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: `删除成功`,
duration: 2,
});
} else {
message.error({
content: `${res.msg}`,
duration: 2,
});
}
})
.finally(() => {
fnTabActiveTopTag('#');
hide();
});
},
});
}
/**多列表新增单行 */
function arrayAdd() {
const len = tableState.arrayData.length;
let lastItme = {};
let newIndex = 0;
// 无数据时生成临时记录
if (len === 0) {
lastItme = tableState.arrayDataRule;
} else {
lastItme = tableState.arrayData[len - 1];
}
const from = Object.assign({}, JSON.parse(JSON.stringify(lastItme)));
for (const key in from) {
const row = from[key];
if (key === 'index') {
if (from[key]['value'] !== '') {
newIndex = parseInt(from[key]['value']) + 1;
}
if (isNaN(newIndex)) {
newIndex = 0;
}
from[key]['value'] = newIndex;
tableState.arrayNewIndex = newIndex;
continue;
}
// 子嵌套的不初始
if (row.array) {
from[key]['value'] = null;
continue;
}
const type = row.type;
const filter = row.filter;
switch (type) {
case 'int':
if (filter && filter.indexOf('~') !== -1) {
const filterArr = filter.split('~');
const minInt = parseInt(filterArr[0]);
from[key]['value'] = minInt;
} else {
from[key]['value'] = 0;
}
break;
case 'enum':
if (filter && filter.indexOf('{') === 0) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
from[key]['value'] = Object.keys(filterJson)[0];
} else {
from[key]['value'] = '0';
}
break;
case 'bool':
if (filter && filter.indexOf('{') === 0) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
from[key]['value'] = Object.keys(filterJson)[0];
} else {
from[key]['value'] = false;
}
break;
case 'ipv4':
case 'ipv6':
case 'regex':
default:
from[key]['value'] = '';
}
}
tableState.editRecord = from;
tableState.arrayData.push(from);
}
/**多列表新增单行确认 */
function arrayAddOk() {
const from = toRaw(tableState.editRecord);
const loc = from['index']['value'];
const neType = neTypeSelect.value[0];
// 遍历提取属性和值
let data: Record<string, any> = {};
for (const key in from) {
// 子嵌套的不插入
if (from[key]['array']) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(from[key]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
// UPF参数不统一
// if (neType === 'UPF') {
// data[parseFirstUpper(key)] = from[key]['value'];
// } else {
// data[key] = from[key]['value'];
// }
data[key] = from[key]['value'];
}
// 发送
const hide = message.loading({ content: t('common.loading') });
addParamConfigInfo(
{
neType: neType,
neId: neTypeSelect.value[1],
topTag: tabState.tabActiveTopTag,
loc,
},
data
)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: `Index 为 ${from['index']['value']} 记录新增成功`,
duration: 3,
});
} else {
tableState.arrayData.pop();
message.warning({
content: `新增失败`,
duration: 3,
});
}
})
.catch(err => {
console.error(err);
message.error({
content: `非法操作记录参数`,
duration: 3,
});
})
.finally(() => {
hide();
tableState.arrayNewIndex = -1;
tableState.editRecord = {};
});
}
/**多列表展开嵌套行 */
function arrayChildExpand(key: Record<string, any>, row: Record<string, any>) {
const loc = key['value'];
tableState.arrayChildExpandKeys = [];
tableState.arrayChildLoc = '';
const from = Object.assign({}, JSON.parse(JSON.stringify(row)));
// 无数据时
if (!Array.isArray(from.value)) {
from.value = [];
}
const dataArr = Object.freeze(from.value);
const ruleArr = Object.freeze(from.array);
// 列表项数据
const dataArray = [];
for (const item of dataArr) {
let record: Record<string, any> = {};
for (const key of Object.keys(item)) {
// 规则为准
for (const rule of ruleArr) {
if (rule['name'] === key) {
const ruleItem = Object.assign({}, rule, { value: item[key] });
record[ruleItem.name] = ruleItem;
break;
}
}
}
dataArray.push(record);
}
tableState.arrayChildData = dataArray;
// 无数据时,需要临时数据用于新增
if (dataArray.length === 0) {
let itemTemp: Record<string, any> = {};
for (const rule of ruleArr) {
itemTemp[rule.name] = rule;
}
tableState.arrayChildDataRule = itemTemp;
}
// 列表字段
const columns: Record<string, any>[] = [];
for (const rule of ruleArr) {
columns.push({
title: rule.display,
dataIndex: rule.name,
align: 'left',
width: 5,
});
}
tableState.arrayChildColumns = columns;
tableState.arrayChildColumns.push({
title: t('common.operate'),
key: 'index',
align: 'left',
fixed: 'right',
width: 5,
});
// 设置展开key
tableState.arrayChildExpandKeys = [key];
// UPF参数不统一
// if (neTypeSelect.value[0] === 'UPF') {
// tableState.arrayChildLoc = `${loc}/${parseFirstUpper(from['name'])}`;
// } else {
// tableState.arrayChildLoc = `${loc}/${from['name']}`;
// }
tableState.arrayChildLoc = `${loc}/${from['name']}`;
tableState.arrayChildNewIndex = -1;
// 设置展开列表标题
tableState.arrayChildTitle = `${from['display']}`;
}
/**多列表嵌套行编辑 */
function arrayChildEdit(row: Record<string, any>) {
tableState.arrayChildEditRecord = Object.assign(
{},
JSON.parse(JSON.stringify(row))
);
}
/**多列表嵌套行编辑关闭 */
function arrayChildEditClose() {
if (tableState.arrayNewIndex !== -1) {
tableState.arrayChildNewIndex = -1;
tableState.arrayChildData.pop();
}
tableState.arrayChildEditRecord = {};
}
/**多列表嵌套行编辑确认 */
function arrayChildEditOk() {
const from = toRaw(tableState.arrayChildEditRecord);
const loc = `${tableState.arrayChildLoc}/${from['index']['value']}`;
const neType = neTypeSelect.value[0];
let data: Record<string, any> = {};
for (const key in from) {
// 子嵌套的不插入
if (from[key]['array']) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(from[key]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
// UPF参数不统一
// if (neType === 'UPF') {
// data[parseFirstUpper(key)] = from[key]['value'];
// } else {
// data[key] = from[key]['value'];
// }
data[key] = from[key]['value'];
}
// 发送
const hide = message.loading({ content: t('common.loading') });
updateParamConfigInfo(
'array',
{
neType: neType,
neId: neTypeSelect.value[1],
topTag: tabState.tabActiveTopTag,
loc,
},
data
)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: `Index 为 ${from['index']['value']} 记录修改成功`,
duration: 3,
});
fnTabActiveTopTag('#');
} else {
message.warning({
content: `记录修改失败`,
duration: 3,
});
}
})
.catch(err => {
console.error(err);
message.error({
content: `非法操作记录参数`,
duration: 3,
});
})
.finally(() => {
hide();
tableState.arrayChildNewIndex = -1;
tableState.arrayChildEditRecord = {};
});
}
/**多列表嵌套行删除单行 */
function arrayChildDelete(row: Record<string, any>) {
const from = toRaw(row);
const loc = `${tableState.arrayChildLoc}/${from['index']['value']}`;
Modal.confirm({
title: '提示',
content: `确认删除${tableState.arrayChildTitle} Index 为 【${from['index']['value']}】 的数据项?`,
onOk() {
const hide = message.loading({ content: t('common.loading') });
delParamConfigInfo({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
topTag: tabState.tabActiveTopTag,
loc,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: `删除成功`,
duration: 2,
});
} else {
message.error({
content: `${res.msg}`,
duration: 2,
});
}
})
.finally(() => {
fnTabActiveTopTag('#');
hide();
});
},
});
}
/**多列表嵌套行新增单行 */
function arrayChildAdd() {
const len = tableState.arrayChildData.length;
let lastItme = {};
let newIndex = 0;
// 无数据时生成临时记录
if (len === 0) {
lastItme = tableState.arrayChildDataRule;
} else {
lastItme = tableState.arrayChildData[len - 1];
}
const from = Object.assign({}, JSON.parse(JSON.stringify(lastItme)));
for (const key in from) {
const row = from[key];
if (key === 'index') {
if (from[key]['value'] !== '') {
newIndex = parseInt(from[key]['value']) + 1;
}
if (isNaN(newIndex)) {
newIndex = 0;
}
from[key]['value'] = newIndex;
tableState.arrayChildNewIndex = newIndex;
continue;
}
// 子嵌套的不初始
if (row.array) {
from[key]['value'] = null;
continue;
}
const type = row.type;
const filter = row.filter;
switch (type) {
case 'int':
if (filter && filter.indexOf('~') !== -1) {
const filterArr = filter.split('~');
const minInt = parseInt(filterArr[0]);
from[key]['value'] = minInt;
} else {
from[key]['value'] = 0;
}
break;
case 'enum':
if (filter && filter.indexOf('{') === 0) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
from[key]['value'] = Object.keys(filterJson)[0];
} else {
from[key]['value'] = '0';
}
break;
case 'bool':
if (filter && filter.indexOf('{') === 0) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
from[key]['value'] = Object.keys(filterJson)[0];
} else {
from[key]['value'] = false;
}
break;
case 'ipv4':
case 'ipv6':
case 'regex':
default:
from[key]['value'] = '';
}
}
tableState.arrayChildEditRecord = from;
tableState.arrayChildData.push(from);
}
/**多列表新增单行确认 */
function arrayChildAddOk() {
const from = toRaw(tableState.arrayChildEditRecord);
const loc = `${tableState.arrayChildLoc}/${from['index']['value']}`;
const neType = neTypeSelect.value[0];
let data: Record<string, any> = {};
for (const key in from) {
// 子嵌套的不插入
if (from[key]['array']) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(from[key]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
// UPF参数不统一
// if (neType === 'UPF') {
// data[parseFirstUpper(key)] = from[key]['value'];
// } else {
// data[key] = from[key]['value'];
// }
data[key] = from[key]['value'];
}
// 发送
const hide = message.loading({ content: t('common.loading') });
addParamConfigInfo(
{
neType: neType,
neId: neTypeSelect.value[1],
topTag: tabState.tabActiveTopTag,
loc,
},
data
)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: `Index 为 ${from['index']['value']} 记录新增成功`,
duration: 3,
});
fnTabActiveTopTag('#');
} else {
tableState.arrayChildData.pop();
message.warning({
content: `新增失败`,
duration: 3,
});
}
})
.catch(err => {
console.error(err);
message.error({
content: `非法操作记录参数`,
duration: 3,
});
})
.finally(() => {
hide();
tableState.arrayChildNewIndex = -1;
tableState.arrayChildEditRecord = {};
});
}
/**规则校验 */
function ruleVerification(row: Record<string, any>): (string | boolean)[] {
let result = [true, ''];
const type = row.type;
const value = row.value;
const filter = row.filter;
const display = row.display;
// 子嵌套的不检查
if (row.array) {
return result;
}
// 可选的同时没有值不检查
if (row.optional === 'true' && !value) {
return result;
}
switch (type) {
case 'int':
if (filter && filter.indexOf('~') !== -1) {
const filterArr = filter.split('~');
const minInt = parseInt(filterArr[0]);
const maxInt = parseInt(filterArr[1]);
const valueInt = parseInt(value);
if (valueInt < minInt || valueInt > maxInt) {
return [false, `${display} 参数值不在合理范围 ${filter}`];
}
}
break;
case 'ipv4':
if (!regExpIPv4.test(value)) {
return [false, `${display} 不是合法的IPV4地址`];
}
break;
case 'ipv6':
if (!regExpIPv6.test(value)) {
return [false, `${display} 不是合法的IPV6地址`];
}
break;
case 'enum':
if (filter && filter.indexOf('{') === 1) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
if (!Object.keys(filterJson).includes(`${value}`)) {
return [false, `${display} 不是合理的枚举值`];
}
}
break;
case 'bool':
if (filter && filter.indexOf('{') === 1) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
if (!Object.values(filterJson).includes(`${value}`)) {
return [false, `${display} 不是合理的布尔类型的值`];
}
}
break;
case 'string':
if (filter && filter.indexOf('~') !== -1) {
try {
const filterArr = filter.split('~');
let rule = new RegExp(
'^\\S{' + filterArr[0] + ',' + filterArr[1] + '}$'
);
if (!rule.test(value)) {
return [false, `${display} 参数值不合理`];
}
} catch (error) {
console.error(error);
}
}
break;
case 'regex':
if (filter) {
try {
let regex = new RegExp(filter);
if (!regex.test(value)) {
return [false, `${display} 参数值不合理`];
}
} catch (error) {
console.error(error);
}
}
break;
default:
return [false, `${display} 输入值是未知类型`];
}
return result;
}
onMounted(() => {
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
// 过滤不可用的网元
neCascaderOtions.value = useNeInfoStore().getNeCascaderOtions.filter(
(item: any) => {
return !['OMC'].includes(item.value);
}
);
// 默认选择AMF
const item = neCascaderOtions.value.find(s => s.value === 'UPF');
if (item) {
const info = item.children[0];
neTypeSelect.value = [info.neType, info.neId];
}
fnGetParamConfigTopTab();
fnGetParamConfigNode();
state.treeLoading = false;
}
} else {
message.warning({
content: `暂无网元列表数据`,
duration: 2,
});
}
});
});
// =================
/**对象信息状态类型 */
type StateType = {
/**网元 loading */
treeLoading: boolean;
/**网元配置 tree */
treeData: DataNode[];
/**选择对应Node tree */
treeSelectNode: Record<string, any>;
/**表单标题 */
title: string;
/**编辑行记录 */
editRecord: Record<string, any>;
};
/**对象信息状态 */
let state: StateType = reactive({
treeLoading: true,
treeData: [],
treeSelectNode: {
title: '',
keyType: '',
},
title: '',
editRecord: {},
});
/**表格字段列 */
let listColumns: ColumnsType = [
{
title: 'Key',
dataIndex: 'display',
align: 'left',
width: '30%',
},
{
title: 'Value',
dataIndex: 'value',
align: 'left',
width: '70%',
},
];
/**查询配置Node节点 */
function fnGetParamConfigNode() {
const neType = neTypeSelect.value[0];
if (!neType) {
message.warning({
content: `请选择网元类型`,
duration: 3,
});
return;
}
// 获取数据
getParamConfigTopTab(neType).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
state.treeData = res.data.map(item => ({
children: undefined,
title: item.topDisplay,
key: item.topTag,
// 绑定数据
keyType: '',
keyRecordList: [],
keyRecordRule: {},
}));
} else {
message.warning({
content: `暂无配置项数据`,
duration: 3,
});
}
});
}
function fnLoadConfigNodeData(treeNode: EventDataNode): Promise<void> {
console.log(treeNode, treeNode.dataRef?.children);
return new Promise<void>(resolve => {
if (treeNode.title.indexOf('Index-') === 0) {
resolve();
return;
}
if (treeNode.dataRef?.children) {
resolve();
return;
}
// 加载数据
const neType = neTypeSelect.value[0];
const neId = neTypeSelect.value[1];
const topTag = treeNode.key as string;
getParamConfigInfoTree(neType, topTag, neId).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
const { type, data, recordRule } = res.data;
if (treeNode.dataRef && type === 'list') {
treeNode.dataRef.keyType = type;
treeNode.dataRef.keyRecordList = data;
treeNode.dataRef.keyRecordRule = recordRule;
}
if (treeNode.dataRef && type === 'array') {
treeNode.dataRef.keyType = type;
treeNode.dataRef.keyRecordList = data;
treeNode.dataRef.keyRecordRule = recordRule;
// 添加子节点
if (!treeNode.dataRef.children) {
treeNode.dataRef.children = data.map(item => ({
title: item.title,
key: `${treeNode.key}/${item.key}`,
}));
}
}
//
state.title = treeNode.title;
state.treeSelectNode = Object.assign({}, treeNode.dataRef);
state.treeData = [...state.treeData];
resolve();
} else {
message.warning({
content: `暂无配置数据`,
duration: 3,
});
}
});
});
}
/**查询可选命令列表 */
function fnSelectConfigNode(_: any, info: any) {
console.log('fnSelectConfigNode ', info);
state.editRecord = {};
const node = info.node;
if (node.title.indexOf('Index-') === 0) {
const parentNode = Object.assign(
{},
JSON.parse(JSON.stringify(node.parent.node))
);
const child = parentNode.keyRecordList.find((item: any) => {
return item.title === node.title;
});
state.treeSelectNode = Object.assign({}, child);
state.title = node.title;
} else {
state.treeSelectNode = Object.assign({}, node.dataRef);
state.title = node.title;
}
}
/**单列表编辑 */
function listEdit2(row: Record<string, any>) {
state.editRecord = Object.assign({}, row);
}
/**单列表编辑关闭 */
function listEditClose2() {
state.editRecord = {};
}
/**单列表编辑确认 */
function listEditOk2() {
const from = toRaw(state.editRecord);
// 检查规则
const [ok, msg] = ruleVerification(from);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
let data = {
[from['name']]: from['value'],
};
console.log({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
topTag: tabState.tabActiveTopTag,
});
console.log(data);
}
/**多列表编辑 */
function arrayEdit2(record: Record<string, any>[]) {
const row: Record<string, any> = {};
for (const item of record) {
row[item.name] = Object.assign({}, item);
}
state.editRecord = row;
}
/**多列表编辑关闭 */
function arrayEditClose2() {
state.editRecord = {};
}
/**多列表编辑确认 */
function arrayEditOk2() {
const from = toRaw(state.editRecord);
const loc = from['index']['value'];
const neType = neTypeSelect.value[0];
// 遍历提取属性和值
let data: Record<string, any> = {};
for (const key in from) {
// 子嵌套的不插入
if (from[key]['array']) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(from[key]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
data[key] = from[key]['value'];
}
console.log({
neType: neType,
neId: neTypeSelect.value[1],
topTag: tabState.tabActiveTopTag,
loc,
});
console.log(data);
}
/**对话框对象信息状态类型 */
type ModalArrayChildStateType = {
/**框是否显示 */
visible: boolean;
/**标题 */
title: string;
/**array数据 */
data: Record<string, any>[];
};
/**对话框对象信息状态 */
let modalArrayChildState: ModalArrayChildStateType = reactive({
visible: false,
title: '上传更新',
data: [],
});
</script>
<template>
<PageContainer>
<a-row :gutter="16">
<a-col :span="6">
<!-- 网元类型 -->
<a-card :bordered="false" title="网元类型" :loading="state.treeLoading">
<!-- 插槽-卡片右侧 -->
<template #extra> </template>
<a-form layout="vertical" autocomplete="off">
<a-form-item name="neId ">
<a-cascader
v-model:value="neTypeSelect"
:options="neCascaderOtions"
:allow-clear="false"
placeholder="请选择网元"
@change="fnGetParamConfigNode"
/>
</a-form-item>
<a-form-item name="listeningPort">
<a-directory-tree
:tree-data="state.treeData"
:load-data="fnLoadConfigNodeData"
@select="fnSelectConfigNode"
>
<template #title="{ title, key }">
<span>{{ title }}</span>
<span> - {{ key }} </span>
</template>
</a-directory-tree>
</a-form-item>
</a-form>
</a-card>
</a-col>
<a-col :span="18">
<!-- 配置参数显示内容 -->
<a-card :bordered="false" :loading="!state.title">
<template #title>
<a-typography-text strong v-if="state.title">
{{ state.title }}
</a-typography-text>
<a-typography-text type="danger" v-else>
左侧配置中选择要操作项
</a-typography-text>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space
:size="8"
v-show="state.treeSelectNode.keyType === 'array'"
>
<a-button
type="primary"
@click.prevent="arrayEditClose2"
size="small"
>
<template #icon> <PlusOutlined /> </template>
{{ t('common.addText') }}
</a-button>
</a-space>
<a-space
:size="8"
v-show="state.treeSelectNode.title.indexOf('Index-') === 0"
>
<a-button type="default" @click.prevent="arrayEditClose2">
<template #icon> <ClearOutlined /> </template>
关闭
</a-button>
<a-button
type="default"
@click.prevent="arrayEdit2(state.treeSelectNode.record)"
>
<template #icon> <FormOutlined /> </template>
{{ t('common.editText') }}
</a-button>
<a-button type="primary" @click.prevent="arrayEditOk2">
<template #icon> <SendOutlined /> </template>
发送
</a-button>
</a-space>
</template>
<!-- list类型显示表格列表 -->
<a-table
v-show="state.treeSelectNode.keyType === 'list'"
class="table"
row-key="name"
:columns="listColumns"
:data-source="state.treeSelectNode.keyRecordList"
size="middle"
:pagination="false"
:bordered="true"
:scroll="{ x: true }"
>
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'value'">
<a-tooltip placement="topLeft">
<template #title v-if="record.comment">
{{ record.comment }}
</template>
<div class="editable-cell">
<div
v-if="state.editRecord['display'] === record['display']"
class="editable-cell__input-wrapper"
>
<a-space :size="16" align="center" direction="horizontal">
<a-input
v-if="
['string', 'ipv6', 'ipv4', 'regex'].includes(
record['type']
)
"
v-model:value="state.editRecord['value']"
style="min-width: 200px"
></a-input>
<a-input-number
v-else-if="record['type'] === 'int'"
v-model:value="state.editRecord['value']"
:min="0"
:max="65535"
style="min-width: 200px"
></a-input-number>
<a-switch
v-else-if="record['type'] === 'bool'"
v-model:checked="state.editRecord['value']"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
></a-switch>
<a-select
v-else-if="record['type'] === 'enum'"
v-model:value="state.editRecord['value']"
:allow-clear="true"
style="min-width: 200px"
>
<a-select-option
:value="+v"
:key="+v"
v-for="(k, v) in JSON.parse(record['filter'])"
>
{{ k }}
</a-select-option>
</a-select>
<a-popconfirm
title="确认更新该属性值吗?"
placement="top"
@confirm="listEditOk2()"
>
<CheckOutlined class="editable-cell__icon-edit" />
</a-popconfirm>
<CloseOutlined
class="editable-cell__icon-edit"
@click="listEditClose2()"
/>
</a-space>
</div>
<div v-else class="editable-cell__text-wrapper">
{{ `${text}` || '&nbsp;' }}
<EditOutlined
class="editable-cell__icon"
v-if="record.access !== 'read'"
@click="listEdit2(record)"
/>
</div>
</div>
</a-tooltip>
</template>
</template>
</a-table>
<!-- array类型显示提示内容 -->
<a-typography v-show="state.treeSelectNode.keyType === 'array'">
<a-typography-title>
{{ state.treeSelectNode.title }}
</a-typography-title>
<a-typography-paragraph>
可通过右上角
<a-typography-text mark>添加</a-typography-text>
当前到 {{ state.treeSelectNode.title }} 配置
<br />
当前存在以下配置项:
</a-typography-paragraph>
<a-typography-text
strong
v-for="item in state.treeSelectNode.children"
>
{{ item.title }} <br />
</a-typography-text>
</a-typography>
<!-- array类型数据项显示表单项 -->
<a-form
class="form"
v-show="state.treeSelectNode.title.indexOf('Index-') === 0"
layout="horizontal"
autocomplete="off"
:validate-on-rule-change="false"
:validateTrigger="[]"
>
<a-form-item
v-for="item in state.treeSelectNode.record"
:label="item.display"
:name="item.name"
:required="item.optional === 'false'"
>
<a-tooltip placement="topLeft">
<template #title v-if="item.comment">
{{ item.comment }}
</template>
<div class="editable-cell">
{{ state.editRecord[item.name] }}
<div
v-if="
!Array.isArray(item.array) &&
state.editRecord[item.name] !== undefined
"
class="editable-cell__input-wrapper"
>
<a-space :size="16" align="center" direction="horizontal">
<a-input
v-if="
['string', 'ipv6', 'ipv4', 'regex'].includes(
item['type']
)
"
v-model:value="state.editRecord[item.name]['value']"
></a-input>
<a-input-number
v-else-if="item['type'] === 'int'"
v-model:value="state.editRecord[item.name]['value']"
:min="0"
:max="65535"
></a-input-number>
<a-switch
v-else-if="item['type'] === 'bool'"
v-model:checked="state.editRecord[item.name]['value']"
: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.editRecord[item.name]['value']"
:allow-clear="true"
style="width: 100%"
>
<a-select-option
:value="+v"
:key="+v"
v-for="(k, v) in JSON.parse(item['filter'])"
>
{{ k }}
</a-select-option>
</a-select>
</a-space>
</div>
<a-button
type="link"
size="small"
@click.prevent=""
v-else-if="Array.isArray(item.array)"
>
详情
</a-button>
<div v-else class="editable-cell__text-wrapper">
{{ `${item.value}` }}
</div>
</div>
</a-tooltip>
</a-form-item>
</a-form>
</a-card>
</a-col>
</a-row>
<a-card
:bordered="false"
:body-style="{
marginBottom: '24px',
marginTop: '24px',
paddingBottom: 0,
}"
>
<!-- 表格搜索栏 -->
<a-form name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="网元类型" name="neTypeSelect">
<a-cascader
v-model:value="neTypeSelect"
:options="neCascaderOtions"
:allow-clear="false"
placeholder="请选择网元"
@change="fnGetParamConfigTopTab"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button
type="primary"
@click.prevent="fnGetParamConfigTopTab"
>
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ paddingTop: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title> </template>
<a-tabs
v-model:activeKey="tabState.tabActiveTopTag"
:tab-position="tabState.tabPosition"
@change="fnTabActiveTopTag"
>
<template #rightExtra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="default" @click.prevent="fnTabActiveTopTag('#')">
<template #icon>
<ReloadOutlined />
</template>
</a-button>
</a-tooltip>
</a-space>
</template>
<a-tab-pane
v-for="tab in tabState.tabNames"
:key="tab.topTag"
:tab="tab.topDisplay"
>
<!-- 单列表格列表 -->
<a-table
v-show="tableState.type === 'list'"
class="table"
row-key="name"
:columns="tableState.listColumns"
:loading="tableState.loading"
:data-source="tableState.listData"
:size="tableState.size"
:pagination="false"
:bordered="true"
:scroll="{ x: true }"
>
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'value'">
<a-tooltip>
<template #title v-if="record.comment">
{{ record.comment }}
</template>
<div class="editable-cell">
<div
v-if="
tableState.editRecord['display'] === record['display']
"
class="editable-cell__input-wrapper"
>
<a-space :size="16" align="center" direction="horizontal">
<a-input
v-if="
['string', 'ipv6', 'ipv4', 'regex'].includes(
record['type']
)
"
v-model:value="tableState.editRecord['value']"
:placeholder="record['filter']"
></a-input>
<a-input-number
v-else-if="record['type'] === 'int'"
v-model:value="tableState.editRecord['value']"
:min="0"
:max="65535"
:placeholder="record['filter']"
></a-input-number>
<a-switch
v-else-if="record['type'] === 'bool'"
v-model:checked="tableState.editRecord['value']"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
></a-switch>
<a-select
v-else-if="record['type'] === 'enum'"
v-model:value="tableState.editRecord['value']"
:placeholder="record['filter']"
:allow-clear="true"
>
<a-select-option
:value="+v"
:key="+v"
v-for="(k, v) in JSON.parse(record['filter'])"
>
{{ k }}
</a-select-option>
</a-select>
<a-popconfirm
title="确认更新该属性值吗?"
placement="top"
@confirm="listEditOk()"
>
<CheckOutlined class="editable-cell__icon-edit" />
</a-popconfirm>
<CloseOutlined
class="editable-cell__icon-edit"
@click="listEditClose()"
/>
</a-space>
</div>
<div v-else class="editable-cell__text-wrapper">
{{ `${text}` || '&nbsp;' }}
<EditOutlined
class="editable-cell__icon"
v-if="record.access !== 'read'"
@click="listEdit(record)"
/>
</div>
</div>
</a-tooltip>
</template>
</template>
</a-table>
<!-- 多列表格列表 -->
<a-table
v-show="tableState.type === 'array'"
class="table"
row-key="index"
:columns="tableState.arrayColumns"
:loading="tableState.loading"
:data-source="tableState.arrayData"
:size="tableState.size"
:pagination="false"
:bordered="true"
:scroll="{ x: tableState.arrayColumns.length * 200, y: 450 }"
:show-expand-column="false"
v-model:expanded-row-keys="tableState.arrayChildExpandKeys"
>
<template #title>
<a-button type="primary" @click.prevent="arrayAdd" size="small">
<template #icon> <PlusOutlined /> </template>
{{ t('common.addText') }}
</a-button>
</template>
<template #bodyCell="{ column, text, record }">
<template v-if="column.key === 'index'">
<a-space
:size="16"
align="center"
v-if="
tableState.editRecord[column.key]?.value ===
text[column.key]?.value
"
>
<a-tooltip>
<template #title>提交</template>
<a-popconfirm
v-if="
tableState.arrayNewIndex === text[column.key]?.value
"
:title="`确认提交新增 Index 为 【${
text[column.key]?.value
}】 的记录吗?`"
placement="left"
@confirm="arrayAddOk()"
>
<CheckOutlined class="editable-cell__icon-edit" />
</a-popconfirm>
<a-popconfirm
v-else
:title="`确认提交更新 Index 为 【${
text[column.key]?.value
}】 的记录吗?`"
placement="left"
@confirm="arrayEditOk()"
>
<CheckOutlined class="editable-cell__icon-edit" />
</a-popconfirm>
</a-tooltip>
<a-tooltip>
<template #title>取消</template>
<CloseOutlined
class="editable-cell__icon-edit"
@click="arrayEditClose()"
/>
</a-tooltip>
</a-space>
<a-space :size="8" align="center" v-else>
<a-tooltip>
<template #title>{{ t('common.editText') }}</template>
<a-button type="link" @click.prevent="arrayEdit(record)">
<template #icon><FormOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.deleteText') }}</template>
<a-button type="link" @click.prevent="arrayDelete(record)">
<template #icon><DeleteOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
<template v-else>
<a-tooltip>
<template #title v-if="text.comment">
{{ text.comment }}
</template>
<div class="editable-cell">
{{ tableState.editRecord[text.name] }}
<div
v-if="
column.dataIndex !== 'index' &&
!Array.isArray(text.array) &&
tableState.editRecord['index']?.value ===
record['index']?.value &&
tableState.editRecord[text.name]?.name ===
column.dataIndex
"
class="editable-cell__input-wrapper"
>
<a-space :size="16" align="center" direction="horizontal">
<a-input
v-if="
['string', 'ipv6', 'ipv4', 'regex'].includes(
text['type']
)
"
v-model:value="
tableState.editRecord[text.name]['value']
"
:placeholder="text['filter']"
></a-input>
<a-input-number
v-else-if="text['type'] === 'int'"
v-model:value="
tableState.editRecord[text.name]['value']
"
:min="0"
:max="65535"
:placeholder="text['filter']"
></a-input-number>
<a-switch
v-else-if="text['type'] === 'bool'"
v-model:checked="
tableState.editRecord[text.name]['value']
"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
></a-switch>
<a-select
v-else-if="text['type'] === 'enum'"
v-model:value="
tableState.editRecord[text.name]['value']
"
:placeholder="text['filter']"
:allow-clear="true"
>
<a-select-option
:value="v"
:key="v"
v-for="(k, v) in JSON.parse(text['filter'])"
>
{{ k }}
</a-select-option>
</a-select>
</a-space>
</div>
<a-button
type="link"
size="small"
@click.prevent="arrayChildExpand(record['index'], text)"
v-else-if="Array.isArray(text.array)"
>
详情
</a-button>
<div v-else class="editable-cell__text-wrapper">
{{ `${text.value}` }}
</div>
</div>
</a-tooltip>
</template>
</template>
<template #expandedRowRender>
<a-table
class="table"
row-key="index"
:columns="tableState.arrayChildColumns"
:data-source="tableState.arrayChildData"
:size="tableState.size"
:pagination="false"
:bordered="true"
:scroll="{
x: tableState.arrayChildColumns.length * 200,
y: 450,
}"
>
<template #title>
<a-button
type="primary"
@click.prevent="arrayChildAdd"
size="small"
>
<template #icon> <PlusOutlined /> </template>
{{ t('common.addText') }} {{ tableState.arrayChildTitle }}
</a-button>
</template>
<template #bodyCell="{ column, text, record }">
<template v-if="column.key === 'index'">
<a-space
:size="16"
align="center"
v-if="
tableState.arrayChildEditRecord[column.key]?.value ===
text[column.key]?.value
"
>
<a-tooltip>
<template #title>提交</template>
<a-popconfirm
v-if="
tableState.arrayChildNewIndex ===
text[column.key]?.value
"
:title="`确认提交新增 Index 为 【${
text[column.key]?.value
}】 的记录吗?`"
placement="left"
@confirm="arrayChildAddOk()"
>
<CheckOutlined class="editable-cell__icon-edit" />
</a-popconfirm>
<a-popconfirm
v-else
:title="`确认提交更新 Index 为 【${
text[column.key]?.value
}】 的记录吗?`"
placement="left"
@confirm="arrayChildEditOk()"
>
<CheckOutlined class="editable-cell__icon-edit" />
</a-popconfirm>
</a-tooltip>
<a-tooltip>
<template #title>取消</template>
<CloseOutlined
class="editable-cell__icon-edit"
@click="arrayChildEditClose()"
/>
</a-tooltip>
</a-space>
<a-space :size="8" align="center" v-else>
<a-tooltip>
<template #title>{{ t('common.editText') }}</template>
<a-button
type="link"
@click.prevent="arrayChildEdit(record)"
>
<template #icon><FormOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.deleteText') }}</template>
<a-button
type="link"
@click.prevent="arrayChildDelete(record)"
>
<template #icon><DeleteOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
<template v-else>
<a-tooltip>
<template #title v-if="text.comment">
{{ text.comment }}
</template>
<div class="editable-cell">
<div
v-if="
column.dataIndex !== 'index' &&
!Array.isArray(text.array) &&
tableState.arrayChildEditRecord['index']?.value ===
record['index']?.value &&
tableState.arrayChildEditRecord[text.name]?.name ===
column.dataIndex
"
class="editable-cell__input-wrapper"
>
<a-space
:size="16"
align="center"
direction="horizontal"
>
<a-input
v-if="
['string', 'ipv6', 'ipv4', 'regex'].includes(
text['type']
)
"
v-model:value="
tableState.arrayChildEditRecord[text.name][
'value'
]
"
:placeholder="text['filter']"
></a-input>
<a-input-number
v-else-if="text['type'] === 'int'"
v-model:value="
tableState.arrayChildEditRecord[text.name][
'value'
]
"
:min="0"
:max="65535"
:placeholder="text['filter']"
></a-input-number>
<a-switch
v-else-if="text['type'] === 'bool'"
v-model:checked="
tableState.arrayChildEditRecord[text.name][
'value'
]
"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
></a-switch>
<a-select
v-else-if="text['type'] === 'enum'"
v-model:value="
tableState.arrayChildEditRecord[text.name][
'value'
]
"
:placeholder="text['filter']"
:allow-clear="true"
>
<a-select-option
:value="v"
:key="v"
v-for="(k, v) in JSON.parse(text['filter'])"
>
{{ k }}
</a-select-option>
</a-select>
</a-space>
</div>
<a-button
type="link"
size="small"
@click.prevent="
arrayChildExpand(record['index'], text)
"
v-else-if="Array.isArray(text.array)"
>
详情
</a-button>
<div v-else class="editable-cell__text-wrapper">
{{ `${text.value}` }}
</div>
</div>
</a-tooltip>
</template>
</template>
</a-table>
</template>
</a-table>
</a-tab-pane>
</a-tabs>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.editable-cell {
&__icon {
display: none;
cursor: pointer;
}
&__icon:hover {
color: var(--ant-primary-color);
}
&__icon-edit:hover {
color: var(--ant-primary-color);
}
&__text-wrapper {
font-size: 16px;
color: #333;
}
&__text-wrapper:hover &__icon {
display: inline-block;
}
}
</style>