免费开源luckysheet+luckyExcel,本地导入文件,渲染excel,公式计算,导出excel

项目需求:

本地导入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文档

//文件上传按钮
<div> 
         <a-upload
          :file-list="fileList"
          name="file"
          :multiple="false"  //禁止多选
          :showUploadList="{showRemoveIcon: false}"  //隐藏删除文件icon
          :before-upload="handleUpload"
        >
          <a-button type="primary" class="upload">
           点击上传
          </a-button>
        </a-upload>
        <div class="uploadTip">
          只能上传xlsx文件!
        </div>
 </div>

 //渲染excel容器
 <div id="luckysheet" class="luckySheet" v-show="showLuckyExcel"></div>


//-----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对象上的属性和方法了;

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>测试</title>
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">

    <link rel='stylesheet' href='./plugins/css/pluginsCss.css'/>
    <link rel='stylesheet' href='./plugins/plugins.css'/>
    <link rel='stylesheet' href='./css/luckysheet.css'/>
    <link rel='stylesheet' href='./assets/iconfont/iconfont.css'/>
    <script src="./plugins/js/plugin.js"></script>
    <script src="./luckysheet.umd.js"></script>

  </head>
  <body>
    <div id="app"></div>
  </body>

</html>
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;      
        }
    },

Java提供了多种方式来实现Excel导入导出,下面介绍两种常用的方式: 1. 使用Apache POI Apache POI是一个开源的Java API,可以处理Microsoft Office格式的文件,包括Excel。使用POI可以方便地读写Excel文件。 示例代码: //导入Excel文件 FileInputStream fis = new FileInputStream(new File("文件路径")); Workbook workbook = new XSSFWorkbook(fis); Sheet sheet = workbook.getSheetAt(0); for (Row row : sheet) { for (Cell cell : row) { //读取单元格数据 } } //导出Excel文件 Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("表格名称"); Row row = sheet.createRow(0); Cell cell = row.createCell(0); cell.setCellValue("单元格数据"); FileOutputStream fos = new FileOutputStream(new File("文件路径")); workbook.write(fos); 2. 使用EasyExcel EasyExcel是一个基于POI封装的Java库,可以简化Excel导入导出操作。它提供了一系列的注解来定义Excel文件的格式。 示例代码: //导入Excel文件 EasyExcel.read("文件路径", 数据类.class, new ReadListener<数据类>() { @Override public void onException(Exception e) { //读取异常处理 } @Override public void onRead(List<数据类> data) { //读取数据处理 } }).sheet().doRead(); //导出Excel文件 EasyExcel.write("文件路径", 数据类.class).sheet("表格名称").doWrite(数据列表); 其中,数据类是自定义的Java类,注解可以用来定义Excel文件的格式,数据列表是要导出的数据列表。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值