小工具知识网

当前位置: 首页 > 行业知识 > 前端Excel导出实用方案(完整源码,可直接应用)

行业知识

前端Excel导出实用方案(完整源码,可直接应用)

2026-11-09 12:24:26 来源:互联网转载

目录

前言:

技术选型:

主要功能点:

核心代码:

完整代码:

开发文档


前言:

在前后端分离开发为主流的时代,很多时候,excel导出已不再由后端主导,而是把导出的操作移交到了前端。本文在全局导出组件封装上,保持了高度的扩展性,无论大家用的是element组件库还是antd vue的组件库或者其他的组件库,都容易进行更换。

技术选型:

vue + antd vue + sheetjs

前端导出excel导出,需借助第三方插件,目前两款导出最为主流。

一款是sheetjs,优点支持多种excel格式,但是官方文档全是英文
SheetJS Community Edition | SheetJS Community Edition

一款是exceljs,优点是中文文档很全,缺点是导出格式受限,仅支持部分格式

https://github.com/exceljs/exceljs/blob/master/README_zh.md

因公司业务需要,用户需支持多种excel的格式,所以本文笔者主要针对sheetjs进行封装调用。

主要功能点:

  • 自定义dom
  • 拆分成多张表导出(默认超过1万条数据自动拆分)
  • 自定义过滤函数
  • 各种标题自定义
  • 数据排序
  • 支持大数据量导出

    核心代码:

    // 文件名称const filename = fileName;//Excel第一个sheet的名称const ws_name = sheetName;// 创建sheetconst ws = XLSX.utils.aoa_to_sheet([this.tableTitle]);//添加数据XLSX.utils.sheet_add_json(ws, apiData, {   skipHeader: true,   origin:origin});// 创建wokbookconst wb = XLSX.utils.book_new();// 将数据添加到工作薄XLSX.utils.book_append_sheet(wb, ws, ws_name);// 导出文件XLSX.writeFile(wb, filename);

    完整代码:

    安装sheetjs

    npm i xlsx

    全局导出组件代码:

    ExportExcelComponent.vue

                    导出excel                                重新导出                          继续导出                 关闭                   import * as XLSX from "xlsx";export default {  props: {    //自定义过滤函数    filterFunction: {      type: Function,      default: null,    },    //sheet名    ws_name: {      type: String,      default: "Sheet",    },    //导出的excel的表名    filename: {      type: String,      default: "Excel" + new Date().getTime(),    },    //拆分成每个表多少条数据,需要搭配isSplit属性一起使用    multiFileSize: {      type: Number,      default: 10e3,    },    //模态框标题    modelTitle: {      type: String,      default: "导出excel",    },    //是否自定义dom,如果采用插槽,需要开启该属性,否则dom为默认button    isCustom: {      type: Boolean,      default: false,    },    // 导出的数据表的表头    tableTitleData: {      type: Array,      required: true,      default: () => [],    },    //请求数据的api函数    asyncDataApi: {      type: Function,      default: () => {},    },    //请求参数    listQuery: {      type: Object,      default: () => ({}),    },  },  data() {    return {      ws: null,      isAbnormal: false,      btnLoading: false,      progressStatus: "active",      visible: false,      percent: 0,      tableData: [],      currentPage: 1,      multiFileNum: 0,    };  },  computed: {    // 导出的数据表的表头    tableTitle() {      return this.tableTitleData.map((item) => {        return item.title;      });    },    //导出数据表的表头的code    tableCode() {      return this.tableTitleData.map((item) => {        return item.code;      });    },  },  watch: {    percent: {      handler(newVal) {        if (newVal > 100) {          this.progressStatus = "success";          setTimeout(() => {            this.handleClose();          }, 500);        }      },    },  },  methods: {    //按照指定的title顺序映射排序数组对象    sortData(data, tit_code) {      const newData = [];      data.forEach((item) => {        const newObj = {};        tit_code.forEach((v) => {          newObj[v] = item[v] || "";        });        newData.push(newObj);      });      return newData;    },    handleClose() {      console.log("close");      this.resetExport();      this.visible = false;    },    resetExport() {      this.percent = 0;      this.progressStatus = "active";      this.isAbnormal = false;      this.tableData = [];      this.currentPage = 1;      this.multiFileNum = 0;      this.ws = XLSX.utils.aoa_to_sheet([this.tableTitle]);    },    //获取进度条百分比    getPersent(res) {      const persent_num =        ((res.paginator.currentPage * res.paginator.size) /          res.paginator.total) *        100;      this.percent = parseInt(persent_num) - 1;    },    //异常处理    handleAbnormal() {      this.btnLoading = false;      this.progressStatus = "exception";      this.isAbnormal = true;    },    async startExport() {      if (!this.asyncDataApi) {        return new Promise(new Error("asyncDataApi is required"));      }      this.resetExport();      await this.getTableData();    },    //请求导出的数据和标题    async getTableData() {      this.visible = true;      this.btnLoading = true;      this.isAbnormal = false;      try {        const res = await this.asyncDataApi({          ...this.listQuery,          page: this.currentPage,        });        if (res.code !== 200) {          this.handleAbnormal();          this.$message.error(res.message || this.t("requestException"));          return;        }        let apiData = res.data;        apiData = this.sortData(apiData, this.tableCode);        if (this.filterFunction) {          apiData = this.filterFunction(apiData);        }        apiData = apiData.map((item) => Object.values(item));        this.addSheetData(apiData, res);        this.currentPage = res.paginator.currentPage + 1;        console.log("res", res);        this.getPersent(res);        const isSplit =          res.paginator.currentPage * res.paginator.size >=          this.multiFileSize * (this.multiFileNum + 1);        if (isSplit) {          this.splitExport();        }        if (res.paginator.currentPage  this.multiFileNum * this.multiFileSize      ) {        this.multiFileNum += 1;        this.exportExcel(          this.filename + this.multiFileNum + ".xlsx",          this.ws_name + this.multiFileNum        );      } else if (!this.multiFileNum) {        this.exportExcel(this.filename + ".xlsx", this.ws_name);      }    },    //拆分成多个excel导出    splitExport() {      this.multiFileNum += 1;      this.exportExcel(        this.filename + this.multiFileNum + ".xlsx",        this.ws_name + this.multiFileNum      );      //重置表格      this.ws = XLSX.utils.aoa_to_sheet([this.tableTitle]);    },    addSheetData(apiData, res) {      //添加数据到表格 origin为每次添加数据从第几行开始      XLSX.utils.sheet_add_json(this.ws, apiData, {        skipHeader: true,        origin:          (this.currentPage - 1) * res.paginator.size -          this.multiFileSize * this.multiFileNum +          1,      });    },    //导出所有数据到一个excel    exportExcel(fileName, sheetName) {      // 文件名称      const filename = fileName;      //Excel第一个sheet的名称      const ws_name = sheetName;      // 创建wokbook      const wb = XLSX.utils.book_new();      // 将数据添加到工作薄      XLSX.utils.book_append_sheet(wb, this.ws, ws_name);      // 导出文件      XLSX.writeFile(wb, filename);    },  },};

    调用示例:

    App.vue

          

    测试表格导出

    Dropdown 1st menu item 2nd menu item 3rd item import ExportExcelComponent from "./ExportExcelComponent/ExportExcelComponent.vue";import { asyncDataApi } from "./request";import dayjs from "dayjs";export default { data() { return { listQuery: { name: "yyy", age: 18, }, isCustom: true, asyncDataApi: null, title: [ { code: "id", title: "序号" }, { code: "hobby", title: "爱好" }, { code: "name", title: "姓名" }, { code: "age", title: "年龄" }, // { code: "hobby", title: "爱好" }, { code: "sex", title: "性别" }, { code: "address", title: "地址" }, { code: "birthday", title: "生日" }, { code: "createTime", title: "创建时间" }, { code: "updateTime", title: "更新时间" }, { code: "remark", title: "备注" }, { code: "status", title: "状态" }, ], }; }, methods: { handleDateFilter(data) { const res = data.reduce((pre, cur) => { for (let i in cur) { if (i === "createTime") { cur[i] = dayjs(cur[i] * 1000).format("YYYY-MM-DD HH:mm:ss"); } } pre.push(cur); return pre; }, []); return res; }, async handleMenuClick(val) { // const titleNewData = []; // for (let i = 1; i { // titleNewData.push({ code: item.code + i, title: item.title + i }); // }); // } // this.title = titleNewData; console.log("点击了导出excel", val); await (this.asyncDataApi = asyncDataApi); this.$refs.export.startExport(); }, // async handleClick() { // console.log("点击了导出excel"); // await (this.asyncDataApi = asyncDataApi); // this.$refs.export.startExport(); // }, }, components: { ExportExcelComponent, },};

    mock数据:

    request.js

    const asyncDataApi = (listquery) => {  console.log("params", listquery);  // 模拟异步请求接口  return new Promise((resolve, reject) => {    setTimeout(() => {      const data = [];      for (let i = listquery.page * 100; i  

    开发文档

    调用方式:

    如果不采用自定义dom的话,直接点击默认的按钮可直接导出表格数据; 如果采用自定义dom的话,通过ref实例调用子组件内的startExport方法,执行导出操作

                  导出excel      ​  import { asyncDataApi } from '@/api/member'  export default{    data(){      return:{        titles:[]        asyncDataApi,      }    }    methods:{      handleClick(){        this.$refs.export.startExport();      }    }  }

    API

    属性如下

    参数说明类型默认值
    listQuery请求参数Object{}
    asyncDataApi请求数据的api函数Function必传
    tableTitleData导出的数据表的表头Array必传
    isCustom是否自定义dom,如果采用插槽,需要开启该属性,否则dom为默认button;可以传递 v-slot:custom 来自定义 dom。Booleanfalse
    modelTitle模态框标题String"导出excel"

    multiFileSize

    拆分成每个表多少条数据,需要搭配isSplit属性一起使用Number10e3
    filename导出的excel的表名String"Excel" + new Date().getTime()
    ws_namesheet名String"Sheet"
    filterFunction自定义过滤函数;可在业务层处理数据格式,如时间格式化等Function(data)null

    FAQ

    filterFunction怎么使用

                  导出excel      ​  import { asyncDataApi } from '@/api/member'  export default{    data(){      return:{        titles:[]        asyncDataApi,      }    }    methods:{      handleDateFilter(data) {       const res = data.reduce((pre, cur) => {        for (let i in cur) {          if (i === "createTime") {            cur[i] = dayjs(cur[i] * 1000).format("YYYY-MM-DD HH:mm:ss");            }          }          pre.push(cur);          return pre;        }, []);        return res;      },      handleClick(){        this.$refs.export.startExport();      }    }  }

    导出表格数据为空是什么情况?

    因为导出的表格数据的顺序和标题的顺序并不一定是一致的,所以在组件内部做了映射排序,一定要确保传入的标题数据在调用导出接口之前执行。如果传递的标题有误,在进行映射的时候,这时标题和表格数据并不匹配,那么就会出现数据映射为空的情况

    Promise Error:"asyncDataApi is required"

    当传递给组件的后端api需要在点击dom后赋值再传递的时候,一定要确保在导入后端api之后再调用组件内的导出方法,否则因为后端api还没传递过去就调用,然后抛错或者导出异常

    正确示例:

    async handleClick() {  await (this.asyncDataApi = asyncDataApi);  this.$refs.export.getTableData();},

    后端导出表格api数据返回格式

    因该组件为全局组件,方便以后复用,需与后端协商规定好数据导出的格式。以下为笔者的公司,与后端同事协商的数据格式。大家可根据自己公司需要,更改以上源码中后端返回值字段。

    //后端返回数据结构{    "status": true,    "data": [        {...},        {...},    ],    "paginator": {        "currentPage": 1,        "total": 200,        "size": 20,        "page": 10    }}

上一篇:cs怎么选服务器2?

下一篇:搭建魅力棋牌网站全攻略,吸引玩家,轻松上手!