硅谷甄选(三)登录注册
今天跑了步很舒服
一.登录模块
1.1登录路由静态组件
src\views\login\index.vue
<template>
<div class="login_container">
<el-row>
<el-col :span="12" :xs="0"></el-col>
<el-col :span="12" :xs="24">
<el-form class="login_form">
<h1>Hello</h1>
<h2>欢迎来到硅谷甄选</h2>
<el-form-item>
<el-input
:prefix-icon="User"
v-model="loginForm.username"
></el-input>
</el-form-item>
<el-form-item>
<el-input
type="password"
:prefix-icon="Lock"
v-model="loginForm.password"
show-password
></el-input>
</el-form-item>
<el-form-item>
<el-button class="login_btn" type="primary" size="default">
登录
</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { User, Lock } from '@element-plus/icons-vue'
import { reactive } from 'vue'
//收集账号与密码数据
let loginForm = reactive({ username: 'admin', password: '111111' })
</script>
<style lang="scss" scoped>
.login_container {
width: 100%;
height: 100vh;
background: url('@/assets/images/background.jpg') no-repeat;
background-size: cover;
.login_form {
position: relative;
width: 80%;
top: 30vh;
background: url('@/assets/images/login_form.png') no-repeat;
background-size: cover;
padding: 40px;
h1 {
color: white;
font-size: 40px;
}
h2 {
color: white;
font-size: 20px;
margin: 20px 0px;
}
.login_btn {
width: 100%;
}
}
}
</style>
注意
el-col是24份的,在此左右分为了12份。我们在右边放置我们的结构。
:xs="0"是为了响应式。
el-form下的element-plus元素都用el-form-item包裹起来。
通过 row (行)和 col (列)组件,并通过 col 组件的 span 属性我们就可以自由地组合布局。
row 行提供 gutter 属性来指定列之间的间距,其默认值为0。
通过制定 col 组件的 offset 属性可以指定分栏偏移的栏数。
参照了 Bootstrap 的 响应式设计,预设了五个响应尺寸:xs、sm、md、lg 和 xl。
我自己改写的静态页面
<template>
<!-- Element Plus 表单组件 -->
<el-form>
<!-- 标题部分,使用条件渲染显示不同文本 -->
<h2>{{ isLogin ? '欢迎来到硅谷甄选' : '注册新账户' }}</h2>
<!-- 用户名输入框 -->
<el-form-item label="用户名">
<el-input
v-model="form.username"
:prefix-icon="User"
></el-input>
</el-form-item>
<!-- 密码输入框 -->
<el-form-item label="密码">
<el-input
v-model="form.password"
:type="passwordVisible ? 'text' : 'password'"
:prefix-icon="Lock"
>
<!-- 使用具名插槽自定义后缀图标(显示/隐藏密码的小眼睛) -->
<template #suffix>
<el-icon
class="cursor-pointer"
@click="togglePasswordVisibility"
>
<!-- 动态组件,根据passwordVisible切换显示不同图标 -->
<component :is="passwordVisible ? View : Hide" />
</el-icon>
</template>
</el-input>
</el-form-item>
<!-- 确认密码输入框,仅在注册时显示 -->
<el-form-item v-if="!isLogin" label="确认密码">
<el-input
v-model="form.confirmPassword"
:type="passwordVisible ? 'text' : 'password'"
:prefix-icon="Lock"
>
<template #suffix>
<el-icon class="cursor-pointer" @click="togglePasswordVisibility">
<component :is="passwordVisible ? View : Hide" />
</el-icon>
</template>
</el-input>
</el-form-item>
<!-- 按钮区域 -->
<el-form-item>
<!-- 登录/注册按钮 -->
<el-button type="primary" @click="submitForm">
{{ isLogin ? '登录' : '注册' }}
</el-button>
<!-- 默认账号按钮 -->
<el-button type="text" @click="useDefaultAccount">
使用默认账号
</el-button>
</el-form-item>
<!-- 切换登录/注册的按钮 -->
<el-form-item>
<el-button type="text" @click="toggleLoginRegister">
{{ isLogin ? '没有账户?立即注册' : '已有账户?立即登录' }}
</el-button>
</el-form-item>
</el-form>
</template>
<script setup lang="ts">
// 导入Element Plus的图标组件
import { User, Lock, View, Hide } from '@element-plus/icons-vue'
// 导入Vue的响应式API
import { reactive, ref } from 'vue'
// 导入用户状态管理store
import useUserStore from '@/store/modules/user'
// 导入路由实例
import { useRouter } from 'vue-router'
// 导入Element Plus的消息提示组件
import { ElMessage } from 'element-plus'
// 创建store和router实例
const userStore = useUserStore()
const router = useRouter()
// 控制当前是登录还是注册状态
const isLogin = ref(true)
// 控制密码是否可见
const passwordVisible = ref(false)
// 表单数据
const form = reactive({
username: '',
password: '',
confirmPassword: ''
})
// 切换密码显示/隐藏
const togglePasswordVisibility = () => {
passwordVisible.value = !passwordVisible.value
}
// 切换登录/注册状态
const toggleLoginRegister = () => {
isLogin.value = !isLogin.value
// 切换时清空表单
form.username = ''
form.password = ''
form.confirmPassword = ''
}
// 使用默认账号
const useDefaultAccount = () => {
form.username = '123'
form.password = '111111'
}
// 提交表单
const submitForm = async () => {
if (isLogin.value) {
try {
const result = await userStore.userLogin(form.username, form.password)
if (result === 'ok') {
ElMessage.success('登录成功')
await router.push('/home')
}
} catch (err: any) {
console.error(err)
ElMessage.error(err.message || '登录失败')
}
} else {
if (form.password !== form.confirmPassword) {
ElMessage.error('两次输入的密码不一致')
return
}
console.log('注册', form.username, form.password)
}
}
</script>
<style scoped lang="scss">
/* 标题样式 */
h2 {
text-align: center;
margin-bottom: 20px;
}
/* 鼠标悬停时显示手型指针 */
.cursor-pointer {
cursor: pointer;
}
/* 按钮间距 */
.el-button {
margin-right: 10px;
}
</style>
1.2登陆业务实现
登录按钮绑定回调
回调应该做的事情
const login = () => {
//点击登录按钮以后干什么
//通知仓库发起请求
//请求成功->路由跳转
//请求失败->弹出登陆失败信息
}
仓库store初始化
大仓库
安装pinia:pnpm i pinia@2.0.34
//创建用户相关的小仓库
import { defineStore } from 'pinia'
//创建用户小仓库
const useUserStore = defineStore('User', {
//小仓库存储数据地方
state: () => {},
//处理异步|逻辑地方
actions: {},
getters: {},
})
//对外暴露小仓库
export default useUserStore
按钮回调
//登录按钮的回调
const login = async () => {
//按钮加载效果
loading.value = true
//点击登录按钮以后干什么
//通知仓库发起请求
//请求成功->路由跳转
//请求失败->弹出登陆失败信息
try {
//也可以书写.then语法
await useStore.userLogin(loginForm)
//编程式导航跳转到展示数据的首页
$router.push('/')
//登录成功的提示信息
ElNotification({
type: 'success',
message: '登录成功!',
})
//登录成功,加载效果也消失
loading.value = false
} catch (error) {
//登陆失败加载效果消失
loading.value = false
//登录失败的提示信息
ElNotification({
type: 'error',
message: (error as Error).message,
})
}
}
用户仓库
//创建用户相关的小仓库
import {defineStore} from "pinia";
// 引入接口
import {reqLogin} from "../../api/user";
//引入数据类型
import type {loginForm} from '@/api/user/type'
//创建用户小仓库
const useUserStore = defineStore('User', {
小仓库存储数据地方
state: () => {
return {
token: localStorage.getItem('TOKEN')//用户的唯一标识
}
},
//处理异步|逻辑地方
actions: {
async userLogin(data: loginForm) {
//登陆的请求
async
userLogin(data
:
loginForm
)
{
//登陆的请求
const result: any = await reqLogin(data)
if (result.code == 200) {
this.token = result.data.token
localStorage.setItem('TOKEN', result.data.token)
return 'ok'
} else {
return Promise.reject(new Error(result.data.message))
}
}
}
},
getters: {},
})
export default useUserStore
小结
- Element-plus中ElNotification用法(弹窗):引入:
import { ElNotification } from 'element-plus'
使用://登录失败的提示信息
ElNotification({
type: 'error',
message: (error as Error).message,
})Element-plus中el-button的loading属性。
pinia使用actions、state的方式和vuex不同:需要引入函数和创建实例
$router的使用:也需要引入函数和创建实例
在actions中使用state的token数据:this.token
类型定义需要注意。
promise的使用和vue2现在看来是一样的 -
模板封装登陆业务
result返回类型封装
src\api\user\type.ts
interface dataType {
token?: string
message?: string
}
//登录接口返回的数据类型
export interface loginResponseData {
code: number
data: dataType
}
State仓库类型封装
src\store\modules\types\type.ts
//定义小仓库数据state类型
export interface UserState {
token: string | null
}
本地存储封装
src\utils\token.ts
//封装本地存储存储数据与读取数据方法
export const SET_TOKEN = (token: string) => {
localStorage.setItem('TOKEN', token)
}
export const GET_TOKEN = () => {
return localStorage.getItem('TOKEN')
}
登录时间的判断
- 封装函数 src\utils\time.ts
//封装函数:获取当前时间段 export const getTime = () => { let message = '' //通过内置构造函数Date const hour = new Date().getHours() if (hour <= 9) { message = '早上' } else if (hour <= 14) { message = '上午' } else if (hour <= 18) { message = '下午' } else { message = '晚上' } return message }
表单校验规则
表单校验
表单绑定项 - 表单元素绑定项
- Form 组件提供了表单验证的功能,只需为 rules 属性传入约定的验证规则,并将 form-Item 的 prop 属性设置为需要验证的特殊键值即可
- 使用规则rules
//定义表单校验需要的配置对象 const rules = { username: [ //规则对象属性: { required: true, // required,代表这个字段务必要校验的 min: 5, //min:文本长度至少多少位 max: 10, // max:文本长度最多多少位 message: '长度应为6-10位', // message:错误的提示信息 trigger: 'change', //trigger:触发校验表单的时机 change->文本发生变化触发校验, blur:失去焦点的时候触发校验规则 }, ], password: [ { required: true, min: 6, max: 10, message: '长度应为6-15位', trigger: 'change', }, ], }
- 校验规则通过后运行
const login = async () => { //保证全部表单项校验通过 await loginForms.value.validate() 。。。。。。 }
自定义表单校验
- 修改使用规则rules使用自己编写的函数作为规则校验。
//定义表单校验需要的配置对象
const rules = {
username: [
//规则对象属性:
/* {
required: true, // required,代表这个字段务必要校验的
min: 5, //min:文本长度至少多少位
max: 10, // max:文本长度最多多少位
message: '长度应为6-10位', // message:错误的提示信息
trigger: 'change', //trigger:触发校验表单的时机 change->文本发生变化触发校验, blur:失去焦点的时候触发校验规则
}, */
{ trigger: 'change', validator: validatorUserName },
],
password: [
{ trigger: 'change', validator: validatorPassword },
],
}
- 自定义校验规则函数
//自定义校验规则函数 const validatorUserName = (rule: any, value: any, callback: any) => { //rule:校验规则对象 //value:表单元素文本内容 //callback:符合条件,callback放行通过,不符合:注入错误提示信息 if (value.length >= 5) { callback() } else { callback(new Error('账号长度至少5位')) } } const validatorPassword = (rule: any, value: any, callback: any) => { if (value.length >= 6) { callback() } else { callback(new Error('密码长度至少6位')) } }
再来总体讲一下(以下是用我自己改写的代码)
要学习这个登录页面
建议您按以下顺序学习这些关键文件: - 基础页面结构
src/view/login/index.vue // 登录页面主文件
src/components/LoginForm.vue // 登录表单组件
这两个文件包含了:
- Vue3 组件的基本结构
- Element Plus 组件的使用
组件拆分和组织方式 - 路由配置
src/router/index.ts // 路由配置文件
src/App.vue // 根组件,包含路由出口
学习:
- 路由的基本配置
- 页面跳转的实现
- 路由出口的使用
- 状态管理
src/store/index.ts // Pinia 主配置
src/store/modules/user.ts // 用户状态管理模块
学习:
- Pinia 的基本使用
- 状态管理的概念
- 数据持久化(如 token 的存储)
-
4. API 请求
src/api/user/index.ts // 用户相关 API 接口定义
src/utils/request.ts // Axios 请求封装
学习:
- API 接口的组织方式
- Axios 的基本使用
- 请求拦截器和响应拦截器
- 配置文件
vite.config.ts // Vite 配置文件
tsconfig.json // TypeScript 配置
学习:
- 项目基本配置
- 路径别名配置
- TypeScript 配置
- TypeScript 类型声明的基础
-
6. 类型声明
shims-vue.d.ts // Vue 文件的类型声明
先从 login/index.vue 和 LoginForm.vue 开始,理解基本的页面结构和组件通信
- 然后学习路由配置,理解页面跳转的实现
-
// 1. 引入必要的函数和类型 import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' // 2. 定义路由配置 const routes: Array<RouteRecordRaw> = [ { path: '/', // 访问路径 redirect: '/home' // 重定向到首页 }, { path: '/login', // 登录页路径 name: 'Login', // 路由名称 component: Login // 对应的组件 } ] // 3. 创建路由实例 const router = createRouter({ history: createWebHistory(), // 使用HTML5历史模式 routes })
- 接着学习状态管理,理解数据共享的方式
Pinia用于管理全局状态。我们以用户状态为例:// 1. 创建store import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { // 状态(数据) state: () => ({ token: localStorage.getItem('token') || '', username: '', }), // 动作(异步操作) actions: { async userLogin(username: string, password: string) { // 处理登录逻辑 if (username === '123' && password === '111111') { this.token = 'default_token' this.username = username localStorage.setItem('token', this.token) return 'ok' } } } })
重要概念解释
路由相关:
- path: 访问路径
- component: 对应的组件
- redirect: 重定向
- router.push(): 编程式导航
- <router-view>: 路由出口,显示当前路由对应的组件
- state: 存储数据的地方
- actions: 处理业务逻辑,特别是异步操作
- store: 状态管理的容器
- defineStore: 创建store的函数
-
Pinia相关:
- 最后了解 API 请求和项目配置