crm客户关系管理系统(vue+maven多模块+SSM)

5 篇文章 0 订阅
4 篇文章 0 订阅

一、技术介绍

1.1 maven多模块

这里写了maven多模块的介绍和为什么要使用maven多模块,后面还有maven多模块项目搭建实战。

https://blog.csdn.net/qq_36892672/article/details/103836614

1.2 前后端分离

  • vue基础:

https://blog.csdn.net/qq_36892672/article/details/103772412

  • vue进阶:

https://blog.csdn.net/qq_36892672/article/details/103787320

  • element ui :

https://blog.csdn.net/qq_36892672/article/details/103809746

  • 项目使用vue-admin-master,这个在网上有很多的教程,这里就不做详解了,这是一个基于vue的前端后台管理的demo,我们只需要在其中加上我们自己的模块就好了。

//download.csdn.net/download/qq_36892672/12106039

1.3 SSM框架

SSM集成:

https://blog.csdn.net/qq_36892672/article/details/103752919

1.4 第三方登录

1.5 Lucene搜索引擎

1.6 Saas模式

SAAS:软件即服务

本系统是基于saas服务的。统一开放,维护,租户(注册付费的公司)需要在本系统中进行注册,并付费,然后根据付费情况使用系统功能。

多租户(Multi Tenancy/Tenant)是一种软件架构,其定义是:在一台服务器上运行单个应用实例,它为多个租户提供服务。

我们项目是基于saas的,提供给其他公司进行付费使用,租户需要先在平台完成注册和付费。

我们项目采用的是:
共享数据库,共享scheme,共享数据库表,通过租户id进行区别:

所有租户的数据都存放在一个数据库的同一套表中, 在表中增加tenant_id标志字段,表明该记录是属于哪个租户的。

优点:数据源和数据库的管理都比较简单。和原来的应用没有差别。

缺点:数据权限比较复杂,增加程序的复杂性。如果应用比较复杂,很多数据表都需要加入客户标志字段,很多查询都需要包括该字段,会比较麻烦。如果有遗漏,、特别是查询条件中遗漏该字段,就会造成一个客户看到另一个客户的数据。

1.7 shiro权限管理

shiro权限管理:

https://blog.csdn.net/qq_36892672/article/details/103541985

二、项目需求分析

2.1 需求文档

2.2 系统模块图

系统模块图

2.3 数据库设计

数据库sql语句见附件。

//download.csdn.net/download/qq_36892672/12106112

2.4 模块详解

2.4.1 订单管理:(t_order)

  • 业务描述:用户缴纳定金后需要将定金合同录入系统,
    增加、删除(批量删除、根据id删除)、修改、查询(条件:单号、签订时间范围、营销人员、客户、状态)、生成合同
    sn:yyyyMMdd-XXXX,自动生成
    -定金客户:从客户池中选取
    签订时间:手动录入yyyy-MM-dd
    营销人员:从当前登录的用户中获取
  • 模型:
    数据库模型
  • 使用人员:营销人员

2.4.2 合同管理:(t_ontract)

  • 业务描述:客户确认购买公司时由订单管理生成合同,对合同进行修改保存,保存的时候将将对应订单的状态改为已签订合同,同时生成保修单。
    搜索:(客户、合同单号、订单单号、签订时间)
    新增:选择未签订合同订单。
    模型:
    数据库模型
  • 合同付款明细(t_ontract_detail)
    数据库模型
  • 使用人员:营销人员

2.4.3 保修管理:(t_repair)

  • 业务描述:保修单由合同生成是自动生成,只做修改,即增加保修明细。
  • 模型:
    数据库模型
  • 保修单明细(t_repair_detail)
  • 数据库模型
  • 使用人员:客服人员

2.4.4 业务流程

销售人员在线下签订订单录入,录入后可以录入信息进行修改删除和生成合同,一个订单只能生成一个合同,生成后禁用生成合同,若将该合格删除,那么生成合同恢复,生成的合同字段不完整,一些信息需要手动录入和修改,合同签订可以生成保单,一个合同可能对应多个保单,所以生成保单后可以继续生成,不禁用生成保单。保单和合同的详情采用可编辑表格手动录入。

三、(负责模块)开发

3.1 订单管理

3.1.1 vue

<template>
    <div>
        <!--查询的form表单-->
        <el-form style="margin-top: 20px" :inline="true" :model="searchForm" class="demo-form-inline">
            <el-form-item label="状态">
                <el-select v-model="searchForm.status" placeholder="请选择状态">
                    <el-option label="已签合同" value="1"></el-option>
                    <el-option label="未签合同" value="0"></el-option>
                    <el-option label="定金失效" value="-1"></el-option>
                </el-select>
            </el-form-item>
            <el-form-item label="签订时间">
                <el-col :span="11">
                    <el-date-picker
                            v-model="searchForm.minSignTime"
                            type="date"
                            value-format="yyyy-MM-dd"
                            placeholder="选择日期">
                    </el-date-picker>
                </el-col>
                <el-col class="line" :span="2">-</el-col>
                <el-col :span="11">
                    <el-date-picker
                            v-model="searchForm.maxSignTime"
                            type="date"
                            value-format="yyyy-MM-dd"
                            placeholder="选择日期">
                    </el-date-picker>
                </el-col>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="searchSubmit">查询</el-button>
            </el-form-item>
        </el-form>
        <!--批量删除-->
        <el-button type="danger" style="float: left;margin-bottom:20px;margin-left: 20px" @click="batchRemove"
                   :disabled="this.selectRows.length===0">批量删除
        </el-button>
        <!--添加-->
        <el-button type="primary" style="float: left;margin-bottom:20px;" @click="add">新增</el-button>
        <!--
          :data  根据数据展示列表
          @selection-change: 获取选中列表触发事件(返回的是选中的对象列表)
           v-loading="loading" 值为true代表是加载效果
        -->
        <el-table
                :data="tableData"
                v-loading="loading"
                style="width: 100%"
                @selection-change="selectionChange"
        >
            <el-table-column fixed type="selection" width="55">
            </el-table-column>
            <el-table-column type="index" fixed label="序号" width="70px" sortable="true">
            </el-table-column>
            <el-table-column width="140px" fixed prop="sn" label="单号" sortable="true"></el-table-column>
            <el-table-column width="100px" prop="customer.name" label="订单客户"></el-table-column>
            <el-table-column width="130px" prop="signTime" label="签订时间" sortable="true"></el-table-column>
            <el-table-column width="130px" prop="dueToTime" label="到期时间"></el-table-column>
            <el-table-column width="100px" prop="seller.realName" label="营销人员"></el-table-column>
            <el-table-column width="100px" prop="sum" label="金额" sortable="true"></el-table-column>
            <el-table-column width="100px" prop="status" label="状态">
                <template slot-scope="scope">
                    <el-tag
                            :type="scope.row.status == 1 ? 'success' :scope.row.status == 0 ? 'info' : 'danger'"
                            disable-transitions>{{scope.row.status == 1 ? '已签合同' : scope.row.status == 0 ? '未签合同' :
                        '失效'}}
                    </el-tag>
                </template>
            </el-table-column>
            <el-table-column width="200px" prop="intro" label="摘要"></el-table-column>

            <!--操作-->
            <el-table-column label="操作" width="250px" fixed="right">
                <template slot-scope="scope">
                    <el-button
                            l-tabl size="small"
                            @click="handleEdit(scope.$index, scope.row)">编辑
                    </el-button>
                    <el-button type="warning"
                               @click="createContract(scope.$index, scope.row)"
                               size="small"
                               :disabled="scope.row.status!=0">生成合同
                    </el-button>
                    <el-button
                            size="small"
                            type="danger"
                            @click="handleDelete(scope.$index, scope.row)">删除
                    </el-button>
                </template>
            </el-table-column>
        </el-table>
        <!--分页-->
        <el-pagination style="margin-top: 10px;float: right"
                       @size-change="sizeChange"
                       @current-change="currentChange"
                       :current-page="currentPage"
                       :page-sizes="pageSizes"
                       :page-size="pageSize"
                       layout="total, sizes, prev, pager, next, jumper"
                       :total="total">
        </el-pagination>
        <!--修改、添加弹出框-->
        <el-dialog :title="title" :visible.sync="visible" :close-on-click-modal="false">
            <el-form :model="form" label-width="80px" :rules="formRules" ref="form">
                <el-form-item label="单号">
                    <el-input v-model="form.sn" auto-complete="off"></el-input>
                </el-form-item>
                <el-form-item label="订单客户">
                    <!--@change="optionChange"-->
                    <el-select v-model="form.customerId" placeholder="请选择客户">
                        <el-option
                                v-for="item in customers"
                                :key="item.id"
                                :label="item.name"
                                :value="item.id">
                        </el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="签订时间">
                    <el-date-picker
                            v-model="form.signTime"
                            type="date"
                            value-format="yyyy-MM-dd"
                            placeholder="选择日期">
                    </el-date-picker>
                </el-form-item>
                <el-form-item label="到期时间">
                    <el-date-picker
                            v-model="form.dueToTime"
                            type="date"
                            value-format="yyyy-MM-dd"
                            placeholder="选择日期">
                    </el-date-picker>
                </el-form-item>
                <el-form-item label="金额">
                    <el-input v-model="form.sum" auto-complete="off"></el-input>
                </el-form-item>
                <el-form-item label="摘要">
                    <el-input
                            type="textarea"
                            autosize
                            placeholder="请输入内容"
                            v-model="form.intro">
                    </el-input>
                </el-form-item>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click.native="visible = false">取消</el-button>
                <el-button type="primary" @click.native="submit" :loading="editLoading">提交</el-button>
            </div>
        </el-dialog>

    </div>
</template>

<script>
    export default {
        name: 'Order',
        data() {
            return {
                currentPermissions:[],
                loading: true,
                tableData: [],
                selectRows: [],
                customers: [],

                //分页相关
                //当前页
                currentPage: 1,
                //每页条数集合
                pageSizes: [5, 10, 15, 20],
                //每页条数
                pageSize: 10,
                //总数
                total: 0,

                //修改、增加相关
                //是否可见
                visible: false,
                //标题
                title: "",
                //搜索条件form model
                searchForm: {
                    status: '',
                    minSignTime: '',
                    maxSignTime: ''

                },
                //修改、新增form model
                form: {
                    customerId: '',
                    id: "",
                    sn: "",
                    customer: null,
                    signTime: '',
                    dueToTime: '',
                    sum: '',
                    intro: ''
                },
                formRules: {
                    name: [
                        {required: true, message: '请输入部门', trigger: 'blur'}
                    ]
                },
                editLoading: false
            };
        },
        methods: {
            //加载列表
            loadOrderList() {
                //查询传递参数
                let params = {
                    "currentPage": this.currentPage,
                    "pageSize": this.pageSize,
                    status: this.searchForm.status,
                    minSignTime: this.searchForm.minSignTime,
                    maxSignTime: this.searchForm.maxSignTime
                };
                this.$http.patch("/allUserPermissions").then(res => {
                    this.currentPermissions=res.data;
                });
                this.loading = true;
                this.$http.patch("/order/selectPageByQuery", params).then(res => {
                    this.tableData = res.data.list;
                    this.total = res.data.total;
                    this.loading = false;
                });
            },
            //多选
            selectionChange(v) {
                this.selectRows = v;
            },
            /*optionChange(v){
                console.log(v);
                this.form.customer.id = v;
            },*/
            //每页条数改变
            sizeChange(v) {
                this.currentPage = 1;
                this.pageSize = v;
                this.loadOrderList();
                //加载框
                this.loading = true;
            },
            //当前页改变
            currentChange(v) {
                this.currentPage = v;
                this.loadOrderList();
                //加载框
                this.loading = true
            },
            //删除一条数据
            handleDelete(i, r) {
                if(this.currentPermissions.indexOf("/order/delete")===-1){
                    this.$message({type: 'info',message:"没有权限!"});return;
                }
                this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    this.$http.delete("/order/delete/" + r.id).then(res => {
                        if (res.data.success) {
                            this.$message({
                                type: 'success',
                                message: r.sn + "删除成功"
                            });
                        } else {
                            this.$message({
                                type: 'error',
                                message: res.data.msg
                            });
                        }
                        this.loadOrderList();
                    })
                }).catch(() => {

                });
            },
            //批量删除
            batchRemove() {
                if(this.currentPermissions.indexOf("/order/batchDelete")===-1){
                    this.$message({type: 'info',message:"没有权限!"});return;
                }
                if (!this.selectRows.length) {
                    this.$message.error('亲!请选中数据进行删除哦!!');
                    return;
                }
                this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    this.$http.patch("/order/batchDelete", this.selectRows).then(res => {
                        if (res.data.success) {
                            if (res.data.success) {
                                this.$message({
                                    type: 'success',
                                    message: "共删除" + this.selectRows.length + "条数据"
                                });
                            } else {
                                this.$message({
                                    type: 'error',
                                    message: res.data.msg
                                });
                            }
                            this.loadOrderList();
                        }
                    })
                }).catch(() => {

                });
            },
            //新增
            add() {
                if(this.currentPermissions.indexOf("/order/save")===-1){
                    this.$message({type: 'info',message:"没有权限!"});return;
                }
                this.$http.patch("/customer/list").then(res => {
                    this.customers = res.data;
                });
                for (let k in this.form) {
                    this.form[k] = '';
                }
                this.visible = true;
                this.title = "添加订单";
            },
            //修改
            handleEdit(i, r) {
                if(this.currentPermissions.indexOf("/order/update")===-1){
                    this.$message({type: 'info',message:"没有权限!"});return;
                }
                this.$http.patch("/customer/list").then(res => {
                    this.customers = res.data;
                });
                for (let k in r) {
                    this.form[k] = r[k];
                }
                this.form.customerId = r.customer.id;
                console.log(this.form);
                this.visible = true;
                this.title = "更新订单";
            },
            //提交修改或增加
            submit() {
                this.$refs.form.validate((valid) => {
                    if (valid) {
                        this.editLoading = true;
                        //NProgress.start();
                        let para = Object.assign({}, this.form);
                        let customer = {
                            id: ''
                        };
                        customer.id = para.customerId;
                        para.customer = customer;
                        if (this.form.id) {
                            this.$http.post("/order/update", para).then((res) => {
                                this.editLoading = false;
                                //NProgress.done();
                                if (res.data.success) {
                                    this.$message({
                                        message: '提交成功',
                                        type: 'success'
                                    });
                                } else {
                                    this.$message({
                                        message: res.data.msg,
                                        type: 'error'
                                    });
                                }
                                this.$refs['form'].resetFields();
                                this.visible = false;
                                this.loadOrderList();
                            });
                        }
                        else {
                            this.editLoading = false;
                            this.$http.put("/order/save", para).then((res) => {
                                this.editLoading = false;
                                //NProgress.done();
                                if (res.data.success) {
                                    this.$message({
                                        message: '提交成功',
                                        type: 'success'
                                    });
                                } else {
                                    this.$message({
                                        message: res.data.msg,
                                        type: 'error'
                                    });
                                }
                                this.$refs['form'].resetFields();
                                this.visible = false;
                                this.loadOrderList();
                            });

                        }
                    }
                });
            },
            //生成合同
            createContract(i,r) {
                if(this.currentPermissions.indexOf("/order/createContract")===-1){
                    this.$message({type: 'info',message:"没有权限!"});return;
                }
                this.$http.post("/order/createContract", r).then((res) => {
                    if (res.data.success) {
                        this.$message({
                            message: '生成合同成功!'+res.data.msg,
                            type: 'success'
                        });
                    } else {
                        this.$message({
                            message: res.data.msg,
                            type: 'error'
                        });
                    }
                    this.loadOrderList();
                });
            },

            //搜索框
            searchSubmit() {
                this.loadOrderList();
            }
        },
        mounted() {
            //当页面加载完毕的时候发送ajax请求
            this.loadOrderList();
        }
    }
</script>

<style scoped>

</style>

routes.js

//增加
import Order from './views/system/Order.vue'
import Contract from './views/system/Contract.vue'
import Repair from './views/system/Repair.vue'
.....
let routes = [
    .....
	 {
        path: '/',
        component: Home,
        name: '订单合同管理',
        iconCls: 'fa fa-id-card-o',
        children: [
            { path: '/order', component: Order, name: '定金订单管理' },
            { path: '/contract', component: Contract, name: '合同管理' }
        ]
    },
    {
        path: '/',
        component: Home,
        name: '售后管理',
        iconCls: 'fa fa-address-card',
        leaf: true,//只有一个节点
        children: [
            { path: '/repair', component: Repair, name: '售后管理' }
        ]
    }
];

3.1.2 controller

package com.cl.crm.web.controller;
import com.cl.base.util.AjaxResult;
import com.cl.crm.domain.Order;
import com.cl.crm.query.OrderQuery;
import com.cl.crm.service.IOrderService;
import com.cl.crm.shiro.util.UserContext;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@RequestMapping("/order")
@CrossOrigin
public class OrderController {

    @Autowired
    private IOrderService orderService;



    /**
     * resetful: 它是一个架构风格,它是基于Http协议的扩展
     * 它给你提供了多种请求方式来定位资源
     *          GET      一般是用来做查询的,查询单个对象
     *          POST     一般用来做修改的
     *          DELETE   一般用来做删除
     *          PUT      一般用来做新增
     *          PATCH    一般用来操作批量数据的
     * @return
     */
    @RequestMapping(value = "/list",method = RequestMethod.PATCH)
    @ResponseBody
    public List<Order> list(){
        return orderService.selectAll();
    }

    /**
     * 条件查询
     * @param query
     * @return
     */
    @RequestMapping(value = "/selectByQuery",method = RequestMethod.PATCH)
    @ResponseBody
    public List<Order> selectByQuery(@RequestBody OrderQuery query){
        query.setTenantId(UserContext.getEmployee().getTenantId());
        return orderService.selectByQuery(query);
    }

    /**
     * 条件分页查询
     * @param query
     * @return
     */
    @RequestMapping(value = "/selectPageByQuery",method = RequestMethod.PATCH)
    @ResponseBody
    public PageInfo<Order> selectPageByQuery(@RequestBody OrderQuery query){
        query.setTenantId(UserContext.getEmployee().getTenantId());
        return orderService.selectPageByQuery(query);
    }


    /**
     * 添加订单
     * @param order
     * @return
     *
     * @RequestBody:前端传递json对象,它会自动转为Order对象
     *
     */
    @RequestMapping(value = "/save",method = RequestMethod.PUT)
    @ResponseBody
    public AjaxResult save(@RequestBody Order order){
        try {
            order.setSeller(UserContext.getEmployee());
            order.setTenantId(UserContext.getEmployee().getTenantId());
            orderService.insert(order);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "添加失败!"+e.getMessage());
        }
    }

    /**
     * 修改订单
     * @param order
     * @return
     */
    @RequestMapping(value = "/update",method = RequestMethod.POST)
    @ResponseBody
    public AjaxResult update(@RequestBody Order order){
        try {
            orderService.update(order);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "修改失败!"+e.getMessage());
        }
    }
    /**
     * 生成合同,修改订单状态。
     * @param order
     * @return
     */
    @RequestMapping(value = "/createContract",method = RequestMethod.POST)
    @ResponseBody
    public AjaxResult createContract(@RequestBody Order order){
        try {
           Long contractSn = orderService.updateStatus(order);
            return new AjaxResult(true,"合同编号"+contractSn);
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "修改失败!"+e.getMessage());
        }
    }


    /**
     * 删除
     * @param id
     * @return
     */
    @RequestMapping(value = "/delete/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public AjaxResult delete(@PathVariable("id") Long id){
        try {
            orderService.delete(id);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "删除失败!"+e.getMessage());
        }
    }

    /**
     * 批量删除
     * @param orders
     * @return
     */
    @RequestMapping(value = "/batchDelete",method = RequestMethod.PATCH)
    @ResponseBody
    public AjaxResult batchDelete(@RequestBody List<Order> orders){
        try {
            orderService.batchDelete(orders);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "删除失败!"+e.getMessage());
        }
    }

    /**
     * 查询单个对象
     * @param id
     * @return
     */
    @RequestMapping(value = "/get/{id}",method = RequestMethod.GET)
    @ResponseBody
    public Order get(@PathVariable("id") Long id){
        return orderService.selectById(id);
    }

}

3.1.3 service接口


public interface IOrderService extends IBaseService<Order,Long> {
    Long updateStatus(Order order);
}

3.1.3.1 serviceImpl
@Service
public class OrderServiceImpl extends BaseServiceImpl<Order,Long> implements IOrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private ContractMapper contractMapper;

    @Override
    public BaseMapper<Order, Long> baseMapper() {
        return orderMapper;
    }

    @Override
    public Long updateStatus(Order order) {
        //生成合同对象
        Contract contract = new Contract();
        contract.setCustomer(order.getCustomer());
        contract.setOrder(order);
        contract.setSn(order.getSn());
        contract.setSeller(order.getSeller());
        contract.setTenantId(order.getTenantId());
        //将订单状态改为已签合同
        order.setStatus(1);

        orderMapper.update(order);

        contractMapper.insert(contract);
        return contract.getSn();
    }
}

3.1.4 mapper接口

public interface OrderMapper extends BaseMapper<Order,Long> {
}

3.1.5 mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cl.crm.mapper.OrderMapper">
    <resultMap id="sqlMap" type="Order">
        <id column="id" property="id"/>
        <result column="sn" property="sn"/>
        <result column="signTime" property="signTime"/>
        <result column="dueToTime" property="dueToTime"/>
        <result column="sum" property="sum"/>
        <result column="intro" property="intro"/>
        <result column="status" property="status"/>
        <result column="tenantId" property="tenantId"/>
        <association property="customer" javaType="Customer">
            <id column="cuid" property="id"/>
            <result column="cuname" property="name"/>
        </association>
        <association property="seller" javaType="Employee">
            <id column="eid" property="id"/>
            <result column="erealName" property="realName"/>
        </association>
    </resultMap>
    <sql id="column">
        o.id,
        o.sn,
        o.signTime,
        o.dueToTime,
        o.sum,
        o.intro,
        o.status,
        o.tenant_id AS tenantId,
        cu.id AS cuid,
        cu.name AS cuname,
        e.id AS eid,
        e.realName AS erealName
    </sql>
    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        INSERT INTO
        t_order(sn,customer_id,signTime,dueToTime,seller_id,sum,intro,status,mark,tenant_id)
        VALUES (#{sn}, #{customer.id},#{signTime},#{dueToTime},#{seller.id},
        #{sum},#{intro},#{status},#{mark},#{tenantId})
    </insert>

    <delete id="delete">
        UPDATE t_order set mark =FALSE WHERE id=#{id}
    </delete>

    <delete id="batchDelete">
        UPDATE t_order set mark =FALSE WHERE id IN
        <foreach collection="list" open="(" close=")" item="item" separator=",">
            #{item.id}
        </foreach>
    </delete>
    <update id="update">
        update t_order
        <set>
            <if test="sn!=null">
                sn = #{sn},
            </if>
            <if test="customer!=null and customer.id!=null">
                customer_id = #{customer.id},
            </if>
            <if test="signTime!=null">
                signTime = #{signTime},
            </if>
            <if test="dueToTime!=null">
                dueToTime = #{dueToTime},
            </if>
            <if test="seller!=null and seller.id!=null">
                seller_id = #{seller.id},
            </if>
            <if test="sum!=null">
                sum = #{sum},
            </if>
            <if test="intro!=null and intro!=''">
                intro = #{intro},
            </if>
            <if test="status!=null">
                status = #{status},
            </if>
        </set>
        WHERE id = #{id}
    </update>

    <select id="selectById" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_order o
        LEFT JOIN t_customer cu
        ON o.customer_id = cu.id
        LEFT JOIN t_employee e
        ON o.seller_id = e.id
        WHERE o.id=#{id} AND o.mark=b'1'
    </select>

    <select id="selectAll" resultMap="sqlMap">
        SELECT <include refid="column"/> FROM t_order o
        LEFT JOIN t_customer cu
        ON o.customer_id = cu.id
        LEFT JOIN t_employee e
        ON o.seller_id = e.id
        WHERE o.mark=b'1'
    </select>

    <select id="selectByQuery" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_order o
        LEFT JOIN t_customer cu
        ON o.customer_id = cu.id
        LEFT JOIN t_employee e
        ON o.seller_id = e.id
        WHERE o.mark=b'1'
        <if test="minSignTime!=null">
            AND o.signTime >= #{minSignTime}
        </if>
        <if test="tenantId!=null">
            AND o.tenant_id = #{tenantId}
        </if>
        <if test="maxSignTime!=null">
            AND o.signTime <![CDATA[<]]> #{maxSignTime}
        </if>
        <if test="status!=null">
            AND o.status = #{status}
        </if>
    </select>
</mapper>

ContractDetailMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cl.crm.mapper.ContractDetailMapper">
    <resultMap id="sqlMap" type="ContractDetail">
        <id column="id" property="id"/>
        <result column="payTime" property="payTime"/>
        <result column="mony" property="mony"/>
        <result column="scale" property="scale"/>
        <result column="payment" property="payment"/>
    </resultMap>
    <sql id="column">
        id,payTime,mony,scale,payment
    </sql>

    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        INSERT INTO t_contract_detail(
        payTime,mony,scale,payment)
         VALUES (#{payTime},#{mony},#{scale},#{payment})
    </insert>

    <delete id="delete">
       DELETE FROM  t_contract_detail WHERE id=#{id}
    </delete>

    <delete id="deleteByContractId">
       DELETE FROM  t_contract_detail WHERE contract_id=#{contractId}
    </delete>

    <insert id="batchInsert">
        INSERT INTO t_contract_detail(payTime,mony,scale,payment,contract_id) VALUES
        <foreach collection="contractDetails" item="item" separator=",">
            (#{item.payTime},#{item.mony},#{item.scale},#{item.payment},#{contractId})
        </foreach>
    </insert>

    <delete id="batchDelete">
        DELETE FROM  t_contract_detail WHERE id IN
        <foreach collection="list" open="(" close=")" item="item" separator=",">
            #{item.id}
        </foreach>
    </delete>
    <update id="update">
        update t_contract_detail
        <set>
            <if test="payTime!=null">
                payTime = #{payTime},
            </if>
            <if test="mony!=null">
                mony = #{mony},
            </if>
            <if test="scale!=null">
                scale = #{scale},
            </if>
            <if test="payment!=null">
                payment = #{payment},
            </if>
        </set>
        where id = #{id}
    </update>

    <select id="selectById" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_contract_detail WHERE id=#{id}
    </select>
    <select id="selectByContractId" resultMap="sqlMap">
        SELECT <include refid="column"/> FROM t_contract_detail WHERE contract_id=#{contractId}
    </select>

    <select id="selectAll" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_contract_detail
    </select>

    <select id="selectByQuery" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_contract_detail
        <where>
            <if test="contract!=null and contract.id!=null">
                AND contract_id = #{contract.id}
            </if>
            <if test="minPayTime!=null">
                AND payTime >= #{minPayTime}
            </if>
            <if test="maxPayTime!=null">
                AND payTime <![CDATA[<]]> #{maxPayTime}
            </if>
        </where>
    </select>
</mapper>

3.2 合同管理

3.1.1 vue

<template xmlns:v-slot="http://www.w3.org/1999/XSL/Transform">

    <div>
        <!--查询的form表单-->
        <el-form style="margin-top: 20px" :inline="true" :model="searchForm" class="demo-form-inline">
            <el-form-item label="最小未付金额">
                <el-input type="number" v-model="searchForm.unpaid" placeholder="最小未付金额"/>
            </el-form-item>
            <el-form-item label="签订时间">
                <el-col :span="11">
                    <el-date-picker
                            v-model="searchForm.minSignTime"
                            type="date"
                            value-format="yyyy-MM-dd"
                            placeholder="选择日期">
                    </el-date-picker>
                </el-col>
                <el-col class="line" :span="2">-</el-col>
                <el-col :span="11">
                    <el-date-picker
                            v-model="searchForm.maxSignTime"
                            type="date"
                            value-format="yyyy-MM-dd"
                            placeholder="选择日期">
                    </el-date-picker>
                </el-col>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="searchSubmit">查询</el-button>
            </el-form-item>
        </el-form>
        <!--批量删除-->
        <el-button type="danger" style="float: left;margin-bottom:20px;margin-left: 20px" @click="batchRemove"
                   :disabled="this.selectRows.length===0">批量删除
        </el-button>
        <!--
          :data  根据数据展示列表
          @selection-change: 获取选中列表触发事件(返回的是选中的对象列表)
           v-loading="loading" 值为true代表是加载效果
        -->
        <el-table
                :data="tableData"
                v-loading="loading"
                style="width: 100%"
                @selection-change="selectionChange"
        >
            <el-table-column fixed type="selection" width="55">
            </el-table-column>
            <el-table-column type="index" fixed label="序号" width="70px" sortable="true">
            </el-table-column>
            <el-table-column width="140px" fixed prop="sn" label="合同编号号" sortable="true"></el-table-column>
            <el-table-column width="100px" prop="customer.name" label="合同客户"></el-table-column>
            <el-table-column width="140px" prop="order.sn" label="订单编号"></el-table-column>
            <el-table-column width="130px" prop="signTime" label="签订时间" sortable="true"></el-table-column>
            <el-table-column width="100px" prop="seller.realName" label="营销人员"></el-table-column>
            <el-table-column width="100px" prop="sum" label="金额" sortable="true"></el-table-column>
            <el-table-column width="120px" prop="paid" label="已付金额" sortable="true"></el-table-column>
            <el-table-column width="120px" prop="unpaid" label="未付金额" sortable="true"></el-table-column>
            <el-table-column width="200px" prop="intro" label="摘要"></el-table-column>
            <!--操作-->
            <el-table-column label="操作" width="250px" fixed="right">
                <template slot-scope="scope">
                    <el-button
                            size="small"
                            @click="handleEdit(scope.$index, scope.row)">编辑
                    </el-button>
                    <el-button type="warning"
                               @click="createContract(scope.$index, scope.row)"
                               size="small">生成保单
                    </el-button>
                    <el-button
                            size="small"
                            type="danger"
                            @click="handleDelete(scope.$index, scope.row)">删除
                    </el-button>
                </template>
            </el-table-column>
        </el-table>
        <!--分页-->
        <el-pagination style="margin-top: 10px;float: right"
                       @size-change="sizeChange"
                       @current-change="currentChange"
                       :current-page="currentPage"
                       :page-sizes="pageSizes"
                       :page-size="pageSize"
                       layout="total, sizes, prev, pager, next, jumper"
                       :total="total">
        </el-pagination>
        <!--修改弹出框-->
        <el-dialog :title="title" :visible.sync="visible" :close-on-click-modal="false">
            <el-form :model="form" label-width="80px" :rules="formRules" ref="form">
                <el-form-item label="签订时间">
                    <el-date-picker
                            v-model="form.signTime"
                            type="date"
                            value-format="yyyy-MM-dd"
                            placeholder="选择日期">
                    </el-date-picker>
                </el-form-item>
                <el-form-item label="摘要">
                    <el-input
                            type="textarea"
                            autosize
                            placeholder="请输入内容"
                            v-model="form.intro">
                    </el-input>
                </el-form-item>
                <h6 style="display: inline-block">合同付款详情</h6>

                <!--可编辑表格-->
                <vxe-button @click="insertEvent">插入</vxe-button>
                <vxe-table
                        border
                        show-overflow
                        :data="form.contractDetails"
                        :edit-config="{trigger: 'click', mode: 'cell'}">
                    <vxe-table-column type="seq" width="60"></vxe-table-column>
                    <vxe-table-column width="150" field="payTime" title="付款时间" :edit-render="{name: 'input'}">
                        <template v-slot:edit="{ row }">
                            <input type="date" v-model="row.payTime" class="custom-input">
                        </template>
                    </vxe-table-column>
                    <vxe-table-column field="mony" title="付款金额" :edit-render="{name: 'input'}">
                        <template v-slot:edit="{ row }">
                            <el-input
                                    @change="monyChange"
                                    type="number"
                                    autosize
                                    placeholder="请输入内容"
                                    v-model="row.mony">
                            </el-input>
                        </template>
                    </vxe-table-column>
                    <vxe-table-column field="scale" title="所占比例">
                    </vxe-table-column>
                    <vxe-table-column field="payment" title="是否付款" :edit-render="{name: 'radio'}">
                        <template v-slot:edit="{ row }">
                            <el-radio v-model="row.payment" label="true">true</el-radio>
                            <el-radio v-model="row.payment" label="false">false</el-radio>
                        </template>
                    </vxe-table-column>
                    <vxe-table-column title="操作">
                        <!--<template v-slot="{ row }">
                                <el-button @click="removeEvent(row)">移除</el-button>
                        </template>-->
                        <template slot-scope="scope">
                            <el-button
                                    @click.native.prevent="deleteRow(scope.$index, form.contractDetails)"
                                    type="text"
                                    size="small">
                                移除
                            </el-button>
                        </template>
                    </vxe-table-column>
                </vxe-table>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click.native="visible = false">取消</el-button>
                <el-button type="primary" @click.native="submit" :loading="editLoading">提交</el-button>
            </div>
        </el-dialog>

    </div>
</template>

<script>
    export default {
        name: 'Contract',
        data() {
            return {
                currentPermissions: [],
                loading: true,
                tableData: [],
                selectRows: [],

                //分页相关
                //当前页
                currentPage: 1,
                //每页条数集合
                pageSizes: [5, 10, 15, 20],
                //每页条数
                pageSize: 10,
                //总数
                total: 0,

                //修改、增加相关
                //是否可见
                visible: false,
                //标题
                title: "",
                //搜索条件form model
                searchForm: {
                    unpaid: "",
                    minSignTime: '',
                    maxSignTime: ''
                },
                //修改、新增form model
                form: {
                    signTime: "",
                    intro: "",
                    contractDetails: [],
                },
                formRules: {
                    sn: [
                        {required: true, message: '请输入合同编号', trigger: 'blur'}
                    ]
                },
                editLoading: false,
            };
        },
        methods: {

            //加载列表
            loadContractList() {
                //查询传递参数
                this.$http.patch("/allUserPermissions").then(res => {
                    this.currentPermissions = res.data;
                });
                let params = {
                    "currentPage": this.currentPage,
                    "pageSize": this.pageSize,
                    unpaid: this.searchForm.unpaid,
                    minSignTime: this.searchForm.minSignTime,
                    maxSignTime: this.searchForm.maxSignTime
                };
                this.loading = true;
                this.$http.patch("/contract/selectPageByQuery", params).then(res => {
                    this.tableData = res.data.list;
                    this.total = res.data.total;
                    this.loading = false;
                });
            },
            //多选
            selectionChange(v) {
                this.selectRows = v;
            },
            //每页条数改变
            sizeChange(v) {
                this.currentPage = 1;
                this.pageSize = v;
                this.loadContractList();
                //加载框
                this.loading = true;
            },
            //当前页改变
            currentChange(v) {
                this.currentPage = v;
                this.loadContractList();
                //加载框
                this.loading = true
            },
            //删除一条数据
            handleDelete(i, r) {
                if (this.currentPermissions.indexOf("/contract/delete") === -1) {
                    this.$message({type: 'info', message: "没有权限!"});
                    return;
                }
                this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    this.$http.delete("/contract/delete/" + r.id).then(res => {
                        if (res.data.success) {
                            this.$message({
                                type: 'success',
                                message: r.sn + "删除成功"
                            });
                        } else {
                            this.$message({
                                type: 'error',
                                message: res.data.msg
                            });
                        }
                        this.loadContractList();
                    })
                }).catch(() => {

                });
            },
            //批量删除
            batchRemove() {
                if (this.currentPermissions.indexOf("/contract/batchDelete") === -1) {
                    this.$message({type: 'info', message: "没有权限!"});
                    return;
                }
                if (!this.selectRows.length) {
                    this.$message.error('亲!请选中数据进行删除哦!!');
                    return;
                }
                this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    this.$http.patch("/contract/batchDelete", this.selectRows).then(res => {
                        if (res.data.success) {
                            if (res.data.success) {
                                this.$message({
                                    type: 'success',
                                    message: "共删除" + this.selectRows.length + "条数据"
                                });
                            } else {
                                this.$message({
                                    type: 'error',
                                    message: res.data.msg
                                });
                            }
                            this.loadContractList();
                        }
                    })
                }).catch(() => {

                });
            },
            //修改合同
            handleEdit(i, r) {
                if (this.currentPermissions.indexOf("/contract/update") === -1) {
                    this.$message({type: 'info', message: "没有权限!"});
                    return;
                }
                for (let k in r) {
                    this.form[k] = r[k];
                }
                this.visible = true;
                this.title = "更新合同";
            },
            //修改表单提交
            submit() {
                this.$refs.form.validate((valid) => {
                    if (valid) {
                        this.editLoading = true;
                        //NProgress.start();
                        let para = Object.assign({}, this.form);
                        if (this.form.id) {
                            this.$http.post("/contract/update", para).then((res) => {
                                this.editLoading = false;
                                //NProgress.done();
                                if (res.data.success) {
                                    this.$message({
                                        message: '提交成功',
                                        type: 'success'
                                    });
                                } else {
                                    this.$message({
                                        message: res.data.msg,
                                        type: 'error'
                                    });
                                }
                                this.$refs['form'].resetFields();
                                this.visible = false;
                                this.loadContractList();
                            });
                        }
                        else {
                            this.$http.put("/contract/save", para).then((res) => {
                                this.editLoading = false;
                                //NProgress.done();
                                if (res.data.success) {
                                    this.$message({
                                        message: '提交成功',
                                        type: 'success'
                                    });
                                } else {
                                    this.$message({
                                        message: res.data.msg,
                                        type: 'error'
                                    });
                                }
                                this.$refs['form'].resetFields();
                                this.visible = false;
                                this.loadContractList();
                            });
                        }
                    }
                });
            },
            //生成保单
            createContract(i, r) {
                if (this.currentPermissions.indexOf("/contract/createRepair") === -1) {
                    this.$message({type: 'info', message: "没有权限!"});
                    return;
                }
                this.$http.post("/contract/createRepair", r).then((res) => {
                    if (res.data.success) {
                        this.$message({
                            message: '生成保单成功!' + res.data.msg,
                            type: 'success'
                        });
                    } else {
                        this.$message({
                            message: res.data.msg,
                            type: 'error'
                        });
                    }
                    this.loadOrderList();
                });


            },

            //搜索框
            searchSubmit() {
                this.loadContractList();
            },
            //详情增加按钮
            insertEvent(row) {
                let contractDetail = {
                    id: null,
                    mony: "",
                    scale: "",
                    payTime: "",
                    payment: true
                };
                this.form.contractDetails.push(contractDetail);
            },
            //详情金额改变事件
            monyChange() {
                let contractDetails = this.form.contractDetails;
                var mony = 0.00;
                for (let i = 0; i < contractDetails.length; i++) {
                    mony += parseFloat(contractDetails[i].mony);
                }
                for (let i = 0; i < this.form.contractDetails.length; i++) {
                    this.form.contractDetails[i].scale = parseFloat(contractDetails[i].mony) / mony * 100;
                }
            },
            //详情删除按钮
            deleteRow(index, rows) {
                rows.splice(index, 1);
            }
        },
        mounted() {
            //当页面加载完毕的时候发送ajax请求
            this.loadContractList();
        }
    }
</script>

<style scoped>

</style>

3.1.2 controller

package com.cl.crm.web.controller;
import com.cl.base.util.AjaxResult;
import com.cl.crm.domain.Contract;
import com.cl.crm.query.ContractQuery;
import com.cl.crm.service.IContractService;
import com.cl.crm.shiro.util.UserContext;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@RequestMapping("/contract")
@CrossOrigin
public class ContractController {

    @Autowired
    private IContractService contractService;



    /**
     * resetful: 它是一个架构风格,它是基于Http协议的扩展
     * 它给你提供了多种请求方式来定位资源
     *          GET      一般是用来做查询的,查询单个对象
     *          POST     一般用来做修改的
     *          DELETE   一般用来做删除
     *          PUT      一般用来做新增
     *          PATCH    一般用来操作批量数据的
     * @return
     */
    @RequestMapping(value = "/list",method = RequestMethod.PATCH)
    @ResponseBody
    public List<Contract> list(){
        return contractService.selectAll();
    }

    /**
     * 条件查询
     * @param query
     * @return
     */
    @RequestMapping(value = "/selectByQuery",method = RequestMethod.PATCH)
    @ResponseBody
    public List<Contract> selectByQuery(@RequestBody ContractQuery query){
        query.setTenantId(UserContext.getEmployee().getTenantId());
        return contractService.selectByQuery(query);
    }

    /**
     * 条件分页查询
     * @param query
     * @return
     */
    @RequestMapping(value = "/selectPageByQuery",method = RequestMethod.PATCH)
    @ResponseBody
    public PageInfo<Contract> selectPageByQuery(@RequestBody ContractQuery query){
        query.setTenantId(UserContext.getEmployee().getTenantId());
        return contractService.selectPageByQuery(query);
    }


    /**
     * 添加部门
     * @param contract
     * @return
     *
     * @RequestBody:前端传递json对象,它会自动转为Contract对象
     *
     */

    @RequestMapping(value = "/save",method = RequestMethod.PUT)
    @ResponseBody
    public AjaxResult save(@RequestBody Contract contract){
        try {
            contractService.insert(contract);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "添加失败!"+e.getMessage());
        }
    }

    /**
     * 合同修改
     * @param contract
     * @return
     */
    @RequestMapping(value = "/update",method = RequestMethod.POST)
    @ResponseBody
    public AjaxResult update(@RequestBody Contract contract){
        try {
            contractService.update(contract);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "修改失败!"+e.getMessage());
        }
    }
    /**
     * 合同生成保单
     * @param contract
     * @return
     */
    @RequestMapping(value = "/createRepair",method = RequestMethod.POST)
    @ResponseBody
    public AjaxResult createRepair(@RequestBody Contract contract){
        try {
            Long repairSn = contractService.createRepair(contract);
            return new AjaxResult(true,"合同编号"+repairSn);
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "修改失败!"+e.getMessage());
        }
    }


    /**
     * 删除
     * @param id
     * @return
     */
    @RequestMapping(value = "/delete/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public AjaxResult delete(@PathVariable("id") Long id){
        try {
            contractService.delete(id);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "删除失败!"+e.getMessage());
        }
    }

    /**
     * 批量删除
     * @param contracts
     * @return
     */
    @RequestMapping(value = "/batchDelete",method = RequestMethod.PATCH)
    @ResponseBody
    public AjaxResult batchDelete(@RequestBody List<Contract> contracts){
        try {
            contractService.batchDelete(contracts);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "删除失败!"+e.getMessage());
        }
    }

    /**
     * 查询单个对象
     * @param id
     * @return
     */
    @RequestMapping(value = "/get/{id}",method = RequestMethod.GET)
    @ResponseBody
    public Contract get(@PathVariable("id") Long id){
        return contractService.selectById(id);
    }

}

3.1.3 service接口

public interface IContractService extends IBaseService<Contract,Long> {
    Long createRepair(Contract contract);
}
3.1.3.1 serviceImpl
@Service
public class ContractServiceImpl extends BaseServiceImpl<Contract,Long> implements IContractService {

    //注入合同mapper
    @Autowired
    private ContractMapper contractMapper;

    //注入合同详情mapper
    @Autowired
    private ContractDetailMapper contractDetailMapper;

    //注入订单mapper
    @Autowired
    private OrderMapper orderMapper;

    //注入保单mapper
    @Autowired
    private RepairMapper repairMapper;

    @Override
    public BaseMapper<Contract, Long> baseMapper() {
        return contractMapper;
    }

    @Override
    public void update(Contract contract) {
        //删除合同详情
        contractDetailMapper.deleteByContractId(contract.getId());

        //获取详情列表
        List<ContractDetail> contractDetails = contract.getContractDetails();
        //判断详情是否为空
        if (contractDetails.size()>0) {
            //不为空则计算总金额
            BigDecimal sumMony = new BigDecimal(0.00);
            //不为空则计算总付款金额
            BigDecimal paid = new BigDecimal(0.00);
            //不为空则计算总未付款金额
            BigDecimal unpaid = new BigDecimal(0.00);
            for (ContractDetail c:contractDetails) {
                sumMony = sumMony.add(c.getMony());
                if (c.getPayment()){
                    paid = paid.add(c.getMony());
                } else {
                    unpaid = unpaid.add(c.getMony());
                }
            }
            //设置总金额
            contract.setSum(sumMony);
            //设置付款金额
            contract.setPaid(paid);
            //设置未付款金额
            contract.setUnpaid(unpaid);
            //插入合同详情
            contractDetailMapper.batchInsert(contract.getId(), contract.getContractDetails());
        }
        //修改合同
        super.update(contract);
    }

    @Override
    public void delete(Long id) {
        //根据id获取合同
        Contract contract = contractMapper.selectById(id);
        //根据合同获取订单
        Order order = orderMapper.selectById(contract.getOrder().getId());
        //将订单设置为未付款
        order.setStatus(0);
        //将变更保存到数据库
        orderMapper.update(order);
        //删除合同详情
        contractDetailMapper.deleteByContractId(id);
        super.delete(id);

    }

    @Override
    public void batchDelete(List<Contract> contracts) {

        contracts.forEach(c->{
            //根据合同获取订单
            Order order = orderMapper.selectById(c.getOrder().getId());
            //将订单设置为未付款
            order.setStatus(0);
            //将变更保存到数据库
            orderMapper.update(order);
            //删除合同详情
            contractDetailMapper.deleteByContractId(c.getId());
        });
        super.batchDelete(contracts);
    }

    @Override
    public Long createRepair(Contract contract) {
        //new保单对象
        Repair repair = new Repair();
        //设置保单客户
        repair.setCustomer(contract.getCustomer());
        //设置保单sn
        repair.setSn(contract.getSn());
        //设置合同
        repair.setContract(contract);
        //设置租户
        repair.setTenantId(contract.getTenantId());
        //保存保单
        repairMapper.insert(repair);
        return repair.getSn();
    }
}

3.1.4 mapper接口

ContractMapper

public interface ContractMapper extends BaseMapper<Contract,Long> {
}

ContractDetailMapper

public interface ContractDetailMapper extends BaseMapper<ContractDetail,Long> {
    List<ContractDetail> selectByContractId(Long contractId);

    void deleteByContractId(Long contractId);

    void batchInsert(@Param("contractId") Long contractId, @Param("contractDetails")List<ContractDetail> contractDetails);
}

3.1.5 mapper.xml

ContractMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cl.crm.mapper.ContractMapper">
    <resultMap id="sqlMap" type="Contract">
        <id column="id" property="id"/>
        <result column="sn" property="sn"/>
        <result column="signTime" property="signTime"/>
        <result column="sum" property="sum"/>
        <result column="paid" property="paid"/>
        <result column="unpaid" property="unpaid"/>
        <result column="intro" property="intro"/>
        <result column="tenantId" property="tenantId"/>
        <association property="customer" javaType="Customer">
            <id column="cuid" property="id"/>
            <result column="cuname" property="name"/>
        </association>
        <association property="seller" javaType="Employee">
            <id column="eid" property="id"/>
            <result column="erealName" property="realName"/>
        </association>
        <association property="order" javaType="Order">
            <id column="oid" property="id"/>
            <result column="osn" property="sn"/>
        </association>
        <collection property="contractDetails" ofType="ContractDetail"
                    column="id" select="com.cl.crm.mapper.ContractDetailMapper.selectByContractId"/>
    </resultMap>
    <sql id="column">
        c.id,
        c.sn,
        c.customer_id,
        c.order_id,
        c.signTime,
        c.seller_id,
        c.sum,
        c.paid,
        c.unpaid,
        c.intro,
        c.tenant_id AS tenantId,
        cu.id AS cuid,
        cu.name AS cuname,
        o.id AS oid,
        o.sn AS osn,
        e.id AS eid,
        e.realName AS erealName
    </sql>

    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        INSERT INTO t_contract(
        sn,customer_id,order_id,signTime,seller_id,sum,paid,unpaid,intro,tenant_id
        ) VALUES (
        #{sn},
        #{customer.id},
        #{order.id},
        #{signTime},
        #{seller.id},
        #{sum},
        #{paid},
        #{unpaid},
        #{intro},
        #{tenantId}
        )
    </insert>

    <delete id="delete">
        UPDATE t_contract set mark =FALSE WHERE id=#{id}
    </delete>

    <delete id="batchDelete">
        UPDATE t_contract set mark =FALSE WHERE id IN
        <foreach collection="list" open="(" close=")" item="item" separator=",">
            #{item.id}
        </foreach>
    </delete>
    <update id="update">
        update t_contract
        <set>
            <if test="sn!=null">
                sn = #{sn},
            </if>
            <if test="customer!=null and customer.id!=null">
                customer_id = #{customer.id},
            </if>
            <if test="order!=null and order.id!=null">
                order_id = #{order.id},
            </if>
            <if test="signTime!=null">
                signTime = #{signTime},
            </if>
            <if test="seller!=null and seller.id!=null">
                seller_id = #{seller.id},
            </if>
            <if test="sum!=null">
                sum = #{sum},
            </if>
            <if test="paid!=null">
                paid = #{paid},
            </if>
            <if test="unpaid!=null">
                unpaid = #{unpaid},
            </if>
            <if test="intro!=null and intro!=''">
                intro = #{intro},
            </if>
        </set>
        where id = #{id}
    </update>

    <select id="selectById" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_contract c
        LEFT JOIN t_customer cu ON c.customer_id = cu.id
        LEFT JOIN t_employee e ON c.seller_id = e.id
        LEFT JOIN t_order o ON c.order_id = o.id
        WHERE c.id=#{id} AND c.mark=b'1'
    </select>

    <select id="selectAll" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_contract c
        LEFT JOIN t_customer cu ON c.customer_id = cu.id
        LEFT JOIN t_employee e ON c.seller_id = e.id
        LEFT JOIN t_order o ON c.order_id = o.id
        WHERE c.mark=b'1'
    </select>

    <select id="selectByQuery" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_contract c
        LEFT JOIN t_customer cu ON c.customer_id = cu.id
        LEFT JOIN t_employee e ON c.seller_id = e.id
        LEFT JOIN t_order o ON c.order_id = o.id
        WHERE c.mark=b'1'
        <if test="minSignTime!=null">
            AND c.signTime >= #{minSignTime}
        </if>
        <if test="tenantId!=null">
            AND c.tenant_id = #{tenantId}
        </if>
        <if test="maxSignTime!=null">
            AND c.signTime <![CDATA[<]]> #{maxSignTime}
        </if>
        <if test="unpaid!=null">
            AND c.unpaid >= #{unpaid}
        </if>
    </select>
</mapper>

3.3 保单管理

3.1.1 vue

<template xmlns:v-slot="http://www.w3.org/1999/XSL/Transform">

    <div>
        <!--查询的form表单-->
        <el-form style="margin-top: 20px" :inline="true" :model="searchForm" class="demo-form-inline">
            <el-form-item label="合同编号">
                <el-input type="number" v-model="searchForm.contractSn" placeholder="合同编号"/>
            </el-form-item>
            <el-form-item label="到期时间">
                <el-col :span="11">
                    <el-date-picker
                            v-model="searchForm.minDueToTime"
                            type="date"
                            value-format="yyyy-MM-dd"
                            placeholder="选择日期">
                    </el-date-picker>
                </el-col>
                <el-col class="line" :span="2">-</el-col>
                <el-col :span="11">
                    <el-date-picker
                            v-model="searchForm.maxDueToTime"
                            type="date"
                            value-format="yyyy-MM-dd"
                            placeholder="选择日期">
                    </el-date-picker>
                </el-col>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="searchSubmit">查询</el-button>
            </el-form-item>
        </el-form>
        <!--批量删除-->
        <el-button type="danger" style="float: left;margin-bottom:20px;margin-left: 20px" @click="batchRemove" :disabled="this.selectRows.length===0">批量删除</el-button>
        <!--添加-->
        <!--<el-button type="primary" style="float: left;margin-bottom:20px;" @click="add">新增</el-button>-->
        <!--
          :data  根据数据展示列表
          @selection-change: 获取选中列表触发事件(返回的是选中的对象列表)
           v-loading="loading" 值为true代表是加载效果
        -->
        <el-table
                :data="tableData"
                v-loading="loading"
                style="width: 100%"
                @selection-change="selectionChange"
        >
            <el-table-column type="selection" width="55">
            </el-table-column>
            <el-table-column type="index" label="序号" width="70px" sortable="true">
            </el-table-column>
            <el-table-column  prop="sn" label="保单单号" sortable="true" ></el-table-column>
            <el-table-column  prop="contract.sn" label="合同单号" sortable="true" ></el-table-column>
            <el-table-column prop="customer.name" label="保单客户" ></el-table-column>
            <el-table-column prop="dueToTime" label="到期时间" ></el-table-column>
            <!--操作-->
            <el-table-column label="操作">
                <template slot-scope="scope">
                    <el-button
                            size="small"
                            @click="handleEdit(scope.$index, scope.row)">编辑
                    </el-button>
                    <el-button
                            size="small"
                            type="danger"
                            @click="handleDelete(scope.$index, scope.row)">删除
                    </el-button>
                </template>
            </el-table-column>
        </el-table>
        <!--分页-->
        <el-pagination style="margin-top: 10px;float: right"
                       @size-change="sizeChange"
                       @current-change="currentChange"
                       :current-page="currentPage"
                       :page-sizes="pageSizes"
                       :page-size="pageSize"
                       layout="total, sizes, prev, pager, next, jumper"
                       :total="total">
        </el-pagination>
        <!--修改、添加弹出框-->
        <el-dialog :title="title" :visible.sync="visible" :close-on-click-modal="false">
            <el-form :model="form" label-width="80px" :rules="formRules" ref="form">
                <el-form-item label="保单号">
                    <el-input v-model="form.sn" auto-complete="off"></el-input>
                </el-form-item>
                <el-form-item label="到期时间">
                    <el-date-picker
                            v-model="form.dueToTime"
                            type="date"
                            value-format="yyyy-MM-dd"
                            placeholder="选择日期">
                    </el-date-picker>
                </el-form-item>

                <!--可编辑表格-->
                <h6 style="display: inline-block">保单详情</h6>
                <vxe-button @click="insertEvent">插入</vxe-button>
                <vxe-table
                        border
                        show-overflow
                        :data="form.repairDetails"
                        :edit-config="{trigger: 'click', mode: 'cell'}">
                    <vxe-table-column type="seq" width="60"></vxe-table-column>
                    <vxe-table-column field="repairDate" title="保修时间" :edit-render="{name: 'input'}">
                        <template v-slot:edit="{ row }">
                            <input type="date" v-model="row.repairDate" class="custom-input">
                        </template>
                    </vxe-table-column>
                    <vxe-table-column field="repairContent" title="保修内容" :edit-render="{name: 'input'}">
                        <template v-slot:edit="{ row }">
                            <el-input
                                    type="textarea"
                                    autosize
                                    placeholder="请输入内容"
                                    v-model="row.repairContent">
                            </el-input>
                        </template>
                    </vxe-table-column>
                    <vxe-table-column field="solve" title="是否解决" :edit-render="{name: 'radio'}">
                        <template v-slot:edit="{ row }">
                            <el-radio v-model="row.solve" label="true">true</el-radio>
                            <el-radio v-model="row.solve" label="false">false</el-radio>
                        </template>
                    </vxe-table-column>
                    <vxe-table-column title="操作">
                        <!--<template v-slot="{ row }">
                                <el-button @click="removeEvent(row)">移除</el-button>
                        </template>-->
                        <template slot-scope="scope">
                            <el-button
                                    @click.native.prevent="deleteRow(scope.$index, form.repairDetails)"
                                    type="text"
                                    size="small">
                                移除
                            </el-button>
                        </template>
                    </vxe-table-column>
                </vxe-table>
            </el-form>
            <div slot="footer" class="dialog-footer">
                <el-button @click.native="visible = false">取消</el-button>
                <el-button type="primary" @click.native="submit" :loading="editLoading">提交</el-button>
            </div>
        </el-dialog>

    </div>
</template>

<script>
    export default {
        name: 'Repair',
        data() {
            return {
                currentPermissions:[],
                loading: true,
                tableData: [],
                selectRows: [],
                customers:[],
                //分页相关
                //当前页
                currentPage: 1,
                //每页条数集合
                pageSizes: [5, 10, 15, 20],
                //每页条数
                pageSize: 10,
                //总数
                total: 0,

                //修改、增加相关
                //是否可见
                visible: false,
                //标题
                title: "",
                //搜索条件form model
                searchForm: {
                    contractSn:'',
                    minDueToTime:'',
                    maxDueToTime:''

                },
                //修改、新增form model
                form: {
                    id: "",
                    sn: "",
                    customer:null,
                    dueToTime:'',
                    repairDetails:[]
                },
                formRules: {
                    name: [
                        {required: true, message: '请输入部门', trigger: 'blur'}
                    ]
                },
                editLoading: false
            };
        },
        methods: {

            //详情增加按钮
            insertEvent (row) {
                let repairDetail ={
                    id: null,
                    repairContent: "",
                    repairDate: "",
                    solve: true
                };
                this.form.repairDetails.push(repairDetail);
            },
            //详情删除按钮
            deleteRow(index, rows) {
                rows.splice(index, 1);
            },
            //加载列表
            loadRepairList() {
                this.$http.patch("/allUserPermissions").then(res => {
                    this.currentPermissions=res.data;
                });
                //查询传递参数
                let params = {
                    "currentPage": this.currentPage,
                    "pageSize": this.pageSize,
                    contractSn:this.searchForm.contractSn,
                    minDueToTime:this.searchForm.minDueToTime,
                    maxDueToTime:this.searchForm.maxDueToTime
                };
                this.loading = true;
                this.$http.patch("/repair/selectPageByQuery", params).then(res => {
                    this.tableData = res.data.list;
                    this.total = res.data.total;
                    this.loading = false;
                });
            },
            //多选
            selectionChange(v) {
                this.selectRows = v;
            },
            //每页条数改变
            sizeChange(v) {
                this.currentPage = 1;
                this.pageSize = v;
                this.loadRepairList();
                //加载框
                this.loading = true;
            },
            //当前页改变
            currentChange(v) {
                this.currentPage = v;
                this.loadRepairList();
                //加载框
                this.loading = true
            },
            //删除一条数据
            handleDelete(i, r) {
                if(this.currentPermissions.indexOf("/repair/delete")===-1){
                    this.$message({type: 'info',message:"没有权限!"});return;
                }
                this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    this.$http.delete("/repair/delete/" + r.id).then(res => {
                        if (res.data.success) {
                            this.$message({
                                type: 'success',
                                message: r.sn+"删除成功"
                            });
                        } else {
                            this.$message({
                                type: 'error',
                                message: res.data.msg
                            });
                        }
                        this.loadRepairList();
                    })
                }).catch(() => {

                });
            },
            //批量删除
            batchRemove() {
                if(this.currentPermissions.indexOf("/repair/batchDelete")===-1){
                    this.$message({type: 'info',message:"没有权限!"});return;
                }
                if (!this.selectRows.length) {
                    this.$message.error('亲!请选中数据进行删除哦!!');
                    return;
                }
                this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    this.$http.patch("/repair/batchDelete", this.selectRows).then(res => {
                        if (res.data.success) {
                            if (res.data.success) {
                                this.$message({
                                    type: 'success',
                                    message: "共删除" + this.selectRows.length + "条数据"
                                });
                            } else {
                                this.$message({
                                    type: 'error',
                                    message: res.data.msg
                                });
                            }
                            this.loadRepairList();
                        }
                    })
                }).catch(() => {

                });
            },
            //新增
            /*add() {
                for (let k in this.form) {
                    this.form[k] = '';
                }
                this.visible = true;
                this.title = "添加部门";
            },*/
            handleEdit(i, r) {
                if(this.currentPermissions.indexOf("/repair/update")===-1){
                    this.$message({type: 'info',message:"没有权限!"});return;
                }
                for (let k in r) {
                    this.form[k] = r[k];
                }
                this.visible = true;
                this.title = "更新部门";
            },
            submit() {
                this.$refs.form.validate((valid) => {
                    if (valid) {
                        this.editLoading = true;
                        //NProgress.start();
                        let para = Object.assign({}, this.form);
                        if (this.form.id) {
                            console.log(para);
                            this.editLoading = false;
                             this.$http.post("/repair/update", para).then((res) => {
                                 this.editLoading = false;
                                 //NProgress.done();
                                 if (res.data.success) {
                                     this.$message({
                                         message: '提交成功',
                                         type: 'success'
                                     });
                                 } else {
                                     this.$message({
                                         message: res.data.msg,
                                         type: 'error'
                                     });
                                 }
                                 this.$refs['form'].resetFields();
                                 this.visible = false;
                                 this.loadRepairList();
                             });
                        }
                        /*else {
                            console.log(para);
                            this.$http.put("/repair/save", para).then((res) => {
                                this.editLoading = false;
                                //NProgress.done();
                                if (res.data.success) {
                                    this.$message({
                                        message: '提交成功',
                                        type: 'success'
                                    });
                                } else {
                                    this.$message({
                                        message: res.data.msg,
                                        type: 'error'
                                    });
                                }
                                this.$refs['form'].resetFields();
                                this.visible = false;
                                this.loadRepairList();
                            });
                        }*/
                    }
                });
            },

            //搜索框
            searchSubmit() {
                this.loadRepairList();
            }
        },
        mounted() {
            //当页面加载完毕的时候发送ajax请求
            this.loadRepairList();
        }
    }
</script>

<style scoped>

</style>

3.1.2 controller

package com.cl.crm.web.controller;
import com.cl.base.util.AjaxResult;
import com.cl.crm.domain.Repair;
import com.cl.crm.query.RepairQuery;
import com.cl.crm.service.IRepairService;
import com.cl.crm.shiro.util.UserContext;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@RequestMapping("/repair")
@CrossOrigin
public class RepairController {

    @Autowired
    private IRepairService repairService;



    /**
     * resetful: 它是一个架构风格,它是基于Http协议的扩展
     * 它给你提供了多种请求方式来定位资源
     *          GET      一般是用来做查询的,查询单个对象
     *          POST     一般用来做修改的
     *          DELETE   一般用来做删除
     *          PUT      一般用来做新增
     *          PATCH    一般用来操作批量数据的
     * @return
     */
    @RequestMapping(value = "/list",method = RequestMethod.PATCH)
    @ResponseBody
    public List<Repair> list(){
        return repairService.selectAll();
    }

    /**
     * 条件查询
     * @param query
     * @return
     */
    @RequestMapping(value = "/selectByQuery",method = RequestMethod.PATCH)
    @ResponseBody
    public List<Repair> selectByQuery(@RequestBody RepairQuery query){
        query.setTenantId(UserContext.getEmployee().getTenantId());
        return repairService.selectByQuery(query);
    }

    /**
     * 条件分页查询
     * @param query
     * @return
     */
    @RequestMapping(value = "/selectPageByQuery",method = RequestMethod.PATCH)
    @ResponseBody
    public PageInfo<Repair> selectPageByQuery(@RequestBody RepairQuery query){
        System.out.println(UserContext.getEmployee());
        query.setTenantId(UserContext.getEmployee().getTenantId());
        return repairService.selectPageByQuery(query);
    }


    /**
     * 添加部门
     * @param repair
     * @return
     *
     * @RequestBody:前端传递json对象,它会自动转为Repair对象
     *
     */
    @RequestMapping(value = "/save",method = RequestMethod.PUT)
    @ResponseBody
    public AjaxResult save(@RequestBody Repair repair){
        try {
            repairService.insert(repair);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "添加失败!"+e.getMessage());
        }
    }
    @RequestMapping(value = "/update",method = RequestMethod.POST)
    @ResponseBody
    public AjaxResult update(@RequestBody Repair repair){
        try {
            repairService.update(repair);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "修改失败!"+e.getMessage());
        }
    }


    /**
     * 删除
     * @param id
     * @return
     */
    @RequestMapping(value = "/delete/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public AjaxResult delete(@PathVariable("id") Long id){
        try {
            repairService.delete(id);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "删除失败!"+e.getMessage());
        }
    }

    /**
     * 批量删除
     * @param repairs
     * @return
     */
    @RequestMapping(value = "/batchDelete",method = RequestMethod.PATCH)
    @ResponseBody
    public AjaxResult batchDelete(@RequestBody List<Repair> repairs){
        try {
            repairService.batchDelete(repairs);
            return new AjaxResult();
        } catch (Exception e) {
            e.printStackTrace();
            return new AjaxResult(false, "删除失败!"+e.getMessage());
        }
    }

    /**
     * 查询单个对象
     * @param id
     * @return
     */
    @RequestMapping(value = "/get/{id}",method = RequestMethod.GET)
    @ResponseBody
    public Repair get(@PathVariable("id") Long id){
        return repairService.selectById(id);
    }

}

3.1.3 service接口

public interface IRepairService extends IBaseService<Repair,Long> {
}

3.1.3.1 serviceImpl
@Service
public class RepairServiceImpl extends BaseServiceImpl<Repair,Long> implements IRepairService {

    @Autowired
    private RepairMapper repairMapper;

    @Autowired
    private RepairDetailMapper repairDetailMapper;

    @Override
    public BaseMapper<Repair, Long> baseMapper() {
        return repairMapper;
    }

    @Override
    public void update(Repair repair) {

        //删除订单详情
        repairDetailMapper.deleteByRepairId(repair.getId());

        super.update(repair);
        //获取订单详情
        System.out.println(repair.getRepairDetails());
        if (repair.getRepairDetails().size()>0) {
            //保存订单详情
            repairDetailMapper.batchInsert(repair.getId(), repair.getRepairDetails());
        }
    }

    @Override
    public void delete(Long id) {
        //删除订单详情
        repairDetailMapper.deleteByRepairId(id);
        super.delete(id);

    }

    @Override
    public void batchDelete(List<Repair> repairs) {
        repairs.forEach(r->{
            repairDetailMapper.deleteByRepairId(r.getId());
        });
        super.batchDelete(repairs);
    }
}

3.1.4 mapper接口

repairMapper

public interface RepairMapper extends BaseMapper<Repair,Long> {
}

repairDetailMapper

public interface RepairDetailMapper extends BaseMapper<RepairDetail,Long> {

    List<RepairDetail> selectByRepairId(Long repairId);

    void deleteByRepairId(Long id);

    void batchInsert(@Param("repairId") Long repairId,@Param("repairDetails") List<RepairDetail> repairDetails);
}

3.1.5 mapper.xml

repairMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cl.crm.mapper.RepairMapper">
    <resultMap id="sqlMap" type="Repair">
        <id column="id" property="id"/>
        <result column="sn" property="sn"/>
        <result column="dueToTime" property="dueToTime"/>
        <result column="tenantId" property="tenantId"/>
        <association property="customer" javaType="Customer">
            <id column="cuid" property="id"/>
            <result column="cuname" property="name"/>
        </association>
        <association property="contract" javaType="Contract">
            <id column="cid" property="id"/>
            <result column="csn" property="sn"/>
        </association>
        <collection property="repairDetails" ofType="RepairDetail"
                    column="id" select="com.cl.crm.mapper.RepairDetailMapper.selectByRepairId"/>
    </resultMap>
    <sql id="column">
        r.id,
        r.sn,
        r.dueToTime,
        r.tenant_id AS tenantId,
        c.id AS cid,
        c.sn AS csn,
        cu.id AS cuid,
        cu.name AS cuname
    </sql>

    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        INSERT INTO t_repair(sn,dueToTime,customer_id,tenant_id,contract_id)
         VALUES (#{sn},#{dueToTime},#{customer.id},#{tenantId},#{contract.id})
    </insert>

    <delete id="delete">
       UPDATE t_repair SET mark = FALSE WHERE id=#{id}
    </delete>

    <delete id="batchDelete">
        UPDATE t_repair SET mark = FALSE WHERE id IN
        <foreach collection="list" open="(" close=")" item="item" separator=",">
            #{item.id}
        </foreach>
    </delete>
    <update id="update">
        update t_repair
        <set>
            <if test="sn!=null">
                sn = #{sn},
            </if>
            <if test="dueToTime!=null">
                dueToTime = #{dueToTime},
            </if>
        </set>
        where id = #{id}
    </update>

    <select id="selectById" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_repair r
        LEFT JOIN t_customer cu ON r.customer_id = cu.id
        LEFT JOIN t_contract c ON r.contract_id = c.id
        WHERE r.id=#{id} AND r.mark=b'1'
    </select>

    <select id="selectAll" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_repair r
        LEFT JOIN t_customer cu ON r.customer_id = cu.id
        LEFT JOIN t_contract c ON r.contract_id = c.id
        WHERE r.mark=b'1'
    </select>

    <select id="selectByQuery" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_repair r
        LEFT JOIN t_customer cu ON r.customer_id = cu.id
        LEFT JOIN t_contract c ON r.contract_id = c.id
        WHERE r.mark=b'1'
        <if test="minDueToTime!=null">
            AND r.dueToTime >= #{minDueToTime}
        </if>
        <if test="tenantId!=null">
            AND r.tenant_id = #{tenantId}
        </if>
        <if test="maxDueToTime!=null">
            AND r.dueToTime <![CDATA[<]]> #{maxDueToTime}
        </if>
        <if test="contractSn!=null and contractSn!=''">
            AND c.sn LIKE CONCAT("%",#{contractSn},"%")
        </if>
    </select>
</mapper>

repairDetailMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cl.crm.mapper.RepairDetailMapper">
    <resultMap id="sqlMap" type="RepairDetail">
        <id column="id" property="id"/>
        <result column="repairDate" property="repairDate"/>
        <result column="repairContent" property="repairContent"/>
        <result column="solve" property="solve"/>
    </resultMap>
    <sql id="column">
        id,repairDate,repairContent,solve
    </sql>

    <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        INSERT INTO t_repair_detail(repairDate,repairContent,solve)
         VALUES (#{repairDate},#{repairContent},#{solve})
    </insert>

    <delete id="delete">
       DELETE FROM  t_repair_detail WHERE id=#{id}
    </delete>
    <delete id="deleteByRepairId">
       DELETE FROM  t_repair_detail WHERE repair_id=#{repairId}
    </delete>

    <delete id="batchDelete">
        DELETE FROM  t_repair_detail WHERE id IN
        <foreach collection="list" open="(" close=")" item="item" separator=",">
            #{item.id}
        </foreach>
    </delete>
    <insert id="batchInsert">
        INSERT INTO t_repair_detail(repairDate,repairContent,solve,repair_id) VALUES
        <foreach collection="repairDetails" item="item" separator=",">
            (#{item.repairDate},#{item.repairContent},#{item.solve},#{repairId})
        </foreach>
    </insert>
    <update id="update">
        update t_repair_detail
        <set>
            <if test="repairDate!=null">
                repairDate = #{repairDate},
            </if>
            <if test="repairContent!=null and repairContent!=''">
                repairContent = #{repairContent},
            </if>
            <if test="solve!=null">
                solve = #{solve},
            </if>
        </set>
        where id = #{id}
    </update>

    <select id="selectById" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_repair_detail WHERE id=#{id}
    </select>
    <select id="selectByRepairId" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_repair_detail WHERE repair_id=#{repairId}
    </select>

    <select id="selectAll" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_repair_detail
    </select>

    <select id="selectByQuery" resultMap="sqlMap">
        SELECT
        <include refid="column"/>
        FROM t_repair_detail
        <where>
            <if test="solve!=null">
                AND solve = #{solve}
            </if>
            <if test="minRepairDate!=null">
                AND repairDate >= #{minRepairDate}
            </if>
            <if test="maxRepairDate!=null">
                AND repairDate <![CDATA[<]]> #{maxRepairDate}
            </if>
        </where>
    </select>
</mapper>

3.4 报表

3.4.1 vue

<template>
    <div>
        <!--<button @click="changeType">切换图表类型</button>-->
        <el-form style="margin-top: 20px" :inline="true" :model="searchForm" class="demo-form-inline" >
        <el-form-item label="年份">
            <el-input type="number" maxlength="4" minlength="4" v-model="searchForm.year" placeholder="请输入有效的年份"/>
        </el-form-item>
        <el-form-item label="月份">
            <el-select v-model="searchForm.month" placeholder="请选择">
                <el-option
                        v-for="item in options"
                        :key="item.value"
                        :label="item.label"
                        :value="item.value">
                </el-option>
            </el-select>
        </el-form-item>
        <el-form-item>
            <el-button type="success" @click="searchSubmit">生成图表</el-button>
        </el-form-item>
        <el-form-item>
            <el-button type="primary" @click="changeType">切换图表类型</el-button>
        </el-form-item>
        </el-form>
        <ve-chart :data="chartData" :settings="chartSettings"></ve-chart>
    </div>
</template>

<script>

    export default {
        data () {
            this.typeArr = ['line', 'histogram', 'pie']
            this.index = 0
            return {
                chartData: {
                    columns: [],
                    rows: []
                },
                chartSettings: { type: this.typeArr[this.index] },
                options: [{
                    value: '',
                    label: ''
                },{
                    value: '0',
                    label: '一月'
                }, {
                    value: '1',
                    label: '二月'
                }, {
                    value: '2',
                    label: '三月'
                }, {
                    value: '3',
                    label: '四月'
                }, {
                    value: '4',
                    label: '五月'
                }, {
                    value: '5',
                    label: '六月'
                }, {
                    value: '6',
                    label: '七月'
                }, {
                    value: '7',
                    label: '八月'
                }, {
                    value: '8',
                    label: '九月'
                }, {
                    value: '9',
                    label: '十月'
                }, {
                    value: '10',
                    label: '十一月'
                }, {
                    value: '11',
                    label: '十二月'
                }],
                searchForm:{
                    year:'',
                    month:''
                }
            }
        },
        methods: {
            //加载列表
            loadContractList() {
                let year = this.searchForm.year;
                let month = this.searchForm.month;
                let date='1';
                if (year!=null && year!=''){
                    date = year;
                }
                if (month!=null && month!=''){
                    date = year+"-"+month;
                }
                this.$http.patch("/echarts/contract/"+date).then(res => {
                    this.chartData.columns =['时间', '金额'];
                    let contracts = res.data;
                    for (let i = 0; i<contracts.length;i++){
                        let row = {
                            '时间':'',
                            '金额':''
                        };
                        if ((year!=null && year!=='' )&&( month==null || month==='')){
                            row.时间 = (parseInt(contracts[i].signTime)+1)+"月";
                            row.金额 = contracts[i].paid;
                        } else if (month!=null && month!==''){
                            row.时间 = contracts[i].signTime;
                            row.金额 = contracts[i].paid;
                        }else {
                            row.时间 = contracts[i].signTime+"年";
                            row.金额 = contracts[i].paid;
                        }
                        this.chartData.rows.push(row)
                    }
                });
            },
            changeType: function () {
                this.index++
                if (this.index >= this.typeArr.length) { this.index = 0 }
                this.chartSettings = { type: this.typeArr[this.index] }
            },
            searchSubmit:function () {
                this.chartData = {
                    columns: [],
                        rows: []
                }
                this.loadContractList();
            }
        },
        mounted() {
            //当页面加载完毕的时候发送ajax请求
            this.loadContractList();
        }
    }
</script>

<style scoped>

</style>

3.4.2 controller

package com.cl.crm.web.controller;

import com.cl.crm.domain.Contract;
import com.cl.crm.query.ContractQuery;
import com.cl.crm.service.IContractService;
import com.cl.crm.shiro.util.UserContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.*;

@Controller
@RequestMapping("/echarts")
@CrossOrigin
public class EChartsController {

    @Autowired
    private IContractService contractService;

    @RequestMapping(value = "/contract/{date}", method = RequestMethod.PATCH)
    @ResponseBody
    public List<Map<String, String>> contract(@PathVariable("date") String date) {
        Calendar calendar = Calendar.getInstance();
        Calendar calendar2 = Calendar.getInstance();
        //String year = String.valueOf(date.get(Calendar.YEAR));
        //新建查询对象
        ContractQuery query = new ContractQuery();
        //设置租户对象
        query.setTenantId(UserContext.getEmployee().getTenantId());
        //获取查询结果
        List<Contract> list = contractService.selectByQuery(query);
        List<Map<String, String>> list2 = new ArrayList<>();
        if (date.length() == 1) {
            C:
            for (Contract contract : list) {
                calendar.setTime(contract.getSignTime());
                String year = String.valueOf(calendar.get(Calendar.YEAR));
                C2:
                for (Map<String, String> map : list2) {
                    if (map.get("signTime").equals(year)) {
                        float paid = Float.parseFloat(map.get("paid")) + contract.getPaid().intValue();
                        map.put("paid", paid + "");
                        continue C;
                    }
                }
                Map<String, String> map = new HashMap<>();
                map.put("signTime", year);
                map.put("paid", contract.getPaid() + "");
                list2.add(map);
            }
        } else if (date.length() == 4) {
            C:
            for (Contract contract : list) {
                calendar.setTime(contract.getSignTime());
                String year = String.valueOf(calendar.get(Calendar.YEAR));
                String month = String.valueOf(calendar.get(Calendar.MONTH));
                if (date.equals(year)) {
                    C2: for (Map<String, String> map : list2) {
                        if (map.get("signTime").equals(month)) {
                            float paid = Float.parseFloat(map.get("paid")) + contract.getPaid().intValue();
                            map.put("paid", paid + "");
                            continue C;
                        }
                    }
                    Map<String, String> map = new HashMap<>();
                    map.put("signTime", month);
                    map.put("paid", contract.getPaid() + "");
                    list2.add(map);
                }
            }
        } else {
            C:
            for (Contract contract : list) {
                calendar.setTime(contract.getSignTime());
                String day = String.valueOf(calendar.get(Calendar.DATE));
                String year = String.valueOf(calendar.get(Calendar.YEAR));
                String month = String.valueOf(calendar.get(Calendar.MONTH));
                String date2 = year + "-" + month;
                if (date.equals(date2)) {
                    C2: for (Map<String, String> map : list2) {
                        if (map.get("signTime").equals(day)) {
                            float paid = Float.parseFloat(map.get("paid")) + contract.getPaid().intValue();
                            map.put("paid", paid + "");
                            continue C;
                        }
                    }
                    Map<String, String> map = new HashMap<>();
                    map.put("signTime", day);
                    map.put("paid", contract.getPaid() + "");
                    list2.add(map);
                }
            }
        }

        Collections.sort(list2, new Comparator<Map<String, String>>() {
            @Override
            public int compare(Map<String, String> o1, Map<String, String> o2) {
                // 根据日期进行排序
                int paid1 = Integer.parseInt(o1.get("signTime"));
                int paid2 = Integer.parseInt(o2.get("signTime"));
                return paid1-paid2;
            }
        });
        return list2;
    }
}

3.5 动态菜单

3.5.1 home.vue

...
<!--导航菜单-->
				<el-menu :default-active="$route.path" class="el-menu-vertical-demo" @open="handleopen" @close="handleclose" @select="handleselect"
					 unique-opened router v-show="!collapsed">
                    <template v-for="(item,index) in $router.options.routes" v-if="!item.hidden">
                        <div v-for="menu in menuData">
                            <div v-if="item.name === menu.name">
                                <el-submenu :index="index+''" v-if="!item.leaf">
                                    <template slot="title"><i :class="item.iconCls"></i>{{item.name}}</template>
                                    <div v-for="child in item.children">
                                        <div v-for="menu in menuData">
                                            <div v-if="child.name === menu.name">
                                                <el-menu-item :index="child.path"
                                                              :key="child.path"
                                                              v-if="!child.hidden">
                                                    {{child.name}}
                                                </el-menu-item>
                                            </div>
                                        </div>
                                    </div>
                                </el-submenu>
                            </div>
                        </div>
                        <div v-for="menu in menuData">
                            <div v-if="item.children[0].name === menu.name">
                                <el-menu-item v-if="item.leaf&&item.children.length>0"
                                              :index="item.children[0].path"><i
                                        :class="item.iconCls"></i>{{item.children[0].name}}
                                </el-menu-item>
                            </div>
                        </div>
                    </template>
				</el-menu>
				...

<script>
...
return{
menuData:[],
}


method:{
getMenus(){
                this.$http.patch("/menu/selectByEmployeeId").then(res => {
                    this.menuData = res.data;
                });
            },
}
...
</script>
				

3.5.2 后台代码

MenuController.java
@RequestMapping(value = "/selectByEmployeeId",method = RequestMethod.PATCH)
    @ResponseBody
    public List<Menu> selectByEmployeeId(){
        Long employeeId = UserContext.getEmployee().getId();
        return iMenuService.selectByEmployeeId(employeeId);
    }
    ......
MenuMapper.xml
<select id="selectByEmployeeId" resultType="Menu">
        SELECT DISTINCT m.* FROM t_employee e
        JOIN t_emp_role er ON er.emp_id= e.id
        JOIN t_role r ON r.id = er.role_id
        JOIN t_permission_role pr ON pr.role_id = r.id
        JOIN t_permission p ON p.id=pr.permission_id
        JOIN t_menu m ON m.id = p.menu_id
        WHERE e.id = #{employeeId}
    </select>

四、难点和错误总结

  • 业务逻辑需要理清楚才能开始;
  • 第三方登录集成失败;
  • 动态菜单逻辑判断;
  • 报表数据组装;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值