将ant-design-vue的table表格导出为excel文件 2021-10-07

目的

将ant-design-vue的table表格导出为excel文件
如果有需求修改表格的导出式样时,不需要修改源码,添加新的工序即可。

说明

根据自己的想法做了一下改装,不知道改的合理不合理。
如果有大神愿意指导,希望可以指出不足以及修改方案等。

遇到的问题

xlsx、js-xlsx、node-xlsx的区别,本来以为xlsx可以直接做到和后台一样的文档编辑效果,查了一下才知道有所限制,看来是个大佬的个人作品。

效果图

要导出的表格
要导出的表格
导出后的文件
导出后的文件

例子

1.安装依赖

cnpm i file-saver -S
cnpm i js-xlsx -S
cnpm i -D script-loader

2.创建文件夹vendor
3.将Exoprt2Excel-Custom-Anywo.js创建到vendor文件夹
4.下载https://github.com/eligrey/Blob.js/blob/master/Blob.js到vendor文件夹

5.拷贝以下例子代码
   例子模板来自:https://www.antdv.com/components/table-cn/

<template>
  <div>
    <a-table
      ref="table"
      :columns="columns"
      :data-source="data"
      :scroll="{ x: 1500, y: 300 }"
    >
      <a slot="action" slot-scope="text">action</a>
    </a-table>
    <a-button @click="handleClick">导出</a-button>
  </div>
</template>
<script>
const data = [];
for (let i = 0; i < 100; i++) {
  data.push({
    key: i,
    name: `Edrward ${i}`,
    age: 32,
    address: `London Park no. ${i}`,
  });
}
import { export_table_to_excel } from "./vendor/Exoprt2Excel-Custom-Anywo";
export default {
  data() {
    return {
      data,
      isDownload: false,
    };
  },
  methods: {
    handleClick() {
      this.isDownload = true;
      this.$nextTick(() => {
        export_table_to_excel({ ref: this.$refs.table });
        this.isDownload = false;
      });
    },
  },
  computed: {
    columns() {
      return [
        {
          title: "Full Name",
          width: 100,
          dataIndex: "name",
          key: "name",
          fixed: this.isDownload ? false : "left",
        },
        {
          title: "Age",
          width: 100,
          dataIndex: "age",
          key: "age",
          fixed: this.isDownload ? false : "left",
        },
        { title: "Column 1", dataIndex: "address", key: "1", width: 150 },
        { title: "Column 2", dataIndex: "address", key: "2", width: 150 },
        { title: "Column 3", dataIndex: "address", key: "3", width: 150 },
        { title: "Column 4", dataIndex: "address", key: "4", width: 150 },
        { title: "Column 5", dataIndex: "address", key: "5", width: 150 },
        { title: "Column 6", dataIndex: "address", key: "6", width: 150 },
        { title: "Column 7", dataIndex: "address", key: "7", width: 150 },
        { title: "Column 8", dataIndex: "address", key: "8" },
        {
          title: "Action",
          key: "operation",
          fixed: this.isDownload ? false : "right",
          width: 100,
          scopedSlots: { customRender: "action" },
        },
      ];
    },
  },
};
</script>

Exoprt2Excel-Custom-Anywo.js源码

/**!
 * Exoprt2Excel-Custom-Anywo.js
 * https://blog.csdn.net/y1377593732 2021-10-07
 * xlsx API可以参考:https://www.npmjs.com/package/js-xlsx
 * 引用组件的获取方法:
 *  cnpm i file-saver -S
 *  https://github.com/eligrey/Blob.js/blob/master/Blob.js
 *  cnpm i js-xlsx -S
 *  cnpm i -D script-loader
 * 
 * 参照Export2Excel.js修改的,不知道作者,谁知道给贴一个
 */

require('script-loader!file-saver');
require('script-loader!./Blob');
require('script-loader!js-xlsx/dist/xlsx.core.min');

/**
 * @description 工作簿类
 * @returns 
 */
function Workbook() {
  if (!(this instanceof Workbook)) return new Workbook();
  this.SheetNames = [];
  this.Sheets = {};
}

function 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;
}

/**
 * @description 表格分析方法
 * @param {*} table 
 * @returns 
 */
function generateArray(table, { cellHandles }) {
  let out = []; // 数据集合
  let ranges = []; // 合并单元格规则集合

  // 遍历所有行
  let rows = table.querySelectorAll('tr');
  rows.forEach((row, rI) => {
    // 获取当前行的初始状态,有前面合并单元格的列时,可能会已经存在
    out[rI] = out[rI] || [];
    // 遍历当前行所有列
    let columns = row.querySelectorAll('td,th');
    let cI = 0;
    columns.forEach(cell => {
      // 判断列是否已经被合并单元格占用,如果被占用跳过
      if (out[rI].length > cI && out[rI][cI] != null) {
        // 取得之后第一个等于null的元素的下标,避免稀疏数组带来的问题
        for (let i = cI; i < out[rI].length; i++) {
          if (out[rI][i] == null) {
            cI == i; // 如果更新当前列下标
            break;
          }
          cI = i + 1;
        }
      }
      let colspan = cell.getAttribute('colspan'); // 获取列合并属性
      let rowspan = cell.getAttribute('rowspan'); // 获取行合并属性
      let cellValue = cell.innerText; // 获取单元格的值
      // 纯数字的列转换为数字类型
      if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
      rowspan = parseInt(rowspan || 1);
      colspan = parseInt(colspan || 1);
      // 添加合并单元格规则
      if (rowspan > 1 || colspan > 1) {
        ranges.push({ s: { r: rI, c: cI }, e: { r: rI + rowspan - 1, c: cI + colspan - 1 } });
      }
      // 有合并规则时,所有单元格设置相同值到单元格
      for (let i = rI; i < rI + rowspan; i++) {
        out[i] = out[i] || [];
        for (let j = cI; j < cI + colspan; j++) {
          out[i][j] = cellHandles.reduce((context, x) => {
            // 保留每个流水线步骤的处理结果
            let res = x(context);
            return res == null ? context : res;
          }, {
            rowEl: row,
            colEl: cell,
            value: cellValue,
            rowIndex: i,
            colIndex: j,
            rowspan,
            colspan
          });
        }
      }
      cI++;
    });
  });
  return { out, ranges };
};

/**
 * @description 创建单元格对象
 * @param {*} context 
 * @returns 
 */
function create_cell_object(context) {
  // 转换成A1的表示形式
  var cell_ref = XLSX.utils.encode_cell({ c: context.colIndex, r: context.rowIndex });
  let cell = { v: context.value || '' };

  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';

  return Object.assign(context, {
    ref: cell_ref,
    cell
  })
}

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);
}

/**
 * @description 导出表格到excel 基础版本
 * ant表格导入时,保证没有fixed的列就可以正确导出,滚动条没事
 * @param {ATable} ref 
 */
export function export_table_to_excel_base({ ref, fileName = "test.xlsx", sheetName = "Sheet1", cellHandles = [], sheetHandles = [] }) {
  let theTable;
  if (ref != null || ref.$el != null) {
    // theTable = ref.$el.querySelector("table");
    theTable = ref.$el;
  } else {
    return false;
  }

  // 分析table,并进行流水线处理
  let oo = generateArray(theTable, {
    cellHandles: [create_cell_object, ...cellHandles]
  });

  let wb = new Workbook(); // 创建工作表
  // 创建sheet
  let ws = {
    '!merges': oo.ranges,
    '!ref': XLSX.utils.encode_range({ s: { c: 0, r: 0 }, e: { c: oo.out[0].length - 1, r: oo.out.length - 1 } })
  };

  // 生成单元格
  oo.out.flat().forEach(y => ws[y.ref] = y.cell);

  // sheet流水线处理
  if (sheetHandles && sheetHandles.length) {
    ws = sheetHandles.reduce((context, x) => {
      // 保留每个流水线步骤的处理结果
      let res = x(context);
      return res == null ? context : res;
    }, { ws, cells: oo.out }).ws;
  }

  /* add worksheet to workbook */
  wb.SheetNames.push(sheetName);
  wb.Sheets[sheetName] = ws;
  let wbout = XLSX.write(wb, { bookType: 'xlsx', showGridLines: false, bookSST: false, type: 'binary' });

  saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), fileName)
  return true;
}

/**
 * @description 拷贝表格式样(列宽)
 * @param {*} param0 
 * @returns 
 */
function copy_table_style_cell_width({ ws, cells }) {
  // 确定每列的宽度
  let cols = [];
  for (let i = 0; i < cells[0].length; i++) {
    for (let j = 0; j < cells.length; j++) {
      let cell = cells[j][i];
      if (cell && cell.colspan == 1) {
        cols.push({ wpx: cell.colEl.offsetWidth });
        break;
      }
    }
  }
  ws["!cols"] = cols;
  return { ws, cells };
}

/**
 * @description 颜色转换 
 * TODO 目前方案凑活用一下,如果有好的插件给个推荐
 * @param {*} color 
 * @returns 
 */
function formatColor(color) {
  if (/^rgb/.test(color)) {
    let str = color.replace(/^rgba?\((.*)\)$/, "$1");
    let arr = str.split(", ");
    let $4 = parseFloat(arr[3] || 1);
    let res = arr.slice(0, 3).reduce((res, x) => {
      let v = parseInt(x);
      // v = $4 * v;
      return res += parseInt(v).toString(16).padStart(2, 0)
    }, '');

    if ($4 == 0) {
      return "ffffff";
    }
    return res;
  } else if (color.startsWith("#")) {
    return color.slice(1);
  }
}

/**
 * @description 拷贝表格式样
 * TODO 并没有达到拷贝效果
 * @param {*} param0 
 * @returns 
 */
function copy_table_style_cell(context) {
  let computedStyles = window.getComputedStyle(context.colEl);
  context.cell.s = {
    fill: {
      fgColor: {
        rgb: formatColor(computedStyles["background-color"])
      }
    },
    font: {
      sz: computedStyles["font-size"].slice(0, -2)
    },
    alignment: {
      vertical: "center",
      horizontal: "center",
      wrapText: true
    },
    border: {
      top: { style: 'thin' },
      bottom: { style: 'thin' },
      left: { style: 'thin' },
      right: { style: 'thin' },
    }
  };

  return context;
}

/**
 * @description 导出表格到excel 自定义版本
 * @param {ATable} ref 
 */
export function export_table_to_excel({ ref, fileName = "test.xlsx", sheetName = "Sheet1", cellHandles = [], sheetHandles = [] }) {
  export_table_to_excel_base({
    ref, fileName, sheetName, cellHandles: [
      copy_table_style_cell
      , ...cellHandles], sheetHandles: [
        copy_table_style_cell_width
        , ...sheetHandles]
  });
}
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值