refactor!: 升级 @nuxt/ui@3,重构所有页面和组件,调整配置,移除不在需求中的页面

This commit is contained in:
2026-02-10 18:07:44 +08:00
parent d0bca215c1
commit 75f1987be3
49 changed files with 4892 additions and 6599 deletions

View File

@@ -4,8 +4,9 @@ import { object, string, type InferType } from 'yup'
import type { FormSubmitEvent } from '#ui/types'
import { useFetchWrapped } from '~/composables/useFetchWrapped'
const emit = defineEmits(['close'])
const toast = useToast()
const modal = useModal()
const overlay = useOverlay()
const loginState = useLoginState()
const sms_triggered = ref(false)
@@ -55,7 +56,7 @@ function onSubmit(form: req.user.Login) {
toast.add({
title: '登录失败',
description: res.msg || '未知错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
return
@@ -64,7 +65,7 @@ function onSubmit(form: req.user.Login) {
toast.add({
title: '登录失败',
description: res.msg || '账号或密码错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
return
@@ -73,7 +74,7 @@ function onSubmit(form: req.user.Login) {
toast.add({
title: '登录失败',
description: res.msg || '无法获取登录状态',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
return
@@ -84,7 +85,8 @@ function onSubmit(form: req.user.Login) {
.updateProfile()
.then(() => {
loginState.checkSession()
modal.close()
// TODO: only close the specific modal
overlay.closeAll()
toast.add({
title: '登录成功',
description: `${loginState.user.username}, 欢迎回来`,
@@ -96,7 +98,7 @@ function onSubmit(form: req.user.Login) {
toast.add({
title: '登录失败',
description: err.msg || '网络错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
})
@@ -108,7 +110,7 @@ function onSubmit(form: req.user.Login) {
toast.add({
title: '登录失败',
description: err.msg || '网络错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
})
@@ -130,7 +132,7 @@ const obtainSmsCode = () => {
toast.add({
title: '验证码发送失败',
description: res.msg || '未知错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
return
@@ -140,7 +142,7 @@ const obtainSmsCode = () => {
sms_counting_down.value = 60 // TODO: save timestamp to localstorage
toast.add({
title: '短信验证码已发送',
color: 'indigo',
color: 'primary',
icon: 'i-tabler-circle-check',
})
const interval = setInterval(() => {
@@ -154,7 +156,7 @@ const obtainSmsCode = () => {
toast.add({
title: '验证码发送失败',
description: err.msg || '网络错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
})
@@ -175,7 +177,7 @@ const handle_sms_verify = (e: string[]) => {
toast.add({
title: '登录失败',
description: res.msg || '未知错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
return
@@ -185,7 +187,7 @@ const handle_sms_verify = (e: string[]) => {
toast.add({
title: '登录失败',
description: res.msg || '无法获取登录状态',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
return
@@ -196,7 +198,7 @@ const handle_sms_verify = (e: string[]) => {
.updateProfile()
.then(() => {
loginState.checkSession()
modal.close()
emit('close')
toast.add({
title: '登录成功',
description: `${loginState.user.username}, 欢迎回来`,
@@ -208,7 +210,7 @@ const handle_sms_verify = (e: string[]) => {
toast.add({
title: '登录失败',
description: err.msg || '网络错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
})
@@ -251,7 +253,7 @@ const obtainForgetSmsCode = () => {
toast.add({
title: '验证码发送失败',
description: res.msg || '未知错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
return
@@ -261,7 +263,7 @@ const obtainForgetSmsCode = () => {
sms_counting_down.value = 60 // TODO: save timestamp to localstorage
toast.add({
title: '短信验证码已发送',
color: 'indigo',
color: 'primary',
icon: 'i-tabler-circle-check',
})
const interval = setInterval(() => {
@@ -275,7 +277,7 @@ const obtainForgetSmsCode = () => {
toast.add({
title: '验证码发送失败',
description: err.msg || '网络错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
})
@@ -299,7 +301,7 @@ const onForgetPasswordSubmit = (
toast.add({
title: '重置密码失败',
description: res.msg || '未知错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
return
@@ -307,7 +309,7 @@ const onForgetPasswordSubmit = (
toast.add({
title: '重置密码成功',
description: '请您继续登录',
color: 'green',
color: 'success',
icon: 'i-tabler-circle-check',
})
currentTab.value = 1
@@ -316,7 +318,7 @@ const onForgetPasswordSubmit = (
toast.add({
title: '重置密码失败',
description: err.msg || '未知错误',
color: 'red',
color: 'error',
icon: 'i-tabler-circle-x',
})
})
@@ -324,173 +326,101 @@ const onForgetPasswordSubmit = (
</script>
<template>
<UModal prevent-close>
<UCard>
<template #header>
<div class="flex items-center justify-between">
<h3
class="text-base font-semibold leading-6 text-gray-900 dark:text-white"
>
登录眩生花 AI 助手
</h3>
<UButton
color="gray"
variant="ghost"
icon="i-heroicons-x-mark-20-solid"
class="-my-1"
@click="modal.close()"
/>
</div>
</template>
<UTabs
:items="items"
class="w-full"
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"
<UModal :dismissible="false">
<template #content>
<UCard>
<template #header>
<div class="flex items-center justify-between">
<h3
class="text-base font-semibold leading-6 text-gray-900 dark:text-white"
>
登录眩生花 AI 助手
</h3>
<UButton
color="neutral"
variant="ghost"
icon="i-heroicons-x-mark-20-solid"
class="-my-1"
@click="$emit('close')"
/>
</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>
<UTabs
:items="items"
class="w-full"
v-model="currentTab"
>
<!-- <template #default="{ item, index, selected }">
<div class="relative flex items-center gap-2 truncate">
<span class="truncate">{{ item.label }}</span>
<span
v-if="selected"
class="bg-primary-500 dark:bg-primary-400 absolute -right-4 h-2 w-2 rounded-full"
/>
</div>
</template> -->
<template #content="{ 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-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">
<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"
:autofocus="index === 0"
/>
</PinInputRoot>
</div>
</Transition>
</div>
<div
v-if="item.key === 'recovery'"
class="space-y-3"
>
<UForm
<div
v-if="item.key === 'account'"
class="space-y-3"
:schema="forgetPasswordSchema"
:state="forgetPasswordState"
@submit="onForgetPasswordSubmit"
>
<UFormGroup
<UFormField
label="用户名"
name="username"
required
>
<UInput
v-model="accountForm.username"
:disabled="final_loading"
required
/>
</UFormField>
<UFormField
label="密码"
name="password"
required
>
<UInput
v-model="accountForm.password"
:disabled="final_loading"
type="password"
required
/>
</UFormField>
</div>
<div
v-else-if="item.key === 'sms'"
class="space-y-3"
>
<UFormField
label="手机号"
name="mobile"
required
>
<UButtonGroup class="w-full">
<UInput
v-model="forgetPasswordState.mobile"
v-model="smsForm.mobile"
:disabled="final_loading"
type="tel"
type="sms"
class="w-full"
required
>
<template #leading>
<span class="text-gray-500 dark:text-gray-400 text-xs">
<span class="text-xs text-gray-500 dark:text-gray-400">
+86
</span>
</template>
@@ -501,78 +431,156 @@ const onForgetPasswordSubmit = (
? `${sms_counting_down}秒后重发`
: '获取验证码'
"
@click="obtainForgetSmsCode"
@click="obtainSmsCode"
:loading="sms_sending"
:disabled="!!sms_counting_down"
:disabled="!!sms_counting_down || final_loading"
class="text-xs font-bold"
color="gray"
color="neutral"
/>
</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>
</UFormField>
<Transition name="pin-root">
<div v-if="sms_triggered">
<Label
for="pin-input"
class="pin-label"
>
验证码
</Label>
<PinInputRoot
id="sms-input"
v-model="smsForm.sms_code"
:disabled="sms_sending || final_loading"
placeholder="○"
class="mt-1 flex w-full items-center justify-between gap-2 md:justify-start"
@complete="handle_sms_verify"
type="number"
otp
required
>
<PinInputInput
v-for="(id, index) in 4"
:key="id"
:index="index"
class="pin-input"
:autofocus="index === 0"
/>
</PinInputRoot>
</div>
</Transition>
</div>
<div>
<div
v-if="item.key === 'recovery'"
class="space-y-3"
>
<UForm
class="space-y-3"
:schema="forgetPasswordSchema"
:state="forgetPasswordState"
@submit="onForgetPasswordSubmit"
>
<UFormField
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-xs text-gray-500 dark:text-gray-400"
>
+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="neutral"
/>
</UButtonGroup>
</UFormField>
<UFormField
label="验证码"
name="sms_code"
required
>
<UInput
v-model="forgetPasswordState.sms_code"
type="sms"
class="w-full"
:disabled="final_loading"
/>
</UFormField>
<UFormField
label="新密码"
name="password"
required
>
<UInput
v-model="forgetPasswordState.password"
type="password"
:disabled="final_loading"
/>
</UFormField>
<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="primary"
:loading="final_loading"
>
重置密码
登录
</UButton>
<UButton
variant="link"
color="neutral"
@click="currentTab = 2"
>
忘记密码
</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>
</UCard>
</template>
</UCard>
</template>
</UTabs>
</UCard>
</template>
</UModal>
</template>
<style scoped>
@reference '@/assets/css/main.css';
.pin-root-enter-active,
.pin-root-leave-active {
@apply transition duration-500;
@@ -580,15 +588,15 @@ const onForgetPasswordSubmit = (
.pin-root-enter-from,
.pin-root-leave-to {
@apply opacity-0 -translate-y-2;
@apply -translate-y-2 opacity-0;
}
.pin-input {
@apply w-full md:w-16 aspect-square rounded text-center shadow caret-transparent;
@apply outline-0 ring-indigo-500 focus:ring-3 font-bold;
@apply aspect-square w-full rounded text-center caret-transparent shadow md:w-16;
@apply focus:ring-3 font-bold outline-0 ring-indigo-500;
}
.pin-label {
@apply block text-sm font-medium text-gray-700 dark:text-gray-200 after:content-['*'] after:ms-0.5 after:text-red-500 dark:after:text-red-400;
@apply block text-sm font-medium text-gray-700 after:ms-0.5 after:text-red-500 after:content-['*'] dark:text-gray-200 dark:after:text-red-400;
}
</style>