# Excel 导入功能前后端代码实现与使用规范 > 本文档基于项目中"个人信息"和"企业信息"模块的导入功能实现进行总结,后续开发新模块的导入功能时,按照本文档的步骤和代码模板照写即可。 --- ## 一、整体架构概览 导入功能的整体流程如下: ``` 前端上传 Excel → 后端 Controller 接收 → AutoPoi 解析 Excel → Service 逐行处理 → 字典反向翻译 → 唯一键去重 → 必填校验 → 批量保存 → 返回校验结果 ``` 涉及的文件分层: | 层级 | 文件 | 职责 | |------|------|------| | 前端 - 公共组件 | `src/components/CustomImportModal/src/CustomImportModal.vue` | 通用的导入弹窗组件 | | 前端 - API 层 | `src/views/{module}/{Entity}Info.api.ts` | 定义导入/模板下载接口URL | | 前端 - 列表页 | `src/views/{module}/{Entity}InfoList.vue` | 集成 CustomImportModal 组件 | | 后端 - DTO | `personal/dto/ImportResultItem.java` | 导入校验结果的数据结构(通用) | | 后端 - Controller | `{module}/controller/{Entity}InfoController.java` | 接收文件上传、提供模板下载 | | 后端 - Service接口 | `{module}/service/I{Entity}InfoService.java` | 声明 importExcelData 方法 | | 后端 - Service实现 | `{module}/service/impl/{Entity}InfoServiceImpl.java` | 核心导入逻辑 | --- ## 二、后端实现详解 ### 2.1 ImportResultItem — 导入校验结果通用 DTO **文件位置**: `personal/dto/ImportResultItem.java` ```java package org.jeecg.modules.zjrs.personal.dto; import lombok.Data; import java.util.Map; /** * 导入结果行数据:包含原始Excel列值 + 错误信息(如有) */ @Data public class ImportResultItem { /** Excel行号 */ private int rowNum; /** 该行各列的原始文本值:Excel列名 → 值 */ private Map rowData; /** 错误信息:null或空表示该行校验通过 */ private String errorMsg; } ``` > 此 DTO 是通用结构,所有模块共用。`rowData` 存放该行所有 Excel 列的原始值(key 为 Excel 列名 @Excel.name),`errorMsg` 为 null 表示该行校验通过。 --- ### 2.2 Service 接口 — 声明导入方法 **文件位置**: `{module}/service/I{Entity}InfoService.java` 在接口中新增 `importExcelData` 方法声明: ```java /** * 批量导入Excel数据 * 对解析后的数据进行:字典文本→字典码反向翻译、唯一键去重、必填字段校验、批量保存 * * @param list Excel解析后的数据列表 * @return 每行的导入结果(含原始列值 + 错误信息),当全部无错误时内部会执行批量保存 */ List importExcelData(List<{Entity}Info> list); ``` 示例参考: - `IPersonalInfoService.java` ([service/IPersonalInfoService.java](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/personal/service/IPersonalInfoService.java#L81)) - `IEnterpriseInfoService.java` ([service/IEnterpriseInfoService.java](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/enterprise/service/IEnterpriseInfoService.java#L33)) --- ### 2.3 Service 实现 — 导入核心逻辑(最重要) **文件位置**: `{module}/service/impl/{Entity}InfoServiceImpl.java` 核心方法 `importExcelData` 执行以下步骤: ``` 步骤0:定义常量 → 步骤1:字典文本→字典码反向翻译映射 → 步骤2:唯一键去重(Excel内部+数据库) → 步骤3:逐行校验(字典翻译 + 必填校验 + 补充字段)→ 步骤4:有错误则返回错误明细,无错误则批量保存 ``` #### 2.3.1 必须定义的三个常量 在 ServiceImpl 类中定义三个全局常量: ```java // 常量1:导入时需要进行 文本→字典码 反向翻译的字段:entity字段名 → 字典编码 // 只有字典字段(前端是下拉选择而非自由输入的字段)才需要配置此处 private static final Map IMPORT_DICT_FIELD_MAP = new LinkedHashMap<>(); static { IMPORT_DICT_FIELD_MAP.put("gender", "Gender"); // entity字段名 → 字典编码 IMPORT_DICT_FIELD_MAP.put("education", "Education"); // ... 根据实际业务字段添加 } // 常量2:导入模板的字段名列表,顺序必须与 Controller 中 importTemplate 的 fieldNames 一致 private static final List IMPORT_FIELD_NAMES = Arrays.asList( "idType", "idNumber", "fullName", "gender", "birthDate" // ... 所有模板列字段(包括必填和非必填),顺序与模板导出时的列顺序严格一致 ); // 常量3:日期格式化器 private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); ``` > **注意**:`IMPORT_FIELD_NAMES` 的顺序必须与 Controller 中 `importTemplate` 方法里的 `fieldNames` 数组顺序完全一致,否则错误反馈时列名和列值会错位。 #### 2.3.2 核心方法完整代码模板 ```java @Override public List importExcelData(List<{Entity}Info> list) { // ========== 步骤1:批量查询所有字典项,构建 文本→字典码 的反向映射表 ========== Map> dictTextToCodeMap = new HashMap<>(); for (Map.Entry entry : IMPORT_DICT_FIELD_MAP.entrySet()) { String dictCode = entry.getValue(); if (!dictTextToCodeMap.containsKey(dictCode)) { // 从字典表查询,构建 中文文本→字典码 映射 List items = dictionaryItemMapper.queryDictItemsByCode(dictCode); Map textToCode = new HashMap<>(); for (DictModel item : items) { textToCode.put(item.getText(), item.getValue()); } dictTextToCodeMap.put(dictCode, textToCode); } } // ========== 步骤2:唯一键去重(Excel内部 + 数据库已存在)========== // 以"统一社会信用代码"或"证件号码"作为唯一键,检查Excel中是否有重复、数据库中是否已存在 Map> uniqueKeyPositions = new HashMap<>(); for (int i = 0; i < list.size(); i++) { String uniqueKey = list.get(i).getUniqueKeyField(); // 替换为你的唯一键getter if (oConvertUtils.isNotEmpty(uniqueKey)) { uniqueKeyPositions.computeIfAbsent(uniqueKey, k -> new ArrayList<>()).add(i); } } // 检查Excel内部重复 Map uniqueKeyDuplicateMsg = new HashMap<>(); for (Map.Entry> entry : uniqueKeyPositions.entrySet()) { if (entry.getValue().size() > 1) { String msg = "唯一键【" + entry.getKey() + "】在数据中多次出现(行:" + entry.getValue().stream().map(pos -> String.valueOf(pos + 3)) .collect(Collectors.joining("、")) + ")"; for (Integer pos : entry.getValue()) { uniqueKeyDuplicateMsg.put(String.valueOf(pos), msg); } } } // 检查数据库是否已存在 for (Map.Entry> entry : uniqueKeyPositions.entrySet()) { String uniqueKey = entry.getKey(); // 如果Excel内已经重复,不再重复报数据库重复 if (uniqueKeyDuplicateMsg.containsKey(String.valueOf(entry.getValue().get(0)))) { continue; } LambdaQueryWrapper<{Entity}Info> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq({Entity}Info::getUniqueKeyField, uniqueKey); // 替换 long dbCount = this.count(queryWrapper); if (dbCount > 0) { for (Integer pos : entry.getValue()) { uniqueKeyDuplicateMsg.put(String.valueOf(pos), "唯一键【" + uniqueKey + "】已在系统中存在"); } } } // ========== 步骤3:逐行校验与字段处理 ========== List resultItems = new ArrayList<>(); boolean hasAnyError = false; for (int i = 0; i < list.size(); i++) { {Entity}Info item = list.get(i); int rowNum = i + 3; // Excel行号:标题1行 + 表头1行 = 前2行为头,数据从第3行开始 // 翻译前捕获原始Excel列值,用于错误反馈时展示原数据 Map rowData = captureOriginalValues(item); List rowErrors = new ArrayList<>(); // 唯一键重复校验 String dupMsg = uniqueKeyDuplicateMsg.get(String.valueOf(i)); if (dupMsg != null) { rowErrors.add(dupMsg); } try { // 3.1 字典文本→字典码 反向翻译 for (Map.Entry fieldEntry : IMPORT_DICT_FIELD_MAP.entrySet()) { String fieldName = fieldEntry.getKey(); String dictCode = fieldEntry.getValue(); String textValue = (String) getPropertyValue(item, fieldName); if (oConvertUtils.isNotEmpty(textValue)) { Map textToCode = dictTextToCodeMap.get(dictCode); String codeValue = textToCode.get(textValue.trim()); if (codeValue != null) { setPropertyValue(item, fieldName, codeValue); } else { // 字典中找不到对应值 → 校验失败 rowErrors.add("【" + getFieldExcelName(fieldName) + "】值\"" + textValue + "\"在字典\"" + dictCode + "\"中不存在"); } } } // 3.2 校验必填字段(利用已有的 validateRequiredFields 方法) if (rowErrors.isEmpty()) { try { validateRequiredFields(item); } catch (JeecgBootBizTipException e) { rowErrors.add(e.getMessage()); } } // 3.3 补充成对字段、设置默认值(根据业务需要) if (rowErrors.isEmpty()) { // 例如:设置数据来源为"导入" item.setDataSource("2"); // 其他业务字段补充... } } catch (Exception e) { rowErrors.add(e.getMessage()); } // 构建该行的校验结果 ImportResultItem resultItem = new ImportResultItem(); resultItem.setRowNum(rowNum); resultItem.setRowData(rowData); // 原始Excel列值 if (!rowErrors.isEmpty()) { resultItem.setErrorMsg(String.join(";", rowErrors)); hasAnyError = true; resultItems.add(resultItem); } // 没错误的不加入 resultItems(减少返回数据量) } // ========== 步骤4:有错误则不保存,全部通过则批量保存 ========== if (hasAnyError) { return resultItems; } long start = System.currentTimeMillis(); this.saveBatch(list); log.info("导入消耗时间" + (System.currentTimeMillis() - start) + "毫秒"); return resultItems; } ``` #### 2.3.3 必须实现的三个辅助方法 ```java /** * 根据字段名获取对应的Excel列名(反射读取 @Excel 注解的 name 属性) * 用于错误提示时展示用户能看懂的列名,而非Java驼峰字段名 */ private String getFieldExcelName(String fieldName) { try { java.lang.reflect.Field field = {Entity}Info.class.getDeclaredField(fieldName); org.jeecgframework.poi.excel.annotation.Excel excel = field.getAnnotation(org.jeecgframework.poi.excel.annotation.Excel.class); if (excel != null) { return excel.name(); } } catch (NoSuchFieldException e) { // ignore } return fieldName; // 兜底:返回字段名 } /** * 捕获原始Excel列值,以Excel列名作为key * 用于校验失败时在错误详情弹窗中展示用户填写的原始数据 */ private Map captureOriginalValues({Entity}Info item) { Map rowData = new LinkedHashMap<>(); for (String fieldName : IMPORT_FIELD_NAMES) { String excelName = getFieldExcelName(fieldName); try { Object value = getPropertyValue(item, fieldName); if (value == null) { rowData.put(excelName, ""); } else if (value instanceof java.util.Date) { rowData.put(excelName, DATE_FORMAT.format(value)); // 日期格式化 } else { rowData.put(excelName, value.toString()); } } catch (Exception e) { rowData.put(excelName, ""); } } return rowData; } /** * 通过 getter 方法名反射读取属性值,兼容 @Accessors(chain = true) 的实体 * 注意:这两个方法是通用的静态工具方法,可以抽取到公共工具类中复用 */ private static Object getPropertyValue(Object bean, String propertyName) throws Exception { String getterName = "get" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); return bean.getClass().getMethod(getterName).invoke(bean); } /** * 通过 setter 方法名反射设置属性值,兼容 @Accessors(chain = true) 的实体 */ private static void setPropertyValue(Object bean, String propertyName, Object value) throws Exception { String setterName = "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); bean.getClass().getMethod(setterName, value.getClass()).invoke(bean, value); } ``` --- ### 2.4 Controller — 接收导入请求与提供模板下载 **文件位置**: `{module}/controller/{Entity}InfoController.java` #### 2.4.1 导入Excel数据接口 ```java @RequiresPermissions("{module}:{entityInfo}: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); // 标题行数(模板第0行为标题行) params.setHeadRows(1); // 表头行数(模板第1行为表头行) try { // AutoPoi 将 Excel 解析为 Entity 列表 List<{Entity}Info> list = ExcelImportUtil.importExcel( file.getInputStream(), {Entity}Info.class, params); // 调用 Service 进行校验与保存 List resultItems = {entity}InfoService.importExcelData(list); // 检查是否有行校验失败 boolean hasError = false; for (ImportResultItem item : resultItems) { if (item.getErrorMsg() != null && !item.getErrorMsg().isEmpty()) { hasError = true; break; } } if (hasError) { // 返回 code=500 + result=校验错误行列表,前端会展示错误详情弹窗 return Result.error("导入数据校验失败", resultItems); } return Result.ok("文件导入成功!数据行数:" + list.size()); } catch (Exception e) { String msg = e.getMessage(); log.error(msg, e); if (msg != null && msg.contains("Duplicate entry")) { return Result.error("文件导入失败:有重复数据!"); } else { return Result.error("文件导入失败:" + e.getMessage()); } } finally { try { file.getInputStream().close(); } catch (IOException e) { e.printStackTrace(); } } } return Result.error("文件导入失败!"); } ``` > **关键点**: > - 返回 `Result.error(msg, resultItems)` 时,前端检测到 `code=500` 且 `result` 是数组,就会打开错误详情弹窗 > - `ImportParams.titleRows` 和 `headRows` 的值必须与导出的模板结构一致 #### 2.4.2 下载导入模板接口 ```java @RequiresPermissions("{module}:{entityInfo}:importExcel") @RequestMapping(value = "/importTemplate") public void importTemplate(HttpServletRequest request, HttpServletResponse response) throws Exception { String title = "{业务名}导入模板"; ExportParams exportParams = new ExportParams(title, "导入模板", ExcelType.XSSF); // 定义模板的列(必填在前,非必填在后) String[] fieldNames = {"idType", "idNumber", "fullName", /* 所有列... */}; // 构建一条示例数据 List<{Entity}Info> dataList = new ArrayList<>(List.of(buildExampleData())); // AutoPoi 生成完整 workbook(含列头、样式、示例数据) Workbook workbook = ExcelExportUtil.exportExcel( exportParams, {Entity}Info.class, dataList, fieldNames); Sheet sheet = workbook.getSheetAt(0); // AutoPoi 带 ExportParams(title,...) 时:第0行为标题行,第1行为表头行 Row headerRow = sheet.getRow(1); // 定义必填字段集合(与 Service 中 validateRequiredFields 保持一致) Set requiredFields = new HashSet<>(Arrays.asList( "idType", "idNumber", "fullName" /* 必填字段列表 */ )); // 必填字段表头加红色加粗样式 CellStyle requiredStyle = workbook.createCellStyle(); Font requiredFont = workbook.createFont(); requiredFont.setColor(IndexedColors.RED.getIndex()); requiredFont.setBold(true); requiredStyle.setFont(requiredFont); // 复制 AutoPoi 默认的边框/对齐等样式 if (headerRow != null && headerRow.getCell(0) != null) { requiredStyle.cloneStyleFrom(headerRow.getCell(0).getCellStyle()); requiredStyle.setFont(requiredFont); } for (int i = 0; i < fieldNames.length; i++) { Cell cell = headerRow.getCell(i); if (cell != null && requiredFields.contains(fieldNames[i])) { cell.setCellStyle(requiredStyle); // 必填字段表头标红 } } // 写出响应 response.setContentType( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-disposition", "attachment; filename=" + URLEncoder.encode(title + ".xlsx", "UTF-8")); workbook.write(response.getOutputStream()); workbook.close(); } /** * 构建模板的示例数据行 * 字典字段填写中文文本(与字典表一致),导入时会自动反向翻译为字典码 */ private {Entity}Info buildExampleData() { {Entity}Info example = new {Entity}Info(); example.setFullName("张三"); // 自由文本字段 example.setGender("男"); // 字典字段 → 填写中文标签文本 example.setEducation("大学本科"); // 字典字段 → 填写中文标签文本 // ... 所有模板列的示例数据 return example; } ``` > **注意**:`fieldNames` 数组的顺序必须与 ServiceImpl 中 `IMPORT_FIELD_NAMES` 常量完全一致。必填字段集合 `requiredFields` 要与 `validateRequiredFields` 方法中的校验逻辑一致。 --- ## 三、前端实现详解 ### 3.1 CustomImportModal 公共组件 **文件位置**: `src/components/CustomImportModal/src/CustomImportModal.vue` 该组件是通用的导入弹窗,所有模块可直接复用,无需修改组件源码。 #### 组件功能 1. **文件选择**:支持 `.xls`、`.xlsx` 格式,限制 2MB 2. **模板下载**:可配置下载按钮(传入 `downloadUrl`) 3. **上传导入**:调用 `importUrl` 上传文件 4. **错误展示**:当后端返回 `code=500` + `result` 数组时,自动弹出错误详情表格,展示每行的原始 Excel 数据和错误原因 #### Props 参数 | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | `auth` | `String` | 否 | `''` | 权限标识,用于 v-auth 控制按钮显示 | | `importUrl` | `String` | **是** | — | 导入上传的接口URL | | `downloadUrl` | `String` | 否 | `''` | 模板下载的接口URL | | `templateName` | `String` | 否 | `'下载导入模板'` | 模板下载按钮文本 | | `buttonText` | `String` | 否 | `'导入'` | 触发按钮文本 | | `buttonIcon` | `String` | 否 | `'ant-design:import-outlined'` | 触发按钮图标 | | `buttonType` | `String` | 否 | `'primary'` | 触发按钮类型 | | `success` | `Function` | 否 | — | 导入成功后的回调函数 | #### Events | 事件名 | 说明 | 回调参数 | |--------|------|----------| | `success` | 导入成功后触发 | 无 | --- ### 3.2 API 层 — 定义导入接口URL **文件位置**: `src/views/{module}/{Entity}Info.api.ts` 在 API 枚举和导出函数中添加: ```typescript enum Api { // ... 其他接口 importExcel = '/enterprise/enterpriseInfo/importExcel', // 导入上传URL importTemplate = '/enterprise/enterpriseInfo/importTemplate', // 模板下载URL } /** * 导入api(传给 CustomImportModal 的 importUrl) */ export const getImportUrl = Api.importExcel; /** * 导入模板下载api(传给 CustomImportModal 的 downloadUrl) */ export const getImportTemplateUrl = Api.importTemplate; ``` --- ### 3.3 列表页 — 集成 CustomImportModal **文件位置**: `src/views/{module}/{Entity}InfoList.vue` #### 3.3.1 模板中引入组件 在表格标题区域(`#tableTitle` 插槽)中添加: ```html ``` #### 3.3.2 script 中导入 ```typescript import { CustomImportModal } from '/@/components/CustomImportModal'; import { getImportUrl, getImportTemplateUrl } from './{Entity}Info.api'; ``` 不需要在 `components` 中注册(组件已通过 `withInstall` 全局注册)。 --- ## 四、新模块接入步骤清单 按以下步骤操作,即可为新模块添加完整的导入功能: ### 后端(5步) 1. **确认 Entity 的 `@Excel` 注解**:实体类中需要用 `@Excel(name = "列名")` 标注字段,这样 AutoPoi 才能按列名解析 Excel,错误提示也能展示中文列名 2. **Service 接口添加方法**: ```java List importExcelData(List<{Entity}Info> list); ``` 3. **ServiceImpl 实现 importExcelData**: - 定义 `IMPORT_DICT_FIELD_MAP`(字典字段映射) - 定义 `IMPORT_FIELD_NAMES`(模板列顺序,与第5步模板列顺序一致) - 定义 `DATE_FORMAT` - 照抄 2.3.2 节的代码模板,替换实体类名、唯一键字段 - 照抄 2.3.3 节的三个辅助方法 4. **Controller 新增两个接口**: - `POST /importExcel` — 照抄 2.4.1 节代码模板 - `GET /importTemplate` — 照抄 2.4.2 节代码模板,编写 `buildExampleData()` 方法 5. **核对一致性**: - Controller 的 `fieldNames` 顺序 = ServiceImpl 的 `IMPORT_FIELD_NAMES` 顺序 - Controller 的 `requiredFields` 集合 = ServiceImpl 的 `validateRequiredFields` 逻辑 ### 前端(2步) 6. **API 层添加接口URL**: ```typescript export const getImportUrl = Api.importExcel; export const getImportTemplateUrl = Api.importTemplate; ``` 7. **列表页引入 CustomImportModal**: ```html ``` --- ## 五、数据流转示意图 ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ 前端 │ │ │ │ [导入按钮] → [选择Excel文件] → [开始导入] │ │ │ │ │ │ ▼ ▼ │ │ [下载模板] POST /importExcel │ │ GET /importTemplate 上传 MultipartFile │ │ │ ├──────────────────────────────────────────────────────────────────────────┤ │ 后端 │ │ │ │ Controller.importExcel() │ │ │ │ │ ▼ │ │ ExcelImportUtil.importExcel() → List(AutoPoi解析,字典字段仍是中文)│ │ │ │ │ ▼ │ │ Service.importExcelData() │ │ │ │ │ ├── 步骤1:批量查字典,构建 中文→字典码 映射 │ │ ├── 步骤2:唯一键去重(Excel内部 + 数据库存在) │ │ ├── 步骤3:逐行校验 │ │ │ ├── 字典文本→字典码 反向翻译 │ │ │ ├── 必填字段校验(调用 validateRequiredFields) │ │ │ └── 补充默认值(如 dataSource = "2") │ │ │ │ │ └── 步骤4: │ │ ├── 有错误 → return List(不保存) │ │ │ 前端展示错误详情表格 │ │ └── 无错误 → saveBatch() 批量写入数据库 │ │ │ └──────────────────────────────────────────────────────────────────────────┘ ``` --- ## 六、注意事项与常见问题 1. **日期格式**:Excel 中日期列请填写 `yyyy-MM-dd` 格式(如 `1990-01-01`),模板示例数据也使用此格式 2. **字典字段**:模板中字典列填写的是**中文标签文本**(如性别填"男"而不是字典码"1"),导入时会自动反向翻译为字典码存入数据库 3. **Excel 行号计算**:数据从第3行开始(第0行标题 + 第1行表头),所以错误提示中的行号 = `列表索引 + 3` 4. **唯一键去重**:如果业务的唯一键不止一个列,需要扩展步骤2的去重逻辑。如果业务没有唯一键约束,可以跳过步骤2 5. **字段顺序一致性**:Controller 的 `fieldNames`、ServiceImpl 的 `IMPORT_FIELD_NAMES`、Entity 的 `@Excel` 注解,三者的列顺序必须完全一致 6. **权限标识**:导入按钮和模板下载使用 `{module}:{entityInfo}:importExcel` 权限,Controller 方法也需要加 `@RequiresPermissions` 7. **大量数据导入**:超过 1000 行数据时,建议使用 `saveBatch(list, 500)` 分批次保存 --- ## 七、涉及的核心文件清单 | 文件 | 作用 | |------|------| | [CustomImportModal.vue](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecgboot-vue3/src/components/CustomImportModal/src/CustomImportModal.vue) | 前端公共导入弹窗组件 | | [ImportResultItem.java](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/personal/dto/ImportResultItem.java) | 导入校验结果通用DTO | | [PersonalInfoController.java](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/personal/controller/PersonalInfoController.java#L226-L328) | 个人信息导入Controller(参考实现) | | [EnterpriseInfoController.java](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/enterprise/controller/EnterpriseInfoController.java#L216-L360) | 企业信息导入Controller(参考实现) | | [PersonalInfoServiceImpl.java](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/personal/service/impl/PersonalInfoServiceImpl.java#L402-L648) | 个人信息导入Service实现(参考实现) | | [EnterpriseInfoServiceImpl.java](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/enterprise/service/impl/EnterpriseInfoServiceImpl.java#L151-L351) | 企业信息导入Service实现(参考实现) | | [PersonalInfo.api.ts](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecgboot-vue3/src/views/recruitment/personal/PersonalInfo.api.ts) | 个人信息API定义(参考实现) | | [EnterpriseInfo.api.ts](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecgboot-vue3/src/views/recruitment/enterprise/EnterpriseInfo.api.ts) | 企业信息API定义(参考实现) | | [PersonalInfoList.vue](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecgboot-vue3/src/views/recruitment/personal/PersonalInfoList.vue) | 个人信息列表页(组件集成示例) | | [EnterpriseInfoList.vue](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecgboot-vue3/src/views/recruitment/enterprise/EnterpriseInfoList.vue) | 企业信息列表页(组件集成示例) | | [IPersonalInfoService.java](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/personal/service/IPersonalInfoService.java) | 个人信息Service接口(接口声明示例) | | [IEnterpriseInfoService.java](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecg-boot/jeecg-boot-module/jeecg-module-zjrs/src/main/java/org/jeecg/modules/zjrs/enterprise/service/IEnterpriseInfoService.java) | 企业信息Service接口(接口声明示例) |