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