Bläddra i källkod

Merge branch 'master' of http://192.168.1.41:3000/SmartCost/YangHuCost

Conflicts:
	modules/reports/controllers/rpt_controller.js
TonyKang 5 år sedan
förälder
incheckning
15281ef470
57 ändrade filer med 6170 tillägg och 3220 borttagningar
  1. 159 136
      config/config.js
  2. 2 3
      config/gulpConfig.js
  3. 86 87
      gulpfile.js
  4. 6 1
      importserver.js
  5. 9 1
      modules/import/controllers/import_controller.js
  6. 1 0
      modules/import/routes/import_route.js
  7. 15 1
      modules/main/controllers/project_controller.js
  8. 24 0
      modules/main/facade/bid_facade.js
  9. 723 597
      modules/main/facade/project_facade.js
  10. 31 0
      modules/main/models/project.js
  11. 1 0
      modules/main/models/project_consts.js
  12. 6 0
      modules/main/routes/main_route.js
  13. 1 0
      modules/main/routes/project_route.js
  14. 13 1
      modules/pm/controllers/pm_controller.js
  15. 48 8
      modules/pm/facade/pm_facade.js
  16. 1 2
      modules/pm/routes/pm_route.js
  17. 2 2
      modules/reports/controllers/rpt_controller.js
  18. 2017 2080
      package-lock.json
  19. 27 3
      public/common_constants.js
  20. 14 1
      public/common_util.js
  21. 3 3
      public/gljUtil.js
  22. 37 0
      public/web/PerfectLoad.js
  23. 30 21
      public/web/gljUtil.js
  24. 3 1
      public/web/id_tree.js
  25. 1 1
      public/web/scMathUtil.js
  26. 295 186
      web/building_saas/css/custom.css
  27. 1 1
      web/building_saas/fee_rates/fee_rate.html
  28. 57 0
      web/building_saas/glj/html/project_glj.html
  29. 1 1
      web/building_saas/js/global.js
  30. 103 0
      web/building_saas/main/html/main.html
  31. 5 0
      web/building_saas/main/js/controllers/block_controller.js
  32. 1 1
      web/building_saas/main/js/main.js
  33. 3 1
      web/building_saas/main/js/models/cache_tree.js
  34. 9 5
      web/building_saas/main/js/models/calc_program.js
  35. 8 5
      web/building_saas/main/js/models/main_consts.js
  36. 49 3
      web/building_saas/main/js/models/project.js
  37. 12 2
      web/building_saas/main/js/models/project_glj.js
  38. 2 17
      web/building_saas/main/js/models/ration.js
  39. 2 1
      web/building_saas/main/js/views/calc_program_view.js
  40. 25 5
      web/building_saas/main/js/views/config_material_view.js
  41. 1 1
      web/building_saas/main/js/views/divide_view.js
  42. 1 1
      web/building_saas/main/js/views/fee_rate_view.js
  43. 2 0
      web/building_saas/main/js/views/glj_col.js
  44. 13 4
      web/building_saas/main/js/views/project_glj_view.js
  45. 3 3
      web/building_saas/main/js/views/project_view.js
  46. 30 23
      web/building_saas/main/js/views/tender_price_view.js
  47. 0 1
      web/building_saas/pm/html/project-management.html
  48. 3 3
      web/building_saas/pm/js/pm_newMain.js
  49. 1 1
      web/building_saas/pm/js/pm_share.js
  50. 67 0
      web/building_saas/standard_interface/config.js
  51. 695 0
      web/building_saas/standard_interface/export/anhui_maanshan.js
  52. 787 0
      web/building_saas/standard_interface/export/base.js
  53. 185 0
      web/building_saas/standard_interface/export/guangdong_zhongshan.js
  54. 89 0
      web/building_saas/standard_interface/export/view.js
  55. 134 0
      web/building_saas/standard_interface/index.js
  56. 79 0
      web/over_write/js/quanguo_2018.js
  57. 247 6
      web/over_write/js/shandong_2016.js

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 159 - 136
config/config.js


+ 2 - 3
config/gulpConfig.js

@@ -97,7 +97,6 @@ module.exports = {
         'public/web/sheet/sheet_common.js',
         'public/web/slideResize.js',
        // 'lib/ztree/*.js',
-        'lib/spreadjs/sheets/gc.spread.sheets.all.11.1.2',
        // 'lib/spreadjs/views/gc.spread.views.dataview.10.0.0.min.js',
        // "lib/spreadjs/views/common/gc.spread.common.10.0.0.min.js",
       //  'lib/spreadjs/views/plugins/gc.spread.views.gridlayout.10.0.0.min.js',
@@ -213,7 +212,7 @@ module.exports = {
         'lib/jquery-contextmenu/jquery.contextMenu.css',
     ],
     compleRation_ration_jspaths:[
-        '/public/web/uuid.js',
+        'public/web/uuid.js',
         'lib/jquery-contextmenu/jquery.contextMenu.min.js',
         'lib/jquery-contextmenu/jquery.ui.position.js',
         'lib/ztree/jquery.ztree.core.js',
@@ -240,7 +239,7 @@ module.exports = {
         'web/building_saas/complementary_ration_lib/js/ration_glj.js',
         'web/building_saas/complementary_ration_lib/js/ration_coe.js',
         'web/building_saas/complementary_ration_lib/js/ration_assist.js',
-        'web/building_saas/complementary_ration_lib/js/ration_installation.js.js',
+        'web/building_saas/complementary_ration_lib/js/ration_installation.js',
         'public/web/slideResize.js',
         'web/building_saas/complementary_ration_lib/js/coe.js',
         'web/building_saas/complementary_ration_lib/js/init.js'

+ 86 - 87
gulpfile.js

@@ -96,7 +96,7 @@ let compleGljOptions = {
     csspaths: compleGlj_csspaths,
     concatName: 'compleGlj.all.min',
     srcHtml: 'web/src/html/complementary_glj_lib/tools-gongliaoji.html',
-    htmlDest: 'web/building_saas/complementary_glj_lib/html/tools-gongliaoji.html',
+    htmlDest: 'web/building_saas/complementary_glj_lib/html',
     htmlName: 'tools-gongliaoji.html',
     injectList: [
         'web/dest/scripts/compleGlj.all.min' + version + '.js',
@@ -112,7 +112,7 @@ let compleRation_rationOptions = {
     csspaths: compleRation_ration_csspaths,
     concatName: 'compleRation_ration.all.min',
     srcHtml: 'web/src/html/complementary_ration_lib/dinge.html',
-    htmlDest: 'web/building_saas/complementary_ration_lib/html/dinge.html',
+    htmlDest: 'web/building_saas/complementary_ration_lib/html',
     htmlName: 'dinge.html',
     injectList: [
         'web/dest/scripts/compleRation_ration.all.min' + version + '.js',
@@ -129,7 +129,7 @@ let compleRation_gljOptions = {
     csspaths: compleRation_glj_csspaths,
     concatName: 'compleRation_glj.all.min',
     srcHtml: 'web/src/html/complementary_ration_lib/gongliao.html',
-    htmlDest: 'web/building_saas/complementary_ration_lib/html/gongliao.html',
+    htmlDest: 'web/building_saas/complementary_ration_lib/html',
     htmlName: 'gongliao.html',
     injectList: [
         'web/dest/scripts/compleRation_glj.all.min' + version + '.js',
@@ -146,7 +146,7 @@ let compleRation_coeOptions = {
     csspaths: compleRation_coe_csspaths,
     concatName: 'compleRation_coe.all.min',
     srcHtml: 'web/src/html/complementary_ration_lib/fuzhu.html',
-    htmlDest: 'web/building_saas/complementary_ration_lib/html/fuzhu.html',
+    htmlDest: 'web/building_saas/complementary_ration_lib/html',
     htmlName: 'fuzhu.html',
     injectList: [
         'web/dest/scripts/compleRation_coe.all.min' + version + '.js',
@@ -163,7 +163,7 @@ let compleRation_instOptions = {
     csspaths: compleRation_inst_csspaths,
     concatName: 'compleRation_inst.all.min',
     srcHtml: 'web/src/html/complementary_ration_lib/anzhuang.html',
-    htmlDest: 'web/building_saas/complementary_ration_lib/html/anzhuang.html',
+    htmlDest: 'web/building_saas/complementary_ration_lib/html',
     htmlName: 'anzhuang.html',
     injectList: [
         'web/dest/scripts/compleRation_inst.all.min' + version + '.js',
@@ -183,7 +183,7 @@ let unitPriceOptions={
   injectList:['web/dest/scripts/unitPrice.all.min.'+version+'.js']
 }
 
-function minify(options) {
+function minify(options,done) {
     if(options.jspaths){
         return gulp.src(options.jspaths)
             .pipe($.plumber())
@@ -191,7 +191,7 @@ function minify(options) {
             .pipe($.concat(options.concatName+"."+version+".js"))
             .pipe(gulp.dest(scriptsDest));
     }
-    return null;
+    return done();
 }
 
 function css(options,done) {
@@ -207,8 +207,7 @@ function css(options,done) {
 
 function inject(options) {
     var target = gulp.src(options.htmlDest+'/'+options.htmlName);
-    var sources = gulp.src(options.injectList, {read: false});
-
+    var sources = gulp.src(options.injectList, {read: false,allowEmpty: true});
     return target.pipe($.plumber())
         .pipe($.inject(sources))
         .pipe(gulp.dest(options.htmlDest));
@@ -229,42 +228,42 @@ gulp.task('css',function () {
     return css(commonOptions);
 })
 
-gulp.task('common', gulp.parallel('minify','css'));
+gulp.task('common', gulp.series('minify','css'));
 
-gulp.task('login_minify',gulp.series('common'), function (){
-    return minify(loginOptions);
+gulp.task('login_minify', function (){
+  return minify(loginOptions);
 });
 
 gulp.task('login_css',function (done) {
     return css(loginOptions,done);
 })
 
-gulp.task('login_inject',gulp.parallel('login_minify','login_css'),function () {
-    return inject(loginOptions);
-})
+gulp.task('login_inject',gulp.series('login_minify','login_css',function () {
+  return inject(loginOptions);
+}))
 
-gulp.task('login',gulp.series('login_inject'), function (){
-    return htmlmin(loginOptions);
-});
+gulp.task('login',gulp.series('login_inject', function (){
+  return htmlmin(loginOptions);
+}));
 
 
-gulp.task('header_minify',gulp.series('common'), function (){
-    return minify(headerOptions);
+gulp.task('header_minify',function (done){
+  return minify(headerOptions,done);
 });
 
 gulp.task('header_css',function (done) {
     return css(headerOptions,done);
 })
 
-gulp.task('header_inject',gulp.parallel('header_minify','header_css'),function () {
-    return inject(headerOptions);
-})
+gulp.task('header_inject',gulp.series('header_minify','header_css',function () {
+  return inject(headerOptions);
+}))
 
-gulp.task('header',gulp.series('header_inject'), function (){
-    return htmlmin(headerOptions);
-});
+gulp.task('header',gulp.series('header_inject', function (){
+  return htmlmin(headerOptions);
+}));
 
-gulp.task('pm_minify',gulp.series('common'), function (){
+gulp.task('pm_minify',function (){
     return minify(pmOptions);
 });
 
@@ -272,120 +271,120 @@ gulp.task('pm_css',function (done) {
     return css(pmOptions,done);
 })
 
-gulp.task('pm_inject',gulp.parallel('pm_minify','pm_css'),function () {
-    return inject(pmOptions);
-})
+gulp.task('pm_inject',gulp.series('pm_minify','pm_css',function () {
+  return inject(pmOptions);
+}))
 
-gulp.task('pm',gulp.series('pm_inject'), function (){
-    return htmlmin(pmOptions);
-});
+gulp.task('pm',gulp.series('pm_inject', function (){
+  return htmlmin(pmOptions);
+}));
 
-gulp.task('main_minify',gulp.series('common'), function (){
-    return minify(mainOptions);
+gulp.task('main_minify',function (){
+  return minify(mainOptions);
 });
 
 gulp.task('main_css',function (done) {
     return css(mainOptions,done);
 })
 
-gulp.task('main_inject',gulp.series('main_minify'),function () {//, ['main_minify','main_css'  ] main css 打包到一起会出现样式冲突问题, 现改成不打包
-    return inject(mainOptions);
-})
+gulp.task('main_inject',gulp.series('main_minify',function () {//, ['main_minify','main_css'  ] main css 打包到一起会出现样式冲突问题, 现改成不打包
+return inject(mainOptions);
+}))
 
-gulp.task('main',gulp.series('main_inject'), function (){
-    return htmlmin(mainOptions);
-});
+gulp.task('main',gulp.series('main_inject', function (){
+  return htmlmin(mainOptions);
+}));
 
-gulp.task('compleGlj_minify', gulp.series('common'), function () {
-    return minify(compleGljOptions);
+gulp.task('compleGlj_minify', function () {
+  return minify(compleGljOptions);
 });
 
 gulp.task('compleGlj_css', function (done) {
     return css(compleGljOptions,done);
 });
 
-gulp.task('compleGlj_inject', gulp.parallel('compleGlj_minify', 'compleGlj_css'), function () {
-    return inject(compleGljOptions);
-});
+gulp.task('compleGlj_inject', gulp.series('compleGlj_minify', 'compleGlj_css', function () {
+  return inject(compleGljOptions);
+}));
 
-gulp.task('compleGlj', gulp.series('compleGlj_inject'), function () {
-    return htmlmin(compleGljOptions);
-});
+gulp.task('compleGlj', gulp.series('compleGlj_inject', function () {
+  return htmlmin(compleGljOptions);
+}));
 
-gulp.task('compleRation_ration_minify', gulp.series('common'), function () {
-    return minify(compleRation_rationOptions);
+gulp.task('compleRation_ration_minify', function () {
+  return minify(compleRation_rationOptions);
 });
 
 gulp.task('compleRation_ration_css', function (done) {
     return css(compleRation_rationOptions,done);
 });
 
-gulp.task('compleRation_ration_inject', gulp.parallel('compleRation_ration_minify', 'compleRation_ration_css'), function () {
-    return inject(compleRation_rationOptions);
-});
+gulp.task('compleRation_ration_inject', gulp.series('compleRation_ration_minify', 'compleRation_ration_css', function () {
+  return inject(compleRation_rationOptions);
+}));
 
-gulp.task('compleRation_ration', gulp.parallel('compleRation_ration_inject'), function () {
-    return htmlmin(compleRation_rationOptions);
-});
+gulp.task('compleRation_ration', gulp.series('compleRation_ration_inject', function () {
+  return htmlmin(compleRation_rationOptions);
+}));
 
-gulp.task('compleRation_glj_minify', gulp.series('common'), function () {
-    return minify(compleRation_gljOptions);
+gulp.task('compleRation_glj_minify',  function () {
+  return minify(compleRation_gljOptions);
 });
 
 gulp.task('compleRation_glj_css', function (done) {
     return css(compleRation_gljOptions,done);
 });
 
-gulp.task('compleRation_glj_inject', gulp.parallel('compleRation_glj_minify', 'compleRation_glj_css'), function () {
-    return inject(compleRation_gljOptions);
-});
+gulp.task('compleRation_glj_inject', gulp.series('compleRation_glj_minify', 'compleRation_glj_css', function () {
+  return inject(compleRation_gljOptions);
+}));
 
-gulp.task('compleRation_glj', gulp.series('compleRation_glj_inject'), function () {
-    return htmlmin(compleRation_gljOptions);
-});
+gulp.task('compleRation_glj', gulp.series('compleRation_glj_inject', function () {
+  return htmlmin(compleRation_gljOptions);
+}));
 
-gulp.task('compleRation_coe_minify', gulp.series('common'), function () {
-    return minify(compleRation_coeOptions);
+gulp.task('compleRation_coe_minify',  function () {
+  return minify(compleRation_coeOptions);
 });
 
 gulp.task('compleRation_coe_css', function (done) {
     return css(compleRation_coeOptions,done);
 });
 
-gulp.task('compleRation_coe_inject', gulp.parallel('compleRation_coe_minify', 'compleRation_coe_css'), function () {
-    return inject(compleRation_coeOptions);
-});
+gulp.task('compleRation_coe_inject', gulp.series('compleRation_coe_minify', 'compleRation_coe_css', function () {
+  return inject(compleRation_coeOptions);
+}));
 
-gulp.task('compleRation_coe', gulp.series('compleRation_coe_inject'), function () {
-    return htmlmin(compleRation_coeOptions);
-});
+gulp.task('compleRation_coe', gulp.series('compleRation_coe_inject', function () {
+  return htmlmin(compleRation_coeOptions);
+}));
 
-gulp.task('compleRation_inst_minify', gulp.series('common'), function () {
-    return minify(compleRation_instOptions);
-});
+gulp.task('compleRation_inst_minify', function () {
+  return minify(compleRation_instOptions);
+} );
 
 gulp.task('compleRation_inst_css', function (done) {
     return css(compleRation_instOptions,done);
 });
 
-gulp.task('compleRation_inst_inject', gulp.parallel('compleRation_inst_minify', 'compleRation_inst_css'), function () {
-    return inject(compleRation_instOptions);
-});
+gulp.task('compleRation_inst_inject', gulp.series('compleRation_inst_minify', 'compleRation_inst_css', function () {
+  return inject(compleRation_instOptions);
+}));
 
-gulp.task('compleRation_inst', gulp.series('compleRation_inst_inject'), function () {
-    return htmlmin(compleRation_instOptions);
-});
+gulp.task('compleRation_inst', gulp.series('compleRation_inst_inject', function () {
+  return htmlmin(compleRation_instOptions);
+}));
 
 gulp.task('unitPrice_minify', function (){
   return minify(unitPriceOptions);
 });
 
-gulp.task('unitPrice_inject',gulp.series('unitPrice_minify'),function (){
+gulp.task('unitPrice_inject',gulp.series('unitPrice_minify',function (){
   return inject(unitPriceOptions);
-})
+}))
 
-gulp.task('unit_price',gulp.series('unitPrice_inject'), function (){
+gulp.task('unit_price',gulp.series('unitPrice_inject', function (){
   return htmlmin(unitPriceOptions);
-});
+}));
 
-gulp.task('build',gulp.parallel('header','login','pm','main', 'compleGlj', 'compleRation_ration', 'compleRation_glj', 'compleRation_coe', 'compleRation_inst','unit_price'));
+gulp.task('build',gulp.series('common','header','login','pm','main', 'compleGlj', 'compleRation_ration', 'compleRation_glj', 'compleRation_coe', 'compleRation_inst','unit_price'));

+ 6 - 1
importserver.js

@@ -44,6 +44,9 @@ fileUtils.getGlobbedFiles('./modules/import/routes/*.js').forEach(function(model
     require(path.resolve(modelPath))(app);
 });
 
+const cfgCacheUtil = require("./config/cacheCfg");
+cfgCacheUtil.setupDftCache();
+
 //app.use(express.static(_rootDir+"/web"));
 //app.use(express.static(_rootDir+"/lib"));
 //let rations_Router = require("./modules/rationLibEditor/routes/rationLibEditor_route");
@@ -62,9 +65,11 @@ app.use(function(err, req, res, next) {
 //设置外增的Date对象Format函数
 //备注: 经过测试nodejs 8.9.3版本不支持eval的方式修改prototype,为兼容考虑,把方法调整到stringUtil文件里
 require('./public/stringUtil').setupDateFormat();
-let importPort = config[process.env.NODE_ENV].importPor?config[process.env.NODE_ENV].importPor:2050; 
+let importPort = config[process.env.NODE_ENV].importPort?config[process.env.NODE_ENV].importPort:2050; 
+
 
 app.listen(importPort, function(){
+    console.log(importPort);
     console.log('import server started!');
 });
 

+ 9 - 1
modules/import/controllers/import_controller.js

@@ -5,6 +5,7 @@
  * Created by jimiz on 2017/4/9.
  */
 let logger = require("../../../logs/log_helper").logger;
+const Project = require('../../main/models/project');
 let pm_facade = require('../../pm/facade/pm_facade');
 let controller = {
     importProject:async function (req){
@@ -34,7 +35,14 @@ let controller = {
         let data = JSON.parse(req.body.dataString);
         result.data = await pm_facade.accessToCopyProject(req.body.userID,req.body.compilationID,data);
         return result
-    }
+    },
+    async getDataForInterface (req) {
+        const result = {
+            error: 0
+        };
+        result.data = await Project.getDataSync(req.body.project_id);
+        return result;
+    },
 };
 
 

+ 1 - 0
modules/import/routes/import_route.js

@@ -11,6 +11,7 @@ module.exports = function (app) {
     importRouter.post('/copyProject',importController.action);
     importRouter.post('/copyConstructionProject',importController.action);
     importRouter.post('/prepareInitialData',importController.action);
+    importRouter.post('/getDataForInterface',importController.action);
     importRouter.get('/test',function (req,res) {
         res.json("hello word");
     })

+ 15 - 1
modules/main/controllers/project_controller.js

@@ -4,7 +4,7 @@
 var Project = require('../models/project');
 let logger = require('../../../logs/log_helper').logger;
 let project_facade = require("../facade/project_facade");
-
+const redirectToImportServer = require('../../pm/controllers/pm_controller').redirectToImportServer;
 //统一回调函数
 var callback = function(req, res, err, message, data){
     res.json({error: err, message: message, data: data});
@@ -48,6 +48,20 @@ module.exports = {
             }
         }, isReport, req.session.sessionUser.id);
     },
+    getDataForInterface: async function (req, res) {
+        const data = JSON.parse(req.body.data);
+        let result={
+            error:0
+        };
+        try {
+            result = await redirectToImportServer(data,"getDataForInterface",req);
+        } catch (err) {
+            console.log(err);
+            result.error=1;
+            result.message = err.message;
+        }
+        res.json(result);
+    },
     markUpdateProject:async function (req,res) {
         let result={
             error:0

+ 24 - 0
modules/main/facade/bid_facade.js

@@ -0,0 +1,24 @@
+/**
+ * Created by zhang on 2019/9/11.
+ */
+
+
+module.exports={
+    getData:getData
+};
+
+const mongoose = require('mongoose');
+let bidEvaluationListModel = mongoose.model("bid_evaluation_list");
+let consts = require('../../main/models/project_consts');
+
+
+
+function getData(projectID, callback) {
+    bidEvaluationListModel.find({'projectID': projectID}).lean().exec((err, datas) => {
+        if (err) {
+            callback(1, '', null);
+        } else {
+            callback(0, consts.projectConst.BID_EVALUATION_LIST, datas);
+        }
+    })
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 723 - 597
modules/main/facade/project_facade.js


+ 31 - 0
modules/main/models/project.js

@@ -20,6 +20,7 @@ let installation_facade = require('../facade/installation_facade');
 let pmController = require('../../pm/controllers/pm_controller');
 let divide_facade = require('../facade/divide_facade');
 let evaluate_facade = require('../facade/evaluate_facade');
+let bid_facade = require('../facade/bid_facade');
 
 const commonFacade = require('../../main/facade/common_facade');
 const GLJListModel = require("../../glj/models/glj_list_model");
@@ -48,6 +49,7 @@ moduleMap[projectConsts.RATION_TEMPLATE] = ration_template;
 moduleMap[projectConsts.PROJECT_INFO] = pmController;
 moduleMap[projectConsts.DIVIDE_SETTING] = divide_facade;
 moduleMap[projectConsts.EVALUATE_LIST] = evaluate_facade;
+moduleMap[projectConsts.BID_EVALUATION_LIST] = bid_facade;
 
 var Project = function (){};
 
@@ -125,6 +127,35 @@ Project.prototype.getData = function(projectID, callback, isReport, userID){
     });
 };
 
+Project.prototype.getDataSync = function(projectID){
+    return new Promise((resolve, reject) => {
+        const functions = [];
+        const firstTime = +new Date();
+        for (const itemName in moduleMap){
+            functions.push((function(itemName){
+                return function (cb) {
+                    const startTime = +new Date();
+                    moduleMap[itemName].getData(projectID, function(err, moduleName, data){
+                        const endTime = +new Date();
+                        console.log(moduleName+'---------------'+(endTime - startTime));
+                        cb(err, {moduleName: moduleName, data: data})
+                    }, true); // true 返回调价后的数据
+                }
+            })(itemName))
+        }
+    
+        asyncTool.parallel(functions, function(err, results) {
+            if (!err) {
+                const lastTime = +new Date();
+                console.log('最后加载时间---------------'+(lastTime - firstTime));
+                resolve(results);
+            } else {
+                reject(err);
+            }
+        });
+    });
+};
+
 Project.prototype.getFilterData = function (projectID, filter, callback) {
     let functions = [];
     let getModuleData = function (moduleName) {

+ 1 - 0
modules/main/models/project_consts.js

@@ -23,6 +23,7 @@ let projectConst = {
     INSTALLATION_FEE:'installation_fee',
     PROJECT_INFO: 'project_info',
     EVALUATE_LIST:'evaluate_list',
+    BID_EVALUATION_LIST:'bid_evaluation_list',
     DIVIDE_SETTING:'divide_setting'
 };
 

+ 6 - 0
modules/main/routes/main_route.js

@@ -33,6 +33,11 @@ module.exports =function (app) {
                 }
                 const markReadProjectIDs = isOpenShareProject ? await pmFacade.markShareItemsRead(projectID, req.session.sessionUser.id) : [];
                 const version = await systemSettingModel.getVersion();
+                let boqType = null;
+                const constructProject = await pmFacade.getConstructionProject(projectID);
+                if (constructProject && constructProject.property && constructProject.property.boqType) {
+                    boqType = constructProject.property.boqType;
+                }
                 res.render('building_saas/main/html/main.html',
                     {
                         userAccount: req.session.userAccount,
@@ -47,6 +52,7 @@ module.exports =function (app) {
                         options:JSON.stringify(options),
                         overWriteUrl:req.session.sessionCompilation.overWriteUrl,
                         markReadProjectIDs: JSON.stringify(markReadProjectIDs),
+                        boqType,
                         title:config[process.env.NODE_ENV].title?config[process.env.NODE_ENV].title:"纵横公路养护云造价",
                         version
                     });

+ 1 - 0
modules/main/routes/project_route.js

@@ -9,6 +9,7 @@ module.exports = function (app) {
 
     projectRouter.post('/save', projectController.save);
     projectRouter.post('/getData', projectController.getData);
+    projectRouter.post('/getDataForInterface', projectController.getDataForInterface);
     projectRouter.post('/markUpdateProject', projectController.markUpdateProject);
     projectRouter.post('/removeProjectMark', projectController.removeProjectMark);
     projectRouter.post('/updateNodes', projectController.updateNodes);

+ 13 - 1
modules/pm/controllers/pm_controller.js

@@ -40,6 +40,7 @@ let callback = function (req, res, err, message, data) {
 
 
 module.exports = {
+    redirectToImportServer,
     checkRight: function (req, res) {
         if (typeof req.body.data === 'object') {
             req.body.data = JSON.stringify(req.body.data);
@@ -915,7 +916,18 @@ module.exports = {
             result.message = err.message;
         }
         res.json(result);
-    }
+    },
+    getProjectByGranularity: async function (req, res) {
+        try {
+            const data = JSON.parse(req.body.data);
+            const userID = req.session.sessionUser.id;
+            const version = req.session.compilationVersion;
+            const projData = await pm_facade.getProjectByGranularity(data.tenderID, data.granularity, data.requestForSummaryInfo, userID, version);
+            callback(req, res, 0, 'success', projData);
+        } catch (err) {
+            callback(req, res, 1, err, null);
+        }
+    },
 };
 
 

+ 48 - 8
modules/pm/facade/pm_facade.js

@@ -9,6 +9,7 @@
     };
 //先导出后require可以解决循环引用问题
 module.exports={
+    getProjectByGranularity,
     prepareShareList,
     getShareList,
     addShareList,
@@ -92,6 +93,7 @@ const shareListModel = mongoose.model('share_list');
 let welcomeModel = mongoose.model("welcome_setting");
 let divideModel = mongoose.model("divide_setting");
 let evaluateListModel = mongoose.model("evaluate_list");
+let bidListModel = mongoose.model("bid_evaluation_list");
 
 let featureLibModel =  mongoose.model("std_project_feature_lib");
 let scMathUtil = require('../../../public/scMathUtil').getUtil();
@@ -100,7 +102,11 @@ const SectionTreeDao = require('../../complementary_ration_lib/models/sectionTre
 let sectionTreeDao = new SectionTreeDao();
 const CounterModel = require("../../glj/models/counter_model");
 const moment = require('moment-timezone');
-const { fixedFlag, SharePermissionChangeType } = require('../../../public/common_constants');
+const {
+    fixedFlag,
+    SharePermissionChangeType,
+    GRANULARITY,
+} = require('../../../public/common_constants');
 const notDeleted = [{deleteInfo: null}, {'deleteInfo.deleted': false}];
 let cipher = require('../../../public/cipher');
 let index = require("../../system_setting/model/index");
@@ -583,7 +589,8 @@ async function copyProject(userID, compilationID, data, newProjectID = null, del
         copyRationSubList(originalID,newProjectID,billMap.uuidMaping,rationMap.uuidMaping,projectGLJMap.IDMap,quantityDetailModel),
         copyRationSubList(originalID,newProjectID,billMap.uuidMaping,rationMap.uuidMaping,projectGLJMap.IDMap,rationInstallationModel),
         copyRationSubList(originalID,newProjectID,billMap.uuidMaping,rationMap.uuidMaping,projectGLJMap.IDMap,rationTemplateModel),
-        copyMaterialList(originalID,newProjectID,projectGLJMap.IDMap,evaluateListModel)
+        copyMaterialList(originalID,newProjectID,projectGLJMap.IDMap,evaluateListModel),
+        copyMaterialList(originalID,newProjectID,projectGLJMap.IDMap,bidListModel),
     ];
     if(originalProperty.calcProgramFile){
         copyTasks.push(commonCopy(newProjectID,originalProperty.calcProgramFile.ID,calcProgramFileID,calcProgramsModel));
@@ -1637,7 +1644,7 @@ async function exportTenderData(data){
     result.labourCoes = await labourCoesModel.findOne({projectID:data.projectID}).lean();
     result.divide_setting = await divideModel.findOne({projectID:data.projectID}, '-_id').lean();
     result.evaluateList = await evaluateListModel.find({projectID:data.projectID}, '-_id').lean();
-
+    result.bidList = await bidListModel.find({projectID:data.projectID}, '-_id').lean();
     return cipher.aesEncrypt(JSON.stringify(result));
 }
 
@@ -1791,9 +1798,9 @@ async function importProject(data,req,updateData) {
             let [constructionProjectID,projectIDMap,labourCoeFileIDMap,calcProgramFileIDMap] = await handleMainProjectDatas(mainData,updateData,req.session.sessionUser.id);
             result.constructionProjectID = constructionProjectID;
             if(datas.length > 1 ){//生成后统一次插入 2020-05-29
-              let newProjectSettings=[],bills=[],rations=[],projectGLJs=[],rationGLJs=[],rationCoes=[],quantityDetails=[],rationInstallations=[],rationTemplates=[],newCalcProgramsFiles=[],newLabourCoes=[],newDivides=[],evaluateList;
+              let newProjectSettings=[],bills=[],rations=[],projectGLJs=[],rationGLJs=[],rationCoes=[],quantityDetails=[],rationInstallations=[],rationTemplates=[],newCalcProgramsFiles=[],newLabourCoes=[],newDivides=[],evaluateList=[],bidList=[];
                 for(let i = 1;i<datas.length;i++){
-                  let [newProjectSetting,tbills,trations,tprojectGLJs,trationGLJs,trationCoes,tquantityDetails,trationInstallations,trationTemplates,newCalcProgramsFile,newLabourCoe,newDivide,tevaluateList] =  await handleEachProject(datas[i],projectIDMap,labourCoeFileIDMap,calcProgramFileIDMap)
+                  let [newProjectSetting,tbills,trations,tprojectGLJs,trationGLJs,trationCoes,tquantityDetails,trationInstallations,trationTemplates,newCalcProgramsFile,newLabourCoe,newDivide,tevaluateList,tbidList] =  await handleEachProject(datas[i],projectIDMap,labourCoeFileIDMap,calcProgramFileIDMap)
                   if(newProjectSetting) newProjectSettings.push(newProjectSetting);
                   if(tbills.length > 0) bills = bills.concat(tbills);
                   if(trations.length > 0) rations = rations.concat(trations);
@@ -1807,6 +1814,7 @@ async function importProject(data,req,updateData) {
                   if(newLabourCoe) newLabourCoes.push(newLabourCoe);
                   if(newDivide) newDivides.push(newDivide);
                   if(tevaluateList.length > 0) evaluateList = evaluateList.concat(tevaluateList);
+                  if(tbidList.length > 0) bidList = bidList.concat(tbidList);
                 }
 
                 if(newProjectSettings.length > 0) await insertMany(newProjectSettings,projectSettingModel);
@@ -1822,6 +1830,7 @@ async function importProject(data,req,updateData) {
                 if(newLabourCoes.length>0) await insertMany(newLabourCoes,labourCoesModel);
                 if(newDivides.length>0) await divideModel.insertMany(newDivides);
                 if(evaluateList.length > 0) await insertMany(evaluateList,evaluateListModel);
+                if(bidList.length > 0) await insertMany(bidList,bidListModel);
             }
 
          }
@@ -1830,7 +1839,7 @@ async function importProject(data,req,updateData) {
 }
 
 async function handleEachProject(data,projectIDMap,labourCoeFileIDMap,calcProgramFileIDMap){
-    let bills = [],rations = [],projectGLJs = [],rationGLJs=[],rationCoes=[],quantityDetails=[],rationInstallations=[],rationTemplates=[],evaluateList=[];
+    let bills = [],rations = [],projectGLJs = [],rationGLJs=[],rationCoes=[],quantityDetails=[],rationInstallations=[],rationTemplates=[],evaluateList=[],bidList=[];
     let newProjectSetting =null,newCalcProgramsFile = null,newLabourCoe = null,newDivide=null;
     let billsIDMap = {},projectGLJIDMap={},rationIDMap = {};
     let newProjectID = projectIDMap[data.projSetting.projectID];
@@ -1879,6 +1888,7 @@ async function handleEachProject(data,projectIDMap,labourCoeFileIDMap,calcProgra
     if(data.rationInstallations && data.rationInstallations.length > 0) rationInstallations = setRationSubList(data.rationInstallations,newProjectID,billsIDMap,rationIDMap,projectGLJIDMap);
     if(data.rationTemplates && data.rationTemplates.length > 0) rationTemplates = setRationSubList(data.rationTemplates,newProjectID,billsIDMap,rationIDMap,projectGLJIDMap);
     if(data.evaluateList && data.evaluateList.length > 0) evaluateList = setMaterialList(data.evaluateList,newProjectID,projectGLJIDMap);
+    if(data.bidList && data.bidList.length > 0) bidList = setMaterialList(data.bidList,newProjectID,projectGLJIDMap);
 
     //生成projectSetting 文件
     if(data.projSetting){
@@ -1912,7 +1922,7 @@ async function handleEachProject(data,projectIDMap,labourCoeFileIDMap,calcProgra
       delete newDivide._id;
     } 
 
-    return [newProjectSetting,bills,rations,projectGLJs,rationGLJs,rationCoes,quantityDetails,rationInstallations,rationTemplates,newCalcProgramsFile,newLabourCoe,newDivide,evaluateList]
+    return [newProjectSetting,bills,rations,projectGLJs,rationGLJs,rationCoes,quantityDetails,rationInstallations,rationTemplates,newCalcProgramsFile,newLabourCoe,newDivide,evaluateList,bidList]
 
 }
 
@@ -2215,4 +2225,34 @@ async function getWelcomeInfo(compilationId,sessionUser,isFree=true) {
 
     return [isShow,context,showTime]
 
-}
+}
+
+/**
+ * 根据单位工程,获取建设项目-单位工程,不包含无单位工程的单项工程
+ * @param {Number} tenderID - 单位ID
+ * @param {Number} granularity - 导出颗粒度
+ * @param {Object} requestForSummaryInfo - 项目表级汇总字段(建设项目、单位工程汇总)
+ * @param {String} userID - 用户ID
+ * @param {String} versionName - 版本
+ */
+async function getProjectByGranularity(tenderID, granularity, requestForSummaryInfo, userID, versionName) {
+    const theTender = await projectModel.findOne({ userID: userID, ID: tenderID }).lean(); //加上session的userID,防止tenderID被篡改过
+    if (!theTender) {
+        return null;
+    }
+    const constructionProject = await projectModel.findOne({ ID: theTender.ParentID }).lean();
+    if (!constructionProject) {
+        return null;
+    }
+    constructionProject.children = granularity === GRANULARITY.PROJECT
+        ? await projectModel.find({ ParentID: constructionProject.ID, $or: notDeleted }).lean()
+        : [theTender];
+    // 获取汇总信息
+    constructionProject.summaryInfo = await getSummaryInfo([constructionProject.ID], requestForSummaryInfo);
+    // 获取编制软件信息: 软件公司;软件名;版本号;授权信息;
+    const systemSetting = await systemSettingModel.findOne({});
+    const company = systemSetting.company || '珠海纵横创新软件有限公司';
+    const version = systemSetting.version || '';
+    constructionProject.softInfo = `${company};${versionName};${version};${userID}`;
+    return constructionProject;
+}

+ 1 - 2
modules/pm/routes/pm_route.js

@@ -53,11 +53,9 @@ module.exports = function (app) {
     pmRouter.post('/getFeeRateFile', pmController.getFeeRateFileList);
     pmRouter.post('/updateFiles', pmController.updateFiles);
     pmRouter.post('/defaultSettings', pmController.defaultSettings);
-    //GC
     pmRouter.post('/getGCDatas', pmController.getGC);
     pmRouter.post('/recGC', pmController.recGC);
     pmRouter.post('/delGC', pmController.delGC);
-    //share
     pmRouter.post('/getProjectShareInfo', pmController.projectShareInfo);
     pmRouter.post('/getInitialShareData', pmController.getInitialShareData);
     pmRouter.post('/share', pmController.share);
@@ -67,6 +65,7 @@ module.exports = function (app) {
     pmRouter.post('/importProject', pmController.importProject);
     pmRouter.post('/copyConstructionProject',systemMiddleware.tenderNumberChecking,pmController.copyConstructionProject);
     pmRouter.post('/importProcessChecking', pmController.importProcessChecking);
+    pmRouter.post('/getProjectByGranularity', pmController.getProjectByGranularity);
 
 
     app.use('/pm/api', pmRouter);

+ 2 - 2
modules/reports/controllers/rpt_controller.js

@@ -269,7 +269,7 @@ function getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, orientation, custo
                             }
                         } catch (ex) {
                             console.log("报表数据异常: userId " + user_id + ", project id: " + prj_id);
-                            console.log(ex);
+                            console.log(ex.message);
                             cb('Exception occurs while on going...', null);
                         }
                     };
@@ -456,7 +456,7 @@ function getBillsSummaryReportPages(req, user_id, prjIds, rpt_id, pageSize, orie
                 }
             } catch (ex) {
                 console.log("报表数据异常: userId " + user_id + ", project id: " + JSON.stringify(prjIds));
-                console.log(ex);
+                console.log(ex.message);
                 cb('Exception occurs while on going...', null);
             }
         });

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 2017 - 2080
package-lock.json


+ 27 - 3
public/common_constants.js

@@ -95,6 +95,12 @@
         EMERGENCY_FEE: 42
     };
 
+    // 导出粒度
+    const GRANULARITY = {
+        PROJECT: 1, // 导出建设项目
+        TENDER: 3 // 导出单位工程
+    };
+
     // 补充人材机库
     const COMPLEMENTARY_LIB = 'complementaryLib';
 
@@ -109,8 +115,8 @@
 
     // 工程量清单类型
     const BOQType = {
-        TENDER: 1, // 招标
-        BIDDER: 2, // 投标
+        BID_INVITATION: 1, // 招标
+        BID_SUBMISSION: 2, // 投标
     };
 
     const SourceType = {
@@ -144,8 +150,24 @@
         SHARE_CANCEL: 2,
     };
 
+    // 导出的文件类型选项
+    const EXPORT_KIND = {
+        BID_INVITATION: 1, // 招标
+        BID_SUBMISSION: 2, // 投标
+        CONTROL: 3 // 控制价
+    };
+
+    // 定额类型
+    const RationType = {
+        RATION: 1,
+        VOLUME_PRICE:2,
+        GLJ_RATION: 3,
+        INSTALL: 4,
+    };
+
     return {
         fixedFlag,
+        GRANULARITY,
         COMPLEMENTARY_LIB,
         COMPILATION,
         ValuationType,
@@ -154,6 +176,8 @@
         StorageKey,
         SharePermissionChangeType,
         PageTarget,
-        BlankType
+        BlankType,
+        EXPORT_KIND,
+        RationType,
     };
 })

+ 14 - 1
public/common_util.js

@@ -18,11 +18,16 @@
     function isDef(val) {
         return typeof val !== 'undefined' && val !== null;
     }
-
+    
     function isEmptyVal(val) {
         return val === null || val === undefined || val === '';
     }
 
+    // v是否有值,不为undefined、null、''
+    function hasValue(v) {
+        return typeof v !== 'undefined' && v !== null && v !== '';
+    }
+
     // 是否近似相等(null = undefined = '', 1 = '1'...)
     function similarEqual(a, b) {
         // null == '' 为false,所以不能用非严等
@@ -32,6 +37,12 @@
         return a == b;
     }
 
+    // 是否是汉字(基本汉字)
+    function isHan(str) {
+        const reg = /[\u4e00-\u9fa5]/;
+        return reg.test(str);
+    }
+
     // 将树数据排序好
     function getSortedTreeData(rootID, items) {
         return sortSameDedth(rootID, items).reverse();
@@ -121,7 +132,9 @@
     return {
         isDef,
         isEmptyVal,
+        hasValue,
         similarEqual,
+        isHan,
         getSortedTreeData,
         handleFullscreen,
         standardNumber,

+ 3 - 3
public/gljUtil.js

@@ -5,9 +5,9 @@ const fs = require('fs');
 let _= require('lodash');
 const scMathUtil = require('./scMathUtil').getUtil();
 
-let gljNodeUtil = null;
-let data = fs.readFileSync(__dirname + '/web/gljUtil.js', 'utf8', 'r');
-eval(data + ' ; gljNodeUtil = gljUtil; ');
+let gljNodeUtil = require("./web/gljUtil");
+// let data = fs.readFileSync(__dirname + '/web/gljUtil.js', 'utf8', 'r');
+// eval(data + ' ; gljNodeUtil = gljUtil; ');
 
 module.exports = {
     calcProjectGLJQuantity :calcProjectGLJQuantity,

+ 37 - 0
public/web/PerfectLoad.js

@@ -129,3 +129,40 @@ jQuery.bootstrapLoading = {
         }
     }
 };
+
+const SCComponent = (() => {
+    /*
+    * 假滚动条,调用end方法后进度直接跳到100%
+    * 使用利用合成线程的css动画,不会被js执行阻塞。
+    * */
+    const InitProgressBar = (() => {
+        function ProgressBar() {
+            this.$modal = $('#progress');
+            this.$title = $('#progress-title');
+            this.$title.text('导出');
+            this.$content = $('#progress-content');
+            this.$content.text('正在导出...');
+            this.$barCover = $('#progressCover');
+            this.animationName = 'progress-cover-move';
+        }
+        //显示滚动条,自动处理滚动条速度
+        ProgressBar.prototype.start = function (title, content) {
+            this.$title.text(title);
+            this.$content.text(content);
+            this.$barCover.css('transform', 'translate(0)');
+            this.$barCover.addClass(this.animationName);
+            this.$modal.modal('show');
+        };
+        //结束显示滚动条,滚动条从当前位置滚到100% 消失
+        ProgressBar.prototype.end = function () {
+            this.$barCover.removeClass(this.animationName);
+            this.$barCover.css('transform', 'translate(100%)');
+            setTimeout(() => {
+                this.$modal.modal('hide');
+            }, 500);
+        };
+        return ProgressBar;
+    })();
+
+    return { InitProgressBar }
+})();

+ 30 - 21
public/web/gljUtil.js

@@ -180,7 +180,7 @@ let gljUtil = {
                 continue;
             }
             if(!pglj.is_adjust_price){
-                tender_glj_quantity = this.getRationGLJTenderQuantity(rg,tem_ration,q_decimal,scMathUtil);
+                tender_glj_quantity = this.getRationGLJTenderQuantity(rg,tem_ration,q_decimal,scMathUtil,pglj);
                 tender_r_quantity = this.getRationTenderQuantity(tem_ration,q_decimal,scMathUtil);
             }
             let total = scMathUtil.roundForObj(glj_quantity*r_quantity, q_decimal);
@@ -204,7 +204,7 @@ let gljUtil = {
         result.tenderQuantity = tender_qantity_sum;
         return result;
     },
-    getRationGLJTenderQuantity:function (ration_glj,ration,q_decimal,scMathUtil) {
+    getRationGLJTenderQuantity:function (ration_glj,ration,q_decimal,scMathUtil,projectGLJ) {
         let coeMap = {
             1:'labour',  //人工
             2:'material',//材料
@@ -221,14 +221,12 @@ let gljUtil = {
             }
         }
         let coe = 1;
-        if (!calcTools.isTenderProjectGLJ(ration_glj)){
-            coe = 1;
-        }
-        else{
-            coe = ration.quantityCoe&&this.isNotEmpty(ration.quantityCoe[coeField])?ration.quantityCoe[coeField]:1;
-            coe = parseFloat(coe);
-        }
-
+        if(projectGLJ && projectGLJ.is_adjust_price == 0){
+          coe = ration.quantityCoe&&this.isNotEmpty(ration.quantityCoe[coeField])?ration.quantityCoe[coeField]:1;
+          coe = parseFloat(coe);
+        }else{
+          coe = 1;
+        }   
         if (coe == 0) coe = 1;
         let glj_quantity = scMathUtil.roundForObj(ration_glj.quantity, q_decimal);
         return scMathUtil.roundForObj(glj_quantity * coe,q_decimal);
@@ -282,7 +280,8 @@ let gljUtil = {
         }
     },
     getFlag:function (b) {
-        return _.find(b.flags,{"fieldName":"fixed"});
+      let lo_sh = typeof _ !== 'undefined'?_:this._;
+      return lo_sh.find(b.flags,{"fieldName":"fixed"});
     },
     getGLJPrice:function (glj,projectGLJDatas,calcOptions,labourCoeDatas,decimalObj,isRadio,_,scMathUtil,ext,tenderCoe, isReport) {
         let result = {};
@@ -433,14 +432,16 @@ let gljUtil = {
         return parseInt(str.substr(0,1));
     },
     sortRationGLJ:function (list, std) {
-        const field = std ? 'gljType' : 'type';
-        list = _.sortByAll(list, [function (item) {
-            return gljUtil.getMainType(item[field]);
-        }, gljUtil.getCodeSortMath()]);
-        return list;
+      const field = std ? 'gljType' : 'type';
+      let lo_sh = typeof _ !== 'undefined'?_:this._;
+      list = lo_sh.sortByAll(list, [function (item) {
+          return gljUtil.getMainType(item[field]);
+      }, gljUtil.getCodeSortMath()]);
+      return list;
     },
     sortMixRatio:function (list) {
-        return _.sortByAll(list, ["code"]);
+      let lo_sh = typeof _ !== 'undefined'?_:this._;
+      return lo_sh.sortByAll(list, ["code"]);
     },
     //项目工料机 混凝土、砂浆、配合比排序与定额工料机不一样,同时,type取值的地方不一样
     sortProjectGLJ:function (list,lodash) {
@@ -494,9 +495,10 @@ let gljUtil = {
         return {glj_id:-99,unit_price_file_id:unitFileID,connect_key:connect_key,consumption:consumption,code:'80CCS',name:'车船税',unit:'元',type:302,specs:'',from:"cpt"}
     },
     updateProperty: function (obj, doc) {
-        _.forEach(doc, function (n, key) {
+      let lo_sh = typeof _ !== 'undefined'?_:this._;
+      lo_sh.forEach(doc, function (n, key) {
             obj[key] = n;
-        });
+      });
     },
     getTotalQuantity:function(glj,ration,rd,gd){
         if(ration){
@@ -504,7 +506,8 @@ let gljUtil = {
             quantity = (quantity == 0 || quantity == undefined || quantity == null || quantity == "") ? 0 : quantity;
             quantity = scMathUtil.roundForObj(quantity, rd);//计算前进行4舍5入
             glj.quantity = scMathUtil.roundForObj(glj.quantity, gd);
-            glj.tenderQuantity = this.getRationGLJTenderQuantity(glj, ration, gd, scMathUtil);
+            let pglj = calcTools.getProjectGLJ(glj);
+            glj.tenderQuantity = this.getRationGLJTenderQuantity(glj, ration, gd, scMathUtil,pglj);
 
             return scMathUtil.roundToString(quantity * glj.quantity, gd);
         }
@@ -569,8 +572,9 @@ let gljUtil = {
         }
     },
     setProperty:function(Obj,updateData) {
+      let lo_sh = typeof _ !== 'undefined'?_:this._;
         for(let ukey in updateData){
-            if(_.isObject(updateData[ukey]) && _.isObject(Obj[ukey])&&!_.isArray(updateData[ukey])){
+            if(lo_sh.isObject(updateData[ukey]) && lo_sh.isObject(Obj[ukey])&&!lo_sh.isArray(updateData[ukey])){
                 setProperty(Obj[ukey],updateData[ukey]);
             }else {
                 Obj[ukey] = updateData[ukey];
@@ -637,4 +641,9 @@ let gljUtil = {
     hasCompMaterial:[202, 203, 204],//有组成物的材料
     hasCompMachine:[301],//有组成物的机械
     machineComposition:[201,302,303]//可以做为机械组成物的类型
+}
+
+if (typeof module !== 'undefined') {
+  gljUtil._ = require("lodash");
+  module.exports = gljUtil;
 }

+ 3 - 1
public/web/id_tree.js

@@ -243,7 +243,9 @@ var idTree = {
         Node.prototype.serialNo = function () {
             return this.tree.items.indexOf(this);
         };
-
+        Node.prototype.row = function () {
+            return this.serialNo() + 1;
+        }
         Node.prototype.addChild = function (node) {
             var preSibling = this.children.length === 0 ? null : this.children[this.children.length - 1];
             node.parent = this;

+ 1 - 1
public/web/scMathUtil.js

@@ -173,7 +173,7 @@ let scMathUtil = {
     roundForObj:function(obj,decimal){
         let me = this;
         let value;
-        if(obj === undefined || obj === null) return 0;
+        if(obj === undefined || obj === null || isNaN(obj)) return 0;
         let n = Math.pow(10,decimal);
         if(me.isNumber(obj)){
           value = Math.round(obj * n) / n;

+ 295 - 186
web/building_saas/css/custom.css

@@ -1,41 +1,47 @@
-
-.text-green{
+.text-green {
     color: #172a30
 }
-label.title{
+
+label.title {
     display: inline-block;
     width: 100px;
 }
-.modal-feeRate {max-width: 550px}
 
+.modal-feeRate {
+    max-width: 550px
+}
 
-div.resize{
+div.resize {
     height: 10px;
     background: #efefef;
     width: 100%;
     cursor: s-resize;
 }
-div.resize-y{
+
+div.resize-y {
     height: 5px;
     background: #efefef;
     width: 100%;
     cursor: s-resize;
 }
-div.resize-x{
+
+div.resize-x {
     width: 4px;
     height: 100%;
     background: #efefef;
     cursor: w-resize;
     float: left;
 }
+
 /*.zlfb-check{
     margin-left: 20px;
 }*/
-legend.legend{
-    display:block;
-    width:auto;
-    font-size:0.9rem;
-    top:-15px;
+
+legend.legend {
+    display: block;
+    width: auto;
+    font-size: 0.9rem;
+    top: -15px;
     background: white;
 }
 
@@ -47,34 +53,37 @@ legend.legend{
     margin-left: 14px;
 }
 
-.filterType{
+.filterType {
     width: 98px;
     float: left;
-    height: 100%;
 }
+
 .left {
     float: left
 }
-.full-h{
+
+.full-h {
     height: 100%;
 }
-.half-h{
+
+.half-h {
     height: 50%;
 }
-.a_color{
+
+.a_color {
     color: #007bff;
 }
 
-.filterType a{
+.filterType a {
     padding: 1px;
     padding-top: 7px;
     padding-bottom: 7px;
 }
 
-#gljPriceTenderCoe::-webkit-outer-spin-button,
-#gljPriceTenderCoe::-webkit-inner-spin-button {
+#gljPriceTenderCoe::-webkit-outer-spin-button, #gljPriceTenderCoe::-webkit-inner-spin-button {
     -webkit-appearance: none;
 }
+
 #gljPriceTenderCoe {
     -moz-appearance: textfield;
 }
@@ -85,84 +94,83 @@ legend.legend{
 }
 
 .message-box {
-    position:absolute;
-    background:#000;
-    padding:8px 10px;
+    position: absolute;
+    background: #000;
+    padding: 8px 10px;
     line-height: 18px;
-    border-radius:4px;
-    text-align:left;
-    font:0.9rem Calibri;
-    box-shadow:2px 2px 6px #ccc;
-    color:#fff;
+    border-radius: 4px;
+    text-align: left;
+    font: 0.9rem Calibri;
+    box-shadow: 2px 2px 6px #ccc;
+    color: #fff;
 }
+
 .triangle-border {
-    position:absolute;
-    left:10px;
-    overflow:hidden;
-    width:0;
-    height:0;
-    border-width:6px;
-    border-style:solid dashed dashed dashed;
+    position: absolute;
+    left: 10px;
+    overflow: hidden;
+    width: 0;
+    height: 0;
+    border-width: 6px;
+    border-style: solid dashed dashed dashed;
 }
+
 .triangle-border_dropdown {
-    position:absolute;
-    left:0px;
-    overflow:hidden;
-    width:0;
-    height:0;
-    border-width:4px;
+    position: absolute;
+    left: 0px;
+    overflow: hidden;
+    width: 0;
+    height: 0;
+    border-width: 4px;
     z-index: 10;
-    border-style:solid dashed dashed dashed;
+    border-style: solid dashed dashed dashed;
 }
+
 .tb-border_dropdown {
-    top:7px;
-    border-color:#000 transparent transparent transparent;
+    top: 7px;
+    border-color: #000 transparent transparent transparent;
 }
 
 .tb-background_dropdown {
-    bottom:-11px;
-    border-color:#000 transparent transparent transparent;
+    bottom: -11px;
+    border-color: #000 transparent transparent transparent;
 }
 
 .tb-border {
-    bottom:-12px;
-    border-color:#000 transparent transparent transparent;
+    bottom: -12px;
+    border-color: #000 transparent transparent transparent;
 }
+
 .tb-background {
-    bottom:-11px;
-    border-color:#000 transparent transparent transparent;
+    bottom: -11px;
+    border-color: #000 transparent transparent transparent;
 }
 
 .tb-border_up {
-    top:-12px;
-    border-color:transparent transparent #000 transparent;
+    top: -12px;
+    border-color: transparent transparent #000 transparent;
 }
+
 .tb-background_up {
-    top:-11px;
-    border-color:transparent transparent #000 transparent;
+    top: -11px;
+    border-color: transparent transparent #000 transparent;
 }
 
-
-.elf-options:hover{
+.elf-options:hover {
     background-color: #CCCCCC;
 }
 
-.inline-div{
-    display:inline;
+.inline-div {
+    display: inline;
 }
 
 /*快捷切换单位工程*/
 
-
-
-
-
 /*.menu a:hover {
     color: #fff;
     background: #40DE5A;
 }*/
 
-
 .menu ul ul {
     visibility: hidden;
     position: absolute;
@@ -182,12 +190,13 @@ legend.legend{
     visibility: visible;
 }
 
-.ui-datepicker-next, .ui-datepicker-next:hover{
+.ui-datepicker-next, .ui-datepicker-next:hover {
     background-image: url("/lib/jquery-ui/images/ui-icons_444444_256x240.png");
     background-repeat: no-repeat;
     background-position: -28px -12px;
 }
-.ui-datepicker-prev, .ui-datepicker-prev:hover{
+
+.ui-datepicker-prev, .ui-datepicker-prev:hover {
     background-image: url("/lib/jquery-ui/images/ui-icons_444444_256x240.png");
     background-repeat: no-repeat;
     background-position: -90px -12px;
@@ -197,226 +206,261 @@ legend.legend{
     padding: 0;
 }
 
-#toolToastWrap{
-    display:-webkit-box;
-    display:-ms-flexbox;
-    display:flex;
-    -webkit-box-pack:center;
-    -ms-flex-pack:center;
-    justify-content:center;
-    pointer-events:none;
+#toolToastWrap {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-pack: center;
+    -ms-flex-pack: center;
+    justify-content: center;
+    pointer-events: none;
 }
 
-#toolToast{
-    padding:11px 20px;
-    line-height:18px;
-    font-size:14px;
-    position:relative;
-    word-wrap:break-word;
-    color:#fff;
-    text-align:center;
-    background:rgba(0,0,0,.75);
-    background-size:cover;
-    -webkit-user-select:none;
-    -moz-user-select:none;
-    -ms-user-select:none;
-    user-select:none;
-    display:-webkit-box;
-    display:-ms-flexbox;
-    display:flex;
-    -webkit-box-align:center;
-    -ms-flex-align:center;
-    align-items:center;
-    max-width:686px;
-    box-sizing:border-box;
-    box-shadow:0 0 0 0 rgba(0,0,0,.15),0 2px 5px 0 rgba(0,0,0,.25);
-    pointer-events:auto;
-    border-radius:2px
-}
-
-#toolToastBtn{
-    color:#0188fb;
-    margin:0 2px;
-    white-space:nowrap;
-    cursor:pointer;
-    font-weight:700
+#toolToast {
+    padding: 11px 20px;
+    line-height: 18px;
+    font-size: 14px;
+    position: relative;
+    word-wrap: break-word;
+    color: #fff;
+    text-align: center;
+    background: rgba(0, 0, 0, .75);
+    background-size: cover;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-align: center;
+    -ms-flex-align: center;
+    align-items: center;
+    max-width: 686px;
+    box-sizing: border-box;
+    box-shadow: 0 0 0 0 rgba(0, 0, 0, .15), 0 2px 5px 0 rgba(0, 0, 0, .25);
+    pointer-events: auto;
+    border-radius: 2px
+}
+
+#toolToastBtn {
+    color: #0188fb;
+    margin: 0 2px;
+    white-space: nowrap;
+    cursor: pointer;
+    font-weight: 700
 }
 
-#toolToastBtn:hover{
-    color:#4060c9
+#toolToastBtn:hover {
+    color: #4060c9
 }
 
-#toolToastBtn:active{
-    color:#354ea1
+#toolToastBtn:active {
+    color: #354ea1
 }
 
-.select_input{
-    border:none;
+.select_input {
+    border: none;
 }
 
 /*.es-list>li:hover{
     background: lightgrey;
 }*/
 
-.es-list-selected{
+.es-list-selected {
     background: lightgrey;
 }
 
-.es-list>li{
-    font-size:13px;
+.es-list>li {
+    font-size: 13px;
 }
-.es-list{
-    white-space:nowrap;
+
+.es-list {
+    white-space: nowrap;
 }
 
 /*.dropdown-toggle::after{
     vertical-align:.5em
 }*/
-#esInput{
-    font-size:13px;
+
+#esInput {
+    font-size: 13px;
     color: black;
     height: 100%;
 }
-.ration_glj_spread{
+
+.ration_glj_spread {
     width: 83%;
     float: left;
 }
 
-.item_spread{
+.item_spread {
     width: 29.8%;
     float: left;
     background: #F1F1F1;
-    word-wrap:break-word
+    word-wrap: break-word
 }
 
-input.text-right{
+input.text-right {
     text-align: right;
 }
 
-.cus-width{
+.cus-width {
     width: 100px;
     margin-left: 10px;
 }
-.more{
-    padding-left:.25rem!important
+
+.more {
+    padding-left: .25rem!important
 }
+
 .bottom-tznrTools {
     height: 30px;
     line-height: 30px;
-    background:#efefef;
-    bottom:30px;
-    left:0px;
+    background: #efefef;
+    bottom: 30px;
+    left: 0px;
     z-index: 999
 }
 
-.zmhs-link{
-    padding:0.4em 0.4em !important;
+.zmhs-link {
+    padding: 0.4em 0.4em !important;
 }
 
 /*修改tooltip默认最大宽度 */
-.tooltip-inner{
+
+.tooltip-inner {
     max-width: 400px !important;
 }
-.applySuccess{
+
+.applySuccess {
     display: none;
     color: #43CD80;
     margin-left: 8px
 }
+
 /*占位底色*/
+
 .occupied {
     background: #f1f1f1;
 }
-.material-ration-left{
-    float:left;
+
+.material-ration-left {
+    float: left;
     width: 90%;
 }
-.material-ass{
+
+.material-ass {
     width: 10%;
-    background:#f7f7f9
+    background: #f7f7f9
 }
+
 /*书签批注*/
-.annotate-color-1::before{
+
+.annotate-color-1::before {
     color: #E2F2C5 !important;
-    -webkit-text-stroke:.5px #ced4da;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-2::before{
+
+.annotate-color-2::before {
     color: #F9E2CF !important;
-    -webkit-text-stroke:.5px #ced4da;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-3::before{
-    color:#F2EFD9 !important;
-    -webkit-text-stroke:.5px #ced4da;
+
+.annotate-color-3::before {
+    color: #F2EFD9 !important;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-4::before{
-    color:#F5D1DA !important;
-    -webkit-text-stroke:.5px #ced4da;
+
+.annotate-color-4::before {
+    color: #F5D1DA !important;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-5::before{
-    color:#E3E3E3 !important;
-    -webkit-text-stroke:.5px #ced4da;
+
+.annotate-color-5::before {
+    color: #E3E3E3 !important;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-6::before{
-    color:#B6F3F2 !important;
-    -webkit-text-stroke:.5px #ced4da;
+
+.annotate-color-6::before {
+    color: #B6F3F2 !important;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-7::before{
-    color:#ECE0F5 !important;
-    -webkit-text-stroke:.5px #ced4da;
+
+.annotate-color-7::before {
+    color: #ECE0F5 !important;
+    -webkit-text-stroke: .5px #ced4da;
 }
+
 /*书签批注*/
-.annotate-color-1::before{
+
+.annotate-color-1::before {
     color: #E2F2C5 !important;
-    -webkit-text-stroke:.5px #ced4da;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-2::before{
+
+.annotate-color-2::before {
     color: #F9E2CF !important;
-    -webkit-text-stroke:.5px #ced4da;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-3::before{
-    color:#F2EFD9 !important;
-    -webkit-text-stroke:.5px #ced4da;
+
+.annotate-color-3::before {
+    color: #F2EFD9 !important;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-4::before{
-    color:#F5D1DA !important;
-    -webkit-text-stroke:.5px #ced4da;
+
+.annotate-color-4::before {
+    color: #F5D1DA !important;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-5::before{
-    color:#E3E3E3 !important;
-    -webkit-text-stroke:.5px #ced4da;
+
+.annotate-color-5::before {
+    color: #E3E3E3 !important;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-6::before{
-    color:#B6F3F2 !important;
-    -webkit-text-stroke:.5px #ced4da;
+
+.annotate-color-6::before {
+    color: #B6F3F2 !important;
+    -webkit-text-stroke: .5px #ced4da;
 }
-.annotate-color-7::before{
-    color:#ECE0F5 !important;
-    -webkit-text-stroke:.5px #ced4da;
+
+.annotate-color-7::before {
+    color: #ECE0F5 !important;
+    -webkit-text-stroke: .5px #ced4da;
 }
+
 .z-index-3000 {
     z-index: 3000;
 }
+
 .text-ellipsis {
     overflow: hidden;
     white-space: nowrap;
     text-overflow: ellipsis;
 }
+
 .border-radius {
     border-radius: .2rem !important;
 }
+
 .pm-i {
     width: 18px;
 }
+
 .calcbase-btn {
     width: 24px;
     padding-left: 0;
     padding-right: 0;
 }
+
 .hide-area {
     display: none;
 }
+
 .middle-modal-width {
     max-width: 650px;
 }
+
 .middle-modal-height {
     height: 500px;
 }
@@ -431,27 +475,32 @@ input.text-right{
 }
 
 /* 初始样式,防止projspread初始化完后背景从白突然变灰 */
+
 .poj-list {
-    height: 1000px; 
+    height: 1000px;
     background: #f7f7f9;
 }
+
 .form-control-inline {
-  display: inline-block !important;
-  width: 80% !important;
+    display: inline-block !important;
+    width: 80% !important;
 }
-.unit_price_header{
-  padding-top:6px;
-  margin-left: 50px;
-  margin-right: 100px !important;
+
+.unit_price_header {
+    padding-top: 6px;
+    margin-left: 50px;
+    margin-right: 100px !important;
 }
+
 .default-cursor {
     cursor: default !important;
 }
 
-.material_link.active{
-  border:2px solid #ff6501 !important;
-  border-right: 1px solid #fff !important;
+.material_link.active {
+    border: 2px solid #ff6501 !important;
+    border-right: 1px solid #fff !important;
 }
+
 .table-sc th {
     font-weight: normal;
 }
@@ -463,6 +512,7 @@ input.text-right{
 }
 
 /* 右键菜单input */
+
 .menu-input {
     width: 2.5rem;
     border: 1px solid rgb(221, 221, 221);
@@ -470,9 +520,68 @@ input.text-right{
     height: 1.3rem;
     text-align: center;
 }
+
 .menu-input:focus {
     outline: none;
 }
+
 /* .menu-input::selection {
     background-color: rgb(41, 128, 185);
-} */
+} */
+
+/* 进度条动画 */
+
+.progress-bar {
+    position: relative;
+    width: 100%;
+}
+
+.progress-move {
+    position: absolute;
+    left: 0;
+    top: 0;
+    z-index: 999;
+    width: 200%;
+    height: 100%;
+    border-radius: .25rem;
+    animation: progressMove 20s linear infinite;
+}
+
+@keyframes progressMove {
+    from {
+        transform: translateX(0);
+    }
+    to {
+        transform: translateX(-280px);
+    }
+}
+
+.progress-cover {
+    position: absolute;
+    left: 0;
+    top: 0;
+    z-index: 1000;
+    width: 100%;
+    height: 100%;
+    background-color: #e9ecef;
+}
+
+.progress-cover-move {
+    animation: progressCoverMove 40s linear;
+    animation-fill-mode: forwards;
+}
+
+@keyframes progressCoverMove {
+    0% {
+        transform: translateX(0);
+    }
+    20% {
+        transform: translateX(330px);
+    }
+    40% {
+        transform: translateX(380px);
+    }
+    100% {
+        transform: translateX(420px);
+    }
+}

+ 1 - 1
web/building_saas/fee_rates/fee_rate.html

@@ -5,7 +5,7 @@
         <div class="form-inline py-1">
             <label  class="mx-2" >使用费率文件:<span id="feeRateFileName">费率1</span>(<label class="a_color" id="pop-lv">与<span id="projectCount">3</span> 个单位工程同步</label>)
                 <a class="btn btn-sm ml-1" href="#" data-toggle="modal" data-target="#change-lv" id="changFeeRateFile"><i class="fa fa-exchange"></i> 选择其他</a>
-                <a class="btn btn-sm ml-1" href="#" data-toggle="modal" id="saveAs" data-target="#copy-lv"><i class="fa fa-files-o"></i> 另存单独用</a></label>
+                <a class="btn btn-sm ml-1" href="#" data-toggle="modal" id="save-as" data-target="#copy-lv"><i class="fa fa-files-o"></i> 另存单独用</a></label>
         </div>
     </div>
     <div class="toolsbar_feeRate px-1">

+ 57 - 0
web/building_saas/glj/html/project_glj.html

@@ -24,6 +24,7 @@
         <ul class="nav flex-column nav-side mt-2 mb-2"  style="margin-top: 0px!important;">
             <li class="nav-item"><a class="nav-link active" href="#" id="ALL">所有工料机</a></li>
             <li class="nav-item"><a class="nav-link" href="javascript:void(0)" id="ZGCL">暂估材料</a></li>
+            <li class="nav-item"><a class="nav-link" href="javascript:void(0)" id="PBCL">评标材料</a></li>
         </ul>
       </div>
 
@@ -320,4 +321,60 @@
             </div>
         </div>
     </div>
+</div>
+
+<!--暂估材料弹出 从人材机汇总中选择-->
+<div class="modal fade" id="selectPGLJ" data-backdrop="static">
+  <div class="modal-dialog modal-lg" 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">&times;</span>
+              </button>
+          </div>
+          <div class="modal-body" style="padding: 0px">
+              <div class="btn-toolbar" style="margin: 10px">
+                  <div class="btn-group btn-group-sm mr-2">
+                      <button type="button" class="btn btn-outline-primary pglj_sel_check_btn">全选</button>
+                      <button type="button" class="btn btn-outline-primary pglj_sel_check_btn">全选主材</button>
+                      <button type="button" class="btn btn-outline-primary pglj_sel_check_btn">全选设备</button>
+                  </div>
+                  <!--前面3个按钮 有选中状态,出现 取消 按钮-->
+                  <button type="button" class="btn btn-outline-danger btn-sm mr-2 pglj_sel_check_btn">取消</button>
+                  <div class="input-group input-group-sm mr-2" style="width:200px">
+                      <input type="text" class="form-control form-control-sm"  placeholder="查找" id="pglj_sel_input" value="">
+                      <div class="input-group-append">
+                          <button class="btn btn-outline-primary btn-sm" id="pglj_sel_btn_find" type="button"><i class="fa fa-search" aria-hidden="true"></i></button>
+                      </div>
+                  </div>
+                  <div class="input-group input-group-sm">
+                      <div class="form-check form-check-inline">
+                          <input class="form-check-input pglj_sel_input" type="checkbox" id="pglj_sel_all" value="0">
+                          <label class="form-check-label" for="pglj_sel_all">所有</label>
+                      </div>
+                      <div class="form-check form-check-inline">
+                          <input class="form-check-input pglj_sel_input pglj_sel_input_other" type="checkbox" id="pglj_sel_material" checked value="2">
+                          <label class="form-check-label" for="pglj_sel_material">材</label>
+                      </div>
+                      <div class="form-check form-check-inline">
+                          <input class="form-check-input pglj_sel_input pglj_sel_input_other" type="checkbox" id="pglj_sel_main" checked value="4">
+                          <label class="form-check-label" for="pglj_sel_main">主材</label>
+                      </div>
+                      <div class="form-check form-check-inline">
+                          <input class="form-check-input pglj_sel_input pglj_sel_input_other" type="checkbox" id="pglj_sel_eqp" checked value="5">
+                          <label class="form-check-label" for="pglj_sel_eqp">设备</label>
+                      </div>
+                  </div>
+              </div>
+              <div class="modal-auto-height" style="margin-top:10px; border: 1px solid #ccc">
+                  <div  id="pglj_from_sheet" style="overflow: hidden;height: 100%"></div>
+              </div>
+          </div>
+          <div class="modal-footer">
+              <button type="button" class="btn btn-primary" id="pglj_sel_confirm" data-dismiss="modal">确定</button>
+              <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+          </div>
+      </div>
+  </div>
 </div>

+ 1 - 1
web/building_saas/js/global.js

@@ -20,7 +20,7 @@ function autoFlashHeight(){
     $(".main-data-full").height($(window).height()-headerHeight-toolsbarHeight-1);
     $(".main-data-full-fl").height($(window).height()-headerHeight-toolsbarHeight-37);
     $(".main-data-full-feeRate").height($(window).height()-headerHeight-78);
-    $(".main-data-full-tender").height($(window).height()-headerHeight-btntoolsbarHeight-10);
+    $(".main-data-full-tender").height($(window).height()-headerHeight-btntoolsbarHeight-10-30);  // 30是留个水平进度条位置
     $(".main-data-not").height($(window).height()-headerHeight-1);
     $(".main-data-side-search").height($(window).height()-headerHeight-toolsbarHeight-64);
     $(".side-content").height($(window).height()-headerHeight );

+ 103 - 0
web/building_saas/main/html/main.html

@@ -46,6 +46,7 @@
         const G_SHOW_BLOCK_LIB = false;
         const markReadProjectIDs = JSON.parse('<%- markReadProjectIDs %>');
         const VERSION = '<%- version %>';
+        const CUR_BOQ_TYPE = '<%- boqType %>';
     </script>
 </head>
 
@@ -90,6 +91,13 @@
                             <a id="uploadGld" class="dropdown-item" href="#import" data-toggle="modal" data-target="#import">导入广联达算量Excel清单</a>
                         </div>-->
                     </span>
+                    <% if(overWriteUrl === '/web/over_write/js/quanguo_2018.js' && boqType) { %>
+                    <span data-toggle="modal" data-target="#interface-export-modal">
+                        <span id="open-export-modal" class="btn btn-light btn-sm" data-toggle="tooltip" data-original-title="数据接口" data-placement="bottom">
+                            <a href="javascript:void(0);"><i class="fa fa-code-fork"></i></a>
+                        </span>
+                    </span>
+                    <% }%>
                    <!-- <a href="javascript:void(0)" class="btn btn-light btn-sm" id="insertRation" data-toggle="tooltip" data-placement="bottom" data-original-title="插入定额"><i class="fa fa-sign-in" aria-hidden="true"></i></a>-->
                     <a href="javascript:void(0)" class="btn btn-light btn-sm" id="delete" data-toggle="tooltip" data-placement="bottom" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
                     <a href="javascript:void(0)" class="btn btn-light btn-sm" id="upLevel" data-toggle="tooltip" data-placement="bottom" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
@@ -1900,6 +1908,96 @@
             </div>
         </div>
     </div>
+    <!--进度条-->
+    <div class="modal fade" id="progress" data-backdrop="static" style="z-index: 1080;">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 id="progress-title" class="modal-title"></h5>
+                </div>
+                <div class="modal-body">
+                    <h5 id="progress-content" class="my-3"></h5>
+                    <div class="progress mb-3">
+                        <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0"
+                            aria-valuemax="100">
+                            <div class="progress-move progress-bar-striped"></div>
+                            <div class="progress-cover" id="progressCover"></div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!--弹出 数据接口导出-->
+    <div class="modal fade" id="interface-export-modal" data-backdrop="static">
+        <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">&times;</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group row">
+                        <label class="mb-0 pr-0 col-2 col-form-label col-form-label-sm">地区选择</label>
+                        <div class="col-6 row">
+                            <div class="col-6 pr-0">
+                                <select class="form-control  form-control-sm" id="export-parent-area">
+                                </select>
+                            </div>
+                            <div class="col-6 pr-0">
+                                <select class="form-control  form-control-sm" id="export-sub-area">
+                                </select>
+                            </div>
+                        </div>
+                    </div>
+                    <!--招标-->
+                    <% if (boqType == 1) { %>
+                    <div class="form-group">
+                        <label class="mb-0">招标接口文件导出</label>
+                        <small class="form-text text-muted">招标接口文件由以下2个文件组成,其内容必须保持一致,因此建议一次性全部导出。</small>
+                    </div>
+                    <div class="form-group">
+                        <div class="form-check ml-4">
+                            <input class="form-check-input" type="checkbox" value="1" id="ex-bid" checked>
+                            <label class="form-check-label" for="ex-bid">
+                                招标工程量清单
+                            </label>
+                            <small class="form-text text-muted">招标工程量清单数据文件,用于投标人投标报价</small>
+                        </div>
+                        <div class="form-check ml-4">
+                            <input class="form-check-input" type="checkbox" value="3" id="ex-control">
+                            <label class="form-check-label" for="ex-control">
+                                招标控制价
+                            </label>
+                            <small class="form-text text-muted">包含完整组价数据的招标控制价文件</small>
+                        </div>
+                    </div>
+                    <% } else { %>
+                    <!--投标-->
+                    <div class="form-group">
+                        <label class="mb-0">投标接口文件导出</label>
+                    </div>
+                    <div class="form-group">
+                        <div class="form-check ml-4">
+                            <input class="form-check-input" type="checkbox" value="2" id="ex-tender" checked>
+                            <label class="form-check-label" for="ex-tender">
+                                投标文件
+                            </label>
+                            <small class="form-text text-muted">投标工程数据文件</small>
+                        </div>
+                    </div>
+                    <% } %>
+                </div>
+                <div class="modal-footer">
+                    <a id="interface-export-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>
     <%include ../../../common/components/share/index.html %>
     <img src="/web/dest/css/img/folder_open.png" id="folder_open_pic" style="display: none">
     <img src="/web/dest/css/img/folder_close.png" id="folder_close_pic" style="display: none">
@@ -1925,6 +2023,7 @@
     <script src="/lib/spreadjs/views/plugins/gc.spread.views.gridlayout.10.0.0.min.js" type="text/javascript"></script>
     <script src="/lib/js-xlsx/xlsx.core.min.js"></script>
     <script src="/lib/lz-string/lz-string.min.js"></script>
+    <script src="/lib/fileSaver/FileSaver.min.js"></script>
     <script type="text/javascript" src="/lib/jspdf/jspdf.min.js"></script>
     <!-- inject:js -->
     <!--<script type="text/javascript" src="/test/tmp_data/test_ration_calc/ration_calc_base.js"></script>-->
@@ -2056,6 +2155,10 @@
     <script type="text/javascript" src="/web/building_saas/main/js/views/divide_view.js"></script>
     <script type="text/javascript" src="/public/web/storageUtil.js"></script>
     <script type="text/javascript" src="/web/building_saas/report/js/rpt_jspdf.js"></script>
+    <script src="/web/building_saas/standard_interface/config.js"></script>;
+    <script src="/web/building_saas/standard_interface/index.js"></script>;
+    <script src="/web/building_saas/standard_interface/export/base.js"></script>;
+    <script src="/web/building_saas/standard_interface/export/view.js"></script>;
 
     <!-- endinject -->
 

+ 5 - 0
web/building_saas/main/js/controllers/block_controller.js

@@ -578,6 +578,9 @@ let BlockController = {
             delete tem_ration.referenceRationID;//删除关联的主定额信息
             delete tem_ration.__v;
             delete tem_ration.sourceType;
+            delete tem_ration.quantityCoe;
+            delete tem_ration.rationQuantityCoe;
+            delete tem_ration.tenderQuantity;
 
             tem_ration.projectID = projectObj.project.ID();
             tem_ration.ID = uuid.v1();
@@ -604,6 +607,8 @@ let BlockController = {
             delete  temData.quantity_details;
             delete  temData.__v;
             delete  temData.sourceType;
+            delete  temData.quantityCoe;
+            delete  temData.rationQuantityCoe;
 
             temData.projectID = projectObj.project.ID();
             let newID = uuid.v1(); //新的清单ID

+ 1 - 1
web/building_saas/main/js/main.js

@@ -17,13 +17,13 @@ $(function () {
         $(e.relatedTarget.hash).removeClass('active');
         $("#subItems").addClass('active');
         $(gljOprObj.activeTab).addClass('active');
-        subObj.initGljSubTab();
         autoFlashHeight();
         projectObj.refreshMainSpread();
         billsGuidance.refreshWorkBook();
         billsLibObj.refreshBillsSpread();
         billsLibObj.refreshBillsRelaSpread();
         rationLibObj.refreshSpread();
+        subObj.initGljSubTab();
         if($('#linkJSCX').hasClass('active'))
             calcProgramObj.refreshCalcProgram(projectObj.project.mainTree.selected, 2);
         //autoFlashHeight 里已经包含了 refreshSubSpread();

+ 3 - 1
web/building_saas/main/js/models/cache_tree.js

@@ -190,7 +190,9 @@ var cacheTree = {
         Node.prototype.serialNo = function () {
             return this.tree.items.indexOf(this);
         }
-
+        Node.prototype.row = function () {
+            return this.serialNo() + 1;
+        }
         Node.prototype.addChild = function (node) {
             var preSibling = this.children.length === 0 ? null : this.children[this.children.length - 1];
             node.parent = this;

+ 9 - 5
web/building_saas/main/js/models/calc_program.js

@@ -2252,6 +2252,14 @@ class CalcProgram {
         return me.getTotalFee(baseNodes, excludeNodes, tender);
     };
 
+    initGljPriceTenderCoe (){
+        if (projectObj.project.property.tenderSetting && projectObj.project.property.tenderSetting.gljPriceTenderCoe
+            && (projectObj.project.property.tenderSetting.gljPriceTenderCoe != 1)){
+            projectObj.project.property.tenderSetting.gljPriceTenderCoe = 1;    // 修改缓存值,用于计算
+            projectObj.project.property.needRestoreGgljPriceTenderCoe = true;   // 做个标记,告诉回调函数
+        }
+    };
+
     // 反向调价需初始化调价树、缓存数据等
     initReverseTenderDatas (){
         for(let node of tender_obj.tenderTree.items){
@@ -2265,11 +2273,7 @@ class CalcProgram {
             this.clearTenderCache(node);
         };
         // 反向调价时人材机单价调整系数要归1:因为既可以调量又可以调价,以哪个为基准进行反调?过于复杂,仅以通用的调量逻辑为基准即可满足需求。
-        if (projectObj.project.property.tenderSetting && projectObj.project.property.tenderSetting.gljPriceTenderCoe
-            && (projectObj.project.property.tenderSetting.gljPriceTenderCoe != 1)){
-            projectObj.project.property.tenderSetting.gljPriceTenderCoe = 1;    // 修改缓存值,用于计算
-            projectObj.project.property.needRestoreGgljPriceTenderCoe = true;   // 做个标记,告诉回调函数
-        }
+        this.initGljPriceTenderCoe();
     };
 
     // 反向调价

+ 8 - 5
web/building_saas/main/js/models/main_consts.js

@@ -20,6 +20,7 @@ const ModuleNames = {
     projectInfo: 'project_info',
     divide_setting:'divide_setting',
     evaluate_list:'evaluate_list',
+    bid_evaluation_list:'bid_evaluation_list'
 };
 
 let gljType = gljUtil.gljType;
@@ -140,10 +141,10 @@ const volumePriceMaps = {
     5: "量设"
 };
 const rationType = {
-    ration: 1,
-    volumePrice: 2,
-    gljRation: 3,
-    install:4
+    ration: commonConstants.RationType.RATION,
+    volumePrice: commonConstants.RationType.VOLUME_PRICE,
+    gljRation: commonConstants.RationType.GLJ_RATION,
+    install: commonConstants.RationType.INSTALL
 };
 const rationPrefix = { //定额前缀,补/借
     none: '',
@@ -315,7 +316,9 @@ const filterType = {
     JGCL:'8',
     ZGCL:'9',
     SCHZ:'10',
-    ZYCL:'11'
+    ZYCL:'11',
+    AMAE:'12',
+    PBCL:'13'
 };
 const filterTypeArray = ['1','2','3','4','5'];
 

+ 49 - 3
web/building_saas/main/js/models/project.js

@@ -30,7 +30,44 @@ var PROJECT = {
             });
 
         };
-        tools.doAfterLoad = function(result, callback){
+        // isInit: 是否是初始化,导出接口用到getData,但是非初始化,多次调用getData不会覆盖原有缓存数据
+        tools.doAfterLoad = function(result, isInit, callback){
+            var counter;
+            //必须要先load ProjectInfo的信息
+            let projectInfoModule = result.find(data => data.moduleName === ModuleNames.projectInfo);
+            if (projectInfoModule) {
+                me._project.projectInfo = projectInfoModule.data;
+            }
+            result.forEach(function(item){
+                if (item.moduleName !== ModuleNames.projectInfo) {
+                    if (me.modules[item.moduleName]){
+                        me.modules[item.moduleName].loadData(item.data, isInit);
+                    } else if (item.moduleName === me.projCounter) {
+                        counter = item.data;
+                    } else if (item.moduleName === me.projSetting) {
+                        me._project.projSetting = item.data;
+                        me._project.projSetting.moduleName = me.projSetting;
+                    } else if(item.moduleName === ModuleNames.projectGLJ){
+                        me._project.projectGLJ.loadToCache(item.data);
+                    }
+                }
+            });
+            for (module in counter) {
+                if (me.modules[module]) {
+                    me.modules[module].setMaxID(counter[module]);
+                }
+            }
+            if (isInit) {
+                projectInfoObj.showProjectInfo(me._project.projectInfo);
+            }
+            me._project.property = me._project.projectInfo.property;
+            me._project.loadMainTree();
+            //me.test(result[0].data[0]);
+            if (callback) {
+                callback(0);
+            }
+        };
+        /* tools.doAfterLoad = function(result, callback){
             var counter;
             //必须要先load ProjectInfo的信息
             let projectInfoModule = result.find(data => data.moduleName === ModuleNames.projectInfo);
@@ -63,7 +100,7 @@ var PROJECT = {
             if (callback) {
                 callback(0);
             }
-        };
+        }; */
         tools.eachItem=function(item){
             if (me.modules[item.moduleName]){
                 me.modules[item.moduleName].doAfterUpdate(item.err, item.data);
@@ -104,6 +141,7 @@ var PROJECT = {
             this.calcBase = calcBase;
             this.divide_setting = new DivideSetting(this);
             this.evaluate_list = new EvaluateList(this);
+            this.bid_evaluation_list = new BidEvaluationList(this);
             // this.masterField = {ration: 'billsItemID', volumePrice: 'billsItemID'};
             this.masterField = {ration: 'billsItemID'};
         };
@@ -238,7 +276,8 @@ var PROJECT = {
                 timeout: 50000,
                 success: function (result) {
                     if (!result.error) {
-                        tools.doAfterLoad(result.data, callback);
+                        const isInit = true;
+                        tools.doAfterLoad(result.data, isInit, callback);
                         // for test calc
                         //tools.doAfterLoad([{moduleName: 'bills', data: BillsData}, {'moduleName': 'ration', data: DrawingData}], callback);
                     } else {
@@ -251,6 +290,13 @@ var PROJECT = {
                 }
             });
         };
+        project.prototype.loadDataSync = async function () {
+            const data = await ajaxPost('/project/getDataForInterface', {user_id: tools._userID, project_id: tools._ID});
+            if (data) {
+                const isInit = false;
+                tools.doAfterLoad(data, isInit);
+            }
+        };
 
         project.prototype.beginUpdate = function(operation){
             if (tools.updateLock === 0){

+ 12 - 2
web/building_saas/main/js/models/project_glj.js

@@ -74,7 +74,6 @@ ProjectGLJ.prototype.synLoadData = function () {
 
 ProjectGLJ.prototype.loadToCache = function (data) {
     this.datas = data;
-    projectObj.project.projectGLJ = this;
 }
 
 
@@ -332,7 +331,7 @@ ProjectGLJ.prototype.updateCalcMaterials = async function(projectGLJs,value) {//
     for(let dm of dataMaps){
       let glj = dm.glj;
       glj.unit_price[updateField] = value;
-      if(dm.data.ext) gljUtil.setProperty(glj.unit_price,);
+      if(dm.data.ext) gljUtil.setProperty(glj.unit_price,dm.data.ext);
       if(updateField == 'calcMaterial' && value ==0){///标记为0即删除材料计算标记,要删除其下挂的原价计算,运费计算,定额计算,消耗量重新计算
         calcQ = true;
         let connect_key = gljUtil.getIndex(glj);
@@ -1805,3 +1804,14 @@ class EvaluateList {
       this.datas = datas;
   };
 }
+
+class BidEvaluationList {
+  constructor (project) {
+      this.project = project;
+      this.datas = [];
+      project.registerModule(ModuleNames.bid_evaluation_list, this);
+  };
+  loadData (datas) {
+      this.datas = datas;
+  };
+}

+ 2 - 17
web/building_saas/main/js/models/ration.js

@@ -303,28 +303,13 @@ var Ration = {
                 }
             }
         }
-
-        ration.prototype.getChangePosUpdateData = function (ration1, ration2) {
-            var updateData = [];
-            updateData.push({updateType: 'ut_update', updateData: this.getTempRationData(ration1.ID, ration1.billsItemID, ration2.serialNo)});
-            updateData.push({updateType: 'ut_update', updateData: this.getTempRationData(ration2.ID, ration2.billsItemID, ration1.serialNo)});
-            return updateData;
-        };
-        ration.prototype.changePos = function (ration1, ration2) {
-            var updateData = this.getChangePosUpdateData(ration1, ration2);
-            this.project.pushNow('insertRation', [this.getSourceType()], [updateData]);
-
-            var preSerialNo = ration1.serialNo;
-            ration1.serialNo = ration2.serialNo;
-            ration2.serialNo = preSerialNo;
-        };
         ration.prototype.changeMultiPos = function (baseRation, rations) {
             const updateData = [];
             let tempSerialNo = baseRation.serialNo;
             rations.forEach(ration => {
                 updateData.push({
                     updateType: 'ut_update',
-                    updateData: this.getTempRationData(ration.ID, ration.billsItemID, tempSerialNo)
+                    updateData: { ID: ration.ID, serialNo: tempSerialNo, projectID: this.project.ID()}
                 });
                 let orgSerialNo = ration.serialNo;
                 ration.serialNo = tempSerialNo;
@@ -332,7 +317,7 @@ var Ration = {
             });
             updateData.push({
                 updateType: 'ut_update',
-                updateData: this.getTempRationData(baseRation.ID, baseRation.billsItemID, tempSerialNo)
+                updateData: { ID: baseRation.ID, serialNo: tempSerialNo, projectID: this.project.ID()}
             });
             baseRation.serialNo = tempSerialNo;
             this.project.pushNow('insertRation', [this.getSourceType()], [updateData]);

+ 2 - 1
web/building_saas/main/js/views/calc_program_view.js

@@ -52,7 +52,8 @@ let calcProgramObj = {
             case 1:
                 // doNothing
                 break;
-            case 2, 4:
+            case 2:
+            case 4:
                 projectObj.project.calcProgram.innerCalc(treeNode, []);
                 projectObj.project.calcProgram.rationMap = null;
                 delete treeNode.changed;

+ 25 - 5
web/building_saas/main/js/views/config_material_view.js

@@ -494,7 +494,7 @@ let configMaterialObj = {
                     alert("当前材料市场价已被锁定,修改请先返回人材机汇总界面解除锁定。");
                     return me.showBidMaterialDatas();
                 }
-                return projectObj.project.projectGLJ.updatePrice(bid,dataCode,value,'rg', null,projectGljObject.refreshViewsData);
+                return projectObj.project.projectGLJ.updatePrice(bid,dataCode,value,'rg',projectGljObject.refreshViewsData);
             }
             value =  scMathUtil.roundToString(value,getDecimal('glj.unitPrice'));
         }
@@ -570,7 +570,7 @@ let configMaterialObj = {
         if(dataCode == 'marketPrice'){
             dataCode = 'market_price';
             if(evaluate.is_related){//关联的情况下,直接修改工料机价格
-                return projectObj.project.projectGLJ.updatePrice(evaluate,dataCode,value,'rg',null,projectGljObject.refreshViewsData);
+                return projectObj.project.projectGLJ.updatePrice(evaluate,dataCode,value,'rg',projectGljObject.refreshViewsData);
             }
         }
         if(value && dataCode === 'quantity'){//修改数量需做4舍5入
@@ -743,7 +743,7 @@ let configMaterialObj = {
                             materialAdjustObj.checkedDefualtOption("glj_sel_input");
                             $("#selectFromGLJ").modal('show');
                         }else {
-                            materialAdjustObj.checkedDefualtOption("pglj_sel_input");
+                           // materialAdjustObj.checkedDefualtOption("pglj_sel_input"); 暂时没有这个
                             $("#selectPGLJ").modal('show');
                         }
                     }
@@ -864,9 +864,9 @@ let pgljSelObj={
     getSheetDatas(glj){
         let projectGLJ = projectObj.project.projectGLJ;
         let materialIdList = projectGLJ.datas.constData.materialIdList;
-        let data = materialAdjustObj.getCommonObject(glj);
-        console.log(projectGljObject.displayType);
+        let data = this.getCommonObject(glj);
         data.select = projectGljObject.displayType == filterType.ZGCL?glj.is_evaluate:glj.is_eval_material;
+        if(!gljUtil.isDef(data.select)) data.select = 0; 
         data.from = glj;
         data.short_name = projectGLJ.getShortNameByID(glj.type);
         // 只有材料才显示是否暂估
@@ -875,6 +875,25 @@ let pgljSelObj={
         data.is_evaluate = glj.is_evaluate;
         return data;
     },
+    getCommonObject:function (glj) {
+      let data ={
+          select:0,
+          id:glj.id,
+          code:glj.code,
+          name:glj.name,
+          specs:glj.specs,
+          unit:glj.unit,
+          type:glj.type,
+          quantity:glj.quantity,
+          supply:glj.supply,
+          originPlace:glj.originPlace,
+          vender:glj.vender,
+          brand:glj.brand,
+          remark:glj.remark
+      };
+      gljOprObj.setGLJPrice(data,glj);
+      return data;
+  },
     checkByType:function (type) {
         for(let d of this.datas){
             d.select = 0;
@@ -890,6 +909,7 @@ let pgljSelObj={
                     break;
                 case "取消":
                     d.select = projectGljObject.displayType == filterType.ZGCL?d.is_evaluate:d.is_eval_material;
+                    if(!gljUtil.isDef(d.select)) d.select = 0;
                     break;
             }
         }

+ 1 - 1
web/building_saas/main/js/views/divide_view.js

@@ -828,7 +828,7 @@ let divideObj = {
         parentMap[divide.ParentID] = [];
       }else{
         divide = this.calcBills(divide);
-        updateDatas.push({ID:divide.ID,type:'update',doc:{fees:divide.fees}});  
+        if(divide.fees) updateDatas.push({ID:divide.ID,type:'update',doc:{fees:divide.fees}});  
       }
     }
     for(let c of divideSetting.divideList){

+ 1 - 1
web/building_saas/main/js/views/fee_rate_view.js

@@ -1062,7 +1062,7 @@ $(function(){
         //if(newVal&&newVal!=feeRateFile.libID)  feeRateObject.changeFeeRateStandard(newVal);
     });
 
-    $('#saveAs').bind('click', function (){
+    $('#save-as').bind('click', function (){
         var feeRateFile = projectObj.project.FeeRate.getActivateFeeRate();
         $('#copyFeeRateName').val(feeRateFile.name+'副本');
         $('#valid_name').val(feeRateFile.name+'副本');

+ 2 - 0
web/building_saas/main/js/views/glj_col.js

@@ -42,6 +42,8 @@ let gljCol = {
             {headerName: "定额价", headerWidth: 70, dataCode: "basePrice", hAlign: "right", dataType: "Number",validator:"number", spanRows: [2]},
             {headerName: "预算价", headerWidth: 70, dataCode: "marketPrice", hAlign: "right", dataType: "Number",validator:"number",spanRows: [2]},
             {headerName: "规格", headerWidth: 120, dataCode: "specs", hAlign: "left", dataType: "String",cellType:'tipsCell',spanRows: [2]},
+            {headerName: "暂估", headerWidth: 45, dataCode: "is_evaluate", hAlign: "center", dataType: "String",cellType:'checkBox',spanRows: [2]},
+            {headerName: "评标材料", headerWidth: 35, dataCode: "is_eval_material", dataType: "String",cellType: "checkBox",spanRows: [2]},
             {headerName: "主要\n材料", headerWidth: 45, dataCode: "is_main_material", hAlign: "center", dataType: "String",cellType:'checkBox',spanRows: [2]},
             {headerName: "新工料机", headerWidth: 50, dataCode: "is_add", hAlign: "center", dataType: "String",cellType:'checkBox',spanRows: [2]},
             {headerName: "不调价", headerWidth: 55, dataCode: "is_adjust_price", dataType: "String",cellType: "checkBox",spanRows: [2]},

+ 13 - 4
web/building_saas/main/js/views/project_glj_view.js

@@ -374,7 +374,7 @@ projectGljObject={
             return false;
         }
 
-        if(isPaste == false &&(dataCode=='is_adjust_price'||dataCode=='is_evaluate'||dataCode=='is_main_material')){//除了粘贴,拖动填充等操作,其它的都不能编辑
+        if(isPaste == false &&(dataCode=='is_adjust_price'||dataCode=='is_evaluate'||dataCode=='is_main_material'||dataCode == 'is_eval_material')){//除了粘贴,拖动填充等操作,其它的都不能编辑
             return false;
         }
         if(dataCode=='basePrice'||dataCode=='marketPrice'||dataCode=='supply'){//有组成物时,市场单价、定额价、供货方式不能修改
@@ -702,7 +702,8 @@ projectGljObject={
     },
     initSheetViews(){
       let me = projectGljObject;
-      if(me.displayType == filterType.ZGCL ){
+      if(me.displayType == filterType.ZGCL || me.displayType == filterType.PBCL){
+        configMaterialObj.setNavLinkText(me.displayType);
         $('#project-glj-main').hide();
         $('#config_material').show();
       } else { 
@@ -712,7 +713,7 @@ projectGljObject={
     },
     refreshViewsData:function(){
       let me = projectGljObject;
-      if(me.displayType == filterType.ZGCL) return configMaterialObj.refreshSheetDatas();
+      if(me.displayType == filterType.ZGCL|| me.displayType == filterType.PBCL) return configMaterialObj.refreshSheetDatas();
       me.refreshDataSheet();
     },
     refreshDataSheet:function (refresh) {//refresh = true 的时候不用更新表头信息
@@ -900,6 +901,9 @@ projectGljObject={
         if (materialIdList.indexOf(glj.type) >= 0) {
             data.is_evaluate = glj.is_evaluate;
         }
+         //是“材料”、“主材”、“设备”时显示评标材料
+        if (materialIdList.indexOf(glj.type) >= 0||glj.type == gljType.MAIN_MATERIAL || glj.type == gljType.EQUIPMENT) data.is_eval_material = glj.is_eval_material ? glj.is_eval_material : 0;
+
         if(glj.materialCoe !== null && glj.materialCoe !==undefined){
             data.materialCoe =  scMathUtil.roundForObj(glj.materialCoe,getDecimal("material"));
         }
@@ -1069,8 +1073,12 @@ projectGljObject={
             if(dataCode === 'supply_quantity'){//修改数量需做4舍5入
                 value=  scMathUtil.roundForObj(value,getDecimal('glj.quantity'));
             }
-            if(dataCode === 'is_evaluate'||dataCode === 'is_adjust_price'||dataCode === 'is_main_material'){
+            if(dataCode === 'is_evaluate'||dataCode === 'is_adjust_price'||dataCode === 'is_main_material'||dataCode == 'is_eval_material'){
                 value = value == true?1:0;
+                if(dataCode === 'is_evaluate'|| dataCode == 'is_eval_material'){
+                  configMaterialObj.updateConfigMaterial(dataCode,value,recode);//暂估材料,主要材料等新需求
+                  return
+              }
             }
             if(dataCode === 'materialType' && (value == null || value=="")){//删除三材类别时,清空三材系数
                 value = null;
@@ -1645,6 +1653,7 @@ $(function () {
         me.displayType = filterType[this.id];
         me.initSheetViews();
         me.refreshViewsData();
+        loadProjectGljSize();
     });
 
     $("#mixRatio-nav").on('shown.bs.tab', function () {

+ 3 - 3
web/building_saas/main/js/views/project_view.js

@@ -878,7 +878,7 @@ var projectObj = {
         this.project.loadDatas(function (err) {
             projectInfoObj.refreshTotalPriceSpan(); // 工具栏中的总造价信息
             let mTime = +new Date();
-            projectInfoObj.showProjectInfo(that.project.projectInfo);
+            //projectInfoObj.showProjectInfo(that.project.projectInfo);
             //快速列设置
             if(!colSettingObj.getVisible('itemCharacterText')){
                 switchTznrHtml(true);
@@ -2211,10 +2211,11 @@ const throttleDownLevel = _.throttle(() => {
     const mainTreeNodes = controller.getValidNodesWithinSelection();
     const billsNodes = mainTreeNodes.map(node => node.source);
     const orgParent = mainTreeNodes[0].parent;
+    const preNode = mainTreeNodes[0].preSibling;//降级前的前一节点变成降级后新的父节点,要重新计算
     project.Bills.multiDownLevelBills(project.Bills.tree, billsNodes);
     controller.multiDownLevel(mainTreeNodes);
     controller.refreshTreeNode([orgParent, ...mainTreeNodes]);
-    projectObj.converseCalculateBills(orgParent);
+    projectObj.converseCalculateBills(preNode);
 }, throttleTime);
 $('#downLevel').click(throttleDownLevel);
 $('#insertRation').click(function () {
@@ -2234,7 +2235,6 @@ const throttleUpMove = _.throttle(() => {
         project.Bills.multiUpMoveBills(project.Bills.tree, sourceNodes);
         controller.multiUpMove(mainTreeNodes);
     } else if (firstNode.sourceType === commonConstants.SourceType.RATION) {
-        //project.Ration.changePos(selected.source, selected.preSibling.source);
         project.Ration.changeMultiPos(mainTreeNodes[0].preSibling.source, sourceNodes);
         controller.multiUpMove(mainTreeNodes);
     };

+ 30 - 23
web/building_saas/main/js/views/tender_price_view.js

@@ -226,12 +226,17 @@ let tender_obj={
 
     },
     onEnterCell : function (sender,args) {
-        let me = tender_obj, row = args.row, col = args.col;
+        let me = tender_obj, row = args.row, col = args.col, lock = false;
         if ([7, 8].includes(col)){                                  // 目标单价、目标合价
             let treeNode = me.tenderTree.items[row];
-            if (calcTools.isCalcBaseBill(treeNode)){                    // 公式结点只读
+            if (calcTools.isCalcBaseBill(treeNode))                   // 公式结点只读
+                lock = true;
+
+            if ((col = 7) && calcTools.isParentBill(treeNode))
+                lock = true;
+
+            if (lock)
                 me.tenderSheet.getCell(row, col).locked(true);
-            };
         }
     },
     updateChildrenValue:function (node,dataCode,value,datas,nodes) {
@@ -460,26 +465,6 @@ $(function () {
         //console.log($('#gljPriceTenderCoe').val()) ;
     });
 
-    $('#cleanTender').bind('click',function () {
-        let me = tender_obj,datas = [];
-        for(let node of me.tenderTree.items){
-            let tem_updateData = {type:node.sourceType,data:{}};
-            me.cleanTargetPrice(tem_updateData,node);
-            me.cleanTenderCoe(tem_updateData,node);
-            me.cleanTenderPrice(tem_updateData,node);
-            if(!_.isEmpty(tem_updateData.data)){//如果需要更新
-                tem_updateData.data.ID = node.data.ID;
-                datas.push(tem_updateData);
-            }
-        }
-        datas.push({type:ModuleNames.project,data:{'ID' : projectObj.project.ID(),'property.tenderSetting.gljPriceTenderCoe':1}});//恢复人材机单价调整系数为1。
-        // datas.push({type:ModuleNames.project,data:{'ID' : projectObj.project.ID(),'property.hasTender': false}});
-        me.updateTenderData(datas,function () {
-            me.refreshTenderTreeByDatas(datas);
-        });
-        $('#calcTender').trigger('click');
-    });
-
     $('#calcPriceOption').change(function(){
         let me = tender_obj;
 
@@ -527,6 +512,28 @@ $(function () {
         projectObj.project.calcProgram.doTenderCalc(callback);
     });
 
+    $('#cleanTender').bind('click',function () {
+        let me = tender_obj,datas = [];
+        for(let node of me.tenderTree.items){
+            let tem_updateData = {type:node.sourceType,data:{}};
+            me.cleanTargetPrice(tem_updateData,node);
+            me.cleanTenderCoe(tem_updateData,node);
+            me.cleanTenderPrice(tem_updateData,node);
+            if(!_.isEmpty(tem_updateData.data)){//如果需要更新
+                tem_updateData.data.ID = node.data.ID;
+                datas.push(tem_updateData);
+            }
+        };
+        projectObj.project.calcProgram.initGljPriceTenderCoe();
+        datas.push({type:ModuleNames.project,data:{'ID' : projectObj.project.ID(),'property.tenderSetting.gljPriceTenderCoe':1}});//恢复人材机单价调整系数为1。
+        // datas.push({type:ModuleNames.project,data:{'ID' : projectObj.project.ID(),'property.hasTender': false}});
+        me.updateTenderData(datas,function () {
+            // me.refreshTenderTreeByDatas(datas);
+            $('#calcTender').trigger('click');
+        });
+
+    });
+
     $('#cbShowTenderFields').on('click', function () {
         let showFields = $('#cbShowTenderFields').prop("checked");
         projectObj.project.saveProperty('tenderSetting.showTenderFields', showFields);

+ 0 - 1
web/building_saas/pm/html/project-management.html

@@ -714,7 +714,6 @@
 <script type="text/javascript" src="/public/web/syntax-detection.js"></script>
 <script src="/web/building_saas/js/global.js"></script>
 <script src="/public/web/uuid.js"></script>
-<script src="/public/web/PerfectLoad.js"></script>
 <script src="/public/web/date_util.js"></script>
 <script src="/web/building_saas/pm/js/pm_tree.js"></script>
 <script src="/public/web/id_tree.js"></script>

+ 3 - 3
web/building_saas/pm/js/pm_newMain.js

@@ -37,7 +37,7 @@ let regions = [];
 
 const { 
     ValuationType: { BUDGET, BOQ },
-    BOQType: { TENDER, BIDDER },
+    BOQType: { BID_INVITATION, BID_SUBMISSION },
 } = window.commonConstants;
 const { 
     similarEqual,
@@ -2497,11 +2497,11 @@ function initProjectOptSet($target) {
                         <label for="staticEmail" class="col-auto col-form-label col-form-label-sm">清单类型</label>
                         <div class="col">
                             <div class="custom-control custom-radio custom-control-inline">
-                                <input type="radio" value="${TENDER}" name="boq-type-input" checked id="boq-type-tender" class="custom-control-input">
+                                <input type="radio" value="${BID_INVITATION}" name="boq-type-input" checked id="boq-type-tender" class="custom-control-input">
                                 <label class="custom-control-label" for="boq-type-tender">招标</label>
                             </div>
                             <div class="custom-control custom-radio custom-control-inline">
-                                <input type="radio" value="${BIDDER}" name="boq-type-input" id="boq-type-bidder" class="custom-control-input">
+                                <input type="radio" value="${BID_SUBMISSION}" name="boq-type-input" id="boq-type-bidder" class="custom-control-input">
                                 <label class="custom-control-label" checked for="boq-type-bidder">投标</label>
                             </div>
                         </div>

+ 1 - 1
web/building_saas/pm/js/pm_share.js

@@ -867,7 +867,7 @@ const pmShare = (function () {
         }
         for (let node of items) {
             if (node.children.length === 0) {//project
-                rst.push({ ID: node.data.ID, fileHierarchyName: getFileHierarchyName(node), valuationType: node.data.property.valuationType || '' })
+                rst.push({ ID: node.data.ID, fileHierarchyName: getFileHierarchyName(node), valuationType: node.data.property && node.data.property.valuationType || '' })
             }
         }
         return rst;

+ 67 - 0
web/building_saas/standard_interface/config.js

@@ -0,0 +1,67 @@
+/*
+ * @Descripttion: 地区配置
+ * @Author: vian
+ * @Date: 2020-08-20 16:03:23
+ */
+
+const INTERFACE_CONFIG = (() => {
+  const {
+    EXPORT_KIND: {
+      BID_INVITATION,
+      BID_SUBMISSION,
+      CONTROL
+    }
+  } = window.commonConstants;
+
+  // 注意:相同地区的导入导出接口js文件名称应相同。eg: 安徽马鞍山导出:在export目录下:anhui_maanshan.js; 在import目录下:anhui_maanshan.js;
+  return {
+    '安徽@马鞍山': {
+      scriptName: 'anhui_maanshan.js',
+      fileSuffix: {
+        [BID_INVITATION]: '.MASGLZB',
+        [BID_SUBMISSION]: '.MASGLTB',
+        [CONTROL]: '.MASGLKZJ',
+      },
+    },
+    '安徽@淮北': {
+      scriptName: 'anhui_maanshan.js',
+      fileSuffix: {
+        [BID_INVITATION]: '.HBGLZB',
+        [BID_SUBMISSION]: '.HBGLTB',
+        [CONTROL]: '.HBGLKZJ',
+      },
+    },
+    '安徽@芜湖': {
+      scriptName: 'anhui_maanshan.js',
+      fileSuffix: {
+        [BID_INVITATION]: '.WHGLZB',
+        [BID_SUBMISSION]: '.WHGLTB',
+        [CONTROL]: '.WHGLKZJ',
+      },
+    },
+    '安徽@黄山': {
+      scriptName: 'anhui_maanshan.js',
+      fileSuffix: {
+        [BID_INVITATION]: '.HSGLZB',
+        [BID_SUBMISSION]: '.HSGLTB',
+        [CONTROL]: '.HSGLKZJ',
+      },
+    },
+    '安徽@宣城': {
+      scriptName: 'anhui_maanshan.js',
+      fileSuffix: {
+        [BID_INVITATION]: '.XCGLZB',
+        [BID_SUBMISSION]: '.XCGLTB',
+        [CONTROL]: '.XCGLKZJ',
+      },
+    },
+    '广东@中山': {
+      scriptName: 'guangdong_zhongshan.js',
+      fileSuffix: {
+        [BID_INVITATION]: '.XML',
+        [BID_SUBMISSION]: '.XML',
+        [CONTROL]: '.XML',
+      },
+    }
+  };
+})()

+ 695 - 0
web/building_saas/standard_interface/export/anhui_maanshan.js

@@ -0,0 +1,695 @@
+/*
+ * @Descripttion: 安徽-马鞍山 接口
+ * @Author: vian
+ * @Date: 2020-08-17 15:40:08
+ */
+
+// INTERFACE_EXPORT =,必须这么写,这样才能在导出时动态加载脚本后,覆盖前端代码
+INTERFACE_EXPORT = (() => {
+    'use strict';
+
+    /**
+     * 
+     * @param {String} areaKey - 地区标识,如:'安徽@马鞍山',有些地区的接口只是取值上有不同,共有一个接口脚本, 需要通过地区标识确定一些特殊处理
+     * @param {Number} exportKind - 导出类型,招标、投标、控制价
+     * @param {Object} projectData - 项目表数据:{ 建设项目Data, children: [单位工程...] }
+     * @param {Object} tenderDetailMap - 单位工程ID与getData接口数据(projectObj.project的结构)的映射。
+     * @return {Promise<Array>} - 返回的数据结构必须按照规定:[{ data, exportKind, fileName }],参考web\building_saas\standard_interface\index.js中的注释说明
+     */
+    async function entry(areaKey, exportKind, projectData, tenderDetailMap) {
+        const {
+            CONFIG: { TYPE },
+            UTIL: {
+                getValueByKey,
+                getHan,
+                getFee,
+            },
+            Element,
+        } = INTERFACE_EXPORT_BASE;
+
+        const {
+            EXPORT_KIND: { BID_INVITATION, BID_SUBMISSION, CONTROL },
+            fixedFlag,
+            SourceType,
+            RationType,
+        } = window.commonConstants
+
+        const GljType = gljUtil.gljType;
+
+        const { isEmptyVal, isDef } = window.commonUtil;
+
+        const isBidInvitation = exportKind === BID_INVITATION; // 是否是招标
+        const isBidSubmission = exportKind === BID_SUBMISSION; // 是否是投标
+        const isControl = exportKind === CONTROL; // 是否是控制价
+
+        // 节点定义--------------------------------
+
+        // 建设项目基本信息
+        function JingJiBiao(projectName, information) {
+            const taxModeMap = {
+                '一般计税': '1',
+                '简易计税': '2',
+            };
+            const czzt = {
+                [BID_INVITATION]: '招标',
+                [BID_SUBMISSION]: '投标',
+                [CONTROL]: '招标控制',
+            };
+            const attrs = [
+                { name: 'Xmbh', value: getValueByKey(information, 'projNum') }, // 项目编号
+                { name: 'Xmmc', value: projectName }, // 项目名称
+                { name: 'Bzlx', value: '清单' }, // 项目编制类型
+                { name: 'Jjyj', value: '【18清单】2018部颁清单计价依据' }, // 计价依据
+                { name: 'Xmqzzh', value: getValueByKey(information, 'startAndChainages') }, // 项目起止桩号
+                { name: 'Jsdw', value: getValueByKey(information, 'constructingUnits') }, // 建设单位
+                { name: 'Czzt', value: czzt[exportKind] }, // 操作状态:招标、投标、招标控制,即导出接口时,所选的文件类型
+                { name: 'Jsfs', value: taxModeMap[getValueByKey(information, 'taxMode')] || '1', type: TYPE.INT }, // 计税方式,默认1。1=一般计税 2=简易计税
+                { name: 'Version', value: '1.0' },
+            ];
+            Element.call(this, 'JingJiBiao', attrs);
+        }
+
+        const subArea = areaKey.split('@')[1];
+
+        // 招标信息
+        function ZhaoBiaoXx(information) {
+            const attrs = [
+                { name: 'Zbr', value: getValueByKey(information, 'tendereeName') }, // 招标人
+                { name: 'Zxr', value: getValueByKey(information, 'costConsultant') }, // 造价咨询人
+                { name: 'ZbrDb', value: getValueByKey(information, 'tenderAuthorizer') }, // 招标人法定代表人或其授权人
+                { name: 'ZxrDb', value: getValueByKey(information, 'consultantAuthorizer') }, // 造价咨询人法定代表人或其授权人
+                { name: 'Bzr', value: getValueByKey(information, 'tenderCompiler') }, // 编制人
+                { name: 'Fhr', value: getValueByKey(information, 'tenderExaminer') }, // 复核人
+                { name: 'BzTime', value: getValueByKey(information, 'compilationTime'), type: TYPE.DATE }, // 编制时间
+                { name: 'FhTime', value: getValueByKey(information, 'reviewTime'), type: TYPE.DATE }, // 复核时间
+            ];
+            // 额外字段
+            const extraMap = {
+                '淮北': [
+                    { name: 'ZbrNssbh', value: getValueByKey(information, 'tendereeTaxpayerIdentificationNo') }, // 招标人纳税识别号
+                    { name: 'ZxrNssbh', value: getValueByKey(information, 'costConsultantTaxpayerIdentificationNo') }, // 造价咨询人纳税识别号
+                    { name: 'ZbrDbSfzh', value: getValueByKey(information, 'tenderAuthorizerIDNo') }, // 招标人法定代表人或其授权人身份证号
+                    { name: 'ZxrNssbh', value: getValueByKey(information, 'consultantAuthorizerTaxpayerIdentificationNo') }, // 造价咨询人法定代表或其授权人纳税识别号
+                ]
+            };
+            if (extraMap[subArea]) {
+                attrs.push(...extraMap[subArea]);
+            }
+            Element.call(this, 'ZhaoBiaoXx', attrs);
+        }
+
+        // 招标控制价信息
+        function ZhaoBiaoKzXx(information, totalCost) {
+            const attrs = [
+                { name: 'Zbr', value: getValueByKey(information, 'tendereeName') }, // 招标人
+                { name: 'Zxr', value: getValueByKey(information, 'costConsultant') }, // 造价咨询人
+                { name: 'ZbrDb', value: getValueByKey(information, 'tenderAuthorizer') }, // 招标人法定代表人或其授权人
+                { name: 'ZxrDb', value: getValueByKey(information, 'consultantAuthorizer') }, // 造价咨询人法定代表人或其授权人
+                { name: 'Bzr', value: getValueByKey(information, 'tenderCompiler') }, // 编制人
+                { name: 'Fhr', value: getValueByKey(information, 'tenderExaminer') }, // 复核人
+                { name: 'BzTime', value: getValueByKey(information, 'compilationTime'), type: TYPE.DATE }, // 编制时间
+                { name: 'FhTime', value: getValueByKey(information, 'reviewTime'), type: TYPE.DATE }, // 复核时间
+                { name: 'Zbkzj', value: totalCost, type: TYPE.DECIMAL }, // 控制价总价(元),取“投标报价”的金额。
+            ];
+            // 额外字段
+            const extraMap = {
+                '淮北': [
+                    { name: 'ZbrNssbh', value: getValueByKey(information, 'tendereeTaxpayerIdentificationNo') }, // 招标人纳税识别号
+                    { name: 'ZxrNssbh', value: getValueByKey(information, 'costConsultantTaxpayerIdentificationNo') }, // 造价咨询人纳税识别号
+                    { name: 'ZbrDbSfzh', value: getValueByKey(information, 'tenderAuthorizerIDNo') }, // 招标人法定代表人或其授权人身份证号
+                    { name: 'ZxrNssbh', value: getValueByKey(information, 'consultantAuthorizerTaxpayerIdentificationNo') }, // 造价咨询人法定代表或其授权人纳税识别号
+                ]
+            };
+            if (extraMap[subArea]) {
+                attrs.push(...extraMap[subArea]);
+            }
+            Element.call(this, 'ZhaoBiaoKzXx', attrs);
+        }
+
+        // 招标控制价信息
+        function TouBiaoXx(information, totalCost) {
+            const attrs = [
+                { name: 'Zbr', value: getValueByKey(information, 'tendereeName') }, // 招标人
+                { name: 'Tbr', value: getValueByKey(information, 'bidder') }, // 投标人
+                { name: 'TbrDb', value: getValueByKey(information, 'bidderAuthorizer') }, // 投标人法定代表或其授权
+                { name: 'Bzr', value: getValueByKey(information, 'tenderCompiler') }, // 编制人
+                { name: 'BzTime', value: getValueByKey(information, 'compilationTime'), type: TYPE.DATE }, // 编制时间
+                { name: 'Tbzj', value: totalCost, type: TYPE.DECIMAL }, // 控制价总价(元),取“投标报价”的金额。
+            ];
+            // 额外字段
+            const extraMap = {
+                '淮北': [
+                    { name: 'ZbrNssbh', value: getValueByKey(information, 'tendereeTaxpayerIdentificationNo') }, // 招标人纳税识别号
+                    { name: 'TbrNssbh', value: getValueByKey(information, 'bidderTaxpayerIdentificationNo') }, // 投标人纳税识别号
+                    { name: 'TbrDbsfzh', value: getValueByKey(information, 'tenderAuthorizerIDNo') }, // 投标人法定代表或其授权人身份证号
+                ]
+            };
+            if (extraMap[subArea]) {
+                attrs.push(...extraMap[subArea]);
+            }
+            Element.call(this, 'TouBiaoXx', attrs);
+        }
+
+        // 单项工程信息,因项目管理中无“单项工程”这一层,从单位工程的工程特征信息中拼凑出来
+        function Dxgcxx(code, name) {
+            const attrs = [
+                { name: 'Dxgcbh', value: code }, // 单项工程编号
+                { name: 'Dxgcmc', value: name }, // 单项工程名称
+            ];
+            Element.call(this, 'Dxgcxx', attrs);
+        }
+
+        // 单位工程信息
+        function Dwgcxx(tenderName, feature) {
+            const attrs = [
+                { name: 'Dwgcbh', value: getValueByKey(feature, '') }, // 单位工程编号
+                { name: 'Dwgcmc', value: tenderName }, // 单位工程名称
+            ];
+            Element.call(this, 'Dwgcxx', attrs);
+        }
+
+        // 取费信息(费率信息)
+        function Qfxx() {
+            Element.call(this, 'Qfxx');
+        }
+
+        // 计价费率表
+        function JjFlb() {
+            Element.call(this, 'JjFlb');
+        }
+
+        // 计价费率表明细,造价书费率页面左侧最底层数据
+        function JjFlbMx(rootItem, item) {
+            // 费率明细名称-编码映射表
+            const FeeRateCodeMap = {
+                '冬季施工增加费': 'DJF',
+                '雨季施工增加费': 'YJF',
+                '夜间施工增加费': 'YEF',
+                '工地转移费': 'ZYF',
+                '高原施工增加费': 'GYF',
+                '风沙地区增加费': 'FSF',
+                '沿海地区增加费': 'YHF',
+                '行车干扰增加费': 'XCF',
+                '施工辅助费': 'SFF',
+                '养老保险费': 'YLF',
+                '失业保险费': 'SYF',
+                '医疗保险费': 'YBF',
+                '住房公积金': 'ZFF',
+                '工伤保险费': 'GSF',
+                '基本费用': 'JBF',
+                '主副食运费补贴': 'YFF',
+                '职工探亲补贴': 'TQF',
+                '职工取暖补贴': 'QNF',
+                '财务费用': 'CWF',
+                '利润': 'LR',
+                '税金': 'SJ',
+            };
+            // 费率工程名称-取费类别映射表
+            const FeeRateTypeMap = {
+                '土方': 1,
+                '石方': 3,
+                '运输': 2,
+                '路面': 4,
+                '隧道': 11,
+                '构造物I': 5,
+                '构造物I(不计冬)': 16,
+                '构造物II': 6,
+                '构造物III(桥梁)': 9,
+                '构造物III(除桥以外不计雨)': 8,
+                '技术复杂大桥': 10,
+                '钢材及钢结构(桥梁)': 12,
+                '钢材及钢结构(除桥以外不计夜)': 13,
+                '费率为0': 17,
+                '路面(不计雨)': 4,
+                '构造物I(不计雨)': 16,
+                '构造物III(除桥以外)': 8,
+                '钢材及钢结构(除桥以外)': 13,
+                '设备': 15,
+                '量价': 14,
+            };
+            const rate = isEmptyVal(item.rate) ? '100' : item.rate; // 为空时输出=100,为0时输出=0
+            const attrs = [
+                { name: 'Bm', value: FeeRateCodeMap[item.name] }, // 编码
+                { name: 'Name', value: item.name }, // 名称
+                { name: 'Fl', value: rate, type: TYPE.DECIMAL }, // 费率
+                { name: 'Qflb', value: FeeRateTypeMap[rootItem.name], type: TYPE.INT }, // 取费类别
+            ];
+            Element.call(this, 'JjFlbMx', attrs);
+        }
+
+        // 计价费率项
+        function JjFlx() {
+            Element.call(this, 'JjFlx');
+        }
+
+        // 计价费率项明细,造价书费率页面右侧最顶层数据
+        function JjFlxMx(item) {
+            // 编码取名称拼音首字母
+            const allHanName = getHan(item.name || '');
+            const code = pinyinUtil.getFirstLetter(allHanName).toLowerCase();
+            let value;
+            if (isDef(item.value)) {
+                value = item.value;
+            } else {
+                const selected = item.optionList.find(item => item.selected);
+                value = selected.name;
+            }
+            const attrs = [
+                { name: 'Bm', value: code }, // 编码
+                { name: 'Mc', value: item.name }, // 名称
+                { name: 'ShuZhi', value: value }, // 数值
+            ];
+            Element.call(this, 'JjFlxMx', attrs);
+        }
+
+        // 清单项目
+        function QdXm() {
+            Element.call(this, 'QdXm');
+        }
+        // 标题类别:1=100~700清单合计,2=暂估价合计,3=清单不包含暂估价合计,4=计日工,5=暂列金额(不含计日工总额)),6=投标报价,0=其他
+        const BillsTitleType = {
+            [fixedFlag.ONE_SEVEN_BILLS]: '1',
+            [fixedFlag.PROVISIONAL_TOTAL]: '2',
+            [fixedFlag.BILLS_TOTAL_WT_PROV]: '3',
+            [fixedFlag.DAYWORK_LABOR]: '4',
+            [fixedFlag.PROVISIONAL]: '5',
+            [fixedFlag.TOTAL_COST]: '6',
+        };
+        // 清单标题 造价书的第一层数据。
+        function QdBt(node) {
+            const row = node.row();
+            const fee = isBidInvitation ? '0' : getFee(node.data.fees, 'common.tenderTotalFee')
+            const attrs = [
+                { name: 'Xh', value: row, type: TYPE.INT }, // 序号
+                { name: 'Bm', value: node.data.code }, // 编码
+                { name: 'Name', value: node.data.name }, // 名称
+                { name: 'Je', value: fee, type: TYPE.DECIMAL }, // 金额
+                { name: 'Code', value: `F${row}` }, // 行引用
+                { name: 'Jsgs', value: node.data.calcBase }, // 计算基数
+                { name: 'Lb', value: BillsTitleType[node.getFlag()], type: TYPE.INT }, // 类别
+            ];
+            Element.call(this, 'QdBt', attrs);
+        }
+
+        // 清单明细 (只有100-700章清单标题输出)
+        function QdMx(node) {
+            const row = node.row();
+            const attrs = [
+                { name: 'Xh', value: row, type: TYPE.INT }, // 序号
+                { name: 'Qdbm', value: node.data.code }, // 编码
+                { name: 'Name', value: node.data.name }, // 名称
+                { name: 'Dw', value: node.data.unit }, // 单位
+                { name: 'Sl', value: node.data.quantity, type: TYPE.DECIMAL }, // 工程量
+                { name: 'Sl2', value: '0', type: TYPE.DECIMAL }, // 工程量2
+                { name: 'Rgf', value: isBidInvitation ? '0' : getFee(node.data.fees, 'labour.tenderTotalFee'), type: TYPE.DECIMAL }, // 人工费
+                { name: 'Clf', value: isBidInvitation ? '0' : getFee(node.data.fees, 'material.tenderTotalFee'), type: TYPE.DECIMAL }, // 材料费
+                { name: 'Jxf', value: isBidInvitation ? '0' : getFee(node.data.fees, 'machine.tenderTotalFee'), type: TYPE.DECIMAL }, // 机械费
+                { name: 'Sbf', value: isBidInvitation ? '0' : getFee(node.data.fees, 'equipment.tenderTotalFee'), type: TYPE.DECIMAL }, // 设备费
+                { name: 'Csf', value: isBidInvitation ? '0' : getFee(node.data.fees, 'measure.tenderTotalFee'), type: TYPE.DECIMAL }, // 措施费
+                { name: 'Glf', value: isBidInvitation ? '0' : getFee(node.data.fees, 'manage.tenderTotalFee'), type: TYPE.DECIMAL }, // 企业管理费
+                { name: 'Gf', value: isBidInvitation ? '0' : getFee(node.data.fees, 'force.tenderTotalFee'), type: TYPE.DECIMAL }, // 规费
+                { name: 'Lr', value: isBidInvitation ? '0' : getFee(node.data.fees, 'profit.tenderTotalFee'), type: TYPE.DECIMAL }, // 利润
+                { name: 'Sj', value: isBidInvitation ? '0' : getFee(node.data.fees, 'tax.tenderTotalFee'), type: TYPE.DECIMAL }, // 税金
+                { name: 'Zhdj', value: isBidInvitation ? '0' : getFee(node.data.fees, 'common.tenderUnitFee'), type: TYPE.DECIMAL }, // 单价
+                { name: 'Zhhj', value: isBidInvitation ? '0' : getFee(node.data.fees, 'common.tenderTotalFee'), type: TYPE.DECIMAL }, // 合价
+                { name: 'Zgj', value: isBidInvitation ? '0' : getFee(node.data.fees, 'estimate.tenderTotalFee'), type: TYPE.DECIMAL }, // 暂估价
+                { name: 'Iszg', value: node.data.specialProvisional === '专业工程', type: TYPE.BOOL }, // 是否暂定 如果专项暂定列选择了“专业工程”,则输出true,否则是false。
+                { name: 'Djfx', value: !!node.data.unitPriceAnalysis, type: TYPE.BOOL }, // 单价分析
+                { name: 'Jsgs', value: node.data.calcBase }, // 计算基数
+                { name: 'Bl', value: '' }, // 变量
+                { name: 'Bz', value: node.data.remark }, // 备注
+            ];
+            Element.call(this, 'QdMx', attrs);
+        }
+
+        // 定额组价
+        function Qdxdezj() {
+            Element.call(this, 'Qdxdezj');
+        }
+
+        // 定额租价明细 招标文件不输出
+        function QdxdezjMx(ration) {
+            const typeMap = {
+                [RationType.RATION]: '1',
+                [RationType.VOLUME_PRICE + GljType.LABOUR]: '2',
+                [RationType.VOLUME_PRICE + GljType.GENERAL_MATERIAL]: '3',
+                [RationType.VOLUME_PRICE + GljType.GENERAL_MACHINE]: '4',
+                [RationType.VOLUME_PRICE + GljType.EQUIPMENT]: '2',
+            };
+            const typeKey = (ration.type || '') + (ration.subType || '');
+            const type = typeMap[typeKey];
+            const attrs = [
+                { name: 'Debm', value: ration.code }, // 编码
+                { name: 'Mc', value: ration.name }, // 名称
+                { name: 'Dw', value: ration.unit }, // 单位
+                { name: 'Sl', value: ration.tenderQuantity }, // 工程量
+                { name: 'Dj', value: getFee(ration.fees, 'common.tenderUnitFee'), type: TYPE.DECIMAL }, // 单价
+                { name: 'Hj', value: getFee(ration.fees, 'common.tenderTotalFee'), type: TYPE.DECIMAL }, // 合价
+                { name: 'Rgf', value: getFee(ration.fees, 'labour.tenderTotalFee'), type: TYPE.DECIMAL }, // 人工费
+                { name: 'Clf', value: getFee(ration.fees, 'material.tenderTotalFee'), type: TYPE.DECIMAL }, // 材料费
+                { name: 'Jxf', value: getFee(ration.fees, 'machine.tenderTotalFee'), type: TYPE.DECIMAL }, // 机械费
+                { name: 'Sbf', value: getFee(ration.fees, 'equipment.tenderTotalFee'), type: TYPE.DECIMAL }, // 设备费
+                { name: 'Csf', value: getFee(ration.fees, 'measure.tenderTotalFee'), type: TYPE.DECIMAL }, // 措施费
+                { name: 'Glf', value: getFee(ration.fees, 'manage.tenderTotalFee'), type: TYPE.DECIMAL }, // 企业管理费
+                { name: 'Gf', value: getFee(ration.fees, 'force.tenderTotalFee'), type: TYPE.DECIMAL }, // 规费
+                { name: 'Lr', value: getFee(ration.fees, 'profit.tenderTotalFee'), type: TYPE.DECIMAL }, // 利润
+                { name: 'Sj', value: getFee(ration.fees, 'tax.tenderTotalFee'), type: TYPE.DECIMAL }, // 税金
+                { name: 'Delb', value: type, type: TYPE.INT }, // 取定额/量价/设备的类别。(1=普通定额,2=人工,3=材料,4=机械,5=设备)
+                { name: 'Iszd', value: 'false', type: TYPE.BOOL }, // 暂时,全部取fals
+            ];
+            Element.call(this, 'QdxdezjMx', attrs);
+        }
+
+        // 定额人材机含量
+        function Qdxdercjhl() {
+            Element.call(this, 'Qdxdercjhl');
+        }
+
+        // 定额人材机含量明细
+        function QdxdercjhlMx(rcjID, quantity) {
+            const attrs = [
+                { name: 'RcjId', value: rcjID }, // 人材机资源ID
+                { name: 'Sl', value: quantity, type: TYPE.DECIMAL }, // 消耗量
+            ]
+            Element.call(this, 'QdxdercjhlMx', attrs);
+        }
+
+        // 清单人材机含量
+        function Qdxrcjhl() {
+            Element.call(this, 'Qdxrcjhl');
+        }
+
+        // 清单人材机含量明细
+        function QdxrcjhlMx(item) {
+            const attrs = [
+                { name: 'RcjId', value: item.rcjID }, // 人材机资源ID
+                { name: 'Rcjhl', value: item.contain, type: TYPE.DECIMAL }, // 含量:取叶子清单下所有的人材机的含量,=各定额下的人材机总消耗量之和,再除以清单工程量
+                { name: 'Rcjhj', value: item.totalPrice, type: TYPE.DECIMAL }, // 合价:人材机含量*人材机预算价
+                { name: 'Zgjbz', value: item.isEvaluate, type: TYPE.BOOL }, // 是否暂估,根据资源ID,读取工料机汇总界面对应的“是否暂估”
+                { name: 'Zcbz', value: 'false', type: TYPE.BOOL }, // 主材标记,公路上无此概念,默认输出=fales
+                { name: 'Zyclbz', value: item.isMainMaterial, type: TYPE.BOOL }, // 根据资源ID,读取工料机汇总界面对应的“主要材料”
+            ]
+            Element.call(this, 'QdxrcjhlMx', attrs);
+        }
+
+        // 计日工
+        function Jrg() {
+            Element.call(this, 'Jrg');
+        }
+
+        // 计日工标题
+        function JrgBt(node) {
+            const typeMap = {
+                [fixedFlag.LABOUR_SERVICE]: '1',
+                [fixedFlag.MATERIAL]: '2',
+                [fixedFlag.CONSTRUCTION_MACHINE]: '3',
+            };
+            const attrs = [
+                { name: 'Name', value: node.data.name }, // 名称
+                { name: 'Je', value: isBidInvitation ? '0' : getFee(node.data.fees, 'common.tenderTotalFee'), type: TYPE.DECIMAL }, // 金额
+                { name: 'Lb', value: typeMap[node.getFlag()], type: TYPE.INT }, // 类别:1=劳务;2=材料;3=施工机械
+                { name: 'Bz', value: node.data.remark }, // 备注
+            ];
+            Element.call(this, 'JrgBt', attrs);
+        }
+
+        // 计日工明细
+        function JrgMx(node) {
+            const attrs = [
+                { name: 'Bh', value: node.data.code }, // 编码
+                { name: 'Name', value: node.data.name }, // 名称
+                { name: 'Dw', value: node.data.unit }, // 单位
+                { name: 'Sl', value: node.data.quantity }, // 工程量
+                { name: 'Dj', value: isBidInvitation ? '0' : getFee(node.data.fees, 'common.tenderTotalFee'), type: TYPE.DECIMAL }, // 单价
+                { name: 'Hj', value: isBidInvitation ? '0' : getFee(node.data.fees, 'common.tenderUnitFee'), type: TYPE.DECIMAL }, // 合价
+            ];
+            Element.call(this, 'JrgBJrgMx', attrs);
+        }
+
+        // 暂估材料表、评标材料表
+        function gljRefRoot(eleName) {
+            Element.call(this, eleName);
+        }
+
+        // 暂估材料表、评标材料表明细
+        function gljRefElement(eleName, glj) {
+            const attrs = [
+                { name: 'Xh', value: glj.seq }, // 序号
+                { name: 'RcjId', value: projectGLJIDToRcjID[glj.projectGLJID] }, // 资源ID
+                { name: 'Bm', value: glj.code }, // 编码
+                { name: 'Mc', value: glj.name }, // 名称
+                { name: 'Ggxh', value: glj.specs }, // 规格型号
+                { name: 'Dw', value: glj.unit }, // 单位
+                { name: 'Sl', value: BID_INVITATION ? '0' : glj.quantity, type: TYPE.DECIMAL }, // 工程量
+                { name: 'Dj', value: glj.marketPrice, type: TYPE.DECIMAL }, // 暂定价、单价
+                { name: 'Hj', value: BID_INVITATION ? '0' : glj.totalPrice, type: TYPE.DECIMAL }, // 合价
+                { name: 'Bz', value: glj.remark, type: TYPE.DECIMAL }, // 备注
+            ];
+            Element.call(this, eleName, attrs);
+        }
+        
+        // 人材机汇总
+        function Rcjhz() {
+            Element.call(this, 'Rcjhz');
+        }
+        
+        // 人材机汇总明细
+        function RcjhzMx(glj) {
+            const attrs = [
+                { name: 'RcjId', value: projectGLJIDToRcjID[glj.id] }, // 资源ID
+                { name: 'Bm', value: glj.code }, // 编码
+                { name: 'Mc', value: glj.name }, // 名称
+                { name: 'Ggxh', value: glj.specs }, // 规格型号
+                { name: 'Dw', value: glj.unit }, // 单位
+                { name: 'Dj', value: glj.priceInfo.tenderPrice, type: TYPE.DECIMAL }, // 预算价,调后
+                { name: 'Sl', value: glj.tenderQuantity, type: TYPE.DECIMAL }, // 总消耗量
+                { name: 'Hj', value: glj.totalPrice, type: TYPE.DECIMAL }, // 合价,人材料总消耗量*预算价
+                { name: 'Cd', value: '' }, // 产地
+                { name: 'Gycs', value: '' }, // 厂商
+                { name: 'Rcjlb', value: '', type: TYPE.INT }, // 人材机类型 1=人工;2=材料;3=机械
+                { name: 'Jgbz', value: 'false', type: TYPE.BOOL }, // 供材方
+                { name: 'Zyclbj', value: !!glj.is_main_material, type: TYPE.BOOL }, // 主要材料
+                { name: 'Zgjbz', value: !!glj.is_evaluate, type: TYPE.BOOL }, // 是否暂估
+                { name: 'Zcbz', value: 'false', type: TYPE.BOOL }, // 主材标记
+            ]
+            Element.call(this, 'RcjhzMx', attrs);
+        }
+        // 组装数据 --------------------------------------
+
+        // 组装建设项目数据
+        function setupConstruction(constructionData) {
+            const information = constructionData.property && constructionData.property.basicInformation || [];
+            const summaryInfo = constructionData.summaryInfo[constructionData.ID];
+            const jingJiBiao = new JingJiBiao(constructionData.name, information);
+            if (isBidInvitation) {
+                jingJiBiao.children.push(new ZhaoBiaoXx(information));
+            } else if (isControl) {
+                jingJiBiao.children.push(new ZhaoBiaoKzXx(information, summaryInfo.totalCost));
+            } else {
+                jingJiBiao.children.push(new TouBiaoXx(information, summaryInfo.totalCost));
+            }
+            // 将单位工程工程特征中,单项工程编号、名称相同的,插入到建设项目和分段(单位工程)的中间层。
+            const midLayerMap = {}; // 单项工程key(code@name)与单项工程节点映射
+            for (const tenderData of constructionData.children) {
+                const feature = tenderData.property && tenderData.property.projectFeature || [];
+                const midLayerCode = getValueByKey(feature, 'singleProjNo');
+                const midLayerName = getValueByKey(feature, 'singleProjName');
+                const midLayerKey = `${midLayerCode}@${midLayerName}`;
+                if (!midLayerMap[midLayerKey]) {
+                    jingJiBiao.children.push(midLayerMap[midLayerKey] = new Dxgcxx(midLayerCode, midLayerName));
+                }
+                midLayerMap[midLayerKey].children.push(setupTender(tenderData, feature));
+            }
+            const suffix = INTERFACE_CONFIG[areaKey]['fileSuffix'][exportKind];
+            return [{
+                data: jingJiBiao,
+                exportKind,
+                fileName: `${constructionData.name}${suffix}`
+            }];
+        }
+
+        // getData接口数据
+        let curDetail;
+
+        // 需要先设置项目人材机的关联ID(从1开始),因为定额人材机等一些节点需要用到这个关联ID
+        const projectGLJIDToRcjID = {}; // 项目人材机ID与新生成的整形ID映射
+        const projectGLJMap = {}; // 项目人材机与项目人材机数据映射
+
+        // 组装单位工程数据
+        function setupTender(tenderData, feature) {
+            curDetail = tenderDetailMap[tenderData.ID];
+            curDetail.projectGLJ.datas.gljList.forEach((glj, index) => {
+                projectGLJIDToRcjID[glj.id] = index + 1;
+                projectGLJMap[glj.id] = glj;
+            });
+            const dwgcxx = new Dwgcxx(tenderData.name, feature);
+            dwgcxx.children.push(
+                setupFeeRate(curDetail.FeeRate),
+                setupBills(curDetail.mainTree)
+            );
+            return dwgcxx;
+        }
+
+        // 组装费率数据
+        function setupFeeRate(feeRateDetail) {
+            const qfxx = new Qfxx();
+            const jjflb = new JjFlb();
+            // 费率界面左侧底层数据
+            let curRootItem;
+            feeRateDetail.datas.rates.forEach(item => {
+                if (!item.ParentID) {
+                    curRootItem = item;
+                } else if (!item.sum) { // 最底层
+                    jjflb.children.push(new JjFlbMx(curRootItem, item));
+                }
+            });
+            // 费率界面右侧顶层数据
+            const jjflx = new JjFlx();
+            const flxmxData = feeRateDetail
+                .getAllSubRates()
+                .filter(item => !item.isSub)
+                .map(item => new JjFlxMx(item));
+            jjflx.children.push(...flxmxData);
+
+            qfxx.children.push(jjflb, jjflx);
+            return qfxx;
+        }
+
+        // 组装清单数据
+        function setupBills(mainTree) {
+            const qdxm = new QdXm();
+            const qdbtData = mainTree.roots.map(node => new QdBt(node));
+            qdxm.children.push(...qdbtData);
+
+            mainTree.roots.forEach(node => {
+                const flag = node.getFlag();
+                const qdbt = new QdBt(node);
+                if (flag === fixedFlag.ONE_SEVEN_BILLS) { // 100章到700章清单需要输出详细数据
+                    qdbt.children.push(...setupSubBills(node.children));
+                } else if (flag === fixedFlag.DAYWORK_LABOR) {
+                    qdbt.children.push(setupDaywork(node.children));
+                }
+                qdxm.children.push(qdbt);
+            });
+
+            function setupSubBills(nodes) {
+                const rst = [];
+                nodes.forEach(node => {
+                    const qdmx = new QdMx(node);
+                    rst.push(qdmx);
+                    const subIsRations = node.children.length && !node.source.children.length;
+                    if (subIsRations) {
+                        qdmx.children.push(...setupRations(node.children));
+                        qdmx.children.push(...setupBillsContain(node.data));
+                    } else {
+                        qdmx.children.push(...setupSubBills(node.children));
+                    }
+                });
+                return rst;
+            }
+
+            function setupDaywork(nodes) {
+                const dayworkRoot = new Jrg();
+                const dayworkBT = nodes.map(node => {
+                    const bt = new JrgBt(node);
+                    const dayworkMX = node.children.map(child => new JrgMx(child));
+                    bt.children.push(...dayworkMX);
+                    return bt;
+                });
+                dayworkRoot.children.push(...dayworkBT);
+                return dayworkRoot;
+            }
+
+            return qdxm;
+        }
+
+        // 组装定额、定额人材机数据
+        function setupRations(rationNodes) {
+            // 招标文件不输出
+            if (isBidInvitation) {
+                return [];
+            }
+            const rationRoot = new Qdxdezj();
+            const rationEles = rationNodes.map(node => {
+                const rationEle = new QdxdezjMx(node.data);
+                // 定额人材机
+                const rationGLJList = curDetail.ration_glj.datas.filter(glj => glj.rationID === node.data.ID);
+                const rationGLJRoot = new Qdxdercjhl();
+                const rationGLJEles = rationGLJList.map(glj => new QdxdercjhlMx(projectGLJIDToRcjID[glj.projectGLJID], glj.tenderQuantity));
+                rationGLJRoot.children.push(...rationGLJEles);
+                rationEle.children.push(rationGLJRoot);
+                return rationEle;
+            });
+            rationRoot.children.push(...rationEles);
+            return [rationRoot];
+        }
+
+        // 组装清单人材机含量数据
+        function setupBillsContain(bills) {
+            if (isBidInvitation) {
+                return [];
+            }
+            // 读取清单下的人材机,先将各定额下的人材机汇总,相同的合并(相同的项目人材机ID)
+            const gljList = curDetail.ration_glj.datas.filter(glj => glj.billsItemID === bills.ID);
+            if (!gljList) {
+                return [];
+            }
+            const map = {};
+            gljList.forEach(glj => {
+                if (!map[glj.projectGLJID]) {
+                    const projectGLJ = projectGLJMap[glj.projectGLJID];
+                    map[glj.projectGLJID] = {
+                        rcjID: projectGLJIDToRcjID[glj.projectGLJID],
+                        totalQuantity: glj.tenderQuantity,
+                        price: glj.tenderPrice,
+                        isEvaluate: !!projectGLJ.is_evaluate,
+                        isMainMaterial: !!projectGLJ.is_main_material
+                    };
+                } else {
+                    map[glj.projectGLJID].totalQuantity = scMathUtil.roundTo(map[glj.projectGLJID].totalQuantity + glj.tenderQuantity, -6);
+                }
+            });
+            const containItems = Object
+                .values(map)
+                .map(glj => {
+                    const contain = glj.totalQuantity / (bills.quantity || 1);
+                    return {
+                        rcjID: glj.rcjID,
+                        contain: scMathUtil.roundTo(contain, -6), // 固定取6位
+                        totalPrice: scMathUtil.roundTo(contain * glj.price, -2), // 固定取2位
+                        isEvaluate: glj.isEvaluate,
+                        isMainMaterial: glj.isMainMaterial
+                    };
+                });
+            const root = new Qdxrcjhl();
+            root.children.push(...containItems.map(item => new QdxrcjhlMx(item)));
+            return [root]
+        }
+
+        // 组装人材机汇总相关表数据
+        function setupGLJList() {
+            // 暂估材料
+            const zgcl = new gljRefRoot('ZgCl');
+            const zgclData = curDetail.evaluateMaterialData.map(glj => new gljRefElement('ZgClMx', glj));
+            zgcl.children.push(...zgclData);
+            
+            // 评标材料
+            const jpcl = new gljRefRoot('JpCl');
+            const jpclData = curDetail.bidMaterialData.map(glj => new gljRefElement('JpClMx', glj));
+            jpcl.children.push(...jpclData);
+            if (BID_INVITATION) {
+                return [zgcl, jpcl];
+            }
+            // 人材机汇总 (招标不导出)
+            const rcjhz = new Rcjhz();
+        }
+
+        return setupConstruction(projectData);
+
+
+    }
+
+    return {
+        entry,
+    };
+})();

+ 787 - 0
web/building_saas/standard_interface/export/base.js

@@ -0,0 +1,787 @@
+/**
+ * @author Zhong
+ * @date 2019/6/20
+ * @version
+ */
+const INTERFACE_EXPORT_BASE = (() => {
+    'use strict';
+
+    const { hasValue, isHan } = window.commonUtil;
+
+    // 属性类型
+    const TYPE = {
+        DATE: 1, // 日期类型YYYY-MM-DD
+        DATE_TIME: 2, // 日期类型YYY-MM-DDTHH:mm:ss
+        INT: 3, // 整数类型
+        DECIMAL: 4, // 数值类型,不限制小数位数
+        NUM2: 5, // 数值类型2:最多两位小数
+        BOOL: 6 // 布尔型
+    };
+    // 需要特殊处理的属性类型默认空值(当一个值为undefined、null的时候,默认给赋什么值)
+    const DEFAULT_VALUE = {
+        [TYPE.INT]: '0',
+        [TYPE.DECIMAL]: '0',
+        [TYPE.NUM2]: '0',
+        [TYPE.BOOL]: 'false'
+    };
+    // 空白字符处理
+    const WHITE_SPACE = {
+        COLLAPSE: 1 // 移除所有空白字符(换行、回车、空格以及制表符会被替换为空格,开头和结尾的空格会被移除,而多个连续的空格会被缩减为一个单一的空格)
+    };
+    // 承包人材料调整类型
+    const ADJUST_TYPE = {
+        info: 'priceInfo', // 造价信息差额调整法
+        coe: 'priceCoe' // 价格指数调整法
+    };
+    // 加载数据间隔,减少服务器压力
+    const TIMEOUT_TIME = 400;
+    const {
+        GRANULARITY,
+        EXPORT_KIND
+    } = window.commonConstants;
+    /* const EXPORT_KIND_NAME = {
+        1: '招标',
+        2: '投标',
+        3: '控制价'
+    }; */
+    // 配置项
+    const CONFIG = Object.freeze({
+        TYPE,
+        WHITE_SPACE,
+        ADJUST_TYPE,
+        TIMEOUT_TIME,
+    });
+
+    // 缓存项 不需要的时候需要清空
+    const _cache = {
+        // 项目数据(不包含详细数据,项目管理数据)
+        projectData: {},
+        // 当前导出类型,默认投标
+        exportKind: EXPORT_KIND.BID_SUBMISSION,
+        // 记录拉取的单位工程项目详细数据,导出的时候,可能会导出多个文件,只有导出第一个文件的时候需要请求数据
+        tenderDetailMap: {}
+    };
+    // 返回缓存项
+    function getItem(key) {
+        return _cache[key] || null;
+    }
+    // 设置缓存项
+    function setItem(key, value) {
+        // 与原数据是同类型的数据才可设置成功
+        if (_cache[key] &&
+            Object.prototype.toString.call(_cache[key]) ===
+            Object.prototype.toString.call(value)) {
+            _cache[key] = value;
+        }
+    }
+    // 清空缓存项
+    function clear() {
+        _cache.projectData = {};
+        _cache.exportKind = EXPORT_KIND.BID_SUBMISSION;
+        _cache.tenderDetailMap = {};
+    }
+    const CACHE = Object.freeze({
+        getItem,
+        setItem,
+        clear
+    });
+
+
+    /*
+     * 定义不设置一个Node方法统一进入的原因:模板化比较直观,不分开定义节点的话,调用传参也很麻烦而且不直观。
+     * 一个节点对应一个构造方法,方便调整配置、方便其他版本开发、接手的人看起来更直观
+     * @param  {String}name 节点名
+     *         {Array}attrs 节点属性数据
+     * @return {void}
+     * */
+    function Element(name, attrs = []) {
+        this.name = name;
+        this.attrs = attrs;
+        check(this.attrs);
+        handleXMLEntity(this.attrs);
+        this.children = [];
+    }
+
+    /*
+     * xml字符实体的处理,这些特殊字符不处理会导致xml文件格式出错:""、<>、&
+     * 要先处理&amp
+     * */
+    const _xmlEntity = {
+        '&': '&amp;',
+        '\n': '&#xA;',
+        '"': '&quot;',
+        '\'': '&apos;',
+        '<': '&lt;',
+        '>': '&gt;'
+    };
+    // 对每个元素的所有属性值进行特殊字符处理
+    function handleXMLEntity(attrs) {
+        for (const attr of attrs) {
+            if (!attr.value) {
+                continue;
+            }
+            for (const [key, value] of Object.entries(_xmlEntity)) {
+                attr.value = attr.value.replace(new RegExp(key, 'g'), value);
+            }
+        }
+    }
+    // 获取处理实体字符后的数据
+    function getParsedData(arr) {
+        return arr.map(data => {
+            for (const [key, value] of Object.entries(_xmlEntity)) {
+                data = data.replace(new RegExp(key, 'g'), value);
+            }
+            return data;
+        });
+    }
+
+    // 获取Date类型默认值
+    function getDateTypeDefaultValue(date) {
+        const month = String(date.getMonth() + 1);
+        const formattedMonth = month.length === 1 ? `0${month}` : month;
+        const day = String(date.getDate());
+        const formattedDay = day.length === 1 ? `0${day}` : day;
+        return `${date.getFullYear()}-${formattedMonth}-${formattedDay}`;
+    }
+
+    /*
+     * 检查
+     * 创建节点时检查节点的数据(原本是用于自检,现在来处理默认值)
+     * @param {Array}datas 需要检查的属性数据
+     * @return {void}
+     * */
+    function check(datas) {
+        for (const data of datas) {
+            const isHasValue = hasValue(data.value);
+            // 值统一转换成String,并且处理各类型属性空值时的默认取值
+            data.value = !isHasValue
+                ? DEFAULT_VALUE[data.type]
+                    ? DEFAULT_VALUE[data.type]
+                    : ''
+                : String(data.value);
+            if (data.whiteSpace && data.whiteSpace === WHITE_SPACE.COLLAPSE) {  //处理空格相关
+                data.value = data.value.replace(/[\r\n\t]/g, ' ');
+                data.value = data.value.trim();
+                data.value = data.value.replace(/\s{1,}/g, ' ');
+            }
+            // 类型对应得值不正确时,赋类型对应默认值
+            if (!data.type) {
+                continue;
+            }
+            const dateReg = /([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8])))/;
+            if (data.type === TYPE.DATE && !dateReg.test(data.value)) {
+                data.value = getDateTypeDefaultValue(new Date());
+            } else if (data.type === TYPE.INT && !Number.isInteger(parseFloat(data.value))) {
+                data.value = DEFAULT_VALUE[TYPE.INT];
+            } else if (data.type === TYPE.DECIMAL && isNaN(parseFloat(data.value))) {
+                data.value = DEFAULT_VALUE[TYPE.DECIMAL];
+            } else if (data.type === TYPE.NUM2) {
+                data.value = DEFAULT_VALUE[TYPE.NUM2];
+            } else if (data.type === TYPE.BOOL && !['true', 'false'].includes(String(data.value))) {
+                data.value = DEFAULT_VALUE[TYPE.BOOL];
+            }
+        }
+    }
+    // 等待一段时间
+    function setTimeoutSync(handle, time) {
+        return new Promise((resolve, reject) => {
+            setTimeout(() => {
+                if (handle && typeof handle === 'function') {
+                    handle();
+                }
+                resolve();
+            }, time);
+        });
+    }
+
+    /*
+     * 将节点属性数据(attr数组)转换成简单key-value数据
+     * @param  {Object}ele 元素节点数据Element实例
+     * @return {Object}
+     * */
+    function getPlainAttrs(ele) {
+        const obj = {};
+        ele.attrs.forEach(attr => obj[attr.name] = attr.value);
+        return obj;
+    }
+    /*
+     * 从fees数组中获取相关费用
+     * @param  {Array}fees 费用数组
+     *         {String}feeFields 费用字段
+     * @return {Number}
+     * @example getFee(source.fees, 'common.totalFee')
+     * */
+    function getFee(fees, feeFields) {
+        if (!Array.isArray(fees)) {
+            return 0;
+        }
+        const fields = feeFields.split('.');
+        const fee = fees.find(data => data.fieldName === fields[0]);
+        if (!fee) {
+            return 0;
+        }
+        return fee[fields[1]] || 0;
+    }
+    // 获取节点的汇总价格
+    function getAggregateFee(nodes) {
+        const total = nodes.reduce((acc, node) => {
+            const price = getFee(node.data.fees, 'common.totalFee');
+            return acc += price;
+        }, 0);
+        return scMathUtil.roundTo(total, -2);
+    }
+    // 获取固定类别行的费用
+    function getFeeByFlag(items, flag, feeFields) {
+        const node = items.find(node => node.getFlag() === flag);
+        return node ? getFee(node.data.fees, feeFields) : '0';
+    }
+    /*
+     * 根据key获取对应的基本信息、工程特征数据
+     * @param  {Array}data
+     *         {String}key
+     * @return {String}
+     * @example getValueByKey(source.basicInformation, 'projectScale')
+     * */
+    function getValueByKey(items, key) {
+        for (const item of items) {
+            if (item.key === key) {
+                return item.value;
+            }
+            if (item.items && item.items.length) {
+                const value = getValueByKey(item.items, key);
+                if (value) {
+                    return value;
+                }
+            }
+        }
+        return '';
+    }
+    // 获取关联材料
+    function getRelGLJ(allGLJs, gljId) {
+        return allGLJs.find(glj => glj.id === gljId);
+    }
+    // 随机生成机器信息码:CPU信息;硬盘序列号;mac地址;
+    // 保存在localStorage中
+    function generateHardwareId() {
+        const hardwareCacheId = window.localStorage.getItem('hardwareId');
+        if (hardwareCacheId) {
+            return hardwareCacheId;
+        }
+        const charList = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
+            'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
+            'V', 'W', 'X', 'Y', 'Z'];
+        function generateCpuId() {
+            let id = '';
+            let count = 16;
+            while (count--) {
+                const randomIdx = parseInt(Math.random() * 16);
+                id += charList[randomIdx];
+            }
+            return id;
+        }
+        function generateDiskId() {
+            let id = '';
+            let count = 8;
+            while (count--) {
+                const randomIdx = parseInt(Math.random() * 36);
+                id += charList[randomIdx];
+            }
+            return id;
+        }
+        function generateMacId() {
+            const idList = [];
+            let outerCount = 6;
+            while (outerCount--) {
+                let tempId = '';
+                let innerCount = 2;
+                while (innerCount--) {
+                    const randomIdx = parseInt(Math.random() * 16);
+                    tempId += charList[randomIdx];
+                }
+                idList.push(tempId);
+            }
+            return idList.join('-');
+        }
+        const cpuId = generateCpuId();
+        const diskId = generateDiskId();
+        const macId = generateMacId();
+        const hardwareId = [cpuId, diskId, macId].join(';');
+        window.localStorage.setItem('hardwareId', hardwareId);
+        return hardwareId;
+    }
+    // 数组打平成对象
+    function arrayToObj(arr) {
+        const rst = {};
+        for (const data of arr) {
+            rst[data.key] = data.value;
+        }
+        return rst;
+    }
+    /*
+     * 检测层数是否有效
+     * @param  {Number}maxDepth(最大深度)
+     *         {Object}node(需要检测的清单树节点)
+     * @return {Boolean}
+     * */
+    function validDepth(maxDepth, node) {
+        const nodeDepth = node.depth();
+        const allNodes = node.getPosterity();
+        //检测相对深度
+        for (const n of allNodes) {
+            const relativeDepth = n.depth() - nodeDepth;
+            if (relativeDepth > maxDepth) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // 根据数据的NextSiblingID进行排序,返回排序后的数组
+    function sortByNext(datas) {
+        const target = [];
+        const temp = {};
+        for (const data of datas) {
+            temp[data.ID] = { me: data, next: null, prev: null };
+        }
+        for (const data of datas) {
+            const next = temp[data.NextSiblingID] || null;
+            temp[data.ID].next = next;
+            if (next) {
+                next.prev = temp[data.ID];
+            }
+        }
+        let first = null;
+        for (const data of datas) {
+            const me = temp[data.ID];
+            if (!me.prev) {
+                first = me;
+            }
+        }
+        if (!first) {
+            return datas;
+        }
+        while (first) {
+            target.push(first.me);
+            first = first.next;
+        }
+        return target;
+    }
+
+    /*
+     * 根据粒度获取项目(不包含详细数据)数据
+     * @param  {Number}granularity 导出粒度
+     *         {Object}requestForSummaryInfo 项目表级汇总字段(建设项目、单位工程汇总)
+     *         {Number}tenderID 单位工程ID
+     *         {String}userID 用户ID
+     * @return {Object} 返回的数据结构:{children: [{children: []}]} 最外层为建设项目,中间为单项工程,最底层为单位工程
+     * */
+    async function getProjectByGranularity(granularity, requestForSummaryInfo, tenderID, userID) {
+        let projectData = _cache.projectData;
+        // 没有数据,需要拉取
+        if (!Object.keys(projectData).length) {
+            projectData = await ajaxPost('/pm/api/getProjectByGranularity', { user_id: userID, tenderID, granularity, requestForSummaryInfo });
+            _cache.projectData = projectData;
+        }
+        return projectData;
+    }
+
+    /*
+     * 通过getData接口获取单位工程详细数据(带缓存功能)
+     * @param  {Number}tenderID 单位工程ID
+     *         {String}userID 用户ID
+     * @return {Object} 跟projectObj.project的数据结构一致
+     * */
+    async function getTenderDetail(tenderID, userID) {
+        // 获取单位工程详细数据
+        let tenderDetail = _cache.tenderDetailMap[tenderID];
+        if (!tenderDetail) {
+            tenderDetail = PROJECT.createNew(tenderID, userID);
+            await tenderDetail.loadDataSync();
+            // 标记序号
+            const count = Object.keys(_cache.tenderDetailMap).length;
+            tenderDetail.serialNo = count + 1;
+            _cache.tenderDetailMap[tenderID] = tenderDetail;
+        }
+        return tenderDetail;
+    }
+
+    // 获取普通基数: {xxx}
+    function getNormalBase(str) {
+        const reg = /{.+?}/g;
+        const matchs = str.match(reg);
+        return matchs || [];
+    }
+    // 获取id引用基数: @xxx-xxx-xx
+    function getIDBase(str) {
+        const reg = /@.{36}/g;
+        const matchs = str.match(reg);
+        return matchs || [];
+    }
+
+    // 转换基数表达式
+    // 1.有子项,则取固定清单对应基数
+    // 2.无子项,有基数,a.优先转换为行代号(不可自身) b.不能转换为行代号则找对应字典
+    // 3.基数中有无法转换的,根据导出类型决定
+    function transformCalcBase(exportKind, tenderDetail, node, { CalcBaseMap, FlagCalcBaseMap }) {
+        let expr = node.data.calcBase || '';
+        if (node.children.length) {
+            const flag = node.getFlag();
+            return FlagCalcBaseMap[flag] || '';
+        }
+        if (expr) {
+            let illegal = false;
+            const normalBase = getNormalBase(expr);
+            const idBase = getIDBase(expr);
+            // 普通基数转基数字典
+            normalBase.forEach(base => {
+                let replaceStr = CalcBaseMap[base];
+                // 转换成行代号的优先级比较高,进行清单匹配
+                const flag = FlagCalcBaseMap[base];
+                if (flag) {
+                    const flagNode = tenderDetail.mainTree.items.find(mNode => mNode.getFlag() === flag);
+                    // 匹配到了 普通基数转换成行引用
+                    if (flagNode) {
+                        replaceStr = `F${flagNode.serialNo() + 1}`;
+                    }
+                }
+                // 存在无法处理的基数
+                if (!replaceStr) {
+                    illegal = true;
+                    return;
+                }
+                expr = expr.replace(new RegExp(base, 'g'), replaceStr);
+            });
+            // id引用转行代号引用
+            idBase.forEach(base => {
+                const id = base.match(/[^@]+/)[0];
+                const theNode = tenderDetail.mainTree.getNodeByID(id);
+                const rowCode = theNode ? `F${theNode.serialNo() + 1}` : '';
+                if (!rowCode) {
+                    illegal = true;
+                    return;
+                }
+                expr = expr.replace(new RegExp(base, 'g'), rowCode);
+            });
+            // 不合法
+            // 在我们软件中的基数无法找到映射代号的情况下
+            // 导出招标、控制价时,基数为空
+            // 导出投标时,基数=综合合价/费率
+            if (illegal) {
+                if (exportKind === EXPORT_KIND.BID_INVITATION || exportKind === EXPORT_KIND.CONTROL) {
+                    return '';
+                } else {
+                    const totalFee = getFee(node.data.fees, 'common.totalFee');
+                    const feeRate = node.data.feeRate;
+                    return +feeRate ? scMathUtil.roundTo(totalFee / (feeRate / 100), -2) : totalFee
+                }
+            }
+            return expr;
+        }
+    }
+    // 转换基数说明,根据转换后的基数处理
+    // 1.行引用转换为对应行的名称
+    // 2.基数字典转换为中文
+    function transformCalcBaseState(tenderDetail, expr, CalcStateMap) {
+        if (!expr) {
+            return '';
+        }
+        expr = String(expr);
+        // 提取基数
+        const bases = expr.split(/[\+\-\*\/]/g);
+        // 提取操作符
+        const oprs = expr.match(/[\+\-\*\/]/g);
+        // 转换后的基数
+        const newBase = [];
+        let illegal = false;
+        for (const base of bases) {
+            // 行引用转换为名称.
+            if (/F\d+/.test(base)) {
+                const rowCode = base.match(/\d+/)[0];
+                const node = tenderDetail.mainTree.items[rowCode - 1];
+                if (!node || !node.data.name) {
+                    illegal = true;
+                    break;
+                }
+                newBase.push(node && node.data.name ? node.data.name : '');
+            } else if (CalcStateMap[base]) {    // 字典转换为中文
+                newBase.push(CalcStateMap[base]);
+            } else if (/^\d+(\.\d+)?$/.test(base)) {    // 金额
+                newBase.push(base);
+            } else {
+                illegal = true;
+                break;
+            }
+        }
+        if (illegal) {
+            return '';
+        }
+        let newExpr = '';
+        for (let i = 0; i < newBase.length; i++) {
+            newExpr += newBase[i];
+            if (oprs && oprs[i]) {
+                newExpr += oprs[i];
+            }
+        }
+        return newExpr;
+    }
+    // 获取节点的某属性
+    function getAttr(ele, name) {
+        return (ele.attrs.find(attr => attr.name === name) || {}).value;
+    }
+    // 设置节点的某属性
+    function setAttr(ele, name, value) {
+        const attr = ele.attrs.find(attr => attr.name === name);
+        if (attr) {
+            attr.value = value;
+        }
+    }
+    // 从srcEle节点中获取元素名为eleName的元素
+    function getElementFromSrc(srcEle, eleName) {
+        if (!srcEle || !srcEle.children || !srcEle.children.length) {
+            return [];
+        }
+        return srcEle.children.filter(ele => ele.name === eleName);
+    }
+    /*
+     * 设置完工程编号后,更新原始数据的工程编号
+     * 更新原始数据前需要将编号里的特殊字符进行转换
+     * @param  {Array}exportData 提取出来的需要导出的数据
+     *         {Array}codes 工程编号表中填写的工程编号
+     *         {String}EngineeringName 单项工程元素的名称
+     *         {String}tenderName 单位工程元素的名称
+     *         {String}codeName 编号属性的名称
+     * @return {void}
+     * */
+    function setupCode(exportData, codes, EngineeringName, tenderName, codeName) {
+        // 转换xml实体字符
+        let parsedCodes = getParsedData(codes);
+        // 给导出数据里的单项工程、单位工程填上用户设置的工程编号
+        exportData.forEach(orgData => {
+            let curIdx = 0;
+            let engs = getElementFromSrc(orgData.data, EngineeringName);
+            engs.forEach(eng => {
+                eng.attrs.find(attr => attr.name === codeName).value = parsedCodes[curIdx++];
+                let tenders = getElementFromSrc(eng, tenderName);
+                tenders.forEach(tender => {
+                    tender.attrs.find(attr => attr.name === codeName).value = parsedCodes[curIdx++];
+                });
+            });
+        });
+    }
+
+    // 将文本的中文提取出来
+    function getHan(str) {
+        if (!str) {
+            return '';
+        }
+        return str
+            .split('')
+            .reduce((acc, cur) => {
+                if (isHan(cur)) {
+                    acc.push(cur);
+                }
+                return acc;
+            }, [])
+            .join('');
+    }
+
+    const UTIL = Object.freeze({
+        hasValue,
+        setTimeoutSync,
+        getFee,
+        getAggregateFee,
+        getFeeByFlag,
+        getPlainAttrs,
+        getValueByKey,
+        getRelGLJ,
+        generateHardwareId,
+        arrayToObj,
+        validDepth,
+        sortByNext,
+        getTenderDetail,
+        getProjectByGranularity,
+        getNormalBase,
+        getIDBase,
+        transformCalcBase,
+        transformCalcBaseState,
+        getElementFromSrc,
+        getAttr,
+        setAttr,
+        getParsedData,
+        setupCode,
+        getHan,
+    });
+
+    // 开始标签
+    function _startTag(ele) {
+        let rst = `<${ele.name}`;
+        for (const attr of ele.attrs) {
+            rst += ` ${attr.name}="${attr.value}"`;
+        }
+        rst += ele.children.length > 0 ? '>' : '/>';
+        return rst;
+    }
+    // 结束标签
+    function _endTag(ele) {
+        return `</${ele.name}>`;
+    }
+    // 拼接成xml字符串
+    function _toXMLStr(eles) {
+        let rst = '';
+        for (const ele of eles) {
+            rst += _startTag(ele);
+            if (ele.children.length > 0) {
+                rst += _toXMLStr(ele.children);
+                rst += _endTag(ele);
+            }
+        }
+        return rst;
+    }
+    // 格式化xml字符串
+    function _formatXml(text) {
+        // 去掉多余的空格
+        text = '\n' + text.replace(/>\s*?</g, ">\n<");
+        // 调整格式
+        const reg = /\n(<(([^\?]).+?)(?:\s|\s*?>|\s*?(\/)>)(?:.*?(?:(?:(\/)>)|(?:<(\/)\2>)))?)/mg;
+        const nodeStack = [];
+        const output = text.replace(reg, function ($0, all, name, isBegin, isCloseFull1, isCloseFull2, isFull1, isFull2) {
+            const isClosed = (isCloseFull1 === '/') || (isCloseFull2 === '/') || (isFull1 === '/') || (isFull2 === '/');
+            let prefix = '';
+            if (isBegin === '!') {
+                prefix = getPrefix(nodeStack.length);
+            } else {
+                if (isBegin !== '/') {
+                    prefix = getPrefix(nodeStack.length);
+                    if (!isClosed) {
+                        nodeStack.push(name);
+                    }
+                } else {
+                    nodeStack.pop();
+                    prefix = getPrefix(nodeStack.length);
+                }
+            }
+            return '\n' + prefix + all;
+        });
+        return output.substring(1);
+
+        function getPrefix(prefixIndex) {
+            const span = '    ';
+            const output = [];
+            for (let i = 0; i < prefixIndex; i++) {
+                output.push(span);
+            }
+            return output.join('');
+        }
+    }
+
+    /**
+     * 提取要导出的数据
+     * @param {Function} entryFunc - 提取数据的入口方法
+     * @param {Object} requestForSummaryInfo - 项目表级汇总字段(建设项目、单位工程汇总)
+     * @param {Number} exportKind - 导出的文件类型:1-招标、2-投标、3-控制价
+     * @param {String} areaKey - 地区标识,如:'安徽@马鞍山'
+     * @param {Number} tenderID - 单位工程ID
+     * @param {String} userID - 用户ID
+     * @return {Promise<Array>} - [{data: Object, exportKind: Number, fileName: String}]
+     */
+    async function extractExportData(entryFunc, requestForSummaryInfo, exportKind, areaKey, tenderID, userID) {
+        // 默认导出投标文件
+        if (!exportKind || ![1, 2, 3].includes(exportKind)) {
+            exportKind = EXPORT_KIND.BID_SUBMISSION;
+        }
+        // 拉取标段数据:建设项目、单位工程数据(projects表数据)
+        const projectData = await getProjectByGranularity(GRANULARITY.PROJECT, requestForSummaryInfo, tenderID, userID);
+        if (!projectData) {
+            throw '获取项目数据错误';
+        }
+        // 单位工程按照树结构数据进行排序,这样导出才的单位工程顺序才是对的
+        projectData.children = sortByNext(projectData.children);
+        // 先获取需要导出的单位工程的详细数据
+        const tenderDetailMap = getItem('tenderDetailMap');
+        for (const tenderItem of projectData.children) {
+            if (!tenderDetailMap[tenderItem.ID]) {
+                await setTimeoutSync(() => { }, TIMEOUT_TIME); // 需要请求项目详细数据的时候,间隔一段时间再初始单位工程数据,减少服务器压力
+            }
+            // 获取单位工程详细数据
+            const detail = await getTenderDetail(tenderItem.ID, userID);
+            tenderDetailPretreatment(detail);
+            console.log(detail);
+        }
+        // 提取相关项目的详细导出数据
+        return await entryFunc(areaKey, exportKind, projectData, tenderDetailMap);
+    }
+
+    // 对getData返回的数据进行一些通用预处理,方便各接口直接取值、处理
+    function tenderDetailPretreatment(tenderDetail) {
+        const projectGLJList = tenderDetail.projectGLJ.datas.gljList;
+        const bidEvaluationList = tenderDetail.bid_evaluation_list.datas;
+        const evaluateList = tenderDetail.evaluate_list.datas;
+        const decimalInfo = tenderDetail.projectInfo.property.decimal;
+        // 项目人材机汇总排序
+        gljUtil.sortRationGLJ(projectGLJList);
+        // 计算人材机总消耗量,否则projectGLJ.datas.gljList里的数据不会有消耗量数据
+        gljUtil.calcProjectGLJQuantity(tenderDetail.projectGLJ.datas, tenderDetail.ration_glj.datas, tenderDetail.Ration.datas, tenderDetail.Bills.datas, tenderDetail.property.decimal.glj.quantity, _, scMathUtil);
+        projectGLJList.forEach(glj => {
+            // 项目人材机设置价格信息,否则projectGLJ.datas.gljList里的数据不会有相关价格信息
+            // 价格信息存在新的priceInfo字段,以免对一些方法造成影响
+            glj.priceInfo = gljUtil.getGLJPrice(glj, tenderDetail.projectGLJ.datas, tenderDetail.property.calcOptions, tenderDetail.labourCoe.datas, decimalInfo, false, _, scMathUtil, {}, tenderDetail.projectGLJ.getTenderPriceCoe(glj, tenderDetail.property));
+            // 计算合价:人材料总消耗量*预算价
+            glj.priceInfo.totalPrice = scMathUtil.roundForObj(glj.priceInfo.tenderPrice * glj.tenderQuantity, 2);
+        });
+        // 获取暂估价材料数据,getData原始数据evaluate_list.datas里的数据缺少一些价格数据,需要调用额外接口
+        tenderDetail.evaluateMaterialData = configMaterialObj.getEvaluateMaterialDatas(projectGLJList, evaluateList, decimalInfo);
+        // 获取评标材料数据
+        tenderDetail.bidMaterialData = configMaterialObj.getBidMaterialDatas(projectGLJList, bidEvaluationList, decimalInfo);
+
+
+    }
+
+    /**
+     * 根据各自费用定额的文件结构,导出文件
+     * 每个费用定额可能导出的结果文件都不同
+     * 比如广东18需要将一个建设项目文件,多个单位工程文件打包成一个zip文件。重庆18就没这种要求
+     * @param {Array} extractData - 提取的数据
+     * @param {Function} saveAsFunc - 各自费用定额的导出方法,适应不同接口需要不同的最终文件形式
+     */
+    async function exportFile(extractData, saveAsFunc) {
+        // 获取文件数据
+        const fileData = extractData.map(extractObj => {
+            // 转换成xml字符串
+            let xmlStr = _toXMLStr([extractObj.data]);
+            // 加上xml声明
+            xmlStr = `<?xml version="1.0" encoding="utf-8"?>${xmlStr}`;
+            // 格式化
+            xmlStr = _formatXml(xmlStr);
+            const blob = new Blob([xmlStr], { type: 'text/plain;charset=utf-8' });
+            return {
+                blob: blob,
+                exportKind: extractObj.exportKind,
+                fileName: extractObj.fileName
+            };
+        });
+        if (!saveAsFunc) {
+            return fileData;
+        }
+        // 导出
+        await saveAsFunc(fileData);
+    }
+
+    /**
+     * 默认的通用导出文件方法:一个文件数据对应一个xml文件(变更后缀)
+     * @param {Array} fileData - 默认的通用导出文件方法:一个文件数据对应一个xml文件(变更后缀)
+     * @return {Void}
+     */
+    async function defaultSaveAs(fileData) {
+        fileData.forEach(fileItem => saveAs(fileItem.blob, fileItem.fileName));
+    }
+
+    return {
+        CONFIG,
+        CACHE,
+        UTIL,
+        Element,
+        extractExportData,
+        defaultSaveAs,
+        exportFile,
+    };
+})();

+ 185 - 0
web/building_saas/standard_interface/export/guangdong_zhongshan.js

@@ -0,0 +1,185 @@
+// INTERFACE_EXPORT =,必须这么写,这样才能在导出时动态加载脚本后,覆盖前端代码
+INTERFACE_EXPORT = (() => {
+  'use strict';
+
+  /**
+   * 
+   * @param {String} areaKey - 地区标识,如:'安徽@马鞍山',有些地区的接口只是取值上有不同,共有一个接口脚本, 需要通过地区标识确定一些特殊处理
+   * @param {Number} exportKind - 导出类型,招标、投标、控制价
+   * @param {Object} projectData - 项目表数据:{ 建设项目Data, children: [单位工程...] }
+   * @param {Object} tenderDetailMap - 单位工程ID与getData接口数据(projectObj.project的结构)的映射。
+   * @return {Promise<Array>} - 返回的数据结构必须按照规定:[{ data, exportKind, fileName }],参考web\building_saas\standard_interface\index.js中的注释说明
+   */
+  async function entry(areaKey, exportKind, projectData, tenderDetailMap) {
+    console.log(areaKey)
+    //<CprjInfo CprjName="中山接口报错" CprjType="QDYS">
+    const {
+      UTIL,
+      Element,
+    } = INTERFACE_EXPORT_BASE;
+
+    const {
+      EXPORT_KIND: {
+        BID_INVITATION,
+        BID_SUBMISSION,
+        CONTROL
+      },
+      fixedFlag,
+    } = window.commonConstants
+    let cprjType = {
+      1: 'ZBKZJ', // 招标
+      2: 'TBBJ', // 投标
+      3: 'GCLQD' // 控制价
+    }
+
+    const isBidInvitation = exportKind === BID_INVITATION; // 是否是招标
+    const isBidSubmission = exportKind === BID_SUBMISSION; // 是否是投标
+    const isControl = exportKind === CONTROL; // 是否是控制价
+
+    // 建设项目根节点信息
+    function CprjInfo(projectName) {
+      const attrs = [{
+        name: 'CprjName',
+        value: projectName
+      }, {
+        name: 'CprjType',
+        value: cprjType[exportKind]
+      }];
+      Element.call(this, 'CprjInfo', attrs);
+    }
+    // 项目基本信息
+    function SystemInfo(projectData) {
+      let hardID = UTIL.generateHardwareId();
+      let [cpuId, diskId, macId] = hardID.split(';');
+      const attrs = [{
+          name: 'Name',
+          value: '公路工程造价数据标准'
+        }, {
+          name: 'Version',
+          value: '1.0'
+        }, {
+          name: 'SoftwareName',
+          value: '纵横公路云造价'
+        },
+        {
+          name: 'SoftwareVer',
+          value: VERSION
+        }, {
+          name: 'SoftwareCompany',
+          value: '珠海纵横创新软件有限公司'
+        }, {
+          name: 'MakeDate',
+          value: ''
+        }, {
+          name: 'Key1',
+          value: window.btoa(VERSION)
+        }, {
+          name: 'Key2',
+          value: window.btoa(diskId)
+        }, {
+          name: 'Key3',
+          value: window.btoa(macId)
+        }
+      ];
+      if (isBidSubmission) attrs.splice(6, 0, {
+        name: 'BidderVer',
+        value: ''
+      })
+      Element.call(this, 'SystemInfo', attrs);
+    }
+
+    //造价依据
+    function CostBasis() { // ItemStandardNo="GYSFX-000000-2018-86"
+      const attrs = [{
+        name: 'MakeRuleNo',
+        value: 'GYSBB-000000-2018-86'
+      }, {
+        name: 'MakeRuleName',
+        value: '公路工程建设项目概算预算编制办法'
+      }, {
+        name: 'ItemStandardNo',
+        value: 'GYSFX-000000-2018-86'
+      }];
+      Element.call(this, 'CostBasis', attrs);
+
+      //定额库信息
+
+      //to do 改成实际的值
+      function NormLib() {
+        const attrs = [{
+          name: 'NormLibNo',
+          value: '0'
+        }, {
+          name: 'NormLibName',
+          value: '部颁公路工程预算定额(2018)'
+        }, {
+          name: 'Type',
+          value: 'ZDEK'
+        }];
+        Element.call(this, 'NormLib', attrs);
+      }
+      this.children.push(new NormLib());
+    }
+
+    function Rate(feeRateInfo) {
+      //RateNo="1" Name="纵横软件测试版本" RateLibNo="GYSFL-000000-2018-86"
+      const attrs = [{
+        name: 'RateNo',
+        value: feeRateInfo.ID
+      }, {
+        name: 'Name',
+        value: feeRateInfo.name
+      }, {
+        name: 'RateLibNo',
+        value: feeRateInfo.libID
+      }];
+      Element.call(this, 'Rate', attrs);
+
+      let RateParams = {
+        name: 'RateParams',
+        attrs: [],
+        children: []
+      }
+      RateParams.children.push(new RateParam());
+      this.children.push(RateParams);
+
+      function RateParam() {
+        //<RateParam RateTypeNo="DJSGZJFFL" RateParamNo="0" Ratio="100"/>
+        const attrs = [{
+          name: 'RateTypeNo',
+          value: 'DJSGZJFFL'
+        }, {
+          name: 'RateParamNo',
+          value: '0'
+        }, {
+          name: 'Ratio',
+          value: '100'
+        }];
+        Element.call(this, 'RateParam', attrs);
+      }
+
+
+
+    }
+
+
+    let data = new CprjInfo(projectData.name);
+    data.children.push(new SystemInfo());
+    data.children.push(new CostBasis());
+    let feeRataDatas = tenderDetailMap[projectObj.project.ID()].FeeRate.datas;
+    data.children.push(new Rate(feeRataDatas));
+
+
+
+
+    return [{
+      data: data,
+      exportKind: exportKind,
+      fileName: projectData.name + INTERFACE_CONFIG[areaKey]['fileSuffix'][exportKind]
+    }]
+
+  }
+  return {
+    entry,
+  };
+})();

+ 89 - 0
web/building_saas/standard_interface/export/view.js

@@ -0,0 +1,89 @@
+/**
+ *
+ *
+ * @author Zhong
+ * @date 2019/6/5
+ * @version
+ */
+//导出接口相关
+const EXPORT_VIEW = (() => {
+    'use strict';
+
+    const _base = INTERFACE_EXPORT_BASE;
+    const _cache = _base.CACHE;
+    // 导出数据缓存,为了自检完后,再导出的时候不需要重新运行相关提取程序。(暂时取消了自检,但还是留着这个缓存机制)
+    let _exportCache = [];
+    // 操作状态
+    const STATE = {
+        checking: false, // 自检
+        exporting: false, // 导出
+    };
+    // 回到初始状态,需要清空cache中的数据
+    function resetState() {
+        _exportCache = [];
+        _cache.clear();
+    }
+    //事件监听
+    function exportListener() {
+        // 导出接口
+        $('#interface-export-confirm').click(async function () {
+            const pr = new SCComponent.InitProgressBar();
+            try {
+                const checkedDatas = $('#interface-export-modal input[type="checkbox"]:checked');
+                if (!checkedDatas.length) {
+                    throw '请勾选导出文件。';
+                }
+                const parentArea = $('#export-parent-area').val();
+                const subArea = $('#export-sub-area').val();
+                if (!parentArea || !subArea) {
+                    throw '请选择有效地区。';
+                }
+                // 按照地区动态加载导出脚本
+                const areaKey = `${parentArea}@${subArea}`;
+                await STD_INTERFACE.loadScriptByArea(areaKey, STD_INTERFACE.ScriptType.EXPORT);
+                if (STATE.exporting) {
+                    return;
+                }
+                STATE.exporting = true;
+                if (!_exportCache || !_exportCache.length) {
+                    pr.start('导出数据接口', '正在导出文件,请稍候……');
+                    for (const checkedData of checkedDatas) {
+                        const boqType = parseInt($(checkedData).val());
+                        const requestForSummaryInfo = INTERFACE_EXPORT.requestForSummaryInfo || {};
+                        const projectID = projectObj.project.ID();
+                        const exportData = await _base.extractExportData(INTERFACE_EXPORT.entry, requestForSummaryInfo, boqType, areaKey, projectID, userID);
+                        _exportCache.push(...exportData);
+                    }
+                }
+                if (_exportCache && _exportCache.length) {
+                    // 导出文件
+                    await _base.exportFile(_exportCache, INTERFACE_EXPORT.saveAsFile || INTERFACE_EXPORT_BASE.defaultSaveAs);
+                }
+            } catch (err) {
+                console.log(err);
+                alert(err);
+            } finally {
+                pr.end();
+                setTimeout(() => {
+                    STATE.exporting = false;
+                }, 300);
+            }
+        });
+        //导出窗口--------
+        $('#interface-export-modal').on('hide.bs.modal', function () {
+            resetState();
+            STATE.checking = false;
+            STATE.exporting = false;
+            $('#interface-export-modal input[type="checkbox"]:eq(0)').prop('checked', true);
+        });
+        $('#interface-export-modal input[type="checkbox"]').click(function () {
+            resetState();
+        });
+    }
+    return { exportListener, resetState }
+})();
+
+$(document).ready(() => {
+    $('#interface-export-modal').on('show.bs.modal', () => STD_INTERFACE.initExportAreas($('#export-parent-area'), $('#export-sub-area')));
+    EXPORT_VIEW.exportListener();
+})

+ 134 - 0
web/building_saas/standard_interface/index.js

@@ -0,0 +1,134 @@
+/*
+ * @Descripttion: 招投标数据接口
+ * @Author: vian
+ * @Date: 2020-08-17 15:07:31
+ */
+
+/**
+ * 用于导出的挂载变量,各地区对外接口需要作覆盖它。
+ * 注意:导出脚本必须有一个“entry”方法挂载在“INTERFACE_EXPORT”对象上。 eg: INTERFACE_EXPORT = { entry: () => void }
+ * entry方法返回的结果必须为[{ data, exportKind,(导出类型,招、投标、控制价) fileName(文件名) }]
+ * 其中data为xml的object形式:
+ * xml: 
+ * <JingJiBiao Xmbh="001">
+ *   <ZhaoBiaoXx Zbr="vian">
+ *   </ZhaoBiaoXx>
+ * </JingJiBiao>
+ * entry方法返回的数据结构:
+ * {
+ *   name: 'JingJiBiao',
+ *   attrs: [{ name: 'xmbh', value: '001' }],
+ *   children: [
+ *      { 
+ *        name: 'ZhaoBiaoXx',
+ *        attrs: [{ name: 'Zbr', value: 'vian' }],
+ *        children: []
+ *      }  
+ *   ]
+ * }
+ */
+let INTERFACE_EXPORT = {};
+
+// 用于导入的挂载变量
+let INTERFACE_IMPORT = {};
+
+const STD_INTERFACE = (() => {
+    'use strict';
+
+    // 根据地区配置,初始化地区选项
+    function initExportAreas($parentAreas, $subAreas) {
+        const connectedAreas = Object.keys(INTERFACE_CONFIG);
+        const parentMap = {};
+        connectedAreas.forEach(connectedArea => {
+            const areas = connectedArea.split('@');
+            (parentMap[areas[0]] || (parentMap[areas[0]] = [])).push(areas[1]);
+        });
+        const parentAreasHtml = Object
+            .keys(parentMap)
+            .reduce((acc, area) => acc += `<option value="${area}">${area}</option>`, '');
+        $parentAreas.html(parentAreasHtml);
+        const subAreasHtml = parentMap[Object.keys(parentMap)[0]].reduce((acc, area) => acc += `<option value="${area}">${area}</option>`, '');
+        $subAreas.html(subAreasHtml);
+        // 父级地区变更,子地区选项更新
+        $parentAreas.change(function () {
+            EXPORT_VIEW.resetState(); // 清空接口缓存
+            const curArea = $(this).val();
+            const subAreasHtml = parentMap[curArea].reduce((acc, area) => acc += `<option value="${area}">${area}</option>`, '');
+            $subAreas.html(subAreasHtml);
+        });
+        $subAreas.change(function() {
+            EXPORT_VIEW.resetState();
+        })
+    }
+
+    /**
+     * 动态加载script
+     * 由于后续的接口可能会非常多,一次性加载所有的接口文件完全没必要,而且不可控,很容易导致初次加载速度变慢。
+     * 在选定相关地区后,再根据地区对应的script路径,动态加载script
+     * 不需要缓存,缓存可能会影响正常操作的性能。而且导入导出时动态获取接口文件的需要的时间,客户是不可感知的。
+     * @param {String} path - 需要加载的scipt路径
+     * @return {Promise}
+     */
+    function loadScript(path) {
+        return new Promise((resolve, reject) => {
+            const scriptID = 'interface-script';
+            const body = document.getElementsByTagName('body')[0];
+            // 移除原来的
+            const orgScript = document.getElementById(scriptID);
+            if (orgScript) {
+                body.removeChild(orgScript);
+            }
+            // 增加新的
+            const script = document.createElement('script');
+            script.src = path;
+            script.type = 'text/javascript';
+            script.id = scriptID;
+            body.appendChild(script);
+            script.onload = script.onreadystatechange = function () { // ie、ff触发事件不同,都写上
+                if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
+                    script.onload = script.onreadystatechange = null;
+                    resolve();
+                }
+            };
+            script.onerror = function () {
+                reject('script加载失败。');
+            };
+        });
+    }
+
+    const ScriptType = {
+        EXPORT: 'export',
+        IMPORT: 'import',
+    };
+
+    let curArea = '';
+    /**
+     * 根据地区和脚本类型加载脚本
+     * @param {String} area - 地区选项 eg: '安徽省@马鞍山'
+     * @param {Number} scriptType - 脚本类型
+     * @return {Void}
+     */
+    async function loadScriptByArea(area, scriptType) {
+        if (area === curArea) {
+            return;
+        }
+        curArea = area;
+        const configItem = INTERFACE_CONFIG[area];
+        if (!configItem) {
+            throw new Error(`[${area}]不存在有效配置。`);
+        }
+        const scriptName = configItem.scriptName;
+        if (!scriptName) {
+            throw new Error(`[${area}]不存在有效脚本。`);
+        }
+        const fullPath = `/web/building_saas/standard_interface/${scriptType}/${scriptName}`;
+        await loadScript(fullPath);
+    }
+
+    return {
+        initExportAreas,
+        ScriptType,
+        loadScriptByArea,
+    };
+
+})();

+ 79 - 0
web/over_write/js/quanguo_2018.js

@@ -0,0 +1,79 @@
+const progression = [];
+const deficiency = {};
+if (typeof module !== 'undefined' && !module.nodeType) {
+    module.exports = {
+        progression,
+        deficiency
+    };
+}
+
+
+let isQG2018 = true;
+function overwriteRationCalcBases() {
+    if (typeof rationCalcBases == 'undefined') return;
+    for (let key in rationCalcBases) delete rationCalcBases[key];
+
+    rationCalcBases['人工费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['材料费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, baseMaterialTypes, priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['施工机械使用费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, baseMachineTypes, priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['施工机械人工费'] = function (node, isTender) {
+        return calcTools.machineDetailFee(node, node.data.gljList, [], gljType.MACHINE_LABOUR, isTender);
+    };
+    rationCalcBases['设备购置费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.EQUIPMENT], priceTypes.ptMarketPrice, isTender);
+    };
+
+    rationCalcBases['定额人工费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptBasePrice, isTender);
+    };
+    rationCalcBases['定额材料费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, baseMaterialTypes, priceTypes.ptBasePrice, isTender);
+    };
+    rationCalcBases['定额施工机械使用费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, baseMachineTypes, priceTypes.ptBasePrice, isTender);
+    };
+    rationCalcBases['定额商品砼费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR], priceTypes.ptBasePrice, isTender);
+    };
+    rationCalcBases['定额设备费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.EQUIPMENT], priceTypes.ptBasePrice, isTender);
+    };
+    rationCalcBases['定额外购砼构件费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.PURCHASE_COMPONENT], priceTypes.ptBasePrice, isTender);
+    };
+    rationCalcBases['定额绿化苗木费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.GREEN_SEEDLING], priceTypes.ptBasePrice, isTender);
+    };
+};
+
+(function overwriteFeeTypes() {
+    if (typeof cpFeeTypes == 'undefined') return;
+    cpFeeTypes = [
+        { type: 'marketLabour', name: '人工费' },
+        { type: 'marketMaterial', name: '材料费' },
+        { type: 'marketMachine', name: '施工机械使用费' },
+        { type: 'marketMachineLabour', name: '施工机械人工费' },
+        { type: 'marketEquipment', name: '设备购置费' },
+        { type: 'marketDirect', name: '直接费' },
+
+        { type: 'labour', name: '定额人工费' },
+        { type: 'material', name: '定额材料费' },
+        { type: 'machine', name: '定额施工机械使用费' },
+        { type: 'equipment', name: '定额设备费' },
+        { type: 'direct', name: '定额直接费' },
+
+        { type: 'measure', name: '措施费' },
+        { type: 'manage', name: '企业管理费' },
+        { type: 'force', name: '规费' },
+        { type: 'profit', name: '利润' },
+        { type: 'tax', name: '税金' },
+        { type: 'common', name: '建安费' },
+        { type: 'rationCommon', name: '定额建安费' }
+    ];
+})();

+ 247 - 6
web/over_write/js/shandong_2016.js

@@ -1,7 +1,248 @@
-if (typeof module !== 'undefined') {
-  module.exports = { 
-      getDefalutAssistProductionFeeRate: function () {
-          return 5
-      }
-  };
+let isSD2016 = true;
+function overwriteRationCalcBases() {
+    if (typeof rationCalcBases == 'undefined') return;
+    for (let key in rationCalcBases) delete rationCalcBases[key];
+
+    rationCalcBases['人工费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.LABOUR], priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['材料费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, baseMaterialTypes, priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['施工机械使用费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, baseMachineTypes, priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['商品砼费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.COMMERCIAL_CONCRETE, gljType.COMMERCIAL_MORTAR], priceTypes.ptMarketPrice, isTender);
+    };
+    rationCalcBases['外购砼构件费'] = function (node, isTender) {
+        return calcTools.rationBaseFee(node, [gljType.PURCHASE_COMPONENT], priceTypes.ptMarketPrice, isTender);
+    };
+};
+
+(function overwriteFeeTypes() {
+    if (typeof cpFeeTypes == 'undefined') return;
+    cpFeeTypes = [
+        { type: 'marketDirect', name: '直接费' },
+        { type: 'marketDirectWork', name: '直接工程费' },
+        { type: 'marketLabour', name: '人工费' },
+        { type: 'marketMaterial', name: '材料费' },
+        { type: 'marketMachine', name: '施工机械使用费' },
+        { type: 'otherFee', name: '其他工程费' },
+        { type: 'otherFee1', name: '其他工程费I' },
+        { type: 'otherFee2', name: '其他工程费II' },
+        { type: 'indirectFee', name: '间接费' },
+        { type: 'manage', name: '企业管理费' },
+        { type: 'force', name: '规费' },
+        { type: 'profit', name: '利润' },
+        { type: 'tax', name: '税金' },
+        { type: 'common', name: '建安费' }
+    ];
+})();
+
+// 清单基数
+const progression = ['养护工程管理费', '养护工程设计费'];
+const deficiency = {};
+if (typeof baseFigureMap !== 'undefined') {
+    const { fixedFlag } = commonConstants;
+    const budgetMap = {
+        // 显示:除清单固定类别是“建筑安装工程费”的以外部分可显示。
+        '建筑安装工程费': {
+            base: 'JZAZGCF',
+            fixedFlag: fixedFlag.CONSTRUCTION_INSTALL_FEE,
+            filter: [fixedFlag.CONSTRUCTION_INSTALL_FEE],
+            pick: false,
+        },
+        // 显示:仅清单固定类别是“安全生产费”的可显示。
+        '建筑安装工程费(不含设备费)': {
+            base: 'JZAZGCFBHSB',
+            fixedFlag: null,
+            filter: [fixedFlag.SAFE_COST],
+            pick: true
+        },
+        // 显示:除清单固定类别是“建筑安装工程费”、“土地使用及拆迁补偿费”的以外部分可显示。
+        '土地使用及拆迁补偿费': {
+            base: 'TDSYJCQBCF',
+            fixedFlag: fixedFlag.LAND_USED_DEMOLITION,
+            filter: [fixedFlag.CONSTRUCTION_INSTALL_FEE, fixedFlag.LAND_USED_DEMOLITION],
+            pick: false,
+        },
+        // 显示:除清单固定类别是“建筑安装工程费”、“土地使用及拆迁补偿费”、“工程建设其他费用”的以外部分可显示。
+        '工程建设其他费用': {
+            base: 'GCJSQTFY',
+            fixedFlag: fixedFlag.MAINTENANCE_EXPENSES,
+            filter: [fixedFlag.CONSTRUCTION_INSTALL_FEE, fixedFlag.LAND_USED_DEMOLITION, fixedFlag.MAINTENANCE_EXPENSES],
+            pick: false,
+        },
+        // 显示:仅清单固定类别是“养护工程其他费用”部分可显示。
+        '养护工程管理费': {
+            isProgressive: true,
+            base: 'YHGCGLF',
+            fixedFlag: null,
+            filter: [fixedFlag.MAINTENANCE_EXPENSES],
+            pick: true,
+        },
+        // 显示:仅清单固定类别是“养护工程其他费用”部分可显示。
+        '养护工程设计费': {
+            isProgressive: true,
+            base: 'YHGCSJF',
+            fixedFlag: null,
+            filter: [fixedFlag.MAINTENANCE_EXPENSES],
+            pick: true,
+        },
+        // 显示:仅“价差预备费”可显示
+        '价差预备费': {
+            base: 'JCYBF',
+            fixedFlag: null,
+            filter: [fixedFlag.SPREAD_BUDGET_FEE],
+            pick: true,
+        },
+    };
+    const boqMap = {
+        //仅允许用于固定类别是“第100章至700章清单”以外的清单
+        '各章清单合计': {
+            base: 'GZQDHJ',
+            fixedFlag: fixedFlag.ONE_SEVEN_BILLS,
+            filter: [fixedFlag.ONE_SEVEN_BILLS],
+            pick: false
+        },
+        //仅允许用于固定类别是“第100章至700章清单”以外的清单
+        '专项暂定合计': {
+            base: 'ZXZDHJ',
+            fixedFlag: null,
+            filter: [fixedFlag.ONE_SEVEN_BILLS],
+            pick: false
+        },
+        /*
+        *  清单固定行[第100章至700章清单]下的[第100章清单]需要允许清单可使用基数{100章以外合计}
+        *  因此{100章以外合计}不设置关联的清单固定行
+        * */
+        //仅允许用于固定类别为“100章清单”引用
+        '100章以外清单合计': {
+            base: 'YBZYHQDHJ',
+            fixedFlag: null,
+            filter: [fixedFlag.ONE_HUNDRED_BILLS],
+            pick: true
+        }
+    };
+    baseFigureMap.budget = budgetMap;
+    baseFigureMap.boq = boqMap;
+}
+
+if (typeof baseFigureTemplate !== 'undefined') {
+    const { fixedFlag } = commonConstants;
+    baseFigureTemplate.budget = {
+        // 建筑安装工程费 算法:取清单固定类别是“建筑安装工程费”的金额。
+        JZAZGCF(tender) {
+            return cbTools.getBaseFee(fixedFlag.CONSTRUCTION_INSTALL_FEE, tender, 'common');
+        },
+        // 土地使用及拆迁补偿费 算法:取清单固定类别是“土地使用及拆迁补偿费”的金额。
+        TDSYJCQBCF(tender) {
+            return cbTools.getBaseFee(fixedFlag.LAND_USED_DEMOLITION, tender, 'common');
+        },
+        // 工程建设其他费用 算法:取清单固定类别是“养护工程其他费用”的金额。
+        GCJSQTFY(tender) {
+            return cbTools.getBaseFee(fixedFlag.MAINTENANCE_EXPENSES, tender, 'common');
+        },
+        // 养护工程管理费 算法:以{建筑安装工程费}为基数,采用累进办法计算
+        YHGCGLF(tender) {
+            const baseFee = this['JZAZGCF'](tender);
+            if (!tender) {
+                calcBase.baseProgressiveFee = baseFee;
+            }
+            return calculateUtil.getProgressiveFee(baseFee, '养护工程管理费', projectObj.project.property.progressiveInterval, decimalObj.bills.totalPrice, deficiency);
+        },
+
+        // 养护工程设计费 算法:以{建筑安装工程费}为基数,采用累进办法计算。
+        YHGCSJF(tender) {
+            const baseFee = this['JZAZGCF'](tender);
+            if (!tender) {
+                calcBase.baseProgressiveFee = baseFee;
+            }
+            return calculateUtil.getProgressiveFee(baseFee, '养护工程设计费', projectObj.project.property.progressiveInterval, decimalObj.bills.totalPrice, deficiency);
+        },
+        /*  价差预备费 算法:以建筑安装工程费为基数,按设计文件编制年始至养护项目工程竣工年终的年数和年工程造价增涨率计算。
+            价差预备费 P * [(1+i)^(n-1) -1]
+            P——建筑安装工程费总额(元);
+            i——年工程造价增涨率(%);
+            n——设计文件编制年至养护项目开工年+养护项目建设期限(年)。
+        */
+        JCYBF(tender) {
+            //建筑安装工程费作为基数
+            const installFee = this['JZAZGCF'](tender);
+            //年造价增涨
+            const costGrowthRate = calcBase.project.property.costGrowthRate
+                ? calcBase.project.property.costGrowthRate
+                : 0;
+            //增涨计费年限
+            const growthPeriod = projectObj.project.property.growthPeriod
+                ? calcBase.project.property.growthPeriod
+                : 0;
+            //= P * [(1+i)^(n-1) -1]
+            return (installFee * (Math.pow(1 + costGrowthRate, growthPeriod - 1) - 1)).toDecimal(decimalObj.bills.totalPrice);
+        }
+
+    };
+
+    baseFigureTemplate.boq = {
+        //{各章清单合计}
+        // 取清单固定类别是“第100章至700章清单”的金额
+        'GZQDHJ': function (tender) {
+            return cbTools.getBaseFee(calcBase.fixedFlag.ONE_SEVEN_BILLS, tender, 'common');
+        },
+        //{专项暂定合计}
+        // 汇总专项暂定列有值的清单的金额
+        'ZXZDHJ': function (tender) {
+            let rst = 0,
+                feeField = 'common',
+                subFeeField = tender ? 'tenderTotalFee' : 'totalFee';
+            let billsData = calcBase.project.Bills.datas,
+                filterData = billsData.filter(function (data) {
+                    return data.specialProvisional;
+                });
+            for (let data of filterData) {
+                if (cbTools.isUnDef(data.feesIndex) || _.isEmpty(data.feesIndex) ||
+                    cbTools.isUnDef(data.feesIndex[feeField]) || cbTools.isUnDef(data.feesIndex[feeField][subFeeField])) {
+                    continue;
+                }
+                rst += data.feesIndex[feeField][subFeeField];
+            }
+            return rst.toDecimal(decimalObj.bills.totalPrice);
+        },
+        //{100章以外清单合计}
+        // 取清单固定清单[第100章至700章清单]的金额,但扣除清单100章下的金额。
+        // 如果是固定清单[第100章至700章清单]下100章以外清单引用此基数,要排除自身(目前只允许100章的清单使用,所以暂时不需要此判断)
+        'YBZYHQDHJ': function (tender) {
+            let oneToSeven = cbTools.findNodeByFlag(fixedFlag.ONE_SEVEN_BILLS);
+            if (!oneToSeven) {
+                return 0;
+            }
+            //100-700章固定节点的所有子节点
+            let allChildren = [];
+            function getChildren(nodes) {
+                allChildren = allChildren.concat(nodes);
+                for (let node of nodes) {
+                    if (node.children.length > 0) {
+                        getChildren(node.children);
+                    }
+                }
+            }
+            getChildren(oneToSeven.children);
+            //扣除的节点:100章的节点[100-200)
+            let deductNodes = allChildren.filter(cbTools.withingOneHundred);
+            //计算金额
+            let fullFeeField = tender ? 'common.tenderTotalFee' : 'common.totalFee';
+            return projectObj.project.calcProgram.getTotalFee([oneToSeven], deductNodes, fullFeeField).toDecimal(decimalObj.bills.totalPrice);
+        }
+    };
+}
+
+if (typeof module !== 'undefined' && !module.nodeType) {
+    module.exports = {
+        progression,
+        deficiency,
+        getDefalutAssistProductionFeeRate: function () {
+            return 5
+        }
+    };
 }