业务层-hooks封装之useExport

307 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情

名称:useExport

业务需求:

点击一个导出按钮,导出选中的数据或者默认全部数据成为excel下载下来

封装原因:

业务需求中充斥着大量臃肿的代码,以下为项目上的逻辑功能块

image.png

    Request.$httpXMLInstance({
      url: Service.parse("exportProjectFileInfo"),
      method: "post",
      headers: {
        "Content-Type": "application/json",
      },
      data,
    }).then((res) => {
      let fileName = "项目文件" + DateHelper.currentDate() + ".xlsx";
      let fileType = "application/vnd.ms-excel;charset=utf-8";
      var blob = new Blob([res], {
        type: fileType,
      });
      if ("download" in document.createElement("a")) {
        var downloadElement = document.createElement("a");
        var href = window.URL.createObjectURL(blob); // 创建下载的链接
        downloadElement.href = href;
        downloadElement.download = fileName; // 下载后文件名
        document.body.appendChild(downloadElement);
        downloadElement.click(); // 点击下载
        document.body.removeChild(downloadElement); // 下载完成移除元素
        window.URL.revokeObjectURL(href); // 释放掉blob对象
      } else {
        // IE10+下载
        navigator.msSaveBlob(blob, fileName);
      }

实现一个导出的功能,逻辑写这么多,麻烦,思考下,我们能不能这么干:
useXXX('接口别名xxx', data)

是不是很爽呢?

Hooks设计

那我们开始设计

设计思路:

1、请求入参需要兼容字符串和对象方式,方便业务兼容调用
2、接口返回的二进制文件流,需要转换成浏览器的Blob,然后使用a标签下载

参数逻辑处理

进行了多次判断,如果是对象从对象的key中获取

String

  • 接口别名

Object

  • name 接口别名
  • headers 请求头信息
  • fileName 文件名称
  • fileType 文件类型
  if(typeof options == 'object' ){
    name = options.name ?? ''
    options.headers && (headers = { ...exportheaders, ...options.headers })
    //文件名:判断是否为函数
    if (typeof options.fileName == 'function') {
      fileName = options.fileName && options.fileName()
    } else {
      fileName = options.fileName || getDefaultName()
    }
    // 文件类型
    fileType = options?.fileType
  }else{
    name = options
    fileName = getDefaultName()
  }
/**
 * 获取默认文件名
 * @returns 文件名称 xxx.
 */
const getDefaultName = () => {
  // fileName = getHashCode(randomString(5)) + '.xlsx'
  return DateHelper.currentDate() + '.xlsx'
}

默认文件名
调用 getDefaultName方法,考虑了2种方式

  • 生成5位随机数,随机数生成HashCode
  • 根据当前日期生成,代码都已提供,目前采用当前日期方式

返回二进制文件流-下载

  // 返回二进制文件流
  const result = await Service.useHttpXML({ name, headers }, data)
  // 文件流转换后进行下载
  download(formatToBlob(result, fileType), fileName)

Blob 解释

  • Blob: 前端的一个专门用于支持文件操作的二进制对象
  • ArrayBuffer:前端的一个通用的二进制缓冲区,类似数组,但在API和特性上却有诸多不同
  • Buffer:Node.js提供的一个二进制缓冲区,常用来处理I/O操作

image.png

关于前端文件流的参考文件: www.cnblogs.com/penghuwan/p…

代码实践:

import { Service } from '@basic-library'
import { DateHelper } from '@basic-utils';

const exportheaders = {
  "Content-Type": "application/json"
}

/**
 * 格式化成为 Blob
 * @param {*} result  
 * @param {*} result  
 * @returns 
 */
const formatToBlob = (result, fileType) => {
  const type = fileType || 'application/vnd.ms-excel;charset=utf-8'
  return new Blob([result], { type })
}

/**
 * 文件流-下载
 * @param {*} blob 文件流
 * @param {*} fileName 文件名称
 */
const download = (blob, fileName) => {
  if ('download' in document.createElement('a')) {
    var downloadElement = document.createElement('a')
    var href = window.URL.createObjectURL(blob) // 创建下载的链接
    downloadElement.href = href
    downloadElement.download = fileName 
    // 下载后文件名
    document.body.appendChild(downloadElement)
    // 点击下载
    downloadElement.click() 
    // 下载完成移除元素
    document.body.removeChild(downloadElement)
    // 释放掉blob对象
    window.URL.revokeObjectURL(href) 
  } else { // IE10+下载
    navigator.msSaveBlob(blob, fileName)
  }
}
/**
 * 生成随机数
 * @param {*} e 位数
 * @returns 
 */
const randomString =(e)=>{
  e = e || 32;
  var t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678",
  a = t.length,
  n = "";
  for (i = 0; i < e; i++) n += t.charAt(Math.floor(Math.random() * a));
  return n
}

/**
 * 获取字符串的哈希值
 * @param {String} str
 * @param {Boolean} caseSensitive
 * @return {Number} hashCode
 */
 const getHashCode =(str,caseSensitive)=>{
  if(!caseSensitive){
      str = str.toLowerCase();
  }
  // 1315423911=b'1001110011001111100011010100111'
  var hash  =   1315423911,i,ch;
  for (i = str.length - 1; i >= 0; i--) {
      ch = str.charCodeAt(i);
      hash ^= ((hash << 5) + ch + (hash >> 2));
  }
  return  (hash & 0x7FFFFFFF);
}

/**
 * 获取默认文件名
 * @returns 文件名称 xxx.
 */
const getDefaultName = () => {
  // fileName = getHashCode(randomString(5)) + '.xlsx'
  return DateHelper.currentDate() + '.xlsx'
}

/**
 * 文件下载
 * @param {*} options String|Object
 * --String
 * 接口别名
 * --Object
 * name 接口别名
 * headers 请求头信息
 * fileName 文件名称
 * fileType 文件类型
 * @param {*} data 接口传递参数
 */
const useExport = async (options, data) => {
  // 请求头信息
  let headers = ""
  // 接口别名
  let name = ""
  // 默认'application/vnd.ms-excel;charset=utf-8'
  let fileType = ""

  if(typeof options == 'object' ){
    name = options.name ?? ''
    options.headers && (headers = { ...exportheaders, ...options.headers })
    //文件名:判断是否为函数
    if (typeof options.fileName == 'function') {
      fileName = options.fileName && options.fileName()
    } else {
      fileName = options.fileName || getDefaultName()
    }
    // 文件类型
    fileType = options?.fileType
  }else{
    name = options
    fileName = getDefaultName()
  }
  
  // 返回二进制文件流
  const result = await Service.useHttpXML({ name, headers }, data)
  // 文件流转换后进行下载
  download(formatToBlob(result, fileType), fileName)
}

export default useExport

业务使用:

useExport('接口别名xxx', data)

useExport({
  name: '接口别名xxx',
  fileName: '岗位标准.xlsx',
}, data)

useExport({
  name: '接口别名xxx',
  fileName: () => '岗位标准' + DateHelper.currentDate() + '.xlsx',
}, data)

封装,完毕~~!还没测试哦....

f82dfc1a18004c55ade3641c0b6dbbb.jpg