Vue项目使用Univer Sheets

Univer Excel主页链接:安装步骤

1. 安装

使用预设模式的包管理器安装
- 预设模式:可以理解为开包即用的模式,省去很多配置,当然自由度不如插件模式

pnpm add @univerjs/presets @univerjs/preset-sheets-core

2. 前端代码

<template><div ref="container" style="height: 70vh"></div>
</template><script lang="ts" setup>import { onMounted, onBeforeUnmount, ref, watch, reactive } from 'vue';import { createUniver, FUniver, LocaleType, merge, Univer } from '@univerjs/presets';import { UniverSheetsCorePreset } from '@univerjs/preset-sheets-core';import UniverPresetSheetsCoreZhCN from '@univerjs/preset-sheets-core/lib/locales/zh-CN';import '@univerjs/preset-sheets-core/lib/index.css';const props = defineProps({excelData: { // 父组件传递的excel数据type: Object,default: () => ({}),},isEdit: { // 是否是编辑模式type: Boolean,default: false,},});const container = ref<HTMLElement | null>(null);let univerInstance: Univer | null = null;let univerAPIInstance: FUniver | null = null;onMounted(() => {const { univer, univerAPI } = createUniver({locale: LocaleType.ZH_CN,locales: {[LocaleType.ZH_CN]: merge({}, UniverPresetSheetsCoreZhCN),},presets: [UniverSheetsCorePreset({container: container.value as HTMLElement,// 设置excel上方的menu菜单menu: {// 隐藏加粗按钮// 'sheet.command.set-range-bold': {//   hidden: true,// },},}),],});// 监听attachments变化,重新加载数据watch(() => props.excelData,(newAttachments) => {if (newAttachments) {// univerAPI.createWorkbook(newAttachments);univerAPI.createWorkbook(workbook1);  // 开始可以使用数据workbook0或者workboot1来测试是否正常加载数据const permission = univerAPI.getPermission();const workbookEditablePermission = permission.permissionPointsDefinition.WorkbookEditablePermission;// unitId 是工作簿的 id, WorkbookEditablePermission 是权限点位, false 表示该权限不可使用// 权限:如果是查看则不可编辑,由props.isEdit确定是编辑或查看模式permission.setWorkbookPermissionPoint(newAttachments.id, workbookEditablePermission, props.isEdit);}});univerInstance = univer;univerAPIInstance = univerAPI;});// 添加一个方法来获取当前表格数据const getExcelData = () => {if (!univerAPIInstance) return null;// 获取当前活动的工作簿const workbook = univerAPIInstance.getActiveWorkbook();if (!workbook) return null;// 获取工作簿数据(JSON格式)const workbookData = workbook.getSnapshot();return workbookData;};// 如果此组件是在modal中展示,则用户在点击modal的确认按钮后,会调用此方法获取当前表格数据defineExpose({getExcelData,});// 最简单的可显示的数据,最重要的地方是sheelt1和里面Id的字符串必须一致const workbook0 = reactive({id: '1',sheets: {sheelt1: {id: 'sheelt1',name: '工作簿的名字1',cellData: {// 第一行0: {// 第一列0: { v: 'A1' },// 第二列1: { v: 'B1' },},// 第二行1: {// 第一列0: { v: 'A2' },// 第二列1: { v: 'B2' },},},},},});// 官网提供的数据,比较复杂,包含了很多样式//  const workbook1 = reactive({//     id: 'gyI0JO',//     sheetOrder: ['RSfWjJFv4opmE1JaiRj80'],//     name: '',//     appVersion: '0.5.0',//     locale: 'zhCN',//     styles: {},//     sheets: {//       RSfWjJFv4opmE1JaiRj80: {//         id: 'RSfWjJFv4opmE1JaiRj80',//         name: '测试',//         tabColor: '',//         hidden: 0,//         rowCount: 30,//         columnCount: 10,//         zoomRatio: 1,//         freeze: {//           startRow: -1,//           startColumn: -1,//           ySplit: 0,//           xSplit: 0,//         },//         scrollTop: 0,//         scrollLeft: 0,//         defaultColumnWidth: 73,//         defaultRowHeight: 23,//         mergeData: [],//         cellData: {//           // 第一行//           0: {//             // 第一列//             0: { v: 'A1' },//             // 第二列//             1: { v: 'B1' },//           },//           // 第二行//           1: {//             // 第一列//             0: { v: 'A2' },//             // 第二列//             1: { v: 'B2' },//           },//         },//         rowData: {},//         columnData: {//           0: {//             w: 125,//             hd: 0,//           },//           1: {//             w: 125,//             hd: 0,//           },//           2: {//             w: 125,//             hd: 0,//           },//           3: {//             w: 125,//             hd: 0,//           },//           4: {//             w: 125,//             hd: 0,//           },//           5: {//             w: 125,//             hd: 0,//           },//           6: {//             w: 125,//             hd: 0,//           },//           7: {//             w: 125,//             hd: 0,//           },//           8: {//             w: 125,//             hd: 0,//           },//           9: {//             w: 125,//             hd: 0,//           },//         },//         showGridlines: 1,//         rowHeader: {//           width: 46,//           hidden: 0,//         },//         columnHeader: {//           height: 20,//           hidden: 0,//         },//         selections: ['A1'],//         rightToLeft: 0,//       },//     },//     resources: [//       {//         name: 'SHEET_DEFINED_NAME_PLUGIN',//         data: '',//       },//     ],//   });onBeforeUnmount(() => {univerInstance?.dispose();univerAPIInstance?.dispose();univerInstance = null;univerAPIInstance = null;});
</script>

特别需要注意的地方

在这里插入图片描述

单元格样式

单元格样式链接

工具栏菜单项

工具栏菜单项链接

3. 文件的保存

官网收费项目
前端发送数据给后端,后端通过判断各种样式将数据使用poi保存成文件,在此只实现了简单的样式保存,供参考

文件实体类

class ExcelVo {private String id;private String fileName;private Map<String, Object> jsonExcel;
}

保存和查看文件的方法

import java.io.*;
import java.lang.reflect.Method;
import java.util.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.regex.Pattern;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFPalette;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.web.multipart.MultipartFile;/*** @Description: Excel文件操作服务* @Date:   2025-06-15* @Version: V1.0* */
@Slf4j
@Service
public class ExcelSavaAndReadServiceImpl {// 读取yml配置文件中的路径@Value("${jeecg.path.upload:}")private String uploadPath;// save&updata数据public void updateExcelJson(ExcelVo fileData) {if(StringUtils.isBlank(fileData.getId())) {// 此处应该使用自定义错误throw new Exception("id为空");}String fileName = fileData.getFileName();Map<String, Object> univerData = fileData.getJsonExcel();// log.info("univerData==:" + univerData);if (StringUtils.isBlank(fileName)) {throw new Exception("文件名称为空");}Path filePath = Paths.get(uploadPath, fileName);Workbook workbook = null;try {if (Files.exists(filePath)) {// 文件存在:打开并更新try (InputStream is = new FileInputStream(filePath.toFile())) {workbook = WorkbookFactory.create(is);}updateWorkbookFromUniver(workbook, univerData, false); // 更新模式} else {// 文件不存在:新建完整工作簿workbook = createWorkbook(fileName); // 根据扩展名创建空工作簿updateWorkbookFromUniver(workbook, univerData, true); // 完整创建模式}// 保存工作簿try (FileOutputStream fos = new FileOutputStream(filePath.toFile())) {workbook.write(fos);}log.info("Excel文件{}成功: {}", Files.exists(filePath) ? "更新" : "创建", fileName);} catch (Exception e) {log.error("处理Excel文件失败: {}", fileName, e);throw new RuntimeException("Excel操作失败: " + e.getMessage(), e);} finally {if (workbook != null) {try {workbook.close();} catch (IOException e) {log.error("关闭工作簿失败", e);}}}}// 读取excel数据public Map<String, Object> getUniverSheetData(String fileName) {//获取文件Path filePath = Paths.get(uploadPath + "/" + fileName);if (!Files.exists(filePath)) {throw new RuntimeException("Excel文件不存在");}try (FileInputStream fis = new FileInputStream(filePath.toFile());//从输入流 fis 中创建一个 Excel 工作簿对象 Workbook,用于后续读取和解析 Excel 文件内容。//根据文件输入流自动识别 Excel 格式(如 .xls 或 .xlsx),并创建对应的 HSSFWorkbook 或 XSSFWorkbook 实例Workbook workbook = WorkbookFactory.create(fis)) {//解析Excel文件 转成 Univer识别格式return convertToUniverFormat(workbook);} catch (Exception e) {throw new RuntimeException("Excel解析失败", e);}}private Map<String, Object> convertToUniverFormat(Workbook workbook) {Map<String, Object> univerData = new LinkedHashMap<>();univerData.put("id", "workbook-" + UUID.randomUUID());Map<String, Map<String, Object>> sheetsMap = new LinkedHashMap<>();HSSFPalette hssfPalette = null;if (workbook instanceof HSSFWorkbook) {hssfPalette = ((HSSFWorkbook) workbook).getCustomPalette();}for (int i = 0; i < workbook.getNumberOfSheets(); i++) {Sheet sheet = workbook.getSheetAt(i);String sheetId = "sheet_" + i;Map<String, Object> sheetData = new LinkedHashMap<>();sheetData.put("id", sheetId);sheetData.put("name", sheet.getSheetName());// 合并单元格处理List<Map<String, Integer>> mergeData = new ArrayList<>();for (int j = 0; j < sheet.getNumMergedRegions(); j++) {CellRangeAddress region = sheet.getMergedRegion(j);Map<String, Integer> mergeInfo = new HashMap<>();mergeInfo.put("startRow", region.getFirstRow());mergeInfo.put("startColumn", region.getFirstColumn());mergeInfo.put("endRow", region.getLastRow());mergeInfo.put("endColumn", region.getLastColumn());mergeData.add(mergeInfo);}sheetData.put("mergeData", mergeData);Map<String, Map<String, Map<String, Object>>> cellData = new HashMap<>();Map<String, Map<String, Object>> cellStyleMap = new HashMap<>();List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();// 公式共享ID生成器Map<String, String> formulaIdMap = new HashMap<>();for (int r = 0; r <= sheet.getLastRowNum(); r++) {Row row = sheet.getRow(r);if (row == null) continue;Map<String, Map<String, Object>> rowData = new HashMap<>();for (int c = 0; c < row.getLastCellNum(); c++) {Cell cell = row.getCell(c, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);Map<String, Object> cellInfo = new HashMap<>();// 处理单元格值switch (cell.getCellType()) {case STRING:cellInfo.put("v", cell.getStringCellValue());cellInfo.put("t", "text");break;case NUMERIC:if (DateUtil.isCellDateFormatted(cell)) {cellInfo.put("v", cell.getDateCellValue().getTime());cellInfo.put("t", "date");} else {cellInfo.put("v", cell.getNumericCellValue());cellInfo.put("t", "number");}break;case BOOLEAN:cellInfo.put("v", cell.getBooleanCellValue());cellInfo.put("t", "boolean");break;case FORMULA:// 添加=前缀String formula = "=" + cell.getCellFormula();cellInfo.put("f", formula);cellInfo.put("t", "formula");// 生成公式共享IDString formulaKey = r + ":" + c + ":" + formula;if (!formulaIdMap.containsKey(formulaKey)) {formulaIdMap.put(formulaKey, "fid_" + UUID.randomUUID().toString().substring(0, 8));}cellInfo.put("si", formulaIdMap.get(formulaKey));try {switch (cell.getCachedFormulaResultType()) {case NUMERIC:cellInfo.put("v", cell.getNumericCellValue());break;case STRING:cellInfo.put("v", cell.getStringCellValue());break;case BOOLEAN:cellInfo.put("v", cell.getBooleanCellValue());break;default:cellInfo.put("v", "");}} catch (Exception e) {cellInfo.put("v", "");}break;default:cellInfo.put("v", "");cellInfo.put("t", "text");}// ============== 样式重构 ==============CellStyle style = cell.getCellStyle();Map<String, Object> styleMap = new HashMap<>();Font font = workbook.getFontAt(style.getFontIndex());String fontColor = getFontColor(workbook, font);// 1. 背景色if (style.getFillPattern() != FillPatternType.NO_FILL) {String bgColorHex = getColorHex(style.getFillForegroundColor(), hssfPalette);if (bgColorHex != null) {Map<String, String> bgColorMap = new HashMap<>();bgColorMap.put("rgb", bgColorHex);styleMap.put("bg", bgColorMap);}}// 2. 字体样式 - 使用文档要求的属性名//Map<String, Object> fontMap = new HashMap<>();styleMap.put("ff", font.getFontName()); // 字体名称styleMap.put("fs", font.getFontHeightInPoints()); // 字体大小styleMap.put("bl", font.getBold() ? 1 : 0); // 加粗styleMap.put("it", font.getItalic() ? 1 : 0); // 斜体// 3. 下划线样式 - 符合Univer格式Map<String, Object> ulMap = new HashMap<>();ulMap.put("s", (font.getUnderline() != Font.U_NONE) ? 1 : 0); // 是否显示ulMap.put("c", 0); // 颜色不跟随字体ulMap.put("t", 0); // 下划线类型if (fontColor != null) {Map<String, String> ulColorMap = new HashMap<>();ulColorMap.put("rgb", fontColor);ulMap.put("cl", ulColorMap);}styleMap.put("ul", ulMap);// 4. 删除线样式 - 符合Univer格式Map<String, Object> stMap = new HashMap<>();stMap.put("s", font.getStrikeout() ? 1 : 0); // 是否显示stMap.put("c", 0); // 颜色不跟随字体stMap.put("t", 0); // 删除线类型if (fontColor != null) {Map<String, String> stColorMap = new HashMap<>();stColorMap.put("rgb", fontColor);stMap.put("cl", stColorMap);}styleMap.put("st", stMap);// 5. 上标/下标short typeOffset = font.getTypeOffset();if (typeOffset == Font.SS_SUPER) {styleMap.put("va", 3); // 上标} else if (typeOffset == Font.SS_SUB) {styleMap.put("va", 2); // 下标} else {styleMap.put("va", 1); // 正常}//styleMap.put("font", fontMap);// 6. 文字颜色if (fontColor != null) {Map<String, String> textColorMap = new HashMap<>();textColorMap.put("rgb", fontColor);styleMap.put("cl", textColorMap);}// 7. 文字旋转Map<String, Object> trMap = new HashMap<>();trMap.put("a", style.getRotation()); // 旋转角度trMap.put("v", 0); // 0=水平,1=垂直styleMap.put("tr", trMap);// 8. 对齐方式HorizontalAlignment alignment = style.getAlignment();VerticalAlignment verticalAlignment = style.getVerticalAlignment();if (alignment != null) {switch (alignment) {case LEFT:styleMap.put("ht", 1);break;case CENTER:case CENTER_SELECTION:styleMap.put("ht", 2);break;case RIGHT:styleMap.put("ht", 3);break;default:styleMap.put("ht", 1);}} else {styleMap.put("ht", 1);}if (verticalAlignment != null) {switch (verticalAlignment) {case TOP:styleMap.put("vt", 1);break;case CENTER:styleMap.put("vt", 2);break;case BOTTOM:styleMap.put("vt", 3);break;default:styleMap.put("vt", 2);}} else {styleMap.put("vt", 2);}// 9. 文本截断方式styleMap.put("tb", style.getWrapText() ? 3 : 1);// 10. 内边距(示例值)Map<String, Integer> pdMap = new HashMap<>();pdMap.put("t", 2); // 上pdMap.put("b", 2); // 下pdMap.put("l", 3); // 左pdMap.put("r", 3); // 右styleMap.put("pd", pdMap);// 11. 边框样式Map<String, Map<String, Object>> borderMap = new HashMap<>();addBorderStyle(borderMap, "t", style.getBorderTop(), style.getTopBorderColor(), hssfPalette);addBorderStyle(borderMap, "r", style.getBorderRight(), style.getRightBorderColor(), hssfPalette);addBorderStyle(borderMap, "b", style.getBorderBottom(), style.getBottomBorderColor(), hssfPalette);addBorderStyle(borderMap, "l", style.getBorderLeft(), style.getLeftBorderColor(), hssfPalette);if (!borderMap.isEmpty()) {styleMap.put("bd", borderMap);}cellInfo.put("s", styleMap);// ============== 样式结束 ==============cellStyleMap.put(r + ":" + c, styleMap);rowData.put(String.valueOf(c), cellInfo);}if (!rowData.isEmpty()) {cellData.put(String.valueOf(r), rowData);}}// 处理合并单元格样式for (CellRangeAddress region : mergedRegions) {int firstRow = region.getFirstRow();int firstCol = region.getFirstColumn();String sourceKey = firstRow + ":" + firstCol;Map<String, Object> sourceStyle = cellStyleMap.get(sourceKey);if (sourceStyle != null) {for (int r = region.getFirstRow(); r <= region.getLastRow(); r++) {for (int c = region.getFirstColumn(); c <= region.getLastColumn(); c++) {if (r == firstRow && c == firstCol) continue;String rowKey = String.valueOf(r);String colKey = String.valueOf(c);if (cellData.containsKey(rowKey)) {Map<String, Map<String, Object>> rowData = cellData.get(rowKey);if (rowData.containsKey(colKey)) {rowData.get(colKey).put("s", sourceStyle);} else {Map<String, Object> newCell = new HashMap<>();newCell.put("v", "");newCell.put("t", "text");newCell.put("s", sourceStyle);rowData.put(colKey, newCell);}} else {Map<String, Map<String, Object>> newRow = new HashMap<>();Map<String, Object> newCell = new HashMap<>();newCell.put("v", "");newCell.put("t", "text");newCell.put("s", sourceStyle);newRow.put(colKey, newCell);cellData.put(rowKey, newRow);}}}}}sheetData.put("cellData", cellData);sheetsMap.put(sheetId, sheetData);}univerData.put("sheets", sheetsMap);return univerData;}// 添加边框样式(符合Univer格式)private void addBorderStyle(Map<String, Map<String, Object>> borderMap, String position,BorderStyle borderStyle, short colorIndex, HSSFPalette hssfPalette) {if (borderStyle == BorderStyle.NONE) return;Map<String, Object> borderStyleMap = new HashMap<>();borderStyleMap.put("s", convertBorderStyle(borderStyle));String colorHex = getColorHex(colorIndex, hssfPalette);if (colorHex == null) colorHex = "#000000";Map<String, String> colorMap = new HashMap<>();colorMap.put("rgb", colorHex);borderStyleMap.put("cl", colorMap);borderMap.put(position, borderStyleMap);}// 边框样式转换方法(返回数字枚举)private int convertBorderStyle(BorderStyle borderStyle) {if (borderStyle == null) return 0;switch (borderStyle) {case NONE: return 0;       // 无边框case THIN: return 1;       // 细实线case MEDIUM: return 2;     // 中等实线case DASHED: return 3;     // 虚线case DOTTED: return 4;     // 点线case THICK: return 5;      // 粗实线case DOUBLE: return 6;     // 双实线case HAIR: return 7;       // 极细线case MEDIUM_DASHED: return 8;          // 中等虚线case DASH_DOT: return 9;               // 点划线case MEDIUM_DASH_DOT: return 10;       // 中等点划线case DASH_DOT_DOT: return 11;          // 点点划线case MEDIUM_DASH_DOT_DOT: return 12;   // 中等点点划线case SLANTED_DASH_DOT: return 13;      // 斜点划线default: return 0;}}// 获取字体颜色(支持XSSF和HSSF)private String getFontColor(Workbook workbook, Font font) {// 处理XSSF格式(.xlsx)if (workbook instanceof XSSFWorkbook) {try {Method getXSSFColorMethod = font.getClass().getMethod("getXSSFColor");XSSFColor color = (XSSFColor) getXSSFColorMethod.invoke(font);if (color != null && color.getRGB() != null) {return String.format("#%02X%02X%02X",color.getRGB()[0] & 0xFF,color.getRGB()[1] & 0xFF,color.getRGB()[2] & 0xFF);}} catch (Exception e) {return "#000000";}}// 处理HSSF格式(.xls)else if (workbook instanceof HSSFWorkbook && font instanceof HSSFFont) {HSSFFont hssfFont = (HSSFFont) font;if (hssfFont.getColor() == HSSFFont.COLOR_NORMAL) {return "#000000";}HSSFColor color = ((HSSFWorkbook) workbook).getCustomPalette().getColor(hssfFont.getColor());if (color != null) {short[] rgb = color.getTriplet();return String.format("#%02X%02X%02X", rgb[0], rgb[1], rgb[2]);}}return "#000000";}// 统一颜色处理方法private String getColorHex(Color color, HSSFPalette hssfPalette) {if (color == null) return null;if (color instanceof XSSFColor) {byte[] rgb = ((XSSFColor) color).getRGB();if (rgb != null) {return String.format("#%02X%02X%02X", rgb[0] & 0xFF, rgb[1] & 0xFF, rgb[2] & 0xFF);}}else if (color instanceof HSSFColor) {HSSFColor hssfColor = (HSSFColor) color;short[] rgb = hssfColor.getTriplet();if (rgb != null) {return String.format("#%02X%02X%02X", rgb[0], rgb[1], rgb[2]);}}return null;}// 获取颜色值的重载方法(支持索引)private String getColorHex(short colorIndex, HSSFPalette hssfPalette) {if (hssfPalette == null) return null;HSSFColor color = hssfPalette.getColor(colorIndex);return (color != null) ?String.format("#%02X%02X%02X",color.getTriplet()[0],color.getTriplet()[1],color.getTriplet()[2]) :null;}// 读取已存在的Excel文件private Workbook getExistingWorkbook(Path filePath) throws Exception {try (InputStream is = new FileInputStream(filePath.toFile())) {return WorkbookFactory.create(is);}}// 单元格样式创建方法private Workbook createWorkbook(String fileName) throws Exception {// 根据文件扩展名创建对应类型的工作簿if (fileName.toLowerCase().endsWith(".xlsx")) {return new XSSFWorkbook();} else if (fileName.toLowerCase().endsWith(".xls")) {return new HSSFWorkbook();} else {throw new IllegalArgumentException("不支持的文件类型: " + fileName);}}@SuppressWarnings("unchecked")private void updateWorkbookFromUniver(Workbook workbook, Map<String, Object> univerData, boolean isNewFile) {// 获取工作表顺序和所有工作表数据List<String> sheetOrder = (List<String>) univerData.get("sheetOrder");Map<String, Map<String, Object>> sheets = (Map<String, Map<String, Object>>) univerData.get("sheets");Map<String, Map<String, Object>> styles = (Map<String, Map<String, Object>>) univerData.get("styles");Map<String, CellStyle> styleCache = new HashMap<>();for (String sheetId : sheetOrder) {Map<String, Object> sheetData = sheets.get(sheetId);if (sheetData == null) continue;String sheetName = (String) sheetData.get("name");Sheet sheet;if (isNewFile) {sheet = workbook.createSheet(sheetName);// 设置默认行高列宽int defaultRowHeight = getInt(sheetData, "defaultRowHeight", 24);int defaultColumnWidth = getInt(sheetData, "defaultColumnWidth", 88);sheet.setDefaultRowHeightInPoints(defaultRowHeight);sheet.setDefaultColumnWidth(defaultColumnWidth);} else {sheet = workbook.getSheet(sheetName);if (sheet == null) {log.warn("工作表不存在: {}, 跳过更新", sheetName);continue;}}// ========= 关键修复1:正确处理合并单元格 =========List<Map<String, Integer>> mergeData = (List<Map<String, Integer>>) sheetData.get("mergeData");if (mergeData != null) {// 移除现有合并区域for (int i = sheet.getNumMergedRegions() - 1; i >= 0; i--) {sheet.removeMergedRegion(i);}// 添加新合并区域for (Map<String, Integer> mergeInfo : mergeData) {CellRangeAddress region = new CellRangeAddress(mergeInfo.getOrDefault("startRow", 0),mergeInfo.getOrDefault("endRow", 0),mergeInfo.getOrDefault("startColumn", 0),mergeInfo.getOrDefault("endColumn", 0));sheet.addMergedRegion(region);}}// 处理单元格数据Map<String, Map<String, Map<String, Object>>> cellData =(Map<String, Map<String, Map<String, Object>>>) sheetData.get("cellData");if (cellData != null) {for (Map.Entry<String, Map<String, Map<String, Object>>> rowEntry : cellData.entrySet()) {int rowIndex = safeParseInt(rowEntry.getKey(), -1);if (rowIndex < 0) continue;Row row = sheet.getRow(rowIndex);if (row == null) {row = sheet.createRow(rowIndex);}Map<String, Map<String, Object>> rowData = rowEntry.getValue();for (Map.Entry<String, Map<String, Object>> cellEntry : rowData.entrySet()) {int colIndex = safeParseInt(cellEntry.getKey(), -1);if (colIndex < 0) continue;Cell cell = row.getCell(colIndex);if (cell == null) {cell = row.createCell(colIndex);}Map<String, Object> cellInfo = cellEntry.getValue();updateCellFromUniver(workbook, cell, cellInfo, styles, styleCache, isNewFile);}}}}}private void updateCellFromUniver(Workbook workbook, Cell cell, Map<String, Object> cellInfo,Map<String, Map<String, Object>> styles,Map<String, CellStyle> styleCache, boolean isNewFile) {// 设置单元格值String cellType = String.valueOf(cellInfo.get("t"));Object value = cellInfo.get("v");switch (cellType) {case "1": // 文本cell.setCellValue(value != null ? value.toString() : "");break;case "2": // 数字if (value instanceof Number) {cell.setCellValue(((Number) value).doubleValue());} else if (value != null) {try {cell.setCellValue(Double.parseDouble(value.toString()));} catch (NumberFormatException e) {cell.setCellValue(value.toString());}}break;case "3": // 布尔值if (value instanceof Boolean) {cell.setCellValue((Boolean) value);} else if ("true".equalsIgnoreCase(String.valueOf(value))) {cell.setCellValue(true);} else if ("false".equalsIgnoreCase(String.valueOf(value))) {cell.setCellValue(false);}break;case "date": // 日期if (value instanceof Number) {cell.setCellValue(new Date(((Number) value).longValue()));}break;case "formula": // 公式if (cellInfo.containsKey("f")) {String formula = (String) cellInfo.get("f");if (formula.startsWith("=")) {formula = formula.substring(1);}cell.setCellFormula(formula);}break;default:// 保持原值}// ========= 关键修复2:支持内联样式和样式引用 =========Object styleObj = cellInfo.get("s");if (styleObj != null) {Map<String, Object> styleMap = null;// 情况1:直接内联样式 (如: s={bg={...}, cl={...}})if (styleObj instanceof Map) {styleMap = (Map<String, Object>) styleObj;}// 情况2:样式ID引用 (如: s="b4_FtO")else if (styleObj instanceof String) {String styleId = (String) styleObj;if (!"null".equals(styleId) && styles != null) {styleMap = styles.get(styleId);}}if (styleMap != null) {CellStyle newStyle = workbook.createCellStyle();// 克隆原有样式(更新模式)if (!isNewFile && cell.getCellStyle() != null) {newStyle.cloneStyleFrom(cell.getCellStyle());}applyFullStyle(workbook, newStyle, styleMap);cell.setCellStyle(newStyle);}}}private byte convertUnderlineType(int univerType) {switch (univerType) {case 0: return FontUnderline.SINGLE.getByteValue();case 1: return FontUnderline.DOUBLE.getByteValue();case 2: return FontUnderline.SINGLE_ACCOUNTING.getByteValue();case 3: return FontUnderline.DOUBLE_ACCOUNTING.getByteValue();default: return FontUnderline.SINGLE.getByteValue();}}// 新建文件时应用完整样式@SuppressWarnings("unchecked")private void applyFullStyle(Workbook workbook, CellStyle cellStyle, Map<String, Object> styleMap) {// 字体处理// 1. 处理字体Font font = workbook.createFont();boolean fontModified = false;// 字体属性可能在顶层,也可能在font子对象中/* Map<String, Object> fontMap = null;if (styleMap.containsKey("font")) {fontMap = (Map<String, Object>) styleMap.get("font");} else {// 检查顶层是否有字体属性if (styleMap.containsKey("ff") || styleMap.containsKey("fs") ||styleMap.containsKey("bold") || styleMap.containsKey("italic")) {fontMap = styleMap;}}*/// 字体属性可能在顶层,也可能在font子对象中Map<String, Object> fontMap = styleMap.containsKey("font") ?(Map<String, Object>) styleMap.get("font") : styleMap;// 字体名称if (fontMap.containsKey("ff") || fontMap.containsKey("name")) {String fontName = getString(fontMap, "ff", getString(fontMap, "name", "Arial"));font.setFontName(fontName);fontModified = true;}if (fontMap != null) {// 字体名称if (fontMap.containsKey("ff") || fontMap.containsKey("name")) {String fontName = getString(fontMap, "ff", getString(fontMap, "name", "Arial"));font.setFontName(fontName);fontModified = true;}// 字体大小if (fontMap.containsKey("fs") || fontMap.containsKey("size")) {Object size = fontMap.get("fs");if (size == null) size = fontMap.get("size");if (size instanceof Number) {font.setFontHeightInPoints(((Number) size).shortValue());fontModified = true;}}// 粗体 (bl 或 bold)if (fontMap.containsKey("bl") || fontMap.containsKey("bold")) {Object bold = fontMap.get("bl");if (bold == null) bold = fontMap.get("bold");if (bold instanceof Boolean) {font.setBold((Boolean) bold);fontModified = true;} else if (bold instanceof Number) {font.setBold(((Number) bold).intValue() == 1);fontModified = true;}}// 斜体 (it 或 italic)if (fontMap.containsKey("it") || fontMap.containsKey("italic")) {Object italic = fontMap.get("it");if (italic == null) italic = fontMap.get("italic");if (italic instanceof Boolean) {font.setItalic((Boolean) italic);fontModified = true;} else if (italic instanceof Number) {font.setItalic(((Number) italic).intValue() == 1);fontModified = true;}}// 下划线 (ul)if (fontMap.containsKey("ul")) {Object ulObj = fontMap.get("ul");if (ulObj instanceof Map) {Map<String, Object> ulMap = (Map<String, Object>) ulObj;if (ulMap.containsKey("s") && getInt(ulMap, "s", 0) == 1) {// 设置下划线类型int underlineType = getInt(ulMap, "t", FontUnderline.SINGLE.getByteValue());font.setUnderline((byte) underlineType);// 设置下划线颜色(如果指定)if (ulMap.containsKey("cl") && !getBoolean(ulMap, "c", true)) {Object clObj = ulMap.get("cl");if (clObj instanceof Map) {Map<String, String> clMap = (Map<String, String>) clObj;String rgb = clMap.get("rgb");if (isValidHexColor(rgb)) {setFontColor(workbook, font, rgb);}}}fontModified = true;}}}// 删除线 (st)if (fontMap.containsKey("st")) {Object stObj = fontMap.get("st");if (stObj instanceof Map) {Map<String, Object> stMap = (Map<String, Object>) stObj;if (stMap.containsKey("s") && getInt(stMap, "s", 0) == 1) {font.setStrikeout(true);// 设置删除线颜色(如果指定)if (stMap.containsKey("cl") && !getBoolean(stMap, "c", true)) {Object clObj = stMap.get("cl");if (clObj instanceof Map) {Map<String, String> clMap = (Map<String, String>) clObj;String rgb = clMap.get("rgb");if (isValidHexColor(rgb)) {// 删除线颜色通过字体颜色设置(POI限制)setFontColor(workbook, font, rgb);}}}fontModified = true;}}} else if (fontMap.containsKey("strikeThrough")) {// 兼容旧版Object strike = fontMap.get("strikeThrough");if (strike instanceof Boolean) {font.setStrikeout((Boolean) strike);fontModified = true;}}// 上划线 (ol) - POI不支持,但我们可以模拟实现if (fontMap.containsKey("ol")) {Object olObj = fontMap.get("ol");if (olObj instanceof Map) {Map<String, Object> olMap = (Map<String, Object>) olObj;if (olMap.containsKey("s") && getInt(olMap, "s", 0) == 1) {// POI不支持上划线,使用下划线并调整位置模拟font.setUnderline((byte) 8); // 使用双下划线模拟fontModified = true;}}}// 字体颜色 (顶层cl属性)if (styleMap.containsKey("cl")) {Object clObj = styleMap.get("cl");if (clObj instanceof Map) {Map<String, String> clMap = (Map<String, String>) clObj;String rgb = clMap.get("rgb");if (isValidHexColor(rgb)) {setFontColor(workbook, font, rgb);fontModified = true;}}}if (fontModified) {cellStyle.setFont(font);}// 2. 背景色 (顶层bg属性)if (styleMap.containsKey("bg")) {Object bgObj = styleMap.get("bg");if (bgObj instanceof Map) {Map<String, String> bgMap = (Map<String, String>) bgObj;String rgb = bgMap.get("rgb");if (isValidHexColor(rgb)) {setBackgroundColor(workbook, cellStyle, rgb);}}}// 3. 水平对齐 (顶层ht属性)if (styleMap.containsKey("ht")) {Object ht = styleMap.get("ht");if (ht instanceof Number) {int align = ((Number) ht).intValue();switch (align) {case 1:cellStyle.setAlignment(HorizontalAlignment.LEFT);break;case 2:cellStyle.setAlignment(HorizontalAlignment.CENTER);break;case 3:cellStyle.setAlignment(HorizontalAlignment.RIGHT);break;case 4:cellStyle.setAlignment(HorizontalAlignment.JUSTIFY);break;case 5:cellStyle.setAlignment(HorizontalAlignment.FILL);break;}}}// 4. 垂直对齐 (顶层vt属性)if (styleMap.containsKey("vt")) {Object vt = styleMap.get("vt");if (vt instanceof Number) {int verticalAlign = ((Number) vt).intValue();switch (verticalAlign) {case 1:cellStyle.setVerticalAlignment(VerticalAlignment.TOP);break;case 2:cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);break;case 3:cellStyle.setVerticalAlignment(VerticalAlignment.BOTTOM);break;case 4:cellStyle.setVerticalAlignment(VerticalAlignment.JUSTIFY);break;}}}// 5. 自动换行 (顶层wrap属性)if (styleMap.containsKey("wrap")) {Object wrap = styleMap.get("wrap");if (wrap instanceof Boolean) {cellStyle.setWrapText((Boolean) wrap);} else if (wrap instanceof Number) {cellStyle.setWrapText(((Number) wrap).intValue() == 1);}}// 6. 文字旋转 (tr)if (styleMap.containsKey("tr")) {Object trObj = styleMap.get("tr");if (trObj instanceof Map) {Map<String, Object> trMap = (Map<String, Object>) trObj;// 垂直文本if (trMap.containsKey("v") && getInt(trMap, "v", 0) == 1) {cellStyle.setRotation((short) 255); // 垂直文本}// 旋转角度else if (trMap.containsKey("a")) {int angle = getInt(trMap, "a", 0);// POI角度范围:-90° 到 90°,转换为0-180度if (angle > 90) angle = 90;if (angle < -90) angle = -90;cellStyle.setRotation((short) angle);}}}// 7. 边框处理 (顶层bd属性)if (styleMap.containsKey("bd")) {Map<String, Map<String, Object>> borderMap = (Map<String, Map<String, Object>>) styleMap.get("bd");for (Map.Entry<String, Map<String, Object>> entry : borderMap.entrySet()) {String position = entry.getKey();Map<String, Object> borderStyle = entry.getValue();Object styleObj = borderStyle.get("s");int borderStyleValue = styleObj instanceof Number ? ((Number) styleObj).intValue() : 0;String rgb = null;Object colorObj = borderStyle.get("cl");if (colorObj instanceof Map) {Map<String, String> colorMap = (Map<String, String>) colorObj;rgb = colorMap.get("rgb");}applyBorderStyle(cellStyle, position, borderStyleValue, rgb, workbook);}}// 8. 文本方向 (tb) - 1: 从左到右, 2: 从右到左if (styleMap.containsKey("tb")) {Object tb = styleMap.get("tb");if (tb instanceof Number) {int textDirection = ((Number) tb).intValue();if (textDirection == 2) {cellStyle.setRotation((short) 180); // 从右到左}}}// 9. 内边距 (pd)if (styleMap.containsKey("pd")) {Object pdObj = styleMap.get("pd");if (pdObj instanceof Map) {Map<String, Object> pdMap = (Map<String, Object>) pdObj;// POI 不支持直接设置内边距,但可以通过缩进模拟int left = getInt(pdMap, "l", 0);if (left > 0) {cellStyle.setIndention((short) (left / 3)); // 近似转换}}}}}// 新增辅助方法private boolean getBoolean(Map<String, Object> map, String key, boolean defaultValue) {Object value = map.get(key);if (value instanceof Boolean) {return (Boolean) value;} else if (value instanceof Number) {return ((Number) value).intValue() == 1;} else if (value != null) {return "true".equalsIgnoreCase(value.toString());}return defaultValue;}private String getString(Map<String, Object> map, String key, String defaultValue) {Object value = map.get(key);return value != null ? value.toString() : defaultValue;}// 只更新特定的样式属性@SuppressWarnings("unchecked")private void applyPartialStyle(Workbook workbook, CellStyle newStyle, Map<String, Object> styleMap) {// 字体处理(只更新提供的属性)if (styleMap.containsKey("font")) {Font font = workbook.createFont();Map<String, Object> fontMap = (Map<String, Object>) styleMap.get("font");// 保留原字体Font originalFont = workbook.getFontAt(newStyle.getFontIndex());if (originalFont != null) {font.setFontName(originalFont.getFontName());font.setFontHeightInPoints(originalFont.getFontHeightInPoints());font.setBold(originalFont.getBold());font.setItalic(originalFont.getItalic());font.setUnderline(originalFont.getUnderline());font.setColor(originalFont.getColor());}// 更新Univer提供的属性if (fontMap.containsKey("name")) {font.setFontName(fontMap.get("name").toString());}if (fontMap.containsKey("size")) {Object size = fontMap.get("size");if (size instanceof Number) {font.setFontHeightInPoints(((Number) size).shortValue());}}if (fontMap.containsKey("bold")) {Object bold = fontMap.get("bold");if (bold instanceof Boolean) {font.setBold((Boolean) bold);}}if (fontMap.containsKey("italic")) {Object italic = fontMap.get("italic");if (italic instanceof Boolean) {font.setItalic((Boolean) italic);}}if (fontMap.containsKey("underline")) {Object underline = fontMap.get("underline");if (underline instanceof Number) {font.setUnderline(((Number) underline).byteValue());}}if (fontMap.containsKey("color")) {Object colorObj = fontMap.get("color");if (colorObj instanceof Map) {Map<String, String> colorMap = (Map<String, String>) colorObj;String rgb = colorMap.get("rgb");if (isValidHexColor(rgb)) {setFontColor(workbook, font, rgb);}}}newStyle.setFont(font);}// 背景色(只更新提供的)if (styleMap.containsKey("bg")) {Object bgObj = styleMap.get("bg");if (bgObj instanceof Map) {Map<String, String> bgMap = (Map<String, String>) bgObj;String rgb = bgMap.get("rgb");if (isValidHexColor(rgb)) {setBackgroundColor(workbook, newStyle, rgb);}}}// 对齐方式(只更新提供的)if (styleMap.containsKey("ht")) {Object ht = styleMap.get("ht");if (ht instanceof Number) {int align = ((Number) ht).intValue();switch (align) {case 1: newStyle.setAlignment(HorizontalAlignment.LEFT); break;case 2: newStyle.setAlignment(HorizontalAlignment.CENTER); break;case 3: newStyle.setAlignment(HorizontalAlignment.RIGHT); break;}}}// 垂直对齐(只更新提供的)if (styleMap.containsKey("vt")) {Object vt = styleMap.get("vt");if (vt instanceof Number) {int verticalAlign = ((Number) vt).intValue();switch (verticalAlign) {case 1: newStyle.setVerticalAlignment(VerticalAlignment.TOP); break;case 2: newStyle.setVerticalAlignment(VerticalAlignment.CENTER); break;case 3: newStyle.setVerticalAlignment(VerticalAlignment.BOTTOM); break;}}}// 自动换行(只更新提供的)if (styleMap.containsKey("wrap")) {Object wrap = styleMap.get("wrap");if (wrap instanceof Boolean) {newStyle.setWrapText((Boolean) wrap);}}}@SuppressWarnings("unchecked")private void applyUniverStyleToCell(Workbook workbook, CellStyle cellStyle, Map<String, Object> styleMap) {// 字体处理if (styleMap.containsKey("font")) {Map<String, Object> fontMap = (Map<String, Object>) styleMap.get("font");Font font = workbook.createFont();Object name = fontMap.get("name");if (name != null) font.setFontName(name.toString());Object size = fontMap.get("size");if (size instanceof Number) {font.setFontHeightInPoints(((Number) size).shortValue());} else if (size != null) {try {font.setFontHeightInPoints(Short.parseShort(size.toString()));} catch (NumberFormatException ignored) {}}Object bold = fontMap.get("bold");if (bold instanceof Boolean) {font.setBold((Boolean) bold);} else if ("true".equalsIgnoreCase(String.valueOf(bold))) {font.setBold(true);}Object italic = fontMap.get("italic");if (italic instanceof Boolean) {font.setItalic((Boolean) italic);} else if ("true".equalsIgnoreCase(String.valueOf(italic))) {font.setItalic(true);}Object underline = fontMap.get("underline");if (underline instanceof Number) {font.setUnderline(((Number) underline).byteValue());} else if (underline != null) {try {font.setUnderline(Byte.parseByte(underline.toString()));} catch (NumberFormatException ignored) {}}// 字体颜色if (fontMap.containsKey("color")) {Object colorObj = fontMap.get("color");if (colorObj instanceof Map) {Map<String, String> colorMap = (Map<String, String>) colorObj;String rgb = colorMap.get("rgb");if (rgb != null && isValidHexColor(rgb)) {setFontColor(workbook, font, rgb);}}}cellStyle.setFont(font);}// 背景颜色if (styleMap.containsKey("bg")) {Object bgObj = styleMap.get("bg");if (bgObj instanceof Map) {Map<String, String> bgMap = (Map<String, String>) bgObj;String rgb = bgMap.get("rgb");if (rgb != null && isValidHexColor(rgb)) {setBackgroundColor(workbook, cellStyle, rgb);}}}// 对齐方式Object ht = styleMap.get("ht");if (ht instanceof Number) {int align = ((Number) ht).intValue();switch (align) {case 1: cellStyle.setAlignment(HorizontalAlignment.LEFT); break;case 2: cellStyle.setAlignment(HorizontalAlignment.CENTER); break;case 3: cellStyle.setAlignment(HorizontalAlignment.RIGHT); break;default: cellStyle.setAlignment(HorizontalAlignment.LEFT);}}Object vt = styleMap.get("vt");if (vt instanceof Number) {int verticalAlign = ((Number) vt).intValue();switch (verticalAlign) {case 1: cellStyle.setVerticalAlignment(VerticalAlignment.TOP); break;case 2: cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); break;case 3: cellStyle.setVerticalAlignment(VerticalAlignment.BOTTOM); break;default: cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);}}// 自动换行Object wrap = styleMap.get("wrap");if (wrap instanceof Boolean) {cellStyle.setWrapText((Boolean) wrap);} else if ("true".equalsIgnoreCase(String.valueOf(wrap))) {cellStyle.setWrapText(true);} else if ("false".equalsIgnoreCase(String.valueOf(wrap))) {cellStyle.setWrapText(false);}// 边框处理if (styleMap.containsKey("bd")) {Map<String, Map<String, Object>> borderMap =(Map<String, Map<String, Object>>) styleMap.get("bd");for (Map.Entry<String, Map<String, Object>> entry : borderMap.entrySet()) {String position = entry.getKey();Map<String, Object> borderStyle = entry.getValue();Object styleObj = borderStyle.get("s");int borderStyleValue = styleObj instanceof Number ? ((Number) styleObj).intValue() : 0;String rgb = null;Object colorObj = borderStyle.get("cl");if (colorObj instanceof Map) {Map<String, String> colorMap = (Map<String, String>) colorObj;rgb = colorMap.get("rgb");}applyBorderStyle(cellStyle, position, borderStyleValue, rgb, workbook);}}}private void applyBorderStyle(CellStyle cellStyle, String position, int styleValue, String rgb, Workbook workbook) {BorderStyle borderStyle = convertToPoiBorderStyle(styleValue);// 处理无边框情况if (borderStyle == BorderStyle.NONE) {switch (position) {case "t": cellStyle.setBorderTop(BorderStyle.NONE); break;case "r": cellStyle.setBorderRight(BorderStyle.NONE); break;case "b": cellStyle.setBorderBottom(BorderStyle.NONE); break;case "l": cellStyle.setBorderLeft(BorderStyle.NONE); break;}return;}if (workbook instanceof XSSFWorkbook) {// XSSF (xlsx) 处理XSSFCellStyle xssfStyle = (XSSFCellStyle) cellStyle;XSSFColor color = null;if (rgb != null && isValidHexColor(rgb)) {byte[] rgbBytes = parseHexColor(rgb);color = new XSSFColor(rgbBytes, null);}switch (position) {case "t":xssfStyle.setBorderTop(borderStyle);if (color != null) xssfStyle.setTopBorderColor(color);break;case "r":xssfStyle.setBorderRight(borderStyle);if (color != null) xssfStyle.setRightBorderColor(color);break;case "b":xssfStyle.setBorderBottom(borderStyle);if (color != null) xssfStyle.setBottomBorderColor(color);break;case "l":xssfStyle.setBorderLeft(borderStyle);if (color != null) xssfStyle.setLeftBorderColor(color);break;}} else {// HSSF (xls) 处理short colorIndex = IndexedColors.BLACK.getIndex();if (rgb != null && isValidHexColor(rgb)) {colorIndex = getColorIndex(workbook, rgb);}switch (position) {case "t":cellStyle.setBorderTop(borderStyle);cellStyle.setTopBorderColor(colorIndex);break;case "r":cellStyle.setBorderRight(borderStyle);cellStyle.setRightBorderColor(colorIndex);break;case "b":cellStyle.setBorderBottom(borderStyle);cellStyle.setBottomBorderColor(colorIndex);break;case "l":cellStyle.setBorderLeft(borderStyle);cellStyle.setLeftBorderColor(colorIndex);break;}}}private BorderStyle convertToPoiBorderStyle(int styleValue) {// 根据Univer官方文档映射边框样式switch (styleValue) {case 0: return BorderStyle.NONE;       // 无边框case 1: return BorderStyle.THIN;       // 细实线case 2: return BorderStyle.MEDIUM;     // 中等实线case 3: return BorderStyle.DASHED;     // 虚线case 4: return BorderStyle.DOTTED;     // 点线case 5: return BorderStyle.THICK;      // 粗实线case 6: return BorderStyle.DOUBLE;     // 双实线case 7: return BorderStyle.HAIR;       // 极细线case 8: return BorderStyle.MEDIUM_DASHED;          // 中等虚线case 9: return BorderStyle.DASH_DOT;               // 点划线case 10: return BorderStyle.MEDIUM_DASH_DOT;       // 中等点划线case 11: return BorderStyle.DASH_DOT_DOT;          // 点点划线case 12: return BorderStyle.MEDIUM_DASH_DOT_DOT;   // 中等点点划线case 13: return BorderStyle.SLANTED_DASH_DOT;      // 斜点划线default: return BorderStyle.NONE;}}private short getColorIndex(Workbook workbook, String rgb) {if (!isValidHexColor(rgb)) {return IndexedColors.BLACK.getIndex();}try {byte[] rgbBytes = parseHexColor(rgb);int r = rgbBytes[0] & 0xFF;int g = rgbBytes[1] & 0xFF;int b = rgbBytes[2] & 0xFF;if (workbook instanceof HSSFWorkbook) {HSSFWorkbook hssfWorkbook = (HSSFWorkbook) workbook;HSSFPalette palette = hssfWorkbook.getCustomPalette();return findOrCreateHSSFColor(palette, r, g, b);} else if (workbook instanceof XSSFWorkbook) {// XSSF使用直接RGB值,返回任意值(实际使用XSSFColor)return IndexedColors.BLACK.getIndex();}} catch (Exception e) {log.error("颜色转换失败: {}", rgb, e);}return IndexedColors.BLACK.getIndex();}private void setBackgroundColor(Workbook workbook, CellStyle cellStyle, String rgb) {if (!isValidHexColor(rgb)) return;try {byte[] rgbBytes = parseHexColor(rgb);int r = rgbBytes[0] & 0xFF;int g = rgbBytes[1] & 0xFF;int b = rgbBytes[2] & 0xFF;if (workbook instanceof XSSFWorkbook) {XSSFColor color = new XSSFColor(new java.awt.Color(r, g, b), null);((XSSFCellStyle) cellStyle).setFillForegroundColor(color);cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);} else if (workbook instanceof HSSFWorkbook) {HSSFWorkbook hssfWorkbook = (HSSFWorkbook) workbook;HSSFPalette palette = hssfWorkbook.getCustomPalette();// 查找相似颜色或创建新索引short colorIndex = findOrCreateHSSFColor(palette, r, g, b);cellStyle.setFillForegroundColor(colorIndex);cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);}} catch (Exception e) {log.error("设置背景色失败: {}", rgb, e);}}// 辅助方法:处理HSSF颜色private short findOrCreateHSSFColor(HSSFPalette palette, int r, int g, int b) {// 尝试查找相似颜色HSSFColor color = palette.findSimilarColor(r, g, b);if (color != null) {return color.getIndex();}// 创建新颜色(从56开始的自定义索引)for (short i = 56; i < 64; i++) {if (palette.getColor(i) == null) {palette.setColorAtIndex(i, (byte) r, (byte) g, (byte) b);return i;}}// 没有可用槽位则使用默认return IndexedColors.GREY_25_PERCENT.getIndex();}private void setFontColor(Workbook workbook, Font font, String rgb) {if (!isValidHexColor(rgb)) return;try {byte[] rgbBytes = parseHexColor(rgb);if (workbook instanceof XSSFWorkbook && font instanceof XSSFFont) {XSSFColor color = new XSSFColor(rgbBytes, null);((XSSFFont) font).setColor(color);} else if (workbook instanceof HSSFWorkbook) {HSSFWorkbook hssfWorkbook = (HSSFWorkbook) workbook;HSSFPalette palette = hssfWorkbook.getCustomPalette();HSSFColor color = palette.findSimilarColor(rgbBytes[0] & 0xFF,rgbBytes[1] & 0xFF,rgbBytes[2] & 0xFF);font.setColor(color.getIndex());}} catch (Exception e) {log.error("设置字体颜色失败: {}", rgb, e);}}// 辅助方法private boolean isValidHexColor(String color) {return color != null && Pattern.matches("^#[0-9A-Fa-f]{6}$", color);}private byte[] parseHexColor(String hexColor) {if (!isValidHexColor(hexColor)) {return new byte[]{0, 0, 0}; // 默认黑色}return new byte[]{(byte) Integer.parseInt(hexColor.substring(1, 3), 16),(byte) Integer.parseInt(hexColor.substring(3, 5), 16),(byte) Integer.parseInt(hexColor.substring(5, 7), 16)};}private int safeParseInt(String str, int defaultValue) {try {return Integer.parseInt(str);} catch (NumberFormatException e) {return defaultValue;}}private int getInt(Map<String, Object> map, String key, int defaultValue) {Object value = map.get(key);if (value instanceof Number) {return ((Number) value).intValue();} else if (value != null) {try {return Integer.parseInt(value.toString());} catch (NumberFormatException e) {return defaultValue;}}return defaultValue;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/916189.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/916189.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python day24

浙大疏锦行 python day24 内容&#xff1a; 元组&#xff1a;类比于列表&#xff0c;不过元组的元素不能被修改&#xff0c;显示也是从[]改为了()&#xff0c;其余操作则是和列表类似&#xff0c;且元组是有序的可迭代对象&#xff1a;即可以使用迭代器访问的对象&#xff0c…

Three.js 动画系统入门:Tween.js 与 AnimationMixer 的使用

引言 动画是 Three.js 中增强 3D 场景动态效果的核心技术&#xff0c;能够为用户带来沉浸式体验。Three.js 支持通过 Tween.js 实现简单的属性动画&#xff0c;以及通过 AnimationMixer 处理复杂的混合动画和骨骼动画。本文将深入探讨如何使用 Tween.js 控制 Object3D 的属性动…

装修进度管理系统功能对比:主流工具9选

本文分享了9款常用的装修进度管理软件&#xff0c;包括&#xff1a;1.Worktile&#xff1b;2.中望软件&#xff1b;3.三维家&#xff1b;4.Procore&#xff1b;5.易达装修管理系统&#xff1b;6.装修管家&#xff1b;7.Zoho Projects&#xff1b;8.中建君联&#xff1b;9.一品装…

深度学习篇---预训练模型

在深度学习中&#xff0c;预训练模型&#xff08;Pretrained Model&#xff09; 是提升开发效率和模型性能的 “利器”。无论是图像识别、自然语言处理还是语音识别&#xff0c;预训练模型都被广泛使用。下面从概念、使用原因、场景、作用等方面详细介绍&#xff0c;并结合 Pyt…

Redis ①⑦-分布式锁

分布式锁 分布式锁是锁的一种&#xff0c;都是为了解决多线程/多进程环境下&#xff0c;对共享资源的访问冲突问题。 不过&#xff0c;像 Java 的 synchronized 或者 C 的 mutex 这种锁&#xff0c;都是进程内的锁&#xff0c;而分布式锁则是跨越进程/机器的锁。也就是可以针对…

OpenCV-图像预处理➀【图像颜色空间转换、灰度化实验、二值化处理、镜像翻转 和 仿射变换】

文章目录先言一、图像颜色空间转换1.RGB颜色空间2.颜色加法3.颜色加权加法4.HSV颜色空间5.图像转换&#xff08;cvtColor()&#xff09;二、灰度实验1.灰度图2.图像灰度化&#xff08;最大值法&#xff09;3.图像灰度化&#xff08;平均值法&#xff09;4.图像灰度化&#xff0…

APP逆向 day9 安卓开发基础

一.前言 app逆向当然要学安卓基础啦&#xff01;今天我们来教安卓基础当然&#xff0c;安卓基础不会教的很多&#xff0c;比java还要少&#xff0c;还是那句话&#xff0c;了解就好。 二.安卓环境搭建 2.1 安卓介绍 如果做安卓开发 需要会java代码安卓SDK(安卓提供的内置…

Jmeter的元件使用介绍:(三)配置元件详解02

六、计数器 可以用来做一些变量自增操作。 1、Starting value:定义初始值 2、递增&#xff1a;定义每次执行递增多少 3、Maximum value:定义承受的最大值 4、数据格式&#xff1a;可以不填&#xff0c;也可以定义成000;001;002等等任意格式都行。&#xff08;1&#xff09;如…

JavaWeb学习打卡15(JSP标签、JSTL标签、EL表达式)

EL表达式&#xff1a;${ }获取数据执行运算获取web开发的常用对象在pom.xml 文件中导入JSP、JSTL相关依赖&#xff1a;<!--JSP依赖--><!-- https://mvnrepository.com/artifact/javax.servlet.jsp/javax.servlet.jsp-api --><dependency><groupId>java…

7.22数据结构——顺序表

文章目录一、思维导图二、实现顺序表的功能代码head.htest.cmain.c一、思维导图 二、实现顺序表的功能代码 head.h #ifndef __HEAD_H__ #define __HEAD_H__#include <stdio.h> #include <string.h> #include <stdlib.h> //数组的最大长度 #define MAXSIZE …

【如何无限制免费试用 IDEA || Pycharm(JB 全家桶)】

如何无限制免费试用 IDEA || Pycharm(JB 全家桶) 一、目标:解决 JB 全家桶试用时长痛点 如果你是程序员,大概率用过 JetBrains 家的 IDE——IDEA 写 Java、Pycharm 写 Python、WebStorm 做前端,体验确实顶流,但官方 30 天试用到期后,动辄几千的年费实在让人肉痛。 咱…

Qt(资源库和按钮组)

这一节是对上一节的补充&#xff0c;上一节提到QLabel类和QAabstractButton类&#xff0c;这节内容&#xff1a;1.如设置资源库&#xff0c;使用资源设置图片2. 使用按钮组管理多个按钮。一、资源库1. 资源库作用Qt的资源库&#xff08;Resource System&#xff0c;.qrc文件&am…

一道检验编码能力的字符串的题目

#include<iostream> #include<vector> #include<string> using namespace std; int bNum0,gNum0; int findEnd(string& s,int si){int lens.size();//当前字母在哪个字符串中,存入comp中string comp;if(s[si]b||s[si]o||s[si]y){comp"boy";bNu…

UniApp X 网络请求避坑指南:从 JS 到 UTS 的 JSON 数据处理全解析

在 UniApp 开发中&#xff0c;我们经常需要通过 uni.request 获取服务器返回的 JSON 数据&#xff0c;并将其绑定到页面或进行逻辑处理。但在 UniApp X&#xff08;基于 UTS&#xff09; 中&#xff0c;由于引入了 强类型语言特性&#xff0c;处理 JSON 数据的方式与 JS 有明显…

iOS 网络请求常用依赖库与系统自带 API 介绍与示例

iOS 网络请求常用依赖库与系统自带 API 介绍与示例 在 iOS 开发中&#xff0c;进行网络请求是几乎所有应用都不可或缺的功能。开发者有多种选择来处理网络通信&#xff0c;从系统自带的 URLSession 到各种流行的第三方库。下面我将为您介绍 URLSession、AFNetworking、Alamofir…

JavaScript 中 let 在循环中的作用域机制解析

一、let在循环中的特殊性 let作为ES6引入的块级作用域声明&#xff0c;在循环结构中存在特殊行为&#xff0c;其核心区别于var的函数作用域特性。理解这一特性对于编写正确的闭包逻辑至关重要。 在 ECMAScript 规范里&#xff0c;let声明的变量具有块级作用域特性&#xff0c;这…

@Subscribe@AllowConcurrentEvents解析这两个注解

@Subscribe@AllowConcurrentEvents解析这两个注解 @Subscribe 和 @AllowConcurrentEvents 是 Guava EventBus(Google 开源的事件总线框架)中用于处理事件订阅的注解,主要用于实现组件间的解耦通信。下面分别解析: 1. @Subscribe 注解 作用:标记一个方法为事件订阅者方法,…

好看的小程序推广单页HTML源码 可用作导航页

内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示三、学习资料下载一、详细介绍 响应式的小程序推广单页HTML源码。这个设计采用了现代化的UI元素&#xff0c;包含吸引人的标题、特性展示、二维码区域和行动号召按钮。 二、效果展示 1.部分代码 代码如下&#xff0…

华为仓颉编程语言实践体验

华为仓颉编程语言实践体验 目前华为仓颉编程语言因为其推出时间较短&#xff0c;生态系统不完善。官网资料权威&#xff0c;但比较庞大难懂。快速实验入门&#xff0c;是学习一门编程语言的法宝。网上靠谱的资料稀少&#xff0c;特此撰文介绍&#xff0c;帮助初学者减少挫折感&…

YOLOv11实战,使用YOLOv11训练自己的数据集和推理(附YOLOv11网络结构图)

2024年计算机视觉领域的颠覆性突破,YOLOv11以22%的参数量减少和0.3%的mAP提升重新定义实时目标检测的边界 本文将手把手带你完成YOLOv11的全流程实战,包含环境配置、数据准备、模型训练、推理部署及创新优化方案,并深度解析其网络架构设计思想。 一、YOLOv11核心创新解析 …