37 Commits

Author SHA1 Message Date
TsMask
c1187383b6 fix: 系统引导跳转不重复引导 2024-08-03 10:09:16 +08:00
lai
8d8605e0cd 调整网元配置模块 2024-08-02 18:55:53 +08:00
TsMask
8af48936e5 chore: 更新版本号 2.240801 2024-08-02 10:11:42 +08:00
TsMask
e21a8dc898 perf: 优化快速安装配置公共参数页面 2024-08-01 17:34:16 +08:00
TsMask
3c1ee63359 fix: 开站网元安装信息保存触发操作不一致 2024-07-30 18:23:51 +08:00
TsMask
bc3940016a perf: 网元公共参数合并到网元快速安装,移除相关多语言翻译 2024-07-30 18:22:32 +08:00
TsMask
6ab4fbea6f fix: UDM签约数据cnType改5GC Flag 2024-07-30 11:27:12 +08:00
TsMask
c73fcbd91c chore: 更新版本号 2.240729 2024-07-29 18:27:15 +08:00
TsMask
023e317b0d feat: 网元配置备份操作权限控制删除/编辑按钮 2024-07-29 14:36:19 +08:00
lai
543a2b7434 新增 统一配置网元 2024-07-29 10:38:14 +08:00
TsMask
4ae9051411 style: 系统设置LOGO和文档判断是否开启语言切换显示控件 2024-07-27 15:01:56 +08:00
TsMask
532546c3f5 chore: 更新版本号 2.240727 2024-07-27 10:28:23 +08:00
TsMask
ab0d26513c perf: 网元备份导入导出弹窗表单重构 2024-07-26 18:27:15 +08:00
TsMask
8e9498ec83 feat: 网元备份文件表格页面 2024-07-26 18:25:15 +08:00
TsMask
ca048f223c feat: UDM鉴权文件导入K4文件支持 2024-07-26 15:28:45 +08:00
TsMask
e9ff6493dd fix: 看板UPF流量吞吐初始查询和ws监听12_neId 2024-07-25 18:48:01 +08:00
TsMask
396fb0b124 fix: KPI接收指定对应neId,实时数据不支持搜索 2024-07-25 18:23:28 +08:00
TsMask
3a854be8fe fix: 查询IMS在线用户数接口数据格式兼容 2024-07-25 10:50:54 +08:00
TsMask
c1f34f56ac fix: 对使用手册,官网进行限制 2024-07-24 18:10:38 +08:00
lai
b80bae0126 Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-07-24 17:18:18 +08:00
lai
6c383b58c8 对使用手册,官网进行限制 2024-07-24 17:18:15 +08:00
TsMask
06db1344c3 fix: 日志事件cm结果多语言显示错误 2024-07-23 14:48:53 +08:00
TsMask
fac47279b5 fix: 日志IMS-CDR类型查询默认空进行全查询,表格宽度增加 2024-07-23 14:26:09 +08:00
TsMask
a36a12f783 fix: 日志事件类型查询默认空进行全查询 2024-07-23 14:25:08 +08:00
TsMask
acd19ebdbd style: 看板基站数量分开展示避免分不清数 2024-07-19 14:41:40 +08:00
TsMask
d53c4c34f0 style: 新增网元信息时ssh默认密码初始 2024-07-18 15:44:35 +08:00
TsMask
2d9011cf6b fix: 看板资源图无网元状体数据取值异常 2024-07-17 18:11:22 +08:00
TsMask
fcf53d0995 fix: 网元删除记录自动移除不要刷新列表 2024-07-17 18:06:59 +08:00
TsMask
a2d93ddafe fix: 系统名称横向滚动衔接动画 2024-07-16 11:21:16 +08:00
TsMask
79920542c1 chore: 更新版本号240712 2024-07-12 21:23:12 +08:00
TsMask
6efd9cb61a fix: 根据浏览器地址栏hostname加33030端口连接后端服务 2024-07-11 17:56:24 +08:00
TsMask
4dc6699974 style: UDM用户数据限制imsi长度输入 2024-07-11 15:02:53 +08:00
TsMask
3bdae264e1 fix: 网元信息单行更新局部信息变更失效 2024-07-10 15:47:28 +08:00
TsMask
9e0a99d160 style: 数据CDR的表头宽度调整 2024-07-09 19:02:54 +08:00
TsMask
5ac0ca41eb style: 签约用户IMSI编辑时禁止修改,cnType添加提示 2024-07-09 18:36:27 +08:00
TsMask
a7de701d4d fix: 支持kvdb依赖包安装,db_ip默认0.0.0.0 2024-07-09 16:11:58 +08:00
TsMask
f0561242ca fix: fetch请求拼接地址栏参数不区分请求方法 2024-07-09 10:04:46 +08:00
47 changed files with 2491 additions and 1558 deletions

View File

@@ -5,13 +5,13 @@ VITE_HISTORY_HASH = false
VITE_HISTORY_BASE_URL = "/"
# 应用名称
VITE_APP_NAME = "Core Network EMS"
VITE_APP_NAME = "Core Network OMC"
# 应用标识
VITE_APP_CODE = "CN EMS"
VITE_APP_CODE = "OMC"
# 应用版本
VITE_APP_VERSION = "2.240704"
VITE_APP_VERSION = "2.240801"
# 接口基础URL地址-不带/后缀
VITE_API_BASE_URL = "/omc-api"

View File

@@ -5,13 +5,13 @@ VITE_HISTORY_HASH = true
VITE_HISTORY_BASE_URL = "/"
# 应用名称
VITE_APP_NAME = "Core Network EMS"
VITE_APP_NAME = "Core Network OMC"
# 应用标识
VITE_APP_CODE = "CN EMS"
VITE_APP_CODE = "OMC"
# 应用版本
VITE_APP_VERSION = "2.240704"
VITE_APP_VERSION = "2.240801"
# 接口基础URL地址-不带/后缀
VITE_API_BASE_URL = "/omc-api"

View File

@@ -11,8 +11,9 @@
*/
(function () {
// host = ip:port
const host = '192.168.8.100:33030';
// const host = '192.168.8.100:33030';
const host = `${window.location.hostname}:33030`;
// Service Address
sessionStorage.setItem('baseUrl', `http://${host}`);
// websocket Address

View File

@@ -0,0 +1,83 @@
import { request } from '@/plugins/http-fetch';
/**
* 网元配置文件备份记录列表
* @param query 查询参数
* @returns object
*/
export function listNeConfigBackup(query: Record<string, any>) {
return request({
url: '/ne/config/backup/list',
method: 'get',
params: query,
});
}
/**
* 网元配置文件备份记录修改
* @param data 数据 { id, name, remark }
* @returns object
*/
export function updateNeConfigBackup(data: Record<string, any>) {
return request({
url: '/ne/config/backup',
method: 'put',
data: data,
});
}
/**
* 网元配置文件备份记录下载
* @param id 记录ID
* @returns object
*/
export async function downNeConfigBackup(id: string) {
return await request({
url: '/ne/config/backup/download',
method: 'get',
params: { id },
responseType: 'blob',
timeout: 180_000,
});
}
/**
* 网元配置文件备份记录删除
* @param id 记录ID
* @returns object
*/
export async function delNeConfigBackup(id: string) {
return request({
url: '/ne/config/backup',
method: 'delete',
params: { id },
});
}
/**
* 网元配置文件备份导出
* @param data 数据 { neType, neId }
* @returns object
*/
export function exportNeConfigBackup(data: Record<string, any>) {
return request({
url: '/ne/config/backup/export',
method: 'post',
data: data,
responseType: 'blob',
timeout: 180_000,
});
}
/**
* 网元配置文件备份导入
* @param data 数据 { neType, neId, type, path }
* @returns object
*/
export function importNeConfigBackup(data: Record<string, any>) {
return request({
url: '/ne/config/backup/import',
method: 'post',
data: data,
});
}

View File

@@ -113,7 +113,6 @@ export function batchDelUDMAuth(neId: string, imsi: string, num: number) {
/**
* UDM鉴权用户导入
* @param neId 网元ID
* @param data 表单数据对象
* @returns object
*/

View File

@@ -52,9 +52,11 @@ export async function listUENumByIMS(neId: String) {
method: 'get',
});
if (result.code === RESULT_CODE_SUCCESS) {
return Object.assign(result, {
data: result.data['ueNum'],
});
let num = result.data['ueNum'] || 0;
if (num === 0) {
num = result.data.data['ueNum'] || 0;
}
return Object.assign(result, { data: num });
}
// 模拟数据

View File

@@ -20,7 +20,7 @@ export const NE_TYPE_LIST = [
/**
* 网元拓展包列表,默认顺序
* IMS-adb/rtproxy/mf
* UDM-adb
* IMS-adb/kvdb/rtproxy/mf
* UDM-adb/kvdb
*/
export const NE_EXPAND_LIST = ['ADB', 'RTPROXY', 'MF'];
export const NE_EXPAND_LIST = ['ADB', 'KVDB', 'RTPROXY', 'MF'];

View File

@@ -668,9 +668,8 @@ export default {
local:'Local File',
localUpload:'Local Upload',
exportTip:'Confirm that you want to export the network element configuration file?',
exportMsg:'Exported successfully, please download from [Backup Management].',
filePlease: "Please upload a file",
fileNamePlease: 'Please select the server file',
exportMsg:'Exporting Network Element Configuration Information to a File Succeeded',
pathPlease: 'No Backup File Found',
},
},
neHost: {
@@ -770,18 +769,13 @@ export default {
uploadChangeOk: 'Network Element renewed license successfully and is being calibrated in the background!',
uploadChangeFail: "Some network elements failed to update the license, please check whether the service terminal environment is available!",
},
neConfPara5G: {
headerTip: 'Check and save the public parameter properties of the network element before performing the installation of the network element, and make sure that the latest parameter properties are applied.',
headerTipToPage: 'Jump Installation',
save: 'Save',
reload: 'Reload',
title: 'Save Info',
sync: 'Sync to NE',
syncNe: 'Select NE',
syncNeDone: 'Synchronization to network element terminals complete',
saveOk: 'Save Success!',
neConfigBackup: {
name: "Name",
downTip: 'Confirmed to download the backup file [{txt}]?',
title: "Modify Backup {txt}",
},
neQuickSetup: {
reloadPara5G: 'Reload',
stepPrev: 'Previous',
stepPrevTip: 'Confirm that you want to abandon the current change and return to the previous step?',
stepNext: 'Next',
@@ -874,8 +868,8 @@ export default {
checkDel: 'Check Delete',
batchAddText: 'Batch Add',
batchDelText: 'Batch Delete',
enable:'Enable',
disable:'Disable',
enable:'Enabled',
disable:'Disabled',
startIMSI: 'Start IMSI',
imsiTip: 'IMSI=MCC+MNC+MSIN',
imsiTip1: 'MCC=Mobile Country Code, consisting of three digits.',
@@ -888,7 +882,8 @@ export default {
micoTip: 'Signed MICO business flag bits',
rfspTip:'RFSP index, in NG-RAN, the index of a specific RRM configuration, parameter between 0 and 127',
ueTypeTip: 'Operator-defined subscriber UE Usage Type, integer, parameter between 0 and 127',
epsFlagTip: 'Enable or disable 4G EPS service',
cnFlag: 'Whether to enable 5G Core Network service',
epsFlagTip: 'Whether to enable 4G EPS service',
contextIdTip: 'To sign up for an APN Context ID, you must select it from the APN Context list.',
apnContextTip: 'The list of APNs available to the phone, up to six, is defined in the HSS.',
staticIpTip: 'Specify the static IP address to be used by the cell phone user to access the Internet, and "-" means dynamic IP address is used.',
@@ -1947,6 +1942,7 @@ export default {
stepNeInfoStepNext: 'Confirm that you want to proceed to the next step to configure the parameters of the network element?',
stepPara5GTitle: "Configuration Parameter",
stepPara5GDesc: "Setting network element global parameter information",
savePara5GOk: 'Save Success!',
stepPara5GStepPrev: 'Confirm that you want to abandon the current change and return to the previous step?',
stepPara5GStepNext: 'Confirm that you want to proceed to the next step for the network element service installation?',
stepInstallTitle: "Service Install",

View File

@@ -668,9 +668,8 @@ export default {
local:'本地文件',
localUpload:'本地上传',
exportTip:'确认要导出网元配置信息到文件?',
exportMsg:'导出成功,请到【备份管理】进行下载',
filePlease: "请上传文件",
fileNamePlease: '请选择服务器文件',
exportMsg:'导出网元配置信息到文件成功',
pathPlease: '未发现文件',
},
},
neHost: {
@@ -770,18 +769,13 @@ export default {
uploadChangeOk: '网元更新许可证成功,正在后台校验!',
uploadChangeFail: "部分网元更新许可证失败,请检查服务终端环境是否可用!",
},
neConfPara5G: {
headerTip: '进行网元安装前检查并保存网元公共参数属性,确认应用为最新参数属性',
headerTipToPage: '跳转安装',
save: '保存',
reload: '刷新',
title: '保存信息',
sync: '同步到网元',
syncNe: '选择网元',
syncNeDone: '同步到网元终端完成',
saveOk: '保存成功!',
neConfigBackup: {
name: "名称",
downTip: '确认要下载备份文件【{txt}】吗?',
title: "修改备份信息 {txt}",
},
neQuickSetup: {
reloadPara5G: '刷新',
stepPrev: '上一步',
stepPrevTip: '确认要放弃当前变更返回上一步吗?',
stepNext: '下一步',
@@ -888,7 +882,8 @@ export default {
micoTip: '签约的 MICO 业务标志位',
rfspTip:'RFSP 索引,在 NG-RAN 中,特定 RRM 配置的索引,参数介于0到127之间',
ueTypeTip: '运营商定义的用户 UE Usage Type整型参数介于0到127之间',
epsFlagTip: '是否开启4G EPS 服务',
cnFlag: '是否开启 5G Core Network 服务',
epsFlagTip: '是否开启 4G EPS 服务',
contextIdTip: '签约APN 上下文ID必须从APN Context list 中选择。',
apnContextTip: '手机可用的APN列表最多六个在HSS中定义。',
staticIpTip: '指定手机用户上网时使用的静态IP地址,为"-"时表示使用动态IP地址',
@@ -1947,6 +1942,7 @@ export default {
stepNeInfoStepNext: '确认要下一步进行网元配置参数?',
stepPara5GTitle: "网元配置参数",
stepPara5GDesc: "设置网元全局参数信息",
savePara5GOk: '保存成功!',
stepPara5GStepPrev: '确认要放弃当前变更返回上一步吗?',
stepPara5GStepNext: '确认要下一步进行网元服务安装吗?',
stepInstallTitle: "网元服务安装",

View File

@@ -176,6 +176,7 @@ function fnCheckAppNameOverflow() {
if (!text) return;
if (text.offsetWidth > container.offsetWidth) {
text.classList.add('app-name_scrollable');
text.setAttribute('data-content', text.innerText);
} else {
text.classList.remove('app-name_scrollable');
}
@@ -342,11 +343,17 @@ onUnmounted(() => {
:href="appStore.officialUrl"
target="_blank"
size="small"
v-perms:has="['system:setting:official']"
v-if="appStore.officialUrl !== '#'"
>
{{ t('loayouts.basic.officialUrl') }}
</a-button>
<a-button type="link" size="small" @click="fnClickHelpDoc()">
<a-button
type="link"
size="small"
v-perms:has="['system:setting:doc']"
@click="fnClickHelpDoc()"
>
{{ t('loayouts.basic.helpDoc') }}
</a-button>
</a-space>
@@ -382,20 +389,26 @@ onUnmounted(() => {
// text-overflow: ellipsis;
white-space: nowrap;
width: 148px;
&_scrollable {
// padding-left: 100%;
> .app-name_scrollable {
padding-right: 12px;
display: inline-block;
animation: scrollable-animation linear 6s infinite both;
}
@keyframes scrollable-animation {
0% {
transform: translate3d(0, 0, 0);
&::after {
content: attr(data-content);
position: absolute;
top: 0;
right: -100%;
transition: right 0.3s ease;
}
100% {
transform: translate3d(-100%, 0, 0);
@keyframes scrollable-animation {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(-100% - 12px));
}
}
}
}

View File

@@ -89,49 +89,54 @@ function fnChangeLocale(e: any) {
</a-button>
<!-- 锁屏操作 -->
<a-tooltip placement="bottom">
<template #title>{{ t('loayouts.rightContent.lock') }}</template>
<a-button type="text" style="color: inherit" @click="fnClickLock()">
<template #icon>
<LockOutlined />
</template>
</a-button>
<ProModal
:drag="true"
:width="400"
:minHeight="200"
:mask-closable="false"
v-model:visible="lockConfirm"
:title="t('loayouts.rightContent.lockTip')"
@ok="fnClickLockToPage()"
>
<a-space>
{{ t('loayouts.rightContent.lockPasswd') }}
<a-input-password
v-model:value="lockPasswd"
:placeholder="t('common.inputPlease')"
>
<template #prefix>
<a-tooltip
:title="t('loayouts.rightContent.lockPasswdTip')"
placement="topLeft"
>
<UnlockOutlined />
</a-tooltip>
</template>
</a-input-password>
</a-space>
</ProModal>
</a-tooltip>
<span v-perms:has="['system:setting:lock']">
<a-tooltip placement="bottom">
<template #title>{{ t('loayouts.rightContent.lock') }}</template>
<a-button type="text" style="color: inherit" @click="fnClickLock()">
<template #icon>
<LockOutlined />
</template>
</a-button>
<ProModal
:drag="true"
:width="400"
:minHeight="200"
:mask-closable="false"
v-model:visible="lockConfirm"
:title="t('loayouts.rightContent.lockTip')"
@ok="fnClickLockToPage()"
>
<a-space>
{{ t('loayouts.rightContent.lockPasswd') }}
<a-input-password
v-model:value="lockPasswd"
:placeholder="t('common.inputPlease')"
>
<template #prefix>
<a-tooltip
:title="t('loayouts.rightContent.lockPasswdTip')"
placement="topLeft"
>
<UnlockOutlined />
</a-tooltip>
</template>
</a-input-password>
</a-space>
</ProModal>
</a-tooltip>
</span>
<a-tooltip placement="bottom">
<template #title>{{ t('loayouts.rightContent.helpDoc') }}</template>
<a-button type="text" style="color: inherit" @click="fnClickHelpDoc()">
<template #icon>
<QuestionCircleOutlined />
</template>
</a-button>
</a-tooltip>
<!-- 用户帮助手册 -->
<span v-perms:has="['system:setting:doc']">
<a-tooltip placement="bottom">
<template #title>{{ t('loayouts.rightContent.helpDoc') }}</template>
<a-button type="text" style="color: inherit" @click="fnClickHelpDoc()">
<template #icon>
<QuestionCircleOutlined />
</template>
</a-button>
</a-tooltip>
</span>
<a-tooltip placement="bottom">
<template #title>{{ t('loayouts.rightContent.fullscreen') }}</template>

View File

@@ -165,8 +165,8 @@ function beforeRequest(options: OptionsType): OptionsType | Promise<any> {
}
}
// get请求拼接地址栏参数
if (options.method === 'get' && options.params) {
// 请求拼接地址栏参数
if (options.params) {
let paramStr = '';
const params = options.params;
for (const key in params) {

View File

@@ -170,6 +170,10 @@ router.beforeEach(async (to, from, next) => {
if (appStore.bootloader && to.path !== '/quick-start') {
next({ name: 'QuickStart' });
}
// 不重复引导
if (!appStore.bootloader && to.path === '/quick-start') {
next({ name: 'Index' });
}
const token = getToken();

View File

@@ -42,7 +42,7 @@ let queryParams = reactive({
/**网元类型 */
neType: 'AMF',
neId: '001',
eventType: 'auth-result',
eventType: '',
imsi: '',
sortField: 'timestamp',
sortOrder: 'desc',
@@ -58,9 +58,9 @@ let queryParams = reactive({
/**查询参数重置 */
function fnQueryReset() {
eventTypes.value = ['auth-result'];
eventTypes.value = [];
queryParams = Object.assign(queryParams, {
eventType: 'auth-result',
eventType: '',
imsi: '',
startTime: '',
endTime: '',
@@ -74,7 +74,7 @@ function fnQueryReset() {
}
/**记录类型 */
const eventTypes = ref<string[]>(['auth-result']);
const eventTypes = ref<string[]>([]);
/**查询记录类型变更 */
function fnQueryEventTypeChange(value: any) {
@@ -607,10 +607,7 @@ onBeforeUnmount(() => {
:value="record.eventJSON.authCode"
/>
</span>
<span
v-if="record.eventType === 'detach'"
:title="record.eventJSON.detachTime"
>
<span v-if="record.eventType === 'detach'">
<span>{{ t('views.dashboard.ue.resultOk') }}</span>
</span>
<span v-if="record.eventType === 'cm-state'">
@@ -706,7 +703,7 @@ onBeforeUnmount(() => {
/>
</span>
<span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOK') }}
{{ t('views.dashboard.ue.resultOk') }}
</span>
<span v-if="record.eventType === 'cm-state'">
<DictTag

View File

@@ -44,7 +44,7 @@ let queryParams = reactive({
/**网元类型 */
neType: 'IMS',
neId: '001',
recordType: 'MOC',
recordType: '',
callerParty: '',
calledParty: '',
sortField: 'timestamp',
@@ -61,9 +61,9 @@ let queryParams = reactive({
/**查询参数重置 */
function fnQueryReset() {
recordTypes.value = ['MOC'];
recordTypes.value = [];
queryParams = Object.assign(queryParams, {
recordType: 'MOC',
recordType: '',
callerParty: '',
calledParty: '',
startTime: '',
@@ -78,7 +78,7 @@ function fnQueryReset() {
}
/**记录类型 */
const recordTypes = ref<string[]>(['MOC']);
const recordTypes = ref<string[]>([]);
/**查询记录类型变更 */
function fnQueryRecordTypeChange(value: any) {
@@ -639,7 +639,7 @@ onBeforeUnmount(() => {
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',

View File

@@ -43,7 +43,7 @@ let queryParams = reactive({
/**网元类型 */
neType: 'MME',
neId: '001',
eventType: 'auth-result',
eventType: '',
imsi: '',
sortField: 'timestamp',
sortOrder: 'desc',
@@ -59,9 +59,9 @@ let queryParams = reactive({
/**查询参数重置 */
function fnQueryReset() {
eventTypes.value = ['auth-result'];
eventTypes.value = [];
queryParams = Object.assign(queryParams, {
eventType: 'auth-result',
eventType: '',
imsi: '',
startTime: '',
endTime: '',
@@ -75,7 +75,7 @@ function fnQueryReset() {
}
/**记录类型 */
const eventTypes = ref<string[]>(['auth-result']);
const eventTypes = ref<string[]>([]);
/**查询记录类型变更 */
function fnQueryEventTypeChange(value: any) {
@@ -670,7 +670,7 @@ onBeforeUnmount(() => {
/>
</span>
<span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOK') }}
{{ t('views.dashboard.ue.resultOk') }}
</span>
<span v-if="record.eventType === 'cm-state'">
<DictTag

View File

@@ -237,7 +237,7 @@ function handleRanderChart(
function fnChangeData(data: any[], itemID: string) {
let info = data.find((item: any) => item.id === itemID);
if (!info.neState.online) return;
if (!info || !info.neState.online) return;
// if (!info.neState.online) {
// info = data.find((item: any) => item.id === itemID);
// graphNodeClickID.value = itemID;

View File

@@ -203,17 +203,14 @@ function handleRanderChart() {
/**查询初始UPF数据 */
function fnGetInitData() {
// 查询10分钟前的
const nowDate: Date = new Date();
const tenMinutesAgo = new Date(nowDate.getTime() - 5 * 60 * 1000);
// 查询5分钟前的
const nowDate = new Date().getTime();
listKPIData({
neType: 'UPF',
neId: '001',
startTime: parseDateToStr(tenMinutesAgo),
endTime: parseDateToStr(nowDate),
// startTime: '2024-03-20 19:50:00',
// endTime: '2024-03-20 19:55:00',
startTime: nowDate - 5 * 60 * 1000,
endTime: nowDate,
interval: 5, // 5秒
sortField: 'timeGroup',
sortOrder: 'asc',

View File

@@ -159,14 +159,14 @@
/* 概览区域 衍生基站信息 */
.skim.base {
height: 20.6%;
height: 12%;
}
.skim.base .inner .data {
display: flex;
flex-direction: row;
align-items: center;
height: 45%;
height: 75%;
}
.skim.base .inner .data .item {
display: flex;
@@ -178,7 +178,7 @@
/* 用户行为 */
.userActivity {
/* min-height: 35.8rem; */
height: 60%;
height: 54.6%;
}
.userActivity .inner .chart {
width: 100%;

View File

@@ -1,3 +1,4 @@
import { parseDateToStr } from '@/utils/date-utils';
import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils';
import { ref } from 'vue';
@@ -22,7 +23,7 @@ export const upfFlowData = ref<FDType>({
/**UPF-流量数据 数据解析 */
export function upfFlowParse(data: Record<string, string>) {
upfFlowData.value.lineXTime.push(data['timeGroup']);
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup']));
const upN3 = parseSizeFromKbs(+data['UPF.03'], 5);
upfFlowData.value.lineYUp.push(upN3[0]);
const downN6 = parseSizeFromKbs(+data['UPF.06'], 5);
@@ -78,7 +79,7 @@ export function upfTFParse(data: Record<string, string>) {
export const upfTFActive = ref<number>(0);
/**属性复位 */
export function upfTotalFlowReset() {
export function upfTotalFlowReset() {
upfFlowData.value = {
lineXTime: [],
lineYUp: [],

View File

@@ -94,7 +94,7 @@ export default function useWS() {
}
switch (data.groupId) {
// kpiEvent 指标UPF
case '12':
case '12_001':
if (data.data) {
upfFlowParse(data.data);
}
@@ -197,12 +197,12 @@ export default function useWS() {
params: {
/**订阅通道组
*
* 指标UPF (GroupID:12)
* 指标UPF (GroupID:12_neId)
* AMF_UE会话事件(GroupID:1010)
* MME_UE会话事件(GroupID:1011)
* IMS_CDR会话事件(GroupID:1005)
*/
subGroupID: '12,1010,1011,1005',
subGroupID: '12_001,1010,1011,1005',
},
onmessage: wsMessage,
onerror: wsError,

View File

@@ -270,7 +270,7 @@ onBeforeUnmount(() => {
<div class="skim panel base">
<div class="inner">
<h3>
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp;
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; 5G
{{ t('views.dashboard.overview.skim.baseTitle') }}
</h3>
<div class="data">
@@ -302,6 +302,14 @@ onBeforeUnmount(() => {
<span>{{ t('views.dashboard.overview.skim.gnbUeNum') }}</span>
</div>
</div>
</div>
</div>
<div class="skim panel base">
<div class="inner">
<h3>
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; 4G
{{ t('views.dashboard.overview.skim.baseTitle') }}
</h3>
<div class="data">
<div
class="item toRouter"

View File

@@ -197,7 +197,7 @@ let tableColumns: ColumnsType = [
title: t('views.dashboard.cdr.smfInvocationTime'), // 调用时间
dataIndex: 'cdrJSON',
align: 'left',
width: 250,
width: 200,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.invocationTimestamp;
@@ -616,7 +616,7 @@ onBeforeUnmount(() => {
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',

View File

@@ -153,11 +153,10 @@ type nfStateType = {
/**网元详细信息 */
let pronInfo: nfStateType = reactive({
hostName: '5gc',
osInfo:
'Linux 5gc 4.15.0-112-generic #113-Ubuntu SMP Thu Jul 9 23:41:39 UTC 2020 x86_64 GNU/Linux',
dbInfo: 'adb v1.0.1',
osInfo: 'Linux 5gc 4.15.0-112-generic 2020 x86_64 GNU/Linux',
dbInfo: 'db v9.9.9',
ipAddress: '-',
port: 3030,
port: 33030,
version: '-',
cpuUse: '-',
memoryUse: '-',

View File

@@ -134,16 +134,6 @@ onMounted(() => {
function fnChangeLocale(e: any) {
changeLocale(e.key);
}
/**系统使用手册跳转 */
function fnClickHelpDoc(language?: string) {
const routeData = router.resolve({ name: 'HelpDoc' });
let href = routeData.href;
if (language) {
href = `${routeData.href}?language=${language}`;
}
window.open(href, '_blank');
}
</script>
<template>
@@ -166,23 +156,6 @@ function fnClickHelpDoc(language?: string) {
<img :src="logoUrl" class="logo-brand" :alt="appStore.appName" />
</template>
</div>
<div class="header-right">
<a-space direction="horizontal" :size="8">
<a-button
type="link"
:href="appStore.officialUrl"
target="_blank"
size="small"
v-if="appStore.officialUrl !== '#'"
>
{{ t('loayouts.basic.officialUrl') }}
</a-button>
<a-button type="link" size="small" @click="fnClickHelpDoc()">
{{ t('loayouts.basic.helpDoc') }}
</a-button>
</a-space>
</div>
</header>
<a-card :bordered="false" class="login-card">

View File

@@ -1,296 +0,0 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message } from 'ant-design-vue/lib';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useI18n from '@/hooks/useI18n';
import {
getPara5GFilee,
listNeInfo,
savePara5GFile,
updateNeInfo,
} from '@/api/ne/neInfo';
import useNeInfoStore from '@/store/modules/neinfo';
import Para5GForm from './components/Para5GForm.vue';
const { t } = useI18n();
/**对象信息信息状态类型 */
type StateType = {
/**保存选择同步到网元窗 */
visible: boolean;
/**网元选择 */
neSelectOtions: any[];
/**同步到网元 */
sync: boolean;
syncNe: string[];
syncMsg: string;
/**表单数据 */
from: Record<string, any>;
/**OMC信息需修改当前的IP */
omcInfo: Record<string, any>;
/**根据网元显示配置项 */
hasNE: {
amf: boolean;
upf: boolean;
ims: boolean;
mme: boolean;
};
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对象信息状态 */
let state: StateType = reactive({
visible: false,
neSelectOtions: [],
sync: false,
syncNe: [],
syncMsg: '',
from: {},
omcInfo: {},
hasNE: {
amf: false,
upf: false,
ims: false,
mme: false,
},
confirmLoading: false,
});
/**对话框弹出确认执行函数*/
function fnModalOk() {
if (state.confirmLoading) return;
state.confirmLoading = true;
savePara5GFile({
content: toRaw(state.from),
syncNe: state.sync ? state.syncNe : [],
})
.then(res => {
if (state.sync) {
if (res.code === RESULT_CODE_SUCCESS) {
state.syncMsg = t('views.ne.neConfPara5G.syncNeDone');
} else {
state.syncMsg = res.msg;
}
} else {
message.success(t('views.ne.neConfPara5G.saveOk'));
// 更新omc_ip
state.omcInfo.ip = state.from.sbi.omc_ip;
updateNeInfo(toRaw(state.omcInfo));
}
})
.finally(() => {
state.confirmLoading = false;
});
}
/**对话框弹出关闭执行函数*/
function fnModalCancel() {
state.visible = false;
state.sync = false;
state.syncNe = [];
state.syncMsg = '';
}
/**保存文件数据*/
function fnSaveData() {
state.visible = true;
}
/**获取文件数据*/
function fnGetData() {
state.confirmLoading = true;
Promise.all([
getPara5GFilee(),
listNeInfo({
pageNum: 1,
pageSize: 20,
}),
]).then(resArr => {
// 已保存的配置
if (resArr[0].code === RESULT_CODE_SUCCESS) {
Object.assign(state.from, resArr[0].data);
}
// 填充固定网元类型的ip
if (
resArr[1].code === RESULT_CODE_SUCCESS &&
Array.isArray(resArr[1].rows)
) {
for (const item of resArr[1].rows) {
switch (item.neType) {
case 'OMC':
// state.from.sbi.omc_ip = item.ip;
Object.assign(state.omcInfo, item); // 主动改OMC_IP
break;
case 'IMS':
state.from.sbi.ims_ip = item.ip;
// state.from.external.ims_sip_ip = item.ip;
state.hasNE.ims = true;
break;
case 'AMF':
state.from.sbi.amf_ip = item.ip;
state.hasNE.amf = true;
break;
case 'AUSF':
state.from.sbi.ausf_ip = item.ip;
break;
case 'UDM':
state.from.sbi.udm_ip = item.ip;
state.from.sbi.adb_ip = '0.0.0.0';
break;
case 'SMF':
state.from.sbi.smf_ip = item.ip;
break;
case 'PCF':
state.from.sbi.pcf_ip = item.ip;
break;
case 'NSSF':
state.from.sbi.nssf_ip = item.ip;
break;
case 'NRF':
state.from.sbi.nrf_ip = item.ip;
break;
case 'UPF':
state.from.sbi.upf_ip = item.ip;
state.hasNE.upf = true;
break;
case 'LMF':
state.from.sbi.lmf_ip = item.ip;
break;
case 'NEF':
state.from.sbi.nef_ip = item.ip;
break;
case 'MME':
state.from.sbi.mme_ip = item.ip;
if (item.ip.includes('.')) {
state.from.external.mmes11_ip = item.ip + '/24';
}
state.hasNE.mme = true;
break;
case 'N3IWF':
state.from.sbi.n3iwf_ip = item.ip;
break;
}
}
}
state.confirmLoading = false;
});
}
onMounted(() => {
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
for (const row of res.data) {
state.neSelectOtions.push({
label: `[${row.neType} ${row.neId}] ${row.neName}`,
value: `${row.neType}@${row.neId}`,
});
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
// 获取文件数据
fnGetData();
});
});
</script>
<template>
<PageContainer>
<template #content>
{{ t('views.ne.neConfPara5G.headerTip') }}
<RouterLink :to="{ name: 'NeQuickSetup_2142' }">
{{ t('views.ne.neConfPara5G.headerTipToPage') }}
</RouterLink>
</template>
<a-card :bordered="false">
<!-- 公共参数表单 -->
<Para5GForm v-model:data="state.from" :ne="state.hasNE"></Para5GForm>
<div style="padding: 24px 12px 0; text-align: end">
<a-space :size="8" align="center">
<a-button
type="primary"
:loading="state.confirmLoading"
@click="fnSaveData()"
>
<template #icon><SaveOutlined /></template>
{{ t('views.ne.neConfPara5G.save') }}
</a-button>
<a-button
type="default"
:loading="state.confirmLoading"
@click.prevent="fnGetData()"
>
<template #icon><ReloadOutlined /></template>
{{ t('views.ne.neConfPara5G.reload') }}
</a-button>
</a-space>
</div>
</a-card>
<!-- 保存选择同步网元 -->
<ProModal
:drag="true"
:minHeight="0"
:keyboard="false"
:mask-closable="false"
:visible="state.visible"
:title="t('views.ne.neConfPara5G.title')"
:confirm-loading="state.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="syncNeModal"
layout="horizontal"
:label-col="{ span: 5 }"
:label-wrap="true"
>
<a-form-item :label="t('views.ne.neConfPara5G.sync')" name="sync">
<a-switch
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
v-model:checked="state.sync"
:disabled="state.confirmLoading"
></a-switch>
</a-form-item>
<a-form-item
:label="t('views.ne.neConfPara5G.syncNe')"
name="syncNe"
v-if="state.sync"
>
<a-select
v-model:value="state.syncNe"
mode="multiple"
:placeholder="t('common.selectPlease')"
:max-tag-count="3"
:options="state.neSelectOtions"
>
<template #maxTagPlaceholder="omittedValues">
<span>+ {{ omittedValues.length }} ...</span>
</template>
</a-select>
</a-form-item>
<a-form-item label="Sync Msg" name="syncMsg" v-if="state.syncMsg">
<a-textarea
:disabled="true"
:value="state.syncMsg"
:auto-size="{ minRows: 2, maxRows: 8 }"
style="background-color: transparent; color: rgba(0, 0, 0, 0.85)"
/>
</a-form-item>
</a-form>
</ProModal>
</PageContainer>
</template>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,605 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { Form, Modal, TableColumnsType, message } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
import useDictStore from '@/store/modules/dict';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { parseDateToStr } from '@/utils/date-utils';
import {
delNeConfigBackup,
downNeConfigBackup,
listNeConfigBackup,
updateNeConfigBackup,
} from '@/api/ne/neConfigBackup';
import saveAs from 'file-saver';
const { t } = useI18n();
const { getDict } = useDictStore();
/**字典数据-状态 */
let dictStatus = ref<DictType[]>([]);
/**网元参数 */
let neOtions = ref<Record<string, any>[]>([]);
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: undefined,
/**名称 */
name: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
neType: undefined,
name: '',
pageNum: 1,
pageSize: 20,
});
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: any[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: false,
data: [],
selectedRowKeys: [],
});
/**表格字段列 */
let tableColumns = ref<TableColumnsType>([
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'left',
width: 100,
},
{
title: t('views.ne.common.neType'),
dataIndex: 'neType',
align: 'left',
width: 100,
},
{
title: t('views.ne.common.neId'),
dataIndex: 'neId',
align: 'left',
width: 100,
},
{
title: t('common.createTime'),
dataIndex: 'createTime',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value);
},
width: 150,
},
{
title: t('views.ne.neConfigBackup.name'),
dataIndex: 'name',
align: 'left',
width: 200,
resizable: true,
minWidth: 100,
maxWidth: 300,
ellipsis: true,
},
{
title: t('common.remark'),
dataIndex: 'remark',
key: 'remark',
align: 'left',
width: 150,
resizable: true,
minWidth: 100,
maxWidth: 300,
ellipsis: true,
},
{
title: t('common.operate'),
key: 'id',
align: 'left',
},
]);
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
listNeConfigBackup(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
tablePagination.total = res.total;
tableState.data = res.rows;
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
}
tableState.loading = false;
});
}
/**信息文件下载 */
function fnDownloadFile(row: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neConfigBackup.downTip', { txt: row.name }),
onOk() {
const hide = message.loading(t('common.loading'), 0);
downNeConfigBackup(row.id)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 2,
});
saveAs(res.data, `${row.name}`);
} else {
message.error({
content: `${res.msg}`,
duration: 2,
});
}
})
.finally(() => {
hide();
});
},
});
}
/**
* 记录删除
* @param id 编号
*/
function fnRecordDelete(id: string) {
if (!id || modalState.confirmLoading) return;
let msg = id;
if (id === '0') {
msg = `...${tableState.selectedRowKeys.length}`;
id = tableState.selectedRowKeys.join(',');
}
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.dashboard.ue.delTip', { msg }),
onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
delNeConfigBackup(id)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
fnGetList(1);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
},
});
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
visibleByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visibleByEdit: false,
title: '备份记录',
from: {
id: undefined,
name: '',
remark: '',
},
confirmLoading: false,
});
/**
* 对话框弹出显示为 新增或者修改
* @param noticeId 网元id, 不传为新增
*/
function fnModalVisibleByEdit(row: Record<string, any>) {
if (modalState.confirmLoading) return;
modalState.from.id = row.id;
modalState.from.name = row.name;
modalState.from.remark = row.remark;
modalState.title = t('views.ne.neConfigBackup.title', { txt: row.id });
modalState.visibleByEdit = true;
}
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
name: [
{
required: true,
message: '请输入名称',
},
],
})
);
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
modalStateFrom
.validate()
.then(e => {
modalState.confirmLoading = true;
const from = toRaw(modalState.from);
const hide = message.loading(t('common.loading'), 0);
updateNeConfigBackup(from)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.msgSuccess', { msg: modalState.title }),
duration: 3,
});
modalState.visibleByEdit = false;
modalStateFrom.resetFields();
fnGetList();
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
fnGetList();
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
})
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.visibleByEdit = false;
modalStateFrom.resetFields();
}
onMounted(() => {
// 初始字典数据
getDict('ne_license_status').then(res => {
dictStatus.value = res;
});
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
neOtions.value = useNeInfoStore().getNeSelectOtions;
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
// 获取列表数据
fnGetList();
});
});
</script>
<template>
<PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType ">
<a-auto-complete
v-model:value="queryParams.neType"
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
:allow-clear="true"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item :label="t('views.ne.neConfigBackup.name')" name="name">
<a-input
v-model:value="queryParams.name"
:allow-clear="true"
:placeholder="t('common.inputPlease')"
></a-input>
</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="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')"
v-perms:has="['ne:neConfigBackup:remove']"
>
<template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }}
</a-button>
</a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip placement="topRight">
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown placement="bottomRight" trigger="click">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
<a-menu-item key="middle">
{{ t('common.size.middle') }}
</a-menu-item>
<a-menu-item key="small">
{{ t('common.size.small') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 180 }"
@resizeColumn="(w:number, col:any) => (col.width = w)"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.downloadText') }}</template>
<a-button type="link" @click.prevent="fnDownloadFile(record)">
<template #icon><DownloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.deleteText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordDelete(record.id)"
v-perms:has="['ne:neConfigBackup:remove']"
>
<template #icon><DeleteOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.editText') }}</template>
<a-button
type="link"
@click.prevent="fnModalVisibleByEdit(record)"
v-perms:has="['ne:neConfigBackup:edit']"
>
<template #icon><FormOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</a-card>
<!-- 新增框或修改框 -->
<ProModal
:drag="true"
:width="512"
:destroyOnClose="true"
:keyboard="false"
:mask-closable="false"
:visible="modalState.visibleByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:wrapper-col="{ span: 18 }"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-form-item
:label="t('views.ne.neConfigBackup.name')"
name="name"
v-bind="modalStateFrom.validateInfos.name"
>
<a-input
v-model:value="modalState.from.name"
:allow-clear="true"
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
<a-form-item
:label="t('common.remark')"
name="remark"
v-bind="modalStateFrom.validateInfos.remark"
>
<a-textarea
v-model:value="modalState.from.remark"
:auto-size="{ minRows: 2, maxRows: 6 }"
:maxlength="400"
:show-count="true"
/>
</a-form-item>
</a-form>
</ProModal>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -1,15 +1,17 @@
<script setup lang="ts">
import { reactive, toRaw, watch } from 'vue';
import { Form, Modal, Upload, message } from 'ant-design-vue/lib';
import { Form, Modal, Upload, message, notification } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import { FileType } from 'ant-design-vue/lib/upload/interface';
import { FileType, UploadFile } from 'ant-design-vue/lib/upload/interface';
import {
exportSet,
importFile,
listServerFile,
} from '@/api/configManage/neManage';
exportNeConfigBackup,
importNeConfigBackup,
listNeConfigBackup,
} from '@/api/ne/neConfigBackup';
import saveAs from 'file-saver';
import { uploadFile } from '@/api/tool/file';
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
@@ -28,32 +30,49 @@ const props = defineProps({
},
});
/**表格所需option */
const neManageOption = reactive({
importType: [
{ label: t('views.ne.neInfo.backConf.server'), value: 'server' },
{ label: t('views.ne.neInfo.backConf.local'), value: 'local' },
/**导入状态数据 */
const importState = reactive({
typeOption: [
{ label: t('views.ne.neInfo.backConf.server'), value: 'backup' },
{ label: t('views.ne.neInfo.backConf.local'), value: 'upload' },
],
serverFileName: <any[]>[],
backupData: <any[]>[],
});
/**查询网元远程服务器备份文件 */
function typeChange(value: any) {
if (value === 'server') {
modalState.from.fileName = undefined;
listServerFile(modalState.from).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
neManageOption.serverFileName = [];
res.data.forEach((item: any) => {
neManageOption.serverFileName.push({
label: item.fileName,
value: item.fileName,
});
function backupSearch(name?: string) {
const { neType, neId } = modalState.from;
listNeConfigBackup({
neType,
neId,
name,
pageNum: 1,
pageSize: 20,
}).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
importState.backupData = [];
res.rows.forEach((item: any) => {
importState.backupData.push({
label: item.name,
value: item.path,
});
}
});
} else {
modalState.from.file = null;
});
}
});
}
/**服务器备份文件选择切换 */
function backupChange(value: any) {
if (!value) {
backupSearch();
}
}
/**类型切换 */
function typeChange(value: any) {
modalState.from.path = undefined;
if (value === 'backup') {
backupSearch();
}
}
@@ -67,9 +86,8 @@ type ModalStateType = {
from: {
neType: string;
neId: string;
importType: 'local' | 'server';
file: File | null;
fileName: string | undefined;
type: 'upload' | 'backup';
path: string | undefined;
};
/**确定按钮 loading */
confirmLoading: boolean;
@@ -84,9 +102,8 @@ let modalState: ModalStateType = reactive({
from: {
neType: '',
neId: '',
importType: 'local',
file: null,
fileName: undefined,
type: 'upload',
path: undefined,
},
confirmLoading: false,
uploadFiles: [],
@@ -96,16 +113,10 @@ let modalState: ModalStateType = reactive({
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
file: [
path: [
{
required: true,
message: t('views.ne.neInfo.backConf.filePlease'),
},
],
fileName: [
{
required: true,
message: t('views.ne.neInfo.backConf.fileNamePlease'),
message: t('views.ne.neInfo.backConf.pathPlease'),
},
],
})
@@ -117,21 +128,13 @@ const modalStateFrom = Form.useForm(
*/
function fnModalOk() {
if (modalState.confirmLoading) return;
const from = toRaw(modalState.from);
let validateName = ['importType'];
if (from.importType === 'local') {
validateName.push('file');
} else {
validateName.push('fileName');
}
modalStateFrom
.validate(validateName)
.validate()
.then(e => {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
importFile(from)
importNeConfigBackup(from)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
@@ -168,6 +171,12 @@ function fnModalCancel() {
emit('update:visible', false);
}
/**表单上传前删除 */
function fnBeforeRemoveFile(file: UploadFile) {
modalState.from.path = undefined;
return true;
}
/**表单上传前检查或转换压缩 */
function fnBeforeUploadFile(file: FileType) {
if (modalState.confirmLoading) return false;
@@ -187,12 +196,30 @@ function fnBeforeUploadFile(file: FileType) {
/**表单上传文件 */
function fnUploadFile(up: UploadRequestOption) {
// 改为完成状态
const file = modalState.uploadFiles[0];
file.percent = 100;
file.status = 'done';
// 预置到表单
modalState.from.file = up.file as File;
// 发送请求
const hide = message.loading(t('common.loading'), 0);
modalState.confirmLoading = true;
let formData = new FormData();
formData.append('file', up.file);
formData.append('subPath', 'import');
uploadFile(formData)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 改为完成状态
const file = modalState.uploadFiles[0];
file.percent = 100;
file.status = 'done';
// 预置到表单
const { fileName } = res.data;
modalState.from.path = fileName;
} else {
message.error(res.msg, 3);
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
}
/**监听是否显示,初始数据 */
@@ -220,10 +247,17 @@ function fnExportConf(neType: string, neId: string) {
content: t('views.ne.neInfo.backConf.exportTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
exportSet({ neType, neId })
exportNeConfigBackup({ neType, neId })
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('views.ne.neInfo.backConf.exportMsg'), 3);
notification.success({
message: t('common.tipTitle'),
description: t('views.ne.neInfo.backConf.exportMsg'),
});
saveAs(
res.data,
`${neType}_${neId}_config_backup_${Date.now()}.zip`
);
} else {
message.error(`${res.msg}`, 3);
}
@@ -258,44 +292,44 @@ defineExpose({
<a-form name="modalStateFrom" layout="horizontal" :label-col="{ span: 6 }">
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.common.neType')"
name="neType"
v-bind="modalStateFrom.validateInfos.neType"
>
<a-form-item :label="t('views.ne.common.neType')" name="neType">
{{ modalState.from.neType }}
</a-form-item>
<a-form-item
:label="t('views.ne.neInfo.backConf.importType')"
name="importType"
name="type"
>
<a-select
v-model:value="modalState.from.importType"
v-model:value="modalState.from.type"
default-value="server"
:options="neManageOption.importType"
:options="importState.typeOption"
@change="typeChange"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.common.neId')"
name="neId"
v-bind="modalStateFrom.validateInfos.neId"
>
<a-form-item :label="t('views.ne.common.neId')" name="neId">
{{ modalState.from.neId }}
</a-form-item>
<a-form-item
:label="t('views.ne.neInfo.backConf.server')"
name="fileName"
v-bind="modalStateFrom.validateInfos.fileName"
v-if="modalState.from.importType === 'server'"
v-bind="modalStateFrom.validateInfos.path"
v-if="modalState.from.type === 'backup'"
>
<a-select
v-model:value="modalState.from.fileName"
:options="neManageOption.serverFileName"
v-model:value="modalState.from.path"
:options="importState.backupData"
:placeholder="t('common.selectPlease')"
:show-search="true"
:default-active-first-option="false"
:show-arrow="false"
:allow-clear="true"
:filter-option="false"
:not-found-content="null"
@search="backupSearch"
@change="backupChange"
>
</a-select>
</a-form-item>
@@ -303,8 +337,8 @@ defineExpose({
<a-form-item
:label="t('views.ne.neInfo.backConf.local')"
name="file"
v-bind="modalStateFrom.validateInfos.file"
v-if="modalState.from.importType === 'local'"
v-bind="modalStateFrom.validateInfos.path"
v-if="modalState.from.type === 'upload'"
>
<a-upload
name="file"
@@ -314,9 +348,10 @@ defineExpose({
:max-count="1"
:show-upload-list="{
showPreviewIcon: false,
showRemoveIcon: false,
showRemoveIcon: true,
showDownloadIcon: false,
}"
:remove="fnBeforeRemoveFile"
:before-upload="fnBeforeUploadFile"
:custom-request="fnUploadFile"
:disabled="modalState.confirmLoading"

View File

@@ -124,8 +124,8 @@ let modalState: ModalStateType = reactive({
addr: '',
port: 22,
user: 'omcuser',
authMode: '2',
password: '',
authMode: '0',
password: 'a9tU53r',
privateKey: '',
passPhrase: '',
remark: '',

View File

@@ -233,8 +233,10 @@ function fnModalEditOk(from: Record<string, any>) {
if (item && res.code === RESULT_CODE_SUCCESS) {
item.neType = from.neType;
item.neId = from.neId;
item.ip = from.ip;
item.rmUid = from.rmUid;
item.neName = from.neName;
item.ip = from.ip;
item.port = from.port;
item.status = res.data.online ? '1' : '0';
Object.assign(item.serverState, res.data);
const resouresUsage = parseResouresUsage(item.serverState);
@@ -281,10 +283,10 @@ function fnRecordDelete(id: string) {
message.success(t('common.operateOk'), 3);
// 过滤掉删除的id
tableState.data = tableState.data.filter(item => {
if (id.indexOf(',')) {
if (id.indexOf(',') > -1) {
return !tableState.selectedRowKeys.includes(item.id);
} else {
return item.id != id;
return item.id !== id;
}
});
// 刷新缓存
@@ -556,7 +558,7 @@ onMounted(() => {
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 150 }"
:scroll="{ x: tableColumns.length * 120 }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',

View File

@@ -157,11 +157,7 @@ type StateType = {
optionType: 'upload' | 'option';
/**文件上传 */
visibleByFile: boolean;
/**
* 依赖包类型
* IMS-rtproxy/mf/adb
* UDM-adb
*/
/**网元拓展包列表类型 */
depType: string[];
/**软件包信息数据 */
from: {

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { reactive, onMounted, watch } from 'vue';
import { ref, watch } from 'vue';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const emit = defineEmits(['update:data']);
@@ -21,109 +21,66 @@ const props = defineProps({
},
});
/**对话框对象信息状态类型 */
type StateType = {
/**表单数据 */
from: Record<string, any>;
/**根据网元显示配置项 */
hasNE: {
amf: boolean;
upf: boolean;
ims: boolean;
mme: boolean;
};
};
/**对话框对象信息状态 */
let state: StateType = reactive({
from: {
basic: {
plmnId: {
mcc: '001',
mnc: '01',
},
tac: '4388',
snssai: {
sst: '1',
sd: '000001',
},
dnn_data: 'internet',
dnn_ims: 'ims',
/**表单信息状态 */
let fromState = ref({
basic: {
plmnId: {
mcc: '001',
mnc: '01',
},
external: {
amfn2_ip: '192.168.8.120',
upfn3_ip: '192.168.8.190/24',
upfn3_gw: '192.168.1.1',
upfn6_ip: '192.168.8.191/24',
upfn6_gw: '192.168.1.1',
ue_pool: '10.2.1.0/24',
//
mmes1_ip: '192.168.8.220/20',
mmes10_ip: '172.16.5.221/24',
mmes11_ip: '172.16.5.220/24',
ims_sip_ip: '192.168.8.110',
upf_type: 'LightUPF',
upfn3_pci: '0000:00:00.0',
upfn3_mac: '00:00:00:00:00:00',
upfn6_pci: '0000:00:00.0',
upfn6_mac: '00:00:00:00:00:00',
},
sbi: {
omc_ip: '172.16.5.100',
ims_ip: '172.16.5.110',
amf_ip: '172.16.5.120',
ausf_ip: '172.16.5.130',
udm_ip: '172.16.5.140',
adb_ip: '0.0.0.0',
smf_ip: '172.16.5.150',
pcf_ip: '172.16.5.160',
nssf_ip: '172.16.5.170',
nrf_ip: '172.16.5.180',
upf_ip: '172.16.5.190',
lmf_ip: '172.16.5.200',
nef_ip: '172.16.5.210',
mme_ip: '172.16.5.220',
n3iwf_ip: '172.16.5.230',
tac: '4388',
snssai: {
sst: '1',
sd: '000001',
},
dnn_data: 'internet',
dnn_ims: 'ims',
},
hasNE: {
amf: false,
upf: false,
ims: false,
mme: false,
external: {
amfn2_ip: '192.168.8.120',
upfn3_ip: '192.168.8.190/24',
upfn3_gw: '192.168.1.1',
upfn6_ip: '192.168.8.191/24',
upfn6_gw: '192.168.1.1',
ue_pool: '10.2.1.0/24',
//
mmes1_ip: '192.168.8.220/20',
mmes10_ip: '172.16.5.221/24',
mmes11_ip: '172.16.5.220/24',
ims_sip_ip: '192.168.8.110',
upf_type: 'LightUPF',
upfn3_pci: '0000:00:00.0',
upfn3_mac: '00:00:00:00:00:00',
upfn6_pci: '0000:00:00.0',
upfn6_mac: '00:00:00:00:00:00',
},
sbi: {
omc_ip: '172.16.5.100',
ims_ip: '172.16.5.110',
amf_ip: '172.16.5.120',
ausf_ip: '172.16.5.130',
udm_ip: '172.16.5.140',
db_ip: '0.0.0.0',
smf_ip: '172.16.5.150',
pcf_ip: '172.16.5.160',
nssf_ip: '172.16.5.170',
nrf_ip: '172.16.5.180',
upf_ip: '172.16.5.190',
lmf_ip: '172.16.5.200',
nef_ip: '172.16.5.210',
mme_ip: '172.16.5.220',
n3iwf_ip: '172.16.5.230',
},
});
/**监听数据 */
watch(
() => props.data,
val => {
if (val) {
Object.assign(state.from, val);
}
},
{ deep: true }
);
watch(
() => props.ne,
val => {
if (val) {
Object.assign(state.hasNE, val);
}
},
{ deep: true }
);
watch(
() => state.from,
() => fromState,
val => {
if (val) emit('update:data', val);
},
{ deep: true, immediate: true }
);
onMounted(() => {});
</script>
<template>
@@ -140,7 +97,7 @@ onMounted(() => {});
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="DNN_DATA" name="basic.dnn_data">
<a-input
v-model:value="state.from.basic.dnn_data"
v-model:value="fromState.basic.dnn_data"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -155,13 +112,13 @@ onMounted(() => {});
</a-form-item>
<a-form-item label="MCC" name="basic.plmnId.mcc">
<a-input
v-model:value="state.from.basic.plmnId.mcc"
v-model:value="fromState.basic.plmnId.mcc"
placeholder="1-65535"
></a-input>
</a-form-item>
<a-form-item label="SST" name="basic.snssai.sst">
<a-input-number
v-model:value="state.from.basic.snssai.sst"
v-model:value="fromState.basic.snssai.sst"
:min="1"
:max="3"
placeholder="1-3"
@@ -177,7 +134,7 @@ onMounted(() => {});
</a-form-item>
<a-form-item label="TAC" name="basic.tac">
<a-input
v-model:value="state.from.basic.tac"
v-model:value="fromState.basic.tac"
placeholder="1-65535"
></a-input>
</a-form-item>
@@ -185,7 +142,7 @@ onMounted(() => {});
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="DNN_IMS" name="basic.dnn_ims">
<a-input
v-model:value="state.from.basic.dnn_ims"
v-model:value="fromState.basic.dnn_ims"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -194,13 +151,13 @@ onMounted(() => {});
</a-form-item>
<a-form-item label="MNC" name="basic.plmnId.mnc">
<a-input
v-model:value="state.from.basic.plmnId.mnc"
v-model:value="fromState.basic.plmnId.mnc"
placeholder="1-65535"
></a-input>
</a-form-item>
<a-form-item label="SD" name="basic.snssai.sd">
<a-input
v-model:value="state.from.basic.snssai.sd"
v-model:value="fromState.basic.snssai.sd"
placeholder="1-65535"
></a-input>
</a-form-item>
@@ -220,7 +177,7 @@ onMounted(() => {});
:validateTrigger="[]"
>
<a-input
v-model:value="state.from.sbi.omc_ip"
v-model:value="fromState.sbi.omc_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -237,14 +194,13 @@ onMounted(() => {});
</a-form-item>
</a-col>
</a-row>
<template v-if="state.hasNE.amf">
<template v-if="props.ne.amf">
<a-divider orientation="left">AMF</a-divider>
<a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="N2_IP" name="external.amfn2_ip">
<a-input
v-model:value="state.from.external.amfn2_ip"
v-model:value="fromState.external.amfn2_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -262,7 +218,7 @@ onMounted(() => {});
</template>
</a-col>
<a-col :lg="16" :md="16" :xs="24" v-if="state.hasNE.upf">
<a-col :lg="16" :md="16" :xs="24" v-if="props.ne.upf">
<a-divider orientation="left">UPF</a-divider>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
@@ -272,7 +228,7 @@ onMounted(() => {});
help="Install of Standard or Light"
>
<a-select
v-model:value="state.from.external.upf_type"
v-model:value="fromState.external.upf_type"
:placeholder="t('common.selectPlease')"
>
<a-select-option value="StandardUPF">
@@ -285,7 +241,7 @@ onMounted(() => {});
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="UE_POOL" name="external.ue_pool">
<a-input
v-model:value="state.from.external.ue_pool"
v-model:value="fromState.external.ue_pool"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -305,7 +261,7 @@ onMounted(() => {});
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N3_IP" name="external.upfn3_ip">
<a-input
v-model:value="state.from.external.upfn3_ip"
v-model:value="fromState.external.upfn3_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -320,7 +276,7 @@ onMounted(() => {});
</a-form-item>
<a-form-item label="N3_GW" name="external.upfn3_gw">
<a-input
v-model:value="state.from.external.upfn3_gw"
v-model:value="fromState.external.upfn3_gw"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -338,11 +294,11 @@ onMounted(() => {});
:lg="12"
:md="12"
:xs="24"
v-if="state.from.external.upf_type === 'StandardUPF'"
v-if="fromState.external.upf_type === 'StandardUPF'"
>
<a-form-item label="N3_PCI" name="external.upfn3_pci">
<a-input
v-model:value="state.from.external.upfn3_pci"
v-model:value="fromState.external.upfn3_pci"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -359,7 +315,7 @@ onMounted(() => {});
</a-form-item>
<a-form-item label="N3_MAC" name="external.upfn3_mac">
<a-input
v-model:value="state.from.external.upfn3_mac"
v-model:value="fromState.external.upfn3_mac"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -376,12 +332,12 @@ onMounted(() => {});
</a-form-item>
</a-col>
</a-row>
<template v-if="state.from.external.upf_type === 'StandardUPF'">
<template v-if="fromState.external.upf_type === 'StandardUPF'">
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N6_IP" name="external.upfn6_ip">
<a-input
v-model:value="state.from.external.upfn6_ip"
v-model:value="fromState.external.upfn6_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -390,7 +346,7 @@ onMounted(() => {});
</a-form-item>
<a-form-item label="N6_GW" name="external.upfn6_gw">
<a-input
v-model:value="state.from.external.upfn6_gw"
v-model:value="fromState.external.upfn6_gw"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -401,7 +357,7 @@ onMounted(() => {});
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N6_PCI" name="external.upfn6_pci">
<a-input
v-model:value="state.from.external.upfn6_pci"
v-model:value="fromState.external.upfn6_pci"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -410,7 +366,7 @@ onMounted(() => {});
</a-form-item>
<a-form-item label="N6_MAC" name="external.upfn6_mac">
<a-input
v-model:value="state.from.external.upfn6_mac"
v-model:value="fromState.external.upfn6_mac"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -423,13 +379,13 @@ onMounted(() => {});
</a-col>
<a-col :lg="8" :md="8" :xs="24">
<template v-if="state.hasNE.ims">
<template v-if="props.ne.ims">
<a-divider orientation="left">IMS</a-divider>
<a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="SIP_IP" name="external.ims_sip_ip">
<a-input
v-model:value="state.from.external.ims_sip_ip"
v-model:value="fromState.external.ims_sip_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -446,13 +402,13 @@ onMounted(() => {});
</a-row>
</template>
<template v-if="state.hasNE.mme">
<template v-if="props.ne.mme">
<a-divider orientation="left">MME</a-divider>
<a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="S1_IP" name="external.mmes1_ip">
<a-input
v-model:value="state.from.external.mmes1_ip"
v-model:value="fromState.external.mmes1_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -471,7 +427,7 @@ onMounted(() => {});
</a-form-item>
<a-form-item label="S10_IP" name="external.mmes10_ip">
<a-input
v-model:value="state.from.external.mmes10_ip"
v-model:value="fromState.external.mmes10_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
@@ -486,7 +442,7 @@ onMounted(() => {});
</a-form-item>
<a-form-item label="S11_IP" name="external.mmes11_ip">
<a-input
v-model:value="state.from.external.mmes11_ip"
v-model:value="fromState.external.mmes11_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"

View File

@@ -231,6 +231,17 @@ function fnHostAuthorized() {
});
}
/**返回上一步 */
function fnStepPrev() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neQuickSetup.stepPrevTip'),
onOk() {
fnToStepName('Para5G');
},
});
}
/**下一步操作 */
function fnStepNext() {
if (!state.stepNext) return;
@@ -254,207 +265,232 @@ onMounted(() => {
</script>
<template>
<a-descriptions :column="{ lg: 3, md: 2, sm: 2, xs: 1 }" bordered>
<a-descriptions-item :label="t('views.ne.neQuickSetup.addr')" :span="3">
{{ state.info.addr }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.ne.neQuickSetup.kernelName')">
{{ state.info.kernelName }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.ne.neQuickSetup.machine')">
{{ state.info.machine }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.ne.neQuickSetup.kernelRelease')">
{{ state.info.kernelRelease }}
</a-descriptions-item>
<a-descriptions-item>
<template #label>
{{ t('views.ne.neQuickSetup.prettyName') }}
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.ne.neQuickSetup.prettyNameTip') }}
<div class="ne">
<a-descriptions :column="{ lg: 3, md: 2, sm: 2, xs: 1 }" bordered>
<a-descriptions-item :label="t('views.ne.neQuickSetup.addr')" :span="3">
{{ state.info.addr }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.ne.neQuickSetup.kernelName')">
{{ state.info.kernelName }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.ne.neQuickSetup.machine')">
{{ state.info.machine }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.ne.neQuickSetup.kernelRelease')">
{{ state.info.kernelRelease }}
</a-descriptions-item>
<a-descriptions-item>
<template #label>
{{ t('views.ne.neQuickSetup.prettyName') }}
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.ne.neQuickSetup.prettyNameTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
{{ state.info.prettyName }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.ne.neQuickSetup.nodename')">
{{ state.info.nodename }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.ne.neQuickSetup.auth')">
<a-tag :color="state.info.sudo ? 'success' : 'error'">
<template #icon>
<CheckCircleOutlined v-if="state.info.sudo" />
<CloseCircleOutlined v-else />
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
{{ state.info.prettyName }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.ne.neQuickSetup.nodename')">
{{ state.info.nodename }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.ne.neQuickSetup.auth')">
<a-tag :color="state.info.sudo ? 'success' : 'error'">
<template #icon>
<CheckCircleOutlined v-if="state.info.sudo" />
<CloseCircleOutlined v-else />
</template>
{{ t('views.ne.neQuickSetup.sudo') }}
</a-tag>
{{ t('views.ne.neQuickSetup.sudo') }}
</a-tag>
<a-tag :color="state.info.sshLink ? 'success' : 'error'">
<template #icon>
<CheckCircleOutlined v-if="state.info.sshLink" />
<CloseCircleOutlined v-else />
</template>
{{ t('views.ne.neQuickSetup.sshLink') }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item :span="2">
<a-form
name="checkStateFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:label-wrap="false"
<a-tag :color="state.info.sshLink ? 'success' : 'error'">
<template #icon>
<CheckCircleOutlined v-if="state.info.sshLink" />
<CloseCircleOutlined v-else />
</template>
{{ t('views.ne.neQuickSetup.sshLink') }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
<a-form
name="checkStateFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:label-wrap="true"
style="margin-top: 20px; width: 68%"
>
<a-row :gutter="8">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neHost.addr')"
name="addr"
v-bind="checkStateFrom.validateInfos.addr"
>
<a-input
v-model:value="state.from.addr"
allow-clear
:maxlength="128"
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neHost.port')"
name="port"
v-bind="checkStateFrom.validateInfos.port"
>
<a-input-number
v-model:value="state.from.port"
:min="10"
:max="65535"
:step="1"
:maxlength="5"
style="width: 100%"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neHost.user')"
name="user"
v-bind="checkStateFrom.validateInfos.user"
>
<a-input
v-model:value="state.from.user"
allow-clear
:maxlength="32"
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.neHost.authMode')">
<a-select
v-model:value="state.from.authMode"
default-value="0"
:options="dict.neHostAuthMode"
>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item
v-if="state.from.authMode === '0'"
:label="t('views.ne.neHost.password')"
:label-col="{ span: 3 }"
:label-wrap="true"
name="password"
v-bind="checkStateFrom.validateInfos.password"
>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neHost.addr')"
name="addr"
v-bind="checkStateFrom.validateInfos.addr"
>
<a-input
v-model:value="state.from.addr"
allow-clear
:maxlength="128"
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neHost.port')"
name="port"
v-bind="checkStateFrom.validateInfos.port"
>
<a-input-number
v-model:value="state.from.port"
:min="10"
:max="65535"
:step="1"
:maxlength="5"
style="width: 100%"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neHost.user')"
name="user"
v-bind="checkStateFrom.validateInfos.user"
>
<a-input
v-model:value="state.from.user"
allow-clear
:maxlength="32"
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.neHost.authMode')">
<a-select
v-model:value="state.from.authMode"
default-value="0"
:options="dict.neHostAuthMode"
>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-input-password
v-model:value="state.from.password"
:maxlength="128"
:placeholder="t('common.inputPlease')"
>
</a-input-password>
</a-form-item>
<template v-if="state.from.authMode === '1'">
<a-form-item
v-if="state.from.authMode === '0'"
:label="t('views.ne.neHost.password')"
:label="t('views.ne.neHost.privateKey')"
:label-col="{ span: 3 }"
:label-wrap="true"
name="privateKey"
v-bind="checkStateFrom.validateInfos.privateKey"
>
<a-textarea
v-model:value="state.from.privateKey"
:auto-size="{ minRows: 4, maxRows: 6 }"
:maxlength="3000"
:show-count="true"
:placeholder="t('views.ne.neHost.privateKeyPlease')"
/>
</a-form-item>
<a-form-item
:label="t('views.ne.neHost.passPhrase')"
:label-col="{ span: 3 }"
:label-wrap="true"
name="password"
v-bind="checkStateFrom.validateInfos.password"
>
<a-input-password
v-model:value="state.from.password"
v-model:value="state.from.passPhrase"
:maxlength="128"
:placeholder="t('common.inputPlease')"
>
</a-input-password>
</a-form-item>
</template>
<template v-if="state.from.authMode === '1'">
<a-form-item
:label="t('views.ne.neHost.privateKey')"
:label-col="{ span: 3 }"
:label-wrap="true"
name="privateKey"
v-bind="checkStateFrom.validateInfos.privateKey"
<a-form-item :wrapper-col="{ span: 8, offset: 3 }">
<a-space direction="horizontal" :size="18">
<a-button
type="primary"
ghost
html-type="submit"
@click="fnCheckInfo()"
:loading="state.confirmLoading"
>
<a-textarea
v-model:value="state.from.privateKey"
:auto-size="{ minRows: 4, maxRows: 6 }"
:maxlength="3000"
:show-count="true"
:placeholder="t('views.ne.neHost.privateKeyPlease')"
/>
</a-form-item>
{{ t('views.ne.neHost.test') }}
</a-button>
<a-form-item
:label="t('views.ne.neHost.passPhrase')"
:label-col="{ span: 3 }"
:label-wrap="true"
<a-button
type="dashed"
@click="fnHostAuthorized()"
:disabled="state.confirmLoading"
v-if="state.from.authMode !== '2'"
>
<a-input-password
v-model:value="state.from.passPhrase"
:maxlength="128"
:placeholder="t('common.inputPlease')"
>
</a-input-password>
</a-form-item>
</template>
{{ t('views.ne.neHost.authRSA') }}
</a-button>
<a-form-item :wrapper-col="{ span: 10, offset: 3 }">
<a-space direction="horizontal" :size="18">
<a-button
type="primary"
ghost
html-type="submit"
@click="fnCheckInfo()"
:loading="state.confirmLoading"
>
{{ t('views.ne.neHost.test') }}
</a-button>
<a-button
type="primary"
@click="fnStepNext()"
:disabled="!state.stepNext"
>
{{ t('views.ne.neQuickSetup.stepNext') }}
</a-button>
<a-button
type="link"
@click="fnCheckInfoReset()"
:disabled="state.confirmLoading"
>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-form>
<a-button
type="dashed"
@click="fnHostAuthorized()"
:disabled="state.confirmLoading"
v-if="state.from.authMode !== '2'"
>
{{ t('views.ne.neHost.authRSA') }}
</a-button>
<div class="ne-oper">
<a-space direction="horizontal" :size="18">
<a-button @click="fnStepPrev()">
{{ t('views.ne.neQuickSetup.stepPrev') }}
</a-button>
<a-button
type="link"
@click="fnCheckInfoReset()"
:disabled="state.confirmLoading"
>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-form>
</a-descriptions-item>
</a-descriptions>
<a-button
type="primary"
@click="fnStepNext()"
:disabled="!state.stepNext"
>
{{ t('views.ne.neQuickSetup.stepNext') }}
</a-button>
</a-space>
</div>
</div>
</template>
<style lang="less" scoped></style>
<style lang="less" scoped>
.ne {
min-height: 400px;
display: flex;
flex-direction: column;
& .ant-form {
flex: 1;
}
&-oper {
text-align: end;
}
}
</style>

View File

@@ -0,0 +1,137 @@
import { reactive, toRaw } from 'vue';
import { getPara5GFilee, savePara5GFile, updateNeInfo } from '@/api/ne/neInfo';
import useNeInfoStore from '@/store/modules/neinfo';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
/**对象信息信息状态类型 */
type StateType = {
/**表单数据 */
from: Record<string, any>;
/**OMC信息需修改当前的IP */
omcInfo: Record<string, any>;
/**根据网元显示配置项 */
hasNE: {
amf: boolean;
upf: boolean;
ims: boolean;
mme: boolean;
};
/**确定按钮 loading */
confirmLoading: boolean;
};
export function usePara5G() {
/**对象信息状态 */
let state: StateType = reactive({
from: {},
omcInfo: {},
hasNE: {
amf: false,
upf: false,
ims: false,
mme: false,
},
confirmLoading: false,
});
/**载入数据*/
function fnReloadData() {
state.confirmLoading = true;
Promise.all([getPara5GFilee(), useNeInfoStore().fnRefreshNelist()]).then(
resArr => {
// 已保存的配置
if (resArr[0].code === RESULT_CODE_SUCCESS) {
Object.assign(state.from, resArr[0].data);
}
// 填充固定网元类型的ip
if (
resArr[1].code === RESULT_CODE_SUCCESS &&
Array.isArray(resArr[1].data)
) {
for (const item of resArr[1].data) {
// 公共配置文件sbi的各网元IP
switch (item.neType) {
case 'OMC':
state.from.sbi.omc_ip = item.ip;
Object.assign(state.omcInfo, item); // 主动改OMC_IP
break;
case 'IMS':
state.from.sbi.ims_ip = item.ip;
state.hasNE.ims = true;
break;
case 'AMF':
state.from.sbi.amf_ip = item.ip;
state.hasNE.amf = true;
break;
case 'AUSF':
state.from.sbi.ausf_ip = item.ip;
break;
case 'UDM':
state.from.sbi.udm_ip = item.ip;
state.from.sbi.db_ip = '0.0.0.0';
break;
case 'SMF':
state.from.sbi.smf_ip = item.ip;
break;
case 'PCF':
state.from.sbi.pcf_ip = item.ip;
break;
case 'NSSF':
state.from.sbi.nssf_ip = item.ip;
break;
case 'NRF':
state.from.sbi.nrf_ip = item.ip;
break;
case 'UPF':
state.from.sbi.upf_ip = item.ip;
state.hasNE.upf = true;
break;
case 'LMF':
state.from.sbi.lmf_ip = item.ip;
break;
case 'NEF':
state.from.sbi.nef_ip = item.ip;
break;
case 'MME':
state.from.sbi.mme_ip = item.ip;
if (item.ip.includes('.')) {
state.from.external.mmes11_ip = item.ip + '/24';
}
state.hasNE.mme = true;
break;
case 'N3IWF':
state.from.sbi.n3iwf_ip = item.ip;
break;
}
}
}
state.confirmLoading = false;
}
);
}
/**保存数据 */
async function fnSaveData() {
if (state.confirmLoading) return;
state.confirmLoading = true;
const res = await savePara5GFile({
content: toRaw(state.from),
syncNe: [],
});
if (res.code === RESULT_CODE_SUCCESS) {
// 更新omc_ip
if (state.omcInfo.id) {
state.omcInfo.ip = state.from.sbi.omc_ip;
await updateNeInfo(toRaw(state.omcInfo));
}
}
state.confirmLoading = false;
return res;
}
return {
state,
fnReloadData,
fnSaveData,
};
}

View File

@@ -1,4 +1,10 @@
import { reactive } from 'vue';
import {
defineAsyncComponent,
onMounted,
reactive,
shallowRef,
watch,
} from 'vue';
/**步骤信息状态类型 */
type StepStateType = {
@@ -16,7 +22,7 @@ type StepStateType = {
/**步骤信息状态 */
export const stepState: StepStateType = reactive({
stepName: 'Start',
stepName: 'Para5G',
steps: [
{
title: '服务器环境',
@@ -35,15 +41,15 @@ export const stepState: StepStateType = reactive({
description: '网元服务授权激活',
},
],
current: 0,
current: -1,
neHost: {},
neInfo: {},
});
/**步骤信息状态复位 */
export function fnRestStepState(t?: any) {
stepState.stepName = 'Start';
stepState.current = 0;
stepState.stepName = 'Para5G';
stepState.current = -1;
stepState.neHost = {};
stepState.neInfo = {};
// 多语言翻译
@@ -80,3 +86,26 @@ export function fnToStepName(stepName: string) {
stepState.stepName = stepName;
}
export function useStep({ t }: any) {
// 异步加载组件
const Start = defineAsyncComponent(() => import('../components/Start.vue'));
// 当前组件
const currentComponent = shallowRef(Start);
watch(
() => stepState.stepName,
v => {
const loadComponent = defineAsyncComponent(
() => import(`../components/${v}.vue`)
);
currentComponent.value = loadComponent;
}
);
onMounted(() => {
fnRestStepState(t);
});
return { currentComponent };
}

View File

@@ -1,34 +1,71 @@
<script lang="ts" setup>
import { PageContainer } from 'antdv-pro-layout';
import { defineAsyncComponent, watch, shallowRef, onMounted } from 'vue';
import { stepState, fnRestStepState } from './hooks/useStep';
import Para5GForm from './components/Para5GForm.vue';
import {
stepState,
fnToStepName,
fnRestStepState,
useStep,
} from './hooks/useStep';
import { usePara5G } from './hooks/usePara5G';
import useI18n from '@/hooks/useI18n';
import { onMounted, onUnmounted, watch } from 'vue';
const { t } = useI18n();
// 异步加载组件
const Start = defineAsyncComponent(() => import('./components/Start.vue'));
// 当前组件
const currentComponent = shallowRef(Start);
const { currentComponent } = useStep(t);
const { state, fnReloadData, fnSaveData } = usePara5G();
watch(
() => stepState.stepName,
v => {
const loadComponent = defineAsyncComponent(
() => import(`./components/${v}.vue`)
);
currentComponent.value = loadComponent;
if (v === 'Para5G') {
fnReloadData();
}
}
);
onMounted(() => {
fnReloadData();
});
onUnmounted(() => {
fnRestStepState(t);
});
/**公共参数保存前下一步进行网元安装 */
function fnNext() {
fnSaveData().then(() => {
fnToStepName('Start');
});
}
</script>
<template>
<PageContainer>
<a-card :bordered="false">
<a-card :bordered="false" v-if="stepState.stepName === 'Para5G'">
<!-- 公共参数表单 -->
<Para5GForm v-model:data="state.from" :ne="state.hasNE"></Para5GForm>
<div style="padding: 24px 12px 0; text-align: end">
<a-space :size="8" align="center">
<a-button
type="primary"
:loading="state.confirmLoading"
@click="fnNext()"
>
{{ t('views.ne.neQuickSetup.stepNext') }}
</a-button>
<a-button
type="default"
:disabled="state.confirmLoading"
@click.prevent="fnReloadData()"
>
<template #icon><ReloadOutlined /></template>
{{ t('views.ne.neQuickSetup.reloadPara5G') }}
</a-button>
</a-space>
</div>
</a-card>
<a-card :bordered="false" v-else>
<!-- 插槽-卡片左侧 -->
<template #title>
<!-- 步骤进度 -->

View File

@@ -91,6 +91,9 @@ async function fnModalOk() {
if (expandFile['ADB']) {
pkgArr.push(expandFile['ADB']);
}
if (expandFile['KVDB']) {
pkgArr.push(expandFile['KVDB']);
}
if (expandFile['RTPROXY']) {
pkgArr.push(expandFile['RTPROXY']);
}
@@ -104,6 +107,8 @@ async function fnModalOk() {
const udm = from.find(s => s.neType === 'UDM');
if (udm && expandFile['ADB']) {
udm.path = [expandFile['ADB'], udm.path].join(',');
} else if (udm && expandFile['KVDB']) {
udm.path = [expandFile['KVDB'], udm.path].join(',');
}
// 安装带依赖包-指定网元时

View File

@@ -530,7 +530,7 @@ function fnRecordDelete(imsi: string) {
}
/**
* UDM鉴权用户导出
* UDM鉴权用户勾选导出
*/
function fnRecordExport(type: string = 'txt') {
const selectLen = tableState.selectedRowKeys.length;
@@ -543,13 +543,15 @@ function fnRecordExport(type: string = 'txt') {
let content = '';
if (type == 'txt') {
for (const row of rows) {
content += `${row.imsi},${row.ki},${row.algoIndex},${row.amf},${row.opc}\r\n`;
const opc = row.opc === '-' ? '' : `,${row.opc}`;
content += `${row.imsi},${row.ki},${row.algoIndex},${row.amf}${opc}\r\n`;
}
}
if (type == 'csv') {
content = `IMSI,ki,Algo Index,AMF,OPC\r\n`;
for (const row of rows) {
content += `${row.imsi},${row.ki},${row.algoIndex},${row.amf},${row.opc}\r\n`;
const opc = row.opc === '-' ? '' : `,${row.opc}`;
content += `${row.imsi},${row.ki},${row.algoIndex},${row.amf}${opc}\r\n`;
}
}
@@ -557,7 +559,7 @@ function fnRecordExport(type: string = 'txt') {
saveAs(blob, `UDMAuth_${Date.now()}.${type}`);
}
/**列表导出 */
/**列表导出全部数据 */
function fnExportList(type: string) {
const neId = queryParams.neId;
if (!neId) return;
@@ -651,6 +653,10 @@ type ModalUploadImportStateType = {
loading: boolean;
/**上传结果信息 */
msg: string;
/**导入类型 */
typeOptions: { label: string; value: string }[];
/**表单 */
from: { typeVal: string; typeData: any };
};
/**对话框表格信息导入对象信息状态 */
@@ -659,11 +665,27 @@ let uploadImportState: ModalUploadImportStateType = reactive({
title: t('components.UploadModal.uploadTitle'),
loading: false,
msg: '',
typeOptions: [
{ label: 'Default', value: 'default' },
{ label: 'K4', value: 'k4' },
],
from: {
typeVal: 'default',
typeData: undefined,
},
});
/**对话框表格信息导入类型选择 */
function fnModalUploadImportTypeChange() {
uploadImportState.from.typeData = '';
uploadImportState.msg = '';
}
/**对话框表格信息导入弹出窗口 */
function fnModalUploadImportOpen() {
uploadImportState.msg = '';
uploadImportState.from.typeVal = 'default';
uploadImportState.from.typeData = undefined;
uploadImportState.loading = false;
uploadImportState.visible = true;
}
@@ -702,6 +724,7 @@ function fnModalUploadImportUpload(file: File) {
return importUDMAuth({
neId: neId,
uploadPath: filePath,
...uploadImportState.from,
});
})
.then(res => {
@@ -770,6 +793,7 @@ onMounted(() => {
<a-input
v-model:value="queryParams.imsi"
allow-clear
:maxlength="15"
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
@@ -1026,6 +1050,7 @@ onMounted(() => {
<a-input
v-model:value="modalState.from.imsi"
allow-clear
:maxlength="15"
:disabled="!!modalState.from.id"
>
<template #prefix>
@@ -1101,54 +1126,52 @@ onMounted(() => {
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="KI"
name="ki"
v-bind="modalStateFrom.validateInfos.ki"
>
<a-input
v-model:value="modalState.from.ki"
allow-clear
:maxlength="32"
:disabled="!!modalState.from.id"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.kiTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
<a-form-item
label="KI"
name="ki"
v-bind="modalStateFrom.validateInfos.ki"
:label-col="{ span: 3 }"
:labelWrap="true"
>
<a-input
v-model:value="modalState.from.ki"
allow-clear
:maxlength="32"
:disabled="!!modalState.from.id"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.kiTip') }}
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="OPC"
name="opc"
v-bind="modalStateFrom.validateInfos.opc"
>
<a-input
v-model:value="modalState.from.opc"
allow-clear
:maxlength="32"
:disabled="!!modalState.from.id"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.opcTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-form-item
label="OPC"
name="opc"
v-bind="modalStateFrom.validateInfos.opc"
:label-col="{ span: 3 }"
:labelWrap="true"
>
<a-input
v-model:value="modalState.from.opc"
allow-clear
:maxlength="32"
:disabled="!!modalState.from.id"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.opcTip') }}
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-form>
</ProModal>
@@ -1196,7 +1219,11 @@ onMounted(() => {
name="imsi"
v-bind="modalStateBatchFrom.validateInfos.imsi"
>
<a-input v-model:value="modalState.BatchForm.imsi" allow-clear>
<a-input
v-model:value="modalState.BatchForm.imsi"
allow-clear
:maxlength="15"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
@@ -1270,52 +1297,50 @@ onMounted(() => {
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="KI"
name="ki"
v-bind="modalStateBatchFrom.validateInfos.ki"
>
<a-input
v-model:value="modalState.BatchForm.ki"
allow-clear
:maxlength="32"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.kiTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
<a-form-item
label="KI"
name="ki"
v-bind="modalStateBatchFrom.validateInfos.ki"
:label-col="{ span: 3 }"
:labelWrap="true"
>
<a-input
v-model:value="modalState.BatchForm.ki"
allow-clear
:maxlength="32"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.kiTip') }}
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="OPC"
name="opc"
v-bind="modalStateBatchFrom.validateInfos.opc"
>
<a-input
v-model:value="modalState.BatchForm.opc"
allow-clear
:maxlength="32"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.opcTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-form-item
label="OPC"
name="opc"
v-bind="modalStateBatchFrom.validateInfos.opc"
:label-col="{ span: 3 }"
:labelWrap="true"
>
<a-input
v-model:value="modalState.BatchForm.opc"
allow-clear
:maxlength="32"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.opcTip') }}
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-form>
</ProModal>
@@ -1344,7 +1369,11 @@ onMounted(() => {
name="imsi"
v-bind="modalStateBatchDelFrom.validateInfos.imsi"
>
<a-input v-model:value="modalState.BatchDelForm.imsi" allow-clear>
<a-input
v-model:value="modalState.BatchDelForm.imsi"
allow-clear
:maxlength="15"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
@@ -1389,6 +1418,16 @@ onMounted(() => {
:size="10"
>
<template #default>
<a-radio-group
v-model:value="uploadImportState.from.typeVal"
:options="uploadImportState.typeOptions"
@change="fnModalUploadImportTypeChange"
/>
<a-input-password
v-if="uploadImportState.from.typeVal === 'k4'"
v-model:value="uploadImportState.from.typeData"
:placeholder="t('common.inputPlease')"
/>
<a-textarea
:disabled="true"
:hidden="!uploadImportState.msg"

View File

@@ -223,7 +223,7 @@ onMounted(() => {
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="IMSI" name="imsi">
<a-input v-model:value="queryParams.imsi" allow-clear></a-input>
<a-input v-model:value="queryParams.imsi" allow-clear :maxlength="15"></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">

View File

@@ -8,7 +8,6 @@ import { ColumnsType } from 'ant-design-vue/lib/table';
import UploadModal from '@/components/UploadModal/index.vue';
import TableColumnsDnd from '@/components/TableColumnsDnd/index.vue';
import useNeInfoStore from '@/store/modules/neinfo';
import useDictStore from '@/store/modules/dict';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import saveAs from 'file-saver';
@@ -26,15 +25,6 @@ import {
} from '@/api/neData/udm_sub';
import { uploadFile } from '@/api/tool/file';
const { t } = useI18n();
const { getDict } = useDictStore();
/**字典数据 */
let dict: {
/**CN Type可选类型 */
udmSubCNType: DictType[];
} = reactive({
udmSubCNType: [],
});
/**网元参数 */
let neOtions = ref<Record<string, any>[]>([]);
@@ -147,15 +137,16 @@ let tableColumns = ref<ColumnsType>([
width: 100,
},
{
title: 'CN Type',
title: '5G',
dataIndex: 'cn',
key: 'cn',
key: 'cnFlag',
align: 'center',
width: 100,
},
{
title: 'EPS',
title: '4G',
dataIndex: 'epsFlag',
key: 'epsFlag',
align: 'center',
width: 100,
},
@@ -1200,13 +1191,6 @@ function delBigRow(bigIndex: any) {
}
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('udm_sub_cn_type')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.udmSubCNType = resArr[0].value;
}
});
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
@@ -1262,6 +1246,7 @@ onMounted(() => {
<a-input
v-model:value="queryParams.imsi"
allow-clear
:maxlength="15"
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
@@ -1271,6 +1256,7 @@ onMounted(() => {
<a-input
v-model:value="queryParams.msisdn"
allow-clear
:maxlength="32"
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
@@ -1474,8 +1460,19 @@ onMounted(() => {
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'cn'">
<DictTag :options="dict.udmSubCNType" :value="record.cn" />
<template v-if="column.key === 'cnFlag'">
{{
record.cn === '3'
? t('views.neUser.sub.enable')
: t('views.neUser.sub.disable')
}}
</template>
<template v-if="column.key === 'epsFlag'">
{{
record.epsFlag === '1'
? t('views.neUser.sub.enable')
: t('views.neUser.sub.disable')
}}
</template>
<template v-if="column.key === 'imsi'">
<a-space :size="8" align="center">
@@ -1535,7 +1532,12 @@ onMounted(() => {
name="imsi"
v-bind="modalStateFrom.validateInfos.imsi"
>
<a-input v-model:value="modalState.from.imsi" allow-clear>
<a-input
v-model:value="modalState.from.imsi"
allow-clear
:maxlength="15"
:disabled="!!modalState.from.id"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
@@ -1549,13 +1551,6 @@ onMounted(() => {
</template>
</a-input>
</a-form-item>
<a-form-item label="CN Type" name="cn">
<a-select
v-model:value="modalState.from.cn"
:options="dict.udmSubCNType"
:placeholder="t('common.selectPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
@@ -1694,6 +1689,22 @@ onMounted(() => {
<a-divider orientation="left" style="margin: -2px">5G</a-divider>
</template>
<a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24">
<a-form-item
label="5GC Flag"
name="cnFlag"
:help="t('views.neUser.sub.cnFlag')"
>
<a-select v-model:value="modalState.from.cn">
<a-select-option value="3">
{{ t('views.neUser.sub.enable') }}
</a-select-option>
<a-select-option value="0">
{{ t('views.neUser.sub.disable') }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item
label="5G Subscribed UE AMBR Template"
@@ -2096,13 +2107,6 @@ onMounted(() => {
</template>
</a-input>
</a-form-item>
<a-form-item label="CN Type" name="cn">
<a-select
v-model:value="modalState.BatchForm.cn"
:options="dict.udmSubCNType"
:placeholder="t('common.selectPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
@@ -2242,6 +2246,22 @@ onMounted(() => {
</template>
<a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24">
<a-form-item
label="5GC Flag"
name="cnFlag"
:help="t('views.neUser.sub.cnFlag')"
>
<a-select v-model:value="modalState.BatchForm.cn">
<a-select-option value="3">
{{ t('views.neUser.sub.enable') }}
</a-select-option>
<a-select-option value="0">
{{ t('views.neUser.sub.disable') }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item
label="5G Subscribed UE AMBR Template"
@@ -2616,7 +2636,11 @@ onMounted(() => {
name="imsi"
v-bind="modalStateBatchDelFrom.validateInfos.imsi"
>
<a-input v-model:value="modalState.BatchDelForm.imsi" allow-clear>
<a-input
v-model:value="modalState.BatchDelForm.imsi"
allow-clear
:maxlength="15"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>

View File

@@ -254,15 +254,7 @@ function fnGetListTitle() {
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
tableColumns.value = [];
const columns: ColumnsType = [
{
title: t('views.perfManage.perfData.neName'),
dataIndex: 'neName',
key: 'neName',
align: 'left',
width: 100,
},
];
const columns: ColumnsType = [];
for (const item of res.data) {
const kpiDisplay = item[`${language}Title`];
const kpiValue = item[`kpiId`];
@@ -277,6 +269,13 @@ function fnGetListTitle() {
maxWidth: 300,
});
}
columns.push({
title: t('views.perfManage.perfData.neName'),
dataIndex: 'neName',
key: 'neName',
align: 'left',
width: 100,
});
columns.push({
title: t('views.perfManage.goldTarget.time'),
dataIndex: 'timeGroup',
@@ -458,7 +457,7 @@ function fnRanderChartData() {
}
for (const item of orgData) {
chartDataXAxisData.push(item['timeGroup']);
chartDataXAxisData.push(parseDateToStr(+item['timeGroup']));
const keys = Object.keys(item);
for (const y of chartDataYSeriesData) {
for (const key of keys) {
@@ -505,21 +504,23 @@ function fnLegendSelected(bool: any) {
/**图表实时统计 */
function fnRealTimeSwitch(bool: any) {
if (bool) {
tableState.seached = false;
// 建立链接
const options: OptionsType = {
url: '/ws',
params: {
/**订阅通道组
*
* 指标(GroupID:10)
* 指标(GroupID:10_neType_neId)
*/
subGroupID: '10',
subGroupID: `10_${queryParams.neType}_${queryParams.neId}`,
},
onmessage: wsMessage,
onerror: wsError,
};
ws.connect(options);
} else {
tableState.seached = true;
ws.close();
}
}
@@ -543,39 +544,40 @@ function wsMessage(res: Record<string, any>) {
return;
}
// kpiEvent 黄金指标指标事件
if (data.groupId === '10') {
const kpiEvent = data.data;
// 非对应网元类型
if (kpiEvent.neType !== queryParams.neType) return;
const kpiEvent = data.data;
tableState.data.unshift(kpiEvent);
tablePagination.total++;
for (const key of Object.keys(data.data)) {
const v = kpiEvent[key];
// x轴
if (key === 'timeGroup') {
// chartDataXAxisData.shift();
chartDataXAxisData.push(v);
continue;
}
// y轴
const yItem = chartDataYSeriesData.find(item => item.key === key);
if (yItem) {
// yItem.data.shift();
yItem.data.push(v);
}
// 非对应网元类型
if (kpiEvent.neType !== queryParams.neType) return;
for (const key of Object.keys(data.data)) {
const v = kpiEvent[key];
// x轴
if (key === 'timeGroup') {
// chartDataXAxisData.shift();
chartDataXAxisData.push(parseDateToStr(+v));
continue;
}
// y
const yItem = chartDataYSeriesData.find(item => item.key === key);
if (yItem) {
// yItem.data.shift();
yItem.data.push(+v);
}
// 绘制图数据
kpiChart.value.setOption({
xAxis: {
data: chartDataXAxisData,
},
series: chartDataYSeriesData,
});
}
// 绘制图数据
kpiChart.value.setOption({
xAxis: {
data: chartDataXAxisData,
},
series: chartDataYSeriesData,
});
}
onMounted(() => {
// 目前支持的 AMF AUSF MME MOCNGW NSSF SMF UDM UPF
// 目前支持的 AMF AUSF MME MOCNGW NSSF SMF UDM UPF PCF
// 获取网元网元列表
neInfoStore.fnNelist().then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
@@ -583,15 +585,9 @@ onMounted(() => {
// 过滤不可用的网元
neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter(
(item: any) => {
return ![
'OMC',
'PCF',
'NSSF',
'NEF',
'NRF',
'LMF',
'N3IWF',
].includes(item.value);
return !['OMC', 'NSSF', 'NEF', 'NRF', 'LMF', 'N3IWF'].includes(
item.value
);
}
);
if (neCascaderOptions.value.length === 0) {
@@ -619,9 +615,9 @@ onMounted(() => {
// 查询当前小时
const nowDate: Date = new Date();
nowDate.setMinutes(0, 0, 0);
queryRangePicker.value[0] = parseDateToStr(nowDate);
queryRangePicker.value[0] = `${nowDate.getTime()}`;
nowDate.setMinutes(59, 59, 59);
queryRangePicker.value[1] = parseDateToStr(nowDate);
queryRangePicker.value[1] = `${nowDate.getTime()}`;
fnGetListTitle();
// 绘图
fnRanderChart();
@@ -666,15 +662,17 @@ onBeforeUnmount(() => {
<a-col :lg="10" :md="12" :xs="24">
<a-form-item
:label="t('views.perfManage.goldTarget.timeFrame')"
name="eventTime"
name="timeFrame"
>
<a-range-picker
v-model:value="queryRangePicker"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
bordered
:allow-clear="false"
show-time
/>
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
value-format="x"
style="width: 100%"
></a-range-picker>
</a-form-item>
</a-col>
<a-col :lg="4" :md="12" :xs="24">
@@ -747,15 +745,6 @@ onBeforeUnmount(() => {
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center" v-show="tableState.showTable">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
@@ -833,11 +822,16 @@ onBeforeUnmount(() => {
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumnsDnd.length * 200, y: 450 }"
:scroll="{ x: tableColumnsDnd.length * 200, y: 'calc(100vh - 480px)' }"
@resizeColumn="(w:number, col:any) => (col.width = w)"
:show-expand-column="false"
@change="fnTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'timeGroup'">
{{ parseDateToStr(+record.timeGroup) }}
</template>
</template>
</a-table>
<!-- 图表 -->

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@ import {
updateNeInfo,
} from '@/api/ne/neInfo';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import Para5GForm from '@/views/ne/neConfPara5G/components/Para5GForm.vue';
import Para5GForm from '@/views/ne/neQuickSetup/components/Para5GForm.vue';
const { t } = useI18n();
/**对象信息信息状态类型 */
@@ -60,7 +60,7 @@ function fnSave() {
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('views.ne.neConfPara5G.saveOk'));
message.success(t('views.system.quickStart.savePara5GOk'));
state.saveFile = true;
// 更新omc_ip
state.omcInfo.ip = state.from.sbi.omc_ip;
@@ -113,7 +113,7 @@ function fnGetList() {
break;
case 'UDM':
state.from.sbi.udm_ip = item.ip;
state.from.sbi.adb_ip = '0.0.0.0';
state.from.sbi.db_ip = '0.0.0.0';
break;
case 'SMF':
state.from.sbi.smf_ip = item.ip;

View File

@@ -206,6 +206,7 @@ function fnCheckAppNameOverflow() {
if (!text) return;
if (text.offsetWidth > container.offsetWidth) {
text.classList.add('app-name_scrollable');
text.setAttribute('data-content', text.innerText);
} else {
text.classList.remove('app-name_scrollable');
}
@@ -398,18 +399,25 @@ onMounted(() => {
font-size: 18px;
> .app-name_scrollable {
// padding-left: 100%;
padding-right: 12px;
display: inline-block;
animation: scrollable-animation linear 6s infinite both;
}
@keyframes scrollable-animation {
0% {
transform: translate3d(0, 0, 0);
&::after {
content: attr(data-content);
position: absolute;
top: 0;
right: -100%;
transition: right 0.3s ease;
}
100% {
transform: translate3d(-100%, 0, 0);
@keyframes scrollable-animation {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(-100% - 12px));
}
}
}
}

View File

@@ -118,20 +118,6 @@ onMounted(() => {
<a-form layout="vertical">
<template v-if="state.edite">
<a-form-item>
<a-select
v-model:value="state.language"
style="width: 100px"
size="small"
v-perms:has="['system:setting:i18n']"
>
<a-select-option
v-for="opt in optionsLocale"
:key="opt.value"
:value="opt.value"
>
{{ opt.label }}
</a-select-option>
</a-select>
<a-upload
name="file"
list-type="text"
@@ -145,6 +131,24 @@ onMounted(() => {
{{ t('views.system.setting.uploadFile') }}
</a-button>
</a-upload>
<span
v-perms:has="['system:setting:i18n']"
v-show="appStore.i18nOpen"
>
<a-select
v-model:value="state.language"
style="width: 100px"
size="small"
>
<a-select-option
v-for="opt in optionsLocale"
:key="opt.value"
:value="opt.value"
>
{{ opt.label }}
</a-select-option>
</a-select>
</span>
</a-form-item>
<a-form-item>
@@ -164,26 +168,30 @@ onMounted(() => {
<template v-else>
<a-form-item>
<a-space direction="horizontal" :size="16">
<a-select
v-model:value="state.language"
style="width: 100px"
size="small"
v-perms:has="['system:setting:i18n']"
>
<a-select-option
v-for="opt in optionsLocale"
:key="opt.value"
:value="opt.value"
>
{{ opt.label }}
</a-select-option>
</a-select>
<a-button type="link" @click="fnClickHelpDoc(state.language)">
<template #icon>
<QuestionCircleOutlined />
</template>
{{ t('views.system.setting.sysHelpDocOpen') }}
</a-button>
<span
v-perms:has="['system:setting:i18n']"
v-show="appStore.i18nOpen"
>
<a-select
v-model:value="state.language"
style="width: 100px"
size="small"
>
<a-select-option
v-for="opt in optionsLocale"
:key="opt.value"
:value="opt.value"
>
{{ opt.label }}
</a-select-option>
</a-select>
</span>
</a-space>
</a-form-item>
<a-button type="default" @click="fnEdit(true)">

View File

@@ -184,12 +184,15 @@ watch(
/**检查系统名称是否超出范围进行滚动 */
function fnCheckAppNameOverflow() {
const container: HTMLDivElement | null = document.querySelector('.header-icon > .app-name');
const container: HTMLDivElement | null = document.querySelector(
'.header-icon > .app-name'
);
if (!container) return;
const text: HTMLDivElement | null = container.querySelector('.marquee');
if (!text) return;
if (text.offsetWidth > container.offsetWidth) {
text.classList.add('app-name_scrollable');
text.setAttribute('data-content', text.innerText);
} else {
text.classList.remove('app-name_scrollable');
}
@@ -201,7 +204,7 @@ watch(
);
onMounted(() => {
fnCheckAppNameOverflow()
fnCheckAppNameOverflow();
Object.assign(state, {
language: currentLocale.value,
filePath: '',
@@ -239,7 +242,10 @@ onMounted(() => {
</div>
</div>
</a-form-item>
<a-form-item v-perms:has="['system:setting:i18n']">
<a-form-item
v-perms:has="['system:setting:i18n']"
v-show="appStore.i18nOpen"
>
<a-select v-model:value="state.language" style="width: 100px">
<a-select-option
v-for="opt in optionsLocale"
@@ -362,18 +368,25 @@ onMounted(() => {
font-size: 18px;
> .app-name_scrollable {
// padding-left: 100%;
padding-right: 12px;
display: inline-block;
animation: scrollable-animation linear 6s infinite both;
}
@keyframes scrollable-animation {
0% {
transform: translate3d(0, 0, 0);
&::after {
content: attr(data-content);
position: absolute;
top: 0;
right: -100%;
transition: right 0.3s ease;
}
100% {
transform: translate3d(-100%, 0, 0);
@keyframes scrollable-animation {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(-100% - 12px));
}
}
}
}

View File

@@ -31,14 +31,18 @@ const { t } = useI18n();
{{ t('views.system.setting.sysLoginBg') }}
</a-divider>
<ChangeLogoBG></ChangeLogoBG>
<a-divider orientation="left">
{{ t('views.system.setting.sysHelpDoc') }}
</a-divider>
<ChangeHelpDoc></ChangeHelpDoc>
<a-divider orientation="left">
{{ t('views.system.setting.sysOfficialUrl') }}
</a-divider>
<ChangeOfficialUrl></ChangeOfficialUrl>
<div v-perms:has="['system:setting:doc']">
<a-divider orientation="left">
{{ t('views.system.setting.sysHelpDoc') }}
</a-divider>
<ChangeHelpDoc></ChangeHelpDoc>
</div>
<div v-perms:has="['system:setting:official']">
<a-divider orientation="left">
{{ t('views.system.setting.sysOfficialUrl') }}
</a-divider>
<ChangeOfficialUrl></ChangeOfficialUrl>
</div>
<div v-perms:has="['system:setting:i18n']">
<a-divider orientation="left">
{{ t('views.system.setting.i18n') }}