浏览代码

1. 指标模板,导入excel解析计算规则
2. 指标模板界面,设置指标计算规则
3. 指标模板界面,加载全部计算参数

MaiXinRong 7 年之前
父节点
当前提交
d98bb1fca8

+ 1 - 1
app/base/base_controller.js

@@ -37,7 +37,7 @@ class BaseController extends Controller {
         data.moment = moment;
 
         const contentString = await this.ctx.renderView(view, data);
-        const modalString = modal === '' ? '' : await this.ctx.renderView(modal);
+        const modalString = modal === '' ? '' : await this.ctx.renderView(modal, data);
         const renderData = {
             content: contentString,
             modal: modalString,

+ 10 - 0
app/controller/template_controller.js

@@ -30,10 +30,14 @@ module.exports = app => {
             const condition = {template_id: 1, node_id: id};
             const selectNode = await ctx.service.templateNode.getDataByCondition(condition);
             const selectIndex = await ctx.service.templateIndex.getAllDataByCondition({ where: condition });
+            const globalParams = await ctx.service.templateParam.getAllDataByCondition({ where: {template_id: 1, node_id: 0}});
+            const nodeParams = await ctx.service.templateParam.getAllDataByCondition({ where: condition});
             const renderData = {
                 nodes: JSON.stringify(treeNode),
                 selectNode: selectNode,
                 selectIndex: selectIndex,
+                globalParams: globalParams,
+                nodeParams: nodeParams,
             }
             await this.layout('template/index.ejs', renderData, 'template/modal.ejs');
         }
@@ -65,6 +69,12 @@ module.exports = app => {
                 ctx.redirect('/template');
             }
         }
+
+        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;
+        }
     }
 
     return TemplateController;

+ 27 - 0
app/public/js/cookies.js

@@ -0,0 +1,27 @@
+/**
+ * cookies相关操作
+ *
+ * @author CaiAoLin
+ * @date 2018/1/8
+ * @version
+ */
+let Cookies = {
+    get: function(name) {
+        if(document.cookie.length <= 0) {
+            return "";
+        }
+
+        let start = document.cookie.indexOf(name + "=");//获取字符串的起点
+        if(start < 0) {
+            return "";
+        }
+        // 获取值的起点
+        start = start + name.length + 1;
+        // 获取结尾处
+        let end = document.cookie.indexOf(";", start);
+        // 如果是最后一个,结尾就是cookie字符串的结尾
+        end = end === -1 ? document.cookie.length : end;
+        // 截取字符串返回
+        return decodeURI(document.cookie.substring(start, end));
+    },
+};

+ 38 - 2
app/public/js/global.js

@@ -48,6 +48,42 @@ $(".bg-nav > li > a").click(function() {
           }
       }
   });
-
-
 });
+
+/**
+ * 动态请求数据
+ * @param {String} url - 请求链接
+ * @param data - 提交数据
+ * @param {function} successCallback - 返回成功回调
+ * @param {function} errorCallBack - 返回失败回调
+ */
+const postData = function (url, data, successCallback, errorCallBack) {
+    $.ajax({
+        type:"POST",
+        url: url,
+        data: {'data': JSON.stringify(data)},
+        dataType: 'json',
+        cache: false,
+        timeout: 5000,
+        beforeSend: function(xhr) {
+            let csrfToken = Cookies.get('csrfToken');
+            xhr.setRequestHeader('x-csrf-token', csrfToken);
+        },
+        success: function(result){
+            if (result.err === 0) {
+                if (successCallback) {
+                    successCallback(result.data);
+                }
+            } else {
+                if (errorCallBack) {
+                    errorCallBack();
+                }
+            }
+        },
+        error: function(jqXHR, textStatus, errorThrown){
+            if (errorCallBack) {
+                errorCallBack();
+            }
+        }
+    });
+};

+ 120 - 0
app/public/js/template.js

@@ -0,0 +1,120 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/4/23
+ * @version
+ */
+
+$(document).ready(function () {
+    const refreshDelParamVisible = function () {
+        if ($('#rule').children().length > 1) {
+            $('#delParam').show();
+        } else {
+            $('#delParam').hide();
+        }
+    };
+    const addParamHtml = function (html, param) {
+        html.push('<span class="badge badge-light" title="' + param.text() + '"');
+        html.push(' name="' + param.text() + '"');
+        html.push(' code="' + param.attr('value') + '">');
+        html.push(param.text());
+        html.push('</span>');
+    };
+    $('a[name=setRule]').click(function () {
+        const rule = $(this).attr('value');
+        const index = this.parentNode.parentNode;
+        $('#rule').attr('curIndex', index.attributes['index_id'].nodeValue);
+        $('span', $('#rule')).remove();
+        const html = [];
+        const params = rule.split('/');
+        for (let i = 0, iLen = params.length; i < iLen; i++) {
+            const p = $('option[value='+ params[i] +']');
+            addParamHtml(html, p);
+            if (i < iLen - 1) {
+                html.push('<span class="badge badge-light" title="/" code="/" name="/">/ </span>');
+            }
+        }
+        $('#delParam').before(html.join(''));
+        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];
+        const addParam = function () {
+            const paramId = paramType.selectedIndex === 0 ? 'globalParams' : (paramType.selectedIndex === 1 ? 'nodeParams' : 'calcParams');
+            const paramSelect = $('select', $('#' + paramId))[0];
+            if (paramSelect.children.length > 0 && paramSelect.selectedIndex >= 0) {
+                const param = paramSelect.children[paramSelect.selectedIndex];
+                const html = [];
+                addParamHtml(html, $(param));
+                $('#delParam').before(html.join(''));
+                $('#paramAlert').hide();
+            } else {
+                $('#paramAlert').text('无选定参数').show();
+            }
+        }
+        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 ) {
+                $('#paramAlert').text('计算式后只可添加参数').show();
+            } else {
+                addParam();
+            }
+        } else if (paramType.selectedIndex === 2) {
+            $('#paramAlert').text('计算式前需含有参数').show();
+        } else {
+            addParam();
+        }
+    });
+    $('#delParam').click(function () {
+        $('#delParam').prev().remove();
+        refreshDelParamVisible();
+    });
+    $('#ruleOk').click(function () {
+        const lastParam = $('#delParam').prev();
+        if (!lastParam) {
+            $('#paramAlert').text('未设置计算规则').show();
+        } else if (lastParam.attr('code') === lastParam.attr('name')) {
+            $('#paramAlert').text('计算式后应添加参数').show();
+        } else {
+            const indexRow = $('tr[index_id=' + $('#rule').attr('curIndex') + ']');
+            const params = $('span', $('#rule'));
+            const data = {
+                id: $('#rule').attr('curIndex'),
+                rule: '',
+                calc_rule: '',
+                parse_rule: '',
+            };
+            for (const p of params) {
+                const code = $(p).attr('code');
+                const name = $(p).attr('name');
+                data.rule = data.rule + name;
+                data.calc_rule = data.calc_rule + code;
+                data.parse_rule = code === name ? data.parse_rule + code : data.parse_rule + code + '(' + name + ')';
+            }
+
+            postData('/template/setIndexRule', data, function () {
+                $('a', indexRow).val(data.calc_rule);
+                $('td[name=rule]', indexRow).text(data.rule);
+                const aHtml = $('a', indexRow)[0].outerHTML;
+                $('td[name=parse_rule]', indexRow).empty();
+                $('td[name=parse_rule]', indexRow).append(data.parse_rule + aHtml);
+                $('paramAlert').hide();
+                $('#set-count').modal('hide');
+            }, function () {
+                $('#paramAlert').text('提交计算规则失败,请重试').show();
+            })
+        }
+    });
+});

+ 1 - 0
app/router.js

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

+ 5 - 3
app/service/template_index.js

@@ -22,15 +22,17 @@ module.exports = app => {
         }
 
         async importData(datas, transaction) {
-            console.log(datas[241]);
-            console.log(datas[242]);
-            console.log(datas[243]);
             await transaction.delete(this.tableName, {template_id: 1});
             const insertResult = await transaction.insert(this.tableName, datas);
             if (insertResult.affectedRows !== datas.length) {
                 throw '导入指标错误';
             }
         }
+
+        async setRule(data) {
+            const result = await this.db.update(this.tableName, data);
+            return result;
+        }
     };
 
     return TemplateIndex;

+ 96 - 3
app/service/template_node.js

@@ -8,6 +8,49 @@
  * @version
  */
 
+const paramCode = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+    '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: '路基长度(指不含桥梁、隧道的路基长度(双幅平均计))',
+    },
+];
 module.exports = app => {
     class TemplateNode extends app.BaseService {
 
@@ -23,6 +66,54 @@ module.exports = app => {
         }
 
         /**
+         * 从计算规则中解析出指标参数
+         * @param {String} rule - 指标规则
+         * @param {Number} nodeId - 指标节点id
+         * @param {Array} params - 参数列表
+         * @returns {*[]}
+         * @private
+         */
+        _parseParam(rule, nodeId, params) {
+            if (rule === '') { return; }
+            const self = this;
+            const ruleParams = rule.split('/');
+            const nodeParams = params.filter(function (p) {
+                return p.node_id === nodeId;
+            });
+            const addParam = function (paramName) {
+                if (paramName === '') { return ''; }
+                let param = self.ctx.helper.findObj(defaultGlobalParams, 'name', paramName);
+                if (!param) {
+                    param = self.ctx.helper.findObj(nodeParams, 'name', paramName);
+                }
+                if (!param) {
+                    const newParam = {
+                        template_id: 1,
+                        node_id: nodeId,
+                        param_id: nodeParams.length + 1,
+                        code: paramCode[nodeParams.length],
+                        name: paramName,
+                    };
+                    nodeParams.push(newParam);
+                    params.push(newParam);
+                    return newParam.code;
+                } else {
+                    return param.code;
+                }
+            }
+            if (ruleParams.length > 1) {
+                const paramName1 = ruleParams[0];
+                const paramCode1 = addParam(paramName1);
+                const paramName2 = ruleParams.slice(1, ruleParams.length).join('/');
+                const paramCode2 = addParam(paramName2);
+                return [paramCode1 +  '/' + paramCode2,
+                    paramCode1 + '(' + paramName1 + ')' + '/' + paramCode2 + '(' + paramName2 + ')'];
+            } else {
+                const paramCode = addParam(rule);
+                return [paramCode, paramCode + '(' + rule + ')'];
+            }
+        }
+        /**
          * 查找父节点(根据编号),忽略大小写
          * e.g. z1(z), z1-e(z1), z1-e-a(z1-e)
          * @param code
@@ -56,7 +147,7 @@ module.exports = app => {
          * @param {Array} indexes - 解析后的指标
          * @private
          */
-        _parseSheetData(excelSheet, nodes, indexes) {
+        _parseSheetData(excelSheet, nodes, indexes, params) {
             for (const row of excelSheet.data) {
                 if (!row[0]) { continue; }
                 if (this.ctx.helper.ValidTemplateNodeCode(row[0])) {
@@ -89,6 +180,7 @@ module.exports = app => {
                     } else if (row[7] === '√') {
                         index.index_type = 4;
                     }
+                    [index.calc_rule, index.parse_rule] = this._parseParam(index.rule, index.node_id, params);
                     indexes.push(index);
                 }
             }
@@ -105,9 +197,9 @@ module.exports = app => {
             const limit = 30000;
             const transaction = await this.db.beginTransaction();
             try {
-                const nodes = [], indexes = [];
+                const nodes = [], indexes = [], params = [];
                 for (const sheet of excelSheets) {
-                    this._parseSheetData(sheet, nodes, indexes);
+                    this._parseSheetData(sheet, nodes, indexes, params);
                 }
 
                 if (nodes.length > 0) {
@@ -117,6 +209,7 @@ module.exports = app => {
                         throw '导入指标节点错误';
                     }
                     await this.ctx.service.templateIndex.importData(indexes, transaction);
+                    await this.ctx.service.templateParam.importData(params.concat(defaultGlobalParams), transaction);
                 } else {
                     throw 'Excel文件中无标准的指标数据';
                 }

+ 34 - 0
app/service/template_param.js

@@ -0,0 +1,34 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/4/23
+ * @version
+ */
+
+module.exports = app => {
+    class TemplateParam extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局context
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'template_param';
+        }
+
+        async importData(datas, transaction) {
+            await transaction.delete(this.tableName, {template_id: 1});
+            const insertResult = await transaction.insert(this.tableName, datas);
+            if (insertResult.affectedRows !== datas.length) {
+                throw '导入指标参数错误';
+            }
+        }
+    };
+
+    return TemplateParam;
+};

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

@@ -61,6 +61,7 @@
     </div>
 </div>
 <%- modal %>
+<script src="/public/js/cookies.js"></script>
 <script src="/public/js/global.js"></script>
 <script type="text/javascript">
     autoFlashHeight();

+ 35 - 3
app/view/template/index.ejs

@@ -32,16 +32,48 @@
                         <th>设置规则</th>
                     </tr>
                     <% for (const index of selectIndex) { %>
-                    <tr>
+                    <tr index_id="<%= index.id %>">
                         <td><%= index.code %></td>
                         <td><%= index.name %></td>
                         <td><%= index.unit1 %></td>
                         <td><%= index.unit2 %></td>
-                        <td><%= index.rule %></td>
-                        <td><%= index.rule %><a href="#set-count" data-toggle="modal" data-target="#set-count"><i class="fa fa-cog"></i></a></td>
+                        <td name="rule"><%= index.rule %></td>
+                        <td name="parse_rule"><%= index.parse_rule %><a name="setRule" href="#set-count" data-toggle="modal" data-target="#set-count" value="<%- index.calc_rule %>"><i class="fa fa-cog"></i></a></td>
                     </tr>
                     <% } %>
                 </table>
+                <div class="container-fluid">
+                    <legend class="mt-5">参数设置</legend>
+                    <div class="row">
+                        <div class="col-6">
+                            <div class="card">
+                                <div class="card-body">
+                                    <h6 class="card-title"><b>z1 第一部分 建筑安装工程费用</b> 参数</h6>
+                                    <table class="table table-sm table-bordered table-hover">
+                                        <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>
+                                        <% } %>
+                                    </table>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="col-6">
+                            <div class="card border-danger">
+                                <div class="card-body">
+                                    <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>
+                                        <% for (const p of globalParams) { %>
+                                        <tr><td code="<%= p.code %>"><%= p.name %></td><td><input class="form-control form-control-sm"></td></tr>
+                                        <% } %>
+                                    </table>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
             </div>
         </div>
     </div>

+ 31 - 16
app/view/template/modal.ejs

@@ -1,6 +1,6 @@
 <!-- 设置计算式 -->
 <div id="set-count" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
-    <div class="modal-dialog">
+    <div class="modal-dialog" id="setRuleForm">
         <div class="modal-content">
             <div class="modal-header">
                 <h5 class="modal-title">设置规则</h5>
@@ -8,42 +8,48 @@
             </div>
             <div class="modal-body">
                 <h5>当前规则:</h5>
-                <h5>
-                    <span class="badge badge-light" title="合价">合价 <!--<a href="#" class="text-danger" title="移除"><i class="fa fa-remove"></i></a>--></span>
-                    <span class="badge badge-light" title="/">/ <!--<a href="#" class="text-danger" title="移除"><i class="fa fa-remove"></i></a>--></span>
-                    <span class="badge badge-light" title="路线总长度">路线总长度 <a href="#" class="text-danger" title="移除"><i class="fa fa-remove"></i></a></span>
+                <h5 id="rule">
+                    <span class="badge badge-light" title="合价">合价 </span>
+                    <span class="badge badge-light" title="/">/ </span>
+                    <span class="badge badge-light" title="路线总长度">路线总长度 </span>
+                    <a href="javascript:void(0);" class="text-danger" title="移除" id="delParam"><i class="fa fa-remove"></i></a>
                 </h5>
                 <div class="form-group">
-                    <select class="form-control">
+                    <select id="paramType" class="form-control">
                         <option>全局参数</option>
                         <option>本项目节参数</option>
                         <option>计算式</option>
                     </select>
                 </div>
                 <!--全局参数-->
-                <div class="form-group">
+                <div class="form-group" id="globalParams" name="param">
                     <select class="form-control">
-                        <option>总造价</option>
-                        <option>路线总长度</option>
-                        <option>计算式</option>
+                        <% for (const p of globalParams) { %>
+                        <option value="<%= p.code %>"><%= p.name %></option>
+                        <% } %>
                     </select>
                 </div>
                 <!--本项目节参数-->
-                <div class="form-group">
+                <div class="form-group" id="nodeParams" name="param" style="display: none;">
                     <select class="form-control">
-                        <option>合价</option>
+                        <% for (const p of nodeParams) { %>
+                        <option value="<%= p.code %>"><%= p.name %></option>
+                        <% } %>
                     </select>
                 </div>
                 <!--计算式-->
-                <div class="form-group">
+                <div class="form-group" id="calcParams" name="param" style="display: none;">
                     <select class="form-control">
                         <option>/</option>
                     </select>
                 </div>
-                <button class="btn btn-outline-primary">添加</button>
+                <button id="addParam" class="btn btn-outline-primary">添加</button>
+                <div id="paramAlert" class="alert alert-danger mt-3" role="alert" style="display: none;">
+                    2个参数之间需要一个计算式
+                </div>
             </div>
             <div class="modal-footer">
-                <button class="btn btn-primary">确定</button>
+                <button id="ruleOk" class="btn btn-primary">确定</button>
                 <button class="btn btn-secondary" data-dismiss="modal" aria-hidden="true">取消</button>
             </div>
         </div>
@@ -71,4 +77,13 @@
             </div>
         </div>
     </form>
-</div>
+</div>
+<script>
+    const globalParams = <%- JSON.stringify(globalParams) %>;
+    const nodeParams = <%- JSON.stringify(nodeParams) %>;
+    const calcParams = [{
+        code: '/',
+        name: '/'
+    }];
+</script>
+<script src="/public/js/template.js"></script>