学习SpringBoot+Vue前后端分离项目,原项目GitHub地址,项目作者江雨一点雨博客。
前端页面设计
DepMana.vue
<template>
<div style="width: 500px;">
<el-input
placeholder="输入部门名称进行搜索"
prefix-icon="el-icon-search"
v-model="filterText">
</el-input>
<el-tree
style="margin-top: 10px"
:data="deps"
:props="defaultProps"
//是否在点击节点时展开或收缩节点,默认为true,false为只有点击箭头图标才有效。
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="tree">
<span class="custom-tree-node" style="display: flex;justify-content: space-between;width: 100%" slot-scope="{ node, data }">
<span>{{ data.name }}</span>
<span>
<el-button
type="primary"
size="mini"
class="depBtn"
@click="() => showAddDepView(data)">
添加部门
</el-button>
<el-button
type="danger"
size="mini"
class="depBtn"
@click="() => deleteDep(data)">
删除部门
</el-button>
</span>
</span>
</el-tree>
<el-dialog
title="添加部门"
:visible.sync="dialogVisible"
width="30%">
<div>
<div>
<el-tag style="margin-bottom: 8px;margin-right: 8px">上级部门</el-tag>
<span>{{pname}}</span>
</div>
<div>
<el-tag style="margin-right: 8px">部门名称</el-tag>
<el-input v-model="dep.name" size="small" placeholder="请输入部门名称..." style="width: 70%"></el-input>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="dialogVisible = false">取 消</el-button>
<el-button size="small" type="primary" @click="doAddDep">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: "DepMana",
data(){
return{
filterText:'',
deps:[],
dialogVisible:false,
dep:{
name:'',
parentId:-1,
},
pname:'',
defaultProps: {
children: 'children',
label: 'name'
}
}
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
}
},
mounted(){
this.initDeps();
},
methods: {
removeDep2Deps(deps,id){
for (let i = 0; i <deps.length; i++) {
let d=deps[i];
if (d.id == id) {
deps.splice(i,1);
return;
}else {
this.removeDep2Deps(d.children,id);
}
}
},
addDep2Deps(deps,dep){
for (let i = 0; i <deps.length; i++) {
let d=deps[i];
if (d.id == dep.parentId) {
d.children=d.children.concat(dep);
return;
}else {
this.addDep2Deps(d.children,dep);
}
}
},
initDep(){
this.dep={
name:'',
parentId:-1,
};
this.pname=''
},
doAddDep(){
this.postRequest("/system/basic/department/",this.dep).then(resp=>{
if (resp) {
this.addDep2Deps(this.deps,resp.obj);
this.initDep();
this.dialogVisible=false;
}
})
},
deleteDep(data){
if (data.parent) {
this.$message.error("父部门删除失败")
}else {
this.$confirm('此操作将永久删除【' + data.name + '】部门, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.deleteRequest("/system/basic/department/" + data.id).then(resp => {
if (resp) {
this.removeDep2Deps(this.deps,data.id);
this.initDep();
this.dialogVisible=false;
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
showAddDepView(data){
this.pname=data.name;
this.dep.parentId=data.id;
this.dialogVisible=true;
},
initDeps(){
this.getRequest("/system/basic/department/").then(resp=>{
if (resp) {
this.deps=resp;
}
})
},
filterNode(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
}
},
}
</script>
<style>
.depBtn{
padding: 2px;
}
</style>
搜索功能详解
在部门管理页面有搜索功能可以快速定位到想要找的部门,filterText受到watch监控,当你在输入框打字时,就会调用watch里的filterText方法,val即你输入的字符。然后执行tree里面的filterNode方法,
filterNode(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
}
value指输入的字符,data则是传入的JSON数据。如果value不存在或者为空,当前data保留,即部门树全部展开。
如果value有值,查看data中的name包含value的下标是否等于-1,等于-1返回true,当前data保留,否则剔除。
后端端口设计
Department
model/Department 添加result和getter setter
private Integer result;
Controller
DepartmentController
@RestController
@RequestMapping("/system/basic/department")
public class DepartmentController {
@Autowired
DepartmentService departmentService;
@GetMapping("/")
public List<Department> getAllDepartments(){
return departmentService.getAllDepartments();
}
@PostMapping("/")
public RespBean addDep(@RequestBody Department dep){
departmentService.addDep(dep);
if (dep.getResult()==1){
return RespBean.ok("添加成功",dep);
}
return RespBean.error("添加失败");
}
@DeleteMapping("/{id}")
public RespBean deleteDepById(@PathVariable Integer id){
Department dep=new Department();
dep.setId(id);
departmentService.deleteDepById(dep);
//对应下面的deleteDep返回的result
if (dep.getResult()==-2){
return RespBean.error("该部门下有子部门,删除失败");
}else if (dep.getResult()==-1){
return RespBean.error("该部门下有员工,删除失败");
}else if (dep.getResult()==1){
return RespBean.ok("删除成功");
}
return RespBean.error("删除失败");
}
}
Service
DepartmentService
@Service
public class DepartmentService {
@Autowired
DepartmentMapper departmentMapper;
public List<Department> getAllDepartments() {
return departmentMapper.getAllDepartmentsByParentId(-1);
}
public void addDep(Department dep) {
dep.setEnabled(true);
departmentMapper.addDep(dep);
}
public void deleteDepById(Department dep) {
departmentMapper.deleteDepById(dep);
}
}
Mapper
DepartmentMapper
void addDep(Department dep);
void deleteDepById(Department dep);
XML
DepartmentMapper.xml
<resultMap id="DepartmentWithChildren" type="org.javaboy.vhr.model.Department" extends="BaseResultMap" >
<collection property="children" ofType="org.javaboy.vhr.model.Department"
select="org.javaboy.vhr.mapper.DepartmentMapper.getAllDepartmentsByParentId" column="id"/>
</resultMap>
<select id="deleteDepById" statementType="CALLABLE">
//调用存储过程deleteDep
call deleteDep(#{id ,mode=IN,jdbcType=INTEGER},#{result,mode=OUT,jdbcType=INTEGER})
</select>
<select id="addDep" statementType="CALLABLE">
call addDep(#{name,mode=IN,jdbcType=VARCHAR},#{parentId,mode=IN,jdbcType=INTEGER},
#{enabled,mode=IN,jdbcType=BOOLEAN},#{result,mode=OUT,jdbcType=INTEGER},#{id,mode=OUT,jdbcType=INTEGER})
</select>
<select id="getAllDepartmentsByParentId" resultMap="DepartmentWithChildren">
select * from department where parentId=#{pid}
</select>
存储过程解析
两个存储过程在我们当初导入SQL脚本的时候已经建好了,我们来分析一下。
addDep
//创建addDep存储过程,三个输入参数depName、parentId、enabled,两个输出参数result、result2
CREATE DEFINER=`root`@`localhost` PROCEDURE `addDep`(in depName varchar(32),in parentId int,in enabled boolean,out result int,out result2 int)
begin
//声明两个参数
declare did int;
declare pDepPath varchar(64);
//把三个输入参数插入到department表中
insert into department set name=depName,parentId=parentId,enabled=enabled;
//把刚才插入操作受影响的行数赋值给result,为1则表示插入成功,0表示失败
select row_count() into result;
//把department表中新添加的一行的id赋值给did
select last_insert_id() into did;
set result2=did;
select depPath into pDepPath from department where id=parentId;
//更新depPath为pDepPath.did
update department set depPath=concat(pDepPath,'.',did) where id=did;
//设置isParent为true
update department set isParent=true where id=parentId;
end
deleteDep
CREATE DEFINER=`root`@`localhost` PROCEDURE `deleteDep`(in did int,out result int)
begin
declare ecount int;
declare pid int;
declare pcount int;
declare a int;
//创建a表,从department表中选择行插入到a表中 条件:id等于did且没有子部门
select count(*) into a from department where id=did and isParent=false;
//没有行被选中 说明有子部门,删除失败
if a=0 then set result=-2;
else
//a表中有数据,继续在employee表中查找员工并放入ecount中
select count(*) into ecount from employee where departmentId=did;
//大于零说明有员工,删除失败
if ecount>0 then set result=-1;
else
// 把parentId传给pid
select parentId into pid from department where id=did;
delete from department where id=did and isParent=false;
//删除的行数为1
select row_count() into result;
select count(*) into pcount from department where parentId=pid;
//如果没有子部门,isParent设置为false
if pcount=0 then update department set isParent=false where id=pid;
end if;
end if;
end if;
end