likes
comments
collection
share

好用的原生导出Excel功能

作者站长头像
站长
· 阅读数 31
好用的原生导出Excel功能

excel.js好用、简洁的实现方式导出 Excel 功能;

完整片段

先上代码,下面再做说明

export interface FormatJsonOption<T> {
  /** 表头字段 */
  header: string
  /** 对应表头的字段`key` */
  key: keyof T
  /** 
   * 条件处理函数
   * @param item 表单当前项
   * @param index 索引
   */
  handle?: (item: T, index: number) => number | string
}

/**
 * 格式化`json`返回导出表格需要的数据
 * @param target 处理的目标数组
 * @param options 处理配置数组,字段顺序按照这个来
 */
export function formatJson<T>(target: Array<T>, options: Array<FormatJsonOption<T>>) {
  const headers = options.map(item => item.header);
  const list: Array<Array<string | number>> = [];

  for (let i = 0; i < target.length; i++) {
    const item = target[i];
    list[i] = [];
    for (let j = 0; j < options.length; j++) {
      const option = options[j];
      const key = option.key;
      if (Object.prototype.hasOwnProperty.call(item, key)) {
        if (option.handle) {
          list[i].push(option.handle(item, i));
        } else {
          list[i].push(item[key] as any);
        }
      } else {
        console.warn("function formatJson >> item 中不存在对应的 key 值");
      }
    }
  }
  return {
    headers,
    list
  }
}

interface NativeExportOptions {
  /** 表格头部列表 */
  header: Array<string>
  /**
   * 添加的表格头部
   * - 在固定头部的前面插入
   */
  insertHeader?: string
  /** 导出的表格数据(二维数组) */
  data: Array<Array<string | number>>
  /** 导出的文件名 */
  fileName: string
  /** 表格文字排版 */
  textAlign?: "left"|"center"|"right"
  /** 图片尺寸配置 */
  imgSize?: {
    /** 图片宽度,默认 100 */
    width?: number
    /** 图片宽度,默认 100 */
    height?: number
  }
}

/**
 * 原生导出`Excel`函数
 * @param option 
 */
export function exportExcelByNative(option: NativeExportOptions) {

  /** 字符串中包含`http`则默认为图片地址 */
  const reg = /http/;
  /** 表头的长度 */
  const headLength = option.header.length;
  /** 记录条数 */
  const tableLength = option.data.length;
  /** 设置图片大小 */
  const width = option.imgSize?.width || 100;
  /** 图片高度 */
  const height = option.imgSize?.height || 100;

  // 添加表头信息
  let thead = `<thead>${option.insertHeader || ""}<tr>`;
  for (let i = 0; i < headLength; i++) {
    thead += `<th>${option.header[i]}</th>`;
  }
  thead += "</tr></thead>";

  // 添加每一行数据
  let tbody = "<tbody>";

  for (let i = 0; i < tableLength; i++) {
    tbody += "<tr>";
    const rows = option.data[i];
    for (let j = 0; j < rows.length; j++) {
      const row = rows[j];
      // 如果为图片,则需要加 div包住图片
      if (row && reg.test(row.toString())) {
        tbody += `<td style="width: ${width}px; height: ${height}px; text-align: center; vertical-align: middle">
                    <div style="display: inline">
                        <img src="${row}" width="${width}" height="${height}">
                    </div>
                </td>`;
      } else {
        tbody += `<td style="text-align: ${option.textAlign || "left"}">${row}</td>`;
      }
    }
    tbody += "</tr>";
  }

  tbody += "</tbody>";

  const ctx = {
    worksheet: option.fileName,
    table: thead + tbody
  }
  // return console.log(ctx);
  
  // 编码要用`utf-8`不然默认`gbk`会出现中文乱码
  const prefix = "data:application/vnd.ms-excel;base64,";
  const template = `<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta charset="UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>{table}</table></body></html>`;

  function base64(val: string) {
    return window.btoa(unescape(encodeURIComponent(val)));
    // return window.btoa(decodeURIComponent(encodeURIComponent(val)));
  }

  function format(value: string, info: any) {
    return value.replace(/{(\w+)}/g, (m, p) => {
      return info[p];
    })
  }

  // 创建下载
  const label = document.createElement("a");
  label.setAttribute("href", `${prefix}` + base64(format(template, ctx)));
  label.setAttribute("download", option.fileName);
  document.body.appendChild(label);
  label.click();
  label.remove();
}

使用

const tableData = [
  { id: 1, name: "项目-1", state: 1, date: new Date().toLocaleDateString() },
  { id: 2, name: "项目-2", state: 0, date: new Date().toLocaleDateString() },
  { id: 11, name: "项目-3", state: 2, date: new Date().toLocaleDateString() },
  { id: 13, name: "项目-4", state: 3, date: new Date().toLocaleDateString() },
];

function onDownload() {
  const data = formatJson(tableData, [
    {
      header: "项目ID",
      key: "id"
    },
    {
      header: "项目名称",
      key: "name"
    },
    {
      header: "项目状态",
      key: "state",
      handle(row) {
        return ["待开始", "审核中", "进行中", "已结束"][row.state];
      }
    },
    {
      header: "项目创建时间",
      key: "date"
    },
  ]);
  exportExcelByNative({
    header: data.headers,
    data: data.list,
    fileName: "xxx项目-Excel"
  });
}

实现原理

核心就是将html标签标记为excel文件类型,然后通过数据组装成表格标签,最后转base64给到<a>点击下载

<html
  xmlns:o="urn:schemas-microsoft-com:office:office"
  xmlns:x="urn:schemas-microsoft-com:office:excel"
  xmlns="http://www.w3.org/TR/REC-html40"
>
  <head>
    <meta charset="UTF-8">
    <!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]-->
  </head>
  <body>
    <table>{table}</table>
  </body>
</html>

主要部分

// 编码要用`utf-8`不然默认`gbk`会出现中文乱码
  const prefix = "data:application/vnd.ms-excel;base64,";
  const template = `<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta charset="UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table>{table}</table></body></html>`;

  function base64(val: string) {
    return window.btoa(unescape(encodeURIComponent(val)));
    // return window.btoa(decodeURIComponent(encodeURIComponent(val)));
  }

  function format(value: string, info: any) {
    return value.replace(/{(\w+)}/g, (m, p) => {
      return info[p];
    })
  }

  // 创建下载
  const label = document.createElement("a");
  label.setAttribute("href", `${prefix}` + base64(format(template, ctx)));
  label.setAttribute("download", option.fileName);
  document.body.appendChild(label);
  label.click();
  label.remove();

至于怎么去组装数据、封装使用,我定义了formatJson函数,作为处理数据的转化工具,最后传入exportExcelByNative即可。

  • 并且导出的数据如果为网络链接时,将处理为图片导出,这里我简单用正则去匹配,需要完善的则交给开发者自行修改即可。
  • 支持自定义头部,处理起来也非常简单,就是写html,无需像excel.js那种需要配置复杂的数据结构来完成。
转载自:https://juejin.cn/post/7361064330169614371
评论
请登录