|
@@ -0,0 +1,327 @@
|
|
|
+package org.dromara.system.controller;
|
|
|
+
|
|
|
+import jakarta.servlet.http.HttpServletRequest;
|
|
|
+import jakarta.servlet.http.HttpServletResponse;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
|
|
+import org.apache.poi.ss.usermodel.*;
|
|
|
+import org.apache.poi.ss.util.CellRangeAddress;
|
|
|
+import org.apache.poi.xssf.usermodel.*;
|
|
|
+import org.dromara.system.domain.vo.EnrollProjectVo;
|
|
|
+
|
|
|
+import java.io.*;
|
|
|
+import java.util.*;
|
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+public class TestPoi {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public static void main(String[] args) throws IOException {
|
|
|
+ System.out.println(importData());
|
|
|
+ }
|
|
|
+
|
|
|
+ private static List<EnrollProjectVo> importData() throws IOException {
|
|
|
+ List<EnrollProjectVo> enrolls = new ArrayList<>();
|
|
|
+ File file = new File("D:\\poi_demo.xlsx");
|
|
|
+
|
|
|
+ try (FileInputStream fis = new FileInputStream(file);
|
|
|
+ Workbook workbook = new XSSFWorkbook(fis)) {
|
|
|
+
|
|
|
+ Sheet sheet = workbook.getSheetAt(0);
|
|
|
+
|
|
|
+ // 获取第一行(表头),用于获取项目名称
|
|
|
+ Row headerRow = sheet.getRow(2);
|
|
|
+ // 动态确定有效最大列数(去除表头末尾的空列)
|
|
|
+ int lastCellIndex = findValidLastColumnIndex(headerRow);
|
|
|
+ List<String> projectNames = new ArrayList<>();
|
|
|
+
|
|
|
+ // 从第6列(F列,索引5)开始收集项目名称
|
|
|
+ for (int i = 6; i < lastCellIndex; i++) {
|
|
|
+ Cell cell = headerRow.getCell(i);
|
|
|
+ if (cell != null && cell.getStringCellValue() != null && !cell.getStringCellValue().trim().isEmpty()) {
|
|
|
+ projectNames.add(cell.getStringCellValue().trim());
|
|
|
+ } else {
|
|
|
+ projectNames.add("项目_" + i); // 防止空标题
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从第3行开始读数据
|
|
|
+ for (int i = 3; i <= sheet.getLastRowNum(); i++) {
|
|
|
+ Row row = sheet.getRow(i);
|
|
|
+ if (row == null) continue;
|
|
|
+ if (isRowEmpty(row, 0, lastCellIndex)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ EnrollProjectVo enroll = new EnrollProjectVo();
|
|
|
+ Map<String, Boolean> selections = new LinkedHashMap<>(); // 保持顺序
|
|
|
+
|
|
|
+ // A列:姓名
|
|
|
+ Cell nameCell = row.getCell(0);
|
|
|
+ if (nameCell != null) {
|
|
|
+ enroll.setName(getCellValueAsString(nameCell));
|
|
|
+ }
|
|
|
+
|
|
|
+ // B列:性别
|
|
|
+ Cell sexCell = row.getCell(1);
|
|
|
+ if (sexCell != null) {
|
|
|
+ enroll.setSex(getCellValueAsString(sexCell));
|
|
|
+ }
|
|
|
+
|
|
|
+ // C列:年龄
|
|
|
+ Cell ageCell = row.getCell(2);
|
|
|
+ if (ageCell != null) {
|
|
|
+ enroll.setAge(getCellValueAsString(ageCell));
|
|
|
+ }
|
|
|
+
|
|
|
+ // D列:队伍名称
|
|
|
+ Cell teamCell = row.getCell(3);
|
|
|
+ if (teamCell != null) {
|
|
|
+ enroll.setTeamName(getCellValueAsString(teamCell));
|
|
|
+ }
|
|
|
+
|
|
|
+ // E列:领队
|
|
|
+ Cell leaderCell = row.getCell(4);
|
|
|
+ if (leaderCell != null) {
|
|
|
+ enroll.setLeader(getCellValueAsString(leaderCell));
|
|
|
+ }
|
|
|
+
|
|
|
+ // F列:联系方式
|
|
|
+ Cell phoneCell = row.getCell(5);
|
|
|
+ if (phoneCell != null) {
|
|
|
+ enroll.setPhone(getCellValueAsString(phoneCell));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从第6列开始读取项目名称
|
|
|
+ for (int j = 6; j < lastCellIndex; j++) {
|
|
|
+ Cell cell = row.getCell(j);
|
|
|
+ String projectName = projectNames.get(j - 6); // 对应项目名
|
|
|
+ boolean selected = isCellSelected(cell);
|
|
|
+ selections.put(projectName, selected);
|
|
|
+ }
|
|
|
+
|
|
|
+ enroll.setProjectSelections(selections);
|
|
|
+ enrolls.add(enroll);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return enrolls;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 辅助方法:将 Cell 转为字符串
|
|
|
+ private static String getCellValueAsString(Cell cell) {
|
|
|
+ if (cell == null) return "";
|
|
|
+
|
|
|
+ switch (cell.getCellType()) {
|
|
|
+ case Cell.CELL_TYPE_STRING:
|
|
|
+ return cell.getStringCellValue().trim();
|
|
|
+ case Cell.CELL_TYPE_NUMERIC:
|
|
|
+ if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
|
|
|
+ return cell.getDateCellValue().toString();
|
|
|
+ } else {
|
|
|
+ return String.valueOf((int) cell.getNumericCellValue()); // 或者保留小数用 double
|
|
|
+ }
|
|
|
+ case Cell.CELL_TYPE_BOOLEAN:
|
|
|
+ return String.valueOf(cell.getBooleanCellValue());
|
|
|
+ default:
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断单元格是否表示“已选择”
|
|
|
+ * 支持:是、yes、true、1、✔、✅、√ 等
|
|
|
+ */
|
|
|
+ private static boolean isCellSelected(Cell cell) {
|
|
|
+ if (cell == null) return false;
|
|
|
+
|
|
|
+ // 先检查单元格类型
|
|
|
+ if (cell.getCellType() == Cell.CELL_TYPE_BOOLEAN) {
|
|
|
+ return cell.getBooleanCellValue();
|
|
|
+ }
|
|
|
+ if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
|
|
|
+ return cell.getNumericCellValue() == 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ String value = getCellValueAsString(cell).trim().toLowerCase();
|
|
|
+ return !value.isEmpty() &&
|
|
|
+ (value.equals("是") || value.equals("yes") || value.equals("true") ||
|
|
|
+ value.equals("1") || value.contains("✔") || value.contains("✅") ||
|
|
|
+ value.contains("√") || value.equals("×"));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断某行在指定范围内是否为空(无有效数据)
|
|
|
+ *
|
|
|
+ * @param row 行对象
|
|
|
+ * @param startCol 起始列索引
|
|
|
+ * @param endCol 结束列索引(不包含)
|
|
|
+ * @return true 表示该行为空
|
|
|
+ */
|
|
|
+ private static boolean isRowEmpty(Row row, int startCol, int endCol) {
|
|
|
+ if (row == null) return true;
|
|
|
+
|
|
|
+ for (int i = startCol; i < endCol; i++) {
|
|
|
+ Cell cell = row.getCell(i);
|
|
|
+ if (cell != null) {
|
|
|
+ // 如果单元格不为null,检查其是否有非空值
|
|
|
+ String value = getCellValueAsString(cell);
|
|
|
+ if (value != null && !value.trim().isEmpty()) {
|
|
|
+ return false; // 有有效数据
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true; // 所有列都为空
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 找到行中最后一个“有效”单元格的索引(从右往左找第一个非空单元格)
|
|
|
+ *
|
|
|
+ * @param row 行对象
|
|
|
+ * @return 有效最大列索引(不包含),即 getLastCellNum() 的合理值
|
|
|
+ */
|
|
|
+ private static int findValidLastColumnIndex(Row row) {
|
|
|
+ if (row == null) return 0;
|
|
|
+
|
|
|
+ int lastCellNum = row.getLastCellNum(); // 物理最后一列
|
|
|
+ if (lastCellNum <= 0) return 0;
|
|
|
+
|
|
|
+ // 从右往左扫描,找到第一个非空单元格
|
|
|
+ for (int i = lastCellNum - 1; i >= 0; i--) {
|
|
|
+ Cell cell = row.getCell(i);
|
|
|
+ String value = getCellValueAsString(cell);
|
|
|
+ if (value != null && !value.trim().isEmpty()) {
|
|
|
+ return i + 1; // 返回有效列数(索引+1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0; // 全为空
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void export() {
|
|
|
+ //1.加载Excel模板文件
|
|
|
+ // String template = "template/enroll_template.xlsx";
|
|
|
+ String template = "D:\\enroll_template.xlsx";
|
|
|
+ System.out.println(template);
|
|
|
+ XSSFSheet sheet = null;
|
|
|
+ try (
|
|
|
+ // InputStream inputStream = TestPoi.class.getClassLoader().getResourceAsStream(template);
|
|
|
+ InputStream inputStream = new FileInputStream(template);
|
|
|
+ XSSFWorkbook xwb = new XSSFWorkbook(inputStream)) {
|
|
|
+ sheet = xwb.getSheetAt(0);
|
|
|
+ assert sheet != null;
|
|
|
+ //7列3行开始横着渲染 excel表对应6行2列
|
|
|
+ // 2. 获取默认赛事动态项目(赛事项目)
|
|
|
+ // Map<String, List<String>> projectMap = gameEventProjectService.mapProjectTypeAndProject(eventId);
|
|
|
+ Map<String, List<String>> projectMap = new HashMap<>();
|
|
|
+ projectMap.put("径赛项目", List.of("4*100米接力", "4*400米接力", "跳高"));
|
|
|
+ projectMap.put("田赛项目", List.of("跨栏", "接力", "50米"));
|
|
|
+
|
|
|
+ // 3. 渲染分类
|
|
|
+ int currentColumnIndex = 6;
|
|
|
+ Row row = sheet.getRow(2);
|
|
|
+ if (row == null) {
|
|
|
+ row = sheet.createRow(2);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (Map.Entry<String, List<String>> entry : projectMap.entrySet()) {
|
|
|
+ String categoryName = entry.getKey();
|
|
|
+ List<String> projectList = entry.getValue();
|
|
|
+ int projectCount = projectList.size();
|
|
|
+ // 3.1 创建单元格并设置分类名称
|
|
|
+ Cell cell = row.createCell(currentColumnIndex);
|
|
|
+ cell.setCellValue(categoryName);
|
|
|
+
|
|
|
+ // 3.2 合并单元格:从 currentColumnIndex 开始,合并 projectCount 个单元格
|
|
|
+ int lastColumnIndex = currentColumnIndex + projectCount - 1;
|
|
|
+ CellRangeAddress region = new CellRangeAddress(2, 2, currentColumnIndex, lastColumnIndex);
|
|
|
+ sheet.addMergedRegion(region);
|
|
|
+
|
|
|
+ // 3.3 创建样式:边框 + 居中 + 加粗
|
|
|
+ CellStyle style = xwb.createCellStyle();
|
|
|
+ style.setBorderTop(BorderStyle.THIN);
|
|
|
+ style.setBorderBottom(BorderStyle.THIN);
|
|
|
+ style.setBorderLeft(BorderStyle.THIN);
|
|
|
+ style.setBorderRight(BorderStyle.THIN);
|
|
|
+ style.setTopBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
+ style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
+ style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
+ style.setRightBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
+
|
|
|
+ style.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+
|
|
|
+ // 字体加粗
|
|
|
+ Font font = xwb.createFont();
|
|
|
+ font.setBold(true);
|
|
|
+ style.setFont(font);
|
|
|
+
|
|
|
+ // 应用样式
|
|
|
+ cell.setCellStyle(style);
|
|
|
+
|
|
|
+ // 3.4 更新起始列:为下一个分类腾出位置
|
|
|
+ currentColumnIndex = lastColumnIndex + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 渲染项目(在第4行,索引为3)
|
|
|
+ currentColumnIndex = 6;
|
|
|
+ Row projectRow = sheet.getRow(3);
|
|
|
+ if (projectRow == null) {
|
|
|
+ projectRow = sheet.createRow(3); // 如果第4行不存在,创建它
|
|
|
+ }
|
|
|
+ for (Map.Entry<String, List<String>> entry : projectMap.entrySet()) {
|
|
|
+ List<String> projectList = entry.getValue();
|
|
|
+ // 4.1 遍历当前分类下的每个项目
|
|
|
+ for (String projectName : projectList) {
|
|
|
+ Cell cell = projectRow.createCell(currentColumnIndex);
|
|
|
+ cell.setCellValue(projectName);
|
|
|
+
|
|
|
+ // 3.3 创建样式:边框 + 居中 + 加粗
|
|
|
+ CellStyle style = xwb.createCellStyle();
|
|
|
+ style.setBorderTop(BorderStyle.THIN);
|
|
|
+ style.setBorderBottom(BorderStyle.THIN);
|
|
|
+ style.setBorderLeft(BorderStyle.THIN);
|
|
|
+ style.setBorderRight(BorderStyle.THIN);
|
|
|
+ style.setTopBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
+ style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
+ style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
+ style.setRightBorderColor(IndexedColors.BLACK.getIndex());
|
|
|
+
|
|
|
+ style.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+
|
|
|
+ // 字体加粗
|
|
|
+ Font font = xwb.createFont();
|
|
|
+ font.setBold(true);
|
|
|
+ style.setFont(font);
|
|
|
+
|
|
|
+ // 应用样式
|
|
|
+ cell.setCellStyle(style);
|
|
|
+
|
|
|
+ // 移动到下一列
|
|
|
+ currentColumnIndex++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //3.将Excel文件通过Response输出
|
|
|
+ // 设置Excel文件路径
|
|
|
+ File target = new File("D:\\poi_demo.xlsx");
|
|
|
+ try {
|
|
|
+ // 创建指向该路径的输出流
|
|
|
+ FileOutputStream stream = new FileOutputStream(target);
|
|
|
+ // 将数据导出到Excel表格
|
|
|
+ xwb.write(stream);
|
|
|
+ // 关闭输出流
|
|
|
+ stream.close();
|
|
|
+ } catch (FileNotFoundException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ log.error("下载Excel模板异常,异常信息为:【{}】", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|