批量导出并设置样式/批量导入

批量导出及导入是非常常见的,并且js-xlsx是使用很广的插件,它分两个版本,社区版基本能实现普通的导出和导入及简单的样式设置(但是正因为社区版免费所以样式设置好多坑),专业版需要付费,能实现很多样式设置。
导出:
Blob.js

(function (view) {
    "use strict";

    view.URL = view.URL || view.webkitURL;

    if (view.Blob && view.URL) {
        try {
            new Blob;
            return;
        } catch (e) {}
    }

    // Internally we use a BlobBuilder implementation to base Blob off of
    // in order to support older browsers that only have BlobBuilder
    var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
            var
                get_class = function(object) {
                    return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
                }
                , FakeBlobBuilder = function BlobBuilder() {
                    this.data = [];
                }
                , FakeBlob = function Blob(data, type, encoding) {
                    this.data = data;
                    this.size = data.length;
                    this.type = type;
                    this.encoding = encoding;
                }
                , FBB_proto = FakeBlobBuilder.prototype
                , FB_proto = FakeBlob.prototype
                , FileReaderSync = view.FileReaderSync
                , FileException = function(type) {
                    this.code = this[this.name = type];
                }
                , file_ex_codes = (
                    "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
                    + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
                ).split(" ")
                , file_ex_code = file_ex_codes.length
                , real_URL = view.URL || view.webkitURL || view
                , real_create_object_URL = real_URL.createObjectURL
                , real_revoke_object_URL = real_URL.revokeObjectURL
                , URL = real_URL
                , btoa = view.btoa
                , atob = view.atob

                , ArrayBuffer = view.ArrayBuffer
                , Uint8Array = view.Uint8Array
                ;
            FakeBlob.fake = FB_proto.fake = true;
            while (file_ex_code--) {
                FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
            }
            if (!real_URL.createObjectURL) {
                URL = view.URL = {};
            }
            URL.createObjectURL = function(blob) {
                var
                    type = blob.type
                    , data_URI_header
                    ;
                if (type === null) {
                    type = "application/octet-stream";
                }
                if (blob instanceof FakeBlob) {
                    data_URI_header = "data:" + type;
                    if (blob.encoding === "base64") {
                        return data_URI_header + ";base64," + blob.data;
                    } else if (blob.encoding === "URI") {
                        return data_URI_header + "," + decodeURIComponent(blob.data);
                    } if (btoa) {
                        return data_URI_header + ";base64," + btoa(blob.data);
                    } else {
                        return data_URI_header + "," + encodeURIComponent(blob.data);
                    }
                } else if (real_create_object_URL) {
                    return real_create_object_URL.call(real_URL, blob);
                }
            };
            URL.revokeObjectURL = function(object_URL) {
                if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
                    real_revoke_object_URL.call(real_URL, object_URL);
                }
            };
            FBB_proto.append = function(data/*, endings*/) {
                var bb = this.data;
                // decode data to a binary string
                if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
                    var
                        str = ""
                        , buf = new Uint8Array(data)
                        , i = 0
                        , buf_len = buf.length
                        ;
                    for (; i < buf_len; i++) {
                        str += String.fromCharCode(buf[i]);
                    }
                    bb.push(str);
                } else if (get_class(data) === "Blob" || get_class(data) === "File") {
                    if (FileReaderSync) {
                        var fr = new FileReaderSync;
                        bb.push(fr.readAsBinaryString(data));
                    } else {
                        // async FileReader won't work as BlobBuilder is sync
                        throw new FileException("NOT_READABLE_ERR");
                    }
                } else if (data instanceof FakeBlob) {
                    if (data.encoding === "base64" && atob) {
                        bb.push(atob(data.data));
                    } else if (data.encoding === "URI") {
                        bb.push(decodeURIComponent(data.data));
                    } else if (data.encoding === "raw") {
                        bb.push(data.data);
                    }
                } else {
                    if (typeof data !== "string") {
                        data += ""; // convert unsupported types to strings
                    }
                    // decode UTF-16 to binary string
                    bb.push(unescape(encodeURIComponent(data)));
                }
            };
            FBB_proto.getBlob = function(type) {
                if (!arguments.length) {
                    type = null;
                }
                return new FakeBlob(this.data.join(""), type, "raw");
            };
            FBB_proto.toString = function() {
                return "[object BlobBuilder]";
            };
            FB_proto.slice = function(start, end, type) {
                var args = arguments.length;
                if (args < 3) {
                    type = null;
                }
                return new FakeBlob(
                    this.data.slice(start, args > 1 ? end : this.data.length)
                    , type
                    , this.encoding
                );
            };
            FB_proto.toString = function() {
                return "[object Blob]";
            };
            FB_proto.close = function() {
                this.size = this.data.length = 0;
            };
            return FakeBlobBuilder;
        }(view));

    view.Blob = function Blob(blobParts, options) {
        var type = options ? (options.type || "") : "";
        var builder = new BlobBuilder();
        if (blobParts) {
            for (var i = 0, len = blobParts.length; i < len; i++) {
                builder.append(blobParts[i]);
            }
        }
        return builder.getBlob(type);
    };
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));

Export2Excel.js

/* eslint-disable */
require('script-loader!file-saver');
require('./Blob.js');
require('script-loader!xlsx-style/dist/xlsx.core.min');  // 设置样式的插件

function generateArray(table) {
    var out = [];
    var rows = table.querySelectorAll('tr');
    var ranges = [];
    for (var R = 0; R < rows.length; ++R) {
        var outRow = [];
        var row = rows[R];
        var columns = row.querySelectorAll('td');
        for (var C = 0; C < columns.length; ++C) {
            var cell = columns[C];
            var colspan = cell.getAttribute('colspan');
            var rowspan = cell.getAttribute('rowspan');
            var cellValue = cell.innerText;
            if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;

            //Skip ranges
            ranges.forEach(function (range) {
                if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
                    for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
                }
            });

            //Handle Row Span
            if (rowspan || colspan) {
                rowspan = rowspan || 1;
                colspan = colspan || 1;
                ranges.push({s: {r: R, c: outRow.length}, e: {r: R + rowspan - 1, c: outRow.length + colspan - 1}});
            }
            ;

            //Handle Value
            outRow.push(cellValue !== "" ? cellValue : null);

            //Handle Colspan
            if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
        }
        out.push(outRow);
    }
    return [out, ranges];
};

function datenum(v, date1904) {
    if (date1904) v += 1462;
    var epoch = Date.parse(v);
    return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
}

function sheet_from_array_of_arrays(data, opts) {
    var ws = {};
    var range = {s: {c: 10000000, r: 10000000}, e: {c: 0, r: 0}};
    for (var R = 0; R != data.length; ++R) {
        for (var C = 0; C != data[R].length; ++C) {
            if (range.s.r > R) range.s.r = R;
            if (range.s.c > C) range.s.c = C;
            if (range.e.r < R) range.e.r = R;
            if (range.e.c < C) range.e.c = C;
            var cell = {v: data[R][C]};
            if (cell.v == null) continue;
            var cell_ref = XLSX.utils.encode_cell({c: C, r: R});
            if (typeof cell.v === 'number') cell.t = 'n';
            else if (typeof cell.v === 'boolean') cell.t = 'b';
            else if (cell.v instanceof Date) {
                cell.t = 'n';
                cell.z = XLSX.SSF._table[14];
                cell.v = datenum(cell.v);
            }
            else cell.t = 's';
            ws[cell_ref] = cell;
        }
    }
    if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
    return ws;
}

function Workbook() {
    if (!(this instanceof Workbook)) return new Workbook();
    this.SheetNames = [];
    this.Sheets = {}
}

function s2ab(s) {
    var buf = new ArrayBuffer(s.length);
    var view = new Uint8Array(buf);
    for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
    return buf;
}

export function export_table_to_excel(id) {
    var theTable = document.getElementById(id);
    var oo = generateArray(theTable);
    var ranges = oo[1];

    /* original data */
    var data = oo[0];
    var ws_name = "SheetJS";
    console.log(data);

    var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);

    /* add ranges to worksheet */
    // ws['!cols'] = ['apple', 'banan'];
    ws['!merges'] = ranges;

    /* add worksheet to workbook */
    wb.SheetNames.push(ws_name);
    wb.Sheets[ws_name] = ws;

    var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});

    saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx")
}

function formatJson(jsonData) {
    console.log(jsonData)
}
export function export_json_to_excel(th, jsonData, defaultTitle, excelExplain) {
    /* original data */
    var data = jsonData;
    data.unshift(th);
    // 逆向迭代,向数组首项添加说明项
    for( let i = excelExplain.length-1; i!= -1; i-- ){
        data.unshift(excelExplain[i])
    }
    var ws_name = "SheetJS";
    var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
    // 特殊样式
    let specialStyle = {
        fill: {
            fgColor: {
                rgb: 'FFFF00'  //背景颜色
            }
        },
        font: {
            // sz: 18,
            bold: true     //字体
        },
        // alignment: {
        //     horizontal: 'center'    //居中
        // },
    };
    // 通用样式
    let generalStyle = {
        fill: {
            fgColor: {
                rgb: 'FFFF00'  //背景颜色
            }
        }
    };
    // 统一边框样式
    // let borderStyle = {
    //     border: {
    //         top: { style: 'thin' },    //上边框
    //         bottom: { style: 'thin' },    //下边框
    //         left: { style: 'thin' },    //左边框
    //         right: { style: 'thin' },    //右边框
    //     },
    // };
    
    // 设置列高(可以设置成功,但无效,因为库源码就不支持改行高)
    ws['!rows'] = [
        {hpx: 100}, {hpx: 100}, {hpx: 100}, {hpx: 100},{hpx: 100},{hpx: 100},{hpx: 100},{hpx: 100},{hpx: 100},{hpx: 100},{hpx: 100},{hpx: 100},{hpx: 100},{hpx: 100},{hpx: 100},{hpx: 100}
    ];
    
    if ( ws.B8.v == '省内管控现象'){
        // 合并A1-Q1
        ws['!merges'] = [
            {
            s: {c: 0, r: 0},
            e: {c: 16, r: 0}
            },
            {
            s: {c: 0, r: 1},
            e: {c: 16, r: 1}
            },
            {
            s: {c: 0, r: 2},
            e: {c: 16, r: 2}
            },
            {
            s: {c: 0, r: 3},
            e: {c: 16, r: 3}
            },
            {
            s: {c: 0, r: 4},
            e: {c: 16, r: 4}
            },
            {
            s: {c: 0, r: 5},
            e: {c: 16, r: 5}
            },
            {
            s: {c: 0, r: 6},
            e: {c: 16, r: 6}
            },
        ];
        // 设置列宽
        ws['!cols'] = [
            {wpx: 80}, {wpx: 160}, {wpx: 160}, {wpx: 160},{wpx: 160},{wpx: 160},{wpx: 240},{wpx: 240},{wpx: 80},{wpx: 240},{wpx: 80},{wpx: 240},{wpx: 80},{wpx: 80},{wpx: 240},{wpx: 240},{wpx: 80}
        ];
        // 根据键值首字母渲染制定列样式
        for( let i in ws ){
            let temp = i.substr(0,1);
            if( temp == 'A' || temp == 'C' || temp == 'E' || temp == 'J' || temp == 'L' || temp == 'M' || temp == 'N'){
                ws[i].s = generalStyle;
            }
        }
    }else if( ws.B8.v == '省内管控原因'){
        // 合并A1-S1
        ws['!merges'] = [
            {
            s: {c: 0, r: 0},
            e: {c: 18, r: 0}
            },
            {
            s: {c: 0, r: 1},
            e: {c: 18, r: 1}
            },
            {
            s: {c: 0, r: 2},
            e: {c: 18, r: 2}
            },
            {
            s: {c: 0, r: 3},
            e: {c: 18, r: 3}
            },
            {
            s: {c: 0, r: 4},
            e: {c: 18, r: 4}
            },
            {
            s: {c: 0, r: 5},
            e: {c: 18, r: 5}
            },
            {
            s: {c: 0, r: 6},
            e: {c: 18, r: 6}
            },
            {
            s: {c: 0, r: 6},
            e: {c: 18, r: 6}
            },
            {
            s: {c: 0, r: 6},
            e: {c: 18, r: 6}
            },
        ];
        // 设置列宽
        ws['!cols'] = [
            {wpx: 80}, {wpx: 160}, {wpx: 160}, {wpx: 160},{wpx: 160},{wpx: 160},{wpx: 180},{wpx: 180},{wpx: 240},{wpx: 240},{wpx: 240},{wpx: 240},{wpx: 80},{wpx: 240},{wpx: 80},{wpx: 80},{wpx: 240},{wpx: 240},{wpx: 80}
        ];
        // 根据键值首字母渲染制定列样式
        for( let i in ws ){
            let temp = i.substr(0,1);
            if( temp == 'A' || temp == 'C' || temp == 'E' || temp == 'G' || temp == 'L' || temp == 'N' || temp == 'N' || temp == 'O' || temp == 'P'){
                ws[i].s = generalStyle;
            }
        }
    }
    // 设置说明字段样式
    ws.A1.s = specialStyle;
    ws.A2.s = specialStyle;
    ws.A3.s = specialStyle;
    ws.A4.s = specialStyle;
    ws.A5.s = specialStyle;
    ws.A6.s = specialStyle;
    ws.A7.s = specialStyle;

    console.log('after修改单元格样式',ws)
    /* add worksheet to workbook */
    wb.SheetNames.push(ws_name);
    wb.Sheets[ws_name] = ws;
    var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
    var title = defaultTitle || '列表'
    saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), title + ".xlsx")
}

业务组件:

// 导出
    handleExport(){
      let self = this;
      require.ensure([], () => {
        var self = this;
        const { export_json_to_excel } = require('../../exportExcel/Export2Excel');
        const tHeader = self.excelHeader;
        // 上面设置Excel的表格第一行的标题
        const filterVal = self.excelFilterVal;
        let exportList = [];
        let formData = Object.assign( {},self.conditionQueryForm)
        formData.pageNumber = '';
        formData.pageSize = '';
        // 将联动对象转为code值
        formData.controlCode = formData.controlCode.controlCode;
        formData.phenomenonCode1 = formData.phenomenonCode1.phenomenonCode;
        formData.primaryProduct = formData.primaryProduct.productName;
        // 传递时,将节点编码存入字段
        formData.groupCode = self.queryBlocTree.code;
        console.log('查询条件',formData)
        exportPhenoList( formData )
        .then( response => {
          if(response.data.code == 200){
            exportList = response.data.data.list ;
            console.log('导出数据',exportList)
            exportList.forEach( item => {
              switch(item.orderType){
                case '1':
                item.orderType = '咨询';
                break;
                case '2':
                item.orderType = '投诉';
                break;
              };
              switch(item.isEndNode){
                case 'Y':
                item.isEndNode = '是';
                break;
                case 'N':
                item.isEndNode = '否';
                break;
              };
            });
            const list = exportList;
            const data = self.formatJson(filterVal, list);
            // 导出excel
            export_json_to_excel(tHeader, data, '现象配置excel' ,self.excelExplain);
          }else{
            self.$message({
                message: response.data.msg,
                type: 'warning'
            });
          }
        })
        .catch((err) =>{
            
        })
      })
    },
    // 导出格式化Json
    formatJson(filterVal, jsonData) {
      return jsonData.map(v => filterVal.map(j => v[j]))
    },

data数据模型中的定义:

excelHeader:['管控编码','省内管控现象', '大类编码','省内大类','小类编码','省内小类','一级产品','二级产品','工单类型','省内集团关系ID','集团编码','集团名称','映射层级','是否是末节点','一级产品','二级产品','增值名称','操作'],
excelFilterVal: ['controlCode','controlName','phenomenonCode1','phenomenonName1','phenomenonCode2','phenomenonName2','primaryProduct','secondaryProduct','orderType','phenomenonGroupId','groupCode','groupName','mappingLevel','isEndNode','groupPrimaryProduct','groupSecondaryProduct','valueAddedName'],
excelExplain: [['批量导入规则:'],['1、操作列请输入操作类型:省内添加,省内修改,省内删除,集团添加,集团修改,集团删除。'],['2、标黄列不需要修改系统自动处理。'],['3、添加省内类型,请管控填起,不能只填小类。'],['4、一级产品:移动电话、有线宽带、ITV、固定电话、小灵通。'],['5、二级产品:上网卡、语音、数据、VoLTE、5G、光纤宽带、普通宽带、标清、高清。'],['注:一二级产品为多选,以英文逗号分隔。']]   // excel使用说明,每条字段定义为数组形式是为了在excel中换行

导入有两种,一是用el-upload模拟:

<!-- 导入excel组件
  @author Sunwei
  @prop [Array] keyLabelMap      父组件传下的key-label关系表
  @prop [Array] jsonList        传递给父组件的excel数组
  @create 2019-10-25
-->
<template>
  <div class="import">
    <el-upload
        ref="upload"
        action="/wm/upload/"
        :show-file-list="false"
        :on-change="readExcel"
        :auto-upload="false">
        <el-button
            slot="trigger"
            class="clickButton"
            >
        </el-button>
    </el-upload>
    <!-- start of 批量导入对话框 -->
    <el-dialog title="批量导入" :visible.sync="dialogFormVisible2" :before-close="handleCloseDialog2" :modal-append-to-body='false'>
        <!-- start of 展示excel数据 -->
        <el-table
            :data="jsonList"
            :header-cell-style="{background:'#f9f9f9'}"
            stripe
            height="300"
            style="width: 100%">
            <el-table-column align="center" v-for="(item,index) in keyLabelMap" :key="index" width="130px">
                <template slot="header" slot-scope="scope">
                    <div :title="item.label" >{{item.label | fomartHeader}}</div>
                </template>
                <template slot-scope="scope">
                    <span>{{scope.row[item.key]}}</span>
                </template>
            </el-table-column>
        </el-table>
        <div class="errtips-box">
        <el-tag type="danger" v-for="(item,index) in errTipsList" :key="index">您当前Excel缺少&nbsp;{{item}}&nbsp;字段</el-tag>
        </div>
        <!-- end of 展示excel数据 -->
        <div slot="footer" class="dialog-footer">
            <el-button @click="handleCloseDialog2" class="button-cancel">取 消</el-button>
            <el-button type="primary" @click="handleSubmit" class="button-confirm">提 交</el-button>
        </div>
    </el-dialog>        
    <!-- end of 批量导入对话框 -->
  </div>
</template>

<script>
import XLSX from 'xlsx';
export default {
  name: 'Import',
  props:{
     keyLabelMap:''
  },
  data () {
    return {
      dialogFormVisible2: false,   // 控制dialog
      excelHeader: [],   // 表格表头
      excelList:[],     // 解析excel的数组
      errTipsList:[],   // 错误信息提示列表
      emptyAttrList:[],  // 存放excel有key无value的字段
      jsonList:[]
    }
  },
  filters: {
      fomartHeader(label){
          if(label.length > 5){
              label = label.slice(0,5) + '...';
          }else{
              label = label;
          }
          return label;
      }
  },
  methods:{
    readExcel(file) {
        let self = this;
        let fileType = file.name.slice( file.name.indexOf('.') + 1 );
        if( fileType != 'xlsx' ){
            self.$message({
                message: `请选择xlsx类型文件`,
                type: 'warning'
            });
            self.handleCloseDialog2();
            return;
        };
        self.dialogFormVisible2 = true;
        const fileReader = new FileReader();
        fileReader.onload = (ev) => {
            try {
                const data = ev.target.result;
                const workbook = XLSX.read(data, {
                    type: 'binary'
                });
                // 这儿只读取了表格中的工作表1,如要读取全部工作表则要做循环,并将每个工作表的数据用临时变量存进一个总的数组,最后在总的数据里对所有数据进行操作
                const sheetArray = XLSX.utils.sheet_to_json(workbook.Sheets.Sheet1);
                self.jsonList = sheetArray;
                let errTips = [];
                for(let i of self.jsonList){
                    for(let j in i){
                        if(self.excelHeader.indexOf(j) == -1){
                            // 收集excel所包含字段
                            self.excelHeader.push(j)
                        };
                    };
                    for(let item of self.keyLabelMap){
                        // 判断Excel字段是否完整
                        if(self.excelHeader.indexOf( item.label ) == -1){
                            errTips.push(item.label)
                            self.emptyAttrList.push(item.key)
                        }else{
                            i[item.key] = i[item.label];
                            delete i[item.label];
                        };
                    };
                    // 在每个对象循环完都进行header的清空,从而避免当第一行全字段而影响第二行有缺失字段时errTips的提示情况
                    self.excelHeader = [];
                };
                // 写在这儿就能取到Observer的属性值
                for(let item of self.jsonList){
                    if(item.effDates){
                        item.effDates = self.formatDate(item.effDates)
                    };
                    if(item.expDates){
                        item.expDates = self.formatDate(item.expDates)
                    };
                };
                self.errTipsList = new Set(errTips);
            } catch (e) {
                this.$message.warning('文件类型不正确!');
                return false;
            }
        };
        fileReader.readAsBinaryString(file.raw);
    },
    // 关闭批量导入--dialog
    handleCloseDialog2(){
        var self = this ;
        self.dialogFormVisible2 = false ;
        self.errTipsList = [];
        self.excelHeader = [];
    },
    // 格式化excel时间字段
    formatDate(numb) {
        let self = this;        
        // let tem = numb.toString().slice(numb.toString().indexOf('.')+1)
        let time = new Date((numb - 1) * 24 * 3600000 + 1)
        time.setYear(time.getFullYear() - 70)
        let year = time.getFullYear() + ''
        let month = time.getMonth() + 1 + ''
        if(month < 10){
            month = '0' + month;
        }
        let date = time.getDate() + ''
        if(date <10){
            date = '0' + date;
        }
        let hours = time.getHours() ;
        hours = self.correctHours(hours);
        if(hours <10){
            hours = '0' + hours;
        }
        let minutes = time.getMinutes();
        if(minutes <10){
            minutes = '0' + minutes;
        }
        let seconds = time.getSeconds();
        if(seconds <10){
            seconds = '0' + seconds;
        }
        return numb = year + '/' + month + '/' + date + ' ' + hours + ':' + minutes + ':' + seconds;
    },
    // 矫正时间偏差
    correctHours(hours){
        if(1 <= hours && hours <=7){
            hours = hours + 16;
        }else if(8 <= hours && hours<= 23){
            hours = hours - 8;
        }else if(hours == 0){
            hours = hours + 24 -8;
        }
        return hours;
    },
    // 提交excel数据
    handleSubmit(){
        var self = this ;
        // 缺少字段只做提示但并不阻止导入
        for(let item of self.emptyAttrList){
            for(let i of self.jsonList){
                if(!i[item]){
                  self.$set(i,`${item}`,'')
                };
            };
        };
        console.log('第二种导入方案解析excel',self.jsonList)
        self.$emit('ExcelOk',self.jsonList);
        self.dialogFormVisible2 = false ;
        self.excelHeader = [];
    },
  },
  created(){
      let self = this;
  }
}
</script>


<style lang='scss' scoped>
// 设置组件样式适应父组件
.import .upload{
    width: 100%;
    height: 100%;
    opacity: 0;
}
.import > div{
    width: 100%;
    height: 100%;
}
/deep/ .el-upload.el-upload--text{
    width: 100%;
    height: 100%;
    .el-button.clickButton{
        width: 100%;
        height: 100%;
        opacity: 0;
    }
}
// 将标题居左
/deep/ .el-dialog__title{
    margin-left: -668px;
}
// 错误消息列表
.errtips-box{
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
}
// 表头高度
/deep/ .el-table__header-wrapper{
    height: 55px;
}
</style>

业务组件:

<el-button class="bulk-import">批量导入
   <!-- start of 自定义导入组件 -->
     <ct-import class="ct-import" :pageName="pageName" @ExcelOk="handleImport"/>
   <!-- end of 自定义导入组件 -->
</el-button>
// 批量导入
    handleImport(excelList){
      let self = this;
      excelList = excelList.slice(1);
      self.importList = excelList;
      self.errTipsList = [];
      // 校验表格  
      for( var i = 0; i < self.importList.length; i++){
        if( self.importList[i].action == '省内添加' ){
          if( self.importList[i].bigPhenomenonName && self.importList[i].smallPhenomenonName ){
            if( self.importList[i].smallPSecondaryProduct && !self.importList[i].smallPrimaryProduct ){
              let errItem = {
                label: `Excel第${i + 9}行,数据格式不正确`
              };
              self.errTipsList.push( errItem );
            }
          }else{
            let errItem = {
              label: `Excel第${i + 9}行,数据格式不正确`
            };
            self.errTipsList.push( errItem );
          };
          // return;
        }else if( self.importList[i].action == '省内修改' ){
          if( self.importList[i].smallPhenomenonCode && self.importList[i].smallPhenomenonName ){
            if( self.importList[i].smallPSecondaryProduct && !self.importList[i].smallPrimaryProduct ){
              let errItem = {
                label: `Excel第${i + 9}行,数据格式不正确`
              };
              self.errTipsList.push( errItem );
            }
          }else{
            let errItem = {
              label: `Excel第${i + 9}行,数据格式不正确`
            };
            self.errTipsList.push( errItem );
          };
          // return;
        }else if( self.importList[i].action == '省内删除' ){
          if( self.importList[i].smallPhenomenonCode ){
            // self.fetchImport( self.importList );
          }else{
            let errItem = {
              label: `Excel第${i + 9}行,数据格式不正确`
            };
            self.errTipsList.push( errItem );
          }
          // return;
        }else if( self.importList[i].action == '集团添加' ){
          if( self.importList[i].smallPhenomenonName && self.importList[i].groupCode ){
            if( self.importList[i].groupPSecondaryProduct && !self.importList[i].groupPrimaryProduct ){
              let errItem = {
                label: `Excel第${i + 9}行,数据格式不正确`
              };
              self.errTipsList.push( errItem );
            }
          }else{
            let errItem = {
              label: `Excel第${i + 9}行,数据格式不正确`
            };
            self.errTipsList.push( errItem );
          };
          // return;
        }else if( self.importList[i].action == '集团修改' ){
          if( self.importList[i].phenomenonGroupId && self.importList[i].groupCode ){
            if( self.importList[i].groupPSecondaryProduct && !self.importList[i].groupPrimaryProduct ){
              let errItem = {
                label: `Excel第${i + 9}行,数据格式不正确`
              };
              self.errTipsList.push( errItem );
            }
          }else{
            let errItem = {
              label: `Excel第${i + 9}行,数据格式不正确`
            };
            self.errTipsList.push( errItem );
          };
          // return;
        }else if( self.importList[i].action == '集团删除' ){
          if( self.importList[i].phenomenonGroupId ){
            // self.fetchImport( self.importList );
          }else{
            let errItem = {
              label: `Excel第${i + 9}行,数据格式不正确`
            };
            self.errTipsList.push( errItem );
          }
          // return;
        }else if( !self.importList[i].action ){
          let errItem = {
            label: `Excel第${i + 9}行,操作字段未填写`
          };
          self.errTipsList.push( errItem );
          // return;
        }else{
          let errItem = {
            label: `Excel第${i + 9}行,操作字段为无效操作`
          };
          self.errTipsList.push( errItem );
          // return;
        }
      };
      self.importDialogVisible = true;
    },

二是用input框模拟(IE不兼容):

<!-- 导入excel组件
  @author Sunwei
  @prop [Array] keyLabelMap      父组件传下的key-label关系表
  @prop [Array] excelList        传递给父组件的excel数组
  @create 2019-07-17
-->
<template>
  <div class="import">
    <input class="upload" type="file" @change="importfromExcel(this)" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" ref="importInput"/>
    <!-- start of 批量导入对话框 -->
    <el-dialog title="批量导入" :visible.sync="dialogFormVisible2" :before-close="handleCloseDialog2" :modal-append-to-body='false'>
        <!-- start of 展示excel数据 -->
        <el-table
            :data="excelList"
            :header-cell-style="{background:'#f9f9f9'}"
            stripe
            height="300"
            style="width: 100%">
            <el-table-column align="center" v-for="(item,index) in keyLabelMap" :key="index" width="130px">
                <template slot="header" slot-scope="scope">
                    <div :title="item.label" >{{item.label | fomartHeader}}</div>
                </template>
                <template slot-scope="scope">
                    <span>{{scope.row[item.key]}}</span>
                </template>
            </el-table-column>
        </el-table>
        <div class="errtips-box">
        <el-tag type="danger" v-for="(item,index) in errTipsList" :key="index">您当前Excel缺少&nbsp;{{item}}&nbsp;字段</el-tag>
        </div>
        <!-- end of 展示excel数据 -->
        <div slot="footer" class="dialog-footer">
            <el-button @click="handleCloseDialog2" class="button-cancel">取 消</el-button>
            <el-button type="primary" @click="handleSubmit" class="button-confirm">提 交</el-button>
        </div>
    </el-dialog>        
    <!-- end of 批量导入对话框 -->
  </div>
</template>

<script>
import XLSX from 'xlsx';
export default {
  name: 'Import',
  props:{
     keyLabelMap:''
  },
  data () {
    return {
      dialogFormVisible2: false,   // 控制dialog
      excelHeader: [],   // 表格表头
      excelList:[],     // 解析excel的数组
      errTipsList:[],   // 错误信息提示列表
      emptyAttrList:[],  // 存放excel有key无value的字段
    }
  },
  filters: {
      fomartHeader(label){
          if(label.length > 5){
              label = label.slice(0,5) + '...';
          }else{
              label = label;
          }
          return label;
      }
  },
  methods:{
    // 解析excel
    importfromExcel(obj) {
        let self = this;
        self.dialogFormVisible2 = true;
        // 通过DOM取文件数据
        this.file = event.currentTarget.files[0];
        let fileType = self.file.name.slice(self.file.name.indexOf('.') + 1)
        if(fileType != 'xlsx'){
            self.$message({
                message: `请选择xlsx类型文件`,
                type: 'warning'
            });
            self.handleCloseDialog2();
            return;
        }
        var rABS = false; //是否将文件读取为二进制字符串
        var f = this.file;
        var reader = new FileReader();
        FileReader.prototype.readAsBinaryString = function(f) {
             var binary = "";
             var rABS = false; //是否将文件读取为二进制字符串
             var wb; //读取完成的数据
             var outdata;
             var reader = new FileReader();
             reader.onload = function(e) {
                   var bytes = new Uint8Array(reader.result);
                   var length = bytes.byteLength;
                   for(var i = 0; i < length; i++) {
                       binary += String.fromCharCode(bytes[i]);
                   }
                   var XLSX = require('xlsx');
                   if(rABS) {
                       wb = XLSX.read(btoa(fixdata(binary)), { //手动转化
                           type: 'base64'
                       });
                   } else {
                       wb = XLSX.read(binary, {
                           type: 'binary'
                       });
                   }
                   outdata = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);//outdata--解析出来的数组
                   this.da = [...outdata]
                   let arr = []
                   // 将解析的数据赋值给表格数据源
                   self.excelList = outdata;
                   // TODO:写在这儿,取不到Observer的属性
                //    for(let item of xx){
                //       console.log('这是每行的时间aaaa',item,typeof item,item.effDates)
                //       item.effDates = self.formatDate(item.effDates)
                //    }
                   let errTips = [];
                   for(let i of self.excelList){
                       for(let j in i){
                           if(self.excelHeader.indexOf(j) == -1){
                                // 收集excel所包含字段
                                self.excelHeader.push(j)
                           } 
                       }
                       for(let item of self.keyLabelMap){
                            // 判断Excel字段是否完整
                            if(self.excelHeader.indexOf( item.label ) == -1){
                                errTips.push(item.label)
                                self.emptyAttrList.push(item.key)
                            }else{
                                i[item.key] = i[item.label];
                                delete i[item.label];
                            }
                        }
                        // 在每个对象循环完都进行header的清空,从而避免当第一行全字段而影响第二行有缺失字段时errTips的提示情况
                        self.excelHeader = [];
                   }
                   // 写在这儿就能取到Observer的属性值
                   for(let item of self.excelList){
                       if(item.effDates){
                          item.effDates = self.formatDate(item.effDates)
                       }
                       if(item.expDates){
                          item.expDates = self.formatDate(item.expDates)
                       }
                   }
                   self.errTipsList = new Set(errTips);
                   this.da.map(v => {
                       let obj = {}
                       obj.id = v.id
                       obj.status = v.status
                       arr.push(obj)
                   })
                   let para = {
                       withList: arr
                   }
             }
             reader.readAsArrayBuffer(f);
        }
        if(rABS) {
            reader.readAsArrayBuffer(f);
        } else {
            reader.readAsBinaryString(f);
        };
    },
    // 关闭批量导入--dialog
    handleCloseDialog2(){
        var self = this ;
        // 重置value值,避免相同文件不能重复选择
        self.$refs.importInput.value = '';
        self.dialogFormVisible2 = false ;
        self.errTipsList = [];
        self.excelHeader = [];
    },
    // 格式化excel时间字段
    formatDate(numb) {
        let self = this;        
        // let tem = numb.toString().slice(numb.toString().indexOf('.')+1)
        let time = new Date((numb - 1) * 24 * 3600000 + 1)
        time.setYear(time.getFullYear() - 70)
        let year = time.getFullYear() + ''
        let month = time.getMonth() + 1 + ''
        if(month < 10){
            month = '0' + month;
        }
        let date = time.getDate() + ''
        if(date <10){
            date = '0' + date;
        }
        let hours = time.getHours() ;
        hours = self.correctHours(hours);
        if(hours <10){
            hours = '0' + hours;
        }
        let minutes = time.getMinutes();
        if(minutes <10){
            minutes = '0' + minutes;
        }
        let seconds = time.getSeconds();
        if(seconds <10){
            seconds = '0' + seconds;
        }
        return numb = year + '/' + month + '/' + date + ' ' + hours + ':' + minutes + ':' + seconds;
    },
    // 矫正时间偏差
    correctHours(hours){
        if(1 <= hours && hours <=7){
            hours = hours + 16;
        }else if(8 <= hours && hours<= 23){
            hours = hours - 8;
        }else if(hours == 0){
            hours = hours + 24 -8;
        }
        return hours;
    },
    // 提交excel数据
    handleSubmit(){
        var self = this ;
        for(let item of self.emptyAttrList){
            for(let i of self.excelList){
                if(!i[item]){
                  self.$set(i,`${item}`,'')
                }
            }
        }
        console.log('所解析excel数据',self.excelList)
        self.$emit('ExcelOk',self.excelList)
        self.dialogFormVisible2 = false ;
        // 重置value值,避免相同文件不能重复选择
        self.$refs.importInput.value = '';
        self.excelHeader = [];
    },
  },
  created(){
      let self = this;
  }
}
</script>


<style lang='scss' scoped>
// 最外层
.import .upload{
    width: 100%;
    height: 100%;
    opacity: 0;
}
// 将标题居左
/deep/ .el-dialog__title{
    margin-left: -668px;
}
// 错误消息列表
.errtips-box{
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
}
// 表头高度
/deep/ .el-table__header-wrapper{
    height: 55px;
}
</style>

最后附上几个链接,讲解js-xlsx导出/导入:
js-xlsx工具类库 xlsxUtils 使用示例
js-xlsx excel导出学习记录1 基础
js-xlsx excel导出学习记录2 样式
利用js-xlsx在vue中与element-ui结合实现excel前端导入
node+demo 使用xlsx-style设置表格的宽高等样式
前端vue中使用js-xlsx导出excel文件(可以修改样式

你可能感兴趣的:(脚踢原生JS,js-xlsx,导出导入,xlsx-style)