| | |
| | | <template> |
| | | <div class="login"> |
| | | <div class="login-mask" /> |
| | | <div class="login-form-wrap"> |
| | | <div class="login-form mx-6"> |
| | | <div class="login-form__content px-2 py-10"> |
| | | <header> |
| | | <img src="/@/assets/images/logo.png" class="mr-4" /> |
| | | <h1>{{ title }}</h1> |
| | | </header> |
| | | <div :class="prefixCls" class="relative w-full h-full px-4"> |
| | | <AppLocalePicker |
| | | class="absolute top-4 right-4 enter-x text-white xl:text-gray-600" |
| | | :showText="false" |
| | | /> |
| | | <AppDarkModeToggle class="absolute top-3 right-7 enter-x" /> |
| | | |
| | | <a-form class="mx-auto mt-10" :model="formData" :rules="formRules" ref="formRef"> |
| | | <a-form-item name="account"> |
| | | <a-input size="large" v-model:value="formData.account" placeholder="vben" /> |
| | | </a-form-item> |
| | | <a-form-item name="password"> |
| | | <a-input-password |
| | | autofocus="autofocus" |
| | | size="large" |
| | | visibilityToggle |
| | | v-model:value="formData.password" |
| | | placeholder="123456" |
| | | /> |
| | | </a-form-item> |
| | | <a-form-item name="verify" v-if="openLoginVerify"> |
| | | <BasicDragVerify v-model:value="formData.verify" ref="verifyRef" /> |
| | | </a-form-item> |
| | | <a-form-item> |
| | | <a-button |
| | | type="primary" |
| | | size="large" |
| | | class="rounded-sm" |
| | | block |
| | | @click="login" |
| | | :loading="formState.loading" |
| | | >登录</a-button |
| | | > |
| | | </a-form-item> |
| | | </a-form> |
| | | <span class="-enter-x xl:hidden"> |
| | | <AppLogo :alwaysShowTitle="true" /> |
| | | </span> |
| | | |
| | | <div class="container relative h-full py-2 mx-auto sm:px-10"> |
| | | <div class="flex h-full"> |
| | | <div class="hidden xl:flex xl:flex-col xl:w-6/12 min-h-full mr-4 pl-4"> |
| | | <AppLogo class="-enter-x" /> |
| | | <div class="my-auto"> |
| | | <img |
| | | :alt="title" |
| | | src="../../../assets/svg/login-box-bg.svg" |
| | | class="w-1/2 -mt-16 -enter-x" |
| | | /> |
| | | <div class="mt-10 font-medium text-white -enter-x"> |
| | | <span class="mt-4 text-3xl inline-block"> {{ t('sys.login.signInTitle') }}</span> |
| | | </div> |
| | | <div class="mt-5 text-md text-white font-normal dark:text-gray-500 -enter-x"> |
| | | {{ t('sys.login.signInDesc') }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <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" |
| | | > |
| | | <LoginForm /> |
| | | <ForgetPasswordForm /> |
| | | <RegisterForm /> |
| | | <MobileForm /> |
| | | <QrCodeForm /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent, reactive, ref, unref, toRaw, computed } from 'vue'; |
| | | import { BasicDragVerify, DragVerifyActionType } from '/@/components/Verify/index'; |
| | | import { userStore } from '/@/store/modules/user'; |
| | | import { appStore } from '/@/store/modules/app'; |
| | | import { useMessage } from '/@/hooks/web/useMessage'; |
| | | import { useSetting } from '/@/hooks/core/useSetting'; |
| | | import { defineComponent, computed } from 'vue'; |
| | | |
| | | import { AppLogo } from '/@/components/Application'; |
| | | import { AppLocalePicker, AppDarkModeToggle } from '/@/components/Application'; |
| | | import LoginForm from './LoginForm.vue'; |
| | | import ForgetPasswordForm from './ForgetPasswordForm.vue'; |
| | | import RegisterForm from './RegisterForm.vue'; |
| | | import MobileForm from './MobileForm.vue'; |
| | | import QrCodeForm from './QrCodeForm.vue'; |
| | | |
| | | import { useGlobSetting } from '/@/hooks/setting'; |
| | | import { useI18n } from '/@/hooks/web/useI18n'; |
| | | import { useDesign } from '/@/hooks/web/useDesign'; |
| | | import { useLocaleStore } from '/@/store/modules/locale'; |
| | | |
| | | export default defineComponent({ |
| | | components: { BasicDragVerify }, |
| | | name: 'Login', |
| | | components: { |
| | | AppLogo, |
| | | LoginForm, |
| | | ForgetPasswordForm, |
| | | RegisterForm, |
| | | MobileForm, |
| | | QrCodeForm, |
| | | AppLocalePicker, |
| | | AppDarkModeToggle, |
| | | }, |
| | | setup() { |
| | | const { globSetting } = useSetting(); |
| | | const { notification } = useMessage(); |
| | | const formRef = ref<any>(null); |
| | | const verifyRef = ref<RefInstanceType<DragVerifyActionType>>(null); |
| | | |
| | | const openLoginVerifyRef = computed(() => appStore.getProjectConfig.openLoginVerify); |
| | | |
| | | const formData = reactive({ |
| | | account: '', |
| | | password: '', |
| | | verify: undefined, |
| | | }); |
| | | const formState = reactive({ |
| | | loading: false, |
| | | }); |
| | | |
| | | const formRules = reactive({ |
| | | account: [{ required: true, message: '请输入账号', trigger: 'blur' }], |
| | | password: [{ required: true, message: '请输入密码', trigger: 'blur' }], |
| | | verify: unref(openLoginVerifyRef) ? [{ required: true, message: '请通过验证码校验' }] : [], |
| | | }); |
| | | |
| | | function resetVerify() { |
| | | const verify = unref(verifyRef); |
| | | if (!verify) return; |
| | | formData.verify && verify.$.resume(); |
| | | formData.verify = undefined; |
| | | } |
| | | |
| | | async function handleLogin() { |
| | | const form = unref(formRef); |
| | | if (!form) return; |
| | | formState.loading = true; |
| | | try { |
| | | const data = await form.validate(); |
| | | const userInfo = await userStore.login( |
| | | toRaw({ |
| | | password: data.password, |
| | | username: data.account, |
| | | }) |
| | | ); |
| | | if (userInfo) { |
| | | notification.success({ |
| | | message: '登录成功', |
| | | description: `欢迎回来: ${userInfo.realName}`, |
| | | duration: 3, |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | } finally { |
| | | resetVerify(); |
| | | formState.loading = false; |
| | | } |
| | | } |
| | | const globSetting = useGlobSetting(); |
| | | const { prefixCls } = useDesign('login'); |
| | | const { t } = useI18n(); |
| | | const localeStore = useLocaleStore(); |
| | | |
| | | return { |
| | | formRef, |
| | | verifyRef, |
| | | formData, |
| | | formState, |
| | | formRules, |
| | | login: handleLogin, |
| | | openLoginVerify: openLoginVerifyRef, |
| | | title: globSetting && globSetting.title, |
| | | t, |
| | | prefixCls, |
| | | title: computed(() => globSetting?.title ?? ''), |
| | | showLocale: localeStore.getShowPicker, |
| | | }; |
| | | }, |
| | | }); |
| | | </script> |
| | | <style lang="less" scoped> |
| | | @import (reference) '../../../design/index.less'; |
| | | <style lang="less"> |
| | | @prefix-cls: ~'@{namespace}-login'; |
| | | @logo-prefix-cls: ~'@{namespace}-app-logo'; |
| | | @countdown-prefix-cls: ~'@{namespace}-countdown-input'; |
| | | @dark-bg: #293146; |
| | | |
| | | .login { |
| | | position: relative; |
| | | height: 100vh; |
| | | background: url(../../../assets/images/login/login-bg.png) no-repeat; |
| | | background-size: 100% 100%; |
| | | html[data-theme='dark'] { |
| | | .@{prefix-cls} { |
| | | background: @dark-bg; |
| | | |
| | | &-mask { |
| | | display: none; |
| | | height: 100%; |
| | | background: url(../../../assets/images/login/login-in.png) no-repeat; |
| | | background-size: 100% 100%; |
| | | &::before { |
| | | background-image: url(/@/assets/svg/login-bg-dark.svg); |
| | | } |
| | | |
| | | .respond-to(large, { display: block;}); |
| | | .ant-input, |
| | | .ant-input-password { |
| | | background-color: #232a3b; |
| | | } |
| | | |
| | | .ant-btn:not(.ant-btn-link):not(.ant-btn-primary) { |
| | | border: 1px solid #4a5569; |
| | | } |
| | | |
| | | &-form { |
| | | background: transparent !important; |
| | | } |
| | | |
| | | .app-iconify { |
| | | color: #fff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .@{prefix-cls} { |
| | | overflow: hidden; |
| | | @media (max-width: @screen-xl) { |
| | | background: #293146; |
| | | |
| | | .@{prefix-cls}-form { |
| | | background: #fff; |
| | | } |
| | | } |
| | | |
| | | &-form { |
| | | &::before { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | background: @white; |
| | | border: 10px solid rgba(255, 255, 255, 0.5); |
| | | border-width: 10px; |
| | | border-radius: 4px; |
| | | background-clip: padding-box; |
| | | .respond-to(xlarge, { margin: 0 56px}); |
| | | height: 100%; |
| | | margin-left: -48%; |
| | | background-image: url(/@/assets/svg/login-bg.svg); |
| | | background-position: 100%; |
| | | background-repeat: no-repeat; |
| | | background-size: auto 100%; |
| | | content: ''; |
| | | @media (max-width: @screen-xl) { |
| | | display: none; |
| | | } |
| | | } |
| | | |
| | | &-wrap { |
| | | position: absolute; |
| | | top: 0; |
| | | right: 0; |
| | | .@{logo-prefix-cls} { |
| | | position: absolute; |
| | | top: 12px; |
| | | height: 30px; |
| | | |
| | | &__title { |
| | | font-size: 16px; |
| | | color: #fff; |
| | | } |
| | | |
| | | img { |
| | | width: 32px; |
| | | } |
| | | } |
| | | |
| | | .container { |
| | | .@{logo-prefix-cls} { |
| | | display: flex; |
| | | width: 100%; |
| | | height: 100%; |
| | | justify-content: center; |
| | | align-items: center; |
| | | .respond-to(large, { width: 40%;}); |
| | | .respond-to(xlarge, { width: 33.3%;}); |
| | | } |
| | | width: 60%; |
| | | height: 80px; |
| | | |
| | | &__content { |
| | | width: 100%; |
| | | height: 100%; |
| | | border: 1px solid #999; |
| | | border-radius: 2px; |
| | | |
| | | header { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | |
| | | img { |
| | | display: inline-block; |
| | | width: 80px; |
| | | } |
| | | |
| | | h1 { |
| | | margin-bottom: 0; |
| | | font-size: 24px; |
| | | color: @primary-color; |
| | | text-align: center; |
| | | } |
| | | &__title { |
| | | font-size: 24px; |
| | | color: #fff; |
| | | } |
| | | |
| | | form { |
| | | width: 80%; |
| | | img { |
| | | width: 48px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | &-sign-in-way { |
| | | .anticon { |
| | | font-size: 22px; |
| | | color: #888; |
| | | cursor: pointer; |
| | | |
| | | &:hover { |
| | | color: @primary-color; |
| | | } |
| | | } |
| | | } |
| | | |
| | | input:not([type='checkbox']) { |
| | | min-width: 360px; |
| | | |
| | | @media (max-width: @screen-lg) { |
| | | min-width: 300px; |
| | | } |
| | | |
| | | @media (max-width: @screen-md) { |
| | | min-width: 280px; |
| | | } |
| | | |
| | | @media (max-width: @screen-sm) { |
| | | min-width: 180px; |
| | | } |
| | | } |
| | | .@{countdown-prefix-cls} input { |
| | | min-width: unset; |
| | | } |
| | | |
| | | .ant-divider-inner-text { |
| | | font-size: 12px; |
| | | color: @text-color-secondary; |
| | | } |
| | | } |
| | | </style> |