提交 5465f058 作者: vben

feat(user): add user login expiration example

上级 d5b76892
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
- 新增 `JsonPreview`Json 数据查看组件 - 新增 `JsonPreview`Json 数据查看组件
- 表格的数据列(column)和操作列(actionColumn)的字段可以根据权限和业务来控制是否显示 - 表格的数据列(column)和操作列(actionColumn)的字段可以根据权限和业务来控制是否显示
- 新增权限控制表格示例(AuthColumn.vue) - 新增权限控制表格示例(AuthColumn.vue)
- 新增用户登录过期示例
### ⚡ Performance Improvements ### ⚡ Performance Improvements
......
import { MockMethod } from 'vite-plugin-mock'; import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess } from '../_util'; import { resultSuccess, resultError } from '../_util';
const userInfo = { const userInfo = {
name: 'Vben', name: 'Vben',
...@@ -51,4 +51,12 @@ export default [ ...@@ -51,4 +51,12 @@ export default [
return resultSuccess(userInfo); return resultSuccess(userInfo);
}, },
}, },
{
url: '/basic-api/user/sessionTimeout',
method: 'post',
statusCode: 401,
response: () => {
return resultError();
},
},
] as MockMethod[]; ] as MockMethod[];
...@@ -3,8 +3,11 @@ import { GetAccountInfoModel } from './model/accountModel'; ...@@ -3,8 +3,11 @@ import { GetAccountInfoModel } from './model/accountModel';
enum Api { enum Api {
ACCOUNT_INFO = '/account/getAccountInfo', ACCOUNT_INFO = '/account/getAccountInfo',
SESSION_TIMEOUT = '/user/sessionTimeout',
} }
// Get personal center-basic settings // Get personal center-basic settings
export const accountInfoApi = () => defHttp.get<GetAccountInfoModel>({ url: Api.ACCOUNT_INFO }); export const accountInfoApi = () => defHttp.get<GetAccountInfoModel>({ url: Api.ACCOUNT_INFO });
export const sessionTimeoutApi = () => defHttp.post<void>({ url: Api.SESSION_TIMEOUT });
...@@ -5,28 +5,29 @@ ...@@ -5,28 +5,29 @@
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting'; import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { useUserStoreWidthOut } from '/@/store/modules/user';
import { SettingButtonPositionEnum } from '/@/enums/appEnum'; import { SettingButtonPositionEnum } from '/@/enums/appEnum';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import SessionTimeoutLogin from '/@/views/sys/login/SessionTimeoutLogin.vue';
export default defineComponent({ export default defineComponent({
name: 'LayoutFeatures', name: 'LayoutFeatures',
components: { components: {
BackTop, BackTop,
LayoutLockPage: createAsyncComponent(() => import('/@/views/sys/lock/index.vue')), LayoutLockPage: createAsyncComponent(() => import('/@/views/sys/lock/index.vue')),
SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue')), SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue')),
SessionTimeoutLogin,
}, },
setup() { setup() {
const { const { getUseOpenBackTop, getShowSettingButton, getSettingButtonPosition, getFullContent } =
getUseOpenBackTop, useRootSetting();
getShowSettingButton, const userStore = useUserStoreWidthOut();
getSettingButtonPosition,
getFullContent,
} = useRootSetting();
const { prefixCls } = useDesign('setting-drawer-fearure'); const { prefixCls } = useDesign('setting-drawer-fearure');
const { getShowHeader } = useHeaderSetting(); const { getShowHeader } = useHeaderSetting();
const getIsSessionTimeout = computed(() => userStore.getSessionTimeout);
const getIsFixedSettingDrawer = computed(() => { const getIsFixedSettingDrawer = computed(() => {
if (!unref(getShowSettingButton)) { if (!unref(getShowSettingButton)) {
return false; return false;
...@@ -44,6 +45,7 @@ ...@@ -44,6 +45,7 @@
getUseOpenBackTop, getUseOpenBackTop,
getIsFixedSettingDrawer, getIsFixedSettingDrawer,
prefixCls, prefixCls,
getIsSessionTimeout,
}; };
}, },
}); });
...@@ -53,6 +55,7 @@ ...@@ -53,6 +55,7 @@
<LayoutLockPage /> <LayoutLockPage />
<BackTop v-if="getUseOpenBackTop" :target="getTarget" /> <BackTop v-if="getUseOpenBackTop" :target="getTarget" />
<SettingDrawer v-if="getIsFixedSettingDrawer" :class="prefixCls" /> <SettingDrawer v-if="getIsFixedSettingDrawer" :class="prefixCls" />
<SessionTimeoutLogin v-if="getIsSessionTimeout" />
</template> </template>
<style lang="less"> <style lang="less">
......
...@@ -2,6 +2,7 @@ export default { ...@@ -2,6 +2,7 @@ export default {
feat: 'Page Function', feat: 'Page Function',
icon: 'Icon', icon: 'Icon',
tabs: 'Tabs', tabs: 'Tabs',
sessionTimeout: 'Session Timeout',
print: 'Print', print: 'Print',
contextMenu: 'Context Menu', contextMenu: 'Context Menu',
download: 'Download', download: 'Download',
......
export default { export default {
feat: '功能', feat: '功能',
icon: '图标', icon: '图标',
sessionTimeout: '登录过期',
tabs: '标签页操作', tabs: '标签页操作',
print: '打印', print: '打印',
contextMenu: '右键菜单', contextMenu: '右键菜单',
......
...@@ -6,9 +6,6 @@ const menu: MenuModule = { ...@@ -6,9 +6,6 @@ const menu: MenuModule = {
menu: { menu: {
name: t('routes.demo.comp.comp'), name: t('routes.demo.comp.comp'),
path: '/comp', path: '/comp',
tag: {
dot: true,
},
children: [ children: [
{ {
path: 'basic', path: 'basic',
...@@ -191,9 +188,6 @@ const menu: MenuModule = { ...@@ -191,9 +188,6 @@ const menu: MenuModule = {
{ {
name: t('routes.demo.editor.editor'), name: t('routes.demo.editor.editor'),
path: 'editor', path: 'editor',
tag: {
dot: true,
},
children: [ children: [
{ {
path: 'json', path: 'json',
......
...@@ -6,7 +6,9 @@ const menu: MenuModule = { ...@@ -6,7 +6,9 @@ const menu: MenuModule = {
menu: { menu: {
name: t('routes.demo.feat.feat'), name: t('routes.demo.feat.feat'),
path: '/feat', path: '/feat',
tag: {
dot: true,
},
children: [ children: [
{ {
path: 'icon', path: 'icon',
...@@ -17,6 +19,13 @@ const menu: MenuModule = { ...@@ -17,6 +19,13 @@ const menu: MenuModule = {
name: t('routes.demo.feat.ws'), name: t('routes.demo.feat.ws'),
}, },
{ {
name: t('routes.demo.feat.sessionTimeout'),
path: 'session-timeout',
tag: {
content: 'new',
},
},
{
path: 'tabs', path: 'tabs',
name: t('routes.demo.feat.tabs'), name: t('routes.demo.feat.tabs'),
}, },
......
...@@ -6,17 +6,10 @@ const menu: MenuModule = { ...@@ -6,17 +6,10 @@ const menu: MenuModule = {
menu: { menu: {
name: t('routes.demo.flow.name'), name: t('routes.demo.flow.name'),
path: '/flow', path: '/flow',
tag: {
dot: true,
},
children: [ children: [
{ {
path: 'flowChart', path: 'flowChart',
name: t('routes.demo.flow.flowChart'), name: t('routes.demo.flow.flowChart'),
tag: {
content: 'new',
},
}, },
], ],
}, },
......
...@@ -12,6 +12,7 @@ const feat: AppRouteModule = { ...@@ -12,6 +12,7 @@ const feat: AppRouteModule = {
icon: 'ion:git-compare-outline', icon: 'ion:git-compare-outline',
title: t('routes.demo.feat.feat'), title: t('routes.demo.feat.feat'),
}, },
children: [ children: [
{ {
path: 'icon', path: 'icon',
...@@ -30,6 +31,14 @@ const feat: AppRouteModule = { ...@@ -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', path: 'print',
name: 'Print', name: 'Print',
component: () => import('/@/views/demo/feat/print/index.vue'), component: () => import('/@/views/demo/feat/print/index.vue'),
......
...@@ -25,6 +25,7 @@ interface UserState { ...@@ -25,6 +25,7 @@ interface UserState {
userInfo: Nullable<UserInfo>; userInfo: Nullable<UserInfo>;
token?: string; token?: string;
roleList: RoleEnum[]; roleList: RoleEnum[];
sessionTimeout?: boolean;
} }
export const useUserStore = defineStore({ export const useUserStore = defineStore({
...@@ -36,6 +37,8 @@ export const useUserStore = defineStore({ ...@@ -36,6 +37,8 @@ export const useUserStore = defineStore({
token: undefined, token: undefined,
// roleList // roleList
roleList: [], roleList: [],
// Whether the login expired
sessionTimeout: false,
}), }),
getters: { getters: {
getUserInfo(): UserInfo { getUserInfo(): UserInfo {
...@@ -47,9 +50,12 @@ export const useUserStore = defineStore({ ...@@ -47,9 +50,12 @@ export const useUserStore = defineStore({
getRoleList(): RoleEnum[] { getRoleList(): RoleEnum[] {
return this.roleList.length > 0 ? this.roleList : getAuthCache<RoleEnum[]>(ROLES_KEY); return this.roleList.length > 0 ? this.roleList : getAuthCache<RoleEnum[]>(ROLES_KEY);
}, },
getSessionTimeout(): boolean {
return !!this.sessionTimeout;
},
}, },
actions: { actions: {
setToken(info: string) { setToken(info: string | undefined) {
this.token = info; this.token = info;
setAuthCache(TOKEN_KEY, info); setAuthCache(TOKEN_KEY, info);
}, },
...@@ -61,10 +67,14 @@ export const useUserStore = defineStore({ ...@@ -61,10 +67,14 @@ export const useUserStore = defineStore({
this.userInfo = info; this.userInfo = info;
setAuthCache(USER_INFO_KEY, info); setAuthCache(USER_INFO_KEY, info);
}, },
setSessionTimeout(flag: boolean) {
this.sessionTimeout = flag;
},
resetState() { resetState() {
this.userInfo = null; this.userInfo = null;
this.token = ''; this.token = '';
this.roleList = []; this.roleList = [];
this.sessionTimeout = false;
}, },
/** /**
* @description: login * @description: login
...@@ -85,7 +95,9 @@ export const useUserStore = defineStore({ ...@@ -85,7 +95,9 @@ export const useUserStore = defineStore({
// get user info // get user info
const userInfo = await this.getUserInfoAction({ userId }); 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; return userInfo;
} catch (error) { } catch (error) {
return null; return null;
......
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import router from '/@/router'; // import router from '/@/router';
import { PageEnum } from '/@/enums/pageEnum'; // import { PageEnum } from '/@/enums/pageEnum';
import { useUserStoreWidthOut } from '/@/store/modules/user';
const { createMessage } = useMessage(); const { createMessage } = useMessage();
const error = createMessage.error!; const error = createMessage.error!;
export function checkStatus(status: number, msg: string): void { export function checkStatus(status: number, msg: string): void {
const { t } = useI18n(); const { t } = useI18n();
const userStore = useUserStoreWidthOut();
switch (status) { switch (status) {
case 400: case 400:
error(`${msg}`); error(`${msg}`);
...@@ -17,7 +19,8 @@ export function checkStatus(status: number, msg: string): void { ...@@ -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. // Return to the current page after successful login. This step needs to be operated on the login page.
case 401: case 401:
error(t('sys.api.errMsg401')); error(t('sys.api.errMsg401'));
router.push(PageEnum.BASE_LOGIN); userStore.setToken(undefined);
userStore.setSessionTimeout(true);
break; break;
case 403: case 403:
error(t('sys.api.errMsg403')); error(t('sys.api.errMsg403'));
......
import { isObject, isString } from '/@/utils/is'; import { isObject, isString } from '/@/utils/is';
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
export function createNow<T extends boolean>( export function createNow<T extends boolean>(
join: boolean, join: boolean,
restful: T restful: T
...@@ -16,7 +18,6 @@ export function createNow(join: boolean, restful = false): string | object { ...@@ -16,7 +18,6 @@ export function createNow(join: boolean, restful = false): string | object {
return { _t: now }; return { _t: now };
} }
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
/** /**
* @description: Format request parameter time * @description: Format request parameter time
*/ */
......
<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>
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
<AppLocalePicker <AppLocalePicker
class="absolute top-4 right-4 enter-x text-white xl:text-gray-600" class="absolute top-4 right-4 enter-x text-white xl:text-gray-600"
:showText="false" :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"> <span class="-enter-x xl:hidden">
<AppLogo :alwaysShowTitle="true" /> <AppLogo :alwaysShowTitle="true" />
...@@ -31,7 +32,25 @@ ...@@ -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="h-full xl:h-auto flex py-5 xl:py-0 xl:my-0 w-full xl:w-6/12">
<div <div
:class="`${prefixCls}-form`" :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 /> <LoginForm />
<ForgetPasswordForm /> <ForgetPasswordForm />
...@@ -72,6 +91,11 @@ ...@@ -72,6 +91,11 @@
AppLocalePicker, AppLocalePicker,
AppDarkModeToggle, AppDarkModeToggle,
}, },
props: {
sessionTimeout: {
type: Boolean,
},
},
setup() { setup() {
const globSetting = useGlobSetting(); const globSetting = useGlobSetting();
const { prefixCls } = useDesign('login'); const { prefixCls } = useDesign('login');
......
<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>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论