springboot|Springboot+Vue+Element实现的CRM管理系统


文章目录

    • 项目运行效果
    • 前言
    • 项目介绍
    • 项目搭建,
    • 登录功能
    • 主界面
    • 用户管理之分页展示数据
    • 用户管理之删除数据
    • 用户管理之添加用户
    • 用户管理之编辑用户
    • 积分管理之积分兑换

项目运行效果 springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

前言 有正在学习springboot+vue开发的小伙伴可评论区留言,我们一起学习交流,或者对springboot+vue开发有疑问的小伙伴也可一起交流解决。文章主要讲解前后端之间的通信,交互,对后端控制层以外的代码不做过多暴露,想要springboot+vue项目源码可私信评论我
项目介绍 本文主要记录使用springboot和vue进行整合开发流程,实现前后端分离,具体步骤和知识点都会讲解记录或者是在注释中进行说明。使用Element工具提供组件,实现(CRM)客户关系管理的一个系统。开发工具使用Idea和Vscode,使用vue-cli搭建项目,实现前后端分离。下文将对项目的用户管理模块和积分管理模块进行讲解记录,管理员可对用户进行添加,删除,修改,查询等功能。积分模块在用户管理模块的基础上多了积分兑换功能,因此积分模块只对积分兑换功能进行讲解。后端控制层使用REST风格提供接口
项目搭建, 如果已经安装过vue-cli脚手架可跳过这一步,使用Idea的搭建springboot就不再叙述了**
第一步::全局安装脚手架工具:npm install -g vue-cli
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

创建成功
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

第二步:创建工程:访问到目标文件夹,点击地址栏切换到cmd,输入命令:vue init webpack vue-website。 vue-website是工程名
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片
第三步:启动工程:npm run dev
自此vue-cli的项目搭建完成,系统自动产生这样的文件夹,使用Vscode去打开文件夹即可。
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片
项目搭建完成,接下来进行开发
登录功能 效果展示
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

后端
使用@RequestMapping("/api/user")进行窄化路径,controller接口:
@PostMapping("/login") public Map login(@RequestBody User cuser){ Map map = new HashMap(); System.out.println(cuser); User user = userService.login(cuser.getUloginid(),cuser.getUpassword()); if(user!=null) { map.put("status", 200); map.put("msg","登录成功"); map.put("data", user); }else { map.put("status", 0); map.put("msg","登录失败"); map.put("data", user); } return map; }

因此访问全路径为:http://localhost:7070/api/user/login
前端Login.vue


【springboot|Springboot+Vue+Element实现的CRM管理系统】1,model 表单数据对象,在标签中加上这个属性等于"loginFormData"将会找到export default中的 data() 中的 return 中的loginFormData,loginFormData存放的便是后端取得的对象
,2,rules 表单验证规则*,在标签中加上这个属性
表单将会自动绑定到export default中的 data() 中的 return 中的rules
3,prop,传入 Form 组件的 model 中的字段,由于model中绑定的是loginFormData,因此图中会找到loginFormData中的uloginid字段
4,v-model=“loginFormData.upassword”,在标签中使用它是的输入框的值与该字段绑定
5,show-password在标签中使用它使得输入框后面出现一个眼睛可以展示或者隐藏输入框的内容,通常用在密码框
export default { data() { return { loginFormData:{ uloginid:'', upassword:'' }, rules:{ uloginid:[ { required: true, message: '账号不能为空', trigger: 'blur' }, { min:3, max:10, message: '账号长度在 3 到 10 个字符之间', trigger: 'blur' } ], upassword:[ { required: true, message: '密码不能为空', trigger: 'blur' }, { min: 3, max:10, message: '密码长度在 3 到 10 个字符之间', trigger: 'blur' } ] } } },

6,resetField 对该表单项进行重置,将其值重置为初始值并移除校验结果,使用方法:@click=“resetForm”
重置表单

在 methods:{ }定义该方法即可。
resetForm(){ this.$refs.loginFormRef.resetFields(); }

核心方法
用户登录

在用户登录按钮上绑定方法@click=“userLogin”,在methods:{ }中定义该方法,
实现userLogin方法一:
如果控制层使用对象来接收前端,需在控制层方法的参数区使用@RequestBody来反序列化,将json转成对象以供后端使用,在userLogin中实现步骤如下:
1,在验证通过后再触发ajax异步登录,
2,ajax 完成登录验证
3,动态通过路由管理器切换组件
4,在sessionStorage 对象中存用户的身份信息;
详细步骤在代码中已经注释
methods:{ userLogin(){ //必须在验证通过后再触发ajax异步登录, //validate 任一表单项被校验后触发 被校验的表单项 prop 值,校验是否通过,错误消息(如果存在) this.$refs.loginFormRef.validate(async valid=>{ if(!valid){ //验证未通过,在页面给出验证未通过提示信息. this.$message.error('抱歉,表单验证未通过,登录未提交'); return; } //ajax 完成登录验证,后端用对象接收,已经在main.js中//设置ajax请求的基地址 //axios.defaults.baseURL='http://localhost:7070/api/',因此在post中只需要加上'user/login'即可。 //this.loginFormData传入前端绑定的用户名,密码组成的对象,把调用后端的返回结果data赋值给res const{data:res}= await this.$axios.post('user/login',this.loginFormData); //返回一个Promise对象 if(res.status==0) { this.$message.error('抱歉,登录失败'); return; } this.$message.success('登录成功........'); //动态通过路由管理器切换组件 this.$router.push('/home') //在sessionStorage 对象中存用户的身份信息; sessionStorage.setItem('cuser',res.data.uname) });

实现userLogin方法二:
控制层通过参数接收前端
1,在export default加上import Qs from ‘qs’
2,把 const {data:res}= await this.$axios.post(‘user/login’,this.loginFormData); //返回一个Promise对象换成以下代码即可
//后端通过参数接收前端,记得先导入import Qs from 'qs' const{data:res}= await this.$axios({ method:'post', url:'user/login', data:Qs.stringify(this.loginFormData) })

切换到主界面

this.$router.push('/home')

使用它之前需要在route中配置路由,因此在登录成功之后会切换到主界面。在主界面(/home)路由中有user这个子路由。
import Vue from 'vue' import Router from 'vue-router' //import HelloWorld from '@/components/HelloWorld' import Login from '@/components/Login.vue' import Home from '@/components/Home.vue' import User from '@/components/User.vue' Vue.use(Router)export default new Router({ routes: [ {path: '/login', name: 'Login',component: Login}, {path: '/',redirect:'/login'}, {path:'/home',name:'Home',component:Home,children:[ {path: '/user', name: 'User',component: User}, ]},] })

主界面 完成登录之后动态通过路由管理器切换组件到主界面,采用Element的导航,
效果展示
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

collapse 是否水平折叠收起菜单(仅在 mode 为 vertical 时可用)
menuData:[ { mid:1001, mtitle:'用户管理', micon:'el-icon-user-solid', children:[ {sid:100101,stitle:'用户列表',sicon:'el-icon-s-custom',path:'/user'}, {sid:100102,stitle:'导出用户',sicon:'el-icon-s-cooperation',path:'/quill'}, {sid:100103,stitle:'导入用户',sicon:'el-icon-s-order',path:'/uimport'} ] }

在home界面的导航中提供一个menuData为用户管理,用户管理中有子组件,user,因此点击用户列表可跳转到User.vue
用户管理之分页展示数据 效果展示
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

后端:
先在实体类中加入一个分页的类,startindex代表起始页,offset代表每页中的记录条数
在Mapper中

在service中,主要步骤已经在代码中进行注释
//最终返回一个pager对象 @Override public Pager pager(int pageindex, int offset,String searchkey) { //new 一个pager对象 Pager pager = new Pager(); //new 一个map Map map =new HashMap(); //将startindex起始页放入map中,每一次的startindex都是不同的 map.put("startindex", (pageindex-1) * offset); //将offset每页记录数放入map中 map.put("offset", offset); //获取当前页数据; List data= https://www.it610.com/article/userMapper.userpager(map); //对pager中的五个属性执行set方法 pager.setData(data); pager.setPageindex(pageindex); pager.setPagesize(offset); int totals = userMapper.count(searchkey); //得到总记录数; pager.setTotals(totals); int pages = totals % offset ==0 ? totals/offset : totals/offset+1; pager.setPages(pages); return pager; }

controller层中
@GetMapping("/list") public Pager userPager(int pageindex, int offset,String searchKey){ System.out.println(searchKey); return userService.pager(pageindex, offset,searchKey); }

自此后端代码结束,提供了list接口供前端调用
前端User.vue
1,created():在创建vue对象时,当html渲染之前触发;但是注意,全局vue.js不强制刷新或者重启时只创建一次,也就是说,created()只会触发一次;
2,activated():在vue对象存活的情况下,进入当前存在activated()函数的页面时,一进入页面就触发;可用于初始化页面数据、keepalive缓存组件后,可执行方法;
3,deactivated():离开组件时执行;注意:activated()和deactivated()只有在包裹的时候才有效;
在methods中定义函数去调用后端接口并得到返回结果
定义好要给后端传递的参数
export default { data(){ return{ pageArgs:{ pageindex:1, offset:5 }, totals:0, userData:[], deptData:[] } }, ```bash

将参数放入getuserdata()方法中去请求后端,得到返回结果
methods:{ asyncgetuserdata(){ //{params:this.pageArgs}:传数据回后端 const {data:res} =await this.$axios.get('user/list',{params:this.pageArgs}); //把查询结果赋值给 userData:[] this.userData = https://www.it610.com/article/res.data; this.totals = res.totals; //总记录数; } }

通过created去调用getuserdata()方法
created(){ //一般异步取后端数据; this.getuserdata(); },

4,prop="uloginid"绑定数据库uloginid字段,用于表格展示数据

5,日期格式化,在表格中有提供formatter 属性
formatter 用来格式化内容 Function(row, column, cellValue, index)

在methods方法中定义
dateFormat(row,column,cellValue,index){ //row:当前绑定的行对象,column:列,cellvalue:单元格值,index:索引 let now = new Date(cellValue); return `${now.getFullYear()}-${now.getMonth()+1}-${now.getDate()}`; },

分页
1,total 总条目数
2,page-size 每页显示条目个数,支持 .sync 修饰符
3,page-sizes 每页显示个数选择器的选项设置
4,current-page 当前页数,支持 .sync 修饰符
5,size-change pageSize(每页记录数) 改变时会触发
6,current-change currentPage (当前页)改变时会触发
分页导航

切换每页记录数触发事件,重新修改每页记录数再去调用获取每页数据的方法
handleSizeChange(nsize){ //当pagesize大小改为,重新分页,展示数据; this.pageArgs.offset = nsize; //改pagesize; this.getuserdata(); },

切换页码时重新调用获取每页数据方法
handleCurrentChange(npageindex){ //当切换页面码,显示当前页数据 this.pageArgs.pageindex = npageindex; this.getuserdata(); },

表格绑定userData里面的数据

难点:
查询用户表,用户表中的有一个部门字段,里面1,2,3,保存着部门表的主键,需要查询部门表,将对应着的部门名称显示出来
在表格中scope.row.deptno得到用户表中的部门编号字段,需要经过一个deptFilter过滤器的处理得到部门名称

在methods中定义方法去获取后端提供的接口去获取部门把查询结果赋值给deptData,事先已经在data中定义了deptData[]
async getAllDepts(){ const {data:res} =await this.$axios.get('dept/list'); this.deptData = https://www.it610.com/article/res; },

在export default中定义过滤器 deptFilter(ovalue)根据部门编号找到部门名称
filters:{ deptFilter(ovalue){ return that.deptData.find(item=>item.deptno==ovalue).dname; ; } }

注意:需要在export default 之外定义var that; 然后在created方法中把this给that,this的作用域就变大,才可在过滤器中使用
created(){ //一般异步取后端数据; this.getAllDepts(); this.getuserdata(); //分页获取用户数据; that = this; },

用户管理之删除数据 效果展示
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

控制层中写好@DeleteMapping("/delete/{uid}")供前端调用,需要前端传递要删除的记录的id
@DeleteMapping("/delete/{uid}") public Map remove(@PathVariable(name = "uid") int uid){ Map map = new HashMap(); int affter =userService.removeUser(uid); if(affter>0) { map.put("status", 200); map.put("msg", "删除成功"); }else { map.put("status", 0); map.put("msg", "删除失败"); } return map; }

前端
在表格中提供删除的按钮

通过@click="removeUser(scope.row.uid)"调用methods中的方法,scope.row.uid:取得该记录的id
async removeUser(uid){ //需要做删除确认; const val = await this.$confirm('确认删除本条数据?','删除确认',{confirmButtonText:'确认',cancelButtonText:'取消',type:'warning'}).catch(e=>e); if(val=='cancel'){ this.$message('你取消了删除操作'); return; } // 如果点击了取消以下的代码就不会执行,点击确定就会调用后端提供的接口 const {data:res} = await this.$axios.delete('user/delete/'+uid); if(res.status==200){ //删除成功; this.$message.success('删除成功........'); //刷新数据 this.getuserdata(); }else{ this.$message.error('删除失败.....'); } },

用户管理之添加用户 效果展示
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

后端
在控制层中提供接口实现添加功能,前端提供user的json数据,后端需要用到@RequestBody将其反序列化为user对象
@PostMapping("/insert") public Map insert(@RequestBody User user){ Map map = new HashMap(); int affter =userService.insertUser(user); if(affter>0) { map.put("status", 200); map.put("msg", "添加成功"); }else { map.put("status", 0); map.put("msg", "添加失败"); } return map; }

前端
使用@click="inAddUser"为添加用户按钮提供一个单击事件
添加用户

在inAddUser中将addDialogVisible设置为true,默认为false,此对话框不显示,设置为true后显示,即被添加按钮被单击后弹出对话框
inAddUser(){ //设计一个独立的添加组件 //this.$router.push('/adduser') //做一个对话框,默认隐藏,通过此按钮显示出来; this.addDialogVisible=true; },

addDialogVisible设置为true后,:visible.sync=“addDialogVisible”,即可弹出对话框,在对话框中内嵌表单
"footer" class="dialog-footer"> 取 消 添 加

在表单中:model="addFormData"绑定addFormData的值

在data的return中的addFormData中定义表单中对应要添加的数据,每一项应该与数据库中的字段名一样。
return{ //对话框默认为false不弹出 addDialogVisible:false, updateDialogVisible:false, pageArgs:{ pageindex:1, offset:5, searchKey:'' }, totals:0, userData:[], deptData:[], addFormData:{ uloginid:'', upassword:'', utel:'', uaddress:'', uname:'', uemail:'', deptno:'', udate:'', ustateid:false },

在表单中的每一个输入框通过v-model="addFormData.utel"来与addFormData中定义的数据绑定。
此prop="uloginid"是与rules中的uloginid验证规则绑定

addRole:{ uloginid:[ { required: true, message: '账号不能为空', trigger: 'blur' }, { min:3, max:10, message: '账号长度在 3 到 10 个字符之间', trigger: 'blur' } ], upassword:[ { required: true, message: '账号不能为空', trigger: 'blur' }, { min:3, max:10, message: '账号长度在 3 到 10 个字符之间', trigger: 'blur' } ], utel:[ { validator: checkTel, trigger: 'blur' }, ], uname:[ { required: true, message: '账号不能为空', trigger: 'blur' }, ], uemail:[ { validator: checkEmail, trigger: 'blur' }, ] }

在data中自定义验证,在rules中调用该函数
data(){ //自定义验证 var checkTel=(rule, value, callback)=>{ if (value =https://www.it610.com/article/=='') { callback(new Error('请输入电话')); }else{ let reg=/^1(3|7|9|8|5){1}(\d){9}$/; if(!reg.test(value)){ callback(new Error('电话格式不正确')); } } callback(); }; var checkEmail=(rule, value, callback)=>{ if (value =https://www.it610.com/article/=='') { callback(new Error('请输入邮箱')); }else{ let reg=/^(\w){3,}@(\w)+.(cn|com|net|org|gov|cc){1}$/; if(!reg.test(value)){ callback(new Error('邮箱格式不正确')); } } callback(); };

绑定数据和完成验证后,点击添加按钮触发insertUser(),调用后端接口。根据返回值的status判断是否添加成功。具体步骤在代码中已经注释
async insertUser(){ //验证通过后才提交服务器; this.$refs.addFormRef.validate(async valid=>{ if(!valid){ //验证未通过,在页面给出验证未通过提示信息. this.$message.error('抱歉,表单验证未通过,未提交数据到服务器....'); return; } //实现添加 const {data:res} =await this.$axios.post('user/insert',this.addFormData); //返回一个Promise对象 if(res.status==200){ this.$message.success('添加成功........'); //重置表单 this.$refs.addFormRef.resetFields(); //关闭对话框 this.addDialogVisible=false; //刷新数据; this.getuserdata(); }else{ this.$message.error('添加失败, 请重新添加........'); //重置表单 this.$refs.addFormRef.resetFields(); }});

用户管理之编辑用户 效果展示
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片
后端
在控制层中提供/update接口给前端调用
@PutMapping("/update") public Map updateUser(@RequestBody User user){ Map map = new HashMap(); int affter =userService.updateUser(user); if(affter>0) { map.put("status", 200); map.put("msg", "更新成功"); }else { map.put("status", 0); map.put("msg", "更新失败"); } return map; }

前端
在展示数据的表格中提供编辑的按钮,scope.row那一行数据
编辑

与之对应的edit函数,this.updateDialogVisible=true; 将默认隐藏的对话框弹出,this.updateFormData.uid =user.uid; 给updateFormData赋新值
edit(user){ this.updateDialogVisible=true; //设默认值 ; this.updateFormData.uid =user.uid; //主键,可以不存页面显示,但需要提供; this.updateFormData.uloginid = user.uloginid; this.updateFormData.upassword = user.upassword; this.updateFormData.utel = user.utel; this.updateFormData.uaddress = user.uaddress; this.updateFormData.uname = user.uname; this.updateFormData.uemail = user.uemail; this.updateFormData.deptno = user.deptno; this.updateFormData.udate = user.udate; this.updateFormData.ustateid = user.ustateid; },

弹出的updateDialogVisible对话框
"footer" class="dialog-footer"> 取 消 保存修改

@click=“resetUpdateForm”; 弹出的编辑用户的取消按钮,绑定resetUpdateForm函数
resetUpdateForm(){ //this.$refs.updateFormRef.resetFields(); this.$message.info('取消修改') this.updateDialogVisible=false; },

@click=“updateUser”:弹出的编辑用户的保存修改按钮,绑定updateUser函数,
async updateUser(){ this.$refs.updateFormRef.validate(async valid=>{ if(!valid){ //验证未通过,在页面给出验证未通过提示信息. this.$message.error('抱歉,表单验证未通过,未提交数据到服务器....'); return; }const res = await this.$axios.put('user/update',this.updateFormData); if(res.status==200){ this.$message.success('修改成功......'); this.updateDialogVisible=false; //刷新数据 this.getuserdata(); }else{ this.$message.error('抱歉,修改失败....'); this.updateDialogVisible=false; }}); },

积分管理之积分兑换 功能介绍:
点击操作按钮之后弹出要兑换的礼品的表单,在选择任意一个礼品进行兑换,兑换成功之后,已用积分被修改为原积分加该礼品所需积分,剩余积分被修改为原积分减该礼品所需积分
效果展示
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片
springboot|Springboot+Vue+Element实现的CRM管理系统
文章图片

后端控制层代码
主要逻辑:
1,前端传入两个参数分别是用户表的id和礼品表的id,首先根据礼品表的id查询的到要兑换的礼品所需要的积分。
2,根据用户表id得知对哪个用户进行操作,并查询出该用户已用积分和剩余积分,并且根据步骤一种的查询结果,对已用积分和剩余积分进行重新赋值
3,将被赋值过的已用积分和剩余积分放入map集合中,在Mapper文件中写一条update语句即可。
@PostMapping("/login") public Map login(int vid,int gid){ int gpoint = giftService.GetGiftPoint(gid); int ed = pointService.zkVpointed(vid); int sy = pointService.zkVrestpoints(vid); int vpointed=ed+gpoint; int vrestpoints=sy-gpoint; Map paramMap= new HashMap(); paramMap.put("vpointed",vpointed); paramMap.put("vrestpoints",vrestpoints); paramMap.put("vid",vid); boolean b = pointService.updatePoint(paramMap); Map map = new HashMap(); if(b) { map.put("status", 200); map.put("msg", "更新成功"); }else { map.put("status", 0); map.put("msg", "更新失败"); } return map; }

前端:
定义一个对象里面有两个参数传递给后端
export default { data() { return { zkupdateFormData:{ vid:'',//用户id gid:''//礼品id },

定义获取这两个参数的函数
1,获取用户id,点击按钮触发toexchange(scope.row.vid)