Browse Source

chore: 修复登陆重定向

master
wangxiang 2 years ago
parent
commit
32c6236972
No known key found for this signature in database
GPG Key ID: 1BA7946AB6B232E4
  1. 6
      src/enums/appEnum.ts
  2. 8
      src/layouts/default/feature/index.vue
  3. 19
      src/router/guard/permissionGuard.ts
  4. 8
      src/router/routes/demo/feat.ts
  5. 20
      src/router/routes/index.ts
  6. 4
      src/settings/projectSetting.ts
  7. 15
      src/store/modules/user.ts
  8. 9
      src/utils/http/axios/checkStatus.ts
  9. 10
      src/views/core/loginmini/MiniLogin.vue
  10. 48
      src/views/core/loginmini/SessionTimeoutLogin.vue
  11. 56
      src/views/demo/feat/session-timeout/index.vue
  12. 559
      src/views/system/ssoLogin/kics/index.vue
  13. 4
      types/config.d.ts

6
src/enums/appEnum.ts

@ -45,3 +45,9 @@ export enum RouterTransitionEnum {
FADE_BOTTOM = 'fade-bottom', FADE_BOTTOM = 'fade-bottom',
FADE_SCALE = 'fade-scale', FADE_SCALE = 'fade-scale',
} }
/** 会话超时处理(重新加载路由,覆盖还原路由) */
export enum SessionTimeoutProcessingEnum {
ROUTE_JUMP,
PAGE_COVERAGE,
}

8
src/layouts/default/feature/index.vue

@ -6,6 +6,8 @@
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
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/core/loginmini/SessionTimeoutLogin.vue';
import { useUserStoreWithOut } from '/@/store/modules/user';
export default defineComponent({ export default defineComponent({
name: 'LayoutFeatures', name: 'LayoutFeatures',
@ -13,12 +15,16 @@
BackTop, BackTop,
LayoutLockPage: createAsyncComponent(() => import('/@/views/core/lock/index.vue')), LayoutLockPage: createAsyncComponent(() => import('/@/views/core/lock/index.vue')),
SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue')), SettingDrawer: createAsyncComponent(() => import('/@/layouts/default/setting/index.vue')),
SessionTimeoutLogin,
}, },
setup() { setup() {
const { getUseOpenBackTop, getShowSettingButton, getSettingButtonPosition, getFullContent } = useRootSetting(); const { getUseOpenBackTop, getShowSettingButton, getSettingButtonPosition, getFullContent } = useRootSetting();
const userStore = useUserStoreWithOut();
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;
@ -36,6 +42,7 @@
getUseOpenBackTop, getUseOpenBackTop,
getIsFixedSettingDrawer, getIsFixedSettingDrawer,
prefixCls, prefixCls,
getIsSessionTimeout
}; };
}, },
}); });
@ -45,6 +52,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">

19
src/router/guard/permissionGuard.ts

@ -33,11 +33,24 @@ export function createPermissionGuard(router: Router) {
return next(userStore.getUserInfo.homePath); return next(userStore.getUserInfo.homePath);
} }
// 放过白名单路由
if (whitePathList.includes(to.path as PageEnum))
return next();
// 校验token权限信息 // 校验token权限信息
const token = userStore.getAccessToken; const token = userStore.getAccessToken;
// 放过白名单路由
if (whitePathList.includes(to.path as PageEnum)) {
if (to.path === LOGIN_PATH && token) {
const isSessionTimeout = userStore.getSessionTimeout;
try {
await userStore.afterLoginAction();
if (!isSessionTimeout) {
next((to.query?.redirect as string) || '/');
return;
}
} catch {
//
}
}
return next();
}
if (!token) { if (!token) {
// 重定向登录页面 // 重定向登录页面
const redirectLogin: { path: string; replace: boolean; query?: Recordable<string>; } = { path: LOGIN_PATH, replace: true }; const redirectLogin: { path: string; replace: boolean; query?: Recordable<string>; } = { path: LOGIN_PATH, replace: true };

8
src/router/routes/demo/feat.ts

@ -39,6 +39,14 @@ const feat: AppRouteModule = {
title: t('routes.demo.feat.requestDemo'), title: t('routes.demo.feat.requestDemo'),
}, },
}, },
{
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',

20
src/router/routes/index.ts

@ -43,10 +43,30 @@ export const LoginRoute: AppRouteRecordRaw = {
}, },
}; };
export const SsoLoginRoute: AppRouteRecordRaw[] = [
{
path: '/sso/login/kics',
name: 'kicsLogin',
component: () => import('/@/views/system/ssoLogin/kics/index.vue'),
meta: {
title: t('routes.basic.login'),
},
},
{
path: '/sso/login/klab',
name: 'klabLogin',
component: () => import('/@/views/system/ssoLogin/klab/index.vue'),
meta: {
title: t('routes.basic.login'),
},
}
];
export const basicRoutes = [ export const basicRoutes = [
LoginRoute, LoginRoute,
RootRoute, RootRoute,
...mainOutRoutes, ...mainOutRoutes,
...SsoLoginRoute,
REDIRECT_ROUTE, REDIRECT_ROUTE,
PAGE_NOT_FOUND_ROUTE, PAGE_NOT_FOUND_ROUTE,
]; ];

4
src/settings/projectSetting.ts

@ -14,6 +14,7 @@ import {
RouterTransitionEnum, RouterTransitionEnum,
SettingButtonPositionEnum, SettingButtonPositionEnum,
TabsThemeEnum, TabsThemeEnum,
SessionTimeoutProcessingEnum,
} from '/@/enums/appEnum'; } from '/@/enums/appEnum';
import { SIDE_BAR_BG_COLOR_LIST, HEADER_PRESET_BG_COLOR_LIST } from './designSetting'; import { SIDE_BAR_BG_COLOR_LIST, HEADER_PRESET_BG_COLOR_LIST } from './designSetting';
import { primaryColor } from '../../build/config/themeConfig'; import { primaryColor } from '../../build/config/themeConfig';
@ -32,6 +33,9 @@ const setting: ProjectConfig = {
// 权限相关的缓存存储在 sessionStorage 或 localStorage // 权限相关的缓存存储在 sessionStorage 或 localStorage
permissionCacheType: CacheTypeEnum.LOCAL, permissionCacheType: CacheTypeEnum.LOCAL,
// 会话超时处理
sessionTimeoutProcessing: SessionTimeoutProcessingEnum.ROUTE_JUMP,
// 颜色 // 颜色
themeColor: primaryColor, themeColor: primaryColor,

15
src/store/modules/user.ts

@ -25,6 +25,7 @@ import { h } from 'vue';
interface UserState { interface UserState {
userInfo: Nullable<User>; userInfo: Nullable<User>;
sessionTimeout?: boolean;
lastUpdateTime: number; lastUpdateTime: number;
roleIds: string[]; roleIds: string[];
permissions: string[]; permissions: string[];
@ -37,6 +38,8 @@ export const useUserStore = defineStore({
state: (): UserState => ({ state: (): UserState => ({
// 用户信息 // 用户信息
userInfo: getAuthCache<User>(USER_INFO_KEY), userInfo: getAuthCache<User>(USER_INFO_KEY),
// 登录是否过期记录(确保下次登陆不刷新路由直接还原)
sessionTimeout: false,
// 最后一次性更新用户信息时间 // 最后一次性更新用户信息时间
lastUpdateTime: 0, lastUpdateTime: 0,
// 角色ID用于权限校验 // 角色ID用于权限校验
@ -64,6 +67,9 @@ export const useUserStore = defineStore({
getPermissions(): string[] { getPermissions(): string[] {
return this.permissions; return this.permissions;
}, },
getSessionTimeout(): boolean {
return !!this.sessionTimeout;
},
getLastUpdateTime(): number { getLastUpdateTime(): number {
return this.lastUpdateTime; return this.lastUpdateTime;
}, },
@ -90,7 +96,11 @@ export const useUserStore = defineStore({
this.lastUpdateTime = new Date().getTime(); this.lastUpdateTime = new Date().getTime();
setAuthCache(USER_INFO_KEY, userInfo); setAuthCache(USER_INFO_KEY, userInfo);
}, },
setSessionTimeout(flag: boolean) {
this.sessionTimeout = flag;
},
resetState(): void { resetState(): void {
this.setSessionTimeout(false);
this.setUserInfo(null); this.setUserInfo(null);
this.setAccessToken(''); this.setAccessToken('');
this.setRefreshToken(''); this.setRefreshToken('');
@ -119,6 +129,10 @@ export const useUserStore = defineStore({
if (!this.getAccessToken) return null; if (!this.getAccessToken) return null;
// 获取用户信息 // 获取用户信息
const userInfo = await this.getUserInfoAction(); const userInfo = await this.getUserInfoAction();
const sessionTimeout = this.sessionTimeout;
if (sessionTimeout) {
this.setSessionTimeout(false);
} else {
// 处理路由与菜单的构建,并且进行缓存 // 处理路由与菜单的构建,并且进行缓存
const permissionStore = usePermissionStore(); const permissionStore = usePermissionStore();
// 使用isDynamicAddedRoute字段做菜单路由缓存功能 // 使用isDynamicAddedRoute字段做菜单路由缓存功能
@ -130,6 +144,7 @@ export const useUserStore = defineStore({
permissionStore.setDynamicAddedRoute(true); permissionStore.setDynamicAddedRoute(true);
} }
goHome && await router.replace(userInfo?.homePath || PageEnum.BASE_HOME); goHome && await router.replace(userInfo?.homePath || PageEnum.BASE_HOME);
}
return userInfo; return userInfo;
}, },
/** 获取用户信息 */ /** 获取用户信息 */

9
src/utils/http/axios/checkStatus.ts

@ -9,7 +9,10 @@ import type { ErrorMessageMode } from '/#/axios';
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 { useUserStoreWithOut } from '/@/store/modules/user'; import { useUserStoreWithOut } from '/@/store/modules/user';
import projectSetting from '/@/settings/projectSetting';
import { SessionTimeoutProcessingEnum } from '/@/enums/appEnum';
const stp = projectSetting.sessionTimeoutProcessing;
const { createMessage, createErrorModal } = useMessage(); const { createMessage, createErrorModal } = useMessage();
export function checkStatus(status: number, msg: string, errorMessageMode: ErrorMessageMode = 'message'): boolean { export function checkStatus(status: number, msg: string, errorMessageMode: ErrorMessageMode = 'message'): boolean {
@ -40,6 +43,12 @@ export function checkStatus(status: number, msg: string, errorMessageMode: Error
case 424: case 424:
// token 过期特殊处理返回 424 不是 401 // token 过期特殊处理返回 424 不是 401
errMessage = t('sys.api.errMsg424'); errMessage = t('sys.api.errMsg424');
if (stp === SessionTimeoutProcessingEnum.PAGE_COVERAGE) {
userStore.setAccessToken('');
userStore.setSessionTimeout(true);
} else {
userStore.logout(true);
}
userStore.logout(true); userStore.logout(true);
break; break;
case 426: case 426:

10
src/views/core/loginmini/MiniLogin.vue

@ -1,7 +1,7 @@
<template> <template>
<div :class="prefixCls" class="login-background-img"> <div :class="prefixCls" class="login-background-img">
<AppLocalePicker v-if="showLocale" class="absolute top-4 right-4 enter-x xl:text-gray-600" :showText="false"/> <AppLocalePicker v-if="!sessionTimeout && showLocale" class="absolute top-4 right-4 enter-x xl:text-gray-600" :showText="false"/>
<AppDarkModeToggle class="absolute top-3 right-10 enter-x"/> <AppDarkModeToggle v-if="!sessionTimeout" class="absolute top-3 right-10 enter-x"/>
<div v-if="!getIsMobile" class="aui-logo"> <div v-if="!getIsMobile" class="aui-logo">
<div> <div>
<h3> <h3>
@ -198,6 +198,12 @@
import { useAppInject } from '/@/hooks/web/useAppInject'; import { useAppInject } from '/@/hooks/web/useAppInject';
import { GithubFilled, WechatFilled, AlipayCircleFilled, DingtalkCircleFilled } from '@ant-design/icons-vue'; import { GithubFilled, WechatFilled, AlipayCircleFilled, DingtalkCircleFilled } from '@ant-design/icons-vue';
defineProps({
sessionTimeout: {
type: Boolean,
},
});
const { prefixCls } = useDesign('mini-login'); const { prefixCls } = useDesign('mini-login');
const { notification, createMessage } = useMessage(); const { notification, createMessage } = useMessage();
const userStore = useUserStore(); const userStore = useUserStore();

48
src/views/core/loginmini/SessionTimeoutLogin.vue

@ -0,0 +1,48 @@
<template>
<transition>
<div :class="prefixCls">
<MiniLogin sessionTimeout/>
</div>
</transition>
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref } from 'vue';
import MiniLogin from './MiniLogin.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useUserStore } from '/@/store/modules/user';
import { usePermissionStore } from '/@/store/modules/permission';
import { useAppStore } from '/@/store/modules/app';
const { prefixCls } = useDesign('st-login');
const userStore = useUserStore();
const permissionStore = usePermissionStore();
const appStore = useAppStore();
const userId = ref<Nullable<number | string>>(0);
onMounted(() => {
// UserId
userId.value = userStore.getUserInfo?.id;
console.log('Mounted', userStore.getUserInfo);
});
onBeforeUnmount(() => {
if (userId.value && userId.value !== userStore.getUserInfo.id) {
// 便
document.location.reload();
} else if (permissionStore.getLastBuildMenuTime === 0) {
// F5
document.location.reload();
}
});
</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>

56
src/views/demo/feat/session-timeout/index.vue

@ -0,0 +1,56 @@
<template>
<PageWrapper
title="登录过期示例"
content="用户登录过期示例,不再跳转登录页,直接生成页面覆盖当前页面,方便保持过期前的用户状态!"
>
<a-card title="请点击下面的按钮访问测试接口" extra="所访问的接口会返回Token过期响应">
<a-card-grid style="width: 50%; text-align: center">
<a-button type="primary" @click="test1">HttpStatus == 401</a-button>
</a-card-grid>
<a-card-grid style="width: 50%; text-align: center">
<span/>
<a-button class="ml-4" type="primary" @click="test2">Response.code == 401</a-button>
</a-card-grid>
</a-card>
</PageWrapper>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { PageWrapper } from '/@/components/Page';
import { useUserStore } from '/@/store/modules/user';
//import { sessionTimeoutApi, tokenExpiredApi } from '/@/api/demo/account';
import { Card } from 'ant-design-vue';
export default defineComponent({
name: 'TestSessionTimeout',
components: { ACardGrid: Card.Grid, ACard: Card, PageWrapper },
setup() {
const userStore = useUserStore();
async function test1() {
userStore.setAccessToken('');
userStore.setSessionTimeout(true);
// mockHttp
//
// if (import.meta.env.PROD) {
// userStore.setToken(undefined);
// userStore.setSessionTimeout(true);
// } else {
// // api401
// await sessionTimeoutApi();
// }
}
async function test2() {
// apicode401jsonHttp200
try {
//await tokenExpiredApi();
} catch (err) {
console.log('接口访问错误:', (err as Error).message || '错误');
}
}
return { test1, test2 };
},
});
</script>

559
src/views/system/ssoLogin/kics/index.vue

@ -0,0 +1,559 @@
<template>
<div :class="prefixCls" class="login-background-img">
<AppLocalePicker v-if="showLocale" class="absolute top-4 right-4 enter-x xl:text-gray-600" :showText="false"/>
<AppDarkModeToggle class="absolute top-3 right-10 enter-x"/>
<div v-if="!getIsMobile" class="aui-logo">
<div>
<h3>
<img :src="logoImg">
</h3>
</div>
</div>
<div v-else class="aui-phone-logo">
<img :src="logoPhoneImg">
</div>
<div v-show="type === 'login'">
<div class="aui-content">
<div class="aui-container">
<div class="aui-form">
<div class="aui-image">
<div class="aui-image-text"/>
</div>
<div class="aui-formBox">
<div class="aui-formWell">
<div class="aui-flex aui-form-nav">
<div
class="aui-flex-box"
:class="activeIndex === 'accountLogin' ? 'activeNav on' : ''"
@click="loginClick('accountLogin')"
>{{ t('sys.login.signInFormTitle') }}</div>
<div
class="aui-flex-box"
:class="activeIndex === 'phoneLogin' ? 'activeNav on' : ''"
@click="loginClick('phoneLogin')"
>{{ t('sys.login.mobileSignInFormTitle') }}</div>
</div>
<div class="aui-form-box" style="height: 180px">
<a-form
v-if="activeIndex === 'accountLogin'"
ref="loginRef"
:model="formData"
@keypress.enter="loginHandleClick"
>
<div class="aui-account">
<div class="aui-inputClear">
<i class="icon icon-code"/>
<a-form-item>
<a-input v-model:value="formData.username" class="fix-auto-fill" :placeholder="t('sys.login.userName')"/>
</a-form-item>
</div>
<div class="aui-inputClear">
<i class="icon icon-password"/>
<a-form-item>
<a-input
v-model:value="formData.password"
class="fix-auto-fill"
type="password"
:placeholder="t('sys.login.password')"
/>
</a-form-item>
</div>
<div class="aui-inputClear">
<i class="icon icon-code"/>
<a-form-item>
<a-input
v-model:value="formData.inputCode"
class="fix-auto-fill"
type="text"
:placeholder="t('sys.login.captcha')"
/>
</a-form-item>
<div class="aui-code">
<img v-if="randCodeData.requestCodeSuccess" :src="randCodeData.randCodeImage" @click="handleChangeCheckCode">
<img
v-else
style="margin-top: 2px; max-width: initial"
:src="defaultCaptchaImg"
@click="handleChangeCheckCode"
>
</div>
</div>
<div class="aui-flex">
<div class="aui-flex-box">
<div class="aui-choice">
<a-input v-model:value="rememberMe" class="fix-auto-fill" type="checkbox"/>
<span style="margin-left: 5px">{{ t('sys.login.rememberMe') }}</span>
</div>
</div>
<div class="aui-forget">
<a @click="forgetHandelClick"> {{ t('sys.login.forgetPassword') }}</a>
</div>
</div>
</div>
</a-form>
<a-form
v-else
ref="phoneFormRef"
:model="phoneFormData"
@keypress.enter="loginHandleClick"
>
<div class="aui-account phone">
<div class="aui-inputClear phoneClear">
<a-input v-model:value="phoneFormData.mobile" class="fix-auto-fill" :placeholder="t('sys.login.mobile')"/>
</div>
<div class="aui-inputClear">
<a-input
v-model:value="phoneFormData.smscode"
class="fix-auto-fill"
:maxlength="6"
:placeholder="t('sys.login.smsCode')"
/>
<div v-if="showInterval" class="aui-code" @click="getLoginCode">
<a>{{ t('component.countdown.normalText') }}</a>
</div>
<div v-else class="aui-code">
<span class="aui-get-code code-shape">{{ t('component.countdown.sendText', [unref(timeRuning)]) }}</span>
</div>
</div>
</div>
</a-form>
</div>
<div class="aui-formButton">
<div class="aui-flex">
<a-button
:loading="loginLoading"
class="aui-link-login aui-flex-box"
type="primary"
@click="loginHandleClick"
>{{ t('sys.login.loginButton') }}</a-button>
</div>
<div class="aui-flex">
<a class="aui-linek-code aui-flex-box" @click="codeHandleClick">{{ t('sys.login.qrSignInFormTitle') }}</a>
</div>
<!--<div class="aui-flex">
<a class="aui-linek-code aui-flex-box" @click="registerHandleClick">{{ t('sys.login.registerButton') }}</a>
</div>-->
</div>
</div>
<a-form>
<div class="aui-flex aui-third-text">
<div class="aui-flex-box aui-third-border">
<span>{{ t('sys.login.otherSignIn') }}</span>
</div>
</div>
<div class="aui-flex" :class="`${prefixCls}-sign-in-way`">
<div class="aui-flex-box">
<div class="aui-third-login">
<a title="github" @click="onThirdLogin('github')"><GithubFilled/></a>
</div>
</div>
<div class="aui-flex-box">
<div class="aui-third-login">
<a title="支付宝" @click="onThirdLogin('alipay')"><AlipayCircleFilled/></a>
</div>
</div>
<div class="aui-flex-box">
<div class="aui-third-login">
<a title="钉钉" @click="onThirdLogin('dingtalk')"><DingtalkCircleFilled/></a>
</div>
</div>
<div class="aui-flex-box">
<div class="aui-third-login">
<a title="微信" @click="onThirdLogin('wechat_open')"><WechatFilled/></a>
</div>
</div>
</div>
</a-form>
</div>
</div>
</div>
</div>
</div>
<div v-show="type === 'forgot'" :class="`${prefixCls}-form`">
<MiniForgotpad ref="forgotRef" @go-back="goBack" @success="handleSuccess"/>
</div>
<div v-show="type === 'register'" :class="`${prefixCls}-form`">
<MiniRegister ref="registerRef" @go-back="goBack" @success="handleSuccess"/>
</div>
<div v-show="type === 'codeLogin'" :class="`${prefixCls}-form`">
<MiniCodelogin ref="codeRef" @go-back="goBack" @success="handleSuccess"/>
</div>
</div>
</template>
<script lang="ts" setup>
import { getCaptcha } from '/@/api/platform/core/controller/user';
import { onMounted, reactive, ref, toRaw, unref } from 'vue';
import defaultCaptchaImg from '/@/assets/images/captcha.jpg';
import { useUserStore } from '/@/store/modules/user';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import MiniForgotpad from './MiniForgotpad.vue';
import MiniRegister from './MiniRegister.vue';
import MiniCodelogin from './MiniCodelogin.vue';
import logoImg from '/@/assets/images/logo-tag.png';
import logoPhoneImg from '/@/assets/images/logo.png';
import { AppLocalePicker, AppDarkModeToggle } from '/@/components/Application';
import { useLocaleStore } from '/@/store/modules/locale';
import { useDesign } from '/@/hooks/web/useDesign';
import { useAppInject } from '/@/hooks/web/useAppInject';
import { GithubFilled, WechatFilled, AlipayCircleFilled, DingtalkCircleFilled } from '@ant-design/icons-vue';
const { prefixCls } = useDesign('mini-login');
const { notification, createMessage } = useMessage();
const userStore = useUserStore();
const { t } = useI18n();
const localeStore = useLocaleStore();
const showLocale = localeStore.getShowPicker;
const randCodeData = reactive<any>({
randCodeImage: '',
requestCodeSuccess: false,
});
const rememberMe = ref<string>('0');
//
const activeIndex = ref<string>('accountLogin');
const type = ref<string>('login');
//
const formData = reactive<any>({
realKey: '',
inputCode: '',
username: '',
password: '',
});
//
const phoneFormData = reactive<any>({
mobile: '',
smscode: '',
});
const loginRef = ref();
//
const codeRef = ref();
//
const showInterval = ref<boolean>(true);
// 60s
const timeRuning = ref<number>(60);
//
const timer = ref<any>(null);
//
const forgotRef = ref();
//
const registerRef = ref();
const loginLoading = ref<boolean>(false);
const { getIsMobile } = useAppInject();
/**
* 获取验证码
*/
async function handleChangeCheckCode() {
formData.inputCode = '';
try {
const codeModel = await getCaptcha();
randCodeData.randCodeImage = codeModel.img;
randCodeData.requestCodeSuccess = true;
formData.realKey = codeModel.realKey;
} catch(error) {
randCodeData.requestCodeSuccess = false;
}
}
/**
* 切换登录方式
*/
function loginClick(type) {
activeIndex.value = type;
}
/**
* 账号或者手机登录
*/
async function loginHandleClick() {
if (unref(activeIndex) === 'accountLogin') {
await accountLogin();
} else {
await phoneLogin();
}
}
async function accountLogin() {
if (!formData.username) {
createMessage.warn(t('sys.login.accountPlaceholder'));
return;
}
if (!formData.password) {
createMessage.warn(t('sys.login.passwordPlaceholder'));
return;
}
if (!formData.inputCode) {
createMessage.warn(t('sys.login.smsPlaceholder'));
return;
}
try {
loginLoading.value = true;
const userInfo = await userStore.login(toRaw({
password: formData.password,
username: formData.username,
realKey: formData.realKey,
code: formData.inputCode,
}));
if (userInfo) {
notification.success({
message: t('sys.login.loginSuccessTitle'),
description: `${t('sys.login.loginSuccessDesc')}: ${userInfo.nickName}`,
duration: 3,
});
}
} catch (error){
formData.code='';
formData.realKey='';
await handleChangeCheckCode();
} finally {
loginLoading.value = false;
}
}
/**
* 手机号登录
*/
async function phoneLogin() {
notification.success({
message: '功能待开发中.',
duration: 3,
});
}
/**
* 获取手机验证码
*/
async function getLoginCode() {
/*if (!phoneFormData.mobile) {
createMessage.warn(t('sys.login.mobilePlaceholder'));
return;
}*/
const TIME_COUNT = 60;
if (!unref(timer)) {
timeRuning.value = TIME_COUNT;
showInterval.value = false;
timer.value = setInterval(() => {
if (unref(timeRuning) > 0 && unref(timeRuning) <= TIME_COUNT) {
timeRuning.value = timeRuning.value - 1;
} else {
showInterval.value = true;
clearInterval(unref(timer));
timer.value = null;
}
}, 1000);
}
}
/**
* 第三方登录
* @param type
*/
function onThirdLogin(type) {
notification.success({
message: '功能待开发中.',
duration: 3,
});
}
/**
* 忘记密码
*/
function forgetHandelClick() {
type.value = 'forgot';
setTimeout(() => {
forgotRef.value.initForm();
}, 300);
}
/**
* 返回登录页面
*/
function goBack() {
activeIndex.value = 'accountLogin';
type.value = 'login';
}
/**
* 忘记密码/注册账号回调事件
* @param value
*/
function handleSuccess(value) {
Object.assign(formData, value);
Object.assign(phoneFormData, { mobile: '', smscode: '' });
type.value = 'login';
activeIndex.value = 'accountLogin';
handleChangeCheckCode();
}
/**
* 注册
*/
function registerHandleClick() {
type.value = 'register';
setTimeout(() => {
registerRef.value.initForm();
}, 300);
}
/**
* 二维码登录
*/
function codeHandleClick() {
type.value = 'codeLogin';
setTimeout(() => {
codeRef.value.initFrom();
}, 300);
}
onMounted(() => {
//
handleChangeCheckCode();
});
</script>
<style lang="less" scoped>
.login-background-img {
background-image: url(../icon/login-bg.png);
background-size: cover;
background-position: top center;
background-repeat: no-repeat;
}
.aui-logo {
width: 180px;
height: 80px;
position: absolute;
top: 2%;
left: 8%;
z-index: 4;
}
:deep(.ant-input:focus) {
box-shadow: none;
}
.aui-get-code {
float: right;
position: relative;
z-index: 3;
background: #ffffff;
color: #1573e9;
border-radius: 100px;
padding: 5px 16px;
margin: 7px;
border: 1px solid #1573e9;
top: 12px;
}
.aui-get-code:hover {
color: #1573e9;
}
.code-shape {
border-color: #dadada !important;
color: #aaa !important;
}
.aui-link-login{
height: 42px;
padding: 10px 15px;
font-size: 14px;
border-radius: 8px;
margin-top: 15px;
margin-bottom: 8px;
}
.aui-phone-logo{
position: absolute;
margin-left: 10px;
width: 60px;
top:2px;
z-index: 4;
}
.top-3{
top: 0.45rem;
}
</style>
<style lang="less">
@prefix-cls: ~'@{namespace}-mini-login';
@dark-bg: #293146;
html[data-theme='dark'] {
.@{prefix-cls} {
background-color: @dark-bg !important;
background-image: none;
&::before {
background-image: url(/@/assets/images/login-bg-dark.svg);
}
.aui-inputClear{
background-color: #232a3b !important;
}
.ant-input,
.ant-input-password {
background-color: #232a3b !important;
}
.ant-btn:not(.ant-btn-link):not(.ant-btn-primary) {
border: 1px solid #4a5569 !important;
}
&-form {
background: @dark-bg !important;
}
.app-iconify {
color: #fff !important;
}
.aui-inputClear input,.aui-input-line input,.aui-choice{
color: #c9d1d9 !important;
}
.aui-formBox{
background-color: @dark-bg !important;
}
.aui-third-text span{
background-color: @dark-bg !important;
}
.aui-form-nav .aui-flex-box{
color: #c9d1d9 !important;
}
.aui-formButton .aui-linek-code{
background: @dark-bg !important;
color: white !important;
}
.aui-code-line{
border-left: none !important;
}
.ant-checkbox-inner,.aui-success h3{
border-color: #c9d1d9;
}
}
input.fix-auto-fill,
.fix-auto-fill input {
-webkit-text-fill-color: #c9d1d9 !important;
box-shadow: inherit !important;
}
&-sign-in-way {
.anticon {
font-size: 22px !important;
color: #888 !important;
cursor: pointer !important;
&:hover {
color: @primary-color !important;
}
}
}
.ant-divider-inner-text {
font-size: 12px !important;
color: @text-color-secondary !important;
}
.aui-third-login a{
background: transparent;
}
}
</style>

4
types/config.d.ts vendored

@ -10,7 +10,7 @@ import {
ContentEnum, ContentEnum,
ThemeEnum, ThemeEnum,
RouterTransitionEnum, RouterTransitionEnum,
SettingButtonPositionEnum, SettingButtonPositionEnum, SessionTimeoutProcessingEnum,
} from '/@/enums/appEnum'; } from '/@/enums/appEnum';
import { CacheTypeEnum } from '/@/enums/cacheEnum'; import { CacheTypeEnum } from '/@/enums/cacheEnum';
@ -93,6 +93,8 @@ export interface ProjectConfig {
showDarkModeToggle: boolean; showDarkModeToggle: boolean;
// 配置按钮的显示位置 // 配置按钮的显示位置
settingButtonPosition: SettingButtonPositionEnum; settingButtonPosition: SettingButtonPositionEnum;
// Session timeout processing
sessionTimeoutProcessing: SessionTimeoutProcessingEnum;
// 网站灰色模式,打开可能的哀悼日期 // 网站灰色模式,打开可能的哀悼日期
grayMode: boolean; grayMode: boolean;
// 是否开启色弱模式 // 是否开启色弱模式

Loading…
Cancel
Save