feat: 独立登录页面
This commit is contained in:
2
app.vue
2
app.vue
@@ -26,6 +26,8 @@ onMounted(() => {
|
|||||||
icon: 'i-tabler-alert-triangle',
|
icon: 'i-tabler-alert-triangle',
|
||||||
})
|
})
|
||||||
modal.open(ModalAuthentication)
|
modal.open(ModalAuthentication)
|
||||||
|
} else if (!res && !loginState.token) {
|
||||||
|
router.replace('/user/authenticate')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ export const useVideoSubtitleEmbedding = async (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`video clip: ${videoUrl}`)
|
||||||
|
|
||||||
const videoClip = new MP4Clip((await fetch(videoUrl)).body!)
|
const videoClip = new MP4Clip((await fetch(videoUrl)).body!)
|
||||||
const videoSprite = new OffscreenSprite(videoClip)
|
const videoSprite = new OffscreenSprite(videoClip)
|
||||||
videoSprite.time = { duration: videoClip.meta.duration, offset: 0 }
|
videoSprite.time = { duration: videoClip.meta.duration, offset: 0 }
|
||||||
|
|||||||
9
layouts/authenticate.vue
Normal file
9
layouts/authenticate.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script lang="ts" setup></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -37,28 +37,40 @@ const links = [
|
|||||||
label: 'AI 工具导航',
|
label: 'AI 工具导航',
|
||||||
icon: 'tabler:planet',
|
icon: 'tabler:planet',
|
||||||
to: '/aigc/navigation',
|
to: '/aigc/navigation',
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
[{
|
[
|
||||||
label: 'support@fenshenzhike.com',
|
{
|
||||||
slot: 'account',
|
label: 'support@fenshenzhike.com',
|
||||||
disabled: true,
|
slot: 'account',
|
||||||
}], [{
|
disabled: true,
|
||||||
label: '账号管理',
|
},
|
||||||
icon: 'i-tabler-user-circle',
|
],
|
||||||
to: '/profile'
|
[
|
||||||
}], [{
|
{
|
||||||
label: '注销登录',
|
label: '账号管理',
|
||||||
icon: 'i-tabler-logout',
|
icon: 'i-tabler-user-circle',
|
||||||
click: () => loginState.logout().then(() => toast.add({
|
to: '/profile',
|
||||||
title: '退出登录',
|
},
|
||||||
description: `您已成功退出登录账号`,
|
],
|
||||||
color: 'indigo',
|
[
|
||||||
icon: 'i-tabler-logout-2',
|
{
|
||||||
})),
|
label: '注销登录',
|
||||||
}],
|
icon: 'i-tabler-logout',
|
||||||
|
click: () =>
|
||||||
|
loginState.logout().then(() => {
|
||||||
|
toast.add({
|
||||||
|
title: '退出登录',
|
||||||
|
description: `您已成功退出登录账号`,
|
||||||
|
color: 'indigo',
|
||||||
|
icon: 'i-tabler-logout-2',
|
||||||
|
})
|
||||||
|
router.push({ path: '/user/authenticate' })
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
const open_login_modal = () => {
|
const open_login_modal = () => {
|
||||||
@@ -70,23 +82,38 @@ const open_login_modal = () => {
|
|||||||
<div class="relative grid w-full min-h-screen">
|
<div class="relative grid w-full min-h-screen">
|
||||||
<header>
|
<header>
|
||||||
<h1 class="inline-flex flex-col">
|
<h1 class="inline-flex flex-col">
|
||||||
<span class="text-lg text-neutral-600 dark:text-neutral-300 font-bold">AIGC 微课视频研创平台</span>
|
<span class="text-lg text-neutral-600 dark:text-neutral-300 font-bold">
|
||||||
|
AIGC 微课视频研创平台
|
||||||
|
</span>
|
||||||
<!-- <span class="text-xs text-neutral-600 dark:text-neutral-300">眩生花科技</span> -->
|
<!-- <span class="text-xs text-neutral-600 dark:text-neutral-300">眩生花科技</span> -->
|
||||||
</h1>
|
</h1>
|
||||||
<div class="hidden md:block">
|
<div class="hidden md:block">
|
||||||
<UHorizontalNavigation :links="links" class="select-none"/>
|
<UHorizontalNavigation
|
||||||
|
:links="links"
|
||||||
|
class="select-none"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row items-center gap-4">
|
<div class="flex flex-row items-center gap-4">
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<UButton
|
<UButton
|
||||||
:icon="isDark ? 'i-line-md-sunny-outline-to-moon-alt-loop-transition' : 'i-line-md-moon-alt-to-sunny-outline-loop-transition'"
|
:icon="
|
||||||
|
isDark
|
||||||
|
? 'i-line-md-sunny-outline-to-moon-alt-loop-transition'
|
||||||
|
: 'i-line-md-moon-alt-to-sunny-outline-loop-transition'
|
||||||
|
"
|
||||||
color="gray"
|
color="gray"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
aria-label="Theme"
|
aria-label="Theme"
|
||||||
@click="isDark = !isDark"
|
@click="isDark = !isDark"
|
||||||
/>
|
/>
|
||||||
<UButton v-if="!loginState.is_logged_in" label="登录或注册" size="xs" class="font-bold" color="indigo"
|
<UButton
|
||||||
@click="open_login_modal"/>
|
v-if="!loginState.is_logged_in"
|
||||||
|
label="登录或注册"
|
||||||
|
size="xs"
|
||||||
|
class="font-bold"
|
||||||
|
color="indigo"
|
||||||
|
@click="open_login_modal"
|
||||||
|
/>
|
||||||
<UDropdown
|
<UDropdown
|
||||||
v-if="loginState.is_logged_in"
|
v-if="loginState.is_logged_in"
|
||||||
:items="items"
|
:items="items"
|
||||||
@@ -105,18 +132,28 @@ const open_login_modal = () => {
|
|||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
<p class="flex items-center gap-1">
|
<p class="flex items-center gap-1">
|
||||||
已登录为
|
已登录为
|
||||||
<UBadge v-if="loginState.user.auth_code === 2" color="amber" size="xs" variant="subtle">
|
<UBadge
|
||||||
|
v-if="loginState.user.auth_code === 2"
|
||||||
|
color="amber"
|
||||||
|
size="xs"
|
||||||
|
variant="subtle"
|
||||||
|
>
|
||||||
OP
|
OP
|
||||||
</UBadge>
|
</UBadge>
|
||||||
</p>
|
</p>
|
||||||
<p class="truncate whitespace-nowrap max-w-40 font-medium text-gray-900 dark:text-white">
|
<p
|
||||||
|
class="truncate whitespace-nowrap max-w-40 font-medium text-gray-900 dark:text-white"
|
||||||
|
>
|
||||||
{{ loginState.user?.username }}
|
{{ loginState.user?.username }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #item="{ item }">
|
<template #item="{ item }">
|
||||||
<span class="truncate">{{ item.label }}</span>
|
<span class="truncate">{{ item.label }}</span>
|
||||||
<UIcon :name="item.icon" class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500 ms-auto"/>
|
<UIcon
|
||||||
|
:name="item.icon"
|
||||||
|
class="flex-shrink-0 h-4 w-4 text-gray-400 dark:text-gray-500 ms-auto"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</UDropdown>
|
</UDropdown>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
@@ -124,11 +161,13 @@ const open_login_modal = () => {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<slot/>
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p class="text-sm text-neutral-500">© {{ dayjs().year() }} 重庆眩生花科技有限公司</p>
|
<p class="text-sm text-neutral-500">
|
||||||
|
© {{ dayjs().year() }} 重庆眩生花科技有限公司
|
||||||
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -136,25 +175,25 @@ const open_login_modal = () => {
|
|||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
@apply bg-neutral-50 dark:bg-neutral-950 bg-fixed;
|
@apply bg-neutral-50 dark:bg-neutral-950 bg-fixed;
|
||||||
@apply bg-[url('~/assets/background-pattern.svg')] dark:bg-[url('~/assets/background-pattern-dark.svg')];
|
/* @apply bg-[url('~/assets/background-pattern.svg')] dark:bg-[url('~/assets/background-pattern-dark.svg')]; */
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
--bar-width: 5px;
|
--bar-width: 5px;
|
||||||
width: var(--bar-width);
|
width: var(--bar-width);
|
||||||
height: var(--bar-width)
|
height: var(--bar-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background-color: transparent
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
--bar-color: rgba(0, 0, 0, .1);
|
--bar-color: rgba(0, 0, 0, 0.1);
|
||||||
background-color: var(--bar-color);
|
background-color: var(--bar-color);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background-clip: content-box;
|
background-clip: content-box;
|
||||||
border: 1px solid transparent
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -191,4 +230,4 @@ footer {
|
|||||||
@apply dark:bg-neutral-900 dark:border-neutral-800;
|
@apply dark:bg-neutral-900 dark:border-neutral-800;
|
||||||
@apply flex flex-row items-center justify-between px-4;
|
@apply flex flex-row items-center justify-between px-4;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
571
pages/user/authenticate.vue
Normal file
571
pages/user/authenticate.vue
Normal file
@@ -0,0 +1,571 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FormSubmitEvent } from '#ui/types'
|
||||||
|
import { object, string, type InferType } from 'yup'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'authenticate',
|
||||||
|
})
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
|
const loginState = useLoginState()
|
||||||
|
|
||||||
|
const sms_triggered = ref(false)
|
||||||
|
const sms_sending = ref(false)
|
||||||
|
const sms_counting_down = ref(0)
|
||||||
|
const final_loading = ref(false)
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
key: 'sms',
|
||||||
|
label: '短信登录',
|
||||||
|
icon: 'i-tabler-message-2',
|
||||||
|
description: '使用短信验证码登录',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'account',
|
||||||
|
label: '密码登录',
|
||||||
|
icon: 'i-tabler-key',
|
||||||
|
description: '使用已有账号和密码登录',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'recovery',
|
||||||
|
label: '找回密码',
|
||||||
|
icon: 'i-tabler-lock',
|
||||||
|
description: '忘记密码时,可以通过手机号和验证码重置密码',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const currentTab = ref(0)
|
||||||
|
|
||||||
|
const accountForm = reactive<req.user.Login>({ username: '', password: '' })
|
||||||
|
const smsForm = reactive({ mobile: '', sms_code: [] })
|
||||||
|
|
||||||
|
function onSubmit(form: req.user.Login) {
|
||||||
|
console.log('Submitted form:', form)
|
||||||
|
final_loading.value = true
|
||||||
|
useFetchWrapped<req.user.Login, BaseResponse<resp.user.Login>>(
|
||||||
|
'App.User_User.Login',
|
||||||
|
{
|
||||||
|
username: form.username,
|
||||||
|
password: form.password,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
final_loading.value = false
|
||||||
|
if (res.ret !== 200) {
|
||||||
|
toast.add({
|
||||||
|
title: '登录失败',
|
||||||
|
description: res.msg || '未知错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!res.data.is_login) {
|
||||||
|
toast.add({
|
||||||
|
title: '登录失败',
|
||||||
|
description: res.msg || '账号或密码错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!res.data.token || !res.data.user_id) {
|
||||||
|
toast.add({
|
||||||
|
title: '登录失败',
|
||||||
|
description: res.msg || '无法获取登录状态',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loginState.token = res.data.token
|
||||||
|
loginState.user.id = res.data.user_id
|
||||||
|
loginState
|
||||||
|
.updateProfile()
|
||||||
|
.then(() => {
|
||||||
|
loginState.checkSession()
|
||||||
|
toast.add({
|
||||||
|
title: '登录成功',
|
||||||
|
description: `${loginState.user.username}, 欢迎回来`,
|
||||||
|
color: 'primary',
|
||||||
|
icon: 'i-tabler-login-2',
|
||||||
|
})
|
||||||
|
router.replace('/')
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.add({
|
||||||
|
title: '登录失败',
|
||||||
|
description: err.msg || '网络错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
final_loading.value = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.add({
|
||||||
|
title: '登录失败',
|
||||||
|
description: err.msg || '网络错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const obtainSmsCode = () => {
|
||||||
|
smsForm.sms_code = []
|
||||||
|
sms_sending.value = true
|
||||||
|
|
||||||
|
useFetchWrapped<req.user.SmsLogin, BaseResponse<resp.user.SmsLogin>>(
|
||||||
|
'App.User_User.MobileLogin',
|
||||||
|
{
|
||||||
|
mobile: smsForm.mobile,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.ret !== 200) {
|
||||||
|
sms_sending.value = false
|
||||||
|
toast.add({
|
||||||
|
title: '验证码发送失败',
|
||||||
|
description: res.msg || '未知错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sms_triggered.value = true
|
||||||
|
sms_sending.value = false
|
||||||
|
sms_counting_down.value = 60 // TODO: save timestamp to localstorage
|
||||||
|
toast.add({
|
||||||
|
title: '短信验证码已发送',
|
||||||
|
color: 'indigo',
|
||||||
|
icon: 'i-tabler-circle-check',
|
||||||
|
})
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
sms_counting_down.value--
|
||||||
|
if (sms_counting_down.value <= 0) {
|
||||||
|
clearInterval(interval)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.add({
|
||||||
|
title: '验证码发送失败',
|
||||||
|
description: err.msg || '网络错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle_sms_verify = (e: string[]) => {
|
||||||
|
final_loading.value = true
|
||||||
|
useFetchWrapped<
|
||||||
|
req.user.SmsLoginVerify,
|
||||||
|
BaseResponse<resp.user.SmsLoginVerify>
|
||||||
|
>('App.User_User.MobileLoginVerify', {
|
||||||
|
mobile: smsForm.mobile,
|
||||||
|
sms_code: e.join(''),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.ret !== 200) {
|
||||||
|
smsForm.sms_code = []
|
||||||
|
toast.add({
|
||||||
|
title: '登录失败',
|
||||||
|
description: res.msg || '未知错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!res.data.token || !res.data.person_id) {
|
||||||
|
smsForm.sms_code = []
|
||||||
|
toast.add({
|
||||||
|
title: '登录失败',
|
||||||
|
description: res.msg || '无法获取登录状态',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loginState.token = res.data.token
|
||||||
|
loginState.user.id = res.data.person_id
|
||||||
|
loginState
|
||||||
|
.updateProfile()
|
||||||
|
.then(() => {
|
||||||
|
loginState.checkSession()
|
||||||
|
toast.add({
|
||||||
|
title: '登录成功',
|
||||||
|
description: `${loginState.user.username}, 欢迎回来`,
|
||||||
|
color: 'primary',
|
||||||
|
icon: 'i-tabler-login-2',
|
||||||
|
})
|
||||||
|
router.replace('/')
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.add({
|
||||||
|
title: '登录失败',
|
||||||
|
description: err.msg || '网络错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
final_loading.value = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => (final_loading.value = false))
|
||||||
|
}
|
||||||
|
|
||||||
|
const forgetPasswordState = reactive({
|
||||||
|
mobile: '',
|
||||||
|
sms_code: '',
|
||||||
|
password: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const forgetPasswordSchema = object({
|
||||||
|
mobile: string()
|
||||||
|
.required('请输入手机号')
|
||||||
|
.matches(/^1[3-9]\d{9}$/, '手机号格式不正确'),
|
||||||
|
sms_code: string().required('请输入验证码').length(4, '验证码长度为4位'),
|
||||||
|
password: string().required('请输入新密码').min(6, '密码长度至少为6位'),
|
||||||
|
})
|
||||||
|
|
||||||
|
type ForgetPasswordSchema = InferType<typeof forgetPasswordSchema>
|
||||||
|
|
||||||
|
const obtainForgetSmsCode = () => {
|
||||||
|
forgetPasswordState.sms_code = ''
|
||||||
|
sms_sending.value = true
|
||||||
|
|
||||||
|
useFetchWrapped<req.user.SmsChangePasswordVerify, BaseResponse<{}>>(
|
||||||
|
'App.User_User.ForgotPasswordSms',
|
||||||
|
{
|
||||||
|
mobile: forgetPasswordState.mobile,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.ret !== 200) {
|
||||||
|
sms_sending.value = false
|
||||||
|
toast.add({
|
||||||
|
title: '验证码发送失败',
|
||||||
|
description: res.msg || '未知错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sms_triggered.value = true
|
||||||
|
sms_sending.value = false
|
||||||
|
sms_counting_down.value = 60 // TODO: save timestamp to localstorage
|
||||||
|
toast.add({
|
||||||
|
title: '短信验证码已发送',
|
||||||
|
color: 'indigo',
|
||||||
|
icon: 'i-tabler-circle-check',
|
||||||
|
})
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
sms_counting_down.value--
|
||||||
|
if (sms_counting_down.value <= 0) {
|
||||||
|
clearInterval(interval)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.add({
|
||||||
|
title: '验证码发送失败',
|
||||||
|
description: err.msg || '网络错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onForgetPasswordSubmit = (
|
||||||
|
event: FormSubmitEvent<ForgetPasswordSchema>
|
||||||
|
) => {
|
||||||
|
final_loading.value = true
|
||||||
|
useFetchWrapped<req.user.SmsChangePassword, BaseResponse<{}>>(
|
||||||
|
'App.User_User.ForgotPassword',
|
||||||
|
{
|
||||||
|
mobile: event.data.mobile,
|
||||||
|
sms_code: event.data.sms_code,
|
||||||
|
new_password: event.data.password,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
final_loading.value = false
|
||||||
|
if (res.ret !== 200) {
|
||||||
|
toast.add({
|
||||||
|
title: '重置密码失败',
|
||||||
|
description: res.msg || '未知错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toast.add({
|
||||||
|
title: '重置密码成功',
|
||||||
|
description: '请您继续登录',
|
||||||
|
color: 'green',
|
||||||
|
icon: 'i-tabler-circle-check',
|
||||||
|
})
|
||||||
|
currentTab.value = 1
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast.add({
|
||||||
|
title: '重置密码失败',
|
||||||
|
description: err.msg || '未知错误',
|
||||||
|
color: 'red',
|
||||||
|
icon: 'i-tabler-circle-x',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full min-h-screen flex flex-col justify-center items-center">
|
||||||
|
<div class="flex flex-col items-center py-12 gap-2">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">
|
||||||
|
AIGC 微课视频研创平台
|
||||||
|
</h1>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
请使用以下方式登录
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<UTabs
|
||||||
|
:items="items"
|
||||||
|
class="w-full sm:w-[400px]"
|
||||||
|
v-model="currentTab"
|
||||||
|
>
|
||||||
|
<template #default="{ item, index, selected }">
|
||||||
|
<div class="flex items-center gap-2 relative truncate">
|
||||||
|
<span class="truncate">{{ item.label }}</span>
|
||||||
|
<span
|
||||||
|
v-if="selected"
|
||||||
|
class="absolute -right-4 w-2 h-2 rounded-full bg-primary-500 dark:bg-primary-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #item="{ item }">
|
||||||
|
<UCard @submit.prevent="() => onSubmit(accountForm)">
|
||||||
|
<template #header>
|
||||||
|
<p
|
||||||
|
class="text-base font-semibold leading-6 text-gray-900 dark:text-white"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</p>
|
||||||
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{{ item.description }}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="item.key === 'account'"
|
||||||
|
class="space-y-3"
|
||||||
|
>
|
||||||
|
<UFormGroup
|
||||||
|
label="用户名"
|
||||||
|
name="username"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<UInput
|
||||||
|
v-model="accountForm.username"
|
||||||
|
:disabled="final_loading"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</UFormGroup>
|
||||||
|
<UFormGroup
|
||||||
|
label="密码"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<UInput
|
||||||
|
v-model="accountForm.password"
|
||||||
|
:disabled="final_loading"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</UFormGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="item.key === 'sms'"
|
||||||
|
class="space-y-3"
|
||||||
|
>
|
||||||
|
<UFormGroup
|
||||||
|
label="手机号"
|
||||||
|
name="mobile"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<UButtonGroup class="w-full">
|
||||||
|
<UInput
|
||||||
|
v-model="smsForm.mobile"
|
||||||
|
:disabled="final_loading"
|
||||||
|
type="sms"
|
||||||
|
class="w-full"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<template #leading>
|
||||||
|
<span class="text-gray-500 dark:text-gray-400 text-xs">
|
||||||
|
+86
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</UInput>
|
||||||
|
<UButton
|
||||||
|
:label="
|
||||||
|
sms_counting_down
|
||||||
|
? `${sms_counting_down}秒后重发`
|
||||||
|
: '获取验证码'
|
||||||
|
"
|
||||||
|
@click="obtainSmsCode"
|
||||||
|
:loading="sms_sending"
|
||||||
|
:disabled="!!sms_counting_down || final_loading"
|
||||||
|
class="text-xs font-bold"
|
||||||
|
color="gray"
|
||||||
|
/>
|
||||||
|
</UButtonGroup>
|
||||||
|
</UFormGroup>
|
||||||
|
<Transition name="pin-root">
|
||||||
|
<div
|
||||||
|
v-if="sms_triggered"
|
||||||
|
class="w-full overflow-hidden"
|
||||||
|
>
|
||||||
|
<Label
|
||||||
|
for="pin-input"
|
||||||
|
class="pin-label"
|
||||||
|
>
|
||||||
|
验证码
|
||||||
|
</Label>
|
||||||
|
<PinInputRoot
|
||||||
|
id="sms-input"
|
||||||
|
v-model="smsForm.sms_code"
|
||||||
|
:disabled="sms_sending || final_loading"
|
||||||
|
placeholder="○"
|
||||||
|
class="w-full flex gap-2 justify-between md:justify-start items-center mt-1"
|
||||||
|
@complete="handle_sms_verify"
|
||||||
|
type="number"
|
||||||
|
otp
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<PinInputInput
|
||||||
|
v-for="(id, index) in 4"
|
||||||
|
:key="id"
|
||||||
|
:index="index"
|
||||||
|
class="pin-input w-12 h-12 text-center rounded-lg"
|
||||||
|
:autofocus="index === 0"
|
||||||
|
/>
|
||||||
|
</PinInputRoot>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="item.key === 'recovery'"
|
||||||
|
class="space-y-3"
|
||||||
|
>
|
||||||
|
<UForm
|
||||||
|
class="space-y-3"
|
||||||
|
:schema="forgetPasswordSchema"
|
||||||
|
:state="forgetPasswordState"
|
||||||
|
@submit="onForgetPasswordSubmit"
|
||||||
|
>
|
||||||
|
<UFormGroup
|
||||||
|
label="手机号"
|
||||||
|
name="mobile"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<UButtonGroup class="w-full">
|
||||||
|
<UInput
|
||||||
|
v-model="forgetPasswordState.mobile"
|
||||||
|
:disabled="final_loading"
|
||||||
|
type="tel"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<template #leading>
|
||||||
|
<span class="text-gray-500 dark:text-gray-400 text-xs">
|
||||||
|
+86
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</UInput>
|
||||||
|
<UButton
|
||||||
|
:label="
|
||||||
|
sms_counting_down
|
||||||
|
? `${sms_counting_down}秒后重发`
|
||||||
|
: '获取验证码'
|
||||||
|
"
|
||||||
|
@click="obtainForgetSmsCode"
|
||||||
|
:loading="sms_sending"
|
||||||
|
:disabled="!!sms_counting_down"
|
||||||
|
class="text-xs font-bold"
|
||||||
|
color="gray"
|
||||||
|
/>
|
||||||
|
</UButtonGroup>
|
||||||
|
</UFormGroup>
|
||||||
|
<UFormGroup
|
||||||
|
label="验证码"
|
||||||
|
name="sms_code"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<UInput
|
||||||
|
v-model="forgetPasswordState.sms_code"
|
||||||
|
type="sms"
|
||||||
|
class="w-full"
|
||||||
|
:disabled="final_loading"
|
||||||
|
/>
|
||||||
|
</UFormGroup>
|
||||||
|
<UFormGroup
|
||||||
|
label="新密码"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<UInput
|
||||||
|
v-model="forgetPasswordState.password"
|
||||||
|
type="password"
|
||||||
|
:disabled="final_loading"
|
||||||
|
/>
|
||||||
|
</UFormGroup>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<UButton
|
||||||
|
type="submit"
|
||||||
|
:loading="final_loading"
|
||||||
|
>
|
||||||
|
重置密码
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</UForm>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template
|
||||||
|
#footer
|
||||||
|
v-if="item.key === 'account'"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<UButton
|
||||||
|
type="submit"
|
||||||
|
color="black"
|
||||||
|
:loading="final_loading"
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
variant="link"
|
||||||
|
color="gray"
|
||||||
|
@click="currentTab = 2"
|
||||||
|
>
|
||||||
|
忘记密码
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UCard>
|
||||||
|
</template>
|
||||||
|
</UTabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
Reference in New Issue
Block a user