前端实现Excel文件的导入导出

前言 

         原本项目要求前端把excel中的数据导入指定表格中,并把指定表格的数据导出到excel中,单纯的数据导入导出功能直接使用 js-xlsx 则满足要求。

        后续要求导出的excel文件带样式,使用js-xlsx无法满足,加上 xlsx-style 则可设置excel样式。

        现在要求根据模板生成excel文件,即把数据插入预设好的excel文件中,由于 js-xlsx 加上XLSX.read(file, {..., cellStyles: true}) 读取excel模板时样式不完整,最终无法生成与excel模板样式一样的文件。查了一下是免费的js-xlsx(SheetJS)不能获取完整样式,专业版还是可以。最后移除 js-xlsx + xlsx-style,使用 ExcelJs 实现。

        注意:SheetJS不能导入 .xls 的文件,如需要导入.xls,需继续保留 js-xlsx

使用js-xlsx实现Excel数据的导入导出

  • 安装 

// 安装
npm install xlsx
// 引入使用
import XLSX from "xlsx";
  • 基本操作 

1、excel工作簿
const workbook = {
    SheetNames: [],           //工作表名数组
    Sheets: {}                //工作表对象 键名对应SheetNames的key
}

//读取(本地或网络)文件,并创建一个工作薄
const workbook = XLSX.read(data, opts);
//创建一个新的工作簿
const workbook = XLSX.utils.book_new();
//通过抓取页面中的 HTML TABLE 创建工作表
const workbook = XLSX.utils.table_to_book(dom_element, opts);
2、工作表  
const worksheet = {
    '!ref': 'A1:C2',         //工作表的范围 必须 否则不显示
    '!margins': [            //合并单元格列表
        {
            s: { c: 1, r: 0 },    //s start 开始;c cols 开始列;r rows 开始行
            e: { c: 4, r: 0 },    //e end  结束;c cols 结束列;r rows 结束行
        }
    ],
    A1: {t: 's', v: 'A'},    //单元格
    A2: {t: 'n', v: 1},
}

const rows = [
    { A: 1, B: 2, C: 3, D: 4 },
    { A: 11, B: 22, C: 33, D: 44 }
]
// 数组对象转为sheet表
const worksheet = XLSX.utils.json_to_sheet(rows);

const rows = [
    [1, 2, 3, 4, 5],
    [11, 22, 33, 44, 55]
]
// 将数组转为sheet
const worksheet = XLSX.utils.aoa_to_sheet(rows);

//接受一个表 DOM 元素并返回一个类似于输入表的工作薄    
const worksheet = XLSX.utils.table_to_sheet(DOM);
4、将工作表附加到工作簿
XLSX.utils.book_append_sheet(workbook, worksheet, sheet_name);
// 第四个参数为true时,该函数将从指定的工作表名称开始。 
// 如果工作簿中存在工作表名称,则将通过查找名称主干并递增计数器来选择新的工作表名称。
XLSX.utils.book_append_sheet(workbook, sheetA, "Sheet2", true); // Sheet2
XLSX.utils.book_append_sheet(workbook, sheetB, "Sheet2", true); // Sheet3
5、写入文件
XLSX.write(workbook, options)
XLSX.writeFile(workbook, filename, options)

 options 参数

名称描述默认值
type输出数据编码,必须,值:base64、binary、string、buffer、array、file
cellDates存储日期为d类型(默认为n)false
bookSST生成共享字符串表false
bookType文档格式类型,必须值:xlsx、xlsm、xlsb、biff8、biff5、biff4、biff3、biff2、xlml、numbers、ods、fods、wk3、csv、txt、sylk、html、dif、dbf、wk1、rtf、prn、eth"xlsx"
sheet单页格式的工作表名称""
compression对基于ZIP的格式使用ZIP压缩false
Props重写工作簿时重写工作簿属性
4、读取文件
XLSX.read(data, options)
XLSX.readFile(filename, options)

options 参数

名称描述默认值
type输入数据编码,必须,值:base64、binary、string、buffer、array、file
raw如果为 true,纯文本解析将不会解析值 false
codepage代码页,如果指定,则在适当时使用代码页 
sheetStubs为存根单元创建 z 类型的单元对象false
sheetRows工作表行数,If >0、读取前 sheetRows 行0
bookProps如果为 true,则仅解析足以获取图书元数据 false
password如果已定义且文件已加密,则使用密码 ""
sheets如果指定,则仅解析指定的工作表
xlfn如果为 true,则在公式中保留 _xlfn. 前缀alse
 5、sheet生成对应格式的数据
let svg = XLSX.utils.sheet_to_csv(worksheet)
let html = XLSX.utils.sheet_to_html(worksheet)
let json = XLSX.utils.sheet_to_json(worksheet)
  • 封装excel文件的导入导出

/* eslint-disable */
require('script-loader!file-saver');
require('./Blob.js');
require('script-loader!xlsx/dist/xlsx.core.min');
function generateArray(table) {
    var out = [];
    var rows = table.querySelectorAll('tr');
    var ranges = [];
    for (var R = 0; R < rows.length; ++R) {
        var outRow = [];
        var row = rows[R];
        var columns = row.querySelectorAll('td');
        for (var C = 0; C < columns.length; ++C) {
            var cell = columns[C];
            var colspan = cell.getAttribute('colspan');
            var rowspan = cell.getAttribute('rowspan');
            var cellValue = cell.innerText;
            if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;

            //Skip ranges
            ranges.forEach(function (range) {
                if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
                    for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
                }
            });

            //Handle Row Span
            if (rowspan || colspan) {
                rowspan = rowspan || 1;
                colspan = colspan || 1;
                ranges.push({ s: { r: R, c: outRow.length }, e: { r: R + rowspan - 1, c: outRow.length + colspan - 1 } });
            }
            ;

            //Handle Value
            outRow.push(cellValue !== "" ? cellValue : null);

            //Handle Colspan
            if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
        }
        out.push(outRow);
    }
    return [out, ranges];
};

function datenum(v, date1904) {
    if (date1904) v += 1462;
    var epoch = Date.parse(v);
    return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
}

function sheet_from_array_of_arrays(data, opts) {
    var ws = {};
    var range = { s: { c: 10000000, r: 10000000 }, e: { c: 0, r: 0 } };
    for (var R = 0; R != data.length; ++R) {
        for (var C = 0; C != data[R].length; ++C) {
            if (range.s.r > R) range.s.r = R;
            if (range.s.c > C) range.s.c = C;
            if (range.e.r < R) range.e.r = R;
            if (range.e.c < C) range.e.c = C;
            var cell = { v: data[R][C] };
            if (cell.v == null) continue;
            var cell_ref = XLSX.utils.encode_cell({ c: C, r: R });

            if (typeof cell.v === 'number') cell.t = 'n';
            else if (typeof cell.v === 'boolean') cell.t = 'b';
            else if (cell.v instanceof Date) {
                cell.t = 'n';
                cell.z = XLSX.SSF._table[14];
                cell.v = datenum(cell.v);
            }
            else cell.t = 's';

            ws[cell_ref] = cell;
        }
    }
    if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
    return ws;
}

function Workbook() {
    if (!(this instanceof Workbook)) return new Workbook();
    this.SheetNames = [];
    this.Sheets = {};
}

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;
}

export function export_table_to_excel(id) {
    var theTable = document.getElementById(id);
    var oo = generateArray(theTable);
    var ranges = oo[1];

    /* original data */
    var data = oo[0];
    var ws_name = "SheetJS";

    var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);

    /* add ranges to worksheet */
    // ws['!cols'] = ['apple', 'banan'];
    ws['!merges'] = ranges;

    /* add worksheet to workbook */
    wb.SheetNames.push(ws_name);
    wb.Sheets[ws_name] = ws;

    var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' });

    saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), "test.xlsx")
}

function formatJson(jsonData) {
}
export function export_json_to_excel(th, jsonData, defaultTitle) {
    /* original data */

    var data = jsonData;
    data.unshift(th);
    var ws_name = "SheetJS";

    var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);


    /* add worksheet to workbook */
    wb.SheetNames.push(ws_name);
    wb.Sheets[ws_name] = ws;

    var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' });
    var title = defaultTitle || '列表'
    saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), title + ".xlsx")
}

使用插件xlsx-style解决导出excel文件带样式

  •  安装

npm install xlsx-style --save 
  • 报错

This relative module was not found:  * ./cptable in ./node_modules/xlsx-style/dist/cpexcel.js 

 找到在\node_modules\xlsx-style\dist\cpexcel.js 807行的var cpt = require('./cpt' + 'able');更换成var cpt = cptable;保存继续。

  • 使用

import XLSXSTYLE from 'xlsx-style'

// 设置某个单元格样式
worksheet.B3.s = {
    // 字体
    font: {
        name: '仿宋',
        sz: 14,
        bold: true
    },
    // 居中
    alignment: {
        horizontal: 'center',
        vertical: 'center',
        wrapText: true
    },
    // 单元格边框
    border: {
        top: {
            style: 'thin'
        },
        bottom: {
            style: 'thin'
        },
        left: {
            style: 'thin'
        },
        right: {
            style: 'thin'
        }
    }
}

// XLSX.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' });
// 更改为
XLSXSTYLE.write(wb, { bookType: 'xlsx', bookSST: false, type: 'binary' , cellStyles: true });

导出excel自定义公式失效

一般情况下添加 cellFormula 则可使用自定义公式,XLSX.write(workbook, {..., cellFormula: true}) ,如果版本过低或其他情况下不起作用,可找到在\node_modules\xlsx-style\xlsx.js\node_modules\xlsx-style\xlsx.js 中 function write_ws_xml_cell() 修改

if (cell.v === undefined && cell.s === undefined && !cell.f) return "";

var o = { r: ref }, v = cell.f ? writetag('f', escapexml(cell.f)) : writetag('v', escapexml(vv));

使用ExcelJs实现Excel导入导出并带样式配置

  •  安装

// 安装
npm install exceljs
// 引入使用
import ExcelJS from "exceljs";
  • 基本操作

1、新建一个excel工作簿
const workbook = new ExcelJS.Workbook();
2、在工作簿中新建一张工作表
const worksheet = workbook.addWorksheet('Sheet1');
3、在工作簿中删除一张工作表
workbook.removeWorksheet(worksheet.id);
4、获取工作簿中的某一张工作表
// 遍历所有工作表
workbook.worksheets.forEach(worksheet => {
    console.log(worksheet);
})
// 注意: workbook.worksheets.forEach 仍然是可以正常运行的, 但是以下的方式更好
workbook.eachSheet((worksheet, sheetId) => {
    console.log(worksheet, sheetId);
});

// 按 name 提取工作表
const worksheet = workbook.getWorksheet('Sheet1');

// 按 id 提取工作表
const worksheet = workbook.getWorksheet(1);
5、获取工作表中的所有数据集
const sheetData = worksheet.getSheetValues();
 6、获取工作表中某列数据
const cB = worksheet.getColumn('B');
const c1 = worksheet.getColumn(1);

// 迭代每列中单元格的数据
c1.eachCell(c => {
    console.log(c.value);
});
// 指定列的全部数据
console.log(c1.values);
7、获取工作表中某行数据
const r3 = worksheet.getRow(3);

// 迭代每行中单元格的数据
r3.eachCell(r => {
    console.log(r.value);
});
// 指定行的全部数据
console.log(r3.values);
8、向指定单元格写入数据
worksheet.getCell('A1').value = '插入指定值';
worksheet.getCell(3, 3).value = '根据行列插入值';
9、向指定列中写入一组数据
const c3 = worksheet.getColumn(3);
c3.values = [1, 2, 3, 4, 5, 6];
 10、向指定行中写入一组数据
const r3 = worksheet.getRow(3);
r3.values = [1, 2, 3, 4, 5, 6];
 11、插入行
// 按键值插入行,每次向下移动行
worksheet.insertRow(1, { id: 1, name: 'John Doe', dob: new Date(1970, 1, 1) });

// 通过连续数组插入一行(分配给列A, B, C)
worksheet.insertRow(1, [3, 'Sam', new Date()]);

// 插入多行数组,如 rows两行,将当前位置1后面的行向下移动2行
var rows = [
    [5, 'Bob', new Date()],
    { id: 6, name: 'Barbara', dob: new Date() }
];
worksheet.insertRows(1, rows, 'o');
参数描述默认值
位置要插入的行号,从那里向下推所有行
值/秒新行/秒值
风格‘i’ 代表从上面的行继承,’i+’ 包括空单元格,’o’ 代表原始样式,’o+’ 包括空单元格,’n’ 代表无‘n’
 12、复制行
 worksheet.duplicateRow(1, 2, false);
参数描述默认值
开始要复制的行号
数量您想要复制该行的次数1
插入

true:插入的重复新行,之前的所有行会向下推;

false:复制的行数直接替换原本的行;

true
13、合并单元格
// 合并一组单元格 
worksheet.mergeCells('A4:B5');
// 从左上到右下进行合并
worksheet. mergeCells('K10', 'M12');
// 按开始行,开始列,结束行,结束列合并(相当于K10:M12)
worksheet.mergeCells(10, 11, 12, 13);
14、取消单元格合并
worksheet.unMergeCells (A4);
  •  封装excel文件的导入导出


import ExcelJS from "exceljs";
import JSZipUtils from "jszip-utils";

function getZipFileBinaryContent(fileUrl) {
    return new Promise((resolve) => {
        JSZipUtils.getBinaryContent(fileUrl, (err, data) => {
            if (err) {
                // 抛出异常
                throw err;
            } else {
                resolve(data);
            }
        });
    });
}

function getColHeader(worksheet, opts) {
    let type = opts ? opts.colHeaderType : null; // 列头类型
    if (type == null) {
        return null; // 无列头类型列头不设置
    }
    let colHeader = {};
    const header = opts.header; // 表头

    // 表头是指定行
    if (type == 'headerRow') {
        let hRow = parseInt(header);
        if (isNaN(hRow) || hRow < 1) {
            hRow = 1;  // 默认第一行
        }
        colHeader = worksheet.getRow(hRow).values;
    }

    // 表头是数组
    if (type == 'headerArray' && Array.isArray(header)) {
        header.forEach((name, index) => {
            colHeader[index + 1] = name;
        });
    }

    // 表头是列属性
    if (type == 'headerColType') {
        const colKey = header || 'number'; // 列属性 无指定默认使用 number
        const keys = opts.targetKeys;  // 目标
        worksheet.columns.forEach(col => {
            let cn = col.number;
            let ck = col[colKey];

            if (ck && keys && keys[ck]) {
                colHeader[cn] = keys[ck];
            } else if (!keys && ck) {
                colHeader[cn] = ck;
            }
        });
    }

    return colHeader
}

/**
 * 导入excel表指定工作表数据
 * @param {File|Blob} file 导入xlsx文件
 * @param {String|Number} [sheetName=1] 工作表名/id
 * @param {Object} [s2JOpts] 数据转换参数
 * @param [s2JOpts.defval] 默认值
 * @param [s2JOpts.colHeaderType] {'headerRow'|'headerArray'|'headerColType'}  列头类型
 * @param [s2JOpts.header] {number|string|array}  表头
 * @param [s2JOpts.targetKeys] {array}  列头目标对应关系,colHeaderType== 'headerColType' 时使用
 * @returns 
 */
export async function import_excel_to_json(file, sheetName, s2JOpts) {
    const workbook = new ExcelJS.Workbook();
    // 读取excel文件
    await workbook.xlsx.load(file);

    // 获取指定工作表,无指定默认获取第一张
    if (!sheetName) sheetName = 1;
    const worksheet = workbook.getWorksheet(sheetName);

    // 获取工作表中的数据集(二维数组,注意下标是从 1 开始)
    const sheetData = worksheet.getSheetValues();

    // 设置数据默认值
    if (s2JOpts && s2JOpts.defval != undefined) {
        for (let i = 1; i <= worksheet.rowCount; i++) {
            if (!Array.isArray(sheetData[i])) {
                sheetData[i] = [];
            }
            for (let j = 1; j <= worksheet.columnCount; j++) {
                if ([null, undefined].includes(sheetData[i][j])) {
                    sheetData[i][j] = s2JOpts.defval
                }
            }
        }
    }

    // 自定义列头
    let colHeader = getColHeader(worksheet, s2JOpts);

    // 表格列头不为空需要重置工作表数据
    if (colHeader) {
        for (let i = 1; i <= worksheet.rowCount; i++) {
            // 定义临时对象存储每一行内容
            let tempObj = {};
            if (Array.isArray(sheetData[i])) {
                for (let n in colHeader) {
                    tempObj[colHeader[n]] = sheetData[i][n];
                }
            }

            // 替换重置数据
            sheetData[i] = tempObj;
        }
    }

    return sheetData;
}

export async function import_excel_to_worksheet(file, valueType) {
    const workbook = new ExcelJS.Workbook();
    // 读取excel文件
    await workbook.xlsx.load(file);
    if (valueType == 'sheetNames') {
        let names = []
        workbook.eachSheet((worksheet, sheetId) => {
            names.push(worksheet.name)
        });
        return names
    }

    return;
}

/**
 * 导出json数据到excel表
 * @param {[{header: string, key:string}]} tableHeader 表头
 * @param tableHeader[].header 表头显示值 例:名称
 * @param tableHeader[].key 表头key 例:name
 * @param {[{string}]|[[]]} jsonArray 导出数据
 * @param jsonArray[].string 导出数据 例:name: '001'
 * @param {string} [fileName='导出数据'] 文件名
 * @param {string} [sheetName='SheetJS'] 工作表面
 */
export async function export_json_to_excel(tableHeader, jsonArray, fileName, sheetName) {
    const workbook = new ExcelJS.Workbook();
    // 新建工作表
    if (!sheetName) sheetName = 'SheetJS';
    const worksheet = workbook.addWorksheet(sheetName);

    if (!tableHeader || tableHeader.length == 0) {
        tableHeader = Object.keys(jsonArray[0]).map(item => ({ header: item, key: item }));
    }
    // 往表中插入表头
    worksheet.columns = tableHeader;

    // 往表中添加行
    worksheet.addRows(jsonArray);

    // 重新生成 Excel 文件下载
    const newFile = await workbook.xlsx.writeBuffer();
    saveAs(new Blob([newFile], { type: "application/octet-stream" }), `${fileName || '导出数据'}.xlsx`);
}


/**
 * 导出excel模板组合文档
 * @param {string} demoUrl excle模板路径
 * @param {{string: {string}}} exportData 导出数据
 * @param exportData.string 导出数据-工作表名
 * @param exportData.string.string 导出数据-工作表名-指定单元格值 例: A1: '测试'
 * @param {string} [fileName='导出模板'] 文件名
 */
export async function export_template_to_excel(demoUrl, exportData, fileName) {
    // 读取并获得模板文件的二进制内容
    const fileContent = await getZipFileBinaryContent(demoUrl);
    const workbook = new ExcelJS.Workbook();
    // 加载 workbook 信息
    await workbook.xlsx.load(fileContent);

    //遍历所有工作表
    workbook.eachSheet((worksheet, sheetId) => {
        const sheetData = exportData[worksheet.name];
        let insertList = {}; // 要插入数据集
        for (let key in sheetData) {
            const keyCell = worksheet.getCell(key)
            let keyValue = sheetData[key];
            // 先插入非数组类型(即当个单元格的值),避免插入数组类型(即在指定行插入多行)时向下推导致插入单元格的值不正确
            if (!Array.isArray(keyValue)) {
                // 在指定单元格插入内容      
                keyCell.value = resetCustomCellValue(keyValue, keyCell);
            } else if (Array.isArray(keyValue) && keyValue.length > 0) {
                const start = keyCell.row; // 复制开始行数
                if (!Array.isArray(insertList[start])) {
                    // 开发复制行还未有数据集,直接赋值
                    insertList[start] = keyValue;
                } else {
                    // 复制行已有数据集,获取最大数据数,逐行赋值
                    const maxNum = insertList[start].length > keyValue.length ? insertList[start].length : keyValue.length;
                    for (let i = 0; i < maxNum; i++) {
                        if (!insertList[start][i]) {
                            insertList[start][i] = keyValue[i];
                        } else {
                            for (let k in keyValue[i]) {
                                insertList[start][i][k] = keyValue[i][k]
                            }
                        }
                    }
                }
            }
        }

        /**
         * 注意:使用duplicateRow、insertRows、spliceRows 方法在指定行插入多行数据,从插入行以下的合并单元格会失效,公式不会自动下移
         * 解决方案:
         *      获取原始合并单元格信息,把插入行下的合并单元格向下推,最后取消原本的再重新合并
         *      获取原行号,插入行后使其行号向下推,再替换掉原来的
         */
        // 获取所有合并单元格信息
        let mergeList = new Array();
        for (let key in worksheet._merges) {
            const m = worksheet._merges[key];
            // 按开始行,开始列,结束行,结束列 存储        
            mergeList.push([m.top, m.left, m.bottom, m.right]);
        }
        // 删除之前所有合并单元格
        for (let key in worksheet._merges) {
            worksheet.unMergeCells(key);
        }

        // 记录插入行后行号移动后值
        let moveRows = { maxMoveAmount: 0 };
        for (let i = 1; i <= worksheet.rowCount; i++) {
            moveRows[i] = i;
        }

        // 开始插入数据
        for (let row in insertList) {
            // row 为原始行,即未插入任何数据时的行
            let start = parseInt(row); // 开始复制行
            for (let i in insertList) {
                // 在row之上有插入数据,row开始行需要向下推
                if (i < row) {
                    start += insertList[i].length - 1;
                }
            }
            const amount = insertList[row].length - 1; // 复制数量
            if (amount > 0) {
                let newMerges = new Array();
                mergeList.forEach(m => {
                    if (m[0] == start && m[2] == start) {
                        // 复制开始行内有合并单元格,所有复制行都需要加上
                        for (let i = 0; i < amount; i++) {
                            const num = start + i + 1;
                            newMerges.push([num, m[1], num, m[3]])
                        }
                    } else if (m[0] == start && m[2] > start) {
                        // 开始行是复制开始行,结束行大于复制开始行,合并单元格需要向下合并复制数量行
                        m[2] += amount;
                    } else if (m[0] > start) {
                        // 开始行在复制开始行之下,需要向下推移复制数量行
                        m[0] += amount;
                        m[2] += amount;
                    }
                })
                mergeList = mergeList.concat(newMerges);

                // 插入复制行
                worksheet.duplicateRow(start, amount, true);

                moveRows.maxMoveAmount += amount;
                for (let r in moveRows) {
                    // 把在原始行下的行号下移复制行数
                    if (r != 'maxMoveAmount' && r.indexOf('#') == -1 && r > row) {
                        moveRows[r] += amount;
                    }
                }
            }

            // 插入数组数据
            let kCol = {};
            insertList[row].forEach((item, index) => {
                // 获取插入行数
                const kRow = start + index;
                if (index != 0) {
                    moveRows[kRow + '#' + start] = kRow;
                }
                for (let k in item) {
                    if (!kCol[k]) {
                        kCol[k] = worksheet.getCell(k).col;
                    }
                    const kCell = worksheet.getCell(kRow, kCol[k]);
                    kCell.value = resetCustomCellValue(item[k], kCell);;
                }
            })
        }

        // 重置所有合并单元格
        mergeList.forEach(m => {
            worksheet.mergeCells(m[0], m[1], m[2], m[3]);
        })

        // 处理自定义公式
        const sheetValues = worksheet.getSheetValues();
        sheetValues.forEach((values, r) => {
            if (Array.isArray(values)) {
                const params = { moveRows: moveRows, curRow: r };
                values.forEach((val, c) => {
                    if (val && val.formula) {
                        worksheet.getCell(r, c).value = resetCustomCellValue(val, null, 'formula', params)
                    }
                })
            }
        })
    });

    // 重新生成 Excel 文件下载
    const newFile = await workbook.xlsx.writeBuffer();
    saveAs(new Blob([newFile], { type: "application/octet-stream" }), `${fileName || '导出模板'}.xlsx`);
}


/**
 * 重置自定义单元格值
 * @param {*} oldVal 单元格值
 * @param {object} [cell] 单元格数据
 * @param {string} [type] 值处理类型
 * @param {object} [params] 指定值处理类型所需参数
 * --- type: 'formula', params: {moveRows: 原始行移动后行数信息, curRow: 当元格所在行 }
 * @returns 
 */
function resetCustomCellValue(oldVal, cell, type, params) {
    let newVal = oldVal;
    if (type == 'formula') {
        let formula = oldVal.formula;
        const { moveRows, curRow } = params;
        const regex = new RegExp('[A-Z]+[0-9]+', 'g');
        let cellKeys = formula.match(regex);
        if (Array.isArray(cellKeys) && moveRows && curRow) {
            cellKeys.sort((a, b) => {
                // 倒叙排列,使其单元格由后往前处理,避免出错
                return b.replace(new RegExp('[A-Z]+'), '') - a.replace(new RegExp('[A-Z]+'), '');
            }).forEach(k => {
                let row = k.replace(new RegExp('[A-Z]+'), '');
                let col = k.replace(new RegExp('[0-9]+'), '');
                let newRow = moveRows[curRow + '#' + row] || moveRows[row] || moveRows.maxMoveAmount + parseInt(row);
                formula = formula.replace(k, col + newRow);
            })
        }
        newVal = { formula: formula };
    } else if (cell && cell.numFmt && typeof oldVal != 'number') {
        // 设置数字属性,非数字类型值需要转换
        let num = parseFloat(oldVal);
        if (!isNaN(num)) {
            newVal = num;
        }
    }

    return newVal;
}

使用

const { import_excel_to_json, export_json_to_excel, export_template_to_excel } = require('./Export2Excel');
或
import { import_excel_to_json, export_json_to_excel, export_template_to_excel } from '@./tableDesign';


// ===================导入===================== 
// colHeaderType 未指定,不对数据重构,直接得到一个二维数组

// colHeaderType = 'headerRow' 拿指定行数据作为表头,重构对象数组
// 例:r1 = ['key1','key2'] jsonData = [{key1:'A1,key2:'B1'},{key1:'A2',key2:'B2'} ...]
let sheet2JSONOpts1 = { colHeaderType: 'headerRow', header: 1 };

// colHeaderType = 'headerArray' 拿指定表头,重构对象数组
// 例:r1 = ['id','name'] jsonData = [{id:'A1,name:'B1'},{id:'A2',name:'B2'} ...]
let sheet2JSONOpts2 = { colHeaderType: 'headerArray', header: ['id', 'name'] };

// colHeaderType = 'headerColType' 拿列指定属性值作为表头,重构对象数组
// 例:未设置targetKeys  jsonData = [{A:'A1,B:'B1'},{A:'A2',B:'B2'} ...]
// 例:targetKeys = { A: 'id', B: 'name' } jsonData =  [{id:'A1,name:'B1'},{id:'A2',name:'B2'} ...]
let sheet2JSONOpts3 = { colHeaderType: 'headerColType', header: 'letter', targetKeys: { A: 'id', B: 'name' } };
const jsonData = await import_excel_to_json(file, 1, sheet2JSONOpts3);


// ===================导出=====================
let tHeader = [
    { header: '流程名称', key: 'flowName' },
    { header: '流程分类', key: 'catename' },
];
let dataList = [
    { flowName: '报销申请', catename: '财务' },
    { flowName: '财务管理', catename: '财务' },
]
export_json_to_excel(tHeader, dataList);


// ===================导出模板=====================
const fileUrl = 'xlsxtemplate.xlsx';
const exportData = {
    Sheet1: {
        A1: '测试xlsx模板标题',
        B3: '姓名',
        D4: 34,
        A6: [
            { A6: 1, B6: 2, C6: 3, D6: 5, E6: 9 },
            { A6: 2, B6: 2, C6: 3, D6: 5, E6: 9 },
            { A6: 3, B6: 2, C6: 3, D6: 5, E6: 9 },
            { A6: 4, B6: 2, C6: 3, D6: 5, E6: 9 }
        ]
    },
    Sheet2: {
        A1: '测试xlsx模板标题11111'
    }
}
export_template_to_excel(fileUrl, exportData);

参考文档:exceljs中文文档|exceljs js中文教程|解析 | npm中文文档 (npmdoc.org)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在 NestJS 中导入导出 Excel 文件可以使用 `exceljs` 库来实现。以下是一个简单的示例: 首先,确保已经安装了 `exceljs` 这个库: ``` npm install exceljs ``` 然后,创建一个导出 Excel 的函数: ```typescript import { Injectable } from '@nestjs/common'; import * as ExcelJS from 'exceljs'; import { Response } from 'express'; @Injectable() export class ExcelService { async exportToExcel(data: any[], res: Response, filename: string) { const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('Sheet 1'); // 将数据写入工作表 worksheet.addRows(data); // 设置响应头,告诉浏览器下载文件 res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); res.setHeader('Content-Disposition', `attachment; filename=${filename}.xlsx`); // 将工作簿数据写入响应流 await workbook.xlsx.write(res); // 结束响应 res.end(); } } ``` 在你的控制器中使用 `ExcelService` 导出 Excel 文件: ```typescript import { Controller, Get, Res } from '@nestjs/common'; import { ExcelService } from './excel.service'; @Controller('excel') export class ExcelController { constructor(private readonly excelService: ExcelService) {} @Get('export') async exportToExcel(@Res() res) { const data = [ ['Name', 'Age'], ['John Doe', 30], ['Jane Smith', 25], // 更多数据行... ]; await this.excelService.exportToExcel(data, res, 'data'); } } ``` 在这个例子中,我们创建了一个 `ExcelService` 来处理 Excel 相关的操作。在 `exportToExcel` 方法中,我们使用 `exceljs` 创建一个工作簿,将数据写入工作表,并将工作簿数据写入响应流中,实现导出 Excel 文件的功能。 在控制器中,我们通过 `@Get('export')` 装饰器创建了一个 GET 路由,当用户访问 `/excel/export` 时,会触发 `exportToExcel` 方法导出 Excel 文件。 注意:以上示例是基于 NestJS 的示例,你需要在自己的项目中相应地进行配置和使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值