vue3 +ts 导出 Excel 表格(支持多工作簿以及合并单元格)

331 阅读2分钟

好久没写文章了,去年写的一套xlsx 前端导出文档忘记写出来了,因为公司业务原因,需要自己写一套方法,所以打算发出来,如果有什么不好的地方也希望大家能多多指点

推荐使用 npm库 xlsx-js-style

npm i -s xlsx-js-style

使用方法

import { utils as xlsxUtils, writeFile, CellObject, WorkSheet, CellStyle } from 'xlsx-js-style';

// 导出多页面excel configData配置的数据--此方法是多页面导出
export const exportMultipleData = (filename: string, configData: Record<string, any>) => {
    // 非对象不操作
    if (!configData || !(configData instanceof Object)) {
        return false;
    }
    const workbook = xlsxUtils.book_new();

    Object.keys(configData).forEach(key => {
        // key为页面名称
        let options = configData[key];
        let { worksheet } = getWorksheetInfo(options.headers, options.originData, options.extraHandler);
        xlsxUtils.book_append_sheet(workbook, worksheet, key);
    });
    writeFile(workbook, filename + '.xlsx');
};
// 基本版本-导出单个页面
export const exportMultipleHeadersData = (
    filename: string, //文件名
    headers: Cell[], //导出配置
    originData: Record<string, any>[], //data数据
    extraHandler?: (worksheet: WorkSheet) => void
) => {
    const workbook = xlsxUtils.book_new();
    let { worksheet } = getWorksheetInfo(headers, originData, extraHandler);
    xlsxUtils.book_append_sheet(workbook, worksheet);
    writeFile(workbook, filename + '.xlsx');
};

主要方法 getWorksheetInfo

interface Cell {
    key: string; //唯一标识
    title: string; //标题文字
    children?: Cell[]; // 合并单元格
    isLeaf?: boolean;
    maxRowObj?: { value: number };
    row?: number; // row index
    col_s?: number; // column start index
    col_e?: number; //column end index
    header_s?: CellStyle; // header style
    s?: CellStyle; // cell style
    t?: CellObject['t']; // cell data type
    l?: CellObject['l']; //cel link
    headFontStyle?: CellStyle['font'];
    headFontAlign?: CellStyle['alignment'];
    headBg?: string;
    fill?: CellStyle['fill'];
    colFontAlign?: CellStyle['alignment'];
    colBg?: string;
}
// 获取excel信息对象 抽离公共逻辑
const getWorksheetInfo = (
    headersList: Cell[],
    originData: Record<string, any>[],
    extraHandler?: (worksheet: WorkSheet) => void
) => {
    let row = 0;
    let maxRowObj = { value: 0 };
    let maxCol = 0;
    let headers = headersList
    console.log(headers);

    const keys: string[] = [];
    const keyCellMap = new Map<string, Cell>();
    /** dfs to handle header row and col index */
    const dfs = (arr: Cell[]) => {
        arr.forEach(cell => {
            cell.row = row;
            cell.maxRowObj = maxRowObj;
            if (!cell.children) {
                cell.isLeaf = true;
                cell.col_s = maxCol;
                maxCol++;
                cell.col_e = maxCol;
                keys.push(cell.key);
                keyCellMap.set(cell.key, cell);
            } else {
                cell.col_s = maxCol;
                row++;
                cell.col_e = dfs(cell.children);
            }
        });
        maxRowObj.value = Math.max(maxRowObj.value, row);
        row--;
        return maxCol;
    };
    dfs(headers);
    const headerData: CellObject[][] = [];
    const sequence = [headers];
    const merges: typeof worksheet['!merges'] = [];
    /** bfs to handle header cell merge  */
    const bfs = () => {
        while (sequence.length > 0) {
            let temp = sequence.shift();
            if (temp) {
                temp.forEach(cell => {
                    merges.push({
                        s: {
                            r: cell.row!,
                            c: cell.col_s!,
                        },
                        e: {
                            r: cell.children ? cell.row! : cell.maxRowObj!.value,
                            c: cell.col_e! - 1,
                        },
                    });
                    if (!headerData[cell.row!]) {
                        headerData[cell.row!] = [];
                        headerData[cell.row!].length = maxCol;
                    }
                    headerData[cell.row!][cell.col_s!] = { v: cell.title, t: 's', s: cell.header_s };

                    if (cell.children) {
                        sequence.push(cell.children);
                    }
                });
            }
        }
    };
    bfs();
    /** handle data */

    const data: CellObject[][] = originData.map(row => {
        return keys.map(key => {
            const cell = keyCellMap.get(key)!;
            const cellObj: CellObject = { v: row[key], t: 's' };
            if (cell.s) {
                cellObj.s = cell.s;
            }
            if (cell.t) {
                cellObj.t = cell.t;
            }
            return cellObj;
        });
    });

    const excelData = headerData.concat(data);
    const worksheet = xlsxUtils.aoa_to_sheet(excelData);
    console.log(excelData);
    
    worksheet['!merges'] = merges;

    /*设置worksheet每列的最大宽度*/
    const colWidth = headers.map(row =>{
          if(row.wch){
            return {wch:row.wch}
          }
           let val = row.title;
           /*先判断是否为null/undefined*/
           if (val == null) {
               return { wch: 10 };
           } else if (val.toString().charCodeAt(0) > 255) {
               /*再判断是否为中文*/
               return { wch: val.toString().length * 2 + 5 };
           } else {
               return { wch: val.toString().length + 5 };
           }
      }
    );
    console.log(colWidth);
    
    worksheet['!cols'] = colWidth;

    extraHandler && extraHandler(worksheet);
    return {
        worksheet,
    };
};

header配置参考

{
    headers: [
        {
            key: 'scheme_name',
            title: '名称',
            headFontStyle: {
                name: '楷体',
                sz:10,
                bold: true,
                color: {rgb: "ffffff"}
            },
            headFontAlign: { horizontal: 'left', vertical: 'center' }, //设置标题水平竖直方向居中,并自动换行展示
            headBg: '2C497D', //设置标题单元格的背景颜色
            wch:24
        },
]