当前位置: 首页 > 工具软件 > ExcelJS > 使用案例 >

使用exceljs读取和导出Excel并支持图片导出

葛季萌
2023-12-01
  • 等待提示使用Vue插件Loading;
  • 导出图片需要将图片转换成base64格式,我在后台直接写了个url转base64接口并开启缓存
  • 导出exce支持多层对象读取数据,例如:{a:{b:[{c:1}]}};key可以写成’a.b[0].c’
  • 此前我们应该先引入exceljs插件const ExcelJS = require('exceljs');

由于对js的框架不甚了解,所以只能手写取值逻辑

/**
 * 多级获取数据
 * @param key 用点分开
 * @param data 数据对象
 */
export function getData(key, data) {
    if (data != null && key) {
        if (key.indexOf('.') > -1) {
            let keys = key.split('.');
            let nextKey = key.substr(keys[0].length + 1);
            let currKey = keys[0];
            if (currKey.indexOf('[') > -1) {
                return getData(nextKey, getData(currKey, data))
            }
            return getData(nextKey, data[keys[0]]);
        } else if (key.indexOf('[') > -1) {
            let currKeyPre = key.substr(0, key.indexOf('['));
            let indexStart = key.indexOf('[') + 1;
            let i = Number(key.substr(indexStart, key.length - indexStart - 1));
            let d = data[currKeyPre];
            if (d instanceof Array) {
                return d[i];
            }
            return d;
        } else {
            return data[key];
        }
    }
}

读取ExcelReader(代码相对较少)

  • 使用方法,后面我们有Vue的现成工具可以使用
ExcelReader.instance().setDateFormat(this.dateFormat).parse(file).then(reader => {
    let data = reader.getData(); // 可以指定获取哪个Sheet和标题位置
    this.titles = data.titles;
    this.list = data.list;
});
  • 用到的日期格式方法
export function formatDate(date, fmt) {
    if (!date) {
        return "";
    }
    if (!fmt) {
        fmt = 'yyyy-MM-dd';
    }
    try {
        if (typeof date === 'string' && date.indexOf("-") > -1) {
            date = new Date(date.replace(/-/g, '/'));
        } else {
            date = new Date(date);
        }
    } catch (e) {
        console.log(e);
    }
    if (date == null || date === '') {
        return null;
    }
    if (/(y+)/.test(fmt)) {
        fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
    }
    let o = {
        'M+': date.getMonth() + 1,
        'd+': date.getDate(),
        "h+": date.getHours() % 12 === 0 ? 12 : date.getHours() % 12, //小时
        "H+": date.getHours(), //小时
        'm+': date.getMinutes(),
        's+': date.getSeconds()
    };
    for (let k in o) {
        if (new RegExp(`(${k})`).test(fmt)) {
            let str = o[k] + '';
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
        }
    }
    return fmt;
}
export class ExcelReader {
    static instance() {
        return new ExcelReader();
    }

    workbook = {};
    sheets = [];
    dateFormat = "yyyy-MM-dd HH:mm:ss";

    setDateFormat(format) {
        this.dateFormat = format;
        return this;
    }

    parse(file) {
        return new Promise((ok, cancel) => {
            let loading = Loading.service({
                lock: true,
                text: `读取${file.name}...`,
                spinner: 'el-icon-loading',
                background: 'rgba(0, 0, 0, 0.7)'
            });
            this.workbook = new ExcelJS.Workbook();
            this.workbook.xlsx.load(file, {}).then(res => {
                let sheets = this.workbook.worksheets;
                for (let i = 0; i < sheets.length; i++) {
                    let sheet = sheets[i];
                    let sheetData = {name: sheet.name, index: i, rows: []};
                    let rowCount = sheet.rowCount;
                    for (let rowIndex = 1; rowIndex <= rowCount; rowIndex++) {
                        let row = sheet.getRow(rowIndex);
                        let rowData = [];
                        for (let cellIndex = 1; cellIndex <= row.cellCount; cellIndex++) {
                            let cell = row.getCell(cellIndex);
                            // type:2数值,3字符串,4日期,6公式
                            if (cell.type === 4 && cell.value && this.dateFormat) {
                                if (this.dateFormat.toLowerCase() === 'timestamp') {
                                    rowData.push(new Date(cell.value).getTime());
                                } else {
                                    rowData.push(formatDate(new Date(cell.value), this.dateFormat));
                                }
                            } else {
                                rowData.push(cell.text.trim());
                            }
                        }
                        sheetData.rows.push(rowData);
                    }
                    this.sheets.push(sheetData);
                }
                console.log(this.sheets);
                loading.close();
                ok(this);
            }).catch(err => {
                loading.close();
                cancel(err);
            })
        });
    }

	/**
	@param sheetIndex 指定Sheet 0开始
	@param titleIndex 指定标题行0开始
	@param sheetName 如果不为空则sheetIndex失效
	*/
    getData(sheetIndex, titleIndex = 0, sheetName) {
        // 解析
        let readIndex = 0;
        if (sheetIndex != null) {
            readIndex = sheetIndex;
        }
        if (sheetName) {
            readIndex = this.sheets.findIndex(sheet => sheet.name.toLowerCase() === sheetName.toLowerCase());
        }
        let sheet = this.sheets[readIndex];
        if (sheet.data) {
            return sheet.data;
        }
        let titleData = sheet.rows[titleIndex];
        let list = [];
        for (let i = titleIndex + 1; i < sheet.rows.length; i++) {
            let data = {message: '', loading: false};
            for (let ci = 0; ci < titleData.length; ci++) {
                data[titleData[ci]] = sheet.rows[i][ci];
            }
            list.push(data);
        }
        sheet.data = {titles: titleData, list: list};
        return sheet.data;
    }
}

导出ExcelBuilder(这里使用上面的getData方法)

  • 将url转换成base64方法(因为跨域问题,我后台自己写的)
export function imageToBase64(url) {
    return new Promise((ok, cancel) => {
        api.base64(url).then(res => {
            const extension = url.split('?')[0].substring(url.split('?')[0].lastIndexOf('.') + 1).toLowerCase();
            ok({base64: res.data, extension});
        }).catch(err => {
            ok();
        })
    })
}
  • 下载exceljs生成的工作簿
function openDownloadDialog(url, saveName) {
    if (typeof url == 'object' && url instanceof Blob) {
        url = URL.createObjectURL(url); // 创建blob地址
    }
    let aLink = document.createElement('a');
    aLink.href = url;
    aLink.download = saveName || ''; // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
    let event;
    if (window.MouseEvent) event = new MouseEvent('click');
    else {
        event = document.createEvent('MouseEvents');
        event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
    }
    aLink.dispatchEvent(event);
}
  • 真正的导出Excel方法
// 使用方式:ExcelBuilder.buildSheet([],sheetName:'test',titles:[{'key':'name'}],cloWidth:[{col:15}]).write(fileName);
export class ExcelBuilder {
    static instance() {
        return new ExcelBuilder();
    }

    workbook = new ExcelJS.Workbook();
    currSheet = null;
    loading = Loading.service({
        lock: true,
        text: `生成中...`,
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)'
    });

    // 生成标题
    buildTitle(titles) {
        let sheet = this.currSheet;
        if (!sheet) {
            throw Error("不能直接生成标题,请设置currSheet变量");
        }
        if (titles) {
            let keys = Object.keys(titles);
            let columns = [];
            let row = sheet.getRow(1);
            row.height = 25;
            for (let i = 0; i < keys.length; i++) {
                let key = keys[i];
                let config = titles[key];
                let header = config.header || config;
                let width = config.width || header.length * 3;
                let image = config.image || false;
                let hidden = config.hidden || false;
                columns.push({header, key, width, image, hidden});
                let cell = row.getCell(i + 1);
                cell.fill = {type: 'pattern', pattern: 'solid', fgColor: {argb: 'FFB6DDE8'}};
                cell.font = {size: 15, bold: true};
                cell.alignment = {vertical: 'middle', horizontal: 'center'};
                cell.border = {
                    top: {style: 'thin'},
                    left: {style: 'thin'},
                    bottom: {style: 'thin'},
                    right: {style: 'thin'}
                };
            }
            console.log("标题:", columns);
            sheet.columns = columns;
            return columns;
        }
    }

    addImage(cell, images, index, config) {
        let workbook = this.workbook;
        let sheet = this.currSheet;
        return new Promise((o, c) => {
            if (index >= images.length) {
                return o();
            }
            let width = 1 / images.length;
            imageToBase64(images[index]).then(data => {
                if (data) {
                    let image = workbook.addImage(data);
                    // TODO 这的布局可能还有问题
                    sheet.addImage(image, {
                        tl: {col: cell.col - 1 + width * index, row: cell.row - 1},
                        br: {col: cell.col - 1 + width * (index + 1), row: cell.row}
                    });
                } else {
                    cell.value = {text: images.join(",")};
                }
                this.addImage(cell, images, index + 1, config).then(r => o());
            })
        })
    }

    addCell(value, cell, config) {
        return new Promise((ok, cancel) => {
            if (value) {
                if (config.image) {
                    if (value instanceof Array) {
                        // 如果需要下载图片
                        this.addImage(cell, value, 0, config).then(r => ok());
                    } else {
                        this.addImage(cell, [value], 0, config).then(r => ok());
                    }
                } else {
                    cell.value = value;
                    return ok();
                }
            } else {
                return ok();
            }
        })
    }

    addRow(data, row) {
        let keys = this.currSheet.columns ? this.currSheet.columns.map(c => c.key) : Object.keys(data);
        let _this = this;
        let call = (i) => {
            return new Promise((ok, cancel) => {
                if (i >= keys.length) {
                    return ok();
                }
                let cell = row.getCell(i + 1);
                cell.alignment = {vertical: 'middle', horizontal: 'center'};
                let config = _this.currSheet.titles[i];
                _this.addCell(getData(keys[i], data), cell, config).then(_ => call(i + 1).then(_ => ok()));
            });
        };
        return new Promise((ok, cancel) => call(0).then(_ => ok()));
    }

    buildSheet(list = [], {sheetName, titles, defaultRowHeight = 15}) {
        if (this.loading) {
            this.loading = Loading.service({
                lock: true,
                text: `生成中...`,
                spinner: 'el-icon-loading',
                background: 'rgba(0, 0, 0, 0.7)'
            });
        }
        let _this = this;
        sheetName = sheetName || 'Sheet' + (this.workbook.worksheets.length);
        return new Promise((ok, cancel) => {
            let sheet = this.workbook.getWorksheet(sheetName);
            sheet = sheet || this.workbook.addWorksheet(sheetName);
            // sheet.properties.defaultRowHeight = defaultRowHeight;
            this.currSheet = sheet;
            this.currSheet.titles = this.buildTitle(titles);
            let startRowNumber = sheet.lastRow.number + 1;
            if (list && list.length) {
                let addList = (i) => {
                    return new Promise((ok, cancel) => {
                        if (i >= list.length) {
                            return ok();
                        }
                        let row = sheet.getRow(startRowNumber + i);
                        row.height = defaultRowHeight;
                        let data = list[i];
                        _this.loading.text = `${sheetName},进度:${i + 1}/${list.length}`;
                        _this.addRow(data, row).then(_ => addList(i + 1).then(_ => ok(_this)))
                    });
                };
                addList(0).then(_ => {
                    console.log(`${sheetName}生成完成`);
                    _this.loading.close();
                    ok(_this);
                });
            } else {
                _this.loading.close();
                ok(_this);
            }
        })

    }

    write(fileName) {
        console.log("开始下载");
        let buffer = this.workbook.xlsx.writeBuffer();
        buffer.then(res => {
            let blob = new Blob([res], {type: "application/octet-stream"});
            openDownloadDialog(blob, fileName + '.xlsx');
        })
    }
}

接下来是Vue插件,没有用Vue的到此就结束了

  • 读取插件(数据导入功能用到),因为支持错误导出,所以还需要上面的导出功能
// 主要用于下载模板
export function downloadFile(url, fileName = '') {
    axios.create().request({url: url, responseType: 'arraybuffer'}).then(res => {
        let blob = new Blob([res.data], {type: 'application/stream'});
        let objectUrl = URL.createObjectURL(blob);
        downloadURL(objectUrl, fileName)
    })
}
  • 使用方法
<read-excel-button :post="doImport"
   :columns="importColumns"
   template-name="合同导入模板.xlsx"
   date-format="yyyy-MM-dd"
   template="导入模板base64格式,带前缀">
</read-excel-button>
<script>
data(){
return {
importColumns: {
	"商品名": {required: true, key: 'name', reg: /\s+/,validator:(data,config,value)=>true},
}
}
doImport(data, index) {
  return new Promise((ok, cancel) => {
      console.log(data);
      product.add(data).then(res => {
          console.log(res);
          ok();
      }).catch(err => {
          console.log(err);
          cancel(err.msg);
      });
      // 解析合同分成
  })
}
</script>
<template>
    <div style="margin-left:10px;margin-right:10px;display: inline-block;">
        <el-button @click="selectFile" type="info">{{name}}</el-button>
        <input type="file" ref="file" @change="getFile" style="display: none;" accept=".xlsx"/>
        <el-dialog :title="title" :visible.sync="show" width="90%" @opened="selectAll" top="20px" :close-on-click-modal="false">
            <el-table id="importTable" :data="list" height="400px" @selection-change="selectionChange" ref="importTable"
                      :row-class-name="({row,index})=>row.message?'warning-row':row.loading?'current-row':row.success?'success-row ':''">
                <el-table-column type="selection" width="55" :selectable="(row)=>!row.disabled"></el-table-column>
                <el-table-column type="index"></el-table-column>
                <el-table-column v-for="title in titles" :label="title" :prop="title" :key="title" :min-width="title.length*15+20"></el-table-column>
                <el-table-column label="状态" prop="message" min-width="100px">
                    <template slot-scope="scope">
                        <div v-if="scope.row.message" style="color:red;">{{scope.row.message}}</div>
                        <div v-else-if="scope.row.loading"><i class="el-icon-loading"></i></div>
                        <div v-else-if="scope.row.success">完成</div>
                    </template>
                </el-table-column>
            </el-table>
            <span slot="footer" class="dialog-footer">
                    <el-button @click="exportError" v-if="errorList.length && !importing" type="warning">导出错误({{errorList.length}})</el-button>
                    <el-button @click="show = false" type="danger">关 闭</el-button>
                    <el-button type="primary" @click="doImport" :loading="importing">导入</el-button>
                </span>
        </el-dialog>
    </div>
</template>

<script>
    import {ExcelBuilder, ExcelReader} from "../utils/excel";
    import {downloadFile} from "../utils/download";

    export default {
        name: "ReadExcelButton",
        props: {
            name: {type: String, default: "上传数据"},
            title: {type: String, default: "数据内容"},
            post: Function,
            postAll: Function,
            template: String, // 模板base64
            templateName: {
                type: String,
                default: '模板下载.xlsx',
            },
            dateFormat: {
                type: String,
                default: 'yyyy-MM-dd HH:mm:ss', // timestamp
            },
            columns: Object, //字段设置与校验 excel标题:{required:false,reg:'正则校验',validator:function(value),key:'将数据复制到字段',formatter:function(value),message:'错误提示'}
        },
        data() {
            return {
                show: false,
                titles: [],
                list: [],
                selectList: [],
                importing: false,
                fileName: this.name,
            }
        },
        computed: {
            errorList() {
                return this.list.filter(i => i.error)
            }
        },
        methods: {
            // 选择文件
            selectFile() {
                /// 下载模板提示
                this.$confirm("请选择操作", '导入数据', {
                    confirmButtonText: '导入数据', cancelButtonText: '下载模板', distinguishCancelAndClose: true, callback: (action, instance) => {
                        if (action === 'confirm') {
                            this.$refs.file.dispatchEvent(new MouseEvent('click'));
                        } else if (action === 'cancel' && this.template) {
                            downloadFile(this.template, this.templateName);
                        }
                    }
                });
            },
            validAndFormat(list) {
                if (this.columns) {
                    // 验证数据
                    let keys = Object.keys(this.columns);
                    for (let i = 0; i < list.length; i++) {
                        let data = list[i]; // 当前验证数据
                        for (let k = 0; k < keys.length; k++) {
                            let key = keys[k]; // 当前验证字段
                            let value = data[key];
                            let config = this.columns[key]; // 字段配置;reg,validator,key,formatter,message
                            let valid = true; // 是否是有效的数据
                            let empty = (value == null || value === '');
                            if (config.required && empty) {
                                valid = false;
                            } else if (config.reg != null && (config.required || !empty)) {
                                if (!RegExp(config.reg).test(value)) {
                                    valid = false;
                                }
                            }
                            if (config.validator != null && typeof config.validator === 'function' && valid) {
                                valid = config.validator(data, config, value);
                            }
                            if (!valid) {
                                data.disabled = true;
                                data.error = true;
                                data.message = config.message || `${key},无效数据`;
                            }
                            // 数据格式化
                            if (config.formatter != null && typeof config.formatter === 'function' && valid) {
                                value = config.formatter(data, config, value);
                            }
                            if (config.key != null && config.key) {
                                if (config.key.includes('.')) {
                                    let ks = config.key.split(".");
                                    if (data[ks[0]] == null) {
                                        data[ks[0]] = {};
                                    }
                                    let lastValue = data[ks[0]];
                                    for (let ki = 0; ki < ks.length; ki++) {
                                        if (lastValue == null) {
                                            data[ks[ki]] = {};
                                        }
                                        if (ki === ks.length - 1) {
                                            lastValue[ks[ki]] = value;
                                        } else {
                                            lastValue = data[ks[ki]];
                                        }
                                    }
                                }
                                data[config.key] = value;
                            } else {
                                data[key] = value;
                            }
                        }
                    }
                    console.log("格式化后结果:", list);
                }
            },
            getFile() {
                let file = this.$refs.file.files[0];
                this.fileName = file.name.substr(0, file.name.lastIndexOf("."));
                ExcelReader.instance().setDateFormat(this.dateFormat).parse(file).then(reader => {
                    let data = reader.getData();
                    this.titles = data.titles;
                    this.list = data.list;
                    this.validAndFormat(this.list);
                    this.show = true;
                    // 清空选中文件,下次可以继续选择同样文件
                    this.$refs.file.value = null;
                });
            },
            selectAll() {
                this.$refs.importTable.toggleAllSelection(this.list.filter(i => !i.disabled));
            },
            doImport() {
                if (this.selectList.length <= 0) {
                    this.$message({message: '请选择数据', type: "warning"});
                    return;
                }
                console.log("选中数据:", this.selectList);
                this.importing = true;
                if (this.post != null) {
                    let success = [];
                    let tableRowsEle = null;
                    try {
                        let tableEle = document.getElementById('importTable');
                        let tableBodyEle = tableEle.getElementsByClassName('el-table__body')[0];
                        tableRowsEle = tableBodyEle.getElementsByClassName('el-table__row');
                    } catch (e) {
                        console.error(e);
                    }

                    let doPost = (data, index, call) => {
                        if (index >= this.selectList.length) {
                            call();
                            return;
                        }
                        // 滚动到可显示的底部区域
                        if (tableRowsEle) {
                            tableRowsEle[index].scrollIntoView(false);
                        }
                        data.loading = true;
                        this.post(data, index).then(res => {
                            success.push(data);
                            doPost(this.selectList[index + 1], index + 1, call)
                        }).catch(err => {
                            data.message = err;
                            data.error = true;
                            doPost(this.selectList[index + 1], index + 1, call)
                        }).finally(() => {
                            data.loading = false;
                        });
                    };
                    doPost(this.selectList[0], 0, res => {
                        success.forEach((suc) => {
                            this.$refs.importTable.toggleRowSelection(suc);
                            suc.disabled = true;
                            suc.success = true;
                        });
                        this.importing = false;
                        this.$message({type: 'success', message: '导入完成'});
                        this.$emit("success", success);
                    })
                } else if (this.postAll != null) {
                    this.postAll(this.selectList);
                }
            },
            selectionChange(list) {
                this.selectList = list;
            },
            exportError() {
                if (this.errorList && this.errorList.length) {
                    let titles = {};
                    for (let i = 0; i < this.titles.length; i++) {
                        let key = this.titles[i];
                        titles[key] = key;
                    }
                    titles['message'] = '状态';
                    ExcelBuilder.instance().buildSheet(this.errorList, {titles}).then(workbook => {
                        workbook.write(this.fileName + '-错误数据');
                    })
                } else {
                    this.$message({type: 'error', message: '没有错误字体'})
                }
            }
        }
    }
</script>

<style scoped>
    .el-table >>> .warning-row {
        background: #f7b4ad;
    }

    .el-table >>> .current-row {
        background-color: #ceffaa;
    }

    .el-table >>> .success-row {
        background-color: #e9fbe6;
    }
</style>

导出数据

  • 支持直接导出el-table数据
  • 支持导出图片
  • 支持导出多个Sheet
  • 涉及到数据格式化需要同getData功能setData
export function setData(key, data, value) {
    if (key != null) {
        if (key.indexOf(".") > -1) {
            let keys = key.split('.');
            let nextKey = key.substr(keys[0].length + 1);
            let currKey = keys[0];
            if (currKey.indexOf('[') > -1) {
                let d = getData(currKey, data);
                if (!d) {
                    d = setData(currKey, data, nextKey ? {} : value);
                }
                return setData(nextKey, d, value)
            }
            if (!data[keys[0]]) {
                data[keys[0]] = {};
            }
            return setData(nextKey, data[keys[0]], value);
        } else if (key.indexOf('[') > -1) {
            let currKeyPre = key.substr(0, key.indexOf('['));
            let indexStart = key.indexOf('[') + 1;
            let i = Number(key.substr(indexStart, key.length - indexStart - 1));
            if (!data[currKeyPre]) {
                data[currKeyPre] = [];
            }
            if (data[currKeyPre] instanceof Array) {
                if (data[currKeyPre].length < i) {
                    for (let j = data[currKeyPre].length; j <= i; j++) {
                        data[currKeyPre][j] = {};
                    }
                }
                data[currKeyPre][i] = value;
                return data[currKeyPre][i];
            }
            data[currKeyPre] = value;
            return data;
        } else {
            data[key] = value;
            return data;
        }
    }
}
  • 下面是导出主要功能(具体使用方法在插件中有个示例)
<template>
    <el-button @click="doExport">导出</el-button>
</template>

<script>
    import {ExcelBuilder} from "../utils/excel";
    import {getData, setData} from "../utils";

    export default {
        name: "BuilderExcelButton",
        props: {
            // 导出配置:{sheetName:'Sheet1',data:[Function,Array],columns,expand,tableRef}
            config: {
                type: [Object, Array, Function],
                default() {
                    return {
                        sheetName: 'Sheet1',
                        data: [{
                            name: '测试',
                            product: {price: 1},
                            channels: [
                                {name: '渠道1', channelId: 1},
                                {name: '渠道2', channelId: 2}
                            ]
                        }], // 数组或查询方法
                        tableRef: 'list', // 用于获取表格字段,需要配置对应prop,label属性
                        image: false, // 是否导出图片
                        columns: { // 与tableRef冲突,但是优先级大于tableRef
                            'name': {
                                header: '标题名称',
                                image: false, // 是否下载图片
                                formatter: function (data, column, value) {
                                    console.log('格式化数据:', value);
                                    return data['name'];
                                }
                            }
                        },
                        // 扩展字段,用于导出不在tableRef中的字段配置
                        expand: {
                            'product.price': {header: '价格'}
                        }
                    }
                }
            },
            name: String, // 导出文件名
        },
        data() {
            return {
                loading: false,
            }
        },
        methods: {
            doExport() {
                this.loading = false;
                // 解析配置到数组
                let loadConfig = () => new Promise((ok, cancel) => ok(this.config));
                if (typeof this.config === 'function') {
                    loadConfig = this.config;
                }
                loadConfig().then(config => {
                    let configs = config instanceof Array ? config : [config];
                    console.log("开始导出", config);
                    if (!config) {
                        return;
                    }
                    // 解析标题配置到config.titles
                    configs.forEach((c, i) => this.parseHeaders(c, i));
                    this.exportSheet(configs).then(workbook => {
                        console.log("完成", workbook);
                        this.loading = false;
                        let name = this.name ? this.name : "导出列表";
                        workbook.write(name);
                    }).catch(err => {
                        this.$message({type: 'error', message: '导出失败:' + err})
                    })
                })

            },
            // 解析表格对应的列配置
            parseTableColumns(tableRef) {
                if (tableRef) {
                    let refObject = this.parentRefObject(tableRef, this.$parent);
                    if (refObject) {
                        let columns = refObject.tableColumns();
                        let titles = {};
                        columns.filter(c => c.property).forEach(c => {
                            titles[c.property] = {header: c.label, image: c.label.includes("图"), formatter: c.formatter};
                        });
                        return titles;
                    }
                }
                return null;
            },
            // 解析单个配置文件
            parseHeaders(config, index) {
                config.sheetName = config.sheetName || `Sheet${index + 1}`;
                let titles = config.columns || this.parseTableColumns(config.tableRef);
                if (titles) {
                    if (config.expand) {
                        let entries = Object.entries(titles);
                        entries.forEach((e, i) => e.index = i);
                        Object.entries(config.expand).forEach(e => entries.push(e));
                        // TODO 根据索引排序标题
                        config.titles = Object.fromEntries(entries);
                    } else {
                        config.titles = titles;
                    }
                }
                if (config.titles) {
                    // 重新设置图片属性
                    let keys = Object.keys(config.titles);
                    for (let i = 0; i < keys.length; i++) {
                        let title = config.titles[keys[i]];
                        if (title.image) {
                            title.image = config.image || false;
                        }
                    }
                }
            },
            // 创建Sheet
            exportSheet(configs, index = 0, workbook) {
                return new Promise((ok, cancel) => {
                    if (index >= configs.length) {
                        return ok(workbook);
                    }
                    console.log("创建数据:", configs[index]);
                    let config = configs[index];
                    let data = config.data;
                    let defaultRowHeight = Object.values(config.titles).find(t => t.image) ? 50 : 15;
                    if (typeof data === 'function') {
                        data().then(list => {
                            data = list;
                            this.formatterData(config.titles, list);
                            (workbook || ExcelBuilder.instance()).buildSheet(list, {titles: config.titles, sheetName: config.sheetName, defaultRowHeight}).then(workbook => {
                                this.exportSheet(configs, index + 1, workbook).then(r => ok(r));
                            })
                        }).catch(err => {
                            this.$message({type: 'error', message: config.sheetName + '导出失败'});
                            this.exportSheet(configs, index + 1, workbook).then(r => ok(r));
                        })
                    } else if (data instanceof Array) {
                        this.formatterData(config.titles, data);
                        (workbook || ExcelBuilder.instance()).buildSheet(data, {titles: config.titles, sheetName: config.sheetName, defaultRowHeight}).then(workbook => {
                            this.exportSheet(configs, index + 1, workbook).then(r => ok(r));
                        })
                    } else {
                        cancel();
                    }
                })
            },
            formatterData(titles, list) {
                let formatterColumns = Object.entries(titles).filter(c => c[1].formatter != null);
                for (let i = 0; i < formatterColumns.length; i++) {
                    let key = formatterColumns[i][0];
                    let column = formatterColumns[i][1];
                    let formatter = column.formatter;
                    for (let di = 0; di < list.length; di++) {
                        let data = list[di];
                        let value = getData(key, data);
                        if (formatter != null) {
                            setData(key, data, formatter(data, column, value));
                        }
                    }
                }
            },
            exportList() {
                let titles = this.columns || this.parseTableColumns();
                console.log(titles);
                if (!titles) {
                    this.$message({type: 'error', message: '导出字段配置错误,请联系管理员'});
                    return;
                }
                // this.loading = true;
                this.loadData(titles).then(list => {
                    // 格式化数据
                    let formatterColumns = Object.entries(titles).filter(c => c[1].formatter != null);
                    for (let i = 0; i < formatterColumns.length; i++) {
                        let key = formatterColumns[i][0];
                        let column = formatterColumns[i][1];
                        let formatter = column.formatter;
                        for (let di = 0; di < list.length; di++) {
                            let data = list[di];
                            let value = data[key];
                            if (formatter != null) {
                                data[key] = formatter(data, column, value)
                            }
                        }
                    }
                    console.log(list);
                    console.log(titles);
                    ExcelBuilder.instance().buildSheet(list, {titles}).then(workbook => {
                        this.loading = false;
                        let name = this.name ? this.name : "字体列表";
                        workbook.write(name);
                    })
                }).catch(err => {
                });
            },
            parentRefObject(ref, parent) {
                if (parent) {
                    if (parent.$refs[ref]) {
                        return parent.$refs[ref];
                    } else {
                        return this.parentRefObject(ref, parent.$parent)
                    }
                }
                return null;
            }
        }
    }
</script>

<style scoped>

</style>

写完了,电脑好卡.有疑问请直接留言,我会不定期查看.

 类似资料: