Browse Source

feat(user): add user login expiration example

vben 3 years ago
parent
commit
5465f058ce

+ 1 - 0
CHANGELOG.zh_CN.md

@@ -7,6 +7,7 @@
 - 新增 `JsonPreview`Json 数据查看组件
 - 表格的数据列(column)和操作列(actionColumn)的字段可以根据权限和业务来控制是否显示
 - 新增权限控制表格示例(AuthColumn.vue)
+- 新增用户登录过期示例
 
 ### ⚡ Performance Improvements
 

+ 9 - 1
mock/demo/account.ts

@@ -1,5 +1,5 @@
 import { MockMethod } from 'vite-plugin-mock';
-import { resultSuccess } from '../_util';
+import { resultSuccess, resultError } from '../_util';
 
 const userInfo = {
   name: 'Vben',
@@ -51,4 +51,12 @@ export default [
       return resultSuccess(userInfo);
     },
   },
+  {
+    url: '/basic-api/user/sessionTimeout',
+    method: 'post',
+    statusCode: 401,
+    response: () => {
+      return resultError();
+    },
+  },
 ] as MockMethod[];

+ 3 - 0
src/api/demo/account.ts

@@ -3,8 +3,11 @@ import { GetAccountInfoModel } from './model/accountModel';
 
 enum Api {
   ACCOUNT_INFO = '/account/getAccountInfo',
+  SESSION_TIMEOUT = '/user/sessionTimeout',
 }
 
 // Get personal center-basic settings
 
 export const accountInfoApi = () => defHttp.get<GetAccountInfoModel>({ url: Api.ACCOUNT_INFO });
+
+export const sessionTimeoutApi = () => defHttp.post<void>({ url: Api.SESSION_TIMEOUT });

+ 10 - 7
src/layouts/default/feature/index.vue

@@ -5,28 +5,29 @@
   import { useRootSetting } from '/@/hooks/setting/useRootSetting';
   import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
   import { useDesign } from '/@/hooks/web/useDesign';
+  import { useUserStoreWidthOut } from '/@/store/modules/user';
 
   import { SettingButtonPositionEnum } from '/@/enums/appEnum';
   import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
 
+  import SessionTimeoutLogin from '/@/views/sys/login/SessionTimeoutLogin.vue';
   export default defineComponent({
     name: 'LayoutFeatures',
     components: {
       BackTop,
       LayoutLockPage: createAsyncComponent(() => import('/@/views/sys/lock/index.vue')),
       SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue')),
+      SessionTimeoutLogin,
     },
     setup() {
-      const {
-        getUseOpenBackTop,
-        getShowSettingButton,
-        getSettingButtonPosition,
-        getFullContent,
-      } = useRootSetting();
-
+      const { getUseOpenBackTop, getShowSettingButton, getSettingButtonPosition, getFullContent } =
+        useRootSetting();
+      const userStore = useUserStoreWidthOut();
       const { prefixCls } = useDesign('setting-drawer-fearure');
       const { getShowHeader } = useHeaderSetting();
 
+      const getIsSessionTimeout = computed(() => userStore.getSessionTimeout);
+
       const getIsFixedSettingDrawer = computed(() => {
         if (!unref(getShowSettingButton)) {
           return false;
@@ -44,6 +45,7 @@
         getUseOpenBackTop,
         getIsFixedSettingDrawer,
         prefixCls,
+        getIsSessionTimeout,
       };
     },
   });
@@ -53,6 +55,7 @@
   <LayoutLockPage />
   <BackTop v-if="getUseOpenBackTop" :target="getTarget" />
   <SettingDrawer v-if="getIsFixedSettingDrawer" :class="prefixCls" />
+  <SessionTimeoutLogin v-if="getIsSessionTimeout" />
 </template>
 
 <style lang="less">

+ 1 - 0
src/locales/lang/en/routes/demo/feat.ts

@@ -2,6 +2,7 @@ export default {
   feat: 'Page Function',
   icon: 'Icon',
   tabs: 'Tabs',
+  sessionTimeout: 'Session Timeout',
   print: 'Print',
   contextMenu: 'Context Menu',
   download: 'Download',

+ 1 - 0
src/locales/lang/zh_CN/routes/demo/feat.ts

@@ -1,6 +1,7 @@
 export default {
   feat: '功能',
   icon: '图标',
+  sessionTimeout: '登录过期',
   tabs: '标签页操作',
   print: '打印',
   contextMenu: '右键菜单',

+ 0 - 6
src/router/menus/modules/demo/comp.ts

@@ -6,9 +6,6 @@ const menu: MenuModule = {
   menu: {
     name: t('routes.demo.comp.comp'),
     path: '/comp',
-    tag: {
-      dot: true,
-    },
     children: [
       {
         path: 'basic',
@@ -191,9 +188,6 @@ const menu: MenuModule = {
       {
         name: t('routes.demo.editor.editor'),
         path: 'editor',
-        tag: {
-          dot: true,
-        },
         children: [
           {
             path: 'json',

+ 10 - 1
src/router/menus/modules/demo/feat.ts

@@ -6,7 +6,9 @@ const menu: MenuModule = {
   menu: {
     name: t('routes.demo.feat.feat'),
     path: '/feat',
-
+    tag: {
+      dot: true,
+    },
     children: [
       {
         path: 'icon',
@@ -17,6 +19,13 @@ const menu: MenuModule = {
         name: t('routes.demo.feat.ws'),
       },
       {
+        name: t('routes.demo.feat.sessionTimeout'),
+        path: 'session-timeout',
+        tag: {
+          content: 'new',
+        },
+      },
+      {
         path: 'tabs',
         name: t('routes.demo.feat.tabs'),
       },

+ 0 - 7
src/router/menus/modules/demo/flow.ts

@@ -6,17 +6,10 @@ const menu: MenuModule = {
   menu: {
     name: t('routes.demo.flow.name'),
     path: '/flow',
-    tag: {
-      dot: true,
-    },
-
     children: [
       {
         path: 'flowChart',
         name: t('routes.demo.flow.flowChart'),
-        tag: {
-          content: 'new',
-        },
       },
     ],
   },

+ 9 - 0
src/router/routes/modules/demo/feat.ts

@@ -12,6 +12,7 @@ const feat: AppRouteModule = {
     icon: 'ion:git-compare-outline',
     title: t('routes.demo.feat.feat'),
   },
+
   children: [
     {
       path: 'icon',
@@ -30,6 +31,14 @@ const feat: AppRouteModule = {
       },
     },
     {
+      path: 'session-timeout',
+      name: 'SessionTimeout',
+      component: () => import('/@/views/demo/feat/session-timeout/index.vue'),
+      meta: {
+        title: t('routes.demo.feat.sessionTimeout'),
+      },
+    },
+    {
       path: 'print',
       name: 'Print',
       component: () => import('/@/views/demo/feat/print/index.vue'),

+ 14 - 2
src/store/modules/user.ts

@@ -25,6 +25,7 @@ interface UserState {
   userInfo: Nullable<UserInfo>;
   token?: string;
   roleList: RoleEnum[];
+  sessionTimeout?: boolean;
 }
 
 export const useUserStore = defineStore({
@@ -36,6 +37,8 @@ export const useUserStore = defineStore({
     token: undefined,
     // roleList
     roleList: [],
+    // Whether the login expired
+    sessionTimeout: false,
   }),
   getters: {
     getUserInfo(): UserInfo {
@@ -47,9 +50,12 @@ export const useUserStore = defineStore({
     getRoleList(): RoleEnum[] {
       return this.roleList.length > 0 ? this.roleList : getAuthCache<RoleEnum[]>(ROLES_KEY);
     },
+    getSessionTimeout(): boolean {
+      return !!this.sessionTimeout;
+    },
   },
   actions: {
-    setToken(info: string) {
+    setToken(info: string | undefined) {
       this.token = info;
       setAuthCache(TOKEN_KEY, info);
     },
@@ -61,10 +67,14 @@ export const useUserStore = defineStore({
       this.userInfo = info;
       setAuthCache(USER_INFO_KEY, info);
     },
+    setSessionTimeout(flag: boolean) {
+      this.sessionTimeout = flag;
+    },
     resetState() {
       this.userInfo = null;
       this.token = '';
       this.roleList = [];
+      this.sessionTimeout = false;
     },
     /**
      * @description: login
@@ -85,7 +95,9 @@ export const useUserStore = defineStore({
         // get user info
         const userInfo = await this.getUserInfoAction({ userId });
 
-        goHome && (await router.replace(PageEnum.BASE_HOME));
+        const sessionTimeout = this.sessionTimeout;
+        sessionTimeout && this.setSessionTimeout(false);
+        !sessionTimeout && goHome && (await router.replace(PageEnum.BASE_HOME));
         return userInfo;
       } catch (error) {
         return null;

+ 6 - 3
src/utils/http/axios/checkStatus.ts

@@ -1,13 +1,15 @@
 import { useMessage } from '/@/hooks/web/useMessage';
 import { useI18n } from '/@/hooks/web/useI18n';
-import router from '/@/router';
-import { PageEnum } from '/@/enums/pageEnum';
+// import router from '/@/router';
+// import { PageEnum } from '/@/enums/pageEnum';
+import { useUserStoreWidthOut } from '/@/store/modules/user';
 
 const { createMessage } = useMessage();
 
 const error = createMessage.error!;
 export function checkStatus(status: number, msg: string): void {
   const { t } = useI18n();
+  const userStore = useUserStoreWidthOut();
   switch (status) {
     case 400:
       error(`${msg}`);
@@ -17,7 +19,8 @@ export function checkStatus(status: number, msg: string): void {
     // Return to the current page after successful login. This step needs to be operated on the login page.
     case 401:
       error(t('sys.api.errMsg401'));
-      router.push(PageEnum.BASE_LOGIN);
+      userStore.setToken(undefined);
+      userStore.setSessionTimeout(true);
       break;
     case 403:
       error(t('sys.api.errMsg403'));

+ 2 - 1
src/utils/http/axios/helper.ts

@@ -1,5 +1,7 @@
 import { isObject, isString } from '/@/utils/is';
 
+const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
+
 export function createNow<T extends boolean>(
   join: boolean,
   restful: T
@@ -16,7 +18,6 @@ export function createNow(join: boolean, restful = false): string | object {
   return { _t: now };
 }
 
-const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
 /**
  * @description: Format request parameter time
  */

+ 25 - 0
src/views/demo/feat/session-timeout/index.vue

@@ -0,0 +1,25 @@
+<template>
+  <PageWrapper
+    title="登录过期示例"
+    content="用户登录过期示例,不再跳转登录页,直接生成页面覆盖当前页面,方便保持过期前的用户状态!"
+  >
+    <a-button type="primary" @click="test">点击触发用户登录过期</a-button>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { PageWrapper } from '/@/components/Page';
+
+  import { sessionTimeoutApi } from '/@/api/demo/account';
+
+  export default defineComponent({
+    name: 'TestSessionTimeout',
+    components: { PageWrapper },
+    setup() {
+      async function test() {
+        await sessionTimeoutApi();
+      }
+      return { test };
+    },
+  });
+</script>

+ 26 - 2
src/views/sys/login/Login.vue

@@ -3,8 +3,9 @@
     <AppLocalePicker
       class="absolute top-4 right-4 enter-x text-white xl:text-gray-600"
       :showText="false"
+      v-if="!sessionTimeout"
     />
-    <AppDarkModeToggle class="absolute top-3 right-7 enter-x" />
+    <AppDarkModeToggle class="absolute top-3 right-7 enter-x" v-if="!sessionTimeout" />
 
     <span class="-enter-x xl:hidden">
       <AppLogo :alwaysShowTitle="true" />
@@ -31,7 +32,25 @@
         <div class="h-full xl:h-auto flex py-5 xl:py-0 xl:my-0 w-full xl:w-6/12">
           <div
             :class="`${prefixCls}-form`"
-            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"
+            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
+            "
           >
             <LoginForm />
             <ForgetPasswordForm />
@@ -72,6 +91,11 @@
       AppLocalePicker,
       AppDarkModeToggle,
     },
+    props: {
+      sessionTimeout: {
+        type: Boolean,
+      },
+    },
     setup() {
       const globSetting = useGlobSetting();
       const { prefixCls } = useDesign('login');

+ 32 - 0
src/views/sys/login/SessionTimeoutLogin.vue

@@ -0,0 +1,32 @@
+<template>
+  <transition>
+    <div :class="prefixCls">
+      <Login sessionTimeout />
+    </div>
+  </transition>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import Login from './Login.vue';
+
+  import { useDesign } from '/@/hooks/web/useDesign';
+  export default defineComponent({
+    name: 'SessionTimeoutLogin',
+    components: { Login },
+    setup() {
+      const { prefixCls } = useDesign('st-login');
+      return { prefixCls };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  @prefix-cls: ~'@{namespace}-st-login';
+
+  .@{prefix-cls} {
+    position: fixed;
+    z-index: 9999999;
+    width: 100%;
+    height: 100%;
+    background: @component-background;
+  }
+</style>