liu
2026-09-03 34e14f4d8f4756f12ad90d0d478c4c2d575203b8
feat(srm): 供应商档案支持 Excel 导入导出

- SrmSupplierController 导出改走中文表头 SrmSupplierExcelVO,新增 get-import-template、import-excel 接口(权限 srm:supplier:import)
- 新增导入 VO 与结果 VO;SrmSupplierService 新增 importSupplierList(按编码判重、updateSupport 更新/新增、失败逐行汇总、类型支持名称/编码、状态留空默认启用)
- ErrorCodeConstants 新增 SUPPLIER_IMPORT_LIST_IS_EMPTY
- 前端(yudao-ui-admin-vue3 element-plus):供应商页新增导入按钮/弹窗与导入模板下载,导出补齐 download.excel
- docs:新增供应商档案导入导出联调方案

Co-Authored-By: Claude <noreply@anthropic.com>
已添加4个文件
已修改6个文件
611 ■■■■■ 文件已修改
docs/srm_supplier_import_export_integration.md 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierController.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierExcelVO.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierImportExcelVO.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierImportRespVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-ui/yudao-ui-admin-vue3/src/api/srm/supplier/index.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-ui/yudao-ui-admin-vue3/src/views/srm/supplier/index.vue 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/srm_supplier_import_export_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,116 @@
# SRM ä¾›åº”商档案 å¯¼å‡º / å¯¼å…¥ - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
| é¡µé¢ | å‰ç«¯ç»„ä»¶ | åŽç«¯æŽ¥å£å‰ç¼€ | è¯´æ˜Ž |
|------|----------|--------------|------|
| ä¾›åº”商档案 | `srm/supplier/index`(组件名 SrmSupplier) | `/srm/supplier` | æ–°å¢ž"导出、导入、下载模板"工具栏入口 |
## æƒé™ï¼ˆæŒ‰é’®æ˜¾éšï¼Œä¸ŽåŽç«¯ @PreAuthorize ä¸€è‡´ï¼‰
| æ“ä½œ | æŒ‰é’®æƒé™ | åŽç«¯æŽ¥å£æ ¡éªŒ |
|------|----------|--------------|
| å¯¼å‡º | `srm:supplier:export` | `/srm/supplier/export-excel` |
| ä¸‹è½½æ¨¡æ¿ / å¯¼å…¥ | `srm:supplier:import` | `/srm/supplier/get-import-template`、`/srm/supplier/import-excel` |
> æ–°æŒ‰é’®èœå• SQL è§ `sql/mysql/srm_supplier_import_export_menu.sql`(幂等,含普通角色授权);超级管理员自动拥有全部菜单。
> è‹¥åŽ†å²åº“ä¸­ `srm:supplier:export` æŒ‰é’®èœå•已存在但角色未分配,执行该脚本即可补齐。
## API ä¸€è§ˆ
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž | æƒé™ |
|------|------|------|------|
| GET | `/srm/supplier/export-excel` | æŒ‰å½“前列表查询条件导出全部命中行(不分页) | å¯¼å‡ºæƒé™ |
| GET | `/srm/supplier/get-import-template` | ä¸‹è½½å¯¼å…¥æ¨¡æ¿ï¼ˆå« 1 è¡Œç¤ºä¾‹æ•°æ®ï¼Œå¯åˆ åŽå¡«å†™ï¼‰ | å¯¼å…¥æƒé™ |
| POST | `/srm/supplier/import-excel` | ä¸Šä¼  Excel å¯¼å…¥ï¼ˆmultipart) | å¯¼å…¥æƒé™ |
### å¯¼å…¥æŽ¥å£å‚æ•°
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| file | MultipartFile | æ˜¯ | å¾…导入的 Excel(.xls/.xlsx) |
| updateSupport | Boolean | å¦ | æ˜¯å¦æ”¯æŒæ›´æ–°ï¼š`true`=按"供应商编码"已存在则更新;默认 `false`=仅新增,已存在则该行报"供应商编码已存在" |
### å¯¼å…¥å“åº”结构
`code: 0` è¡¨ç¤ºæŽ¥å£æ‰§è¡Œå®Œæˆï¼ˆéž 0 ä¸ºå‚æ•°/系统异常)。业务层的部分成功通过 `data` è¿”回:
```json
{
  "code": 0,
  "data": {
    "createList": ["新增成功的供应商名称"],
    "updateList": ["更新成功的供应商名称"],
    "failureList": { "失败的供应商名称": "失败原因" }
  }
}
```
> å‰ç«¯çº¦å®šï¼š`failureList` éžç©ºå³è§†ä¸º"部分失败",应弹窗逐行提示失败原因;`createList`/`updateList` ç”¨äºŽæ±‡æ€»æç¤º"成功新增 x æ¡ã€æ›´æ–° y æ¡"。
## å¯¼å‡ºæŽ¥å£
- å¤ç”¨åˆ—表查询过滤条件(GET æŸ¥è¯¢å‚数:code/name/type/status/contactName/contactMobile),导出全部命中行,无需传页码。
- æˆåŠŸè¿”å›žäºŒè¿›åˆ¶æ–‡ä»¶æµï¼Œæ–‡ä»¶åï¼š`供应商数据.xlsx`;失败时可能返回 HTTP 200 + JSON,需区分二进制与 JSON。
- å¯¼å‡ºè¡¨å¤´ä¸ºä¸­æ–‡ï¼Œ**类型列显示名称(生产型/贸易型/服务型/其他),状态列显示 å¯ç”¨/禁用**(与页面标签一致),便于阅读:
| åˆ—名 | å¯¹åº”字段 | è¯´æ˜Ž |
|------|----------|------|
| ä¾›åº”商编码 | code | |
| ä¾›åº”商名称 | name | |
| ä¾›åº”商简称 | shortName | |
| ä¾›åº”商类型 | type | å¯¼å‡ºä¸ºä¸­æ–‡åç§° |
| ä¼ä¸šæ€§è´¨ | companyNature | |
| æ³¨å†Œèµ„金(万元) | registeredCapital | |
| è¥ä¸šæ‰§ç…§ç¼–号 | businessLicenseNo | |
| ç¨Žå· | taxNo | |
| æ³•人代表 | legalPerson | |
| å…¬å¸åœ°å€ | address | |
| å…¬å¸ç½‘址 | website | |
| è”系人 | contactName | |
| è”系电话 | contactMobile | |
| é‚®ç®± | contactEmail | |
| å¼€æˆ·é“¶è¡Œ | bankName | |
| é“¶è¡Œè´¦å· | bankAccount | |
| çŠ¶æ€ | status | å¯¼å‡ºä¸º å¯ç”¨/禁用 |
| å¤‡æ³¨ | remark | |
| åˆ›å»ºæ—¶é—´ | createTime | |
> è¯´æ˜Žï¼šæ­¤å‰å¯¼å‡ºç›´æŽ¥ä½¿ç”¨ `SrmSupplierRespVO` è¾“出,表头为英文字段名;本次改造为独立 `SrmSupplierExcelVO`,表头与取值口径变化如上,导出文件不可直接回导(回导请用导入模板)。
## å¯¼å…¥æ¨¡æ¿è¯´æ˜Žï¼ˆä¸šåŠ¡è§„åˆ™ï¼‰
下载模板文件名:`供应商导入模板.xlsx`,内含 1 è¡Œç¤ºä¾‹æ•°æ®ï¼ˆå¯åˆ é™¤åŽæ•´è¡¨å¡«å†™ï¼‰ã€‚
| åˆ—名 | å¿…å¡« | å¡«å†™è§„则 |
|------|------|----------|
| ä¾›åº”商编码 | æ˜¯ | å…¨å±€å”¯ä¸€ï¼Œé‡å¤ç¼–码按 `updateSupport` å†³å®šæŠ¥é”™æˆ–æ›´æ–° |
| ä¾›åº”商名称 | æ˜¯ | |
| ä¾›åº”商简称 | å¦ | |
| ä¾›åº”商类型(生产型/贸易型/服务型/其他) | å¦ | å¯å¡«ä¸­æ–‡åç§°æˆ–编码 1/2/3/4;无法识别时按原样保存 |
| ä¼ä¸šæ€§è´¨(国企/民营/外资/合资) | å¦ | å¡«å®žé™…文本 |
| æ³¨å†Œèµ„金(万元) | å¦ | æ•°å€¼ |
| è¥ä¸šæ‰§ç…§ç¼–号 / ç¨Žå· | å¦ | |
| æ³•人代表 | å¦ | |
| å…¬å¸åœ°å€ / å…¬å¸ç½‘址 | å¦ | |
| è”系人 / è”系电话 / é‚®ç®± | å¦ | |
| å¼€æˆ·é“¶è¡Œ / é“¶è¡Œè´¦å· | å¦ | |
| çŠ¶æ€(0启用/1禁用) | å¦ | å¡« 0/1;留空默认 0(启用) |
| å¤‡æ³¨ | å¦ | |
行级处理规则:
| åœºæ™¯ | è§„则 |
|------|------|
| ç¼–码或名称为空 | è¯¥è¡Œå¤±è´¥ï¼š"供应商编码和供应商名称不能为空" |
| ç¼–码已存在 ä¸” updateSupport=false | è¯¥è¡Œå¤±è´¥ï¼š"供应商编码已存在" |
| ç¼–码已存在 ä¸” updateSupport=true | æ›´æ–°è¯¥ä¾›åº”商档案 |
| ç¼–码不存在 | æ–°å¢žä¾›åº”商档案 |
| å•行其它校验异常(如编码唯一冲突) | è¯¥è¡Œå¤±è´¥å¹¶è®°å½•原因,不影响其它行导入 |
## æ³¨æ„äº‹é¡¹
1. å»ºè®®åœ¨ä¾›åº”商档案工具栏增加"导入""下载模板"两个入口;导入交互需包含 `updateSupport` é€‰é¡¹ï¼ˆå¤é€‰æ¡†"更新已存在的供应商",默认不勾选),导入结果按上文响应结构给出成功/失败汇总与失败原因。
2. "导出"按钮现有实现保持不变(`export-excel`),仅后端输出列/表头优化。
3. ä¾›åº”商数据可通过准入审批自动创建,此类记录可能缺少编码/类型,导入模板不受影响(按编码匹配,空编码不影响既有数据)。
4. æƒé™è¯´æ˜Žï¼šæ¨¡æ¿ä¸‹è½½ä¸Žå¯¼å…¥ä½¿ç”¨åŒä¸€æƒé™ `srm:supplier:import`。
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierController.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import cn.iocoder.yudao.module.srm.enums.SupplierTypeEnum;
import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -17,8 +18,11 @@
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -88,12 +92,82 @@
    }
    @GetMapping("/export-excel")
    @Operation(summary = "导出供应商 Excel")
    @Operation(summary = "导出供应商档案 Excel")
    @PreAuthorize("@ss.hasPermission('srm:supplier:export')")
    public void exportSupplierExcel(@Valid SrmSupplierPageReqVO pageReqVO, HttpServletResponse response) throws IOException {
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        // å¤ç”¨åˆ†é¡µè¿‡æ»¤æ¡ä»¶ï¼Œå¯¼å‡ºå†…容与列表一致
        List<SrmSupplierDO> list = supplierService.getSupplierPage(pageReqVO).getList();
        ExcelUtils.write(response, "供应商数据.xlsx", "供应商", SrmSupplierRespVO.class, BeanUtils.toBean(list, SrmSupplierRespVO.class));
        List<SrmSupplierExcelVO> excelList = new ArrayList<>();
        for (SrmSupplierDO supplier : list) {
            SrmSupplierExcelVO excelVO = BeanUtils.toBean(supplier, SrmSupplierExcelVO.class);
            excelVO.setType(toTypeLabel(supplier.getType()));
            excelVO.setStatus(toStatusLabel(supplier.getStatus()));
            excelList.add(excelVO);
        }
        ExcelUtils.write(response, "供应商数据.xlsx", "供应商", SrmSupplierExcelVO.class, excelList);
    }
    @GetMapping("/get-import-template")
    @Operation(summary = "获得导入供应商档案模板")
    @PreAuthorize("@ss.hasPermission('srm:supplier:import')")
    public void getImportTemplate(HttpServletResponse response) throws IOException {
        SrmSupplierImportExcelVO sample = new SrmSupplierImportExcelVO();
        sample.setCode("GYS0001");
        sample.setName("示例供应商");
        sample.setShortName("示例");
        sample.setType("生产型");
        sample.setCompanyNature("民营");
        sample.setRegisteredCapital(new java.math.BigDecimal("1000"));
        sample.setBusinessLicenseNo("91320000XXXXXXXXXX");
        sample.setTaxNo("91320000XXXXXXXXXX");
        sample.setLegalPerson("张三");
        sample.setAddress("示例市示例区示例路1号");
        sample.setWebsite("http://www.example.com");
        sample.setContactName("李四");
        sample.setContactMobile("13800000000");
        sample.setContactEmail("contact@example.com");
        sample.setBankName("示例银行示例支行");
        sample.setBankAccount("6222020000000000000");
        sample.setStatus(0);
        sample.setRemark("导入模板示例行,可删除");
        ExcelUtils.write(response, "供应商导入模板.xlsx", "供应商", SrmSupplierImportExcelVO.class, Arrays.asList(sample));
    }
    @PostMapping("/import-excel")
    @Operation(summary = "导入供应商档案 Excel")
    @PreAuthorize("@ss.hasPermission('srm:supplier:import')")
    public CommonResult<SrmSupplierImportRespVO> importSupplierExcel(@RequestParam("file") MultipartFile file,
                                                                     @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
        List<SrmSupplierImportExcelVO> list = ExcelUtils.read(file, SrmSupplierImportExcelVO.class);
        return success(supplierService.importSupplierList(list, updateSupport));
    }
    // ========== Excel å±•示辅助 ==========
    /**
     * ä¾›åº”商类型编码 -> ä¸­æ–‡åç§°ï¼ˆå¦‚ 1 -> ç”Ÿäº§åž‹ï¼‰ï¼›æ— æ³•识别时原样返回
     */
    private static String toTypeLabel(String typeCode) {
        if (typeCode == null) {
            return null;
        }
        for (SupplierTypeEnum e : SupplierTypeEnum.values()) {
            if (typeCode.equals(String.valueOf(e.getCode()))) {
                return e.getName();
            }
        }
        return typeCode;
    }
    /**
     * çŠ¶æ€ 0 å¯ç”¨ / 1 ç¦ç”¨ -> ä¸­æ–‡æ–‡æ¡ˆï¼›ç©ºå€¼è¿”回空
     */
    private static String toStatusLabel(Integer status) {
        if (status == null) {
            return null;
        }
        return status == 0 ? "启用" : "禁用";
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierExcelVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier.vo;
import cn.idev.excel.annotation.ExcelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * SRM ä¾›åº”商档案导出 Excel VO
 *
 * ä¸Žåˆ—表展示口径一致;类型、状态列导出为可读文本(如 ç”Ÿäº§åž‹ / å¯ç”¨ï¼‰ï¼Œæ–¹ä¾¿ä¸šåŠ¡é˜…è¯»
 */
@Data
public class SrmSupplierExcelVO {
    @ExcelProperty("供应商编码")
    private String code;
    @ExcelProperty("供应商名称")
    private String name;
    @ExcelProperty("供应商简称")
    private String shortName;
    @ExcelProperty("供应商类型")
    private String type;
    @ExcelProperty("企业性质")
    private String companyNature;
    @ExcelProperty("注册资金(万元)")
    private BigDecimal registeredCapital;
    @ExcelProperty("营业执照编号")
    private String businessLicenseNo;
    @ExcelProperty("税号")
    private String taxNo;
    @ExcelProperty("法人代表")
    private String legalPerson;
    @ExcelProperty("公司地址")
    private String address;
    @ExcelProperty("公司网址")
    private String website;
    @ExcelProperty("联系人")
    private String contactName;
    @ExcelProperty("联系电话")
    private String contactMobile;
    @ExcelProperty("邮箱")
    private String contactEmail;
    @ExcelProperty("开户银行")
    private String bankName;
    @ExcelProperty("银行账号")
    private String bankAccount;
    @ExcelProperty("状态")
    private String status;
    @ExcelProperty("备注")
    private String remark;
    @ExcelProperty("创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierImportExcelVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier.vo;
import cn.idev.excel.annotation.ExcelProperty;
import lombok.Data;
import java.math.BigDecimal;
/**
 * SRM ä¾›åº”商档案导入 Excel VO
 *
 * ç¼–码、名称为必填;类型支持填中文名称(生产型/贸易型/服务型/其他)或编码(1/2/3/4);
 * çŠ¶æ€ 0=启用、1=禁用,留空默认启用
 */
@Data
public class SrmSupplierImportExcelVO {
    @ExcelProperty("供应商编码")
    private String code;
    @ExcelProperty("供应商名称")
    private String name;
    @ExcelProperty("供应商简称")
    private String shortName;
    @ExcelProperty("供应商类型(生产型/贸易型/服务型/其他)")
    private String type;
    @ExcelProperty("企业性质(国企/民营/外资/合资)")
    private String companyNature;
    @ExcelProperty("注册资金(万元)")
    private BigDecimal registeredCapital;
    @ExcelProperty("营业执照编号")
    private String businessLicenseNo;
    @ExcelProperty("税号")
    private String taxNo;
    @ExcelProperty("法人代表")
    private String legalPerson;
    @ExcelProperty("公司地址")
    private String address;
    @ExcelProperty("公司网址")
    private String website;
    @ExcelProperty("联系人")
    private String contactName;
    @ExcelProperty("联系电话")
    private String contactMobile;
    @ExcelProperty("邮箱")
    private String contactEmail;
    @ExcelProperty("开户银行")
    private String bankName;
    @ExcelProperty("银行账号")
    private String bankAccount;
    @ExcelProperty("状态(0启用/1禁用)")
    private Integer status;
    @ExcelProperty("备注")
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierImportRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier.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 = "管理后台 - SRM ä¾›åº”商导入 Response VO")
@Data
@Builder
public class SrmSupplierImportRespVO {
    @Schema(description = "新增成功的供应商名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
    private List<String> createList;
    @Schema(description = "更新成功的供应商名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
    private List<String> updateList;
    @Schema(description = "导入失败的供应商集合 key ä¸ºä¾›åº”商名称,value ä¸ºå¤±è´¥åŽŸå› ", requiredMode = Schema.RequiredMode.REQUIRED)
    private Map<String, String> failureList;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java
@@ -14,6 +14,7 @@
    ErrorCode SUPPLIER_EXISTS_REFERENCE = new ErrorCode(1_040_100_002, "供应商被业务引用,无法删除");
    ErrorCode SUPPLIER_CONTACT_NOT_EXISTS = new ErrorCode(1_040_100_003, "供应商联系人不存在");
    ErrorCode SUPPLIER_NOT_ENABLE = new ErrorCode(1_040_100_004, "供应商已停用,无法参与投标");
    ErrorCode SUPPLIER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_040_100_005, "导入的供应商数据不能为空");
    // ========== ä¾›åº”商准入 1_040_200_000 ==========
    ErrorCode APPLY_NOT_EXISTS = new ErrorCode(1_040_200_000, "准入申请不存在");
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierService.java
@@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.srm.service.supplier;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierImportExcelVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierImportRespVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
@@ -41,6 +43,15 @@
    PageResult<SrmSupplierDO> getSupplierPage(SrmSupplierPageReqVO pageReqVO);
    /**
     * æ‰¹é‡å¯¼å…¥ä¾›åº”商档案
     *
     * @param importList     å¯¼å…¥æ•°æ®
     * @param updateSupport  æ˜¯å¦æ›´æ–°å·²å­˜åœ¨çš„供应商(按编码判断),false æ—¶é‡å¤ç¼–码记为失败
     * @return å¯¼å…¥ç»“果(新增/更新/失败 æ˜Žç»†ï¼‰
     */
    SrmSupplierImportRespVO importSupplierList(List<SrmSupplierImportExcelVO> importList, Boolean updateSupport);
    /**
     * èŽ·å–ä¾›åº”å•†åˆ—è¡¨ï¼ˆä»…è¿”å›žå·²é€šè¿‡å‡†å…¥å®¡æ‰¹çš„ä¾›åº”å•†ï¼Œç”¨äºŽä¸‹æ‹‰é€‰æ‹©ï¼‰
     */
    List<SrmSupplierDO> getSupplierSimpleList();
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
@@ -1,10 +1,15 @@
package cn.iocoder.yudao.module.srm.service.supplier;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierImportExcelVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierImportRespVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.*;
@@ -17,18 +22,19 @@
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderBidMapper;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.srm.enums.SupplierTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.hutool.core.collection.CollUtil;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Service
@Validated
@@ -104,6 +110,49 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public SrmSupplierImportRespVO importSupplierList(List<SrmSupplierImportExcelVO> importList, Boolean updateSupport) {
        if (CollUtil.isEmpty(importList)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_IMPORT_LIST_IS_EMPTY);
        }
        SrmSupplierImportRespVO respVO = SrmSupplierImportRespVO.builder()
                .createList(new ArrayList<>())
                .updateList(new ArrayList<>())
                .failureList(new LinkedHashMap<>())
                .build();
        // é€è¡Œå¯¼å…¥ï¼šå•行失败记录到 failureList,不影响其它行
        for (SrmSupplierImportExcelVO importVO : importList) {
            String code = StrUtil.trim(importVO.getCode());
            String name = StrUtil.trim(importVO.getName());
            if (StrUtil.isBlank(code) || StrUtil.isBlank(name)) {
                respVO.getFailureList().put(StrUtil.blankToDefault(name, code), "供应商编码和供应商名称不能为空");
                continue;
            }
            SrmSupplierSaveReqVO saveReqVO = toSaveReqVO(importVO);
            try {
                SrmSupplierDO existByCode = supplierMapper.selectByCode(code);
                if (existByCode != null) {
                    if (!Boolean.TRUE.equals(updateSupport)) {
                        respVO.getFailureList().put(name, "供应商编码已存在");
                        continue;
                    }
                    saveReqVO.setId(existByCode.getId());
                    updateSupplier(saveReqVO);
                    respVO.getUpdateList().add(name);
                } else {
                    createSupplier(saveReqVO);
                    respVO.getCreateList().add(name);
                }
            } catch (Exception ex) {
                // å•条失败,记录并继续
                String reason = ex instanceof ServiceException ? ((ServiceException) ex).getMessage() : ex.getMessage();
                respVO.getFailureList().put(name, reason == null ? "导入失败" : reason);
            }
        }
        return respVO;
    }
    @Override
    public List<SrmSupplierDO> getSupplierSimpleList() {
        return supplierMapper.selectApprovedList();
    }
@@ -126,6 +175,64 @@
        return supplierMapper.selectBatchIds(ids);
    }
    // ========== å¯¼å…¥è¾…助方法 ==========
    /**
     * å¯¼å…¥ VO -> ä¿å­˜ VO,复用 create/update é€»è¾‘ï¼›
     * ç±»åž‹æ”¯æŒä¸­æ–‡åç§°æˆ–编码(1~4),状态留空默认启用
     */
    private SrmSupplierSaveReqVO toSaveReqVO(SrmSupplierImportExcelVO importVO) {
        SrmSupplierSaveReqVO saveReqVO = new SrmSupplierSaveReqVO();
        saveReqVO.setCode(StrUtil.trim(importVO.getCode()));
        saveReqVO.setName(StrUtil.trim(importVO.getName()));
        saveReqVO.setShortName(StrUtil.trimToNull(importVO.getShortName()));
        saveReqVO.setType(parseTypeCode(importVO.getType()));
        saveReqVO.setCompanyNature(StrUtil.trimToNull(importVO.getCompanyNature()));
        saveReqVO.setRegisteredCapital(importVO.getRegisteredCapital());
        saveReqVO.setBusinessLicenseNo(StrUtil.trimToNull(importVO.getBusinessLicenseNo()));
        saveReqVO.setTaxNo(StrUtil.trimToNull(importVO.getTaxNo()));
        saveReqVO.setLegalPerson(StrUtil.trimToNull(importVO.getLegalPerson()));
        saveReqVO.setAddress(StrUtil.trimToNull(importVO.getAddress()));
        saveReqVO.setWebsite(StrUtil.trimToNull(importVO.getWebsite()));
        saveReqVO.setContactName(StrUtil.trimToNull(importVO.getContactName()));
        saveReqVO.setContactMobile(StrUtil.trimToNull(importVO.getContactMobile()));
        saveReqVO.setContactEmail(StrUtil.trimToNull(importVO.getContactEmail()));
        saveReqVO.setBankName(StrUtil.trimToNull(importVO.getBankName()));
        saveReqVO.setBankAccount(StrUtil.trimToNull(importVO.getBankAccount()));
        saveReqVO.setStatus(importVO.getStatus() != null ? importVO.getStatus() : CommonStatusEnum.ENABLE.getStatus());
        saveReqVO.setRemark(StrUtil.trimToNull(importVO.getRemark()));
        return saveReqVO;
    }
    /**
     * ä¾›åº”商类型解析:中文名称或编码(1~4)均转换为编码字符串;
     * æ— æ³•识别时保留原值,避免导入被阻塞
     */
    private String parseTypeCode(String type) {
        String t = StrUtil.trimToNull(type);
        if (t == null) {
            return null;
        }
        for (SupplierTypeEnum e : SupplierTypeEnum.values()) {
            if (t.equalsIgnoreCase(e.getName()) || t.equals(String.valueOf(e.getCode()))) {
                return String.valueOf(e.getCode());
            }
        }
        // å…¼å®¹ Excel æ•°å­—单元格被读成 "1.0" ç­‰å¸¦å°æ•°å½¢å¼
        try {
            BigDecimal bd = new BigDecimal(t);
            if (bd.stripTrailingZeros().scale() <= 0) {
                int i = bd.intValueExact();
                if (i >= 1 && i <= 4) {
                    return String.valueOf(i);
                }
            }
        } catch (NumberFormatException ignored) {
            // å¿½ç•¥ï¼šä¿æŒåŽŸå€¼
        }
        return t;
    }
    // ========== æ ¡éªŒæ–¹æ³• ==========
    private void validateSupplierExists(Long id) {
yudao-ui/yudao-ui-admin-vue3/src/api/srm/supplier/index.ts
@@ -67,6 +67,14 @@
  exportSupplierExcel: async (params: any) =>
    request.download({ url: '/srm/supplier/export-excel', params }),
  // ä¸‹è½½å¯¼å…¥æ¨¡æ¿
  getImportTemplate: async () =>
    request.download({ url: '/srm/supplier/get-import-template' }),
  // å¯¼å…¥ Excel(formData éœ€åŒ…含 file、updateSupport)
  importSupplierExcel: async (formData: any) =>
    request.upload({ url: '/srm/supplier/import-excel', data: formData }),
  // è”系人列表
  getContactList: async (supplierId: number) =>
    request.get({ url: '/srm/supplier-contact/list-by-supplier', params: { supplierId } }),
yudao-ui/yudao-ui-admin-vue3/src/views/srm/supplier/index.vue
@@ -26,6 +26,9 @@
        <el-button type="primary" plain @click="openForm()" v-hasPermi="['srm:supplier:create']">
          <Icon icon="ep:plus" class="mr-5px" /> æ–°å¢ž
        </el-button>
        <el-button type="warning" plain @click="handleImport" v-hasPermi="['srm:supplier:import']">
          <Icon icon="ep:upload" class="mr-5px" /> å¯¼å…¥
        </el-button>
        <el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['srm:supplier:export']">
          <Icon icon="ep:download" class="mr-5px" /> å¯¼å‡º
        </el-button>
@@ -198,10 +201,43 @@
      <el-descriptions-item label="备注" :span="2">{{ detailData.remark }}</el-descriptions-item>
    </el-descriptions>
  </el-dialog>
  <!-- å¯¼å…¥å¯¹è¯æ¡† -->
  <el-dialog title="供应商档案导入" v-model="importVisible" width="440px" @closed="resetImport">
    <el-upload
      ref="importUploadRef"
      v-model:file-list="fileList"
      :auto-upload="false"
      :disabled="importLoading"
      :limit="1"
      :on-exceed="handleImportExceed"
      accept=".xlsx, .xls"
      action="none"
      drag
    >
      <Icon icon="ep:upload" />
      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
      <template #tip>
        <div class="el-upload__tip text-center">
          <div class="el-upload__tip">
            <el-checkbox v-model="updateSupport" />
            æ˜¯å¦æ›´æ–°å·²ç»å­˜åœ¨çš„供应商(按供应商编码判断)
          </div>
          <span>仅允许导入 xls、xlsx æ ¼å¼æ–‡ä»¶ã€‚</span>
          <el-link :underline="false" style="font-size: 12px; vertical-align: baseline" type="primary" @click="handleDownloadTemplate">下载模板</el-link>
        </div>
      </template>
    </el-upload>
    <template #footer>
      <el-button :disabled="importLoading" type="primary" @click="handleImportSubmit">ç¡® å®š</el-button>
      <el-button @click="importVisible = false">取 æ¶ˆ</el-button>
    </template>
  </el-dialog>
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { SrmSupplierApi, SrmSupplierVO, SrmSupplierContactVO } from '@/api/srm/supplier'
defineOptions({ name: 'SrmSupplier' })
@@ -318,13 +354,88 @@
  try {
    await message.exportConfirm()
    exportLoading.value = true
    await SrmSupplierApi.exportSupplierExcel(queryParams)
    const data = await SrmSupplierApi.exportSupplierExcel(queryParams)
    download.excel(data, '供应商数据.xlsx')
  } catch {
  } finally {
    exportLoading.value = false
  }
}
// ========== å¯¼å…¥ ==========
const importVisible = ref(false)
const importLoading = ref(false)
const importUploadRef = ref()
const fileList = ref<any[]>([])
const updateSupport = ref(false)
/** æ‰“开导入弹窗 */
const handleImport = () => {
  resetImport()
  importVisible.value = true
}
/** ä¸‹è½½å¯¼å…¥æ¨¡æ¿ */
const handleDownloadTemplate = async () => {
  try {
    const data = await SrmSupplierApi.getImportTemplate()
    download.excel(data, '供应商导入模板.xlsx')
  } catch {
  }
}
/** ä¸Šä¼ æ–‡ä»¶æ•°é‡è¶…限 */
const handleImportExceed = () => {
  message.error('最多只能上传一个文件!')
}
/** é‡ç½®å¯¼å…¥çŠ¶æ€ */
const resetImport = () => {
  importLoading.value = false
  fileList.value = []
  updateSupport.value = false
  importUploadRef.value?.clearFiles()
}
/** æäº¤å¯¼å…¥ */
const handleImportSubmit = async () => {
  if (fileList.value.length === 0) {
    message.error('请上传文件')
    return
  }
  importLoading.value = true
  try {
    const formData = new FormData()
    formData.append('updateSupport', updateSupport.value ? 'true' : 'false')
    formData.append('file', fileList.value[0].raw as Blob)
    const res = await SrmSupplierApi.importSupplierExcel(formData)
    if (res.code !== 0) {
      message.error(res.msg)
      return
    }
    // æ±‡æ€»å¯¼å…¥ç»“果:新增/更新/失败
    const data = res.data || {}
    const createList = data.createList || []
    const updateList = data.updateList || []
    const failureList = data.failureList || {}
    let text = '成功新增数量:' + createList.length + ';'
    text += '成功更新数量:' + updateList.length + ';'
    text += '失败数量:' + Object.keys(failureList).length + '。'
    const failureNames = Object.keys(failureList)
    for (const name of failureNames) {
      text += '【' + name + ':' + failureList[name] + '】'
    }
    message.alert(text)
    importVisible.value = false
    await getList()
  } catch {
    message.error('上传失败,请您重新上传!')
  } finally {
    importLoading.value = false
    resetImport()
  }
}
onMounted(() => {
  getList()
})