先附上源码地址:
觉得不错的话顺手一个star
效果展示
最新vite
搭建项目
npm create vite@latest mingsl-login -- --template vue-ts
配置tsconfig
tsconfig.node.json
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
tsconfig.json
{
"compilerOptions": {
"target": "ESNext", // 将代码编译为最新版本的 JS
"useDefineForClassFields": true, // 是 TypeScript 3.7.0 中新增的一个编译选项
"module": "ESNext", // 使用 ES Module 格式打包编译后的文件
"moduleResolution": "Node", // 使用 Node 的模块解析策略
"strict": true, // 启用所用严格的类型检查
"jsx": "preserve", // 保留原始的 JSX 代码,不进行编译
"resolveJsonModule": true, // 允许引入 JSON 文件
"isolatedModules": true, // 该属性要求所有文件都是 ES Module 模块。
"esModuleInterop": true, // 允许使用 import 引入使用 export = 导出的内容
"lib": ["ESNext", "DOM"], // 引入 ES 最新特性和 DOM 接口的类型定义
"skipLibCheck": true, // 跳过对 .d.ts 文件的类型检查
"noEmit": true // 不输出文件,即编译后不会生成任何js文件
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
配置文件系统路径别名
安装 @types/node
npm install @types/node
修改vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve:{
alias:{
'@':resolve(__dirname,'src')
}
}
})
修改tsconfig.json
{
"compilerOptions": {
//...
"baseUrl": ".", //查询的基础路径
"paths": { "@/*": ["src/*"]} //路径映射,配合别名使用
}
//...
}
安装 element plus
npm install element-plus --save
配置Volar
插件支持
// tsconfig.json
{
"compilerOptions": {
// ...
"types": ["element-plus/global"]
}
}
自动导入
首先你需要安装unplugin-vue-components
和 unplugin-auto-import
这两款插件`
npm install -D unplugin-vue-components unplugin-auto-import
然后把下列代码插入到你的 Vite
配置文件中`
// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
// ...
plugins: [
// ...
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
全局引入css
样式
//main.ts
import 'element-plus/dist/index.css'
安装 icon
图库
安装
npm install @element-plus/icons-vue
全局注册
// main.ts
// 如果您正在使用CDN引入,请删除下面一行。
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.mount('#app')
定义 LoginReq 接口
// @/interface/user.ts
/**
* 登录表单提交请求数据类型
*/
export interface LoginReq{
username:string;
password:string;
}
自定义loginForm 组件
<!-- @/compaments/loginForm.vue -->
<template>
<ElForm class="login-form" ref="loginRef" :model="loginParam" :rules="loginRules">
<h1 class="login-title">登录</h1>
<ElFormItem prop="username">
<ElInput placeholder="请输入账号" :prefix-icon="User" v-model="loginParam.username" size="large"></ElInput>
</ElFormItem>
<ElFormItem prop="password">
<ElInput placeholder="请输入密码" show-password :prefix-icon="Lock" v-model="loginParam.password" size="large"></ElInput>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" class="login-btn" size="large" @click="submit(loginRef)">登录</ElButton>
</ElFormItem>
</ElForm>
</template>
<script setup lang="ts">
import { User ,Lock} from '@element-plus/icons-vue';
import { LoginReq } from '@/interface/user';
import {reactive,ref} from 'vue'
import { ElMessage, FormInstance,FormRules } from 'element-plus';
const loginParam:LoginReq=reactive({
username:"",
password:""
})
const loginRef=ref<FormInstance>()
const loginRules:FormRules=reactive({
username:[{required:true,message:"账号不能为空",trigger:'blur'}],
password:[{required:true,message:"密码不能为空",trigger:'blur'}]
})
const submit=(formEl: FormInstance | undefined)=>{
if(!formEl){
return false
}
formEl.validate(async(validate:boolean)=>{
if(validate){
console.log("开始做登录的逻辑");
}else{
return false;
}
})
}
</script>
<style scoped>
.login-form{
grid-column: 1;
grid-row: 1;
opacity: 1;
transition: 1s ease-in-out;
transition-delay: 0.5s;
/* 上下 | 左右 */
padding: 1% 25%;
z-index: 1;
}
.login-form.sign-up-model{
opacity: 0;
transition: 1s ease-in-out;
z-index: 0;
}
.login-title{
text-align: center;
color:#444;
}
.login-btn{
width: 100%;
font-size: 18px;
}
</style>
定义 RegisterReq 接口
// @/interface/user.ts
/**
* 注册表单提交请求数据类型
*/
export interface RegisterReq{
username:string;
password:string;
email:string
}
自定义registerForm 组件
<!-- @/compaments/registerForm.vue -->
<template>
<ElForm class="register-form" ref="registerRef" :model="registerParam" :rules="registerRules">
<h1 class="register-title">注册</h1>
<ElFormItem prop="username">
<ElInput placeholder="请输入账号" :prefix-icon="User" v-model="registerParam.username" size="large"></ElInput>
</ElFormItem>
<ElFormItem prop="password">
<ElInput placeholder="请输入密码" show-password :prefix-icon="Lock" v-model="registerParam.password" size="large"></ElInput>
</ElFormItem>
<ElFormItem prop="email">
<ElInput placeholder="请输入邮箱" :prefix-icon="Message" v-model="registerParam.email" size="large"></ElInput>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" class="register-btn" size="large" @click="submit(registerRef)">注册</ElButton>
</ElFormItem>
</ElForm>
</template>
<script setup lang="ts">
import { User, Lock, Message } from '@element-plus/icons-vue';
import { RegisterReq } from '@/interface/user';
import {reactive,ref} from 'vue'
import { ElMessage, FormInstance,FormRules } from 'element-plus';
const registerParam:RegisterReq=reactive({
username:"",
password:"",
email:""
})
const registerRef=ref<FormInstance>()
const registerRules:FormRules=reactive({
username:[{required:true,message:"账号不能为空",trigger:'blur'}],
password:[{required:true,message:"密码不能为空",trigger:'blur'}
,{required:true,message:"密码是6~20位",min:6,max:20 ,trigger:'blur'}],
email:[{required:true,message:"邮箱不能为空",trigger:'blur'}]
})
const submit=(formEl: FormInstance | undefined)=>{
if(!formEl){
return false
}
formEl.validate(async (validate:boolean)=>{
if(validate){
console.log("开始做注册的逻辑");
}else{
return false;
}
})
}
</script>
<style scoped>
.register-form {
grid-row: 1;
grid-column: 1;
opacity: 0;
transition: 1s ease-in-out;
/* 上下 | 左右 */
padding: 1% 25%;
z-index: 0;
}
.register-form.sign-up-model {
opacity: 1;
transition: 1s ease-in-out;
transition-delay: 0.5s;
z-index: 1;
}
.register-title{
text-align: center;
color: #444;
}
.register-btn{
width: 100%;
font-size: 18px;
}
</style>
去掉页面内边距占满屏幕
<!-- index.html -->
<style>
body{
box-sizing: border-box;
margin: 0;
padding: 0;
}
</style>
配置 app.vue 组件实现效果
<template>
<div :class="{ container: true, 'sign-up-model': vari }">
<div class="inner-left-container">
<div class="login-content">
<h1 style="color: white;">明思梨教育</h1>
<ElButton type="primary" @click="onClick" size="large">去注册</ElButton>
</div>
<img src="@/assets/login-bg.svg" class="image">
</div>
<div class="inner-right-container">
<div class="register-content">
<h1 style="color: white;">明思梨教育</h1>
<ElButton type="primary" @click="onClick" size="large">去登录</ElButton>
</div>
<img src="@/assets/register-bg.svg" class="image">
</div>
<div class="inner-sign-up-container">
<login :class="{ 'sign-up-model': vari }"></login>
<register :class="{ 'sign-up-model': vari }"></register>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import login from '@/components/login/loginForm.vue'
import register from '@/components/login/registerForm.vue'
const onClick = () => {
vari.value = !vari.value
}
let vari = ref<boolean>(false)
</script>
<style scoped>
.container {
width: 100vw;
height: 100vh;
background-color: white;
overflow: hidden;
position: relative;
display: flex;
flex-direction: row;
}
.container::before {
content: "";
width: 2000px;
height: 2000px;
background-color: rgb(160, 209, 35);
position: absolute;
border-radius: 50%;
transform: translateY(-50%);
right: 48%;
top: -10%;
transition: 1.8s ease-in-out;
z-index: 2;
}
.inner-left-container {
width: 0;
flex: 1;
z-index: 2;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-end;
/* 上边|右边|下边|左边 */
padding: 3rem 10% 2rem 10%;
pointer-events: all;
}
.inner-right-container {
width: 0;
flex: 1;
z-index: 2;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: flex-start;
/* 上边|右边|下边|左边 */
padding: 3rem 10% 2rem 10%;
pointer-events: none;
}
.container .inner-right-container .register-content,
.container .inner-right-container .image {
transform: translateX(1000px);
transition: 1s ease-in-out;
transition-delay: 0.5s;
}
.container .inner-left-container .login-content,
.container .inner-left-container .image {
transform: translateX(0px);
transition: 1s ease-in-out;
transition-delay: 0.5s;
}
.image {
width: 100%;
}
.inner-sign-up-container {
width: 50%;
height: 50%;
position: absolute;
right: 0;
top: 20%;
transition: 1s ease-in-out;
transition-delay: 0.5s;
display: grid;
grid-template-columns: 1fr;
}
/* 动画 */
.container.sign-up-model::before {
transform: translate(100%, -50%);
transition: 1.8s ease-in-out;
right: 52%;
}
.container.sign-up-model .inner-right-container .register-content,
.container.sign-up-model .inner-right-container .image {
transform: translateX(0px);
transition: 1s ease-in-out;
transition-delay: 0.5s;
}
.container.sign-up-model .inner-left-container .login-content,
.container.sign-up-model .inner-left-container .image {
transform: translateX(-1000px);
transition: 1s ease-in-out;
transition-delay: 0.5s;
}
.container.sign-up-model .inner-sign-up-container {
width: 50%;
height: 50%;
top: 20%;
right: 50%;
transition: 1s ease-in-out;
transition-delay: 0.5s;
}
.container.sign-up-model .inner-right-container {
pointer-events: all;
}
.container.sign-up-model .inner-left-container {
pointer-events: none;
}
</style>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。