diff --git a/locales/en.yaml b/locales/en.yaml index 3a6f0531f2..6f3982aaaa 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -116,7 +116,8 @@ login: username: Username password: Password verifyCode: VerifyCode - remember: Remember Password + remember: No need to login for 7 days + rememberInfo: After checking and logging in, you will automatically log in to the system without entering your username and password within 7 days sure: Sure Password forget: Forget Password? login: Login diff --git a/locales/zh-CN.yaml b/locales/zh-CN.yaml index 407970de37..f7da8017bf 100644 --- a/locales/zh-CN.yaml +++ b/locales/zh-CN.yaml @@ -116,7 +116,8 @@ login: username: 账号 password: 密码 verifyCode: 验证码 - remember: 记住密码 + remember: 7天内免登录 + rememberInfo: 勾选并登录后,7天内无需输入用户名和密码会自动登入系统 sure: 确认密码 forget: 忘记密码? login: 登录 diff --git a/package.json b/package.json index 4e42c32f81..27a8a69f04 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "prettier": "^3.0.3", "rimraf": "^5.0.5", "rollup-plugin-visualizer": "^5.9.2", - "sass": "^1.68.0", + "sass": "^1.69.0", "sass-loader": "^13.3.2", "stylelint": "^15.10.3", "stylelint-config-html": "^1.1.0", @@ -157,7 +157,7 @@ "tailwindcss": "^3.3.3", "terser": "^5.21.0", "typescript": "^5.2.2", - "vite": "^4.4.10", + "vite": "^4.4.11", "vite-plugin-cdn-import": "^0.3.5", "vite-plugin-compression": "^0.5.1", "vite-plugin-mock": "2.9.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 688c81e95d..2e25219936 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,7 +73,7 @@ specifiers: responsive-storage: ^2.2.0 rimraf: ^5.0.5 rollup-plugin-visualizer: ^5.9.2 - sass: ^1.68.0 + sass: ^1.69.0 sass-loader: ^13.3.2 sortablejs: ^1.15.0 stylelint: ^15.10.3 @@ -96,7 +96,7 @@ specifiers: v-contextmenu: 3.0.0 v3-infinite-loading: ^1.3.1 version-rocket: ^1.7.0 - vite: ^4.4.10 + vite: ^4.4.11 vite-plugin-cdn-import: ^0.3.5 vite-plugin-compression: ^0.5.1 vite-plugin-mock: 2.9.6 @@ -194,8 +194,8 @@ devDependencies: '@types/sortablejs': 1.15.3 '@typescript-eslint/eslint-plugin': 6.7.4_sjhwt3bl5psuxqi3hx6z7r6ola '@typescript-eslint/parser': 6.7.4_jk7qbkaijtltyu4ajmze3dfiwa - '@vitejs/plugin-vue': 4.4.0_vite@4.4.10+vue@3.3.4 - '@vitejs/plugin-vue-jsx': 3.0.2_vite@4.4.10+vue@3.3.4 + '@vitejs/plugin-vue': 4.4.0_vite@4.4.11+vue@3.3.4 + '@vitejs/plugin-vue-jsx': 3.0.2_vite@4.4.11+vue@3.3.4 '@vue/eslint-config-prettier': 8.0.0_rj7fo27gtcc4oitmthuutitbrm '@vue/eslint-config-typescript': 12.0.0_ljkbukdqy6rudcxzcb5p2o2hbq autoprefixer: 10.4.16_postcss@8.4.31 @@ -214,8 +214,8 @@ devDependencies: prettier: 3.0.3 rimraf: 5.0.5 rollup-plugin-visualizer: 5.9.2 - sass: 1.68.0 - sass-loader: 13.3.2_sass@1.68.0 + sass: 1.69.0 + sass-loader: 13.3.2_sass@1.69.0 stylelint: 15.10.3_typescript@5.2.2 stylelint-config-html: 1.1.0_a6l2rvr7enkswjarqif24xxgi4 stylelint-config-recess-order: 4.3.0_stylelint@15.10.3 @@ -231,10 +231,10 @@ devDependencies: tailwindcss: 3.3.3 terser: 5.21.0 typescript: 5.2.2 - vite: 4.4.10_aoxrcfqgusexnpex5mio6763sm + vite: 4.4.11_e5w4bvq32mzkrz2cg5gbeogbay vite-plugin-cdn-import: 0.3.5 - vite-plugin-compression: 0.5.1_vite@4.4.10 - vite-plugin-mock: 2.9.6_mockjs@1.1.0+vite@4.4.10 + vite-plugin-compression: 0.5.1_vite@4.4.11 + vite-plugin-mock: 2.9.6_mockjs@1.1.0+vite@4.4.11 vite-plugin-remove-console: 2.1.1 vite-svg-loader: 4.0.0 vue-eslint-parser: 9.3.1_eslint@8.50.0 @@ -1095,7 +1095,7 @@ packages: ajv: 6.12.6 debug: 4.3.4 espree: 9.6.1 - globals: 13.22.0 + globals: 13.23.0 ignore: 5.2.4 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -1241,7 +1241,7 @@ packages: dependencies: '@intlify/bundle-utils': 7.4.0_vue-i18n@9.5.0 '@intlify/shared': 9.5.0 - '@rollup/pluginutils': 5.0.4 + '@rollup/pluginutils': 5.0.5 '@vue/compiler-sfc': 3.3.4 debug: 4.3.4 fast-glob: 3.3.1 @@ -1716,11 +1716,11 @@ packages: picomatch: 2.3.1 dev: true - /@rollup/pluginutils/5.0.4: - resolution: {integrity: sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==} + /@rollup/pluginutils/5.0.5: + resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==} engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true @@ -2108,7 +2108,7 @@ packages: nanoid: 3.3.6 dev: false - /@vitejs/plugin-vue-jsx/3.0.2_vite@4.4.10+vue@3.3.4: + /@vitejs/plugin-vue-jsx/3.0.2_vite@4.4.11+vue@3.3.4: resolution: {integrity: sha512-obF26P2Z4Ogy3cPp07B4VaW6rpiu0ue4OT2Y15UxT5BZZ76haUY9guOsZV3uWh/I6xc+VeiW+ZVabRE82FyzWw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -2118,20 +2118,20 @@ packages: '@babel/core': 7.23.0 '@babel/plugin-transform-typescript': 7.22.15_@babel+core@7.23.0 '@vue/babel-plugin-jsx': 1.1.5_@babel+core@7.23.0 - vite: 4.4.10_aoxrcfqgusexnpex5mio6763sm + vite: 4.4.11_e5w4bvq32mzkrz2cg5gbeogbay vue: 3.3.4 transitivePeerDependencies: - supports-color dev: true - /@vitejs/plugin-vue/4.4.0_vite@4.4.10+vue@3.3.4: + /@vitejs/plugin-vue/4.4.0_vite@4.4.11+vue@3.3.4: resolution: {integrity: sha512-xdguqb+VUwiRpSg+nsc2HtbAUSGak25DXYvpQQi4RVU1Xq1uworyoH/md9Rfd8zMmPR/pSghr309QNcftUVseg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.0.0 vue: ^3.2.25 dependencies: - vite: 4.4.10_aoxrcfqgusexnpex5mio6763sm + vite: 4.4.11_e5w4bvq32mzkrz2cg5gbeogbay vue: 3.3.4 dev: true @@ -2950,7 +2950,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001546 - electron-to-chromium: 1.4.542 + electron-to-chromium: 1.4.543 node-releases: 2.0.13 update-browserslist-db: 1.0.13_browserslist@4.22.1 @@ -3883,8 +3883,8 @@ packages: - '@vue/composition-api' dev: false - /electron-to-chromium/1.4.542: - resolution: {integrity: sha512-6+cpa00G09N3sfh2joln4VUXHquWrOFx3FLZqiVQvl45+zS9DskDBTPvob+BhvFRmTBkyDSk0vvLMMRo/qc6mQ==} + /electron-to-chromium/1.4.543: + resolution: {integrity: sha512-t2ZP4AcGE0iKCCQCBx/K2426crYdxD3YU6l0uK2EO3FZH0pbC4pFz/sZm2ruZsND6hQBTcDWWlo/MLpiOdif5g==} /element-plus/2.3.14_vue@3.3.4: resolution: {integrity: sha512-9yvxUaU4jXf2ZNPdmIxoj/f8BG8CDcGM6oHa9JIqxLjQlfY4bpzR1E5CjNimnOX3rxO93w1TQ0jTVt0RSxh9kA==} @@ -4171,7 +4171,7 @@ packages: file-entry-cache: 6.0.1 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.22.0 + globals: 13.23.0 graphemer: 1.4.0 ignore: 5.2.4 imurmurhash: 0.1.4 @@ -4625,8 +4625,8 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - /globals/13.22.0: - resolution: {integrity: sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==} + /globals/13.23.0: + resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} engines: {node: '>=8'} dependencies: type-fest: 0.20.2 @@ -7589,7 +7589,7 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: false - /sass-loader/13.3.2_sass@1.68.0: + /sass-loader/13.3.2_sass@1.69.0: resolution: {integrity: sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==} engines: {node: '>= 14.15.0'} peerDependencies: @@ -7611,11 +7611,11 @@ packages: optional: true dependencies: neo-async: 2.6.2 - sass: 1.68.0 + sass: 1.69.0 dev: true - /sass/1.68.0: - resolution: {integrity: sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==} + /sass/1.69.0: + resolution: {integrity: sha512-l3bbFpfTOGgQZCLU/gvm1lbsQ5mC/WnLz3djL2v4WCJBDrWm58PO+jgngcGRNnKUh6wSsdm50YaovTqskZ0xDQ==} engines: {node: '>=14.0.0'} hasBin: true dependencies: @@ -7782,7 +7782,7 @@ packages: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.15 + spdx-license-ids: 3.0.16 dev: true /spdx-exceptions/2.3.0: @@ -7793,11 +7793,11 @@ packages: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} dependencies: spdx-exceptions: 2.3.0 - spdx-license-ids: 3.0.15 + spdx-license-ids: 3.0.16 dev: true - /spdx-license-ids/3.0.15: - resolution: {integrity: sha512-lpT8hSQp9jAKp9mhtBU4Xjon8LPGBvLIuBiSVhMEtmLecTh2mO0tlqrAMp47tBXzMr13NJMQ2lf7RpQGLJ3HsQ==} + /spdx-license-ids/3.0.16: + resolution: {integrity: sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==} dev: true /split2/3.2.2: @@ -8568,7 +8568,7 @@ packages: /unimport/3.4.0: resolution: {integrity: sha512-M/lfFEgufIT156QAr/jWHLUn55kEmxBBiQsMxvRSIbquwmeJEyQYgshHDEvQDWlSJrVOOTAgnJ3FvlsrpGkanA==} dependencies: - '@rollup/pluginutils': 5.0.4 + '@rollup/pluginutils': 5.0.5 escape-string-regexp: 5.0.0 fast-glob: 3.3.1 local-pkg: 0.4.3 @@ -8721,7 +8721,7 @@ packages: - rollup dev: true - /vite-plugin-compression/0.5.1_vite@4.4.10: + /vite-plugin-compression/0.5.1_vite@4.4.11: resolution: {integrity: sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==} peerDependencies: vite: '>=2.0.0' @@ -8729,12 +8729,12 @@ packages: chalk: 4.1.2 debug: 4.3.4 fs-extra: 10.1.0 - vite: 4.4.10_aoxrcfqgusexnpex5mio6763sm + vite: 4.4.11_e5w4bvq32mzkrz2cg5gbeogbay transitivePeerDependencies: - supports-color dev: true - /vite-plugin-mock/2.9.6_mockjs@1.1.0+vite@4.4.10: + /vite-plugin-mock/2.9.6_mockjs@1.1.0+vite@4.4.11: resolution: {integrity: sha512-/Rm59oPppe/ncbkSrUuAxIQihlI2YcBmnbR4ST1RA2VzM1C0tEQc1KlbQvnUGhXECAGTaQN2JyasiwXP6EtKgg==} engines: {node: '>=12.0.0'} peerDependencies: @@ -8751,7 +8751,7 @@ packages: fast-glob: 3.3.1 mockjs: 1.1.0 path-to-regexp: 6.2.1 - vite: 4.4.10_aoxrcfqgusexnpex5mio6763sm + vite: 4.4.11_e5w4bvq32mzkrz2cg5gbeogbay transitivePeerDependencies: - rollup - supports-color @@ -8768,8 +8768,8 @@ packages: svgo: 3.0.2 dev: true - /vite/4.4.10_aoxrcfqgusexnpex5mio6763sm: - resolution: {integrity: sha512-TzIjiqx9BEXF8yzYdF2NTf1kFFbjMjUSV0LFZ3HyHoI3SGSPLnnFUKiIQtL3gl2AjHvMrprOvQ3amzaHgQlAxw==} + /vite/4.4.11_e5w4bvq32mzkrz2cg5gbeogbay: + resolution: {integrity: sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -8800,7 +8800,7 @@ packages: esbuild: 0.18.20 postcss: 8.4.31 rollup: 3.29.4 - sass: 1.68.0 + sass: 1.69.0 terser: 5.21.0 optionalDependencies: fsevents: 2.3.3 diff --git a/src/layout/components/setting/index.vue b/src/layout/components/setting/index.vue index 6543f6587b..9600a8455f 100644 --- a/src/layout/components/setting/index.vue +++ b/src/layout/components/setting/index.vue @@ -8,13 +8,6 @@ import { nextTick, onBeforeMount } from "vue"; -import { - useDark, - debounce, - useGlobal, - storageLocal, - storageSession -} from "@pureadmin/utils"; import { getConfig } from "@/config"; import { useRouter } from "vue-router"; import panel from "../panel/index.vue"; @@ -27,6 +20,7 @@ import { useAppStoreHook } from "@/store/modules/app"; import { toggleTheme } from "@pureadmin/theme/dist/browser-utils"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange"; +import { useDark, debounce, useGlobal, storageLocal } from "@pureadmin/utils"; import dayIcon from "@/assets/svg/day.svg?component"; import darkIcon from "@/assets/svg/dark.svg?component"; @@ -133,7 +127,6 @@ const multiTagsCacheChange = () => { function onReset() { removeToken(); storageLocal().clear(); - storageSession().clear(); const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig(); useAppStoreHook().setLayout(Layout); setEpThemeColor(EpThemeColor); diff --git a/src/router/index.ts b/src/router/index.ts index b4b658357e..6625bc666f 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,16 +1,13 @@ import "@/utils/sso"; +import Cookies from "js-cookie"; import { getConfig } from "@/config"; import NProgress from "@/utils/progress"; import { transformI18n } from "@/plugins/i18n"; -import { sessionKey, type DataInfo } from "@/utils/auth"; +import { buildHierarchyTree } from "@/utils/tree"; +import remainingRouter from "./modules/remaining"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { usePermissionStoreHook } from "@/store/modules/permission"; -import { - Router, - createRouter, - RouteRecordRaw, - RouteComponent -} from "vue-router"; +import { isUrl, openLink, storageLocal, isAllEmpty } from "@pureadmin/utils"; import { ascending, getTopMenu, @@ -22,10 +19,18 @@ import { formatTwoStageRoutes, formatFlatteningRoutes } from "./utils"; -import { buildHierarchyTree } from "@/utils/tree"; -import { isUrl, openLink, storageSession, isAllEmpty } from "@pureadmin/utils"; - -import remainingRouter from "./modules/remaining"; +import { + Router, + createRouter, + RouteRecordRaw, + RouteComponent +} from "vue-router"; +import { + type DataInfo, + userKey, + removeToken, + multipleTabsKey +} from "@/utils/auth"; /** 自动导入全部静态路由,无需再手动引入!匹配 src/router/modules 目录(任何嵌套级别)中具有 .ts 扩展名的所有文件,除了 remaining.ts 文件 * 如何匹配所有文件请看:https://github.com/mrmlnc/fast-glob#basic-syntax @@ -109,7 +114,7 @@ router.beforeEach((to: ToRouteType, _from, next) => { handleAliveRoute(to); } } - const userInfo = storageSession().getItem>(sessionKey); + const userInfo = storageLocal().getItem>(userKey); NProgress.start(); const externalLink = isUrl(to?.name as string); if (!externalLink) { @@ -125,7 +130,7 @@ router.beforeEach((to: ToRouteType, _from, next) => { function toCorrectRoute() { whiteList.includes(to.fullPath) ? next(_from.fullPath) : next(); } - if (userInfo) { + if (Cookies.get(multipleTabsKey) && userInfo) { // 无权限跳转403页面 if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) { next({ path: "/error/403" }); @@ -187,6 +192,7 @@ router.beforeEach((to: ToRouteType, _from, next) => { if (whiteList.indexOf(to.path) !== -1) { next(); } else { + removeToken(); next({ path: "/login" }); } } else { diff --git a/src/router/utils.ts b/src/router/utils.ts index 2436e2be86..bd89e260fd 100644 --- a/src/router/utils.ts +++ b/src/router/utils.ts @@ -13,13 +13,13 @@ import { cloneDeep, isAllEmpty, intersection, - storageSession, + storageLocal, isIncludeAllChildren } from "@pureadmin/utils"; import { getConfig } from "@/config"; import { menuType } from "@/layout/types"; import { buildHierarchyTree } from "@/utils/tree"; -import { sessionKey, type DataInfo } from "@/utils/auth"; +import { userKey, type DataInfo } from "@/utils/auth"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { usePermissionStoreHook } from "@/store/modules/permission"; const IFrame = () => import("@/layout/frameView.vue"); @@ -81,10 +81,10 @@ function isOneOfArray(a: Array, b: Array) { : true; } -/** 从sessionStorage里取出当前登陆用户的角色roles,过滤无权限的菜单 */ +/** 从localStorage里取出当前登陆用户的角色roles,过滤无权限的菜单 */ function filterNoPermissionTree(data: RouteComponent[]) { const currentRoles = - storageSession().getItem>(sessionKey)?.roles ?? []; + storageLocal().getItem>(userKey)?.roles ?? []; const newTree = cloneDeep(data).filter((v: any) => isOneOfArray(v.meta?.roles, currentRoles) ); @@ -184,9 +184,9 @@ function handleAsyncRoutes(routeList) { /** 初始化路由(`new Promise` 写法防止在异步请求中造成无限循环)*/ function initRouter() { if (getConfig()?.CachingAsyncRoutes) { - // 开启动态路由缓存本地sessionStorage + // 开启动态路由缓存本地localStorage const key = "async-routes"; - const asyncRouteList = storageSession().getItem(key) as any; + const asyncRouteList = storageLocal().getItem(key) as any; if (asyncRouteList && asyncRouteList?.length > 0) { return new Promise(resolve => { handleAsyncRoutes(asyncRouteList); @@ -196,7 +196,7 @@ function initRouter() { return new Promise(resolve => { getAsyncRoutes().then(({ data }) => { handleAsyncRoutes(cloneDeep(data)); - storageSession().setItem(key, data); + storageLocal().setItem(key, data); resolve(router); }); }); diff --git a/src/store/modules/types.ts b/src/store/modules/types.ts index 9ac647d7f6..352e3732a1 100644 --- a/src/store/modules/types.ts +++ b/src/store/modules/types.ts @@ -41,4 +41,5 @@ export type userType = { roles?: Array; verifyCode?: string; currentPage?: number; + isRemembered?: boolean; }; diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index e6b24d7698..7b39e1fcd0 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -3,24 +3,25 @@ import { store } from "@/store"; import { userType } from "./types"; import { routerArrays } from "@/layout/types"; import { router, resetRouter } from "@/router"; -import { storageSession } from "@pureadmin/utils"; +import { storageLocal } from "@pureadmin/utils"; import { getLogin, refreshTokenApi } from "@/api/user"; import { UserResult, RefreshTokenResult } from "@/api/user"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; -import { type DataInfo, setToken, removeToken, sessionKey } from "@/utils/auth"; +import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth"; export const useUserStore = defineStore({ id: "pure-user", state: (): userType => ({ // 用户名 - username: - storageSession().getItem>(sessionKey)?.username ?? "", + username: storageLocal().getItem>(userKey)?.username ?? "", // 页面级别权限 - roles: storageSession().getItem>(sessionKey)?.roles ?? [], + roles: storageLocal().getItem>(userKey)?.roles ?? [], // 前端生成的验证码(按实际需求替换) verifyCode: "", // 判断登录页面显示哪个组件(0:登录(默认)、1:手机登录、2:二维码登录、3:注册、4:忘记密码) - currentPage: 0 + currentPage: 0, + // 是否勾选了7天内免登录 + isRemembered: false }), actions: { /** 存储用户名 */ @@ -39,6 +40,10 @@ export const useUserStore = defineStore({ SET_CURRENTPAGE(value: number) { this.currentPage = value; }, + /** 存储是否勾选了7天内免登录 */ + SET_ISREMEMBERED(bool: boolean) { + this.isRemembered = bool; + }, /** 登入 */ async loginByUsername(data) { return new Promise((resolve, reject) => { diff --git a/src/utils/auth.ts b/src/utils/auth.ts index a673803442..14774651d3 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,5 +1,5 @@ import Cookies from "js-cookie"; -import { storageSession } from "@pureadmin/utils"; +import { storageLocal } from "@pureadmin/utils"; import { useUserStoreHook } from "@/store/modules/user"; export interface DataInfo { @@ -15,22 +15,29 @@ export interface DataInfo { roles?: Array; } -export const sessionKey = "user-info"; +export const userKey = "user-info"; export const TokenKey = "authorized-token"; +/** + * 通过`multiple-tabs`是否在`cookie`中,判断用户是否已经登录系统, + * 从而支持多标签页打开已经登录的系统后无需再登录。 + * 浏览器完全关闭后`multiple-tabs`将自动从`cookie`中销毁, + * 再次打开浏览器需要重新登录系统 + * */ +export const multipleTabsKey = "multiple-tabs"; /** 获取`token` */ export function getToken(): DataInfo { // 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错 return Cookies.get(TokenKey) ? JSON.parse(Cookies.get(TokenKey)) - : storageSession().getItem(sessionKey); + : storageLocal().getItem(userKey); } /** * @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里(浏览器关闭自动销毁) + * 将`username`、`roles`、`refreshToken`、`expires`这四条信息放在key值为`user-info`的localStorage里(利用`multipleTabsKey`当浏览器完全关闭后自动销毁) */ export function setToken(data: DataInfo) { let expires = 0; @@ -44,10 +51,20 @@ export function setToken(data: DataInfo) { }) : Cookies.set(TokenKey, cookieString); - function setSessionKey(username: string, roles: Array) { + Cookies.set( + multipleTabsKey, + "true", + useUserStoreHook().isRemembered + ? { + expires: 7 + } + : {} + ); + + function setUserKey(username: string, roles: Array) { useUserStoreHook().SET_USERNAME(username); useUserStoreHook().SET_ROLES(roles); - storageSession().setItem(sessionKey, { + storageLocal().setItem(userKey, { refreshToken, expires, username, @@ -57,20 +74,21 @@ export function setToken(data: DataInfo) { if (data.username && data.roles) { const { username, roles } = data; - setSessionKey(username, roles); + setUserKey(username, roles); } else { const username = - storageSession().getItem>(sessionKey)?.username ?? ""; + storageLocal().getItem>(userKey)?.username ?? ""; const roles = - storageSession().getItem>(sessionKey)?.roles ?? []; - setSessionKey(username, roles); + storageLocal().getItem>(userKey)?.roles ?? []; + setUserKey(username, roles); } } -/** 删除`token`以及key值为`user-info`的session信息 */ +/** 删除`token`以及key值为`user-info`的localStorage信息 */ export function removeToken() { Cookies.remove(TokenKey); - sessionStorage.clear(); + Cookies.remove(multipleTabsKey); + storageLocal().removeItem(userKey); } /** 格式化token(jwt格式) */ diff --git a/src/views/login/index.vue b/src/views/login/index.vue index a3115e238c..ee81bb75f3 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -37,6 +37,7 @@ import globalization from "@/assets/svg/globalization.svg?component"; import Lock from "@iconify-icons/ri/lock-fill"; import Check from "@iconify-icons/ep/check"; import User from "@iconify-icons/ri/user-3-fill"; +import Info from "@iconify-icons/ri/information-line"; defineOptions({ name: "Login" @@ -107,6 +108,9 @@ onBeforeUnmount(() => { watch(imgCode, value => { useUserStoreHook().SET_VERIFYCODE(value); }); +watch(checked, bool => { + useUserStoreHook().SET_ISREMEMBERED(bool); +});