精华内容
下载资源
问答
  • xlsx.core.min.js,xlsx.js下载,参考下载地址https://cdn.bootcss.com/xlsx/0.12.7/xlsx.core.min.js
  • Xlsx.js 插件

    2020-07-15 14:37:38
    Xlsx.js和xlsx.full.js
  • XLSX.js 是一个用来转换 BASE64 编码的 XLSX 文件数据为 JavaScript 对象,也支持 JavaScript 对象到 XLSX 数据的转换。 标签:xlsxjs
  • 前端下载excel插件 xlsx.js
  • 关于xlsx.js的使用

    2020-10-19 12:30:19
    解决:使用xlsx.js可实现以上两种方式的Excel表上传下载 PS:使用后端接口进行上传时,开发者工具的network可看到传递的参数:file(brinary) 具体使用,可参考以后几篇文章: 1、...
    展开全文
  • 前端使用xlsx.js导出有复杂表头的excel 参考: Vue项目中使用xlsx-style导出有格式的表格,这个博客中的实现是针对单个excel手写合并样式,比较繁琐。 我这里实现了根据table的表头数据结构,自动生成和页面上相同...

    前端使用xlsx.js导出有复杂表头的excel

    参考: Vue项目中使用xlsx-style导出有格式的表格,这个博客中的实现是针对单个excel手写合并样式,比较繁琐。

    我这里实现了根据table的表头数据结构,自动生成和页面上相同的excel表格。

    1、简介

    需求要导出表格数据到excel,本来想在后端用poi来弄,但是回想起之前同事被poi支配的恐惧,我搜了一下还是觉得在前端使用xlsx.js来搞,这样比较简单。虽然没有要求表格样式,但是我觉得还是弄成多级表头比较好看。

    2、效果

    • 页面导出数据:在线预览

      图片1

      8

    • 根据表头结构,自动合并excel单元格,生成的文件如下

      图片2

      image-20210125162616238

    3、实现

    • 请先阅读上面连接中的博客,我这里只是增强,不再重复赘述;
    • 其中的xlsx.full.min.js依赖需要按照参考博客中的方式下载,安装到本地;或者打开步骤2的预览,f12 找到对应加载的资源 copy 到你本地。

    1、表头结构

    表头的包含关系可用json很好的表达出来,其中子表头放在父表头的child属性中,依次类推,无限嵌套

    [
      {
        name: '姓名',
        prop: 'name',
      },
      {
        name: '专业技能',
        child: [
          {
            name: '前端',
            child: [
              {
                name: 'JavaScript',
                prop: 'js'
              },
              {
                name: 'CSS',
                prop: 'css'
              }
            ]
          },
          {
            name: '后端',
            child: [
              {
                name: 'java',
                child: [
                  {
                    name: 'nio',
                    prop: 'nio'
                  },
                  {
                    name: '基础',
                    prop: 'basic'
                  }
                ]
              },
              {
                name: '框架',
                child: [
                  {
                    name: 'SpringBoot',
                    prop: 'springboot'
                  },
                  {
                    name: 'MyBatis',
                    prop: 'mybatis'
                  }
                ]
              }
            ]
          },
        ]
      },
    ]
    

    2、计算合并数据

    关键点在于如何根据表头的步骤3.1里的json结构,计算出xlsx所需要的marges属性;这一步比较复杂

    1)、xlsxmargs属性介绍

    先看一下下面这个json串,这是xlsx需要的合并数据,用来告诉它从哪合并到哪。

    • s表示start,意思是需要合并的起始位置
    • e表示end,意思是需要合并的结束位置
    • r表示row,行下标,从0开始
    • c表示column,列下标,从0开始
    [
      {"s":{"r":0,"c":1},"e":{"r":0,"c":6}},
      {"s":{"r":1,"c":1},"e":{"r":1,"c":2}},
    ]
    

    所以上面的json中的第一条数据表示,从第0行的第1列开始(包括该列),合并到第0行的第6列(包括该列)。效果就是图片2里展示的表格的专业技能表头。第二行就是前端表头的合并属性。

    2)、根据表头结构计算出合并属性

    现在很好办了,根据步骤3.1中json表头,生成步骤3.2.1里面的合并属性。由于代码太多,我这里就直接说一下思路吧

    1. 将原始的json转换成数组,由于我们的表头要么只有横向列合并,要么就是纵向行合并。所以先将数据处理成下面这种形式。

      这部分的逻辑实现在buildHeader(revealList)方法。可以看到这里的一个数组对应excel表格中的一行,其中需要合并的行或列使用占位符填充,方便后面计算。

      image-20210125161352085

    2. 根据数组计算出合并样式

      这部分的逻辑实现在doMerges(arr),到这一步得出的合并样式可以直接使用了。可以说整篇文章的核心就是这两步。

      [
        {"s":{"r":0,"c":1},"e":{"r":0,"c":6}},
        {"s":{"r":1,"c":1},"e":{"r":1,"c":2}},
        {"s":{"r":1,"c":3},"e":{"r":1,"c":6}},
        {"s":{"r":2,"c":3},"e":{"r":2,"c":4}},
        {"s":{"r":2,"c":5},"e":{"r":2,"c":6}},
        {"s":{"r":0,"c":0},"e":{"r":3,"c":0}},
        {"s":{"r":2,"c":1},"e":{"r":3,"c":1}},
        {"s":{"r":2,"c":2},"e":{"r":3,"c":2}}
      ]
      

    4、给表头加背景色和边框

    ​ 可以看到图二的表头有背景色和边框,由于使用npm下载手动copy过来的源码里没有aoa_to_sheet方法,所以从github复制了一份。然后按照自己的逻辑修改了一下,这个方法的主要作用就是将我们生成的二位数组数据,构造成xlsx需要的cell单元格对象。

    我们可以在这个cell对象上做很多事情,例如给他加上单元格样式;

    1)、xlsxcell对象介绍

    进入文档Ctrl+F搜索Cell Object关键字,这里简单介绍下我们需要知道的几个属性

    1. v属性,就是放我们的原始数据的

    2. s属性,就是style,描述单元格样式的属性;我们主要对他进行设置,参数说明详见这里,进去搜索关键字Cell Styles

      // 示例
      const cell = {
      	v: '123',
      	s:{
      		fill:{patternType:'solid'},
      		font:{italic:true},
          alignment:{wrapText:true}
      	}
      }
      

      s属性文档

      Style Attribute Sub Attributes Values
      fill patternType "solid" or "none"
      fgColor COLOR_SPEC
      bgColor COLOR_SPEC
      font name "Calibri" // default
      sz "11" // font size in points
      color COLOR_SPEC
      bold true or false
      underline true or false
      italic true or false
      strike true or false
      outline true or false
      shadow true or false
      vertAlign true or false
      numFmt "0" // integer index to built in formats, see StyleBuilder.SSF property
      "0.00%" // string matching a built-in format, see StyleBuilder.SSF
      "0.0%" // string specifying a custom format
      "0.00%;\\(0.00%\\);\\-;@" // string specifying a custom format, escaping special characters
      "m/dd/yy" // string a date format using Excel’s format notation
      alignment vertical "bottom" or "center" or "top"
      horizontal "left" or "center" or "right"
      wrapText trueor false
      readingOrder 2 // for right-to-left
      textRotation Number from 0 to 180 or 255 (default is 0)
      90 is rotated up 90 degrees
      45 is rotated up 45 degrees
      135 is rotated down 45 degrees
      180 is rotated down 180 degrees
      255 is special, aligned vertically
      border top { style: BORDER_STYLE, color: COLOR_SPEC }
      bottom { style: BORDER_STYLE, color: COLOR_SPEC }
      left { style: BORDER_STYLE, color: COLOR_SPEC }
      right { style: BORDER_STYLE, color: COLOR_SPEC }
      diagonal { style: BORDER_STYLE, color: COLOR_SPEC }
      diagonalUp true or false
      diagonalDown true or false

    2)、aoa_to_sheet 方法构造cell对象

    1. 入参data如下图所示,包含了表头部分和数据部分;这个是在步骤3.2.2经过doMerges(arr)方法加工后去除占位符后的数据。

    image-20210125165554286

    1. 入参headerRows,表示表头占用几行,为了给表头加样式
    2. aoa_to_sheet 方法
    aoa_to_sheet(data, headerRows) {
      const ws = {};
      const range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } };
      // 遍历步骤1里面的二维数组数据
      for (let R = 0; R !== data.length; ++R) {
        for (let C = 0; C !== data[R].length; ++C) {
          if (range.s.r > R) { range.s.r = R; }
          if (range.s.c > C) { range.s.c = C; }
          if (range.e.r < R) { range.e.r = R; }
          if (range.e.c < C) { range.e.c = C; }
          /// 构造cell对象,对所有excel单元格使用如下样式
          const cell = {
            v: data[R][C] || '',
            s: {
              font: { name: "宋体", sz: 11, color: { auto: 1 } },
              // 单元格对齐方式
              alignment: {
                /// 自动换行
                wrapText: 1,
                // 水平居中
                horizontal: "center",
                // 垂直居中
                vertical: "center"
              }
            }
          };
          // 头部列表加边框
          if (R < headerRows) {
            cell.s.border = {
              top: { style: 'thin', color: { rgb: "000000" } },
              left: { style: 'thin', color: { rgb: "000000" } },
              bottom: { style: 'thin', color: { rgb: "000000" } },
              right: { style: 'thin', color: { rgb: "000000" } },
            };
            // 给个背景色
            cell.s.fill = {
              patternType: 'solid',
              fgColor: { theme: 3, "tint": 0.3999755851924192, rgb: 'DDD9C4' },
              bgColor: { theme: 7, "tint": 0.3999755851924192, rgb: '8064A2' }
            }
          }
          const cell_ref = XLSX.utils.encode_cell({ c: C, r: R });
          // 该单元格的数据类型,只判断了数值类型、布尔类型,字符串类型,省略了其他类型
          // 自己可以翻文档加其他类型
          if (typeof cell.v === 'number') {
            cell.t = 'n';
          } else if (typeof cell.v === 'boolean') {
            cell.t = 'b';
          } else {
            cell.t = 's';
          }
          ws[cell_ref] = cell;
        }
      }
      if (range.s.c < 10000000) {
        ws['!ref'] = XLSX.utils.encode_range(range);
      }
      return ws;
    }
    

    5、固定表头

    将表头固定,使用!freeze属性

    xSplit代表固定哪一列,设置为0就不生效了,不知道为什么,必须要固定一列

    ySplit代表固定前几行,根据上面计算的参数headerRows

    ws["!freeze"] = {
      xSplit: "1",// 第一列
      ySplit: "" + headerRows,// headerRows是表头有几行
      topLeftCell: "B" + (headerRows + 1),
      activePane: "bottomRight",
      state: "frozen",
    };
    

    6、单元格列宽

    使用!cols参数来表示每一列的宽度;

    文档原文:

    array of column properties objects. Column widths are actually stored in files in a normalized manner, measured in terms of the “Maximum Digit Width” (the largest width of the rendered digits 0-9, in pixels). When parsed, the column objects store the pixel width in the wpx field, character width in the wch field, and the maximum digit width in the MDW field.

    // 第一列100像素,第二列200像素...依次类推
    ws['!cols'] = [{wpx:100},{wpx:200}];
    

    7、效率

    我这里只测试了大概2000条数据的导出,不算网络开销,0.5秒以内,关键还是看客户端的电脑配置。

    7、全部源码

    其实工作中的需求比这个要复杂,不仅要合并表头,还要根据后端返回的有父子关系的json数据来合并这些单元格。同时还要对父数据下面的子数据集合做合计,输出到excel表格中。在博客里我把这一部分省略掉了,但其实源码里面还有部分逻辑没删。如果后面有人需要,我会把这部分逻辑写出来。

    全部源码如下:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <title>多级表头excel导出</title>
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    </head>
    
    <body>
      <div id="app">
        <el-button @click="exportData">导出</el-button>
        <el-table @selection-change="handleSelectionChange" :data="list" style="width: 100%" size="mini">
          <el-table-column type="selection" width="55"></el-table-column>
          <el-table-column label="姓名" prop="name" align="center"></el-table-column>
          <el-table-column label="专业技能" align="center">
            <el-table-column label="前端" align="center">
              <el-table-column label="JavaScript" prop="js" align="center"></el-table-column>
              <el-table-column label="CSS" prop="css" align="center"></el-table-column>
            </el-table-column>
            <el-table-column label="后端" align="center">
              <el-table-column label="java" align="center">
                <el-table-column label="nio" prop="nio" align="center"></el-table-column>
                <el-table-column label="基础" prop="basic" align="center"></el-table-column>
              </el-table-column>
              <el-table-column label="框架" align="center">
                <el-table-column label="SpringBoot" prop="springboot" align="center"></el-table-column>
                <el-table-column label="MyBatis" prop="mybatis" align="center"></el-table-column>
              </el-table-column>
            </el-table-column>
          </el-table-column>
        </el-table>
      </div>
      <script src="https://unpkg.com/vue/dist/vue.js"></script>
      <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    
      <script src="lib/style/xlsx.full.min.js"></script>
      <script>
        const app = new Vue({
          el: "#app",
          data() {
            return {
              selectionData: [],
              list: [
                { name: '张三', js: '熟练', css: '一般', nio: '了解', basic: '精通', springboot: '熟练', mybatis: '了解' },
                { name: '张三', js: '熟练', css: '一般', nio: '了解', basic: '精通', springboot: '熟练', mybatis: '了解' },
                { name: '张三', js: '熟练', css: '一般', nio: '了解', basic: '精通', springboot: '熟练', mybatis: '了解' },
                { name: '张三', js: '熟练', css: '一般', nio: '了解', basic: '精通', springboot: '熟练', mybatis: '了解' },
              ],
              revealList: [
                {
                  name: '姓名',
                  prop: 'name',
                },
                {
                  name: '专业技能',
                  child: [
                    {
                      name: '前端',
                      child: [
                        {
                          name: 'JavaScript',
                          prop: 'js'
                        },
                        {
                          name: 'CSS',
                          prop: 'css'
                        }
                      ]
                    },
                    {
                      name: '后端',
                      child: [
                        {
                          name: 'java',
                          child: [
                            {
                              name: 'nio',
                              prop: 'nio'
                            },
                            {
                              name: '基础',
                              prop: 'basic'
                            }
                          ]
                        },
                        {
                          name: '框架',
                          child: [
                            {
                              name: 'SpringBoot',
                              prop: 'springboot'
                            },
                            {
                              name: 'MyBatis',
                              prop: 'mybatis'
                            }
                          ]
                        }
                      ]
                    },
                  ]
                },
              ],
            }
          },
          methods: {
            handleSelectionChange(selection) {
              this.selectionData = selection
            },
            exportData() {
              if (!Array.isArray(this.selectionData) || this.selectionData.length < 1) {
                this.$message({ type: 'error', message: '请选择需要导出的数据!' })
                return;
              }
              let sheetName = 'xlsx复杂表格导出demo'
              // excel表头
              let excelHeader = this.buildHeader(this.revealList);
              // 头部行数,用来固定表头
              let headerRows = excelHeader.length;
              // 提取数据
              let dataList = this.extractData(this.selectionData, this.revealList);
              excelHeader.push(...dataList, []);
              // 计算合并
              let merges = this.doMerges(excelHeader)
              // 生成sheet
              let ws = this.aoa_to_sheet(excelHeader, headerRows);
              // 单元格合并
              ws['!merges'] = merges;
              // 头部冻结
              ws["!freeze"] = {
                xSplit: "1",
                ySplit: "" + headerRows,
                topLeftCell: "B" + (headerRows + 1),
                activePane: "bottomRight",
                state: "frozen",
              };
              // 列宽
              ws['!cols'] = [{wpx:165}];
              let workbook = {
                SheetNames: [sheetName],
                Sheets: {}
              };
              workbook.Sheets[sheetName] = ws;
              // excel样式
              let wopts = {
                bookType: 'xlsx',
                bookSST: false,
                type: 'binary',
                cellStyles: true
              };
              let wbout = XLSX.write(workbook, wopts);
              let blob = new Blob([this.s2ab(wbout)], { type: "application/octet-stream" });
              this.openDownloadXLSXDialog(blob, sheetName + '.xlsx')
            },
    
            /**
             * 构建excel表头
             * @param revealList 列表页面展示的表头
             * @returns {[]} excel表格展示的表头
             */
            buildHeader(revealList) {
              let excelHeader = [];
              // 构建生成excel表头需要的数据结构
              this.getHeader(revealList, excelHeader, 0, 0);
              // 多行表头长短不一,短的向长的看齐,不够的补上行合并占位符
              let max = Math.max(...(excelHeader.map(a => a.length)))
              excelHeader.filter(e => e.length < max).forEach(
                e => this.pushRowSpanPlaceHolder(e, max - e.length))
              return excelHeader;
            },
    
            /**
             * 生成头部 
             * @param headers 展示的头部
             * @param excelHeader excel头部
             * @param deep 深度
             * @param perOffset 前置偏移量
             * @returns {number}  后置偏移量
             */
            getHeader(headers, excelHeader, deep, perOffset) {
              let offset = 0
              let cur = excelHeader[deep]
              if (!cur) {
                cur = excelHeader[deep] = []
              }
              // 填充行合并占位符
              this.pushRowSpanPlaceHolder(cur, perOffset - cur.length)
              for (let i = 0; i < headers.length; i++) {
                let head = headers[i]
                cur.push(head.name)
                if (head.hasOwnProperty('child') && Array.isArray(head.child)
                  && head.child.length > 0) {
                  let childOffset = this.getHeader(head.child, excelHeader, deep + 1,
                    cur.length - 1)
                  // 填充列合并占位符
                  this.pushColSpanPlaceHolder(cur, childOffset - 1)
                  offset += childOffset
                } else {
                  offset++
                }
              }
              return offset;
    
            },
            /**
            * 根据选中的数据和展示的列,生成结果
            * @param selectionData
            * @param revealList
            */
            extractData(selectionData, revealList) {
              // 列
              let headerList = this.flat(revealList);
              // 导出的结果集
              let excelRows = [];
              // 如果有child集合的话会用到
              let dataKeys = new Set(Object.keys(selectionData[0]));
              selectionData.some(e => {
                if (e.child && e.child.length > 0) {
                  let childKeys = Object.keys(e.child[0]);
                  for (let i = 0; i < childKeys.length; i++) {
                    dataKeys.delete(childKeys[i]);
                  }
                  return true;
                }
              })
              this.flatData(selectionData, (list) => {
                excelRows.push(...this.buildExcelRow(dataKeys, headerList, list));
              })
              return excelRows;
            },
    
            /**
             *
             * 
             * */
            buildExcelRow(mainKeys, headers, rawDataList) {
              // 合计行
              let sumCols = [];
              // 数据行
              let rows = [];
              for (let i = 0; i < rawDataList.length; i++) {
                let cols = []
                let rawData = rawDataList[i];
                // 提取数据
                for (let j = 0; j < headers.length; j++) {
                  let header = headers[j];
                  // 父元素键需要行合并
                  if (rawData['rowSpan'] === 0 && mainKeys.has(header.prop)) {
                    cols.push('!$ROW_SPAN_PLACEHOLDER')
                  } else {
                    let value;
                    if (typeof header.exeFun === 'function') {
                      value = header.exeFun(rawData);
                    } else {
                      value = rawData[header.prop];
                    }
                    cols.push(value)
                    // 如果该列需要合计,并且是数字类型
                    if (header['summable'] && typeof value === 'number') {
                      sumCols[j] = (sumCols[j] ? sumCols[j] : 0) + value;
                    }
                  }
                }
                rows.push(cols);
              }
              // 如果有合计行
              if (sumCols.length > 0) {
                rows.push(...this.sumRowHandle(sumCols));
              }
              return rows;
            },
            sumRowHandle(sumCols) {
              //TODO
              return [];
            },
            /**
             * 合并头部单元格
             **/
            doMerges(arr) {
              // 要么横向合并 要么纵向合并
              let deep = arr.length;
              let merges = [];
              for (let y = 0; y < deep; y++) {
                // 先处理横向合并
                let row = arr[y];
                let colSpan = 0
                for (let x = 0; x < row.length; x++) {
                  if (row[x] === '!$COL_SPAN_PLACEHOLDER') {
                    row[x] = undefined;
                    if(x+1 === row.length){
                      merges.push({ s: { r: y, c: x - colSpan - 1}, e: { r: y, c: x } })
                    }
                    colSpan++
                  } else if (colSpan > 0 && x > colSpan) {
                    merges.push({ s: { r: y, c: x - colSpan - 1 }, e: { r: y, c: x - 1 } })
                    colSpan = 0
                  } else {
                    colSpan = 0
                  }
                }
              }
              // 再处理纵向合并
              let colLength = arr[0].length
              for (let x = 0; x < colLength; x++) {
                let rowSpan = 0
                for (let y = 0; y < deep; y++) {
                  if (arr[y][x] === '!$ROW_SPAN_PLACEHOLDER') {
                    arr[y][x] = undefined;
                    if(y+1 === deep){
                      merges.push({ s: { r: y - rowSpan , c: x }, e: { r: y , c: x } })
                    }
                    rowSpan++;
                  } else if (rowSpan > 0 && y > rowSpan) {
                    merges.push({ s: { r: y - rowSpan - 1, c: x }, e: { r: y - 1, c: x } })
                    rowSpan = 0;
                  } else {
                    rowSpan = 0;
                  }
                }
              }
              return merges;
            },
    
            /**
             * 从github复制过来的
             */
            aoa_to_sheet(data, headerRows) {
              const ws = {};
              const range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } };
              for (let R = 0; R !== data.length; ++R) {
                for (let C = 0; C !== data[R].length; ++C) {
                  if (range.s.r > R) {
                    range.s.r = R;
                  }
                  if (range.s.c > C) {
                    range.s.c = C;
                  }
                  if (range.e.r < R) {
                    range.e.r = R;
                  }
                  if (range.e.c < C) {
                    range.e.c = C;
                  }
                  /// 这里生成cell的时候,使用上面定义的默认样式
                  const cell = {
                    v: data[R][C] || '',
                    s: {
                      font: { name: "宋体", sz: 11, color: { auto: 1 } },
                      alignment: {
                        /// 自动换行
                        wrapText: 1,
                        // 居中
                        horizontal: "center",
                        vertical: "center",
                        indent: 0
                      }
                    }
                  };
                  // 头部列表加边框
                  if (R < headerRows) {
                    cell.s.border = {
                      top: { style: 'thin', color: { rgb: "000000" } },
                      left: { style: 'thin', color: { rgb: "000000" } },
                      bottom: { style: 'thin', color: { rgb: "000000" } },
                      right: { style: 'thin', color: { rgb: "000000" } },
                    };
                    cell.s.fill = {
                      patternType: 'solid',
                      fgColor: { theme: 3, "tint": 0.3999755851924192, rgb: 'DDD9C4' },
                      bgColor: { theme: 7, "tint": 0.3999755851924192, rgb: '8064A2' }
                    }
                  }
                  const cell_ref = XLSX.utils.encode_cell({ c: C, r: R });
                  if (typeof cell.v === 'number') {
                    cell.t = 'n';
                  } else if (typeof cell.v === 'boolean') {
                    cell.t = 'b';
                  } else {
                    cell.t = 's';
                  }
                  ws[cell_ref] = cell;
                }
              }
              if (range.s.c < 10000000) {
                ws['!ref'] = XLSX.utils.encode_range(range);
              }
              return ws;
            },
            /**
             * 填充行合并占位符
             * */
            pushRowSpanPlaceHolder(arr, count) {
              for (let i = 0; i < count; i++) {
                arr.push('!$ROW_SPAN_PLACEHOLDER')
              }
            },
            // 填充列合并占位符
            pushColSpanPlaceHolder(arr, count) {
              for (let i = 0; i < count; i++) {
                arr.push('!$COL_SPAN_PLACEHOLDER')
              }
            },
            /**
             * 展开数据,为了实现父子关系的数据进行行合并
             *   [{
             *     a:1,
             *     b:2,
             *     child: [
             *       {
             *         c:3
             *       },
             *       {
             *         c:4
             *       }
             *     ]
             *   }]
             *
             *   展开为
             *   [
             *    {
             *      a:1,
             *      b:2,
             *      c:3,
             *      rowSpan:2,
             *      child:[...]
             *    },
             *    {
             *      a:1,
             *      b:2,
             *      c:4,
             *      rowSpan:0,
             *      child:[...]
             *    }
             *   ]
             *
             *
             * @param list
             * @param eachDataCallBack
             */
            flatData(list, eachDataCallBack) {
              let resultList = [];
              for (let i = 0; i < list.length; i++) {
                let data = list[i];
                let rawDataList = [];
                // 每个子元素都和父元素合并成一条数据
                if (data.child && data.child.length > 0) {
                  for (let j = 0; j < data.child.length; j++) {
                    delete data.child[j].bsm
                    let copy = Object.assign({}, data, data.child[j]);
                    rawDataList.push(copy);
                    copy['rowSpan'] = (j > 0 ? 0 : data.child.length);
                  }
                } else {
                  data['rowSpan'] = 1;
                  rawDataList.push(data);
                }
                resultList.push(...rawDataList);
                if (typeof eachDataCallBack === 'function') {
                  eachDataCallBack(rawDataList)
                }
              }
              return resultList;
            }
            ,
            // 扁平头部
            flat(revealList) {
              let result = []
              revealList.forEach(e => {
                if (e.hasOwnProperty('child')) {
                  result.push(...this.flat(e.child))
                } else if (e.hasOwnProperty('exeFun')) {
                  result.push(e)
                } else if (e.hasOwnProperty('prop')) {
                  result.push(e)
                }
              })
              return result;
            },
            s2ab(s) {
              let buf = new ArrayBuffer(s.length);
              let view = new Uint8Array(buf);
              for (let i = 0; i !== s.length; ++i) {
                view[i] = s.charCodeAt(i) & 0xFF;
              }
              return buf;
            },
            openDownloadXLSXDialog(url, saveName) {
              if (typeof url == 'object' && url instanceof Blob) {
                url = URL.createObjectURL(url); // 创建blob地址
              }
              var aLink = document.createElement('a');
              aLink.href = url;
              aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
              var event;
              if (window.MouseEvent) {
                event = new MouseEvent('click');
              } else {
                event = document.createEvent('MouseEvents');
                event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false,
                  false, false, false, 0, null);
              }
              aLink.dispatchEvent(event);
            }
          }
        })
      </script>
    </body>
    
    </html>
    

    总结

    这篇博客是我花了一下午的时间划水写出来的,如果有帮助到你,请给个赞!

    展开全文
  • 文件名:xlsx.core.min.jsxlsx.core.min.js免费下载,可以使用此js在前端导出xlsx表格数据
  • 各方面评估框架后选择了SheetJS的xlsx.js。 SheetJS功能强大。使用方便,文档详细。 github地址:SheetJS 参考文献:Jiao_0805 《js-xlsx使用》 兼容性 使用方法 实现思路 通过<input typ...

    介绍

    应项目需求需要做一个纯前端读取excel文件的功能。各方面评估框架后选择了SheetJS的xlsx.js。
    SheetJS功能强大。使用方便,文档详细。
    github地址:SheetJS
    参考文献:Jiao_0805 《js-xlsx使用》

    兼容性

    浏览器兼容性

    使用方法

    实现思路

    通过<input type="file" >获取选中的本地excel文件流。再通过 FileReader 对象读取文件,然后使用FileReader.onload 事件监听操作,最后用js-xlsx.js处理数据

    文件引用

    • https://github.com/SheetJS/sheetjs/tree/master/dist
      js文件
      一般来说只需要xlsx.core.min.js,xlsx.full.mini.js则包含了全部的功能。
      直接引入即可。
      <script lang="javascript" src="dist/xlsx.full.min.js"></script>
      npm:
      npm install xlsx
      bower:
      bower install js-xlsx

    代码示例及详解

    import XLSX from ".../dist/js/sheetjs/xlsx.core.min.js"
    
    if(!document.getElementById('readLocalFile')){    //如若已经有对应的dom元素直接点击,没有则add一个
    	let inputFile =	document.createElement("input");
    	inputFile.setAttribute("id","readLocalFile");
    	inputFile.setAttribute("type","file");
    	inputFile.setAttribute("accept","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel");
    	 inputFile.setAttribute("multiple", false);  //是否可以多选。这里设置为否
    	inputFile.click();
    	document.body.appendChild(inputFile);
    	console.log("使用 ID为readLocalFile 的 input dom对象获取上传文件的内容,以json格式数据返回");
    	document.getElementById('readLocalFile').addEventListener('change', function(e) {   //选择文件后执行
    		let  files = e.target.files;
    		if(files.length == 0) return;
    		let  file = files[0];
    		let  reader = new FileReader();
    		reader.readAsBinaryString(file);
    		reader.onload = function(e) {   //处理load事件。读取操作完成时触发。
    			let  data = e.target.result;
    			let  workbook = XLSX.read(data, {type: 'binary'}); //XLSX:/xlsx.core.min.js 通过XLSX.read(data, {type: type})方法读取excel 后面介绍
    			console.log(workbook ); 
    			let  sheetNames = workbook.SheetNames; // 工作表名称集合
    			let  worksheet = workbook.Sheets[sheetNames[0]]; // 这里我们只读取第一张sheet 
    			let  json = XLSX.utils.sheet_to_json(worksheet); //  读取workbook  这里可以自己写方法输出表格 这里建议使用XLSX.utils.工具类输出数据   这里以json格式输出数据 还有其他格式 代码后介绍
    			if(typeof(callback) == "function") callback(json);   //回调 
    			 document.getElementById('readLocalFile').value = null; //读取后清空
    		};
    	});
      }else{
    	  document.getElementById('readLocalFile').click();  //已有dom元素则点击
      }
    

    XLSX.read(data, {type: type})方法读取excel,返回WorkBook的对象,type主要取值如下:
    base64: 以base64方式读取;
    binary: BinaryString格式(byte n is data.charCodeAt(n))
    string: UTF8编码的字符串;
    buffer: nodejs Buffer;
    array: Uint8Array,8位无符号数组;
    工具类常用导出格式:
    XLSX.utils.sheet_to_csv:生成CSV格式
    XLSX.utils.sheet_to_txt:生成纯文本格式
    XLSX.utils.sheet_to_html:生成HTML格式
    XLSX.utils.sheet_to_json:生成JSON格式

    workBook对象示例:workbook对象

    开发注意

    FileReader仅用于以安全的方式从用户(远程)系统读取文件内容 它不能用于从文件系统中按路径名简单地读取文件。 要在JavaScript中按路径名读取文件,应使用标准Ajax解决方案进行服务器端文件读取,如果读取跨域,则使用CORS权限。

    转至目录

    展开全文
  • 使用SheetJS xlsx.js上传预览excel文件

    转载于https://blog.csdn.net/Handsome3618/article/details/86546647?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-1-86546647.nonecase,并在此基础上做了一点点的修改,再次感恩大佬的创作!!!

    <template>

      <div>

        <input type="file"

               @change="importf($event)" />

        <el-table :data="tableData.slice((currentPage-1)*pagesize,currentPage*pagesize)"

                  style="width: 100%">

          <el-table-column v-for="(item,index) in tableLabel"

                           :key="index"

                           :prop="item.prop"

                           :label="item.label"

                           width="180"></el-table-column>

        </el-table>

        <el-pagination @size-change="handleSizeChange"

                       @current-change="handleCurrentChange"

                       :current-page="currentPage"

                       :page-sizes="[1, 20, 30, 40]"

                       :page-size="pagesize"

                       layout="total, sizes, prev, pager, next, jumper"

                       :total="tableData.length">

        </el-pagination>

      </div>

    </template>

    <script>

    import pagination from '@/components/elPagination/elPagination'

    import * as XLSX from 'xlsx'

    export default {

      data() {

        return {

          tableLabel: [],

          tableData: [],

          currentPage: 1,

          pagesize: 1

        }

      },

     

      mounted() {},

      methods: {

        // 初始页currentPage、初始每页数据数pagesize和数据data

        handleSizeChange(size) {

          this.pagesize = size

          console.log(this.pagesize) //每页下拉显示数据

        },

        handleCurrentChange(currentPage) {

          this.currentPage = currentPage

          console.log(this.currentPage) //点击第几页

        },

        importf(obj) {

          //导入

          if (!obj.target.files) {

            return

          }

          var f = obj.target.files[0]

          console.log(f)

          var reader = new FileReader()

          var jsonobject_0

          var result = []

          reader.onload = e => {

            var data = e.target.result

            //获取xlsx对象

            var workbook = XLSX.read(data, {

              type: 'binary'

            })

     

            jsonobject_0 = XLSX.utils.sheet_to_json(

              workbook.Sheets[workbook.SheetNames[0]]

            )

            var keys = []

            for (var key of Object.keys(jsonobject_0[0])) {

              keys.push(key)

            }

            // console.log(keys)

            var keysLen = keys.length

            for (var i = 0; i < keysLen; i++) {

              this.tableLabel.push({ label: keys[i], prop: 'key' + Number(i + 1) })

            }

     

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

              var n = 0

              var item = {}

              for (var key of Object.keys(jsonobject_0[i])) {

                n++

                var mylabel = 'key' + n

                item[mylabel] = jsonobject_0[i][key]

              }

              this.tableData.push(item)

            }

            // console.log(this.tableData)

          }

          reader.readAsBinaryString(f)

        }

      }

    }

    </script>


     

    展开全文
  • 之前的项目一直使用的是nodejs的node-xlsx.js中间件,最近的项目部署时不能使用外网,而node-xlsx.js的路径太长不能U盘复制,现在改用xlsx.js中间件。  现在把自己封装的使用方法分享出来,和大家一起学习。  ...
  • xlsx.js文件

    2018-01-16 11:11:45
    js获取excel数据,实现前端获取excel的工具包,请参考
  • 在作以前一个项目的时候,须要有生成excel表格的功能,在网上查询一番后,发现不少人都推荐Sheetjs的xlsx.js这款工具,本身使用了一下,感受也不错,上手简单,功能结构清晰。所以,在这里就我本身使用到的功能部分...
  • node.js篇 第一步引入包 npm install xlsx -save 第二步使用 var xl =require('xlsx'); //workbook 对象,指的是整份 Excel 文档。我们在使用 js-xlsx 读取 Excel 文档之后就会获得 workbook 对象。 var ...
  • 首先还是要先引入XLSX,然后通过以下操作可以获得表头 import XLSX from "xlsx"; var excelFile; var reader = new FileReader(); reader.readAsBinaryString(file);... excelFile = XLSX.read(data,
  • 使用XLSX.js处理excle表

    千次阅读 2017-05-27 14:11:01
    最近做一个档案管理系统,有个需求是读取本地excle将...1 下载js-xlsx,部署到工程里,引入xlsx.full.min.js 2 使用filereader 上代码 $('#file').change(function(ee){ var files=ee.target.files; var file=new
  • 使用的js库:xlsx.js 思路: 计算每一列所有单元格的文本长度(包括列头),取最大值作为列宽 代码: getCellWidth为核心方法,用于计算单元格宽度的 如果包含中文,则宽度为:文本长度2.1,否则为:文本长度1.1,这个...
  • xlsx.js设置Excel样式

    万次阅读 热门讨论 2019-10-25 13:41:34
    "SheetJS" ; //处理主表内容 let keyList = Object . keys ( masterTable ) let valueList = Object . values ( masterTable ) let mastertableList = [ ] for ( let i = 0 ; i < ...
  • XLSX.js 导出Excel demo

    2019-09-30 03:08:29
    GitHub:https://github.com/SheetJS/js-xlsx 一个js操作Excel的工具,如下代码,很方便的就将json数据导出为Excel文件。 使用示例: //json 数据 var data = [{ "a": 1, "x": 2, "b": 3, "y": 4, ....
  • 纯前端 xlsx.js 导出

    千次阅读 2018-11-18 14:56:11
    js/xlsx.full.min.js " > script > < script type = " text/javascript " > /* 需要导出的JSON数据 */ var data = [ { "姓名" : "张三" , "城市" : "Seattle" , "age" : "12" } , { "姓名...
  • Excel表格插件:js-xlsx.js 的使用

    万次阅读 2018-04-19 11:10:06
    1、描述需求前两天,有一个需求,excel的某一列中含有不同...2、寻找插件从网上搜索到一个插件名为:js-xlsx。其GitHub地址为:https://github.com/SheetJS/js-xlsx。该插件支持IE8及以上版本浏览器,当然还支持saf...
  • xlsx.full.min.js

    2021-02-23 17:50:33
    xlsx.full.min.js

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,076
精华内容 830
关键字:

xlsx.js