Skip to content

Commit

Permalink
release: 2.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
kailong321200875 committed Aug 27, 2023
1 parent af8d8ba commit d34217f
Show file tree
Hide file tree
Showing 7 changed files with 306 additions and 83 deletions.
22 changes: 21 additions & 1 deletion .vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ function createNav() {
text: 'useCrudSchemas',
link: '/hooks/useCrudSchemas',
},
{
text: 'useTagsView(2.1.0+)',
link: '/hooks/useTagsView',
},
{
text: 'useStorage(2.1.0+)',
link: '/hooks/useStorage',
},
],
},
{
Expand Down Expand Up @@ -193,6 +201,14 @@ function createSidebar() {
text: 'useCrudSchemas',
link: '/hooks/useCrudSchemas',
},
{
text: 'useTagsView(2.1.0+)',
link: '/hooks/useTagsView',
},
{
text: 'useStorage(2.1.0+)',
link: '/hooks/useStorage',
},
],
'/components/': [
{
Expand All @@ -212,7 +228,7 @@ function createSidebar() {
link: '/components/icon',
},
{
text: 'Permission 权限组件',
text: 'Permission 权限组件(2.1.0+)',
link: '/components/permission',
},
],
Expand Down Expand Up @@ -284,6 +300,10 @@ function createSidebar() {
text: 'Footer 页脚组件',
link: '/components/footer',
},
{
text: 'JsonEditor JSON编辑器组件(2.2.0+)',
link: '/components/json-editor',
},
],
},
{
Expand Down
58 changes: 58 additions & 0 deletions components/json-editor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# JsonEditor JSON编辑器组件(2.2.0+)

基于 [vue-json-pretty](https://leezng.github.io/vue-json-pretty/) 封装。

可自行阅读 [vue-json-pretty文档](https://github.com/leezng/vue-json-pretty)

JsonEditor 组件位于 [src/components/JsonEditor](https://github.com/kailong321200875/vue-element-plus-admin/tree/master/src/components/JsonEditor)

## 用法

```vue
<script setup lang="ts">
<script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap'
import { JsonEditor } from '@/components/JsonEditor'
import { useI18n } from '@/hooks/web/useI18n'
import { ref, watch } from 'vue'
const { t } = useI18n()
const defaultData = ref({
title: '标题',
content: '内容'
})
watch(
() => defaultData.value,
(val) => {
console.log(val)
},
{
deep: true
}
)
setTimeout(() => {
defaultData.value = {
title: '异步标题',
content: '异步内容'
}
}, 4000)
</script>
<template>
<ContentWrap :title="t('richText.jsonEditor')" :message="t('richText.jsonEditorDes')">
<JsonEditor v-model="defaultData" />
</ContentWrap>
</template>
```

## JsonEditor 属性

可查看 [vue-json-pretty文档](https://github.com/leezng/vue-json-pretty)

## Editor 事件

可查看 [vue-json-pretty文档](https://github.com/leezng/vue-json-pretty)
2 changes: 1 addition & 1 deletion group/group.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

仅供技术交流

<img src = "https://github.com/kailong321200875/my-image/raw/master/chat-0828.jpg" />
<img src = "https://github.com/kailong321200875/my-image/raw/master/chat-0903.jpg" />
137 changes: 66 additions & 71 deletions guide/auth.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
# 权限

项目中集成了2种权限处理方式
项目中集成了 2 种权限处理方式

1. 通过用户角色来过滤菜单(前端方式控制),菜单由路由配置自动生成
2. 通过后台来动态生成路由表(服务端方式控制)
1. 第一种是由前端来控制菜单,即服务端只返回有权限的 keys,由前端自行去匹配
2. 第二种是通过服务端返回的路由数据结构来动态生成路由表

目前项目中提供了两种不同方式的帐号
目前项目中提供了测试的帐号

**admin/admin test/test**

`admin` 帐号用于模拟服务端控制权限,服务端返回什么就渲染什么

`test` 帐号用于模拟前端控制权限,服务端只返回需要显示的菜单 key,前端进行匹配渲染
**admin/admin**

## 前端控制权限

**实现原理:** 在前端固定写死路由的权限,指定路由有哪些权限可以查看。只初始化通用的路由,需要权限才能访问的路由没有被加入路由表内。在登陆后或者其他方式获取用户角色后,通过角色去遍历路由表,获取该角色可以访问的路由表,生成路由表,再通过 `router.addRoutes` 添加到路由实例,实现权限的过滤。
**实现原理** 在前端固定写死路由的权限,指定路由有哪些权限可以查看。只初始化通用的路由,需要权限才能访问的路由没有被加入路由表内。在登陆后或者其他方式获取对应的路由 keys 后,遍历路由表去匹配 keys,过滤生成可以访问的路由表,再通过 `router.addRoutes` 添加到路由实例,实现权限的过滤。

**缺点:** 权限相对不自由,如果服务端改动角色,前端也需要跟着改动,并且排序什么的都需要前端控制。
**缺点** 权限相对不自由,因为路由表的控制在前端,不管是要排序还是修改,都需要前端去修改,服务端只提供有权限的路由 keys

## 后台动态获取

**实现原理:** 是通过接口动态生成路由表,且遵循一定的数据结构返回。前端根据需要处理该数据为可识别的结构,再通过 `router.addRoutes` 添加到路由实例,实现权限的动态生成。
**实现原理:** 是通过接口动态生成路由表,且遵循一定的数据结构返回。前端根据需要处理该数据为可识别的结构,再通过 `router.addRoutes` 添加到路由实例,实现权限的动态生成。

**优点:** 所有的菜单控制都是通过服务端的接口返回,前端只负责渲染,后期维护成本降低,优先推荐此方式。

## 实现

Expand All @@ -31,17 +29,17 @@

```ts
generateRoutes(
type: 'admin' | 'test' | 'none',
type: 'server' | 'frontEnd' | 'static',
routers?: AppCustomRouteRecordRaw[] | string[]
): Promise<unknown> {
return new Promise<void>((resolve) => {
let routerMap: AppRouteRecordRaw[] = []
if (type === 'admin') {
if (type === 'server') {
// 模拟后端过滤菜单
routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[])
} else if (type === 'test') {
routerMap = generateRoutesByServer(routers as AppCustomRouteRecordRaw[])
} else if (type === 'frontEnd') {
// 模拟前端过滤菜单
routerMap = generateRoutesFn1(cloneDeep(asyncRouterMap), routers as string[])
routerMap = generateRoutesByFrontEnd(cloneDeep(asyncRouterMap), routers as string[])
} else {
// 直接读取静态路由表
routerMap = cloneDeep(asyncRouterMap)
Expand All @@ -50,10 +48,10 @@ generateRoutes(
this.addRouters = routerMap.concat([
{
path: '/:path(.*)*',
redirect: '/404',
redirect: '/404',
name: '404Page',
meta: {
hidden: true,
hidden: true,
breadcrumb: false
}
}
Expand All @@ -67,95 +65,96 @@ generateRoutes(

### 前端控制实现

2.[src/utils/routerHelper.ts](https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/utils/routerHelper.ts)`generateRoutesFn1()` 进行更改。目前本项目的前端权限控制,是根据 `path` 是否相同来进行过滤演示的,如果不符合需求,需要手动更改以下判断逻辑。
2.[src/utils/routerHelper.ts](https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/utils/routerHelper.ts)`generateRoutesByFrontEnd ()` 进行更改。目前本项目的前端权限控制,是根据 `path` 是否相同来进行过滤演示的,如果不符合需求,需要手动更改以下判断逻辑。

```ts
// 前端控制路由生成
export const generateRoutesFn1 = (
export const generateRoutesByFrontEnd = (
routes: AppRouteRecordRaw[],
keys: string[],
basePath = '/'
): AppRouteRecordRaw[] => {
const res: AppRouteRecordRaw[] = []
const res: AppRouteRecordRaw[] = [];

for (const route of routes) {
const meta = route.meta as RouteMeta
const meta = route.meta as RouteMeta;
// skip some route
if (meta.hidden && !meta.showMainRoute) {
continue
continue;
}

let data: Nullable<AppRouteRecordRaw> = null
let data: Nullable<AppRouteRecordRaw> = null;

let onlyOneChild: Nullable<string> = null
let onlyOneChild: Nullable<string> = null;
if (route.children && route.children.length === 1 && !meta.alwaysShow) {
onlyOneChild = (
isUrl(route.children[0].path)
? route.children[0].path
: pathResolve(pathResolve(basePath, route.path), route.children[0].path)
) as string
) as string;
}

// 开发者可以根据实际情况进行扩展
for (const item of keys) {
// 通过路径去匹配
if (isUrl(item) && (onlyOneChild === item || route.path === item)) {
data = Object.assign({}, route)
data = Object.assign({}, route);
} else {
const routePath = pathResolve(basePath, onlyOneChild || route.path)
const routePath = pathResolve(basePath, onlyOneChild || route.path);
if (routePath === item || meta.followRoute === item) {
data = Object.assign({}, route)
data = Object.assign({}, route);
}
}
}

// recursive child routes
if (route.children && data) {
data.children = generateRoutesFn1(route.children, keys, pathResolve(basePath, data.path))
data.children = generateRoutesByFrontEnd (route.children, keys, pathResolve(basePath, data.path));
}
if (data) {
res.push(data as AppRouteRecordRaw)
res.push(data as AppRouteRecordRaw);
}
}
return res
}
return res;
};
```

### 后台动态获取

3.[src/utils/routerHelper.ts](https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/utils/routerHelper.ts)`generateRoutesFn2()` 进行更改。
3.[src/utils/routerHelper.ts](https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/utils/routerHelper.ts)`generateRoutesByServer ()` 进行更改。

```ts
// 后端控制路由生成
export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
const res: AppRouteRecordRaw[] = []
export const generateRoutesByServer = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
const res: AppRouteRecordRaw[] = [];

for (const route of routes) {
const data: AppRouteRecordRaw = {
path: route.path,
name: route.name,
redirect: route.redirect,
meta: route.meta
}
meta: route.meta,
};
if (route.component) {
const comModule = modules[`../${route.component}.vue`] || modules[`../${route.component}.tsx`]
const component = route.component as string
const comModule =
modules[`../${route.component}.vue`] || modules[`../${route.component}.tsx`];
const component = route.component as string;
if (!comModule && !component.includes('#')) {
console.error(`未找到${route.component}.vue文件或${route.component}.tsx文件,请创建`)
console.error(`未找到${route.component}.vue文件或${route.component}.tsx文件,请创建`);
} else {
// 动态加载路由文件,可根据实际情况进行自定义逻辑
data.component =
component === '#' ? Layout : component.includes('##') ? getParentLayout() : comModule
component === '#' ? Layout : component.includes('##') ? getParentLayout() : comModule;
}
}
// recursive child routes
if (route.children) {
data.children = generateRoutesFn2(route.children)
data.children = generateRoutesByServer (route.children);
}
res.push(data as AppRouteRecordRaw)
res.push(data as AppRouteRecordRaw);
}
return res
}
return res;
};
```

### 公用部分修改
Expand All @@ -167,48 +166,44 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
```ts
// 获取角色信息
const getRole = async () => {
// 模拟获取角色信息,开发者需要自行更改以下逻辑。
const { getFormData } = methods
const formData = await getFormData<UserLoginType>()
const formData = await getFormData<UserType>()
const params = {
roleName: formData.username
}
// admin - 模拟后端过滤菜单
// test - 模拟前端过滤菜单
const res =
formData.username === 'admin'
? await getAdminRoleApi({ params })
: await getTestRoleApi({ params })
appStore.getDynamicRouter && appStore.getServerDynamicRouter
? await getAdminRoleApi(params)
: await getTestRoleApi(params)
if (res) {
const { wsCache } = useCache()
const routers = res.data.list || []
wsCache.set('roleRouters', routers)

// 不管最终要使用什么方式进行权限过滤,都需要调用 permissionStore.generateRoutes()
formData.username === 'admin'
? await permissionStore.generateRoutes('admin', routers).catch(() => {})
: await permissionStore.generateRoutes('test', routers).catch(() => {})
const routers = res.data || []
setStorage('roleRouters', routers)
appStore.getDynamicRouter && appStore.getServerDynamicRouter
? await permissionStore.generateRoutes('server', routers).catch(() => {})
: await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})

// 过滤完路由之后,需要动态注册路由
permissionStore.getAddRouters.forEach((route) => {
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
})
permissionStore.setIsAddRouters(true)
push({ path: redirect.value || permissionStore.addRouters[0].path })
}
}
};
```

5.[src/permission.ts](https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/permission.ts),以下这种情况,是考虑到手动刷新,所以需要获取到缓存中的动态菜单重新渲染。

```ts
// 开发者可根据实际情况进行修改
const roleRouters = wsCache.get('roleRouters') || []
const userInfo = wsCache.get(appStore.getUserInfo)

userInfo.role === 'admin'
? await permissionStore.generateRoutes('admin', roleRouters as AppCustomRouteRecordRaw[])
: await permissionStore.generateRoutes('test', roleRouters as string[])
const roleRouters = getStorage('roleRouters') || []

// 是否使用动态路由
if (appStore.getDynamicRouter) {
appStore.serverDynamicRouter
? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
: await permissionStore.generateRoutes('frontEnd', roleRouters as string[])
} else {
await permissionStore.generateRoutes('static')
}

permissionStore.getAddRouters.forEach((route) => {
router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表
Expand Down
Loading

0 comments on commit d34217f

Please sign in to comment.