前端保留样式导入Excel编辑并导出

用x-spreadsheet开发时需要用到导入导出功能,找了一堆只找到sheetJs的demo(https://docs.sheetjs.com/docs/demos/grid/xs),在线体验(https://docs.sheetjs.com/xspreadsheet/);

但是sheetJs要保留样式需要升级pro版本,所以用Excel.js 做了一个简单的实现,基本满足目前的需求, 原理是导入时把excel的数据格式转换成x-spreadsheet的数据格式,导出时在反过来转换。

因为搜到的其他人的文章给了我很大的帮助,所以用sheetJs的demo代码整理修改成这个单文件的示例,如果有人遇到同样的问题,希望能帮你节省一两个小时的时间

效果如图:

仓库地址:github.com/changgeee/x...

在线体验:changgeee.github.io/x-spreadshe...

主要代码:

scss 复制代码
// 导入
let workbookData = [];
      wb.eachSheet((sheet, sheetIndex) => {
        // 构造x-data-spreadsheet 的 sheet 数据源结构
        let sheetData = { name: sheet.name, styles: [], rows: {}, merges: [] }
        // 收集合并单元格信息
        let mergeAddressData = []
        for (let mergeRange in sheet._merges) {
          sheetData.merges.push(sheet._merges[mergeRange].shortRange)
          let mergeAddress = {}
          // 合并单元格起始地址
          mergeAddress.startAddress = sheet._merges[mergeRange].tl
          // 合并单元格终止地址
          mergeAddress.endAddress = sheet._merges[mergeRange].br
          // Y轴方向跨度
          mergeAddress.YRange = sheet._merges[mergeRange].model.bottom - sheet._merges[mergeRange].model.top
          // X轴方向跨度
          mergeAddress.XRange = sheet._merges[mergeRange].model.right - sheet._merges[mergeRange].model.left
          mergeAddressData.push(mergeAddress)
        }
        sheetData.cols = {}
        for (let i = 0; i < sheet.columns.length; i++) {
          sheetData.cols[i.toString()] = {}
          if (sheet.columns[i].width) {
            sheetData.cols[i.toString()].width = sheet.columns[i].width * 8
          } else {
            // 默认列宽
            sheetData.cols[i.toString()].width = 100
          }
        }

        // 遍历行
        sheet.eachRow((row, rowIndex) => {
          sheetData.rows[(rowIndex - 1).toString()] = { cells: {} }
          if (row.height) {
            sheetData.rows[(rowIndex - 1).toString()].height = row.height * 1.5;
          }
          row.eachCell({ includeEmpty: true }, function (cell, colNumber) {
            let cellText = ''
            if (cell.value && cell.value.result) {
              // Excel 单元格有公式
              cellText = cell.value.result
            } else if (cell.value && cell.value.richText) {
              // Excel 单元格是多行文本
              for (let text in cell.value.richText) {
                // 多行文本做累加
                cellText += cell.value.richText[text].text
              }
            }
            else {
              // Excel 单元格无公式
              cellText = cell.value
            }

            // 背景色
            let backGroundColor = null
            if (cell.style.fill && cell.style.fill.fgColor && cell.style.fill.fgColor.argb) {
              backGroundColor = argb2hex(cell.style.fill.fgColor.argb)
            }

            if (backGroundColor) {
              cell.style.bgcolor = backGroundColor
            }
            // 字体颜色
            let fontColor = null
            if (cell.style.font && cell.style.font.color && cell.style.font.color.argb) {
              fontColor = argb2hex(cell.style.font.color.argb)
            }
            if (fontColor) {
              //console.log(fontColor)
              cell.style.color = fontColor
            }

            // 对齐 
            if (cell.style.alignment && cell.style.alignment.horizontal) {
              cell.style.align = cell.style.alignment.horizontal
              cell.style.valign = cell.style.alignment.vertical || "middle";
            }
            if (cell.style.border) {
              // excel border
              // {
              //   "left": {
              //       "style": "thin",
              //       "color": {
              //           "argb": "FF000000"
              //       }
              //   },
              //   "right": {
              //       "style": "thin",
              //       "color": {
              //           "argb": "FF000000"
              //       }
              //   },
              //   "top": {
              //       "style": "thin",
              //       "color": {
              //           "argb": "FF000000"
              //       }
              //   },
              //   "bottom": {
              //       "style": "thin",
              //       "color": {
              //           "argb": "FF000000"
              //       }
              //   }
              // }

              // xs border
              // {
              //   "bottom": [
              //       "thin",
              //       "#000"
              //   ],
              //   "top": [
              //       "thin",
              //       "#000"
              //   ],
              //   "left": [
              //       "thin",
              //       "#000"
              //   ],
              //   "right": [
              //       "thin",
              //       "#000"
              //   ]
              // }
              const { left, right, top, bottom } = cell.style.border;
              let bd = {
                top: _.isArray(top) ? top : [top.style, argb2hex(top.color.argb)],
                bottom: _.isArray(bottom) ? bottom : [bottom.style, argb2hex(bottom.color.argb)],
                left: _.isArray(left) ? left : [left.style, argb2hex(left.color.argb)],
                right: _.isArray(right) ? right : [right.style, argb2hex(right.color.argb)]
              }
              cell.style.border = bd;
            }
            //处理合并单元格
            let mergeAddress = _.find(mergeAddressData, function (o) { return o.startAddress == cell._address })
            if (mergeAddress) {
              // 遍历的单元格属于合并单元格
              if (cell.master.address != mergeAddress.startAddress) {
                // 不是合并单元格中的第一个单元格不需要计入数据源
                return
              }
              // 说明是合并单元格区域的起始单元格
              sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()] = { text: cellText, style: 0, merge: [mergeAddress.YRange, mergeAddress.XRange] }
              sheetData.styles.push(cell.style)
              //对应的style存放序号
              sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()].style = sheetData.styles.length - 1
            }
            else {
              // 非合并单元格
              sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()] = { text: cellText, style: 0 }
              //解析单元格,包含样式
              sheetData.styles.push(cell.style)
              //对应的style存放序号
              sheetData.rows[(rowIndex - 1).toString()].cells[(colNumber - 1).toString()].style = sheetData.styles.length - 1
            }
          });
        })
        workbookData.push(sheetData)
      })
      return workbookData;

这种方式只适合轻量化的web端表格编辑,如果需求复杂或者使用频率高,建议使用其他技术或商业库,如:

相关推荐
taller_200017 小时前
【012】数据类型入门: Excel 数据到 Python 值的转换
python·excel·pie·py
彦3 天前
Excel实用技巧笔记
excel
Logic1013 天前
Excel流程控制if函数,IF+数组轻松解决复杂判断问题
excel·流程控制·条件判断·if·excel函数·if函数·数组公式
跨境猫小妹4 天前
跨境电商订单导出CSV用Excel打开后订单号变成科学计数法如何定位并修复
excel·跨境电商·营销策略
weixin_440401694 天前
大数据技术-数据分析(EXcel+Python数据处理+SQL)
大数据·python·数据分析·excel
jaysee-sjc5 天前
【苍穹外卖】Day12:工作台数据看板与 Excel 报表导出
java·开发语言·数据库·spring boot·spring·微信·excel
ms365copilot5 天前
Copilot 代理开始进入Word、Excel、PowerPoint,不再只是“问一句答一句”
word·excel·copilot
wuyk5555 天前
PyQt6-第14讲-数据库与Excel双向同步
数据库·python·ui·excel·pyqt
qq_364682155 天前
AI如何将录音内容自动整理成Excel清单
人工智能·excel
Logic1015 天前
Excel信息+照片文件夹,自动合成word格式化文档
word·excel·办公自动化·邮件合并·office技巧·自动插入图片·插入域