🔖 项目重命名.
This commit is contained in:
60
orion-visor-ui/src/router/constants.ts
Normal file
60
orion-visor-ui/src/router/constants.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
import type { TagProps } from '@/store/modules/tab-bar/types';
|
||||
|
||||
export const REDIRECT_ROUTE_NAME = 'redirect';
|
||||
|
||||
export const LOGIN_ROUTE_NAME = 'login';
|
||||
|
||||
export const FORBIDDEN_ROUTER_NAME = 'forbidden';
|
||||
|
||||
export const NOT_FOUND_ROUTER_NAME = 'notFound';
|
||||
|
||||
export const DEFAULT_ROUTE_NAME = 'workplace';
|
||||
|
||||
export const DEFAULT_ROUTE_FULL_PATH = '/workplace';
|
||||
|
||||
/**
|
||||
* 默认路由
|
||||
*/
|
||||
export const DEFAULT_ROUTER = { name: DEFAULT_ROUTE_NAME, children: [] };
|
||||
|
||||
/**
|
||||
* 路由白名单
|
||||
*/
|
||||
export const WHITE_ROUTER_LIST = [
|
||||
{ name: LOGIN_ROUTE_NAME, children: [] },
|
||||
{ name: REDIRECT_ROUTE_NAME, children: [] },
|
||||
];
|
||||
|
||||
/**
|
||||
* 状态路由
|
||||
*/
|
||||
export const STATUS_ROUTER_LIST = [
|
||||
{ name: NOT_FOUND_ROUTER_NAME, children: [] },
|
||||
{ name: FORBIDDEN_ROUTER_NAME, children: [] },
|
||||
];
|
||||
|
||||
/**
|
||||
* 默认 tab 页面
|
||||
*/
|
||||
export const DEFAULT_TAB = {
|
||||
title: '工作台',
|
||||
name: DEFAULT_ROUTE_NAME,
|
||||
path: DEFAULT_ROUTE_FULL_PATH,
|
||||
fullPath: DEFAULT_ROUTE_FULL_PATH,
|
||||
};
|
||||
|
||||
/**
|
||||
* router 转 tag
|
||||
*/
|
||||
export const routerToTag = (route: RouteLocationNormalized): TagProps => {
|
||||
const { name, meta, path, fullPath, query } = route;
|
||||
return {
|
||||
title: meta.locale || '',
|
||||
name: String(name),
|
||||
path,
|
||||
fullPath,
|
||||
query,
|
||||
ignoreCache: meta.ignoreCache,
|
||||
};
|
||||
};
|
||||
16
orion-visor-ui/src/router/guard/index.ts
Normal file
16
orion-visor-ui/src/router/guard/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { Router } from 'vue-router';
|
||||
import setupUserLoginInfoGuard from './user-login-info';
|
||||
import setupPermissionGuard from './router-permission';
|
||||
import setupRouteEmitterGuard from './router-listener-emitter';
|
||||
|
||||
/**
|
||||
* 创建路由守卫
|
||||
*/
|
||||
export default function createRouteGuard(router: Router) {
|
||||
// 路由监听守卫
|
||||
setupRouteEmitterGuard(router);
|
||||
// 登录检查守卫
|
||||
setupUserLoginInfoGuard(router);
|
||||
// 权限检查守卫
|
||||
setupPermissionGuard(router);
|
||||
}
|
||||
11
orion-visor-ui/src/router/guard/router-listener-emitter.ts
Normal file
11
orion-visor-ui/src/router/guard/router-listener-emitter.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { Router } from 'vue-router';
|
||||
import { setRouteEmitter } from '@/utils/route-listener';
|
||||
|
||||
/**
|
||||
* 初始化路由监听订阅
|
||||
*/
|
||||
export default function setupRouteEmitterGuard(router: Router) {
|
||||
router.beforeEach(async (to) => {
|
||||
setRouteEmitter(to);
|
||||
});
|
||||
}
|
||||
39
orion-visor-ui/src/router/guard/router-permission.ts
Normal file
39
orion-visor-ui/src/router/guard/router-permission.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { Router } from 'vue-router';
|
||||
import NProgress from 'nprogress';
|
||||
import { useMenuStore } from '@/store';
|
||||
import { NOT_FOUND_ROUTER_NAME, WHITE_ROUTER_LIST } from '../constants';
|
||||
import usePermission from '@/hooks/permission';
|
||||
|
||||
export default function setupPermissionGuard(router: Router) {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const menuStore = useMenuStore();
|
||||
// 未加载菜单 并且 不在白名单内 则加载菜单
|
||||
if (
|
||||
!menuStore.menuFetched &&
|
||||
!WHITE_ROUTER_LIST.find((el) => el.name === to.name)
|
||||
) {
|
||||
// 加载菜单
|
||||
await menuStore.fetchMenu();
|
||||
}
|
||||
// 检测是否可以访问
|
||||
const permission = usePermission();
|
||||
const access = permission.accessRouter(to);
|
||||
// 刚进入页面时 重定向的 meta 是空的
|
||||
if (access && to.meta.locale === undefined && menuStore.menuFetched) {
|
||||
to.meta = to.matched[to.matched.length - 1].meta;
|
||||
}
|
||||
if (access) {
|
||||
// 正常跳转
|
||||
next();
|
||||
} else {
|
||||
// 页面不存在
|
||||
next({ name: NOT_FOUND_ROUTER_NAME });
|
||||
}
|
||||
// 修改页面标题
|
||||
const locale = to.meta?.locale;
|
||||
if (locale) {
|
||||
document.title = locale;
|
||||
}
|
||||
NProgress.done();
|
||||
});
|
||||
}
|
||||
50
orion-visor-ui/src/router/guard/user-login-info.ts
Normal file
50
orion-visor-ui/src/router/guard/user-login-info.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { Router, LocationQueryRaw } from 'vue-router';
|
||||
import NProgress from 'nprogress';
|
||||
import { useUserStore } from '@/store';
|
||||
import { isLogin } from '@/utils/auth';
|
||||
|
||||
/**
|
||||
* 初始化用户登录路由守卫
|
||||
*/
|
||||
export default function setupUserLoginInfoGuard(router: Router) {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start();
|
||||
const userStore = useUserStore();
|
||||
if (isLogin()) {
|
||||
// 获取用户信息
|
||||
if (userStore.id) {
|
||||
next();
|
||||
} else {
|
||||
try {
|
||||
// 获取用户信息
|
||||
await userStore.info();
|
||||
next();
|
||||
} catch (error) {
|
||||
// 获取失败退出登录
|
||||
await userStore.logout();
|
||||
next({
|
||||
name: 'login',
|
||||
query: {
|
||||
redirect: to.name,
|
||||
...to.query,
|
||||
} as LocationQueryRaw,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 未登录跳转到登录页
|
||||
if (to.name === 'login') {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
// 跳转到登录页
|
||||
next({
|
||||
name: 'login',
|
||||
query: {
|
||||
redirect: to.name,
|
||||
...to.query,
|
||||
} as LocationQueryRaw,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
33
orion-visor-ui/src/router/index.ts
Normal file
33
orion-visor-ui/src/router/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { RouteLocationRaw } from 'vue-router';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import NProgress from 'nprogress';
|
||||
import { appRoutes } from './routes';
|
||||
import BASE_ROUTERS from './routes/base';
|
||||
import createRouteGuard from './guard';
|
||||
import { openWindow } from '@/utils';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
NProgress.configure({ showSpinner: false });
|
||||
|
||||
// 创建路由
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
...BASE_ROUTERS,
|
||||
...appRoutes,
|
||||
],
|
||||
scrollBehavior() {
|
||||
return { top: 0 };
|
||||
},
|
||||
});
|
||||
|
||||
// 创建路由守卫
|
||||
createRouteGuard(router);
|
||||
|
||||
// 新页面打开路由
|
||||
export const openNewRoute = (route: RouteLocationRaw) => {
|
||||
const { href } = router.resolve(route);
|
||||
openWindow(href);
|
||||
};
|
||||
|
||||
export default router;
|
||||
76
orion-visor-ui/src/router/routes/base.ts
Normal file
76
orion-visor-ui/src/router/routes/base.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
import { DEFAULT_ROUTE_FULL_PATH, FORBIDDEN_ROUTER_NAME, LOGIN_ROUTE_NAME, NOT_FOUND_ROUTER_NAME, REDIRECT_ROUTE_NAME, } from '@/router/constants';
|
||||
|
||||
// 默认布局
|
||||
export const DEFAULT_LAYOUT = () => import('@/layout/default-layout.vue');
|
||||
|
||||
// 全屏布局
|
||||
export const FULL_LAYOUT = () => import('@/layout/full-layout.vue');
|
||||
|
||||
// 根页面
|
||||
export const ROOT_ROUTER: RouteRecordRaw = {
|
||||
path: '/',
|
||||
redirect: DEFAULT_ROUTE_FULL_PATH,
|
||||
};
|
||||
|
||||
// 登录页面
|
||||
export const LOGIN_ROUTER: RouteRecordRaw = {
|
||||
path: '/login',
|
||||
name: LOGIN_ROUTE_NAME,
|
||||
meta: {
|
||||
locale: '登录'
|
||||
},
|
||||
component: () => import('@/views/authentication/login/index.vue'),
|
||||
};
|
||||
|
||||
// 重定向页面
|
||||
export const REDIRECT_ROUTER: RouteRecordRaw = {
|
||||
path: '/redirect',
|
||||
name: 'redirectWrapper',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
locale: '重定向',
|
||||
hideInMenu: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/redirect/:path',
|
||||
name: REDIRECT_ROUTE_NAME,
|
||||
component: () => import('@/views/base/redirect/index.vue'),
|
||||
meta: {
|
||||
locale: '重定向',
|
||||
hideInMenu: true,
|
||||
noAffix: true
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 403 页面
|
||||
export const FORBIDDEN_ROUTE: RouteRecordRaw = {
|
||||
path: '/403',
|
||||
name: FORBIDDEN_ROUTER_NAME,
|
||||
meta: {
|
||||
locale: '403'
|
||||
},
|
||||
component: () => import('@/views/base/status/forbidden/index.vue'),
|
||||
};
|
||||
|
||||
// 404 页面
|
||||
export const NOT_FOUND_ROUTE: RouteRecordRaw = {
|
||||
// path: '/:pathMatch(.*)*',
|
||||
path: '/404',
|
||||
name: NOT_FOUND_ROUTER_NAME,
|
||||
meta: {
|
||||
locale: '404'
|
||||
},
|
||||
component: () => import('@/views/base/status/not-found/index.vue'),
|
||||
};
|
||||
|
||||
export default [
|
||||
ROOT_ROUTER,
|
||||
LOGIN_ROUTER,
|
||||
REDIRECT_ROUTER,
|
||||
NOT_FOUND_ROUTE,
|
||||
FORBIDDEN_ROUTE
|
||||
];
|
||||
20
orion-visor-ui/src/router/routes/index.ts
Normal file
20
orion-visor-ui/src/router/routes/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { RouteRecordNormalized } from 'vue-router';
|
||||
|
||||
// 应用模块
|
||||
const modules = import.meta.glob('./modules/*.ts', { eager: true });
|
||||
|
||||
// 应用路由
|
||||
export const appRoutes: RouteRecordNormalized[] = formatModules(modules, []);
|
||||
|
||||
// 格式化模块
|
||||
function formatModules(_modules: any, result: RouteRecordNormalized[]) {
|
||||
Object.keys(_modules).forEach((key) => {
|
||||
const defaultModule = _modules[key].default;
|
||||
if (!defaultModule) return;
|
||||
const moduleList = Array.isArray(defaultModule)
|
||||
? [...defaultModule]
|
||||
: [defaultModule];
|
||||
result.push(...moduleList);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
27
orion-visor-ui/src/router/routes/modules/asset-audit.ts
Normal file
27
orion-visor-ui/src/router/routes/modules/asset-audit.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
|
||||
const ASSET_AUDIT: AppRouteRecordRaw = {
|
||||
name: 'assetAuditModule',
|
||||
path: '/asset-audit-module',
|
||||
component: DEFAULT_LAYOUT,
|
||||
children: [
|
||||
{
|
||||
name: 'connectLog',
|
||||
path: '/connect-log',
|
||||
component: () => import('@/views/asset-audit/connect-log/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'connectSession',
|
||||
path: '/connect-session',
|
||||
component: () => import('@/views/asset-audit/connect-session/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'sftpLog',
|
||||
path: '/sftp-log',
|
||||
component: () => import('@/views/asset-audit/sftp-log/index.vue'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default ASSET_AUDIT;
|
||||
29
orion-visor-ui/src/router/routes/modules/asset.ts
Normal file
29
orion-visor-ui/src/router/routes/modules/asset.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
|
||||
const ASSET: AppRouteRecordRaw = {
|
||||
name: 'assetModule',
|
||||
path: '/asset-module',
|
||||
component: DEFAULT_LAYOUT,
|
||||
children: [
|
||||
{
|
||||
name: 'hostList',
|
||||
path: '/host-list',
|
||||
component: () => import('@/views/asset/host-list/index.vue'),
|
||||
}, {
|
||||
name: 'hostKey',
|
||||
path: '/host-key',
|
||||
component: () => import('@/views/asset/host-key/index.vue'),
|
||||
}, {
|
||||
name: 'hostIdentity',
|
||||
path: '/host-identity',
|
||||
component: () => import('@/views/asset/host-identity/index.vue'),
|
||||
}, {
|
||||
name: 'assetGrant',
|
||||
path: '/asset-grant',
|
||||
component: () => import('@/views/asset/grant/index.vue'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default ASSET;
|
||||
17
orion-visor-ui/src/router/routes/modules/dashboard.ts
Normal file
17
orion-visor-ui/src/router/routes/modules/dashboard.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
|
||||
const DASHBOARD: AppRouteRecordRaw = {
|
||||
name: 'dashboard',
|
||||
path: '/dashboard',
|
||||
component: DEFAULT_LAYOUT,
|
||||
children: [
|
||||
{
|
||||
name: 'workplace',
|
||||
path: '/workplace',
|
||||
component: () => import('@/views/dashboard/workplace/index.vue'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default DASHBOARD;
|
||||
37
orion-visor-ui/src/router/routes/modules/exec.ts
Normal file
37
orion-visor-ui/src/router/routes/modules/exec.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
|
||||
const EXEC: AppRouteRecordRaw = {
|
||||
name: 'execModule',
|
||||
path: '/exec-module',
|
||||
component: DEFAULT_LAYOUT,
|
||||
children: [
|
||||
{
|
||||
name: 'execCommand',
|
||||
path: '/exec-command',
|
||||
component: () => import('@/views/exec/exec-command/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'execCommandLog',
|
||||
path: '/exec-log',
|
||||
component: () => import('@/views/exec/exec-command-log/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'batchUpload',
|
||||
path: '/batch-upload',
|
||||
component: () => import('@/views/exec/batch-upload/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'uploadTask',
|
||||
path: '/upload-task',
|
||||
component: () => import('@/views/exec/upload-task/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'execTemplate',
|
||||
path: '/exec-template',
|
||||
component: () => import('@/views/exec/exec-template/index.vue'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default EXEC;
|
||||
20
orion-visor-ui/src/router/routes/modules/host.ts
Normal file
20
orion-visor-ui/src/router/routes/modules/host.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
import { FULL_LAYOUT } from '../base';
|
||||
|
||||
const HOST: AppRouteRecordRaw = {
|
||||
name: 'hostModule',
|
||||
path: '/host-module',
|
||||
component: FULL_LAYOUT,
|
||||
children: [
|
||||
{
|
||||
name: 'terminal',
|
||||
path: '/terminal',
|
||||
component: () => import('@/views/host/terminal/index.vue'),
|
||||
meta: {
|
||||
noAffix: true
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default HOST;
|
||||
36
orion-visor-ui/src/router/routes/modules/job.ts
Normal file
36
orion-visor-ui/src/router/routes/modules/job.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
import { DEFAULT_LAYOUT, FULL_LAYOUT } from '../base';
|
||||
|
||||
const JOB: AppRouteRecordRaw[] = [
|
||||
{
|
||||
name: 'jobModule',
|
||||
path: '/job-module',
|
||||
component: DEFAULT_LAYOUT,
|
||||
children: [
|
||||
{
|
||||
name: 'execJob',
|
||||
path: '/exec-job',
|
||||
component: () => import('@/views/job/exec-job/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'execJobLog',
|
||||
path: '/exec-job-log',
|
||||
component: () => import('@/views/job/exec-job-log/index.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'jobFullModule',
|
||||
path: '/job-full-module',
|
||||
component: FULL_LAYOUT,
|
||||
children: [
|
||||
{
|
||||
name: 'execJobLogView',
|
||||
path: '/job-log-view',
|
||||
component: () => import('@/views/job/exec-job-log-view/index.vue'),
|
||||
},
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
export default JOB;
|
||||
27
orion-visor-ui/src/router/routes/modules/system.ts
Normal file
27
orion-visor-ui/src/router/routes/modules/system.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
|
||||
const SYSTEM: AppRouteRecordRaw = {
|
||||
name: 'systemModule',
|
||||
path: '/system-module',
|
||||
component: DEFAULT_LAYOUT,
|
||||
children: [
|
||||
{
|
||||
name: 'systemMenu',
|
||||
path: '/menu',
|
||||
component: () => import('@/views/system/menu/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'dictKey',
|
||||
path: '/dict-key',
|
||||
component: () => import('@/views/system/dict-key/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'dictValue',
|
||||
path: '/dict-value',
|
||||
component: () => import('@/views/system/dict-value/index.vue'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default SYSTEM;
|
||||
32
orion-visor-ui/src/router/routes/modules/user.ts
Normal file
32
orion-visor-ui/src/router/routes/modules/user.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { AppRouteRecordRaw } from '../types';
|
||||
import { DEFAULT_LAYOUT } from '../base';
|
||||
|
||||
const USER: AppRouteRecordRaw = {
|
||||
name: 'userModule',
|
||||
path: '/user-module',
|
||||
component: DEFAULT_LAYOUT,
|
||||
children: [
|
||||
{
|
||||
name: 'role',
|
||||
path: '/role',
|
||||
component: () => import('@/views/user/role/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'user',
|
||||
path: '/user',
|
||||
component: () => import('@/views/user/user/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'userInfo',
|
||||
path: '/user-info',
|
||||
component: () => import('@/views/user/info/index.vue'),
|
||||
},
|
||||
{
|
||||
name: 'operatorLog',
|
||||
path: '/operator-log',
|
||||
component: () => import('@/views/user/operator-log/index.vue'),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default USER;
|
||||
20
orion-visor-ui/src/router/routes/types.ts
Normal file
20
orion-visor-ui/src/router/routes/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { RouteMeta, NavigationGuard } from 'vue-router';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export type Component<T = any> =
|
||||
| ReturnType<typeof defineComponent>
|
||||
| (() => Promise<typeof import('*.vue')>)
|
||||
| (() => Promise<T>);
|
||||
|
||||
export interface AppRouteRecordRaw {
|
||||
path?: string;
|
||||
name?: string | symbol;
|
||||
meta?: RouteMeta;
|
||||
redirect?: string;
|
||||
component: Component | string;
|
||||
children?: AppRouteRecordRaw[];
|
||||
alias?: string | string[];
|
||||
props?: Record<string, any>;
|
||||
beforeEnter?: NavigationGuard | NavigationGuard[];
|
||||
fullPath?: string;
|
||||
}
|
||||
25
orion-visor-ui/src/router/typings.d.ts
vendored
Normal file
25
orion-visor-ui/src/router/typings.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'vue-router';
|
||||
|
||||
/**
|
||||
* 前端覆盖后端
|
||||
*/
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
// 图标
|
||||
icon?: string;
|
||||
// 名称
|
||||
locale?: string;
|
||||
// 排序
|
||||
order?: number;
|
||||
// 是否隐藏菜单
|
||||
hideInMenu?: boolean;
|
||||
// 是否添加到 tab
|
||||
noAffix?: boolean;
|
||||
// 是否忽略缓存
|
||||
ignoreCache?: boolean;
|
||||
// 是否新窗口打开
|
||||
newWindow?: boolean;
|
||||
// 是否活跃
|
||||
activeMenu?: string;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user