Browse Source

1. 指标节点,绑定分项节点
2. 指标参数,绑定参数取值

MaiXinRong 7 years ago
parent
commit
8b645a5942

+ 12 - 7
app/base/base_controller.js

@@ -36,13 +36,18 @@ class BaseController extends Controller {
     async layout(view, data = {}, modal = '') {
         data.moment = moment;
 
-        const contentString = await this.ctx.renderView(view, data);
-        const modalString = modal === '' ? '' : await this.ctx.renderView(modal, data);
-        const renderData = {
-            content: contentString,
-            modal: modalString,
-        };
-        await this.ctx.render('layout/layout.ejs', renderData);
+        try {
+            const contentString = await this.ctx.renderView(view, data);
+            const modalString = modal === '' ? '' : await this.ctx.renderView(modal, data);
+            const renderData = {
+                content: contentString,
+                modal: modalString,
+            };
+
+            await this.ctx.render('layout/layout.ejs', renderData);
+        } catch (err) {
+            console.log(err);
+        }
     }
 }
 

+ 23 - 0
app/const/template_node.js

@@ -0,0 +1,23 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/4/25
+ * @version
+ */
+
+// 指标节点绑定类别
+const matchType = {
+    code: 1,
+    name: 2,
+}
+const matchTypeStr = [];
+matchTypeStr[matchType.code] = '匹配分项编号';
+matchTypeStr[matchType.name] = '匹配分项名称';
+
+module.exports = {
+    matchType,
+    matchTypeStr
+}

+ 138 - 0
app/const/template_param.js

@@ -0,0 +1,138 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/4/24
+ * @version
+ */
+
+// 参数绑定类别
+const matchType = {
+    fixed_id: 1,
+    node_default: 2,
+    properties: 3,
+    code: 4,
+    non_match: 5,
+}
+const matchTypeStr = [];
+matchTypeStr[matchType.fixed_id] = '全局匹配参数';
+matchTypeStr[matchType.node_default] = '节点默认参数';
+matchTypeStr[matchType.properties] = '属性匹配参数';
+matchTypeStr[matchType.code] = '编号匹配参数';
+matchTypeStr[matchType.non_match] = '非匹配参数';
+const validMatchType = [matchType.properties, matchType.code, matchType.non_match];
+const validProperties = {
+    loadLength: 'loadLength',
+    loadWidth: 'loadWidth'
+}
+
+// 参数取值
+const matchNum = {
+    total_price: 1,
+    dgn_quantity1: 2,
+    dgn_quantity2: 3,
+    quantity: 4,
+};
+const matchNumStr = [];
+matchNumStr[matchNum.total_price] = '合价';
+matchNumStr[matchNum.dgn_quantity1] = '设计数量1';
+matchNumStr[matchNum.dgn_quantity2] = '设计数量2';
+matchNumStr[matchNum.quantity] = '清单数量';
+
+// 默认全局参数
+const defaultGlobalParams = [
+    {
+        template_id: 1,
+        node_id: 0,
+        param_id: 1,
+        code: 'g_a',
+        name: '公路基本造价',
+        match_type: matchType.fixed_id,
+        match_key: '10',
+        match_num: matchNum.total_price,
+    },{
+        template_id: 1,
+        node_id: 0,
+        param_id: 2,
+        code: 'g_b',
+        name: '建安费',
+        match_type: matchType.fixed_id,
+        match_id: '1',
+        match_num: matchNum.total_price,
+    },{
+        template_id: 1,
+        node_id: 0,
+        param_id: 3,
+        code: 'g_c',
+        name: '工程建设其他费用',
+        match_type: matchType.fixed_id,
+        match_id: '3',
+        match_num: matchNum.total_price,
+    },{
+        template_id: 1,
+        node_id: 0,
+        param_id: 4,
+        code: 'g_d',
+        name: '路线总长度(主线长度)',
+        match_type: matchType.non_match,
+    },{
+        template_id: 1,
+        node_id: 0,
+        param_id: 5,
+        code: 'g_e',
+        name: '建筑总面积{路线总长度(主线长度)×路基(或桥隧)宽度}',
+        match_type: matchType.non_match,
+    },{
+        template_id: 1,
+        node_id: 0,
+        param_id: 6,
+        code: 'g_f',
+        name: '路基长度(指不含桥梁、隧道的路基长度(双幅平均计))',
+        match_type: matchType.non_match,
+    },
+];
+// 默认节点参数
+const defaultNodeParams = [
+    {
+        template_id: 1,
+        param_id: 1,
+        code: 'a',
+        name: '本项合价',
+        match_type: matchType.node_default,
+        match_num: matchNum.total_price,
+    }, {
+        template_id: 1,
+        param_id: 2,
+        code: 'a',
+        name: '本项数量1',
+        match_type: matchType.node_default,
+        match_num: matchNum.dgn_quantity1,
+    }, {
+        template_id: 1,
+        param_id: 3,
+        code: 'a',
+        name: '本项数量2',
+        match_type: matchType.node_default,
+        match_num: matchNum.dgn_quantity2,
+    }, {
+        template_id: 1,
+        param_id: 4,
+        code: 'a',
+        name: '本项清单数量',
+        match_type: matchType.node_default,
+        match_num: matchNum.quantity,
+    }
+];
+
+module.exports = {
+    matchType,
+    matchTypeStr,
+    validMatchType,
+    validProperties,
+    matchNum,
+    matchNumStr,
+    defaultGlobalParams,
+    defaultNodeParams
+}

+ 82 - 0
app/controller/template_controller.js

@@ -11,6 +11,8 @@
 // excel解析
 const excel = require('node-xlsx');
 const sendToWormhole = require('stream-wormhole');
+const paramConst = require('../const/template_param');
+const nodeConst = require('../const/template_node');
 module.exports = app => {
     class TemplateController extends app.BaseController {
         /**
@@ -38,10 +40,17 @@ module.exports = app => {
                 selectIndex: selectIndex,
                 globalParams: globalParams,
                 nodeParams: nodeParams,
+                paramConst: paramConst,
+                nodeConst: nodeConst,
             }
             await this.layout('template/index.ejs', renderData, 'template/modal.ejs');
         }
 
+        /**
+         * 导出Excel数据
+         * @param ctx
+         * @returns {Promise<void>}
+         */
         async uploadExcel(ctx) {
             let stream;
             try {
@@ -70,11 +79,84 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 指标节点,绑定匹配规则
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async updateNodeMatch(ctx) {
+            const data =  JSON.parse(ctx.request.body.data);
+            const condition = {
+                template_id: 1,
+                node_id: data.id
+            };
+            delete data.id;
+            const responseData = {
+                err: 0,
+                msg: '',
+                data,
+            };
+            try {
+                responseData.data =  await ctx.service.templateNode.updateNodeMatch(data, condition);
+            } catch (err) {
+                console.log(err);
+                responseData.err = 1;
+                responseData.msg = err.toString();
+                responseData.data = await ctx.service.templateNode.getDataByCondition(condition);
+            }
+            if (responseData.data.match_type) {
+                responseData.data.match_type_str = nodeConst.matchTypeStr[responseData.data.match_type];
+            } else {
+                responseData.data.match_type_str = '';
+            }
+            ctx.body = responseData;
+        }
+
+        /**
+         * 设置指标计算规则
+         * @param ctx
+         * @returns {Promise<void>}
+         */
         async setIndexRule(ctx) {
             const result = await ctx.service.templateIndex.setRule(JSON.parse(ctx.request.body.data));
             const responseData = result ? { err: 0, msg: '', data: [], } : { err: 1, msg: '提交数据失败', data: [], };
             ctx.body = responseData;
         }
+
+        /**
+         * 指标参数,绑定参数取值等
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async updateParamMatch(ctx) {
+            const data =  JSON.parse(ctx.request.body.data);
+            const condition = {
+                template_id: 1,
+                node_id: data.node_id,
+                code: data.code,
+            };
+            delete data.node_id;
+            delete data.code;
+            const responseData = {
+                err: 0,
+                msg: '',
+                data,
+            };
+            try {
+                responseData.data =  await ctx.service.templateParam.updateNodeMatch(data, condition);
+            } catch (err) {
+                console.log(err);
+                responseData.err = 1;
+                responseData.msg = err.toString();
+                responseData.data = await ctx.service.templateParam.getDataByCondition(condition);
+            }
+            if (responseData.data.match_num) {
+                responseData.data.match_num_str = paramConst.matchNumStr[responseData.data.match_num];
+            } else {
+                responseData.data.match_num_str = '';
+            }
+            ctx.body = responseData;
+        }
     }
 
     return TemplateController;

+ 41 - 0
app/extend/helper.js

@@ -141,7 +141,37 @@ module.exports = {
         const reg2 = /([0-9]+$)/i;
         return reg1.test(code) && reg2.test(code);
     },
+    /**
+     * 检查code 是否 是有效的绑定分项编号
+     * @param {String} code - 编号
+     * @return {Boolean}
+     */
+    validMatchCode(code) {
+        const parts = code.split('-');
+        const reg1 = /(^[a-z]+$)/i;
+        const reg2 = /(^[0-9]+$)/;
+        for (const i in parts) {
+            if (i == 0) {
+                if (!reg2.test(parts[i])) {
+                    return false;
+                }
+            } else {
+                if (!(reg1.test(parts[i]) || reg2.test(parts[i]))) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    },
 
+    /**
+     * 在数组arr中查找a.field=value的成员a
+     *
+     * @param {Array} arr - 数组
+     * @param {String} field - 匹配属性名称
+     * @param {any} value - 匹配属性值
+     * @returns {*}
+     */
     findObj(arr, field, value) {
         if (arr.length === 0) { return undefined; }
         for (const a of arr) {
@@ -150,5 +180,16 @@ module.exports = {
             }
         }
         return undefined;
+    },
+
+    /**
+     * 读取json文件并解析
+     *
+     * @param {String} fileName - json文件路径
+     * @returns {any}
+     */
+    loadJsonFile(fileName) {
+        const sJson = fs.readFileSync(fileName);
+        return JSON.parse(sJson);
     }
 }

+ 1 - 28
app/public/css/main.css

@@ -12,34 +12,7 @@ body {
   color:#999
 }
 /*自定义css*/
-/*滚动条*/
-/* 滚动条 */
-::-webkit-scrollbar-thumb:horizontal { /*水平滚动条的样式*/
-	width: 5px;
-	background-color: #ddd;
-	-webkit-border-radius: 6px;
-}
-::-webkit-scrollbar-track-piece {
-	background-color: #fff; /*滚动条的背景颜色*/
-	-webkit-border-radius: 0; /*滚动条的圆角宽度*/
-}
-::-webkit-scrollbar {
-	width: 10px; /*滚动条的宽度*/
-	height: 8px; /*滚动条的高度*/
-}
-::-webkit-scrollbar-thumb:vertical { /*垂直滚动条的样式*/
-	height: 50px;
-	background-color: #ddd;
-	-webkit-border-radius: 6px;
-	outline: 1px solid #fff;
-	outline-offset: -1px;
-	border: 1px solid #fff;
-}
-::-webkit-scrollbar-thumb:hover { /*滚动条的hover样式*/
-	height: 50px;
-	background-color: #999;
-	-webkit-border-radius: 6px;
-}
+
 .sjs-height-1,.sjs-height-2,.sjs-height-3{
   overflow: auto;
 }

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

@@ -76,7 +76,7 @@ const postData = function (url, data, successCallback, errorCallBack) {
                 }
             } else {
                 if (errorCallBack) {
-                    errorCallBack();
+                    errorCallBack(result.data);
                 }
             }
         },

+ 63 - 1
app/public/js/template.js

@@ -9,6 +9,7 @@
  */
 
 $(document).ready(function () {
+    // 删除规则计算参数按钮是否显示
     const refreshDelParamVisible = function () {
         if ($('#rule').children().length > 1) {
             $('#delParam').show();
@@ -16,6 +17,7 @@ $(document).ready(function () {
             $('#delParam').hide();
         }
     };
+    // 合成新增参数的html
     const addParamHtml = function (html, param) {
         html.push('<span class="badge badge-light" title="' + param.text() + '"');
         html.push(' name="' + param.text() + '"');
@@ -23,6 +25,7 @@ $(document).ready(function () {
         html.push(param.text());
         html.push('</span>');
     };
+    // 弹窗设置指标计算规则
     $('a[name=setRule]').click(function () {
         const rule = $(this).attr('value');
         const index = this.parentNode.parentNode;
@@ -41,11 +44,13 @@ $(document).ready(function () {
         refreshDelParamVisible();
         $('#set-count').modal('show');
     });
+    // 编辑计算规则,选择要添加的计算参数类型
     $('#paramType').change(function () {
         const param = this.selectedIndex === 0 ? 'globalParams' : (this.selectedIndex === 1 ? 'nodeParams' : 'calcParams');
         $('div[name=param]').hide();
         $('#' + param).show();
     });
+    // 编辑计算规则,添加计算参数到规则
     $('#addParam').click(function () {
         const lastParam = $('#delParam').prev();
         const paramType = $('#paramType')[0];
@@ -63,7 +68,6 @@ $(document).ready(function () {
             }
         }
         if (lastParam) {
-            console.log(lastParam.attr('code'));
             if (lastParam.attr('code') !== '/' && paramType.selectedIndex !== 2) {
                 $('#paramAlert').text('2个参数之间需要一个计算式').show();
             } else if (lastParam.attr('code') === '/' && paramType.selectedIndex === 2 ) {
@@ -77,10 +81,12 @@ $(document).ready(function () {
             addParam();
         }
     });
+    // 编辑计算规则,删除规则中计算参数
     $('#delParam').click(function () {
         $('#delParam').prev().remove();
         refreshDelParamVisible();
     });
+    // 设置指标计算规则
     $('#ruleOk').click(function () {
         const lastParam = $('#delParam').prev();
         if (!lastParam) {
@@ -117,4 +123,60 @@ $(document).ready(function () {
             })
         }
     });
+    // 指标节点,绑定分项节点
+    $('#nodeMatchCode').blur(function () {
+        const self = $(this);
+        if (self.val() === self.attr('org-value')) { return; }
+        const path = window.location.pathname.split('/');
+        postData('/template/updateNodeMatch', {
+            id: path.length > 2 ? path[2] : 1,
+            match_key: $(this).val(),
+        }, function (data) {
+            self.attr('org-value', data.match_key);
+            self.attr('title', data.match_type_str);
+            self.attr('data-original-title', data.match_type_str);;
+        }, function (data) {
+            self.val(data.match_key);
+            self.attr('org-value', data.match_key);
+            self.attr('title', data.match_type_str);
+            self.attr('data-original-title', data.match_type_str);;
+        });
+    });
+    // 指标参数,绑定参数取值(分项编号)
+    $('input[name=paramMatchCode]').blur(function () {
+        const self = $(this);
+        if (self.val() === self.attr('org-value')) { return; }
+        const path = window.location.pathname.split('/');
+        const paramCode = $(this).parent().parent().prev().attr('code');
+        postData('/template/updateParamMatch', {
+            node_id: path.length > 2 ? path[2] : 1,
+            code: paramCode,
+            match_key: $(this).val(),
+        }, function (data) {
+            self.attr('org-value', data.match_key);
+        }, function (data) {
+            self.val(data.match_key);
+            self.attr('org-value', data.match_key);
+        })
+    });
+    // 指标参数,选择取值类别
+    $('a[name=paramMatchNum]').click(function () {
+        const self = $(this);
+        const newMatchNum = self.attr('value');
+        const oldMatchNum = self.parent().prev().val();
+        if (newMatchNum === oldMatchNum) { return; }
+        const path = window.location.pathname.split('/');
+        const paramCode = $(this).parent().parent().parent().parent().prev().attr('code');
+        postData('/template/updateParamMatch', {
+            node_id: path.length > 2 ? path[2] : 1,
+            code: paramCode,
+            match_num: newMatchNum,
+        }, function (data) {
+            self.parent().prev().val(data.match_num);
+            self.parent().prev().text(data.match_num_str);
+        }, function (data) {
+            self.parent().prev().val(data.match_num);
+            self.parent().prev().text(data.match_num_str);
+        })
+    });
 });

+ 2 - 0
app/router.js

@@ -24,7 +24,9 @@ module.exports = app => {
     // 指标模板
     app.get('/template', sessionAuth, 'templateController.index');
     app.post('/template/uploadExcel', sessionAuth, 'templateController.uploadExcel');
+    app.post('/template/updateNodeMatch', sessionAuth, 'templateController.updateNodeMatch');
     app.post('/template/setIndexRule', sessionAuth, 'templateController.setIndexRule');
+    app.post('/template/updateParamMatch', sessionAuth, 'templateController.updateParamMatch');
 
     // 指标对比
     app.get('/compare', sessionAuth, 'compareController.index');

+ 16 - 1
app/service/template_index.js

@@ -21,7 +21,15 @@ module.exports = app => {
             this.tableName = 'template_index';
         }
 
-        async importData(datas, transaction) {
+        /**
+         * 导入指标数据
+         *
+         * @param {Array|Object} datas
+         * @param transaction - 事务
+         * @returns {Promise<void>}
+         */
+        async importData(data, transaction) {
+            const datas = data instanceof Array ? data : [data];
             await transaction.delete(this.tableName, {template_id: 1});
             const insertResult = await transaction.insert(this.tableName, datas);
             if (insertResult.affectedRows !== datas.length) {
@@ -29,6 +37,13 @@ module.exports = app => {
             }
         }
 
+        /**
+         * 设置计算规则
+         * 应同步设置rule, calc_rule, parse_rule
+         *
+         * @param data
+         * @returns {Promise<*>}
+         */
         async setRule(data) {
             const result = await this.db.update(this.tableName, data);
             return result;

+ 61 - 45
app/service/template_node.js

@@ -12,45 +12,8 @@ const paramCode = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', '
     'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'aa', 'ab', 'ac', 'ad', 'ae', 'af',
     'ag', 'ah', 'ai', 'aj', 'ak', 'al', 'am', 'an', 'ao', 'ap', 'aq', 'ar', 'as', 'at', 'au',
     'av', 'aw', 'ax', 'ay', 'az'];
-const defaultGlobalParams = [
-    {
-        template_id: 1,
-        node_id: 0,
-        param_id: 1,
-        code: 'g_a',
-        name: '公路基本造价',
-    },{
-        template_id: 1,
-        node_id: 0,
-        param_id: 2,
-        code: 'g_b',
-        name: '建安费',
-    },{
-        template_id: 1,
-        node_id: 0,
-        param_id: 3,
-        code: 'g_c',
-        name: '工程建设其他费用',
-    },{
-        template_id: 1,
-        node_id: 0,
-        param_id: 4,
-        code: 'g_d',
-        name: '路线总长度(主线长度)',
-    },{
-        template_id: 1,
-        node_id: 0,
-        param_id: 5,
-        code: 'g_e',
-        name: '建筑总面积{路线总长度(主线长度)×路基(或桥隧)宽度}',
-    },{
-        template_id: 1,
-        node_id: 0,
-        param_id: 6,
-        code: 'g_f',
-        name: '路基长度(指不含桥梁、隧道的路基长度(双幅平均计))',
-    },
-];
+const paramConst = require('../const/template_param');
+const nodeConst = require('../const/template_node');
 module.exports = app => {
     class TemplateNode extends app.BaseService {
 
@@ -66,6 +29,39 @@ module.exports = app => {
         }
 
         /**
+         * 加载默认节点参数
+         * @param {Array} params - 参数数组
+         * @param {Array} defaultParams - 默认参数数组
+         * @param {Number} nodeId - 指标节点Id
+         * @private
+         */
+        _loadDefaultParam(params, defaultParams, nodeId) {
+            const newParams = JSON.parse(JSON.stringify(defaultParams));
+            for (const p of newParams) {
+                p.node_id = nodeId;
+                params.push(p);
+            }
+        }
+        /**
+         * 过滤指标节点的参数(含节点参数初始化)
+         * @param {Array} params - 全部参数数组
+         * @param {Number} nodeId - 指标节点Id
+         * @private
+         */
+        _filterNodeParams(params, nodeId) {
+            let nodeParams = params.filter(function (p) {
+                return p.node_id === nodeId;
+            });
+            if (nodeParams.length > 0) {
+                return nodeParams;
+            } else {
+                this._loadDefaultParam(params, paramConst.defaultNodeParams, nodeId);
+                return params.filter(function (p) {
+                    return p.node_id === nodeId;
+                });
+            }
+        }
+        /**
          * 从计算规则中解析出指标参数
          * @param {String} rule - 指标规则
          * @param {Number} nodeId - 指标节点id
@@ -77,12 +73,10 @@ module.exports = app => {
             if (rule === '') { return; }
             const self = this;
             const ruleParams = rule.split('/');
-            const nodeParams = params.filter(function (p) {
-                return p.node_id === nodeId;
-            });
+            const nodeParams = this._filterNodeParams(params, nodeId);
             const addParam = function (paramName) {
                 if (paramName === '') { return ''; }
-                let param = self.ctx.helper.findObj(defaultGlobalParams, 'name', paramName);
+                let param = self.ctx.helper.findObj(paramConst.defaultGlobalParams, 'name', paramName);
                 if (!param) {
                     param = self.ctx.helper.findObj(nodeParams, 'name', paramName);
                 }
@@ -93,6 +87,7 @@ module.exports = app => {
                         param_id: nodeParams.length + 1,
                         code: paramCode[nodeParams.length],
                         name: paramName,
+                        match_type: paramConst.matchType.non_match,
                     };
                     nodeParams.push(newParam);
                     params.push(newParam);
@@ -100,7 +95,7 @@ module.exports = app => {
                 } else {
                     return param.code;
                 }
-            }
+            };
             if (ruleParams.length > 1) {
                 const paramName1 = ruleParams[0];
                 const paramCode1 = addParam(paramName1);
@@ -209,7 +204,7 @@ module.exports = app => {
                         throw '导入指标节点错误';
                     }
                     await this.ctx.service.templateIndex.importData(indexes, transaction);
-                    await this.ctx.service.templateParam.importData(params.concat(defaultGlobalParams), transaction);
+                    await this.ctx.service.templateParam.importData(params.concat(paramConst.defaultGlobalParams), transaction);
                 } else {
                     throw 'Excel文件中无标准的指标数据';
                 }
@@ -218,11 +213,32 @@ module.exports = app => {
                 result = true;
             } catch(err) {
                 await transaction.rollback();
+                console.log(err);
                 throw err;
             }
             return result;
         }
 
+        /**
+         * 保存指标节点绑定信息
+         *
+         * @param data
+         * @returns {Promise<*>}
+         */
+        async updateNodeMatch(data, condition) {
+            try {
+                if (this.ctx.helper.validMatchCode(data.match_key)) {
+                    data.match_type = nodeConst.matchType.code;
+                } else {
+                    data.match_type = nodeConst.matchType.name;
+                }
+                await this.db.update(this.tableName, data, {where: condition});
+            } catch (err) {
+                console.log(err);
+            }
+            return await this.getDataByCondition(condition);
+        }
+
     }
 
     return TemplateNode;

+ 29 - 1
app/service/template_param.js

@@ -8,6 +8,7 @@
  * @version
  */
 
+const paramConst = require('../const/template_param');
 module.exports = app => {
     class TemplateParam extends app.BaseService {
         /**
@@ -21,13 +22,40 @@ module.exports = app => {
             this.tableName = 'template_param';
         }
 
-        async importData(datas, transaction) {
+        /**
+         * 导入指标参数
+         *
+         * @param {Array|Object} datas
+         * @param transaction - 事务
+         * @returns {Promise<void>}
+         */
+        async importData(data, transaction) {
+            const datas = data instanceof Array ? data : [data];
             await transaction.delete(this.tableName, {template_id: 1});
             const insertResult = await transaction.insert(this.tableName, datas);
             if (insertResult.affectedRows !== datas.length) {
                 throw '导入指标参数错误';
             }
         }
+
+        /**
+         * 保存指标节点匹配规则
+         *
+         * @param {Object} data
+         * @returns {Promise<*>}
+         */
+        async updateNodeMatch(data, condition) {
+            try {
+                if (data.match_key && !this.ctx.helper.validMatchCode(data.match_key)) {
+                    throw '用于绑定的分项编号有误'
+                }
+                data.match_type = paramConst.matchType.code;
+                await this.db.update(this.tableName, data, {where: condition});
+            } catch (err) {
+                console.log(err);
+            }
+            return await this.getDataByCondition(condition);
+        }
     };
 
     return TemplateParam;

+ 1 - 1
app/view/lib/detail.ejs

@@ -13,7 +13,7 @@
         <div class="c-body">
             <div class="row">
                 <div class="col-5">
-                    <div class="sjs-height-1">
+                    <div class="sjs-height-1" id="">
                         <table class="table table-bordered table-sm">
                             <tr>
                                 <th></th>

+ 70 - 9
app/view/template/index.ejs

@@ -13,11 +13,19 @@
 </div>
 <div class="panel-content">
     <div class="panel-title">
-        <div class="title-main">
-            <h2>
-                <%= selectNode.code %> <%= selectNode.name %>
+        <div class="title-main d-flex justify-content-between">
+            <div>
+                <div class="btn-group">
+                    <%= selectNode.code %> <%= selectNode.name %>
+                </div>
+                <div class="btn-group ml-3">
+                    <input id="nodeMatchCode" data-toggle="tooltip" data-placement="bottom" title="<%- selectNode.match_type ? nodeConst.matchTypeStr[selectNode.match_type] : '' %>" type="text" class="m-0 form-control form-control-sm" placeholder="填写绑定的分项节点" value="<%= selectNode.match_key%>" org-value="<%= selectNode.match_key%>">
+                </div>
+                <a href="javascript:void(0)" data-toggle="popover"  data-content="默认都以分项编号为绑定依据,预留费用请输入分项名称。"><i class="fa fa-question-circle"></i></a>
+            </div>
+            <div>
                 <a href="#add-index" data-toggle="modal" data-target="#add-index" class="btn btn-primary btn-sm pull-right">添加指标</a>
-            </h2>
+            </div>
         </div>
     </div>
     <div class="content-wrap">
@@ -48,11 +56,36 @@
                         <div class="col-6">
                             <div class="card">
                                 <div class="card-body">
-                                    <h6 class="card-title"><b>z1 第一部分 建筑安装工程费用</b> 参数</h6>
+                                    <h6 class="card-title"><b><%= selectNode.code %> <%= selectNode.name %></b> 参数</h6>
                                     <table class="table table-sm table-bordered table-hover">
-                                        <tr><th>参数名称</th><th>绑定清单<br><small class="text-muted">(为空则在指标库填写具体值)</small></th></tr>
+                                        <tr><th>参数名称</th><th>绑定分项<br><small class="text-muted">(为空则在指标库填写具体值)</small></th></tr>
                                         <% for (const n of nodeParams) { %>
-                                        <tr><td code="<%= n.code %>"><%= n.name %></td><td><input class="form-control form-control-sm"></td></tr>
+                                        <tr>
+                                            <td code="<%= n.code %>"><%= n.name %></td>
+                                            <td>
+                                                <% if (n.match_type !== paramConst.matchType.node_default) { %>
+                                                <div class="input-group input-group-sm">
+                                                    <div class="input-group-prepend">
+                                                        <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" value="<%= n.match_num %>">
+                                                           <% if (n.match_num) { %>
+                                                            <%- paramConst.matchNumStr[n.match_num] %>
+                                                            <% } else { %>
+                                                            为空
+                                                            <% } %>
+                                                        </button>
+                                                        <div class="dropdown-menu">
+                                                            <% for (const mn in paramConst.matchNumStr) { %>
+                                                            <a class="dropdown-item" href="javascript: void(0)" name="paramMatchNum" value="<%- mn %>"><%- paramConst.matchNumStr[mn] %></a>
+                                                            <% } %>
+                                                        </div>
+                                                    </div>
+                                                    <input name="paramMatchCode" type="text" class="form-control" aria-label="Text input with dropdown button" value="<%- n.match_key %>" org-value="<%- n.match_key %>">
+                                                </div>
+                                                <% } else { %>
+                                                <small class="text-muted">(自动绑定)</small>
+                                                <% } %>
+                                            </td>
+                                        </tr>
                                         <% } %>
                                     </table>
                                 </div>
@@ -64,9 +97,34 @@
                                     <h6 class="card-title text-center">全局参数 </h6>
                                     <table class="table table-sm table-bordered table-hover">
                                         <tr><th colspan="2" class="text-center"><b class="text-danger">*全局参数影响所有指标,谨慎修改</b></th></tr>
-                                        <tr><th>参数名称</th><th>绑定清单<br><small class="text-muted">(为空则在指标库填写具体值)</small></th></tr>
+                                        <tr><th>参数名称</th><th>绑定分项<br><small class="text-muted">(为空则在指标库填写具体值)</small></th></tr>
                                         <% for (const p of globalParams) { %>
-                                        <tr><td code="<%= p.code %>"><%= p.name %></td><td><input class="form-control form-control-sm"></td></tr>
+                                        <tr>
+                                            <td code="<%= p.code %>"><%= p.name %></td>
+                                            <td>
+                                                <% if (p.match_type !== paramConst.matchType.fixed_id) { %>
+                                                <div class="input-group input-group-sm">
+                                                    <div class="input-group-prepend">
+                                                        <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" value="<%= p.match_num %>">
+                                                            <% if (p.match_num) { %>
+                                                                <%- paramConst.matchNumStr[p.match_num] %>
+                                                            <% } else { %>
+                                                                为空
+                                                            <% } %>
+                                                        </button>
+                                                        <div class="dropdown-menu">
+                                                            <% for (const mn in paramConst.matchNumStr) { %>
+                                                            <a class="dropdown-item" href="javascript: void(0)" name="paramMatchNum" value="<%- mn %>"><%- paramConst.matchNumStr[mn] %></a>
+                                                            <% } %>
+                                                        </div>
+                                                    </div>
+                                                    <input name="paramMatchCode" type="text" class="form-control" aria-label="Text input with dropdown button">
+                                                </div>
+                                                <% } else { %>
+                                                <small class="text-muted">(自动绑定)</small>
+                                                <% } %>
+                                            </td>
+                                        </tr>
                                         <% } %>
                                     </table>
                                 </div>
@@ -106,4 +164,7 @@
         const node = treeObj.getNodeByParam('node_id', selectNode);
         treeObj.selectNode(node);
     });
+</script>
+<script type="text/javascript">
+    $('#nodeMatchCode').tooltip('show')
 </script>

+ 15 - 0
test/app/extend/helper.test.js

@@ -27,4 +27,19 @@ describe('test/app/extend/helper.test.js', () => {
         const ctx = app.mockContext();
         assert(ctx.helper.ValidTemplateIndexCode('z2-d-d-1~z2-d-d-5   高填方路段处理   同z2-d-c-1~z2-d-c-5') === false);
     });
+    it('validMatchCode test', function () {
+        const ctx = app.mockContext();
+        assert(ctx.helper.validMatchCode('1-1-1'));
+        assert(ctx.helper.validMatchCode('1-1-X'));
+        assert(ctx.helper.validMatchCode('1-X-1'));
+        assert(!ctx.helper.validMatchCode('1-1-'));
+        assert(!ctx.helper.validMatchCode('X-1-1'));
+        assert(!ctx.helper.validMatchCode('1-人-1'));
+    });
+    it('loadJsonFile test', function () {
+        const ctx = app.mockContext();
+        const vJ = ctx.helper.loadJsonFile(app.baseDir + '/test/app/extend/test.json');
+        assert(vJ.bills);
+        assert(vJ.properties);
+    });
 });