| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 |
- <template>
- <div class="auth-view">
- <div class="auth-container">
- <div class="auth-decoration">
- <div class="decoration-circle circle-1"></div>
- <div class="decoration-circle circle-2"></div>
- <div class="decoration-circle circle-3"></div>
- </div>
- <el-card class="auth-card" shadow="never">
- <el-button class="back-btn" text @click="router.push('/')">
- <el-icon><ArrowLeft /></el-icon>
- 返回主页
- </el-button>
- <div class="auth-header">
- <h1 class="auth-title">欢迎</h1>
- <p class="auth-subtitle">请登录或注册您的账号</p>
- </div>
- <el-tabs v-model="activeTab" class="auth-tabs" stretch>
- <el-tab-pane label="登录" name="login">
- <el-form
- ref="loginFormRef"
- :model="loginForm"
- :rules="loginRules"
- class="auth-form"
- @submit.prevent="handleLogin"
- >
- <el-form-item prop="account">
- <el-input
- v-model="loginForm.account"
- placeholder="请输入账号"
- :prefix-icon="User"
- size="large"
- />
- </el-form-item>
- <el-form-item prop="password">
- <el-input
- v-model="loginForm.password"
- type="password"
- placeholder="请输入密码"
- :prefix-icon="Lock"
- show-password
- size="large"
- />
- </el-form-item>
- <el-form-item>
- <el-button
- type="primary"
- :loading="loginLoading"
- size="large"
- class="auth-submit"
- @click="handleLogin"
- >
- 登录
- </el-button>
- </el-form-item>
- </el-form>
- </el-tab-pane>
- <el-tab-pane label="注册" name="register">
- <el-form
- ref="registerFormRef"
- :model="registerForm"
- :rules="registerRules"
- class="auth-form"
- @submit.prevent="handleRegister"
- >
- <el-form-item prop="account">
- <el-input
- v-model="registerForm.account"
- placeholder="请输入账号"
- :prefix-icon="User"
- size="large"
- />
- </el-form-item>
- <el-form-item prop="username">
- <el-input
- v-model="registerForm.username"
- placeholder="请输入用户名"
- :prefix-icon="User"
- size="large"
- />
- </el-form-item>
- <el-form-item prop="password">
- <el-input
- v-model="registerForm.password"
- type="password"
- placeholder="请输入密码"
- :prefix-icon="Lock"
- show-password
- size="large"
- />
- </el-form-item>
- <el-form-item prop="confirmPassword">
- <el-input
- v-model="registerForm.confirmPassword"
- type="password"
- placeholder="请再次输入密码"
- :prefix-icon="Lock"
- show-password
- size="large"
- />
- </el-form-item>
- <el-form-item>
- <el-button
- type="primary"
- :loading="registerLoading"
- size="large"
- class="auth-submit"
- @click="handleRegister"
- >
- 注册
- </el-button>
- </el-form-item>
- </el-form>
- </el-tab-pane>
- </el-tabs>
- </el-card>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { reactive, ref } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import { ElMessage } from 'element-plus'
- import { User, Lock, ArrowLeft } from '@element-plus/icons-vue'
- import type { FormInstance, FormRules } from 'element-plus'
- import { getAuthController } from '../api/auth-controller'
- import type { LoginDto, RegisterDto } from '../api/models'
- import { useLoginUserStore } from '../store'
- import type { User as UserType } from '../type'
- const route = useRoute()
- const router = useRouter()
- const loginUserStore = useLoginUserStore()
- const activeTab = ref('login')
- const loginFormRef = ref<FormInstance>()
- const registerFormRef = ref<FormInstance>()
- const loginLoading = ref(false)
- const registerLoading = ref(false)
- const loginForm = reactive({
- account: '',
- password: ''
- })
- const registerForm = reactive({
- account: '',
- username: '',
- password: '',
- confirmPassword: ''
- })
- const validateConfirmPassword = (_rule: any, value: string, callback: any) => {
- if (value !== registerForm.password) {
- callback(new Error('两次输入密码不一致'))
- } else {
- callback()
- }
- }
- const loginRules = reactive<FormRules>({
- account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
- password: [
- { required: true, message: '请输入密码', trigger: 'blur' },
- { min: 4, message: '密码长度至少4位', trigger: 'blur' }
- ]
- })
- const registerRules = reactive<FormRules>({
- account: [
- { required: true, message: '请输入账号', trigger: 'blur' },
- { min: 4, max: 20, message: '账号长度4-20个字符', trigger: 'blur' }
- ],
- username: [
- { required: true, message: '请输入用户名', trigger: 'blur' },
- { min: 2, max: 32, message: '用户名长度2-32个字符', trigger: 'blur' }
- ],
- password: [
- { required: true, message: '请输入密码', trigger: 'blur' },
- { min: 4, max: 20, message: '密码长度4-20个字符', trigger: 'blur' }
- ],
- confirmPassword: [
- { required: true, message: '请再次输入密码', trigger: 'blur' },
- { validator: validateConfirmPassword, trigger: 'blur' }
- ]
- })
- const handleLogin = async () => {
- const valid = await loginFormRef.value?.validate().catch(() => false)
- if (!valid) return
- loginLoading.value = true
- try {
- const authApi = getAuthController()
- const res = await authApi.login({
- account: loginForm.account,
- password: loginForm.password
- } as LoginDto)
- const tokenData = res.data
- if (res.code === 200 && tokenData?.token) {
- localStorage.setItem('token', tokenData.token)
- loginUserStore.loginSuccess({
- id: String(tokenData.userId || ''),
- name: tokenData.username || tokenData.account || '用户',
- role: (tokenData.role as UserType['role']) || 'user'
- })
- ElMessage.success('登录成功')
- router.push((route.query.redirect as string) || '/')
- } else {
- ElMessage.error(res.message || '登录失败')
- }
- } catch (err: any) {
- ElMessage.error(err.message || '登录失败,请检查网络')
- } finally {
- loginLoading.value = false
- }
- }
- const handleRegister = async () => {
- const valid = await registerFormRef.value?.validate().catch(() => false)
- if (!valid) return
- registerLoading.value = true
- try {
- const authApi = getAuthController()
- const res = await authApi.register({
- account: registerForm.account,
- password: registerForm.password,
- username: registerForm.username
- } as RegisterDto)
- if (res.code === 200) {
- ElMessage.success('注册成功,请登录')
- activeTab.value = 'login'
- registerForm.account = ''
- registerForm.username = ''
- registerForm.password = ''
- registerForm.confirmPassword = ''
- } else {
- ElMessage.error(res.message || '注册失败')
- }
- } catch (err: any) {
- ElMessage.error(err.message || '注册失败,请检查网络')
- } finally {
- registerLoading.value = false
- }
- }
- </script>
- <style scoped>
- .auth-view {
- display: flex;
- justify-content: center;
- align-items: center;
- min-height: 100vh;
- background: linear-gradient(135deg, #fdf2f8 0%, #fce7f3 50%, #fbcfe8 100%);
- position: relative;
- overflow: hidden;
- }
- .auth-decoration {
- position: absolute;
- inset: 0;
- pointer-events: none;
- }
- .decoration-circle {
- position: absolute;
- border-radius: 50%;
- opacity: 0.15;
- }
- .circle-1 {
- width: 400px;
- height: 400px;
- background: #db2777;
- top: -100px;
- right: -100px;
- animation: float 20s ease-in-out infinite;
- }
- .circle-2 {
- width: 300px;
- height: 300px;
- background: #f472b6;
- bottom: -80px;
- left: -80px;
- animation: float 15s ease-in-out infinite reverse;
- }
- .circle-3 {
- width: 200px;
- height: 200px;
- background: #ca8a04;
- top: 50%;
- left: 60%;
- animation: float 18s ease-in-out infinite;
- }
- @keyframes float {
- 0%,
- 100% {
- transform: translate(0, 0) scale(1);
- }
- 25% {
- transform: translate(20px, -30px) scale(1.05);
- }
- 50% {
- transform: translate(-10px, 20px) scale(0.95);
- }
- 75% {
- transform: translate(15px, 10px) scale(1.02);
- }
- }
- .auth-container {
- position: relative;
- z-index: 1;
- width: 100%;
- max-width: 420px;
- padding: 20px;
- }
- .auth-card {
- border-radius: 16px;
- border: 1px solid rgba(219, 39, 119, 0.1);
- background: rgba(255, 255, 255, 0.9);
- backdrop-filter: blur(10px);
- padding: 8px;
- }
- .back-btn {
- position: absolute;
- top: 16px;
- left: 16px;
- color: #be185d;
- font-size: 14px;
- cursor: pointer;
- transition: color 0.2s;
- }
- .back-btn:hover {
- color: #db2777;
- }
- .back-btn .el-icon {
- margin-right: 4px;
- }
- .auth-header {
- text-align: center;
- padding: 24px 0 16px;
- }
- .auth-title {
- margin: 0 0 8px;
- font-size: 28px;
- font-weight: 600;
- color: #831843;
- letter-spacing: 2px;
- }
- .auth-subtitle {
- margin: 0;
- font-size: 14px;
- color: #be185d;
- }
- .auth-tabs {
- padding: 0 8px 8px;
- }
- :deep(.auth-tabs .el-tabs__header) {
- margin-bottom: 24px;
- }
- :deep(.auth-tabs .el-tabs__item) {
- font-size: 16px;
- font-weight: 500;
- color: #831843;
- transition: color 0.2s;
- }
- :deep(.auth-tabs .el-tabs__item.is-active) {
- color: #db2777;
- }
- :deep(.auth-tabs .el-tabs__active-bar) {
- background-color: #db2777;
- height: 3px;
- border-radius: 3px;
- }
- :deep(.auth-tabs .el-tabs__nav-wrap::after) {
- background-color: rgba(219, 39, 119, 0.15);
- height: 1px;
- }
- .auth-form {
- padding: 0 8px;
- }
- :deep(.el-input__wrapper) {
- border-radius: 10px;
- padding: 8px 12px;
- box-shadow: 0 0 0 1px rgba(219, 39, 119, 0.15) inset;
- transition: box-shadow 0.2s;
- }
- :deep(.el-input__wrapper:hover) {
- box-shadow: 0 0 0 1px rgba(219, 39, 119, 0.3) inset;
- }
- :deep(.el-input__wrapper.is-focus) {
- box-shadow: 0 0 0 1px #db2777 inset;
- }
- :deep(.el-input__prefix) {
- color: #db2777;
- margin-right: 8px;
- }
- .auth-submit {
- width: 100%;
- border-radius: 10px;
- font-size: 16px;
- font-weight: 500;
- letter-spacing: 4px;
- background: linear-gradient(135deg, #db2777 0%, #be185d 100%);
- border: none;
- transition: all 0.2s;
- }
- .auth-submit:hover {
- background: linear-gradient(135deg, #be185d 0%, #9d174d 100%);
- transform: translateY(-1px);
- box-shadow: 0 4px 12px rgba(219, 39, 119, 0.3);
- }
- .auth-submit:active {
- transform: translateY(0);
- }
- :deep(.el-form-item) {
- margin-bottom: 20px;
- }
- :deep(.el-form-item__error) {
- padding-left: 4px;
- }
- @media (max-width: 480px) {
- .auth-container {
- padding: 16px;
- }
- .auth-title {
- font-size: 24px;
- }
- .circle-1 {
- width: 250px;
- height: 250px;
- }
- .circle-2 {
- width: 200px;
- height: 200px;
- }
- .circle-3 {
- width: 150px;
- height: 150px;
- }
- }
- </style>
|