mirror of
				https://github.com/pure-admin/vue-pure-admin.git
				synced 2025-11-03 13:44:47 +08:00 
			
		
		
		
	refactor: permission (#357)
* refactor: permission * chore: update * chore: update * chore: update * chore: update * chore: update * chore: update * chore: update * chore: update * chore: update * fix: 修复`mix`混合模式导航在生产环境左侧菜单一定机率不显示的问题 * chore: update * chore: update * chore: update * chore: update * chore: update * chore: update * chore: update * chore: update
This commit is contained in:
		
							parent
							
								
									cedc84d31a
								
							
						
					
					
						commit
						6ef4cf9fb6
					
				@ -32,7 +32,7 @@ menus:
 | 
			
		||||
  hsRole: Role Manage
 | 
			
		||||
  hsDept: Dept Manage
 | 
			
		||||
  hseditor: Editor
 | 
			
		||||
  hserror: Error Page
 | 
			
		||||
  hsabnormal: Abnormal Page
 | 
			
		||||
  hsfourZeroFour: "404"
 | 
			
		||||
  hsfourZeroOne: "403"
 | 
			
		||||
  hsFive: "500"
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ menus:
 | 
			
		||||
  hsRole: 角色管理
 | 
			
		||||
  hsDept: 部门管理
 | 
			
		||||
  hseditor: 编辑器
 | 
			
		||||
  hserror: 错误页面
 | 
			
		||||
  hsabnormal: 异常页面
 | 
			
		||||
  hsfourZeroFour: "404"
 | 
			
		||||
  hsfourZeroOne: "403"
 | 
			
		||||
  hsFive: "500"
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,12 @@
 | 
			
		||||
// 根据角色动态生成路由
 | 
			
		||||
// 模拟后端动态生成路由
 | 
			
		||||
import { MockMethod } from "vite-plugin-mock";
 | 
			
		||||
 | 
			
		||||
// http://mockjs.com/examples.html#Object
 | 
			
		||||
/**
 | 
			
		||||
 * roles:页面级别权限,这里模拟二种 "admin"、"common"
 | 
			
		||||
 * admin:管理员角色
 | 
			
		||||
 * common:普通角色
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const systemRouter = {
 | 
			
		||||
  path: "/system",
 | 
			
		||||
  meta: {
 | 
			
		||||
@ -15,7 +20,8 @@ const systemRouter = {
 | 
			
		||||
      name: "User",
 | 
			
		||||
      meta: {
 | 
			
		||||
        icon: "flUser",
 | 
			
		||||
        title: "menus.hsUser"
 | 
			
		||||
        title: "menus.hsUser",
 | 
			
		||||
        roles: ["admin"]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@ -23,7 +29,8 @@ const systemRouter = {
 | 
			
		||||
      name: "Role",
 | 
			
		||||
      meta: {
 | 
			
		||||
        icon: "role",
 | 
			
		||||
        title: "menus.hsRole"
 | 
			
		||||
        title: "menus.hsRole",
 | 
			
		||||
        roles: ["admin"]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@ -31,7 +38,8 @@ const systemRouter = {
 | 
			
		||||
      name: "Dept",
 | 
			
		||||
      meta: {
 | 
			
		||||
        icon: "dept",
 | 
			
		||||
        title: "menus.hsDept"
 | 
			
		||||
        title: "menus.hsDept",
 | 
			
		||||
        roles: ["admin"]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@ -41,7 +49,8 @@ const systemRouter = {
 | 
			
		||||
      meta: {
 | 
			
		||||
        icon: "dict",
 | 
			
		||||
        title: "menus.hsDict",
 | 
			
		||||
        keepAlive: true
 | 
			
		||||
        keepAlive: true,
 | 
			
		||||
        roles: ["admin"]
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
@ -52,13 +61,14 @@ const permissionRouter = {
 | 
			
		||||
  meta: {
 | 
			
		||||
    title: "menus.permission",
 | 
			
		||||
    icon: "lollipop",
 | 
			
		||||
    rank: 7
 | 
			
		||||
    rank: 10
 | 
			
		||||
  },
 | 
			
		||||
  children: [
 | 
			
		||||
    {
 | 
			
		||||
      path: "/permission/page/index",
 | 
			
		||||
      name: "PermissionPage",
 | 
			
		||||
      meta: {
 | 
			
		||||
        roles: ["admin", "common"],
 | 
			
		||||
        title: "menus.permissionPage"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
@ -67,7 +77,8 @@ const permissionRouter = {
 | 
			
		||||
      name: "PermissionButton",
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: "menus.permissionButton",
 | 
			
		||||
        authority: []
 | 
			
		||||
        roles: ["admin", "common"],
 | 
			
		||||
        auths: ["btn_add", "btn_edit", "btn_delete"]
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
@ -78,7 +89,7 @@ const frameRouter = {
 | 
			
		||||
  meta: {
 | 
			
		||||
    icon: "monitor",
 | 
			
		||||
    title: "menus.hsExternalPage",
 | 
			
		||||
    rank: 10
 | 
			
		||||
    rank: 7
 | 
			
		||||
  },
 | 
			
		||||
  children: [
 | 
			
		||||
    {
 | 
			
		||||
@ -86,14 +97,16 @@ const frameRouter = {
 | 
			
		||||
      name: "FramePure",
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: "menus.hsPureDocument",
 | 
			
		||||
        frameSrc: "http://yiming_chang.gitee.io/pure-admin-doc"
 | 
			
		||||
        frameSrc: "http://yiming_chang.gitee.io/pure-admin-doc",
 | 
			
		||||
        roles: ["admin", "common"]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: "/external",
 | 
			
		||||
      name: "http://yiming_chang.gitee.io/pure-admin-doc",
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: "menus.externalLink"
 | 
			
		||||
        title: "menus.externalLink",
 | 
			
		||||
        roles: ["admin", "common"]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@ -101,7 +114,8 @@ const frameRouter = {
 | 
			
		||||
      name: "FrameEp",
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: "menus.hsEpDocument",
 | 
			
		||||
        frameSrc: "https://element-plus.org/zh-CN/"
 | 
			
		||||
        frameSrc: "https://element-plus.org/zh-CN/",
 | 
			
		||||
        roles: ["admin", "common"]
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
@ -119,7 +133,8 @@ const tabsRouter = {
 | 
			
		||||
      path: "/tabs/index",
 | 
			
		||||
      name: "Tabs",
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: "menus.hstabs"
 | 
			
		||||
        title: "menus.hstabs",
 | 
			
		||||
        roles: ["admin", "common"]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@ -127,7 +142,8 @@ const tabsRouter = {
 | 
			
		||||
      name: "TabQueryDetail",
 | 
			
		||||
      meta: {
 | 
			
		||||
        // 不在menu菜单中显示
 | 
			
		||||
        showLink: false
 | 
			
		||||
        showLink: false,
 | 
			
		||||
        roles: ["admin", "common"]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@ -135,39 +151,22 @@ const tabsRouter = {
 | 
			
		||||
      component: "params-detail",
 | 
			
		||||
      name: "TabParamsDetail",
 | 
			
		||||
      meta: {
 | 
			
		||||
        showLink: false
 | 
			
		||||
        showLink: false,
 | 
			
		||||
        roles: ["admin", "common"]
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 添加不同按钮权限到/permission/button页面中
 | 
			
		||||
function setDifAuthority(authority, routes) {
 | 
			
		||||
  routes.children[1].meta.authority = [authority];
 | 
			
		||||
  return routes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default [
 | 
			
		||||
  {
 | 
			
		||||
    url: "/getAsyncRoutes",
 | 
			
		||||
    method: "get",
 | 
			
		||||
    response: ({ query }) => {
 | 
			
		||||
      if (query.name === "admin") {
 | 
			
		||||
    response: () => {
 | 
			
		||||
      return {
 | 
			
		||||
          code: 0,
 | 
			
		||||
          info: [
 | 
			
		||||
            tabsRouter,
 | 
			
		||||
            frameRouter,
 | 
			
		||||
            systemRouter,
 | 
			
		||||
            setDifAuthority("v-admin", permissionRouter)
 | 
			
		||||
          ]
 | 
			
		||||
        success: true,
 | 
			
		||||
        data: [systemRouter, permissionRouter, frameRouter, tabsRouter]
 | 
			
		||||
      };
 | 
			
		||||
      } else {
 | 
			
		||||
        return {
 | 
			
		||||
          code: 0,
 | 
			
		||||
          info: [tabsRouter, setDifAuthority("v-test", permissionRouter)]
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
] as MockMethod[];
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ export default [
 | 
			
		||||
    method: "post",
 | 
			
		||||
    response: () => {
 | 
			
		||||
      return {
 | 
			
		||||
        code: 0,
 | 
			
		||||
        success: true,
 | 
			
		||||
        data: {
 | 
			
		||||
          list: [
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										36
									
								
								mock/login.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								mock/login.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
// 根据角色动态生成路由
 | 
			
		||||
import { MockMethod } from "vite-plugin-mock";
 | 
			
		||||
 | 
			
		||||
export default [
 | 
			
		||||
  {
 | 
			
		||||
    url: "/login",
 | 
			
		||||
    method: "post",
 | 
			
		||||
    response: ({ body }) => {
 | 
			
		||||
      if (body.username === "admin") {
 | 
			
		||||
        return {
 | 
			
		||||
          success: true,
 | 
			
		||||
          data: {
 | 
			
		||||
            username: "admin",
 | 
			
		||||
            // 一个用户可能有多个角色
 | 
			
		||||
            roles: ["admin"],
 | 
			
		||||
            accessToken: "eyJhbGciOiJIUzUxMiJ9.admin",
 | 
			
		||||
            refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
 | 
			
		||||
            expires: "2023/10/30 00:00:00"
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
      } else {
 | 
			
		||||
        return {
 | 
			
		||||
          success: true,
 | 
			
		||||
          data: {
 | 
			
		||||
            username: "common",
 | 
			
		||||
            // 一个用户可能有多个角色
 | 
			
		||||
            roles: ["common"],
 | 
			
		||||
            accessToken: "eyJhbGciOiJIUzUxMiJ9.common",
 | 
			
		||||
            refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh",
 | 
			
		||||
            expires: "2023/10/30 00:00:00"
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
] as MockMethod[];
 | 
			
		||||
@ -29,8 +29,8 @@ export default [
 | 
			
		||||
    method: "get",
 | 
			
		||||
    response: () => {
 | 
			
		||||
      return {
 | 
			
		||||
        code: 0,
 | 
			
		||||
        info: mapList()
 | 
			
		||||
        success: true,
 | 
			
		||||
        data: mapList()
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								mock/refreshToken.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								mock/refreshToken.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
import { MockMethod } from "vite-plugin-mock";
 | 
			
		||||
 | 
			
		||||
// 模拟刷新token接口
 | 
			
		||||
export default [
 | 
			
		||||
  {
 | 
			
		||||
    url: "/refreshToken",
 | 
			
		||||
    method: "post",
 | 
			
		||||
    response: ({ body }) => {
 | 
			
		||||
      if (body.refreshToken) {
 | 
			
		||||
        return {
 | 
			
		||||
          success: true,
 | 
			
		||||
          data: {
 | 
			
		||||
            accessToken: "eyJhbGciOiJIUzUxMiJ9.admin",
 | 
			
		||||
            refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
 | 
			
		||||
            // `expires`选择这种日期格式是为了方便调试,后端直接设置时间戳或许更方便(每次都应该递增)。如果后端返回的是时间戳格式,前端开发请来到这个目录`src/utils/auth.ts`,把第`38`行的代码换成expires = data.expires即可。
 | 
			
		||||
            expires: "2023/10/30 23:59:59"
 | 
			
		||||
          }
 | 
			
		||||
        };
 | 
			
		||||
      } else {
 | 
			
		||||
        return {
 | 
			
		||||
          success: false,
 | 
			
		||||
          data: {}
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
] as MockMethod[];
 | 
			
		||||
@ -6,7 +6,7 @@ export default [
 | 
			
		||||
    method: "post",
 | 
			
		||||
    response: () => {
 | 
			
		||||
      return {
 | 
			
		||||
        code: 0,
 | 
			
		||||
        success: true,
 | 
			
		||||
        data: {
 | 
			
		||||
          list: [
 | 
			
		||||
            {
 | 
			
		||||
@ -71,7 +71,7 @@ export default [
 | 
			
		||||
    method: "post",
 | 
			
		||||
    response: () => {
 | 
			
		||||
      return {
 | 
			
		||||
        code: 0,
 | 
			
		||||
        success: true,
 | 
			
		||||
        data: [
 | 
			
		||||
          {
 | 
			
		||||
            name: "杭州总公司",
 | 
			
		||||
@ -212,7 +212,7 @@ export default [
 | 
			
		||||
    method: "post",
 | 
			
		||||
    response: () => {
 | 
			
		||||
      return {
 | 
			
		||||
        code: 0,
 | 
			
		||||
        success: true,
 | 
			
		||||
        data: {
 | 
			
		||||
          list: [
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@
 | 
			
		||||
    "@pureadmin/components": "^1.1.0",
 | 
			
		||||
    "@pureadmin/descriptions": "^1.1.0",
 | 
			
		||||
    "@pureadmin/table": "^1.2.0",
 | 
			
		||||
    "@pureadmin/utils": "^1.1.4",
 | 
			
		||||
    "@pureadmin/utils": "^1.1.5",
 | 
			
		||||
    "@vueuse/core": "^9.3.0",
 | 
			
		||||
    "@vueuse/motion": "^2.0.0-beta.12",
 | 
			
		||||
    "@vueuse/shared": "^9.3.0",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@ -22,7 +22,7 @@ specifiers:
 | 
			
		||||
  "@pureadmin/descriptions": ^1.1.0
 | 
			
		||||
  "@pureadmin/table": ^1.2.0
 | 
			
		||||
  "@pureadmin/theme": ^2.4.0
 | 
			
		||||
  "@pureadmin/utils": ^1.1.4
 | 
			
		||||
  "@pureadmin/utils": ^1.1.5
 | 
			
		||||
  "@types/element-resize-detector": 1.1.3
 | 
			
		||||
  "@types/js-cookie": ^3.0.1
 | 
			
		||||
  "@types/lodash": ^4.14.180
 | 
			
		||||
@ -132,7 +132,7 @@ dependencies:
 | 
			
		||||
  "@pureadmin/components": 1.1.0_vue@3.2.40
 | 
			
		||||
  "@pureadmin/descriptions": 1.1.0
 | 
			
		||||
  "@pureadmin/table": 1.2.0
 | 
			
		||||
  "@pureadmin/utils": 1.1.4_888d42e6b1d4aaf209a7326195b5949d
 | 
			
		||||
  "@pureadmin/utils": 1.1.5_888d42e6b1d4aaf209a7326195b5949d
 | 
			
		||||
  "@vueuse/core": 9.3.0_vue@3.2.40
 | 
			
		||||
  "@vueuse/motion": 2.0.0-beta.12_vue@3.2.40
 | 
			
		||||
  "@vueuse/shared": 9.3.0_vue@3.2.40
 | 
			
		||||
@ -1428,10 +1428,10 @@ packages:
 | 
			
		||||
      string-hash: 1.1.3
 | 
			
		||||
    dev: true
 | 
			
		||||
 | 
			
		||||
  /@pureadmin/utils/1.1.4_888d42e6b1d4aaf209a7326195b5949d:
 | 
			
		||||
  /@pureadmin/utils/1.1.5_888d42e6b1d4aaf209a7326195b5949d:
 | 
			
		||||
    resolution:
 | 
			
		||||
      {
 | 
			
		||||
        integrity: sha512-c3Zl9v6usKUqz6y8wYhk89g/hXz/I5QzHS7dTum8/YomqDMBph7c70u0J1dAgruDnEIIB2SNDuEWyGD8054WsQ==
 | 
			
		||||
        integrity: sha512-5nQZyFAbs59gkMBj0WLox7BlY7llILR/ENo2QNEKW6avMt8sDL1+858EFjEbELl6enPsVvJpoCTxatmZzVjyAw==
 | 
			
		||||
      }
 | 
			
		||||
    peerDependencies:
 | 
			
		||||
      dayjs: "*"
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,11 @@
 | 
			
		||||
import { http } from "../utils/http";
 | 
			
		||||
 | 
			
		||||
type Result = {
 | 
			
		||||
  success: boolean;
 | 
			
		||||
  data?: {
 | 
			
		||||
    /** 列表数据 */
 | 
			
		||||
    list: Array<any>;
 | 
			
		||||
  };
 | 
			
		||||
  code?: number;
 | 
			
		||||
  msg?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 卡片列表 */
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { http } from "../utils/http";
 | 
			
		||||
 | 
			
		||||
type Result = {
 | 
			
		||||
  code: number;
 | 
			
		||||
  info: Array<any>;
 | 
			
		||||
  success: boolean;
 | 
			
		||||
  data: Array<any>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 地图数据 */
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import { http } from "../utils/http";
 | 
			
		||||
 | 
			
		||||
type Result = {
 | 
			
		||||
  code: number;
 | 
			
		||||
  info: Array<any>;
 | 
			
		||||
  success: boolean;
 | 
			
		||||
  data: Array<any>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getAsyncRoutes = (params?: object) => {
 | 
			
		||||
  return http.request<Result>("get", "/getAsyncRoutes", { params });
 | 
			
		||||
export const getAsyncRoutes = () => {
 | 
			
		||||
  return http.request<Result>("get", "/getAsyncRoutes");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,13 @@
 | 
			
		||||
import { http } from "../utils/http";
 | 
			
		||||
 | 
			
		||||
type Result = {
 | 
			
		||||
  success: boolean;
 | 
			
		||||
  data?: {
 | 
			
		||||
    /** 列表数据 */
 | 
			
		||||
    list: Array<any>;
 | 
			
		||||
    /** 总数 */
 | 
			
		||||
    total: number;
 | 
			
		||||
    total?: number;
 | 
			
		||||
  };
 | 
			
		||||
  code?: number;
 | 
			
		||||
  msg?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 获取用户管理列表 */
 | 
			
		||||
 | 
			
		||||
@ -1,26 +1,39 @@
 | 
			
		||||
import { http } from "../utils/http";
 | 
			
		||||
 | 
			
		||||
type Result = {
 | 
			
		||||
  svg?: string;
 | 
			
		||||
  code?: number;
 | 
			
		||||
  info?: object;
 | 
			
		||||
export type UserResult = {
 | 
			
		||||
  success: boolean;
 | 
			
		||||
  data: {
 | 
			
		||||
    /** 用户名 */
 | 
			
		||||
    username: string;
 | 
			
		||||
    /** 当前登陆用户的角色 */
 | 
			
		||||
    roles: Array<string>;
 | 
			
		||||
    /** `token` */
 | 
			
		||||
    accessToken: string;
 | 
			
		||||
    /** 用于调用刷新`accessToken`的接口时所需的`token` */
 | 
			
		||||
    refreshToken: string;
 | 
			
		||||
    /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
 | 
			
		||||
    expires: Date;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 获取验证码 */
 | 
			
		||||
export const getVerify = () => {
 | 
			
		||||
  return http.request<Result>("get", "/captcha");
 | 
			
		||||
export type RefreshTokenResult = {
 | 
			
		||||
  success: boolean;
 | 
			
		||||
  data: {
 | 
			
		||||
    /** `token` */
 | 
			
		||||
    accessToken: string;
 | 
			
		||||
    /** 用于调用刷新`accessToken`的接口时所需的`token` */
 | 
			
		||||
    refreshToken: string;
 | 
			
		||||
    /** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
 | 
			
		||||
    expires: Date;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 登录 */
 | 
			
		||||
export const getLogin = (data: object) => {
 | 
			
		||||
  return http.request("post", "/login", { data });
 | 
			
		||||
export const getLogin = (data?: object) => {
 | 
			
		||||
  return http.request<UserResult>("post", "/login", { data });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 刷新token */
 | 
			
		||||
export const refreshToken = (data: object) => {
 | 
			
		||||
  return http.request("post", "/refreshToken", { data });
 | 
			
		||||
export const refreshTokenApi = (data?: object) => {
 | 
			
		||||
  return http.request<RefreshTokenResult>("post", "/refreshToken", { data });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// export const searchVague = (data: object) => {
 | 
			
		||||
//   return http.request("post", "/searchVague", { data });
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								src/components/ReAuth/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/components/ReAuth/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
import auth from "./src/auth";
 | 
			
		||||
 | 
			
		||||
const Auth = auth;
 | 
			
		||||
 | 
			
		||||
export { Auth };
 | 
			
		||||
							
								
								
									
										20
									
								
								src/components/ReAuth/src/auth.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/components/ReAuth/src/auth.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
import { defineComponent, Fragment } from "vue";
 | 
			
		||||
import { hasAuth } from "/@/router/utils";
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
  name: "Auth",
 | 
			
		||||
  props: {
 | 
			
		||||
    value: {
 | 
			
		||||
      type: undefined,
 | 
			
		||||
      default: []
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  setup(props, { slots }) {
 | 
			
		||||
    return () => {
 | 
			
		||||
      if (!slots) return null;
 | 
			
		||||
      return hasAuth(props.value) ? (
 | 
			
		||||
        <Fragment>{slots.default?.()}</Fragment>
 | 
			
		||||
      ) : null;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
@ -116,7 +116,8 @@ onMounted(() => {
 | 
			
		||||
            :disabled="item.disabled"
 | 
			
		||||
            :style="{
 | 
			
		||||
              cursor: item.disabled === false ? 'pointer' : 'not-allowed',
 | 
			
		||||
              color: item.disabled === false ? '' : '#00000040'
 | 
			
		||||
              color: item.disabled === false ? '' : '#00000040',
 | 
			
		||||
              background: 'transparent'
 | 
			
		||||
            }"
 | 
			
		||||
            @click="onControl(item, key)"
 | 
			
		||||
          >
 | 
			
		||||
 | 
			
		||||
@ -92,8 +92,8 @@ onBeforeMount(() => {
 | 
			
		||||
 | 
			
		||||
      // 获取模拟车辆信息
 | 
			
		||||
      mapJson()
 | 
			
		||||
        .then(({ info }) => {
 | 
			
		||||
          let points: object = info.map(v => {
 | 
			
		||||
        .then(({ data }) => {
 | 
			
		||||
          let points: object = data.map(v => {
 | 
			
		||||
            return {
 | 
			
		||||
              lnglat: [v.lng, v.lat],
 | 
			
		||||
              ...v
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										13
									
								
								src/directives/auth/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/directives/auth/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
import { hasAuth } from "/@/router/utils";
 | 
			
		||||
import { Directive, type DirectiveBinding } from "vue";
 | 
			
		||||
 | 
			
		||||
export const auth: Directive = {
 | 
			
		||||
  mounted(el: HTMLElement, binding: DirectiveBinding) {
 | 
			
		||||
    const { value } = binding;
 | 
			
		||||
    if (value) {
 | 
			
		||||
      !hasAuth(value) && el.parentNode.removeChild(el);
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new Error("need auths! Like v-auth=\"['btn.add','btn.edit']\"");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@ -1,2 +1,2 @@
 | 
			
		||||
export * from "./permission";
 | 
			
		||||
export * from "./auth";
 | 
			
		||||
export * from "./elResizeDetector";
 | 
			
		||||
 | 
			
		||||
@ -1,18 +0,0 @@
 | 
			
		||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
 | 
			
		||||
import { Directive } from "vue";
 | 
			
		||||
import type { DirectiveBinding } from "vue";
 | 
			
		||||
 | 
			
		||||
export const auth: Directive = {
 | 
			
		||||
  mounted(el: HTMLElement, binding: DirectiveBinding) {
 | 
			
		||||
    const { value } = binding;
 | 
			
		||||
    if (value) {
 | 
			
		||||
      const authRoles = value;
 | 
			
		||||
      const hasAuth = usePermissionStoreHook().buttonAuth.includes(authRoles);
 | 
			
		||||
      if (!hasAuth) {
 | 
			
		||||
        el.parentNode.removeChild(el);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new Error("need roles! Like v-auth=\"['admin','test']\"");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import { cloneDeep } from "lodash-unified";
 | 
			
		||||
import SearchResult from "./SearchResult.vue";
 | 
			
		||||
import SearchFooter from "./SearchFooter.vue";
 | 
			
		||||
import { useNav } from "/@/layout/hooks/useNav";
 | 
			
		||||
@ -31,7 +32,7 @@ const handleSearch = useDebounceFn(search, 300);
 | 
			
		||||
 | 
			
		||||
/** 菜单树形结构 */
 | 
			
		||||
const menusData = computed(() => {
 | 
			
		||||
  return deleteChildren(usePermissionStoreHook().menusTree);
 | 
			
		||||
  return deleteChildren(cloneDeep(usePermissionStoreHook().wholeMenus));
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const show = computed({
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@ import panel from "../panel/index.vue";
 | 
			
		||||
import { emitter } from "/@/utils/mitt";
 | 
			
		||||
import { resetRouter } from "/@/router";
 | 
			
		||||
import { templateRef } from "@vueuse/core";
 | 
			
		||||
import { removeToken } from "/@/utils/auth";
 | 
			
		||||
import { routerArrays } from "/@/layout/types";
 | 
			
		||||
import { useNav } from "/@/layout/hooks/useNav";
 | 
			
		||||
import { useAppStoreHook } from "/@/store/modules/app";
 | 
			
		||||
@ -131,7 +132,7 @@ const multiTagsCacheChange = () => {
 | 
			
		||||
 | 
			
		||||
/** 清空缓存并返回登录页 */
 | 
			
		||||
function onReset() {
 | 
			
		||||
  router.push("/login");
 | 
			
		||||
  removeToken();
 | 
			
		||||
  storageLocal.clear();
 | 
			
		||||
  storageSession.clear();
 | 
			
		||||
  const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
 | 
			
		||||
@ -140,6 +141,7 @@ function onReset() {
 | 
			
		||||
  useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache);
 | 
			
		||||
  toggleClass(Grey, "html-grey", document.querySelector("html"));
 | 
			
		||||
  toggleClass(Weak, "html-weakness", document.querySelector("html"));
 | 
			
		||||
  router.push("/login");
 | 
			
		||||
  useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
 | 
			
		||||
  resetRouter();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,7 @@ nextTick(() => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => route.path,
 | 
			
		||||
  () => [route.path, usePermissionStoreHook().wholeMenus],
 | 
			
		||||
  () => {
 | 
			
		||||
    getDefaultActive(route.path);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ const menuData = computed(() => {
 | 
			
		||||
    : usePermissionStoreHook().wholeMenus;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function getSubMenuData(path) {
 | 
			
		||||
function getSubMenuData(path: string) {
 | 
			
		||||
  // path的上级路由组成的数组
 | 
			
		||||
  const parentPathArr = getParentPaths(
 | 
			
		||||
    path,
 | 
			
		||||
@ -41,6 +41,7 @@ function getSubMenuData(path) {
 | 
			
		||||
  if (!parenetRoute?.children) return;
 | 
			
		||||
  subMenuData.value = parenetRoute?.children;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
getSubMenuData(route.path);
 | 
			
		||||
 | 
			
		||||
onBeforeMount(() => {
 | 
			
		||||
@ -50,7 +51,7 @@ onBeforeMount(() => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => route.path,
 | 
			
		||||
  () => [route.path, usePermissionStoreHook().wholeMenus],
 | 
			
		||||
  () => {
 | 
			
		||||
    getSubMenuData(route.path);
 | 
			
		||||
    menuSelect(route.path, routers);
 | 
			
		||||
 | 
			
		||||
@ -1,27 +1,26 @@
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
import { router } from "/@/router";
 | 
			
		||||
import { getConfig } from "/@/config";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import { emitter } from "/@/utils/mitt";
 | 
			
		||||
import { routeMetaType } from "../types";
 | 
			
		||||
import type { StorageConfigs } from "/#/index";
 | 
			
		||||
import { routerArrays } from "/@/layout/types";
 | 
			
		||||
import { useGlobal } from "@pureadmin/utils";
 | 
			
		||||
import { transformI18n } from "/@/plugins/i18n";
 | 
			
		||||
import { router, remainingPaths } from "/@/router";
 | 
			
		||||
import { useAppStoreHook } from "/@/store/modules/app";
 | 
			
		||||
import { remainingPaths, resetRouter } from "/@/router";
 | 
			
		||||
import { i18nChangeLanguage } from "@wangeditor/editor";
 | 
			
		||||
import { storageSession, useGlobal } from "@pureadmin/utils";
 | 
			
		||||
import { useUserStoreHook } from "/@/store/modules/user";
 | 
			
		||||
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
 | 
			
		||||
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 | 
			
		||||
 | 
			
		||||
const errorInfo = "当前路由配置不正确,请检查配置";
 | 
			
		||||
 | 
			
		||||
export function useNav() {
 | 
			
		||||
  const pureApp = useAppStoreHook();
 | 
			
		||||
  const routers = useRouter().options.routes;
 | 
			
		||||
 | 
			
		||||
  /** 用户名 */
 | 
			
		||||
  const username: string =
 | 
			
		||||
    storageSession.getItem<StorageConfigs>("info")?.username;
 | 
			
		||||
  const username = computed(() => {
 | 
			
		||||
    return useUserStoreHook()?.username;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  /** 设置国际化选中后的样式 */
 | 
			
		||||
  const getDropdownItemStyle = computed(() => {
 | 
			
		||||
@ -40,7 +39,7 @@ export function useNav() {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const avatarsStyle = computed(() => {
 | 
			
		||||
    return username ? { marginRight: "10px" } : "";
 | 
			
		||||
    return username.value ? { marginRight: "10px" } : "";
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const isCollapse = computed(() => {
 | 
			
		||||
@ -69,10 +68,7 @@ export function useNav() {
 | 
			
		||||
 | 
			
		||||
  /** 退出登录 */
 | 
			
		||||
  function logout() {
 | 
			
		||||
    useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
 | 
			
		||||
    storageSession.removeItem("info");
 | 
			
		||||
    router.push("/login");
 | 
			
		||||
    resetRouter();
 | 
			
		||||
    useUserStoreHook().logOut();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function backHome() {
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ export type routeMetaType = {
 | 
			
		||||
  icon?: string;
 | 
			
		||||
  showLink?: boolean;
 | 
			
		||||
  savedPosition?: boolean;
 | 
			
		||||
  authority?: Array<string>;
 | 
			
		||||
  auths?: Array<string>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type RouteConfigs = {
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,10 @@ app.component("IconifyIconOffline", IconifyIconOffline);
 | 
			
		||||
app.component("IconifyIconOnline", IconifyIconOnline);
 | 
			
		||||
app.component("FontIcon", FontIcon);
 | 
			
		||||
 | 
			
		||||
// 全局注册按钮级别权限组件
 | 
			
		||||
import { Auth } from "/@/components/ReAuth";
 | 
			
		||||
app.component("Auth", Auth);
 | 
			
		||||
 | 
			
		||||
getServerConfig(app).then(async config => {
 | 
			
		||||
  app.use(router);
 | 
			
		||||
  await router.isReady();
 | 
			
		||||
 | 
			
		||||
@ -2,8 +2,8 @@ import { getConfig } from "/@/config";
 | 
			
		||||
import { toRouteType } from "./types";
 | 
			
		||||
import NProgress from "/@/utils/progress";
 | 
			
		||||
import { findIndex } from "lodash-unified";
 | 
			
		||||
import type { StorageConfigs } from "/#/index";
 | 
			
		||||
import { transformI18n } from "/@/plugins/i18n";
 | 
			
		||||
import { sessionKey, type DataInfo } from "/@/utils/auth";
 | 
			
		||||
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 | 
			
		||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
 | 
			
		||||
import {
 | 
			
		||||
@ -15,6 +15,7 @@ import {
 | 
			
		||||
import {
 | 
			
		||||
  ascending,
 | 
			
		||||
  initRouter,
 | 
			
		||||
  isOneOfArray,
 | 
			
		||||
  getHistoryMode,
 | 
			
		||||
  findRouteByPath,
 | 
			
		||||
  handleAliveRoute,
 | 
			
		||||
@ -121,10 +122,10 @@ router.beforeEach((to: toRouteType, _from, next) => {
 | 
			
		||||
      handleAliveRoute(newMatched);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  const name = storageSession.getItem<StorageConfigs>("info");
 | 
			
		||||
  const userInfo = storageSession.getItem<DataInfo<number>>(sessionKey);
 | 
			
		||||
  NProgress.start();
 | 
			
		||||
  const externalLink = isUrl(to?.name as string);
 | 
			
		||||
  if (!externalLink)
 | 
			
		||||
  if (!externalLink) {
 | 
			
		||||
    to.matched.some(item => {
 | 
			
		||||
      if (!item.meta.title) return "";
 | 
			
		||||
      const Title = getConfig().Title;
 | 
			
		||||
@ -132,7 +133,12 @@ router.beforeEach((to: toRouteType, _from, next) => {
 | 
			
		||||
        document.title = `${transformI18n(item.meta.title)} | ${Title}`;
 | 
			
		||||
      else document.title = transformI18n(item.meta.title);
 | 
			
		||||
    });
 | 
			
		||||
  if (name) {
 | 
			
		||||
  }
 | 
			
		||||
  if (userInfo) {
 | 
			
		||||
    // 无权限跳转403页面
 | 
			
		||||
    if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) {
 | 
			
		||||
      next({ path: "/error/403" });
 | 
			
		||||
    }
 | 
			
		||||
    if (_from?.name) {
 | 
			
		||||
      // name为超链接
 | 
			
		||||
      if (externalLink) {
 | 
			
		||||
@ -143,8 +149,11 @@ router.beforeEach((to: toRouteType, _from, next) => {
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // 刷新
 | 
			
		||||
      if (usePermissionStoreHook().wholeMenus.length === 0)
 | 
			
		||||
        initRouter(name.username).then((router: Router) => {
 | 
			
		||||
      if (
 | 
			
		||||
        usePermissionStoreHook().wholeMenus.length === 0 &&
 | 
			
		||||
        to.path !== "/login"
 | 
			
		||||
      )
 | 
			
		||||
        initRouter().then((router: Router) => {
 | 
			
		||||
          if (!useMultiTagsStoreHook().getMultiTagsCache) {
 | 
			
		||||
            const { path } = to;
 | 
			
		||||
            const index = findIndex(remainingRouter, v => {
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ const errorRouter: RouteConfigsTable = {
 | 
			
		||||
  redirect: "/error/403",
 | 
			
		||||
  meta: {
 | 
			
		||||
    icon: "information-line",
 | 
			
		||||
    title: $t("menus.hserror"),
 | 
			
		||||
    title: $t("menus.hsabnormal"),
 | 
			
		||||
    rank: 9
 | 
			
		||||
  },
 | 
			
		||||
  children: [
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { RouteLocationNormalized } from "vue-router";
 | 
			
		||||
 | 
			
		||||
export interface toRouteType extends RouteLocationNormalized {
 | 
			
		||||
  meta: {
 | 
			
		||||
    roles: Array<string>;
 | 
			
		||||
    keepAlive?: boolean;
 | 
			
		||||
    dynamicLevel?: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@ -9,10 +9,16 @@ import {
 | 
			
		||||
import { router } from "./index";
 | 
			
		||||
import { isProxy, toRaw } from "vue";
 | 
			
		||||
import { loadEnv } from "../../build";
 | 
			
		||||
import { cloneDeep } from "lodash-unified";
 | 
			
		||||
import { useTimeoutFn } from "@vueuse/core";
 | 
			
		||||
import { RouteConfigs } from "/@/layout/types";
 | 
			
		||||
import { buildHierarchyTree } from "@pureadmin/utils";
 | 
			
		||||
import {
 | 
			
		||||
  isString,
 | 
			
		||||
  storageSession,
 | 
			
		||||
  buildHierarchyTree,
 | 
			
		||||
  isIncludeAllChildren
 | 
			
		||||
} from "@pureadmin/utils";
 | 
			
		||||
import { cloneDeep, intersection } from "lodash-unified";
 | 
			
		||||
import { sessionKey, type DataInfo } from "/@/utils/auth";
 | 
			
		||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
 | 
			
		||||
const IFrame = () => import("/@/layout/frameView.vue");
 | 
			
		||||
// https://cn.vitejs.dev/guide/features.html#glob-import
 | 
			
		||||
@ -38,7 +44,7 @@ function ascending(arr: any[]) {
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 过滤meta中showLink为false的路由 */
 | 
			
		||||
/** 过滤meta中showLink为false的菜单 */
 | 
			
		||||
function filterTree(data: RouteComponent[]) {
 | 
			
		||||
  const newTree = cloneDeep(data).filter(
 | 
			
		||||
    (v: { meta: { showLink: boolean } }) => v.meta?.showLink !== false
 | 
			
		||||
@ -49,6 +55,37 @@ function filterTree(data: RouteComponent[]) {
 | 
			
		||||
  return newTree;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 过滤children长度为0的的目录,当目录下没有菜单时,会过滤此目录,目录没有赋予roles权限,当目录下只要有一个菜单有显示权限,那么此目录就会显示 */
 | 
			
		||||
function filterChildrenTree(data: RouteComponent[]) {
 | 
			
		||||
  const newTree = cloneDeep(data).filter((v: any) => v?.children?.length !== 0);
 | 
			
		||||
  newTree.forEach(
 | 
			
		||||
    (v: { children }) => v.children && (v.children = filterTree(v.children))
 | 
			
		||||
  );
 | 
			
		||||
  return newTree;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 判断两个数组彼此是否存在相同值 */
 | 
			
		||||
function isOneOfArray(a: Array<string>, b: Array<string>) {
 | 
			
		||||
  return Array.isArray(a) && Array.isArray(b)
 | 
			
		||||
    ? intersection(a, b).length > 0
 | 
			
		||||
      ? true
 | 
			
		||||
      : false
 | 
			
		||||
    : true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 从sessionStorage里取出当前登陆用户的角色roles,过滤无权限的菜单 */
 | 
			
		||||
function filterNoPermissionTree(data: RouteComponent[]) {
 | 
			
		||||
  const currentRoles =
 | 
			
		||||
    storageSession.getItem<DataInfo<number>>(sessionKey).roles ?? [];
 | 
			
		||||
  const newTree = cloneDeep(data).filter((v: any) =>
 | 
			
		||||
    isOneOfArray(v.meta?.roles, currentRoles)
 | 
			
		||||
  );
 | 
			
		||||
  newTree.forEach(
 | 
			
		||||
    (v: any) => v.children && (v.children = filterNoPermissionTree(v.children))
 | 
			
		||||
  );
 | 
			
		||||
  return filterChildrenTree(newTree);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 批量删除缓存路由(keepalive) */
 | 
			
		||||
function delAliveRoutes(delAliveRouteList: Array<RouteConfigs>) {
 | 
			
		||||
  delAliveRouteList.forEach(route => {
 | 
			
		||||
@ -115,13 +152,13 @@ function addPathMatch() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 初始化路由 */
 | 
			
		||||
function initRouter(name: string) {
 | 
			
		||||
function initRouter() {
 | 
			
		||||
  return new Promise(resolve => {
 | 
			
		||||
    getAsyncRoutes({ name }).then(({ info }) => {
 | 
			
		||||
      if (info.length === 0) {
 | 
			
		||||
        usePermissionStoreHook().changeSetting(info);
 | 
			
		||||
    getAsyncRoutes().then(({ data }) => {
 | 
			
		||||
      if (data.length === 0) {
 | 
			
		||||
        usePermissionStoreHook().handleWholeMenus(data);
 | 
			
		||||
      } else {
 | 
			
		||||
        formatFlatteningRoutes(addAsyncRoutes(info)).map(
 | 
			
		||||
        formatFlatteningRoutes(addAsyncRoutes(data)).map(
 | 
			
		||||
          (v: RouteRecordRaw) => {
 | 
			
		||||
            // 防止重复添加路由
 | 
			
		||||
            if (
 | 
			
		||||
@ -144,7 +181,7 @@ function initRouter(name: string) {
 | 
			
		||||
            resolve(router);
 | 
			
		||||
          }
 | 
			
		||||
        );
 | 
			
		||||
        usePermissionStoreHook().changeSetting(info);
 | 
			
		||||
        usePermissionStoreHook().handleWholeMenus(data);
 | 
			
		||||
      }
 | 
			
		||||
      addPathMatch();
 | 
			
		||||
    });
 | 
			
		||||
@ -275,30 +312,29 @@ function getHistoryMode(): RouterHistory {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 是否有权限 */
 | 
			
		||||
function hasPermissions(value: Array<string>): boolean {
 | 
			
		||||
  if (value && value instanceof Array && value.length > 0) {
 | 
			
		||||
    const roles = usePermissionStoreHook().buttonAuth;
 | 
			
		||||
    const permissionRoles = value;
 | 
			
		||||
 | 
			
		||||
    const hasPermission = roles.some(role => {
 | 
			
		||||
      return permissionRoles.includes(role);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (!hasPermission) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  } else {
 | 
			
		||||
    return false;
 | 
			
		||||
/** 获取当前页面按钮级别的权限 */
 | 
			
		||||
function getAuths(): Array<string> {
 | 
			
		||||
  return router.currentRoute.value.meta.auths as Array<string>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 是否有按钮级别的权限 */
 | 
			
		||||
function hasAuth(value: string | Array<string>): boolean {
 | 
			
		||||
  if (!value) return false;
 | 
			
		||||
  /** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */
 | 
			
		||||
  const metaAuths = getAuths();
 | 
			
		||||
  const isAuths = isString(value)
 | 
			
		||||
    ? metaAuths.includes(value)
 | 
			
		||||
    : isIncludeAllChildren(value, metaAuths);
 | 
			
		||||
  return isAuths ? true : false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  hasAuth,
 | 
			
		||||
  getAuths,
 | 
			
		||||
  ascending,
 | 
			
		||||
  filterTree,
 | 
			
		||||
  initRouter,
 | 
			
		||||
  hasPermissions,
 | 
			
		||||
  isOneOfArray,
 | 
			
		||||
  getHistoryMode,
 | 
			
		||||
  addAsyncRoutes,
 | 
			
		||||
  delAliveRoutes,
 | 
			
		||||
@ -306,5 +342,6 @@ export {
 | 
			
		||||
  findRouteByPath,
 | 
			
		||||
  handleAliveRoute,
 | 
			
		||||
  formatTwoStageRoutes,
 | 
			
		||||
  formatFlatteningRoutes
 | 
			
		||||
  formatFlatteningRoutes,
 | 
			
		||||
  filterNoPermissionTree
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2,9 +2,7 @@ import { defineStore } from "pinia";
 | 
			
		||||
import { store } from "/@/store";
 | 
			
		||||
import { cacheType } from "./types";
 | 
			
		||||
import { constantMenus } from "/@/router";
 | 
			
		||||
import { cloneDeep } from "lodash-unified";
 | 
			
		||||
import { RouteConfigs } from "/@/layout/types";
 | 
			
		||||
import { ascending, filterTree } from "/@/router/utils";
 | 
			
		||||
import { ascending, filterTree, filterNoPermissionTree } from "/@/router/utils";
 | 
			
		||||
 | 
			
		||||
export const usePermissionStore = defineStore({
 | 
			
		||||
  id: "pure-permission",
 | 
			
		||||
@ -13,40 +11,15 @@ export const usePermissionStore = defineStore({
 | 
			
		||||
    constantMenus,
 | 
			
		||||
    // 整体路由生成的菜单(静态、动态)
 | 
			
		||||
    wholeMenus: [],
 | 
			
		||||
    // 深拷贝一个菜单树,与导航菜单不突出
 | 
			
		||||
    menusTree: [],
 | 
			
		||||
    buttonAuth: [],
 | 
			
		||||
    // 缓存页面keepAlive
 | 
			
		||||
    cachePageList: []
 | 
			
		||||
  }),
 | 
			
		||||
  actions: {
 | 
			
		||||
    /** 获取异步路由菜单 */
 | 
			
		||||
    asyncActionRoutes(routes) {
 | 
			
		||||
      if (this.wholeMenus.length > 0) return;
 | 
			
		||||
      this.wholeMenus = filterTree(
 | 
			
		||||
        ascending(this.constantMenus.concat(routes))
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      this.menusTree = cloneDeep(
 | 
			
		||||
    /** 组装整体路由生成的菜单 */
 | 
			
		||||
    handleWholeMenus(routes: any[]) {
 | 
			
		||||
      this.wholeMenus = filterNoPermissionTree(
 | 
			
		||||
        filterTree(ascending(this.constantMenus.concat(routes)))
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      const getButtonAuth = (arrRoutes: Array<RouteConfigs>) => {
 | 
			
		||||
        if (!arrRoutes || !arrRoutes.length) return;
 | 
			
		||||
        arrRoutes.forEach((v: RouteConfigs) => {
 | 
			
		||||
          if (v.meta && v.meta.authority) {
 | 
			
		||||
            this.buttonAuth.push(...v.meta.authority);
 | 
			
		||||
          }
 | 
			
		||||
          if (v.children) {
 | 
			
		||||
            getButtonAuth(v.children);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      getButtonAuth(this.wholeMenus);
 | 
			
		||||
    },
 | 
			
		||||
    async changeSetting(routes) {
 | 
			
		||||
      await this.asyncActionRoutes(routes);
 | 
			
		||||
    },
 | 
			
		||||
    cacheOperate({ mode, name }: cacheType) {
 | 
			
		||||
      switch (mode) {
 | 
			
		||||
@ -64,8 +37,6 @@ export const usePermissionStore = defineStore({
 | 
			
		||||
    /** 清空缓存页面 */
 | 
			
		||||
    clearAllCachePage() {
 | 
			
		||||
      this.wholeMenus = [];
 | 
			
		||||
      this.menusTree = [];
 | 
			
		||||
      this.buttonAuth = [];
 | 
			
		||||
      this.cachePageList = [];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -37,8 +37,8 @@ export type setType = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type userType = {
 | 
			
		||||
  token: string;
 | 
			
		||||
  name?: string;
 | 
			
		||||
  username?: string;
 | 
			
		||||
  roles?: Array<string>;
 | 
			
		||||
  verifyCode?: string;
 | 
			
		||||
  currentPage?: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,55 +1,56 @@
 | 
			
		||||
import { defineStore } from "pinia";
 | 
			
		||||
import { store } from "/@/store";
 | 
			
		||||
import { userType } from "./types";
 | 
			
		||||
import { router } from "/@/router";
 | 
			
		||||
import { routerArrays } from "/@/layout/types";
 | 
			
		||||
import { router, resetRouter } from "/@/router";
 | 
			
		||||
import { storageSession } from "@pureadmin/utils";
 | 
			
		||||
import { getLogin, refreshToken } from "/@/api/user";
 | 
			
		||||
import { getToken, setToken, removeToken } from "/@/utils/auth";
 | 
			
		||||
import { getLogin, refreshTokenApi } from "/@/api/user";
 | 
			
		||||
import { UserResult, RefreshTokenResult } from "/@/api/user";
 | 
			
		||||
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 | 
			
		||||
 | 
			
		||||
const data = getToken();
 | 
			
		||||
let token = "";
 | 
			
		||||
let name = "";
 | 
			
		||||
if (data) {
 | 
			
		||||
  const dataJson = JSON.parse(data);
 | 
			
		||||
  if (dataJson) {
 | 
			
		||||
    token = dataJson?.accessToken;
 | 
			
		||||
    name = dataJson?.name ?? "admin";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
import {
 | 
			
		||||
  type DataInfo,
 | 
			
		||||
  setToken,
 | 
			
		||||
  removeToken,
 | 
			
		||||
  sessionKey
 | 
			
		||||
} from "/@/utils/auth";
 | 
			
		||||
 | 
			
		||||
export const useUserStore = defineStore({
 | 
			
		||||
  id: "pure-user",
 | 
			
		||||
  state: (): userType => ({
 | 
			
		||||
    token,
 | 
			
		||||
    name,
 | 
			
		||||
    username:
 | 
			
		||||
      storageSession.getItem<DataInfo<number>>(sessionKey)?.username ?? "",
 | 
			
		||||
    // 页面级别权限
 | 
			
		||||
    roles: storageSession.getItem<DataInfo<number>>(sessionKey)?.roles ?? [],
 | 
			
		||||
    // 前端生成的验证码(按实际需求替换)
 | 
			
		||||
    verifyCode: "",
 | 
			
		||||
    // 登录显示组件判断 0:登录 1:手机登录 2:二维码登录 3:注册 4:忘记密码,默认0:登录
 | 
			
		||||
    // 判断登录页面显示哪个组件(0:登录(默认)、1:手机登录、2:二维码登录、3:注册、4:忘记密码)
 | 
			
		||||
    currentPage: 0
 | 
			
		||||
  }),
 | 
			
		||||
  actions: {
 | 
			
		||||
    SET_TOKEN(token) {
 | 
			
		||||
      this.token = token;
 | 
			
		||||
    /** 存储用户名 */
 | 
			
		||||
    SET_USERNAME(username: string) {
 | 
			
		||||
      this.username = username;
 | 
			
		||||
    },
 | 
			
		||||
    SET_NAME(name) {
 | 
			
		||||
      this.name = name;
 | 
			
		||||
    /** 存储角色 */
 | 
			
		||||
    SET_ROLES(roles: Array<string>) {
 | 
			
		||||
      this.roles = roles;
 | 
			
		||||
    },
 | 
			
		||||
    SET_VERIFYCODE(verifyCode) {
 | 
			
		||||
    /** 存储前端生成的验证码 */
 | 
			
		||||
    SET_VERIFYCODE(verifyCode: string) {
 | 
			
		||||
      this.verifyCode = verifyCode;
 | 
			
		||||
    },
 | 
			
		||||
    SET_CURRENTPAGE(value) {
 | 
			
		||||
    /** 存储登录页面显示哪个组件 */
 | 
			
		||||
    SET_CURRENTPAGE(value: number) {
 | 
			
		||||
      this.currentPage = value;
 | 
			
		||||
    },
 | 
			
		||||
    /** 登入 */
 | 
			
		||||
    async loginByUsername(data) {
 | 
			
		||||
      return new Promise<void>((resolve, reject) => {
 | 
			
		||||
      return new Promise<UserResult>((resolve, reject) => {
 | 
			
		||||
        getLogin(data)
 | 
			
		||||
          .then(data => {
 | 
			
		||||
            if (data) {
 | 
			
		||||
              setToken(data);
 | 
			
		||||
              resolve();
 | 
			
		||||
              setToken(data.data);
 | 
			
		||||
              resolve(data);
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
          .catch(error => {
 | 
			
		||||
@ -57,23 +58,28 @@ export const useUserStore = defineStore({
 | 
			
		||||
          });
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    /** 登出 清空缓存 */
 | 
			
		||||
    /** 前端登出(不调用接口) */
 | 
			
		||||
    logOut() {
 | 
			
		||||
      this.token = "";
 | 
			
		||||
      this.name = "";
 | 
			
		||||
      this.username = "";
 | 
			
		||||
      this.roles = [];
 | 
			
		||||
      removeToken();
 | 
			
		||||
      storageSession.clear();
 | 
			
		||||
      useMultiTagsStoreHook().handleTags("equal", routerArrays);
 | 
			
		||||
      router.push("/login");
 | 
			
		||||
      useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
 | 
			
		||||
      resetRouter();
 | 
			
		||||
    },
 | 
			
		||||
    /** 刷新token */
 | 
			
		||||
    async refreshToken(data) {
 | 
			
		||||
      removeToken();
 | 
			
		||||
      return refreshToken(data).then(data => {
 | 
			
		||||
    /** 刷新`token` */
 | 
			
		||||
    async handRefreshToken(data) {
 | 
			
		||||
      return new Promise<RefreshTokenResult>((resolve, reject) => {
 | 
			
		||||
        refreshTokenApi(data)
 | 
			
		||||
          .then(data => {
 | 
			
		||||
            if (data) {
 | 
			
		||||
          setToken(data);
 | 
			
		||||
          return data;
 | 
			
		||||
              setToken(data.data);
 | 
			
		||||
              resolve(data);
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
          .catch(error => {
 | 
			
		||||
            reject(error);
 | 
			
		||||
          });
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-dark {
 | 
			
		||||
  z-index: 99999 !important;
 | 
			
		||||
  z-index: 9999 !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 重置 el-button 中 icon 的 margin */
 | 
			
		||||
 | 
			
		||||
@ -1,42 +1,72 @@
 | 
			
		||||
import Cookies from "js-cookie";
 | 
			
		||||
import { storageSession } from "@pureadmin/utils";
 | 
			
		||||
import { useUserStoreHook } from "/@/store/modules/user";
 | 
			
		||||
 | 
			
		||||
const TokenKey = "authorized-token";
 | 
			
		||||
 | 
			
		||||
type paramsMapType = {
 | 
			
		||||
  name: string;
 | 
			
		||||
  expires: number;
 | 
			
		||||
export interface DataInfo<T> {
 | 
			
		||||
  /** token */
 | 
			
		||||
  accessToken: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 获取token */
 | 
			
		||||
export function getToken() {
 | 
			
		||||
  // 此处与TokenKey相同,此写法解决初始化时Cookies中不存在TokenKey报错
 | 
			
		||||
  return Cookies.get("authorized-token");
 | 
			
		||||
  /** `accessToken`的过期时间(时间戳) */
 | 
			
		||||
  expires: T;
 | 
			
		||||
  /** 用于调用刷新accessToken的接口时所需的token */
 | 
			
		||||
  refreshToken: string;
 | 
			
		||||
  /** 用户名 */
 | 
			
		||||
  username?: string;
 | 
			
		||||
  /** 当前登陆用户的角色 */
 | 
			
		||||
  roles?: Array<string>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 设置token以及过期时间(cookies、sessionStorage各一份),后端需要将用户信息和token以及过期时间都返回给前端,过期时间主要用于刷新token */
 | 
			
		||||
export function setToken(data) {
 | 
			
		||||
  const { accessToken, expires, name } = data;
 | 
			
		||||
  // 提取关键信息进行存储
 | 
			
		||||
  const paramsMap: paramsMapType = {
 | 
			
		||||
    name,
 | 
			
		||||
    expires: Date.now() + parseInt(expires),
 | 
			
		||||
    accessToken
 | 
			
		||||
  };
 | 
			
		||||
  const dataString = JSON.stringify(paramsMap);
 | 
			
		||||
  useUserStoreHook().SET_TOKEN(accessToken);
 | 
			
		||||
  useUserStoreHook().SET_NAME(name);
 | 
			
		||||
export const sessionKey = "user-info";
 | 
			
		||||
export const TokenKey = "authorized-token";
 | 
			
		||||
 | 
			
		||||
/** 获取`token` */
 | 
			
		||||
export function getToken(): DataInfo<number> {
 | 
			
		||||
  // 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错
 | 
			
		||||
  return Cookies.get(TokenKey)
 | 
			
		||||
    ? JSON.parse(Cookies.get(TokenKey))
 | 
			
		||||
    : storageSession.getItem(sessionKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 设置`token`以及一些必要信息并采用无感刷新`token`方案
 | 
			
		||||
 * 无感刷新:后端返回`accessToken`(访问接口使用的`token`)、`refreshToken`(用于调用刷新`accessToken`的接口时所需的`token`,`refreshToken`的过期时间(比如30天)应大于`accessToken`的过期时间(比如2小时))、`expires`(`accessToken`的过期时间)
 | 
			
		||||
 * 将`accessToken`、`expires`这两条信息放在key值为authorized-token的cookie里(过期自动销毁)
 | 
			
		||||
 * 将`username`、`roles`、`refreshToken`、`expires`这四条信息放在key值为`user-info`的sessionStorage里(浏览器关闭自动销毁)
 | 
			
		||||
 */
 | 
			
		||||
export function setToken(data: DataInfo<Date>) {
 | 
			
		||||
  let expires = 0;
 | 
			
		||||
  const { accessToken, refreshToken } = data;
 | 
			
		||||
  expires = new Date(data.expires).getTime();
 | 
			
		||||
  const cookieString = JSON.stringify({ accessToken, expires });
 | 
			
		||||
 | 
			
		||||
  expires > 0
 | 
			
		||||
    ? Cookies.set(TokenKey, dataString, {
 | 
			
		||||
        expires: expires / 86400000
 | 
			
		||||
    ? Cookies.set(TokenKey, cookieString, {
 | 
			
		||||
        expires: (expires - Date.now()) / 86400000
 | 
			
		||||
      })
 | 
			
		||||
    : Cookies.set(TokenKey, dataString);
 | 
			
		||||
  sessionStorage.setItem(TokenKey, dataString);
 | 
			
		||||
    : Cookies.set(TokenKey, cookieString);
 | 
			
		||||
 | 
			
		||||
  function setSessionKey(username: string, roles: Array<string>) {
 | 
			
		||||
    useUserStoreHook().SET_USERNAME(username);
 | 
			
		||||
    useUserStoreHook().SET_ROLES(roles);
 | 
			
		||||
    storageSession.setItem(sessionKey, {
 | 
			
		||||
      refreshToken,
 | 
			
		||||
      expires,
 | 
			
		||||
      username,
 | 
			
		||||
      roles
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
/** 删除token */
 | 
			
		||||
  if (data.username && data.roles) {
 | 
			
		||||
    const { username, roles } = data;
 | 
			
		||||
    setSessionKey(username, roles);
 | 
			
		||||
  } else {
 | 
			
		||||
    const { username, roles } =
 | 
			
		||||
      storageSession.getItem<DataInfo<number>>(sessionKey);
 | 
			
		||||
    setSessionKey(username, roles);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 删除`token`以及key值为`user-info`的session信息 */
 | 
			
		||||
export function removeToken() {
 | 
			
		||||
  Cookies.remove(TokenKey);
 | 
			
		||||
  sessionStorage.removeItem(TokenKey);
 | 
			
		||||
  sessionStorage.removeItem(sessionKey);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import Axios, { AxiosInstance, AxiosRequestConfig } from "axios";
 | 
			
		||||
import {
 | 
			
		||||
  resultType,
 | 
			
		||||
  PureHttpError,
 | 
			
		||||
  RequestMethods,
 | 
			
		||||
  PureHttpResponse,
 | 
			
		||||
@ -21,7 +20,7 @@ const defaultConfig: AxiosRequestConfig = {
 | 
			
		||||
  //   process.env.NODE_ENV === "production"
 | 
			
		||||
  //     ? VITE_PROXY_DOMAIN_REAL
 | 
			
		||||
  //     : VITE_PROXY_DOMAIN,
 | 
			
		||||
  // 当前使用mock模拟请求,将baseURL制空,如果你的环境用到了http请求,请删除下面的baseURL启用上面的baseURL,并将11行、16行代码注释取消
 | 
			
		||||
  // 当前使用mock模拟请求,将baseURL制空,如果你的环境用到了http请求,请删除下面的baseURL启用上面的baseURL,并将第10行、15行代码注释取消
 | 
			
		||||
  baseURL: "",
 | 
			
		||||
  timeout: 10000,
 | 
			
		||||
  headers: {
 | 
			
		||||
@ -47,7 +46,7 @@ class PureHttp {
 | 
			
		||||
  /** 请求拦截 */
 | 
			
		||||
  private httpInterceptorsRequest(): void {
 | 
			
		||||
    PureHttp.axiosInstance.interceptors.request.use(
 | 
			
		||||
      (config: PureHttpRequestConfig) => {
 | 
			
		||||
      async (config: PureHttpRequestConfig) => {
 | 
			
		||||
        const $config = config;
 | 
			
		||||
        // 开启进度条动画
 | 
			
		||||
        NProgress.start();
 | 
			
		||||
@ -60,26 +59,33 @@ class PureHttp {
 | 
			
		||||
          PureHttp.initConfig.beforeRequestCallback($config);
 | 
			
		||||
          return $config;
 | 
			
		||||
        }
 | 
			
		||||
        const token = getToken();
 | 
			
		||||
        if (token) {
 | 
			
		||||
          const data = JSON.parse(token);
 | 
			
		||||
        /** 请求白名单(通过设置请求白名单,防止token过期后再请求造成的死循环问题) */
 | 
			
		||||
        const whiteList = ["/refreshToken", "/login"];
 | 
			
		||||
        return whiteList.some(v => config.url.indexOf(v) > -1)
 | 
			
		||||
          ? config
 | 
			
		||||
          : new Promise(resolve => {
 | 
			
		||||
              const data = getToken();
 | 
			
		||||
              if (data) {
 | 
			
		||||
                const now = new Date().getTime();
 | 
			
		||||
                const expired = parseInt(data.expires) - now <= 0;
 | 
			
		||||
                if (expired) {
 | 
			
		||||
                  // token过期刷新
 | 
			
		||||
                  useUserStoreHook()
 | 
			
		||||
              .refreshToken(data)
 | 
			
		||||
              .then((res: resultType) => {
 | 
			
		||||
                config.headers["Authorization"] = "Bearer " + res.accessToken;
 | 
			
		||||
                return $config;
 | 
			
		||||
                    .handRefreshToken({ refreshToken: data.refreshToken })
 | 
			
		||||
                    .then(res => {
 | 
			
		||||
                      config.headers["Authorization"] =
 | 
			
		||||
                        "Bearer " + res.data.accessToken;
 | 
			
		||||
                      resolve($config);
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
            config.headers["Authorization"] = "Bearer " + data.accessToken;
 | 
			
		||||
            return $config;
 | 
			
		||||
                  config.headers["Authorization"] =
 | 
			
		||||
                    "Bearer " + data.accessToken;
 | 
			
		||||
                  resolve($config);
 | 
			
		||||
                }
 | 
			
		||||
              } else {
 | 
			
		||||
          return $config;
 | 
			
		||||
                resolve($config);
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
      },
 | 
			
		||||
      error => {
 | 
			
		||||
        return Promise.reject(error);
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
import { cloneDeep } from "lodash-unified";
 | 
			
		||||
import { transformI18n } from "/@/plugins/i18n";
 | 
			
		||||
import ElTreeLine from "/@/components/ReTreeLine";
 | 
			
		||||
import { extractPathList, deleteChildren } from "@pureadmin/utils";
 | 
			
		||||
@ -9,8 +10,9 @@ defineOptions({
 | 
			
		||||
  name: "LineTree"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
let menusTree = cloneDeep(usePermissionStoreHook().wholeMenus);
 | 
			
		||||
let menusData = computed(() => {
 | 
			
		||||
  return deleteChildren(usePermissionStoreHook().menusTree);
 | 
			
		||||
  return deleteChildren(menusTree);
 | 
			
		||||
});
 | 
			
		||||
let expandedKeys = extractPathList(menusData.value);
 | 
			
		||||
let dataProps = {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, computed } from "vue";
 | 
			
		||||
import { cloneDeep } from "lodash-unified";
 | 
			
		||||
import type { ElTreeV2 } from "element-plus";
 | 
			
		||||
import { transformI18n } from "/@/plugins/i18n";
 | 
			
		||||
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
 | 
			
		||||
@ -23,9 +24,10 @@ let dataProps = ref({
 | 
			
		||||
  children: "children"
 | 
			
		||||
});
 | 
			
		||||
const treeRef = ref<InstanceType<typeof ElTreeV2>>();
 | 
			
		||||
let menusTree = cloneDeep(usePermissionStoreHook().wholeMenus);
 | 
			
		||||
 | 
			
		||||
let menusData = computed(() => {
 | 
			
		||||
  return deleteChildren(usePermissionStoreHook().menusTree);
 | 
			
		||||
  return deleteChildren(menusTree);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
let expandedKeys = extractPathList(menusData.value);
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,12 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import {
 | 
			
		||||
  ref,
 | 
			
		||||
  reactive,
 | 
			
		||||
  watch,
 | 
			
		||||
  computed,
 | 
			
		||||
  onMounted,
 | 
			
		||||
  onBeforeUnmount
 | 
			
		||||
} from "vue";
 | 
			
		||||
import { useI18n } from "vue-i18n";
 | 
			
		||||
import Motion from "./utils/motion";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
@ -12,7 +20,6 @@ import { initRouter } from "/@/router/utils";
 | 
			
		||||
import { useNav } from "/@/layout/hooks/useNav";
 | 
			
		||||
import { message } from "@pureadmin/components";
 | 
			
		||||
import type { FormInstance } from "element-plus";
 | 
			
		||||
import { storageSession } from "@pureadmin/utils";
 | 
			
		||||
import { $t, transformI18n } from "/@/plugins/i18n";
 | 
			
		||||
import { operates, thirdParty } from "./utils/enums";
 | 
			
		||||
import { useLayout } from "/@/layout/hooks/useLayout";
 | 
			
		||||
@ -22,14 +29,6 @@ import { ReImageVerify } from "/@/components/ReImageVerify";
 | 
			
		||||
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
 | 
			
		||||
import { useTranslationLang } from "/@/layout/hooks/useTranslationLang";
 | 
			
		||||
import { useDataThemeChange } from "/@/layout/hooks/useDataThemeChange";
 | 
			
		||||
import {
 | 
			
		||||
  ref,
 | 
			
		||||
  reactive,
 | 
			
		||||
  watch,
 | 
			
		||||
  computed,
 | 
			
		||||
  onMounted,
 | 
			
		||||
  onBeforeUnmount
 | 
			
		||||
} from "vue";
 | 
			
		||||
 | 
			
		||||
import dayIcon from "/@/assets/svg/day.svg?component";
 | 
			
		||||
import darkIcon from "/@/assets/svg/dark.svg?component";
 | 
			
		||||
@ -38,6 +37,7 @@ import globalization from "/@/assets/svg/globalization.svg?component";
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "Login"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const imgCode = ref("");
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const loading = ref(false);
 | 
			
		||||
@ -47,11 +47,11 @@ const currentPage = computed(() => {
 | 
			
		||||
  return useUserStoreHook().currentPage;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { t } = useI18n();
 | 
			
		||||
const { initStorage } = useLayout();
 | 
			
		||||
initStorage();
 | 
			
		||||
 | 
			
		||||
const { t } = useI18n();
 | 
			
		||||
const { dataTheme, dataThemeChange } = useDataThemeChange();
 | 
			
		||||
dataThemeChange();
 | 
			
		||||
const { title, getDropdownItemStyle, getDropdownItemClass } = useNav();
 | 
			
		||||
const { locale, translationCh, translationEn } = useTranslationLang();
 | 
			
		||||
 | 
			
		||||
@ -66,17 +66,17 @@ const onLogin = async (formEl: FormInstance | undefined) => {
 | 
			
		||||
  if (!formEl) return;
 | 
			
		||||
  await formEl.validate((valid, fields) => {
 | 
			
		||||
    if (valid) {
 | 
			
		||||
      // 模拟请求,需根据实际开发进行修改
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        loading.value = false;
 | 
			
		||||
        storageSession.setItem("info", {
 | 
			
		||||
          username: "admin",
 | 
			
		||||
          accessToken: "eyJhbGciOiJIUzUxMiJ9.test"
 | 
			
		||||
        });
 | 
			
		||||
        initRouter("admin").then(() => {});
 | 
			
		||||
      useUserStoreHook()
 | 
			
		||||
        .loginByUsername({ username: ruleForm.username })
 | 
			
		||||
        .then(res => {
 | 
			
		||||
          if (res.success) {
 | 
			
		||||
            // 获取后端路由
 | 
			
		||||
            initRouter().then(() => {
 | 
			
		||||
              message.success("登录成功");
 | 
			
		||||
              router.push("/");
 | 
			
		||||
      }, 2000);
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
      return fields;
 | 
			
		||||
@ -84,16 +84,6 @@ const onLogin = async (formEl: FormInstance | undefined) => {
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function onHandle(value) {
 | 
			
		||||
  useUserStoreHook().SET_CURRENTPAGE(value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch(imgCode, value => {
 | 
			
		||||
  useUserStoreHook().SET_VERIFYCODE(value);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
dataThemeChange();
 | 
			
		||||
 | 
			
		||||
/** 使用公共函数,避免`removeEventListener`失效 */
 | 
			
		||||
function onkeypress({ code }: KeyboardEvent) {
 | 
			
		||||
  if (code === "Enter") {
 | 
			
		||||
@ -108,6 +98,10 @@ onMounted(() => {
 | 
			
		||||
onBeforeUnmount(() => {
 | 
			
		||||
  window.document.removeEventListener("keypress", onkeypress);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(imgCode, value => {
 | 
			
		||||
  useUserStoreHook().SET_VERIFYCODE(value);
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
@ -258,7 +252,7 @@ onBeforeUnmount(() => {
 | 
			
		||||
                    :key="index"
 | 
			
		||||
                    class="w-full mt-4"
 | 
			
		||||
                    size="default"
 | 
			
		||||
                    @click="onHandle(index + 1)"
 | 
			
		||||
                    @click="useUserStoreHook().SET_CURRENTPAGE(index + 1)"
 | 
			
		||||
                  >
 | 
			
		||||
                    {{ t(item.title) }}
 | 
			
		||||
                  </el-button>
 | 
			
		||||
 | 
			
		||||
@ -1,36 +1,80 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import type { StorageConfigs } from "/#/index";
 | 
			
		||||
import { storageSession } from "@pureadmin/utils";
 | 
			
		||||
import { type CSSProperties, computed } from "vue";
 | 
			
		||||
import { hasAuth, getAuths } from "/@/router/utils";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "PermissionButton"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const auth = ref(
 | 
			
		||||
  storageSession.getItem<StorageConfigs>("info").username || "admin"
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
function changRole(value) {
 | 
			
		||||
  storageSession.setItem("info", {
 | 
			
		||||
    username: value,
 | 
			
		||||
    accessToken: `eyJhbGciOiJIUzUxMiJ9.${value}`
 | 
			
		||||
let width = computed((): CSSProperties => {
 | 
			
		||||
  return {
 | 
			
		||||
    width: "85vw"
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
  window.location.reload();
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <el-card>
 | 
			
		||||
  <el-space direction="vertical" size="large">
 | 
			
		||||
    <el-tag :style="width" size="large" effect="dark">
 | 
			
		||||
      当前拥有的code列表:{{ getAuths() }}
 | 
			
		||||
    </el-tag>
 | 
			
		||||
 | 
			
		||||
    <el-card shadow="never" :style="width">
 | 
			
		||||
      <template #header>
 | 
			
		||||
        <div class="card-header">组件方式判断权限</div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <Auth value="btn_add">
 | 
			
		||||
        <el-button type="success"> 拥有code:'btn_add' 权限可见 </el-button>
 | 
			
		||||
      </Auth>
 | 
			
		||||
      <Auth :value="['btn_edit']">
 | 
			
		||||
        <el-button type="primary"> 拥有code:['btn_edit'] 权限可见 </el-button>
 | 
			
		||||
      </Auth>
 | 
			
		||||
      <Auth :value="['btn_add', 'btn_edit', 'btn_delete']">
 | 
			
		||||
        <el-button type="danger">
 | 
			
		||||
          拥有code:['btn_add', 'btn_edit', 'btn_delete'] 权限可见
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </Auth>
 | 
			
		||||
    </el-card>
 | 
			
		||||
 | 
			
		||||
    <el-card shadow="never" :style="width">
 | 
			
		||||
      <template #header>
 | 
			
		||||
        <div class="card-header">函数方式判断权限</div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <el-button type="success" v-if="hasAuth('btn_add')">
 | 
			
		||||
        拥有code:'btn_add' 权限可见
 | 
			
		||||
      </el-button>
 | 
			
		||||
      <el-button type="primary" v-if="hasAuth(['btn_edit'])">
 | 
			
		||||
        拥有code:['btn_edit'] 权限可见
 | 
			
		||||
      </el-button>
 | 
			
		||||
      <el-button
 | 
			
		||||
        type="danger"
 | 
			
		||||
        v-if="hasAuth(['btn_add', 'btn_edit', 'btn_delete'])"
 | 
			
		||||
      >
 | 
			
		||||
        拥有code:['btn_add', 'btn_edit', 'btn_delete'] 权限可见
 | 
			
		||||
      </el-button>
 | 
			
		||||
    </el-card>
 | 
			
		||||
 | 
			
		||||
    <el-card shadow="never" :style="width">
 | 
			
		||||
      <template #header>
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
        <el-radio-group v-model="auth" @change="changRole">
 | 
			
		||||
          <el-radio-button label="admin" />
 | 
			
		||||
          <el-radio-button label="test" />
 | 
			
		||||
        </el-radio-group>
 | 
			
		||||
          指令方式判断权限(该方式不能动态修改权限)
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
    <p v-auth="'v-admin'">只有admin可看</p>
 | 
			
		||||
    <p v-auth="'v-test'">只有test可看</p>
 | 
			
		||||
      <el-button type="success" v-auth="'btn_add'">
 | 
			
		||||
        拥有code:'btn_add' 权限可见
 | 
			
		||||
      </el-button>
 | 
			
		||||
      <el-button type="primary" v-auth="['btn_edit']">
 | 
			
		||||
        拥有code:['btn_edit'] 权限可见
 | 
			
		||||
      </el-button>
 | 
			
		||||
      <el-button type="danger" v-auth="['btn_add', 'btn_edit', 'btn_delete']">
 | 
			
		||||
        拥有code:['btn_add', 'btn_edit', 'btn_delete'] 权限可见
 | 
			
		||||
      </el-button>
 | 
			
		||||
    </el-card>
 | 
			
		||||
  </el-space>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
:deep(.el-tag) {
 | 
			
		||||
  justify-content: start;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -1,53 +1,69 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, unref } from "vue";
 | 
			
		||||
import type { StorageConfigs } from "/#/index";
 | 
			
		||||
import { storageSession } from "@pureadmin/utils";
 | 
			
		||||
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
 | 
			
		||||
import { initRouter } from "/@/router/utils";
 | 
			
		||||
import { type CSSProperties, ref, computed } from "vue";
 | 
			
		||||
import { useUserStoreHook } from "/@/store/modules/user";
 | 
			
		||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "PermissionPage"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
let purview = ref<string>(
 | 
			
		||||
  storageSession.getItem<StorageConfigs>("info").username
 | 
			
		||||
);
 | 
			
		||||
let width = computed((): CSSProperties => {
 | 
			
		||||
  return {
 | 
			
		||||
    width: "85vw"
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function changRole() {
 | 
			
		||||
  if (unref(purview) === "admin") {
 | 
			
		||||
    storageSession.setItem("info", {
 | 
			
		||||
      username: "test",
 | 
			
		||||
      accessToken: "eyJhbGciOiJIUzUxMiJ9.test"
 | 
			
		||||
    });
 | 
			
		||||
    window.location.reload();
 | 
			
		||||
  } else {
 | 
			
		||||
    storageSession.setItem("info", {
 | 
			
		||||
      username: "admin",
 | 
			
		||||
      accessToken: "eyJhbGciOiJIUzUxMiJ9.admin"
 | 
			
		||||
    });
 | 
			
		||||
    window.location.reload();
 | 
			
		||||
let username = ref(useUserStoreHook()?.username);
 | 
			
		||||
 | 
			
		||||
const options = [
 | 
			
		||||
  {
 | 
			
		||||
    value: "admin",
 | 
			
		||||
    label: "管理员角色"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    value: "common",
 | 
			
		||||
    label: "普通角色"
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
function onChange() {
 | 
			
		||||
  useUserStoreHook()
 | 
			
		||||
    .loginByUsername({ username: username.value })
 | 
			
		||||
    .then(res => {
 | 
			
		||||
      if (res.success) {
 | 
			
		||||
        usePermissionStoreHook().clearAllCachePage();
 | 
			
		||||
        initRouter();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <el-card>
 | 
			
		||||
  <el-space direction="vertical" size="large">
 | 
			
		||||
    <el-tag :style="width" size="large" effect="dark">
 | 
			
		||||
      模拟后台根据不同角色返回对应路由,观察左侧菜单变化(管理员角色可查看系统管理菜单、普通角色不可查看系统管理菜单)
 | 
			
		||||
    </el-tag>
 | 
			
		||||
    <el-card shadow="never" :style="width">
 | 
			
		||||
      <template #header>
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
        <span>
 | 
			
		||||
          当前角色:
 | 
			
		||||
          <span style="font-size: 26px">{{ purview }}</span>
 | 
			
		||||
          <p style="color: #ffa500">
 | 
			
		||||
            查看左侧菜单变化(系统管理),模拟后台根据不同角色返回对应路由
 | 
			
		||||
          </p>
 | 
			
		||||
        </span>
 | 
			
		||||
          <span>当前角色:{{ username }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
    <el-button
 | 
			
		||||
      type="primary"
 | 
			
		||||
      @click="changRole"
 | 
			
		||||
      :icon="useRenderIcon('user', { color: '#fff' })"
 | 
			
		||||
    >
 | 
			
		||||
      切换角色
 | 
			
		||||
    </el-button>
 | 
			
		||||
      <el-select v-model="username" @change="onChange">
 | 
			
		||||
        <el-option
 | 
			
		||||
          v-for="item in options"
 | 
			
		||||
          :key="item.value"
 | 
			
		||||
          :label="item.label"
 | 
			
		||||
          :value="item.value"
 | 
			
		||||
        />
 | 
			
		||||
      </el-select>
 | 
			
		||||
    </el-card>
 | 
			
		||||
  </el-space>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
:deep(.el-tag) {
 | 
			
		||||
  justify-content: start;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, computed } from "vue";
 | 
			
		||||
import { cloneDeep } from "lodash-unified";
 | 
			
		||||
import { transformI18n } from "/@/plugins/i18n";
 | 
			
		||||
import { TreeSelect } from "@pureadmin/components";
 | 
			
		||||
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
 | 
			
		||||
@ -16,13 +17,12 @@ defineOptions({
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { toDetail, router } = useDetail();
 | 
			
		||||
let menusTree = cloneDeep(usePermissionStoreHook().wholeMenus);
 | 
			
		||||
 | 
			
		||||
let treeData = computed(() => {
 | 
			
		||||
  return appendFieldByUniqueId(
 | 
			
		||||
    deleteChildren(usePermissionStoreHook().menusTree),
 | 
			
		||||
    0,
 | 
			
		||||
    { disabled: true }
 | 
			
		||||
  );
 | 
			
		||||
  return appendFieldByUniqueId(deleteChildren(menusTree), 0, {
 | 
			
		||||
    disabled: true
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const value = ref<string[]>([]);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								types/global.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								types/global.d.ts
									
									
									
									
										vendored
									
									
								
							@ -14,6 +14,7 @@ declare module "vue" {
 | 
			
		||||
    IconifyIconOffline: typeof import("../src/components/ReIcon")["IconifyIconOffline"];
 | 
			
		||||
    IconifyIconOnline: typeof import("../src/components/ReIcon")["IconifyIconOnline"];
 | 
			
		||||
    FontIcon: typeof import("../src/components/ReIcon")["FontIcon"];
 | 
			
		||||
    Auth: typeof import("../src/components/ReAuth")["Auth"];
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -74,8 +74,10 @@ export interface RouteChildrenConfigsTable {
 | 
			
		||||
    showLink?: boolean;
 | 
			
		||||
    /** 是否显示父级菜单 `可选` */
 | 
			
		||||
    showParent?: boolean;
 | 
			
		||||
    /** 路由权限设置 `可选` */
 | 
			
		||||
    authority?: Array<string>;
 | 
			
		||||
    /** 页面级别权限设置 `可选` */
 | 
			
		||||
    roles?: Array<string>;
 | 
			
		||||
    /** 按钮级别权限设置 `可选` */
 | 
			
		||||
    auths?: Array<string>;
 | 
			
		||||
    /** 路由组件缓存(开启 `true`、关闭 `false`)`可选` */
 | 
			
		||||
    keepAlive?: boolean;
 | 
			
		||||
    /** 内嵌的`iframe`链接 `可选` */
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user