瀏覽代碼

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

Tony Kang 1 年之前
父節點
當前提交
80768b9d27
共有 76 個文件被更改,包括 4991 次插入2463 次删除
  1. 1 1
      Dockerfile
  2. 5 4
      modules/all_models/bills_template_items.js
  3. 3 1
      modules/all_models/stdBills_bills.js
  4. 4 3
      modules/all_models/stdGlj_glj.js
  5. 13 0
      modules/all_models/stdRation_lossRate.js
  6. 5 1
      modules/all_models/stdRation_ration.js
  7. 15 0
      modules/all_models/std_price_info_summary.js
  8. 47 43
      modules/bill_code_lib/controllers/bill_code_controller.js
  9. 17 17
      modules/bills_lib/controllers/stdBillsLib_permissionController.js
  10. 51 47
      modules/bills_template_lib/controllers/bills_template_controller.js
  11. 42 37
      modules/calc_program_lib/controllers/calc_program_controller.js
  12. 6 0
      modules/common/base/base_util.js
  13. 45 40
      modules/fee_rate_lib/controllers/fee_rate_controller.js
  14. 41 36
      modules/main_col_lib/controllers/main_col_controller.js
  15. 69 63
      modules/material_replace_lib/controllers/material_replace_controller.js
  16. 47 43
      modules/monomer_template_lib/controllers/monomer_template_controller.js
  17. 43 4
      modules/price_info_lib/controllers/index.js
  18. 207 106
      modules/price_info_lib/facade/index.js
  19. 4 1
      modules/price_info_lib/routes/index.js
  20. 77 0
      modules/price_info_summary/controllers/index.js
  21. 80 0
      modules/price_info_summary/facade/index.js
  22. 19 0
      modules/price_info_summary/routes/index.js
  23. 45 40
      modules/project_feature_lib/controllers/project_feature_controller.js
  24. 41 0
      modules/ration_repository/controllers/loss_controller.js
  25. 10 1
      modules/ration_repository/controllers/repository_views_controller.js
  26. 92 0
      modules/ration_repository/models/lossRate.js
  27. 50 45
      modules/ration_repository/routes/ration_rep_routes.js
  28. 87 85
      modules/reports/controllers/rpt_tpl_controller.js
  29. 1 1
      modules/std_billsGuidance_lib/controllers/libController.js
  30. 10 4
      modules/std_billsGuidance_lib/controllers/viewController.js
  31. 117 115
      modules/std_billsGuidance_lib/facade/facades.js
  32. 44 40
      modules/std_bills_unitprice_feature_lib/controllers/bills_unitprice_feature_controller.js
  33. 20 19
      modules/std_glj_lib/controllers/gljMapController.js
  34. 23 20
      modules/std_glj_lib/controllers/viewsController.js
  35. 2 1
      modules/std_glj_lib/models/gljMapModel.js
  36. 47 42
      modules/structural_segment_lib/controllers/structural_segment_controller.js
  37. 27 0
      modules/users/controllers/compilation_controller.js
  38. 24 19
      modules/users/controllers/login_controller.js
  39. 35 20
      modules/users/controllers/manager_controller.js
  40. 10 0
      modules/users/models/compilation_model.js
  41. 64 55
      modules/users/models/engineering_lib_model.js
  42. 1 0
      modules/users/routes/compilation_route.js
  43. 6 5
      package.json
  44. 139 0
      public/cut_word/segmentit.js
  45. 17 10
      public/web/lock_util.js
  46. 8 5
      web/common/js/slideResize.js
  47. 338 291
      web/maintain/bill_template_lib/js/bills_template_edit.js
  48. 43 2
      web/maintain/bills_lib/html/qingdan.html
  49. 18 1
      web/maintain/bills_lib/scripts/bills_lib_setting.js
  50. 14 3
      web/maintain/bills_lib/scripts/db_controller.js
  51. 35 0
      web/maintain/price_info_lib/css/index.css
  52. 40 2
      web/maintain/price_info_lib/html/edit.html
  53. 87 0
      web/maintain/price_info_lib/js/common.js
  54. 0 891
      web/maintain/price_info_lib/js/index.js
  55. 179 0
      web/maintain/price_info_lib/js/priceArea.js
  56. 493 0
      web/maintain/price_info_lib/js/priceClass.js
  57. 317 0
      web/maintain/price_info_lib/js/priceEmpty.js
  58. 184 0
      web/maintain/price_info_lib/js/priceItem.js
  59. 27 0
      web/maintain/price_info_lib/js/priceKeyword.js
  60. 96 0
      web/maintain/price_info_lib/js/priceRecommend.js
  61. 91 0
      web/maintain/price_info_summary/css/index.css
  62. 54 0
      web/maintain/price_info_summary/html/main.html
  63. 26 0
      web/maintain/price_info_summary/js/index.js
  64. 259 0
      web/maintain/price_info_summary/js/summarySheet.js
  65. 1 1
      web/maintain/ration_repository/anzhuang.html
  66. 10 2
      web/maintain/ration_repository/dinge.html
  67. 193 159
      web/maintain/ration_repository/js/coe.js
  68. 444 0
      web/maintain/ration_repository/js/lossRate.js
  69. 200 132
      web/maintain/ration_repository/js/ration_glj.js
  70. 26 2
      web/maintain/report/html/rpt_tpl_dtl_info.html
  71. 22 0
      web/maintain/report/js/rpt_tpl_main.js
  72. 8 1
      web/maintain/std_glj_lib/js/glj.js
  73. 11 0
      web/users/js/compilation.js
  74. 3 1
      web/users/js/manager.js
  75. 1 1
      web/users/views/compilation/add.html
  76. 10 0
      web/users/views/manager/authority.html

+ 1 - 1
Dockerfile

@@ -6,7 +6,7 @@ WORKDIR /ConstructionOperation
 COPY package.json /ConstructionOperation
 
 RUN mkdir tmp \
-    && cnpm install
+    && cnpm install --registry=https://registry.npmmirror.com/
 
 FROM base-alpine:latest
 

+ 5 - 4
modules/all_models/bills_template_items.js

@@ -26,20 +26,21 @@ let BillsTemplateSchema = {
     // 类别
     type: Number,
     // 标记
-    flags:{
+    flags: {
         type: [flagsSchema],
         default: []
     },
     // 所属模板库ID
-    libID: {type:String,index:true},
+    libID: { type: String, index: true },
     //计算基数
     calcBase: String,
     itemCharacterText: String,
     jobContentText: String,
+    cantDelete: Boolean,
     //费率ID
-    feeRateID:Number,
+    feeRateID: Number,
     quantity: String,
 };
 
-mongoose.model(collectionName, new Schema(BillsTemplateSchema, {versionKey: false, collection: collectionName}));
+mongoose.model(collectionName, new Schema(BillsTemplateSchema, { versionKey: false, collection: collectionName }));
 

+ 3 - 1
modules/all_models/stdBills_bills.js

@@ -19,7 +19,7 @@ const stdBills_bills = new Schema({
     name: String,
     unit: String,
     ruleText: String,
-    engineerContent:String,//工程内容
+    engineerContent: String,//工程内容
     engineering: Number, //工程专业,填计算程序工程专业ID
     fixedFlag: Number,  //固定ID
     Expression: String,
@@ -32,6 +32,8 @@ const stdBills_bills = new Schema({
     sectionInfo: Schema.Types.Mixed,
     deleted: Boolean,
     kind: Number, // 前台里(重构、微前端)BRType: 1: 大项费用、 8:XMJ 4:清单
+    erratumRecord: String, // 勘误记录
+    unitPrice: Number, // 单价
 },
     { versionKey: false }
 );

+ 4 - 3
modules/all_models/stdGlj_glj.js

@@ -14,8 +14,8 @@ const std_gljComponent = new Schema(
             default: {}
         }
     },
-    {_id: false},
-    {versionKey: false}
+    { _id: false },
+    { versionKey: false }
 );
 
 const std_glj = new Schema({
@@ -40,6 +40,7 @@ const std_glj = new Schema({
     purchaseStorageRate: Number, //采购保管费率
     offSiteTransportLossRate: Number, //场外运输损耗率
     handlingLossRate: Number, //每增一次装卸损耗率
-},{versionKey: false});
+    lossRate: Number, // 损耗率
+}, { versionKey: false });
 
 mongoose.model('std_glj_lib_gljList', std_glj, 'std_glj_lib_gljList');

+ 13 - 0
modules/all_models/stdRation_lossRate.js

@@ -0,0 +1,13 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+
+const lossRateSchema = new Schema({
+  libID: { type: String, index: true },
+  ID: String,
+  serialNo: Number,
+  name: String,
+  rate: Number,
+});
+
+mongoose.model('std_ration_lib_loss_rate_list', lossRateSchema, 'std_ration_lib_loss_rate_list');

+ 5 - 1
modules/all_models/stdRation_ration.js

@@ -7,7 +7,11 @@ const Schema = mongoose.Schema;
 const rationGljItemSchema = new Schema({
     gljId: Number,
     consumeAmt: Number,
-    proportion: { type: Number, default: 0 } //配合比,暂时无需使用,默认0
+    proportion: { type: Number, default: 0 }, //配合比,暂时无需使用,默认0
+    lossRateID: String,
+    lossRateNo: Number,
+    lossRateName: String,
+    lossRate: Number,
 }, { _id: false });
 
 const rationAssItemSchema = new Schema({

+ 15 - 0
modules/all_models/std_price_info_summary.js

@@ -0,0 +1,15 @@
+const mongoose = require('mongoose');
+
+const Schema = mongoose.Schema;
+const collectionName = 'std_price_info_summary';
+
+const modelSchema = {
+  ID: { type: String, required: true },
+  code: String, // 主从对应码
+  classCode: String, // 别名编码
+  expString: String, // 计算式,
+  name: String, // 材料名称
+  specs: String, // 规格型号
+  unit: String, // 单位
+};
+mongoose.model(collectionName, new Schema(modelSchema, { versionKey: false, collection: collectionName }));

+ 47 - 43
modules/bill_code_lib/controllers/bill_code_controller.js

@@ -6,111 +6,115 @@ import billCodeFacade from "../facade/bill_code_facade";
 let config = require("../../../config/config.js");
 let logger = require('../../../logs/log_helper').logger;
 import CompilationModel from '../../users/models/compilation_model';
+import { checkCompilationPermission } from '../../common/base/base_util';
 
-class BillCodeController extends BaseController{
+class BillCodeController extends BaseController {
     async main(request, response) {
-     
+
         let compilationModel = new CompilationModel();
-        let compilationList = await compilationModel.getCompilationList({_id: 1, name: 1});
-        compilationList.unshift({_id: 'all', name: '所有'});
+        let compilationList = await compilationModel.getPermissionCompilationList(request, { _id: 1, name: 1 });
+        compilationList.unshift({ _id: 'all', name: '所有' });
         let activeCompilation = compilationList.find(compilation => compilation._id.toString() === request.query.filter);
         if (activeCompilation) {
             activeCompilation.active = 'active';
         } else {
             compilationList[0].active = 'active'
         }
-        let filter = request.query.filter ? {compilationID: request.query.filter} : {};
-        let templateLibs = await billCodeFacade.findByCondition(filter,{feature:0},false);
+        let filter = request.query.filter ? { compilationID: request.query.filter } : {};
+        let templateLibs = await billCodeFacade.findByCondition(filter, { feature: 0 }, false);
+        const compilationPermission = request.session.managerData.compilationPermission || [];
+        templateLibs = templateLibs.filter(lib => compilationPermission.includes(lib.compilationID));
         let renderData = {
-            title:'递延清单',
+            title: '递延清单',
             userAccount: request.session.managerData.username,
             userID: request.session.managerData.userID,
-            templateLibs:templateLibs,
+            templateLibs: templateLibs,
             compilationList: compilationList,
             layout: 'maintain/common/html/layout'
         };
         response.render("maintain/bill_code_lib/html/main", renderData);
     }
-    async addLib(request, response){
+    async addLib(request, response) {
         try {
             await billCodeFacade.addLib(request.body);
-        }catch (error) {
+        } catch (error) {
             console.log(error);
         }
         response.redirect(request.headers.referer);
     }
-    async findLib(request, response){
-        let result={
-            error:0
+    async findLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let conditions={'ID' : data.ID};
+            let conditions = { 'ID': data.ID };
             let resultData = await billCodeFacade.findByCondition(conditions);
-            result.data=resultData;
-        }catch (err){
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async saveLib(request, response){
-        let result={
-            error:0
+    async saveLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
             console.log(data.data);
-            let resultData= await billCodeFacade.saveLib(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await billCodeFacade.saveLib(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async deleteLibByID(request,response){
+    async deleteLibByID(request, response) {
         logger.info(`delete projectFeatureLib ${request.ip}`);
-        let result={
-            error:0
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await billCodeFacade.deleteLibByID(data.ID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await billCodeFacade.deleteLibByID(data.ID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async edit(request,response){
+    async edit(request, response) {
         //先取出替换库信息:
         let libID = request.params.libID;
-       
-        let billCodeSegment = await billCodeFacade.findByCondition({'ID':libID});
-        let items = await billCodeFacade.findItemsByCondition({'libID':libID},"-_id -libID -ID");
+
+        let billCodeSegment = await billCodeFacade.findByCondition({ 'ID': libID });
+        let items = await billCodeFacade.findItemsByCondition({ 'libID': libID }, "-_id -libID -ID");
         console.log(items);
-        if(billCodeSegment){
+        if (billCodeSegment) {
+            checkCompilationPermission(request, response, billCodeSegment.compilationID, '/billCode/main')
             let randerData = {
-                title:'递延清单',
-                mainURL:'/billCode/main',
-                libName:billCodeSegment.name,
+                title: '递延清单',
+                mainURL: '/billCode/main',
+                libName: billCodeSegment.name,
                 userAccount: request.session.managerData.username,
                 userID: request.session.managerData.userID,
-                templateLibs:JSON.stringify({data:items}),
-                libID:libID,
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                templateLibs: JSON.stringify({ data: items }),
+                libID: libID,
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
                 layout: 'maintain/common/html/edit_layout'
             };
             response.render("maintain/bill_code_lib/html/edit", randerData);
-        }else {
+        } else {
             response.redirect(request.headers.referer);
         }
     }

+ 17 - 17
modules/bills_lib/controllers/stdBillsLib_permissionController.js

@@ -5,51 +5,51 @@ let billsController = require("./bills_lib_controllers");
 import baseController from "../../common/base/base_controller";
 import CompilationModel from "../../users/models/compilation_model";
 
-let callback = function(req, res, err, message, data){
-    res.json({error: err, message: message, data: data});
+let callback = function (req, res, err, message, data) {
+    res.json({ error: err, message: message, data: data });
 }
-class billsLibPermContr extends baseController{
-    async getCompilationList(req, res){
-        try{
+class billsLibPermContr extends baseController {
+    async getCompilationList(req, res) {
+        try {
             let compilationModel = new CompilationModel(), rst = [];
-            let compilationList = await compilationModel.getCompilationList();
-            if(compilationList.length <= 0){
+            let compilationList = await compilationModel.getPermissionCompilationList(req);
+            if (compilationList.length <= 0) {
                 throw '没有数据';
             }
-            else{
+            else {
 
                 compilationList.forEach(function (compilation) {
-                    rst.push({_id: compilation._id, name: compilation.name});
+                    rst.push({ _id: compilation._id, name: compilation.name });
                 })
                 callback(req, res, false, '', rst);
             }
         }
-        catch(err) {
+        catch (err) {
             callback(req, res, err, '没有数据', null);
         }
     }
-    getMaxNumber(req, res){
+    getMaxNumber(req, res) {
         billsController.getMaxNumber(req, res);
     }
-    getABillsLib(req, res){
+    getABillsLib(req, res) {
         billsController.getABillsLib(req, res);
     }
-    getStdBillsLib(req, res){
+    getStdBillsLib(req, res) {
         billsController.getStdBillsLib(req, res);
     }
-    createStdBillsLib(req, res){
+    createStdBillsLib(req, res) {
         billsController.createStdBillsLib(req, res);
     }
     copyStdBillsLib(req, res) {
         billsController.copyStdBillsLib(req, res);
     }
-    deleteStdBillsLib(req, res){
+    deleteStdBillsLib(req, res) {
         billsController.deleteStdBillsLib(req, res);
     }
-    renameStdBillsLib(req, res){
+    renameStdBillsLib(req, res) {
         billsController.renameStdBillsLib(req, res);
     }
-    getStdBillsLibName(req, res){
+    getStdBillsLibName(req, res) {
         billsController.getStdBillsLibName(req, res);
     }
 

+ 51 - 47
modules/bills_template_lib/controllers/bills_template_controller.js

@@ -8,8 +8,9 @@
 import BaseController from "../../common/base/base_controller";
 import CompilationModel from '../../users/models/compilation_model';
 import billsTemplateFacade from "../facade/bills_template_facade";
-import {default as BillsFixedFlagConst, List as BillsFixedFlagList} from "../../common/const/bills_fixed.js";
-import {default as BillsTypeFlagConst, List as BillsTypeFlagList} from "../../common/const/bills_type.js";
+import { default as BillsFixedFlagConst, List as BillsFixedFlagList } from "../../common/const/bills_fixed.js";
+import { default as BillsTypeFlagConst, List as BillsTypeFlagList } from "../../common/const/bills_type.js";
+import { checkCompilationPermission } from '../../common/base/base_util';
 let config = require("../../../config/config.js");
 
 class BillsTemplateController extends BaseController {
@@ -22,11 +23,13 @@ class BillsTemplateController extends BaseController {
      * @return {void}
      */
     async main(request, response) {
-        let filter = request.query.filter ? {compilationId: request.query.filter} : null;
+        let filter = request.query.filter ? { compilationId: request.query.filter } : null;
         let templateLibs = await billsTemplateFacade.getAllLibs(filter);
+        const compilationPermission = request.session.managerData.compilationPermission || [];
+        templateLibs = templateLibs.filter(lib => compilationPermission.includes(lib.compilationId));
         let compilationModel = new CompilationModel();
-        let compilationList = await compilationModel.getCompilationList({_id: 1, name: 1});
-        compilationList.unshift({_id: 'all', name: '所有'});
+        let compilationList = await compilationModel.getPermissionCompilationList(request, { _id: 1, name: 1 });
+        compilationList.unshift({ _id: 'all', name: '所有' });
         let activeCompilation = compilationList.find(compilation => compilation._id.toString() === request.query.filter);
         if (activeCompilation) {
             activeCompilation.active = 'active';
@@ -34,119 +37,120 @@ class BillsTemplateController extends BaseController {
             compilationList[0].active = 'active'
         }
         let randerData = {
-            title:'清单模板',
+            title: '清单模板',
             userAccount: request.session.managerData.username,
             userID: request.session.managerData.userID,
-            templateLibs:templateLibs,
+            templateLibs: templateLibs,
             compilationList: compilationList,
             layout: 'maintain/common/html/layout',
-            LicenseKey:config.getLicenseKey(process.env.NODE_ENV)
+            LicenseKey: config.getLicenseKey(process.env.NODE_ENV)
         };
 
         response.render("maintain/bill_template_lib/html/main", randerData);
     }
     async updateBillsTemplateItem(request, response) {
         let libID = request.params.libID;
-        let result = {error: 1, message: '更新数据错误', data: null};
+        let result = { error: 1, message: '更新数据错误', data: null };
         try {
             let data = JSON.parse(request.body.data);
-            if(libID){
-                 result = await await billsTemplateFacade.updateTemplateItem(libID,data);
+            if (libID) {
+                result = await await billsTemplateFacade.updateTemplateItem(libID, data);
                 if (result) {
-                    result = {error: 0, message: '', data: data};
+                    result = { error: 0, message: '', data: data };
                 }
             }
 
-        }catch (err){
+        } catch (err) {
             console.log(err);
             result.message = err.message;
         }
         response.json(result);
     }
 
-    async editTemplate(request, response){
+    async editTemplate(request, response) {
 
         //先取出清单库信息:
         let libID = request.params.libID;
         let templateLib = await billsTemplateFacade.getLibByID(libID);
-        if(templateLib){
+        if (templateLib) {
+            checkCompilationPermission(request, response, templateLib.compilationId, '/billsTemplate/main');
 
             let templateDatas = await billsTemplateFacade.getTemplateDatasByLibID(libID);
 
             let randerData = {
-                title:'清单模板',
-                mainURL:'/billsTemplate/main',
-                libName:templateLib.name,
+                title: '清单模板',
+                mainURL: '/billsTemplate/main',
+                libName: templateLib.name,
                 userAccount: request.session.managerData.username,
                 userID: request.session.managerData.userID,
-                billsTemplateData:JSON.stringify(templateDatas),
+                billsTemplateData: JSON.stringify(templateDatas),
                 billsFixedFlagList: JSON.stringify(BillsFixedFlagList),
                 billsTypeFlagList: JSON.stringify(BillsTypeFlagList),
-                libID:libID,
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                libID: libID,
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
                 layout: 'maintain/common/html/edit_layout'
             };
             response.render("maintain/bill_template_lib/html/edit", randerData);
-        }else {
+        } else {
             response.redirect(request.headers.referer);
         }
     }
 
-    async deleteLibByID(request,response){
-        let result={
-            error:0
+    async deleteLibByID(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await billsTemplateFacade.deleteLibByID(data.ID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await billsTemplateFacade.deleteLibByID(data.ID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
 
-    async saveLib(request, response){
-        let result={
-            error:0
+    async saveLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await billsTemplateFacade.saveLib(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await billsTemplateFacade.saveLib(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
 
-    async getLibByID(request, response){
-        let result={
-            error:0
+    async getLibByID(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await billsTemplateFacade.getLibByID(data.libID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await billsTemplateFacade.getLibByID(data.libID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
 
-    async addLib(request, response){
+    async addLib(request, response) {
         try {
             await billsTemplateFacade.addLib(request.body);
-        }catch (error) {
+        } catch (error) {
             console.log(error);
         }
         response.redirect(request.headers.referer);
@@ -157,10 +161,10 @@ class BillsTemplateController extends BaseController {
             const data = JSON.parse(req.body.data);
             const userName = req.session.managerData.username;
             await billsTemplateFacade.copyLib(data.libID, data.name, userName);
-            res.json({error: 0, data: null, message: 'success'})
+            res.json({ error: 0, data: null, message: 'success' })
         } catch (err) {
             console.log(err);
-            res.json({error: 1, data: null, message: 'fail'});
+            res.json({ error: 1, data: null, message: 'fail' });
         }
     }
 }

+ 42 - 37
modules/calc_program_lib/controllers/calc_program_controller.js

@@ -5,20 +5,23 @@ import BaseController from "../../common/base/base_controller";
 import CompilationModel from '../../users/models/compilation_model';
 let config = require("../../../config/config.js");
 import calcProgramFacade from "../facade/calc_program_facade";
+import { checkCompilationPermission } from '../../common/base/base_util';
 
 class CalcProgramController extends BaseController {
     async main(request, response) {
         let compilationModel = new CompilationModel();
-        let compilationList = await compilationModel.getCompilationList({_id: 1, name: 1});
-        compilationList.unshift({_id: 'all', name: '所有'});
+        let compilationList = await compilationModel.getPermissionCompilationList(request, { _id: 1, name: 1 });
+        compilationList.unshift({ _id: 'all', name: '所有' });
         let activeCompilation = compilationList.find(compilation => compilation._id.toString() === request.query.filter);
         if (activeCompilation) {
             activeCompilation.active = 'active';
         } else {
             compilationList[0].active = 'active'
         }
-        let filter = request.query.filter ? {compilationId: request.query.filter} : {};
-        let calcProgramLibs = await calcProgramFacade.findByCondition(filter, {templates: 0}, false);
+        let filter = request.query.filter ? { compilationId: request.query.filter } : {};
+        let calcProgramLibs = await calcProgramFacade.findByCondition(filter, { templates: 0 }, false);
+        const compilationPermission = request.session.managerData.compilationPermission || [];
+        calcProgramLibs.filter(lib => compilationPermission.includes(lib.compilationId));
         let randerData = {
             title: '计算程序模板库',
             userAccount: request.session.managerData.username,
@@ -29,81 +32,83 @@ class CalcProgramController extends BaseController {
         };
         response.render("maintain/calc_program_lib/html/main", randerData);
     }
-    async addLib(request, response){
+    async addLib(request, response) {
         try {
             await calcProgramFacade.addLib(request.body);
-        }catch (error) {
+        } catch (error) {
             console.log(error);
         }
         response.redirect(request.headers.referer);
     }
-    async findLib(request, response){
-        let result={
-            error:0
+    async findLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let conditions={ID:data.ID};
+            let conditions = { ID: data.ID };
             let resultData = await calcProgramFacade.findByCondition(conditions);
-            result.data=resultData;
-        }catch (err){
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async saveLib(request, response){
-        let result={
-            error:0
+    async saveLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await calcProgramFacade.saveLib(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await calcProgramFacade.saveLib(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async deleteLibByID(request,response){
-        let result={
-            error:0
+    async deleteLibByID(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await calcProgramFacade.deleteLibByID(data.ID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await calcProgramFacade.deleteLibByID(data.ID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async edit(request,response){
+    async edit(request, response) {
         //先取出替换库信息:
         let libID = request.params.libID;
-        let programLib = await calcProgramFacade.findByCondition({'ID':libID});
-        if(programLib){
+        let programLib = await calcProgramFacade.findByCondition({ 'ID': libID });
+        if (programLib) {
+            checkCompilationPermission(request, response, programLib.compilationId, '/calcProgram/main');
+
             let randerData = {
-                title:'计算程序模板库',
-                mainURL:'/calcProgram/main',
-                libName:programLib.libName,
+                title: '计算程序模板库',
+                mainURL: '/calcProgram/main',
+                libName: programLib.libName,
                 userAccount: request.session.managerData.username,
                 userID: request.session.managerData.userID,
-                templateList:JSON.stringify(programLib.templates),
-                libID:libID,
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                templateList: JSON.stringify(programLib.templates),
+                libID: libID,
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
                 layout: 'maintain/common/html/edit_layout'
             };
             response.render("maintain/calc_program_lib/html/edit", randerData);
-        }else {
+        } else {
             response.redirect(request.headers.referer);
         }
     }

+ 6 - 0
modules/common/base/base_util.js

@@ -0,0 +1,6 @@
+export const checkCompilationPermission = (req, res, compilationID, redirectUrl) => {
+  const compilationPermission = req.session.managerData.compilationPermission || [];
+  if (!compilationPermission.includes(compilationID)) {
+    res.redirect(redirectUrl);
+  }
+}

+ 45 - 40
modules/fee_rate_lib/controllers/fee_rate_controller.js

@@ -5,105 +5,110 @@ import BaseController from "../../common/base/base_controller";
 import CompilationModel from '../../users/models/compilation_model';
 let config = require("../../../config/config.js");
 import feeRateFacade from "../facade/fee_rate_facade";
+import { checkCompilationPermission } from '../../common/base/base_util';
 
-class FeeRateController extends BaseController{
+class FeeRateController extends BaseController {
     async main(request, response) {
         let compilationModel = new CompilationModel();
-        let compilationList = await compilationModel.getCompilationList({_id: 1, name: 1});
-        compilationList.unshift({_id: 'all', name: '所有'});
+        let compilationList = await compilationModel.getPermissionCompilationList(request, { _id: 1, name: 1 });
+        compilationList.unshift({ _id: 'all', name: '所有' });
         let activeCompilation = compilationList.find(compilation => compilation._id.toString() === request.query.filter);
         if (activeCompilation) {
             activeCompilation.active = 'active';
         } else {
             compilationList[0].active = 'active'
         }
-        let filter = request.query.filter ? {compilationId: request.query.filter} : {};
-        let feeRateLibs = await feeRateFacade.findByCondition(filter,{rates:0},false);
+        let filter = request.query.filter ? { compilationId: request.query.filter } : {};
+        let feeRateLibs = await feeRateFacade.findByCondition(filter, { rates: 0 }, false);
+        const compilationPermission = request.session.managerData.compilationPermission || [];
+        feeRateLibs = feeRateLibs.filter(lib => compilationPermission.includes(lib.compilationId));
         let randerData = {
-            title:'费率标准库',
+            title: '费率标准库',
             userAccount: request.session.managerData.username,
             userID: request.session.managerData.userID,
-            feeRateLibs:feeRateLibs,
+            feeRateLibs: feeRateLibs,
             compilationList: compilationList,
             layout: 'maintain/common/html/layout'
         };
         response.render("maintain/fee_rate_lib/html/main", randerData);
     }
-    async addLib(request, response){
+    async addLib(request, response) {
         try {
             await feeRateFacade.addLib(request.body);
-        }catch (error) {
+        } catch (error) {
             console.log(error);
         }
         response.redirect(request.headers.referer);
     }
-    async findLib(request, response){
-        let result={
-            error:0
+    async findLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let conditions={ID:data.ID};
+            let conditions = { ID: data.ID };
             let resultData = await feeRateFacade.findByCondition(conditions);
-            result.data=resultData;
-        }catch (err){
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async saveLib(request, response){
-        let result={
-            error:0
+    async saveLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await feeRateFacade.saveLib(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await feeRateFacade.saveLib(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async deleteLibByID(request,response){
-        let result={
-            error:0
+    async deleteLibByID(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await feeRateFacade.deleteLibByID(data.ID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await feeRateFacade.deleteLibByID(data.ID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async edit(request,response){
+    async edit(request, response) {
         //先取出替换库信息:
         let libID = request.params.libID;
-        let feeRateLib = await feeRateFacade.findByCondition({'ID':libID});
-        if(feeRateLib){
+        let feeRateLib = await feeRateFacade.findByCondition({ 'ID': libID });
+        if (feeRateLib) {
+            checkCompilationPermission(request, response, feeRateLib.compilationId, '/feeRate/main');
+
             let randerData = {
-                title:'费率标准库',
-                mainURL:'/feeRate/main',
-                libName:feeRateLib.libName,
+                title: '费率标准库',
+                mainURL: '/feeRate/main',
+                libName: feeRateLib.libName,
                 userAccount: request.session.managerData.username,
                 userID: request.session.managerData.userID,
-                rateList:JSON.stringify(feeRateLib.rates),
-                libID:libID,
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                rateList: JSON.stringify(feeRateLib.rates),
+                libID: libID,
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
                 layout: 'maintain/common/html/edit_layout'
             };
             response.render("maintain/fee_rate_lib/html/edit", randerData);
-        }else {
+        } else {
             response.redirect(request.headers.referer);
         }
     }

+ 41 - 36
modules/main_col_lib/controllers/main_col_controller.js

@@ -8,105 +8,110 @@
  */
 import BaseController from "../../common/base/base_controller";
 import mainColFacade from "../facade/main_col_facade";
+import { checkCompilationPermission } from '../../common/base/base_util';
+
 let config = require("../../../config/config.js");
 
 class MainColController extends BaseController {
 
     async main(request, response) {
         let mainColLibs = await mainColFacade.getAllLibs();
+        const compilationPermission = request.session.managerData.compilationPermission || [];
+        mainColLibs = mainColLibs.filter(lib => compilationPermission.includes(lib.compilationId));
         let randerData = {
-            title:'列设置',
+            title: '列设置',
             userAccount: request.session.managerData.username,
             userID: request.session.managerData.userID,
-            mainColLibs:mainColLibs,
-            LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+            mainColLibs: mainColLibs,
+            LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
             layout: 'maintain/common/html/layout'
         };
 
         response.render("maintain/main_col_lib/html/main", randerData);
     }
 
-    async addLib(request, response){
+    async addLib(request, response) {
         try {
             await mainColFacade.addLib(request.body);
-        }catch (error) {
+        } catch (error) {
             console.log(error);
         }
         response.redirect(request.headers.referer);
     }
 
-    async deleteLibByID(request,response){
-        let result={
-            error:0
+    async deleteLibByID(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await mainColFacade.deleteLibByID(data.ID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await mainColFacade.deleteLibByID(data.ID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
 
-    async getLibByID(request, response){
-        let result={
-            error:0
+    async getLibByID(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await mainColFacade.getLibByID(data.libID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await mainColFacade.getLibByID(data.libID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
 
-    async saveLib(request, response){
-        let result={
-            error:0
+    async saveLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await mainColFacade.saveLib(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await mainColFacade.saveLib(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
 
-    async edit(request,response){
+    async edit(request, response) {
         let result = {
-            error:0
+            error: 0
         }
         //先取出替换库信息:
         let libID = request.params.libID;
         let colLib = await mainColFacade.getLibByID(libID);
-        if(colLib){
+        if (colLib) {
+            checkCompilationPermission(request, response, colLib.compilationId, '/mainTreeCol/main')
             let randerData = {
-                title:'列设置库',
-                mainURL:'/mainTreeCol/main',
-                libName:colLib.name,
+                title: '列设置库',
+                mainURL: '/mainTreeCol/main',
+                libName: colLib.name,
                 userAccount: request.session.managerData.username,
                 userID: request.session.managerData.userID,
-                main_tree_col:JSON.stringify(colLib.main_tree_col),
-                libID:libID,
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                main_tree_col: JSON.stringify(colLib.main_tree_col),
+                libID: libID,
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
                 layout: 'maintain/common/html/edit_layout'
             };
             response.render("maintain/main_col_lib/html/edit", randerData);
-        }else {
+        } else {
             response.redirect(request.headers.referer);
         }
 

+ 69 - 63
modules/material_replace_lib/controllers/material_replace_controller.js

@@ -4,8 +4,10 @@
 import BaseController from "../../common/base/base_controller";
 import materialFacade from "../facade/material_replace_facade";
 let config = require("../../../config/config.js");
+import { checkCompilationPermission } from '../../common/base/base_util';
 
-class ReplaceController extends BaseController{
+
+class ReplaceController extends BaseController {
     /**
      * 材料替换库页面
      *
@@ -14,151 +16,155 @@ class ReplaceController extends BaseController{
      * @return {void}
      */
     async main(request, response) {
-        let materialLibs = await materialFacade.findByCondition({},null,false);
+        let materialLibs = await materialFacade.findByCondition({}, null, false);
+        const compilationPermission = request.session.managerData.compilationPermission || [];
+        materialLibs = materialLibs.filter(lib => compilationPermission.includes(lib.compilationId));
         let randerData = {
-            title:'材料替换库',
+            title: '材料替换库',
             userAccount: request.session.managerData.username,
             userID: request.session.managerData.userID,
-            materialLibs:materialLibs,
+            materialLibs: materialLibs,
             layout: 'maintain/common/html/layout'
         };
         response.render("maintain/material_replace_lib/html/main", randerData);
     }
-    async edit(request,response){
+    async edit(request, response) {
         //先取出替换库信息:
         let libID = request.params.libID;
-        let materialLib = await materialFacade.findByCondition({'ID':libID});
-        if(materialLib){
+        let materialLib = await materialFacade.findByCondition({ 'ID': libID });
+        if (materialLib) {
+            checkCompilationPermission(request, response, materialLib.compilationId, '/materialReplace/main');
+
             let billsLibId = materialLib.billsLibId;
             let compilationId = materialLib.compilationId;
-            let gljLib = await  materialFacade.findGLJLibByComID(compilationId);
+            let gljLib = await materialFacade.findGLJLibByComID(compilationId);
             let billsList = await materialFacade.findBillsByLibID(libID);
             //let templateDatas = await materialFacade.getTemplateDatasByLibID(libID);
             //let stdBills = await  materialFacade.getStdBillsByLib(billsLibId);
 
             let randerData = {
-                title:'材料替换库',
-                mainURL:'/materialReplace/main',
-                libName:materialLib.name,
+                title: '材料替换库',
+                mainURL: '/materialReplace/main',
+                libName: materialLib.name,
                 userAccount: request.session.managerData.username,
                 userID: request.session.managerData.userID,
-                billsList:JSON.stringify(billsList),
-                billsLibId:billsLibId,
-                gljLibID:gljLib.ID,
-                libID:libID,
+                billsList: JSON.stringify(billsList),
+                billsLibId: billsLibId,
+                gljLibID: gljLib.ID,
+                libID: libID,
                 //stdBills:JSON.stringify(stdBills),
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
                 layout: 'maintain/common/html/edit_layout'
             };
             response.render("maintain/material_replace_lib/html/edit", randerData);
-        }else {
+        } else {
             response.redirect(request.headers.referer);
         }
     }
-    async findLib(request, response){
-        let result={
-            error:0
+    async findLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let conditions={};
-            if(data.compilationID) conditions.compilationId = data.compilationID;
-            if(data.billLibID) conditions.billsLibId = data.billLibID;
-            if(data.ID) conditions.ID = data.ID;
+            let conditions = {};
+            if (data.compilationID) conditions.compilationId = data.compilationID;
+            if (data.billLibID) conditions.billsLibId = data.billLibID;
+            if (data.ID) conditions.ID = data.ID;
             let resultData = await materialFacade.findByCondition(conditions);
-            result.data=resultData;
-        }catch (err){
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async addLib(request, response){
+    async addLib(request, response) {
         try {
             await materialFacade.addLib(request.body);
-        }catch (error) {
+        } catch (error) {
             console.log(error);
         }
         response.redirect(request.headers.referer);
     }
-    async saveLib(request, response){
-        let result={
-            error:0
+    async saveLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await materialFacade.saveLib(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await materialFacade.saveLib(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async deleteLibByID(request,response){
-        let result={
-            error:0
+    async deleteLibByID(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await materialFacade.deleteLibByID(data.ID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await materialFacade.deleteLibByID(data.ID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async saveBills(request,response){
-        let result={
-            error:0
+    async saveBills(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await materialFacade.saveBills(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await materialFacade.saveBills(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async saveMaterial(request,response){
-        let result={
-            error:0
+    async saveMaterial(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await materialFacade.saveMaterial(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await materialFacade.saveMaterial(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async findMaterial (request,response){
-        let result={
-            error:0
+    async findMaterial(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
             let resultData = await materialFacade.getMaterialByBillsID(data.billsItemID);
-            result.data=resultData;
-        }catch (err){
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);

+ 47 - 43
modules/monomer_template_lib/controllers/monomer_template_controller.js

@@ -3,115 +3,119 @@
  */
 import BaseController from "../../common/base/base_controller";
 import monomerTemplateFacade from "../facade/monomer_template_facade";
+import { checkCompilationPermission } from '../../common/base/base_util';
 let config = require("../../../config/config.js");
 let logger = require('../../../logs/log_helper').logger;
 import CompilationModel from '../../users/models/compilation_model';
 let mongoose = require('mongoose');
 let monomerTemplateModel = mongoose.model('std_monomer_template');
 
-class MonomerTemplateController extends BaseController{
+class MonomerTemplateController extends BaseController {
     async main(request, response) {
-     
+
         let compilationModel = new CompilationModel();
-        let compilationList = await compilationModel.getCompilationList({_id: 1, name: 1});
-        compilationList.unshift({_id: 'all', name: '所有'});
+        let compilationList = await compilationModel.getPermissionCompilationList(request, { _id: 1, name: 1 });
+        compilationList.unshift({ _id: 'all', name: '所有' });
         let activeCompilation = compilationList.find(compilation => compilation._id.toString() === request.query.filter);
         if (activeCompilation) {
             activeCompilation.active = 'active';
         } else {
             compilationList[0].active = 'active'
         }
-        let filter = request.query.filter ? {compilationID: request.query.filter} : {};
-        let templateLibs = await monomerTemplateFacade.findByCondition(filter,{feature:0},false);
+        let filter = request.query.filter ? { compilationID: request.query.filter } : {};
+        let templateLibs = await monomerTemplateFacade.findByCondition(filter, { feature: 0 }, false);
+        const compilationPermission = request.session.managerData.compilationPermission || [];
+        templateLibs = templateLibs.filter(lib => compilationPermission.includes(lib.compilationID))
         let renderData = {
-            title:'单体模板库',
+            title: '单体模板库',
             userAccount: request.session.managerData.username,
             userID: request.session.managerData.userID,
-            templateLibs:templateLibs,
+            templateLibs: templateLibs,
             compilationList: compilationList,
             layout: 'maintain/common/html/layout'
         };
         response.render("maintain/monomer_template_lib/html/main", renderData);
     }
-    async addLib(request, response){
+    async addLib(request, response) {
         try {
             await monomerTemplateFacade.addLib(request.body);
-        }catch (error) {
+        } catch (error) {
             console.log(error);
         }
         response.redirect(request.headers.referer);
     }
-    async findLib(request, response){
-        let result={
-            error:0
+    async findLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let conditions={'ID' : data.ID};
+            let conditions = { 'ID': data.ID };
             let resultData = await monomerTemplateFacade.findByCondition(conditions);
-            result.data=resultData;
-        }catch (err){
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async saveLib(request, response){
-        let result={
-            error:0
+    async saveLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
             console.log(data.data);
-            let resultData= await monomerTemplateFacade.saveLib(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await monomerTemplateFacade.saveLib(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async deleteLibByID(request,response){
+    async deleteLibByID(request, response) {
         logger.info(`delete projectFeatureLib ${request.ip}`);
-        let result={
-            error:0
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await monomerTemplateFacade.deleteLibByID(data.ID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await monomerTemplateFacade.deleteLibByID(data.ID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async edit(request,response){
+    async edit(request, response) {
         //先取出替换库信息:
         let libID = request.params.libID;
-       
-        let monomerTemplate = await monomerTemplateFacade.findByCondition({'ID':libID});
-        
-        if(monomerTemplate){
+
+        let monomerTemplate = await monomerTemplateFacade.findByCondition({ 'ID': libID });
+
+        if (monomerTemplate) {
+            checkCompilationPermission(request, response, monomerTemplate.compilationID, '/monomerTemplate/main');
             let randerData = {
-                title:'单体模板库',
-                mainURL:'/monomerTemplate/main',
-                libName:monomerTemplate.name,
+                title: '单体模板库',
+                mainURL: '/monomerTemplate/main',
+                libName: monomerTemplate.name,
                 userAccount: request.session.managerData.username,
                 userID: request.session.managerData.userID,
-                templateLibs:JSON.stringify(monomerTemplate),
-                libID:libID,
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                templateLibs: JSON.stringify(monomerTemplate),
+                libID: libID,
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
                 layout: 'maintain/common/html/edit_layout'
             };
             response.render("maintain/monomer_template_lib/html/edit", randerData);
-        }else {
+        } else {
             response.redirect(request.headers.referer);
         }
     }

+ 43 - 4
modules/price_info_lib/controllers/index.js

@@ -1,5 +1,6 @@
 import BaseController from "../../common/base/base_controller";
 import CompilationModel from '../../users/models/compilation_model';
+import { checkCompilationPermission } from '../../common/base/base_util';
 const multiparty = require('multiparty');
 const excel = require('node-xlsx');
 const fs = require('fs');
@@ -9,7 +10,7 @@ const config = require("../../../config/config.js");
 class PriceInfoController extends BaseController {
     async main(req, res) {
         const compilationModel = new CompilationModel();
-        const compilationList = await compilationModel.getCompilationList({ _id: 1, name: 1 });
+        const compilationList = await compilationModel.getPermissionCompilationList(req, { _id: 1, name: 1 });
         compilationList.unshift({ _id: 'all', name: '所有' });
         const activeCompilation = compilationList.find(compilation => compilation._id.toString() === req.query.filter);
         if (activeCompilation) {
@@ -18,7 +19,9 @@ class PriceInfoController extends BaseController {
             compilationList[0].active = 'active'
         }
         const filter = req.query.filter ? { compilationID: req.query.filter } : {};
-        const libs = await facade.getLibs(filter);
+        let libs = await facade.getLibs(filter);
+        const compilationPermission = req.session.managerData.compilationPermission || [];
+        libs = libs.filter(lib => compilationPermission.includes(lib.compilationID));
         libs.forEach(lib => {
             compilationList.forEach(compilation => {
                 if (compilation._id.toString() === lib.compilationID) {
@@ -48,6 +51,8 @@ class PriceInfoController extends BaseController {
         if (!libs.length) {
             return res.send(404);
         }
+        checkCompilationPermission(req, res, libs[0].compilationID, '/priceInfo/main');
+
         const areaList = await facade.getAreas(libs[0].compilationID);
         const renderData = {
             compilationID: libs[0].compilationID,
@@ -219,9 +224,9 @@ class PriceInfoController extends BaseController {
 
     async calcPriceIndex(req, res) {
         try {
-            const { period, libID ,compilationID} = JSON.parse(req.body.data);
+            const { period, libID, compilationID } = JSON.parse(req.body.data);
             const areaID = '971fb9a0-0f93-11eb-b53c-45271c1df90f';//写死珠海地区
-            const data = await facade.calcPriceIndex(libID,period, areaID,compilationID);
+            const data = await facade.calcPriceIndex(libID, period, areaID, compilationID);
             res.json({ error: 0, message: 'getCLass success', data });
         } catch (err) {
             console.log(err);
@@ -240,6 +245,28 @@ class PriceInfoController extends BaseController {
         }
     }
 
+    async getPriceEmptyData(req, res) {
+        try {
+            const { libID, compilationID, areaID } = JSON.parse(req.body.data);
+            const data = await facade.getPriceEmptyData(compilationID, libID, areaID);
+            res.json({ error: 0, message: 'getPriceEmptyData success', data });
+        } catch (err) {
+            console.log(err);
+            res.json({ error: 1, message: err.toString() });
+        }
+    }
+
+    async getRecommendPriceSummaryData(req, res) {
+        try {
+            const { keyword } = JSON.parse(req.body.data);
+            const data = await facade.getRecommendPriceSummaryData(keyword);
+            res.json({ error: 0, message: 'getRecommendPriceSummaryData success', data });
+        } catch (err) {
+            console.log(err);
+            res.json({ error: 1, message: err.toString() });
+        }
+    }
+
     async editPriceData(req, res) {
         try {
             const { postData } = JSON.parse(req.body.data);
@@ -262,6 +289,18 @@ class PriceInfoController extends BaseController {
         }
     }
 
+    // 匹配总表
+    async matchSummary(req, res) {
+        try {
+            const { compilationID, libID, areaID } = JSON.parse(req.body.data);
+            await facade.matchSummary(compilationID, libID, areaID);
+            res.json({ error: 0, message: 'matchSummary success' });
+        } catch (err) {
+            console.log(err);
+            res.json({ error: 1, message: err.toString() });
+        }
+    }
+
 }
 
 module.exports = {

+ 207 - 106
modules/price_info_lib/facade/index.js

@@ -11,11 +11,12 @@ const priceInfoAreaModel = mongoose.model('std_price_info_areas');
 const compilationModel = mongoose.model('compilation');
 const importLogsModel = mongoose.model('import_logs');
 const priceInfoIndexModel = mongoose.model('std_price_info_index');
-
+const priceInfoSummaryModel = mongoose.model('std_price_info_summary');
+const { getWordArray, alias } = require('../../../public/cut_word/segmentit');
 
 
 async function getLibs(query) {
-    return await priceInfoLibModel.find(query).lean();
+    return await priceInfoLibModel.find(query).sort({ period: 1 }).lean();
 }
 
 async function createLib(name, period, compilationID) {
@@ -322,107 +323,6 @@ async function importKeyData(libID, mainData, subData) {
     }
 }
 
-/* async function importExcelData(libID, sheetData) {
-    const libs = await getLibs({ ID: libID });
-    const compilationID = libs[0].compilationID;
-    // 建立区映射表:名称-ID映射、ID-名称映射
-    const areaList = await getAreas(compilationID);
-    const areaMap = {};
-    areaList.forEach(({ ID, name }) => {
-        areaMap[name] = ID;
-        areaMap[ID] = name;
-    });
-    // 建立分类映射表:地区名称@分类名称:ID映射
-    const classMap = {};
-    const classList = await getClassData(libID);
-    classList.forEach(({ ID, areaID, name }) => {
-        const areaName = areaMap[areaID] || '';
-        classMap[`${areaName}@${name}`] = ID;
-    });
-    // 第一行获取行映射
-    const colMap = {};
-    for (let col = 0; col < sheetData[0].length; col++) {
-        const cellText = sheetData[0][col];
-        switch (cellText) {
-            case '地区':
-                colMap.area = col;
-                break;
-            case '分类':
-                colMap.class = col;
-                break;
-            case '编码':
-                colMap.code = col;
-                break;
-            case '名称':
-                colMap.name = col;
-                break;
-            case '规格型号':
-                colMap.specs = col;
-                break;
-            case '单位':
-                colMap.unit = col;
-                break;
-            case '不含税价':
-                colMap.noTaxPrice = col;
-                break;
-            case '含税价':
-                colMap.taxPrice = col;
-                break;
-        }
-    }
-    // 提取数据
-    const data = [];
-    let curAreaName;
-    let curClassName;
-    for (let row = 1; row < sheetData.length; row++) {
-        const areaName = sheetData[row][colMap.area] || '';
-        const className = sheetData[row][colMap.class] || '';
-        const code = sheetData[row][colMap.code] || '';
-        const name = sheetData[row][colMap.name] || '';
-        const specs = sheetData[row][colMap.specs] || '';
-        const unit = sheetData[row][colMap.unit] || '';
-        const noTaxPrice = sheetData[row][colMap.noTaxPrice] || '';
-        const taxPrice = sheetData[row][colMap.taxPrice] || '';
-        if (!code && !name && !specs && !noTaxPrice && !taxPrice) { // 认为是空数据
-            continue;
-        }
-        if (areaName && areaName !== curAreaName) {
-            curAreaName = areaName;
-        }
-        if (className && className !== curClassName) {
-            curClassName = className;
-        }
-        const areaID = areaMap[curAreaName];
-        if (!areaID) {
-            continue;
-        }
-        const classID = classMap[`${curAreaName}@${curClassName}`];
-        if (!classID) {
-            continue;
-        }
-        data.push({
-            ID: uuidV1(),
-            compilationID,
-            libID,
-            areaID,
-            classID,
-            period: libs[0].period,
-            code,
-            name,
-            specs,
-            unit,
-            noTaxPrice,
-            taxPrice
-        });
-    }
-    if (data.length) {
-        await priceInfoItemModel.remove({ libID });
-        await priceInfoItemModel.insertMany(data);
-    } else {
-        throw 'excel没有有效数据。'
-    }
-} */
-
 // 获取费用定额的地区数据
 async function getAreas(compilationID) {
     return await priceInfoAreaModel.find({ compilationID }, '-_id ID name serialNo').lean();
@@ -477,17 +377,28 @@ const UpdateType = {
 async function editPriceData(postData) {
     const bulks = [];
     postData.forEach(data => {
+        const filter = { ID: data.ID };
+        // 为了命中索引,ID暂时还没添加索引,数据量太大,担心内存占用太多
+        if (data.areaID) {
+            filter.areaID = data.areaID;
+        }
+        if (data.compilationID) {
+            filter.compilationID = data.compilationID;
+        }
+        if (data.period) {
+            filter.period = data.period;
+        }
         if (data.type === UpdateType.UPDATE) {
             bulks.push({
                 updateOne: {
-                    filter: { ID: data.ID },
+                    filter,
                     update: { ...data.data }
                 }
             });
         } else if (data.type === UpdateType.DELETE) {
             bulks.push({
                 deleteOne: {
-                    filter: { ID: data.ID }
+                    filter,
                 }
             });
         } else {
@@ -616,6 +527,192 @@ async function calcPriceIndex(libID, period, areaID, compilationID) {
     return message;
 }
 
+const getMatchSummaryKey = (item) => {
+    const props = ['name', 'specs', 'unit'];
+    return props.map(prop => {
+        const subKey = item[prop] ? item[prop].trim() : '';
+        return subKey;
+    }).join('@');
+
+}
+
+const getSummaryMap = (items) => {
+    const map = {};
+    items.forEach(item => {
+        const key = getMatchSummaryKey(item);
+        map[key] = item;
+    });
+    return map;
+}
+
+// 匹配总表
+// 按规则匹配信息价的编码、别名编码、计算式(只匹配珠海建筑,要单独标记珠海地区);
+// 匹配规则:名称+规格型号+单位,与总表一致则自动填入编码、别名编码、计算式(珠海建筑);
+const matchSummary = async (compilationID, libID, areaID) => {
+    const updateBulks = [];
+    const areaFilter = { compilationID };
+    if (areaID) {
+        areaFilter.ID = areaID;
+    }
+    const areas = await priceInfoAreaModel.find(areaFilter, '-_id ID name').lean();
+    const areaNameMap = {};
+    areas.forEach(area => {
+        areaNameMap[area.ID] = area.name;
+    });
+    const filter = { libID };
+    if (areaID) {
+        filter.areaID = areaID;
+    }
+    const priceItems = await priceInfoItemModel.find(filter, '-_id ID compilationID name specs unit areaID period').lean();
+    const summaryItems = await priceInfoSummaryModel.find({}, '-_id ID name specs unit code classCode expString').lean();
+    const summaryMap = getSummaryMap(summaryItems);
+    priceItems.forEach(priceItem => {
+        const key = getMatchSummaryKey(priceItem);
+        const matched = summaryMap[key];
+        if (matched) {
+            const updateObj = {
+                code: matched.code,
+                classCode: matched.classCode,
+            }
+            console.log(matched);
+            console.log(updateObj);
+            const areaName = areaNameMap[priceItem.areaID];
+            /* if (/珠海/.test(areaName)) {
+                updateObj.expString = matched.expString;
+            } */
+            updateBulks.push({
+                updateOne: {
+                    filter: { ID: priceItem.ID, compilationID: priceItem.compilationID, areaID: priceItem.areaID, period: priceItem.period },
+                    update: updateObj
+                }
+            })
+        }
+    });
+    if (updateBulks.length) {
+        console.log(`updateBulks.length`, updateBulks.length);
+        await priceInfoItemModel.bulkWrite(updateBulks);
+    }
+}
+
+// 获取空数据(没有别名编码)
+const getPriceEmptyData = async (compilationID, libID, areaID) => {
+    const lib = await priceInfoLibModel.findOne({ ID: libID }).lean();
+    if (!lib) {
+        return [];
+    }
+    const filter = { compilationID, libID, period: lib.period };
+    if (areaID) {
+        filter.areaID = areaID;
+    }
+    const priceItems = await priceInfoItemModel.find(filter).lean();
+    return priceItems.filter(item => !item.classCode);
+};
+
+const getMatchPrice = (allInfoPrice, nameArray, needHandleLongWord = true) => {
+    let items = [];
+    let maxNum = 0; // 最大匹配数
+    const matchMap = {}; // 匹配储存
+    let handleLongWord = false;
+    if (needHandleLongWord) {
+        for (const na of nameArray) {
+            if (na.length >= 5) handleLongWord = true;
+        }
+    }
+
+    for (const info of allInfoPrice) {
+        // specs
+        const matchString = alias(info.name + info.specs); // 组合名称和规格型号
+        info.matchString = matchString;
+        let matchCount = 0;
+        for (const na of nameArray) {
+            if (matchString.indexOf(na) !== -1) {
+                matchCount += 1;
+                if (needHandleLongWord && na.length >= 5) handleLongWord = false; // 有5个字的,并且匹配上了,这里就为false不用再处理一次了
+            }
+        }
+        if (matchCount > 0) {
+            if (matchMap[matchCount]) {
+                matchMap[matchCount].push(info);
+            } else {
+                matchMap[matchCount] = [info];
+            }
+            if (matchCount > maxNum) maxNum = matchCount;
+        }
+    }
+    if (maxNum > 0) items = matchMap[maxNum];
+
+    return { items, handleLongWord };
+}
+
+// 获取推荐总表数据
+const getRecommendPriceSummaryData = async (keyword) => {
+    const nameArray = getWordArray(keyword);
+    console.log(`nameArray`);
+    console.log(nameArray);
+    const allItems = await priceInfoSummaryModel.find({}).lean();
+    let { items } = getMatchPrice(allItems, nameArray);
+
+    // 按匹配位置排序 如[ '橡胶', '胶圈', '给水' ] 先显示橡胶
+    items = _.sortBy(items, item => {
+        const ms = item.matchString;
+        for (let i = 0; i < nameArray.length; i += 1) {
+            if (ms.indexOf(nameArray[i]) !== -1) return i;
+        }
+        return 0;
+    });
+
+    return items;
+}
+
+// 处理价格¥符号
+/* const handlePriceText = async () => {
+    const libs = await priceInfoLibModel.find({}).lean();
+    for (const lib of libs) {
+        const libID = lib.ID;
+        const bulks = [];
+        const items = await priceInfoItemModel.find({ libID }, '-_id ID noTaxPrice areaID period').lean();
+        items.forEach(item => {
+            if (item.noTaxPrice && /¥/.test(item.noTaxPrice)) {
+                const noTaxPrice = item.noTaxPrice.replace('¥', '').replace(',', '');
+                bulks.push({ updateOne: { filter: { ID: item.ID, areaID: item.areaID, period: item.period }, update: { $set: { noTaxPrice } } } });
+                // bulks.push({ deleteOne: { filter: { ID: item.ID, areaID: item.areaID, period: item.period } } });
+            }
+        });
+        if (bulks.length) {
+            const chunks = _.chunk(bulks, Math.floor(bulks.length / 500) + 1);
+            for (const chunk of chunks) {
+
+                if (chunk.length) {
+                    await priceInfoItemModel.bulkWrite(chunk);
+                }
+            }
+        }
+    }
+} */
+
+// 删除价格为空的
+const handlePriceText = async () => {
+    const libs = await priceInfoLibModel.find({}).lean();
+    for (const lib of libs) {
+        const libID = lib.ID;
+        const bulks = [];
+        const items = await priceInfoItemModel.find({ libID }, '-_id ID noTaxPrice areaID period').lean();
+        items.forEach(item => {
+            if (!item.noTaxPrice) {
+                bulks.push({ deleteOne: { filter: { ID: item.ID, areaID: item.areaID, period: item.period } } });
+            }
+        });
+        if (bulks.length) {
+            const chunks = _.chunk(bulks, Math.floor(bulks.length / 500) + 1);
+            for (const chunk of chunks) {
+                if (chunk.length) {
+                    await priceInfoItemModel.bulkWrite(chunk);
+                }
+            }
+        }
+    }
+}
+
 module.exports = {
     getLibs,
     createLib,
@@ -633,5 +730,9 @@ module.exports = {
     calcPriceIndex,
     getPriceData,
     editPriceData,
-    editClassData
+    editClassData,
+    matchSummary,
+    getPriceEmptyData,
+    getRecommendPriceSummaryData,
+    handlePriceText,
 }

+ 4 - 1
modules/price_info_lib/routes/index.js

@@ -25,7 +25,10 @@ module.exports = function (app) {
     router.post("/getPriceData", priceInfoController.auth, priceInfoController.init, priceInfoController.getPriceData);
     router.post("/editPriceData", priceInfoController.auth, priceInfoController.init, priceInfoController.editPriceData);
     router.post("/editClassData", priceInfoController.auth, priceInfoController.init, priceInfoController.editClassData);
-
+    router.post("/matchSummary", priceInfoController.auth, priceInfoController.init, priceInfoController.matchSummary);
+    router.post("/getPriceEmptyData", priceInfoController.auth, priceInfoController.init, priceInfoController.getPriceEmptyData);
+    router.post("/getRecommendPriceSummaryData", priceInfoController.auth, priceInfoController.init, priceInfoController.getRecommendPriceSummaryData);
+    
     app.use("/priceInfo", router);
 };
 

+ 77 - 0
modules/price_info_summary/controllers/index.js

@@ -0,0 +1,77 @@
+import BaseController from "../../common/base/base_controller";
+const excel = require('node-xlsx');
+const fs = require('fs');
+const facade = require('../facade/index');
+const config = require("../../../config/config.js");
+
+class PriceInfoSummaryController extends BaseController {
+    async main(req, res) {
+        const renderData = {
+            title: '材料信息价总表',
+            userAccount: req.session.managerData.username,
+            userID: req.session.managerData.userID,
+            LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
+        };
+        res.render("maintain/price_info_summary/html/main.html", renderData);
+    }
+
+    // 获取分页数据
+    async getPagingData(req, res) {
+        try {
+            const { page, pageSize, searchStr } = JSON.parse(req.body.data);
+            const data = await facade.getPagingData(page, pageSize, searchStr);
+            res.json({ error: 0, message: 'getData success', data });
+        } catch (err) {
+            console.log(err);
+        }
+    }
+
+    // 编辑总表
+    async editSummaryData(req, res) {
+        try {
+            const { postData } = JSON.parse(req.body.data);
+            await facade.editSummaryData(postData);
+            res.json({ error: 0, message: 'editPrice success' });
+        } catch (err) {
+            console.log(err);
+            res.json({ error: 1, message: err.toString() });
+        }
+    }
+
+    // 保存至总表
+    async saveInSummary(req, res) {
+        try {
+            const { documents } = JSON.parse(req.body.data);
+            await facade.saveInSummary(documents);
+            res.json({ error: 0, message: 'saveInSummary success' });
+        } catch (err) {
+            console.log(err);
+            res.json({ error: 1, message: err.toString() });
+        }
+    }
+
+    async exportSummaryData(request, response) {
+        try {
+            const excelData = await facade.exportExcelData();
+            const buffer = excel.build([{ name: "材料信息价总表", data: excelData }]);
+            const filePath = './public/exportPriceSummary.xlsx';
+            fs.writeFileSync(filePath, buffer, 'binary');
+            const stats = fs.statSync(filePath);
+            // 下载相关header
+            response.set({
+                'Content-Type': 'application/octet-stream',
+                'Content-Disposition': 'attachment; filename=infoPriceSummary.xlsx',
+                'Content-Length': stats.size
+            });
+            fs.createReadStream(filePath).pipe(response);
+            fs.unlink(filePath);
+        } catch (error) {
+            response.end(error);
+        }
+    }
+
+}
+
+module.exports = {
+    priceInfoSummaryController: new PriceInfoSummaryController()
+};

+ 80 - 0
modules/price_info_summary/facade/index.js

@@ -0,0 +1,80 @@
+const mongoose = require('mongoose');
+
+const priceInfoSummaryModel = mongoose.model('std_price_info_summary');
+
+// 获取分页数据
+const getPagingData = async (page, pageSize, searchStr) => {
+    let query = {};
+    if (searchStr) {
+        const nameReg = new RegExp(searchStr);
+        query = {
+            $or: [{ classCode: searchStr }, { name: { $regex: nameReg } }]
+        }
+    }
+    const totalCount = await priceInfoSummaryModel.count(query);
+    const items = await priceInfoSummaryModel.find(query).lean().sort({ classCode: -1 }).skip(page * pageSize).limit(pageSize);
+    return { items, totalCount };
+}
+
+const UpdateType = {
+    UPDATE: 'update',
+    DELETE: 'delete',
+    CREATE: 'create',
+};
+
+// 编辑表格
+async function editSummaryData(postData) {
+    const bulks = [];
+    postData.forEach(data => {
+        if (data.type === UpdateType.UPDATE) {
+            bulks.push({
+                updateOne: {
+                    filter: { ID: data.ID },
+                    update: { ...data.data }
+                }
+            });
+        } else if (data.type === UpdateType.DELETE) {
+            bulks.push({
+                deleteOne: {
+                    filter: { ID: data.ID }
+                }
+            });
+        } else {
+            bulks.push({
+                insertOne: {
+                    document: data.data
+                }
+            });
+        }
+    });
+    if (bulks.length) {
+        await priceInfoSummaryModel.bulkWrite(bulks);
+    }
+}
+
+// 保存至总表
+async function saveInSummary(documents) {
+    await priceInfoSummaryModel.insertMany(documents);
+}
+
+// 导出excel数据
+async function exportExcelData() {
+    const items = await priceInfoSummaryModel.find({}).sort({ classCode: 1 }).lean();
+    // 整理数据
+    let classData = [];
+    for (const tmp of items) {
+        const item = [tmp.code || '', tmp.classCode || '', tmp.expString || '', tmp.name || '', tmp.specs || '', tmp.unit || ''];
+        classData.push(item);
+    }
+    const excelData = [['主从对应码', '别名编码', '计算式', '材料名称', '规格型号', '单位']];
+    excelData.push.apply(excelData, classData);
+
+    return excelData;
+}
+
+module.exports = {
+    getPagingData,
+    editSummaryData,
+    saveInSummary,
+    exportExcelData,
+}

+ 19 - 0
modules/price_info_summary/routes/index.js

@@ -0,0 +1,19 @@
+/**
+ * Created by zhang on 2018/9/3.
+ */
+
+const express = require("express");
+const router = express.Router();
+const { priceInfoSummaryController } = require('../controllers/index');
+
+module.exports = function (app) {
+    router.get("/main", priceInfoSummaryController.auth, priceInfoSummaryController.init, priceInfoSummaryController.main);
+    router.post("/getPagingData", priceInfoSummaryController.auth, priceInfoSummaryController.init, priceInfoSummaryController.getPagingData);
+    router.post("/editSummaryData", priceInfoSummaryController.auth, priceInfoSummaryController.init, priceInfoSummaryController.editSummaryData);
+    router.post("/saveInSummary", priceInfoSummaryController.auth, priceInfoSummaryController.init, priceInfoSummaryController.saveInSummary);
+    router.get("/export", priceInfoSummaryController.auth, priceInfoSummaryController.init, priceInfoSummaryController.exportSummaryData);
+
+    app.use("/priceInfoSummary", router);
+};
+
+

+ 45 - 40
modules/project_feature_lib/controllers/project_feature_controller.js

@@ -5,105 +5,110 @@ import BaseController from "../../common/base/base_controller";
 import featureFacade from "../facade/project_feature_facade";
 let config = require("../../../config/config.js");
 import CompilationModel from '../../users/models/compilation_model';
+import { checkCompilationPermission } from '../../common/base/base_util';
 
-class FeatureController extends BaseController{
+class FeatureController extends BaseController {
     async main(request, response) {
         let compilationModel = new CompilationModel();
-        let compilationList = await compilationModel.getCompilationList({_id: 1, name: 1});
-        compilationList.unshift({_id: 'all', name: '所有'});
+        let compilationList = await compilationModel.getPermissionCompilationList(request, { _id: 1, name: 1 });
+        compilationList.unshift({ _id: 'all', name: '所有' });
         let activeCompilation = compilationList.find(compilation => compilation._id.toString() === request.query.filter);
         if (activeCompilation) {
             activeCompilation.active = 'active';
         } else {
             compilationList[0].active = 'active'
         }
-        let filter = request.query.filter ? {compilationId: request.query.filter} : {};
-        let featureLibs = await featureFacade.findByCondition(filter,{feature:0},false);
+        let filter = request.query.filter ? { compilationId: request.query.filter } : {};
+        let featureLibs = await featureFacade.findByCondition(filter, { feature: 0 }, false);
+        const compilationPermission = request.session.managerData.compilationPermission || [];
+        featureLibs = featureLibs.filter(lib => compilationPermission.includes(lib.compilationId));
         let randerData = {
-            title:'工程特征库',
+            title: '工程特征库',
             userAccount: request.session.managerData.username,
             userID: request.session.managerData.userID,
-            featureLibs:featureLibs,
+            featureLibs: featureLibs,
             compilationList: compilationList,
             layout: 'maintain/common/html/layout'
         };
         response.render("maintain/project_feature_lib/html/main", randerData);
     }
-    async addLib(request, response){
+    async addLib(request, response) {
         try {
             await featureFacade.addLib(request.body);
-        }catch (error) {
+        } catch (error) {
             console.log(error);
         }
         response.redirect(request.headers.referer);
     }
-    async findLib(request, response){
-        let result={
-            error:0
+    async findLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let conditions={'ID' : data.ID};
+            let conditions = { 'ID': data.ID };
             let resultData = await featureFacade.findByCondition(conditions);
-            result.data=resultData;
-        }catch (err){
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async saveLib(request, response){
-        let result={
-            error:0
+    async saveLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await featureFacade.saveLib(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await featureFacade.saveLib(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async deleteLibByID(request,response){
-        let result={
-            error:0
+    async deleteLibByID(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await featureFacade.deleteLibByID(data.ID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await featureFacade.deleteLibByID(data.ID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async edit(request,response){
+    async edit(request, response) {
         //先取出替换库信息:
         let libID = request.params.libID;
-        let featureLib = await featureFacade.findByCondition({'ID':libID});
-        if(featureLib){
+        let featureLib = await featureFacade.findByCondition({ 'ID': libID });
+        if (featureLib) {
+            checkCompilationPermission(request, response, featureLib.compilationId, '/projectFeature/main');
+
             let randerData = {
-                title:'工程特征库',
-                mainURL:'/projectFeature/main',
-                libName:featureLib.name,
+                title: '工程特征库',
+                mainURL: '/projectFeature/main',
+                libName: featureLib.name,
                 userAccount: request.session.managerData.username,
                 userID: request.session.managerData.userID,
-                featureList:JSON.stringify(featureLib.feature),
-                libID:libID,
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                featureList: JSON.stringify(featureLib.feature),
+                libID: libID,
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
                 layout: 'maintain/common/html/edit_layout'
             };
             response.render("maintain/project_feature_lib/html/edit", randerData);
-        }else {
+        } else {
             response.redirect(request.headers.referer);
         }
     }

+ 41 - 0
modules/ration_repository/controllers/loss_controller.js

@@ -0,0 +1,41 @@
+import BaseController from "../../common/base/base_controller";
+
+const facade = require('../models/lossRate');
+
+class LossController extends BaseController {
+  async getLossList(req, res) {
+    try {
+      const { libID } = req.body;
+      const data = await facade.getLossList(libID);
+      res.json({ error: 0, message: 'getLossList success', data });
+    } catch (err) {
+      console.log(err);
+      res.json({ error: 1, message: err.toString() });
+    }
+  }
+
+  async saveLossList(req, res) {
+    try {
+      const data = await facade.save(JSON.parse(req.body.data));
+      res.json({ error: 0, message: 'saveLossList success', data });
+    } catch (err) {
+      console.log(err);
+      res.json({ error: 1, message: err.toString() });
+    }
+  }
+
+  async getItemsBySerialNos(req, res) {
+    try {
+      const { libID, serialNos } = JSON.parse(req.body.data);
+      const data = await facade.getItemsBySerialNos(libID, serialNos);
+      res.json({ error: 0, message: 'getItemsBySerialNos success', data });
+    } catch (err) {
+      console.log(err);
+      res.json({ error: 1, message: err.toString() });
+    }
+  }
+}
+
+module.exports = {
+  lossController: new LossController()
+};

+ 10 - 1
modules/ration_repository/controllers/repository_views_controller.js

@@ -3,6 +3,7 @@
  */
 import BaseController from "../../common/base/base_controller";
 import CompilationModel from '../../users/models/compilation_model';
+import { checkCompilationPermission } from '../../common/base/base_util';
 let config = require("../../../config/config.js");
 import mongoose from 'mongoose';
 const compilationModel = mongoose.model('compilation');
@@ -11,12 +12,17 @@ const fs = require('fs');
 const rationDao = require('../models/ration_item');
 class ViewsController extends BaseController {
     async redirectMain(req, res) {
+        const compilationPermission = req.session.managerData.compilationPermission || [];
+        if (req.query.filter) {
+            checkCompilationPermission(req, res, req.query.filter, '/rationRepository/main');
+        }
         let filter = req.query.filter ? { compilationId: req.query.filter } : null;
         let allLibs = await rationLibModel.find({}, { _id: 0, recentOpr: 0 }),
             rationLibs = allLibs.filter(lib => filter && lib.compilationId === filter.compilationId || !filter),
             allNames = allLibs.map(lib => lib.dispName);
+        rationLibs = rationLibs.filter(lib => compilationPermission.includes(lib.compilationId));
         let compilationModel = new CompilationModel();
-        let compilationList = await compilationModel.getCompilationList({ _id: 1, name: 1 });
+        let compilationList = await compilationModel.getPermissionCompilationList(req, { _id: 1, name: 1 });
         compilationList.unshift({ _id: 'all', name: '所有' });
         let activeCompilation = compilationList.find(compilation => compilation._id.toString() === req.query.filter);
         if (activeCompilation) {
@@ -45,6 +51,7 @@ class ViewsController extends BaseController {
         let priceProperties = [];
         let stdRationLib = await rationLibModel.findOne({ ID: repId });
         if (stdRationLib) {
+            checkCompilationPermission(req, res, stdRationLib.compilationId, '/rationRepository/main');
             let compilation = await compilationModel.findOne({ _id: mongoose.Types.ObjectId(stdRationLib.compilationId) });
             priceProperties = compilation.priceProperties ? compilation.priceProperties : [];
             let absoluteUrl = compilation.overWriteUrl ? req.app.locals.rootDir + compilation.overWriteUrl : req.app.locals.rootDir;
@@ -71,6 +78,7 @@ class ViewsController extends BaseController {
         let priceProperties = [];
         let stdRationLib = await rationLibModel.findOne({ ID: repId });
         if (stdRationLib) {
+            checkCompilationPermission(req, res, stdRationLib.compilationId, '/rationRepository/main');
             let compilation = await compilationModel.findOne({ _id: mongoose.Types.ObjectId(stdRationLib.compilationId) });
             priceProperties = compilation.priceProperties ? compilation.priceProperties : [];
             let absoluteUrl = compilation.overWriteUrl ? req.app.locals.rootDir + compilation.overWriteUrl : req.app.locals.rootDir;
@@ -111,6 +119,7 @@ class ViewsController extends BaseController {
         const redirectCoe = `/rationRepository/coeList?repository=${repId}&locked=${locked}`;
         const redirectRation = `/rationRepository/ration?repository=${repId}&locked=${locked}`;
         let stdRationLib = await rationLibModel.findOne({ ID: repId });
+        checkCompilationPermission(req, res, stdRationLib.compilationId, '/rationRepository/main');
         res.render('maintain/ration_repository/anzhuang.html',
             {
                 userAccount: req.session.managerData.username,

+ 92 - 0
modules/ration_repository/models/lossRate.js

@@ -0,0 +1,92 @@
+const mongoose = require('mongoose');
+const uuidV1 = require('uuid/v1');
+const _ = require('lodash');
+const lossModel = mongoose.model('std_ration_lib_loss_rate_list');
+const rationModel = mongoose.model('std_ration_lib_ration_items');
+
+const getLossList = async (libID) => {
+  return await lossModel.find({ libID }).lean();
+}
+
+const batchUpdateRationLoss = async (libID, updateRateData) => {
+  const bulks = [];
+  const updateRateMap = {};
+  updateRateData.forEach(item => {
+    updateRateMap[item.ID] = item;
+  });
+  const rations = await rationModel.find({ rationRepId: libID }, '-_id ID rationGljList').lean();
+  rations.forEach(ration => {
+    if (ration.rationGljList && ration.rationGljList.length) {
+      let needUpdate = false;
+      ration.rationGljList.forEach(rGlj => {
+        const matchRateItem = updateRateMap[rGlj.lossRateID];
+        if (matchRateItem) {
+          needUpdate = true;
+          rGlj.lossRateNo = matchRateItem.serialNo;
+          rGlj.lossRateName = matchRateItem.name;
+          rGlj.lossRate = matchRateItem.rate;
+        }
+      });
+      if (needUpdate) {
+        bulks.push({ updateOne: { filter: { ID: ration.ID }, update: { $set: { rationGljList: ration.rationGljList } } } });
+      }
+    }
+  });
+  if (bulks.length) {
+    await rationModel.bulkWrite(bulks);
+  }
+
+}
+
+const save = async (data) => {
+  console.log(data);
+  const { addArr, updateArr, deleteArr } = data;
+  const bulks = [];
+  const newData = [];
+  if (addArr && addArr.length) {
+    addArr.forEach(item => {
+      const newItem = { ...item, ID: uuidV1() };
+      console.log(newItem);
+      newData.push(newItem);
+      bulks.push({ insertOne: { document: newItem } });
+    });
+  }
+  const updateRateData = [];
+  let libID;
+  if (updateArr && updateArr.length) {
+    updateArr.forEach(item => {
+      if (!libID) {
+        libID = item.libID;
+      }
+      const updateObj = { ...item };
+      delete updateObj.ID;
+      delete updateObj.libID;
+      updateRateData.push({ name: updateObj.name, rate: updateObj.rate, serialNo: updateObj.serialNo, ID: item.ID });
+      bulks.push({ updateOne: { filter: { ID: item.ID }, update: { $set: updateObj } } });
+    });
+  }
+  if (deleteArr && deleteArr.length) {
+    deleteArr.forEach(item => {
+      bulks.push({ deleteOne: { filter: { ID: item.ID } } });
+    })
+  }
+  if (bulks.length) {
+    await lossModel.bulkWrite(bulks);
+  }
+  if (libID && updateRateData.length) {
+    await batchUpdateRationLoss(libID, updateRateData)
+  }
+  return newData;
+}
+
+
+const getItemsBySerialNos = async (libID, serialNos) => {
+  return await lossModel.find({ libID, serialNo: { $in: serialNos } }).lean();
+}
+
+
+module.exports = {
+  getLossList,
+  save,
+  getItemsBySerialNos,
+}

+ 50 - 45
modules/ration_repository/routes/ration_rep_routes.js

@@ -2,7 +2,7 @@
  * Created by Tony on 2017/4/20.
  */
 var express = require("express");
-var apiRouter =express.Router();
+var apiRouter = express.Router();
 //var _rootDir = __dirname;
 import ViewsController from "../controllers/repository_views_controller";
 import RationRepositoryController from "../controllers/ration_repository_controller";
@@ -22,10 +22,11 @@ let installationController = new InstallationController();
 let searchController = new SearchController();
 let repositoryGljController = new RepositoryGljController();
 let gljController = new GljController();
+const { lossController } = require('../controllers/loss_controller');
 
-module.exports =  function (app) {
+module.exports = function (app) {
     app.get('/rationRepository/main', viewsController.auth, viewsController.init, viewsController.redirectMain);
-    app.get('/rationRepository/ration',viewsController.auth, viewsController.init, viewsController.redirectRation);
+    app.get('/rationRepository/ration', viewsController.auth, viewsController.init, viewsController.redirectRation);
     app.get('/rationRepository/lmm', viewsController.auth, viewsController.init, viewsController.redirectGlj);
     app.get('/rationRepository/coeList', viewsController.auth, viewsController.init, viewsController.redirectCoeList);
     app.get('/rationRepository/installation', viewsController.auth, viewsController.init, viewsController.redirectInstallation);
@@ -33,53 +34,57 @@ module.exports =  function (app) {
     apiRouter.post("/prepareInitData", rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.prepareInitData);
     apiRouter.post("/getCompilationList", rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.getCompilationList);
     apiRouter.post("/getRationLibsByCompilation", rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.getRationLibsByCompilation);
-    apiRouter.post("/getRationLib",rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.getRationLib);
+    apiRouter.post("/getRationLib", rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.getRationLib);
 
-    apiRouter.post("/getRationDisplayNames",rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.getDisPlayRationLibs);
-    apiRouter.post("/editRationLibs",rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.updateRationRepositoryName);
-    apiRouter.post("/addRationRepository",rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.addRationRepository);
-    apiRouter.post("/deleteRationLibs",rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.deleteRationLib);
-    apiRouter.post("/getRealLibName",rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.getRealLibName);
-    apiRouter.post("/getLibIDByName",rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.getLibIDByName);
+    apiRouter.post("/getRationDisplayNames", rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.getDisPlayRationLibs);
+    apiRouter.post("/editRationLibs", rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.updateRationRepositoryName);
+    apiRouter.post("/addRationRepository", rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.addRationRepository);
+    apiRouter.post("/deleteRationLibs", rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.deleteRationLib);
+    apiRouter.post("/getRealLibName", rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.getRealLibName);
+    apiRouter.post("/getLibIDByName", rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.getLibIDByName);
 
     apiRouter.get('/sectionTemplateCount/:compilationId', rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.sectionTemplateCount);
     apiRouter.post('/initSectionTemplate', rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.initSectionTemplate);
-    apiRouter.post("/getRationTree",rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.getRationChapterTree);
-    apiRouter.post("/getNewRationTreeID",rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.getNewRationTreeID);
-    apiRouter.post("/createNewNode",rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.createNewNode);
-    apiRouter.post("/updateNodes",rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateNodes);
-    apiRouter.post("/deleteNodes",rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.deleteNodes);
-    apiRouter.post("/updateExplanation",rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateExplanation);
-    apiRouter.post("/updateErratumRecord",rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateErratumRecord);
-    apiRouter.post("/updateRuleText",rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateRuleText);
-    apiRouter.post("/updateSituation",rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateSituation);
-    apiRouter.post("/updateAnnoSituation",rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateAnnoSituation);
+    apiRouter.post("/getRationTree", rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.getRationChapterTree);
+    apiRouter.post("/getNewRationTreeID", rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.getNewRationTreeID);
+    apiRouter.post("/createNewNode", rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.createNewNode);
+    apiRouter.post("/updateNodes", rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateNodes);
+    apiRouter.post("/deleteNodes", rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.deleteNodes);
+    apiRouter.post("/updateExplanation", rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateExplanation);
+    apiRouter.post("/updateErratumRecord", rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateErratumRecord);
+    apiRouter.post("/updateRuleText", rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateRuleText);
+    apiRouter.post("/updateSituation", rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateSituation);
+    apiRouter.post("/updateAnnoSituation", rationChapterTreeController.auth, rationChapterTreeController.init, rationChapterTreeController.updateAnnoSituation);
 
-    apiRouter.post("/getRationItems",rationController.auth, rationController.init, rationController.getRationItemsBySection);
-    apiRouter.post("/getRationItemsByLib",rationController.auth, rationController.init, rationController.getRationItemsByLib);
-    apiRouter.post("/mixUpdateRationItems",rationController.auth, rationController.init, rationController.mixUpdateRationItems);
-    apiRouter.post("/updateRationBasePrc",rationController.auth, rationController.init, rationController.updateRationBasePrc);
-    apiRouter.post("/getRationGljIds",rationController.auth, rationController.init, rationController.getRationGljIds);
-    apiRouter.post("/getRationsCodes",rationController.auth, rationController.init, rationController.getRationsCodes);
-    apiRouter.post("/updateJobContent",rationController.auth, rationController.init, rationController.updateJobContent);
-    apiRouter.post("/updateAnnotation",rationController.auth, rationController.init, rationController.updateAnnotation);
-    apiRouter.post("/updateRationTemplate",rationController.auth, rationController.init, rationController.updateRationTemplate);
+    apiRouter.post("/getRationItems", rationController.auth, rationController.init, rationController.getRationItemsBySection);
+    apiRouter.post("/getRationItemsByLib", rationController.auth, rationController.init, rationController.getRationItemsByLib);
+    apiRouter.post("/mixUpdateRationItems", rationController.auth, rationController.init, rationController.mixUpdateRationItems);
+    apiRouter.post("/updateRationBasePrc", rationController.auth, rationController.init, rationController.updateRationBasePrc);
+    apiRouter.post("/getRationGljIds", rationController.auth, rationController.init, rationController.getRationGljIds);
+    apiRouter.post("/getRationsCodes", rationController.auth, rationController.init, rationController.getRationsCodes);
+    apiRouter.post("/updateJobContent", rationController.auth, rationController.init, rationController.updateJobContent);
+    apiRouter.post("/updateAnnotation", rationController.auth, rationController.init, rationController.updateAnnotation);
+    apiRouter.post("/updateRationTemplate", rationController.auth, rationController.init, rationController.updateRationTemplate);
 
-    apiRouter.post("/createNewGljTypeNode",repositoryGljController.auth, gljController.init, gljController.createNewGljTypeNode);
-    apiRouter.post("/updateGljNodes",repositoryGljController.auth, gljController.init, gljController.updateGljNodes);
-    apiRouter.post("/deleteGljNodes",repositoryGljController.auth, gljController.init, gljController.deleteGljNodes);
-    apiRouter.post("/getGljDistType",repositoryGljController.auth, gljController.init, gljController.getGljDistType);
-    apiRouter.post("/getGljTree",repositoryGljController.auth, gljController.init, gljController.getGljTree);
-    apiRouter.post("/getGljItems",repositoryGljController.auth, gljController.init, gljController.getGljItems);
-    apiRouter.post("/mixUpdateGljItems",repositoryGljController.auth, gljController.init, gljController.mixUpdateGljItems);
-    apiRouter.post("/getGljItemsByIds",repositoryGljController.auth, gljController.init, gljController.getGljItemsByIds);
-    apiRouter.post("/getGljItemsByCodes",repositoryGljController.auth, gljController.init, gljController.getGljItemsByCodes);
+    apiRouter.post("/createNewGljTypeNode", repositoryGljController.auth, gljController.init, gljController.createNewGljTypeNode);
+    apiRouter.post("/updateGljNodes", repositoryGljController.auth, gljController.init, gljController.updateGljNodes);
+    apiRouter.post("/deleteGljNodes", repositoryGljController.auth, gljController.init, gljController.deleteGljNodes);
+    apiRouter.post("/getGljDistType", repositoryGljController.auth, gljController.init, gljController.getGljDistType);
+    apiRouter.post("/getGljTree", repositoryGljController.auth, gljController.init, gljController.getGljTree);
+    apiRouter.post("/getGljItems", repositoryGljController.auth, gljController.init, gljController.getGljItems);
+    apiRouter.post("/mixUpdateGljItems", repositoryGljController.auth, gljController.init, gljController.mixUpdateGljItems);
+    apiRouter.post("/getGljItemsByIds", repositoryGljController.auth, gljController.init, gljController.getGljItemsByIds);
+    apiRouter.post("/getGljItemsByCodes", repositoryGljController.auth, gljController.init, gljController.getGljItemsByCodes);
 
-    apiRouter.post("/getCoeReference",coeListController.auth, coeListController.init, coeListController.getCoeReference);
-    apiRouter.post("/getCoeList",coeListController.auth, coeListController.init, coeListController.getCoeList);
-    apiRouter.post("/saveCoeList",coeListController.auth, coeListController.init, coeListController.saveCoeList);
-    apiRouter.post("/getCoeItemsByIDs",coeListController.auth, coeListController.init, coeListController.getCoeItemsByIDs);
-    apiRouter.post("/getCoeItemsByNos",coeListController.auth, coeListController.init, coeListController.getCoeItemsByNos);
+    apiRouter.post("/getCoeReference", coeListController.auth, coeListController.init, coeListController.getCoeReference);
+    apiRouter.post("/getCoeList", coeListController.auth, coeListController.init, coeListController.getCoeList);
+    apiRouter.post("/saveCoeList", coeListController.auth, coeListController.init, coeListController.saveCoeList);
+    apiRouter.post("/getCoeItemsByIDs", coeListController.auth, coeListController.init, coeListController.getCoeItemsByIDs);
+    apiRouter.post("/getCoeItemsByNos", coeListController.auth, coeListController.init, coeListController.getCoeItemsByNos);
+
+    apiRouter.post("/getLossList", lossController.auth, lossController.init, lossController.getLossList);
+    apiRouter.post("/saveLossList", lossController.auth, lossController.init, lossController.saveLossList);
+    apiRouter.post("/getItemsBySerialNos", lossController.auth, lossController.init, lossController.getItemsBySerialNos);
 
     //安装增加费
     apiRouter.post('/getInstallation', installationController.auth, installationController.init, installationController.getInstallation);
@@ -87,7 +92,7 @@ module.exports =  function (app) {
     apiRouter.post('/updateSection', installationController.auth, installationController.init, installationController.updateSection);
     apiRouter.post('/batchUpdateInst', installationController.auth, installationController.init, installationController.batchUpdateInst);
 
-    apiRouter.post('/getRationItem',searchController.auth, searchController.init, searchController.getRationItem);
+    apiRouter.post('/getRationItem', searchController.auth, searchController.init, searchController.getRationItem);
     apiRouter.post('/findRation', searchController.auth, searchController.init, searchController.findRation);
 
     apiRouter.post('/reCalcAll', rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.reCalcAll);
@@ -95,5 +100,5 @@ module.exports =  function (app) {
     apiRouter.post('/upload', rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.uploadSourceData);
     apiRouter.get('/export', rationRepositoryController.auth, rationRepositoryController.init, rationRepositoryController.exportRationData);
 
-    app.use("/rationRepository/api",apiRouter);
+    app.use("/rationRepository/api", apiRouter);
 }

+ 87 - 85
modules/reports/controllers/rpt_tpl_controller.js

@@ -18,12 +18,12 @@ import rttFacade from "../facade/rpt_tpl_tree_node_facade";
 import CompilationModel from "../../users/models/compilation_model";
 
 //统一回调函数
-let callback = function(req, res, err, message, data){
-    res.json({error: err, message: message, data: data});
+let callback = function (req, res, err, message, data) {
+    res.json({ error: err, message: message, data: data });
 };
 
 let mExport = {
-    getCustomerCfg: function(req, res) {
+    getCustomerCfg: function (req, res) {
         let params = JSON.parse(req.body.params),
             userId = params.userId,
             me = this;
@@ -43,7 +43,7 @@ let mExport = {
                     rst.push(null);
                     for (let itemCfg of custCfg) {
                         // rst = itemCfg;
-                        let doc = (itemCfg._doc)?itemCfg._doc:itemCfg;
+                        let doc = (itemCfg._doc) ? itemCfg._doc : itemCfg;
                         let dest = {};
                         copyRptCfg(doc, dest);
                         if (doc.userId !== "-100") {
@@ -59,10 +59,10 @@ let mExport = {
                     copyRptCfg(rst[0], userDest);
                     rst.push(userDest);
                 }
-                callback(req,res, false, "", rst);
+                callback(req, res, false, "", rst);
             } else {
                 //failed
-                callback(req,res, true, "失败!", null);
+                callback(req, res, true, "失败!", null);
             }
         })
     },
@@ -76,27 +76,27 @@ let mExport = {
         rptCustCfgFacade.saveCustomizeCfg(custCfg).then(function (rst) {
             if (rst) {
                 //success
-                callback(req,res, false, "", "success!");
+                callback(req, res, false, "", "success!");
             } else {
                 //failed
-                callback(req,res, true, "更新失败!", null);
+                callback(req, res, true, "更新失败!", null);
             }
         })
     },
-    getDftTemplates: function(req, res) {
-        let filter = {"userId": "-100", "$or": [{"isDeleted": null}, {"isDeleted": false} ]};
-        TreeNodeModel.find(filter, '-_id', function(err, data){
+    getDftTemplates: function (req, res) {
+        let filter = { "userId": "-100", "$or": [{ "isDeleted": null }, { "isDeleted": false }] };
+        TreeNodeModel.find(filter, '-_id', function (err, data) {
             if (err) {
-                callback(req,res, true,"", null);
+                callback(req, res, true, "", null);
             } else {
-                callback(req,res,false,"", data);
+                callback(req, res, false, "", data);
             }
         });
     },
-    getExtCodeTpl: function(req, res) {
-        rptExtCodeModel.find({}).exec().then(function(rstCodeTpl) {
+    getExtCodeTpl: function (req, res) {
+        rptExtCodeModel.find({}).exec().then(function (rstCodeTpl) {
             if (rstCodeTpl) {
-                callback(req,res, false, "", rstCodeTpl);
+                callback(req, res, false, "", rstCodeTpl);
             } else {
                 callback(req, res, 'The report template was not found!', null);
             }
@@ -104,16 +104,16 @@ let mExport = {
     },
     getCompilationList(req, res) {
         let compilationModel = new CompilationModel();
-        let compilationList = compilationModel.getCompilationList();
+        let compilationList = compilationModel.getPermissionCompilationList(req);
         if (compilationList) {
             compilationList.then(function (rst) {
-                callback(req,res,false,"", rst);
+                callback(req, res, false, "", rst);
             })
         } else {
-            callback(req,res, true,"no result", null);
+            callback(req, res, true, "no result", null);
         }
     },
-    getRptTplTree: function(req, res) {
+    getRptTplTree: function (req, res) {
         let params = JSON.parse(req.body.params),
             compilationId = params.compilationId,
             userId = params.userId,
@@ -134,11 +134,11 @@ let mExport = {
         if (!compilationId) {
             compilationId = req.session.sessionCompilation._id;
         }
-        rttFacade.findTplTree(compilationId, userIds).then(function(result) {
+        rttFacade.findTplTree(compilationId, userIds).then(function (result) {
             if (result) {
-                callback(req,res,false,"", result);
+                callback(req, res, false, "", result);
             } else {
-                callback(req,res, true,"no result", null);
+                callback(req, res, true, "no result", null);
             }
         });
     },
@@ -149,76 +149,76 @@ let mExport = {
         if (!compilationId) {
             compilationId = req.session.sessionCompilation._id;
         }
-        rttFacade.findTplTreeByCompilation(compilationId).then(function(result) {
+        rttFacade.findTplTreeByCompilation(compilationId).then(function (result) {
             if (result) {
-                callback(req,res,false,"", result);
+                callback(req, res, false, "", result);
             } else {
-                callback(req,res, true,"no result", null);
+                callback(req, res, true, "no result", null);
             }
         });
     },
-    updateTreeNodes: function(req, res) {
+    updateTreeNodes: function (req, res) {
         let params = JSON.parse(req.body.params),
             nodes = params.nodes;
         let functions = [];
         for (let node of nodes) {
-            functions.push((function(doc) {
-                return function(cb) {
-                    TreeNodeModel.update({ID: doc.ID}, doc, cb);
+            functions.push((function (doc) {
+                return function (cb) {
+                    TreeNodeModel.update({ ID: doc.ID }, doc, cb);
                 };
             })(node));
         }
-        async.parallel(functions, function(err, results) {
-            callback(req,res, err, "", results);
+        async.parallel(functions, function (err, results) {
+            callback(req, res, err, "", results);
         });
     },
-    deleteRptTplNodes: function(req, res){
+    deleteRptTplNodes: function (req, res) {
         let params = JSON.parse(req.body.params),
             nodeIds = params.nodeIds,
             preNodeId = params.preNodeId,
             preNodeNextId = params.preNodeNextId;
         let functions = [];
         if (preNodeId !== -1) {
-            functions.push((function(nodeId, nextId) {
-                return function(cb) {
-                    TreeNodeModel.update({ID: nodeId}, {"NextSiblingID": nextId}, cb);
+            functions.push((function (nodeId, nextId) {
+                return function (cb) {
+                    TreeNodeModel.update({ ID: nodeId }, { "NextSiblingID": nextId }, cb);
                 };
             })(preNodeId, preNodeNextId));
         }
         for (let nId of nodeIds) {
-            functions.push((function(nodeId) {
-                return function(cb) {
-                    TreeNodeModel.update({ID: nodeId}, {"isDeleted": true}, cb);
+            functions.push((function (nodeId) {
+                return function (cb) {
+                    TreeNodeModel.update({ ID: nodeId }, { "isDeleted": true }, cb);
                 };
             })(nId));
         }
-        async.parallel(functions, function(err, results) {
-            callback(req,res, err, "", results);
+        async.parallel(functions, function (err, results) {
+            callback(req, res, err, "", results);
         });
     },
-    createTreeRootNode: function(req, res){
+    createTreeRootNode: function (req, res) {
         let params = JSON.parse(req.body.params),
             doc = params.doc;
         rttFacade.createNewTree(doc).then(function (rst) {
             if (rst) {
                 //success
-                callback(req,res, false, "", rst);
+                callback(req, res, false, "", rst);
             } else {
                 //failed
-                callback(req,res, true, "创建失败!", null);
+                callback(req, res, true, "创建失败!", null);
             }
         })
     },
-    updateTreeRootNode: function(req, res){
+    updateTreeRootNode: function (req, res) {
         let params = JSON.parse(req.body.params),
             doc = params.doc;
         rttFacade.updateTree(doc.compilationId, doc.engineerId, doc.userId, doc).then(function (rst) {
             if (rst) {
                 //success
-                callback(req,res, false, "", rst);
+                callback(req, res, false, "", rst);
             } else {
                 //failed
-                callback(req,res, true, "更新失败!", null);
+                callback(req, res, true, "更新失败!", null);
             }
         })
     },
@@ -228,17 +228,17 @@ let mExport = {
             compilationId = params.compilationId, engineerId = params.engineerId,
             userId = params.userId,
             nodeName = params.nodeName
-        ;
+            ;
         if (req.session.sessionUser && req.session.sessionUser.id) userId = req.session.sessionUser.id;
-        let filter = {"compilationId": compilationId, "engineerId": engineerId, "userId": userId, "$or": [{"isDeleted": null}, {"isDeleted": false}]};
-        let updateStatement = {"$set": {"name": nodeName}};
+        let filter = { "compilationId": compilationId, "engineerId": engineerId, "userId": userId, "$or": [{ "isDeleted": null }, { "isDeleted": false }] };
+        let updateStatement = { "$set": { "name": nodeName } };
         rttFacade.updateTreeInDetail(filter, updateStatement).then(function (rst) {
             if (rst) {
                 //success
-                callback(req,res, false, "", rst);
+                callback(req, res, false, "", rst);
             } else {
                 //failed
-                callback(req,res, true, "更新失败!", null);
+                callback(req, res, true, "更新失败!", null);
             }
         });
     },
@@ -248,17 +248,17 @@ let mExport = {
             // engineerId = params.engineerId,
             userId = params.userId,
             subNode = params.subNode
-        ;
+            ;
         if (req.session.sessionUser && req.session.sessionUser.id) userId = req.session.sessionUser.id; //备注:这段逻辑只会在前端有效,后端运维不会走到
-        let filter = {"compilationId": compilationId, "userId": userId, "items.ID": subNode.ID, "$or": [{"isDeleted": null}, {"isDeleted": false}]};
-        let updateStatement = {$set: {"items.$": subNode}};
+        let filter = { "compilationId": compilationId, "userId": userId, "items.ID": subNode.ID, "$or": [{ "isDeleted": null }, { "isDeleted": false }] };
+        let updateStatement = { $set: { "items.$": subNode } };
         rttFacade.updateTreeInDetail(filter, updateStatement).then(function (rst) {
             if (rst) {
                 //success
-                callback(req,res, false, "", rst);
+                callback(req, res, false, "", rst);
             } else {
                 //failed
-                callback(req,res, true, "更新失败!", null);
+                callback(req, res, true, "更新失败!", null);
             }
         });
     },
@@ -275,32 +275,32 @@ let mExport = {
             rttFacade.removeTreePhycically(compilationId, engineerId, userId).then(function (rst) {
                 if (rst) {
                     //success
-                    callback(req,res, false, "", rst);
+                    callback(req, res, false, "", rst);
                 } else {
                     //failed
-                    callback(req,res, true, "删除失败!", null);
+                    callback(req, res, true, "删除失败!", null);
                 }
             })
         } else {
             rttFacade.removeTree(compilationId, engineerId, userId).then(function (rst) {
                 if (rst) {
                     //success
-                    callback(req,res, false, "", rst);
+                    callback(req, res, false, "", rst);
                 } else {
                     //failed
-                    callback(req,res, true, "删除失败!", null);
+                    callback(req, res, true, "删除失败!", null);
                 }
             })
         }
     },
-    getNewNodeID: function(req, res) {
+    getNewNodeID: function (req, res) {
         let params = JSON.parse(req.body.params),
             scope = params.scope;
-        counter.counterDAO.getIDAfterCount(counter.moduleName.report, scope, function(err, result){
-            callback(req,res, false, "", result.sequence_value);
+        counter.counterDAO.getIDAfterCount(counter.moduleName.report, scope, function (err, result) {
+            callback(req, res, false, "", result.sequence_value);
         });
     },
-    createDftRptTpl: function(req, res) {
+    createDftRptTpl: function (req, res) {
         let params = JSON.parse(req.body.params),
             treeNodeId = params.treeNodeId,
             rptDftTplId = params.rptDftTplId,
@@ -310,10 +310,10 @@ let mExport = {
             engineerId = params.engineerId,
             userId = params.userId,
             subNode = params.subNode
-        ;
+            ;
         if (req.session.sessionUser && req.session.sessionUser.id) userId = req.session.sessionUser.id;
-        let filter = {"ID": rptDftTplId};
-        RptTplModel.findOne(filter, '-_id').exec().then(function(dftTplRst) {
+        let filter = { "ID": rptDftTplId };
+        RptTplModel.findOne(filter, '-_id').exec().then(function (dftTplRst) {
             if (dftTplRst) {
                 let _doc = dftTplRst["_doc"];
                 _doc["ID"] = treeNodeId;
@@ -323,17 +323,17 @@ let mExport = {
                 let rptTpl = new RptTplModel(_doc);
                 rptTpl.save(function (err, actTplRst) {
                     if (err) {
-                        callback(req,res, "报表模板创建错误", "", null);
+                        callback(req, res, "报表模板创建错误", "", null);
                     } else {
-                        let filter = {"compilationId": compilationId, "engineerId": engineerId, "userId": userId, "items.ID": subNode.ID, "$or": [{"isDeleted": null}, {"isDeleted": false}]};
-                        let updateStatement = {$set: {"items.$": subNode}};
+                        let filter = { "compilationId": compilationId, "engineerId": engineerId, "userId": userId, "items.ID": subNode.ID, "$or": [{ "isDeleted": null }, { "isDeleted": false }] };
+                        let updateStatement = { $set: { "items.$": subNode } };
                         rttFacade.updateTreeInDetail(filter, updateStatement).then(function (rst) {
                             if (rst) {
                                 //success
-                                callback(req,res, false, "", actTplRst);
+                                callback(req, res, false, "", actTplRst);
                             } else {
                                 //failed
-                                callback(req,res, true, "更新失败!", null);
+                                callback(req, res, true, "更新失败!", null);
                             }
                         });
                     }
@@ -346,10 +346,10 @@ let mExport = {
     getRefRptTpl: function (req, res) {
         let params = JSON.parse(req.body.params),
             rptTplId = params.rptTplId;
-        let filter = {"ID": rptTplId};
-        RptTplModel.findOne(filter, '-_id').exec().then(function(rstTpl) {
+        let filter = { "ID": rptTplId };
+        RptTplModel.findOne(filter, '-_id').exec().then(function (rstTpl) {
             if (rstTpl) {
-                callback(req,res, false, "", rstTpl);
+                callback(req, res, false, "", rstTpl);
             } else {
                 callback(req, res, 'The report template was not found!', null);
             }
@@ -358,8 +358,8 @@ let mExport = {
     updateRptTpl: function (req, res) {
         let params = JSON.parse(req.body.params),
             rptTpl = JSON.parse(params.rptTpl);
-        let filter = {"ID": parseInt(rptTpl[JV.PROP_ID])},
-            options = {"overwrite": true};
+        let filter = { "ID": parseInt(rptTpl[JV.PROP_ID]) },
+            options = { "overwrite": true };
         RptTplModel.update(filter, rptTpl, options, function (err, rst) {
             if (err) {
                 callback(req, res, true, 'The report template was updated failed!', false);
@@ -372,8 +372,8 @@ let mExport = {
         let params = JSON.parse(req.body.params),
             orgRptTplId = params.orgRptTplId,
             newID = params.newRptTplId;
-        let filter = {"ID": orgRptTplId};
-        RptTplModel.findOne(filter, '-_id').exec().then(function(baseTplRst) {
+        let filter = { "ID": orgRptTplId };
+        RptTplModel.findOne(filter, '-_id').exec().then(function (baseTplRst) {
             if (baseTplRst) {
                 let _doc = baseTplRst["_doc"];
                 _doc["ID"] = newID;
@@ -383,9 +383,9 @@ let mExport = {
                 let rptTpl = new RptTplModel(_doc);
                 rptTpl.save(function (err, actTplRst) {
                     if (err) {
-                        callback(req,res, "报表模板创建错误", "", null);
+                        callback(req, res, "报表模板创建错误", "", null);
                     } else {
-                        callback(req,res, false, "", newID);
+                        callback(req, res, false, "", newID);
                     }
                 });
             } else {
@@ -396,14 +396,16 @@ let mExport = {
 };
 
 function copyRptCfg(src, dest) {
-    dest.margins = {Left: src.margins.Left, Right: src.margins.Right, Top: src.margins.Top, Bottom: src.margins.Bottom};
+    dest.margins = { Left: src.margins.Left, Right: src.margins.Right, Top: src.margins.Top, Bottom: src.margins.Bottom };
     dest.showVerticalLine = src.showVerticalLine;
     dest.isNarrow = src.isNarrow;
     dest.fillZero = src.fillZero;
     dest.fonts = [];
     for (let font of src.fonts) {
-        dest.fonts.push({"ID": font["ID"], "CfgDispName": font["CfgDispName"], "Name": font["Name"], "FontHeight": font["FontHeight"], "FontColor": font["FontColor"],
-            "FontBold": font["FontBold"], "FontItalic": font["FontItalic"], "FontUnderline": font["FontUnderline"], "FontStrikeOut": font["FontStrikeOut"], "FontAngle": font["FontAngle"]});
+        dest.fonts.push({
+            "ID": font["ID"], "CfgDispName": font["CfgDispName"], "Name": font["Name"], "FontHeight": font["FontHeight"], "FontColor": font["FontColor"],
+            "FontBold": font["FontBold"], "FontItalic": font["FontItalic"], "FontUnderline": font["FontUnderline"], "FontStrikeOut": font["FontStrikeOut"], "FontAngle": font["FontAngle"]
+        });
     }
 }
 

+ 1 - 1
modules/std_billsGuidance_lib/controllers/libController.js

@@ -18,7 +18,7 @@ class BillsGuideLibController extends BaseController{
     //获取编办及编办清单库信息
     async getComBillsLibInfo(req, res){
         try{
-            let comBillsLibInfo = await billsGuidanceFacade.getComBillsLibInfo();
+            let comBillsLibInfo = await billsGuidanceFacade.getComBillsLibInfo(req);
             callback(req, res, 0, '', comBillsLibInfo);
         }
         catch(err) {

+ 10 - 4
modules/std_billsGuidance_lib/controllers/viewController.js

@@ -8,19 +8,25 @@
  * @version
  */
 import BaseController from "../../common/base/base_controller";
+import mongoose from 'mongoose';
 let config = require("../../../config/config.js");
-class ViewsController extends BaseController{
-    redirectMain(req, res){
+const billsGuideLibModel = mongoose.model('std_billsGuidance_lib');
+import { checkCompilationPermission } from '../../common/base/base_util';
+
+class ViewsController extends BaseController {
+    redirectMain(req, res) {
         res.render('maintain/billsGuidance_lib/html/main.html',
             {
                 userAccount: req.session.managerData.username
             });
     }
-    redirectGuidance(req, res){
+    async redirectGuidance(req, res) {
+        const lib = await billsGuideLibModel.findOne({ ID: req.query.libID, deleted: false }).lean();
+        checkCompilationPermission(req, res, lib.compilationId, '/billsGuidance/main')
         res.render('maintain/billsGuidance_lib/html/zhiyin.html',
             {
                 userAccount: req.session.managerData.username,
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV)
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV)
             });
     }
 }

+ 117 - 115
modules/std_billsGuidance_lib/facade/facades.js

@@ -32,22 +32,22 @@ module.exports = {
     testItems
 };
 
-async function getCompilationList() {
+async function getCompilationList(req) {
     let compilationModel = new CompilationModel();
-    return await compilationModel.getCompilationList();
+    return await compilationModel.getPermissionCompilationList(req);
 }
 
-async function getComBillsLibInfo() {
-    let rst = {compilationList: [], billsLibs: []};
-    let compilationList = await getCompilationList();
-    if(compilationList.length <= 0){
+async function getComBillsLibInfo(req) {
+    let rst = { compilationList: [], billsLibs: [] };
+    let compilationList = await getCompilationList(req);
+    if (compilationList.length <= 0) {
         throw '没有数据';
     }
-    else{
-        for(let compilation of compilationList){
-            rst.compilationList.push({_id: compilation._id, name: compilation.name});
+    else {
+        for (let compilation of compilationList) {
+            rst.compilationList.push({ _id: compilation._id, name: compilation.name });
         }
-        rst.billsLibs = await billsLibModel.find({deleted: false}, '-_id billsLibId billsLibName');
+        rst.billsLibs = await billsLibModel.find({ deleted: false }, '-_id billsLibId billsLibName');
         return rst;
     }
 }
@@ -57,84 +57,86 @@ async function getBillsGuideLibs(findData) {
 }
 
 //拷贝工作内容并转化为树结构,形成项目指引数据
-async function genGuidanceItems(guidanceLibId, billsLibId){
-    let bills = await stdBillsModel.find({billsLibId: billsLibId, deleted: false, 'jobs.0': {$exists: true}});
+async function genGuidanceItems(guidanceLibId, billsLibId) {
+    let bills = await stdBillsModel.find({ billsLibId: billsLibId, deleted: false, 'jobs.0': { $exists: true } });
     //设置工作内容数据
     let jobIds = [];
     let totalJobs = [];
-    for(let bill of bills){
-        for(let job of bill.jobs){
+    for (let bill of bills) {
+        for (let job of bill.jobs) {
             jobIds.push(job.id);
         }
     }
     jobIds = Array.from(new Set(jobIds));
-    if(jobIds.length > 0){
-        totalJobs = await stdBillsJobsModel.find({deleted: false, id: {$in: jobIds}});
+    if (jobIds.length > 0) {
+        totalJobs = await stdBillsJobsModel.find({ deleted: false, id: { $in: jobIds } });
     }
-    if(totalJobs.length > 0){
+    if (totalJobs.length > 0) {
         let jobIdIndex = {};//id索引
-        for(let job of totalJobs){
+        for (let job of totalJobs) {
             jobIdIndex[job.id] = job;
         }
         let insertArr = [];
-        for(let bill of bills){
+        for (let bill of bills) {
             //排序后根据serialNo转换成NextSiblingID,倒序
             bill.jobs.sort(function (a, b) {
                 let rst = 0;
-                if(a.serialNo > b.serialNo){
+                if (a.serialNo > b.serialNo) {
                     rst = -1;
                 }
-                else if(a.serialNo < b.serialNo){
+                else if (a.serialNo < b.serialNo) {
                     rst = 1;
                 }
                 return rst;
             });
             let jobNoIndex = {};//下标索引
-            for(let i = 0; i < bill.jobs.length; i++){
-                let newItem = {libID: guidanceLibId, ID: uuidV1(), ParentID: -1, NextSiblingID: jobNoIndex[i - 1] ? jobNoIndex[i - 1]['ID'] : -1,
-                    name: jobIdIndex[bill.jobs[i]['id']]['content'], type: 0, billsID: bill.ID};
+            for (let i = 0; i < bill.jobs.length; i++) {
+                let newItem = {
+                    libID: guidanceLibId, ID: uuidV1(), ParentID: -1, NextSiblingID: jobNoIndex[i - 1] ? jobNoIndex[i - 1]['ID'] : -1,
+                    name: jobIdIndex[bill.jobs[i]['id']]['content'], type: 0, billsID: bill.ID
+                };
                 jobNoIndex[i] = newItem;
-                insertArr.push({insertOne: {document: newItem}});
+                insertArr.push({ insertOne: { document: newItem } });
             }
         }
         await billsGuideItemsModel.bulkWrite(insertArr);
     }
 }
 
-async function initBillsGuideLib(updateData){
+async function initBillsGuideLib(updateData) {
     await billsGuideLibModel.create(updateData);
     await genGuidanceItems(updateData.ID, updateData.billsLibId);
 }
 
 async function updateBillsGuideLib(data) {
-    if(data.updateType === 'delete'){
+    if (data.updateType === 'delete') {
         //删除所有条目
         await billsGuideLibModel.remove(data.findData);
-        await billsGuideItemsModel.remove({libID: data.findData.ID});
+        await billsGuideItemsModel.remove({ libID: data.findData.ID });
     }
     else {
-        await billsGuideLibModel.update(data.findData, {$set: data.updateData});
-        await engLibModel.update({'billsGuidance_lib.id': data.findData.ID}, {$set: {'billsGuidance_lib.$.name': data.updateData.name}}, {multi: true});
+        await billsGuideLibModel.update(data.findData, { $set: data.updateData });
+        await engLibModel.update({ 'billsGuidance_lib.id': data.findData.ID }, { $set: { 'billsGuidance_lib.$.name': data.updateData.name } }, { multi: true });
     }
 }
 
-async function getLibWithBills(libID){
-    let guidanceLib = await getBillsGuideLibs({ID: libID});
-    if(guidanceLib.length === 0){
+async function getLibWithBills(libID) {
+    let guidanceLib = await getBillsGuideLibs({ ID: libID });
+    if (guidanceLib.length === 0) {
         throw '不存在此指引库!';
     }
-    let billsLib = await stdBillsLibModel.findOne({billsLibId: guidanceLib[0].billsLibId});
-    if(!billsLib){
+    let billsLib = await stdBillsLibModel.findOne({ billsLibId: guidanceLib[0].billsLibId });
+    if (!billsLib) {
         throw '引用的清单规则库不存在!';
     }
-    let bills = await stdBillsModel.find({billsLibId: billsLib.billsLibId}, '-_id code name ID NextSiblingID ParentID jobs items comment');
-    return {guidanceLib: guidanceLib[0], bills};
+    let bills = await stdBillsModel.find({ billsLibId: billsLib.billsLibId }, '-_id code name ID NextSiblingID ParentID jobs items comment');
+    return { guidanceLib: guidanceLib[0], bills };
 }
 
-function getAttrs(field, datas){
+function getAttrs(field, datas) {
     let rst = [];
-    for(let data of datas){
-        if(data[field]){
+    for (let data of datas) {
+        if (data[field]) {
             rst.push(data[field]);
         }
     }
@@ -143,8 +145,8 @@ function getAttrs(field, datas){
 
 //定额项目指所引用定额是否被删除
 function rationAllExist(rationItems, stdRationIdx) {
-    for(let item of rationItems){
-        if(!stdRationIdx[item.rationID]){
+    for (let item of rationItems) {
+        if (!stdRationIdx[item.rationID]) {
             return false;
         }
     }
@@ -152,71 +154,71 @@ function rationAllExist(rationItems, stdRationIdx) {
 }
 
 //将同层树结构转为顺序数组
-function chainToArr(nodes){
+function chainToArr(nodes) {
     let rst = [];
     let tempIdx = {};
     let nodeIdx = {};
     //建索引
-    for(let node of nodes){
-        tempIdx[node.ID] = {ID: node.ID, NextSiblingID: node.NextSiblingID, preSibling: null, nextSibling: null};
+    for (let node of nodes) {
+        tempIdx[node.ID] = { ID: node.ID, NextSiblingID: node.NextSiblingID, preSibling: null, nextSibling: null };
         nodeIdx[node.ID] = node;
     }
     //建链
-    for(let i in tempIdx){
+    for (let i in tempIdx) {
         let temp = tempIdx[i];
-        if(temp.NextSiblingID != -1){
+        if (temp.NextSiblingID != -1) {
             let next = tempIdx[temp.NextSiblingID];
             temp.nextSibling = next;
             next.preSibling = temp;
         }
     }
     let firstNode = null;
-    for(let i in tempIdx){
-        if(!tempIdx[i].preSibling){
+    for (let i in tempIdx) {
+        if (!tempIdx[i].preSibling) {
             firstNode = tempIdx[i];
             break;
         }
     }
     //获得顺序队列
-    while(firstNode){
+    while (firstNode) {
         rst.push(nodeIdx[firstNode.ID]);
         firstNode = firstNode.nextSibling;
     }
     return rst;
 }
 
-async function getItemsBybills(guidanceLibID, billsID){
-    const type = {job: 0, ration: 1};
-    let items = await billsGuideItemsModel.find({libID: guidanceLibID, billsID: billsID, deleted: false});
-    let rationItems = _.filter(items, {type: type.ration});
+async function getItemsBybills(guidanceLibID, billsID) {
+    const type = { job: 0, ration: 1 };
+    let items = await billsGuideItemsModel.find({ libID: guidanceLibID, billsID: billsID, deleted: false });
+    let rationItems = _.filter(items, { type: type.ration });
     let rationIds = getAttrs('rationID', rationItems);
-    let stdRations = await stdRationModel.find({ID: {$in: rationIds}, $or: [{isDeleted: null}, {isDeleted: false}]});
+    let stdRations = await stdRationModel.find({ ID: { $in: rationIds }, $or: [{ isDeleted: null }, { isDeleted: false }] });
     let stdRationIndex = {};
-    for(let stdRation of stdRations){
+    for (let stdRation of stdRations) {
         stdRationIndex[stdRation.ID] = stdRation;
     }
     //判断定额完整性
-    if(!rationAllExist(rationItems, stdRationIndex)){
+    if (!rationAllExist(rationItems, stdRationIndex)) {
         //建定额链, 排序后再清除不存在的定额,保证顺序正确性
         rationItems = chainToArr(rationItems);
         //清除已被删除的定额
         let removeIds = [];
         _.remove(rationItems, function (item) {
-            if(!stdRationIndex[item.rationID]){
+            if (!stdRationIndex[item.rationID]) {
                 removeIds.push(item.ID);
                 return true;
             }
             return false;
         });
         _.remove(items, function (item) {
-           return removeIds.includes(item.ID);
+            return removeIds.includes(item.ID);
         });
-        await billsGuideItemsModel.remove({ID: {$in: removeIds}});
+        await billsGuideItemsModel.remove({ ID: { $in: removeIds } });
         //重组树结构
         let bulkArr = [];
-        for(let i = 0, len = rationItems.length; i < len; i++){
+        for (let i = 0, len = rationItems.length; i < len; i++) {
             rationItems[i].NextSiblingID = rationItems[i + 1] ? rationItems[i + 1].ID : -1;
-            bulkArr.push({updateOne: {filter: {ID: rationItems[i].ID}, update: {$set: {NextSiblingID: rationItems[i].NextSiblingID}}}});
+            bulkArr.push({ updateOne: { filter: { ID: rationItems[i].ID }, update: { $set: { NextSiblingID: rationItems[i].NextSiblingID } } } });
         }
         await billsGuideItemsModel.bulkWrite(bulkArr);
     }
@@ -225,82 +227,82 @@ async function getItemsBybills(guidanceLibID, billsID){
 
 async function updateItems(updateDatas) {
     let bulkArr = [];
-    for(let updateData of updateDatas){
-        if(updateData.updateType === 'create'){
-            bulkArr.push({insertOne: {document: updateData.updateData}});
+    for (let updateData of updateDatas) {
+        if (updateData.updateType === 'create') {
+            bulkArr.push({ insertOne: { document: updateData.updateData } });
         }
-        else if(updateData.updateType === 'update'){
-            bulkArr.push({updateOne: {filter: updateData.findData, update: {$set: updateData.updateData}}});
+        else if (updateData.updateType === 'update') {
+            bulkArr.push({ updateOne: { filter: updateData.findData, update: { $set: updateData.updateData } } });
         }
-        else{
-            bulkArr.push({deleteOne: {filter: updateData.findData}});
+        else {
+            bulkArr.push({ deleteOne: { filter: updateData.findData } });
         }
     }
-    if(bulkArr.length > 0){
+    if (bulkArr.length > 0) {
         await billsGuideItemsModel.bulkWrite(bulkArr);
     }
 }
 
 async function testItems(libID) {
-    let items = await billsGuideItemsModel.find({libID: libID});
+    let items = await billsGuideItemsModel.find({ libID: libID });
     //删除垃圾数据
     let delBulk = [];
     let itemsMapping = {};
-    for(let item of items){
+    for (let item of items) {
         itemsMapping[item.ID] = true;
     }
-    for(let item of items){
-        if(item.ParentID != -1 && !itemsMapping[item.ParentID]){
+    for (let item of items) {
+        if (item.ParentID != -1 && !itemsMapping[item.ParentID]) {
             delBulk.push({
                 deleteOne: {
-                    filter: {ID: item.ID}
+                    filter: { ID: item.ID }
                 }
             });
         }
     }
-    if(delBulk.length > 0){
+    if (delBulk.length > 0) {
         console.log(`delBulk.length`);
         console.log(delBulk.length);
         await billsGuideItemsModel.bulkWrite(delBulk);
     }
- /*   //查找同层节点含有相同NextSiblingID的节点
-    let rst = [];
-    let billsGroup = {};
-    for(let item of items){
-        if(!billsGroup[item.billsID]){
-            billsGroup[item.billsID] = [item];
-        }
-        else {
-            billsGroup[item.billsID].push(item);
-        }
-    }
-    for(let bGroup in billsGroup){
-        let group = billsGroup[bGroup];
-        let parentGroup = {};
-        for(let gItem of group){
-            if(!parentGroup[gItem.ParentID]){
-                parentGroup[gItem.ParentID] = [gItem]
-            }
-            else {
-                parentGroup[gItem.ParentID].push(gItem);
-            }
-        }
-        for(let pGroup in parentGroup){
-            let pGroupData = parentGroup[pGroup];
-            let nextGroup = {};
-            for(let nItem of pGroupData){
-                let sameNext = _.filter(pGroupData, {NextSiblingID: nItem.NextSiblingID});
-                if(sameNext.length > 1){
-                    console.log(`sameNext`);
-                    console.log(sameNext);
-                    if(!nextGroup[nItem.ParentID + nItem.NextSiblingID]){
-                        rst.push({NextSiblingID: nItem.NextSiblingID, ParentID: nItem.ParentID});
-                        nextGroup[nItem.ParentID + nItem.NextSiblingID] = 1;
-                    }
-                }
-            }
-        }
-
-    }*/
+    /*   //查找同层节点含有相同NextSiblingID的节点
+       let rst = [];
+       let billsGroup = {};
+       for(let item of items){
+           if(!billsGroup[item.billsID]){
+               billsGroup[item.billsID] = [item];
+           }
+           else {
+               billsGroup[item.billsID].push(item);
+           }
+       }
+       for(let bGroup in billsGroup){
+           let group = billsGroup[bGroup];
+           let parentGroup = {};
+           for(let gItem of group){
+               if(!parentGroup[gItem.ParentID]){
+                   parentGroup[gItem.ParentID] = [gItem]
+               }
+               else {
+                   parentGroup[gItem.ParentID].push(gItem);
+               }
+           }
+           for(let pGroup in parentGroup){
+               let pGroupData = parentGroup[pGroup];
+               let nextGroup = {};
+               for(let nItem of pGroupData){
+                   let sameNext = _.filter(pGroupData, {NextSiblingID: nItem.NextSiblingID});
+                   if(sameNext.length > 1){
+                       console.log(`sameNext`);
+                       console.log(sameNext);
+                       if(!nextGroup[nItem.ParentID + nItem.NextSiblingID]){
+                           rst.push({NextSiblingID: nItem.NextSiblingID, ParentID: nItem.ParentID});
+                           nextGroup[nItem.ParentID + nItem.NextSiblingID] = 1;
+                       }
+                   }
+               }
+           }
+   
+       }*/
     return delBulk.length;
 }

+ 44 - 40
modules/std_bills_unitprice_feature_lib/controllers/bills_unitprice_feature_controller.js

@@ -6,105 +6,109 @@ import BaseController from "../../common/base/base_controller";
 import featureFacade from "../facade/bills_unitprice_feature_facade";
 let config = require("../../../config/config.js");
 import CompilationModel from '../../users/models/compilation_model';
+import { checkCompilationPermission } from '../../common/base/base_util';
 
-class BillsUnitPriceFeatureController extends BaseController{
+class BillsUnitPriceFeatureController extends BaseController {
     async main(request, response) {
         let compilationModel = new CompilationModel();
-        let compilationList = await compilationModel.getCompilationList({_id: 1, name: 1});
-        compilationList.unshift({_id: 'all', name: '所有'});
+        let compilationList = await compilationModel.getPermissionCompilationList(request, { _id: 1, name: 1 });
+        compilationList.unshift({ _id: 'all', name: '所有' });
         let activeCompilation = compilationList.find(compilation => compilation._id.toString() === request.query.filter);
         if (activeCompilation) {
             activeCompilation.active = 'active';
         } else {
             compilationList[0].active = 'active'
         }
-        let filter = request.query.filter ? {compilationId: request.query.filter} : {};
-        let featureLibs = await featureFacade.findByCondition(filter,{feature:0},false);
+        let filter = request.query.filter ? { compilationId: request.query.filter } : {};
+        let featureLibs = await featureFacade.findByCondition(filter, { feature: 0 }, false);
+        const compilationPermission = request.session.managerData.compilationPermission || [];
+        featureLibs = featureLibs.filter(lib => compilationPermission.includes(lib.compilationId));
         let randerData = {
-            title:'清单估算指标库',
+            title: '清单估算指标库',
             userAccount: request.session.managerData.username,
             userID: request.session.managerData.userID,
-            featureLibs:featureLibs,
+            featureLibs: featureLibs,
             compilationList: compilationList,
             layout: 'maintain/common/html/layout'
         };
         response.render("maintain/bills_unitprice_feature_lib/html/main", randerData);
     }
-    async addLib(request, response){
+    async addLib(request, response) {
         try {
             await featureFacade.addLib(request.body);
-        }catch (error) {
+        } catch (error) {
             console.log(error);
         }
         response.redirect(request.headers.referer);
     }
-    async findLib(request, response){
-        let result={
-            error:0
+    async findLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let conditions={'ID' : data.ID};
+            let conditions = { 'ID': data.ID };
             let resultData = await featureFacade.findByCondition(conditions);
-            result.data=resultData;
-        }catch (err){
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async saveLib(request, response){
-        let result={
-            error:0
+    async saveLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await featureFacade.saveLib(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await featureFacade.saveLib(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async deleteLibByID(request,response){
-        let result={
-            error:0
+    async deleteLibByID(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await featureFacade.deleteLibByID(data.ID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await featureFacade.deleteLibByID(data.ID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async edit(request,response){
+    async edit(request, response) {
         //先取出替换库信息:
         let libID = request.params.libID;
-        let featureLib = await featureFacade.findByCondition({'ID':libID});
-        if(featureLib){
+        let featureLib = await featureFacade.findByCondition({ 'ID': libID });
+        if (featureLib) {
+            checkCompilationPermission(request, response, featureLib.compilationId, '/billsUnitPriceFeature/main');
             let randerData = {
-                title:'清单估算指标库',
-                mainURL:'/billsUnitPriceFeature/main',
-                libName:featureLib.name,
+                title: '清单估算指标库',
+                mainURL: '/billsUnitPriceFeature/main',
+                libName: featureLib.name,
                 userAccount: request.session.managerData.username,
                 userID: request.session.managerData.userID,
-                featureList:JSON.stringify(featureLib.feature),
-                libID:libID,
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                featureList: JSON.stringify(featureLib.feature),
+                libID: libID,
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
                 layout: 'maintain/common/html/edit_layout'
             };
             response.render("maintain/bills_unitprice_feature_lib/html/edit", randerData);
-        }else {
+        } else {
             response.redirect(request.headers.referer);
         }
     }

+ 20 - 19
modules/std_glj_lib/controllers/gljMapController.js

@@ -3,21 +3,21 @@
  */
 
 import BaseController from "../../common/base/base_controller";
-import {GljMapDao} from "../models/gljMapModel";
+import { GljMapDao } from "../models/gljMapModel";
 import CompilationModel from "../../users/models/compilation_model";
 let gljMapDao = new GljMapDao();
 
-let callback = function(req, res, err, message, data){
-    res.json({error: err, message: message, data: data});
+let callback = function (req, res, err, message, data) {
+    res.json({ error: err, message: message, data: data });
 };
-class GljMapController extends BaseController{
+class GljMapController extends BaseController {
     //该费用定额下补充人材机分类树模板数据条数
     async classTemplateCount(req, res) {
         try {
             let count = await gljMapDao.classTemplateCount(req.params.compilationId);
-            callback(req, res, 0, 'success', {count});
+            callback(req, res, 0, 'success', { count });
         } catch (err) {
-            callback(req, res, 1, err, {count: 0});
+            callback(req, res, 1, err, { count: 0 });
         }
     }
     //将该标准人材机库的分类树设置为该费用定额下补充人材机分类树的模板
@@ -30,50 +30,51 @@ class GljMapController extends BaseController{
             callback(req, res, 1, err, null);
         }
     }
-    async getCompilationList(req, res){
-        try{
+    async getCompilationList(req, res) {
+        try {
             let compilationModel = new CompilationModel(), rst = [];
-            let compilationList = await compilationModel.getCompilationList();
-            if(compilationList.length <= 0){
+            let compilationList = await compilationModel.getPermissionCompilationList(req);
+            if (compilationList.length <= 0) {
                 throw '没有数据';
             }
-            else{
+            else {
 
                 compilationList.forEach(function (compilation) {
-                    rst.push({_id: compilation._id, name: compilation.name});
+                    rst.push({ _id: compilation._id, name: compilation.name });
                 })
                 callback(req, res, false, '', rst);
             }
         }
-        catch(err) {
+        catch (err) {
             callback(req, res, err, '没有数据', null);
         }
     }
-    getGljLib(req, res){
+    getGljLib(req, res) {
         let libId = req.body.libId;
         gljMapDao.getGljLib(libId, function (err, message, data) {
             callback(req, res, err, message, data);
         })
     }
-    getAllGljLib(req, res){
+    getAllGljLib(req, res) {
+        const compilationPermission = req.session.managerData.compilationPermission || [];
         gljMapDao.getAllGljLib(function (err, message, data) {
             callback(req, res, err, message, data);
-        })
+        }, compilationPermission)
     }
-    createGljLib(req, res){
+    createGljLib(req, res) {
         let gljLibObj = JSON.parse(req.body.gljLibObj);
         gljMapDao.createGljLib(gljLibObj, function (err, message, data) {
             callback(req, res, err, message, data);
         })
     }
-    renameGljLib(req, res){
+    renameGljLib(req, res) {
         let oprtor = req.body.oprtor,
             renameObj = JSON.parse(req.body.renameObj);
         gljMapDao.renameGljLib(oprtor, renameObj, function (err, message) {
             callback(req, res, err, message, null);
         })
     }
-    removeGljLib(req, res){
+    removeGljLib(req, res) {
         let oprtor = req.body.oprtor,
             libId = req.body.libId;
         gljMapDao.removeGljLib(oprtor, libId, function (err, message) {

+ 23 - 20
modules/std_glj_lib/controllers/viewsController.js

@@ -11,24 +11,27 @@ const compilationModel = mongoose.model('compilation');
 const stdGljLibModel = mongoose.model('std_glj_lib_map');
 let config = require("../../../config/config.js");
 const fs = require('fs');
-class ViewsController extends BaseController{
-    redirectMain(req, res){
+import { checkCompilationPermission } from '../../common/base/base_util';
+
+class ViewsController extends BaseController {
+    redirectMain(req, res) {
         res.render('maintain/std_glj_lib/html/main.html',
-        {
-            userAccount: req.session.managerData.username
-        });
+            {
+                userAccount: req.session.managerData.username
+            });
     }
-    async redirectGlj(req, res){
+    async redirectGlj(req, res) {
         let overWriteUrl = null;
-        let stdGljLib = await stdGljLibModel.findOne({ID: req.query.gljLibId, deleted: false});
+        let stdGljLib = await stdGljLibModel.findOne({ ID: req.query.gljLibId, deleted: false });
         let priceProperties = [],
             consumeAmtProperties = [];
-        if(stdGljLib){
-           let compilation = await compilationModel.findOne({_id: mongoose.Types.ObjectId(stdGljLib.compilationId)});
-           priceProperties = compilation.priceProperties ? compilation.priceProperties : [];
-           consumeAmtProperties = compilation.consumeAmtProperties ? compilation.consumeAmtProperties : [];
-           let absoluteUrl = compilation.overWriteUrl ? req.app.locals.rootDir + compilation.overWriteUrl : req.app.locals.rootDir;
-           overWriteUrl = fs.existsSync(absoluteUrl) && fs.statSync(absoluteUrl).isFile()? compilation.overWriteUrl : null;
+        if (stdGljLib) {
+            checkCompilationPermission(req, res, stdGljLib.compilationId, '/stdGljRepository/main');
+            let compilation = await compilationModel.findOne({ _id: mongoose.Types.ObjectId(stdGljLib.compilationId) });
+            priceProperties = compilation.priceProperties ? compilation.priceProperties : [];
+            consumeAmtProperties = compilation.consumeAmtProperties ? compilation.consumeAmtProperties : [];
+            let absoluteUrl = compilation.overWriteUrl ? req.app.locals.rootDir + compilation.overWriteUrl : req.app.locals.rootDir;
+            overWriteUrl = fs.existsSync(absoluteUrl) && fs.statSync(absoluteUrl).isFile() ? compilation.overWriteUrl : null;
         }
         // await gljDao.copyLib(7,24); //UAT 部颁2018 -> 部颁2018计价标准
         // await gljDao.copyLib(7,25); //PROD 部颁2018 -> 部颁2018计价标准
@@ -42,13 +45,13 @@ class ViewsController extends BaseController{
         // await gljDao.copyLib(14,33); //PROD 部颁公路工料机库(2007营改增) -> 广东公路养护人材机库(2010) // 这个是公路PROD2.0的数据库,在hw服务器上,4080端口
 
         res.render('maintain/std_glj_lib/html/gongliao.html',
-        {
-            userAccount: req.session.managerData.username,
-            LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
-            priceProperties: JSON.stringify(priceProperties),
-            consumeAmtProperties: JSON.stringify(consumeAmtProperties),
-            overWriteUrl: overWriteUrl
-        });
+            {
+                userAccount: req.session.managerData.username,
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
+                priceProperties: JSON.stringify(priceProperties),
+                consumeAmtProperties: JSON.stringify(consumeAmtProperties),
+                overWriteUrl: overWriteUrl
+            });
     }
 }
 

+ 2 - 1
modules/std_glj_lib/models/gljMapModel.js

@@ -89,9 +89,10 @@ class GljMapDao extends OprDao{
             }
         })
     }
-    async getAllGljLib(callback){
+    async getAllGljLib(callback, compilationPermission){
         try{
             let gljLibs = await gljMapModel.find({deleted: false});
+            gljLibs = gljLibs.filter(lib => compilationPermission.includes(lib.compilationId));
             for(let gljLib of gljLibs){
                 let tempRationLibs = [];
                 for(let rationLib of gljLib.rationLibs){

+ 47 - 42
modules/structural_segment_lib/controllers/structural_segment_controller.js

@@ -6,109 +6,114 @@ import structuralSegmentFacade from "../facade/structural_segment_facade";
 let config = require("../../../config/config.js");
 let logger = require('../../../logs/log_helper').logger;
 import CompilationModel from '../../users/models/compilation_model';
+import { checkCompilationPermission } from '../../common/base/base_util';
 
-class StructuralSegmentController extends BaseController{
+class StructuralSegmentController extends BaseController {
     async main(request, response) {
-     
+
         let compilationModel = new CompilationModel();
-        let compilationList = await compilationModel.getCompilationList({_id: 1, name: 1});
-        compilationList.unshift({_id: 'all', name: '所有'});
+        let compilationList = await compilationModel.getPermissionCompilationList(request, { _id: 1, name: 1 });
+        compilationList.unshift({ _id: 'all', name: '所有' });
         let activeCompilation = compilationList.find(compilation => compilation._id.toString() === request.query.filter);
         if (activeCompilation) {
             activeCompilation.active = 'active';
         } else {
             compilationList[0].active = 'active'
         }
-        let filter = request.query.filter ? {compilationID: request.query.filter} : {};
-        let templateLibs = await structuralSegmentFacade.findByCondition(filter,{feature:0},false);
+        let filter = request.query.filter ? { compilationID: request.query.filter } : {};
+        let templateLibs = await structuralSegmentFacade.findByCondition(filter, { feature: 0 }, false);
+        const compilationPermission = request.session.managerData.compilationPermission || [];
+        templateLibs = templateLibs.filter(lib => compilationPermission.includes(lib.compilationID));
         let renderData = {
-            title:'结构分部库',
+            title: '结构分部库',
             userAccount: request.session.managerData.username,
             userID: request.session.managerData.userID,
-            templateLibs:templateLibs,
+            templateLibs: templateLibs,
             compilationList: compilationList,
             layout: 'maintain/common/html/layout'
         };
         response.render("maintain/structural_segment_lib/html/main", renderData);
     }
-    async addLib(request, response){
+    async addLib(request, response) {
         try {
             await structuralSegmentFacade.addLib(request.body);
-        }catch (error) {
+        } catch (error) {
             console.log(error);
         }
         response.redirect(request.headers.referer);
     }
-    async findLib(request, response){
-        let result={
-            error:0
+    async findLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let conditions={'ID' : data.ID};
+            let conditions = { 'ID': data.ID };
             let resultData = await structuralSegmentFacade.findByCondition(conditions);
-            result.data=resultData;
-        }catch (err){
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async saveLib(request, response){
-        let result={
-            error:0
+    async saveLib(request, response) {
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
             console.log(data.data);
-            let resultData= await structuralSegmentFacade.saveLib(data);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await structuralSegmentFacade.saveLib(data);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async deleteLibByID(request,response){
+    async deleteLibByID(request, response) {
         logger.info(`delete projectFeatureLib ${request.ip}`);
-        let result={
-            error:0
+        let result = {
+            error: 0
         };
         try {
             let data = request.body.data;
             data = JSON.parse(data);
-            let resultData= await structuralSegmentFacade.deleteLibByID(data.ID);
-            result.data=resultData;
-        }catch (err){
+            let resultData = await structuralSegmentFacade.deleteLibByID(data.ID);
+            result.data = resultData;
+        } catch (err) {
             console.log(err);
-            result.error=1;
+            result.error = 1;
             result.message = err.message;
         }
         response.json(result);
     }
-    async edit(request,response){
+    async edit(request, response) {
         //先取出替换库信息:
         let libID = request.params.libID;
-       
-        let structuralSegment = await structuralSegmentFacade.findByCondition({'ID':libID});
-        if(structuralSegment){
+
+        let structuralSegment = await structuralSegmentFacade.findByCondition({ 'ID': libID });
+        if (structuralSegment) {
+            checkCompilationPermission(request, response, structuralSegment.compilationID, '/structuralSegment/main');
+
             let randerData = {
-                title:'结构分部库',
-                mainURL:'/structuralSegment/main',
-                libName:structuralSegment.name,
+                title: '结构分部库',
+                mainURL: '/structuralSegment/main',
+                libName: structuralSegment.name,
                 userAccount: request.session.managerData.username,
                 userID: request.session.managerData.userID,
-                templateLibs:JSON.stringify(structuralSegment),
-                libID:libID,
-                LicenseKey:config.getLicenseKey(process.env.NODE_ENV),
+                templateLibs: JSON.stringify(structuralSegment),
+                libID: libID,
+                LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
                 layout: 'maintain/common/html/edit_layout'
             };
             response.render("maintain/structural_segment_lib/html/edit", randerData);
-        }else {
+        } else {
             response.redirect(request.headers.referer);
         }
     }

+ 27 - 0
modules/users/controllers/compilation_controller.js

@@ -366,6 +366,33 @@ class CompilationController extends BaseController {
     }
 
     /**
+ * 通过工程专业ID拷贝工程专业
+ * @param request
+ * @param response
+ * @returns {Promise.<void>}
+ */
+    async copyEngineer(request, response) {
+        let result = {
+            error: 0
+        };
+        try {
+            let data = request.body.data;
+            data = JSON.parse(data);
+            if (data.id) {
+                let engineeringLibModel = new EngineeringLibModel();
+                result.data = await engineeringLibModel.copyEngineer(data.id);
+            } else {
+                throw new Error("提交数据有误");
+            }
+        } catch (err) {
+            console.log(err);
+            result.error = 1;
+            result.message = err.message;
+        }
+        response.json(result);
+    }
+
+    /**
      * 修改保存工程专业信息-用于异步操作
      * @param request
      * @param response

+ 24 - 19
modules/users/controllers/login_controller.js

@@ -25,10 +25,10 @@ class LoginController extends BaseController {
      * @return {void|Mixed}
      */
     index(request, response) {
-        let title =  config[process.env.NODE_ENV].title?config[process.env.NODE_ENV].title:'养护云版';
+        let title = config[process.env.NODE_ENV].title ? config[process.env.NODE_ENV].title : '养护云版';
         let renderData = {
             layout: false,
-            title:title
+            title: title
         };
         let managerSessionData = request.session.managerData;
         if (managerSessionData !== undefined) {
@@ -72,16 +72,17 @@ class LoginController extends BaseController {
                     iconClass: 'glyphicon glyphicon-home'
                 }
             };
+            let compilationPermission = []; // 可用编办权限
             let toolMenuData = [];
             let toolPermissionController = [];
             if (managerData.super_admin !== 1) {
                 let permissionGroup = managerData.permission !== undefined && managerData.permission !== '' ?
-                    await permissionGroupModel.findDataByCondition({_id: managerData.permission}) : '';
+                    await permissionGroupModel.findDataByCondition({ _id: managerData.permission }) : '';
                 // let otherPermission = [];
                 if (permissionGroup !== undefined && permissionGroup !== '' && permissionGroup.permission !== undefined && permissionGroup.permission !== '') {
                     let permissionIdList = JSON.parse(permissionGroup.permission);
                     for (let top of permissionIdList.top) {
-                        let permissionInfo = await permissionModel.findDataByCondition({_id:top});
+                        let permissionInfo = await permissionModel.findDataByCondition({ _id: top });
                         menuData[permissionInfo.controller] = {
                             title: permissionInfo.name,
                             url: permissionInfo.url,
@@ -91,10 +92,10 @@ class LoginController extends BaseController {
                         }
                     }
                     for (let per in permissionIdList) {
-                        if (per !== 'top' && per !== 'tool') {
+                        if (per !== 'top' && per !== 'tool' && per !== 'compilation') {
                             let permissionArray = permissionIdList[per];
                             for (let pa of permissionArray) {
-                                let permissionInfo = await permissionModel.findDataByCondition({_id:pa});
+                                let permissionInfo = await permissionModel.findDataByCondition({ _id: pa });
                                 if (permissionInfo !== undefined && permissionInfo !== '') {
                                     if (permissionInfo.isMenu) {
                                         // 属于二级菜单
@@ -118,11 +119,13 @@ class LoginController extends BaseController {
                                     }
                                 }
                             }
+                        } else if (per === 'compilation') {
+                            compilationPermission = permissionIdList[per];
                         } else if (per === 'tool') {
                             // 工具里的页面权限
                             let permissionArray = permissionIdList[per];
                             for (let pa of permissionArray) {
-                                let permissionInfo = await permissionModel.findDataByCondition({_id:pa});
+                                let permissionInfo = await permissionModel.findDataByCondition({ _id: pa });
                                 if (permissionInfo.isMenu) {
                                     toolMenuData.push({
                                         title: permissionInfo.name,
@@ -144,7 +147,7 @@ class LoginController extends BaseController {
                 }
             } else {
                 // 获取数据库菜单列表
-                let menuPermissionList = await permissionModel.getList({isMenu:true});
+                let menuPermissionList = await permissionModel.getList({ isMenu: true });
                 let subList = [];
                 for (let menu of menuPermissionList) {
                     if (menu.pid === 0) {
@@ -156,16 +159,16 @@ class LoginController extends BaseController {
                             children: {},
                         }
                     } else if (menu.pid !== 4) {
-                      subList.push(menu);//防止子节点ID比父节点ID小的问题
+                        subList.push(menu);//防止子节点ID比父节点ID小的问题
                     }
                 }
-                for(let s of subList){
-                  let action = {
-                   title: s.name,
-                   url: s.url,
-                   name: s.action,
-                   };
-                   menuData[s.controller].children[s.action] = action;
+                for (let s of subList) {
+                    let action = {
+                        title: s.name,
+                        url: s.url,
+                        name: s.action,
+                    };
+                    menuData[s.controller].children[s.action] = action;
                 }
 
                 // 超级管理员二级菜单添加
@@ -177,7 +180,7 @@ class LoginController extends BaseController {
             }
             // 获取所有工具里的权限控制器名称
             let toolAllPermission = [];
-            let toolAllPermissionList = await permissionModel.getList({pid:4});
+            let toolAllPermissionList = await permissionModel.getList({ pid: 4 });
             for (let tool of toolAllPermissionList) {
                 toolAllPermission.push(tool.controller);
                 if (managerData.super_admin === 1) {
@@ -197,7 +200,8 @@ class LoginController extends BaseController {
 
             let managerSession = {
                 username: managerData.username,
-                real_name:managerData.real_name,
+                compilationPermission,
+                real_name: managerData.real_name,
                 loginTime: currentTime,
                 sessionToken: sessionToken,
                 userID: managerData.id,
@@ -219,9 +223,10 @@ class LoginController extends BaseController {
             };
             let updateResult = managerModel.updateById(managerData._id, updateData);
             if (!updateResult) {
-                throw {code: 44003, err: '更新登录信息失败!'};
+                throw { code: 44003, err: '更新登录信息失败!' };
             }
         } catch (error) {
+            console.log(error);
             responseData.error = error.code;
             responseData.msg = error.err;
         }

+ 35 - 20
modules/users/controllers/manager_controller.js

@@ -11,7 +11,8 @@ import PermissionModel from "../models/permission_model";
 import PermissionGroupModel from "../models/permission_group_model";
 import Config from "../../../config/config";
 let config = require("../../../config/config.js");
-import {default as category, List as categoryList} from "../../common/const/category_const.js";
+import { default as category, List as categoryList } from "../../common/const/category_const.js";
+import CompilationModel from '../models/compilation_model';
 
 class ManagerController extends BaseController {
 
@@ -30,7 +31,7 @@ class ManagerController extends BaseController {
         try {
             // 查找管理员用户列表
             let managerModel = new ManagerModel();
-            let total = await managerModel.count({super_admin: 0});
+            let total = await managerModel.count({ super_admin: 0 });
 
             // 分页数据
             let page = request.query.page === undefined ? 1 : request.query.page;
@@ -56,7 +57,7 @@ class ManagerController extends BaseController {
                 filter.officeName = officeInfo.name
             }
             if (request.query.permission !== undefined && request.query.permission !== '0') {
-                let permissionGroupInfo = await permissionGroupModel.findDataByCondition({_id: request.query.permission});
+                let permissionGroupInfo = await permissionGroupModel.findDataByCondition({ _id: request.query.permission });
                 filter.permissionGroupName = permissionGroupInfo.name;
             }
 
@@ -70,7 +71,7 @@ class ManagerController extends BaseController {
                         for (let p in groupPermissionList) {
                             if (p === 'top') {
                                 for (let t of groupPermissionList[p]) {
-                                    let topInfo = await permissionModel.findDataByCondition({_id:t});
+                                    let topInfo = await permissionModel.findDataByCondition({ _id: t });
                                     topPermissionList.push(topInfo.name);
                                 }
                                 break;
@@ -89,7 +90,7 @@ class ManagerController extends BaseController {
                     });
                     managerList[tmp].officeName = cate !== undefined ? cate.name : '';
 
-                    let groupInfo = managerList[tmp].permission !== '' ? await permissionGroupModel.findDataByCondition({_id:managerList[tmp].permission}) : '';
+                    let groupInfo = managerList[tmp].permission !== '' ? await permissionGroupModel.findDataByCondition({ _id: managerList[tmp].permission }) : '';
                     managerList[tmp].permissionName = groupInfo !== undefined && groupInfo !== '' ? groupInfo.name : '';
                 }
             }
@@ -105,9 +106,9 @@ class ManagerController extends BaseController {
             permissionGroupList: permissionGroupList,
             permissionGroupList2: permissionGroupList2,
             layout: 'users/views/layout/layout',
-            title : config[process.env.NODE_ENV].title?config[process.env.NODE_ENV].title:'养护云版',
+            title: config[process.env.NODE_ENV].title ? config[process.env.NODE_ENV].title : '养护云版',
             filter: filter,
-            LicenseKey:config.getLicenseKey(process.env.NODE_ENV)
+            LicenseKey: config.getLicenseKey(process.env.NODE_ENV)
         };
         response.render('users/views/manager/index', renderData);
     }
@@ -122,7 +123,7 @@ class ManagerController extends BaseController {
         let id = request.body.manager_id;
         let permission = request.body.permission !== '0' ? request.body.permission : '';
         let managerModel = new ManagerModel();
-        let result = await managerModel.updateById(id, {permission: permission});
+        let result = await managerModel.updateById(id, { permission: permission });
 
         if (!result) {
             throw '修改失败';
@@ -196,7 +197,7 @@ class ManagerController extends BaseController {
         let id = request.params.id;
 
         let managerModel = new ManagerModel();
-        let result = await managerModel.updateById(id, {can_login: canLogin});
+        let result = await managerModel.updateById(id, { can_login: canLogin });
 
         // 修改成功
         if (!result) {
@@ -217,7 +218,7 @@ class ManagerController extends BaseController {
         try {
             // 查找对应超级管理员数据
             let managerModel = new ManagerModel();
-            adminData = await managerModel.findDataByCondition({username: 'admin'});
+            adminData = await managerModel.findDataByCondition({ username: 'admin' });
 
         } catch (error) {
             console.log(error);
@@ -225,9 +226,9 @@ class ManagerController extends BaseController {
 
         let renderData = {
             adminData: adminData,
-            title : config[process.env.NODE_ENV].title?config[process.env.NODE_ENV].title:'养护云版',
+            title: config[process.env.NODE_ENV].title ? config[process.env.NODE_ENV].title : '养护云版',
             layout: 'users/views/layout/layout',
-            LicenseKey:config.getLicenseKey(process.env.NODE_ENV)
+            LicenseKey: config.getLicenseKey(process.env.NODE_ENV)
         };
         response.render('users/views/manager/save', renderData);
     }
@@ -284,17 +285,25 @@ class ManagerController extends BaseController {
         let groupList = [];
         let topPermissionList = [];
         let permissionList = [];
+        let compilationList = [];
         try {
+            // 获取编办列表
+            const compilationModel = new CompilationModel();
+            compilationList = await compilationModel.getCompilationList({ _id: 1, name: 1 });
+
+            console.log('enrerererrer');
             // 获取最高级权限列表
             let permissionModel = new PermissionModel();
-            topPermissionList = await permissionModel.getList({pid:0});
+            topPermissionList = await permissionModel.getList({ pid: 0 });
+
+            console.log('enrerererrer');
 
             // 获取所有权限列表,按排序
             permissionList = topPermissionList;
             for (let index in permissionList) {
-                let count = await permissionModel.count({pid:permissionList[index].ID});
+                let count = await permissionModel.count({ pid: permissionList[index].ID });
                 if (count > 0) {
-                    permissionList[index].secondPermissionList = await permissionModel.getList({pid: permissionList[index].ID});
+                    permissionList[index].secondPermissionList = await permissionModel.getList({ pid: permissionList[index].ID });
                 } else {
                     permissionList[index].secondPermissionList = [];
                 }
@@ -305,6 +314,9 @@ class ManagerController extends BaseController {
             let permissionGroupModel = new PermissionGroupModel();
             let total = await permissionGroupModel.count();
 
+            console.log(`total`);
+            console.log(total);
+
             // 分页数据
             let page = request.query.page === undefined ? 1 : request.query.page;
             pageData = {
@@ -320,14 +332,14 @@ class ManagerController extends BaseController {
             if (groupList.length > 0) {
                 let managerModel = new ManagerModel();
                 for (let tmp in groupList) {
-                    let managerCount = await managerModel.count({permission: groupList[tmp]._id});
+                    let managerCount = await managerModel.count({ permission: groupList[tmp]._id });
                     groupList[tmp].manager_count = managerCount;
                     let groupPermissionList = JSON.parse(groupList[tmp].permission);
                     for (let p in groupPermissionList) {
                         if (p === 'top') {
                             let topPermissionList = [];
                             for (let t of groupPermissionList[p]) {
-                                let topInfo = await permissionModel.findDataByCondition({_id:t});
+                                let topInfo = await permissionModel.findDataByCondition({ _id: t });
                                 topPermissionList.push(topInfo.name);
                             }
                             groupList[tmp].top_name = topPermissionList.join(',');
@@ -338,14 +350,17 @@ class ManagerController extends BaseController {
         } catch (error) {
 
         }
+        console.log(`groupList`);
+        console.log(groupList);
         let renderData = {
+            compilationList,
             groupList: groupList,
             topPermissionList: topPermissionList,
             permissionList: permissionList,
             pages: pageData,
-            title : config[process.env.NODE_ENV].title?config[process.env.NODE_ENV].title:'养护云版',
+            title: config[process.env.NODE_ENV].title ? config[process.env.NODE_ENV].title : '养护云版',
             layout: 'users/views/layout/layout',
-            LicenseKey:config.getLicenseKey(process.env.NODE_ENV)
+            LicenseKey: config.getLicenseKey(process.env.NODE_ENV)
         };
         response.render('users/views/manager/authority', renderData);
     }
@@ -394,7 +409,7 @@ class ManagerController extends BaseController {
             // 并清空用户所在权限组
             let managerModel = new ManagerModel();
             await managerModel.updateByPermission(id);
-        } catch(err) {
+        } catch (err) {
             throw err;
         }
         response.redirect(request.headers.referer);

+ 10 - 0
modules/users/models/compilation_model.js

@@ -59,6 +59,16 @@ class CompilationModel extends BaseModel {
         return [];
     }
 
+    // 根据用户权限过滤编办
+    async getPermissionCompilationList(req, fields = null) {
+        const compilationPermission = req.session.managerData.compilationPermission || [];
+        console.log(`req.session.managerData`);
+        console.log(req.session.managerData.compilationPermission);
+        const compilationList = await this.getCompilationList(fields);
+        console.log(compilationList);
+        return compilationList.filter(item => compilationPermission.includes(item._id) || compilationPermission.includes(item._id.toString()));
+    }
+
     /**
      * 获取编办列表
      *

+ 64 - 55
modules/users/models/engineering_lib_model.js

@@ -9,7 +9,7 @@ import mongoose from "mongoose";
 import BaseModel from "../../common/base/base_model";
 import CompilationModel from "./compilation_model";
 let stdRationLibModel = mongoose.model("std_ration_lib_map");
-import {default as EngineeringConst, List as EngineeringList} from "../../common/const/engineering";
+import { default as EngineeringConst, List as EngineeringList } from "../../common/const/engineering";
 
 class EngineeringLibModel extends BaseModel {
     /**
@@ -36,7 +36,7 @@ class EngineeringLibModel extends BaseModel {
             return result;
         }
         let id = '';
-        for(let tmp of data) {
+        for (let tmp of data) {
             if (tmp.engineering === engineering) {
                 id = tmp.engineering_id;
                 break;
@@ -45,16 +45,16 @@ class EngineeringLibModel extends BaseModel {
         if (id === '') {
             return result;
         }
-        let condition = {_id: id};
+        let condition = { _id: id };
         return this.findDataByCondition(condition);
     }
 
-    async getLibsByValuationID(valuationID){
-        return this.findDataByCondition({valuationID:valuationID},null,false);
+    async getLibsByValuationID(valuationID) {
+        return this.findDataByCondition({ valuationID: valuationID }, null, false);
     }
 
-    async deleteByValuationID(valuationID){
-        return await this.db.delete({valuationID:valuationID});
+    async deleteByValuationID(valuationID) {
+        return await this.db.delete({ valuationID: valuationID });
     }
 
 
@@ -63,14 +63,14 @@ class EngineeringLibModel extends BaseModel {
      * @param valuationID
      * @returns {Promise.<*>}
      */
-    async addStdLib(valuationID){
+    async addStdLib(valuationID) {
         let stdLibs = [];
-        for(let eng of EngineeringList){
+        for (let eng of EngineeringList) {
             let tem = {
-                glj_col:{showAdjustPrice:false},
-                valuationID:valuationID,
-                name:eng.name,
-                engineering:eng.value
+                glj_col: { showAdjustPrice: false },
+                valuationID: valuationID,
+                name: eng.name,
+                engineering: eng.value
             };
             stdLibs.push(tem);
         }
@@ -78,18 +78,18 @@ class EngineeringLibModel extends BaseModel {
         return result;
     }
 
-    async addEngineer(data){
-        data.glj_col = {showAdjustPrice:false};
-        if(data.compilationId && data.compilationId!=""){
+    async addEngineer(data) {
+        data.glj_col = { showAdjustPrice: false };
+        if (data.compilationId && data.compilationId != "") {
             data.ration_lib = [];
-            let rationList = await stdRationLibModel.find({compilationId:data.compilationId},['ID','dispName']);
-            for(let i =0;i< rationList.length;i++){
+            let rationList = await stdRationLibModel.find({ compilationId: data.compilationId }, ['ID', 'dispName']);
+            for (let i = 0; i < rationList.length; i++) {
                 let tem = {
-                    id:rationList[i].ID,
-                    name:rationList[i].dispName,
-                    isDefault:false
+                    id: rationList[i].ID,
+                    name: rationList[i].dispName,
+                    isDefault: false
                 };
-                if(i == 0) tem.isDefault = true;
+                if (i == 0) tem.isDefault = true;
                 data.ration_lib.push(tem);
             }
         }
@@ -97,6 +97,15 @@ class EngineeringLibModel extends BaseModel {
         return result;
     }
 
+    // 拷贝工程专业
+    async copyEngineer(sourceID) {
+        const engineering = await this.db.findOne({ _id: sourceID });
+        const newEngineering = engineering._doc;
+        delete newEngineering._id;
+        newEngineering.visible = false;
+        await this.db.create(newEngineering);
+    }
+
     /**
      * 新增标准库
      *
@@ -105,23 +114,23 @@ class EngineeringLibModel extends BaseModel {
      * @return {Promise}
      */
     async addLib(engineerId, data) {
-        if(data.glj_col){
-            data.glj_col =  JSON.parse(data.glj_col);
+        if (data.glj_col) {
+            data.glj_col = JSON.parse(data.glj_col);
         }
-        data.isInstall == 'true'?data.isInstall=true:data.isInstall=false;
+        data.isInstall == 'true' ? data.isInstall = true : data.isInstall = false;
         let result = false;
         data = this.filterLibData(data);
         try {
-            let engineeringLib = await this.findDataByCondition({_id:engineerId});
-            if(engineeringLib){
+            let engineeringLib = await this.findDataByCondition({ _id: engineerId });
+            if (engineeringLib) {
                 // 存在则直接更新
                 delete data.id;
                 delete data.section;
-                let condition = {_id: engineerId};
+                let condition = { _id: engineerId };
                 result = await this.db.update(condition, data);
                 result = result.ok === 1;
-            }else {
-                throw  new Error("找不到对应的工程专业");
+            } else {
+                throw new Error("找不到对应的工程专业");
             }
 
         } catch (error) {
@@ -146,16 +155,16 @@ class EngineeringLibModel extends BaseModel {
         data.engineering = parseInt(data.engineering);
         //需求修改,工程专业可以随便输入了
         //检测专业工程是否合法
-   /*     let match = false;
-        for(let index in EngineeringConst) {
-            if (EngineeringConst[index] === data.engineering) {
-                match = true;
-                break;
-            }
-        }
-        if (!match) {
-            throw '工程专业错误';
-        }*/
+        /*     let match = false;
+             for(let index in EngineeringConst) {
+                 if (EngineeringConst[index] === data.engineering) {
+                     match = true;
+                     break;
+                 }
+             }
+             if (!match) {
+                 throw '工程专业错误';
+             }*/
 
         // 判断标准清单
         data.bill_lib = this._validLib(data.bill_lib);
@@ -183,7 +192,7 @@ class EngineeringLibModel extends BaseModel {
 
         //判断基本信息
         data.info_lib = this._validLib(data.info_lib);
-        
+
         //判断累进区间库
         data.progressive_lib = this._validLib(data.progressive_lib);
 
@@ -208,24 +217,24 @@ class EngineeringLibModel extends BaseModel {
     _validLib(libData) {
         let result = [];
         // 判断标准库
-        if (libData === undefined || libData ===null ||libData === '') {
+        if (libData === undefined || libData === null || libData === '') {
             return result;//throw '标准库不能为空'; 按新需求,标准库等不做非空判断
         }
         libData = libData instanceof Array ? libData : [libData];
-        for(let tmp in libData) {
+        for (let tmp in libData) {
             result[tmp] = JSON.parse(libData[tmp]);
         }
         return result;
     }
 
     //设置默认定额库
-    setDefaultRation(data){
-        if(data.ration_lib && data.ration_lib.length>0){
-            if(data.ration_isDefault && data.ration_isDefault != ""){
-                for(let r of data.ration_lib){
-                    r.id.toString() == data.ration_isDefault?r.isDefault = true:r.isDefault=false;
+    setDefaultRation(data) {
+        if (data.ration_lib && data.ration_lib.length > 0) {
+            if (data.ration_isDefault && data.ration_isDefault != "") {
+                for (let r of data.ration_lib) {
+                    r.id.toString() == data.ration_isDefault ? r.isDefault = true : r.isDefault = false;
                 }
-            }else {
+            } else {
                 data.ration_lib[0].isDefault = true;
             }
         }
@@ -246,11 +255,11 @@ class EngineeringLibModel extends BaseModel {
 
         // 整理需要查找的数据
         let findIdList = [];
-        for(let engineering of valuationData.engineering_list) {
+        for (let engineering of valuationData.engineering_list) {
             findIdList.push(engineering.engineering_id);
         }
 
-        let condition = {_id: {$in: findIdList}};
+        let condition = { _id: { $in: findIdList } };
         let libData = await this.findDataByCondition(condition, null, false);
         if (libData === null) {
             return result;
@@ -258,7 +267,7 @@ class EngineeringLibModel extends BaseModel {
 
         // 整理数据
         let countData = {};
-        for(let tmp of libData) {
+        for (let tmp of libData) {
             countData[tmp._id] = {
                 bill_count: tmp.bill_lib.length,
                 ration_count: tmp.ration_lib.length,
@@ -270,7 +279,7 @@ class EngineeringLibModel extends BaseModel {
         }
 
 
-        for(let engineering of valuationData.engineering_list) {
+        for (let engineering of valuationData.engineering_list) {
             if (countData[engineering.engineering_id] !== undefined) {
                 result[engineering.engineering] = countData[engineering.engineering_id];
             }
@@ -281,7 +290,7 @@ class EngineeringLibModel extends BaseModel {
 
     async copyRationLibsToOthers(valuationID, engineeringID) {
         const compilationModel = new CompilationModel();
-        const compilation = await compilationModel.model.findOne({ $or: [{ 'bill_valuation.id': valuationID }, { 'ration_valuation.id': valuationID }]});
+        const compilation = await compilationModel.model.findOne({ $or: [{ 'bill_valuation.id': valuationID }, { 'ration_valuation.id': valuationID }] });
         if (!compilation) {
             return;
         }
@@ -294,7 +303,7 @@ class EngineeringLibModel extends BaseModel {
         if (!engineering) {
             return;
         }
-        await this.model.updateMany({ valuationID: { $in: valuationIDList } }, {$set: { ration_lib: engineering.ration_lib }});
+        await this.model.updateMany({ valuationID: { $in: valuationIDList } }, { $set: { ration_lib: engineering.ration_lib } });
     }
 
 }

+ 1 - 0
modules/users/routes/compilation_route.js

@@ -27,6 +27,7 @@ module.exports = function (app) {
     router.post('/save-valuation', compilationController.auth, compilationController.init, compilationController.saveValuation);
     router.post('/update-engineer', compilationController.auth, compilationController.init, compilationController.updateEngineer);
     router.post('/delete-engineer', compilationController.auth, compilationController.init, compilationController.deleteEngineer);
+    router.post('/copy-engineer', compilationController.auth, compilationController.init, compilationController.copyEngineer);
     router.post('/save-lib', compilationController.auth, compilationController.init, compilationController.saveEngineering);
     router.post('/valuation/:section/enable', compilationController.auth, compilationController.init, compilationController.enableSwitch);
     router.post('/valuation/:section/fileTypes', compilationController.auth, compilationController.init, compilationController.setFileTypes);

+ 6 - 5
package.json

@@ -29,13 +29,14 @@
     "bluebird": "^3.5.0",
     "jszip": "^3.1.3",
     "log4js": "~2.3.3",
-    "multiparty": "^4.1.3",
+    "lz-string": "^1.4.4",
     "moment-timezone": "^0.5.27",
+    "multiparty": "^4.1.3",
+    "node-schedule": "^1.3.0",
     "node-xlsx": "^0.11.2",
     "pdfkit": "^0.8.2",
-    "ueditor": "^1.2.3",
-    "node-schedule": "^1.3.0",
-    "lz-string": "^1.4.4"
+    "segmentit": "^2.0.3",
+    "ueditor": "^1.2.3"
   },
   "scripts": {
     "start": "C:\\Users\\mai\\AppData\\Roaming\\npm\\babel-node.cmd operation.js",
@@ -54,4 +55,4 @@
     "uat_hw_server": "SET NODE_ENV=uat_hw&& babel-node operation.js",
     "prod_hw_server": "SET NODE_ENV=prod_hw&& babel-node operation.js"
   }
-}
+}

+ 139 - 0
public/cut_word/segmentit.js

@@ -0,0 +1,139 @@
+const { Segment, Tokenizer, dicts, synonyms, stopwords, modules } = require('segmentit');
+
+const scWords = ['硅酸盐水泥', '综合工', '铜芯', '螺纹钢', 'mm2', 'mm'];
+
+/* const buffer = readFileSync(join(__dirname, '../dict/scWords.utf8'));
+const dicText = buffer.toString();
+export const scWords = dicText.split(/\r?\n/); */
+
+class CusTokenizer extends Tokenizer {
+  segment;
+
+  split(words) {
+    // 需要的话可以获取到 this.segment 里的各种信息
+    // const TABLE = this.segment.getDict('TABLE');
+    const ret = [];
+    // 这个拦截器放在第一位,优先级最高words 一般只有一个元素
+    for (const word of words) {
+      let source = word.w;
+      for (const scWord of scWords) {
+        if (source.includes(scWord)) {
+          // ret.push({ w: scWord, p: TABLE[scWord].p });
+          ret.push({ w: scWord, p: 1000 }); // 这个p 表示词性,有值时,后一个分词器就不会再对这个词进行处理了, 对于我们使用场景来说没什么用,给个默认值就好
+          const reg = new RegExp(`${scWord}`, 'g');
+          source = source.replace(reg, '');
+        }
+      }
+      if (source) ret.push({ w: source });
+    }
+
+    return ret;
+  }
+}
+
+const useDefault = (segmentInstance) => {
+  segmentInstance.use([CusTokenizer, ...modules]);
+  segmentInstance.loadDict(dicts); // dicText,
+  segmentInstance.loadSynonymDict(synonyms);
+  segmentInstance.loadStopwordDict(stopwords);
+  return segmentInstance;
+};
+
+const segmentit = useDefault(new Segment());
+
+const scCut = (text) => {
+  const options = { stripPunctuation: true, simple: true };
+  return segmentit.doSegment(text, options);
+};
+
+// 获取分词数组。
+const getCutWords = (aName, aSpecs) => {
+  const cutNames = scCut(aName);
+  const cutSpecs = scCut(aSpecs); // 规格字符混杂,分词效果极差,使用 cutHMM()效果略好。
+  const rst = [...cutNames, ...cutSpecs];
+  return rst;
+}
+
+// 返回匹配数。
+const getMatchCount = (aUserWords, aMatchStr) => {
+  let count = 0;
+  for (const word of aUserWords) {
+    if (aMatchStr.indexOf(word) > -1) count += 1;
+  }
+  return count;
+}
+
+// 分词算法相关:别名替换。返回替换后的新字符串。
+const alias = (aStr) => {
+  if (!aStr) {
+    return aStr;
+  }
+  // 替换前
+  const a1 = ['φ', '混凝土'];
+  // 替换后,标准。用户自定义词库以a2中的词为标准录入。
+  const a2 = ['Ф', '砼'];
+  for (const key in a1) {
+    const patt = new RegExp(a1[key], 'g');
+    aStr = aStr.replace(patt, a2[key]);
+  }
+  return aStr;
+}
+
+const handelThreeWord = (word) => {
+  function getArr(tem, list) {
+    const nameArray = scCut(tem);
+    // 说明是一个词
+    if (nameArray.length === 1) list.push(tem);
+  }
+
+  const arr = [];
+  getArr(word[0] + word[1], arr);
+  getArr(word[1] + word[2], arr);
+  if (arr.length > 0) return arr;
+  return [word];
+}
+
+// 自定义特殊处理
+const cusSegment = (nameArray, keyword) => {
+  const temArr = [];
+  for (let a of nameArray) {
+    // 混凝土 和 砼 统一认成砼
+    if (a === '混凝土') a = '砼';
+    if (a === '砼' || a === '砖') {
+      temArr.push(a);
+    } else if (a.length > 1) {
+      // 如果是三个字的词,优先识别成两个字
+      if (a.length === 3 && !scWords.includes(a)) {
+        const sArr = handelThreeWord(a);
+        temArr.push(...sArr);
+      } else {
+        temArr.push(a);
+      }
+    }
+  }
+  if (keyword.length === 1 && temArr.length === 0) temArr.push(keyword);
+  return temArr;
+}
+
+const getWordArray = (keyword) => {
+  let wordArray = [];
+  if (keyword.length < 3) {
+    // 小于3个字的直接按一个词处理
+    wordArray.push(keyword);
+  } else {
+    wordArray = scCut(keyword);
+  }
+
+  // 自定义分词特殊处理
+  wordArray = cusSegment(wordArray, keyword);
+  // console.log(`分词结果:${wordArray}`);
+  return wordArray;
+}
+
+module.exports = {
+  scCut,
+  getCutWords,
+  getMatchCount,
+  alias,
+  getWordArray,
+};

+ 17 - 10
public/web/lock_util.js

@@ -20,11 +20,11 @@ const lockUtil = (() => {
         const $btns = $range.find('.lock-btn-control');
         const toolList = [];
         for (const $btn of $btns) {
-            toolList.push({$ref: $($btn), type: 'button'});
+            toolList.push({ $ref: $($btn), type: 'button' });
         }
         const $texts = $range.find('.lock-text-control');
         for (const $text of $texts) {
-            toolList.push({$ref: $($text), type: 'text'});
+            toolList.push({ $ref: $($text), type: 'text' });
         }
         toolList.forEach(item => {
             switch (item.type) {
@@ -44,7 +44,7 @@ const lockUtil = (() => {
         spreads.forEach(spread => {
             spread.unbind(GC.Spread.Sheets.Events.ButtonClicked);
             const sheetCount = spread.getSheetCount();
-            for(let i = 0; i < sheetCount; i++){
+            for (let i = 0; i < sheetCount; i++) {
                 const sheet = spread.getSheet(i);
                 sheet.unbind(GC.Spread.Sheets.Events.ButtonClicked);
                 sheet.unbind(GC.Spread.Sheets.Events.EditStarting);
@@ -60,8 +60,8 @@ const lockUtil = (() => {
                 sheet.options.isProtected = true;
                 const rowCount = sheet.getRowCount();
                 const colCount = sheet.getColumnCount();
-                for(let row = 0; row < rowCount; row++){
-                    for(let col = 0; col < colCount; col++){
+                for (let row = 0; row < rowCount; row++) {
+                    for (let col = 0; col < colCount; col++) {
                         sheet.getCell(row, col).locked(true);
                     }
                 }
@@ -77,14 +77,20 @@ const lockUtil = (() => {
         const curURL = reg.test(originURL) ? originURL.replace(reg, `locked=${locked}`) : `${originURL}&locked=${locked}`;
         $url.prop('href', curURL);
     }
+
+    function displayLock($lock, locked) {
+        $lock.data('locked', locked);
+        const innerHtml = locked ? '<i class="fa fa-unlock-alt"></i>' : '<i class="fa fa-lock"></i>';
+        $lock.html(innerHtml);
+        const title = locked ? '解锁' : '锁定';
+        $lock.prop('title', title);
+    }
+
+
     // 库列表页面,锁定按钮点击操作
     function handleLockClick($lock) {
         const curLocked = !$lock.data().locked;
-        $lock.data('locked', curLocked);
-        const innerHtml = curLocked ? '<i class="fa fa-unlock-alt"></i>' : '<i class="fa fa-lock"></i>';
-        $lock.html(innerHtml);
-        const title = curLocked ? '解锁' : '锁定';
-        $lock.prop('title', title);
+        displayLock($lock, curLocked);
         const $url = $lock.parent().parent().children(':first-child').children(':first-child');
         lockURL(curLocked, $url);
         const $range = $lock.parent().parent();
@@ -103,6 +109,7 @@ const lockUtil = (() => {
         lockTools,
         lockSpreads,
         lockURL,
+        displayLock,
         handleLockClick,
         lockSpreadsAndTools
     }

+ 8 - 5
web/common/js/slideResize.js

@@ -14,7 +14,7 @@
  * 清除相关缓存数据: project_view.js--->$('#property_default').click(callback)
  * */
 
-const SlideResize = (function() {
+const SlideResize = (function () {
     // 水平拖动条的宽度
     const resizeWidth = 10;
     //函数防抖
@@ -27,11 +27,14 @@ const SlideResize = (function() {
     }
     //设置水平拖动条的宽度
     //@param {Object dom}resize滚动条
-    function setResizeWidth (resize) {
+    function setResizeWidth(resize, getOtherFunc) {
         //滚动条节点 及 同层非滚动条节点的索引
         let bros = resize.parent().children();
-        let index = bros.index(resize),
-            otherIndex = index ? 0 : 1;
+        let index = bros.index(resize);
+        let otherIndex = index ? 0 : 1;
+        if (getOtherFunc) {
+            otherIndex = getOtherFunc(index);
+        }
         const other = resize.parent().children(`:eq(${otherIndex})`);
         let resizeParentWidth = resize.parent().width();
         let resizeDecimalWidth = resizeWidth / resizeParentWidth,
@@ -81,7 +84,7 @@ const SlideResize = (function() {
                     rightPercentWidth = rightChange / eleObj.parent.width() * 100 + '%';
                 eleObj.left.css('width', leftPercentWidth);
                 eleObj.right.css('width', rightPercentWidth);
-                setResizeWidth(eleObj.resize);
+                setResizeWidth(eleObj.resize, eleObj.getOtherFunc);
                 mouseMoveCount += Math.abs(moveSize);
                 deBounce(function () {
                     if (callback) {

+ 338 - 291
web/maintain/bill_template_lib/js/bills_template_edit.js

@@ -4,236 +4,254 @@
 
 const locked = lockUtil.getLocked();
 let TEMPLATE_BILLS_SETTING = {
-    "emptyRows":1,
-    "headRows":1,
-    "headRowHeight":[35],
+    "emptyRows": 1,
+    "headRows": 1,
+    "headRowHeight": [35],
     "treeCol": 1,
-    "cols":[{
-        "width":80,
-        "readOnly":locked,
-        "head":{
-            "titleNames":["类别"],
-            "spanCols":[1],
-            "spanRows":[1],
-            "vAlign":[1],
-            "hAlign":[1],
-            "font":["Arial"]
+    "cols": [{
+        "width": 80,
+        "readOnly": locked,
+        "head": {
+            "titleNames": ["类别"],
+            "spanCols": [1],
+            "spanRows": [1],
+            "vAlign": [1],
+            "hAlign": [1],
+            "font": ["Arial"]
         },
-        "data":{
-            "field":"type",
-            "vAlign":0,
-            "hAlign":1,
-            "font":"Arail",
+        "data": {
+            "field": "type",
+            "vAlign": 0,
+            "hAlign": 1,
+            "font": "Arail",
         }
     }, {
-        "width":200,
-        "readOnly":locked,
-        "head":{
-            "titleNames":["编号"],
-            "spanCols":[1],
-            "spanRows":[1],
-            "vAlign":[1],
-            "hAlign":[1],
-            "font":["Arial"],
+        "width": 200,
+        "readOnly": locked,
+        "head": {
+            "titleNames": ["编号"],
+            "spanCols": [1],
+            "spanRows": [1],
+            "vAlign": [1],
+            "hAlign": [1],
+            "font": ["Arial"],
         },
-        "data":{
-            "field":"code",
-            "vAlign":0,
-            "hAlign":3,
-            "font":"Arail",
+        "data": {
+            "field": "code",
+            "vAlign": 0,
+            "hAlign": 3,
+            "font": "Arail",
             "formatter": '@'
         }
     }, {
-        "width":300,
-        "readOnly":locked,
-        "head":{
-            "titleNames":["名称"],
-            "spanCols":[1],
-            "spanRows":[1],
-            "vAlign":[1],
-            "hAlign":[1],
-            "font":["Arial"]
+        "width": 300,
+        "readOnly": locked,
+        "head": {
+            "titleNames": ["名称"],
+            "spanCols": [1],
+            "spanRows": [1],
+            "vAlign": [1],
+            "hAlign": [1],
+            "font": ["Arial"]
         },
-        "data":{
-            "field":"name",
-            "vAlign":0,
-            "hAlign":3,
-            "font":"Arail"
+        "data": {
+            "field": "name",
+            "vAlign": 0,
+            "hAlign": 3,
+            "font": "Arail"
         }
     }, {
-        "width":50,
-        "readOnly":locked,
-        "head":{
-            "titleNames":["单位"],
-            "spanCols":[1],
-            "spanRows":[1],
-            "vAlign":[1],
-            "hAlign":[1],
-            "font":["Arial"]
+        "width": 50,
+        "readOnly": locked,
+        "head": {
+            "titleNames": ["单位"],
+            "spanCols": [1],
+            "spanRows": [1],
+            "vAlign": [1],
+            "hAlign": [1],
+            "font": ["Arial"]
         },
-        "data":{
-            "field":"unit",
-            "vAlign":0,
-            "hAlign":1,
-            "font":"Arail"
+        "data": {
+            "field": "unit",
+            "vAlign": 0,
+            "hAlign": 1,
+            "font": "Arail"
         }
     }, {
-            "width":80,
-            "readOnly":locked,
-            "head":{
-                "titleNames":["工程量"],
-                "spanCols":[1],
-                "spanRows":[1],
-                "vAlign":[1],
-                "hAlign":[1],
-                "font":["Arial"]
-            },
-            "data":{
-                "field":"quantity",
-                "type":'Number',
-                "vAlign":0,
-                "hAlign":2,
-                "font":"Arail"
-            }
+        "width": 80,
+        "readOnly": locked,
+        "head": {
+            "titleNames": ["工程量"],
+            "spanCols": [1],
+            "spanRows": [1],
+            "vAlign": [1],
+            "hAlign": [1],
+            "font": ["Arial"]
+        },
+        "data": {
+            "field": "quantity",
+            "type": 'Number',
+            "vAlign": 0,
+            "hAlign": 2,
+            "font": "Arail"
+        }
     }, {
-        "width":200,
-        "readOnly":locked,
-        "head":{
-            "titleNames":["清单固定类别"],
-            "spanCols":[1],
-            "spanRows":[1],
-            "vAlign":[1],
-            "hAlign":[1],
-            "font":["Arial"]
+        "width": 200,
+        "readOnly": locked,
+        "head": {
+            "titleNames": ["清单固定类别"],
+            "spanCols": [1],
+            "spanRows": [1],
+            "vAlign": [1],
+            "hAlign": [1],
+            "font": ["Arial"]
         },
-        "data":{
-            "field":"flagsIndex.fixed.flag",
-            "vAlign":0,
-            "hAlign":3,
-            "font":"Arail",
+        "data": {
+            "field": "flagsIndex.fixed.flag",
+            "vAlign": 0,
+            "hAlign": 3,
+            "font": "Arail",
         }
     },
     {
-      width: 200,
-      readOnly: locked,
-      head: {
-        titleNames: ["计价规则"],
-        spanCols: [1],
-        spanRows: [1],
-        vAlign: [1],
-        hAlign: [1],
-        font: ["Arial"],
-      },
-      data: {
-        field: "itemCharacterText",
-        vAlign: 0,
-        hAlign: 3,
-        font: "Arail",
-      },
+        width: 40,
+        readOnly: locked,
+        head: {
+            titleNames: ["不可\n删除"],
+            spanCols: [1],
+            spanRows: [1],
+            vAlign: [1],
+            hAlign: [1],
+            font: ["Arial"],
+        },
+        data: {
+            field: "cantDelete",
+            vAlign: 0,
+            hAlign: 1,
+            font: "Arail",
+        },
+    },
+    {
+        width: 200,
+        readOnly: locked,
+        head: {
+            titleNames: ["计价规则"],
+            spanCols: [1],
+            spanRows: [1],
+            vAlign: [1],
+            hAlign: [1],
+            font: ["Arial"],
+        },
+        data: {
+            field: "itemCharacterText",
+            vAlign: 0,
+            hAlign: 3,
+            font: "Arail",
+        },
     },
     {
-      width: 200,
-      readOnly: locked,
-      head: {
-        titleNames: ["计价内容"],
-        spanCols: [1],
-        spanRows: [1],
-        vAlign: [1],
-        hAlign: [1],
-        font: ["Arial"],
-      },
-      data: {
-        field: "jobContentText",
-        vAlign: 0,
-        hAlign: 3,
-        font: "Arail",
-      },
+        width: 200,
+        readOnly: locked,
+        head: {
+            titleNames: ["计价内容"],
+            spanCols: [1],
+            spanRows: [1],
+            vAlign: [1],
+            hAlign: [1],
+            font: ["Arial"],
+        },
+        data: {
+            field: "jobContentText",
+            vAlign: 0,
+            hAlign: 3,
+            font: "Arail",
+        },
     },
     {
-        "width":250,
-        "readOnly":locked,
-        "head":{
-            "titleNames":["计算基数"],
-            "spanCols":[1],
-            "spanRows":[1],
-            "vAlign":[1],
-            "hAlign":[1],
-            "font":["Arial"]
+        "width": 250,
+        "readOnly": locked,
+        "head": {
+            "titleNames": ["计算基数"],
+            "spanCols": [1],
+            "spanRows": [1],
+            "vAlign": [1],
+            "hAlign": [1],
+            "font": ["Arial"]
         },
-        "data":{
-            "field":"calcBase",
-            "vAlign":0,
-            "hAlign":3,
-            "font":"Arail",
+        "data": {
+            "field": "calcBase",
+            "vAlign": 0,
+            "hAlign": 3,
+            "font": "Arail",
         }
     }, {
-        "width":50,
-        "readOnly":locked,
-        "head":{
-            "titleNames":["费率ID"],
-            "spanCols":[1],
-            "spanRows":[1],
-            "vAlign":[1],
-            "hAlign":[1],
-            "font":["Arial"]
+        "width": 50,
+        "readOnly": locked,
+        "head": {
+            "titleNames": ["费率ID"],
+            "spanCols": [1],
+            "spanRows": [1],
+            "vAlign": [1],
+            "hAlign": [1],
+            "font": ["Arial"]
         },
-        "data":{
-            "field":"feeRateID",
-            "type":'Number',
-            "vAlign":0,
-            "hAlign":1,
-            "font":"Arail"
+        "data": {
+            "field": "feeRateID",
+            "type": 'Number',
+            "vAlign": 0,
+            "hAlign": 1,
+            "font": "Arail"
         }
     }, {
-        "width":50,
-        "readOnly":true,
-        "head":{
-            "titleNames":["ID"],
-            "spanCols":[1],
-            "spanRows":[1],
-            "vAlign":[1],
-            "hAlign":[1],
-            "font":["Arial"]
+        "width": 50,
+        "readOnly": true,
+        "head": {
+            "titleNames": ["ID"],
+            "spanCols": [1],
+            "spanRows": [1],
+            "vAlign": [1],
+            "hAlign": [1],
+            "font": ["Arial"]
         },
-        "data":{
-            "field":"ID",
-            "vAlign":0,
-            "hAlign":1,
-            "font":"Arail"
+        "data": {
+            "field": "ID",
+            "vAlign": 0,
+            "hAlign": 1,
+            "font": "Arail"
         }
     }, {
-        "width":50,
-        "readOnly":true,
-        "head":{
-            "titleNames":["ParentID"],
-            "spanCols":[1],
-            "spanRows":[1],
-            "vAlign":[1],
-            "hAlign":[1],
-            "font":["Arial"]
+        "width": 50,
+        "readOnly": true,
+        "head": {
+            "titleNames": ["ParentID"],
+            "spanCols": [1],
+            "spanRows": [1],
+            "vAlign": [1],
+            "hAlign": [1],
+            "font": ["Arial"]
         },
-        "data":{
-            "field":"ParentID",
-            "vAlign":0,
-            "hAlign":1,
-            "font":"Arail"
+        "data": {
+            "field": "ParentID",
+            "vAlign": 0,
+            "hAlign": 1,
+            "font": "Arail"
         }
     }, {
-        "width":50,
-        "readOnly":true,
-        "head":{
-            "titleNames":["NextSiblingID"],
-            "spanCols":[1],
-            "spanRows":[1],
-            "vAlign":[1],
-            "hAlign":[1],
-            "font":["Arial"]
+        "width": 50,
+        "readOnly": true,
+        "head": {
+            "titleNames": ["NextSiblingID"],
+            "spanCols": [1],
+            "spanRows": [1],
+            "vAlign": [1],
+            "hAlign": [1],
+            "font": ["Arial"]
         },
-        "data":{
-            "field":"NextSiblingID",
-            "vAlign":0,
-            "hAlign":1,
-            "font":"Arail"
+        "data": {
+            "field": "NextSiblingID",
+            "vAlign": 0,
+            "hAlign": 1,
+            "font": "Arail"
         }
     }]
 };
@@ -256,13 +274,13 @@ $(document).ready(function () {
         setButtonValid(tree.selected && tree.selected.canUpMove(), $('#upMove'));
         setButtonValid(tree.selected && tree.selected.canDownMove(), $('#downMove'));
         setButtonValid(tree.selected ? true : false, $('#delete'));
-        
+
     };
     let RefreshBillsData = function (datas) {
         datas.forEach(function (data) {
             let node = tree.findNode(data.data.ID);
             if (node) {
-                setFlagsIndex(data.data,node.data.flagsIndex);
+                setFlagsIndex(data.data, node.data.flagsIndex);
                 $.extend(true, node.data, data.data);
             }
         });
@@ -270,7 +288,7 @@ $(document).ready(function () {
     let getNameValueComboCellType = function (datas) {
         let comboItems = [];
         for (let data of datas) {
-            comboItems.push({text: data.name, value: data.value});
+            comboItems.push({ text: data.name, value: data.value });
         }
         let combo = new GC.Spread.Sheets.CellTypes.ComboBox();
         combo.editorValueType(GC.Spread.Sheets.CellTypes.EditorValueType.value)
@@ -284,7 +302,7 @@ $(document).ready(function () {
     let getNameToValueMap = function (listString) {
         let map = {};
         let datas = JSON.parse(listString);
-        for(let data of datas){
+        for (let data of datas) {
             map[data.name] = data.value;
         }
         return map;
@@ -312,32 +330,32 @@ $(document).ready(function () {
             }
         }
     };
-    let getRealValue = function (value,map) {//中文到实际值的转换
-        if(value) value = value.replace(/[\s\r\n]/g, "");//去掉空格,回车等无用字符
-        if(map[value]!==undefined && map[value]!==null) value = map[value];
+    let getRealValue = function (value, map) {//中文到实际值的转换
+        if (value) value = value.replace(/[\s\r\n]/g, "");//去掉空格,回车等无用字符
+        if (map[value] !== undefined && map[value] !== null) value = map[value];
         return value;
     };
-    let setUpdateData = function (node,data,col,value,setting) {
+    let setUpdateData = function (node, data, col, value, setting) {
         let fieldName = setting.cols[col].data.field;
         let valueType = setting.cols[col].data.type;
-        if(fieldName == 'type'){
-            value = getRealValue(value,typeMap);
+        if (fieldName == 'type') {
+            value = getRealValue(value, typeMap);
         }
-        if(fieldName == 'flagsIndex.fixed.flag'){
-            value = getRealValue(value,fixedFlagMap);
+        if (fieldName == 'flagsIndex.fixed.flag') {
+            value = getRealValue(value, fixedFlagMap);
         }
         if (/flagsIndex/.test(fieldName)) {
             data.data.flags = [];
             let flagField = fieldName.split('.');
-            data.data.flags.push({fieldName: flagField[1],flag: value});
+            data.data.flags.push({ fieldName: flagField[1], flag: value });
         } else {
-            if(value && valueType == 'Number') value = parseInt(value);
+            if (value && valueType == 'Number') value = parseInt(value);
             setFee(data.data, fieldName, value);
         }
     };
-    let setFlagsIndex = function (data,flagsIndex) {
+    let setFlagsIndex = function (data, flagsIndex) {
         if (data.flags) {
-            flagsIndex?data.flagsIndex = flagsIndex:data.flagsIndex={};
+            flagsIndex ? data.flagsIndex = flagsIndex : data.flagsIndex = {};
             for (let flag of data.flags) {
                 data.flagsIndex[flag.fieldName] = flag;
             }
@@ -349,12 +367,12 @@ $(document).ready(function () {
     let templateData = JSON.parse(billsTemplateData);
     for (let data of templateData) {
         setFlagsIndex(data);
-       /* if (data.flags) {
-            data.flagsIndex = {};
-            for (let flag of data.flags) {
-                data.flagsIndex[flag.fieldName] = flag;
-            }
-        }*/
+        /* if (data.flags) {
+             data.flagsIndex = {};
+             for (let flag of data.flags) {
+                 data.flagsIndex[flag.fieldName] = flag;
+             }
+         }*/
     }
 
     for (col of TEMPLATE_BILLS_SETTING.cols) {
@@ -362,12 +380,14 @@ $(document).ready(function () {
             col.data.cellType = getTypeFlagCellType();
         } else if (col.data.field === 'flagsIndex.fixed.flag' && TEMPLATE_BILLS_SETTING.cols.indexOf(col) !== TEMPLATE_BILLS_SETTING.treeCol) {
             col.data.cellType = getFixedFlagCellType();
+        } else if (col.data.field === 'cantDelete' && TEMPLATE_BILLS_SETTING.cols.indexOf(col) !== TEMPLATE_BILLS_SETTING.treeCol) {
+            col.data.cellType = new GC.Spread.Sheets.CellTypes.CheckBox();
         }
     }
 
-    let tree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true});
+    let tree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true });
     let billsSpread = TREE_SHEET_HELPER.createNewSpread($('#billsSpread')[0]);
-    sheetCommonObj.bindEscKey(billsSpread, [{sheet: billsSpread.getSheet(0), editStarting: null, editEnded: billsOnEditEnded}]);
+    sheetCommonObj.bindEscKey(billsSpread, [{ sheet: billsSpread.getSheet(0), editStarting: null, editEnded: billsOnEditEnded }]);
     let controller = TREE_SHEET_CONTROLLER.createNew(tree, billsSpread.getActiveSheet(), TEMPLATE_BILLS_SETTING);
     let fixedFlagMap = getNameToValueMap(billsFixedFlagList);
     let typeMap = getNameToValueMap(billsTypeFlagList);
@@ -375,19 +395,45 @@ $(document).ready(function () {
     //billsSpread.getSheet(0).setFormatter(-1, 1, '@');
     controller.bind('refreshBaseActn', RefreshBaseActn);
 
+    function onButtonClicked(sender, info) {
+        if (info.sheet.isEditing()) {
+            info.sheet.endEdit(true);
+        }
+        const fieldName = controller.setting.cols[info.col].data.field;
+        var node = controller.tree.items[info.row];
+        if (node && fieldName === 'cantDelete') {
+            const isChecked = Boolean(info.sheet.getValue(info.row, info.col));
+            const data = { type: "update", data: { ID: node.getID() } };
+            setFee(data.data, fieldName, isChecked);
+            console.log(isChecked);
+            const updateData = [data];
+            CommonAjax.post(
+                updateUrl,
+                updateData,
+                function (data) {
+                    setFee(node.data, fieldName, isChecked);
+                    controller.refreshTreeNode([node], false);
+                },
+                function () {
+                    controller.refreshTreeNode([node], false);
+                }
+            );
+        }
+    }
+
     function billsOnEditEnded(sender, info) {
         var node = controller.tree.items[info.row];
         var fieldName = controller.setting.cols[info.col].data.field;
         var valueType = controller.setting.cols[info.col].data.type;
         let value = info.editingText;
-        if(node){
-            var data = {type: 'update', data: {ID: node.getID()}};
+        if (node) {
+            var data = { type: 'update', data: { ID: node.getID() } };
             if (/flagsIndex/.test(fieldName)) {
                 data.data.flags = [];
                 let flagField = fieldName.split('.');
-                data.data.flags.push({fieldName: flagField[1], flag: info.editingText});
+                data.data.flags.push({ fieldName: flagField[1], flag: info.editingText });
             } else {
-                if(value && valueType == 'Number') value = parseInt(info.editingText);
+                if (value && valueType == 'Number') value = parseInt(info.editingText);
                 setFee(data.data, fieldName, value);
             }
             var updateData = [data];
@@ -397,23 +443,24 @@ $(document).ready(function () {
             }, function () {
                 controller.refreshTreeNode([node], false);
             });
-        }else {
-            info.sheet.getCell(info.row,info.col).value("");
+        } else {
+            info.sheet.getCell(info.row, info.col).value("");
         }
     }
     billsSpread.bind(GC.Spread.Sheets.Events.EditEnded, billsOnEditEnded);
+    billsSpread.bind(GC.Spread.Sheets.Events.ButtonClicked, onButtonClicked);
     billsSpread.bind(GC.Spread.Sheets.Events.ClipboardPasted, function (e, info) {
         console.log("ClipboardPasted");
-        var node, iRow, iCol, curRow, curCol, datas = [], data, fieldName,valueType,value, updateData;
-        for (iRow = 0; iRow < info.cellRange.rowCount; iRow ++) {
+        var node, iRow, iCol, curRow, curCol, datas = [], data, fieldName, valueType, value, updateData;
+        for (iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
             curRow = info.cellRange.row + iRow;
             node = controller.tree.items[curRow];
             if (node) {
-                data = {type: 'update', data: {ID: node.getID()}};
+                data = { type: 'update', data: { ID: node.getID() } };
                 for (iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     curCol = info.cellRange.col + iCol;
                     value = info.sheet.getText(curRow, curCol);
-                    setUpdateData(node,data,curCol,value,controller.setting);
+                    setUpdateData(node, data, curCol, value, controller.setting);
                 }
                 datas.push(data);
             }
@@ -425,17 +472,17 @@ $(document).ready(function () {
             controller.showTreeData();
         });
     });
-    billsSpread.bind(GC.Spread.Sheets.Events.RangeChanged, function (e,info) {
+    billsSpread.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
         let datas = [];
-        let changGroup = _.groupBy(info.changedCells,'row');
-        for(let row in changGroup){
+        let changGroup = _.groupBy(info.changedCells, 'row');
+        for (let row in changGroup) {
             let node = controller.tree.items[row];
             if (node) {
-                let data = {type: 'update', data: {ID: node.getID()}};
+                let data = { type: 'update', data: { ID: node.getID() } };
                 for (let cell of changGroup[row]) {
                     let value = info.sheet.getText(cell.row, cell.col);
-                    if(value=="") value = null;
-                    setUpdateData(node,data,cell.col,value,controller.setting);
+                    if (value == "") value = null;
+                    setUpdateData(node, data, cell.col, value, controller.setting);
                 }
                 datas.push(data);
             }
@@ -451,7 +498,7 @@ $(document).ready(function () {
     tree.loadDatas(templateData);
     controller.showTreeData();
     let sel = billsSpread.getActiveSheet().getSelections()[0];
-    controller.setTreeSelected(tree.items[sel.row == -1?0:sel.row]);//初始化选中项
+    controller.setTreeSelected(tree.items[sel.row == -1 ? 0 : sel.row]);//初始化选中项
     RefreshBaseActn(tree);
     lockUtil.lockSpreadsAndTools([billsSpread], $(document.body), locked);
     $('#insert').click(function () {
@@ -478,20 +525,20 @@ $(document).ready(function () {
     $('#m_insert_confirm').click(function () {
         let me = this;
         let insertCount = $("#insertCount").val();
-        if(isNaN(insertCount)||insertCount<1){
+        if (isNaN(insertCount) || insertCount < 1) {
             $("#insertError").show();
             return;
         }
         $(me).addClass('disabled');
         let selected = controller.tree.selected, updateData;
         if (selected) {
-            updateData = controller.tree.getInsertDatas(insertCount,selected.getParentID(), selected.getNextSiblingID());
+            updateData = controller.tree.getInsertDatas(insertCount, selected.getParentID(), selected.getNextSiblingID());
         } else {
             updateData = controller.tree.getInsertDatas(insertCount);
         }
         if (updateData.length > 0) {
             CommonAjax.post(updateUrl, updateData, function (data) {
-                data = _.filter(data,{'type':'new'});
+                data = _.filter(data, { 'type': 'new' });
                 console.log(data);
                 controller.m_insert(data);
                 controller.showTreeData();
@@ -504,22 +551,22 @@ $(document).ready(function () {
             $(me).removeClass('disabled');
         }
 
-      /*  var selected = controller.tree.selected, updateData;
-        if (selected) {
-            updateData = controller.tree.getInsertData(selected.getParentID(), selected.getNextSiblingID());
-        } else {
-            updateData = controller.tree.getInsertData();
-        }
-        if (updateData.length > 0) {
-            CommonAjax.post(updateUrl, updateData, function (data) {
-                controller.insert();
-                controller.showTreeData();
-                $(me).removeClass('disabled');
-            });
-        } else {
-            alert('新增节点失败, 请重试.');
-            $(me).removeClass('disabled');
-        }*/
+        /*  var selected = controller.tree.selected, updateData;
+          if (selected) {
+              updateData = controller.tree.getInsertData(selected.getParentID(), selected.getNextSiblingID());
+          } else {
+              updateData = controller.tree.getInsertData();
+          }
+          if (updateData.length > 0) {
+              CommonAjax.post(updateUrl, updateData, function (data) {
+                  controller.insert();
+                  controller.showTreeData();
+                  $(me).removeClass('disabled');
+              });
+          } else {
+              alert('新增节点失败, 请重试.');
+              $(me).removeClass('disabled');
+          }*/
     });
 
 
@@ -527,9 +574,9 @@ $(document).ready(function () {
     $('#delete').click(function () {
         let me = this;
         $(me).addClass('disabled');
-        let [deleteMap,deleteNodes] = getNodesAndMapFromSheet(controller);
+        let [deleteMap, deleteNodes] = getNodesAndMapFromSheet(controller);
         if (deleteNodes.length > 0) {
-            let updateData = controller.tree.getDeleteDatas(deleteMap,deleteNodes);
+            let updateData = controller.tree.getDeleteDatas(deleteMap, deleteNodes);
             CommonAjax.post(updateUrl, updateData, function (data) {
                 controller.m_delete(deleteNodes);
                 controller.showTreeData();
@@ -540,20 +587,20 @@ $(document).ready(function () {
     $('#upLevel').click(function () {
         let me = this;
         $(me).addClass('disabled');
-        let [dMap,dNodes] = getNodesAndMapFromSheet(controller);
+        let [dMap, dNodes] = getNodesAndMapFromSheet(controller);
         let newNodes = [dNodes[0]];
-        if(dNodes.length > 1){//如果是多选,则去掉与第一个节点不同级的节点
-            for(let i = 1;i<dNodes.length;i++){
-                if(dNodes[i].parent == dNodes[0].parent) newNodes.push(dNodes[i])
+        if (dNodes.length > 1) {//如果是多选,则去掉与第一个节点不同级的节点
+            for (let i = 1; i < dNodes.length; i++) {
+                if (dNodes[i].parent == dNodes[0].parent) newNodes.push(dNodes[i])
             }
         }
-        let updateDatas =  controller.tree.getUpLevelDatas(newNodes);
-        if(updateDatas.length > 0){
+        let updateDatas = controller.tree.getUpLevelDatas(newNodes);
+        if (updateDatas.length > 0) {
             CommonAjax.post(updateUrl, updateDatas, function (data) {
                 controller.m_upLevel(newNodes);
-                for(let u of updateDatas){
+                for (let u of updateDatas) {
                     let node = controller.tree.findNode(u.data.ID);
-                    refreshNodeData(node,u.data);
+                    refreshNodeData(node, u.data);
                 }
                 controller.showTreeData();
                 $(me).removeClass('disabled');
@@ -563,20 +610,20 @@ $(document).ready(function () {
     $('#downLevel').click(function () {
         let me = this;
         $(me).addClass('disabled');
-        let [dMap,dNodes] = getNodesAndMapFromSheet(controller);
+        let [dMap, dNodes] = getNodesAndMapFromSheet(controller);
         let newNodes = [dNodes[0]];
-        if(dNodes.length > 1){//如果是多选,则去掉与第一个节点不同级的节点
-            for(let i = 1;i<dNodes.length;i++){
-                if(dNodes[i].parent == dNodes[0].parent) newNodes.push(dNodes[i])
+        if (dNodes.length > 1) {//如果是多选,则去掉与第一个节点不同级的节点
+            for (let i = 1; i < dNodes.length; i++) {
+                if (dNodes[i].parent == dNodes[0].parent) newNodes.push(dNodes[i])
             }
         }
         let updateDatas = controller.tree.getDownLevelDatas(newNodes);
-        if(updateDatas.length > 0){
+        if (updateDatas.length > 0) {
             CommonAjax.post(updateUrl, updateDatas, function (data) {
                 controller.m_downLevel(newNodes);
-                for(let u of updateDatas){
+                for (let u of updateDatas) {
                     let node = controller.tree.findNode(u.data.ID);
-                    refreshNodeData(node,u.data);
+                    refreshNodeData(node, u.data);
                 }
                 controller.showTreeData();
                 $(me).removeClass('disabled');
@@ -610,39 +657,39 @@ $(document).ready(function () {
         }
     });
     function getNodesAndMapFromSheet(controller) {//表格中选中的节点整理,只留下父节点
-        let selection = controller.sheet.getSelections()[0],map={},nodes=[];
-        for(let i=0;i < selection.rowCount;i++){
-            let tem_node = controller.tree.items[selection.row+i];
-            if(i == 0){//第一个直接添加;
+        let selection = controller.sheet.getSelections()[0], map = {}, nodes = [];
+        for (let i = 0; i < selection.rowCount; i++) {
+            let tem_node = controller.tree.items[selection.row + i];
+            if (i == 0) {//第一个直接添加;
                 map[tem_node.getID()] = tem_node;
                 nodes.push(tem_node);
-            }else {
-                setNodeToMapAndArray(tem_node,map,nodes);
+            } else {
+                setNodeToMapAndArray(tem_node, map, nodes);
             }
         }
-        return [map,nodes];
+        return [map, nodes];
     }
-    function setNodeToMapAndArray(node,map,array) {
+    function setNodeToMapAndArray(node, map, array) {
         let nodeID = node.getID();
-        if(map[nodeID]==undefined||map[nodeID]==null){
-            newMap(node,node.parent,map,array)
+        if (map[nodeID] == undefined || map[nodeID] == null) {
+            newMap(node, node.parent, map, array)
         }
-        function newMap(node,parent,map,array) {
-            let nodeID =node.getID();
-            if(parent==null){//说明已经是最顶层了
-                map[nodeID]=node;
+        function newMap(node, parent, map, array) {
+            let nodeID = node.getID();
+            if (parent == null) {//说明已经是最顶层了
+                map[nodeID] = node;
                 array.push(node);
-            }else {
+            } else {
                 let parentID = parent.getID();
-                if(map[parentID]==undefined||map[parentID]==null){
-                    newMap(node,parent.parent,map,array);
+                if (map[parentID] == undefined || map[parentID] == null) {
+                    newMap(node, parent.parent, map, array);
                 }
             }
         }
     }
-    function refreshNodeData(node,data) {
-        for(let key in data){
-            if(key == 'ID') continue;
+    function refreshNodeData(node, data) {
+        for (let key in data) {
+            if (key == 'ID') continue;
             node.data[key] = data[key];
         }
     }

+ 43 - 2
web/maintain/bills_lib/html/qingdan.html

@@ -95,6 +95,10 @@
                       <a trigger="billsRecharge" class="pull-right mr-3 uploadImgTrigger lock-btn-control" href="javacript:void(0);" data-toggle="modal" data-target="#uploadimg" ><i class="fa fa-image"></i>上传图片</a>
                       <label for="exampleTextarea">补注:</label>
                       <textarea class="form-control" id="exampleTextarea" rows="8"></textarea>
+                      <div>
+                        <label for="erratumRecordText">勘误记录:</label>
+                        <textarea class="form-control lock-btn-control" id="erratumRecordText" rows="8" style="height: calc(100% - 38px);"></textarea>
+                    </div>
                   </div>
                <!-- <div class="row">
                   <div class="col ovf-hidden" style="width:50%; height: 100%; float: left;">
@@ -372,7 +376,14 @@
         theme:"material",
         readOnly: locked
     });
-    codeEditor.setSize('auto','350px');
+    codeEditor.setSize('auto', '350px');
+    const erratumRecordEditor = CodeMirror.fromTextArea(document.getElementById("erratumRecordText"), {
+        mode: "text/html",
+        lineNumbers: true,
+        theme:"material",
+        readOnly: locked
+    });
+    erratumRecordEditor.setSize('auto','350px');
     autoFlashHeight();
     let BillsFixedFlagList = JSON.parse('<%- BillsFixedFlagList %>');
     let userAccount = '<%= userAccount %>';
@@ -506,6 +517,7 @@
     function showBillsSheet(datas, jobsSheet, designsSheet, itemsSheet, setting) {
         billsSpread = new GC.Spread.Sheets.Workbook($('#spreadBills')[0], {sheetCount: 1});
         let billsSheet = billsSpread.getSheet(0);
+        billsSheet.frozenColumnCount(3);
         sheetCommonObj.bindEscKey(billsSpread, [{sheet: billsSpread.getSheet(0), editStarting: dbController.onEditStart, editEnded: dbController.onEditEnded}]);
         billsSpread.focus(true);
         setSheet.initSheet(billsSpread, setting, true);
@@ -530,6 +542,8 @@
         dbController.controller = controller;
         sheetBillsDatas = tools.getsheetDatas(controller.sheet, 'bills', controller);
         setSheet.formatter(billsSpread.getActiveSheet());
+        const codeCol = setting.cols.findIndex(item => item.data.field ==='code');
+        billsSheet.setFormatter(-1, codeCol, '@', GC.Spread.Sheets.SheetArea.viewport);
         //setTagId
         setTagID(controller, setting);
         if (!controller.tree.selected && controller.tree.findNode(controller.sheet.getTag(0, 0, GC.Spread.Sheets.SheetArea.viewport))) {
@@ -543,6 +557,8 @@
         bindBillsRangeChanged(controller, billsSpread.getActiveSheet(), setting);
         //补注内容改变
         rechargeChange(controller);//
+        // 勘误错误改变
+        erratumRecordChange(controller)
         //焦点控制
         switchFcs(controller, controller.sheet, billsSpread, jobsSheet, designsSheet, itemsSheet);
         //jobs
@@ -772,6 +788,26 @@
         });*/
     }
 
+    // 勘误记录
+    function erratumRecordChange(controller){
+        erratumRecordEditor.on('change', async function () {
+            console.log('1111');
+            const node = controller.tree.selected;
+            const erratumRecord = erratumRecordEditor.getValue();
+            if (!node || erratumRecord === node.data.erratumRecord) {
+                return;
+            }
+            try {
+                const updateId = node.getID();
+                await ajaxPost('/stdBillsEditor/updateBills', { billsLibId, updateId, field: 'erratumRecord', data: erratumRecord });
+                node.data.erratumRecord = erratumRecord;
+            } catch(error) {
+                alert(error.message);
+                erratumRecordEditor.setValue(node.data.erratumRecord || '');
+            }
+        });
+    }
+
     function jobOperation(controller, jobsSheet, callback){
         mainAjax.getMaxNumber(billsLibId, 'jobs', function(result){
             if(result.length === 0){
@@ -861,6 +897,8 @@
                 codeEditor.setValue('');
                 //$('#exampleTextarea').val('');
             }
+            const erratumRecord = controller.tree.selected.data.erratumRecord || '';
+            erratumRecordEditor.setValue(erratumRecord);
         }
     }
 
@@ -873,6 +911,8 @@
                 //rechargeArea
                 const recharge = controller.tree.selected.data.recharge || '';
                 codeEditor.setValue(recharge);
+                const erratumRecord = controller.tree.selected.data.erratumRecord || '';
+                erratumRecordEditor.setValue(erratumRecord);
                 //$('#exampleTextarea').val(controller.tree.selected.data.recharge);
                 if(field === 'jobs'){
                     tools.clearData(sheet);
@@ -952,7 +992,8 @@
                     sheet.setValue(i, 3, sheetBillsDatas.datasIdx['rowIdx'+ i].unit);
                     sheet.setValue(i, 4, sheetBillsDatas.datasIdx['rowIdx'+ i].ruleText);
                     sheet.setValue(i, 5, sheetBillsDatas.datasIdx['rowIdx'+ i].engineerContent);
-                    sheet.setValue(i, 6, sheetBillsDatas.datasIdx['rowIdx'+ i].engineering);
+                    sheet.setValue(i, 6, sheetBillsDatas.datasIdx['rowIdx'+ i].unitPrice);
+                    //sheet.setValue(i, 7, sheetBillsDatas.datasIdx['rowIdx'+ i].engineering);
                     sheet.setValue(i, 7, sheetBillsDatas.datasIdx['rowIdx'+ i].fixedFlag);
                 }
                 else {

+ 18 - 1
web/maintain/bills_lib/scripts/bills_lib_setting.js

@@ -107,6 +107,23 @@ var billsLibSetting = {
         },
         {
             head: {
+                titleNames: ['单价'],
+                spanCols: [1],
+                spanRows: [2],
+                vAlign: [1, 1],
+                hAlign: [1, 1],
+                font: 'Arial'
+            },
+            data: {
+                field: 'unitPrice',
+                vAlign: 1,
+                hAlign: 2,
+                font: 'Arial'
+            },
+            width: 80
+        },
+        /* {
+            head: {
                 titleNames: ['工程专业'],
                 spanCols: [1],
                 spanRows: [2],
@@ -121,7 +138,7 @@ var billsLibSetting = {
                 font: 'Arial'
             },
             width: 80
-        },
+        }, */
         {
             head: {
                 titleNames: ['固定ID'],

+ 14 - 3
web/maintain/bills_lib/scripts/db_controller.js

@@ -373,12 +373,18 @@ var dbController = {
         if (node) {
             updateId = node.getID();
             field = billsLibSetting.cols[args.col].data.field;
-            if (field === 'engineering') {
+            /* if (field === 'engineering') {
                 if (isNaN(args.editingText) || args.editingText % 1 !== 0) {
                     args.sheet.setValue(args.row, args.col, dbController.currentEditData ? dbController.currentEditData : '');
                     alert('工程专业只能输入整数!');
                     return;
                 }
+            } else  */if (field === 'unitPrice') {
+                if (isNaN(args.editingText)) {
+                    args.sheet.setValue(args.row, args.col, dbController.currentEditData ? dbController.currentEditData : '');
+                    alert('单价只能输入数值!');
+                    return;
+                }
             }
             node.data[field] = args.editingText;
             sheetBillsDatas.datasIdx['rowIdx' + args.row][field] = args.editingText;
@@ -802,6 +808,10 @@ var tools = {
                         if (!isNaN(value) && value % 1 === 0) {
                             validData[setting.cols[j].data.field] = value;
                         }
+                    } else if (setting.cols[j].data.field === 'unitPrice') {
+                        if (!isNaN(value)) {
+                            validData[setting.cols[j].data.field] = value;
+                        }
                     } else if (setting.cols[j].data.field === 'fixedFlag') {
                         let findData = BillsFixedFlagList.find((x) => x.name === value);
                         if (findData) {
@@ -1133,9 +1143,10 @@ var tools = {
                 let kind = sheet.getValue(i, 0), code = sheet.getValue(i, 1), name = sheet.getValue(i, 2),
                     unit = sheet.getValue(i, 3), ruleText = sheet.getValue(i, 4),
                     engineerContent = sheet.getValue(i, 5),
-                    engineering = sheet.getValue(i, 6),
+                    unitPrice = sheet.getValue(i, 6),
+                    // engineering = sheet.getValue(i, 7),
                     fixedFlag = sheet.getValue(i, 7);
-                let data = { kind: kind, code: code, name: name, unit: unit, ruleText: ruleText, engineerContent, engineering: engineering, fixedFlag: fixedFlag, rowIdx: i };
+                let data = { kind: kind, code: code, name: name, unit: unit, ruleText: ruleText, engineerContent, unitPrice, /* engineering: engineering, */ fixedFlag: fixedFlag, rowIdx: i };
                 sheetDatas.datas.push(data);
                 sheetDatas.datasIdx['rowIdx' + i] = data;
             }

+ 35 - 0
web/maintain/price_info_lib/css/index.css

@@ -117,4 +117,39 @@ body {
 
 .main .right .bottom {
     height: 30%;
+}
+
+.empty-spread {
+    height: 300px;
+}
+
+.recommend {
+    margin: 4px 0;
+}
+
+.recommend-spread {
+    height: 200px;
+}
+
+.recommend-input {
+    display: inline-block;
+    width: 200px !important;
+}
+
+#save-in-summary {
+    margin-bottom: 4px;
+}
+
+#match-all {
+    display: inline-block;
+    margin-left: 20px;
+    color: #000;
+}
+
+#match-all-input {
+    margin-left: -1rem !important;
+}
+
+#match-all-label {
+    padding: 0;
 }

+ 40 - 2
web/maintain/price_info_lib/html/edit.html

@@ -19,7 +19,16 @@
             <span class="header-logo px-2">Smartcost</span>
             <div class="navbar-text"><a href="/priceInfo/main">信息价库</a><i
                     class="fa fa-angle-right fa-fw"></i><%= libName  %>
-               <button id="calc-price-index">计算指数</button>     
+               <button id="calc-price-index">计算指数</button>
+               <button id="match-summary">匹配总表</button> 
+               <button id="show-empty">显示空数据</button> 
+               <button id="check-repeat">检测重复别名编码</button> 
+               <div class="form-check" id="match-all">
+                <input class="form-check-input" type="checkbox" value="" id="match-all-input">
+                <label class="form-check-label" for="match-all-input" id="match-all-label">
+                  匹配所有
+                </label>
+              </div>
             </div>
 
         </nav>
@@ -83,6 +92,28 @@
             </div>
         </div>
     </div>
+
+    <div class="modal fade in" id="empty-area" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content" style=" width: 900px;">
+                <div class="modal-header">
+                    <h5 class="modal-title">新增材料</h5>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">×</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <button id="save-in-summary">保存至总表</button> 
+                    <div class="empty-spread"></div>
+                    <div class="recommend">
+                        <span class="recommend-title">相似材料数据</span>
+                        <input class="form-control form-control-sm recommend-input" id="recommend-search" type="text" value="" placeholder="输入回车进行搜索">
+                    </div>
+                    <div class="recommend-spread"></div>
+                </div>
+            </div>
+        </div>
+    </div>
     <!-- JS. -->
     <script src="/lib/jquery/jquery.min.js"></script>
     <script src="/lib/jquery-contextmenu/jquery.contextMenu.min.js"></script>
@@ -108,7 +139,14 @@
         const compilationID = '<%- compilationID %>';
         const curLibPeriod = '<%- period %>';
     </script>
-    <script src="/web/maintain/price_info_lib/js/index.js"></script>
+        <script src="/web/maintain/price_info_lib/js/common.js"></script>
+        <script src="/web/maintain/price_info_lib/js/priceArea.js"></script>
+        <script src="/web/maintain/price_info_lib/js/priceClass.js"></script>
+        <script src="/web/maintain/price_info_lib/js/priceKeyword.js"></script>
+        <script src="/web/maintain/price_info_lib/js/priceItem.js"></script>
+        <script src="/web/maintain/price_info_lib/js/index.js"></script>
+        <script src="/web/maintain/price_info_lib/js/priceRecommend.js"></script>
+        <script src="/web/maintain/price_info_lib/js/priceEmpty.js"></script>
 </body>
 
 </html>

+ 87 - 0
web/maintain/price_info_lib/js/common.js

@@ -0,0 +1,87 @@
+
+function setAlign(sheet, headers) {
+  const fuc = () => {
+    headers.forEach(({ hAlign, vAlign }, index) => {
+      sheetCommonObj.setAreaAlign(sheet.getRange(-1, index, -1, 1), hAlign, vAlign)
+    });
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+function setFormatter(sheet, headers) {
+  const fuc = () => {
+    headers.forEach(({ formatter }, index) => {
+      if (formatter) {
+        sheet.setFormatter(-1, index, formatter);
+      }
+    });
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+function initSheet(dom, setting) {
+  const workBook = sheetCommonObj.buildSheet(dom, setting);
+  const sheet = workBook.getSheet(0);
+  setAlign(sheet, setting.header);
+  setFormatter(sheet, setting.header);
+  return workBook;
+}
+
+function showData(sheet, data, headers, emptyRows) {
+  const fuc = () => {
+    sheet.setRowCount(data.length);
+    data.forEach((item, row) => {
+      headers.forEach(({ dataCode }, col) => {
+        sheet.setValue(row, col, item[dataCode] || '');
+      });
+    });
+    if (emptyRows) {
+      sheet.addRows(data.length, emptyRows);
+    }
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+// 获取当前表中行数据
+function getRowData(sheet, row, headers) {
+  const item = {};
+  headers.forEach(({ dataCode }, index) => {
+    const value = sheet.getValue(row, index) || '';
+    if (value) {
+      item[dataCode] = value;
+    }
+  });
+  return item;
+}
+
+// 获取表数据和缓存数据的不同数据
+function getRowDiffData(curRowData, cacheRowData, headers) {
+  let item = null;
+  headers.forEach(({ dataCode }) => {
+    const curValue = curRowData[dataCode];
+    const cacheValue = cacheRowData[dataCode];
+    if (!cacheValue && !curValue) {
+      return;
+    }
+    if (cacheValue !== curValue) {
+      if (!item) {
+        item = {};
+      }
+      item[dataCode] = curValue || '';
+    }
+  });
+  return item;
+}
+
+const TIME_OUT = 1000 * 20;
+const libID = window.location.search.match(/libID=([^&]+)/)[1];
+
+const UpdateType = {
+  UPDATE: 'update',
+  DELETE: 'delete',
+  CREATE: 'create',
+};
+
+const DEBOUNCE_TIME = 200;
+
+const locked = lockUtil.getLocked();

+ 0 - 891
web/maintain/price_info_lib/js/index.js

@@ -1,898 +1,7 @@
-
-function setAlign(sheet, headers) {
-    const fuc = () => {
-        headers.forEach(({ hAlign, vAlign }, index) => {
-            sheetCommonObj.setAreaAlign(sheet.getRange(-1, index, -1, 1), hAlign, vAlign)
-        });
-    };
-    sheetCommonObj.renderSheetFunc(sheet, fuc);
-}
-
-function setFormatter(sheet, headers) {
-    const fuc = () => {
-        headers.forEach(({ formatter }, index) => {
-            if (formatter) {
-                sheet.setFormatter(-1, index, formatter);
-            }
-        });
-    };
-    sheetCommonObj.renderSheetFunc(sheet, fuc);
-}
-
-function initSheet(dom, setting) {
-    const workBook = sheetCommonObj.buildSheet(dom, setting);
-    const sheet = workBook.getSheet(0);
-    setAlign(sheet, setting.header);
-    setFormatter(sheet, setting.header);
-    return workBook;
-}
-
-function showData(sheet, data, headers, emptyRows) {
-    const fuc = () => {
-        sheet.setRowCount(data.length);
-        data.forEach((item, row) => {
-            headers.forEach(({ dataCode }, col) => {
-                sheet.setValue(row, col, item[dataCode] || '');
-            });
-        });
-        if (emptyRows) {
-            sheet.addRows(data.length, emptyRows);
-        }
-    };
-    sheetCommonObj.renderSheetFunc(sheet, fuc);
-}
-
-const TIME_OUT = 10000;
-const libID = window.location.search.match(/libID=([^&]+)/)[1];
-
-const UpdateType = {
-    UPDATE: 'update',
-    DELETE: 'delete',
-    CREATE: 'create',
-};
-
-const DEBOUNCE_TIME = 200;
-
-const locked = lockUtil.getLocked();
-
-// 地区表
-const AREA_BOOK = (() => {
-    const cache = areaList;
-    const setting = {
-        header: [
-            { headerName: '序号', headerWidth: 60, dataCode: 'serialNo', dataType: 'Number', hAlign: 'center', vAlign: 'center' },
-            { headerName: '地区', headerWidth: $('#area-spread').width() - 80, dataCode: 'name', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-        ]
-    };
-    // 初始化表格
-    const workBook = initSheet($('#area-spread')[0], setting);
-    lockUtil.lockSpreads([workBook], locked);
-    workBook.options.allowExtendPasteRange = false;
-    workBook.options.allowUserDragDrop = true;
-    workBook.options.allowUserDragFill = true;
-    const sheet = workBook.getSheet(0);
-
-    // 排序显示
-    cache.sort((a, b) => a.serialNo - b.serialNo);
-
-    // 显示数据
-    showData(sheet, cache, setting.header);
-
-    // 编辑处理
-    async function handleEdit(changedCells) {
-        const updateData = [];
-        let reSort = false;
-        changedCells.forEach(({ row, col }) => {
-            const field = setting.header[col].dataCode;
-            let value = sheet.getValue(row, col);
-            if (field === 'serialNo') {
-                reSort = true;
-                value = +value;
-            }
-            updateData.push({
-                row,
-                field,
-                value,
-                ID: cache[row].ID,
-            });
-        });
-        try {
-            await ajaxPost('/priceInfo/editArea', { updateData }, TIME_OUT);
-            updateData.forEach(({ row, field, value }) => cache[row][field] = value);
-            if (reSort) {
-                cache.sort((a, b) => a.serialNo - b.serialNo);
-                showData(sheet, cache, setting.header);
-            }
-        } catch (err) {
-            // 恢复各单元格数据
-            sheetCommonObj.renderSheetFunc(sheet, () => {
-                changedCells.forEach(({ row, col, field }) => {
-                    sheet.setValue(row, col, cache[row][field]);
-                });
-            });
-        }
-    }
-    sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
-        const changedCells = [{ row: info.row, col: info.col }];
-        handleEdit(changedCells);
-    });
-    sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
-        handleEdit(info.changedCells);
-    });
-
-    const curArea = { ID: null };
-    // 焦点变更处理
-    const debounceSelectionChanged = _.debounce(function (e, info) {
-        const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
-        handleSelectionChanged(row);
-    }, DEBOUNCE_TIME, { leading: true }); // leading = true : 先触发再延迟
-    function handleSelectionChanged(row) {
-        const areaItem = cache[row];
-        curArea.ID = areaItem && areaItem.ID || null;
-        CLASS_BOOK.initData(libID, curArea.ID);
-    }
-    sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, debounceSelectionChanged);
-
-    // 新增
-    async function insert() {
-        const data = {
-            compilationID,
-            ID: uuid.v1(),
-            name: '',
-        };
-        try {
-            $.bootstrapLoading.start();
-            await ajaxPost('/priceInfo/insertArea', { insertData: [data] });
-            // 新增的数据总是添加在最后
-            sheet.addRows(cache.length, 1);
-            cache.push(data);
-            const lastRow = cache.length - 1;
-            sheet.setSelection(lastRow, 0, 1, 1);
-            sheet.showRow(lastRow, GC.Spread.Sheets.VerticalPosition.top);
-            handleSelectionChanged(lastRow);
-        } catch (err) {
-            alert(err);
-        } finally {
-            $.bootstrapLoading.end();
-        }
-    }
-
-    // 删除
-    async function del() {
-        try {
-            $.bootstrapLoading.start();
-            await ajaxPost('/priceInfo/deleteArea', { deleteData: [curArea.ID] });
-            const index = cache.findIndex(item => item.ID === curArea.ID);
-            sheet.deleteRows(index, 1);
-            cache.splice(index, 1);
-            const row = sheet.getActiveRowIndex();
-            handleSelectionChanged(row);
-        } catch (err) {
-            alert(err);
-        } finally {
-            $.bootstrapLoading.end();
-        }
-    }
-
-    // 右键功能
-    function buildContextMenu() {
-        $.contextMenu({
-            selector: '#area-spread',
-            build: function ($triggerElement, e) {
-                // 控制允许右键菜单在哪个位置出现
-                const offset = $('#area-spread').offset();
-                const x = e.pageX - offset.left;
-                const y = e.pageY - offset.top;
-                const target = sheet.hitTest(x, y);
-                if (target.hitTestType === 3) { // 在表格内
-                    const sel = sheet.getSelections()[0];
-                    if (sel && sel.rowCount === 1 && typeof target.row !== 'undefined') {
-                        const orgRow = sheet.getActiveRowIndex();
-                        if (orgRow !== target.row) {
-                            sheet.setActiveCell(target.row, target.col);
-                            handleSelectionChanged(target.row);
-                        }
-                    }
-                    return {
-                        items: {
-                            insert: {
-                                name: '新增',
-                                icon: "fa-arrow-left",
-                                disabled: function () {
-                                    return locked;
-                                },
-                                callback: function (key, opt) {
-                                    insert();
-                                }
-                            },
-                            del: {
-                                name: '删除',
-                                icon: "fa-arrow-left",
-                                disabled: function () {
-                                    return locked || !cache[target.row];
-                                },
-                                callback: function (key, opt) {
-                                    del();
-                                }
-                            },
-                        }
-                    };
-                }
-                else {
-                    return false;
-                }
-            }
-        });
-    }
-    buildContextMenu();
-
-    return {
-        handleSelectionChanged,
-        curArea,
-    }
-
-})();
-
-// 分类表
-const CLASS_BOOK = (() => {
-    const setting = {
-        header: [{ headerName: '分类', headerWidth: $('#area-spread').width(), dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' }],
-        controller: {
-            cols: [
-                {
-                    data: {
-                        field: 'name',
-                        vAlign: 1,
-                        hAlign: 0,
-                        font: 'Arial'
-                    },
-                }
-            ],
-            headRows: 1,
-            headRowHeight: [30],
-            emptyRows: 0,
-            treeCol: 0
-        },
-        tree: {
-            id: 'ID',
-            pid: 'ParentID',
-            nid: 'NextSiblingID',
-            rootId: -1
-        }
-    };
-    // 初始化表格
-    const workBook = initSheet($('#class-spread')[0], setting);
-    workBook.options.allowExtendPasteRange = false;
-    workBook.options.allowUserDragDrop = true;
-    workBook.options.allowUserDragFill = true;
-    const sheet = workBook.getSheet(0);
-
-    let tree;
-    let controller;
-    // 初始化数据
-    async function initData(libID, areaID) {
-        if (!areaID) {
-            tree = null;
-            controller = null;
-            sheet.setRowCount(0);
-            PRICE_BOOK.clear();
-            return;
-        }
-        $.bootstrapLoading.start();
-        try {
-            const data = await ajaxPost('/priceInfo/getClassData', { libID, areaID }, TIME_OUT);
-            tree = idTree.createNew(setting.tree);
-            tree.loadDatas(data);
-            tree.selected = tree.items.length > 0 ? tree.items[0] : null;
-            controller = TREE_SHEET_CONTROLLER.createNew(tree, sheet, setting.controller, false);
-            controller.showTreeData();
-            handleSelectionChanged(0);
-            sheet.setSelection(0, 0, 1, 1);
-            lockUtil.lockSpreads([workBook], locked);
-        } catch (err) {
-            console.log(err);
-            tree = null;
-            controller = null;
-            sheet.setRowCount(0);
-            alert(err);
-        } finally {
-            $.bootstrapLoading.end();
-        }
-    }
-
-    // 编辑处理
-    async function handleEdit(changedCells) {
-        const updateData = [];
-        changedCells.forEach(({ row, col }) => {
-            updateData.push({
-                row,
-                type: UpdateType.UPDATE,
-                filter: { ID: tree.items[row].data.ID },
-                update: { name: sheet.getValue(row, col) }
-            });
-        });
-        try {
-            await ajaxPost('/priceInfo/editClassData', { updateData }, TIME_OUT);
-            updateData.forEach(({ row, update: { name } }) => tree.items[row].data.name = name);
-        } catch (err) {
-            // 恢复各单元格数据
-            sheetCommonObj.renderSheetFunc(sheet, () => {
-                changedCells.forEach(({ row }) => {
-                    sheet.setValue(tree.items[row].data.name);
-                });
-            });
-        }
-    }
-    sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
-        const changedCells = [{ row: info.row, col: info.col }];
-        handleEdit(changedCells);
-    });
-    sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
-        handleEdit(info.changedCells);
-    });
-
-    // 树操作相关
-    const $insert = $('#tree-insert');
-    const $remove = $('#tree-remove');
-    const $upLevel = $('#tree-up-level');
-    const $downLevel = $('#tree-down-level');
-    const $downMove = $('#tree-down-move');
-    const $upMove = $('#tree-up-move');
-    const $calcPriceIndex = $('#calc-price-index');
-
-    // 插入
-    let canInsert = true;
-    async function insert() {
-        try {
-            if (!canInsert) {
-                return false;
-            }
-            canInsert = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            const newItem = {
-                libID,
-                areaID: AREA_BOOK.curArea.ID,
-                ID: uuid.v1(),
-                name: '',
-                ParentID: '-1',
-                NextSiblingID: '-1'
-            };
-            if (selected) {
-                newItem.ParentID = selected.data.ParentID;
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.data.ID },
-                    update: { NextSiblingID: newItem.ID }
-                });
-                if (selected.nextSibling) {
-                    newItem.NextSiblingID = selected.nextSibling.data.ID;
-                }
-            }
-            updateData.push({
-                type: UpdateType.CREATE,
-                document: newItem
-            });
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.insertByID(newItem.ID);
-            handleSelectionChanged(sheet.getActiveRowIndex());
-        } catch (err) {
-            console.log(err);
-            alert(err);
-        } finally {
-            canInsert = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $insert.click(_.debounce(insert, DEBOUNCE_TIME, { leading: true }));
-
-    // 删除
-    let canRemove = true;
-    async function remove() {
-        try {
-            if (!canRemove) {
-                return false;
-            }
-            canRemove = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            const children = selected.getPosterity();
-            [selected, ...children].forEach(node => updateData.push({
-                type: UpdateType.DELETE,
-                filter: { ID: node.data.ID }
-            }));
-            if (selected.preSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.preSibling.data.ID },
-                    update: { NextSiblingID: selected.data.NextSiblingID }
-                });
-            }
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.delete();
-            handleSelectionChanged(sheet.getActiveRowIndex());
-        } catch (err) {
-            alert(err);
-        } finally {
-            canRemove = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $remove.click(_.debounce(remove, DEBOUNCE_TIME, { leading: true }));
-
-    // 升级
-    let canUpLevel = true;
-    async function upLevel() {
-        try {
-            if (!canUpLevel) {
-                return false;
-            }
-            canUpLevel = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            if (selected.preSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.preSibling.data.ID },
-                    update: { NextSiblingID: -1 }
-                });
-            }
-            if (selected.parent) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.parent.data.ID },
-                    update: { NextSiblingID: selected.data.ID }
-                });
-            }
-            updateData.push({
-                type: UpdateType.UPDATE,
-                filter: { ID: selected.data.ID },
-                update: { ParentID: selected.parent.data.ParentID, NextSiblingID: selected.parent.data.NextSiblingID }
-            });
-            let curNode = selected.nextSibling;
-            while (curNode) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: curNode.data.ID },
-                    update: { ParentID: selected.data.ID }
-                });
-                curNode = curNode.nextSibling;
-            }
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.upLevel();
-            refreshTreeButton(tree.selected);
-        } catch (err) {
-            alert(err);
-        } finally {
-            canUpLevel = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $upLevel.click(_.debounce(upLevel, DEBOUNCE_TIME, { leading: true }));
-
-    // 降级
-    let canDownLevel = true;
-    async function downLevel() {
-        try {
-            if (!canDownLevel) {
-                return false;
-            }
-            canDownLevel = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            if (selected.preSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.preSibling.data.ID },
-                    update: { NextSiblingID: selected.data.NextSiblingID }
-                });
-                const preSiblingLastChild = selected.preSibling.children[selected.preSibling.children.length - 1];
-                if (preSiblingLastChild) {
-                    updateData.push({
-                        type: UpdateType.UPDATE,
-                        filter: { ID: preSiblingLastChild.data.ID },
-                        update: { NextSiblingID: selected.data.ID }
-                    });
-                }
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.data.ID },
-                    update: { ParentID: selected.preSibling.data.ID, NextSiblingID: -1 }
-                });
-            }
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.downLevel();
-            refreshTreeButton(tree.selected);
-        } catch (err) {
-            alert(err);
-        } finally {
-            canDownLevel = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $downLevel.click(_.debounce(downLevel, DEBOUNCE_TIME, { leading: true }));
-
-    // 下移
-    let canDownMove = true;
-    async function downMove() {
-        try {
-            if (!canDownMove) {
-                return false;
-            }
-            canDownMove = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            if (selected.preSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.preSibling.data.ID },
-                    update: { NextSiblingID: selected.data.NextSiblingID }
-                });
-            }
-            updateData.push({
-                type: UpdateType.UPDATE,
-                filter: { ID: selected.data.ID },
-                update: { NextSiblingID: selected.nextSibling.data.NextSiblingID }
-            });
-            updateData.push({
-                type: UpdateType.UPDATE,
-                filter: { ID: selected.nextSibling.data.ID },
-                update: { NextSiblingID: selected.data.ID }
-            });
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.downMove();
-            refreshTreeButton(tree.selected);
-        } catch (err) {
-            alert(err);
-        } finally {
-            canDownMove = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $downMove.click(_.debounce(downMove, DEBOUNCE_TIME, { leading: true }));
-
-    // 上移
-    let canUpMove = true;
-    async function upMove() {
-        try {
-            if (!canUpMove) {
-                return false;
-            }
-            canUpMove = false;
-            $.bootstrapLoading.start();
-            const updateData = [];
-            const selected = tree.selected;
-            if (selected.preSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: selected.preSibling.data.ID },
-                    update: { NextSiblingID: selected.data.NextSiblingID }
-                });
-            }
-            const prePreSibling = selected.preSibling.preSibling;
-            if (prePreSibling) {
-                updateData.push({
-                    type: UpdateType.UPDATE,
-                    filter: { ID: prePreSibling.data.ID },
-                    update: { NextSiblingID: selected.data.ID }
-                });
-            }
-            updateData.push({
-                type: UpdateType.UPDATE,
-                filter: { ID: selected.data.ID },
-                update: { NextSiblingID: selected.preSibling.data.ID }
-            });
-            await ajaxPost('/priceInfo/editClassData', { updateData });
-            controller.upMove();
-            refreshTreeButton(tree.selected);
-        } catch (err) {
-            alert(err);
-        } finally {
-            canUpMove = true;
-            $.bootstrapLoading.end();
-        }
-    }
-
-    $upMove.click(_.debounce(upMove, DEBOUNCE_TIME, { leading: true }));
-
-
-    // 刷新树操作按钮有效性
-    function refreshTreeButton(selected) {
-        if (locked) {
-            return;
-        }
-        $insert.removeClass('disabled');
-        $remove.removeClass('disabled');
-        $upLevel.removeClass('disabled');
-        $downLevel.removeClass('disabled');
-        $downMove.removeClass('disabled');
-        $upMove.removeClass('disabled');
-        if (!selected) {
-            $remove.addClass('disabled');
-            $upLevel.addClass('disabled');
-            $downLevel.addClass('disabled');
-            $downMove.addClass('disabled');
-            $upMove.addClass('disabled');
-        } else {
-            if (!selected.preSibling) {
-                $downLevel.addClass('disabled');
-                $upMove.addClass('disabled');
-            }
-            if (!selected.nextSibling) {
-                $downMove.addClass('disabled');
-            }
-            if (!selected.parent) {
-                $upLevel.addClass('disabled');
-            }
-        }
-    }
-
-    // 焦点变更处理
-    const curClass = { ID: null };
-    function handleSelectionChanged(row) {
-        const classNode = tree.items[row] || null;
-        tree.selected = classNode;
-        refreshTreeButton(classNode);
-        curClass.ID = classNode && classNode.data && classNode.data.ID || null;
-        const classIDList = []
-        if (classNode) {
-            classIDList.push(classNode.data.ID);
-            const children = classNode.getPosterity();
-            children.forEach(child => classIDList.push(child.data.ID));
-        }
-        PRICE_BOOK.initData(classIDList);
-    }
-    const debounceSelectionChanged = _.debounce(function (e, info) {
-        const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
-        handleSelectionChanged(row);
-    }, DEBOUNCE_TIME, { leading: true });
-    sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, debounceSelectionChanged);
-
-
-
-    $calcPriceIndex.click(_.debounce(async () => {
-        $.bootstrapLoading.start();
-        try {
-            const data = await ajaxPost('/priceInfo/calcPriceIndex', { libID, period: curLibPeriod, compilationID }, TIME_OUT);
-            //alert(data);
-
-            if (data) {
-                const htmlStr = data.replace(/\n/gm, '<br>'); //replaceAll('\n','<br>',data);
-                $("#result-info-body").html(htmlStr);
-                $("#result-info").modal('show');
-            } else {
-                alert('计算完成!')
-            }
-
-
-        } catch (error) {
-            console.log(error);
-        }
-        $.bootstrapLoading.end();
-
-    }, DEBOUNCE_TIME, { leading: true }));
-
-
-    return {
-        initData,
-        handleSelectionChanged,
-        curClass,
-    }
-
-})();
-
-// 关键字表
-const KEYWORD_BOOK = (() => {
-    const setting = {
-        header: [
-            { headerName: '关键字', headerWidth: 200, dataCode: 'keyword', dataType: 'String', hAlign: 'left', vAlign: 'center' },
-            { headerName: '单位', headerWidth: 70, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-            { headerName: '关键字效果', headerWidth: 100, dataCode: 'coe', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-            { headerName: '组别', headerWidth: 50, dataCode: 'group', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-            { headerName: '选项号', headerWidth: 70, dataCode: 'optionCode', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-        ],
-    };
-    // 初始化表格
-    const workBook = initSheet($('#keyword-spread')[0], setting);
-    workBook.options.allowUserDragDrop = false;
-    workBook.options.allowUserDragFill = false;
-    lockUtil.lockSpreads([workBook], true);
-    const sheet = workBook.getSheet(0);
-
-    // 显示关键字数据
-    const showKeywordData = (keywordList) => {
-        showData(sheet, keywordList, setting.header);
-    }
-
-    return {
-        showKeywordData
-    }
-})();
-
-// 价格信息表
-const PRICE_BOOK = (() => {
-    const setting = {
-        header: [
-            { headerName: '编码', headerWidth: 100, dataCode: 'code', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
-            { headerName: '别名编码', headerWidth: 70, dataCode: 'classCode', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
-            { headerName: '名称', headerWidth: 200, dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' },
-            { headerName: '规格型号', headerWidth: 120, dataCode: 'specs', dataType: 'String', hAlign: 'left', vAlign: 'center' },
-            { headerName: '单位', headerWidth: 80, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
-            { headerName: '不含税价', headerWidth: 80, dataCode: 'noTaxPrice', dataType: 'String', hAlign: 'right', vAlign: 'center' },
-            { headerName: '含税价', headerWidth: 80, dataCode: 'taxPrice', dataType: 'String', hAlign: 'right', vAlign: 'center' },
-            { headerName: '月份备注', headerWidth: 140, dataCode: 'dateRemark', dataType: 'String', hAlign: 'left', vAlign: 'center' },
-            { headerName: '计算式', headerWidth: 100, dataCode: 'expString', dataType: 'String', hAlign: 'left', vAlign: 'center' },
-        ],
-    };
-    // 初始化表格
-    const workBook = initSheet($('#price-spread')[0], setting);
-    workBook.options.allowUserDragDrop = true;
-    workBook.options.allowUserDragFill = true;
-    lockUtil.lockSpreads([workBook], locked);
-    const sheet = workBook.getSheet(0);
-
-    let cache = [];
-    // 清空
-    function clear() {
-        cache = [];
-        sheet.setRowCount(0);
-    }
-    // 初始化数据
-    async function initData(classIDList) {
-        if (!classIDList || !classIDList.length) {
-            return clear();
-        }
-        $.bootstrapLoading.start();
-        try {
-            cache = await ajaxPost('/priceInfo/getPriceData', { classIDList }, TIME_OUT);
-            cache = _.sortBy(cache, 'classCode');
-            showData(sheet, cache, setting.header, 5);
-            const row = sheet.getActiveRowIndex();
-            const keywordList = cache[row] && cache[row].keywordList || [];
-            KEYWORD_BOOK.showKeywordData(keywordList);
-        } catch (err) {
-            cache = [];
-            sheet.setRowCount(0);
-            alert(err);
-        } finally {
-            $.bootstrapLoading.end();
-        }
-    }
-
-    // 获取当前表中行数据
-    function getRowData(sheet, row, headers) {
-        const item = {};
-        headers.forEach(({ dataCode }, index) => {
-            const value = sheet.getValue(row, index) || '';
-            if (value) {
-                item[dataCode] = value;
-            }
-        });
-        return item;
-    }
-
-    // 获取表数据和缓存数据的不同数据
-    function getRowDiffData(curRowData, cacheRowData, headers) {
-        let item = null;
-        headers.forEach(({ dataCode }) => {
-            const curValue = curRowData[dataCode];
-            const cacheValue = cacheRowData[dataCode];
-            if (!cacheValue && !curValue) {
-                return;
-            }
-            if (cacheValue !== curValue) {
-                if (!item) {
-                    item = {};
-                }
-                item[dataCode] = curValue || '';
-            }
-        });
-        return item;
-    }
-
-    // 编辑处理
-    async function handleEdit(changedCells) {
-        const postData = []; // 请求用
-        // 更新缓存用
-        const updateData = [];
-        const deleteData = [];
-        const insertData = [];
-        try {
-            changedCells.forEach(({ row }) => {
-                if (cache[row]) {
-                    const rowData = getRowData(sheet, row, setting.header);
-                    if (Object.keys(rowData).length) { // 还有数据,更新
-                        const diffData = getRowDiffData(rowData, cache[row], setting.header);
-                        if (diffData) {
-                            postData.push({ type: UpdateType.UPDATE, ID: cache[row].ID, data: diffData });
-                            updateData.push({ row, data: diffData });
-                        }
-                    } else { // 该行无数据了,删除
-                        postData.push({ type: UpdateType.DELETE, ID: cache[row].ID });
-                        deleteData.push(cache[row]);
-                    }
-                } else { // 新增
-                    const rowData = getRowData(sheet, row, setting.header);
-                    if (Object.keys(rowData).length) {
-                        rowData.ID = uuid.v1();
-                        rowData.libID = libID;
-                        rowData.compilationID = compilationID;
-                        rowData.areaID = AREA_BOOK.curArea.ID;
-                        rowData.classID = CLASS_BOOK.curClass.ID;
-                        rowData.period = curLibPeriod;
-                        postData.push({ type: UpdateType.CREATE, data: rowData });
-                        insertData.push(rowData);
-                    }
-                }
-            });
-            if (postData.length) {
-                await ajaxPost('/priceInfo/editPriceData', { postData }, TIME_OUT);
-                // 更新缓存,先更新然后删除,最后再新增,防止先新增后缓存数据的下标与更新、删除数据的下标对应不上
-                updateData.forEach(item => {
-                    Object.assign(cache[item.row], item.data);
-                });
-                deleteData.forEach(item => {
-                    const index = cache.indexOf(item);
-                    if (index >= 0) {
-                        cache.splice(index, 1);
-                    }
-                });
-                insertData.forEach(item => cache.push(item));
-                if (deleteData.length || insertData.length) {
-                    showData(sheet, cache, setting.header, 5);
-                }
-            }
-        } catch (err) {
-            // 恢复各单元格数据
-            showData(sheet, cache, setting.header, 5);
-        }
-    }
-    sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
-        const changedCells = [{ row: info.row }];
-        handleEdit(changedCells);
-    });
-    sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
-        const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
-        // 显示关键字数据
-        const keywordList = cache[row] && cache[row].keywordList || [];
-        KEYWORD_BOOK.showKeywordData(keywordList);
-    });
-    sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
-        const changedRows = [];
-        let preRow;
-        info.changedCells.forEach(({ row }) => {
-            if (row !== preRow) {
-                changedRows.push({ row });
-            }
-            preRow = row;
-        });
-        handleEdit(changedRows);
-    });
-
-    return {
-        clear,
-        initData,
-    }
-})();
-
 $(document).ready(() => {
     console.log('进入信息价');
     $('[data-toggle="tooltip"]').tooltip();
     AREA_BOOK.handleSelectionChanged(0);
     const $range = $(document.body);
     lockUtil.lockTools($range, locked);
-
-
-
-
 });

+ 179 - 0
web/maintain/price_info_lib/js/priceArea.js

@@ -0,0 +1,179 @@
+// 地区表
+const AREA_BOOK = (() => {
+  const cache = areaList;
+  const setting = {
+    header: [
+      { headerName: '序号', headerWidth: 60, dataCode: 'serialNo', dataType: 'Number', hAlign: 'center', vAlign: 'center' },
+      { headerName: '地区', headerWidth: $('#area-spread').width() - 80, dataCode: 'name', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+    ]
+  };
+  // 初始化表格
+  const workBook = initSheet($('#area-spread')[0], setting);
+  lockUtil.lockSpreads([workBook], locked);
+  workBook.options.allowExtendPasteRange = false;
+  workBook.options.allowUserDragDrop = true;
+  workBook.options.allowUserDragFill = true;
+  const sheet = workBook.getSheet(0);
+
+  // 排序显示
+  cache.sort((a, b) => a.serialNo - b.serialNo);
+
+  // 显示数据
+  showData(sheet, cache, setting.header);
+
+  // 编辑处理
+  async function handleEdit(changedCells) {
+    const updateData = [];
+    let reSort = false;
+    changedCells.forEach(({ row, col }) => {
+      const field = setting.header[col].dataCode;
+      let value = sheet.getValue(row, col);
+      if (field === 'serialNo') {
+        reSort = true;
+        value = +value;
+      }
+      updateData.push({
+        row,
+        field,
+        value,
+        ID: cache[row].ID,
+      });
+    });
+    try {
+      await ajaxPost('/priceInfo/editArea', { updateData }, TIME_OUT);
+      updateData.forEach(({ row, field, value }) => cache[row][field] = value);
+      if (reSort) {
+        cache.sort((a, b) => a.serialNo - b.serialNo);
+        showData(sheet, cache, setting.header);
+      }
+    } catch (err) {
+      // 恢复各单元格数据
+      sheetCommonObj.renderSheetFunc(sheet, () => {
+        changedCells.forEach(({ row, col, field }) => {
+          sheet.setValue(row, col, cache[row][field]);
+        });
+      });
+    }
+  }
+  sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
+    const changedCells = [{ row: info.row, col: info.col }];
+    handleEdit(changedCells);
+  });
+  sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
+    handleEdit(info.changedCells);
+  });
+
+  const curArea = { ID: null, name: '' };
+  // 焦点变更处理
+  const debounceSelectionChanged = _.debounce(function (e, info) {
+    const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
+    handleSelectionChanged(row);
+  }, DEBOUNCE_TIME, { leading: true }); // leading = true : 先触发再延迟
+  function handleSelectionChanged(row) {
+    const areaItem = cache[row];
+    curArea.ID = areaItem && areaItem.ID || null;
+    curArea.name = areaItem && areaItem.name || '';
+    CLASS_BOOK.initData(libID, curArea.ID);
+  }
+  sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, debounceSelectionChanged);
+
+  // 新增
+  async function insert() {
+    const data = {
+      compilationID,
+      ID: uuid.v1(),
+      name: '',
+    };
+    try {
+      $.bootstrapLoading.start();
+      await ajaxPost('/priceInfo/insertArea', { insertData: [data] });
+      // 新增的数据总是添加在最后
+      sheet.addRows(cache.length, 1);
+      cache.push(data);
+      const lastRow = cache.length - 1;
+      sheet.setSelection(lastRow, 0, 1, 1);
+      sheet.showRow(lastRow, GC.Spread.Sheets.VerticalPosition.top);
+      handleSelectionChanged(lastRow);
+    } catch (err) {
+      alert(err);
+    } finally {
+      $.bootstrapLoading.end();
+    }
+  }
+
+  // 删除
+  async function del() {
+    try {
+      $.bootstrapLoading.start();
+      await ajaxPost('/priceInfo/deleteArea', { deleteData: [curArea.ID] });
+      const index = cache.findIndex(item => item.ID === curArea.ID);
+      sheet.deleteRows(index, 1);
+      cache.splice(index, 1);
+      const row = sheet.getActiveRowIndex();
+      handleSelectionChanged(row);
+    } catch (err) {
+      alert(err);
+    } finally {
+      $.bootstrapLoading.end();
+    }
+  }
+
+  // 右键功能
+  function buildContextMenu() {
+    $.contextMenu({
+      selector: '#area-spread',
+      build: function ($triggerElement, e) {
+        // 控制允许右键菜单在哪个位置出现
+        const offset = $('#area-spread').offset();
+        const x = e.pageX - offset.left;
+        const y = e.pageY - offset.top;
+        const target = sheet.hitTest(x, y);
+        if (target.hitTestType === 3) { // 在表格内
+          const sel = sheet.getSelections()[0];
+          if (sel && sel.rowCount === 1 && typeof target.row !== 'undefined') {
+            const orgRow = sheet.getActiveRowIndex();
+            if (orgRow !== target.row) {
+              sheet.setActiveCell(target.row, target.col);
+              handleSelectionChanged(target.row);
+            }
+          }
+          return {
+            items: {
+              insert: {
+                name: '新增',
+                icon: "fa-arrow-left",
+                disabled: function () {
+                  return locked;
+                },
+                callback: function (key, opt) {
+                  insert();
+                }
+              },
+              del: {
+                name: '删除',
+                icon: "fa-arrow-left",
+                disabled: function () {
+                  return locked || !cache[target.row];
+                },
+                callback: function (key, opt) {
+                  del();
+                }
+              },
+            }
+          };
+        }
+        else {
+          return false;
+        }
+      }
+    });
+  }
+  buildContextMenu();
+
+  return {
+    handleSelectionChanged,
+    curArea,
+    cache,
+  }
+
+})();

+ 493 - 0
web/maintain/price_info_lib/js/priceClass.js

@@ -0,0 +1,493 @@
+// 分类表
+const CLASS_BOOK = (() => {
+  const setting = {
+    header: [{ headerName: '分类', headerWidth: $('#area-spread').width(), dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' }],
+    controller: {
+      cols: [
+        {
+          data: {
+            field: 'name',
+            vAlign: 1,
+            hAlign: 0,
+            font: 'Arial'
+          },
+        }
+      ],
+      headRows: 1,
+      headRowHeight: [30],
+      emptyRows: 0,
+      treeCol: 0
+    },
+    tree: {
+      id: 'ID',
+      pid: 'ParentID',
+      nid: 'NextSiblingID',
+      rootId: -1
+    }
+  };
+  // 初始化表格
+  const workBook = initSheet($('#class-spread')[0], setting);
+  workBook.options.allowExtendPasteRange = false;
+  workBook.options.allowUserDragDrop = true;
+  workBook.options.allowUserDragFill = true;
+  const sheet = workBook.getSheet(0);
+
+  let tree;
+  let controller;
+  // 初始化数据
+  async function initData(libID, areaID) {
+    if (!areaID) {
+      tree = null;
+      controller = null;
+      sheet.setRowCount(0);
+      PRICE_BOOK.clear();
+      return;
+    }
+    $.bootstrapLoading.start();
+    try {
+      const data = await ajaxPost('/priceInfo/getClassData', { libID, areaID }, TIME_OUT);
+      tree = idTree.createNew(setting.tree);
+      tree.loadDatas(data);
+      tree.selected = tree.items.length > 0 ? tree.items[0] : null;
+      controller = TREE_SHEET_CONTROLLER.createNew(tree, sheet, setting.controller, false);
+      controller.showTreeData();
+      handleSelectionChanged(0);
+      sheet.setSelection(0, 0, 1, 1);
+      lockUtil.lockSpreads([workBook], locked);
+    } catch (err) {
+      tree = null;
+      controller = null;
+      sheet.setRowCount(0);
+      alert(err);
+    } finally {
+      $.bootstrapLoading.end();
+    }
+  }
+
+  // 编辑处理
+  async function handleEdit(changedCells) {
+    const updateData = [];
+    changedCells.forEach(({ row, col }) => {
+      updateData.push({
+        row,
+        type: UpdateType.UPDATE,
+        filter: { ID: tree.items[row].data.ID },
+        update: { name: sheet.getValue(row, col) }
+      });
+    });
+    try {
+      await ajaxPost('/priceInfo/editClassData', { updateData }, TIME_OUT);
+      updateData.forEach(({ row, update: { name } }) => tree.items[row].data.name = name);
+    } catch (err) {
+      // 恢复各单元格数据
+      sheetCommonObj.renderSheetFunc(sheet, () => {
+        changedCells.forEach(({ row }) => {
+          sheet.setValue(tree.items[row].data.name);
+        });
+      });
+    }
+  }
+  sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
+    const changedCells = [{ row: info.row, col: info.col }];
+    handleEdit(changedCells);
+  });
+  sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
+    handleEdit(info.changedCells);
+  });
+
+  // 树操作相关
+  const $insert = $('#tree-insert');
+  const $remove = $('#tree-remove');
+  const $upLevel = $('#tree-up-level');
+  const $downLevel = $('#tree-down-level');
+  const $downMove = $('#tree-down-move');
+  const $upMove = $('#tree-up-move');
+  const $calcPriceIndex = $('#calc-price-index');
+  const $matchSummary = $('#match-summary');
+  const $showEmpty = $('#show-empty');
+
+  // 插入
+  let canInsert = true;
+  async function insert() {
+    try {
+      if (!canInsert) {
+        return false;
+      }
+      canInsert = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      const newItem = {
+        libID,
+        areaID: AREA_BOOK.curArea.ID,
+        ID: uuid.v1(),
+        name: '',
+        ParentID: '-1',
+        NextSiblingID: '-1'
+      };
+      if (selected) {
+        newItem.ParentID = selected.data.ParentID;
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.data.ID },
+          update: { NextSiblingID: newItem.ID }
+        });
+        if (selected.nextSibling) {
+          newItem.NextSiblingID = selected.nextSibling.data.ID;
+        }
+      }
+      updateData.push({
+        type: UpdateType.CREATE,
+        document: newItem
+      });
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.insertByID(newItem.ID);
+      handleSelectionChanged(sheet.getActiveRowIndex());
+    } catch (err) {
+      alert(err);
+    } finally {
+      canInsert = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $insert.click(_.debounce(insert, DEBOUNCE_TIME, { leading: true }));
+
+  // 删除
+  let canRemove = true;
+  async function remove() {
+    try {
+      if (!canRemove) {
+        return false;
+      }
+      canRemove = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      const children = selected.getPosterity();
+      [selected, ...children].forEach(node => updateData.push({
+        type: UpdateType.DELETE,
+        filter: { ID: node.data.ID }
+      }));
+      if (selected.preSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.preSibling.data.ID },
+          update: { NextSiblingID: selected.data.NextSiblingID }
+        });
+      }
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.delete();
+      handleSelectionChanged(sheet.getActiveRowIndex());
+    } catch (err) {
+      alert(err);
+    } finally {
+      canRemove = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $remove.click(_.debounce(remove, DEBOUNCE_TIME, { leading: true }));
+
+  // 升级
+  let canUpLevel = true;
+  async function upLevel() {
+    try {
+      if (!canUpLevel) {
+        return false;
+      }
+      canUpLevel = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      if (selected.preSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.preSibling.data.ID },
+          update: { NextSiblingID: -1 }
+        });
+      }
+      if (selected.parent) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.parent.data.ID },
+          update: { NextSiblingID: selected.data.ID }
+        });
+      }
+      updateData.push({
+        type: UpdateType.UPDATE,
+        filter: { ID: selected.data.ID },
+        update: { ParentID: selected.parent.data.ParentID, NextSiblingID: selected.parent.data.NextSiblingID }
+      });
+      let curNode = selected.nextSibling;
+      while (curNode) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: curNode.data.ID },
+          update: { ParentID: selected.data.ID }
+        });
+        curNode = curNode.nextSibling;
+      }
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.upLevel();
+      refreshTreeButton(tree.selected);
+    } catch (err) {
+      alert(err);
+    } finally {
+      canUpLevel = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $upLevel.click(_.debounce(upLevel, DEBOUNCE_TIME, { leading: true }));
+
+  // 降级
+  let canDownLevel = true;
+  async function downLevel() {
+    try {
+      if (!canDownLevel) {
+        return false;
+      }
+      canDownLevel = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      if (selected.preSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.preSibling.data.ID },
+          update: { NextSiblingID: selected.data.NextSiblingID }
+        });
+        const preSiblingLastChild = selected.preSibling.children[selected.preSibling.children.length - 1];
+        if (preSiblingLastChild) {
+          updateData.push({
+            type: UpdateType.UPDATE,
+            filter: { ID: preSiblingLastChild.data.ID },
+            update: { NextSiblingID: selected.data.ID }
+          });
+        }
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.data.ID },
+          update: { ParentID: selected.preSibling.data.ID, NextSiblingID: -1 }
+        });
+      }
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.downLevel();
+      refreshTreeButton(tree.selected);
+    } catch (err) {
+      alert(err);
+    } finally {
+      canDownLevel = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $downLevel.click(_.debounce(downLevel, DEBOUNCE_TIME, { leading: true }));
+
+  // 下移
+  let canDownMove = true;
+  async function downMove() {
+    try {
+      if (!canDownMove) {
+        return false;
+      }
+      canDownMove = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      if (selected.preSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.preSibling.data.ID },
+          update: { NextSiblingID: selected.data.NextSiblingID }
+        });
+      }
+      updateData.push({
+        type: UpdateType.UPDATE,
+        filter: { ID: selected.data.ID },
+        update: { NextSiblingID: selected.nextSibling.data.NextSiblingID }
+      });
+      updateData.push({
+        type: UpdateType.UPDATE,
+        filter: { ID: selected.nextSibling.data.ID },
+        update: { NextSiblingID: selected.data.ID }
+      });
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.downMove();
+      refreshTreeButton(tree.selected);
+    } catch (err) {
+      alert(err);
+    } finally {
+      canDownMove = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $downMove.click(_.debounce(downMove, DEBOUNCE_TIME, { leading: true }));
+
+  // 上移
+  let canUpMove = true;
+  async function upMove() {
+    try {
+      if (!canUpMove) {
+        return false;
+      }
+      canUpMove = false;
+      $.bootstrapLoading.start();
+      const updateData = [];
+      const selected = tree.selected;
+      if (selected.preSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: selected.preSibling.data.ID },
+          update: { NextSiblingID: selected.data.NextSiblingID }
+        });
+      }
+      const prePreSibling = selected.preSibling.preSibling;
+      if (prePreSibling) {
+        updateData.push({
+          type: UpdateType.UPDATE,
+          filter: { ID: prePreSibling.data.ID },
+          update: { NextSiblingID: selected.data.ID }
+        });
+      }
+      updateData.push({
+        type: UpdateType.UPDATE,
+        filter: { ID: selected.data.ID },
+        update: { NextSiblingID: selected.preSibling.data.ID }
+      });
+      await ajaxPost('/priceInfo/editClassData', { updateData });
+      controller.upMove();
+      refreshTreeButton(tree.selected);
+    } catch (err) {
+      alert(err);
+    } finally {
+      canUpMove = true;
+      $.bootstrapLoading.end();
+    }
+  }
+
+  $upMove.click(_.debounce(upMove, DEBOUNCE_TIME, { leading: true }));
+
+
+  // 刷新树操作按钮有效性
+  function refreshTreeButton(selected) {
+    if (locked) {
+      return;
+    }
+    $insert.removeClass('disabled');
+    $remove.removeClass('disabled');
+    $upLevel.removeClass('disabled');
+    $downLevel.removeClass('disabled');
+    $downMove.removeClass('disabled');
+    $upMove.removeClass('disabled');
+    if (!selected) {
+      $remove.addClass('disabled');
+      $upLevel.addClass('disabled');
+      $downLevel.addClass('disabled');
+      $downMove.addClass('disabled');
+      $upMove.addClass('disabled');
+    } else {
+      if (!selected.preSibling) {
+        $downLevel.addClass('disabled');
+        $upMove.addClass('disabled');
+      }
+      if (!selected.nextSibling) {
+        $downMove.addClass('disabled');
+      }
+      if (!selected.parent) {
+        $upLevel.addClass('disabled');
+      }
+    }
+  }
+
+  // 焦点变更处理
+  const curClass = { ID: null };
+  function handleSelectionChanged(row) {
+    curRow = row;
+    const classNode = tree.items[row] || null;
+    tree.selected = classNode;
+    refreshTreeButton(classNode);
+    curClass.ID = classNode && classNode.data && classNode.data.ID || null;
+    const classIDList = []
+    if (classNode) {
+      classIDList.push(classNode.data.ID);
+      const children = classNode.getPosterity();
+      children.forEach(child => classIDList.push(child.data.ID));
+    }
+    PRICE_BOOK.initData(classIDList);
+  }
+  const debounceSelectionChanged = _.debounce(function (e, info) {
+    const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
+    handleSelectionChanged(row);
+  }, DEBOUNCE_TIME, { leading: true });
+  sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, debounceSelectionChanged);
+
+  const reload = () => {
+    if (curClass.ID) {
+      const node = tree.nodes[tree.prefix + curClass.ID];
+      if (node) {
+        handleSelectionChanged(node.serialNo())
+      }
+    }
+  }
+
+
+
+  $calcPriceIndex.click(_.debounce(async () => {
+    $.bootstrapLoading.start();
+    try {
+      const data = await ajaxPost('/priceInfo/calcPriceIndex', { libID, period: curLibPeriod, compilationID }, TIME_OUT);
+      //alert(data);
+
+      if (data) {
+        const htmlStr = data.replace(/\n/gm, '<br>'); //replaceAll('\n','<br>',data);
+        $("#result-info-body").html(htmlStr);
+        $("#result-info").modal('show');
+      } else {
+        alert('计算完成!')
+      }
+
+
+    } catch (error) {
+      console.log(error);
+    }
+    $.bootstrapLoading.end();
+
+  }, DEBOUNCE_TIME, { leading: true }));
+
+  // 匹配总表
+  $matchSummary.click(_.debounce(async () => {
+    $.bootstrapLoading.progressStart('匹配总表', true);
+    $("#progress_modal_body").text('正在匹配总表,请稍后...');
+    try {
+      const matchAll = $('#match-all-input')[0].checked;
+      const areaID = matchAll ? '' : AREA_BOOK.curArea?.ID;
+      debugger;
+      await ajaxPost('/priceInfo/matchSummary', { libID, compilationID, areaID }, 1000 * 60 * 10);
+      setTimeout(() => {
+        $.bootstrapLoading.progressEnd();
+        window.location.reload()
+      }, 1000);
+    } catch (error) {
+      alert(error)
+      console.log(error);
+      $.bootstrapLoading.progressEnd();
+    }
+  }, DEBOUNCE_TIME, { leading: true }));
+
+  // 显示空数据
+  $showEmpty.click(() => {
+    $('#empty-area').modal('show');
+  });
+
+
+  return {
+    initData,
+    handleSelectionChanged,
+    curClass,
+    reload,
+  }
+
+})();

+ 317 - 0
web/maintain/price_info_lib/js/priceEmpty.js

@@ -0,0 +1,317 @@
+// 空数据表
+const EMPTY_BOOK = (() => {
+  const setting = {
+    header: [
+      { headerName: '主从对应码', headerWidth: 100, dataCode: 'code', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '别名编码', headerWidth: 70, dataCode: 'classCode', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '计算式', headerWidth: 100, dataCode: 'expString', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '材料名称', headerWidth: 275, dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '规格型号', headerWidth: 180, dataCode: 'specs', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '单位', headerWidth: 80, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+    ],
+  };
+  // 初始化表格
+  const workBookObj = {
+    book: null,
+    sheet: null,
+  };
+  const buildWorkBook = () => {
+    workBookObj.book = initSheet($('.empty-spread')[0], setting);
+    workBookObj.book.options.allowUserDragDrop = true;
+    workBookObj.book.options.allowUserDragFill = true;
+    workBookObj.book.options.allowExtendPasteRange = false;
+    lockUtil.lockSpreads([workBookObj.book], locked);
+    workBookObj.sheet = workBookObj.book.getSheet(0);
+  }
+
+
+  // 总数据(数据没合并前)
+  let totalData = [];
+  let totalMap = {};
+
+  // 表格显示的数据(合并后)
+  const cache = [];
+
+  const getCompareKey = (item) => {
+    const props = ['name', 'specs', 'unit'];
+    return props.map(prop => item[prop] ? item[prop].trim() : '').join('@');
+  }
+
+  const setTotalMap = (items) => {
+    totalMap = {};
+    items.forEach(item => {
+      const key = getCompareKey(item);
+      if (totalMap[key]) {
+        totalMap[key].push(item);
+      } else {
+        totalMap[key] = [item];
+      }
+    })
+  }
+
+  // 获取表格数据,汇总空数据,多地区可能存在相同材料,按名称规格单位做筛选,重复材料仅显示一条即可
+  const getTableData = (items) => {
+    const map = {};
+    items.forEach(item => {
+      const key = getCompareKey(item);
+      if (!map[key]) {
+        map[key] = { ...item };
+      }
+    });
+    return Object.values(map);
+  }
+
+  // 根据表格数据,获取实际信息价数据(一对多)
+  const getItemsFromTableItem = (item) => {
+    const key = getCompareKey(item);
+    return totalMap[key] || [];
+  }
+
+  // 获取材料关键字: 名称 规格
+  const getKeyword = (item) => {
+    return item ? `${item.name} ${item.specs}` : '';
+  }
+
+  // 改变关键字
+  const changeKeyword = (item) => {
+    const keyword = getKeyword(item);
+    $('#recommend-search').val(keyword);
+    if (!keyword) {
+      RECOMMEND_BOOK.clear();
+    } else {
+      RECOMMEND_BOOK.loadRecommendData(keyword);
+    }
+  }
+
+  // 清空
+  function clear() {
+    cache.length = 0;
+    workBookObj.sheet.setRowCount(0);
+  }
+
+  let curRow = 0;
+
+  // 初始化数据
+  async function initData() {
+    clear();
+    curRow = 0;
+    $.bootstrapLoading.start();
+    try {
+      const matchAll = $('#match-all-input')[0].checked;
+      const areaID = matchAll ? '' : AREA_BOOK.curArea?.ID;
+      totalData = await ajaxPost('/priceInfo/getPriceEmptyData', { libID, compilationID, areaID }, 1000 * 60 * 10);
+      setTotalMap(totalData);
+      const tableData = getTableData(totalData);
+      cache.push(...tableData)
+      showData(workBookObj.sheet, cache, setting.header);
+      changeKeyword(cache[0]);
+    } catch (err) {
+      clear();
+      alert(err);
+    } finally {
+      $.bootstrapLoading.end();
+    }
+  }
+
+  // 编辑处理
+  async function handleEdit(changedCells, diffMap, needRefresh) {
+    const postData = []; // 请求用
+    // 更新缓存用
+    const updateData = [];
+    const deleteData = [];
+    const insertData = [];
+    try {
+      changedCells.forEach(({ row }) => {
+        if (cache[row]) {
+          const rowData = getRowData(workBookObj.sheet, row, setting.header);
+          if (Object.keys(rowData).length) { // 还有数据,更新
+            let diffData;
+            if (diffMap) {
+              diffData = diffMap[row];
+            } else {
+              diffData = getRowDiffData(rowData, cache[row], setting.header);
+            }
+            if (diffData) {
+              // 改一行, 实际可能是改多行,表格一行数据是多行合并显示的
+              const items = getItemsFromTableItem(cache[row]);
+              items.forEach(item => {
+                // 只有珠海建筑才更新计算式
+                const updateObj = { ...diffData };
+                const area = AREA_BOOK.cache.find(areaItem => areaItem.ID === item.areaID);
+                if (diffMap) {
+                  delete updateObj.expString;
+                  delete diffData.expString;
+                }
+                postData.push({ type: UpdateType.UPDATE, ID: item.ID, areaID: area.ID, compilationID, period: curLibPeriod, data: updateObj });
+              });
+              updateData.push({ row, data: diffData });
+            }
+          } else { // 该行无数据了,删除
+            const items = getItemsFromTableItem(cache[row]);
+            items.forEach(item => {
+              const area = AREA_BOOK.cache.find(areaItem => areaItem.ID === item.areaID);
+              postData.push({ type: UpdateType.DELETE, areaID: area.ID, compilationID, period: curLibPeriod, ID: item.ID });
+            });
+            deleteData.push(cache[row]);
+          }
+        }
+      });
+      if (postData.length) {
+        $.bootstrapLoading.start();
+        await ajaxPost('/priceInfo/editPriceData', { postData }, TIME_OUT);
+        // 更新缓存,先更新然后删除,最后再新增,防止先新增后缓存数据的下标与更新、删除数据的下标对应不上
+        updateData.forEach(item => {
+          // 更新总表
+          const curItem = cache[item.row];
+          const compareKey = getCompareKey(curItem);
+          const totalItems = totalMap[compareKey];
+          if (totalItems) {
+            const newCompareKey = getCompareKey({ ...curItem, ...item.data });
+            totalItems.forEach(totalItem => {
+              Object.assign(totalItem, item.data);
+            });
+            if (newCompareKey !== compareKey) {
+              totalMap[newCompareKey] = totalItems;
+              delete totalMap[compareKey];
+            }
+          }
+          // 更新表格缓存
+          Object.assign(cache[item.row], item.data);
+        });
+        deleteData.forEach(item => {
+          // 更新总表
+          const compareKey = getCompareKey(item);
+          const totalItems = totalMap[compareKey];
+          if (totalItems) {
+            const totalItemIDs = totalItems.map(item => item.ID);
+            totalData = totalData.filter(totalItem => !totalItemIDs.includes(totalItem));
+            delete totalMap[compareKey];
+          }
+          // 更新表格缓存
+          const index = cache.indexOf(item);
+          if (index >= 0) {
+            cache.splice(index, 1);
+          }
+        });
+        insertData.forEach(item => cache.push(item));
+        if (deleteData.length || insertData.length || needRefresh) {
+          showData(workBookObj.sheet, cache, setting.header);
+        }
+        $.bootstrapLoading.end();
+        CLASS_BOOK.reload();
+      }
+    } catch (err) {
+      // 恢复各单元格数据
+      showData(workBookObj.sheet, cache, setting.header);
+      $.bootstrapLoading.end();
+    }
+  }
+
+  // 跟新行的编号、编码编码
+  async function updateRowCode(code, classCode, expString) {
+    const item = cache[curRow];
+    if (!item) {
+      return;
+    }
+    const diffData = { code, classCode, expString };
+    await handleEdit([{ row: curRow }], { [curRow]: diffData }, true);
+  }
+
+  const bindEvent = () => {
+    workBookObj.sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
+      const changedCells = [{ row: info.row }];
+      handleEdit(changedCells);
+    });
+    workBookObj.sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
+      const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
+      if (curRow !== row) {
+        const item = cache[row];
+        changeKeyword(item);
+      }
+      curRow = row;
+    });
+    workBookObj.sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
+      const changedRows = [];
+      let preRow;
+      info.changedCells.forEach(({ row }) => {
+        if (row !== preRow) {
+          changedRows.push({ row });
+        }
+        preRow = row;
+      });
+      handleEdit(changedRows);
+    });
+  }
+
+  // 将空表的表格保存至总表
+  const saveInSummary = async () => {
+    const documents = [];
+    const removeIDs = [];
+    cache.filter(item => item.code && item.classCode).forEach(item => {
+      removeIDs.push(item.ID);
+      documents.push({
+        ID: uuid.v1(),
+        code: item.code,
+        classCode: item.classCode,
+        expString: item.expString,
+        name: item.name,
+        specs: item.specs,
+        unit: item.unit,
+      });
+    });
+    if (!documents.length) {
+      alert('不存在可保存数据');
+      return;
+    }
+    console.log(documents);
+    try {
+      $.bootstrapLoading.progressStart('保存至总表', true);
+      $("#progress_modal_body").text('正在保存至总表,请稍后...');
+      await ajaxPost('/priceInfoSummary/saveInSummary', { documents }, 1000 * 60 * 5);
+      setTimeout(() => {
+        $.bootstrapLoading.progressEnd();
+        alert('保存成功');
+        const filterCache = cache.filter(item => !removeIDs.includes(item.ID));
+        cache.length = 0;
+        cache.push(...filterCache);
+        showData(workBookObj.sheet, cache, setting.header);
+      }, 1000);
+    } catch (error) {
+      setTimeout(() => {
+        $.bootstrapLoading.progressEnd();
+      }, 500);
+      console.log(error);
+      alert(error);
+    }
+  }
+
+
+  return {
+    buildWorkBook,
+    bindEvent,
+    clear,
+    initData,
+    workBookObj,
+    updateRowCode,
+    saveInSummary,
+  }
+})();
+
+$(document).ready(() => {
+  $('#empty-area').on('shown.bs.modal', function () {
+    if (!EMPTY_BOOK.workBookObj.book) {
+      EMPTY_BOOK.buildWorkBook();
+      EMPTY_BOOK.bindEvent();
+    }
+    EMPTY_BOOK.initData();
+  });
+
+  $('#empty-area').on('hidden.bs.modal', function () {
+    EMPTY_BOOK.clear();
+  });
+
+  // 保存至总表
+  $('#save-in-summary').click(() => {
+    EMPTY_BOOK.saveInSummary();
+  });
+});

+ 184 - 0
web/maintain/price_info_lib/js/priceItem.js

@@ -0,0 +1,184 @@
+// 价格信息表
+const PRICE_BOOK = (() => {
+  const setting = {
+    header: [
+      { headerName: '编码', headerWidth: 100, dataCode: 'code', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '别名编码', headerWidth: 70, dataCode: 'classCode', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '名称', headerWidth: 200, dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '规格型号', headerWidth: 120, dataCode: 'specs', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '单位', headerWidth: 80, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+      { headerName: '不含税价', headerWidth: 80, dataCode: 'noTaxPrice', dataType: 'String', hAlign: 'right', vAlign: 'center' },
+      { headerName: '含税价', headerWidth: 80, dataCode: 'taxPrice', dataType: 'String', hAlign: 'right', vAlign: 'center' },
+      { headerName: '月份备注', headerWidth: 140, dataCode: 'dateRemark', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '计算式', headerWidth: 100, dataCode: 'expString', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+    ],
+  };
+  // 初始化表格
+  const workBook = initSheet($('#price-spread')[0], setting);
+  workBook.options.allowUserDragDrop = true;
+  workBook.options.allowUserDragFill = true;
+  lockUtil.lockSpreads([workBook], locked);
+  const sheet = workBook.getSheet(0);
+
+  let cache = [];
+  // 清空
+  function clear() {
+    cache = [];
+    sheet.setRowCount(0);
+  }
+
+  // 是否正在检测重复数据
+  let isCheckingRepeat = false;
+
+  // 初始化数据
+  async function initData(classIDList) {
+    isCheckingRepeat = false;
+    $('#check-repeat').text('检测重复别名编码');
+    if (!classIDList || !classIDList.length) {
+      return clear();
+    }
+    $.bootstrapLoading.start();
+    try {
+      cache = await ajaxPost('/priceInfo/getPriceData', { classIDList }, 1000 * 60 * 10);
+      cache = _.sortBy(cache, 'classCode');
+      showData(sheet, cache, setting.header, 5);
+      const row = sheet.getActiveRowIndex();
+      const keywordList = cache[row] && cache[row].keywordList || [];
+      KEYWORD_BOOK.showKeywordData(keywordList);
+    } catch (err) {
+      cache = [];
+      sheet.setRowCount(0);
+      alert(err);
+    } finally {
+      $.bootstrapLoading.end();
+    }
+  }
+
+  // 编辑处理
+  async function handleEdit(changedCells, needLoading = true) {
+    $.bootstrapLoading.start();
+    const areaID = AREA_BOOK.curArea.ID;
+    const postData = []; // 请求用
+    // 更新缓存用
+    const updateData = [];
+    const deleteData = [];
+    const insertData = [];
+    try {
+      changedCells.forEach(({ row }) => {
+        if (cache[row]) {
+          const rowData = getRowData(sheet, row, setting.header);
+          if (Object.keys(rowData).length) { // 还有数据,更新
+            const diffData = getRowDiffData(rowData, cache[row], setting.header);
+            if (diffData) {
+              postData.push({ type: UpdateType.UPDATE, ID: cache[row].ID, areaID, compilationID, period: curLibPeriod, data: diffData });
+              updateData.push({ row, data: diffData });
+            }
+          } else { // 该行无数据了,删除
+            postData.push({ type: UpdateType.DELETE, ID: cache[row].ID, areaID, compilationID, period: curLibPeriod });
+            deleteData.push(cache[row]);
+          }
+        } else { // 新增
+          const rowData = getRowData(sheet, row, setting.header);
+          if (Object.keys(rowData).length) {
+            rowData.ID = uuid.v1();
+            rowData.libID = libID;
+            rowData.compilationID = compilationID;
+            rowData.areaID = AREA_BOOK.curArea.ID;
+            rowData.classID = CLASS_BOOK.curClass.ID;
+            rowData.period = curLibPeriod;
+            postData.push({ type: UpdateType.CREATE, data: rowData });
+            insertData.push(rowData);
+          }
+        }
+      });
+      if (postData.length) {
+        await ajaxPost('/priceInfo/editPriceData', { postData }, TIME_OUT);
+        // 更新缓存,先更新然后删除,最后再新增,防止先新增后缓存数据的下标与更新、删除数据的下标对应不上
+        updateData.forEach(item => {
+          Object.assign(cache[item.row], item.data);
+        });
+        deleteData.forEach(item => {
+          const index = cache.indexOf(item);
+          if (index >= 0) {
+            cache.splice(index, 1);
+          }
+        });
+        insertData.forEach(item => cache.push(item));
+        if (deleteData.length || insertData.length) {
+          showData(sheet, cache, setting.header, isCheckingRepeat ? 0 : 5);
+        }
+      }
+    } catch (err) {
+      // 恢复各单元格数据
+      showData(sheet, cache, setting.header, 5);
+    }
+    $.bootstrapLoading.end();
+  }
+  sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
+    const changedCells = [{ row: info.row }];
+    handleEdit(changedCells);
+  });
+  sheet.bind(GC.Spread.Sheets.Events.SelectionChanged, function (e, info) {
+    const row = info.newSelections && info.newSelections[0] ? info.newSelections[0].row : 0;
+    // 显示关键字数据
+    const keywordList = cache[row] && cache[row].keywordList || [];
+    KEYWORD_BOOK.showKeywordData(keywordList);
+  });
+  sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
+    const changedRows = [];
+    let preRow;
+    info.changedCells.forEach(({ row }) => {
+      if (row !== preRow) {
+        changedRows.push({ row });
+      }
+      preRow = row;
+    });
+    handleEdit(changedRows);
+  });
+
+  // 显示重复别名编码数据
+  let orgCache = [...cache];
+  const showRepeatData = () => {
+    if (isCheckingRepeat) {
+      $('#check-repeat').text('检测重复别名编码');
+      isCheckingRepeat = false;
+      cache = orgCache;
+      showData(sheet, cache, setting.header, 5);
+      return;
+    }
+    $('#check-repeat').text('检测重复别名编码(检测中)');
+    isCheckingRepeat = true;
+    const tableData = [];
+    const classCodeMap = {};
+    cache.forEach(item => {
+      const classCode = item.classCode || '';
+      if (!classCodeMap[classCode]) {
+        classCodeMap[classCode] = 1;
+      } else {
+        classCodeMap[classCode] = classCodeMap[classCode] + 1;
+      }
+    });
+    cache.forEach(item => {
+      const classCode = item.classCode || '';
+      if (classCodeMap[classCode] > 1) {
+        tableData.push(item);
+      }
+    });
+    orgCache = [...cache];
+    cache = tableData;
+    showData(sheet, cache, setting.header);
+  }
+
+  return {
+    clear,
+    initData,
+    showRepeatData,
+  }
+})();
+
+$(document).ready(() => {
+  // 检测重复别名编码
+  $('#check-repeat').click(() => {
+    PRICE_BOOK.showRepeatData();
+  });
+});

+ 27 - 0
web/maintain/price_info_lib/js/priceKeyword.js

@@ -0,0 +1,27 @@
+// 关键字表
+const KEYWORD_BOOK = (() => {
+  const setting = {
+    header: [
+      { headerName: '关键字', headerWidth: 200, dataCode: 'keyword', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '单位', headerWidth: 70, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+      { headerName: '关键字效果', headerWidth: 100, dataCode: 'coe', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+      { headerName: '组别', headerWidth: 50, dataCode: 'group', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+      { headerName: '选项号', headerWidth: 70, dataCode: 'optionCode', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+    ],
+  };
+  // 初始化表格
+  const workBook = initSheet($('#keyword-spread')[0], setting);
+  workBook.options.allowUserDragDrop = false;
+  workBook.options.allowUserDragFill = false;
+  lockUtil.lockSpreads([workBook], true);
+  const sheet = workBook.getSheet(0);
+
+  // 显示关键字数据
+  const showKeywordData = (keywordList) => {
+    showData(sheet, keywordList, setting.header);
+  }
+
+  return {
+    showKeywordData
+  }
+})();

+ 96 - 0
web/maintain/price_info_lib/js/priceRecommend.js

@@ -0,0 +1,96 @@
+// 推荐相似材料
+const RECOMMEND_BOOK = (() => {
+  const setting = {
+    header: [
+      { headerName: '主从对应码', headerWidth: 100, dataCode: 'code', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '别名编码', headerWidth: 70, dataCode: 'classCode', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '计算式', headerWidth: 100, dataCode: 'expString', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '材料名称', headerWidth: 275, dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '规格型号', headerWidth: 180, dataCode: 'specs', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '单位', headerWidth: 80, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+    ],
+  };
+  // 初始化表格
+  // 初始化表格
+  const workBookObj = {
+    book: null,
+    sheet: null,
+  };
+  const buildWorkBook = () => {
+    workBookObj.book = initSheet($('.recommend-spread')[0], setting);
+    workBookObj.book.options.allowUserDragDrop = true;
+    workBookObj.book.options.allowUserDragFill = true;
+    workBookObj.book.options.allowExtendPasteRange = false;
+    lockUtil.lockSpreads([workBookObj.book], true);
+    workBookObj.sheet = workBookObj.book.getSheet(0);
+  }
+
+  // 表格显示的数据
+  const cache = [];
+
+  // 清空
+  function clear() {
+    cache.length = 0;
+    workBookObj.sheet.setRowCount(0);
+  }
+  // 初始化数据
+  async function loadRecommendData(keyword) {
+    clear();
+    $.bootstrapLoading.start();
+    try {
+      const items = await ajaxPost('/priceInfo/getRecommendPriceSummaryData', { keyword }, 1000 * 60 * 5);
+      cache.push(...items);
+      showData(workBookObj.sheet, cache, setting.header);
+    } catch (err) {
+      clear();
+      alert(err);
+    } finally {
+      $.bootstrapLoading.end();
+    }
+  }
+
+  // 双击更新空表编码和别名编码
+  const handleDbClick = (sender, args) => {
+    const item = cache[args.row];
+    if (item) {
+      // 只有珠海才更新计算式
+      //const expString = /珠海/.test(AREA_BOOK.curArea.name || '') ? item.expString : undefined;
+      EMPTY_BOOK.updateRowCode(item.code || '', item.classCode || '', item.expString || '');
+    }
+  }
+
+  const bindEvent = () => {
+    workBookObj.book.bind(GC.Spread.Sheets.Events.CellDoubleClick, handleDbClick)
+  }
+
+
+  return {
+    buildWorkBook,
+    bindEvent,
+    clear,
+    loadRecommendData,
+    workBookObj,
+  }
+})();
+
+$(document).ready(() => {
+  $('#empty-area').on('shown.bs.modal', function () {
+    // 生成表格
+    if (!RECOMMEND_BOOK.workBookObj.book) {
+      RECOMMEND_BOOK.buildWorkBook();
+      RECOMMEND_BOOK.bindEvent();
+    }
+  });
+
+  $('#empty-area').on('hidden.bs.modal', function () {
+    RECOMMEND_BOOK.clear();
+  });
+
+  // 回车搜索分词
+  $('#recommend-search').bind('keydown', function (event) {
+    if (event.keyCode === 13) {
+      const searchStr = $(this).val();
+      RECOMMEND_BOOK.loadRecommendData(searchStr);
+    }
+  })
+});

+ 91 - 0
web/maintain/price_info_summary/css/index.css

@@ -0,0 +1,91 @@
+html {
+    height: 100%;
+}
+
+body {
+    height: 100%;
+    font-size: 0.9rem;
+}
+
+.dropdown-menu {
+    font-size: 0.9rem
+}
+
+/*自定义css*/
+
+.header {
+    position: relative;
+    background: #e1e1e1
+}
+
+.header .header-logo {
+    background: #ff6501;
+    color: #fff;
+    float: left;
+    padding-top: .25rem;
+    padding-bottom: .25rem;
+    margin-right: 1rem;
+    font-size: 1.25rem;
+    line-height: inherit
+}
+
+.top-msg {
+    position: fixed;
+    top: 0;
+    width: 100%;
+    z-index: 999
+}
+
+.in-1 {
+    padding-left: 0rem!important
+}
+
+.in-2 {
+    padding-left: 1rem!important
+}
+
+.in-3 {
+    padding-left: 1.5rem!important
+}
+
+.in-4 {
+    padding-left: 2rem!important
+}
+
+.in-5 {
+    padding-left: 2.5rem!important
+}
+
+.in-6 {
+    padding-left: 3rem!important
+}
+
+.disabled {
+    pointer-events: none;
+    opacity: .65;
+    color: #666;
+}
+
+.wrapper {
+    position: absolute;
+    top: 38px;
+    bottom: 0;
+    width: 100%;
+}
+
+.search {
+    padding: 5px 10px;
+    /* background-color: #f7f7f7; */
+}
+
+.search-input {
+    width: 250px;
+}
+
+.main {
+    height: calc(100% - 78px);
+}
+
+.sheet {
+    height: 100%;
+}

+ 54 - 0
web/maintain/price_info_summary/html/main.html

@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
+    <title>材料信息价总表</title>
+    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
+    <link rel="stylesheet" href="/web/maintain/price_info_summary/css/index.css">
+    <link rel="stylesheet" href="/lib/font-awesome/font-awesome.min.css">
+    <link rel="stylesheet" href="/lib/spreadjs/sheets/css/gc.spread.sheets.sc.css" type="text/css">
+    <link rel="stylesheet" href="/lib/jquery-contextmenu/jquery.contextMenu.css" type="text/css">
+</head>
+
+<body>
+    <div class="header">
+        <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 ">
+            <span class="header-logo px-2">材料信息价总表</span>
+            <a class="lock" data-locked="true" href="javascript:void(0);" title="解锁"><i
+                class="fa fa-unlock-alt"></i></a>
+                <a id="export" class="btn btn-success btn-sm" href="javascript:void(0);" title="导出" style="margin-left: 10px;"><i class="fa fa-sign-out fa-rotate-270"></i>导出</a>
+        </nav>
+    </div>
+    <div class="search">
+        <input class="form-control form-control-sm search-input" id="summary-search" type="text" value="" placeholder="输入回车进行搜索">
+    </div>
+
+    <div class="main">
+        <div class="sheet" id="summary-spread"></div>
+    </div>
+    <!-- JS. -->
+    <script src="/lib/jquery/jquery.min.js"></script>
+    <script src="/lib/jquery-contextmenu/jquery.contextMenu.min.js"></script>
+    <script src="/lib/jquery-contextmenu/jquery.ui.position.js"></script>
+    <script src="/lib/tether/tether.min.js"></script>
+    <script src="/lib/bootstrap/bootstrap.min.js"></script>
+    <script src="/public/web/PerfectLoad.js"></script>
+    <script src="/lib/spreadjs/sheets/gc.spread.sheets.all.11.1.2.min.js"></script>
+    <script>GC.Spread.Sheets.LicenseKey = '<%- LicenseKey %>';</script>
+    <script src="/public/web/uuid.js"></script>
+    <script src="/lib/lodash/lodash.js"></script>
+    <script src="/public/web/scMathUtil.js"></script>
+    <script src="/public/web/treeDataHelper.js"></script>
+    <script src="/public/web/common_ajax.js"></script>
+    <script src="/public/web/lock_util.js"></script>
+    <script src="/public/web/id_tree.js"></script>
+    <script src="/public/web/tools_const.js"></script>
+    <script src="/public/web/tree_sheet/tree_sheet_controller.js"></script>
+    <script src="/public/web/tree_sheet/tree_sheet_helper.js"></script>
+    <script src="/public/web/sheet/sheet_common.js"></script>
+    <script src="/web/maintain/price_info_summary/js/summarySheet.js"></script>
+    <script src="/web/maintain/price_info_summary/js/index.js"></script>
+</body>
+</html>

+ 26 - 0
web/maintain/price_info_summary/js/index.js

@@ -0,0 +1,26 @@
+
+
+$(document).ready(() => {
+    //init();
+    // $('[data-toggle="tooltip"]').tooltip();
+    // AREA_BOOK.handleSelectionChanged(0);
+    // 锁定、解锁
+    lockUtil.displayLock($('.lock'), lockUtil.getLocked());
+    $('.lock').click(function () {
+        window.location.search = `?locked=${!lockUtil.getLocked()}`;
+    });
+
+    // 搜索
+    $('#summary-search').bind('keydown', function (event) {
+        if (event.keyCode === 13) {
+            // 回车搜索
+            const searchStr = $(this).val();
+            SUMMARY_BOOK.handleSearch(searchStr);
+        }
+    });
+
+    // 点击导出按钮
+    $('#export').click(() => {
+        window.location.href = '/priceInfoSummary/export';
+    });
+});

+ 259 - 0
web/maintain/price_info_summary/js/summarySheet.js

@@ -0,0 +1,259 @@
+
+function setAlign(sheet, headers) {
+  const fuc = () => {
+    headers.forEach(({ hAlign, vAlign }, index) => {
+      sheetCommonObj.setAreaAlign(sheet.getRange(-1, index, -1, 1), hAlign, vAlign)
+    });
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+function setFormatter(sheet, headers) {
+  const fuc = () => {
+    headers.forEach(({ formatter }, index) => {
+      if (formatter) {
+        sheet.setFormatter(-1, index, formatter);
+      }
+    });
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+function initSheet(dom, setting) {
+  const workBook = sheetCommonObj.buildSheet(dom, setting);
+  const sheet = workBook.getSheet(0);
+  setAlign(sheet, setting.header);
+  setFormatter(sheet, setting.header);
+  return workBook;
+}
+
+function showData(sheet, data, headers, emptyRows) {
+  /* const style = new GC.Spread.Sheets.Style();
+  style.wordWrap = true; */
+  const fuc = () => {
+    sheet.setRowCount(data.length);
+    data.forEach((item, row) => {
+      headers.forEach(({ dataCode }, col) => {
+        //sheet.setStyle(row, col, style, GC.Spread.Sheets.SheetArea.viewport);
+        sheet.setValue(row, col, item[dataCode] || '');
+      });
+      sheet.autoFitRow(row);
+    });
+    if (emptyRows) {
+      sheet.addRows(data.length, emptyRows);
+    }
+    //sheet.autoFitRow(0);
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+// 获取当前表中行数据
+function getRowData(sheet, row, headers) {
+  const item = {};
+  headers.forEach(({ dataCode }, index) => {
+    const value = sheet.getValue(row, index) || '';
+    if (value) {
+      item[dataCode] = value;
+    }
+  });
+  return item;
+}
+
+// 获取表数据和缓存数据的不同数据
+function getRowDiffData(curRowData, cacheRowData, headers) {
+  let item = null;
+  headers.forEach(({ dataCode }) => {
+    const curValue = curRowData[dataCode];
+    const cacheValue = cacheRowData[dataCode];
+    if (!cacheValue && !curValue) {
+      return;
+    }
+    if (cacheValue !== curValue) {
+      if (!item) {
+        item = {};
+      }
+      item[dataCode] = curValue || '';
+    }
+  });
+  return item;
+}
+
+const UpdateType = {
+  UPDATE: 'update',
+  DELETE: 'delete',
+  CREATE: 'create',
+};
+
+const TIME_OUT = 20000;
+
+const SUMMARY_BOOK = (() => {
+  const locked = lockUtil.getLocked();
+  const setting = {
+    header: [
+      { headerName: '主从对应码', headerWidth: 200, dataCode: 'code', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '别名编码', headerWidth: 100, dataCode: 'classCode', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '计算式', headerWidth: 100, dataCode: 'expString', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '材料名称', headerWidth: 350, dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '规格型号', headerWidth: 200, dataCode: 'specs', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '单位', headerWidth: 80, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+    ],
+  };
+  // 初始化表格
+  const workBook = initSheet($('#summary-spread')[0], setting);
+  workBook.options.allowUserDragDrop = true;
+  workBook.options.allowUserDragFill = true;
+  lockUtil.lockSpreads([workBook], locked);
+  const sheet = workBook.getSheet(0);
+
+  // 当前数据缓存
+  const cache = [];
+  // 清空
+  function clear() {
+    cache.length = 0;
+    sheet.setRowCount(0);
+  }
+
+  let loading = false;
+  // 当前页面数据总量
+  let totalCount = 0;
+  // 当前页数
+  let curPage = 0;
+
+  // 搜索内容
+  let searchStr = '';
+
+  // 加载分页数据
+  const loadPageData = async (page) => {
+    curPage = page;
+    loading = true;
+    const data = await ajaxPost('/priceInfoSummary/getPagingData', { page, searchStr, pageSize: 100 }, TIME_OUT);
+    totalCount = data.totalCount;
+    cache.push(...data.items);
+    showData(sheet, cache, setting.header, 5);
+    loading = false;
+  }
+
+  // 搜索
+  const handleSearch = (val) => {
+    if (val) {
+      // 处理特殊字符,否则正则搜不到
+      searchStr = val
+        .replace(/\\/g, '\\\\')
+        .replace(/\[/g, '\\[')
+        .replace(/\]/g, '\\]')
+        .replace(/\(/g, '\\(')
+        .replace(/\)/g, '\\)')
+        .replace(/\+/g, '\\+')
+        .replace(/\?/g, '\\?')
+        .replace(/\*/g, '\\*')
+        .replace(/\$/g, '\\$')
+        .replace(/\^/g, '\\^')
+    } else {
+      searchStr = '';
+    }
+    clear();
+    loadPageData(0);
+  }
+
+
+  // 无限滚动加载
+  const onTopRowChanged = (sender, args) => {
+    const bottomRow = args.sheet.getViewportBottomRow(1);
+    console.log(cache.length, totalCount, loading, cache.length - 1, bottomRow)
+    if (cache.length >= totalCount || loading) {
+      return;
+    }
+    if (cache.length - 1 <= bottomRow) {
+      loadPageData(curPage + 1, searchStr);
+    }
+  }
+  sheet.bind(GC.Spread.Sheets.Events.TopRowChanged, _.debounce(onTopRowChanged, 100));
+
+  // 编辑处理
+  async function handleEdit(changedCells) {
+    $.bootstrapLoading.start();
+    const postData = []; // 请求用
+    // 更新缓存用
+    const updateData = [];
+    const deleteData = [];
+    const insertData = [];
+    try {
+      changedCells.forEach(({ row }) => {
+        if (cache[row]) {
+          const rowData = getRowData(sheet, row, setting.header);
+          if (Object.keys(rowData).length) { // 还有数据,更新
+            const diffData = getRowDiffData(rowData, cache[row], setting.header);
+            if (diffData) {
+              postData.push({ type: UpdateType.UPDATE, ID: cache[row].ID, data: diffData });
+              updateData.push({ row, data: diffData });
+            }
+          } else { // 该行无数据了,删除
+            postData.push({ type: UpdateType.DELETE, ID: cache[row].ID });
+            deleteData.push(cache[row]);
+          }
+        } else { // 新增
+          const rowData = getRowData(sheet, row, setting.header);
+          if (Object.keys(rowData).length) {
+            rowData.ID = uuid.v1();
+            postData.push({ type: UpdateType.CREATE, data: rowData });
+            insertData.push(rowData);
+          }
+        }
+      });
+      if (postData.length) {
+        await ajaxPost('/priceInfoSummary/editSummaryData', { postData }, 1000 * 60 * 2);
+        // 更新缓存,先更新然后删除,最后再新增,防止先新增后缓存数据的下标与更新、删除数据的下标对应不上
+        updateData.forEach(item => {
+          Object.assign(cache[item.row], item.data);
+        });
+        deleteData.forEach(item => {
+          const index = cache.indexOf(item);
+          if (index >= 0) {
+            cache.splice(index, 1);
+          }
+        });
+        insertData.forEach(item => cache.push(item));
+        if (deleteData.length || insertData.length) {
+          showData(sheet, cache, setting.header, 5);
+        }
+      }
+    } catch (err) {
+      // 恢复各单元格数据
+      showData(sheet, cache, setting.header, 5);
+    }
+    $.bootstrapLoading.end();
+  }
+  sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
+    const changedCells = [{ row: info.row }];
+    handleEdit(changedCells);
+  });
+  sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
+    const changedRows = [];
+    let preRow;
+    info.changedCells.forEach(({ row }) => {
+      if (row !== preRow) {
+        changedRows.push({ row });
+      }
+      preRow = row;
+    });
+    handleEdit(changedRows);
+  });
+
+  const initData = async () => {
+    try {
+      $.bootstrapLoading.start();
+      await loadPageData(0);
+    } catch (error) {
+      console.log(error);
+      alert(error.message);
+    }
+    $.bootstrapLoading.end();
+  }
+
+  initData();
+
+  return {
+    sheet,
+    handleSearch,
+  }
+})()

+ 1 - 1
web/maintain/ration_repository/anzhuang.html

@@ -13,7 +13,7 @@
     <link rel="stylesheet" href="/lib/jquery-contextmenu/jquery.contextMenu.css">
 </head>
 
-<body>
+<body style="overflow: hidden;">
     <div class="header">
         <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 ">
             <span class="header-logo px-2">Smartcost</span>

+ 10 - 2
web/maintain/ration_repository/dinge.html

@@ -52,6 +52,9 @@
                 <li class="nav-item">
                     <a class="nav-link px-3" href="javascript:void(0);" id="zmhs">子目换算</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link px-3" href="javascript:void(0);" id="loss">损耗率</a>
+                </li>
             </ul>
         </nav>
     </div>
@@ -203,15 +206,19 @@
                             </div>
                         </div>
                     </div>
-                    <div class="main-side p-0 main-side-right" id="zmhsContent" style="width: 25%; display: none">
+                    <div class="main-side p-0 main-side-right" id="rightContent" style="width: 25%; display: none">
                         <div class="resize-x" id="slideResizeRight"></div>
-                        <div style="width: 99%; float: left">
+                        <div style="width: 99%; float: left" id="zmhsWrap">
                             <div class="main-data-top-fluid" id="mainSpread">
                             </div>
                             <div class="resize-y" id="zmhsAdjResize"></div>
                             <div class="bottom-content" id="contentSpread">
                             </div>
                         </div>
+                        <div style="width: 99%; float: left" id="lossWrap">
+                            <div class="main-data" id="lossSpread">
+                            </div>
+                        </div>
                     </div>
                 </div>
             </div>
@@ -713,6 +720,7 @@
         <script type="text/javascript" src="/public/web/QueryParam.js"></script>
         <script type="text/javascript" src="/public/web/storageUtil.js"></script>
         <script type="text/javascript" src="/web/maintain/ration_repository/js/coe.js"></script>
+        <script type="text/javascript" src="/web/maintain/ration_repository/js/lossRate.js"></script>
         <script src="/lib/codemirror/codemirror.js"></script>
         <script src="/lib/codemirror/xml.js"></script>
         <script src="/web/common/js/uploadImg.js"></script>

+ 193 - 159
web/maintain/ration_repository/js/coe.js

@@ -2,24 +2,27 @@
  * Created by CSL on 2017-05-18.
  */
 
-$(document).ready(function () {
-    function refreshALlWorkBook() {
-        if (sectionTreeObj.workBook) {
-            sectionTreeObj.workBook.refresh();
-        }
-        if (rationOprObj.workBook) {
-            rationOprObj.workBook.refresh();
-        }
-        if (rationGLJOprObj.sheet && rationGLJOprObj.sheet.getParent()) {
-            rationGLJOprObj.sheet.getParent().refresh();
-        }
-        if (coeOprObj.workBook) {
-            coeOprObj.workBook.refresh();
-        }
-        if (gljAdjOprObj.workBook) {
-            gljAdjOprObj.workBook.refresh();
-        }
+function refreshALlWorkBook() {
+    if (sectionTreeObj.workBook) {
+        sectionTreeObj.workBook.refresh();
+    }
+    if (rationOprObj.workBook) {
+        rationOprObj.workBook.refresh();
+    }
+    if (rationGLJOprObj.sheet && rationGLJOprObj.sheet.getParent()) {
+        rationGLJOprObj.sheet.getParent().refresh();
+    }
+    if (coeOprObj.workBook) {
+        coeOprObj.workBook.refresh();
+    }
+    if (gljAdjOprObj.workBook) {
+        gljAdjOprObj.workBook.refresh();
     }
+    if (typeof lossObj !== 'undefined' && lossObj.workBook) {
+        lossObj.workBook.refresh();
+    }
+}
+$(document).ready(function () {
     //定额章节树与定额表
     let leftElesObj = {};
     leftElesObj.module = moduleName;
@@ -27,8 +30,8 @@ $(document).ready(function () {
     leftElesObj.parent = $('#dataRow');
     leftElesObj.left = $('#leftContent');
     leftElesObj.right = $('#mainContent');
-    let maxEval = `$('#zmhsContent').is(':visible') ? $('#dataRow').width() - $('#zmhsContent').width() - 300 : $('#dataRow').width()  - 300`;
-    SlideResize.horizontalSlide(leftElesObj, {min: 300, max: maxEval}, function () {
+    let maxEval = `$('#rightContent').is(':visible') ? $('#dataRow').width() - $('#rightContent').width() - 300 : $('#dataRow').width()  - 300`;
+    SlideResize.horizontalSlide(leftElesObj, { min: 300, max: maxEval }, function () {
         refreshALlWorkBook();
         sectionTreeObj.loadRateWidth();
     });
@@ -48,14 +51,17 @@ $(document).ready(function () {
     rightElesObj.resize = $('#slideResizeRight');
     rightElesObj.parent = $('#dataRow');
     rightElesObj.left = $('#mainContent');
-    rightElesObj.right = $('#zmhsContent');
+    rightElesObj.right = $('#rightContent');
     let maxEvalRight = `$('#dataRow').width() - $('#leftContent').width() - 200`;
-    SlideResize.horizontalSlide(rightElesObj, {min: 200, max: maxEvalRight}, function () {
+    rightElesObj.getOtherFunc = (index) => {
+        return index ? 0 : $('#zmhs').hasClass('active') ? 1 : 2;
+    };
+    SlideResize.horizontalSlide(rightElesObj, { min: 200, max: maxEvalRight }, function () {
         refreshALlWorkBook();
     });
     //设置水平拖动条的宽度
     //@param {Object dom}resize滚动条
-    function setResizeWidth (resize) {
+    function setResizeWidth(resize) {
         const fixedWidth = 10;
         //跟滚动条同层的其他节点
         let bros = resize.parent().children();
@@ -76,8 +82,8 @@ $(document).ready(function () {
         //宽度比例localstorage key
         let leftContentKey = `${moduleName}${$('#leftContent').attr('id')}Width`,
             mainContentKey = `${moduleName}${$('#mainContent').attr('id')}Width`,
-            zmhsContentKey = `${moduleName}${$('#zmhsContent').attr('id')}Width`;
-        let zmhsWidth = getLocalCache(zmhsContentKey) ? getLocalCache(zmhsContentKey) :$('#zmhsContent')[0].style.width,
+            zmhsContentKey = `${moduleName}${$('#rightContent').attr('id')}Width`;
+        let zmhsWidth = getLocalCache(zmhsContentKey) ? getLocalCache(zmhsContentKey) : $('#rightContent')[0].style.width,
             mainContentWidth = $('#mainContent')[0].style.width,
             leftContentWidth;
         zmhsWidth = parseFloat(zmhsWidth.replace('%', ''));
@@ -98,7 +104,7 @@ $(document).ready(function () {
         setLocalCache(leftContentKey, `${leftContentWidth}%`);
         $('#mainContent').css('width', `${mainContentWidth}%`);
         setLocalCache(mainContentKey, `${mainContentWidth}%`);
-        $('#zmhsContent').css('width', `${zmhsWidth}%`);
+        $('#rightContent').css('width', `${zmhsWidth}%`);
         setLocalCache(zmhsContentKey, `${zmhsWidth}%`);
         let resizes = [$('#slideResizeLeft'), $('#slideResizeRight')];
         for (let resize of resizes) {
@@ -107,18 +113,23 @@ $(document).ready(function () {
         sectionTreeObj.loadRateWidth();
     }
     $('#zmhs').click(function () {
-        if(!$(this).hasClass('active')){
+        if (!$(this).hasClass('active')) {
+            $('#loss').removeClass('active');
             $(this).addClass('active');
             refreshAfterZmhs(true);
-            $('#zmhsContent').show();
-            if(!coeOprObj.workBook){
+            $('#zmhsWrap').show();
+            $('#lossWrap').hide();
+            $('#rightContent').show();
+            if (!coeOprObj.workBook) {
                 pageObj.initPage();
             }
             refreshALlWorkBook();
         } else {
             $(this).removeClass('active');
             refreshAfterZmhs(false);
-            $('#zmhsContent').hide();
+            $('#rightContent').hide();
+            $('#zmhsWrap').hide();
+            $('#lossWrap').hide();
             refreshALlWorkBook();
         }
     });
@@ -167,14 +178,14 @@ function loadZmhsAdjSize(resizeObj) {
     });
 }
 var pageObj = {
-    initPage: function (){
+    initPage: function () {
         coeOprObj.buildSheet($('#mainSpread')[0]);
         gljAdjOprObj.buildSheet($('#contentSpread')[0]);
         coeOprObj.getCoeList();
         gljAdjOprObj.getGljItemsOcc();
         lockUtil.lockSpreads([coeOprObj.workBook, gljAdjOprObj.workBook], locked);
     },
-    showData: function(sheet, setting, data) {
+    showData: function (sheet, setting, data) {
         let me = pageObj, ch = GC.Spread.Sheets.SheetArea.viewport;
         sheet.suspendPaint();
         sheet.suspendEvent();
@@ -184,16 +195,16 @@ var pageObj = {
             var hAlign = "left", vAlign = "center";
             if (setting.header[col].hAlign) {
                 hAlign = setting.header[col].hAlign;
-            } else if (setting.header[col].dataType !== "String"){
+            } else if (setting.header[col].dataType !== "String") {
                 hAlign = "right";
             }
-            if(setting.header[col].readOnly){
+            if (setting.header[col].readOnly) {
                 sheet.getRange(-1, col, -1, 1).locked(true);
             }
-            else{
+            else {
                 sheet.getRange(-1, col, -1, 1).locked(false);
             }
-            vAlign = setting.header[col].vAlign?setting.header[col].vAlign:vAlign;
+            vAlign = setting.header[col].vAlign ? setting.header[col].vAlign : vAlign;
             sheetCommonObj.setAreaAlign(sheet.getRange(-1, col, -1, 1), hAlign, vAlign);
             if (setting.header[col].formatter) {
                 sheet.setFormatter(-1, col, setting.header[col].formatter, GC.Spread.Sheets.SheetArea.viewport);
@@ -216,17 +227,17 @@ let coeOprObj = {
     currentMaxNo: null,
     setting: {
         header: [
-            {headerName:"编号", headerWidth:50, dataCode:"serialNo", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false},
-            {headerName:"名称", headerWidth:200, dataCode:"name", dataType: "String", hAlign: "left", vAlign: "center", readOnly: false},
-            {headerName:"内容", headerWidth:150, dataCode:"content", dataType: "String", hAlign: "left", vAlign: "center", readOnly: false},
-            {headerName:"原人材机编码", headerWidth:90, dataCode:"original_code", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false},
-            {headerName:"可选人材机编码", headerWidth:150, dataCode:"option_codes", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false}
+            { headerName: "编号", headerWidth: 50, dataCode: "serialNo", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false },
+            { headerName: "名称", headerWidth: 200, dataCode: "name", dataType: "String", hAlign: "left", vAlign: "center", readOnly: false },
+            { headerName: "内容", headerWidth: 150, dataCode: "content", dataType: "String", hAlign: "left", vAlign: "center", readOnly: false },
+            { headerName: "原人材机编码", headerWidth: 90, dataCode: "original_code", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false },
+            { headerName: "可选人材机编码", headerWidth: 150, dataCode: "option_codes", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false }
         ]
     },
     buildSheet: function (container) {
         let me = coeOprObj;
         me.workBook = sheetCommonObj.buildSheet(container, me.setting, 30);
-        sheetCommonObj.bindEscKey(me.workBook, [{sheet: me.workBook.getSheet(0), editStarting: null, editEnded: me.onEditEnded}]);
+        sheetCommonObj.bindEscKey(me.workBook, [{ sheet: me.workBook.getSheet(0), editStarting: null, editEnded: me.onEditEnded }]);
         me.workSheet = me.workBook.getSheet(0);
         me.workSheet.options.isProtected = true;
         me.onDelOpr(me.workBook, me.setting);
@@ -237,7 +248,7 @@ let coeOprObj = {
         me.workBook.bind(GC.Spread.Sheets.Events.ClipboardPasted, me.onClipboardPasted);
     },
     onSelectionChanged: function (sender, info) {
-        if(info.oldSelections.length === 0 && info.newSelections.length > 0 || info.oldSelections[0].row !== info.newSelections[0].row){
+        if (info.oldSelections.length === 0 && info.newSelections.length > 0 || info.oldSelections[0].row !== info.newSelections[0].row) {
             const row = info.newSelections[0].row;
             coeOprObj.coeSelInit(row);
         }
@@ -245,11 +256,11 @@ let coeOprObj = {
     coeSelInit: function (row) {
         const me = coeOprObj;
         const adj = gljAdjOprObj;
-        if(row < me.currentCoeList.length){
+        if (row < me.currentCoeList.length) {
             me.currentCoe = me.currentCoeList[row];
             adj.currentGljAdjList = me.currentCoe.coes;
             adj.buildDynamicComboBox(adj.workSheet);
-        } else{
+        } else {
             me.currentCoe = null;
             adj.currentGljAdjList = [];
             adj.buildBaseCell(adj.workSheet);
@@ -261,42 +272,42 @@ let coeOprObj = {
     },
     onEditEnded: function (sender, args) {
         let me = coeOprObj, addArr = [], updateArr = [], dataCode = me.setting.header[args.col].dataCode;
-        if(args.editingText && args.editingText.toString().trim().length > 0){
+        if (args.editingText && args.editingText.toString().trim().length > 0) {
             let inputT = args.editingText.toString().trim();
             //update
-            if(args.row < me.currentCoeList.length){
+            if (args.row < me.currentCoeList.length) {
                 let updateObj = me.currentCoeList[args.row];
-                if(updateObj[dataCode] != inputT){
-                    if(dataCode === 'serialNo'){
-                        if(me.isInt(inputT) && !me.hasTisNo(me.currentCoeList, inputT)){
+                if (updateObj[dataCode] != inputT) {
+                    if (dataCode === 'serialNo') {
+                        if (me.isInt(inputT) && !me.hasTisNo(me.currentCoeList, inputT)) {
                             me.currentMaxNo = me.currentMaxNo >= inputT ? me.currentMaxNo : inputT;
                             updateObj[dataCode] = inputT;
                             updateArr.push(updateObj);
                             me.save([], updateArr, [], true);
                         }
-                        else if(!me.isInt(inputT)){
+                        else if (!me.isInt(inputT)) {
                             alert('编号只能为整数!');
                             args.sheet.setValue(args.row, args.col, updateObj[dataCode] + '');
                         }
-                        else if(me.hasTisNo(me.currentCoeList, inputT)){
+                        else if (me.hasTisNo(me.currentCoeList, inputT)) {
                             alert('该编号已存在!');
                             args.sheet.setValue(args.row, args.col, updateObj[dataCode] + '');
                         }
                     }
                     else {
                         updateObj[dataCode] = inputT;
-                        me.setOptionList(dataCode,inputT,updateObj);
+                        me.setOptionList(dataCode, inputT, updateObj);
                         updateArr.push(updateObj);
                         me.save([], updateArr, [], true);
                     }
                 }
             }
             //insert
-            else{
+            else {
                 let newCoe = {};
                 newCoe.libID = pageOprObj.rationLibId;
-                if(dataCode === 'serialNo'){
-                    if(me.isInt(inputT) && !me.hasTisNo(me.currentCoeList, inputT)){
+                if (dataCode === 'serialNo') {
+                    if (me.isInt(inputT) && !me.hasTisNo(me.currentCoeList, inputT)) {
                         me.currentMaxNo = me.currentMaxNo >= inputT ? me.currentMaxNo : inputT;
                         newCoe[dataCode] = inputT;
                         addArr.push(newCoe);
@@ -304,19 +315,19 @@ let coeOprObj = {
                             me.updateCurrentCoeList(result);
                         });
                     }
-                    else if(!me.isInt(inputT)){
+                    else if (!me.isInt(inputT)) {
                         args.sheet.setValue(args.row, args.col, '');
                         alert('编号只能为整数!');
                     }
-                    else if(me.hasTisNo(me.currentCoeList, inputT)){
+                    else if (me.hasTisNo(me.currentCoeList, inputT)) {
                         args.sheet.setValue(args.row, args.col, '');
                         alert('该编号已存在!');
                     }
                 }
-                else{
+                else {
                     newCoe.serialNo = ++me.currentMaxNo;
                     newCoe[dataCode] = inputT;
-                    me.setOptionList(dataCode,inputT,newCoe);
+                    me.setOptionList(dataCode, inputT, newCoe);
                     addArr.push(newCoe);
                     me.save(addArr, [], [], true, function (result) {
                         me.updateCurrentCoeList(result);
@@ -325,21 +336,21 @@ let coeOprObj = {
             }
         }
     },
-    setOptionList:function (dataCode,inputT,obj) {
-        if(dataCode == "option_codes"){//所选人材的情况,要获取人材机下拉列表
+    setOptionList: function (dataCode, inputT, obj) {
+        if (dataCode == "option_codes") {//所选人材的情况,要获取人材机下拉列表
             inputT = inputT.replace(/[\s\r\n]/g, "")//去掉空格换行等字符
             let optionList = [];
             let options = inputT.split("|");
-            for(let code of options){
-                let name = gljAdjOprObj.getGljName(code,gljAdjOprObj.gljList,true);
-                if(name) optionList.push({text:name,value:code});
+            for (let code of options) {
+                let name = gljAdjOprObj.getGljName(code, gljAdjOprObj.gljList, true);
+                if (name) optionList.push({ text: name, value: code });
             }
             obj.option_list = optionList;
         }
     },
     onClipboardPasting: function (sender, info) {
         let me = coeOprObj, maxCol = info.cellRange.col + info.cellRange.colCount - 1;
-        if(maxCol > me.setting.header.length){
+        if (maxCol > me.setting.header.length) {
             info.cancel = true;
         }
     },
@@ -347,14 +358,14 @@ let coeOprObj = {
         let me = coeOprObj, addArr = [], updateArr = [];
         let items = sheetCommonObj.analyzePasteData(me.setting, info);
         let uniqItems = me.makeUniqItems(items);
-        for(let i = 0, len = uniqItems.length; i < len; i++){
+        for (let i = 0, len = uniqItems.length; i < len; i++) {
             let row = i + info.cellRange.row;
             //update
-            if(row < me.currentCoeList.length){
+            if (row < me.currentCoeList.length) {
                 let updateObj = me.currentCoeList[row];
-                for(let attr in uniqItems[i]){
-                    if(attr === 'serialNo'){
-                        if(me.isInt(uniqItems[i][attr]) && !me.hasTisNo(me.currentCoeList, uniqItems[i][attr])){
+                for (let attr in uniqItems[i]) {
+                    if (attr === 'serialNo') {
+                        if (me.isInt(uniqItems[i][attr]) && !me.hasTisNo(me.currentCoeList, uniqItems[i][attr])) {
                             me.currentMaxNo = me.currentMaxNo >= uniqItems[i][attr] ? me.currentMaxNo : uniqItems[i][attr];
                             updateObj[attr] = uniqItems[i][attr];
                         }
@@ -367,7 +378,7 @@ let coeOprObj = {
             }
             //insert
             else {
-                if(typeof uniqItems[i].serialNo !== 'undefined' && uniqItems[i] && me.isInt(uniqItems[i].serialNo) && !me.hasTisNo(me.currentCoeList, uniqItems[i].serialNo)){
+                if (typeof uniqItems[i].serialNo !== 'undefined' && uniqItems[i] && me.isInt(uniqItems[i].serialNo) && !me.hasTisNo(me.currentCoeList, uniqItems[i].serialNo)) {
                     me.currentMaxNo = me.currentMaxNo >= uniqItems[i].serialNo ? me.currentMaxNo : uniqItems[i].serialNo;
                 }
                 else {
@@ -377,7 +388,7 @@ let coeOprObj = {
                 addArr.push(uniqItems[i]);
             }
         }
-        if(addArr.length > 0 || updateArr.length > 0){
+        if (addArr.length > 0 || updateArr.length > 0) {
             me.save(addArr, updateArr, [], true, function (result) {
                 me.updateCurrentCoeList(result);
             });
@@ -390,21 +401,21 @@ let coeOprObj = {
             let sheet = workBook.getSheet(0);
             let sels = sheet.getSelections();
             let idx = sels[0].row;
-            for(let i = 0, len = sels.length; i < len; i++){
-                if(idx > sels[i].row){
+            for (let i = 0, len = sels.length; i < len; i++) {
+                if (idx > sels[i].row) {
                     idx = sels[i].row;
                 }
-                if(sels[i].colCount === setting.header.length){//can del
-                    for(let r = 0, rLen = sels[i].rowCount; r < rLen; r++){
+                if (sels[i].colCount === setting.header.length) {//can del
+                    for (let r = 0, rLen = sels[i].rowCount; r < rLen; r++) {
                         let row = sels[i].row + r;
-                        if(row < me.currentCoeList.length){
-                            deleteArr.push({libID: me.currentCoeList[row].libID, ID: me.currentCoeList[row].ID});
+                        if (row < me.currentCoeList.length) {
+                            deleteArr.push({ libID: me.currentCoeList[row].libID, ID: me.currentCoeList[row].ID });
                         }
                     }
                     me.currentCoeList.splice(sels[i].row, sels[i].rowCount);
                 }
             }
-            if(deleteArr.length > 0){
+            if (deleteArr.length > 0) {
                 me.save([], [], deleteArr, true);
                 me.currentCoe = typeof me.currentCoeList[idx] !== 'undefined' ? me.currentCoeList[idx] : null;
                 that.currentGljAdjList = me.currentCoe ? me.currentCoe.coes : [];
@@ -419,18 +430,18 @@ let coeOprObj = {
         const me = this;
         $.contextMenu({
             selector: '#mainSpread',
-            build: function($triggerElement, e){
+            build: function ($triggerElement, e) {
                 //控制允许右键菜单在哪个位置出现
                 const target = SheetDataHelper.safeRightClickSelection($triggerElement, e, me.workBook);
                 const sheet = me.workBook.getSheet(0);
-                if(target.hitTestType === 3){//在表格内&& typeof target.row !== 'undefined' && typeof target.col !== 'undefined'
-                    if(typeof target.row !== 'undefined'){
+                if (target.hitTestType === 3) {//在表格内&& typeof target.row !== 'undefined' && typeof target.col !== 'undefined'
+                    if (typeof target.row !== 'undefined') {
                         //控制按钮是否可用
                         sheet.setActiveCell(target.row, target.col);
                         me.coeSelInit(target.row);
                     }
                     return {
-                        callback: function(){},
+                        callback: function () { },
                         items: {
                             getReference: {
                                 name: '查找引用',
@@ -446,7 +457,7 @@ let coeOprObj = {
                         }
                     };
                 }
-                else{
+                else {
                     return false;
                 }
             }
@@ -478,20 +489,20 @@ let coeOprObj = {
     //粘贴的数据,编号唯一化,去除编号重复的项
     makeUniqItems: function (items) {
         let rst = [];
-        for(let i = 0, len = items.length; i < len; i++){
-            if(typeof items[i].serialNo !== 'undefined' && items[i].serialNo){
-                if(rst.length === 0){
+        for (let i = 0, len = items.length; i < len; i++) {
+            if (typeof items[i].serialNo !== 'undefined' && items[i].serialNo) {
+                if (rst.length === 0) {
                     rst.push(items[i]);
                 }
-                else{
+                else {
                     let isExist = false;
-                    for(let j = 0, jLen = rst.length; j < jLen; j++){
-                        if(items[i].serialNo === rst[j].serialNo){
+                    for (let j = 0, jLen = rst.length; j < jLen; j++) {
+                        if (items[i].serialNo === rst[j].serialNo) {
                             isExist = true;
                             break;
                         }
                     }
-                    if(!isExist){
+                    if (!isExist) {
                         rst.push(items[i]);
                     }
                 }
@@ -507,8 +518,8 @@ let coeOprObj = {
     },
     hasTisNo: function (coeList, newSerialNo) {
         let rst = false;
-        for(let i = 0, len = coeList.length; i < len; i++){
-            if(coeList[i].serialNo == newSerialNo){
+        for (let i = 0, len = coeList.length; i < len; i++) {
+            if (coeList[i].serialNo == newSerialNo) {
                 rst = true;
                 break;
             }
@@ -517,15 +528,15 @@ let coeOprObj = {
     },
     updateCurrentCoeList: function (newCoeList) {
         let me = coeOprObj;
-        if(newCoeList){
+        if (newCoeList) {
             me.currentCoeList = me.currentCoeList.concat(newCoeList);
         }
     },
     sortCoeList: function (coeList) {
         coeList.sort(function (a, b) {
             let rst = 0;
-            if(a.serialNo > b.serialNo) rst = 1;
-            else if(a.serialNo < b.serialNo) rst = -1;
+            if (a.serialNo > b.serialNo) rst = 1;
+            else if (a.serialNo < b.serialNo) rst = -1;
             return rst;
         });
     },
@@ -534,19 +545,19 @@ let coeOprObj = {
         $.ajax({
             type: 'post',
             url: '/rationRepository/api/getCoeList',
-            data: {libID: pageOprObj.rationLibId},
+            data: { libID: pageOprObj.rationLibId },
             dataType: 'json',
-            timeout:20000,
+            timeout: 20000,
             success: function (result) {
-                if(!result.error){
+                if (!result.error) {
                     me.currentCoeList = result.data;
                     me.sortCoeList(me.currentCoeList);
-                    me.currentMaxNo =  me.currentCoeList.length > 0 ? me.currentCoeList[me.currentCoeList.length - 1].serialNo : 0;
+                    me.currentMaxNo = me.currentCoeList.length > 0 ? me.currentCoeList[me.currentCoeList.length - 1].serialNo : 0;
                     pageObj.showData(me.workSheet, me.setting, me.currentCoeList);
                     me.workSheet.clearSelection();
                 }
             },
-            error:function(err){
+            error: function (err) {
                 alert("内部程序错误!");
             }
         });
@@ -554,19 +565,19 @@ let coeOprObj = {
     save: function (addArr, updateArr, deleteArr, refresh, callback) {
         let me = coeOprObj;
         $.ajax({
-            type:"POST",
-            url:"api/saveCoeList",
-            data: {data: JSON.stringify({addArr: addArr, updateArr: updateArr, deleteArr: deleteArr})},
-            dataType:"json",
-            timeout:5000,
-            success:function(result){
+            type: "POST",
+            url: "api/saveCoeList",
+            data: { data: JSON.stringify({ addArr: addArr, updateArr: updateArr, deleteArr: deleteArr }) },
+            dataType: "json",
+            timeout: 5000,
+            success: function (result) {
                 if (result.error) {
                     alert(result.message);
-                } else{
-                    if(callback){
-                        if(result.message === 'mixed'){
-                            for(let i = 0, len = result.data.length; i < len; i++){
-                                if(result.data[i][0] === 'addSc'){
+                } else {
+                    if (callback) {
+                        if (result.message === 'mixed') {
+                            for (let i = 0, len = result.data.length; i < len; i++) {
+                                if (result.data[i][0] === 'addSc') {
                                     result.data = result.data[i][1];
                                     break;
                                 }
@@ -574,20 +585,39 @@ let coeOprObj = {
                         }
                         callback(result.data);
                     }
-                    if(refresh){
+                    if (refresh) {
                         me.sortCoeList(me.currentCoeList);
                         me.currentMaxNo = me.currentCoeList.length > 0 ? me.currentCoeList[me.currentCoeList.length - 1].serialNo : 0;
                         pageObj.showData(me.workSheet, me.setting, me.currentCoeList);
                     }
                 }
             },
-            error:function(err){
+            error: function (err) {
                 alert("内部程序错误!");
             }
         });
     }
 };
 
+// 验证数量的有效性:可以输入数值,也可以输入表达式,eg: [人材机编码]*1.5+1
+const validateAmount = (amountStr) => {
+    if (!amountStr) {
+        return true;
+    }
+    let str = amountStr.replace(/\s/g, '');
+    if (!str) {
+        return true;
+    }
+    try {
+        str = amountStr.replace(/\s/g, '').replace(/\[\d+\]/g, '0');
+        eval(str);
+
+    } catch (error) {
+        return false;
+    }
+    return true;
+}
+
 let gljAdjOprObj = {
     workBook: null,
     workSheet: null,
@@ -596,28 +626,28 @@ let gljAdjOprObj = {
 
     setting: {
         header: [
-            {headerName:"调整类型", headerWidth:80, dataCode:"coeType", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false},
-            {headerName:"人材机编码", headerWidth:80, dataCode:"gljCode", dataType: "String", formatter: '@', hAlign: "center", vAlign: "center", readOnly: false},
+            { headerName: "调整类型", headerWidth: 80, dataCode: "coeType", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false },
+            { headerName: "人材机编码", headerWidth: 80, dataCode: "gljCode", dataType: "String", formatter: '@', hAlign: "center", vAlign: "center", readOnly: false },
             // readOnly: true --> false,需要兼容粘贴列包含只读项
-            {headerName:"名称", headerWidth:100, dataCode:"gljName", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false},
-            {headerName:"操作符", headerWidth:60, dataCode:"operator", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false},
-            {headerName:"数量", headerWidth:80, dataCode:"amount", dataType: "String", hAlign: "center", vAlign: "center" , readOnly: false},
-            {headerName:"替换为编码", headerWidth:80, dataCode:"replaceCode", dataType: "String", formatter: '@', hAlign: "center", vAlign: "center", readOnly: false},
+            { headerName: "名称", headerWidth: 100, dataCode: "gljName", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false },
+            { headerName: "操作符", headerWidth: 60, dataCode: "operator", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false },
+            { headerName: "数量", headerWidth: 80, dataCode: "amount", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false },
+            { headerName: "替换为编码", headerWidth: 80, dataCode: "replaceCode", dataType: "String", formatter: '@', hAlign: "center", vAlign: "center", readOnly: false },
             // readOnly: true --> false
-            {headerName:"替换为名称", headerWidth:100, dataCode:"replaceName", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false}
+            { headerName: "替换为名称", headerWidth: 100, dataCode: "replaceName", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false }
         ],
         comboItems: {
             //调整类型下拉菜单
-            coeType: ['定额', '人工', '材料', '机械', '主材', '设备', '单个工料机','替换人材机',"所选人材机"],
+            coeType: ['定额', '人工', '材料', '机械', '主材', '设备', '单个工料机', '替换人材机', "所选人材机"],
             //操作符下拉菜单
-            operator: ['+', '-', '*', '/', '=','+*','-*']
+            operator: ['+', '-', '*', '/', '=', '+*', '-*']
         }
     },
     buildSheet: function (container) {
         let me = gljAdjOprObj;
         me.workBook = sheetCommonObj.buildSheet(container, me.setting, 3);
         me.workSheet = me.workBook.getSheet(0);
-        sheetCommonObj.bindEscKey(me.workBook, [{sheet: me.workBook.getSheet(0), editStarting: me.onEditStart, editEnded: me.onEditEnded}]);
+        sheetCommonObj.bindEscKey(me.workBook, [{ sheet: me.workBook.getSheet(0), editStarting: me.onEditStart, editEnded: me.onEditEnded }]);
         me.workSheet.options.isProtected = true;
         me.onDelOpr(me.workBook, me.setting);
         me.workSheet.clearSelection();
@@ -691,36 +721,36 @@ let gljAdjOprObj = {
     onEditEnded: function (sender, args) {
         let me = gljAdjOprObj, isUpdate = false,
             dataCode = me.setting.header[args.col].dataCode;
-        if(args.editingText && args.editingText.toString().trim().length > 0){
-            if(dataCode === 'amount' &&  isNaN(args.editingText)){
-                alert("只能输入数值!");
+        if (args.editingText && args.editingText.toString().trim().length > 0) {
+            if (dataCode === 'amount' && !validateAmount(args.editingText)) {
+                alert("请输入数值或者表达式,如:[人材机编码]*0.3+1");
                 args.sheet.setValue(args.row, args.col, typeof me.currentGljAdjList[args.row] !== 'undefined' && typeof me.currentGljAdjList[args.row][dataCode] !== 'undefined'
                     ? me.currentGljAdjList[args.row][dataCode] + '' : '');
             }
             else {
                 //update
-                if(args.row < me.currentGljAdjList.length && args.editingText.toString().trim() !== me.currentGljAdjList[args.row][dataCode]){
+                if (args.row < me.currentGljAdjList.length && args.editingText.toString().trim() !== me.currentGljAdjList[args.row][dataCode]) {
                     let updateObj = me.currentGljAdjList[args.row];
-                    if(dataCode === 'gljCode' && typeof updateObj.coeType !== 'undefined' && (updateObj.coeType === '单个工料机'||updateObj.coeType === '替换人材机')){
+                    if (dataCode === 'gljCode' && typeof updateObj.coeType !== 'undefined' && (updateObj.coeType === '单个工料机' || updateObj.coeType === '替换人材机')) {
                         let gljName = me.getGljName(args.editingText, me.gljList);
-                        if(gljName){
+                        if (gljName) {
                             updateObj.gljCode = args.editingText;
                             updateObj.gljName = gljName;
                             isUpdate = true;
                         } else {
-                            alert("不存在编号为"+ args.editingText +"的工料机");
+                            alert("不存在编号为" + args.editingText + "的工料机");
                         }
-                    }else if(dataCode === 'replaceCode' && typeof updateObj.coeType !== 'undefined' && updateObj.coeType === '替换人材机'){
+                    } else if (dataCode === 'replaceCode' && typeof updateObj.coeType !== 'undefined' && updateObj.coeType === '替换人材机') {
                         let gljName = me.getGljName(args.editingText, me.gljList);
-                        if(gljName){
+                        if (gljName) {
                             updateObj.replaceCode = args.editingText;
                             updateObj.replaceName = gljName;
                             isUpdate = true;
                         } else {
-                            alert("不存在编号为"+ args.editingText +"的工料机");
+                            alert("不存在编号为" + args.editingText + "的工料机");
                         }
                     }
-                    else if(dataCode === 'coeType'){
+                    else if (dataCode === 'coeType') {
                         isUpdate = true;
                         updateObj[dataCode] = args.editingText;
                         updateObj.gljCode = '';
@@ -728,19 +758,19 @@ let gljAdjOprObj = {
                         updateObj.replaceCode = '';
                         updateObj.replaceName = '';
                     }
-                    else if(dataCode !== 'gljCode') {
+                    else if (dataCode !== 'gljCode') {
                         isUpdate = true;
                         updateObj[dataCode] = args.editingText;
                     }
                 }
                 //insert
-                else if(args.row >= me.currentGljAdjList.length){
+                else if (args.row >= me.currentGljAdjList.length) {
                     isUpdate = true;
                     let newAdjGlj = {};
                     newAdjGlj[dataCode] = args.editingText;
                     me.currentGljAdjList.push(newAdjGlj);
                 }
-                if(isUpdate){
+                if (isUpdate) {
                     coeOprObj.save([], [coeOprObj.currentCoe], [], false, function () {
                         console.log(me.currentGljAdjList);
                         me.show(me.currentGljAdjList);
@@ -785,9 +815,13 @@ let gljAdjOprObj = {
                 }
             },
             amount: function (v, cur, tar) {
-                if (!isNaN(v)) {
+                /* if (!isNaN(v)) {
+                    tar.amount = v;
+                } */
+                if (validateAmount(v)) {
                     tar.amount = v;
                 }
+
             },
             operator: function (v, cur, tar) {
                 if (v === '' || me.setting.comboItems.operator.includes(v)) {
@@ -809,12 +843,12 @@ let gljAdjOprObj = {
             }
         };
         let rst = [];
-        for(let i = 0, len = pasteItems.length; i < len; i++){
+        for (let i = 0, len = pasteItems.length; i < len; i++) {
             let row = i + info.cellRange.row;
             let target = {},
                 curObj = me.currentGljAdjList[row],
                 pasteItem = pasteItems[i];
-            if(row < me.currentGljAdjList.length){
+            if (row < me.currentGljAdjList.length) {
                 target.index = row;//要有下标做为匹配的依据,不然在复制多行并且某个单元格是只读的情况下,这里返回的updateList个数会比选中的行数少,造成更新行和实际不匹配的情况
             }
             for (let pasteKey in pasteItem) {
@@ -822,7 +856,7 @@ let gljAdjOprObj = {
                     rules[pasteKey](pasteItem[pasteKey], curObj, target);
                 }
             }
-            if(Object.keys(target).length > 0){
+            if (Object.keys(target).length > 0) {
                 rst.push(target);
             }
         }
@@ -832,20 +866,20 @@ let gljAdjOprObj = {
         let me = gljAdjOprObj, row;
         let items = sheetCommonObj.analyzePasteData(me.setting, info);
         let validData = me.getValidPasteData(items, info);
-        for(let i = 0, len = validData.length; i < len; i++){
+        for (let i = 0, len = validData.length; i < len; i++) {
             row = i + info.cellRange.row;
             //update
-            if(row < me.currentGljAdjList.length && typeof validData[i].index !=='undefined'){
+            if (row < me.currentGljAdjList.length && typeof validData[i].index !== 'undefined') {
                 let updateObj = me.currentGljAdjList[validData[i].index];//这里改成读取下标
-                delete  validData[i].index; //清除下标
+                delete validData[i].index; //清除下标
                 Object.assign(updateObj, validData[i])
             }
             //insert
-            else{
+            else {
                 me.currentGljAdjList.push(validData[i]);
             }
         }
-        if(validData.length > 0){
+        if (validData.length > 0) {
             coeOprObj.save([], [coeOprObj.currentCoe], [], false, function () {
                 me.show(me.currentGljAdjList);
             });
@@ -860,15 +894,15 @@ let gljAdjOprObj = {
             let sheet = workBook.getSheet(0);
             let sels = sheet.getSelections();
             let isUpdate = false;
-            for(let i = 0, len = sels.length; i < len; i++){
-                if(sels[i].colCount === setting.header.length){//can del
-                    if(sels[i].row < me.currentGljAdjList.length){
+            for (let i = 0, len = sels.length; i < len; i++) {
+                if (sels[i].colCount === setting.header.length) {//can del
+                    if (sels[i].row < me.currentGljAdjList.length) {
                         isUpdate = true;
                         me.currentGljAdjList.splice(sels[i].row, sels[i].rowCount);
                     }
                 }
             }
-            if(isUpdate){
+            if (isUpdate) {
                 coeOprObj.save([], [coeOprObj.currentCoe], [], false, function () {
                     me.show(me.currentGljAdjList);
                 });
@@ -877,11 +911,11 @@ let gljAdjOprObj = {
         workBook.commandManager().setShortcutKey(null, GC.Spread.Commands.Key.del, false, false, false, false);
         workBook.commandManager().setShortcutKey('gljAdjDel', GC.Spread.Commands.Key.del, false, false, false, false);
     },
-    getGljName: function (gljCode, gljList,withSpecs) {//withSpecs 是否带上规格型号
+    getGljName: function (gljCode, gljList, withSpecs) {//withSpecs 是否带上规格型号
         let rst = null;
-        for(let i = 0, len = gljList.length; i < len; i++){
-            if(gljCode === gljList[i].code){
-                rst = withSpecs == true ?gljList[i].name +" - "+gljList[i].specs :gljList[i].name;
+        for (let i = 0, len = gljList.length; i < len; i++) {
+            if (gljCode === gljList[i].code) {
+                rst = withSpecs == true ? gljList[i].name + " - " + gljList[i].specs : gljList[i].name;
                 break;
             }
         }
@@ -896,17 +930,17 @@ let gljAdjOprObj = {
         $.ajax({
             type: 'post',
             url: '/stdGljRepository/api/getGljItemsOccupied',
-            data: {repId: pageOprObj.gljLibId, occupation: '-_id code name specs'},
+            data: { repId: pageOprObj.gljLibId, occupation: '-_id code name specs' },
             dataType: 'json',
             timeout: 5000,
-            success:function(result){
+            success: function (result) {
                 if (result.error) {
                     alert(result.message);
-                } else{
+                } else {
                     me.gljList = result.data;
                 }
             },
-            error:function(err){
+            error: function (err) {
                 alert("内部程序错误!");
             }
         });

+ 444 - 0
web/maintain/ration_repository/js/lossRate.js

@@ -0,0 +1,444 @@
+let lossObj = {
+  workBook: null,
+  workSheet: null,
+  currentLossList: [],
+  currentLoss: null,
+  currentMaxNo: null,
+  setting: {
+    header: [
+      { headerName: "编号", headerWidth: 50, dataCode: "serialNo", dataType: "String", hAlign: "center", vAlign: "center", readOnly: false },
+      { headerName: "名称", headerWidth: 200, dataCode: "name", dataType: "String", hAlign: "left", vAlign: "center", readOnly: false },
+      { headerName: "损耗率", headerWidth: 150, dataCode: "rate", dataType: "Number", hAlign: "right", vAlign: "center", readOnly: false },
+    ]
+  },
+  buildSheet: function (container) {
+    let me = lossObj;
+    me.workBook = sheetCommonObj.buildSheet(container, me.setting, 30);
+    sheetCommonObj.bindEscKey(me.workBook, [{ sheet: me.workBook.getSheet(0), editStarting: null, editEnded: me.onEditEnded }]);
+    me.workSheet = me.workBook.getSheet(0);
+    me.workSheet.options.isProtected = true;
+    me.onDelOpr(me.workBook, me.setting);
+    me.workSheet.bind(GC.Spread.Sheets.Events.SelectionChanged, me.onSelectionChanged);
+    me.workSheet.bind(GC.Spread.Sheets.Events.EditEnded, me.onEditEnded);
+    me.workBook.bind(GC.Spread.Sheets.Events.ClipboardPasting, me.onClipboardPasting);
+    me.workBook.bind(GC.Spread.Sheets.Events.ClipboardPasted, me.onClipboardPasted);
+  },
+  onSelectionChanged: function (sender, info) {
+    if (info.oldSelections.length === 0 && info.newSelections.length > 0 || info.oldSelections[0].row !== info.newSelections[0].row) {
+      const row = info.newSelections[0].row;
+      lossObj.lossSelInit(row);
+    }
+  },
+  lossSelInit: function (row) {
+    const me = lossObj;
+    if (row < me.currentLossList.length) {
+      me.currentLoss = me.currentLossList[row];
+    } else {
+      me.currentLoss = null;
+    }
+  },
+  onEditEnded: function (sender, args) {
+    let me = lossObj, addArr = [], updateArr = [], dataCode = me.setting.header[args.col].dataCode;
+    if (args.editingText && args.editingText.toString().trim().length > 0) {
+      let inputT = args.editingText.toString().trim();
+      //update
+      if (args.row < me.currentLossList.length) {
+        let updateObj = me.currentLossList[args.row];
+        if (updateObj[dataCode] != inputT) {
+          if (dataCode === 'serialNo') {
+            if (me.isInt(inputT) && !me.hasTisNo(me.currentLossList, inputT)) {
+              me.currentMaxNo = me.currentMaxNo >= inputT ? me.currentMaxNo : inputT;
+              updateObj[dataCode] = inputT;
+              updateArr.push(updateObj);
+              me.save([], updateArr, [], true);
+            }
+            else if (!me.isInt(inputT)) {
+              alert('编号只能为整数!');
+              args.sheet.setValue(args.row, args.col, updateObj[dataCode] + '');
+            }
+            else if (me.hasTisNo(me.currentLossList, inputT)) {
+              alert('该编号已存在!');
+              args.sheet.setValue(args.row, args.col, updateObj[dataCode] + '');
+            }
+          } else if (dataCode === 'rate' && isNaN(inputT)) {
+            alert('损耗率只能为数值!');
+            args.sheet.setValue(args.row, args.col, updateObj[dataCode] || '');
+          }
+          else {
+            updateObj[dataCode] = inputT;
+            updateArr.push(updateObj);
+            me.save([], updateArr, [], true);
+          }
+        }
+      }
+      //insert
+      else {
+        let newLoss = {};
+        newLoss.libID = pageOprObj.rationLibId;
+        if (dataCode === 'serialNo') {
+          if (me.isInt(inputT) && !me.hasTisNo(me.currentLossList, inputT)) {
+            me.currentMaxNo = me.currentMaxNo >= inputT ? me.currentMaxNo : inputT;
+            newLoss[dataCode] = inputT;
+            addArr.push(newLoss);
+            me.save(addArr, [], [], true, function (result) {
+              me.updateCurrentLossList(result);
+            });
+          }
+          else if (!me.isInt(inputT)) {
+            args.sheet.setValue(args.row, args.col, '');
+            alert('编号只能为整数!');
+          }
+          else if (me.hasTisNo(me.currentLossList, inputT)) {
+            args.sheet.setValue(args.row, args.col, '');
+            alert('该编号已存在!');
+          }
+        }
+        else if (dataCode === 'rate' && isNaN(inputT)) {
+          args.sheet.setValue(args.row, args.col, updateObj[dataCode] + '');
+          alert('损耗率只能为数值!');
+        }
+        else {
+          newLoss.serialNo = ++me.currentMaxNo;
+          newLoss[dataCode] = inputT;
+          addArr.push(newLoss);
+          me.save(addArr, [], [], true, function (result) {
+            me.updateCurrentLossList(result);
+          });
+        }
+      }
+    }
+  },
+  onClipboardPasting: function (sender, info) {
+    let me = lossObj, maxCol = info.cellRange.col + info.cellRange.colCount - 1;
+    if (maxCol > me.setting.header.length) {
+      info.cancel = true;
+    }
+  },
+  onClipboardPasted: function (sender, info) {
+    debugger;
+    let me = lossObj, addArr = [], updateArr = [];
+    let items = sheetCommonObj.analyzePasteData(me.setting, info);
+    let uniqItems = me.makeUniqItems(items);
+    for (let i = 0, len = uniqItems.length; i < len; i++) {
+      let row = i + info.cellRange.row;
+      //update
+      if (row < me.currentLossList.length) {
+        let updateObj = me.currentLossList[row];
+        for (let attr in uniqItems[i]) {
+          if (attr === 'serialNo') {
+            if (me.isInt(uniqItems[i][attr]) && !me.hasTisNo(me.currentLossList, uniqItems[i][attr])) {
+              me.currentMaxNo = me.currentMaxNo >= uniqItems[i][attr] ? me.currentMaxNo : uniqItems[i][attr];
+              updateObj[attr] = uniqItems[i][attr];
+            }
+          }
+          else if (attr !== 'rate' || !isNaN(uniqItems[i][attr])) {
+            updateObj[attr] = uniqItems[i][attr];
+          }
+        }
+        updateArr.push(updateObj);
+      }
+      //insert
+      else {
+        if (typeof uniqItems[i].serialNo !== 'undefined' && uniqItems[i] && me.isInt(uniqItems[i].serialNo) && !me.hasTisNo(me.currentLossList, uniqItems[i].serialNo)) {
+          me.currentMaxNo = me.currentMaxNo >= uniqItems[i].serialNo ? me.currentMaxNo : uniqItems[i].serialNo;
+        }
+        else {
+          uniqItems[i].serialNo = ++me.currentMaxNo;
+        }
+        if (typeof uniqItems[i].rate !== 'undefined' && isNaN(uniqItems[i].rate)) {
+          delete uniqItems[i].rate;
+        }
+        uniqItems[i].libID = pageOprObj.rationLibId;
+        addArr.push(uniqItems[i]);
+      }
+    }
+    if (addArr.length > 0 || updateArr.length > 0) {
+      me.save(addArr, updateArr, [], true, function (result) {
+        me.updateCurrentLossList(result);
+      });
+    }
+  },
+  onDelOpr: function (workBook, setting) {
+    let me = lossObj;
+    workBook.commandManager().register('coeListDel', function () {
+      let deleteArr = [];
+      let sheet = workBook.getSheet(0);
+      let sels = sheet.getSelections();
+      let idx = sels[0].row;
+      for (let i = 0, len = sels.length; i < len; i++) {
+        if (idx > sels[i].row) {
+          idx = sels[i].row;
+        }
+        if (sels[i].colCount === setting.header.length) {//can del
+          for (let r = 0, rLen = sels[i].rowCount; r < rLen; r++) {
+            let row = sels[i].row + r;
+            if (row < me.currentLossList.length) {
+              deleteArr.push({ libID: me.currentLossList[row].libID, ID: me.currentLossList[row].ID });
+            }
+          }
+          me.currentLossList.splice(sels[i].row, sels[i].rowCount);
+        }
+      }
+      if (deleteArr.length > 0) {
+        me.save([], [], deleteArr, true);
+        me.currentLoss = typeof me.currentLossList[idx] !== 'undefined' ? me.currentLossList[idx] : null;
+      }
+
+    });
+    workBook.commandManager().setShortcutKey(null, GC.Spread.Commands.Key.del, false, false, false, false);
+    workBook.commandManager().setShortcutKey('coeListDel', GC.Spread.Commands.Key.del, false, false, false, false);
+  },
+  //粘贴的数据,编号唯一化,去除编号重复的项
+  makeUniqItems: function (items) {
+    let rst = [];
+    for (let i = 0, len = items.length; i < len; i++) {
+      if (typeof items[i].serialNo !== 'undefined' && items[i].serialNo) {
+        if (rst.length === 0) {
+          rst.push(items[i]);
+        }
+        else {
+          let isExist = false;
+          for (let j = 0, jLen = rst.length; j < jLen; j++) {
+            if (items[i].serialNo === rst[j].serialNo) {
+              isExist = true;
+              break;
+            }
+          }
+          if (!isExist) {
+            rst.push(items[i]);
+          }
+        }
+      }
+      else {
+        rst.push(items[i]);
+      }
+    }
+    return rst;
+  },
+  isInt: function (num) {
+    return !isNaN(num) && num % 1 === 0;
+  },
+  hasTisNo: function (lossList, newSerialNo) {
+    let rst = false;
+    for (let i = 0, len = lossList.length; i < len; i++) {
+      if (lossList[i].serialNo == newSerialNo) {
+        rst = true;
+        break;
+      }
+    }
+    return rst;
+  },
+  updateCurrentLossList: function (newCoeList) {
+    let me = lossObj;
+    if (newCoeList) {
+      me.currentLossList = me.currentLossList.concat(newCoeList);
+    }
+  },
+  sortLossList: function (lossList) {
+    lossList.sort(function (a, b) {
+      let rst = 0;
+      if (a.serialNo > b.serialNo) rst = 1;
+      else if (a.serialNo < b.serialNo) rst = -1;
+      return rst;
+    });
+  },
+  getLossList: function () {
+    let me = lossObj;
+    $.ajax({
+      type: 'post',
+      url: '/rationRepository/api/getLossList',
+      data: { libID: pageOprObj.rationLibId },
+      dataType: 'json',
+      timeout: 20000,
+      success: function (result) {
+        if (!result.error) {
+          me.currentLossList = result.data;
+          me.sortLossList(me.currentLossList);
+          me.currentMaxNo = me.currentLossList.length > 0 ? me.currentLossList[me.currentLossList.length - 1].serialNo : 0;
+          pageObj.showData(me.workSheet, me.setting, me.currentLossList);
+          me.workSheet.clearSelection();
+        }
+      },
+      error: function (err) {
+        alert("内部程序错误!");
+      }
+    });
+  },
+  updateRationGljs: function (updateMap, rationGljs) {
+    if (!rationGljs || !rationGljs.length) {
+      return;
+    }
+    rationGljs.forEach(glj => {
+      const match = updateMap[glj.lossRateID];
+      if (match) {
+        glj.lossRateNo = match.serialNo;
+        glj.lossRateName = match.name;
+        glj.lossRate = match.rate;
+      }
+    })
+  },
+  updateRationAndGljCache: function (updateRateData) {
+    debugger;
+    const updateMap = {};
+    updateRateData.forEach(item => {
+      updateMap[item.ID] = item;
+    });
+    const rations = Object.values(rationOprObj.currentRations).flat();
+    rations.forEach(ration => {
+      lossObj.updateRationGljs(updateMap, ration.rationGljList);
+    })
+    const rGljList = Object.values(rationGLJOprObj.cache).flat();
+    lossObj.updateRationGljs(updateMap, rGljList);
+    if (rationGLJOprObj.currentRationItem) {
+      rationGLJOprObj.showGljItems(rationGLJOprObj.currentRationItem.ID);
+    }
+  },
+  save: function (addArr, updateArr, deleteArr, refresh, callback) {
+    let me = lossObj;
+    $.ajax({
+      type: "POST",
+      url: "api/saveLossList",
+      data: { data: JSON.stringify({ addArr: addArr, updateArr: updateArr, deleteArr: deleteArr }) },
+      dataType: "json",
+      timeout: 5000,
+      success: function (result) {
+        if (result.error) {
+          alert(result.message);
+        } else {
+          debugger;
+          if (updateArr && updateArr.length) {
+            lossObj.updateRationAndGljCache(updateArr);
+          }
+          if (callback) {
+            callback(result.data);
+          }
+          if (refresh) {
+            me.sortLossList(me.currentLossList);
+            me.currentMaxNo = me.currentLossList.length > 0 ? me.currentLossList[me.currentLossList.length - 1].serialNo : 0;
+            pageObj.showData(me.workSheet, me.setting, me.currentLossList);
+          }
+        }
+      },
+      error: function (err) {
+        alert("内部程序错误!");
+      }
+    });
+  }
+}
+
+$(document).ready(function () {
+  //设置水平拖动条的宽度
+  //@param {Object dom}resize滚动条
+  function setResizeWidth(resize) {
+    const fixedWidth = 10;
+    //跟滚动条同层的其他节点
+    let bros = resize.parent().children();
+    //滚动条节点 及 同层非滚动条节点的索引
+    let index = bros.index(resize),
+      otherIndex = index ? 0 : 2;
+    const other = resize.parent().children(`:eq(${otherIndex})`);
+    let resizeParentWidth = resize.parent().width();
+    let resizeDecimalWidth = fixedWidth / resizeParentWidth,
+      otherDecimalWidth = 1 - resizeDecimalWidth;
+    let resizePercentWidth = resizeDecimalWidth * 100 + '%',
+      otherPercentWidth = otherDecimalWidth * 100 + '%';
+    resize.css('width', resizePercentWidth);
+    other.css('width', otherPercentWidth);
+  }
+  function refreshAfterShow(visible) {
+    const min = 20;
+    //宽度比例localstorage key
+    let leftContentKey = `${moduleName}${$('#leftContent').attr('id')}Width`,
+      mainContentKey = `${moduleName}${$('#mainContent').attr('id')}Width`,
+      lossContentKey = `${moduleName}${$('#rightContent').attr('id')}Width`;
+    let lossWidth = getLocalCache(lossContentKey) ? getLocalCache(lossContentKey) : $('#rightContent')[0].style.width,
+      mainContentWidth = $('#mainContent')[0].style.width,
+      leftContentWidth;
+    lossWidth = parseFloat(lossWidth.replace('%', ''));
+    mainContentWidth = parseFloat(mainContentWidth.replace('%', ''));
+    if (visible) {
+      mainContentWidth = mainContentWidth - lossWidth / 2 < min ? min : mainContentWidth - lossWidth / 2;
+      if (100 - mainContentWidth - lossWidth < min) {
+        leftContentWidth = min;
+        lossWidth = 100 - mainContentWidth - leftContentWidth;
+      } else {
+        leftContentWidth = 100 - mainContentWidth - lossWidth;
+      }
+    } else {
+      mainContentWidth += lossWidth / 2;
+      leftContentWidth = 100 - mainContentWidth;
+    }
+    $('#leftContent').css('width', `${leftContentWidth}%`);
+    setLocalCache(leftContentKey, `${leftContentWidth}%`);
+    $('#mainContent').css('width', `${mainContentWidth}%`);
+    setLocalCache(mainContentKey, `${mainContentWidth}%`);
+    $('#rightContent').css('width', `${lossWidth}%`);
+    setLocalCache(lossContentKey, `${lossWidth}%`);
+    let resizes = [$('#slideResizeLeft'), $('#slideResizeRight')];
+    for (let resize of resizes) {
+      setResizeWidth(resize);
+    }
+    sectionTreeObj.loadRateWidth();
+  }
+  $('#loss').click(function () {
+    if (!$(this).hasClass('active')) {
+      $('#zmhs').removeClass('active');
+      $(this).addClass('active');
+      refreshAfterShow(true);
+      $('#lossWrap').show();
+      $('#zmhsWrap').hide();
+      $('#rightContent').show();
+      if (!lossObj.workBook) {
+        pageObj.initPage();
+      }
+      refreshALlWorkBook();
+    } else {
+      $(this).removeClass('active');
+      refreshAfterShow(false);
+      $('#rightContent').hide();
+      $('#zmhsWrap').hide();
+      $('#lossWrap').hide();
+      refreshALlWorkBook();
+    }
+  });
+
+  var pageObj = {
+    initPage: function () {
+      lossObj.buildSheet($('#lossSpread')[0]);
+      lossObj.getLossList();
+      lockUtil.lockSpreads([lossObj.workBook], locked);
+    },
+    showData: function (sheet, setting, data) {
+      let me = pageObj, ch = GC.Spread.Sheets.SheetArea.viewport;
+      sheet.suspendPaint();
+      sheet.suspendEvent();
+      sheet.clear(0, 0, sheet.getRowCount(), sheet.getColumnCount(), GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data);
+      sheet.setRowCount(data.length + 3);
+      for (let col = 0; col < setting.header.length; col++) {
+        var hAlign = "left", vAlign = "center";
+        if (setting.header[col].hAlign) {
+          hAlign = setting.header[col].hAlign;
+        } else if (setting.header[col].dataType !== "String") {
+          hAlign = "right";
+        }
+        if (setting.header[col].readOnly) {
+          sheet.getRange(-1, col, -1, 1).locked(true);
+        }
+        else {
+          sheet.getRange(-1, col, -1, 1).locked(false);
+        }
+        vAlign = setting.header[col].vAlign ? setting.header[col].vAlign : vAlign;
+        sheetCommonObj.setAreaAlign(sheet.getRange(-1, col, -1, 1), hAlign, vAlign);
+        if (setting.header[col].formatter) {
+          sheet.setFormatter(-1, col, setting.header[col].formatter, GC.Spread.Sheets.SheetArea.viewport);
+        }
+        for (let row = 0; row < data.length; row++) {
+          let val = data[row][setting.header[col].dataCode];
+          sheet.setValue(row, col, val, ch);
+        }
+      }
+      sheet.resumeEvent();
+      sheet.resumePaint();
+    }
+  };
+});

+ 200 - 132
web/maintain/ration_repository/js/ration_glj.js

@@ -10,25 +10,27 @@ var rationGLJOprObj = {
     tempCacheArr: [],//被更新的工料机,若更新的工料机不存在,则恢复
     cache: {},
     setting: {
-        header:[
-            {headerName:"编码",headerWidth:80,dataCode:"code", dataType: "String", formatter: "@"},
-            {headerName:"名称",headerWidth:160,dataCode:"name", dataType: "String"},
-            {headerName:"规格型号",headerWidth:100,dataCode:"specs", dataType: "String"},
-            {headerName:"单位",headerWidth:60,dataCode:"unit", dataType: "String", hAlign: "center", vAlign: "center"},
-            {headerName:"定额价",headerWidth:80, dataCode:"basePrice", dataType: "Number", formatter:"0.00",  precision: 2},
-            {headerName:"定额消耗",headerWidth:80, dataCode:"consumeAmt", dataType: "Number", formatter: "0.000", precision: 3},
-            {headerName:"类型",headerWidth:70,dataCode:"gljType", dataType: "String", hAlign: "center", vAlign: "center"},
-            {headerName:"配合比",headerWidth:70,dataCode:"proportion", dataType: "Number", formatter:"0.00",  precision: 2}
+        header: [
+            { headerName: "编码", headerWidth: 80, dataCode: "code", dataType: "String", formatter: "@" },
+            { headerName: "名称", headerWidth: 160, dataCode: "name", dataType: "String" },
+            { headerName: "规格型号", headerWidth: 100, dataCode: "specs", dataType: "String" },
+            { headerName: "单位", headerWidth: 60, dataCode: "unit", dataType: "String", hAlign: "center", vAlign: "center" },
+            { headerName: "定额价", headerWidth: 80, dataCode: "basePrice", dataType: "Number", formatter: "0.00", precision: 2 },
+            { headerName: "定额消耗", headerWidth: 80, dataCode: "consumeAmt", dataType: "Number", formatter: "0.000", precision: 3 },
+            { headerName: "类型", headerWidth: 70, dataCode: "gljType", dataType: "String", hAlign: "center", vAlign: "center" },
+            { headerName: "配合比", headerWidth: 70, dataCode: "proportion", dataType: "Number", formatter: "0.00", precision: 2 },
+            { headerName: "损耗编号", headerWidth: 80, dataCode: "lossRateNo", dataType: "Number" },
+            { headerName: "损耗率", headerWidth: 80, dataCode: "lossRate", dataType: "Number", hAlign: "right", vAlign: "center" },
         ],
-        view:{
-            comboBox:[],
-            lockColumns:[1,2,3,4,6]
+        view: {
+            comboBox: [],
+            lockColumns: [1, 2, 3, 4, 6, 8, 9]
         }
     },
     getDistTypeTree: function (gljDistType) {
         let distType;
         let distTypeTree = {
-            prefix : 'gljDistType',
+            prefix: 'gljDistType',
             distTypes: {},
             comboDatas: [],
             distTypesArr: []
@@ -45,18 +47,18 @@ var rationGLJOprObj = {
         gljDistType.forEach(function (typeData) {
             distType = distTypeTree.distTypes[distTypeTree.prefix + typeData.ID];
             let parent = distTypeTree.distTypes[distTypeTree.prefix + typeData.ParentID];
-            if(parent){
+            if (parent) {
                 distType.parent = parent;
                 parent.children.push(distType);
             }
         });
         distTypeTree.distTypesArr.forEach(function (distTypeObj) {
-            if(distTypeObj.children.length === 0 && distTypeObj.data.fullName !== '普通机械' &&distTypeObj.data.fullName !== '机械组成物'
-                && distTypeObj.data.fullName !== '机上人工'){
-                distTypeTree.comboDatas.push({text: distTypeObj.data.fullName, value: distTypeObj.data.ID});
+            if (distTypeObj.children.length === 0 && distTypeObj.data.fullName !== '普通机械' && distTypeObj.data.fullName !== '机械组成物'
+                && distTypeObj.data.fullName !== '机上人工') {
+                distTypeTree.comboDatas.push({ text: distTypeObj.data.fullName, value: distTypeObj.data.ID });
             }
-            if(distTypeObj.data.fullName === '机械'){
-                distTypeTree.comboDatas.push({text: distTypeObj.data.fullName, value: distTypeObj.data.ID});
+            if (distTypeObj.data.fullName === '机械') {
+                distTypeTree.comboDatas.push({ text: distTypeObj.data.fullName, value: distTypeObj.data.ID });
             }
         });
         return distTypeTree;
@@ -65,7 +67,7 @@ var rationGLJOprObj = {
         this.distTypeTree = this.getDistTypeTree(gljDistTypeList);
 
     },
-    buildSheet: function(sheet) {
+    buildSheet: function (sheet) {
         this.sheet = sheet;
         sheetCommonObj.initSheet(this.sheet, this.setting, 30);
         this.onContextmenuOpr();
@@ -106,32 +108,33 @@ var rationGLJOprObj = {
         spreadBook.commandManager().setShortcutKey(null, GC.Spread.Commands.Key.del, false, false, false, false);
         spreadBook.commandManager().setShortcutKey('rationGljDelete', GC.Spread.Commands.Key.del, false, false, false, false);
     },
-    onClipboardPasting: function(sender, args) {
+    onClipboardPasting: function (sender, args) {
         const me = rationGLJOprObj;
         const rationCache = rationOprObj.getCache();
         const rationRow = rationOprObj.workBook.getSheet(0).getSelections()[0].row;
         me.currentRationItem = rationRow < rationCache.length ? rationCache[rationRow] : null;
-        if(me.currentRationItem && typeof me.cache["_GLJ_" + me.currentRationItem.ID] === 'undefined'){
+        if (me.currentRationItem && typeof me.cache["_GLJ_" + me.currentRationItem.ID] === 'undefined') {
             me.cache["_GLJ_" + me.currentRationItem.ID] = [];
         }
         const field = me.setting.header[args.cellRange.col].dataCode;
-        const canPasteFields = ['code', 'consumeAmt', 'proportion'];
+        const canPasteFields = ['code', 'consumeAmt', 'proportion', 'lossRateNo'];
         if (!me.currentRationItem || !(canPasteFields.includes(field) && args.cellRange.colCount === 1)) {
             args.cancel = true;
         }
     },
-    onClipboardPasted: function(e, info) {
+    onClipboardPasted: async function (e, info) {
         const me = rationGLJOprObj;
         me.tempCacheArr = [];
         const gljCache = me.cache["_GLJ_" + me.currentRationItem.ID];
         const field = me.setting.header[info.cellRange.col].dataCode;
+        debugger;
         if (field === 'code') {
-            const pasteList = sheetCommonObj.analyzePasteData({header:[{dataCode: "code"}] }, info);
+            const pasteList = sheetCommonObj.analyzePasteData({ header: [{ dataCode: "code" }] }, info);
             const codes = [];
             for (let i = 0; i < pasteList.length; i++) {
                 let rowIdx = info.cellRange.row + i;
-                if(rowIdx < gljCache.length){//更新
-                    me.tempCacheArr.push({org: gljCache[rowIdx], newCode: pasteList[i].code});
+                if (rowIdx < gljCache.length) {//更新
+                    me.tempCacheArr.push({ org: gljCache[rowIdx], newCode: pasteList[i].code });
                     gljCache.splice(rowIdx--, 1);
                 }
                 codes.push(pasteList[i].code);
@@ -139,26 +142,47 @@ var rationGLJOprObj = {
             me.addGljItems(codes, pageOprObj.gljLibId, info.cellRange);
         } else if (gljCache && info.cellRange.row < gljCache.length) {
             const pasteList = sheetCommonObj.analyzePasteData(me.setting, info);
-            const maxCount = info.cellRange.row + info.cellRange.rowCount -1 > gljCache.length -1 ?
+            const maxCount = info.cellRange.row + info.cellRange.rowCount - 1 > gljCache.length - 1 ?
                 gljCache.length - info.cellRange.row : info.cellRange.rowCount;
             const precision = me.setting.header[info.cellRange.col].precision;
-            for (let i = 0; i < maxCount; i++) {
-                let roundCons = scMathUtil.roundTo(pasteList[i][field], -precision);
-                gljCache[info.cellRange.row + i][field] = roundCons;
+            if (field === 'lossRateNo') {
+                const lossRateNos = pasteList.map(item => item.lossRateNo);
+                const lossMap = await rationGLJOprObj.getLossMap(lossRateNos);
+                for (let i = 0; i < maxCount; i++) {
+                    const curGLJ = gljCache[info.cellRange.row + i]
+                    const serialNo = pasteList[i][field];
+                    const loss = lossMap[serialNo];
+                    if (loss) {
+                        curGLJ.lossRateID = loss.ID;
+                        curGLJ.lossRateNo = loss.serialNo;
+                        curGLJ.lossRateName = loss.name;
+                        curGLJ.lossRate = loss.rate;
+                    } else {
+                        delete curGLJ.lossRateID;
+                        delete curGLJ.lossRateNo;
+                        delete curGLJ.lossRateName;
+                        delete curGLJ.lossRate;
+                    }
+                }
+            } else {
+                for (let i = 0; i < maxCount; i++) {
+                    const pasteVal = precision ? scMathUtil.roundTo(pasteList[i][field], -precision) : pasteList[i][field];
+                    gljCache[info.cellRange.row + i][field] = pasteVal;
+                }
             }
             me.updateRationItem(function () {
                 me.sheet.getParent().focus(true);
             });
-            if (info.cellRange.row + info.cellRange.rowCount -1 >= gljCache.length -1) {
+            if (info.cellRange.row + info.cellRange.rowCount - 1 >= gljCache.length - 1) {
                 me.sheet.suspendPaint();
-                for(let rowIdx = gljCache.length; rowIdx <= info.cellRange.row + info.cellRange.rowCount -1; rowIdx++){
+                for (let rowIdx = gljCache.length; rowIdx <= info.cellRange.row + info.cellRange.rowCount - 1; rowIdx++) {
                     me.sheet.setValue(rowIdx, info.cellRange.col, '');
                 }
                 me.sheet.resumePaint();
             }
         } else if (info.cellRange.row >= gljCache.length) {
             me.sheet.suspendPaint();
-            for(let rowIdx = info.cellRange.row; rowIdx <= info.cellRange.row + info.cellRange.rowCount -1; rowIdx ++){
+            for (let rowIdx = info.cellRange.row; rowIdx <= info.cellRange.row + info.cellRange.rowCount - 1; rowIdx++) {
                 me.sheet.setValue(rowIdx, info.cellRange.col, '');
             }
             me.sheet.resumePaint();
@@ -169,31 +193,31 @@ var rationGLJOprObj = {
         const rationSection = rationOprObj.getCache();
         const rationRow = rationOprObj.workBook.getSheet(0).getSelections()[0].row;
         me.currentRationItem = rationRow < rationSection.length ? rationSection[rationRow] : null;
-        if(me.currentRationItem && typeof me.cache["_GLJ_" + me.currentRationItem.ID] === 'undefined'){
+        if (me.currentRationItem && typeof me.cache["_GLJ_" + me.currentRationItem.ID] === 'undefined') {
             me.cache["_GLJ_" + me.currentRationItem.ID] = [];
         }
         const isEmptyRation = !me.currentRationItem;
         if (isEmptyRation) {
             return args.cancel = true;
         }
-        const canEditFields = ['code', 'consumeAmt', 'proportion'];
+        const canEditFields = ['code', 'consumeAmt', 'proportion', 'lossRateNo'];
         const emptyGLJCanEditFields = ['code'];
         const isEmptyGLJ = args.row >= me.cache["_GLJ_" + me.currentRationItem.ID].length;
         const editingField = me.setting.header[args.col].dataCode;
-        const isValidField =  isEmptyGLJ && emptyGLJCanEditFields.includes(editingField) || !isEmptyGLJ && canEditFields.includes(editingField);
+        const isValidField = isEmptyGLJ && emptyGLJCanEditFields.includes(editingField) || !isEmptyGLJ && canEditFields.includes(editingField);
         if (!isValidField) {
             return args.cancel = true;
         }
     },
-    onCellEditEnd: function(sender, args) {
+    onCellEditEnd: async function (sender, args) {
         const me = rationGLJOprObj;
         me.tempCacheArr = [];
         const cacheArr = me.cache["_GLJ_" + me.currentRationItem.ID];
         const editingField = me.setting.header[args.col].dataCode;
         const trimText = args.editingText ? args.editingText.trim() : '';
         const curGLJ = cacheArr[args.row];
-        const originText = curGLJ  && !commonUtil.isEmptyVal(curGLJ[editingField]) ? String(curGLJ[editingField]) : '';
-        if (!trimText || trimText === originText) {
+        const originText = curGLJ && !commonUtil.isEmptyVal(curGLJ[editingField]) ? String(curGLJ[editingField]) : '';
+        if ((!trimText && editingField === 'code') || trimText === originText) {
             args.sheet.setValue(args.row, args.col, originText);
             return;
         }
@@ -205,7 +229,7 @@ var rationGLJOprObj = {
                 return;
             }
             if (args.row < cacheArr.length) { // 替换人材机
-                me.tempCacheArr.push({org: cacheArr[args.row], newCode: args.editingText.toString().trim()});
+                me.tempCacheArr.push({ org: cacheArr[args.row], newCode: args.editingText.toString().trim() });
                 cacheArr.splice(args.row, 1);
             }
             me.addGljItems([trimText], pageOprObj.gljLibId)
@@ -216,32 +240,65 @@ var rationGLJOprObj = {
                 $('#alertModal').modal('show');
                 args.sheet.setValue(args.row, args.col, originText);
             } else {
-                const precision = me.setting.header[args.col].precision;
-                const roundText = scMathUtil.roundTo(trimText, -precision);
-                curGLJ[editingField] = roundText;
+                if (editingField === 'lossRateNo') {
+                    const lossMap = await rationGLJOprObj.getLossMap([trimText]);
+                    const loss = lossMap[trimText];
+                    if (loss) {
+                        curGLJ.lossRateID = loss.ID;
+                        curGLJ.lossRateNo = loss.serialNo;
+                        curGLJ.lossRateName = loss.name;
+                        curGLJ.lossRate = loss.rate;
+                    } else {
+                        delete curGLJ.lossRateID;
+                        delete curGLJ.lossRateNo;
+                        delete curGLJ.lossRateName;
+                        delete curGLJ.lossRate;
+                    }
+                } else {
+                    const precision = me.setting.header[args.col].precision;
+                    const roundText = scMathUtil.roundTo(trimText, -precision);
+                    curGLJ[editingField] = roundText;
+                }
                 me.updateRationItem(function () {
                     me.sheet.getParent().focus(true);
                 });
             }
         }
     },
+    getLossMap: async function (serialNos) {
+        const lossList = await rationGLJOprObj.getLossListByNos(serialNos);
+        const map = {};
+        lossList.forEach(item => {
+            map[item.serialNo] = item;
+        });
+        return map;
+    },
+    getLossListByNos: async function (serialNos) {
+        try {
+            const res = await ajaxPost('/rationRepository/api/getItemsBySerialNos', { serialNos, libID: pageOprObj.rationLibId });
+            return res;
+        } catch (error) {
+            return [];
+        }
+    },
+
     onContextmenuOpr: function () {//右键菜单
         let me = this;
         let raCoe = rationCoeOprObj;
         $.contextMenu({
             selector: '#rdSpread',
-            build: function($triggerElement, e){
+            build: function ($triggerElement, e) {
                 //控制允许右键菜单在哪个位置出现
                 let target = SheetDataHelper.safeRightClickSelection($triggerElement, e, me.sheet.getParent());
                 let sheet = me.sheet;
-                if(me.sheet.getParent().getActiveSheetIndex() === 0 && target.hitTestType === 3){//在表格内&& typeof target.row !== 'undefined' && typeof target.col !== 'undefined'
+                if (me.sheet.getParent().getActiveSheetIndex() === 0 && target.hitTestType === 3) {//在表格内&& typeof target.row !== 'undefined' && typeof target.col !== 'undefined'
                     //rationGlj表
-                    if(typeof target.row !== 'undefined'){
+                    if (typeof target.row !== 'undefined') {
                         //控制按钮是否可用
                         sheet.setActiveCell(target.row, target.col);
                     }
                     return {
-                        callback: function(){},
+                        callback: function () { },
                         items: {
                             "add": {
                                 name: "添加人材机",
@@ -258,18 +315,19 @@ var rationGLJOprObj = {
                                     gljSelOprObj.initRadio();
                                     gljSelOprObj.gljCurTypeId = null;
                                     //默认点击树根节点
-                                    if(gljSelOprObj.rootNode){
+                                    if (gljSelOprObj.rootNode) {
                                         gljSelOprObj.treeObj.selectNode(gljSelOprObj.rootNode);
                                         gljSelTreeOprObj.setting.callback.onClick(null, 'componentTree', gljSelOprObj.rootNode);
                                     }
                                     //弹出窗口
                                     $('#selGlj').modal('show');
-                                }},
+                                }
+                            },
                             "delete": {
                                 name: "删除人材机",
                                 disabled: function () {
                                     const inValidCell = !commonUtil.isDef(target.row) || !commonUtil.isDef(target.col);
-                                    const rationGlj =  me.cache['_GLJ_' + me.currentRationItem.ID];
+                                    const rationGlj = me.cache['_GLJ_' + me.currentRationItem.ID];
                                     const inValidData = !rationGlj || target.row >= rationGlj.length;
                                     if (locked || inValidCell || !me.currentRationItem || inValidData) {
                                         return true;
@@ -278,23 +336,24 @@ var rationGLJOprObj = {
                                 },
                                 icon: "fa-remove",
                                 callback: function (key, opt) {
-                                    const rationGlj =  me.cache['_GLJ_' + me.currentRationItem.ID];
+                                    const rationGlj = me.cache['_GLJ_' + me.currentRationItem.ID];
                                     rationGlj.splice(target.row, 1);
-                                    me.updateRationItem(function(){
+                                    me.updateRationItem(function () {
                                         me.sheet.getParent().focus();
                                     });
                                     sheetCommonObj.cleanData(me.sheet, me.setting, -1);
                                     me.showGljItems(me.currentRationItem.ID);
-                                }},
+                                }
+                            },
                         }
                     };
                 }
                 //rationCoe表
-                else if(me.sheet.getParent().getActiveSheetIndex() === 2 && target.hitTestType === 3 && typeof target.row !== 'undefined' && typeof target.col !== 'undefined'){
-                    let currentCache = raCoe.curRation && raCoe.isDef(raCoe.cache["_Coe_" + raCoe.curRation.ID])  ? raCoe.cache["_Coe_" + raCoe.curRation.ID] : [];
+                else if (me.sheet.getParent().getActiveSheetIndex() === 2 && target.hitTestType === 3 && typeof target.row !== 'undefined' && typeof target.col !== 'undefined') {
+                    let currentCache = raCoe.curRation && raCoe.isDef(raCoe.cache["_Coe_" + raCoe.curRation.ID]) ? raCoe.cache["_Coe_" + raCoe.curRation.ID] : [];
                     sheet.setActiveCell(target.row, target.col);
                     return {
-                        callback: function(){},
+                        callback: function () { },
                         items: {
                             "upMove": {
                                 name: "上移",
@@ -305,7 +364,7 @@ var rationGLJOprObj = {
                                 },
                                 icon: "fa-arrow-up",
                                 callback: function (key, opt) {
-                                    raCoe.upMove(currentCache[target.row], currentCache[target.row - 1], {row: target.row - 1, col: target.col});
+                                    raCoe.upMove(currentCache[target.row], currentCache[target.row - 1], { row: target.row - 1, col: target.col });
                                 }
                             },
                             "downMove": {
@@ -317,7 +376,7 @@ var rationGLJOprObj = {
                                 },
                                 icon: "fa-arrow-down",
                                 callback: function (key, opt) {
-                                    raCoe.downMove(currentCache[target.row], currentCache[target.row + 1], {row: target.row + 1, col: target.col});
+                                    raCoe.downMove(currentCache[target.row], currentCache[target.row + 1], { row: target.row + 1, col: target.col });
                                 }
                             },
                             "ref": {
@@ -330,19 +389,19 @@ var rationGLJOprObj = {
                                 icon: "fa-arrow-left",
                                 callback: function (key, opt) {
                                     raCoe.updateSectionRation(rationOprObj.currentRations["_SEC_ID_" + rationOprObj.currentSectionId], currentCache[target.row], function (updateArr) {
-                                        for(let i = 0, len = updateArr.length; i < len; i++){
+                                        for (let i = 0, len = updateArr.length; i < len; i++) {
                                             let ration = updateArr[i];
                                             let rationCoeList = updateArr[i].rationCoeList;
                                             let newNo = 1;
-                                            for(let j = 0, jLen = rationCoeList.length; j < jLen; j++){
-                                                if(rationCoeList[j].no >= newNo){
+                                            for (let j = 0, jLen = rationCoeList.length; j < jLen; j++) {
+                                                if (rationCoeList[j].no >= newNo) {
                                                     newNo = rationCoeList[j].no + 1;
                                                 }
                                             }
                                             let theCache = raCoe.cache["_Coe_" + ration.ID];
-                                            if(theCache !== undefined && theCache !== null){
+                                            if (theCache !== undefined && theCache !== null) {
                                                 let newCoe = {};
-                                                for(let attr in currentCache[target.row]){
+                                                for (let attr in currentCache[target.row]) {
                                                     newCoe[attr] = currentCache[target.row][attr];
                                                 }
                                                 newCoe.no = newNo;
@@ -355,7 +414,7 @@ var rationGLJOprObj = {
                         }
                     };
                 }
-                else{
+                else {
                     return false;
                 }
             }
@@ -363,40 +422,40 @@ var rationGLJOprObj = {
     },
     getRecoveryArr: function (tempDelArr, newArr) {//获得更新的code不存在,恢复删除的被更新数据
         let rst = [];
-        for(let i = 0, len = tempDelArr.length; i < len; i++){
+        for (let i = 0, len = tempDelArr.length; i < len; i++) {
             let isExist = false;
-            for(let j = 0, jLen = newArr.length; j < jLen; j++){
-                if(tempDelArr[i].newCode == newArr[j].code){
+            for (let j = 0, jLen = newArr.length; j < jLen; j++) {
+                if (tempDelArr[i].newCode == newArr[j].code) {
                     isExist = true;
                     break;
                 }
             }
-            if(!isExist){
+            if (!isExist) {
                 rst.push(tempDelArr[i].org);
             }
         }
         return rst;
     },
-    addGljItems: function(codes, repId) {
+    addGljItems: function (codes, repId) {
         var me = this;
         $.ajax({
-            type:"POST",
-            url:"api/getGljItemsByCodes",
-            data:{"gljCodes": JSON.stringify(codes), repId: repId},
-            dataType:"json",
-            cache:false,
-            timeout:5000,
-            success:function(result){
+            type: "POST",
+            url: "api/getGljItemsByCodes",
+            data: { "gljCodes": JSON.stringify(codes), repId: repId },
+            dataType: "json",
+            cache: false,
+            timeout: 5000,
+            success: function (result) {
                 if (result) {
-                    if(result.data.length > 0){
-                        if(priceProperties && priceProperties.length > 0){
+                    if (result.data.length > 0) {
+                        if (priceProperties && priceProperties.length > 0) {
                             let priceField = priceProperties[0].price.dataCode;
-                            for(let glj of result.data){
+                            for (let glj of result.data) {
                                 glj.basePrice = glj.priceProperty && glj.priceProperty[priceField] ? glj.priceProperty[priceField] : 0;
                             }
                         }
                         sheetCommonObj.cleanData(me.sheet, me.setting, -1);
-                        var rstArr = [], dummyR = {gljId: 0, consumeAmt:0},
+                        var rstArr = [], dummyR = { gljId: 0, consumeAmt: 0 },
                             newAddArr = [],
                             validGlj = [];
                         for (var i = 0; i < result.data.length; i++) {
@@ -428,7 +487,7 @@ var rationGLJOprObj = {
                             }
                             me.cache["_GLJ_" + me.currentRationItem.ID] = cacheArr.concat(validGlj);
                             let recoveryArr = me.getRecoveryArr(me.tempCacheArr, result.data);
-                            if(recoveryArr.length > 0){
+                            if (recoveryArr.length > 0) {
                                 me.cache["_GLJ_" + me.currentRationItem.ID] = me.cache["_GLJ_" + me.currentRationItem.ID].concat(recoveryArr);
                             }
                         }
@@ -439,15 +498,15 @@ var rationGLJOprObj = {
                             });
                         }
                     }
-                    else{
-                        let cacheArr = me.cache["_GLJ_" + me.currentRationItem.ID]?  me.cache["_GLJ_" + me.currentRationItem.ID] : [];
+                    else {
+                        let cacheArr = me.cache["_GLJ_" + me.currentRationItem.ID] ? me.cache["_GLJ_" + me.currentRationItem.ID] : [];
                         let recoveryArr = me.getRecoveryArr(me.tempCacheArr, []);
-                        if(recoveryArr.length > 0){
+                        if (recoveryArr.length > 0) {
                             me.cache["_GLJ_" + me.currentRationItem.ID] = cacheArr.concat(recoveryArr);
                         }
                         //更新的工料机不存在
                         $('#alertModalBtn').click();
-                        $('#alertText').text("人材机"+ codes + "不存在,请查找你所需要的人材机,或新增人材机");
+                        $('#alertText').text("人材机" + codes + "不存在,请查找你所需要的人材机,或新增人材机");
                         $('#alertModalCls').click(function () {
                             me.showGljItems(me.currentRationItem.ID);
                         });
@@ -457,24 +516,24 @@ var rationGLJOprObj = {
                     }
                 }
             },
-            error:function(err){
+            error: function (err) {
                 alert(err);
             }
         })
     },
-    round(v, e){
-        var t=1;
-        for(;e>0;t*=10,e--);
-        for(;e<0;t/=10,e++);
-        return Math.round(v*t)/t;
+    round(v, e) {
+        var t = 1;
+        for (; e > 0; t *= 10, e--);
+        for (; e < 0; t /= 10, e++);
+        return Math.round(v * t) / t;
     },
     rationCal: function () {
         let me = rationGLJOprObj;
         let rationBasePrc = 0;
-        if(me.currentRationItem && me.cache['_GLJ_' + me.currentRationItem.ID]){
+        if (me.currentRationItem && me.cache['_GLJ_' + me.currentRationItem.ID]) {
             let cacheArr = me.cache['_GLJ_' + me.currentRationItem.ID];
             cacheArr.forEach(function (gljData) {
-                if(gljData.gljType && gljData.basePrice && gljData.consumeAmt){
+                if (gljData.gljType && gljData.basePrice && gljData.consumeAmt) {
                     rationBasePrc += gljData.basePrice * gljData.consumeAmt;
                 }
             });
@@ -482,7 +541,7 @@ var rationGLJOprObj = {
         }
         return rationBasePrc;
     },
-    updateRationItem: function(callback) {
+    updateRationItem: function (callback) {
         var me = this, updateArr = [];
         if (me.currentRationItem) {
             me.currentRationItem.rationGljList = me.buildRationItemGlj();
@@ -491,27 +550,32 @@ var rationGLJOprObj = {
             me.currentRationItem.basePrice = price;
             updateArr.push(me.currentRationItem);
             rationOprObj.mixUpdateRequest(updateArr, [], [], function () {
-                if(callback) callback();
+                if (callback) callback();
             });
         }
     },
 
-    buildRationItemGlj: function(){
+    buildRationItemGlj: function () {
         var me = this, rst = [];
         if (me.currentRationItem && me.cache["_GLJ_" + me.currentRationItem.ID]) {
             var cacheArr = me.cache["_GLJ_" + me.currentRationItem.ID];
             for (var i = 0; i < cacheArr.length; i++) {
-                rst.push({
+                const rGlj = {
                     gljId: cacheArr[i].gljId,
                     consumeAmt: cacheArr[i].consumeAmt,
-                    proportion: cacheArr[i].proportion
-                });
+                    proportion: cacheArr[i].proportion,
+                    lossRateID: cacheArr[i].lossRateID,
+                    lossRateNo: cacheArr[i].lossRateNo,
+                    lossRateName: cacheArr[i].lossRateName,
+                    lossRate: cacheArr[i].lossRate
+                };
+                rst.push(rGlj);
             }
         }
         return rst;
     },
 
-    createRationGljDisplayItem: function(rItem, repGlj) {
+    createRationGljDisplayItem: function (rItem, repGlj) {
         var rst = {};
         rst.gljId = rItem.gljId;
         rst.consumeAmt = rItem.consumeAmt;
@@ -522,9 +586,13 @@ var rationGLJOprObj = {
         rst.unit = repGlj.unit;
         rst.basePrice = repGlj.basePrice;
         rst.gljType = repGlj.gljType;
+        rst.lossRateID = rItem.lossRateID;
+        rst.lossRateNo = rItem.lossRateNo;
+        rst.lossRateName = rItem.lossRateName;
+        rst.lossRate = rItem.lossRate;
         return rst;
     },
-    getGljItems: function(rationItem, callback) {
+    getGljItems: function (rationItem, callback) {
         var me = this, rationID = rationItem.ID, rationGljList = rationItem.rationGljList ? rationItem.rationGljList : [];
         me.currentRationItem = rationItem;
         if (me.cache["_GLJ_" + rationID]) {
@@ -534,43 +602,43 @@ var rationGLJOprObj = {
             for (var i = 0; i < rationGljList.length; i++) {
                 gljIds.push(rationGljList[i].gljId);
             }
-                $.ajax({
-                    type:"POST",
-                    url:"api/getGljItemsByIds",
-                    data:{"gljIds": JSON.stringify(gljIds)},
-                    dataType:"json",
-                    cache:false,
-                    timeout:5000,
-                    success:function(result){
-                        sheetCommonObj.cleanSheet(me.sheet, me.setting, -1);
-                        if (result) {
-                            if(priceProperties && priceProperties.length > 0){
-                                let priceField = priceProperties[0].price.dataCode;
-                                for(let glj of result.data){
-                                    glj.basePrice = glj.priceProperty && glj.priceProperty[priceField] ? glj.priceProperty[priceField] : 0;
-                                }
+            $.ajax({
+                type: "POST",
+                url: "api/getGljItemsByIds",
+                data: { "gljIds": JSON.stringify(gljIds) },
+                dataType: "json",
+                cache: false,
+                timeout: 5000,
+                success: function (result) {
+                    sheetCommonObj.cleanSheet(me.sheet, me.setting, -1);
+                    if (result) {
+                        if (priceProperties && priceProperties.length > 0) {
+                            let priceField = priceProperties[0].price.dataCode;
+                            for (let glj of result.data) {
+                                glj.basePrice = glj.priceProperty && glj.priceProperty[priceField] ? glj.priceProperty[priceField] : 0;
                             }
-                            var cacheArr = [];
-                            for (var j = 0; j < rationGljList.length; j++) {
-                                for (var i = 0; i < result.data.length; i++) {
-                                    if (rationGljList[j].gljId == result.data[i].ID) {
-                                        cacheArr.push(me.createRationGljDisplayItem(rationGljList[j], result.data[i]));
-                                        break;
-                                    }
+                        }
+                        var cacheArr = [];
+                        for (var j = 0; j < rationGljList.length; j++) {
+                            for (var i = 0; i < result.data.length; i++) {
+                                if (rationGljList[j].gljId == result.data[i].ID) {
+                                    cacheArr.push(me.createRationGljDisplayItem(rationGljList[j], result.data[i]));
+                                    break;
                                 }
                             }
-                            me.cache["_GLJ_" + rationID] = cacheArr;
-                            me.showGljItems(rationID);
                         }
-                        if(callback) callback();
-                    },
-                    error:function(err){
-                        alert(err);
+                        me.cache["_GLJ_" + rationID] = cacheArr;
+                        me.showGljItems(rationID);
                     }
-                })
+                    if (callback) callback();
+                },
+                error: function (err) {
+                    alert(err);
+                }
+            })
         }
     },
-    showGljItems: function(rationID) {
+    showGljItems: function (rationID) {
         var me = this;
         if (me.cache["_GLJ_" + rationID]) {
             sheetCommonObj.cleanData(me.sheet, me.setting, -1);

+ 26 - 2
web/maintain/report/html/rpt_tpl_dtl_info.html

@@ -83,12 +83,20 @@
                 </div>
                 <div class="input-group col-2">
                     <div class="input-group-addon">项目汇总级别</div>
-                    <select class="form-control input-sm" id="element_sumLv_flags" onchange="zTreeOprObj.onChangeFlag('sumLevelType', this)"><option value ="NA">N/A</option><option value ="construct">建设项目级别</option><option value ="Single">单项工程级别</option><option value ="custom">用户自选工程</option></select>
+                    <select class="form-control input-sm" id="element_sumLv_flags" onchange="zTreeOprObj.onChangeFlag('sumLevelType', this)">
+                        <option value ="NA">N/A</option>
+                        <option value ="construct">建设项目级别</option>
+                        <option value ="Single">单项工程级别</option>
+                        <option value ="custom">用户自选工程</option>
+                        <option value="complexUnit">跨项目自选工程(3个建设项目)</option>
+                        <option value="stageContrast">阶段对比</option>
+                        <option value="unitPriceContrast">单价偏差对比</option>
+                    </select>
                 </div>
             </div>
                
         </div>
-        <div>
+        <div class="row">
             <div class="input-group col-3">
                 <div class="input-group-addon">审核对比数据级别</div>
                 <select id="element_contrastType_select" class="form-control" onchange="zTreeOprObj.onChangeFlag('contrastType', this)">
@@ -98,6 +106,22 @@
                     <option value="quantities">工程量明细</option>
                 </select>
             </div>
+            <div class="input-group col-2">
+                <div class="input-group-addon">存在数据级别</div>
+                <select id="element_existLevel_select" class="form-control" onchange="zTreeOprObj.onChangeFlag('existLevel', this)">
+                    <option value="NA">N/A</option>
+                    <option value="true">是</option>
+                    <option value="false">否</option>
+                </select>
+            </div>
+            <div class="input-group col-3">
+                <div class="input-group-addon">建设项目汇总类型</div>
+                <select class="form-control input-sm" id="element_constructSumFlags_select"
+                    onchange="zTreeOprObj.onChangeFlag('constructSumType', this)">
+                    <option value="NA">N/A</option>
+                    <option value="constructSum">建设项目汇总</option>
+                </select>
+            </div>
         </div>
     </div>
 </div>

+ 22 - 0
web/maintain/report/js/rpt_tpl_main.js

@@ -953,6 +953,9 @@ let zTreeOprObj = {
                             if (sumLvType === 'construct') $("#element_sumLv_flags")[0].selectedIndex = 1
                             else if (sumLvType === 'Single') $("#element_sumLv_flags")[0].selectedIndex = 2
                             else if (sumLvType === 'custom') $("#element_sumLv_flags")[0].selectedIndex = 3
+                            else if (sumLvType === 'complexUnit') $("#element_sumLv_flags")[0].selectedIndex = 4
+                            else if (sumLvType === 'stageContrast') $("#element_sumLv_flags")[0].selectedIndex = 5
+                            else if (sumLvType === 'unitPriceContrast') $("#element_sumLv_flags")[0].selectedIndex = 6
                             else $("#element_sumLv_flags")[0].selectedIndex = 0;
                         } else {
                             $("#element_sumLv_flags")[0].selectedIndex = 0;
@@ -965,15 +968,34 @@ let zTreeOprObj = {
                             else if (contrastType === 'quantities') $("#element_contrastType_select")[0].selectedIndex = 3
                             else $("#element_contrastType_select")[0].selectedIndex = 0;
                         }
+                        if (me.currentNode.flags.hasOwnProperty('existLevel')) {
+                            let existLevel = me.currentNode.flags['existLevel'];
+                            if (existLevel) $("#element_existLevel_select")[0].selectedIndex = 1
+                            else $("#element_existLevel_select")[0].selectedIndex = 0;
+                        } else {
+                            $("#element_existLevel_select")[0].selectedIndex = 0;
+                        }
+                        if (me.currentNode.flags.hasOwnProperty('constructSumType')) {
+                            let val = me.currentNode.flags['constructSumType'];
+                            if (val === 'constructSum') {
+                                $("#element_constructSumFlags_select")[0].selectedIndex = 1;
+                            } else {
+                                $("#element_constructSumFlags_select")[0].selectedIndex = 0;
+                            }
+                        } else {
+                            $("#element_constructSumFlags_select")[0].selectedIndex = 0;
+                        }
                     } else {
                         $("#element_flags_select")[0].selectedIndex = 0;
                         $("#element_prjFlags_select")[0].selectedIndex = 0;
                         $("#element_sumLv_flags")[0].selectedIndex = 0;
+                        $("#element_constructSumFlags_select")[0].selectedIndex = 0;
                         // 清空工程类型
                         $("#valuationSelector").hide();
                         $("#element_prjFlags_select")[0].value = '';
                         $("#element_prjFlags_selectStr")[0].value = '';
                         $('#valuationSelector .mutiSelector li').removeClass('checked');
+                        $("#element_existLevel_select")[0].selectedIndex = 0;
                     }
 
                     if ($("#rpt_tpl_visual_tab")[0].className === "nav-link p-1 active") {

+ 8 - 1
web/maintain/std_glj_lib/js/glj.js

@@ -223,6 +223,7 @@ let repositoryGljObj = {
             }
         }
         let tailHeaders = [
+            { headerName: "损耗率", headerWidth: 90, dataCode: "lossRate", dataType: "Number", hAlign: "center", vAlign: "center" },
             { headerName: "毛重系数", headerWidth: 90, dataCode: "grossWeightCoe", dataType: "Number", hAlign: "center", vAlign: "center" },
             { headerName: "采购保管费率", headerWidth: 90, dataCode: "purchaseStorageRate", dataType: "Number", hAlign: "center", vAlign: "center" },
             { headerName: "场外运输损耗率", headerWidth: 100, dataCode: "offSiteTransportLossRate", dataType: "Number", hAlign: "center", vAlign: "center" },
@@ -836,7 +837,7 @@ let repositoryGljObj = {
                             args.sheet.setValue(args.row, args.col, _.find(me.distTypeTree.comboDatas, { value: me.currentGlj.gljType }).text);
                         });
                         return;
-                    } else if (me.feeDataCode.includes(dataCode) && rObj[dataCode] !== me.currentEditingGlj[dataCode]) {
+                    } else if ((me.feeDataCode.includes(dataCode) || dataCode === 'lossRate') && rObj[dataCode] !== me.currentEditingGlj[dataCode]) {
                         if (isNaN(rObj[dataCode])) {
                             args.sheet.setValue(args.row, args.col, me.currentEditingGlj[dataCode] ? me.currentEditingGlj[dataCode] : '');
                             alert(`${me.setting.header[args.col].headerName}只能输入数值!`);
@@ -1216,6 +1217,9 @@ let repositoryGljObj = {
         if (typeof pasteObj.unit !== 'undefined') {
             tempObj.unit = pasteObj.unit;
         }
+        if (typeof pasteObj.lossRate !== 'undefined' && !isNaN(pasteObj.lossRate)) {
+            tempObj.lossRate = pasteObj.lossRate;
+        }
         if (typeof pasteObj.gljType !== 'undefined') {
             let isExsit = false;
             for (let i = 0; i < me.distTypeTree.comboDatas.length; i++) {
@@ -1341,6 +1345,9 @@ let repositoryGljObj = {
                 }
             }
         }
+        if (typeof pasteObj.lossRate !== 'undefined' && isNaN(pasteObj[feeCode])) {
+            return false;
+        }
         for (let feeCode of me.feeDataCode) {
             if (typeof pasteObj[feeCode] !== 'undefined' && (isNaN(pasteObj[feeCode]) || me.getParentType(pasteObj.gljType) !== 2)) {
                 return false;

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

@@ -1083,6 +1083,17 @@ function deleteEngineerClick(engineerID, element) {
     }, null, ['确定', '取消'], false);
 }
 
+function copyEngineerClick(engineerID) {
+    hintBox.infoBox('操作确认', '是否拷贝所选工程专业?', 2, async function () {
+        try {
+            await ajaxPost('/compilation/copy-engineer', { id: engineerID });
+            window.location.reload();
+        } catch (err) {
+            console.log(err);
+        }
+    }, null, ['确定', '取消'], false);
+}
+
 
 function engineerVisibleChange(checkBox, engineerID) {
     if (engineerID) {

+ 3 - 1
web/users/js/manager.js

@@ -115,7 +115,9 @@ $(document).ready(function() {
         let topid = $(this).attr('data-controller');
         if($(this).is(':checked')) {
             // 选中
-            $('#'+ topid).prop('checked', true);
+            if (topid !== 'compilation') {
+                $('#' + topid).prop('checked', true);
+            }
         } else {
             // 取消选中(排除工具这个独立分离的url)
             if (topid !== 'tool') {

+ 1 - 1
web/users/views/compilation/add.html

@@ -101,7 +101,7 @@
                                     <td><%= engineering.ration_lib.length %></td>
                                     <td><%= engineering.glj_lib.length %></td>
                                     <td><label><input type="checkbox"  <% if (engineering.visible) { %>checked<% } %>  onclick='engineerVisibleChange(this,"<%= engineering._id.toString()%>")'> 显示</label></td>
-                                    <td><a class="btn-link" href="/compilation/<%= section %>/<%= valuationId %>/<%= engineering._id.toString()%>">编辑</a>/<a onclick="deleteEngineerClick('<%= engineering._id.toString()%>',this)" class='btn btn-link btn-sm' style="padding: 0px">删除</a></td>
+                                    <td><a class="btn-link" href="/compilation/<%= section %>/<%= valuationId %>/<%= engineering._id.toString()%>">编辑</a>/<a onclick="deleteEngineerClick('<%= engineering._id.toString()%>',this)" class='btn btn-link btn-sm' style="padding: 0px">删除</a>/<a onclick="copyEngineerClick('<%= engineering._id.toString()%>')" class='btn btn-link btn-sm' style="padding: 0px">拷贝</a></td>
                                 </tr>
                                 <% }) %>
                             </tbody>

+ 10 - 0
web/users/views/manager/authority.html

@@ -83,6 +83,16 @@
                     </div>
                     <% } %>
                     <% } %>
+                                        <div class="form-group">
+                        <label for="title">编办权限</label>
+                        <div class="checkbox" id="compilation-area" style="max-height: 150px; overflow: auto; margin-top: 0 !important;">
+                            <% for(let compilation of compilationList){ %>
+                            <label>
+                                <input type="checkbox" data-controller="compilation" name="permission_compilation[]" value="<%= compilation._id %>"> <%= compilation.name %>
+                            </label>&nbsp;
+                            <% } %>
+                        </div>
+                    </div>
                 </div>
                 <div class="modal-footer">
                     <button type="submit" class="btn btn-primary">确定</button>