@asasugar-use/custom-json2excel
Version:
✌传入json,可自定义表格标题名称和列数、头部名称、过滤列和绑定生成开始与成功的回调函数
379 lines (357 loc) • 12.3 kB
text/typescript
import { isFunction, isObject, isNullOrUnDef, isBase64 } from './utils/is';
import { readerData, base64ToBlob, stringToBlob } from './utils/index';
import type { AnyObject, AnyObjectArray, VoidFunction, Json2ExcelParams, ElsExtend } from './typing';
export class JsonToExcel {
// 可选的json数组,用于存储要导出的数据
data: AnyObjectArray;
// 可选的作用域对象,用于解析数据中的嵌套字段
scope?: AnyObject;
// 可选的有序键数组,用于指定导出数据的列顺序
orderedKey?: string[];
// 可选的过滤器数组,用于指定要排除的列
filters?: string[];
// 可选的标题行配置
title?: ElsExtend[];
// 可选的页脚行配置
footer?: ElsExtend[];
// 可选的键映射对象,用于自定义列名
keyMap?: AnyObject;
// 导出文件的名称
name?: string;
// 导出文件的类型,默认为 'xls'
type?: 'xls' | 'csv';
// 导出开始时的回调函数
onStart?: VoidFunction;
// 导出成功时的回调函数
onSuccess?: VoidFunction;
// 导出失败时的回调函数
onError?: (err?: any) => void;
constructor({
data = [],
scope = {},
orderedKey = [],
filters = [],
title = [],
footer = [],
keyMap = {},
name = 'excel',
type = 'xls',
onStart = () => { },
onSuccess = () => { },
onError = (err) => { console.log(err); },
}: Json2ExcelParams) {
// 初始化实例属性
this.data = data;
this.scope = scope;
this.filters = filters;
this.footer = footer;
this.orderedKey = orderedKey;
this.keyMap = keyMap;
this.name = name;
this.title = title;
this.type = type;
this.onStart = onStart;
this.onSuccess = onSuccess;
this.onError = onError;
}
// 生成 Excel 或 CSV 文件
public generate () {
// 如果数据为空,则调用 onError 回调函数并返回
if (!this.data || !this.data.length) {
this.onError && this.onError();
return;
}
// 调用 onStart 回调函数
this.onStart && this.onStart();
// 处理数据,应用过滤器、键映射等
let json = this._getProcessedJson(this.data);
// 将键名转换为自定义(如果有配置)
json = this._toChsKeys(json);
// 如果导出类型为 CSV
if (this.type == 'csv') {
return this._export(
this._jsonToCSV(json),
`${this.name}.${this.type}`,
'application/csv'
);
}
// 导出 Excel 文件
return this._export(
this._jsonToXLS(json),
`${this.name}.${this.type}`,
'application/vnd.ms-excel'
);
}
// 获取对象的最后一个值
private _getObjLastValue (obj?: any, scope?: any): unknown {
// 如果 scope 是对象,则递归获取最后一个值
if (isObject(scope)) {
let k = Object.keys(scope)[0];
let v = Object.values(scope)[0];
return this._getObjLastValue(obj[k], v);
}
// 返回 obj 中 scope 对应的值
return obj[scope];
}
/**
* 将 JSON 数据中的键名转换为自定义(如果有配置)
* @param json - 要转换的 JSON 数据
* @returns 转换后的 JSON 数据
*/
private _toChsKeys (json: AnyObjectArray): AnyObjectArray {
return json.map((item) => {
let newItem: AnyObject = {};
// step 1: 指定key顺序,适用于需要key按照一定顺序,并且只保留key中存在的字段
if (this.orderedKey && this.orderedKey.length) {
for (let keyItem of this.orderedKey) {
newItem[keyItem] = item[keyItem];
}
}
// step 2: 判断是否需要额外过滤数据(正常情况指定了orderedKey的话,可不需要配置该字段就支持过滤)
if (this.filters && this.filters.length) {
for (let filterItem of this.filters) {
delete newItem[filterItem];
}
}
// step 3: 判断scope,获取深层次数据展示
if (this.scope && Object.keys(this.scope).length) {
let scopeItem = Object.keys(newItem).length ? newItem : item;
for (let key in scopeItem) {
if (this.scope.hasOwnProperty(key)) {
newItem[key] = this._getObjLastValue(
scopeItem[key],
this.scope[key]
);
} else {
newItem[key] = scopeItem[key];
}
}
}
// step 4: keyMap 映射表,自定义表格列名称
if (this.keyMap && Object.keys(this.keyMap).length) {
let keyMapItem = Object.keys(newItem).length ? newItem : item;
for (let key in keyMapItem) {
if (this.keyMap.hasOwnProperty(key)) {
newItem[this.keyMap[key]] = keyMapItem[key];
delete keyMapItem[key];
}
}
}
return Object.keys(newItem).length ? newItem : item;
});
}
/**
* 下载文件
* @param blob - 要下载的 Blob 对象
* @param filename - 文件名
*/
private _download (blob: Blob, filename: string) {
// IE浏览器
if (window.navigator && (window.navigator as any).msSaveOrOpenBlob) {
(window.navigator as any).msSaveOrOpenBlob(blob, filename);
} else {
// 其他浏览器
const anchor = document.createElement('a');
const url = window.URL.createObjectURL(blob);
anchor.href = url;
anchor.setAttribute('download', filename);
anchor.innerHTML = 'downloading...';
anchor.style.display = 'none';
document.body.appendChild(anchor);
setTimeout(() => {
anchor.click();
document.body.removeChild(anchor);
setTimeout(() => {
self.URL.revokeObjectURL(anchor.href);
}, 250);
}, 66);
}
}
/**
* 导出文件
* @param data - 文件数据
* @param filename - 文件名
* @param mime - 文件类型
*/
private _export (data: string, filename: string, mime: string) {
new Promise((resolve) => {
let blob: Blob;
if (isBase64(data)) {
blob = base64ToBlob(data, mime);
} else {
blob = stringToBlob(data, mime);
}
resolve(this._download(blob, filename));
})
.then(() => {
this.onSuccess && this.onSuccess();
})
.catch((err) => {
this.onError && this.onError(err);
});
}
/**
* 将 JSON 数据转换为 XLS 文件
* @param data - 要转换的 JSON 数据
* @returns 转换后的 XLS 文件内容
*/
private _jsonToXLS (data: AnyObjectArray) {
let xlsTemp =
'<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"><head><meta name=ProgId content=Excel.Sheet> <meta name=Generator content="Microsoft Excel 11"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>{worksheet}</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--><style type="text/css">td{mso-number-format:\@;}</style></head><body><table>${table}</table></body></html>';
let xlsData = '<thead><tr>';
//Header
if (this.title && this.title.length) {
for (let i of this.title) {
xlsData += `<th colspan=${i.colspan}>${i.name}`;
}
xlsData += '<th></tr>';
}
//Fields
for (let key in data[0]) {
xlsData += '<th>' + key + '</th>';
}
xlsData += '</tr></thead>';
xlsData += '<tbody>';
//Data
data.map((item) => {
xlsData += '<tbody><tr>';
for (let key in item) {
xlsData += `<td>${item[key]}</td>`;
}
xlsData += '</tr></tbody>';
});
//Footer
if (this.footer && this.footer.length) {
xlsData += '<tfooter><tr>';
for (let i of this.footer) {
xlsData += `<th colspan=${i.colspan}>${i.name}`;
}
xlsData += '<th></tr></tr></tfooter>';
}
return xlsTemp.replace('${table}', xlsData);
}
/**
* 将 JSON 数据转换为 CSV 文件
* @param data - 要转换的 JSON 数据
* @returns 转换后的 CSV 文件内容
*/
private _jsonToCSV (data: AnyObjectArray) {
var csvData = '';
//Header
if (this.title && this.title.length) {
for (let i of this.title) {
csvData += `${i.name}`;
}
csvData += '\r\n';
}
//Fields
for (let key in data[0]) {
csvData += key + ',';
}
csvData = csvData.slice(0, csvData.length - 1);
csvData += '\r\n';
//Data
data.map((item) => {
for (let key in item) {
let escapedCSV = item[key] + ''; // cast Numbers to string
if (escapedCSV.match(/[,"\n]/)) {
escapedCSV = '"' + escapedCSV.replace(/\"/g, '""') + '"';
}
csvData += escapedCSV + ',';
}
csvData = csvData.slice(0, csvData.length - 1);
csvData += '\r\n';
});
//Footer
if (this.footer && this.footer.length) {
for (let i of this.footer) {
csvData += `${i.name}`;
}
csvData += '\r\n';
}
return csvData;
}
/**
* 处理 JSON 数据
* @param data - 要处理的 JSON 数据
* @returns 处理后的 JSON 数据
*/
private _getProcessedJson (data: AnyObjectArray) {
let keys = this._getKeys(data);
let newData: AnyObjectArray = [];
data.map((item: AnyObject) => {
let newItem: AnyObject = {};
for (let label in keys) {
let property = keys[label];
newItem[label] = this._getNestedData(property, item);
}
newData.push(newItem);
});
return newData;
}
/**
* 获取对象数组的键集合
* @param data 对象数组,每个元素是一个对象
* @returns 返回一个对象,其属性名来自于数组中第一个对象的键
*/
private _getKeys (data: AnyObjectArray): AnyObject {
let keys: AnyObject = {};
for (let key in data[0]) {
keys[key] = key;
}
return keys;
}
/**
* 获取嵌套数据
* 该方法用于根据嵌套的键获取对象中深层的值
* @param key {Object} 包含要访问的字段名称的对象或字段名称本身
* @param item {Object} 包含嵌套数据的项
* @returns {any} 返回从嵌套键获取的值,如果值为null或undefined,则返回空字符串
*/
private _getNestedData (key: { field: any; }, item: { [x: string]: any; }) {
// 提取字段名称,如果key是对象,则使用其field属性,否则直接使用key
const field = isObject(key) ? key.field : key;
// 初始化从嵌套键获取的值
let valueFromNestedKey = null;
// 分割字段名称以处理嵌套结构
let keyNestedSplit = `${field}`.split('.');
// 从item中获取嵌套键的值
valueFromNestedKey = item[keyNestedSplit[0]];
// 遍历分割的键以获取深层嵌套的值
for (let j = 1; j < keyNestedSplit.length; j++) {
valueFromNestedKey = valueFromNestedKey[keyNestedSplit[j]];
}
// 调用回调函数处理获取的值
valueFromNestedKey = this._callItemCallback(key, valueFromNestedKey);
// 将null或undefined的值替换为空字符串
valueFromNestedKey =
isNullOrUnDef(valueFromNestedKey)
? ''
: valueFromNestedKey; // 过滤null、undefined的值
// 返回处理后的值
return valueFromNestedKey;
}
/**
* 调用特定字段的回调函数
*
* 此函数用于检查给定的字段对象是否包含一个回调函数,并在存在的情况下调用该回调函数
* 如果回调函数不存在或字段不是一个对象,则直接返回传入的itemValue值
*
* @param field 包含callback属性的字段对象,callback属性是一个函数
* @param itemValue 要传递给回调函数的值,如果没有回调函数则直接返回这个值
* @returns 调用回调函数后的结果或直接返回的itemValue
*/
private _callItemCallback (
field: { field?: any; callback?: any; },
itemValue: any
) {
if (isObject(field) && isFunction(field.callback)) {
return field.callback(itemValue);
}
return itemValue;
}
}
// 将工作表数据转换为 JSON
export async function excelToJson (rawFile: File) {
return await readerData(rawFile);
}