Commit 41b8575b8d878d872639e7be4917241ddc4899ac
1 parent
b26eb626
CRM:完善客户导入
Showing
6 changed files
with
108 additions
and
28 deletions
yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java
| @@ -4,7 +4,7 @@ import cn.hutool.core.io.resource.ResourceUtil; | @@ -4,7 +4,7 @@ import cn.hutool.core.io.resource.ResourceUtil; | ||
| 4 | import cn.hutool.core.lang.Assert; | 4 | import cn.hutool.core.lang.Assert; |
| 5 | import cn.hutool.core.text.csv.CsvRow; | 5 | import cn.hutool.core.text.csv.CsvRow; |
| 6 | import cn.hutool.core.text.csv.CsvUtil; | 6 | import cn.hutool.core.text.csv.CsvUtil; |
| 7 | -import cn.hutool.core.util.StrUtil; | 7 | +import cn.hutool.core.util.ObjUtil; |
| 8 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | 8 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| 9 | import cn.iocoder.yudao.framework.ip.core.Area; | 9 | import cn.iocoder.yudao.framework.ip.core.Area; |
| 10 | import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; | 10 | import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; |
| @@ -18,6 +18,7 @@ import java.util.Map; | @@ -18,6 +18,7 @@ import java.util.Map; | ||
| 18 | import java.util.function.Function; | 18 | import java.util.function.Function; |
| 19 | 19 | ||
| 20 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | 20 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| 21 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst; | ||
| 21 | 22 | ||
| 22 | /** | 23 | /** |
| 23 | * 区域工具类 | 24 | * 区域工具类 |
| @@ -76,29 +77,55 @@ public class AreaUtils { | @@ -76,29 +77,55 @@ public class AreaUtils { | ||
| 76 | } | 77 | } |
| 77 | 78 | ||
| 78 | /** | 79 | /** |
| 80 | + * 获得指定区域对应的编号 | ||
| 81 | + * | ||
| 82 | + * @param path 区域编号 | ||
| 83 | + * @return 编号 | ||
| 84 | + */ | ||
| 85 | + public static Area getArea(String path) { | ||
| 86 | + String[] paths = path.split("/"); | ||
| 87 | + Area area = null; | ||
| 88 | + for (int i = 0; i < paths.length; i++) { | ||
| 89 | + final int finalI = i; | ||
| 90 | + if (area == null) { | ||
| 91 | + area = findFirst(convertList(areas.values(), a -> a), item -> ObjUtil.equal(paths[finalI], item.getName())); | ||
| 92 | + continue; | ||
| 93 | + } | ||
| 94 | + area = findFirst(area.getChildren(), item -> ObjUtil.equal(paths[finalI], item.getName())); | ||
| 95 | + } | ||
| 96 | + return area; | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + /** | ||
| 79 | * 获取所有节点的全路径名称如:河南省/石家庄市/新华区 | 100 | * 获取所有节点的全路径名称如:河南省/石家庄市/新华区 |
| 80 | * | 101 | * |
| 81 | * @param areas 地区树 | 102 | * @param areas 地区树 |
| 82 | * @return 所有节点的全路径名称 | 103 | * @return 所有节点的全路径名称 |
| 83 | */ | 104 | */ |
| 84 | public static List<String> getAllAreaNodePaths(List<Area> areas) { | 105 | public static List<String> getAllAreaNodePaths(List<Area> areas) { |
| 85 | - return convertList(areas, AreaUtils::buildTreePath); | 106 | + List<String> paths = new ArrayList<>(); |
| 107 | + areas.forEach(area -> traverse(area, "", paths)); | ||
| 108 | + return paths; | ||
| 86 | } | 109 | } |
| 87 | 110 | ||
| 88 | - // TODO @puhui999: 展开树再构建全路径 | ||
| 89 | - private static String buildTreePath(Area node) { | ||
| 90 | - if (node.getParent() == null || Area.ID_CHINA.equals(node.getParent().getId())) { // 忽略中国 | ||
| 91 | - // 已经是根节点,直接返回节点名称 | ||
| 92 | - return node.getName(); | ||
| 93 | - } else { | ||
| 94 | - // 递归拼接上级节点的名称 | ||
| 95 | - Area parent = getArea(node.getParent().getId()); | ||
| 96 | - if (parent != null) { | ||
| 97 | - String parentPath = buildTreePath(parent); | ||
| 98 | - return parentPath + "/" + node.getName(); | ||
| 99 | - } | 111 | + /** |
| 112 | + * 构建一棵树的所有节点的全路径名称,并将其存储为 "祖先/父级/子级" 的形式 | ||
| 113 | + * | ||
| 114 | + * @param node 父节点 | ||
| 115 | + * @param path 全路径名称 | ||
| 116 | + * @param paths 全路径名称列表 | ||
| 117 | + */ | ||
| 118 | + private static void traverse(Area node, String path, List<String> paths) { | ||
| 119 | + if (node == null) { | ||
| 120 | + return; | ||
| 121 | + } | ||
| 122 | + // 构建当前节点的路径 | ||
| 123 | + String currentPath = path.isEmpty() ? node.getName() : path + "/" + node.getName(); | ||
| 124 | + paths.add(currentPath); | ||
| 125 | + // 递归遍历子节点 | ||
| 126 | + for (Area child : node.getChildren()) { | ||
| 127 | + traverse(child, currentPath, paths); | ||
| 100 | } | 128 | } |
| 101 | - return StrUtil.EMPTY; | ||
| 102 | } | 129 | } |
| 103 | 130 | ||
| 104 | /** | 131 | /** |
yudao-framework/yudao-spring-boot-starter-excel/pom.xml
| @@ -46,6 +46,11 @@ | @@ -46,6 +46,11 @@ | ||
| 46 | <groupId>com.alibaba</groupId> | 46 | <groupId>com.alibaba</groupId> |
| 47 | <artifactId>easyexcel</artifactId> | 47 | <artifactId>easyexcel</artifactId> |
| 48 | </dependency> | 48 | </dependency> |
| 49 | + <dependency> | ||
| 50 | + <groupId>cn.iocoder.boot</groupId> | ||
| 51 | + <artifactId>yudao-spring-boot-starter-biz-ip</artifactId> | ||
| 52 | + <scope>provided</scope> <!-- 设置为 provided,只有 ExcelUtils 使用 --> | ||
| 53 | + </dependency> | ||
| 49 | </dependencies> | 54 | </dependencies> |
| 50 | 55 | ||
| 51 | </project> | 56 | </project> |
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.excel.core.convert; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.convert.Convert; | ||
| 4 | +import cn.iocoder.yudao.framework.ip.core.Area; | ||
| 5 | +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; | ||
| 6 | +import com.alibaba.excel.converters.Converter; | ||
| 7 | +import com.alibaba.excel.enums.CellDataTypeEnum; | ||
| 8 | +import com.alibaba.excel.metadata.GlobalConfiguration; | ||
| 9 | +import com.alibaba.excel.metadata.data.ReadCellData; | ||
| 10 | +import com.alibaba.excel.metadata.property.ExcelContentProperty; | ||
| 11 | +import lombok.extern.slf4j.Slf4j; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * Excel 数据地区转换器 | ||
| 15 | + * | ||
| 16 | + * @author HUIHUI | ||
| 17 | + */ | ||
| 18 | +@Slf4j | ||
| 19 | +public class AreaConvert implements Converter<Object> { | ||
| 20 | + | ||
| 21 | + @Override | ||
| 22 | + public Class<?> supportJavaTypeKey() { | ||
| 23 | + throw new UnsupportedOperationException("暂不支持,也不需要"); | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + @Override | ||
| 27 | + public CellDataTypeEnum supportExcelTypeKey() { | ||
| 28 | + throw new UnsupportedOperationException("暂不支持,也不需要"); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + @Override | ||
| 32 | + public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty, | ||
| 33 | + GlobalConfiguration globalConfiguration) { | ||
| 34 | + // 解析地区编号 | ||
| 35 | + String label = readCellData.getStringValue(); | ||
| 36 | + Area area = AreaUtils.getArea(label); | ||
| 37 | + if (area == null) { | ||
| 38 | + log.error("[convertToJavaData][label({}) 解析不掉]", label); | ||
| 39 | + return null; | ||
| 40 | + } | ||
| 41 | + // 将 value 转换成对应的属性 | ||
| 42 | + Class<?> fieldClazz = contentProperty.getField().getType(); | ||
| 43 | + return Convert.convert(fieldClazz, area.getId()); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | +} |
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java
| @@ -9,6 +9,7 @@ import org.apache.poi.hssf.usermodel.HSSFDataValidation; | @@ -9,6 +9,7 @@ import org.apache.poi.hssf.usermodel.HSSFDataValidation; | ||
| 9 | import org.apache.poi.ss.usermodel.*; | 9 | import org.apache.poi.ss.usermodel.*; |
| 10 | import org.apache.poi.ss.util.CellRangeAddressList; | 10 | import org.apache.poi.ss.util.CellRangeAddressList; |
| 11 | 11 | ||
| 12 | +import java.util.Comparator; | ||
| 12 | import java.util.List; | 13 | import java.util.List; |
| 13 | 14 | ||
| 14 | /** | 15 | /** |
| @@ -18,12 +19,13 @@ import java.util.List; | @@ -18,12 +19,13 @@ import java.util.List; | ||
| 18 | */ | 19 | */ |
| 19 | public class SelectSheetWriteHandler implements SheetWriteHandler { | 20 | public class SelectSheetWriteHandler implements SheetWriteHandler { |
| 20 | 21 | ||
| 21 | - private List<KeyValue<Integer, List<String>>> selectMap; | 22 | + private final List<KeyValue<Integer, List<String>>> selectMap; |
| 22 | 23 | ||
| 23 | - private final char[] alphabet = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', | 24 | + private static final char[] ALPHABET = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', |
| 24 | 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; | 25 | 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; |
| 25 | 26 | ||
| 26 | public SelectSheetWriteHandler(List<KeyValue<Integer, List<String>>> selectMap) { | 27 | public SelectSheetWriteHandler(List<KeyValue<Integer, List<String>>> selectMap) { |
| 28 | + selectMap.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错 | ||
| 27 | this.selectMap = selectMap; | 29 | this.selectMap = selectMap; |
| 28 | } | 30 | } |
| 29 | 31 | ||
| @@ -86,17 +88,17 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { | @@ -86,17 +88,17 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { | ||
| 86 | */ | 88 | */ |
| 87 | private String getExcelColumn(int num) { | 89 | private String getExcelColumn(int num) { |
| 88 | String column = ""; | 90 | String column = ""; |
| 89 | - int len = alphabet.length - 1; | 91 | + int len = ALPHABET.length - 1; |
| 90 | int first = num / len; | 92 | int first = num / len; |
| 91 | int second = num % len; | 93 | int second = num % len; |
| 92 | if (num <= len) { | 94 | if (num <= len) { |
| 93 | - column = alphabet[num] + ""; | 95 | + column = ALPHABET[num] + ""; |
| 94 | } else { | 96 | } else { |
| 95 | - column = alphabet[first - 1] + ""; | 97 | + column = ALPHABET[first - 1] + ""; |
| 96 | if (second == 0) { | 98 | if (second == 0) { |
| 97 | - column = column + alphabet[len] + ""; | 99 | + column = column + ALPHABET[len] + ""; |
| 98 | } else { | 100 | } else { |
| 99 | - column = column + alphabet[second - 1] + ""; | 101 | + column = column + ALPHABET[second - 1] + ""; |
| 100 | } | 102 | } |
| 101 | } | 103 | } |
| 102 | return column; | 104 | return column; |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
| @@ -37,7 +37,10 @@ import org.springframework.web.bind.annotation.*; | @@ -37,7 +37,10 @@ import org.springframework.web.bind.annotation.*; | ||
| 37 | 37 | ||
| 38 | import java.io.IOException; | 38 | import java.io.IOException; |
| 39 | import java.time.LocalDateTime; | 39 | import java.time.LocalDateTime; |
| 40 | -import java.util.*; | 40 | +import java.util.ArrayList; |
| 41 | +import java.util.Arrays; | ||
| 42 | +import java.util.List; | ||
| 43 | +import java.util.Map; | ||
| 41 | import java.util.stream.Stream; | 44 | import java.util.stream.Stream; |
| 42 | 45 | ||
| 43 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 46 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| @@ -332,8 +335,6 @@ public class CrmCustomerController { | @@ -332,8 +335,6 @@ public class CrmCustomerController { | ||
| 332 | // 获取客户来源 | 335 | // 获取客户来源 |
| 333 | List<String> customerSources = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_SOURCE); | 336 | List<String> customerSources = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_SOURCE); |
| 334 | selectMap.add(new KeyValue<>(10, customerSources)); | 337 | selectMap.add(new KeyValue<>(10, customerSources)); |
| 335 | - // 升序不然创建下拉会报错 | ||
| 336 | - selectMap.sort(Comparator.comparing(item -> item.getValue().size())); | ||
| 337 | return selectMap; | 338 | return selectMap; |
| 338 | } | 339 | } |
| 339 | 340 |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java
| 1 | package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; | 1 | package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; | 3 | import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; |
| 4 | +import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert; | ||
| 4 | import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; | 5 | import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; |
| 5 | import com.alibaba.excel.annotation.ExcelProperty; | 6 | import com.alibaba.excel.annotation.ExcelProperty; |
| 6 | import lombok.AllArgsConstructor; | 7 | import lombok.AllArgsConstructor; |
| @@ -39,14 +40,12 @@ public class CrmCustomerImportExcelVO { | @@ -39,14 +40,12 @@ public class CrmCustomerImportExcelVO { | ||
| 39 | @ExcelProperty("邮箱") | 40 | @ExcelProperty("邮箱") |
| 40 | private String email; | 41 | private String email; |
| 41 | 42 | ||
| 42 | - // TODO @puhui999:需要选择省市区,需要研究下,怎么搞合理点; | ||
| 43 | - @ExcelProperty("地区编号") | 43 | + @ExcelProperty(value = "地区", converter = AreaConvert.class) |
| 44 | private Integer areaId; | 44 | private Integer areaId; |
| 45 | 45 | ||
| 46 | @ExcelProperty("详细地址") | 46 | @ExcelProperty("详细地址") |
| 47 | private String detailAddress; | 47 | private String detailAddress; |
| 48 | 48 | ||
| 49 | - // TODO @puhui999:industryId、level、source 字段,可以研究下怎么搞下拉框 | ||
| 50 | @ExcelProperty(value = "所属行业", converter = DictConvert.class) | 49 | @ExcelProperty(value = "所属行业", converter = DictConvert.class) |
| 51 | @DictFormat(CRM_CUSTOMER_INDUSTRY) | 50 | @DictFormat(CRM_CUSTOMER_INDUSTRY) |
| 52 | private Integer industryId; | 51 | private Integer industryId; |