Browse Source

release: update `3.6.0`

i18n
xiaoxian521 2 years ago
parent
commit
f14077bc6f
  1. 2
      locales/en.yaml
  2. 2
      locales/zh-CN.yaml
  3. 33
      mock/asyncRoutes.ts
  4. 36
      mock/login.ts
  5. 27
      mock/refreshToken.ts
  6. 2
      package.json
  7. 2
      public/serverConfig.json
  8. 8
      src/api/routes.ts
  9. 43
      src/api/user.ts
  10. 5
      src/components/ReAuth/index.ts
  11. 20
      src/components/ReAuth/src/auth.tsx
  12. 13
      src/directives/auth/index.ts
  13. 2
      src/directives/index.ts
  14. 18
      src/directives/permission/index.ts
  15. 3
      src/layout/components/search/components/SearchModal.vue
  16. 4
      src/layout/components/setting/index.vue
  17. 6
      src/layout/components/sidebar/logo.vue
  18. 2
      src/layout/components/sidebar/mixNav.vue
  19. 5
      src/layout/components/sidebar/vertical.vue
  20. 22
      src/layout/hooks/useNav.ts
  21. 2
      src/layout/types.ts
  22. 4
      src/main.ts
  23. 21
      src/router/index.ts
  24. 2
      src/router/modules/error.ts
  25. 1
      src/router/types.ts
  26. 92
      src/router/utils.ts
  27. 37
      src/store/modules/permission.ts
  28. 4
      src/store/modules/types.ts
  29. 84
      src/store/modules/user.ts
  30. 2
      src/style/element-plus.scss
  31. 86
      src/utils/auth.ts
  32. 32
      src/utils/http/index.ts
  33. 23
      src/views/login/index.vue
  34. 86
      src/views/permission/button/index.vue
  35. 86
      src/views/permission/page/index.vue
  36. 1
      types/global.d.ts
  37. 6
      types/index.ts

2
locales/en.yaml

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

2
locales/zh-CN.yaml

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

33
mock/asyncRoutes.ts

@ -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 }) => {
if (query.name === "admin") {
response: () => {
return { return {
code: 0,
info: [setDifAuthority("v-admin", permissionRouter)]
success: true,
data: [permissionRouter]
}; };
} else {
return {
code: 0,
info: [setDifAuthority("v-test", permissionRouter)]
};
}
} }
} }
] as MockMethod[]; ] as MockMethod[];

36
mock/login.ts

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

@ -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[];

2
package.json

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

2
public/serverConfig.json

@ -1,5 +1,5 @@
{ {
"Version": "3.5.0",
"Version": "3.6.0",
"Title": "PureAdmin", "Title": "PureAdmin",
"FixedHeader": true, "FixedHeader": true,
"HiddenSideBar": false, "HiddenSideBar": false,

8
src/api/routes.ts

@ -1,10 +1,10 @@
import { http } from "../utils/http"; import { http } from "../utils/http";
type Result = { type Result = {
code: number;
info: Array<any>;
success: boolean;
data: Array<any>;
}; };
export const getAsyncRoutes = (params?: object) => {
return http.request<Result>("get", "/getAsyncRoutes", { params });
export const getAsyncRoutes = () => {
return http.request<Result>("get", "/getAsyncRoutes");
}; };

43
src/api/user.ts

@ -1,26 +1,39 @@
import { http } from "../utils/http"; import { http } from "../utils/http";
type Result = {
svg?: string;
code?: number;
info?: object;
export type UserResult = {
success: boolean;
data: {
/** 用户名 */
username: string;
/** 当前登陆用户的角色 */
roles: Array<string>;
/** `token` */
accessToken: string;
/** 用于调用刷新`accessToken`的接口时所需的`token` */
refreshToken: string;
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
expires: Date;
};
}; };
/** 获取验证码 */
export const getVerify = () => {
return http.request<Result>("get", "/captcha");
export type RefreshTokenResult = {
success: boolean;
data: {
/** `token` */
accessToken: string;
/** 用于调用刷新`accessToken`的接口时所需的`token` */
refreshToken: string;
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
expires: Date;
};
}; };
/** 登录 */ /** 登录 */
export const getLogin = (data: object) => {
return http.request("post", "/login", { data });
export const getLogin = (data?: object) => {
return http.request<UserResult>("post", "/login", { data });
}; };
/** 刷新token */ /** 刷新token */
export const refreshToken = (data: object) => {
return http.request("post", "/refreshToken", { data });
export const refreshTokenApi = (data?: object) => {
return http.request<RefreshTokenResult>("post", "/refreshToken", { data });
}; };
// export const searchVague = (data: object) => {
// return http.request("post", "/searchVague", { data });
// };

5
src/components/ReAuth/index.ts

@ -0,0 +1,5 @@
import auth from "./src/auth";
const Auth = auth;
export { Auth };

20
src/components/ReAuth/src/auth.tsx

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

@ -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']\"");
}
}
};

2
src/directives/index.ts

@ -1,2 +1,2 @@
export * from "./permission";
export * from "./auth";
export * from "./elResizeDetector"; export * from "./elResizeDetector";

18
src/directives/permission/index.ts

@ -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']\"");
}
}
};

3
src/layout/components/search/components/SearchModal.vue

@ -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({

4
src/layout/components/setting/index.vue

@ -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();
} }

6
src/layout/components/sidebar/logo.vue

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

2
src/layout/components/sidebar/mixNav.vue

@ -48,7 +48,7 @@ nextTick(() => {
}); });
watch( watch(
() => route.path,
() => [route.path, usePermissionStoreHook().wholeMenus],
() => { () => {
getDefaultActive(route.path); getDefaultActive(route.path);
} }

5
src/layout/components/sidebar/vertical.vue

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

22
src/layout/hooks/useNav.ts

@ -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 { routerArrays } from "/@/layout/types";
import { useGlobal } from "@pureadmin/utils";
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 { 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 =
storageSession.getItem<StorageConfigs>("info")?.username;
const username = computed(() => {
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]);
storageSession.removeItem("info");
router.push("/login");
resetRouter();
useUserStoreHook().logOut();
} }
function backHome() { function backHome() {

2
src/layout/types.ts

@ -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 = {

4
src/main.ts

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

21
src/router/index.ts

@ -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)
initRouter(name.username).then((router: Router) => {
if (
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 => {

2
src/router/modules/error.ts

@ -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: [

1
src/router/types.ts

@ -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;
}; };

92
src/router/utils.ts

@ -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 }) => {
if (info.length === 0) {
usePermissionStoreHook().changeSetting(info);
getAsyncRoutes().then(({ data }) => {
if (data.length === 0) {
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 {
if (value && value instanceof Array && value.length > 0) {
const roles = usePermissionStoreHook().buttonAuth;
const permissionRoles = value;
const hasPermission = roles.some(role => {
return permissionRoles.includes(role);
});
/** 获取当前页面按钮级别的权限 */
function getAuths(): Array<string> {
return router.currentRoute.value.meta.auths as Array<string>;
}
if (!hasPermission) {
return false;
}
return true;
} else {
return false;
}
/** 是否有按钮级别的权限 */
function hasAuth(value: string | Array<string>): boolean {
if (!value) return false;
/** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */
const metaAuths = getAuths();
const isAuths = isString(value)
? metaAuths.includes(value)
: isIncludeAllChildren(value, metaAuths);
return isAuths ? true : false;
} }
export { 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
}; };

37
src/store/modules/permission.ts

@ -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 { RouteConfigs } from "/@/layout/types";
import { ascending, filterTree } from "/@/router/utils";
import { ascending, filterTree, filterNoPermissionTree } 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) {
if (this.wholeMenus.length > 0) return;
this.wholeMenus = filterTree(
ascending(this.constantMenus.concat(routes))
);
this.menusTree = cloneDeep(
/** 组装整体路由生成的菜单 */
handleWholeMenus(routes: any[]) {
this.wholeMenus = filterNoPermissionTree(
filterTree(ascending(this.constantMenus.concat(routes))) 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 = [];
} }
} }

4
src/store/modules/types.ts

@ -37,8 +37,8 @@ export type setType = {
}; };
export type userType = { export type userType = {
token: string;
name?: string;
username?: string;
roles?: Array<string>;
verifyCode?: string; verifyCode?: string;
currentPage?: number; currentPage?: number;
}; };

84
src/store/modules/user.ts

@ -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 { getToken, setToken, removeToken } from "/@/utils/auth";
import { getLogin, refreshTokenApi } from "/@/api/user";
import { UserResult, RefreshTokenResult } from "/@/api/user";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
const data = getToken();
let token = "";
let name = "";
if (data) {
const dataJson = JSON.parse(data);
if (dataJson) {
token = dataJson?.accessToken;
name = dataJson?.name ?? "admin";
}
}
import {
type DataInfo,
setToken,
removeToken,
sessionKey
} from "/@/utils/auth";
export const useUserStore = defineStore({ export const useUserStore = defineStore({
id: "pure-user", id: "pure-user",
state: (): userType => ({ state: (): userType => ({
token,
name
username:
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);
resolve();
setToken(data.data);
resolve(data);
} }
}) })
.catch(error => { .catch(error => {
@ -47,23 +58,28 @@ export const useUserStore = defineStore({
}); });
}); });
}, },
/** 登出 清空缓存 */
/** 前端登出(不调用接口) */
logOut() { logOut() {
this.token = "";
this.name = "";
this.username = "";
this.roles = [];
removeToken(); removeToken();
storageSession.clear();
useMultiTagsStoreHook().handleTags("equal", routerArrays);
router.push("/login"); router.push("/login");
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
resetRouter();
}, },
/** 刷新token */
async refreshToken(data) {
removeToken();
return refreshToken(data).then(data => {
/** 刷新`token` */
async handRefreshToken(data) {
return new Promise<RefreshTokenResult>((resolve, reject) => {
refreshTokenApi(data)
.then(data => {
if (data) { if (data) {
setToken(data);
return data;
setToken(data.data);
resolve(data);
} }
})
.catch(error => {
reject(error);
});
}); });
} }
} }

2
src/style/element-plus.scss

@ -33,7 +33,7 @@
} }
.is-dark { .is-dark {
z-index: 99999 !important;
z-index: 9999 !important;
} }
/* 重置 el-button 中 icon 的 margin */ /* 重置 el-button 中 icon 的 margin */

86
src/utils/auth.ts

@ -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";
type paramsMapType = {
name: string;
expires: number;
export interface DataInfo<T> {
/** token */
accessToken: string; accessToken: string;
};
/** `accessToken`的过期时间(时间戳) */
expires: T;
/** 用于调用刷新accessToken的接口时所需的token */
refreshToken: string;
/** 用户名 */
username?: string;
/** 当前登陆用户的角色 */
roles?: Array<string>;
}
export const sessionKey = "user-info";
export const TokenKey = "authorized-token";
/** 获取token */
export function getToken() {
// 此处与TokenKey相同,此写法解决初始化时Cookies中不存在TokenKey报错
return Cookies.get("authorized-token");
/** 获取`token` */
export function getToken(): DataInfo<number> {
// 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错
return Cookies.get(TokenKey)
? JSON.parse(Cookies.get(TokenKey))
: storageSession.getItem(sessionKey);
} }
/** 设置token以及过期时间(cookies、sessionStorage各一份),后端需要将用户信息和token以及过期时间都返回给前端,过期时间主要用于刷新token */
export function setToken(data) {
const { accessToken, expires, name } = data;
// 提取关键信息进行存储
const paramsMap: paramsMapType = {
name,
expires: Date.now() + parseInt(expires),
accessToken
};
const dataString = JSON.stringify(paramsMap);
useUserStoreHook().SET_TOKEN(accessToken);
useUserStoreHook().SET_NAME(name);
/**
* @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, {
expires: expires / 86400000
? Cookies.set(TokenKey, cookieString, {
expires: (expires - Date.now()) / 86400000
}) })
: Cookies.set(TokenKey, dataString);
sessionStorage.setItem(TokenKey, dataString);
: Cookies.set(TokenKey, cookieString);
function setSessionKey(username: string, roles: Array<string>) {
useUserStoreHook().SET_USERNAME(username);
useUserStoreHook().SET_ROLES(roles);
storageSession.setItem(sessionKey, {
refreshToken,
expires,
username,
roles
});
}
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);
} }

32
src/utils/http/index.ts

@ -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();
if (token) {
const data = JSON.parse(token);
/** 请求白名单,放置一些不需要token的接口(通过设置请求白名单,防止token过期后再请求造成的死循环问题) */
const whiteList = ["/refreshToken", "/login"];
return whiteList.some(v => config.url.indexOf(v) > -1)
? config
: new Promise(resolve => {
const data = getToken();
if (data) {
const now = new Date().getTime(); const now = new Date().getTime();
const expired = parseInt(data.expires) - now <= 0; const expired = parseInt(data.expires) - now <= 0;
if (expired) { if (expired) {
// token过期刷新 // token过期刷新
useUserStoreHook() useUserStoreHook()
.refreshToken(data)
.then((res: resultType) => {
config.headers["Authorization"] = "Bearer " + res.accessToken;
return $config;
.handRefreshToken({ refreshToken: data.refreshToken })
.then(res => {
config.headers["Authorization"] =
"Bearer " + res.data.accessToken;
resolve($config);
}); });
} else { } else {
config.headers["Authorization"] = "Bearer " + data.accessToken;
return $config;
config.headers["Authorization"] =
"Bearer " + data.accessToken;
resolve($config);
} }
} else { } else {
return $config;
resolve($config);
} }
});
}, },
error => { error => {
return Promise.reject(error); return Promise.reject(error);

23
src/views/login/index.vue

@ -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) {
//
setTimeout(() => {
loading.value = false;
storageSession.setItem("info", {
username: "admin",
accessToken: "eyJhbGciOiJIUzUxMiJ9.test"
});
initRouter("admin").then(() => {});
useUserStoreHook()
.loginByUsername({ username: ruleForm.username })
.then(res => {
if (res.success) {
//
initRouter().then(() => {
message.success("登录成功"); message.success("登录成功");
router.push("/"); 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") {

86
src/views/permission/button/index.vue

@ -1,36 +1,80 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue";
import type { StorageConfigs } from "/#/index";
import { storageSession } from "@pureadmin/utils";
import { type CSSProperties, computed } from "vue";
import { hasAuth, getAuths } from "/@/router/utils";
defineOptions({ defineOptions({
name: "PermissionButton" name: "PermissionButton"
}); });
const auth = ref(
storageSession.getItem<StorageConfigs>("info").username || "admin"
);
function changRole(value) {
storageSession.setItem("info", {
username: value,
accessToken: `eyJhbGciOiJIUzUxMiJ9.${value}`
});
window.location.reload();
}
let width = computed((): CSSProperties => {
return {
width: "85vw"
};
});
</script> </script>
<template> <template>
<el-card>
<el-space direction="vertical" size="large">
<el-tag :style="width" size="large" effect="dark">
当前拥有的code列表{{ getAuths() }}
</el-tag>
<el-card shadow="never" :style="width">
<template #header>
<div class="card-header">组件方式判断权限</div>
</template>
<Auth value="btn_add">
<el-button type="success"> 拥有code'btn_add' 权限可见 </el-button>
</Auth>
<Auth :value="['btn_edit']">
<el-button type="primary"> 拥有code['btn_edit'] 权限可见 </el-button>
</Auth>
<Auth :value="['btn_add', 'btn_edit', 'btn_delete']">
<el-button type="danger">
拥有code['btn_add', 'btn_edit', 'btn_delete'] 权限可见
</el-button>
</Auth>
</el-card>
<el-card shadow="never" :style="width">
<template #header>
<div class="card-header">函数方式判断权限</div>
</template>
<el-button type="success" v-if="hasAuth('btn_add')">
拥有code'btn_add' 权限可见
</el-button>
<el-button type="primary" v-if="hasAuth(['btn_edit'])">
拥有code['btn_edit'] 权限可见
</el-button>
<el-button
type="danger"
v-if="hasAuth(['btn_add', 'btn_edit', 'btn_delete'])"
>
拥有code['btn_add', 'btn_edit', 'btn_delete'] 权限可见
</el-button>
</el-card>
<el-card shadow="never" :style="width">
<template #header> <template #header>
<div class="card-header"> <div class="card-header">
<el-radio-group v-model="auth" @change="changRole">
<el-radio-button label="admin" />
<el-radio-button label="test" />
</el-radio-group>
指令方式判断权限该方式不能动态修改权限
</div> </div>
</template> </template>
<p v-auth="'v-admin'">只有admin可看</p>
<p v-auth="'v-test'">只有test可看</p>
<el-button type="success" v-auth="'btn_add'">
拥有code'btn_add' 权限可见
</el-button>
<el-button type="primary" v-auth="['btn_edit']">
拥有code['btn_edit'] 权限可见
</el-button>
<el-button type="danger" v-auth="['btn_add', 'btn_edit', 'btn_delete']">
拥有code['btn_add', 'btn_edit', 'btn_delete'] 权限可见
</el-button>
</el-card> </el-card>
</el-space>
</template> </template>
<style lang="scss" scoped>
:deep(.el-tag) {
justify-content: start;
}
</style>

86
src/views/permission/page/index.vue

@ -1,53 +1,69 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, unref } from "vue";
import type { StorageConfigs } from "/#/index";
import { storageSession } from "@pureadmin/utils";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
import { initRouter } from "/@/router/utils";
import { type CSSProperties, ref, computed } from "vue";
import { useUserStoreHook } from "/@/store/modules/user";
import { usePermissionStoreHook } from "/@/store/modules/permission";
defineOptions({ defineOptions({
name: "PermissionPage" name: "PermissionPage"
}); });
let purview = ref<string>(
storageSession.getItem<StorageConfigs>("info").username
);
let width = computed((): CSSProperties => {
return {
width: "85vw"
};
});
function changRole() {
if (unref(purview) === "admin") {
storageSession.setItem("info", {
username: "test",
accessToken: "eyJhbGciOiJIUzUxMiJ9.test"
});
window.location.reload();
} else {
storageSession.setItem("info", {
username: "admin",
accessToken: "eyJhbGciOiJIUzUxMiJ9.admin"
});
window.location.reload();
let username = ref(useUserStoreHook()?.username);
const options = [
{
value: "admin",
label: "管理员角色"
},
{
value: "common",
label: "普通角色"
} }
];
function onChange() {
useUserStoreHook()
.loginByUsername({ username: username.value })
.then(res => {
if (res.success) {
usePermissionStoreHook().clearAllCachePage();
initRouter();
}
});
} }
</script> </script>
<template> <template>
<el-card>
<el-space direction="vertical" size="large">
<el-tag :style="width" size="large" effect="dark">
模拟后台根据不同角色返回对应路由具体参考完整版pure-admin代码
</el-tag>
<el-card shadow="never" :style="width">
<template #header> <template #header>
<div class="card-header"> <div class="card-header">
<span>
当前角色
<span style="font-size: 26px">{{ purview }}</span>
<p style="color: #ffa500">
查看左侧菜单变化(系统管理)模拟后台根据不同角色返回对应路由
</p>
</span>
<span>当前角色{{ username }}</span>
</div> </div>
</template> </template>
<el-button
type="primary"
@click="changRole"
:icon="useRenderIcon('user', { color: '#fff' })"
>
切换角色
</el-button>
<el-select v-model="username" @change="onChange">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-card> </el-card>
</el-space>
</template> </template>
<style lang="scss" scoped>
:deep(.el-tag) {
justify-content: start;
}
</style>

1
types/global.d.ts

@ -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"];
} }
} }

6
types/index.ts

@ -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…
Cancel
Save