SpringBoot开发简单的前后端分离系统
自学Java 水平比较低 放弃春招准备全力秋招
第一次学习项目 分享一下 适合小白
git源码:https://github.com/2020GetGoodOffer/test
csdn:https://blog.csdn.net/qq_41112238/article/details/105727188
安装node和vue
首先安装node.js,下载地址:node.js
下好后.exe直接安装就行了,然后在命令行通过node -v
查看是否安装成功
然后初始化,通过node init
完成,一路按回车就行
然后设置一个镜像点,不然可能因为网速问题一直无法成功
之后下载vuenpm install -g @vue/cli
等待一段时间后,vue -V
查看是否安装成功
创建vue项目
通过vue ui命令
然后访问localhost:8000这个地址
创建新工程
下一步,手动
下一步打开router和vuex,关闭代码校验
下一步,选中使用历史记录,然后完成,创建项目,不保存预设,稍微等待几分钟
进行测试,点击任务,serve,启动,然后输出,观察url地址
访问localhost:8080 ,没有问题
然后命令行CTRL+c退出,关闭服务
IDEA导入VUE工程
直接import刚才的工程就行,一路next和finish
然后需要安装一个vue的插件,如果网不好就用手机热点,多试几次。。
大概试了1个多小时,安装成功后,有一个app.vue
在命令行通过npm run serve
启动
通过ctrl+c终止
router-link标签表示映射的视图,app.vue是主视图,可以跳到home和about
映射关系配置在router目录下的index.js
创建SpringBoot项目
选择组件
这里我下载不下来2.2.6,使用以前下载的2.2.1.RELEASE
创建数据库表
数据库叫stock,表叫stock,有四个字段表示id,股票名,当前价格和涨跌幅比率
配置文件
在resources下新建一个application.yml,配置你自己的数据库信息
创建Vue视图
在vue工程创建一个Vue组件,叫Stock
template就是html,script就是js,style就是css
编写假数据进行一个简单的测试
在index.js中配置路由
命令行启动
访问localhost:8080/stock,成功了
增加两组测试数据
接收结果
Vue是动态加载的,此时再看刚才的页面
更好的写法是通过循环来返回结果
创建实体类
回到spring boot工程,创建股票对应实体类
@Entity @Data public class Stock { @Id private Integer id; private String name; private Double price; private Double rate; }
创建操作实体类的接口
只需要基础JpaRepository即可,第一个参数是实体类类型,第二个参数是主键类型
public interface StockRepository extends JpaRepository<Stock,Integer> { }
在接口处右键点击goto->test,创建一个test测试类
@SpringBootTest class StockRepositoryTest { @Autowired private StockRepository stockRepository; @Test void findAll(){ System.out.println(stockRepository.findAll()); } }
插入两行测试数据
运行
测试查询
新建一个controller包,创建一个Controller
@RestController//声明基于rest的控制器 @RequestMapping("/stock")//根url配置 public class StockController { @Autowired//自动注入 private StockRepository stockRepository; @GetMapping("/findAll")//get请求所有数据 public List<Stock> findAll(){ return stockRepository.findAll(); } }
启动springboot
访问localhost:8181/stock/findAll
,OJBK
替换假数据
之前我们Vue里的数据是假的,是我们自己加入测试的,不是数据库中的,所以我们现在要让前端的数据请求后端的。
安装axios
回到Vue项目,在控制台CTRL+c停止服务,输入vue add axios,添加该服务
等待一段时间,安装成功
编写请求
我们希望每次刷新页面时都会加载新数据,写到初始化函数中,还是要写到export default代码块中:
then里面的回调函数表示输出当前获取的结果,由于我们的Vue项目在8080,而后端的SpringBoot在8181,因此存在一个跨域问题
解决跨域问题
回到SpringBoot项目
创建一个config包,一个配置类,需要实现WebMvcConfigurer接口,并重写addCorsMappings方法,允许任何跨域请求
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET","POST","HEAD","PUT","DELETE","OPTIONS") .allowCredentials(true) .maxAge(3600) .allowedHeaders("*"); } }
然后重新启动SpringBoot服务器,此时我们刷新刚才的vue视图,打开F12开发者模式,在其中可以发现请求数据成功了,在console中可以看到
然后将测试的输出语句改为替换语句,将请求结果的值赋值给stocks股票数组,created里axios代码块外的this是vue对象,而axios里的this是回调函数,所以我们通过中间变量_this存储代码块外面的this,也就是vue对象,然后才能获取stocks。
此时,已经显示的是数据库中的数据,35.42,而不是我们写的假数据,34.12
Element UI
按之前的步骤,重新创建一个Vue工程,之后,搜索element ui
选中并安装
安装成功
待Vue项目创建成功后,打开IDEA,导入Vue项目
如果打开后有这个el-button就代表成功了
修改App.Vue的内容为以下内容:
<template> <div id="app"> <el-container style="height: 500px; border: 1px solid #eee"> <el-aside width="200px" style="background-color: rgb(238, 241, 246)"> <el-menu :default-openeds="['1', '3']"> <el-submenu index="1"> <template slot="title"><i class="el-icon-message"></i>导航一</template> <el-menu-item-group> <template slot="title">分组一</template> <el-menu-item index="1-1">选项1</el-menu-item> <el-menu-item index="1-2">选项2</el-menu-item> </el-menu-item-group> <el-menu-item-group title="分组2"> <el-menu-item index="1-3">选项3</el-menu-item> </el-menu-item-group> <el-submenu index="1-4"> <template slot="title">选项4</template> <el-menu-item index="1-4-1">选项4-1</el-menu-item> </el-submenu> </el-submenu> <el-submenu index="2"> <template slot="title"><i class="el-icon-menu"></i>导航二</template> <el-menu-item-group> <template slot="title">分组一</template> <el-menu-item index="2-1">选项1</el-menu-item> <el-menu-item index="2-2">选项2</el-menu-item> </el-menu-item-group> <el-menu-item-group title="分组2"> <el-menu-item index="2-3">选项3</el-menu-item> </el-menu-item-group> <el-submenu index="2-4"> <template slot="title">选项4</template> <el-menu-item index="2-4-1">选项4-1</el-menu-item> </el-submenu> </el-submenu> <el-submenu index="3"> <template slot="title"><i class="el-icon-setting"></i>导航三</template> <el-menu-item-group> <template slot="title">分组一</template> <el-menu-item index="3-1">选项1</el-menu-item> <el-menu-item index="3-2">选项2</el-menu-item> </el-menu-item-group> <el-menu-item-group title="分组2"> <el-menu-item index="3-3">选项3</el-menu-item> </el-menu-item-group> <el-submenu index="3-4"> <template slot="title">选项4</template> <el-menu-item index="3-4-1">选项4-1</el-menu-item> </el-submenu> </el-submenu> </el-menu> </el-aside> <el-container> <el-header style="text-align: right; font-size: 12px"> <el-dropdown> <i class="el-icon-setting" style="margin-right: 15px"></i> <el-dropdown-menu slot="dropdown"> <el-dropdown-item>查看</el-dropdown-item> <el-dropdown-item>新增</el-dropdown-item> <el-dropdown-item>删除</el-dropdown-item> </el-dropdown-menu> </el-dropdown> <span>王小虎</span> </el-header> <el-main> <el-table :data="tableData"> <el-table-column prop="date" label="日期" width="140"> </el-table-column> <el-table-column prop="name" label="姓名" width="120"> </el-table-column> <el-table-column prop="address" label="地址"> </el-table-column> </el-table> </el-main> </el-container> </el-container> </div> </template> <style> .el-header { background-color: #B3C0D1; color: #333; line-height: 60px; } .el-aside { color: #333; } </style> <script> export default { data() { const item = { date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }; return { tableData: Array(20).fill(item) } } }; </script>
再刷新首页
其中的一些标签含义如下:
- el-container 构建整个页面框架
- el-aside 构建左侧菜单
- el-menu 左侧菜单内容,常用属性:
default-openeds:默认展开的菜单,通过菜单的index值来关联
default-active:默认选中的菜单,通过菜单的index值来关联 - el-submenu:可展开的菜单,常用属性:
index:菜单下标,文本类型,不能是数值类型 - template:对应el-submenu的菜单名
- i:设置菜单图标,通过class属性
- el-menu-item:菜单的子结点,不可再展开,常用属性
index:菜单下标,文本类型,不能是数值类型
通过router动态构建左侧菜单
在view下创建四个Vue组件,分别为PageOne/Two/Three/Four
只需要修改每个h1标签中的数字即可
在index.jsp中,先导入四个组件
修改映射
将App.vue的main标签中的内容替换为
<router-view></router-view>
此时的效果
接着修改index.jsp中的配置
const routes = [ { path:"/", name:"导航1", component:App, children:[ { path:"/pageOne", name:"页面1", component:PageOne }, { path:"/pageTwo", name:"页面2", component:PageTwo } ] }, { path:"/navigation", name:"导航2", component:App, children:[ { path:"/pageThree", name:"页面3", component:PageThree }, { path:"/pageFour", name:"页面4", component:PageFour } ] } ]
此时的效果
修改el-aside标签中的内容
<el-aside width="200px" style="background-color: rgb(238, 241, 246)"> <el-menu> <el-submenu v-for="(item,index) in $router.options.routes" :index="index+''"> <template slot="title"><i class="el-icon-message"></i>{{item.name}}</template> <el-menu-item v-for="(item2,index2) in item.children" :index="index+'-'+index2">{{item2.name}}</el-menu-item> </el-submenu> </el-menu> </el-aside>
此时的效果
最后,只留下aside和main部分
此时的效果
设置动态路由导航栏
App.vue里只保留一行router-view标签,把内容移到新创建的Index.vue
在index.js配置映射,将app替换为刚才创建的index,因为Vue里App.vue是基础视图,其他视图相当于都在它的基础之上显示
此时的效果
接下来实现通过点击左边的页面1234切换页面的功能:
- 给el-menu添加router属性
- 在页面添加router-view标签
- 设置el-menu-item的index值,即为跳转页面
此时的效果,选哪个就跳哪个
通过redirect属性设置访问loaclhost8080的默认页面
此时访问8080会默认跳到pageOne
但是没有选中效果,通过class属性指定,利用三面运算符,当route.path(浏览器url的地址)和item2.path(设置的路由地址)一样时,具有选中效果
此时具有选中效果
前后端分离开发数据对接
复制table代码
将pageOne.vue复制为以下表单代码
<template> <el-table :data="tableData" border style="width: 100%"> <el-table-column fixed prop="date" label="日期" width="150"> </el-table-column> <el-table-column prop="name" label="姓名" width="120"> </el-table-column> <el-table-column prop="province" label="省份" width="120"> </el-table-column> <el-table-column prop="city" label="市区" width="120"> </el-table-column> <el-table-column prop="address" label="地址" width="300"> </el-table-column> <el-table-column prop="zip" label="邮编" width="120"> </el-table-column> <el-table-column fixed="right" label="操作" width="100"> <template slot-scope="scope"> <el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button> <el-button type="text" size="small">编辑</el-button> </template> </el-table-column> </el-table> </template> <script> export default { methods: { handleClick(row) { console.log(row); } }, data() { return { tableData: [{ date: '2016-05-02', name: '王小虎', province: '上海', city: '普陀区', address: '上海市普陀区金沙江路 1518 弄', zip: 200333 }, { date: '2016-05-04', name: '王小虎', province: '上海', city: '普陀区', address: '上海市普陀区金沙江路 1517 弄', zip: 200333 }, { date: '2016-05-01', name: '王小虎', province: '上海', city: '普陀区', address: '上海市普陀区金沙江路 1519 弄', zip: 200333 }, { date: '2016-05-03', name: '王小虎', province: '上海', city: '普陀区', address: '上海市普陀区金沙江路 1516 弄', zip: 200333 }] } } } </script>
复制分页代码
创建一个div标签,将刚才的table放进去,再加入分页代码
<el-pagination background layout="prev, pager, next" :total="1000"> </el-pagination>
此时效果
创建假数据
table中的数据来自data,先修改data数据
修改table数据,prop就是data中的属性,label是表单中的列名
此时效果
修改分页
每页3条记录,一共30条10页,点击事件为page方法
page-size前面要和total一样加上冒号,当时漏掉了
page(currentPage){ switch (currentPage) { case 1: this.tableData=[{ id: 1, name: '科技股', price: 45.43, rate: +1.57 }, { id: 2, name: '教育股', price: 35.22, rate: +0.19 }, { id: 3, name: '石油股', price: 45.43, rate: -4.61 }, { id: 4, name: '餐饮股', price: 17.43, rate: -9.77 }]; break; case 2: this.tableData=[{ id: 4, name: '科技股1', price: 45.43, rate: +1.57 }, { id: 5, name: '教育股2', price: 35.22, rate: +0.19 }, { id: 6, name: '石油股3', price: 45.43, rate: -4.61 }, { id: 7, name: '餐饮股4', price: 17.43, rate: -9.77 }]; break; } }
此时点击第二页会显示新的数据
实现分页查询
由于前端是分页显示,但是可以看到后端查询的数据是一次全部查出来,需要进行分页
先增加一点数据,然后查询,显示了全部
修改StockController中的方法,传入page和size,表示第几页,显示几条记录
如果第一页就是0,page=要显示的页-1
@GetMapping("/findAll/{page}/{size}")//get请求所有数据 public Page<Stock> findAll(@PathVariable("page")Integer page, @PathVariable("size")Integer size){ PageRequest pageRequest=PageRequest.of(page,size); return stockRepository.findAll(pageRequest); }
重启springboot,测试,传入0和3
查询第二页就传1和3
跟之前一样,先安装axios
替换为真数据,跟之前一样,还是通过create参数和_this的赋值
此时默认显示前3条
修改pageOne为以下代码
<template> <div> <el-table :data="tableData" border style="width: 100%"> <el-table-column fixed prop="id" label="股票id" width="150"> </el-table-column> <el-table-column prop="name" label="股票名" width="120"> </el-table-column> <el-table-column prop="price" label="股票当前价格" width="120"> </el-table-column> <el-table-column prop="rate" label="股票涨跌幅" width="120"> </el-table-column> <el-table-column fixed="right" label="操作" width="100"> <template slot-scope="scope"> <el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button> <el-button type="text" size="small">编辑</el-button> </template> </el-table-column> </el-table> <el-pagination background layout="prev, pager, next" :page-size="pageSize" :total="total" @current-change="page"> </el-pagination> </div> </template> <script> export default { methods: { handleClick(row) { console.log(row); }, page(currentPage){ const _this=this; axios.get('http://localhost:8181/stock/findAll/'+(currentPage-1)+'/3').then(function (resp) { _this.tableData=resp.data.content; _this.pageSize=resp.data.size; _this.total=resp.data.totalElements; }) } }, data() { return { pageSize:'', total:'', tableData:'' } }, created() { const _this=this; axios.get('http://localhost:8181/stock/findAll/0/3').then(function (resp) { console.log(resp); _this.tableData=resp.data.content; _this.pageSize=resp.data.size; _this.total=resp.data.totalElements; }) } } </script>
此时成功实现分页
添加数据
在前端添加数据
修改index.js,把之前的pageOne改为stockQuery,pageTwo改为stockUpdate
现在的效果
接下来开始完成添加数据的功能,将stockUpdate.vue修改为以下内容
model对象绑定数据,rules对象校验表单
<template> <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm"> <el-form-item label="股票名称" prop="name"> <el-input v-model="ruleForm.name"></el-input> </el-form-item> <el-form-item label="当前价格" prop="price"> <el-input v-model="ruleForm.price"></el-input> </el-form-item> <el-form-item label="股票涨跌幅" prop="rate"> <el-input v-model="ruleForm.rate"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')">添加</el-button> <el-button @click="resetForm('ruleForm')">重置</el-button> </el-form-item> </el-form> </template> <script> export default { data() { return { ruleForm: { name: '', price: '', rate: '', }, rules: { name: [ { required: true, message: '请输入名称', trigger: 'blur' }, ], price: [ { required: true, message: '请输入价格', trigger: 'change' } ], rate: [ {required: true, message: '请输入涨跌幅', trigger: 'change' } ] } }; }, methods: { submitForm(formName) { //_this表示当前vue对象 const _this=this; this.$refs[formName].validate((valid) => { if (valid) { alert('submit!'); } else { return false; } }); }, //重置表单 resetForm(formName) { this.$refs[formName].resetFields(); } } } </script>
当前效果
接下来需要写后台保存数据的代码
在后台添加数据
给id先加上自增注解
在controller中增加接口
@PostMapping("/save")//post请求提交数据 public void save(@RequestBody Stock stock){ stockRepository.save(stock); }
重启SpringBoot,回到Vue项目,添加提交逻辑
测试,输入数据
点击添加,再查询数据库
前端也可以即时查询
优化提示效果
使用message方法
此时添加会弹出提示框
添加成功后回跳查询页
submitForm(formName) { const _this=this; this.$refs[formName].validate((valid) => { if (valid) { axios.post('http://localhost:8181/stock/save',this.ruleForm).then(function (resp) { _this.$alert(_this.ruleForm.name, '添加成功', '消息',{ confirmButtonText: '确定', callback: action => { } }); //回跳 _this.$router.push('/StockQuery'); }) } else { return false; } }); }
会提示添加成功,并回跳查询页面
修改和删除数据
这里老师的代码有点问题,还需要做如下调整:
App.vue和Index.vue
把Index.vue和App.vue的template里的内容互换
配置index.js
将之前的StockUpdate改名StockAdd,新创建一个StockUpdate,在index.js配置
修改查询页
当点击修改时,将股票的行号传过去
修改StockUpdate
<template> <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm"> <el-form-item label="股票名称" prop="name"> <el-input v-model="ruleForm.name"></el-input> </el-form-item> <el-form-item label="当前价格" prop="price"> <el-input v-model="ruleForm.price"></el-input> </el-form-item> <el-form-item label="股票涨跌幅" prop="rate"> <el-input v-model="ruleForm.rate"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')">添加</el-button> <el-button @click="resetForm('ruleForm')">重置</el-button> </el-form-item> </el-form> </template> <script> export default { data() { return { ruleForm: { name: '', price: '', rate: '', }, rules: { name: [ { required: true, message: '请输入名称', trigger: 'blur' }, ], price: [ { required: true, message: '请输入价格', trigger: 'change' } ], rate: [ { required: true, message: '请输入涨跌幅', trigger: 'change' } ] } }; }, methods: { submitForm(formName) { const _this=this; this.$refs[formName].validate((valid) => { if (valid) { axios.post('http://localhost:8181/stock/save',this.ruleForm).then(function (resp) { _this.$alert(_this.ruleForm.name, '添加成功', '消息',{ confirmButtonText: '确定', callback: action => { } }); _this.$router.push('/StockQuery'); }) } else { return false; } }); }, //重置表单 resetForm(formName) { this.$refs[formName].resetFields(); }, },created() { alert(this.$route.query.id); } } </script>
先进行简单测试,例如点击第一行
弹出1
然后跳了过来
提供根据id查询接口
在Springboot的controller增加方法
@GetMapping("/findById/{id}")//get请求单个数据 public Stock findById(@PathVariable("id")Integer id){ return stockRepository.findById(id).get(); }
简单测试:
没有问题,回到StockUpdate.vue的created方法
此时点击修改按钮,会自动查询显示现在的最新值,为了显示id列,增加一段标签
增加id属性,设置为只读
此时会显示id
修改功能
先把按钮改为修改
成功
提供修改接口,之后重启SpringBoot服务器
@PutMapping("/update")//put请求修改数据 public void update(@RequestBody Stock stock){ stockRepository.save(stock); }
只需要修改这三处
测试,给名字加上test
成功
增加删除点击事件del
增加删除接口del
@DeleteMapping("/deleteById/{id}") public void delete(@PathVariable("id")Integer id){ stockRepository.deleteById(id); }
然后重启服务器
调用接口
回到Vue项目
测试:
成功
如果希望删除后可以刷新,使用window.location.reload
#Java工程师##Spring#