项目需求:

本地导入excel,页面渲染excel,一键计算:根据计算逻辑求出得分回写到对应单元格,最后导出excel;

前端技术:Vue2,luckysheet,luckyExcel,exceljs,mathjs,antdv

Luckysheet ,一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。

上传下载demo:  luckysheet-demo: luckysheet-demo

用到的插件和api:

1,本地导入excel并渲染

npm下载luckysheet后通过 import 方式引入报错,官网给出了两种引入方式:CDN , 本地引入;CDN没什么说的直接引入即可,此次介绍一下本地引入;

快速上手 | Luckysheet文档

//文件上传按钮

:file-list="fileList"

name="file"

:multiple="false" //禁止多选

:showUploadList="{showRemoveIcon: false}" //隐藏删除文件icon

:before-upload="handleUpload"

>

点击上传

只能上传xlsx文件!

//渲染excel容器

//-----methods----------------------------------------------

import LuckyExcel from 'luckyexcel'; //引入LuckyExcel

//luckysheet 引入报错,官网给出了两种引入方式:CDN , 本地引入

//(1)上传文件

handleUpload(file){

if(file.name.substring(file.name.length-5) === '.xlsx'){

this.spinning = true

this.spinningTip = '文件上传中...'

this.file = file;

this.fileList = [file]; //只允许上传一个文件

LuckyExcel.transformExcelToLucky(file, (exportJson, luckysheetfile) => {

luckysheet.destroy();

this.initExcel(exportJson);

},error =>{

this.$message.error(error)

});

return false;

}else{

this.$message.error('文件格式错误,请上传.xlsx文件!')

}

},

//(2)渲染excel

initExcel(exportJson) {

//工作表保护

exportJson.sheets.forEach(t=>{

t.config.authority = {

sheet: 1,

hintText: "您试图更改的单元格或图表位于受保护的工作表中!",

allowRangeList: [

{ sqref: '$A$2:$D$6' },//设置A2~D6为可编辑区域,其它区域不可编辑

],

}

});

//初始化excel

luckysheet.create({

container: 'luckysheet', //dom id

showtoolbar: false, //隐藏工具栏

sheetFormulaBar: false, //隐藏公式栏

enableAddRow: false, //隐藏新增row

showtoolbarConfig: {

print: false // 隐藏插件内部打印按钮

},

sheetRightClickConfig: { // 工作表右键:禁用 删除,复制...

delete: false,

copy: false,

rename: false,

color: false,

hide: false,

move: false,

},

cellRightClickConfig: { //单元格右键配置

paste: false, // 粘贴

insertRow: false, // 插入行

insertColumn: false, // 插入列

deleteRow: false, // 删除选中行

deleteColumn: false, // 删除选中列

deleteCell: false, // 删除单元格

hideRow: false, // 隐藏选中行和显示选中行

hideColumn: false, // 隐藏选中列和显示选中列

clear: false, // 清除内容

matrix: false, // 矩阵操作选区

sort: false, // 排序选区

filter: false, // 筛选选区

chart: false, // 图表生成

image: false, // 插入图片

link: false, // 插入链接

data: false, // 数据验证

cellFormat: false // 设置单元格格式

},

showinfobar: false, // 显示头部返回标题栏

data: exportJson.sheets, //excel数据

});

},

2,luckysheet 本地引入步骤:

(1)gitee上下载项目: Luckysheet: Luckysheet ,一款纯前端类似excel的在线表格,功能强大、配置简单、完全开源。

  (2) 下载后执行 npm run build 打包,生成的dist文件中找到以下文件(直接把除了index.html,demoData的copy过去),加入vue public文件夹下

(3)index.html引入后,直接在vue组件中就可以用luckysheet对象上的属性和方法了;

测试

3,luckysheet 常用API

luckysheet.getAllSheets() //1,获取所有sheet

//2,排除sheet中的空白行,注意第一个单元格为null的情况,根据实际情况调整

let rowCount = sheet.data.filter(t => t[0] !== null).length;

//3,获取G列数据,获取cellValue用 v ,修改cellValue V 和 m 都要修改(官网有介绍)

let sheetCol_G = sheet.data.slice(6, rowCount).map(row => row[6].v);

//4,获取L列数据

let sheetSpans_L = this.getSpans(sheet,6,11,rowCount - 6);

//获取合并单元格

getSpans(sheet,r,c,rs){

let spans = [], merge = sheet.config.merge;

for ( let i in merge) {

let _r = i.split('_')[0], _c = i.split('_')[1];

if (_c == c && _r >= r && _r < r+rs) {

spans.push(merge[i]);

}

}

return spans;

},

//5,合并单元格按照row顺序排序

sheetSpans_L.sort((a,b)=>{

return a.r - b.r;

});

//6,获取L列非合并单元格:

//(1) 先获取所有合并单元格的rowIndex

getSpansIndex(spans){

let spansIndexs = [];

for(let i = 0; i < spans.length; i++){

let {r,rs} = spans[i];

for(let j = 0; j < rs; j++){

let index = r + j;

spansIndexs.push(index)

}

}

return spansIndexs

},

//(2)再遍历看L列这行的rowIndex是否包含在里面,不在合并行里就是非合并单元格

getSingleRow(r,rowCount,col,spans){

let spansIndexs = this.getSpansIndex(spans);

let singleRows = [];

for(let i = r; i < r+rowCount; i++){

singleRows.push({

r:i,

c: col,

rs:1,

cs:1

})

}

}

//7,修改单元格的值

sheet.data[4][14].m = '新数据'; //4:rowIndex, 14:colIndex

sheet.data[4][14].v = '新数据';

//8,修改单元格背景色,字体颜色(注意:导出时exceljs只支持argb格式,与luckysheet不兼容,需要转化)

sheet.data[row][col].bg = "yellow"

sheet.data[row][col].fc = "red"

//9,最后执行refresh页面数据才会更新

luckysheet.refresh();

4,导出excel

(1)直接复制该js文件

import Excel from 'exceljs';

import FileSaver from 'file-saver';

let workbook = null;

const exportSheetExcel = function(table, value, index) {

//创建工作簿,可以为工作簿添加属性

if(index === 0){

workbook = new Excel.Workbook();

}

if (table.data.length === 0) { return true; }

const worksheet = workbook.addWorksheet(table.name);

const merge = (table.config && table.config.merge) || {};

const borderInfo = (table.config && table.config.borderInfo) || {};

// 设置单元格合并,设置单元格边框,设置单元格样式,设置值

setStyleAndValue(table.data, worksheet);

setMerge(merge, worksheet);

setBorder(borderInfo, worksheet);

// 写入 buffer

if(index == 4){

const buffer = workbook.xlsx.writeBuffer().then(data => {

const blob = new Blob([data], {

type: 'application/vnd.ms-excel;charset=utf-8'

});

console.log('导出成功!');

FileSaver.saveAs(blob, `${value}.xlsx`);

});

return buffer;

}

};

var setMerge = function(luckyMerge = {}, worksheet) {

    const mergearr = Object.values(luckyMerge);

    mergearr.forEach(function(elem) {

    // elem格式:{r: 0, c: 0, rs: 1, cs: 2}

    // 按开始行,开始列,结束行,结束列合并(相当于 K10:M12)

        worksheet.mergeCells(

            elem.r + 1,

            elem.c + 1,

            elem.r + elem.rs,

            elem.c + elem.cs

        );

    });

};

var setBorder = function(luckyBorderInfo, worksheet) {

    if (!Array.isArray(luckyBorderInfo)) { return; }

    // console.log('luckyBorderInfo', luckyBorderInfo)

    luckyBorderInfo.forEach(function(elem) {

    // 现在只兼容到borderType 为range的情况

    // console.log('ele', elem)

        if (elem.rangeType === 'range') {

            let border = borderConvert(elem.borderType, elem.style, elem.color);

            let rang = elem.range[0];

            // console.log('range', rang)

            let row = rang.row;

            let column = rang.column;

            for (let i = row[0] + 1; i < row[1] + 2; i++) {

                for (let y = column[0] + 1; y < column[1] + 2; y++) {

                    worksheet.getCell(i, y).border = border;

                }

            }

        }

        if (elem.rangeType === 'cell') {

            // col_index: 2

            // row_index: 1

            // b: {

            //   color: '#d0d4e3'

            //   style: 1

            // }

            const { col_index, row_index } = elem.value;

            const borderData = Object.assign({}, elem.value);

            delete borderData.col_index;

            delete borderData.row_index;

            let border = addborderToCell(borderData, row_index, col_index);

            // console.log('bordre', border, borderData)

            worksheet.getCell(row_index + 1, col_index + 1).border = border;

        }

    // console.log(rang.column_focus + 1, rang.row_focus + 1)

    // worksheet.getCell(rang.row_focus + 1, rang.column_focus + 1).border = border

    });

};

var setStyleAndValue = function(cellArr, worksheet) {

    if (!Array.isArray(cellArr)) { return; }

    cellArr.forEach(function(row, rowid) {

// const dbrow = worksheet.getRow(rowid+1);

// //设置单元格行高,默认乘以1.2倍

// dbrow.height=luckysheet.getRowHeight([rowid])[rowid]*1.2;

        row.every(function(cell, columnid) {

if(rowid==0){

const dobCol = worksheet.getColumn(columnid+1);

//设置单元格列宽除以8

dobCol.width=luckysheet.getColumnWidth([columnid])[columnid]/8;

}

            if (!cell) { return true; }

//设置背景色

let bg = cell.bg || "#FFFFFF"; //默认white

bg = bg === 'yellow' ? "FFFF00" : bg.replace('#', '');

let fill = {

        type: 'pattern',

        pattern: 'solid',

        fgColor: { argb: bg }

    };

            let font = fontConvert(

                cell.ff,

                cell.fc,

                cell.bl,

                cell.it,

                cell.fs,

                cell.cl,

                cell.ul

            );

            let alignment = alignmentConvert(cell.vt, cell.ht, cell.tb, cell.tr);

            let value = '';

            if (cell.f) {

                value = { formula: cell.f, result: cell.v };

            } else if (!cell.v && cell.ct && cell.ct.s) {

                // xls转为xlsx之后,内部存在不同的格式,都会进到富文本里,即值不存在与cell.v,而是存在于cell.ct.s之后

                // value = cell.ct.s[0].v

                cell.ct.s.forEach(arr => {

                    value += arr.v;

                });

            } else {

                value = cell.v;

            }

            //  style 填入到_value中可以实现填充色

            let letter = createCellPos(columnid);

            let target = worksheet.getCell(letter + (rowid + 1));

            // console.log('1233', letter + (rowid + 1))

            for (const key in fill) {

                target.fill = fill;

                break;

            }

            target.font = font;

            target.alignment = alignment;

            target.value = value;

            return true;

        });

    });

};

var fontConvert = function(

    ff = 0,

    fc = '#000000',

    bl = 0,

    it = 0,

    fs = 10,

    cl = 0,

    ul = 0

) {

    // luckysheet:ff(样式), fc(颜色), bl(粗体), it(斜体), fs(大小), cl(删除线), ul(下划线)

    const luckyToExcel = {

        0: '微软雅黑',

        1: '宋体(Song)',

        2: '黑体(ST Heiti)',

        3: '楷体(ST Kaiti)',

        4: '仿宋(ST FangSong)',

        5: '新宋体(ST Song)',

        6: '华文新魏',

        7: '华文行楷',

        8: '华文隶书',

        9: 'Arial',

        10: 'Times New Roman ',

        11: 'Tahoma ',

        12: 'Verdana',

        num2bl: function(num) {

            return num === 0 ? false : true;

        }

    };

    // 出现Bug,导入的时候ff为luckyToExcel的val

//设置字体颜色

fc = fc === 'red' ? 'FFFF0000' : fc.replace('#', '');

    let font = {

        name: typeof ff === 'number' ? luckyToExcel[ff] : ff,

        family: 1,

        size: fs,

        color: { argb: fc },

        bold: luckyToExcel.num2bl(bl),

        italic: luckyToExcel.num2bl(it),

        underline: luckyToExcel.num2bl(ul),

        strike: luckyToExcel.num2bl(cl)

    };

    return font;

};

var alignmentConvert = function(

    vt = 'default',

    ht = 'default',

    tb = 'default',

    tr = 'default'

) {

    // luckysheet:vt(垂直), ht(水平), tb(换行), tr(旋转)

    const luckyToExcel = {

        vertical: {

            0: 'middle',

            1: 'top',

            2: 'bottom',

            default: 'top'

        },

        horizontal: {

            0: 'center',

            1: 'left',

            2: 'right',

            default: 'left'

        },

        wrapText: {

            0: false,

            1: false,

            2: true,

            default: false

        },

        textRotation: {

            0: 0,

            1: 45,

            2: -45,

            3: 'vertical',

            4: 90,

            5: -90,

            default: 0

        }

    };

    let alignment = {

        vertical: luckyToExcel.vertical[vt],

        horizontal: luckyToExcel.horizontal[ht],

        wrapText: luckyToExcel.wrapText[tb],

        textRotation: luckyToExcel.textRotation[tr]

    };

    return alignment;

};

var borderConvert = function(borderType, style = 1, color = '#000') {

    // 对应luckysheet的config中borderinfo的的参数

    if (!borderType) {

        return {};

    }

    const luckyToExcel = {

        type: {

            'border-all': 'all',

            'border-top': 'top',

            'border-right': 'right',

            'border-bottom': 'bottom',

            'border-left': 'left'

        },

        style: {

            0: 'none',

            1: 'thin',

            2: 'hair',

            3: 'dotted',

            4: 'dashDot', // 'Dashed',

            5: 'dashDot',

            6: 'dashDotDot',

            7: 'double',

            8: 'medium',

            9: 'mediumDashed',

            10: 'mediumDashDot',

            11: 'mediumDashDotDot',

            12: 'slantDashDot',

            13: 'thick'

        }

    };

    let template = {

        style: luckyToExcel.style[style],

        color: { argb: color.replace('#', '') }

    };

    let border = {};

    if (luckyToExcel.type[borderType] === 'all') {

        border['top'] = template;

        border['right'] = template;

        border['bottom'] = template;

        border['left'] = template;

    } else {

        border[luckyToExcel.type[borderType]] = template;

    }

    // console.log('border', border)

    return border;

};

function addborderToCell(borders, row_index, col_index) {

    let border = {};

    const luckyExcel = {

        type: {

            l: 'left',

            r: 'right',

            b: 'bottom',

            t: 'top'

        },

        style: {

            0: 'none',

            1: 'thin',

            2: 'hair',

            3: 'dotted',

            4: 'dashDot', // 'Dashed',

            5: 'dashDot',

            6: 'dashDotDot',

            7: 'double',

            8: 'medium',

            9: 'mediumDashed',

            10: 'mediumDashDot',

            11: 'mediumDashDotDot',

            12: 'slantDashDot',

            13: 'thick'

        }

    };

    // console.log('borders', borders)

    for (const bor in borders) {

    // console.log(bor)

        if (borders[bor].color.indexOf('rgb') === -1) {

            border[luckyExcel.type[bor]] = {

                style: luckyExcel.style[borders[bor].style],

                color: { argb: borders[bor].color.replace('#', '') }

            };

        } else {

            border[luckyExcel.type[bor]] = {

                style: luckyExcel.style[borders[bor].style],

                color: { argb: borders[bor].color }

            };

        }

    }

    return border;

}

function createCellPos(n) {

    let ordA = 'A'.charCodeAt(0);

    let ordZ = 'Z'.charCodeAt(0);

    let len = ordZ - ordA + 1;

    let s = '';

    while (n >= 0) {

        s = String.fromCharCode((n % len) + ordA) + s;

        n = Math.floor(n / len) - 1;

    }

    return s;

}

export {

exportSheetExcel

};

(2)使用(用了批量导出优化,显示下载进度),“requestAnimationFrame” 优化由于js计算时造成UI线程阻塞(即页面loading 失效)

handleExport(){

this.spinning = true;

const total = 6; //sheet总数+1

const batchSize = 1; // 每批计算数量

let current = 0; // 当前计算进度

let tableArr = luckysheet.getAllSheets();

const fileName = '测试表_输出';

const doBatchExport = () => {

for (let i = 0; i < batchSize; i++) {

if(current > 0 ){

let index = current - 1;

let sheet = tableArr[index];

exportSheetExcel(sheet, fileName,index);

}

current++;

// 更新计算进度

this.updateExportProgress(current, total);

// 计算完成后

if (current >= total) {

return;

}

}

// 继续下一批计算任务

requestAnimationFrame(doBatchExport);

};

// 开始第一批计算任务

requestAnimationFrame(doBatchExport);

},

updateExportProgress(current, total, ){

if(current < total){

this.spinningTip = `下载进度( ${(current/(total - 1))*100} % )...`

}else{

this.spinning = false;

}

},

相关链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: