功能主要是使用excejs实现的,第一需要安装exceljs(这里用的4.4.0,其他版本貌似有些函数不支持)
npm install exceljs@4.4.0 -S
附上exceljs 的官方文档 链接:
中文:http://www.npmdoc.org/exceljszhongwenwendangexceljs-jszhongwenjiaochengjiexi.html
英文: https://github.com/exceljs/exceljs#streaming-xlsx
由于中文的是直译,所以有些地方, 参数等的需要看英文的才能确认。
第一写个把目录下所有文件都合并成一个文件的模块,如下:
import * as fs from "fs"
import * as path from "path"
const basePath = path.resolve(__dirname, "/files/")
// 合并文件的方法, 把目录 dirs里的所有文件合并成一个
export function mergeFile(dirs: string, name: string = "") {
const files = fs.readdirSync(path.resolve(__dirname, dirs))
if (files.length === 0) return ""
name = name || files[0].substr(files[0].lastIndexOf( / ) + 1).split("_")[0]
const fileUrl = path.resolve(basePath, name + .xls )
const wStream = fs.createWriteStream(fileUrl)
if (addFileToStream(files, dirs, wStream)) {
return fileUrl
} else {
return ""
}
}
function addFileToStream(files: string[], baseDir: string, wStream) { // 递归合并的方法
if(files.length === 0) {
// 合并完成
wStream.end()
return true
}
const dir = path.resolve(__dirname, baseDir, files.shift())
const rStream = fs.createReadStream(dir)
rStream.pipe(wStream, {
end: false
})
rStream.on("end", () => {
addFileToStream(files, baseDir, wStream)
})
rStream.on("error", err => {
// 此处错误信息应该写入到log日志中
console.log(err)
return false
})
}
然后是基类,这里的基类是为了处理大文件的,直接影响到文件,且不占用内存。所以是流式.这里包含了大部分的用法和公共方法
// 流式创建excel基类
import {
exchangeWordToNum,
exchangeColor,
} from utils
const Excel = require( exceljs );
interface ExcelOptions {
sheetName?: string, // sheet名
useStyles?: boolean, // 是否启用样式, 会增加性能开销
useSharedStrings?: boolean, // 是否启用共享字符串,可以优化性能和空间
creator?: string, // 制作人
rowHeight?: number, // 默认行高
colWidth?: number, // 默认列宽
tabColor?: string, // 选项卡颜色
}
export default class ExcelBase {
fileName: string; //下载得文件路径
workbook: any;
sheetName: string = sheet-1 ;
filePath: string;
options: ExcelOptions = {};
isCommit: boolean = false; // 文件是否生成完成
constructor(fileName: string = , options: ExcelOptions = {}) {
// 真实存储文件得路径
fileName = (fileName ? fileName + - : ) + Date.now() + .xlsx
this.filePath = fileName
this.fileName = baseFileDir + fileName
this.options = options
const opt = {
filename: this.filePath,
useStyles: options.useStyles || true,
useSharedStrings: options.useSharedStrings || true
};
const workbook = new Excel.stream.xlsx.WorkbookWriter(opt);
workbook.creator = options.creator || Windows ;
workbook.lastModifiedBy = options.creator || Windows ;
workbook.created = new Date();
this.sheetName = options.sheetName || this.sheetName
// sheet 属性选项
const sheetOptions = {
properties:{
tabColor: options.tabColor ? exchangeColor(options.tabColor) : "ffffff00", // 选项卡颜色
defaultRowHeight: options.rowHeight || 22, // 默认行高
defaultColWidth: options.colWidth || 20, // 默认列宽
// outlineLevelCol:0, // 列 大纲级别
// outlineLevelRow: 0, // 行 大纲级别
// dyDescent: 55, // 不知道啥意思
}
}
workbook.addWorksheet(this.sheetName, sheetOptions)
this.workbook = workbook
}
get currentSheet() {
return this.workbook.getWorksheet(this.sheetName)
}
get allSheets() {
return this.workbook.worksheets
}
/**
* 添加sheet
* @param sheetName
*/
addSheet(sheetName = ) {
if (!sheetName) {
const length = this.allSheets.length
sheetName = sheet- + length
}
// sheet 属性选项
const sheetOptions = {
properties:{
tabColor: this.options.tabColor ? exchangeColor(this.options.tabColor) : "ffffff00", // 选项卡颜色
defaultRowHeight: this.options.rowHeight || 22, // 默认行高
defaultColWidth: this.options.colWidth || 20, // 默认列宽
}
}
this.workbook.addSheet(sheetName, sheetOptions)
this.sheetName = sheetName
}
/**
* 删除sheet
*/
delSheet(sheetName) {
if (!sheetName) return false
const sheet = this.workbook.getSheet(sheetName)
return this.workbook.removeWorksheet(sheet.id)
}
/**
* 根据名字获取sheet
* @param sheetName
* @returns
*/
getSheet(sheetName = ) {
return this.workbook.getWorksheet(sheetName) || this.currentSheet
}
/**
* 同步添加行数据,对象也转为数组直接添加,【直接写入文件】
*/
addRowData(data, sheetName = ) {
sheetName && (this.sheetName = sheetName)
if(data[0] && data[0] instanceof Array) {
data.forEach(cur => {
this.currentSheet.addRow(cur)
})
} else {
data.forEach(cur => {
const arr = Object.values(cur)
this.currentSheet.addRow(arr)
})
}
}
/**
* 删除行
* @param index 从哪行开始删除(注意,第一行是1)
* @param count 删除几行
*/
delRows(index: number, count: number = 1, sheetName: string = ) {
const sheet = this.getSheet(sheetName)
sheet.spliceRows(index, count)
}
/**
* 删除列
* @param index 从哪行开始删除(注意,第一行是1)
* @param count 删除几行
*/
delCols(index: number, count: number = 1, sheetName: string = ) {
const sheet = this.getSheet(sheetName)
sheet.spliceColumns(index, count)
}
/**
* 添加行
* @param index 从哪行开始添加(注意,第一行是1)
* @param data 二维数组;添加几行data.length就是几
*/
addRows(index: number, data: any[] = [], sheetName: string = ) {
const sheet = this.getSheet(sheetName)
if (data[0] && data[0] instanceof Array) {
sheet.spliceRows(index, 0, ...data)
} else {
sheet.spliceRows(index, 0, data)
}
}
/**
* 添加列
* @param index 从哪列开始添加(注意,第一行是1)
* @param data 二维数组;添加几列data.length就是几
*/
addCols(index: number, data: any[] = [], sheetName: string = ) {
const sheet = this.getSheet(sheetName)
if (data[0] && data[0] instanceof Array) {
sheet.spliceCols(index, 0, ...data)
} else {
sheet.spliceCols(index, 0, data)
}
}
/**
* 删除cell
* @param A1 A1表达式, 支持范围(A1:B5)
* @param sheetName
*/
delCells(A1: string, sheetName: string = ) {
const sheet = this.getSheet(sheetName)
const getAReg = /([a-z]+?)d/i
const cellInfo: Array<{row: number, cell1: number, cell2: number }> = []
const A1s = A1.split( : )
if (A1s.some(item => {
const res = getAReg.exec(item)
if (!res) return true
cellInfo.push({
row: exchangeWordToNum(res[1]) + 1,
cell1: Number(A1.replace(res[0], )),
cell2: Number(A1.replace(res[0], ))
})
})) return false
if (cellInfo[1] && cellInfo[1].cell2 !== cellInfo[0].cell1) {
cellInfo[0].cell2 = cellInfo[1].cell2
cellInfo[1].cell1 = cellInfo[0].cell1
}
cellInfo.forEach(item => {
const row = sheet.getRow(item.row)
row.splice(item.cell1, item.cell2 - item.cell1)
})
}
/**
* 根据A1表达式,设置具体某个单元格的样式
* @param A1
*/
setStyleByA1(A1: string, style: {[key:string]: any}) {
const cell = this.currentSheet.getCell(A1)
// 设置颜色
style.fill && (cell.fill = {
type: style.fill.type || pattern ,
pattern: style.fill.pattern || solid ,
fgColor: { argb: exchangeColor(style.fill.color) || FF222222 }, // 注意,这里的颜色是 argb
});
// 设置border
style.border && (cell.border = {
// style 可选值 thin thick double dotted 等
top: {style: style.border.style || thin , color: {argb: exchangeColor(style.border.color) || FF333333 }},
left: {style: style.border.style || thin , color: {argb: exchangeColor(style.border.color) || FF333333 }},
bottom: {style: style.border.style || thin , color: {argb: exchangeColor(style.border.color) || FF333333 }},
right: {style: style.border.style || thin , color: {argb: exchangeColor(style.border.color) || FF333333 }}
})
// 设置字体字号
style.font && (cell.font = {
name: style.font.name || 宋体 ,
family: 4, // 字体样式,西文样式或者等宽样式等。4标识等宽,适合表格,或者1 自动适配
size: style.font.size || 14,
color: { argb: exchangeColor(style.font.color) || FF222222 },
underline: style.font.underline || none ,
italic: style.cellStyle.italic || none ,
bold: style.font.bold || false
})
}
/**
* 根据index,设置一行的样式
* @param index
*/
setRowStyleByIndex(index: number, style: {[key:string]: any}) {
const row = this.currentSheet.getRow(index)
// 设置颜色
style.fill && (row.fill = {
type: style.fill.type || pattern ,
pattern: style.fill.pattern || solid ,
fgColor: { argb: exchangeColor(style.fill.color) || FF222222 }, // 注意,这里的颜色是 argb
});
// 设置border
style.border && (row.border = {
// style 可选值 thin thick double dotted 等
top: {style: style.border.style || thin , color: {argb: exchangeColor(style.border.color) || FF333333 }},
left: {style: style.border.style || thin , color: {argb: exchangeColor(style.border.color) || FF333333 }},
bottom: {style: style.border.style || thin , color: {argb: exchangeColor(style.border.color) || FF333333 }},
right: {style: style.border.style || thin , color: {argb: exchangeColor(style.border.color) || FF333333 }}
})
// 设置字体字号
style.font && (row.font = {
name: style.font.name || 宋体 ,
family: 4, // 字体样式,西文样式或者等宽样式等。4标识等宽,适合表格,或者1 自动适配
size: style.font.size || 14,
color: { argb: exchangeColor(style.font.color) || FF222222 },
underline: style.font.underline || none ,
italic: style.cellStyle.italic || none ,
bold: style.font.bold || false
})
}
/**
* 根据A1 设置单元格值
*/
setValueByA1(A1: string, value: string|number|Date) {
this.currentSheet.getCell(A1).value = value
}
/**
* 设置index这一行的数据
* @param index
* @param data 值数组
*/
setRowValuesByIndex(index: number, data: Array<string|number|Date>) {
this.currentSheet.getRow(index).values = data
}
/**
* 传入列索引或列字母,设置列值
* @param letter
* @param data 值数组
*/
setColValuesByIndex(letter: string | number, data: Array<string|number|Date>) {
const index = typeof letter === number ? letter:exchangeWordToNum(letter)
this.currentSheet.getColumn(index).values = data
}
/**
* 所有操作写入到文件,【并返回文件路径】 用于下载
*/
writeToFile() {
// 最后修改时间
this.workbook.modified = new Date()
// 所有更改提交
this.workbook.commit()
this.isCommit = true
// 返回下载路径
return this.fileName
}
}
一些问题点
** 这里要注意 流式的写入文件就是为了减少大文件操作内存溢出的问题,所以添加完row的样式和值之后,要row.commit(),或者 worksheet.commit() 才会释放内存 **
** 样式问题:如果你给每部分赋值的样式是同一个对象,那么他们用的就是同一个样式文件,即你有某个部分单独改过这个样式,那么这个修改会作用于所有使用这个样式文件的地方,这个尤其需要注意。为了避免大多数问题deepclone是个不错的选择 **
** 合并单元格设置border问题: 如果你合并了几个单元格,然后再对这个合并单元格设置border的时候,这个border将只对这个被合并的区域中第一个单元格生效。如果想要对整个合并单元格生效,那么还是要把合并前的每个cell都设置border才行 **
© 版权声明
文章版权归作者所有,未经允许请勿转载。如内容涉嫌侵权,请在本页底部进入<联系我们>进行举报投诉!
THE END
















暂无评论内容