整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

java导出大量的excel

java导出大量的excel

话少说,直入主题

基本思路为 创建一个临时文件 写入数据 导出数据 删除临时文件

首先需要两个jar包

antlr和stringtemplate

创建数据库中的类Row

 private String name1;
 
 private String name2;
 
 private String name3;
 public String getName1() {
 return name1;
 }
 public void setName1(String name1) {
 this.name1=name1;
 }
 public String getName2() {
 return name2;
 }
 public void setName2(String name2) {
 this.name2=name2;
 }
 public String getName3() {
 return name3;
 }
 public void setName3(String name3) {
 this.name3=name3;
 }


然后需要创建对应的Worksheet类

<span style="font-size:10px;">private int columnNum;
 private int rowNum;
 private List<Row> rows;
 private List<SfOrder> orders;
 public List<SfOrder> getOrders() {
 return orders;
 }
 public void setOrders(List<SfOrder> orders) {
 this.orders=orders;
 }
 public String getSheet() {
 return sheet;
 }
 public void setSheet(String sheet) {
 this.sheet=sheet;
 }
 public List<Row> getRows() {
 return rows;
 }
 public void setRows(List<Row> rows) {
 this.rows=rows;
 }
 public int getColumnNum() {
 return columnNum;
 }
 public void setColumnNum(int columnNum) {
 this.columnNum=columnNum;
 }
 public int getRowNum() {
 return rowNum;
 }
 public void setRowNum(int rowNum) {
 this.rowNum=rowNum;
 }</span>

然后需要写两个文件分别为

head.st

<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:o="urn:schemas-microsoft-com:office:office"
 xmlns:x="urn:schemas-microsoft-com:office:excel"
 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:html="http://www.w3.org/TR/REC-html40">
 <DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
 <Created>1996-12-17T01:32:42Z</Created>
 <LastSaved>2013-08-02T09:21:24Z</LastSaved>
 <Version>11.9999</Version>
 </DocumentProperties>
 <OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office">
 <RemovePersonalInformation/>
 </OfficeDocumentSettings>
 <ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel">
 <WindowHeight>4530</WindowHeight>
 <WindowWidth>8505</WindowWidth>
 <WindowTopX>480</WindowTopX>
 <WindowTopY>120</WindowTopY>
 <AcceptLabelsInFormulas/>
 <ProtectStructure>False</ProtectStructure>
 <ProtectWindows>False</ProtectWindows>
 </ExcelWorkbook>
 <Styles>
 <Style ss:ID="Default" ss:Name="Normal">
 <Alignment ss:Vertical="Bottom"/>
 <Borders/>
 <Font ss:FontName="宋体" x:CharSet="134" ss:Size="12"/>
 <Interior/>
 <NumberFormat/>
 <Protection/>
 </Style>
 </Styles>
和body.st
 $worksheet:{
 <Worksheet ss:Name="$it.sheet$">
 <Table ss:ExpandedColumnCount="$it.columnNum$" ss:ExpandedRowCount="$it.rowNum$" x:FullColumns="1"
 x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="14.25">
 $it.rows:{
 <Row>
 <Cell><Data ss:Type="String">$it.name1$</Data></Cell>
 </Row>
 }$
 </Table>
 </Worksheet>
}$

下面就是程序代码

首先你要从数据中查询数据,假设你查询的数据为List数据

 //我这里的list是从map中获取的你可以直接获取
 List<Row> list=(List<Row>) result.get("list");
 list=(List<SfOrder>) result.get("list");
 if(list.size()==0){
 return null;
 }
 long startTimne=System.currentTimeMillis();
 StringTemplateGroup stGroup=new StringTemplateGroup("stringTemplate"); 
 stGroup.setFileCharEncoding("UTF-8");//设置编码,否则有乱码,导出excel格式不正确
 //写入excel文件头部信息
 StringTemplate head=stGroup.getInstanceOf("/template/head");
 String path=request.getSession().getServletContext().getRealPath("/upload/excel/test1.xls");
 File file=new File(path);
 PrintWriter writer=new PrintWriter(new BufferedOutputStream(new FileOutputStream(file)));
 writer.print(head.toString());
 writer.flush();
 int sheets=10;
 //excel单表最大行数是65535
 int maxRowNum=3000;
 int maxRowNumLast=0;
 //计算要分几个sheet
 sheets=(list.size()%maxRowNum==0) ? list.size()/maxRowNum:list.size()/maxRowNum+1;
 //计算最后一个sheet有多少行
 maxRowNumLast=list.size()-(sheets-1)*maxRowNum;
上面为预备阶段,下面则直接向文件写数据
//写入excel文件数据信息
 for(int i=0;i<sheets;i++){
 Integer nums=0;
 StringTemplate body=stGroup.getInstanceOf("/template/body");
 Worksheet worksheet=new Worksheet();
 worksheet.setSheet(" "+(i+1)+" ");
 worksheet.setColumnNum(3);
 worksheet.setRowNum(maxRowNum);
 List<Row> orders=new ArrayList<Row>();
 if(i==(sheets-1)){
 for(int j=0;j<maxRowNumLast;j++){
 nums=i*maxRowNumLast+j;
 SfOrder order=new SfOrder();
 order.setOrderId(list.get(nums).getOrderId());
 orders.add(order);
 }
 }else{
 for(int j=0;j<maxRowNum;j++){
 nums=i*maxRowNum+j;
 Row order=new Row();
 order.setOrderId(list.get(nums).getOrderId());
 orders.add(order);
 }
 }
 worksheet.setOrders(orders);
 body.setAttribute("worksheet", worksheet);
 writer.print(body.toString());
 writer.flush();
 orders.clear();
 orders=null;
 worksheet=null;
 body=null;
 Runtime.getRuntime().gc();
 System.out.println("正在生成excel文件的 sheet"+(i+1));
 }
下面则写入服务器磁盘
//写入excel文件尾部
 writer.print("</Workbook>");
 writer.flush();
 writer.close();
 System.out.println("生成excel文件完成");
 long endTime=System.currentTimeMillis();
 System.out.println("用时="+((endTime-startTimne)/1000)+"秒");
下面要读取文件,即正式的导出
 //创建file对象
 File upload=new File(path);
 //设置response的编码方式
 response.setContentType("application/vnd.ms-excel");
 response.setHeader("Content-Disposition","attachment;filename=ceshi.xls");
 //读出文件到i/o流
 FileInputStream fis=new FileInputStream(upload);
 BufferedInputStream buff=new BufferedInputStream(fis);
 byte [] b=new byte[1024];//相当于我们的缓存
 long k=0;//该值用于计算当前实际下载了多少字节
 //从response对象中得到输出流,准备下载
 OutputStream myout=response.getOutputStream();
 //开始循环下载
 while(k<upload.length()){
 int j=buff.read(b,0,1024);
 k+=j;
 //将b中的数据写到客户端的内存
 myout.write(b,0,j);
 }
 //将写入到客户端的内存的数据,刷新到磁盘
 myout.flush();
 myout.close();
如果需要可以,比如这个文件是动态的我们可以让这个文件是动态的,就要将此文件删除
 File fileDel=new File(path); 
 // 如果文件路径所对应的文件存在,并且是一个文件,则直接删除 
 if (fileDel.exists() && file.isFile()) { 
 fileDel.delete()
 }

这样就大功告成啦,导出所用的时间大部分是花费在数据库查询中,当然这也是你数据库功底的问题啦

篇文章主要介绍使用 exceljsfile-saverjszip实现下载包含多层级文件夹、多个 excel、每个 excel 支持多个 sheet 的 zip 压缩包。上一篇文章:前端复杂表格导出excel,一键导出 Antd Table 看这篇就够了(附源码)[1]详细介绍了如何实现解析 Antd Table、组装数据和调整表格的样式,感兴趣的可以先看看。本篇将接着上一篇,重点讲方法的更高级抽象,和下载多层级文件夹的 zip 压缩包。源码地址:github.com/cachecats/e…[2]

实现效果

最终下载的是 压缩包.zip,解压之后包含多个文件夹,每个文件夹下又可以无限嵌套子文件夹,excel 文件可以自由选择放到根目录下,或者子文件夹下。实现效果如图:

使用方法

使用方式也很简单,经过高度封装后,只需按照方法参数的规则传入参数即可:

downloadFiles2ZipWithFolder({
      zipName: '压缩包',
      folders: [
        {
          folderName: '文件夹1',
          files: [
            {
              filename: 'test',
              sheets: [{
                sheetName: 'test',
                columns: columns,
                dataSource: list
              }]
            },
            {
              filename: 'test2',
              sheets: [{
                sheetName: 'test',
                columns: columns,
                dataSource: list
              }]
            },
          ]
        },
        {
          folderName: '文件夹2',
          files: [
            {
              filename: 'test',
              sheets: [{
                sheetName: 'test',
                columns: columns,
                dataSource: list
              }]
            },
            {
              filename: 'test2',
              sheets: [{
                sheetName: 'test',
                columns: columns,
                dataSource: list
              }]
            },
          ]
        },
        {
          folderName: '文件夹2/文件夹2-1',
          files: [
            {
              filename: 'test',
              sheets: [{
                sheetName: 'test',
                columns: columns,
                dataSource: list
              }]
            },
            {
              filename: 'test2',
              sheets: [{
                sheetName: 'test',
                columns: columns,
                dataSource: list
              }]
            },
          ]
        },
        {
          folderName: '文件夹2/文件夹2-1/文件夹2-1-1',
          files: [
            {
              filename: 'test',
              sheets: [{
                sheetName: 'test',
                columns: columns,
                dataSource: list
              }]
            },
            {
              filename: 'test2',
              sheets: [{
                sheetName: 'test',
                columns: columns,
                dataSource: list
              }]
            },
          ]
        },
        {
          folderName: '',
          files: [
            {
              filename: 'test',
              sheets: [{
                sheetName: 'test',
                columns: columns,
                dataSource: list
              },
                {
                  sheetName: 'test2',
                  columns: columns,
                  dataSource: list
                }
              ]
            },
            {
              filename: 'test2',
              sheets: [{
                sheetName: 'test',
                columns: columns,
                dataSource: list
              }]
            },
          ]
        }
      ]
    })
复制代码

这里会封装三个方法,分别满足不同场景下的导出需求:

  • downloadExcel:导出普通的单文件 excel,预设样式,可包含多个 sheet。
  • downloadFiles2Zip:将多个 excel 文件导出到一个 zip 压缩包内,没有嵌套文件夹。
  • downloadFiles2ZipWithFolder:导出包含多级子文件夹、每级包含多个 excel 文件的 zip 压缩包。

一、封装普通的下载导出 excel 方法

我们来封装一个常用的,预定义好样式,直接能开箱即用的导出方法,使用者不用关心具体细节,只管简单的调用:

function onExportExcel() {
  downloadExcel({
    filename: 'test',
    sheets: [{
      sheetName: 'test',
      columns: columns,
      dataSource: list
    }]
  })
}
复制代码

如上,直接调用 downloadExcel方法,它传入一个对象作为参数,分别有 filenamesheets两个属性。

  • filename:文件名。不用带 .xlsx后缀,会自动加后缀名。
  • sheets:sheet 数组。传入几个 sheet 对象就会创建几个 sheet 页。

Sheet对象的定义:

export interface ISheet {
  // sheet 的名字
  sheetName: string;
  // 这个 sheet 中表格的 column,类型同 antd 的 column
  columns: ColumnType<any>[];
  // 表格的数据
  dataSource: any[];
}
复制代码

核心代码

downloadExcel方法关键源码:

export interface IDownloadExcel {
  filename: string;
  sheets: ISheet[];
}

export interface ISheet {
  // sheet 的名字
  sheetName: string;
  // 这个 sheet 中表格的 column,类型同 antd 的 column
  columns: ColumnType<any>[];
  // 表格的数据
  dataSource: any[];
}

/**
 * 下载导出简单的表格
 * @param params
 */
export function downloadExcel(params: IDownloadExcel) {
  // 创建工作簿
  const workbook = new ExcelJs.Workbook();
  params?.sheets?.forEach((sheet) => handleEachSheet(workbook, sheet));
  saveWorkbook(workbook, `${params.filename}.xlsx`);
}


function handleEachSheet(workbook: Workbook, sheet: ISheet) {
  // 添加sheet
  const worksheet = workbook.addWorksheet(sheet.sheetName);
  // 设置 sheet 的默认行高。设置默认行高跟自动撑开单元格冲突
  // worksheet.properties.defaultRowHeight = 20;
  // 设置列
  worksheet.columns = generateHeaders(sheet.columns);
  handleHeader(worksheet);
  handleData(worksheet, sheet);
}


export function saveWorkbook(workbook: Workbook, fileName: string) {
  // 导出文件
  workbook.xlsx.writeBuffer().then((data: any) => {
    const blob = new Blob([data], {type: ''});
    saveAs(blob, fileName);
  });
}
复制代码

generateHeaders方法是设置表格的列。handleHeader方法负责处理表头,设置表头的高度、背景色、字体等样式。handleData方法处理每一行具体的数据。这三个方法的实现在上篇文章都有介绍,如需了解更多请查看源码:github.com/cachecats/e…[3]

导出的 excel 效果如下图,列宽会根据传入的 width 动态计算,单元格高度会根据内容自动撑开。

二、导出包含多个 excel 的 zip 压缩包

如果没有多级目录的需求,只想把多个 excel 文件打包到一个压缩包里,可以用 downloadFiles2Zip这个方法,得到的目录结构如下图:

参数结构如下,支持导出多个 excel 文件,每个 excel 文件又可以包含多个 sheet。

export interface IDownloadFiles2Zip {
  // 压缩包的文件名
  zipName: string;
  files: IDownloadExcel[];
}

export interface IDownloadExcel {
  filename: string;
  sheets: ISheet[];
}

export interface ISheet {
  // sheet 的名字
  sheetName: string;
  // 这个 sheet 中表格的 column,类型同 antd 的 column
  columns: ColumnType<any>[];
  // 表格的数据
  dataSource: any[];
}
复制代码

使用示例

function onExportZip() {
  downloadFiles2Zip({
    zipName: '压缩包',
    files: [
      {
        filename: 'test',
        sheets: [
          {
            sheetName: 'test',
            columns: columns,
            dataSource: list
          },
          {
            sheetName: 'test2',
            columns: columns,
            dataSource: list
          }
        ]
      },
      {
        filename: 'test2',
        sheets: [{
          sheetName: 'test',
          columns: columns,
          dataSource: list
        }]
      },
      {
        filename: 'test3',
        sheets: [{
          sheetName: 'test',
          columns: columns,
          dataSource: list
        }]
      }
    ]
  })
}
复制代码

核心代码

通过 handleEachFile()方法处理每个 fille 对象,每个 file 其实就是一个 excel 文件,即一个 workbook。给每个 excel 创建 workbook并将数据写入,然后通过 JsZip库写入到压缩文件内,最终用 file-saver库提供的 saveAs方法导出压缩文件。注意 12、13行,handleEachFile()方法返回的是一个 Promise,需要等所有异步方法都执行完之后再执行下面的生成 zip 方法,否则可能会遗漏文件。

import {saveAs} from 'file-saver';
import * as ExcelJs from 'exceljs';
import {Workbook, Worksheet, Row} from 'exceljs';
import JsZip from 'jszip'

/**
 * 导出多个文件为zip压缩包
 */
export async function downloadFiles2Zip(params: IDownloadFiles2Zip) {
  const zip = new JsZip();
  // 待每个文件都写入完之后再生成 zip 文件
  const promises = params?.files?.map(async param => await handleEachFile(param, zip, ''))
  await Promise.all(promises);
  zip.generateAsync({type: "blob"}).then(blob => {
    saveAs(blob, `${params.zipName}.zip`)
  })
}

async function handleEachFile(param: IDownloadExcel, zip: JsZip, folderName: string) {
  // 创建工作簿
  const workbook = new ExcelJs.Workbook();
  param?.sheets?.forEach((sheet) => handleEachSheet(workbook, sheet));
  // 生成 blob
  const data = await workbook.xlsx.writeBuffer();
  const blob = new Blob([data], {type: ''});
  if (folderName) {
    zip.folder(folderName)?.file(`${param.filename}.xlsx`, blob)
  } else {
    // 写入 zip 中一个文件
    zip.file(`${param.filename}.xlsx`, blob);
  }
}

function handleEachSheet(workbook: Workbook, sheet: ISheet) {
  // 添加sheet
  const worksheet = workbook.addWorksheet(sheet.sheetName);
  // 设置 sheet 的默认行高。设置默认行高跟自动撑开单元格冲突
  // worksheet.properties.defaultRowHeight = 20;
  // 设置列
  worksheet.columns = generateHeaders(sheet.columns);
  handleHeader(worksheet);
  handleDataWithRender(worksheet, sheet);
}
复制代码

render 渲染的单元格处理

数据处理还有一点需要注意,因为有的单元格是通过 render 函数渲染的,render 函数里可能进行了一系列复杂的计算,所以如果 column 中有 render 的话不能直接以 dataIndex 为 key 进行取值,要拿到 render 函数执行后的值才是正确的。比如 Table 的 columns 如下:

const columns: ColumnsType<any> = [
    {
      width: 50,
      dataIndex: 'id',
      key: 'id',
      title: 'ID',
      render: (text, row) => <div><p>{row.id + 20}</p></div>,
    },
    {
      width: 100,
      dataIndex: 'name',
      key: 'name',
      title: '姓名',
    },
    {
      width: 50,
      dataIndex: 'age',
      key: 'age',
      title: '年龄',
    },
    {
      width: 80,
      dataIndex: 'gender',
      key: 'gender',
      title: '性别',
    },
  ];
复制代码

第一列传入了 render 函数 render: (text, row)=> <div><p>{row.id + 20}</p></div>,经过计算后,ID 列显示的值应该是原来的 id + 20。构造的数据原来的 id 是 0-4,页面上显示的应该是 20-24,如下图:

这时导出的 excel 应该跟页面上显示的一模一样,这样才是正确的。点击【导出zip】按钮,解压后打开下载的其中一个 excel,验证显示的内容跟在线表格完全一致。

那么是如何做到的呢?主要看 handleDataWithRender()方法:

/**
 * 如果 column 有 render 函数,则以 render 渲染的结果显示
 * @param worksheet
 * @param sheet
 */
function handleDataWithRender(worksheet: Worksheet, sheet: ISheet) {
  const {dataSource, columns} = sheet;
  const rowsData = dataSource?.map(data => {
    return columns?.map(column => {
      // @ts-ignore
      const renderResult = column?.render?.(data[column.dataIndex], data);
      if (renderResult) {
        // 如果不是 object 说明没包裹标签,是基本类型直接返回
        if (typeof renderResult !== "object") {
          return renderResult;
        }
        // 如果是 object 说明包裹了标签,逐级取出值
        return getValueFromRender(renderResult);
      }
      // @ts-ignore
      return data[column.dataIndex];
    })
  })
  // 添加行
  const rows = worksheet.addRows(rowsData);
  // 设置每行的样式
  addStyleToData(rows);
}


// 递归取出 render 里的值
// @ts-ignore
function getValueFromRender(renderResult: any) {
  if (renderResult?.type) {
    let children = renderResult?.props?.children;
    if (children?.type) {
      return getValueFromRender(children);
    } else {
      return children;
    }
  }
  return ''
}
复制代码

worksheet.addRows()可以添加数据对象,也可以添加由每行的每列组成的二维数组。由于我们要自己控制每个单元格显示的内容,所以采用第二种方式,传入一个二维数组来构造 row。结构如下图所示:

循环 dataSourcecolumns,就得到了每个单元格要显示的内容,通过执行 render 函数,得到 render 执行后的结果:const renderResult=column?.render?.(data[column.dataIndex], data);注意 render 需要传入两个参数,一个是 text,一个是这行的数据对象,我们都能确定参数的值,所以直接传入。然后判断 renderResult的类型,如果是 object 类型,说明是个由 html 标签包裹的 ReactNode,需要递归取出最终渲染的值。如果是非 object 类型,说明是 boolean 或者 string 这样的基本类型,即没有被标签包裹,可以直接展示。由于我们采用了递归来取最后渲染的值,所以无论嵌套了多少层标签,都可以正确的取到值。

三、导出包含多个子文件夹、多个excel文件的 zip 压缩包

如果文件、文件夹嵌套比较深,可以使用 downloadFiles2ZipWithFolder()方法。文件结构如下图:

核心代码

export interface IDownloadFiles2ZipWithFolder {
  zipName: string;
  folders: IFolder[];
}

export interface IFolder {
  folderName: string;
  files: IDownloadExcel[];
}

export interface IDownloadExcel {
  filename: string;
  sheets: ISheet[];
}

export interface ISheet {
  // sheet 的名字
  sheetName: string;
  // 这个 sheet 中表格的 column,类型同 antd 的 column
  columns: ColumnType<any>[];
  // 表格的数据
  dataSource: any[];
}


/**
 * 导出支持多级文件夹的压缩包
 * @param params
 */
export async function downloadFiles2ZipWithFolder(params: IDownloadFiles2ZipWithFolder) {
  const zip = new JsZip();
  const outPromises = params?.folders?.map(async folder => await handleFolder(zip, folder))
  await Promise.all(outPromises);
  zip.generateAsync({type: "blob"}).then(blob => {
    saveAs(blob, `${params.zipName}.zip`)
  })
}

async function handleFolder(zip: JsZip, folder: IFolder) {
  console.log({folder})
  let folderPromises: Promise<any>[] = [];
  const promises = folder?.files?.map(async param => await handleEachFile(param, zip, folder.folderName));
  await Promise.all([...promises, ...folderPromises]);
}
复制代码

跟上一个方法 downloadFiles2Zip相比,参数的数据结构多了层 folders,其他的逻辑基本没变。所以 downloadFiles2ZipWithFolder方法能实现downloadFiles2Zip方法的所有功能。

使用示例

如文章开头的使用示例,为了方便看清结构,将每个对象的 files 值删除,精简之后得到如下结构:

downloadFiles2ZipWithFolder({
      zipName: '压缩包',
      folders: [
        {
          folderName: '文件夹1',
          files: []
        },
        {
          folderName: '文件夹2',
          files: []
        },
        {
          folderName: '文件夹2/文件夹2-1',
          files: []
        },
        {
          folderName: '文件夹2/文件夹2-1/文件夹2-1-1',
          files: []
        },
        {
          folderName: '',
          files: []
        }
      ]
    })
复制代码

不管嵌套几层文件夹,folders永远是一个一维数组,每一项里面也不会嵌套 folders。多级目录是通过文件名 folderName实现的。

  • folderName为空字符串,则将它的 files放入压缩包的顶级目录中,不在任何子文件内。
  • folderName为普通字符串,如:文件夹1,则以 folderName为文件名新建一个文件夹,并将它的 files放入此文件夹下。
  • folderName为带斜杠的字符串,如:文件夹2/文件夹2-1/文件夹2-1-1,则按照顺序依次新建 n 个文件夹并保持嵌套关系,最终将它的files放入最后一个文件夹下。

如需查看 demo 完整代码,源码地址:github.com/cachecats/e…[4]

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:cloud.tencent.com/developer/s…[5]


关于本文

作者:solocoder

https://juejin.cn/post/7080169896209809445

述可帮助您找到构建可处理大量数据的Web应用程序的正确解决方案。

在本文中,我将基于其功能和许可策略简要概述五个流行的独立JavaScript电子表格库。这些图书馆可以免费在非营利项目中实施,也可以提供商业用途的付费许可。我希望此概述可以帮助您找到构建旨在处理大量数据的Web应用程序的正确解决方案。

Handsontable

Handsontable据说是一个带有电子表格外观的JavaScript网格。它是一个纯JavaScript库,支持React,Vue.js和Angular。其基本功能列表包括折叠列,调整大小,移动和隐藏列和行,添加注释,显示列摘要,导出数据,应用条件格式,使用数据验证和添加下拉菜单的功能。也可以对数据进行排序和过滤并使用自动填充。

更有趣的是高级功能列表。例如,开发人员可以选择在触发表呈现时应使用哪个渲染器。此外,还可以创建自定义插件,使用自定义按钮,并在单元格之间定义自定义边框。此外,还有诸如多列排序,嵌套标题,修剪行 等功能。

Handsontable提供 三种类型的许可证:非商业免费许可证,开发人员(每个开发人员790美元),企业(自定义价格)许可证。

我的结论:Handsontable是非商业项目和那些准备为丰富功能付费的人的一个很好的选择。

AG-网格

ag-Grid是一个JavaScript网格/电子表格组件,可以与Angular,Angular JS 1.x,React,Vue.js,Polymer和Web Components轻松集成,没有第三方依赖。这个100,000行的演示判断它的快速性能和数十 行。

分组和聚合功能允许用户以他们想要的方式处理数据。可以按特定列对数据进行分组,并且可以在分组行中显示各种聚合列值。ag-Grid提供快速过滤功能和自定义过滤器。延迟加载允许在用户滚动时显示所需的行数并请求其他数据,这有助于节省服务器资源。ag-Grid支持实时更新,每秒可处理数百个更新。您可以阅读有关这些功能和其他功能的更多信息,并在功能概述页面上查看一些演示 。

该库有 两个版本:社区和企业。社区版本由MIT许可证涵盖,仅包括基本功能。具有所有可用功能的Enterprise许可证有三个选项:Single Application Developer(每个开发人员750美元),Multiple Application Developer(每个开发人员1,200美元)和部署许可证(每个生产环境750美元)。

我的结论是:ag-Grid提供了许多有用的功能,并且可以与不同的JS框架进行简单的集成。对于大预算项目来说,这似乎是一个不错的选择,因为许可非常灵活而且价格昂贵。

dhtmlxSpreadsheet

dhtmlxSpreadsheet是一个可自定义的JavaScript电子表格组件,具有Material skin和类似Excel的界面。它为三个流行的框架提供了包装器:React,Vue.js和Angular。

您可以根据其用户指南自定义此电子表格的几乎每个元素- 例如,使用工具栏,菜单和上下文菜单控件的自定义图标字体包而不是Material-design控件。单元格格式功能允许您更改文本颜色和装饰,背景颜色,设置文本对齐,将不同的数字格式应用于单元格值(百分比,货币甚至自定义格式),调整列大小等。

还可以锁定单元格,通过在单元格中键入数据来自动填充包含内容的单元格,并拖动填充句柄以延长其他单元格中的一系列数字或字母。dhtmlxSpreadsheet提供了相当多的导航热键列表。此外,该库提供了从Excel文件导入和导出数据的机会。为此,dhtmlxSpreadsheet开发人员已经实现了他们自己的开源库Excel2Json和Json2Excel。

dhtmlxSpreadsheet提供四个主要许可证:免费GNU GPL v2,商业许可证(最多5个开发人员149美元),企业许可证(最多20个开发人员团队449美元),终极许可证(无限数量的开发人员669美元)。

我的判断:dhtmlxSpreadsheet提供了一组基本功能和对流行框架的支持,可以被认为是物有所值的。

Clusterize.js

开发人员将Clusterize.js描述 为一个很小的插件,可以轻松显示大型数据集。它的权重仅为2.3KB gzipped,但遗憾的是它没有提供任何高级功能。其主要目的是使具有大量行的表在网页上平滑运行。Clusterize.js不是用所有使用的标签“污染”DOM,而是将列表拆分为簇,然后显示当前滚动位置的元素,并在列表的顶部和底部添加额外的行以模拟表的完整高度,以便浏览器显示完整列表的滚动条。此库适用于现代浏览器,并支持所有主要的移动设备。

好消息是Clusterize.js非常实惠。在 可用的许可证中,您可以找到个人许可证(个人项目免费),商业许可证(25美元用于商业用途和无限数量的项目),以及扩展许可证(110美元;可以包含在待售产品中)。

我的结论是:Clusterize.js是那些寻找能够快速运行并节省一些钱的单一用途工具的人的绝佳选择。

SlickGrid

SlickGrid是一个简洁而简约的JavaScript电子表格组件。自适应虚拟滚动允许处理数十万行而没有任何延迟。该库支持jQuery UI主题并支持广泛的自定义。用户可以调整列的大小,重新排序,显示或隐藏,使用分组,过滤,自定义聚合器和其他功能。可插入单元格格式器和编辑器允许您扩展Web应用程序的功能。

如您所见,SlickGrid提供了一组基本功能,可以满足普通用户的需求。不幸的是,根据项目的 GitHub页面,这个库最近没有受到开发人员的太多关注。好消息是SlickGrid是免费提供的。

我的判断:如果你不是在寻找丰富的功能或者买不起商业许可证,那么SlickGrid可能是个不错的选择。