| | |
| | | <el-row style="margin-right: -10px; margin-left: -10px"> |
| | | <el-col :span="24" style="padding-right: 10px; padding-left: 10px"> |
| | | <el-form-item> |
| | | <LoginFormTitle style="width: 100%" /> |
| | | <LoginFormTitle style="width: 100%"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="24" style="padding-right: 10px; padding-left: 10px"> |
| | |
| | | mode="pop" |
| | | @success="handleLogin" |
| | | /> |
| | | <el-col :span="24" style="padding-right: 10px; padding-left: 10px"> |
| | | <el-form-item> |
| | | <el-row :gutter="5" justify="space-between" style="width: 100%"> |
| | | <el-col :span="8"> |
| | | <XButton |
| | | :title="t('login.btnMobile')" |
| | | class="w-[100%]" |
| | | @click="setLoginState(LoginStateEnum.MOBILE)" |
| | | /> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <XButton |
| | | :title="t('login.btnQRCode')" |
| | | class="w-[100%]" |
| | | @click="setLoginState(LoginStateEnum.QR_CODE)" |
| | | /> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <XButton |
| | | :title="t('login.btnRegister')" |
| | | class="w-[100%]" |
| | | @click="setLoginState(LoginStateEnum.REGISTER)" |
| | | /> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider> |
| | | <el-col :span="24" style="padding-right: 10px; padding-left: 10px"> |
| | | <el-form-item> |
| | | <div class="w-[100%] flex justify-between"> |
| | | <Icon |
| | | v-for="(item, key) in socialList" |
| | | :key="key" |
| | | :icon="item.icon" |
| | | :size="30" |
| | | class="anticon cursor-pointer" |
| | | color="#999" |
| | | @click="doSocialLogin(item.type)" |
| | | /> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </template> |
| | | <script lang="ts" setup> |
| | | import { ElLoading } from 'element-plus' |
| | | import {ElLoading} from 'element-plus' |
| | | import LoginFormTitle from './LoginFormTitle.vue' |
| | | import type { RouteLocationNormalizedLoaded } from 'vue-router' |
| | | import type {RouteLocationNormalizedLoaded} from 'vue-router' |
| | | |
| | | import { useIcon } from '@/hooks/web/useIcon' |
| | | import {useIcon} from '@/hooks/web/useIcon' |
| | | |
| | | import * as authUtil from '@/utils/auth' |
| | | import { usePermissionStore } from '@/store/modules/permission' |
| | | import {usePermissionStore} from '@/store/modules/permission' |
| | | import * as LoginApi from '@/api/login' |
| | | import { LoginStateEnum, useFormValid, useLoginState } from './useLogin' |
| | | import {LoginStateEnum, useFormValid, useLoginState} from './useLogin' |
| | | |
| | | defineOptions({ name: 'LoginForm' }) |
| | | defineOptions({name: 'LoginForm'}) |
| | | |
| | | const { t } = useI18n() |
| | | const message = useMessage() |
| | | const iconHouse = useIcon({ icon: 'ep:house' }) |
| | | const iconAvatar = useIcon({ icon: 'ep:avatar' }) |
| | | const iconLock = useIcon({ icon: 'ep:lock' }) |
| | | const {t} = useI18n() |
| | | const iconHouse = useIcon({icon: 'ep:house'}) |
| | | const iconAvatar = useIcon({icon: 'ep:avatar'}) |
| | | const iconLock = useIcon({icon: 'ep:lock'}) |
| | | const formLogin = ref() |
| | | const { validForm } = useFormValid(formLogin) |
| | | const { setLoginState, getLoginState } = useLoginState() |
| | | const { currentRoute, push } = useRouter() |
| | | const {validForm} = useFormValid(formLogin) |
| | | const {getLoginState} = useLoginState() |
| | | const {currentRoute, push} = useRouter() |
| | | const permissionStore = usePermissionStore() |
| | | const redirect = ref<string>('') |
| | | const loginLoading = ref(false) |
| | |
| | | rememberMe: true // 默认记录我。如果不需要,可手动修改 |
| | | } |
| | | }) |
| | | |
| | | const socialList = [ |
| | | { icon: 'ant-design:wechat-filled', type: 30 }, |
| | | { icon: 'ant-design:dingtalk-circle-filled', type: 20 }, |
| | | { icon: 'ant-design:github-filled', type: 0 }, |
| | | { icon: 'ant-design:alipay-circle-filled', type: 0 } |
| | | ] |
| | | |
| | | // 获取验证码 |
| | | const getCode = async () => { |
| | |
| | | authUtil.removeLoginForm() |
| | | } |
| | | authUtil.setToken(res) |
| | | if (!redirect.value) { |
| | | redirect.value = '/' |
| | | if (!redirect.value || redirect.value == "/") { |
| | | redirect.value = '/index' |
| | | } |
| | | // 判断是否为SSO登录 |
| | | if (redirect.value.indexOf('sso') !== -1) { |
| | | window.location.href = window.location.href.replace('/login?redirect=', '') |
| | | } else { |
| | | push({ path: redirect.value || permissionStore.addRouters[0].path }) |
| | | push({path: redirect.value || permissionStore.addRouters[0].path}) |
| | | } |
| | | } finally { |
| | | loginLoading.value = false |
| | |
| | | } |
| | | } |
| | | |
| | | // 社交登录 |
| | | const doSocialLogin = async (type: number) => { |
| | | if (type === 0) { |
| | | message.error('此方式未配置') |
| | | } else { |
| | | loginLoading.value = true |
| | | if (loginData.tenantEnable === 'true') { |
| | | // 尝试先通过 tenantName 获取租户 |
| | | await getTenantId() |
| | | // 如果获取不到,则需要弹出提示,进行处理 |
| | | if (!authUtil.getTenantId()) { |
| | | try { |
| | | const data = await message.prompt('请输入租户名称', t('common.reminder')) |
| | | if (data?.action !== 'confirm') throw 'cancel' |
| | | const res = await LoginApi.getTenantIdByName(data.value) |
| | | authUtil.setTenantId(res) |
| | | } catch (error) { |
| | | if (error === 'cancel') return |
| | | } finally { |
| | | loginLoading.value = false |
| | | } |
| | | } |
| | | } |
| | | // 计算 redirectUri |
| | | // tricky: type、redirect需要先encode一次,否则钉钉回调会丢失。 |
| | | // 配合 Login/SocialLogin.vue#getUrlValue() 使用 |
| | | const redirectUri = |
| | | location.origin + |
| | | '/social-login?' + |
| | | encodeURIComponent(`type=${type}&redirect=${redirect.value || '/'}`) |
| | | |
| | | // 进行跳转 |
| | | const res = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri)) |
| | | window.location.href = res |
| | | } |
| | | } |
| | | watch( |
| | | () => currentRoute.value, |
| | | (route: RouteLocationNormalizedLoaded) => { |