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

这篇具有很好参考价值的文章主要介绍了前端Excel导出实用方案(完整源码,可直接应用)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言:

技术选型:

主要功能点:

核心代码:

完整代码:

开发文档


前言:

在前后端分离开发为主流的时代,很多时候,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;
// 创建sheet
const ws = XLSX.utils.aoa_to_sheet([this.tableTitle]);
//添加数据
XLSX.utils.sheet_add_json(ws, apiData, {
   skipHeader: true,
   origin:origin
});
// 创建wokbook
const wb = XLSX.utils.book_new();
// 将数据添加到工作薄
XLSX.utils.book_append_sheet(wb, ws, ws_name);
// 导出文件
XLSX.writeFile(wb, filename);

完整代码:

安装sheetjs

npm i xlsx

全局导出组件代码:

ExportExcelComponent.vue

<template>
  <div id="excel-export">
    <slot name="custom" v-if="isCustom"></slot>

    <a-button ghost type="primary" @click="startExport" v-else>
      导出excel
    </a-button>

    <a-modal
      v-if="visible"
      v-model="visible"
      :title="modelTitle"
      :maskClosable="false"
      :closable="false"
    >
      <template #footer>
        <a-button
          type="primary"
          ghost
          v-if="isAbnormal"
          :loading="btnLoading"
          @click="startExport"
        >
          重新导出
        </a-button>
        <a-button
          type="primary"
          ghost
          v-if="isAbnormal"
          :loading="btnLoading"
          @click="getTableData"
        >
          继续导出
        </a-button>
        <a-button :loading="btnLoading" @click="handleClose"> 关闭 </a-button>
      </template>
      <a-progress
        :percent="percent"
        :status="progressStatus"
        class="progress"
      />
    </a-modal>
  </div>
</template>
<script>
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 < res.paginator.page) {
          this.getTableData();
          return;
        }

        //当数据不满足拆分数量时触发
        this.hadnleOneExport(res);

        this.percent += 2;
        this.btnLoading = false;
        this.$message.success("导出成功");
      } catch (error) {
        console.log(error);
        this.$message.error("网络错误,请稍后再试");
        this.handleAbnormal();
      }
    },
    //当数据不满足拆分数量时触发
    hadnleOneExport(res) {
      if (
        this.multiFileNum &&
        res.paginator.total > 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);
    },
  },
};
</script>

调用示例:

App.vue

<template>
  <div>
    <h1>测试表格导出</h1>
    <div>
      <ExportExcelComponent
        :tableTitleData="title"
        :asyncDataApi="asyncDataApi"
        :isCustom="isCustom"
        :listQuery="listQuery"
        ref="export"
        :filterFunction="handleDateFilter"
      >
        <template #custom>
          <!-- <a-button type="primary" @click="handleClick">导出excel</a-button> -->
          <a-dropdown-button>
            Dropdown
            <a-menu slot="overlay" @click="handleMenuClick">
              <a-menu-item key="1">
                <a-icon type="user" />1st menu item
              </a-menu-item>
              <a-menu-item key="2">
                <a-icon type="user" />2nd menu item
              </a-menu-item>
              <a-menu-item key="3">
                <a-icon type="user" />3rd item
              </a-menu-item>
            </a-menu>
          </a-dropdown-button>
        </template>
      </ExportExcelComponent>
    </div>
  </div>
</template>
<script>
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 < 500; i++) {
      //   this.title.forEach((item) => {
      //     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,
  },
};
</script>

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 < (listquery.page + 1) * 100; i++) {
        const obj = {
          id: i - 99,
          name: "姓名" + i,
          age: 20 + i,
          hobby:
            "赵客缦胡缨,吴钩霜雪明。银鞍照白马,飒沓如流星。十步杀一人,千里不留行。事了拂衣去,深藏身与名。闲过信陵饮,脱剑膝前横。将炙啖朱亥,持觞劝侯嬴。" +
            i,
          sex: "男" + i,
          birthday: "2020-01-01",
          createTime: "1701155392",
          updateTime: "2020-01-01",
          remark: "备注" + i,
          status: "1" + i,
        };

        // let newObj = {};
        // for (var a = 1; a < 500; a++) {
        //   for (let k in obj) {
        //     newObj[k + a] = obj[k];
        //   }
        // }
        // data.push(newObj);
        data.push(obj);
      }

      resolve({
        data,
        code: 200,
        msg: "请求成功",
        paginator: {
          page: 1000,
          size: 100,
          total: 100000,
          currentPage: listquery.page,
        },
      });
    }, 100);
  });
};
export { asyncDataApi };

开发文档

调用方式:

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

<template>
  <ExportExcelComponent
    ...
    :isCustom = "true"
    :asyncDataApi="asyncDataApi"
    :tableTitleData="titles"
    ref="export"
  >
    <template #custom>
        <a-button type="primary" @click="handleClick">导出excel</a-button>
    </template>
  </ExportExcelComponent>
</template>
​
<script>
  import { asyncDataApi } from '@/api/member'
  export default{
    data(){
      return:{
        titles:[]
        asyncDataApi,
      }
    }
    methods:{
      handleClick(){
        this.$refs.export.startExport();
      }
    }
  }
</script>

API

属性如下

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

multiFileSize

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

FAQ

filterFunction怎么使用

<template>
  <ExportExcelComponent
    ...
    :isCustom = "true"
    :asyncDataApi="asyncDataApi"
    :tableTitleData="titles"
    ref="export"
    :filterFunction="handleDateFilter"
  >
    <template #custom>
        <a-button type="primary" @click="handleClick">导出excel</a-button>
    </template>
  </ExportExcelComponent>
</template>
​
<script>
  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();
      }
    }
  }
</script>

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

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

Promise Error:"asyncDataApi is required"

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

正确示例:

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

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

因该组件为全局组件,方便以后复用,需与后端协商规定好数据导出的格式。以下为笔者的公司,与后端同事协商的数据格式。大家可根据自己公司需要,更改以上源码中后端返回值字段。文章来源地址https://www.toymoban.com/news/detail-763229.html

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

到了这里,关于前端Excel导出实用方案(完整源码,可直接应用)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 前端导入导出excel记录

    前端模块的导入导出excel功能,大体分为两个逻辑。 前端使用导入组件,获取excel,交给 后端处理 前端使用导入组件,获取excel,自己 解析数据 ,然后调用数据存储的方法。 我们分别对这两种方法进行记录。 导出 组件: 方法: api: util: 导入 组件: 方法: 工具方法: 导

    2024年02月12日
    浏览(38)
  • 前端excel导出图片

    有了上一次的excel导出文字,客户还是不满足,又要把所有图片放到excel里,一目了然。 还好,上一次的插件exceljs支持导出图片。 1、放在全局 2、获取图片资源 这里一定要用同步代码async await等图片回来了,我们再去操作导出。 sort就是数组排序,要按后端返回的document_nam

    2024年04月29日
    浏览(33)
  • 前端实现导出Excel

    1. 创建excel文件夹 2. Blob.js 文件夹内容 ↓ Export2Excel.js 文件夹内容 ↓

    2024年02月10日
    浏览(38)
  • 纯前端实现 导入 与 导出 Excel

    最近经常在做 不规则 Excel 的导入,或者一些普通 Excel 的导出,当前以上说的都是纯前端来实现;下面我们来聊聊经常用到的Excel导出与导入的实现方案,本文实现技术栈以 Vue2 + JS 为例 导入分类: 调用 API 完全由后端来解析数据,清洗数据,前端只负责调用 API ; 前端解析

    2024年02月09日
    浏览(44)
  • 【前端】批量导入和导出Excel数据

    excel导入功能需要使用npm包 xlsx ,所以需要安装 xlsx 插件,读取和写入都依赖她 vue-element-admin模板提供了一个导入excel数据的文件,我们只需用即可 代码地址: https://github.com/PanJiaChen/vue-element-admin/blob/master/src/components/UploadExcel/index.vue 将vue-element-admin提供的导入功能新建一个组件

    2024年02月09日
    浏览(49)
  • Vue前端表格导出Excel文件

    分享一个Vue前端导出Excel文件的方法。记录学习! 功能需求 :将表格的全部数据导出Excel格式的文件 前端 :Vue3+Element-Plus 这个导出方法全部为前端操作,后端只需要传入表格数据到前端即可(基础的多表查询,用的内连接) 2.1 核心方法 将这个导出方法单独封装出来,带一

    2023年04月24日
    浏览(91)
  • 基于SpringBoot + EasyExcel + Vue + Blob实现导出Excel文件的前后端完整过程

    首先前端发起HTTP请求之后,后端返回一个Excel输出流,然后前端用Blob类型接收数据,并且解析响应头数据以及提取源文件名,最后用a标签完成下载。 一、后端代码 (1)导入阿里巴巴的EasyExcel依赖(pom.xml) (2)控制层(GameController.java) (3)接口层(IGameService.java) (4)

    2024年02月16日
    浏览(43)
  • xlsx库实现纯前端导入导出Excel

    最近做了前端导入、导出 Excel 的需求,用到了 js-xlsx 这个库,该库文档提供的用例很少,并不是很友好。本文总结一下我是如何实现需求的。 提供一个 Excel 文件,将里面的内容转成 JSON 导入数据 提供一个 JSON 文件,生成 Excel 文件并导出 导入与导出既可以前端做,也可以后

    2023年04月08日
    浏览(52)
  • 使用vue实现导出Excel功能【纯前端】

    最近接手一个项目,其中一个需求是将查询出来table中的数据导出为Excel文件,并下载到本地。 问题来了,这种东西,不是应该后端去实现更好一些吗?如果放在前端做,要拿到全部数据,然后把这些数据进行解析,再进行一系列的骚操作转化成Excel文件,假如数据量少还好,

    2024年02月10日
    浏览(45)
  • Vue前端实现excel的导入、导出、打印功能

    导入导出依赖: npm install xlsx@0.16.9 npm install xlsx-style@0.8.13 --save 安装xlsx-style,运行报错 This relative module was not found: ./cptable in ./node_modules/xlsx-style@0.8.13@xlsx-style/dist/cpexcel.js 解决报错 在node_modulesxlsx-styledistcpexcel.js 807行 的 var cpt = require(\\\'./cpt\\\' + \\\'able\\\'); 改为: var cpt = cptable; 打印

    2023年04月08日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包