# 自定义导入组件(CustomImportModal)使用文档 ## 一、组件概述 `CustomImportModal` 是一个封装的 Excel 导入弹窗组件,提供以下能力: 1. **文件上传**:选择 `.xls` / `.xlsx` 文件,内置前端格式和大小校验(最大 2MB) 2. **模板下载**:支持从后端下载 Excel 导入模板(含表头 + 示例数据) 3. **权限控制**:可通过 `auth` 属性配置导入按钮的权限标识 4. **导入校验**:上传后由后端进行字典反向翻译、必填校验、去重校验 5. **错误展示**:校验失败时以表格形式展示每行错误数据及具体错误原因 6. **导入成功回调**:导入成功后触发 `success` 事件,通常用于刷新列表 > 组件只会处理 `code === 201`(部分成功)、`code === 500`(有结构化校验结果 / 普通失败)、其他(成功)三种情况。业务后端需按约定格式返回数据。 --- ## 二、组件 Props | 属性 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | `auth` | `String` | 否 | `''` | 按钮权限标识,为空时按钮不进行权限控制 | | `importUrl` | `String` | **是** | - | 后端 Excel 导入接口 URL | | `downloadUrl` | `String` | 否 | `''` | 后端模板下载接口 URL,为空时不显示下载模板区域 | | `templateName` | `String` | 否 | `'下载导入模板'` | 模板下载按钮显示的文字 | | `buttonText` | `String` | 否 | `'导入'` | 触发按钮显示文字 | | `buttonIcon` | `String` | 否 | `'ant-design:import-outlined'` | 触发按钮图标 | | `buttonType` | `String` | 否 | `'primary'` | 触发按钮类型(Ant Design Button type) | | `success` | `Function` | 否 | - | 导入成功后的回调函数(也可通过 `@success` 事件监听) | --- ## 三、组件 Events | 事件名 | 参数 | 触发时机 | |--------|------|----------| | `success` | 无 | 文件导入成功且无错误;或部分成功(code=201)关闭弹窗后 | --- ## 四、后端 API 约定 ### 4.1 导入接口 `POST {importUrl}` **请求方式**:`multipart/form-data`,字段名为 `file` **响应格式**(与组件逻辑的对应关系): | 响应 `code` | 组件行为 | |-------------|----------| | `200` 或 其他非 201/500 | 提示 `message`,关闭弹窗,触发 `success` | | `201` | 部分成功,弹出警告框,若 `result.fileUrl` 存在则提供错误详情下载链接,关闭弹窗,触发 `success` | | `500` 且 `result` 为 `ImportResultItem[]` 非空数组 | 弹出"导入校验结果"表格弹窗,展示每行错误详情,**不关闭导入手弹窗**,不触发 `success` | | `500` 且 `result` 不是数组或为空 | 仅提示 `message` 错误信息 | ### 4.2 `ImportResultItem` 结构 ```java public class ImportResultItem { private Integer rowNum; // Excel 行号 private Map rowData; // 该行的列名→列值映射 private String errorMsg; // 错误信息(多个错误用分号拼接) } ``` ### 4.3 模板下载接口 `GET {downloadUrl}` 返回 Excel 文件的二进制流(`Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`)。 --- ## 五、前端使用示例 以"个人基本信息"页面的导入为例。 ### 5.1 API 定义(PersonalInfo.api.ts) ```typescript // 文件:jeecgboot-vue3/src/views/recruitment/personal/PersonalInfo.api.ts enum Api { importExcel = '/personal/personalInfo/importExcel', importTemplate = '/personal/personalInfo/importTemplate', } // 导入接口URL export const getImportUrl = Api.importExcel; // 模板下载接口URL export const getImportTemplateUrl = Api.importTemplate; ``` ### 5.2 Vue 模板中使用 ```vue ``` --- ## 六、后端实现参考 ### 6.1 Controller 层(PersonalInfoController.java) ```java // 文件:jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/.../personal/controller/PersonalInfoController.java /** * 通过excel导入数据 * 解析Excel → 调用Service进行字典反向翻译、校验、补充字段、批量保存 */ @RequiresPermissions("personal:personal_info:importExcel") @RequestMapping(value = "/importExcel", method = RequestMethod.POST) public Result importExcel(HttpServletRequest request, HttpServletResponse response) { MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; Map fileMap = multipartRequest.getFileMap(); for (Map.Entry entity : fileMap.entrySet()) { MultipartFile file = entity.getValue(); ImportParams params = new ImportParams(); params.setTitleRows(1); params.setHeadRows(1); params.setNeedSave(true); try { // 解析Excel为实体列表 List list = ExcelImportUtil.importExcel( file.getInputStream(), PersonalInfo.class, params); // 调用Service进行校验和处理 List resultItems = personalInfoService.importExcelData(list); // 检查是否有校验错误 boolean hasError = false; for (ImportResultItem item : resultItems) { if (item.getErrorMsg() != null && !item.getErrorMsg().isEmpty()) { hasError = true; break; } } if (hasError) { return Result.error("导入数据校验失败", resultItems); } return Result.ok("文件导入成功!数据行数:" + list.size()); } catch (Exception e) { String msg = e.getMessage(); if (msg != null && msg.contains("Duplicate entry")) { return Result.error("文件导入失败:有重复数据!"); } else { return Result.error("文件导入失败:" + e.getMessage()); } } } return Result.error("文件导入失败!"); } ``` ### 6.2 Service 层核心逻辑(PersonalInfoServiceImpl.importExcelData) | 步骤 | 说明 | |------|------| | 1. 字典反向翻译 | 根据 `IMPORT_DICT_FIELD_MAP`(字段→字典编码映射),批量查询字典项,构建 `文本→字典码` 的反向映射表,将 Excel 中的字典中文文本转为字典码值 | | 2. 证件号码去重 | 检查 Excel 内部是否有重复证件号 + 数据库中是否已存在相同证件号,重复则标记错误 | | 3. 逐行校验 | 捕获原始列值 → 字典文本转码 → 必填字段校验 → 补充成对字段(如户口所在地和区域名称) → 设置数据来源为"2"(导入) | | 4. 批量保存或返回错误 | 若有任何错误行则**不保存**,直接返回 `ImportResultItem` 列表;全部通过则 `saveBatch` 批量插入 | **涉及的字典字段映射(IMPORT_DICT_FIELD_MAP)**: - 证件类型 → `IDType` - 性别 → `Gender` - 民族 → `Ethnicity` - 国籍 → `Nationality` - 婚姻状况 → `MaritalStatus` - 学历 → `Education` - 政治面貌 → `PoliticalStatus` - 工作经验 → `WorkExperience` - 户口类型 → `HouseholdType` - 求职人员类别 → `JobSeekerCategory` - 求职状态 → `JobSeekerStatus` ### 6.3 模板下载接口 ```java /** * 下载导入模板 * 包含指定字段的表头 + 一条示例数据,方便用户参照填写 */ @RequiresPermissions("personal:personal_info:importExcel") @RequestMapping(value = "/importTemplate") public ModelAndView importTemplate(HttpServletRequest request, HttpServletResponse response) { ModelAndView mv = new ModelAndView(new JeecgEntityExcelView()); String title = "个人基本信息导入模板"; mv.addObject(NormalExcelConstants.FILE_NAME, title); mv.addObject(NormalExcelConstants.CLASS, PersonalInfo.class); ExportParams exportParams = new ExportParams(title, "导入模板", ExcelType.XSSF); mv.addObject(NormalExcelConstants.PARAMS, exportParams); // 指定导出字段 String exportFields = "idType,idNumber,fullName,gender,birthDate,..."; mv.addObject(NormalExcelConstants.EXPORT_FIELDS, exportFields); // 一条示例数据 mv.addObject(NormalExcelConstants.DATA_LIST, new ArrayList<>(List.of(buildExampleData()))); return mv; } ``` --- ## 七、完整数据流 ``` 用户点击"导入"按钮 │ ▼ 打开导入弹窗 → 用户选择Excel文件(前端校验格式、大小) │ ├── 可选:点击下载模板按钮 → GET {downloadUrl} → 返回Excel二进制流 → 浏览器下载 │ ▼ 用户点击"开始导入" │ ▼ POST {importUrl} (multipart/form-data, file字段) │ ▼ 后端 Controller 解析Excel → Service 处理 ├── 字典文本 → 字典码反向翻译 ├── 证件号码去重(Excel内部 + 数据库) ├── 逐行必填校验 ├── 补充关联字段 │ ▼ ┌─ 全部通过 ───────────────────────────────┐ │ saveBatch 批量保存 → Result.ok → 前端提示成功 → 关闭弹窗 → 触发 @success → 刷新列表 │ ├─ 有校验错误 ─────────────────────────────┐ │ Result.error(resultItems) → 前端弹出"导入校验结果"弹窗 → 表格展示每行错误 → 用户关闭 → 可重新选择文件再导入 │ └─ 数据库唯一键冲突 ────────────────────────┐ Result.error("有重复数据") → 前端提示错误 ``` --- ## 八、涉及文件清单 | 文件 | 角色 | |------|------| | `jeecgboot-vue3/src/components/CustomImportModal/index.ts` | 组件入口,注册全局组件 | | `jeecgboot-vue3/src/components/CustomImportModal/src/CustomImportModal.vue` | 导入弹窗核心实现(上传、校验展示、模板下载) | | `jeecgboot-vue3/src/views/recruitment/personal/PersonalInfoList.vue` | 使用示例:引入组件并配置属性 | | `jeecgboot-vue3/src/views/recruitment/personal/PersonalInfo.api.ts` | API 定义:`getImportUrl`、`getImportTemplateUrl` | | `jeecg-boot/.../personal/controller/PersonalInfoController.java` | 后端导入接口 `POST /importExcel`、模板下载 `GET /importTemplate` | | `jeecg-boot/.../personal/service/impl/PersonalInfoServiceImpl.java` | 导入核心逻辑:字典翻译、去重校验、逐行校验、批量保存 | | `jeecg-boot/.../personal/dto/ImportResultItem.java` | 导入结果行 DTO:rowNum、rowData、errorMsg |