解决luckysheet.js导出图片不展示和错位的问题

1,104 阅读4分钟

一、前言

项目中需要excel文档在线编辑的功能,查询资料后,选择了Luckysheet,因为luckysheet可以方便获取表格中的数据,传给后端。文档导出功能使用的是exceljs插件,导出代码参考这篇文章(luckysheet导出excel表格(使用exceljs,支持图片)-CSDN博客),使用过程中发现导出的文档中的图片出现不展示或者错位的问题,在此记录一下解决方案。

二、正文

1、不显示

初始的导出代码用的是指定图片宽高的方式

worksheet.addImage(imageId, { 
    tl: { col: 0, row: 0 }, //图片左上角坐标
    ext: { width: 500, height: 200 } //图片宽高(px)
});

完整代码为

var setImages = function (table, worksheet, workbook) {
    let {
      images,
      visibledatacolumn,//所有行的位置
      visibledatarow //所有列的位置
    } = {...table}
    if (typeof images != 'object') return;
    for (let key in images) {
    // 通过 base64  将图像添加到工作簿
        const myBase64Image = images[key].src;
        //开始行 开始列 结束行 结束列
        const item = images[key];
        const imageId = workbook.addImage({
            base64: myBase64Image,
            extension: 'png'
        });
 
        const col_st = getImagePosition(item.default.left,visibledatacolumn);
        const row_st = getImagePosition(item.default.top,visibledatarow);
 
        //模式1,图片左侧与luckysheet位置一样,像素比例保持不变,但是,右侧位置可能与原图所在单元格不一致
        worksheet.addImage(imageId, {
            tl: { col: col_st, row: row_st},
            ext: { width: item.default.width, height: item.default.height },
        });
        //模式2,图片四个角位置没有变动,但是图片像素比例可能和原图不一样
        // const w_ed = item.default.left+item.default.width;
        // const h_ed = item.default.top+item.default.height;
        // const col_ed = getImagePosition(w_ed,visibledatacolumn);
        // const row_ed = getImagePosition(h_ed,visibledatarow);
        // worksheet.addImage(imageId, {
        //   tl: { col: col_st, row: row_st},
        //   br: { col: col_ed, row: row_ed},
        // });
    }
};

本身代码没有问题,图片不显示是因为luckysheet只处理了模式二,就是需要指定图片的左上角和右下角的位置

// 在 B2:D6 的一部分上插入图像 
worksheet.addImage(imageId2, { 
    tl: { col: 1.5, row: 1.5 }, 
    br: { col: 3.5, row: 5.5 } 
});

luckysheet源码

位置:node_modules/luckyexcel/dist/luckyexcel.cjs.js

var xdrFrom = xdrFroms[0], xdrTo = xdrTos[0], xdr_blipfill = xdr_blipfills[0];
var rembed = getXmlAttibute(xdr_blipfill.attributeList, "r:embed", null);
var imageObject = _this.getBase64ByRid(rembed, drawingRelsFile);
// let aoff = xdr_xfrm.getInnerElements("a:off"), aext = xdr_xfrm.getInnerElements("a:ext");
// if(aoff!=null && aext!=null && aoff.length>0 && aext.length>0){
//     let aoffAttribute = aoff[0].attributeList, aextAttribute = aext[0].attributeList;
//     let x = getXmlAttibute(aoffAttribute, "x", null);
//     let y = getXmlAttibute(aoffAttribute, "y", null);
//     let cx = getXmlAttibute(aextAttribute, "cx", null);
//     let cy = getXmlAttibute(aextAttribute, "cy", null);
//     if(x!=null && y!=null && cx!=null && cy!=null && imageObject !=null){
// let x_n = getPxByEMUs(parseInt(x), "c"),y_n = getPxByEMUs(parseInt(y));
// let cx_n = getPxByEMUs(parseInt(cx), "c"),cy_n = getPxByEMUs(parseInt(cy));
var x_n = 0, y_n = 0;
var cx_n = 0, cy_n = 0;
imageObject.fromCol = _this.getXdrValue(xdrFrom.getInnerElements("xdr:col"));
imageObject.fromColOff = getPxByEMUs(_this.getXdrValue(xdrFrom.getInnerElements("xdr:colOff")));
imageObject.fromRow = _this.getXdrValue(xdrFrom.getInnerElements("xdr:row"));
imageObject.fromRowOff = getPxByEMUs(_this.getXdrValue(xdrFrom.getInnerElements("xdr:rowOff")));
imageObject.toCol = _this.getXdrValue(xdrTo.getInnerElements("xdr:col"));
imageObject.toColOff = getPxByEMUs(_this.getXdrValue(xdrTo.getInnerElements("xdr:colOff")));
imageObject.toRow = _this.getXdrValue(xdrTo.getInnerElements("xdr:row"));
imageObject.toRowOff = getPxByEMUs(_this.getXdrValue(xdrTo.getInnerElements("xdr:rowOff")));
imageObject.originWidth = cx_n;
imageObject.originHeight = cy_n;

可以看到ext的处理代码被注释掉了,图片要展示的话必须要有fromCol、formRow、toCol、toRow四个参数,也就是图片左上角和右下角在excel中的坐标。所以使用前文导出代码中的模式二导出后,在luckysheet导入就能看到图片了。

2、图片错位

图片错位是exceljs的问题,excel中的图片计算出来的坐标有可能是浮点数,而exceljs插入图片,浮点数的话会出现错位,github上找到了解决方案,详情见(github.com/exceljs/exc… 设置tl和br的时候,需要传入nativeCol,nativeRow,nativeColOff,nativeRowOff四个参数,可以省略col和row,nativeCol和nativeRow表示图片所在单元格的行和列,值是整数,nativeColOff和nativeRowOff是锚点在单元格中的偏移量,单位为EMU。(单位换算:ExcelJsColWidthInEMU = ExcelJsColWidth * 10000;ExcelJsrowHeightInEMU = ExcelJsRowHeight * 10000)

改造setImages方法:

var setImages = function (table, worksheet, workbook) {
    let {
        images,
        visibledatacolumn,//所有行的位置
        visibledatarow //所有列的位置
    } = { ...table }
    if (typeof images != 'object') return;
    for (let key in images) {
        // 通过 base64  将图像添加到工作簿
        const myBase64Image = images[key].src;
        //开始行 开始列 结束行 结束列
        const item = images[key];
        const imageId = workbook.addImage({
            base64: myBase64Image,
            extension: 'png'
        });

        // 只有设置tl、br的图片可以被luckysheet识别并展示,设置ext宽高的不行
        const col_st = getImagePosition(item.default.left, visibledatacolumn);
        const row_st = getImagePosition(item.default.top, visibledatarow);
        const w_ed = item.default.left + item.default.width;
        const h_ed = item.default.top + item.default.height;
        const col_ed = getImagePosition(w_ed, visibledatacolumn);
        const row_ed = getImagePosition(h_ed, visibledatarow);
        // 需要设置nativeCol,nativeColOff,nativeRow,nativeRowOff,可以省略col,row
        worksheet.addImage(imageId, {
            tl: { nativeCol: col_st.native, nativeColOff: col_st.nativeOff, nativeRow: row_st.native, nativeRowOff: row_st.nativeOff },
            br: { nativeCol: col_ed.native, nativeColOff: col_ed.nativeOff, nativeRow: row_ed.native, nativeRowOff: row_ed.nativeOff },
            editAs: 'oneCell',
        });
    }
};

改造getImagePosition方法

/**
 * 计算获取图片的坐标
 * @param {number} num 坐标点的top,left值
 * @param {array} arr 所有的行、列的长度集合
 * @returns 满足exceljs的坐标值,需要计算图片在cell中的偏移量nativeOff。
 */
var getImagePosition = function (num, arr) {
    for (let i = 0; i < arr.length; i++) {
        const item = arr[i]
        if (num < item) {
            const cell = i > 0 ? arr[i] - arr[i - 1]:item
            // 偏移量单位为Emu,所以单元格的宽高需要转换为Emu的单位,cellWidth = cellWidth*10000
            const cellInEmu = cell * 10000
            const rowOrCol = i > 0 ? (num - arr[i - 1]) / (arr[i] - arr[i - 1]) + i:num/item
            const native = Math.floor(rowOrCol)
            const nativeOff = parseInt(new Big(rowOrCol).minus(native).toNumber() * cellInEmu)
            return { rowOrCol, native, nativeOff }
        }
    }
}

注意:浮点数加减不能直接运算,直接运算可能会出现精度缺失,我使用的是big.js插件,偏移量要是整数,需要使用parseInt方法转换一下。