文章目录
由于项目参考的是尚硅谷在线教育项目,前端代码我直接copy的所以没有写,后端代码大部分都是配置
1. 前端登录功能的地址改变
1.1. 修改配置文件的请求地址
在config文件夹里面有dev.env.js,修改BASE_API
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
// BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
BASE_API: '"http://localhost:8001"',
})
2.2登录调用的两个方法
login登录操作方法和登录之后获取用户信息的方法,所有创建两个接口的方法实现登录
根据前端的请求可以得出
(1)login 返回token值
(2)info 返回roles name avatar
2.2.1编写login和info接口
package com.blb.eduservice.controller;
import com.blb.common_utils.result.Res;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
@RestController
@Api(tags = "登录模块")
@RequestMapping("/eduservice/user")
public class EduLoginController {
//登录
@PostMapping("/login")
@ApiOperation("登录")
public Res login()
{
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("token","admin");
return Res.ok().data(hashMap);
}
@PostMapping("/info")
@ApiOperation("获取用户信息")
public Res info()
{
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("roles","admin");
hashMap.put("name","admin");
hashMap.put("avatar","https://pics0.baidu.com/feed/d8f9d72a6059252d08b2669f1919f9335ab5b90b.jpeg?token=94ad5f47db03f206e334510ba906055a");
return Res.ok().data(hashMap);
}
}
3 最终测试以及出现的问题
3.1跨域问题
通过一个地址去访问另一个地址, 这个过程中如果三个地方有任何一个地方不一样
- 访问协议 http https
- ip地址
- 端口号
3.2 跨域解决方式
- 在Controller上加注解@CrossOrigin
- 使用网关(后面笔记建)
跨域解决1(加注解@CrossOrigin)
package com.blb.eduservice.controller;
import com.blb.common_utils.result.Res;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
@RestController
@Api(tags = "登录模块")
@RequestMapping("/eduservice/user")
@CrossOrigin
public class EduLoginController {
//登录
@PostMapping("/login")
@ApiOperation("登录")
public Res login()
{
System.out.println("111");
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("token","admin");
return Res.ok().data(hashMap);
}
@GetMapping("/info")
@ApiOperation("获取用户信息")
public Res info()
{
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("roles","admin");
hashMap.put("name","admin");
hashMap.put("avatar","https://pics0.baidu.com/feed/d8f9d72a6059252d08b2669f1919f9335ab5b90b.jpeg?token=94ad5f47db03f206e334510ba906055a");
return Res.ok().data(hashMap);
}
}
4.框架的使用过程
4.1添加路由
在router.js中添加路由
2. 点击路由显示的路由对应页面的内容
3. 在views创建Vue页面
4. 在api文件夹创建js文件,定义接口地址和参数
5. 在创建的vue页面引入js文件,调用方法实现功能
data、created、methods
5.讲师列表前端实现
5.1. 添加路由
在router目录下的index.js中添加路由信息
```{
path: '/teacher',
component: Layout,
redirect: '/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' }
}
]
},
5.2. 创建页面对应的路由
有路由信息可知,讲师管理的地址的views/deu/teacher/list文件而添加讲师在views/deu/teacher/list(.vue可以省略不写),创建响应的文件
5.3在api文件夹创建teacher.js,定义访问接口路径
import request from '@/utils/request'
export default{
//讲师列表,分页查询(条件分页查询)
getTeacherPageList(current,limit,teacherQuery)
{
return request({
url:`/eduservice/teacher/pageTeacherCondition/${current}/${limit}`,
method:post,
data:teacherQuery
})
}
}
5.4 在讲师列表页面list.vue页面调用定义的接口方法,得到返回的数据
<script>
// 引入调用teacher.js的文件
import teacher from '@/api/edu/teacher'
import request from '@/utils/request'
export default {
//写核心代码的位置
data() {//定义变量以及初始值
return{
list:null,
page:1,
limit:10,
total:0,
teacherQuery:{}
}
},
created() {//页面渲染之前执行,一般调用methods定义的方法
this.getTeacherList()
},
methods:{//创建具体的方法。调用teacher.js定义的方法
//讲师列表
getTeacherList(){
teacher.getTeacherPageList(this.page,this.limit,this.teacherQuery)
.then(respons=>{//请求失败
console.log("请求成功")
console.log(respons)
this.teacherQuery=respons.data.record
this.total=respons.data.total
console.log(this.teacherQuery)
console.log(this.total)
})
.catch(err=>{//请求成功
console.log("请求失败")
console.log(err)
})
}
}
}
</script>
5.5请求接口获取数据在页面进行显示
list.vue
<template>
<div class="app-container">
讲师列表
<!-- 表格 -->
<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>
</div>
</template>
<script>
// 引入调用teacher.js的文件
import teacher from '@/api/edu/teacher'
import request from '@/utils/request'
export default {
//写核心代码的位置
data() {//定义变量以及初始值
return{
list:null,
page:1,
limit:10,
total:0,
teacherQuery:{}
}
},
created() {//页面渲染之前执行,一般调用methods定义的方法
this.getTeacherList()
},
methods:{//创建具体的方法。调用teacher.js定义的方法
//讲师列表
getTeacherList(){
teacher.getTeacherPageList(this.page,this.limit,this.teacherQuery)
.then(respons=>{//请求失败
console.log("请求成功")
console.log(respons)
this.list=respons.data.record
this.total=respons.data.total
console.log(this.list)
console.log(this.total)
})
.catch(err=>{//请求成功
console.log("请求失败")
console.log(err)
})
}
}
}
</script>
5.6添加分页查询
list.vue
在上面的基础上添加分页操作
<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="getTeacherList"
/>
修改getTeacherList函数
getTeacherList(page = 1){
//page默认是1,如果查询其他页,会自动边参数,
this.page=page
teacher.getTeacherPageList(this.page,this.limit,this.teacherQuery)
.then(respons=>{//请求失败
console.log("请求成功")
console.log(respons)
this.list=respons.data.record
this.total=respons.data.total
console.log(this.list)
console.log(this.total)
})
.catch(err=>{//请求成功
console.log("请求失败")
console.log(err)
})
}
我将data中的limit改成了5,也就是没有显示5条数据,结果如下
5.7条件查询
使用element-ui组件来实现条件查询,在列表上添加条件输入表单,使用v-model数据绑定
data中的teacherQuery 会通过v-module会将属性自动传过来
在list,vue中
<!--查询表单-->
<el-form :inline="true" class="demo-form-inline">
<el-form-item>
<el-input v-model="teacherQuery.name" placeholder="讲师名"/>
</el-form-item>
<el-form-item>
<el-select v-model="teacherQuery.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="teacherQuery.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="teacherQuery.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="getTeacherList()">查询</el-button>
<el-button type="default" @click="resetData()">清空</el-button>
</el-form>
5.8 清空功能
实现:1. 清空表单输入的条件数据 2. 查询所有数据
根据清空请求的函数,创建函数,即:resetData()函数
list.vue中
resetData(){//清空方法 1.查询所有数据 2.清空表单数据
//清空表单数据
this.teacherQuery={}
//查询所有数据
this.getTeacherList()
}
5.9讲师删除功能
- 在每条记录后面添加删除按钮
- 绑定按钮对应的事件
- 在绑定事件的方法传递删除讲师的id值
- 在api文件夹teacher.js定义删除接口的地址
//删除讲师
deleteTeacherById(id)
{
return request({
url:`/eduservice/teacher/deleteTeacherById/${id}`,
method:'delete'
})
}
- list.vue页面调用方法实现删除
removeDataById(id)
{
this.$confirm('此操作将永久删除该g该讲师的记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => { //点击确定执行then方法
//调用删除方法
teacher.deleteTeacherById(id)
.then(response=>{ //删除成功
this.$message({
type: 'success',
message: '删除成功!'
});
})
//回到列表页面
this.getTeacherList()
})
}
5.10 添加讲师
需求:点击添加按钮,进入表单页面,输入讲师信息,在表单页面点击保存,提交接口,添加数据到数据库,调转到讲师列表
5.10.1写添加讲师页面
save.vue
<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="saveTeacher">保存</el-button>
</el-form-item>
</el-form>
</div>
5.10.2api定义接口地址
teacher.js中
//添加讲师
addTeacher(teacher){
return request({
url:`/eduservice/teacher/addTeacher`,
method:'post',
data:teacher
})
}
5.10.3在save.vue页面中实现调用
重点:路由跳转
<script>
import teacherApi from '@/api/edu/teacher'
export default {
data(){
return {
teacher: {},
saveBtnDisabled:false
}
},
created(){
},
methods: {
saveOrUpdate()
{
},
saveTeacher()
{
teacherApi.addTeacher(this.teacher)
.then(response=>{
//提示信息
this.$message({
type: 'success',
message: '添加成功!'
});
//回到列表页面 路由跳转
this.$router.push({path: '/teacher/table'})
})
}
}
}
</script>
5.11讲师修改
5.11.1. 每条记录后面添加修改按钮
前端已写了,可以不用写了
5.11.2. 点击修改按钮,进入表单页面,进行数据回显
根据id查询数据显示
5.11.3. 通过路由跳转进入数据回显页面,在路由index页面添加路由
在/teacher下面
{
path: 'edit/:id', // :/id相当于占位符
name: 'EduTeacherEdit',
component: ()=>import('@/views/edu/teacher/save'),
meta: { title: '编辑讲师', noCache: true},
hidden: true //路由不显示即隐藏起来
}
路由链接修改
在list.vue中修改路由地址
5.11.4. 在表单页面中实现数据回显
- 在teacher.js定义根据id查询的接口
//通过id得到讲师的信息
getTeacherById(id){
return request({
url:`/eduservice/teacher/getTeacher/${id}`,
method:'get'
})
}
- 在save.vue中调用接口实现数据的回
//根据讲师id查询信息
getInfo(id)
{
teacherApi.getTeacherById(id)
.then(response => {
this.teacher=response.data.eduTeacher
})
}
- 调用根据id查询的方法
因为添加和修改都使用了save页面,需要区分添加还是修改,只有修改是查询数据回显
方法:判断路径的方法里面是否有讲师的id值,如果有id值修改,没有id值直接添加
created(){//渲染页面之前执行
if(this.$route.params && this.$route.params.id)
{
const id=this.$route.params.id;
console.log(id)
this.getInfo(id)
}
},
5.11.5最终实现修改
- 在api下面的teacher.js中定义修改接口
//修改讲师信息
updateTeacher(teacher){
return request({
url:`/eduservice/teacher/updateEduTeacher`,
method:'post',
data:teacher
})
}
- 想页面中调用修改的方法
//修改讲师信息
updateTeacher(){
teacherApi.updateTeacher(this.teacher)
.then(response => {//提示信息
this.$message({
type: 'success',
message: '修改成功!'
});
//页面跳转
this.$router.push({path: '/teacher/table'})
})
}
saveOrUpdate()
{
//判断是否有id 有id修改 没有id添加
if(this.teacher.id)
{
this.updateTeacher()
}
else
{
this.saveTeacher()
}
}
6. 遇到的问题
第一次点击修改进行了数据回显,但第二次点击添加讲师,进入表单页面,但是表单页面显示修改时回显的数据,正确效果应该是将表单数据清空
解决方式
问题没有解决的分析
多次路由跳转到同一个页面,在页面中created方法只会执行一次,后面在执行中不会进行跳转
最终方案:使用vue监听器
watch:{ //监听
$route(to,from) //路由变化方式,路由发生变化方法会执行
{
this.init()
}
},
methods: {
init(){
if(this.$route.params && this.$route.params.id)
{
const id=this.$route.params.id;
// this.getInfo(id)
}
else
{
this.teacher={}
}
},
}
7. 代码总汇
index.js
import Vue from 'vue'
import Router from 'vue-router'
// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
Vue.use(Router)
/* Layout */
import Layout from '../views/layout/Layout'
/**
* hidden: true if `hidden:true` will not show in the sidebar(default is false)
* alwaysShow: true if set true, will always show the root menu, whatever its child routes length
* if not set alwaysShow, only more than one route under the children
* it will becomes nested mode, otherwise not show the root menu
* redirect: noredirect if `redirect:noredirect` will no redirect in the breadcrumb
* name:'router-name' the name is used by <keep-alive> (must set!!!)
* meta : {
title: 'title' the name show in submenu and breadcrumb (recommend set)
icon: 'svg-name' the icon show in the sidebar,
}
**/
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',
hidden: true,
children: [{
path: 'dashboard',
component: () => import('@/views/dashboard/index')
}]
},
{
path: '/teacher',
component: Layout,
redirect: '/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' }
},
{
path: 'edit/:id', // :/id相当于占位符
name: 'EduTeacherEdit',
component: ()=>import('@/views/edu/teacher/save'),
meta: { title: '编辑讲师', noCache: true},
hidden: true //路由不显示即隐藏起来
}
]
},
{
path: '/example',
component: Layout,
redirect: '/example/table',
name: 'Example',
meta: { title: 'Example', icon: 'example' },
children: [
{
path: 'table',
name: 'Table',
component: () => import('@/views/table/index'),
meta: { title: 'Table', icon: 'table' }
},
{
path: 'tree',
name: 'Tree',
component: () => import('@/views/tree/index'),
meta: { title: 'Tree', icon: 'tree' }
}
]
},
{
path: '/form',
component: Layout,
children: [
{
path: 'index',
name: 'Form',
component: () => import('@/views/form/index'),
meta: { title: 'Form', icon: 'form' }
}
]
},
{
path: '/nested',
component: Layout,
redirect: '/nested/menu1',
name: 'Nested',
meta: {
title: 'Nested',
icon: 'nested'
},
children: [
{
path: 'menu1',
component: () => import('@/views/nested/menu1/index'), // Parent router-view
name: 'Menu1',
meta: { title: 'Menu1' },
children: [
{
path: 'menu1-1',
component: () => import('@/views/nested/menu1/menu1-1'),
name: 'Menu1-1',
meta: { title: 'Menu1-1' }
},
{
path: 'menu1-2',
component: () => import('@/views/nested/menu1/menu1-2'),
name: 'Menu1-2',
meta: { title: 'Menu1-2' },
children: [
{
path: 'menu1-2-1',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
name: 'Menu1-2-1',
meta: { title: 'Menu1-2-1' }
},
{
path: 'menu1-2-2',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
name: 'Menu1-2-2',
meta: { title: 'Menu1-2-2' }
}
]
},
{
path: 'menu1-3',
component: () => import('@/views/nested/menu1/menu1-3'),
name: 'Menu1-3',
meta: { title: 'Menu1-3' }
}
]
},
{
path: 'menu2',
component: () => import('@/views/nested/menu2/index'),
meta: { title: 'menu2' }
}
]
},
{
path: 'external-link',
component: Layout,
children: [
{
path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
meta: { title: 'External Link', icon: 'link' }
}
]
},
{ path: '*', redirect: '/404', hidden: true }
]
export default new Router({
// mode: 'history', //后端支持可开
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
})
teacher.js
import Vue from 'vue'
import Router from 'vue-router'
// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
Vue.use(Router)
/* Layout */
import Layout from '../views/layout/Layout'
/**
* hidden: true if `hidden:true` will not show in the sidebar(default is false)
* alwaysShow: true if set true, will always show the root menu, whatever its child routes length
* if not set alwaysShow, only more than one route under the children
* it will becomes nested mode, otherwise not show the root menu
* redirect: noredirect if `redirect:noredirect` will no redirect in the breadcrumb
* name:'router-name' the name is used by <keep-alive> (must set!!!)
* meta : {
title: 'title' the name show in submenu and breadcrumb (recommend set)
icon: 'svg-name' the icon show in the sidebar,
}
**/
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',
hidden: true,
children: [{
path: 'dashboard',
component: () => import('@/views/dashboard/index')
}]
},
{
path: '/teacher',
component: Layout,
redirect: '/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' }
},
{
path: 'edit/:id', // :/id相当于占位符
name: 'EduTeacherEdit',
component: ()=>import('@/views/edu/teacher/save'),
meta: { title: '编辑讲师', noCache: true},
hidden: true //路由不显示即隐藏起来
}
]
},
{
path: '/example',
component: Layout,
redirect: '/example/table',
name: 'Example',
meta: { title: 'Example', icon: 'example' },
children: [
{
path: 'table',
name: 'Table',
component: () => import('@/views/table/index'),
meta: { title: 'Table', icon: 'table' }
},
{
path: 'tree',
name: 'Tree',
component: () => import('@/views/tree/index'),
meta: { title: 'Tree', icon: 'tree' }
}
]
},
{
path: '/form',
component: Layout,
children: [
{
path: 'index',
name: 'Form',
component: () => import('@/views/form/index'),
meta: { title: 'Form', icon: 'form' }
}
]
},
{
path: '/nested',
component: Layout,
redirect: '/nested/menu1',
name: 'Nested',
meta: {
title: 'Nested',
icon: 'nested'
},
children: [
{
path: 'menu1',
component: () => import('@/views/nested/menu1/index'), // Parent router-view
name: 'Menu1',
meta: { title: 'Menu1' },
children: [
{
path: 'menu1-1',
component: () => import('@/views/nested/menu1/menu1-1'),
name: 'Menu1-1',
meta: { title: 'Menu1-1' }
},
{
path: 'menu1-2',
component: () => import('@/views/nested/menu1/menu1-2'),
name: 'Menu1-2',
meta: { title: 'Menu1-2' },
children: [
{
path: 'menu1-2-1',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
name: 'Menu1-2-1',
meta: { title: 'Menu1-2-1' }
},
{
path: 'menu1-2-2',
component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
name: 'Menu1-2-2',
meta: { title: 'Menu1-2-2' }
}
]
},
{
path: 'menu1-3',
component: () => import('@/views/nested/menu1/menu1-3'),
name: 'Menu1-3',
meta: { title: 'Menu1-3' }
}
]
},
{
path: 'menu2',
component: () => import('@/views/nested/menu2/index'),
meta: { title: 'menu2' }
}
]
},
{
path: 'external-link',
component: Layout,
children: [
{
path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
meta: { title: 'External Link', icon: 'link' }
}
]
},
{ path: '*', redirect: '/404', hidden: true }
]
export default new Router({
// mode: 'history', //后端支持可开
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
})
list.vue
<template>
<div class="app-container">
讲师列表
<!--查询表单-->
<el-form :inline="true" class="demo-form-inline">
<el-form-item>
<el-input v-model="teacherQuery.name" placeholder="讲师名"/>
</el-form-item>
<el-form-item>
<el-select v-model="teacherQuery.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="teacherQuery.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="teacherQuery.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="getTeacherList()">查询</el-button>
<el-button type="default" @click="resetData()">清空</el-button>
</el-form>
<!-- 表格 -->
<el-table
: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="'/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="getTeacherList"
/>
</div>
</template>
<script>
// 引入调用teacher.js的文件
import teacher from '@/api/edu/teacher'
import request from '@/utils/request'
export default {
//写核心代码的位置
data() {//定义变量以及初始值
return{
list:null,
page:1,
limit:5,
total:0,
teacherQuery:{} //通过v-module会将属性自动传过来
}
},
created() {//页面渲染之前执行,一般调用methods定义的方法
this.getTeacherList()
},
methods:{//创建具体的方法。调用teacher.js定义的方法
//讲师列表
getTeacherList(page = 1){
this.page=page
teacher.getTeacherPageList(this.page,this.limit,this.teacherQuery)
.then(respons=>{//请求失败
console.log("请求成功")
console.log(respons)
this.list=respons.data.record
this.total=respons.data.total
console.log(this.list)
console.log(this.total)
})
.catch(err=>{//请求成功
console.log("请求失败")
console.log(err)
})
},
resetData(){//清空方法 1.查询所有数据 2.清空表单数据
//清空表单数据
this.teacherQuery={}
//查询所有数据
this.getTeacherList()
},
removeDataById(id)
{
this.$confirm('此操作将永久删除该g该讲师的记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => { //点击确定执行then方法
//调用删除方法
teacher.deleteTeacherById(id)
.then(response=>{ //删除成功
this.$message({
type: 'success',
message: '删除成功!'
});
this.getTeacherList()
})
//回到列表页面
this.getTeacherList()
})
}
}
}
</script>
save.vue
<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>
import teacherApi from '@/api/edu/teacher'
export default {
data(){
return {
teacher: {},
saveBtnDisabled:false
}
},
created(){//渲染页面之前执行
this.init()
},
watch:{ //监听
$route(to,from) //路由变化方式,路由发生变化方法会执行
{
this.init()
}
},
methods: {
init(){
if(this.$route.params && this.$route.params.id)
{
const id=this.$route.params.id;
// this.getInfo(id)
}
else
{
this.teacher={}
}
},
saveOrUpdate()
{
//判断是否有id 有id修改 没有id添加
if(this.teacher.id)
{
this.updateTeacher()
}
else
{
this.saveTeacher()
}
},
saveTeacher()
{
teacherApi.addTeacher(this.teacher)
.then(response=>{
//提示信息
this.$message({
type: 'success',
message: '添加成功!'
});
//回到列表页面 路由跳转
this.$router.push({path: '/teacher/table'})
})
},
//根据讲师id查询信息
getInfo(id)
{
console.log(id)
teacherApi.getTeacherById(id)
.then(response => {
this.teacher=response.data.eduTeacher
})
},
//修改讲师信息
updateTeacher(){
teacherApi.updateTeacher(this.teacher)
.then(response => {//提示信息
this.$message({
type: 'success',
message: '修改成功!'
});
//页面跳转
this.$router.push({path: '/teacher/table'})
})
}
}
}
</script>
第六天内容
1. 对象存储OSS
为了解决海量数据存储与弹性扩容,项目中我们采用云存储的解决方案- 阿里云OSS。
1.1开通阿里云账号
1.2找到对象存储OSS
1.3 开通云对象存储OSS
1.4开通成功后进入管理控制台
1.4创建一个bucket
1.5进入文件管理,上传文件
2.使用java代码操作阿里云oss上传文件到阿里云oss操作
2.1 准备工作
创建操作阿里云oss许可证(阿里颁发的id和密钥)
- 点击头像,选择AccessKey管理
- 点击创建AccessKey
2.2如何找到官方文档
- 找到对象存储OSS新手学习路径
- 在oss学习路径中找到API和SDK选择JavaSDK
打开后查看操作文档
2.3后端集成oss
2.3.1在service模块下创建一个service_oss子模块
2.3.2引入相关的依赖
ervice-oss上级模块service已经引入service的公共依赖,所以service-oss模块只需引入阿里云oss相关依赖即可,
service父模块已经引入了service-base模块,所以Swagger相关默认已经引入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 阿里云oss依赖 -->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<!-- 日期工具栏依赖 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
</dependencies>
2.3.3配置application.properties
#服务端口
server.port=8002
#服务名
spring.application.name=service-oss
#环境设置:dev、test、prod
spring.profiles.active=dev
#阿里云 OSS
#不同的服务器,地址不同
aliyun.oss.file.endpoint=your endpoint
aliyun.oss.file.keyid=your accessKeyId
aliyun.oss.file.keysecret=your accessKeySecret
#bucket可以在控制台创建,也可以使用java代码创建
aliyun.oss.file.bucketname=guli-file
2.3.4启动启动类报错
原因:启动的时候找数据库的配置,但这个模块不需要操作数据库,知识做到上传到oss功能,没有配置数据库
解决方式
- 追加上数据库配置
- 在启动类添加属性,默认不去加载数据库配置
方式2解决问题(启动类上面不加载数据库)
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class ServiceOssApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceOssApplication.class, args);
}
}
2.4 实现文件上传
2.4.1创建一个常用类用于读取配置文件
//当项目启动后,spring接口 spring加载之后 执行接口一个方法
@Component
public class PropertiesUtils implements InitializingBean {
//读取配置文件中的内容
@Value("${aliyun.oss.file.endpoint}")
private String endpoint;
@Value("${aliyun.oss.file.keyid}")
private String keyId;
@Value("${aliyun.oss.file.keysecret}")
private String keySecret;
@Value("${aliyun.oss.file.bucketname}")
private String bucketName;
//定义公开静态常量
public static String END_POINT;
public static String KEY_ID;
public static String KEY_SECRET;
public static String BUCKET_NAME;
@Override
public void afterPropertiesSet() throws Exception {
END_POINT=endpoint;
KEY_ID=keyId;
KEY_SECRET=keySecret;
BUCKET_NAME=bucketName;
}
}
2.4.2创建controller
@RestController
@RequestMapping("/eduoss/fileoss")
@Api(tags = "上传头像")
@CrossOrigin
public class OssController {
@Autowired
private OssService ossService;
//上传头像
@PostMapping()
@ApiOperation("上传讲师头像")
public Res uploadOssFile(MultipartFile file)
{
//上传文件 MultipartFile
String url= ossService.uploadFileAvatar(file);
//返回上传路径
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("url",url);
return Res.ok().data(hashMap);
}
}
2.4.3创建service
@Service
public class OssServiceImpl implements OssService {
@Override
public String uploadFileAvatar(MultipartFile file) {
// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
String endpoint = PropertiesUtils.END_POINT;
String accessKeyId = PropertiesUtils.KEY_ID;
String accessKeySecret = PropertiesUtils.KEY_SECRET;
String bucketName = PropertiesUtils.BUCKET_NAME;
InputStream inputStream = null;
try {
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 获取上传文件的输入流
inputStream = file.getInputStream();
//获取前文件名称
String filename = file.getOriginalFilename();
//在文件名称里面生成一个唯一的值
String uuid = UUID.randomUUID().toString().replace("-", ".");
//生成不同的文件名,避免导致最后一次上传打之前上传的文件覆盖
filename=uuid+filename;
//把文件进行日期分类
//获取当前日期
String datePath = new DateTime().toString("yyyy/MM/dd");
filename=datePath+'/'+filename;
//调用oss方法实现上传
/**
* 参数一:Bucket名称
* 参数二:上传到oss的文件路径和文件名
* 参数三:上传文件输入流
*/
ossClient.putObject(bucketName,filename,inputStream);
// 关闭OSSClient。
ossClient.shutdown();
//上传文件后返回文件路径
//需要将上传到阿里云的oss路径手动拼接处理
String url="https://"+bucketName+"."+endpoint+"/"+filename;
return url;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
3. Nginx
Nginx是一个反向代理服务器具有请求转发、负载均衡、动静分离的作用
特点:使用cmd命令启动Nginx,如果关闭cmd窗口,Nginx不会停止,需要使用nginx.exe -s stop
3.1 配置nginx实现请求转发功能
3.1.1找到nginx.conf文件
3.1.2修改nginx.conf文件
-
将80端口改为81
-
配置nginx转发规则
-
修改前端请求地址为nginx地址
在dev.env.js中
-
重新启动nginx(先停止在启动)
停止:nginx.exe -s stop
启动:nginx.exe
4.添加讲师上传头像前端整合
4.1 在添加讲师页面,创建上传组件,实现上传
使用element-ui组件实现
在源码里面找到组件,复制到前端项目src components里面
4.2使用组件
data定义变量好初始化值
data(){
return {
teacher: {},
imagecropperShow:false,//上传弹框组件是否显示
imagecropperKey:0,//上传组件key值
BASE_API:process.env.BASE_API, //dev.env.jd里面的地址
saveBtnDisabled:false //报存按钮是否禁用
}
},
4.3引入组件和声明组件
import ImageCropper from '@/components/ImageCropper'
import PanThumb from '@/components/PanThumb'
export default {
components: { ImageCropper, PanThumb },
4.4修改上传地址
在上面的基础上save.vue进行了修改。最终代码
<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 label="讲师头像">
<!-- 头衔缩略图 -->
<pan-thumb :image="teacher.avatar"/>
<!-- 文件上传按钮 -->
<el-button type="primary" icon="el-icon-upload" @click="imagecropperShow=true">更换头像
</el-button>
<!--
v-show:是否显示上传组件
:key:类似于id,如果一个页面多个图片上传控件,可以做区分
:url:后台上传的url地址
@close:关闭上传组件
@crop-upload-success:上传成功后的回调 -->
<image-cropper
v-show="imagecropperShow"
:width="300"
:height="300"
:key="imagecropperKey"
:url="BASE_API+'/eduoss/fileoss'"
field="file"
@close="close"
@crop-upload-success="cropSuccess"/>
</el-form-item>
<el-form-item>
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import teacherApi from '@/api/edu/teacher'
import ImageCropper from '@/components/ImageCropper'
import PanThumb from '@/components/PanThumb'
export default {
components: { ImageCropper, PanThumb },
data(){
return {
teacher: {},
imagecropperShow:false,//上传弹框组件是否显示
imagecropperKey:0,//上传组件key值
BASE_API:process.env.BASE_API, //dev.env.jd里面的地址
saveBtnDisabled:false //报存按钮是否禁用
}
},
created(){//渲染页面之前执行
this.init()
},
watch:{ //监听
$route(to,from) //路由变化方式,路由发生变化方法会执行
{
this.init()
}
},
methods: {
close(){//关闭弹窗的方法
this.imagecropperShow=false
this.imagecropperKey=this.imagecropperKey+1
},
cropSuccess(data)//上传成功的方法
{
this.imagecropperShow=false
//上传接口返回图片地址
console.log(data.url)
this.teacher.avatar=data.url
this.imagecropperKey=this.imagecropperKey+1
},
init(){
if(this.$route.params && this.$route.params.id)
{
const id=this.$route.params.id;
this.getInfo(id)
}
else
{
this.teacher={}
}
},
saveOrUpdate()
{
//判断是否有id 有id修改 没有id添加
if(this.teacher.id)
{
this.updateTeacher()
}
else
{
this.saveTeacher()
}
},
saveTeacher()
{
teacherApi.addTeacher(this.teacher)
.then(response=>{
//提示信息
this.$message({
type: 'success',
message: '添加成功!'
});
//回到列表页面 路由跳转
this.$router.push({path: '/teacher/table'})
})
},
//根据讲师id查询信息
getInfo(id)
{
console.log(id)
teacherApi.getTeacherById(id)
.then(response => {
this.teacher=response.data.eduTeacher
})
},
//修改讲师信息
updateTeacher(){
teacherApi.updateTeacher(this.teacher)
.then(response => {//提示信息
this.$message({
type: 'success',
message: '修改成功!'
});
//页面跳转
this.$router.push({path: '/teacher/table'})
})
}
}
}
</script>