Vue 3 项目实战教程大事件管理系统 (一):从零开始搭建项目基础
文章目录
- 技术栈介绍
- 1.包管理工具pnpm的安装
- 2.调整项目目录
- 3.VueRouter4路由语法规则
- 4.引入 element-ui 组件库
- 5.Pinia - 构建用户仓库 和 持久化
- 5.1 基本配置-构建用户仓库和开启持久化
- 5.2 优化-pinia 独立维护和仓库统一导出
- 5.2.1 pinia 独立维护
- 5.2.2 仓库统一导出
- 6. 数据交互-请求工具设计
技术栈介绍
本项目的技术栈 本项目技术栈基于 ES6、vue3、pinia、vue-router 、vite 、axios 和 element-plus
1.包管理工具pnpm的安装
pnpm优势:比同类工具快 2倍 左右、节省磁盘空间
安装方式:mac 系统要加 sudo 提升为管理员权限
sudo npm install -g pnpm
安装pnpm 版本为:10.12.4
创建项目:
pnpm create vue
项目初始化完成,可执行以下命令:
cd Vue3-big-event-admin
pnpm install
pnpm dev
2.调整项目目录
默认生成的目录结构不满足我们的开发需求,所以这里需要做一些自定义改动。主要是两个工作:删除文件和修改内容
- 删除初始化的默认文件
- 修改剩余代码内容
- 新增调整我们需要的目录结构
- 拷贝初始化资源文件,安装预处理器插件
router/index.js
删掉stores 中的 js 文件
删掉views 中的.vue 文件
将 assets 文件夹中的内容删除
将main.js 中的导入 css 删除
将 app.vue 清
空
<script setup>
</script><template><div>我是 App.vue</div>
</template><style scoped></style>
新建文件夹 api 和 utils
将下面的静态资源放入到 assets 文件夹中
将 main.scss 导入进 main.js 中,需要安装scss 预处理
pnpm add sass -D
下完之后导入进去
import '@/assets/main.scss'
3.VueRouter4路由语法规则
默认代码如下:
import { createRouter, createWebHistory } from 'vue-router'//createRouter创建路由实例
//配置 history 模式: createWebHistory 地址栏不带#
//配置 hash 模式: createHashHistory 地址栏带#
const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: []
})export default router
在 app.vue 写两个按钮模拟路由跳转
$router.push(‘’)和 Vue2 一样
但是如果点击事件是自定义的函数,就和 Vue2 的不同了,在 Vue3 CompositionAPI 中
- 获取路由对象 const router=useRouter()
- //获取路由参数 const route =useRoute()
代码如下:
<script setup>
import {useRoute,useRouter} from 'vue-router'
//Vue3 中 获取路由对象 router
const router=useRouter()
//获取路由参数 route
const route =useRoute()const goList=()=>{console.log(router,route)router.push('list')
}
</script><template><div>我是 App.vue<button @click="$router.push('/home')">跳首页</button><button @click="goList">跳列表页</button></div>
</template><style scoped></style>
在路由配置中的这一项:history: createWebHistory(import.meta.env.BASE_URL),
把import.meta.env.BASE_URL改成/TB,效果如下:
路径前面会多 TB
import.meta.env.BASE_URL是一个参数,在vite.config.js中修改
4.引入 element-ui 组件库
官方文档: https://element-plus.org/zh-CN/
按照官方文档:
安装:
pnpm install element-plus
快速开始按需引入:
pnpm add -D unplugin-vue-components unplugin-auto-import
对照文档中给出的内容进行比对配置:
配置完成后重启项目
pnpm dev
把 button 改成 el-button 测试效果,补充:默认下 components 下的文件也会被自动注册
<el-button @click="$router.push('/home')">跳首页</el-button >
<el-button @click="goList">跳列表页</el-button >
5.Pinia - 构建用户仓库 和 持久化
5.1 基本配置-构建用户仓库和开启持久化
在创建 Vue 脚手架时,已经导入好了 pinia,现在开启 pinia 的持久化,完成持久化配置
官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/
安装包依赖:
pnpm add pinia-plugin-persistedstate
将插件添加到你的 pinia 实例中
配置 stores/user.js
import { defineStore } from 'pinia'
import { ref } from 'vue'export const useStore = defineStore('big-user',() => {const token = ref('') // 定义 tokenconst setToken = (newToken) => (token.value = newToken) // 设置 tokenconst removeToken=()=>{token.value=''}return { token,setToken,removeToken }},{persist: true,},
)
在 app.vue 中测试 pinia
//导入 pinia
import {useUserStore} from '@/stores/user'
const userStore=useUserStore()<p>{{ userStore.token }}</p>
<el-button @click="userStore.setToken('qrafdsgsdfs')">登入</el-button >
<el-button @click="userStore.removeToken">退出</el-button >
5.2 优化-pinia 独立维护和仓库统一导出
5.2.1 pinia 独立维护
-
现在:初始化代码在
main.js
中,仓库代码在 stores 中,代码分散职能不单一 -
优化:由 stores 统一维护,在
stores/index.js
中完成 pinia 初始化,交付 main.js 使用
在 main.js 中拿走和 pinia 有关的代码放到 stores 下的 index.js
移除前的 main.js
移除后的 main.js和移除后的 index.js
重新启动服务
pnpm dev
5.2.2 仓库统一导出
-
现在:使用一个仓库 import { useUserStore } from
./stores/user.js
不同仓库路径不一致 -
优化:由 stores/index.js 统一导出,导入路径统一
./stores
,而且仓库维护在 stores/modules 中
随着项目的扩大,将 stores 下面的 js 内容放到了 modules文件夹内
如果想在 app.vue 中导入就要写
import {useUserStore} from '@/stores/modules/user'
这样太麻烦了,希望改成如下情况,有 stores 下面的 index.js 统一管理
import {useUserStore,XXXStore} from '@/stores'
在改写 index.js
import{useUserStore} from './modules/user'
export{useUserStore} //假设还有一个 store
import{useCountStore} from './modules/user'
export{useCountStore}
这样写还是麻烦,改为更简单的方法
export * from './modules/user'
export * from './modules/counter'
6. 数据交互-请求工具设计
一开始做项目的时候,确实可以直接用 axios 请求接口数据,不需要配置什么拦截器
但随着项目越来越复杂,你就会发现这样写代码有一堆问题
写在utils/request.js
首先按照 axios
pnpm add axios
1.每个请求都要手动加 token,很麻烦
axios.get('/api/userinfo', {headers: {Authorization: store.token}
})
你有 20 个接口,就要写 20 次 headers,改 token 也要改 20 次。2.错误提示要手动写,容易忘
axios.get('/api/userinfo').then(res => {if (res.data.code !== 0) {ElMessage.error(res.data.message)}})你会发现:所有接口都要加 if...else,非常重复,而且不统一,UI 上也不好看。
3.token 过期时跳登录,没法统一处理
.catch(err => {if (err.response.status === 401) {router.push('/login')}
})
太繁琐有了拦截器,一切就变得自动化了!
// 请求拦截器:自动加 token
// 响应拦截器:自动弹提示 + 跳登录
你只需要这样用就行了:
request.get('/my/userinfo')
无需关注 token、错误码、跳转页面,所有的逻辑已经帮你封装好了。
统一封装 axios 请求模板:
官方文档:http://www.axios-js.com/zh-cn/docs/#axios-create-config
import axios from 'axios'const baseURL = 'http://big-event-vue-api-t.itheima.net'const instance = axios.create({// 1. 基础地址,超时时间(配置项)
})//请求拦截器
instance.interceptors.request.use((config) => {// 2. 携带token,将 token 值赋给请求头return config},(err) => Promise.reject(err)
)//响应拦截器
instance.interceptors.response.use(
//http 返回 200 成功(res) => { // 3. 正确拿到了业务数据// 4. 业务逻辑错误,例如限制密码字母大写return res},(err) => {// 5. 处理http 返回401错误return Promise.reject(err)}
)export default instance
本项目封装:
//导入 stores axios router 和组件库 element-ui
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'//创建 axios 实例,配置一些默认项,如超时 10 秒
const instance = axios.create({baseURL,timeout: 100000
})//请求拦截器
instance.interceptors.request.use(
//http200 返回正确(config) => {const userStore = useUserStore()//如果有 token,将 token赋值给请求头,为什么是Authorization,见解释 1if (userStore.token) { config.headers.Authorization = userStore.token}return config},//错误抛出错误信息(err) => Promise.reject(err)
)instance.interceptors.response.use((res) => {//业务逻辑正确(code=0),处理如下if (res.data.code === 0) {return res}//业务逻辑错误(code!=0),手动抛出错误信息,ElMessage是组件,type是他的样式ElMessage({ message: res.data.message || '服务异常', type: 'error' })return Promise.reject(res.data)},//如果 http 返回是是 401,表示用户未登入,或者 token 过期,得重新登入(err) => {ElMessage({ message: err.response.data.message || '服务异常', type: 'error' })console.log(err)if (err.response?.status === 401) {router.push('/login')}//其他错误抛出错误信息return Promise.reject(err)}
)
//instance(默认导出) 是你真正用来发请求的 axios 实例
//baseURL 导出整个请求的“基础路径前缀”
export default instance
export { baseURL }
解释 1:为什么是Authorization?
看接口文档,给你的请求头需要什么?
解释 2:为什么在响应拦截器中 res 中写的返回是 return Promise.reject(res.data),err 写的是这个return Promise.reject(err)?
拦截器分支 | 拿到的内容 | 我们关心什么 | 为什么这么写 |
---|---|---|---|
res => {} | HTTP 200 成功返回 | 业务数据的状态码(res.data.code ) | 所以 Promise.reject(res.data) ,把“业务失败信息”抛出去 |
err => {} | 网络错误 / 状态码非 200 | 完整错误对象(包含状态码、错误信息) | 所以 Promise.reject(err) ,保留完整异常信息给调用方分析 |
res 关注的是 code=几,表示是哪里自定义的错误的业务逻辑
解释 3:导出使用
// 使用时
import request, { baseURL } from '@/utils/request'