Browse Source

单元指标

MaiXinRong 5 years ago
parent
commit
276b49c867

+ 103 - 0
app/controller/unit_compare_controller.js

@@ -0,0 +1,103 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const libConst = require('../const/lib');
+module.exports = app => {
+    class UnitCompareController extends app.BaseController {
+        /**
+         * 指标对比页面
+         *
+         * @param {object} ctx - egg全局context
+         * @return {void}
+         */
+        async index (ctx) {
+            try {
+                const libList = await ctx.service.quotaLib.getList(libConst.status.enter);
+                const dyNames = await ctx.service.templateNode.getDanYuanNames();
+                const xMatchDyNames = await ctx.service.templateNode.getXMatchDanYuanNames();
+                for (const dn of dyNames) {
+                    dn.indexClass = this.app.nodeConst.indexClass.dy;
+                    const x = xMatchDyNames.find(function (a) {
+                        return a.name === dn.name;
+                    });
+                    dn.xMatch = false || (x !== null && x !== undefined);
+                }
+                const renderData = {
+                    libList,
+                    nodeConst: this.app.nodeConst,
+                    dyNames,
+                };
+                await this.layout('unit_compare/index.ejs', renderData, 'unit_compare/modal.ejs');
+            } catch(err) {
+                console.log(err);
+            }
+        }
+
+        async getParent(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+
+                if (!data.name) throw '选择单元指标错误';
+                if (!data.xMatch) throw '该单元指标无可对比项,请刷新页面重新';
+                if (!data.lib_id && data.lib_id < 0) throw '选择造价文件错误';
+
+                const parent = await this.ctx.service.tenderNode.searchParent(data.lib_id, data.name);
+
+                ctx.body = {err: 0, msg: '', data: parent}
+            } catch (err) {
+                console.log(err);
+                if (err.stack) {
+                    ctx.body = {err: 1, msg: '获取对比项失败,请重试', data: []};
+                } else {
+                    ctx.body = {err: 1, msg: err, data: []};
+                }
+            }
+        }
+
+        async search(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                console.log(data);
+
+                if (!data.lib_id) {
+                    throw '查询造价文件数据错误';
+                }
+                if (!data.name) {
+                    throw '查询的指标分类错误';
+                }
+                const condition = {lib_id: data.lib_id, class_name: data.name};
+                if (data.xMatch) {
+                    if (!data.selects || data.selects.length === 0) {
+                        throw '查询的对比项错误';
+                    }
+                    condition.bills_xid = [];
+                    for (const s of data.selects) {
+                        condition.bills_xid.push(s.id);
+                    }
+                }
+
+                const nodes = await ctx.service.tenderNode.getAllDataByCondition({ where: condition });
+                for (const n of nodes) {
+                    const condition = {
+                        lib_id: n.lib_id,
+                        node_id: n.node_id,
+                    };
+                    n.children = await ctx.service.tenderIndex.getAllDataByCondition({where: condition});
+                }
+                ctx.body = {err: 0, msg: '', data: nodes};
+            } catch (err) {
+                ctx.body = {err: 1, msg: err.toString(), data: []};
+                console.log(err);
+            }
+        }
+    }
+
+    return UnitCompareController;
+};

+ 0 - 2
app/public/js/compare.js

@@ -70,7 +70,6 @@ $(document).ready(function () {
 
             if (this.tenders.length > 0) {
                 this.sheet.getCell(0, cols.unit2+1).text('经济指标').hAlign(hCenter).vAlign(vCenter);
-                //this.sheet.removeSpan(0, cols.unit2+1, spreadNS.SheetArea.viewport);
                 this.sheet.addSpan(0, cols.unit2+1, 1, this.tenders.length);
                 for (let i = 0, iLen = this.tenders.length; i < iLen; i++) {
                     colsName.push('lib_' + this.tenders[i].lib_id);
@@ -83,7 +82,6 @@ $(document).ready(function () {
                 colsName.push('averageIndex');
                 cols.averageIndex = 4+this.tenders.length;
                 this.sheet.getCell(0, cols.averageIndex).text('平均指标').hAlign(hCenter).vAlign(vCenter);
-                //this.sheet.removeSpan(0, cols.averageIndex, spreadNS.SheetArea.viewport);
                 this.sheet.addSpan(0, cols.averageIndex, 2, 1);
                 this.sheet.setColumnWidth(cols.averageIndex, 100);
             }

+ 402 - 0
app/public/js/unit_compare.js

@@ -0,0 +1,402 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+$(document).ready(() => {
+    const spreadNS = GC.Spread.Sheets;
+    const spread = SpreadJsObj.createNewSpread($('.sjs-height-1')[0]);
+    const sheet = spread.getActiveSheet();
+
+    const hCenter = spreadNS.HorizontalAlign.center;
+    const vCenter = spreadNS.VerticalAlign.center;
+
+    const compareObj = {
+        cacheList: [],
+        sortNodeData: function (data) {
+            data.sort(function (a, b) {
+                if (a.code > b.code) {
+                    return 1;
+                } else if (a.code < b.code) {
+                    return -1;
+                } else {
+                    return 0;
+                }
+            });
+        },
+        showCompareData: function () {
+            const cols = compareObj.cols, colsName = compareObj.colsName;
+            const colCount = colsName.length;
+            function loadNode (node, row) {
+                sheet.getCell(row, compareObj.cols.code).text(node.code).wordWrap(true).vAlign(vCenter);
+                sheet.getCell(row, compareObj.cols.name).text(node.name).wordWrap(true).vAlign(vCenter);
+                sheet.getRange(row, 0, 1, colCount).backColor('#dae5ee');
+                sheet.autoFitRow(row);
+            }
+            function loadIndex(index, row) {
+                for (const colName of colsName) {
+                    if (colName === 'averageIndex') {
+                        const aver = Number((index.sumValue / index.sumCount).toFixed(2));
+                        sheet.getCell(row, cols[colName]).value(aver).wordWrap(true).vAlign(vCenter);
+                    } else if (index[colName]) {
+                        sheet.getCell(row, cols[colName]).value(index[colName]).wordWrap(true).vAlign(vCenter);
+                    }
+                }
+                sheet.autoFitRow(row);
+            }
+
+            SpreadJsObj.beginMassOperationSheet(sheet);
+
+            let iRow = 2;
+            sheet.setRowCount(2);
+            console.log(compareObj.showData);
+            for (const sd of compareObj.showData) {
+                sheet.addRows(iRow, 1);
+                loadNode(sd, iRow);
+                iRow += 1;
+                for (const index of sd.children) {
+                    sheet.addRows(iRow, 1);
+                    loadIndex(index, iRow);
+                    iRow += 1;
+                }
+            }
+
+            SpreadJsObj.endMassOperationSheet(sheet);
+        },
+        initXMatchCompareHeader: function (data) {
+            const cols = {}, colsName = [];
+            const indexColCount = data.selects.length + 1;
+            SpreadJsObj.beginMassOperationSheet(sheet);
+
+            const spans = sheet.getSpans();
+            for (const span of spans) {
+                sheet.removeSpan(span.row, span.col);
+            }
+            sheet.setColumnCount(6 + indexColCount);
+            sheet.setRowCount(2);
+            sheet.frozenRowCount(2);
+
+            colsName.push('code');
+            cols.code = 0;
+            sheet.getCell(0, cols.code).text('指标编号').hAlign(hCenter).vAlign(vCenter);
+            sheet.addSpan(0, cols.code, 2, 1);
+            sheet.setColumnWidth(cols.code, 80);
+
+            colsName.push('name');
+            cols.name = 1;
+            sheet.getCell(0, cols.name).text('项目或费用名称').hAlign(hCenter).vAlign(vCenter);
+            sheet.addSpan(0, cols.name, 2, 1);
+            sheet.setColumnWidth(cols.name, 220);
+
+            colsName.push('unit1');
+            cols.unit1 = 2;
+            colsName.push('unit2');
+            cols.unit2 = 3;
+            sheet.getCell(0, cols.unit1).text('指标单位').hAlign(hCenter).vAlign(vCenter);
+            sheet.addSpan(0, cols.unit1, 2, 2);
+            sheet.setColumnWidth(cols.unit1, 65);
+            sheet.setColumnWidth(cols.unit2, 65);
+
+            if (data.selects.length > 0) {
+                sheet.getCell(0, cols.unit2+1).text('经济指标').hAlign(hCenter).vAlign(vCenter);
+                sheet.addSpan(0, cols.unit2+1, 1, indexColCount - 1);
+
+                for (let i = 0, iLen = indexColCount - 1; i < iLen; i++) {
+                    const colName = 'bills_xid' + (data.selects[i].id > 0 ? '_' + data.selects[i].id : '');
+                    colsName.push(colName);
+                    cols[colName] = 4+i;
+                    sheet.getCell(1, cols[colName]).text(data.selects[i].id > 0 ? data.selects[i].name : '其他')
+                        .hAlign(hCenter).vAlign(vCenter).wordWrap(true);
+                    sheet.setColumnWidth(cols[colName], 150);
+                }
+
+                colsName.push('averageIndex');
+                cols.averageIndex = 4 + indexColCount - 1;
+                sheet.getCell(0, cols.averageIndex).text('平均指标').hAlign(hCenter).vAlign(vCenter);
+                sheet.addSpan(0, cols.averageIndex, 2, 1);
+                sheet.setColumnWidth(cols.averageIndex, 100);
+            }
+
+            colsName.push('rule');
+            cols.rule = 4 + indexColCount;
+            sheet.getCell(0, cols.rule).text('计算规则').hAlign(hCenter).vAlign(vCenter);
+            sheet.addSpan(0, cols.rule, 2, 1);
+            sheet.setColumnWidth(cols.rule, 300);
+
+            colsName.push('memo');
+            cols.memo = 5 + indexColCount;
+            sheet.getCell(0, cols.memo).text('备注').hAlign(hCenter).vAlign(vCenter);
+            sheet.addSpan(0, cols.memo, 2, 1);
+            sheet.setColumnWidth(cols.memo, 100);
+
+            sheet.autoFitRow(0);
+            sheet.autoFitRow(1);
+
+            compareObj.colsName = colsName;
+            compareObj.cols = cols;
+            SpreadJsObj.endMassOperationSheet(sheet);
+        },
+        sortXMatchCompareData: function (data) {
+            function findNode(data, src, xid) {
+                for (const node of data) {
+                    const field = xid > 0 ? 'bills_xid_' + xid : 'bills_xid';
+                    if (node.code === src.code && node.name === src.name && node[field] === src[field]) {
+                        return node;
+                    }
+                }
+                return null;
+            }
+            function findIndex(data, src) {
+                for (const index of data) {
+                    if (index.code === src.code && index.name === src.name) {
+                        return index;
+                    }
+                }
+                return null;
+            }
+            const result = [];
+            for (const select of compareObj.data.selects) {
+                const searchData = data.filter(function (x) { return x.bills_xid === select.id});
+                for (const sd of searchData) {
+                    let sortNode = findNode(result, sd, select.id);
+                    if (!sortNode) {
+                        sortNode = {
+                            code: sd.code,
+                            name: sd.name,
+                            children: [],
+                        };
+                        result.push(sortNode);
+                    }
+                    if (select.id > 0) {
+                        sortNode['bills_xid_' + select.id] = sd.bills_xid;
+                    } else {
+                        sortNode['bills_xid'] = sd.bills_xid;
+                    }
+                    for (const index of sd.children) {
+                        let sortIndex = findIndex(sortNode.children, index);
+                        if (!sortIndex) {
+                            sortIndex = {
+                                code: index.code,
+                                name: index.name,
+                                unit1: index.unit1,
+                                unit2: index.unit2,
+                                sumValue: 0,
+                                sumCount: 0,
+                                rule: index.rule,
+                                memo: index.memo,
+                            };
+                            sortNode.children.push(sortIndex)
+                        }
+                        if (select.id > 0) {
+                            sortIndex['bills_xid_' + select.id] = index.value;
+                        } else {
+                            sortIndex['bills_xid'] = index.value;
+                        }
+                        sortIndex.sumValue += index.value;
+                        sortIndex.sumCount += 1;
+                    }
+                }
+            }
+            compareObj.sortNodeData(result);
+            return result;
+        },
+        loadXMatchCompareData(data) {
+            compareObj.initXMatchCompareHeader(compareObj.data);
+            compareObj.showData = compareObj.sortXMatchCompareData(data);
+            compareObj.showCompareData();
+        },
+        initCommonCompareHeader: function () {
+            const cols = {}, colsName = [];
+            SpreadJsObj.beginMassOperationSheet(sheet);
+
+            const spans = sheet.getSpans();
+            for (const span of spans) {
+                sheet.removeSpan(span.row, span.col);
+            }
+            sheet.setColumnCount(7);
+            sheet.setRowCount(2);
+            sheet.frozenRowCount(2);
+
+            colsName.push('code');
+            cols.code = 0;
+            sheet.getCell(0, cols.code).text('指标编号').hAlign(hCenter).vAlign(vCenter);
+            sheet.addSpan(0, cols.code, 2, 1);
+            sheet.setColumnWidth(cols.code, 80);
+
+            colsName.push('name');
+            cols.name = 1;
+            sheet.getCell(0, cols.name).text('项目或费用名称').hAlign(hCenter).vAlign(vCenter);
+            sheet.addSpan(0, cols.name, 2, 1);
+            sheet.setColumnWidth(cols.name, 220);
+
+            colsName.push('unit1');
+            cols.unit1 = 2;
+            colsName.push('unit2');
+            cols.unit2 = 3;
+            sheet.getCell(0, cols.unit1).text('指标单位').hAlign(hCenter).vAlign(vCenter);
+            sheet.addSpan(0, cols.unit1, 2, 2);
+            sheet.setColumnWidth(cols.unit1, 65);
+            sheet.setColumnWidth(cols.unit2, 65);
+
+            colsName.push('value');
+            cols.name = 4;
+            sheet.getCell(0, cols.name).text('经济指标').hAlign(hCenter).vAlign(vCenter);
+            sheet.addSpan(0, cols.name, 2, 1);
+            sheet.setColumnWidth(cols.name, 220);
+
+            colsName.push('rule');
+            cols.rule = 5;
+            sheet.getCell(0, cols.rule).text('计算规则').hAlign(hCenter).vAlign(vCenter);
+            sheet.addSpan(0, cols.rule, 2, 1);
+            sheet.setColumnWidth(cols.rule, 300);
+
+            colsName.push('memo');
+            cols.memo = 6;
+            sheet.getCell(0, cols.memo).text('备注').hAlign(hCenter).vAlign(vCenter);
+            sheet.addSpan(0, cols.memo, 2, 1);
+            sheet.setColumnWidth(cols.memo, 100);
+
+            compareObj.colsName = colsName;
+            compareObj.cols = cols;
+            SpreadJsObj.endMassOperationSheet(sheet);
+        },
+        loadCommonCompareData(data) {
+            compareObj.initCommonCompareHeader();
+            compareObj.sortNodeData(data);
+            this.showData = data;
+            compareObj.showCompareData();
+        },
+        loadCompareData: function (data) {
+            compareObj.resultData = data;
+            if (compareObj.data.xMatch) {
+                compareObj.loadXMatchCompareData(data);
+            } else {
+                compareObj.loadCommonCompareData(data);
+            }
+        },
+        load: function () {
+            postData(window.location.pathname + '/search', compareObj.data, function (result) {
+                compareObj.lastData = compareObj.data;
+                $('#select-lib').text(compareObj.data.lib_name + ': ' + compareObj.data.name);
+                compareObj.loadCompareData(result);
+                if (compareObj.data.xMatch) {
+                    $('#select-data').modal('hide');
+                } else {
+                    $('#generate-data').modal('hide');
+                }
+            });
+        },
+        generateXList: function (arr) {
+            $('#select-title').text('选择对比项 —— ' + compareObj.data.lib_name + ': ' + compareObj.data.name);
+            const html = [];
+            for (const r of arr) {
+                html.push('<tr>');
+                html.push('<td class="text-center">', "<input x-data='" + JSON.stringify({id: r.bills_xid, name: r.name, code: r.code}) + "' type='checkbox'>", '</td>');
+                html.push('<td>', r.code, '</td>');
+                html.push('<td>', r.bills_xid === -1 ? '其他' : r.name, '</td>');
+                html.push('<td class="text-center">', r.units, '</td>');
+                html.push('<td class="text-right">', r.dgn_quantity1, '</td>');
+                html.push('<td class="text-right">', r.dgn_quantity2, '</td>');
+                html.push('<td class="text-right">', r.dgn_price, '</td>');
+                html.push('<td class="text-right">', r.total_price, '</td>');
+                html.push('</tr>');
+            }
+            $('#x-list').html(html.join(''));
+            $('#select-data').modal('show');
+            $('#generate-data').modal('hide');
+        },
+        loadParentNode: function () {
+            const history = compareObj.cacheList.find(function (l) {
+                return l.lib_id === compareObj.data.lib_id && l.className === compareObj.data.className;
+            });
+            if (!history) {
+                postData(window.location.pathname + '/parent', compareObj.data, function (result) {
+                    compareObj.cacheList.push({lib_id: compareObj.data.lib_id, className: compareObj.data.className, xList: result});
+                    compareObj.generateXList(result);
+                });
+            } else {
+                compareObj.generateXList(history.xList);
+            }
+        },
+    };
+    compareObj.initCommonCompareHeader();
+    $('#generate-data').modal('show');
+    $('.dropdown-item').click(function () {
+        $('#btnGroupDrop1').text($(this).text()).attr('data', $(this).attr('data'));
+        if ($('#hint1').attr('hint-type') === 'dy') {
+            $('#hint1').hide();
+        }
+    });
+    $('input[type=radio]').bind('click', function () {
+        $('input[type=radio]').parent().parent().addClass('table-secondary');
+        if (this.checked) {
+            $(this).parent().parent().removeClass('table-secondary');
+        }
+        if ($('#hint1').attr('hint-type') === 'bd') {
+            $('#hint1').hide();
+        }
+    });
+    $('#next').click(function () {
+        const dy = $('#btnGroupDrop1').attr('data');
+        if (!dy) {
+            $('#hint1').attr('hint-type', 'dy').html('未选择单元指标类型').show();
+            return;
+        }
+        const bd = $('input[type=radio]:checked')[0];
+        if (!bd) {
+            $('#hint1').attr('hint-type', 'bd').html('未选择造价文件').show();
+            return;
+        }
+        compareObj.data = JSON.parse(dy);
+        const lib_data = JSON.parse(bd.getAttribute('lib-data'));
+        compareObj.data.lib_id = lib_data.id;
+        compareObj.data.lib_name = lib_data.filename;
+        if (compareObj.data.xMatch) {
+            compareObj.loadParentNode();
+        } else {
+            compareObj.load();
+        }
+    });
+    $('#search-x').click(function () {
+        const keyword = $('#x-keyword').val();
+        if (keyword === '') return;
+
+        const tr = $('tr', '#x-list');
+        tr.removeClass('table-warning');
+        for (const r of tr) {
+            const name = r.children[2].innerText;
+            if (name.indexOf(keyword) >= 0) {
+                $('#hint2').hide();
+                r.classList.add('table-warning');
+                return;
+            }
+        }
+        $('#hint2').html('未能搜索到匹配项').show();
+    });
+    $('#select').click(function () {
+        if (compareObj.data.xMatch) {
+            if (!compareObj.lastData || compareObj.lastData.lib_id !== compareObj.data.lib_id || compareObj.lastData.name !== compareObj.data.name) {
+                compareObj.loadParentNode();
+            } else {
+                $('#select-data').modal('show');
+            }
+        }
+    });
+    $('#select-ok').click(function () {
+        const select = $('input[type=checkbox]:checked');
+        if (select.length === 0) {
+            $('#hint2').html('未选择对比项').show();
+            return;
+        }
+        compareObj.data.selects = [];
+        for (const s of select) {
+            compareObj.data.selects.push(JSON.parse(s.getAttribute('x-data')));
+        }
+        compareObj.load();
+    });
+});

+ 5 - 0
app/router.js

@@ -36,4 +36,9 @@ module.exports = app => {
     app.get('/compare', sessionAuth, 'compareController.index');
     app.post('/compare/search', sessionAuth, 'compareController.search');
     app.post('/compare/searchClass', sessionAuth, 'compareController.searchClass');
+
+    // 单元指标对比
+    app.get('/unitCompare', sessionAuth, 'unitCompareController.index');
+    app.post('/unitCompare/parent', sessionAuth, 'unitCompareController.getParent');
+    app.post('/unitCompare/search', sessionAuth, 'unitCompareController.search');
 };

+ 42 - 8
app/service/match.js

@@ -67,10 +67,11 @@ module.exports = app => {
             return true;
         }
 
-        _matchCodeByRules(code, rules) {
+        _matchCodeByRules(code, rules, bills) {
             const ruleArr = rules.split(';');
             for (const r of ruleArr) {
                 if (this._matchCodeByRule(code, r)) {
+                    bills.match_key = r;
                     return true;
                 }
             }
@@ -89,9 +90,14 @@ module.exports = app => {
             return this.bills.filter(function (b) {
                 if (!b.match_node) {
                     if (node.match_type === nodeConst.matchType.code) {
-                        return self._matchCodeByRules(b.code, node.match_key);
+                        return self._matchCodeByRules(b.code, node.match_key, b);
                     } else {
-                        return node.match_key === b.name;
+                        if (node.match_key === b.name) {
+                            b.match_key = b.name;
+                            return true;
+                        } else {
+                            return false;
+                        }
                     }
                 } else {
                     return false;
@@ -159,7 +165,7 @@ module.exports = app => {
          */
         _getCodeParamValue(param) {
             for (const b of this.bills) {
-                if (this._matchCodeByRules(b.code, param.match_key)) {
+                if (this._matchCodeByRules(b.code, param.match_key, b)) {
                     return this._getNodeBillsValue(param, b);
                 }
             }
@@ -218,7 +224,7 @@ module.exports = app => {
                     match_type: np.match_type,
                     match_key: np.match_key,
                     match_num: np.match_num,
-                }
+                };
                 newParam.match_value = this._getParamValue(newParam, nodeBills);
                 newParam.calc_value = newParam.match_value;
                 this.params.push(newParam);
@@ -253,6 +259,30 @@ module.exports = app => {
             }
         }
 
+        _getXMatchId(bills) {
+            const reg = /-([a-z])-/i;
+            if (reg.test(bills.match_key)) {
+                const ruleParts = bills.match_key.split('-');
+                const charReg = /(^[a-z]+$)/i;
+                let node = bills, parent = bills;
+                for (let i = ruleParts.length - 1; i>= 0; i--) {
+                    if (parent) {
+                        if (charReg.test(ruleParts[i])) {
+                            return parent.n_id;
+                        }
+                        node = parent;
+                        parent = this.bills.find(function(x) {
+                            return x.n_id === node.n_pid;
+                        });
+                    } else {
+                        return -1;
+                    }
+                }
+            } else {
+                return -1;
+            }
+        }
+
         /**
          * 匹配指标节点
          * @param node
@@ -270,8 +300,9 @@ module.exports = app => {
                     code: node.code,
                     name: node.name,
                     match_type: node.match_type,
-                    match_key: node.match_key,
+                    match_key: mb.match_key,
                     bills_id: mb.n_id,
+                    bills_xid: node.match_type === nodeConst.matchType.code ? this._getXMatchId(mb) : -1,
                     index_class: node.index_class,
                     class_name: node.class_name,
                 };
@@ -312,8 +343,11 @@ module.exports = app => {
                 });
                 this.ctx.service.indexCalc.calculate(nodeIndexes, globalParams, nodeParams);
             }
+            for (const b of this.bills) {
+                delete b.match_key;
+            }
         }
-    };
+    }
 
     return Match;
-}
+};

+ 9 - 0
app/service/template_node.js

@@ -274,6 +274,15 @@ module.exports = app => {
             return names;
         }
 
+        async getXMatchDanYuanNames() {
+            const sql = 'SELECT `class_name` As name FROM ' + this.tableName +
+                '  WHERE index_class = ? and match_key REGEXP "-[a-z]-"' +
+                '  GROUP By `class_name`';
+            const sqlParam = [nodeConst.indexClass.dy];
+            const names = await this.db.query(sql, sqlParam);
+            return names;
+        }
+
     }
 
     return TemplateNode;

+ 11 - 0
app/service/tender_node.js

@@ -42,6 +42,17 @@ module.exports = app => {
                 return await this.db.query(sql, sqlParam);
             }
         }
+
+        async searchParent(tenderId, className) {
+            const sql = 'SELECT tn.bills_xid, qb.code, qb.name, qb.units, qb.dgn_quantity1, qb.dgn_quantity2, qb.dgn_price, qb.total_price' +
+                '  FROM ' + this.tableName + ' As tn' +
+                '  LEFT JOIN ' + this.ctx.service.bills.tableName + ' As qb ON tn.bills_xid = qb.n_id' +
+                '  WHERE tn.`lib_id` = ? and tn.class_name = ?' +
+                '  GROUP By tn.bills_xid' +
+                '  ORDER By qb.code ASC';
+            const param = [tenderId, className];
+            return await this.db.query(sql, param);
+        }
     };
 
     return TenderNode;

+ 44 - 0
app/view/unit_compare/index.ejs

@@ -0,0 +1,44 @@
+<div class="panel-content">
+    <div class="panel-title fluid">
+        <div class="title-main d-flex">
+            <div>
+                <div class="d-inline-flex">
+                    <a class="btn btn-primary btn-sm" href="#generate-data" data-toggle="modal" data-target="#generate-data" id="select-lib">选择造价文件</a>
+                </div>
+                <div class="d-inline-flex">
+                    <button type="button" class="btn btn-primary btn-sm" data-toggle="modal" id="select">选择对比项</button>
+                </div>
+                <div class="d-inline-flex">
+                    <div class="input-group input-group-sm">
+                        <input type="text" class="form-control" placeholder="输入指标节点编号或者名称" aria-label="Recipient's username" aria-describedby="button-addon2" style="width:220px">
+                        <div class="input-group-append">
+                            <button class="btn btn-primary" type="button" id="button-addon2">搜索</button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="ml-auto">
+                <div class="d-inline-block">
+                    <a href="search-Library-print.html" class="btn btn-primary btn-sm" target="_blank"><i class="fa fa-file-excel-o"></i> 导出Excel</a>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-header p-0 col-12">
+        </div>
+        <div class="c-body">
+            <div class="sjs-height-1" style="overflow:hidden;border:1px solid #dee2e6">
+            </div>
+        </div>
+    </div>
+</div>
+<script src="/public/js/file-saver/FileSaver.js"></script>
+<script src="/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js"></script>
+<script src="/public/js/spreadjs/sheets/pluggable/gc.spread.sheets.print.10.0.1.min.js"></script>
+<script src="/public/js/spreadjs/sheets/interop/gc.spread.excelio.10.0.1.min.js"></script>
+<script>
+    GC.Spread.Sheets.LicenseKey = "559432293813965#A0y3iTOzEDOzkjMyMDN9UTNiojIklkI1pjIEJCLi4TPB9mM5AFNTd4cvZ7SaJUVy3CWKtWYXx4VVhjMpp7dYNGdx2ia9sEVlZGOTh7NRlTUwkWR9wEV4gmbjBDZ4ElR8N7cGdHVvEWVBtCOwIGW0ZmeYVWVr3mI0IyUiwCMzETN8kzNzYTM0IicfJye&Qf35VfiEzRwEkI0IyQiwiIwEjL6ByUKBCZhVmcwNlI0IiTis7W0ICZyBlIsIyNyMzM5ADI5ADNwcTMwIjI0ICdyNkIsIibj9SbvNmL4N7bjRnch56ciojIz5GRiwiI8+Y9sWY9QmZ0Jyp96uL9v6L0wap9biY9qiq95q197Wr9g+89iojIh94Wiqi";
+</script>
+<script src="/public/js/spreadjs_rela/spreadjs_zh<%= min %>.js"></script>
+<script src="/public/js/unit_compare<%= min %>.js"></script>

+ 77 - 0
app/view/unit_compare/modal.ejs

@@ -0,0 +1,77 @@
+
+<!-- 选择造价文件 -->
+<div id="generate-data" class="modal" tabindex="-1" role="dialog" aria-hidden="true">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择造价文件</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+            </div>
+            <div class="modal-body">
+                <div class="btn-group btn-group-sm mb-2" role="group">
+                    <button id="btnGroupDrop1" type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                        请选择单元
+                    </button>
+                    <div class="dropdown-menu" aria-labelledby="btnGroupDrop1">
+                        <% for (const dy of dyNames) { %>
+                        <a class="dropdown-item" data='<%- JSON.stringify(dy) %>' href="javascript: void(0);"><%- dy.name %></a>
+                        <% } %>
+                    </div>
+                </div>
+                <div class="modal-height-500-scroll">
+                    <table class="table table-bordered">
+                        <tr><th>造价文件</th><th>选择</th></tr>
+                        <% if(libList.length > 0) { %>
+                        <% libList.forEach(function(lib) { %>
+                        <tr class="table-secondary">
+                            <td><%- lib.filename %></td>
+                            <td><input type="radio" name="bd" lib-data='<%- JSON.stringify(lib) %>'></td>
+                        </tr>
+                        <% }) %>
+                        <% } %>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <div id="hint1" class="text-danger fa fa-exclamation-circle" style="display: none;"></div>
+                <button class="btn btn-primary" data-toggle="modal" id="next">下一步</button>
+                <button class="btn btn-secondary" data-dismiss="modal" aria-hidden="true">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!-- 选择项目节 -->
+<div id="select-data" class="modal" tabindex="-1" role="dialog" aria-hidden="true">
+    <div class="modal-dialog modal-xl">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="select-title">选择对比项</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
+            </div>
+            <div class="modal-body">
+                <div class="d-flex justify-content-end mb-2">
+                    <div class="d-inline-flex">
+                        <div class="input-group input-group-sm">
+                            <input type="text" class="form-control" placeholder="输入桥梁名称" aria-label="Recipient's username" aria-describedby="search-x" style="width:220px" id="x-keyword">
+                            <div class="input-group-append">
+                                <button class="btn btn-primary" type="button" id="search-x">搜索</button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-height-500-scroll">
+                    <table class="table table-bordered">
+                        <thead><tr><th width="80">选择</th><th>项目节编号</th><th>结构物名称</th><th>单位</th><th>数量1</th><th>数量2</th><th>经济指标</th><th>金额</th></tr></thead>
+                        <tbody id="x-list">
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <div id="hint2" class="text-danger fa fa-exclamation-circle" style="display: none;"></div>
+                <button class="btn btn-primary" data-toggle="modal" id="select-ok">生成指标</button>
+                <button class="btn btn-secondary" data-dismiss="modal" aria-hidden="true">取消</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 6 - 0
config/menu.js

@@ -26,6 +26,12 @@ const menu = {
         icon: 'fa-search',
         display: true,
         url: '/compare',
+    },
+    unitCompare: {
+        name: '单元指标',
+        icon: 'fa-search-plus',
+        display: true,
+        url: '/unitCompare',
     }
 };