LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

vue 下使用 exceljs + x-spreadsheet 带样式导出Excel

admin
2024年6月12日 12:17 本文热度 1088

上一篇,这次加入的是从x-speadsheet导出Excel,并且带有x-speadsheet中的样式,重点关注 exportExcel 这个方法,我加入了 tinycolor 这个库用来翻译颜色值,值得注意的是, exceljs的颜色值是 argb 不是 rgba,一定不要弄混了a 是代表的透明度放在最前面

<template>

  <div>

    <div>

      <input

        type="file"

        @change="loadExcelFile"

        accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel"

      />

      <button @click="exportJson">导出JSON</button>

      <button @click="exportExcel">导出xlsx</button>

    </div>

    <!--web spreadsheet组件-->

    <div ref="sheetContainer" id="x-spreadsheet-demo"></div>

  </div>

</template>

 

<script>

// 在spreadsheet.js中初始化json数据 https://blog.csdn.net/CBGCampus/article/details/125366246

// 带样式导出 https://blog.csdn.net/weixin_42302145/article/details/121476579

// 引入依赖包

import Spreadsheet from "x-data-spreadsheet";

import zhCN from 'x-data-spreadsheet/src/locale/zh-cn'

import _ from "lodash";

import * as XLSX from "xlsx";

import * as Excel from 'exceljs/dist/exceljs'

import * as tinycolor from "tinycolor2";

export default {

  name: "xspreadsheet-demo",

  data() {

    return {

      xs: null,

      jsondata: {

        type: "",

        label: "",

      },

    };

  },

  mounted() {

    this.init();

  },

  methods: {

    init() {

      //设置中文

      Spreadsheet.locale("zh-cn", zhCN);

      this.xs = new Spreadsheet("#x-spreadsheet-demo", {

        mode: "edit",

        showToolbar: true,

        showGrid: true,

        showContextmenu: true,

        showBottomBar: true,

        view: {

          height: () => (_.isNumber(this.$refs.sheetContainer.offsetHeight) && this.$refs.sheetContainer)? this.$refs.sheetContainer.offsetHeight : 0,

          width: () => (_.isNumber(this.$refs.sheetContainer.offsetWidth) && this.$refs.sheetContainer)? this.$refs.sheetContainer.offsetWidth : 0 ,

        },

        formats: [],

        fonts: [],

        formula: [],

        row: {

          len: 9999,

          height: 25,

        },

        col: {

          len: 26,

          width: 100,

          indexWidth: 60,

          minWidth: 60,

        },

        style: {

          bgcolor: "#ffffff",

          align: "left",

          valign: "middle",

          textwrap: false,

          textDecoration: "normal",

          strikethrough: false,

          color: "#0a0a0a",

          align: "center",

          valign: "middle",

          font: {

            name: "Helvetica",

            size: 10,

            bold: false,

            italic: false,

          }

        },

      })

        .loadData([

          { 

            name: "调配单变量",

            styles : [

              {

              /*表头样式*/

              bgcolor: '#f4f5f8',

              textwrap: true,

              color: '#900b09',

              border: {

                top: ['thin', '#1E1E1E'],

                bottom: ['thin', '#1E1E1E'],

                right: ['thin', '#1E1E1E'],

                left: ['thin', '#1E1E1E'],

              },

              font: { 

                bold: true 

              }

            }

            ], 

            rows: [

              { cells: [{ 

                 text:'序号',

                 editable: false,

                 style: 0

                },{ 

                 text:'订单号',

                 editable: false,

                 style: 0

                }] 

              }

            ], 

            merges:[] 

          }

        ])

        .change((cdata) => {

        });

 

      this.xs

        .on("cell-selected", (cell, ri, ci) => {

          console.log("cell:", cell, ", ri:", ri, ", ci:", ci);

        })

        .on("cell-edited", (text, ri, ci) => {

          console.log("text:", text, ", ri: ", ri, ", ci:", ci);

      });

    },

    transRgba(rgba) {

      let result = '';

      let reg = /\(.*\)/;  //字符串匹配括号内的子串

      let arr = []

      result = reg.exec(rgba)[0];  // 截取'(255,255,255,0.6)'

      result = result.substr(1, result.length - 2); //截取十进制的'255,255,255,0.6'

      arr = result.split(','); //字符串切割 ['255','255','255','0.6']

      for (let i = 0; i < arr.length; i++) {

          arr[i] = parseFloat(arr[i]); //字符串类型转为浮点数类型

          if (i == arr.length - 1) {  //对于最后一个透明度数据,需要先将0-1的数值*255

              arr[i] = arr[i] * 255;

          }

          arr[i] = trans10to16(arr[i]);

      }

      return arr.join('');

    },

    // 导入excel

    loadExcelFile(e) {

      const wb = new Excel.Workbook();

      const reader = new FileReader()

      reader.readAsArrayBuffer(e.target.files[0])

      reader.onload = () => {

        const buffer = reader.result;

        // 微软的 Excel ColorIndex 一个索引数字对应一个颜色

        const indexedColors = [

          '000000',

          'FFFFFF',

          'FF0000',

          '00FF00',

          '0000FF',

          'FFFF00',

          'FF00FF',

          '00FFFF',

          '000000',

          'FFFFFF',

          'FF0000',

          '00FF00',

          '0000FF',

          'FFFF00',

          'FF00FF',

          '00FFFF',

          '800000',

          '008000',

          '000080',

          '808000',

          '800080',

          '008080',

          'C0C0C0',

          '808080',

          '9999FF',

          '993366',

          'FFFFCC',

          'CCFFFF',

          '660066',

          'FF8080',

          '0066CC',

          'CCCCFF',

          '000080',

          'FF00FF',

          'FFFF00',

          '00FFFF',

          '800080',

          '800000',

          '008080',

          '0000FF',

          '00CCFF',

          'CCFFFF',

          'CCFFCC',

          'FFFF99',

          '99CCFF',

          'FF99CC',

          'CC99FF',

          'FFCC99',

          '3366FF',

          '33CCCC',

          '99CC00',

          'FFCC00',

          'FF9900',

          'FF6600',

          '666699',

          '969696',

          '003366',

          '339966',

          '003300',

          '333300',

          '993300',

          '993366',

          '333399',

          '333333',

        ];

        wb.xlsx.load(buffer).then(workbook => {

          let workbookData = []

          console.log(workbook)

          workbook.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) {

                // 不知道为什么从 exceljs 读取的宽度显示到 x-data-spreadsheet 特别小, 这里乘以8

                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: {} }         

              //includeEmpty = false 不包含空白单元格

              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) {

                  // 8位字符颜色先转rgb再转16进制颜色

                  backGroundColor = ((val) => {

                    val = val.trim().toLowerCase();  //去掉前后空格

                    let color = {};

                    try {

                        let argb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(val);

                        color.r = parseInt(argb[2], 16);

                        color.g = parseInt(argb[3], 16);

                        color.b = parseInt(argb[4], 16);

                        color.a = parseInt(argb[1], 16) / 255;

                        return tinycolor(`rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`).toHexString()

                    } catch (e) {

                      console.log(e)

                    }

                  })(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) {

                  // 8位字符颜色先转rgb再转16进制颜色

                  fontColor = ((val) => {

                    val = val.trim().toLowerCase();  //去掉前后空格

                    let color = {};

                    try {

                        let argb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(val)

                        color.r = parseInt(argb[2], 16);

                        color.g = parseInt(argb[3], 16);

                        color.b = parseInt(argb[4], 16);

                        color.a = parseInt(argb[1], 16) / 255;

                        return tinycolor(`rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`).toHexString()

                    } catch (e) {

                      console.log(e)

                    }

                  })(cell.style.font.color.argb)

                }

                if(fontColor) {

                  //console.log(fontColor)

                  cell.style.color = fontColor

                }

                //************************************************************************ */

 

                // exceljs 对齐的格式转成 x-date-spreedsheet 能识别的对齐格式 

                if(cell.style.alignment && cell.style.alignment.horizontal) {

                  cell.style.align = cell.style.alignment.horizontal

                  cell.style.valign = cell.style.alignment.vertical

                }

 

                //处理合并单元格

                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)

          })

          this.xs.loadData(workbookData);

        })

      }

    },

    // 导出excel

    exportExcel() {

      const exceljsWorkbook = new Excel.Workbook();

      exceljsWorkbook.modified = new Date();

      this.xs.getData().forEach(function (xws) {

        let rowobj = xws.rows;

        // 构造exceljs文档结构

        const exceljsSheet = exceljsWorkbook.addWorksheet(xws.name)

        // 读取列宽

        let sheetColumns = []

        let colIndex = 0

        for(let col in xws.cols){

          if(xws.cols[col].width) {

            sheetColumns.push({ header: colIndex + '', key: colIndex + '', width: xws.cols[col].width / 8})

          }

          colIndex ++

        }

        exceljsSheet.columns = sheetColumns

        for (let ri = 0; ri < rowobj.len; ++ri) {

          let row = rowobj[ri];

          if (!row) continue;

          // 构造exceljs的行(如果尚不存在,则将返回一个新的空对象)

          const exceljsRow = exceljsSheet.getRow(ri + 1)

          Object.keys(row.cells).forEach(function (k) {

            let idx = +k;

            if (isNaN(idx)) return;

            const exceljsCell = exceljsRow.getCell(Number(k) + 1)

            exceljsCell.value = row.cells[k].text

            //console.log(row.cells[k])

            //console.log(xws.styles[row.cells[k].style])

            // 水平对齐方式

            if(xws.styles[row.cells[k].style] && xws.styles[row.cells[k].style].alignment) {

              if(xws.styles[row.cells[k].style].alignment.vertical) {

                if(exceljsCell.alignment == undefined) {

                  exceljsCell.alignment = {}

                }

                exceljsCell.alignment.vertical = xws.styles[row.cells[k].style].alignment.vertical

              }

            }

            // 垂直对齐方式

            if(xws.styles[row.cells[k].style] && xws.styles[row.cells[k].style].valign) {

              if(!exceljsCell.alignment == undefined) {

                exceljsCell.alignment = {}

              }

              exceljsCell.alignment.horizontal = xws.styles[row.cells[k].style].valign

            }

            // 边框

            exceljsCell.border = xws.styles[row.cells[k].style].border

            // 背景色

            if(xws.styles[row.cells[k].style].bgcolor) {

              let rgb = tinycolor(xws.styles[row.cells[k].style].bgcolor).toRgb()

              let rHex = parseInt(rgb.r).toString(16).padStart(2, '0')

              let gHex = parseInt(rgb.g).toString(16).padStart(2, '0')

              let bHex = parseInt(rgb.b).toString(16).padStart(2, '0')

              let aHex = parseInt(rgb.a).toString(16).padStart(2, '0')

              let _bgColor = aHex + rHex + gHex + bHex

              // 设置exceljs背景色

              exceljsCell.fill = {

                type: 'pattern',

                pattern:'solid',

                fgColor:{argb: _bgColor}

              }

            }

            // 字体

            exceljsCell.font = xws.styles[row.cells[k].style].font

            // 字体颜色

            if(xws.styles[row.cells[k].style].color) {          

              let rgb = tinycolor(xws.styles[row.cells[k].style].color).toRgb()

              let rHex = parseInt(rgb.r).toString(16).padStart(2, '0')

              let gHex = parseInt(rgb.g).toString(16).padStart(2, '0')

              let bHex = parseInt(rgb.b).toString(16).padStart(2, '0')

              let aHex = parseInt(rgb.a).toString(16).padStart(2, '0')

              let _fontColor =  aHex + rHex + gHex + bHex

              exceljsCell.font.color = { argb: _fontColor }

            }

            // 合并单元格

            if(row.cells[k].merge){

              // 开始行

              let startRow = ri + 1

              // 结束行,加上Y轴跨度

              let endRow = startRow + row.cells[k].merge[0]

              // 开始列

              let startColumn = Number(k) + 1

              // 结束列,加上X轴跨度

              let endColumn = startColumn + row.cells[k].merge[1]

              // 按开始行,开始列,结束行,结束列合并

              exceljsSheet.mergeCells(startRow,startColumn,endRow,endColumn);

            }

          });

        }

      });

      // writeBuffer 把写好的excel 转换成 ArrayBuffer 类型

      exceljsWorkbook.xlsx.writeBuffer().then((data) => {

        const link = document.createElement('a');

        // Blob 实现下载excel

        const blob = new Blob([data], {

          type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8',

        });

        link.href = window.URL.createObjectURL(blob);

        link.download = '调配单变量配置.xlsx';

        link.click();

      });

    },

    // 导出为 JSON

    exportJson() {

      let sheetsData = this.xs.getData();

      let rows = Object.entries(sheetsData[0].rows);

      let objectProperties = [

        "Index",

        "OrderIndex",

        "OrderNo",

        "ProductName",

        "OrderStatus",

      ];

      let jsonData = [];

      // 遍历数据,跳过第一行表头

      for (let i = 1; i < rows.length; i++) {

        if (rows[i] && rows[i][1] && rows[i][1].cells) {

          let row = Object.entries(rows[i][1].cells);

          // 构造行对象

          let JsonRow = {

            Index: null,

            OrderIndex: null,

            OrderNo: null,

            ProductName: null,

            OrderStatus: null,

          };

          for (let k = 0; k < row.length; k++) {

            let cells = row[k];

            JsonRow[objectProperties[k]] = cells[1].text;

          }

          jsonData.push(JsonRow);

        }

      }

      console.log(jsonData);

    },

    stox(wb) {

      var out = [];

      wb.SheetNames.forEach(function (name) {

        var o = { name: name, rows: {} };

        var ws = wb.Sheets[name];

        var aoa = XLSX.utils.sheet_to_json(ws, { raw: false, header: 1 });

        aoa.forEach(function (r, i) {

          var cells = {};

          r.forEach(function (c, j) {

            cells[j] = { text: c };

          });

          o.rows[i] = { cells: cells };

        });

        out.push(o);

      });

      return out;

    },

    fixData(data) {

      var o = "",

        l = 0,

        w = 10240;

      for (; l < data.byteLength / w; ++l)

        o += String.fromCharCode.apply(

          null,

          new Uint8Array(data.slice(l * w, l * w + w))

        );

      o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w)));

      return o;

    },

  },

};

</script>

<style scoped>

.container {

  width: 100%;

  height: 100%;

  display: flex;

  flex-direction: column;

  .toolbar {

    width: 100%;

    height: 50px;

  }

  .grid {

    width: 100%;

    height: calc(100% - 80px);

  }

  /deep/ .x-spreadsheet-toolbar {

    padding: 0px;

    width: calc(100% - 2px) !important;

  }

}

</style>


该文章在 2024/6/12 12:17:18 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved