Bläddra i källkod

release: 2.2.0

kailong321200875 1 år sedan
förälder
incheckning
d34217f594
7 ändrade filer med 306 tillägg och 83 borttagningar
  1. 21 1
      .vitepress/config.js
  2. 58 0
      components/json-editor.md
  3. 1 1
      group/group.md
  4. 66 71
      guide/auth.md
  5. 1 10
      guide/mock.md
  6. 51 0
      hooks/useStorage.md
  7. 108 0
      hooks/useTagsView.md

+ 21 - 1
.vitepress/config.js

@@ -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',
+        },
       ],
     },
     {
@@ -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/': [
       {
@@ -212,7 +228,7 @@ function createSidebar() {
             link: '/components/icon',
           },
           {
-            text: 'Permission 权限组件',
+            text: 'Permission 权限组件(2.1.0+)',
             link: '/components/permission',
           },
         ],
@@ -284,6 +300,10 @@ function createSidebar() {
             text: 'Footer 页脚组件',
             link: '/components/footer',
           },
+          {
+            text: 'JsonEditor JSON编辑器组件(2.2.0+)',
+            link: '/components/json-editor',
+          },
         ],
       },
       {

+ 58 - 0
components/json-editor.md

@@ -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)

+ 1 - 1
group/group.md

@@ -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" />

+ 66 - 71
guide/auth.md

@@ -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` 添加到路由实例,实现权限的动态生成。
+
+**优点:** 所有的菜单控制都是通过服务端的接口返回,前端只负责渲染,后期维护成本降低,优先推荐此方式。
 
 ## 实现
 
@@ -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)
@@ -50,10 +48,10 @@ generateRoutes(
     this.addRouters = routerMap.concat([
       {
         path: '/:path(.*)*',
-         redirect: '/404',
+        redirect: '/404',
         name: '404Page',
         meta: {
-           hidden: true,
+          hidden: true,
           breadcrumb: false
         }
       }
@@ -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;
+};
 ```
 
 ### 公用部分修改
@@ -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) // 动态添加可访问路由表

+ 1 - 10
guide/mock.md

@@ -127,16 +127,7 @@ export const delTableListApi = (ids: string[] | number[]): Promise<IResponse> =>
 
 ## axios 配置
 
-**axios** 请求封装存放于 [src/hooks/web/useAxios.ts](https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/hooks/web/useAxios.ts) 中。
-
-其他的配置,则存放在 [src/config/axios/](https://github.com/kailong321200875/vue-element-plus-admin/tree/master/src/config/axios)
-
-```js
-
-├── config.ts // axios 配置
-├── index.ts // 接口返回统一处理
-
-```
+**axios** 请求封装存放于 [src/config/axios](https://github.com/kailong321200875/vue-element-plus-admin/blob/master/src/config/axios) 中。
 
 ### config.ts 配置说明
 

+ 51 - 0
hooks/useStorage.md

@@ -0,0 +1,51 @@
+# useStorage(2.1.0+)
+
+用于操作 localStorage 和 sessionStorage
+
+useStorage 位于 [src/hooks/web/useStorage.ts](https://github.com/kailong321200875/vue-element-plus-admin/tree/master/src/hooks/web/useStorage(2.ts)
+
+默认使用 `sessionStorage`,如需要使用 `localStorage` ,只需要传入 `localStorage` 即可,如:useStorage('localStorage')
+
+支持非字符串类型存取值
+
+## 用法
+
+```vue
+<script setup lang="ts">
+import { useStorage } from '@/hooks/web/useStorage'
+
+const { setStorage, getStorage, removeStorage, clear } = useStorage()
+
+setStorage('key', { name: 'Jok' })
+
+getStorage('key')
+
+removeStorage('key')
+
+clear()
+</script>
+
+```
+
+### 参数介绍
+
+```ts
+const { setStorage, getStorage, removeStorage, clear } = useStorage('localStorage')
+```
+
+**setStorage**
+
+`setStorage` 存储数据
+
+
+**getStorage**
+
+`getStorage` 获取某个存储数据
+
+**removeStorage**
+
+`removeStorage` 清除某个存储数据
+
+**clear**
+
+`clear` 清除所有缓存数据,如果需要排除某些数据,可以传入 excludes 来排除,如:clear(['key']),这样 `key` 就不会被清除

+ 108 - 0
hooks/useTagsView.md

@@ -0,0 +1,108 @@
+# useTagsView(2.1.0+)
+
+操作标签页
+
+useTagsView 位于 [src/hooks/web/useTagsView.ts](https://github.com/kailong321200875/vue-element-plus-admin/tree/master/src/hooks/web/useTagsView.ts)
+
+## 用法
+
+```vue
+<script setup lang="ts">
+<script setup lang="ts">
+import { ContentWrap } from '@/components/ContentWrap'
+import { ElButton } from 'element-plus'
+import { useTagsView } from '@/hooks/web/useTagsView'
+import { useRouter } from 'vue-router'
+
+const { push } = useRouter()
+
+const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage, setTitle } =
+  useTagsView()
+
+const closeAllTabs = () => {
+  closeAll(() => {
+    push('/dashboard/analysis')
+  })
+}
+
+const closeLeftTabs = () => {
+  closeLeft()
+}
+
+const closeRightTabs = () => {
+  closeRight()
+}
+
+const closeOtherTabs = () => {
+  closeOther()
+}
+
+const refresh = () => {
+  refreshPage()
+}
+
+const closeCurrentTab = () => {
+  closeCurrent(undefined, () => {
+    push('/dashboard/analysis')
+  })
+}
+
+const setTabTitle = () => {
+  setTitle(new Date().getTime().toString())
+}
+
+const setAnalysisTitle = () => {
+  setTitle(`分析页-${new Date().getTime().toString()}`, '/dashboard/analysis')
+}
+</script>
+
+<template>
+  <ContentWrap title="useTagsView">
+    <ElButton type="primary" @click="closeAllTabs"> 关闭所有标签页 </ElButton>
+    <ElButton type="primary" @click="closeLeftTabs"> 关闭左侧标签页 </ElButton>
+    <ElButton type="primary" @click="closeRightTabs"> 关闭右侧标签页 </ElButton>
+    <ElButton type="primary" @click="closeOtherTabs"> 关闭其他标签页 </ElButton>
+    <ElButton type="primary" @click="closeCurrentTab"> 关闭当前标签页 </ElButton>
+    <ElButton type="primary" @click="refresh"> 刷新当前标签页 </ElButton>
+    <ElButton type="primary" @click="setTabTitle"> 修改当前标题 </ElButton>
+    <ElButton type="primary" @click="setAnalysisTitle"> 修改分析页标题 </ElButton>
+  </ContentWrap>
+</template>
+
+</script>
+
+```
+
+### 参数介绍
+
+```ts
+const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage, setTitle } = useTagsView()
+```
+
+**closeAll**
+
+`closeAll` 用于关闭所有标签页
+
+**closeLeft**
+
+`closeLeft` 用于关闭当前左侧标签页
+
+**closeRight**
+
+`closeRight` 用于关闭当前右侧标签页
+
+**closeOther**
+
+`closeOther` 用于关闭除当前标签页外的所有标签页
+
+**closeCurrent**
+
+`closeCurrent` 用于关闭除当前标签页
+
+**refreshPage**
+
+`refreshPage` 用于刷新当前标签页
+
+**setTitle**
+
+`setTitle(title: string, path: string)` 用于设置某个标签页的标签,接收 标题和一个完整的path路径