Bläddra i källkod

Merge branch '1.0.0_online' of http://smartcost.f3322.net:3000/SmartCost/ConstructionOperation into 1.0.0_online

zhongzewei 7 år sedan
förälder
incheckning
b245c11709
33 ändrade filer med 1641 tillägg och 105 borttagningar
  1. 486 0
      lib/json/json2.js
  2. 5 0
      modules/all_models/engineering_lib.js
  3. 22 0
      modules/all_models/project_feature_lib.js
  4. 1 0
      modules/bills_template_lib/controllers/bills_template_controller.js
  5. 32 2
      modules/material_replace_lib/controllers/material_replace_controller.js
  6. 71 10
      modules/material_replace_lib/facade/material_replace_facade.js
  7. 2 0
      modules/material_replace_lib/routes/material_replace_router.js
  8. 99 0
      modules/project_feature_lib/controllers/project_feature_controller.js
  9. 38 0
      modules/project_feature_lib/facade/project_feature_facade.js
  10. 29 0
      modules/project_feature_lib/routes/project_feature_router.js
  11. 114 19
      modules/reports/rpt_component/jpc_flow_tab.js
  12. 1 1
      modules/reports/util/rpt_pdf_util.js
  13. 6 1
      modules/users/controllers/compilation_controller.js
  14. 3 0
      modules/users/models/engineering_lib_model.js
  15. 54 0
      public/web/id_tree.js
  16. 22 0
      public/web/tree_sheet/tree_sheet_controller.js
  17. 3 3
      test/demo/stringTest.js
  18. 30 0
      web/maintain/bill_template_lib/html/edit.html
  19. 63 2
      web/maintain/bill_template_lib/js/bills_template_edit.js
  20. 1 1
      web/maintain/material_replace_lib/html/edit.html
  21. 217 20
      web/maintain/material_replace_lib/js/material_replace_edit.js
  22. 36 0
      web/maintain/project_feature_lib/html/edit.html
  23. 108 0
      web/maintain/project_feature_lib/html/main.html
  24. 76 0
      web/maintain/project_feature_lib/js/project_feature.js
  25. 37 0
      web/maintain/project_feature_lib/js/project_feature_edit.js
  26. 11 10
      web/maintain/report/js/rpt_tpl_cfg_helper.js
  27. 2 4
      web/maintain/report/js/rpt_tpl_field_location.js
  28. 2 15
      web/maintain/report/rpt_tpl_detail_field_location.html
  29. 10 0
      web/users/css/custom.css
  30. 28 11
      web/users/js/compilation.js
  31. 20 1
      web/users/views/compilation/engineering.html
  32. 5 5
      web/users/views/compilation/modal.html
  33. 7 0
      web/users/views/tool/index.html

+ 486 - 0
lib/json/json2.js

@@ -0,0 +1,486 @@
+/**
+ * Created by zhang on 2018/9/4.
+ */
+/*
+ http://www.JSON.org/json2.js
+ 2010-03-20
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value       any JavaScript value, usually an object or array.
+
+ replacer    an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space       an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or ' '),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear()   + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate())      + 'T' +
+ f(this.getUTCHours())     + ':' +
+ f(this.getUTCMinutes())   + ':' +
+ f(this.getUTCSeconds())   + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+ */
+
+/*jslint evil: true, strict: false */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+ */
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+    this.JSON = {};
+}
+
+(function () {
+
+    function f(n) {
+        // Format integers to have at least two digits.
+        return n < 10 ? '0' + n : n;
+    }
+
+    if (typeof Date.prototype.toJSON !== 'function') {
+
+        Date.prototype.toJSON = function (key) {
+
+            return isFinite(this.valueOf()) ?
+                this.getUTCFullYear()   + '-' +
+                f(this.getUTCMonth() + 1) + '-' +
+                f(this.getUTCDate())      + 'T' +
+                f(this.getUTCHours())     + ':' +
+                f(this.getUTCMinutes())   + ':' +
+                f(this.getUTCSeconds())   + 'Z' : null;
+        };
+
+        String.prototype.toJSON =
+            Number.prototype.toJSON =
+                Boolean.prototype.toJSON = function (key) {
+                    return this.valueOf();
+                };
+    }
+
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        gap,
+        indent,
+        meta = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        },
+        rep;
+
+
+    function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+        escapable.lastIndex = 0;
+        return escapable.test(string) ?
+            '"' + string.replace(escapable, function (a) {
+                var c = meta[a];
+                return typeof c === 'string' ? c :
+                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+            }) + '"' :
+            '"' + string + '"';
+    }
+
+
+    function str(key, holder) {
+
+// Produce a string from holder[key].
+
+        var i,          // The loop counter.
+            k,          // The member key.
+            v,          // The member value.
+            length,
+            mind = gap,
+            partial,
+            value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+        if (value && typeof value === 'object' &&
+            typeof value.toJSON === 'function') {
+            value = value.toJSON(key);
+        }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+        if (typeof rep === 'function') {
+            value = rep.call(holder, key, value);
+        }
+
+// What happens next depends on the value's type.
+
+        switch (typeof value) {
+            case 'string':
+                return quote(value);
+
+            case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+                return isFinite(value) ? String(value) : 'null';
+
+            case 'boolean':
+            case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+                return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+            case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+                if (!value) {
+                    return 'null';
+                }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+                gap += indent;
+                partial = [];
+
+// Is the value an array?
+
+                if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                    length = value.length;
+                    for (i = 0; i < length; i += 1) {
+                        partial[i] = str(i, value) || 'null';
+                    }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+                    v = partial.length === 0 ? '[]' :
+                        gap ? '[\n' + gap +
+                            partial.join(',\n' + gap) + '\n' +
+                            mind + ']' :
+                            '[' + partial.join(',') + ']';
+                    gap = mind;
+                    return v;
+                }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+                if (rep && typeof rep === 'object') {
+                    length = rep.length;
+                    for (i = 0; i < length; i += 1) {
+                        k = rep[i];
+                        if (typeof k === 'string') {
+                            v = str(k, value);
+                            if (v) {
+                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                            }
+                        }
+                    }
+                } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                    for (k in value) {
+                        if (Object.hasOwnProperty.call(value, k)) {
+                            v = str(k, value);
+                            if (v) {
+                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                            }
+                        }
+                    }
+                }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+                v = partial.length === 0 ? '{}' :
+                    gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+                        mind + '}' : '{' + partial.join(',') + '}';
+                gap = mind;
+                return v;
+        }
+    }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+    if (typeof JSON.stringify !== 'function') {
+        JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+            var i;
+            gap = '';
+            indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+            if (typeof space === 'number') {
+                for (i = 0; i < space; i += 1) {
+                    indent += ' ';
+                }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+            } else if (typeof space === 'string') {
+                indent = space;
+            }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+            rep = replacer;
+            if (replacer && typeof replacer !== 'function' &&
+                (typeof replacer !== 'object' ||
+                typeof replacer.length !== 'number')) {
+                throw new Error('JSON.stringify');
+            }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+            return str('', {'': value});
+        };
+    }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+    if (typeof JSON.parse !== 'function') {
+        JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+            var j;
+
+            function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+                var k, v, value = holder[key];
+                if (value && typeof value === 'object') {
+                    for (k in value) {
+                        if (Object.hasOwnProperty.call(value, k)) {
+                            v = walk(value, k);
+                            if (v !== undefined) {
+                                value[k] = v;
+                            } else {
+                                delete value[k];
+                            }
+                        }
+                    }
+                }
+                return reviver.call(holder, key, value);
+            }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+            text = String(text);
+            cx.lastIndex = 0;
+            if (cx.test(text)) {
+                text = text.replace(cx, function (a) {
+                    return '\\u' +
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+                });
+            }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+            if (/^[\],:{}\s]*$/.
+                test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+                replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+                replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+                return typeof reviver === 'function' ?
+                    walk({'': j}, '') : j;
+            }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+            throw new SyntaxError('JSON.parse');
+        };
+    }
+}());
+

+ 5 - 0
modules/all_models/engineering_lib.js

@@ -53,6 +53,11 @@ let modelSchema = {
         type: Schema.Types.Mixed,
         default: []
     },
+    //工程特征库
+    feature_lib:{
+        type: Schema.Types.Mixed,
+        default: []
+    },
     //设置人材机显示列
     glj_col:{
         showAdjustPrice:Boolean//是否显示调整价列

+ 22 - 0
modules/all_models/project_feature_lib.js

@@ -0,0 +1,22 @@
+/**
+ * Created by zhang on 2018/9/3.
+ */
+
+//工程特征库
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+const oprSchema = require('../all_schemas/opr_schema');
+const project_feature_lib = new Schema({
+        ID:{type:String,index:true},
+        creator: String,
+        createDate: Number,
+        recentOpr: [oprSchema],
+        name: String,
+        feature:{
+            type: [Schema.Types.Mixed],
+            default: []
+        }
+    }, {versionKey: false}
+);
+
+mongoose.model("std_project_feature_lib", project_feature_lib,"std_project_feature_lib");

+ 1 - 0
modules/bills_template_lib/controllers/bills_template_controller.js

@@ -47,6 +47,7 @@ class BillsTemplateController extends BaseController {
 
         }catch (err){
             console.log(err);
+            result.message = err.message;
         }
         response.json(result);
     }

+ 32 - 2
modules/material_replace_lib/controllers/material_replace_controller.js

@@ -24,7 +24,6 @@ class ReplaceController extends BaseController{
         };
         response.render("maintain/material_replace_lib/html/main", randerData);
     }
-
     async edit(request,response){
         //先取出替换库信息:
         let libID = request.params.libID;
@@ -54,7 +53,6 @@ class ReplaceController extends BaseController{
             response.redirect(request.headers.referer);
         }
     }
-
     async findLib(request, response){
         let result={
             error:0
@@ -131,6 +129,38 @@ class ReplaceController extends BaseController{
         }
         response.json(result);
     }
+    async saveMaterial(request,response){
+        let result={
+            error:0
+        };
+        try {
+            let data = request.body.data;
+            data = JSON.parse(data);
+            let resultData= await materialFacade.saveMaterial(data);
+            result.data=resultData;
+        }catch (err){
+            console.log(err);
+            result.error=1;
+            result.message = err.message;
+        }
+        response.json(result);
+    }
+    async findMaterial (request,response){
+        let result={
+            error:0
+        };
+        try {
+            let data = request.body.data;
+            data = JSON.parse(data);
+            let resultData = await materialFacade.getMaterialByBillsID(data.billsItemID);
+            result.data=resultData;
+        }catch (err){
+            console.log(err);
+            result.error=1;
+            result.message = err.message;
+        }
+        response.json(result);
+    }
 }
 
 export default ReplaceController;

+ 71 - 10
modules/material_replace_lib/facade/material_replace_facade.js

@@ -11,6 +11,7 @@ let replaceBillModel = mongoose.model("std_replace_bills");
 let replaceMaterialModel = mongoose.model("std_replace_material");
 let StdBillsLib = mongoose.model('std_bills_lib_list');
 let stdBillsModel = mongoose.model('std_bills_lib_bills');
+let stdGLJMode = mongoose.model('std_glj_lib_gljList');
 
 const stdGljLibModel = mongoose.model('std_glj_lib_map');
 
@@ -64,17 +65,79 @@ let materialReplaceLib = {
         return await replaceBillModel.find({libID:libID});
     },
     saveBills:async function(data){
-        let addList = [],updateList =[],deleteList=[];
-        for(let d of data){
-            if(d.type == 'add') addList.push(d);
-            if(d.type == 'update') updateList.push(d);
-            if(d.type == 'delete') deleteList.push(d);
-        }
+        let [addList,updateList,deleteList]  = prepareDatas(data);
         let p = await Promise.all([addBills(addList),updateBills(updateList),deleteBills(deleteList)]);
         return p;
+    },
+    saveMaterial:async function(data){
+        let [addList,updateList,deleteList]  = prepareDatas(data);
+        let p = await Promise.all([addMaterial(addList),replaceMaterial(updateList),deleteMaterial(deleteList)]);
+        return p;
+    },
+    getMaterialByBillsID : async function(billsID){
+       return  await replaceMaterialModel.find({billsItemID:billsID});
     }
 };
-
+function prepareDatas(data) {//整理数据
+    let addList = [],updateList =[],deleteList=[];
+    for(let d of data){
+        if(d.type == 'add') addList.push(d);
+        if(d.type == 'update') updateList.push(d);
+        if(d.type == 'delete') deleteList.push(d);
+    }
+    return [addList,updateList,deleteList]
+}
+async function addMaterial(datas) {
+    let newMaterial = [],missCodes=[];
+    for(let d of datas){
+        let stdGLJ = await stdGLJMode.findOne({repositoryId:d.gljLibID,code:d.code});
+        if(stdGLJ){
+            let temM = {
+                libID:d.libID,
+                billsItemID:d.billsItemID,
+                code:d.code,
+                name:stdGLJ.name,
+                specs:stdGLJ.specs,
+                type:stdGLJ.gljType,
+                unit:stdGLJ.unit
+            };
+            temM.ID = uuidV1();
+            newMaterial.push(temM);
+        }else {
+            missCodes.push(d.code);
+        }
+    }
+    if(newMaterial.length>0){
+        await replaceMaterialModel.create(newMaterial);
+    }
+    return {type:'add',list:newMaterial,missCodes:missCodes};
+}
+async function replaceMaterial(datas) {
+    let tasks = [],list=[],missCodes = [];
+    for(let d of datas){
+        let stdGLJ = await stdGLJMode.findOne({repositoryId:d.gljLibID,code:d.code});
+        if(stdGLJ){
+            let updateData =  {code:d.code, name:stdGLJ.name, specs:stdGLJ.specs, type:stdGLJ.gljType, unit:stdGLJ.unit};
+            let task = {
+                updateOne:{filter:{ID:d.ID}, update :updateData}
+            };
+            tasks.push(task);
+            list.push({ID:d.ID,updateData:updateData})
+        }else {
+            missCodes.push(d.code)
+        }
+    }
+    if(tasks.length > 0) await replaceMaterialModel.bulkWrite(tasks);
+    return {type:'update',list:list,missCodes:missCodes};
+}
+async function deleteMaterial(datas) {
+    let IDList = [];
+    for(let d of datas){
+        if(d.ID) IDList.push(d.ID);
+    }
+    await replaceMaterialModel.deleteMany({ID:{"$in": IDList}});
+    return {type:'delete',list:IDList};
+}
 async function addBills(datas) {
     let newBills = [],missCodes=[];
     for(let d of datas){
@@ -97,7 +160,6 @@ async function addBills(datas) {
     }
     return {type:'add',list:newBills,missCodes:missCodes};
 }
-
 async function updateBills(datas) {
     let tasks = [],list=[],missCodes = [];
     for(let d of datas){
@@ -124,15 +186,14 @@ async function updateBills(datas) {
     if(tasks.length > 0) await replaceBillModel.bulkWrite(tasks);
     return {type:'update',list:list,missCodes:missCodes};
 }
-
 async function deleteBills(datas) {
     let IDList = [];
     for(let d of datas){
         if(d.ID) IDList.push(d.ID)
     }
+    await replaceMaterialModel.deleteMany({billsItemID:{"$in": IDList}});//删除清单时要把对应的材料也删除了
     await replaceBillModel.deleteMany({ID:{"$in": IDList}});
     return {type:'delete',list:IDList};
-
 }
 
 

+ 2 - 0
modules/material_replace_lib/routes/material_replace_router.js

@@ -16,6 +16,8 @@ module.exports =function (app){
     repRouter.post("/saveLib", replaceController.auth, replaceController.init, replaceController.saveLib);
     repRouter.post("/deleteLibByID", replaceController.auth, replaceController.init, replaceController.deleteLibByID);
     repRouter.post("/saveBills", replaceController.auth, replaceController.init, replaceController.saveBills);
+    repRouter.post("/saveMaterial", replaceController.auth, replaceController.init, replaceController.saveMaterial);
+    repRouter.post("/findMaterial", replaceController.auth, replaceController.init, replaceController.findMaterial);
     app.use("/materialReplace", repRouter);
 };
 

+ 99 - 0
modules/project_feature_lib/controllers/project_feature_controller.js

@@ -0,0 +1,99 @@
+/**
+ * Created by zhang on 2018/9/3.
+ */
+import BaseController from "../../common/base/base_controller";
+import featureFacade from "../facade/project_feature_facade";
+let config = require("../../../config/config.js");
+
+class FeatureController extends BaseController{
+    async main(request, response) {
+        let featureLibs = await featureFacade.findByCondition({},{feature:0},false);
+        let randerData = {
+            title:'工程特征库',
+            userAccount: request.session.managerData.username,
+            userID: request.session.managerData.userID,
+            featureLibs:featureLibs,
+            layout: 'maintain/common/html/layout'
+        };
+        response.render("maintain/project_feature_lib/html/main", randerData);
+    }
+    async addLib(request, response){
+        try {
+            await featureFacade.addLib(request.body);
+        }catch (error) {
+            console.log(error);
+        }
+        response.redirect(request.headers.referer);
+    }
+    async findLib(request, response){
+        let result={
+            error:0
+        };
+        try {
+            let data = request.body.data;
+            data = JSON.parse(data);
+            let conditions={'ID' : data.ID};
+            let resultData = await featureFacade.findByCondition(conditions);
+            result.data=resultData;
+        }catch (err){
+            console.log(err);
+            result.error=1;
+            result.message = err.message;
+        }
+        response.json(result);
+    }
+    async saveLib(request, response){
+        let result={
+            error:0
+        };
+        try {
+            let data = request.body.data;
+            data = JSON.parse(data);
+            let resultData= await featureFacade.saveLib(data);
+            result.data=resultData;
+        }catch (err){
+            console.log(err);
+            result.error=1;
+            result.message = err.message;
+        }
+        response.json(result);
+    }
+    async deleteLibByID(request,response){
+        let result={
+            error:0
+        };
+        try {
+            let data = request.body.data;
+            data = JSON.parse(data);
+            let resultData= await featureFacade.deleteLibByID(data.ID);
+            result.data=resultData;
+        }catch (err){
+            console.log(err);
+            result.error=1;
+            result.message = err.message;
+        }
+        response.json(result);
+    }
+    async edit(request,response){
+        //先取出替换库信息:
+        let libID = request.params.libID;
+        let featureLib = await featureFacade.findByCondition({'ID':libID});
+        if(featureLib){
+            let randerData = {
+                title:'工程特征库',
+                mainURL:'/projectFeature/main',
+                libName:featureLib.name,
+                userAccount: request.session.managerData.username,
+                userID: request.session.managerData.userID,
+                featureList:JSON.stringify(featureLib.feature),
+                libID:libID,
+                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                layout: 'maintain/common/html/edit_layout'
+            };
+            response.render("maintain/project_feature_lib/html/edit", randerData);
+        }else {
+            response.redirect(request.headers.referer);
+        }
+    }
+}
+export default FeatureController;

+ 38 - 0
modules/project_feature_lib/facade/project_feature_facade.js

@@ -0,0 +1,38 @@
+/**
+ * Created by zhang on 2018/9/3.
+ */
+import mongoose from "mongoose";
+const uuidV1 = require('uuid/v1');
+let moment = require("moment");
+let projectFeatureModel = mongoose.model('std_project_feature_lib');
+
+
+let projectFeatureLib = {
+    findByCondition:async function(conditions,options,single=true){
+        if(single == true){
+            return await projectFeatureModel.findOne(conditions,options);
+        }else {
+            return await  projectFeatureModel.find(conditions,options);
+        }
+    },
+    addLib : async function (data){
+        let now = new Date().getTime();
+        let dateStr = moment(now).format('YYYY-MM-DD HH:mm:ss');
+        let newLib = {
+            creator: data.userAccount,
+            createDate: now,
+            recentOpr: [{operator: data.userAccount, operateDate: dateStr}],
+            name: data.name,
+        };
+        newLib.ID = uuidV1();
+        return await projectFeatureModel.create(newLib);
+    },
+    saveLib:async function(param) {
+        return await projectFeatureModel.findOneAndUpdate(param.query,param.data,{new:true});
+    },
+    deleteLibByID:async function(ID){
+        return await projectFeatureModel.deleteOne({ID:ID});
+    },
+};
+
+export default projectFeatureLib

+ 29 - 0
modules/project_feature_lib/routes/project_feature_router.js

@@ -0,0 +1,29 @@
+/**
+ * Created by zhang on 2018/9/3.
+ */
+
+let express = require("express");
+let featureRouter =express.Router();
+import FeatureController from "../controllers/project_feature_controller";
+let featureController = new FeatureController();
+
+module.exports =function (app){
+
+    featureRouter.get("/main", featureController.auth, featureController.init, featureController.main);
+    featureRouter.post("/addLib", featureController.auth, featureController.init, featureController.addLib);
+    featureRouter.post("/findLib", featureController.auth, featureController.init, featureController.findLib);
+    featureRouter.post("/saveLib", featureController.auth, featureController.init, featureController.saveLib);
+    featureRouter.post("/deleteLibByID", featureController.auth, featureController.init, featureController.deleteLibByID);
+    featureRouter.get("/edit/:libID", featureController.auth, featureController.init, featureController.edit);
+/*    repRouter.get("/edit/:libID", replaceController.auth, replaceController.init, replaceController.edit);
+    repRouter.post("/findLib", replaceController.auth, replaceController.init, replaceController.findLib);
+    repRouter.post("/addLib", replaceController.auth, replaceController.init, replaceController.addLib);
+    repRouter.post("/saveLib", replaceController.auth, replaceController.init, replaceController.saveLib);
+    repRouter.post("/deleteLibByID", replaceController.auth, replaceController.init, replaceController.deleteLibByID);
+    repRouter.post("/saveBills", replaceController.auth, replaceController.init, replaceController.saveBills);
+    repRouter.post("/saveMaterial", replaceController.auth, replaceController.init, replaceController.saveMaterial);
+    repRouter.post("/findMaterial", replaceController.auth, replaceController.init, replaceController.findMaterial);*/
+    app.use("/projectFeature", featureRouter);
+};
+
+

+ 114 - 19
modules/reports/rpt_component/jpc_flow_tab.js

@@ -160,7 +160,7 @@ JpcFlowTabSrv.prototype.createNew = function(){
         me.multiCols = 1;
         me.pagesAmt = 0;
     };
-    JpcFlowTabResult.sorting = function(rptTpl, dataObj, dataSeq) {
+    JpcFlowTabResult.sorting = function(rptTpl, dataObj, dataSeq, $CURRENT_RPT) {
         let me = this;
         let FLOW_NODE_STR = me.isEx?JV.NODE_FLOW_INFO_EX:JV.NODE_FLOW_INFO;
         if (rptTpl[FLOW_NODE_STR][JV.NODE_FLOW_SEG_SUM]) JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[FLOW_NODE_STR][JV.NODE_FLOW_SEG_SUM][JV.PROP_SUM_FIELDS], me.seg_sum_tab_fields, me.seg_sum_fields_idx, me.isEx);
@@ -174,11 +174,24 @@ JpcFlowTabSrv.prototype.createNew = function(){
             me.segments.push(dataSeq[si].slice(0));
         }
         //pre-sum the data(for seg sum display)
+        //考虑到实际会有离散指标的汇总,这些离散指标数据是通过计算式后得来的,这种情况下,不适宜在sorting阶段进行汇总统计,现挪到preSetupPages阶段处理
+    };
+    JpcFlowTabResult.sumSeg = function (dataObj, $CURRENT_RPT) {
+        let me = this;
         let data_details = me.isEx?dataObj[JV.DATA_DETAIL_DATA_EX]:dataObj[JV.DATA_DETAIL_DATA],
             data_fields = [];
         for (let i = 0; i < me.seg_sum_fields_idx.length; i++) {
-            let data_field = data_details[me.seg_sum_fields_idx[i]];
-            data_fields.push(data_field);
+            if (typeof(me.seg_sum_fields_idx[i])=="object") {
+                let exField = JE.F(me.seg_sum_fields_idx[i][JV.PROP_ID], $CURRENT_RPT);
+                if (exField) {
+                    data_fields.push(exField["data_field"]);
+                } else {
+                    data_fields.push(null);
+                }
+            } else {
+                let data_field = data_details[me.seg_sum_fields_idx[i]];
+                data_fields.push(data_field);
+            }
         }
         for (let i = 0; i < me.segments.length; i++) { //seg level
             if (me.segments[i].length > 0) {
@@ -192,7 +205,6 @@ JpcFlowTabSrv.prototype.createNew = function(){
                 }
                 me.segSumValLst.push(rowGrandTotal);
             }
-
         }
     };
     JpcFlowTabResult.sumUpGrp = function ($CURRENT_RPT, dataObj, segIdx, preGrpIdx, nexGrpIdx) {
@@ -329,14 +341,17 @@ JpcFlowTabSrv.prototype.createNew = function(){
                             doc.fontSize(12);
                         }
                         let hasSplitStr = false, splitStrArr = [];
+                        let accAmt = 0;
                         for (let i = 0; i < values.length; i++) {
-                            let amt = JpcCommonHelper.getStringLinesInArea(area, values[i], doc) - 1;
-                            rst += amt;
-                            if (amt > 0) {
+                            let amt = JpcCommonHelper.getStringLinesInArea(area, values[i], doc);
+                            accAmt += amt;
+                            // rst += amt;
+                            if (amt > 1) {
                                 hasSplitStr = true;
                                 splitStrArr.push(i);
                             }
                         }
+                        if (accAmt > rst) rst = accAmt;
                         if (hasSplitStr) {
                             let newValArr = [];
                             for (let i = 0; i < values.length; i++) {
@@ -395,6 +410,7 @@ JpcFlowTabSrv.prototype.createNew = function(){
         if (followTabEx) {
             JpcFieldHelper.findAndPutDataFieldIdx(rptTpl, rptTpl[JV.NODE_FLOW_INFO_EX][JV.NODE_FLOW_CONTENT][JV.PROP_FLOW_FIELDS], null, me.disp_fields_ex_idx, true);
         }
+        me.sumSeg(dataObj, $CURRENT_RPT); //考虑到实际会有离散指标的汇总,这些离散指标数据是通过计算式后得来的,这种情况下,不适宜在sorting阶段进行汇总统计,现统一挪到这里处理
         if (me.paging_option === JV.PAGING_OPTION_INFINITY) {
             rst = me.segments.length;
             let pageStatus = [true, true, false, true, true, true, false, false];
@@ -759,8 +775,17 @@ JpcFlowTabSrv.prototype.createNew = function(){
                 let contentValuesIdx = me.dispValueIdxLst[page - 1];
                 let page_sum_data_fields = [];
                 for (let i = 0; i < me.page_sum_fields_idx.length; i++) {
-                    let data_field = data_details[me.page_sum_fields_idx[i]];
-                    page_sum_data_fields.push(data_field);
+                    if (typeof(me.page_sum_fields_idx[i])=="object") {
+                        let exField = JE.F(me.page_sum_fields_idx[i][JV.PROP_ID], $CURRENT_RPT);
+                        if (exField) {
+                            page_sum_data_fields.push(exField["data_field"]);
+                        } else {
+                            page_sum_data_fields.push(null);
+                        }
+                    } else {
+                        let data_field = data_details[me.page_sum_fields_idx[i]];
+                        page_sum_data_fields.push(data_field);
+                    }
                 }
                 let rowGrandTotal = [];
                 for (let di = 0; di < page_sum_data_fields.length; di++) {
@@ -976,11 +1001,13 @@ JpcFlowTabSrv.prototype.createNew = function(){
             }
             let rst = JpcCommonOutputHelper.createCommonOutput(tab_field, showText, controls);
             rst[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, rows, rowIdx, cols, colIdx, me.multiCols, multiColIdx, true, false);
+            rst[JV.PROP_IS_AUTO_HEIGHT] = true;
             return rst;
         } else {
             if (contentValInfo[3] > 0) showText = '';
             let rst = JpcCommonOutputHelper.createCommonOutput(tab_field, showText, controls);
             rst[JV.PROP_AREA] = JpcAreaHelper.outputArea(tab_field[JV.PROP_AREA], band, unitFactor, rows, rowIdx, cols, colIdx, me.multiCols, multiColIdx, true, false);
+            rst[JV.PROP_IS_AUTO_HEIGHT] = false;
             return rst;
         }
     };
@@ -1055,17 +1082,24 @@ function setupControl(mergeCell, controls) {
             "Shrink": "T",
             "ShowZero": orgCtrl.ShowZero,
             "Horizon": orgCtrl.Horizon,
-            "Vertical": "top",
+            "Vertical": orgCtrl.Vertical,
             "Wrap": "T",
-            "VerticalForExcel": "justify"
+            "VerticalForExcel": "center"
         };
     } else {
         mergeCell[JV.PROP_CONTROL].Shrink = "T";
         mergeCell[JV.PROP_CONTROL].Vertical = "top";
         mergeCell[JV.PROP_CONTROL].Wrap = "T";
-        mergeCell[JV.PROP_CONTROL].VerticalForExcel = "justify";
+        mergeCell[JV.PROP_CONTROL].VerticalForExcel = "center";
         orgCtrl = mergeCell[JV.PROP_CONTROL];
     }
+    if (mergeCell[JV.PROP_IS_AUTO_HEIGHT]) {
+        mergeCell[JV.PROP_CONTROL]["VerticalForExcel"] = 'justify';
+        // mergeCell[JV.PROP_CONTROL]["Vertical"] = 'center';
+    } else {
+        mergeCell[JV.PROP_CONTROL]["VerticalForExcel"] = 'center';
+        // mergeCell[JV.PROP_CONTROL]["Vertical"] = 'top';
+    }
     return orgCtrl;
 }
 
@@ -1075,31 +1109,92 @@ function combineAutoHeightCells(prepareObj, page, controls) {
         //merge cells' value and area
         //备注: 系统逻辑已经把Cell的顺序放好,无需再做排序。
         for (let mergeKey in prepareObj.pageCellObj) {
-            if (prepareObj.pageCellObj[mergeKey].length > 1) {
-                let firstMergeCell = prepareObj.pageCellObj[mergeKey][0].cell;
+            let sameColCells = prepareObj.pageCellObj[mergeKey]; //左右位置相同的Cell先放在一起,统一处理
+            if (sameColCells.length > 1) {
+                let firstMergeCell = sameColCells[0].cell;
+                firstMergeCell[JV.PROP_STYLE] = firstMergeCell[JV.PROP_STYLE].slice(0, firstMergeCell[JV.PROP_STYLE].indexOf("_AutoHeightMerge")); //首先还原original style
+                let orgCtrl = setupControl(firstMergeCell, controls);
+                let validValueAmt = 0, fullValidValueAmt = 0;
+                for (let i = 1; i < sameColCells.length; i++) {
+                    let mergeCell = sameColCells[i].cell;
+                    if (mergeCell[JV.PROP_STYLE].indexOf("_AutoHeightMerge_Top") < 0) {
+                        fullValidValueAmt++;
+                        //merge into the firstMergeCell! position & value
+                        firstMergeCell[JV.PROP_AREA][JV.PROP_BOTTOM] = mergeCell[JV.PROP_AREA][JV.PROP_BOTTOM];
+                        if (mergeCell[JV.PROP_VALUE]) {
+                            firstMergeCell[JV.PROP_VALUE] = firstMergeCell[JV.PROP_VALUE] + "|" + mergeCell[JV.PROP_VALUE];
+                            validValueAmt++;
+                        } else {
+                            // firstMergeCell[JV.PROP_VALUE] = firstMergeCell[JV.PROP_VALUE] + "|" ;
+                        }
+                        rst.push(sameColCells[i].cellIdx); //记下Cell的位置,在函数外消除
+                        //如果到了最后一条数据,得判断firstMergeCell是否满格(即数据是满的,有可能有些格数据也有折行但不是自动行高判断指标)
+                        //不满格的cell的Vertical强制设置为 'center'
+                        if (i === sameColCells.length - 1 && validValueAmt !== fullValidValueAmt) {
+                            // firstMergeCell[JV.PROP_CONTROL].Shrink = orgCtrl.Shrink;
+                            // firstMergeCell[JV.PROP_CONTROL].Wrap = "F";
+                            // firstMergeCell[JV.PROP_CONTROL].VerticalForExcel = null;
+                            firstMergeCell[JV.PROP_CONTROL].VerticalForExcel = 'center';
+                        }
+                    } else {
+                        //碰到新开始的自动行高行,判断原先的firstMergeCell是否满格(即数据是满的,有可能有些格数据也有折行但不是自动行高判断指标)
+                        //不满格的cell的Vertical强制设置为 'center'
+                        if (validValueAmt !== fullValidValueAmt) {
+                            // firstMergeCell[JV.PROP_CONTROL].Shrink = orgCtrl.Shrink;
+                            // firstMergeCell[JV.PROP_CONTROL].Wrap = "F";
+                            // firstMergeCell[JV.PROP_CONTROL].VerticalForExcel = null;
+                            firstMergeCell[JV.PROP_CONTROL].VerticalForExcel = 'center';
+                        }
+                        firstMergeCell = sameColCells[i].cell;
+                        firstMergeCell[JV.PROP_STYLE] = firstMergeCell[JV.PROP_STYLE].slice(0, firstMergeCell[JV.PROP_STYLE].indexOf("_AutoHeightMerge"));
+                        orgCtrl = setupControl(firstMergeCell, controls);
+                        validValueAmt = 0;
+                        fullValidValueAmt = 0;
+                    }
+                }
+            }
+        }
+        rst.sort(function (i1, i2) {
+            return (i1 > i2)?1:-1;
+        });
+    }
+    return rst;
+}
+
+function combineAutoHeightCellsBk(prepareObj, page, controls) {
+    let rst = [];
+    if (prepareObj.cellsArr) {
+        //merge cells' value and area
+        //备注: 系统逻辑已经把Cell的顺序放好,无需再做排序。
+        for (let mergeKey in prepareObj.pageCellObj) {
+            let sameColCells = prepareObj.pageCellObj[mergeKey]; //左右位置相同的Cell先放在一起,统一处理
+            if (sameColCells.length > 1) {
+                let firstMergeCell = sameColCells[0].cell;
                 firstMergeCell[JV.PROP_STYLE] = firstMergeCell[JV.PROP_STYLE].slice(0, firstMergeCell[JV.PROP_STYLE].indexOf("_AutoHeightMerge"));
                 let orgCtrl = setupControl(firstMergeCell, controls);
                 let validValueAmt = 0;
-                for (let i = 1; i < prepareObj.pageCellObj[mergeKey].length; i++) {
-                    let mergeCell = prepareObj.pageCellObj[mergeKey][i].cell;
+                for (let i = 1; i < sameColCells.length; i++) {
+                    let mergeCell = sameColCells[i].cell;
                     if (mergeCell[JV.PROP_STYLE].indexOf("_AutoHeightMerge_Top") < 0) {
                         //merge into the firstMergeCell!
                         firstMergeCell[JV.PROP_AREA][JV.PROP_BOTTOM] = mergeCell[JV.PROP_AREA][JV.PROP_BOTTOM];
                         firstMergeCell[JV.PROP_VALUE] = firstMergeCell[JV.PROP_VALUE] + "|" + mergeCell[JV.PROP_VALUE];
                         if (mergeCell[JV.PROP_VALUE]) validValueAmt++;
-                        rst.push(prepareObj.pageCellObj[mergeKey][i].cellIdx);
-                        if (i === prepareObj.pageCellObj[mergeKey].length - 1 && validValueAmt === 0) {
+                        rst.push(sameColCells[i].cellIdx);
+                        if (i === sameColCells.length - 1 && validValueAmt === 0) {
                             firstMergeCell[JV.PROP_CONTROL].Shrink = orgCtrl.Shrink;
                             firstMergeCell[JV.PROP_CONTROL].Wrap = "F";
                             firstMergeCell[JV.PROP_CONTROL].VerticalForExcel = null;
+                            // firstMergeCell[JV.PROP_CONTROL].VerticalForExcel = 'center';
                         }
                     } else {
                         if (validValueAmt === 0) {
                             firstMergeCell[JV.PROP_CONTROL].Shrink = orgCtrl.Shrink;
                             firstMergeCell[JV.PROP_CONTROL].Wrap = "F";
                             firstMergeCell[JV.PROP_CONTROL].VerticalForExcel = null;
+                            // firstMergeCell[JV.PROP_CONTROL].VerticalForExcel = 'center';
                         }
-                        firstMergeCell = prepareObj.pageCellObj[mergeKey][i].cell;
+                        firstMergeCell = sameColCells[i].cell;
                         firstMergeCell[JV.PROP_STYLE] = firstMergeCell[JV.PROP_STYLE].slice(0, firstMergeCell[JV.PROP_STYLE].indexOf("_AutoHeightMerge"));
                         orgCtrl = setupControl(firstMergeCell, controls);
                         validValueAmt = 0;

+ 1 - 1
modules/reports/util/rpt_pdf_util.js

@@ -231,7 +231,7 @@ function export_pdf_file (pageData, paperSize, fName, callback) {
             doc.rotate(font.FontAngle,rotateOptions);
         }
         doc.text(val,output[0], output[1], options);
-        doc.font(__dirname + '/pdf_base_files/Smart.ttf');
+        doc.font(__dirname + '/pdf_base_files/simhei_bold_italic.ttf');
         // doc.restore();
     }
 

+ 6 - 1
modules/users/controllers/compilation_controller.js

@@ -21,6 +21,7 @@ import STDCalcProgramModel from "../../common/std/std_calc_program_model";
 const billsGuidanceFc = require('../../std_billsGuidance_lib/facade/facades');
 import mainColFacade from "../../main_col_lib/facade/main_col_facade";
 import billTemplateFacade from "../../bills_template_lib/facade/bills_template_facade";
+import projectFeatureFacade from "../../project_feature_lib/facade/project_feature_facade";
 let config = require("../../../config/config.js");
 const fs = require('fs');
 
@@ -175,7 +176,7 @@ class CompilationController extends BaseController {
         let section = request.params.section;
         let selectedCompilation = request.session.selectedCompilation;
 
-        let compilationList = [],billList = [], rationList = [], gljList = [],feeRateList = [], libData = {}, billsTemplateData = [];
+        let compilationList = [],billList = [], rationList = [], gljList = [],feeRateList = [], libData = {}, billsTemplateData = [],featureList = [];
         let valuationData = {}, valuationList = {}, artificialCoefficientList = [], calculationList = [], billsGuidanceList = [], mainTreeColList = [];
         let billTemplateList = [];
         try {
@@ -229,6 +230,9 @@ class CompilationController extends BaseController {
             //获取清单指引数据
             billsGuidanceList = await billsGuidanceFc.getBillsGuideLibs({compilationId: selectedCompilation._id, $or: [{deleted: null}, {deleted: false}]});
 
+            //获取工程特征库
+            featureList = await projectFeatureFacade.findByCondition({},null,false);
+
         } catch (error) {
             console.log(error);
         }
@@ -250,6 +254,7 @@ class CompilationController extends BaseController {
             gljCol:JSON.stringify(libData.glj_col),
             calculationList: JSON.stringify(calculationList),
             billsGuidanceList: JSON.stringify(billsGuidanceList),
+            featureList:JSON.stringify(featureList),
             layout: 'users/views/layout/layout',
             LicenseKey:config.getLicenseKey(process.env.NODE_ENV)
         };

+ 3 - 0
modules/users/models/engineering_lib_model.js

@@ -160,6 +160,9 @@ class EngineeringLibModel extends BaseModel {
         // 判断人工系数
         data.artificial_lib = this._validLib(data.artificial_lib);
 
+        //判断工程特征库
+        data.feature_lib = this._validLib(data.feature_lib);
+
         //计税方式组合
         data.tax_group = this._validLib(data.tax_group);
 

+ 54 - 0
public/web/id_tree.js

@@ -519,6 +519,33 @@ var idTree = {
             }
             return node;
         };
+        Tree.prototype.m_insert = function (datas,parentID, nextSiblingID) {
+           // var newID = this.newNodeID(), node = null, data = {};
+            var parent = parentID == -1 ? null : this.nodes[this.prefix + parentID];
+            var nextSibling = nextSiblingID == -1 ? null: this.nodes[this.prefix + nextSiblingID];
+            let preInsertNode = null,nodes = [];
+            for(let d of datas){
+                let node = new Node(this,d.data);
+                if(preInsertNode == null){
+                    if (nextSibling) {
+                        tools.addNodes(this, parent, [node], nextSibling.siblingIndex());
+                    } else {
+                        tools.addNodes(this, parent, [node]);
+                    }
+                }else {
+                    tools.addNodes(this, parent, [node], preInsertNode.siblingIndex());
+                }
+                this.nodes[this.prefix + d.data.ID] = node;
+                if(preInsertNode) node.setNextSibling(preInsertNode);
+                preInsertNode = node;
+                nodes.push(node);
+            }
+            tools.sortTreeItems(this);
+            return nodes;
+        };
+
+
+
         Tree.prototype.insertByID = function (newID, parentID, nextSiblingID) {
             var node = null, data = {};
             var parent = parentID == -1 ? null : this.nodes[this.prefix + parentID];
@@ -557,6 +584,33 @@ var idTree = {
             }
             return data;
         };
+        //插入多行
+        Tree.prototype.getInsertDatas = function (rowCount,parentID, nextSiblingID) {
+            let data = [],preInsertID = null,lastID;
+            let parent = parentID == -1 ? null : this.nodes[this.prefix + parentID];
+            let nextSibling = nextSiblingID == -1 ? null: this.nodes[this.prefix + nextSiblingID];
+            for(let i=0;i<rowCount ;i++){//先插入的在最后,后插的在最前
+                let newID = this.newNodeID();
+                if(newID !== -1){
+                    if(preInsertID == null){//说明是第一个插入的
+                        data.push({type: 'new', data: this.getDataTemplate(newID, parent ? parent.getID() : this.setting.rootId, nextSibling ? nextSibling.getID() : this.setting.rootId)});
+                    }else {//其它的下一节点ID取上一个插入的节点
+                        data.push({type: 'new', data: this.getDataTemplate(newID, parent ? parent.getID() : this.setting.rootId, preInsertID)});
+                    }
+                    this.maxNodeID(newID);
+                    preInsertID = newID;
+                }
+            }
+            if (nextSibling && nextSibling.preSibling) {
+                tools.addUpdateDataForNextSibling(data, nextSibling.preSibling, preInsertID);
+            } else if (parent && parent.children.length !== 0) {
+                tools.addUpdateDataForNextSibling(data, parent.lastChild(), preInsertID);
+            } else if (!parent && this.roots.length !== 0) {
+                tools.addUpdateDataForNextSibling(data, this.roots[this.roots.length - 1], preInsertID);
+            }
+            return data;
+
+        };
         Tree.prototype.insertByData = function (data, parentID, nextSiblingID) {
             var parent = parentID == -1 ? null : this.nodes[this.prefix + parentID];
             var nextSibling = nextSiblingID == -1 ? null : this.nodes[this.prefix + nextSiblingID];

+ 22 - 0
public/web/tree_sheet/tree_sheet_controller.js

@@ -42,6 +42,28 @@ var TREE_SHEET_CONTROLLER = {
                 }
             }
         };
+        controller.prototype.m_insert = function (datas) {
+            let nodes = [], that = this,  sels = this.sheet.getSelections();
+            if(this.tree){
+                if (this.tree.selected) {
+                    nodes = this.tree.m_insert(datas,this.tree.selected.getParentID(), this.tree.selected.getNextSiblingID());
+                } else {
+                    nodes = this.tree.m_insert(datas);
+                }
+            }
+            let length = nodes.length;
+            if (nodes.length > 0) {
+                TREE_SHEET_HELPER.massOperationSheet(this.sheet, function () {
+                    that.sheet.addRows(nodes[length - 1].serialNo(), length);
+                    TREE_SHEET_HELPER.refreshTreeNodeData(that.setting, that.sheet, nodes, false);
+                    that.setTreeSelected(nodes[length - 1]);
+                    that.sheet.setSelection(nodes[length - 1].serialNo(), sels[0].col, 1, 1);
+                    //that.sheet.showRow(newNode.serialNo(), GC.Spread.Sheets.VerticalPosition.center);
+                });
+            }
+        };
+
+
         controller.prototype.insertByID = function (ID) {
             var newNode = null, that = this,  sels = this.sheet.getSelections();
             if (this.tree) {

+ 3 - 3
test/demo/stringTest.js

@@ -32,9 +32,9 @@ let strUtil = require('../../public/stringUtil');
 // })
 
 test('string encodeURI', function(t){
-    let str = 123;
-    console.log(str.toString());
-    console.log(str.toString().replace('\t', ' '));
+    let str = "讠";
+    console.log(str);
+    console.log(encodeURI(str));
     t.end();
 })
 

+ 30 - 0
web/maintain/bill_template_lib/html/edit.html

@@ -16,6 +16,7 @@
                         <a href="" class="btn btn-sm"><i class="fa fa-scissors" aria-hidden="true"></i> 剪切</a>
                         <a href="" class="btn btn-sm"><i class="fa fa-clipboard" aria-hidden="true"></i> 粘贴</a>
                         <a href="javascript:void(0)" class="btn btn-sm" id="insert"><i class="fa fa-sign-in" aria-hidden="true"></i> 插入</a>
+                        <a href="javascript:void(0)" class="btn btn-sm" id="m_insert" data-toggle="modal" data-target="#insertInputDiv"><i class="fa fa-sign-in" aria-hidden="true"></i> 插入多行</a>
                         <a href="javascript:void(0)" class="btn btn-sm" id="delete"><i class="fa fa-remove" aria-hidden="true"></i> 删除</a>
                         <a href="javascript:void(0)" class="btn btn-sm" id="upLevel"><i class="fa fa-arrow-left" aria-hidden="true"></i> 升级</a>
                         <a href="javascript:void(0)" class="btn btn-sm" id="downLevel"><i class="fa fa-arrow-right" aria-hidden="true"></i> 降级</a>
@@ -30,6 +31,35 @@
     </div>
 </div>
 
+<!--弹出插入多行对话框-->
+<div class="modal fade" id="insertInputDiv" data-backdrop="static" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">插入多行</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <form>
+                    <div class="form-group">
+                        <label>插入行数</label>
+                        <input id="insertCount" class="form-control" placeholder="行数" type="number" min="1" value="1" step="1">
+                        <small class="form-text text-danger" id="insertError" style="display: none">请输入正确的行数。</small>
+                        <input id="libID" type="hidden">
+                    </div>
+                </form>
+            </div>
+            <div class="modal-footer">
+                <a id="m_insert_confirm" href="javascript: void(0);" class="btn btn-primary" >确定</a>
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+
 <script>
     let billsTemplateData = '<%- billsTemplateData %>';
     let billsFixedFlagList = '<%- billsFixedFlagList %>';

+ 63 - 2
web/maintain/bill_template_lib/js/bills_template_edit.js

@@ -229,6 +229,7 @@ $(document).ready(function () {
         datas.forEach(function (data) {
             let node = tree.findNode(data.data.ID);
             if (node) {
+                setFlagsIndex(data.data,node.data.flagsIndex);
                 $.extend(true, node.data, data.data);
             }
         });
@@ -301,16 +302,26 @@ $(document).ready(function () {
             setFee(data.data, fieldName, value);
         }
     };
+    let setFlagsIndex = function (data,flagsIndex) {
+        if (data.flags) {
+            flagsIndex?data.flagsIndex = flagsIndex:data.flagsIndex={};
+            for (let flag of data.flags) {
+                data.flagsIndex[flag.fieldName] = flag;
+            }
+        }
+    };
+
 
     billsTemplateData = billsTemplateData.replace(/\n/g, '\\n');
     let templateData = JSON.parse(billsTemplateData);
     for (let data of templateData) {
-        if (data.flags) {
+        setFlagsIndex(data);
+       /* if (data.flags) {
             data.flagsIndex = {};
             for (let flag of data.flags) {
                 data.flagsIndex[flag.fieldName] = flag;
             }
-        }
+        }*/
     }
 
     for (col of TEMPLATE_BILLS_SETTING.cols) {
@@ -429,6 +440,56 @@ $(document).ready(function () {
             $(me).removeClass('disabled');
         }
     });
+
+    $('#m_insert_confirm').click(function () {
+        let me = this;
+        let insertCount = $("#insertCount").val();
+        if(isNaN(insertCount)||insertCount<1){
+            $("#insertError").show();
+            return;
+        }
+        $(me).addClass('disabled');
+        let selected = controller.tree.selected, updateData;
+        if (selected) {
+            updateData = controller.tree.getInsertDatas(insertCount,selected.getParentID(), selected.getNextSiblingID());
+        } else {
+            updateData = controller.tree.getInsertDatas(insertCount);
+        }
+        if (updateData.length > 0) {
+            CommonAjax.post(updateUrl, updateData, function (data) {
+                data = _.filter(data,{'type':'new'});
+                console.log(data);
+                controller.m_insert(data);
+                controller.showTreeData();
+                $("#insertInputDiv").modal('hide');
+                $("#insertError").hide();
+                $(me).removeClass('disabled');
+            });
+        } else {
+            alert('新增节点失败, 请重试.');
+            $(me).removeClass('disabled');
+        }
+
+      /*  var selected = controller.tree.selected, updateData;
+        if (selected) {
+            updateData = controller.tree.getInsertData(selected.getParentID(), selected.getNextSiblingID());
+        } else {
+            updateData = controller.tree.getInsertData();
+        }
+        if (updateData.length > 0) {
+            CommonAjax.post(updateUrl, updateData, function (data) {
+                controller.insert();
+                controller.showTreeData();
+                $(me).removeClass('disabled');
+            });
+        } else {
+            alert('新增节点失败, 请重试.');
+            $(me).removeClass('disabled');
+        }*/
+    });
+
+
+
     $('#delete').click(function () {
         let me = this;
         $(me).addClass('disabled');

+ 1 - 1
web/maintain/material_replace_lib/html/edit.html

@@ -12,7 +12,7 @@
             <div class="row">
                 <div class="tools-bar p-1 d-flex justify-content-between col-lg-12" style="background: #f7f7f7;">
                     <div class="input-group input-group-sm col-lg-4">
-                        <input class="form-control" placeholder="搜索定位" type="text">
+                        <input class="form-control" placeholder="搜索定位" type="text" id = 'keyword'>
                         <span class="input-group-btn">
                               <button class="btn btn-secondary" type="button"><i class="fa fa-search"></i></button>
                             </span>

+ 217 - 20
web/maintain/material_replace_lib/js/material_replace_edit.js

@@ -5,6 +5,7 @@
 let materialOjb = {
     billsSpread:null,
     materialSpread:null,
+    allBills:JSON.parse(billsList),
     billsList:JSON.parse(billsList),
     materialList:[],
     billsSetting:{
@@ -49,6 +50,9 @@ let materialOjb = {
         this.materialSheet = this.materialSpread .getSheet(0);
         sheetCommonObj.initSheet(this.materialSheet,this.materialSetting, 30);
         this.materialSheet.name('materialSheet');
+        this.materialSheet.bind(GC.Spread.Sheets.Events.ValueChanged, this.onMaterialValueChange);
+        this.materialSheet.bind(GC.Spread.Sheets.Events.EditStarting, this.onMaterialEditStarting);
+        this.materialSheet.bind(GC.Spread.Sheets.Events.RangeChanged, this.onMaterialRangeChange);
         this.initRightClick("materialSpread",this.materialSpread);
         this.refreshSheet();
 
@@ -68,14 +72,19 @@ let materialOjb = {
             if(me.billsList[sel.row + i]) deleteList.push(getDeleteDatas(me.billsList[sel.row + i]));
         }
         if(deleteList.length > 0) await me.saveBills(deleteList);
-
-
         function getDeleteDatas(tem) {
             return {type:'delete', ID:tem.ID}
         }
 
     },
-
+    deleteMaterial:async function(sheet){
+        let me = this,deleteList = [];
+        let sel = sheet.getSelections()[0];
+        for(let i = 0; i<sel.rowCount;i++){
+            if(me.materialList[sel.row + i]) deleteList.push(me.getMaterialUpdateData(null,me.materialList[sel.row + i].ID,true));
+        }
+        if(deleteList.length > 0) await me.saveMaterial(deleteList);
+    },
     initRightClick : function(id,spread) {
         let me = this;
         let sheet = spread.getActiveSheet();
@@ -94,20 +103,55 @@ let materialOjb = {
                         return !me.canDelete(sheet);
                     },
                     callback: function (key, opt) {
-                        sheet.name() == 'billsSheet' ? me.deleteBills(sheet):"";
+                        sheet.name() == 'billsSheet' ? me.deleteBills(sheet):me.deleteMaterial(sheet);
                        console.log( me.rightClickTarget);
                     }
                 }
             }
         });
     },
-    refreshSheet:function(){
+    refreshSheet:async function(){
+        this.getBillsList();
         sheetCommonObj.showData(this.billsSheet,this.billsSetting,this.billsList);
         this.billsSheet.setRowCount(this.billsList.length + 30);
-
-
+        this.showMaterialList();
+    },
+    getBillsList:function () {
+        let keyword = $("#keyword").val();
+        if(isDef(keyword)&&keyword!==''){
+            this.billsList = _.filter(this.allBills,function (item) {
+                return item.code.indexOf(keyword)!=-1 || item.name.indexOf(keyword)!=-1;
+            })
+        }else {
+            this.billsList = this.allBills;
+        }
+        this.billsList = _.sortBy(this.billsList,'code');
+    },
+    getMateriaList:async function () {
+        let billsItemID =  this.getCurrentBillsID();
+        if(billsItemID){
+            this.materialList = await this.getMaterialByBillsID(billsItemID)//getMaterialByBills
+        }else {
+            this.materialList = [];
+        }
+    },
+    showMaterialList:async function () {
+        await this.getMateriaList();
+        this.refreshMaterialSheet();
+    },
+    refreshMaterialSheet:function () {
+        this.materialList = _.sortBy(this.materialList,'code');
+        sheetCommonObj.showData(this.materialSheet,this.materialSetting,this.materialList);
+        this.materialSheet.setRowCount(this.materialList.length + 30);
     },
     onBillsSelectionChange:function (sender,args) {
+        let me = materialOjb;
+        let nsel = args.newSelections?args.newSelections[0]:null;
+        let osel = args.oldSelections?args.oldSelections[0]:null;
+        if(nsel && osel && nsel.row != osel.row){
+            me.showMaterialList();
+            me.materialSheet.showRow(0, GC.Spread.Sheets.VerticalPosition.top);
+        }
         args.sheet.repaint();
     },
     onBillsRangeChange:function (sender,args) {
@@ -117,14 +161,14 @@ let materialOjb = {
             for(let c of args.changedCells){
                 let field = me.billsSetting.header[c.col].dataCode;
                 let newValue =  args.sheet.getCell(c.row,c.col).value();
+                let data = null;
                 if(me.validateBills(field,newValue)){
                     if(c.row < me.billsList.length){
-                        let data = me.getUpdateData(field,newValue,me.billsList[c.row].code);
-                        if(data) updateDatas.push(data);
+                         data = me.getUpdateData(field,newValue,me.billsList[c.row].code);
                     }else if(field == 'code'){//如果是在空白行粘贴,并且是编码列,则是新增,其它的忽略;
-                        let data = me.getUpdateData(field,newValue,null);
-                        if(data) updateDatas.push(data);
+                         data = me.getUpdateData(field,newValue,null);
                     }
+                    if(data) updateDatas.push(data);
                 }else {
                     break;
                 }
@@ -136,8 +180,33 @@ let materialOjb = {
         }
          me.refreshSheet();
     },
+    onMaterialRangeChange:function(sender,args){
+        let me = materialOjb;
+        let updateDatas = [];
+        if(args.action == GC.Spread.Sheets.RangeChangedAction.paste){
+            for(let c of args.changedCells){
+                let code =  args.sheet.getCell(c.row,c.col).value(),data = null;
+                if(me.validateMaterial(code)){
+                    if(c.row < me.materialList.length){
+                         data = me.getMaterialUpdateData(code,me.materialList[c.row].ID);
+                    }else {//如果是在空白行粘贴,并且是编码列,则是新增,其它的忽略;
+                         data = me.getMaterialUpdateData(code,null);
+                    }
+                    if(data) updateDatas.push(data);
+                }else {
+                    break;
+                }
+            }
+            if(updateDatas.length > 0){
+                me.saveMaterial(updateDatas);
+                return;
+            }
+        }
+        me.showMaterialList();
+    },
+
 
-    onBillsValueChange: function(sander,args){
+    onBillsValueChange: function(sender,args){
         let me = materialOjb;
         let field = me.billsSetting.header[args.col].dataCode;
         let code = null;
@@ -153,7 +222,35 @@ let materialOjb = {
         }
         me.refreshSheet();
     },
+    onMaterialEditStarting : function (sender,args) {
+        let me = materialOjb;
+        if(!me.getCurrentBillsID()) args.cancel = true; //如果没选中清单则不能编辑
+    },
 
+    onMaterialValueChange:function(sender,args){
+        let me = materialOjb;
+        let ID = null;
+        if(args.row < me.materialList.length){
+            ID = me.materialList[args.row].ID;
+        }
+        if(me.validateMaterial(args.newValue)){
+            let data = me.getMaterialUpdateData(args.newValue,ID);
+            if(data){
+                me.saveMaterial([data]);
+                return
+            }
+        }
+        me.showMaterialList();
+
+    },
+    validateMaterial:function (value) {
+        value = value.toString().replace(/[\s\r\n]/g, "");//去除空格换行等字符;
+        if(_.find(this.materialList,{code:value})){
+            alert("人材机:"+value+" 已存在");
+            return false;
+        }
+        return true;
+    },
     validateBills:function (field,value) {
         if(field == 'code'){
             value = value.toString().replace(/[\s\r\n]/g, "");//去除空格换行等字符;
@@ -168,10 +265,40 @@ let materialOjb = {
         }
         return true;
     },
+    getCurrentBillsID:function(){
+      let sel = this.billsSheet.getSelections()[0];
+      if(sel.row < this.billsList.length){
+           return this.billsList[sel.row].ID;
+      }
+      return null;
+    },
+    getMaterialUpdateData:function(code,ID,isDelete){
+        if(isDelete == true){
+            return {type:'delete', ID:ID}
+        }
+        code = code.toString().replace(/[\s\r\n]/g, "");//去除空格换行等字符;
+        if((!isDef(ID)||ID=='')&& code != null){//新增
+            let billsItemID = this.getCurrentBillsID();
+            return {
+                type:'add',
+                code:code,
+                billsItemID:billsItemID,
+                libID:$('#libID').val(),
+                gljLibID:parseInt($('#gljLibID').val())
+            }
+        }else { //替换材料
+            return {
+                type:'update',
+                ID:ID,
+                code:code,
+                gljLibID:parseInt($('#gljLibID').val())
+            }
+        }
+    },
     getUpdateData:function (field,newValue,code) {
         if(field == 'code'){
             newValue = newValue.toString().replace(/[\s\r\n]/g, "");//去除空格换行等字符;
-            if(!isDef(code) || code ==''&&newValue!=null){//说明是新增
+            if((!isDef(code) || code =='')&&newValue!=null){//说明是新增
                 return {
                     type:'add',
                     code:newValue,
@@ -198,6 +325,19 @@ let materialOjb = {
             }
         }
     },
+    saveMaterial:async function(datas){
+        try {
+            let result = await ajaxPost("/materialReplace/saveMaterial",datas);
+            let missCodes = [];
+            for(let r of result){
+                if(r.missCodes && r.missCodes.length >0) missCodes =missCodes.concat(r.missCodes);
+            }
+            if(missCodes.length > 0) alert(`没有找到人材机:${missCodes.join("、")}`);
+        }catch (err){
+            console.log(err);
+        }
+        this.showMaterialList();
+    },
     saveBills:async function (datas) {
         try {
             let result = await ajaxPost("/materialReplace/saveBills",datas);
@@ -205,30 +345,87 @@ let materialOjb = {
             for(let r of result){
                 if(r.missCodes && r.missCodes.length >0) missCodes =missCodes.concat(r.missCodes);
                 if(r.type == 'add'){
-                    this.billsList = this.billsList.concat(r.list);
+                    this.allBills = this.allBills.concat(r.list);
                 }if(r.type == 'update'){
                     for(let l of r.list){
-                        this.updateCache(l.code,l.updateData)
+                       this.updateBillsCache(l.code,l.updateData);
                     }
                 }if(r.type == 'delete'){
-                     _.remove(this.billsList,function (item) {
+                    _.remove(this.allBills,function (item) {
                         return _.includes(r.list,item.ID)
                     })
                 }
             }
             if(missCodes.length > 0) alert(`没有找到清单:${missCodes.join("、")}`);
-            this.refreshSheet();
         }catch (err){
             console.log(err);
         }
+        this.refreshSheet();
+    },
+    saveDatas:async function (datas,type ='bills') {
+        try {
+            let currentList = type =='bills'?this.billsList:this.materialList;
+            let url = type =='bills'?"/materialReplace/saveBills":"/materialReplace/saveMaterial";
+            let text = type =='bills'?"清单":"人材机";
+            let result = await ajaxPost(url,datas);
+            let missCodes = [];
+            for(let r of result){
+                if(r.missCodes && r.missCodes.length >0) missCodes =missCodes.concat(r.missCodes);
+                if(r.type == 'add'){
+                    currentList = currentList.concat(r.list);
+                }if(r.type == 'update'){
+                    for(let l of r.list){
+                        type =='bills'?this.updateBillsCache(l.code,l.updateData):this.updateMaterialCache(l.ID,l.updateData);
+                    }
+                }if(r.type == 'delete'){
+                    _.remove(currentList,function (item) {
+                        return _.includes(r.list,item.ID)
+                    })
+                }
+            }
+            if(missCodes.length > 0) alert(`没有找到${text}:${missCodes.join("、")}`);
+        }catch (err){
+            console.log(err);
+        }
+
     },
-    updateCache:function (code,updateData) {
-        let bill = _.find(this.billsList,{'code':code});
+    getMaterialByBillsID:async function(billsItemID){
+        try {
+            let result = await ajaxPost("/materialReplace/findMaterial",{billsItemID:billsItemID});
+            return result;
+        }catch (err){
+            console.log(err);
+            return [];
+        }
+    },
+    updateMaterialCache:function (ID,updateData) {
+        this.updateCache(this.materialList,{'ID':ID},updateData)
+    },
+
+    updateBillsCache:function (code,updateData) {
+         this.updateCache(this.allBills,{'code':code},updateData)
+    },
+
+    updateCache:function (list,condition,updateData) {
+        let item = _.find(list,condition);
         for(let key in updateData){
-            bill[key] = updateData[key]
+            item[key] = updateData[key]
         }
     }
 };
+let last = 0;
+
+$(document).ready(function () {
+    $("#keyword").on('input propertychange', function(event) {
+        last = event.timeStamp;//利用event的timeStamp来标记时间,这样每次事件都会修改last的值,注意last必需为全局变量
+        setTimeout(function () {    //设时延迟0.5s执行
+            if (last - event.timeStamp == 0) { //如果时间差为0(也就是你停止输入0.5s之内都没有其它的keyup事件发生)则做你想要做的事
+               materialOjb.refreshSheet();
+            }
+
+        }, 500);
+    })
+});
 
 function isDef(obj) {
     return obj!==undefined && obj!==null;

+ 36 - 0
web/maintain/project_feature_lib/html/edit.html

@@ -0,0 +1,36 @@
+<nav class="navbar navbar-toggleable-lg justify-content-between navbar-light p-0 second_header">
+    <ul class="nav nav-tabs" role="tablist">
+        <li class="nav-item">
+            <a class="nav-link active px-3" href="javascript: void(0);">工程特征</a>
+        </li>
+    </ul>
+</nav>
+
+<div class="main">
+    <div class="content" >
+        <div class="container-fluid" >
+        <div class=" col-lg-12 p-0">
+            <nav class="navbar sticky-top navbar-toggleable-md navbar-light bg-faded tools-bar">
+                <div class="collapse navbar-collapse" id="navbarNav">
+                    <div class="tools-btn btn-group align-top">
+                        <a href="javascript:void(0)" class="btn btn-sm" id="format"><i class="fa fa-list-alt" aria-hidden="true"></i> 校验格式</a>
+                        <a href="javascript:void(0)" class="btn btn-sm" id="save"><i class="fa fa-floppy-o" aria-hidden="true"></i> 保存</a>
+                    </div>
+                </div>
+            </nav>
+            <textarea class="form-control" id="featureList" rows="38"></textarea>
+        </div>
+        </div>
+        <input type="hidden" id="libID" value="<%= libID %>">
+    </div>
+</div>
+
+
+
+<script type="text/javascript">
+    //自适应高度
+    let featureList = '<%- featureList %>';
+
+</script>
+<script type="text/javascript" src="/lib/json/json2.js"></script>
+<script type="text/javascript" src="/web/maintain/project_feature_lib/js/project_feature_edit.js"></script>

+ 108 - 0
web/maintain/project_feature_lib/html/main.html

@@ -0,0 +1,108 @@
+<div class="main">
+    <div class="content">
+        <div class="container-fluid">
+            <div class="row">
+                <div class="col-md-5">
+                    <div class="warp-p2 mt-3">
+                        <table class="table table-hover table-bordered">
+                            <thead><tr><th >库名称</th><th width="160">添加时间</th><th width="120">操作</th></tr></thead>
+                            <tbody id="showArea">
+                            <% for(let lib of featureLibs){ %>
+                            <tr class="libTr">
+                                <td id="<%= lib.ID%>"><a href="/projectFeature/edit/<%= lib.ID%>"><%= lib.name%></a></td>
+                                <td><%= moment(lib.createDate).format('YYYY-MM-DD')%></td>
+                                <td>
+                                    <a style="color: #0275d8" onclick='getFeatureLib("<%= lib.ID%>")' title="编辑"><i class="fa fa-pencil-square-o"></i></a>
+                                    <a style="color: #0275d8" onclick='showDeleteModal("<%= lib.ID%>")' class="text-danger" title="删除"><i class="fa fa-remove"></i></a>
+                                </td>
+                            </tr>
+                            <% } %>
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!--弹出添加-->
+<div class="modal fade" id="add" data-backdrop="static" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">添加工程特征换库</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <form id="addLibForm" method="post" action="/projectFeature/addLib" enctype="application/x-www-form-urlencoded21">
+                    <div class="form-group">
+                        <label>库名称</label>
+                        <input id="name" name="name" class="form-control" placeholder="请输入特征库名称" type="text">
+                        <small class="form-text text-danger" id="nameError" style="display: none">请输入特征库名称。</small>
+                    </div>
+                    <input type="hidden" name = "userAccount" value="<%= userAccount%>">
+                </form>
+            </div>
+            <div class="modal-footer">
+                <button id="addLibs"  class="btn btn-primary">新建</button>
+                <button type="button" id="cancelBtn" class="btn btn-secondary" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!--弹出编辑-->
+<div class="modal fade" id="edit" data-backdrop="static" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">工程特征库库</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <form>
+                    <div class="form-group">
+                        <label>工程特征库名称</label>
+                        <input id="renameText" class="form-control" placeholder="输入名称" type="text" value="">
+                        <small class="form-text text-danger" id="renameError" style="display: none">请输入名称。</small>
+                        <input id="libID" type="hidden">
+                    </div>
+                </form>
+            </div>
+            <div class="modal-footer">
+                <a id="rename" href="javascript: void(0);" class="btn btn-primary" >确定</a>
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!--弹出删除-->
+<div class="modal fade" id="del" data-backdrop="static" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">删除确认</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <h5 class="text-danger">删除后无法恢复,确认是否删除?</h5>
+                <input type="hidden" id="libID_del">
+                <input type="hidden" id="delCount">
+            </div>
+            <div class="modal-footer">
+                <a id="delete" href="javascript:void(0);" class="btn btn-danger" >确认</a>
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<script type="text/javascript" src="/web/maintain/project_feature_lib/js/project_feature.js"></script>

+ 76 - 0
web/maintain/project_feature_lib/js/project_feature.js

@@ -0,0 +1,76 @@
+/**
+ * Created by zhang on 2018/9/3.
+ */
+$(document).ready(function() {
+    // 保存按钮
+    $("#addLibs").click(async function() {
+        let name = $('#name').val();
+        if(name==''){
+            $("#nameError").show();
+            return;
+        }else {
+            $("#addLibs").attr("disabled",true);//防止重复提交
+            $("#addLibForm").submit();
+        }
+    });
+
+    $("#rename").click(async function() {
+        let libID = $("#libID").val();
+        let name = $('#renameText').val();
+        if(libID!=''){
+            if(name ==''){
+                $("#renameError").show();
+                return;
+            }else {
+                try {
+                    let newFeature = await ajaxPost("/projectFeature/saveLib",{query:{ID:libID},data:{name:name}});
+                    $("#"+libID).children("a").text(newFeature.name);
+                    $("#edit").modal('hide');
+                }catch(err) {
+                    console.log(err);
+                }
+            }
+        }
+    });
+
+    $("#delete").click(async function() {
+        let libID = $("#libID_del").val();
+        let delCount = parseInt($("#delCount").val());
+        delCount = delCount+1;
+        $("#delCount").val(delCount);
+        if(delCount == 3){
+            if(libID!=""){
+                try {
+                    let result = await ajaxPost("/projectFeature/deleteLibByID",{ID:libID});
+                    if(result.ok){
+                        $("#"+libID).parent(".libTr").remove();
+                    }
+                    $("#del").modal('hide');
+                }catch (err){
+                    console.log(err);
+                }
+            }
+        }
+    });
+});
+
+async function getFeatureLib (ID) {
+    try {
+        let lib = await ajaxPost("/projectFeature/findLib",{ID:ID});
+        if(lib){
+            $("#renameText").val(lib.name);
+            $("#libID").val(ID);
+            $("#edit").modal({show:true});
+        }else {
+            alert("没有找到材料库");
+        }
+    }catch (err){
+        console.log(err);
+    }
+}
+
+function showDeleteModal(ID){
+    $("#libID_del").val(ID);
+    $("#delCount").val(0);
+    $("#del").modal({show:true});
+}

+ 37 - 0
web/maintain/project_feature_lib/js/project_feature_edit.js

@@ -0,0 +1,37 @@
+/**
+ * Created by zhang on 2018/9/3.
+ */
+featureObj = {
+
+};
+
+$(document).ready(function () {
+   $("#featureList").val(JSON.stringify(JSON.parse(featureList),null,4));
+
+   $("#format").click( function() {
+       try {
+           let jsonText =  $("#featureList").val();
+           $("#featureList").val(JSON.stringify(JSON.parse(jsonText),null,4));
+       }catch (err){
+           console.log(err);
+           alert("输入的JSON格式有误,请重新输入!");
+       }
+
+   })
+    $("#save").click(async function() {
+        try {
+            let libID = $("#libID").val();
+            let jsonText =  $("#featureList").val();
+            let newFeature = await ajaxPost("/projectFeature/saveLib",{query:{ID:libID},data:{feature:JSON.parse(jsonText)}});
+            console.log(newFeature);
+        }catch (err){
+            console.log(err);
+            alert("保存失败,请查看输入数据");
+        }
+
+    })
+
+
+
+});
+//featureObj.initSpread();

+ 11 - 10
web/maintain/report/js/rpt_tpl_cfg_helper.js

@@ -138,20 +138,21 @@ let rpt_tpl_cfg_helper = {
             $("#element_area_1")[0].style.display = "none";
             // $("#element_area_2")[0].style.display = "none";
             $("#element_pre_suff")[0].style.display = "none";
-            if (treeNode[JV.PROP_NAME] === JV.NODE_FLOW_COLUMN) {
+            if (treeNode[JV.PROP_NAME] === JV.NODE_FLOW_COLUMN || treeNode[JV.PROP_NAME] === JV.NODE_FLOW_CONTENT) {
                 //一些可视化操作
                 $("#element_visual_div")[0].style.display = "";
-                let nextNode = treeNode.getNextNode();
-                while (nextNode !== null && nextNode !== undefined) {
-                    if (nextNode[JV.PROP_NAME] === JV.NODE_FLOW_CONTENT) {
-                        break;
-                    } else {
-                        nextNode = nextNode.getNextNode();
-                    }
+                let columnParentNode = null;
+                let contentParentNode = null;
+                if (treeNode[JV.PROP_NAME] === JV.NODE_FLOW_CONTENT) {
+                    contentParentNode = treeNode;
+                    columnParentNode = treeNode.getPreNode();
+                } else {
+                    columnParentNode = treeNode;
+                    contentParentNode = treeNode.getNextNode();
                 }
                 let rptTpl = (zTreeOprObj.currentNode)?zTreeOprObj.currentNode.rptTpl:null;
-                fieldLocationOprObj.iniSpreadJs(treeNode, treeNode.getNextNode());
-                fieldLocationOprObj.setupColumn(rptTpl, treeNode, treeNode.getNextNode());
+                fieldLocationOprObj.iniSpreadJs(columnParentNode, contentParentNode);
+                fieldLocationOprObj.setupColumn(rptTpl, columnParentNode, contentParentNode);
             } else {
                 $("#element_visual_div")[0].style.display = "none";
             }

+ 2 - 4
web/maintain/report/js/rpt_tpl_field_location.js

@@ -28,7 +28,7 @@ let fieldLocationOprObj = {
         me.columnParentNode = columnParentNode;
         me.contentParentNode = contentParentNode;
     },
-    restore: function () {
+    restoreColumn: function () {
         let me = this;
         let rptTpl = (zTreeOprObj.currentNode)?zTreeOprObj.currentNode.rptTpl:null;
         me.setupColumn(rptTpl, me.columnParentNode, me.contentParentNode);
@@ -170,11 +170,9 @@ let fieldLocationOprObj = {
             sheet = me.columnWorkBook.getActiveSheet();
         sheet.addRows(sheet.getRowCount() - 1, 1);
         let rc = sheet.getRowCount();
-        // let node = me.columnParentNode.items[0]
         for (let cc = 0; cc < sheet.getColumnCount(); cc++) {
             me.private_setupCellDft(sheet.getCell(rc - 2, cc));
         }
-        // columnParentNode.items
     },
     deleteRow: function () {
         let me = fieldLocationOprObj,
@@ -617,7 +615,7 @@ let fieldLocationOprObj = {
         return rst;
     },
 
-    applyBack: function () {
+    applyColumnBack: function () {
         let me = this;
         let rptTpl = (zTreeOprObj.currentNode)?zTreeOprObj.currentNode.rptTpl:null;
         if (rptTpl && me.columnParentNode && me.contentParentNode && confirm(`请确认提交应用!`)) {

+ 2 - 15
web/maintain/report/rpt_tpl_detail_field_location.html

@@ -7,19 +7,6 @@
                         <ul id="tpl_data_info_reversed" class="ztree"></ul>
                     </div>
                 </div>
-                <!--
-                    <div class="tab-bar">
-                        <div class="form-group">
-                            <div class="ztree-warp" style="height: 130px;">
-                                <ul id="tpl_discrete_fields_params_reversed" class="ztree"></ul>
-                            </div>
-                            <br/>
-                            <div class="ztree-warp" style="height: 240px;">
-                                <ul id="tpl_data_selected_field_map_reversed" class="ztree"></ul>
-                            </div>
-                        </div>
-                    </div>
-                -->
                 <div class="form-group col-md-4" style="max-height: 410px;">
                     <div class="ztree-warp" style="height: 300px;">
                         <ul id="tpl_data_selected_field_map_reversed" class="ztree"></ul>
@@ -180,8 +167,8 @@
                 <div class="input-group col-12">
                     <div style="width:97%; height: 300px;">
                         <h5>表栏</h5>
-                        <button class="btn btn-primary btn-sm" onclick="fieldLocationOprObj.applyBack()">应用</button>
-                        <button class="btn btn-primary btn-sm" onclick="fieldLocationOprObj.restore()">恢复</button>
+                        <button class="btn btn-primary btn-sm" onclick="fieldLocationOprObj.applyColumnBack()">应用</button>
+                        <button class="btn btn-primary btn-sm" onclick="fieldLocationOprObj.restoreColumn()">恢复</button>
                         <button class="btn btn-primary btn-sm" onclick="fieldLocationOprObj.addCol((zTreeOprObj.currentNode)?zTreeOprObj.currentNode.rptTpl:null)">新增列</button>
                         <button class="btn btn-primary btn-sm" onclick="fieldLocationOprObj.deleteCol()">删除列</button>
                         <button class="btn btn-primary btn-sm" onclick="fieldLocationOprObj.addRow()">新增行</button>

+ 10 - 0
web/users/css/custom.css

@@ -6,6 +6,16 @@
 .engineeringInput {
   -moz-appearance: textfield;
 }
+
+.none_number_step::-webkit-outer-spin-button,
+.none_number_step::-webkit-inner-spin-button {
+  -webkit-appearance: none;
+}
+.none_number_step {
+  -moz-appearance: textfield;
+}
+
+
 .btn-link:focus, .btn-link:hover{
   text-decoration: none
 }

+ 28 - 11
web/users/js/compilation.js

@@ -60,7 +60,7 @@ $(document).ready(function() {
                     return false;
                 }
 
-                let removeHtml = '<a class="pull-right text-danger remove-lib" data-model="bill" ' +
+                let removeHtml = '<a class="pull-right text-danger remove-lib" data-model="'+model+'" ' +
                     'title="移除"><span class="glyphicon glyphicon-remove"></span></a>';
                 let tmpHtml = '<p class="form-control-static">' + removeHtml + addLib.name +
                     '<input type="hidden" data-id="'+ addLib.id +'" name=\'' + model + '_lib\' value=\'' + JSON.stringify(addLib) + '\'>' + '</p>';
@@ -194,6 +194,10 @@ $(document).ready(function() {
                 $("#program-area").show();
                 $("#add-compilation-title").text('添加计算程序');
                 break;
+            case 'feature':
+                $("#feature-area").show();
+                $("#add-compilation-title").text('添加工程特征');
+                break;
         }
         $("#addcompilation").modal('show');
     });
@@ -210,7 +214,7 @@ $(document).ready(function() {
     });
 
     // 移除操作
-    $(".bill-list, .ration-list, .glj-list, .fee-list, .artificial-list, .program-list, .billsGuidance-list").on("click", ".remove-lib", function() {
+    $(".bill-list, .ration-list, .glj-list, .fee-list, .artificial-list, .program-list, .billsGuidance-list,.feature-list").on("click", ".remove-lib", function() {
         $(this).parent().remove();
     });
 
@@ -356,6 +360,7 @@ function initCompilation() {
     let billsGuidanceData = billsGuidanceList === undefined ? [] : JSON.parse(billsGuidanceList);
     let billTemplateData = billTemplateList == undefined ? [] : JSON.parse(billTemplateList);
     let mainTreeColData= mainTreeColList == undefined ? [] : JSON.parse(mainTreeColList);
+    let featureData = featureList == undefined?[]: JSON.parse(featureList);
     /*mainTreeCol = mainTreeCol !== '' ? mainTreeCol.replace(/\n/g, '\\n') : mainTreeCol;
     billsTemplateData = billsTemplateData.replace(/\n/g, '\\n');
 
@@ -446,6 +451,14 @@ function initCompilation() {
         html += tmpHtml;
     }
     $("select[name='fee_lib']").children("option").first().after(html);
+
+    //工程特征库
+    html = '';
+    for(let tmp of featureData){
+        let tmpHtml = '<option value="' + tmp.ID + '">' + tmp.name + '</option>';
+        html += tmpHtml;
+    }
+    $("select[name='feature_lib']").children("option").first().after(html);
 }
 
 /**
@@ -459,10 +472,12 @@ function getAndValidData(model) {
     let standardBill = $("select[name='standard_bill']").children("option:selected").val();
     let rationLib = $("select[name='ration_lib']").children("option:selected").val();
     let gljLib = $("select[name='glj_lib']").children("option:selected").val();
-    let feeLib = $("select[name='fee_lib']").children("option:selected").val();
+   // let feeLib = $("select[name='fee_lib']").children("option:selected").val();
     let artificialLib = $("select[name='artificial_lib']").children("option:selected").val();
     let programLib = $("select[name='program_lib']").children("option:selected").val();
     let billsGuidanceLib = $("select[name='billsGuidance_lib']").children("option:selected").val();
+    let featureLib = $("select[name='feature_lib']").children("option:selected").val();
+
 
     if (name === '' && model === 'all') {
         throw '编办名字不能为空';
@@ -480,12 +495,8 @@ function getAndValidData(model) {
         throw '请选择人材机库';
     }
 
-    if (model === 'fee' && (feeLib === '' || feeLib === undefined)) {
-        throw '请选择费率标准';
-    }
-
     if (model === 'artificial' && (artificialLib === '' || artificialLib === undefined)) {
-        throw '请选择费率库';
+        throw '请选择人工系数库';
     }
 
     if (model === 'program' && (programLib === '' || programLib === undefined)) {
@@ -499,10 +510,12 @@ function getAndValidData(model) {
     let standardBillString = $("select[name='standard_bill']").children("option:selected").text();
     let rationLibString = $("select[name='ration_lib']").children("option:selected").text();
     let gljLibString = $("select[name='glj_lib']").children("option:selected").text();
-    let feeLibString = $("select[name='fee_lib']").children("option:selected").text();
+  //  let feeLibString = $("select[name='fee_lib']").children("option:selected").text();
     let artificialString = $("select[name='artificial_lib']").children("option:selected").text();
     let programString = $("select[name='program_lib']").children("option:selected").text();
     let billsGuidanceString = $("select[name='billsGuidance_lib']").children("option:selected").text();
+    let featrueString = $("select[name='feature_lib']").children("option:selected").text();
+
 
     let result = {
         name: name,
@@ -518,10 +531,10 @@ function getAndValidData(model) {
             id: gljLib,
             name: gljLibString
         },
-        fee: {
+      /*  fee: {
             id: feeLib,
             name: feeLibString
-        },
+        },*/
         artificial: {
             id: artificialLib,
             name: artificialString
@@ -533,6 +546,10 @@ function getAndValidData(model) {
         billsGuidance: {
             id: billsGuidanceLib,
             name: billsGuidanceString
+        },
+        feature:{
+            id:featureLib,
+            name:featrueString
         }
     };
     return result;

+ 20 - 1
web/users/views/compilation/engineering.html

@@ -11,7 +11,7 @@
     <div class="content-wrap">
         <div class="c-header" style="padding:0">
             <ul class="nav nav-tabs">
-                <li role="presentation" class="active"><a href="javascript:void(0);"><%= libData.name %></a></li>
+                <li role="presentation" class="active"><a href="javascript:void(0);"><%= libData.name %> - <%= libData.feeName %></a></li>
             </ul>
         </div>
         <div class="c-body">
@@ -106,7 +106,25 @@
                                 </div>
                                 <a href="#" class="btn btn-link btn-sm add-compilation" data-model="artificial">添加</a>
                             </div>
+                            <div class="form-group col-md-3">
+                                <label>工程特征</label>
+                                <div class="feature-list">
+                                    <% if (Object.keys(libData).length > 0 && libData.feature_lib && libData.feature_lib.length > 0) { %>
+                                    <% libData.feature_lib.forEach(function (feature, index){ %>
+                                    <p class="form-control-static">
+                                        <a class="pull-right text-danger remove-lib" data-model="feature" title="移除" data-id="<%= feature.id %>">
+                                            <span class="glyphicon glyphicon-remove"></span>
+                                        </a>
+                                        <input type="hidden" name="feature_lib" data-id="<%= feature.id %>" value="<%= JSON.stringify({id: feature.id, name: feature.name}) %>">
+                                        <% if (index === 0) {%><i class="glyphicon glyphicon-flag"></i>&nbsp;<% } %><%= feature.name %>
+                                    </p>
+                                    <% }) %>
+                                    <% } %>
+                                </div>
+                                <a href="#" class="btn btn-link btn-sm add-compilation" data-model="feature">添加</a>
+                            </div>
                     </div>
+
                     <div class="col-md-12">
                         <a data-toggle="modal" data-target="#other_setting" class="btn btn-primary btn-sm " style="margin-right:5px">显示设置</a>
                     </div>
@@ -194,6 +212,7 @@
     let gljCol = '<%- gljCol %>';
     let billTemplateList = '<%- billTemplateList %>';
     let mainTreeColList = '<%- mainTreeColList %>';
+    let featureList = '<%- featureList %>';
     let colSpread = null;
     let colEditSpread = null;
 </script>

+ 5 - 5
web/users/views/compilation/modal.html

@@ -51,16 +51,16 @@
                         </div>
                     </div>
                 </div>
-         <!--       <div class="form-group" id="fee-area">
-                    <label>费率标准</label>
+                <div class="form-group" id="feature-area">
+                    <label>工程特征</label>
                     <div class="row">
                         <div class="col-xs-12">
-                            <select class="form-control" name="fee_lib">
-                                <option value="">请选择费率库</option>
+                            <select class="form-control" name="feature_lib">
+                                <option value="">请选择工程特征库</option>
                             </select>
                         </div>
                     </div>
-                </div>-->
+                </div>
                 <div class="form-group" id="artificial-area">
                     <label>人工系数</label>
                     <div class="row">

+ 7 - 0
web/users/views/tool/index.html

@@ -69,6 +69,13 @@
                     </h2>
                 </div>
             </div>
+            <div class="col-xs-6 mb-30 ">
+                <div class="c-body">
+                    <h2>工程特征库
+                        <a id="projectFeature" href="/projectFeature/main" target="_blank" class="btn btn-primary pull-right">进入</a>
+                    </h2>
+                </div>
+            </div>
         </div>
     </div>
 </div>