2 Commits
dev ... master

Author SHA1 Message Date
wkc
76c68cd544 Merge branch 'dev' 2026-03-06 13:40:54 +08:00
fedf789511 0302-海宁管户报表优化+客群代码初稿 2026-03-02 17:00:27 +08:00
26 changed files with 2095 additions and 19 deletions

88
ibs-group/pom.xml Normal file
View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.8.8</version>
</parent>
<artifactId>ibs-group</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Mockito依赖 -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.3.3</version> <!-- 请根据需要选择合适的版本 -->
<scope>test</scope>
</dependency>
<!-- JUnit依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version> <!-- 请根据需要选择合适的版本 -->
<scope>test</scope>
</dependency>
<!-- 通用工具-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>1.18.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.31</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.postgresql</groupId>-->
<!-- <artifactId>postgresql</artifactId>-->
<!-- <version>42.3.3</version>-->
<!-- </dependency>-->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-system</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel-core</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.2</version>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ibs</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,164 @@
package com.ruoyi.group.controller;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.group.domain.dto.CustGroupMemberTemplate;
import com.ruoyi.group.domain.dto.CustGroupQueryDTO;
import com.ruoyi.group.domain.dto.GridImportDTO;
import com.ruoyi.group.domain.entity.CustGroup;
import com.ruoyi.group.domain.vo.CustGroupVO;
import com.ruoyi.group.service.ICustGroupService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;
/**
* 客群管理Controller
*
* @author ruoyi
*/
@Api(tags = "客群管理接口")
@RestController
@RequestMapping("/group/cust")
public class CustGroupController extends BaseController {
@Resource
private ICustGroupService custGroupService;
/**
* 查询客群列表
*/
@ApiOperation("查询客群列表")
@Log(title = "客群管理-查询客群列表")
@GetMapping("/list")
public TableDataInfo listCustGroup(CustGroupQueryDTO dto) {
startPage();
List<CustGroupVO> list = custGroupService.listCustGroup(dto);
return getDataTable(list);
}
/**
* 获取客群详情
*/
@ApiOperation("获取客群详情")
@Log(title = "客群管理-获取客群详情")
@GetMapping("/{id}")
public AjaxResult getCustGroup(@PathVariable Long id) {
CustGroupVO vo = custGroupService.getCustGroup(id);
return AjaxResult.success(vo);
}
/**
* 异步创建客群(网格导入)
*/
@ApiOperation("异步创建客群(网格导入)")
@Log(title = "客群管理-网格导入创建客群", businessType = BusinessType.INSERT)
@PostMapping("/createByGrid")
public AjaxResult createCustGroupByGrid(@RequestBody @Valid GridImportDTO gridImportDTO) {
String id = custGroupService.createCustGroupByGrid(gridImportDTO);
return AjaxResult.success(id);
}
/**
* 异步创建客群(模板导入)
*/
@ApiOperation("异步创建客群(模板导入)")
@Log(title = "客群管理-异步创建客群", businessType = BusinessType.INSERT)
@PostMapping(value = "/createByTemplate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public AjaxResult createCustGroupByTemplate(@RequestPart("dto") @Valid String dtoJson,
@RequestPart("file") MultipartFile file) {
CustGroup custGroup = JSON.parseObject(dtoJson, CustGroup.class);
return AjaxResult.success(custGroupService.createCustGroupByTemplate(custGroup, file));
}
/**
* 更新客群
*/
@ApiOperation("更新客群")
@Log(title = "客群管理-更新客群", businessType = BusinessType.UPDATE)
@PostMapping("/update")
public AjaxResult updateCustGroup(@RequestBody @Valid CustGroup custGroup) {
String result = custGroupService.updateCustGroup(custGroup);
return AjaxResult.success(result);
}
/**
* 更新客群(网格导入)
*/
@ApiOperation("更新客群(网格导入)")
@Log(title = "客群管理-更新客群(网格导入)", businessType = BusinessType.UPDATE)
@PostMapping("/updateByGrid")
public AjaxResult updateCustGroupByGrid(@RequestBody @Valid GridImportDTO gridImportDTO) {
String result = custGroupService.updateCustGroupByGrid(gridImportDTO);
return AjaxResult.success(result);
}
/**
* 更新客群(模板导入)
*/
@ApiOperation("更新客群(模板导入)")
@Log(title = "客群管理-更新客群(模板导入)", businessType = BusinessType.UPDATE)
@PostMapping(value = "/updateByTemplate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public AjaxResult updateCustGroupByTemplate(@RequestPart("dto") @Valid String dtoJson,
@RequestPart("file") MultipartFile file) {
CustGroup custGroup = JSON.parseObject(dtoJson, CustGroup.class);
return AjaxResult.success(custGroupService.updateCustGroupByTemplate(custGroup, file));
}
/**
* 轮询客群创建状态
*/
@ApiOperation("轮询客群创建状态")
@Log(title = "客群管理-轮询客群创建状态")
@GetMapping("/createStatus/{id}")
public AjaxResult getCreateStatus(@PathVariable Long id) {
String status = custGroupService.getCreateStatus(id);
return AjaxResult.success(status);
}
/**
* 客户信息模板下载
*/
@ApiOperation("客户信息模板")
@Log(title = "客群管理-客户信息模板", businessType = BusinessType.EXPORT)
@PostMapping("/download")
public void download(HttpServletResponse response) {
ExcelUtil<CustGroupMemberTemplate> util = new ExcelUtil<>(CustGroupMemberTemplate.class);
util.exportExcel(response, null, "客户信息模板");
}
/**
* 删除客群
*/
@ApiOperation("删除客群")
@Log(title = "客群管理-删除客群", businessType = BusinessType.DELETE)
@PostMapping("/delete")
public AjaxResult deleteCustGroup(@RequestBody List<Long> idList) {
String result = custGroupService.deleteCustGroup(idList);
return AjaxResult.success(result);
}
/**
* 手动移除客群客户
*/
@ApiOperation("手动移除客群客户")
@Log(title = "客群管理-手动移除客户", businessType = BusinessType.DELETE)
@PostMapping("/removeMembers")
public AjaxResult removeMembers(@RequestParam Long groupId, @RequestBody List<Long> memberIds) {
String result = custGroupService.removeMembers(groupId, memberIds);
return AjaxResult.success(result);
}
}

View File

@@ -0,0 +1,49 @@
package com.ruoyi.group.domain.dto;
import com.ruoyi.common.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 客群客户Excel模板
*
* @author ruoyi
*/
@Data
public class CustGroupMemberTemplate {
/**
* 客户类型
*/
@ApiModelProperty(value = "客户类型:个人/企业/商户", name = "custType")
@Excel(name = "客户类型(个人/企业/商户)", prompt = "必填", combo = "个人,企业,商户")
private String custType;
/**
* 客户号
*/
@ApiModelProperty(value = "客户号", name = "custId")
@Excel(name = "客户号", prompt = "必填")
private String custId;
/**
* 客户姓名
*/
@ApiModelProperty(value = "客户姓名", name = "custName")
@Excel(name = "客户姓名")
private String custName;
/**
* 客户身份证号
*/
@ApiModelProperty(value = "客户身份证号(个人客户必填)", name = "custIdc")
@Excel(name = "客户身份证号(个人客户必填)")
private String custIdc;
/**
* 统信码
*/
@ApiModelProperty(value = "统信码(企业/商户客户必填)", name = "socialCreditCode")
@Excel(name = "统信码(企业/商户客户必填)")
private String socialCreditCode;
}

View File

@@ -0,0 +1,53 @@
package com.ruoyi.group.domain.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 客群查询DTO
*
* @author ruoyi
*/
@Data
public class CustGroupQueryDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 客群名称(模糊查询)
*/
@ApiModelProperty(value = "客群名称", name = "groupName")
private String groupName;
/**
* 客群模式0=静态, 1=动态
*/
@ApiModelProperty(value = "客群模式", name = "groupMode")
private String groupMode;
/**
* 创建方式1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格
*/
@ApiModelProperty(value = "创建方式", name = "createMode")
private String createMode;
/**
* 客群状态0=正常, 1=已禁用
*/
@ApiModelProperty(value = "客群状态", name = "groupStatus")
private String groupStatus;
/**
* 页码
*/
@ApiModelProperty(value = "页码", name = "pageNum")
private Integer pageNum = 1;
/**
* 每页大小
*/
@ApiModelProperty(value = "每页大小", name = "pageSize")
private Integer pageSize = 10;
}

View File

@@ -0,0 +1,58 @@
package com.ruoyi.group.domain.dto;
import com.ruoyi.group.domain.entity.CustGroup;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 网格导入客群DTO
*
* @author ruoyi
*/
@Data
public class GridImportDTO {
/**
* 客群信息
*/
@ApiModelProperty(value = "客群信息", name = "custGroup")
@Valid
@NotNull(message = "客群信息不能为空")
private CustGroup custGroup;
/**
* 网格类型0=绩效网格, 1=地理网格, 2=绘制网格
*/
@ApiModelProperty(value = "网格类型0=绩效网格, 1=地理网格, 2=绘制网格", name = "gridType")
@NotBlank(message = "网格类型不能为空")
private String gridType;
/**
* 绩效网格业务类型retail=零售, corporate=公司gridType=0时必填
*/
@ApiModelProperty(value = "绩效网格业务类型retail=零售, corporate=公司", name = "cmpmBizType")
private String cmpmBizType;
/**
* 绩效网格客户经理柜员号列表gridType=0时必填
*/
@ApiModelProperty(value = "客户经理柜员号列表", name = "userNames")
private List<String> userNames;
/**
* 地理网格ID列表gridType=1时必填
*/
@ApiModelProperty(value = "地理网格ID列表", name = "regionGridIds")
private List<Long> regionGridIds;
/**
* 绘制网格ID列表gridType=2时必填
*/
@ApiModelProperty(value = "绘制网格ID列表", name = "drawGridIds")
private List<Long> drawGridIds;
}

View File

@@ -0,0 +1,172 @@
package com.ruoyi.group.domain.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 客群实体
*
* @author ruoyi
*/
@Data
@TableName("ibs_cust_group")
public class CustGroup {
/**
* 主键ID
*/
@ApiModelProperty(value = "主键ID", name = "id")
@TableId(type = IdType.AUTO)
private Long id;
/**
* 客群名称
*/
@ApiModelProperty(value = "客群名称", name = "groupName")
private String groupName;
/**
* 客群模式0=静态, 1=动态
*/
@ApiModelProperty(value = "客群模式0=静态, 1=动态", name = "groupMode")
private String groupMode;
/**
* 创建方式1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格
*/
@ApiModelProperty(value = "创建方式1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格", name = "createMode")
private String createMode;
/**
* 柜员号
*/
@ApiModelProperty(value = "柜员号", name = "userName")
private String userName;
/**
* 柜员名称
*/
@ApiModelProperty(value = "柜员名称", name = "nickName")
private String nickName;
/**
* 所属机构ID
*/
@ApiModelProperty(value = "所属机构ID", name = "deptId")
@TableField(fill = FieldFill.INSERT)
private Long deptId;
/**
* 是否开启共享0=否, 1=是
*/
@ApiModelProperty(value = "是否开启共享0=否, 1=是", name = "shareEnabled")
private Integer shareEnabled;
/**
* 可见部门ID列表逗号分隔
*/
@ApiModelProperty(value = "可见部门ID列表逗号分隔", name = "shareDeptIds")
private String shareDeptIds;
/**
* 共享部门ID列表非表字段用于接收前端传参
*/
@ApiModelProperty(value = "共享部门ID列表", name = "shareDeptIdList")
@TableField(exist = false)
private List<Long> shareDeptIdList;
/**
* 客群状态0=正常, 1=已禁用
*/
@ApiModelProperty(value = "客群状态0=正常, 1=已禁用", name = "groupStatus")
private String groupStatus;
/**
* 创建状态0=创建中, 1=创建成功, 2=创建失败
*/
@ApiModelProperty(value = "创建状态0=创建中, 1=创建成功, 2=创建失败", name = "createStatus")
private String createStatus;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private String createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 删除标识0=正常, 1=删除
*/
@TableLogic(value = "0", delval = "1")
@TableField("del_flag")
private Integer delFlag;
/**
* 备注
*/
@ApiModelProperty(value = "备注", name = "remark")
private String remark;
/**
* 网格类型0=绩效网格, 1=地理网格, 2=绘制网格(动态客群使用)
*/
@ApiModelProperty(value = "网格类型", name = "gridType")
private String gridType;
/**
* 绩效业务类型retail=零售, corporate=公司(动态客群使用)
*/
@ApiModelProperty(value = "绩效业务类型", name = "cmpmBizType")
private String cmpmBizType;
/**
* 客户经理列表(逗号分隔,动态客群使用)
*/
@ApiModelProperty(value = "客户经理列表", name = "gridUserNames")
private String gridUserNames;
/**
* 地理网格ID列表逗号分隔动态客群使用
*/
@ApiModelProperty(value = "地理网格ID列表", name = "regionGridIds")
private String regionGridIds;
/**
* 绘制网格ID列表逗号分隔动态客群使用
*/
@ApiModelProperty(value = "绘制网格ID列表", name = "drawGridIds")
private String drawGridIds;
/**
* 有效期截止时间
*/
@ApiModelProperty(value = "有效期截止时间", name = "validTime")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date validTime;
}

View File

@@ -0,0 +1,89 @@
package com.ruoyi.group.domain.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 客群客户关联实体
*
* @author ruoyi
*/
@Data
@TableName("ibs_cust_group_member")
public class CustGroupMember {
/**
* 主键ID
*/
@ApiModelProperty(value = "主键ID", name = "id")
@TableId(type = IdType.AUTO)
private Long id;
/**
* 客群ID
*/
@ApiModelProperty(value = "客群ID", name = "groupId")
private Long groupId;
/**
* 客户类型0=个人, 1=商户, 2=企业
*/
@ApiModelProperty(value = "客户类型0=个人, 1=商户, 2=企业", name = "custType")
private String custType;
/**
* 客户号
*/
@ApiModelProperty(value = "客户号", name = "custId")
private String custId;
/**
* 客户姓名(冗余)
*/
@ApiModelProperty(value = "客户姓名(冗余)", name = "custName")
private String custName;
/**
* 客户身份证号
*/
@ApiModelProperty(value = "客户身份证号", name = "custIdc")
private String custIdc;
/**
* 统信码(商户/企业有)
*/
@ApiModelProperty(value = "统信码(商户/企业有)", name = "socialCreditCode")
private String socialCreditCode;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private String createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 删除标识0=正常, 1=删除
*/
@TableLogic(value = "0", delval = "1")
@TableField("del_flag")
private Integer delFlag;
/**
* 手动移除标识0=否, 1=是(被手动移除的客户不会被定时任务重新导入)
*/
@ApiModelProperty(value = "手动移除标识0=否, 1=是", name = "manualRemove")
@TableField("manual_remove")
private Integer manualRemove;
}

View File

@@ -0,0 +1,66 @@
package com.ruoyi.group.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 客群客户VO
*
* @author ruoyi
*/
@Data
public class CustGroupMemberVO {
/**
* 主键ID
*/
@ApiModelProperty(value = "主键ID", name = "id")
private Long id;
/**
* 客群ID
*/
@ApiModelProperty(value = "客群ID", name = "groupId")
private Long groupId;
/**
* 客户类型0=个人, 1=商户, 2=企业
*/
@ApiModelProperty(value = "客户类型0=个人, 1=商户, 2=企业", name = "custType")
private String custType;
/**
* 客户号
*/
@ApiModelProperty(value = "客户号", name = "custId")
private String custId;
/**
* 客户姓名
*/
@ApiModelProperty(value = "客户姓名", name = "custName")
private String custName;
/**
* 客户身份证号
*/
@ApiModelProperty(value = "客户身份证号", name = "custIdc")
private String custIdc;
/**
* 统信码(商户/企业有)
*/
@ApiModelProperty(value = "统信码(商户/企业有)", name = "socialCreditCode")
private String socialCreditCode;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "创建时间", name = "createTime")
private Date createTime;
}

View File

@@ -0,0 +1,122 @@
package com.ruoyi.group.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 客群VO
*
* @author ruoyi
*/
@Data
public class CustGroupVO {
/**
* 主键ID
*/
@ApiModelProperty(value = "主键ID", name = "id")
private Long id;
/**
* 客群名称
*/
@ApiModelProperty(value = "客群名称", name = "groupName")
private String groupName;
/**
* 客群模式0=静态, 1=动态
*/
@ApiModelProperty(value = "客群模式0=静态, 1=动态", name = "groupMode")
private String groupMode;
/**
* 创建方式1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格
*/
@ApiModelProperty(value = "创建方式1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格", name = "createMode")
private String createMode;
/**
* 柜员号
*/
@ApiModelProperty(value = "柜员号", name = "userName")
private String userName;
/**
* 柜员名称
*/
@ApiModelProperty(value = "柜员名称", name = "nickName")
private String nickName;
/**
* 所属机构ID
*/
@ApiModelProperty(value = "所属机构ID", name = "deptId")
private Long deptId;
/**
* 是否开启共享0=否, 1=是
*/
@ApiModelProperty(value = "是否开启共享0=否, 1=是", name = "shareEnabled")
private Integer shareEnabled;
/**
* 可见部门ID列表
*/
@ApiModelProperty(value = "可见部门ID列表", name = "shareDeptIds")
private List<Long> shareDeptIds;
/**
* 客群状态0=正常, 1=已禁用
*/
@ApiModelProperty(value = "客群状态0=正常, 1=已禁用", name = "groupStatus")
private String groupStatus;
/**
* 客户数量
*/
@ApiModelProperty(value = "客户数量", name = "custCount")
private Integer custCount;
/**
* 创建者
*/
@ApiModelProperty(value = "创建者", name = "createBy")
private String createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "创建时间", name = "createTime")
private Date createTime;
/**
* 更新者
*/
@ApiModelProperty(value = "更新者", name = "updateBy")
private String updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "更新时间", name = "updateTime")
private Date updateTime;
/**
* 备注
*/
@ApiModelProperty(value = "备注", name = "remark")
private String remark;
/**
* 客户列表
*/
@ApiModelProperty(value = "客户列表", name = "custList")
private List<CustGroupMemberVO> custList;
}

View File

@@ -0,0 +1,34 @@
package com.ruoyi.group.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.group.domain.dto.CustGroupQueryDTO;
import com.ruoyi.group.domain.entity.CustGroup;
import com.ruoyi.group.domain.vo.CustGroupVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 客群Mapper接口
*
* @author ruoyi
*/
public interface CustGroupMapper extends BaseMapper<CustGroup> {
/**
* 查询客群数量根据部门ID和共享设置
*
* @param deptId 部门ID
* @param userName 用户名
* @return 数量
*/
// Long countByDeptOrUser(@Param("deptId") Long deptId, @Param("userName") Long userName);
/**
* 查询客群列表
*
* @param dto 查询条件
* @return 客群VO列表
*/
List<CustGroupVO> selectCustGroupList(@Param("dto") CustGroupQueryDTO dto);
}

View File

@@ -0,0 +1,21 @@
package com.ruoyi.group.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.group.domain.entity.CustGroupMember;
import org.apache.ibatis.annotations.Param;
/**
* 客群客户关联Mapper接口
*
* @author ruoyi
*/
public interface CustGroupMemberMapper extends BaseMapper<CustGroupMember> {
/**
* 查询客群客户数量
*
* @param groupId 客群ID
* @return 数量
*/
Long countByGroupId(@Param("groupId") Long groupId);
}

View File

@@ -0,0 +1,120 @@
package com.ruoyi.group.service;
import com.ruoyi.group.domain.dto.CustGroupQueryDTO;
import com.ruoyi.group.domain.dto.GridImportDTO;
import com.ruoyi.group.domain.entity.CustGroup;
import com.ruoyi.group.domain.vo.CustGroupVO;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 客群Service接口
*
* @author ruoyi
*/
public interface ICustGroupService {
/**
* 查询客群列表
*
* @param dto 查询条件
* @return 客群VO列表
*/
List<CustGroupVO> listCustGroup(CustGroupQueryDTO dto);
/**
* 异步创建客群(模板导入)
*
* @param custGroup 客群实体
* @param file Excel文件
* @return 客群ID
*/
String createCustGroupByTemplate(CustGroup custGroup, MultipartFile file);
/**
* 异步创建客群(网格导入)
*
* @param gridImportDTO 网格导入条件
* @return 客群ID
*/
String createCustGroupByGrid(GridImportDTO gridImportDTO);
/**
* 更新客群(网格导入)
*
* @param gridImportDTO 网格导入条件
* @return 结果消息
*/
String updateCustGroupByGrid(GridImportDTO gridImportDTO);
/**
* 更新客群(模板导入)
*
* @param custGroup 客群实体
* @param file Excel文件
* @return 结果消息
*/
String updateCustGroupByTemplate(CustGroup custGroup, MultipartFile file);
/**
* 更新客群
*
* @param custGroup 客群实体
* @return 结果消息
*/
String updateCustGroup(CustGroup custGroup);
/**
* 删除客群
*
* @param idList 客群ID列表
* @return 结果消息
*/
String deleteCustGroup(List<Long> idList);
/**
* 获取客群详情
*
* @param id 客群ID
* @return 客群VO
*/
CustGroupVO getCustGroup(Long id);
/**
* 检查客群名称是否存在
*
* @param groupName 客群名称
* @return true=存在, false=不存在
*/
boolean checkGroupNameExist(String groupName);
/**
* 查询客群创建状态
*
* @param id 客群ID
* @return 创建状态0=创建中, 1=创建成功, 2=创建失败
*/
String getCreateStatus(Long id);
/**
* 手动移除客群客户
*
* @param groupId 客群ID
* @param memberIds 客群成员ID列表
* @return 结果消息
*/
String removeMembers(Long groupId, List<Long> memberIds);
/**
* 更新动态客群(定时任务调用)
* 根据原始导入条件重新查询并更新客户列表
*/
void updateDynamicCustGroups();
/**
* 检查并禁用过期客群(定时任务调用)
*/
void checkAndDisableExpiredGroups();
}

View File

@@ -0,0 +1,817 @@
package com.ruoyi.group.service.impl;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.ibs.cmpm.domain.entity.GridCmpm;
import com.ruoyi.ibs.cmpm.mapper.GridCmpmMapper;
import com.ruoyi.ibs.draw.domain.entity.DrawGridShapeRelate;
import com.ruoyi.ibs.draw.domain.entity.DrawShapeCust;
import com.ruoyi.ibs.draw.mapper.DrawGridShapeRelateMapper;
import com.ruoyi.ibs.draw.mapper.DrawShapeCustMapper;
import com.ruoyi.ibs.grid.domain.vo.CustVO;
import com.ruoyi.ibs.grid.service.RegionGridListService;
import com.ruoyi.group.domain.dto.CustGroupMemberTemplate;
import com.ruoyi.group.domain.dto.CustGroupQueryDTO;
import com.ruoyi.group.domain.dto.GridImportDTO;
import com.ruoyi.group.domain.entity.CustGroup;
import com.ruoyi.group.domain.entity.CustGroupMember;
import com.ruoyi.group.domain.vo.CustGroupMemberVO;
import com.ruoyi.group.domain.vo.CustGroupVO;
import com.ruoyi.group.mapper.CustGroupMapper;
import com.ruoyi.group.mapper.CustGroupMemberMapper;
import com.ruoyi.group.service.ICustGroupService;
import com.ruoyi.ibs.grid.domain.entity.RegionCustUser;
import com.ruoyi.ibs.grid.domain.entity.RegionGrid;
import com.ruoyi.ibs.grid.mapper.RegionCustUserMapper;
import com.ruoyi.ibs.grid.mapper.RegionGridMapper;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
/**
* 客群Service实现
*
* @author ruoyi
*/
@Service
@Slf4j
public class CustGroupServiceImpl implements ICustGroupService {
@Resource
private CustGroupMapper custGroupMapper;
@Resource
private CustGroupMemberMapper custGroupMemberMapper;
@Resource(name = "excelImportExecutor")
private ExecutorService executorService;
@Resource
private GridCmpmMapper gridCmpmMapper;
@Resource
private RegionCustUserMapper regionCustUserMapper;
@Resource
private RegionGridMapper regionGridMapper;
@Resource
private RegionGridListService regionGridListService;
@Resource
private DrawShapeCustMapper drawShapeCustMapper;
@Resource
private DrawGridShapeRelateMapper drawGridShapeRelateMapper;
@Resource
private SysDeptMapper sysDeptMapper;
@Override
public List<CustGroupVO> listCustGroup(CustGroupQueryDTO dto) {
return custGroupMapper.selectCustGroupList(dto);
}
@Override
@Transactional(rollbackFor = Exception.class)
public String createCustGroupByTemplate(CustGroup custGroup, MultipartFile file) {
// 检查客群名称是否存在
if (checkGroupNameExist(custGroup.getGroupName())) {
throw new ServiceException("客群名称已存在");
}
// 获取当前用户信息
SysUser user = SecurityUtils.getLoginUser().getUser();
custGroup.setUserName(user.getUserName());
custGroup.setNickName(user.getNickName());
// 设置默认值
custGroup.setGroupMode("0"); // 静态
custGroup.setCreateMode("1"); // 模板导入
if (StringUtils.isEmpty(custGroup.getGroupStatus())) {
custGroup.setGroupStatus("0");
}
// 设置创建状态为创建中
custGroup.setCreateStatus("0");
// 插入客群
custGroupMapper.insert(custGroup);
// 获取当前用户部门编码(异步线程中无法获取)
String headId = SecurityUtils.getHeadId();
// 异步导入客户
executorService.submit(() -> doImportCustGroupByTemplate(custGroup, file, headId));
return String.valueOf(custGroup.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public String createCustGroupByGrid(GridImportDTO gridImportDTO) {
CustGroup custGroup = gridImportDTO.getCustGroup();
// 检查客群名称是否存在
if (checkGroupNameExist(custGroup.getGroupName())) {
throw new ServiceException("客群名称已存在");
}
// 获取当前用户信息
SysUser user = SecurityUtils.getLoginUser().getUser();
custGroup.setUserName(user.getUserName());
custGroup.setNickName(user.getNickName());
// 默认状态为正常
if (StringUtils.isEmpty(custGroup.getGroupStatus())) {
custGroup.setGroupStatus("0");
}
// 设置创建模式和状态
custGroup.setGroupMode(custGroup.getGroupMode());
// 根据网格类型设置创建方式0=模板导入, 2=绩效网格, 3=地理网格, 4=绘制网格
String gridType = gridImportDTO.getGridType();
if ("0".equals(gridType)) {
custGroup.setCreateMode("2"); // 绩效网格
} else if ("1".equals(gridType)) {
custGroup.setCreateMode("3"); // 地理网格
} else if ("2".equals(gridType)) {
custGroup.setCreateMode("4"); // 绘制网格
} else {
throw new ServiceException("无效的网格类型");
}
// 保存网格导入条件(用于动态客群后续更新)
custGroup.setGridType(gridImportDTO.getGridType());
if ("0".equals(gridType)) {
custGroup.setCmpmBizType(gridImportDTO.getCmpmBizType());
if (gridImportDTO.getUserNames() != null && !gridImportDTO.getUserNames().isEmpty()) {
custGroup.setGridUserNames(String.join(",", gridImportDTO.getUserNames()));
}
} else if ("1".equals(gridType)) {
if (gridImportDTO.getRegionGridIds() != null && !gridImportDTO.getRegionGridIds().isEmpty()) {
custGroup.setRegionGridIds(gridImportDTO.getRegionGridIds().stream()
.map(String::valueOf).collect(Collectors.joining(",")));
}
} else if ("2".equals(gridType)) {
if (gridImportDTO.getDrawGridIds() != null && !gridImportDTO.getDrawGridIds().isEmpty()) {
custGroup.setDrawGridIds(gridImportDTO.getDrawGridIds().stream()
.map(String::valueOf).collect(Collectors.joining(",")));
}
}
// 设置创建状态为创建中
custGroup.setCreateStatus("0");
// 插入客群
custGroupMapper.insert(custGroup);
// 重新设置回DTO确保异步线程能获取到正确的ID和状态
gridImportDTO.setCustGroup(custGroup);
// 获取当前用户部门编码(异步线程中无法获取)
String headId = SecurityUtils.getHeadId();
// 异步导入客户
executorService.submit(() -> doImportCustGroupByGrid(gridImportDTO, headId));
return String.valueOf(custGroup.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public String updateCustGroup(CustGroup custGroup) {
CustGroup existGroup = custGroupMapper.selectById(custGroup.getId());
if (existGroup == null) {
throw new ServiceException("客群不存在");
}
// 检查客群名称是否存在(排除自己)
if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) {
throw new ServiceException("客群名称已存在");
}
custGroupMapper.updateById(custGroup);
return "客群更新成功";
}
@Override
@Transactional(rollbackFor = Exception.class)
public String updateCustGroupByGrid(GridImportDTO gridImportDTO) {
CustGroup custGroup = gridImportDTO.getCustGroup();
// 检查客群是否存在
CustGroup existGroup = custGroupMapper.selectById(custGroup.getId());
if (existGroup == null) {
throw new ServiceException("客群不存在");
}
// 更新客群基本信息
if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) {
throw new ServiceException("客群名称已存在");
}
// 重新查询数据库,获取最新状态
CustGroup latestGroup = custGroupMapper.selectById(custGroup.getId());
latestGroup.setGroupName(custGroup.getGroupName());
latestGroup.setGroupMode(custGroup.getGroupMode());
latestGroup.setRemark(custGroup.getRemark());
latestGroup.setValidTime(custGroup.getValidTime());
latestGroup.setShareEnabled(custGroup.getShareEnabled());
latestGroup.setShareDeptIds(custGroup.getShareDeptIds());
// 更新数据库
custGroupMapper.updateById(latestGroup);
// 重新设置回DTO确保异步线程能获取到正确的ID和状态
gridImportDTO.setCustGroup(latestGroup);
// 获取当前用户部门编码(异步线程中无法获取)
String headId = SecurityUtils.getHeadId();
// 异步导入客户
executorService.submit(() -> doImportCustGroupByGrid(gridImportDTO, headId));
return "客群更新中";
}
@Override
@Transactional(rollbackFor = Exception.class)
public String updateCustGroupByTemplate(CustGroup custGroup, MultipartFile file) {
// 检查客群是否存在
CustGroup existGroup = custGroupMapper.selectById(custGroup.getId());
if (existGroup == null) {
throw new ServiceException("客群不存在");
}
// 更新客群基本信息
if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) {
throw new ServiceException("客群名称已存在");
}
// 重新查询数据库,获取最新状态
CustGroup latestGroup = custGroupMapper.selectById(custGroup.getId());
latestGroup.setGroupName(custGroup.getGroupName());
latestGroup.setGroupMode(custGroup.getGroupMode());
latestGroup.setRemark(custGroup.getRemark());
latestGroup.setValidTime(custGroup.getValidTime());
latestGroup.setShareEnabled(custGroup.getShareEnabled());
latestGroup.setShareDeptIds(custGroup.getShareDeptIds());
// 更新数据库
custGroupMapper.updateById(latestGroup);
// 获取当前用户部门编码(异步线程中无法获取)
String headId = SecurityUtils.getHeadId();
// 异步导入客户(使用最新状态的对象)
executorService.submit(() -> doImportCustGroupByTemplate(latestGroup, file, headId));
return "客群更新中";
}
@Override
@Transactional(rollbackFor = Exception.class)
public String deleteCustGroup(List<Long> idList) {
if (idList == null || idList.isEmpty()) {
throw new ServiceException("请选择要删除的客群");
}
for (Long id : idList) {
// 删除客群客户关联
LambdaQueryWrapper<CustGroupMember> memberWrapper = new LambdaQueryWrapper<>();
memberWrapper.eq(CustGroupMember::getGroupId, id);
custGroupMemberMapper.delete(memberWrapper);
// 删除客群
custGroupMapper.deleteById(id);
}
return "客群删除成功";
}
@Override
@Transactional(rollbackFor = Exception.class)
public String removeMembers(Long groupId, List<Long> memberIds) {
if (memberIds == null || memberIds.isEmpty()) {
throw new ServiceException("请选择要移除的客户");
}
// 检查客群是否存在
CustGroup custGroup = custGroupMapper.selectById(groupId);
if (custGroup == null) {
throw new ServiceException("客群不存在");
}
// 标记为手动移除(软删除)
for (Long memberId : memberIds) {
CustGroupMember member = custGroupMemberMapper.selectById(memberId);
if (member != null && member.getGroupId().equals(groupId)) {
member.setManualRemove(1);
member.setDelFlag(1);
custGroupMemberMapper.updateById(member);
}
}
return "成功移除 " + memberIds.size() + " 个客户";
}
@Override
public CustGroupVO getCustGroup(Long id) {
CustGroup custGroup = custGroupMapper.selectById(id);
if (custGroup == null) {
throw new ServiceException("客群不存在");
}
CustGroupVO vo = new CustGroupVO();
vo.setId(custGroup.getId());
vo.setGroupName(custGroup.getGroupName());
vo.setGroupMode(custGroup.getGroupMode());
vo.setCreateMode(custGroup.getCreateMode());
vo.setUserName(custGroup.getUserName());
vo.setNickName(custGroup.getNickName());
vo.setDeptId(custGroup.getDeptId());
vo.setShareEnabled(custGroup.getShareEnabled());
vo.setGroupStatus(custGroup.getGroupStatus());
vo.setRemark(custGroup.getRemark());
vo.setCreateBy(custGroup.getCreateBy());
vo.setCreateTime(custGroup.getCreateTime());
vo.setUpdateBy(custGroup.getUpdateBy());
vo.setUpdateTime(custGroup.getUpdateTime());
// 处理共享部门ID列表
if (StringUtils.isNotEmpty(custGroup.getShareDeptIds())) {
List<Long> deptIds = new ArrayList<>();
for (String idStr : custGroup.getShareDeptIds().split(",")) {
if (StringUtils.isNotEmpty(idStr)) {
deptIds.add(Long.valueOf(idStr));
}
}
vo.setShareDeptIds(deptIds);
}
// 查询客户列表
LambdaQueryWrapper<CustGroupMember> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustGroupMember::getGroupId, id);
List<CustGroupMember> memberList = custGroupMemberMapper.selectList(wrapper);
List<CustGroupMemberVO> memberVOList = new ArrayList<>();
for (CustGroupMember member : memberList) {
CustGroupMemberVO memberVO = new CustGroupMemberVO();
memberVO.setId(member.getId());
memberVO.setGroupId(member.getGroupId());
memberVO.setCustType(member.getCustType());
memberVO.setCustId(member.getCustId());
memberVO.setCustName(member.getCustName());
memberVO.setCustIdc(member.getCustIdc());
memberVO.setSocialCreditCode(member.getSocialCreditCode());
memberVO.setCreateTime(member.getCreateTime());
memberVOList.add(memberVO);
}
vo.setCustList(memberVOList);
vo.setCustCount(memberVOList.size());
return vo;
}
@Override
public boolean checkGroupNameExist(String groupName) {
LambdaQueryWrapper<CustGroup> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustGroup::getGroupName, groupName);
return custGroupMapper.selectCount(wrapper) > 0;
}
@Override
public String getCreateStatus(Long id) {
CustGroup custGroup = custGroupMapper.selectById(id);
if (custGroup == null) {
throw new ServiceException("客群不存在");
}
return custGroup.getCreateStatus();
}
@Override
public void updateDynamicCustGroups() {
log.info("开始更新动态客群...");
// 查询所有动态客群
LambdaQueryWrapper<CustGroup> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustGroup::getGroupMode, "1"); // 1=动态
wrapper.eq(CustGroup::getGroupStatus, "0"); // 0=正常
wrapper.eq(CustGroup::getCreateStatus, "1"); // 1=创建成功
List<CustGroup> dynamicGroups = custGroupMapper.selectList(wrapper);
if (dynamicGroups.isEmpty()) {
log.info("没有需要更新的动态客群");
return;
}
log.info("找到 {} 个动态客群需要更新", dynamicGroups.size());
Date now = new Date();
for (CustGroup custGroup : dynamicGroups) {
// 检查有效期,过期的客群跳过更新
if (custGroup.getValidTime() != null && custGroup.getValidTime().before(now)) {
log.info("动态客群已过期跳过更新客群ID{},客群名称:{},有效期:{}",
custGroup.getId(), custGroup.getGroupName(), custGroup.getValidTime());
continue;
}
try {
updateDynamicCustGroup(custGroup);
log.info("动态客群更新成功客群ID{},客群名称:{}", custGroup.getId(), custGroup.getGroupName());
} catch (Exception e) {
log.error("动态客群更新失败客群ID{},客群名称:{}", custGroup.getId(), custGroup.getGroupName(), e);
}
}
log.info("动态客群更新完成,总计:{},成功:{},失败:{}",
dynamicGroups.size(),
dynamicGroups.stream().filter(g -> {
// 假设更新成功的状态设置
LambdaQueryWrapper<CustGroup> w = new LambdaQueryWrapper<>();
w.eq(CustGroup::getId, g.getId());
// 这里简单统计,实际可以通过更精确的方式
return true;
}).count(),
0);
}
@Override
public void checkAndDisableExpiredGroups() {
log.info("开始检查并禁用过期客群...");
Date now = new Date();
// 查询所有正常状态且有过期时间的客群
LambdaQueryWrapper<CustGroup> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CustGroup::getGroupStatus, "0"); // 0=正常
wrapper.isNotNull(CustGroup::getValidTime);
wrapper.le(CustGroup::getValidTime, now); // validTime <= now
List<CustGroup> expiredGroups = custGroupMapper.selectList(wrapper);
if (expiredGroups.isEmpty()) {
log.info("没有需要禁用的过期客群");
return;
}
log.info("找到 {} 个过期客群需要禁用", expiredGroups.size());
for (CustGroup custGroup : expiredGroups) {
try {
custGroup.setGroupStatus("1"); // 1=已禁用
custGroupMapper.updateById(custGroup);
log.info("客群已禁用客群ID{},客群名称:{},有效期:{}",
custGroup.getId(), custGroup.getGroupName(), custGroup.getValidTime());
} catch (Exception e) {
log.error("禁用过期客群失败客群ID{},客群名称:{}", custGroup.getId(), custGroup.getGroupName(), e);
}
}
log.info("过期客群禁用完成,总计:{}", expiredGroups.size());
}
/**
* 更新单个动态客群(增量更新方式)
*/
private void updateDynamicCustGroup(CustGroup custGroup) {
// 从客群部门获取 headId定时任务中无登录用户上下文
// headId 是 deptId 的前三位
String headId = "";
if (custGroup.getDeptId() != null) {
String deptIdStr = String.valueOf(custGroup.getDeptId());
headId = deptIdStr.substring(0, 3);
}
// 查询现有客户(排除手动移除的客户,只查询正常状态的客户)
LambdaQueryWrapper<CustGroupMember> existWrapper = new LambdaQueryWrapper<>();
existWrapper.eq(CustGroupMember::getGroupId, custGroup.getId())
.eq(CustGroupMember::getDelFlag, 0)
.eq(CustGroupMember::getManualRemove, 0)
.select(CustGroupMember::getCustId, CustGroupMember::getCustType);
List<CustGroupMember> existMembers = custGroupMemberMapper.selectList(existWrapper);
Set<String> existKeySet = existMembers.stream()
.map(m -> m.getCustId() + "_" + m.getCustType())
.collect(Collectors.toSet());
// 构造 GridImportDTO 复用现有导入方法
GridImportDTO gridImportDTO = new GridImportDTO();
gridImportDTO.setCustGroup(custGroup);
gridImportDTO.setGridType(custGroup.getGridType());
gridImportDTO.setCmpmBizType(custGroup.getCmpmBizType());
// 根据网格类型设置参数并查询
List<CustGroupMember> newMemberList = new ArrayList<>();
String gridType = custGroup.getGridType();
if ("0".equals(gridType)) {
// 绩效网格
if (StringUtils.isNotEmpty(custGroup.getGridUserNames())) {
gridImportDTO.setUserNames(Arrays.asList(custGroup.getGridUserNames().split(",")));
}
newMemberList.addAll(importFromCmpmGrid(custGroup, gridImportDTO, headId));
} else if ("1".equals(gridType)) {
// 地理网格
if (StringUtils.isNotEmpty(custGroup.getRegionGridIds())) {
gridImportDTO.setRegionGridIds(Arrays.stream(custGroup.getRegionGridIds().split(","))
.map(Long::valueOf).collect(Collectors.toList()));
}
newMemberList.addAll(importFromRegionGrid(custGroup, gridImportDTO));
} else if ("2".equals(gridType)) {
// 绘制网格
if (StringUtils.isNotEmpty(custGroup.getDrawGridIds())) {
gridImportDTO.setDrawGridIds(Arrays.stream(custGroup.getDrawGridIds().split(","))
.map(Long::valueOf).collect(Collectors.toList()));
}
newMemberList.addAll(importFromDrawGrid(custGroup, gridImportDTO));
}
// 计算差异
Set<String> newKeySet = newMemberList.stream()
.map(m -> m.getCustId() + "_" + m.getCustType())
.collect(Collectors.toSet());
// 需要删除的客户:存在于旧列表但不存在于新列表
List<String> toDeleteKeys = new ArrayList<>(existKeySet);
toDeleteKeys.removeAll(newKeySet);
// 需要新增的客户:存在于新列表但不存在于旧列表
List<CustGroupMember> toAddMembers = newMemberList.stream()
.filter(m -> !existKeySet.contains(m.getCustId() + "_" + m.getCustType()))
.collect(Collectors.toList());
// 删除不再存在的客户
if (!toDeleteKeys.isEmpty()) {
for (String key : toDeleteKeys) {
String[] parts = key.split("_");
LambdaQueryWrapper<CustGroupMember> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.eq(CustGroupMember::getGroupId, custGroup.getId())
.eq(CustGroupMember::getCustId, parts[0])
.eq(CustGroupMember::getCustType, parts[1]);
custGroupMemberMapper.delete(deleteWrapper);
}
}
// 插入新增客户
int addCount = 0;
for (CustGroupMember member : toAddMembers) {
try {
custGroupMemberMapper.insert(member);
addCount++;
} catch (org.springframework.dao.DuplicateKeyException e) {
log.debug("客户已存在跳过groupId={}, custId={}", member.getGroupId(), member.getCustId());
}
}
log.info("客群客户更新完成客群ID{},原数量:{},新数量:{},新增:{},删除:{}",
custGroup.getId(), existMembers.size(), newMemberList.size(), addCount, toDeleteKeys.size());
}
/**
* 异步导入客户
*/
private void doImportCustGroupByTemplate(CustGroup custGroup, MultipartFile file, String headId) {
try {
// 解析Excel
List<CustGroupMemberTemplate> templateList = EasyExcel.read(file.getInputStream())
.head(CustGroupMemberTemplate.class)
.sheet()
.doReadSync();
if (templateList.isEmpty()) {
throw new ServiceException("导入列表为空");
}
// 校验和去重
Set<String> uniqueCustIds = new HashSet<>();
List<CustGroupMember> memberList = new ArrayList<>();
for (CustGroupMemberTemplate template : templateList) {
// 校验客户类型
String custType = template.getCustType();
if (StringUtils.isEmpty(custType)) {
throw new ServiceException("客户类型不能为空");
}
// 转换客户类型值
String custTypeValue;
switch (custType) {
case "个人":
custTypeValue = "0";
break;
case "企业":
custTypeValue = "2";
break;
case "商户":
custTypeValue = "1";
break;
default:
throw new ServiceException("客户类型填写错误,只能填写:个人、企业、商户");
}
// 客户号不能为空
if (StringUtils.isEmpty(template.getCustId())) {
throw new ServiceException("客户号不能为空");
}
// 检查重复
if (!uniqueCustIds.add(template.getCustId())) {
continue; // 跳过重复客户
}
// 创建客户成员
CustGroupMember member = new CustGroupMember();
member.setGroupId(custGroup.getId());
member.setCustType(custTypeValue);
member.setCustId(template.getCustId());
member.setCustName(template.getCustName());
member.setCustIdc(template.getCustIdc());
member.setSocialCreditCode(template.getSocialCreditCode());
member.setCreateTime(new Date());
// 个人客户必须有身份证号
if ("0".equals(custTypeValue) && StringUtils.isEmpty(member.getCustIdc())) {
throw new ServiceException("个人客户[" + template.getCustId() + "]身份证号不能为空");
}
// 企业/商户客户必须有统信码
if (("1".equals(custTypeValue) || "2".equals(custTypeValue))
&& StringUtils.isEmpty(member.getSocialCreditCode())) {
throw new ServiceException("企业/商户客户[" + template.getCustId() + "]统信码不能为空");
}
memberList.add(member);
}
// 批量插入
int batchSize = 1000;
int successCount = 0;
int skippedCount = 0;
int restoredCount = 0;
for (int i = 0; i < memberList.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, memberList.size());
List<CustGroupMember> batchList = memberList.subList(i, endIndex);
for (CustGroupMember member : batchList) {
try {
custGroupMemberMapper.insert(member);
successCount++;
} catch (DuplicateKeyException e) {
// 客户已存在,检查是否是被手动移除的
LambdaQueryWrapper<CustGroupMember> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CustGroupMember::getGroupId, member.getGroupId())
.eq(CustGroupMember::getCustId, member.getCustId())
.eq(CustGroupMember::getCustType, member.getCustType());
CustGroupMember existMember = custGroupMemberMapper.selectOne(queryWrapper);
if (existMember != null && existMember.getManualRemove() != null && existMember.getManualRemove() == 1) {
// 是被手动移除的客户,清除标记并恢复
existMember.setManualRemove(0);
existMember.setDelFlag(0);
existMember.setCustName(member.getCustName());
custGroupMemberMapper.updateById(existMember);
restoredCount++;
log.debug("恢复手动移除的客户groupId={}, custId={}", member.getGroupId(), member.getCustId());
} else {
// 正常存在的客户,跳过
skippedCount++;
log.debug("客户已存在跳过groupId={}, custId={}", member.getGroupId(), member.getCustId());
}
}
}
}
log.info("客群客户导入完成模板客群ID{},成功:{},跳过重复:{},恢复:{}",
custGroup.getId(), successCount, skippedCount, restoredCount);
// 更新创建状态为成功
custGroup.setCreateStatus("1");
custGroupMapper.updateById(custGroup);
} catch (Exception e) {
// 更新创建状态为失败
custGroup.setCreateStatus("2");
custGroupMapper.updateById(custGroup);
log.error("客群客户导入失败客群ID{}", custGroup.getId(), e);
throw new ServiceException("客群客户导入失败: " + e.getMessage());
}
}
/**
* 异步导入客户(网格方式)
*/
private void doImportCustGroupByGrid(GridImportDTO gridImportDTO, String headId) {
CustGroup custGroup = gridImportDTO.getCustGroup();
try {
List<CustGroupMember> memberList = new ArrayList<>();
String gridType = gridImportDTO.getGridType();
// 根据网格类型查询客户
if ("0".equals(gridType)) {
// 绩效网格
memberList.addAll(importFromCmpmGrid(custGroup, gridImportDTO, headId));
} else if ("1".equals(gridType)) {
// 地理网格
memberList.addAll(importFromRegionGrid(custGroup, gridImportDTO));
} else if ("2".equals(gridType)) {
// 绘制网格
memberList.addAll(importFromDrawGrid(custGroup, gridImportDTO));
}
if (memberList.isEmpty()) {
throw new ServiceException("未查询到任何客户");
}
// 批量插入
int batchSize = 1000;
int successCount = 0;
int skippedCount = 0;
int restoredCount = 0;
for (int i = 0; i < memberList.size(); i += batchSize) {
int endIndex = Math.min(i + batchSize, memberList.size());
List<CustGroupMember> batchList = memberList.subList(i, endIndex);
for (CustGroupMember member : batchList) {
try {
custGroupMemberMapper.insert(member);
successCount++;
} catch (DuplicateKeyException e) {
// 客户已存在,检查是否是被手动移除的
LambdaQueryWrapper<CustGroupMember> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CustGroupMember::getGroupId, member.getGroupId())
.eq(CustGroupMember::getCustId, member.getCustId())
.eq(CustGroupMember::getCustType, member.getCustType());
CustGroupMember existMember = custGroupMemberMapper.selectOne(queryWrapper);
if (existMember != null && existMember.getManualRemove() != null && existMember.getManualRemove() == 1) {
// 是被手动移除的客户,清除标记并恢复
existMember.setManualRemove(0);
existMember.setDelFlag(0);
existMember.setCustName(member.getCustName());
custGroupMemberMapper.updateById(existMember);
restoredCount++;
log.debug("恢复手动移除的客户groupId={}, custId={}", member.getGroupId(), member.getCustId());
} else {
// 正常存在的客户,跳过
skippedCount++;
log.debug("客户已存在跳过groupId={}, custId={}", member.getGroupId(), member.getCustId());
}
}
}
}
log.info("客群客户导入完成网格客群ID{},成功:{},跳过重复:{},恢复:{}",
custGroup.getId(), successCount, skippedCount, restoredCount);
// 更新创建状态为成功
custGroup.setCreateStatus("1");
custGroupMapper.updateById(custGroup);
} catch (Exception e) {
// 更新创建状态为失败
custGroup.setCreateStatus("2");
custGroupMapper.updateById(custGroup);
log.error("客群客户导入失败客群ID{}", custGroup.getId(), e);
throw new ServiceException("客群客户导入失败: " + e.getMessage());
}
}
/**
* 从绩效网格导入客户
*/
private List<CustGroupMember> importFromCmpmGrid(CustGroup custGroup, GridImportDTO gridImportDTO, String headId) {
List<CustGroupMember> memberList = new ArrayList<>();
// 确定要查询的网格类型
List<String> gridTypes = new ArrayList<>();
String cmpmBizType = gridImportDTO.getCmpmBizType();
if ("retail".equals(cmpmBizType)) {
gridTypes.add("retail");
} else if ("corporate".equals(cmpmBizType)) {
gridTypes.add("corporate");
gridTypes.add("corporate_account");
} else {
throw new ServiceException("请选择绩效网格业务类型(零售/公司)");
}
// 查询客户
for (String userName : gridImportDTO.getUserNames()) {
for (String gridType : gridTypes) {
List<GridCmpm> cmpmList = gridCmpmMapper.getGridCmpmByUserName(userName, headId, gridType);
for (GridCmpm cmpm : cmpmList) {
CustGroupMember member = new CustGroupMember();
member.setGroupId(custGroup.getId());
member.setCustId(cmpm.getCustId());
member.setCustName(cmpm.getCustName());
member.setCustType(cmpm.getCustType());
member.setCustIdc(cmpm.getCustIdc());
member.setSocialCreditCode(cmpm.getUsci());
member.setCreateTime(new Date());
memberList.add(member);
}
}
}
return memberList;
}
/**
* 从地理网格导入客户
*/
private List<CustGroupMember> importFromRegionGrid(CustGroup custGroup, GridImportDTO gridImportDTO) {
List<CustGroupMember> memberList = new ArrayList<>();
// 查询地理网格获取编码
if (gridImportDTO.getRegionGridIds() == null || gridImportDTO.getRegionGridIds().isEmpty()) {
throw new ServiceException("请选择地理网格");
}
List<RegionGrid> regionGrids = regionGridMapper.selectBatchIds(gridImportDTO.getRegionGridIds());
// 使用 selectAllCustFromGrid 方法查询所有客户(不限制客户类型)
for (RegionGrid regionGrid : regionGrids) {
List<RegionCustUser> custUsers = regionGridListService.selectAllCustFromGrid(regionGrid);
for (RegionCustUser custUser : custUsers) {
CustGroupMember member = new CustGroupMember();
member.setGroupId(custGroup.getId());
member.setCustId(custUser.getCustId());
member.setCustName(custUser.getCustName());
member.setCustType(custUser.getCustType());
member.setCreateTime(new Date());
memberList.add(member);
}
}
return memberList;
}
/**
* 从绘制网格导入客户
*/
private List<CustGroupMember> importFromDrawGrid(CustGroup custGroup, GridImportDTO gridImportDTO) {
List<CustGroupMember> memberList = new ArrayList<>();
if (gridImportDTO.getDrawGridIds() == null || gridImportDTO.getDrawGridIds().isEmpty()) {
throw new ServiceException("请选择绘制网格");
}
// 查询绘制网格关联的图形ID
for (Long gridId : gridImportDTO.getDrawGridIds()) {
LambdaQueryWrapper<DrawGridShapeRelate> relateWrapper = new LambdaQueryWrapper<>();
relateWrapper.eq(DrawGridShapeRelate::getGridId, gridId);
List<DrawGridShapeRelate> relates = drawGridShapeRelateMapper.selectList(relateWrapper);
for (DrawGridShapeRelate relate : relates) {
// 根据图形ID查询客户
LambdaQueryWrapper<DrawShapeCust> custWrapper = new LambdaQueryWrapper<>();
custWrapper.eq(DrawShapeCust::getShapeId, relate.getShapeId());
List<DrawShapeCust> shapeCusts = drawShapeCustMapper.selectList(custWrapper);
for (DrawShapeCust shapeCust : shapeCusts) {
CustGroupMember member = new CustGroupMember();
member.setGroupId(custGroup.getId());
member.setCustId(shapeCust.getCustId());
member.setCustName(shapeCust.getCustName());
member.setCustType(shapeCust.getCustType());
member.setCreateTime(new Date());
memberList.add(member);
}
}
}
return memberList;
}
}

View File

@@ -0,0 +1,46 @@
<?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.ruoyi.group.mapper.CustGroupMapper">
<select id="selectCustGroupList" resultType="CustGroupVO">
SELECT
cg.id,
cg.group_name,
cg.group_mode,
cg.create_mode,
cg.user_name,
cg.nick_name,
cg.dept_id,
cg.share_enabled,
cg.share_dept_ids,
cg.group_status,
cg.create_by,
cg.create_time,
cg.update_by,
cg.update_time,
cg.remark,
cg.del_flag,
(SELECT COUNT(*) FROM ibs_cust_group_member cgm WHERE cgm.group_id = cg.id AND cgm.del_flag = '0') AS cust_count
FROM ibs_cust_group cg
<where>
cg.del_flag = '0'
and create_status = '1'
<if test="dto.groupName != null and dto.groupName != ''">
AND cg.group_name LIKE CONCAT('%', #{dto.groupName}, '%')
</if>
<if test="dto.groupMode != null and dto.groupMode != ''">
AND cg.group_mode = #{dto.groupMode}
</if>
<if test="dto.createMode != null and dto.createMode != ''">
AND cg.create_mode = #{dto.createMode}
</if>
<if test="dto.groupStatus != null and dto.groupStatus != ''">
AND cg.group_status = #{dto.groupStatus}
</if>
</where>
ORDER BY cg.create_time DESC
</select>
</mapper>

View File

@@ -97,6 +97,13 @@ public class GridCmpmController extends BaseController {
return gridCmpmService.selectCustManagerResult();
}
@GetMapping("/custManager/custLevel/list")
@Log(title = "绩效网格-管户报表星级列表")
@ApiOperation("管户报表星级列表")
public AjaxResult getCustLevelListForManager() {
return success(gridCmpmService.getCustLevelListForManager());
}
@GetMapping("/custLevel/count")
@Log(title = "绩效网格-查询客户分层等级")
@ApiOperation("查询客户分层等级")

View File

@@ -18,4 +18,8 @@ public class CustManagerDTO {
@ApiModelProperty(value = "网点名",notes = "")
private String branchId;
/** 状态类型current本月、last上月、rise上升、fall下降 */
@ApiModelProperty(value = "状态类型", notes = "current本月、last上月、rise上升、fall下降")
private String statusType;
}

View File

@@ -152,6 +152,13 @@ public class GridCmpmService {
return new DwbRetailResultVO();
}
/**
* 获取管户报表星级列表
*/
public List<String> getCustLevelListForManager() {
return gridCmpmMapper.getCustLevelList();
}
/**
* 每月定时设置统计数据
*/
@@ -226,7 +233,6 @@ public class GridCmpmService {
// 获取上月各星级客户数
Map<String, Integer> historyLevelCountMap = getCustLevelCountMap(custManagerDTO, "last");
Map<String, Integer> custLevelCompLm = calculateLevelChanges(historyLevelCountMap, currentLevelCountMap);
vo.setCustLevelCompLm(custLevelCompLm);
// 在 vo.setCustLevelCompLm 之前,按顺序重新组织数据
String[] order = {"5星", "4星", "3星", "2星", "1星", "基础", "长尾"};

View File

@@ -35,4 +35,12 @@ public interface RegionGridListService {
List<RegionGridListVO> getSecGridListByManager(RegionGridListDTO regionGridListDTO);
/**
* 查询网格内所有客户(不限制客户类型,用于导入客群)
* @param regionGrid 地理网格对象
* @return 网格内所有客户列表
*/
List<RegionCustUser> selectAllCustFromGrid(RegionGrid regionGrid);
}

View File

@@ -2,6 +2,7 @@ package com.ruoyi.ibs.grid.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.ibs.draw.domain.dto.grid.CustCountDTO;
@@ -429,4 +430,25 @@ public class RegionGridListServiceImpl implements RegionGridListService {
return secGridListByManager;
}
/**
* 查询网格内所有客户(不限制客户类型,用于导入客群)
* @param regionGrid 地理网格对象
* @return 网格内所有客户列表
*/
@Override
public List<RegionCustUser> selectAllCustFromGrid(RegionGrid regionGrid) {
LambdaQueryWrapper<RegionCustUser> queryWrapper = new LambdaQueryWrapper<>();
// 根据网格等级判断使用一级还是二级网格ID查询
if (regionGrid.getGridLevel().equals("1")) {
queryWrapper.eq(RegionCustUser::getTopGridId, regionGrid.getGridId());
} else if (regionGrid.getGridLevel().equals("2")) {
queryWrapper.eq(RegionCustUser::getSecGridId, regionGrid.getGridId());
} else {
throw new ServiceException("无效的网格等级: " + regionGrid.getGridLevel());
}
// 不限制客户类型,查询所有类型的客户
return regionCustUserMapper.selectList(queryWrapper);
}
}

View File

@@ -142,6 +142,28 @@
<if test="managerId != null and managerId !='' ">and manager_id = #{managerId}</if>
<if test="outletId != null and outletId !='' ">and outlet_id = #{outletId}</if>
<if test="branchId != null and branchId !='' ">and branch_id = #{branchId}</if>
<if test="statusType != null and statusType !=''">
<choose>
<when test="statusType == 'current'">
<!-- 本月:本月是指定星级的客户 -->
and cust_level = #{custLevel}
</when>
<when test="statusType == 'last'">
<!-- 上月:上月是指定星级的客户 -->
and cust_level_lm = #{custLevel}
</when>
<when test="statusType == 'rise'">
<!-- 上升:上升到指定星级的客户(本月是指定星级,上月星级更低) -->
and cust_level = #{custLevel}
and cust_level_comp_lm like '上升%'
</when>
<when test="statusType == 'fall'">
<!-- 下降:从指定星级下降的客户(上月是指定星级,本月星级更低) -->
and cust_level_lm = #{custLevel}
and cust_level_comp_lm like '下降%'
</when>
</choose>
</if>
</where>
</select>
@@ -438,6 +460,7 @@
select distinct
cust_level
from dwb_retail_cust_level_manager_detail_875
order by cust_level
</select>
<select id="getCustCountByLevel" resultType="int">

View File

@@ -226,6 +226,7 @@
<module>ruoyi-generator</module>
<module>ruoyi-common</module>
<module>ibs</module>
<module>ibs-group</module>
</modules>
<packaging>pom</packaging>

View File

@@ -74,6 +74,13 @@
<version>${ruoyi.version}</version>
</dependency>
<!-- 客群管理-->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ibs-group</artifactId>
<version>${ruoyi.version}</version>
</dependency>
</dependencies>
<build>

4
ruoyi-ui/.gitignore vendored
View File

@@ -21,6 +21,4 @@ selenium-debug.log
dist.zip
package-lock.json
yarn.lock
vue.config.js
yarn.lock

View File

@@ -14,4 +14,12 @@ export function gridCmpmCustManagerResult(data) {
method: 'get',
params: data
})
}
// 获取管户报表星级列表
export function getCustLevelList() {
return request({
url: '/grid/cmpm/custManager/custLevel/list',
method: 'get'
})
}

View File

@@ -1,12 +1,13 @@
<template>
<div>
<!-- 统计卡片 -->
<div :gutter="24" class="sum-box">
<el-card class="box-card">
<div class="my-span-checklist-title">
总资产余额
</div>
<div class="my-span-checklist-main">
<span>{{ cardInfo.custAumBal }}</span>
<span>{{ formatYi(cardInfo.custAumBal) }}</span>
</div>
</el-card>
<el-card class="box-card">
@@ -14,7 +15,7 @@
总资产余额较上月变动
</div>
<div class="my-span-checklist-main">
<span>{{ cardInfo.aumBalCompLm }}</span>
<span>{{ formatYi(cardInfo.aumBalCompLm) }}</span>
</div>
</el-card>
<el-card class="box-card">
@@ -22,7 +23,7 @@
总资产余额月日均
</div>
<div class="my-span-checklist-main">
<span>{{ cardInfo.custAumMonthAvg }}</span>
<span>{{ formatYi(cardInfo.custAumMonthAvg) }}</span>
</div>
</el-card>
<el-card class="box-card">
@@ -66,6 +67,33 @@
</div>
</el-card>
</div>
<!-- 搜索区域 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="90px">
<el-form-item label="客户星级" prop="custLevel">
<el-select v-model="queryParams.custLevel" placeholder="请选择" clearable style="width: 150px">
<el-option
v-for="level in custLevelOptions"
:key="level"
:label="level"
:value="level"
/>
</el-select>
</el-form-item>
<el-form-item label="星级状态" prop="statusType">
<el-select v-model="queryParams.statusType" placeholder="请选择" clearable style="width: 150px">
<el-option label="本月" value="current" />
<el-option label="上月" value="last" />
<el-option label="上升" value="rise" />
<el-option label="下降" value="fall" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-card class="header-statics" style="display: none">
<ul class="statics-cnt">
<li>
@@ -122,7 +150,7 @@
</template>
<script>
import { gridCmpmCustManagerList, gridCmpmCustManagerResult } from '@/api/gridSearch/accountManageReport/index'
import { gridCmpmCustManagerList, gridCmpmCustManagerResult, getCustLevelList } from '@/api/gridSearch/accountManageReport/index'
export default {
data() {
return {
@@ -137,19 +165,50 @@ import { gridCmpmCustManagerList, gridCmpmCustManagerResult } from '@/api/gridSe
},
tableData: [],
loading: false,
custLevelOptions: [],
queryParams: {
pageNum: 1,
pageSize: 10
pageSize: 10,
custLevel: null,
statusType: null
},
total: 0,
topData: []
}
},
mounted() {
// 从 URL 参数获取筛选条件
const { custLevel, statusType } = this.$route.query
if (custLevel) {
this.queryParams.custLevel = custLevel
}
if (statusType) {
this.queryParams.statusType = statusType
}
this.loadCustLevelOptions()
this.getData()
this.getSum()
},
methods: {
/** 加载客户星级选项 */
loadCustLevelOptions() {
getCustLevelList().then(res => {
this.custLevelOptions = res.data || []
}).catch(() => {
this.custLevelOptions = []
})
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1
this.getData()
},
/** 重置按钮操作 */
resetQuery() {
this.queryParams.custLevel = null
this.queryParams.statusType = null
this.handleQuery()
},
getData() {
this.loading = true;
gridCmpmCustManagerList(this.queryParams).then(res => {
@@ -183,10 +242,21 @@ import { gridCmpmCustManagerList, gridCmpmCustManagerResult } from '@/api/gridSe
},
toSum(obj) {
let sum = 0
Object.keys(obj).map(key => {
sum = sum + obj[key] * 1
Object.keys(obj).forEach(key => {
// 只计算2星级及以上的key中包含"2星"、"3星"、"4星"、"5星"
if (key.includes('2星') || key.includes('3星') || key.includes('4星') || key.includes('5星')) {
sum += obj[key]
}
})
return sum
},
/** 格式化为亿元 */
formatYi(value) {
if (value === null || value === '' || value === undefined) return '-'
const num = parseFloat(value)
if (isNaN(num)) return '-'
// 换算为亿元保留2位小数
return (num / 100000000).toFixed(2) + '亿元'
}
}
}

View File

@@ -18,9 +18,9 @@
</div>
<div class="top_num">
<span style="font-size: 14px;">较上月变动</span>
<span v-if="String(item.inc).includes('-')" style=" font-size: 14px;color: #EF3F35">{{ changeData(item.inc)
<span v-if="String(item.inc).includes('-')" style=" font-size: 14px;color: #00B453">{{ changeData(item.inc)
}}<i class="el-icon-caret-bottom"></i></span>
<span v-else style=" font-size: 14px;color: #00B453">{{ changeData(item.inc) }}<i
<span v-else style=" font-size: 14px;color: #EF3F35">{{ changeData(item.inc) }}<i
class="el-icon-caret-top"></i></span>
</div>
</div>
@@ -30,18 +30,24 @@
<div class="vt-main top-vt-main">
<div class="top_num">
<span>上月值</span>
<span style="font-size: 14px;">{{ item.yiAmt }}</span>
<span style="font-size: 14px; cursor: pointer; color: #409EFF;" @click="goToCustManager(item.itemNm, 'last')">{{ item.yiAmt }}</span>
</div>
<div class="top_num">
<span>当月值</span>
<span style="font-size: 14px;">{{ item.inc }}</span>
<span style="font-size: 14px; cursor: pointer; color: #409EFF;" @click="goToCustManager(item.itemNm, 'current')">{{ item.inc }}</span>
</div>
<div class="top_num">
<span style="font-size: 14px;">较上月变动</span>
<span v-if="String(item.curAmt).includes('-')" style=" font-size: 14px;color: #EF3F35">{{ item.curAmt
}}<i class="el-icon-caret-bottom"></i></span>
<span v-else style=" font-size: 14px;color: #00B453">{{ item.curAmt }}<i
class="el-icon-caret-top"></i></span>
<span v-if="String(item.curAmt).includes('-')"
style="font-size: 14px; color: #00B453; cursor: pointer;"
@click="goToCustManager(item.itemNm, 'fall')">
{{ item.curAmt }}<i class="el-icon-caret-bottom"></i>
</span>
<span v-else
style="font-size: 14px; color: #EF3F35; cursor: pointer;"
@click="goToCustManager(item.itemNm, 'rise')">
{{ item.curAmt }}<i class="el-icon-caret-top"></i>
</span>
</div>
</div>
</div>
@@ -875,6 +881,23 @@ export default {
}
},
methods: {
/** 跳转到管户报表页面 */
goToCustManager(itemNm, statusType) {
// 从 itemNm 中提取星级,如 "3星客户" → "3星""基础客户" → "基础"
// 使用正则匹配以"星"或"基础"或"长尾"开头的内容
let custLevel = itemNm.replace('客户', '').trim()
// 如果没有"客户"后缀,直接使用整个名称
if (custLevel === itemNm) {
custLevel = itemNm
}
this.$router.push({
path: '/gridSearch/accountManageReport',
query: {
custLevel: custLevel,
statusType: statusType
}
})
},
getData() {
this.loading = true
if (this.selectedTab === '3') {