天天看点

CRM -营销管理

1. 学习目标

CRM -营销管理

2. 营销管理表结构分析

3. 营销机会管理功能实现

3.1. 准备工作

3.1.1. 生成代码

通过 mybatis-generator 生成代码。能够生成 JavaBean对象、mapper 映射文件以及 mapper 接口。

<table tableName="t_sale_chance" domainObjectName="SaleChance"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
        </table>
           

3.1.2. IDEA创建MYSQL链接

点击右侧的菜单栏 “DataBase”

3.2. 营销机会数据查询

3.2.1. 页面效果

3.2.2. 后端代码实现

layui 框架通过表格展示后端表数据,数据格式见官网测试数据地址。

3.2.2.1. 查询条件
  • SaleChanceQuery.java

在 crm 包下创建 query 包,新建 SaleChanceQuery.java 查询类,设置对应的查询条件

package com.xxxx.crm.query;

import com.xxxx.crm.base.BaseQuery;

/**
 * 营销机会管理多条件查询条件
 */
public class SaleChanceQuery extends BaseQuery {

    private String customerName; // 客户名称
    private String createMan; // 创建人
    private String state; // 分配状态


    public String getCustomerName() {
        return customerName;
    }

    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }

    public String getCreateMan() {
        return createMan;
    }

    public void setCreateMan(String createMan) {
        this.createMan = createMan;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}
           
3.2.2.2. 设置SQL
  • SaleChanceMapper.xml
<!--多条件分页查询数据-->
  <select id="queryByParams" parameterType="com.xxxx.crm.query.SaleChanceQuery" resultType="saleChance">
    select
    *
    from
    t_sale_chance
    <where>
      is_valid = 1
      <if test="customerName != null and customerName != ''">
        and  customer_name like concat("%",#{customerName},"%")
      </if>
      <if test="createMan != null and createMan != ''">
        and  create_man = #{createMan}
      </if>
      <if test="state != null and state != ''">
        and  state = #{state}
      </if>
    </where>
  </select>
           

3.2.2.3. 接口定义

  • SaleChanceMapper.java
package com.xxxx.crm.dao;

import com.xxxx.crm.base.BaseMapper;
import com.xxxx.crm.query.SaleChanceQuery;
import com.xxxx.crm.vo.SaleChance;

import java.util.List;
import java.util.Map;

public interface SaleChanceMapper extends BaseMapper<SaleChance,Integer> {
    //多条件分页查询数据
    public List<SaleChance> queryByParams(SaleChanceQuery query);

    //查询所有销售人员数据
    //public List<Map<String,Object>> queryAllSales();
}
           
3.2.2.4. Service
  • SaleChanceService.java
package com.xxxx.crm.service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.xxxx.crm.base.BaseService;
import com.xxxx.crm.dao.SaleChanceMapper;
import com.xxxx.crm.query.SaleChanceQuery;
import com.xxxx.crm.utils.AssertUtil;
import com.xxxx.crm.utils.PhoneUtil;
import com.xxxx.crm.vo.SaleChance;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;




import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class SaleChanceService extends BaseService<SaleChance,Integer> {
    @Resource
    private SaleChanceMapper saleChanceMapper;

    /**
     * 多条件查询数据
     * @param saleChanceQuery
     * @return
     */
    public Map<String, Object> queryByParams(SaleChanceQuery saleChanceQuery){
        Map<String, Object> map = new HashMap<>();
        //开启分页
        PageHelper.startPage(saleChanceQuery.getPage(),saleChanceQuery.getLimit());
        List<SaleChance> saleChances = saleChanceMapper.queryByParams(saleChanceQuery);
        //按照分页条件,格式化数据
        PageInfo<SaleChance> saleChancePageInfo = new PageInfo<>(saleChances);

        map.put("code",0);
        map.put("msg","");
        map.put("count",saleChancePageInfo.getTotal());
        map.put("data",saleChancePageInfo.getList());
        return map;
    }
}
           
3.2.2.5. Controller
  • SaleChanceController.java
package com.xxxx.crm.controller;

import com.xxxx.crm.base.BaseController;
import com.xxxx.crm.base.ResultInfo;
import com.xxxx.crm.query.SaleChanceQuery;
import com.xxxx.crm.service.SaleChanceService;
import com.xxxx.crm.utils.AssertUtil;
import com.xxxx.crm.utils.CookieUtil;
import com.xxxx.crm.vo.SaleChance;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping("sale_chance")
public class SaleChanceController extends BaseController {
    @Resource
    private SaleChanceService saleChanceService;

    /**
     * 多条件分页查询数据
     * @param saleChanceQuery
     * @return
     */
    @GetMapping("list")
    @ResponseBody
    public Map<String, Object> queryByParams(SaleChanceQuery saleChanceQuery){
        return saleChanceService.queryByParams(saleChanceQuery);
    }
}
           
3.2.2.6. 时间格式化

在 SaleChance 实体类中的时间字段上添加注解,用来格式化时间

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 
private Date assignTime; 
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 
private Date createDate;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateDate;
           

3.2.3. 前端核心代码

3.2.3.1. 页面模板

在 resources/views/saleChance 目录创建 sale_chance.ftl 模块文件,模板内容如下 (模板依赖的 layui

文件由 common.ftl 文件提供),layui 表格数据展示模板文件实现参考该地址。

  • sale_chance.ftl
<!DOCTYPE html>
<html>
<head>
    <title>营销机会管理</title>
    <#include "../common.ftl">
</head>
<body class="childrenBody">

<form class="layui-form" >
    <blockquote class="layui-elem-quote quoteBox">
        <form class="layui-form">
            <div class="layui-inline">
                <div class="layui-input-inline">
                    <input type="text" name="customerName" class="layui-input searchVal" placeholder="客户名" />
                </div>
                <div class="layui-input-inline">
                    <input type="text" name="createMan" class="layui-input
							searchVal" placeholder="创建人" />
                </div>
                <div class="layui-input-inline">
                    <select name="state"  id="state">
                        <option value="" >分配状态</option>
                        <option value="0">未分配</option>
                        <option value="1" >已分配</option>
                    </select>
                </div>
                <a class="layui-btn search_btn" id="btnSearch" data-type="reload">
                    <i class="layui-icon">&#xe615;</i> 搜索
                </a>
            </div>
        </form>
    </blockquote>

    <!-- 数据表格 -->
    <table id="saleChanceList" class="layui-table"  lay-filter="saleChances">
    </table>

    <#--头部工具栏-->
    <script type="text/html" id="toolbarDemo">
        <div class="layui-btn-container">
            <a class="layui-btn layui-btn-normal addNews_btn" lay-event="add">
                <i class="layui-icon">&#xe608;</i>
                添加
            </a>
            <a class="layui-btn layui-btn-normal delNews_btn" lay-event="del">
                <i class="layui-icon">&#xe608;</i>
                删除
            </a>
        </div>
    </script>


    <!--操作 行工具栏-->
    <script id="saleChanceListBar" type="text/html">
        <a class="layui-btn layui-btn-xs" id="edit" lay-event="edit">编辑</a>
        <a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del">删除</a>
    </script>

</form>

<script type="text/javascript" src="${ctx}/js/saleChance/sale.chance.js"></script>
</body>
</html>
           
3.2.3.2. 页面入口

点击左侧的菜单,进入对应的页面

SaleChanceController 后台设置对应的接口

/**
     * 打开营销机会管理页面
     * @return
     */
    @GetMapping("index")
    public String index(){
        return "saleChance/sale_chance";
    }
           
3.2.3.3. 核心 JS

public/js/saleChance 目录下创建 sale.chance.js 文件,初始化 layui 表格数据,layui 表格数据展示模

板文件实现参考该地址。

sale.chance.js

layui.use(['table','layer'],function(){
    var layer = parent.layer === undefined ? layui.layer : top.layer,
        $ = layui.jquery,
        table = layui.table;

    /**
     * 营销机会列表展示
     */
    var  tableIns = table.render({  //返回表格渲染的唯一标识
        elem: '#saleChanceList', // 表格绑定的ID
        url : ctx + '/sale_chance/list', // 访问数据的地址
        cellMinWidth : 95,
        page : true, // 开启分页
        height : "full-125",
        limits : [10,15,20,25],
        limit : 10,
        toolbar: "#toolbarDemo",
        id : "saleChanceListTable",
        cols : [[
            {type: "checkbox", fixed:"center"},
            {field: "id", title:'编号',fixed:"true"},
            {field: 'chanceSource', title: '机会来源',align:"center"},
            {field: 'customerName', title: '客户名称',  align:'center'},
            {field: 'cgjl', title: '成功几率', align:'center'},
            {field: 'overview', title: '概要', align:'center'},
            {field: 'linkMan', title: '联系人',  align:'center'},
            {field: 'linkPhone', title: '联系电话', align:'center'},
            {field: 'description', title: '描述', align:'center'},
            {field: 'createMan', title: '创建人', align:'center'},
            {field: 'createDate', title: '创建时间', align:'center'},
            {field: 'uname', title: '指派人', align:'center'},
            {field: 'assignTime', title: '分配时间', align:'center'},
            {field: 'state', title: '分配状态', align:'center',templet:function(d){
                    return formatterState(d.state);
                }},
            {field: 'devResult', title: '开发状态', align:'center',templet:function (d) {
                    return formatterDevResult(d.devResult);
                }},
            {title: '操作', templet:'#saleChanceListBar',fixed:"right",align:"center", minWidth:150}
        ]]
    });


    /**
     * 格式化分配状态
     *  0 - 未分配
     *  1 - 已分配
     *  其他 - 未知
     * @param state
     * @returns {string}
     */
    function formatterState(state){
        if(state==0) {
            return "<div style='color: yellow'>未分配</div>";
        } else if(state==1) {
            return "<div style='color: green'>已分配</div>";
        } else {
            return "<div style='color: red'>未知</div>";
        }
    }

    /**
     * 格式化开发状态
     *  0 - 未开发
     *  1 - 开发中
     *  2 - 开发成功
     *  3 - 开发失败
     * @param value
     * @returns {string}
     */
    function formatterDevResult(value){
        if(value == 0) {
            return "<div style='color: yellow'>未开发</div>";
        } else if(value==1) {
            return "<div style='color: #00FF00;'>开发中</div>";
        } else if(value==2) {
            return "<div style='color: #00B83F'>开发成功</div>";
        } else if(value==3) {
            return "<div style='color: red'>开发失败</div>";
        } else {
            return "<div style='color: #af0000'>未知</div>"
        }
    }

});
           

这里涉及到 layui 列值格式化问题,机会数据分配状态与机会数据开发结果格式化实现参考官网

template 模板使用介绍。

3.2.3.4. 多条件查询

表格数据展示成功后,接下来考虑添加多条件查询点击事件,这里使用 layui 表格 reload 重载基础方法

实现,点击这里参考官网介绍

  • sale_chance.ftl
<a class="layui-btn search_btn" id="btnSearch" data-type="reload">
                    <i class="layui-icon">&#xe615;</i> 搜索
                </a>
           
  • sale.chance.js 添加搜索点击事件
//这里以搜索为例
    $("#btnSearch").click(function (){
        tableIns.reload({
            where: { //设定异步数据接口的额外参数,任意设
                customerName:$('[name="customerName"]').val(),
                createMan:$('[name="createMan"]').val(),
                state:$('[name="state"]').val()
            }
            ,page: {
                curr: 1 //重新从第 1 页开始
            }
        });
    });
           

3.3. 营销机会数据添加

3.3.1. 后端代码实现

3.3.1.1. 实现思路
添加数据
           1.校验参数
               customerName   客户名称 非空
               linkMan       联系人   非空
               linkPhone      手机号码 非空  手机号11位正则校验
           2.设置默认值
               is_valid     数据有效   0无效 1有效
               create_date  数据创建时间
               update_date  数据修改时间
               create_man   数据的创建人  当前登录用户(交给controller层从cookie获取)直接设置到 salechance对象中
     
               判断用户是否设置了分配人
                   如果分配了
                       assign_man   分配人
                       assign_time  分配时间
                       state        已分配 分配状态  0未分配 1已分配
                       dev_result   开发中 开发状态  0-未开发 1-开发中 2-开发成功 3-开发失败
                   如果未分配
                       state        未分配 分配状态  0未分配 1已分配
                       dev_result   未开发 开发状态  0-未开发 1-开发中 2-开发成功 3-开发失败
            3.执行添加操作,判断是否添加成功    
           
3.3.1.2. 核心代码

SaleChanceService.java

public void addSlaChance(SaleChance saleChance){
        //校验参数
        checkParams(saleChance.getCustomerName(),saleChance.getLinkMan(),saleChance.getLinkPhone());
        //设置默认值
        saleChance.setIsValid(1);
        saleChance.setUpdateDate(new Date());
        saleChance.setCreateDate(new Date());

        //判断用户是否设置了分配人
        if(StringUtils.isBlank(saleChance.getAssignMan())){
            //未分配状态
            saleChance.setState(0);
            saleChance.setDevResult(0);
        }else{
            //分配了人员
            saleChance.setAssignTime(new Date());
            saleChance.setState(1);
            saleChance.setDevResult(1);
        }

        //执行添加操作,判断是否添加成功
        AssertUtil.isTrue(saleChanceMapper.insertSelective(saleChance) < 1,"营销机会数据添加失败");
    }
    
 /**
     * 校验添加数据
     customerName   客户名称 非空
     *          linkMan       联系人   非空
     *          linkPhone      手机号码 非空  手机号11位正则校验
     * @param customerName
     * @param linkMan
     * @param linkPhone
     */
    private void checkParams(String customerName, String linkMan, String linkPhone) {
        AssertUtil.isTrue(StringUtils.isBlank(customerName),"客户名称不能为空");
        AssertUtil.isTrue(StringUtils.isBlank(linkMan),"联系人不能为空");
        AssertUtil.isTrue(StringUtils.isBlank(linkPhone),"手机号码不能为空");
        //校验手机号是否符合规范
        AssertUtil.isTrue(!PhoneUtil.isMobile(linkPhone),"手机号不符合规范");
    }
           
3.3.1.3. 方法调用

SaleChanceController.java

/**
     * 添加数据
     * @return
     */
    @PostMapping("save")
    @ResponseBody
    public ResultInfo save(HttpServletRequest request, SaleChance saleChance){
        //获取创建人
        String userName = CookieUtil.getCookieValue(request, "userName");
        saleChance.setCreateMan(userName);
        saleChanceService.addSlaChance(saleChance);
        return success();
    }
           
3.3.1.4. 页面转发

对于机会数据添加与更新表单页可以实现共享,这里在转发机会数据添加与更新页面时共用一套代码即

可 (考虑更新时涉及到机会数据显示操作,这里根据机会id查询机会记录并放入到请求域中)。

@RequestMapping("toAddUpdatePage")
    public String toAddUpdatePage(){
        //如果是修改操作那么需要将修改的数据映射在页面中
        return "saleChance/add_update";
    }
           

3.3.2. 前端核心代码

3.3.2.1. 页面效果

点击"添加"按钮,打开对应的对话框

3.3.2.2. 页面模板

views/saleChance 目录下添加 add_update.ftl 页面模板

<!DOCTYPE html>
<html>
<head>
    <#include "../common.ftl">
</head>
<body class="childrenBody">
<form class="layui-form" style="width:80%;">
    <input type="hidden" name="id" id="hidId" value="${(saleChance.id)!}">
    <input type="hidden" id="assignId" value="${(saleChance.assignMan)!}"><#--获取当前数据的指派人id-->
    <div class="layui-form-item layui-row layui-col-xs12">
        <label class="layui-form-label">客户名称</label>
        <div class="layui-input-block">
            <input type="text" class="layui-input" lay-verify="required"
                   name="customerName" id="customerName"  value="${(saleChance.customerName)!}" placeholder="请输入客户名称">
        </div>
    </div>
    <div class="layui-form-item layui-row layui-col-xs12">
        <label class="layui-form-label">机会来源</label>
        <div class="layui-input-block">
            <input type="text" class="layui-input"  name="chanceSource"
                   id="chanceSource" value="${(saleChance.chanceSource)!}" placeholder="请输入机会来源">
        </div>
    </div>
    <div class="layui-form-item layui-row layui-col-xs12">
        <label class="layui-form-label">联系人</label>
        <div class="layui-input-block">
            <input type="text" class="layui-input" name="linkMan"
                   lay-verify="required"  value="${(saleChance.linkMan)!}" placeholder="请输入联系人">
        </div>
    </div>

    <div class="layui-form-item layui-row layui-col-xs12">
        <label class="layui-form-label">联系电话</label>
        <div class="layui-input-block">
            <input type="text" class="layui-input" lay-verify="phone"
                   name="linkPhone" value="${(saleChance.linkPhone)!}" id="phone" placeholder="请输入联系电话">
        </div>
    </div>
    <div class="layui-form-item layui-row layui-col-xs12">
        <label class="layui-form-label">概要</label>
        <div class="layui-input-block">
            <input type="text" class="layui-input"
                   name="overview" value="${(saleChance.overview)!}" id="phone" placeholder="请输入概要">
        </div>
    </div>
    <div class="layui-form-item layui-row layui-col-xs12">
        <label class="layui-form-label">成功几率(%)</label>
        <div class="layui-input-block">
            <input type="text" class="layui-input" name="cgjl" value="${(saleChance.cgjl)!}"
                   placeholder="请输入成功几率">
        </div>
    </div>
    <div class="layui-form-item layui-row layui-col-xs12">
        <label class="layui-form-label">机会描述</label>
        <div class="layui-input-block">
                    <textarea placeholder="请输入机会描述信息" name="description" class="layui-textarea">
                  	    ${(saleChance.description)!}
                    </textarea>
        </div>
    </div>
    <div class="layui-form-item layui-row layui-col-xs12">
        <label class="layui-form-label">指派给</label>
        <div class="layui-input-block">
            <select name="assignMan" id="assignMan">
                <option value="">请选择</option>
            </select>
        </div>
    </div>
    <br/>
    <div class="layui-form-item layui-row layui-col-xs12">
        <div class="layui-input-block">
            <button class="layui-btn layui-btn-lg" lay-submit="" lay-filter="addOrUpdateSaleChance">
                确认
            </button>
            <button class="layui-btn layui-btn-lg layui-btn-normal">取消</button>
        </div>
    </div>
</form>
<script type="text/javascript" src="${ctx}/js/saleChance/add.update.js"></script>
</body>
</html>
           
3.3.2.3. 工具栏事件
  • sale_chance.ftl

这里对于对话框打开方法代码,请参考官网页面。

<#--头部工具栏-->
    <script type="text/html" id="toolbarDemo">
        <div class="layui-btn-container">
            <a class="layui-btn layui-btn-normal addNews_btn" lay-event="add">
                <i class="layui-icon">&#xe608;</i>
                添加
            </a>
            <a class="layui-btn layui-btn-normal delNews_btn" lay-event="del">
                <i class="layui-icon">&#xe608;</i>
                删除
            </a>
        </div>
    </script>
           
  • sale_chancel.js

监听头部工具栏事件,参考官网页面。

/**
     * 监听表格的头部工具栏
     */
    //监听事件
    table.on('toolbar(saleChances)', function(obj){
        console.log(obj);
        switch(obj.event){
            case 'add':
                openAddOrUpdateDialog();//打开添加修改的窗口页面
                break;
            case 'del':
                layer.msg('删除');
                break;
        };
    });

 /**
     * 打开添加修改的窗口页面
     */
    function openAddOrUpdateDialog(id){
        var title = "<h2>营销机会管理 - 机会添加</h2>";
        var url = ctx + "/sale_chance/toAddUpdatePage";

        //通过参数id判断目前是修改还是添加操作
        /*if(id){
            title = "<h2>营销机会管理 - 机会修改</h2>";
            url += "?id="+id;
        }*/

        //打开修改添加页面
        layer.open({
            type:2,   //ifame
            title:title,
            content: url,   //页面的内容
            area:['500px','620px'], //设置宽高
            maxmin:true //可以伸缩页面大小
        });
    }
           
3.3.2.4. 核心 JS

js/saleChance 目录下添加 add.update.js 文件,完成机会数据添加与更新表单提交操作。监听submit

提交,参考官网页面。

layui.use(['form','jquery','jquery_cookie'], function () {
    var form = layui.form,
        layer = layui.layer,
        $ = layui.jquery,
        $ = layui.jquery_cookie($);


    //加载下拉框  指派人
    /* $.post(ctx+"/sale_chance/queryAllSales",function (data){
        //获取下拉框
        var am = $("#assignMan");
        // <#--获取当前数据的指派人id-->
        var aid = $("#assignId").val();
        if(data != null){
            for(var i = 0; i < data.length; i++){
                //回显当前数据的指派人
                if(aid == data[i].id){
                    var opt = "<option selected value="+data[i].id+">"+data[i].name+"</option>";
                }else{
                    var opt = "<option value="+data[i].id+">"+data[i].name+"</option>";
                }
                am.append(opt);
            }
        }

        // 重新渲染下拉框内容
        layui.form.render("select");
    });*/

    /**
     * 监听表单的提交
     *     on监听 submit事件
     */
    form.on('submit(addOrUpdateSaleChance)',function (data){
        // 提交数据时的加载层 (https://layer.layui.com/)
        var index = layer.msg("数据提交中,请稍后...",{
            icon:16, // 图标
            time:false, // 不关闭
            shade:0.8 // 设置遮罩的透明度
        });
        var url= ctx + "/sale_chance/save";

        //判断当前页面中是否有id值,如果有则是修改
        if($("#hidId").val()){
            url= ctx + "/sale_chance/update";
        }

        console.log(data.field);
        //发送请求
        $.post(url,data.field,function (data){
            if(data.code == 200){
                //关闭弹出框
                layer.close(index);
                //关闭iframe层
                layer.closeAll("iframe");
                //刷新父页面,将添加的新数据展示
                parent.location.reload();
            }else{
                layer.msg(data.msg,{icon:5})
            }
        });

        return false;//阻止表单提交
    })
});
           
3.3.2.5. 关闭弹出层
  • add_update.ftl

给按钮设置 id 属性值,通过 id 绑定点击事件

  • add.update.js
/*** 关闭弹出层 */
$("#closeBtn").click(function () {
    // 先得到当前iframe层的索引 
    var index = parent.layer.getFrameIndex(window.name); 
   // 再执行关闭 
    parent.layer.close(index);
});
           

3.4. 营销机会数据更新

3.4.1. 后端代码实现

3.4.1.1. 实现思路
/**
     * 修改数据
     *      1.校验参数
     *          id属性是必须存在的,查询数据库校验
     *          customerName   客户名称 非空
     *          linkMan       联系人   非空
     *          linkPhone      手机号码 非空  手机号11位正则校验
     *      2.默认值
     *          update_date  修改时间
     *
     *          判断是否指派了工作人员
     *              1.修改前没有分配人
     *                  修改后没有分配人
     *                      不做任何操作
     *                  修改后有分配人
     *                      dev_result  开发状态
     *                      assign_time 分配时间
     *                      state       分配状态
     *
     *              2.修改前有分配人
     *                  修改后没有分配人
     *                      assign_time 分配时间 null
     *                      dev_result  开发状态
     *                      state       分配状态 0
     *                  修改后有分配人
     *                      判断更改后的人员和更改前的人员有没有变动
     *                          没有变动不做操作
     *                          有变动,assign_time最新的时间
     *     3.执行修改操作,判断是否修改成功
     *
     * @param saleChance
     */
           
3.4.1.2. 核心代码
  • SaleChanceService.java
public void updateSaleChance(SaleChance saleChance){
        //判断id是否存在
        AssertUtil.isTrue(saleChance.getId() == null,"数据异常,请重试");
        //校验非空参数
        checkParams(saleChance.getCustomerName(),saleChance.getLinkMan(),saleChance.getLinkPhone());
        //设置默认值
        saleChance.setUpdateDate(new Date());

        //通过现有的id查询修改之前的数据
        SaleChance dbSaleChance = saleChanceMapper.selectByPrimaryKey(saleChance.getId());
        AssertUtil.isTrue(dbSaleChance == null,"数据异常,请重试");

        //判断原有数据中是否有分配人
        if(StringUtils.isBlank(dbSaleChance.getAssignMan())){
            //进入当前判断说明修改前没有分配人

            //判断修改后是否有分配人
            if(!StringUtils.isBlank(saleChance.getAssignMan())){
                //修改后有分配人
                saleChance.setAssignTime(new Date());
                saleChance.setState(1);
                saleChance.setDevResult(1);
            }
            //修改后没有分配人,什么都不做

        }else{
            //进入当前判断说明修改前有分配人

            //判断修改后是否有分配人
            if(StringUtils.isBlank(saleChance.getAssignMan())){
                //修改后没有分配人
                saleChance.setAssignTime(null);
                saleChance.setState(0);
                saleChance.setDevResult(0);
            }else{
                //修改后有分配人

                //判断前后的分配人是否有变化
                if(!dbSaleChance.getAssignMan().equals(saleChance.getAssignMan())){
                    //不是一个人,有变化
                    saleChance.setAssignTime(new Date());
                }else{
                    //相同的分配人  那么前台后台都没有设置分配的时间,那么结合刚修改的sql条件,那么原有的数据机会被更改
                    saleChance.setAssignTime(new Date());
                }
            }

        }

        //执行修改操作
        AssertUtil.isTrue(saleChanceMapper.updateByPrimaryKeySelective(saleChance) < 1,"营销数据修改失败");
    }
           
3.4.1.3. 方法调用

这里机会数据更新与添加操作实现代码共用,修改原有的添加方法。

  • SaleChanceController.java
/**
     * 添加数据
     * @return
     */
    @PostMapping("update")
    @ResponseBody
    public ResultInfo update(SaleChance saleChance){
        saleChanceService.updateSaleChance(saleChance);
        return success();
    }
           
3.4.1.4. 页面转发

SaleChanceController.java

/**
     * 打开营销机会修改/添加的页面
     * @return
     */
   /* @RequestMapping("toAddUpdatePage")
    public String toAddUpdatePage(){
        //如果是修改操作那么需要将修改的数据映射在页面中
        return "saleChance/add_update";
    }*/
    @RequestMapping("toAddUpdatePage")
    public String toAddUpdatePage(Integer id,HttpServletRequest request){
        //如果是修改操作那么需要将修改的数据映射在页面中
        if(id != null){
            SaleChance saleChance = saleChanceService.selectByPrimaryKey(id);
            AssertUtil.isTrue(saleChance == null,"数据异常,请重试");
            request.setAttribute("saleChance",saleChance);
        }
        return "saleChance/add_update";
    }
           

3.4.2. 前端核心代码

3.4.2.1. 页面效果
3.4.2.2. 表格行事件
  • sale_chance.ftl
<!--操作 行工具栏-->
    <script id="saleChanceListBar" type="text/html">
        <a class="layui-btn layui-btn-xs" id="edit" lay-event="edit">编辑</a>
        <a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del">删除</a>
    </script>
           
  • sale.chance.js

表格添加行监听事件,行监听事件参考这里。

/**
     * 监听表格的行工具栏
     */
    //监听事件
    table.on('tool(saleChances)', function(obj){
        if(obj.event == "edit"){
            openAddOrUpdateDialog(obj.data.id);
        }
    });
           

openAddOrUpdateDialog方法参考数据添加操作。

/**
     * 打开添加修改的窗口页面
     */
    function openAddOrUpdateDialog(id){
        var title = "<h2>营销机会管理 - 机会添加</h2>";
        var url = ctx + "/sale_chance/toAddUpdatePage";

        //通过参数id判断目前是修改还是添加操作
        if(id){
            title = "<h2>营销机会管理 - 机会修改</h2>";
            url += "?id="+id;
        }

        //打开修改添加页面
        layer.open({
            type:2,   //ifame
            title:title,
            content: url,   //页面的内容
            area:['500px','620px'], //设置宽高
            maxmin:true //可以伸缩页面大小
        });
    }
           
  • add_update.ftl

在页面中通过EL表达式获取作用域中的数据。 value里面

<input type="text" class="layui-input" name="cgjl" value="${(saleChance.cgjl)!}"
                   placeholder="请输入成功几率">
           

添加隐藏域,存放营销机会的ID

3.4.2.3. 核心 JS

修改操作的 JS 与添加操作的可共用,通过隐藏域中存放的营销机会ID,来判断当前的操作行为。

/**
     * 监听表单的提交
     *     on监听 submit事件
     */
    form.on('submit(addOrUpdateSaleChance)', function (data) {
        // 提交数据时的加载层 (https://layer.layui.com/)
        var index = layer.msg("数据提交中,请稍后...", {
            icon: 16, // 图标
            time: false, // 不关闭
            shade: 0.8 // 设置遮罩的透明度
        });
        var url = ctx + "/sale_chance/save";

        //判断当前页面中是否有id值,如果有则是修改
        if ($("#hidId").val()) {
            url = ctx + "/sale_chance/update";
        }

        console.log(data.field);
        //发送请求
        $.post(url, data.field, function (data) {
            if (data.code == 200) {
                //关闭弹出框
                layer.close(index);
                //关闭iframe层
                layer.closeAll("iframe");
                //刷新父页面,将添加的新数据展示
                parent.location.reload();
            } else {
                layer.msg(data.msg, {icon: 5})
            }
        });

        return false;//阻止表单提交
    })
           
3.4.2.4. 加载下拉框
  • SaleChanceMapper.java
//查询所有销售人员数据
    public List<Map<String,Object>> queryAllSales();
           
  • SaleChanceMapper.xml.xml
<!--查询所有销售人员-->
  <select id="queryAllSales" resultType="map">
    SELECT
	    u.id id,u.user_name name
    FROM
        t_user u
        LEFT JOIN t_user_role ur ON ur.user_id = u.id
        LEFT JOIN t_role r ON ur.role_id = r.id
    WHERE
        u.is_valid = 1 and r.is_valid = 1 and role_name = "销售"
  </select>
           
  • SaleChanceService.java
//查询所有销售人员
    public List<Map<String,Object>> queryAllSales(){
        return saleChanceMapper.queryAllSales();
    }
           
  • SaleChanceController.java
/**
     * 查询所有销售人员
     */
    @PostMapping("queryAllSales")
    @ResponseBody
    public List<Map<String,Object>> queryAllSales(){
        return saleChanceService.queryAllSales();
    }
           
  • add_update.ftl
<input type="hidden" id="assignId" value="${(saleChance.assignMan)!}"><#--获取当前数据的指派人id-->
           
  • add.update.js
//加载下拉框  指派人
     $.post(ctx+"/sale_chance/queryAllSales",function (data){
         //获取下拉框
         var am = $("#assignMan");
         // <#--获取当前数据的指派人id-->
         var aid = $("#assignId").val();
         if(data != null){
             for(var i = 0; i < data.length; i++){
                 //回显当前数据的指派人
                 if(aid == data[i].id){
                     var opt = "<option selected value="+data[i].id+">"+data[i].name+"</option>";
                 }else{
                     var opt = "<option value="+data[i].id+">"+data[i].name+"</option>";
                 }
                 am.append(opt);
             }
         }

         // 重新渲染下拉框内容
         layui.form.render("select");
     });
           
  • 显示指派人
  1. 修改查询的SQL语句 ( SaleChanceMapper.xml )
<select id="queryByParams" parameterType="com.xxxx.crm.query.SaleChanceQuery" resultType="saleChance">
    select
    sc.*,u.user_name uname
    from
    t_sale_chance sc
    left join t_user u on sc.assign_man = u.id
    <where>
      sc.is_valid = 1
      <if test="customerName != null and customerName != ''">
        and  customer_name like concat("%",#{customerName},"%")
      </if>
      <if test="createMan != null and createMan != ''">
        and  create_man = #{createMan}
      </if>
      <if test="state != null and state != ''">
        and  state = #{state}
      </if>
    </where>
  </select>
           
  1. JavaBean中添加属性字段 ( SaleChance.java )
private String uname;// 指派人

    public String getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }
           

3.5. 营销机会数据删除

3.5.1. 后端代码实现

3.5.1.1. 设置SQL

SaleChanceMapper.xml

这里机会数据支持数据批量删除操作,后端接收前端数组参数借助 mybatis 动态标签 foreach 实现记录

批量删除。

<!--批量删除-->
  <update id="deleteBatch" >
    UPDATE t_sale_chance
    SET is_valid = 0
    WHERE
    id
    IN
    <foreach collection="array" open="(" close=")" item="id" separator=",">
      #{id}
    </foreach>
  </update>
           
3.5.1.2. 接口定义

在 BaseMapper 中定义了删除的方法,无须在 SaleChanceMapper 类中再定义。

/**
     * 批量删除
     * @param ids
     * @return
     */
    public Integer deleteBatch(ID[] ids) throws DataAccessException;
           
3.5.1.3. Service

SaleChanceService.java

/**
     * 逻辑删除
     * @param ids
     */
    @Transactional
    public void deleteBatchs(Integer[] ids){
        AssertUtil.isTrue(ids == null || ids.length < 1,"未选中删除数据");
        saleChanceMapper.deleteBatch(ids);
    }
           
3.5.1.4. Controller

SaleChanceController.java

/**
     * 逻辑删除
     * @param ids
     */
    @RequestMapping("deleteBatch")
    @ResponseBody
    public ResultInfo deleteBatchs(Integer[] ids){
        saleChanceService.deleteBatchs(ids);
        return success("删除成功");
    }
           

3.5.2. 前端核心代码

3.5.2.1. 工具栏事件

sale_chance.ftl

<#--头部工具栏-->
    <script type="text/html" id="toolbarDemo">
        <div class="layui-btn-container">
            <a class="layui-btn layui-btn-normal addNews_btn" lay-event="add">
                <i class="layui-icon">&#xe608;</i>
                添加
            </a>
            <a class="layui-btn layui-btn-normal delNews_btn" lay-event="del">
                <i class="layui-icon">&#xe608;</i>
                删除
            </a>
        </div>
    </script>
           

sale_chancel.js

/**
     * 监听表格的头部工具栏
     */
    //监听事件
    table.on('toolbar(saleChances)', function(obj){
        console.log(obj);
        switch(obj.event){
            case 'add':
                openAddOrUpdateDialog();//打开添加修改的窗口页面
                break;
            case 'del':
                layer.msg('删除');
                break;
        };
    });

    //批量删除
    function deleteBatch(data){
        //判断是否选中数据
        if(data.length == 0){
            layer.msg("请至少选中一条数据");
            return;
        }
        //向用户确认删除行为
        layer.confirm("您确定要删除选中的记录吗?",{
            btn:["确认","取消"],
        },function (index) {
            //关闭弹出框
            layer.close(index);

            //拼接后台需要的id数组  ids=1&ids=2
            var str = "ids=";
            for(var i = 0; i < data.length; i++){
                //判断是否是倒数第二个
                if(i < data.length - 1){
                    str += data[i].id + "&ids=";
                }else{
                    str += data[i].id;
                }
            }
            console.log(str);

            $.ajax({
                type:"post",
                url: ctx+"/sale_chance/deleteBatch",
                data:str,
                dataType:"json",
                success:function(data){
                    if(data.code == 200){
                        //刷新数据表格
                        tableIns.reload();
                    }else{
                        layer.msg(data.msg,{icon:5})
                    }
                }
            });
        })
    }

 /**
     * 打开添加修改的窗口页面
     */
    function openAddOrUpdateDialog(id){
        var title = "<h2>营销机会管理 - 机会添加</h2>";
        var url = ctx + "/sale_chance/toAddUpdatePage";

        //通过参数id判断目前是修改还是添加操作
        if(id){
            title = "<h2>营销机会管理 - 机会修改</h2>";
            url += "?id="+id;
        }

        //打开修改添加页面
        layer.open({
            type:2,   //ifame
            title:title,
            content: url,   //页面的内容
            area:['500px','620px'], //设置宽高
            maxmin:true //可以伸缩页面大小
        });
    }
           
3.5.2.2. 表格行事件
<!--操作 行工具栏-->
    <script id="saleChanceListBar" type="text/html">
        <a class="layui-btn layui-btn-xs" id="edit" lay-event="edit">编辑</a>
        <a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del">删除</a>
    </script>
           

sale.chance.js

/**
     * 监听表格的行工具栏
     */
    //监听事件
    table.on('tool(saleChances)', function(obj){
        if(obj.event == "edit"){
            openAddOrUpdateDialog(obj.data.id);
        }else if(obj.event == "del"){
            // 询问是否确认删除
            layer.confirm("确定要删除这条记录吗?", {icon: 3, title:"营销机会数据管理"}, function (index) {
                // 关闭窗口
                layer.close(index);
                // 发送ajax请求,删除记录
                $.ajax({
                    type:"post",
                    url: ctx + "/sale_chance/deleteBatch",
                    data:{
                        ids:obj.data.id
                    },
                    dataType:"json",
                    success:function (result) {
                        if (result.code == 200) {
                            // 加载表格
                            tableIns.reload();
                        } else {
                            layer.msg(result.msg, {icon: 5});
                        }
                    }
                });
            });
        }
    });
           

4. 客户开发计划功能实现

4.1. 开发计划数据查询

4.1.1. 页面效果

4.1.2. 后端代码实现

这里展示开发计划项数据为已分配给当前登录用户的营销机会数据后续对客户进行下一步的开发操

作,所以在数据展示上查询的机会数据分配状态为已分配状态。

4.1.2.1. 查询条件

SaleChanceQuery.java

新增分配人assignMan,开发状态devResult查询条件。

package com.xxxx.crm.query;

import com.xxxx.crm.base.BaseQuery;

/**
 * 营销机会管理多条件查询条件
 */
public class SaleChanceQuery extends BaseQuery {

    private String customerName; // 客户名称
    private String createMan; // 创建人
    private String state; // 分配状态

    private Integer assignMan; //指派人
    private Integer devResult; //开发状态

    public Integer getAssignMan() {
        return assignMan;
    }

    public void setAssignMan(Integer assignMan) {
        this.assignMan = assignMan;
    }

    public Integer getDevResult() {
        return devResult;
    }

    public void setDevResult(Integer devResult) {
        this.devResult = devResult;
    }

    public String getCustomerName() {
        return customerName;
    }

    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }

    public String getCreateMan() {
        return createMan;
    }

    public void setCreateMan(String createMan) {
        this.createMan = createMan;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}
           
4.1.2.2. 设置SQL

SaleChanceMapper.xml

查询语句中添加分配人与开发状态查询条件。

<select id="queryByParams" parameterType="com.xxxx.crm.query.SaleChanceQuery" resultType="saleChance">
    select
    sc.*,u.user_name uname
    from
    t_sale_chance sc
    left join t_user u on sc.assign_man = u.id
    <where>
      sc.is_valid = 1
      <if test="customerName != null and customerName != ''">
        and  customer_name like concat("%",#{customerName},"%")
      </if>
      <if test="createMan != null and createMan != ''">
        and  create_man = #{createMan}
      </if>
      <if test="state != null and state != ''">
        and  state = #{state}
      </if>
      <if test="assignMan != null">
        and  assign_man = #{assignMan}
      </if>
      <if test="devResult != null">
        and  dev_result = #{devResult}
      </if>
    </where>
  </select>
           
4.1.2.3. Controller

SaleChanceController.java

添加开发计划项数据查询标识 flag,如果前端页面查询参数 flag=1,代表当前查询为开发计划数据,设

置查询分配人参数。

@GetMapping("list")
    @ResponseBody
    public Map<String, Object> queryByParams(SaleChanceQuery saleChanceQuery,Integer flag,HttpServletRequest request){
        //判断lfag参数是否存在区分 客户开发计划和营销机会的页面
        if(flag != null && flag == 1){
            int id = LoginUserUtil.releaseUserIdFromCookie(request);
            saleChanceQuery.setAssignMan(id);
        }
        return saleChanceService.queryByParams(saleChanceQuery);
    }
           

4.1.3. 前端核心代码

4.1.3.1. 页面模板

cus_dev_plan.ftl

views/cusDevPlan 目录下创建 cus_dev_plan.ftl 文件,展示已分配计划项列表数据,这里对于机

会数据展示行操作有两种状态

详情:机会数据已开发结束,点击详情展示计划项相关数据

开发:机会数据处于开发中,点击开发添加计划项数据

<!DOCTYPE html>
<html>
	<head>
		<title>客户开发计划管理</title>
		<#include "../common.ftl">
	</head>
	<body class="childrenBody">
		<form class="layui-form" >
			<blockquote class="layui-elem-quote quoteBox">
				<form class="layui-form" >
					<div class="layui-inline">
						<div class="layui-input-inline">
							<input type="text" name="customerName"
								   class="layui-input
							searchVal" placeholder="客户名" />
						</div>
						<div class="layui-input-inline">
							<input type="text" name="createMan" class="layui-input
							searchVal" placeholder="创建人" />
						</div>
						<div class="layui-input-inline">
							<select name="devResult"  id="devResult"  >
								<option value="" >请选择</option>
								<option value="0">未开发</option>
								<option value="1" >开发中</option>
								<option value="2" >开发成功</option>
								<option value="3" >开发失败</option>
							</select>
						</div>
						<a class="layui-btn search_btn" data-type="reload" id="btnSearch">
							<i class="layui-icon">&#xe615;</i> 搜索
						</a>
					</div>
				</form>
			</blockquote>

			<#--数据表格-->
			<table id="saleChanceList" class="layui-table"  lay-filter="saleChances">
			</table>
		
		
			<script type="text/html" id="toolbarDemo">
			</script>
		
		
			<!--操作-->
			<script id="op" type="text/html" >
				{{# if (d.devResult=== 0 || d.devResult==1) { }}
					<a href="javascript:;"  class="layui-btn layui-btn-warm layui-btn-xs"  lay-event="dev">开发</a>
				{{# } else { }}
					<a href="javascript:;" class="layui-btn layui-btn-normal layui-btn-xs"  lay-event="info">详情</a>
				{{# } }}
			</script>
		
		</form>
	<script type="text/javascript" src="${ctx}/js/cusDevPlan/cus.dev.plan.js"></script>
	</body>
</html>
           
4.1.3.2. 页面入口

点击左侧的菜单,进入对应的页面

<dd>
 <a href="javascript:;" class="layui-menu-tips" data-type="tabAdd" data-tab-mpi="m-p-i-2" data-tab="cus_dev_plan/index" target="_self"><i class="fa fa-ellipsis-h"></i><span class="layui-left-nav"> 客户开发计划</span></a>
 </dd>
           

CusDevPlanController 后台设置对应的接口

@RequestMapping("index")
    public String toIndex(){

        return "cusDevPlan/cus_dev_plan";
    }
           
4.1.3.3. 核心 JS

js/cusDevPlan 目录下新建 cus.dev.plan.js 文件

cus.dev.plan.js

layui.use(['table','layer'],function(){
    var layer = parent.layer === undefined ? layui.layer : top.layer,
        $ = layui.jquery,
        table = layui.table;


    /**
     * 客户开发数据列表
     */
    var  tableIns = table.render({
        elem: '#saleChanceList',
        url : ctx+'/sale_chance/list?flag=1&state=1',
        cellMinWidth : 95,
        page : true,
        height : "full-125",
        limits : [10,15,20,25],
        limit : 10,
        toolbar: "#toolbarDemo",
        id : "saleChanceListTable",
        cols : [[
            {type: "checkbox", fixed:"center"},
            {field: "id", title:'编号',fixed:"true"},
            {field: 'chanceSource', title: '机会来源',align:"center"},
            {field: 'customerName', title: '客户名称',  align:'center'},
            {field: 'cgjl', title: '成功几率', align:'center'},
            {field: 'overview', title: '概要', align:'center'},
            {field: 'linkMan', title: '联系人',  align:'center'},
            {field: 'linkPhone', title: '联系电话', align:'center'},
            {field: 'description', title: '描述', align:'center'},
            {field: 'createMan', title: '创建人', align:'center'},
            {field: 'createDate', title: '创建时间', align:'center'},
            {field: 'devResult', title: '开发状态', align:'center',templet:function (d) {
                    return formatterDevResult(d.devResult);
            }},
            {title: '操作',fixed:"right",align:"center", minWidth:150,templet:"#op"}
        ]]
    });

    /**
     * 格式化开发状态
     * @param value
     * @returns {string}
     */
    function formatterDevResult(value){
        /**
         * 0-未开发
         * 1-开发中
         * 2-开发成功
         * 3-开发失败
         */
        if(value==0){
            return "<div style='color: yellow'>未开发</div>";
        }else if(value==1){
            return "<div style='color: #00FF00;'>开发中</div>";
        }else if(value==2){
            return "<div style='color: #00B83F'>开发成功</div>";
        }else if(value==3){
            return "<div style='color: red'>开发失败</div>";
        }else {
            return "<div style='color: #af0000'>未知</div>"
        }
    }
});
           

列模板template对应的配置 cus_dev_plan.ftl

这里涉及的{{# }} 语法参考官网介绍

<!--操作-->
			<script id="op" type="text/html" >
				{{# if (d.devResult=== 0 || d.devResult==1) { }}
					<a href="javascript:;"  class="layui-btn layui-btn-warm layui-btn-xs"  lay-event="dev">开发</a>
				{{# } else { }}
					<a href="javascript:;" class="layui-btn layui-btn-normal layui-btn-xs"  lay-event="info">详情</a>
				{{# } }}
			</script>
           
4.1.3.4. 多条件查询

添加多条件查询点击事件

cus_dev_plan.ftl

</div>
	<a class="layui-btn search_btn" data-type="reload" id="btnSearch">
	<i class="layui-icon">&#xe615;</i> 搜索
</a>
           

cus.dev.plan.js 添加搜索点击事件

//数据表格重载
    $("#btnSearch").click(function (){
        tableIns.reload({
            where: { //设定异步数据接口的额外参数,任意设
                customerName:$('[name="customerName"]').val(),
                createMan:$('[name="createMan"]').val(),
                devResult:$('[name="devResult"]').val()
            }
            ,page: {
                curr: 1 //重新从第 1 页开始
            }
        });
    });
           

4.2. 营销机会数据开发与详情

<!--营销机会数据开发与详情-->
        <table tableName="t_cus_dev_plan" domainObjectName="CusDevPlan"
               enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
        </table>
           

4.2.1. 行监听事件

cus_dev_plan.ftl

<!--操作-->
<script id="op" type="text/html" >
	{{# if (d.devResult=== 0 || d.devResult==1) { }}
		<a href="javascript:;"  class="layui-btn layui-btn-warm layui-btn-xs"  lay-event="dev">开发</a>
	{{# } else { }}
		<a href="javascript:;" class="layui-btn layui-btn-normal layui-btn-xs"  lay-event="info">详情</a>
	{{# } }}
</script>
           

cus.dev.plan.js

/**
     * 监听表格的行工具栏
     */
    table.on('tool(saleChances)', function(obj){
        if(obj.event == "dev"){
            openDevOrInfoDialog("计划项数据开发",obj.data.id);
        }else if(obj.event == "info"){
            openDevOrInfoDialog("计划项数据维护",obj.data.id);
        }
    });


    //打开客户开发计划的窗口
    function openDevOrInfoDialog(title,sId){
        layui.layer.open({
            title:title,
            type: 2,  //iframe层
            area:["750px","550px"],
            maxmin: true,
            content:ctx + "/cus_dev_plan/toCusDevPlanDataPage?sId="+sId
        });
    }
           

4.2.2. 视图转发方法

CusDevPlanController.java

前端点击开发|详情按钮,后端提供转发到开发计划项数据视图页面,转发时获取当前点击的机会数据的

id,便于在视图中显示机会数据信息。

/**
     * 打开客户计划详情页
     * @param sId
     * @param request
     * @return
     */
    @RequestMapping("toCusDevPlanDataPage")
    public String toCusDevPlanDataPage(Integer sId, HttpServletRequest request){
        AssertUtil.isTrue(sId == null,"数据异常,请重试");
        SaleChance saleChance = saleChanceService.selectByPrimaryKey(sId);
        //将数据存在作用域中
        if(saleChance != null){
            request.setAttribute("saleChance",saleChance);
        }

        return "cusDevPlan/cus_dev_plan_data";
    }
           

4.2.3. 视图添加

cus_dev_plan_data.ftl

在 views/cusDevPlan 目录下添加 cus_dev_plan_data.ftl 视图模板,显示机会数据与计划项列表数据。

这里是否显示视图中表格头工具栏与行按钮信息由机会数据开发状态决定。

<!DOCTYPE html>
<html>
	<head>
		<title>客户开发计划管理</title>
		<#include "../common.ftl">
	</head>
	<body class="childrenBody">
	<div class="layui-col-md12">
		<div class="layui-card">
			<div class="layui-card-body">
				<form class="layui-form" >
					<#-- 营销机会的ID -->
					<input name="id" type="hidden" value="${(saleChance.id)!}"/>
					<div class="layui-form-item layui-row">
						<div class="layui-col-xs6">
							<label class="layui-form-label">客户名称</label>
							<div class="layui-input-block">
								<input type="text" class="layui-input"
									   name="customerName" id="customerName"  value="${(saleChance.customerName)!}" readonly="readonly">
							</div>
						</div>
						<div class="layui-col-xs6">
							<label class="layui-form-label">机会来源</label>
							<div class="layui-input-block">
								<input type="text" class="layui-input"
									   name="chanceSource" id="chanceSource" value="${(saleChance.chanceSource)!}" readonly="readonly">
							</div>
						</div>
					</div>

					<div class="layui-form-item layui-row">
						<div class="layui-col-xs6">
							<label class="layui-form-label">联系人</label>
							<div class="layui-input-block">
								<input type="text" class="layui-input"
									   name="linkMan"  lay-verify="required"  value="${(saleChance.linkMan)!}" readonly="readonly">
							</div>
						</div>
						<div class="layui-col-xs6">
							<label class="layui-form-label">联系电话</label>
							<div class="layui-input-block">
								<input type="text" class="layui-input"
									   lay-verify="phone" name="linkPhone" value="${(saleChance.linkPhone)!}" id="phone" readonly="readonly">
							</div>
						</div>
					</div>

					<div class="layui-form-item layui-row">
						<div class="layui-col-xs6">
							<label class="layui-form-label">概要</label>
							<div class="layui-input-block">
								<input type="text" class="layui-input"
									   name="overview" value="${(saleChance.overview)!}" id="phone"  readonly="readonly">
							</div>
						</div>
						<div class="layui-col-xs6">
							<label class="layui-form-label">成功几率(%)</label>
							<div class="layui-input-block">
								<input type="text" class="layui-input"
									   name="cgjl" value="${(saleChance.cgjl)!}"  readonly="readonly">
							</div>
						</div>
					</div>
				</form>
			</div>
		</div>
	</div>


	<#--数据表格-->
	<div class="layui-col-md12">
		<table id="cusDevPlanList" class="layui-table"  lay-filter="cusDevPlans"></table>
	</div>

	<#-- 如果开发状态是 0=未开发 和 1=开发中,则显示头部工具栏和行工具栏 -->
	<#if saleChance?? && saleChance.devResult==0 || saleChance.devResult==1>
		<script type="text/html" id="toolbarDemo">
			<div class="layui-btn-container">
				<a class="layui-btn layui-btn-normal addNews_btn" lay-event="add">
					<i class="layui-icon">&#xe608;</i>
					添加计划项
				</a>
				<a class="layui-btn layui-btn-normal addNews_btn" lay-event="success">
					<i class="layui-icon">&#xe608;</i>
					开发成功
				</a>
				<a class="layui-btn layui-btn-normal addNews_btn" lay-event="failed">
					<i class="layui-icon">&#xe608;</i>
					开发失败
				</a>
			</div>
		</script>

		<!--操作-->
		<script id="cusDevPlanListBar" type="text/html">
			<a class="layui-btn layui-btn-xs" id="edit" lay-event="edit">编辑</a>
			<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del">删除</a>
		</script>
	</#if>

	<script type="text/javascript" src="${ctx}/js/cusDevPlan/cus.dev.plan.data.js"></script>
	</body>
</html>
           

4.2.3.1. 添加 JS 文件

cus.dev.plan.data.js

在 js/cusDevPlan 目录下添加 cus.dev.plan.data.js

layui.use(['table','layer'],function(){
    var layer = parent.layer === undefined ? layui.layer : top.layer,
        $ = layui.jquery,
        table = layui.table;

    /**
     * 计划项数据展示
     */
    var  tableIns = table.render({
        elem: '#cusDevPlanList',
        url : ctx+'/cus_dev_plan/list?sId='+$('[name="id"]').val(),
        cellMinWidth : 95,
        page : true,
        height : "full-125",
        limits : [10,15,20,25],
        limit : 10,
        toolbar: "#toolbarDemo",
        id : "cusDevPlanListTable",
        cols : [[
            {type: "checkbox", fixed:"center"},
            {field: "id", title:'编号',fixed:"true"},
            {field: 'planItem', title: '计划项',align:"center"},
            {field: 'exeAffect', title: '执行效果',align:"center"},
            {field: 'planDate', title: '执行时间',align:"center"},
            {field: 'createDate', title: '创建时间',align:"center"},
            {field: 'updateDate', title: '更新时间',align:"center"},
            {title: '操作',fixed:"right",align:"center", minWidth:150,templet:"#cusDevPlanListBar"}
        ]]
    });

});
           

4.2.4. 计划项数据列表查询

4.2.4.1. 接口定义

CusDevPlanQuery.java 查询类定义

package com.xxxx.crm.query;

import com.xxxx.crm.base.BaseQuery;

public class CusDevPlanQuery extends BaseQuery {

    private Integer sId;

    public Integer getsId() {
        return sId;
    }

    public void setsId(Integer sId) {
        this.sId = sId;
    }
}
           
4.2.4.2. 设置SQL

CusDevPlanMapper.xml 核心 SQL 配置

<!--多条件分页查询客户计划表-->
  <select id="queryByParams" parameterType="int" resultType="cusDevPlan">
    select
        *
    from
        t_cus_dev_plan
    where
        is_valid = 1
    and
        sale_chance_id = #{sId}
  </select>
           
4.2.4.3. Service

CusDevPlanService.java

/*多条件分页查询客户计划表*/
    public Map<String, Object> queryByParams(CusDevPlanQuery query){
        Map<String, Object> map = new HashMap<>();
        //开启分页
        PageHelper.startPage(query.getPage(),query.getLimit());
        List<CusDevPlan> cusDevPlans = cusDevPlanMapper.queryByParams(query);
        //按照分页条件,格式化数据
        PageInfo<CusDevPlan> cusDevPlanPageInfo = new PageInfo<>(cusDevPlans);

        map.put("code",0);
        map.put("msg","");
        map.put("count",cusDevPlanPageInfo.getTotal());
        map.put("data",cusDevPlanPageInfo.getList());
        return map;
    }
           
4.2.4.4. Controller

CusDevPlanController.java 列表方法

/*多条件分页查询客户计划表*/
    @RequestMapping("list")
    @ResponseBody
    public Map<String, Object> queryByParams(CusDevPlanQuery query){
        return cusDevPlanService.queryByParams(query);
    }
           

4.3. 开发计划项添加与更新

4.3.1. 页面效果

4.3.2. 计划项添加

4.3.2.1. 实现思路
/**
     * 添加数据
     *      1.数据校验
     *          saleChanceId   营销机会的id  非空 / 数据存在
     *          计划内容         非空
     *          计划时间         非空
     *      2.设置默认值
     *          is_valid     数据是否有效
     *          create_date
     *          update_date
     *      3.执行添加操作,判断
     */
           
4.3.2.2. 核心代码

CusDevPlanService.java

@Transactional
    public void addCusDevPan(CusDevPlan cusDevPlan){
        // 数据校验
        checkParams(cusDevPlan.getSaleChanceId(),cusDevPlan.getPlanItem(),cusDevPlan.getPlanDate());
        //设置默认值
        cusDevPlan.setIsValid(1);
        cusDevPlan.setCreateDate(new Date());
        cusDevPlan.setUpdateDate(new Date());
        //执行添加操作,判断
        AssertUtil.isTrue(cusDevPlanMapper.insertSelective(cusDevPlan) < 1,"计划添加失败");
    }

/**
     * 校验添加和修改的数据
     *      saleChanceId   营销机会的id  非空 / 数据存在
     *      计划内容         非空
     *      计划时间         非空
     * @param saleChanceId
     * @param planItem
     * @param planDate
     */
    private void checkParams(Integer saleChanceId, String planItem, Date planDate) {
        AssertUtil.isTrue(null == saleChanceId ||
                null == saleChanceMapper.selectByPrimaryKey(saleChanceId),"营销机会数据不存在");
        AssertUtil.isTrue(StringUtils.isBlank(planItem),"计划内容不能为空");
        AssertUtil.isTrue(null == planDate,"计划日期不能为空");
    }
           

CusDevPlanController.java

/**
     * 计划项数据的添加
     * @param cusDevPlan
     * @return
     */
    @PostMapping("save")
    @ResponseBody
    public ResultInfo addCusDevPan(CusDevPlan cusDevPlan){
        cusDevPlanService.addCusDevPan(cusDevPlan);
        return success("计划添加成功");
    }
           

4.3.3. 计划项更新

4.3.3.1. 实现思路
/**
     * 修改数据
     *      1.数据校验
     *          计划项 id       非空/数据存在
     *          saleChanceId   营销机会的id  非空 / 数据存在
     *          计划内容         非空
     *          计划时间         非空
     *      2.设置默认值
     *          update_date
     *      3.执行添加操作,判断
     */
           
4.3.3.2. 核心代码

CusDevPlanService.java

@Transactional
    public void updateCusDevPan(CusDevPlan cusDevPlan){
        AssertUtil.isTrue(cusDevPlan.getId() == null || null == cusDevPlanMapper.selectByPrimaryKey(cusDevPlan.getId()),"计划项数据异常");
        // 数据校验
        checkParams(cusDevPlan.getSaleChanceId(),cusDevPlan.getPlanItem(),cusDevPlan.getPlanDate());
        //设置默认值
        cusDevPlan.setUpdateDate(new Date());
        //执行添加操作,判断
        AssertUtil.isTrue(cusDevPlanMapper.updateByPrimaryKeySelective(cusDevPlan) < 1,"计划修改失败");
    }
           

CusDevPlanController.java

@PostMapping("update")
    @ResponseBody
    public ResultInfo updateCusDevPan(CusDevPlan cusDevPlan){
        cusDevPlanService.updateCusDevPan(cusDevPlan);
        return success("计划修改成功");
    }
           

CusDevPlan.java

设置参数为Date类型时,想要传入的时间格式

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date planDate;
           

4.3.4. 前端核心代码

4.3.4.1. 工具栏与行监听事件

cus.dev.plan.data.js

/**
     * 监听头部工具栏
     */
    table.on('toolbar(cusDevPlans)', function(obj){
        var checkStatus = table.checkStatus(obj.config.id);
        console.log(checkStatus.data);
        switch(obj.event){
            case 'add':
                addOrUpdateCusDevPlanDialog();
                break;
            case 'success':
                updateDevResult(2);
                break;
            case 'failed':
                updateDevResult(3);
                break;
        };
    });

    /**
     * 监听行工具栏
     */
    table.on('tool(cusDevPlans)', function(obj){
        //编辑/修改功能
        if(obj.event == "edit"){
            addOrUpdateCusDevPlanDialog(obj.data.id);
        }else if(obj.event == "del"){
            // 询问用户是否确认删除
            layer.confirm("确定删除当前数据?", {icon:3, title:"开发计划管理"}, function (index) {
                $.post(ctx+"/cus_dev_plan/delete",{id:obj.data.id},function (data){
                    if(data.code == 200){
                        //关闭确认框
                        layer.close(index);
                        //刷新页面
                        window.location.reload();
                        //关闭
                        // tableIns.reload();
                    }else{
                        layer.msg(data.msg,{icon:5});
                    }
                });


            });
        }
    });



    //打开计划项修改添加窗口
    function addOrUpdateCusDevPlanDialog(id){
        //添加操作
        var title = "<h2>计划项管理-添加计划项</h2>";
        var url = ctx+"/cus_dev_plan/toAddOrUpdatePage?sId="+$('[name="id"]').val(); //jquery获取salechanceId

        //修改操作有id值
        if(id){
            title = "<h2>计划项管理-修改计划项</h2>";
            url += "&id="+id;
        }

        //通过layui iframe打开
        layer.open({
            type:2,
            title:title,
            content:url,
            maxmin:true,
            area:["500px","300px"]
        });


    }
           
4.3.4.2. 视图转发方法

CusDevPlanController.java

添加updateCusDevPan 转发计划项添加与更新表单页面

/**
     * 转发跳转到计划数据项页面
     * @return
     */
    @RequestMapping("toAddOrUpdatePage")
    public String updateCusDevPan(Integer id,Integer sId,HttpServletRequest request){
        //修改操作有id
        if(id != null){
            CusDevPlan cusDevPlan = cusDevPlanService.selectByPrimaryKey(id);
            AssertUtil.isTrue(null == cusDevPlan,"计划项数据异常请重试");
            request.setAttribute("cusDevPlan",cusDevPlan);
        }
        request.setAttribute("sId",sId);
        return "cusDevPlan/add_update";
    }
           
4.3.4.3. 页面

add_update.ftl

views/cusDevPlan 目录下添加 add_update.ftl 文件

<!DOCTYPE html>
<html>
    <head>
        <#include "../common.ftl">
    </head>
    <body class="childrenBody">
        <form class="layui-form" style="width:80%;">
            <input name="id" type="hidden" value="${(cusDevPlan.id)!}"/>
            <input name="saleChanceId" type="hidden" value="${sId!}"/>
            <div class="layui-form-item layui-row layui-col-xs12">
                <label class="layui-form-label">计划项内容</label>
                <div class="layui-input-block">
                    <input type="text" class="layui-input userName" lay-verify="required" name="planItem"
                           id="planItem" value="${(cusDevPlan.planItem)!}" placeholder="请输入计划项内容">
                </div>
            </div>
            <div class="layui-form-item layui-row layui-col-xs12">
                <label class="layui-form-label">计划时间</label>
                <div class="layui-input-block">
                    <#if (cusDevPlan.planDate)??>
                        <input type="date" class="layui-input userName" lay-verify="required" name="planDate"
                               id="planDate" placeholder="请输入计划项时间" value="${(cusDevPlan.planDate)?string("yyyy-MM-dd")}" >
                        <#else>
                        <input type="date" class="layui-input userName" lay-verify="required" name="planDate"
                               id="planDate"  placeholder="请输入计划项时间">
                    </#if>
                </div>
            </div>
            <div class="layui-form-item layui-row layui-col-xs12">
                <label class="layui-form-label">执行效果</label>
                <div class="layui-input-block">
                    <input type="text" class="layui-input userEmail" lay-verify="required" name="exeAffect"
                           value="${(cusDevPlan.exeAffect)!}" id="exeAffect" placeholder="请输入执行效果">
                </div>
            </div>


            <br/>
            <div class="layui-form-item layui-row layui-col-xs12">
                <div class="layui-input-block">
                    <button class="layui-btn layui-btn-lg" lay-submit="" lay-filter="addOrUpdateCusDevPlan">确认</button>
                    <button class="layui-btn layui-btn-lg layui-btn-normal" id="closeBtn">取消</button>
                </div>
            </div>
        </form>

    <script type="text/javascript" src="${ctx}/js/cusDevPlan/add.update.js"></script>
    </body>
</html>
           
4.3.4.4. JS文件

add.update.js

js/cusDevPlan 目录下添加 add.update.js 文件,实现计划项数据添加与更新表单提交

layui.use(['form', 'layer','laydate'], function () {
    var form = layui.form,
        laydate = layui.laydate,
        layer = parent.layer === undefined ? layui.layer : top.layer,
        $ = layui.jquery;

    //常规用法
    laydate.render({
        elem: '#planDate'
    });

    /**
     * 添加或更新计划项
     */
    form.on("submit(addOrUpdateCusDevPlan)", function (data) {
        // 弹出loading层
        var index = top.layer.msg('数据提交中,请稍候', {icon: 16, time: false, shade: 0.8});
        var url = ctx+"/cus_dev_plan/save";

        if($('[name="id"]').val()){
            url = ctx+"/cus_dev_plan/update";
        }

        $.post(url,data.field,function (data){
            if(data.code == 200){
                //关闭弹出框
                layer.close(index);
                //关闭iframe层
                layer.closeAll("iframe");
                //刷新父页面,将添加的新数据展示
                parent.location.reload();
            }else{
                layer.msg(data.msg,{icon:5})
            }
        });
        return false; //阻止表单提交
    });

    /**
     * 关闭弹出层
     */
    $("#closeBtn").click(function () {
        var index = parent.layer.getFrameIndex(window.name); //先得到当前iframe层的索引
        parent.layer.close(index); //再执行关闭
    });

});
           
4.3.4.5. 关闭弹出层

add_update.ftl

给按钮设置 id 属性值,通过 id 绑定点击事件

<div class="layui-input-block">
                    <button class="layui-btn layui-btn-lg" lay-submit="" lay-filter="addOrUpdateCusDevPlan">确认</button>
                    <button class="layui-btn layui-btn-lg layui-btn-normal" id="closeBtn">取消</button>
                </div>
           

add.update.js

/**
     * 关闭弹出层
     */
    $("#closeBtn").click(function () {
        var index = parent.layer.getFrameIndex(window.name); //先得到当前iframe层的索引
        parent.layer.close(index); //再执行关闭
    });
           

4.4. 开发计划项删除

4.4.1. 后端代码实现

CusDevPlanService.java

/**
     * 删除时计划项
     * @param id
     */
    @Transactional
    public void delete(Integer id) {
        CusDevPlan cusDevPlan = cusDevPlanMapper.selectByPrimaryKey(id);
        AssertUtil.isTrue(id == null || null == cusDevPlan,"待删除计划项不存在");
        //设置数据失效
        cusDevPlan.setIsValid(0);
        AssertUtil.isTrue(cusDevPlanMapper.updateByPrimaryKeySelective(cusDevPlan) < 1,"计划项删除失败");
    }
           

CusDevPlanController.java

@PostMapping("delete")
    @ResponseBody
    public ResultInfo delete(Integer id){
        cusDevPlanService.delete(id);
        return success();
    }
           

4.4.2. 前端核心代码

cus.dev.plan.data.js

修改 js/cusDevPlan 目录下 cus.dev.plan.data.js,添加删除行监听事件

/**
     * 监听行工具栏
     */
    table.on('tool(cusDevPlans)', function(obj){
        //编辑/修改功能
        if(obj.event == "edit"){
            addOrUpdateCusDevPlanDialog(obj.data.id);
        }else if(obj.event == "del"){
            // 询问用户是否确认删除
            layer.confirm("确定删除当前数据?", {icon:3, title:"开发计划管理"}, function (index) {
                $.post(ctx+"/cus_dev_plan/delete",{id:obj.data.id},function (data){
                    if(data.code == 200){
                        //关闭确认框
                        layer.close(index);
                        //刷新页面
                        window.location.reload();
                        //关闭
                        // tableIns.reload();
                    }else{
                        layer.msg(data.msg,{icon:5});
                    }
                });


            });
        }
    });
           

4.5. 营销机会数据开发状态更新

4.5.1. 后端代码实现

状态更新的设计表为 t_sale_chance 营销机会表,所以更新的核心代码在营销机会模块。

SaleChanceService.java

/**
     * 更新开发状态
     *  1.校验参数
     *      非空
     *  2.更新的数据必须存在
     * @param id
     * @param devResult
     */
    @Transactional
    public void updateDevResult(Integer id,Integer devResult){
        SaleChance saleChance = saleChanceMapper.selectByPrimaryKey(id);
        AssertUtil.isTrue(id == null || null == saleChance,"更新数据不存在");
        AssertUtil.isTrue(null == devResult,"更新状态状态不存在");
        //设置状态
        saleChance.setDevResult(devResult);
        saleChance.setUpdateDate(new Date());
        AssertUtil.isTrue(saleChanceMapper.updateByPrimaryKeySelective(saleChance) < 1,"状态更新失败");
    }
           

SaleChanceController.java

/**
     * 更新开发状态
     * @param id
     * @param devResult
     * @return
     */
    @PostMapping("updateDevResult")
    @ResponseBody
    public ResultInfo updateDevResult(Integer id,Integer devResult){
        saleChanceService.updateDevResult(id,devResult);
        return success();
    }
           

4.5.2. 前端核心代码

修改cus.dev.plan.data.js 添加表格头监听事件(开发成功,开发失败)

/**
     * 监听头部工具栏
     */
    table.on('toolbar(cusDevPlans)', function(obj){
        var checkStatus = table.checkStatus(obj.config.id);
        console.log(checkStatus.data);
        switch(obj.event){
            case 'add':
                addOrUpdateCusDevPlanDialog();
                break;
            case 'success':
                updateDevResult(2);
                break;
            case 'failed':
                updateDevResult(3);
                break;
        };
    });

    //更新营销机会的开发状态
    function updateDevResult(devResult){
        var id = $('[name="id"]').val();
        $.post(ctx+"/sale_chance/updateDevResult",{id:id,devResult:devResult},function (data){
            if(data.code == 200){
                //刷新页面
                parent.location.reload();
                //关闭
                // tableIns.reload();
            }else{
                layer.msg(data.msg,{icon:5});
            }
        });
    }