谷粒学院day5 讲师管理模块的前端实现

2022-10-26 17:22:19 浏览数 (2)

day5 讲师管理模块的前端实现

目 录
  • day5 讲师管理模块的前端实现
    • 1.登录功能改造
    • 2.跨域问题
    • 3.前端框架开发过程
    • 4.讲师列表的前端实现
    • 5.讲师删除功能的前端实现
    • 6.讲师添加
    • 7.讲师修改
    • 8.路由切换的问题及解决

1.登录功能改造

前端页面登录的url经常会挂掉,要改为本地地址。启动前端的demo项目。浏览器右键选择inspect打开调试界面,切到Network,点击Login,具体操作参考下图。

可以看到前端访问的url。我们把它改造成后端的8001端口访问的页面。

打开src/api/login.js。

代码语言:javascript复制
import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/vue-admin-template/user/login',
    method: 'post',
    data
  })
}

这里返回的request是导入的/utils/request。点进去看看。原来是axios的Ajax请求。里面的baseURL是什么?

代码语言:javascript复制
// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api 的 base_url
  timeout: 5000 // 请求超时时间
})

config/dev.env.js中配置了baseURL。

代码语言:javascript复制
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
})

将其改为本地8001.

代码语言:javascript复制
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  BASE_API: '"http://localhost:8001"',
})

注意到前端的login.js中有两个方法,login()方法和info()方法,分别用于登录和获取登录信息,因此后端也需要开放两个接口。在src/store/modules下的user.js可以看到用户的信息。包括name,roleavatar,token,前三个参数需要后端info()提供,tokenlogin()提供。

controller目录下新建LoginContreller.java.启动后端的项目。

代码语言:javascript复制
@RestController
@RequestMapping("eduservice/user")
public class LoginController {

    @PostMapping("login")
    public R login() {
        return R.ok().data("token","admin");
    }

    @PostMapping("info")
    public R getInfo() {
        return R.ok().data("roles","admin").data("name","可爱粥").data("avatar","http://img.desktx.com/d/file/wallpaper/scenery/20170303/dfe53a7300794009a029131a062836d5.jpg");
    }
}

将前端login.js中的接口改成与后端一致。

代码语言:javascript复制
export function login(username, password) {
  return request({
    url: 'eduservice/user/login',
    method: 'post',
    data: {
      username,
      password
    }
  })
}

export function getInfo(token) {
  return request({
    url: 'eduservice/user/info',
    method: 'get',
    params: { token }
  })
}
2.跨域问题

重新启动前端(改了配置文件的情况必须重启)。

这里报了一个Network Error,在console中查看具体的错误信息如下。

这个错误有个专业的术语:跨域问题。所谓跨域,是指通过一个地址去访问另一个地址,协议、ip、端口号不完全相同,则访问为跨域访问。本项目中前端地址为:http://localhost:9528/, 访问后端地址:http://localhost:8001/,这就是一个跨域问题。

在后端接口中添加@CrossOrigin注解允许跨域访问即可解决,也可以使用网关解决(后面讲解)。

好了,重启后台刷新页面。前方高帅。

3.前端框架开发过程

在生产环境中,开发一个新功能,先看以前的模块怎么开发,然后进行模仿。在这个项目中我们也采取这样的方式。

前面已经提过,前端框架的入口文件是index.html与main.js。在main.js中有一行如下。

代码语言:javascript复制
import router from './router'

查看router目录,里面的index.html文件就实现了路由功能。对应前端页面查看代码可以很容易理清代码功能对应的逻辑。对该页面进行改造。从这里作为路口阅读,可以大致了解下前端框架代码的开发过程,后面小节中具体进行实现。

4.讲师列表的前端实现

(1)添加前端路由

代码语言:javascript复制
  {
    path: '/teacher',
    component: Layout,
    redirect: '/teacher/table', //在页面中访问'/teacher'会被重定向到'/teacher/table'
    name: '讲师管理',
    meta: { title: '讲师管理', icon: 'example' },
    children: [
      {
        path: 'table',
        name: '讲师列表',
        component: () => import('@/views/table/index'),
        meta: { title: '讲师列表', icon: 'table' }
      },
      {
        path: 'save',
        name: '添加讲师',
        component: () => import('@/views/tree/index'),
        meta: { title: '添加讲师', icon: 'tree' }
      }
    ]
  },

效果很帅。

(2)替换跳转页面

component替换成讲师列表与添加讲师的页面,如下所示。

代码语言:javascript复制
  {
    path: '/teacher',
    component: Layout,
    redirect: '/teacher/table', //在页面中访问'/teacher'会被重定向到'/teacher/table'
    name: '讲师管理',
    meta: { title: '讲师管理', icon: 'example' },
    children: [
      {
        path: 'table',
        name: '讲师列表',
        component: () => import('@/views/edu/teacher/list'),
        meta: { title: '讲师列表', icon: 'table' }
      },
      {
        path: 'save',
        name: '添加讲师',
        component: () => import('@/views/edu/teacher/save'),
        meta: { title: '添加讲师', icon: 'tree' }
      }
    ]
  },

如下图建立目录与跳转文件。

list.vue

代码语言:javascript复制
<template>
 <div class="app-container">
  讲师列表
  </div>

</template>

save.vue

代码语言:javascript复制
<template>
 <div class="app-container">
  添加讲师
  </div>

</template>

(3)定义前端接口

src/api下新建目录edu/teacher.js.模仿login.js,对照后端接口eduserviceController很容易完成前端接口的改造。

代码语言:javascript复制
import request from '@/utils/request' 

// 讲师列表:多条件查询(带分页)
export function pageTeacherCondition(page, limit, teacherQuery) {
  return request({
    //    url: "eduservice/edu-teacher/pageTeacherCondition/"   page   "/"  limit,
    url: `eduservice/edu-teacher/pageTeacherCondition/${page}/${limit}`,
    method: 'post',
    // 包装成为json类型的数据传值
    data: teacherQuery
    
  })
}

(4)调用接口

在list.vue中调用前端实现的接口,这样就可以在讲师列表的跳转页面获得后端传过来的数据。

代码语言:javascript复制
<template>
 <div class="app-container">
  讲师列表
  </div>
</template>

<script>
// 调用teacher.js
import {teacher} from '@/api/edu/teacher'
export default {
  data() { // 定义变量及初始化数据
    return {
      list: null, // 查询返回的记录集
      page: 1, //当前页
      limit: 10,
      teacherQuery: {},
      total: 0 //总记录数
    }
  },
  created() {
    this.getList()
  },
  methods: {
    getList() {
      teacher.pageTeacherCondition(this.page, this.limit, this.teacherQuery)
      .then(
        response => {
          console.log(response)
      })
      .catch(
        error => {
          console.log(error)
      })
    }
  }
}
</script>

在展示数据前记得在后端接口添加注解@CrossOrigin解决跨域问题,看下结果。

response中的数据取出来具体展示下。

代码语言:javascript复制
this.total = response.data.total
this.list =  response.data.records
console.log(this.total)
console.log(this.list)

注意一定要使用this.xx,否则会报下面的错哒。

代码语言:javascript复制
ReferenceError:  xx is not defined

(5)展示获取数据

使用element-ui的表格组件进行修改。这里将代码复制到list.vue中替换到template即可。

代码语言:javascript复制
.<template>
  <div>
    <el-table
      :data="list"
      style="width: 100%"
      border
      fit
      highlight-current-row
      element-loading-text="数据加载中"
      v-loading="listLoading"
    >
      <el-table-column prop="number" label="序号" width="70" align="center">
        <template slot-scope="scope">
          {{ (page - 1) * limit   scope.$index   1 }}
        </template>
      </el-table-column>
      <el-table-column prop="name" label="名称" width="80"> </el-table-column>
      <el-table-column label="头衔" width="80">
        <template slot-scope="scope">
          {{ scope.row.level === 1 ? "高级讲师" : "首席讲师" }}
        </template>
      </el-table-column>
      <el-table-column prop="intro" label="资历" />
      <el-table-column prop="gmtCreate" label="添加时间" width="160" />
      <el-table-column prop="sort" label="排序" width="60" />
      <el-table-column label="操作" width="200" align="center">
        <template slot-scope="scope">
          <router-link :to="'/teacher/edit/'   scope.row.id">
            <el-button type="primary" size="mini" icon="el-icon-edit"
              >修改</el-button
            >
          </router-link>
          <el-button
            type="danger"
            size="mini"
            icon="el-icon-delete"
            @click="removeDataById(scope.row.id)"
            >删除</el-button
          >
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

看下效果吧。

这段代码究竟是什么意思呢?

注意到第四行:data="list",这代表遍历我们data中的list,看看script代码,这个list原来就是我们拿到的数据结果集。

代码语言:javascript复制
this.list =  response.data.records

下面的代码是生成序号,显然:(page - 1) * limit scope.$index 1就是记录的序号计算公式。

代码语言:javascript复制
<el-table-column prop="number" label="序号" width="70" align="center">
   <template slot-scope="scope">
      {{ (page - 1) * limit   scope.$index   1 }}
   </template>
</el-table-column>

至于其他数据可以对照这后端传过来的数据进行处理与命名。注意到下面代码对于后端传的数据进行了一层包装,使用===将比较两个数据的值、与类型,而==在js中只判断值。比如’1’与1的值相等,类型不同。

代码语言:javascript复制
 <el-table-column label="头衔" width="80">
        <template slot-scope="scope">
          {{ scope.row.level === 1 ? "高级讲师" : "首席讲师" }}
        </template>
</el-table-column>

(6)实现分页

上面成功展示了数据,但是还没有实现分页,下面来实现分页的过程。 有了element-ui,分页简直是太简单了。直接复用它的分页代码即可。在</el-table>组件后面添加分页组件如下。

代码语言:javascript复制
<!--分页组件-->
<el-pagination
               background
               layout="prev, pager, next,total,jumper"
               :total="total"
               :page-size="limit"
               style="padding: 30px 0; text-align: center"
               :current-page="page"
               @current-change="getList"
               >
</el-pagination>

@current-change就是v-on:current-change,将页面切换功能与getList方法绑定起来,这样点击上一页、下一页是可以获得更新的数据。

如果想要根据页数获得数据,还要将getList()方法也要进行下改造,下面代码将传入page进行查询,page默认值为1.

代码语言:javascript复制
 getList(page = 1) {
      this.page = page
      ...
 }

(7)条件查询

在element-ui中也可以找到对应的组件。条件查询的组件放在el-table上。

代码语言:javascript复制
    <!--多条件查询表单-->
    <el-form
      :inline="true"
      class="demo-form-inline"
      style="margin-left: 20px; margin-top: 12px;"
    >
      <el-form-item label="名称">
        <el-input
          v-model="teacherQuery.name"
          placeholder="请输入名称"
        ></el-input>
      </el-form-item>
      <el-form-item label="级别">
        <el-select v-model="teacherQuery.level" placeholder="讲师头衔">
          <el-option label="高级讲师" :value="1"></el-option>
          <el-option label="特级讲师" :value="2"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="添加时间">
        <el-time-picker
          placeholder="选择开始时间"
          v-model="teacherQuery.begin"
          value-format="yyyy-MM-dd HH:mm:ss"
          default-time="00:00:00"
          type="datetime"
        ></el-time-picker>
      </el-form-item>
      <el-form-item>
        <el-time-picker
          placeholder="选择截止时间"
          v-model="teacherQuery.end"
          value-format="yyyy-MM-dd HH:mm:ss"
          default-time="00:00:00"
          type="datetime"
        ></el-time-picker>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" @click="getList()"
          >查询</el-button
        >
        <el-button type="default" @click="resetData()">清空</el-button>
      </el-form-item>
    </el-form>

inline表示数据在一行显示。v-model是双向绑定数据。

来吧,展示。

那么怎么清空搜索的条件呢?这个业务其实有两件事要做:清空搜素框、查询全部数据。

代码语言:javascript复制
  // 清空搜索条件
  resetData() {
    // 清空搜索框
   this.teacherQuery = {}
   // 显示全部表单数据
   this.getList()
  }
5.讲师删除功能的前端实现

(1) ui实现

ui上的我们之前已经实现了,现在拿出来瞅一眼。其实就是写个button,绑定一个事件removeDataById,根据id获得这一行的数据.

代码语言:javascript复制
<el-button type="danger"
           size="mini"
           icon="el-icon-delete"
           @click="removeDataById(scope.row.id)">
           删除
</el-button>

scipt中添加这个方法。

代码语言:javascript复制
 // 删除
  removeDataById(id) {
    
  }

(2)接口定义

teacher.js中开放前端的接口。

代码语言:javascript复制
  removeTeacher(id) {
    return request({
      url: `/eduservice/edu-teacher/removeTeacher/${id}`,
      method: 'delete'
    })
  }

(3)调用接口

之前已经把teacher.js依赖引入了,现在直接进行调用就可以了。

代码语言:javascript复制
  // 删除
  removeDataById(id) {
    teacher.removeTeacher(id)
    .then(
      response => {
        // 提示删除成功

        // 回到列表页面
        this.getList()
      })
    .catch(
      error => {
        
      })
  }

不过,您现在试下效果,就会发现点下删除数据就没了,嗖的一下很快啊,那要是不小心误点了咋办。完善下。

代码语言:javascript复制
 removeDataById(id) {
        this.$confirm('此操作将永久删除该讲师, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
        .then(() => {
          teacher.removeTeacher(id)
          .then(() => { this.getList()})
          this.$message({
            type: 'success',
            message: '删除成功!'
          }) 
        })
        .catch(() => {
          this.$message({
            type: 'info',
            message: '已取消删除'
          })         
        })
 }
6.讲师添加

(1)teacher.js实现接口

代码语言:javascript复制
 addTeacher(teacher) {
      return request({
        url: `/eduservice/edu-teacher/addTeacher`,
        method: 'post',
        data: teacher
      })
  }

(2)save.vue调用接口展示数据

代码语言:javascript复制
<template>
    <div class="app-container">
        <el-form label-width="120px">
            <el-form-item label="讲师名称">
                <el-input v-model="teacher.name" />
            </el-form-item>
            <el-form-item label="讲师排序">
                <el-input-number
                                 v-model="teacher.sort"
                                 controls-position="right"
                                 min="0"
                                 />
            </el-form-item>
            <el-form-item label="讲师头衔">
                <el-select v-model="teacher.level" clearable placeholder="选择讲师头衔">
                    <!--
                      数据类型一定要和取出的json中的一致,否则没法回填
                      因此,这里value使用动态绑定的值,保证其数据类型是number
                    -->
                    <el-option :value="1" label="高级讲师" />
                    <el-option :value="2" label="首席讲师" />
                </el-select>
            </el-form-item>
            <el-form-item label="讲师资历">
                <el-input v-model="teacher.career" />
            </el-form-item>
            <el-form-item label="讲师简介">
                <el-input v-model="teacher.intro" :rows="10" type="textarea" />
            </el-form-item>
            <!-- 讲师头像:TODO -->
            <el-form-item>
                <el-button
                           :disabled="saveBtnDisabled"
                           type="primary"
                           @click="saveOrUpdate"
                           >保存</el-button
                    >
            </el-form-item>
        </el-form>
    </div>
</template>

<script>
// 调用teacher.js
import teacher from '@/api/edu/teacher'
export default {
  data() { // 定义变量及初始化数据
    return {
      teacher:{
        name:"",
        sort:0,
        level:1,
        career:"",
        intro:""
      },
      saveBtnDisabled: false //save的button是否禁用,避免重复保存
    }
  },
  created() {

  },
  methods: {
  saveOrUpdate(){
    this.saveBtnDisabled = true,
    this.addTeacher()
  },
   addTeacher(){
     teacher.addTeacher(this.teacher)
     .then(
       () => {
         this.$message({
            type: 'success',
            message: '添加讲师成功!'
            }) 
         this.$router.push({ path:'/teacher/table' }) // 页面路由
       })
   }
  }
}
</script>

不过新增加的数据不一定在第一行,可能还要翻页,这不友好。在后端接口中pageTeacherCondition()新增排序功能。

代码语言:javascript复制
//  排序
wrapper.orderByAsc("gmt_create");
7.讲师修改

实现讲师修改功能其实要做两件事:1.显示原来的用户数据 2.更新数据。下面来实现这个功能。

(1)ui实现

首先教师列表要有一个按钮来修改,就是上面那个红色的按钮,前面list.vue代码其实已经写了,这里摘录下来。

代码语言:javascript复制
      <el-table-column label="操作" width="200" align="center">
        <template slot-scope="scope">
          <router-link :to="'/teacher/edit/'   scope.row.id">
            <el-button type="primary" size="mini" icon="el-icon-edit"
              >修改</el-button>
          </router-link>
          ...
        </template>
      </el-table-column>
    </el-table>

里面有router-link表示点击修改需要路由的urlscope.row.id是获取修改行数据的id,我们将它添加到路由的地址,这样就方便我们修改获取id,根据id回写数据并且修改。

(2)添加隐藏路由

点击了修改后页面要路由到哪呢?其实就是路由到我们之前的讲师添加页面,所以讲师添加与讲师修改是共用一个页面。但是上面代码中讲师添加的url是/edu/teacher/save,讲师修改的url/edu/teacher/edit/,两个不同的url怎么公用这一个页面呢?

答案是使用隐藏路由。在router/index.js中新增路由如下。修改路径path与添加不同,转到了同一个页面save.vue

代码语言:javascript复制
{
    path: 'edit/:id',
    name: '修改讲师',
    component: () => import('@/views/edu/teacher/save.vue'),
    meta: { title: '修改讲师', icon: 'tree' },
    hidden: true
 }

(3)回写数据

点击修改后的页面中应该有修改用户的数据,我们下面来实现数据的回写。先要实现接口,根据id拿到teacher。在teacher.js中开放接口如下。

代码语言:javascript复制
 findTeacher(id) {
    return request({
      url: `/eduservice/edu-teacher/findTeacher/${id}`,
      method: 'get'
    })
  }

接下来要在用户的展示页面save.vue中调用findTeacher()方法。

代码语言:javascript复制
 findTeacher(id) {
     teacher.findTeacher(id)
     .then((response)=>{
       console.log(response)
       this.teacher = response.data.item
       })
   }

注意这里的item与后端代码传过来的数据保持一致,如果你传的不是item就对应替换即可。

代码语言:javascript复制
    @ApiOperation("查找教师")
    @GetMapping("/findTeacher/{id}")
    public R findTeacher(@PathVariable String id) {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            throw new GuliException(1234, "自定义异常");
        }
        EduTeacher eduTeacher = eduTeacherService.getById(id);
        return R.ok().data("item", eduTeacher);
    }

关定义方法也不能实现回写,还要在created()中在页面初始化渲染时调用方法。不过这里需要根据url是否传递了id值,来判断页面功能是修改还是添加,如果是修改就调用findTeacher()将数据进行回写。注意下面是route而不是router

代码语言:javascript复制
 created() {//在页面渲染之前
    //判断路径中是否有id值
    if(this.$route.params && this.$route.params.id){
        //从路径中获取id值
        const id = this.$route.params.id
        //调用根据id查询的方法
        this.findTeacher(id)
    }

(4)修改数据

最后还需要实现修改功能。先在teacher.js中把接口写好。

代码语言:javascript复制
 updateTeacher(teacher) {
    return request({
      url: `/eduservice/edu-teacher/updateTeacher`,
      method: 'post',
      data: teacher
    })
  }

save.js调用下。

代码语言:javascript复制
   updateTeacher() {
     teacher.updateTeacher(this.teacher)
     .then(
        () => {
         this.$message({
            type: 'success',
            message: '修改讲师成功!'
            }) 
         this.$router.push({ path:'/teacher/table' })
       })
     
   }

添加、修改二合一。点保存按钮时判断是添加还是修改,执行不同的行为。

代码语言:javascript复制
saveOrUpdate() {
    //判断修改还是新增操作
    //根据teacher对象是否有id值来判断
    if (!this.teacher.id) {
        //没有id值,做【新增操作】
        this.saveBtnDisabled = true;
        this.addTeacher()
    }else{
        //有id值,做【修改操作】
        this.updateTeacher()
    }
  },
8.路由切换的问题及解决

上面代码存在一个小问题:点击修改后,数据回写到了表单中,再点击添加讲师,表单中依旧存在回写数据。如下图(添加讲师页面居然有回写数据)

是不是在添加路由时将teacher数据置空就可以解决这个问题啦?试验下。

代码语言:javascript复制
created() {//在页面渲染之前
    //判断路径中是否有id值
    if(this.$route.params && this.$route.params.id){
        //从路径中获取id值
        const id = this.$route.params.id
        //调用根据id查询的方法
        this.findTeacher(id)
    }
    else{
      this.teacher = {}
    }
},

答案是不行,因为从讲师修改到讲师添加其实是同一个页面,created()只会执行一次。通过监听可以解决这个bug。

代码语言:javascript复制
 created() {//在页面渲染之前
    this.init()
  },
  
  watch: {
      $route(to, from) {
      //路由变化方式,当路由发送变化,方法就执行
       console.log("watch $route");
       this.init()
   },

  methods: {
        init(){
        //判断路径中是否有id值
        if(this.$route.params && this.$route.params.id){
        //从路径中获取id值
        const id = this.$route.params.id
        //调用根据id查询的方法
        this.findTeacher(id)
   		 }
  }

0 人点赞