Преглед изворни кода

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

Conflicts:
	modules/pm/controllers/pm_controller.js
zhangweicheng пре 7 година
родитељ
комит
aca51bfe35
48 измењених фајлова са 1647 додато и 383 уклоњено
  1. 33 0
      modules/all_models/rpt_customize_cfg.js
  2. 1 0
      modules/all_models/user.js
  3. 3 1
      modules/common/helper/mongoose_helper.js
  4. 35 2
      modules/pm/controllers/pm_controller.js
  5. 1 0
      modules/pm/routes/pm_route.js
  6. 17 8
      modules/reports/controllers/rpt_controller.js
  7. 49 34
      modules/reports/controllers/rpt_tpl_controller.js
  8. 18 0
      modules/reports/facade/rpt_cust_cfg_facade.js
  9. 2 2
      modules/reports/routes/report_router.js
  10. 1 0
      modules/reports/routes/rpt_tpl_router.js
  11. 6 2
      modules/users/controllers/login_controller.js
  12. 80 0
      modules/users/controllers/sms_controller.js
  13. 1 1
      modules/users/controllers/user_controller.js
  14. 1 1
      modules/users/models/log_model.js
  15. 97 0
      modules/users/models/sms_model.js
  16. 27 5
      modules/users/models/user_model.js
  17. 21 0
      modules/users/routes/sms_route.js
  18. 2 2
      public/web/rpt_value_define.js
  19. 2 2
      server.js
  20. 1 1
      test/calculation/test_analyzer.js
  21. 1 1
      test/unit/reports/rpt_cfg.js
  22. 4 4
      test/unit/reports/test_cover_01.js
  23. 3 3
      test/unit/reports/test_cover_02.js
  24. 2 2
      test/unit/reports/test_get_prj_data.js
  25. 2 2
      test/unit/reports/test_preview_page.js
  26. 76 0
      test/unit/reports/test_rpt_cust_cfg.js
  27. 106 0
      test/unit/reports/test_tpl_07.js
  28. 2 2
      test/unit/reports/test_tpl_09.js
  29. 2 2
      test/unit/reports/test_tpl_09_1.js
  30. 68 30
      web/building_saas/main/js/models/calc_program.js
  31. 1 3
      web/building_saas/main/js/views/calc_base_view.js
  32. 82 6
      web/building_saas/main/js/views/calc_program_manage.js
  33. 3 33
      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. 193 0
      web/building_saas/report/html/rpt_content_format.html
  38. 38 0
      web/building_saas/report/html/rpt_export.html
  39. 13 117
      web/building_saas/report/html/rpt_main.html
  40. 66 0
      web/building_saas/report/html/rpt_margins.html
  41. 87 39
      web/building_saas/report/js/rpt_main.js
  42. 1 1
      web/common/html/header.html
  43. 2 1
      web/common/html/page.html
  44. 34 0
      web/users/html/login.html
  45. 3 3
      web/users/html/user-info.html
  46. 8 8
      web/users/html/user-safe.html
  47. 15 16
      web/users/html/user-set.html
  48. 134 0
      web/users/js/login.js

+ 33 - 0
modules/all_models/rpt_customize_cfg.js

@@ -0,0 +1,33 @@
+/**
+ * Created by Tony on 2018/4/18.
+ * 用户自定义报表输出格式(如字体,边距,竖线,窄体,小数补0等)
+ */
+import mongoose from "mongoose";
+let Schema = mongoose.Schema;
+let CustomizeCfgSchema = new Schema({
+    userId: String,
+    fonts: [{
+        "ID" : String,
+        "CfgDispName" : String,
+        "Name" : String,
+        "FontHeight" : Number,
+        "FontColor" : String,
+        "FontBold" : String,
+        "FontItalic" : String,
+        "FontUnderline" : String,
+        "FontStrikeOut" : String,
+        "FontAngle" : Number
+        }
+    ],
+    margins: {
+        Left: Number,
+        Right: Number,
+        Top: Number,
+        Bottom: Number
+    },
+    showVerticalLine: Boolean,
+    isNarrow: Boolean,
+    fillZero: Boolean
+});
+
+mongoose.model("rpt_customize_cfg", CustomizeCfgSchema, "rpt_customize_cfg");

+ 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);
         });
     }
 

+ 35 - 2
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');
 let pm_facade = require('../facade/pm_facade');
 
@@ -354,8 +356,39 @@ module.exports = {
         ProjectsData.recGC(userID, nodes, function (err, msg, data) {
             callback(request, response, err, msg, data);
         });
-    },
-    moveProject:async function(req,res){
+   },
+
+    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);
+        }    },
+moveProject:async function(req,res){
         let result={
             error:0
         }

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

@@ -49,6 +49,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);
 };

+ 17 - 8
modules/reports/controllers/rpt_controller.js

@@ -71,8 +71,10 @@ function getAllPagesCommonOrg(rpt_id, pageSize, option, cb) {
         }
     );
 }
-
-function getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, option, cb) {
+function setupCustomizeCfg(customizeCfg, rptTpl) {
+    //
+}
+function getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, orientation, customizeCfg, option, cb) {
     let rptTpl = null;
     rptTplFacade.getRptTemplate(rpt_id).then(function(rst) {
         rptTpl = rst;
@@ -84,7 +86,9 @@ function getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, option, cb) {
                 if (!err) {
                     let tplData = rptDataUtil.assembleData(rawDataObj);
                     let printCom = JpcEx.createNew();
-                    rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pageSize;
+                    if (pageSize) rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_PAGE_SIZE] = pageSize;
+                    if (orientation) rptTpl[JV.NODE_MAIN_INFO][JV.NODE_PAGE_INFO][JV.PROP_ORIENTATION] = orientation;
+                    if (customizeCfg) setupCustomizeCfg(customizeCfg, rptTpl);
                     let defProperties = rptUtil.getReportDefaultCache();
                     let dftOption = option||JV.PAGING_OPTION_NORMAL;
                     printCom.initialize(rptTpl);
@@ -113,10 +117,13 @@ module.exports = {
         let params = JSON.parse(req.body.params),
             rpt_id = params.rpt_tpl_id,
             prj_id = params.prj_id,
-            user_id = params.user_id,
-            pageSize = params.pageSize;
+            pageSize = params.pageSize,
+            orientation = params.orientation,
+            customizeCfg = params.custCfg
+        ;
         // req.session.sessionUser.ssoId
-        getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, null, function (err, pageRst) {
+        let user_id = req.session.sessionUser.id;
+        getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, orientation, customizeCfg, null, function (err, pageRst) {
             callback(req, res, err, pageRst);
         });
     },
@@ -133,12 +140,13 @@ module.exports = {
         let prj_id = req.params.prj_id,
             rpt_id = req.params.rpt_id,
             pageSize = req.params.size,
+            orientation = req.params.orientation,
             rptName = req.params.rptName,
             isOneSheet = req.params.isOneSheet,
             option = req.params.option;
         let user_id = req.session.sessionUser.id;
         let dftOption = option||JV.PAGING_OPTION_NORMAL;
-        getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, dftOption, function(err, pageRst){
+        getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, orientation, null, dftOption, function(err, pageRst){
             try {
                 rpt_xl_util.exportExcel(pageRst, pageSize, rptName, isOneSheet, null, function(newName){
                     res.setHeader('Content-Type', 'application/vnd.openxmlformats');
@@ -226,10 +234,11 @@ module.exports = {
         let prj_id = req.params.prj_id,
             rpt_id = req.params.rpt_id,
             pageSize = req.params.size,
+            orientation = req.params.orientation,
             rptName = req.params.rptName
         ;
         let user_id = req.session.sessionUser.id;
-        getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, JV.PAGING_OPTION_NORMAL, function(err, pageRst){
+        getAllPagesCommon(user_id, prj_id, rpt_id, pageSize, orientation, null, JV.PAGING_OPTION_NORMAL, function(err, pageRst){
             rpt_pdf_util.export_pdf_file(pageRst, pageSize, rptName,function (newName) {
                 res.setHeader('Content-Type', 'application/vnd.openxmlformats');
                 res.setHeader("Content-Disposition", "attachment; filename=" + strUtil.getPinYinCamelChars(rptName) + ".pdf");

+ 49 - 34
modules/reports/controllers/rpt_tpl_controller.js

@@ -10,22 +10,48 @@ import counter from "../../../public/counter/counter";
 let RptTplModel = mongoose.model('rpt_templates');
 let TreeNodeModel = mongoose.model('rpt_tpl_tree');
 
-import rpttplDefObj from "../../../public/rpt_tpl_def";
-let rptTplDef = rpttplDefObj.getUtil();
+let rptCustCfgFacade = require("../facade/rpt_cust_cfg_facade");
 
-//import stringUtil from "../../../public/stringUtil";
 import JV from "../rpt_component/jpc_value_define";
 import rttFacade from "../facade/rpt_tpl_tree_node_facade";
 import CompilationModel from "../../users/models/compilation_model";
 
-//import test_glj_type_util from "../../../public/cache/std_glj_type_util");
-
 //统一回调函数
 let callback = function(req, res, err, message, data){
     res.json({error: err, message: message, data: data});
 };
 
 let mExport = {
+    getCustomerCfg(req, res) {
+        let params = JSON.parse(req.body.params)
+            // userId = params.userId
+        ;
+        let userId = req.session.sessionUser.id;
+        let userIds = [];
+        userIds.push(userId);
+        if (userId !== "-100") {
+            userIds.push("-100");
+        }
+        rptCustCfgFacade.getCustomizeCfg(userIds).then(function (custCfg) {
+            if (custCfg) {
+                //success
+                let rst = null;
+                if (custCfg.length > 0) {
+                    for (let itemCfg of custCfg) {
+                        rst = itemCfg;
+                        let doc = (itemCfg._doc)?itemCfg._doc:itemCfg;
+                        if (doc.userId !== "-100") {
+                            break;
+                        }
+                    }
+                }
+                callback(req,res, false, "", rst);
+            } else {
+                //failed
+                callback(req,res, true, "失败!", null);
+            }
+        })
+    },
     getDftTemplates(req, res) {
         let filter = {"userId": "-100", "$or": [{"isDeleted": null}, {"isDeleted": false} ]};
         TreeNodeModel.find(filter, '-_id', function(err, data){
@@ -50,12 +76,18 @@ let mExport = {
     getRptTplTree: function(req, res) {
         let params = JSON.parse(req.body.params),
             compilationId = params.compilationId,
-            userId = params.userId,
+            // userId = params.userId,
             engineerId = params.engineerId;
+        let userId = req.session.sessionUser.id;
+        let userIds = [];
+        userIds.push(userId);
+        if (userId != "-100") {
+            userIds.push("-100");
+        }
         if (!compilationId) {
             compilationId = req.session.sessionCompilation._id;
         }
-        rttFacade.findTplTree(compilationId, engineerId, userId).then(function(result) {
+        rttFacade.findTplTree(compilationId, engineerId, userIds).then(function(result) {
             if (result) {
                 callback(req,res,false,"", result);
             } else {
@@ -63,29 +95,6 @@ let mExport = {
             }
         });
     },
-    // getRptTplTree_org: function(req, res) {
-    //     let params = JSON.parse(req.body.params),
-    //         grpType = params.grpType,
-    //         userId = params.userId,
-    //         tplType = params.tplType;
-    //     let filter = {"grpType": grpType, "$or": [{"isDeleted": null}, {"isDeleted": false} ]};
-    //     if (userId) {
-    //         filter.userId = userId;
-    //     }
-    //     if ((tplType && tplType !== rptTplDef.TplType.ALL)) {
-    //         filter.tplType = tplType;
-    //     }
-    //     TreeNodeModel.find(filter, '-_id', function(err, data){
-    //         if (err) {
-    //             callback(req,res, true,"", null);
-    //         } else {
-    //             callback(req,res,false,"", data);
-    //         }
-    //     });
-    //     // let obj = test_glj_type_util.getStdGljTypeCacheObj();
-    //     // console.log(obj.toArray());
-    //     // console.log(obj.getItemById(1));
-    // },
     updateTreeNodes: function(req, res) {
         let params = JSON.parse(req.body.params),
             nodes = params.nodes;
@@ -153,9 +162,11 @@ let mExport = {
     },
     updateTopNodeName: function (req, res) {
         let params = JSON.parse(req.body.params),
-            compilationId = params.compilationId, engineerId = params.engineerId, userId = params.userId,
+            compilationId = params.compilationId, engineerId = params.engineerId,
+            // userId = params.userId,
             nodeName = params.nodeName
         ;
+        let userId = req.session.sessionUser.id;
         let filter = {"compilationId": compilationId, "engineerId": engineerId, "userId": userId, "$or": [{"isDeleted": null}, {"isDeleted": false}]};
         let updateStatement = {"$set": {"name": nodeName}};
         rttFacade.updateTreeInDetail(filter, updateStatement).then(function (rst) {
@@ -170,9 +181,11 @@ let mExport = {
     },
     updateSubLevelOneNode: function (req, res) {
         let params = JSON.parse(req.body.params),
-            compilationId = params.compilationId, engineerId = params.engineerId, userId = params.userId,
+            compilationId = params.compilationId, engineerId = params.engineerId,
+            // userId = params.userId,
             subNode = params.subNode
         ;
+        let userId = req.session.sessionUser.id;
         let filter = {"compilationId": compilationId, "engineerId": engineerId, "userId": userId, "items.ID": subNode.ID, "$or": [{"isDeleted": null}, {"isDeleted": false}]};
         let updateStatement = {$set: {"items.$": subNode}};
         rttFacade.updateTreeInDetail(filter, updateStatement).then(function (rst) {
@@ -190,9 +203,10 @@ let mExport = {
         let params = JSON.parse(req.body.params),
             compilationId = params.compilationId,
             engineerId = params.engineerId,
-            userId = params.userId,
+            // userId = params.userId,
             isPhysically = params.isPhysically
             ;
+        let userId = req.session.sessionUser.id;
         if (isPhysically) {
             rttFacade.removeTreePhycically(compilationId, engineerId, userId).then(function (rst) {
                 if (rst) {
@@ -254,9 +268,10 @@ let mExport = {
             grpKey = params.grpKey,
             compilationId = params.compilationId,
             engineerId = params.engineerId,
-            userId = params.userId,
+            // userId = params.userId,
             subNode = params.subNode
         ;
+        let userId = req.session.sessionUser.id;
         let filter = {"ID": rptDftTplId};
         RptTplModel.findOne(filter, '-_id').exec().then(function(dftTplRst) {
             if (dftTplRst) {

+ 18 - 0
modules/reports/facade/rpt_cust_cfg_facade.js

@@ -0,0 +1,18 @@
+/**
+ * Created by Tony on 2018/4/18.
+ */
+
+import mongoose from "mongoose";
+let rpt_customize_cfg_mdl = mongoose.model("rpt_customize_cfg");
+
+module.exports = {
+    getCustomizeCfg: getCustomizeCfg
+};
+
+async function getCustomizeCfg(userIds) {
+    let filter = {"userId": userIds};
+    if (userIds instanceof Array) {
+        filter.userId = {$in: userIds};
+    }
+    return await rpt_customize_cfg_mdl.find(filter, '-_id');
+}

+ 2 - 2
modules/reports/routes/report_router.js

@@ -25,8 +25,8 @@ module.exports =function (app) {
     rptRouter.get('/getTestPDF/:id/:size/:rptName', reportController.getTestPDF);
     //now is the real:
     rptRouter.post('/getReport', reportController.getReportAllPages);
-    rptRouter.get('/getExcel/:prj_id/:rpt_id/:size/:rptName/:isOneSheet/:option', reportController.getExcel);
-    rptRouter.get('/getPDF/:prj_id/:rpt_id/:size/:rptName', reportController.getPDF);
+    rptRouter.get('/getExcel/:prj_id/:rpt_id/:size/:orientation/:rptName/:isOneSheet/:option', reportController.getExcel);
+    rptRouter.get('/getPDF/:prj_id/:rpt_id/:size/:orientation/:rptName', reportController.getPDF);
     // rptRouter.get('/getExcelInOneBook/:ids/:size/:rptName/:option', reportController.getExcelInOneBook);
 
     app.use("/report_api", rptRouter);

+ 1 - 0
modules/reports/routes/rpt_tpl_router.js

@@ -28,6 +28,7 @@ module.exports = function (app) {
     rptTplRouter.post('/getRefRptTpl', reportTplController.getRefRptTpl);
     rptTplRouter.post('/updateRptTpl', reportTplController.updateRptTpl);
     rptTplRouter.post('/getCompilationList', reportTplController.getCompilationList);
+    rptTplRouter.post('/getCustomizeCfg', reportTplController.getCustomerCfg);
 
     rptTplRouter.post('/getUserRptCfg', reportCfgController.getReportUserCfg);
     rptTplRouter.post('/getMappingFields', reportCfgController.getAllMappingFields);

+ 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);
+};

+ 2 - 2
public/web/rpt_value_define.js

@@ -235,8 +235,8 @@ const JV = {
 
     ORIENTATION_PORTRAIT: "PORTRAIT",
     ORIENTATION_LANDSCAPE: "LANDSCAPE",
-    ORIENTATION_PORTRAIT_CHN: "纵",
-    ORIENTATION_LANDSCAPE_CHN: "横",
+    ORIENTATION_PORTRAIT_CHN: "纵",
+    ORIENTATION_LANDSCAPE_CHN: "横",
     SIZE_A3: [11.69, 16.54],
     SIZE_A4: [8.27, 11.69],
     SIZE_A5: [5.83, 8.27],

+ 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 {

+ 1 - 1
test/calculation/test_analyzer.js

@@ -213,5 +213,5 @@ let calcTemplate = {
 let expr = "F2+F5+F6+F10";
 let arr = analyzer.getFArr(expr);
 console.log(JSON.stringify(arr));
-let id = analyzer.getFID('F10', calcTemplate);
+let id = analyzer.getID('F10', calcTemplate);
 console.log(JSON.stringify(id));

+ 1 - 1
test/unit/reports/rpt_cfg.js

@@ -73,7 +73,7 @@ module.exports = {
             "FontAngle" : "0"
         },
         {
-            "ID" : "GramdTotal",
+            "ID" : "GrandTotal",
             "CfgDispName" : "总合计",
             "Name" : "smartSimSun",
             "FontHeight" : "12",

+ 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");

+ 76 - 0
test/unit/reports/test_rpt_cust_cfg.js

@@ -0,0 +1,76 @@
+/**
+ * Created by Tony on 2018/4/19.
+ */
+
+let test = require('tape');
+
+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 userId_Leng = "59cdf14a0034a1000ba52b97"; //小冷User Id 换成_id了
+let userId_Dft = userId_Leng;
+/*/
+ let userId_Dft = "595328da1934dc327cad08eb";
+ //*/
+
+let rptCustCfgFacade = require("../../../modules/reports/facade/rpt_cust_cfg_facade");
+
+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('测试 - 用户自定义报表配置: ', function (t) {
+    let userIds = [];
+    userIds.push(userId_Leng);
+    userIds.push('-100');
+    rptCustCfgFacade.getCustomizeCfg(userIds).then(function(custCfg) {
+        // console.log(custCfg);
+        if (custCfg.length > 1) {
+            // let rst = null;
+            for (let itemCfg of custCfg) {
+                let doc = (itemCfg._doc)?itemCfg._doc:itemCfg;
+                if (doc.userId === userId_Leng) {
+                    // rst = doc;
+                    console.log(doc);
+                    break;
+                }
+            }
+        }
+        // console.log(err);
+        // console.log(msg);
+        t.pass('pass succeeded!');
+        t.end();
+    });
+});
+//*/
+
+test('close the connection', function (t) {
+    setTimeout(function () {
+        mongoose.disconnect();
+        t.pass('closing db connection');
+        t.end();
+    }, 500);
+    // mongoose.disconnect();
+    // t.pass('closing db connection');
+    // t.end();
+});

+ 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");

+ 68 - 30
web/building_saas/main/js/models/calc_program.js

@@ -194,7 +194,7 @@ let calcTools = {
         let ns = fieldName.split(".");
         if (ns.length != 2)
             return 0
-        else if (treeNode.data.feesIndex[ns[0]] && treeNode.data.feesIndex[ns[0]][ns[1]])
+        else if (treeNode.data.feesIndex && treeNode.data.feesIndex[ns[0]] && treeNode.data.feesIndex[ns[0]][ns[1]])
             return parseFloat(treeNode.data.feesIndex[ns[0]][ns[1]])
         else
             return 0;
@@ -708,7 +708,7 @@ let analyzer = {
         let arrF = expr.match(pattF);
         return arrF ? arrF : [];
     },
-    getAtIDArr: function (expr) {
+    getAtIDArr: function (expr) {     // ['@1','@2']
         let patt = new RegExp(/@\d+/gi);
         let arr = expr.match(patt);
         return arr ? arr : [];
@@ -719,12 +719,20 @@ let analyzer = {
         return arrBase ? arrBase : [];
     },
 
-    getFID: function (FName, calcTemplate) {          // F3、F22 → 4、99
+    getID: function (FName, calcTemplate) {          // F13 → 4
         let idx = FName.slice(1) - 1;
         let id = calcTemplate.calcItems[idx] ? calcTemplate.calcItems[idx].ID : null;
         return id;
     },
-    getFItem: function (FName, calcTemplate){      // F3 → calcItems[2] → {Object}
+    getFName: function (atID, calcTemplate) {          // @3 → F7
+        for (var i = 0; i < calcTemplate.calcItems.length; i++) {
+            if (calcTemplate.calcItems[i].ID == atID.slice(1)) {
+                return 'F'+ (i + 1);
+            }
+        }
+        return null;
+    },
+    getItem: function (FName, calcTemplate){      // F3 → calcItems[2] → {Object}
         let idx = FName.slice(1) - 1;
         return calcTemplate.calcItems[idx];
     },
@@ -797,32 +805,46 @@ let analyzer = {
 
         return true;  // 表达式合法
     },
-    getExpression: function (dispExpr, calcTemplate) {
-        function refLineToID(expr, template) {
-            let rst = expr;
-            let fArr = me.getFArr(rst);
-            let IDArr = [];
-            for (let F of fArr){
-                let ID = me.getFID(F, template);
-                IDArr.push(ID);
-            };
-            for (let i = 0; i < fArr.length; i++) {
-                let patt = new RegExp(fArr[i]);
-                let val = `@${IDArr[i]}`;
-                rst = rst.replace(patt, val);
-            };
-            return rst;
+    refIDToLines: function (expression, template) {    // @2、@3 → F7、F8
+        let rst = expression;
+        let atIDArr = analyzer.getAtIDArr(rst);
+        let fArr = [];
+        for (let atID of atIDArr) {
+            let FN = analyzer.getFName(atID, template);
+            fArr.push(FN);
         };
-
-        let me = analyzer;
-        let expr = me.standard(dispExpr);
-        return refLineToID(expr, calcTemplate);
+        for (let i = 0; i < atIDArr.length; i++) {
+            let patt = new RegExp(atIDArr[i]);
+            let val = fArr[i];
+            rst = rst.replace(patt, val);
+        };
+        return rst;
+    },
+    refLineToIDs: function (dispExpr, template) {   // F7、F8 → @2、@3
+        let rst = analyzer.standard(dispExpr);
+        let fArr = me.getFArr(rst);
+        let IDArr = [];
+        for (let F of fArr) {
+            let ID = me.getID(F, template);
+            IDArr.push(ID);
+        };
+        for (let i = 0; i < fArr.length; i++) {
+            let patt = new RegExp(fArr[i]);
+            let val = `@${IDArr[i]}`;
+            rst = rst.replace(patt, val);
+        };
+        return rst;
+    },
+    getDispExpr: function (expression, calcTemplate) {
+        return analyzer.refIDToLines(expression, calcTemplate);
+    },
+    getExpression: function (dispExpr, calcTemplate) {
+        return analyzer.refLineToIDs(dispExpr, calcTemplate);
     },
     getDispExprUser: function (dispExpr, labourCoe) {   // labourCoe 是 str 类型
-        let me = analyzer;
-        let expr = me.standard(dispExpr);
-        expr = expr.replace(/L/g, labourCoe);
-        return expr;
+        let rst = analyzer.standard(dispExpr);
+        rst = rst.replace(/L/g, labourCoe);
+        return rst;
     },
     getCompiledExpr: function (expression, labourCoe) {   // labourCoe 是 str 类型
         let me = analyzer;
@@ -1053,8 +1075,7 @@ class CalcProgram {
             for (let idx of template.compiledSeq) {
                 let item = template.calcItems[idx];
 
-                let lc = 0;
-                if (item.labourCoeID) lc = me.compiledLabourCoes[item.labourCoeID].coe.toString();
+                let lc = me.getLabourCoe(item);
                 // 用于界面显示。disExpr是公式模板,不允许修改:人工系数占位符被修改后变成数值,第二次无法正确替换。
                 item.dispExprUser = analyzer.getDispExprUser(item.dispExpr, lc);
                 if (item.expression == 'HJ')
@@ -1083,7 +1104,8 @@ class CalcProgram {
 
             for (let i = 0; i < template.calcItems.length; i++) {
                 let item = template.calcItems[i];
-                item.expression = analyzer.getExpression(item.dispExpr, template);
+                // item.expression = analyzer.getExpression(item.dispExpr, template);
+                item.dispExpr = analyzer.getDispExpr(item.expression, template);
                 template.compiledCalcItems[item.ID] = item;
                 template.compiledCalcItems[item.ID + "_idx"] = i;
             }
@@ -1103,6 +1125,22 @@ class CalcProgram {
             }
         };
     };
+    
+    refreshTemplate(template){
+        let me = this;
+        for (let item of template){
+             item.dispExpr = analyzer.getDispExpr(item.expression, template);
+             item.dispExprUser = analyzer.getDispExprUser(item.dispExpr, me.getLabourCoe(item));
+        };
+    };
+
+    getLabourCoe(calcItem){
+        let me = this;
+        let lc = 0;
+        if (calcItem.labourCoeID)
+            lc = me.compiledLabourCoes[calcItem.labourCoeID].coe.toString();
+        return lc;
+    };
 
     // 存储、刷新零散的多个结点。
     saveNodes(treeNodes, callback){

+ 1 - 3
web/building_saas/main/js/views/calc_base_view.js

@@ -281,9 +281,7 @@ let calcBaseView = {
                 if (calcItem.dispExpr != expr){
                     if (analyzer.isLegal(expr, calcItem.ID, template)){
                         let cp = projectObj.project.calcProgram;
-                        let lc = 0;
-                        if (calcItem.labourCoeID)
-                            lc = cp.compiledLabourCoes[calcItem.labourCoeID].coe.toString();
+                        let lc = cp.getLabourCoe(calcItem);
                         calcItem.dispExpr = expr;
                         calcItem.dispExprUser = analyzer.getDispExprUser(expr, lc);
                         calcItem.expression = analyzer.getExpression(expr, template);

+ 82 - 6
web/building_saas/main/js/views/calc_program_manage.js

@@ -62,6 +62,8 @@ let calcProgramManage = {
         me.detailSpread.getSheet(0).bind(GC.Spread.Sheets.Events.CellChanged, me.onDetailCellChanged);
         me.detailSpread.getSheet(0).bind(GC.Spread.Sheets.Events.EditEnded, me.onDetailEditEnded);
         me.detailSpread.getSheet(0).bind(GC.Spread.Sheets.Events.EnterCell, me.onEnterCell);
+        me.loadMainContextMenu();
+        me.loadDetailContextMenu();
         let mSheet = me.mainSpread.getSheet(0);
         sheetCommonObj.showData(mSheet, me.mainSetting, me.datas);
 
@@ -116,15 +118,89 @@ let calcProgramManage = {
         }
     },
     onEnterCell: function (sender, args) {
-       /* let t = calcProgramManage.getSelectionInfo().template;
+        let t = calcProgramManage.getSelectionInfo().template;
         let c = calcProgramManage.getSelectionInfo().calcItem;
-        c.expression = analyzer.getExpression(c.dispExpr, t);
-        let lc = 0;
-        if (c.labourCoeID) lc = projectObj.project.calcProgram.compiledLabourCoes[c.labourCoeID].coe.toString();
+        let lc = projectObj.project.calcProgram.getLabourCoe(c);
+        c.dispExpr = analyzer.getDispExpr(c.expression, t);
+        c.dispExprUser = analyzer.getDispExprUser(c.dispExpr, lc);
         c.compiledExpr = analyzer.getCompiledExpr(c.expression, lc);
+        let e = c.expression + ' ' + c.dispExpr + ' ' + c.dispExprUser + ' ' + c.compiledExpr;
+        projectObj.testDisplay('', e);
+    },
+    loadMainContextMenu: function () {
+        $.contextMenu({
+            selector: '#mainSpread',
+            items: {
+                "copyTemplate": {
+                    name: "另存为...",
+                    icon: 'fa-sign-in',
+                    disabled: function () {
+                        return false;
+                    },
+                    visible: function(key, opt){
+                        return true;
+                    },
+                    callback: function (key, opt) {
+                        // doCopy
+                    }
+                },
+                "spr1": '--------',
+                "deleteTemplate": {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    disabled: function () {
+                        return false;
+                    },
+                    visible: function(key, opt){
+                        return true;
+                    },
+                    callback: function () {
+                        // doDelete
+                    }
+                }
+            }
+        });
+    },
+    loadDetailContextMenu: function () {
+        $.contextMenu({
+            selector: '#detailSpread',
+            build: function ($trigger, e) {
+                var target = SheetDataHelper.safeRightClickSelection($trigger, e, calcProgramManage.detailSpread);
+                // return false;
+                // return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+            },
+            items: {
+                "insertCalcItem": {
+                    name: "插入行",
+                    icon: 'fa-sign-in',
+                    disabled: function () {
+                        return false;
+                    },
+                    callback: function (key, opt) {
+                        // do
+                    }
+                },
+                "deleteCalcItem": {
+                    name: '删除行',
+                    icon: 'fa-remove',
+                    disabled: function () {
+                        // var idx = calcProgramManage.detailSpread.getActiveSheet().getActiveRowIndex();
+                        // return idx >9;
+                        return false;
+                    },
+                    callback: function () {
+                        // var selected = project.mainTree.selected;
+                        // if(selected.sourceType == project.Bills.getSourceType()){
+                        //     project.Bills.deleteSelectedNode();
+                        // }else {
+                        //     $("#delete_row").modal({show:true});//弹出删除提示框;
+                        // }
+                    }
+                }
+            }
+        });
+
 
-        let e = c.dispExpr + '  ' + c.expression + '  ' + c.compiledExpr;
-        projectObj.testDisplay('', e);*/
     },
     saveCalcItem: function (data,callback) {//data
         let me = this;

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

@@ -31,9 +31,6 @@ var projectObj = {
         }
         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);
@@ -455,35 +452,6 @@ 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;
@@ -1187,7 +1155,9 @@ var projectObj = {
         let me = this;
         TREE_SHEET_HELPER.massOperationSheet(sheet, function () {
             for(let node of nodes){
-                sheet.setStyle(node.serialNo(), -1, me.getNodeColorStyle(sheet, node));
+                if(node){
+                    sheet.setStyle(node.serialNo(), -1, me.getNodeColorStyle(sheet, node));
+                }
             }
         });
      

+ 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

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

+ 193 - 0
web/building_saas/report/html/rpt_content_format.html

@@ -0,0 +1,193 @@
+<div class="modal fade" id="content_format" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">格式</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="row mb-1">
+                    <div class="col-3">
+                        表标题
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>宋体</option></select>
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>20</option></select>
+                    </div>
+                    <div class="col-3">
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="加粗"><i class="fa fa-bold"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="斜体"><i class="fa fa-italic"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="下划线"><i class="fa fa-underline"></i></a>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-3">
+                        表栏头
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>宋体</option></select>
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>20</option></select>
+                    </div>
+                    <div class="col-3">
+                        <a href="" class="btn btn-sm btn-outline-secondary active" title="加粗"><i class="fa fa-bold"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="斜体"><i class="fa fa-italic"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="下划线"><i class="fa fa-underline"></i></a>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-3">
+                        表正文
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>宋体</option></select>
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>20</option></select>
+                    </div>
+                    <div class="col-3">
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="加粗"><i class="fa fa-bold"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary active" title="斜体"><i class="fa fa-italic"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="下划线"><i class="fa fa-underline"></i></a>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-3">
+                        表合计
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>宋体</option></select>
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>20</option></select>
+                    </div>
+                    <div class="col-3">
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="加粗"><i class="fa fa-bold"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="斜体"><i class="fa fa-italic"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary active" title="下划线"><i class="fa fa-underline"></i></a>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-3">
+                        表眉表脚
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>宋体</option></select>
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>20</option></select>
+                    </div>
+                    <div class="col-3">
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="加粗"><i class="fa fa-bold"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="斜体"><i class="fa fa-italic"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="下划线"><i class="fa fa-underline"></i></a>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-3">
+                        页眉页脚
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>宋体</option></select>
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>20</option></select>
+                    </div>
+                    <div class="col-3">
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="加粗"><i class="fa fa-bold"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="斜体"><i class="fa fa-italic"></i></a>
+                        <a href="" class="btn btn-sm btn-outline-secondary" title="下划线"><i class="fa fa-underline"></i></a>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-3">
+                        表格线粗
+                    </div>
+                    <div class="col-4">
+                        <select class="form-control form-control-sm"><option>1</option></select>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-3">
+                        边框线粗
+                    </div>
+                    <div class="col-4">
+                        <select class="form-control form-control-sm"><option>1</option></select>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-4">
+                        <div class="form-check">
+                            <label class="form-check-label">
+                                <input type="checkbox" class="form-check-input">
+                                报表边框横线
+                            </label>
+                        </div>
+                    </div>
+                    <div class="col-4">
+                        <div class="form-check">
+                            <label class="form-check-label">
+                                <input type="checkbox" class="form-check-input">
+                                报表边框竖线
+                            </label>
+                        </div>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-4">
+                        <div class="form-check">
+                            <label class="form-check-label">
+                                <input type="checkbox" class="form-check-input">
+                                报表表格横线
+                            </label>
+                        </div>
+                    </div>
+                    <div class="col-4">
+                        <div class="form-check">
+                            <label class="form-check-label">
+                                <input type="checkbox" class="form-check-input">
+                                报表表格竖线
+                            </label>
+                        </div>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-4">
+                        <div class="form-check">
+                            <label class="form-check-label">
+                                <input type="checkbox" class="form-check-input">
+                                自动换行打印
+                            </label>
+                        </div>
+                    </div>
+                    <div class="col-4">
+                        <div class="form-check">
+                            <label class="form-check-label">
+                                <input type="checkbox" class="form-check-input">
+                                内容窄体输出
+                            </label>
+                        </div>
+                    </div>
+                    <div class="col-4">
+                        <div class="form-check">
+                            <label class="form-check-label">
+                                <input type="checkbox" class="form-check-input">
+                                小数补0
+                            </label>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-link float-left" data-dismiss="modal">恢复默认值</button>
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a href="" class="btn btn-primary">确定</a>
+            </div>
+        </div>
+    </div>
+</div>

+ 38 - 0
web/building_saas/report/html/rpt_export.html

@@ -0,0 +1,38 @@
+<div class="modal fade" id="export" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择导出格式</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-6">
+                        <a class="btn btn-block btn-outline-secondary" id="PDF_TYPE" onclick="rptControlObj.changeType('PDF')">
+                            <!-- <i class="fa fa-check-square pull-right"></i> -->
+                            <div class="card-body text-center">
+                                <h1 class="display-3"><i class="fa fa-file-pdf-o"></i></h1>
+                                <h1>PDF</h1>
+                            </div>
+                        </a>
+                    </div>
+                    <div class="col-6">
+                        <a class="btn btn-block btn-primary" id="EXCEL_TYPE" onclick="rptControlObj.changeType('Excel')">
+                            <!-- <i class="fa fa-check-square pull-right"></i> -->
+                            <div class="card-body text-center">
+                                <h1 class="display-3"><i class="fa fa-file-excel-o"></i></h1>
+                                <h1>Excel</h1>
+                            </div>
+                        </a>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a onclick="rptControlObj.outputRpt();" class="btn btn-primary">确定</a>
+            </div>
+        </div>
+    </div>
+</div>

+ 13 - 117
web/building_saas/report/html/rpt_main.html

@@ -28,19 +28,15 @@
                         <div class="panel-body">
                             <div class="btn-group" role="group" aria-label="Button group with nested dropdown">
                                 <div class="btn-group" role="group">
-                                    <button id="btnGroupDrop1" type="button" class="btn btn-secondary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                                        纵向
-                                    </button>
+                                    <button id="btnRptOrientation" type="button" class="btn btn-secondary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">纵向</button>
                                     <div class="dropdown-menu" aria-labelledby="btnGroupDrop1">
-                                        <a class="dropdown-item" href="#">横向</a>
+                                        <a class="dropdown-item" id="hrefRptOrientation" onclick="zTreeOprObj.changeOrientation(this)">横向</a>
                                     </div>
                                 </div>
                                 <div class="btn-group" role="group">
-                                    <button id="btnGroupDrop2" type="button" class="btn btn-secondary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                                        A4
-                                    </button>
+                                    <button id="btnRptPageSize" type="button" class="btn btn-secondary btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">A4</button>
                                     <div class="dropdown-menu" aria-labelledby="btnGroupDrop2">
-                                        <a class="dropdown-item" href="#">A3</a>
+                                        <a class="dropdown-item" id="hrefRptPageSize" onclick="zTreeOprObj.changePageSize(this)">A3</a>
                                     </div>
                                 </div>
                             </div>
@@ -51,22 +47,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_margins"><i class="fa fa-file-o"></i> 页边距</button>
+                                <button type="button" class="btn btn-secondary btn-sm" data-toggle="modal" data-target="#content_format"><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">
@@ -100,102 +86,12 @@
     </div>
 </div>
 <!--报表的弹窗-->
-<!--1弹出纸张-->
-<div class="modal fade" id="paper" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">纸张</h5>
-                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-                    <span aria-hidden="true">&times;</span>
-                </button>
-            </div>
-            <div class="modal-body">
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
-                <a href="" class="btn btn-primary">确定</a>
-            </div>
-        </div>
-    </div>
-</div>
-<!--2弹出页面-->
-<div class="modal fade" id="format" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">格式</h5>
-                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-                    <span aria-hidden="true">&times;</span>
-                </button>
-            </div>
-            <div class="modal-body">
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
-                <a href="" class="btn btn-primary">确定</a>
-            </div>
-        </div>
-    </div>
-</div>
-<!--3弹出格式-->
-<div class="modal fade" id="content" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">内容</h5>
-                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-                    <span aria-hidden="true">&times;</span>
-                </button>
-            </div>
-            <div class="modal-body">
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
-                <a href="" class="btn btn-primary">确定</a>
-            </div>
-        </div>
-    </div>
-</div>
+<!--1弹出纸张页边距-->
+<%include ./rpt_margins.html %>
+<!--2弹出页面格式-->
+<%include ./rpt_content_format.html %>
 <!--弹出导出-->
-<div class="modal fade" id="export" data-backdrop="static">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <div class="modal-header">
-                <h5 class="modal-title">选择导出格式</h5>
-                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-                    <span aria-hidden="true">&times;</span>
-                </button>
-            </div>
-            <div class="modal-body">
-                <div class="row">
-                    <div class="col-6">
-                        <a class="btn btn-block btn-outline-secondary" id="PDF_TYPE" onclick="rptControlObj.changeType('PDF')">
-                            <!-- <i class="fa fa-check-square pull-right"></i> -->
-                            <div class="card-body text-center">
-                                <h1 class="display-3"><i class="fa fa-file-pdf-o"></i></h1>
-                                <h1>PDF</h1>
-                            </div>
-                        </a>
-                    </div>
-                    <div class="col-6">
-                        <a class="btn btn-block btn-primary" id="EXCEL_TYPE" onclick="rptControlObj.changeType('Excel')">
-                            <!-- <i class="fa fa-check-square pull-right"></i> -->
-                            <div class="card-body text-center">
-                                <h1 class="display-3"><i class="fa fa-file-excel-o"></i></h1>
-                                <h1>Excel</h1>
-                            </div>
-                        </a>
-                    </div>
-                </div>
-            </div>
-            <div class="modal-footer">
-                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
-                <a onclick="rptControlObj.outputRpt();" class="btn btn-primary">确定</a>
-            </div>
-        </div>
-    </div>
-</div>
+<%include ./rpt_export.html %>
 <script>
     const SCREEN_DPI = [];
     function getScreenDPI() {

+ 66 - 0
web/building_saas/report/html/rpt_margins.html

@@ -0,0 +1,66 @@
+<div class="modal fade" id="paper_margins" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">纸张</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="row mb-1">
+                    <div class="col-3">
+                        纸张大小
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>A4</option><option>A3</option></select>
+                    </div>
+                    <div class="col-3">
+                        纸张方向
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>纵向</option><option>横向</option></select>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-3">
+                        缩放比例
+                    </div>
+                    <div class="col-3">
+                        <select class="form-control form-control-sm"><option>100%</option></select>
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-3">
+                        页边距
+                    </div>
+                    <div class="input-group input-group-sm col-md-4">
+                        <label class="input-group-addon">上</label>
+                        <input type="number" class="form-control" value="12">
+                    </div>
+                    <div class="input-group input-group-sm col-md-4">
+                        <label class="input-group-addon">下</label>
+                        <input type="number" class="form-control" value="12">
+                    </div>
+                </div>
+                <div class="row mb-1">
+                    <div class="col-3">
+                    </div>
+                    <div class="input-group input-group-sm col-md-4">
+                        <label class="input-group-addon">左</label>
+                        <input type="number" class="form-control" value="12">
+                    </div>
+                    <div class="input-group input-group-sm col-md-4">
+                        <label class="input-group-addon">右</label>
+                        <input type="number" class="form-control" value="12">
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-link float-left" data-dismiss="modal">恢复默认值</button>
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <a href="" class="btn btn-primary">确定</a>
+            </div>
+        </div>
+    </div>
+</div>

+ 87 - 39
web/building_saas/report/js/rpt_main.js

@@ -13,10 +13,11 @@ let rptTplObj = {
     iniPage: function() {
         let me = this;
         if (!me.hasInitialized) {
-            zTreeOprObj.getReportTemplateTree(userID);
+            zTreeOprObj.getCustomerCfg();
+            zTreeOprObj.getReportTemplateTree();
             me.hasInitialized = true;
             let canvas = document.getElementById("rptCanvas");
-            canvas.onclick = canvasOprObj.cavansOnClick;
+            canvas.onclick = canvasOprObj.canvasOnClick;
             canvas.onmousemove = canvasOprObj.canvasOnMouseMove;
         }
     }
@@ -26,18 +27,11 @@ let zTreeOprObj = {
     treeObj: null,
     currentNode: null,
     currentRptPageRst: null,
+    reportPageCfg: null,
     currentPage: 1,
     maxPages: 0,
-    getReportTemplateTree: function(userId) {
+    getReportTemplateTree: function() {
         let me = zTreeOprObj, params = {};
-        params.userId = [];
-        params.userId.push(userId);
-        params.userId.push(-100);
-        // let allEngIds = [];
-        // for (let item of engineeringList) {
-        //     allEngIds.push(item.value);
-        // }
-        // params.engineerId = allEngIds;
         params.engineerId = projectInfoObj.projectInfo.property.engineering;
         CommonAjax.postEx("report_tpl_api/getRptTplTree", params, 20000, true, function(result){
                 zTreeHelper.createTreeDirectly(result, rpt_tpl_setting, "rptTplTree", me);
@@ -45,6 +39,18 @@ let zTreeOprObj = {
             }, null, null
         );
     },
+    getCustomerCfg: function() {
+        let me = zTreeOprObj, params = {};
+        params.engineerId = projectInfoObj.projectInfo.property.engineering;
+        CommonAjax.postEx("report_tpl_api/getCustomizeCfg", params, 20000, true, function(result){
+                if (result) {
+                    me.reportPageCfg = result;
+                } else {
+                    me.reportPageCfg = null;
+                }
+            }, null, null
+        );
+    },
     refreshNodes: function() {
         let me = this;
         let private_setupIsParent = function(node){
@@ -69,36 +75,77 @@ let zTreeOprObj = {
     },
     onClick: function(event,treeId,treeNode) {
         let me = zTreeOprObj;
-        let canvas = document.getElementById("rptCanvas");
         if (treeNode.nodeType === TPL_TYPE_TEMPLATE && treeNode.refId > 0) {
             let params = {};
             let pageSize = rptControlObj.getCurrentPageSize();
-            params.user_id = userID;
             params.pageSize = pageSize;
             params.rpt_tpl_id = treeNode.refId;
             params.prj_id = projectInfoObj.projectInfo.ID;
             me.currentNode = treeNode;
-            CommonAjax.postEx("report_api/getReport", params, 5000, true,
-                function(result){
-                    let pageRst = result;
-                    if (pageRst) {
-                        me.currentRptPageRst = pageRst;
-                        me.maxPages = pageRst.items.length;
-                        me.currentPage = 1;
-                        me.displayPageValue();
-                        let size = JpcCanvasOutput.getReportSizeInPixel(me.currentRptPageRst, getScreenDPI());
-                        canvas.width = size[0] + 20;
-                        if (size[1] > size[0]) {
-                            canvas.height = size[1] + 100;
-                        } else {
-                            canvas.height = size[1] + 50;
-                        }
-                        me.showPage(1, canvas);
-                    }
-                }, null, null
-            );
+            me.requestReport(params);
+        }
+    },
+    changePageSize: function(dom) {
+        let me = zTreeOprObj,
+            targetDom = document.getElementById("btnRptPageSize");
+        let tmpStr = targetDom.innerHTML.trim();
+        targetDom.innerHTML = dom.innerHTML.trim();
+        dom.innerHTML = tmpStr;
+        me.changeCfg();
+
+    },
+    changeOrientation: function(dom) {
+        let me = zTreeOprObj,
+            targetDom = document.getElementById("btnRptOrientation");
+        let tmpStr = targetDom.innerHTML.trim();
+        targetDom.innerHTML = dom.innerHTML.trim();
+        dom.innerHTML = tmpStr;
+        me.changeCfg();
+    },
+    changeCfg: function() {
+        let me = zTreeOprObj;
+        let params = {};
+        params.pageSize = rptControlObj.getCurrentPageSize();
+        params.orientation = rptControlObj.getCurrentOrientation();
+        params.rpt_tpl_id = me.currentNode.refId;
+        params.prj_id = projectInfoObj.projectInfo.ID;
+        params.custCfg = me.reportPageCfg;
+        me.requestReport(params);
+    },
+    resetAfter: function (pageRst) {
+        let size = pageRst[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE].slice(0);
+        if (size[0] > size[1]) {
+            document.getElementById("btnRptOrientation").innerHTML = "横向";
+            document.getElementById("hrefRptOrientation").innerHTML = "纵向";
+        } else {
+            document.getElementById("btnRptOrientation").innerHTML = "纵向";
+            document.getElementById("hrefRptOrientation").innerHTML = "横向";
         }
     },
+    requestReport: function (params) {
+        let me = zTreeOprObj;
+        CommonAjax.postEx("report_api/getReport", params, 5000, true,
+            function(result){
+                let pageRst = result;
+                if (pageRst) {
+                    me.resetAfter(pageRst);
+                    let canvas = document.getElementById("rptCanvas");
+                    me.currentRptPageRst = pageRst;
+                    me.maxPages = pageRst.items.length;
+                    me.currentPage = 1;
+                    me.displayPageValue();
+                    let size = JpcCanvasOutput.getReportSizeInPixel(me.currentRptPageRst, getScreenDPI());
+                    canvas.width = size[0] + 20;
+                    if (size[1] > size[0]) {
+                        canvas.height = size[1] + 100;
+                    } else {
+                        canvas.height = size[1] + 50;
+                    }
+                    me.showPage(1, canvas);
+                }
+            }, null, null
+        );
+    },
     showPage: function (pageNum, canvas) {
         let me = zTreeOprObj;
         if (pageNum >= 1 && pageNum <= me.maxPages) {
@@ -133,7 +180,7 @@ let canvasOprObj = {
             }
         }
     },
-    cavansOnClick: function(event){
+    canvasOnClick: function(event){
         if (zTreeOprObj.currentNode) {
             let x = event.offsetX - JpcCanvasOutput.offsetX, canvas = event.originalTarget;
             if (!(canvas)) canvas = event.target; //chrome浏览器不认event.originalTarget,只认event.target或event.currentTarget
@@ -153,13 +200,14 @@ let canvasOprObj = {
 let rptControlObj = {
     currentOutputType: "Excel",
     getCurrentPageSize: function() {
-        let rst = "A4";
-        //
+        // let rst = "A4";
+        let rst = document.getElementById("btnRptPageSize").innerHTML.trim();
+        //btnRptPageSize
         return rst;
     },
     getCurrentOrientation: function() {
-        let rst = "横向";
-        //
+        // let rst = "横向";
+        let rst = document.getElementById("btnRptOrientation").innerHTML.trim();
         return rst;
     },
     getCurrentReportOption: function() {
@@ -199,7 +247,7 @@ let rptControlObj = {
             let orgRptName = zTreeOprObj.currentNode.name;
             orgRptName = orgRptName.replace('【', '').replace('】','').replace('-','_').replace(' ','');
             let url =  "/report_api/getExcel/" + projectInfoObj.projectInfo.ID + "/" + zTreeOprObj.currentNode.refId + "/" +
-                me.getCurrentPageSize() + "/" + orgRptName + "/" + false + "/" + 'normal';
+                me.getCurrentPageSize() + "/" + me.getCurrentOrientation() + "/" + orgRptName + "/" + false + "/" + 'normal';
             window.location = url;//这里不能使用get方法跳转,否则下载不成功
         }
     },
@@ -211,7 +259,7 @@ let rptControlObj = {
             let orgRptName = zTreeOprObj.currentNode.name;
             orgRptName = orgRptName.replace('【', '').replace('】','').replace('-','_');
             let url =  "/report_api/getPDF/" + projectInfoObj.projectInfo.ID + "/" + zTreeOprObj.currentNode.refId + "/" +
-                me.getCurrentPageSize() + "/" + orgRptName;
+                me.getCurrentPageSize() + "/" + me.getCurrentOrientation() + "/" + orgRptName;
             window.location = url;//这里不能使用get方法跳转,否则下载不成功
         }
     },

+ 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}