17_纯前端实现导出功能

js-xlsx

中文文档

  • SheetJS 出品的 js-xlsx 是一款非常方便的只需要纯 JS 即可读取和导出 excel 的工具库,功能强大,支持格式众多,支持 xls、xlsx 等十几种格式

工作类

1. 从数组(数组项为数组)创建工作表

const worksheet = XLSX.utils.aoa_to_sheet(excleData);

  • XLSX.utils.aoa_to_sheet:接收js数组并返回数据对应的工作表
    • 将一个二维数组转成 sheet,会自动处理 number、string、boolean、date 等类型数据
将数组中的数据添加到现有工作表

XLSX.utils.sheet_add_aoa(worksheet, excleData);

  • XLSX.utils.sheet_add_aoa:接收js数组并更新现有工作表

2. 从对象数组创建工作表

const worksheet = XLSX.utils.json_to_sheet(newList);

  • XLSX.utils.json_to_sheet:接收js对象数组并返回数据对应的工作表
将对象数组中的数据添加到现有工作表

XLSX.utils.sheet_add_json(worksheet, excleData);

  • XLSX.utils.sheet_add_json:接收js对象数组并更新现有工作表

3. 创建新工作簿

const workbook = XLSX.utils.book_new();

若不带参数 book_new 函数将创建一个空工作簿

单一工作表

const workbook = XLSX.utils.book_new(worksheet);
const workbook = XLSX.utils.book_new(worksheet, 'Blatte');

  • 参数1:工作表对象,它将被添加到新工作簿中
  • 参数2:工作表的名称,默认 Sheet1

4. 将工作表附加到工作簿

XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');

5. 工作表对象属性

  • worksheet['!cols']:列对象数组

    • 每个列对象对属性进行编码,包括级别、宽度和可见性
  • worksheet['!rows']:行对象数组

    • 每个行对象对属性进行编码,包括级别、高度和可见性
  • worksheets['!merges']:与工作表中合并的单元格相对应的范围对象数组

    • 纯文本格式不支持合并单元格

    • CSV 导出将写入合并范围中的所有单元格(如果存在),因此请确保仅设置范围中的第一个单元格(左上角)

快速上手

1. 操作 dom 导出

domExportExcel(id, fileName) {
  // 1. 获取表格的 dom 对象 'data-table'
  let elt = document.getElementById(id);

  // 2. 创建工作簿
  let wb = XLSX.utils.table_to_book(elt, {
    sheet: 'Sheet1'
  });

  // 3. 导出
  return XLSX.writeFile(wb, fileName)
},

2. 二维数组导出

exportExcel(excleData, excleDataTitle, fileName) {

  excleData.unshift(excleDataTitle);
  // 1. 生成工作表:aoa_to_sheet 把数组转换为工作表
  const worksheet = XLSX.utils.aoa_to_sheet(excleData);

  // 2. book_new 新建一个工作簿
  const workbook = XLSX.utils.book_new();

  // 3. book_append_sheet 将工作表添加到工作簿
  XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');

  // 4. writeFile 输出工作表
  XLSX.writeFile(workbook, fileName);
},
const excleDataTitle = ['周一', '周二', '周三', '周四', '周五'];
const excleDataArr = [
  ['语文', '数学', '历史', '政治', '英语'],
  ['数学', '数学', '政治', '英语', '英语'],
  ['政治', '英语', '历史', '政治', '数学']
];
exportExcel(excleDataArr, excleDataTitle, '课程表.xlsx');

3. 对象数组导出

exportExcel(excleDataObj, excleDataTitle, fileName) {
  // 替换 表头 -> { '周一': '语文', ... }
  const excleData = [];
  excleDataObj.map(item => {
    let o = {};
    Object.entries(item).map(([key, value]) => {
      o[excleDataTitle[key]] = value;
    })
    excleData.push(o)
  })

  // 1. 生成工作表:json_to_sheet 把json转换为工作表
  const worksheet = XLSX.utils.json_to_sheet(excleData);

  // // 设置表格样式,!cols为列宽
  // const options = {
  //   '!cols': [
  //     { wpx: 100 },
  //     { wpx: 100 },
  //     { wpx: 100 },
  //     { wpx: 100 },
  //     { wpx: 100 },
  //   ]
  // };

  // // 设置每列的列宽,10代表10个字符,注意中文占2个字符
  // worksheet['!cols'] = options['!cols'];
  // // 合并单元格
  // worksheet['!merges'] = [{ e: { c: 2, r: 2 }, s: { c: 1, r: 1 } }];

  // 2. book_new 新建一个工作簿
  const workbook = XLSX.utils.book_new();

  // 3. book_append_sheet 将工作表添加到工作簿
  XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');

  // 4. writeFile 输出工作表
  XLSX.writeFile(workbook, fileName);
},
const excleDataTitle = {
  'Monday': '周一',
  'Tuesday': '周二',
  'Wednesday': '周三',
  'Thursday': '周四',
  'Friday': '周五'
};
const excleDataObj = [
  { Monday: '语文', Tuesday: '数学', Wednesday: '历史', Thursday: '政治', Friday: '英语' },
  { Monday: '数学', Tuesday: '数学', Wednesday: '政治', Thursday: '英语', Friday: '英语' },
  { Monday: '政治', Tuesday: '英语', Wednesday: '历史', Thursday: '政治', Friday: '数学' }
];

exportExcel(excleDataObj, excleDataTitle, '课程表.xlsx');

应用

由前端生成并下载表格文件

/**
 * @typedef {object} ExportConfig
 * @prop {array[]|object[]} data 目标数据
 * @prop {'matrix'|'dictArray'} dataType 数据的数据格式
 *  - dictArray:成员是对象的数组
 *  - matrix:二维数组
 * @prop {{[key: string]: string} | string[]} headers 表头
 */

import cloneDeep from "lodash/cloneDeep";
import * as XLSX from "xlsx";

/**
 * 由前端生成并下载表格文件
 * @param {ExportConfig} setting
 */
function exportLocalSheet(setting) {
  const { dataType } = setting;

  switch (dataType) {
    case "matrix":
      exportSheetOfMatrix(setting);
      break;

    default:
      exportSheetOfDictArray(setting);
      break;
  }
}

/**
 * 下载【对象数组】的表格
 * @param {ExportConfig} setting
 */
function exportSheetOfDictArray(setting) {
  const { data, headers, fileName } = setting;
  // 替换 表头
  const tableData = data.map((row) => {
    const newRow = {};

    Object.entries(row).map(([key, value]) => {
      newRow[headers[key]] = value;
    });

    return newRow;
  });

  // 1. 生成工作表:json_to_sheet 把json转换为工作表
  const worksheet = XLSX.utils.json_to_sheet(tableData);

  // 2. book_new 新建一个工作簿
  const workbook = XLSX.utils.book_new();

  // 3. book_append_sheet 将工作表添加到工作簿
  XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");

  // 4. writeFile 输出工作表
  XLSX.writeFile(workbook, fileName);
}

/**
 * 下载【二维数组】的表格
 * @param {ExportConfig} setting
 * @param {string[]} setting.headers
 */
function exportSheetOfMatrix(setting) {
  const { data, headers, fileName } = setting;
  const tableData = cloneDeep(data);

  tableData.unshift(headers);

  // 1. 生成工作表:aoa_to_sheet 把数组转换为工作表
  const worksheet = XLSX.utils.aoa_to_sheet(tableData);

  // 2. book_new 新建一个工作簿
  const workbook = XLSX.utils.book_new();

  // 3. book_append_sheet 将工作表添加到工作簿
  XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");

  // 4. writeFile 输出工作表
  XLSX.writeFile(workbook, fileName);
}

export { exportLocalSheet, exportSheetOfDictArray, exportSheetOfMatrix };

组件实现

<template>
  <div
    v-if="isDownloadOpen"
    class="ow-download"
    :class="[`ow-download-${type}`, { 'ow-old-school': oldSchool }]">
    <!-- 简易模式 -->
    <div
      v-if="type === 'simple'"
      class="icon-btn"
      :title="btnTips"
      @click="onDownloadClick">
      <Icon type="md-download" size="16"></Icon>
    </div>
    <!-- 完整模式 -->
    <div v-else-if="type === 'complete'" class="row align-items-center">
      <Button
        type="success"
        v-bind="btnProps"
        :title="btnTips"
        @click="onDownloadClick">
        <slot>
          <Icon type="md-download" size="14"></Icon>
          下载明细
        </slot>
      </Button>
      <Dropdown
        trigger="click"
        v-if="hasExportTypes"
        @on-click="onExportTypeChange">
        <Button
          class="more-btn"
          title="选择下载格式"
          type="success"
          v-bind="btnProps">
          <Icon type="md-arrow-dropdown"></Icon>
        </Button>
        <DropdownMenu slot="list">
          <DropdownItem
            v-for="item in exportTypeSelection.options"
            :key="item.value"
            :name="item.label"
            :selected="item.label === innerExportType"
            >{{ item.label }}</DropdownItem
          >
        </DropdownMenu>
      </Dropdown>
    </div>
  </div>
</template>
<script>
import { exportLocalSheet } from "./download";

/* 下载文件的格式 */
const EXPORT_FILE_TYPES = {
  xls: 1,
  csv: 2,
  txt: 3,
  1: "xlsx",
  2: "csv",
  3: "txt",
};

export default {
  name: "LocalDownloadBtn",
  props: {
    /** 下载的数据 */
    data: Array,

    /** 下载的表头 */
    headers: Array,

    /** 下载的文件名 */
    fileName: String,

    /** 组件的类型 [simple, complete] */
    type: {
      type: String,
      default: "simple",
    },

    /** 显示下载格式选项 */
    hasExportTypes: {
      type: Boolean,
      default: true,
    },

    /** 传入的下载格式值 */
    exportType: {
      type: String,
      default: "csv",
    },

    /** type=complete 时的按钮属性 */
    btnProps: Object,

    /* 旧的样式 */
    oldSchool: Boolean,
  },
  data() {
    return {
      exportTypeSelection: {
        visible: false,
        options: [
          { label: "xlsx", value: EXPORT_FILE_TYPES.xlsx },
          { label: "csv", value: EXPORT_FILE_TYPES.csv },
          // { label: 'txt', value: EXPORT_FILE_TYPES.txt }
        ],
      },
      innerExportType: this.exportType,
    };
  },
  watch: {
    exportType(val) {
      this.innerExportType = val;
    },
  },
  computed: {
    isDownloadOpen() {
      return this.$store.state.gameConfig.isDownloadOpen;
    },
    btnTips() {
      return `点击下载,当前格式:${this.innerExportType}`;
    },
  },
  methods: {
    // 点击下载
    onDownloadClick() {
      const parmas = {};
      const downloadSetting = {
        IS_STOP_DOWNLOAD: false, // 中止下载,这里之前的组件放置的地方不一样,应该统一
        exportType: this.innerExportType,
      };

      this.$emit("beforeStart", parmas, downloadSetting);
      if (!downloadSetting.IS_STOP_DOWNLOAD) {
        exportLocalSheet({
          headers: this.headers,
          data: this.data,
          fileName: `${this.fileName}.${this.innerExportType}`,
          ...parmas,
        });
      }
    },
    onExportTypeChange(val) {
      this.innerExportType = val;
      // 通知父组件更新 exportType 的值
      this.$emit("update:exportType", val);
      this.$emit("onExportTypeChange", val);
    },
  },
};
</script>

posted on 2024-06-11 11:30  pleaseAnswer  阅读(7)  评论(0编辑  收藏  举报