Browse Source

转换Excel

zhongzewei 6 years ago
parent
commit
a03dd0cb0f

File diff suppressed because it is too large
+ 3 - 0
lib/fileSaver/FileSaver.min.js


File diff suppressed because it is too large
+ 36 - 0
lib/spreadjs/sheets/interop/gc.spread.excelio.11.1.2.min.js


+ 2 - 1
modules/ration_repository/controllers/repository_views_controller.js

@@ -12,7 +12,8 @@ class ViewsController extends BaseController{
         res.render('maintain/ration_repository/main.html',
             {
                 userAccount: req.session.managerData.username,
-                userID: req.session.managerData.userID
+                userID: req.session.managerData.userID,
+                LicenseKey:config.getLicenseKey(process.env.NODE_ENV)
             });
     }
     async redirectRation(req, res){

+ 549 - 0
web/maintain/ration_repository/js/main.js

@@ -2,7 +2,554 @@
  * Created by Syusuke on 2017/3/17.
  */
 
+//转换excel相关
+//Excel中人材机的数据不存在合并列
+const TransferExcel = (function () {
+    const $transferModal = $('#transfer');
+    const $file = $('#transfer-file');
+    const $transfer = $('#transferConfirm');
+    const $exportSpread = $('#exportSpread');
+    let exportSpread = null;
+    let transferData = [];
+    //--以防需要可配置的情况
+    const fixedCount = 4; //固定列数(顺序号、项目、单位、代号)
+    const beginCol = 0;//起始的列
+    //固定的这几列的列号映射是动态的,因为有一些子表,这些列进行了合并列的处理
+    let colMapping = {
+        serialNo: 0,
+        name: 1,
+        unit: 2,
+        code: 3,
+        consumeAmt: 4, //消耗量开始的列
+    };
+    //---
+    function isUnDef(v) {
+        return typeof v === 'undefined' || v === null;
+    }
+    //取消所有空格
+    function trimAll(v) {
+        return typeof v === 'string' ? v.replace(/\s/g, '') : v;
+    }
+    //单元格是否有数据
+    function cellHasData(cell) {
+        return cell && cell.value;
+    }
+    //行是否有数据
+    //@param {Object}rowData(行数据) {Array}deduct(排除的列)
+    function rowHasData(rowData, deduct = null) {
+        for (let col in rowData) {
+            if (deduct && deduct.includes(col)) {
+                continue;
+            }
+            let cell = rowData[col];
+            if (cell && cell.value) {
+                return true;
+            }
+        }
+        return false;
+    }
+    //行为列头行 //第一列为数值,且后面其他列有数据(不单纯认为是第二列,因为怕有合并序号列)
+    function headerRow(rowData) {
+        return rowData[0] && rowData[0].value && /^\d+$/.test(rowData[0].value) && rowHasData(rowData, ['0']);
+
+    }
+    //去除定额子表数组中的空行(有的表格是在 单位:xx 下面还跟着一些垃圾数据,导致subRation的range范围判定变大了,但是四列后面的数据是空的)
+    function simplifyRationTable(arr) {
+        let target = [];
+        for (let subArr of arr) {
+            let emptyRow = subArr.every(function (ele) {
+                return ele === null;
+            });
+            if (emptyRow) {
+                continue;
+            }
+            target.push(subArr);
+        }
+        return target;
+    }
+    //将二维数组转换成每个元素都有值的二维数据(去没值元素,因为前四列有可能合并列)
+    function simplifyGljTable(arr) {
+        let target = [];
+        for (let subArr of arr) {
+            let subTarget = [];
+            for (let ele of subArr) {
+                if (ele !== null) {
+                    subTarget.push(ele);
+                }
+            }
+            target.push(subTarget);
+        }
+        return target;
+    }
+    //获取前固定四列的动态列映射(解决这几列可能有合并列的问题)
+    function getColMapping(rowData, colCount) {
+        function getUndefinedField(obj) {
+            let needFields = ['serialNo', 'name', 'unit', 'code', 'consumeAmt'];
+            for (let field of needFields) {
+                if (!obj.hasOwnProperty(field)) {
+                    return field;
+                }
+            }
+            return null;
+        }
+        //假定序号列为第一列
+        let mapping = {serialNo: 0};
+        for (let i = 1; i < colCount; i++) {
+            let col = beginCol + i,
+                cell = rowData[col];
+            //还没设置的字段(必须要有的是serialNo, name, unit, code, consumeAmt)
+            if (cell && cell.value) {
+                let field = getUndefinedField(mapping);
+                if (field) {
+                    mapping[field] = col;
+                }
+                if (typeof mapping.consumeAmt !== 'undefined') {
+                    return mapping;
+                }
+            }
+        }
+        return null;
+    }
+    
+    //获取表头定额名称数据(最后一行为定额末位编码)
+    //合并的单元格处理:为了更好的让定额获得对应的定额名称,将合并单元格做填值处理, eg: [a]['合并'] = [a][a]
+    //@param {Object}dataTable {Object}range(表头的范围,row, col, rowCount, colCount) {Array}spans(spread解析Excel后的合并数组)
+    //@return {Array}
+    function getSubRationTable(dataTable, range, spans) {
+        let subTable = [];
+        for (let i = 0; i < range.rowCount; i++) {
+            subTable.push(Array(range.colCount).fill(null));
+        }
+        //获取合并的单元格填充数据
+        let fillArr = [];
+        for (let i = 0; i < range.rowCount; i++) {
+            let row = range.row + i;
+            if (!dataTable[row]) {
+                continue;
+            }
+            for (let j = 0; j < range.colCount; j++) {
+                let col = range.col + j;
+                let cell = dataTable[row][col];
+                //只有有值的单元格,判断合并才有意义,有值且合并列的单元格,每列的值认为都是一样的
+                if (cellHasData(cell)) {
+                    //是否合并了单元格
+                    let span = spans.find(function (data) {
+                        return data.row === row && data.col === col;
+                    });
+                    //这个fillData给subData填值用
+                    let fillData = {value: trimAll(cell.value), range: {row: i, col: j}};
+                    if (span) {
+                        fillData.range.rowCount = span.rowCount;
+                        fillData.range.colCount = span.colCount;
+                    } else {
+                        fillData.range.rowCount = 1;
+                        fillData.range.colCount = 1;
+                    }
+                    fillArr.push(fillData);
+                }
+            }
+        }
+        //将数据填充到subData中
+        for (let fillData of fillArr) {
+            //合并行不需要向下填值(否则会重复)
+            let row = fillData.range.row;
+            //合并列需要向右填值
+            for (let j = 0; j < fillData.range.colCount; j++) {
+                let col = fillData.range.col + j;
+                subTable[row][col] = trimAll(fillData.value);
+            }
+        }
+        return simplifyRationTable(subTable);
+    }
+    //获取工料机子表数据(序号至最末消耗量)
+    //@param {Object}dataTable {Object}range(工料机子表的范围,row, col, rowCount, colCount)
+    //@return {Array}
+    function getSubGljTable(dataTable, range) {
+        let gljTable = [];
+        for (let i = 0; i < range.rowCount; i++) {
+            gljTable.push(Array(range.colCount).fill(null));
+        }
+        for (let i = 0; i < range.rowCount; i++) {
+            let row = range.row + i;
+            if (!dataTable[row]) {
+                continue;
+            }
+            for (let j = 0; j < range.colCount; j++) {
+                let col = range.col + j;
+                let cell = dataTable[row][col];
+                if (cellHasData(cell)) {
+                    gljTable[i][j] = cell.value;
+                }
+            }
+        }
+        //工料机数据每个单元格应该都有值,没有的为合并列造成,这里将没值的单元格去除
+
+        return simplifyGljTable(gljTable);
+    }
+    /*
+    * 从原本的excel中提取数据
+    * 需要的数据结构:eg: [{code: '1-1-1', name: '伐树', unit: '表列单位', subTable: [{subGlj: [], subRation: []}]}]
+    * subTable为定额数据对应的子表数据,其中有定额名称子表、工料机子表
+    * */
+    function extractDataFromExcel(sheetData) {
+        let dataTable = sheetData.data.dataTable,
+            spans = sheetData.spans;
+        //行数据是定额行 eg: 1-1-1 人工挖土方
+        //@param {Number}row
+        //@return {Object || Null} eg: {code: '1-1-1', name: '人工挖土方'}
+        function rationRow(row) {
+            let cell = dataTable[row][beginCol];
+            if (!cell || !cell.value || typeof cell.value !== 'string') {
+                return false;
+            }
+            let v = trimAll(cell.value);
+            //[\-,—,一] 这里是因为pdf转出来的excel(需要转换的excel)会吧"-"显示成各种奇怪的横杆
+            //第一数值限制在3位以内,防止有: 2019-05-01等日期干扰
+            let reg = /^(\d{1,3}[\-,_,—,一]{1}\d+[\-,_,—,一]{1}\d+)(\w{0,}[\u4e00-\u9fa5]{1,})/;
+            let match = reg.exec(v);
+            if (match && match.length === 3 && match[0] && match[1] && match[2]) {
+                return {code: match[1].replace(/[_,—,一]/g, '-'), name: match[2]};
+            }
+            return null;
+        }
+        //单位数据行 eg: 单位:表列单位
+        //@return {String || Null} eg: '表列单位'
+        function unitRow(row) {
+            let cell = dataTable[row][beginCol];
+            if (!cell || !cell.value || typeof cell.value !== 'string') {
+                return false;
+            }
+            let v = trimAll(cell.value);
+            let reg = /单位[\:, :]([\w+, \u4e00-\u9fa5]{0,})/;
+            let match = reg.exec(v);
+            if (match && match.length === 2 && match[0] && match[1]) {
+                return match[1];
+            }
+            return null;
+        }
+        //行数据是人材机数据行
+        //表头后,某行第一列为数值,第二列有值,则该行为紧接着表头的人材机数据行
+        //@return {Boolean}
+        function rowIsGlj(rowData) {
+            let numberCell = rowData[colMapping.serialNo],
+                valueCell = rowData[colMapping.name];
+            return numberCell && numberCell.value && /^\d+$/.test(numberCell.value) && valueCell && Boolean(valueCell.value);
+        }
+        //连续有数据的列数(每个子表格中工料机的数据列数)
+        //由于序号、项目、单位、代号可能存在合并列,因此从消耗量列(消耗量列不会合并列,消耗量对应的列号已在getColMapping获取)开始统计
+        function getDataCount(row) {
+            let consumeAmtCol = colMapping.consumeAmt;
+            for (let col = consumeAmtCol; col < sheetData.columnCount; col++) {
+                let cell = dataTable[row][col];
+                if (!cell || !cell.value) {
+                    return col;
+                }
+            }
+            return sheetData.columnCount;
+        }
+        //获取表格的子表范围(定额名称表头范围、工料机数据范围)
+        //遇到单位:xx行rowA后,获取紧跟其后的表格,该表格最少要有一行工料机数据行rowB,表头范围则为rowA至rowB
+        //@param {Number}beginRow
+        function getTableRange(beginRow) {
+            let hasTHead = false,
+                hasTable = false,
+                hasMapping = false; //是否获取过固定列映射
+            let range = {
+                subRation: {},
+                subGlj: {},
+            };
+            for (let row = beginRow; row < sheetData.rowCount; row++) {
+                if (!dataTable[row]) {
+                    continue;
+                }
+                //第一个有数据的行,为表头第一行
+                if (rowHasData(dataTable[row]) && !hasTHead) {
+                    hasTHead = true;
+                    range.subRation.row = row;
+                }
+                //获取当前子表的固定列映射
+                if (hasTHead && !hasTable && !hasMapping && headerRow(dataTable[row])) {
+                    hasMapping = true;
+                    colMapping = getColMapping(dataTable[row], sheetData.columnCount);
+                    if (!colMapping) {
+                        return null;
+                    }
+                }
+                //第一条工料机数据行
+                if (hasTHead && !hasTable && colMapping && rowIsGlj(dataTable[row])) {
+                    hasTable = true;
+                    range.subGlj.row = row;
+                    range.subGlj.col = 0;
+                    range.subRation.col = colMapping.consumeAmt;
+                    range.subRation.rowCount = range.subGlj.row - range.subRation.row;
+                    range.subGlj.colCount = getDataCount(row);
+                    range.subRation.colCount = range.subGlj.colCount - colMapping.consumeAmt;
+                }
+                if (hasTable && !rowIsGlj(dataTable[row])) {
+                    range.subGlj.rowCount = row - range.subGlj.row;
+                    return range;
+                }
+                if (hasTable && (row === sheetData.rowCount - 1 || !dataTable[row + 1])) {
+                    range.subGlj.rowCount = row - range.subGlj.row + 1;
+                    return range;
+                }
+            }
+            return null;
+        }
+        //分析整个表
+        let extractData = [];
+        //定额行后必须要跟着单位行
+        let hasUnit = false;
+        for (let row = 0; row < sheetData.rowCount; row++) {
+            if (!dataTable[row] || !rowHasData(dataTable[row])) {
+                continue;
+            }
+            let rationData = rationRow(row);
+            if (rationData) {
+                if (!hasUnit) {
+                    extractData.pop();
+                }
+                hasUnit = false;
+                //转换数据数组的每个元素,为定额基本数据:不完整的code、不完整的name、unit、和子表门构成
+                //subTable: [{subRation: [], subGlj: []}],subRation为每个子表表头数据(排除掉了顺序号至代号),
+                //subGlj为每个子表工料机数据
+                let basicData = {
+                    code: rationData.code,
+                    name: rationData.name,
+                    unit: null,
+                    subTable: []
+                };
+                extractData.push(basicData);
+            }
+            let unitData = unitRow(row);
+            if (unitData) {
+                hasUnit = true;
+                let thisBasicData = extractData[extractData.length - 1];
+                if (thisBasicData) {
+                    if (!thisBasicData.unit) {
+                        thisBasicData.unit = unitData;
+                    }
+                    //获取表格数据
+                    let range = getTableRange(row + 1);
+                    if (range) {
+                        let subRationTable = getSubRationTable(dataTable, range.subRation, spans),
+                            subGljTable = getSubGljTable(dataTable, range.subGlj);
+                        thisBasicData.subTable.push({subRation: subRationTable, subGlj: subGljTable});
+                        //跳过其中的行
+                        row = range.subGlj.row + range.subGlj.rowCount - 1;
+                    }
+                }
+            }
+        }
+        return extractData;
+    }
+    /*
+    * 转换数据,将提取出来的数据转换成另外一种数据结构,便于转化Excel的结构
+    * 需要的数据结构: eg: [{code: '1-1-1-1', name: '伐树xxx', unit: '表列单位', gljList: [{code,name,unit,comsumeAmt}]}]
+    * */
+    function transferDataFromExtract(extractData) {
+        //从一个提取的数据(1定额数据及其子表数据)中获取一份转换数据
+        function transfer(source) {
+            //以完整定额编码为属性,因为1个定额可能会有多张子表,且完整定额编码相同,子工料机不同,方便直接添加后续的工料机
+            let temp = {},
+                target = [];
+            let basicCode = source.code,
+                basicName = source.name,
+                basicUnit = source.unit;
+            //处理消耗量,可能有(3.5) 和 - 的情况, 处理:(3.5) => 3.5  - => 0
+            function handleConsumeAmt(consumeAmt) {
+                if (typeof consumeAmt === 'string') {
+                    consumeAmt = trimAll(consumeAmt);
+                    consumeAmt = consumeAmt.replace(/[\-,_,—,一,\(,\),(,)]/g, '');
+                    if (!consumeAmt) {
+                        return 0;
+                    }
+                }
+                return consumeAmt;
+            }
+            //从工料机子表中获取工料机数据, index为定额编码对应的下标索引
+            function getGljList(gljTable, index) {
+                let gljList = [];
+                //获取的工料机对应列
+                let gljColMapping = {
+                    name: 1,
+                    unit: 2,
+                    code: 3,
+                    consumeAmtCol: fixedCount + index
+                };
+                for (let rowData of gljTable) {
+                    //工料机数据必须要有名称、单位、编码
+                    if (!rowData[gljColMapping.name] ||
+                        !rowData[gljColMapping.unit] ||
+                        !rowData[gljColMapping.code]) {
+                        continue;
+                    }
+                    let consumeAmt = isUnDef(rowData[gljColMapping.consumeAmtCol]) ? 0 : handleConsumeAmt(rowData[gljColMapping.consumeAmtCol]);
+                    gljList.push({
+                        name: rowData[gljColMapping.name],
+                        unit: rowData[gljColMapping.unit],
+                        code: rowData[gljColMapping.code],
+                        consumeAmt: consumeAmt,
+                    });
+                }
+                return gljList;
+            }
+            //拼接定额工料机数据
+            for (let table of source.subTable) {
+                let rationTable = table.subRation;
+                if (!rationTable || rationTable.length === 0) {
+                    continue;
+                }
+                let lastRationCodes = rationTable.pop(); //定额子表,最后一行是末位定额编码
+                for (let i = 0; i < lastRationCodes.length; i++) {
+                    let lastCode = lastRationCodes[i];
+                    //拼接定额编码
+                    let compleCode = `${basicCode}-${lastCode}`,
+                        gljList = getGljList(table.subGlj, i);
+                    if (!temp[compleCode]) { //该定额不存在
+                        temp[compleCode] = {
+                            code: compleCode,
+                            unit: basicUnit,
+                            name: basicName,
+                            gljList: gljList
+                        };
+                    } else { //该定额已存在,则追加工料机
+                        temp[compleCode].gljList = temp[compleCode].gljList.concat(gljList);
+                    }
+                    //拼接定额名称
+                    for (let rationNameRow of rationTable) {
+                        if (rationNameRow[i]) {
+                            temp[compleCode].name += ` ${rationNameRow[i]}`;
+                        }
+                    }
+                }
+            }
+            //将temp对象转换为数据
+            for (let code in temp) {
+                target.push(temp[code]);
+            }
+            return target;
+        }
+        let transferData = [];
+        for (let data of extractData) {
+            let unitTargetData = transfer(data);
+            transferData = transferData.concat(unitTargetData);
+        }
+        return transferData;
+    }
+    //导入Excel
+    function exportToExcel(transferData, fileName) {
+        $.bootstrapLoading.start();
+        setTimeout(function () {
+            if (exportSpread) {
+                exportSpread.destroy();
+            }
+            exportSpread = new GC.Spread.Sheets.Workbook($exportSpread[0], {sheetCount: 1});
+            let sheet = exportSpread.getSheet(0);
+            sheet.suspendPaint();
+            sheet.suspendEvent();
+            //往表格填值
+            let curRow = 0,
+                fillCol = {
+                    code: 1,
+                    name: 2,
+                    unit: 3,
+                    consumeAmt: 4
+                };
+            function getRowCount() {
+                let count = 0;
+                for (let data of transferData) {
+                    count += 1 + data.gljList.length;
+                }
+                return count;
+            }
+            sheet.setRowCount(getRowCount());
+            for (let data of transferData) {
+                sheet.setValue(curRow, 0, '定额');
+                sheet.setValue(curRow, fillCol.code, data.code);
+                sheet.setValue(curRow, fillCol.name, data.name);
+                sheet.setValue(curRow, fillCol.unit, data.unit);
+                curRow++;
+                for (let glj of data.gljList) {
+                    sheet.setValue(curRow, fillCol.code, glj.code);
+                    sheet.setValue(curRow, fillCol.name, glj.name);
+                    sheet.setValue(curRow, fillCol.unit, glj.unit);
+                    sheet.setValue(curRow, fillCol.consumeAmt, glj.consumeAmt);
+                    curRow++;
+                }
+            }
+            sheet.resumeEvent();
+            sheet.resumePaint();
+            let json = exportSpread.toJSON();
+            let excelIo = new GC.Spread.Excel.IO();
+            excelIo.save(json, function(blob) {
+                saveAs(blob, fileName);
+                $.bootstrapLoading.end();
+                $transferModal.modal('hide');
+            }, function(e) {
+                $.bootstrapLoading.end();
+                $transferModal.modal('hide');
+                console.log(e);
+            });
+        }, 200);
+    }
+    function eventListener() {
+        $transferModal.on('hidden.bs.modal', function () {
+            $file.val('');
+            transferData = [];
+        });
+        //导入excel,提取并转换定额数据
+        $file.change(function () {
+            $transfer.addClass('disabled');
+            let file = $(this)[0];
+            let excelFile = file.files[0];
+            if(excelFile) {
+                let xlsReg = /xls$/g;
+                if(excelFile.name && xlsReg.test(excelFile.name)){
+                    alert('请选择xlsx文件');
+                    $(this).val('');
+                    return;
+                }
+                $.bootstrapLoading.start();
+                $('#loadingPage').css('z-index', '2000');
+                //前端解析excel数据
+                let excelIo = new GC.Spread.Excel.IO();
+                let sDate = +new Date();
+                excelIo.open(excelFile, function (json) {
+                    console.log(json);
+                    let extractData = extractDataFromExcel(json.sheets.Sheet1);
+                    transferData = transferDataFromExtract(extractData);
+                    console.log(`解析Excel文件时间:${+new Date() - sDate}`);
+                    $.bootstrapLoading.end();
+                    $transfer.removeClass('disabled');
+                }, function (e) {
+                    $.bootstrapLoading.end();
+                    $transfer.removeClass('disabled');
+                    alert(e.errorMessage);
+                });
+            }
+        });
+        //确认转换,导出转换的Excel
+        $transfer.click(function () {
+            if (!transferData || transferData.length === 0) {
+                //没有转换数据
+                alert('没有转换数据');
+                return;
+            }
+            let fileName = '转换数据.xlsx';
+            if ($file[0].files && $file[0].files[0] && $file[0].files[0].name) {
+                fileName = '转换数据' + $file[0].files[0].name;
+            }
+            exportToExcel(transferData, fileName);
+        });
+    }
+
+    return {eventListener}
+})();
+
 $(function () {
+    TransferExcel.eventListener();
+
     let dispNameArr;
     let preDeleteId = null;
     let deleteCount = 0;
@@ -292,6 +839,7 @@ function getAllRationLib(callback){
                         "<td><a class='btn btn-secondary btn-sm import-source' href='javacript:void(0);' data-id='"+ id +"' title='导入原始数据'><i class='fa fa-sign-in fa-rotate-90'></i>导入</a></td>" +
                         "<td><a class='btn btn-success btn-sm export' href='javacript:void(0);' data-toggle='modal' data-id='"+ id +"' data-target='#emport' title='导出内部数据'><i class='fa fa-sign-out fa-rotate-270'></i>导出</a> " +
                         "<a class='btn btn-secondary btn-sm import-data' href='javacript:void(0);' data-id='"+ id +"' title='导入内部数据'><i class='fa fa-sign-in fa-rotate-90'></i>导入</a></td>" +
+                        "<td><a class='btn btn-secondary btn-sm transfer-excel' data-toggle='modal' data-target='#transfer' href='javacript:void(0);' data-id='"+ id +"' title='转换Excel'><i class='fa fa-sign-in fa-rotate-90'></i>转换</a></td>" +
                         "<td><a class='btn btn-secondary btn-sm set-comple' href='javacript:void(0);' data-id='"+ id +"' title='将章节树设为补充模板数据'><i class='fa fa-sign-in fa-rotate-90'></i>设置</a></td>" +
                         "</tr>");
                     $("#tempId").attr("id", id);
@@ -380,6 +928,7 @@ function createRationLib(rationObj, dispNamesArr){
                     "<td><a class='btn btn-secondary btn-sm import-source' href='javacript:void(0);' data-id='"+ id +"' title='导入原始数据'><i class='fa fa-sign-in fa-rotate-90'></i>导入</a></td>" +
                     "<td><a class='btn btn-success btn-sm export' href='javacript:void(0);' data-toggle='modal' data-id='"+ id +"' data-target='#emport' title='导出内部数据'><i class='fa fa-sign-out fa-rotate-270'></i>导出</a> " +
                     "<a class='btn btn-secondary btn-sm import-data' href='javacript:void(0);' data-id='"+ id +"' title='导入内部数据'><i class='fa fa-sign-in fa-rotate-90'></i>导入</a></td>" +
+                    "<td><a class='btn btn-secondary btn-sm transfer-excel' data-toggle='modal' data-target='#transfer' href='javacript:void(0);' data-id='"+ id +"' title='转换Excel'><i class='fa fa-sign-in fa-rotate-90'></i>转换</a></td>" +
                     "<td><a class='btn btn-secondary btn-sm set-comple' href='javacript:void(0);' data-id='"+ id +"' title='将章节树设为补充模板数据'><i class='fa fa-sign-in fa-rotate-90'></i>设置</a></td>" +
                     "</tr>");
             }

+ 35 - 2
web/maintain/ration_repository/main.html

@@ -30,12 +30,13 @@
 </div>
 <div class="main">
     <div class="content">
+        <div id="exportSpread" style="display: none"></div>
         <div class="container-fluid">
             <div class="row">
                 <div class="col-md-8">
                     <div class="warp-p2 mt-3">
                         <table class="table table-hover table-bordered">
-                            <thead><tr><th>定额库名称</th><th width="160">费用定额</th><th width="160">添加时间</th><th width="90">操作</th><th width="90">原始数据</th><th width="150">内部数据</th><th width="90">补充模板</th></tr></thead>
+                            <thead><tr><th>定额库名称</th><th width="160">费用定额</th><th width="160">添加时间</th><th width="90">操作</th><th width="90">原始数据</th><th width="150">内部数据</th><th width="100">转换Excel</th><th width="90">补充模板</th></tr></thead>
                             <tbody id="showArea">
                             </tbody>
                         </table>
@@ -229,6 +230,34 @@
         </div>
     </div>
 </div>
+<!--转换Excel-->
+<div class="modal fade" id="transfer" data-backdrop="static" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">转换Excel</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <!--<div class="alert alert-warning" role="alert">
+                    导入操作会覆盖数据,请谨慎操作!!
+                </div>-->
+                <form>
+                    <div class="form-group">
+                        <label>请选择Excel格式文件</label>
+                        <input id="transfer-file" class="form-control-file" type="file" accept=".xlsx,.xls"/>
+                    </div>
+                </form>
+            </div>
+            <div class="modal-footer">
+                <a href="javascript:void(0);" class="btn btn-primary" id="transferConfirm">确定转换</a>
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
 <!--弹出设置模板-->
 <div class="modal fade" id="template" data-backdrop="static" style="display: none;" aria-hidden="true">
     <div class="modal-dialog" role="document">
@@ -260,6 +289,10 @@
 <script src="/public/web/PerfectLoad.js"></script>
 <script src="/web/maintain/ration_repository/js/global.js"></script>
 <script src="/public/web/common_ajax.js"></script>
+<script src="/lib/spreadjs/sheets/gc.spread.sheets.all.11.1.2.min.js"></script>
+<script src="/lib/spreadjs/sheets/interop/gc.spread.excelio.11.1.2.min.js"></script>
+<script>GC.Spread.Sheets.LicenseKey =  '<%- LicenseKey %>';</script>
+<script src="/lib/fileSaver/FileSaver.min.js"></script>
 <!-- zTree -->
 <script type="text/javascript" src="/public/web/date_util.js"></script>
 <script type="text/javascript" src="/lib/ztree/jquery.ztree.core.js"></script>
@@ -271,4 +304,4 @@
     let userAccount = '<%=userAccount %>';
 </script>
 
-</html>
+</html>