新增:新增前端项目模块(基于 Vue3、TypeScript、Arco Design Pro Vue 技术栈),已对接现有 API

This commit is contained in:
2022-12-28 22:44:57 +08:00
parent 6a7ad96fa3
commit 9064d06ff5
239 changed files with 22549 additions and 34 deletions

View File

@@ -0,0 +1,16 @@
import { appRoutes, appExternalRoutes } from '../routes';
const mixinRoutes = [...appRoutes, ...appExternalRoutes];
const appClientMenus = mixinRoutes.map((el) => {
const { name, path, meta, redirect, children } = el;
return {
name,
path,
meta,
redirect,
children,
};
});
export default appClientMenus;

View File

@@ -0,0 +1,18 @@
export const WHITE_LIST = [
{ name: 'notFound', children: [] },
{ name: 'login', children: [] },
];
export const NOT_FOUND = {
name: 'notFound',
};
export const REDIRECT_ROUTE_NAME = 'Redirect';
export const DEFAULT_ROUTE_NAME = 'Workplace';
export const DEFAULT_ROUTE = {
title: 'menu.dashboard.workplace',
name: DEFAULT_ROUTE_NAME,
fullPath: '/dashboard/workplace',
};

View File

@@ -0,0 +1,17 @@
import type { Router } from 'vue-router';
import { setRouteEmitter } from '@/utils/route-listener';
import setupUserLoginInfoGuard from './userLoginInfo';
import setupPermissionGuard from './permission';
function setupPageGuard(router: Router) {
router.beforeEach(async (to) => {
// emit route change
setRouteEmitter(to);
});
}
export default function createRouteGuard(router: Router) {
setupPageGuard(router);
setupUserLoginInfoGuard(router);
setupPermissionGuard(router);
}

View File

@@ -0,0 +1,55 @@
import type { Router, RouteRecordNormalized } from 'vue-router';
import NProgress from 'nprogress'; // progress bar
import usePermission from '@/hooks/permission';
import { useLoginStore, useAppStore } from '@/store';
import { appRoutes } from '../routes';
import { WHITE_LIST, NOT_FOUND } from '../constants';
export default function setupPermissionGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
const appStore = useAppStore();
const loginStore = useLoginStore();
const Permission = usePermission();
const permissionsAllow = Permission.accessRouter(to);
if (appStore.menuFromServer) {
// 针对来自服务端的菜单配置进行处理
// Handle routing configuration from the server
// 根据需要自行完善来源于服务端的菜单配置的permission逻辑
// Refine the permission logic from the server's menu configuration as needed
if (
!appStore.appAsyncMenus.length &&
!WHITE_LIST.find((el) => el.name === to.name)
) {
await appStore.fetchServerMenuConfig();
}
const serverMenuConfig = [...appStore.appAsyncMenus, ...WHITE_LIST];
let exist = false;
while (serverMenuConfig.length && !exist) {
const element = serverMenuConfig.shift();
if (element?.name === to.name) exist = true;
if (element?.children) {
serverMenuConfig.push(
...(element.children as unknown as RouteRecordNormalized[])
);
}
}
if (exist && permissionsAllow) {
next();
} else next(NOT_FOUND);
} else {
// eslint-disable-next-line no-lonely-if
if (permissionsAllow) next();
else {
const destination =
Permission.findFirstPermissionRoute(appRoutes, loginStore.role) ||
NOT_FOUND;
next(destination);
}
}
NProgress.done();
});
}

View File

@@ -0,0 +1,43 @@
import type { Router, LocationQueryRaw } from 'vue-router';
import NProgress from 'nprogress'; // progress bar
import { useLoginStore } from '@/store';
import { isLogin } from '@/utils/auth';
export default function setupUserLoginInfoGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
NProgress.start();
const loginStore = useLoginStore();
if (isLogin()) {
if (loginStore.role) {
next();
} else {
try {
await loginStore.info();
next();
} catch (error) {
await loginStore.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,
});
}
});
}

View File

@@ -0,0 +1,37 @@
import { createRouter, createWebHistory } from 'vue-router';
import NProgress from 'nprogress'; // progress bar
import 'nprogress/nprogress.css';
import { appRoutes } from './routes';
import { REDIRECT_MAIN, NOT_FOUND_ROUTE } from './routes/base';
import createRouteGuard from './guard';
NProgress.configure({ showSpinner: false }); // NProgress Configuration
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
redirect: 'login',
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue'),
meta: {
requiresAuth: false,
},
},
...appRoutes,
REDIRECT_MAIN,
NOT_FOUND_ROUTE,
],
scrollBehavior() {
return { top: 0 };
},
});
createRouteGuard(router);
export default router;

View File

@@ -0,0 +1,31 @@
import type { RouteRecordRaw } from 'vue-router';
import { REDIRECT_ROUTE_NAME } from '@/router/constants';
export const DEFAULT_LAYOUT = () => import('@/layout/default-layout.vue');
export const REDIRECT_MAIN: RouteRecordRaw = {
path: '/redirect',
name: 'redirectWrapper',
component: DEFAULT_LAYOUT,
meta: {
requiresAuth: true,
hideInMenu: true,
},
children: [
{
path: '/redirect/:path',
name: REDIRECT_ROUTE_NAME,
component: () => import('@/views/redirect/index.vue'),
meta: {
requiresAuth: true,
hideInMenu: true,
},
},
],
};
export const NOT_FOUND_ROUTE: RouteRecordRaw = {
path: '/:pathMatch(.*)*',
name: 'notFound',
component: () => import('@/views/not-found/index.vue'),
};

View File

@@ -0,0 +1,10 @@
export default {
path: 'https://arco.design',
name: 'arcoWebsite',
meta: {
locale: 'menu.arcoWebsite',
icon: 'icon-link',
requiresAuth: true,
order: 8,
},
};

View File

@@ -0,0 +1,10 @@
export default {
path: 'https://arco.design/vue/docs/pro/faq',
name: 'faq',
meta: {
locale: 'menu.faq',
icon: 'icon-question-circle',
requiresAuth: true,
order: 9,
},
};

View File

@@ -0,0 +1,25 @@
import type { RouteRecordNormalized } from 'vue-router';
const modules = import.meta.glob('./modules/*.ts', { eager: true });
const externalModules = import.meta.glob('./externalModules/*.ts', {
eager: true,
});
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;
}
export const appRoutes: RouteRecordNormalized[] = formatModules(modules, []);
export const appExternalRoutes: RouteRecordNormalized[] = formatModules(
externalModules,
[]
);

View File

@@ -0,0 +1,39 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const DASHBOARD: AppRouteRecordRaw = {
path: '/dashboard',
name: 'dashboard',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.dashboard',
requiresAuth: true,
icon: 'icon-dashboard',
order: 0,
},
children: [
{
path: 'workplace',
name: 'Workplace',
component: () => import('@/views/dashboard/workplace/index.vue'),
meta: {
locale: 'menu.dashboard.workplace',
requiresAuth: true,
roles: ['*'],
},
},
{
path: 'monitor',
name: 'Monitor',
component: () => import('@/views/dashboard/monitor/index.vue'),
meta: {
locale: 'menu.dashboard.monitor',
requiresAuth: true,
roles: ['admin'],
},
},
],
};
export default DASHBOARD;

View File

@@ -0,0 +1,48 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const EXCEPTION: AppRouteRecordRaw = {
path: '/exception',
name: 'exception',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.exception',
requiresAuth: true,
icon: 'icon-exclamation-circle',
order: 6,
},
children: [
{
path: '403',
name: '403',
component: () => import('@/views/exception/403/index.vue'),
meta: {
locale: 'menu.exception.403',
requiresAuth: true,
roles: ['admin'],
},
},
{
path: '404',
name: '404',
component: () => import('@/views/exception/404/index.vue'),
meta: {
locale: 'menu.exception.404',
requiresAuth: true,
roles: ['*'],
},
},
{
path: '500',
name: '500',
component: () => import('@/views/exception/500/index.vue'),
meta: {
locale: 'menu.exception.500',
requiresAuth: true,
roles: ['*'],
},
},
],
};
export default EXCEPTION;

View File

@@ -0,0 +1,38 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const FORM: AppRouteRecordRaw = {
path: '/form',
name: 'form',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.form',
icon: 'icon-settings',
requiresAuth: true,
order: 3,
},
children: [
{
path: 'step',
name: 'Step',
component: () => import('@/views/form/step/index.vue'),
meta: {
locale: 'menu.form.step',
requiresAuth: true,
roles: ['admin'],
},
},
{
path: 'group',
name: 'Group',
component: () => import('@/views/form/group/index.vue'),
meta: {
locale: 'menu.form.group',
requiresAuth: true,
roles: ['admin'],
},
},
],
};
export default FORM;

View File

@@ -0,0 +1,38 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const LIST: AppRouteRecordRaw = {
path: '/list',
name: 'list',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.list',
requiresAuth: true,
icon: 'icon-list',
order: 2,
},
children: [
{
path: 'search-table', // The midline path complies with SEO specifications
name: 'SearchTable',
component: () => import('@/views/list/search-table/index.vue'),
meta: {
locale: 'menu.list.searchTable',
requiresAuth: true,
roles: ['*'],
},
},
{
path: 'card',
name: 'Card',
component: () => import('@/views/list/card/index.vue'),
meta: {
locale: 'menu.list.cardList',
requiresAuth: true,
roles: ['*'],
},
},
],
};
export default LIST;

View File

@@ -0,0 +1,28 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const PROFILE: AppRouteRecordRaw = {
path: '/profile',
name: 'profile',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.profile',
requiresAuth: true,
icon: 'icon-file',
order: 4,
},
children: [
{
path: 'basic',
name: 'Basic',
component: () => import('@/views/profile/basic/index.vue'),
meta: {
locale: 'menu.profile.basic',
requiresAuth: true,
roles: ['admin'],
},
},
],
};
export default PROFILE;

View File

@@ -0,0 +1,38 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const RESULT: AppRouteRecordRaw = {
path: '/result',
name: 'result',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.result',
icon: 'icon-check-circle',
requiresAuth: true,
order: 5,
},
children: [
{
path: 'success',
name: 'Success',
component: () => import('@/views/result/success/index.vue'),
meta: {
locale: 'menu.result.success',
requiresAuth: true,
roles: ['admin'],
},
},
{
path: 'error',
name: 'Error',
component: () => import('@/views/result/error/index.vue'),
meta: {
locale: 'menu.result.error',
requiresAuth: true,
roles: ['admin'],
},
},
],
};
export default RESULT;

View File

@@ -0,0 +1,38 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const USER: AppRouteRecordRaw = {
path: '/user',
name: 'user',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.user',
icon: 'icon-user',
requiresAuth: true,
order: 7,
},
children: [
{
path: 'info',
name: 'Info',
component: () => import('@/views/user/info/index.vue'),
meta: {
locale: 'menu.user.info',
requiresAuth: true,
roles: ['*'],
},
},
{
path: 'setting',
name: 'Setting',
component: () => import('@/views/user/setting/index.vue'),
meta: {
locale: 'menu.user.setting',
requiresAuth: true,
roles: ['*'],
},
},
],
};
export default USER;

View File

@@ -0,0 +1,39 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const VISUALIZATION: AppRouteRecordRaw = {
path: '/visualization',
name: 'visualization',
component: DEFAULT_LAYOUT,
meta: {
locale: 'menu.visualization',
requiresAuth: true,
icon: 'icon-apps',
order: 1,
},
children: [
{
path: 'data-analysis',
name: 'DataAnalysis',
component: () => import('@/views/visualization/data-analysis/index.vue'),
meta: {
locale: 'menu.visualization.dataAnalysis',
requiresAuth: true,
roles: ['admin'],
},
},
{
path: 'multi-dimension-data-analysis',
name: 'MultiDimensionDataAnalysis',
component: () =>
import('@/views/visualization/multi-dimension-data-analysis/index.vue'),
meta: {
locale: 'menu.visualization.multiDimensionDataAnalysis',
requiresAuth: true,
roles: ['admin'],
},
},
],
};
export default VISUALIZATION;

View File

@@ -0,0 +1,20 @@
import { defineComponent } from 'vue';
import type { RouteMeta, NavigationGuard } from 'vue-router';
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;
}

View File

@@ -0,0 +1,16 @@
import 'vue-router';
declare module 'vue-router' {
interface RouteMeta {
roles?: string[]; // Controls roles that have access to the page
requiresAuth: boolean; // Whether login is required to access the current page (every route must declare)
icon?: string; // The icon show in the side menu
locale?: string; // The locale name show in side menu and breadcrumb
hideInMenu?: boolean; // If true, it is not displayed in the side menu
hideChildrenInMenu?: boolean; // if set true, the children are not displayed in the side menu
activeMenu?: string; // if set name, the menu will be highlighted according to the name you set
order?: number; // Sort routing menu items. If set key, the higher the value, the more forward it is
noAffix?: boolean; // if set true, the tag will not affix in the tab-bar
ignoreCache?: boolean; // if set true, the page will not be cached
}
}