文章目录
目录结构
├── public
│ └── logo.png # LOGO
| └── index.html # Vue 入口模板
├── src
│ ├── api # Api ajax 等
│ ├── assets # 本地静态资源
│ ├── config # 项目基础配置,包含路由,全局设置
│ ├── components # 业务通用组件
│ ├── core # 项目引导, 全局配置初始化,依赖包引入等
│ ├── router # Vue-Router
│ ├── store # Vuex
│ ├── utils # 工具库
│ ├── locales # 国际化资源
│ ├── views # 业务页面入口和常用模板
│ ├── App.vue # Vue 模板入口
│ └── main.js # Vue 入口 JS
│ └── permission.js # 路由守卫(路由权限控制)
│ └── global.less # 全局样式
├── tests # 测试工具
├── README.md
└── package.json
路线 app -> router-view -> Layout -> basic-layout -> router-view -> RouteView
App.vue
组件
- ConfigProvider 全局化配置 国际化切换语言入口
- router-view 全局页面入口
做了哪些事
-
menuState 取缓存的菜单状态并初始化
-
lang 语言初始化(默认英语)
-
theme 主题初始化
- 通过给 html 标签添加属性
data-pro-theme
- 通过给 html 标签添加属性
-
将
isMobile
、realDark
、menuState
向下传递
router 入口
文件
- staticRoutes 静态路由(不需要权限)
- AsyncWorkplace 登录后首页(仪表盘)
- createRouter 创建 router 实例并设置 history 模式
做了哪些事
-
defineAsyncComponent 同步渲染仪表盘组件, 其余组件均为异步加载
-
staticRoutes 登录白名单
- login 登录页
- register 注册页
- register-result 注册结果页
- 404 404 页
-
routes 需要权限处理的页面
Layout
组件
WaterMark
水印LeftMenuLayout
一二级分栏展示BasicLayout
一二同栏展示
做了哪些事
<water-mark :disabled="true"></water-mark>
控制是否禁止显示水印<left-menu-layout></left-menu-layout>
显示一二级分栏
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x5RUXSMg-1628669727112)(9C7F378C627548B2AB20C3797EB0CEF8)]<basic-layout ></basic-layout>
显示一二级同栏 目前一般使用的基础布局
basic-layout
组件
pro-provider
pro 容器sider-menu
左侧菜单组件a-drawer
左侧菜单放在抽屉里HeaderView
头部组件multi-tab
快捷 tabrouter-view
视图wrap-content
主内容容器
做了哪些事
-
pro-provider 布局宽度/语言
-
sider-menu 左侧菜单
-
a-drawer 移动端
-
header-view 头部组件
- notice-icon 消息通知组件
- avatar-dropdown 个人信息操作
- select-lang 切换语言组件
-
wrap-content 添加 class 类名
route-view
组件
transition
动画multi-tab-store-consumer
创建消费端
做了哪些事
- multi-tab-store-consumer
- 取缓存路由
- 保存路由组件
- 合并页面组件
登录鉴权
main.ts
- 引入了
import './router/router-guards';
/router/router-guards
- router.beforeEach 全局前置守卫
- 是否登录 --> 已登录 --> 除了免权限白名单都跳登录页
- 是否登录 --> 未登录 --> 查看是否已经生成权限表
- 已生成直接跳转
- 未生成采用静态路由或动态路由
- 登录 -> access token -> localstorage -> 通过 token 获取 user 信息包括 user 权限 -> user 权限和本地路由通过对比生成权限树或服务端返回完整的权限树 -> token 过期 -> 重新登录
登录
-
handleSubmit 登录确定
- 验证对应的登录方式
- 调用 store.dispatch(
user/${LOGIN}
, { …values}) - 跳转到
/
-
获取 token
store/user/actions 登录函数
[LOGIN]({ commit }, info) {
return new Promise((resolve, reject) => {
// call ajax
postAccountLogin(info)
.then(res => {
// 将token储存到state里并且缓存到local-storage
commit(SET_TOKEN, res.token);
resolve(res);
})
.catch(error => {
reject(error);
});
});
}
- 路由守卫拦截
跳转登录成功后跳转 ‘/’,
/router/router-guards
会进行拦截
// 获取用户信息(包含权限)
const info = await store.dispatch(`user/${GET_INFO}`);
const allowRouters = await store.dispatch(`user/${GENERATE_ROUTES}`, info);
// const allowRouters = store.dispatch(`user/${GENERATE_ROUTES_DYNAMIC}`, info);
- 获取用户信息并存储用户信息
store/user/actions
// store/user/actions [GET_INFO] 获取用户信息
[GET_INFO]({ commit }) {
return new Promise((resolve, reject) => {
getCurrentUser()
.then((res: UserInfo) => {
// 将用户信息存储到state里
commit(SET_INFO, res);
resolve(res);
})
.catch(err => {
// 获取登录用户信息后,直接清理掉当前 token 并强制让流程走到登录页
commit(SET_TOKEN, null);
reject(err);
});
});
},
// store/user/mutations [SET_INFO] 设置用户信息
[SET_INFO]: (state, info) => {
if (info.role) {
state.role = info.role;
}
if (info.userid) {
state.username = info.userid;
state.nickname = info.userid;
}
if (info.name) {
state.nickname = info.name;
}
if (info.avatar) {
state.avatar = info.avatar;
}
state.extra = { ...info };
},
静态构建路由权限管理
- 处理用户权限
import { hasAuthority, filterChildRoute } from '@/utils/authority';
// store/user/actions [GENERATE_ROUTES] 从路由表构建路由
// 从路由表构建路由(前端对比后端权限字段过滤静态路由表)
[GENERATE_ROUTES]({ commit }, info: UserInfo) {
return new Promise<RouteRecordRaw[]>(resolve => {
// 修改这里可以进行接口返回的对象结构进行改变
// 亦或是去掉 info.role 使用别的属性替代
// 任何方案都可以,只需要将最后拼接构建好的路由数组使用
// router.addRoute() 添加到当前运行时的路由中即可
const { permissions } = (info.role || {}) as Role;
// 返回index下children路由
const allRoutes = filterMenu(routes);
// permissionsKey = ['home','form']
const permissionsKey = permissions?.map(permission => permission.name);
/**
* 先处理一层
* 如果这层有children的话,就要递归处理对应权限,返回用户权限对应的路由
*/
const allowRoutes = !permissionsKey
? allRoutes
: allRoutes.filter(route => {
// parnent route filter
const hasAllow = hasAuthority(route as MenuDataItem, permissionsKey!);
if (hasAllow && route.children && route.children.length > 0) {
// current route children filter
route.children = filterChildRoute(route as MenuDataItem, permissionsKey!);
}
return hasAllow;
});
// 解构路由表 _代表的是index下的children, mainRoute是index路由
const {
// eslint-disable-next-line
children: _,
...mainRoute
} = routes[0];
const route = {
...mainRoute,
children: allowRoutes,
};
// 当采用动态路由时,/workplace可能不是重定向首页,解决原pro-vip框架跳转错误问题
route.redirect = allowRoutes[0]?.path;
// 添加动态路由
router.addRoute(route);
// 存储用户权限匹配出来的路由
commit(SET_ROUTERS, allowRoutes);
resolve(allowRoutes);
});
},
- 获取 index 下 children 全部路由
@/utils/menu-util
filterMenu
export const filterMenu = (routes: MenuDataItem[]): RouteRecordRaw[] => {
return routes.find((item) => item.name === "index")?.children || [];
};
- 具体处理权限函数
// @/utils/authority
import { MenuDataItem } from '@/router/typing';
export const filterChildRoute = (route: MenuDataItem, permissions: string[]) =>
route.children
?.filter(item => {
// 递归遍历返回与权限相符的路由
const hasAllow = hasAuthority(item, permissions);
if (hasAllow && item.children && item.children.length > 0) {
item.children = filterChildRoute(item, permissions!);
}
return hasAllow;
})
.filter(item => item);
// permissions: Permission[]
export const hasAuthority = (route: MenuDataItem, permissions: string[]) => {
if (route.meta?.authority) {
return permissions.some(value => {
return route.meta?.authority?.includes(value);
});
}
// 如true 返回: 没有authority属性的路由 + 符合权限的路由, false 返回 有authority属性的路由并且符合权限的路由
return true;
};
- 缓存路由
[SET_ROUTERS]: (state, allowRoutes) => {
state.allowRouters = allowRoutes;
},
动态构建路由权限管理
-
获取用户信息
-
获取接口数据构建前端路由
// store/user/actions [GENERATE_ROUTES_DYNAMIC] 从路由表构建路由
// 从后端获取路由表结构体,并构建前端路由
[GENERATE_ROUTES_DYNAMIC]({ commit }) {
return new Promise<RouteRecordRaw>(resolve => {
generatorDynamicRouter()
.then((routes: RouteRecordRaw) => {
const allowRoutes = routes.children || [];
// 添加到路由表
router.addRoute(routes);
commit(SET_ROUTERS, allowRoutes);
resolve(routes);
})
.catch(err => {
console.error('generatorDynamicRouter', err);
});
});
},
- 缓存路由
[SET_ROUTERS]: (state, allowRoutes) => {
state.allowRouters = allowRoutes;
},