Explorar o código

1. 变更令页面无法打开问题
2. 台账分解,导入Excel清单 1.0版本(暂不识别固定ID)

MaiXinRong %!s(int64=6) %!d(string=hai) anos
pai
achega
43f27789de

+ 12 - 12
app/controller/deal_bills_controller.js

@@ -18,16 +18,16 @@ module.exports = app => {
         /**
          * 获取标段的签约清单数据
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
-        async getData (ctx) {
+        async getData(ctx) {
             const responseData = {
                 err: 0,
                 msg: '',
                 data: [],
             };
             try {
-                responseData.data =  await ctx.service.dealBills.getAllDataByCondition({ where: {tender_id: ctx.tender.id} });
+                responseData.data = await ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });
             } catch (error) {
                 this.log(error);
                 responseData.err = 1;
@@ -40,9 +40,9 @@ module.exports = app => {
         /**
          * 导入Excel数据
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
-        async loadExcel (ctx) {
+        async loadExcel(ctx) {
             const responseData = {
                 err: 0,
                 msg: '',
@@ -51,7 +51,7 @@ module.exports = app => {
             let stream;
             try {
                 stream = await ctx.getFileStream();
-                const create_time = Date.parse(new Date())/1000;
+                const create_time = Date.parse(new Date()) / 1000;
                 const fileInfo = path.parse(stream.filename);
                 const fileName = this.app.baseDir + '/app/public/deal_bills/uploads/' + 'deal_bills' + create_time + fileInfo.ext;
                 // 保存文件
@@ -66,7 +66,7 @@ module.exports = app => {
                     throw '导入数据失败';
                 }
 
-                responseData.data =  await this.ctx.service.dealBills.getAllDataByCondition({ where: {tender_id: ctx.tender.id} });
+                responseData.data = await this.ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });
             } catch (err) {
                 this.log(err);
                 // 失败需要消耗掉stream 以防卡死
@@ -82,9 +82,9 @@ module.exports = app => {
         /**
          * 下载模板文件
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
-        async download (ctx) {
+        async download(ctx) {
             const file = ctx.params.file;
             if (file) {
                 try {
@@ -92,12 +92,12 @@ module.exports = app => {
                     if (file === 'template.xls') {
                         fileName = this.app.baseDir + '/app/public/deal_bills/deal_template.xls';
                     } else {
-                        const create_time = Date.parse(new Date())/1000;
+                        const create_time = Date.parse(new Date()) / 1000;
                         fileName = this.app.baseDir + '/app/public/deal_bills/downloads/' + ctx.tender.id + '-' + create_time + '.xlsx';
                         // todo 导出签约清单Excel
                     }
                     ctx.body = await fs.readFileSync(fileName);
-                } catch(err) {
+                } catch (err) {
                     this.log(err);
                 }
             }
@@ -105,4 +105,4 @@ module.exports = app => {
     }
 
     return DealBillsController;
-};
+};

+ 13 - 13
app/controller/ledger_audit_controller.js

@@ -31,7 +31,7 @@ module.exports = app => {
         _getSpreadSetting() {
             const _ = this.app._;
             function removeFieldCols(setting, cols) {
-                _.remove(setting.cols, function (c) {
+                _.remove(setting.cols, function(c) {
                     return cols.indexOf(c.field) > -1;
                 });
             }
@@ -53,7 +53,7 @@ module.exports = app => {
          * 台账审批页面(get)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async index(ctx) {
             try {
@@ -73,7 +73,7 @@ module.exports = app => {
                 const auditors = await ctx.service.ledgerAudit.getAuditors(ctx.tender.id, ctx.tender.data.ledger_times);
                 if (ctx.tender.data.user_id !== ctx.session.sessionUser.accountId) {
                     if (auditors.length > 0) {
-                        const auditor = this.app._.find(auditors, function (a) {return a.audit_id === ctx.session.sessionUser.accountId});
+                        const auditor = auditors.find(function(a) { return a.audit_id === ctx.session.sessionUser.accountId; });
                         if (!auditor) {
                             throw '您无权查看台账审批';
                         }
@@ -97,10 +97,10 @@ module.exports = app => {
                 renderData.posSpreadSetting = JSON.stringify(posSpread);
                 renderData.readOnly = true;
                 await this.layout('ledger/audit.ejs', renderData, 'ledger/audit_modal.ejs');
-            } catch(err) {
+            } catch (err) {
                 console.log(err);
                 this.log(err);
-                ctx.redirect('/tender/'+ ctx.tender.id);
+                ctx.redirect('/tender/' + ctx.tender.id);
             }
         }
 
@@ -108,7 +108,7 @@ module.exports = app => {
          * 新增审批人(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async add(ctx) {
             const responseData = {
@@ -141,7 +141,7 @@ module.exports = app => {
          * 移除审批人(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async remove(ctx) {
             const responseData = {
@@ -152,7 +152,7 @@ module.exports = app => {
             try {
                 const data = JSON.parse(ctx.request.body.data);
                 const id = data.auditorId instanceof Number ? data.auditorId : this.app._.toNumber(data.auditorId);
-                if ( isNaN(id) || id <= 0) {
+                if (isNaN(id) || id <= 0) {
                     throw '参数错误';
                 }
 
@@ -174,7 +174,7 @@ module.exports = app => {
          * 上报(post)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async start(ctx) {
             try {
@@ -199,12 +199,12 @@ module.exports = app => {
          * 审批(post)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async check(ctx) {
             try {
                 const tender = ctx.tender;
-                if (!tender.data || tender.data.ledger_status !== auditConst.status.checking ) {
+                if (!tender.data || tender.data.ledger_status !== auditConst.status.checking) {
                     throw '当前标段数据有误';
                 }
                 const curAudit = await ctx.service.ledgerAudit.getCurAuditor(tender.id, tender.data.ledger_times);
@@ -218,7 +218,7 @@ module.exports = app => {
 
                 await ctx.service.ledgerAudit.check(tender.id, checkType, ctx.request.body.opinion, tender.data.ledger_times);
 
-                ctx.redirect('/tender/'+ ctx.tender.id + '/ledger/explode');
+                ctx.redirect('/tender/' + ctx.tender.id + '/ledger/explode');
             } catch (err) {
                 this.log(err);
                 ctx.session.postError = err.toString();
@@ -228,4 +228,4 @@ module.exports = app => {
     }
 
     return LedgerAuditController;
-};
+};

+ 66 - 19
app/controller/ledger_controller.js

@@ -10,12 +10,14 @@
 
 const stdDataAddType = {
     self: 1,
-    withParent: 2
-}
+    withParent: 2,
+};
 const auditConst = require('../const/audit').flow;
 const tenderMenu = require('../../config/menu').tenderMenu;
 const measureType = require('../const/tender').measureType;
 const spreadConst = require('../const/spread');
+const fs = require('fs');
+const LzString = require('lz-string');
 
 module.exports = app => {
 
@@ -37,10 +39,10 @@ module.exports = app => {
         /**
          * 检查标段是否只读(审核中,审核完成)
          * @param {Object} tenderData
-         * @returns {boolean}
+         * @return {boolean}
          * @private
          */
-        _ledgerReadOnly () {
+        _ledgerReadOnly() {
             const tender = this.ctx.tender.data;
             return tender.ledger_status === auditConst.status.checking || tender.ledger_status === auditConst.status.checked;
         }
@@ -52,7 +54,7 @@ module.exports = app => {
         _getSpreadSetting() {
             const _ = this.app._;
             function removeFieldCols(setting, cols) {
-                _.remove(setting.cols, function (c) {
+                _.remove(setting.cols, function(c) {
                     return cols.indexOf(c.field) > -1;
                 });
             }
@@ -87,7 +89,7 @@ module.exports = app => {
                 const times = tender.data.ledger_status === auditConst.status.checkNo ? tender.data.ledger_times - 1 : tender.data.ledger_times;
                 const auditors = await ctx.service.ledgerAudit.getAuditors(tender.id, times);
                 const content = auditors.length > 0 ? await ctx.service.ledgerAuditContent.getAllDataByCondition({
-                    where: {tender_id: tender.id, times: times, audit_id: auditors[0].audit_id}
+                    where: { tender_id: tender.id, times, audit_id: auditors[0].audit_id },
                 }) : null;
                 const ledgerData = await ctx.service.ledger.getDataByTenderId(tender.id, -1);
                 const user = await ctx.service.projectAccount.getAccountInfoById(ctx.tender.data.user_id);
@@ -241,7 +243,6 @@ module.exports = app => {
                 }
                 const data = JSON.parse(ctx.request.body.data);
                 responseData.data = await ctx.service.ledger.updateCalc(ctx.tender.id, data);
-                console.log(responseData.data);
             } catch (err) {
                 this.log(err);
                 responseData.err = 1;
@@ -284,7 +285,7 @@ module.exports = app => {
         /**
          * 从标准项目表添加数据 (Ajax)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async addFromStandardLib(ctx) {
             const responseData = {
@@ -300,7 +301,7 @@ module.exports = app => {
                 if ((isNaN(data.id) || data.id <= 0) || !data.stdType || !data.stdNode) {
                     throw '参数错误';
                 }
-                //todo 校验项目是否使用该库的权限
+                // todo 校验项目是否使用该库的权限
 
                 let stdLib;
                 switch (data.stdType) {
@@ -342,7 +343,7 @@ module.exports = app => {
          * data.batchData.children = [{code, name, unit, unit_price, quantity}] -- 工程量清单列表
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async batchInsert(ctx) {
             const responseData = {
@@ -382,7 +383,7 @@ module.exports = app => {
          * 查询
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async search(ctx) {
             const responseData = {
@@ -416,7 +417,7 @@ module.exports = app => {
         /**
          * 定位
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async locate(ctx) {
             const responseData = {
@@ -447,7 +448,7 @@ module.exports = app => {
          * 获取全部子节点
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async posterity(ctx) {
             const responseData = {
@@ -479,7 +480,7 @@ module.exports = app => {
          * 获取部位明细数据(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async pos(ctx) {
             try {
@@ -488,10 +489,10 @@ module.exports = app => {
                 condition.tid = ctx.tender.id;
 
                 const posData = await ctx.service.pos.getPosData(condition);
-                ctx.body = {err: 0, msg: '', data: posData};
+                ctx.body = { err: 0, msg: '', data: posData };
             } catch (err) {
                 this.log(err);
-                ctx.body = {err: 1, msg: err.toString(), data: []};
+                ctx.body = { err: 1, msg: err.toString(), data: [] };
             }
         }
 
@@ -499,7 +500,7 @@ module.exports = app => {
          * 更新部位明细数据
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async posUpdate(ctx) {
             try {
@@ -507,13 +508,59 @@ module.exports = app => {
                 const data = JSON.parse(ctx.request.body.data);
                 const responseData = await ctx.service.pos.savePosData(data, ctx.tender.id);
                 ctx.body = { err: 0, msg: '', data: responseData };
-            } catch(err) {
+            } catch (err) {
                 this.log(err);
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
 
         /**
+         * 上传 清单Excel 并导入
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async uploadExcel(ctx) {
+            try {
+                const compressData = ctx.request.body.data;
+                const data = JSON.parse(LzString.decompressFromUTF16(compressData));
+                const responseData = { err: 0, msg: '', data: {}, };
+                await ctx.service.ledger.importExcel(data);
+                responseData.data.bills = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
+                responseData.data.pos = await ctx.service.pos.getPosData(null);
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = { err: 1, msg: err.toString(), data: null };
+            }
+
+        }
+
+        /**
+         * 下载(清单Excel模板 or 导出项目台账Excel)
+         * @param ctx
+         * @return {Promise<void>}
+         */
+        async download(ctx) {
+            const file = ctx.params.file;
+            if (file) {
+                try {
+                    let fileName;
+                    if (file === 'template') {
+                        fileName = this.app.baseDir + '/app/public/files/template/ledger/导入分项清单Excel格式.xls';
+                    } else if (file === 'ledger') {
+                        const create_time = Date.parse(new Date()) / 1000;
+                        fileName = this.app.baseDir + '/app/public/files/downloads/ledger/' + ctx.tender.id + '-' + create_time + '.xlsx';
+                        // todo 导出台账清单Excel
+                    }
+                    ctx.body = await fs.readFileSync(fileName);
+                } catch (err) {
+                    this.log(err);
+                    this.setMessage(err.toString(), this.messageType.ERROR);
+                }
+            }
+        }
+
+        /**
          * 台账变更页面 (Get)
          *
          * @param {object} ctx - egg全局变量
@@ -527,7 +574,7 @@ module.exports = app => {
                     preUrl: '/tender/' + ctx.tender.id,
                 };
                 await this.layout('ledger/change.ejs', renderData, 'ledger/change_modal.ejs');
-            } catch(err) {
+            } catch (err) {
                 this.log(err);
                 ctx.redirect(ctx.request.header.referer);
             }

+ 37 - 37
app/controller/setting_controller.js

@@ -49,21 +49,21 @@ module.exports = app => {
                 const rule = ctx.service.project.rule('saveInfo');
                 const jsValidator = await this.jsValidator.convert(rule).build();
                 const officeName = officeList[salesmanData.office];
-                var date = new Date(projectData.create_time*1000);//如果date为10位不需要乘1000
-                var Y = date.getFullYear() + '-';
-                var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
-                var D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' ';
-                var h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
-                var m = (date.getMinutes() <10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
-                var s = (date.getSeconds() <10 ? '0' + date.getSeconds() : date.getSeconds());
-                const dateStr = Y+M+D+h+m+s;
+                const date = new Date(projectData.create_time * 1000);// 如果date为10位不需要乘1000
+                const Y = date.getFullYear() + '-';
+                const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
+                const D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' ';
+                const h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
+                const m = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
+                const s = (date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds());
+                const dateStr = Y + M + D + h + m + s;
                 const renderData = {
                     projectData,
                     salesmanData,
                     officeName,
                     officeList,
                     jsValidator,
-                    dateStr
+                    dateStr,
                 };
                 await this.layout('setting/info.ejs', renderData);
             } catch (error) {
@@ -75,7 +75,7 @@ module.exports = app => {
         /**
          * 项目设置 -- 账号设置(Get)
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async user(ctx) {
             try {
@@ -100,7 +100,7 @@ module.exports = app => {
          * 项目设置 -- 自定义标段分类(Get)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async category(ctx) {
             try {
@@ -130,7 +130,7 @@ module.exports = app => {
          * 新增分类(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async addCategory(ctx) {
             try {
@@ -148,7 +148,7 @@ module.exports = app => {
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
-                ctx.body = {err: 1, msg: err.toString(), data: null};
+                ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
 
@@ -156,25 +156,25 @@ module.exports = app => {
          * 编辑分类(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async updateCategory(ctx) {
             try {
                 const projectId = ctx.session.sessionProject.id;
-                const responseData = { err: 0, msg: '', data: null, };
+                const responseData = { err: 0, msg: '', data: null };
 
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data.id) {
                     throw '提交数据错误';
                 }
                 if (data.name) {
-                    const count = await ctx.service.category.count({pid: projectId, name: data.name});
+                    const count = await ctx.service.category.count({ pid: projectId, name: data.name });
                     if (count >= 1) {
                         throw '存在同名类别';
                     }
                 }
 
-                const result = await ctx.service.category.update(data, {id: data.id});
+                const result = await ctx.service.category.update(data, { id: data.id });
                 if (!result) {
                     throw '提交数据失败';
                 }
@@ -184,13 +184,13 @@ module.exports = app => {
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
-                ctx.body = {err: 1, msg: err.toString(), data: null};
+                ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
 
         async setCategoryValue(ctx) {
             try {
-                const responseData = { err: 0, msg: '', data: {}, };
+                const responseData = { err: 0, msg: '', data: {} };
 
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data.id) {
@@ -204,7 +204,7 @@ module.exports = app => {
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
-                ctx.body = {err: 1, msg: err instanceof String ? err : '提交数据失败', data: null};
+                ctx.body = { err: 1, msg: err instanceof String ? err : '提交数据失败', data: null };
             }
         }
 
@@ -212,7 +212,7 @@ module.exports = app => {
          * 删除分类(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async deleteCategory(ctx) {
             try {
@@ -229,9 +229,9 @@ module.exports = app => {
                 await ctx.service.category.deleteCategory(projectId, data.id);
 
                 ctx.body = responseData;
-            } catch(err) {
+            } catch (err) {
                 this.log(err);
-                ctx.body = {err: 1, msg: err.toString(), data: null};
+                ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
 
@@ -239,14 +239,14 @@ module.exports = app => {
          * 调整分类层次排序(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async resetCategoryLevel(ctx) {
             try {
                 const projectId = ctx.session.sessionProject.id;
                 const responseData = {
                     err: 0, msg: '', data: null,
-                }
+                };
                 const data = JSON.parse(ctx.request.body.data);
 
                 await ctx.service.category.resetCategoryLevel(data);
@@ -254,7 +254,7 @@ module.exports = app => {
                 ctx.body = responseData;
             } catch (err) {
                 this.log(err);
-                ctx.body = {err: 1, msg: err.toString(), data: null};
+                ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
 
@@ -262,30 +262,30 @@ module.exports = app => {
          * @author wangfeng
          * @date 2018-10-12 15:48:05
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
-        async updateinfo(ctx){
+        async updateinfo(ctx) {
             try {
                 const projectId = ctx.params.id;
                 const responseData = {
                     err: 0, msg: '', data: null,
-                }
+                };
                 const conditionData = {
-                    id:projectId
+                    id: projectId,
+                };
+                const data = ctx.request.body;
+                const result = await ctx.service.project.update(data, conditionData);
+                if (!result) {
+                    throw '提交数据失败';
                 }
-                 const data = ctx.request.body;
-                 const result = await ctx.service.project.update(data, conditionData);
-                 if (!result) {
-                     throw '提交数据失败'
-                 }
 
                 ctx.redirect('/setting/info');
             } catch (err) {
                 this.log(err);
-                ctx.body = {err: 1, msg: err.toString(), data: null};
+                ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
     }
 
     return SettingController;
-};
+};

+ 19 - 19
app/controller/tender_controller.js

@@ -43,7 +43,7 @@ module.exports = app => {
                     measureType: tenderConst.measureType,
                 };
                 await this.layout(view, renderData, modal);
-            } catch(err) {
+            } catch (err) {
                 this.log(err);
                 this.ctx.redirect('/dashboard');
             }
@@ -63,7 +63,7 @@ module.exports = app => {
          * 计量进度(Get)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async listProgress(ctx) {
             await this._list('tender/progress.ejs', tenderConst.progressTableCol);
@@ -73,7 +73,7 @@ module.exports = app => {
          * 标段管理(Get)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async listManage(ctx) {
             await this._list('tender/manage.ejs', tenderConst.manageTableCol, 'tender/manage_modal.ejs');
@@ -83,7 +83,7 @@ module.exports = app => {
          * 新增标段(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async addTender(ctx) {
             try {
@@ -100,7 +100,7 @@ module.exports = app => {
                 ctx.body = responseData;
             } catch (error) {
                 this.log(error);
-                ctx.body = {err: 1, msg: error.toString(), data: null};
+                ctx.body = { err: 1, msg: error.toString(), data: null };
             }
         }
 
@@ -108,7 +108,7 @@ module.exports = app => {
          * 编辑标段(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async updateTender(ctx) {
 
@@ -118,7 +118,7 @@ module.exports = app => {
          * 删除标段(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async deleteTender(ctx) {
 
@@ -128,7 +128,7 @@ module.exports = app => {
          * 标段概况(Get)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async tenderInfo(ctx) {
             try {
@@ -140,7 +140,6 @@ module.exports = app => {
                 };
                 await this.layout('tender/detail.ejs', renderData);
             } catch (error) {
-                console.log(error);
                 this.log(error);
                 this.ctx.redirect('/list');
             }
@@ -150,7 +149,7 @@ module.exports = app => {
          * 保存标段属性等(Ajax)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async saveTenderInfo(ctx) {
             try {
@@ -159,10 +158,10 @@ module.exports = app => {
                     throw '提交数据错误';
                 }
                 await ctx.service.tenderInfo.saveTenderInfo(ctx.tender.id, data);
-                ctx.body = {err: 0, msg: '', data: data};
-            } catch(err) {
+                ctx.body = { err: 0, msg: '', data };
+            } catch (err) {
                 this.log(err);
-                ctx.body = {err: 1, msg: err.toString(), data: null};
+                ctx.body = { err: 1, msg: err.toString(), data: null };
             }
         }
 
@@ -170,11 +169,12 @@ module.exports = app => {
          * 设置标段计量类型并调整到标段概况(Get)
          *
          * @param ctx
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async tenderType(ctx) {
             try {
-                const tenderId = ctx.params.id, type = ctx.params.type;
+                const tenderId = ctx.params.id,
+                    type = ctx.query.type;
                 if (!tenderId) {
                     throw '当前未打开标段';
                 }
@@ -183,10 +183,10 @@ module.exports = app => {
                     throw '标段数据错误';
                 }
                 if (!tender.measure_type) {
-                    await ctx.service.tender.update({measure_type: type}, {id: tender.id});
+                    await ctx.service.tender.update({ measure_type: type }, { id: tender.id });
                 }
                 ctx.redirect('/tender/' + tenderId);
-            } catch(error) {
+            } catch (error) {
                 this.log(error);
                 ctx.redirect('/list');
             }
@@ -224,8 +224,8 @@ module.exports = app => {
         async switchTender(ctx) {
             let tenderId = ctx.params.tenderId;
             tenderId = parseInt(tenderId);
-            try{
-                if(isNaN(tenderId) || tenderId <= 0) {
+            try {
+                if (isNaN(tenderId) || tenderId <= 0) {
                     throw '参数错误';
                 }
                 const result = await ctx.service.tender.switchTender(tenderId);

+ 331 - 0
app/lib/analysis_excel.js

@@ -0,0 +1,331 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+const _ = require('lodash');
+
+class ImportBaseTree {
+    /**
+     * 构造函数
+     * @param {Array} tempData - 清单模板数据
+     */
+    constructor (tempData) {
+        // 常量
+        this.splitChar = '-';
+        // 索引
+        // 以code为索引
+        this.codeNodes = {};
+        this.items = [];
+        this.roots = [];
+        this.pos = [];
+        this.tempData = [];
+
+        // 缓存
+        this.finalNode = null;
+        this.finalXmjNode = null;
+        this.keyNodeId = 1;
+
+        this._loadTemplateTree(tempData);
+    }
+
+    /**
+     * 加载 清单模板
+     * @param {Array} data - 模板数据
+     * @private
+     */
+    _loadTemplateTree(data) {
+        for (const node of data) {
+            node.ledger_id = node.id;
+            node.ledger_pid = node.pid;
+            delete node.id;
+            delete node.pid;
+            if (node.code) {
+                this.codeNodes[node.code] = node;
+            }
+            this.items.push(node);
+            if (node.ledger_pid === -1) {
+                this.roots.push(node);
+            }
+            if (node.ledger_id >= this.keyNodeId) {
+                this.keyNodeId = node.ledger_id + 1;
+            }
+            this.tempData.push(node);
+        }
+        for (const node of this.items) {
+            node.children = this.items.filter(function (i) {
+                return i.ledger_pid === node.ledger_id;
+            });
+        }
+    }
+
+    /**
+     * 根据 编号、名称 查找模板节点
+     * @param {Object} node - 要查找的节点
+     * @returns {*}
+     */
+    findTempData(node) {
+        return this.tempData.find(function (td) {
+            return td.code === node.code && td.name === node.name;
+        });
+    }
+
+    /**
+     * 根据 编号 查找 父项项目节
+     * @param {String} code - 子项编号
+     * @returns {*}
+     */
+    findXmjParent(code) {
+        const codePath = code.split(this.splitChar);
+        if (codePath.length > 1) {
+            codePath.splice(codePath.length - 1, 1);
+            return this.codeNodes[codePath.join(this.splitChar)];
+        }
+    }
+
+    /**
+     * 添加 树节点 并完善该节点的树结构
+     * @param {Object} node - 添加节点
+     * @param {Object} parent - 父项
+     * @returns {*}
+     */
+    addNodeWithParent(node, parent) {
+        node.ledger_id = this.keyNodeId;
+        this.keyNodeId += 1;
+        node.ledger_pid = parent ? parent.ledger_id : -1;
+        node.level = parent ? parent.level + 1 : 1;
+        node.order = parent ? parent.children.length + 1 : this.roots.length + 1;
+        node.full_path = parent ? parent.full_path + '.' + node.ledger_id : '' + node.ledger_id;
+        if (parent) {
+            parent.children.push(node);
+        } else {
+            this.roots.push(node);
+        }
+        this.items.push(node);
+        this.codeNodes[node.code] = node;
+        this.defineCacheData(node);
+        return node;
+    }
+
+    /**
+     * 定义缓存节点(添加工程量清单、部位明细需使用缓存定位)
+     * @param {Object} node - 当前添加的节点
+     */
+    defineCacheData(node) {
+        this.finalNode = node;
+        if (node.code) {
+            this.finalXmjNode = node;
+        }
+    }
+
+    /**
+     * 添加 项目节
+     * @param {Object} node - 项目节
+     * @returns {*}
+     */
+    addXmjNode(node) {
+        node.children = [];
+        if (node.code.split(this.splitChar).length > 1) {
+            const temp = this.findTempData(node);
+            if (temp) {
+                this.defineCacheData(temp);
+                return temp;
+            } else {
+                const parent = this.findXmjParent(node.code);
+                return this.addNodeWithParent(node, parent);
+            }
+        } else {
+            const n = this.codeNodes[node.code];
+            if (!n) {
+                return this.addNodeWithParent(node, null);
+            } else {
+                this.defineCacheData(n);
+                return n;
+            }
+        }
+    }
+
+    /**
+     * 添加 工程量清单
+     * @param {Object} node - 工程量清单
+     */
+    addGclNode(node) {
+        node.pos = [];
+        if (this.finalXmjNode) {
+            return this.addNodeWithParent(node, this.finalXmjNode);
+        }
+    }
+
+    /**
+     * 添加 部位明细
+     * @param {object} pos - 部位明细
+     * @returns {*}
+     */
+    addPos (pos){
+        if (this.finalNode && this.finalNode.pos) {
+            this.finalNode.pos.push(pos);
+            this.pos.push(pos);
+            return pos;
+        }
+    }
+}
+
+class AnalysisExcelTree {
+    /**
+     * 构造函数
+     */
+    constructor() {
+        this.colsDef = null;
+        this.colHeaderMatch = {
+            code: ['项目节编号', '预算项目节'],
+            b_code: ['清单子目号', '清单编号', '子目号'],
+            pos: ['部位明细'],
+            name: ['名称'],
+            unit: ['单位'],
+            quantity: ['清单数量'], // 施工图复核数量
+            dgn_qty1: ['设计数量1'],
+            dgn_qty2: ['设计数量2'],
+            unit_price: ['单价'],
+            drawing_code: ['图号'],
+            memo: ['备注'],
+        };
+    }
+
+    /**
+     * 读取项目节节点
+     * @param {Array} row - excel行数据
+     * @returns {*}
+     * @private
+     */
+    _loadXmjNode(row) {
+        const node = {};
+        node.code = row[this.colsDef.code];
+        node.name = row[this.colsDef.name];
+        node.unit = row[this.colsDef.unit];
+        node.quantity = row[this.colsDef.quantity];
+        node.dgn_qty1 = row[this.colsDef.dgn_qty1];
+        node.dgn_qty2 = row[this.colsDef.dgn_qty2];
+        node.unit_price = row[this.colsDef.unit_price];
+        node.drawing_code = row[this.colsDef.drawing_code];
+        node.memo = row[this.colsDef.memo];
+        if (node.quantity && node.unit_price) {
+            node.total_price = node.quantity * node.unit_price;
+        } else {
+            node.total_price = null;
+        }
+        return this.cacheTree.addXmjNode(node);
+    }
+    /**
+     * 读取工程量清单数据
+     * @param {Array} row - excel行数据
+     * @returns {*}
+     * @private
+     */
+    _loadGclNode(row) {
+        const node = {};
+        node.b_code = row[this.colsDef.b_code];
+        node.name = row[this.colsDef.name];
+        node.unit = row[this.colsDef.unit];
+        node.quantity = row[this.colsDef.quantity];
+        node.unit_price = row[this.colsDef.unit_price];
+        node.drawing_code = row[this.colsDef.drawing_code];
+        node.memo = row[this.colsDef.memo];
+        if (node.quantity && node.unit_price) {
+            node.total_price = node.quantity * node.unit_price;
+        } else {
+            node.total_price = null;
+        }
+        return this.cacheTree.addGclNode(node);
+    }
+    /**
+     * 读取部位明细数据
+     * @param {Array} row - excel行数据
+     * @returns {*}
+     * @private
+     */
+    _loadPos(row) {
+        const pos = {};
+        pos.name = row[this.colsDef.name];
+        pos.quantity = row[this.colsDef.quantity];
+        pos.drawing_code = row[this.colsDef.drawing_code];
+        return this.cacheTree.addPos(pos);
+    }
+
+    /**
+     * 读取数据行
+     * @param {Array} row - excel数据行
+     * @param {Number} index - 行索引号
+     */
+    loadRowData(row, index) {
+        let result;
+        // 含code识别为项目节,含posCode识别为部位明细,其他识别为工程量清单
+        if (row[this.colsDef.code]) {
+            // 第三部分(编号为'3')及之后的数据不读取
+            if (row[this.colsDef.code] === '3') {
+                this.loadEnd = true;
+                return;
+            }
+            result = this._loadXmjNode(row)
+        } else if (row[this.colsDef.pos]) {
+            result = this._loadPos(row);
+        } else {
+            result = this._loadGclNode(row);
+        }
+        // 读取失败则写入错误数据 todo 返回前端告知用户?
+        if (!result) {
+            this.errorData.push({
+                serialNo: index,
+                data: row,
+            });
+        }
+    }
+
+    /**
+     * 读取表头并检查
+     * @param {Number} row - Excel数据行
+     */
+    checkColHeader(row) {
+        const colsDef = {};
+        for (const iCol in row) {
+            const text = row[iCol];
+            for (const head in this.colHeaderMatch) {
+                const match = this.colHeaderMatch[head];
+                if (match.indexOf(text) >= 0) {
+                    colsDef[head] = iCol;
+                }
+            }
+        }
+        if (colsDef.code && colsDef.b_code && colsDef.pos) {
+            this.colsDef = colsDef;
+        }
+    }
+
+    /**
+     * 将excel清单 平面数据 解析为 树结构数据
+     * @param {object} sheet - excel清单数据
+     * @param {Array} tempData - 新建项目使用的清单模板
+     * @returns {ImportBaseTree}
+     */
+    analysisData(sheet, tempData) {
+        this.colsDef = null;
+        this.cacheTree = new ImportBaseTree(tempData);
+        this.errorData = [];
+        this.loadEnd = false;
+
+        for (const iRow in sheet.rows) {
+            const row = sheet.rows[iRow];
+            if (this.colsDef && !this.loadEnd) {
+                this.loadRowData(row, iRow);
+            } else {
+                this.checkColHeader(row);
+            }
+        }
+        return this.cacheTree;
+    }
+}
+
+module.exports = AnalysisExcelTree;

+ 2 - 2
app/middleware/auto_logger.js

@@ -16,9 +16,9 @@ module.exports = options => {
                 return ctx.getLogger('ledger');
             } else if (ctx.url.match(/stage/)) {
                 return ctx.getLogger('stage');
-            } else {
-                return ctx.getLogger('mixed');
             }
+            return ctx.getLogger('mixed');
+
         }
         if (this.session.sessionUser) {
             const bLogger = getBussinessLogger(this);

BIN=BIN
app/public/files/template/ledger/common.xls


BIN=BIN
app/public/files/template/ledger/导入分项清单EXCEL格式.xls


+ 40 - 0
app/public/js/global.js

@@ -146,6 +146,46 @@ const postData = function (url, data, successCallback, errorCallBack) {
 };
 
 /**
+ * 动态请求数据(压缩数据)
+ * @param {String} url - 请求链接
+ * @param data - 提交数据
+ * @param {function} successCallback - 返回成功回调
+ * @param {function} errorCallBack - 返回失败回调
+ */
+const postDataCompress = function (url, data, successCallback, errorCallBack) {
+    $.ajax({
+        type:"POST",
+        url: url,
+        data: {'data': LZString.compressToUTF16(JSON.stringify(data))},
+        dataType: 'json',
+        cache: false,
+        timeout: 5000,
+        beforeSend: function(xhr) {
+            let csrfToken = Cookies.get('csrfToken');
+            xhr.setRequestHeader('x-csrf-token', csrfToken);
+        },
+        success: function(result){
+            if (result.err === 0) {
+                if (successCallback) {
+                    successCallback(result.data);
+                }
+            } else {
+                toast('error: ' + result.msg, 'error', 'exclamation-circle');
+                if (errorCallBack) {
+                    errorCallBack(result.msg);
+                }
+            }
+        },
+        error: function(jqXHR, textStatus, errorThrown){
+            toast('error ' + textStatus + " " + errorThrown, 'error', 'exclamation-circle');
+            if (errorCallBack) {
+                errorCallBack();
+            }
+        }
+    });
+};
+
+/**
  * 动态请求数据
  * @param {String} url - 请求链接
  * @param data - 提交数据

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1505 - 0
app/public/js/js-xlsx/cpexcel.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 8999 - 0
app/public/js/js-xlsx/jszip.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 2 - 0
app/public/js/js-xlsx/shim.min.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 17 - 0
app/public/js/js-xlsx/xlsx.core.min.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 30144 - 0
app/public/js/js-xlsx/xlsx.extendscript.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 23 - 0
app/public/js/js-xlsx/xlsx.full.min.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 20988 - 0
app/public/js/js-xlsx/xlsx.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 14 - 0
app/public/js/js-xlsx/xlsx.min.js


+ 144 - 0
app/public/js/js-xlsx/xlsx.utils.js

@@ -0,0 +1,144 @@
+var xlsxUtils = {
+    Binary: {
+        fixdata(data) { //文件流转BinaryString
+            var o = "",
+                l = 0,
+                w = 10240;
+            for (; l < data.byteLength / w; ++l) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w)));
+            o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w)));
+            return o;
+        },
+        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;
+        }
+    },
+    _wb: null,
+    _rABS: false,
+    /**
+     * @desc  导入根据文件
+     * @param {File} f 文件
+     * @param {Function} c 回调
+     * @return {Object} 回调值
+     */
+    import(f, c) {
+        this.wb = null;
+        var reader = new FileReader();
+        reader.onload = function (e) {
+            var data = e.target.result;
+            xlsxUtils._wb = xlsxUtils._rABS ? XLSX.read(btoa(xlsxUtils.Binary.fixdata(data)), { type: 'base64' }) : XLSX.read(data, { type: 'binary' });
+            if (typeof c == "function") { c(xlsxUtils._wb); }
+        };
+        if (xlsxUtils._rABS) {
+            reader.readAsArrayBuffer(f);
+        } else {
+            reader.readAsBinaryString(f);
+        }
+    },
+    /**
+     * @desc  根据表Sheet名获取数据
+     * @param {String} name
+     * @return {Object}
+     */
+    getSheetByName(name, opt) {//
+        return XLSX.utils.sheet_to_json(xlsxUtils._wb.Sheets[name], opt);
+    },
+    /**
+     * @desc  根据表Sheet索引获取数据
+     * @param {Number} index
+     * @return {Object}
+     */
+    getSheetByIndex(index = 0, opt) {
+        return xlsxUtils.getSheetByName(xlsxUtils._wb.SheetNames[index], opt);
+    },
+    /**
+     * @desc 导出
+     * @param {Array} data 数据{title1:dataList,title2:dataList....}
+     * @param {String} type
+     * @return {Blob}
+     */
+    export(data, type) {
+        var tmpWB = null;
+        for (var title in data) {
+            var tmpdata = xlsxUtils.format2Sheet(data[title]);
+            tmpWB = xlsxUtils.format2WB(tmpdata, title, tmpWB);
+        }
+        return xlsxUtils.format2Blob(tmpWB, type);
+    },
+    /**
+     * 从数据数组或对象中根据key生成相同key值的对象
+     * @param {Object|Array} data
+     * @return {Object}
+     */
+    readDataHead(data) {
+        var o = {}, d = Array.isArray(data) ? Object.keys(data[0]) : data; for (var i of d) o[i] = i;
+        return o;
+    },
+    /**
+     * @desc 格式化数据为Sheet格式
+     * @param {Array} json 数据
+     * @param {Number} n 列偏移
+     * @param {Number} r 行偏移
+     * @param {Array} keyMap 对象键数组
+     * @param {Function|Boolean} t 数据
+     */
+    format2Sheet(json, n, r, keyMap, t) {
+        keyMap = keyMap || Object.keys(json[0]);
+        var types = (t == undefined ? ((v) => (({ "number": "n", undefined: "s", "boolean": "b","string":"s" })[typeof v])||"s") : t);
+        n = n || 0;
+        r = r || 0;
+        var tmpdata = {};//用来保存转换好的json
+        var t1 = json.map((v, i) => keyMap.map((k, j) => Object.assign({}, {
+            v: v[k],
+            position: ((j + n) > 25 ? xlsxUtils.getCharCol((j + n)) : String.fromCharCode(65 + (j + n))) + (i + 1 + r),
+        }))).reduce((prev, next) => prev.concat(next)).forEach((v, i) => tmpdata[v.position] = {
+            v: v.v,
+            t: types?types(v.v):"s"
+        });
+        return tmpdata;
+    },
+    /**
+     * @desc 格式化数据为Sheet格式
+     * @param {Array} sheetData
+     * @param {String} title
+     * @param {Object} wb
+     * @param {Object} ref
+     */
+    format2WB(sheetData, title, wb, ref) {
+        title = title || "mySheet";
+        var outputPos = Object.keys(sheetData);
+        if (!wb) wb = { Sheets: {}, SheetNames: [] };
+        wb.SheetNames.push(title);
+        wb.Sheets[title] = Object.assign({}, sheetData, {
+            '!ref': ref || (outputPos[0] + ':' + outputPos.reverse().find(_=>_.indexOf("!")==-1))//设置填充区域
+        });
+        return wb;
+    },
+    /**
+     * @desc 将xlsx Workbook 转为blob
+     * @param {Array} wb
+     * @param {String} type 类型
+     */
+    format2Blob(wb, type) {
+        return new Blob([xlsxUtils.Binary.s2ab(XLSX.write(wb,
+            { bookType: (type == undefined ? 'xlsx' : type), bookSST: false, type: 'binary' }//这里的数据是用来定义导出的格式类型
+        ))], { type: "" });
+    },
+    /**
+     * @desc 匹配单元格对应的标识
+     * @param {Number} n
+     */
+    getCharCol(n) {
+        let temCol = '',
+            s = '',
+            m = 0
+        while (n > 0) {
+            m = n % 26 + 1
+            s = String.fromCharCode(m + 64) + s
+            n = (n - m) / 26
+        }
+        return s
+    },
+};

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
app/public/js/js-xlsx/xlsx.utils.min.js


+ 61 - 5
app/public/js/ledger.js

@@ -30,7 +30,7 @@ $(document).ready(function() {
     if (checkTzMeasureType()) {
         treeSetting.calcFields = ['total_price'];
     } else {
-        treeSetting.calcFields = ['deal_tp'];
+        treeSetting.calcFields = ['deal_tp', 'total_price'];
     }
     treeSetting.calcFun = function (node) {
         if (checkZero(node.dgn_qty1)) {
@@ -46,7 +46,6 @@ $(document).ready(function() {
     // 加载台账数据到界面
     SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
 
-
     // 初始化 部位明细
     const pos = new PosData({
         id: 'id', masterId: 'lid',
@@ -554,6 +553,16 @@ $(document).ready(function() {
                         }
                         $('#batch').modal('show');
                     }
+                },
+                'importExcel': {
+                    name: '导入分项清单Excel',
+                    icon: 'file-excel-o',
+                    disabled: function (key, opt) {
+                        return tender.ledger_status !== 1;
+                    },
+                    callback: function (key, opt) {
+                        $('#upload-ledger').modal('show');
+                    }
                 }
             }
         });
@@ -777,9 +786,6 @@ $(document).ready(function() {
         SpreadJsObj.addDeleteBind(posSpread, posOperationObj.deletePress);
         posSpread.bind(GC.Spread.Sheets.Events.EditEnding, posOperationObj.editEnding);
         posSpread.bind(GC.Spread.Sheets.Events.ClipboardPasted, posOperationObj.clipboardPasted);
-        // posSpread.bind(GC.Spread.Sheets.Events.CellClick, function (e, info) {
-        //     console.log(info.sheet.getCell(info.row, info.col));
-        // });
         // 右键菜单
         $.contextMenu({
             selector: '#pos-spread',
@@ -1228,5 +1234,55 @@ $(document).ready(function() {
             }
         });
     });
+    // 选择excel文件后,加载全部sheet
+    $('#upload-ledger-file').change(function () {
+        if (this.files.length === 0) {
+            $('#upload-ledger-sheets').html('').hide();
+            return;
+        }$('#upload-ledger-sheets').hide();
+        try {
+            xlsxUtils.import(this.files[0], (excelData) => {
+                console.log(excelData);
+                if (excelData.SheetNames.length > 0) {
+                    const html = [];
+                    html.push('<label for="exampleFormControlFile1">勾选要导入的工作簿</label>');
+                    html.push('<div class="form-control">')
+                    for (const iName in excelData.SheetNames) {
+                        const name = excelData.SheetNames[iName];
+                        html.push('<div class="form-group form-check">');
+                        html.push('<input type="radio" class="form-check-input" name="sheetName"', (iName == 0 ? ' checked=""' : ''), ' value="' + name + '"',  '>');
+                        html.push('<label class="form-check-label" for="ledger-dgn-qty">', name, '</label>');
+                        html.push('</div>');
+                    }
+                    html.push('</div>');
+                    $('#upload-ledger-sheets').html(html.join('')).show();
+                } else {
+                    toast('选择的Excel无有效数据,请重新选择', 'hint');
+                    $('#upload-ledger-sheets').hide();
+                }
+            });
+        } catch(err) {
+            toast('加载excel异常,请刷新当前页面', 'error');
+            $('#upload-ledger-sheets').hide();
+        }
+    });
+    // 上传excel内容,并导入
+    $('#upload-ledger-ok').click(function () {
+        const sheetName = $('input[name=sheetName]:checked').val();
+        if (sheetName) {
+            const sheet = {
+                rows: xlsxUtils.getSheetByName(sheetName, {header: 1}),
+                merge: xlsxUtils._wb.Sheets[sheetName]["!merges"]
+            };
+            postDataCompress(window.location.pathname + '/upload-excel', sheet, function (result) {
+                ledgerTree.loadDatas(result.bills);
+                treeCalc.calculateAll(ledgerTree);
+                SpreadJsObj.loadSheetData(ledgerSpread.getActiveSheet(), 'tree', ledgerTree);
+                pos.loadDatas(result.pos);
+                posOperationObj.loadCurPosData();
+                $('#upload-ledger').modal('hide');
+            });
+        }
+    });
 });
 

+ 8 - 3
app/public/js/path_tree.js

@@ -163,7 +163,7 @@ const createNewPathTree = function (type, setting) {
             // 以排序为索引
             this.nodes = [];
             // 根节点
-            this.children = {};
+            this.children = [];
             // 树设置
             this.setting = setting;
         }
@@ -184,6 +184,7 @@ const createNewPathTree = function (type, setting) {
                 }
             };
             self.nodes = [];
+            this.children = this.getChildren(null);
             addSortNodes(this.getChildren(null));
         }
         /**
@@ -194,11 +195,15 @@ const createNewPathTree = function (type, setting) {
             // 清空旧数据
             this.items = {};
             this.nodes = [];
+            this.datas = [];
+            this.children = [];
             // 加载全部数据
             for (const data of datas) {
                 const keyName = itemsPre + data[this.setting.id];
-                this.items[keyName] = JSON.parse(JSON.stringify(data));
-                this.datas.push(this.items[keyName]);
+                if (!this.items[keyName]) {
+                    this.items[keyName] = JSON.parse(JSON.stringify(data));
+                    this.datas.push(this.items[keyName]);
+                }
             }
             this.sortTreeNode();
             for (const node of this.nodes) {

+ 21 - 0
app/public/js/spreadjs_rela/import_excel.js

@@ -0,0 +1,21 @@
+/**
+ * 导入Excel相关js
+ *
+ * @author Mai
+ * @date 2019/01/04
+ * @version
+ */
+'use strict';
+
+class ImportExcel {
+    constructor (setting) {
+        this.setting = setting;
+    }
+
+    analysisExcel(file) {
+        const excelIo = new GC.Spread.Excel.IO();
+        excelIo.open(file, function () {
+
+        });
+    }
+}

+ 1 - 1
app/public/js/tender_list.js

@@ -289,7 +289,7 @@ function bindTenderUrl() {
             window.location.href = '/tender/' + tenderId;
         } else {
             for (const a of $('a', '#jlms')) {
-                a.href = '/tender/' + tenderId + '/' + $(a).attr('mst');
+                a.href = '/tender/' + tenderId + '/type?type=' + $(a).attr('mst');
             }
             $('#jlms').modal('show');
         }

+ 11 - 9
app/router.js

@@ -64,7 +64,7 @@ module.exports = app => {
     app.post('/list/del', sessionAuth, 'tenderController.deleteTender');
     // 标段概况
     app.get('/tender/:id', sessionAuth, tenderCheck, 'tenderController.tenderInfo');
-    app.get('/tender/:id/:type', sessionAuth, 'tenderController.tenderType');
+    app.get('/tender/:id/type', sessionAuth, 'tenderController.tenderType');
     app.post('/tender/:id/save', sessionAuth, tenderCheck, 'tenderController.saveTenderInfo');
     app.post('/tender/rule', sessionAuth, 'tenderController.rule');
 
@@ -78,6 +78,8 @@ module.exports = app => {
     app.post('/tender/:id/ledger/paste-block', sessionAuth, tenderCheck, 'ledgerController.pasteBlock');
     app.post('/tender/:id/ledger/add-by-std', sessionAuth, tenderCheck, 'ledgerController.addFromStandardLib');
     app.post('/tender/:id/ledger/batch-insert', sessionAuth, tenderCheck, 'ledgerController.batchInsert');
+    app.post('/tender/:id/ledger/explode/upload-excel', sessionAuth, tenderCheck, 'ledgerController.uploadExcel');
+    app.get('/tender/:id/ledger/download/:file', sessionAuth, tenderCheck, 'ledgerController.download');
     app.post('/tender/:id/pos', sessionAuth, tenderCheck, 'ledgerController.pos');
     app.post('/tender/:id/pos/update', sessionAuth, tenderCheck, 'ledgerController.posUpdate');
 
@@ -123,13 +125,13 @@ module.exports = app => {
     app.get('/tender/:id/measure/stage/:order/report', sessionAuth, tenderCheck, 'stageController.report');
 
     // 变更管理
-    app.get('/tender/:id/change', sessionAuth, 'changeController.index');
-    app.get('/tender/:id/change/status/:status', sessionAuth, 'changeController.status');
-    app.post('/tender/:id/change/newCode', sessionAuth, 'changeController.newCode');
-    app.post('/tender/:id/change/add', sessionAuth, 'changeController.add');
-    app.get('/tender/:id/change/:cid/info', sessionAuth, 'changeController.info');
-    app.get('/tender/:id/change/:cid/bills', sessionAuth, 'changeController.bills');
-    app.get('/tender/:id/change/:cid/file', sessionAuth, 'changeController.file');
+    app.get('/tender/:id/change', sessionAuth, tenderCheck, 'changeController.index');
+    app.get('/tender/:id/change/status/:status', sessionAuth, tenderCheck, 'changeController.status');
+    app.post('/tender/:id/change/newCode', sessionAuth, tenderCheck, 'changeController.newCode');
+    app.post('/tender/:id/change/add', sessionAuth, tenderCheck, 'changeController.add');
+    app.get('/tender/:id/change/:cid/info', sessionAuth, tenderCheck, 'changeController.info');
+    app.get('/tender/:id/change/:cid/bills', sessionAuth, tenderCheck, 'changeController.bills');
+    app.get('/tender/:id/change/:cid/file', sessionAuth, tenderCheck, 'changeController.file');
 
     // 变更单位管理
     app.post('/change/update/company', sessionAuth, 'changeController.updateCompany');
@@ -169,7 +171,7 @@ module.exports = app => {
     // app.get('/tender/:id/stage/:order/deal', sessionAuth, tenderSelect, 'stageController.stageDeal');
     // app.get('/tender/:id/stage/:order/report', sessionAuth, tenderSelect, 'stageController.stageReport');
 
-    //标准库相关
+    // 标准库相关
     app.post('/std/bills/get-data', sessionAuth, 'stdBillsController.getData');
     app.post('/std/bills/get-children', sessionAuth, 'stdBillsController.getChildren');
     app.post('/std/chapter/get-data', sessionAuth, 'stdChapterController.getData');

+ 127 - 65
app/service/ledger.js

@@ -47,7 +47,7 @@ module.exports = app => {
          * @param {Array|Object} data - 新增数据
          * @param {Number} tenderId - 标段id
          * @param {Object} transaction - 新增事务
-         * @returns {Promise<boolean>} - {Promise<是否正确新增成功>}
+         * @return {Promise<boolean>} - {Promise<是否正确新增成功>}
          */
         async innerAdd(data, tenderId, transaction) {
             const datas = data instanceof Array ? data : [data];
@@ -183,7 +183,7 @@ module.exports = app => {
         /**
          * 根据主键id获取数据
          * @param {Array|Number} id - 主键id
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getDataByIds(id) {
             const ids = id instanceof Array ? id : [id];
@@ -203,7 +203,7 @@ module.exports = app => {
          * 根据标准清单源检索
          * @param tenderId
          * @param source
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async getDataBySource(tenderId, source) {
             this.initSqlBuilder();
@@ -323,7 +323,7 @@ module.exports = app => {
          * 根据 父节点id 获取孙子节点
          * @param tenderId
          * @param nodeId
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async getPosterityByParentId(tenderId, nodeId) {
             if (tenderId <= 0 || !nodeId) {
@@ -403,7 +403,7 @@ module.exports = app => {
          * 根据full_path检索自己及所有父项
          * @param {Number} tenderId - 标段id
          * @param {Array|String} fullPath - 节点完整路径
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          * @private
          */
         async getFullLevelDataByFullPath(tenderId, fullPath) {
@@ -416,26 +416,27 @@ module.exports = app => {
             });
             this.sqlBuilder.setAndWhere('full_path', {
                 value: explodePath,
-                operate: 'in'
+                operate: 'in',
             });
 
             const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
             const data = await this.db.query(sql, sqlParam);
 
             return data;
-        };
+        }
 
         /**
          * 获取sql条件语句片段,仅供search、searchRange方法使用
          * @param {Object} where - 条件参数
-         * @returns {*[]}
+         * @return {*[]}
          * e.g.
          * where = {type: 'And', value: 'A', operate: '=', fields: ['code', 'name']}
          * return [sql: 'And (?? = \'A\' Or ?? = \'A\')', sqlParam: ['code', name]]
          * @private
          */
         _getWhereString(where) {
-            const sqlParam = [], sqlPart = [];
+            const sqlParam = [],
+                sqlPart = [];
             const values = where.value instanceof Array ? where.value : [where.value];
             const fields = where.fields instanceof Array ? where.fields : [where.fields];
             for (const field of fields) {
@@ -452,7 +453,7 @@ module.exports = app => {
          *
          * @param {Number} tenderId - 标段id
          * @param {Object} key - 查询信息
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async search(tenderId, key) {
             let sql = 'Select * From ?? Where';
@@ -473,7 +474,7 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {Object} key - 查询信息
          * @param {Array} range - 查询范围
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async searchRange(tenderId, key, range) {
             let sql = 'Select * From ?? Where';
@@ -501,11 +502,11 @@ module.exports = app => {
          * @param {Number} pid - 父节点id
          * @param {Number} order - order取值
          * @param {String} orderOperate - order比较操作符
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async addUpChildren(tenderId, pid, order, orderOperate) {
             this.initSqlBuilder();
-            const sql = ['SELECT SUM(??) As value FROM ?? ', ' WHERE ']
+            const sql = ['SELECT SUM(??) As value FROM ?? ', ' WHERE '];
             const sqlParam = ['total_price', this.tableName];
             sql.push(' ?? = ' + tenderId);
             sqlParam.push('tender_id');
@@ -524,14 +525,14 @@ module.exports = app => {
          * @param {Number} parentId - 父节点id
          * @param {Number} order - 自增起始order(含)
          * @param {Number} incre - 自增量
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          * @private
          */
         async _updateChildrenOrderAfter(tenderId, parentId, order, incre = 1) {
             this.initSqlBuilder();
             this.sqlBuilder.setAndWhere('tender_id', {
                 value: tenderId,
-                operate: '='
+                operate: '=',
             });
             this.sqlBuilder.setAndWhere('order', {
                 value: order,
@@ -617,7 +618,7 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {Object} parentData - 父项数据
          * @param {Object} data - 新增节点,初始数据
-         * @returns {Promise<*>} - 新增结果
+         * @return {Promise<*>} - 新增结果
          * @private
          */
         async _addChildNodeData(tenderId, parentData, data) {
@@ -657,25 +658,25 @@ module.exports = app => {
          * @param tenderId
          * @param parentData
          * @param data
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          * @private
          */
         async _addChildAutoOrder(tenderId, parentData, data) {
             const self = this;
-            const findPreData = function (list, a) {
+            const findPreData = function(list, a) {
                 if (!list || list.length === 0) { return null; }
                 for (let i = 0, iLen = list.length; i < iLen; i++) {
                     if (self.ctx.helper.compareCode(list[i].code, a.code) > 0) {
-                        return i > 0 ? list[i-1] : null;
+                        return i > 0 ? list[i - 1] : null;
                     }
                 }
-                return list[list.length -1];
-            }
+                return list[list.length - 1];
+            };
 
             const pid = parentData ? parentData.ledger_id : rootId;
             const children = await this.getChildrenByParentId(tenderId, pid);
             const preData = findPreData(children, data);
-            let parent = null;
+            const parent = null;
             if (!preData || children.indexOf(preData) < children.length - 1) {
                 await this._updateChildrenOrderAfter(tenderId, pid, preData ? preData.order + 1 : 1);
             }
@@ -735,8 +736,8 @@ module.exports = app => {
             const result = {
                 name: stdData.name,
                 unit: stdData.unit,
-                source: stdData.source
-            }
+                source: stdData.source,
+            };
             result.code = stdData.code ? stdData.code : '';
             result.b_code = stdData.b_code ? stdData.b_code : '';
             return result;
@@ -747,7 +748,7 @@ module.exports = app => {
          * @param {Number} tenderId
          * @param {Number} selectId
          * @param {Object} stdData
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async addStdNode(tenderId, selectId, stdData) {
             const newData = this._filterStdData(stdData);
@@ -761,14 +762,18 @@ module.exports = app => {
          * @param {Number} selectId - 选中节点id
          * @param {Object} stdData - 节点数据
          * @param {StandardLib} stdLib - 标准库
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async addStdNodeWithParent(tenderId, stdData, stdLib) {
             const fullLevel = await stdLib.getFullLevelDataByFullPath(stdData.list_id, stdData.full_path);
-            fullLevel.sort(function (x, y) {
-                return x.level - y.level
+            fullLevel.sort(function(x, y) {
+                return x.level - y.level;
             });
-            let isNew = false, node, firstNew, updateParent, addResult;
+            let isNew = false,
+                node,
+                firstNew,
+                updateParent,
+                addResult;
             const expandIds = [];
             this.transaction = await this.db.beginTransaction();
             try {
@@ -785,7 +790,7 @@ module.exports = app => {
                             tender_id: tenderId,
                             ledger_pid: parent ? parent.ledger_id : rootId,
                             code: stdNode.code,
-                            name: stdNode.name
+                            name: stdNode.name,
                         });
                         if (!node) {
                             isNew = true;
@@ -793,7 +798,7 @@ module.exports = app => {
                             newData.is_leaf = (i === len - 1);
                             [addResult, node] = await this._addChildAutoOrder(tenderId, parent, newData);
                             if (parent && parent.is_leaf) {
-                                await this.transaction.update(this.tableName, {id: parent.id, is_leaf: false} );
+                                await this.transaction.update(this.tableName, { id: parent.id, is_leaf: false });
                                 updateParent = parent;
                             }
                             firstNew = node;
@@ -809,12 +814,13 @@ module.exports = app => {
             }
 
             // 查询应返回的结果
-            let createData = [], updateData = [];
+            let createData = [],
+                updateData = [];
             if (firstNew) {
                 createData = await this.getDataByFullPath(tenderId, firstNew.full_path + '%');
                 updateData = await this.getNextsData(tenderId, firstNew.ledger_pid, firstNew.order);
                 if (updateParent) {
-                    updateData.push(await this.getDataByCondition({id: updateParent.id}));
+                    updateData.push(await this.getDataByCondition({ id: updateParent.id }));
                 }
             }
             const expandData = await this.getChildrenByParentId(tenderId, expandIds);
@@ -825,7 +831,7 @@ module.exports = app => {
          * 删除节点
          * @param {Number} tenderId - 标段id
          * @param {Object} deleteData - 删除节点数据
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          * @private
          */
         async _deleteNodeData(tenderId, deleteData) {
@@ -878,7 +884,7 @@ module.exports = app => {
                 // 选中节点--全部后节点 order--
                 await this._updateSelectNextsOrder(selectData, -1);
                 // 删除部位明细
-                await this.transaction.delete(this.ctx.service.pos.tableName, {tid: tenderId, lid: this._.map(deleteData, 'id')});
+                await this.transaction.delete(this.ctx.service.pos.tableName, { tid: tenderId, lid: this._.map(deleteData, 'id') });
                 await this.ctx.service.pos.deletePosData(this.transaction, tenderId, this._.map(deleteData, 'id'));
                 await this.transaction.commit();
             } catch (err) {
@@ -1086,7 +1092,7 @@ module.exports = app => {
                 if (selectData.order === 1) {
                     await this.transaction.update(this.tableName, {
                         id: parentData.id,
-                        is_leaf: true
+                        is_leaf: true,
                     });
                 }
                 // 选中节点--父节点--全部后兄弟节点 order+1
@@ -1096,7 +1102,7 @@ module.exports = app => {
                     ledger_pid: parentData.ledger_pid,
                     order: parentData.order + 1,
                     level: selectData.level - 1,
-                    full_path: newFullPath
+                    full_path: newFullPath,
                 };
                 await this.transaction.update(this.tableName, updateData);
                 // 选中节点--全部子节点(含孙) level-1, full_path变更
@@ -1192,7 +1198,7 @@ module.exports = app => {
                 if (preData.is_leaf) {
                     const updateData2 = {
                         id: preData.id,
-                        is_leaf: false
+                        is_leaf: false,
                     };
                     await this.transaction.update(this.tableName, updateData2);
                 }
@@ -1230,14 +1236,14 @@ module.exports = app => {
             return result;
         }
 
-         /**
+        /**
          * newData中,以orgData为基准,过滤掉orgData中未定义或值相等的部分
          * @param {Object} orgData
          * @param {Object} newData
          * @private
          */
         _filterChangedField(orgData, newData) {
-            const result= {};
+            const result = {};
             let bChanged = false;
             for (const prop in orgData) {
                 if (newData[prop] !== orgData[prop]) {
@@ -1251,7 +1257,7 @@ module.exports = app => {
         /**
          * 检查data中是否含有计算字段
          * @param {Object} data
-         * @returns {boolean}
+         * @return {boolean}
          * @private
          */
         _checkCalcField(data) {
@@ -1301,7 +1307,6 @@ module.exports = app => {
          * @return {Array} - 提交后的数据
          */
         async updateInfos(tenderId, datas) {
-            console.log(datas);
             if (tenderId <= 0) {
                 throw '标段不存在';
             }
@@ -1397,7 +1402,7 @@ module.exports = app => {
                         const newId = maxId + index + 1;
                         const idChange = {
                             org: data.id,
-                        }
+                        };
                         delete data.id;
                         if (!data.is_leaf) {
                             for (const children of datas) {
@@ -1441,7 +1446,7 @@ module.exports = app => {
             }
             const createData = await this.getDataByIds(newIds);
             const updateData = await this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order + copyNodes.length);
-            const posData = await this.ctx.service.pos.getPosData({lid: newIds});
+            const posData = await this.ctx.service.pos.getPosData({ lid: newIds });
             return {
                 ledger: { create: createData, update: updateData },
                 pos: posData,
@@ -1453,7 +1458,7 @@ module.exports = app => {
          * @param {Number} tenderId - 标段id
          * @param {Object} updateMap - 增量更新数,使用更新父项的full_path为索引
          *        e.g: {'1.2.6.8': 30, '1.2.6.10': 40}表示'1.2.6.8'增量30,'1.2.6.10'增量40(此处同步更新'1.2.6', '1.2', '1')
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          * @private
          */
         async _increCalcParent(tenderId, updateMap) {
@@ -1462,16 +1467,16 @@ module.exports = app => {
 
                 this.sqlBuilder.setAndWhere('tender_id', {
                     value: tenderId,
-                    operate: '='
+                    operate: '=',
                 });
                 const fullPath = this.ctx.helper.explodePath(prop);
                 this.sqlBuilder.setAndWhere('full_path', {
                     value: this.ctx.helper.explodePath(prop),
-                    operate: 'in'
+                    operate: 'in',
                 });
                 this.sqlBuilder.setUpdateData('total_price', {
                     value: updateMap[prop] > 0 ? updateMap[prop] : -updateMap[prop],
-                    selfOperate: updateMap[prop] > 0 ? '+' : '-'
+                    selfOperate: updateMap[prop] > 0 ? '+' : '-',
                 });
 
                 const [sql, sqlParam] = this.sqlBuilder.build(this.tableName, 'update');
@@ -1483,7 +1488,7 @@ module.exports = app => {
          * 提交数据 - 响应计算(增量方式计算)
          * @param {Number} tenderId
          * @param {Object} data
-         * @returns {Promise<*>}
+         * @return {Promise<*>}
          */
         async updateCalc(tenderId, data) {
             // 简单验证数据
@@ -1529,7 +1534,7 @@ module.exports = app => {
                 throw err;
             }
 
-            return {update: await this.getDataByIds(ids)};
+            return { update: await this.getDataByIds(ids) };
         }
 
         /**
@@ -1538,11 +1543,12 @@ module.exports = app => {
          * @param xmj
          * @param order
          * @param parentData
-         * @returns {Promise<*[]>}
+         * @return {Promise<*[]>}
          * @private
          */
         async _sortBatchInsertData(tenderId, xmj, order, parentData) {
-            const result = [], newIds = [];
+            const result = [],
+                newIds = [];
             let tp = 0;
             const cacheKey = keyPre + tenderId;
             let maxId = parseInt(await this.cache.get(cacheKey));
@@ -1556,7 +1562,7 @@ module.exports = app => {
                 ledger_id: maxId + 1,
                 ledger_pid: parentData.ledger_id,
                 is_leaf: xmj.children.length === 0,
-                order: order,
+                order,
                 level: parentData.level + 1,
                 name: xmj.name,
             };
@@ -1569,7 +1575,7 @@ module.exports = app => {
                     ledger_id: maxId + 1 + i + 1,
                     ledger_pid: parent.ledger_id,
                     is_leaf: true,
-                    order: i+1,
+                    order: i + 1,
                     level: parent.level + 1,
                     b_code: gcl.b_code,
                     name: gcl.name,
@@ -1594,7 +1600,7 @@ module.exports = app => {
          * @param {Number} tenderId - 标段Id
          * @param {Number} selectId - 选中节点Id
          * @param {Object} data - 批量插入数据
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async batchInsertChild(tenderId, selectId, data) {
             const result = { ledger: {}, pos: null };
@@ -1607,7 +1613,7 @@ module.exports = app => {
             }
 
             this.transaction = await this.db.beginTransaction();
-            let newIds = [];
+            const newIds = [];
             try {
                 const lastChild = await this.getLastChildData(tenderId, selectId);
                 // 更新父项isLeaf
@@ -1647,14 +1653,14 @@ module.exports = app => {
                 }
                 this.cache.set(cacheKey, maxId + data.length + 1, 'EX', this.ctx.app.config.cacheTime);
                 await this.transaction.commit();
-            } catch(err) {
+            } catch (err) {
                 await this.transaction.rollback();
                 throw err;
             }
 
             // 查询应返回的结果
             result.ledger.create = await this.getDataByIds(newIds);
-            result.pos = await this.ctx.service.pos.getPosData({lid: newIds});
+            result.pos = await this.ctx.service.pos.getPosData({ lid: newIds });
             return result;
         }
 
@@ -1663,7 +1669,7 @@ module.exports = app => {
          * @param {Number} tenderId - 标段Id
          * @param {Number} selectId - 选中节点Id
          * @param {Object} data - 批量插入数据
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async batchInsertNext(tenderId, selectId, data) {
             const result = { ledger: {}, pos: null };
@@ -1714,7 +1720,7 @@ module.exports = app => {
                 }
                 this.cache.set(cacheKey, maxId + data.length + 1, 'EX', this.ctx.app.config.cacheTime);
                 await this.transaction.commit();
-            } catch(err) {
+            } catch (err) {
                 await this.transaction.rollback();
                 throw err;
             }
@@ -1722,7 +1728,7 @@ module.exports = app => {
             // 查询应返回的结果
             result.ledger.create = await this.getDataByIds(newIds);
             result.ledger.update = await this.getNextsData(selectData.tender_id, selectData.ledger_pid, selectData.order + data.length);
-            result.pos = await this.ctx.service.pos.getPosData({lid: newIds});
+            result.pos = await this.ctx.service.pos.getPosData({ lid: newIds });
             return result;
         }
 
@@ -1731,10 +1737,10 @@ module.exports = app => {
          * @param {Number} tid - 标段id
          * @param {Number} id - 需要计算的节点的id
          * @param {Object} transaction - 操作所属事务,没有则创建
-         * @returns {Promise<void>}
+         * @return {Promise<void>}
          */
         async calc(tid, id, transaction) {
-            const node = await this.getAllDataByCondition({id: id});
+            const node = await this.getAllDataByCondition({ id });
             if (!node) {
                 throw '数据错误';
             }
@@ -1749,18 +1755,74 @@ module.exports = app => {
          * 查找定位 --> 废弃
          * @param tenderId
          * @param nodeId
-         * @returns {Promise<{expand: *}>}
+         * @return {Promise<{expand: *}>}
          */
         async locateNode(tenderId, nodeId) {
             const node = await this.getDataByNodeId(tenderId, nodeId);
             if (!node) {
-                throw '查询数据有误'
+                throw '查询数据有误';
             }
             const expandIds = node.full_path.split('.');
             expandIds.pop();
             const expandData = await this.getChildrenByParentId(tenderId, expandIds);
             return { expand: expandData };
         }
+
+        async _importCacheTreeNode(transaction, node) {
+            const data = {
+                tender_id: this.ctx.tender.id,
+                ledger_id: node.ledger_id,
+                ledger_pid: node.ledger_pid,
+                level: node.level,
+                order: node.order,
+                is_leaf: !node.children || node.children.length === 0,
+                full_path: node.full_path,
+                code: node.code,
+                b_code: node.b_code,
+                name: node.name,
+                unit: node.unit,
+                quantity: node.quantity,
+                unit_price: node.unit_price,
+                total_price: node.total_price,
+                dgn_qty1: node.dgn_qty1,
+                dgn_qty2: node.dgn_qty2,
+                memo: node.memo,
+                drawing_code: node.drawing_code,
+            };
+            const result = await transaction.insert(this.tableName, data);
+            if (node.children && node.children.length > 0) {
+                for (const child of node.children) {
+                    await this._importCacheTreeNode(transaction, child);
+                }
+            } else if (node.pos && node.pos.length > 0) {
+                await this.ctx.service.pos.insertLedgerPosData(transaction, this.ctx.tender.id, result.insertId, node.pos);
+            }
+        }
+
+        async importExcel(excelData) {
+            const AnalysisExcel = require('../lib/analysis_excel');
+            const analysisExcel = new AnalysisExcel();
+            const tempData = await this.ctx.service.tenderNodeTemplate.getData(true);
+            const cacheTree = analysisExcel.analysisData(excelData, tempData);
+            const cacheKey = keyPre + this.ctx.tender.id;
+            const orgMaxId = parseInt(await this.cache.get(cacheKey));
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.delete(this.tableName, {tender_id: this.ctx.tender.id});
+                await transaction.delete(this.ctx.service.pos.tableName, {tid: this.ctx.tender.id});
+                for (const node of cacheTree.roots) {
+                    await this._importCacheTreeNode(transaction, node);
+                }
+                await transaction.commit();
+                this.cache.set(cacheKey, cacheTree.items.length + 1, 'EX', this.ctx.app.config.cacheTime);
+            } catch (err) {
+                await transaction.rollback();
+                if (orgMaxId) {
+                    this.cache.set(cacheKey, orgMaxId, 'EX', this.ctx.app.config.cacheTime);
+                }
+                throw err;
+            }
+        }
     }
 
     return Ledger;

+ 1 - 0
app/view/layout/layout.ejs

@@ -27,6 +27,7 @@
     <script src="/public/js/jquery-contextmenu/jquery.ui.position.min.js"></script>
     <script src="/public/js/jquery-contextmenu/jquery.contextMenu.min.js"></script>
     <script src="/public/js/lodash.js"></script>
+    <script src="/public/js/lz-string/lz-string.js"></script>
 </head>
 
 <body>

+ 2 - 0
app/view/ledger/explode.ejs

@@ -138,6 +138,8 @@
     let posSpreadSetting = JSON.parse('<%- posSpreadSetting %>');
 </script>
 <script src="/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js"></script>
+<script src="/public/js/js-xlsx/xlsx.full.min.js"></script>
+<script src="/public/js/js-xlsx/xlsx.utils.js"></script>
 <script>
     GC.Spread.Sheets.LicenseKey = "559432293813965#A0y3iTOzEDOzkjMyMDN9UTNiojIklkI1pjIEJCLi4TPB9mM5AFNTd4cvZ7SaJUVy3CWKtWYXx4VVhjMpp7dYNGdx2ia9sEVlZGOTh7NRlTUwkWR9wEV4gmbjBDZ4ElR8N7cGdHVvEWVBtCOwIGW0ZmeYVWVr3mI0IyUiwCMzETN8kzNzYTM0IicfJye&Qf35VfiEzRwEkI0IyQiwiIwEjL6ByUKBCZhVmcwNlI0IiTis7W0ICZyBlIsIyNyMzM5ADI5ADNwcTMwIjI0ICdyNkIsIibj9SbvNmL4N7bjRnch56ciojIz5GRiwiI8+Y9sWY9QmZ0Jyp96uL9v6L0wap9biY9qiq95q197Wr9g+89iojIh94Wiqi";
 </script>

+ 28 - 1
app/view/ledger/explode_modal.ejs

@@ -1,3 +1,30 @@
+<!--导入清单Excel-->
+<div class="modal fade" id="upload-ledger" data-backdrop="static" enctype="multipart/form-data">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">导入清单Excel</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label for="exampleFormControlFile1">Excel模板</label>
+                    <div class="form-control"><a id="downloadLedgerTemplate" href="/tender/<%- ctx.tender.id %>/ledger/download/template" class="btn btn-sm btn-link">下载</a></div>
+                </div>
+                <div class="form-group">
+                    <label for="exampleFormControlFile1">导入清单Excel文件</label>
+                    <div class="form-control"><input type="file" class="form-control-file" id="upload-ledger-file" accept="*.xls"></div>
+                </div>
+                <div class="form-group" id="upload-ledger-sheets" style="display: none;">
+                    <label for="exampleFormControlFile1">勾选要导入的Sheet</label>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary" id="upload-ledger-ok">确认上传</button>
+            </div>
+        </div>
+    </div>
+</div>
 <!--上传签约清单-->
 <div class="modal fade" id="upload-deal" data-backdrop="static" enctype="multipart/form-data">
     <div class="modal-dialog" role="document">
@@ -8,7 +35,7 @@
             <div class="modal-body">
                 <div class="form-group">
                     <label for="exampleFormControlFile1">Excel模板</label>
-                    <div class="form-control"><a id="downloadDealTemplate" href="/deal/download/template.xls" class="btn btn-sm btn-link">下载</a></div>
+                    <div class="form-control"><a id="downloadDealTemplate" href="/tender/<%- ctx.tender.id %>/deal/download/template.xls" class="btn btn-sm btn-link">下载</a></div>
                 </div>
                 <div class="form-group">
                     <label for="exampleFormControlFile1">上传签约清单Excel文件</label>

+ 29 - 29
config/menu.js

@@ -45,8 +45,8 @@ const tenderMenu = {
                 icon: '<i class="fa fa-pie-chart"></i> ',
                 display: true,
                 url: '',
-            }
-        ]
+            },
+        ],
     },
     ledger: {
         name: '0号台帐',
@@ -61,12 +61,12 @@ const tenderMenu = {
                 name: '台帐审批',
                 display: true,
                 url: '/ledger/audit',
-            },{
+            }, {
                 name: '台帐修订',
                 display: true,
                 url: '/ledger/change',
             },
-        ]
+        ],
     },
     measure: {
         name: '计量台帐',
@@ -81,12 +81,12 @@ const tenderMenu = {
                 name: '清单汇总',
                 display: true,
                 url: '/measure/gather',
-            },{
+            }, {
                 name: '审核比较',
                 display: true,
                 url: '/measure/compare',
             },
-        ]
+        ],
     },
     change: {
         name: '工程变更',
@@ -97,8 +97,8 @@ const tenderMenu = {
                 icon: '<i class="fa fa-retweet"></i> ',
                 display: true,
                 url: '/change',
-            }
-        ]
+            },
+        ],
     },
     report: {
         name: '报表',
@@ -109,8 +109,8 @@ const tenderMenu = {
                 icon: '<i class="fa fa-file-text-o"></i> ',
                 display: true,
                 url: '/report',
-            }
-        ]
+            },
+        ],
     },
 };
 
@@ -125,8 +125,8 @@ const stageMenu = {
                 display: true,
                 url: '/measure/stage',
                 fixedUrl: true,
-            }
-        ]
+            },
+        ],
     },
     ledger: {
         name: '本期计量台帐',
@@ -138,8 +138,8 @@ const stageMenu = {
                 display: true,
                 url: '',
                 class: ' class="ml-3"',
-            }
-        ]
+            },
+        ],
     },
     detail: {
         name: '中间计量',
@@ -151,8 +151,8 @@ const stageMenu = {
                 display: true,
                 url: '/detail',
                 class: ' class="ml-3"',
-            }
-        ]
+            },
+        ],
     },
     pay: {
         name: '合同支付',
@@ -164,8 +164,8 @@ const stageMenu = {
                 display: true,
                 url: '/pay',
                 class: ' class="ml-3"',
-            }
-        ]
+            },
+        ],
     },
     change: {
         name: '变更令',
@@ -177,8 +177,8 @@ const stageMenu = {
                 display: true,
                 url: '/change',
                 class: ' class="ml-3"',
-            }
-        ]
+            },
+        ],
     },
     compare: {
         name: '审核比较',
@@ -190,8 +190,8 @@ const stageMenu = {
                 display: true,
                 url: '/compare',
                 class: ' class="ml-3"',
-            }
-        ]
+            },
+        ],
     },
     report: {
         name: '报表',
@@ -203,8 +203,8 @@ const stageMenu = {
                 display: true,
                 url: '/report',
                 class: '',
-            }
-        ]
+            },
+        ],
     },
 };
 
@@ -218,8 +218,8 @@ const sumMenu = {
                 icon: '<i class="fa fa-pie-chart"></i> ',
                 display: true,
                 url: '/sum',
-            }
-        ]
+            },
+        ],
     },
     stage: {
         name: '上报期',
@@ -230,10 +230,10 @@ const sumMenu = {
                 icon: '<i class="fa fa-calendar-check-o"></i> ',
                 display: true,
                 url: '/sum/stage',
-            }
-        ]
+            },
+        ],
     },
-}
+};
 
 const settingMenu = {
     info: {

+ 59 - 0
test/app/lib/analysis_excel.test.js

@@ -0,0 +1,59 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const { app, assert } = require('egg-mock/bootstrap');
+const AnalysisExcel = require('../../../app/lib/analysis_excel');
+
+describe('test/app/lib/analysis_excel.test.js', () => {
+    it('analysis Test Data', function* () {
+        const analysisExcel = new AnalysisExcel();
+        const testData = {
+            rows: [
+                ['项目节编号', '清单子目号', '部位明细', '名称', '单位', '清单数量', '设计数量1', '设计数量2', '单价', '合价', '图号', '备注'],
+                ['1', null, null, '第一部分 建筑安装工程费', '公路公里'],
+                ['1-1', null, null, '临时工程', '公路公里'],
+                ['1-1-6', null, null, '临时道路修建、养护与拆除(包括原道路的养护费)'],
+                [ null, '103-1', null, '临时道路修建、养护与拆除(包括原道路的养护费)', '总额', 1, null, null, 50000 ],
+                [ '1-1-7', null, null, '临时占地' ],
+                [ null, '103-2', null, '临时占地', '总额', 1, null, null, 57350 ],
+                [ '1-1-8', null, null, '临时供电设施' ],
+                [ null, '103-3-a', null, '设施架设、拆除', '总额', 1, null, null, 638406 ],
+                [ null, '103-3-b', null, '设施维修', '总额', 1, null, null, 50000 ],
+                [ '1-1-9', null, null, '电讯设施的提供、维修与拆除' ],
+                [ null, '103-4', null, '电讯设施的提供、维修与拆除', '总额', 1, null, null, 50000 ],
+                [ '1-1-10', null, null, '承包人驻地建设' ],
+                [ null, '104-1', null, '承包人驻地建设', '总额', 1, null, null, 200000 ],
+                [ '1-2', null, null, '路基工程', 'km' ],
+                [ '1-2-2', null, null, '挖方', 'm3' ],
+                [ '1-2-2-1', null, null, '挖土方', 'm3' ],
+                [ '1-2-2-1-1', null, null, '挖路基土方', 'm3' ],
+                [ null, '203-1-a', null, ' ', 'm3', 92954.75, null, null, 7.53 ],
+                [ null, null, 1, 'K0+000-K1+000', null, 11619.34375, null, null, null, null, '第二册S-2-1' ],
+                [ null, null, 2, 'K1+000-K1+800', null, 11619.34375, null, null, null, null, '第二册S-2-1' ],
+                [ null, null, 3, 'K2+800-K3+004', null, 11619.34375, null, null, null, null, '第二册S-2-1' ],
+                [ null, null, 4, 'K3+004-K4+040', null, 11619.34375, null, null, null, null, '第二册S-2-1' ],
+                [ null, null, 5, 'K4+040-K5+000', null, 11619.34375, null, null, null, null, '第二册S-2-1' ],
+                [ null, null, 6, 'K5+000-K6+000', null, 11619.34375, null, null, null, null, '第二册S-2-1' ],
+                [ null, null, 7, 'K6+000-K7+000', null, 11619.34375, null, null, null, null, '第二册S-2-1' ],
+                [ null, null, 8, 'K7+000-K7+700', null, 11619.34375, null, null, null, null, '第二册S-2-1' ],
+                [ '1-2-2-2', null, null, '挖石方', 'm3' ],
+                [ '1-2-2-2-1', null, null, '挖软石', 'm3' ],
+                [ null, '203-1-b', null, '挖石方', 'm3', 105.36, null, null, 16.11 ],
+            ],
+            range: 'A1:L29',
+        };
+        const ctx = app.mockContext();
+        const templateData = yield ctx.service.tenderNodeTemplate.getData(true);
+        const result = analysisExcel.analysisData(testData, templateData);
+        assert(result.items.length === 21);
+        assert(result.roots.length === 1);
+        assert(result.pos.length === 8);
+    });
+});