前端Web案例-部门管理
前端Web案例-部门管理
相关信息
在前面的课程中,我们学习了Vue工程化的基础内容、TS、ElementPlus,那接下来呢,我们要通过一个案例,加强大家对于Vue项目的理解,并掌握Vue项目的开发。 这个案例呢,就是我们之前所做的Yangeit智能学习辅助系统。

在这个案例中,我们主要完成 部门管理 和 员工管理 的功能开发。 而今天呢,我们先来完成部门管理的功能开发,而在完成部门管理的功能开发之前,先需要完成基础的准备工作。 所以今天的课程安排如下:
- 前后端分类开发
- 准备工作
- 页面布局
- Vue-Router
- 部门管理
1. 前后端分离开发
前后端分离开发
在之前的课程中,我们介绍过,现在的企业项目开发有2种开发模式:前后台混合开发和前后台分离开发。
前后台混合开发,顾名思义就是前台后台代码混在一起开发。这种开发模式有如下缺点:
- 沟通成本高:后台人员发现前端有问题,需要找前端人员修改,前端修改成功,再交给后台人员使用
- 分工不明确:后台开发人员需要开发后台代码,也需要开发部分前端代码。很难培养专业人才
- 不便管理:所有的代码都在一个工程中
- 难以维护:前端代码更新,和后台无关,但是需要整个工程包括后台一起重新打包部署。
所以我们目前基本都是采用的前后台分离开发方式,如下图所示:

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

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

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

如果使用了案例代码,已经安装了,了解即可
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')
如果已经导入yangeit提供的
vue-tlias-management.zip
已经提前配置好了 👍
最终完整的 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
运行程序,结果如下:

总结
课堂作业
- 根据上述提示,完成准备工作,并运行成功,效果如下🎤

3. 页面布局
页面布局
我们在制作一个页面的时候,一定是先关注整体的页面布局,然后再关注具体的细节处理 。 所以这一小节,我们就先来完成页面的整体布局。

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

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

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

操作在这!!! 👇 👇
- 在
src/views
目录下,再创建一个子目录layout
,在其中新建一个页面,页面命名为:index.vue
。

- 在
index.vue
中准备好基础的组件结构后,就可以将代码直接复制到<template> </template>
标签中。

- 最终的顶栏布局效果如下所示:

顶栏布局完毕之后,接下来,我们再来完成左侧菜单栏的制作。
左侧菜单栏的制作,也不需要我们自己实现,其实在 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完整代码
代码已经在提供的资料中的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
。
4. Vue Router
4.1 路由介绍和入门
路由介绍和入门

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


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

- VueRouter:路由器类,根据路由请求在路由视图中动态渲染选中的组件
<router-link>
;:请求链接组件,浏览器会解析成<a>
;<router-view>
;:动态视图组件,用来渲染展示与路由路径对应的组件常用
代码操作
介绍完了VueRouter之后,接下来,我们就通过一个入门程序,来演示一下VueRouter的使用。
1). 安装 vue-router
(创建Vue项目时,已经选择,观察node_modules文件夹中,是否存在vue-router文件夹,如果存在就已经安装了 )
npm install vue-router@4
2. 在src下创建router文件,如果存在就不用创建,将已经提供的index.ts路由文件,导入到route文件夹中,具体教程如下:👇 👇

在
main.ts
入口文件中进行配置,加入如下配置
import router from './router'
//..... 创建完vue的应用实例后,调用app.use
app.use(router)
3. 在 App.vue
根组件中,定义 <RouterView></RouterView>
标签
该标签将用于显示,访问的请求路径对应的组件。
<script setup lang="ts">
</script>
<template>
<RouterView></RouterView>
</template>
<style scoped>
</style>

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

但是,切换左边的条目,绿色区域无变化!!!
5. 分析
这里我们用到了Vue中的嵌套路由,具体定义方式,主要是在配置路由信息时,通过children
来描述。如你所见,children
配置只是另一个路由数组,就像 routes
本身一样。因此,你可以根据自己的需要,不断地嵌套视图。
那为什么没有不断的变化嵌套视图尼?
解决方案:

6. 在进行测试


已经可以根据点击,切换具体页面了
问题:但是这些页面,是固定的页面,不是表格!!!肿么办!!!
总结
课堂作业
- 参考上述步骤,完成项目架子的搭建🎤
4.2 首页制作🎯
首页制作
其实首页,我们只需要展示一张图片即可。 直接在 index/index.vue
中引入一张图片即可,具体代码如下:
将图片放到assets文件夹中(模版工程中存在)
<script setup lang="ts">
</script>
<template>
<img src="../../assets/index.png" />
</template>
<style scoped>
</style>
最终效果如下:

总结
课堂作业
- 根据上述提示,完成首页制作🎤
5. 部门管理
部门管理的页面内容,写在
src/views/dept/index.vue
中。
5.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。
添加代码后,最终 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')

其他的方法,可以直接从资料中导入
- 修改
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/, ''),
}
}
}
添加位置如下所示:

在提供的资料中,也存在
5. 然后,我们就可以启动服务器端的程序,进行测试了(测试时,记得将之前编写的登录校验的过滤器、拦截器、AOP程序全部注释掉 )。
运行结果:

总结
课堂作业
- 根据上述提示,完成部门列表的书写,细心 细心 细心
注意:参考步骤完成上述的前端代码,多敲几遍。
5.2 新增部门
新增部门
接下来,我们再来完成新增部门的功能实现。

代码操作
1). 在 src/views/dept/index.vue
中完成页面布局,并编写交互逻辑,完成数据绑定。
完整代码如下:
<script setup lang="ts">
import {ref, onMounted} from 'vue'
import type { DeptModelArray, DeptModel } from '@/api/model/model'
import {queryAllApi, addApi} from '@/api/dept'
import { ElMessage } from 'element-plus';
//声明列表展示数据
let tableData = ref<DeptModelArray>([])
//动态加载数据-查询部门
const queryAll = async () => {
const result = await queryAllApi()
tableData.value = result.data
}
//钩子函数
onMounted(() => {
queryAll()
})
//新增部门
const dialogFormVisible = ref<boolean>(false)
const deptForm = ref<DeptModel>({name: ''})
const formTitle = ref<string>('')
//点击新增按钮触发的函数
const add = () => {
formTitle.value = '新增部门'
dialogFormVisible.value = true
deptForm.value = {name: ''}
}
//点击保存按钮-发送异步请求
const save = async () => {
const result = await addApi(deptForm.value)
if(result.code){
ElMessage.success('操作成功')
}else{
ElMessage.error(result.msg)
}
dialogFormVisible.value = false
queryAll()
}
</script>
<template>
<h1>部门管理</h1>
<el-button type="primary" style="float: right" @click="add">+ 新增</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>
<!-- 新增部门 / 修改部门对话框 -->
<el-dialog v-model="dialogFormVisible" :title="formTitle" width="30%">
<el-form :model="deptForm">
<el-form-item label="部门名称" label-width="80px">
<el-input v-model="deptForm.name" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="save">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<style scoped>
</style>
2). 在 src/api/dept.ts
中增加如下代码
//添加部门
export const addApi = (dept:DeptModel) => request.post<any, ResultModel>('/depts', dept)
目前 src/api/dept.ts
文件中完整代码如下:
import request from "@/utils/request"
import type { DeptModel, ResultModel } from "./model/model"
//列表查询
export const queryAllApi = () => request.get<any, ResultModel>('/depts')
//添加部门
export const addApi = (dept:DeptModel) => request.post<any, ResultModel>('/depts', dept)
打开浏览器进行测试,效果如下:


总结
课堂作业
- 按照上述的步骤,一步一步的完成以便,记得按照步骤哦!! 先写效果,在关注数据交互🎤
5.3 修改部门
修改部门
对于修改操作,通常会分为两步进行:
- 查询回显
- 保存修改

交互逻辑:
- 点击 编辑 按钮,根据ID进行查询,弹出对话框,完成页面回显展示。(查询回显)
- 点击 确定 按钮,保存修改后的数据,完成数据更新操作。(保存修改)
代码操作
查询回显
1). 在 src/api/dept.ts
中定义根据id查询的请求
//根据ID查询
export const queryInfoApi = (id:number) => request.get(`/depts/${id}`)
2). 在 src/views/dept/index.vue
中添加根据ID查询回显的逻辑
为修改按钮绑定事件 <template></template>
:
<el-button size="small" type="primary" @click="update(scope.row.id)">修改</el-button>
在 <script> </script>
添加JS逻辑:
//修改部门-查询回显
const update = async (id:number) => {
formTitle.value = '修改部门'
dialogFormVisible.value = true
deptForm.value = {name: ''}
const result = await queryInfoApi(id)
deptForm.value = result.data
}
回显效果:
到目前为止,完整的 src/views/dept/index.vue
代码如下:
阴影部分是发生修改的部分
<script setup lang="ts">
import {ref, onMounted} from 'vue'
import type { DeptModelArray, DeptModel } from '@/api/model/model'
import {queryAllApi, addApi, queryInfoApi} from '@/api/dept'
import { ElMessage } from 'element-plus';
//声明列表展示数据
let tableData = ref<DeptModelArray>([])
//动态加载数据-查询部门
const queryAll = async () => {
const result = await queryAllApi()
tableData.value = result.data
}
//钩子函数
onMounted(() => {
queryAll()
})
//新增部门
const dialogFormVisible = ref<boolean>(false)
const deptForm = ref<DeptModel>({name: ''})
const formTitle = ref<string>('')
//点击新增按钮触发的函数
const add = () => {
formTitle.value = '新增部门'
dialogFormVisible.value = true
deptForm.value = {name: ''}
}
//点击保存按钮-发送异步请求
const save = async () => {
const result = await addApi(deptForm.value)
if(result.code){
ElMessage.success('操作成功')
}else{
ElMessage.error(result.msg)
}
dialogFormVisible.value = false
queryAll()
}
//修改部门-查询回显
const update = async (id:number) => {
formTitle.value = '修改部门'
dialogFormVisible.value = true
deptForm.value = {name: ''}
const result = await queryInfoApi(id)
deptForm.value = result.data
}
</script>
<template>
<h1>部门管理</h1>
<el-button type="primary" style="float: right" @click="add">+ 新增</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="update(scope.row.id)">修改</el-button>
<el-button size="small" type="danger" @click="">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新增部门 / 修改部门对话框 -->
<el-dialog v-model="dialogFormVisible" :title="formTitle" width="30%">
<el-form :model="deptForm">
<el-form-item label="部门名称" label-width="80px">
<el-input v-model="deptForm.name" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="save">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<style scoped>
</style>
保存修改
由于 新增部门 和 修改部门使用的是同一个Dialog对话框,当前点击 “确定” 按钮的时候,有可能执行的是新增操作,也有可能是修改操作。

那应该如何辨别到底是新增,还是修改操作呢 ?
其实,我们只需要根据 deptForm
对象的id属性值,来判断即可。 如果没有id,则是新增操作 ;如果有id,则是修改操作。
所以,保存修改功能实现如下:
1). 在 src/api/dept.ts
中增加如下修改部门的请求
//修改部门
export const updateApi = (dept:DeptModel) => request.put<any, ResultModel>('/depts', dept)
2). 在 src/views/dept/index.vue
中完善(修改) save 函数的逻辑
//点击保存按钮-发送异步请求
const save = async () => {
let result = null;
if(deptForm.value.id){
result = await updateApi(deptForm.value) //有id, 执行修改操作
}else {
result = await addApi(deptForm.value) //没有id, 执行新增操作
}
if(result.code){
ElMessage.success('操作成功')
}else{
ElMessage.error(result.msg)
}
dialogFormVisible.value = false
queryAll()
}
修改位置:

运行效果:
总结
课堂作业
- 根据上述的步骤提示完成修改操作🎤
5.4 删除部门
删除部门

代码操作
1). 在 src/api/dept.ts
中增加如下删除部门的请求
//删除部门
export const deleteApi = (id:number) => request.delete<any, ResultModel>(`/depts?id=${id}`)
2). 在 src/views/dept/index.vue
中为什么 删除 按钮绑定事件
<el-button size="small" type="danger" @click="deleteById(scope.row.id)">删除</el-button>
3). 在 src/views/dept/index.vue
编写根据ID删除数据的函数
//删除部门
const deleteById =async (id:number) => {
//弹出确认框
ElMessageBox.confirm('您确认删除此部门吗? ', '确认删除').then( async () => {
let result = await deleteApi(id)
if(result.code){ //成功
ElMessage.success('删除成功')
queryAll()
}else {
ElMessage.error(result.msg)
}
}).catch(() => {
ElMessage.info('取消删除')
})
}
打开浏览器做一个测试:

至此dept/index.vue的完整代码:
点击查看完整代码
<script setup lang="ts">
import {ref, onMounted} from 'vue'
import type { DeptModelArray,DeptModel } from '@/api/model/model'
import {queryAllApi,addApi,queryInfoApi,updateApi,deleteApi} from '@/api/dept'
import { ElMessage,ElMessageBox } from 'element-plus';
//声明列表展示数据
let tableData = ref<DeptModelArray>([])
//动态加载数据-查询部门
const queryAll = async () => {
const result = await queryAllApi()
tableData.value = result.data
}
//钩子函数
onMounted(() => {
queryAll()
})
//新增部门
const dialogFormVisible = ref<boolean>(false)
const deptForm = ref<DeptModel>({name: ''})
const formTitle = ref<string>('')
//点击新增按钮触发的函数
const add = () => {
formTitle.value = '新增部门'
dialogFormVisible.value = true
deptForm.value = {name: ''}
}
//点击保存按钮-发送异步请求
const save = async () => {
let result = null;
if(deptForm.value.id){
result = await updateApi(deptForm.value) //有id, 执行修改操作
}else {
result = await addApi(deptForm.value) //没有id, 执行新增操作
}
if(result.code){
ElMessage.success('操作成功')
}else{
ElMessage.error(result.msg)
}
dialogFormVisible.value = false
queryAll()
}
//修改部门-查询回显
const update = async (id:number) => {
formTitle.value = '修改部门'
dialogFormVisible.value = true
deptForm.value = {name: ''}
const result = await queryInfoApi(id)
deptForm.value = result.data
}
//删除部门
const deleteById =async (id:number) => {
//弹出确认框
ElMessageBox.confirm('您确认删除此部门吗? ', '确认删除').then( async () => {
let result = await deleteApi(id)
if(result.code){ //成功
ElMessage.success('删除成功')
queryAll()
}else {
ElMessage.error(result.msg)
}
}).catch(() => {
ElMessage.info('取消删除')
})
}
</script>
<template>
<h1>部门管理</h1>
<el-button type="primary" style="float: right" @click="add">+ 新增</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="update(scope.row.id)">修改</el-button>
<el-button size="small" type="danger" @click="deleteById(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新增部门 / 修改部门对话框 -->
<el-dialog v-model="dialogFormVisible" :title="formTitle" width="30%">
<el-form :model="deptForm">
<el-form-item label="部门名称" label-width="80px">
<el-input v-model="deptForm.name" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="save">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<style scoped>
</style>
总结
课堂作业
- 根据删除的步骤,完成删除逻辑🎤
确保后端代码没有错误哦,如果前端出错了,请检查后端代码的日志信息
5.5 表单校验
表单校验
目前,我们已经基本完成了部门管理的增删改查操作。 接下来,我们对部门管理的功能进行,最后一块完善工作,增加表单校验。 从页面原型中,我们可以看到,新增部门的时候部门名称,不能为空,而且长度得在2-10之间。

5.5.1 ElementPlus 参考
Form 组件允许你验证用户的输入是否符合规范,来帮助你找到和纠正错误。Form
组件提供了表单验证的功能,只需为 rules
属性传入约定的验证规则,并将 form-Item
的 prop
属性设置为需要验证的特殊键值即可。

代码操作
实现
1). 定义表单校验规则
//定义表单校验规则
const deptFormRef = ref<FormInstance>()
const rules = ref<FormRules<DeptModel>>({
name: [
{ required: true, message: '部门名称不能为空', trigger: 'blur' },
{ min: 2, max: 10, message: '部门名称长度在2-10个字之间', trigger: 'blur' },
]
})
2). 将表单校验规则与表单绑定
为表单 <el-form>
绑定 rules
属性绑定表单校验规则 。 为每一个表单项,指定 prop
属性,设置为需要验证的属性名。

3). 表单提交时,校验表单,校验通过,则允许提交表单。
修改save方法的逻辑,需要加入表单校验的逻辑。
//点击保存按钮-发送异步请求
const save = async (form:FormInstance | undefined) => {
if(!form) return;
await form.validate(async (valid) => {
if (valid) {
let result = null;
if(deptForm.value.id){
result = await updateApi(deptForm.value)
}else {
result = await addApi(deptForm.value)
}
if(result.code){
ElMessage.success('操作成功')
}else{
ElMessage.error(result.msg)
}
dialogFormVisible.value = false
queryAll()
}
})
}
4). 重置表单校验结果
//重置表单校验结果
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.resetFields()
}
然后在点击 "新增" / "修改" 按钮的时候,调用 resetForm 函数,重置表单校验结果。

最终,部门管理的完整代码如下:
1). src/api/dept.ts
import request from "@/utils/request"
import type { DeptModel, ResultModel } from "./model/model"
//列表查询
export const queryAllApi = () => request.get<any, ResultModel>('/depts')
//添加部门
export const addApi = (dept:DeptModel) => request.post<any, ResultModel>('/depts', dept)
//根据ID查询
export const queryInfoApi = (id:number) => request.get(`/depts/${id}`)
//修改部门
export const updateApi = (dept:DeptModel) => request.put<any, ResultModel>('/depts', dept)
//删除部门
export const deleteApi = (id:number) => request.delete<any, ResultModel>(`/depts?id=${id}`)
2). src/views/dept/index.vue
<script setup lang="ts">
import {ref, onMounted} from 'vue'
import type { DeptModelArray, DeptModel } from '@/api/model/model'
import {queryAllApi, addApi, queryInfoApi, updateApi, deleteApi} from '@/api/dept'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
//声明列表展示数据
let tableData = ref<DeptModelArray>([])
//动态加载数据-查询部门
const queryAll = async () => {
const result = await queryAllApi()
tableData.value = result.data
}
//钩子函数
onMounted(() => {
queryAll()
})
//新增部门
const dialogFormVisible = ref<boolean>(false)
const deptForm = ref<DeptModel>({name: ''})
const formTitle = ref<string>('')
//点击新增按钮触发的函数
const add = () => {
formTitle.value = '新增部门'
dialogFormVisible.value = true
deptForm.value = {name: ''}
}
//点击保存按钮-发送异步请求
const save = async (form:FormInstance | undefined) => {
if(!form) return;
await form.validate(async (valid) => {
if (valid) {
let result = null;
if(deptForm.value.id){
result = await updateApi(deptForm.value)
}else {
result = await addApi(deptForm.value)
}
if(result.code){
ElMessage.success('操作成功')
}else{
ElMessage.error(result.msg)
}
dialogFormVisible.value = false
queryAll()
}
})
}
//修改部门-查询回显
const update = async (id:number) => {
formTitle.value = '修改部门'
dialogFormVisible.value = true
deptForm.value = {name: ''}
const result = await queryInfoApi(id)
deptForm.value = result.data
}
//删除部门
const deleteById =async (id:number) => {
//弹出确认框
ElMessageBox.confirm('您确认删除此部门吗? ', '确认删除').then( async () => {
let result = await deleteApi(id)
if(result.code){ //成功
ElMessage.success('删除成功')
queryAll()
}else {
ElMessage.error(result.msg)
}
}).catch(() => {
ElMessage.info('取消删除')
})
}
//定义表单校验规则
const deptFormRef = ref<FormInstance>()
const rules = ref<FormRules<DeptModel>>({
name: [
{ required: true, message: '部门名称不能为空', trigger: 'blur' },
{ min: 2, max: 10, message: '部门名称长度在2-10个字之间', trigger: 'blur' },
]
})
//重置表单校验结果
const resetForm = (form: FormInstance | undefined) => {
if (!form) return
form.resetFields()
}
</script>
<template>
<h1>部门管理</h1>
<el-button type="primary" style="float: right" @click="add(); resetForm(deptFormRef);">+ 新增</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="update(scope.row.id); resetForm(deptFormRef);">修改</el-button>
<el-button size="small" type="danger" @click="deleteById(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新增部门 / 修改部门对话框 -->
<el-dialog v-model="dialogFormVisible" :title="formTitle" width="30%">
<el-form :model="deptForm" :rules="rules" ref="deptFormRef">
<el-form-item label="部门名称" label-width="80px" prop="name">
<el-input v-model="deptForm.name" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogFormVisible = false; resetForm(deptFormRef);">取消</el-button>
<el-button type="primary" @click="save(deptFormRef)">确定</el-button>
</span>
</template>
</el-dialog>
</template>
<style scoped>
</style>