From 650d423b15a40031f02fdfaff15dfef872857825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B3=95=E6=8B=8951246?= <719602854@qq.com> Date: Fri, 6 Jun 2025 08:59:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E6=8E=A5=E5=8F=A3=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E5=87=BA=E5=AE=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/handler/DefaultDBFieldHandler.java | 11 +- .../core/util/SecurityFrameworkUtils.java | 7 + .../web/core/util/WebFrameworkUtils.java | 21 + .../controller/admin/user/UserController.java | 8 +- .../admin/user/vo/user/UserImportExcelVO.java | 6 +- .../service/user/AdminUserServiceImpl.java | 15 +- yudao-module-visit/pom.xml | 6 + .../customerinfo/CustomerInfoController.java | 31 ++ .../vo/CustomerImportExcelVO.java | 47 +++ .../customerinfo/vo/CustomerImportRespVO.java | 24 ++ .../controller/admin/home/HomeController.java | 104 +++++ .../admin/home/vo/HomeBfrjfbqkRespVO.java | 11 + .../admin/home/vo/HomeBfrtjRespVO.java | 11 + .../admin/home/vo/HomeBfztjRespVO.java | 20 + .../admin/home/vo/HomeFirstRespVO.java | 40 ++ .../admin/home/vo/HomeKhxzdjzbqkRespVO.java | 25 ++ .../controller/admin/home/vo/HomeReqVO.java | 26 ++ .../controller/admin/info/vo/InfoRespVO.java | 8 +- .../admin/product/ProductController.java | 2 +- .../customerinfo/CustomerInfoMapper.java | 10 + .../visit/dal/mysql/info/InfoMapper.java | 13 + .../visit/enums/ErrorCodeConstants.java | 4 +- .../customerinfo/CustomerInfoService.java | 2 + .../customerinfo/CustomerInfoServiceImpl.java | 42 ++ .../visit/service/home/HomeService.java | 32 ++ .../visit/service/home/HomeServiceImpl.java | 396 ++++++++++++++++++ .../src/api/visit/customerinfo/index.ts | 6 + .../src/views/system/dept/index.vue | 1 + .../visit/customerinfo/CustomerImportForm.vue | 134 ++++++ .../src/views/visit/customerinfo/index.vue | 27 +- 30 files changed, 1065 insertions(+), 25 deletions(-) create mode 100644 yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/vo/CustomerImportExcelVO.java create mode 100644 yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/vo/CustomerImportRespVO.java create mode 100644 yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/HomeController.java create mode 100644 yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfrjfbqkRespVO.java create mode 100644 yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfrtjRespVO.java create mode 100644 yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfztjRespVO.java create mode 100644 yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeFirstRespVO.java create mode 100644 yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeKhxzdjzbqkRespVO.java create mode 100644 yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeReqVO.java create mode 100644 yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/home/HomeService.java create mode 100644 yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/home/HomeServiceImpl.java create mode 100644 yudao-ui/yudao-ui-admin-vue3/src/views/visit/customerinfo/CustomerImportForm.vue diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java index 94dada1..16d3af3 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java @@ -32,14 +32,15 @@ public class DefaultDBFieldHandler implements MetaObjectHandler { baseDO.setUpdateTime(current); } - Long userId = WebFrameworkUtils.getLoginUserId(); +// Long userId = WebFrameworkUtils.getLoginUserId(); + String userName = WebFrameworkUtils.getLoginUserName(); // 当前登录用户不为空,创建人为空,则当前登录用户为创建人 - if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) { - baseDO.setCreator(userId.toString()); + if (Objects.nonNull(userName) && Objects.isNull(baseDO.getCreator())) { + baseDO.setCreator(userName); } // 当前登录用户不为空,更新人为空,则当前登录用户为更新人 - if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) { - baseDO.setUpdater(userId.toString()); + if (Objects.nonNull(userName) && Objects.isNull(baseDO.getUpdater())) { + baseDO.setUpdater(userName); } } } diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java index ffc5861..90b8250 100644 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java @@ -15,6 +15,8 @@ import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import java.util.Collections; +import java.util.Objects; +import java.util.Optional; /** * 安全服务工具类 @@ -127,6 +129,11 @@ public class SecurityFrameworkUtils { // 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号; // 原因是,Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息 WebFrameworkUtils.setLoginUserId(request, loginUser.getId()); + String nickname = Optional.ofNullable(loginUser.getInfo()) + .map(info -> info.get("nickname")) + .orElse("未知"); + + WebFrameworkUtils.setLoginUserName(request, nickname); WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType()); } diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java index 6dc18c2..f41d7f5 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java @@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletRequest; public class WebFrameworkUtils { private static final String REQUEST_ATTRIBUTE_LOGIN_USER_ID = "login_user_id"; + private static final String REQUEST_ATTRIBUTE_LOGIN_USER_NAME = "login_user_name"; private static final String REQUEST_ATTRIBUTE_LOGIN_USER_TYPE = "login_user_type"; private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result"; @@ -68,6 +69,16 @@ public class WebFrameworkUtils { request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId); } + /** + * 登录用户的用户名 + * + * @param request 登录用户 + * @param nickname 用户 + */ + public static void setLoginUserName(ServletRequest request, String nickname) { + request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_NAME, nickname); + } + /** * 设置用户类型 * @@ -91,6 +102,12 @@ public class WebFrameworkUtils { } return (Long) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID); } + public static String getLoginUserName(HttpServletRequest request) { + if (request == null) { + return null; + } + return request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_NAME).toString(); + } /** * 获得当前用户的类型 @@ -127,6 +144,10 @@ public class WebFrameworkUtils { HttpServletRequest request = getRequest(); return getLoginUserId(request); } + public static String getLoginUserName() { + HttpServletRequest request = getRequest(); + return getLoginUserName(request); + } public static Integer getTerminal() { HttpServletRequest request = getRequest(); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java index 1f6600b..07839f2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java @@ -147,10 +147,10 @@ public class UserController { public void importTemplate(HttpServletResponse response) throws IOException { // 手动创建导出 demo List list = Arrays.asList( - UserImportExcelVO.builder().username("falao").deptId(1L).email("666666@qq.com").mobile("15601691300") - .nickname("模板用户1").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(), - UserImportExcelVO.builder().username("fala").deptId(2L).email("666666@qq.com").mobile("15601701300") - .nickname("模板用户2").status(CommonStatusEnum.DISABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build() + UserImportExcelVO.builder().username("zhangsan").deptId(1L).email("666666@qq.com").mobile("15601691300") + .nickname("张三").status(CommonStatusEnum.ENABLE.getStatus()).build(), + UserImportExcelVO.builder().username("lisi").deptId(2L).email("666666@qq.com").mobile("15601701300") + .nickname("李四").status(CommonStatusEnum.DISABLE.getStatus()).build() ); // 输出 ExcelUtils.write(response, "用户导入模板.xls", "用户列表", UserImportExcelVO.class, list); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java index a360f1a..83a89f2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserImportExcelVO.java @@ -23,7 +23,7 @@ public class UserImportExcelVO { @ExcelProperty("登录名称") private String username; - @ExcelProperty("用户名称") + @ExcelProperty("真实姓名") private String nickname; @ExcelProperty("部门编号") @@ -35,10 +35,6 @@ public class UserImportExcelVO { @ExcelProperty("手机号码") private String mobile; - @ExcelProperty(value = "用户性别", converter = DictConvert.class) - @DictFormat(DictTypeConstants.USER_SEX) - private Integer sex; - @ExcelProperty(value = "账号状态", converter = DictConvert.class) @DictFormat(DictTypeConstants.COMMON_STATUS) private Integer status; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index c3043f9..2bf35eb 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -50,6 +50,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*; +import static net.sf.jsqlparser.util.validation.metadata.NamedObject.user; /** * 后台用户 Service 实现类 @@ -110,7 +111,10 @@ public class AdminUserServiceImpl implements AdminUserService { userPostMapper.insertBatch(convertList(user.getPostIds(), postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId))); } - + // 2.3 默认都给一个id159业务员角色 + Set roles = new HashSet<>(); + roles.add(159L); + permissionService.assignUserRole(user.getId(), roles); // 3. 记录操作日志上下文 LogRecordContext.putVariable("user", user); return user.getId(); @@ -482,8 +486,13 @@ public class AdminUserServiceImpl implements AdminUserService { // 2.2.1 判断如果不存在,在进行插入 AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername()); if (existUser == null) { - userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class) - .setPassword(encodePassword(initPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组 + AdminUserDO adminUserDO = BeanUtils.toBean(importUser, AdminUserDO.class) + .setPassword(encodePassword(initPassword)).setPostIds(new HashSet<>()); + userMapper.insert(adminUserDO); // 设置默认密码及空岗位编号数组 + //插入成功后,给该用户一个默认的业务员角色 + Set roles = new HashSet<>(); + roles.add(159L); + permissionService.assignUserRole(adminUserDO.getId(), roles); respVO.getCreateUsernames().add(importUser.getUsername()); return; } diff --git a/yudao-module-visit/pom.xml b/yudao-module-visit/pom.xml index bad052a..615dd2f 100644 --- a/yudao-module-visit/pom.xml +++ b/yudao-module-visit/pom.xml @@ -51,6 +51,12 @@ 2.4.2-jdk8-SNAPSHOT compile + + cn.iocoder.boot + yudao-module-system-biz + 2.4.2-jdk8-SNAPSHOT + compile + diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/CustomerInfoController.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/CustomerInfoController.java index dfae132..d9b292e 100644 --- a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/CustomerInfoController.java +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/CustomerInfoController.java @@ -1,8 +1,12 @@ package cn.iocoder.yudao.module.visit.controller.admin.customerinfo; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.module.visit.controller.admin.product.vo.ProductImportExcelVO; +import cn.iocoder.yudao.module.visit.controller.admin.product.vo.ProductImportRespVO; import cn.iocoder.yudao.module.visit.controller.admin.product.vo.ProductSimpleReqVO; import cn.iocoder.yudao.module.visit.service.product.ProductService; import com.fhs.common.utils.StringUtil; +import io.swagger.v3.oas.annotations.Parameters; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import org.springframework.validation.annotation.Validated; @@ -31,6 +35,7 @@ import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*; import cn.iocoder.yudao.module.visit.controller.admin.customerinfo.vo.*; import cn.iocoder.yudao.module.visit.dal.dataobject.customerinfo.CustomerInfoDO; import cn.iocoder.yudao.module.visit.service.customerinfo.CustomerInfoService; +import org.springframework.web.multipart.MultipartFile; @Tag(name = "管理后台 - 客户信息") @RestController @@ -130,5 +135,31 @@ public class CustomerInfoController { } + @GetMapping("/get-import-template") + @Operation(summary = "获得导入客户信息模板") + public void importTemplate(HttpServletResponse response) throws IOException { + // 手动创建导出 demo + List list = Collections.singletonList( + CustomerImportExcelVO.builder().customerName("张三").companyName("xxx有限公司").contact("13678944321") + .customerType(0).department("0").build() + ); + // 输出 + ExcelUtils.write(response, "客户信息导入模板.xls", "客户信息列表", CustomerImportExcelVO.class, list); + } + + @PostMapping("/import") + @Operation(summary = "导入客户信息") + @Parameters({ + @Parameter(name = "file", description = "Excel 文件", required = true), + @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true") + }) + @PreAuthorize("@ss.hasPermission('visit:customer-info:import')") + public CommonResult importExcel(@RequestParam("file") MultipartFile file, + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { + List list = ExcelUtils.read(file, CustomerImportExcelVO.class); + return success(customerInfoService.importCustomerList(list, updateSupport)); + } + + } \ No newline at end of file diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/vo/CustomerImportExcelVO.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/vo/CustomerImportExcelVO.java new file mode 100644 index 0000000..55a128e --- /dev/null +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/vo/CustomerImportExcelVO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.visit.controller.admin.customerinfo.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.system.enums.DictTypeConstants; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.math.BigDecimal; + +/** + * 用户 Excel 导入 VO + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = false) // 设置 chain = false,避免用户导入有问题 +public class CustomerImportExcelVO { + + @ExcelProperty("客户姓名") + private String customerName; + + @ExcelProperty("联系方式") + @Pattern(regexp = "^$|^\\d{11}$", message = "联系方式必须是11位数字") + private String contact; + + @ExcelProperty("公司名称") + private String companyName; + + @ExcelProperty(value = "性质等级", converter = DictConvert.class) + @DictFormat("customer_type") + private Integer customerType; + + @ExcelProperty(value = "客户部门", converter = DictConvert.class) + @DictFormat("customer_dept") + private String department; + +} diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/vo/CustomerImportRespVO.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/vo/CustomerImportRespVO.java new file mode 100644 index 0000000..11d8dab --- /dev/null +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/vo/CustomerImportRespVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.visit.controller.admin.customerinfo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - 客户信息导入 Response VO") +@Data +@Builder +public class CustomerImportRespVO { + + @Schema(description = "创建成功的公司名称数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List createCompanyNames; + + @Schema(description = "更新成功的公司名称数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List updateCompanyNames; + + @Schema(description = "导入失败的公司名称用户集合,key 为公司名称,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED) + private Map failureCompanyNames; + +} diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/HomeController.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/HomeController.java new file mode 100644 index 0000000..a38805a --- /dev/null +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/HomeController.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.module.visit.controller.admin.home; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.visit.controller.admin.home.vo.*; +import cn.iocoder.yudao.module.visit.service.customerinfo.CustomerInfoService; +import cn.iocoder.yudao.module.visit.service.home.HomeService; +import cn.iocoder.yudao.module.visit.service.product.ProductService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 首页信息") +@RestController +@RequestMapping("/visit/home") +@Validated +public class HomeController { + + @Resource + private CustomerInfoService customerInfoService; + @Resource + private ProductService productService; + @Resource + private HomeService homeService; + + + @GetMapping("/getHomeInfoFirst") + @Operation(summary = "获得首页第一排信息") + public CommonResult getHomeInfoFirst(@Valid HomeReqVO reqVO) { + HomeFirstRespVO data = homeService.getHomeInfoFirst(reqVO); + return success(data); + } + + + @GetMapping("/getHomeInfoBfztj") + @Operation(summary = "获得首页拜访周统计") + public CommonResult getHomeInfoBfztj(@Valid HomeReqVO reqVO) { + HomeBfztjRespVO data = homeService.getHomeInfoBfztj(reqVO); + return success(data); + } + + + @GetMapping("/getHomeInfoKhxzdjzbqk") + @Operation(summary = "获得首页客户性质等级占比情况") + public CommonResult getHomeInfoKhxzdjzbqk(@Valid HomeReqVO reqVO) { + HomeKhxzdjzbqkRespVO data = homeService.getHomeInfoKhxzdjzbqk(reqVO); + return success(data); + } + + @GetMapping("/getHomeInfoBfrjfbqk") + @Operation(summary = "获得首页拜访人均分布情况") + public CommonResult getHomeInfoBfrjfbqk(@Valid HomeReqVO reqVO) { + HomeBfrjfbqkRespVO data = homeService.getHomeInfoBfrjfbqk(reqVO); + return success(data); + } + + @GetMapping("/getHomeInfoKhbffszbqk") + @Operation(summary = "获得首页客户拜访方式占比情况") + public CommonResult getHomeInfoKhbffszbqk(@Valid HomeReqVO reqVO) { + HomeKhxzdjzbqkRespVO data = homeService.getHomeInfoKhbffszbqk(reqVO); + return success(data); + } + + @GetMapping("/getHomeInfoKhbmzbqk") + @Operation(summary = "获得首页客户部门占比情况") + public CommonResult getHomeInfoKhbmzbqk(@Valid HomeReqVO reqVO) { + HomeKhxzdjzbqkRespVO data = homeService.getHomeInfoKhbmzbqk(reqVO); + return success(data); + } + + @GetMapping("/getHomeInfoKhbflxzbqk") + @Operation(summary = "获得首页客户拜访类型占比情况") + public CommonResult getHomeInfoKhbflxzbqk(@Valid HomeReqVO reqVO) { + HomeKhxzdjzbqkRespVO data = homeService.getHomeInfoKhbflxzbqk(reqVO); + return success(data); + } + + @GetMapping("/getHomeInfoBfcplxzbqk") + @Operation(summary = "获得首页拜访产品类型占比情况") + public CommonResult getHomeInfoBfcplxzbqk(@Valid HomeReqVO reqVO) { + HomeBfrjfbqkRespVO data = homeService.getHomeInfoBfcplxzbqk(reqVO); + return success(data); + } + + + @GetMapping("/getHomeInfoBfrtj") + @Operation(summary = "获得首页拜访日统计") + public CommonResult getHomeInfoBfrtj(@Valid HomeReqVO reqVO) { + HomeBfrtjRespVO data = homeService.getHomeInfoBfrtj(reqVO); + return success(data); + } + + + + + + + +} \ No newline at end of file diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfrjfbqkRespVO.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfrjfbqkRespVO.java new file mode 100644 index 0000000..0c63174 --- /dev/null +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfrjfbqkRespVO.java @@ -0,0 +1,11 @@ +package cn.iocoder.yudao.module.visit.controller.admin.home.vo; + +import lombok.Data; + +import java.util.List; + +@Data +public class HomeBfrjfbqkRespVO { + private List names; // xAxis,比如 ["张三", "李四", "王五"] + private List values; // series,比如 [78, 65, 41] +} diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfrtjRespVO.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfrtjRespVO.java new file mode 100644 index 0000000..df5f68a --- /dev/null +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfrtjRespVO.java @@ -0,0 +1,11 @@ +package cn.iocoder.yudao.module.visit.controller.admin.home.vo; + +import lombok.Data; + +import java.util.List; + +@Data +public class HomeBfrtjRespVO { + private List dates; // xAxis,如 ["4月01日", "4月02日", ...] + private List counts; // yAxis,对应每一天的拜访次数 +} diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfztjRespVO.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfztjRespVO.java new file mode 100644 index 0000000..089d9ac --- /dev/null +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeBfztjRespVO.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.visit.controller.admin.home.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "首页拜访周统计数据响应 VO") +public class HomeBfztjRespVO { + + @Schema(description = "周标签") + private List weeks; // 如:["WEEK1", "WEEK2", ..., "WEEKN"] + + @Schema(description = "拜访次数") + private List visitCounts; // 每周对应次数,如:[10, 25, 12, 3] + + @Schema(description = "总周数") + private Integer totalWeeks; +} diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeFirstRespVO.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeFirstRespVO.java new file mode 100644 index 0000000..8737468 --- /dev/null +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeFirstRespVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.visit.controller.admin.home.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.visit.controller.admin.product.vo.ProductSimpleReqVO; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 首页第一行 Response VO") +@Data +public class HomeFirstRespVO { + + @Schema(description = "拜访客户数量") + private String bfkhsl; + + @Schema(description = "商业客户数量") + private String sykhsl; + + @Schema(description = "医疗客户数量") + private String ylkhsl; + + @Schema(description = "客户拜访总次数") + private String visitCount; + + @Schema(description = "商业拜访次数") + private String sykfcs; + + @Schema(description = "医疗拜访次数") + private String ylkfcs; + + @Schema(description = "业务员数量") + private String ywysl; + +} \ No newline at end of file diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeKhxzdjzbqkRespVO.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeKhxzdjzbqkRespVO.java new file mode 100644 index 0000000..b299bf5 --- /dev/null +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeKhxzdjzbqkRespVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.visit.controller.admin.home.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class HomeKhxzdjzbqkRespVO { + + @Schema(description = "总拜访次数") + private Integer total; + + @Schema(description = "按客户类型分类的统计数据") + private List list; + + @Data + public static class TypeCountItem { + @Schema(description = "客户类型标签(字典值)") + private String name; + + @Schema(description = "拜访数量") + private Integer value; + } +} diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeReqVO.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeReqVO.java new file mode 100644 index 0000000..4fc0548 --- /dev/null +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/home/vo/HomeReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.visit.controller.admin.home.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 首页数据 request VO") +@Data +@ToString(callSuper = true) +public class HomeReqVO { + + @Schema(description = "公司名称") + private String companyName; + + @Schema(description = "查询时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] searchTime; + +} \ No newline at end of file diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/info/vo/InfoRespVO.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/info/vo/InfoRespVO.java index 36bae40..db92246 100644 --- a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/info/vo/InfoRespVO.java +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/info/vo/InfoRespVO.java @@ -85,12 +85,12 @@ public class InfoRespVO { private String visitProductNames; @Schema(description = "拜访方式(字典)") - @ExcelProperty(value = "拜访方式(字典)", converter = DictConvert.class) + @ExcelProperty(value = "拜访方式", converter = DictConvert.class) @DictFormat("visit_method") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中 private Integer visitMethod; @Schema(description = "拜访类型(字典)", example = "2") - @ExcelProperty(value = "拜访类型(字典)", converter = DictConvert.class) + @ExcelProperty(value = "拜访类型", converter = DictConvert.class) @DictFormat("visit_type") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中 private Integer visitType; @@ -109,6 +109,10 @@ public class InfoRespVO { @Schema(description = "服务记录图片URL列表(JSON数组)") private String serviceImages; + @Schema(description = "业务经理") + @ExcelProperty("业务经理") + private String creator; + } \ No newline at end of file diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/product/ProductController.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/product/ProductController.java index 813cee2..b802481 100644 --- a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/product/ProductController.java +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/product/ProductController.java @@ -128,7 +128,7 @@ public class ProductController { @Parameter(name = "file", description = "Excel 文件", required = true), @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true") }) - @PreAuthorize("@ss.hasPermission('system:product:import')") + @PreAuthorize("@ss.hasPermission('visit:product:import')") public CommonResult importExcel(@RequestParam("file") MultipartFile file, @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { List list = ExcelUtils.read(file, ProductImportExcelVO.class); diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/dal/mysql/customerinfo/CustomerInfoMapper.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/dal/mysql/customerinfo/CustomerInfoMapper.java index 7d98de7..44ecbf8 100644 --- a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/dal/mysql/customerinfo/CustomerInfoMapper.java +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/dal/mysql/customerinfo/CustomerInfoMapper.java @@ -27,6 +27,16 @@ public interface CustomerInfoMapper extends BaseMapperX { .betweenIfPresent(CustomerInfoDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(CustomerInfoDO::getId)); } + default PageResult selectHomePage(CustomerInfoPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(CustomerInfoDO::getCustomerName, reqVO.getCustomerName()) + .eqIfPresent(CustomerInfoDO::getContact, reqVO.getContact()) + .eqIfPresent(CustomerInfoDO::getCompanyName, reqVO.getCompanyName()) + .eqIfPresent(CustomerInfoDO::getCustomerType, reqVO.getCustomerType()) + .eqIfPresent(CustomerInfoDO::getCityCode, reqVO.getCityCode()) + .betweenIfPresent(CustomerInfoDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(CustomerInfoDO::getId)); + } //根据公司名称查询客户信息(验证是否唯一使用) default CustomerInfoDO selectByCompanyName(String companyName){ diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/dal/mysql/info/InfoMapper.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/dal/mysql/info/InfoMapper.java index 78d91b3..f72051f 100644 --- a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/dal/mysql/info/InfoMapper.java +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/dal/mysql/info/InfoMapper.java @@ -30,6 +30,19 @@ public interface InfoMapper extends BaseMapperX { .betweenIfPresent(InfoDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(InfoDO::getId)); } + default PageResult selectHomePage(InfoPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(InfoDO::getCustomerName, reqVO.getCustomerName()) + .eqIfPresent(InfoDO::getContact, reqVO.getContact()) + .eqIfPresent(InfoDO::getCompanyName, reqVO.getCompanyName()) + .betweenIfPresent(InfoDO::getVisitDate, reqVO.getVisitDate()) + .eqIfPresent(InfoDO::getCustomerStatus, reqVO.getCustomerStatus()) + .eqIfPresent(InfoDO::getVisitProductIds, reqVO.getVisitProductIds()) + .eqIfPresent(InfoDO::getVisitMethod, reqVO.getVisitMethod()) + .eqIfPresent(InfoDO::getVisitType, reqVO.getVisitType()) + .betweenIfPresent(InfoDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(InfoDO::getId)); + } default List selectByCompanyName(String companyName) { return selectList(new LambdaQueryWrapperX() diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/enums/ErrorCodeConstants.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/enums/ErrorCodeConstants.java index 2078f40..a7e86b5 100644 --- a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/enums/ErrorCodeConstants.java +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/enums/ErrorCodeConstants.java @@ -11,12 +11,14 @@ public interface ErrorCodeConstants { // ========== 产品 ========== ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1001000001, "产品不存在"); - ErrorCode PRODUCT_IMPORT_LIST_IS_EMPTY = new ErrorCode(1001000002, "导入产品数据不能为空!"); ErrorCode PRODUCT_PRODUCT_EXISTS = new ErrorCode(1001000002, "导入产品已存在!"); + ErrorCode PRODUCT_IMPORT_LIST_IS_EMPTY = new ErrorCode(1001000003, "导入产品数据不能为空!"); // ========== 客户信息 ========== ErrorCode CUSTOMER_INFO_NOT_EXISTS = new ErrorCode(1002000001, "客户信息不存在"); ErrorCode CUSTOMER_INFO_COMPANY_NAME_DUPLICATE = new ErrorCode(1002000002, "该公司名称已存在"); + ErrorCode CUSTOMER_INFO_IMPORT_LIST_IS_EMPTY = new ErrorCode(1002000003, "导入客户数据不能为空!"); + ErrorCode CUSTOMER_INFO_EXISTS = new ErrorCode(1002000004, "导入客户数据已存在!"); // ========== 客户拜访记录 ========== diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/customerinfo/CustomerInfoService.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/customerinfo/CustomerInfoService.java index 10f98d3..a48afe6 100644 --- a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/customerinfo/CustomerInfoService.java +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/customerinfo/CustomerInfoService.java @@ -57,4 +57,6 @@ public interface CustomerInfoService { List getAllCustomerInfo(); + + CustomerImportRespVO importCustomerList(List list, Boolean updateSupport); } \ No newline at end of file diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/customerinfo/CustomerInfoServiceImpl.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/customerinfo/CustomerInfoServiceImpl.java index 5683a2a..5faacf9 100644 --- a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/customerinfo/CustomerInfoServiceImpl.java +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/customerinfo/CustomerInfoServiceImpl.java @@ -1,6 +1,10 @@ package cn.iocoder.yudao.module.visit.service.customerinfo; +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.module.infra.service.file.FileService; +import cn.iocoder.yudao.module.visit.controller.admin.product.vo.ProductPageReqVO; +import cn.iocoder.yudao.module.visit.dal.dataobject.product.ProductDO; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; @@ -131,4 +135,42 @@ public class CustomerInfoServiceImpl implements CustomerInfoService { return customerInfoMapper.selectList(); } + @Override + public CustomerImportRespVO importCustomerList(List list, Boolean updateSupport) { + // 1 参数校验 + if (CollUtil.isEmpty(list)) { + throw exception(CUSTOMER_INFO_IMPORT_LIST_IS_EMPTY); + } + + // 2. 遍历,逐个创建 or 更新 + CustomerImportRespVO respVO = CustomerImportRespVO.builder().createCompanyNames(new ArrayList<>()) + .updateCompanyNames(new ArrayList<>()).failureCompanyNames(new LinkedHashMap<>()).build(); + list.forEach(importCompany -> { + + // 2.1.1 校验,判断是否有不符合的原因 + try { + validateCompanyUnique(importCompany.getCompanyName()); + } catch (ServiceException ex) { + respVO.getFailureCompanyNames().put(importCompany.getCompanyName(), ex.getMessage()); + return; + } + + // 2.2.1 判断如果不存在,在进行插入 + CustomerInfoDO existCompany = customerInfoMapper.selectByCompanyName(importCompany.getCompanyName()); + if (existCompany == null) { + customerInfoMapper.insert(BeanUtils.toBean(importCompany, CustomerInfoDO.class)); + respVO.getCreateCompanyNames().add(importCompany.getCompanyName()); + } + }); + return respVO; + } + + private void validateCompanyUnique(String companyName) { + CustomerInfoPageReqVO customerPageReqVO = new CustomerInfoPageReqVO(); + customerPageReqVO.setCompanyName(companyName); + PageResult customerInfoDOPageResult = customerInfoMapper.selectPage(customerPageReqVO); + if (customerInfoDOPageResult.getTotal() > 0) { + throw exception(CUSTOMER_INFO_EXISTS); + } + } } \ No newline at end of file diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/home/HomeService.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/home/HomeService.java new file mode 100644 index 0000000..1200945 --- /dev/null +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/home/HomeService.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.visit.service.home; + +import cn.iocoder.yudao.module.visit.controller.admin.home.vo.*; + +import javax.validation.Valid; + +/** + * 主页 Service 接口 + * + * @author 超级管理员 + */ +public interface HomeService { + + + HomeFirstRespVO getHomeInfoFirst(HomeReqVO reqVO); + + HomeBfztjRespVO getHomeInfoBfztj(HomeReqVO reqVO); + + HomeKhxzdjzbqkRespVO getHomeInfoKhxzdjzbqk(@Valid HomeReqVO reqVO); + + HomeBfrjfbqkRespVO getHomeInfoBfrjfbqk(@Valid HomeReqVO reqVO); + + HomeKhxzdjzbqkRespVO getHomeInfoKhbffszbqk(@Valid HomeReqVO reqVO); + + HomeKhxzdjzbqkRespVO getHomeInfoKhbmzbqk(@Valid HomeReqVO reqVO); + + HomeKhxzdjzbqkRespVO getHomeInfoKhbflxzbqk(@Valid HomeReqVO reqVO); + + HomeBfrjfbqkRespVO getHomeInfoBfcplxzbqk(@Valid HomeReqVO reqVO); + + HomeBfrtjRespVO getHomeInfoBfrtj(@Valid HomeReqVO reqVO); +} \ No newline at end of file diff --git a/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/home/HomeServiceImpl.java b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/home/HomeServiceImpl.java new file mode 100644 index 0000000..644c080 --- /dev/null +++ b/yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/home/HomeServiceImpl.java @@ -0,0 +1,396 @@ +package cn.iocoder.yudao.module.visit.service.home; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.system.api.dict.DictDataApi; +import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; +import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper; +import cn.iocoder.yudao.module.visit.controller.admin.customerinfo.vo.CustomerInfoPageReqVO; +import cn.iocoder.yudao.module.visit.controller.admin.home.vo.*; +import cn.iocoder.yudao.module.visit.controller.admin.info.vo.InfoPageReqVO; +import cn.iocoder.yudao.module.visit.dal.dataobject.customerinfo.CustomerInfoDO; +import cn.iocoder.yudao.module.visit.dal.dataobject.info.InfoDO; +import cn.iocoder.yudao.module.visit.dal.dataobject.product.ProductDO; +import cn.iocoder.yudao.module.visit.dal.mysql.customerinfo.CustomerInfoMapper; +import cn.iocoder.yudao.module.visit.dal.mysql.info.InfoMapper; +import cn.iocoder.yudao.module.visit.dal.mysql.product.ProductMapper; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.Resource; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 主页 Service 实现类 + * + * @author 超级管理员 + */ +@Service +@Validated +public class HomeServiceImpl implements HomeService { + + @Resource + private CustomerInfoMapper customerInfoMapper; + @Resource + private InfoMapper infoMapper; + @Resource + private ProductMapper productMapper; + @Resource + private AdminUserMapper userMapper; + @Resource + private DictDataApi dictDataApi; + + //首页第一排数据 + @Override + public HomeFirstRespVO getHomeInfoFirst(HomeReqVO reqVO) { + HomeFirstRespVO homeFirstRespVO = new HomeFirstRespVO(); + //业务员数量(不按时间) + List userDOS = userMapper.selectList(); + homeFirstRespVO.setYwysl(String.valueOf(userDOS.size())); + //按创建时间查这段时间的客户数量 + CustomerInfoPageReqVO customerInfoPageReqVO = new CustomerInfoPageReqVO(); + customerInfoPageReqVO.setCreateTime(reqVO.getSearchTime()); + customerInfoPageReqVO.setPageSize(-1);//不分页 + customerInfoPageReqVO.setCompanyName(reqVO.getCompanyName()); + PageResult customerInfoDOPageResult = customerInfoMapper.selectHomePage(customerInfoPageReqVO);//精准查询 + //拜访客户总数量 + homeFirstRespVO.setBfkhsl(String.valueOf(customerInfoDOPageResult.getTotal())); + //商业客户数量 + String sykhsl = customerInfoDOPageResult.getList().stream().filter(customerInfoDO -> customerInfoDO.getCustomerType() == 0).count() + ""; + //医疗客户数量 + String ylkhsl = customerInfoDOPageResult.getList().stream().filter(customerInfoDO -> customerInfoDO.getCustomerType() == 1).count() + ""; + homeFirstRespVO.setSykhsl(sykhsl); + homeFirstRespVO.setYlkhsl(ylkhsl); + //按拜访时间查这段时间的拜访次数 + InfoPageReqVO infoPageReqVO = new InfoPageReqVO(); + infoPageReqVO.setVisitDate(reqVO.getSearchTime()); + infoPageReqVO.setPageSize(-1);//不分页 + infoPageReqVO.setCompanyName(reqVO.getCompanyName()); + PageResult infoDOPageResult = infoMapper.selectHomePage(infoPageReqVO);//精准查询 + //客户拜访总次数 + homeFirstRespVO.setVisitCount(String.valueOf(infoDOPageResult.getTotal())); + homeFirstRespVO.setSykfcs(String.valueOf(infoDOPageResult.getList().stream().filter(infoDO -> infoDO.getVisitType() == 0).count())); + homeFirstRespVO.setYlkfcs(String.valueOf(infoDOPageResult.getList().stream().filter(infoDO -> infoDO.getVisitType() == 1).count())); + return homeFirstRespVO; + } + + //拜访周统计 + @Override + public HomeBfztjRespVO getHomeInfoBfztj(HomeReqVO reqVO) { + LocalDateTime start = reqVO.getSearchTime()[0]; + LocalDateTime end = reqVO.getSearchTime()[1]; + + // 对齐到每周一 + start = start.with(DayOfWeek.MONDAY); + List weeks = new ArrayList<>(); + List counts = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + LocalDateTime weekStart = start.plusWeeks(i); + LocalDateTime weekEnd = weekStart.plusDays(6).with(LocalTime.MAX); + LocalDateTime[] week = new LocalDateTime[]{weekStart, weekEnd}; + if (weekStart.isAfter(end)) break; + + int count = 0;//拜访次数 + InfoPageReqVO infoPageReqVO = new InfoPageReqVO(); + infoPageReqVO.setVisitDate(week); + infoPageReqVO.setPageSize(-1);//不分页 + infoPageReqVO.setCompanyName(reqVO.getCompanyName()); + PageResult infoDOPageResult = infoMapper.selectHomePage(infoPageReqVO);//精准查询 + weeks.add("WEEK" + (i + 1)); + counts.add(count); + } + + HomeBfztjRespVO vo = new HomeBfztjRespVO(); + vo.setWeeks(weeks); + vo.setVisitCounts(counts); + vo.setTotalWeeks(weeks.size()); + return vo; + } + + //客户性质等级占比情况 + @Override + public HomeKhxzdjzbqkRespVO getHomeInfoKhxzdjzbqk(HomeReqVO reqVO) { + //1.先查出所有的客户信息 + InfoPageReqVO infoPageReqVO = new InfoPageReqVO(); + infoPageReqVO.setPageSize(-1); + infoPageReqVO.setVisitDate(reqVO.getSearchTime()); + infoPageReqVO.setCompanyName(reqVO.getCompanyName()); + PageResult infoDOPageResult = infoMapper.selectHomePage(infoPageReqVO); + List records = infoDOPageResult.getList(); + + // 2.分组计数:Map + Map typeCountMap = records.stream() + .collect(Collectors.groupingBy(InfoDO::getCustomerStatus, Collectors.counting())); + + // 3. 加载字典并转换为 Map + List dictList = dictDataApi.getDictDataList("customer_type"); + Map dictMap = dictList.stream() + .collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel)); + + // 4. 组装返回列表 + List list = typeCountMap.entrySet().stream() + .map(entry -> { + HomeKhxzdjzbqkRespVO.TypeCountItem item = new HomeKhxzdjzbqkRespVO.TypeCountItem(); + item.setName(dictMap.getOrDefault(entry.getKey().toString(), "未知")); + item.setValue(entry.getValue().intValue()); + return item; + }) + .collect(Collectors.toList()); + + // 5. 返回结果 + HomeKhxzdjzbqkRespVO respVO = new HomeKhxzdjzbqkRespVO(); + respVO.setList(list); + respVO.setTotal(records.size()); + return respVO; + } + + //拜访人均分布情况 + @Override + public HomeBfrjfbqkRespVO getHomeInfoBfrjfbqk(HomeReqVO reqVO) { + //1.先查出所有的拜访记录 + InfoPageReqVO infoPageReqVO = new InfoPageReqVO(); + infoPageReqVO.setPageSize(-1); + infoPageReqVO.setVisitDate(reqVO.getSearchTime()); + infoPageReqVO.setCompanyName(reqVO.getCompanyName()); + PageResult infoDOPageResult = infoMapper.selectHomePage(infoPageReqVO); + List records = infoDOPageResult.getList(); + // 按业务员分组计数 + Map countMap = records.stream() + .filter(r -> StringUtils.isNotBlank(r.getCreator())) + .collect(Collectors.groupingBy(InfoDO::getCreator, Collectors.counting())); + + // 倒序排序,取前10 + List> top10 = countMap.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .limit(10) + .collect(Collectors.toList()); + + // 拆分为两个数组 + List names = top10.stream().map(Map.Entry::getKey).collect(Collectors.toList()); + List values = top10.stream().map(e -> e.getValue().intValue()).collect(Collectors.toList()); + + HomeBfrjfbqkRespVO respVO = new HomeBfrjfbqkRespVO(); + respVO.setNames(names); + respVO.setValues(values); + return respVO; + } + + // 客户拜访方式分布情况 + @Override + public HomeKhxzdjzbqkRespVO getHomeInfoKhbffszbqk(HomeReqVO reqVO) { + //1.先查出所有的客户信息 + InfoPageReqVO infoPageReqVO = new InfoPageReqVO(); + infoPageReqVO.setPageSize(-1); + infoPageReqVO.setVisitDate(reqVO.getSearchTime()); + infoPageReqVO.setCompanyName(reqVO.getCompanyName()); + PageResult infoDOPageResult = infoMapper.selectHomePage(infoPageReqVO); + List records = infoDOPageResult.getList(); + + // 2.分组计数:Map + Map typeCountMap = records.stream() + .collect(Collectors.groupingBy(InfoDO::getVisitMethod, Collectors.counting())); + + // 3. 加载字典并转换为 Map + List dictList = dictDataApi.getDictDataList("visit_method"); + Map dictMap = dictList.stream() + .collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel)); + + // 4. 组装返回列表 + List list = typeCountMap.entrySet().stream() + .map(entry -> { + HomeKhxzdjzbqkRespVO.TypeCountItem item = new HomeKhxzdjzbqkRespVO.TypeCountItem(); + item.setName(dictMap.getOrDefault(entry.getKey().toString(), "未知")); + item.setValue(entry.getValue().intValue()); + return item; + }) + .collect(Collectors.toList()); + + // 5. 返回结果 + HomeKhxzdjzbqkRespVO respVO = new HomeKhxzdjzbqkRespVO(); + respVO.setList(list); + respVO.setTotal(records.size()); + return respVO; + } + + // 客户部门分布情况 + @Override + public HomeKhxzdjzbqkRespVO getHomeInfoKhbmzbqk(HomeReqVO reqVO) { + //1.先查出所有的客户信息 + InfoPageReqVO infoPageReqVO = new InfoPageReqVO(); + infoPageReqVO.setPageSize(-1); + infoPageReqVO.setVisitDate(reqVO.getSearchTime()); + infoPageReqVO.setCompanyName(reqVO.getCompanyName()); + PageResult infoDOPageResult = infoMapper.selectHomePage(infoPageReqVO); + List records = infoDOPageResult.getList(); + + // 2.分组计数:Map + Map typeCountMap = records.stream() + .collect(Collectors.groupingBy(InfoDO::getDepartment, Collectors.counting())); + + // 3. 加载字典并转换为 Map + List dictList = dictDataApi.getDictDataList("customer_dept"); + Map dictMap = dictList.stream() + .collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel)); + + // 4. 组装返回列表 + List list = typeCountMap.entrySet().stream() + .map(entry -> { + HomeKhxzdjzbqkRespVO.TypeCountItem item = new HomeKhxzdjzbqkRespVO.TypeCountItem(); + item.setName(dictMap.getOrDefault(entry.getKey().toString(), "未知")); + item.setValue(entry.getValue().intValue()); + return item; + }) + .collect(Collectors.toList()); + + // 5. 返回结果 + HomeKhxzdjzbqkRespVO respVO = new HomeKhxzdjzbqkRespVO(); + respVO.setList(list); + respVO.setTotal(records.size()); + return respVO; + } + + // 客户拜访类型分布情况 + @Override + public HomeKhxzdjzbqkRespVO getHomeInfoKhbflxzbqk(HomeReqVO reqVO) { + //1.先查出所有的客户信息 + InfoPageReqVO infoPageReqVO = new InfoPageReqVO(); + infoPageReqVO.setPageSize(-1); + infoPageReqVO.setVisitDate(reqVO.getSearchTime()); + infoPageReqVO.setCompanyName(reqVO.getCompanyName()); + PageResult infoDOPageResult = infoMapper.selectHomePage(infoPageReqVO); + List records = infoDOPageResult.getList(); + + // 2.分组计数:Map + Map typeCountMap = records.stream() + .collect(Collectors.groupingBy(InfoDO::getVisitType, Collectors.counting())); + + // 3. 加载字典并转换为 Map + List dictList = dictDataApi.getDictDataList("visit_type"); + Map dictMap = dictList.stream() + .collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel)); + + // 4. 组装返回列表 + List list = typeCountMap.entrySet().stream() + .map(entry -> { + HomeKhxzdjzbqkRespVO.TypeCountItem item = new HomeKhxzdjzbqkRespVO.TypeCountItem(); + item.setName(dictMap.getOrDefault(entry.getKey().toString(), "未知")); + item.setValue(entry.getValue().intValue()); + return item; + }) + .collect(Collectors.toList()); + + // 5. 返回结果 + HomeKhxzdjzbqkRespVO respVO = new HomeKhxzdjzbqkRespVO(); + respVO.setList(list); + respVO.setTotal(records.size()); + return respVO; + } + + @Override + public HomeBfrjfbqkRespVO getHomeInfoBfcplxzbqk(HomeReqVO reqVO) { + //1.先查出所有的客户信息 + InfoPageReqVO infoPageReqVO = new InfoPageReqVO(); + infoPageReqVO.setPageSize(-1); + infoPageReqVO.setVisitDate(reqVO.getSearchTime()); + infoPageReqVO.setCompanyName(reqVO.getCompanyName()); + PageResult infoDOPageResult = infoMapper.selectHomePage(infoPageReqVO); + List records = infoDOPageResult.getList(); + // 2. 收集所有产品 ID + Set productIdSet = records.stream() + .flatMap(r -> { + String ids = r.getVisitProductIds(); + if (StringUtils.isBlank(ids)) return Stream.empty(); + return Arrays.stream(ids.split(",")) + .map(String::trim) + .filter(StringUtils::isNotBlank) + .map(Long::valueOf); + }) + .collect(Collectors.toSet()); + + // 3. 查询产品信息(id => 名称-规格) + List productList = productMapper.selectBatchIds(productIdSet); + Map productMap = productList.stream() + .collect(Collectors.toMap( + ProductDO::getId, + p -> p.getProductName() + "-" + p.getSpecification() + )); + + // 4. 遍历所有拜访记录统计产品使用次数 + Map statMap = new HashMap<>(); + for (InfoDO record : records) { + if (StringUtils.isBlank(record.getVisitProductIds())) continue; + for (String pidStr : record.getVisitProductIds().split(",")) { + Long pid = NumberUtils.toLong(pidStr.trim(), -1); + if (!productMap.containsKey(pid)) continue; + String key = productMap.get(pid); + statMap.merge(key, 1, Integer::sum); + } + } + + // 5. 排序取前 N(如 top10) + List> topList = statMap.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .limit(10) + .collect(Collectors.toList()); + + List names = topList.stream().map(Map.Entry::getKey).collect(Collectors.toList()); + List values = topList.stream().map(Map.Entry::getValue).collect(Collectors.toList()); + + // 6. 构建返回结果 + HomeBfrjfbqkRespVO vo = new HomeBfrjfbqkRespVO(); + vo.setNames(names); + vo.setValues(values); + return vo; + } + + @Override + public HomeBfrtjRespVO getHomeInfoBfrtj(HomeReqVO reqVO) { + //1.先查出所有的客户信息 + InfoPageReqVO infoPageReqVO = new InfoPageReqVO(); + infoPageReqVO.setPageSize(-1); + infoPageReqVO.setVisitDate(reqVO.getSearchTime()); + infoPageReqVO.setCompanyName(reqVO.getCompanyName()); + PageResult infoDOPageResult = infoMapper.selectHomePage(infoPageReqVO); + List records = infoDOPageResult.getList(); + // 2. 按日期(yyyy-MM-dd)分组计数 + Map dateCountMap = records.stream() + .collect(Collectors.groupingBy( + r -> r.getVisitDate().toLocalDate(), + Collectors.counting() + )); + + // 3. 排序(从晚到早)+ 限制最多31天 + List> sorted = dateCountMap.entrySet().stream() + .sorted(Map.Entry.comparingByKey().reversed()) + .limit(31) + .sorted(Map.Entry.comparingByKey()) // 前端从左到右看,从早到晚排列 + .collect(Collectors.toList()); + + // 4. 构造 VO + List dates = sorted.stream() + .map(e -> formatDate(e.getKey())) // 4月01日 + .collect(Collectors.toList()); + List counts = sorted.stream() + .map(e -> e.getValue().intValue()) + .collect(Collectors.toList()); + + HomeBfrtjRespVO vo = new HomeBfrtjRespVO(); + vo.setDates(dates); + vo.setCounts(counts); + return vo; + } + + // 工具方法:格式化为 4月01日 + private String formatDate(LocalDate date) { + return date.getMonthValue() + "月" + String.format("%02d", date.getDayOfMonth()) + "日"; + } +} \ No newline at end of file diff --git a/yudao-ui/yudao-ui-admin-vue3/src/api/visit/customerinfo/index.ts b/yudao-ui/yudao-ui-admin-vue3/src/api/visit/customerinfo/index.ts index 3e200b0..4ccd63e 100644 --- a/yudao-ui/yudao-ui-admin-vue3/src/api/visit/customerinfo/index.ts +++ b/yudao-ui/yudao-ui-admin-vue3/src/api/visit/customerinfo/index.ts @@ -69,4 +69,10 @@ export const CustomerInfoApi = { exportCustomerInfo: async (params) => { return await request.download({ url: `/visit/customer-info/export-excel`, params }) }, + + + // 下载客户信息导入模板 + importProductTemplate: async () => { + return await request.download({ url: '/visit/customer-info/get-import-template' }) + } } diff --git a/yudao-ui/yudao-ui-admin-vue3/src/views/system/dept/index.vue b/yudao-ui/yudao-ui-admin-vue3/src/views/system/dept/index.vue index 5e47846..9e42edd 100644 --- a/yudao-ui/yudao-ui-admin-vue3/src/views/system/dept/index.vue +++ b/yudao-ui/yudao-ui-admin-vue3/src/views/system/dept/index.vue @@ -66,6 +66,7 @@ +