前端三件套-后端必备

本文最后更新于:4 个月前

1 JS

1.1 JavaScript-导入导出

JS 提供的导入导出机制,可以实现按需导入。
命名导出:

1
2
3
4
5
6
7
8
9
10
11
12
13
//showMessage.js
//简单的展示信息
function simpleMessage(msg){
console.1og(msg)
}
//复杂的展示信息
function complexMessage(msg){
console.log(new Date()+":"+msg)
}
//批量导出一批方法或变量,也可以在方法头导出
export {simpleMessage,complexMessage}
//支持别名导出
//export {simpleMessage as sm,complexMessage as cm}

命名导入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- message.html -->
<body>
<div>
<button id="bn">点我展示信息</button>
</div>
<script type="module">
import {complexMessage} from './showMessage.js'
<!-- import {complexMessage as cm} from './showMessage.js' -->
document.getElementById("btn").onclick function(){
complexMessage('bbbbb');
<!-- cm('aaa') -->
}
</script>
</body>

导入和导出的时候,可以使用 as 重命名


当 js 中的方法过多时可以采用默认导出导入方式。
默认导出(导出一个 js 对象,通过对象的方式调用方法):

1
2
3
4
5
6
7
8
9
10
11
//showMessage.js
//简单的展示信息
function simpleMessage(msg){
console.1og(msg)
}
//复杂的展示信息
function complexMessage(msg){
console.log(new Date()+":"+msg)
}
//默认导出
export default {simpleMessage,complexMessage}

导入默认:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- message.html -->
<body>
<div>
<button id="bn">点我展示信息</button>
</div>
<script type="module">
import messageMethods from './showMessage.js'
messageMethods.simpleMessage('aaa');
messageMethods.complexMessage('bbb');
}
</script>
</body>

1.2 …的用法

  1. 扩展运算符(Spread Operator)

数组展开

1
2
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]

函数调用:

1
2
3
4
5
6
function sum(a, b, c) {
return a + b + c;
}

const numbers = [1, 2, 3];
const result = sum(...numbers); // 6

对象字面量

1
2
3
const obj1 = { x: 1, y: 2 };
const obj2 = { ...obj1, z: 3 }; // { x: 1, y: 2, z: 3 }

  1. 剩余参数(Rest Parameters): 剩余参数允许我们将多个参数收集到一个数组中,可以在函数定义时使用。
1
2
3
4
5
6
7
8
9
10
function sum(...numbers) {
let total = 0;
for (let number of numbers) {
total += number;
}
return total;
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(4, 5, 6, 7)); // 22

在上面的例子中,剩余参数 ...numbers 接收了所有传递给 sum 函数的参数,并将它们作为一个数组存储在 numbers 中。


2 Vue

2.1 快速入门

  1. 准备工作
  • 引入 Vue 模块
  • 创建 Vue 的应用实例
  • 定义元素(div),交给 Vue 控制
  1. 构建用户界面
  • 准备数据
  • 用插值表达式渲染

2.1.1 渐进式用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h1>{{message}}</h1>
<p>点击次数:{{count}}</p>
<p>得分:{{double()}}</p>
<button @click="increment">+</button>
</div>
<!-- 引入vue模块 -->
<script type="module">
import { createApp, ref } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
// 创建一个vue实例
const app = createApp({
// 组合式写法
setup() {
// 定义一个数据
const message = ref('hello world')
const count = ref(0)
// 定义一个方法
const increment = () => {
count.value++
}
// 定义一个计算属性
const double = () => {
return count.value * 2
}
return {
message,
count,
increment,
double
}
}
})
// 挂载到id为app的元素上
app.mount('#app')
</script>
</body>
</html>

2.2 常用指令

指令:HTML 标签上带有 V-前缀的特殊属性,不同的指令具有不同的含义,可以实现不同的功能。
常用指令:

指令 作用
v-for 列表渲染,遍历容器的元素或者对象的属性
v-bind 为 HTML 标签绑定属性值,如设置 href,css 样式等
v-if/v-else-if/v-else 条件性的渲染某元素,判定为 true 时渲染,否侧不渲染
v-show 根据条件展示某元素,区别在于切换的是 display 属性的值
v-model 在表单元素上创建双向数据绑定
v-on 为 HTML 标签绑定事件

2.2.1 v-for

作用:列表渲染,遍历容器的元素或者对象的属性
语法:v-for="(item,ndex)in items"
参数说明:

  • items 为遍历的数组
  • item 为遍历出来的元素
  • index 为索引/下标,从 0 开始;可以省略,省略 index 语法:v-for="item in items"

[!WARNING] 注意
遍历的数组,选择式必须在 data 中定义,组合式直接定义响应式数据;要想让哪个标签循环展示多次,就在哪个标签上使用 V-for 指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="item in list">
{{item}}
</li>
</ul>
<ol>
<li v-for="(item,index) in list">
{{index}}-{{item}}
</li>
</ol>
<table border="1" width="100%" align="center" cellspacing="0" cellpadding="0">
<thead>
<tr style="text-align: center;">
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
</tr>
</thead>
<tbody style="text-align: center;">
<tr v-for="(item,index) in objLs">
<td>{{index}}-{{item.name}}</td>
<td>{{index}}-{{item.age}}</td>
<td>{{index}}-{{item.sex}}</td>
</tr>
</tbody>
</table>
</div>
<script type="module">
import { createApp, ref, reactive } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
const app = createApp({
setup () {
const list = ref(['a', 'b', 'c'])
const objLs = reactive([
{
name: 'ruan',
age: 12,
sex: '🚹'
},
{
name: 'alleyf',
age: 20,
sex: '🚹'
},
{
name: 'wang',
age: 21,
sex: '🚺'
}
])
return {
list,
objLs
}
}
})
app.mount('#app')
</script>
</script>
</body>
</html>

2.2.2 v-bind

作用:动态为 HTML 标签绑定属性值,如设置 href,src,style 样式等。
语法:v-bind:属性名="属性值"
简化::属性名="属性值"

v-bind 所绑定的数据,必须在 data 中定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<div id="app">
<a :href="url" style="text-decoration:solid;color:red;">{{title}}</a>
</div>
<script type="module">
import { createApp, ref, reactive } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
const app = createApp({
setup () {
const url = ref("https://www.baidu.com")
const title = ref("百度一下")
return {
url,
title
}
}
})
app.mount('#app')
</script>
</script>
</body>

2.2.3 v-if/v-show

作用:这两类指令,都是用来控制元素的显示与隐藏的
==v-if==
- 语法:v-if="表达式",表达式值为 true,显示;false,隐藏
- 其它:可以配合 v-else-if/v-else 进行链式调用条件判断
- 原理:基于条件判断,来控制创建或移除元素节点(条件渲染)
- 场景:要么显示,要么不显示,不频繁切换的场景
==v-show==
- 语法:v-show=”表达式”,表达式值为 true,显示;false,隐藏
- 原理:基于 CSS 样式 display 来控制显示与隐藏
- 场景:频繁切换显示隐藏的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<body>
<div id="app">
v-if-手串的价格为:<span v-if="customer.level === 0">¥{{price}}</span>
<span v-else-if="customer.level === 1">¥{{price+20}}</span>
<span v-else>¥{{price+50}}</span><br>
v-show-手串的价格为:<span v-show="customer.level === 0">¥{{price}}</span>
<span v-show="customer.level === 1">¥{{price+20}}</span>
<span v-show="customer.level >= 2">¥{{price+50}}</span>
</div>
<script type="module">
import { createApp, ref, reactive } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
const app = createApp({
setup () {
const price = ref(100)
const customer = reactive({
name: '小王',
age: 18,
level: 1
})
return {
customer,
price
}
}
})
app.mount('#app')
</script>
</script>
</body>

2.2.4 v-on

作用:为 html 标签绑定事件
语法:v-on:事件名="函数名"
简写为:@事件名="函数名"

选择式函数需要定义在 methods 选项内部,组合式直接定义箭头函数并 return 函数名即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<div id="app">
<button type="button" v-on:click="handleClick(1)">按钮</button> &nbsp;
<button type="button" @click="handleClick(2)">按钮</button>
</div>
<script type="module">
import { createApp, ref, reactive } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
const app = createApp({
setup () {
const handleClick = (num) => {
console.log('你点击了按钮' + num)
}
return {
handleClick
}
}
})
app.mount('#app')
</script>
</script>
</body>

2.2.5 v-model

作用:在表单元素上使用,双向数据绑定。可以方便的获取或设置表单项数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<body>
<div id="app">
<form action="#app" method="get">
<input type="text" placeholder="请输入姓名" v-model="queryForm.name" name="name" id="1">
<input type="text" placeholder="请输入年龄" v-model="queryForm.age" name="age" id="2">
<br>
当前输入的姓名:{{queryForm.name}} &nbsp; 当前输入的年龄:{{queryForm.age}}
<br>
<button type="submit">搜索</button>
<button type="reset" @click="resetForm">重置</button>
</form>
<br>
<table border="1" width="100%" align="center" cellspacing="0" cellpadding="0">
<thead>
<tr style="text-align: center;">
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
</tr>
</thead>
<tbody style="text-align: center;">
<tr v-for="(item,index) in objLs">
<td>{{index}}-{{item.name}}</td>
<td>{{index}}-{{item.age}}</td>
<td>{{index}}-{{item.sex}}</td>
</tr>
</tbody>
</table>
</div>
<script type="module">
import { createApp, ref, reactive } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
const app = createApp({
setup () {
const queryForm = ref({
"name": "",
"age": null
})
const resetForm = () => {
queryForm.value = {
"name": "",
"age": null
}
}
const objLs = reactive([
{
name: 'ruan',
age: 12,
sex: '🚹'
},
{
name: 'alleyf',
age: 20,
sex: '🚹'
},
])
return {
queryForm, resetForm, objLs
}
}
})
app.mount('#app')
</script>
</script>
</body>

2.2.6 生命周期

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。
生命周期:指一个对象从创建到销毁的整个过程。
生命周期的八个阶段 :每个阶段会自动执行一个生命周期方法(钩子),让开发者有机会在特定的阶段执行自己的代码

状态 阶段周期
beforeCreate 创建前
beforeMount 载入前
beforeUnmount 组件销毁前
beforeUpdate 数据更新前
created 创建后
mounted 挂载完成
unmounted 组件销毁后
updated 数据更新后
生命周期函数用法示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script type="module">
import { createApp, ref, reactive, onMounted } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
const app = createApp({
setup () {
const mounted = ref(false)
//入参为Lambda表达式,回调函数格式
onMounted(() => {
console.log(`组件挂在完毕`)
})
return {
mounted
}
}
})
app.mount('#app')
</script>

2.2.7 axios 的使用

介绍:Axios 对原生的 Aja 进行了封装,简化书写,快速开发。
官网: https://www.axios-http.cn/
Axios 使用步骤

  • 引入 Axios 的 js 文件(参照官网)
  • 使用 Axios 发送请求,并获取相应结果
    • method:请求方式,GET/POST.
    • urL:请求路径
    • data:请求敛据

Axios-请求方式别名

  • 为了方便起见,Axos 已经为所有支持的请求方法提供了别名
  • 格式:axios.请求方式(urL[,data[,config])
    GET:
    • axios.get(url).then((res)=>{...}).catch((err)=>{....)
      POST:
    • axios.post(url,data).then((res)=>{..]).catch((err)=>{....)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<script type="module">
import { createApp, ref, reactive, onMounted } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
const app = createApp({
setup () {
axios.defaults.baseURL = 'http://127.0.0.1:8080'
axios.defaults.headers.post['Content-Type'] = 'application/json'
axios.defaults.crossDomain = true;
const getAllArticle = () => axios({
//请求参数配置
method: 'get',
url: '/api/article/list',
headers: {
'Authorization': localStorage.getItem('token')
}
}).then(({ data }) => {
// 成功回调函数
console.log(data)
}).catch((err) => {
// 失败回调函数
console.log(err)
})
const login = () => axios({
//请求参数配置
method: 'post',
url: '/api/user/login',
params: {
username: 'admin',
password: '123123'
}
}).then(({ data }) => {
// 成功回调函数
console.log(data)
if (data.code === 200) {
localStorage.setItem('token', data.data)
}
}).catch((err) => {
// 失败回调函数
console.log(err)
})
onMounted(() => {
console.log(`组件挂在完毕`)
login()
console.log(`token:${localStorage.getItem('token')}`)
getAllArticle()
})
return {
login,
getAllArticle
}
}
})
app.mount('#app')
</script>

2.3 局部使用案例

对大事件后端的文章做列表展示和分页搜索功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
<body>
<div id="app">
<input type="text" placeholder="请输入分类id" v-model="queryForm.categoryId" name="categoryId" id="1">
<input type="text" placeholder="请输入发布状态" v-model="queryForm.state" name="state" id="2">
<button type="submit" @click="queryArticle()">搜索</button>
<button type="reset" @click="resetForm()">重置</button>
<br>
<table border="1" width="100%" align="center" cellspacing="0" cellpadding="0">
<thead>
<tr style="text-align: center;">
<th>标题</th>
<th>分类</th>
<th>发表时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody style="text-align: center;">
<tr v-for="(item,index) in objLs">
<td>{{item.title}}</td>
<td>{{item.categoryId}}</td>
<td>{{item.createBy}}</td>
<td>{{item.state}}</td>
<td>
<a href="javascript:void(0)" @click="editArticle(item.id)">编辑</a>
<b> | </b>
<a href="javascript:void(0)" @click="delArticle(item.id)">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
<script type="module">
import { createApp, ref, reactive, onMounted} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
const app = createApp({
setup () {
const queryForm = ref({
"pageNum": 1,
"pageSize": 5,
"categoryId": null,
"state": null
})
const resetForm = () => {
queryForm.value = {
"pageNum": 1,
"pageSize": 5,
"categoryId": null,
"state": null
}
getAllArticle()
}
const objLs = ref([])
axios.defaults.baseURL = 'http://127.0.0.1:8080'
axios.defaults.headers.post['Content-Type'] = 'application/json'
axios.defaults.crossDomain = true;
// 请求函数配置
const getAllArticle = () => axios({
//请求参数配置
method: 'get',
url: '/api/article/list',
// headers: {
// 'Authorization': localStorage.getItem('token')
// }
}).then(({ data }) => {
// 成功回调函数
console.log(data)
objLs.value = data.data
}).catch((err) => {
// 失败回调函数
console.log(err)
})
const login = () => axios({
//请求参数配置
method: 'post',
url: '/api/user/login',
params: {
username: 'admin',
password: '123123'
}
}).then(({ data }) => {
// 成功回调函数
console.log(data)
if (data.code === 200) {
localStorage.setItem('token', data.data)
}
}).catch((err) => {
// 失败回调函数
console.log(err)
})
const queryArticle = () => {
axios({
//请求参数配置
method: 'get',
url: '/api/article/page',
headers: {
'Authorization': localStorage.getItem('token')
},
params: queryForm.value
}).then(({ data }) => {
// 成功回调函数
console.log(data)
objLs.value = data.data.items
}).catch((err) => {
// 失败回调函数
console.log(err)
if (err.response.status === 401) {
alert('登录失效,请重新登录')
}
})
}
const delArticle = (id) => {
axios({
//请求参数配置
method: 'delete',
url: `/api/article/${id}`,
}).then(({ data }) => {
// 成功回调函数
console.log(data)
getAllArticle()
}).catch((err)=>{
// 失败回调函数
console.log(err)
if (err.response.status === 401) {
alert('登录失效,请重新登录')
}
})
}
onMounted(() => {
login()
axios.defaults.headers['Authorization'] = localStorage.getItem('token')
getAllArticle()
})
return {
queryForm, resetForm, objLs, login, getAllArticle, queryArticle
}
}
})
app.mount('#app')
</script>
</script>
</body>

2.4 整站使用 Vue(工程化)

2.4.1 项目初始化

2.4.1.1 环境准备

介绍:create-vue 是 Vue 官方提供的最新的脚手架工具,用于快速生成一个工程化的 Vue 项目。
create-vue 提供了如下功能:

[!tip] 功能

  • 统一的目录结构
  • 本地调试
  • 热部署
  • 单元测试
  • 集成打包

依赖环境:NodeJS


2.4.1.2 Vue 项目-创建

创建一个工程化的 Vue 项目,执行命令:npm init vue@latestnpm create vue@latest

[!NOTE] 新建 Vue 项目选项

  1. Project name: ——》项目名称,默认值:Vu e-project,可输入想要的项目名称。
  2. Add TypeScript?—-》是否加入 TypeScript 组件?默认值:No。
  3. Add JSX Support?———–》是否加入]SX 支持?默认值:No。
  4. Add Vue Router —–》是否为单页应用程序开发添加/ue Router 路由管理组件?默认值:No。
  5. Add Pinia —————》是否添加 inia 组件来进行状态管理?默认值:No。
  6. Add Vitest ————–》是否添加/itest 来进行单元测试?默认值:No。
  7. Add an End-to-End —————-》是否添加端到端测试?默认值 No。
  8. Add ESLint for code quality?————–》是否添加 ESLint 来进行代码质量检查?默认值:No。

2.4.1.3 Vue 项目-安装依赖

进入项目目录,执行命令安装当前项目的依赖:npm install

[!error] 注意
创建项目以及安装依赖的过程,都是需要联网的

2.4.1.4 Vue 项目-启动

执行命令:npm run dev ,就可以启动 vue 项目了。


2.4.2 Vue 项目开发流程

2.4.2.1 目录结构

|250

1
2
3
4
5
6
7
8
9
<script setup>
// 数据和行为
</script>
<template>
<!-- 骨架和布局 -->
</template>
<style scoped>
/* 样式和风格 */
</style>

2.4.3 API 风格

Vue 的组件有两种不同的风格:组合式 API 和选项式 API

[!warning]

  • setup:是一个标识,告诉 Vue 需要进行一些处理,让我们可以更简洁的使用组合式 API。
  • ref ():接收一个内部值,返回一个响应式的 ref 对象,此对象只有一个指向内部值的属性 value。
  • onMounted ():在组合式 API 中的钩子方法,注册一个回调函> 数,在组件挂载完成后执行。

组合式(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup>
import {onMounted,ref} from 'vue';
const count=ref(0);/声明响应式变量
function increment(){/声明函数
count value++;
}
onMounted(()=>{/声明钩子函数
console.log('Vue Mounted .')
})
</script>
<template>
<button @click="increment">count:count</button>
</template>

选项式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
export default {
data(){/声明响应式对象
return {
count: 0
}
},
methods:{/声明方法,可以通过组件实例访问
increment:function(){
this.count++
}
},
mounted(){//声明钩子函数
console.log('Vue mounted ...')
}
}
</script>
<template>
<button @click="increment">count:{count }</button>
</template>

选项式 API,可以用包含多个选项的对象来描述组件的逻辑,如:data,methods,mounted 等。


2.4.4 定制 axios 为 request

2.4.4.1 拦截器

在请求或响应被 then 或 catch 处理前拦截它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 定制请求的实例
// 导入axios
import axios from 'axios'
import {ElNotification} from 'element-plus'
// 定义请求公共前缀,超时时间,跨域,请求头和实例
const baseURL = '';
const instance = axios.create({
baseURL: baseURL,
timeout: 5000,
crossDomain: true,
headers: {
'Authorization': localStorage.getItem('token'),
}
})
// 拦截器是异步的,所以需要返回一个promise
// 添加响应拦截器
instance.interceptors.response.use(
response => {
// 对响应数据做些事
if (response.data.code === 200) {
return response.data // 直接返回包装后的数据,不含响应头等附加信息。
} else {
ElNotification({
title: '系统提示',
message: response.data.message ? response.data.message : '服务异常',
type: 'error',
duration: 3000
});
// 异步操作的状态转换为失败返回
return Promise.reject(response.data);
}
},
error => {
// 对响应错误做些事,异步的状态转化成失败的状态
ElNotification({
title: '系统提示',
message: error.message ? error.message : '服务异常',
type: 'error',
duration: 3000
})
return Promise.reject(error)
}
)
// 添加请求拦截器
instance.interceptors.request.use(
config => {
if (config.method === 'post') {
config.headers['Content-Type'] = 'application/json'
}
// 在发送请求之前做些什么
return config
},
error => {
// 对请求错误做些什么
ElNotification({
title: '系统提示',
message: error.message ? error.message : '请求异常',
type: 'error',
duration: 3000
})
return Promise.reject(error)
}
)
export default instance

2.4.4.2 各种请求定制

不但可以避免使用同步等待 await和async,将回调函数以参数的形式传递给 axios 的回调函数进行执行。还可以自定义各种请求,设置参数和默认回调函数等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import instance from "./request";
//get请求定制
function get (url, params = null, success = defaultSuccess, error = defaultError) {
return instance({
method: 'get',
url: url,
params: params
}).then(res => success(res)).catch(err => error(err));
}
//post请求定制
function post (url, params = null, data = null, success = defaultSuccess, error = defaultError) {
return instance({
method: 'post',
url: url,
params: params,
data: data
}).then(res => success(res)).catch(err => error(err));
}
//默认成功回调
function defaultSuccess (response) {
console.log(response);
}
//默认失败回调
function defaultError (error) {
console.log(error);
}
export default {
get,
post
}

2.5 Element-Plus

Element:是饿了么团队研发的,基于 Vue3,面向设计师和开发者的组件库。
组件:组成网页的部件,例如超链接、按钮、图片、表格、表单、分页条等。
官网: https://element-plus.org/zh-CN/#/Zh-CN


2.5.1 快速入门

  1. 准备工作:
  • 创建一个工程化的 vue 项目
  • 参照官方文档,安装 Element Plus 组件库(在当前工程的目录下):npm install element-plus --save
  • main.js 中引入 Element Plus 组件库(参照官方文档)
    1
    2
    3
    4
    5
    6
    7
    8
    import './assets/main.css'
    import 'element-plus/dist/index.css'
    import { createApp } from 'vue'
    import ElementPlus from 'element-plus'
    import App from './App.vue'
    const app = createApp(App)
    app.use(ElementPlus)
    app.mount('#app')
  1. 制作组件:
    访问 Element 官方文档,复制组件代码,调整参数进行自定义。

2.5.2 常用组件

表格,卡片,表单,按钮,分页,下拉选项框。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<template>
<div>
<div>
<el-card class="box-card">
<div class="card-header">
<span>文章管理</span>
<el-button type="primary" @click="addArticle">发布文章</el-button>
</div>
<hr>
<div>
<el-form :inline="true" :model="queryForm" class="el-form">
<el-form-item label="分类">
<el-select v-model="queryForm.categoryId" placeholder="分类id" clearable>
<el-option label="人文" value="17" />
<el-option label="科学" value="16" />
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryForm.state" placeholder="状态" clearable>
<el-option label="已发布" value="已发布" />
<el-option label="草稿" value="草稿" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="queryArticle" :icon="Search" />
<el-button type="warning" @click="resetForm" :icon="Loading" />
</el-form-item>
</el-form>
</div>
<div>
<el-table :data="objLs" width="100%" row-class-name="success-row">
<el-table-column prop="title" label="标题" />
<el-table-column prop="categoryId" label="分类" />
<el-table-column prop="createBy" label="发表时间" />
<el-table-column prop="state" label="状态" />
<el-table-column label="操作">
<template #default="{ row }">
<el-row>
<!-- <el-col :span="6"> -->
<el-button type="primary" disabled circle :icon="Edit" @click="editArticle(row)" />
<!-- </el-col> -->
<!-- <el-col :span="16"> -->
<el-button type="danger" disabled circle :icon="Delete" @click="deleteArticle(row)" />
<!-- </el-col> -->
</el-row>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="el-pag">
<el-pagination v-model:current-page="queryForm.pageNum" v-model:page-size="queryForm.pageSize"
:page-sizes="[2, 5, 10, 20]" :small="small" :disabled="disabled" :background="background"
layout="total,sizes, prev, pager, next,jumper" :total="total" @size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { Edit, Delete, Search, Loading } from '@element-plus/icons-vue'
import user from '@/api/user'
import article from '@/api/article'
/**
* 分页配置
*/
const small = ref(false)
const background = ref(false)
const disabled = ref(false)
const total = ref(0)
const handleSizeChange = (val) => {
console.log(`${val} items per page`)
}
const handleCurrentChange = (val) => {
console.log(`current page: ${val}`)
}
/**
* 表单配置
*/
const queryForm = ref({
"pageNum": 1,
"pageSize": 5,
"categoryId": null,
"state": null,
})
/**
* 重置表单
*/
const resetForm = () => {
queryForm.value = {
"pageNum": 1,
"pageSize": 2,
"categoryId": null,
"state": null,
}
getAllArticle()
}
/**
* 文章列表
*/
const objLs = ref([
{
"id": 1,
"title": "标题1",
"categoryId": 1,
"createBy": "admin",
"state": 1
},
{
"id": 2,
"title": "标题2",
"categoryId": 2,
"createBy": "admin",
"state": 1
},
])
// 请求函数配置
const getAllArticle = () => {
article.articleGetByPage(queryForm.value, res => {
total.value = res.data.data.total
objLs.value = res.data.data.items
}, err => {
if (err.status === 401) {
alert('登录失效,请重新登录')
}
})
}
const login = () => {
user.login(
{
username: 'admin',
password: '123123'
}
)
}
const queryArticle = () => {
article.articleGetByPage(queryForm.value, res => {
objLs.value = res.data.data.items
}, err => {
if (err.status === 401) {
alert('登录失效,请重新登录')
}
})
}
onMounted(() => {
// login()
getAllArticle()
})
</script>
<style scoped>
.card-header {
margin: 10px 0;
display: flex;
align-items: center;
justify-content: space-between;
}
.el-form {
margin: 20px 0;
}
.el-pag {
margin: 20px 0;
display: flex;
justify-content: flex-end;
}
.el-table .success-row {
margin: 20px;
--el-table-tr-bg-color: var(--el-color-success-light-9);
}
</style>

下拉选项框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<script setup>
// 路由
const router = useRouter();
// 状态
const tokenStore = useTokenStore();
// 用户信息
const userInfoStore = useUserInfoStore();

const logout = () => {
ElMessageBox.confirm('确定要退出登录吗?',
'温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 清空个人信息和token
tokenStore.delToken();
userInfoStore.removeInfo();
// 跳转到登录页面
router.push('/login');
ElMessage.success('退出登录成功');
}).catch(() => {
ElMessage.info('取消退出登录');
});
}

// 获取用户信息并保存到store中
const getUserInfo = () => {
user.userInfoService(res => {
userInfoStore.setInfo(res.data);
}, err => {
console.log(err);
})
}

// 处理下拉选项框
const handleCommand = (command) => {
if (command === 'logout') {
logout();
} else {
router.push("/user/" + command);
}
}
</script>


<template>
<el-dropdown placement="bottom-end" @command="handleCommand">
<span class="el-dropdown__box">
<el-avatar :src="userInfoStore.info.userPic || userInfoStore.defaultAvatar || avatar"/>
<el-icon>
<CaretBottom/>
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu class="el-dropdown-menu--right">
<el-dropdown-item :icon="User" command="info">基本资料</el-dropdown-item>
<el-dropdown-item :icon="Crop" command="avatar">更换头像</el-dropdown-item>
<el-dropdown-item :icon="EditPen" command="resetPsd">重置密码</el-dropdown-item>
<el-dropdown-item :icon="SwitchButton" command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>

el-dropdown-item 标签的 command 属性就是传递给@command回调函数的参数,对参数进行判断进行不同处理。


3 黑马大事件-前端

3.1 环境准备

  1. 创建 Vue 工程
    npm init vue@latest
  2. 安装依赖
  • Element-Plus
    npm install element-plus --save
  • Axios
    npm install axios
  • Sass
    npm install sass -D
  1. 目录调整
  • 删除 components 下面自动生成的内容
  • 新建目录 api、utils、views
  • 将资料中的静态资源拷贝到 assets 目录下
  • 删除 App.vue 中自动生成的内容

3.2 细节要点

3.2.1 表单校验

  • 数据绑定
    参考接口文档给属性起名
  • 表单校验
    el-form 标签上通过 rules 属性,绑定校验规则
    el-form-item 标签上通过 prop 属性,指定校验项
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //自定义字段校验器
    const rePasswordValid = (rule, value, callback) => {
    if (value == null || value === '') {
    return callback(new Error('请再次确认密码'))
    }
    if (registerData.value.password !== value) {
    return callback(new Error('两次输入密码不一致'))
    }
    callback()
    };
    //用于注册的表单校验模型
    const registerDataRules = reactive({
    username: [
    {required: true, message: '请输入用户名', trigger: 'blur'},
    {min: 4, max: 16, message: '用户名的长度必须为4~16位', trigger: 'blur'}
    ],
    password: [
    {required: true, message: '请输入密码', trigger: 'blur'},
    {min: 4, max: 16, message: '密码长度必须为4~16位', trigger: 'blur'}
    ],
    rePassword: [
    {validator: rePasswordValid, trigger: 'blur'}
    ]
    });
  • 请求校验
    对提交的表单数据需要先进行验证数据有效性再发起请求
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    import {User, Lock} from '@element-plus/icons-vue'  
    import {reactive, ref} from 'vue'
    import {ElMessage} from "element-plus";
    import user from '@/api/user'
    //控制注册与登录表单的显示, 默认显示注册
    const isRegister = ref(false);
    // 定义数据模型
    const form = ref()
    const registerData = ref({
    username: '',
    password: '',
    rePassword: ''
    });
    //自定义确认密码的校验器
    const rePasswordValid = (rule, value, callback) => {
    if (value == null || value === '') {
    return callback(new Error('请再次确认密码'))
    }
    if (registerData.value.password !== value) {
    return callback(new Error('两次输入密码不一致'))
    }
    callback()
    };
    //用于注册的表单校验模型
    const registerDataRules = reactive({
    username: [
    {required: true, message: '请输入用户名', trigger: 'blur'},
    {min: 4, max: 16, message: '用户名的长度必须为4~16位', trigger: 'blur'}
    ],
    password: [
    {required: true, message: '请输入密码', trigger: 'blur'},
    {min: 4, max: 16, message: '密码长度必须为4~16位', trigger: 'blur'}
    ],
    rePassword: [
    {validator: rePasswordValid, trigger: 'blur'}
    ]
    });
    // 表单数据提交事件
    const register = () => {
    // 表单校验
    form.value.validate((valid) => {
    if (valid) {
    user.registerService(registerData.value, res => {
    alert(JSON.stringify(res))
    if (res.code === 200) {
    ElMessage.success('注册成功')
    } else {
    ElMessage.error('注册失败:' + res.message)
    }
    }, err => {
    console.log(err)
    ElMessage.error('系统内部发生错误')
    })
    }
    return false
    })
    };
    //表单数据重置事件
    const resetForm = (formEl) => {
    if (!formEl) return
    formEl.resetFields()
    }

3.2.2 跨域处理

由于浏览器的同源策略限制,向不同源(不同协议、不同域名、不同端口)发送 ajax 请求会失败,解决办法一般通过代理实现。
后端接口路径格式为:
eg:

  1. http://localhost:8080/user/register
  2. http://localhost:8080/api/user/register

[!WARNING] 注意

  • 第一种 baseURL 中需要添加 api,并且在代理配置处需要重写请求路径。
  • 第二种 baseURL 中不需要添加 api,因为后端接口自带了 api,也不需要重写请求路径。

request.js 配置:

1
2
3
4
import axios from 'axios';
const baseURL = '/api'; //第一种
const baseURL = ''; //第二种
const instance axios.create({baseURL})

vite.config.js 配置:

1
2
3
4
5
6
7
8
9
10
server: {  
proxy: {
// 配置跨域,获取请求路径中包含了api的接口
'/api': {
target: 'http://localhost:8080', // 后端接口源,代理目标的基础路径
changeOrigin: true, //修改源
// rewrite: (path) => path.replace(/^\/api/, '') //后端自带了api路径,无需重写
}
}
}

3.2.3 未登录统一处理

未登录或者 token 过期等响应异常情况进行统一处理跳转到登录页。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 添加响应拦截器
instance.interceptors.response.use(
response => {
// 对响应数据做些事
// console.log(response.data)
// 异步操作的状态转换为成功返回
if (response.data.code === 200) {
return response.data // 直接返回包装后的数据,不含响应头等附加信息。
} else {
// 异步操作的状态转换为失败返回
ElNotification.error(response.data.message ? response.data.message : '服务异常');
return Promise.reject(response.data);
}
},
error => {
// 判断状态码
const errorMessages = {
401: '未登录',
403: '无权限',
404: '资源不存在',
500: '服务器内部异常',
};
// 根据状态码,返回对应的错误信息
const errorMessage = errorMessages[error.response.status] || '未知错误';
ElNotification.error(errorMessage);
if (error.response.status) {
router.push({path: '/login'})
}
return Promise.reject(error);
}
)

对 token 过期的路由跳转时进行异常处理路由前判断 token 决定是否放行

1
2
3
4
5
6
7
8
9
10
11
12
router.beforeEach((to, from, next) => {
const tokenStore = useTokenStore();
// 直接放行登录和注册页
if (to.path === '/login' || to.path === '/register') return next()
// 判断是否登录含有token,有:放行,没有:跳转到登录页
if (tokenStore.token !== '') {
next()
} else {
ElMessage.error('请先登录')
return router.push('/login')
}
})

3.2.4 一对多关系前端 id 转义

对于响应数据中存在关联id数据需要转化为对应表id行的数据进行可视化展示,采用以下嵌套循环处理的方法进行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const getAllArticle = () => {
// 获取分页数据,数据中的分类是id的形式存在
article.articleGetByPage(queryForm.value, res => {
total.value = res.data.total
articleLs.value = res.data.items
// 处理数据对分类id进行转义为分类名称
for (let i = 0; i < articleLs.value.length; i++) {
for (let j = 0; j < categorys.value.length; j++) {
if (articleLs.value[i].categoryId === categorys.value[j].id) {
articleLs.value[i].categoryName = categorys.value[j].categoryName
}
}
}
}, err => {
if (err.status === 401) {
alert('登录失效,请重新登录')
}
})
}

3.2.5 富文本编辑器

文章内容需要使用到富文本编辑器,这里咱们使用一个开源的富文本编辑器 Quill;除此以外还有tinymce(常用)。
官网地址: https://vueup.github.io/vue-quill/
安装:

1
npm install @vueup/vue-quill@latest --save

导入组件和样式:

1
2
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'

页面长使用quill组件:

1
2
3
4
5
6
<quill-editor
theme="snow"
v-model:content="articleModel.content"
contentType="html"
>
</quill-editor>

样式美化:

1
2
3
4
5
6
.editor {
width: 100%;
:deep(.ql-editor) {
min-height: 200px;
}
}

3.2.6 图片上传

将来当点击+图标,选择本地图片后,el-upload这个组件会自动发送请求,把图片上传到指定的服务器上,而不需要我们自己使用axios发送异步请求,所以需要给el-upload标签添加一些属性,控制请求的发送

[!tip] el-upload属性

  • auto-upload:是否自动上传
  • action: 服务器接口路径
  • name: 上传的文件字段名
  • headers: 设置上传的请求头
  • on-success: 上传成功的回调函数

3.2.7


3.3 路由管理(vue-router)

[!tip] Vue Router

  1. 安装 vue-router npm install vue-router@4
  2. src/router/index.js 中创建路由器,并导出
  3. 在 vue 应用实例中使用 vue-route
  4. 声明 router-view 标签,展示组件内容
  1. 路由关系和路由器定义 index.js:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import {createRouter, createWebHistory} from 'vue-router'  
    // 在component处动态导入组件
    // 定义路由关系
    const routes = [
    {
    path: '/',
    component: () => import("@/views/Layout.vue"),
    },
    {
    path: '/login',
    component: () => import("@/views/Login.vue"),
    }]
    // 创建路由
    const router = createRouter({
    history: createWebHistory(),
    routes: routes
    })
    // 导出路由
    export default router
  2. main.js 入口文件使用路由:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import './assets/main.scss'  
    import 'element-plus/dist/index.css'
    import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
    import {createApp} from 'vue'
    import ElementPlus from 'element-plus'
    import router from '@/router'
    import App from './App.vue'
    const app = createApp(App)
    app.use(ElementPlus, {locale: zhCn})
    app.use(router)
    app.mount('#app')
  3. app.vue 根组件渲染路由:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <script setup>
    // 数据和行为
    </script>
    <template>
    <router-view/>
    </template>
    <style scoped>
    /* 样式和风格 */
    </style>
  4. 路由切换:
    1
    2
    3
    4
    import {useRouter} from "vue-router";
    // 路由
    const router = useRouter();
    router.push({path: '/'})

3.3.1 子路由

配置子路由

  • 定义子路由关系
  • 声明 router-view 标签
  • 为菜单项 el-menu-item 设置 index 属性,设置点击后的路由路径
  1. 子路由配置(包括路径,重定向,组件,子路由等信息):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    // 定义路由关系
    const routes = [
    {
    path: '/',
    redirect: '/article/category',
    component: () => import("@/views/Layout.vue"),
    children: [
    {
    path: 'article/category', component: () => import("@/views/article/ArticleCategory.vue"),
    },
    {
    path: 'article/manage',
    component: () => import("@/views/article/ArticleManage.vue"),
    },
    {
    path: 'user/',
    redirect: '/user/info',
    children: [
    {
    name: 'userInfo',
    path: 'info',
    component: () => import("@/views/user/UserInfo.vue"),
    },
    {
    path: 'avatar',
    component: () => import("@/views/user/UserAvatar.vue"),
    },
    {
    path: 'resetPsd',
    component: () => import("@/views/user/UserResetPassword.vue"),
    }
    ]
    },
    ]
    },
    {
    path: '/login',
    component: () => import("@/views/Login.vue"),
    }]
  2. 菜单设置 index 属性,值为路由地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <el-menu active-text-color="#ffd04b" background-color="#232323" router
    text-color="#fff">
    <el-menu-item index="/article/category">
    <el-icon>
    <Management/>
    </el-icon>
    <span>文章分类</span>
    </el-menu-item>
    <el-menu-item index="/article/manage">
    <el-icon>
    <Promotion/>
    </el-icon>
    <span>文章管理</span>
    </el-menu-item>
    <el-sub-menu index="/user">
    <template #title>
    <el-icon>
    <UserFilled/>
    </el-icon>
    <span>个人中心</span>
    </template>
    <el-menu-item index="/user/info">
    <el-icon>
    <User/>
    </el-icon>
    <span>基本资料</span>
    </el-menu-item>
    <el-menu-item index="/user/avatar">
    <el-icon>
    <Crop/>
    </el-icon>
    <span>更换头像</span>
    </el-menu-item>
    <el-menu-item index="/user/resetPsd">
    <el-icon>
    <EditPen/>
    </el-icon>
    <span>重置密码</span>
    </el-menu-item>
    </el-sub-menu>
    </el-menu>
  3. 在需要显示路由组件的区域放置 router-view 标签
1
2
3
4
5
6
<!-- 中间区域 -->
<el-main>
<div style="width: 1290px; height: 570px;border: 1px solid red;">
<router-view/>
</div>
</el-main>

3.4 状态管理(Pinia)

Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态
|500

[!tip] pinia 用法

  1. 安装 pinia, npm install pinia
  2. vue 应用实例中使用 pinia
  3. src/stores/token.js定义 store
  4. 组件中使用 store

|500
用法详情:
1. 将 pinia 挂载到 app 中:
1
2
3
4
5
6
7
import {createApp} from 'vue'  
import {createPinia} from "pinia";
import App from './App.vue'
const app = createApp(App)
const pinia = createPinia();
app.use(pinia)
app.mount('#app')

2. 在 store 文件夹下定义 store:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//导入pinia
import {defineStore} from "pinia";
import {ref} from "vue";
/**
* 定义状态
* 第一个参数:名字,唯一性
* 第二个参数:函数,函数的内部可以定义状态的所有内容
* 返回值:函数
* @type {StoreDefinition<"token", {token: string}, {}, {setToken(*): void}>}
*/
// export const useTokenStore = defineStore('token', {
// // 定义状态,可以设置默认初值
// state: () => ({token: ''}),
// // 定义对状态的操作(修改,删除,获取···)
// actions: {
// setToken(token) {
// this.token = token
// },
// delToken() {
// this.token = ''
// }
// }
// })
export const useTokenStore = defineStore('token', () => {
// 1.定义状态,可以设置默认初值
const token = ref('')
// 2.定义对状态的操作(修改,删除,获取···)
// 2.1修改token
const setToken = (newToken) => {
token.value = newToken
}
// 2.2删除token
const delToken = () => {
token.value = ''
}
return {
token, setToken, delToken
}
}
)

3. 使用定义的 store:
1
2
3
4
5
import {useTokenStore} from "@/stores/token.js";
const tokenStore = useTokenStore();
tokenStore.token //获取token
tokenStore.setToken(newToken) //修改token
tokenStore.delToken() //删除/重置 token

### 3.4.1 axios 请求拦截器
在 axios 的请求前拦截器中统一在请求头添加 token 便于后续接口认证。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 添加请求拦截器
instance.interceptors.request.use(
config => {
// 在发送请求之前做些什么
let tokenStore = useTokenStore();
console.log("token:" + tokenStore.token)
if ((config.url !== '/user/login' || config.url !== '/user/register') && tokenStore.token) {
config.headers.Authorization = tokenStore.token
}
if (config.method === 'post') {
config.headers['Content-Type'] = 'application/json'
}
return config
},
error => {
// 对请求错误做些什么
ElNotification.error(error.message ? error.message : '请求异常');
Promise.reject(error)
}
)

3.4.2 Pinia 持久化插件-persist

Pinia 默认是内存存储,当刷新浏览器的时候会丢失数据
Persist 插件可以将 pinia 中的数据持久化的存储

[!tip] 插件使用

  1. persist:npm install pinia-persistedstate-plugin
  2. 在 piniat 中使用 persist
  3. 定义状态 Store 时指定持久化配置参数
    具体使用详情:
  1. 导入持久化插件,pinia 使用该插件:
    1
    2
    3
    4
    import {createPinia} from "pinia";  
    const persist = createPersistedState()
    pinia.use(persist)
    app.use(pinia)
  2. 配置 defineStore 的配置项开启持久化:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    export const useTokenStore = defineStore('token', () => {
    // 1.定义状态,可以设置默认初值
    const token = ref('')
    // 2.定义对状态的操作(修改,删除,获取···)
    // 2.1 修改 token
    const setToken = (newToken) => {
    token.value = newToken
    }
    // 2.2 删除 token
    const delToken = () => {
    token.value = ''
    }
    return {
    token, setToken, delToken
    }
    },
    {
    persist: true //开启状态持久化
    }
    )

3.5 成果展示

Image 1 Image 2

Image 1 Image 2

Image 1 Image 2

Image 1 Image 2


4 参考

  1. 实战篇-38_vue指令_v-bind_哔哩哔哩_bilibili
  2. Java程序员用学前端么?java开发所需的前端技术全教程(HTML/CSS/js/vue2/vue3/react)_哔哩哔哩_bilibili
  3. 快速上手 | Vue.js
  4. javascript中…的用法_js中…-CSDN博客

前端三件套-后端必备
https://alleyf.github.io/2024/01/e99038e3aa4f.html
作者
fcs
发布于
2024年1月1日
更新于
2024年1月7日
许可协议