day5 讲师管理模块的前端实现
目 录- day5 讲师管理模块的前端实现
- 1.登录功能改造
- 2.跨域问题
- 3.前端框架开发过程
- 4.讲师列表的前端实现
- 5.讲师删除功能的前端实现
- 6.讲师添加
- 7.讲师修改
- 8.路由切换的问题及解决
- 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
,role
,avatar
,token
,前三个参数需要后端info()
提供,token
由login()
提供。
在controller
目录下新建LoginContreller.java
.启动后端的项目。
@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
中的接口改成与后端一致。
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
替换成讲师列表与添加讲师的页面,如下所示。
{
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
很容易完成前端接口的改造。
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
中的数据取出来具体展示下。
this.total = response.data.total
this.list = response.data.records
console.log(this.total)
console.log(this.list)
注意一定要使用this.xx
,否则会报下面的错哒。
ReferenceError: xx is not defined
(5)展示获取数据
使用element-ui的表格组件进行修改。这里将代码复制到list.vue中替换到template
即可。
.<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原来就是我们拿到的数据结果集。
this.list = response.data.records
下面的代码是生成序号,显然:(page - 1) * limit scope.$index 1
就是记录的序号计算公式。
<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的值相等,类型不同。
<el-table-column label="头衔" width="80">
<template slot-scope="scope">
{{ scope.row.level === 1 ? "高级讲师" : "首席讲师" }}
</template>
</el-table-column>
(6)实现分页
上面成功展示了数据,但是还没有实现分页,下面来实现分页的过程。 有了element-ui,分页简直是太简单了。直接复用它的分页代码即可。在</el-table>
组件后面添加分页组件如下。
<!--分页组件-->
<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.
getList(page = 1) {
this.page = page
...
}
(7)条件查询
在element-ui中也可以找到对应的组件。条件查询的组件放在el-table
上。
<!--多条件查询表单-->
<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
获得这一行的数据.
<el-button type="danger"
size="mini"
icon="el-icon-delete"
@click="removeDataById(scope.row.id)">
删除
</el-button>
在scipt
中添加这个方法。
// 删除
removeDataById(id) {
}
(2)接口定义
在teacher.js
中开放前端的接口。
removeTeacher(id) {
return request({
url: `/eduservice/edu-teacher/removeTeacher/${id}`,
method: 'delete'
})
}
(3)调用接口
之前已经把teacher.js
依赖引入了,现在直接进行调用就可以了。
// 删除
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()
新增排序功能。
// 排序
wrapper.orderByAsc("gmt_create");
7.讲师修改
实现讲师修改功能其实要做两件事:1.显示原来的用户数据 2.更新数据。下面来实现这个功能。
(1)ui实现
首先教师列表要有一个按钮来修改,就是上面那个红色的按钮,前面list.vue
代码其实已经写了,这里摘录下来。
<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
表示点击修改需要路由的url
。scope.row.id
是获取修改行数据的id
,我们将它添加到路由的地址,这样就方便我们修改获取id,根据id回写数据并且修改。
(2)添加隐藏路由
点击了修改后页面要路由到哪呢?其实就是路由到我们之前的讲师添加页面,所以讲师添加与讲师修改是共用一个页面。但是上面代码中讲师添加的url是/edu/teacher/save
,讲师修改的url
是/edu/teacher/edit/
,两个不同的url
怎么公用这一个页面呢?
答案是使用隐藏路由。在router/index.js
中新增路由如下。修改路径path
与添加不同,转到了同一个页面save.vue
。
{
path: 'edit/:id',
name: '修改讲师',
component: () => import('@/views/edu/teacher/save.vue'),
meta: { title: '修改讲师', icon: 'tree' },
hidden: true
}
(3)回写数据
点击修改后的页面中应该有修改用户的数据,我们下面来实现数据的回写。先要实现接口,根据id
拿到teacher
。在teacher.js
中开放接口如下。
findTeacher(id) {
return request({
url: `/eduservice/edu-teacher/findTeacher/${id}`,
method: 'get'
})
}
接下来要在用户的展示页面save.vue
中调用findTeacher()
方法。
findTeacher(id) {
teacher.findTeacher(id)
.then((response)=>{
console.log(response)
this.teacher = response.data.item
})
}
注意这里的item
与后端代码传过来的数据保持一致,如果你传的不是item
就对应替换即可。
@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
中把接口写好。
updateTeacher(teacher) {
return request({
url: `/eduservice/edu-teacher/updateTeacher`,
method: 'post',
data: teacher
})
}
在save.js
调用下。
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
数据置空就可以解决这个问题啦?试验下。
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。
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)
}
}