Vue工程化/TS/ElementPlus
Vue工程化/TS/ElementPlus
今日目标
目标
前面我们在介绍Vue的时候,我们讲到Vue是一款用于构建用户界面的渐进式JavaScript框架 。(官方:https://cn.vuejs.org/)

那在前面的课程中,我们已经学习了Vue的基本语法、表达式、指令,并基于Vue的核心包,完成了Vue的案例。
今天主要完成如下功能:
基于Vue进行整站开发。🎯
1. TS
1.1 概述以及入门案例 ✏️
概述以及入门案例
TypeScript(简称 TS)是JavaScript的超集(继承了JS全部语法),TypeScript = Type + JavaScript。
简单说,就是在JS的基础上,为JS添加了类型支持。
TypeScript是微软开发的开源编程语言,可以在任何运行JavaScript的地方运行。
类型注解:是指在变量、函数等定义的时候,使用特定语法(: type)来指定其类型,并在代码中限制只能接收特定类型的值。
为什么要用TypeScript ?
动态类型语言
- 编写bug多,影响开发效率
- 运行时才会发现问题
- 开发工具代码提示功能弱
- 不便项目维护
静态类型语言
- 有利于发现错误(编写时)
- 有利于代码的静态分析
- 便于语法提示和自动补全
- 利于项目维护
代码操作
准备:
- 以管理员身份运行CMD,安装TS官方提供的编译器:npm install -g typescript (只需要做一次即可)
image 编码:
- 定义ts文件,定义变量,指定类型注解
- 编译ts文件,测试程序运行
image 编译命令:
tsc Helloworld.ts

编译之后的文件为:`Helloworld.js`
注意 :TS代码编译目标版本为es3(比较低),可以通过参数
–target
指定编译的目标版本。如:es5、es6、es2016... esnext

总结
课堂作业
- 安装TS官方提供的编译器
npm install -g typescript (只需要做一次即可)
- 完成上述步骤,对比与java运行的异同?🎤
- 定义字符串变量 bookname,值为你最喜欢的书籍。然后编译成js,在浏览器中运行!
运行位置:

1.2 常用类型 🍐
TS常用类型
TS中除了支持JS中的数据类型之外,还提供了新的实用的数据类型。 常见类型如下:
类型 | 例子 | 备注 |
---|---|---|
字符串类型 | string | |
数字类型 | number | 整数 、小数 |
布尔类型 | boolean | true、false |
null/undefined类型 | null / undefined | 表示null和undefined本身,意义不大 |
任意类型 | any | 没有指定任何类型 |
数组类型 | number[] / Array<number> | |
联合类型 | number | string |
字面量类型 | 'left' | 'center' |
函数类型 | () => void | 对函数的参数及返回值指定类型 |
对象类型 | 限定对象的结构(属性及方法) | |
复杂类型 | interface接口 | |
泛型 | <T> |
代码操作
基础类型
基础类型:string,number,boolean,null,undefined,any,数组。
// 基本类型
let uname:string='yange'; // 字符串类型
let age:number=35;// 数字类型
let isStudent:boolean=true;// 布尔类型
let hobbies:string[]=['sing','dance','draw'];// 数组类型
let address:any='beijing';// 任意类型 不推荐
let hobby:null=null;// 空类型 很少用到,作用不大
let abc:undefined=undefined;// 未定义类型 作用不大
原则上,不推荐使用any!!! 这会让TypeScript又回到JavaScript(失去TS类型保护的优势)。
联合类型
联合类型:是指由两个或多个其他类型组成的类型,表示可以是其中的任意一种。
写法:类型1 | 类型2
// 联合类型
let arr2:(number|string)[]=[1,2,3,4,5,'a','b'];
console.log(arr2.length);
let flag:(boolean|number)=true;
flag=1;
flag=3;
flag=false;

TS中的联合类型中指定的多种类型之间使用 | 分隔,建议使用()括起来。
- 类型别名:相当于一种自定义类型,为任意类型起别名。
- 使用场景:当同一类型(复杂)别多次使用时,可以通过类型别名,简化该类型(复杂)的书写。
- 定义语法:type customType = 指定类型
type strNum = (string | number)[];// 定义一个数组类型
let arr3:strNum=[1,2,3,4,5,'a','b'];// 定义一个数组
let arr4:strNum=['1',2,3,4,5,'a','b'];// 定义一个数组
类型别名type,是可以为任意类型指定别名的。
函数类型
函数类型实际上指的是:函数的参数及返回值的类型
语法一:单独指定参数、返回值类型
image-20231016183424890 与JS不同,TS中函数调用时传入的参数个数必须与函数定义时参数个数一致。
语法二:书写完成函数类型(同时指定参数、返回值类型)
image-20231016183457515 可选参数:在TS里我们可以在参数后使用
?
实现可选参数的功能。而且可选参数只能出现在参数列表的最后。image-20231016183514149 如果函数没有返回值,则函数的返回值类型为:void
对象类型 & 接口interface
对象类型
TS中的对象类型就是来描述对应的结构的(有什么类型的属性和方法)

- 说明:
- 直接使用{}来描述对象的结构。属性采用 属性名:类型 的形式;方法采用 方法名():返回值类型 的形式。
- 如果方法有参数,就在方法名后面的小括号中指定参数类型(如:say(content:string):void)。
- 在一行中指定多个属性类型时,可以使用 逗号/分号 来分割。
- 方法的类型,也可以使用箭头函数形式,比如:say:() => void。
- 对象的属性或方法,也可以是可选的,此时就可以声明可选属性/方法,使用 ?(问号)来表示。
接口interface
当一个对象类型被多次使用时,我们可以使用 接口(interface)来描述对象的类型,达到 复用 的目的。

- 说明:
- 接口使用
interface
关键字来声明,接口名称可以是任意合法的变量名称。 - 接口中定义的属性或方法,结尾可以使用逗号(
,
)/分号(;
)分隔;如果每一行只有一个属性,后面也可以不写分号(;
)。
- 接口使用
Interface(接口) 与 type(类型别名)对比
- 相同点:都可以给对象指定类型。
- 不同点:
interface(接口),只能为对象指定类型。
type(类型别名),可以为任意类型指定别名。
image-20231016183908918
类型推论
在TS中,在有些没有明确指出类型 的地方,类型推论会帮助提供类型。
换句话说:由于类型推论的存在,某些地方,类型注解是可以省略不写的。
常见场景:
- 声明变量并初始化
- 决定函数返回值时

提示:如果不知道类型,可以通过鼠标,放在变量名称 或 方法名称上,利用VSCode的提示来查询类型。
总结
课堂作业
- TS中这些类型,和java中的类型对应一下?相似吗?🎤
- 定义一个字符串数组,将你的爱好定义在里面
- 定义一个对象类型,描述你喜欢的书籍(名字,概要,主角,类型,章节数)
- 定义一个方法,求取1-n之和
2. ElementPlus ✏️ ✏️
2.1 介绍和快速入门
ElementPlus快速入门
Element:是饿了么公司前端开发团队提供的一套基于 Vue3 的网站组件库,用于快速构建网页。
Element 提供了很多组件(组成网页的部件)供我们使用。例如 超链接、按钮、图片、表格等等。
官方网站:https://element-plus.org/zh-CN/#/zh-CN
如下图所示就是我们开发的页面和ElementUI提供的效果对比:可以发现ElementUI提供的各式各样好看的按钮

ElementPlus的学习方式和我们之前的学习方式不太一样,对于ElementPlus,我们作为一个后台开发者,只需要学会如何从 ElementPlus 的官网拷贝组件到我们自己的页面中,并且做一些修改即可。 我们主要学习的是ElementPlus中提供的常用组件,至于其他组件同学们可以通过我们这几个组件的学习掌握到ElementPlus的学习技巧,然后课后自行学习。
代码操作
准备工作:
资料/04. ElementPlus基础工程/vue-project02.zip
),解压到工作目录中,使用VSCode将其打开。
1️⃣ 将资料中提供的基础工程(
2️⃣ 参照官方文档,安装ElementPlus的组件库(在当前工程的目录下),执行如下命令:【这一步可以不做,已经安装好了】
npm install element-plus --save

main.ts
中引入ElementPlus组件库 (参照官方文档),最终 main.ts
中代码如下:
3️⃣ 在import { createApp } from 'vue' // 引入createApp方法
// 引入App.vue组件
import App from './App.vue'
// 引入main.css文件
import './assets/main.css'
// 引入ElementPlus
import ElementPlus from 'element-plus'
// 引入ElementPlus的样式
import 'element-plus/dist/index.css'
// 创建Vue应用
const app = createApp(App)
// 使用ElementPlus
app.use(ElementPlus)
app.mount('#app')
制作组件:
- 访问ElementPlus的官方文档,查看对应的组件源代码。

- 在
views
目录下,创建Element.vue
组件文件,复制组件代码,调整成自己想要的 。
<script setup lang="ts">
</script>
<template>
<el-row class="mb-4">
<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>
</el-row>
</template>
<style scoped>
</style>
scoped 实现组件的私有化,不对全局造成样式污染,表示当前style属性只属于当前模块。
原理:scoped会在DOM结构及css样式上加上唯一性的标记
data-v-xxxxx
属性,即CSS带属性选择器,以此完成类似作用域的选择方式,从而达到样式私有化,不污染全局的作用。
- 在App.vue中导入
Element.vue
组件文件

- 启动项目,访问 http://localhost:5173

总结
课堂作业
- 参考上述步骤,完成入门案例的书写!🎤
2.2 常见组件
常见组件
官网:https://element-plus.org/zh-CN/component/overview.html
常见组件
代码操作
表格组件
用于展示多条结构类似的数据, 可对数据进行排序、筛选、对比或其他自定义操作。

接下来我们通过代码来演示。
1️⃣ 首先我们需要来到 ElementPlus 的组件库中,找到表格组件,如下图所示:

Element.vue
,组件中,需要注意的是,我们组件包括了3个部分,如果官方有除了template部分之外的style和script都需要复制。具体操作如下图所示:
2️⃣ 然后新建一个组件 
整体代码如下所示:
<script setup lang="ts">
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>
<template>
<!-- Button按钮 -->
<el-row class="mb-4">
<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>
</el-row>
<br>
<!-- Table表格 -->
<el-table :data="tableData" border 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>
<style scoped>
</style>
此时回到浏览器,我们页面呈现如下效果:

Table表格组件,属性说明:
- data: 主要定义table组件的数据模型
- prop: 定义列的数据应该绑定data中定义的具体的数据模型
- label: 定义列的标题
- width: 定义列的宽度
分页条组件
Pagination: 分页组件,主要提供分页工具条相关功能。其展示效果图下图所示:

默认情况下,ElementPlus的组件是英文的,如果希望使用中文语言,可以在 main.ts
中做如下配置:
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
app.use(ElementPlus, {locale: zhCn})
然后还需要在 env.d.ts
中增加如下配置项:
declare module 'element-plus/dist/locale/zh-cn.mjs'
接下来我们通过代码来演示功能。
首先在官网找到分页组件,我们选择带背景色分页组件,如下图所示:

然后复制代码到我们的 Element.vue
组件文件的template中,在 <template> </template>
拷贝如下代码:
<el-pagination
v-model:current-page="currentPage4"
v-model:page-size="pageSize4"
:page-sizes="[100, 200, 300, 400]"
layout="total, sizes, prev, pager, next, jumper"
:total="400"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
在 <script> </script>
中拷贝如下代码:
import { ref } from 'vue'
const currentPage4 = ref(4)
const pageSize4 = ref(100)
const handleSizeChange = (val: number) => {
console.log(`${val} items per page`)
}
const handleCurrentChange = (val: number) => {
console.log(`current page: ${val}`)
}
目前,整个 Element.vue
的文件内容如下:
<script setup lang="ts">
import { ref } from 'vue'
const currentPage4 = ref(4)
const pageSize4 = ref(100)
const handleSizeChange = (val: number) => {
console.log(`${val} items per page`)
}
const handleCurrentChange = (val: number) => {
console.log(`current page: ${val}`)
}
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>
<template>
<!-- Button按钮 -->
<el-row class="mb-4">
<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>
</el-row>
<br>
<!-- Table表格 -->
<el-table :data="tableData" border 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>
<br>
<!-- Pagination分页条 -->
<el-pagination
v-model:current-page="currentPage4"
v-model:page-size="pageSize4"
:page-sizes="[100, 200, 300, 400]"
layout="total, sizes, prev, pager, next, jumper"
:total="400"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</template>
<style scoped>
</style>
打开浏览器,查看页面效果如下:

Pagination 分页组件的属性如下:
对于分页组件我们需要关注的是如下几个重要属性(可以通过查阅官网组件中最下面的组件属性详细说明得到):
- background: 添加北京颜色,也就是上图蓝色背景色效果。
- layout: 分页工具条的布局,其具体值包含
sizes
,prev
,pager
,next
,jumper
,total
这些值 - total: 数据的总数量

对于分页组件,除了上述几个属性,还有2个非常重要的事件我们需要去学习:
- size-change : pageSize 改变时会触发
- current-change :currentPage 改变时会触发
对话框组件
在保留当前页面状态的情况下,告知用户并承载相关操作。

首先我们需要在ElementPlus官方找到Dialog组件,如下图所示:

然后复制如下代码到我们的组件文件 Element.vue
的 <template></template>
模块中:
<el-button @click="dialogTableVisible = true">
打开对话框
</el-button>
<el-dialog v-model="dialogTableVisible" title="Shipping address">
<el-table :data="tableData">
<el-table-column property="date" label="Date" width="150" />
<el-table-column property="name" label="Name" width="200" />
<el-table-column property="address" label="Address" />
</el-table>
</el-dialog>
然后复制如下代码到我们的组件文件 Element.vue
的 <script></script>
模块中:
const dialogTableVisible = ref(false)
最终,完成的 Element.vue
的代码如下:
<script setup lang="ts">
import { ref } from 'vue'
const currentPage4 = ref(4)
const pageSize4 = ref(100)
const handleSizeChange = (val: number) => {
console.log(`${val} items per page`)
}
const handleCurrentChange = (val: number) => {
console.log(`current page: ${val}`)
}
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'}
]
const dialogTableVisible = ref(false)
</script>
<template>
<!-- Button按钮 -->
<el-row class="mb-4">
<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>
</el-row>
<br>
<!-- Table表格 -->
<el-table :data="tableData" border 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>
<br>
<!-- Pagination分页条 -->
<el-pagination
v-model:current-page="currentPage4"
v-model:page-size="pageSize4"
:page-sizes="[100, 200, 300, 400]"
layout="total, sizes, prev, pager, next, jumper"
:total="400"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
<br>
<el-button @click="dialogTableVisible = true">
打开对话框
</el-button>
<el-dialog v-model="dialogTableVisible" title="Shipping address">
<el-table :data="tableData">
<el-table-column property="date" label="Date" width="150" />
<el-table-column property="name" label="Name" width="200" />
<el-table-column property="address" label="Address" />
</el-table>
</el-dialog>
</template>
<style scoped>
</style>
打开浏览器,最终的页面效果如下:

Dialog对话框组件使用的关键点,就是控制其显示与隐藏。 通过 v-model 给定的boolean值,来控制Dialog的显示与隐藏。
表单组件
Form 表单:由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据。
表单在我们前端的开发中使用的还是比较多的,接下来我们学习这个组件,与之前的流程一样,我们首先需要在ElementPlus的官方找到对应的组件示例:如下图所示:

然后复制如下代码到我们的组件文件 Element.vue
的 <template></template>
模块中:
<!-- Form 表单 -->
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="Approved by">
<el-input v-model="formInline.user" placeholder="Approved by" clearable />
</el-form-item>
<el-form-item label="Activity zone">
<el-select v-model="formInline.region" placeholder="Activity zone" clearable>
<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-date-picker v-model="formInline.date" type="date" placeholder="Pick a date" clearable/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Query</el-button>
</el-form-item>
</el-form>
然后复制如下代码到我们的组件文件 Element.vue
的 <script></script>
模块中:
const formInline = ref({
user: '',
region: '',
date: '',
})
const onSubmit = () => {
console.log('submit!')
}
打开浏览器,查看页面效果:

总结
课堂作业
- 参考上述步骤,完成组件的显示(CV大法)
- 对比上述的组件,主要分为几个部分操作??🎤
2.3 案例
案例
需求:基于ElementPlus组件库制作如下页面,并异步获取数据,完成页面展示。

代码操作
1). 准备工作
由于在案例中,我们需要在vue项目中使用Axios,需要安装axios,需要在当前项目的目录下执行如下命令:
npm install axios

2). 编码实现
在views目录下,再定义一个文件 EmpList.vue
,具体代码实现如下:
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const searchEmp = ref({
name: '',
gender: '',
job: '',
})
onMounted(() => {
search();
})
const search = async () => {
const url = `https://web-server.itheima.net/emps/list?name=${searchEmp.value.name}&gender=${searchEmp.value.gender}&job=${searchEmp.value.job}`
const result = await axios.get(url)
tableData.value = result.data.data
}
const clear = () => {
searchEmp.value = { name: '', gender: '', job: '' }
search();
}
let tableData = ref([])
</script>
<template>
<div id="center">
<el-form :inline="true" :model="searchEmp" class="demo-form-inline">
<el-form-item label="姓名">
<el-input v-model="searchEmp.name" placeholder="请输入姓名" clearable />
</el-form-item>
<el-form-item label="性别">
<el-select v-model="searchEmp.gender" placeholder="请选择" clearable>
<el-option label="男" value="1" />
<el-option label="女" value="2" />
</el-select>
</el-form-item>
<el-form-item label="职位">
<el-select v-model="searchEmp.job" placeholder="请选择" clearable>
<el-option label="班主任" value="1" />
<el-option label="讲师" value="2" />
<el-option label="咨询师" value="3" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="search">查询</el-button>
<el-button type="primary" @click="clear">清空</el-button>
</el-form-item>
</el-form>
<br>
<!-- 表格 -->
<el-table :data="tableData" border style="width: 100%; ">
<el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column prop="name" label="姓名" width="100" align="center" />
<el-table-column label="头像" width="120" align="center">
<template #default="scope">
<img :src="scope.row.image" width="50">
</template>
</el-table-column>
<el-table-column prop="gender" label="性别" width="120" align="center">
<template #default="scope">
{{ scope.row.gender == 1 ? '男' : '女' }}
</template>
</el-table-column>
<el-table-column label="职位" width="180" align="center">
<template #default="scope">
<span v-if="scope.row.job == 1">班主任</span>
<span v-else-if="scope.row.job == 2">讲师</span>
<span v-else-if="scope.row.job == 3">咨询师</span>
<span v-else>其他</span>
</template>
</el-table-column>
<el-table-column prop="entrydate" label="入职日期" width="180" align="center" />
<el-table-column prop="updatetime" label="更新时间" align="center" />
</el-table>
</div>
</template>
<style scoped>
#center {
width: 70%;
margin: auto;
margin-top: 100px;
}
</style>
3). 在 App.vue
中引入 EmpList.vue
文件
<script setup lang="ts">
import EmpList from './views/EmpList.vue'
</script>
<template>
<EmpList></EmpList>
</template>
<style scoped>
</style>
4). 打开浏览器,查看页面效果

总结
课堂作业
- 参考上述步骤,完成案例效果!!🎤
3. 前后端分离开发
前后端分离开发
在之前的课程中,我们介绍过,现在的企业项目开发有2种开发模式:前后台混合开发和前后台分离开发。
前后台混合开发,顾名思义就是前台后台代码混在一起开发。这种开发模式有如下缺点:
- 沟通成本高:后台人员发现前端有问题,需要找前端人员修改,前端修改成功,再交给后台人员使用
- 分工不明确:后台开发人员需要开发后台代码,也需要开发部分前端代码。很难培养专业人才
- 不便管理:所有的代码都在一个工程中
- 难以维护:前端代码更新,和后台无关,但是需要整个工程包括后台一起重新打包部署。
所以我们目前基本都是采用的前后台分离开发方式,如下图所示:

我们将原先的工程分为前端工程和后端工程这2个工程,然后前端工程交给专业的前端人员开发,后端工程交给专业的后端人员开发。
问题1 前端页面需要数据,可以通过发送异步请求,从后台工程获取。但是,我们前后台是分开来开发的,那么前端人员怎么知道后台返回数据的格式呢?
问题2 后端人员开发,怎么知道前端人员需要的数据格式呢?
所以针对这个问题,我们前后台统一制定一套规范!我们前后台开发人员都需要遵循这套规范开发,这就是我们的接口文档。接口文档有离线版和在线版本,接口文档示可以查询今天提供资料/接口文档里面的资料。
问题3 那么接口文档的内容怎么来的呢?
是我们后台开发者根据产品经理提供的产品原型和需求文档所撰写出来的,产品原型示例可以参考今天提供资料/页面原型里面的资料。
那么基于前后台分离开发的模式下,我们后台开发者开发一个功能的具体流程如何呢?如下图所示:

- 需求分析 :首先我们需要阅读需求文档,分析需求,理解需求。
- 接口定义 :查询接口文档中关于需求的接口的定义,包括地址,参数,响应数据类型等等
- 前后台并行开发 :各自按照接口文档进行开发,实现需求
- 测试 :前后台开发完了,各自按照接口文档进行测试
- 前后段联调测试 :前段工程请求后端工程,测试功能
4. 准备工作
准备工作

代码操作
1️⃣ 创建Vue项目
在自己工作目录下,运行 cmd
打开命令行,运行如下指令,来创建vue项目【这步可以不用操作,直接导入资料中提供的 vue-tlias-management.zip
】。
npm init vue@latest

2️⃣ 安装依赖
1). 在命令行中执行如下命令,为创建好的Vue项目安装 ElementPlus、Axios
的依赖。
npm install element-plus --save
npm install axios

2). 为创建好的 Vue项目 配置ElementPlus (参照官网),在 main.ts
中引入如下配置信息 【注意:是追加如下内容】:
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
//引入ElementPlus的Icon组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus, {locale: zhCn})
app.mount('#app')
最终完整的 main.ts
文件内容如下:
import { createApp } from 'vue'
import { createPinia } from 'pinia' //导入pinia
//导入
import App from './App.vue'
import router from './router'
import './assets/main.css'
//导入ElementPlus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
//创建vue的应用实例
const app = createApp(App)
//注册ElementPlus的Icon组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
//应用pinia router elementPlus
app.use(createPinia())
app.use(router)
app.use(ElementPlus, {locale: zhCn})
app.mount('#app')
3). 在 env.d.ts
中引入ElementPlus的语言包
declare module 'element-plus/dist/locale/zh-cn.mjs'
注意:以上的这些,我们都不需要操作,因为在提供的资料中已经准备好了基础工程,我们直接导入进来即可。
3️⃣ 精简项目
由于基于Vue脚手架创建的项目中,里面携带了很多的多余的Vue组件。 并准备对应的组件存放目录 。
- 删除
components
目录中的vue文件 - 删除
views
目录中的vue文件 - 清空根组件文件
App.vue
中的内容,只保留基础的vue组件文件的结构标签<script>
<template>
<style>

注意:以上的这些,我们都不需要操作,因为在提供的资料中已经准备好了基础工程,我们直接导入进来即可。
执行npm run dev
运行程序,结果如下:

总结
课堂作业
- 根据上述提示,完成准备工作,并运行成功🎤
5. 页面布局
页面布局
我们在制作一个页面的时候,一定是先关注整体的页面布局,然后再关注具体的细节处理 。 所以这一小节,我们就先来完成页面的整体布局。

我们会看到,整个页面分为这么三个部分:
- 页头部分
- 侧边栏
- 主区域
而要完成这样的页面布局,我们其实是可以借助于 ElementPlus
中提供的 Container布局容器
来实现:

Container布局容器,用于布局的容器组件,方便快速搭建页面的基本结构:
<el-container>
:外层容器。 当子元素中包含 <el-header>
或 <el-footer>
时,全部子元素会垂直上下排列, 否则会水平左右排列。
<el-header>
:顶栏容器。
<el-aside>
:侧边栏容器。
<el-main>
:主要区域容器。
<el-footer>
:底栏容器。
而针对于我们当前案例的页面布局,基本的结构如下:

提示:当
<el-container>
子元素中包含<el-header>
或<el-footer>
时,全部子元素会垂直上下排列, 否则会水平左右排列。
代码操作
我们可以参照 ElementPlus
的官方网站中的 布局,拷贝其源码,然后对其做一个改造。 具体参照的源码如下:

1). 在 src/views
目录下,再创建一个子目录 layout
,在其中新建一个页面,页面命名为:index.vue
。
2). 在 index.vue
中准备好基础的组件结构后,就可以将代码直接复制到 <template> </template>
标签中。

<script setup lang="ts">
</script>
<template>
<div class="common-layout">
<el-container>
<!-- 顶栏 - header -->
<el-header>Header</el-header>
<!-- 左侧菜单 & 主区域 -->
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-main>Main</el-main>
</el-container>
</el-container>
</div>
</template>
<style scoped>
</style>
然后,我们先根据页面原型中的布局显示进行调整。 先完成顶栏部分的制作,具体的代码如下:
<script setup lang="ts">
</script>
<template>
<div class="common-layout">
<el-container>
<!-- 顶栏 - header -->
<el-header class="header">
<span class="title">Yangeit智能学习辅助系统</span>
<span class="right_tool">
<a href=""><el-icon><EditPen /></el-icon> 修改密码 </a>
<a href=""><el-icon><SwitchButton /></el-icon> 退出登录 </a>
</span>
</el-header>
<!-- 左侧菜单 & 主区域 -->
<el-container>
<el-aside width="200px">Aside</el-aside>
<el-main>Main</el-main>
</el-container>
</el-container>
</div>
</template>
<style scoped>
.header {
background-image: linear-gradient(to right, #e70cc5, #e94dcf, #eb6fd8, #ec8bdf, #eea5e6);
line-height: 60px;
}
.title {
color: white;
font-size: 35px;
font-family: 楷体;
}
.right_tool {
float: right;
}
a {
text-decoration: none;
color: white;
}
</style>
最终的顶栏布局效果如下所示:

3.3 左侧菜单
顶栏布局完毕之后,接下来,我们再来完成左侧菜单栏的制作。 左侧菜单栏的制作,也不需要我们自己实现,其实在 ElementPlus
中已经提供了对应的菜单组件,我们可以直接参考【PS: 其实就是复制过来,参考页面原型和需求,将其改造成我们需要的样子就可以了】。
参考代码的出处如下:

然后就可以参考其提供的源码,复制到我们的侧边栏部分 <el-aside> ... </el-aside>
,然后根据我们案例的需要进行改造,改造成我们需要的样子即可。
最终左侧菜单栏的代码如下:
<!-- 左侧菜单 -->
<el-aside width="200px" class="aside">
<el-scrollbar>
<el-menu router>
<!-- 首页菜单 -->
<el-menu-item index="/index">
<el-icon><Promotion /></el-icon> 首页
</el-menu-item>
<!-- 班级管理菜单 -->
<el-sub-menu index="/manage">
<template #title>
<el-icon><Menu /></el-icon> 班级学员管理
</template>
<el-menu-item index="/clazz">
<el-icon><HomeFilled /></el-icon>班级管理
</el-menu-item>
<el-menu-item index="/stu">
<el-icon><UserFilled /></el-icon>学员管理
</el-menu-item>
</el-sub-menu>
<!-- 系统信息管理 -->
<el-sub-menu index="/system">
<template #title>
<el-icon><Tools /></el-icon>系统信息管理
</template>
<el-menu-item index="/dept">
<el-icon><HelpFilled /></el-icon>部门管理
</el-menu-item>
<el-menu-item index="/emp">
<el-icon><Avatar /></el-icon>员工管理
</el-menu-item>
</el-sub-menu>
<!-- 数据统计管理 -->
<el-sub-menu index="/report">
<template #title>
<el-icon><Histogram /></el-icon>数据统计管理
</template>
<el-menu-item index="/empReport">
<el-icon><InfoFilled /></el-icon>员工信息统计
</el-menu-item>
<el-menu-item index="/stuReport">
<el-icon><Share /></el-icon>学员信息统计
</el-menu-item>
<el-menu-item index="/log">
<el-icon><Document /></el-icon>日志信息统计
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-scrollbar>
</el-aside>
并在 <style></style>
中添加如下样式:
.aside {
border: 1px solid #ccc;
height: 690px;
width: 220px;
}
最终,浏览器打开的效果如下:

index.vue完整代码
👇 👇 👇
点击查看index.vue完整代码
到目前为止,layout/index.vue
中的内容如下:
<script setup lang="ts">
</script>
<template>
<div class="common-layout">
<el-container>
<!-- 顶栏 - header -->
<el-header class="header">
<span class="title">Yangeit智能学习辅助系统</span>
<span class="right_tool">
<a href=""><el-icon><EditPen /></el-icon> 修改密码 </a>
<a href=""><el-icon><SwitchButton /></el-icon> 退出登录 </a>
</span>
</el-header>
<!-- 左侧菜单 & 主区域 -->
<el-container>
<!-- 左侧菜单 -->
<el-aside width="200px" class="aside">
<el-scrollbar>
<el-menu router>
<!-- 首页菜单 -->
<el-menu-item index="/index">
<el-icon><Promotion /></el-icon> 首页
</el-menu-item>
<!-- 班级管理菜单 -->
<el-sub-menu index="/manage">
<template #title>
<el-icon><Menu /></el-icon> 班级学员管理
</template>
<el-menu-item index="/clazz">
<el-icon><HomeFilled /></el-icon>班级管理
</el-menu-item>
<el-menu-item index="/stu">
<el-icon><UserFilled /></el-icon>学员管理
</el-menu-item>
</el-sub-menu>
<!-- 系统信息管理 -->
<el-sub-menu index="/system">
<template #title>
<el-icon><Tools /></el-icon>系统信息管理
</template>
<el-menu-item index="/dept">
<el-icon><HelpFilled /></el-icon>部门管理
</el-menu-item>
<el-menu-item index="/emp">
<el-icon><Avatar /></el-icon>员工管理
</el-menu-item>
</el-sub-menu>
<!-- 数据统计管理 -->
<el-sub-menu index="/report">
<template #title>
<el-icon><Histogram /></el-icon>数据统计管理
</template>
<el-menu-item index="/empReport">
<el-icon><InfoFilled /></el-icon>员工信息统计
</el-menu-item>
<el-menu-item index="/stuReport">
<el-icon><Share /></el-icon>学员信息统计
</el-menu-item>
<el-menu-item index="/log">
<el-icon><Document /></el-icon>日志信息统计
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-scrollbar>
</el-aside>
<el-main>Main</el-main>
</el-container>
</el-container>
</div>
</template>
<style scoped>
.header {
background-image: linear-gradient(to right, #e70cc5, #e94dcf, #eb6fd8, #ec8bdf, #eea5e6);
line-height: 60px;
}
.title {
color: white;
font-size: 35px;
font-family: 楷体;
}
.right_tool {
float: right;
}
a {
text-decoration: none;
color: white;
}
.aside {
border: 1px solid #ccc;
height: 690px;
width: 220px;
}
</style>
目前,我们点击左侧的菜单,右侧主区域展示的内容,还不能做到动态变化 。 那应该如何做到动态变化呢 ?

那要完成这个功能效果,我们就需要用到Vue生态中的路由 Vue-Router
。
6. Vue Router
6.1 路由介绍和入门
路由介绍和入门

- Vue Router:Vue的官方路由。 为Vue提供富有表现力、可配置的、方便的路由。
- Vue中的路由,主要定义的是路径与组件之间的对应关系。
比如,我们打开一个网站,点击左侧菜单,地址栏的地址发生变化。 地址栏地址一旦发生变化,在主区域显示对应的页面组件。


VueRouter主要由以下三个部分组成,如下所示:

- VueRouter:路由器类,根据路由请求在路由视图中动态渲染选中的组件
- <router-link>:请求链接组件,浏览器会解析成<a>
- <router-view>:动态视图组件,用来渲染展示与路由路径对应的组件
代码操作
介绍完了VueRouter之后,接下来,我们就通过一个入门程序,来演示一下VueRouter的使用。
1). 安装 vue-router
(创建Vue项目时,已经选择)
npm install vue-router@4
2). 在 main.ts
入口文件中进行配置,加入如下配置
import router from './router'
//..... 创建完vue的应用实例后,调用app.use
app.use(router)
3). 在 src/views 目录下再定义一个文件夹,在文件夹中再创建一个 vue 组件文件

4). 定义路由
在 src/router/index.ts
中定义路由表信息,在其中主要是定义请求路径与组件之间的对应关系。 完整的文件内容如下:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: () => import('../views/layout/index.vue')
},
{
path: '/index',
name: 'index',
component: () => import('../views/index/index.vue')
}
]
})
export default router
5). 在 App.vue
根组件中,定义 <RouterView></RouterView>
标签
该标签将用于显示,访问的请求路径对应的组件。
<script setup lang="ts">
</script>
<template>
<RouterView></RouterView>
</template>
<style scoped>
</style>
6). 测试
浏览器访问请求路径 http://127.0.0.1:5173/index
,展示如下页面内容(该页面内容,就是我们在 index/index.vue
中定义的页面内容):

浏览器访问请求路径 http://127.0.0.1:5173/
,展示如下页面内容 (该页面内容,就是我们在 layout/index.vue
中定义的页面内容):

到此,我们发现,我们请求不同的请求路径,就可以在页面中显示不同的组件。具体的访问流程如下:

总结
课堂作业
- 参考上述步骤,完成路由文件index.ts 的创建,以及完成页面的切换🎤
6.2 案例
前言
那接下来,我们就要基于 VueRouter
来完成点击 左侧菜单,动态切换主展示区域内容的动态效果。
代码操作
1). 准备案例的空页面 (资料中已经提供,直接复制到项目的 src/views
目录中即可)

2). 在 src/router/index.ts
中配置路由信息
这里我们用到了Vue中的嵌套路由,具体定义方式,主要是在配置路由信息时,通过children
来描述。如你所见,children
配置只是另一个路由数组,就像 routes
本身一样。因此,你可以根据自己的需要,不断地嵌套视图。
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: () => import('../views/layout/index.vue'),
redirect: '/index',
children: [
{
path: 'index',
name: 'index',
component: () => import('../views/index/index.vue') //首页
},
{
path: 'emp',
name: 'emp',
component: () => import('../views/emp/index.vue') //员工管理
},
{
path: 'dept',
name: 'dept',
component: () => import('../views/dept/index.vue') //部门管理
},
{
path: 'clazz',
name: 'clazz',
component: () => import('../views/clazz/index.vue') //班级管理
},
{
path: 'stu',
name: 'stu',
component: () => import('../views/stu/index.vue') //学员管理
}
]
}
]
})
export default router
3). 完善左侧菜单栏 layout/index.vue
,菜单栏关联路由

菜单关联了路由之后,我们点击对应的菜单,就会根据菜单的唯一标识 index
,在地址栏中请求访问对应的地址。
4). 在Vue组件中,动态展示与路由对应的组件 。
需要在 layout/index.vue
中的 <el-main></el-main>
中添加动态路由视图组件 <RouterView></RouterView>
。如下:
<!-- 主展示区域 -->
<el-main>
<RouterView></RouterView>
</el-main>
最终完整的 layout/index.vue
代码如下:
<script setup lang="ts">
</script>
<template>
<div class="common-layout">
<el-container>
<!-- 顶栏 - header -->
<el-header class="header">
<span class="title">Yangeit智能学习辅助系统</span>
<span class="right_tool">
<a href=""><el-icon><EditPen /></el-icon> 修改密码 </a>
<a href=""><el-icon><SwitchButton /></el-icon> 退出登录 </a>
</span>
</el-header>
<!-- 左侧菜单 & 主区域 -->
<el-container>
<!-- 左侧菜单 -->
<el-aside width="200px" class="aside">
<el-scrollbar>
<el-menu router>
<!-- 首页菜单 -->
<el-menu-item index="/index">
<el-icon><Promotion /></el-icon> 首页
</el-menu-item>
<!-- 班级管理菜单 -->
<el-sub-menu index="/manage">
<template #title>
<el-icon><Menu /></el-icon> 班级学员管理
</template>
<el-menu-item index="/clazz">
<el-icon><HomeFilled /></el-icon>班级管理
</el-menu-item>
<el-menu-item index="/stu">
<el-icon><UserFilled /></el-icon>学员管理
</el-menu-item>
</el-sub-menu>
<!-- 系统信息管理 -->
<el-sub-menu index="/system">
<template #title>
<el-icon><Tools /></el-icon>系统信息管理
</template>
<el-menu-item index="/dept">
<el-icon><HelpFilled /></el-icon>部门管理
</el-menu-item>
<el-menu-item index="/emp">
<el-icon><Avatar /></el-icon>员工管理
</el-menu-item>
</el-sub-menu>
<!-- 数据统计管理 -->
<el-sub-menu index="/report">
<template #title>
<el-icon><Histogram /></el-icon>数据统计管理
</template>
<el-menu-item index="/empReport">
<el-icon><InfoFilled /></el-icon>员工信息统计
</el-menu-item>
<el-menu-item index="/stuReport">
<el-icon><Share /></el-icon>学员信息统计
</el-menu-item>
<el-menu-item index="/log">
<el-icon><Document /></el-icon>日志信息统计
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-scrollbar>
</el-aside>
<!-- 主展示区域 -->
<el-main>
<RouterView></RouterView>
</el-main>
</el-container>
</el-container>
</div>
</template>
<style scoped>
.header {
background-image: linear-gradient(to right, #e70cc5, #e94dcf, #eb6fd8, #ec8bdf, #eea5e6);
line-height: 60px;
}
.title {
color: white;
font-size: 35px;
font-family: 楷体;
}
.right_tool {
float: right;
}
a {
text-decoration: none;
color: white;
}
.aside {
border: 1px solid #ccc;
height: 690px;
width: 220px;
}
</style>
5). 测试


6.3 首页制作🎯
首页制作
其实首页,我们只需要展示一张图片即可。 直接在 index/index.vue
中引入一张图片即可,具体代码如下:
将图片放到assets文件夹中(模版工程中存在)
<script setup lang="ts">
</script>
<template>
<img src="../../assets/index.png" />
</template>
<style scoped>
</style>
最终效果如下:

总结
课堂作业
- 根据上述提示,完成首页制作🎤
7. 部门管理
部门管理的页面内容,写在 src/views/dept/index.vue
中。
7.1 部门列表
部门列表
- 基本布局
- 加载数据
- 程序优化
代码操作
基本布局
首先,根据页面原型、需求说明、接口文档,先完成页面的基本布局 。 可以参考 ElementPlus
中的组件,拷贝过来适当做一个改造。

部门管理组件 src/views/dept/index.vue
具体的页面布局代码如下:
<script setup lang="ts">
import {ref} from 'vue'
import type { DeptModelArray } from '@/api/model/model'
//声明列表展示数据
let tableData = ref<DeptModelArray>([])
</script>
<template>
<h1>部门管理</h1>
<el-button type="primary" style="float: right" @click="">+ 新增</el-button>
<br><br>
<!-- 部门数据表格 -->
<el-table :data="tableData" border style="width: 100%">
<el-table-column type="index" label="序号" width="80" align="center"/>
<el-table-column prop="name" label="部门名称" width="250" align="center"/>
<el-table-column prop="updateTime" label="最后操作时间" width="300" align="center"/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button size="small" type="primary" @click="">修改</el-button>
<el-button size="small" type="danger" @click="">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<style scoped>
</style>
表格中每一列展示的属性 prop
都是根据接口文档来的,接口文档返回什么样的数据,我们就安装对应的数据格式进行解析。
加载数据
根据需求,需要在新增、修改、删除部门之后,加载最新的部门数据。 在打开页面之后,也需要自动加载部门数据。 那接下来,我们就需要基于axios发送异步请求,动态获取数据。
需要在 src/views/dept/index.vue
中增加如下代码,在页面加载完成发送异步请求 (https://mock.apifox.com/m1/3161925-0-default/depts
),动态加载的Axios。
<script setup lang="ts">
import {ref, onMounted} from 'vue'
import type { DeptModelArray } from '@/api/model/model'
import axios from 'axios'
//声明列表展示数据
let tableData = ref<DeptModelArray>([])
//动态加载数据-查询部门
const queryAll = async () => {
const result = await axios.get('https://mock.apifox.com/m1/3161925-0-default/depts')
tableData.value = result.data.data
}
//钩子函数
onMounted(() => {
queryAll()
})
</script>
添加代码后,最终 src/views/dept/index.vue
代码如下:
<script setup lang="ts">
import {ref, onMounted} from 'vue'
import type { DeptModelArray } from '@/api/model/model'
import axios from 'axios'
//声明列表展示数据
let tableData = ref<DeptModelArray>([])
//动态加载数据-查询部门
const queryAll = async () => {
const result = await axios.get('https://mock.apifox.com/m1/3161925-0-default/depts')
tableData.value = result.data.data
}
//钩子函数
onMounted(() => {
queryAll()
})
</script>
<template>
<h1>部门管理</h1>
<el-button type="primary" style="float: right" @click="">+ 新增</el-button>
<br><br>
<!-- 部门数据表格 -->
<el-table :data="tableData" border style="width: 100%">
<el-table-column type="index" label="序号" width="80" align="center"/>
<el-table-column prop="name" label="部门名称" width="250" align="center"/>
<el-table-column prop="updateTime" label="最后操作时间" width="300" align="center"/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button size="small" type="primary" @click="">修改</el-button>
<el-button size="small" type="danger" @click="">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<style scoped>
</style>
代码编写完成之后,打开浏览器进行测试 ,我们可以看到数据可以正常的查询出来,并展示在页面中。

思考:直接在Vue组件中,基于axios发送异步请求,存在什么问题?

我们刚才在完成部门列表查询时,是直接基于axios发送异步请求,直接将接口的请求地址放在组件文件 .vue
中。 而如果开发一个大型的项目,组件文件可能会很多很多很多,如果前端开发完毕,进行前后端联调测试了,需要修改请求地址,那么此时,就需要找到每一个 .vue
文件,然后挨个修改。 所以上述的代码,虽然实现了动态加载数据的功能。 但是存在以下问题:
- 请求路径难以维护
- 数据解析繁琐
程序优化
1). 为了解决上述问题,我们在前端项目开发时,通常会定义一个请求处理的工具类 - src/utils/request.ts
。 在这个工具类中,对axios进行了封装。 具体代码如下:
import axios from 'axios'
//创建axios实例对象
const request = axios.create({
baseURL: '/api',
timeout: 600000
})
//axios的响应 response 拦截器
request.interceptors.response.use(
(response) => { //成功回调
return response.data
},
(error) => { //失败回调
return Promise.reject(error)
}
)
export default request
2). 而与服务端进行异步交互的逻辑,通常会按模块,封装在一个单独的API中,如:src/api/dept.ts
import request from "@/utils/request"
import type { ResultModel } from "./model/model"
//列表查询
export const queryAllApi = () => request.get<any, ResultModel>('/depts')
3). 修改 src/views/dept/index.vue
中的代码
现在就不需要每次直接调用axios发送异步请求了,只需要将我们定义的对应模块的API导入进来,就可以直接使用了。
<script setup lang="ts">
import {ref, onMounted} from 'vue'
import type { DeptModelArray } from '@/api/model/model'
import {queryAllApi} from '@/api/dept'
//声明列表展示数据
let tableData = ref<DeptModelArray>([])
//动态加载数据-查询部门
const queryAll = async () => {
const result = await queryAllApi()
tableData.value = result.data
}
//钩子函数
onMounted(() => {
queryAll()
})
</script>
做完上面这三部之后,我们打开浏览器发现,并不能访问到接口数据。原因是因为,目前请求路径不对。

4). 在 vite.config.ts
中配置前端请求服务器的信息
在服务器中配置代理proxy的信息,并在配置代理时,执行目标服务器。 以及url路径重写的规则。

server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
secure: false,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
}
}
}
添加位置如下所示:

然后,我们就可以启动服务器端的程序,进行测试了(测试时,记得将之前编写的登录校验的过滤器、拦截器、AOP程序全部注释掉 )。

总结
课堂作业
- 根据上述提示,完成部门列表的书写🎤