编辑
2025-01-05
技术
00
请注意,本文编写于 344 天前,最后修改于 344 天前,其中某些信息可能已经过时。

目录

基于Vue3 + TypeScript + ElementPlus实现的登录注册页面模板
代码解读
程序源码

基于Vue3 + TypeScript + ElementPlus实现的登录注册页面模板

很早之前就想把登录注册页做在一起,开源的一些又比较简陋或者技术栈不太一样,b站上一些很精美的又是原生三件套做的,最近刚好有个项目要做,就先做了一个练练手

代码解读

其实页面很简单,需要注意的就几点

  1. 在页面切换时需要使用await nextTick等待Dom元素完全更新后切换,不然会报错
  2. 如果要取消滚动条,请修改App.vue中的全局样式
html
<style> html, body { height: 100%; margin: 0; padding: 0; overflow: hidden; /* 添加这行来防止滚动 */ } #app { height: 100%; /* 确保 app 容器也是全高的 */ } </style>
  1. 模板并没有详细写业务逻辑部分,业务逻辑详细内容请自行填充

程序源码

html
<template> <div class="login-page"> <div class="content-box"> <div class="form-side"> <div class="header"> <h2 class="title">{{ model ? '欢迎回来' : '创建账号' }}</h2> <p class="subtitle">{{ model ? '登录您的账号' : '开始您的旅程' }}</p> </div> <div class="form-container"> <el-form v-if="model" :model="loginForm" :rules="loginRules" ref="loginFormRef" class="login-form"> <el-form-item prop="username"> <el-input v-model="loginForm.username" placeholder="用户名/学号/邮箱" prefix-icon="User" /> </el-form-item> <el-form-item prop="password"> <el-input v-model="loginForm.password" type="password" placeholder="密码" prefix-icon="Lock" /> </el-form-item> <el-form-item> <el-checkbox v-model="loginForm.rememberMe">记住我</el-checkbox> </el-form-item> <el-form-item> <el-button type="primary" :loading="loading" @click="login" class="submit-btn"> {{ loading ? '登录中...' : '登录' }} </el-button> </el-form-item> </el-form> <el-form v-else :model="registerForm" :rules="registerRules" ref="registerFormRef" class="register-form"> <el-form-item prop="studentId"> <el-input v-model="registerForm.studentId" placeholder="学号" prefix-icon="Ticket" /> </el-form-item> <el-form-item prop="username"> <el-input v-model="registerForm.username" placeholder="用户名" prefix-icon="User" /> </el-form-item> <el-form-item prop="email"> <el-input v-model="registerForm.email" placeholder="邮箱" prefix-icon="Message" /> </el-form-item> <el-form-item prop="password"> <el-input v-model="registerForm.password" type="password" placeholder="密码" prefix-icon="Lock" /> </el-form-item> <el-form-item prop="confirmPassword"> <el-input v-model="registerForm.confirmPassword" type="password" placeholder="确认密码" prefix-icon="Lock" /> </el-form-item> <el-form-item> <el-button type="primary" :loading="loading" @click="register" class="submit-btn"> {{ loading ? '注册中...' : '注册' }} </el-button> </el-form-item> </el-form> <div class="switch-area"> <div class="switch-button" :class="{ 'right-aligned': model }"> <span class="switch-text">{{ model ? '初来乍到?' : '已有账号?' }}</span> <el-button link type="primary" @click="handleSwitch" class="switch-link"> {{ model ? '注册账号' : '返回登录' }} </el-button> </div> </div> </div> </div> <div class="image-side"> <div class="overlay"></div> <img src="@/assets/login-background.jpg" alt="Login Background" /> </div> </div> </div> </template> <script setup lang="ts"> import { ref, nextTick, onMounted, onUnmounted } from 'vue' import type { FormInstance } from 'element-plus' const model = ref(true) const loginFormRef = ref<FormInstance>() const registerFormRef = ref<FormInstance>() const handleSwitch = async () => { // 先重置当前表单 if (model.value) { loginFormRef.value?.resetFields() } else { registerFormRef.value?.resetFields() } // 切换模式 model.value = !model.value // 等待 DOM 更新 await nextTick() } const loginForm = ref({ username: '', password: '', rememberMe: false }) const loading = ref(false) // 增强密码验证规则 // eslint-disable-next-line @typescript-eslint/no-explicit-any const passwordValidator = (rule: any, value: string, callback: any) => { if (value === '') { callback(new Error('请输入密码')) } else if (value.length < 8) { callback(new Error('密码长度不能小于8位')) } else if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) { callback(new Error('密码必须包含大小写字母和数字')) } else { callback() } } // 确认密码验证 // eslint-disable-next-line @typescript-eslint/no-explicit-any const confirmPasswordValidator = (rule: any, value: string, callback: any) => { if (value === '') { callback(new Error('请再次输入密码')) } else if (value !== registerForm.value.password) { callback(new Error('两次输入密码不一致')) } else { callback() } } const loginRules = ref({ username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' }, { validator: passwordValidator, trigger: 'blur' } ] }) const registerForm = ref({ username: '', studentId: '', email: '', password: '', confirmPassword: '' }) const registerRules = ref({ username: [{ required: true, message: '请输入用户名', trigger: 'blur' }], studentId: [ { required: true, message: '请输入学号', trigger: 'blur' }, { pattern: /^\d+$/, message: '学号必须为数字', trigger: 'blur' } ], email: [ { required: true, message: '请输入邮箱', trigger: 'blur' }, { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' }, { validator: passwordValidator, trigger: 'blur' } ], confirmPassword: [ { required: true, message: '请确认密码', trigger: 'blur' }, { validator: confirmPasswordValidator, trigger: 'blur' } ] }) // 在组件挂载时检查是否有保存的登录信息 onMounted(() => { const savedCredentials = localStorage.getItem('userCredentials') if (savedCredentials) { const { username, rememberMe } = JSON.parse(savedCredentials) loginForm.value.username = username loginForm.value.rememberMe = rememberMe } }) onUnmounted(() => { localStorage.removeItem('userCredentials') }) const login = async () => { if (!loginFormRef.value) return try { loading.value = true const valid = await loginFormRef.value.validate() if (valid) { // 处理登录逻辑... if (loginForm.value.rememberMe) { localStorage.setItem('userCredentials', JSON.stringify({ username: loginForm.value.username, rememberMe: true })) } else { localStorage.removeItem('userCredentials') } } } catch (error) { console.error('登录验证失败:', error) } finally { loading.value = false } } const register = async () => { if (!registerFormRef.value) return try { loading.value = true const valid = await registerFormRef.value.validate() if (valid) { // 处理注册逻辑... } } catch (error) { console.error('注册验证失败:', error) } finally { loading.value = false } } </script> <style scoped lang="scss"> @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600&family=Open+Sans&display=swap'); .login-page { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: linear-gradient(135deg, #f5f7fa 0%, #5a1167 100%); padding: 20px; .content-box { display: flex; background: white; width: 800px; border-radius: 16px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; background-color: rgba(255, 255, 255, 0.8); backdrop-filter: blur(40px); -webkit-backdrop-filter: blur(10px); overflow: hidden; &:hover { box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2); } .form-side { flex: 1; padding: 40px; min-width: 300px; } .image-side { position: relative; width: 400px; overflow: hidden; img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.3s ease; } .overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(135deg, rgba(149, 3, 127, 0.4) 0%, rgba(0, 0, 0, 0.4) 100%); z-index: 1; } &:hover img { transform: scale(1.05); } } } .header { text-align: center; margin-bottom: 40px; .title { font-size: 28px; color: #2c3e50; margin: 0 0 8px; font-weight: 600; font-family: "Cormorant Garamond", "Times New Roman", Georgia, serif; letter-spacing: 0.5px; } .subtitle { font-size: 16px; color: #7f8c8d; margin: 0; font-family: "Poppins", "Helvetica Neue", Arial, sans-serif; letter-spacing: 0.3px; } } .form-container { .el-form-item { margin-bottom: 16px; } :deep(.el-input) { .el-input__wrapper { padding: 6px; border-radius: 0; box-shadow: none !important; border-bottom: 1px solid #dcdfe6; background-color: transparent; transition: all 0.3s ease; &.is-focus { border-bottom-color: var(--el-color-primary); } } } .submit-btn { width: 100%; padding: 12px; font-size: 16px; border-radius: 8px; margin-top: 12px; transition: all 0.3s ease; &:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } } :deep(.el-checkbox) { margin-left: 0; margin-bottom: 8px; .el-checkbox__label { color: #7f8c8d; } } } .switch-area { margin-top: 32px; padding-top: 20px; border-top: 1px solid #eee; .switch-button { display: flex; align-items: center; gap: 8px; transition: all 0.3s ease; &.right-aligned { justify-content: flex-end; } .switch-text { color: #7f8c8d; font-size: 14px; } .switch-link { font-size: 14px; font-weight: 500; &:hover { text-decoration: underline; } } } } } // 响应式设计 @media (max-width: 900px) { .login-page { .content-box { width: 100%; max-width: 500px; margin: 20px; .image-side { display: none; } } } } @media (max-width: 480px) { .login-page { .content-box { .form-side { padding: 24px; } } } } </style>
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:MapleCity

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!