!91 feat(excel): 导出模板自动生成示例数据行

* feat(excel): 导出模板自动生成示例数据行
* fix(excel): 字典导入兼容中英文逗号分隔多值,修复BgPartymatter导入Map.of参数错位
* fix(import): Excel导入时注入bpmStatus和delFlag默认值,避免新增记录缺少流程状态
This commit is contained in:
wsm
2026-07-24 07:53:09 +00:00
parent 497c0a0ad0
commit 72d378c577
7 changed files with 116 additions and 17 deletions
@@ -176,6 +176,14 @@ public class JeecgController<T, S extends IService<T>> {
writeExcel(response, clazz, title, Collections.emptyList()); writeExcel(response, clazz, title, Collections.emptyList());
} }
/**
* 导出自定义数据(含表头),供子类导出模板示例行
*/
protected void exportXlsWithData(HttpServletResponse response,
Class<T> clazz, String title, List<T> data) throws IOException {
writeExcel(response, clazz, title, data);
}
private void writeExcel(HttpServletResponse response, Class<T> clazz, private void writeExcel(HttpServletResponse response, Class<T> clazz,
String title, List<T> data) throws IOException { String title, List<T> data) throws IOException {
ExcelAnnotationUtils.write(response, clazz, title, data, dictQuery()); ExcelAnnotationUtils.write(response, clazz, title, data, dictQuery());
@@ -111,6 +111,54 @@ public final class ExcelAnnotationUtils {
return columns; return columns;
} }
/**
* 根据 @ExcelColumn 注解自动构建一个填充了示例数据的实体实例,用于导出模板。
* String 字段填 "示例" + 列名,dict 字段填 "示例文本",日期填当天,数字填 1。
*/
public static <T> T buildExample(Class<T> clazz) {
try {
T obj = clazz.getDeclaredConstructor().newInstance();
for (Field field : getAllFields(clazz)) {
ExcelColumn col = field.getAnnotation(ExcelColumn.class);
if (col == null) continue;
field.setAccessible(true);
setExampleValue(field, obj, col);
}
return obj;
} catch (Exception e) {
throw new RuntimeException("【Excel工具】构建示例数据失败, class: " + clazz.getSimpleName(), e);
}
}
private static void setExampleValue(Field field, Object obj, ExcelColumn col) throws Exception {
Class<?> type = field.getType();
boolean hasDict = StringUtils.isNotEmpty(col.dictTable()) || StringUtils.isNotEmpty(col.dicCode());
if (type == String.class) {
field.set(obj, hasDict ? "示例文本" : "示例" + col.name());
} else if (type == Date.class) {
field.set(obj, new Date());
} else if (type == LocalDate.class) {
field.set(obj, LocalDate.now());
} else if (type == LocalDateTime.class) {
field.set(obj, LocalDateTime.now());
} else if (type == Integer.class || type == int.class) {
field.set(obj, 1);
} else if (type == Long.class || type == long.class) {
field.set(obj, 1L);
} else if (type == Double.class || type == double.class) {
field.set(obj, 1.0);
} else if (type == Float.class || type == float.class) {
field.set(obj, 1.0f);
} else if (type == Short.class || type == short.class) {
field.set(obj, (short) 1);
} else if (type == BigDecimal.class) {
field.set(obj, BigDecimal.ONE);
} else if (type == Boolean.class || type == boolean.class) {
field.set(obj, false);
}
}
// ==================== 导出 ==================== // ==================== 导出 ====================
/** /**
@@ -214,15 +262,16 @@ public final class ExcelAnnotationUtils {
// ==================== 内部辅助方法 ==================== // ==================== 内部辅助方法 ====================
static boolean isValidDictDisplay(String[] replace, String value) { static boolean isValidDictDisplay(String[] replace, String value) {
String normalized = normalizeComma(value);
for (String s : replace) { for (String s : replace) {
String[] arr = s.split(","); String[] arr = s.split(",");
if (arr.length >= 2 && arr[0].equals(value)) { if (arr.length >= 2 && arr[0].equals(normalized)) {
return true; return true;
} }
} }
// 逗号分隔多值:逐个验证每个部分是否在字典范围内 // 逗号分隔多值:逐个验证每个部分是否在字典范围内(兼容中英文逗号)
if (value.contains(",")) { if (normalized.contains(",")) {
String[] parts = value.split(","); String[] parts = normalized.split(",");
for (String part : parts) { for (String part : parts) {
String trimmed = part.trim(); String trimmed = part.trim();
if (trimmed.isEmpty()) continue; if (trimmed.isEmpty()) continue;
@@ -234,7 +283,10 @@ public final class ExcelAnnotationUtils {
break; break;
} }
} }
if (!found) return false; if (!found) {
log.warn("【Excel导入-字典校验】多值中'{}'不在字典范围, 输入值: {}, replace长度: {}", trimmed, value, replace.length);
return false;
}
} }
return true; return true;
} }
@@ -249,9 +301,10 @@ public final class ExcelAnnotationUtils {
String resolved = displayToCode.getOrDefault(value, value); String resolved = displayToCode.getOrDefault(value, value);
if (type == String.class) { if (type == String.class) {
// 逗号分隔多值:逐个翻译后再拼回(如 "部门A,部门B" → "id_a,id_b" // 逗号分隔多值:逐个翻译后再拼回(如 "部门A,部门B" → "id_a,id_b",兼容中英文逗号
if (resolved.equals(value) && value.contains(",")) { String normalized = normalizeComma(value);
String[] parts = value.split(","); if (resolved.equals(value) && normalized.contains(",")) {
String[] parts = normalized.split(",");
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < parts.length; i++) { for (int i = 0; i < parts.length; i++) {
if (i > 0) sb.append(","); if (i > 0) sb.append(",");
@@ -406,6 +459,14 @@ public final class ExcelAnnotationUtils {
return sb.toString(); return sb.toString();
} }
/**
* 将中文逗号归一化为英文逗号,兼容 Excel 中两种逗号混用。
*/
private static String normalizeComma(String value) {
if (value == null) return null;
return value.replace('', ',');
}
private static String[] mergeReplace(String[] original, String[] dictReplace) { private static String[] mergeReplace(String[] original, String[] dictReplace) {
if (original == null || original.length == 0) return dictReplace; if (original == null || original.length == 0) return dictReplace;
if (dictReplace == null || dictReplace.length == 0) return original; if (dictReplace == null || dictReplace.length == 0) return original;
@@ -18,6 +18,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.api.vo.Result; import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.excel.ExcelAnnotationUtils;
import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.util.oConvertUtils;
import org.jeecg.common.system.vo.SelectTreeModel; import org.jeecg.common.system.vo.SelectTreeModel;
import org.jeecg.modules.bg.constant.BpmStatus; import org.jeecg.modules.bg.constant.BpmStatus;
@@ -456,7 +457,9 @@ public class BgXiSpeakController extends JeecgController<BgXiSpeak, IBgXiSpeakSe
@RequiresPermissions("bg.xispeak:bg_xi_speak:exportXls") @RequiresPermissions("bg.xispeak:bg_xi_speak:exportXls")
@GetMapping(value = "/exportXlsHeaders") @GetMapping(value = "/exportXlsHeaders")
public void exportXlsHeaders(HttpServletResponse response) throws IOException { public void exportXlsHeaders(HttpServletResponse response) throws IOException {
super.exportXlsHeadersByEasyExcel(response, BgXiSpeak.class, "习总书记重要讲话指示批示情况"); BgXiSpeak example = ExcelAnnotationUtils.buildExample(BgXiSpeak.class);
super.exportXlsWithData(response, BgXiSpeak.class, "习总书记重要讲话指示批示情况",
java.util.Collections.singletonList(example));
} }
/** /**
@@ -10,6 +10,9 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.system.base.controller.JeecgController; import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.api.vo.Result; import org.jeecg.common.api.vo.Result;
import org.jeecg.common.util.excel.ExcelAnnotationUtils;
import org.jeecg.modules.bg.constant.BpmStatus;
import org.jeecg.modules.bg.constant.SupervisionConstant;
import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak; import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak;
import org.jeecg.modules.demo.dqinspecttask.entity.DqInspectTask; import org.jeecg.modules.demo.dqinspecttask.entity.DqInspectTask;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -466,10 +469,11 @@ public class BgPartymatterController extends JeecgController<BgPartymatter, IBgP
return super.importExcelByEasyExcel(request, BgPartymatter.class, return super.importExcelByEasyExcel(request, BgPartymatter.class,
Map.of( Map.of(
BgPartymatterConstant.ClassFieldName.MEETING_TYPE_NAME, meetingType, BgPartymatterConstant.ClassFieldName.MEETING_TYPE_NAME, meetingType,
BgPartymatterConstant.ClassFieldName.DEL_FLAG_NAME, BgPartymatterConstant.ClassFieldName.DEL_FLAG_NAME, BgPartymatterConstant.DelFlag.NORMAL,
BgPartymatterConstant.DelFlag.NORMAL)); SupervisionConstant.ClassFieldName.BPM_STATUS_NAME, BpmStatus.NOT_START.getCode()));
} }
/** /**
* 通过EasyExcel导出数据,根据 meetingType 动态切换标题 * 通过EasyExcel导出数据,根据 meetingType 动态切换标题
*/ */
@@ -482,14 +486,15 @@ public class BgPartymatterController extends JeecgController<BgPartymatter, IBgP
} }
/** /**
* 导出Excel表头(EasyExcel),通过 meetingType 参数指定模板类型 * 导出Excel模板(含表头和一行示例数据),通过 meetingType 参数指定模板类型
*/ */
@RequiresPermissions("bgpartymatter:bg_partymatter:exportXls") @RequiresPermissions("bgpartymatter:bg_partymatter:exportXls")
@GetMapping(value = "/exportXlsHeaders") @GetMapping(value = "/exportXlsHeaders")
public void exportXlsHeaders(HttpServletResponse response, public void exportXlsHeaders(HttpServletResponse response,
@RequestParam(defaultValue = "party") String meetingType) throws IOException { @RequestParam(defaultValue = "party") String meetingType) throws IOException {
String title = BgPartymatterConstant.MeetingType.OFFICE.equals(meetingType) ? "所务会" : "党委会"; String title = BgPartymatterConstant.MeetingType.OFFICE.equals(meetingType) ? "所务会" : "党委会";
super.exportXlsHeadersByEasyExcel(response, BgPartymatter.class, title); BgPartymatter example = ExcelAnnotationUtils.buildExample(BgPartymatter.class);
super.exportXlsWithData(response, BgPartymatter.class, title, java.util.Collections.singletonList(example));
} }
@@ -13,6 +13,9 @@ import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import org.flowable.engine.RuntimeService; import org.flowable.engine.RuntimeService;
import org.jeecg.modules.bg.constant.BpmStatus;
import org.jeecg.modules.bg.constant.SupervisionConstant;
import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak;
import org.jeecgframework.poi.excel.ExcelImportUtil; import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants; import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams; import org.jeecgframework.poi.excel.entity.ExportParams;
@@ -24,6 +27,7 @@ import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.base.controller.JeecgController; import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum; import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.excel.ExcelAnnotationUtils;
import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.demo.bqtakepulse.dto.BgTakepulseBpmSaveDTO; import org.jeecg.modules.demo.bqtakepulse.dto.BgTakepulseBpmSaveDTO;
import org.jeecg.modules.demo.bqtakepulse.entity.BgTakepulseFeedback; import org.jeecg.modules.demo.bqtakepulse.entity.BgTakepulseFeedback;
@@ -449,7 +453,12 @@ public class BgTakepulseController extends JeecgController<BgTakepulse, IBgTakep
@RequiresPermissions("bqtakepulse:bg_takepulse:importExcel") @RequiresPermissions("bqtakepulse:bg_takepulse:importExcel")
@PostMapping(value = "/importExcelByEasyExcel") @PostMapping(value = "/importExcelByEasyExcel")
public Result<?> importExcelByEasyExcel(HttpServletRequest request) { public Result<?> importExcelByEasyExcel(HttpServletRequest request) {
return super.importExcelByEasyExcel(request, BgTakepulse.class); return super.importExcelByEasyExcel(request, BgTakepulse.class,
Map.of(
SupervisionConstant.ClassFieldName.BPM_STATUS_NAME,
BpmStatus.NOT_START.getCode(),
SupervisionConstant.ClassFieldName.DEL_FLAG_NAME,
SupervisionConstant.DelFlag.NORMAL));
} }
/** /**
@@ -468,7 +477,9 @@ public class BgTakepulseController extends JeecgController<BgTakepulse, IBgTakep
@RequiresPermissions("bqtakepulse:bg_takepulse:exportXls") @RequiresPermissions("bqtakepulse:bg_takepulse:exportXls")
@GetMapping(value = "/exportXlsHeaders") @GetMapping(value = "/exportXlsHeaders")
public void exportXlsHeaders(HttpServletResponse response) throws IOException { public void exportXlsHeaders(HttpServletResponse response) throws IOException {
super.exportXlsHeadersByEasyExcel(response, BgTakepulse.class, "我为四所把把脉"); BgTakepulse example = ExcelAnnotationUtils.buildExample(BgTakepulse.class);
super.exportXlsWithData(response, BgTakepulse.class, "我为四所把把脉",
java.util.Collections.singletonList(example));
} }
} }
@@ -23,6 +23,7 @@ import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result; import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum; import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.excel.ExcelAnnotationUtils;
import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.util.oConvertUtils;
import org.jeecg.common.system.base.controller.JeecgController; import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.modules.demo.dqinspecttask.entity.DqInspectProgress; import org.jeecg.modules.demo.dqinspecttask.entity.DqInspectProgress;
@@ -354,7 +355,9 @@ public class DqInspectTaskController extends JeecgController<DqInspectTask, IDqI
@RequiresPermissions("dqinspecttask:dq_inspect_task:exportXls") @RequiresPermissions("dqinspecttask:dq_inspect_task:exportXls")
@GetMapping(value = "/exportXlsHeaders") @GetMapping(value = "/exportXlsHeaders")
public void exportXlsHeaders(HttpServletResponse response) throws IOException { public void exportXlsHeaders(HttpServletResponse response) throws IOException {
super.exportXlsHeadersByEasyExcel(response, DqInspectTask.class, "dq_inspect_task"); DqInspectTask example = ExcelAnnotationUtils.buildExample(DqInspectTask.class);
super.exportXlsWithData(response, DqInspectTask.class, "dq_inspect_task",
java.util.Collections.singletonList(example));
} }
} }
@@ -13,6 +13,9 @@ import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.util.oConvertUtils;
import org.jeecg.common.system.vo.SelectTreeModel; import org.jeecg.common.system.vo.SelectTreeModel;
import org.jeecg.modules.bg.constant.BpmStatus;
import org.jeecg.modules.bg.constant.SupervisionConstant;
import org.jeecg.modules.demo.bqtakepulse.entity.BgTakepulse;
import org.jeecg.modules.dj.inspectimprove.entity.DjInspectImprove; import org.jeecg.modules.dj.inspectimprove.entity.DjInspectImprove;
import org.jeecg.modules.dj.inspectimprove.service.IDjInspectImproveService; import org.jeecg.modules.dj.inspectimprove.service.IDjInspectImproveService;
@@ -310,7 +313,12 @@ public class DjInspectImproveController extends JeecgController<DjInspectImprove
@RequiresPermissions("dj.inspectimprove:dj_inspect_improve:importExcel") @RequiresPermissions("dj.inspectimprove:dj_inspect_improve:importExcel")
@RequestMapping(value = "/importExcelByEasyExcel", method = RequestMethod.POST) @RequestMapping(value = "/importExcelByEasyExcel", method = RequestMethod.POST)
public Result<?> importExcelByEasyExcel(HttpServletRequest request) { public Result<?> importExcelByEasyExcel(HttpServletRequest request) {
return super.importExcelByEasyExcel(request, DjInspectImprove.class); return super.importExcelByEasyExcel(request, DjInspectImprove.class,
Map.of(
SupervisionConstant.ClassFieldName.BPM_STATUS_NAME,
BpmStatus.NOT_START.getCode(),
SupervisionConstant.ClassFieldName.DEL_FLAG_NAME,
SupervisionConstant.DelFlag.NORMAL));
} }
/** /**