今天跑了步很舒服
一.登录模块
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-inputtype="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.tsinterface dataType {token?: stringmessage?: string
}//登录接口返回的数据类型
export interface loginResponseData {code: numberdata: 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 = ''//通过内置构造函数Dateconst 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 = usernamelocalStorage.setItem('token', this.token)return 'ok'}}} })
重要概念解释
路由相关:
- path: 访问路径
- component: 对应的组件
- redirect: 重定向
- router.push(): 编程式导航
- <router-view>: 路由出口,显示当前路由对应的组件
- state: 存储数据的地方
- actions: 处理业务逻辑,特别是异步操作
- store: 状态管理的容器
- defineStore: 创建store的函数
-
Pinia相关:
- 最后了解 API 请求和项目配置