Browse Source

Merge branch 'master' of http://smartcost.f3322.net:3000/SmartCost/ConstructionCost

chenshilong 7 years ago
parent
commit
0baa1e4650
44 changed files with 1186 additions and 210 deletions
  1. 1 0
      modules/all_models/user.js
  2. 3 1
      modules/common/helper/mongoose_helper.js
  3. 1 1
      modules/main/routes/main_route.js
  4. 34 0
      modules/pm/controllers/pm_controller.js
  5. 2 1
      modules/pm/models/project_model.js
  6. 1 0
      modules/pm/routes/pm_route.js
  7. 6 2
      modules/users/controllers/login_controller.js
  8. 80 0
      modules/users/controllers/sms_controller.js
  9. 1 1
      modules/users/controllers/user_controller.js
  10. 1 1
      modules/users/models/log_model.js
  11. 97 0
      modules/users/models/sms_model.js
  12. 27 5
      modules/users/models/user_model.js
  13. 21 0
      modules/users/routes/sms_route.js
  14. 1 0
      public/web/tree_sheet/tree_sheet_controller.js
  15. 6 3
      public/web/tree_sheet/tree_sheet_helper.js
  16. 2 2
      server.js
  17. 4 4
      test/unit/reports/test_cover_01.js
  18. 3 3
      test/unit/reports/test_cover_02.js
  19. 2 2
      test/unit/reports/test_get_prj_data.js
  20. 2 2
      test/unit/reports/test_preview_page.js
  21. 106 0
      test/unit/reports/test_tpl_07.js
  22. 2 2
      test/unit/reports/test_tpl_09.js
  23. 2 2
      test/unit/reports/test_tpl_09_1.js
  24. 3 3
      web/building_saas/css/main.css
  25. 11 4
      web/building_saas/main/html/main.html
  26. 1 1
      web/building_saas/main/js/controllers/project_controller.js
  27. 0 1
      web/building_saas/main/js/main.js
  28. 10 1
      web/building_saas/main/js/models/bills.js
  29. 27 0
      web/building_saas/main/js/models/project.js
  30. 1 1
      web/building_saas/main/js/models/quantity_detail.js
  31. 7 2
      web/building_saas/main/js/views/glj_view.js
  32. 23 4
      web/building_saas/main/js/views/main_tree_col.js
  33. 194 69
      web/building_saas/main/js/views/project_view.js
  34. 45 0
      web/building_saas/pm/html/project-management-Recycle.html
  35. 257 48
      web/building_saas/pm/js/pm_gc.js
  36. 1 1
      web/building_saas/pm/js/pm_newMain.js
  37. 4 14
      web/building_saas/report/html/rpt_main.html
  38. 1 1
      web/common/html/header.html
  39. 2 1
      web/common/html/page.html
  40. 34 0
      web/users/html/login.html
  41. 3 3
      web/users/html/user-info.html
  42. 8 8
      web/users/html/user-safe.html
  43. 15 16
      web/users/html/user-set.html
  44. 134 0
      web/users/js/login.js

+ 1 - 0
modules/all_models/user.js

@@ -13,6 +13,7 @@ let collectionName = 'user';
 
 // 表结构
 let schema = {
+    ssoId: Number,
     username: String,
     email: String,
     mobile: String,

+ 3 - 1
modules/common/helper/mongoose_helper.js

@@ -61,9 +61,11 @@ class MongooseHelper {
         let self = this;
         let limit = 0;
         let skip = 0;
+        let sort = {_id:1};
         if (option !== null && Object.keys(option).length > 0) {
             limit = option.pageSize !== undefined ? option.pageSize : limit;
             skip = option.offset !== undefined ? option.offset : skip;
+            sort = option.sort !== undefined ? option.sort : sort;
         }
 
         return new Promise(function (resolve, reject) {
@@ -73,7 +75,7 @@ class MongooseHelper {
                 } else {
                     resolve(data);
                 }
-            }).skip(skip).limit(limit);
+            }).sort(sort).skip(skip).limit(limit);
         });
     }
 

+ 1 - 1
modules/main/routes/main_route.js

@@ -19,7 +19,7 @@ module.exports =function (app) {
                     {
                         userAccount: req.session.userAccount,
                         userID: req.session.sessionUser.id,
-                        projectData: projectData,
+                        projectData: projectData
                     });
             } else {
                 res.redirect('/pm');

+ 34 - 0
modules/pm/controllers/pm_controller.js

@@ -13,6 +13,8 @@ let fee_rate_facade = require("../../fee_rates/facade/fee_rates_facade");
 let billsModel = require('../../main/models/bills').model;
 let rationsModel = require('../../main/models/ration').model;
 let projectModel = mongoose.model('projects');
+let unitPriceFileModel = mongoose.model('unit_price_file');
+let feeRateFileModel = mongoose.model('fee_rate_file');
 let asyncTool = require('async');
 
 //统一回调函数
@@ -353,5 +355,37 @@ module.exports = {
         ProjectsData.recGC(userID, nodes, function (err, msg, data) {
             callback(request, response, err, msg, data);
         });
+    },
+
+    delGC: async function(request, response){
+        let data = JSON.parse(request.body.data);
+        let delDatas = data.delDatas;
+        let bulkProjs = [], bulkUFs = [], bulkFFs = [];
+        try{
+            for(let data of delDatas){
+                if(data.updateType === 'Project'){
+                    bulkProjs.push({deleteOne: {filter: {ID: data.ID}}});
+                }
+                else if(data.updateType === fileType.unitPriceFile){
+                    bulkUFs.push({deleteOne: {filter: {id: data.ID}}});
+                }
+                else{
+                    bulkFFs.push({deleteOne: {filter: {ID: data.ID}}});
+                }
+            }
+            if(bulkProjs.length > 0){
+                await projectModel.bulkWrite(bulkProjs);
+            }
+            if(bulkUFs.length > 0){
+                await unitPriceFileModel.bulkWrite(bulkUFs);
+            }
+            if(bulkFFs.length > 0){
+                await feeRateFileModel.bulkWrite(bulkFFs);
+            }
+            callback(request, response, 0, 'success', null);
+        }
+        catch(err){
+            callback(request, response, 1, err, null);
+        }
     }
 };

+ 2 - 1
modules/pm/models/project_model.js

@@ -145,7 +145,8 @@ ProjectsDAO.prototype.updateUserProjects = async function (userId, compilationId
                     if(parseInt(data.updateData.property.engineering)==4){
                         await installationFacade.copyInstallationFeeFromLib(data.updateData.ID,data.updateData.property.engineering_id);
                     }
-
+                    //锁定清单
+                    data.updateData.property.lockBills = false;
 
                 }
                 newProject = new Projects(data.updateData);

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

@@ -48,6 +48,7 @@ module.exports = function (app) {
     //GC
     pmRouter.post('/getGCDatas', pmController.getGCDatas);
     pmRouter.post('/recGC', pmController.recGC);
+    pmRouter.post('/delGC', pmController.delGC);
 
     app.use('/pm/api', pmRouter);
 };

+ 6 - 2
modules/users/controllers/login_controller.js

@@ -54,12 +54,17 @@ class LoginController {
 
             // 正确登录后 存入session
             let userData = responseData[0];
+
+            if (userData.mobile === '') {
+                return response.json({error: 2,ssoId: userData.id});
+            }
             let sessionUser = {
                 ssoId: userData.id,
                 username: userData.username,
                 email: userData.useremail,
                 mobile: userData.mobile,
             };
+
             request.session.sessionUser = sessionUser;
             // 记录用户数据到数据库
             let result = await userModel.markUser(sessionUser, request);
@@ -76,6 +81,7 @@ class LoginController {
             compilationList = preferenceSetting.login_ask === 1 ? await compilationModel.getList() : [];
             // 获取编办信息
             let sessionCompilation = request.session.sessionCompilation;
+
             if (preferenceSetting.login_ask === 0 && !sessionCompilation &&
                 preferenceSetting.select_version !== '') {
 
@@ -83,12 +89,10 @@ class LoginController {
                 request.session.sessionCompilation = compilationData;
             }
 
-
         } catch (error) {
             console.log(error);
             return response.json({error: 1, msg: error});
         }
-        console.log(request.session.lastPage);
         response.json({
             error: 0,
             msg: '',

+ 80 - 0
modules/users/controllers/sms_controller.js

@@ -0,0 +1,80 @@
+/**
+ * 短信相关控制器
+ *
+ * @author EllisRan
+ * @date 2018/4/17
+ * @version
+ */
+import SmsModel from "../models/sms_model";
+
+class SmsController {
+
+    /**
+     * 发送短信验证码
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async code(request, response) {
+        let responseData = {
+            err: 0,
+            msg: ''
+        };
+        let mobile = request.body.mobile;
+        let type = request.body.type;
+        try {
+            let smsModel = new SmsModel();
+            let returnStatus = await smsModel.sendSmsFromSSO(mobile, type);
+            if (returnStatus === null) {
+                throw '获取数据失败!';
+            }
+            if(returnStatus != 1){
+                responseData.err = 1;
+                responseData.msg = await smsModel.getStatusMsg(returnStatus);
+            }
+        } catch (error) {
+            console.log(error);
+            responseData.err = 1;
+            responseData.msg = error;
+        }
+
+        response.json(responseData);
+    }
+
+    /**
+     * 手机和验证码验证并更新sso
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async setMobile(request, response) {
+        let responseData = {
+            err: 0,
+            msg: ''
+        };
+        let ssoId = request.body.ssoId;
+        let mobile = request.body.mobile;
+        let code = request.body.code;
+        try {
+            let smsModel = new SmsModel();
+            let returnStatus = await smsModel.checkCodeFromSSO(ssoId, mobile, code);
+            if (returnStatus === null) {
+                throw '获取数据失败!';
+            }
+            if(returnStatus != 1){
+                responseData.err = 1;
+                responseData.msg = await smsModel.getStatusMsg(returnStatus);
+            }
+        } catch (error) {
+            console.log(error);
+            responseData.err = 1;
+            responseData.msg = error;
+        }
+
+        response.json(responseData);
+    }
+}
+
+export default SmsController;

+ 1 - 1
modules/users/controllers/user_controller.js

@@ -59,7 +59,7 @@ class UserController extends BaseController {
             let sessionUser = request.session.sessionUser;
             // 切换验证场景
             let userModel = new UserModel();
-            let condition = {email: sessionUser.email};
+            let condition = {ssoId: sessionUser.ssoId};
             userModel.setScene('saveInfo');
             let result = await userModel.updateUser(condition, updateData);
 

+ 1 - 1
modules/users/models/log_model.js

@@ -95,7 +95,7 @@ class LogModel extends BaseModel {
         };
         page = parseInt(page);
         page = page <= 1 ? 1 : page;
-        let option = {pageSize: pageSize, offset: parseInt((page - 1) * pageSize)};
+        let option = {pageSize: pageSize, offset: parseInt((page - 1) * pageSize), sort: {create_time:-1}};
 
         let logList = await this.db.find(condition, null, option);
         logList = logList.length > 0 ? logList : [];

+ 97 - 0
modules/users/models/sms_model.js

@@ -0,0 +1,97 @@
+/**
+ * 短信业务模型
+ *
+ * @author EllisRan
+ * @date 2018/4/17
+ * @version
+ */
+import Request from "request";
+import BaseModel from "../../common/base/base_model";
+
+class SmsModel extends BaseModel {
+
+    /**
+     * 状态信息
+     *
+     * @var {object}
+     */
+    statusMsg = { 1:'发送成功', 2:'该手机号已注册', 3:'该手机号未注册', 0:'参数有误',
+        4:'短信接口方出错', 5:'验证码添加到数据库出错', 6:'手机号更改到数据库出错',
+        7:'验证码有误', 8:'sso账号不存在' };
+
+    /**
+     * 根据手机号和短信类型调用SSO短信接口获取信息
+     *
+     * @param {string} mobile
+     * @param {int} type  短信用途: 1->未注册过sso手机或更换手机号; 2->找回密码或修改密码;
+     * @return {int}  返回状态码
+     */
+    async sendSmsFromSSO(mobile, type) {
+        let postData = {
+            url: 'http://sso.smartcost.com.cn/building/api/smscode',
+            form: {mobile: mobile, type: type},
+            encoding: 'utf8'
+        };
+        return new Promise(function (resolve, reject) {
+            try {
+                // 请求接口
+                Request.post(postData, function (err, postResponse, body) {
+                    if (err) {
+                        throw '请求错误';
+                    }
+                    if (postResponse.statusCode !== 200) {
+                        throw '通行证验证失败!';
+                    }
+                    resolve(body);
+                });
+            } catch (error) {
+                reject([]);
+            }
+        });
+    }
+
+    /**
+     * 根据手机号和短信调用SSO短信接口并更新手机号
+     *
+     * @param {string} ssoid
+     * @param {string} mobile
+     * @param {string} code
+     * @return {int}  返回状态码
+     */
+    async checkCodeFromSSO(ssoid, mobile, code) {
+        let postData = {
+            url: 'http://sso.smartcost.com.cn/building/api/set/mobile',
+            form: {ssoId: ssoid, mobile: mobile, code: code},
+            encoding: 'utf8'
+        };
+        return new Promise(function (resolve, reject) {
+            try {
+                // 请求接口
+                Request.post(postData, function (err, postResponse, body) {
+                    if (err) {
+                        throw '请求错误';
+                    }
+                    if (postResponse.statusCode !== 200) {
+                        throw '通行证验证失败!';
+                    }
+                    resolve(body);
+                });
+            } catch (error) {
+                reject([]);
+            }
+        });
+    }
+
+
+    /**
+     * 根据状态码获取反馈信息
+     *
+     * @param {string} status
+     * @return {string}
+     */
+    async getStatusMsg(status) {
+        return this.statusMsg[status];
+    }
+}
+
+export default SmsModel;

+ 27 - 5
modules/users/models/user_model.js

@@ -84,17 +84,28 @@ class UserModel extends BaseModel {
      * @return {Promise}
      */
     async markUser(userData, request = null) {
-        let userDataFromDb = await this.findDataByName(userData.username);
+        let userDataFromDb2 = await this.findDataBySsoId(userData.ssoId);
+        let userDataFromDb = await this.findDataByName(userData.username);  //后面新增的账号可淘汰这方法,当前使用是为了兼容旧的账号
         let result = false;
 
-        if (userDataFromDb === null) {
+        if (userDataFromDb === null && userDataFromDb2 === null) {
             // 不存在用户则入库
             result = await this.addUser(userData);
             userDataFromDb = result;
         } else {
-            // 存在则新增登录信息
-            let logModel = new LogModel();
-            result = await logModel.addLoginLog(userDataFromDb._id, request);
+            // 存在则新增登录信息并更新账号信息
+            // let condition = {ssoId: sessionUser.ssoId};
+            let condition = {username: userData.username};
+            let UpdateData = {
+                email : userData.email,
+                mobile : userData.mobile,
+                ssoId : userData.ssoId
+            };
+            let updateResult = await this.updateUser(condition,UpdateData);
+            if (updateResult.ok === 1) {
+                let logModel = new LogModel();
+                result = await logModel.addLoginLog(userDataFromDb._id, request);
+            }
         }
         request.session.sessionUser.id = userDataFromDb._id;
         request.session.sessionUser.real_name = userDataFromDb.real_name;
@@ -129,6 +140,16 @@ class UserModel extends BaseModel {
     }
 
     /**
+     * 根据ssoID查找数据
+     *
+     * @param {string} ssoId
+     * @return {object}
+     */
+    findDataBySsoId(ssoId) {
+        return this.db.findOne({ssoId: ssoId});
+    }
+
+    /**
      * 新增用户
      *
      * @param {object} userData
@@ -136,6 +157,7 @@ class UserModel extends BaseModel {
      */
     addUser(userData) {
         let insertData = {
+            ssoId: userData.ssoId,
             username: userData.username,
             email: userData.email,
             mobile: userData.mobile,

+ 21 - 0
modules/users/routes/sms_route.js

@@ -0,0 +1,21 @@
+/**
+ * 短信相关路由
+ *
+ * @author EllisRan
+ * @date 2018/4/17
+ * @version
+ */
+import Express from "express";
+import SmsController from "../controllers/sms_controller";
+
+module.exports = function (app) {
+
+    const router = Express.Router();
+    const smsController = new SmsController();
+
+// action定义区域
+    router.post('/code', smsController.code);
+    router.post('/mobile', smsController.setMobile);
+
+    app.use('/sms',router);
+};

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

@@ -16,6 +16,7 @@ var TREE_SHEET_CONTROLLER = {
         };
 
         controller.prototype.showTreeData = function () {
+            //
             var that = this;
             TREE_SHEET_HELPER.showTreeData(this.setting, this.sheet, this.tree);
             this.sheet.unbind(GC.Spread.Sheets.Events.SelectionChanged);

+ 6 - 3
public/web/tree_sheet/tree_sheet_helper.js

@@ -105,7 +105,7 @@ var TREE_SHEET_HELPER = {
         nodes.forEach(function (node) {
             let iRow = node.serialNo();
             if(typeof projectObj !== 'undefined'){
-                let nodeStyle = projectObj.getNodeColorStyle(node);
+                let nodeStyle = projectObj.getNodeColorStyle(sheet, node);
                 if(nodeStyle){
                     sheet.setStyle(iRow, -1, nodeStyle);
                 }
@@ -118,6 +118,7 @@ var TREE_SHEET_HELPER = {
                         sheet.setStyle(iRow, iCol, boldFontStyle);
                     }
                 }
+
                 // var getFieldText = function () {
                 //     var fields = colSetting.data.field.split('.');
                 //     var validField = fields.reduce(function (field1, field2) {
@@ -159,7 +160,7 @@ var TREE_SHEET_HELPER = {
                 if(colSetting.data.autoHeight == true){
                     colSetting.setAutoHeight(cell,node);
                 }
-                if(sheet.name()=='mainSheet'&&gljOprObj.isInstallationNode(node)){//如果是通过安装增加费自动生成的,都是只读类型
+                if(colSetting.editChecking&&colSetting.editChecking(node)){
                     cell.locked(true);
                 }else if (colSetting.readOnly) {
                     if (Object.prototype.toString.apply(colSetting.readOnly) === "[object Function]") {
@@ -171,7 +172,9 @@ var TREE_SHEET_HELPER = {
                     cell.locked(false);
                 }
             });
-            sheet.autoFitRow(node.serialNo());
+            if(setting.setAutoFitRow){
+                setting.setAutoFitRow(sheet,node)
+            }
             if (recursive) {
                 TREE_SHEET_HELPER.refreshTreeNodeData(setting, sheet, node.children, recursive);
             }

+ 2 - 2
server.js

@@ -55,8 +55,8 @@ app.use(session({
 // 登录状态全局判断
 app.use(function (req, res, next) {
     let url = req.originalUrl;
-    if (/^\/login/.test(url) || /\.map|\.ico$/.test(url)) {
-        // 如果是登录页面则忽略判断数据
+    if (/^\/login/.test(url) || /\.map|\.ico$/.test(url) || /^\/sms/.test(url)) {
+        // 如果是登录页面或短信接口则忽略判断数据
         next();
     } else {
         try {

+ 4 - 4
test/unit/reports/test_cover_01.js

@@ -28,11 +28,11 @@ let demoPrjId = - 1;
 let demoRptId = 223, pagesize = "A4";
 
 demoPrjId = 1220; //QA:
-/*
- let userId_Leng = 1142; //小冷User Id
- let userId_Dft = userId_Leng;
+//*
+let userId_Leng = "59cdf14a0034a1000ba52b97"; //小冷User Id 换成_id了
+let userId_Dft = userId_Leng;
 /*/
-let userId_Dft = 76075;
+ let userId_Dft = "595328da1934dc327cad08eb";
 //*/
 
 let rptTplFacade = require("../../../modules/reports/facade/rpt_template_facade");

+ 3 - 3
test/unit/reports/test_cover_02.js

@@ -30,9 +30,9 @@ let demoRptId = 229, pagesize = "A4";
 let userId_Leng = 1142; //小冷User Id
 demoPrjId = 1296; //QA:
 //*/
- let userId_Dft = userId_Leng;
- /*/
-let userId_Dft = 76075;
+let userId_Leng = "59cdf14a0034a1000ba52b97"; //小冷User Id 换成_id了
+/*/
+ let userId_Dft = "595328da1934dc327cad08eb";
 //*/
 
 let rptTplFacade = require("../../../modules/reports/facade/rpt_template_facade");

+ 2 - 2
test/unit/reports/test_get_prj_data.js

@@ -32,10 +32,10 @@ let demoPrjId = - 1;
 // demoPrjId = 720; //QA: DW3
 demoPrjId = 1626; //QA:
 //*/
-let userId_Leng = 1142; //小冷User Id
+let userId_Leng = "59cdf14a0034a1000ba52b97"; //小冷User Id 换成_id了
 let userId_Dft = userId_Leng;
 /*/
- let userId_Dft = 76075;
+ let userId_Dft = "595328da1934dc327cad08eb";
  //*/
 
 let fs = require('fs');

+ 2 - 2
test/unit/reports/test_preview_page.js

@@ -32,10 +32,10 @@ let pagesize = "A4";
 // demoPrjId = 720; //QA: DW3
 // demoPrjId = 1626; //QA:
 //*/
-let userId_Leng = 1142; //小冷User Id
+let userId_Leng = "59cdf14a0034a1000ba52b97"; //小冷User Id 换成_id了
 let userId_Dft = userId_Leng;
 /*/
- let userId_Dft = 76075;
+ let userId_Dft = "595328da1934dc327cad08eb";
  //*/
 
 let rptTplFacade = require("../../../modules/reports/facade/rpt_template_facade");

+ 106 - 0
test/unit/reports/test_tpl_07.js

@@ -0,0 +1,106 @@
+/**
+ * Created by Tony on 2018/4/13.
+ */
+
+let test = require('tape');
+import JpcEx from "../../../modules/reports/rpt_component/jpc_ex";
+import JV from "../../../modules/reports/rpt_component/jpc_value_define";
+let config = require("../../../config/config.js");
+config.setupDb(process.env.NODE_ENV);
+let mongoose = require("mongoose");
+let fileUtils = require("../../../modules/common/fileUtils");
+let path = require('path');
+let dbm = require("../../../config/db/db_manager");
+let rpt_cfg = require('./rpt_cfg');
+dbm.connect(process.env.NODE_ENV);
+
+//统一引用models
+fileUtils.getGlobbedFiles('../../../modules/all_models/*.js').forEach(function(modelPath) {
+    require(path.resolve(modelPath));
+});
+
+let cfgCacheUtil = require("../../../config/cacheCfg");
+cfgCacheUtil.setupDftCache();
+
+let fsUtil = require("../../../public/fsUtil");
+
+let demoPrjId = - 1;
+let demoRptId = 239, pagesize = "A4";
+
+// demoPrjId = 720; //QA: DW3
+demoPrjId = 1626; //QA:
+//*/
+let userId_Leng = "59cdf14a0034a1000ba52b97"; //小冷User Id 换成_id了
+let userId_Dft = userId_Leng;
+/*/
+let userId_Dft = "595328da1934dc327cad08eb";
+//*/
+
+let rptTplFacade = require("../../../modules/reports/facade/rpt_template_facade");
+let rptTplDataFacade = require("../../../modules/reports/facade/rpt_tpl_data_facade");
+
+import rptDataExtractor from "../../../modules/reports/util/rpt_construct_data_util";
+
+let fs = require('fs');
+//设置Date Format函数
+fs.readFile(__dirname.slice(0, __dirname.length - 18) + '/public/web/date_util.js', 'utf8', 'r', function (err, data) {
+    eval(data);
+});
+
+//*
+test('测试 - 测试模板07表: ', function (t) {
+    rptTplFacade.getRptTemplate(demoRptId).then(function(rptTpl) {
+        let rptDataUtil = new rptDataExtractor();
+        rptDataUtil.initialize(rptTpl._doc);
+        let filter = rptDataUtil.getDataRequestFilter();
+        console.log(filter);
+        //正常应该根据报表模板定义的数据类型来请求数据
+        rptTplDataFacade.prepareProjectData(userId_Dft, demoPrjId, filter, function (err, msg, rawDataObj) {
+            if (!err) {
+                try {
+                    // fsUtil.writeObjToFile(rawDataObj, "D:/GitHome/ConstructionCost/tmp/rptTplRawDataObject.jsp");
+                    let tplData = rptDataUtil.assembleData(rawDataObj);
+                    //it's time to build the report!!!
+                    let printCom = JpcEx.createNew();
+                    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pagesize;
+                    let defProperties = rpt_cfg;
+                    let dftOption = JV.PAGING_OPTION_NORMAL;
+                    printCom.initialize(rptTpl);
+                    // fsUtil.writeObjToFile(tplData, "D:/GitHome/ConstructionCost/tmp/rptAssembledData.jsp");
+                    // fsUtil.writeObjToFile(rawDataObj, "D:/GitHome/ConstructionCost/tmp/rptRawDataAfterAssembled.jsp");
+                    printCom.analyzeData(rptTpl, tplData, defProperties, dftOption);
+                    let maxPages = printCom.totalPages;
+                    let pageRst = printCom.outputAsSimpleJSONPageArray(rptTpl, tplData, 1, maxPages, defProperties);
+                    if (pageRst) {
+                        fsUtil.writeObjToFile(pageRst, "D:/GitHome/ConstructionCost/tmp/rptPageResult.jsp");
+                    } else {
+                        console.log("oh! no pages were created!");
+                    }
+                } catch (ex) {
+                    console.log(ex);
+                    t.pass('pass with exception!');
+                    t.end();
+                }
+
+                t.pass('pass succeeded!');
+                t.end();
+            } else {
+                console.log(msg);
+                t.pass('pass with error!');
+                t.end();
+            }
+        })
+    });
+});
+//*/
+
+test('close the connection', function (t) {
+    setTimeout(function () {
+        mongoose.disconnect();
+        t.pass('closing db connection');
+        t.end();
+    }, 3000);
+    // mongoose.disconnect();
+    // t.pass('closing db connection');
+    // t.end();
+});

+ 2 - 2
test/unit/reports/test_tpl_09.js

@@ -30,10 +30,10 @@ let demoRptId = 232, pagesize = "A4";
 // demoPrjId = 720; //QA: DW3
 demoPrjId = 1626; //QA:
 //*/
-let userId_Leng = 1142; //小冷User Id
+let userId_Leng = "59cdf14a0034a1000ba52b97"; //小冷User Id 换成_id了
 let userId_Dft = userId_Leng;
 /*/
-let userId_Dft = 76075;
+ let userId_Dft = "595328da1934dc327cad08eb";
 //*/
 
 let rptTplFacade = require("../../../modules/reports/facade/rpt_template_facade");

+ 2 - 2
test/unit/reports/test_tpl_09_1.js

@@ -27,13 +27,13 @@ let fsUtil = require("../../../public/fsUtil");
 let demoPrjId = - 1;
 let demoRptId = 226, pagesize = "A4";
 
-let userId_Leng = 1142; //小冷User Id
+let userId_Leng = "59cdf14a0034a1000ba52b97"; //小冷User Id 换成_id了
 // demoPrjId = 720; //QA: DW3
 demoPrjId = 1626; //QA:
 //*/
 let userId_Dft = userId_Leng;
 /*/
-let userId_Dft = 76075;
+ let userId_Dft = "595328da1934dc327cad08eb";
 //*/
 
 let rptTplFacade = require("../../../modules/reports/facade/rpt_template_facade");

+ 3 - 3
web/building_saas/css/main.css

@@ -367,9 +367,9 @@ div.resize{
     width: 100%;
     cursor: s-resize;
 }
-.zlfb-check{
-    margin-left: 0;
-}
+/*.zlfb-check{
+    margin-left: 20px;
+}*/
 legend.legend{
     display:block;
     width:auto;

+ 11 - 4
web/building_saas/main/html/main.html

@@ -21,6 +21,7 @@
     <!-- endinject -->
     <script>
         // 这里的变量供页面调用
+        let lockBills = '<%- projectData.property.lockBills %>';
         let userAccount = '<%- userAccount %>';
         let userID = '<%- userID %>';
     </script>
@@ -63,9 +64,9 @@
                     <a href="javascript:void(0)" class="btn btn-sm" id="downLevel" title="降级"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
                     <a href="javascript:void(0)" class="btn btn-sm" id="downMove" title="下移"><i class="fa fa-arrow-down" aria-hidden="true"></i></a>
                     <a href="javascript:void(0)" class="btn btn-sm" id="upMove" title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
-                      <span>
-                  <a href="" class="btn btn-sm" data-toggle="dropdown"><b data-toggle="tooltip" data-placement="bottom">显示至...</b></a>
-                  <div class="dropdown-menu dropdown-menu-left" style="min-width: 6.5rem">
+                    <span>
+                      <a href="" class="btn btn-sm" data-toggle="dropdown"><b data-toggle="tooltip" data-placement="bottom">显示至...</b></a>
+                      <div class="dropdown-menu dropdown-menu-left" style="min-width: 6.5rem">
                       <a class="dropdown-item" href="javascript:void(0);" style="padding: 0rem 1.5rem" id="displayDXFY">大项费用</a>
                       <a class="dropdown-item" href="javascript:void(0);" style="padding: 0rem 1.5rem" id="displayFB1">一级分部</a>
                       <a class="dropdown-item" href="javascript:void(0);" style="padding: 0rem 1.5rem" id="displayFB2">二级分部</a>
@@ -75,7 +76,13 @@
                       <a class="dropdown-item" href="javascript:void(0);" style="padding: 0rem 1.5rem" id="displayZM">子目</a>
                       <a class="dropdown-item" href="javascript:void(0);" style="padding: 0rem 1.5rem" id="displayZD">最底层</a>
                   </div>
-                </span>
+                    </span>
+                      <% if (projectData.property.lockBills == true) { %>
+                      <a href="javascript:void(0)" class="btn btn-sm" name="lockBills"  title="解锁清单"> <i class="fa fa-unlock-alt" aria-hidden="true"></i> 解锁清单</a>
+                      <% } else { %>
+                      <a href="javascript:void(0)" class="btn btn-sm" name="lockBills"  title="锁定清单"> <i class="fa fa-lock" aria-hidden="true"></i> 锁定清单</a>
+                      <% } %>
+
                   </div>
                   <div class="tools-btn">
                       <a href="javacript:void(0);" data-toggle="modal" data-target="#column" class="btn btn-sm"><i class="fa fa-table" aria-hidden="true"></i> 列设置</a>

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

@@ -30,7 +30,7 @@ ProjectController = {
             });
             for(let newNode of newNodes){
                 sc.sheet.addRows(newNode.serialNo(), 1);
-                TREE_SHEET_HELPER.refreshTreeNodeData(sc.setting, sc.sheet, [newNode], false);
+               // TREE_SHEET_HELPER.refreshTreeNodeData(sc.setting, sc.sheet, [newNode], false);
                 sc.setTreeSelected(newNode);
                 sc.sheet.setSelection(newNode.serialNo(), sels[0].col, 1, 1);
                 sc.sheet.showRow(newNode.serialNo(), GC.Spread.Sheets.VerticalPosition.center);

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

@@ -42,7 +42,6 @@ $(function () {
             setLocalCache('lastCol:' + projectId, info.col);
         }
     });
-
 });
 
 /**

+ 10 - 1
web/building_saas/main/js/models/bills.js

@@ -349,11 +349,14 @@ var Bills = {
             return node.downLevel();
         };
 
-        bills.prototype.updateField = function (node, field, newValue) {
+        bills.prototype.updateField = function (node, field, newValue,toBX) {//当toBX为true时类型改为补项
             calcFees.setFee(node.data, field, newValue);
             let updateData = [];
             let data = {'ID': node.getID(), 'projectID': this.project.ID()};
             data[field] = newValue;
+            if(toBX == true){
+                data.type = billType.BX
+            }
             updateData.push({'updateType': 'ut_update', 'updateData': tools.formatBillsUpdateData(data)});
             this.project.pushNow('updateBills', this.getSourceType(), updateData);
         };
@@ -505,6 +508,12 @@ var Bills = {
                 return false;
             }
         };
+        bills.prototype.isBX = function (node) {//判读是否属于补项
+            if(node && node.sourceType == ModuleNames.bills&&node.data.type==billType.BX){
+                return  true;
+            }
+            return false;
+        };
         bills.prototype.isTechMeasure = function (node) {//判读是否属于技术措施项目部分
             let techMeasureCheck = function (checkNode) {
                 if(isFlag(checkNode.data)&&checkNode.data.flagsIndex.fixed.flag==fixedFlag.CONSTRUCTION_TECH){

+ 27 - 0
web/building_saas/main/js/models/project.js

@@ -328,6 +328,33 @@ var PROJECT = {
                 });
             }
         };
+        project.prototype.updateLockBills = function (value,callback) {
+            let mixDatas = {
+                projectID: projectObj.project.ID(),
+                updateType: 'update',
+                properties: {
+                    "property.lockBills":value
+                },
+                labourCoes: {},
+                rations: [],
+                bills: []
+            };
+            $.bootstrapLoading.start();
+            CommonAjax.post('/pm/api/updateMixDatas', {user_id: userID, mixDataArr: mixDatas}, function (rstData) {
+                projectInfoObj.projectInfo.property.lockBills = value;
+                if(callback){
+                    callback();
+                }
+                $.bootstrapLoading.end();
+            });
+        };
+        //返回清单是否被锁定
+        project.prototype.isBillsLocked =function(){
+            if(projectInfoObj.projectInfo.property.lockBills == true){
+                return true;
+            }
+            return false;
+        };
         project.prototype.updateNodes = function (datas,callback) {
           /*  let datas = [
                 {

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

@@ -571,7 +571,7 @@ var quantity_detail = {
             console.log(value);
             let needUpdateChildren = [];//需更新的子定额
             let gljNodes=[];//当定额工程量改变时需刷新的子工料机
-            if(node.children.length>0){//如果有子项
+            if(node.children.length>0){//如果有子项
                 for(let rationNode of node.children){
                     let EXPString = rationNode.data.quantityEXP+"";
                     if(EXPString.indexOf("QDL")!=-1){

+ 7 - 2
web/building_saas/main/js/views/glj_view.js

@@ -317,7 +317,12 @@ var gljOprObj = {
     },
     detailSheetReadonly:function () {
         let selected = projectObj.project.mainTree.selected;
-        if(selected) {//是主材或者是设备时只读
+        if(selected) {
+            //清单锁定时只读
+            if(selected.sourceType == ModuleNames.bills && projectObj.project.isBillsLocked()){
+                return true;
+            }
+            //是主材或者是设备时只读
             if(selected.sourceType == ModuleNames.ration_glj){
                 return true;
             }else if(gljOprObj.isInstallationNode(selected)){//是补项或者是安装类型的定额时只读
@@ -375,7 +380,7 @@ var gljOprObj = {
         }
     },
     isInstallationNode:function(node){
-        if((node.sourceType == ModuleNames.ration&&node.data.type == rationType.install)||(node.sourceType == ModuleNames.bills&&node.data.type==billType.BX)){//是定额安装费类型或者补项
+        if(node.sourceType == ModuleNames.ration&&node.data.type == rationType.install){//是定额安装费类型时只读,原先是补项的时候也是控制只读的||(node.sourceType == ModuleNames.bills&&node.data.type==billType.BX)){//是定额安装费类型或者补项
             return true;
         }
         return false

+ 23 - 4
web/building_saas/main/js/views/main_tree_col.js

@@ -269,15 +269,34 @@ let MainTreeCol = {
                 return new GC.Spread.Sheets.CellTypes.CheckBox();
         }
     },
+    editChecking:function(node){
+        if(node.sourceType==projectObj.project.Bills.getSourceType()&&projectObj.project.isBillsLocked()){
+            return true;
+        }
+        if(gljOprObj.isInstallationNode(node)){//如果是通过安装增加费自动生成的,都是只读类型
+            return true;
+        }
+        return false;
+    },
     setAutoHeight:function (cell,node) {//设置自动行高
+        cell.wordWrap(MainTreeCol.needAutoFit(node));
+    },
+    setAutoFitRow:function (sheet,node) {
+        if(MainTreeCol.needAutoFit(node)){
+            sheet.autoFitRow(node.serialNo());
+        }
+    },
+    needAutoFit:function (node) {
         let displaySetting = projectObj.project.property.displaySetting;
         let billsAutoH = displaySetting && displaySetting.billsAutoHeight?displaySetting.billsAutoHeight:false;
         let rationAutoH = displaySetting && displaySetting.rationAutoHeight?displaySetting.rationAutoHeight:false;
-        if(node.sourceType === projectObj.project.Bills.getSourceType()){
-            cell.wordWrap(billsAutoH);
-        }else {
-            cell.wordWrap(rationAutoH);
+        if(node.sourceType === projectObj.project.Bills.getSourceType()&&billsAutoH == true){
+            return true;
+        }
+        if(node.sourceType !== projectObj.project.Bills.getSourceType()&& rationAutoH == true){
+            return true;
         }
+        return false;
     },
     getEvent: function (eventName) {
         let names = eventName.split('.');

+ 194 - 69
web/building_saas/main/js/views/project_view.js

@@ -21,6 +21,20 @@ var projectObj = {
     },
     treeSelectedChanged: function (node) {
         let project = projectObj.project;
+        //设置选中行底色和恢复前选中行底色
+        let refreshNodes = [node];
+        if(!project.mainTree.preSelected){
+            refreshNodes.push(project.mainTree.items[0]);
+        }
+        else {
+            refreshNodes.push(project.mainTree.preSelected);
+        }
+        project.mainTree.preSelected = node;
+        projectObj.setNodesStyle(projectObj.mainController.sheet, refreshNodes);
+   /*     TREE_SHEET_HELPER.massOperationSheet(projectObj.mainController.sheet, function () {
+            TREE_SHEET_HELPER.refreshTreeNodeData(projectObj.mainController.setting, projectObj.mainController.sheet, refreshNodes, false);
+        });*/
+
         subViewObj.loadComments(node);
         gljOprObj.showDataIfRationSelect(node);
         if (activeSubSheetIs(subSheetIndex.ssiCalcProgram)) {
@@ -36,7 +50,6 @@ var projectObj = {
                 pageCCOprObj.clearData();
             }
         }
-
         // for test interface.  CSLAAAAA
         // projectObj.testDisplay('前四项累计值排除当前选中项' + projectObj.project.calcProgram.getBeforeTaxTotalFee([node]));
 
@@ -60,6 +73,9 @@ var projectObj = {
             if(!node){
                 return false;
             }
+            if(projectObj.project.isBillsLocked()== true){
+                return false;
+            }
             if(node.depth()<=1){//焦点行是树结构的第一/二层节点,灰显。
                 return false;
             }
@@ -89,6 +105,9 @@ var projectObj = {
             if(!node){
                 return false;
             }
+            if(projectObj.project.isBillsLocked()== true){
+                return false;
+            }
             if(node.depth()==0){//焦点行是树结构的第一层节点,灰显。
                 return false;
             }
@@ -116,8 +135,13 @@ var projectObj = {
         };
         let canUpMove = function (node) {
             if(node&&node.preSibling){//有前兄弟
-                if(node.sourceType==that.project.Bills.getSourceType()&&node.data.type == billType.DXFY&&node.data.isAdd!==1){
-                    return false;
+                if(node.sourceType==that.project.Bills.getSourceType()){
+                    if(projectObj.project.isBillsLocked()== true){
+                        return false;
+                    }
+                    if(node.data.type == billType.DXFY&&node.data.isAdd!==1){
+                        return false;
+                    }
                 }
                 return true
             }
@@ -125,8 +149,13 @@ var projectObj = {
         };
         let canDownMove = function (node) {
             if(node&&node.nextSibling){
-                if(node.sourceType==that.project.Bills.getSourceType()&&node.data.type == billType.DXFY&&node.data.isAdd!==1){
-                    return false;
+                if(node.sourceType==that.project.Bills.getSourceType()){
+                    if(projectObj.project.isBillsLocked()== true){
+                        return false;
+                    }
+                    if(node.data.type == billType.DXFY&&node.data.isAdd!==1){
+                        return false;
+                    }
                 }
                 return true;
             }
@@ -235,19 +264,21 @@ var projectObj = {
         return rst;
     },
     updateBillsCode: function (node, value) {
-        let project = projectObj.project;
+        let project = projectObj.project,me = this;
         let stdMatchCode, formatCode, matchs;
         let searchStdBillsAndUpdate = function (stdCode, formatCode) {
             let orgCode = node.data.code?node.data.code.substr(0, 9):"";
             if (stdCode === orgCode || projectInfoObj.projectInfo.engineeringInfo.bill_lib.length === 0) {
-                project.Bills.updateField(node.source, 'code', formatCode, true);
-                projectObj.mainController.refreshTreeNode([node], false);
+                normalUpdate(node,value);
             } else if (projectInfoObj.projectInfo.engineeringInfo.bill_lib.length > 0) {
                 let libId = projectInfoObj.projectInfo.engineeringInfo.bill_lib[0].id;
                 CommonAjax.post('/stdBillsEditor/getStdBillsByCode', {userId: userID, billsLibId: libId, code: stdCode}, function (data) {
                     if (data) {
                         //data.itemCharacter = pageCCOprObj.safeItemCharater(data.itemCharacter);
                         node.data.name = data.name;
+                        if(node.data.type == billType.BX){//从清单库中找到标准清单的话,要把补项改成分项
+                            node.data.type = billType.FX;
+                        }
                         pageCCOprObj.setItemContentNode(node, data.jobContent, data.itemCharacter, node.data.name);
                         if (/\//.test(data.unit)) {
                             let existB = projectObj.project.Bills.sameStdCodeBillsData(data.code);
@@ -269,13 +300,12 @@ var projectObj = {
                             projectObj.mainController.refreshTreeNode([node], false);
                         }
                     } else {
-                        project.Bills.updateField(node.source, 'code', formatCode, true);
-                        projectObj.mainController.refreshTreeNode([node], false);
+                        normalUpdate(node,value);
                     }
                 });
             }
         }
-        if(node.data.type==billType.FX||(node.data.type==billType.BILL&&node.source.children.length==0)){//是分项或者叶子清单的情况下才需要查找替换
+        if(node.data.type==billType.FX||node.data.type==billType.BX||(node.data.type==billType.BILL&&node.source.children.length==0)){//是分项、补项或者叶子清单的情况下才需要查找替换
             if (value&&value.length === 9 && /^[\d]+$/.test(value)) {
                 stdMatchCode = value;
                 formatCode = project.Bills.newFormatCode(stdMatchCode);
@@ -286,16 +316,31 @@ var projectObj = {
                 matchs = project.Bills.sameStdCode(stdMatchCode, node.data.code);
                 if (matchs.indexOf(value) === -1) {
                     searchStdBillsAndUpdate(stdMatchCode, value);
-                } else if (confirm('已存在该编码的清单,是否继续?')) {
-                    formatCode = project.Bills.newFormatCode(stdMatchCode, node.data.code);
-                    searchStdBillsAndUpdate(stdMatchCode, formatCode);
                     return;
+                } else {
+                   if (confirm('已存在该编码的清单,是否继续?')) {
+                       // formatCode = project.Bills.newFormatCode(stdMatchCode, node.data.code);
+                        searchStdBillsAndUpdate(stdMatchCode, value);
+                        return;
+                    }else {
+                       this.mainController.refreshTreeNode([node], false);
+                       return;
+                   }
                 }
             }
         }
-        project.Bills.updateField(node.source, 'code', value, true);
-        this.mainController.refreshTreeNode([node], false);
+        normalUpdate(node,value);
 
+
+        function normalUpdate(billnode,codeValue) {//在标准库中没有找到清单时改分项为补项再更新
+            let toBX = false;
+            if(billnode.data.type == billType.FX){
+                billnode.data.type = billType.BX;
+                 toBX = true;
+            }
+            project.Bills.updateField(billnode.source, 'code', codeValue, toBX);
+            me.mainController.refreshTreeNode([billnode], false);
+        }
     },
     updateRationCode: function (node, value) {
         if(!isDef(node.data.code) && (!isDef(value) || value.toString().trim() == '')){
@@ -410,6 +455,35 @@ var projectObj = {
             projectObj.mainController.refreshTreeNode([node], false);
         }
     },
+    mainSpreadSlectionChanging: function (sender, info) {
+        console.log('bbbb');
+        let oldSel = info.oldSelections[0], newSel = info.newSelections[0];
+        let project = projectObj.project;
+        //设置选中行底色和恢复前选中行底色
+        let refreshNodes = [];
+        if(oldSel){
+            oldSel.row === -1 ? 0 : oldSel.row;
+            for(let i = 0; i < oldSel.rowCount; i++){
+                if(project.mainTree.items[i + oldSel.row]){
+                    refreshNodes.push(project.mainTree.items[i + oldSel.row]);
+                }
+            }
+        }
+        if(newSel){
+            newSel.row === -1 ? 0 : newSel.row;
+            for(let i = 0; i < newSel.rowCount; i++){
+                if(project.mainTree.items[i + newSel.row]){
+                    refreshNodes.push(project.mainTree.items[i + newSel.row]);
+                }
+            }
+        }
+        if(refreshNodes.length > 0){
+            projectObj.setNodesStyle(projectObj.mainController.sheet, refreshNodes, newSel);
+            /* TREE_SHEET_HELPER.massOperationSheet(projectObj.mainController.sheet, function () {
+             TREE_SHEET_HELPER.refreshTreeNodeData(projectObj.mainController.setting, projectObj.mainController.sheet, refreshNodes, false, true);
+             });*/
+        }
+    },
     mainSpreadLeaveCell: function (sender, info) {
         let colSetting = projectObj.mainController.setting.cols[info.col];
         projectObj.lastCol = colSetting;
@@ -524,6 +598,7 @@ var projectObj = {
                 that.project.projSetting.mainGridSetting = JSON.parse(str);
                 that.project.projSetting.mainGridSetting.frozenCols = 4;
                 TREE_SHEET_HELPER.initSetting($('#billsSpread')[0], that.project.projSetting.mainGridSetting);
+                that.project.projSetting.mainGridSetting.setAutoFitRow = MainTreeCol.getEvent("setAutoFitRow");
                 that.project.projSetting.mainGridSetting.cols.forEach(function (col) {
                     col.data.splitFields = col.data.field.split('.');
                     if (col.data.getText && Object.prototype.toString.apply(col.data.getText) === "[object String]") {
@@ -544,6 +619,7 @@ var projectObj = {
                         col.data.formatter = '@';
                     }
                     col.setAutoHeight = MainTreeCol.getEvent("setAutoHeight");
+                    col.editChecking = MainTreeCol.getEvent("editChecking");
                     // 根据配置设置自动行高,在这里先做个标记,然后对每个单元格单独配置
                     if (col.data.field === 'name' || col.data.field === 'itemCharacterText' ||
                         col.data.field === 'jobContentText' || col.data.field === 'adjustState') {
@@ -565,6 +641,7 @@ var projectObj = {
                 });
                 let startShowTime = +new Date();
                 that.mainController = TREE_SHEET_CONTROLLER.createNew(that.project.mainTree, that.mainSpread.getActiveSheet(), that.project.projSetting.mainGridSetting);
+
                 that.mainController.showTreeData();
                 let endShowTime = +new Date();
                 console.log(`show data时间——${endShowTime - startShowTime}`);
@@ -574,7 +651,6 @@ var projectObj = {
 
                 that.mainSpread.getActiveSheet().startEdit();
                 that.mainSpread.getActiveSheet().endEdit();
-
                 that.mainSpread.bind(GC.Spread.Sheets.Events.LeaveCell, that.mainSpreadLeaveCell);
                 that.mainSpread.bind(GC.Spread.Sheets.Events.EnterCell, that.mainSpreadEnterCell);
                 that.mainSpread.bind(GC.Spread.Sheets.Events.EditStarting, that.mainSpreadEditStarting);
@@ -586,7 +662,6 @@ var projectObj = {
                 //let loadOtherStartTime = +new Date();
                 that.loadMainSpreadContextMenu();
                 that.loadFocusLocation();
-
                 socketObject.connect();//连接socket服务器
                 let endTime = +new Date();
                 console.log(`其它时间——${endTime - endShowTime}`);
@@ -618,6 +693,7 @@ var projectObj = {
                     name: "插入大项费用",
                     icon: 'fa-sign-in',
                     disabled: function () {
+                        return projectObj.project.isBillsLocked();
                         //return project.mainTree.selected ? project.mainTree.selected.sourceType !== project.Bills.getSourceType() : false;
                     },
                     callback: function (key, opt) {
@@ -632,7 +708,7 @@ var projectObj = {
                     icon: 'fa-sign-in',
                     disabled: function () {
                         let selected = project.mainTree.selected;
-                        if(selected&&selected.sourceType==project.Bills.getSourceType()){
+                        if(projectObj.project.isBillsLocked()== false&&selected&&selected.sourceType==project.Bills.getSourceType()){
                             if(selected.data.type==billType.FB){
                                 return false;
                             }
@@ -662,7 +738,7 @@ var projectObj = {
                     icon: 'fa-sign-in',
                     disabled: function () {
                         let selected = project.mainTree.selected;
-                        if(selected&&selected.sourceType==project.Bills.getSourceType()){
+                        if(projectObj.project.isBillsLocked()== false&& selected&&selected.sourceType==project.Bills.getSourceType()){
                             if(selected.data.type==billType.FX){//焦点行是分项,有效显示
                                 return false
                             }
@@ -698,7 +774,11 @@ var projectObj = {
                     name: "插入清单",
                     icon: 'fa-sign-in',
                     disabled: function () {
-                        return project.mainTree.selected ? project.mainTree.selected.sourceType !== project.Bills.getSourceType() : false;
+                        let selected = project.mainTree.selected;
+                        if(projectObj.project.isBillsLocked()== false && selected && selected.sourceType === project.Bills.getSourceType()){
+                            return false
+                        }
+                        return true;
                     },
                     callback: function (key, opt) {
                         if(project.mainTree.selected.data.type == billType.DXFY){
@@ -798,7 +878,7 @@ var projectObj = {
                     name: "整理分部",
                     icon: 'fa-sign-in',
                     disabled: function () {
-                       return false;
+                       return projectObj.project.isBillsLocked();
                     },
                     callback: function (key, opt) {
                         zlfb_object.getSectionInfo();
@@ -876,13 +956,18 @@ var projectObj = {
         const projectId = scUrlUtil.GetQueryString('project');
         let row = getLocalCache('lastRow:' + projectId);
         let col = getLocalCache('lastCol:' + projectId);
-        if (row !== null && col !== null) {
-            row = parseInt(row);
-            col = parseInt(col);
-            const sheet = this.mainSpread.getActiveSheet();
-            sheet.setSelection(row, col, 1, 1);
-            this.mainController.setTreeSelected(this.mainController.tree.items[row]);//触发树节点选中事件
-        }
+        if(row == null || col == null){
+            row = 0;
+            col = 0;
+
+
+
+       }
+        row = parseInt(row);
+        col = parseInt(col);
+        const sheet = this.mainSpread.getActiveSheet();
+        sheet.setSelection(row, col, 1, 1);
+        this.mainController.setTreeSelected(this.mainController.tree.items[row]);//触发树节点选中事件
     },
     // 选中区域合计数字
     amountAreaNumber: function(e, info) {
@@ -970,9 +1055,9 @@ var projectObj = {
     },
 
     //根据节点获取行style(颜色、字体加粗)
-    getNodeColorStyle: function (node, colSetting) {
+    getNodeColorStyle: function (sheet, node, colSetting) {
         let colorSetting = optionsOprObj.getOption(optionsOprObj.optionsTypes.COLOROPTS);
-        let mapping = {DEFAULT: 'DEFAULT', DXFY: 'DXFY', FB: 'FB', UNLEAFBILL: 'UNLEAFBILL',
+        let mapping = {DEFAULT: 'DEFAULT', SELECTED: 'SELECTED', DXFY: 'DXFY', FB: 'FB', UNLEAFBILL: 'UNLEAFBILL',
             FX: 'FX', BX: 'BX', UNCBBILL: 'UNCBBILL', CBBILL: 'CBBILL', ZCSB: 'ZCSB'};
         let styleMap = null;
         //中文字段名,由于同一节点中,中文字体大小和数字字体大小不同
@@ -994,47 +1079,53 @@ var projectObj = {
         if(!isDef(node)){
             return null;
         }
-        //清单大类
-        if(node.sourceType === this.project.Bills.getSourceType()){
-            //大项费用
-            //大项费用
-            if(node.data.type === billType.DXFY){
-                styleMap = mapping.DXFY;
-            }
-            //分部
-            if(node.data.type === billType.FB){
-                styleMap = mapping.FB;
-            }
-            //分项
-            else if(node.data.type === billType.FX){
-                styleMap = mapping.FX;
-            }
-            //补项
-            else if(node.data.type === billType.BX){
-                styleMap = mapping.BX;
-            }
-            //清单
-            else if(node.data.type === billType.BILL){
-                //非叶子节点的清单
-                if(node.source.children.length > 0){
-                    styleMap = mapping.UNLEAFBILL;
+        //选中行
+        if(node === this.project.mainTree.selected){
+            styleMap = mapping.SELECTED;
+        }
+        //非选中行
+        else {
+            //清单大类
+            if(node.sourceType === this.project.Bills.getSourceType()){
+                //大项费用
+                if(node.data.type === billType.DXFY){
+                    styleMap = mapping.DXFY;
+                }
+                //分部
+                if(node.data.type === billType.FB){
+                    styleMap = mapping.FB;
+                }
+                //分项
+                else if(node.data.type === billType.FX){
+                    styleMap = mapping.FX;
                 }
-                //未使用基数计算的叶子节点的清单
-                else if(node.source.children.length === 0 && (!isDef(node.data.calcBase) || node.data.calcBase === '')){
-                    styleMap = mapping.UNCBBILL;
+                //补项
+                else if(node.data.type === billType.BX){
+                    styleMap = mapping.BX;
                 }
-                //使用基数计算的叶子节点的清单
-                else if(node.source.children.length === 0 && isDef(node.data.calcBase && node.data.calcBaseValue !== '')){
-                    styleMap = mapping.CBBILL;
+                //清单
+                else if(node.data.type === billType.BILL){
+                    //非叶子节点的清单
+                    if(node.source.children.length > 0){
+                        styleMap = mapping.UNLEAFBILL;
+                    }
+                    //未使用基数计算的叶子节点的清单
+                    else if(node.source.children.length === 0 && (!isDef(node.data.calcBase) || node.data.calcBase === '')){
+                        styleMap = mapping.UNCBBILL;
+                    }
+                    //使用基数计算的叶子节点的清单
+                    else if(node.source.children.length === 0 && isDef(node.data.calcBase && node.data.calcBaseValue !== '')){
+                        styleMap = mapping.CBBILL;
+                    }
                 }
             }
-        }
-        //定额下的主材、设备
-        else if(node.sourceType === this.project.ration_glj.getSourceType()){
-            styleMap = mapping.ZCSB;
-        }
-        else {
-            styleMap = mapping.DEFAULT;
+            //定额下的主材、设备
+            else if(node.sourceType === this.project.ration_glj.getSourceType()){
+                styleMap = mapping.ZCSB;
+            }
+            else {
+                styleMap = mapping.DEFAULT;
+            }
         }
         let styleSetting = colorSetting[styleMap];
         let defaultSetting = colorSetting[mapping.DEFAULT];
@@ -1090,8 +1181,27 @@ var projectObj = {
             style.font = 'bold 13px Arial';
         }
         return style;
+    },
+   //设置节点style
+    setNodesStyle: function (sheet, nodes) {
+        let me = this;
+        TREE_SHEET_HELPER.massOperationSheet(sheet, function () {
+            for(let node of nodes){
+                sheet.setStyle(node.serialNo(), -1, me.getNodeColorStyle(sheet, node));
+            }
+        });
+     
+},
+    loadLockBillsButton:function () {
+        if(projectInfoObj.projectInfo.property.lockBills == true){
+            $("a[name='lockBills']").prop("title","解锁清单");
+            $("a[name='lockBills']").html('<i class="fa fa-unlock-alt" aria-hidden="true"></i> 解锁清单');
+        }else {
+            $("a[name='lockBills']").prop("title","锁定清单");
+            $("a[name='lockBills']").html('<i class="fa fa-lock" aria-hidden="true"></i> 锁定清单');
+        }
     }
-    
+
 };
 // 点击合计框中的复制
 $("body").on("click", "#total-tips a", function() {
@@ -1177,6 +1287,18 @@ $('#downMove').click(function () {
         };
     }
 });
+$("a[name='lockBills']").click(function () {
+    let lockBills = projectInfoObj.projectInfo.property.lockBills;
+    lockBills = !lockBills;
+    projectObj.project.updateLockBills(lockBills,function () {
+        var controller = projectObj.mainController, project = projectObj.project;
+        var selected = controller.tree.selected;
+        let nodes = _.filter(project.mainTree.items,{'sourceType':ModuleNames.bills});
+        controller.refreshTreeNode(nodes);
+        projectObj.mainController.setTreeSelected(selected);//触发树节点选中事件
+        projectObj.loadLockBillsButton();
+    });
+});
 //显示至..
 let displayLevel = function(nodes, depth, type){
     let refreshNodes = [];
@@ -1495,6 +1617,9 @@ function ifCanDelete() {
                 if(node.data.type == billType.DXFY&&node.data.isAdd!=1){
                     return false;
                 }
+                if(projectObj.project.isBillsLocked()== true){
+                    return false;
+                }
             }
             if(m_selection!=true&&node.sourceType === projectObj.project.ration_glj.getSourceType()){//多选的时候不做这一项判断
                 return false;

+ 45 - 0
web/building_saas/pm/html/project-management-Recycle.html

@@ -31,6 +31,25 @@
         </div>
     </div>
 </div>
+<!--弹出清除项目-->
+<div class="modal fade" id="delPoj" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">清除 <i class="fa fa-cubes"></i> 建设项目</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a href="javascript:void(0);" class="btn btn-primary" data-dismiss="modal" id="delPojBtn">确定</a>
+            </div>
+        </div>
+    </div>
+</div>
 <!--弹出恢复文件-->
 <div class="modal fade" id="reFile" data-backdrop="static">
     <div class="modal-dialog" role="document">
@@ -57,3 +76,29 @@
         </div>
     </div>
 </div>
+<!--弹出清除文件-->
+<div class="modal fade" id="delFile" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">恢复单价文件</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body modal-fixed-height">
+                <p>勾选需要恢复的文件,点“确定”按钮,确认从回收站中恢复。</p>
+                <table class="table table-hover table-sm mb-5">
+                    <thead><tr><th>名称</th><th>删除时间</th><th>勾选</th></tr></thead>
+                    <tbody>
+                    <tr><td>XX单价文件</td><td>2017-11-01<br>12:11:43</td><td><input type="checkbox"></td></tr>
+                    </tbody>
+                </table>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a href="javascript:void(0);" class="btn btn-primary" id="delFileBtn" data-dismiss="modal">确定</a>
+            </div>
+        </div>
+    </div>
+</div>

+ 257 - 48
web/building_saas/pm/js/pm_gc.js

@@ -174,9 +174,12 @@ const gcTreeObj = {
             {name: '工程列表', dataCode: 'name', width: 800, vAlign: 'center', hAlign: 'left'},
             {name: '删除日期', dataCode: 'deleteDateTime', width: 170, vAlign: 'center', hAlign: 'left'},
             {name: '创建日期', dataCode: 'createDateTime', width: 170, vAlign: 'center', hAlign: 'left'},
-            {name: '恢复', dataCode: 'recovery', width: 170, vAlign: 'center', hAlign: 'left'},
-            {name: '单价文件', dataCode: 'unitPriceFile', width: 170, vAlign: 'center', hAlign: 'left'},
-            {name: '费率文件', dataCode: 'feeRateFile', width: 170, vAlign: 'center', hAlign: 'left'}
+            {name: '恢复', dataCode: 'recovery', width: 100, vAlign: 'center', hAlign: 'left'},
+            {name: '清除', dataCode: 'delete', width: 100, vAlign: 'center', hAlign: 'left'},
+            {name: '单价文件', dataCode: 'unitPriceFile', width: 100, vAlign: 'center', hAlign: 'left'},
+            {name: '单价文件-清除', dataCode: 'unitPriceFile_delete', width: 100, vAlign: 'center', hAlign: 'left'},
+            {name: '费率文件', dataCode: 'feeRateFile', width: 100, vAlign: 'center', hAlign: 'left'},
+            {name: '费率文件-清除', dataCode: 'feeRateFile_delete', width: 100, vAlign: 'center', hAlign: 'left'}
         ],
         //选中行颜色
         style: {
@@ -221,8 +224,15 @@ const gcTreeObj = {
             sheet.setColumnCount(headers.length);
             sheet.setRowHeight(0, 40, GC.Spread.Sheets.SheetArea.colHeader);
             for(let i = 0, len = headers.length; i < len; i++){
-                sheet.setValue(0, i, headers[i].name, GC.Spread.Sheets.SheetArea.colHeader);
                 sheet.setColumnWidth(i, headers[i].width, GC.Spread.Sheets.SheetArea.colHeader);
+                if(headers[i].dataCode === 'unitPriceFile' || headers[i].dataCode === 'feeRateFile'){
+                    //合并列
+                    sheet.addSpan(0, i, 1, 2, GC.Spread.Sheets.SheetArea.colHeader);
+                }
+                if(headers[i].dataCode === 'unitPriceFile_delete' || headers[i].dataCode === 'feeRateFile_delete'){
+                    continue;
+                }
+                sheet.setValue(0, i, headers[i].name, GC.Spread.Sheets.SheetArea.colHeader);
             }
         };
         me.renderSheetFuc(sheet, fuc);
@@ -304,29 +314,49 @@ const gcTreeObj = {
         me.initSelection(args.newSelections[0], args.oldSelections[0]);
     },
     //点击恢复列,弹出恢复项目窗口
-    recoveryProj: function (node) {
+    oprProj: function (modalId, node) {
         let tenderNodes = m_getTenders(node);
-        $('#rePoj .modal-header').empty();
-        $('p', '#rePoj .modal-body').remove();
-        $('#rePoj .modal-header').html(v_getTitle(node));
-        $('#rePoj .modal-body').html(v_getMoBody(node, tenderNodes));
-        $('#rePoj').modal('show');
+        let type = modalId === '#rePoj' ? 'recovery' : 'delete';
+        $(`${modalId} .modal-header`).empty();
+        $('p', `${modalId} .modal-body`).remove();
+        $(`${modalId} .modal-header`).html(v_getTitle(type, node));
+        $(`${modalId} .modal-body`).html(v_getMoBody(type, node, tenderNodes));
+        $(`${modalId}`).modal('show');
     },
     //点击单价文件恢复,弹出恢复单价文件窗口
     recoveryUnitPrc: function (node) {
         let unitPriceFiles = node.data.unitPriceFiles;
+        let tenders = m_getTenders(node);
         $('#reFile h5').text('恢复单价文件');
         $('tr', '#reFile tbody').remove();
-        $('#reFile tbody').html(v_getFiles(fileType.unitPriceFile, unitPriceFiles));
+        $('#reFile tbody').html(v_getFiles(fileType.unitPriceFile, unitPriceFiles, tenders, 'recovery'));
         $('#reFile').modal('show');
     },
     recoveryFeeRate: function (node) {
         let feeRateFiles = node.data.feeRateFiles;
+        let tenders = m_getTenders(node);
         $('#reFile h5').text('恢复费率文件');
         $('tr', '#reFile tbody').remove();
-        $('#reFile tbody').html(v_getFiles(fileType.feeRateFile, feeRateFiles));
+        $('#reFile tbody').html(v_getFiles(fileType.feeRateFile, feeRateFiles, tenders, 'recovery'));
         $('#reFile').modal('show');
     },
+    //点击单价文件清除,弹出清除单价文件窗口
+    deleteUnitPrc: function (node) {
+        let unitPriceFiles = node.data.unitPriceFiles;
+        let tenders = m_getTenders(node);
+        $('#delFile h5').text('清除单价文件');
+        $('tr', '#delFile tbody').remove();
+        $('#delFile tbody').html(v_getFiles(fileType.unitPriceFile, unitPriceFiles, tenders, 'delete'));
+        $('#delFile').modal('show');
+    },
+    deleteFeeRate: function (node) {
+        let feeRateFiles = node.data.feeRateFiles;
+        let tenders = m_getTenders(node);
+        $('#delFile h5').text('清除费率文件');
+        $('tr', '#delFile tbody').remove();
+        $('#delFile tbody').html(v_getFiles(fileType.feeRateFile, feeRateFiles, tenders, 'delete'));
+        $('#delFile').modal('show');
+    },
     getTreeNodeCell: function (tree) {
         let indent = 20;
         let levelIndent = -5;
@@ -517,20 +547,35 @@ const gcTreeObj = {
                 zoom = hitinfo.sheet.zoom();
             let textLength = this.getAutoFitWidth(value, text, acStyle, zoom, {sheet: hitinfo.sheet, row: hitinfo.row, col: hitinfo.col, sheetArea: GC.Spread.Sheets.SheetArea.viewport});
             if(hitinfo.x - hitinfo.cellRect.x > 0 && hitinfo.x - hitinfo.cellRect.x < textLength){
+                //恢复
                 if(dataCode === 'recovery'){
-                    gcTreeObj.recoveryProj(node);
+                    gcTreeObj.oprProj('#rePoj', node);
+                }
+                //清除
+                else if(dataCode === 'delete'){
+                    gcTreeObj.oprProj('#delPoj', node)
                 }
+                //恢复单价文件
                 else if(dataCode === 'unitPriceFile'){
                     gcTreeObj.recoveryUnitPrc(node);
                 }
+                //清除单价文件
+                else if(dataCode === 'unitPriceFile_delete'){
+                    gcTreeObj.deleteUnitPrc(node);
+                }
+                //恢复费率文件
                 else if(dataCode === 'feeRateFile'){
                     gcTreeObj.recoveryFeeRate(node);
                 }
+                //清除费率文件
+                else if(dataCode === 'feeRateFile_delete'){
+                    gcTreeObj.deleteFeeRate(node);
+                }
             }
         };
         MyBaseCell.prototype.processMouseMove = function (hitInfo) {
             let dataCode = gcTreeObj.setting.header[hitInfo.col]['dataCode'];
-            if(dataCode === 'recovery' || dataCode === 'unitPriceFile' || dataCode === 'feeRateFile'){
+            if(dataCode === 'recovery' || dataCode === 'delete' || dataCode === 'unitPriceFile' || dataCode === 'unitPriceFile_delete' || dataCode === 'feeRateFile' || dataCode === 'feeRateFile_delete'){
                 let sheet = hitInfo.sheet;
                 let div = sheet.getParent().getHost();
                 let canvasId = div.id + "vp_vp";
@@ -569,16 +614,31 @@ const gcTreeObj = {
                 value = '恢复';
             }
         }
+        else if(dataCode === 'delete'){
+            if(deleted(node)){
+                value = '清除';
+            }
+        }
         else if(dataCode === 'unitPriceFile'){
             if(node.data.projType === projectType.project && node.data.unitPriceFiles !== undefined && node.data.unitPriceFiles.length > 0){
                 value = '恢复';
             }
         }
+        else if(dataCode === 'unitPriceFile_delete'){
+            if(node.data.projType === projectType.project && node.data.unitPriceFiles !== undefined && node.data.unitPriceFiles.length > 0){
+                value = '清除';
+            }
+        }
         else if(dataCode === 'feeRateFile'){
             if(node.data.projType === projectType.project && node.data.feeRateFiles !== undefined && node.data.feeRateFiles.length > 0){
                 value = '恢复';
             }
         }
+        else if(dataCode === 'feeRateFile_delete'){
+            if(node.data.projType === projectType.project && node.data.feeRateFiles !== undefined && node.data.feeRateFiles.length > 0){
+                value = '清除';
+            }
+        }
         else {
             value = node.data[dataCode] ? node.data[dataCode] : '';
         }
@@ -644,7 +704,9 @@ $(document).ready(function () {
         projTreeObj.tree = null;
     });
     e_recFiles($('#reFileBtn'));
+    e_delFiles($('#delFileBtn'));
     e_recProj($('#rePojBtn'));
+    e_delProj($('#delPojBtn'));
 });
 
 function gc_init(){
@@ -664,9 +726,9 @@ function gc_init(){
 }
 
 //项目恢复模态框标题
-function v_getTitle(node){
+function v_getTitle(type, node){
     let html = '';
-    html += '<h5 class="modal-title">恢复 ';
+    html += type === 'recovery' ? '<h5 class="modal-title">恢复 ' : '<h5 class="modal-title">清除 ';
     if(node.data.projType === projectType.project){
         html += '<i class="fa fa-cubes"></i>建设项目</h5>';
     }
@@ -681,48 +743,73 @@ function v_getTitle(node){
 }
 
 //项目恢复模态框主体
-function v_getMoBody(oprNode, nodes){
+function v_getMoBody(type, oprNode, nodes){
     let html = '';
-    if(oprNode.data.projType === projectType.tender){
-        decDate = '(' + new Date().Format('MM-dd hh:mm:ss') + '恢复)';
-        let recName = oprNode.data.name + decDate;
-        html += '<p>恢复后将重命名为 <b>' + recName + '</b></p>';
-    }
-    else {
-        if(oprNode.data.projType === projectType.project){
-            html += '<p><i class="fa fa-cubes"></i> ' + oprNode.data.name + '下的单位工程都将恢复都将恢复,恢复后将重命名为</p>';
-        }
-        else if(oprNode.data.projType === projectType.engineering){
-            html += '<p><i class="fa fa-cube"></i> ' + oprNode.data.name + '下的单位工程都将恢复都将恢复,恢复后将重命名为</p>';
+    if(type === 'recovery'){
+        if(oprNode.data.projType === projectType.tender){
+            decDate = '(' + new Date().Format('MM-dd hh:mm:ss') + '恢复)';
+            let recName = oprNode.data.name + decDate;
+            html += '<p>恢复后将重命名为 <b>' + recName + '</b></p>';
         }
-        html += ('<p>');
-        for(let i = 0, len = nodes.length; i < len; i++){
-            let recName = nodes[i].data.name + '(' + new Date().Format('MM-dd hh:mm:ss') +'恢复)';
-            html += '<b>' + recName + '</b>、';
+        else {
+            if(oprNode.data.projType === projectType.project){
+                html += '<p><i class="fa fa-cubes"></i> ' + oprNode.data.name + '下的单位工程都将恢复都将恢复,恢复后将重命名为</p>';
+            }
+            else if(oprNode.data.projType === projectType.engineering){
+                html += '<p><i class="fa fa-cube"></i> ' + oprNode.data.name + '下的单位工程都将恢复都将恢复,恢复后将重命名为</p>';
+            }
+            html += ('<p>');
+            for(let i = 0, len = nodes.length; i < len; i++){
+                let recName = nodes[i].data.name + '(' + new Date().Format('MM-dd hh:mm:ss') +'恢复)';
+                html += '<b>' + recName + '</b>、';
+            }
+            html = html.slice(0, html.length - 1);
+            html += ('</p>');
         }
-        html = html.slice(0, html.length - 1);
-        html += ('</p>');
-    }
 
-    html += ('<p>点“确定”按钮,确认从回收站中恢复</p>');
+        html += ('<p>点“确定”按钮,确认从回收站中恢复</p>');
+    }
+    else{
+        html += ('<p>点“确定”按钮,确认清除数据</p>');
+    }
     return html;
 }
 
 //单价、费率文件恢复弹出框数据
-function v_getFiles(type, files){
+function v_getFiles(type, files, tenders, opr = null){
     let html = '';
+    function hasTheFile(tenders, fileId, fileType){
+        for(let tender of tenders){
+            let fileAttr = fileType === 'UnitPriceFile' ? 'unitPriceFile' : 'feeFile';
+            if(tender.data.property[fileAttr]['id'] == fileId){
+                return true;
+            }
+        }
+        return false;
+    }
     for(let i = 0, len = files.length; i < len; i ++){
         let recName = type === fileType.unitPriceFile ?  files[i].name + '单价文件' : files[i].name + '费率文件';
         let fileId = type === fileType.unitPriceFile ? files[i].id : files[i].ID;
         let recTimeA = formatDate(new Date(files[i].deleteInfo.deleteDateTime), 'yyyy-MM-dd');
         let recTimeB = formatDate(new Date(files[i].deleteInfo.deleteDateTime), 'HH:mm:ss');
-        html += '<tr><td>'+ recName +'</td><td>' + recTimeA + '<br>' + recTimeB + '</td><td><input name="fileItems" type="checkbox" fileId = "' + fileId + '" fileType = "' + type + '"></td></tr>';
+        if(opr && opr === 'delete'){
+            //还被引用,不可删除
+            if(hasTheFile(tenders, fileId, type)){
+                html += '<tr><td>'+ recName +'</td><td>' + recTimeA + '<br>' + recTimeB + '</td><td><input disabled name="fileItems" type="checkbox" fileId = "' + fileId + '" fileType = "' + type + '"></td></tr>';
+            }
+            else {
+                html += '<tr><td>'+ recName +'</td><td>' + recTimeA + '<br>' + recTimeB + '</td><td><input name="fileItems" type="checkbox" fileId = "' + fileId + '" fileType = "' + type + '"></td></tr>';
+            }
+        }
+        else{
+            html += '<tr><td>'+ recName +'</td><td>' + recTimeA + '<br>' + recTimeB + '</td><td><input name="fileItems" type="checkbox" fileId = "' + fileId + '" fileType = "' + type + '"></td></tr>';
+        }
     }
     return html;
 }
 
-//恢复单价、费率文件后前端显示变化
-function v_recFiles(project, fileIds, type){
+//恢复或清除单价、费率文件后前端显示变化
+function v_refreshFiles(project, fileIds, type){
     let projFiles;
     if(type === fileType.unitPriceFile){
         projFiles = project.data.unitPriceFiles;
@@ -767,14 +854,16 @@ function v_removeNode(node){
     }
 }
 
-function v_refreshNode(node){
-    if(deleted(node)){
-        delete node.data.deleteInfo;
+function v_refreshNode(node, recovery = true){
+    if(recovery){
+        if(deleted(node)){
+            delete node.data.deleteInfo;
+        }
     }
     gcTreeObj.refreshNodeData(node);
     let parent = node.parent || null;
     if(parent && parent.data !== undefined){
-        v_refreshNode(parent);
+        v_refreshNode(parent, recovery);
     }
 }
 
@@ -930,6 +1019,49 @@ function m_getRecDatas(oprNode){
     return rst;
 }
 
+//获取清除的额数据
+function m_getDelDatas(oprNode){
+    let rst = [];
+    if(!oprNode){
+        return rst;
+    }
+    function getChild(node){
+        rst.push({updateType: 'Project', ID: node.data.ID});
+        if(node.children.length > 0){
+            for(let child of node.children){
+                getChild(child);
+            }
+        }
+    }
+    getChild(oprNode);
+    //父节点只有一个单位工程,则清除此单位工程的时候,父节点也清除,(建设项目单价、费率文件存在时不清除)
+    if(oprNode.data.projType === projectType.tender){
+        let eng = oprNode.parent, proj = null;
+        if(eng && deleted(eng)){
+            proj = eng.parent;
+            rst.push({updateType: 'Project', ID: eng.data.ID});
+        }
+        if(proj && deleted(proj) && fileEmpty(proj)){
+            rst.push({updateType: 'Project', ID: proj.data.ID})
+        }
+    }
+    else if(oprNode.data.projType === projectType.engineering){
+        let proj = oprNode.parent;
+        if(proj && deleted(proj) && fileEmpty(proj)){
+            rst.push({updateType: 'Project', ID: proj.data.ID});
+        }
+    }
+    else if(oprNode.data.projType === projectType.project){
+        for(let uf of oprNode.data.unitPriceFiles){
+            rst.push({updateType: fileType.unitPriceFile, ID: uf.id});
+        }
+        for(let ff of oprNode.data.feeRateFiles){
+            rst.push({updateType: fileType.feeRateFile, ID: ff.ID});
+        }
+    }
+    return rst;
+}
+
 //获得勾选的单价、费率文件的id
 function m_getFilesObjs(nodes){
     let rst = [];
@@ -989,7 +1121,7 @@ function e_recFiles(btn){
         function caller(){
             //front
             if(recIds.length > 0){
-                v_recFiles(selected, recIds, type);
+                v_refreshFiles(selected, recIds, type);
                 if(deleted(selected)){
                     delete selected.data.deleteInfo;
                 }
@@ -1004,6 +1136,39 @@ function e_recFiles(btn){
     });
 }
 
+//点击单价、费率文件的清除操作(确认)
+function e_delFiles(btn){
+    btn.bind('click', function () {
+        let selected = gcTreeObj.tree.selected;//project
+        let delObjs = m_getFilesObjs($('[name = "fileItems"]:checked'));
+        let type = $('[name = "fileItems"]:checked').attr('fileType');
+        let delDatas = [];
+        let delIds = [];
+        for(let delObj of delObjs){
+            delIds.push(delObj.id);
+            delDatas.push({updateType: type, ID: delObj.id});
+        }
+        //此操作造成了建设项目的文件为空,则清除建设项目
+        if(fileWillEmpty(selected, delIds, type)){
+            delDatas.push({updateType: 'Project', ID: selected.data.ID});
+        }
+        if(delDatas.length > 0){
+            //backend
+            a_delGC(delDatas, caller);
+            //front
+            function caller(){
+                v_refreshFiles(selected, delIds, type);
+                if(fileEmpty(selected) && selected.children.length === 0){
+                    gcTreeObj.remove(selected);
+                }
+                else {
+                    gcTreeObj.refreshNodeData(selected);
+                }
+            }
+        }
+    });
+}
+
 //点击项目下的恢复操作(确认)
 function e_recProj(btn){
     btn.bind('click', function () {
@@ -1018,14 +1183,30 @@ function e_recProj(btn){
             let project = m_project(selected);
             if(project){
                 if(fileObj[fileType.unitPriceFile].length > 0){
-                    v_recFiles(project, fileObj[fileType.unitPriceFile], fileType.unitPriceFile);
+                    v_refreshFiles(project, fileObj[fileType.unitPriceFile], fileType.unitPriceFile);
                 }
                 if(fileObj[fileType.feeRateFile].length > 0){
-                    v_recFiles(project, fileObj[fileType.feeRateFile], fileType.feeRateFile);
+                    v_refreshFiles(project, fileObj[fileType.feeRateFile], fileType.feeRateFile);
                 }
             }
             v_removeNode(selected);
-            v_refreshNode(selected);
+            v_refreshNode(selected, true);
+        }
+    });
+}
+
+function e_delProj(btn){
+    btn.bind('click', function () {
+        let selected  = gcTreeObj.tree.selected;
+        //backend
+        let delDatas = m_getDelDatas(selected);
+        if(delDatas.length > 0){
+            a_delGC(delDatas, caller);
+        }
+        //front
+        function caller() {
+            v_removeNode(selected);
+            v_refreshNode(selected, false);
         }
     });
 }
@@ -1046,6 +1227,14 @@ function a_rec(nodes, callback){
     });
 }
 
+function a_delGC(delDatas, callback){
+    CommonAjax.post('/pm/api/delGC', {user_id: userID, delDatas: delDatas}, function(rstData){
+        if(callback){
+            callback();
+        }
+    })
+}
+
 //去除重名,回收站不处理重名,只保证恢复到项目管理中不出现重名
 function deWeightName(datas){
     let rst = [];
@@ -1114,6 +1303,26 @@ function fIsExist(files, id, type){
     return isExist;
 }
 
+//删除的文件是否会导致建设项目不再存有文件
+function fileWillEmpty(proj, delIds, type){
+    let ufs = proj.data.unitPriceFiles,
+        ffs = proj.data.feeRateFiles;
+    let theFiles = type === fileType.unitPriceFile ? ufs : ffs;
+        otherFiles = type === fileType.unitPriceFile ? ffs : ufs;
+    let uniqIds = Array.from(new Set(delIds));
+    if(theFiles.length === delIds.length){
+        for(let id of uniqIds){
+            if(!fIsExist(theFiles, id, type)){
+                return false;
+            }
+        }
+    }
+    if(otherFiles.length === 0){
+        return true;
+    }
+    return false;
+}
+
 function getRecFileObj(files){
     let rst = Object.create(null);
     let rst_UF = [], rst_FF = [];

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

@@ -362,7 +362,7 @@ const projTreeObj = {
                             $(".slide-sidebar").animate({width: "0"}).removeClass("open")// 关闭处理
                         }
                     });
-                }, 100);
+                }, 500);
             }
             //单项文件,进入造价书界面
             else if(node.data.projType === projectType.tender && withingClickArea()){

+ 4 - 14
web/building_saas/report/html/rpt_main.html

@@ -51,22 +51,12 @@
                     </div>
                     <div class="panel">
                         <div class="panel-body">
-                            <div class="btn-group" role="group">
-                                <button type="button" class="btn btn-secondary btn-sm">-</button>
-                                <a class="btn btn-secondary btn-sm" data-toggle="tooltip" data-placement="bottom" title="重置默认大小">100%</a>
-                                <button type="button" class="btn btn-secondary btn-sm">+</button>
-                            </div>
-                        </div>
-                        <div class="panel-foot text-muted">
-                            缩放
-                        </div>
-                    </div>
-                    <div class="panel">
-                        <div class="panel-body">
                             <div class="btn-group" role="group" aria-label="Button group with nested dropdown">
-                                <button type="button" class="btn btn-secondary btn-sm" data-toggle="modal" data-target="#paper"><i class="fa fa-file-o"></i> 纸张</button>
+                                <button type="button" class="btn btn-secondary btn-sm" data-toggle="modal" data-target="#paper"><i class="fa fa-file-o"></i> 页边距</button>
+                                <button type="button" class="btn btn-secondary btn-sm" data-toggle="modal" data-target="#content"><i class="fa fa-file-text-o"></i> 页面</button>
+                                <!--
                                 <button type="button" class="btn btn-secondary btn-sm" data-toggle="modal" data-target="#format"><i class="fa fa-bold"></i> 格式</button>
-                                <button type="button" class="btn btn-secondary btn-sm" data-toggle="modal" data-target="#content"><i class="fa fa-file-text-o"></i> 内容</button>
+                                -->
                             </div>
                         </div>
                         <div class="panel-foot text-muted">

+ 1 - 1
web/common/html/header.html

@@ -37,7 +37,7 @@
     <div id="testDisplay" style="color:#ff7e0e;">&nbsp;&nbsp;</div>
     <div class="ml-auto navbar-text p-0">
         <div class="dropdown d-inline-block navbar-nav">
-            <button class="btn btn-link btn-sm dropdown-toggle" type="button" data-toggle="dropdown"><%= sessionUser.real_name === '' ? sessionUser.email : sessionUser.real_name %></button>
+            <button class="btn btn-link btn-sm dropdown-toggle" type="button" data-toggle="dropdown"><%= sessionUser.real_name === '' ? sessionUser.mobile : sessionUser.real_name %></button>
             <div class="dropdown-menu dropdown-menu-right">
                 <a class="dropdown-item" href="/user/info" target="_blank">账号资料</a>
                 <a class="dropdown-item" href="user-buy.html" target="_blank">产品购买</a>

+ 2 - 1
web/common/html/page.html

@@ -1,6 +1,7 @@
 <nav aria-label="...">
-    <ul aria-label="pagination" id="pages"></ul>
+    <ul class="pagination" id="pages"></ul>
 </nav>
+<script type="text/javascript" src="/lib/bootstrap/bootstrap-paginator.js"></script>
 <script type="text/javascript">
     let options = {
         bootstrapMajorVersion: 3,

+ 34 - 0
web/users/html/login.html

@@ -66,6 +66,40 @@
             </div>
         </div>
     </div>
+    <!--弹出手机号-->
+    <div class="modal fade" id="phone" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 class="modal-title">验证手机号</h5>
+                </div>
+                <div class="modal-body">
+                    <input type="hidden" id="check_ssoId" value="" />
+                    <div class="form-row">
+                        <div class="form-group col-md-8">
+                            <input type="text" class="form-control" id="mobile" placeholder="输入手机号">
+                            <div class="invalid-feedback">
+                            </div>
+                        </div>
+                    </div>
+                    <div class="form-row">
+                        <div class="form-group col-md-8">
+                            <input type="text" class="form-control" readonly id="code" placeholder="输入验证码">
+                            <div class="invalid-feedback">
+                            </div>
+                        </div>
+                        <div class="form-group col-md-4">
+                            <button class="btn btn-primary" id="get-code">获取验证码</button>
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                    <button class="btn btn-primary" id="check-code">确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
     <!-- JS. -->
     <!-- inject:js -->
     <script type="text/javascript" src="/public/web/scMathUtil.js"></script>

+ 3 - 3
web/users/html/user-info.html

@@ -19,16 +19,16 @@
     <div class="poj-manage container-fluid">
         <div class="row">
             <div class="col-lg-2">
-                <div class="poj-cate mt-3">
+                <div class="mt-3">
                     <ul class="nav nav-pills flex-column">
                         <li class="nav-item">
-                            <a class="nav-link active" href="user-info.html">账号资料</a>
+                            <a class="nav-link active" href="/user/info">账号资料</a>
                         </li>
                         <li class="nav-item">
                             <a class="nav-link" href="/user/safe">账号安全</a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="user-buy.html">产品购买</a>
+                            <a class="nav-link" href="/user/buy">产品购买</a>
                         </li>
                         <li class="nav-item">
                             <a class="nav-link" href="/user/preferences">偏好设置</a>

+ 8 - 8
web/users/html/user-safe.html

@@ -5,7 +5,7 @@
     <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>账号资料-Smartcost</title>
+    <title>账号安全-Smartcost</title>
     <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
     <link rel="stylesheet" href="/web/building_saas/css/main.css">
     <link rel="stylesheet" href="/lib/font-awesome/font-awesome.min.css">
@@ -13,8 +13,8 @@
     <script src="/lib/jquery/jquery-3.2.1.min.js"></script>
     <script src="/lib/popper/popper.min.js"></script>
     <script src="/lib/bootstrap/bootstrap.min.js"></script>
-    <script type="text/javascript" src="/lib/bootstrap/bootstrap-paginator.js"></script>
-    <script src="/web/building_saas/js/global.js"></script>
+    <!--<script type="text/javascript" src="/lib/bootstrap/bootstrap-paginator.js"></script>-->
+    <!--<script src="/web/building_saas/js/global.js"></script>-->
 </head>
 
 <body>
@@ -25,7 +25,7 @@
     <div class="poj-manage container-fluid">
         <div class="row">
             <div class="col-lg-2">
-                <div class="poj-cate mt-3">
+                <div class="mt-3">
                     <ul class="nav nav-pills flex-column">
                         <li class="nav-item">
                             <a class="nav-link" href="/user/info">账号资料</a>
@@ -34,7 +34,7 @@
                             <a class="nav-link active" href="/user/safe">账号安全</a>
                         </li>
                         <li class="nav-item">
-                            <a class="nav-link" href="user-buy.html">产品购买</a>
+                            <a class="nav-link" href="/user/buy">产品购买</a>
                         </li>
                         <li class="nav-item">
                             <a class="nav-link" href="/user/preferences">偏好设置</a>
@@ -129,8 +129,8 @@
     </div>
 </div>
 </body>
-<script type="text/javascript">
-    autoFlashHeight();
-</script>
+<!--<script type="text/javascript">-->
+    <!--autoFlashHeight();-->
+<!--</script>-->
 
 </html>

+ 15 - 16
web/users/html/user-set.html

@@ -19,19 +19,20 @@
         <div class="poj-manage container-fluid">
             <div class="row">
                 <div class="col-lg-2">
-                    <div class="poj-cate mt-3">
+                    <!--<div class="poj-cate mt-3">-->
+                    <div class="mt-3">
                         <ul class="nav nav-pills flex-column">
                             <li class="nav-item">
-                                <a class="nav-link" href="user-info.html">账号资料</a>
+                                <a class="nav-link" href="/user/info">账号资料</a>
                             </li>
                             <li class="nav-item">
-                                <a class="nav-link" href="user-safe.html">账号安全</a>
+                                <a class="nav-link" href="/user/safe">账号安全</a>
                             </li>
                             <li class="nav-item">
-                                <a class="nav-link" href="user-buy.html">产品购买</a>
+                                <a class="nav-link" href="/user/buy">产品购买</a>
                             </li>
                             <li class="nav-item">
-                                <a class="nav-link active" href="user-set.html">偏好设置</a>
+                                <a class="nav-link active" href="/user/preferences">偏好设置</a>
                             </li>
                         </ul>
                     </div>
@@ -42,19 +43,17 @@
                         <form method="post" action="/user/save-preferences">
                             <div class="form-group">
                                 <label class="form-control-label">登录时选择版本</label>
-                                <div>
-                                    <label class="custom-control custom-radio">
-                                      <input name="login_ask" type="radio" class="custom-control-input" value="1"
+                                <div class="form-control">
+                                    <div class="form-check form-check-inline">
+                                      <input name="login_ask" type="radio" class="form-check-input" value="1"
                                         <% if(preferenceSetting.login_ask === 1) { %>checked="checked" <% } %>>
-                                      <span class="custom-control-indicator"></span>
-                                      <span class="custom-control-description">每次询问</span>
-                                    </label>
-                                    <label class="custom-control custom-radio">
-                                      <input name="login_ask" type="radio" class="custom-control-input" value="0"
+                                      <label class="form-check-label">每次询问</label>
+                                    </div>
+                                    <div class="form-check form-check-inline">
+                                      <input name="login_ask" type="radio" class="form-check-input" value="0"
                                         <% if(preferenceSetting.login_ask === 0) { %>checked="checked" <% } %>>
-                                      <span class="custom-control-indicator"></span>
-                                      <span class="custom-control-description">指定版本</span>
-                                    </label>
+                                      <span class="form-check-label">指定版本</span>
+                                    </div>
                                 </div>
                             </div>
                             <div class="form-group">

+ 134 - 0
web/users/js/login.js

@@ -35,6 +35,9 @@ $(document).ready(function () {
                         setVersion(response.compilation_list);
                         $('#ver').modal('show');
                     }
+                } else if(response.error === 2) {
+                    $('#check_ssoId').val(response.ssoId);
+                    $('#phone').modal('show');
                 } else {
                     let msg = response.msg !== undefined ? response.msg : '未知错误';
                     showError(msg, $("input"));
@@ -48,15 +51,146 @@ $(document).ready(function () {
 
     $("input").blur(function () {
         cleanError();
+        cleanValidError($(this));
     });
 
     $(".form-control").on('input', function () {
         $('#hint').html('&nbsp;');
     });
 
+    $("#get-code").click(function() {
+        const mobile = $("#mobile").val();
+        if(!validMobile(mobile)){
+            return false;
+        }
+        const btn = $(this);
+        if(!btn.hasClass('disabled')){
+            $.ajax({
+                url: '/sms/code',
+                type: 'post',
+                data: { mobile: mobile, type: 1},
+                error: function() {
+                    showValidError('短信接口出错!',$('#mobile'));
+                },
+                beforeSend: function() {
+                },
+                success: function(response) {
+                    if (response.err === 0) {
+                        codeSuccess(btn);
+                    } else {
+                        showValidError(response.msg,$('#mobile'));
+                    }
+                }
+            });
+        }
+    });
+
+    $('#check-code').click(function () {
+        const mobile = $("#mobile").val();
+        const ssoId = $("#check_ssoId").val();
+        const code = $("#code").val();
+        if(!validMobile(mobile)) {
+            return false;
+        }
+        if(ssoId === undefined || ssoId === '') {
+            showValidError('账号有误!', $('#code'));
+            return false;
+        }
+        if($.trim(code) === '') {
+            showValidError('验证码不能为空!', $('#code'));
+            return false;
+        }
+        $.ajax({
+            url: '/sms/mobile',
+            type: 'post',
+            data: {ssoId: ssoId, mobile: mobile, code: code},
+            error: function() {
+                showValidError('接口出错!',$('#code'));
+            },
+            beforeSend: function() {
+            },
+            success: function(response) {
+                if (response.err === 0) {
+                    $("#login").click();
+                    $('#phone').modal('hide');
+                } else {
+                    showValidError(response.msg,$('#code'));
+                }
+            }
+        })
+
+    });
+
 });
 
 /**
+ * 获取成功后的操作
+ *
+ * @param {Object} btn - 点击的按钮
+ * @return {void}
+ */
+function codeSuccess(btn) {
+    let counter = 60;
+    btn.removeClass('btn-primary').addClass('btn-outline-secondary disabled').text(counter + '秒 重新获取');
+    btn.parents().siblings('div').children('input').removeAttr('readonly');
+
+    const countDown = setInterval(function() {
+        const countString = counter - 1 <= 0 ? '' : ' ' + (counter - 1) + '秒 ';
+        // 倒数结束后
+        if (countString === '') {
+            clearInterval(countDown);
+            btn.removeClass('btn-outline-secondary disabled').addClass('btn-primary').text('获取验证码');
+        }
+        const text = countString + '重新获取';
+        btn.text(text);
+        counter -= 1;
+    }, 1000);
+}
+
+/**
+ * 验证手机号是否正确
+ *
+ * @return {boolean}
+ */
+function validMobile(mobile) {
+    let result = true;
+    if($.trim(mobile) === ''){
+        showValidError('手机号不能为空!',$('#mobile'));
+        return false;
+    }
+    let mobileValid =  /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1})|(17[0-9]{1})|(14[0-9]{1}))+\d{8})$/;
+    if(!mobileValid.test(mobile)){
+        showValidError('手机号码格式有误!',$('#mobile'));
+        return false;
+    }
+    return result;
+}
+
+/**
+ * 提示验证信息错误
+ *
+ * @param {string} msg
+ * @param {object} element
+ * @return {void}
+ */
+function showValidError(msg, element) {
+    if (element !== null) {
+        element.addClass('is-invalid');
+        element.siblings().text(msg);
+    }
+}
+
+/**
+ * 清除验证信息错误提示
+ *
+ * @return {void}
+ */
+function cleanValidError(element) {
+    element.removeClass('is-invalid');
+    element.siblings().text('');
+}
+
+/**
  * 验证数据
  *
  * @return {boolean}