文章目录
整本书学习过程所有文档以及项目文件以及作者的项目文件都已上传至网盘,提取码如下:链接:https://pan.baidu.com/s/1LQPqtQiqVYjkUNAQWESGSQ
提取码:ziyi
1. 项目构建
1.1 前端项目搭建
使用Vue-cli脚手架快速搭建前端项目
- vue init webpack deptvue
- npm install
1.2 后端项目搭建
使用Spring Initializer快速创建SpringBoot模板项目
1.3 数据库设计
采用MySQL,表结构如下:
2. 查询数据
2.1 后端实现
-
配置文件
在application.yml文件中配置数据库连接、JPA及端口等信息:
spring: datasource: #key:后必须加空格,否则执行报错 url: jdbc:mysql://localhost:3306/test4?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true username: root password: admin123 driver-class-name: com.mysql.cj.jdbc.Driver jpa: show-sql: true properties: hibernate: formate_sql: true server: port: 8081 #需要修改端口,否则和前端端口冲突
-
实体类
配置完成后建立和表结构相对应的实体类Dept:
@Entity @Data public class Dept { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer deptno; private String dname; private String loc; }
-
Dao层
创建DeptRepository接口并继承JpaRepository类,该类中封装了基本的增删改查、分页、排序等方法:
public interface DeptRepository extends JpaRepository<Dept,Integer> { }
-
controller层
这里为了简化操作省略Service层,在DeptController类中创建查询方法:
@RestController @RequestMapping("/dept") public class DeptController { @Autowired private DeptRepository deptRepository; @GetMapping("/findAll") public List<Dept>findAll(){ return deptRepository.findAll(); } }
-
测试
启动项目,在Postman中进行测试:
2.2 前端实现
-
创建Dept页面
首先在components目录下创建部门静态数据模板Dept.vue页面,如下:
<template> <div> <table> <tr> <td>编号</td> <td>部门名称</td> <td>地址</td> </tr> <tr :v-for="item in depts"> <td>{{ item.deptno }}</td> <td>{{ item.dname }}</td> <td>{{ item.loc }}</td> </tr> </table> </div> </template> <script> export default { name: "Dept", data: function() { return { msg: "沈子怡shenziyi", depts: [ { deptno: 1, dname: "研发部", loc: "北京" }, { deptno: 2, dname: "人事部", loc: "北京" }, { deptno: 3, dname: "行政部", loc: "北京" } ] }; }, }; </script> <style></style>
-
修改路由
修改router/index.js文件的路由配置信息:
import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import Dept from '../components/Dept.vue' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Dept', component: Dept } ] })
另外由于main.js是入口JS,在main.js中导入App组件,App组件默认有Vue.js的Logo,将Logo删除,只保留路由占位符即可:
<template> <div id="app"> <router-view/> </div> </template>
-
测试
执行npm run dev指令,启动前端项目,启动成功后在浏览器输入http://localhost:8080/即可访问静态数据,如下所示:
-
引入Axios
首先安装axios依赖:npm install --save axios
依赖添加成功后在main.js中引用并赋值Axios:
import axios from 'axios' Vue.prototype.$http=axios;
接着在Dept页面使用Axios请求后端的实时数据:
<script> export default { name: "Dept", data: function() { return { msg: "沈子怡shenziyi", depts: [] }; }, created: function() { this.$http.get("http://localhost:8081/dept/findAll").then(resp => { this.depts = resp.data; }); } }; </script>
在前后端都启动的情况下,访问http://localhost:8080/,发现请求不到数据,控制台报如下错:
这个错误表面权限不足,因为前端项目和后端项目在不同的端口下启动,所以需要配置跨域请求。使用后端CORS跨域配置,在后端添加CrosConfig类:@Configuration public class CrosConfig implements WebMvcConfigurer { @Bean public CorsFilter corsFilter() { //1.添加CORS配置信息 CorsConfiguration config = new CorsConfiguration(); //1) 允许的域,不要写*,否则cookie就无法使用了 config.addAllowedOrigin("http://localhost:8080"); //2) 是否发送Cookie信息 config.setAllowCredentials(true); //3) 允许的请求方式 config.addAllowedMethod("OPTIONS"); config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET"); config.addAllowedMethod("PUT"); config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE"); config.addAllowedMethod("PATCH"); // 4)允许的头信息 config.addAllowedHeader("*"); //2.添加映射路径,我们拦截一切请求 UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); configSource.registerCorsConfiguration("/**", config); //3.返回新的CorsFilter. return new CorsFilter(configSource); } }
这样就可以成功访问后端的实时数据了:
3. 加载菜单
3.1 引入Element UI
数据成功访问后,引入Element UI组件对数据进行渲染,首先引入依赖:npm install element-ui -S
依赖添加成功后,接着在main.js中引入Element UI:
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
引入之后,在项目中就可以直接使用其相关组件了,官网点击这里。
3.2 菜单
-
菜单模板
菜单是用户成功访问后首页显示的项目所有业务,为了提高开发效率可以直接引入ElementUI中的组件模板:
-
创建首页
在components目录下新建Index.vue作为项目首页,将上述菜单模板代码复制到Index.vue中。然后修改路由配置,使用户能直接访问主页:
<template> <div> <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> <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-submenu> </el-menu> </el-aside> <el-container> <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(5).fill(item) }; } }; </script>
export default new Router({ routes: [ { path: '/', name: 'Index', component: Index } ] })
菜单模板代码解释如下:
- el-container:构建页面框架。
- el-aside:构建左侧菜单。
- el-menu:左侧菜单内容,常用属性。
- :default-openeds="[‘1’, ‘3’]":默认展开的菜单。
- :default-active="‘1-1’":默认选中的菜单。
- el-submenu:可展开的菜单,常用属性。
- index=“1”:菜单的下标,文本类型,不能是数值类型。
- template:对应的el-submenu菜单名。
- i标签i class=“el-icon-setting”:设置菜单图标。
页面简化后页面效果如下:
-
添加子页面
当单击“选项1”和“选项2”菜单,能够动态加载子页面数据,所以在components目录下新建PageOne.vue(页面1)和PageTwo.vue(页面2):
<template> <duv>页面一</duv> </template> <script> export default { name:"PageOne" } </script> <template> <duv>页面二</duv> </template> <script> export default { name:"PageTwo" } </script>
接下来在路由中配置首页的两个子页面:
export default new Router({ routes: [ { path: '/', name: 'Index', component: Index, children:[ { path:"/PageOne", name:"页面一", component:PageOne }, { path:"/PageTwo", name:"页面二", component:PageTwo } ] } ] })
配置完成后修改Index页面,使子页面内容能够被占位符接收,代码如下:
<el-container> <el-header></el-header> <el-main> <router-view /> </el-main> </el-container>
分别在浏览器访问http://localhost:8080/#/PageOne、http://localhost:8080/#/PageTwo,界面如下所示:
4. 动态获取路由菜单
替换静态菜单为路由中的菜单项,并实现菜单和路由动态绑定,修改Index页面代码:(作者这个代码我这边是跑不通的,我用了另一种方式动态配置路由,及在el-menu上添加router属性,然后在el-submenu中为routes属性赋值)
<template>
<div>
<el-container style="height: 500px; border: 1px solid #eee">
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<el-menu router :default-openeds="['1']" default-active="1-1">
<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="item2.path"
:class="$route.path == item2.path ? 'is-active' : ''"
>
{{ item2.name }}
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<el-container>
<el-header></el-header>
<el-main>
<router-view />
<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(5).fill(item)
};
}
};
</script>
接着我们希望用户首次访问时就显示页面一的内容,所以理由需要配置重定向:
//部分代码省略
path:"/",
name:"Index",
component:Index,
redirect:'/PageOne' /*首次访问时展示页面一的内容
4. 带分页数据查询
4.1 后端接口实现
修改DeptController类中的查询接口为待分页接口:
@Autowired
private DeptRepository deptRepository;
@GetMapping("/findAll/{page}/{size}")
/*page是查询第几页
public Page<Dept> findAll(@PathVariable("page") Integer page, @PathVariable("size") Integer size){
PageRequest request = PageRequest.of(page, size);
return deptRepository.findAll(request);
}
重启后端项目,在Postman中进行测试,结果如下:
4.2 前端实现
-
创建静态数据模板
使用ElementUI组件Table表格构建静态表格数据模板,复制模板代码并覆盖PageOne页面,精简代码与数据库字段相对应。同时,引入分页模板代码。
PageOne.vue组件代码如下:
<template> <div> <el-table :data="tableData" border style="width: 100%"> <el-table-column fixed prop="depno" label="编号" width="150"> </el-table-column> <el-table-column prop="dname" label="部门名称" width="120"> </el-table-column> <el-table-column prop="loc" label="地址" width="120"> </el-table-column> <el-table-column fixed="right" label="操作" width="100"> <template slot-scope="scope"> <el-button @click="edit(scope.row)" type="text" size="small" >修改</el-button > <el-button @click="delete scope.row" type="text" size="small" >删除</el-button > </template> </el-table-column> </el-table> <el-pagination background layout="prev,pager,next" :total="1000"> </el-pagination> </div> </template> <script> export default { data() { return { tableData: [ { depno: "001", dname: "shenziyi1", loc: "上海" }, { depno: "002", dname: "shenziyi2", loc: "上海" }, { depno: "002", dname: "shenziyi2", loc: "上海" } ] }; } }; </script>
-
动态获取后台数据
在PageOne页面使用Axios请求后台接口数据,代码如下:
<template> <div> <el-table :data="tableData" border style="width: 100%"> <el-table-column label="编号" prop="depno" width="120"></el-table-column> <el-table-column prop="dname" label="部门名称" width="120"> </el-table-column> <el-table-column prop="loc" label="地址" width="120"> </el-table-column> <el-table-column label="操作" width="100"> <template slot-scope="scope"> <el-button @click="edit(scope.row)" type="text" size="small" >修改</el-button > <el-button @click="delete scope.row" type="text" size="small" >删除</el-button > </template> </el-table-column> </el-table> <el-pagination background layout="prev,pager,next" :total="total" @current-change="page" > </el-pagination> </div> </template> <script> export default { methods: { page: function(currentPage) { this.$http .get("http://localhost:8081/dept/findAll/" + (currentPage - 1) + "/6") .then(resp => { console.log(resp); this.tableData = resp.data.content; this.pageSize = resp.data.size; this.total = resp.data.totalElements; }); } }, data() { return { pageSize: 0, total: 0, tableData: [] }; }, created: function() { this.$http.get("http://localhost:8081/dept/findAll/0/6").then(resp => { console.log(resp); this.tableData = resp.data.content; this.pageSize = resp.data.size; this.total = resp.data.totalElements; }); } }; </script>
-
测试
上述两步后,页面显示效果如下所示:(我也不知道为什么请求到数据,但是depno渲染不到表格上去,然后我直接把作者的文件拉过来就可以了)
5. 部门员工信息的录入
5.1 后端接口的实现
在DeptController类中以插入方式添加接口:
代码如下:
@PostMapping("/save")
public String save(@RequestBody Dept dept){
Dept result = deptRepository.save(dept);
if(result!=null){
return "success";
}else{
return "error";
}
}
5.2 前端实现
-
修改菜单名称
在路由中修改菜单名称,并将组件PageOne.vue改名为DeptManager.vue,将组件PageTwo改名为AddDept.vue(这里为了方便我就没改了)
-
修改Add Dept页面
添加页面布局,使用Element组件中的Form表单实现。页面布局成功之后精简AddDept代码,并使用Axios将页面添加数据提交到后端项目添加接口:
@PostMapping("/save") public String save(@RequestBody Dept dept){ Dept result = deptRepository.save(dept); if(result!=null){ return "success"; }else{ return "error"; } }
经过上面配置后,部门员工的录入功能实现了。
6. 部门数据编辑
6.1 后端接口实现
修改一般分两步:第一步先根据修改ID查询数据,第二步将数据修改后保存。所以在DeptController类中插入单条查询接口和修改接口:
@GetMapping("/findById/{deptno}")
public Dept findById(@PathVariable("deptno") Integer deptno){
return deptRepository.findById(deptno).get();
}
@PutMapping("/update")
public String update(@RequestBody Dept dept){
Dept result = deptRepository.save(dept);
if(result!=null){
return "success";
}else{
return "error";
}
}
6.2 前端实现
-
建立修改页面
在components目录下新建DeptUpdate.vue组件,并在路由中配置:
<template> <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm"> <el-form-item label="编号" prop="deptno"> <el-input v-model="ruleForm.deptno" readonly></el-input> </el-form-item> <el-form-item label="部门名称" prop="dname"> <el-input v-model="ruleForm.dname"></el-input> </el-form-item> <el-form-item label="地址" prop="loc"> <el-input v-model="ruleForm.loc"></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:function() { return { ruleForm: { deptno:'', dname: '', loc: '', }, rules: { dname: [ { required: true, message: '请输入部门名称', trigger: 'blur' }, { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } ], loc: [ { required: true, message: '请输入部门地址', trigger: 'blur' }, { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } ] } }; }, methods: { submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { this.$http.put("http://localhost:8081/dept/update",this.ruleForm).then(resp=>{ if(resp.data=='success'){ this.$alert('修改成功!', '消息', { confirmButtonText: '确定', callback: action => { this.$router.push('/DeptManager'); } }); } }) } else { return false; } }); }, resetForm(formName) { this.$refs[formName].resetFields(); } }, created(){ //alert(this.$route.query.deptno); this.$http.get("http://localhost:8081/dept/findById/"+this.$route.query.deptno).then(resp=>{ this.ruleForm=resp.data; }) } } </script>
-
绑定事件
在DeptManager页面修改绑定事件,并将ID传递给DeptUpdate页面:
edit(row) { this.$router.push({ path:'/update', query:{ deptno:row.deptno } }); },
-
修改页面
修改页面可以借鉴添加页面的布局,在修改页面中首先通过Vue.js的生命周期created()方法在模板渲染之前获取后端数据,然后再渲染成视图。接着将修改的数据传递至后端进行保存:
<template> <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm"> <el-form-item label="编号" prop="deptno"> <el-input v-model="ruleForm.deptno" readonly></el-input> </el-form-item> <el-form-item label="部门名称" prop="dname"> <el-input v-model="ruleForm.dname"></el-input> </el-form-item> <el-form-item label="地址" prop="loc"> <el-input v-model="ruleForm.loc"></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:function() { return { ruleForm: { deptno:'', dname: '', loc: '', }, rules: { dname: [ { required: true, message: '请输入部门名称', trigger: 'blur' }, { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } ], loc: [ { required: true, message: '请输入部门地址', trigger: 'blur' }, { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } ] } }; }, methods: { submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { this.$http.put("http://localhost:8081/dept/update",this.ruleForm).then(resp=>{ if(resp.data=='success'){ this.$alert('修改成功!', '消息', { confirmButtonText: '确定', callback: action => { this.$router.push('/DeptManager'); } }); } }) } else { return false; } }); }, resetForm(formName) { this.$refs[formName].resetFields(); } }, created(){ //alert(this.$route.query.deptno); this.$http.get("http://localhost:8081/dept/findById/"+this.$route.query.deptno).then(resp=>{ this.ruleForm=resp.data; }) } } </script>
经过以上几步配置后,部门数据的修改功能实现了。
7. 部门数据删除
7.1 后端接口实现
对于直接删除一般比较简单,只需在DeptController类中插入删除,代码如下:
@DeleteMapping("/delete/{deptno}")
public void delete(@PathVariable("deptno") Integer deptno){
deptRepository.deleteById(deptno);
}
7.2 前端实现
在DeptManager页面,删除单击事件中使用Axios调用后端删除接口即可,代码如下:
delete1(row){
this.$http.delete("http://localhost:8081/dept/delete/"+row.deptno).then(resp=>{
this.$alert('删除成功!', '消息', {
confirmButtonText: '确定',
callback: action => {
window.location.reload();
}
});
})
},
小型部门管理系统完成。后端负责实现API及业务逻辑,而前端可以独立完成与用户交互的整个过程,两者可以同时开工,不互相依赖,开发效率更高,分工比较均衡。