基于Handsontable.js + Excel.js实现表格预览和导出功能(公式渲染)
本文记录在html中基于Handsontable.js + Excel.js实现表格预览、导出、带公式单元格渲染功能,在这里我们在html中实现,当然也可以在vue、react等框架中使用npm下载导入依赖文件。
Handsontable官方文档
一、开发前的准备引入相关依赖库
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>基于Handsontable.js + Excel.js实现表格预览功能</title><!-- handsontable的css文件 https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css --><link rel="stylesheet" href="./lib/handsontable.full.min.css">
</head>
<body><!-- https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js --><script src="./lib/handsontable.full.min.js"></script><!-- https://cdn.jsdelivr.net/npm/handsontable/dist/languages/zh-CN.js --><script src="./lib/zh-CN.js"></script><!-- https://cdn.jsdelivr.net/npm/color-js --><script src="./lib/color-js.js"></script><!-- https://cdnjs.cloudflare.com/ajax/libs/hyperformula/1.4.0/hyperformula.min.js --><script src="./lib/hyperformula.full.min.js"></script><!-- https://cdn.jsdelivr.net/npm/exceljs/dist/exceljs.min.js --><script src="./lib/exceljs.min.js"></script><!-- https://cdn.jsdelivr.net/npm/xlsx/dist/xlsx.full.min.js --><script src="./lib/xlsx.full.min.js"></script><!-- https://cdn.jsdelivr.net/npm/fxparser@1.0.0/dist/fxparser.min.js --><script src="./lib/fxparser.min.js"></script>
</body>
</html>
二、编写页面布局
<body><input type="file" id="file"><button id="btn">预览</button><button id="export">导出</button>
</body>
三、编写预览核心代码
<script>var hot; //handsontable实例var themeJson; //主题jsonvar sheet; //当前sheetvar Color = net.brehaut.Color; //引入color-js库// 自定义渲染器函数function customRenderer(hotInstance, td, row, column, prop, value, cellProperties) {Handsontable.renderers.TextRenderer(hotInstance, td, row, column, prop, value, cellProperties);// 填充样式if ("fill" in cellProperties) {// 背景颜色if ("fgColor" in cellProperties.fill && cellProperties.fill.fgColor) {td.style.background = getColor(cellProperties.fill.fgColor, themeJson);}}// 字体样式if ("font" in cellProperties) {// 加粗if ("bold" in cellProperties.font && cellProperties.font.bold) {td.style.fontWeight = "700";}// 字体颜色if ("color" in cellProperties.font && cellProperties.font.color) {td.style.color = getColor(cellProperties.font.color, themeJson);}// 字体大小if ("size" in cellProperties.font && cellProperties.font.size) {td.style.fontSize = cellProperties.font.size + "px";}// 字体类型if ("name" in cellProperties.font && cellProperties.font.name) {td.style.fontFamily = cellProperties.font.name;}// 字体倾斜if ("italic" in cellProperties.font && cellProperties.font.italic) {td.style.fontStyle = "italic";}// 下划线if ("underline" in cellProperties.font &&cellProperties.font.underline) {// 其实还有双下划线,但是双下划綫css中没有提供直接的设置方式,需要使用额外的css设置,所以我也就先懒得弄了td.style.textDecoration = "underline";// 删除线if ("strike" in cellProperties.font && cellProperties.font.strike) {td.style.textDecoration = "underline line-through";}} else {// 删除线if ("strike" in cellProperties.font && cellProperties.font.strike) {td.style.textDecoration = "line-through";}}}// 对齐if ("alignment" in cellProperties) {if ("horizontal" in cellProperties.alignment) {// 水平// 这里我直接用handsontable内置类做了,设置成类似htLeft的样子。//(handsontable)其实至支持htLeft, htCenter, htRight, htJustify四种,但是其是它还有centerContinuous、distributed、fill,遇到这几种就会没有效果,也可以自己设置,但是我还是懒的弄了,用到的时候再说吧const name =cellProperties.alignment.horizontal.charAt(0).toUpperCase() +cellProperties.alignment.horizontal.slice(1);td.classList.add(`ht${name}`);}if ("vertical" in cellProperties.alignment) {// 垂直// 这里我直接用handsontable内置类做了,设置成类似htTop的样子。const name =cellProperties.alignment.vertical.charAt(0).toUpperCase() +cellProperties.alignment.vertical.slice(1);td.classList.add(`ht${name}`);}}// 边框if ("border" in cellProperties) {if ("left" in cellProperties.border && cellProperties.border.left) {// 左边框const [borderWidth, borderStyle] = setBorder(cellProperties.border.left.style);let color = "";// console.log(row, column, borderWidth, borderStyle);if (cellProperties.border.left.color) {color = getColor(cellProperties.border.left.color, themeJson);}td.style.borderLeft = `${borderStyle} ${borderWidth} ${color}`;}if ("right" in cellProperties.border && cellProperties.border.right) {// 左边框const [borderWidth, borderStyle] = setBorder(cellProperties.border.right.style);// console.log(row, column, borderWidth, borderStyle);let color = "";if (cellProperties.border.right.color) {color = getColor(cellProperties.border.right.color, themeJson);}td.style.borderRight = `${borderStyle} ${borderWidth} ${color}`;}if ("top" in cellProperties.border && cellProperties.border.top) {// 左边框const [borderWidth, borderStyle] = setBorder(cellProperties.border.top.style);let color = "";// console.log(row, column, borderWidth, borderStyle);if (cellProperties.border.top.color) {color = getColor(cellProperties.border.top.color, themeJson);}td.style.borderTop = `${borderStyle} ${borderWidth} ${color}`;}if ("bottom" in cellProperties.border && cellProperties.border.bottom) {// 左边框const [borderWidth, borderStyle] = setBorder(cellProperties.border.bottom.style);let color = "";// console.log(row, column, borderWidth, borderStyle);if (cellProperties.border.bottom.color) {color = getColor(cellProperties.border.bottom.color, themeJson);}td.style.borderBottom = `${borderStyle} ${borderWidth} ${color}`;}}}// 在 Handsontable 初始化之前注册渲染器Handsontable.renderers.registerRenderer('customStylesRenderer', customRenderer);// 点击预览按钮document.getElementById('btn').addEventListener('click', async function () {var hotData = null; // Handsontable 数据var file = document.getElementById('file').files[0]; // 获取文件对象const workbook = new ExcelJS.Workbook(); // 创建一个工作簿对象await workbook.xlsx.load(file); // 加载Excel文件const worksheet = workbook.getWorksheet(1); // 获取第一个工作表sheet = worksheet; // 将工作表赋值给全局变量// 遍历工作表中的所有行(包括空行)const sheetData = [];worksheet.eachRow({ includeEmpty: true }, function (row, rowNumber) {const row_values = row.values.slice(1); // 获取行数据,并排除第一列为null的数据const newRowValue = [...row_values];// 将行数据添加到sheetData数组中sheetData.push(newRowValue);});// 将数据赋值给HandsontablehotData = sheetData; // 将主题xml转换成jsonconst themeXml = workbook._themes.theme1;const options = {ignoreAttributes: false,attributeNamePrefix: "_",};const parser = new XMLParser(options);const json = parser.parse(themeXml);themeJson = json// 获取合并的单元格const mergeCells = [];for (let i in worksheet._merges) {const { top, left, bottom, right } = worksheet._merges[i].model;mergeCells.push({row: top - 1,col: left - 1,rowspan: bottom - top + 1,colspan: right - left + 1});};// 将数据加载到HyperFormulahot = new Handsontable(document.getElementById('hot'), {// 数据data: hotData,colHeaders: true,rowHeaders: true,language: 'zh-CN',readOnly: true,width: '100%',height: 'calc(100% - 25px)',//handsontable的许可证licenseKey: 'non-commercial-and-evaluation',// 行高rowHeights: function (index) {if (sheet.getRow(index + 1).height) {// exceljs获取的行高不是像素值,事实上,它是23px - 13.8 的一个映射。所以需要将它转化为像素值return sheet.getRow(index + 1).height * (23 / 13.8);}return 23; // 默认},// 列宽colWidths: function (index) {if (sheet.getColumn(index + 1).width) {// exceljs获取的列宽不是像素值,事实上,它是81px - 8.22 的一个映射。所以需要将它转化为像素值return sheet.getColumn(index + 1).width * (81 / 8.22);}return 81; // 默认},// 自定义单元格样式cells: function (row, col, prop) {const cellProperties = {};const cellStyle = sheet.getCell(row + 1, col + 1).style;if (JSON.stringify(cellStyle) !== "{}") {// console.log(row+1, col+1, cellStyle);for (let key in cellStyle) {cellProperties[key] = cellStyle[key];}}return { ...cellProperties, renderer: "customStylesRenderer" };},// 合并单元格mergeCells: mergeCells});//8.将handsontable实例渲染到页面上hot.render();});// 根据主题和明暗度themeId获取颜色function getThemeColor(themeJson, themeId, tint) {let color = "";const themeColorScheme =themeJson["a:theme"]["a:themeElements"]["a:clrScheme"];switch (themeId) {case 0:color = themeColorScheme["a:lt1"]["a:sysClr"]["_lastClr"];break;case 1:color = themeColorScheme["a:dk1"]["a:sysClr"]["_lastClr"];break;case 2:color = themeColorScheme["a:lt2"]["a:srgbClr"]["_val"];break;case 3:color = themeColorScheme["a:dk2"]["a:srgbClr"]["_val"];break;default:color = themeColorScheme[`a:accent${themeId - 3}`]["a:srgbClr"]["_val"];break;}// 根据tint修改颜色深浅color = "#" + color;const colorObj = Color(color);if (tint) {if (tint > 0) {// 淡色color = colorObj.lighten(tint).hex();} else {// 深色color = colorObj.darken(Math.abs(tint)).hex();}}return color;}// 获取颜色function getColor(obj, themeJson) {if ("argb" in obj) {// 标准色// rgba格式去掉前两位: FFFF0000 -> FF0000return "#" + obj.argb.substring(2);} else if ("theme" in obj) {// 主题颜色if ("tint" in obj) {return getThemeColor(themeJson, obj.theme, obj.tint);} else {return getThemeColor(themeJson, obj.theme, null);}}}// 设置边框function setBorder(style) {let borderStyle = "solid";let borderWidth = "1px";switch (style) {case "thin":borderWidth = "thin";break;case "dotted":borderStyle = "dotted";break;case "dashDot":borderStyle = "dashed";break;case "hair":borderStyle = "solid";break;case "dashDotDot":borderStyle = "dashed";break;case "slantDashDot":borderStyle = "dashed";break;case "medium":borderWidth = "2px";break;case "mediumDashed":borderStyle = "dashed";borderWidth = "2px";break;case "mediumDashDotDot":borderStyle = "dashed";borderWidth = "2px";break;case "mdeiumDashDot":borderStyle = "dashed";borderWidth = "2px";break;case "double":borderStyle = "double";break;case "thick":borderWidth = "3px";break;default:break;}// console.log(borderStyle, borderWidth);return [borderStyle, borderWidth];}
</script>
如下图所示:
通过以上代码,我们成功将Excel文件中的数据、样式、合并单元格等信息加载到了Handsontable中,并渲染到了页面上。
对于单元格中带有公式的,按照以上代码会出现问题,就是带有公式的单元格会渲染成"[object Object]",如下图所示:
所以我们需要对公式进行处理,将公式替换为对应的值。请看以下处理公式的方法。
在遍历单元格时我们可以打印看下读取到单元格的数据具体是什么样的,然后根据具体情况进行处理,如下图所示:
由图可以看到带有公式的单元格是一个对象,这就是带有公式的单元格渲染后是"[object Object]"的原因。解决方法就是将带有公式的单元格替换为对应的值,请看以下代码:
worksheet.eachRow({ includeEmpty: true }, function (row, rowNumber) {const row_values = row.values.slice(1); // 获取行数据,并排除第一列为null的数据const newRowValue = [...row_values];newRowValue.forEach(function(item, index) {if(Object.prototype.toString.call(item) === "[object Object]") {if(item.formula) {// 如果是公式,则保留公式,否则将结果作为值newRowValue[index] = item.formula.includes("=") ? item.formula : "=" + item.result;}};})sheetData.push(newRowValue);
});
通过处理后,带有公示的单元格不在渲染为"[object Object]“,但是会渲染出具体使用了什么公式,如:”=SUM(B1:B2)“会渲染为”=2",如下图所示:
解决方法请看以下代码:
// 定义HyperFormula 公式配置配置
const hyperformulaInstance = HyperFormula.buildEmpty({licenseKey: 'internal-use-in-handsontable'
});
hot = new Handsontable(document.getElementById('hot'), {// 数据data: hotData,colHeaders: true,rowHeaders: true,language: 'zh-CN',readOnly: true,// 开启公式formulas: {engine: hyperformulaInstance,sheetName: "Sheet1",},width: '100%',height: 'calc(100% - 25px)',//handsontable的许可证licenseKey: 'non-commercial-and-evaluation',
})
我们开启公式后,带有公示的单元格会渲染为对应的值,如下图所示:
至此,我们成功将Excel文件中的数据、样式、合并单元格等信息加载到了Handsontable中,并渲染到了页面上。
使用Handsontable导出Excel文件,请看以下代码:
// 将handsontable实例导出为excel文件
document.getElementById('export').addEventListener('click', function () {var exportData = Handsontable.helper.createEmptySpreadsheetData(100, 100);hot.getData().forEach(function (row, rowIndex) {row.forEach(function (cell, colIndex) {exportData[rowIndex][colIndex] = cell;});});var workbook = XLSX.utils.book_new();var worksheet = XLSX.utils.aoa_to_sheet(exportData);XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');XLSX.writeFile(workbook, 'export.xlsx');
});
此处贴出整体代码
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>基于Handsontable.js + Excel.js实现表格预览功能</title><!-- handsontable的css文件 https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css --><link rel="stylesheet" href="./lib/handsontable.full.min.css"><style>* {margin: 0;padding: 0;}html,body {width: 100%;height: 100%;}</style>
</head><body><input type="file" id="file"><button id="btn">预览</button><button id="export">导出</button><!-- handsontable的容器 --><div id="hot"></div><!-- https://cdn.jsdelivr.net/npm/color-js --><script src="./lib/color-js.js"></script><!-- https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js --><script src="./lib/handsontable.full.min.js"></script><!-- https://cdn.jsdelivr.net/npm/handsontable/dist/languages/zh-CN.js --><script src="./lib/zh-CN.js"></script><!-- https://cdnjs.cloudflare.com/ajax/libs/hyperformula/1.4.0/hyperformula.min.js --><script src="./lib/hyperformula.full.min.js"></script><!-- https://cdn.jsdelivr.net/npm/exceljs/dist/exceljs.min.js --><script src="./lib/exceljs.min.js"></script><!-- https://cdn.jsdelivr.net/npm/xlsx/dist/xlsx.full.min.js --><script src="./lib/xlsx.full.min.js"></script><!-- https://cdn.jsdelivr.net/npm/fxparser@1.0.0/dist/fxparser.min.js --><script src="./lib/fxparser.min.js"></script><script>//在html中使用excel+handsontable实现excel预览功能window.onload = function () {//1.引入handsontable的css和js文件//3.获取excel文件var hot; //handsontable实例var themeJson; //主题jsonvar sheet; //当前sheetvar Color = net.brehaut.Color; //引入color-js库// 自定义渲染器函数function customRenderer(hotInstance, td, row, column, prop, value, cellProperties) {Handsontable.renderers.TextRenderer(hotInstance, td, row, column, prop, value, cellProperties);// 填充样式if ("fill" in cellProperties) {// 背景颜色if ("fgColor" in cellProperties.fill && cellProperties.fill.fgColor) {td.style.background = getColor(cellProperties.fill.fgColor,themeJson);}}// 字体样式if ("font" in cellProperties) {// 加粗if ("bold" in cellProperties.font && cellProperties.font.bold) {td.style.fontWeight = "700";}// 字体颜色if ("color" in cellProperties.font && cellProperties.font.color) {td.style.color = getColor(cellProperties.font.color, themeJson);}// 字体大小if ("size" in cellProperties.font && cellProperties.font.size) {td.style.fontSize = cellProperties.font.size + "px";}// 字体类型if ("name" in cellProperties.font && cellProperties.font.name) {td.style.fontFamily = cellProperties.font.name;}// 字体倾斜if ("italic" in cellProperties.font && cellProperties.font.italic) {td.style.fontStyle = "italic";}// 下划线if ("underline" in cellProperties.font &&cellProperties.font.underline) {// 其实还有双下划线,但是双下划綫css中没有提供直接的设置方式,需要使用额外的css设置,所以我也就先懒得弄了td.style.textDecoration = "underline";// 删除线if ("strike" in cellProperties.font && cellProperties.font.strike) {td.style.textDecoration = "underline line-through";}} else {// 删除线if ("strike" in cellProperties.font && cellProperties.font.strike) {td.style.textDecoration = "line-through";}}}// 对齐if ("alignment" in cellProperties) {if ("horizontal" in cellProperties.alignment) {// 水平// 这里我直接用handsontable内置类做了,设置成类似htLeft的样子。//(handsontable)其实至支持htLeft, htCenter, htRight, htJustify四种,但是其是它还有centerContinuous、distributed、fill,遇到这几种就会没有效果,也可以自己设置,但是我还是懒的弄了,用到的时候再说吧const name = cellProperties.alignment.horizontal.charAt(0).toUpperCase() +cellProperties.alignment.horizontal.slice(1);td.classList.add(`ht${name}`);}if ("vertical" in cellProperties.alignment) {// 垂直// 这里我直接用handsontable内置类做了,设置成类似htTop的样子。const name =cellProperties.alignment.vertical.charAt(0).toUpperCase() +cellProperties.alignment.vertical.slice(1);td.classList.add(`ht${name}`);}}// 边框if ("border" in cellProperties) {if ("left" in cellProperties.border && cellProperties.border.left) {// 左边框const [borderWidth, borderStyle] = setBorder(cellProperties.border.left.style);let color = "";// console.log(row, column, borderWidth, borderStyle);if (cellProperties.border.left.color) {color = getColor(cellProperties.border.left.color, themeJson);}td.style.borderLeft = `${borderStyle} ${borderWidth} ${color}`;}if ("right" in cellProperties.border && cellProperties.border.right) {// 左边框const [borderWidth, borderStyle] = setBorder(cellProperties.border.right.style);// console.log(row, column, borderWidth, borderStyle);let color = "";if (cellProperties.border.right.color) {color = getColor(cellProperties.border.right.color, themeJson);}td.style.borderRight = `${borderStyle} ${borderWidth} ${color}`;}if ("top" in cellProperties.border && cellProperties.border.top) {// 左边框const [borderWidth, borderStyle] = setBorder(cellProperties.border.top.style);let color = "";// console.log(row, column, borderWidth, borderStyle);if (cellProperties.border.top.color) {color = getColor(cellProperties.border.top.color, themeJson);}td.style.borderTop = `${borderStyle} ${borderWidth} ${color}`;}if ("bottom" in cellProperties.border && cellProperties.border.bottom) {// 左边框const [borderWidth, borderStyle] = setBorder(cellProperties.border.bottom.style);let color = "";// console.log(row, column, borderWidth, borderStyle);if (cellProperties.border.bottom.color) {color = getColor(cellProperties.border.bottom.color, themeJson);}td.style.borderBottom = `${borderStyle} ${borderWidth} ${color}`;}}}// 在 Handsontable 初始化之前注册渲染器Handsontable.renderers.registerRenderer('customStylesRenderer', customRenderer);// 点击预览document.getElementById('btn').addEventListener('click', async function () {var hotData = null; // Handsontable 数据var file = document.getElementById('file').files[0]; // 获取文件对象const workbook = new ExcelJS.Workbook(); // 创建一个工作簿对象await workbook.xlsx.load(file); // 加载Excel文件const worksheet = workbook.getWorksheet(1); // 获取第一个工作表sheet = worksheet; // 将工作表赋值给全局变量// 遍历工作表中的所有行(包括空行)const sheetData = [];worksheet.eachRow({ includeEmpty: true }, function (row, rowNumber) {const row_values = row.values.slice(1); // 获取行数据,并排除第一列为null的数据const newRowValue = [...row_values];newRowValue.forEach(function(item, index) {if(Object.prototype.toString.call(item) === "[object Object]") {if(item.formula) {// 如果是公式,则保留公式,否则将结果作为值newRowValue[index] = item.formula.includes("=") ? item.formula : "=" + item.result;}};})sheetData.push(newRowValue);});// 将数据赋值给HandsontablehotData = sheetData; // 将主题xml转换成jsonconst themeXml = workbook._themes.theme1;const options = {ignoreAttributes: false,attributeNamePrefix: "_",};const parser = new XMLParser(options);const json = parser.parse(themeXml);themeJson = json// 获取合并的单元格const mergeCells = [];for (let i in worksheet._merges) {const { top, left, bottom, right } = worksheet._merges[i].model;mergeCells.push({row: top - 1,col: left - 1,rowspan: bottom - top + 1,colspan: right - left + 1,});};// 定义HyperFormula配置const hyperformulaInstance = HyperFormula.buildEmpty({licenseKey: 'internal-use-in-handsontable'});// 将数据加载到HyperFormulahot = new Handsontable(document.getElementById('hot'), {// 数据data: hotData,colHeaders: true,rowHeaders: true,language: 'zh-CN',readOnly: true,// 公式formulas: {engine: hyperformulaInstance,sheetName: "Sheet1",},width: '100%',height: 'calc(100% - 25px)',//handsontable的许可证licenseKey: 'non-commercial-and-evaluation',// 行高rowHeights: function (index) {if (sheet.getRow(index + 1).height) {// exceljs获取的行高不是像素值,事实上,它是23px - 13.8 的一个映射。所以需要将它转化为像素值return sheet.getRow(index + 1).height * (23 / 13.8);}return 23; // 默认},// 列宽colWidths: function (index) {if (sheet.getColumn(index + 1).width) {// exceljs获取的列宽不是像素值,事实上,它是81px - 8.22 的一个映射。所以需要将它转化为像素值return sheet.getColumn(index + 1).width * (81 / 8.22);}return 81; // 默认},// 自定义单元格样式cells: function (row, col, prop) {const cellProperties = {};const cellStyle = sheet.getCell(row + 1, col + 1).style;if (JSON.stringify(cellStyle) !== "{}") {// console.log(row+1, col+1, cellStyle);for (let key in cellStyle) {cellProperties[key] = cellStyle[key];}}return { ...cellProperties, renderer: "customStylesRenderer" };},// 合并单元格mergeCells: mergeCells});//将handsontable实例渲染到页面上hot.render();});// 将handsontable实例导出为excel文件document.getElementById('export').addEventListener('click', function () {var exportData = Handsontable.helper.createEmptySpreadsheetData(100, 100);hot.getData().forEach(function (row, rowIndex) {row.forEach(function (cell, colIndex) {exportData[rowIndex][colIndex] = cell;});});var workbook = XLSX.utils.book_new();var worksheet = XLSX.utils.aoa_to_sheet(exportData);XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');XLSX.writeFile(workbook, 'export.xlsx');});// 根据主题和明暗度themeId获取颜色function getThemeColor(themeJson, themeId, tint) {let color = "";const themeColorScheme = themeJson["a:theme"]["a:themeElements"]["a:clrScheme"];switch (themeId) {case 0:color = themeColorScheme["a:lt1"]["a:sysClr"]["_lastClr"];break;case 1:color = themeColorScheme["a:dk1"]["a:sysClr"]["_lastClr"];break;case 2:color = themeColorScheme["a:lt2"]["a:srgbClr"]["_val"];break;case 3:color = themeColorScheme["a:dk2"]["a:srgbClr"]["_val"];break;default:color = themeColorScheme[`a:accent${themeId - 3}`]["a:srgbClr"]["_val"];break;}// 根据tint修改颜色深浅color = "#" + color;const colorObj = Color(color);if (tint) {if (tint > 0) {// 淡色color = colorObj.lighten(tint).hex();} else {// 深色color = colorObj.darken(Math.abs(tint)).hex();}}return color;}// 获取颜色function getColor(obj, themeJson) {if ("argb" in obj) {// 标准色// rgba格式去掉前两位: FFFF0000 -> FF0000return "#" + obj.argb.substring(2);} else if ("theme" in obj) {// 主题颜色if ("tint" in obj) {return getThemeColor(themeJson, obj.theme, obj.tint);} else {return getThemeColor(themeJson, obj.theme, null);}}}// 设置边框function setBorder(style) {let borderStyle = "solid";let borderWidth = "1px";switch (style) {case "thin":borderWidth = "thin";break;case "dotted":borderStyle = "dotted";break;case "dashDot":borderStyle = "dashed";break;case "hair":borderStyle = "solid";break;case "dashDotDot":borderStyle = "dashed";break;case "slantDashDot":borderStyle = "dashed";break;case "medium":borderWidth = "2px";break;case "mediumDashed":borderStyle = "dashed";borderWidth = "2px";break;case "mediumDashDotDot":borderStyle = "dashed";borderWidth = "2px";break;case "mdeiumDashDot":borderStyle = "dashed";borderWidth = "2px";break;case "double":borderStyle = "double";break;case "thick":borderWidth = "3px";break;default:break;}// console.log(borderStyle, borderWidth);return [borderStyle, borderWidth];}}</script>
</body></html>
参考文献:
前端实现(excel)xlsx文件预览
相关文章:

基于Handsontable.js + Excel.js实现表格预览和导出功能(公式渲染)
本文记录在html中基于Handsontable.js Excel.js实现表格预览、导出、带公式单元格渲染功能,在这里我们在html中实现,当然也可以在vue、react等框架中使用npm下载导入依赖文件。 Handsontable官方文档 一、开发前的准备引入相关依赖库 <!DOCTYPE ht…...

重学SpringBoot3-集成Redis(十三)之点排行榜实现
更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(十三)之点排行榜实现 1. 为什么选择 Redis 来实现排行榜?2. 项目环境准备2.1. 添加依赖2.2. 配置 Redis 连…...
Java 中方法参数传递的陷阱
前言 在编程过程中,我们经常会遇到一些看似简单却容易出错的问题。本文将通过一个具体的例子,探讨 Java 中方法参数传递的陷阱,并提供详细的解决方法。希望这篇文章能帮助你在未来的开发中避免类似的错误。 问题背景 假设我们的任务是计算…...

哪家云电脑便宜又好用?ToDesk云电脑、顺网云、达龙云全方位评测
陈老老老板🤴 🧙♂️本文专栏:生活(主要讲一下自己生活相关的内容)生活就像海洋,只有意志坚强的人,才能到达彼岸。 🧙♂️本文简述:讲一下市面上云电脑的对比。 🧙♂️上一篇文…...

【汇编语言】寄存器(内存访问)(三)—— 字的传送
文章目录 前言1. 字的传送2. 问题一3. 问题一的分析与解答4. 问题二5. 问题二的分析与解答结语 前言 📌 汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言…...
6 机器学习之应用现状
在过去二十年中,人类收集、存储、传输、处理数据的能力取得了飞速提升,人类社会的各个角落都积累了大量数据,亟需能有效地对数据进行分析利用的计算机算法,而机器学习恰顺应了大时代的这个迫切需求,因此该学科领域很自…...

相似度为 K 的字符串
题目链接 相似度为 K 的字符串 题目描述 注意 s1和s2只包含集合 {‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’} 中的小写字母s2是s1的一个字母异位词 解答思路 可以深度优先遍历交换字母使得s1和s2尽可能接近,基本思路是:设定一个指针idx指向s1和s2的…...
[云] Project Analysis
项目要求分析: 开放性选题: 主题范围:任何与云计算系统相关的主题。项目类型:可以是技术、商业或研究项目。团队规模:最多可组成三人小组。 示例主题: 分析公共云数据:例如,AWS公共数…...

腾讯六宫格本地识别,本地模型识别,腾讯六图识别
基于K哥爬虫昨天发的文章,特此训练了一版腾讯模型,效果不错,特此感谢K哥的指导,效果如下图: 有需求,有疑问的欢迎评论区点出...

Transformer图解以及相关的概念
前言 transformer是目前NLP甚至是整个深度学习领域不能不提到的框架,同时大部分LLM也是使用其进行训练生成模型,所以transformer几乎是目前每一个机器人开发者或者人工智能开发者不能越过的一个框架。接下来本文将从顶层往下去一步步掀开transformer的面…...
Nginx缓存静态文件
在Python项目中,通过Nginx缓存静态文件(如CSS、JS、图片等),可以有效提升网页的加载性能。Nginx可以帮助你缓存静态资源,减少服务器负担,并加速页面加载。 1. 配置Nginx缓存静态文件 首先,你需…...

【隐私计算】隐语HEU同态加密算法解读
HEU: 一个高性能的同态加密算法库,提供了多种 PHE 算法, 包括ZPaillier、FPaillier、IPCL、Damgard Jurik、DGK、OU、EC ElGamal 以及基于FPGA和GPU硬件加速版本的Paillier版本。 本文我们会基于GPU运行HEU Docker容器,编译打包GPaillier并测…...
用C#实现互斥操作
1、传统的lock lock简单易用,适合大多数场景,但在高竞争用情况下可能会导致线程阻塞; Object obj new object(); void method1(){lock (obj){// 进行互斥操作}}2、SpinLock SpinLock在低延迟情况下更有效,因为SpinLock会在忙等…...

【黑马点评优化】之使用Caffeine+Redis实现应用级二层缓存
【黑马点评优化】之使用CaffeineRedis实现应用级二层缓存 1 缓存雪崩定义及解决方案2 为什么要使用多级缓存3 RedisCaffeine实现应用层二级缓存原理4 利用CaffeineRedis解决Redis突然宕机导致的缓存雪崩问题4.1 pom.xml文件引入相关依赖4.2 本地缓存配置类4.3 修改ShopServiceI…...

CEEMDAN +组合预测模型(BiLSTM-Attention + ARIMA)
往期精彩内容: 时序预测:LSTM、ARIMA、Holt-Winters、SARIMA模型的分析与比较 全是干货 | 数据集、学习资料、建模资源分享! EMD、EEMD、FEEMD、CEEMD、CEEMDAN的区别、原理和Python实现(一)EMD-CSDN博客 EMD、EEM…...
2.1.ReactOS系统中断描述符的格式KIDTENTRY结构体
2.1.ReactOS系统中断描述符的格式KIDTENTRY结构体 2.1.ReactOS系统中断描述符的格式KIDTENTRY结构体 文章目录 2.1.ReactOS系统中断描述符的格式KIDTENTRY结构体KIDTENTRY KIDTENTRY 数据结构KIDTENTRY定义了CPU对中断描述符的格式 // // …...

三、ElementPlus下拉搜索加弹窗组件的封装
近期产品提出了一个需求,要求一个form的表单里面的一个组件既可以下拉模糊搜索,又可以弹窗搜索,我就为这个封装了一个组件,下面看效果图。 效果大家看到了,下面就看组件封装和实现方法 第一步,组件封装&…...

androidStudio编译导致的同名.so文件冲突问题解决
files found with path lib/arm64-v8a/libserial_port.so from inputs: ...\build\intermediates\library_jni\debug\jni\arm64-v8a\libserial_port.so C:\Users\...\.gradle\caches\transforms-3\...\jni\arm64-v8a\XXX.so 解决方式如下: 1.将gradle缓存文件删…...

大学新生编程入门指南:如何选择编程语言与制定学习计划
大学新生编程入门指南:如何选择编程语言与制定学习计划 编程已成为当代大学生的必备技能,尤其是在信息技术高速发展的今天,编程能力不仅能帮助你在课堂学习中脱颖而出,更能为未来职业生涯打下坚实的基础。然而,面对如…...

SpringAI快速上手
一、导入依赖 镜像(导入maven依赖) <repositories><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
Leetcode33( 搜索旋转排序数组)
题目表述 整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

Matlab实现任意伪彩色图像可视化显示
Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中,如何展示好看的实验结果图像非常重要!!! 1、灰度原始图像 灰度图像每个像素点只有一个数值,代表该点的亮度(或…...