Commit 5465f058ceb7b130e456feaebb17c3beedb092a5

Authored by vben
1 parent d5b76892

feat(user): add user login expiration example

CHANGELOG.zh_CN.md
... ... @@ -7,6 +7,7 @@
7 7 - 新增 `JsonPreview`Json 数据查看组件
8 8 - 表格的数据列(column)和操作列(actionColumn)的字段可以根据权限和业务来控制是否显示
9 9 - 新增权限控制表格示例(AuthColumn.vue)
  10 +- 新增用户登录过期示例
10 11  
11 12 ### ⚡ Performance Improvements
12 13  
... ...
mock/demo/account.ts
1 1 import { MockMethod } from 'vite-plugin-mock';
2   -import { resultSuccess } from '../_util';
  2 +import { resultSuccess, resultError } from '../_util';
3 3  
4 4 const userInfo = {
5 5 name: 'Vben',
... ... @@ -51,4 +51,12 @@ export default [
51 51 return resultSuccess(userInfo);
52 52 },
53 53 },
  54 + {
  55 + url: '/basic-api/user/sessionTimeout',
  56 + method: 'post',
  57 + statusCode: 401,
  58 + response: () => {
  59 + return resultError();
  60 + },
  61 + },
54 62 ] as MockMethod[];
... ...
src/api/demo/account.ts
... ... @@ -3,8 +3,11 @@ import { GetAccountInfoModel } from './model/accountModel';
3 3  
4 4 enum Api {
5 5 ACCOUNT_INFO = '/account/getAccountInfo',
  6 + SESSION_TIMEOUT = '/user/sessionTimeout',
6 7 }
7 8  
8 9 // Get personal center-basic settings
9 10  
10 11 export const accountInfoApi = () => defHttp.get<GetAccountInfoModel>({ url: Api.ACCOUNT_INFO });
  12 +
  13 +export const sessionTimeoutApi = () => defHttp.post<void>({ url: Api.SESSION_TIMEOUT });
... ...
src/layouts/default/feature/index.vue
... ... @@ -5,28 +5,29 @@
5 5 import { useRootSetting } from '/@/hooks/setting/useRootSetting';
6 6 import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
7 7 import { useDesign } from '/@/hooks/web/useDesign';
  8 + import { useUserStoreWidthOut } from '/@/store/modules/user';
8 9  
9 10 import { SettingButtonPositionEnum } from '/@/enums/appEnum';
10 11 import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
11 12  
  13 + import SessionTimeoutLogin from '/@/views/sys/login/SessionTimeoutLogin.vue';
12 14 export default defineComponent({
13 15 name: 'LayoutFeatures',
14 16 components: {
15 17 BackTop,
16 18 LayoutLockPage: createAsyncComponent(() => import('/@/views/sys/lock/index.vue')),
17 19 SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue')),
  20 + SessionTimeoutLogin,
18 21 },
19 22 setup() {
20   - const {
21   - getUseOpenBackTop,
22   - getShowSettingButton,
23   - getSettingButtonPosition,
24   - getFullContent,
25   - } = useRootSetting();
26   -
  23 + const { getUseOpenBackTop, getShowSettingButton, getSettingButtonPosition, getFullContent } =
  24 + useRootSetting();
  25 + const userStore = useUserStoreWidthOut();
27 26 const { prefixCls } = useDesign('setting-drawer-fearure');
28 27 const { getShowHeader } = useHeaderSetting();
29 28  
  29 + const getIsSessionTimeout = computed(() => userStore.getSessionTimeout);
  30 +
30 31 const getIsFixedSettingDrawer = computed(() => {
31 32 if (!unref(getShowSettingButton)) {
32 33 return false;
... ... @@ -44,6 +45,7 @@
44 45 getUseOpenBackTop,
45 46 getIsFixedSettingDrawer,
46 47 prefixCls,
  48 + getIsSessionTimeout,
47 49 };
48 50 },
49 51 });
... ... @@ -53,6 +55,7 @@
53 55 <LayoutLockPage />
54 56 <BackTop v-if="getUseOpenBackTop" :target="getTarget" />
55 57 <SettingDrawer v-if="getIsFixedSettingDrawer" :class="prefixCls" />
  58 + <SessionTimeoutLogin v-if="getIsSessionTimeout" />
56 59 </template>
57 60  
58 61 <style lang="less">
... ...
src/locales/lang/en/routes/demo/feat.ts
... ... @@ -2,6 +2,7 @@ export default {
2 2 feat: 'Page Function',
3 3 icon: 'Icon',
4 4 tabs: 'Tabs',
  5 + sessionTimeout: 'Session Timeout',
5 6 print: 'Print',
6 7 contextMenu: 'Context Menu',
7 8 download: 'Download',
... ...
src/locales/lang/zh_CN/routes/demo/feat.ts
1 1 export default {
2 2 feat: '功能',
3 3 icon: '图标',
  4 + sessionTimeout: '登录过期',
4 5 tabs: '标签页操作',
5 6 print: '打印',
6 7 contextMenu: '右键菜单',
... ...
src/router/menus/modules/demo/comp.ts
... ... @@ -6,9 +6,6 @@ const menu: MenuModule = {
6 6 menu: {
7 7 name: t('routes.demo.comp.comp'),
8 8 path: '/comp',
9   - tag: {
10   - dot: true,
11   - },
12 9 children: [
13 10 {
14 11 path: 'basic',
... ... @@ -191,9 +188,6 @@ const menu: MenuModule = {
191 188 {
192 189 name: t('routes.demo.editor.editor'),
193 190 path: 'editor',
194   - tag: {
195   - dot: true,
196   - },
197 191 children: [
198 192 {
199 193 path: 'json',
... ...
src/router/menus/modules/demo/feat.ts
... ... @@ -6,7 +6,9 @@ const menu: MenuModule = {
6 6 menu: {
7 7 name: t('routes.demo.feat.feat'),
8 8 path: '/feat',
9   -
  9 + tag: {
  10 + dot: true,
  11 + },
10 12 children: [
11 13 {
12 14 path: 'icon',
... ... @@ -17,6 +19,13 @@ const menu: MenuModule = {
17 19 name: t('routes.demo.feat.ws'),
18 20 },
19 21 {
  22 + name: t('routes.demo.feat.sessionTimeout'),
  23 + path: 'session-timeout',
  24 + tag: {
  25 + content: 'new',
  26 + },
  27 + },
  28 + {
20 29 path: 'tabs',
21 30 name: t('routes.demo.feat.tabs'),
22 31 },
... ...
src/router/menus/modules/demo/flow.ts
... ... @@ -6,17 +6,10 @@ const menu: MenuModule = {
6 6 menu: {
7 7 name: t('routes.demo.flow.name'),
8 8 path: '/flow',
9   - tag: {
10   - dot: true,
11   - },
12   -
13 9 children: [
14 10 {
15 11 path: 'flowChart',
16 12 name: t('routes.demo.flow.flowChart'),
17   - tag: {
18   - content: 'new',
19   - },
20 13 },
21 14 ],
22 15 },
... ...
src/router/routes/modules/demo/feat.ts
... ... @@ -12,6 +12,7 @@ const feat: AppRouteModule = {
12 12 icon: 'ion:git-compare-outline',
13 13 title: t('routes.demo.feat.feat'),
14 14 },
  15 +
15 16 children: [
16 17 {
17 18 path: 'icon',
... ... @@ -30,6 +31,14 @@ const feat: AppRouteModule = {
30 31 },
31 32 },
32 33 {
  34 + path: 'session-timeout',
  35 + name: 'SessionTimeout',
  36 + component: () => import('/@/views/demo/feat/session-timeout/index.vue'),
  37 + meta: {
  38 + title: t('routes.demo.feat.sessionTimeout'),
  39 + },
  40 + },
  41 + {
33 42 path: 'print',
34 43 name: 'Print',
35 44 component: () => import('/@/views/demo/feat/print/index.vue'),
... ...
src/store/modules/user.ts
... ... @@ -25,6 +25,7 @@ interface UserState {
25 25 userInfo: Nullable<UserInfo>;
26 26 token?: string;
27 27 roleList: RoleEnum[];
  28 + sessionTimeout?: boolean;
28 29 }
29 30  
30 31 export const useUserStore = defineStore({
... ... @@ -36,6 +37,8 @@ export const useUserStore = defineStore({
36 37 token: undefined,
37 38 // roleList
38 39 roleList: [],
  40 + // Whether the login expired
  41 + sessionTimeout: false,
39 42 }),
40 43 getters: {
41 44 getUserInfo(): UserInfo {
... ... @@ -47,9 +50,12 @@ export const useUserStore = defineStore({
47 50 getRoleList(): RoleEnum[] {
48 51 return this.roleList.length > 0 ? this.roleList : getAuthCache<RoleEnum[]>(ROLES_KEY);
49 52 },
  53 + getSessionTimeout(): boolean {
  54 + return !!this.sessionTimeout;
  55 + },
50 56 },
51 57 actions: {
52   - setToken(info: string) {
  58 + setToken(info: string | undefined) {
53 59 this.token = info;
54 60 setAuthCache(TOKEN_KEY, info);
55 61 },
... ... @@ -61,10 +67,14 @@ export const useUserStore = defineStore({
61 67 this.userInfo = info;
62 68 setAuthCache(USER_INFO_KEY, info);
63 69 },
  70 + setSessionTimeout(flag: boolean) {
  71 + this.sessionTimeout = flag;
  72 + },
64 73 resetState() {
65 74 this.userInfo = null;
66 75 this.token = '';
67 76 this.roleList = [];
  77 + this.sessionTimeout = false;
68 78 },
69 79 /**
70 80 * @description: login
... ... @@ -85,7 +95,9 @@ export const useUserStore = defineStore({
85 95 // get user info
86 96 const userInfo = await this.getUserInfoAction({ userId });
87 97  
88   - goHome && (await router.replace(PageEnum.BASE_HOME));
  98 + const sessionTimeout = this.sessionTimeout;
  99 + sessionTimeout && this.setSessionTimeout(false);
  100 + !sessionTimeout && goHome && (await router.replace(PageEnum.BASE_HOME));
89 101 return userInfo;
90 102 } catch (error) {
91 103 return null;
... ...
src/utils/http/axios/checkStatus.ts
1 1 import { useMessage } from '/@/hooks/web/useMessage';
2 2 import { useI18n } from '/@/hooks/web/useI18n';
3   -import router from '/@/router';
4   -import { PageEnum } from '/@/enums/pageEnum';
  3 +// import router from '/@/router';
  4 +// import { PageEnum } from '/@/enums/pageEnum';
  5 +import { useUserStoreWidthOut } from '/@/store/modules/user';
5 6  
6 7 const { createMessage } = useMessage();
7 8  
8 9 const error = createMessage.error!;
9 10 export function checkStatus(status: number, msg: string): void {
10 11 const { t } = useI18n();
  12 + const userStore = useUserStoreWidthOut();
11 13 switch (status) {
12 14 case 400:
13 15 error(`${msg}`);
... ... @@ -17,7 +19,8 @@ export function checkStatus(status: number, msg: string): void {
17 19 // Return to the current page after successful login. This step needs to be operated on the login page.
18 20 case 401:
19 21 error(t('sys.api.errMsg401'));
20   - router.push(PageEnum.BASE_LOGIN);
  22 + userStore.setToken(undefined);
  23 + userStore.setSessionTimeout(true);
21 24 break;
22 25 case 403:
23 26 error(t('sys.api.errMsg403'));
... ...
src/utils/http/axios/helper.ts
1 1 import { isObject, isString } from '/@/utils/is';
2 2  
  3 +const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
  4 +
3 5 export function createNow<T extends boolean>(
4 6 join: boolean,
5 7 restful: T
... ... @@ -16,7 +18,6 @@ export function createNow(join: boolean, restful = false): string | object {
16 18 return { _t: now };
17 19 }
18 20  
19   -const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
20 21 /**
21 22 * @description: Format request parameter time
22 23 */
... ...
src/views/demo/feat/session-timeout/index.vue 0 → 100644
  1 +<template>
  2 + <PageWrapper
  3 + title="登录过期示例"
  4 + content="用户登录过期示例,不再跳转登录页,直接生成页面覆盖当前页面,方便保持过期前的用户状态!"
  5 + >
  6 + <a-button type="primary" @click="test">点击触发用户登录过期</a-button>
  7 + </PageWrapper>
  8 +</template>
  9 +<script lang="ts">
  10 + import { defineComponent } from 'vue';
  11 + import { PageWrapper } from '/@/components/Page';
  12 +
  13 + import { sessionTimeoutApi } from '/@/api/demo/account';
  14 +
  15 + export default defineComponent({
  16 + name: 'TestSessionTimeout',
  17 + components: { PageWrapper },
  18 + setup() {
  19 + async function test() {
  20 + await sessionTimeoutApi();
  21 + }
  22 + return { test };
  23 + },
  24 + });
  25 +</script>
... ...
src/views/sys/login/Login.vue
... ... @@ -3,8 +3,9 @@
3 3 <AppLocalePicker
4 4 class="absolute top-4 right-4 enter-x text-white xl:text-gray-600"
5 5 :showText="false"
  6 + v-if="!sessionTimeout"
6 7 />
7   - <AppDarkModeToggle class="absolute top-3 right-7 enter-x" />
  8 + <AppDarkModeToggle class="absolute top-3 right-7 enter-x" v-if="!sessionTimeout" />
8 9  
9 10 <span class="-enter-x xl:hidden">
10 11 <AppLogo :alwaysShowTitle="true" />
... ... @@ -31,7 +32,25 @@
31 32 <div class="h-full xl:h-auto flex py-5 xl:py-0 xl:my-0 w-full xl:w-6/12">
32 33 <div
33 34 :class="`${prefixCls}-form`"
34   - class="my-auto mx-auto xl:ml-20 xl:bg-transparent px-5 py-8 sm:px-8 xl:p-4 rounded-md shadow-md xl:shadow-none w-full sm:w-3/4 lg:w-2/4 xl:w-auto enter-x relative"
  35 + class="
  36 + my-auto
  37 + mx-auto
  38 + xl:ml-20
  39 + xl:bg-transparent
  40 + px-5
  41 + py-8
  42 + sm:px-8
  43 + xl:p-4
  44 + rounded-md
  45 + shadow-md
  46 + xl:shadow-none
  47 + w-full
  48 + sm:w-3/4
  49 + lg:w-2/4
  50 + xl:w-auto
  51 + enter-x
  52 + relative
  53 + "
35 54 >
36 55 <LoginForm />
37 56 <ForgetPasswordForm />
... ... @@ -72,6 +91,11 @@
72 91 AppLocalePicker,
73 92 AppDarkModeToggle,
74 93 },
  94 + props: {
  95 + sessionTimeout: {
  96 + type: Boolean,
  97 + },
  98 + },
75 99 setup() {
76 100 const globSetting = useGlobSetting();
77 101 const { prefixCls } = useDesign('login');
... ...
src/views/sys/login/SessionTimeoutLogin.vue 0 → 100644
  1 +<template>
  2 + <transition>
  3 + <div :class="prefixCls">
  4 + <Login sessionTimeout />
  5 + </div>
  6 + </transition>
  7 +</template>
  8 +<script lang="ts">
  9 + import { defineComponent } from 'vue';
  10 + import Login from './Login.vue';
  11 +
  12 + import { useDesign } from '/@/hooks/web/useDesign';
  13 + export default defineComponent({
  14 + name: 'SessionTimeoutLogin',
  15 + components: { Login },
  16 + setup() {
  17 + const { prefixCls } = useDesign('st-login');
  18 + return { prefixCls };
  19 + },
  20 + });
  21 +</script>
  22 +<style lang="less" scoped>
  23 + @prefix-cls: ~'@{namespace}-st-login';
  24 +
  25 + .@{prefix-cls} {
  26 + position: fixed;
  27 + z-index: 9999999;
  28 + width: 100%;
  29 + height: 100%;
  30 + background: @component-background;
  31 + }
  32 +</style>
... ...