纯前端导出Excel、PDF,单元格合并,设置宽高,字体大小,颜色等

1. 导出excel

1.1 Vue2中使用

基础使用

  1. 首先安装js-xlsx库,使用这个库可以完成一些基本的excel导出,如需导出带样式的excel,则需要使用另外一个库,后面会讲到。
  2. 执行如下命令
js 复制代码
npm install --save xlsx
  1. 在untils文件夹下,新建ecxel.js
js 复制代码
const XLSX = require("xlsx");  //使用import有的属性无法找到
export function exportExcel(filename, data) {
    let exc = XLSX.utils.book_new(); // 初始化一个excel文件
    let exc_data = XLSX.utils.aoa_to_sheet(data);   // 传入数据 , 到excel
    // 将文档插入文件并定义名称
    XLSX.utils.book_append_sheet(exc, exc_data, filename);
    // 执行下载
    XLSX.writeFile(exc, filename + '.xlsx');
}
  1. 使用如下
  2. 表格数据结构
  • 数据结构为一个二维数组,从上到下就是表格每一行的内容,数据层可以用循环把数据给push进去
  • excel_data数据可以是前端用已有的数据处理得到,如果前端数据处理太麻烦(下面会讲一些处理方法),直接扔给后端。

1.2 Vue3 中使用

1.在utils文件夹下新建ecxel.js文件

js 复制代码
import * as XLSX from 'xlsx' 
export function exportExcel(filename,data) {
    let exc = XLSX.utils.book_new();
    let exc_data = XLSX.utils.aoa_to_sheet(data); 
    XLSX.utils.book_append_sheet(exc, exc_data, filename);
    XLSX.writeFile(exc, filename + 'xlsx');
}
  1. 使用
js 复制代码
<template>
   <button @click="download">下载表格</button>
</template>
<script  setup>
import { exportExcel } from "./excelConfig"
const exc_data = [['第一列', '第二列' ,'第三列'],
		['aa', 'bb' ,'cc'],
		['dd', 'ee' ,'ff']];
function download() {  exportExcel('vue3导出的表格',this.exc_data)  }
</script>

1.3 基础excel表格的导出

在until文件目录下新建excel.js,封装导出函数,更加灵活多变,不必自己去处理数组里面的每一项数据。

js 复制代码
let XLSX = require('xlsx');

/**
 * 导出Excel的处理函数
 * @param {Array}   headers    需要导出的列 	示例:[{key: 'date', title: '日期'}, {key: 'name', title: '名称'}]
 * @param {Array}   data       导出的数据 		示例:[{date: '2023-05-31', name: 'megen.huang'}, {date: 'name', name: '姓名'}]
 * @param {String} fileName    文件的名称		示例:'导出结果.xlsx'
 * */
function ExportExcelTwo (headers, data, fileName = '导出结果.xlsx') {
	// 获取标题属性名称
	const excel_headers_key = headers.map(rowItem => rowItem.key)

	// 获取标题名称
	const excel_headers_title = headers.map(rowItem => rowItem.title)
	console.log(excel_headers_title);
	// 数据转换
	var excel_data = data.map(rowItem => {
		var arr = [];
		excel_headers_key.forEach(colItem => {
			arr.push(rowItem[colItem])
		})
		return (arr);
	})

	// 将标题添加到第一行
	excel_data.unshift(excel_headers_title)

	const worksheet = XLSX.utils.aoa_to_sheet(excel_data)
	const workbook = XLSX.utils.book_new()

	// 将数据添加到工作薄
	XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1')
	// 导出 Excel
	XLSX.writeFile(workbook, fileName)
}

export default ExportExcelTwo
举个例子:
  1. 现在需要导出下面这table表的excel表格

2. 这个表格的数据格式如图所示:

3. 对数据进行分析,根据excel导出数据要为一个数组,数组里面的每一项都为数组的格式。现在后端的数据,显然不符合要求,所以需要前端对数据进行处理。

4. 导出结果

1.4 导出带样式的表格,如单元格合并,设置宽度,字体大小等

前提是安装xlsx,已经可以导出普通的excel

js 复制代码
npm install --save xlsx file-saver

然后需要安装

js 复制代码
npm install xlsx-js-style
举个例子:合并单元格

先看导出结果:

js 复制代码
import XLSX from "xlsx-js-style";
js 复制代码
 exportExcel () {
      // 需要导出的数据
      let excelData = [
        ['幼儿园课表', null, null, null], // 标题
        ['序号', '课程名称', '教师名称', '上课地点'], // 表头
        ['1', '体育', '大头老师', '操场']
      ]

      let aoa = excelData;
      var sheet = XLSX.utils.json_to_sheet(aoa);

      // 设置标题行单元格合并
      // s即start, e即end, r即row, c即column

      let mergerarr = [
        // 第一行0的3列进行合并
        { s: { r: 0, c: 0 }, e: { r: 0, c: 3 } },
      ];

      sheet['!merges'] = mergerarr;
      const filename = `交接班审阅日志.xlsx`
      // Excel第一个sheet的名称
      const ws_name = 'Sheet1'
      const wb = XLSX.utils.book_new()
      const ws = XLSX.utils.json_to_sheet(aoa, {
        skipHeader: true,
      })
      // 设置宽度
      ws["!cols"] = [
        { wpx: 100, },
        { wpx: 120, },
        { wpx: 120, },
        { wpx: 100, },
      ];
      ws['!merges'] = sheet['!merges']
      XLSX.utils.book_append_sheet(wb, ws, ws_name) // 将数据添加到工作薄
      XLSX.writeFile(wb, filename) // 导出Excel
    },

如果需要导出下面动态数据的表格,那么可以自己设置一个标记,去记录每一行的需要设置的单元格样式。

下面代码思路可以供参考:

PS:数据叫后端处理,尽量不在前端处理一堆数据,不处理,直接叼他

js 复制代码
 handleExcel () {
      // 时间
      let list = [];//处理数据存放的数组
      let mergerarr = [
        { s: { r: 0, c: 0 }, e: { r: 0, c: 9 } },
        { s: { r: 1, c: 0 }, e: { r: 1, c: 2 } },
        { s: { r: 1, c: 3 }, e: { r: 1, c: 6 } },
        { s: { r: 1, c: 7 }, e: { r: 1, c: 9 } },
        { s: { r: 2, c: 0 }, e: { r: 2, c: 9 } },
      ];
      let tagindex = 2;
      let arr = [];
      arr.push(`交班日期:${this.viewData.WorkGiveDateTimeString}`, null, null);
      arr.push(`时间段:${this.viewData.WorkGiveTimePar.split('-')[0]}到次日${this.viewData.WorkGiveTimePar.split('-')[1]}`, null, null, null);
      arr.push(`交班类型:${this.viewData.WorkTypeTag === 1 ? '白班' : '夜班'}`, null, null);
      list.push(arr);
      let arr2 = [];
      arr2.push('一、接班情况');
      for (let i = 1; i < 10; i++) {
        arr2.push(null);
      }
      list.push(arr2);
      // 接班情况
      // 第四五行
      this.contentData.forEach((el) => {
        tagindex++;
        mergerarr.push({ s: { r: tagindex, c: 0 }, e: { r: tagindex, c: 9 } });
        let tagarr = [];
        tagarr.push(el);
        for (let i = 1; i < 10; i++) {
          tagarr.push(null)
        }
        list.push(tagarr);
      });
      // 本班工作情况
      let arr5 = [];
      arr5.push('二、本班工作情况');
      for (let i = 1; i < 10; i++) {
        arr5.push(null)
      };
      // 第六行
      tagindex++;
      mergerarr.push(
        { s: { r: tagindex, c: 0 }, e: { r: tagindex, c: 9 } }
      )
      list.push(arr5);
      const headers = [
        { key: 'indexTag', title: '序号' },
        { key: 'OperationTickSerial', title: '流水号' },
        { key: 'OTStartDateTimeStr', title: '开始时间' },
        { key: 'aresName', title: '装置/区域' },
        { key: 'EquipTagNumber', title: '位号' },
        { key: 'WorkContent', title: '作业内容' },
        { key: 'MalfTypeName', title: '故障类型' },
        { key: 'WorkEndFeedback', title: '处理结果/反馈' },
        { key: 'WorkHandlePerson', title: '处理人' },
        { key: 'IsOperationTickText', title: '是否完成' },
      ]
      // 获取标题属性名称
      const excel_headers_key = headers.map(rowItem => rowItem.key)

      // 获取标题名称
      const excel_headers_title = headers.map(rowItem => rowItem.title)
      // 数据转换
      let data = this.viewData.OperationTickets;
      var excel_data = data.map(rowItem => {
        var arr = [];
        excel_headers_key.forEach(colItem => {
          arr.push(rowItem[colItem])
        })
        return (arr);
      })
      // 将标题添加到第一行
      excel_data.unshift(excel_headers_title)

      // 第七八行
      excel_data.forEach((el, index) => {
        tagindex++;
        list.push(el);
      })
      // 本班工作情况
      let arr6 = [];
      arr6.push('二、交班情况');
      for (let i = 1; i < 10; i++) {
        arr6.push(null)
      };
      tagindex++;
      mergerarr.push({ s: { r: tagindex, c: 0 }, e: { r: tagindex, c: 9 } });
      list.push(arr6);
      // 交班情况
      this.contentData2.forEach((el, index) => {
        tagindex++;
        // 第九,十行
        mergerarr.push({ s: { r: tagindex, c: 0 }, e: { r: tagindex, c: 9 } });

        let tagarr = [];
        tagarr.push(el);
        for (let i = 1; i < 10; i++) {
          tagarr.push(null)
        };
        list.push(tagarr)
      })
      let arr10 = [];
      arr10.push(`交班人员:${this.viewData.WorkGivePerson}`, null, null,);
      arr10.push(`接班人员:${this.viewData.WorkCatchPerson}`, null, null, null);
      arr10.push(`审阅人:${this.viewData.ApprovalPerson === null ? '' : this.viewData.ApprovalPerson}`, null, null);
      list.push(arr10);
      tagindex++;
      // 第十一行
      mergerarr.push(
        { s: { r: tagindex, c: 0 }, e: { r: tagindex, c: 2 } },
        { s: { r: tagindex, c: 3 }, e: { r: tagindex, c: 6 } },
        { s: { r: tagindex, c: 7 }, e: { r: tagindex, c: 9 } },
      );


      let classesName = `${this.viewData.ClassesName}交接班审阅日志`;
      let arr11 = [];
      arr11.push(classesName);
      for (let i = 1; i < 10; i++) {
        arr11.push(null)
      };
      list.unshift(arr11);

      this.listexcel = list;
      this.mergerarr = mergerarr;
    },
    exportTable () {
    //数据处理函数
      this.handleExcel()
      //单元格处理
      let aoa = this.listexcel;
      var sheet = XLSX.utils.json_to_sheet(aoa);

      // 设置标题行单元格合并
      // s即start, e即end, r即row, c即column
      // 合并从--0行0列开始,到0行3列

      sheet['!merges'] = this.mergerarr;
      const filename = `${this.viewData.ClassesName} ${this.viewData.WorkGiveDateTimeString}交接班审阅日志.xlsx`
      // Excel第一个sheet的名称
      const ws_name = 'Sheet1'
      const wb = XLSX.utils.book_new()
      const ws = XLSX.utils.json_to_sheet(aoa, {
        skipHeader: true,
      })
      // 设置宽度
      ws["!cols"] = [
        { wpx: 100, },
        { wpx: 120, },
        { wpx: 120, },
        { wpx: 100, },
        { wpx: 100, },
        { wpx: 100, },
        { wpx: 100, },
        { wpx: 100, },
        { wpx: 100, },
        { wpx: 100, },
      ];
      ws['!merges'] = sheet['!merges']
      XLSX.utils.book_append_sheet(wb, ws, ws_name) // 将数据添加到工作薄
      XLSX.writeFile(wb, filename) // 导出Excel

    },

2 .excel 导出综合案例(如果其他的看不明白,直接看这个)

最终结果: 如果把这个弄懂,相信可以解决绝大部分的需求!!!

js 复制代码
<template>
  <div class="home">
    <el-button type="primary" @click="export">excel导出</el-button>
  </div>
</template>
<script>
import XLSX from "xlsx-js-style";
export default {
  name: 'HomeView',
  components: {
  },
  data () {
    return {
    }
  },
  methods: {
    export () {
      let excelData = [
        ['幼儿园课表', null, null, null], // 标题
        ['序号', '课程名称', '教师名称', '上课地点'], // 表头
        ['1', '体育', '大头老师', '操场'],
        ['2', '体育', '大头老师', '操场'],
        ['3', '化学', '李老师', '操场'],
        ['4', '化学', '李老师', '操场'],
      ];
      /*
      电子表格软件通常需要至少一个工作表,并强制执行 用户界面中的要求。例如,如果删除了最后一个工作表 在程序中,Apple Numbers 将自动创建一个新的空白表。
    SheetJS 写入函数强制执行此要求。 它们在尝试导出空工作簿时会引发错误。
      */
      const wb = XLSX.utils.book_new();//创建新工作簿
      const ws = XLSX.utils.aoa_to_sheet(excelData);//aoa_to_sheet将 JS 数据数组的数组转换为工作表。

      let mergerarr = [
        // 第一行的 0到3列进行合并
        { s: { r: 0, c: 0 }, e: { r: 0, c: 3 } },
        // 将第单列的第五行第六行进行合并
        { s: { r: 4, c: 2 }, e: { r: 5, c: 2 } },

      ];
      // 设置合并的单元格
      ws['!merges'] = mergerarr;
      // 设置宽度
      // !cols 设置列宽
      // cols 为一个对象数组,依次表示每一列的宽度。
      ws["!cols"] = [
        { wpx: 200, },
        { wpx: 120, },
        { wpx: 220, },
        { wpx: 200, },
        { wpx: 200, },
      ];
      // !rows 设置行高
      // rows 为一个对象数组,依次表示每一行的高度
      const rows = [
        { hpx: 20 },
        { hpx: 16 },
        { hpx: 18 },
        { hpx: 28 },
        { hpx: 20 },
      ]
      ws['!rows'] = rows; // 添加到sheet中
      let borderAll = { //单元格外侧框线
        top: {
          style: 'thin',//BORDER_STYLE 是用来设置边框样式的一个字符串,
        },
        bottom: {
          style: 'thin'
        },
        left: {
          style: 'thin'
        },
        right: {
          style: 'thin'
        }
      };
      // 设置每一个单元格
      for (let key in ws) {
        if (ws[key] instanceof Object) {
          if (key === 'B2') {//给B2这个单元格单独设置样式和背景颜色
            ws[key].s = {
              border: borderAll,
              alignment: {
                horizontal: 'center', //水平居中对齐
                vertical: 'center',//垂直居中
                wrapText: 1,//自动换行
              },
              font: {
                sz: 12,//单元格中字体的样式与颜色设置
                color: {
                  rgb: 'FF0187FA'
                }
              },
              bold: false,//是否加粗
              numFmt: 0,
              // fill 颜色填充属性
              fill: {
                fgColor: { rgb: '87CEEB' },
              },
            }
          } else {
            ws[key].s = {
              border: borderAll,
              alignment: {
                horizontal: 'center', //水平居中对齐
                vertical: 'center',//垂直居中
                wrapText: 1,//自动换行
              },
              font: {
                sz: 12,//单元格中字体的样式与颜色设置
                color: {
                  rgb: 'FF0187FA'
                }
              },
              bold: false,//是否加粗
              numFmt: 0,
              // fill 颜色填充属性
              fill: {
                fgColor: { rgb: 'FFFFFF' },
              },
            }
          }
        }
      };
      XLSX.utils.book_append_sheet(wb, ws, "readme demo");//将上面创建好的ws页添加到wb工作簿中表格内名字为readmedemo
      XLSX.writeFile(wb, "xlsx-js-style-demo.xlsx");////调用导出api
    },
  },
  created () { }
}
</script>
<style lang="less" scoped>
</style>

3. 导出PDF

导出pdf简单多了~

1.安装两个库

js 复制代码
npm install html2canvas
js 复制代码
npm install  jspdf
  1. utils文件夹下新建html2pdf.js文件
js 复制代码
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf'

export const htmlToPDF = async (htmlId, title = "报表", bgColor = "#fff") => {
    let pdfDom = document.getElementById(htmlId)
    pdfDom.style.padding = '0 10px !important'
    const A4Width = 595.28;
    const A4Height = 841.89;
    let canvas = await html2canvas(pdfDom, {
        scale: 2,
        useCORS: true,
        backgroundColor: bgColor,
    });
    let pageHeight = (canvas.width / A4Width) * A4Height;
    let leftHeight = canvas.height;
    let position = 0;
    let imgWidth = A4Width;
    let imgHeight = (A4Width / canvas.width) * canvas.height;
    /*
     根据自身业务需求  是否在此处键入下方水印代码
    */
    //添加水印
    const ctx = canvas.getContext('2d');
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.rotate((20 * Math.PI) / 180);
    ctx.font = '20px Microsoft Yahei';
    ctx.fillStyle = 'rgba(184, 184, 184, 0.8)';
    for (let i = canvas.width * -1; i < canvas.width; i += 240) {
        for (let j = canvas.height * -1; j < canvas.height; j += 200) {
            // 填充文字,x 间距, y 间距
            ctx.fillText('水印名33', i, j);
        }
    }

    
    let pageData = canvas.toDataURL("image/jpeg", 1.0);
    let PDF = new jsPDF("p", 'pt', 'a4');
    if (leftHeight < pageHeight) {
        PDF.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
    } else {
        while (leftHeight > 0) {
            PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight);
            leftHeight -= pageHeight;
            position -= A4Height;
            if (leftHeight > 0) PDF.addPage();
        }
    }

    PDF.save(title + ".pdf");
}
  1. 使用

导出结果:

参考链接:

CSDN博客_js导出excel设置样式

Vue 前端导出Excel表格,多级表头合并

xlsx-style ./cptable' 报错解决办法 - 龙果果 - 博客园

ps: 如果你觉得 xlsx-style 的功能还不够全面,不能实现你需求,这里再推荐一个项目 ExcelJS,这个项目的功能更加全面,而且项目也还在维护,可以试试看能否满足需求。

相关推荐
Front思27 分钟前
vue使用高德地图
javascript·vue.js·ecmascript
zqx_71 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己1 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称2 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普3 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H3 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
花花鱼3 小时前
@antv/x6 导出图片下载,或者导出图片为base64由后端去处理。
vue.js