Day20-前端Web案例——部门管理

目录

  • 部门管理
  • [1. 前后端分离开发](#1. 前后端分离开发)
  • [2. 准备工作](#2. 准备工作)
  • [2.1 创建Vue项目](#2.1 创建Vue项目)
    • [2.2 安装依赖](#2.2 安装依赖)
    • [2.3 精简项目](#2.3 精简项目)
  • [3. 页面布局](#3. 页面布局)
    • [3.1 介绍](#3.1 介绍)
    • [3.2 整体布局](#3.2 整体布局)
    • [3.3 左侧菜单](#3.3 左侧菜单)
  • [4. Vue Router](#4. Vue Router)
    • [4.1 介绍](#4.1 介绍)
    • [4.2 入门](#4.2 入门)
    • [4.3 案例](#4.3 案例)
    • [4.4 首页制作](#4.4 首页制作)
  • [5. 部门管理](#5. 部门管理)
    • 5.1部门列表
      • [5.1.1. 基本布局](#5.1.1. 基本布局)
      • [5.1.2 加载数据](#5.1.2 加载数据)
      • [5.1.3 程序优化](#5.1.3 程序优化)
    • [5.2 新增部门](#5.2 新增部门)
    • [5.3 修改部门](#5.3 修改部门)
      • [5.3.1 查询回显](#5.3.1 查询回显)
      • [5.3.2 保存修改](#5.3.2 保存修改)
    • [5.4 删除部门](#5.4 删除部门)
    • [5.5 表单校验](#5.5 表单校验)
      • [5.5.1 ElementPlus 参考](#5.5.1 ElementPlus 参考)
      • [5.5.2 实现](#5.5.2 实现)

部门管理

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

在这个案例中,我们主要完成 部门管理员工管理 的功能开发。 而今天呢,我们先来完成部门管理的功能开发,而在完成部门管理的功能开发之前,先需要完成基础的准备工作。 所以今天的课程安排如下:

  • 前后端分类开发
  • 准备工作
  • 页面布局
  • Vue-Router
  • 部门管理

1. 前后端分离开发

在之前的课程中,我们介绍过,现在的企业项目开发有2种开发模式:前后台混合开发前后台分离开发

前后台混合开发,顾名思义就是前台后台代码混在一起开发。这种开发模式有如下缺点:

  • 沟通成本高:后台人员发现前端有问题,需要找前端人员修改,前端修改成功,再交给后台人员使用
  • 分工不明确:后台开发人员需要开发后台代码,也需要开发部分前端代码。很难培养专业人才
  • 不便管理:所有的代码都在一个工程中
  • 难以维护:前端代码更新,和后台无关,但是需要整个工程包括后台一起重新打包部署。

所以我们目前基本都是采用的前后台分离开发方式,如下图所示:

我们将原先的工程分为前端工程和后端工程这2个工程,然后前端工程交给专业的前端人员开发,后端工程交给专业的后端人员开发。

前端页面需要数据,可以通过发送异步请求,从后台工程获取。但是,我们前后台是分开来开发的,那么前端人员怎么知道后台返回数据的格式呢?后端人员开发,怎么知道前端人员需要的数据格式呢?

所以针对这个问题,我们前后台统一制定一套规范!我们前后台开发人员都需要遵循这套规范开发,这就是我们的接口文档

那么接口文档的内容怎么来的呢?是我们后台开发者根据产品经理提供的产品原型和需求文档所撰写出来的。

那么基于前后台分离开发的模式下,我们后台开发者开发一个功能的具体流程如何呢?如下图所示:

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

2. 准备工作

2.1 创建Vue项目

在自己工作目录下,运行 cmd 打开命令行,运行如下指令,来创建vue项目【

shell 复制代码
npm init vue@latest

2.2 安装依赖

1). 在命令行中执行如下命令,为创建好的Vue项目安装 ElementPlus、Axios 的依赖。

shell 复制代码
npm install element-plus --save
npm install axios

2). 为创建好的 Vue项目 配置ElementPlus (参照官网),在 main.ts 中引入如下配置信息 【注意:是追加如下内容】:

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 文件内容如下:

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的语言包(可不做)

ts 复制代码
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> 标签中。

html 复制代码
<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>

然后,我们先根据页面原型中的布局显示进行调整。 先完成顶栏部分的制作,具体的代码如下:

html 复制代码
<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> 修改密码 &nbsp;&nbsp;&nbsp;&nbsp;</a>
          <a href=""><el-icon><SwitchButton /></el-icon> 退出登录&nbsp;&nbsp;&nbsp;&nbsp;</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>,然后根据我们案例的需要进行改造,改造成我们需要的样子即可。

最终左侧菜单栏的代码如下:

html 复制代码
<!-- 左侧菜单 -->
<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> 中添加如下样式:

css 复制代码
.aside {
  border: 1px solid #ccc;
  height: 690px;
  width: 220px;
}

最终,浏览器打开的效果如下:

到目前为止,layout/index.vue 中的内容如下:

html 复制代码
<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> 修改密码 &nbsp;&nbsp;&nbsp;&nbsp;</a>
          <a href=""><el-icon><SwitchButton /></el-icon> 退出登录&nbsp;&nbsp;&nbsp;&nbsp;</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项目时,可以选择)

shell 复制代码
npm install vue-router@4

2). 在 main.ts 入口文件中进行配置,加入如下配置

ts 复制代码
import router from './router'

//..... 创建完vue的应用实例后,调用app.use
app.use(router)

3). 在 src/views 目录下再定义一个文件夹,在文件夹中再创建一个 vue 组件文件

4). 定义路由

src/router/index.ts 中定义路由表信息,在其中主要是定义请求路径与组件之间的对应关系。 完整的文件内容如下:

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> 标签

该标签将用于显示,访问的请求路径对应的组件。

html 复制代码
<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). 准备案例的空页面

html 复制代码
<script setup lang="ts">

</script>

<template>
  班级管理|学生管理|员工管理|部门管理|首页展示
</template>

<style scoped>

</style>

2). 在 src/router/index.ts 中配置路由信息

这里我们用到了Vue中的嵌套路由,具体定义方式,主要是在配置路由信息时,通过children 来描述。如你所见,children 配置只是另一个路由数组,就像 routes 本身一样。因此,你可以根据自己的需要,不断地嵌套视图。

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'),
      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> 。如下:

html 复制代码
<!-- 主展示区域 -->
<el-main>
	<RouterView></RouterView>
</el-main>

最终完整的 layout/index.vue 代码如下:

html 复制代码
<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> 修改密码 &nbsp;&nbsp;&nbsp;&nbsp;</a>
          <a href=""><el-icon><SwitchButton /></el-icon> 退出登录&nbsp;&nbsp;&nbsp;&nbsp;</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 中引入一张图片即可,具体代码如下:

html 复制代码
<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 具体的页面布局代码如下:

html 复制代码
<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。

html 复制代码
<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 代码如下:

html 复制代码
<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进行了封装。 具体代码如下:

ts 复制代码
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

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导入进来,就可以直接使用了。

html 复制代码
<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路径重写的规则。

ts 复制代码
  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 中完成页面布局,并编写交互逻辑,完成数据绑定。

完整代码如下:

html 复制代码
<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 中增加如下代码

ts 复制代码
//添加部门
export const addApi = (dept:DeptModel) => request.post<any, ResultModel>('/depts', dept)

目前 src/api/dept.ts 文件中完整代码如下:

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 修改部门

对于修改操作,通常会分为两步进行:

  1. 查询回显
  2. 保存修改

交互逻辑:

  1. 点击 编辑 按钮,根据ID进行查询,弹出对话框,完成页面回显展示。(查询回显)
  2. 点击 确定 按钮,保存修改后的数据,完成数据更新操作。(保存修改)

5.3.1 查询回显

1). 在 src/api/dept.ts 中定义根据id查询的请求

ts 复制代码
//根据ID查询
export const queryInfoApi = (id:number) => request.get(`/depts/${id}`)

2). 在 src/views/dept/index.vue 中添加根据ID查询回显的逻辑

为修改按钮绑定事件 <template></template>:

html 复制代码
<el-button size="small" type="primary" @click="update(scope.row.id)">修改</el-button>

<script> </script> 添加JS逻辑:

ts 复制代码
//修改部门-查询回显
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 代码如下:

html 复制代码
<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>

5.3.2 保存修改

由于 新增部门 和 修改部门使用的是同一个Dialog对话框,当前点击 "确定" 按钮的时候,有可能执行的是新增操作,也有可能是修改操作。

那应该如何辨别到底是新增,还是修改操作呢 ?

其实,我们只需要根据 deptForm 对象的id属性值,来判断即可。 如果没有id,则是新增操作 ;如果有id,则是修改操作。

所以,保存修改功能实现如下:

1). 在 src/api/dept.ts 中增加如下修改部门的请求

ts 复制代码
//修改部门
export const updateApi = (dept:DeptModel) => request.put<any, ResultModel>('/depts', dept)

2). 在 src/views/dept/index.vue 中完善(修改) save 函数的逻辑

ts 复制代码
//点击保存按钮-发送异步请求
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 中增加如下删除部门的请求

ts 复制代码
//删除部门
export const deleteApi = (id:number) => request.delete<any, ResultModel>(`/depts?id=${id}`)

2). 在 src/views/dept/index.vue 中为什么 删除 按钮绑定事件

html 复制代码
<el-button size="small" type="danger"  @click="deleteById(scope.row.id)">删除</el-button>

3). 在 src/views/dept/index.vue 编写根据ID删除数据的函数

ts 复制代码
//删除部门
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-Itemprop 属性设置为需要验证的特殊键值即可。

5.5.2 实现

1). 定义表单校验规则

ts 复制代码
//定义表单校验规则
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方法的逻辑,需要加入表单校验的逻辑。

ts 复制代码
//点击保存按钮-发送异步请求
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). 重置表单校验结果

ts 复制代码
//重置表单校验结果
const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.resetFields()
}

然后在点击 "新增" / "修改" 按钮的时候,调用 resetForm 函数,重置表单校验结果。

最终,部门管理的完整代码如下:

1). src/api/dept.ts

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

html 复制代码
<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>
相关推荐
小吕学编程14 分钟前
ES练习册
java·前端·elasticsearch
Asthenia041221 分钟前
Netty编解码器详解与实战
前端
袁煦丞26 分钟前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
一个专注写代码的程序媛1 小时前
vue组件间通信
前端·javascript·vue.js
一笑code2 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员2 小时前
layui时间范围
前端·javascript·layui
NoneCoder2 小时前
HTML响应式网页设计与跨平台适配
前端·html
凯哥19702 小时前
在 Uni-app 做的后台中使用 Howler.js 实现强大的音频播放功能
前端
烛阴2 小时前
面试必考!一招教你区分JavaScript静态函数和普通函数,快收藏!
前端·javascript
GetcharZp2 小时前
xterm.js 终端神器到底有多强?用了才知道!
前端·后端·go