Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
話少說,直入主題
基本思路為 創(chuàng)建一個(gè)臨時(shí)文件 寫入數(shù)據(jù) 導(dǎo)出數(shù)據(jù) 刪除臨時(shí)文件
首先需要兩個(gè)jar包
antlr和stringtemplate
創(chuàng)建數(shù)據(jù)庫中的類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; }
然后需要?jiǎng)?chuàng)建對(duì)應(yīng)的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>
然后需要寫兩個(gè)文件分別為
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> }$
下面就是程序代碼
首先你要從數(shù)據(jù)中查詢數(shù)據(jù),假設(shè)你查詢的數(shù)據(jù)為List數(shù)據(jù)
//我這里的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");//設(shè)置編碼,否則有亂碼,導(dǎo)出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單表最大行數(shù)是65535 int maxRowNum=3000; int maxRowNumLast=0; //計(jì)算要分幾個(gè)sheet sheets=(list.size()%maxRowNum==0) ? list.size()/maxRowNum:list.size()/maxRowNum+1; //計(jì)算最后一個(gè)sheet有多少行 maxRowNumLast=list.size()-(sheets-1)*maxRowNum; 上面為預(yù)備階段,下面則直接向文件寫數(shù)據(jù) //寫入excel文件數(shù)據(jù)信息 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)); } 下面則寫入服務(wù)器磁盤 //寫入excel文件尾部 writer.print("</Workbook>"); writer.flush(); writer.close(); System.out.println("生成excel文件完成"); long endTime=System.currentTimeMillis(); System.out.println("用時(shí)="+((endTime-startTimne)/1000)+"秒"); 下面要讀取文件,即正式的導(dǎo)出 //創(chuàng)建file對(duì)象 File upload=new File(path); //設(shè)置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];//相當(dāng)于我們的緩存 long k=0;//該值用于計(jì)算當(dāng)前實(shí)際下載了多少字節(jié) //從response對(duì)象中得到輸出流,準(zhǔn)備下載 OutputStream myout=response.getOutputStream(); //開始循環(huán)下載 while(k<upload.length()){ int j=buff.read(b,0,1024); k+=j; //將b中的數(shù)據(jù)寫到客戶端的內(nèi)存 myout.write(b,0,j); } //將寫入到客戶端的內(nèi)存的數(shù)據(jù),刷新到磁盤 myout.flush(); myout.close(); 如果需要可以,比如這個(gè)文件是動(dòng)態(tài)的我們可以讓這個(gè)文件是動(dòng)態(tài)的,就要將此文件刪除 File fileDel=new File(path); // 如果文件路徑所對(duì)應(yīng)的文件存在,并且是一個(gè)文件,則直接刪除 if (fileDel.exists() && file.isFile()) { fileDel.delete() }
這樣就大功告成啦,導(dǎo)出所用的時(shí)間大部分是花費(fèi)在數(shù)據(jù)庫查詢中,當(dāng)然這也是你數(shù)據(jù)庫功底的問題啦
篇文章主要介紹使用 exceljs、file-saver、jszip實(shí)現(xiàn)下載包含多層級(jí)文件夾、多個(gè) excel、每個(gè) excel 支持多個(gè) sheet 的 zip 壓縮包。上一篇文章:前端復(fù)雜表格導(dǎo)出excel,一鍵導(dǎo)出 Antd Table 看這篇就夠了(附源碼)[1]詳細(xì)介紹了如何實(shí)現(xiàn)解析 Antd Table、組裝數(shù)據(jù)和調(diào)整表格的樣式,感興趣的可以先看看。本篇將接著上一篇,重點(diǎn)講方法的更高級(jí)抽象,和下載多層級(jí)文件夾的 zip 壓縮包。源碼地址:github.com/cachecats/e…[2]
實(shí)現(xiàn)效果
最終下載的是 壓縮包.zip,解壓之后包含多個(gè)文件夾,每個(gè)文件夾下又可以無限嵌套子文件夾,excel 文件可以自由選擇放到根目錄下,或者子文件夾下。實(shí)現(xiàn)效果如圖:
使用方法
使用方式也很簡單,經(jīng)過高度封裝后,只需按照方法參數(shù)的規(guī)則傳入?yún)?shù)即可:
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
}]
},
]
}
]
})
復(fù)制代碼
這里會(huì)封裝三個(gè)方法,分別滿足不同場景下的導(dǎo)出需求:
一、封裝普通的下載導(dǎo)出 excel 方法
我們來封裝一個(gè)常用的,預(yù)定義好樣式,直接能開箱即用的導(dǎo)出方法,使用者不用關(guān)心具體細(xì)節(jié),只管簡單的調(diào)用:
function onExportExcel() {
downloadExcel({
filename: 'test',
sheets: [{
sheetName: 'test',
columns: columns,
dataSource: list
}]
})
}
復(fù)制代碼
如上,直接調(diào)用 downloadExcel方法,它傳入一個(gè)對(duì)象作為參數(shù),分別有 filename和 sheets兩個(gè)屬性。
Sheet對(duì)象的定義:
export interface ISheet {
// sheet 的名字
sheetName: string;
// 這個(gè) sheet 中表格的 column,類型同 antd 的 column
columns: ColumnType<any>[];
// 表格的數(shù)據(jù)
dataSource: any[];
}
復(fù)制代碼
核心代碼
downloadExcel方法關(guān)鍵源碼:
export interface IDownloadExcel {
filename: string;
sheets: ISheet[];
}
export interface ISheet {
// sheet 的名字
sheetName: string;
// 這個(gè) sheet 中表格的 column,類型同 antd 的 column
columns: ColumnType<any>[];
// 表格的數(shù)據(jù)
dataSource: any[];
}
/**
* 下載導(dǎo)出簡單的表格
* @param params
*/
export function downloadExcel(params: IDownloadExcel) {
// 創(chuàng)建工作簿
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);
// 設(shè)置 sheet 的默認(rèn)行高。設(shè)置默認(rèn)行高跟自動(dòng)撐開單元格沖突
// worksheet.properties.defaultRowHeight = 20;
// 設(shè)置列
worksheet.columns = generateHeaders(sheet.columns);
handleHeader(worksheet);
handleData(worksheet, sheet);
}
export function saveWorkbook(workbook: Workbook, fileName: string) {
// 導(dǎo)出文件
workbook.xlsx.writeBuffer().then((data: any) => {
const blob = new Blob([data], {type: ''});
saveAs(blob, fileName);
});
}
復(fù)制代碼
generateHeaders方法是設(shè)置表格的列。handleHeader方法負(fù)責(zé)處理表頭,設(shè)置表頭的高度、背景色、字體等樣式。handleData方法處理每一行具體的數(shù)據(jù)。這三個(gè)方法的實(shí)現(xiàn)在上篇文章都有介紹,如需了解更多請(qǐng)查看源碼:github.com/cachecats/e…[3]
導(dǎo)出的 excel 效果如下圖,列寬會(huì)根據(jù)傳入的 width 動(dòng)態(tài)計(jì)算,單元格高度會(huì)根據(jù)內(nèi)容自動(dòng)撐開。
二、導(dǎo)出包含多個(gè) excel 的 zip 壓縮包
如果沒有多級(jí)目錄的需求,只想把多個(gè) excel 文件打包到一個(gè)壓縮包里,可以用 downloadFiles2Zip這個(gè)方法,得到的目錄結(jié)構(gòu)如下圖:
參數(shù)結(jié)構(gòu)如下,支持導(dǎo)出多個(gè) excel 文件,每個(gè) excel 文件又可以包含多個(gè) sheet。
export interface IDownloadFiles2Zip {
// 壓縮包的文件名
zipName: string;
files: IDownloadExcel[];
}
export interface IDownloadExcel {
filename: string;
sheets: ISheet[];
}
export interface ISheet {
// sheet 的名字
sheetName: string;
// 這個(gè) sheet 中表格的 column,類型同 antd 的 column
columns: ColumnType<any>[];
// 表格的數(shù)據(jù)
dataSource: any[];
}
復(fù)制代碼
使用示例
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
}]
}
]
})
}
復(fù)制代碼
核心代碼
通過 handleEachFile()方法處理每個(gè) fille 對(duì)象,每個(gè) file 其實(shí)就是一個(gè) excel 文件,即一個(gè) workbook。給每個(gè) excel 創(chuàng)建 workbook并將數(shù)據(jù)寫入,然后通過 JsZip庫寫入到壓縮文件內(nèi),最終用 file-saver庫提供的 saveAs方法導(dǎo)出壓縮文件。注意 12、13行,handleEachFile()方法返回的是一個(gè) Promise,需要等所有異步方法都執(zhí)行完之后再執(zhí)行下面的生成 zip 方法,否則可能會(huì)遺漏文件。
import {saveAs} from 'file-saver';
import * as ExcelJs from 'exceljs';
import {Workbook, Worksheet, Row} from 'exceljs';
import JsZip from 'jszip'
/**
* 導(dǎo)出多個(gè)文件為zip壓縮包
*/
export async function downloadFiles2Zip(params: IDownloadFiles2Zip) {
const zip = new JsZip();
// 待每個(gè)文件都寫入完之后再生成 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) {
// 創(chuàng)建工作簿
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 中一個(gè)文件
zip.file(`${param.filename}.xlsx`, blob);
}
}
function handleEachSheet(workbook: Workbook, sheet: ISheet) {
// 添加sheet
const worksheet = workbook.addWorksheet(sheet.sheetName);
// 設(shè)置 sheet 的默認(rèn)行高。設(shè)置默認(rèn)行高跟自動(dòng)撐開單元格沖突
// worksheet.properties.defaultRowHeight = 20;
// 設(shè)置列
worksheet.columns = generateHeaders(sheet.columns);
handleHeader(worksheet);
handleDataWithRender(worksheet, sheet);
}
復(fù)制代碼
render 渲染的單元格處理
數(shù)據(jù)處理還有一點(diǎn)需要注意,因?yàn)橛械膯卧袷峭ㄟ^ render 函數(shù)渲染的,render 函數(shù)里可能進(jìn)行了一系列復(fù)雜的計(jì)算,所以如果 column 中有 render 的話不能直接以 dataIndex 為 key 進(jìn)行取值,要拿到 render 函數(shù)執(zhí)行后的值才是正確的。比如 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: '性別',
},
];
復(fù)制代碼
第一列傳入了 render 函數(shù) render: (text, row)=> <div><p>{row.id + 20}</p></div>,經(jīng)過計(jì)算后,ID 列顯示的值應(yīng)該是原來的 id + 20。構(gòu)造的數(shù)據(jù)原來的 id 是 0-4,頁面上顯示的應(yīng)該是 20-24,如下圖:
這時(shí)導(dǎo)出的 excel 應(yīng)該跟頁面上顯示的一模一樣,這樣才是正確的。點(diǎn)擊【導(dǎo)出zip】按鈕,解壓后打開下載的其中一個(gè) excel,驗(yàn)證顯示的內(nèi)容跟在線表格完全一致。
那么是如何做到的呢?主要看 handleDataWithRender()方法:
/**
* 如果 column 有 render 函數(shù),則以 render 渲染的結(jié)果顯示
* @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 說明沒包裹標(biāo)簽,是基本類型直接返回
if (typeof renderResult !== "object") {
return renderResult;
}
// 如果是 object 說明包裹了標(biāo)簽,逐級(jí)取出值
return getValueFromRender(renderResult);
}
// @ts-ignore
return data[column.dataIndex];
})
})
// 添加行
const rows = worksheet.addRows(rowsData);
// 設(shè)置每行的樣式
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 ''
}
復(fù)制代碼
worksheet.addRows()可以添加數(shù)據(jù)對(duì)象,也可以添加由每行的每列組成的二維數(shù)組。由于我們要自己控制每個(gè)單元格顯示的內(nèi)容,所以采用第二種方式,傳入一個(gè)二維數(shù)組來構(gòu)造 row。結(jié)構(gòu)如下圖所示:
循環(huán) dataSource和 columns,就得到了每個(gè)單元格要顯示的內(nèi)容,通過執(zhí)行 render 函數(shù),得到 render 執(zhí)行后的結(jié)果:const renderResult=column?.render?.(data[column.dataIndex], data);注意 render 需要傳入兩個(gè)參數(shù),一個(gè)是 text,一個(gè)是這行的數(shù)據(jù)對(duì)象,我們都能確定參數(shù)的值,所以直接傳入。然后判斷 renderResult的類型,如果是 object 類型,說明是個(gè)由 html 標(biāo)簽包裹的 ReactNode,需要遞歸取出最終渲染的值。如果是非 object 類型,說明是 boolean 或者 string 這樣的基本類型,即沒有被標(biāo)簽包裹,可以直接展示。由于我們采用了遞歸來取最后渲染的值,所以無論嵌套了多少層標(biāo)簽,都可以正確的取到值。
三、導(dǎo)出包含多個(gè)子文件夾、多個(gè)excel文件的 zip 壓縮包
如果文件、文件夾嵌套比較深,可以使用 downloadFiles2ZipWithFolder()方法。文件結(jié)構(gòu)如下圖:
核心代碼
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;
// 這個(gè) sheet 中表格的 column,類型同 antd 的 column
columns: ColumnType<any>[];
// 表格的數(shù)據(jù)
dataSource: any[];
}
/**
* 導(dǎo)出支持多級(jí)文件夾的壓縮包
* @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]);
}
復(fù)制代碼
跟上一個(gè)方法 downloadFiles2Zip相比,參數(shù)的數(shù)據(jù)結(jié)構(gòu)多了層 folders,其他的邏輯基本沒變。所以 downloadFiles2ZipWithFolder方法能實(shí)現(xiàn)downloadFiles2Zip方法的所有功能。
使用示例
如文章開頭的使用示例,為了方便看清結(jié)構(gòu),將每個(gè)對(duì)象的 files 值刪除,精簡之后得到如下結(jié)構(gòu):
downloadFiles2ZipWithFolder({
zipName: '壓縮包',
folders: [
{
folderName: '文件夾1',
files: []
},
{
folderName: '文件夾2',
files: []
},
{
folderName: '文件夾2/文件夾2-1',
files: []
},
{
folderName: '文件夾2/文件夾2-1/文件夾2-1-1',
files: []
},
{
folderName: '',
files: []
}
]
})
復(fù)制代碼
不管嵌套幾層文件夾,folders永遠(yuǎn)是一個(gè)一維數(shù)組,每一項(xiàng)里面也不會(huì)嵌套 folders。多級(jí)目錄是通過文件名 folderName實(shí)現(xiàn)的。
如需查看 demo 完整代碼,源碼地址:github.com/cachecats/e…[4]
我的博客即將同步至騰訊云+社區(qū),邀請(qǐng)大家一同入駐:cloud.tencent.com/developer/s…[5]
關(guān)于本文
https://juejin.cn/post/7080169896209809445
述可幫助您找到構(gòu)建可處理大量數(shù)據(jù)的Web應(yīng)用程序的正確解決方案。
在本文中,我將基于其功能和許可策略簡要概述五個(gè)流行的獨(dú)立JavaScript電子表格庫。這些圖書館可以免費(fèi)在非營利項(xiàng)目中實(shí)施,也可以提供商業(yè)用途的付費(fèi)許可。我希望此概述可以幫助您找到構(gòu)建旨在處理大量數(shù)據(jù)的Web應(yīng)用程序的正確解決方案。
Handsontable
Handsontable據(jù)說是一個(gè)帶有電子表格外觀的JavaScript網(wǎng)格。它是一個(gè)純JavaScript庫,支持React,Vue.js和Angular。其基本功能列表包括折疊列,調(diào)整大小,移動(dòng)和隱藏列和行,添加注釋,顯示列摘要,導(dǎo)出數(shù)據(jù),應(yīng)用條件格式,使用數(shù)據(jù)驗(yàn)證和添加下拉菜單的功能。也可以對(duì)數(shù)據(jù)進(jìn)行排序和過濾并使用自動(dòng)填充。
更有趣的是高級(jí)功能列表。例如,開發(fā)人員可以選擇在觸發(fā)表呈現(xiàn)時(shí)應(yīng)使用哪個(gè)渲染器。此外,還可以創(chuàng)建自定義插件,使用自定義按鈕,并在單元格之間定義自定義邊框。此外,還有諸如多列排序,嵌套標(biāo)題,修剪行 等功能。
Handsontable提供 三種類型的許可證:非商業(yè)免費(fèi)許可證,開發(fā)人員(每個(gè)開發(fā)人員790美元),企業(yè)(自定義價(jià)格)許可證。
我的結(jié)論:Handsontable是非商業(yè)項(xiàng)目和那些準(zhǔn)備為豐富功能付費(fèi)的人的一個(gè)很好的選擇。
AG-網(wǎng)格
ag-Grid是一個(gè)JavaScript網(wǎng)格/電子表格組件,可以與Angular,Angular JS 1.x,React,Vue.js,Polymer和Web Components輕松集成,沒有第三方依賴。這個(gè)100,000行的演示判斷它的快速性能和數(shù)十 行。
分組和聚合功能允許用戶以他們想要的方式處理數(shù)據(jù)??梢园刺囟袑?duì)數(shù)據(jù)進(jìn)行分組,并且可以在分組行中顯示各種聚合列值。ag-Grid提供快速過濾功能和自定義過濾器。延遲加載允許在用戶滾動(dòng)時(shí)顯示所需的行數(shù)并請(qǐng)求其他數(shù)據(jù),這有助于節(jié)省服務(wù)器資源。ag-Grid支持實(shí)時(shí)更新,每秒可處理數(shù)百個(gè)更新。您可以閱讀有關(guān)這些功能和其他功能的更多信息,并在功能概述頁面上查看一些演示 。
該庫有 兩個(gè)版本:社區(qū)和企業(yè)。社區(qū)版本由MIT許可證涵蓋,僅包括基本功能。具有所有可用功能的Enterprise許可證有三個(gè)選項(xiàng):Single Application Developer(每個(gè)開發(fā)人員750美元),Multiple Application Developer(每個(gè)開發(fā)人員1,200美元)和部署許可證(每個(gè)生產(chǎn)環(huán)境750美元)。
我的結(jié)論是:ag-Grid提供了許多有用的功能,并且可以與不同的JS框架進(jìn)行簡單的集成。對(duì)于大預(yù)算項(xiàng)目來說,這似乎是一個(gè)不錯(cuò)的選擇,因?yàn)樵S可非常靈活而且價(jià)格昂貴。
dhtmlxSpreadsheet
dhtmlxSpreadsheet是一個(gè)可自定義的JavaScript電子表格組件,具有Material skin和類似Excel的界面。它為三個(gè)流行的框架提供了包裝器:React,Vue.js和Angular。
您可以根據(jù)其用戶指南自定義此電子表格的幾乎每個(gè)元素- 例如,使用工具欄,菜單和上下文菜單控件的自定義圖標(biāo)字體包而不是Material-design控件。單元格格式功能允許您更改文本顏色和裝飾,背景顏色,設(shè)置文本對(duì)齊,將不同的數(shù)字格式應(yīng)用于單元格值(百分比,貨幣甚至自定義格式),調(diào)整列大小等。
還可以鎖定單元格,通過在單元格中鍵入數(shù)據(jù)來自動(dòng)填充包含內(nèi)容的單元格,并拖動(dòng)填充句柄以延長其他單元格中的一系列數(shù)字或字母。dhtmlxSpreadsheet提供了相當(dāng)多的導(dǎo)航熱鍵列表。此外,該庫提供了從Excel文件導(dǎo)入和導(dǎo)出數(shù)據(jù)的機(jī)會(huì)。為此,dhtmlxSpreadsheet開發(fā)人員已經(jīng)實(shí)現(xiàn)了他們自己的開源庫Excel2Json和Json2Excel。
dhtmlxSpreadsheet提供四個(gè)主要許可證:免費(fèi)GNU GPL v2,商業(yè)許可證(最多5個(gè)開發(fā)人員149美元),企業(yè)許可證(最多20個(gè)開發(fā)人員團(tuán)隊(duì)449美元),終極許可證(無限數(shù)量的開發(fā)人員669美元)。
我的判斷:dhtmlxSpreadsheet提供了一組基本功能和對(duì)流行框架的支持,可以被認(rèn)為是物有所值的。
Clusterize.js
開發(fā)人員將Clusterize.js描述 為一個(gè)很小的插件,可以輕松顯示大型數(shù)據(jù)集。它的權(quán)重僅為2.3KB gzipped,但遺憾的是它沒有提供任何高級(jí)功能。其主要目的是使具有大量行的表在網(wǎng)頁上平滑運(yùn)行。Clusterize.js不是用所有使用的標(biāo)簽“污染”DOM,而是將列表拆分為簇,然后顯示當(dāng)前滾動(dòng)位置的元素,并在列表的頂部和底部添加額外的行以模擬表的完整高度,以便瀏覽器顯示完整列表的滾動(dòng)條。此庫適用于現(xiàn)代瀏覽器,并支持所有主要的移動(dòng)設(shè)備。
好消息是Clusterize.js非常實(shí)惠。在 可用的許可證中,您可以找到個(gè)人許可證(個(gè)人項(xiàng)目免費(fèi)),商業(yè)許可證(25美元用于商業(yè)用途和無限數(shù)量的項(xiàng)目),以及擴(kuò)展許可證(110美元;可以包含在待售產(chǎn)品中)。
我的結(jié)論是:Clusterize.js是那些尋找能夠快速運(yùn)行并節(jié)省一些錢的單一用途工具的人的絕佳選擇。
SlickGrid
SlickGrid是一個(gè)簡潔而簡約的JavaScript電子表格組件。自適應(yīng)虛擬滾動(dòng)允許處理數(shù)十萬行而沒有任何延遲。該庫支持jQuery UI主題并支持廣泛的自定義。用戶可以調(diào)整列的大小,重新排序,顯示或隱藏,使用分組,過濾,自定義聚合器和其他功能??刹迦雴卧窀袷狡骱途庉嬈髟试S您擴(kuò)展Web應(yīng)用程序的功能。
如您所見,SlickGrid提供了一組基本功能,可以滿足普通用戶的需求。不幸的是,根據(jù)項(xiàng)目的 GitHub頁面,這個(gè)庫最近沒有受到開發(fā)人員的太多關(guān)注。好消息是SlickGrid是免費(fèi)提供的。
我的判斷:如果你不是在尋找豐富的功能或者買不起商業(yè)許可證,那么SlickGrid可能是個(gè)不錯(cuò)的選擇。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。