mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 10:12:51 +00:00
feat: user-login
This commit is contained in:
parent
b00ad14214
commit
e4e58bc3bd
|
|
@ -79,7 +79,7 @@ const loginImage = computed(() => {
|
|||
return `${fileURL.value}`
|
||||
} else {
|
||||
const imgName = getThemeImg(theme.themeInfo?.theme)
|
||||
const imgPath = `../../../assets/theme/${imgName}.jpg`
|
||||
const imgPath = `../../assets/theme/${imgName}.jpg`
|
||||
const imageUrl = new URL(imgPath, import.meta.url).href
|
||||
return imageUrl
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<div class="login-warp flex-center">
|
||||
<div class="login-container w-full h-full">
|
||||
<div class="flex-center w-full h-full">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { getThemeImg } from '@/utils/theme'
|
||||
import useStore from '@/stores'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { langList, localeConfigKey, getBrowserLang } from '@/locales/index'
|
||||
defineProps({
|
||||
lang: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
const { user, theme } = useStore()
|
||||
|
||||
const changeLang = (lang: string) => {
|
||||
useLocalStorage(localeConfigKey, getBrowserLang()).value = lang
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const currentLanguage = computed(() => {
|
||||
return langList.value?.filter((v: any) => v.value === user.getLanguage())?.[0]?.label
|
||||
})
|
||||
|
||||
const fileURL = computed(() => {
|
||||
if (theme.themeInfo?.loginImage) {
|
||||
if (typeof theme.themeInfo?.loginImage === 'string') {
|
||||
return theme.themeInfo?.loginImage
|
||||
} else {
|
||||
return URL.createObjectURL(theme.themeInfo?.loginImage)
|
||||
}
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
const loginImage = computed(() => {
|
||||
if (theme.themeInfo?.loginImage) {
|
||||
return `${fileURL.value}`
|
||||
} else {
|
||||
const imgName = getThemeImg(theme.themeInfo?.theme)
|
||||
const imgPath = `../../assets/theme/${imgName}.jpg`
|
||||
const imageUrl = new URL(imgPath, import.meta.url).href
|
||||
return imageUrl
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.login-warp {
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -47,7 +47,7 @@ instance.interceptors.response.use(
|
|||
}
|
||||
if (
|
||||
!response.config.url.includes('/valid') &&
|
||||
!response.config.url.includes('/function_lib/debug')
|
||||
!response.config.url.includes('/tool/debug')
|
||||
) {
|
||||
MsgError(response.data.message)
|
||||
return Promise.reject(response.data)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ router.beforeEach(
|
|||
return
|
||||
}
|
||||
const { user, login } = useStore()
|
||||
const notAuthRouteNameList = ['register', 'login', 'forgot_password', 'reset_password', 'Chat']
|
||||
|
||||
const notAuthRouteNameList = ['login', 'ForgotPassword', 'ResetPassword', 'Chat', 'UserLogin']
|
||||
if (!notAuthRouteNameList.includes(to.name ? to.name.toString() : '')) {
|
||||
if (to.query && to.query.token) {
|
||||
localStorage.setItem('token', to.query.token.toString())
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ export const routes: Array<RouteRecordRaw> = [
|
|||
component: () => import('@/views/chat/index.vue'),
|
||||
},
|
||||
|
||||
// 对话用户登录
|
||||
{
|
||||
path: '/user-login/:accessToken',
|
||||
name: 'UserLogin',
|
||||
component: () => import('@/views/chat/user-login/index.vue'),
|
||||
},
|
||||
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
<template>
|
||||
<UserLoginLayout>
|
||||
<LoginContainer :subTitle="$t('theme.defaultSlogan')">
|
||||
<h2 class="mb-24">{{ $t('views.login.resetPassword') }}</h2>
|
||||
<el-form
|
||||
class="reset-password-form"
|
||||
ref="resetPasswordFormRef"
|
||||
:model="resetPasswordForm"
|
||||
:rules="rules"
|
||||
>
|
||||
<div class="mb-24">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="resetPasswordForm.password"
|
||||
:placeholder="$t('views.login.loginForm.password.placeholder')"
|
||||
show-password
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mb-24">
|
||||
<el-form-item prop="re_password">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="resetPasswordForm.re_password"
|
||||
:placeholder="$t('views.login.loginForm.re_password.placeholder')"
|
||||
show-password
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<el-button size="large" type="primary" class="w-full" @click="resetPassword">{{
|
||||
$t('common.confirm')
|
||||
}}</el-button>
|
||||
<div class="operate-container mt-12">
|
||||
<el-button
|
||||
size="large"
|
||||
class="register"
|
||||
@click="router.push('/login')"
|
||||
link
|
||||
type="primary"
|
||||
icon="ArrowLeft"
|
||||
>
|
||||
{{ $t('views.login.buttons.backLogin') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</LoginContainer>
|
||||
</UserLoginLayout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import type { ResetPasswordRequest } from '@/api/type/user'
|
||||
import LoginContainer from '@/layout/login-layout/LoginContainer.vue'
|
||||
import UserLoginLayout from '@/layout/login-layout/UserLoginLayout.vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import UserApi from '@/api/user/user'
|
||||
import { t } from '@/locales'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { code, email },
|
||||
} = route
|
||||
const resetPasswordForm = ref<ResetPasswordRequest>({
|
||||
password: '',
|
||||
re_password: '',
|
||||
email: '',
|
||||
code: '',
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (code && email) {
|
||||
resetPasswordForm.value.code = code as string
|
||||
resetPasswordForm.value.email = email as string
|
||||
} else {
|
||||
router.push('forgot_password')
|
||||
}
|
||||
})
|
||||
|
||||
const rules = ref<FormRules<ResetPasswordRequest>>({
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.login.loginForm.re_password.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: t('views.login.loginForm.password.lengthMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
re_password: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.login.loginForm.re_password.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: t('views.login.loginForm.password.lengthMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {
|
||||
callback(new Error(t('views.login.loginForm.re_password.validatorMessage')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
const resetPasswordFormRef = ref<FormInstance>()
|
||||
const loading = ref<boolean>(false)
|
||||
const resetPassword = () => {
|
||||
resetPasswordFormRef.value
|
||||
?.validate()
|
||||
.then(() => UserApi.resetPassword(resetPasswordForm.value, loading))
|
||||
.then(() => {
|
||||
MsgSuccess(t('common.modifySuccess'))
|
||||
router.push({ name: 'login' })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
<template>
|
||||
<UserLoginLayout>
|
||||
<LoginContainer :subTitle="$t('theme.defaultSlogan')">
|
||||
<h2 class="mb-24">{{ $t('views.login.resetPassword') }}</h2>
|
||||
<el-form
|
||||
class="reset-password-form"
|
||||
ref="resetPasswordFormRef"
|
||||
:model="resetPasswordForm"
|
||||
:rules="rules"
|
||||
>
|
||||
<div class="mb-24">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="resetPasswordForm.password"
|
||||
:placeholder="$t('views.login.loginForm.password.placeholder')"
|
||||
show-password
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mb-24">
|
||||
<el-form-item prop="re_password">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="resetPasswordForm.re_password"
|
||||
:placeholder="$t('views.login.loginForm.re_password.placeholder')"
|
||||
show-password
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<el-button size="large" type="primary" class="w-full" @click="resetPassword">{{
|
||||
$t('common.confirm')
|
||||
}}</el-button>
|
||||
<div class="operate-container mt-12">
|
||||
<el-button
|
||||
size="large"
|
||||
class="register"
|
||||
@click="router.push('/login')"
|
||||
link
|
||||
type="primary"
|
||||
icon="ArrowLeft"
|
||||
>
|
||||
{{ $t('views.login.buttons.backLogin') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</LoginContainer>
|
||||
</UserLoginLayout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import LoginContainer from '@/layout/login-layout/LoginContainer.vue'
|
||||
import UserLoginLayout from '@/layout/login-layout/UserLoginLayout.vue'
|
||||
import type { ResetPasswordRequest } from '@/api/type/user'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import UserApi from '@/api/user/user-manage'
|
||||
import { t } from '@/locales'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { code, email },
|
||||
} = route
|
||||
const resetPasswordForm = ref<ResetPasswordRequest>({
|
||||
password: '',
|
||||
re_password: '',
|
||||
email: '',
|
||||
code: '',
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (code && email) {
|
||||
resetPasswordForm.value.code = code as string
|
||||
resetPasswordForm.value.email = email as string
|
||||
} else {
|
||||
router.push('forgot_password')
|
||||
}
|
||||
})
|
||||
|
||||
const rules = ref<FormRules<ResetPasswordRequest>>({
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.login.loginForm.re_password.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: t('views.login.loginForm.password.lengthMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
re_password: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.login.loginForm.re_password.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: t('views.login.loginForm.password.lengthMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {
|
||||
callback(new Error(t('views.login.loginForm.re_password.validatorMessage')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
const resetPasswordFormRef = ref<FormInstance>()
|
||||
const loading = ref<boolean>(false)
|
||||
const resetPassword = () => {
|
||||
resetPasswordFormRef.value
|
||||
?.validate()
|
||||
.then(() => UserApi.resetPassword(resetPasswordForm.value, loading))
|
||||
.then(() => {
|
||||
MsgSuccess(t('common.modifySuccess'))
|
||||
router.push({ name: 'login' })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div class="login-form-container">
|
||||
<div class="login-title">
|
||||
<div class="logo text-center">
|
||||
<LogoFull height="45px" />
|
||||
</div>
|
||||
<div class="sub-title text-center" v-if="subTitle">
|
||||
<el-text type="info">{{ subTitle }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
<el-card class="login-card">
|
||||
<slot></slot>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
title: String,
|
||||
subTitle: String
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.login-form-container {
|
||||
width: 480px;
|
||||
|
||||
.login-title {
|
||||
margin-bottom: 32px;
|
||||
.sub-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.login-card {
|
||||
border-radius: 8px;
|
||||
padding: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<div class="login-warp flex-center">
|
||||
<div class="login-container w-full h-full">
|
||||
<el-row class="container w-full h-full">
|
||||
<el-col :xs="0" :sm="0" :md="10" :lg="10" :xl="10" class="left-container">
|
||||
<div class="login-image" :style="{ backgroundImage: `url(${loginImage})` }"></div>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="14" :lg="14" :xl="14" class="right-container flex-center">
|
||||
<el-dropdown trigger="click" type="primary" class="lang" v-if="lang">
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu style="width: 180px">
|
||||
<el-dropdown-item
|
||||
v-for="(lang, index) in langList"
|
||||
:key="index"
|
||||
:value="lang.value"
|
||||
@click="changeLang(lang.value)"
|
||||
class="flex-between"
|
||||
>
|
||||
<span :class="lang.value === user.getLanguage() ? 'primary' : ''">{{
|
||||
lang.label
|
||||
}}</span>
|
||||
|
||||
<el-icon
|
||||
:class="lang.value === user.getLanguage() ? 'primary' : ''"
|
||||
v-if="lang.value === user.getLanguage()"
|
||||
>
|
||||
<Check />
|
||||
</el-icon>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
<el-button>
|
||||
{{ currentLanguage }}<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
</el-dropdown>
|
||||
<slot></slot>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { getThemeImg } from '@/utils/theme'
|
||||
import useStore from '@/stores'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { langList, localeConfigKey, getBrowserLang } from '@/locales/index'
|
||||
defineProps({
|
||||
lang: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
const { user, theme } = useStore()
|
||||
|
||||
const changeLang = (lang: string) => {
|
||||
useLocalStorage(localeConfigKey, getBrowserLang()).value = lang
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const currentLanguage = computed(() => {
|
||||
return langList.value?.filter((v: any) => v.value === user.getLanguage())?.[0]?.label
|
||||
})
|
||||
|
||||
const fileURL = computed(() => {
|
||||
if (theme.themeInfo?.loginImage) {
|
||||
if (typeof theme.themeInfo?.loginImage === 'string') {
|
||||
return theme.themeInfo?.loginImage
|
||||
} else {
|
||||
return URL.createObjectURL(theme.themeInfo?.loginImage)
|
||||
}
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
const loginImage = computed(() => {
|
||||
if (theme.themeInfo?.loginImage) {
|
||||
return `${fileURL.value}`
|
||||
} else {
|
||||
const imgName = getThemeImg(theme.themeInfo?.theme)
|
||||
const imgPath = `../../assets/theme/${imgName}.jpg`
|
||||
const imageUrl = new URL(imgPath, import.meta.url).href
|
||||
return imageUrl
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.login-warp {
|
||||
height: 100vh;
|
||||
|
||||
.login-image {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.right-container {
|
||||
position: relative;
|
||||
.lang {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,450 @@
|
|||
<template>
|
||||
<UserLoginLayout v-if="!loading" v-loading="loading">
|
||||
<LoginContainer :subTitle="theme.themeInfo?.slogan || $t('theme.defaultSlogan')">
|
||||
<h2 class="mb-24" v-if="!showQrCodeTab">{{ loginMode || $t('views.login.title') }}</h2>
|
||||
<div v-if="!showQrCodeTab">
|
||||
<el-form
|
||||
class="login-form"
|
||||
:rules="rules"
|
||||
:model="loginForm"
|
||||
ref="loginFormRef"
|
||||
@keyup.enter="login"
|
||||
>
|
||||
<div class="mb-24">
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="loginForm.username"
|
||||
:placeholder="$t('views.login.loginForm.username.placeholder')"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mb-24">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="loginForm.password"
|
||||
:placeholder="$t('views.login.loginForm.password.placeholder')"
|
||||
show-password
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mb-24" v-if="loginMode !== 'LDAP'">
|
||||
<el-form-item prop="captcha">
|
||||
<div class="flex-between w-full">
|
||||
<el-input
|
||||
size="large"
|
||||
class="input-item"
|
||||
v-model="loginForm.captcha"
|
||||
:placeholder="$t('views.login.loginForm.captcha.placeholder')"
|
||||
>
|
||||
</el-input>
|
||||
|
||||
<img
|
||||
:src="identifyCode"
|
||||
alt=""
|
||||
height="38"
|
||||
class="ml-8 cursor border border-r-4"
|
||||
@click="makeCode"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<el-button
|
||||
size="large"
|
||||
type="primary"
|
||||
class="w-full"
|
||||
@click="loginHandle"
|
||||
:loading="loading"
|
||||
>
|
||||
{{ $t('views.login.buttons.login') }}
|
||||
</el-button>
|
||||
<div class="operate-container flex-between mt-12">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
class="forgot-password"
|
||||
@click="router.push('/forgot_password')"
|
||||
link
|
||||
type="primary"
|
||||
>
|
||||
{{ $t('views.login.forgotPassword') }}?
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showQrCodeTab">
|
||||
<QrCodeTab :tabs="orgOptions" />
|
||||
</div>
|
||||
<div class="login-gradient-divider lighter mt-24" v-if="modeList.length > 1">
|
||||
<span>{{ $t('views.login.moreMethod') }}</span>
|
||||
</div>
|
||||
<div class="text-center mt-16">
|
||||
<template v-for="item in modeList">
|
||||
<el-button
|
||||
v-if="item !== '' && loginMode !== item && item !== 'QR_CODE'"
|
||||
circle
|
||||
:key="item"
|
||||
class="login-button-circle color-secondary"
|
||||
@click="changeMode(item)"
|
||||
>
|
||||
<span
|
||||
:style="{
|
||||
'font-size': item === 'OAUTH2' ? '8px' : '10px',
|
||||
color: user.themeInfo?.theme,
|
||||
}"
|
||||
>{{ item }}</span
|
||||
>
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="item === 'QR_CODE' && loginMode !== item"
|
||||
circle
|
||||
:key="item"
|
||||
class="login-button-circle color-secondary"
|
||||
@click="changeMode('QR_CODE')"
|
||||
>
|
||||
<img src="@/assets/scan/icon_qr_outlined.svg" width="25px" />
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="item === '' && loginMode !== ''"
|
||||
circle
|
||||
:key="item"
|
||||
class="login-button-circle color-secondary"
|
||||
style="font-size: 24px"
|
||||
icon="UserFilled"
|
||||
@click="changeMode('')"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</LoginContainer>
|
||||
</UserLoginLayout>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, onBeforeMount } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { LoginRequest } from '@/api/type/login'
|
||||
import LoginContainer from '@/layout/login-layout/LoginContainer.vue'
|
||||
import UserLoginLayout from '@/layout/login-layout/UserLoginLayout.vue'
|
||||
import loginApi from '@/api/user/login'
|
||||
import authApi from '@/api/system-settings/auth-setting'
|
||||
import { t, getBrowserLang } from '@/locales'
|
||||
import useStore from '@/stores'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import QrCodeTab from '@/views/login/scanCompinents/QrCodeTab.vue'
|
||||
import { MsgConfirm, MsgError } from '@/utils/message.ts'
|
||||
import * as dd from 'dingtalk-jsapi'
|
||||
import { loadScript } from '@/utils/utils'
|
||||
const router = useRouter()
|
||||
const { login, user, theme } = useStore()
|
||||
const { locale } = useI18n({ useScope: 'global' })
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const identifyCode = ref<string>('')
|
||||
|
||||
const loginFormRef = ref<FormInstance>()
|
||||
const loginForm = ref<LoginRequest>({
|
||||
username: '',
|
||||
password: '',
|
||||
captcha: '',
|
||||
})
|
||||
|
||||
const rules = ref<FormRules<LoginRequest>>({
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.login.loginForm.username.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.login.loginForm.password.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
captcha: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.login.loginForm.captcha.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const loginHandle = () => {
|
||||
loginFormRef.value?.validate().then(() => {
|
||||
if (loginMode.value === 'LDAP') {
|
||||
login.asyncLdapLogin(loginForm.value, loading).then(() => {
|
||||
locale.value = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
} else {
|
||||
login.asyncLogin(loginForm.value, loading).then(() => {
|
||||
locale.value = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'
|
||||
localStorage.setItem('workspace_id', 'default')
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function makeCode() {
|
||||
loginApi.getCaptcha().then((res: any) => {
|
||||
identifyCode.value = res.data.captcha
|
||||
})
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
makeCode()
|
||||
})
|
||||
|
||||
const modeList = ref<string[]>([''])
|
||||
const QrList = ref<any[]>([''])
|
||||
const loginMode = ref('')
|
||||
const showQrCodeTab = ref(false)
|
||||
|
||||
interface qrOption {
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const orgOptions = ref<qrOption[]>([])
|
||||
|
||||
function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = (Math.random() * 16) | 0
|
||||
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
function redirectAuth(authType: string) {
|
||||
if (authType === 'LDAP' || authType === '') {
|
||||
return
|
||||
}
|
||||
authApi.getAuthSetting(authType, loading).then((res: any) => {
|
||||
if (!res.data) {
|
||||
return
|
||||
}
|
||||
MsgConfirm(t('views.login.jump_tip'), '', {
|
||||
confirmButtonText: t('views.login.jump'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
confirmButtonClass: '',
|
||||
})
|
||||
.then(() => {
|
||||
if (!res.data.config_data) {
|
||||
return
|
||||
}
|
||||
const config = res.data.config_data
|
||||
const redirectUrl = eval(`\`${config.redirectUrl}\``)
|
||||
let url
|
||||
if (authType === 'CAS') {
|
||||
url = config.ldpUri
|
||||
if (url.indexOf('?') !== -1) {
|
||||
url = `${config.ldpUri}&service=${encodeURIComponent(redirectUrl)}`
|
||||
} else {
|
||||
url = `${config.ldpUri}?service=${encodeURIComponent(redirectUrl)}`
|
||||
}
|
||||
}
|
||||
if (authType === 'OIDC') {
|
||||
const scope = config.scope || 'openid+profile+email'
|
||||
url = `${config.authEndpoint}?client_id=${config.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}`
|
||||
if (config.state) {
|
||||
url += `&state=${config.state}`
|
||||
}
|
||||
}
|
||||
if (authType === 'OAuth2') {
|
||||
url =
|
||||
`${config.authEndpoint}?client_id=${config.clientId}&response_type=code` +
|
||||
`&redirect_uri=${redirectUrl}&state=${uuidv4()}`
|
||||
if (config.scope) {
|
||||
url += `&scope=${config.scope}`
|
||||
}
|
||||
}
|
||||
if (url) {
|
||||
window.location.href = url
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
})
|
||||
}
|
||||
|
||||
function changeMode(val: string) {
|
||||
loginMode.value = val === 'LDAP' ? val : ''
|
||||
if (val === 'QR_CODE') {
|
||||
loginMode.value = val
|
||||
showQrCodeTab.value = true
|
||||
return
|
||||
}
|
||||
showQrCodeTab.value = false
|
||||
loginForm.value = {
|
||||
username: '',
|
||||
password: '',
|
||||
captcha: '',
|
||||
}
|
||||
redirectAuth(val)
|
||||
loginFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
loading.value = true
|
||||
user.asyncGetProfile().then((res) => {
|
||||
if (user.isEnterprise()) {
|
||||
user
|
||||
.getAuthType()
|
||||
.then((res) => {
|
||||
//如果结果包含LDAP,把LDAP放在第一个
|
||||
const ldapIndex = res.indexOf('LDAP')
|
||||
if (ldapIndex !== -1) {
|
||||
const [ldap] = res.splice(ldapIndex, 1)
|
||||
res.unshift(ldap)
|
||||
}
|
||||
modeList.value = [...modeList.value, ...res]
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
user
|
||||
.getQrType()
|
||||
.then((res) => {
|
||||
if (res.length > 0) {
|
||||
modeList.value = ['QR_CODE', ...modeList.value]
|
||||
QrList.value = res
|
||||
QrList.value.forEach((item) => {
|
||||
orgOptions.value.push({
|
||||
key: item,
|
||||
value:
|
||||
item === 'wecom'
|
||||
? t('views.system.authentication.scanTheQRCode.wecom')
|
||||
: item === 'dingtalk'
|
||||
? t('views.system.authentication.scanTheQRCode.dingtalk')
|
||||
: t('views.system.authentication.scanTheQRCode.lark'),
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false))
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
})
|
||||
declare const window: any
|
||||
|
||||
onMounted(() => {
|
||||
makeCode()
|
||||
const route = useRoute()
|
||||
const currentUrl = ref(route.fullPath)
|
||||
const params = new URLSearchParams(currentUrl.value.split('?')[1])
|
||||
const client = params.get('client')
|
||||
|
||||
const handleDingTalk = () => {
|
||||
const code = params.get('corpId')
|
||||
if (code) {
|
||||
dd.runtime.permission.requestAuthCode({ corpId: code }).then((res) => {
|
||||
console.log('DingTalk client request success:', res)
|
||||
user.dingOauth2Callback(res.code).then(() => {
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleLark = () => {
|
||||
const appId = params.get('appId')
|
||||
const callRequestAuthCode = () => {
|
||||
window.tt?.requestAuthCode({
|
||||
appId: appId,
|
||||
success: (res: any) => {
|
||||
user.larkCallback(res.code).then(() => {
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
},
|
||||
fail: (error: any) => {
|
||||
MsgError(error)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
loadScript('https://lf-scm-cn.feishucdn.com/lark/op/h5-js-sdk-1.5.35.js', {
|
||||
jsId: 'lark-sdk',
|
||||
forceReload: true,
|
||||
})
|
||||
.then(() => {
|
||||
if (window.tt) {
|
||||
window.tt.requestAccess({
|
||||
appID: appId,
|
||||
scopeList: [],
|
||||
success: (res: any) => {
|
||||
user.larkCallback(res.code).then(() => {
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
},
|
||||
fail: (error: any) => {
|
||||
const { errno } = error
|
||||
if (errno === 103) {
|
||||
callRequestAuthCode()
|
||||
}
|
||||
},
|
||||
})
|
||||
} else {
|
||||
callRequestAuthCode()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('SDK 加载失败:', error)
|
||||
})
|
||||
}
|
||||
|
||||
switch (client) {
|
||||
case 'dingtalk':
|
||||
handleDingTalk()
|
||||
break
|
||||
case 'lark':
|
||||
handleLark()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.login-gradient-divider {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
color: var(--el-color-info);
|
||||
|
||||
::before {
|
||||
content: '';
|
||||
width: 25%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, rgba(222, 224, 227, 0) 0%, #dee0e3 100%);
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
::after {
|
||||
content: '';
|
||||
width: 25%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, #dee0e3 0%, rgba(222, 224, 227, 0) 100%);
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.login-button-circle {
|
||||
padding: 20px !important;
|
||||
margin: 0 4px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<el-tabs v-model="activeKey" @tab-change="selectTab">
|
||||
<template v-for="item in tabs" :key="item.key">
|
||||
<el-tab-pane :label="item.value" :name="item.key">
|
||||
<div class="text-center mt-16" v-if="item.key === activeKey">
|
||||
<component
|
||||
:is="defineAsyncComponent(() => import(`./${item.key}QrCode.vue`))"
|
||||
:config="config"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, defineAsyncComponent } from 'vue'
|
||||
import useStore from '@/stores'
|
||||
|
||||
interface Tab {
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface PlatformConfig {
|
||||
app_key: string
|
||||
app_secret: string
|
||||
auth_type: string
|
||||
config: any
|
||||
}
|
||||
|
||||
interface Config {
|
||||
app_key: string
|
||||
app_secret: string
|
||||
corpId?: string
|
||||
agentId?: string
|
||||
}
|
||||
|
||||
const props = defineProps<{ tabs: Tab[] }>()
|
||||
const activeKey = ref('')
|
||||
const allConfigs = ref<PlatformConfig[]>([])
|
||||
const config = ref<Config>({ app_key: '', app_secret: '' })
|
||||
// const logoUrl = ref('')
|
||||
const { user } = useStore()
|
||||
async function getPlatformInfo() {
|
||||
try {
|
||||
return await user.getQrSource()
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.tabs.length > 0) {
|
||||
activeKey.value = props.tabs[0].key
|
||||
}
|
||||
allConfigs.value = await getPlatformInfo()
|
||||
updateConfig(activeKey.value)
|
||||
})
|
||||
|
||||
const updateConfig = (key: string) => {
|
||||
const selectedConfig = allConfigs.value.find((item) => item.auth_type === key)
|
||||
if (selectedConfig && selectedConfig.config) {
|
||||
config.value = selectedConfig.config
|
||||
}
|
||||
}
|
||||
|
||||
const selectTab = (key: string) => {
|
||||
activeKey.value = key
|
||||
updateConfig(key)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
<template>
|
||||
<div class="flex-center mb-16">
|
||||
<img src="@/assets/scan/logo_dingtalk.svg" alt="" width="24px" class="mr-4" />
|
||||
<h2>{{ $t('views.system.authentication.scanTheQRCode.dingtalkQrCode') }}</h2>
|
||||
</div>
|
||||
<div class="ding-talk-qrName">
|
||||
<div id="ding-talk-qr"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useScriptTag } from '@vueuse/core'
|
||||
import { ref, watch } from 'vue'
|
||||
import useStore from '@/stores'
|
||||
import { MsgError } from '@/utils/message'
|
||||
// 声明 DTFrameLogin 和 QRLogin 的类型
|
||||
declare global {
|
||||
interface Window {
|
||||
DTFrameLogin: (
|
||||
frameParams: IDTLoginFrameParams,
|
||||
loginParams: IDTLoginLoginParams,
|
||||
successCbk: (result: IDTLoginSuccess) => void,
|
||||
errorCbk?: (errorMsg: string) => void
|
||||
) => void
|
||||
QRLogin: (QRLogin: qrLogin) => Record<any, any>
|
||||
}
|
||||
}
|
||||
|
||||
// 定义接口类型
|
||||
interface IDTLoginFrameParams {
|
||||
id: string
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
|
||||
interface IDTLoginLoginParams {
|
||||
redirect_uri: string
|
||||
response_type: string
|
||||
client_id: string
|
||||
scope: string
|
||||
prompt: string
|
||||
state?: string
|
||||
org_type?: string
|
||||
corpId?: string
|
||||
exclusiveLogin?: string
|
||||
exclusiveCorpId?: string
|
||||
}
|
||||
|
||||
interface IDTLoginSuccess {
|
||||
redirectUrl: string
|
||||
authCode: string
|
||||
state?: string
|
||||
}
|
||||
|
||||
interface qrLogin {
|
||||
id: string
|
||||
goto: string
|
||||
width: string
|
||||
height: string
|
||||
style?: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
config: {
|
||||
app_secret: string
|
||||
app_key: string
|
||||
corp_id: string
|
||||
}
|
||||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const { user } = useStore()
|
||||
const { load } = useScriptTag('https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js')
|
||||
const isConfigReady = ref(false)
|
||||
|
||||
const initActive = async () => {
|
||||
try {
|
||||
await load(true)
|
||||
if (!isConfigReady.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const data = {
|
||||
appKey: props.config.app_key,
|
||||
appSecret: props.config.app_secret,
|
||||
corp_id: props.config.corp_id
|
||||
}
|
||||
|
||||
const redirectUri = encodeURIComponent(window.location.origin)
|
||||
window.DTFrameLogin(
|
||||
{
|
||||
id: 'ding-talk-qr',
|
||||
width: 280,
|
||||
height: 280
|
||||
},
|
||||
{
|
||||
redirect_uri: redirectUri,
|
||||
client_id: data.appKey,
|
||||
scope: 'openid corpid',
|
||||
response_type: 'code',
|
||||
state: 'fit2cloud-ding-qr',
|
||||
prompt: 'consent',
|
||||
corpId: data.corp_id
|
||||
},
|
||||
(loginResult) => {
|
||||
const authCode = loginResult.authCode
|
||||
user.dingCallback(authCode).then(() => {
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
},
|
||||
(errorMsg: string) => {
|
||||
MsgError(errorMsg)
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.config,
|
||||
(newConfig) => {
|
||||
if (newConfig.app_key && newConfig.corp_id) {
|
||||
isConfigReady.value = true
|
||||
initActive()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ding-talk-qrName {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
height: 280px;
|
||||
width: 280px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div class="flex-center mb-16">
|
||||
<img src="@/assets/scan/logo_lark.svg " alt="" width="24px" class="mr-4" />
|
||||
<h2>{{ $t('views.system.authentication.scanTheQRCode.larkQrCode') }}</h2>
|
||||
</div>
|
||||
<div id="lark-qr" class="lark-qrName"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useScriptTag } from '@vueuse/core'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const { load } = useScriptTag(
|
||||
'https://lf-package-cn.feishucdn.com/obj/feishu-static/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.3.js'
|
||||
)
|
||||
|
||||
const props = defineProps<{
|
||||
config: {
|
||||
app_secret: string
|
||||
app_key: string
|
||||
}
|
||||
}>()
|
||||
|
||||
const initActive = async () => {
|
||||
const scriptLoaded = await load(true)
|
||||
if (!scriptLoaded) {
|
||||
console.error('飞书二维码 SDK 加载失败')
|
||||
return
|
||||
}
|
||||
|
||||
const data = {
|
||||
agentId: props.config.app_key,
|
||||
appSecret: props.config.app_secret
|
||||
}
|
||||
|
||||
const redirectUrl = encodeURIComponent(`${window.location.origin}/api/feishu`)
|
||||
const url = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${data.agentId}&redirect_uri=${redirectUrl}&response_type=code&state=fit2cloud-lark-qr`
|
||||
|
||||
const QRLoginObj = window.QRLogin({
|
||||
id: 'lark-qr',
|
||||
goto: url,
|
||||
width: '266',
|
||||
height: '266',
|
||||
style: 'width:280px;height:280px;border:1px solid #e8e8e8;margin:0 auto;border-radius:8px;'
|
||||
})
|
||||
|
||||
window.addEventListener('message', async (event: any) => {
|
||||
if (QRLoginObj.matchOrigin(event.origin) && QRLoginObj.matchData(event.data)) {
|
||||
const loginTmpCode = event.data.tmp_code
|
||||
window.location.href = `${url}&tmp_code=${loginTmpCode}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initActive()
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<div id="wecom-qr" class="wecom-qr" style="margin-left: 50px"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import * as ww from '@wecom/jssdk'
|
||||
import {
|
||||
WWLoginLangType,
|
||||
WWLoginPanelSizeType,
|
||||
WWLoginRedirectType,
|
||||
WWLoginType
|
||||
} from '@wecom/jssdk'
|
||||
import { ref, nextTick, defineProps } from 'vue'
|
||||
import { MsgError } from '@/utils/message'
|
||||
import useStore from '@/stores'
|
||||
import { getBrowserLang } from '@/locales'
|
||||
const router = useRouter()
|
||||
|
||||
const wwLogin = ref({})
|
||||
const obj = ref<any>({ isWeComLogin: false })
|
||||
const { user } = useStore()
|
||||
|
||||
const props = defineProps<{
|
||||
config: {
|
||||
app_secret: string
|
||||
app_key: string
|
||||
corp_id?: string
|
||||
agent_id?: string
|
||||
}
|
||||
}>()
|
||||
|
||||
const init = async () => {
|
||||
await nextTick() // 确保DOM已更新
|
||||
const data = {
|
||||
corpId: props.config.corp_id,
|
||||
agentId: props.config.agent_id
|
||||
}
|
||||
const lang = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'
|
||||
const redirectUri = window.location.origin
|
||||
try {
|
||||
wwLogin.value = ww.createWWLoginPanel({
|
||||
el: '#wecom-qr',
|
||||
params: {
|
||||
login_type: WWLoginType.corpApp,
|
||||
appid: data.corpId || '',
|
||||
agentid: data.agentId,
|
||||
redirect_uri: redirectUri,
|
||||
state: 'fit2cloud-wecom-qr',
|
||||
lang: lang === 'zh-CN' || lang === 'zh-Hant' ? WWLoginLangType.zh : WWLoginLangType.en,
|
||||
redirect_type: WWLoginRedirectType.callback,
|
||||
panel_size: WWLoginPanelSizeType.small
|
||||
},
|
||||
onCheckWeComLogin: obj.value,
|
||||
async onLoginSuccess({ code }: any) {
|
||||
user.wecomCallback(code).then(() => {
|
||||
setTimeout(() => {
|
||||
router.push({ name: 'home' })
|
||||
})
|
||||
})
|
||||
},
|
||||
onLoginFail(err) {
|
||||
MsgError(`${err.errMsg}`)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error initializing login panel:', error)
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wecom-qr {
|
||||
margin-top: -20px;
|
||||
height: 331px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
size="large"
|
||||
class="input-item"
|
||||
v-model="resetPasswordForm.re_password"
|
||||
:placeholder="$t('views.login.loginForm..re_password.placeholder')"
|
||||
:placeholder="$t('views.login.loginForm.re_password.placeholder')"
|
||||
show-password
|
||||
>
|
||||
</el-input>
|
||||
|
|
@ -55,6 +55,8 @@
|
|||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import LoginContainer from '@/layout/login-layout/LoginContainer.vue'
|
||||
import LoginLayout from '@/layout/login-layout/LoginLayout.vue'
|
||||
import type { ResetPasswordRequest } from '@/api/type/user'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@
|
|||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import LoginContainer from '@/layout/login-layout/LoginContainer.vue'
|
||||
import LoginLayout from '@/layout/login-layout/LoginLayout.vue'
|
||||
import type { ResetPasswordRequest } from '@/api/type/user'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
|
|
|
|||
|
|
@ -124,8 +124,8 @@ import {onMounted, ref, onBeforeMount} from 'vue'
|
|||
import {useRoute, useRouter} from 'vue-router'
|
||||
import type {FormInstance, FormRules} from 'element-plus'
|
||||
import type {LoginRequest} from '@/api/type/login'
|
||||
import LoginContainer from '@/views/login/components/LoginContainer.vue'
|
||||
import LoginLayout from '@/views/login/components/LoginLayout.vue'
|
||||
import LoginContainer from '@/layout/login-layout/LoginContainer.vue'
|
||||
import LoginLayout from '@/layout/login-layout/LoginLayout.vue'
|
||||
import loginApi from '@/api/user/login'
|
||||
import authApi from '@/api/system-settings/auth-setting'
|
||||
import {t, getBrowserLang} from '@/locales'
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import LoginLayout from "@/views/login/components/LoginLayout.vue";
|
||||
import LoginContainer from "@/views/login/components/LoginContainer.vue";
|
||||
import LoginLayout from "@/layout/login-layout/LoginLayout.vue";
|
||||
import LoginContainer from "@/layout/login-layout/LoginContainer.vue";
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue