Day20-前端Web案例——部门管理
目录
- 部门管理
- 1. 前后端分离开发
- 2. 准备工作
- 2.1 创建Vue项目
- 2.2 安装依赖
- 2.3 精简项目
- 3. 页面布局
- 3.1 介绍
- 3.2 整体布局
- 3.3 左侧菜单
- 4. Vue Router
- 4.1 介绍
- 4.2 入门
- 4.3 案例
- 4.4 首页制作
- 5. 部门管理
- 5.1部门列表
- 5.1.1. 基本布局
- 5.1.2 加载数据
- 5.1.3 程序优化
- 5.2 新增部门
- 5.3 修改部门
- 5.3.1 查询回显
- 5.3.2 保存修改
- 5.4 删除部门
- 5.5 表单校验
- 5.5.1 ElementPlus 参考
- 5.5.2 实现
部门管理
在前面的课程中,我们学习了Vue工程化的基础内容、TS、ElementPlus,那接下来呢,我们要通过一个案例,加强大家对于Vue项目的理解,并掌握Vue项目的开发。 这个案例呢,就是我们之前所做的Tlias智能学习辅助系统。

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

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

- 需求分析:首先我们需要阅读需求文档,分析需求,理解需求。
- 接口定义:查询接口文档中关于需求的接口的定义,包括地址,参数,响应数据类型等等
- 前后台并行开发:各自按照接口文档进行开发,实现需求
- 测试:前后台开发完了,各自按照接口文档进行测试
- 前后段联调测试:前段工程请求后端工程,测试功能
2. 准备工作
2.1 创建Vue项目
在自己工作目录下,运行 cmd 打开命令行,运行如下指令,来创建vue项目【
npm init vue@latest

2.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/es/locale/lang/zh-cn'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'import App from './App.vue'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import './assets/main.css'import zhCn from 'element-plus/es/locale/lang/zh-cn'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'const app = createApp(App)for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)
}
app.use(createPinia())
app.use(router)
app.use(ElementPlus, {locale: zhCn})app.mount('#app')
3). 在 env.d.ts 中引入ElementPlus的语言包(可不做)
declare module 'element-plus/es/locale/lang/zh-cn'
2.3 精简项目
由于基于Vue脚手架创建的项目里面携带了很多的多余的Vue组件。 并准备对应的组件存放目录 。
- 删除
components目录中的vue文件 - 删除
views目录中的vue文件 - 清空根组件文件
App.vue中的内容,只保留基础的vue组件文件的结构标签<script><template><style>

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

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

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

提示:当
<el-container>子元素中包含<el-header>或<el-footer>时,全部子元素会垂直上下排列, 否则会水平左右排列。
3.2 整体布局
我们可以参照 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">Tlias智能学习辅助系统</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;
}
最终,浏览器打开的效果如下:

到目前为止,layout/index.vue 中的内容如下:
<script setup lang="ts"></script><template><div class="common-layout"><el-container><!-- 顶栏 - header --><el-header class="header"><span class="title">Tlias智能学习辅助系统</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>:动态视图组件,用来渲染展示与路由路径对应的组件
4.2 入门
介绍完了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 中定义的页面内容):

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

4.3 案例
那接下来,我们就要基于 VueRouter 来完成点击 左侧菜单,动态切换主展示区域内容的动态效果。
1). 准备案例的空页面

<script setup lang="ts"></script><template>班级管理|学生管理|员工管理|部门管理|首页展示
</template><style scoped></style>
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">Tlias智能学习辅助系统</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). 测试


4.4 首页制作
其实首页,我们只需要展示一张图片即可。 直接在 index/index.vue 中引入一张图片即可,具体代码如下:
<script setup lang="ts"></script><template><img src="@/assets/index.png">
</template><style scoped></style>
最终效果如下:

5. 部门管理
部门管理的页面内容,写在 src/views/dept/index.vue 中。
5.1部门列表
5.1.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 都是根据接口文档来的,接口文档返回什么样的数据,我们就安装对应的数据格式进行解析。
5.1.2 加载数据
根据需求,需要在新增、修改、删除部门之后,加载最新的部门数据。 在打开页面之后,也需要自动加载部门数据。 那接下来,我们就需要基于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 文件,然后挨个修改。 所以上述的代码,虽然实现了动态加载数据的功能。 但是存在以下问题:
- 请求路径难以维护
- 数据解析繁琐
5.1.3 程序优化
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程序全部注释掉)。

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 = truedeptForm.value = {name: ''}
}//点击保存按钮-发送异步请求
const save = async () => {const result = await addApi(deptForm.value)if(result.code){ElMessage.success('操作成功')}else{ElMessage.error(result.msg)}dialogFormVisible.value = falsequeryAll()
}</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进行查询,弹出对话框,完成页面回显展示。(查询回显)
- 点击 确定 按钮,保存修改后的数据,完成数据更新操作。(保存修改)
5.3.1 查询回显
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 = truedeptForm.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 = truedeptForm.value = {name: ''}
}//点击保存按钮-发送异步请求
const save = async () => {const result = await addApi(deptForm.value)if(result.code){ElMessage.success('操作成功')}else{ElMessage.error(result.msg)}dialogFormVisible.value = falsequeryAll()
}//修改部门-查询回显
const update = async (id:number) => {formTitle.value = '修改部门'dialogFormVisible.value = truedeptForm.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>
5.3.2 保存修改
由于 新增部门 和 修改部门使用的是同一个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 = falsequeryAll()
}
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('取消删除')})
}
打开浏览器做一个测试:


5.5 表单校验
目前,我们已经基本完成了部门管理的增删改查操作。 接下来,我们对部门管理的功能进行,最后一块完善工作,增加表单校验。 从页面原型中,我们可以看到,新增部门的时候部门名称,不能为空,而且长度得在2-10之间。

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

5.5.2 实现
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 = falsequeryAll()}})
}
4). 重置表单校验结果
//重置表单校验结果
const resetForm = (formEl: FormInstance | undefined) => {if (!formEl) returnformEl.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 = truedeptForm.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 = falsequeryAll()}})
}//修改部门-查询回显
const update = async (id:number) => {formTitle.value = '修改部门'dialogFormVisible.value = truedeptForm.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) returnform.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>
相关文章:
Day20-前端Web案例——部门管理
目录 部门管理1. 前后端分离开发2. 准备工作2.1 创建Vue项目2.2 安装依赖2.3 精简项目 3. 页面布局3.1 介绍3.2 整体布局3.3 左侧菜单 4. Vue Router4.1 介绍4.2 入门4.3 案例4.4 首页制作 5. 部门管理5.1部门列表5.1.1. 基本布局5.1.2 加载数据5.1.3 程序优化 5.2 新增部门5.3…...
从切图仔到鸿蒙开发01-文本样式
从切图仔到鸿蒙开发01-文本样式 本系列教程适合 HarmonyOS 初学者,为那些熟悉用 HTML 与 CSS 语法的 Web 前端开发者准备的。 本系列教程会将 HTML/CSS 代码片段替换为等价的 HarmonyOS/ArkUI 代码。 页面结构 HTML 与 ArkUI 在 Web 开发中,HTML 文档结…...
菱形虚拟继承的原理
一 :菱形继承的问题 普通的菱形继承存在数据冗余和二义性的问题 ,如下代码: class Person { public:string _name; //姓名 };class Student : public Person { protected:int _num; //学号 };class Teacher : public Person { protected:int…...
【数据结构】C语言实现树和森林的遍历
C语言实现树和森林的遍历 导读一、树的遍历二、森林的遍历2.1 为什么森林没有后序遍历?2.2 森林中存不存在层序遍历?三、C语言实现3.1 准备工作3.2 数据结构的选择3.3 树与森林的创建3.4 树与森林的遍历3.4.1 先根遍历3.4.2 后根遍历3.4.3 森林的遍历3.5 树与森林的销毁3.6 算…...
第四天 开始Unity Shader的学习之旅之Unity中的基础光照
Unity Shader的学习笔记 第四天 开始Unity Shader的学习之旅之Unity中的基础光照 文章目录 Unity Shader的学习笔记前言一、我们是如何看到这个世界的1. 光源2.吸收和散射3.着色 二、标准光照模型1. 自发光2. 高光反射① Phong模型② Blinn-Phong模型 3.漫反射4.环境光 总结 前…...
基于SpringBoot的“社区居民诊疗健康管理系统”的设计与实现(源码+数据库+文档+PPT)
基于SpringBoot的“社区居民诊疗健康管理系统”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统模块功能结构图 局部E-R图 系统首…...
React Native集成到现有原生Android应用
使用React Native(以下简称RN)从头开始制作一个新的应用会是一个非常好的选择。但如果只想给现有的原生应用中添加一两个视图或是业务流程,RN也同样不在话下。只需简单几步,就可以给原有应用加上新的基于RN的特性、画面和视图等。 一、核心概念 把 React Native 组件集成…...
Java-空链基础入门
经过调研和细致观察,我们发现空链对于初次接触或是对Stream和Optional不太熟悉的人来说,确实存在一定的上手难度,宛如开启了“地狱模式”。为了降低这一门槛,我们决定通过一系列由简入深的案例演示,来逐步引导大家掌握…...
【江协科技STM32】Unix时间戳BKP备份寄存器RTC实时时钟(学习笔记)
Unix时间戳 Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量世界上所有时区的秒计数器相同,不同时区通过…...
PCDN网络设备
PCDN(Peer-to-Peer Content Delivery Network,点对点内容分发网络)是一种基于P2P技术的内容分发网络。它利用用户终端设备之间的直接数据传输来减少中心服务器的压力,并提高内容交付的速度和效率。 PCDN的工作原理 节点共享&…...
3.17-3.23 Web3 游戏周报:Pixudi 双榜领跑,The Forgotten Runiverse 登陆三大主机平台
回顾上周的区块链游戏概况,查看 Footprint Analytics 与 ABGA 最新发布的数据报告。 【3.17–3.23】Web3 游戏行业动态 Ronin 将与 Alpha Growth 等合作推出 1300 万美元增长计划,以向 DeFi 扩张Notcoin 开发工作室 Open Builders 宣布推出 Not Games …...
AppInventor2生成3位数的水仙花数
生成3位水仙花数(每位数字的立方之和刚好等于这个数字)的代码,如下: 来源:【生成Python】AppInventor2中文网已支持代码块转换Python源码! - App Inventor 2 中文网 - 清泛IT社区,为创新赋能&…...
【聚类算法解析系列02】经典聚类算法(上)——K-Means与层次聚类
【聚类算法解析系列02】经典聚类算法(上)——K-Means与层次聚类 引言:算法背后的认知革命 K-Means与层次聚类,这两个诞生于1960年代的算法,至今仍是工业界使用率最高的聚类工具。它们分别代表了两种根本性的世界观&am…...
shadcn如何给dialog增加关闭按钮和隐藏右上角关闭按钮
增加关闭按钮: <DialogFooter><DialogClose asChild><button className"rounded-sm bg-black/100 px-3 py-2 text-xs font-semibold text-white shadow-xs hover:bg-black/90" >Close</button></DialogClose> </DialogF…...
华为机试牛客刷题之HJ59 找出字符串中第一个只出现一次的字符
HJ59 找出字符串中第一个只出现一次的字符 描述 对于给定的字符串,找出第一个只出现一次的字符。如果不存在,则输出 −1。 输入描述: 在一行上输入一个长度为 1≦len(s)≦10^3 、仅由小写字母构成的字符串 s。 输出描述: 如果存…...
C# 中实现一个线程持续读取,另一个线程负责写入,且写入时读取线程暂停
实现思路 暂停信号:通过 ManualResetEventSlim 通知读取线程暂停。 暂停确认:读取线程收到暂停信号后,发送确认信号。 原子性控制:确保写入操作执行期间,读取线程处于完全暂停状态。 恢复机制:写入完成后…...
[Effective C++]条款22:将成员变量声明为private
. 在C中,将成员变量声明为private而不是public,主要是为了遵循面向对象编程(OOP)的封装原则。他有助于隐藏对象的内部实现细节,提供更好地控制,安全性和可维护性。 1、数据隐藏与封装 将成员变量声明为pr…...
心法利器[132] | 大模型系统性能优化trick
心法利器 本栏目主要和大家一起讨论近期自己学习的心得和体会。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有。 2023年新的文章合集已经发布,获取方式看这里:又添十万字-CS的陋室2023年文章合集来袭,更…...
Android第六次面试总结(Java设计模式篇一)
单例模式属于创建型设计模式,它保证一个类仅有一个实例,并且提供一个全局访问点来获取该实例。下面为你详细阐述单例模式的好处和坏处。 好处 资源优化:单例模式能保证一个类只有一个实例,这对于那些创建和销毁开销大的对象&…...
stm第九天433M无线遥控灯
1.433M无线模块工作原理 数据发射模块的工作频率为315M,采用声表谐振器SAW稳频,频率稳定度极高,当环境温度在-25~85度之间变化时,频飘仅为3ppm.接收到信号,接收模块对应针脚输出高电平,有DO D1 D2 D3&#…...
六十天前端强化训练之第三十天之深入解析Vue3电商项目:TechStore全栈实践(文结尾附有源代码)
欢迎来到编程星辰海的博客讲解 看完可以给一个免费的三连吗,谢谢大佬! 目录 深入解析Vue3电商项目:TechStore全栈实践 一、项目架构设计 二、核心功能实现 三、组合式API深度实践 四、性能优化实践 五、项目扩展方向 六、开发经验总结…...
类与对象(中)(详解)
【本节目标】 1. 类的6个默认成员函数 2. 构造函数 3. 析构函数 4. 拷贝构造函数 5. 赋值运算符重载 6. const成员函数 7. 取地址及const取地址操作符重载 1.类的6个默认成员函数 如果一个类中什么成员都没有,简称为空类。 空类中真的什么都没有吗&…...
Spring Boot框架中常用注解
以下是Spring Boot框架中常用注解的详细说明,包括名称、用途、用法、使用位置及扩展示例,按功能模块分类整理: 一、核心启动与配置注解 1. SpringBootApplication 用途:主启动类注解,整合了 Configuration、EnableAu…...
ResNet与注意力机制:深度学习中的强强联合
引言 在深度学习领域,卷积神经网络(CNN)一直是图像处理任务的主流架构。然而,随着网络深度的增加,梯度消失和梯度爆炸问题逐渐显现,限制了网络的性能。为了解决这一问题,ResNet(残差…...
notify_one() 会阻塞吗?
notify_one() 不会阻塞。它是用于唤醒一个等待中的线程,通常是通过条件变量(std::condition_variable)来使用的。调用 notify_one() 会使一个处于等待状态的线程被唤醒并继续执行,但它本身并不会阻塞。 当调用 notify_one() 时&a…...
Flutter项目之页面实现以及路由fluro
目录: 1、项目代码结构2、页面编写以及路由配置main.dart(入口文件)page_content.dartindex.dartapplication.dartpubspec.yamllogin.dartdio_http.dart 3、Fluro路由routes.dartnot_found_page.dart(路由优化,找不到页面时展示此页面) 4、注册页面 1、项…...
《Python实战进阶》第31集:特征工程:特征选择与降维技术
第31集:特征工程:特征选择与降维技术 摘要 特征工程是机器学习和数据科学中不可或缺的一环,其核心目标是通过选择重要特征和降低维度来提升模型性能并减少计算复杂度。本集聚焦于特征选择与降维技术,涵盖过滤法、包裹法、嵌入法等…...
大模型在支气管哮喘手术全流程风险预测与治疗方案制定中的应用研究
目录 一、引言 1.1 研究背景与意义 1.2 研究目标与方法 1.3 研究创新点 二、支气管哮喘概述 2.1 定义与发病机制 2.2 分类与临床表现 2.3 诊断标准与方法 三、大模型技术原理与应用现状 3.1 大模型的基本原理 3.2 在医疗领域的应用案例分析 3.3 适用于支气管哮喘预…...
C++类与对象的第二个简单的实战练习-3.24笔记
哔哩哔哩C面向对象高级语言程序设计教程(118集全) 实战二 Cube.h #pragma once class Cube { private:double length;double width;double height; public:double area(void);double Volume(void);//bool judgement(double L1, double W1, double H1);…...
react自定义hook
自定义hook: 用来封装复用的逻辑,,自定义hook是以use开头的普通函数,,将组件中可复用的状态逻辑抽取到自定义的hook中,简化组件代码 常见自定义hook例子: 封装一个简单的计数器 import {useS…...
