一、前端工程化开篇

1.1 什么是前端工程化

**前端工程化是使用软件工程的方法单独解决前端的开发流程中模块化、组件化、规范化、自动化**的问题,以提高效率和降低成本。

1.2 前端工程化实现技术栈

前端工程化实现的技术栈有很多,我们采用ES6+Nodejs+npm+Vite+VUE3+Router+Pinia+Axios+Element-plus组合来实现。

  • ECMAScript6 VUE3中大量使用ES6语法;
  • Nodejs 前端项目运行环境;
  • npm 依赖下载工具;
  • Vite 前端项目构建工具;
  • VUE3 优秀的渐进式前端框架;
  • Router 通过路由实现页面切换;
  • Pinia 通过状态管理实现组件数据传递;
  • Axios ajax异步请求封装技术实现前后端数据交互;
  • Element-plus 可以提供丰富的快速构建网页的组件仓库;

二、ECMAScript 6

2.1. ES6的介绍

ECMAScript 6,简称ES6,是JavaScript语言的一次重大更新。它于2015年发布,是原来的ECMAScript标准的第六个版本。ES6带来了大量的新特性,包括箭头函数、模板字符串、let和const关键字、解构、默认参数值、模块系统等等,大大提升了JavaScript的开发体验。由于VUE3中大量使用了ES6的语法,所以ES6成为了学习VUE3的门槛之一。ES6对JavaScript的改进在以下几个方面:

  1. 更加简洁:ES6引入了一些新的语法,如箭头函数、类和模板字符串等,使代码更加简洁易懂;
  2. 更强大的功能:ES6引入了一些新的API、解构语法和迭代器等功能,从而使得JavaScript更加强大;
  3. 更好的适用性:ES6引入的模块化功能为JavaScript代码的组织和管理提供了更好的方式,不仅提高了程序的可维护性,还让JavaScript更方便地应用于大型的应用程序;

总的来说,ES6在提高JavaScript的核心语言特性和功能方面取得了很大的进展。它的大多数新特性都已被现在浏览器所支持

历史版本:

标准版本 发布时间 新特性
ES1 1997年 第一版 ECMAScript
ES2 1998年 引入setter和getter函数,增加了try/catch,switch语句允许字符串
ES3 1999年 引入了正则表达式和更好的字符串处理
ES4 取消 取消,部分特性被ES3.1和ES5继承
ES5 2009年 Object.defineProperty,JSON,严格模式,数组新增方法等
ES5.1 2011年 对ES5做了一些勘误和例行修订
ES6 2015年 箭头函数、模板字符串、解构、let和const关键字、类、模块系统等
ES2016 2016年 数组.includes,指数操作符(**),Array.prototype.fill等
ES2017 2017年 异步函数async/await,Object.values/Object.entries,字符串填充
ES2018 2018年 正则表达式命名捕获组,几个有用的对象方法,异步迭代器等
ES2019 2019年 Array.prototype.{flat,flatMap},Object.fromEntries等
ES2020 2020年 BigInt、动态导入、可选链操作符、空位合并操作符
ES2021 2021年 String.prototype.replaceAll,逻辑赋值运算符,Promise.any等
… …

2.2 es6的变量和模板字符串

ES6 新增了letconst,用来声明变量,使用的细节上也存在诸多差异。

  • let 和var的差别:

    1、let 不能重复声明;

    2、let有块级作用域,只能在花括号里面访问;

    3、let在es6中推荐优先使用;

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
<script>
//1. let只有在当前代码块有效代码块. 代码块、函数、全局
{
let a = 1
var b = 2
}
console.log(a); // a is not defined 花括号外面无法访问
console.log(b); // 可以正常输出
//2. 不能重复声明
let name = '天真'
let name = '无邪'
//3. 不存在变量提升(先声明,在使用)
console.log(test) //可以 但是值为undefined
var test = 'test'
console.log(test1) //不可以 let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
let test1 = 'test1'
//4、不会成为window的属性
var a = 100
console.log(window.a) //100
let b = 200
console.log(window.b) //undefined
//5. 循环中推荐使用
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i);
</script>
  • const和var的差异:

    1、新增const和let类似,只是const定义的变量不能修改;

    2、并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动;

1
2
3
4
5
6
7
8
9
10
<script>
//1声明常量
const PI = 3.1415926;
PI=3.14 //报错
//2.对应数组和对象元素修改,不算常量修改,修改值,不修改地址
const TEAM = ['刘德华','张学友','郭富城'];
TEAM.push('黎明');
TEAM=[] // 报错
console.log(TEAM)
</script>

模板字符串(template string)是增强版的字符串,用反引号(`)标识 。

1、字符串中可以出现换行符;

2、可以使用 ${xxx} 形式输出变量和拼接变量;

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
<script>
// 1 多行普通字符串
let ulStr =
'<ul>'+
'<li>JAVA</li>'+
'<li>html</li>'+
'<li>VUE</li>'+
'</ul>'
console.log(ulStr)
// 2 多行模板字符串
let ulStr2 = `
<ul>
<li>JAVA</li>
<li>html</li>
<li>VUE</li>
</ul>`
console.log(ulStr2)
// 3 普通字符串拼接
let name ='张小明'
let infoStr =name+'被评为本年级优秀学员'
console.log(infoStr)
// 4 模板字符串拼接
let infoStr2 =`${name}被评为本年级优秀学员`
console.log(infoStr2)
</script>

2.3 es6的解构表达式

ES6 的解构赋值是一种方便的语法,可以快速将数组或对象中的值拆分并赋值给变量。解构赋值的语法使用花括号 {} 表示对象,方括号 [] 表示数组。通过解构赋值,函数更方便进行参数接受等!

数组解构赋值

1
2
let [a, b, c, d = 4] = [1, 2, 3];
console.log(a,b,c,d); // 4
  • 该语句将数组 [1, 2, 3] 中的第一个值赋值给 a 变量,第二个值赋值给 b 变量,第三个值赋值给 c 变量。可以使用默认值为变量提供备选值,在数组中缺失对应位置的值时使用该默认值。

对象解构赋值

1
2
3
4
let {a, b} = {a: 1, b: 2};
//新增变量名必须和属性名相同,本质是初始化变量的值为对象中同名属性的值
//等价于 let a = 对象.a let b = 对象.b
console.log(a,b);
  • 该语句将对象 {a: 1, b: 2} 中的 a 属性值赋值给 a 变量,b 属性值赋值给 b 变量。可以为标识符分配不同的变量名称,使用 : 操作符指定新的变量名。例如:
1
2
let {a: x, b: y} = {a: 1, b: 2};
console.log(x,y);

函数参数解构赋值

  • 解构赋值也可以用于函数参数,例如:
1
2
3
4
function add([x, y]) {
return x + y;
}
add([1, 2]);
  • 该函数接受一个数组作为参数,将其中的第一个值赋给 x,第二个值赋给 y,然后返回它们的和;

  • ES6 解构赋值让变量的初始化更加简单和便捷。通过解构赋值,我们可以访问到对象中的属性,并将其赋值给对应的变量,从而提高代码的可读性和可维护性;

2.4 es6的箭头函数

ES6 允许使用“箭头” 义函数。语法类似Java中的Lambda表达式。

2.4.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
<script>
//ES6 允许使用“箭头”(=>)定义函数。
//1. 函数声明
let fn1 = function(){}
let fn2 = ()=>{} //箭头函数,此处不需要书写function关键字
let fn3 = x =>{} //单参数可以省略(),多参数无参数不可以!
let fn4 = x => console.log(x) //只有一行方法体可以省略{};
let fun5 = x => x + 1 //当函数体只有一句返回值时,可以省略花括号和 return 语句
//2. 使用特点 箭头函数this关键字
// 在 JavaScript 中,this 关键字通常用来引用函数所在的对象,
// 或者在函数本身作为构造函数时,来引用新对象的实例。
// 但是在箭头函数中,this 的含义与常规函数定义中的含义不同,
// 并且是由箭头函数定义时的上下文来决定的,而不是由函数调用时的上下文来决定的。
// 箭头函数没有自己的this,this指向的是外层上下文环境的this
let person ={
name:"张三",
showName:function (){
console.log(this) // 这里的this是person
console.log(this.name)
},
viewName: () =>{
console.log(this) // 这里的this是window
console.log(this.name)
}
}
person.showName()
person.viewName()

//this应用
function Counter() {
this.count = 0;
setInterval(() => {
// 这里的 this 是上一层作用域中的 this,即 Counter实例化对象
this.count++;
console.log(this.count);
}, 1000);
}
let counter = new Counter();
</script>

2.4.3 rest和spread

rest参数,在形参上使用 ,和JAVA中的可变参数几乎一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
// 1 参数列表中多个普通参数 普通函数和箭头函数中都支持
let fun1 = function (a,b,c,d=10){console.log(a,b,c,d)}
let fun2 = (a,b,c,d=10) =>{console.log(a,b,c,d)}
fun1(1,2,3)
fun2(1,2,3,4)
// 2 ...作为参数列表,称之为rest参数 普通函数和箭头函数中都支持 ,因为箭头函数中无法使用arguments,rest是一种解决方案
let fun3 = function (...args){console.log(args)}
let fun4 = (...args) =>{console.log(args)}
fun3(1,2,3)
fun4(1,2,3,4)
// rest参数在一个参数列表中的最后一个只,这也就无形之中要求一个参数列表中只能有一个rest参数
//let fun5 = (...args,...args2) =>{} // 这里报错
</script>

spread(扩展)参数,在实参上使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
let arr =[1,2,3]
//let arrSpread = ...arr;// 这样不可以,...arr必须在调用方法时作为实参使用
let fun1 =(a,b,c) =>{
console.log(a,b,c)
}
// 调用方法时,对arr进行转换 转换为1,2,3
fun1(...arr)
//应用场景1 合并数组
let arr2=[4,5,6]
let arr3=[...arr,...arr2]
console.log(arr3)
//应用场景2 合并对象属性
let p1={name:"张三"}
let p2={age:10}
let p3={gender:"boy"}
let person ={...p1,...p2,...p3}
console.log(person)
</script>

2.5 es6的对象创建和拷贝

2.5.1 对象创建的语法糖

ES6中新增了对象创建的语法糖,支持了class extends constructor等关键字,让ES6的语法和面向对象的语法更加接近。

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
<script>
class Person{
// 属性
#n;
age;
get name(){
return this.#n;
}
set name(n){
this.#n =n;
}
// 实例方法
eat(food){
console.log(this.age+"岁的"+this.#n +"用筷子吃"+food)
}
// 静态方法
static sum(a,b){
return a+b;
}
// 构造器
constructor(name,age){
this.#n=name;
this.age = age;
}
}
let person =new Person("张三",10);
// 访问对象属性
// 调用对象方法
console.log(person.name)
console.log(person.#n ) // 私有 报错
person.name="小明"
console.log(person.age)
person.eat("火锅")
console.log(Person.sum(1,2))
class Student extends Person{
grade ;
score ;
study(){
}
constructor(name,age ) {
super(name,age);
}
}
let stu =new Student("学生小李",18);
stu.eat("面条")
</script>

2.5.2 对象的深拷贝和浅拷贝

对象的拷贝,快速获得一个和已有对象相同的对象的方式:

  • 浅拷贝:
1
2
3
4
5
6
7
8
9
10
11
<script>
let arr =['java','c','python']
let person ={
name:'张三',
language:arr
}
// 浅拷贝,person2和person指向相同的内存
let person2 = person;
person2.name="小黑"
console.log(person.name)
</script>
  • 深拷贝:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
let arr =['java','c','python']
let person ={
name:'张三',
language:arr
}
// 深拷贝,通过JSON和字符串的转换形成一个新的对象
let person2 = JSON.parse(JSON.stringify(person))
person2.name="小黑"
console.log(person.name)
console.log(person2.name)


//3. spread语法 解构赋值的
let person3 = {...person}
person3.name = "小刚"
console.log(person.name)
console.log(person2.name)
</script>

2.6 es6的模块化处理

2.6.1模块化介绍

模块化是一种组织和管理前端代码的方式,将代码拆分成小的模块单元,使得代码更易于维护、扩展和复用。它包括了定义、导出、导入以及管理模块的方法和规范。前端模块化的主要优势如下:

  1. 提高代码可维护性:通过将代码拆分为小的模块单元,使得代码结构更为清晰,可读性更高,便于开发者阅读和维护;
  2. 提高代码可复用性:通过将重复使用的代码变成可复用的模块,减少代码重复率,降低开发成本;
  3. 提高代码可扩展性:通过模块化来实现代码的松耦合,便于更改和替换模块,从而方便地扩展功能;

目前,前端模块化有多种规范和实现,包括 CommonJS、AMD 和 ES6 模块化。ES6 模块化是 JavaScript 语言的模块标准,使用 import 和 export 关键字来实现模块的导入和导出。现在,大部分浏览器都已经原生支持 ES6 模块化,因此它成为了最为广泛使用的前端模块化标准.。

  • ES6模块化的几种暴露和导入方式:
    1. 分别导出;
    2. 统一导出;
    3. 默认导出;
  • ES6中无论以何种方式导出,导出的都是一个对象,导出的内容都可以理解为是向这个对象中添加属性或者方法!!!

2.6.2 分别导出

1684461046181

  • module.js 向外分别暴露成员:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//1.分别暴露
// 模块想对外导出,添加export关键字即可!
// 导出一个变量
export const PI = 3.14
// 导出一个函数
export function sum(a, b) {
return a + b;
}
// 导出一个类
export class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old.`);
}
}
  • app.js 导入module.js中的成员:
1
2
3
4
5
6
7
8
9
10
11
12
13
/* 
*代表module.js中的所有成员
m1代表所有成员所属的对象
*/
import * as m1 from './module.js'
// 使用暴露的属性
console.log(m1.PI)
// 调用暴露的方法
let result =m1.sum(10,20)
console.log(result)
// 使用暴露的Person类
let person =new m1.Person('张三',10)
person.sayHello()
  • index.html作为程序启动的入口 ,导入 app.js :
    • type=’module’
1
2
<!-- 导入JS文件 添加type='module' 属性,否则不支持ES6的模块化 -->
<script src="./app.js" type="module" />

2.6.3 统一导出

1684461701620

  • module.js向外统一导出成员:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//2.统一暴露
// 模块想对外导出,export统一暴露想暴露的内容!
// 定义一个常量
const PI = 3.14
// 定义一个函数
function sum(a, b) {
return a + b;
}
// 定义一个类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old.`);
}
}
// 统一对外导出(暴露)
export {
PI,
sum,
Person
}
  • app.js导入module.js中的成员:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 
{}中导入要使用的来自于module.js中的成员
{}中导入的名称要和module.js中导出的一致,也可以在此处起别名
{}中如果定义了别名,那么在当前模块中就只能使用别名
{}中导入成员的顺序可以不是暴露的顺序
一个模块中可以同时有多个import
多个import可以导入多个不同的模块,也可以是同一个模块
*/
//import {PI ,Person ,sum } from './module.js'
//import {PI as pi,Person as People,sum as add} from './module.js'
import {PI ,Person ,sum,PI as pi,Person as People,sum as add} from './module.js'
// 使用暴露的属性
console.log(PI)
console.log(pi)
// 调用暴露的方法
let result1 =sum(10,20)
console.log(result1)
let result2 =add(10,20)
console.log(result2)
// 使用暴露的Person类
let person1 =new Person('张三',10)
person1.sayHello()
let person2 =new People('李四',11)
person2.sayHello()

2.6.4 默认导出

1684463528680

  • modules混合向外导出:
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
// 3默认和混合暴露
/*
默认暴露语法 export default sum
默认暴露相当于是在暴露的对象中增加了一个名字为default的属性
三种暴露方式可以在一个module中混合使用
*/
export const PI = 3.14
// 导出一个函数
function sum(a, b) {
return a + b;
}
// 导出一个类
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old.`);
}
}
// 导出默认
export default sum
// 统一导出
export {
Person
}
  • app.js 的default和其他导入写法混用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 
*代表module.js中的所有成员
m1代表所有成员所属的对象
*/
import * as m1 from './module.js'
import {default as add} from './module.js' // 用的少
import add2 from './module.js' // 等效于 import {default as add2} from './module.js'
// 调用暴露的方法
let result =m1.default(10,20)
console.log(result)
let result2 =add(10,20)
console.log(result2)
let result3 =add2(10,20)
console.log(result3)

// 引入其他方式暴露的内容
import {PI,Person} from './module.js'
// 使用暴露的Person类
let person =new Person('张三',10)
person.sayHello()
// 使用暴露的属性
console.log(PI)

三、前端工程化环境搭建

3.1 Nodejs的简介和安装

3.1.1 什么是Nodejs

1684487715655

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,可以使 JavaScript 运行在服务器端。使用 Node.js,可以方便地开发服务器端应用程序,如 Web 应用、API、后端服务,还可以通过 Node.js 构建命令行工具等。相比于传统的服务器端语言(如 PHP、Java、Python 等),Node.js 具有以下特点:

  • 单线程,但是采用了事件驱动、异步 I/O 模型,可以处理高并发请求;
  • 轻量级,使用 C++ 编写的 V8 引擎让 Node.js 的运行速度很快;
  • 模块化,Node.js 内置了大量模块,同时也可以通过第三方模块扩展功能;
  • 跨平台,可以在 Windows、Linux、Mac 等多种平台下运行;

Node.js 的核心是其管理事件和异步 I/O 的能力。Node.js 的异步 I/O 使其能够处理大量并发请求,并且能够避免在等待 I/O 资源时造成的阻塞。此外,Node.js 还拥有高性能网络库和文件系统库,可用于搭建 WebSocket 服务器、上传文件等。在 Node.js 中,我们可以使用 JavaScript 来编写服务器端程序,这也使得前端开发人员可以利用自己已经熟悉的技能来开发服务器端程序,同时也让 JavaScript 成为一种全栈语言。Node.js 受到了广泛的应用,包括了大型企业级应用、云计算、物联网、游戏开发等领域。常用的 Node.js 框架包括 Express、Koa、Egg.js 等,它们能够显著提高开发效率和代码质量。

3.1.2 如何安装Nodejs

  1. 打开官网https://nodejs.org/en下载对应操作系统的 LTS 版本。
  2. 双击安装包进行安装,安装过程中遵循默认选项即可(或者参照https://www.runoob.com/nodejs/nodejs-install-setup.html )。安装完成后,可以在命令行终端输入 node -vnpm -v 查看 Node.js 和 npm 的版本号。
1687765256680
  1. 定义一个app.js文件,文件内定义如下代码。cmd到该文件所在目录,然后在dos上通过node app.js命令即可运行。
1
2
3
4
5
6
7
function sum(a,b){
return a+b;
}
function main(){
console.log(sum(10,20))
}
main()

3.2 npm 配置和使用

3.2.1 npm介绍

1684487779164

NPM全称Node Package Manager,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于后端的Maven的部分功能 。

3.2.2 npm 安装和配置

1、安装 :安装Nodejs,自动安装npm包管理工具!

2、配置依赖下载使用阿里镜像:

  • npm 安装依赖包时默认使用的是官方源,由于国内网络环境的原因,有时会出现下载速度过慢的情况。为了解决这个问题,可以配置使用阿里镜像来加速 npm 的下载速度,打开命令行终端,执行以下命令,配置使用阿里镜像:
1
npm config set registry https://registry.npmmirror.com
  • 验证配置已,查看当前 registry 的配置:如果输出结果为 https://registry.npmmirror.com,说明配置已成功生效。
1
npm config get registry
  • 如果需要恢复默认的官方源,可以执行以下命令:
1
npm config set registry https://registry.npmjs.org/

3、配置全局依赖下载后存储位置:

  • 在 Windows 系统上,npm 的全局依赖默认安装在 <用户目录>\AppData\Roaming\npm 目录下。

  • 如果需要修改全局依赖的安装路径,可以按照以下步骤操作:

    1. 创建一个新的全局依赖存储目录,例如 D:\GlobalNodeModules

    2. 打开命令行终端,执行以下命令来配置新的全局依赖存储路径:

      1
      npm config set prefix "D:\GlobalNodeModules"
    3. 确认配置已生效,可以使用以下命令查看当前的全局依赖存储路径:

      1
      npm config get prefix

4、升级npm版本:

  • cmd 输入npm -v 查看版本,如果node中自带的npm版本过低!则需要升级至9.6.6!
1
npm install -g npm@9.6.6

3.2.3 npm 常用命令

1、项目初始化:

  • npm init
    • 进入一个vscode创建好的项目中,执行 npm init 命令后,npm 会引导您在命令行界面上回答一些问题,例如项目名称、版本号、作者、许可证等信息,并最终生成一个package.json 文件。package.json信息会包含项目基本信息!类似maven的pom.xml。
  • npm init -y
    • 执行,-y yes的意思,所有信息使用当前文件夹的默认值!不用挨个填写!

2、安装依赖 (查看所有依赖地址 https://www.npmjs.com ):

  • npm install 包名 或者 npm install 包名@版本号
    • 安装包或者指定版本的依赖包(安装到当前项目中)。
  • npm install -g 包名
    • 安装全局依赖包(安装到d:/GlobalNodeModules)则可以在任何项目中使用它,而无需在每个项目中独立安装该包。
  • npm install
    • 安装package.json中的所有记录的依赖。

3、升级依赖:

  • npm update 包名
    • 将依赖升级到最新版本。

4、卸载依赖:

  • npm uninstall 包名

5、查看依赖:

  • npm ls

    • 查看项目依赖。
  • npm list -g

    • 查看全局依赖。

6、运行命令:

  • npm run 命令是在执行 npm 脚本时使用的命令。npm 脚本是一组在 package.json 文件中定义的可执行命令。npm 脚本可用于启动应用程序,运行测试,生成文档等,还可以自定义命令以及配置需要运行的脚本。

  • 在 package.json 文件中,scripts 字段是一个对象,其中包含一组键值对,键是要运行的脚本的名称,值是要执行的命令。例如,以下是一个简单的 package.json 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"start": "node index.js",
"test": "jest",
"build": "webpack"
},
"dependencies": {
"express": "^4.17.1",
"jest": "^27.1.0",
"webpack": "^5.39.0"
}
}
  • scripts 对象包含 start、test 和 build 三个脚本。当您运行 npm run start 时,将运行 node index.js,并启动应用程序。同样,运行 npm run test 时,将运行 Jest 测试套件,而 npm run build 将运行 webpack 命令以生成最终的构建输出。
  • 总之,npm run 命令为您提供了一种在 package.json 文件中定义和管理一组指令的方法,可以在项目中快速且灵活地运行各种操作。

四、Vue3简介和快速体验

4.1 Vue3介绍

1684487637025

Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。官网为:https://cn.vuejs.org/

Vue的两个核心功能:

  • 声明式渲染:Vue 基于标准 HTML 拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系;
  • 响应性:Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM ;

VUE作者:尤雨溪

1684487839928
  • 尤雨溪(Evan You),毕业于科尔盖特大学,前端框架Vue.js的作者、HTML5版Clear的打造人、独立开源开发者。曾就职于Google Creative Labs和Meteor Development Group。由于工作中大量接触开源的JavaScript项目,最后自己也走上了开源之路,现全职开发和维护Vue.js;

  • 尤雨溪毕业于上海复旦附中,在美国完成大学学业,本科毕业于Colgate University,后在Parsons设计学院获得Design & Technology艺术硕士学位,任职于纽约Google Creative Lab;

  • 尤雨溪大学专业并非是计算机专业,在大学期间他学习专业是室内艺术和艺术史,后来读了美术设计和技术的硕士,正是在读硕士期间,他偶然接触到了JavaScript,从此被这门编程语言深深吸引,开启了自己的前端生涯;

4.2 Vue3快速体验(非工程化方式)

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 这里也可以用浏览器打开连接,然后将获得的文本单独保存进入一个vue.js的文件,导入vue.js文件即可 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<div id="app">
<!-- 给style属性绑定colorStyle数据 -->
<!-- {{插值表达式 直接将数据放在该位置}} -->
<h1 v-bind:style="colorStyle">{{headline}}</h1>
<!-- v-text设置双标签中的文本 -->
<p v-text="article"></p>
<!-- 给type属性绑定inputType数据 -->
<input v-bind:type ="inputType" value="helloVue3"> <br>
<!-- 给按钮单击事件绑定函数 -->
<button @click="sayHello()">hello</button>
</div>
<script>
//组合api
const app = Vue.createApp({
// 在setup内部自由声明数据和方法即可!最终返回!
setup(){
//定义数据
//在VUE中实现DOM的思路是: 通过修改修数据而影响页面元素
// vue3中,数据默认不是响应式的,需要加ref或者reactive处理,后面会详细讲解
let inputType ='text'
let headline ='hello vue3'
let article ='vue is awesome'
let colorStyle ={'color':'red'}
// 定义函数
let sayHello =()=>{
alert("hello Vue")
}
//在setup函数中,return返回的数据和函数可以在html使用
return {
inputType,
headline,
article,
colorStyle,
sayHello
}
}
});
//挂载到视图
app.mount("#app");
</script>
</body>
</html>

五、Vue3通过Vite实现工程化

5.1 Vite的介绍

1684488376469 1684488405011

在浏览器支持 ES 模块之前,JavaScript 并没有提供原生机制让开发者以模块化的方式进行开发。这也正是我们对 “打包” 这个概念熟悉的原因:使用工具抓取、处理并将我们的源码模块串联成可以在浏览器中运行的文件。时过境迁,我们见证了诸如 webpackRollupParcel 等工具的变迁,它们极大地改善了前端开发者的开发体验。

  • 当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长;
  • 包含数千个模块的大型项目相当普遍。基于 JavaScript 开发的工具就会开始遇到性能瓶颈:通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感;

Vite 旨在利用生态系统中的新进展解决上述问题:浏览器开始原生支持 ES 模块,且越来越多 JavaScript 工具使用编译型语言编写。https://cn.vitejs.dev/guide/why.html。前端工程化的作用包括但不限于以下几个方面:

  1. 快速创建项目:使用脚手架可以快速搭建项目基本框架,避免从零开始搭建项目的重复劳动和繁琐操作,从而节省时间和精力;
  2. 统一的工程化规范:前端脚手架可以预设项目目录结构、代码规范、git提交规范等统一的工程化规范,让不同开发者在同一个项目上编写出风格一致的代码,提高协作效率和质量;
  3. 代码模板和组件库:前端脚手架可以包含一些常用的代码模板和组件库,使开发者在实现常见功能时不再重复造轮子,避免因为轮子质量不高带来的麻烦,能够更加专注于项目的业务逻辑;
  4. 自动化构建和部署:前端脚手架可以自动进行代码打包、压缩、合并、编译等常见的构建工作,可以通过集成自动化部署脚本,自动将代码部署到测试、生产环境等;

5.2 Vite创建Vue3工程化项目

5.2.1 Vite+Vue3项目的创建、启动、停止

1 使用命令行创建工程。

  • 在磁盘的合适位置上,创建一个空目录用于存储多个前端项目;
  • 用vscode打开该目录;
  • 在vocode中打开命令行运行如下命令;
1
npm create vite@latest
  • 第一次使用Vite时会提示下载vite,输入y回车即可,下次使用Vite就不会出现了;
1687769339457
  • 注意: 输入项目名称,选择Vue+JavaScript选项即可;

2 安装项目所需依赖:

  • cd进入刚刚创建的项目目录;
  • npm install命令安装基础依赖;
1
2
cd ./vue3-demo1
npm install

3 启动项目:

  • 查看项目下的package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"name": "vue3-demo1",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"bootstrap": "^5.2.3",
"sass": "^1.62.1",
"vue": "^3.2.47"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.1.0",
"vite": "^4.3.2"
}
}
1
npm run dev

5 停止项目:

  • 命令行上 ctrl+c

5.2.2 Vite+Vue3项目的目录结构

1.下面是 Vite 项目结构和入口的详细说明:

1684489112904
  • public/ 目录:用于存放一些公共资源,如 HTML 文件、图像、字体等,这些资源会被直接复制到构建出的目标目录中。
  • src/ 目录:存放项目的源代码,包括 JavaScript、CSS、Vue 组件、图像和字体等资源。在开发过程中,这些文件会被 Vite 实时编译和处理,并在浏览器中进行实时预览和调试。以下是src内部划分建议:
    1. assets/ 目录:用于存放一些项目中用到的静态资源,如图片、字体、样式文件等。
    2. components/ 目录:用于存放组件相关的文件。组件是代码复用的一种方式,用于抽象出一个可复用的 UI 部件,方便在不同的场景中进行重复使用。
    3. layouts/ 目录:用于存放布局组件的文件。布局组件通常负责整个应用程序的整体布局,如头部、底部、导航菜单等。
    4. pages/ 目录:用于存放页面级别的组件文件,通常是路由对应的组件文件。在这个目录下,可以创建对应的文件夹,用于存储不同的页面组件。
    5. plugins/ 目录:用于存放 Vite 插件相关的文件,可以按需加载不同的插件来实现不同的功能,如自动化测试、代码压缩等。
    6. router/ 目录:用于存放 Vue.js 的路由配置文件,负责管理视图和 URL 之间的映射关系,方便实现页面之间的跳转和数据传递。
    7. store/ 目录:用于存放 Vuex 状态管理相关的文件,负责管理应用程序中的数据和状态,方便统一管理和共享数据,提高开发效率。
    8. utils/ 目录:用于存放一些通用的工具函数,如日期处理函数、字符串操作函数等。
  • vite.config.js 文件:Vite 的配置文件,可以通过该文件配置项目的参数、插件、打包优化等。该文件可以使用 CommonJS 或 ES6 模块的语法进行配置。
  • package.json 文件:标准的 Node.js 项目配置文件,包含了项目的基本信息和依赖关系。其中可以通过 scripts 字段定义几个命令,如 dev、build、serve 等,用于启动开发、构建和启动本地服务器等操作。
  • Vite 项目的入口为 src/main.js 文件,这是 Vue.js 应用程序的启动文件,也是整个前端应用程序的入口文件。在该文件中,通常会引入 Vue.js 及其相关插件和组件,同时会创建 Vue 实例,挂载到 HTML 页面上指定的 DOM 元素中。

2.vite的运行界面:

  • 在安装了 Vite 的项目中,可以在 npm scripts 中使用 vite 可执行文件,或者直接使用 npx vite 运行它。下面是通过脚手架创建的 Vite 项目中默认的 npm scripts:(package.json)。
1
2
3
4
5
6
7
{
"scripts": {
"dev": "vite", // 启动开发服务器,别名:`vite dev`,`vite serve`
"build": "vite build", // 为生产环境构建产物
"preview": "vite preview" // 本地预览生产构建产物
}
}
  • 运行设置端口号:(vite.config.js)。
1
2
3
4
5
6
7
//修改vite项目配置文件 vite.config.js
export default defineConfig({
plugins: [vue()],
server:{
port:3000
}
})

5.2.3 Vite+Vue3项目组件(SFC入门)

什么是VUE的组件?

  • 一个页面作为整体,是由多个部分组成的,每个部分在这里就可以理解为一个组件;
  • 每个.vue文件就可以理解为一个组件,多个.vue文件可以构成一个整体页面;
  • 组件化给我们带来的另一个好处就是组件的复用和维护非常的方便;

什么是.vue文件?

  • 传统的页面有html文件css文件和js文件三个文件组成(多文件组件) ;

  • vue将这文件合并成一个vue文件(Single-File Component,简称 SFC,单文件组件);

  • vue文件对js/css/html统一封装,这是VUE中的概念,该文件由三个部分组成 <script> <template> <style>

    • template标签 代表组件的html部分代码,代替传统的html文件;
    • script标签 代表组件的js代码,代替传统的js文件;
    • style标签 代表组件的css样式代码,代替传统的css文件 ;

工程化vue项目如何组织这些组件?

  • index.html是项目的入口,其中 <div id ='app'></div>是用于挂载所有组建的元素;
  • index.html中的script标签引入了一个main.js文件,具体的挂载过程在main.js中执行;
  • main.js是vue工程中非常重要的文件,他决定这项目使用哪些依赖,导入的第一个组件;
  • App.vue是vue中的核心组件,所有的其他组件都要通过该组件进行导入,该组件通过路由可以控制页面的切换;
1684912274904

5.2.4 Vite+Vue3响应式入门和setup函数

1 使用vite创建一个 vue+JavaScript项目:

1
2
3
npm create vite
npm install
npm run dev
  • App.vue
1
2
3
4
5
6
7
8
9
10
<script>
//存储vue页面逻辑js代码
</script>
<template>
<!-- 页面的样式的是html代码-->
</template>
<style scoped>
/** 存储的是css代码! <style scoped> 是 Vue.js 单文件组件中用于设置组件样式的一种方式。
它的含义是将样式局限在当前组件中,不对全局样式造成影响。 */
</style>

2 Vue3 setup函数和语法糖:

  • 位置:src/App.vue。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script type="module" setup>   
/* 通过setup关键字,可以省略 export default {setup(){ return{}}}这些冗余的语法结构
可以理解为Vue3 语法的入口 相当于Java中的main方法
*/
</script>
<template>
<!-- 必须有一个根标签div -->
<div>
</div>
</template>

<style scoped>
/* 将样式限定在当前组件内部,防止其样式影响到其他组件。 */
</style>

5.2.5 Vite+Vue3关于样式的导入方式

  1. 全局引入main.js;
    1
    import './style/reset.css' //书写引入的资源的相对路径即可!
  2. vue文件script代码引入;
    1
    import './style/reset.css'
  3. Vue文件style代码引入;
    1
    @import './style/reset.css'

image-20231212191833099

六、Vue3视图渲染技术

6.1 模版语法

Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。Vue 会将模板编译成高度优化的 JavaScript 代码。VUE简化了操作DOM的过程

6.1.1 插值表达式和文本渲染

插值表达式:最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 ,即双大括号{{}}

  • 插值表达式是将数据渲染到元素的指定位置的手段之一;
  • 插值表达式不绝对依赖标签,其位置相对自由;
  • 插值表达式中支持javascript的运算表达式;
  • 插值表达式中也支持函数的调用;
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
<script setup>
let number = 100
let getMsg = () =>{
return "hello vue"
}

let sum = (a,b) => {
return a + b
}
</script>

<template>
<!-- 必须有一个根标签div -->
<div>
<h1>{{number}}</h1>
<h1>{{number + 1}}</h1>
<h1>{{number * 3}}</h1>
<h1>{{getMsg()}}</h1>
<h1>{{sum(100,300)}}</h1>
</div>
</template>

<style scoped>

</style>

为了渲染双标中的文本,我们也可以选择使用v-textv-html命令:

  • v-text可以将数据渲染成双标签中间的文本,但是不识别html元素结构的文本;
  • v-html可以将数据渲染成双标签中间的文本,识别html元素结构的文本;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup>
let msg = "hello vue3"
let h1Ele = "<h1>我是H1标签</h1>"
</script>

<template>
<!-- 必须有一个根标签div -->
<div>
<p v-text="msg"></p>
<div v-html="h1Ele"></div>
</div>
</template>

<style scoped>

</style>

6.1.2 Attribute属性渲染

想要渲染一个元素的 attribute,应该使用 v-bind指令。

  • 由于插值表达式不能直接放在标签的属性中,要渲染元素的属性就应该使用v-bind;
  • v-bind可以用于渲染任何元素的属性,语法为 v-bind:属性名='数据名', 可以简写为 :属性名='数据名'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script setup type="module">
const data = {
name:'尚硅谷',
url:"http://www.atguigu.com",
logo:"http://www.atguigu.com/images/index_new/logo.png"
}
</script>
<template>
<div>
<a
v-bind:href='data.url'
target="_self">
<img
:src="data.logo"
:title="data.name">
<br>
<input type="button"
:value="`点击访问${data.name}`">
</a>
</div>
</template>
<style scoped>
</style>

6.1.3 事件的绑定

我们可以使用 v-on 来监听 DOM 事件,并在事件触发时执行对应的 Vue的JavaScript代码。

  • 用法:v-on:click="handler" 或简写为 @click="handler"
  • vue中的事件名=原生事件名去掉on 前缀 如:onClick --> click
  • handler的值可以是方法事件处理器,也可以是内联事件处理器;
  • 绑定事件时,可以通过一些绑定的修饰符,常见的事件修饰符如下:
    • .once:只触发一次事件。[重点]
    • .prevent:阻止默认事件。[重点]
    • .stop:阻止事件冒泡;
    • .capture:使用事件捕获模式而不是冒泡模式,先调用外部触发;
    • .self:只在事件发送者自身触发时才触发事件;
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
<script setup type="module">
import {ref} from 'vue'
// 响应式数据 当发生变化时,会自动更新 dom树
let count=ref(0)
let addCount= ()=>{
count.value++
}
let incrCount= (event)=>{
count.value++
// 通过事件对象阻止组件的默认行为
event.preventDefault();
}
</script>
<template>
<div>
<h1>count的值是:{{ count }}</h1>
<!-- 方法事件处理器 -->
<button v-on:click="addCount()">addCount</button> <br>
<!-- 内联事件处理器 -->
<button @click="count++">incrCount</button> <br>
<!-- 事件修饰符 once 只绑定事件一次 -->
<button @click.once="count++">addOnce</button> <br>
<!-- 事件修饰符 prevent 阻止组件的默认行为 -->
<a href="http://www.atguigu.com" target="_blank" @click.prevent="count++">prevent</a> <br>
<!-- 原生js方式阻止组件默认行为 (推荐) -->
<a href="http://www.atguigu.com" target="_blank" @click="incrCount($event)">prevent</a> <br>
</div>
</template>
<style scoped>
</style>

6.2 响应式基础

此处的响应式是指 : 数据模型(自定义的变量、对象)发生变化时,自动更新DOM树内容,页面上显示的内容会进行同步变化。vue3的数据模型不是自动响应式的,需要我们做一些特殊的处理。

6.2.1 响应式需求案例

需求:实现 + - 按钮,实现数字加一减一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script type="module" setup>
let counter = 0;
function show(){
alert(counter);
}
</script>
<template>
<div>
<button @click="counter--">-</button>
{{ counter }}
<button @click="counter++">+</button>
<hr>
<!-- 此案例,我们发现counter值,会改变,但是页面不改变! 默认Vue3的数据是非响应式的!-->
<button @click="show()">显示counter值</button>
</div>
</template>
<style scoped>
</style>

6.2.2 响应式实现关键字ref

ref 可以将一个基本类型的数据(如字符串,数字等)转换为一个响应式对象。 ref 只能包裹单一元素。

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
<script type="module" setup>
/* 从vue中引入ref方法 */
import {ref} from 'vue'
let counter = ref(0);
function show(){
alert(counter.value);
}
/* 函数中要操作ref处理过的数据,需要通过.value形式 */
let decr = () =>{
counter.value--;
}
let incr = () =>{
counter.value++;
}
</script>
<template>
<div>
<button @click="counter--">-</button>
<button @click="decr()">-</button>
{{ counter }}
<button @click="counter++">+</button>
<button @click="incr()">+</button>
<hr>
<button @click="show()">显示counter值</button>
</div>
</template>
<style scoped>
</style>
  • 在上面的例子中,我们使用 ref 包裹了一个数字,在代码中给这个数字加 1 后,视图也会跟着动态更新。需要注意的是,由于使用了 ref,因此需要在访问该对象时使用 .value 来获取其实际值。

6.2.3 响应式实现关键字reactive

我们可以使用 reactive() 函数创建一个响应式对象或数组:

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
<script type="module" setup>
/* 从vue中引入reactive方法 */
import {ref,reactive} from 'vue'
let data = reactive({
counter:0
})
function show(){
alert(data.counter);
}
/* 函数中要操作reactive处理过的数据,需要通过 对象名.属性名的方式 */
let decr = () =>{
data.counter--;
}
let incr = () =>{
data.counter++;
}
</script>
<template>
<div>
<button @click="data.counter--">-</button>
<button @click="decr()">-</button>
{{ data.counter }}
<button @click="data.counter++">+</button>
<button @click="incr()">+</button>
<hr>
<button @click="show()">显示counter值</button>
</div>
</template>
<style scoped>
</style>

对比ref和reactive:

  • 使用 ref 适用于以下开发场景:
    • 包装基本类型数据:ref 主要用于包装基本类型数据(如字符串、数字等),即只有一个值的数据,如果你想监听这个值的变化,用 ref最为方便。在组件中使用时也很常见。
    • 访问方式简单:ref 对象在访问时与普通的基本类型值没有太大区别,只需要通过 .value访问其实际值即可。
  • 使用 reactive 适用于以下开发场景:
    • 包装复杂对象:reactive可以将一个普通对象转化为响应式对象,这样在数据变化时会自动更新界面,特别适用于处理复杂对象或者数据结构。
    • 需要递归监听的属性:使用 reactive 可以递归追踪所有响应式对象内部的变化,从而保证界面的自动更新。
  • 综上所述,ref适用与简单情形下的数据双向绑定,对于只有一个字符等基本类型数据或自定义组件等情况,建议可以使用 ref;而对于对象、函数等较为复杂的数据结构,以及需要递归监听的属性变化,建议使用 reactive。当然,在实际项目中根据需求灵活选择也是十分必要的。

6.3 条件和列表渲染

6.3.1 条件渲染

v-if 条件渲染:

  • v-if='表达式' 只会在指令的表达式返回真值时才被渲染

  • 也可以使用 v-elsev-if 添加一个“else 区块”。

  • 一个 v-else 元素必须跟在一个 v-if 元素后面,否则它将不会被识别。

1
2
3
4
5
6
7
8
9
10
11
12
13
<script type="module" setup>
import {ref} from 'vue'
let awesome = ref(true)
</script>
<template>
<div>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
<button @click="awesome = !awesome">Toggle</button>
</div>
</template>
<style scoped>
</style>

v-show条件渲染扩展:

  • 另一个可以用来按条件显示一个元素的指令是 v-show。其用法基本一样;

  • 不同之处在于 v-show 会在 DOM 渲染中保留该元素;v-show 仅切换了该元素上名为 display 的 CSS 属性;

  • v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script type="module" setup>
import {ref} from 'vue'
let awesome = ref(true)
</script>
<template>
<div>
<h1 id="ha" v-show="awesome">Vue is awesome!</h1>
<h1 id="hb" v-if="awesome">Vue is awesome!</h1>
<h1 id="hc" v-else>Oh no 😢</h1>
<button @click="awesome = !awesome">Toggle</button>
</div>
</template>
<style scoped>
</style>
1684565503347

v-if vs v-show

  • v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建;

  • v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染;

  • 相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换;

  • 总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适;

6.3.2 列表渲染

我们可以使用 v-for 指令基于一个数组来渲染一个列表:

  • v-for 指令的值需要使用 item in items 形式的特殊语法,其中 items 是源数据的数组,而 item 是迭代项的别名;

  • v-for 块中可以完整地访问父作用域内的属性和变量。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
<script type="module" setup>
import {ref,reactive} from 'vue'
let parentMessage= ref('产品')
let items =reactive([
{
id:'item1',
message:"薯片"
},
{
id:'item2',
message:"可乐"
}
])
</script>
<template>
<div>
<ul>
<!-- :key不写也可以 -->
<li v-for='item in items' :key='item.id'>
{{ item.message }}
</li>
</ul>
<ul>
<!-- index表示索引 -->
<li v-for="(item, index) in items" :key="index">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
</div>
</template>
<style scoped>
</style>

6.4 双向绑定

单项绑定和双向绑定:

  • 单向绑定:响应式数据的变化会更新dom树,但是dom树上用户的操作造成的数据改变不会同步更新到响应式数据
  • 双向绑定:响应式数据的变化会更新dom树,但是dom树上用户的操作造成的数据改变会同步更新到响应式数据
    • 用户通过表单标签才能够输入数据,所以双向绑定都是应用到表单标签上的,其他标签不行;
    • v-model专门用于双向绑定表单标签的value属性,语法为 v-model:value='',可以简写为 v-model=''
    • v-model还可以用于各种不同类型的输入,<textarea><select> 元素;
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
<script type="module" setup>
//引入模块
import { reactive,ref} from 'vue'
let hbs = ref([]); //装爱好的值
let user = reactive({username:null,password:null,introduce:null,pro:null})
function login(){
alert(hbs.value);
alert(JSON.stringify(user));
}
function clearx(){
//user = {};// 这中写法会将数据变成非响应的,应该是user.username=""
user.username=''
user.password=''
user.introduce=''
user.pro=''
hbs.value.splice(0,hbs.value.length);;
}
</script>
<template>
<div>
账号: <input type="text" placeholder="请输入账号!" v-model="user.username"> <br>
密码: <input type="text" placeholder="请输入账号!" v-model="user.password"> <br>
爱好:
<input type="checkbox" name="hbs" v-model="hbs" value="吃">
<input type="checkbox" name="hbs" v-model="hbs" value="喝">
<input type="checkbox" name="hbs" v-model="hbs" value="玩">
<input type="checkbox" name="hbs" v-model="hbs" value="乐">
<br>
简介:<textarea v-model="user.introduce"></textarea>
<br>
籍贯:
<select v-model="user.pro">
<option value="1"></option>
<option value="2"></option>
<option value="3"></option>
<option value="4"></option>
<option value="5"></option>
<option value="6"></option>
</select>
<br>
<button @click="login()">登录</button>
<button @click="clearx()">重置</button>
<hr>
显示爱好:{{ hbs }}
<hr>
显示用户信息:{{ user }}
</div>
</template>
<style scoped>
</style>

6.5 属性计算

模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿

推荐使用计算属性来描述依赖响应式状态的复杂逻辑。

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
<script setup>
import {ref,computed} from "vue"

let msg = ref("")

let reverseMethod = () => {
console.log("业务方法调用!!!")
return msg.value.split("").reverse().join('')
}

let computedMethod = computed(function() {
console.log("计算属性调用!!!")
return msg.value.split("").reverse().join('')
})
</script>

<template>
<!-- 必须有一个根标签div -->
<div>
展现数据: {{reverseMethod()}}<br>
展现数据: {{reverseMethod()}}<br>
展现数据: {{reverseMethod()}}<br>
<!-- 通过属性调用不需要方法 -->
展现数据2: {{computedMethod}}<br>
展现数据2: {{computedMethod}}<br>
展现数据2: {{computedMethod}}<br>
请输入内容: <input type="text" v-model="msg"><br>
</div>

</template>

<style scoped>

</style>

计算属性缓存 vs 方法:

  • 计算属性值会基于其响应式依赖被缓存

image-20231212201731644

6.6 数据监听器

我们需要在状态变化时执行一些操作

我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数:

  • watch主要用于以下场景:
    • 当数据发生变化时需要执行相应的操作;
    • 监听数据变化,当满足一定条件时触发相应操作;
    • 在异步操作前或操作后需要执行相应的操作;

监控响应式数据(watch):

  • 语法:
1
2
watch(msg, function(新数据,旧数据){})
watch(msg, (newValue,oldValue) => { xxx })
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
<script setup>
import {ref,reactive,watch} from "vue"

let msg = ref("")

//监控事件
/* watch(msg, function(newValue,oldValue){
console.log("1:"+newValue+":"+oldValue)
}) */

watch(msg, (newValue,oldValue) => {
let reg = /行动/g
if(newValue.match(reg)){
alert("等你很久了")
}
})

</script>

<template>
<div>
特务发送信息 <input type="text" v-model="msg">
</div>

</template>

<style scoped>

</style>

监控响应式数据(watchEffect):

  • watchEffect默认监听所有的响应式数据 只要数据发生变化 则会触发事件
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
<script setup>
import {ref,reactive,watch,watchEffect} from "vue"

let msg = ref("")
let msg2 = ref("")

watchEffect( () => {
console.log(msg.value)
console.log(msg2.value)
})

</script>

<template>
<div>
数据1:<input type="text" v-model="msg"><br>
数据2:<input type="text" v-model="msg2">
</div>

</template>

<style scoped>

</style>

watch vs. watchEffect

  • watchwatchEffect 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式:
    • watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机;
    • watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确;

6.7. Vue生命周期

6.7.1 生命周期简介

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

  • 周期图解:
  • 常见钩子函数:
    • onBeforeMount() 注册一个钩子,在组件被挂载之前被调用;
    • onMounted() 注册一个回调函数,在组件挂载完成后执行;
    • onBeforeUpdate() 注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用;
    • onUpdated() 注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用;
    • onBeforeUnmount() 注册一个钩子,在组件实例被卸载之前调用;
    • onUnmounted() 注册一个回调函数,在组件实例被卸载之后调用;(暂时测试不了)

6.7.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
<script setup>
import {ref,onMounted,onBeforeUpdate,onUpdated,onBeforeMount,onBeforeUnmount,onUnmounted} from "vue"

let msg = ref("")
onMounted(function() {
console.log("页面加载完成")
})


onBeforeUpdate( () => {
console.log("页面数据修改前调用")
})
onUpdated(function (){
console.log("数据修改之后调用")
})

onBeforeMount(() => {
console.log("组件被加载前调用")
})


onBeforeUnmount( () => {
console.log("组件卸载前")
})

onUnmounted( () => {
console.log("组件卸载后")
})
</script>

<template>
<div>
<h1>生命周期函数介绍</h1>
<input type="text" v-model="msg">
</div>

</template>

<style scoped>

</style>

6.8 Vue组件

6.8.1 组件基础

组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。组件就是实现应用中局部功能代码和资源的集合!在实际应用中,组件常常被组织成层层嵌套的树状结构:

  • 这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。

传统方式编写应用:

组件方式编写应用:

  • 组件化:对js/css/html统一封装,这是Vue中的概念;

  • 模块化:对js的统一封装,这是ES6中的概念;

  • 组件化中,对js部分代码的处理使用ES6中的模块化;

6.8.2 组件化入门案例

案例需求: 创建一个页面,包含头部和菜单以及内容显示区域,每个区域使用独立组建!

1686885192862

1 准备Vue项目:

1
2
3
npm create vite
cd vite项目
npm install

2 安装相关依赖:

1
2
npm install sass
npm install bootstrap

3 创建子组件 在src/components文件下 vscode需要安装Vetur插件,这样Vue文件有快捷提示:

  • Header.vue
1
2
3
4
5
6
7
8
9
<script setup type="module">
</script>
<template>
<div>
欢迎: xx <a href="#">退出登录</a>
</div>
</template>
<style>
</style>
  • Navigator.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup type="module">
</script>
<template>
<!-- 推荐写一个根标签-->
<div>
<ul>
<li>学员管理</li>
<li>图书管理</li>
<li>请假管理</li>
<li>考试管理</li>
<li>讲师管理</li>
</ul>
</div>
</template>
<style>
</style>
  • Content.vue
1
2
3
4
5
6
7
8
9
<script setup type="module">
</script>
<template>
<div>
展示的主要内容!
</div>
</template>
<style>
</style>
  • App.vue 入口组件App引入组件:
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
<script setup>
import Header from './components/Header.vue'
import Navigator from './components/Navigator.vue'
import Content from './components/Content.vue'
</script>
<template>
<div>
<Header class="header"></Header>
<Navigator class="navigator"></Navigator>
<Content class="content"></Content>
</div>
</template>
<style scoped>
.header{
height: 80px;
border: 1px solid red;
}
.navigator{
width: 15%;
height: 800px;
display: inline-block;
border: 1px blue solid;
float: left;
}
.content{
width: 83%;
height: 800px;
display: inline-block;
border: 1px goldenrod solid;
float: right;
}
</style>

4 启动测试

1
npm run dev

6.8.3 组件之间传递数据

6.8.3.1 父传子

Vue3 中父组件向子组件传值可以通过 props 进行

  • 在父组件定义需要传递的参数
1
let msg = "张三"
  • 引入子组件 并且通过属性绑定 传递数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import Son from "./components/Son.vue"
let msg = "张三"
</script>

<template>
<div>
<h1>父子组件参数传递</h1>
<Son :msg="msg"></Son>
</div>

</template>

<style scoped>
</style>

  • 在Son组件中定义传递的参数和数据类型
1
2
3
4
5
6
7
8
 <script setup>
import { defineProps } from 'vue';

defineProps({
//属性名称: 属性类型 String Number Boolean 等
msg: String
})
</script>
  • Son组件中使用数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script setup>
import { defineProps } from 'vue';

defineProps({
//属性名称: 属性类型 String Number Boolean 等
msg: String
})
</script>

<template>
<div>
<h1>我是子组件</h1>
<h3>{{ msg }}</h3>
</div>
</template>

<style scoped>

</style>

6.8.3.2 子传父
  • 在子组件中使用defineEmits 函数自定义事件
    • defineEmits(['自定义事件名称'])
    • emits('事件名称',"事件值")
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
<script setup>
import { defineProps,defineEmits} from 'vue';

//定义需要传递的数据
//1注册自定义事件
let emits = defineEmits(['getName','getAge'])

function sendMsg(){
//2.为自定义事件赋值
//emits('事件名称',"事件的值")
emits('getName',"我是子数据-火舞")
emits('getAge', 19)
}

</script>

<template>
<div>
<h1>我是子组件</h1>
<button @click="sendMsg">向父组件发送数据</button>
</div>
</template>

<style scoped>

</style>

  • 父组件: App.vue

    • 引入子组件
    1
    import Son from "./components/Son.vue"
    • 接收触发事件
    1
    <Son @getName="getName" @getAge="getAge"></Son>
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
<script setup>
import {ref} from "vue"
import Son from "./components/Son.vue"

let name = ref("AAA")
let age = ref("111")

function getName(data){
name.value = data
}

function getAge(data){
age.value = data
}
</script>

<template>
<div>
<h1>父子组件参数传递</h1>
<Son @getName="getName" @getAge="getAge"></Son>
<hr>
子组件的数据: {{ name }} -- {{ age}}
</div>
</template>

<style scoped>
</style>

6.8.3.3 兄弟传参
  • A.vue: 发送数据到App.vue
    • defineEmits 自定义函数
    • emits(‘事件名称’,发送的数据);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script setup>
//1.向父级组件传递参数
import {defineEmits} from 'vue'

const emits = defineEmits(["sendData"])
function sendParent(){
emits('sendData',"我是A的数据要发送")
}
</script>

<template>
<div>
展现内容A <button @click="sendParent">点击向父级传递数据</button>
</div>
</template>

<style scoped>

</style>

  • App.vue接收数据
1
2
3
4
5
6
7
8
9
10
11
<script setup>
import {ref} from "vue"
import A from "./components/A.vue"

let myData = ref("")
let getData = (data) => {
myData.value = data
}
</script>

<A @sendData="getData" :sendSonMsg="myData"></A>
  • App.vue: 发送数据到B.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup>
import {defineProps} from "vue"
// 接收父级发送的数据
defineProps({
sendSonMsg: String
})
</script>

<template>
<div>
展现内容B ~~~~ {{ sendSonMsg}}
</div>
</template>

<style scoped>

</style>

七、Vue3路由机制Router

7.1 路由简介

1 什么是路由?

  • 定义:路由就是根据不同的 URL 地址展示不同的内容或页面;

2 路由的作用:

  • 单页应用程序(SPA)中,路由可以实现不同视图之间的无刷新切换,提升用户体验;
  • 路由还可以实现页面的认证和权限控制,保护用户的隐私和安全;
  • 路由还可以利用浏览器的前进与后退,帮助用户更好地回到之前访问过的页面;

7.2 路由入门案例

1 案例需求分析:

2 创建项目和导入路由依赖:

1
2
3
npm create vite //创建项目cd 项目文件夹 //进入项目文件夹
npm install //安装项目需求依赖
npm install vue-router@4 --save //安装全局的vue-router 4版本

3 准备页面和组件 :

  • components/Home.vue
1
2
3
4
5
6
7
8
9
<script setup>
</script>
<template>
<div>
<h1>Home页面</h1>
</div>
</template>
<style scoped>
</style>
  • components/List.vue
1
2
3
4
5
6
7
8
9
<script setup>
</script>
<template>
<div>
<h1>List页面</h1>
</div>
</template>
<style scoped>
</style>

4.定义路由标签 App.vue

  • 定义路由标签 <router-link to="/请求路径">标签名称</router-link>
  • 定义显示位置 <router-view name='指定标签名称'></router-view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script setup>
</script>
<template>
<div>
<h1>App页面</h1>
<hr/>
<!-- 路由的连接 -->
<router-link to="/">home页</router-link> <br>
<router-link to="/list">list页</router-link> <br>
<hr/>
<!-- 路由连接对应视图的展示位置 -->
<hr> 默认展示位置:<router-view></router-view>
<hr> Home视图展示:<router-view name="homeView"></router-view>
</div>
</template>
<style scoped>
</style>

5 准备路由配置:

  • src/routers/router.js
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
// 导入路由创建的相关方法
import {createRouter,createWebHashHistory} from 'vue-router'
// 导入vue组件
import Home from '../components/Home.vue'
import List from '../components/List.vue'

// 创建路由对象,声明路由规则
const router = createRouter({
//createWebHashHistory() 是 Vue.js 基于 hash 模式创建路由的工厂函数。在使用这种模式下,路由信息保存在 URL 的 hash 中,
//使用 createWebHashHistory() 方法,可以创建一个路由历史记录对象,用于管理应用程序的路由。在 Vue.js 应用中,
//通常使用该方法来创建路由的历史记录对象。
//就是路由中缓存历史记录的对象,vue-router提供
history: createWebHashHistory(),
routes:[
{
path:'/',
/*
component指定组件在默认的路由视图位置展示
components:Home
components指定组件在name为某个值的路由视图位置展示
components:{
default:Home,// 默认路由视图位置
homeView:Home// name为homeView的路由视图位置
}
*/
components:{
default:Home,
homeView:Home
}
},
{
path:'/list',
components:{
listView : List
}
}
]

})
// 对外暴露路由对象
export default router;

6 main.js引入Router配置:

  • 修改文件:main.js (入口文件)
1
2
3
4
5
6
7
8
9
10
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
//导入router模块
import router from './routers/router.js'
let app = createApp(App)
//绑定路由对象
app.use(router)
//挂载视图
app.mount("#app")

7 启动测试:

1
npm run dev

7.3 路由重定向

重定向的作用:将一个路由重定向到另一个路由上。

  • 修改案例:访问/list和/showAll都定向到List.vue.
  • 关键字: redirect:/xxx
  • router.js
1
2
3
4
5
{
path:'/showAll',
// 重定向
redirect :'/list'
}
  • App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup>
</script>
<template>
<div>
<h1>App页面</h1>
<hr/>
<!-- 路由的连接 -->
<router-link to="/">home页</router-link> <br>
<router-link to="/list">list页</router-link> <br>
<router-link to="/showAll">showAll页</router-link> <br>

<hr/>
<!-- 路由连接对应视图的展示位置 -->
<hr> 默认展示位置:<router-view></router-view>
<hr> Home视图展示:<router-view name="homeView"></router-view>
<hr> List视图展示:<router-view name="listView"></router-view>

</div>
</template>
<style scoped>
</style>

7.4 编程式路由(useRouter)

声明式路由:

  • <router-link to="/list">list页</router-link> 这种路由,点击后只能切换/list对应组件,是固定的。

编程式路由:

  • 通过useRouter,动态决定向那个组件切换的路由;
  • 在 Vue 3 和 Vue Router 4 中,你可以使用 useRouter 来实现动态路由(编程式路由);

案例需求:通过普通按钮配合事件绑定实现路由页面跳转

  • App.vue
    • let router = useRouter() 创建对象
    • router.push('/list') 跳转默认页面
    • router.push({path:'/list'})
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
<script setup type="module">
import {useRouter} from 'vue-router'
import {ref} from 'vue'
//创建动态路由对象
let router = useRouter()
let routePath =ref('')
let showList= ()=>{
// 编程式路由
// 直接push一个路径
// router.push('/list')
// push一个带有path属性的对象
router.push({path:'/list'})
}
</script>
<template>
<div>
<h1>App页面</h1>
<hr/>
<!-- 路由的连接 -->
<router-link to="/">home页</router-link> <br>
<router-link to="/list">list页</router-link> <br>
<!-- 动态输入路径,点击按钮,触发单击事件的函数,在函数中通过编程是路由切换页面 -->
<button @click="showList()">showList</button> <br>
<hr/>
<!-- 路由连接对应视图的展示位置 -->
<hr> 默认展示位置:<router-view></router-view>
<hr> Home视图展示:<router-view name="homeView"></router-view>
<hr> List视图展示:<router-view name="listView"></router-view>
</div>
</template>
<style scoped>
</style>

7.5 路由传参(useRoute)

路径参数:

  • 在路径中使用一个动态字段来实现,我们称之为 路径参数
    • 例如: 查看数据详情 /showDetail/11就是要查看详情的id,可以动态添值。

键值对参数:

  • 类似与get请求通过url传参,数据是键值对形式的:

    • 例如: 查看数据详情/showDetail?hid=1hid=1就是要传递的键值对参数。

读取参数:

  • 在 Vue 3 和 Vue Router 4 中,你可以使用 useRoute 这个函数从 Vue 的组合式 API 中获取路由对象;

案例需求 : 切换到ShowDetail.vue组件时,向该组件通过路由传递参数。

  • 修改App.vue文件
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
<script setup type="module">
import {useRouter} from 'vue-router'
//创建动态路由对象
let router = useRouter()
//动态路由路径传参方法
let showDetail= (id,language)=>{
// 尝试使用拼接字符串方式传递路径参数
router.push(`showDetail/${id}/${languange}`)
}
let showDetail2= (id,language)=>{
/*uri键值对参数,需要使用query */
router.push({path:"/showDetail2",query:{id:id,language:language}})
}
</script>
<template>
<div>
<h1>App页面</h1>
<hr/>
<!-- 路径参数 -->
<router-link to="/showDetail/1/JAVA">sh路径传参JAVA</router-link>
<button @click="showDetail(1,'JAVA')">编程式路由路径传参显示JAVA</button>
<hr/>
<!-- 键值对参数 -->
<router-link v-bind:to="{path:'/showDetail2',query:{id:1,language:'Java'}}">键值对传参JAVA</router-link>
<button @click="showDetail2(1,'JAVA')">编程式路由键值对传参JAVA</button>
<hr> showDetail视图展示:<router-view name="showDetailView"></router-view>
<hr> showDetail2视图展示:<router-view name="showDetailView2"></router-view>
</div>
</template>
<style scoped>
</style>
  • 修改router.js增加路径参数占位符
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
import {createRouter,createWebHashHistory} from 'vue-router'
import ShowDetail from '../components/ShowDetail.vue'
import ShowDetail2 from '../components/ShowDetail2.vue'
// 创建路由对象,声明路由规则
const router = createRouter({
history: createWebHashHistory(),
routes:[
{
/* 此处:id :language作为路径的占位符 */
path:'/showDetail/:id/:language',
/* 动态路由传参时,根据该名字找到该路由 */
name:'showDetail',
components:{
showDetailView:ShowDetail
}
},
{
path:'/showDetail2',
components:{
showDetailView2:ShowDetail2
}
},
]
})
// 对外暴露路由对象
export default router;
  • ShowDetail.vue 通过useRoute获取路径参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup type="module">
import{useRoute} from 'vue-router'
import { onUpdated,ref } from 'vue';
// 获取当前的route对象
let route =useRoute()
</script>
<template>
<div>
<h1>ShowDetail页面</h1>
<h3>编号{{route.params.id}}:{{route.params.language}}是世界上最好的语言</h3>
</div>
</template>
<style scoped>
</style>
  • ShowDetail2.vue通过useRoute获取键值对参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup type="module">
import{useRoute} from 'vue-router'
import { onUpdated,ref } from 'vue';
// 获取当前的route对象
let route =useRoute()
</script>
<template>
<div>
<h1>ShowDetail2页面</h1>
<h3>编号{{route.query.id}}:{{route.query.language}}是世界上最好的语言</h3>
</div>
</template>
<style scoped>
</style>

7.6 路由守卫

路由守卫是用于在路由切换期间进行一些特定任务的回调函数。

  1. 全局前置守卫:在路由切换前被调用,可以用于验证用户是否已登录、中断导航、请求数据等;
  2. 全局后置守卫:在路由切换之后被调用,可以用于处理数据、操作 DOM 、记录日志等;

守卫代码的位置: 在router.js中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//全局前置路由守卫
router.beforeEach( (to,from,next) => {
//to 是目标地包装对象 .path属性可以获取地址
//from 是来源地包装对象 .path属性可以获取地址
//next是方法,不调用默认拦截! next() 放行,直接到达目标组件
//next('/地址')可以转发到其他地址,到达目标组件前会再次经过前置路由守卫
console.log(to.path,from.path,next)
//需要判断,注意避免无限重定向
if(to.path == '/index'){
next()
}else{
next('/index')
}
})
//全局后置路由守卫
router.afterEach((to, from) => {
console.log(`Navigate from ${from.path} to ${to.path}`);
});

7.7 登录案例练习

登录案例,登录以后才可以进入home,否则必须进入login。

  • 定义Login.vue
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
<script setup>
import {ref} from 'vue'
import {useRouter} from 'vue-router'
let username =ref('')
let password =ref('')
let router = useRouter();
let login = () =>{
console.log(username.value,password.value)
if(username.value == 'root' & password.value == '123456'){
router.push({path:'/home',query:{'username':username.value}})
//登录成功利用前端存储机制,存储账号!
localStorage.setItem('username',username.value)
//sessionStorage.setItem('username',username)
}else{
alert('登录失败,账号或者密码错误!');
}
}

</script>
<template>
<div>
账号: <input type="text" v-model="username" placeholder="请输入账号!"><br>
密码: <input type="password" v-model="password" placeholder="请输入密码!"><br>
<button @click="login()">登录</button>
</div>
</template>
<style scoped>
</style>
  • 定义Home.vue
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
<script setup>
import {ref} from 'vue'
import {useRoute,useRouter} from 'vue-router'
let route =useRoute()
let router = useRouter()
// 并不是每次进入home页时,都有用户名参数传入
//let username = route.query.username
let username =window.localStorage.getItem('username');
let logout= ()=>{
// 清除localStorge中的username
//window.sessionStorage.removeItem('username')
window.localStorage.removeItem('username')
// 动态路由到登录页
router.push("/login")
}
</script>
<template>
<div>
<h1>Home页面</h1>
<h3>欢迎{{username}}登录</h3>
<button @click="logout">退出登录</button>
</div>
</template>
<style scoped>
</style>
  • App.vue
1
2
3
4
5
6
7
8
9
<script setup type="module">
</script>
<template>
<div>
<router-view></router-view>
</div>
</template>
<style scoped>
</style>
  • 定义routers.js
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
import {createRouter,createWebHashHistory} from 'vue-router'
import Home from '../components/Home.vue'
import Login from '../components/login.vue'
const router = createRouter({
history: createWebHashHistory(),
routes:[
{
path:'/home',
component:Home
},
{
path:'/',
redirect:"/home"
},
{
path:'/login',
component:Login
},
]
})
// 设置路由的全局前置守卫
router.beforeEach((to,from,next)=>{
/*
to 要去那
from 从哪里来
next 放行路由时需要调用的方法,不调用则不放行
*/
console.log(`从哪里来:${from.path},到哪里去:${to.path}`)
if(to.path == '/login'){
//放行路由 注意放行不要形成循环
next()
}else{
//let username =window.sessionStorage.getItem('username');
let username =window.localStorage.getItem('username');
if(null != username){
next()
}else{
next('/login')
}
}
})
// 设置路由的全局后置守卫
router.afterEach((to,from)=>{
console.log(`从哪里来:${from.path},到哪里去:${to.path}`)
})
// 对外暴露路由对象
export default router;
  • 启动测试
1
npm run dev

八、 案例开发-日程管理-第四期

重构前端工程

业务目标展示:

  • 登录页

1690533751478

  • 注册页

1690533770048

  • 日程管理页

1690533809917

创建项目,安装依赖

1
2
3
4
npm create vite
cd 项目目录
npm install
npm install vue-router
  • 项目结构如下
1690516506417

开发视图:

  • Header.vue视图
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
<script setup>
</script>
<template>
<div>
<h1 class="ht">欢迎使用日程管理系统</h1>
<div>
<div class="optionDiv">
<router-link to="/login">
<button class="b1s">登录</button>
</router-link>
<router-link to="/regist">
<button class="b1s">注册</button>
</router-link>
</div>
<div class="optionDiv">
欢迎xxx
<button class="b1b">退出登录</button>
<router-link to="/showSchedule">
<button class="b1b">查看我的日程</button>
</router-link>
</div>
<br>
</div>
</div>
</template>
<style scoped>
.ht{
text-align: center;
color: cadetblue;
font-family: 幼圆;
}
.b1s{
border: 2px solid powderblue;
border-radius: 4px;
width:60px;
background-color: antiquewhite;

}
.b1b{
border: 2px solid powderblue;
border-radius: 4px;
width:100px;
background-color: antiquewhite;
}
.optionDiv{
width: 300px;
float: right;
}
</style>
  • Login.vue视图
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
<script setup>
import{ ref,reactive} from 'vue'
// 响应式数据,保存用户输入的表单信息
let loginUser =reactive({
username:'',
userPwd:''
})
// 响应式数据,保存校验的提示信息
let usernameMsg =ref('')
let userPwdMsg = ref('')
// 校验用户名的方法
function checkUsername(){
// 定义正则
var usernameReg=/^[a-zA-Z0-9]{5,10}$/
// 校验用户名
if(!usernameReg.test(loginUser.username)){
// 格式不合法
usernameMsg.value="格式有误"
return false
}
usernameMsg.value="ok"
return true
}
// 校验密码的方法
function checkUserPwd(){
// 定义正则
var passwordReg=/^[0-9]{6}$/
// 校验密码
if(!passwordReg.test(loginUser.userPwd)){
// 格式不合法
userPwdMsg.value="格式有误"
return false
}
userPwdMsg.value="ok"
return true
}
</script>
<template>
<div>
<h3 class="ht">请登录</h3>
<table class="tab" cellspacing="0px">
<tr class="ltr">
<td>请输入账号</td>
<td>
<input class="ipt"
type="text"
v-model="loginUser.username"
@blur="checkUsername()">
<span id="usernameMsg" v-text="usernameMsg"></span>
</td>
</tr>
<tr class="ltr">
<td>请输入密码</td>
<td>
<input class="ipt"
type="password"
v-model="loginUser.userPwd"
@blur="checkUserPwd()">
<span id="userPwdMsg" v-text="userPwdMsg"></span>
</td>
</tr>
<tr class="ltr">
<td colspan="2" class="buttonContainer">
<input class="btn1" type="button" value="登录">
<input class="btn1" type="button" value="重置">
<router-link to="/regist">
<button class="btn1">去注册</button>
</router-link>
</td>
</tr>
</table>
</div>
</template>
<style scoped>
.ht{
text-align: center;
color: cadetblue;
font-family: 幼圆;
}
.tab{
width: 500px;
border: 5px solid cadetblue;
margin: 0px auto;
border-radius: 5px;
font-family: 幼圆;
}
.ltr td{
border: 1px solid powderblue;
}
.ipt{
border: 0px;
width: 50%;
}
.btn1{
border: 2px solid powderblue;
border-radius: 4px;
width:60px;
background-color: antiquewhite;
}
#usernameMsg , #userPwdMsg {
color: gold;
}
.buttonContainer{
text-align: center;
}
</style>
  • Regist.vue视图
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
<script setup>
import{ ref,reactive} from 'vue'
// 响应式数据,保存用户输入的表单信息
let registUser =reactive({
username:'',
userPwd:''
})
// 响应式数据,保存校验的提示信息
let reUserPwd =ref('')
let reUserPwdMsg =ref('')
let usernameMsg =ref('')
let userPwdMsg = ref('')

// 校验用户名的方法
function checkUsername(){
// 定义正则
let usernameReg=/^[a-zA-Z0-9]{5,10}$/
// 校验
if(!usernameReg.test(registUser.username)){
// 提示
usernameMsg.value = "不合法"
return false
}
// 通过校验
usernameMsg.value="OK"
return true
}
// 校验密码的方法
function checkUserPwd(){
// 定义正则
let passwordReg=/^[0-9]{6}$/
// 校验
if(!passwordReg.test(registUser.userPwd)){
// 提示
userPwdMsg.value = "不合法"
return false
}
// 通过校验
userPwdMsg.value="OK"
return true
}
// 校验密码的方法
function checkReUserPwd(){
// 定义正则
let passwordReg=/^[0-9]{6}$/
// 校验
if(!passwordReg.test(reUserPwd.value)){
// 提示
reUserPwdMsg.value = "不合法"
return false
}
console.log(registUser.userPwd,reUserPwd.value)
// 校验
if(!(registUser.userPwd==reUserPwd.value)){
// 提示
reUserPwdMsg.value = "不一致"
return false
}
// 通过校验
reUserPwdMsg.value="OK"
return true
}
</script>
<template>
<div>
<h3 class="ht">请注册</h3>
<table class="tab" cellspacing="0px">
<tr class="ltr">
<td>请输入账号</td>
<td>
<input class="ipt"
id="usernameInput"
type="text"
name="username"
v-model="registUser.username"
@blur="checkUsername()">

<span id="usernameMsg" class="msg" v-text="usernameMsg"></span>
</td>
</tr>
<tr class="ltr">
<td>请输入密码</td>
<td>
<input class="ipt"
id="userPwdInput"
type="password"
name="userPwd"
v-model="registUser.userPwd"
@blur="checkUserPwd()">
<span id="userPwdMsg" class="msg" v-text="userPwdMsg"></span>
</td>
</tr>
<tr class="ltr">
<td>确认密码</td>
<td>
<input class="ipt"
id="reUserPwdInput"
type="password"
v-model="reUserPwd"
@blur="checkReUserPwd()">
<span id="reUserPwdMsg" class="msg" v-text="reUserPwdMsg"></span>
</td>
</tr>
<tr class="ltr">
<td colspan="2" class="buttonContainer">
<input class="btn1" type="button" value="注册">
<input class="btn1" type="button" value="重置">
<router-link to="/login">
<button class="btn1">去登录</button>
</router-link>
</td>
</tr>
</table>
</div>
</template>
<style scoped>
.ht{
text-align: center;
color: cadetblue;
font-family: 幼圆;
}
.tab{
width: 500px;
border: 5px solid cadetblue;
margin: 0px auto;
border-radius: 5px;
font-family: 幼圆;
}
.ltr td{
border: 1px solid powderblue;

}
.ipt{
border: 0px;
width: 50%;

}
.btn1{
border: 2px solid powderblue;
border-radius: 4px;
width:60px;
background-color: antiquewhite;
}
.msg {
color: gold;
}
.buttonContainer{
text-align: center;
}
</style>
  • ShowSchedule.vue视图
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
<script setup>
</script>
<template>
<div>
<h3 class="ht">您的日程如下</h3>
<table class="tab" cellspacing="0px">
<tr class="ltr">
<th>编号</th>
<th>内容</th>
<th>进度</th>
<th>操作</th>
</tr>
<tr class="ltr">
<td></td>
<td></td>
<td></td>
<td class="buttonContainer">
<button class="btn1">删除</button>
<button class="btn1">保存修改</button>
</td>
</tr>
<tr class="ltr buttonContainer" >
<td colspan="4">
<button class="btn1">新增日程</button>
</td>
</tr>
</table>
</div>
</template>
<style scoped>
.ht{
text-align: center;
color: cadetblue;
font-family: 幼圆;
}
.tab{
width: 80%;
border: 5px solid cadetblue;
margin: 0px auto;
border-radius: 5px;
font-family: 幼圆;
}
.ltr td{
border: 1px solid powderblue;
}
.ipt{
border: 0px;
width: 50%;
}
.btn1{
border: 2px solid powderblue;
border-radius: 4px;
width:100px;
background-color: antiquewhite;
}
#usernameMsg , #userPwdMsg {
color: gold;
}
.buttonContainer{
text-align: center;
}
</style>
  • App.vue视图
1
2
3
4
5
6
7
8
9
10
11
12
<script setup>
import Header from './components/Header.vue'
</script>
<template>
<div>
<Header></Header>
<hr>
<router-view></router-view>
</div>
</template>
<style scoped>
</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
import {createRouter,createWebHashHistory} from 'vue-router'
import Login from '../components/Login.vue'
import Regist from '../components/Regist.vue'
import ShowScedule from '../components/ShowSchedule.vue'
let router = createRouter({
history:createWebHashHistory(),
routes:[
{
path:"/",
component:Login
},
{
path:"/login",
component:Login
},
{
path:"/showSchedule",
component:ShowScedule
},
{
path:"/regist",
component:Regist
}
]
})
export default router
  • 配置main.js
1
2
3
4
5
6
7
8
import { createApp } from 'vue'
import App from './App.vue'
// 导入路由
import router from './router/router.js'
let app =createApp(App)
// 全局使用路由
app.use(router)
app.mount('#app')

九、Vue3数据交互Axios

9.1 Axios介绍

AJAX :

  • AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML);
  • AJAX 优点: 不需要刷新页面,可以实现与服务器的交互
  • 记忆: 局部刷新,异步访问
  • Axios是Ajax的实现方式

image-20231227102207430

什么是axios 官网介绍:https://axios-http.com/zh/docs/intro

  • Axios 是一个基于 Promise网络请求库,作用于node.js 和浏览器中。
  • 它有如下特性:
    • 从浏览器创建 XMLHttpRequests
    • 从 node.js 创建 http 请求
    • 支持 Promise API
    • 拦截请求和响应
    • 转换请求和响应数据
    • 取消请求
    • 自动转换JSON数据
    • 客户端支持防御XSRF

9.2 Axios 入门案例

1 案例需求:请求后台获取随机土味情话。

  • 请求的url
1
2
https://api.uomg.com/api/rand.qinghua 
http://forum.atguigu.cn/api/rand.qinghua
  • 请求的方式
1
GET/POST
  • 数据返回的格式
1
{"code":1,"content":"我努力不是为了你而是因为你。"}

2 准备项目:

1
2
npm create vite
npm install

3 安装Axios:

1
npm install axios

4 设计页面(App.Vue):

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
<script setup type="module">
import axios from 'axios'
import { onMounted,reactive } from 'vue';
let getLoveMessage =()=>{
axios({
method:"post", // 请求方式
url:"https://api.uomg.com/api/rand.qinghua", // 请求的url
data:{
// 当请求方式为post时,data下的数据以JSON串放入请求体,否则以key=value形式放url后
username:"123456"
}
}).then( function (response){//响应成功时要执行的函数
console.log(response)
Object.assign(jsonData,response.data)
}).catch(function (error){// 响应失败时要执行的函数
console.log(error)
})
}
/* 通过onMounted生命周期,自动加载一次 */
onMounted(()=>{
getLoveMessage()
})
</script>
<template>
<div>
<h1>今日土味情话:{{jsonData.content}}</h1>
<button @click="getLoveMessage">获取今日土味情话</button>
</div>
</template>
<style scoped>
</style>

5 启动测试:

1
npm run dev

异步响应的数据结构:

  • 响应的数据是经过包装返回的!一个请求的响应包含以下信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
// `data` 由服务器提供的响应
data: {},
// `status` 来自服务器响应的 HTTP 状态码
status: 200,
// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: 'OK',
// `headers` 是服务器响应头
// 所有的 header 名称都是小写,而且可以使用方括号语法访问
// 例如: `response.headers['content-type']`
headers: {},
// `config` 是 `axios` 请求的配置信息
config: {},
// `request` 是生成此响应的请求
// 在node.js中它是最后一个ClientRequest实例 (in redirects),
// 在浏览器中则是 XMLHttpRequest 实例
request: {}
}
  • then取值
1
2
3
4
5
6
7
then(function (response) {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});

9.3 Axios get和post方法

配置添加语法:

​ params: {key:value,key2:value2} 会在url中拼接字符串. 多用于get请求

​ data: {key:value,key2:value2} 会在请求头中 添加 json串 传递数据 多用于post请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
axios.get(url[, config])
`axios.get(url, {
params:{
上面指定配置key:配置值,
上面指定配置key:配置值
}})
`

axios.post(url[, data[, config]])
`axios.post(url,
{
上面指定配置key:配置值,
上面指定配置key:配置值
}
)`

测试axios.get(… … ):

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
<script setup>
import axios from "axios"
import {ref,onMounted} from "vue"
let message = ref("")
let getLoveMessage = () => {
let url = 'https://api.uomg.com/api/rand.qinghua'
axios.get(url,
{
params: {id: 100,name: 'tom'},
//可以不加
headers: {'Accept' : 'application/json, text/plain, text/html,*/*'}
})
.then(function(response){
message.value = response.data.content
}).catch(function(error){
console.log(error)
})
}
onMounted(function(){
getLoveMessage()
})
</script>
<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>
<style scoped>
</style>

测试 axios.post(… …):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script setup>
import axios from "axios"
import {ref,onMounted} from "vue"
let message = ref("")
let getLoveMessage = () => {
let url = 'https://api.uomg.com/api/rand.qinghua'
axios.post(url,
{username:'admin',password:'123456'})
.then(function(response){
message.value = response.data.content
})
}
onMounted(function(){
getLoveMessage()
})
</script>

<template>
<div>
<h1>{{ message }}</h1>
</div>
</template>
<style scoped>
</style>

9.4 async和await

  • 可以使用async和await简化ajax请求
  • 作用: 使用同步方式实现异步调用
  • 规则:
    • async 标识函数
    • await 标识ajax请求
1
2
3
4
5
6
7
8
9
10
11
12
13
//async和await操作
async function getLoveMessage(){
let url = 'https://api.uomg.com/api/rand.qinghua'
let response = await axios.get(url)
message.value=response.data.content
}

//解构赋值操作
async function getLoveMessage(){
let url = 'https://api.uomg.com/api/rand.qinghua'
let {data} = await axios.get(url)
message.value=data.content
}

9.5 Axios 拦截器

如果想在axios发送请求之前,或者是数据响应回来在执行then方法之前做一些额外的工作,可以通过拦截器完成:

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
// 添加请求拦截器 请求发送之前
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
//config.headers['token'] = 'your_token_value'; //自动添加请求头的写法
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器 数据响应回来
axios.interceptors.response.use(
function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
console.log(response.data)
return response;
},
function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
}
);
  • 定义src/axios.js提取拦截器和配置语法
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
import axios from 'axios'
// 创建instance实例
const instance = axios.create({
baseURL:'https://api.uomg.com',
timeout:10000
})
// 添加请求拦截
instance.interceptors.request.use(
// 设置请求头配置信息
config=>{
//处理指定的请求头
console.log("before request")
config.headers.Accept = 'application/json, text/plain, text/html,*/*'
return config
},
// 设置请求错误处理函数
error=>{
console.log("request error")
return Promise.reject(error)
}
)
// 添加响应拦截器
instance.interceptors.response.use(
// 设置响应正确时的处理函数
response=>{
console.log("after success response")
console.log(response)
return response
},
// 设置响应异常时的处理函数
error=>{
console.log("after fail response")
console.log(error)
return Promise.reject(error)
}
)
// 默认导出
export default instance
  • App.vue
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
<script setup>
//1.导入优化后的axios
import axios from "./request/axios"

let getMsg = async () => {
let {data} = await axios.get
("rand.qinghua",
{params:
{username:"admin"}
})
console.log("ajax调用成功!!!!!")
console.log(data)
console.log(data.content)
}
</script>

<template>
<div>
<h1>Axios入门案例讲解</h1>
<button @click="getMsg">ajax拦截器用法</button>
</div>

</template>

<style scoped>
</style>

十、Vue3状态管理Pinia

10.1 Pinia介绍

如何实现多个组件之间的数据传递?

  • 方式1 组件传参 ;

  • 方式2 路由传参 ;

  • 方式3 通过pinia状态管理定义共享数据;

当我们有多个组件共享一个共同的状态(数据源)时,多个视图可能都依赖于同一份状态。类似于JavaWeb的Session

Pinia 是状态管理库,由 Vue 核心团队维护,对 Vue 2 和 Vue 3 都可用。https://pinia.vuejs.org/zh/introduction.html

10.2 Pinia基本用法

1 准备Vite项目:

1
2
3
npm create vite
npm install
npm install vue-router@4 --save

2 安装Pinia:

1
npm install pinia

3 定义pinia store对象 src/store/store.js (推荐这么命名不是强制):

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
import {defineStore} from "pinia"
//定义共享数据的变量
let userStore = defineStore({
//1.id共享对象的唯一标识符
id: "userStore",

//2.定义共享的数据信息 必须添加return返回值 天生响应式
state: function(){
return {
id: 100,
username: "张三",
age: 19
}
},

//3.定义获取属性的方法
getters: {
getId() {
return this.id
},
getName() {
return this.name
},
getAge(){
return this.age
}
},

//4.定义一些修改数据的操作
actions: {
updateUser(name){
this.name = name
}
}
})

export {
userStore
}

4 在main.js配置Pinia组件到Vue中 :

1
2
3
4
5
6
7
8
9
10
11
12
import { createApp } from 'vue'
import App from './App.vue'
import router from './routers/router.js'
// 导pinia
import { createPinia } from 'pinia'
// 创建pinia对象
let pinia= createPinia()
let app =createApp(App)
app.use(router)
// app中使用pinia功能
app.use(pinia)
app.mount('#app')

5 User.vue 中操作Pinia数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
//1.引入pinia的数据
import {userStore} from "../store/store"

//2.获取pinia对象
let userPina = userStore()

</script>

<template>
<div>
<h1>{{userPina.id}}</h1>
<h1>{{userPina.name}}</h1>
<h1>{{userPina.age}}</h1>
<!-- 利用get函数取值 类似于计算属性 -->
<h1>{{userPina.getId}}</h1>
<h1>{{userPina.getName}}</h1>
<h1>{{userPina.getAge}}</h1>
</div>
</template>
<style scoped>
</style>

6 Update.vue中展示Pinia数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
//1.引入pinia的数据
import {userStore} from "../store/store"

//2.获取pinia对象
let userPina = userStore()

let updateUser = () => {
userPina.id = 200
userPina.name = "李四"
userPina.age = 20
}

</script>

<template>
<div>
<button @click="updateUser">修改用户信息</button>
</div>
</template>
<style scoped>
</style

7 App.vue中通过路由切换组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script setup>
import User from "./components/User.vue"
import updateUser from "./components/UpdateUser.vue"
</script>

<template>
<div>
<User></User>
<!-- 可以使用驼峰规则命名 -->
<update-user></update-user>
</div>
</template>
<style scoped>
</style>

9 启动测试:

1
npm run dev

十一、案例开发-日程管理-第五期

11.1 前端代码处理

11.1.1 创建src/utils/request.js文件

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 axios from 'axios'
// 创建instance实例
const instance = axios.create({
baseURL:'http://localhost:8080/'
})
// 添加请求拦截
instance.interceptors.request.use(
// 设置请求头配置信息
config=>{
//处理指定的请求头
return config
},
// 设置请求错误处理函数
error=>{
return Promise.reject(error)
}
)
// 添加响应拦截器
instance.interceptors.response.use(
// 设置响应正确时的处理函数
response=>{
return response
},
// 设置响应异常时的处理函数
error=>{
return Promise.reject(error)
}
)
// 默认导出
export default instance

11.1.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
<script setup>
import{ ref,reactive} from 'vue'
import {useRouter} from "vue-router"
import request from '../utils/request'

//定义路由对象
let router = useRouter()

//1.定义注册对象
let user = ref({
username: "admin",
password: "123456",
password2: "123456"
})

let message = ref({
usernameMsg: '',
passwordMsg: '',
passwordMsg2: ''
})

//2.校验用户名
async function checkUsername(){
//定义正则表达式校验数据
let reg = /^[a-z0-9]{3,8}$/i
if(!reg.test(user.value.username)){
message.value.usernameMsg = '不合格'
return false
}

let {data} = await request.get("user/checkUsername?username="+user.value.username)
if(data.code !== 200){
message.value.usernameMsg = '用户名被占用'
return false
}else{
message.value.usernameMsg = '用户名可以使用'
return true
}
}

function checkPassword(){
//定义正则表达式校验数据 6位密码
let reg = /^[a-z0-9]{6}$/
console.log(user.value.password)
if(reg.test(user.value.password)){
message.value.passwordMsg = '通过'
return true
}else{
message.value.passwordMsg = '不合格'
return false
}
}

function checkPassword2(){
if(user.value.password === user.value.password2){
message.value.passwordMsg2 = '通过'
return true
}else{
message.value.passwordMsg2 = '不合格'
return false
}
}

//清空用户数据
function clearData(){
user.value = {
username: "",
password: "",
password2: ""
}
}

//注册用户数据
async function registUser(){
let flag = await checkUsername() && checkPassword() && checkPassword2()
if(flag){
//发起ajax请求
let response = await request.post("user/regist",user.value)
console.log(response.data)
if(response.data.code !== 200){
alert("注册失败!")
}else{
alert("用户注册成功!")
router.push("/login")
}
}
}

</script>
<template>
<div>
<h3 class="ht">请注册</h3>
<table class="tab" cellspacing="0px">
<tr class="ltr">
<td>请输入账号</td>
<td>
<input class="ipt" id="usernameInput" type="text" name="username" v-model="user.username" @blur="checkUsername">

<span id="usernameMsg" class="msg" v-text="message.usernameMsg"></span>
</td>
</tr>
<tr class="ltr">
<td>请输入密码</td>
<td>
<input class="ipt" id="userPwdInput" type="password" name="userPwd" v-model="user.password" @blur="checkPassword">
<span id="userPwdMsg" class="msg" v-text="message.passwordMsg"></span>
</td>
</tr>
<tr class="ltr">
<td>确认密码</td>
<td>
<input class="ipt" id="reUserPwdInput" type="password" v-model="user.password2" @blur="checkPassword2">
<span id="reUserPwdMsg" class="msg" v-text="message.passwordMsg2"></span>
</td>
</tr>
<tr class="ltr">
<td colspan="2" class="buttonContainer">
<input class="btn1" type="button" value="注册" @click="registUser">
<input class="btn1" type="button" value="重置" @click="clearData">
<router-link to="/login">
<button class="btn1">去登录</button>
</router-link>
</td>
</tr>
</table>
</div>
</template>
<style scoped>
.ht{
text-align: center;
color: cadetblue;
font-family: 幼圆;
}
.tab{
width: 500px;
border: 5px solid cadetblue;
margin: 0px auto;
border-radius: 5px;
font-family: 幼圆;
}
.ltr td{
border: 1px solid powderblue;

}
.ipt{
border: 0px;
width: 50%;

}
.btn1{
border: 2px solid powderblue;
border-radius: 4px;
width:60px;
background-color: antiquewhite;
}
.msg {
color: gold;
}
.buttonContainer{
text-align: center;
}
</style>

11.1.3 登录页面完成登录

  • 安装cookie组件
1
npm install js-cookie --save
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
<script setup>
import{ ref,reactive} from 'vue'
import request from "../utils/request"
import cookie from "js-cookie"
import {useRouter} from "vue-router"

let router = useRouter()
let user = ref({username:"admin",password:"123456"})
let message = ref({usernameMsg:"",passwordMsg:""})

let checkUsername = () => {
//校验字母或者数字3-8位
let reg = /^[a-z0-9]{3,8}$/i
if(!reg.test(user.value.username)){
message.value.usernameMsg = "用户名校验不通过"
return false
}else{
message.value.usernameMsg = "校验通过"
return true
}
}

let checkPassword = () => {
let reg = /^[a-z0-9]{6}$/
if(!reg.test(user.value.password)){
message.value.passwordMsg = "密码校验不通过"
return false
}else{
message.value.passwordMsg = "密码校验通过"
return true
}
}

let clearData = () => {
user.value = {username:"",password:""}
message.value = {usernameMsg:"",passwordMsg:""}
}

let login = async () => {
if(!checkUsername() && checkPassword()){
//如果校验不通过则不能提交数据
return
}
//发起ajax请求 实现用户登录操作
let {data} = await request.post("user/login",user.value)
if(data.code === 200){
//证明用户登录成功
alert("用户登录成功")
let token = data.data
//将token数据保存到cookie中 单位是天
cookie.set("token",token,{expires: 7})
//跳转到系统首页
router.push("/showSchedule")
}else{
//证明用户名或密码错误
alert("用户名或密码错误")
}
}
</script>
<template>
<div>
<h3 class="ht">请登录</h3>
<table class="tab" cellspacing="0px">
<tr class="ltr">
<td>请输入账号</td>
<td>
<input class="ipt" type="text" v-model="user.username" @change="checkUsername">
<span id="usernameMsg" v-text="message.usernameMsg"></span>
</td>
</tr>
<tr class="ltr">
<td>请输入密码</td>
<td>
<input class="ipt" type="password" v-model="user.password" @change="checkPassword">
<span id="userPwdMsg" v-text="message.passwordMsg"></span>
</td>
</tr>
<tr class="ltr">
<td colspan="2" class="buttonContainer">
<input class="btn1" type="button" value="登录" @click="login">
<input class="btn1" type="button" value="重置" @click="clearData">
<router-link to="/regist">
<button class="btn1">去注册</button>
</router-link>
</td>
</tr>
</table>
</div>
</template>
<style scoped>
.ht{
text-align: center;
color: cadetblue;
font-family: 幼圆;
}
.tab{
width: 500px;
border: 5px solid cadetblue;
margin: 0px auto;
border-radius: 5px;
font-family: 幼圆;
}
.ltr td{
border: 1px solid powderblue;
}
.ipt{
border: 0px;
width: 50%;
}
.btn1{
border: 2px solid powderblue;
border-radius: 4px;
width:60px;
background-color: antiquewhite;
}
#usernameMsg , #userPwdMsg {
color: gold;
}
.buttonContainer{
text-align: center;
}
</style>

11.2 后端代码处理

11.2.1 添加跨域处理器

11.2.1.1 什么是跨域

同源策略(SameOriginPolicy)是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。

所谓同源(即指在同一个域)就是两个页面具有

相同的协议(protocol),域名(host)和端口号

11.2.1.2为什么会产生跨域

前后端分离模式下,客户端请求前端服务器获取视图资源,然后客户端自行向后端服务器获取数据资源,前端服务器的 协议、IP和端口和后端服务器很可能是不一样的、这样就产生了跨域。

1683364198087
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
URL1: http://localhost:8080/xx/xxx
URL2: https://localhost:8080/xx/xxx
协议不同 是跨域请求

URL1: http://localhost:8090/xx/xxx
URL2: http://localhost:8080/xx/xxx
端口不同 是跨域请求

URL1: http://127.0.0.1:8080/xx/xxx
URL2: http://localhost:8080/xx/xxx
域名不同 是跨域请求


URL1: http://localhost/xx/xxx 默认端口:80
URL2: http://localhost:80/xx/xxx
同域请求


URL1: https://localhost/xx/xxx https 默认端口: 443
URL2: https://localhost:443/xx/xxx
同域请求
11.2.1.3 如何解决跨域

前端项目代理模式处理:

1683365066926

后端跨域过滤器方式处理:

1683364436315
  • CrosFilter过滤器
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
package com.atguigu.schedule.filter;
import com.atguigu.schedule.common.Result;
import com.atguigu.schedule.util.WebUtil;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/*")
public class CrosFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println(request.getMethod());
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT,OPTIONS, DELETE, HEAD");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority, content-type, version-info, X-Requested-With");
// 如果是跨域预检请求,则直接在此响应200业务码
if(request.getMethod().equalsIgnoreCase("OPTIONS")){
WebUtil.writeJson(response, Result.ok(null));
}else{
// 非预检请求,放行即可
filterChain.doFilter(servletRequest, servletResponse);
}
}
}
  • 未来我们使用框架,直接用一个@CrossOrigin 就可以解决跨域问题了。

11.2.2 JSON串转化API

  • 引入jar包

image-20231228170839222

11.2.3 重构UserController

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
package com.atguigu.controller;

import com.atguigu.common.Result;
import com.atguigu.common.ResultCodeEnum;
import com.atguigu.pojo.User;
import com.atguigu.service.UserService;
import com.atguigu.service.UserServiceImpl;
import com.atguigu.util.MD5Util;
import com.atguigu.util.WebUtil;
import com.mysql.cj.util.StringUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Random;

@WebServlet("/user/*")
public class UserController extends BaseController {

private UserService userService = new UserServiceImpl();

public void checkUsername(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取用户名
String username = req.getParameter("username");
//2.根据用户名查询数据
User user = userService.findUserByUsername(username);
//3.判断用户名是否存在
Result result = null;
if(user == null){
//说明: 用户名没有被使用 可以使用
result = Result.ok(null);
WebUtil.writeJson(resp,result);
}else{
result = Result.build(null,ResultCodeEnum.USERNAEM_ERROR);
WebUtil.writeJson(resp,result);
}
}


public void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
User user = WebUtil.readJson(req, User.class);
userService.insertUser(user.getUsername(),user.getPassword());
WebUtil.writeJson(resp,Result.ok(null));
}

public void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//接收用户传递的json数据
User user = WebUtil.readJson(req, User.class);
User userDB = userService.findUserByUsername(user.getUsername());
String md5pass = MD5Util.encrypt(user.getPassword());
if(userDB.getPassword().equals(md5pass)){
//返回密钥数据
String token = MD5Util.encrypt(user.getUsername() + new Random().nextInt(1000));
//根据用户名保存到session中
req.getSession().setAttribute(user.getUsername(),token);
WebUtil.writeJson(resp,Result.ok(token));
}else{
WebUtil.writeJson(resp,Result.build(null,ResultCodeEnum.PASSWORD_ERROR));
}
}
}

11.2.4 删除登录校验过滤器

  • 这里不使用cookie和session方式记录用户状态,未来使用token,所以登录过滤器删除即可。

十二、Element-plus组件库

12.1 Element-plus介绍

Element Plus 是一套基于 Vue 3 的开源 UI 组件库,是由饿了么前端团队开发的升级版本 Element UI。Element Plus 提供了丰富的 UI 组件、易于使用的 API 接口和灵活的主题定制功能,可以帮助开发者快速构建高质量的 Web 应用程序。

  • Element Plus 支持按需加载,且不依赖于任何第三方 CSS 库,它可以轻松地集成到任何 Vue.js 项目中。

  • Element Plus 目前已经推出了大量的常用 UI 组件,如按钮、表单、表格、对话框、选项卡等

    可以为开发者提供稳定的使用体验。

  • 与 Element UI 相比,Element Plus 采用了现代化的技术架构和更加先进的设计理念,同时具备更好的性能和更好的兼容性。

  • 官网https://element-plus.org/zh-CN/

  • 由于 Vue 3 不再支持 IE11,Element Plus 也不再支持 IE 浏览器。

12.2 Element-plus入门案例

1 准备vite项目:

1
2
3
4
5
npm create vite  // 注意选择 vue+typeScript
npm install
npm install vue-router@4 --save
npm install pinia
npm install axios

2 安装element-plus:

1
npm install element-plus

3 完整引入element-plus:

  • main.js
1
2
3
4
5
6
7
8
import { createApp } from 'vue'
//导入element-plus相关内容
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

4 入门案例:按钮标签

  • App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>

</script>

<template>
<div>
<el-button>Default</el-button>
<el-button type="primary">Primary</el-button>
<el-button type="success">Success</el-button>
<el-button type="info">Info</el-button>
<el-button type="warning">Warning</el-button>
<el-button type="danger">Danger</el-button>
</div>
</template>
<style scoped>
</style>

5 启动测试:

1
npm run dev

12.3 常用ElementPlus标签

12.3.1 Container 布局容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div class="common-layout">
<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
</el-container>

<el-container>
<el-header>Header</el-header>
<el-main>Main</el-main>
<el-footer>Footer</el-footer>
</el-container>
</div>
</template>

12.3.2 Form表单组件

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
<template>
<el-form :model="form" label-width="120px">
<el-form-item label="Activity name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="Activity zone">
<el-select v-model="form.region" placeholder="please select your zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="Activity time">
<el-col :span="11">
<el-date-picker
v-model="form.date1"
type="date"
placeholder="Pick a date"
style="width: 100%"
/>
</el-col>
<el-col :span="2" class="text-center">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-time-picker
v-model="form.date2"
placeholder="Pick a time"
style="width: 100%"
/>
</el-col>
</el-form-item>
<el-form-item label="Instant delivery">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="Activity type">
<el-checkbox-group v-model="form.type">
<el-checkbox label="Online activities" name="type" />
<el-checkbox label="Promotion activities" name="type" />
<el-checkbox label="Offline activities" name="type" />
<el-checkbox label="Simple brand exposure" name="type" />
</el-checkbox-group>
</el-form-item>
<el-form-item label="Resources">
<el-radio-group v-model="form.resource">
<el-radio label="Sponsor" />
<el-radio label="Venue" />
</el-radio-group>
</el-form-item>
<el-form-item label="Activity form">
<el-input v-model="form.desc" type="textarea" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Create</el-button>
<el-button>Cancel</el-button>
</el-form-item>
</el-form>
</template>

<script lang="ts" setup>
import { reactive } from 'vue'

// do not use same name with ref
const form = reactive({
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: '',
})

const onSubmit = () => {
console.log('submit!')
}
</script>

12.3.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
<template>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
</el-table>
</template>

<script lang="ts" setup>
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles',
},
]
</script>

12.4 ElementPlus课堂案例练习

12.4.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
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
<template>

<!-- 1.定义容器 上下结构 -->
<el-container>
<h1 style="margin: auto;margin-top: 20px;font-size:45px;color:blue">用户管理系统</h1>
<el-header>
<el-button type="primary" style="margin-top:20px">新增用户</el-button>
</el-header>
<el-main>
<!-- 定义表格
:data="数据的来源 数组类型"
prop="date" 数据中的属性
label="Date" 显示字段的名称
stripe: 带斑马纹
border: 带边框线
height="250" 固定表头信息
align="center" 定义对齐方式 居中
-->
<el-table :data="tableData" style="width: 100%" stripe border height="250">

<el-table-column prop="id" label="编号" align="center"/>
<el-table-column prop="name" label="姓名" align="center"/>
<el-table-column prop="age" label="年龄" align="center"/>
<el-table-column prop="sex" label="性别" align="center"/>
<el-table-column label="操作" align="center">
<el-button type="success">修改</el-button>
<el-button type="danger">删除</el-button>
</el-table-column>
</el-table>

<!-- 定义分页插件 -->
<div class="example-pagination-block" >
<!-- 属性介绍 :
1.style="justify-content: center; 水平居中
2.background 分页带背景色
3.layout="prev, pager, next"
prev: 左箭头
pager: 页数
next: 右箭头
total: 显示总页数
sizes: 改变每页条数
4.total: 总条数
current-page: 当前页数
page-size: 页面条数
@size-change 页数改变时调用函数
@current-change 手动点击页数时调用
-->
<el-pagination
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
:page-sizes="pagination.pageSizes"
layout="total,sizes,prev, pager, next"
:total="pagination.total"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
style="justify-content: center;"/>
</div>
</el-main>
</el-container>

</template>

<script lang="ts" setup>
import {ref} from "vue"
const tableData = [
{
id: 100,
name: '安琪拉',
age: 20,
sex: '男',
},
{
id: 100,
name: '安琪拉',
age: 20,
sex: '男',
},
{
id: 100,
name: '安琪拉',
age: 20,
sex: '男',
}
]

//定义分页对象
let pagination = ref({
currentPage: 1,
pageSize: 5,
pageSizes: [5,10,20,40],
total: 500
})


let handleSizeChange = (newSize) => {
alert("每页条数:"+newSize)
}

let handleCurrentChange = (newCurrent) => {
alert("当前页数:"+newCurrent)
}

</script>

<style scoped>
.example-pagination-block{
margin-top: 20px;
margin-bottom: 16px;
}
</style>

image-20231225175617231

12.4.2 分页列表显示

查询top_news中的user表

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
 <!-- 定义分页插件 -->
<div class="example-pagination-block" >
<!-- 属性介绍 :
1.style="justify-content: center; 水平居中
2.background 分页带背景色
3.layout="prev, pager, next"
prev: 左箭头
pager: 页数
next: 右箭头
total: 显示总页数
sizes: 改变每页条数
4.total: 总条数
current-page: 当前页数
page-size: 页面条数
@size-change 页数改变时调用函数
@current-change 手动点击页数时调用
-->
<el-pagination
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
:page-sizes="pagination.pageSizes"
layout="total,sizes,prev, pager, next"
:total="pagination.total"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
style="justify-content: center;"/>
</div>

<script lang="ts" setup>
import {ref,onMounted,reactive} from "vue"
import request from "./utils/request"

//定义分页对象
let pagination = ref({
currentPage: 1,
pageSize: 5,
pageSizes: [5,10,20,40],
total: 500
})


let handleSizeChange = (newSize) => {
//alert("每页条数:"+newSize)
}

let handleCurrentChange = (newCurrent) => {
//alert("当前页数:"+newCurrent)
}

</script>

<style scoped>
.example-pagination-block{
margin-top: 20px;
margin-bottom: 16px;
}
</style>

image-20231225184841558

12.4.3 新增页面

FORM表单操作

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
<!-- 定义对话框
v-model="dialogVisible" 控制是否显示 true 打开对话框 false 关闭对话框
:before-close="handleClose" 关闭对话框时 调用的函数
-->
<el-dialog v-model="dialogVisible" title="用户新增-更新操作" width="35%" :before-close="handleClose">

<!--
1.:model="form" form表单
2.label-position = "对齐方式 左对齐 右对齐 top对齐"
3.:rules="rules" 校验规则
4. ref="ruleFormRef" 代表整个表单对象 let ruleFormRef = ref(null)
-->
<el-form :model="form" label-width="100px" :label-position="labelPosition" :rules="rules" ref="ruleFormRef">
<el-form-item label="ID号:">
<el-input v-model="form.id" disabled/>
</el-form-item>
<el-form-item label="姓名:" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="年龄:" prop="age">
<el-input v-model.number="form.age" />
</el-form-item>
<el-form-item label="性别:" prop="sex">
<el-radio-group v-model="form.sex">
<el-radio label="男" />
<el-radio label="女" />
</el-radio-group>
</el-form-item>
</el-form>

<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="onSubmit">
确定
</el-button>
</span>
</template>
</el-dialog>
image-20231225234912804

作用域插槽

1
2
3
4
5
6
7
8
9
10
<el-table :data="tableData" style="width: 100%" stripe height="400"  border>

<el-table-column label="操作" align="center">
<!-- 作用域插槽 -->
<template #default="scope">
<el-button type="success" @click="findUserByRow(scope.row)">修改</el-button>
<el-button type="danger" @click="deleteUser(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>

消息确认框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { ElMessage, ElMessageBox } from 'element-plus'

let deleteUser = async (user) => {
//添加消息确认框 添加同步操作 如果点击确定 返回值特定值 confirm
let result = await ElMessageBox.confirm(
'你确定删除数据吗?',
'提示信息:',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)

if(result === "confirm"){
let params = {id: user.id}
let {data} = await request.delete("user/deleteUserById",{params})
if(data.code === 200)
ElMessage.success("删除用户成功!")
else
ElMessage.error("删除用户失败!")
findUserList()
}
}

image-20231225234824357