feat(login): 优化登录表单组件样式和用户体验

- 重新设计表单布局,添加欢迎语和用户选择区域
- 优化输入框样式,采用更现代化的圆角和边框设计
- 实现动态主题色系统,所有交互元素跟随系统主题色
- 改进按钮样式,使用渐变背景和悬停效果
- 优化验证码输入区域的视觉效果
- 完善暗色主题适配和响应式设计
This commit is contained in:
Leo 2025-07-06 22:48:53 +08:00
parent 2e69b2e9a0
commit c292ed9454

View File

@ -1,12 +1,18 @@
<script setup lang="ts">
import type { FormInst } from 'naive-ui'
import { useAuthStore } from '@/store'
import { useAppStore, useAuthStore } from '@/store'
import { fetchCaptchaPng } from '@/service/api/auth'
import { local } from '@/utils'
const emit = defineEmits(['update:modelValue'])
const authStore = useAuthStore()
const appStore = useAppStore()
//
const primaryColor = computed(() => appStore.primaryColor)
const primaryColorHover = computed(() => appStore.theme.common.primaryColorHover)
const primaryColorPressed = computed(() => appStore.theme.common.primaryColorPressed)
function toOtherForm(type: any) {
emit('update:modelValue', type)
@ -92,16 +98,39 @@ function checkUserAccount() {
</script>
<template>
<div>
<n-h2 depth="3" class="text-center">
{{ $t('login.signInTitle') }}
</n-h2>
<n-form ref="formRef" :rules="rules" :model="formValue" :show-label="false" size="large">
<div class="space-y-6">
<!-- 头部问候语 -->
<div class="text-center">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">
欢迎回来 👋
</h1>
<p class="text-sm text-gray-600 dark:text-gray-400">
请输入您的详细信息以开始管理您的帐户
</p>
</div>
<!-- 登录表单 -->
<n-form ref="formRef" :rules="rules" :model="formValue" :show-label="false" size="large" class="space-y-4">
<!-- 账号输入 -->
<n-form-item path="account">
<n-input v-model:value="formValue.account" clearable :placeholder="$t('login.accountPlaceholder')" />
<n-input
v-model:value="formValue.account"
clearable
:placeholder="$t('login.accountPlaceholder')"
class="login-input"
/>
</n-form-item>
<!-- 密码输入 -->
<n-form-item path="pwd">
<n-input v-model:value="formValue.pwd" type="password" :placeholder="$t('login.passwordPlaceholder')" clearable show-password-on="click">
<n-input
v-model:value="formValue.pwd"
type="password"
:placeholder="$t('login.passwordPlaceholder')"
clearable
show-password-on="click"
class="login-input"
>
<template #password-invisible-icon>
<icon-park-outline-preview-close-one />
</template>
@ -110,17 +139,19 @@ function checkUserAccount() {
</template>
</n-input>
</n-form-item>
<!-- 验证码输入 -->
<n-form-item path="securityCode">
<div class="flex w-full gap-3">
<n-input
v-model:value="formValue.securityCode"
placeholder="请输入验证码"
clearable
class="flex-1"
class="flex-1 login-input"
maxlength="5"
/>
<div
class="w-32 h-12 cursor-pointer rounded border-2 border-gray-300 hover:border-blue-400 flex items-center justify-center overflow-hidden bg-white transition-colors duration-200"
class="w-32 h-12 cursor-pointer rounded-lg border-2 border-gray-200 flex items-center justify-center overflow-hidden bg-white dark:bg-gray-700 dark:border-gray-600 transition-colors duration-200 captcha-button"
title="点击刷新验证码"
@click="getCaptcha"
>
@ -130,51 +161,111 @@ function checkUserAccount() {
alt="验证码"
class="w-full h-full object-contain"
>
<span v-else class="text-sm text-gray-500">点击获取验证码</span>
<span v-else class="text-sm text-gray-500 dark:text-gray-400">点击获取验证码</span>
</div>
</div>
</n-form-item>
<n-space vertical :size="20">
<div class="flex-y-center justify-between">
<n-checkbox v-model:checked="isRemember">
{{ $t('login.rememberMe') }}
</n-checkbox>
<n-button type="primary" text @click="toOtherForm('resetPwd')">
{{ $t('login.forgotPassword') }}
</n-button>
</div>
<n-button block type="primary" size="large" :loading="isLoading" :disabled="isLoading" @click="handleLogin">
{{ $t('login.signIn') }}
<!-- 记住密码和忘记密码 -->
<div class="flex items-center justify-between">
<n-checkbox v-model:checked="isRemember" class="text-sm">
{{ $t('login.rememberMe') }}
</n-checkbox>
<n-button type="primary" text class="text-sm" @click="toOtherForm('resetPwd')">
{{ $t('login.forgotPassword') }}
</n-button>
<n-flex>
<n-text>{{ $t('login.noAccountText') }}</n-text>
<n-button type="primary" text @click="toOtherForm('register')">
{{ $t('login.signUp') }}
</n-button>
</n-flex>
</n-space>
</div>
<!-- 登录按钮 -->
<n-button
block
type="primary"
size="large"
:loading="isLoading"
:disabled="isLoading"
class="login-button"
@click="handleLogin"
>
{{ $t('login.signIn') }}
</n-button>
<!-- 注册链接 -->
<!-- <div class="text-center"> -->
<!-- <span class="text-sm text-gray-600 dark:text-gray-400">{{ $t('login.noAccountText') }}</span> -->
<!-- <n-button type="primary" text @click="toOtherForm('register')" class="text-sm ml-1"> -->
<!-- {{ $t('login.signUp') }} -->
<!-- </n-button> -->
<!-- </div> -->
</n-form>
<n-divider>
<span op-80>{{ $t('login.or') }}</span>
</n-divider>
<n-space justify="center">
<n-button circle>
<template #icon>
<n-icon><icon-park-outline-wechat /></n-icon>
</template>
</n-button>
<n-button circle>
<template #icon>
<n-icon><icon-park-outline-tencent-qq /></n-icon>
</template>
</n-button>
<n-button circle>
<template #icon>
<n-icon><icon-park-outline-github-one /></n-icon>
</template>
</n-button>
</n-space>
</div>
</template>
<style scoped></style>
<style scoped>
.login-input {
--n-border-radius: 8px;
--n-border: 1px solid #e5e7eb;
--n-border-hover: 1px solid v-bind(primaryColor);
--n-border-focus: 1px solid v-bind(primaryColor);
--n-box-shadow-focus: 0 0 0 3px v-bind(`${primaryColor}19`);
--n-padding-left: 16px;
--n-padding-right: 16px;
--n-font-size: 14px;
--n-height: 48px;
}
.login-button {
--n-border-radius: 8px;
--n-height: 48px;
--n-font-size: 16px;
--n-font-weight: 600;
background: linear-gradient(135deg, v-bind(primaryColor) 0%, v-bind(primaryColorPressed) 100%);
border: none;
transition: all 0.2s ease;
}
.login-button:hover {
background: linear-gradient(135deg, v-bind(primaryColorHover) 0%, v-bind(primaryColorPressed) 100%);
transform: translateY(-1px);
box-shadow: 0 4px 12px v-bind(`${primaryColor}4D`);
}
.social-login-btn {
@apply w-12 h-12 rounded-full border-2 border-gray-200 dark:border-gray-600 flex items-center justify-center transition-all duration-200 text-gray-600 dark:text-gray-400;
&:hover {
border-color: v-bind(primaryColor);
background: v-bind(`${primaryColor}0D`);
color: v-bind(primaryColor);
}
}
.social-login-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.captcha-button:hover {
border-color: v-bind(primaryColor);
}
/* 暗色主题适配 */
.dark .login-input {
--n-color: #374151;
--n-border: 1px solid #4b5563;
--n-border-hover: 1px solid v-bind(primaryColor);
--n-color-focus: #374151;
--n-text-color: #f3f4f6;
}
/* 响应式设计 */
@media (max-width: 640px) {
.login-input {
--n-height: 44px;
}
.login-button {
--n-height: 44px;
--n-font-size: 14px;
}
}
</style>