瀏覽代碼

ExcelImport

yuliang 3 年之前
父節點
當前提交
c90c36ae24
共有 5 個文件被更改,包括 851 次插入390 次删除
  1. 291 0
      src/ExcelImportDialog.ts
  2. 1 0
      src/index.ts
  3. 9 0
      src/lib/systemLib.ts
  4. 458 390
      src/xlsx.ts
  5. 92 0
      yarn.lock

+ 291 - 0
src/ExcelImportDialog.ts

@@ -0,0 +1,291 @@
+import XLSX from 'xlsx'
+import {Scope} from "./Scope"
+import {Column, ImportExcelOption, ImportResult, readExcelWithColumnsSet} from "./xlsx";
+import {lookupFn, lookupScope} from "./lib/lib";
+import {msg} from "./message";
+import _ from "lodash";
+
+export class ExcelImportDialog extends Scope {
+    constructor(option: ImportExcelOption) {
+        const vjson = vjsonFunc(option)
+        super({model, vjson});
+        this.importExcelOption = option;
+    }
+
+    onLoad() {
+        super.onLoad();
+    }
+
+    fileChange(sender) {
+        let file = sender.fileInputEl.dom.files[0]
+        // const senderScope = lookupScope(sender)
+        const topScope = this.topScope
+        let rowValidate: any = this.importExcelOption.rowValidate
+        if (this.importExcelOption.rowValidate && typeof this.importExcelOption.rowValidate === 'string') {
+            rowValidate = lookupFn(topScope, this.importExcelOption.rowValidate).bind(topScope)
+        }
+        let afterClientValidate: any = this.importExcelOption.afterClientValidate
+        if (this.importExcelOption.afterClientValidate && typeof this.importExcelOption.afterClientValidate === 'string') {
+            afterClientValidate = lookupFn(topScope, this.importExcelOption.afterClientValidate).bind(topScope)
+        }
+        let fieldValidate: any = this.importExcelOption.fieldValidate
+        if (this.importExcelOption.fieldValidate && typeof this.importExcelOption.fieldValidate === 'string') {
+            fieldValidate = lookupFn(topScope, this.importExcelOption.fieldValidate).bind(topScope)
+        }
+
+        this.setLoading(true)
+
+        readExcelWithColumnsSet(
+            topScope,
+            file,
+            this.importExcelOption.columns,
+            this.importExcelOption.dataStartRow,
+            this.importExcelOption.titleRowNumber,
+            rowValidate,
+            afterClientValidate,
+            fieldValidate
+        ).then(res => {
+            this.importData = res
+            this.refs.dataGrid.setData(res.allData)
+            this.viewModel.set("dataCount", res.allData.length)
+            this.refs.errGrid.setData(res.errorMsgData)
+            this.viewModel.set("errMsgCount", res.errorMsgData.length)
+        }).catch(e => {
+            this.importData = null
+            this.refs.dataGrid.setData()
+            this.refs.errGrid.setData()
+            msg(e.toString())
+        }).finally(() => {
+            this.setLoading(false)
+        })
+    }
+
+    getDataGridRowRecord(record, rowIndex, rowParams, store) {
+        if (record.get('__hasError__')) {
+            return "x-grid-record-bg-red"
+        }
+    }
+
+    getErrGridRowRecord(record, rowIndex, rowParams, store) {
+        return "x-grid-record-bg-red"
+    }
+
+    dataGridClick(sender, td, cellIndex, record, item, index, e, eOpts) {
+        const id = record.data.__importID__;
+        const subId = this.refs.dataGrid.headerCt.getGridColumns()[cellIndex].dataIndex
+        let selectRecord = null
+        let dataIndex = -1
+        const rows = this.refs.errGrid.getStore().getData().items?.map(r => r.data);
+        for (let i = 0; i < rows.length; i++) {
+            if (rows[i].importID === id) {
+                selectRecord = this.refs.errGrid.getStore().getAt(i)
+                dataIndex = i
+            }
+            if (rows[i].errorId === id + "_" + subId) {
+                selectRecord = this.refs.errGrid.getStore().getAt(i)
+                dataIndex = i
+                break
+            }
+        }
+        if (selectRecord) {
+            this.refs.errGrid.ensureVisible(selectRecord)
+            this.refs.errGrid.selModel.select(dataIndex)
+            // this.refs.errGrid.reload()
+        }
+    }
+
+    errGridClick(sender, record, item, index, e, eOpts) {
+        const id = record.data.importID;
+        const rows = this.refs.dataGrid.getStore().getData().items?.map(r => r.data);
+        let selectRecord = null
+        let dataIndex = -1
+        for (let i = 0; i < rows.length; i++) {
+            if (rows[i].__importID__ === id) {
+                selectRecord = this.refs.dataGrid.getStore().getAt(i)
+                dataIndex = i
+                break
+            }
+        }
+
+        if (selectRecord) {
+            this.refs.dataGrid.ensureVisible(selectRecord)
+            this.refs.dataGrid.selModel.select(dataIndex)
+            // this.refs.dataGrid.reload()
+        }
+    }
+
+    filterData(sender, newValue, oldValue, eOpts) {
+        this.viewModel.set("dataType", newValue)
+        if (this.importData) {
+            this.refs.dataGrid.setData(this.importData[newValue])
+            this.viewModel.set("dataCount", this.importData[newValue].length)
+        }
+    }
+
+    downloadTemplate(sender) {
+        if (this.importExcelOption.dowLoadUrl && this.importExcelOption.dowLoadUrl.length > 0) {
+            window.location.href = this.importExcelOption.dowLoadUrl;
+            return
+        }
+        let xt: string[] = []
+        _.forEach(this.importExcelOption.columns, (v) => {
+            xt.push(v.header);
+        })
+        const filename = this.importExcelOption.templateName ? this.importExcelOption.templateName : "模版.xlsx"; //文件名称
+        var ws_name = "Sheet1"; //Excel第一个sheet的名称
+        var wb = XLSX.utils.book_new(), ws = XLSX.utils.aoa_to_sheet([xt]);
+        XLSX.utils.book_append_sheet(wb, ws, ws_name);  //将数据添加到工作薄
+        XLSX.writeFile(wb, filename); //导出Excel
+    }
+
+    getData(sender) {
+        this.dialogSuccess(this.importData)
+    }
+    importData: ImportResult
+    importExcelOption: ImportExcelOption
+}
+
+const model = {
+    data: {
+        dataType: "all",
+        errMsgCount: 0,
+        dataCount: 0
+    }
+}
+const vjsonFunc = (option) => {
+    return {
+        title: option.title || 'excel导入',
+        layout: 'fit',
+        items: [
+            {
+                layout: 'border',
+                tbar: {
+                    xtype: "form",
+                    items: [
+                        {
+                            xtype: "cols",
+                            items: [
+                                {
+                                    xtype: "filefield",
+                                    fieldLabel: 'excel文件',
+                                    buttonText: '请选择excel文件',
+                                    accept: ".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel",
+                                    flex: 3,
+                                    listeners: {
+                                        change: "scope.fileChange"
+                                    }
+                                }, {
+                                    xtype: "button",
+                                    iconCls: "x-fa fa-download",
+                                    text: "下载模版",
+                                    listeners: {
+                                        click: "scope.downloadTemplate"
+                                    }
+                                }, {
+                                    xtype: 'container',
+                                    border: false,
+                                    layout: 'hbox',
+                                }, {
+                                    xtype: "button",
+                                    iconCls: "x-fa fa-upload",
+                                    text: "提取数据",
+                                    listeners: {
+                                        click: "scope.getData"
+                                    }
+                                }
+                            ]
+                        },
+                    ]
+                },
+                items: [
+                    {
+                        tbar: {
+                            xtype: "toolbar",
+                            title: "导入的数据",
+                            items: [{
+                                xtype: 'label',
+                                html: '<span style="font-size: 14px; font-weight: bold">导入的数据</span>',
+                            }, {
+                                xtype: 'radiogroup',
+                                items: [
+                                    {
+                                        boxLabel: '所有数据',
+                                        checked: true,
+                                        inputValue: 'allData',
+                                    }, {
+                                        boxLabel: '正确数据',
+                                        inputValue: 'okData',
+                                    }, {
+                                        boxLabel: '错误数据',
+                                        inputValue: 'errorData',
+                                    }],
+                                listeners: {
+                                    change: 'scope.filterData',
+                                },
+                            }, {
+                                xtype: "textfield",
+                                readOnly: true,
+                                fieldLabel: "条目数",
+                                bind: "{dataCount}"
+                            }]
+                        },
+                        xtype: 'yvgrid',
+                        reference: 'dataGrid',
+                        layout: 'fit',
+                        pagination: false,
+                        // pageSize: 50,
+                        getRowClass: 'scope.getDataGridRowRecord',
+                        columns: option.columns,
+                        region: 'center',
+                        split: true,
+                        listeners: {
+                            cellclick: "scope.dataGridClick",
+                        },
+                        selModel: {
+                            // type: 'rowmodel',
+                            mode: 'SINGLE',
+                        },
+                    }, {
+                        tbar: {
+                            xtype: "toolbar", items: [
+                                {
+                                    xtype: 'label',
+                                    html: '<span style="font-size: 14px; font-weight: bold; color: red">错误信息</span>',
+                                }, {
+                                    xtype: "textfield",
+                                    readOnly: true,
+                                    fieldLabel: "条目数",
+                                    bind: "{errMsgCount}"
+                                }
+                            ]
+                        },
+                        xtype: 'yvgrid',
+                        reference: 'errGrid',
+                        layout: 'fit',
+                        pagination: false,
+                        getRowClass: 'scope.getErrGridRowRecord',
+                        height: "40%",
+                        columns: [
+                            {dataIndex: "errorId", header: "ID", hidden: true},
+                            {dataIndex: "importID", header: "行号", width: 80},
+                            {dataIndex: "errormessage", header: "错误信息", width: 200},
+                            {dataIndex: "value", header: "值", width: 120},
+                            {dataIndex: "header", header: "字段名", width: 120},
+                            {dataIndex: "dataIndex", header: "字段", width: 120}
+                        ],
+                        region: 'south',
+                        split: true,
+                        listeners: {
+                            itemclick: "scope.errGridClick"
+                        },
+                        selModel: {
+                            // type: 'rowmodel',
+                            mode: 'SINGLE',
+                        },
+                    },
+                ],
+            },
+        ],
+        referenceHolder: true,
+    }
+}

+ 1 - 0
src/index.ts

@@ -29,3 +29,4 @@ export * from './lib/ajax'
 export * from './lib/lib'
 export * from './lib/config'
 export * from './lib/systemLib'
+export * from './ExcelImportDialog'

+ 9 - 0
src/lib/systemLib.ts

@@ -2,6 +2,7 @@ import _ from 'lodash'
 import {Lib, lookupScope, LibParam} from './lib'
 import {ajax} from "./config";
 import {msg as msgSimple, showErrorDialog as showErrorDialogSimple} from "../message";
+import {ImportResult} from "../xlsx";
 
 export const SIMPLE_RE = /^(?:\{(?:(\d+)|([a-z_][\w\.]*))\})$/i
 
@@ -371,6 +372,14 @@ export function tryEnable(data, enableSetting) {
  * @param multiValueSeparator 多个字典值的分割符号
  */
 export function setComboStore(sender, config, getDictFn, bizKey, multiValueSeparator = "") {
+    // sender 和 config为空的时候获取值、
+    if (!sender) {
+        return new Promise((resolve, reject) => {
+            getDictFn(bizKey, (r) => {
+                resolve({config, r})
+            })
+        })
+    }
     if (sender.xtype === 'combotree') {
         getDictFn(bizKey, (r) => {
             if (sender.store) {

+ 458 - 390
src/xlsx.ts

@@ -1,390 +1,458 @@
-// import XLSX from 'xlsx'
-// import _ from 'lodash'
-// // import ExcelImportDialog from "./renderers/excelImportDialog";
-// import * as path from "path";
-// // import {clearLoading, loading} from "./renderers";
-// // @ts-ignore
-// // import XLSX_EXPORT from 'xlsx_export';
-// // @ts-ignore
-// const XLSX_EXPORT = window.LAY_EXCEL
-//
-// export {XLSX};
-// export {XLSX_EXPORT}
-//
-// export function readExcel(file: File): Promise<any> {
-//     return new Promise((resolve, reject) => {
-//         var reader = new FileReader();
-//         reader.onload = function (e) {
-//             if (e.target) {
-//                 const data = e.target.result;
-//                 const workbook = XLSX.read(data, {type: 'binary'});
-//                 resolve(workbook);
-//             } else {
-//                 reject("文件读取失败!!!")
-//             }
-//         };
-//         try {
-//             reader.readAsBinaryString(file);
-//         } catch (e) {
-//             reject(e)
-//         }
-//     })
-// }
-//
-// export declare interface ErrorMsgDataItem {
-//     /** 错误id 由allData 的__importID__字段值 + "_" + field的值 表示哪一行的哪一列出错,行校验错误时候直接为allData 的__importID__ + "" */
-//     errorId: string
-//     /** 对应数据行 allData 的__importID__字段值 */
-//     importID: number
-//     /** 字段 */
-//     field: string
-//     /** 字段值 */
-//     title: string
-//     /** 字段值 */
-//     value: string
-//     /** 错误信息 */
-//     errormessage: string
-// }
-//
-// export declare interface ImportResult {
-//     /** 所有导入的数据,如果有字典会格式化到字典,格式化错误的保持原始值 */
-//     allData: any[]
-//     /** 导入正确的数据 */
-//     okData: any[]
-//     /** 导入错误的数据 */
-//     errorData: any[]
-//     /** 导入错误数据的错误明细 */
-//     errorMsgData: ErrorMsgDataItem[]
-// }
-//
-// /** 定义参数列 */
-// export declare interface Field {
-//     /** 字段 */
-//     dataIndex: string
-//     /** 字段名 */
-//     header: string
-//     /** 校验方法,校验通过返回true, 否则返回错误信息 会记录到错误列表里面 errorMsgData */
-//     validate?: ((v: ValidateObject) => true | string) | string
-//     /** 格式化,表格显示 兼容默认弹出框表格的formatter */
-//     fix?: ((v: any) => any) | string
-//     /** 导入格式化 返回null或者undefined 表示格式化错误,会记录到错误列表里面 errorMsgData */
-//     importFormatter?: ((v: ValidateObject) => null | undefined | string | number) | string
-//     /** 字典, 参与数据校验和表格显示格式化, 校验不通过的,会记录到错误列表里面 errorMsgData 同时兼容默认弹出框表格的字典 */
-//     data?: any | { id: string | number, text: string }
-// }
-//
-// /** 格式化及校验的参数 */
-// export declare interface ValidateObject {
-//     field: Field
-//     ov: any,
-//     nv: any,
-//     rowIndex: number,
-//     data: any,
-//     rowDatas: any[],
-// }
-//
-// /** 行数据校验的参数 */
-// export declare interface RowValidateObject {
-//     fields: Field[],
-//     data: any,
-//     rowIndex: number,
-//     rowDatas: any[]
-// }
-//
-// /**
-//  * 定义切口,可以在外部修改数据
-//  */
-// export declare type AfterClientValidate =
-//     ((importResult: ImportResult, resolve: (value?: (ImportResult | PromiseLike<ImportResult> | undefined)) => void) => ImportResult)
-//     | undefined
-//
-// export function readExcelWithFieldSet(file: File,
-//                                       fieldSet: any[],
-//                                       dataStartRow: number = 2,
-//                                       titleRowNumber: number = 1,
-//                                       rowValidate: ((rv: RowValidateObject) => true | string) | undefined = undefined,
-//                                       otherValidate: AfterClientValidate = undefined,
-//                                       fieldValidate: ((fields: Field[], columnTitles: string[]) => boolean) | undefined = undefined): Promise<ImportResult> {
-//     return new Promise<ImportResult>((resolve, reject) => {
-//         setTimeout(()=>{
-//             readExcel(file).then(workbook => {
-//                 const sheetNames = workbook.SheetNames; // 工作表名称集合
-//                 const worksheet = workbook.Sheets[sheetNames[0]]; // 这里我们只读取第一张sheet
-//                 const titleRowValue: any[] = [];
-//                 const titleRowKey: any[] = [];
-//                 let needLoop: boolean = false; // 是否需要迭代
-//                 let fields: any[] = [];
-//                 if (worksheet && worksheet["!ref"]) {
-//                     const t = worksheet["!ref"];
-//                     const tempArr = t.split(':');
-//                     if (tempArr.length >= 2) {
-//                         let firstRowNumber = tempArr[0].replace(/[^0-9]/ig, "");
-//
-//                         // 选取title的行,删除之前的行
-//                         if (titleRowNumber > firstRowNumber) {
-//                             for (let i = firstRowNumber; i < titleRowNumber; i++) {
-//                                 for (const key in worksheet) {
-//                                     if (key.endsWith(i) && key.replace(/[^0-9]/ig, "") === i) {
-//                                         delete worksheet[key];
-//                                     }
-//                                 }
-//                             }
-//                             const fc = tempArr[0].replace(/[^a-z]/ig, "");
-//                             firstRowNumber = titleRowNumber + '';
-//                             worksheet["!ref"] = fc+firstRowNumber + ":" + tempArr[1]
-//                         }
-//                         const lastRowNumber = tempArr[1].replace(/[^0-9]/ig, "");
-//                         // const firstColNumber = tempArr[0].split(''+firstRowNumber)[0];
-//                         // const lastColNumber = tempArr[1].split(''+lastRowNumber)[0];
-//                         for (const key in worksheet) {
-//                             if (key.endsWith(firstRowNumber) && key.replace(/[^0-9]/ig, "") === firstRowNumber) {
-//                                 titleRowKey.push(key)
-//                                 titleRowValue.push(worksheet[key].v)
-//                             }
-//                         }
-//                         if (fieldSet && fieldSet.length > 0) {
-//                             let length = fieldSet.length;
-//                             length = length <= titleRowKey.length ? length : titleRowKey.length;
-//                             for (let i = 0; i < length; i++) {
-//                                 const vk = titleRowKey[i];
-//                                 const item = fieldSet[i];
-//                                 const field: any = {}
-//                                 if (typeof item === 'string') {
-//                                     field.field = item;
-//                                     field.title = worksheet[vk].v;
-//                                     worksheet[vk] = {t: "s", v: item, h: item, w: item}
-//                                 } else if (typeof item.field === "string") {
-//                                     field.field = item.field;
-//                                     field.title = worksheet[vk].v;
-//                                     worksheet[vk] = {t: "s", v: item.field, h: item.field, w: item.field};
-//                                     if (typeof item.validate === "function") {
-//                                         field.validate = item.validate
-//                                         needLoop = true;
-//                                     }
-//                                     if (typeof item.importFormatter === "function") {
-//                                         field.importFormatter = item.importFormatter
-//                                         needLoop = true;
-//                                     }
-//                                     if ((item.data instanceof Array && item.data.length > 0)
-//                                         || _.isPlainObject(item.data) && Object.keys(item.data).length > 0) {
-//                                         field.data = item.data
-//                                         needLoop = true;
-//                                     }
-//                                 }
-//                                 fields.push(field)
-//                             }
-//                         }
-//                     }
-//                 }
-//
-//                 if (fieldValidate && typeof fieldValidate === "function") {
-//                     if (fieldValidate(fields, titleRowValue) !== true) {
-//                         reject("fields validate error");
-//                         return
-//                     }
-//                 }
-//
-//                 const allData = XLSX.utils.sheet_to_json(worksheet);
-//                 let okData: any[] = [], errorData: any[] = [], errorMsgData: ErrorMsgDataItem[] = [];
-//                 const needItemLoop = needLoop;
-//                 if (rowValidate && typeof rowValidate === "function") {
-//                     needLoop = true;
-//                 }
-//                 if (needLoop === true) {
-//                     for (let index = 0; index < allData.length; index++) {
-//                         const row: any = allData[index];
-//                         const rowNumber = dataStartRow + index;
-//                         row.__importID__ = rowNumber
-//                         let isRowOk: boolean = true;
-//                         if (needItemLoop) {
-//                             for (let num = 0; num < fields.length; num++) {
-//                                 const field = fields[num];
-//                                 const ov = row[field.field];
-//                                 let nv: any = ov;
-//
-//                                 let hasError: boolean = false;
-//                                 const rowNumber = dataStartRow + index;
-//                                 const ei: ErrorMsgDataItem = {
-//                                     errorId: rowNumber + '_' + field.field,
-//                                     importID: rowNumber,
-//                                     field: field.field,
-//                                     title: field.title,
-//                                     value: ov,
-//                                     errormessage: ""
-//                                 }
-//
-//                                 // 格式化
-//                                 if (field.data instanceof Array) {
-//                                     nv = undefined;
-//                                     ei.errormessage = "字典匹配失败"
-//                                     _.forEach(field.data, (v, index) => {
-//                                         if (v.text === ov) {
-//                                             nv = v.id;
-//                                             return
-//                                         }
-//                                     })
-//                                 } else if (_.isPlainObject(field.data)) {
-//                                     nv = undefined;
-//                                     ei.errormessage = "字典匹配失败"
-//                                     _.forEach(field.data, (value, key) => {
-//                                         if (value === ov) {
-//                                             nv = key;
-//                                             return
-//                                         }
-//                                     })
-//                                 }
-//
-//                                 if (field.importFormatter) {
-//                                     const vdata: ValidateObject = {
-//                                         field,
-//                                         ov,
-//                                         nv,
-//                                         rowIndex: index,
-//                                         data: row,
-//                                         rowDatas: allData
-//                                     }
-//                                     nv = undefined;
-//                                     ei.errormessage = "格式化失败";
-//                                     nv = field.importFormatter(vdata)
-//                                 }
-//
-//                                 if (nv === undefined || nv === null) {
-//                                     hasError = true;
-//                                 } else {
-//                                     row[field.field] = nv;
-//                                     ei.errormessage = "";
-//                                 }
-//
-//                                 // 校验
-//                                 if (field.validate) {
-//                                     const vdata: ValidateObject = {
-//                                         field,
-//                                         ov,
-//                                         nv,
-//                                         rowIndex: index,
-//                                         data: row,
-//                                         rowDatas: allData
-//                                     }
-//                                     const errormessage: true | string = field.validate(vdata);
-//                                     if (errormessage === true) {
-//
-//                                     } else {
-//                                         hasError = true;
-//                                         ei.errormessage = ei.errormessage + "/" + errormessage;
-//                                     }
-//                                 }
-//                                 if (hasError === true) {
-//                                     isRowOk = false;
-//                                     errorMsgData.push(ei);
-//                                 }
-//                             }
-//                         }
-//                         if (rowValidate && typeof rowValidate === "function") {
-//                             const errormessage: true | string = rowValidate({
-//                                 fields,
-//                                 data: row,
-//                                 rowIndex: index,
-//                                 rowDatas: allData
-//                             })
-//                             if (errormessage === true) {
-//
-//                             } else {
-//                                 isRowOk = false;
-//
-//                                 const ei: ErrorMsgDataItem = {
-//                                     errorId: rowNumber + '',
-//                                     field: "row",
-//                                     title: "数据行",
-//                                     value: "当前行的数据",
-//                                     importID: rowNumber,
-//                                     errormessage
-//                                 }
-//                                 errorMsgData.push(ei);
-//                             }
-//                         }
-//                         row.__hasError__ = !isRowOk
-//                         if (isRowOk === true) {
-//                             okData.push(row)
-//                         } else {
-//                             errorData.push(row)
-//                         }
-//                     }
-//
-//                 } else {
-//                     // 添加数据的唯一标识
-//                     for (let index = 0; index < allData.length; index++) {
-//                         const row: any = allData[index];
-//                         const rowNumber = dataStartRow + index;
-//                         row.__importID__ = rowNumber;
-//                         row.__hasError__ = false
-//                     }
-//                     okData = allData;
-//                 }
-//                 if (otherValidate && typeof otherValidate === "function") {
-//                     otherValidate({allData, okData, errorData, errorMsgData}, resolve);
-//                 } else {
-//                     resolve({allData, okData, errorData, errorMsgData});
-//                 }
-//                 // clearLoading()
-//             })
-//         }, 50);
-//     })
-// }
-//
-// export declare interface ImportExcelOption {
-//     // 字段设置
-//     fields: Field[],
-//     // 字段校验方法
-//     fieldValidate?: ((fields: Field[], columnTitles: string[]) => boolean) | undefined,
-//     // 数据行校验方法
-//     rowValidate?: ((rv: RowValidateObject) => true | string) | undefined,
-//     // 客户端校验完成后调用
-//     afterClientValidate?: AfterClientValidate,
-//     // Excel起始数据行的的行号 默认给定为2
-//     dataStartRow?: number,
-//     // Excel标题行的的行号 默认给定为1
-//     titleRowNumber?: number,
-//     // 导入完成后调用 回传结果数据
-//     onImportAfter?: (result: ImportResult) => {}
-//     // 隐藏所有数据表格, 默认false
-//     hidAllDataGrid?: boolean,
-//     // 隐藏正确数据表格 默认 false
-//     hidOKDataGrid?: boolean,
-//     // 隐藏错误数据的表格 默认false
-//     hidErrorDataGrid?: boolean,
-//     // 隐藏错误信息表格 默认false
-//     hidErrorMsgGrid?: boolean,
-//     // 所有数据表格的分页大小, 0 为不分页
-//     dataGridPageSize?: 0 | 10 | 20 | 50 | 100 | 200 | 500 | 1000,
-//     // 错误信息表格的分页大小, 0 为不分页
-//     errorMsgGridPageSize?: 0 | 10 | 20 | 50 | 100 | 200 | 500 | 1000,
-//     // 导入模版的名称
-//     templateName?: string,
-//     // 导入模板下载地址
-//     dowLoadUrl?: string,
-//     // 标题
-//     title?: string,
-//     // 弹窗高
-//     height?: string,
-//     // 弹窗宽
-//     width?: string,
-//     // 默认显示的表格
-//     defaultShowData?: "allData" | "okData" | "errorData",
-//     // 自定义错误信息表格的列宽
-//     errMsgGridColWidths?: number[],
-//     // 外部定义的控件
-//     toolBar?: any[],
-//     // 关闭回调
-//     onClose?: string | (() => void)
-// }
-//
-// /**
-//  * 打开excel导入的方法
-//  * @param option 参数配置
-//  * @param sender
-//  */
-// export function importExcel(option: ImportExcelOption, sender: any): ExcelImportDialog {
-//     const dialog = new ExcelImportDialog({option})
-//     dialog.showDialog(sender);
-//     return dialog
-// }
+import XLSX from 'xlsx'
+import _ from 'lodash'
+import {ExcelImportDialog} from "./ExcelImportDialog";
+import * as path from "path";
+import {lookupFn} from "./lib/lib";
+import {Scope} from "./Scope";
+// import {clearLoading, loading} from "./renderers";
+// @ts-ignore
+// import XLSX_EXPORT from 'xlsx_export';
+// @ts-ignore
+const XLSX_EXPORT = window.LAY_EXCEL
+
+export {XLSX};
+export {XLSX_EXPORT}
+
+export function readExcel(file: File): Promise<any> {
+    return new Promise((resolve, reject) => {
+        if (!file) {
+            reject("文件读取失败!!!")
+            return
+        }
+        var reader = new FileReader();
+        reader.onload = function (e) {
+            if (e.target) {
+                const data = e.target.result;
+                const workbook = XLSX.read(data, {type: 'binary'});
+                resolve(workbook);
+            } else {
+                reject("文件读取失败!!!")
+            }
+        };
+        try {
+            reader.readAsBinaryString(file);
+        } catch (e) {
+            reject(e)
+        }
+    })
+}
+
+export declare interface ErrorMsgDataItem {
+    /** 错误id 由allData 的__importID__字段值 + "_" + field的值 表示哪一行的哪一列出错,行校验错误时候直接为allData 的__importID__ + "" */
+    errorId: string
+    /** 对应数据行 allData 的__importID__字段值 */
+    importID: number
+    /** 字段 */
+    dataIndex: string
+    /** 字段值 */
+    header: string
+    /** 字段值 */
+    value: string
+    /** 错误信息 */
+    errormessage: string
+}
+
+export declare interface ImportResult {
+    /** 所有导入的数据,如果有字典会格式化到字典,格式化错误的保持原始值 */
+    allData: any[]
+    /** 导入正确的数据 */
+    okData: any[]
+    /** 导入错误的数据 */
+    errorData: any[]
+    /** 导入错误数据的错误明细 */
+    errorMsgData: ErrorMsgDataItem[]
+}
+
+/** 定义参数列 */
+export declare interface Column {
+    /** 字段 */
+    dataIndex: string
+    /** 字段名 */
+    header: string
+    /** 校验方法,校验通过返回true, 否则返回错误信息 会记录到错误列表里面 errorMsgData */
+    validate?: ((v: ValidateObject) => true | string) | string
+    /** 格式化,表格显示 兼容默认弹出框表格的formatter */
+    fix?: ((v: any) => any) | string
+    /** 导入格式化 返回null或者undefined 表示格式化错误,会记录到错误列表里面 errorMsgData */
+    importFormatter?: ((v: ValidateObject) => null | undefined | string | number) | string
+    /** 字典, 参与数据校验和表格显示格式化, 校验不通过的,会记录到错误列表里面 errorMsgData 同时兼容默认弹出框表格的字典 */
+    data?: any | { id: string | number, text: string }
+}
+
+/** 格式化及校验的参数 */
+export declare interface ValidateObject {
+    column: Column
+    ov: any,
+    nv: any,
+    rowIndex: number,
+    data: any,
+    rowDatas: any[],
+}
+
+/** 行数据校验的参数 */
+export declare interface RowValidateObject {
+    columns: Column[],
+    data: any,
+    rowIndex: number,
+    rowDatas: any[]
+}
+
+/**
+ * 定义切口,可以在外部修改数据
+ */
+export declare type AfterClientValidate =
+    ((importResult: ImportResult, resolve: (value?: (ImportResult | PromiseLike<ImportResult> | undefined)) => void) => ImportResult)
+    | undefined
+
+export function readExcelWithColumnsSet(topScope: Scope,
+                                      file: File,
+                                      columnsSet: any[],
+                                      dataStartRow: number = 2,
+                                      titleRowNumber: number = 1,
+                                      rowValidate: ((rv: RowValidateObject) => true | string) | undefined = undefined,
+                                      otherValidate: AfterClientValidate = undefined,
+                                      fieldValidate: ((columns: Column[], columnTitles: string[]) => boolean) | undefined = undefined): Promise<ImportResult> {
+    return new Promise<ImportResult>((resolve, reject) => {
+
+        const promiseArr = []
+        for (let i = 0; i < columnsSet.length; i++) {
+            const fix = columnsSet[i].fix;
+            if (_.isArray(fix) && fix.length > 0) {
+                const ss = fix[0].toLowerCase()
+                if (ss.indexOf("date") > -1 || ss.indexOf("time") > -1) {
+                    columnsSet[i].importFormatter = function (vdata: ValidateObject) {
+                        // @ts-ignore
+                        return window.moment(vdata.ov)-0
+                    }
+                } else {
+                    const fn = lookupFn(topScope, fix[0])
+                    promiseArr.push(fn(null, columnsSet[i]))
+                    delete columnsSet[i].fix
+                }
+
+            } else if (_.isString(fix)) {
+                const ss = fix.toLowerCase()
+                if (ss.indexOf("date") > -1 || ss.indexOf("time") > -1) {
+                    columnsSet[i].importFormatter = function (vdata: ValidateObject) {
+                        // @ts-ignore
+                        return window.moment(vdata.ov)-0
+                    }
+                } else {
+                    const fn = lookupFn(topScope, fix)
+                    promiseArr.push(fn(null, columnsSet[i]))
+                    delete columnsSet[i].fix
+                }
+            }
+
+        }
+
+        const read = ()=>{
+            readExcel(file).then(workbook => {
+                const sheetNames = workbook.SheetNames; // 工作表名称集合
+                const worksheet = workbook.Sheets[sheetNames[0]]; // 这里我们只读取第一张sheet
+                const titleRowValue: any[] = [];
+                const titleRowKey: any[] = [];
+                let needLoop: boolean = false; // 是否需要迭代
+                let columns: Column[] = [];
+                if (worksheet && worksheet["!ref"]) {
+                    const t = worksheet["!ref"];
+                    const tempArr = t.split(':');
+                    if (tempArr.length >= 2) {
+                        let firstRowNumber = tempArr[0].replace(/[^0-9]/ig, "");
+
+                        // 选取title的行,删除之前的行
+                        if (titleRowNumber > firstRowNumber) {
+                            for (let i = firstRowNumber; i < titleRowNumber; i++) {
+                                for (const key in worksheet) {
+                                    if (key.endsWith(i) && key.replace(/[^0-9]/ig, "") === i) {
+                                        delete worksheet[key];
+                                    }
+                                }
+                            }
+                            const fc = tempArr[0].replace(/[^a-z]/ig, "");
+                            firstRowNumber = titleRowNumber + '';
+                            worksheet["!ref"] = fc+firstRowNumber + ":" + tempArr[1]
+                        }
+                        const lastRowNumber = tempArr[1].replace(/[^0-9]/ig, "");
+                        // const firstColNumber = tempArr[0].split(''+firstRowNumber)[0];
+                        // const lastColNumber = tempArr[1].split(''+lastRowNumber)[0];
+                        for (const key in worksheet) {
+                            if (key.endsWith(firstRowNumber) && key.replace(/[^0-9]/ig, "") === firstRowNumber) {
+                                titleRowKey.push(key)
+                                titleRowValue.push(worksheet[key].v)
+                            }
+                        }
+                        if (columnsSet && columnsSet.length > 0) {
+                            let length = columnsSet.length;
+                            length = length <= titleRowKey.length ? length : titleRowKey.length;
+                            for (let i = 0; i < length; i++) {
+                                const vk = titleRowKey[i];
+                                const item = columnsSet[i];
+                                const column: any = {}
+                                if (typeof item === 'string') {
+                                    column.dataIndex = item;
+                                    column.header = worksheet[vk].v;
+                                    worksheet[vk] = {t: "s", v: item, h: item, w: item}
+                                } else if (typeof item.dataIndex === "string") {
+                                    column.dataIndex = item.dataIndex;
+                                    column.header = worksheet[vk].v;
+                                    worksheet[vk] = {t: "s", v: item.dataIndex, h: item.dataIndex, w: item.dataIndex};
+                                    if (typeof item.validate === "function") {
+                                        column.validate = item.validate
+                                        needLoop = true;
+                                    }
+                                    if (typeof item.importFormatter === "function") {
+                                        column.importFormatter = item.importFormatter
+                                        needLoop = true;
+                                    }
+                                    if ((item.data instanceof Array && item.data.length > 0)
+                                        || _.isPlainObject(item.data) && Object.keys(item.data).length > 0) {
+                                        column.data = item.data
+                                        needLoop = true;
+                                    }
+                                }
+                                columns.push(column)
+                            }
+                        }
+                    }
+                }
+
+                if (fieldValidate && typeof fieldValidate === "function") {
+                    if (fieldValidate(columns, titleRowValue) !== true) {
+                        reject("fields validate error");
+                        return
+                    }
+                }
+
+                const allData = XLSX.utils.sheet_to_json(worksheet);
+                let okData: any[] = [], errorData: any[] = [], errorMsgData: ErrorMsgDataItem[] = [];
+                const needItemLoop = needLoop;
+                if (rowValidate && typeof rowValidate === "function") {
+                    needLoop = true;
+                }
+                if (needLoop === true) {
+                    for (let index = 0; index < allData.length; index++) {
+                        const row: any = allData[index];
+                        const rowNumber = dataStartRow + index;
+                        row.__importID__ = rowNumber
+                        let isRowOk: boolean = true;
+                        if (needItemLoop) {
+                            for (let num = 0; num < columns.length; num++) {
+                                const column = columns[num];
+                                const ov = row[column.dataIndex];
+                                let nv: any = ov;
+
+                                let hasError: boolean = false;
+                                const rowNumber = dataStartRow + index;
+                                const ei: ErrorMsgDataItem = {
+                                    errorId: rowNumber + '_' + column.dataIndex,
+                                    importID: rowNumber,
+                                    dataIndex: column.dataIndex,
+                                    header: column.header,
+                                    value: ov,
+                                    errormessage: ""
+                                }
+
+                                // 格式化
+                                if (column.data instanceof Array) {
+                                    nv = undefined;
+                                    ei.errormessage = "字典匹配失败"
+                                    _.forEach(column.data, (v, index) => {
+                                        if (v.text === ov) {
+                                            nv = v.id;
+                                            return
+                                        }
+                                    })
+                                } else if (_.isPlainObject(column.data)) {
+                                    nv = undefined;
+                                    ei.errormessage = "字典匹配失败"
+                                    _.forEach(column.data, (value, key) => {
+                                        if (value === ov) {
+                                            nv = key;
+                                            return
+                                        }
+                                    })
+                                }
+
+                                if ( typeof column.importFormatter === 'function') {
+                                    const vdata: ValidateObject = {
+                                        column,
+                                        ov,
+                                        nv,
+                                        rowIndex: index,
+                                        data: row,
+                                        rowDatas: allData
+                                    }
+                                    nv = undefined;
+                                    ei.errormessage = "格式化失败";
+                                    nv = column.importFormatter(vdata)
+                                }
+
+                                if (nv === undefined || nv === null || isNaN(nv)) {
+                                    hasError = true;
+                                } else {
+                                    row[column.dataIndex] = nv;
+                                    ei.errormessage = "";
+                                }
+
+                                // 校验
+                                if (typeof column.validate === 'function') {
+                                    const vdata: ValidateObject = {
+                                        column,
+                                        ov,
+                                        nv,
+                                        rowIndex: index,
+                                        data: row,
+                                        rowDatas: allData
+                                    }
+                                    const errormessage: true | string = column.validate(vdata);
+                                    if (errormessage === true) {
+
+                                    } else {
+                                        hasError = true;
+                                        ei.errormessage = ei.errormessage + "/" + errormessage;
+                                    }
+                                }
+                                if (hasError === true) {
+                                    isRowOk = false;
+                                    errorMsgData.push(ei);
+                                }
+                            }
+                        }
+                        if (rowValidate && typeof rowValidate === "function") {
+                            const errormessage: true | string = rowValidate({
+                                columns,
+                                data: row,
+                                rowIndex: index,
+                                rowDatas: allData
+                            })
+                            if (errormessage === true) {
+
+                            } else {
+                                isRowOk = false;
+
+                                const ei: ErrorMsgDataItem = {
+                                    errorId: rowNumber + '',
+                                    dataIndex: "row",
+                                    header: "数据行",
+                                    value: "当前行的数据",
+                                    importID: rowNumber,
+                                    errormessage
+                                }
+                                errorMsgData.push(ei);
+                            }
+                        }
+                        row.__hasError__ = !isRowOk
+                        if (isRowOk === true) {
+                            okData.push(row)
+                        } else {
+                            errorData.push(row)
+                        }
+                    }
+
+                } else {
+                    // 添加数据的唯一标识
+                    for (let index = 0; index < allData.length; index++) {
+                        const row: any = allData[index];
+                        const rowNumber = dataStartRow + index;
+                        row.__importID__ = rowNumber;
+                        row.__hasError__ = false
+                    }
+                    okData = allData;
+                }
+                if (otherValidate && typeof otherValidate === "function") {
+                    otherValidate({allData, okData, errorData, errorMsgData}, resolve);
+                } else {
+                    resolve({allData, okData, errorData, errorMsgData});
+                }
+                // clearLoading()
+            }).catch(e=>{
+                reject(e)
+            })
+        }
+
+        if (promiseArr.length > 0) {
+            Promise.all(promiseArr).then(resArr=>{
+                for (let i = 0; i < resArr.length; i++) {
+                    const tmp = resArr[i]
+                    tmp.config.data = []
+                    if (tmp && tmp.r && Array.isArray(tmp.r.field) && Array.isArray(tmp.r.data)) {
+                        if (tmp.r.field.length > 1) {
+                            const keyKey = tmp.r.field[0]
+                            const valueKey = tmp.r.field[1]
+                            for (let j = 0; j < tmp.r.data.length; j++) {
+                                const it = tmp.r.data[j]
+                                const newIt: any = {}
+                                newIt.id = it[keyKey]
+                                newIt.text = it[valueKey]
+                                tmp.config.data.push(newIt)
+                            }
+                        }
+                    }
+                }
+                read()
+            })
+        } else {
+            setTimeout(read, 50);
+        }
+
+    })
+}
+
+export declare interface ImportExcelOption {
+    // 字段设置
+    columns: Column[],
+    // 字段校验方法
+    fieldValidate?: ((columns: Column[], columnTitles: string[]) => boolean) | string | undefined,
+    // 数据行校验方法
+    rowValidate?: ((rv: RowValidateObject) => true | string) | string |undefined,
+    // 客户端校验完成后调用
+    afterClientValidate?: AfterClientValidate | string,
+    // Excel起始数据行的的行号 默认给定为2
+    dataStartRow?: number,
+    // Excel标题行的的行号 默认给定为1
+    titleRowNumber?: number,
+    // 导入完成后调用 回传结果数据
+    onImportAfter?: ((result: ImportResult) => {}) | string | undefined
+    // 隐藏所有数据表格, 默认false
+    hidAllDataGrid?: boolean,
+    // 隐藏正确数据表格 默认 false
+    hidOKDataGrid?: boolean,
+    // 隐藏错误数据的表格 默认false
+    hidErrorDataGrid?: boolean,
+    // 隐藏错误信息表格 默认false
+    hidErrorMsgGrid?: boolean,
+    // 所有数据表格的分页大小, 0 为不分页
+    dataGridPageSize?: 0 | 10 | 20 | 50 | 100 | 200 | 500 | 1000,
+    // 错误信息表格的分页大小, 0 为不分页
+    errorMsgGridPageSize?: 0 | 10 | 20 | 50 | 100 | 200 | 500 | 1000,
+    // 导入模版的名称
+    templateName?: string,
+    // 导入模板下载地址
+    dowLoadUrl?: string,
+    // 标题
+    title?: string,
+    // 弹窗高
+    height?: string,
+    // 弹窗宽
+    width?: string,
+    // 默认显示的表格
+    defaultShowData?: "allData" | "okData" | "errorData",
+    // 自定义错误信息表格的列宽
+    errMsgGridColWidths?: number[],
+    // 外部定义的控件
+    toolBar?: any[],
+    // 关闭回调
+    onClose?: string | (() => void)
+}
+
+/**
+ * 打开excel导入的方法
+ * @param option 参数配置
+ * @param sender
+ */
+export function importExcel(option: ImportExcelOption, sender: any): ExcelImportDialog {
+    const dialog = new ExcelImportDialog(option)
+    dialog.showDialog(sender,{}, {});
+    return dialog
+}

+ 92 - 0
yarn.lock

@@ -211,6 +211,21 @@ acorn@^7.1.0:
   resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
   integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
+adler-32@~1.2.0:
+  version "1.2.0"
+  resolved "https://registry.nlark.com/adler-32/download/adler-32-1.2.0.tgz#6a3e6bf0a63900ba15652808cb15c6813d1a5f25"
+  integrity sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=
+  dependencies:
+    exit-on-epipe "~1.0.1"
+    printj "~1.1.0"
+
+adler-32@~1.3.0:
+  version "1.3.0"
+  resolved "https://registry.nlark.com/adler-32/download/adler-32-1.3.0.tgz#3cad1b71cdfa69f6c8a91f3e3615d31a4fdedc72"
+  integrity sha1-PK0bcc36afbIqR8+NhXTGk/e3HI=
+  dependencies:
+    printj "~1.2.2"
+
 agent-base@4, agent-base@^4.3.0:
   version "4.3.0"
   resolved "https://registry.npm.taobao.org/agent-base/download/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
@@ -738,6 +753,15 @@ caseless@~0.12.0:
   resolved "https://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
   integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
 
+cfb@^1.1.4:
+  version "1.2.1"
+  resolved "https://registry.nlark.com/cfb/download/cfb-1.2.1.tgz#209429e4c68efd30641f6fc74b2d6028bd202402"
+  integrity sha1-IJQp5MaO/TBkH2/HSy1gKL0gJAI=
+  dependencies:
+    adler-32 "~1.3.0"
+    crc-32 "~1.2.0"
+    printj "~1.3.0"
+
 chalk@^1.1.3:
   version "1.1.3"
   resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -874,6 +898,11 @@ code-point-at@^1.0.0:
   resolved "https://registry.npmmirror.com/code-point-at/download/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
   integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
 
+codepage@~1.15.0:
+  version "1.15.0"
+  resolved "https://registry.nlark.com/codepage/download/codepage-1.15.0.tgz#2e00519024b39424ec66eeb3ec07227e692618ab"
+  integrity sha1-LgBRkCSzlCTsZu6z7AcifmkmGKs=
+
 color-convert@^1.3.0, color-convert@^1.8.2, color-convert@^1.9.0, color-convert@^1.9.1:
   version "1.9.3"
   resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -1065,6 +1094,14 @@ cosmiconfig@^5.0.0:
     js-yaml "^3.13.1"
     parse-json "^4.0.0"
 
+crc-32@~1.2.0:
+  version "1.2.0"
+  resolved "https://registry.nlark.com/crc-32/download/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208"
+  integrity sha1-yy224puIUI4y2d0OwWk+e0Ghggg=
+  dependencies:
+    exit-on-epipe "~1.0.1"
+    printj "~1.1.0"
+
 create-ecdh@^4.0.0:
   version "4.0.4"
   resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
@@ -1742,6 +1779,11 @@ execa@^0.7.0:
     signal-exit "^3.0.0"
     strip-eof "^1.0.0"
 
+exit-on-epipe@~1.0.1:
+  version "1.0.1"
+  resolved "https://registry.nlark.com/exit-on-epipe/download/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692"
+  integrity sha1-C92S6H1ShdJn2qgXHQ6wYVlolpI=
+
 extend@~3.0.2:
   version "3.0.2"
   resolved "https://registry.nlark.com/extend/download/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
@@ -1860,6 +1902,11 @@ form-data@~2.3.2:
     combined-stream "^1.0.6"
     mime-types "^2.1.12"
 
+frac@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.nlark.com/frac/download/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b"
+  integrity sha1-PXT39keMiKG1AgMG10fcYxPHTQs=
+
 from2@^1.3.0:
   version "1.3.0"
   resolved "https://registry.npm.taobao.org/from2/download/from2-1.3.0.tgz#88413baaa5f9a597cfde9221d86986cd3c061dfd"
@@ -4760,6 +4807,21 @@ pretty-format@^24.9.0:
     ansi-styles "^3.2.0"
     react-is "^16.8.4"
 
+printj@~1.1.0:
+  version "1.1.2"
+  resolved "https://registry.nlark.com/printj/download/printj-1.1.2.tgz?cache=0&sync_timestamp=1630361456411&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fprintj%2Fdownload%2Fprintj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222"
+  integrity sha1-2Q3rKXWoufYA+zoclOP0xTx4oiI=
+
+printj@~1.2.2:
+  version "1.2.3"
+  resolved "https://registry.nlark.com/printj/download/printj-1.2.3.tgz?cache=0&sync_timestamp=1630361456411&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fprintj%2Fdownload%2Fprintj-1.2.3.tgz#2cfb2b192a1e5385dbbe5b46658ac34aa828508a"
+  integrity sha1-LPsrGSoeU4XbvltGZYrDSqgoUIo=
+
+printj@~1.3.0:
+  version "1.3.0"
+  resolved "https://registry.nlark.com/printj/download/printj-1.3.0.tgz?cache=0&sync_timestamp=1630361456411&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fprintj%2Fdownload%2Fprintj-1.3.0.tgz#9018a918a790e43707f10625d6e10187a367cff6"
+  integrity sha1-kBipGKeQ5DcH8QYl1uEBh6Nnz/Y=
+
 process-es6@^0.11.2:
   version "0.11.6"
   resolved "https://registry.npmjs.org/process-es6/-/process-es6-0.11.6.tgz#c6bb389f9a951f82bd4eb169600105bd2ff9c778"
@@ -5602,6 +5664,13 @@ sprintf-js@~1.0.2:
   resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
   integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
 
+ssf@~0.11.2:
+  version "0.11.2"
+  resolved "https://registry.nlark.com/ssf/download/ssf-0.11.2.tgz#0b99698b237548d088fc43cdf2b70c1a7512c06c"
+  integrity sha1-C5lpiyN1SNCI/EPN8rcMGnUSwGw=
+  dependencies:
+    frac "~1.1.2"
+
 sshpk@^1.7.0:
   version "1.16.1"
   resolved "https://registry.npm.taobao.org/sshpk/download/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
@@ -6214,6 +6283,16 @@ widest-line@^2.0.0:
   dependencies:
     string-width "^2.1.1"
 
+wmf@~1.0.1:
+  version "1.0.2"
+  resolved "https://registry.nlark.com/wmf/download/wmf-1.0.2.tgz#7d19d621071a08c2bdc6b7e688a9c435298cc2da"
+  integrity sha1-fRnWIQcaCMK9xrfmiKnENSmMwto=
+
+word@~0.3.0:
+  version "0.3.0"
+  resolved "https://registry.nlark.com/word/download/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961"
+  integrity sha1-hUIVfk+OhJ9KNjooiZLUdhLbmWE=
+
 worker-farm@^1.6.0, worker-farm@^1.7.0:
   version "1.7.0"
   resolved "https://registry.npm.taobao.org/worker-farm/download/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8"
@@ -6257,6 +6336,19 @@ xdg-basedir@^3.0.0:
   resolved "https://registry.nlark.com/xdg-basedir/download/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
   integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
 
+xlsx@^0.17.4:
+  version "0.17.4"
+  resolved "https://registry.npmmirror.com/xlsx/download/xlsx-0.17.4.tgz#dc3e3a0954c835f4d0fdd643645db6f4ac3f28f2"
+  integrity sha512-9aKt8g9ZLP0CUdBX8L5xnoMDFwSiLI997eQnDThCaqQMYB9AEBIRzblSSNN/ICMGLYIHUO3VKaItcedZJ3ijIg==
+  dependencies:
+    adler-32 "~1.2.0"
+    cfb "^1.1.4"
+    codepage "~1.15.0"
+    crc-32 "~1.2.0"
+    ssf "~0.11.2"
+    wmf "~1.0.1"
+    word "~0.3.0"
+
 xtend@^2.2.0:
   version "2.2.0"
   resolved "https://registry.npmjs.org/xtend/-/xtend-2.2.0.tgz#eef6b1f198c1c8deafad8b1765a04dad4a01c5a9"