小小导出,我大前端足矣!

前言

  • 常网IT戳我呀!
  • 常网IT源码上线啦!
  • 本篇录入技术选型专栏,希望能祝君拿下Offer一臂之力,各位看官感兴趣可移步🚶。
  • 有人说面试造火箭,进去拧螺丝;其实个人觉得问的问题是项目中涉及的点 || 热门的技术栈都是很好的面试体验,不要是旁门左道冷门的知识,实际上并不会用到的。
  • 接下来想分享一些自己在项目中遇到的导出功能场景。

如果你觉得你现在做的事情很累,很难,那一定是你方法用错了。

一、问题剖析

那是一个倾盆大雨的早上,花瓣随风雨落在我的肩膀上,是五颜六色的花朵。

我轻轻抚摸着他,随后拨开第一朵花瓣,她不爱我。

拨开第二朵,她爱我。

正当我沉迷于甜蜜的幻想中,后端小白🙋喊道:这个导出你前端应该就能做的吧!

🙋🏻‍♂️那是自然,有什么功能是我大前端做不了的,必须得让你们大开眼界。

二、为什么导出要前端做?

前端导出的场景:

  1. 轻量级数据:如果要导出的表格数据相对较小,可以直接在前端生成和导出,避免服务器端的处理和通信开销。
  2. 数据已存在于前端:如果表格数据已经以 JSON 或其他形式存在于前端,可以直接利用前端技术将其导出为 Excel、CSV 或其他格式。
  3. 实时生成/计算:如果导出的表格需要根据用户输入或动态生成,可以使用前端技术基于用户操作实时生成表格,并提供导出功能。
  4. 快速响应:前端导出表格可以提供更快的响应速度,避免等待服务器端的处理和下载时间。

后端导出的场景:

  1. 大量数据:如果要导出的表格数据量很大,超过了前端处理能力或网络传输限制,那么在服务器端进行导出会更高效。
  2. 安全性和数据保护:敏感数据不适合在前端暴露,因此在服务器端进行导出可以更好地控制和保护数据的安全。
  3. 复杂的业务逻辑:如果导出涉及复杂的业务逻辑、数据处理或数据查询,使用服务器端的计算能力和数据库访问更合适。
  4. 跨平台支持:如果需要支持多个前端平台(如 Web、移动应用等),将导出功能放在服务器端可以提供一致的导出体验。

三、讲解一下在前端做的导出

xlsx、xlsx-style

如果是只做表格导出:www.npmjs.com/package/xls...

如果导出要包含样式:www.npmjs.com/package/xls...

javascript 复制代码
import XLSX from "xlsx";

exportData() {
  let tableName = '表格'

  if(!getVal(this.dataList, 'length')){
    this.$message.info("暂时数据");
    return 
  }


  // 处理头部
  let headers = {
    "B2": "字段-B2",
    "E2": "字段-E2",
  }
  const props = [ "B2", "E2" ]
  let tmp_dataListFilter = [
    {
      "B2": "字段-B2",
      "E2": "字段-E2",
    },
    {
      "E2": "2",
      "B2": "2",
    }
  ]

  tmp_dataListFilter.unshift(headers) // 将表头放入数据源前面
  let wb =  XLSX.utils.book_new();
  let contentWs =  XLSX.utils.json_to_sheet(tmp_dataListFilter, {
    skipHeader: true,   // 是否忽略表头,默认为false
    origin: "A2"    // 设置插入位置
  });
  // /单独设置某个单元格内容
  contentWs["A1"]={
    t:"s",
    v:tableName,
  };
  // /设置单元格合并!merges为一个对象数组,每个对象设定了单元格合并的规侧,
  // /{s:{r:0,c:},e:{r:0,c:2}为一个规则,s:起始位置,e:结束位置,r:行,c:列
  contentWs["!merges"]=[{ s:{r:0,c:0 },e:{r:0,c:props.length - 1 }}]

  // 设置单元格的宽度
  contentWs["!cols"] = []
  props.forEach(p => contentWs["!cols"].push({wch: 35}))
  XLSX.utils.book_append_sheet(wb,contentWs,tableName)    // 表格内的下面的tab
  XLSX.writeFile(wb,tableName + ".xlsx"); // 导出文件名字
},

package.json

json 复制代码
"xlsx": "^0.15.5",
"xlsx-style": "^0.8.13"

大概效果如下:

感觉前端导出也很容易。

哦哦,那你别高兴太早。

四、需求升级:单元格要居中和加粗。

xlsx

尝试使用xlsx-style设样式。

官方文档:github.com/rockboom/Sh...

文档说给单元格设置s为对象

javascript 复制代码
let contentWs = XLSX.utils.json_to_sheet(tmp_dataListFilter, {
  skipHeader: true, // 是否忽略表头,默认为false
  origin: "A2", // 设置插入位置
});
// /单独设置某个单元格内容
contentWs["A1"] = {
  t: "s",
  v: tableName,
  s:{	// 这个是关键s
    font: { bold: true },
    alignment: { horizontal: 'center' }
  }
};

发现设置无效。

有人说要改xlsx、xlsx-style源码:

大概的意思是:修改xlsx.extendscript.js、xlsx.full.min.js更改文件变量。

发现仍然无效。

使用binary方式保存

  1. 首先保存的时候 type要改成 binary方式
  2. 保存的时候需要使用 xlsx-style模块
javascript 复制代码
var writingOpt = { 
  bookType: 'xlsx',
  bookSST: true,
  type: 'binary'    // <--- 1.改这里
}


/* 
2.  type:'array'改为'binary' 后因为下面代码会报错, 打不开excel
new Blob([wbout], { type: 'application/octet-stream' }
要文本转换成数组缓存后再生成二进制对象
*/

// 添加String To ArrayBuffer
function s2ab(s) {
  var buf = new ArrayBuffer(s.length);
  var view = new Uint8Array(buf);
  for (var i = 0; i < s.length; i++) {
    view[i] = s.charCodeAt(i) & 0xFF;
  }
  return buf;
} 

let blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' })

FileSaver.saveAs(blob, exportName)

可以下载了。但依然样式没起作用。

使用 xlsx-style 模块生成文件

首先安装模块

npm install xlsx-style 

在项目里安装报好多错误直接强制安装,不检查依赖。

npm install xlsx-style -force

安装完成后 找不到cptable模块会报错

报错内容如下:

bash 复制代码
./node_modules/xlsx-style/dist/cpexcel.js Module not found: Error: Can't resolve './cptable' in 

这个问题在vue.config.js里配置一下就可以解决。

其他框架自己找找方法,反正只要不让他报错能启动就行。

javascript 复制代码
module.exports = {
//  ...其他配置省略
  configureWebpack: { 
//  ...其他配置省略
    externals:{
      './cptable':'var cptable'
    },
  },

安装完xlsx-style后改代码

javascript 复制代码
import XLSX2 from "xlsx-style";    // 1. 引入模块

// 2. 使用`xlsx-style` 生成。 XLSX.write => XLSX2.write
var wbout = XLSX2.write(wb, writingOpt)

仍然无效。

总结xlsx

大概的意思是说:默认不支持改变样式,想要支持改变样式,需要使用它的收费版本。

本着勤俭节约的原则,很多人使用了另一个第三方库:xlsx-style[4] ,但是使用起来极其复杂,还需要改 node_modules 源码,这个库最后更新时间也定格在了 6年前。还有一些其他的第三方样式拓展库,质量参差不齐。

使用成本和后期的维护成本很高,不得不放弃。

ExcelJS

ExcelJS终于可以了

ExcelJS[5] 周下载量 450k,github star 9k,并且拥有中文文档,对国内开发者很友好。虽然文档是以README 的形式,可读性不太好,但重在内容,常用的功能基本都有覆盖。

最近更新时间是6个月内,试用了一下,集成很简单,再加之文档丰富,就选它了。

arduino 复制代码
npm install exceljs
npm install file-saver 		// 下载到本地还需要另一个库:file-saver

基本操作

javascript 复制代码
//导入ExcelJS
import ExcelJS from "exceljs";

//下载文件
download_file(buffer, fileName) {
	console.log("导出");
	let fileURL = window.URL.createObjectURL(new Blob([buffer]));
	let fileLink = document.createElement("a");
	fileLink.href = fileURL;
	fileLink.setAttribute("download", fileName);
	document.body.appendChild(fileLink);
	fileLink.click();
}

导出xlsx表格的代码

javascript 复制代码
//下面是导出的函数
async export() {
	const workbook = new ExcelJS.Workbook();
	const worksheet = workbook.addWorksheet("Sheet1");
	//这里是数据列表
	const data = [
		{ id: 1, name: "艾伦", age: 20, sex: "男", achievement: 90 },
		{ id: 2, name: "柏然", age: 25, sex: "男", achievement: 86 },
	];
	// 设置列,这里的width就是列宽
	worksheet.columns = [
		{ header: "序号", key: "id", width: 10},
        { header: "姓名", key: "name", width: 10 },
	];
	
	// 批量插入数据
	data.forEach(item => worksheet.addRow(item));
	
	// 写入文件
	const buffer = await workbook.xlsx.writeBuffer();
	//下载文件
	this.download_file(buffer, "填报汇总.xlsx");
}

设置行高和列宽

列宽上面已经有了,这里说明一下行高怎么设置
worksheet.getRow(2).height = 30;

合并单元格

worksheet.mergeCells("B1:C1");

自定义表格样式

javascript 复制代码
//设置样式表格样式,font里面设置字体大小,颜色(这里是argb要注意),加粗
//alignment 设置单元格的水平和垂直居中
const B1 = worksheet.getCell('B1')
B1.font = { size: 20, color:{ argb: 'FF8B008B' }, bold: true }
B1.alignment = { horizontal: 'center', vertical: 'middle' }

ExcelJS实战

javascript 复制代码
import ExcelJS from "exceljs";

//下载文件
download_file(buffer, fileName) {
    console.log("导出");
    let fileURL = window.URL.createObjectURL(new Blob([buffer]));
    let fileLink = document.createElement("a");
    fileLink.href = fileURL;
    fileLink.setAttribute("download", fileName);
    document.body.appendChild(fileLink);
    fileLink.click();
},
async exportClick() {
    const loading = this.$loading({
      lock: true,
      text: "数据导出中,请耐心等待!",
      spinner: "el-icon-loading",
      background: "rgba(0, 0, 0, 0.7)",
    });

  this.tableData = [
    { a: 1, b:2 }
  ]
  const enterpriseVisitsColumns = [
    {
        prop: "a",
        label: "银行",
      },
      {
        prop: "b",
        label: "企业数",
      }
  ]

    // 表格数据:this.tableData
    if (!(this.tableData && this.tableData.length)) {
      this.$message.info("暂无数据");
      loading.close();
      return;
    }

    let tableName = this.tableName;	// 表格名
    const workbook = new ExcelJS.Workbook();
    const worksheet = workbook.addWorksheet(tableName);
    const props = enterpriseVisitsColumns();
    //这里是数据列表
    const data = this.tableData;
    // 设置列,这里的width就是列宽
    let arr = [];
    props.forEach((p) => {
    arr.push({
        header: p.label,
        key: p.prop,
        width: 25,
    });
    });
    worksheet.columns = arr;

    // 插入一行到指定位置,现在我往表格最前面加一行,值为表名
    const rowIndex = 1; // 要插入的行位置
    const newRow = worksheet.insertRow(rowIndex);
    // 设置新行的单元格值
    newRow.getCell(1).value = tableName;	// 值为表名

    // 批量插入数据,上面插一条,这里就是从第二行开始加
    data.forEach((item) => worksheet.addRow(item));

    //设置样式表格样式,font里面设置字体大小,颜色(这里是argb要注意),加粗
    //alignment 设置单元格的水平和垂直居中
    // const B1 = worksheet.getCell("B1");
    // B1.font = { size: 20, color: { argb: "FF8B008B" }, bold: true };
    // B1.alignment = { horizontal: "center", vertical: "middle" };

    // 合并单元格,就是把A1开始到J1的单元格合并
    worksheet.mergeCells("A1:J1");	

    // 批量设置所有表格数据的样式
    worksheet.eachRow((row, rowNumber) => {
    let size = rowNumber == 1 ? 16 : rowNumber == 2 ? 12 : "";
    //设置表头样式
    row.eachCell((cell) => {
        cell.font = {
        size,
        // color:{ argb: 'FF8B008B' },
        bold: true,
        };
        cell.alignment = { horizontal: "center", vertical: "middle" };
    });

    //设置所有行高
    row.height = 30;
    });

    // 写入文件
    const buffer = await workbook.xlsx.writeBuffer();
    //下载文件
    this.download_file(buffer, tableName + ".xlsx");

    loading.close();
},

后记

导出功能并不是说都是前端或者后端实现,要具体情况,具体分析,我相信哪方都可以做,但谁适合做,这个才是我们需要去思考的。

就如同我们项目中,该例子后面也是前端实现的,大数据分页当然还是得后端同学来实现较好。

如果有其他更好的方法也欢迎评论区见,这里提供的只是诸多方法之一。

最后,祝君能拿下满意的offer。

我是Dignity_呱,来交个朋友呀,有朋自远方来,不亦乐乎呀!深夜末班车

👍 如果对您有帮助,您的点赞是我前进的润滑剂。

以往推荐

靓仔,说一下keep-alive缓存组件后怎么更新及原理?

面试官问我watch和computed的区别以及选择?

面试官问我new Vue阶段做了什么?

前端仔,快把dist部署到Nginx上

多图详解,一次性啃懂原型链(上万字)

Vue-Cli3搭建组件库

Vue实现动态路由(和面试官吹项目亮点)

项目中你不知道的Axios骚操作(手写核心原理、兼容性)

VuePress搭建项目组件文档

原文链接

juejin.cn/spost/73398...

相关推荐
齐 飞2 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
神仙别闹19 分钟前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
aPurpleBerry43 分钟前
JS常用数组方法 reduce filter find forEach
javascript
GIS程序媛—椰子1 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0011 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端1 小时前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x1 小时前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
木舟10091 小时前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43912 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢2 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js