当前位置: 首页 > news >正文

基于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实现表格预览、导出、带公式单元格渲染功能&#xff0c;在这里我们在html中实现&#xff0c;当然也可以在vue、react等框架中使用npm下载导入依赖文件。 Handsontable官方文档 一、开发前的准备引入相关依赖库 <!DOCTYPE ht…...

重学SpringBoot3-集成Redis(十三)之点排行榜实现

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Redis&#xff08;十三&#xff09;之点排行榜实现 1. 为什么选择 Redis 来实现排行榜&#xff1f;2. 项目环境准备2.1. 添加依赖2.2. 配置 Redis 连…...

Java 中方法参数传递的陷阱

前言 在编程过程中&#xff0c;我们经常会遇到一些看似简单却容易出错的问题。本文将通过一个具体的例子&#xff0c;探讨 Java 中方法参数传递的陷阱&#xff0c;并提供详细的解决方法。希望这篇文章能帮助你在未来的开发中避免类似的错误。 问题背景 假设我们的任务是计算…...

哪家云电脑便宜又好用?ToDesk云电脑、顺网云、达龙云全方位评测

陈老老老板&#x1f934; &#x1f9d9;‍♂️本文专栏&#xff1a;生活&#xff08;主要讲一下自己生活相关的内容&#xff09;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f9d9;‍♂️本文简述&#xff1a;讲一下市面上云电脑的对比。 &#x1f9d9;‍♂️上一篇文…...

【汇编语言】寄存器(内存访问)(三)—— 字的传送

文章目录 前言1. 字的传送2. 问题一3. 问题一的分析与解答4. 问题二5. 问题二的分析与解答结语 前言 &#x1f4cc; 汇编语言是很多相关课程&#xff08;如数据结构、操作系统、微机原理&#xff09;的重要基础。但仅仅从课程的角度出发就太片面了&#xff0c;其实学习汇编语言…...

6 机器学习之应用现状

在过去二十年中&#xff0c;人类收集、存储、传输、处理数据的能力取得了飞速提升&#xff0c;人类社会的各个角落都积累了大量数据&#xff0c;亟需能有效地对数据进行分析利用的计算机算法&#xff0c;而机器学习恰顺应了大时代的这个迫切需求&#xff0c;因此该学科领域很自…...

相似度为 K 的字符串

题目链接 相似度为 K 的字符串 题目描述 注意 s1和s2只包含集合 {‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’} 中的小写字母s2是s1的一个字母异位词 解答思路 可以深度优先遍历交换字母使得s1和s2尽可能接近&#xff0c;基本思路是&#xff1a;设定一个指针idx指向s1和s2的…...

[云] Project Analysis

项目要求分析&#xff1a; 开放性选题&#xff1a; 主题范围&#xff1a;任何与云计算系统相关的主题。项目类型&#xff1a;可以是技术、商业或研究项目。团队规模&#xff1a;最多可组成三人小组。 示例主题&#xff1a; 分析公共云数据&#xff1a;例如&#xff0c;AWS公共数…...

腾讯六宫格本地识别,本地模型识别,腾讯六图识别

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

Transformer图解以及相关的概念

前言 transformer是目前NLP甚至是整个深度学习领域不能不提到的框架&#xff0c;同时大部分LLM也是使用其进行训练生成模型&#xff0c;所以transformer几乎是目前每一个机器人开发者或者人工智能开发者不能越过的一个框架。接下来本文将从顶层往下去一步步掀开transformer的面…...

Nginx缓存静态文件

在Python项目中&#xff0c;通过Nginx缓存静态文件&#xff08;如CSS、JS、图片等&#xff09;&#xff0c;可以有效提升网页的加载性能。Nginx可以帮助你缓存静态资源&#xff0c;减少服务器负担&#xff0c;并加速页面加载。 1. 配置Nginx缓存静态文件 首先&#xff0c;你需…...

【隐私计算】隐语HEU同态加密算法解读

HEU: 一个高性能的同态加密算法库&#xff0c;提供了多种 PHE 算法&#xff0c; 包括ZPaillier、FPaillier、IPCL、Damgard Jurik、DGK、OU、EC ElGamal 以及基于FPGA和GPU硬件加速版本的Paillier版本。 本文我们会基于GPU运行HEU Docker容器&#xff0c;编译打包GPaillier并测…...

用C#实现互斥操作

1、传统的lock lock简单易用&#xff0c;适合大多数场景&#xff0c;但在高竞争用情况下可能会导致线程阻塞&#xff1b; Object obj new object(); void method1(){lock (obj){// 进行互斥操作}}2、SpinLock SpinLock在低延迟情况下更有效&#xff0c;因为SpinLock会在忙等…...

【黑马点评优化】之使用Caffeine+Redis实现应用级二层缓存

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

CEEMDAN +组合预测模型(BiLSTM-Attention + ARIMA)

往期精彩内容&#xff1a; 时序预测&#xff1a;LSTM、ARIMA、Holt-Winters、SARIMA模型的分析与比较 全是干货 | 数据集、学习资料、建模资源分享&#xff01; EMD、EEMD、FEEMD、CEEMD、CEEMDAN的区别、原理和Python实现&#xff08;一&#xff09;EMD-CSDN博客 EMD、EEM…...

2.1.ReactOS系统中断描述符的格式KIDTENTRY结构体

2.&#xff11;.ReactOS系统中断描述符的格式KIDTENTRY结构体 2.&#xff11;.ReactOS系统中断描述符的格式KIDTENTRY结构体 文章目录 2.&#xff11;.ReactOS系统中断描述符的格式KIDTENTRY结构体KIDTENTRY KIDTENTRY 数据结构KIDTENTRY定义了CPU对中断描述符的格式 // // …...

三、ElementPlus下拉搜索加弹窗组件的封装

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

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 解决方式如下&#xff1a; 1.将gradle缓存文件删…...

大学新生编程入门指南:如何选择编程语言与制定学习计划

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

SpringAI快速上手

一、导入依赖 镜像&#xff08;导入maven依赖&#xff09; <repositories><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

使用SSE解决获取状态不一致问题

使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件&#xff0c;这个上传文件是整体功能的一部分&#xff0c;文件在上传的过程中…...

绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化

iOS 应用的发布流程一直是开发链路中最“苹果味”的环节&#xff1a;强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说&#xff0c;这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发&#xff08;例如 Flutter、React Na…...

​​企业大模型服务合规指南:深度解析备案与登记制度​​

伴随AI技术的爆炸式发展&#xff0c;尤其是大模型&#xff08;LLM&#xff09;在各行各业的深度应用和整合&#xff0c;企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者&#xff0c;还是积极拥抱AI转型的传统企业&#xff0c;在面向公众…...