ソースを参照

feat:全国云导出接口根据地区动态加载脚本

vian 5 年 前
コミット
1552cb84c2

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

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

+ 37 - 2
modules/pm/facade/pm_facade.js

@@ -9,6 +9,7 @@
     };
 //先导出后require可以解决循环引用问题
 module.exports={
+    getProjectByGranularity,
     prepareShareList,
     getShareList,
     addShareList,
@@ -100,7 +101,11 @@ const SectionTreeDao = require('../../complementary_ration_lib/models/sectionTre
 let sectionTreeDao = new SectionTreeDao();
 const CounterModel = require("../../glj/models/counter_model");
 const moment = require('moment-timezone');
-const { fixedFlag, SharePermissionChangeType } = require('../../../public/common_constants');
+const {
+    fixedFlag,
+    SharePermissionChangeType,
+    GRANULARITY,
+} = require('../../../public/common_constants');
 const notDeleted = [{deleteInfo: null}, {'deleteInfo.deleted': false}];
 let cipher = require('../../../public/cipher');
 let index = require("../../system_setting/model/index");
@@ -2215,4 +2220,34 @@ async function getWelcomeInfo(compilationId,sessionUser,isFree=true) {
 
     return [isShow,context,showTime]
 
-}
+}
+
+/**
+ * 根据单位工程,获取建设项目-单位工程,不包含无单位工程的单项工程
+ * @param {Number} tenderID - 单位ID
+ * @param {Number} granularity - 导出颗粒度
+ * @param {Object} requestForSummaryInfo - 项目表级汇总字段(建设项目、单位工程汇总)
+ * @param {String} userID - 用户ID
+ * @param {String} versionName - 版本
+ */
+async function getProjectByGranularity(tenderID, granularity, requestForSummaryInfo, userID, versionName) {
+    const theTender = await projectModel.findOne({ userID: userID, ID: tenderID }).lean(); //加上session的userID,防止tenderID被篡改过
+    if (!theTender) {
+        return null;
+    }
+    const constructionProject = await projectModel.findOne({ ID: theTender.ParentID }).lean();
+    if (!constructionProject) {
+        return null;
+    }
+    constructionProject.children = granularity === GRANULARITY.PROJECT
+        ? await projectModel.find({ ParentID: constructionProject.ID, $or: notDeleted }).lean()
+        : [theTender];
+    // 获取汇总信息
+    constructionProject.summaryInfo = await getSummaryInfo([constructionProject.ID], requestForSummaryInfo);
+    // 获取编制软件信息: 软件公司;软件名;版本号;授权信息;
+    const systemSetting = await systemSettingModel.findOne({});
+    const company = systemSetting.company || '珠海纵横创新软件有限公司';
+    const version = systemSetting.version || '';
+    constructionProject.softInfo = `${company};${versionName};${version};${userID}`;
+    return constructionProject;
+}

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

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

+ 7 - 0
public/common_constants.js

@@ -95,6 +95,12 @@
         EMERGENCY_FEE: 42
     };
 
+    // 导出粒度
+    const GRANULARITY = {
+        PROJECT: 1, // 导出建设项目
+        TENDER: 3 // 导出单位工程
+    };
+
     // 补充人材机库
     const COMPLEMENTARY_LIB = 'complementaryLib';
 
@@ -146,6 +152,7 @@
 
     return {
         fixedFlag,
+        GRANULARITY,
         COMPLEMENTARY_LIB,
         COMPILATION,
         ValuationType,

+ 37 - 0
public/web/PerfectLoad.js

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

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

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

+ 46 - 2
web/building_saas/main/html/main.html

@@ -46,6 +46,7 @@
         const G_SHOW_BLOCK_LIB = false;
         const markReadProjectIDs = JSON.parse('<%- markReadProjectIDs %>');
         const VERSION = '<%- version %>';
+        const CUR_BOQ_TYPE = '<%- boqType %>';
     </script>
 </head>
 
@@ -90,6 +91,13 @@
                             <a id="uploadGld" class="dropdown-item" href="#import" data-toggle="modal" data-target="#import">导入广联达算量Excel清单</a>
                         </div>-->
                     </span>
+                    <% if(overWriteUrl === '/web/over_write/js/quanguo_2018.js' && boqType) { %>
+                    <span data-toggle="modal" data-target="#interface-export-modal">
+                        <span id="open-export-modal" class="btn btn-light btn-sm" data-toggle="tooltip" data-original-title="数据接口" data-placement="bottom">
+                            <a href="javascript:void(0);"><i class="fa fa-code-fork"></i></a>
+                        </span>
+                    </span>
+                    <% }%>
                    <!-- <a href="javascript:void(0)" class="btn btn-light btn-sm" id="insertRation" data-toggle="tooltip" data-placement="bottom" data-original-title="插入定额"><i class="fa fa-sign-in" aria-hidden="true"></i></a>-->
                     <a href="javascript:void(0)" class="btn btn-light btn-sm" id="delete" data-toggle="tooltip" data-placement="bottom" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>
                     <a href="javascript:void(0)" class="btn btn-light btn-sm" id="upLevel" data-toggle="tooltip" data-placement="bottom" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
@@ -1900,6 +1908,26 @@
             </div>
         </div>
     </div>
+    <!--进度条-->
+    <div class="modal fade" id="progress" data-backdrop="static" style="z-index: 1080;">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h5 id="progress-title" class="modal-title"></h5>
+                </div>
+                <div class="modal-body">
+                    <h5 id="progress-content" class="my-3"></h5>
+                    <div class="progress mb-3">
+                        <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0"
+                            aria-valuemax="100">
+                            <div class="progress-move progress-bar-striped"></div>
+                            <div class="progress-cover" id="progressCover"></div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
     <!--弹出 数据接口导出-->
     <div class="modal fade" id="interface-export-modal" data-backdrop="static">
         <div class="modal-dialog" role="document">
@@ -1912,6 +1940,19 @@
                     </button>
                 </div>
                 <div class="modal-body">
+                    <div class="form-group row">
+                        <label class="mb-0 pr-0 col-2 col-form-label col-form-label-sm">地区选择</label>
+                        <div class="col-6 row">
+                            <div class="col-6 pr-0">
+                                <select class="form-control  form-control-sm" id="export-parent-area">
+                                </select>
+                            </div>
+                            <div class="col-6 pr-0">
+                                <select class="form-control  form-control-sm" id="export-sub-area">
+                                </select>
+                            </div>
+                        </div>
+                    </div>
                     <!--招标-->
                     <% if (boqType == 1) { %>
                     <div class="form-group">
@@ -1920,7 +1961,7 @@
                     </div>
                     <div class="form-group">
                         <div class="form-check ml-4">
-                            <input class="form-check-input" type="checkbox" value="2" id="ex-bid" checked>
+                            <input class="form-check-input" type="checkbox" value="1" id="ex-bid" checked>
                             <label class="form-check-label" for="ex-bid">
                                 招标工程量清单
                             </label>
@@ -1941,7 +1982,7 @@
                     </div>
                     <div class="form-group">
                         <div class="form-check ml-4">
-                            <input class="form-check-input" type="checkbox" value="1" id="ex-tender" checked>
+                            <input class="form-check-input" type="checkbox" value="2" id="ex-tender" checked>
                             <label class="form-check-label" for="ex-tender">
                                 投标文件
                             </label>
@@ -2113,6 +2154,9 @@
     <script type="text/javascript" src="/web/building_saas/main/js/views/divide_view.js"></script>
     <script type="text/javascript" src="/public/web/storageUtil.js"></script>
     <script type="text/javascript" src="/web/building_saas/report/js/rpt_jspdf.js"></script>
+    <script src="/web/building_saas/standard_interface/index.js"></script>;
+    <script src="/web/building_saas/standard_interface/export/base.js"></script>;
+    <script src="/web/building_saas/standard_interface/export/view.js"></script>;
 
     <!-- endinject -->
 

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

@@ -4,6 +4,23 @@
  * @Date: 2020-08-17 15:40:08
  */
 
+// INTERFACE_EXPORT =,必须这么写,这样才能在导出时动态加载脚本后,覆盖前端代码
 INTERFACE_EXPORT = (() => {
     'use strict';
+
+    /**
+     * 
+     * @param {String} userID - 用户ID
+     * @param {Number} exportKind - 导出类型,招标、投标、控制价
+     * @param {Object} projectData - 项目表数据:{ 建设项目Data, children: [单位工程...] }
+     * @param {Object} tenderDetailMap - 单位工程ID与getData接口数据(projectObj.project的结构)的映射。
+     * @return {Promise<Object>} - 返回的数据结构必须按照规定,参考web\building_saas\standard_interface\index.js中的注释说明
+     */
+    async function entry(userID, exportKind, projectData, tenderDetailMap) {
+        
+    }
+
+    return {
+        entry,
+    };
 })();

+ 48 - 79
web/building_saas/standard_interface/export/base.js

@@ -36,11 +36,7 @@ const XML_EXPORT_BASE = (() => {
     // 加载数据间隔,减少服务器压力
     const TIMEOUT_TIME = 400;
     // 导出粒度
-    const GRANULARITY = {
-        PROJECT: 1, // 导出建设项目
-        ENGINEERING: 2, // 导出单项工程
-        TENDER: 3 // 导出单位工程
-    };
+    const { GRANULARITY } = window.commonConstants;
     // 导出的文件类型选项
     const EXPORT_KIND = {
         BID_INVITATION: 1, // 招标
@@ -109,6 +105,7 @@ const XML_EXPORT_BASE = (() => {
         this.name = name;
         this.attrs = attrs;
         handleXMLEntity(this.attrs);
+        check(this.attrs);
         this.children = [];
     }
 
@@ -354,16 +351,16 @@ const XML_EXPORT_BASE = (() => {
     /*
      * 根据粒度获取项目(不包含详细数据)数据
      * @param  {Number}granularity 导出粒度
-     *         {Object}summaryObj 汇总字段
+     *         {Object}requestForSummaryInfo 项目表级汇总字段(建设项目、单位工程汇总)
      *         {Number}tenderID 单位工程ID
      *         {String}userID 用户ID
      * @return {Object} 返回的数据结构:{children: [{children: []}]} 最外层为建设项目,中间为单项工程,最底层为单位工程
      * */
-    async function getProjectByGranularity(granularity, summaryObj, tenderID, userID) {
+    async function getProjectByGranularity(granularity, requestForSummaryInfo, tenderID, userID) {
         let projectData = _cache.projectData;
         // 没有数据,需要拉取
         if (!Object.keys(projectData).length) {
-            projectData = await ajaxPost('/pm/api/getProjectByGranularity', { user_id: userID, tenderID, granularity, summaryObj });
+            projectData = await ajaxPost('/pm/api/getProjectByGranularity', { user_id: userID, tenderID, granularity, requestForSummaryInfo });
             _cache.projectData = projectData;
         }
         return projectData;
@@ -389,38 +386,6 @@ const XML_EXPORT_BASE = (() => {
         return tenderDetail;
     }
 
-    /*
-     * 提取要导出的数据
-     * @param  {Function}entryFunc 提取数据的入口方法
-     *         {Number}granularity 导出粒度: 1-建设项目、2-单项工程、3-单位工程
-     *         {Number}exportKind 导出的文件类型:1-投标、2-招标、3-控制价
-     *         {Number}tenderID 单位工程ID
-     *         {String}userID 用户ID
-     * @return {Array} 数据结构为:[{data: Object, exportKind: Number, fileName: String}]
-     * */
-    async function extractExportData(entryFunc, granularity, summaryObj, exportKind, tenderID, userID) {
-        // 默认导出建设项目
-        if (!granularity || ![1, 2, 3].includes(granularity)) {
-            granularity = GRANULARITY.PROJECT;
-        }
-        // 默认导出投标文件
-        if (!exportKind || ![1, 2, 3].includes(exportKind)) {
-            exportKind = EXPORT_KIND.BID_SUBMISSION;
-        }
-        // 拉取标段数据:建设项目、单项工程、单位工程数据
-        const projectData = await getProjectByGranularity(granularity, summaryObj, tenderID, userID);
-        if (!projectData) {
-            throw '获取项目数据错误';
-        }
-        // 单项工程、单位工程按照树结构数据进行排序
-        projectData.children = sortByNext(projectData.children);
-        for (const engData of projectData.children) {
-            engData.children = sortByNext(engData.children);
-        }
-        // 提取相关项目的详细导出数据
-        return await entryFunc(userID, exportKind, projectData);
-    }
-
     // 获取普通基数: {xxx}
     function getNormalBase(str) {
         const reg = /{.+?}/g;
@@ -540,34 +505,6 @@ const XML_EXPORT_BASE = (() => {
         }
         return newExpr;
     }
-    // 获取工程编号表格相关数据(导出需要弹出工程编号让用户选择)
-    function getCodeSheetData(projectData) {
-        let curCode = '0';
-        let sheetData = [];
-        sheetData.push(getObj(projectData));
-        projectData.children.forEach(eng => {
-            sheetData.push(getObj(eng));
-            eng.children.forEach(tender => {
-                sheetData.push(getObj(tender));
-            });
-        });
-        // 建设项目父ID设置为-1
-        if (sheetData.length) {
-            sheetData[0].ParentID = -1;
-            sheetData[0].code = '';
-        }
-        return sheetData;
-        function getObj(data) {
-            return {
-                collapsed: false,
-                ID: data.ID,
-                ParentID: data.ParentID,
-                NextSiblingID: data.NextSiblingID,
-                name: data.name,
-                code: data.code || String(curCode++)
-            };
-        }
-    }
     // 获取节点的某属性
     function getAttr(ele, name) {
         return (ele.attrs.find(attr => attr.name === name) || {}).value;
@@ -633,7 +570,6 @@ const XML_EXPORT_BASE = (() => {
         getIDBase,
         transformCalcBase,
         transformCalcBaseState,
-        getCodeSheetData,
         getElementFromSrc,
         getAttr,
         setAttr,
@@ -703,14 +639,47 @@ const XML_EXPORT_BASE = (() => {
         }
     }
 
-    /*
+    /**
+     * 提取要导出的数据
+     * @param {Function} entryFunc - 提取数据的入口方法
+     * @param {Object} requestForSummaryInfo - 项目表级汇总字段(建设项目、单位工程汇总)
+     * @param {Number} exportKind - 导出的文件类型:1-招标、2-投标、3-控制价
+     * @param {Number} tenderID - 单位工程ID
+     * @param {String} userID - 用户ID
+     * @return {Promise<Array>} - [{data: Object, exportKind: Number, fileName: String}]
+     */
+    async function extractExportData(entryFunc, requestForSummaryInfo, exportKind, tenderID, userID) {
+        // 默认导出投标文件
+        if (!exportKind || ![1, 2, 3].includes(exportKind)) {
+            exportKind = EXPORT_KIND.BID_SUBMISSION;
+        }
+        // 拉取标段数据:建设项目、单位工程数据(projects表数据)
+        const projectData = await getProjectByGranularity(GRANULARITY.PROJECT, requestForSummaryInfo, tenderID, userID);
+        if (!projectData) {
+            throw '获取项目数据错误';
+        }
+        // 单位工程按照树结构数据进行排序,这样导出才的单位工程顺序才是对的
+        projectData.children = sortByNext(projectData.children);
+        // 先获取需要导出的单位工程的详细数据
+        const tenderDetailMap = getItem('tenderDetailMap');
+        for (const tenderItem of projectData.children) {
+            if (!tenderDetailMap[tenderItem.ID]) {
+                await setTimeoutSync(() => { }, TIMEOUT_TIME); // 需要请求项目详细数据的时候,间隔一段时间再初始单位工程数据,减少服务器压力
+            }
+            // 获取单位工程详细数据
+            tenderDetail = await getTenderDetail(tenderItem.ID, userID);
+        }
+        // 提取相关项目的详细导出数据
+        return await entryFunc(userID, exportKind, projectData, tenderDetailMap);
+    }
+
+    /**
      * 根据各自费用定额的文件结构,导出文件
      * 每个费用定额可能导出的结果文件都不同
      * 比如广东18需要将一个建设项目文件,多个单位工程文件打包成一个zip文件。重庆18就没这种要求
-     * @param  {Array}extractData 提取的数据
-     *         {Function}saveAsFunc 各自费用定额的导出方法,适应不同接口需要不同的最终文件形式
-     * @return {Array || void}
-     * */
+     * @param {Array} extractData - 提取的数据
+     * @param {Function} saveAsFunc - 各自费用定额的导出方法,适应不同接口需要不同的最终文件形式
+     */
     async function exportFile(extractData, saveAsFunc = defaultSaveAs) {
         // 获取文件数据
         const fileData = extractData.map(extractObj => {
@@ -734,11 +703,11 @@ const XML_EXPORT_BASE = (() => {
         await saveAsFunc(fileData);
     }
 
-    /*
-    * 默认的通用导出文件方法:一个文件数据对应一个xml文件(变更后缀)
-    * @param  {Array}fileData 文件数据
-    * @return {void}
-    * */
+    /**
+     * 默认的通用导出文件方法:一个文件数据对应一个xml文件(变更后缀)
+     * @param {Array} fileData - 默认的通用导出文件方法:一个文件数据对应一个xml文件(变更后缀)
+     * @return {Void}
+     */
     async function defaultSaveAs(fileData) {
         fileData.forEach(fileItem => saveAs(fileItem.blob, fileItem.fileName));
     }

+ 30 - 16
web/building_saas/standard_interface/export/view.js

@@ -26,28 +26,37 @@ const EXPORT_VIEW = (() => {
     //事件监听
     function exportListener() {
         // 导出接口
-        $('#export-confirm').click(async function () {
-            let checkedDatas = $('#export input[type="checkbox"]:checked');
-            if (!checkedDatas.length) {
-                return;
-            }
-            if (STATE.exporting) {
-                return;
-            }
-            STATE.exporting = true;
-            let pr = new SCComponent.InitProgressBar();
+        $('#interface-export-confirm').click(async function () {
+            const pr = new SCComponent.InitProgressBar();
             try {
+                const checkedDatas = $('#interface-export-modal input[type="checkbox"]:checked');
+                if (!checkedDatas.length) {
+                    throw '请勾选导出文件。';
+                }
+                const parentArea = $('#export-parent-area').val();
+                const subArea = $('#export-sub-area').val();
+                if (!parentArea || !subArea) {
+                    throw '请选择有效地区。';
+                }
+                // 按照地区动态加载导出脚本
+                const areaKey = `${parentArea}@${subArea}`;
+                await STD_INTERFACE.loadScriptByArea(areaKey, STD_INTERFACE.ScriptType.EXPORT);
+                if (STATE.exporting) {
+                    return;
+                }
+                STATE.exporting = true;
                 if (!_exportCache || !_exportCache.length) {
                     pr.start('导出数据接口', '正在导出文件,请稍候……');
-                    for (let checkedData of checkedDatas) {
-                        let fileKind = parseInt($(checkedData).val());
-                        let exportData = await _base.extractExportData(XMLStandard.entry, _base.CONFIG.GRANULARITY.PROJECT,
-                            XMLStandard.summaryObj, fileKind, projectObj.project.ID(), userID);
+                    for (const checkedData of checkedDatas) {
+                        const boqType = parseInt($(checkedData).val());
+                        const requestForSummaryInfo = INTERFACE_EXPORT.requestForSummaryInfo || {};
+                        const projectID = projectObj.project.ID();
+                        const exportData = await _base.extractExportData(INTERFACE_EXPORT.entry, requestForSummaryInfo, boqType, projectID, userID);
                         _exportCache.push(...exportData);
                     }
                     if (_exportCache && _exportCache.length) {
                         // 导出文件
-                        await _base.exportFile(_exportCache, XMLStandard.saveAsFile);
+                        await _base.exportFile(_exportCache, INTERFACE_EXPORT.saveAsFile || null);
                     }
                 }
             } catch (err) {
@@ -72,4 +81,9 @@ const EXPORT_VIEW = (() => {
         });
     }
     return { exportListener }
-})();
+})();
+
+$(document).ready(() => {
+    $('#interface-export-modal').on('show.bs.modal', () => STD_INTERFACE.initExportAreas($('#export-parent-area'), $('#export-sub-area')));
+    EXPORT_VIEW.exportListener();
+})

+ 61 - 10
web/building_saas/standard_interface/index.js

@@ -4,21 +4,64 @@
  * @Date: 2020-08-17 15:07:31
  */
 
-// 用于导出的挂载变量,各地区对外接口需要作覆盖它。
-// eg: 导出接口A时,加载了scriptA,此时scriptA的对外接口INTERFACE_EXPORT = { entry }
-//     导出接口B时,加载了scriptB,此时scriptB的对外接口INTERFACE_EXPORT = { entry }
+/**
+ * 用于导出的挂载变量,各地区对外接口需要作覆盖它。
+ * 注意:导出脚本必须有一个“entry”方法挂载在“INTERFACE_EXPORT”对象上。 eg: INTERFACE_EXPORT = { entry: () => void }
+ * entry方法返回的结果必须为xml的object形式:
+ * xml: 
+ * <JingJiBiao Xmbh="001">
+ *   <ZhaoBiaoXx Zbr="vian">
+ *   </ZhaoBiaoXx>
+ * </JingJiBiao>
+ * entry方法返回的数据结构:
+ * {
+ *   name: 'JingJiBiao',
+ *   attrs: [{ name: 'xmbh', value: '001' }],
+ *   children: [
+ *      { 
+ *        name: 'ZhaoBiaoXx',
+ *        attrs: [{ name: 'Zbr', value: 'vian' }],
+ *        children: []
+ *      }  
+ *   ]
+ * }
+ */
 let INTERFACE_EXPORT = {};
-// 用于导入的挂载变量,同上
+
+// 用于导入的挂载变量
 let INTERFACE_IMPORT = {};
 
 const STD_INTERFACE = (() => {
     'use strict';
 
-    // 地区配置,key为地区,value为该地区接口的js文件名。注意:相同地区的导入导出接口js文件名称应相同。
+    // 地区配置,key为地区,value为该地区接口的js文件名。
+    // 注意:相同地区的导入导出接口js文件名称应相同。eg: 安徽马鞍山导出:在export目录下:anhui_maanshan.js; 在import目录下:anhui_maanshan.js;
     const config = {
-        '安徽省@马鞍山': 'anhui_maanshan.js',
+        '安徽@马鞍山': 'anhui_maanshan.js',
     };
 
+    // 根据地区配置,初始化地区选项
+    function initExportAreas($parentAreas, $subAreas) {
+        const connectedAreas = Object.keys(config);
+        const parentMap = {};
+        connectedAreas.forEach(connectedArea => {
+            const areas = connectedArea.split('@');
+            (parentMap[areas[0]] || (parentMap[areas[0]] = [])).push(areas[1]);
+        });
+        const parentAreasHtml = Object
+            .keys(parentMap)
+            .reduce((acc, area) => acc += `<option value="${area}">${area}</option>`, '');
+        $parentAreas.html(parentAreasHtml);
+        const subAreasHtml = parentMap[Object.keys(parentMap)[0]].reduce((acc, area) => acc += `<option value="${area}">${area}</option>`, '');
+        $subAreas.html(subAreasHtml);
+        // 父级地区变更,子地区选项更新
+        $parentAreas.change(function () {
+            const curArea = $(this).val();
+            const subAreasHtml = parentMap[curArea].reduce((acc, area) => acc += `<option value="${area}">${area}</option>`, '');
+            $subAreas.html(subAreasHtml);
+        });
+    }
+
     /**
      * 动态加载script
      * 由于后续的接口可能会非常多,一次性加载所有的接口文件完全没必要,而且不可控,很容易导致初次加载速度变慢。
@@ -29,20 +72,27 @@ const STD_INTERFACE = (() => {
      */
     function loadScript(path) {
         return new Promise((resolve, reject) => {
+            const scriptID = 'interface-script';
             const body = document.getElementsByTagName('body')[0];
+            // 移除原来的
+            const orgScript = document.getElementById(scriptID);
+            if (orgScript) {
+                body.removeChild(orgScript);
+            }
+            // 增加新的
             const script = document.createElement('script');
             script.src = path;
             script.type = 'text/javascript';
+            script.id = scriptID;
             body.appendChild(script);
             script.onload = script.onreadystatechange = function () { // ie、ff触发事件不同,都写上
                 if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
                     script.onload = script.onreadystatechange = null;
-                    cache[path] = 1;
                     resolve();
                 }
             };
             script.onerror = function () {
-                reject('script加载失败,请稍后重试。');
+                reject('script加载失败。');
             };
         });
     }
@@ -73,8 +123,9 @@ const STD_INTERFACE = (() => {
     }
 
     return {
+        initExportAreas,
         ScriptType,
-        loadScriptByArea
-    }
+        loadScriptByArea,
+    };
 
 })();