Vue3项目-大事件
1.ESlint配合Prettier完成代码校验
参考另一片文章
使用pnpm创建项目后eslint不提示错误信息-CSDN博客
2.基于husky的代码检验根据
husky 配置
bash中运行
1.pnpm lint
2.git add .
3.git commit -m '初始化提交'
lint-staged 配置
1安装
pnpm i lint-staged -D
2.配置 package.json
{// ... 省略 ..."lint-staged": {"*.{js,ts,vue}": ["eslint --fix"]} } {"scripts": {// ... 省略 ..."lint-staged": "lint-staged"} }
3.修改 .husky/pre-commit 文件
pnpm lint-staged
4.调整文件目录,安装sass包
pnpm add sass -D
VueRouter4路由语法
router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
// createRouter 创建路由实例
// 配置 history 模式
// 1.history模式:createWebHistory 地址栏不带#
// 2.hash模式:createWebHashHistory 地址栏带#// vite中的环境变量 import.meta.env.BASE_URL 就是vite.config.js中的base配置项
const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),// history: createWebHistory('/jd'),routes: []
})export default router
vite.config.js
import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'// https://vite.dev/config/
export default defineConfig({plugins: [vue(), vueDevTools()],base: '/',resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}}
})
import.meta.env.BASE_URL 是Vite 环境变量:https://cn.vitejs.dev/guide/env-and-mode.html
App.vue
<script setup>
import { useRouter } from 'vue-router'
import { useRoute } from 'vue-router'// 在Vue3 CompositionAPI中
// 1.获取路由对象 router useRouter
// const router=useRouter()
// 2.获取路由参数 route useRoute
// const route=useRoute()const router = useRouter()
const route = useRoute()const goList = () => {console.log(router, route)router.push('/list')
}
</script><template><div>我是app</div><button @click="$router.push('/home')">跳首页</button><button @click="goList">跳列表</button>
</template><style scoped></style>
3.ElementPlus组件库按需导入
1.安装 pnpm add element-plus
2.按需导入 pnpm install -D unplugin-vue-components unplugin-auto-import
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()],}),],
})
3.直接可以使用组件
App.vue
<script setup>
import { useRouter } from 'vue-router'
import { useRoute } from 'vue-router'// 在Vue3 CompositionAPI中
// 1.获取路由对象 router useRouter
// const router=useRouter()
// 2.获取路由参数 route useRoute
// const route=useRoute()const router = useRouter()
const route = useRoute()const goList = () => {console.log(router, route)router.push('/list')
}
</script><template><div>我是app</div><test-demo></test-demo><el-button @click="$router.push('/home')">跳首页</el-button><el-button @click="goList">跳列表</el-button>
</template><style scoped></style>
TestDemo.vue
<script setup></script><template><div>我是测试的组件</div>
</template><style scoped></style>
4.Pinia构建用户仓库和持久化
1.安装插件 pinia-plugin-persistedstate
pnpm add pinia-plugin-persistedstate -D
2.使用 main.js
import persist from 'pinia-plugin-persistedstate' import { createPinia } from 'pinia' 。。。 app.use(createPinia().use(persist))
3.配置 stores/user.js
import { defineStore } from 'pinia' import { ref } from 'vue' // 用户模块 export const useUserStore = defineStore('big-user',() => {const token = ref('') // 定义 tokenconst setToken = (t) => (token.value = t) // 设置 token return { token, setToken }},{persist: true // 持久化} )
App.vue中可以直接使用
<script setup>
import { useRouter } from 'vue-router'
import { useRoute } from 'vue-router'
import { useUserStore } from './stores/user'
// 在Vue3 CompositionAPI中
// 1.获取路由对象 router useRouter
// const router=useRouter()
// 2.获取路由参数 route useRoute
// const route=useRoute()const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const goList = () => {console.log(router, route)router.push('/list')
}
</script><template><div>我是app</div><test-demo></test-demo><el-button @click="$router.push('/home')">跳首页</el-button><el-button @click="goList">跳列表</el-button><el-button type="primary">Primary</el-button><el-button type="success">Success</el-button>{{ userStore.token }}<el-button @click="userStore.setToken('Bear Spojhb')">登陆</el-button><el-button @click="userStore.removeToken()">退出</el-button>
</template><style scoped></style>
pinia独立维护
stores/index.js
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(persist)
export default pinia
这样main.js就可以简写(感觉没有必要 但是文件比较多的时候有用)
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import pinia from '@/stores/index'
import '@/assets/main.scss'
const app = createApp(App)
app.use(pinia)
app.use(router)
app.mount('#app')
仓库统一导出
pinia 独立维护
- 现在:初始化代码在 main.js 中,仓库代码在 stores 中,代码分散职能不单一
- 优化:由 stores 统一维护,在 stores/index.js 中完成 pinia 初始化,交付 main.js 使用
原本的程序App.vue
import { useUserStore } from '@/stores/modules/user.js'
import { useCountStore } from '@/stores/modules/count.js'
需要从两个仓库文件里面导入
<script setup>
import { useRouter } from 'vue-router'
import { useRoute } from 'vue-router'
import { useUserStore } from '@/stores/modules/user.js'
import { useCountStore } from '@/stores/modules/count.js'// 在Vue3 CompositionAPI中
// 1.获取路由对象 router useRouter
// const router=useRouter()
// 2.获取路由参数 route useRoute
// const route=useRoute()const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const countStore = useCountStore()const goList = () => {console.log(router, route)router.push('/list')
}
</script><template><div>我是app</div><test-demo></test-demo><el-button @click="$router.push('/home')">跳首页</el-button><el-button @click="goList">跳列表</el-button><el-button type="primary">Primary</el-button><el-button type="success">Success</el-button>{{ userStore.token }}<el-button @click="userStore.setToken('Bear Spojhb')">登陆</el-button><el-button @click="userStore.removeToken()">退出</el-button>{{ countStore.count }}<el-button @click="countStore.add(1)">+1</el-button></template><style scoped></style>
仓库 统一导出
- 现在:使用一个仓库 import { useUserStore } from ./stores/user.js
不同仓库路径不一致
- 优化:由 stores/index.js 统一导出,导入路径统一 ./stores
,而且仓库维护在 stores/modules 中
写在stores/index.js中
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(persist)
export default pinia// import { useUserStore } from './modules/user'
// export { useUserStore }
// import { useCountStore } from './modules/counter'
// export { useCountStore }//更简单 这一句话等价于上面的两句话
export * from './modules/user'
export * from './modules/counter'
这样App.vue
就可以这样 直接从一个文件里面导入
import { useUserStore, useCountStore } from '@/stores/index.js'
App.vue
<script setup>
import { useRouter } from 'vue-router'
import { useRoute } from 'vue-router'
// import { useUserStore } from '@/stores/modules/user.js'
// import { useCountStore } from '@/stores/modules/count.js'
import { useUserStore, useCountStore } from '@/stores/index.js'// 在Vue3 CompositionAPI中
// 1.获取路由对象 router useRouter
// const router=useRouter()
// 2.获取路由参数 route useRoute
// const route=useRoute()const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const countStore = useCountStore()const goList = () => {console.log(router, route)router.push('/list')
}
</script><template><div>我是app</div><test-demo></test-demo><el-button @click="$router.push('/home')">跳首页</el-button><el-button @click="goList">跳列表</el-button><el-button type="primary">Primary</el-button><el-button type="success">Success</el-button>{{ userStore.token }}<el-button @click="userStore.setToken('Bear Spojhb')">登陆</el-button><el-button @click="userStore.removeToken()">退出</el-button>{{ countStore.count }}<el-button @click="countStore.add(1)">+1</el-button>
</template><style scoped></style>
5.数据交互-请求工具设计
utils/request.js
import axios from 'axios'const baseURL = 'http://big-event-vue-api-t.itheima.net'const instance = axios.create({// TODO 1. 基础地址,超时时间
})instance.interceptors.request.use((config) => {// TODO 2. 携带tokenreturn config},(err) => Promise.reject(err)
)instance.interceptors.response.use((res) => {// TODO 3. 处理业务失败// TODO 4. 摘取核心响应数据return res},(err) => {// TODO 5. 处理401错误return Promise.reject(err)}
)export default instance
完成axios的基本配置
import { useUserStore } from '@/stores/user'
import axios from 'axios'
import router from '@/router'
import { ElMessage } from 'element-plus'const baseURL = 'http://big-event-vue-api-t.itheima.net'const instance = axios.create({baseURL,timeout: 100000
})instance.interceptors.request.use((config) => {const userStore = useUserStore()if (userStore.token) {config.headers.Authorization = userStore.token}return config},(err) => Promise.reject(err)
)instance.interceptors.response.use((res) => {if (res.data.code === 0) {return res}ElMessage({ message: res.data.message || '服务异常', type: 'error' })return Promise.reject(res.data)},(err) => {ElMessage({ message: err.response.data.message || '服务异常', type: 'error' })console.log(err)if (err.response?.status === 401) {router.push('/login')}return Promise.reject(err)}
)export default instance
export { baseURL }
有注释版
import axios from 'axios'
import { useUserStore } from '@/stores'
import { ElMessage } from 'element-plus'
import router from '@/router'const baseURL = 'http://big-event-vue-api-t.itheima.net'const instance = axios.create({// TODO 1. 基础地址,超时时间baseURL,timeout: 10000
})instance.interceptors.request.use((config) => {// TODO 2. 携带tokenconst useStore = useUserStore()if (useStore.token) {config.headers.Authorization = useStore.token}return config},(err) => Promise.reject(err)
)// 响应拦截器
instance.interceptors.response.use((res) => {// TODO 4. 摘取核心响应数据if (res.data.code === 0) {return res}// TODO 3. 处理业务失败// 处理业务失败,给错误提示,抛出错误ElMessage.error(res.data.message || '服务异常')return Promise.reject(res.data)},(err) => {// TODO 5. 处理401错误// 错误的特殊情况=>401权限不足 或 token过期 =>拦截到登陆if (err.response?.status === 401) {router.push('/login')}// 错误的默认情况=>只要给提示ElMessage.error(err.response.data.message || '服务异常')return Promise.reject(err)}
)export default instance
export { baseURL }
6.首页整体路由设计
实现目标:
-
完成整体路由规划【搞清楚要做几个页面,它们分别在哪个路由下面,怎么跳转的.....】
-
通过观察, 点击左侧导航, 右侧区域在切换, 那右侧区域内容一直在变, 那这个地方就是一个路由的出口
-
我们需要搭建嵌套路由
目标:
-
把项目中所有用到的组件及路由表, 约定下来
约定路由规则
path | 文件 | 功能 | 组件名 | 路由级别 |
---|---|---|---|---|
/login | views/login/LoginPage.vue | 登录&注册 | LoginPage | 一级路由 |
/ | views/layout/LayoutContainer.vue | 布局架子 | LayoutContainer | 一级路由 |
├─ /article/manage | views/article/ArticleManage.vue | 文章管理 | ArticleManage | 二级路由 |
├─ /article/channel | views/article/ArticleChannel.vue | 频道管理 | ArticleChannel | 二级路由 |
├─ /user/profile | views/user/UserProfile.vue | 个人详情 | UserProfile | 二级路由 |
├─ /user/avatar | views/user/UserAvatar.vue | 更换头像 | UserAvatar | 二级路由 |
├─ /user/password | views/user/UserPassword.vue | 重置密码 | UserPassword | 二级路由 |
明确了路由规则,可以全部配完,也可以边写边配。
7.登录注册页面 [element-plus 表单 & 表单校验]
安装 element-plus 图标库
pnpm i @element-plus/icons-vue
静态结构准备
<script setup>
import { User, Lock, View } from '@element-plus/icons-vue'
import { ref } from 'vue'
const isRegister = ref(true)
</script><template><!-- el-row表示一行,一行分为24份el-col表示列(1):span="12" 代表在一行中,占12份(50%)(2):span="6" 表示在一行中,占6份(25%)(3):offset="3"代表在一行中,左侧margin份数el-form 整个表单组件el-form-item 表单的一行(一个表单域)el-input 表单元素(输入框)--><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册相关表单 --><el-form ref="form" size="large" autocomplete="off" v-if="isRegister"><el-form-item><h1>注册</h1></el-form-item><el-form-item><el-input :prefix-icon="User" placeholder="请输入用户名"></el-input></el-form-item><el-form-item><el-input :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input></el-form-item><el-form-item><el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码"></el-input></el-form-item><el-form-item><el-button class="button" type="primary" auto-insert-space> 注册 </el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false"> ← 返回 </el-link></el-form-item></el-form><!-- 登陆相关表单 --><el-form ref="form" size="large" autocomplete="off" v-else><el-form-item><h1>登录</h1></el-form-item><el-form-item><el-input :prefix-icon="User" placeholder="请输入用户名"></el-input></el-form-item><el-form-item><el-inputname="password":prefix-icon="Lock"type="password"placeholder="请输入密码"></el-input></el-form-item><!-- <el-form-item><el-input :prefix-icon="View" placeholder="测试代码"></el-input></el-form-item> --><el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><el-form-item><el-button class="button" type="primary" auto-insert-space>登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true"> 注册 → </el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="scss" scoped>
.login-page {height: 100vh;background-color: #fff;.bg {background:url('@/assets/logo2.png') no-repeat 60% center / 240px auto,url('@/assets/login_bg.jpg') no-repeat center / cover;border-radius: 0 20px 20px 0;}.form {display: flex;flex-direction: column;justify-content: center;user-select: none;.title {margin: 0 auto;}.button {width: 100%;}.flex {width: 100%;display: flex;justify-content: space-between;}}
}
</style>
8.注册登录-表单校验
LoginPage.vue
1.结构相关
el-row表示一行,一行分为24份
el-col表示列
(1):span="12" 代表在一行中,占12份(50%)
(2):span="6" 表示在一行中,占6份(25%)
(3):offset="3"代表在一行中,左侧margin份数
el-form 整个表单组件
el-form-item 表单的一行(一个表单域)
el-input 表单元素(输入框)
2.四大校验方式
(1)el-form => :model="ruleForm" 绑定的整个form的数据对象 {xxx,xxx,xxx}
(2)el-form => :rules="rules" 绑定的整个rules规则对象 {xxx,xxx,xxx}
(3)表单元素 => v-model="ruleForm.xxx" 给表单元素,绑定form子属性
(4)el-form-item => prop配置生效的是哪个校验规则(和rules中的字段要对应)
<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref } from 'vue'
const isRegister = ref(true)
// 注册
const formModel = ref({username: '',password: '',repassword: ''
})
// 整个表单的校验规则
// 1.非空校验 required:true message消息提示,trigger触发校验的时机 blur change
// 2.长度校验 min:xx, max:xx
// 3.正则校验 pattern:正则规则 \S非空字符
// 4.自定义校验 => 自己写逻辑校验(校验函数)
// validator:(rule,value,callback)
// (1)rule 当前校验规则相关的信息
// (2)value 所校验的表单元素当前表单值
// (3)callback 无论成功还是失败,都需要callback回调
// - callback() 校验成功
// - callback(new error(错误信息)) 校验失败const rules = {username: [{ required: true, message: '请输入用户名', trigger: 'blur' },{ min: 5, max: 10, message: '用户名必须是5-10位的字符', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' },{ pattern: /^\S{6,15}$/, message: '密码必须是 6-15位 的非空字符', trigger: 'blur' }],repassword: [{ required: true, message: '请输入密码', trigger: 'blur' },{ pattern: /^\S{6,15}$/, message: '密码必须是 6-15位 的非空字符', trigger: 'blur' },{validator: (rule, value, callback) => {// 判断value和当前form中收集的password是否一致if (value !== formModel.value.password) {callback(new Error('两次输入密码不一致'))} else {callback() //就算校验成功,也需要callback}}}]
}
</script><template><!--1.结构相关el-row表示一行,一行分为24份el-col表示列(1):span="12" 代表在一行中,占12份(50%)(2):span="6" 表示在一行中,占6份(25%)(3):offset="3"代表在一行中,左侧margin份数el-form 整个表单组件el-form-item 表单的一行(一个表单域)el-input 表单元素(输入框)2.校验相关(1)el-form => :model="ruleForm" 绑定的整个form的数据对象 {xxx,xxx,xxx}(2)el-form => :rules="rules" 绑定的整个rules规则对象 {xxx,xxx,xxx}(3)表单元素 => v-model="ruleForm.xxx" 给表单元素,绑定form子属性(4)el-form-item => prop配置生效的是哪个校验规则(和rules中的字段要对应)--><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册相关表单 --><el-form:model="formModel":rules="rules"ref="form"size="large"autocomplete="off"v-if="isRegister"><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username"><el-inputv-model="formModel.username":prefix-icon="User"placeholder="请输入用户名"></el-input></el-form-item><el-form-item prop="password"><el-inputv-model="formModel.password":prefix-icon="Lock"type="password"placeholder="请输入密码"></el-input></el-form-item><el-form-item prop="repassword"><el-inputv-model="formModel.repassword":prefix-icon="Lock"type="password"placeholder="请输入再次密码"></el-input></el-form-item><el-form-item><el-button class="button" type="primary" auto-insert-space> 注册 </el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false"> ← 返回 </el-link></el-form-item></el-form><!-- 登陆相关表单 --><el-form ref="form" size="large" autocomplete="off" v-else><el-form-item><h1>登录</h1></el-form-item><el-form-item><el-input :prefix-icon="User" placeholder="请输入用户名"></el-input></el-form-item><el-form-item><el-inputname="password":prefix-icon="Lock"type="password"placeholder="请输入密码"></el-input></el-form-item><!-- <el-form-item><el-input :prefix-icon="View" placeholder="测试代码"></el-input></el-form-item> --><el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><el-form-item><el-button class="button" type="primary" auto-insert-space>登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true"> 注册 → </el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="scss" scoped>
.login-page {height: 100vh;background-color: #fff;.bg {background:url('@/assets/logo2.png') no-repeat 60% center / 240px auto,url('@/assets/login_bg.jpg') no-repeat center / cover;border-radius: 0 20px 20px 0;}.form {display: flex;flex-direction: column;justify-content: center;user-select: none;.title {margin: 0 auto;}.button {width: 100%;}.flex {width: 100%;display: flex;justify-content: space-between;}}
}
</style>
9.注册预校验
需求:点击注册按钮,注册之前,需要先校验
通过 ref 获取到 表单组件
const form = ref() <el-form ref="form">
注册之前进行校验
<el-button@click="register"class="button"type="primary"auto-insert-space >注册 </el-button> const register = async () => {await form.value.validate()console.log('开始注册请求') }
封装 api 实现注册功能
需求:封装注册api,进行注册,注册成功切换到登录
新建 api/user.js 封装
import request from '@/utils/request' export const userRegisterService = ({ username, password, repassword }) =>request.post('/api/reg', { username, password, repassword })
页面中调用
const register = async () => {await form.value.validate()await userRegisterService(formModel.value)ElMessage.success('注册成功')// 切换到登录isRegister.value = false }
(ElMessage报错 不要声明,声明会没有样式)
eslintrc 中声明全局变量名, 解决 ElMessage 报错问题
module.exports = {...globals: {ElMessage: 'readonly',ElMessageBox: 'readonly',ElLoading: 'readonly'} }
10.登陆校验和登陆请求
实现登录校验
【需求说明】给输入框添加表单校验
-
用户名不能为空,用户名必须是5-10位的字符,失去焦点 和 修改内容时触发校验
-
密码不能为空,密码必须是6-15位的字符,失去焦点 和 修改内容时触发校验
操作步骤:
(1)model 属性绑定 form 数据对象,直接绑定之前提供好的数据对象即可
<el-form :model="formModel" >
(2)rules 配置校验规则,共用注册的规则即可
<el-form :rules="rules" >
(3)v-model 绑定 form 数据对象的子属性
<el-inputv-model="formModel.username":prefix-icon="User"placeholder="请输入用户名" ></el-input> <el-inputv-model="formModel.password"name="password":prefix-icon="Lock"type="password"placeholder="请输入密码" ></el-input>
(4)prop 绑定校验规则
<el-form-item prop="username"><el-inputv-model="formModel.username":prefix-icon="User"placeholder="请输入用户名"></el-input> </el-form-item> ...
(5)切换的时候重置
watch(isRegister, () => {formModel.value = {username: '',password: '',repassword: ''} })
登录前的预校验 & 登录成功
【需求说明1】登录之前的预校验
-
登录请求之前,需要对用户的输入内容,进行校验
-
校验通过才发送请求
【需求说明2】登录功能
-
封装登录API,点击按钮发送登录请求
-
登录成功存储token,存入pinia 和 持久化本地storage
-
跳转到首页,给提示
【测试账号】
-
登录的测试账号: shuaipeng
-
登录测试密码: 123456
PS: 每天账号会重置,如果被重置了,可以去注册页,注册一个新号
实现步骤:
(1)注册事件,进行登录前的预校验 (获取到组件调用方法)
<el-form ref="form">const login = async () => {await form.value.validate()console.log('开始登录') }
(2)封装接口 API
export const userLoginService = ({ username, password }) =>request.post('api/login', { username, password })
(3)调用方法将 token 存入 pinia 并 自动持久化本地
const userStore = useUserStore() const router = useRouter() const login = async () => {await form.value.validate()const res = await userLoginService(formModel.value)userStore.setToken(res.data.token)ElMessage.success('登录成功')router.push('/') }
LoginPage.vue
<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { userRegisterService,userLoginService } from '@/api/user'
// import { ElMessage } from 'element-plus'
import { ref, watch } from 'vue'
import { useUserStore } from '@/stores'
import { useRouter } from 'vue-router'
const form = ref()
const isRegister = ref(false)// 注册
const formModel = ref({username: '',password: '',repassword: ''
})
// 整个表单的校验规则
// 1.非空校验 required:true message消息提示,trigger触发校验的时机 blur change
// 2.长度校验 min:xx, max:xx
// 3.正则校验 pattern:正则规则 \S非空字符
// 4.自定义校验 => 自己写逻辑校验(校验函数)
// validator:(rule,value,callback)
// (1)rule 当前校验规则相关的信息
// (2)value 所校验的表单元素当前表单值
// (3)callback 无论成功还是失败,都需要callback回调
// - callback() 校验成功
// - callback(new error(错误信息)) 校验失败const rules = {username: [{ required: true, message: '请输入用户名', trigger: 'blur' },{ min: 5, max: 10, message: '用户名必须是5-10位的字符', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' },{ pattern: /^\S{6,15}$/, message: '密码必须是 6-15位 的非空字符', trigger: 'blur' }],repassword: [{ required: true, message: '请输入密码', trigger: 'blur' },{ pattern: /^\S{6,15}$/, message: '密码必须是 6-15位 的非空字符', trigger: 'blur' },{validator: (rule, value, callback) => {// 判断value和当前form中收集的password是否一致if (value !== formModel.value.password) {callback(new Error('两次输入密码不一致'))} else {callback() //就算校验成功,也需要callback}}}]
}const register = async () => {await form.value.validate()await userRegisterService(formModel.value)ElMessage.success('注册成功')// 切换到登录isRegister.value = false// console.log(formModel.value.username)
}const userStore = useUserStore()
const router = useRouter()
const login = async () => {await form.value.validate()// console.log('开始登陆')// const res = await userLoginService(formModel.username, formModel.password)const res = await userLoginService(formModel.value)userStore.setToken(res.data.token)// console.log(res);ElMessage.success('登录成功')router.push('/')
}// 切换时,重置表单内容
watch(isRegister, () => {formModel.value = {username: '',password: '',repassword:''}
})
</script><template><!--1.结构相关el-row表示一行,一行分为24份el-col表示列(1):span="12" 代表在一行中,占12份(50%)(2):span="6" 表示在一行中,占6份(25%)(3):offset="3"代表在一行中,左侧margin份数el-form 整个表单组件el-form-item 表单的一行(一个表单域)el-input 表单元素(输入框)2.校验相关(1)el-form => :model="ruleForm" 绑定的整个form的数据对象 {xxx,xxx,xxx}(2)el-form => :rules="rules" 绑定的整个rules规则对象 {xxx,xxx,xxx}(3)表单元素 => v-model="ruleForm.xxx" 给表单元素,绑定form子属性(4)el-form-item => prop配置生效的是哪个校验规则(和rules中的字段要对应)--><el-row class="login-page"><el-col :span="12" class="bg"></el-col><el-col :span="6" :offset="3" class="form"><!-- 注册相关表单 --><el-form:model="formModel":rules="rules"ref="form"size="large"autocomplete="off"v-if="isRegister"><el-form-item><h1>注册</h1></el-form-item><el-form-item prop="username"><el-inputv-model="formModel.username":prefix-icon="User"placeholder="请输入用户名"></el-input></el-form-item><el-form-item prop="password"><el-inputv-model="formModel.password":prefix-icon="Lock"type="password"placeholder="请输入密码"></el-input></el-form-item><el-form-item prop="repassword"><el-inputv-model="formModel.repassword":prefix-icon="Lock"type="password"placeholder="请输入再次密码"></el-input></el-form-item><el-form-item><el-button @click="register" class="button" type="primary" auto-insert-space>注册</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = false"> ← 返回 </el-link></el-form-item></el-form><!-- 登陆相关表单 --><el-form :model="formModel" :rules="rules" ref="form" size="large" autocomplete="off" v-else><el-form-item><h1>登录</h1></el-form-item><el-form-item prop="username"><el-input v-model="formModel.username" :prefix-icon="User" placeholder="请输入用户名"></el-input></el-form-item><el-form-item prop="password"><el-inputv-model="formModel.password"name="password":prefix-icon="Lock"type="password"placeholder="请输入密码"></el-input></el-form-item><!-- <el-form-item><el-input :prefix-icon="View" placeholder="测试代码"></el-input></el-form-item> --><el-form-item class="flex"><div class="flex"><el-checkbox>记住我</el-checkbox><el-link type="primary" :underline="false">忘记密码?</el-link></div></el-form-item><el-form-item><el-button @click="login" class="button" type="primary" auto-insert-space>登录</el-button></el-form-item><el-form-item class="flex"><el-link type="info" :underline="false" @click="isRegister = true"> 注册 → </el-link></el-form-item></el-form></el-col></el-row>
</template><style lang="scss" scoped>
.login-page {height: 100vh;background-color: #fff;.bg {background:url('@/assets/logo2.png') no-repeat 60% center / 240px auto,url('@/assets/login_bg.jpg') no-repeat center / cover;border-radius: 0 20px 20px 0;}.form {display: flex;flex-direction: column;justify-content: center;user-select: none;.title {margin: 0 auto;}.button {width: 100%;}.flex {width: 100%;display: flex;justify-content: space-between;}}
}
</style>