xlsx实现excel下载功能——js

js 复制代码
import dayjs from "dayjs";
import * as XLSX from "xlsx";
import XLSXS from "xlsx-style";

export function export2Excel(info: {
  // 文件名(工作簿名)
  title: string;
  // 数据
  data: any[] | Record<string, any>[];
  // 表格列
  header: {
    title: any;
    key: any;
  }[];
}) {
  const { title, data, header } = info;

  // title重命名
  const tableTitle = `${title}_评论数据_${dayjs().format(
    "YYYY-MM-DD_HH_mm_ss"
  )}`;

  // 创建一个工作簿
  const wb = XLSX.utils.book_new();
  // 添加sheet到工作簿中
  addSheet({
    xl: XLSX,
    wb,
    data,
    header,
    tableTitle,
  });

  // 文件存储成blob流数据(二进制流)
  const tmpDown = new Blob([
    s2ab(
      // 为了解决样式丢失问题只能用XLSXD来进行转换
      XLSXS.write(wb, {
        bookType: "xlsx",
        type: "binary",
      })
    ) as any,
  ]);

  // 下载二进制流(title是下载文件的名字)
  downExcel(tmpDown, tableTitle + ".xlsx");
}

/**
 * 添加sheet页
 * @param info
 * wb: 工作簿
 * xl: XSLX库
 * data: 表格数据
 * header: 表格头
 * title: 表格标题
 *
 */
const addSheet = (info: {
  wb: any;
  xl: any;
  data: any[] | Record<string, any>[];
  header: {
    title: any;
    key: any;
  }[];
  tableTitle: string;
}) => {
  const { wb, xl, data, header, tableTitle } = info;
  const keyArr = header.map((item: any) => item.key);
  const nameArr = header.map((item: any) => item.title);

  const excelData = [nameArr];

  data.forEach((item: any) => {
    const dataArr: any = [];
    keyArr.forEach((key: any) => {
      dataArr.push(item[key]);
    });
    excelData.push(dataArr);
  });

  // excelData:第一行是表头其他行是数据

  // xl.utils.aoa_to_sheet: 将js数据转换成excel数据,类型是A1,A2,A3,B1,B2,B3......,并创建sheet
  const tableData = xl.utils.aoa_to_sheet(excelData); // 此处excelData为表格的数据

  // tableData格式:[excel对应的行列:{v:值,s:表格显示配置}]

  // tableData['!cols']:定义导出表格每列的宽度,从左到右依次对应header中从左到右的列
  tableData["!cols"] = [];
  tableData["!rows"] = [{ hpx: 40 }, { hpx: 40 }, { hpx: 40 }];

  // 给导出的excel做样式处理
  header.forEach((col: any, colIndex: any) => {
    // 设置列宽
    switch (col.key) {
      case "commentTime":
        tableData["!cols"].push({ wch: 18 });
        break;
      case "connect":
        tableData["!cols"].push({ wch: 25 });
        break;

      default:
        tableData["!cols"].push({ wch: 15 });
        break;
    }

    // 格式化tableData数据格式,添加样式
    excelData.forEach((row, rowIndex) => {
      // 该数据对应的单元格位置
      const cellPosition = createPosition(colIndex, rowIndex);
      if (tableData[cellPosition]) {
        // 单元格的样式/主题(如果适用)
        tableData[cellPosition].s = {
          // 数据水平垂直居中
          alignment: {
            horizontal: "left",
            vertical: "center",
          },
          fill: {
            // 背景色
            // fgColor: { rgb: "FEE8D9" },
          },
          border: {
            top: { style: "thin", color: { rgb: "ebebeb" } },
            bottom: { style: "thin", color: { rgb: "ebebeb" } },
            left: { style: "thin", color: { rgb: "ebebeb" } },
            right: { style: "thin", color: { rgb: "ebebeb" } },
          },
          // border: {
          //   right: {
          //     style: "dashed",
          //     color: { rgb: "FFFFFF" },
          //   },
          // },
        };
        // 第一行
        if (rowIndex === 0) {
          tableData[cellPosition].s.fill = {
            // 背景色
            // fgColor: { rgb: "fb731a" },
          };
          tableData[cellPosition].s.font = {
            // 字体
            sz: "12",
            // color: { rgb: "FFFFFF" },
            bold: true,
            shadow: true,
          };
        } else if (
          typeof row[colIndex] === "string" &&
          row[colIndex].includes("%")
        ) {
          // 格式化百分号数据为excel中的number类型
          let value = tableData[cellPosition].v;
          value = Number(value.replace("%", ""));
          tableData[cellPosition].v = (value / 100).toFixed(4);
          // 单元格展示类型
          tableData[cellPosition].z = XLSX.SSF._table[10];
          // 单元格数据类型
          tableData[cellPosition].t = "n";
        }
      }
    });
  });

  // 将sheet追加到工作簿, wb:工作簿,tableData:新的sheet,title:sheet名称
  // sheet 名称 不能 超过 31 位,否则 会报错
  xl.utils.book_append_sheet(wb, tableData, tableTitle.substring(0, 31)); // title为自定义的sheet表名
};

// 自定义下载文件方法
/**
 * 使用a标签将文件进行下载
 * @param obj blob流数据
 * @param fileName 文件名
 */
const downExcel = (obj: any, fileName: string) => {
  const a_node = document.createElement("a");
  a_node.download = fileName;
  if ("msSaveOrOpenBlob" in navigator) {
    //  msSaveOrOpenBlob 方法允许用户在客户端上下载文件
    (navigator as any).msSaveOrOpenBlob(obj, fileName);
  } else {
    // createObjectURL根据传入的参数创建一个指向该参数对象的URL.
    a_node.href = URL.createObjectURL(obj);
  }
  a_node.click();
  //
  setTimeout(() => {
    // 释放一个通过URL.createObjectURL()创建的对象URL
    URL.revokeObjectURL(obj);
  }, 2000);
};

/**
 * 文件流转换:将二进制字符串转换成对应的Unicode编码数组
 * @param s 文件的二进制字符串
 * @returns 返回存储文件数据的buffer或数组
 */
const s2ab = (s: any) => {
  if (typeof ArrayBuffer !== "undefined") {
    // buffer存储
    // 在内存中分配s.length个的字节空间
    const buf = new ArrayBuffer(s.length);
    // Uint8Array 数组类型表示一个 8 位无符号整型数组,创建时内容被初始化为 0
    const view = new Uint8Array(buf);
    // 将二进制字符串转换成Unicode编码存储
    for (let i = 0; i != s.length; ++i) {
      view[i] = s.charCodeAt(i) & 0xff;
    }
    return buf;
  } else {
    // 数组存储
    const buf = new Array(s.length);
    for (let i = 0; i != s.length; ++i) {
      buf[i] = s.charCodeAt(i) & 0xff;
    }
    return buf;
  }
};

// 行数 需要 小于 26 * 26
/**
 *
 * @param colIndex 该数据对应列数
 * @param rowIndex 该数据对应的行数
 * @returns 返回的是该数据对应的单单元个位置
 */
const createPosition = (colIndex: any, rowIndex: any) => {
  rowIndex++;
  if (colIndex >= 26) {
    const n1 = Math.floor(colIndex / 26) - 1;
    const n2 = colIndex % 26;
    const s1 = String.fromCharCode(n1 + 65);
    const s2 = String.fromCharCode(n2 + 65);
    return s1 + s2 + rowIndex;
  } else {
    // fromCharCode:将 Unicode 编码转为一个字符 (65对应A)
    return String.fromCharCode(colIndex + 65) + rowIndex;
  }
};
相关推荐
宁波阿成19 分钟前
基于jeecgboot-vue3的Flowable流程-集成仿钉钉流程(一)图标svgicon的使用
前端·vue3·flowable·jeecgboot
前端程序猿i21 分钟前
主流 Canvas 库对比:Fabric.js、Konva.js 和 Pixi.js
运维·javascript·fabric
GISer_Jing1 小时前
Node.js学习路线
前端·javascript·node.js
Wang's Blog2 小时前
Webpack: 持久化缓存大幅提升构建性能
前端·缓存·webpack
程序员储物箱2 小时前
Vue报错:Module not found: Error: Can‘t resolve ‘less-loader‘ in ‘文件地址‘
前端·vue
乐吾乐科技2 小时前
【国产开源可视化引擎Meta2d.js】锚点
前端·编辑器·web·数据可视化·大屏端
Her...3 小时前
electron教程(一)创建项目
前端·javascript·electron
百事牛3 小时前
设置和取消Excel“打开密码”的3种方法
windows·excel
logstach4 小时前
QML-Grid和OpacityMask
前端·qml
C+ 安口木4 小时前
前端代码规范 - 日志打印规范
前端·代码规范