瀏覽代碼

Merge branch 'dev' of http://192.168.1.41:3000/maixinrong/Calculation into dev

laiguoran 3 年之前
父節點
當前提交
970c576d02

+ 2 - 0
app.js

@@ -16,6 +16,7 @@ const _ = require('lodash');
 const BaseService = require('./app/base/base_service');
 const BaseTreeService = require('./app/base/base_tree_service');
 const BaseBillsService = require('./app/base/base_bills_service');
+const BaseBudgetService = require('./app/base/base_budget_service');
 const BaseController = require('./app/base/base_controller');
 
 const menu = require('./config/menu');
@@ -31,6 +32,7 @@ module.exports = app => {
     app.BaseService = BaseService;
     app.BaseTreeService = BaseTreeService;
     app.BaseBillsService = BaseBillsService;
+    app.BaseBudgetService = BaseBudgetService;
 
     // 控制器基类
     app.BaseController = BaseController;

+ 2 - 2
app/base/base_bills_service.js

@@ -650,8 +650,8 @@ class BaseBillsSerivce extends TreeService {
                 newBills.quantity = this.ctx.helper.add(newBills.sgfh_qty,
                     this.ctx.helper.add(newBills.sjcl_qty, newBills.qtcl_qty));
                 newBills.sgfh_tp = this.ctx.helper.mul(newBills.sgfh_qty, newBills.unit_price, tpDecimal);
-                newBills.sjcl_tp = this.ctx.helper.mul(newBills.qtcl_qty, newBills.unit_price, tpDecimal);
-                newBills.qtcl_tp = this.ctx.helper.mul(newBills.sjcl_qty, newBills.unit_price, tpDecimal);
+                newBills.sjcl_tp = this.ctx.helper.mul(newBills.sjcl_qty, newBills.unit_price, tpDecimal);
+                newBills.qtcl_tp = this.ctx.helper.mul(newBills.qtcl_qty, newBills.unit_price, tpDecimal);
                 newBills.total_price = this.ctx.helper.mul(newBills.quantity, newBills.unit_price, tpDecimal);
                 newBills.deal_tp = this.ctx.helper.mul(newBills.deal_qty, newBills.unit_price, tpDecimal);
                 if (defaultData) this.ctx.helper._.assignIn(newBills, defaultData);

+ 66 - 0
app/base/base_budget_service.js

@@ -0,0 +1,66 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+const TreeService = require('./base_tree_service');
+
+class BaseBudget extends TreeService {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @param {String} tableName - 表名
+     * @return {void}
+     */
+    constructor(ctx, setting, tablename) {
+        super(ctx, {
+            mid: 'bid',
+            kid: 'tree_id',
+            pid: 'tree_pid',
+            order: 'order',
+            level: 'level',
+            isLeaf: 'is_leaf',
+            fullPath: 'full_path',
+            keyPre: setting.keyPre,
+            uuid: true,
+        });
+        this.tableName = tablename;
+    }
+
+    async initByTemplate(conn, budgetId, templateId){
+        if (!conn) throw '内部错误';
+        if (budgetId <= 0) throw '概算项目id错误';
+        const data = await this.ctx.service.tenderNodeTemplate.getData(templateId);
+        if (!data.length) throw '模板数据有误';
+
+        // 整理数据
+        const insertData = [];
+        for (const tmp of data) {
+            insertData.push({
+                id: this.uuid.v4(),
+                bid: budgetId,
+                tree_id: tmp.template_id,
+                tree_pid: tmp.pid,
+                level: tmp.level,
+                order: tmp.order,
+                full_path: tmp.full_path,
+                is_leaf: tmp.is_leaf,
+                code: tmp.code,
+                name: tmp.name,
+                node_type: tmp.node_type,
+            });
+        }
+        const operate = await conn.insert(this.tableName, insertData);
+        return operate.affectedRows === data.length;
+    }
+}
+
+module.exports = BaseBudget;

+ 147 - 0
app/controller/budget_controller.js

@@ -0,0 +1,147 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2021/10/27
+ * @version
+ */
+
+module.exports = app => {
+    class BudgetController extends app.BaseController {
+
+        /**
+         * 概算投资
+         *
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async list(ctx) {
+            try {
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.list),
+                };
+                renderData.budgetList = await ctx.service.budget.getAllDataByCondition({
+                    where: { pid: ctx.session.sessionProject.id },
+                    orders: [['name', 'asc']],
+                });
+                renderData.budgetStd = await ctx.service.budgetStd.getDataByProjectId(ctx.session.sessionProject.id);
+                await this.layout('budget/list.ejs', renderData, 'budget/list_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async add(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.name || data.name.length > 100) throw '项目名称有误';
+                if (!data.std_id) throw '概预算标准有误';
+                const result = await ctx.service.budget.add(data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '新建项目失败');
+            }
+        }
+
+        async del(ctx) {
+
+        }
+
+        async save(ctx) {
+
+        }
+
+        async compare(ctx) {
+            try {
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.compare),
+                };
+                await this.layout('budget/compare.ejs', renderData, 'budget/compare_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        _getSpreadSetting(type) {
+            const spreadSetting = {
+                cols: [
+                    {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
+                    {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 230, formatter: '@'},
+                    {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+                    {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+                    {title: '清单数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '设计数量|数量1', colSpan: '2|1', rowSpan: '1|1', field: 'dgn_qty1', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '|数量2', colSpan: '|1', rowSpan: '|1', field: 'dgn_qty2', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '经济指标', colSpan: '1', rowSpan: '2', field: 'dgn_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '金额', colSpan: '1', rowSpan: '2', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
+                    {title: '图册号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 100, formatter: '@'},
+                    {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, formatter: '@'},
+                ],
+                emptyRows: 3,
+                headRows: 2,
+                headRowHeight: [25, 25],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                readOnly: true,
+            };
+            // todo 根据设置判断预算是否需要清单
+            if (type !== 'yu') {
+                spreadSetting.cols = spreadSetting.cols.filter(x => {
+                    return ['b_code', 'quantity'].indexOf(x.field) < 0;
+                });
+            }
+            return spreadSetting;
+        }
+        _getRelaService(type) {
+            switch(type) {
+                case 'gu': return ctx.serivce.budgetGu;
+                case 'gai': return ctx.serivce.budgetGai;
+                case 'yu': return ctx.serivce.budgetYu;
+                default: return null;
+            }
+        }
+
+
+        async detail(ctx) {
+            try {
+                const renderData = {
+                    spreadSetting: this._getSpreadSetting(ctx.params.btype),
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.detail),
+                    needGcl: ctx.params.btype === 'yu',
+                };
+                [renderData.stdBills, renderData.stdChapters] = await ctx.service.budgetStd.getStdList(ctx.budget.std_id, ctx.params.btype);
+                await this.layout('budget/detail.ejs', renderData, 'budget/detail_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async detailLoad(ctx) {
+            try {
+                const relaService = this._getRelaService(ctx.params.btype);
+                ctx.body = {
+                    err: 0,
+                    msg: '',
+                    data: await relaService.getData(ctx.budget.id),
+                }
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '获取数据错误');
+            }
+        }
+
+        async detailUpdate(ctx) {
+
+        }
+
+        async detailUploadExcel(ctx) {
+
+        }
+    }
+
+    return BudgetController;
+};

+ 4 - 0
app/controller/report_controller.js

@@ -726,6 +726,8 @@ module.exports = app => {
                 });
             }
             const roleRelArr = (params.stage_status === 3) ? (await ctx.service.roleRptRel.getRoleRptRelByDetailIds(params.tender_id, params.rpt_ids, params.stage_id)) : [];
+            // console.log(`baseDir: ${baseDir}`);
+            // fsUtil.writeObjToFile(roleRelArr, 'D:/GitHome/temp/签名信息.js');
             const stgAudit = (params.stage_status === 3) ? (await ctx.service.stageAudit.getStageAudit(params.stage_id, params.stage_times)) : [];
             const stgAuditForOrg = (params.stage_status === 3) ? (await ctx.service.stageAudit.getStageAudit(params.stage_id, 1)) : [];
             const stageList = (params.stage_status === 3) ? (await ctx.service.stage.getValidStagesShort(params.tender_id)) : [];
@@ -789,6 +791,8 @@ module.exports = app => {
                     }
                 }
             }
+            // fsUtil.writeObjToFile(pageRstArr, 'D:/GitHome/temp/pageRstArrForExcelAfterReAssign.js');
+            // fsUtil.writeObjToFile(rptRoleRelArr, 'D:/GitHome/temp/签名信息_新.js');
             runnableRst.push(getExcelByPageData(pageRstArr, params.rptName, rptRoleRelArr));
             const uuidRst = await Promise.all(runnableRst);
             ctx.body = { data: uuidRst, waterMarkStr };

+ 37 - 0
app/middleware/budget_check.js

@@ -0,0 +1,37 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = options => {
+    /**
+     * 标段校验 中间件
+     * 1. 读取标段数据(包括属性)
+     * 2. 检验用户是否可见标段(不校验具体权限)
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* budgetCheck(next) {
+        try {
+            // 读取标段数据
+            const id = parseInt(this.params.id);
+            if (!id) throw '参数错误';
+            this.budget = yield this.service.budget.getDataById(id);
+            if (!this.budget) throw '项目不存在';
+            yield next;
+        } catch (err) {
+            this.log(err);
+            if (this.helper.isAjax(this.request)) {
+                this.ajaxErrorBody(err, '概算投资项目未知错误');
+            } else {
+                this.postError(err, '概算投资项目未知错误');
+            }
+        }
+    };
+};

+ 16 - 1
app/public/js/advance.js

@@ -32,7 +32,22 @@ $(document).ready(function () {
     //     })
     //     return false
     // })
-
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
     $('.ml-auto').submit(function () {
         if (!advancePayTotal) {
             $('#erro').modal('show');

+ 18 - 1
app/public/js/advance_audit.js

@@ -9,7 +9,24 @@
  */
 
 $(document).ready(function () {
-    autoFlashHeight()
+    autoFlashHeight();
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
 
     const decimal = 2
     let oldVal = null

+ 61 - 0
app/public/js/budget_compare.js

@@ -0,0 +1,61 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+$(document).ready(() => {
+    autoFlashHeight();
+    const compareSpread = SpreadJsObj.createNewSpread($('#cost-compare')[0]);
+    const compareSheet = compareSpread.getActiveSheet();
+
+    const spreadSetting = {
+        cols: [
+            {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
+            {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, formatter: '@'},
+            {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'},
+            {title: '投资估算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'gu_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'gu_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gu_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '初步概算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'gai_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'gu_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gai_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '施工图预算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'yu_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'gu_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'yu_tp', hAlign: 2, width: 80, type: 'Number'},
+            {title: '决算|数量1/数量2', colSpan: '3|1', rowSpan: '1|1', field: 'final_qty', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|经济指标', colSpan: '|1', rowSpan: '|1', field: 'gu_dgn_price', hAlign: 2, width: 80, type: 'Number'},
+            {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'final_tp', hAlign: 2, width: 80, type: 'Number'},
+        ],
+        emptyRows: 3,
+        headRows: 2,
+        headRowHeight: [25, 25],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        readOnly: true,
+    };
+    SpreadJsObj.initSheet(compareSheet, spreadSetting);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+            compareSpread.refresh();
+        }
+    });
+});

+ 33 - 0
app/public/js/budget_detail.js

@@ -0,0 +1,33 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+$(document).ready(() => {
+    autoFlashHeight();
+    const budgetSpread = SpreadJsObj.createNewSpread($('#budget-spread')[0]);
+    const budgetSheet = budgetSpread.getActiveSheet();
+    SpreadJsObj.initSheet(budgetSheet, spreadSetting);
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+});

+ 28 - 0
app/public/js/budget_list.js

@@ -0,0 +1,28 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const budgetNameChange = function (obj) {
+    if (obj.value.length > 100) {
+        obj.classList.add('is-invalid');
+        $('.invalid-feedback').show();
+    } else {
+        obj.classList.remove('is-invalid');
+        $('.invalid-feedback').hide();
+    }
+};
+
+const addBudget = function () {
+    const name = $('#add-budget-name').val();
+    if (!name || name.length > 100) return;
+    const std_id = parseInt($('[name=std_id]:checked').val());
+    postData('/budget/add', { name, std_id }, function () {
+        window.location.reload();
+    });
+};

+ 9 - 0
app/public/report/js/rpt_archive.js

@@ -132,6 +132,15 @@ let rptArchiveObj = {
         }
     },
 
+    batchArchive: function() {
+        if (zTreeOprObj.checkedRptTplNodes && zTreeOprObj.checkedRptTplNodes.length > 0) {
+            indexDbOprObj.storeReportRequest(current_stage_id, zTreeOprObj.checkedRptTplNodes);
+        }
+    },
+    batchArchiveTest: function() {
+        indexDbOprObj._test();
+    },
+
     _requestArchiveReport: function () {
         let me = rptArchiveObj;
         if (me.currentNode && me.currentArchiveUuid) {

+ 193 - 0
app/public/report/js/rpt_indexDb.js

@@ -0,0 +1,193 @@
+/**
+ * Created by Tony on 2021/11/5.
+ * 实现本地indexDb的业务操作
+ */
+
+const indexDbOprObj = {
+    currentDb: null,
+    iniDb: function (dbName, version) {
+        let rst = window.indexedDB.open(dbName, version);
+        rst.onsuccess = function (event) {
+            indexDbOprObj.currentDb = event.target.result;;
+            console.log('数据库打开成功!');
+        };
+        rst.onupgradeneeded = function (event) {
+            let db = event.target.result;
+            indexDbOprObj.currentDb = db;
+            console.log('数据库升级及初始化!');
+            indexDbOprObj._iniTable(db);
+        }
+    },
+    _iniTable: function(db) {
+        if (db) {
+            //本地表,主要是用来储存报表结果对象,主要是为PDF多表导出用,当然,其他方式也是可以用的(如打印),看实际需要
+            if (!db.objectStoreNames.contains('tb_ReportPages')) {
+                db.createObjectStore('tb_ReportPages', { keyPath: 'id' });
+            }
+            if (!db.objectStoreNames.contains('tb_ReportRequests')) {
+                db.createObjectStore('tb_ReportRequests', { keyPath: 'id' });
+            }
+        }
+    },
+    batchAdd: function (tbName, datas, cb) {
+        let db =  indexDbOprObj.currentDb;
+        let ttlAmt = datas.length;
+        console.log(`已选报表数量:${ttlAmt}`);
+        let cnt = 0;
+        let request = db.transaction([tbName], 'readwrite').objectStore(tbName);
+        request.onsuccess = function (event) {
+            cnt++;
+            console.log(`已选加数量:${cnt}`);
+            if (cnt === ttlAmt && cb) {
+                cb(datas);
+            }
+        };
+        for (let data of datas) {
+            request.add(data);
+        }
+    },
+    _addDummy: function() {
+        let db =  indexDbOprObj.currentDb;
+        // let request = db.transaction(['tb_ReportRequests'], 'readwrite')
+        db.transaction(['tb_ReportRequests'], 'readwrite')
+            .objectStore('tb_ReportRequests')
+            .add({ id: '_2830_1725', name: '多人协同2', pageSize: 'A4', CFG: null, prj_ids: [123,456,789] });
+        // request.onsuccess = function (event) {
+        //     console.log('数据写入成功');
+        // };
+        //
+        // request.onerror = function (event) {
+        //     console.log('数据写入失败');
+        // }
+
+        db.transaction(['tb_ReportRequests'], 'readwrite')
+            .objectStore('tb_ReportRequests')
+            .add({ id: '_2830_1728', name: '多人协同3', pageSize: 'A3', CFG: null, prj_ids: [668] });
+    },
+    _updateDummy: function () {
+        let db =  indexDbOprObj.currentDb;
+        let request = db.transaction(['tb_ReportRequests'], 'readwrite')
+            .objectStore('tb_ReportRequests')
+            .put({ id: '_2830_1725', name: '多人协同2', pageSize: 'A4', CFG: 'CFG', prj_ids: [1,2,3] });
+
+        request.onsuccess = function (event) {
+            console.log('数据写入成功');
+        };
+
+        request.onerror = function (event) {
+            console.log('数据写入失败');
+        }
+    },
+    _getDummy: function () {
+        let db =  indexDbOprObj.currentDb;
+        let transaction = db.transaction(['tb_ReportRequests']);
+        let objectStore = transaction.objectStore('tb_ReportRequests');
+        let request = objectStore.get('_2830_1725');
+
+        request.onerror = function(event) {
+            console.log('事务失败');
+        };
+
+        request.onsuccess = function(event) {
+            if (request.result) {
+                console.log('Name: ' + request.result.name);
+                console.log('PageSize: ' + request.result.pageSize);
+                console.log('CFG: ' + request.result.CFG);
+                console.log('ProjectIds: ' + request.result.prj_ids);
+            } else {
+                console.log('未获得数据记录');
+            }
+        };
+    },
+    _getDummyAll: function (idRst, cb) {
+        let db =  indexDbOprObj.currentDb;
+        let objectStore = db.transaction('tb_ReportRequests').objectStore('tb_ReportRequests');
+
+        objectStore.openCursor().onsuccess = function (event) {
+            let cursor = event.target.result;
+
+            if (cursor) {
+                console.log('Id: ' + cursor.key);
+                console.log('Name: ' + cursor.value.name);
+                console.log('PageSize: ' + cursor.value.pageSize);
+                console.log('gather_select: ' + cursor.value.gather_select);
+                console.log('stage_select: ' + cursor.value.stage_select);
+                console.log('CFG: ');
+                console.log(cursor.value.CFG);
+                if (idRst) {
+                    idRst.push(cursor.key);
+                }
+                cursor.continue();
+            } else {
+                console.log('没有更多数据了!');
+                if (cb) {
+                    cb(idRst);
+                }
+            }
+        };
+    },
+    _removeDummy: function () {
+        let db =  indexDbOprObj.currentDb;
+        let request = db.transaction(['tb_ReportRequests'], 'readwrite')
+            .objectStore('tb_ReportRequests')
+            .delete('_2830_1725');
+
+        request.onsuccess = function (event) {
+            console.log('数据删除成功');
+        };
+
+        request.onerror = function (event) {
+            console.log('数据删除失败');
+        }
+    },
+    _test: function () {
+        // indexDbOprObj._addDummy();
+        // indexDbOprObj._getDummy();
+        let idRst = [];
+        indexDbOprObj._getDummyAll(idRst, (ids)=>{
+            console.log(ids);
+            // indexDbOprObj.removeAll(ids);
+        });
+        // indexDbOprObj._updateDummy();
+        // indexDbOprObj._removeDummy();
+    },
+    storeReportRequest: function (stage_id, rptNodes) {
+        if (rptNodes && rptNodes.length > 0) {
+            let datas = [];
+            for (let node of rptNodes) {
+                let keyStr = `_${stage_id}_${node.refId}`;
+                const gather_select = customSelects.gather_select.find(function (x) {
+                    return x.id === node.refId;
+                });
+                const stage_select = customSelects.stage_select.find(function (x) {
+                    return x.id === node.refId;
+                });
+                let data = {id: keyStr, name: node.name, gather_select, stage_select, pageSize: rptControlObj.getCurrentPageSize(), CFG: CUST_CFG};
+                datas.push(data);
+            }
+            indexDbOprObj.batchAdd('tb_ReportRequests', datas, (rstArr)=>{
+                console.log('batch add succeeded!');
+                console.log(rstArr);
+                //其他操作
+            });
+        }
+    },
+    storeReportPages: function (rptPages, stage_id, rptNode) {
+        //
+    },
+    removeAll: function(idArr) {
+        if (idArr && idArr.length > 0) {
+            let db =  indexDbOprObj.currentDb;
+            let request = db.transaction(['tb_ReportRequests'], 'readwrite').objectStore('tb_ReportRequests');
+            for (let id of idArr) {
+                request.delete(id);
+                request.onsuccess = function (event) {
+                    console.log(`数据('${id}')删除成功`);
+                };
+            }
+        }
+    },
+    getReportPages: function (rpt_id, stage_id) {
+        //
+    }
+};

+ 2 - 0
app/public/report/js/rpt_main.js

@@ -31,6 +31,8 @@ let rptTplObj = {
 
             rptControlObj.loadPDFFonts();
 
+            indexDbOprObj.iniDb('tmpForPDF', 2);
+
             // dynamicLoadJs('/public/jspdf/SmartSimsun-normal.js');
             // dynamicLoadJs('/public/jspdf/SmartSimsun-bold.js', me.pdfFontSimsunCallBack);
         }

+ 31 - 8
app/reports/util/rpt_excel_util.js

@@ -1089,25 +1089,28 @@ function writeDrawings(pageData, signKeyArr, signPathArr, isSinglePage, signShee
     // 优化调整,不再每页都设置一个,重复的签名只需要一个即可(极端case: 13xx多页的签名文档,导出有1.3G大)
     if (isSinglePage) {
         // console.log('singlePage! ');
-        rst.push(writeDrawing(pageData, null, signKeyArr));
+        rst.push(writeDrawing(pageData, null, signKeyArr, 0));
     } else {
         // console.log('pageData! ');
+        // console.log(signKeyArr);
         for (let i = 0; i < pageData.items.length; i++) {
             if (signSheetIdxArr[i]) {
+            // if (signSheetIdxArr[i]) {
                 // console.log('signKeyArr[' + i + ']');
                 // console.log(signKeyArr[i]);
-                rst.push(writeDrawing(pageData, pageData.items[i], signKeyArr));
+                rst.push(writeDrawing(pageData, pageData.items[i], signKeyArr, i));
             }
         }
     }
     // */
     return rst;
 }
-function writeDrawing(pageData, sheetData, subSignKeyArr) {
+function writeDrawing(pageData, sheetData, subSignKeyArr, sheetIdx) {
     const rst = [];
     const xPos = [];
     const yPos = [];
     const yMultiPos = [];
+    // console.log(`sheetIdx: ${sheetIdx}`);
 
     const private_setSheetDrawingCellData = function(signCell, theYPos, startPicIdx, offsetRow) {
         let cellControl;
@@ -1203,9 +1206,10 @@ function writeDrawing(pageData, sheetData, subSignKeyArr) {
     // console.log(subSignKeyArr);
     if (sheetData) {
         // console.log('sheetData');
-        let startPicIdx = 2;
+        let startPicIdx = 2 + sheetIdx * 1000;
         for (const sCell of sheetData[JV.PROP_SIGNATURE_CELLS]) {
             if (subSignKeyArr.indexOf(sCell.signature_name) >= 0) {
+                // console.log(`sCell.signature_name: ${sCell.signature_name} at index: ${startPicIdx}`);
                 private_setSheetDrawingCellData(sCell, yPos, startPicIdx, 0);
                 startPicIdx++;
             }
@@ -1246,6 +1250,7 @@ function writeDrawing(pageData, sheetData, subSignKeyArr) {
         }
     }
     rst.push('</xdr:wsDr>');
+    // console.log(rst);
     return rst;
 }
 function writeDrawingsRels(amt, startIdx) {
@@ -1422,7 +1427,13 @@ function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel,
                 //     }
                 // }
                 // 这里把图片的路径作为key值判断
+                // if (true || signKeyArr.indexOf(signature.signature_name) < 0) {
+                // console.log(`signature: ${signature.signature_name}`);
                 if (signKeyArr.indexOf(signature.signature_name) < 0) {
+                    // console.log(`signature: ${signature.signature_name}`);
+                    if (signature.signature_name === '支表03 清单支付报表(凉山州)_施工单位制表人') {
+                        // console.log(chkRoles);
+                    }
                     if (signature.pic) {
                         const signPath = { signature_name: signature.signature_name,  path: null, pic: null };
                         signPathArr.push(signPath);
@@ -1443,8 +1454,10 @@ function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel,
                                     signSheetIdxArr[pageIdx] = true;
                                 } else if (role.sign_path) {
                                     let sPObj = _getDupPicPath(role.sign_path);
-                                    if (sPObj !== null) {
-                                        signature.signature_name = sPObj.signature_name; //重点,如果有相同路径的,修改signature_name!
+                                    if (sPObj !== null && isSinglePage) { // 只有在isSinglePage为true时,才需要优化签名
+                                    // if (sPObj !== null) {
+                                        signature.signature_name = sPObj.signature_name; // 重点,如果有相同路径的,修改signature_name!
+                                        // signSheetIdxArr[pageIdx] = true;
                                         rst = true;
                                     } else {
                                         const signPath = { signature_name: signature.signature_name, path: null, pic: null };
@@ -1459,6 +1472,9 @@ function _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, roleRel,
                             }
                         }
                     }
+                } else {
+                    // console.log(`duplicate signature: ${signature.signature_name}`);
+                    // signSheetIdxArr[pageIdx] = true;
                 }
                 //*/
             }
@@ -1523,7 +1539,9 @@ module.exports = {
         // console.log(thisRoleRel);
 
         // const hasSignature = false; // 暂时不支持电子签名、草图导出excel
+        // fsUtil.writeObjToFile(pageData, 'D:/GitHome/temp/pageRstArrForExcelBeforeSignatureCheck.js');
         const hasSignature = _checkAndSetSignatureCache(pageData, signKeyArr, signPathArr, thisRoleRel, signSheetIdxArr, isSinglePage); // 因草图的关系,thisRoleRel是否为null就不是充要的条件
+        // fsUtil.writeObjToFile(pageData, 'D:/GitHome/temp/pageRstArrForExcelAfterSignatureCheck.js');
         // console.log('signKeyArr');
         // console.log(signKeyArr);
         // console.log('signPathArr');
@@ -1616,6 +1634,8 @@ module.exports = {
             // data = writeDrawings(pageData, signKeyArr, signPathArr, false, signSheetIdxArr); // 备注:这里根本不需要考虑是否single page
             // console.log('isSinglePage: ' + isSinglePage);
             // console.log(data);
+            // console.log('signSheetIdxArr: ');
+            // console.log(signSheetIdxArr);
             for (let psIdx = 0; psIdx < data.length; psIdx++) {
                 file = 'drawing' + (psIdx + 1) + '.xml';
                 // console.log('drawing1' + (psIdx + 1) + '.xml data');
@@ -1640,7 +1660,9 @@ module.exports = {
                 /*/
                 data = writeDrawingsRels(signKeyArr.length, relsImgAmt); // 一个drawingX.xml.rels文件与一个drawingX.xml对应
                 relsIdx++;
-                relsImgAmt += signKeyArr.length;
+                if (isSinglePage) {
+                    relsImgAmt += signKeyArr.length;
+                }
                 file = 'drawing' + relsIdx + '.xml.rels';
                 zip_drawings_rels.file(file, data.join(''), { compression: 'DEFLATE' });
                 // */
@@ -1853,8 +1875,9 @@ module.exports = {
             // 3. everything is ok, then call me
             // let roleRel = null; // 未来调用的时候,这个属性要从外面给!!!
             // roleRelArr
+            // fsUtil.writeObjToFile(newPageData, 'D:/GitHome/temp/合并前Excel数据.js');
             me.exportExcel(newPageData, paperSize, fName, 'false', sheetNames, custMergeBands, baseDir, roleRelArr, callback);
-            // fsUtil.writeObjToFile(newPageData, 'D:/GitHome/ConstructionOperation/tmp/combinedHeader.js');
+            // fsUtil.writeObjToFile(newPageData, 'D:/GitHome/temp/合并后Excel数据.js');
         } catch (e) {
             console.log(e);
         }

+ 12 - 0
app/router.js

@@ -29,6 +29,7 @@ module.exports = app => {
     const scheduleCheck = app.middlewares.scheduleCheck();
     // 修订
     const reviseCheck = app.middlewares.reviseCheck();
+    const budgetCheck = app.middlewares.budgetCheck();
     // 登入登出相关
     app.get('/login', 'loginController.index');
     app.get('/login/port', api2otherCheck, 'loginController.port');
@@ -573,4 +574,15 @@ module.exports = app => {
 
     // 决策大屏
     app.get('/datacollect', sessionAuth, 'datacollectController.index');
+
+    // 概算投资
+    app.get('/budget', sessionAuth, 'budgetController.list');
+    app.post('/budget/add', sessionAuth, 'budgetController.add');
+    app.post('/budget/del', sessionAuth, 'budgetController.del');
+    app.post('/budget/save', sessionAuth, 'budgetController.save');
+    app.get('/budget/:id/compare', sessionAuth, budgetCheck, 'budgetController.compare');
+    app.get('/budget/:id/:btype', sessionAuth, budgetCheck, 'budgetController.detail');
+    app.post('/budget/:id/:btype/load', sessionAuth, budgetCheck, 'budgetController.detailLoad');
+    app.post('/budget/:id/:btype/update', sessionAuth, tenderCheck, uncheckTenderCheck, 'budgetController.detailUpdate');
+    app.post('/budget/:id/:btype/upload-excel', sessionAuth, tenderCheck, uncheckTenderCheck, 'budgetController.detailUploadExcel');
 };

+ 140 - 0
app/service/budget.js

@@ -0,0 +1,140 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2021/11/9
+ * @version
+ */
+
+
+module.exports = app => {
+
+    class Budget extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'budget';
+        }
+
+        /**
+         * 数据规则
+         *
+         * @param {String} scene - 场景
+         * @return {Object} - 返回数据规则
+         */
+        rule(scene) {
+            let rule = {};
+            switch (scene) {
+                case 'add':
+                    rule = {
+                        name: { type: 'string', required: true, min: 2 },
+                        std_id: { type: 'string', required: true, min: 1 },
+                    };
+                    break;
+                case 'save':
+                    rule = {
+                        name: { type: 'string', required: true, min: 2, max: 100, },
+                    };
+                default:
+                    break;
+            }
+
+            return rule;
+        }
+
+
+        /**
+         * 新增标段
+         *
+         * @param {Object} data - 提交的数据
+         * @return {Boolean} - 返回新增结果
+         */
+        async add(data) {
+            const budgetStd = await this.ctx.service.budgetStd.getDataById(data.std_id);
+            if (!budgetStd) throw '选择的概算标准不存在,请刷新页面重试';
+
+            const conn = await this.db.beginTransaction();
+            try {
+                // 获取当前用户信息
+                const sessionUser = this.ctx.session.sessionUser;
+                // 获取当前项目信息
+                const sessionProject = this.ctx.session.sessionProject;
+
+                const insertData = {
+                    pid: sessionProject.id, user_id: sessionUser.accountId, in_time: new Date(),
+                    name: data.name, std_id: data.std_id,
+                };
+                const operate = await conn.insert(this.tableName, insertData);
+
+                if (operate.insertId === 0) throw '新增标段数据失败';
+
+                // 获取合同支付模板 并添加到标段
+                await this.ctx.service.budgetGu.initByTemplate(conn, operate.insertId, budgetStd.gu_template_id);
+                await this.ctx.service.budgetGai.initByTemplate(conn, operate.insertId, budgetStd.gu_template_id);
+                await this.ctx.service.budgetYu.initByTemplate(conn, operate.insertId, budgetStd.gu_template_id);
+                await conn.commit();
+                return await this.getDataById(operate.insertId);
+            } catch (error) {
+                await conn.rollback();
+                throw error;
+            }
+        }
+
+        /**
+         * 保存标段
+         *
+         * @param {Number} id
+         * @param {Object} postData - 表单post过来的数
+         * @return {Boolean} - 返回执行结果
+         */
+        async save(id, postData) {
+            const rowData = { id, name: postData.name };
+            const result = await this.db.update(this.tableName, rowData);
+            return result.affectedRows > 0;
+        }
+
+        /**
+         * 假删除
+         *
+         * @param {Number} id - 删除的id
+         * @return {Boolean} - 删除结果
+         */
+        async deleteBudget(id) {
+            const updateData = { id, status: this.status.DISABLE };
+            const result = await this.db.update(this.tableName, updateData);
+
+            return result.affectedRows > 0;
+        }
+
+        /**
+         * 真删除
+         * @param {Number} id - 删除的标段id
+         * @return {Promise<boolean>} - 结果
+         */
+        async deleteBudgetNoBackup(id) {
+            const transaction = await this.db.beginTransaction();
+            try {
+                await transaction.delete(this.tableName, { id });
+                await transaction.delete(this.ctx.service.guBudget.tableName, { bid: id });
+                await transaction.delete(this.ctx.service.gaiBudget.tableName, { bid: id });
+                await transaction.delete(this.ctx.service.yuBudget.tableName, { bid: id });
+                await transaction.commit();
+                return true;
+            } catch (err) {
+                this.ctx.log(err);
+                await transaction.rollback();
+                return false;
+            }
+        }
+    }
+
+    return Budget;
+};

+ 30 - 0
app/service/budget_gai.js

@@ -0,0 +1,30 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+
+module.exports = app => {
+    class BudgetGai extends app.BaseBudgetService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, { keyPre: 'budget_gai_maxLid:' });
+            this.tableName = 'budget_gai';
+        }
+    }
+
+    return BudgetGai;
+};

+ 30 - 0
app/service/budget_gu.js

@@ -0,0 +1,30 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+
+
+module.exports = app => {
+    class BudgetGu extends app.BaseBudgetService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, { keyPre: 'budget_gu_maxLid:' });
+            this.tableName = 'budget_gu';
+        }
+    }
+
+    return BudgetGu;
+};

+ 55 - 0
app/service/budget_std.js

@@ -0,0 +1,55 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+
+    class BudgetStd extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'budget_std';
+        }
+
+        async getDataByProjectId(pid) {
+            const project = await this.ctx.service.project.getDataById(pid);
+            const sid =  this._.map(project.budget.split(','), this._.toInteger);
+            return await this.getAllDataByCondition({
+                where: { id: sid },
+                columns: ['id', 'name'],
+            });
+        }
+
+        async getStdList(id, type) {
+            if (!type) return [[], []];
+            const gclField = type + '_bills_id', xmjField = type + '_chapter_id';
+            const std = await this.service.budgetStd.getDataById(id);
+            const billsId = std[gclField] ? this._.map(std[gclField].split(','), this._.toInteger) : [];
+            const chaptersId = std[xmjField] ? this._.map(std[xmjField].split(','), this._.toInteger) : [];
+            const sql = 'SELECT `id`, `name`' +
+                '  From ?? ' +
+                '  WHERE `id` in ( ? ) ORDER BY FIELD(`id`, ?)';
+            const sqlParam = ['zh_std_gcl_list', billsId, billsId];
+            const billsList = billsId.length > 0 ? await this.db.query(sql, sqlParam) : [];
+            const sql2 = 'SELECT `id`, `name`' +
+                '  From ?? ' +
+                '  WHERE `id` in ( ? ) ORDER BY FIELD(`id`, ?)';
+            const sqlParam2 = ['zh_std_xmj_list', chaptersId, chaptersId];
+            const chapterList = chaptersId.length > 0 ? await this.db.query(sql2, sqlParam2) : [];
+            return [billsList, chapterList];
+        }
+    }
+
+    return BudgetStd;
+};

+ 28 - 0
app/service/budget_yu.js

@@ -0,0 +1,28 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class BudgetYu extends app.BaseBudgetService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx, { keyPre: 'budget_yu_maxLid:' });
+            this.tableName = 'budget_yu';
+        }
+    }
+
+    return BudgetYu;
+};

+ 1 - 0
app/view/advance/detail.ejs

@@ -2,6 +2,7 @@
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex justify-content-between">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     第<%- advance.order %>期

+ 1 - 0
app/view/advance/index.ejs

@@ -2,6 +2,7 @@
 <div class="panel-content">
     <div class="panel-title">
         <div class="title-main d-flex justify-content-between">
+            <% include ../tender/tender_sub_mini_menu.ejs %>
             <div>
                 <div class="d-inline-block">
                     <div class="btn-group group-tab">

+ 37 - 0
app/view/budget/compare.ejs

@@ -0,0 +1,37 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="5" href="javascript: void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                            <a class="dropdown-item" name="showLevel" tag="leafXmj" href="javascript: void(0);">只显示项目节</a>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block ml-3">
+                    <a class="btn btn-sm btn-primary" href="javascript: void(0);" id="final-compare">决算对比</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0">
+        </div>
+        <div class="w-100 sub-content">
+            <div class="sjs-height-1" id="cost-compare">
+            </div>
+        </div>
+    </div>
+</div>

+ 0 - 0
app/view/budget/compare_modal.ejs


+ 121 - 0
app/view/budget/detail.ejs

@@ -0,0 +1,121 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <div>
+                <div class="d-inline-block">
+                    <div class="dropdown">
+                        <button class="btn btn-sm btn-light dropdown-toggle text-primary" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                            <i class="fa fa-list-ol"></i> 显示层级
+                        </button>
+                        <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
+                            <a class="dropdown-item" name="showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                            <a class="dropdown-item" name="showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                            <a class="dropdown-item" name="showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                            <a class="dropdown-item" name="showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                            <a class="dropdown-item" name="showLevel" tag="5" href="javascript: void(0);">第五层</a>
+                            <a class="dropdown-item" name="showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                        </div>
+                    </div>
+                </div>
+                <div class="d-inline-block">
+                    <a href="javascript:void(0)" id="insert" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="新增"><i class="fa fa-plus" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="delete" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="up-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="down-level" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="降级"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="down-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="copy" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="复制"><i class="fa fa-files-o" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="cut" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="剪切"><i class="fa fa-scissors" aria-hidden="true"></i></a>
+                    <a href="javascript:void(0)" id="paste" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="粘贴"><i class="fa fa-clipboard" aria-hidden="true"></i></a>
+                </div>
+                <div class="d-inline-block ml-3">
+                    <a class="btn btn-sm btn-primary" href="javascript: void(0);" id="budget-import">导入</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap pr-46">
+        <div class="c-header p-0">
+        </div>
+        <div class="row w-100 sub-content">
+            <div id="left-view" class="c-body" style="width: 100%">
+                <div class="sjs-height-1" id="budget-spread">
+                </div>
+            </div>
+            <div id="right-view" class="c-body" style="display: none; width: 33%;">
+                <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"></div>
+                <div class="tab-content">
+                    <div id="search" class="tab-pane tab-select-show">
+                        <div class="sjs-bar-1">
+                            <div class="input-group input-group-sm pb-1">
+                                <div class="input-group-prepend">
+                                    <div class="input-group-text">
+                                        <input type="radio" name="searchType" id="over"> 超计
+                                    </div>
+                                    <div class="input-group-text">
+                                        <input type="radio" name="searchType" id="empty"> 漏计
+                                    </div>
+                                </div>
+                                <input type="text" class="form-control form-control-sm" placeholder="可查找 项目节编号 / 清单编号 /名称" id="keyword">
+                                <div class="input-group-append">
+                                    <button class="btn btn-outline-secondary btn-sm" type="button" id="searchLedger">搜索</button>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="search-result" class="sjs-sh-1">
+                        </div>
+                    </div>
+                    <div id="std-xmj" class="tab-pane tab-select-show">
+                        <div class="sjs-bar-2">
+                            <div class="pb-1">
+                                <select class="form-control form-control-sm">
+                                    <% for (const c of stdChapters) { %>
+                                    <option value="<%- c.id %>" <% if (stdChapters.indexOf(c) === 0) { %>selected<% } %>><%- c.name %></option>
+                                    <% } %>
+                                </select>
+                            </div>
+                        </div>
+                        <div id="std-xmj-spread" class="sjs-sh-2">
+                        </div>
+                    </div>
+                    <% if (needGcl) { %>
+                    <div id="std-gcl" class="tab-pane tab-select-show">
+                        <div class="sjs-bar-3">
+                            <div class="pb-1">
+                                <select class="form-control form-control-sm">
+                                    <% for (const b of stdBills) { %>
+                                    <option value="<%- b.id %>" <% if (stdBills.indexOf(b) === 0) { %>selected<% } %>><%- b.name %></option>
+                                    <% } %>
+                                </select>
+                            </div>
+                        </div>
+                        <div id="std-gcl-spread" class="sjs-sh-3">
+                        </div>
+                    </div>
+                    <% } %>
+                </div>
+            </div>
+        </div>
+        <div class="side-menu">
+            <ul class="nav flex-column right-nav">
+                <li class="nav-item">
+                    <a class="nav-link" content="#search" href="javascript: void(0);">查找定位</a>
+                </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#std-xmj" href="javascript: void(0);">项目节</a>
+                </li>
+                <% if (needGcl) { %>
+                <li class="nav-item">
+                    <a class="nav-link" content="#std-gcl" href="javascript: void(0);">工程量清单</a>
+                </li>
+                <% } %>
+            </ul>
+        </div>
+    </div>
+</div>
+<script>
+    const needGcl = <%- needGcl %>;
+    const spreadSetting = JSON.parse('<%- JSON.stringify(spreadSetting) %>');
+</script>

+ 0 - 0
app/view/budget/detail_modal.ejs


+ 45 - 0
app/view/budget/list.ejs

@@ -0,0 +1,45 @@
+<div class="panel-content">
+    <div class="panel-title fluid">
+        <div class="title-main  d-flex justify-content-between">
+            <div class="ml-auto">
+                <a href="#add-budget" name="add" data-toggle="modal" data-target="#add-budget" class="btn btn-sm btn-primary pull-right">新建项目</a>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="sjs-height-0" style="background-color: #fff">
+            <div class="c-body">
+                <% if (!budgetList || budgetList.length === 0) { %>
+                <div class="jumbotron">
+                    <h3 class="display-6">还没有项目数据</h3>
+                </div>
+                <% } else { %>
+                <table class="table table-hover table-bordered">
+                    <thead>
+                    <tr>
+                        <th class="text-center">项目名称</th>
+                        <th class="text-center">创建人</th>
+                        <th class="text-center">创建时间</th>
+                        <th class="text-center">操作</th>
+                    </tr>
+                    </thead>
+                    <tbody>
+                    <% for (const bl of budgetList) { %>
+                    <tr>
+                        <td><a href="/budget/<%- bl.id %>/compare"><%- bl.name %></a></td>
+                        <td><%- bl.username %></td>
+                        <td><%- ctx.moment(bl.in_time).format('YYYY-MM-DD HH:mm:ss') %></td>
+                        <td>
+                            <a href="#modify-budget" data-toggle="modal" data-target="#modify-budget" class="btn btn-outline-primary btn-sm">编辑</a>
+                            <a href="#select-rela" data-toggle="modal" data-target="#select-rela" class="btn btn-outline-primary btn-sm">关联标段</a>
+                            <a href="#del-budget" data-toggle="modal" data-target="#del-budget" class="btn btn-outline-danger btn-sm ml-1">删除</a>
+                        </td>
+                    </tr>
+                    <% } %>
+                    </tbody>
+                </table>
+                <% } %>
+            </div>
+        </div>
+    </div>
+</div>

+ 115 - 0
app/view/budget/list_modal.ejs

@@ -0,0 +1,115 @@
+<!--弹出添加标段-->
+<div class="modal fade" id="add-budget" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">添加新项目</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>项目名称<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm"  placeholder="输入项目名称" type="text" id="add-budget-name" onchange="budgetNameChange(this);">
+                    <div class="invalid-feedback">名称超过100个字,请缩减名称。</div>
+                </div>
+                <div class="form-group">
+                    <label>概预算标准<b class="text-danger">*</b></label>
+                    <div>
+                        <% for (const bs of budgetStd) { %>
+                        <div class="form-check form-check-inline mt-2">
+                            <input class="form-check-input" name="std_id" type="radio" id="std<%- bs.id %>" value="<%- bs.id %>" checked="">
+                            <label class="form-check-label" for="std<%- bs.id %>"><%- bs.name %></label>
+                        </div>
+                        <% } %>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="add-budget-ok" onclick="addBudget();">确定添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--编辑标段-->
+<div class="modal fade" id="modify-budget" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">编辑标段</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <label>项目名称<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm is-invalid"  placeholder="输入项目名称" type="text" value="名称超过100个字">
+                    <div class="invalid-feedback">
+                        名称超过100个字,请缩减名称。
+                    </div>
+                </div>
+
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-primary">确定修改</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--删除标段-->
+<div class="modal fade" id="del-budget" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">删除项目</h5>
+            </div>
+            <div class="modal-body">
+                <h6>确认删除「XXX高速公路」?</h6>
+                <h6>删除后,数据无法恢复,请谨慎操作。</h6>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-danger">确定删除</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--弹出关联标段-->
+<div class="modal fade" id="select-rela" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择决算标段</h5>
+            </div>
+            <div class="modal-body">
+                <h5>可选标段</h5>
+                <table class="table table-sm table-bordered">
+                    <thead>
+                    <tr class="text-center">
+                        <th>选择</th>
+                        <th>标段名称</th>
+                        <th>期数</th>
+                        <th>状态</th>
+                    </tr>
+                    </thead>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第一合同段</td><td>第15期</td><td>审批完成</td>
+                    </tr>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第二合同段</td><td>第16期</td><td>审批完成</td>
+                    </tr>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第三合同段</td><td>第15期</td><td>审批完成</td>
+                    </tr>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第四合同段</td><td>第14期</td><td>审批完成</td>
+                    </tr>
+                </table>
+            </div>
+            <div class="modal-footer d-flex justify-content-between">
+                <div>
+                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                    <button type="button" class="btn btn-sm btn-primary" >确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>

+ 14 - 0
app/view/budget/sub_menu.ejs

@@ -0,0 +1,14 @@
+<div class="panel-sidebar" id="sub-menu">
+    <div class="sidebar-title" data-toggle="tooltip" data-placement="right" data-original-title="<%- ctx.budget.name %>">
+        <%- (ctx.budget.name.length > 15 ? ctx.budget.name.substring(0,15) + '...' : ctx.budget.name) %>
+    </div>
+    <div class="scrollbar-auto">
+        <% include ./sub_menu_list.ejs %>
+        <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-upload fa-rotate-270"></i></a></div>
+    </div>
+    <script>
+        new Vue({
+            el: '.scrollbar-auto',
+        });
+    </script>
+</div>

+ 5 - 0
app/view/budget/sub_menu_list.ejs

@@ -0,0 +1,5 @@
+<nav-menu title="返回" url="/budget" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
+<nav-menu title="造价对比" url="/budget/<%= ctx.budget.id %>/compare" ml="3" active="<%= ctx.url.indexOf('/compare') %>"></nav-menu>
+<nav-menu title="投资估算" url="/budget/<%= ctx.budget.id %>/gu" ml="3" active="<%= ctx.url.indexOf('/gu') %>"></nav-menu>
+<nav-menu title="初步概算" url="/budget/<%= ctx.budget.id %>/gai%>" ml="3" active="<%= ctx.url.indexOf('/gai') %>"></nav-menu>
+<nav-menu title="施工图预算" url="/budget/<%= ctx.budget.id %>/yu" ml="3" active="<%= ctx.url.indexOf('/yu') %>"></nav-menu>

+ 16 - 0
app/view/budget/sub_mini_menu.ejs

@@ -0,0 +1,16 @@
+<!--折起的菜单-->
+<div class="min-side" id="sub-mini-menu" style="display: none;">
+    <div id="sub-mini-hint" class="side-switch" data-container="body" data-toggle="popover" data-placement="bottom" data-content="这里打开收起的菜单栏"></div>
+    <div class="side-switch">
+        <i class="fa fa-bars"></i>
+    </div>
+    <div class="side-menu" id="mini-menu-list" style="display: none">
+        <% include ./sub_menu_list.ejs %>
+        <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-upload fa-rotate-90"></i></a></div>
+    </div>
+</div>
+<script>
+    new Vue({
+        el: '.side-menu',
+    });
+</script>

+ 5 - 0
app/view/report/index.ejs

@@ -75,6 +75,10 @@
                                 <div class="panel-body">
                                     <div class="btn-group" role="group">
                                         <button id="btnArchiveRpt" type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" data-target="#guidang" disabled><i class="fa fa-archive"></i> 归档报表</button>
+                                        <!--
+                                        <button id="btnBatchArchiveRpt" type="button" class="btn btn-outline-primary btn-sm" onclick="rptArchiveObj.batchArchive()"><i class="fa fa-archive"></i>批量归档</button>
+                                        <button id="btnBatchArchiveRpt" type="button" class="btn btn-outline-primary btn-sm" onclick="rptArchiveObj.batchArchiveTest()"><i class="fa fa-archive"></i>测试批量归档</button>
+                                        -->
                                         <div class="btn-group" role="group">
                                             <button id="btnArchiveList" type="button" class="btn btn-success btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" disabled>
                                                 已归档
@@ -309,6 +313,7 @@
 <script type="text/javascript" src="/public/report/js/rpt_print.js"></script>
 <script type="text/javascript" src="/public/report/js/rpt_signature.js"></script>
 <script type="text/javascript" src="/public/report/js/rpt_jspdf.js"></script>
+<script type="text/javascript" src="/public/report/js/rpt_indexDb.js"></script>
 
 <!--
 <script type="text/javascript" src="/public/report/js/rpt_custom.js"></script>

+ 8 - 0
config/menu.js

@@ -33,6 +33,14 @@ const menu = {
         children: null,
         caption: '项目',
     },
+    budget: {
+        name: '概算投资',
+        icon: 'fa-pie-chart',
+        display: true,
+        url: '/budget',
+        children: null,
+        caption: '概算投资',
+    },
     // sum: {
     //     name: '总分包',
     //     icon: 'fa-sitemap',

+ 44 - 2
config/web.js

@@ -748,12 +748,12 @@ const JsFiles = {
         advance: {
             main: {
                 files: ['/public/js/decimal.min.js'],
-                mergeFiles: ['/public/js/zh_calc.js', '/public/js/advance.js'],
+                mergeFiles: ['/public/js/zh_calc.js', '/public/js/advance.js', '/public/js/sub_menu.js'],
                 mergeFile: 'advance',
             },
             info: {
                 files: ['/public/js/decimal.min.js'],
-                mergeFiles: ['/public/js/zh_calc.js', '/public/js/advance_audit.js'],
+                mergeFiles: ['/public/js/zh_calc.js', '/public/js/advance_audit.js', '/public/js/sub_menu.js'],
                 mergeFile: 'advance_audit',
             },
         },
@@ -881,6 +881,48 @@ const JsFiles = {
                 mergeFile: 'index',
             },
         },
+        budget: {
+            list: {
+                files: ['/public/js/moment/moment.min.js'],
+                mergeFiles: [
+                    '/public/js/budget_list.js',
+                ],
+                mergeFile: 'budget_list',
+            },
+            compare: {
+                files: [
+                    '/public/js/moment/moment.min.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/component/menu.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/budget_compare.js',
+                ],
+                mergeFile: 'budget_compare.js'
+            },
+            detail: {
+                files: [
+                    '/public/js/moment/moment.min.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/decimal.min.js',
+                    '/public/js/component/menu.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/div_resizer.js',
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/zh_calc.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/budget_detail.js',
+                ],
+                mergeFile: 'budget_detail.js'
+            }
+        }
     },
 };
 

+ 21 - 1
sql/update.sql

@@ -17,6 +17,26 @@ CREATE TABLE `zh_pay_attachment` (
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 
-ALTER TABLE `zh_change_audit_list` ADD `mx_id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '计量单元id' AFTER `gcl_id`;
+ALTER TABLE `zh_change_audit_list` ADD `mx_id` VARCHAR(50) NULL DEFAULT NULL COMMENT '计量单元id' AFTER `gcl_id`;
 
 ALTER TABLE `zh_change_audit_list` CHANGE `gcl_id` `gcl_id` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '台账对应id(非准确)';
+
+-- 估概预标准
+INSERT INTO `calculation`.`zh_permission` (`id`, `name`, `controller`, `action`, `pid`, `icon_class`, `create_time`, `isshow`) VALUES ('72', '估概预标准', 'budget', 'all', '44', '', '0', '1');
+
+ALTER TABLE `zh_project`
+ADD COLUMN `budget`  varchar(100) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL DEFAULT '' COMMENT '估概预标准-id列表(\',\'分隔)' AFTER `valuation`;
+
+CREATE TABLE `zh_budget_std` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '估概预标准id',
+  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '名称',
+  `gu_chapter_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '估算-项目节-id列表('',''分隔)',
+  `gu_template_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '估算-新建模板-id列表('',''分隔)',
+  `gai_chapter_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '概算-项目节-id列表('',''分隔)',
+  `gai_template_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '概算-新建模板-id列表('',''分隔)',
+  `yu_chapter_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '预算-项目节-id列表('',''分隔)',
+  `yu_template_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '预算-新建模板-id列表('',''分隔)',
+  `yu_bills_id` varchar(255) CHARACTER SET ascii NOT NULL DEFAULT '' COMMENT '预算-工程量清单-id列表('',''分隔)',
+  `in_time` datetime NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='估概预标准列表';