crm系统管理模块
前后端分离模式
项目结构
前台
后台
crm系统总体模块
1,营销管理模块
营销管理模块包含对潜在客户的的建立管理和对潜在客户开发过程的管理。
市场经理和市场专员都可以创建潜在用户,通过潜在客户表PotentialCustomer进行录入,
市场部经理可以通过表(CutomerTransfer)进行潜在客户移交到特定市场专员,
市场专员对分配给自己的潜在客户制定开发计划(CustomerDevPlan),力争拿下潜在客户,变为正式客户。
2,客户管理模块
市场专员对分配给自己的潜在客户制定开发计划(表CustomerDevPlan),市场专员收集客户详细信息,建立客户表customer,后期市场专员跟进客户(CustomerTraceHistory),
市场部经理可以通过表(CutomerTransfer)进行客户移交到特定市场专员,
潜在客户长期无法发展为正式客户的可以移到客户资源池管理(CustomerResourcePool)
3系统管理模块
租户管理,根据不同的租户id,所查看的数据也不同,租户在注册时可以选择不同的套餐,
员工管理,公司的所有用户都在此表,由人事专员管理,人事专员页属于员工.市场专员,系统管理员,超级管理员,
管理员对公司部门进行管理维护维护,管理员也属于员工,
超级管理员对系统资源和角色,系统菜单,系统日志.
4.订单合同管理模块
潜在客户交定金后,需要签订(定金)订单,营销人员通过订单和一些订单明细与客户建立关系,订金订单签订后需要生成对应的合同.
项目分工处理
我主要负责客户管理模块
市场专员对分配给自己的潜在客户制定开发计划(表CustomerDevPlan),市场专员收集客户详细信息,建立客户表customer,后期市场专员跟进客户(CustomerTraceHistory),
市场部经理可以通过表(CutomerTransfer)进行客户移交到特定市场专员,
潜在客户长期无法发展为正式客户的可以移到客户资源池管理(CustomerResourcePool)
数据库建表
连表查询
后台代码
公共层
public interface BaseMapper<T> {
//增删改查
//新增
public void save(T t);
//修改
public void update(T t);
//删除 Long
public void delete(Serializable id);
//查询
public T loadById(Serializable id);
//查询所有
public List<T> findAll();
//查询所有的条数
Long queryCount(BaseQuery baseQuery);
//查询当前页数据
List<T> queryData(BaseQuery baseQuery);
}
//把其他的domain公共的内容抽取到这里面
public class BaseDomain {
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
public class BaseQuery {
//当前页
private Long page=1L;
//每页显示的条数
private Long pageSize = 5L;
//公共字段 表示关键字
private String keywords;
public Long getPage() {
return page;
}
public void setPage(Long page) {
this.page = page;
}
public Long getPageSize() {
return pageSize;
}
public void setPageSize(Long pageSize) {
this.pageSize = pageSize;
}
public String getKeywords() {
return keywords;
}
public void setKeywords(String keywords) {
this.keywords = keywords;
}
public Long getStart(){
return (this.page-1)*this.pageSize;
}
}
public interface IBaseService<T> {
//增删改查
//新增
public void save(T t);
//修改
public void update(T t);
//删除 Long
public void delete(Serializable id);
//查询
public T loadById(Serializable id);
//查询所有
public List<T> findAll();
//高级和分页的方法
PageList query(BaseQuery baseQuery);
}
工具类
public class PageList<T> {
private Long total;//总的条数
private List<T> rows = new ArrayList(); //当前页查询数据
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
public List<T> getRows() {
return rows;
}
public void setRows(List<T> rows) {
this.rows = rows;
}
public PageList(Long total, List<T> rows) {
this.total = total;
this.rows = rows;
}
public PageList(){}
}
public class AjaxResult {
private boolean success=true;
private String msg = "操作成功";
public boolean isSuccess() {
return success;
}
public AjaxResult setSuccess(boolean success) {
this.success = success;
return this;
}
public String getMsg() {
return msg;
}
public AjaxResult setMsg(String msg) {
this.msg = msg;
return this;
}
//链式编程
public static AjaxResult me(){
return new AjaxResult();
}
public static void main(String[] args) {
AjaxResult.me().setMsg("操作失败").setSuccess(false);
AjaxResult.me();
}
}
domain层就不写,知道需要的类就行了
mapper层
主要写sql语句以及多对一,一对多等关联查询
<mapper namespace="cn.itsource.crm.mapper.CustomerMapper">
<select id="findAll" resultType="Customer">
select * from t_customer
</select>
<insert id="save" parameterType="Customer">
insert into t_customer(id,name,age,gender,tel,email,qq,wechat,seller,jobname,level,coustomersource,inputuser)
values(#{id},#{name},#{age},#{gender},#{tel},#{email},#{qq},#{wechat},#{seller},#{jobname.id},#{level.id},#{coustomersource},#{inputuser})
</insert>
<delete id="delete" parameterType="long">
delete from t_customer where id=#{id}
</delete>
<update id="update" parameterType="Customer">
update t_customer set name=#{name},age=#{age},gender=#{gender},tel=#{tel},email=#{email},qq=#{qq},wechat=#{wechat},seller=#{seller},jobname=#{jobname.id},level=#{level.id},coustomersource=#{coustomersource},inputuser=#{inputuser}
where id=#{id}
</update>
<select id="loadById" parameterType="long" resultType="Customer">
select * from t_customer where id=#{id}
</select>
<sql id="whereSql">
<where>
<if test="keywords != null and keywords != ''">
and c.name like concat('%',#{keywords},'%')
</if>
</where>
</sql>
<!-- 查询总的记录数-->
<select id="queryCount" parameterType="CustomerQuery" resultType="long">
select count(*)
from t_customer c
<include refid="whereSql"></include>
</select>
<select id="queryData" parameterType="CustomerQuery" resultMap="CustomerMap">
SELECT
c.id,
c.name,
c.age,
c.gender,
c.tel,
c.email,
c.qq,
c.wechat,
c.seller,
d.id eid,
d.name dname,
s.id sid,
s.level sname,
c.coustomersource,
c.inputuser
FROM
t_customer c
LEFT JOIN d_job d ON c.job = d.id
LEFT JOIN d_salary_level s ON c.salarylevel = s.id
<include refid="whereSql"/>
limit #{start},#{pageSize}
</select>
<resultMap id="CustomerMap" type="Customer">
<!--基本信息-->
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="age" column="age"></result>
<result property="gender" column="gender"></result>
<result property="tel" column="tel"></result>
<result property="email" column="email"></result>
<result property="qq" column="qq"></result>
<result property="wechat" column="wechat"></result>
<result property="seller" column="seller"></result>
<result property="coustomersource" column="coustomersource"></result>
<result property="inputuser" column="inputuser"></result>
<!--关联信息-->
<association property="jobname" javaType="Job">
<id property="id" column="eid"></id>
<result property="name" column="dname"></result>
</association>
<association property="level" javaType="Salarylevel">
<id property="id" column="sid"></id>
<result property="level" column="sname"></result>
</association>
</resultMap>
</mapper>
service层没撒好说的
controller层主要写路径方法和请求方式
这里要主要使用注解**@CrossOrigin**来达成前后端结合
示例代码
@Controller
@RequestMapping("/customer")
@CrossOrigin
public class CustomerController {
@Autowired
private ICustomerService customerService;
@RequestMapping(method = RequestMethod.PATCH)
@ResponseBody
@CrossOrigin
public List list(){
return customerService.findAll();
}
@RequestMapping(value="/query",method = RequestMethod.PATCH)
@ResponseBody
@CrossOrigin
public PageList<Customer> query(@RequestBody CustomerQuery customerQuery) {
System.out.println("------------");
return customerService.query(customerQuery);
}
//json格式过来 @RequestBody 接收json数据
@ResponseBody
@RequestMapping(value="/save",method = RequestMethod.PUT)
@CrossOrigin
public AjaxResult save(@RequestBody Customer customer){
try {
customerService.save(customer);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setMsg("操作失败").setSuccess(false);
}
return AjaxResult.me();
}
@ResponseBody
@RequestMapping(value="/update",method = RequestMethod.PUT)
public AjaxResult update(@RequestBody Customer customer){
try {
customerService.update(customer);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setMsg("操作失败").setSuccess(false);
}
return AjaxResult.me();
}
// /employee/id=1 /employee/1
// 编译的问题
@RequestMapping(value="{id}",method = RequestMethod.DELETE)
@ResponseBody
public AjaxResult delete(@PathVariable("id") Long id){
System.out.println("---------------------------------------");
try {
customerService.delete(id);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setMsg("操作失败").setSuccess(false);
}
return AjaxResult.me();
}
@RequestMapping(value="{id}",method = RequestMethod.GET)
@ResponseBody
public AjaxResult findOne(@PathVariable("id") Long id){
System.out.println("---------------------------------------");
try {
Customer customer = customerService.loadById(id);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setMsg("操作失败").setSuccess(false);
}
return AjaxResult.me();
}
}
前台页面使用elementui完成
前台会用就行了,注意路由的引入和main.js的配置
升级最新版注意详情
要改
import 'element-ui/lib/theme-chalk/index.css'
配置路径达成前后台合并
import axios from 'axios';
//设置axios请求基本路径 每次发送请求前面都要添加该路径 http://localhost/department
axios.defaults.baseURL='http://localhost';
Vue.prototype.$http=axios;
前台页面展示代码
<template>
<section>
<!--工具条-->
<el-col :span="24" class="toolbar" style="padding-bottom: 0px;">
<el-form :inline="true" :model="filters">
<el-form-item>
<el-input v-model="filters.keywords" placeholder="关键字"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" v-on:click="getCustomers">查询</el-button>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleAdd">添加</el-button>
</el-form-item>
</el-form>
</el-col>
<!--列表-->
<el-table :data="customers" highlight-current-row v-loading="listLoading" @selection-change="selsChange" style="width: 100%;">
<el-table-column type="selection" width="55">
</el-table-column>
<el-table-column type="index" width="60">
</el-table-column>
<!--<el-table-column prop="id" label="id" width="120" sortable>
</el-table-column>-->
<el-table-column prop="name" label="客户姓名" sortable>
</el-table-column>
<el-table-column prop="age" label="客户年龄" sortable>
</el-table-column>
<el-table-column prop="gender" label="客户性别" sortable>
</el-table-column>
<el-table-column prop="tel" label="电话号码" sortable>
</el-table-column>
<el-table-column prop="email" label="邮箱" sortable>
</el-table-column>
<el-table-column prop="qq" label="QQ" sortable>
</el-table-column>
<el-table-column prop="wechat" label="微信" sortable>
</el-table-column>
<el-table-column prop="seller" label="营销人员" sortable>
</el-table-column>
<el-table-column prop="jobname.name" label="职业" sortable>
</el-table-column>
<el-table-column prop="level.level" label="收入水平" sortable>
</el-table-column>
<el-table-column prop="coustomersource" label="客户来源" sortable>
</el-table-column>
<el-table-column prop="inputuser" label="创建人" sortable>
</el-table-column>
<el-table-column label="操作" width="150">
<template scope="scope">
<el-button size="small" @click="handleEdit(scope.$index, scope.row)">修改</el-button>
<el-button type="danger" size="small" @click="handleDel(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--工具条-->
<el-col :span="24" class="toolbar">
<el-pagination layout="prev, pager, next" @current-change="handleCurrentChange" :page-size="5" :total="total" style="float:right;">
</el-pagination>
</el-col>
<!--编辑界面-->
<el-dialog title="编辑" v-model="customerFormVisible" :visible.sync="customerFormVisible" :close-on-click-modal="false">
<el-form :model="customer" label-width="80px" :rules="customerFormRules" ref="customerForm">
<el-form-item label="客户姓名" prop="name">
<el-input v-model="customer.name" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="客户年龄" prop="age">
<el-input v-model="customer.age" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="客户性别" prop="gender">
<el-input v-model="customer.gender" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="电话号码" prop="tel">
<el-input v-model="customer.tel" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="customer.email" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="QQ" prop="qq">
<el-input v-model="customer.qq" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="微信" prop="wechat">
<el-input v-model="customer.wechat" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="营销人员" prop="seller">
<el-input v-model="customer.seller" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="职业" prop="job">
<el-select v-model="customer.jobname" value-key="name" placeholder="请选择" @change="change()">
<el-option
v-for="item in jobs"
:label="item.name"
:value="item">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="收入水平" prop="level">
<el-select v-model="customer.level" value-key="level" placeholder="请选择" @change="change()">
<el-option
v-for="item in levels"
:label="item.level"
:value="item">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="客户来源" prop="coustomersource">
<el-input v-model="customer.coustomersource" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="创建人" prop="inputuser">
<el-input v-model="customer.inputuser" auto-complete="off"></el-input>
</el-form-item>
<!--<el-transfer v-model="selectedPermissions" :data="allPermissions" :titles="titles" :props="{
key: 'id',
label: 'name'
}" @change="handleChange">
</el-transfer>-->
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click.native="customerFormVisible = false">取消</el-button>
<!-- native支持原生的js注册事件方法 onclick = xxx() elementui/vue method写的方法-->
<el-button type="primary" @click="editSubmit" :loading="editLoading">提交</el-button>
</div>
</el-dialog>
</section>
</template>
<script>
export default {
data() {
return {
filters: {
keywords:'',
name: '',
age:'',
gender:'',
tel:'',
email:'',
qq:'',
wechat:'',
seller:'',
jobname:'',
level:'',
coustomersource:'',
inputuser:'',
},
customers: [],
jobs:[],
levels:[],
selectedPermissions:[],
allPermissions:[{"id":1,"name":"添加员工"},{"id":2,"name":"修改员工"},{"id":3,"name":"删除员工"}],
titles:['所有权限', '已选权限'],
total: 0,
page: 1,
listLoading: false,
sels: [],//列表选中列
customerFormVisible: false,//编辑界面是否显示
editLoading: false,
customerFormRules: {
name: [
//部门名称失去焦点的时候,触发验证规则
{ required: true, message: '请输入部门名称', trigger: 'blur' }
]
},
//编辑界面数据
customer: {
id: 0,
name: '',
age:'',
gender:'',
tel:'',
email:'',
qq:'',
wechat:'',
seller:'',
jobname:{},
level:'',
coustomersource:'',
inputuser:'',
}
}
},
methods: {
//性别显示转换
formatSex: function (row, column) {
console.debug(row.gender)
return row.gender == true ? '男' : row.gender == false ? '女' : '未知';
},
handleCurrentChange(val) {
this.page = val;
this.getCustomers();
},
//获取用户列表
getCustomers() {
//参数
let para = {
page: this.page,//获取当前页
keywords: this.filters.keywords
};
//刷新效果
this.listLoading = true;
//发送请求 axios请求 -- 全部axios http://localhost/customer
this.$http.patch('/customer/query',para).then(res=>{
// getUserListPage(para).then((res) => {
console.log(res);
this.total = res.data.total;
this.customers = res.data.rows;
this.listLoading = false;
//NProgress.done();
});
},
//删除
handleDel: function (index, row) {
this.$confirm('确认删除该记录吗?', '提示', {
type: 'warning'
}).then(() => {
//开启 加载中
this.listLoading = true;
//获取删除的id
this.$http.delete('/customer/'+row.id).then((res) => {
//{'success':true/false,'msg':'xxxx'}
this.listLoading = false;
if(res.data.success){
this.$message({
message: '删除成功',
type: 'success'
});
}else{
this.$message({
message: '删除失败',
type: 'error'
});
}
//查询数据
this.getCustomers();
});
}).catch(() => {
});
},
//显示编辑界面 弹出编辑框
handleEdit: function (index, row) {
//打开对话框
this.customerFormVisible = true;
//回显作用 拷贝row到新的{}对象
this.customer = Object.assign({}, row);
this.getJob();
this.getSalaryLevel();
},
//显示新增界面
handleAdd: function () {
//控制是否弹出对话框
this.customerFormVisible=true;
//清空表单数据
this.customer = {
name: ''
};
//查询部门经理
this.getJob();
this.getSalaryLevel();
},
getJob(){
this.$http.patch("/job").then(res=>{
console.log(res.data);
this.jobs = res.data;
})
},
getSalaryLevel(){
this.$http.patch("/salarylevel").then(res=>{
console.log(res.data);
this.levels = res.data;
})
},
//新增 修改 保存
editSubmit: function () {
//必须验证通过之后,才执行下面的代码
this.$refs.customerForm.validate((valid) => {
if (valid) {
//询问你 是否要提交
this.$confirm('确认提交吗?', '提示', {}).then(() => {
//显示加载 圈
this.editLoading = true;
//备份 表单里面数据 备分一份给para变量
let para = Object.assign({}, this.customer);
let jobname = {
id:para.jobname
}
let level = {
id:para.level
}
//封装产生
para.jobname = jobname;
para.level = level;
//发送ajax请求
//editUser(para).then((res) => {
let url = '/customer/save';
if(this.customer.id){
console.log(this.customer.id);
url='/customer/update';
}
this.$http.put(url,para).then(res=>{
//关闭滚动圈
this.editLoading = false;
this.$message({
message: '提交成功',
type: 'success'
});
//重置表单信息 为null
this.$refs['customerForm'].resetFields();
//关闭对话框
this.customerFormVisible = false;
//重心查询数据
this.getCustomers();
});
});
}
});
},
//新增
addSubmit: function () {
this.$refs.addForm.validate((valid) => {
if (valid) {
this.$confirm('确认提交吗?', '提示', {}).then(() => {
this.addLoading = true;
//NProgress.start();
let para = Object.assign({}, this.addForm);
para.birth = (!para.birth || para.birth == '') ? '' : util.formatDate.format(new Date(para.birth), 'yyyy-MM-dd');
addUser(para).then((res) => {
this.addLoading = false;
//NProgress.done();
this.$message({
message: '提交成功',
type: 'success'
});
this.$refs['addForm'].resetFields();
this.addFormVisible = false;
this.getCustomers();
});
});
}
});
},
selsChange: function (sels) {
this.sels = sels;
},
//批量删除
batchRemove: function () {
var ids = this.sels.map(item => item.id).toString();
this.$confirm('确认删除选中记录吗?', '提示', {
type: 'warning'
}).then(() => {
this.listLoading = true;
//NProgress.start();
let para = { ids: ids };
batchRemoveUser(para).then((res) => {
this.listLoading = false;
//NProgress.done();
this.$message({
message: '删除成功',
type: 'success'
});
this.getCustomers();
});
}).catch(() => {
});
}
},
mounted() {
this.getCustomers();
}
}
</script>
<style scoped>
</style>
最后展示效果展示图
新增
修改回显
查询和分页
第一页
第二页
感受,就是觉得自己还不够熟练,有很多还是不懂,希望能尽快弄懂,前端感觉写起来很痛苦,多加练习,团队合作很重要,一定要听从安排,不要一意孤行,不懂得小组多讨论,才能做得更好