mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-01-23 17:50:25 +08:00
feat(user): add user login expiration example
This commit is contained in:
parent
d5b768929e
commit
5465f058ce
@ -7,6 +7,7 @@
|
||||
- 新增 `JsonPreview`Json 数据查看组件
|
||||
- 表格的数据列(column)和操作列(actionColumn)的字段可以根据权限和业务来控制是否显示
|
||||
- 新增权限控制表格示例(AuthColumn.vue)
|
||||
- 新增用户登录过期示例
|
||||
|
||||
### ⚡ Performance Improvements
|
||||
|
||||
|
@ -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,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 });
|
||||
|
@ -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">
|
||||
|
@ -2,6 +2,7 @@ export default {
|
||||
feat: 'Page Function',
|
||||
icon: 'Icon',
|
||||
tabs: 'Tabs',
|
||||
sessionTimeout: 'Session Timeout',
|
||||
print: 'Print',
|
||||
contextMenu: 'Context Menu',
|
||||
download: 'Download',
|
||||
|
@ -1,6 +1,7 @@
|
||||
export default {
|
||||
feat: '功能',
|
||||
icon: '图标',
|
||||
sessionTimeout: '登录过期',
|
||||
tabs: '标签页操作',
|
||||
print: '打印',
|
||||
contextMenu: '右键菜单',
|
||||
|
@ -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',
|
||||
|
@ -6,7 +6,9 @@ const menu: MenuModule = {
|
||||
menu: {
|
||||
name: t('routes.demo.feat.feat'),
|
||||
path: '/feat',
|
||||
|
||||
tag: {
|
||||
dot: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'icon',
|
||||
@ -16,6 +18,13 @@ const menu: MenuModule = {
|
||||
path: 'ws',
|
||||
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'),
|
||||
|
@ -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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -12,6 +12,7 @@ const feat: AppRouteModule = {
|
||||
icon: 'ion:git-compare-outline',
|
||||
title: t('routes.demo.feat.feat'),
|
||||
},
|
||||
|
||||
children: [
|
||||
{
|
||||
path: 'icon',
|
||||
@ -29,6 +30,14 @@ const feat: AppRouteModule = {
|
||||
title: t('routes.demo.feat.ws'),
|
||||
},
|
||||
},
|
||||
{
|
||||
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',
|
||||
|
@ -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;
|
||||
|
@ -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'));
|
||||
|
@ -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
src/views/demo/feat/session-timeout/index.vue
Normal file
25
src/views/demo/feat/session-timeout/index.vue
Normal file
@ -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>
|
@ -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
src/views/sys/login/SessionTimeoutLogin.vue
Normal file
32
src/views/sys/login/SessionTimeoutLogin.vue
Normal file
@ -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>
|
Loading…
Reference in New Issue
Block a user