之前學習了一下前後端聯調的一般步驟和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、背景接口最好進行統一異常處理,這樣做的好處是可以讓所有開發人員的錯誤結構保持一緻。