release: update 3.6.0
This commit is contained in:
parent
1003a31b4d
commit
f14077bc6f
@ -25,7 +25,7 @@ buttons:
|
|||||||
menus:
|
menus:
|
||||||
hshome: Home
|
hshome: Home
|
||||||
hslogin: Login
|
hslogin: Login
|
||||||
hserror: Error Page
|
hsabnormal: Abnormal Page
|
||||||
hsfourZeroFour: "404"
|
hsfourZeroFour: "404"
|
||||||
hsfourZeroOne: "403"
|
hsfourZeroOne: "403"
|
||||||
hsFive: "500"
|
hsFive: "500"
|
||||||
|
@ -25,7 +25,7 @@ buttons:
|
|||||||
menus:
|
menus:
|
||||||
hshome: 首页
|
hshome: 首页
|
||||||
hslogin: 登录
|
hslogin: 登录
|
||||||
hserror: 错误页面
|
hsabnormal: 异常页面
|
||||||
hsfourZeroFour: "404"
|
hsfourZeroFour: "404"
|
||||||
hsfourZeroOne: "403"
|
hsfourZeroOne: "403"
|
||||||
hsFive: "500"
|
hsFive: "500"
|
||||||
|
@ -1,18 +1,25 @@
|
|||||||
// 根据角色动态生成路由
|
// 模拟后端动态生成路由
|
||||||
import { MockMethod } from "vite-plugin-mock";
|
import { MockMethod } from "vite-plugin-mock";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* roles:页面级别权限,这里模拟二种 "admin"、"common"
|
||||||
|
* admin:管理员角色
|
||||||
|
* common:普通角色
|
||||||
|
*/
|
||||||
|
|
||||||
const permissionRouter = {
|
const permissionRouter = {
|
||||||
path: "/permission",
|
path: "/permission",
|
||||||
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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -21,34 +28,22 @@ 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"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加不同按钮权限到/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: [permissionRouter]
|
||||||
info: [setDifAuthority("v-admin", permissionRouter)]
|
};
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
code: 0,
|
|
||||||
info: [setDifAuthority("v-test", permissionRouter)]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
] as MockMethod[];
|
] as MockMethod[];
|
||||||
|
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[];
|
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[];
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pure-admin-thin",
|
"name": "pure-admin-thin",
|
||||||
"version": "3.5.0",
|
"version": "3.6.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
|
"dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"Version": "3.5.0",
|
"Version": "3.6.0",
|
||||||
"Title": "PureAdmin",
|
"Title": "PureAdmin",
|
||||||
"FixedHeader": true,
|
"FixedHeader": true,
|
||||||
"HiddenSideBar": false,
|
"HiddenSideBar": false,
|
||||||
|
@ -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,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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
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();
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,12 @@ const { title } = useNav();
|
|||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
|
|
||||||
.sidebar-title {
|
.sidebar-title {
|
||||||
|
display: block;
|
||||||
|
width: 160px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: left;
|
||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
@ -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,26 +1,25 @@
|
|||||||
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 { useUserStoreHook } from "/@/store/modules/user";
|
||||||
import { storageSession, useGlobal } from "@pureadmin/utils";
|
|
||||||
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(() => {
|
||||||
@ -39,7 +38,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(() => {
|
||||||
@ -68,10 +67,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 = {
|
||||||
|
@ -44,6 +44,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,
|
||||||
@ -96,10 +97,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;
|
||||||
@ -107,7 +108,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) {
|
||||||
@ -118,8 +124,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,14 @@ 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);
|
||||||
|
resolve(router);
|
||||||
} else {
|
} else {
|
||||||
formatFlatteningRoutes(addAsyncRoutes(info)).map(
|
formatFlatteningRoutes(addAsyncRoutes(data)).map(
|
||||||
(v: RouteRecordRaw) => {
|
(v: RouteRecordRaw) => {
|
||||||
// 防止重复添加路由
|
// 防止重复添加路由
|
||||||
if (
|
if (
|
||||||
@ -144,7 +182,7 @@ function initRouter(name: string) {
|
|||||||
resolve(router);
|
resolve(router);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
usePermissionStoreHook().changeSetting(info);
|
usePermissionStoreHook().handleWholeMenus(data);
|
||||||
}
|
}
|
||||||
addPathMatch();
|
addPathMatch();
|
||||||
});
|
});
|
||||||
@ -275,30 +313,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 +343,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,45 +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: "",
|
||||||
|
// 判断登录页面显示哪个组件(0:登录(默认)、1:手机登录、2:二维码登录、3:注册、4:忘记密码)
|
||||||
|
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: string) {
|
||||||
|
this.verifyCode = verifyCode;
|
||||||
|
},
|
||||||
|
/** 存储登录页面显示哪个组件 */
|
||||||
|
SET_CURRENTPAGE(value: number) {
|
||||||
|
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 => {
|
||||||
@ -47,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的接口(通过设置请求白名单,防止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);
|
||||||
|
@ -7,9 +7,9 @@ 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 { useLayout } from "/@/layout/hooks/useLayout";
|
import { useLayout } from "/@/layout/hooks/useLayout";
|
||||||
|
import { useUserStoreHook } from "/@/store/modules/user";
|
||||||
import { bg, avatar, illustration } from "./utils/static";
|
import { bg, avatar, illustration } from "./utils/static";
|
||||||
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
|
||||||
import { ref, reactive, toRaw, onMounted, onBeforeUnmount } from "vue";
|
import { ref, reactive, toRaw, onMounted, onBeforeUnmount } from "vue";
|
||||||
@ -32,6 +32,7 @@ initStorage();
|
|||||||
|
|
||||||
const { t } = useI18n();
|
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();
|
||||||
|
|
||||||
@ -45,17 +46,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;
|
||||||
@ -63,8 +64,6 @@ const onLogin = async (formEl: FormInstance | undefined) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
dataThemeChange();
|
|
||||||
|
|
||||||
/** 使用公共函数,避免`removeEventListener`失效 */
|
/** 使用公共函数,避免`removeEventListener`失效 */
|
||||||
function onkeypress({ code }: KeyboardEvent) {
|
function onkeypress({ code }: KeyboardEvent) {
|
||||||
if (code === "Enter") {
|
if (code === "Enter") {
|
||||||
|
@ -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">
|
模拟后台根据不同角色返回对应路由(具体参考完整版pure-admin代码)
|
||||||
<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
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