# 第七章 前端工程化 ## 一、前端工程化开篇 ### 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 可以提供丰富的快速构建网页的组件仓库 ## 二、ECMA6Script ### 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的核心语言特性和功能方面取得了很大的进展。由于ES6已经成为了JavaScript的标准,它的大多数新特性都已被现在浏览器所支持,因此现在可以放心地使用ES6来开发前端应用程序。 **历史版本:** | 标准版本 | 发布时间 | 新特性 | | -------- | -------- | ------------------------------------------------------------ | | 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 新增了`let`和`const`,用来声明变量,使用的细节上也存在诸多差异 > + let 和var的差别 1、let 不能重复声明 2、let有块级作用域,非函数的花括号遇见let会有块级作用域,也就是只能在花括号里面访问。 3、let不会预解析进行变量提升 4、let 定义的全局变量不会作为window的属性 5、let在es6中推荐优先使用 ```html ``` + const和var的差异 1、新增const和let类似,只是const定义的变量不能修改 2、并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。 ```html ``` > 模板字符串(template string)是增强版的字符串,用反引号(`)标识 1、字符串中可以出现换行符 2、可以使用 ${xxx} 形式输出变量和拼接变量 ``` html ``` ### 2.3 es6的解构表达式 > ES6 的解构赋值是一种方便的语法,可以快速将数组或对象中的值拆分并赋值给变量。解构赋值的语法使用花括号 `{}` 表示对象,方括号 `[]` 表示数组。通过解构赋值,函数更方便进行参数接受等! > **数组解构赋值** + 可以通过数组解构将数组中的值赋值给变量,语法为: ```javascript let [a, b, c] = [1, 2, 3]; //新增变量名任意合法即可,本质是按照顺序进行初始化变量的值 console.log(a); // 1 console.log(b); // 2 console.log(c); // 3 ``` + 该语句将数组 \[1, 2, 3] 中的第一个值赋值给 a 变量,第二个值赋值给 b 变量,第三个值赋值给 c 变量。 可以使用默认值为变量提供备选值,在数组中缺失对应位置的值时使用该默认值。例如: ```javascript let [a, b, c, d = 4] = [1, 2, 3]; console.log(d); // 4 ``` > **对象解构赋值** + 可以通过对象解构将对象中的值赋值给变量,语法为: ```javascript let {a, b} = {a: 1, b: 2}; //新增变量名必须和属性名相同,本质是初始化变量的值为对象中同名属性的值 //等价于 let a = 对象.a let b = 对象.b console.log(a); // 1 console.log(b); // 2 ``` + 该语句将对象 {a: 1, b: 2} 中的 a 属性值赋值给 a 变量,b 属性值赋值给 b 变量。 可以为标识符分配不同的变量名称,使用 : 操作符指定新的变量名。例如: ```javascript let {a: x, b: y} = {a: 1, b: 2}; console.log(x); // 1 console.log(y); // 2 ``` > **函数参数解构赋值** + 解构赋值也可以用于函数参数。例如: ```javascript function add([x, y]) { return x + y; } add([1, 2]); // 3 ``` + 该函数接受一个数组作为参数,将其中的第一个值赋给 x,第二个值赋给 y,然后返回它们的和。 + ES6 解构赋值让变量的初始化更加简单和便捷。通过解构赋值,我们可以访问到对象中的属性,并将其赋值给对应的变量,从而提高代码的可读性和可维护性。 ### 2.4 es6的箭头函数 > ES6 允许使用“箭头” 义函数。语法类似Java中的Lambda表达式 #### 2.4.1 声明和特点 ```html ``` #### 2.4.2 实践和应用场景 ```html Document
``` #### 2.4.3 rest和spread > rest参数,在形参上使用 和JAVA中的可变参数几乎一样 ``` html ``` > spread参数,在实参上使用rest ```html ``` ### 2.5 es6的对象创建和拷贝 #### 2.5.1 对象创建的语法糖 > ES6中新增了对象创建的语法糖,支持了class extends constructor等关键字,让ES6的语法和面向对象的语法更加接近 ``` javascript 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("面条") ``` #### 2.5.2 对象的深拷贝和浅拷贝 > 对象的拷贝,快速获得一个和已有对象相同的对象的方式 + 浅拷贝 ``` html ``` + 深拷贝 ``` html ``` ### 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](images/1684461046181.png) + module.js 向外分别暴露成员 ``` javascript //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中的成员 ``` javascript /* *代表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 ``` html

{{headline}}


``` ## 五、Vue3通过Vite实现工程化 ### 5.1 Vite的介绍 1684488376469 1684488405011 > 在浏览器支持 ES 模块之前,JavaScript 并没有提供原生机制让开发者以模块化的方式进行开发。这也正是我们对 “打包” 这个概念熟悉的原因:使用工具抓取、处理并将我们的源码模块串联成可以在浏览器中运行的文件。时过境迁,我们见证了诸如 [webpack](https://webpack.js.org/ "webpack")、[Rollup](https://rollupjs.org/ "Rollup") 和 [Parcel](https://parceljs.org/ "Parcel") 等工具的变迁,它们极大地改善了前端开发者的开发体验 + 当我们开始构建越来越大型的应用时,需要处理的 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中打开命令行运行如下命令 ```shell npm create vite@latest ``` + 第一次使用vite时会提示下载vite,输入y回车即可,下次使用vite就不会出现了 ![1687769339457](images/1687769339457.png) + 注意: 选择vue+JavaScript选项即可 > 2 安装项目所需依赖 + cd进入刚刚创建的项目目录 + npm install命令安装基础依赖 ``` shell cd ./vue3-demo1 npm install ``` > 3 启动项目 + 查看项目下的package.json ```json { "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" } } ``` ``` shell npm run dev ``` > 5 停止项目 + 命令行上 ctrl+c #### 5.2.2 Vite+Vue3项目的目录结构 > 1.下面是 Vite 项目结构和入口的详细说明: ![1684489112904](images/1684489112904.png) - 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) ```json { "scripts": { "dev": "vite", // 启动开发服务器,别名:`vite dev`,`vite serve` "build": "vite build", // 为生产环境构建产物 "preview": "vite preview" // 本地预览生产构建产物 } } ``` + 运行设置端口号:(vite.config.js) ```javascript //修改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中的概念 该文件由三个部分组成 ` ``` > 2 vue3响应式数据入门 ```html ``` > 3 vue3 setup函数和语法糖 + 位置:src/App.vue ```vue ``` #### 5.2.5 Vite+Vue3关于样式的导入方式 1. 全局引入main.js ```javascript import './style/reset.css' //书写引入的资源的相对路径即可! ``` 2. vue文件script代码引入 ```javascript import './style/reset.css' ``` 3. Vue文件style代码引入 ```javascript @import './style/reset.css' ``` #### 5.2.6 关于JS和TS选择的问题 > TS是JS的一个超集,使用TS之后,JS的语法更加的像JAVA,实际开发中用的确实更多,那么这里为什么我们没有立即使用TS进行开发,原因如下 1 为了降低难度,提高前端工程化的效率 2 对于学JAVA的我们来说,学习TS非常容易,但是还是需要一些时间 3 TS不是非学不可,不用TS仍然可以正常开发工程化的前端项目 4 尚硅谷已经发布了TS的专项课程,请大家在B站上自行搜索 "尚硅谷 TS" 5 建议大家先学完完整的前端工程化内容,然后再根据需求单独学习TS即可 ## 六、Vue3视图渲染技术 ### 6.1 模版语法 > Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。 #### 6.1.1 插值表达式和文本渲染 > 插值表达式:最基本的数据绑定形式是文本插值,它使用的是“Mustache”语法 ,即双大括号`{{}}` + 插值表达式是将数据渲染到元素的指定位置的手段之一 + 插值表达式不绝对依赖标签,其位置相对自由 + 插值表达式中支持javascript的运算表达式 + 插值表达式中也支持函数的调用 ``` html ``` > 为了渲染双标中的文本,我们也可以选择使用`v-text`和`v-html`命令 + v-*** 这种写法的方式使用的是vue的命令 + v-***的命令必须依赖元素,并且要写在元素的开始标签中 + v-***指令支持ES6中的字符串模板 + 插值表达式中支持javascript的运算表达式 + 插值表达式中也支持函数的调用 + v-text可以将数据渲染成双标签中间的文本,但是不识别html元素结构的文本 + v-html可以将数据渲染成双标签中间的文本,识别html元素结构的文本 ``` html ``` #### 6.1.2 Attribute属性渲染 > 想要渲染一个元素的 attribute,应该使用 `v-bind`指令 + 由于插值表达式不能直接放在标签的属性中,所有要渲染元素的属性就应该使用v-bind + v-bind可以用于渲染任何元素的属性,语法为 `v-bind:属性名='数据名'`, 可以简写为 `:属性名='数据名'` ``` html ``` #### 6.1.3 事件的绑定 > 我们可以使用 `v-on` 来监听 DOM 事件,并在事件触发时执行对应的 Vue的JavaScript代码。 + 用法:`v-on:click="handler"` 或简写为 `@click="handler"` + vue中的事件名=原生事件名去掉`on` 前缀 如:`onClick --> click` + handler的值可以是方法事件处理器,也可以是内联事件处理器 + 绑定事件时,可以通过一些绑定的修饰符,常见的事件修饰符如下 + `.once:只触发一次事件。[重点]` + `.prevent:阻止默认事件。[重点]` + .stop:阻止事件冒泡。 + .capture:使用事件捕获模式而不是冒泡模式。 + .self:只在事件发送者自身触发时才触发事件。 ``` html ``` ### 6.2 响应式基础 > 此处的响应式是指 : 数据模型发生变化时,自动更新DOM树内容,页面上显示的内容会进行同步变化,vue3的数据模型不是自动响应式的,需要我们做一些特殊的处理 #### 6.2.1 响应式需求案例 > 需求:实现 + - 按钮,实现数字加一减一 ```html ``` #### 6.2.2 响应式实现关键字ref > `ref` 可以将一个基本类型的数据(如字符串,数字等)转换为一个响应式对象。 `ref` 只能包裹单一元素 ```html ``` + 在上面的例子中,我们使用 `ref` 包裹了一个数字,在代码中给这个数字加 1 后,视图也会跟着动态更新。需要注意的是,由于使用了 `ref`,因此需要在访问该对象时使用 `.value` 来获取其实际值。 #### 6.2.3 响应式实现关键字reactive > 我们可以使用 [reactive()](https://cn.vuejs.org/api/reactivity-core.html#reactive "reactive()") 函数创建一个响应式对象或数组: ```html ``` > 对比ref和reactive: + 使用 `ref` 适用于以下开发场景: + 包装基本类型数据:`ref` 主要用于包装基本类型数据(如字符串、数字等),即只有一个值的数据,如果你想监听这个值的变化,用 `ref` 最为方便。在组件中使用时也很常见。 + 访问方式简单:`ref` 对象在访问时与普通的基本类型值没有太大区别,只需要通过 `.value` 访问其实际值即可。 + 使用 `reactive` 适用于以下开发场景: + 包装复杂对象:`reactive` 可以将一个普通对象转化为响应式对象,这样在数据变化时会自动更新界面,特别适用于处理复杂对象或者数据结构。 + 需要递归监听的属性:使用 `reactive` 可以递归追踪所有响应式对象内部的变化,从而保证界面的自动更新。 + 综上所述,`ref` 适用与简单情形下的数据双向绑定,对于只有一个字符等基本类型数据或自定义组件等情况,建议可以使用 `ref`;而对于对象、函数等较为复杂的数据结构,以及需要递归监听的属性变化,建议使用 `reactive`。当然,在实际项目中根据需求灵活选择也是十分必要的。 #### 6.2.4 扩展响应式关键字toRefs 和 toRef > toRef基于reactive响应式对象上的一个属性,创建一个对应的 ref响应式数据。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。toRefs将一个响应式对象多个属性转换为一个多个ref数据,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 [toRef()](https://cn.vuejs.org/api/reactivity-utilities.html#toref "toRef()") 创建的。 案例:响应显示reactive对象属性 ```html ``` ### 6.3 条件和列表渲染 #### 6.3.1 条件渲染 > `v-if` 条件渲染 + `v-if='表达式' `只会在指令的表达式返回真值时才被渲染 + 也可以使用 `v-else` 为 `v-if` 添加一个“else 区块”。 + 一个 `v-else` 元素必须跟在一个 `v-if` 元素后面,否则它将不会被识别。 ```html ``` > `v-show`条件渲染扩展: + 另一个可以用来按条件显示一个元素的指令是 `v-show`。其用法基本一样: + 不同之处在于 `v-show` 会在 DOM 渲染中保留该元素;`v-show` 仅切换了该元素上名为 `display` 的 CSS 属性。 + `v-show` 不支持在 ` ``` ### 6.5 属性计算 > 模板中的表达式虽然方便,但也只能用来做简单的操作。如果在模板中写太多逻辑,会让模板变得臃肿,难以维护。比如说,我们有这样一个包含嵌套数组的对象: ```html ``` + 这里的模板看起来有些复杂。我们必须认真看好一会儿才能明白它的计算依赖于 `author.books`。更重要的是,如果在模板中需要不止一次这样的计算,我们可不想将这样的代码在模板里重复好多遍。 > 因此我们推荐使用**计算属性**来描述依赖响应式状态的复杂逻辑。这是重构后的示例: ```html ``` + 我们在这里定义了一个计算属性 `publishedBooksMessage`。`computed()` 方法期望接收一个 getter 函数,返回值为一个**计算属性 ref**。和其他一般的 ref 类似,你可以通过 `publishedBooksMessage.value` 访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 `.value`。 + Vue 的计算属性会自动追踪响应式依赖。它会检测到 `publishedBooksMessage` 依赖于 `author.books`,所以当 `author.books` 改变时,任何依赖于 `publishedBooksMessage` 的绑定都会同时更新。 > 计算属性缓存 vs 方法 + 若我们将同样的函数定义为一个方法而不是计算属性,两种方式在结果上确实是完全相同的,然而,不同之处在于**计算属性值会基于其响应式依赖被缓存**。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 `author.books` 不改变,无论多少次访问 `publishedBooksMessage` 都会立即返回先前的计算结果! ### 6.6 数据监听器 > 计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。我们可以使用 [watch](https://cn.vuejs.org/api/reactivity-core.html#watch "watch")[ 函数](https://cn.vuejs.org/api/reactivity-core.html#watch " 函数")在每次响应式状态发生变化时触发回调函数: + watch主要用于以下场景: + 当数据发生变化时需要执行相应的操作 + 监听数据变化,当满足一定条件时触发相应操作 + 在异步操作前或操作后需要执行相应的操作 > 监控响应式数据(watch): ```html ``` > 监控响应式数据(watchEffect): + watchEffect默认监听所有的响应式数据 ```html ``` > `watch` vs. `watchEffect` + `watch` 和 `watchEffect` 都能响应式地执行有副作用的回调。它们之间的主要区别是追踪响应式依赖的方式: + `watch` 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。`watch` 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。 + `watchEffect`,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确。 ### 6.7. Vue生命周期 #### 6.7.1 生命周期简介 > 每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为`生命周期钩子的函数`,让开发者有机会在特定阶段运行自己的代码! + 周期图解: + 常见钩子函数 + onMounted() 注册一个回调函数,在组件挂载完成后执行。 + onUpdated() 注册一个回调函数,在组件因为响应式状态变更而更新其 DOM 树之后调用。 + onUnmounted() 注册一个回调函数,在组件实例被卸载之后调用。 + onBeforeMount() 注册一个钩子,在组件被挂载之前被调用。 + onBeforeUpdate() 注册一个钩子,在组件即将因为响应式状态变更而更新其 DOM 树之前调用。 + onBeforeUnmount() 注册一个钩子,在组件实例被卸载之前调用。 #### 6.7.2 生命周期案例 ```html ``` ### 6.8 Vue组件 #### 6.8.1 组件基础 > 组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。组件就是实现应用中局部功能代码和资源的集合!在实际应用中,组件常常被组织成层层嵌套的树状结构: + 这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。 > 传统方式编写应用: > 组件方式编写应用: + 组件化:对js/css/html统一封装,这是VUE中的概念 + 模块化:对js的统一封装,这是ES6中的概念 + 组件化中,对js部分代码的处理使用ES6中的模块化 #### 6.8.2 组件化入门案例 > 案例需求: 创建一个页面,包含头部和菜单以及内容显示区域,每个区域使用独立组建! ![1686885192862](images/1686885192862.png) > 1 准备vue项目 ```shell npm create vite cd vite项目 npm install ``` > 2 安装相关依赖 ```shell npm install sass npm install bootstrap ``` > 3 创建子组件 在src/components文件下 vscode需要安装Vetur插件,这样vue文件有快捷提示 + Header.vue ```html ``` + Navigator.vue ```html ``` + Content.vue ```html ``` + App.vue 入口组件App引入组件 ```html ``` > 4 启动测试 ```shell npm run dev ``` #### 6.8.3 组件之间传递数据 ##### 6.8.3.1 父传子 > Vue3 中父组件向子组件传值可以通过 props 进行,具体操作如下: 1. 首先,在父组件中定义需要传递给子组件的值,接着,在父组件的模板中引入子组件,同时在引入子组件的标签中添加 props 属性并为其设置需要传递的值。 2. 在 Vue3 中,父组件通过 props 传递给子组件的值是响应式的。也就是说,如果在父组件中的传递的值发生了改变,子组件中的值也会相应地更新。 + 父组件代码:App.vue ```html ``` + 子组件代码:Son.vue ```html ``` ##### 6.8.3.2 子传父 + 父组件: App.vue ```html ``` + 子组件:Son.vue ```html ``` ##### 6.8.3.3 兄弟传参 ![](images/image_hZ6yocZGY3.png) + Navigator.vue: 发送数据到App.vue ```html ``` + App.vue: 发送数据到Content.vue ```html ``` + Content.vue ```html ``` ## 七、Vue3路由机制router ### 7.1 路由简介 > 1 什么是路由? - 定义:路由就是根据不同的 URL 地址展示不同的内容或页面。 - 通俗理解:路由就像是一个地图,我们要去不同的地方,需要通过不同的路线进行导航。 > 2 路由的作用 - 单页应用程序(SPA)中,路由可以实现不同视图之间的无刷新切换,提升用户体验; - 路由还可以实现页面的认证和权限控制,保护用户的隐私和安全; - 路由还可以利用浏览器的前进与后退,帮助用户更好地回到之前访问过的页面。 ### 7.2 路由入门案例 > 1 案例需求分析 ​ > 2 创建项目和导入路由依赖 ```shell npm create vite //创建项目cd 项目文件夹 //进入项目文件夹 npm install //安装项目需求依赖 npm install vue-router@4 --save //安装全局的vue-router 4版本 ``` > 3 准备页面和组件 + components/Home.vue ``` html ``` + components/List.vue ``` html ``` + components/Add.vue ``` html ``` + components/Update.vue ``` html ``` + App.vue ``` html ``` > 4 准备路由配置 + src/routers/router.js ```javascript // 导入路由创建的相关方法 import {createRouter,createWebHashHistory} from 'vue-router' // 导入vue组件 import Home from '../components/Home.vue' import List from '../components/List.vue' import Add from '../components/Add.vue' import Update from '../components/Update.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 } }, { path:'/add', components:{ addView:Add } }, { path:'/update', components:{ updateView:Update } }, ] }) // 对外暴露路由对象 export default router; ``` > 5 main.js引入router配置 + 修改文件:main.js (入口文件) ```javascript 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") ``` > 6 启动测试 ``` shell npm run dev ``` ### 7.3 路由重定向 > 重定向的作用:将一个路由重定向到另一个路由上 + 修改案例:访问/list和/showAll都定向到List.vue + router.js ``` javascript // 导入路由创建的相关方法 import {createRouter,createWebHashHistory} from 'vue-router' // 导入vue组件 import Home from '../components/Home.vue' import List from '../components/List.vue' import Add from '../components/Add.vue' import Update from '../components/Update.vue' // 创建路由对象,声明路由规则 const router = createRouter({ history: createWebHashHistory(), routes:[ { path:'/', components:{ default:Home, homeView:Home } }, { path:'/list', components:{ listView : List } }, { path:'/showAll', // 重定向 redirect :'/list' }, { path:'/add', components:{ addView:Add } }, { path:'/update', components:{ updateView:Update } }, ] }) // 对外暴露路由对象 export default router; ``` + App.vue ```html ``` ### 7.4 编程式路由(useRouter) > 普通路由 + `list页 `这种路由,to中的内容目前是固定的,点击后只能切换/list对象组件(声明式路由) > 编程式路由 + 通过useRouter,动态决定向那个组件切换的路由 + 在 Vue 3 和 Vue Router 4 中,你可以使用 `useRouter` 来实现动态路由(编程式路由) + 这里的 `useRouter` 方法返回的是一个 router 对象,你可以用它来做如导航到新页面、返回上一页面等操作。 > 案例需求: 通过普通按钮配合事件绑定实现路由页面跳转,不直接使用router-link标签 + App.vue ``` html ``` ### 7.5 路由传参(useRoute) > 路径参数 + 在路径中使用一个动态字段来实现,我们称之为 **路径参数** + 例如: 查看数据详情 `/showDetail/1` ,`1`就是要查看详情的id,可以动态添值! > 键值对参数 + 类似与get请求通过url传参,数据是键值对形式的 + 例如: 查看数据详情`/showDetail?hid=1`,`hid=1`就是要传递的键值对参数 + 在 Vue 3 和 Vue Router 4 中,你可以使用 `useRoute` 这个函数从 Vue 的组合式 API 中获取路由对象。 + `useRoute` 方法返回的是当前的 route 对象,你可以用它来获取关于当前路由的信息,如当前的路径、查询参数等。 > 案例需求 : 切换到ShowDetail.vue组件时,向该组件通过路由传递参数 + 修改App.vue文件 ``` html ``` + 修改router.js增加路径参数占位符 ``` javascript // 导入路由创建的相关方法 import {createRouter,createWebHashHistory} from 'vue-router' // 导入vue组件 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获取路径参数 ``` html ``` - ShowDetail2.vue通过useRoute获取键值对参数 ```html ``` ### 7.6 路由守卫 > 在 Vue 3 中,路由守卫是用于在路由切换期间进行一些特定任务的回调函数。路由守卫可以用于许多任务,例如验证用户是否已登录、在路由切换前提供确认提示、请求数据等。Vue 3 为路由守卫提供了全面的支持,并提供了以下几种类型的路由守卫: 1. **全局前置守卫**:在路由切换前被调用,可以用于验证用户是否已登录、中断导航、请求数据等。 2. **全局后置守卫**:在路由切换之后被调用,可以用于处理数据、操作 DOM 、记录日志等。 3. **守卫代码的位置**: 在router.js中 ```javascript //全局前置路由守卫 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}`); }); ``` > 登录案例,登录以后才可以进入home,否则必须进入login + 定义Login.vue ```html ``` + 定义Home.vue ```html ``` + App.vue ```html ``` + 定义routers.js ``` javascript // 导入路由创建的相关方法 import {createRouter,createWebHashHistory} from 'vue-router' // 导入vue组件 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; ``` + 启动测试 ```shell npm run dev ``` ## 八、 案例开发-日程管理-第五期 ### 8.1 重构前端工程 > 业务目标展示 + 登录页 ![1690533751478](images/1690533751478.png) + 注册页 ![1690533770048](images/1690533770048.png) + 日程管理页 ![1690533809917](images/1690533809917.png) > 创建项目,安装依赖 ``` shell npm create vite cd 项目目录 npm install npm install vue-router ``` + 项目结构如下 ![1690516506417](images/1690516506417.png) > 开发视图 + Header.vue视图 ``` html ``` + Login.vue视图 ``` html ``` + Regist.vue视图 ``` html ``` + ShowSchedule.vue视图 ``` html ``` + App.vue视图 ``` html ``` + 配置路由 ``` javascript 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 ``` javascript 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.0 预讲知识-promise #### 9.0.1 普通函数和回调函数 > 普通函数: 正常调用的函数,一般函数执行完毕后才会继续执行下一行代码 ``` html ``` > 回调函数: 一些特殊的函数,表示未来才会执行的一些功能,后续代码不会等待该函数执行完毕就开始执行了 ```html ``` #### 9.0.2 Promise 简介 > 前端中的异步编程技术,类似Java中的多线程+线程结果回调! + Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了`Promise`对象。 + 所谓`Promise`,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。 > `Promise`对象有以下两个特点。 (1)Promise对象代表一个异步操作,有三种状态:`Pending`(进行中)、`Resolved`(已完成,又称 Fulfilled)和`Rejected`(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是`Promise`这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。 (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从`Pending`变为`Resolved`和从`Pending`变为`Rejected`。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。 #### 9.0.3 Promise 基本用法 > ES6规定,Promise对象是一个构造函数,用来生成Promise实例。 ```html ``` #### 9.0.4 Promise catch() > `Promise.prototype.catch`方法是`.then(null, rejection)`的别名,用于指定发生错误时的回调函数。 ```html ``` #### 9.0.5 async和await的使用 > async和await是ES6中用于处理异步操作的新特性。通常,异步操作会涉及到Promise对象,而async/await则是在Promise基础上提供了更加直观和易于使用的语法。 > async 用于标识函数的 1. async标识函数后,async函数的返回值会变成一个promise对象 2. 如果函数内部返回的数据是一个非promise对象,async函数的结果会返回一个成功状态 promise对象 3. 如果函数内部返回的是一个promise对象,则async函数返回的状态与结果由该对象决定 4. 如果函数内部抛出的是一个异常,则async函数返回的是一个失败的promise对象 ``` html ``` > await 1. await右侧的表达式一般为一个promise对象,但是也可以是一个其他值 2. 如果表达式是promise对象,await返回的是promise成功的值 3. await会等右边的promise对象执行结束,然后再获取结果,后续代码也会等待await的执行 4. 如果表达式是其他值,则直接返回该值 5. await必须在async函数中,但是async函数中可以没有await 6. 如果await右边的promise失败了,就会抛出异常,需要通过 try ... catch捕获处理 ``` html ``` ### 9.1 Axios介绍 > ajax + AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。 + AJAX 不是新的编程语言,而是一种使用现有标准的新方法。 + AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。 + AJAX 不需要任何浏览器插件,但需要用户允许 JavaScript 在浏览器上执行。 + XMLHttpRequest 只是实现 Ajax 的一种方式。 **ajax工作原理:** ![](images/image_bjXPJoLb6a.png) > 原生**javascript方式进行ajax(了解):** ```html ``` > 什么是axios 官网介绍:https://axios-http.com/zh/docs/intro + Axios 是一个基于 [*promise*](https://javascript.info/promise-basics "promise") 网络请求库,作用于[node.js](https://nodejs.org/ "node.js") 和浏览器中。 它是 [*isomorphic*](https://www.lullabot.com/articles/what-is-an-isomorphic-application "isomorphic") 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js `http` 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。它有如下特性 + 从浏览器创建 [XMLHttpRequests](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest "XMLHttpRequests") + 从 node.js 创建 [http](http://nodejs.org/api/http.html "http") 请求 + 支持 [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise "Promise") API + 拦截请求和响应 + 转换请求和响应数据 + 取消请求 + 自动转换JSON数据 + 客户端支持防御[XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery "XSRF") ### 9.2 Axios 入门案例 > 1 案例需求:请求后台获取随机土味情话 + 请求的url ``` http https://api.uomg.com/api/rand.qinghua?format=json 或者使用 http://forum.atguigu.cn/api/rand.qinghua?format=json ``` + 请求的方式 ``` http GET/POST ``` + 数据返回的格式 ```json {"code":1,"content":"我努力不是为了你而是因为你。"} ``` > 2 准备项目 ```javascript npm create vite npm install /*npm install vue-router@4 --save npm install pinia */ ``` > 3 安装axios ```shell npm install axios ``` > 4 设计页面(App.Vue) ```html ``` > 5 启动测试 ```shell npm run dev ``` > 异步响应的数据结构 + 响应的数据是经过包装返回的!一个请求的响应包含以下信息。 ```json { // `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取值 ```javascript then(function (response) { console.log(response.data); console.log(response.status); console.log(response.statusText); console.log(response.headers); console.log(response.config); }); ``` > 6 通过async和await处理异步请求 ```html ``` > axios在发送异步请求时的可选配置: ```json { // `url` 是用于请求的服务器 URL url: '/user', // `method` 是创建请求时使用的方法 method: 'get', // 默认值 // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。 // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL baseURL: 'https://some-domain.com/api/', // `transformRequest` 允许在向服务器发送前,修改请求数据 // 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法 // 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream // 你可以修改请求头。 transformRequest: [function (data, headers) { // 对发送的 data 进行任意转换处理 return data; }], // `transformResponse` 在传递给 then/catch 前,允许修改响应数据 transformResponse: [function (data) { // 对接收的 data 进行任意转换处理 return data; }], // 自定义请求头 headers: {'X-Requested-With': 'XMLHttpRequest'}, // `params` 是与请求一起发送的 URL 参数 // 必须是一个简单对象或 URLSearchParams 对象 params: { ID: 12345 }, // `paramsSerializer`是可选方法,主要用于序列化`params` // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/) paramsSerializer: function (params) { return Qs.stringify(params, {arrayFormat: 'brackets'}) }, // `data` 是作为请求体被发送的数据 // 仅适用 'PUT', 'POST', 'DELETE 和 'PATCH' 请求方法 // 在没有设置 `transformRequest` 时,则必须是以下类型之一: // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams // - 浏览器专属: FormData, File, Blob // - Node 专属: Stream, Buffer data: { firstName: 'Fred' }, // 发送请求体数据的可选语法 // 请求方式 post // 只有 value 会被发送,key 则不会 data: 'Country=Brasil&City=Belo Horizonte', // `timeout` 指定请求超时的毫秒数。 // 如果请求时间超过 `timeout` 的值,则请求会被中断 timeout: 1000, // 默认值是 `0` (永不超时) // `withCredentials` 表示跨域请求时是否需要使用凭证 withCredentials: false, // default // `adapter` 允许自定义处理请求,这使测试更加容易。 // 返回一个 promise 并提供一个有效的响应 (参见 lib/adapters/README.md)。 adapter: function (config) { /* ... */ }, // `auth` HTTP Basic Auth auth: { username: 'janedoe', password: 's00pers3cret' }, // `responseType` 表示浏览器将要响应的数据类型 // 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream' // 浏览器专属:'blob' responseType: 'json', // 默认值 // `responseEncoding` 表示用于解码响应的编码 (Node.js 专属) // 注意:忽略 `responseType` 的值为 'stream',或者是客户端请求 // Note: Ignored for `responseType` of 'stream' or client-side requests responseEncoding: 'utf8', // 默认值 // `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称 xsrfCookieName: 'XSRF-TOKEN', // 默认值 // `xsrfHeaderName` 是带有 xsrf token 值的http 请求头名称 xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值 // `onUploadProgress` 允许为上传处理进度事件 // 浏览器专属 onUploadProgress: function (progressEvent) { // 处理原生进度事件 }, // `onDownloadProgress` 允许为下载处理进度事件 // 浏览器专属 onDownloadProgress: function (progressEvent) { // 处理原生进度事件 }, // `maxContentLength` 定义了node.js中允许的HTTP响应内容的最大字节数 maxContentLength: 2000, // `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数 maxBodyLength: 2000, // `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是 reject promise。 // 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`), // 则promise 将会 resolved,否则是 rejected。 validateStatus: function (status) { return status >= 200 && status < 300; // 默认值 }, // `maxRedirects` 定义了在node.js中要遵循的最大重定向数。 // 如果设置为0,则不会进行重定向 maxRedirects: 5, // 默认值 // `socketPath` 定义了在node.js中使用的UNIX套接字。 // e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程。 // 只能指定 `socketPath` 或 `proxy` 。 // 若都指定,这使用 `socketPath` 。 socketPath: null, // default // `httpAgent` and `httpsAgent` define a custom agent to be used when performing http // and https requests, respectively, in node.js. This allows options to be added like // `keepAlive` that are not enabled by default. httpAgent: new http.Agent({ keepAlive: true }), httpsAgent: new https.Agent({ keepAlive: true }), // `proxy` 定义了代理服务器的主机名,端口和协议。 // 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。 // 使用 `false` 可以禁用代理功能,同时环境变量也会被忽略。 // `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。 // 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers` 中已存在的自定义 `Proxy-Authorization` 请求头。 // 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https` proxy: { protocol: 'https', host: '127.0.0.1', port: 9000, auth: { username: 'mikeymike', password: 'rapunz3l' } }, // see https://axios-http.com/zh/docs/cancellation cancelToken: new CancelToken(function (cancel) { }), // `decompress` indicates whether or not the response body should be decompressed // automatically. If set to `true` will also remove the 'content-encoding' header // from the responses objects of all decompressed responses // - Node only (XHR cannot turn off decompression) decompress: true // 默认值 } ``` ### 9.3 Axios get和post方法 > 配置添加语法 ``` javascript axios.get(url[, config]) axios.get(url,{ 上面指定配置key:配置值, 上面指定配置key:配置值 }) axios.post(url[, data[, config]]) axios.post(url,{key:value //此位置数据,没有空对象即可{}},{ 上面指定配置key:配置值, 上面指定配置key:配置值 }) ``` > 测试get参数 ``` html ``` > 测试post参数 ```html ``` ### 9.4 Axios 拦截器 > 如果想在axios发送请求之前,或者是数据响应回来在执行then方法之前做一些额外的工作,可以通过拦截器完成 ```javascript // 添加请求拦截器 请求发送之前 axios.interceptors.request.use( function (config) { // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); } ); // 添加响应拦截器 数据响应回来 axios.interceptors.response.use( function (response) { // 2xx 范围内的状态码都会触发该函数。 // 对响应数据做点什么 return response; }, function (error) { // 超出 2xx 范围的状态码都会触发该函数。 // 对响应错误做点什么 return Promise.reject(error); } ); ``` + 定义src/axios.js提取拦截器和配置语法 ```javascript 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 ```html ``` ## 十、案例开发-日程管理-第六期 ### 10.1 前端代码处理 #### 10.1.1 创建src/utils/request.js文件 ``` javascript 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 ``` #### 10.1.2 注册页面完成注册 ``` html ``` #### 10.1.3 登录页面完成登录 ``` html ``` ### 10.2 后端代码处理 #### 10.2.1 添加跨域处理器 ##### 10.2.1.1 什么是跨域 > 同源策略(Sameoriginpolicy)是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。**`同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号`** ##### 10.2.1.2为什么会产生跨域 > 前后端分离模式下,客户端请求前端服务器获取视图资源,然后客户端自行向后端服务器获取数据资源,前端服务器的 协议,IP和端口和后端服务器很可能是不一样的,这样就产生了跨域 ![1683364198087](images/1683364198087-1690533535579.png) ##### 10.2.1.3 如何解决跨域 > 前端项目代理模式处理 ![1683365066926](images/1683365066926-1690533535580.png) > 后端跨域过滤器方式处理 ![1683364436315](images/1683364436315-1690533535580.png) + CrosFilter过滤器 ``` java 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 就可以解决跨域问题了 #### 10.2.2 重构UserController ``` java package com.atguigu.schedule.controller; import com.atguigu.schedule.common.Result; import com.atguigu.schedule.common.ResultCodeEnum; import com.atguigu.schedule.pojo.SysUser; import com.atguigu.schedule.service.SysUserService; import com.atguigu.schedule.service.impl.SysUserServiceImpl; import com.atguigu.schedule.util.MD5Util; import com.atguigu.schedule.util.WebUtil; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/user/*") public class UserController extends BaseController{ private SysUserService userService =new SysUserServiceImpl(); /** * 注册时校验用户名是否被占用的业务接口 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void checkUsernameUsed(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); SysUser registUser = userService.findByUsername(username); //封装结果对象 Result result=null; if(null ==registUser){ // 未占用,创建一个code为200的对象 result= Result.ok(null); }else{ // 占用, 创建一个结果为505的对象 result= Result.build(null, ResultCodeEnum.USERNAME_USED); } // 将result对象转换成JSON并响应给客户端 WebUtil.writeJson(resp,result); } /** * 用户注册的业务接口 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 接收要注册的用户信息 SysUser registUser = WebUtil.readJson(req, SysUser.class); // 调用服务层方法,将用户注册进入数据库 int rows =userService.regist(registUser); Result result =null; if(rows>0){ result=Result.ok(null); }else{ result =Result.build(null,ResultCodeEnum.USERNAME_USED); } WebUtil.writeJson(resp,result); } /** * 用户登录的业务接口 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 接收用户请求参数 // 获取要登录的用户名密码 SysUser inputUser = WebUtil.readJson(req, SysUser.class); // 调用服务层方法,根据用户名查询数据库中是否有一个用户 SysUser loginUser =userService.findByUsername(inputUser.getUsername()); Result result = null; if(null == loginUser){ // 没有根据用户名找到用户,说明用户名有误 result=Result.build(null,ResultCodeEnum.USERNAME_ERROR); }else if(! loginUser.getUserPwd().equals(MD5Util.encrypt(inputUser.getUserPwd()))){ // 用户密码有误, result=Result.build(null,ResultCodeEnum.PASSWORD_ERROR); }else{ // 登录成功 result=Result.ok(null); } WebUtil.writeJson(resp,result); } } ``` #### 10.2.3 删除登录校验过滤器 + 这里不使用cookie和session方式记录用户状态,未来使用token,所以登录过滤器删除即可 ## 十一、Vue3状态管理Pinia ### 11.1 Pinia介绍 > 如何实现多个组件之间的数据传递? + 方式1 组件传参 + 方式2 路由传参 + 方式3 通过pinia状态管理定义共享数据 > 当我们有`多个组件共享一个共同的状态(数据源)`时,多个视图可能都依赖于同一份状态。来自不同视图的交互也可能需要更改同一份状态。虽然我们的手动状态管理解决方案(props,组件间通信,模块化)在简单的场景中已经足够了,但是在大规模的生产应用中还有很多其他事项需要考虑: - 更强的团队协作约定 - 与 Vue DevTools 集成,包括时间轴、组件内部审查和时间旅行调试 - 模块热更新 (HMR) - 服务端渲染支持 > [Pinia](https://pinia.vuejs.org/zh/ "Pinia") 就是一个实现了上述需求的状态管理库,由 Vue 核心团队维护,对 Vue 2 和 Vue 3 都可用。https://pinia.vuejs.org/zh/introduction.html ### 11.2 Pinia基本用法 > 1 准备vite项目 ```javascript npm create vite npm install npm install vue-router@4 --save ``` > 2 安装pinia ```javascript npm install pinia ``` > 3 定义pinia store对象 src/store/store.js \[推荐这么命名不是强制] ```javascript import {defineStore } from 'pinia' //定义数据并且对外暴露 // store就是定义共享状态的包装对象 // 内部包含四个属性: id 唯一标识 state 完整类型推理,推荐使用箭头函数 存放的数据 getters 类似属性计算,存储放对数据 // 操作的方法 actions 存储数据的复杂业务逻辑方法 // 理解: store类似Java中的实体类, id就是类名, state 就是装数据值的属性 getters就是get方法,actions就是对数据操作的其他方法 export const definedPerson = defineStore( { id: 'personPinia', //必须唯一 state:()=>{ // state中用于定义数据 return { username:'张三', age:0, hobbies:['唱歌','跳舞'] } }, getters:{// 用于定义一些通过数据计算而得到结果的一些方法 一般在此处不做对数据的修改操作 // getters中的方法可以当做属性值方式使用 getHobbiesCount(){ return this.hobbies.length }, getAge(){ return this.age } }, actions:{ // 用于定义一些对数据修改的方法 doubleAge(){ this.age=this.age*2 } } } ) ``` > 4 在main.js配置pinia组件到vue中 ```javascript 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 Operate.vue 中操作Pinia数据 ```html ``` > 6 List.vue中展示Pinia数据 ``` html ``` > 7 定义组件路由router.js ``` javascript // 导入路由创建的相关方法 import {createRouter,createWebHashHistory} from 'vue-router' // 导入vue组件 import List from '../components/List.vue' import Operate from '../components/Operate.vue' // 创建路由对象,声明路由规则 const router = createRouter({ history: createWebHashHistory(), routes:[ { path:'/opearte', component:Operate }, { path:'/list', component:List }, ] }) // 对外暴露路由对象 export default router; ``` > 8 App.vue中通过路由切换组件 ``` html ``` > 9 启动测试 ```javascript npm run dev ``` ### 11.3 Pinia其他细节 > State (状态) 在大多数情况下,state 都是你的 store 的核心。人们通常会先定义能代表他们 APP 的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数。 + store.js ```javascript import {defineStore} from 'pinia' export const definedPerson = defineStore('personPinia', { state:()=>{ return { username:'', age:0, hobbies:['唱歌','跳舞'] } }, getters:{ getHobbiesCount(){ return this.hobbies.length }, getAge(){ return this.age } }, actions:{ doubleAge(){ this.age=this.age*2 } } } ) ``` + Operate.vue ``` html ``` > 2 Getter 完全等同于 store 的 state 的[计算值](https://cn.vuejs.org/guide/essentials/computed.html "计算值")。可以通过 `defineStore()` 中的 `getters` 属性来定义它们。**推荐**使用箭头函数,并且它将接收 `state` 作为第一个参数: ```javascript export const useStore = defineStore('main', { state: () => ({ count: 0, }), getters: { doubleCount: (state) => state.count * 2, }, }) ``` > 3 Action 相当于组件中的 [method](https://v3.vuejs.org/guide/data-methods.html#methods "method")。它们可以通过 `defineStore()` 中的 `actions` 属性来定义,**并且它们也是定义业务逻辑的完美选择。**类似 [getter](https://pinia.vuejs.org/zh/core-concepts/getters.html "getter"),action 也可通过 `this` 访问**整个 store 实例**,并支持**完整的类型标注(以及自动补全)**。不同的是,`action` 可以是异步的,你可以在它们里面 `await` 调用任何 API,以及其他 action! ```javascript export const useCounterStore = defineStore('main', { state: () => ({ count: 0, }), actions: { increment() { this.count++ }, randomizeCounter() { this.count = Math.round(100 * Math.random()) }, }, }) ``` ## 十二、案例开发-日程管理-第七期 ### 12.1 前端使用pinia存储数据 + 安装pinia依赖 ``` shell npm install pinia ``` + src下创建pinia.js文件 ``` javascript // 导入pinia组件 import {createPinia} from 'pinia' // 创建pinia对象 let pinia = createPinia() // 导出默认的pinia export default pinia ``` + main.js中使用pinia ``` js import { createApp } from 'vue' import App from './App.vue' // 导入路由 import router from './router/router.js' // 导入pinia对象 import pinia from './pinia.js' let app =createApp(App) // 全局使用路由 app.use(router) // 全局使用pinia app.use(pinia) app.mount('#app') ``` + src/store/userStore.js 用于存储用户信息 ``` javascript import {defineStore} from 'pinia' export const defineUser = defineStore('loginUser',{ state:()=>{ return { uid:0, username:'' } }, getters :{ } }) ``` + src/store/scheduleStore.js 用于存储用户的日程信息 ``` javaScript import {defineStore} from 'pinia' export const defineSchedule = defineStore('scheduleList',{ state:()=>{ return { itemList:[ /*{ sid:1, uid:1, title:'学java', completed:1 }, { sid:2, uid:1, title:'学前端', completed:0 }*/ ] } }, getters :{ }, actions:{ } }) ``` + Header.vue中,通过pinia的数据来判断展示何种提示 视图 ``` html ``` + Login.vue中,登录成功后,接收后端响应回来的用户id和用户名,存储于userStore中 ``` html ``` + 服务端登录处理方法,登录成功,返回登录用户的信息 ``` java /** * 用户登录的业务接口 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 接收用户请求参数 // 获取要登录的用户名密码 SysUser inputUser = WebUtil.readJson(req, SysUser.class); // 调用服务层方法,根据用户名查询数据库中是否有一个用户 SysUser loginUser =userService.findByUsername(inputUser.getUsername()); Result result = null; if(null == loginUser){ // 没有根据用户名找到用户,说明用户名有误 result=Result.build(null,ResultCodeEnum.USERNAME_ERROR); }else if(! loginUser.getUserPwd().equals(MD5Util.encrypt(inputUser.getUserPwd()))){ // 用户密码有误, result=Result.build(null,ResultCodeEnum.PASSWORD_ERROR); }else{ // 登录成功,将用户信息存入session req.getSession().setAttribute("sysUser",loginUser); // 登录成功 // 将密码请空后,将用户信息响应给客户端 loginUser.setUserPwd(""); Map data =new HashMap<>(); data.put("loginUser",loginUser); result=Result.ok(data); } WebUtil.writeJson(resp,result); } ``` + router.js中,通过路由守卫控制只有登录状态下才可以进入showSchedule.vue ``` javascript import {createRouter,createWebHashHistory} from 'vue-router' import pinia from '../pinia.js' import {defineUser} from '../store/userStore.js' let sysUser = defineUser(pinia) 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 } ] }) /* 配置路由的前置守卫,在登录状态下才可以范文showSchedule.vue */ router.beforeEach( (to,from,next) =>{ // 如果是查看日程 if(to.path=="/showSchedule"){ // 如果尚未的登录 if(sysUser.username == ''){ alert("您尚未登录,请登录后再查看日程") next("/login") }else{ // 已经登录 放行 next() } // 其他资源 放行 }else{ next() } }) export default router ``` ### 12.2 显示所有日程数据 + ShowSchedule.vue中向后端发送异步请求查询数据并展示 ``` html ``` + SysScheduleController中查询数据并响应json ``` java package com.atguigu.schedule.controller; import com.atguigu.schedule.common.Result; import com.atguigu.schedule.pojo.SysSchedule; import com.atguigu.schedule.service.SysScheduleService; import com.atguigu.schedule.service.impl.SysScheduleServiceImpl; import com.atguigu.schedule.util.WebUtil; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @WebServlet("/schedule/*") public class SysScheduleController extends BaseController{ private SysScheduleService scheduleService =new SysScheduleServiceImpl(); /** * 查询所有日程接口 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void findAllSchedule(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int uid = Integer.parseInt(req.getParameter("uid")); // 调用服务层方法,查询所有日程 List itemList = scheduleService.findItemListByUid(uid); // 将日程信息装入result,转换JSON给客户端 Map data =new HashMap<>(); data.put("itemList",itemList); WebUtil.writeJson(resp,Result.ok(data)); } } ``` + SysScheduleService接口和实现类代码 ``` java package com.atguigu.schedule.service; import com.atguigu.schedule.pojo.SysSchedule; import java.util.List; public interface SysScheduleService { List findItemListByUid(int uid); } // ------------------------------------------------ package com.atguigu.schedule.service.impl; import com.atguigu.schedule.dao.SysScheduleDao; import com.atguigu.schedule.dao.impl.SysScheduleDaoImpl; import com.atguigu.schedule.pojo.SysSchedule; import com.atguigu.schedule.service.SysScheduleService; import java.util.List; public class SysScheduleServiceImpl implements SysScheduleService { private SysScheduleDao scheduleDao =new SysScheduleDaoImpl(); @Override public List findItemListByUid(int uid) { return scheduleDao.findItemListByUid(uid); } } ``` + SysScheduleDao接口和实现类代码 ``` java package com.atguigu.schedule.dao; import com.atguigu.schedule.pojo.SysSchedule; import java.util.List; public interface SysScheduleDao { List findItemListByUid(int uid); } //----------------------------------------------------------- package com.atguigu.schedule.dao.impl; import com.atguigu.schedule.dao.BaseDao; import com.atguigu.schedule.dao.SysScheduleDao; import com.atguigu.schedule.pojo.SysSchedule; import java.util.List; public class SysScheduleDaoImpl extends BaseDao implements SysScheduleDao { @Override public List findItemListByUid(int uid) { String sql ="select sid,uid,title, completed from sys_schedule where uid = ? "; return baseQuery(SysSchedule.class,sql,uid); } } ``` ### 12.3 增加和保存日程数据 + ShowSchedule.vue下,为增加和修改按钮绑定事件 ``` html ``` + SysScheduleController处理 新增和保存修改业务处理接口 ``` java package com.atguigu.schedule.controller; import com.atguigu.schedule.common.Result; import com.atguigu.schedule.pojo.SysSchedule; import com.atguigu.schedule.service.SysScheduleService; import com.atguigu.schedule.service.impl.SysScheduleServiceImpl; import com.atguigu.schedule.util.WebUtil; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @WebServlet("/schedule/*") public class SysScheduleController extends BaseController{ private SysScheduleService scheduleService =new SysScheduleServiceImpl(); /** * 向数据库中增加一个新的默认数据的方法 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void addDefaultSchedule(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int uid = Integer.parseInt(req.getParameter("uid")); // 调用服务层方法,为当前用户新增一个默认空数据 scheduleService.addDefault(uid); WebUtil.writeJson(resp,Result.ok(null)); } /** * 更新日程业务接口 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void updateSchedule(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { SysSchedule sysSchedule = WebUtil.readJson(req, SysSchedule.class); // 调用服务层方法,更新数据 scheduleService.updateSchedule(sysSchedule); // 响应成功 WebUtil.writeJson(resp,Result.ok(null)); } } ``` + SysScheduleService接口和实现类处理业务逻辑 ``` java package com.atguigu.schedule.service; import com.atguigu.schedule.pojo.SysSchedule; import java.util.List; public interface SysScheduleService { Integer addDefault(int uid); Integer updateSchedule(SysSchedule sysSchedule); } // ---------------------------------------------------------- package com.atguigu.schedule.service.impl; import com.atguigu.schedule.dao.SysScheduleDao; import com.atguigu.schedule.dao.impl.SysScheduleDaoImpl; import com.atguigu.schedule.pojo.SysSchedule; import com.atguigu.schedule.service.SysScheduleService; import java.util.List; public class SysScheduleServiceImpl implements SysScheduleService { private SysScheduleDao scheduleDao =new SysScheduleDaoImpl(); @Override public Integer addDefault(int uid) { return scheduleDao.addDefault(uid); } @Override public Integer updateSchedule(SysSchedule sysSchedule) { return scheduleDao.updateSchedule(sysSchedule); } } ``` + SysScheduleDao接口和实现类操作数据 ``` java package com.atguigu.schedule.dao; import com.atguigu.schedule.pojo.SysSchedule; import java.util.List; public interface SysScheduleDao { Integer addDefault(int uid); Integer updateSchedule(SysSchedule sysSchedule); } //------------------------------------------------- package com.atguigu.schedule.dao.impl; import com.atguigu.schedule.dao.BaseDao; import com.atguigu.schedule.dao.SysScheduleDao; import com.atguigu.schedule.pojo.SysSchedule; import java.util.List; public class SysScheduleDaoImpl extends BaseDao implements SysScheduleDao { @Override public Integer addDefault(int uid) { String sql = "insert into sys_schedule value(default,?,'请输入日程',0)"; return baseUpdate(sql,uid); } @Override public Integer updateSchedule(SysSchedule sysSchedule) { String sql ="update sys_schedule set title = ? ,completed = ? where sid =?"; return baseUpdate(sql,sysSchedule.getTitle(),sysSchedule.getCompleted(),sysSchedule.getSid()); } } ``` ### 12.5 删除日程数据 + ShowSchedule.vue中,为删除按钮增加事件 ``` html ``` + SysScheduleController中添加删除业务接口 ``` java package com.atguigu.schedule.controller; import com.atguigu.schedule.common.Result; import com.atguigu.schedule.pojo.SysSchedule; import com.atguigu.schedule.service.SysScheduleService; import com.atguigu.schedule.service.impl.SysScheduleServiceImpl; import com.atguigu.schedule.util.WebUtil; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; @WebServlet("/schedule/*") public class SysScheduleController extends BaseController{ private SysScheduleService scheduleService =new SysScheduleServiceImpl(); /** * 删除日程业务接口 * @param req * @param resp * @throws ServletException * @throws IOException */ protected void removeSchedule(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取要删除的日程id int sid = Integer.parseInt(req.getParameter("sid")); // 调用服务层方法,删除日程 scheduleService.removeSchedule(sid); // 响应200 WebUtil.writeJson(resp,Result.ok(null)); } } ``` + SysScheduleService层处理删除业务的接口和实现类 ``` java package com.atguigu.schedule.service; import com.atguigu.schedule.pojo.SysSchedule; import java.util.List; public interface SysScheduleService { Integer removeSchedule(int sid); } //------------------------------------------------------------------ package com.atguigu.schedule.service.impl; import com.atguigu.schedule.dao.SysScheduleDao; import com.atguigu.schedule.dao.impl.SysScheduleDaoImpl; import com.atguigu.schedule.pojo.SysSchedule; import com.atguigu.schedule.service.SysScheduleService; import java.util.List; public class SysScheduleServiceImpl implements SysScheduleService { private SysScheduleDao scheduleDao =new SysScheduleDaoImpl(); @Override public Integer removeSchedule(int sid) { return scheduleDao.removeBySid(sid); } } ``` + SysScheduleDao操作数据库的接口和实现类 ``` java package com.atguigu.schedule.dao; import com.atguigu.schedule.pojo.SysSchedule; import java.util.List; public interface SysScheduleDao { Integer removeBySid(int sid); } //--------------------------------------------------------- package com.atguigu.schedule.dao.impl; import com.atguigu.schedule.dao.BaseDao; import com.atguigu.schedule.dao.SysScheduleDao; import com.atguigu.schedule.pojo.SysSchedule; import java.util.List; public class SysScheduleDaoImpl extends BaseDao implements SysScheduleDao { @Override public Integer removeBySid(int sid) { String sql ="delete from sys_schedule where sid = ?"; return baseUpdate(sql,sid); } } ``` ## 十三、Element-plus组件库 ### 13.1 Element-plus介绍 > Element Plus 是一套基于 Vue 3 的开源 UI 组件库,是由饿了么前端团队开发的升级版本 Element UI。Element Plus 提供了丰富的 UI 组件、易于使用的 API 接口和灵活的主题定制功能,可以帮助开发者快速构建高质量的 Web 应用程序。 + Element Plus 支持按需加载,且不依赖于任何第三方 CSS 库,它可以轻松地集成到任何 Vue.js 项目中。Element Plus 的文档十分清晰,提供了各种组件的使用方法和示例代码,方便开发者快速上手。 + Element Plus 目前已经推出了大量的常用 UI 组件,如按钮、表单、表格、对话框、选项卡等,此外还提供了一些高级组件,如日期选择器、时间选择器、级联选择器、滑块、颜色选择器等。这些组件具有一致的设计和可靠的代码质量,可以为开发者提供稳定的使用体验。 + 与 Element UI 相比,Element Plus 采用了现代化的技术架构和更加先进的设计理念,同时具备更好的性能和更好的兼容性。Element Plus 的更新迭代也更加频繁,可以为开发者提供更好的使用体验和更多的功能特性。 + Element Plus 可以在支持 [ES2018](https://caniuse.com/?feats=mdn-javascript_builtins_regexp_dotall,mdn-javascript_builtins_regexp_lookbehind_assertion,mdn-javascript_builtins_regexp_named_capture_groups,mdn-javascript_builtins_regexp_property_escapes,mdn-javascript_builtins_symbol_asynciterator,mdn-javascript_functions_method_definitions_async_generator_methods,mdn-javascript_grammar_template_literals_template_literal_revision,mdn-javascript_operators_destructuring_rest_in_objects,mdn-javascript_operators_spread_spread_in_destructuring,promise-finally "ES2018") 和 [ResizeObserver](https://caniuse.com/resizeobserver "ResizeObserver") 的浏览器上运行。 如果您确实需要支持旧版本的浏览器,请自行添加 [Babel](https://babeljs.io/ "Babel") 和相应的 Polyfill + 官网https://element-plus.gitee.io/zh-CN/ + 由于 Vue 3 不再支持 IE11,Element Plus 也不再支持 IE 浏览器。 ![](images/image_rdwmpig76n.png) ### 13.2 Element-plus入门案例 > 1 准备vite项目 ```shell npm create vite // 注意选择 vue+typeScript npm install npm install vue-router@4 --save npm install pinia npm install axios ``` > 2 安装element-plus ```shell npm install element-plus ``` > 3 完整引入element-plus + main.js ```javascript 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 ```html ``` > 5 启动测试 ```shell npm run dev ``` ### 13.3 Element-plus常用组件