vue3 导出数据为 excel 文件

文章目录

  • 安装插件
  • 封装组件 -- Export2Excel.js
  • 多表封装
  • 界面使用 -- 数据处理成二维数组
  • 更多

菜鸟最近做了一个需求,就是需要上传表单并识别,然后识别出来的内容要可以修改,然后想的就是识别内容变成 form 表单,所以并没有使用 SpreadJS ,这个 SpreadJS 有很多 excel 的功能,强大是强大,就是我这个需求没有那么复杂!

然后上传识别交给后端处理了,菜鸟做的就是查看详情的是时候可把表单展示的数据导出成 excel 文件就行!

安装插件

npm i xlsx
npm install -S file-saver

封装组件 – Export2Excel.js

import { saveAs } from 'file-saver'
import * as XLSX from 'xlsx'
 
/**
 *
 * @param {Object} workbook 工作薄
 * @param {Object} worksheet 工作表
 * @param {Object} cell 单元格
 * 标记,引用单元格时所使用的地址格式(如:A1、C7)
 */
function datenum (v, date1904) {
    if (date1904) v += 1462
    var epoch = Date.parse(v)
    return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000)
}
 
function sheetFromArrayOfArrays (data, opts) {
    var ws = {}
    var range = {
        s: {
            c: 10000000,
            r: 10000000
        },
        e: {
            c: 0,
            r: 0
        }
    }
    for (var R = 0; R !== data.length; ++R) {
        for (var 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
            var cell = {
                v: data[R][C] // v表示单元格原始值, t表示内容类型,s-string类型,n-number类型,b-boolean类型,d-date类型,等等
            }
            if (cell.v == null) continue
            /**
             * 通过地址对象 { r: R, c: C } 来获取单元格,R 和 C 分别代表从 0 开始的行和列的索引。
             * XLSX.utils 中的 encode_cell/decode_cell 方法可以转换单元格地址
             *    XLSX.utils.encode_cell({ r: 7, c: 2 })  ===》 C7
             */
            var cellRef = 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 if (cell.v instanceof Date) {
                cell.t = 'n'
                cell.z = XLSX.SSF._table[14]
                cell.v = datenum(cell.v)
            } else cell.t = 's'
 
            ws[cellRef] = cell
        }
    }
    // ws['!ref']:表示所有单元格的范围,例如从A1到F8则记录为A1:F8
    if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range)
    return ws
}
 
function Workbook () {
    if (!(this instanceof Workbook)) return new Workbook()
    this.SheetNames = []
    this.Sheets = {}
}
 
// 字符串转为ArrayBuffer
function s2ab (s) {
    var buf = new ArrayBuffer(s.length)
    var view = new Uint8Array(buf)
    for (var i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF
    return buf
}
 
/**
 *
 * @param {Array} multiHeader  多行表头
 * @param {Array} header  表头
 * @param {Array} data  数据
 * @param {String} filename  文件名
 * @param {Array} merges  合并单元格
 * @param {Boolean} autoWidth  是否设置单元格宽度
 * @param {String} bookType  要生成的文件类型
 */
export function exportJsonToExcel ({
    multiHeader = [],
    header,
    data,
    filename,
    merges = [],
    autoWidth = true,
    bookType = 'xlsx'
} = {}) {
    filename = filename || 'excel-list'
    data = [...data]
    data.unshift(header)
 
    for (let i = multiHeader.length - 1; i > -1; i--) {
        data.unshift(multiHeader[i])
    }
 
    var wsName = 'SheetJS'
    var wb = new Workbook()
    var ws = sheetFromArrayOfArrays(data)
 
    if (merges.length > 0) {
        // ws[!merges]:存放一些单元格合并信息,是一个数组,每个数组由包含s和e构成的对象组成,s表示开始,e表示结束,r表示行,c表示列
        if (!ws['!merges']) ws['!merges'] = []
        merges.forEach(item => {
            ws['!merges'].push(XLSX.utils.decode_range(item))
        })
    }
 
    if (autoWidth) {
        /* 设置worksheet每列的最大宽度 */
        const colWidth = data.map(row => row.map(val => {
            /* 先判断是否为null/undefined */
            if (val == null) {
                return { 'wch': 10 }
            } else if (val.toString().charCodeAt(0) > 255) { /* 再判断是否为中文 */
                return {
                    'wch': val.toString().length * 2
                }
            } else {
                return {
                    'wch': val.toString().length
                }
            }
        }))
        /* 以第一行为初始值 */
        let result = colWidth[0]
        for (let i = 1; i < colWidth.length; i++) {
            for (let j = 0; j < colWidth[i].length; j++) {
                if (result[j] && result[j]['wch'] < colWidth[i][j]['wch']) {
                    result[j]['wch'] = colWidth[i][j]['wch']
                }
            }
        }
        // ws['!cols']设置单元格宽度, [{'wch': 10},{'wch': 10}] ===> 第一列和第二列设置了宽度
        ws['!cols'] = result
    }
 
    /* add worksheet to workbook */
    wb.SheetNames.push(wsName)
    wb.Sheets[wsName] = ws
 
    var wbout = XLSX.write(wb, {
        bookType: bookType,
        bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
        type: 'binary'
    })
    saveAs(
        new Blob([s2ab(wbout)], { type: 'application/octet-stream' }),
        `${filename}.${bookType}`
    )
}

此处参考:vue3中将数据导出为excel表格

多表封装

但是菜鸟的需求是要一个 excel 包含两个表,所以菜鸟对其进行了优化:

import { saveAs } from "file-saver";
import * as XLSX from "xlsx";

/**
 *
 * @param {Object} workbook 工作薄
 * @param {Object} worksheet 工作表
 * @param {Object} cell 单元格
 * 标记,引用单元格时所使用的地址格式(如:A1、C7)
 */
function datenum(v, date1904) {
  if (date1904) v += 1462;
  var epoch = Date.parse(v);
  return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
}

// eslint-disable-next-line
function sheetFromArrayOfArrays(data, opts) {
  var ws = {};
  var range = {
    s: {
      c: 10000000,
      r: 10000000,
    },
    e: {
      c: 0,
      r: 0,
    },
  };
  for (var R = 0; R !== data.length; ++R) {
    for (var 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;
      var cell = {
        v: data[R][C], // v表示单元格原始值, t表示内容类型,s-string类型,n-number类型,b-boolean类型,d-date类型,等等
      };
      if (cell.v == null) continue;
      /**
       * 通过地址对象 { r: R, c: C } 来获取单元格,R 和 C 分别代表从 0 开始的行和列的索引。
       * XLSX.utils 中的 encode_cell/decode_cell 方法可以转换单元格地址
       *    XLSX.utils.encode_cell({ r: 7, c: 2 })  ===》 C7
       */
      var cellRef = 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 if (cell.v instanceof Date) {
        cell.t = "n";
        cell.z = XLSX.SSF._table[14];
        cell.v = datenum(cell.v);
      } else cell.t = "s";

      ws[cellRef] = cell;
    }
  }
  // ws['!ref']:表示所有单元格的范围,例如从A1到F8则记录为A1:F8
  if (range.s.c < 10000000) ws["!ref"] = XLSX.utils.encode_range(range);
  return ws;
}

function Workbook() {
  if (!(this instanceof Workbook)) return new Workbook();
  this.SheetNames = [];
  this.Sheets = {};
}

// 字符串转为ArrayBuffer
function s2ab(s) {
  var buf = new ArrayBuffer(s.length);
  var view = new Uint8Array(buf);
  for (var i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
  return buf;
}

/**
 *
 * @param {Array} multiHeader  多行表头
 * @param {Array} header  表头
 * @param {Array} data  数据
 * @param {String} filename  文件名
 * @param {Array} merges  合并单元格
 * @param {Boolean} autoWidth  是否设置单元格宽度
 * @param {String} bookType  要生成的文件类型
 */
export function exportJsonToExcel({
  multiHeader = [],
  header,
  data,
  filename,
  sheetname,
  merges = [],
  autoWidth = true,
  bookType = "xlsx",
} = {}) {
  filename = filename || "excel-list";

  for (let i in header) {
    data[i] = [...data[i]];
    data[i].unshift(header[i]);
  }

  for (let j in data) {
    for (let i = multiHeader.length - 1; i > -1; i--) {
      data[j].unshift(multiHeader[i]);
    }
  }

  let wsName = [];
  for (let i in sheetname) {
    wsName.push(sheetname[i]);
  }

  var wb = new Workbook();

  let ws = [];
  for (let i in data) {
    ws.push(sheetFromArrayOfArrays(data[i]));
  }

  if (merges.length > 0) {
    // ws[!merges]:存放一些单元格合并信息,是一个数组,每个数组由包含s和e构成的对象组成,s表示开始,e表示结束,r表示行,c表示列
    for (let i in ws) {
      if (!ws[i]["!merges"]) ws["!merges"] = [];
      merges.forEach((item) => {
        ws[i]["!merges"].push(XLSX.utils.decode_range(item));
      });
    }
  }

  if (autoWidth) {
    /* 设置worksheet每列的最大宽度 */
    const colWidth = data.map((row) =>
      row.map((val) => {
        /* 先判断是否为null/undefined */
        if (val == null) {
          return { wch: 10 };
        } else if (val.toString().charCodeAt(0) > 255) {
          /* 再判断是否为中文 */
          return {
            wch: val.toString().length * 2,
          };
        } else {
          return {
            wch: val.toString().length,
          };
        }
      })
    );
    /* 以第一行为初始值 */
    let result = colWidth[0];
    for (let i = 1; i < colWidth.length; i++) {
      for (let j = 0; j < colWidth[i].length; j++) {
        if (result[j] && result[j]["wch"] < colWidth[i][j]["wch"]) {
          result[j]["wch"] = colWidth[i][j]["wch"];
        }
      }
    }
    // ws['!cols']设置单元格宽度, [{'wch': 10},{'wch': 10}] ===> 第一列和第二列设置了宽度
    for (let i in ws) {
      ws[i]["!cols"] = result;
    }
  }

  /* add worksheet to workbook */
  for (let i in wsName) {
    wb.SheetNames.push(wsName[i]);
    wb.Sheets[wsName[i]] = ws[i];
  }

  var wbout = XLSX.write(wb, {
    bookType: bookType,
    bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
    type: "binary",
  });
  saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), `${filename}.${bookType}`);
}

界面使用 – 数据处理成二维数组

第一个的使用直接参考别人的,我这里使用的是菜鸟封装的,如果有误,可以留言,菜鸟进行更改,因为菜鸟这里只测试了一个和两个表格的情况

// 数据处理  --》 读者按照自己的逻辑来
for (const key in envFactors.value) {
  let data1 = []; //设置一个二级列表,导出的表格每一行为一个二级列表
  for (let i in objkeyArr.value) {
    let keyname = objkeyArr.value[i];
    data1.push(envFactors.value[key][keyname]);
  }
  data2.value.push(data1); //把二级列表塞入一级列表
}
console.log(data2); // data2 必须是二维数组

for (const key in sampleGroups.value) {
  let data1 = [];
  data1.push(
    sampleGroups.value[key].sampleTestName,
    sampleGroups.value[key].sampleAnalysisName,
    sampleGroups.value[key].groupFirstWay,
    sampleGroups.value[key].groupSecWay,
    sampleGroups.value[key].groupThirdWay,
    sampleGroups.value[key].groupFourthWay,
    sampleGroups.value[key].groupFifthWay
  );
  data3.value.push(data1);
}
console.log(data3);
// 数据处理结束

// 导出成表格
function exportExcel() {
  exportJsonToExcel({
    header: [
      [
        "检测报告样品名称",
        "分析样品名称",
        "分组名称",
        "分组名称",
        "分组名称",
        "分组名称",
        "分组名称",
      ],
      objkeyArr.value,
    ], // 表名个数可以不等于数据每一行的列数
    filename: "详请表",
    data: [data3.value, data2.value],
    sheetname: ["样品分组表", "环境因子表"],
  });
}

更多

菜鸟这里只使用了header、data、filename、sheetname 等属性,其他属性可以看该篇文章,挺详细的:纯前端js(或者vue)导出excel实现:合并单元格、设置单元格样式、单元格内换行

你可能感兴趣的:(vue3,vue3导出excel,vue3简单导出excel,导出excel)