feat:初始化项目结构

This commit is contained in:
Zzz1zz
2025-06-22 23:55:55 +08:00
commit ac0d6dd5cc
160 changed files with 16402 additions and 0 deletions
+16
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;
+18
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 = 'business';
export const DEFAULT_ROUTE = {
title: 'menu.business.project',
name: DEFAULT_ROUTE_NAME,
fullPath: '/business/project',
};
+17
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);
}
+55
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 { useUserStore, 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 userStore = useUserStore();
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, userStore.role) ||
NOT_FOUND;
next(destination);
}
}
NProgress.done();
});
}
+43
View File
@@ -0,0 +1,43 @@
import type { Router, LocationQueryRaw } from 'vue-router';
import NProgress from 'nprogress'; // progress bar
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.role) {
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,
});
}
});
}
+37
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: true,
},
},
...appRoutes,
REDIRECT_MAIN,
NOT_FOUND_ROUTE,
],
scrollBehavior() {
return { top: 0 };
},
});
createRouteGuard(router);
export default router;
+31
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'),
};
+10
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,
},
};
+10
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,
},
};
@@ -0,0 +1,40 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const RESULT: AppRouteRecordRaw = {
path: '/result',
name: 'result',
component: DEFAULT_LAYOUT,
redirect:'/result/success',
meta: {
locale: 'menu.result',
icon: 'icon-check-circle',
requiresAuth: true,
order: 5,
isFirst:true,
},
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;
+40
View File
@@ -0,0 +1,40 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const USER: AppRouteRecordRaw = {
path: '/user',
name: 'user',
component: DEFAULT_LAYOUT,
redirect:'/user/info',
meta: {
locale: 'menu.user',
icon: 'icon-user',
requiresAuth: true,
order: 7,
isFirst:true,
},
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;
@@ -0,0 +1,41 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const VISUALIZATION: AppRouteRecordRaw = {
path: '/visualization',
name: 'visualization',
component: DEFAULT_LAYOUT,
redirect:'/visualization/data-analysis',
meta: {
locale: 'menu.visualization',
requiresAuth: true,
icon: 'icon-apps',
order: 1,
isFirst:true,
},
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;
+25
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,
[]
);
+173
View File
@@ -0,0 +1,173 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import PageLayout from '@/layout/page-layout.vue';
// 业务路由
const BUSINESS: AppRouteRecordRaw = {
path: '/business',
name: 'business',
component: DEFAULT_LAYOUT,
redirect: '/business/project',
meta: {
locale: 'menu.business',
requiresAuth: true,
icon: 'icon-heart-fill',
permissionKey: 'business',
order: 2,
isFirst: true,
},
children: [
// 项目管理
{
path: 'project',
name: 'business-project',
component: () => import('@/views/business/project/index.vue'),
meta: {
locale: 'menu.business.project',
requiresAuth: true,
roles: ['admin'],
icon: 'icon-attachment',
permissionKey: 'businessProject',
},
},
// 空间管理
{
path: 'space',
name: 'business-space',
component: PageLayout,
redirect: '/business/space/house-view',
meta: {
locale: 'menu.business.space',
requiresAuth: true,
roles: ['*'],
icon: 'icon-brush',
permissionKey: 'businessSpace',
},
children: [
// 房源剖视
{
path: 'house-view',
name: 'business-space-house-view',
component: () => import('@/views/business/space/house-view.vue'),
meta: {
locale: 'menu.business.house-view',
requiresAuth: true,
roles: ['*'],
icon: 'icon-brush',
},
},
// 房源列表
{
path: 'house-list',
name: 'business-space-house-list',
component: () => import('@/views/business/space/house-list.vue'),
meta: {
locale: 'menu.business.house-list',
requiresAuth: true,
roles: ['*'],
icon: 'icon-brush',
permissionKey: 'businessHouseList',
},
},
],
},
// 合同管理
{
path: 'contract',
name: 'business-contract',
component: PageLayout,
redirect: '/business/contract/contract-list',
meta: {
locale: 'menu.business.contract',
requiresAuth: true,
roles: ['*'],
icon: 'icon-brush',
permissionKey: 'businessContract',
},
children: [
// 合同列表
{
path: 'contract-list',
name: 'business-contract-list',
component: () => import('@/views/business/contract/list.vue'),
meta: {
locale: 'menu.business.contract-list',
requiresAuth: true,
roles: ['*'],
icon: 'icon-brush',
permissionKey: 'businessContractList',
},
},
// 合同归档
{
path: 'contract-file',
name: 'business-contract-file',
component: () => import('@/views/business/contract/file.vue'),
meta: {
locale: 'menu.business.contract-file',
requiresAuth: true,
roles: ['*'],
icon: 'icon-brush',
permissionKey: 'businessContractFile',
},
},
// 合同模板
{
path: 'contract-template',
name: 'business-contract-template',
component: () => import('@/views/business/contract/template.vue'),
meta: {
locale: 'menu.business.contract-template',
requiresAuth: true,
roles: ['*'],
icon: 'icon-brush',
permissionKey: 'businessContractTemplate',
},
},
],
},
// 租客管理
{
path: 'tenant',
name: 'business-tenant',
component: PageLayout,
redirect: '/business/tenant/list',
meta: {
locale: 'menu.business.tenant',
requiresAuth: true,
roles: ['*'],
icon: 'icon-font-colors',
permissionKey: 'businessTenant',
},
children: [
// 租客列表
{
path: 'tenant-list',
name: 'business-tenant-list',
component: () => import('@/views/business/tenant/list.vue'),
meta: {
locale: 'menu.business.tenant-list',
requiresAuth: true,
roles: ['*'],
icon: 'icon-font-colors',
permissionKey: 'businessTenantList',
},
},
// 租客联系人
{
path: 'tenant-contacts',
name: 'business-tenant-contacts',
component: () => import('@/views/business/tenant/contacts.vue'),
meta: {
locale: 'menu.business.tenant.contacts',
requiresAuth: true,
roles: ['*'],
icon: 'icon-font-colors',
permissionKey: 'businessTenantContacts',
},
},
],
},
],
};
export default BUSINESS;
+63
View File
@@ -0,0 +1,63 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import PageLayout from '@/layout/page-layout.vue';
const DASHBOARD: AppRouteRecordRaw = {
path: '/dashboard',
name: 'dashboard',
component: DEFAULT_LAYOUT,
redirect: '/dashboard/workplace',
meta: {
locale: 'menu.dashboard.workplace',
requiresAuth: true,
icon: 'icon-dashboard',
order: 1,
isFirst: true,
permissionKey: 'dashboard',
},
children: [
{
path: 'workplace',
name: 'Workplace',
component: PageLayout,
redirect: '/dashboard/workplace/step',
meta: {
locale: 'menu.dashboard.workplace',
requiresAuth: true,
roles: ['*'],
icon: 'icon-dashboard',
permissionKey: 'workplace',
// activeMenu: 'dashboard',
},
children: [
{
path: 'step',
name: 'Step',
component: () => import('@/views/dashboard/workplace/index.vue'),
meta: {
locale: 'menu.form.step',
requiresAuth: true,
roles: ['*'],
icon: 'icon-exclamation-circle',
permissionKey: 'step',
},
},
],
},
{
path: 'monitor',
name: 'Monitor',
component: () => import('@/views/dashboard/monitor/index.vue'),
meta: {
locale: 'menu.dashboard.monitor',
requiresAuth: true,
roles: ['*'],
icon: 'icon-settings',
permissionKey: 'monitor',
// activeMenu: 'dashboard',
},
},
],
};
export default DASHBOARD;
+40
View File
@@ -0,0 +1,40 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const FINANCE: AppRouteRecordRaw = {
path: '/finance',
name: 'finance',
component: DEFAULT_LAYOUT,
redirect: '/finance/bill',
meta: {
locale: 'menu.finance',
icon: 'icon-settings',
requiresAuth: true,
order: 3,
isFirst:true,
},
children: [
{
path: 'bill',
name: 'bill',
component: () => import('@/views/finance/bill/index.vue'),
meta: {
locale: 'menu.finance.bill',
requiresAuth: true,
roles: ['admin'],
},
},
{
path: 'money',
name: 'money',
component: () => import('@/views/finance/money/index.vue'),
meta: {
locale: 'menu.finance.money',
requiresAuth: true,
roles: ['admin'],
},
},
],
};
export default FINANCE;
+40
View File
@@ -0,0 +1,40 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
const PROPERTY: AppRouteRecordRaw = {
path: '/property',
name: 'property',
component: DEFAULT_LAYOUT,
redirect: '/property/hydropower',
meta: {
locale: 'menu.property',
requiresAuth: true,
icon: 'icon-list',
order: 4,
isFirst:true,
},
children: [
{
path: 'hydropower', // The midline path complies with SEO specifications
name: 'hydropower',
component: () => import('@/views/property/hydropower/index.vue'),
meta: {
locale: 'menu.property.hydropower',
requiresAuth: true,
roles: ['*'],
},
},
{
path: 'reservation',
name: 'reservation',
component: () => import('@/views/property/reservation/index.vue'),
meta: {
locale: 'menu.property.reservation',
requiresAuth: true,
roles: ['*'],
},
},
],
};
export default PROPERTY;
+69
View File
@@ -0,0 +1,69 @@
import { DEFAULT_LAYOUT } from '../base';
import { AppRouteRecordRaw } from '../types';
import PageLayout from '@/layout/page-layout.vue';
// 设置
const SETTING: AppRouteRecordRaw = {
path: '/setting',
name: 'setting',
component: DEFAULT_LAYOUT,
redirect: '/setting/basic',
meta: {
locale: 'menu.setting',
requiresAuth: true,
order: 5,
// hideChildrenInMenu:true,
isFirst: true,
},
children: [
// 用户设置
{
path: 'user',
name: 'user-setting',
component: PageLayout,
meta: {
locale: 'menu.setting.user',
requiresAuth: true,
roles: ['admin'],
icon: 'icon-file',
},
children: [
// 用户列表
{
path: 'list',
name: 'setting-user-list',
component: () => import('@/views/setting/user/list.vue'),
meta: {
locale: 'menu.setting.user-list',
requiresAuth: true,
roles: ['*'],
},
},
// 角色权限
{
path: 'role',
name: 'setting-user-role',
component: () => import('@/views/setting/user/role.vue'),
meta: {
locale: 'menu.setting.role',
requiresAuth: true,
roles: ['*'],
},
},
],
},
// 系统配置
{
path: 'system',
name: 'system-setting',
component: () => import('@/views/setting/system/index.vue'),
meta: {
locale: 'menu.setting.system',
requiresAuth: true,
roles: ['admin'],
icon: 'icon-settings',
},
},
],
};
export default SETTING;
+22
View File
@@ -0,0 +1,22 @@
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 & {
permissionKey?: string; // 权限标识,与接口返回的perm_key对应
};
redirect?: string;
component: Component | string;
children?: AppRouteRecordRaw[];
alias?: string | string[];
props?: Record<string, any>;
beforeEnter?: NavigationGuard | NavigationGuard[];
fullPath?: string;
}
+16
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
}
}