天天看点

前后端联调实例-讲师管理

之前学习了一下前后端联调的一般步骤和Nginx的简单配置,现在以讲师管理功能为例来实战一下。

项目环境

后端:SpringBoot + MyBatisPlus +MySQL+Nginx

前端:vue-cli + axios

后端

1、编写讲师管理Controller,包含根据id删除讲师、分页查询讲师列表、新增讲师等请求接口。

@Api(description = "讲师管理")
@RestController
@RequestMapping("/admin/edu/teacher")
@CrossOrigin   //跨域支持
public class TeacherAdminController {
  
  @Autowired
  private TeacherService teacherService;

  @ApiOperation(value = "根据id删除讲师")
  @DeleteMapping("{id}")
  public R removeById(@ApiParam(name = "id",value="讲师id",required = true)
                                    @PathVariable String id){
    teacherService.removeById(id);
    return R.ok();
  }
  
  @ApiOperation(value = "分页查询讲师列表")
  @GetMapping("{page}/{limit}")
  public R pageList(@ApiParam(name = "page",value = "当前页码",required = true)
                               @PathVariable Long page,
                               @ApiParam(name = "limit",value = "每页记录数",required = true)
                               @PathVariable Long limit,
                               @ApiParam(name = "teacherQuery",value = "查询对象",required = true)
                               TeacherQuery teacherQuery){
    if(page <=0 || limit <=0){  
      //抛出统一异常,好处:所有开发人员协同时可保证错误结构一致
      throw new CodingException(ResultCodeEnum.PARAM_ERROR);
    }
    Page<Teacher> pageParam = new Page<>(page,limit);
    //调用自己扩展的查询方法
    teacherService.pageQuery(pageParam,teacherQuery);
    //结果封装
    List<Teacher> records = pageParam.getRecords();
    long total = pageParam.getTotal();
    return R.ok().data("total",total).data("rows",records);
  }
  
  @ApiOperation(value = "新增讲师")
  @PostMapping
  public R save(@ApiParam(name = "teacher",value = "讲师对象",required = true)
                        @RequestBody Teacher teacher){
    teacherService.save(teacher);
    return R.ok();
  }
}
           

2、编写讲师服务接口,并继承IService接口。

public interface TeacherService extends IService<Teacher> {
    void pageQuery(Page<Teacher> pageParam,TeacherQuery teacherQuery);
}
           

3、编写讲师服务接口实现类,并继承ServiceImpl类。

@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherMapper,Teacher> implements TeacherService {
    
    @Override
    public void pageQuery(Page<Teacher> pageParam,TeacherQuery teacherQuery){
        //MP 让我们可以通过面向对象的方式编写SQL
        QueryWrapper<Teacher> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByAsc("sort");
        if(teacherQuery == null){
            baseMapper.selectPage(pageParam,queryWrapper);
            return;
        }
        String name = teacherQuery.getName();
        String level = teacherQuery.getLevel();
        String begin = teacherQuery.getBegin();
        String end = teacherQuery.getEnd();
        if(!StringUtils.isEmpty(name)){
            queryWrapper.like("name",name);//%name%
        }
        if(!StringUtils.isEmpty(level)){
            queryWrapper.eq("level",level);// =level
        }
        if(!StringUtils.isEmpty(begin)){
            queryWrapper.ge("gmt_create",begin); // >begin
        }
        if(!StringUtils.isEmpty(end)){
            queryWrapper.le("gmt_create",end); // <end
        }
        baseMapper.selectPage(pageParam,queryWrapper);
    }
}
           

4、编写讲师Mapper接口,并继承BaseMapper接口,无需定义方法。

public interface TeacherMapper extends BaseMapper<Teacher> {}
           

5、本例中后台有多个端口,使用Nginx来分发前端请求,前端请求直接访问8210端口即可,Nginx配置如下。

server{
  listen 8210;   #监听端口(默认监听接口为80)
  server_name localhost; #服务名
  location ~ /edu/{  #请求路径含 /edu/的请求转发到8110端口
    proxy_pass http://localhost:8110;
  }
  location ~ /admin/sysuser{ #请求路径含 /admin/sysuser的请求转发到8210端口
    proxy_pass http://localhost:8210;
  }
}
           

前端

1、前端使用axios框架来访问后端接口获取数据,axios请求调用函数封装在

src/utils/request.js

中,请求后端接口的根路径

BASE_API

定义在

config/dev.env.js

中,根据不同运行环境,env.js的前缀有所不同,如

prod.env.js

(生产环境),这里则使用的是开发环境。

'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv,{
    NODE_ENV:'"development"',
    BASE_API:'"http://localhost:8210"'
})
           

2、在

src/api

目录下添加

teacher.js

,封装后端请求的api操作,一个Controller对应一个api操作。

import request from '@utils/request'

const api_name = "/admin/edu/teacher"
export default {
   	//分页查询讲师列表
    getPageList(page,limit,searchObj){
        return request({
            url:`${api_name}/${page}/${limit}`,
            method:'get',
            params:searchObj
        })
    },
    //删除指定id讲师
    removeById(id) {
        return request({
            url:`${api_name}/${id}`,
            method:'delete',
        })
    },
    //新增讲师
    save(teacher) {
        return request({
            url:`${api_name}`,
            method:'post',
            data:teacher
        })
    }
}
           

3、在

src/views/edu/teacher

目录下添加新增讲师页

form.vue

,编写页面脚本。

<template>
	<div>
        <! -- 省略html代码 -->
    </div>
</template>

<script>
import teacher from '@/api/edu/teacher'
//绑定表单数据
const defaultForm = {
	name:'',
    sort:1,
    level:2,
    career:'',
    intro:'',
    avatar:''    
}
export default {
    data(){
        return {
      		teacher:defaultForm,
            saveBtnDisabled:false  //保存按钮是否禁用
        }
    },
    
    methods:{
        //watch监视 当路由发生变化时,执行此方法
        watch:{
            $router(to,from){
                console.log("watch $router")
                this.init();
            }
        },
        //钩子函数 此方法在页面渲染前调用,如果页面是同一个,数据不会刷新
        created(){
            console.log("created")
            this.init()
        }
        init(){
            if(this.$router.params && this.$router.params.id){//如果请求中含有id参数,则需要从后台查询数据填充表单
                const id = this.$router.params.id
                this.fetchDataById(id)
            }else{
                this.teacher = {...defaultForm} //对象拷贝
            }
        },
        save(){
            this.saveBtnDisabled = true;
            this.saveData();
        },
        saveData(){ 
            teacher.save(this.teacher).then(response =>{
                return this.$message({
                    type:'success',
                    message:'保存成功!'
                }).then(response =>{
                    this.$router.push({path:'/edu/teacher'}) //路由跳转
                })
            })
        },
        //后台数据查询
        fetchDataById(id){
            teacher.getById(id).then(response =>{
                console.log(response)
                this.teacher = response.data.item
            })
        }
    }
}
</script>
           

4、在

src/views/edu/teacher

目录下添加讲师列表页

list.vue

,编写页面脚本。

<template>
  <div class="app-container">
    <!--查询表单-->
    <el-form :inline="true" class="demo-form-inline">
      <el-form-item>
        <el-input v-model="searchObj.name" placeholder="讲师名" />
      </el-form-item>

      <el-form-item>
        <el-select v-model="searchObj.level" clearable placeholder="讲师头衔">
          <el-option :value="1" label="高级讲师" />
          <el-option :value="2" label="首席讲师" />
        </el-select>
      </el-form-item>

      <el-form-item label="添加时间">
        <el-date-picker
          v-model="searchObj.begin"
          type="datetime"
          placeholder="选择开始时间"
          value-format="yyyy-MM-dd HH:mm:ss"
          default-time="00:00:00"
        />
      </el-form-item>
      <el-form-item>
        <el-date-picker
          v-model="searchObj.end"
          type="datetime"
          placeholder="选择截止时间"
          value-format="yyyy-MM-dd HH:mm:ss"
          default-time="00:00:00"
        />
      </el-form-item>

      <el-button type="primary" icon="el-icon-search" @click="fetchData()">查询</el-button>
      <el-button type="default" @click="resetData()">清空</el-button>
    </el-form>
    <!-- 表格 -->
    <el-table
      v-loading="listLoading"
      :data="list"
      element-loading-text="数据加载中"
      border
      fit
      highlight-current-row
    >
      <el-table-column 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 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="'/edu/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>
    <!-- 分页 -->
    <el-pagination
      :current-page="page"
      :page-size="limit"
      :total="total"
      style="padding: 30px 0; text-align: center;"
      layout="total, prev, pager, next, jumper"
      @current-change="fetchData"
    />
  </div>
</template>
<script>
	import teacher from '@/api/edu/teacher'
    export default {
        data(){
            listLoading:true,// 显示loading信息
            list:null,// 数据列表
            total:0,// 总记录数
            page:1,// 页码
            limit:10,// 每页记录数
            searchObj:{} // 查询条件
        },
        created(){//当页面加载完成还未渲染时调用
            this.fetchData();
        },
        methods:{
            //查询讲师列表
            fetchData(){
                console.log('加载列表')
                teacher.getPageList(this.page,this.limit,this.searchObj).then(response=>{
                    console.log(response)
                    if(response.success === true){
                        this.list = response.data.rows
                        this.total = response.data.total
                    }
                    this.listLoading = false //不显示loading信息
                })
            },
            //清空数据
            resetData(){
                this.searchObj={}
                this.fetchData();
            },
            //删除讲师
            removeDataById(id){
        		this.$confirm('此操作将永久删除该文件,是否继续','提示',{
                    confirmButtonText:'确定',
                    cancelButtonText:'取消',
                    type:'warning'
                }).then(()=>{
                    return teacher.removeById(id);
                }).then(()=>{
                    this.fetchData();
                    this.$message({
                        type:'success',
                        message:'删除成功'
                    })
                }).catch(()=>{
                    this.$message({
                        type:'info',
                        message:'已取消删除'
                    })
                })       
    		}
        }
    }
</script>
           

5、在

src/router/index.js

中配置讲师路由。

import Vue from 'vue'
import Router from 'vue-router'

import Layout from '../views/layout/Layout'

export const constantRouterMap = [{
        path: '/login',
        component: () =>
            import ('@/views/login/index'),
        hidden: true
    },
    {
        path: '/404',
        component: () =>
            import ('@/views/404'),
        hidden: true
    },
    {
        path: '/',
        component: Layout,
        redirect: '/dashboard',
        name: 'Dashboard',
        children: [{
            path: 'dashboard',
            component: () =>
                import ('@/views/dashboard/index'),
            meta: { title: '首页', icon: 'dashboard' }

        }]
    },
    // 讲师路由管理!
    {
        path: '/edu/teacher',
        component: Layout,
        redirect: '/edu/teacher/list',
        name: 'Teacher',
        meta: {
            title: '讲师管理'
        },
        children: [{
            path: 'list',
            component: () =>
                import ('@/views/edu/teacher/list'),
            meta: {
                title: '讲师列表'
            }
        }, 
        {
            path: 'create',
            component: () =>
                import ('@/views/edu/teacher/form'),
            meta: {
                title: '添加讲师'
         },
		 {
			path:'edit/:id',
			name:'EduTeacherEdit',
			component:()=>
				import('@/views/edu/teacher/form'),
			meta:{
				title:'编辑讲师',
				noCache:true
			},
			hidden:true
		}}]
    },

    { path: '*', redirect: '/404', hidden: true }
]

export default new Router({
    // mode: 'history', //后端支持可开
    scrollBehavior: () => ({ y: 0 }),
    routes: constantRouterMap
})
           

6、在终端中输入命令

npm run dev

启动项目进行测试即可。

总结

1、后台接口如果部署多台服务器或多个端口时,可以使用Nginx来对前端的请求进行分发。

2、前端业务代码编写顺序:

env.js->api->views->router

3、当页面路由变化,而数据需要刷新时,可以使用watch来进行监视路由变化,一旦路由发生变化则执行指定的更新操作

4、后台接口最好进行统一异常处理,这样做的好处是可以让所有开发人员的错误结构保持一致。