文章目录
- 技术栈介绍
- 1.包管理工具pnpm的安装
- 2.调整项目目录
- 3.VueRouter4路由语法规则
- [4.引入 element-ui 组件库](#4.引入 element-ui 组件库)
- [5.Pinia - 构建用户仓库 和 持久化](#5.Pinia - 构建用户仓库 和 持久化)
-
- [5.1 基本配置-构建用户仓库和开启持久化](#5.1 基本配置-构建用户仓库和开启持久化)
- [5.2 优化-pinia 独立维护和仓库统一导出](#5.2 优化-pinia 独立维护和仓库统一导出)
-
- [5.2.1 pinia 独立维护](#5.2.1 pinia 独立维护)
- [5.2.2 仓库统一导出](#5.2.2 仓库统一导出)
- [6. 数据交互-请求工具设计](#6. 数据交互-请求工具设计)
技术栈介绍
本项目的技术栈 本项目技术栈基于 ES6、vue3、pinia、vue-router 、vite 、axios 和 element-plus
1.包管理工具pnpm的安装
pnpm优势:比同类工具快 2倍 左右、节省磁盘空间
安装方式:mac 系统要加 sudo 提升为管理员权限
bash
sudo npm install -g pnpm
安装pnpm 版本为:10.12.4
创建项目:
bash
pnpm create vue

项目初始化完成,可执行以下命令:
javascript
cd Vue3-big-event-admin
pnpm install
pnpm dev
2.调整项目目录
默认生成的目录结构不满足我们的开发需求,所以这里需要做一些自定义改动。主要是两个工作:删除文件和修改内容
- 删除初始化的默认文件
- 修改剩余代码内容
- 新增调整我们需要的目录结构
- 拷贝初始化资源文件,安装预处理器插件
router/index.js

删掉stores 中的 js 文件
删掉views 中的.vue 文件
将 assets 文件夹中的内容删除
将main.js 中的导入 css 删除
将 app.vue 清空
javascript
<script setup>
</script>
<template>
<div>我是 App.vue</div>
</template>
<style scoped>
</style>
新建文件夹 api 和 utils
将下面的静态资源放入到 assets 文件夹中
将 main.scss 导入进 main.js 中,需要安装scss 预处理
javascript
pnpm add sass -D
下完之后导入进去
javascript
import '@/assets/main.scss'
3.VueRouter4路由语法规则
默认代码如下:
javascript
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()
代码如下:
javascript
<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/
按照官方文档:
安装:
javascript
pnpm install element-plus
快速开始按需引入:
javascript
pnpm add -D unplugin-vue-components unplugin-auto-import
对照文档中给出的内容进行比对配置:
配置完成后重启项目
javascript
pnpm dev
把 button 改成 el-button 测试效果,补充:默认下 components 下的文件也会被自动注册
javascript
<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/
安装包依赖:
javascript
pnpm add pinia-plugin-persistedstate
将插件添加到你的 pinia 实例中
配置 stores/user.js
javascript
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useStore = defineStore(
'big-user',
() => {
const token = ref('') // 定义 token
const setToken = (newToken) => (token.value = newToken) // 设置 token
const removeToken=()=>{token.value=''}
return { token,setToken,removeToken }
},
{
persist: true,
},
)
在 app.vue 中测试 pinia
javascript
//导入 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

重新启动服务
javascript
pnpm dev
5.2.2 仓库统一导出
-
现在:使用一个仓库 import { useUserStore } from
./stores/user.js
不同仓库路径不一致 -
优化:由 stores/index.js 统一导出,导入路径统一
./stores
,而且仓库维护在 stores/modules 中
随着项目的扩大,将 stores 下面的 js 内容放到了 modules文件夹内
如果想在 app.vue 中导入就要写
javascript
import {useUserStore} from '@/stores/modules/user'
这样太麻烦了,希望改成如下情况,有 stores 下面的 index.js 统一管理
javascript
import {useUserStore,XXXStore} from '@/stores'
在改写 index.js
javascript
import{useUserStore} from './modules/user'
export{useUserStore}
//假设还有一个 store
import{useCountStore} from './modules/user'
export{useCountStore}
这样写还是麻烦,改为更简单的方法
javascript
export * from './modules/user'
export * from './modules/counter'
6. 数据交互-请求工具设计
一开始做项目的时候,确实可以直接用 axios 请求接口数据,不需要配置什么拦截器
但随着项目越来越复杂,你就会发现这样写代码有一堆问题
写在utils/request.js
首先按照 axios
javascript
pnpm add axios
javascript
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
javascript
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
本项目封装:
javascript
//导入 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,见解释 1
if (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:导出使用
javascript
// 使用时
import request, { baseURL } from '@/utils/request'