瀏覽代碼

feat: 定制化分析报告页面修改

cuiyalong 2 年之前
父節點
當前提交
80f3a52bca
共有 25 個文件被更改,包括 1727 次插入315 次删除
  1. 9 0
      src/App.vue
  2. 10 0
      src/api/modules/chart.js
  3. 64 0
      src/assets/style/reset-ele.scss
  4. 3 36
      src/assets/style/selector-cascader-common.scss
  5. 12 13
      src/components/chart/MapChart.vue
  6. 4 0
      src/components/chart/RectTreeMapChart.vue
  7. 1 1
      src/components/selector-cascader/AreaCityCascader.vue
  8. 1 1
      src/components/selector-cascader/BuyerClassCascader.vue
  9. 7 19
      src/components/selector-cascader/IndustryCascader.vue
  10. 7 6
      src/components/selector-cascader/SubscribeClassListCascader.vue
  11. 490 213
      src/views/analysisReport/MarketAnalysisResult.vue
  12. 113 0
      src/views/analysisReport/components/MarketAreaCityScatter.vue
  13. 15 2
      src/views/analysisReport/components/MarketAreaScatter.vue
  14. 204 0
      src/views/analysisReport/components/MarketCityScatter.vue
  15. 155 0
      src/views/analysisReport/components/MarketHistogram.vue
  16. 5 2
      src/views/analysisReport/components/MarketSegment.vue
  17. 114 4
      src/views/analysisReport/components/MarketTop3Table.vue
  18. 4 1
      src/views/analysisReport/components/MarketUserScatter.vue
  19. 436 0
      src/views/analysisReport/components/ProjectDetailsList.vue
  20. 5 0
      src/views/analysisReport/index.vue
  21. 8 0
      src/views/reportData/components/AssociationInput.vue
  22. 25 11
      src/views/reportData/components/ProjectDetailsList.vue
  23. 14 5
      src/views/reportData/components/PurchaseRankingDialog.vue
  24. 18 1
      src/views/reportData/pageMonth.vue
  25. 3 0
      src/views/reportData/pageWeek.vue

+ 9 - 0
src/App.vue

@@ -42,6 +42,15 @@ export default {
   .v-w1200 {
     width: 100%;
   }
+  // 兼容工作桌面下a链接
+  a:focus,
+  a:hover {
+    text-decoration: underline;
+  }
+  a:active,
+  a:hover {
+    outline: 0;
+  }
 }
 
 .big-member-page .el-loading-mask {

+ 10 - 0
src/api/modules/chart.js

@@ -240,3 +240,13 @@ export function getWeekMonthReportProjectList (data) {
     data
   })
 }
+
+// 定制化分析报告项目明细
+export function getMarketAnalysisReportProjectList (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/marketAnalysis/projectInfo',
+    method: 'post',
+    data
+  })
+}

+ 64 - 0
src/assets/style/reset-ele.scss

@@ -193,3 +193,67 @@
     background-color: #2ABED1;
   }
 }
+
+// 从selector-cascader-common.scss中提取成全局
+.selector-cascader {
+  $min-width: 204px;
+
+  position: relative;
+  > .el-popper[x-placement^=bottom] {
+    margin-top: 0;
+  }
+  > .el-popover,
+  > .el-select-dropdown {
+    padding: 0;
+    left: 0!important;
+    border-color: $color_main;
+
+    .el-cascader-menu__wrap {
+      height: 204px;
+    }
+    .el-cascader-menu {
+      min-width: 160px;
+      color: #1d1d1d;
+    }
+    .el-cascader-node,
+    .el-select-dropdown__item {
+      height: 30px;
+      line-height: 30px;
+    }
+  }
+
+  .popper__arrow {
+    display: none!important;
+  }
+  .el-cascader-panel.is-bordered {
+    border: none;
+  }
+
+  // 此处公共样式不定义。可在组件内自行修改
+  // .el-cascader-menu__list {
+  //   min-width: $min-width - 2px;
+  // }
+
+  .virtual-cascader,
+  .el-cascader {
+    min-width: $min-width;
+    height: 30px;
+    line-height: 30px;
+  }
+  .virtual-input,
+  .el-input {
+    height: 100%;
+    .el-input__inner {
+      height: 30px;
+      line-height: 30px;
+      font-size: 14px;
+      color: #1d1d1d;
+      border-color: #e0e0e0;
+    }
+    .el-input__icon {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+  }
+}

+ 3 - 36
src/assets/style/selector-cascader-common.scss

@@ -1,42 +1,9 @@
 .selector-cascader {
-  position: relative;
+  $min-width: 204px;
   ::v-deep {
-    > .el-popover {
-      margin-top: 0;
-      padding: 0;
-      left: 0!important;
-      border-color: $color_main;
-      .el-cascader-menu__wrap {
-        height: 204px;
-      }
-      .el-cascader-menu {
-        min-width: 160px;
-        color: #1d1d1d;
-      }
-      .el-cascader-node {
-        height: 30px;
-      }
+    .el-cascader-menu__list {
+      min-width: $min-width - 2px;
     }
   }
 }
 
-.virtual-cascader {
-  min-width: 204px;
-  height: 30px;
-  line-height: 30px;
-}
-.virtual-input {
-  height: 100%;
-  .el-input__inner {
-    height: 30px;
-    line-height: 30px;
-    font-size: 14px;
-    color: #1d1d1d;
-    border-color: #e0e0e0;
-  }
-  .el-input__icon {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-  }
-}

+ 12 - 13
src/components/chart/MapChart.vue

@@ -7,11 +7,12 @@
       :settings="defaultSettings"
       :events="{ click: clickHandler.bind(this) }"
       @ready="ready"
-      :extend="defaultOptions">
+      :extend="defaultOptionsComputed">
     </ve-map>
 </template>
 <script>
 import { VeMap } from 'v-charts'
+import { merge } from 'lodash'
 const mapJson = require('../../assets/js/china-n.json')
 
 export default {
@@ -19,6 +20,12 @@ export default {
   props: {
     id: String,
     datas: Object,
+    customDefaultOptions: {
+      type: Object,
+      default () {
+        return {}
+      }
+    },
     options: {
       height: String,
       colors: Array || Object,
@@ -106,7 +113,7 @@ export default {
           roam: false, // 关闭缩放功能
           zoom: 1,
           scaleLimit: {
-            min: 1,
+            min: 0.5,
             max: 2
           }
         },
@@ -189,17 +196,9 @@ export default {
     }
   },
   computed: {
-    // getMapDatas () {
-    //   return {
-    //     columns: this.datas.columns,
-    //     rows: this.datas.rows.map(v => {
-    //       return Object.assign({}, v, {
-    //         name: completionMapName(v.name),
-    //         _name: v.name
-    //       })
-    //     })
-    //   }
-    // }
+    defaultOptionsComputed () {
+      return merge(this.defaultOptions, this.customDefaultOptions)
+    }
   },
   watch: {},
   mounted () {},

+ 4 - 0
src/components/chart/RectTreeMapChart.vue

@@ -102,10 +102,14 @@ export default {
       if (!this.myChart) return
       this.mergeOptions()
       this.myChart.setOption(this.defaultOptions)
+      this.myChart.on('click', this.clickChart)
       window.addEventListener('resize', () => {
         // this.myChart.resize()
       })
     },
+    clickChart (e) {
+      this.$emit('clickChart', e)
+    },
     mergeOptions () {
       // 设置数据
       this.defaultOptions.series[0].data = this.datas

+ 1 - 1
src/components/selector-cascader/AreaCityCascader.vue

@@ -25,7 +25,7 @@
     ></el-cascader-panel>
     <div class="el-cascader virtual-cascader" slot="reference">
       <div class="el-input virtual-input el-input--suffix" :class="{ 'is-focus': popover.show }">
-        <div class="el-input__inner" v-html="inputValue"></div>
+        <div class="el-input__inner ellipsis" v-html="inputValue"></div>
         <span class="el-input__suffix">
           <span class="el-input__suffix-inner">
             <i class="el-input__icon el-icon-arrow-down" :class="{ 'is-reverse': popover.show }"></i>

+ 1 - 1
src/components/selector-cascader/BuyerClassCascader.vue

@@ -25,7 +25,7 @@
     ></el-cascader-panel>
     <div class="el-cascader virtual-cascader" slot="reference">
       <div class="el-input virtual-input el-input--suffix" :class="{ 'is-focus': popover.show }">
-        <div class="el-input__inner" v-html="inputValue"></div>
+        <div class="el-input__inner ellipsis" v-html="inputValue"></div>
         <span class="el-input__suffix">
           <span class="el-input__suffix-inner">
             <i class="el-input__icon el-icon-arrow-down" :class="{ 'is-reverse': popover.show }"></i>

+ 7 - 19
src/components/selector-cascader/IndustryCascader.vue

@@ -25,7 +25,7 @@
     ></el-cascader-panel>
     <div class="el-cascader virtual-cascader" slot="reference">
       <div class="el-input virtual-input el-input--suffix" :class="{ 'is-focus': popover.show }">
-        <div class="el-input__inner" v-html="inputValue"></div>
+        <div class="el-input__inner ellipsis" v-html="inputValue"></div>
         <span class="el-input__suffix">
           <span class="el-input__suffix-inner">
             <i class="el-input__icon el-icon-arrow-down" :class="{ 'is-reverse': popover.show }"></i>
@@ -53,14 +53,11 @@ export default {
       type: String,
       default: '行业'
     },
+    // 数据结构和industryListMapExp相同
     showOptions: {
-      type: Array,
+      type: Object,
       default () {
-        return [
-          // '耗材',
-          // '工程机械',
-          // '审计'
-        ]
+        return {}
       }
     }
   },
@@ -84,19 +81,10 @@ export default {
     },
     optionsMap () {
       const optMap = this.showOptions
-      if (optMap.length === 0) {
-        return industryListMapExp
+      if (optMap && Object.keys(optMap).length) {
+        return optMap
       } else {
-        const map = {}
-        for (const key in industryListMapExp) {
-          const item = industryListMapExp[key]
-          let exist = []
-          if (Array.isArray(item)) {
-            exist = item.filter(t => optMap.includes(t))
-          }
-          map[key] = exist
-        }
-        return map
+        return this.industryListMapExp
       }
     },
     optionsList () {

+ 7 - 6
src/components/selector-cascader/SubscribeClassListCascader.vue

@@ -25,7 +25,7 @@
     ></el-cascader-panel>
     <div class="el-cascader virtual-cascader" slot="reference">
       <div class="el-input virtual-input el-input--suffix" :class="{ 'is-focus': popover.show }">
-        <div class="el-input__inner" v-html="inputValue"></div>
+        <div class="el-input__inner ellipsis" v-html="inputValue"></div>
         <span class="el-input__suffix">
           <span class="el-input__suffix-inner">
             <i class="el-input__icon el-icon-arrow-down" :class="{ 'is-reverse': popover.show }"></i>
@@ -102,7 +102,7 @@ export default {
     inputValue () {
       const length = this.value.length
       if (this.value.length) {
-        return `${this.placeholder} <span class="highlight-text">${length}</span>个`
+        return `${this.placeholder} <span class="highlight-text">${length}</span> 个`
       } else {
         return `${this.placeholder}`
       }
@@ -160,7 +160,8 @@ export default {
         value.forEach(item => {
           const key = item[0]
           const value = item[1]
-          if (key) {
+          // 如果没有,则创建空数组
+          if (!map[key]) {
             map[key] = []
           }
           if (value) {
@@ -198,7 +199,7 @@ export default {
         }
         if (Array.isArray(children)) {
           children.forEach(child => {
-            const childKey = child.key.join(' ')
+            const childKey = Array.isArray(child.key) ? child.key.join(' ') : ''
             if (Array.isArray(map[cname])) {
               const r = map[cname].find(item => item === childKey)
               if (r) {
@@ -221,8 +222,8 @@ export default {
 @import '~@/assets/style/selector-cascader-common.scss';
 .subscribe-class-list-cascader {
   ::v-deep {
-    > .el-popover {
-      max-width: 100%;
+    .el-cascader-menu__list {
+      max-width: 200px;
     }
   }
 }

+ 490 - 213
src/views/analysisReport/MarketAnalysisResult.vue

@@ -18,7 +18,7 @@
               <span class="dropdown-text">{{ value.name }}</span>
               <i class="el-icon-caret-bottom" :class="{ highlight: value.dropDownShow }"></i>
             </span>
-            <el-dropdown-menu slot="dropdown" class="report-dropdown-menu">
+            <el-dropdown-menu slot="dropdown" :append-to-body="false" class="report-dropdown-menu">
               <el-dropdown-item
                 v-for="(item, index) in dimensionsOptions[key]"
                 :key="index"
@@ -39,203 +39,290 @@
       </div>
     </div>
     <div class="bg-grey-h24"></div>
-    <div class="analysis-result-list">
-      <section class="analysis-result-section section-market">
-        <div class="analysis-result-title pd-lr20">市场规模</div>
-        <div class="analysis-result-content sub-section-list">
-          <!-- 市场概况 -->
-          <div class="sub-section market-overview" v-if="(sections.market.overview.length && isConf10)">
-            <div class="sub-section-header">
-              <div class="sub-section-title">市场概况</div>
-            </div>
-            <div class="sub-section-content">
-              <MarketOverview :overviewList="sections.market.overview" />
-            </div>
-          </div>
-          <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[0]])" :k="Object.keys(vipUpgradeMap)[0]" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[0]]"></MaskCard>
-          <!-- 项目规模分布 -->
-          <div class="sub-section project-scatter"
-            v-if="(isConf10 && !sections.loaded.top3) || (sections.projectScatter.dataAlready && sections.loaded.top3 && isConf10)"
-            v-loading="!sections.loaded.top3">
-            <div class="sub-section-header">
-              <div class="sub-section-title">项目规模分布</div>
-            </div>
-            <div class="sub-section-content">
-              <ProjectScatter
-                v-if="sections.projectScatter.dataAlready"
-                :chartData="sections.projectScatter.chartData"
-                :tableData="sections.projectScatter.tableData"
-              />
-            </div>
-          </div>
-          <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[1]])" :k="Object.keys(vipUpgradeMap)[1]" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[1]]"></MaskCard>
-          <!-- 时间分布 -->
-          <div class="sub-section time-scatter" v-if="sections.timeScatter.dataAlready">
-            <div class="sub-section-header">
-              <div class="sub-section-title">时间分布</div>
-              <div class="sub-section-action">
-                <div
-                  class="s-a-item"
-                  :class="{
-                    active: item.value === sections.timeScatter.activeAction
-                  }"
-                  v-for="(item, index) in sections.timeScatter.actionList"
-                  :key="index"
-                  @click="clickTimeScatterTab(item)"
-                >{{ item.label }}</div>
+    <el-tabs class="tabs sticky-tab-container" v-model="tabActiveName" :before-leave="beforeTabLeave">
+      <el-tab-pane label="市场分析" name="1">
+        <div class="analysis-result-list">
+          <section class="analysis-result-section section-market">
+            <div class="analysis-result-title pd-lr20">市场规模</div>
+            <div class="analysis-result-content sub-section-list">
+              <!-- 市场概况 -->
+              <div class="sub-section market-overview" v-if="(sections.market.overview.length && isConf10)">
+                <div class="sub-section-header">
+                  <div class="sub-section-title">市场概况</div>
+                </div>
+                <div class="sub-section-content">
+                  <MarketOverview :overviewList="sections.market.overview" />
+                </div>
               </div>
+              <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[0]])" :k="Object.keys(vipUpgradeMap)[0]" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[0]]"></MaskCard>
+              <!-- 项目规模分布 -->
+              <div class="sub-section project-scatter"
+                v-if="(isConf10 && !sections.loaded.top3) || (sections.projectScatter.dataAlready && sections.loaded.top3 && isConf10)"
+                v-loading="!sections.loaded.top3">
+                <div class="sub-section-header">
+                  <div class="sub-section-title">项目规模分布</div>
+                </div>
+                <div class="sub-section-content">
+                  <ProjectScatter
+                    v-if="sections.projectScatter.dataAlready"
+                    :chartData="sections.projectScatter.chartData"
+                    :tableData="sections.projectScatter.tableData"
+                  />
+                </div>
+              </div>
+              <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[1]])" :k="Object.keys(vipUpgradeMap)[1]" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[1]]"></MaskCard>
+              <!-- 时间分布 -->
+              <div class="sub-section time-scatter" v-if="sections.timeScatter.dataAlready">
+                <div class="sub-section-header">
+                  <div class="sub-section-title">时间分布</div>
+                  <div class="sub-section-action">
+                    <div
+                      class="s-a-item"
+                      :class="{
+                        active: item.value === sections.timeScatter.activeAction
+                      }"
+                      v-for="(item, index) in sections.timeScatter.actionList"
+                      :key="index"
+                      @click="clickTimeScatterTab(item)"
+                    >{{ item.label }}</div>
+                  </div>
+                </div>
+                <div class="sub-section-content time-chart-group" key="timeScatterChartsKey">
+                  <MarketTimeScatter width="460px" :chartData="sections.timeScatter[sections.timeScatter.activeAction].count" />
+                  <MarketTimeScatter width="460px" :chartData="sections.timeScatter[sections.timeScatter.activeAction].amount" />
+                </div>
+              </div>
+              <!-- 地区分布 -->
+              <div class="sub-section area-scatter"
+                v-if="(!sections.loaded.top3 && notOneCityFilter && isConf10) || (sections.areaScatter.dataAlready && sections.loaded.top3 && notOneCityFilter && isConf10)"
+                v-loading="!sections.loaded.top3">
+                <div class="sub-section-header">
+                  <div class="sub-section-title">地区分布</div>
+                </div>
+                <div class="sub-section-content">
+                  <MarketAreaCityScatter
+                    v-if="sections.areaScatter.dataAlready"
+                    :chartData="sections.areaScatter.chartData"
+                    :filterAreaMap="reportFilters.area"
+                    :areaDataList="sections.areaScatter.areaDataList"
+                  />
+                </div>
+              </div>
+              <div class="top3-table-list pd-lr20">
+                <div class="ar-table"
+                  v-if="(!sections.loaded.top3 && isConf10) || (sections.areaScatter.projectCountTop3 && sections.loaded.top3 && isConf10)"
+                  v-loading="!sections.loaded.top3">
+                  <p class="ar-table-title">项目数量TOP3地区的重点中标单位</p>
+                  <Top3Table
+                    v-if="sections.areaScatter.projectCountTop3"
+                    :tableData="sections.areaScatter.projectCountTop3"
+                    labelClickable
+                    @clickLabel="clickTop3TableLabel($event, 'areaScatter')"
+                    valueClickable
+                    @clickValue="clickTop3TableValue($event, 'areaScatter')"
+                  />
+                </div>
+                <div class="ar-table"
+                  v-if="!sections.loaded.top3 || (sections.areaScatter.projectAmountTop3 && sections.loaded.top3)"
+                  v-loading="!sections.loaded.top3">
+                  <p class="ar-table-title">项目金额TOP3地区的重点中标单位</p>
+                  <Top3Table
+                    v-if="sections.areaScatter.projectAmountTop3"
+                    :tableData="sections.areaScatter.projectAmountTop3"
+                    labelClickable
+                    @clickLabel="clickTop3TableLabel($event, 'areaScatter')"
+                    valueClickable
+                    @clickValue="clickTop3TableValue($event, 'areaScatter')"
+                  />
+                </div>
+              </div>
+              <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[2]])" :k="Object.keys(vipUpgradeMap)[2]" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[2]]"></MaskCard>
+              <!-- 客户分布 -->
+              <div class="sub-section user-scatter"
+                v-if="!sections.loaded.top3 || (sections.userScatter.list.length && sections.loaded.top3)"
+                v-loading="!sections.loaded.top3">
+                <div class="sub-section-header">
+                  <div class="sub-section-title">客户分布</div>
+                  <div class="view-more-btn" v-if="sections.userScatter.list.length > 10" @click="viewMoreUserScatter">查看详情</div>
+                </div>
+                <div class="sub-section-content">
+                  <MarketUserScatter v-if="sections.userScatter.list.length" :chartData="sections.userScatter.list.slice(0,10)" @clickChart="clickMarketUserScatter" />
+                </div>
+              </div>
+              <div class="top3-table-list pd-lr20">
+                <div class="ar-table"
+                  v-if="!sections.loaded.top3 || (sections.userScatter.projectCountTop3 && sections.loaded.top3)"
+                  v-loading="!sections.loaded.top3">
+                  <p class="ar-table-title">项目数量TOP3客户类型的重点中标单位</p>
+                  <Top3Table
+                    v-if="sections.userScatter.projectCountTop3"
+                    :tableData="sections.userScatter.projectCountTop3"
+                    labelClickable
+                    @clickLabel="clickTop3TableLabel($event, 'userScatter')"
+                    valueClickable
+                    @clickValue="clickTop3TableValue($event, 'userScatter')"
+                  />
+                </div>
+                <div class="ar-table"
+                  v-if="!sections.loaded.top3 || (sections.userScatter.projectAmountTop3 && sections.loaded.top3)"
+                  v-loading="!sections.loaded.top3">
+                  <p class="ar-table-title">项目金额TOP3客户类型的重点中标单位</p>
+                  <Top3Table
+                    v-if="sections.userScatter.projectAmountTop3"
+                    :tableData="sections.userScatter.projectAmountTop3"
+                    labelClickable
+                    @clickLabel="clickTop3TableLabel($event, 'userScatter')"
+                    valueClickable
+                    @clickValue="clickTop3TableValue($event, 'userScatter')"
+                  />
+                </div>
+              </div>
+              <!-- 细分市场 -->
+              <div class="sub-section market-refine"
+                v-if="(isConf10 && !sections.loaded.segment) || (sections.market.refine.dataAlready && sections.loaded.segment && isConf10)"
+                v-loading="!sections.loaded.segment">
+                <div class="sub-section-header">
+                  <div class="sub-section-title">细分市场</div>
+                </div>
+                <div class="sub-section-content">
+                  <MarketSegment
+                    v-if="sections.market.refine.dataAlready"
+                    @clickChart="clickMarketSegment"
+                    :projectCountData="sections.market.refine.projectCountData"
+                    :projectAmountData="sections.market.refine.projectAmountData"
+                  />
+                </div>
+              </div>
+              <div class="top3-table-list pd-lr20" v-if="isConf10">
+                <div class="ar-table"
+                  v-if="(!sections.loaded.segment) || (sections.market.refine.projectCountTop3 && sections.loaded.segment)"
+                  v-loading="!sections.loaded.segment">
+                  <p class="ar-table-title">细分市场的重点中标单位-项目数量</p>
+                  <Top3Table
+                    v-if="sections.market.refine.projectCountTop3"
+                    :tableData="sections.market.refine.projectCountTop3"
+                    labelClickable
+                    @clickLabel="clickTop3TableLabel($event, 'segment')"
+                    valueClickable
+                    @clickValue="clickTop3TableValue($event, 'segment')"
+                  />
+                </div>
+                <div class="ar-table"
+                  v-if="!sections.loaded.segment || (sections.market.refine.projectAmountTop3 && sections.loaded.segment)"
+                  v-loading="!sections.loaded.segment">
+                  <p class="ar-table-title">细分市场的重点中标单位-项目金额</p>
+                  <Top3Table
+                    v-if="sections.market.refine.projectAmountTop3"
+                    :tableData="sections.market.refine.projectAmountTop3"
+                    labelClickable
+                    @clickLabel="clickTop3TableLabel($event, 'segment')"
+                    valueClickable
+                    @clickValue="clickTop3TableValue($event, 'segment')"
+                  />
+                </div>
+              </div>
+              <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[3]])" :k="Object.keys(vipUpgradeMap)[3]" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[3]]"></MaskCard>
             </div>
-            <div class="sub-section-content time-chart-group" key="timeScatterChartsKey">
-              <MarketTimeScatter width="460px" :chartData="sections.timeScatter[sections.timeScatter.activeAction].count" />
-              <MarketTimeScatter width="460px" :chartData="sections.timeScatter[sections.timeScatter.activeAction].amount" />
-            </div>
-          </div>
-          <!-- 地区分布 -->
-          <div class="sub-section area-scatter"
-            v-if="(!sections.loaded.top3 && notOneAreaFilter && isConf10) || (sections.areaScatter.dataAlready && sections.loaded.top3 && notOneAreaFilter && isConf10)"
-            v-loading="!sections.loaded.top3">
-            <div class="sub-section-header">
-              <div class="sub-section-title">地区分布</div>
-            </div>
-            <div class="sub-section-content">
-              <MarketAreaScatter
-                v-if="sections.areaScatter.dataAlready"
-                :chartData="sections.areaScatter.chartData"
-              />
-            </div>
-          </div>
-          <div class="top3-table-list pd-lr20">
-            <div class="ar-table"
-              v-if="(!sections.loaded.top3 && isConf10) || (sections.areaScatter.projectCountTop3 && sections.loaded.top3 && isConf10)"
-              v-loading="!sections.loaded.top3">
-              <p class="ar-table-title">项目数量TOP3地区的重点中标单位</p>
-              <Top3Table v-if="sections.areaScatter.projectCountTop3" :tableData="sections.areaScatter.projectCountTop3" />
-            </div>
-            <div class="ar-table"
-              v-if="!sections.loaded.top3 || (sections.areaScatter.projectAmountTop3 && sections.loaded.top3)"
-              v-loading="!sections.loaded.top3">
-              <p class="ar-table-title">项目金额TOP3地区的重点中标单位</p>
-              <Top3Table v-if="sections.areaScatter.projectAmountTop3" :tableData="sections.areaScatter.projectAmountTop3" />
-            </div>
-          </div>
-          <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[2]])" :k="Object.keys(vipUpgradeMap)[2]" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[2]]"></MaskCard>
-          <!-- 客户分布 -->
-          <div class="sub-section user-scatter"
-            v-if="!sections.loaded.top3 || (sections.userScatter.list.length && sections.loaded.top3)"
-            v-loading="!sections.loaded.top3">
-            <div class="sub-section-header">
-              <div class="sub-section-title">客户分布</div>
-              <div class="view-more-btn" v-if="sections.userScatter.list.length > 10" @click="viewMoreUserScatter">查看详情</div>
-            </div>
-            <div class="sub-section-content">
-              <MarketUserScatter v-if="sections.userScatter.list.length" :chartData="sections.userScatter.list.slice(0,10)" />
-            </div>
-          </div>
-          <div class="top3-table-list pd-lr20">
-            <div class="ar-table"
-              v-if="!sections.loaded.top3 || (sections.userScatter.projectCountTop3 && sections.loaded.top3)"
-              v-loading="!sections.loaded.top3">
-              <p class="ar-table-title">项目数量TOP3客户类型的重点中标单位</p>
-              <Top3Table v-if="sections.userScatter.projectCountTop3" :tableData="sections.userScatter.projectCountTop3" />
-            </div>
-            <div class="ar-table"
-              v-if="!sections.loaded.top3 || (sections.userScatter.projectAmountTop3 && sections.loaded.top3)"
-              v-loading="!sections.loaded.top3">
-              <p class="ar-table-title">项目金额TOP3客户类型的重点中标单位</p>
-              <Top3Table v-if="sections.userScatter.projectAmountTop3" :tableData="sections.userScatter.projectAmountTop3" />
-            </div>
-          </div>
-          <!-- 细分市场 -->
-          <div class="sub-section market-refine"
-            v-if="(isConf10 && !sections.loaded.segment) || (sections.market.refine.dataAlready && sections.loaded.segment && isConf10)"
-            v-loading="!sections.loaded.segment">
-            <div class="sub-section-header">
-              <div class="sub-section-title">细分市场</div>
-            </div>
-            <div class="sub-section-content">
-              <MarketSegment
-                v-if="sections.market.refine.dataAlready"
-                :projectCountData="sections.market.refine.projectCountData"
-                :projectAmountData="sections.market.refine.projectAmountData"
-              />
-            </div>
-          </div>
-          <div class="top3-table-list pd-lr20" v-if="isConf10">
-            <div class="ar-table"
-              v-if="(!sections.loaded.segment) || (sections.market.refine.projectCountTop3 && sections.loaded.segment)"
-              v-loading="!sections.loaded.segment">
-              <p class="ar-table-title">细分市场的重点中标单位-项目数量</p>
-              <Top3Table v-if="sections.market.refine.projectCountTop3" :tableData="sections.market.refine.projectCountTop3" />
-            </div>
-            <div class="ar-table"
-              v-if="!sections.loaded.segment || (sections.market.refine.projectAmountTop3 && sections.loaded.segment)"
-              v-loading="!sections.loaded.segment">
-              <p class="ar-table-title">细分市场的重点中标单位-项目金额</p>
-              <Top3Table v-if="sections.market.refine.projectAmountTop3" :tableData="sections.market.refine.projectAmountTop3" />
-            </div>
-          </div>
-          <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[3]])" :k="Object.keys(vipUpgradeMap)[3]" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[3]]"></MaskCard>
-        </div>
-      </section>
-      <div class="bg-grey-h24" v-if="!sections.loaded.buyerWinner || buyerclassSectionShow"></div>
-      <section class="analysis-result-section section-buyer"
-        v-if="!sections.loaded.buyerWinner || (buyerclassSectionShow && sections.loaded.buyerWinner)"
-        v-loading="!sections.loaded.buyerWinner">
-        <div class="analysis-result-title pd-lr20">采购单位</div>
-        <div class="analysis-result-content sub-section-list">
-          <!-- 采购规模分布 -->
-          <div class="sub-section buyerclass-scatter" v-if="sections.buyerclass.dataAlready">
-            <div class="sub-section-header">
-              <div class="sub-section-title">采购规模分布</div>
-            </div>
-            <div class="sub-section-content">
-              <BuyerScaleScatter v-if="buyerclassSectionShow" :chartData="sections.buyerclass.chartData" />
-            </div>
-          </div>
-          <div class="top3-table-list pd-lr20">
-            <div class="ar-table buyerclass-count-top3" v-if="(sections.buyerclass.projectCountTop3 && isConf10)">
-              <p class="ar-table-title">项目数量TOP3采购单位及其重点合作中标单位</p>
-              <Top3Table v-if="buyerclassSectionShow" :tableData="sections.buyerclass.projectCountTop3" />
-            </div>
-            <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[4]])" k="项目数量TOP3采购单位及其重点合作中标单位" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[4]]"></MaskCard>
-            <div class="ar-table buyerclass-amount-top3" v-if="(sections.buyerclass.projectAmountTop3 && isConf10)">
-              <p class="ar-table-title">采购金额TOP3采购单位及其重点合作中标单位</p>
-              <Top3Table v-if="buyerclassSectionShow" :tableData="sections.buyerclass.projectAmountTop3" />
-            </div>
-            <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[5]])" k="采购金额TOP3采购单位及其重点合作中标单位" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[5]]"></MaskCard>
-          </div>
-        </div>
-      </section>
-      <div class="bg-grey-h24" v-if="!sections.loaded.buyerWinner || winnerSectionShow"></div>
-      <section class="analysis-result-section section-winner"
-        v-if="!sections.loaded.buyerWinner || (winnerSectionShow && sections.loaded.buyerWinner)"
-        v-loading="!sections.loaded.buyerWinner">
-        <div class="analysis-result-title pd-lr20">中标单位</div>
-        <div class="analysis-result-content sub-section-list">
-          <!-- 中标规模分布 -->
-          <div class="sub-section winner-scatter">
-            <div class="sub-section-header">
-              <div class="sub-section-title">中标规模分布</div>
-            </div>
-            <div class="sub-section-content">
-              <BidderScaleScatter v-if="sections.winner.dataAlready" :chartData="sections.winner.chartData" />
-            </div>
-          </div>
-          <div class="top3-table-list pd-lr20">
-            <div class="ar-table winner-count-top3" v-if="isConf10">
-              <p class="ar-table-title">项目数量TOP3中标单位及其重点合作采购单位</p>
-              <Top3Table v-if="sections.winner.projectCountTop3" :tableData="sections.winner.projectCountTop3" />
+          </section>
+          <div class="bg-grey-h24" v-if="!sections.loaded.buyerWinner || buyerclassSectionShow"></div>
+          <section class="analysis-result-section section-buyer"
+            v-if="!sections.loaded.buyerWinner || (buyerclassSectionShow && sections.loaded.buyerWinner)"
+            v-loading="!sections.loaded.buyerWinner">
+            <div class="analysis-result-title pd-lr20">采购单位</div>
+            <div class="analysis-result-content sub-section-list">
+              <!-- 采购规模分布 -->
+              <div class="sub-section buyerclass-scatter" v-if="sections.buyerclass.dataAlready">
+                <div class="sub-section-header">
+                  <div class="sub-section-title">采购规模分布</div>
+                </div>
+                <div class="sub-section-content">
+                  <BuyerScaleScatter v-if="buyerclassSectionShow" :chartData="sections.buyerclass.chartData" />
+                </div>
+              </div>
+              <div class="top3-table-list pd-lr20">
+                <div class="ar-table buyerclass-count-top3" v-if="(sections.buyerclass.projectCountTop3 && isConf10)">
+                  <p class="ar-table-title">项目数量TOP30采购单位及其重点合作中标单位</p>
+                  <Top3Table
+                    v-if="buyerclassSectionShow"
+                    :tableData="sections.buyerclass.projectCountTop3"
+                    labelClickable
+                    @clickLabel="clickTop3TableLabel($event, 'buyerclassTOP')"
+                    valueClickable
+                    @clickValue="clickTop3TableValue($event, 'buyerclassTOP')"
+                  />
+                </div>
+                <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[4]])" k="项目数量TOP30采购单位及其重点合作中标单位" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[4]]"></MaskCard>
+                <div class="ar-table buyerclass-amount-top3" v-if="(sections.buyerclass.projectAmountTop3 && isConf10)">
+                  <p class="ar-table-title">采购金额TOP30采购单位及其重点合作中标单位</p>
+                  <Top3Table
+                    v-if="buyerclassSectionShow"
+                    :tableData="sections.buyerclass.projectAmountTop3"
+                    labelClickable
+                    @clickLabel="clickTop3TableLabel($event, 'buyerclassTOP')"
+                    valueClickable
+                    @clickValue="clickTop3TableValue($event, 'buyerclassTOP')"
+                  />
+                </div>
+                <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[5]])" k="采购金额TOP30采购单位及其重点合作中标单位" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[5]]"></MaskCard>
+              </div>
             </div>
-            <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[6]])" k="项目数量TOP3中标单位及其重点合作采购单位" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[6]]"></MaskCard>
-            <div class="ar-table winner-amount-top3" v-if="isConf10">
-              <p class="ar-table-title">中标金额TOP3中标单位及其重点合作采购单位</p>
-              <Top3Table v-if="sections.winner.projectAmountTop3" :tableData="sections.winner.projectAmountTop3" />
+          </section>
+          <div class="bg-grey-h24" v-if="!sections.loaded.buyerWinner || winnerSectionShow"></div>
+          <section class="analysis-result-section section-winner"
+            v-if="!sections.loaded.buyerWinner || (winnerSectionShow && sections.loaded.buyerWinner)"
+            v-loading="!sections.loaded.buyerWinner">
+            <div class="analysis-result-title pd-lr20">中标单位</div>
+            <div class="analysis-result-content sub-section-list">
+              <!-- 中标规模分布 -->
+              <div class="sub-section winner-scatter">
+                <div class="sub-section-header">
+                  <div class="sub-section-title">中标规模分布</div>
+                </div>
+                <div class="sub-section-content">
+                  <BidderScaleScatter v-if="sections.winner.dataAlready" :chartData="sections.winner.chartData" />
+                </div>
+              </div>
+              <div class="top3-table-list pd-lr20">
+                <div class="ar-table winner-count-top3" v-if="isConf10">
+                  <p class="ar-table-title">项目数量TOP30中标单位及其重点合作采购单位</p>
+                  <Top3Table
+                    v-if="sections.winner.projectCountTop3"
+                    :tableData="sections.winner.projectCountTop3"
+                    labelClickable
+                    @clickLabel="clickTop3TableLabel($event, 'winnerTOP')"
+                    valueClickable
+                    @clickValue="clickTop3TableValue($event, 'winnerTOP')"
+                  />
+                </div>
+                <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[6]])" k="项目数量TOP30中标单位及其重点合作采购单位" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[6]]"></MaskCard>
+                <div class="ar-table winner-amount-top3" v-if="isConf10">
+                  <p class="ar-table-title">中标金额TOP30中标单位及其重点合作采购单位</p>
+                  <Top3Table
+                    v-if="sections.winner.projectAmountTop3"
+                    :tableData="sections.winner.projectAmountTop3"
+                    labelClickable
+                    @clickLabel="clickTop3TableLabel($event, 'winnerTOP')"
+                    valueClickable
+                    @clickValue="clickTop3TableValue($event, 'winnerTOP')"
+                  />
+                </div>
+                <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[7]])" k="中标金额TOP30中标单位及其重点合作采购单位" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[7]]"></MaskCard>
+              </div>
             </div>
-            <MaskCard v-if="!isConf10" @click="openBigPage(vipUpgradeMap[Object.keys(vipUpgradeMap)[7]])" k="中标金额TOP3中标单位及其重点合作采购单位" :item="vipUpgradeMap[Object.keys(vipUpgradeMap)[7]]"></MaskCard>
-          </div>
+          </section>
         </div>
-      </section>
-    </div>
+      </el-tab-pane>
+      <el-tab-pane lazy label="项目明细" name="2">
+        <ProjectDetailsList
+          :initFilters="projectDetailFilters.initFilters"
+          :reportFilters="reportFilters"
+          :subscribeClassList="projectDetailFilters.keywordsGroupList"
+          :subscribeIndustryMap="projectDetailFilters.subscribeIndustryMap"
+          :subscribeAreaMap="projectDetailFilters.subscribeAreaMap"
+          :subscribeBuyerClass="projectDetailFilters.subscribeBuyerClass"
+        />
+      </el-tab-pane>
+    </el-tabs>
     <!-- 更多项目信息 -->
     <user-scatter-dialog v-if="userScatterDialog.visible" :options.sync="userScatterDialog"/>
     <!-- 留资弹窗 -->
@@ -244,11 +331,11 @@
 </template>
 
 <script>
-import { Dropdown, DropdownMenu, DropdownItem, Icon } from 'element-ui'
+import { Dropdown, DropdownMenu, DropdownItem, Icon, Tabs, TabPane } from 'element-ui'
 import MarketOverview from '@/views/analysisReport/components/MarketOverview.vue'
 import ProjectScatter from '@/views/analysisReport/components/ProjectScatter.vue'
 import MarketTimeScatter from '@/views/analysisReport/components/MarketTimeScatter.vue'
-import MarketAreaScatter from '@/views/analysisReport/components/MarketAreaScatter.vue'
+import MarketAreaCityScatter from '@/views/analysisReport/components/MarketAreaCityScatter.vue'
 import MarketSegment from '@/views/analysisReport/components/MarketSegment.vue'
 import BuyerScaleScatter from '@/views/analysisReport/components/BuyerScaleScatter.vue'
 import BidderScaleScatter from '@/views/analysisReport/components/BidderScaleScatter.vue'
@@ -260,6 +347,7 @@ import CollectInfo from '@/components/collect-info/CollectInfo.vue'
 import { getReportAnalysisInfo } from '@/api/modules/'
 import { mapState } from 'vuex'
 import { moneyUnit, dateFormatter, formatPrice } from '@/utils/globalFunctions'
+import ProjectDetailsList from './components/ProjectDetailsList.vue'
 function getImgForVipUpgrade (name, bg = false, suffix = '.png') {
   return require('@/assets/images/report/' + (bg ? 'bg/' : '') + name + suffix)
 }
@@ -270,10 +358,13 @@ export default {
     [Dropdown.name]: Dropdown,
     [DropdownMenu.name]: DropdownMenu,
     [DropdownItem.name]: DropdownItem,
+    [Tabs.name]: Tabs,
+    [TabPane.name]: TabPane,
+    ProjectDetailsList,
     MarketOverview,
     ProjectScatter,
     MarketTimeScatter,
-    MarketAreaScatter,
+    MarketAreaCityScatter,
     MarketSegment,
     BuyerScaleScatter,
     BidderScaleScatter,
@@ -290,6 +381,7 @@ export default {
     return {
       loaded: false,
       loading: false,
+      tabActiveName: '1',
       stickyed: {
         className: ['dim-fixed'],
         startFixedTop: 0 // 从高度为startFixedTop处开始置顶
@@ -362,12 +454,12 @@ export default {
             anchor: 'buyerclass-scatter'
           },
           {
-            name: '项目数量TOP3',
+            name: '项目数量TOP30',
             show: false,
             anchor: 'buyerclass-count-top3'
           },
           {
-            name: '采购金额TOP3',
+            name: '采购金额TOP30',
             show: false,
             anchor: 'buyerclass-amount-top3'
           }
@@ -379,12 +471,12 @@ export default {
             anchor: 'winner-scatter'
           },
           {
-            name: '项目数量TOP3',
+            name: '项目数量TOP30',
             show: false,
             anchor: 'winner-count-top3'
           },
           {
-            name: '中标金额TOP3',
+            name: '中标金额TOP30',
             show: false,
             anchor: 'winner-amount-top3'
           }
@@ -440,6 +532,8 @@ export default {
         areaScatter: {
           dataAlready: false,
           chartData: null,
+          // 原始数据
+          areaDataList: [],
           // 项目数量Top3
           projectCountTop3: null,
           // 项目金额Top3
@@ -471,6 +565,15 @@ export default {
           projectAmountTop3: null
         }
       },
+      projectDetailFilters: {
+        // 该属性会被传入项目明细,更改时会清空其他选择器并恢复initFilter中的选项
+        initFilters: {},
+        // 备选项
+        keywordsGroupList: [],
+        subscribeAreaMap: {},
+        subscribeIndustryMap: {},
+        subscribeBuyerClass: []
+      },
       // 客户分布查看详情弹窗
       userScatterDialog: {
         visible: false,
@@ -553,6 +656,9 @@ export default {
     ...mapState({
       info: state => state.user.info
     }),
+    reportSelectedKeywords () {
+      return this.projectDetailFilters.keywordsGroupList
+    },
     isConf10 () {
       return this.info.power.indexOf(10) !== -1
     },
@@ -566,9 +672,15 @@ export default {
       })
       return tempMap
     },
-    notOneAreaFilter () {
+    notOneCityFilter () {
       const area = this.reportFilters.area
-      const showArea = area && (Object.keys(area).length > 1 || Object.keys(area).length === 0)
+      let cityArr = []
+      for (const key in area) {
+        cityArr = cityArr.concat(area[key])
+      }
+      const cityCount = cityArr.length
+      const onlyOneCity = area && Object.keys(area).length === 1 && cityCount === 1
+      const showArea = !onlyOneCity
       return showArea
     },
     buyerclassSectionShow () {
@@ -580,7 +692,6 @@ export default {
       return winnerState.dataAlready && winnerState.projectCountTop3 && winnerState.projectAmountTop3
     }
   },
-  watch: {},
   created () {
     this.sendRequest()
   },
@@ -596,6 +707,31 @@ export default {
         this.stickyed.startFixedTop = offset.top + 5
       }
     },
+    beforeTabLeave (targetActiveName) {
+      // 无权限点击项目信息明细,提示留资
+      if (targetActiveName === '2') {
+        try {
+        // eslint-disable-next-line no-undef
+          _hmt.push(['_trackEvent', '定制化市场分析报告', 'click', '项目明细tab'])
+        } catch (e) {
+          console.log('未初始化百度统计')
+        }
+        if (this.isConf10) {
+          setTimeout(() => {
+            const headerH = 64
+            window.scrollTo(0, headerH * 2)
+          }, 100)
+          return true
+        } else {
+          // 留资
+          this.openLeaveSourceDialog('pc_analysis_ProjectDetails')
+          this.tabActiveName = '1'
+          return false
+        }
+      } else {
+        return true
+      }
+    },
     onEmpty (info) {
       this.$emit('onEmpty', info)
     },
@@ -721,6 +857,12 @@ export default {
       if (data.buyerclass) {
         this.reportFilters.buyerclass = data.buyerclass.split(',')
       }
+
+      this.projectDetailFilters.keywordsGroupList = this.reportFilters.keys
+      this.projectDetailFilters.subscribeAreaMap = this.reportFilters.area
+      this.projectDetailFilters.subscribeBuyerClass = this.reportFilters.buyerclass
+      this.projectDetailFilters.subscribeIndustryMap = this.reportFilters.industry
+
       this.$emit('loadedFilters', this.reportFilters)
     },
     // 市场概况
@@ -1052,6 +1194,7 @@ export default {
         this.$set(this.sections.areaScatter, 'chartData', areaChartData)
         this.sections.areaScatter.dataAlready = true
         this.showDimensionsOptions('market', 'area-scatter')
+        this.sections.areaScatter.areaDataList = areaList
       }
     },
     // 客户分布
@@ -1250,7 +1393,6 @@ export default {
           }
         })
       }
-
       if (tableDataCount.rows.length) {
         this.$set(this.sections.userScatter, 'projectCountTop3', tableDataCount)
       }
@@ -1660,13 +1802,16 @@ export default {
     },
     dropDownClick (item) {
       if (!item.show) return
-      const $ = this.$querySelector.bind(this)
-      const anchor = item.anchor
-      const offset = $('.' + anchor).offset()
-      const headerH = 64
-      if (offset) {
-        $('body,html').animate({ scrollTop: offset.top - headerH - 64 })
-      }
+      this.tabActiveName = '1'
+      this.$nextTick(() => {
+        const $ = this.$querySelector.bind(this)
+        const anchor = item.anchor
+        const offset = $('.' + anchor).offset()
+        const headerH = 64
+        if (offset) {
+          window.scrollTo(0, offset.top - headerH - 64)
+        }
+      })
     },
     moneyUnit (...args) {
       const m = moneyUnit(...args)
@@ -1685,6 +1830,109 @@ export default {
         count
       }
     },
+    getKeyWordFilterFromName (name) {
+      const target = this.reportSelectedKeywords.find(r => r.s_item === name)
+      const filters = {
+        subscribe: [
+          {
+            a_key: target?.a_key || [],
+            s_item: name
+          }
+        ]
+      }
+      return filters
+    },
+    clickMarketUserScatter (e) {
+      this.recoverBuyerClassFilter(e.name)
+    },
+    clickMarketSegment (e) {
+      this.recoverKeyWordFilter(e.name)
+    },
+    clickTop3TableLabel (e, type) {
+      if (type === 'segment') {
+        this.recoverKeyWordFilter(e.name)
+      } else if (type === 'buyerclassTOP') {
+        this.recoverBuyerFilter(e.name)
+      } else if (type === 'winnerTOP') {
+        this.recoverWinnerFilter(e.winner_name)
+      } else if (type === 'areaScatter') {
+        this.recoverAreaCityFilter(e.name)
+      } else if (type === 'userScatter') {
+        this.recoverBuyerClassFilter(e.name)
+      }
+    },
+    clickTop3TableValue (e, type) {
+      const filters = {}
+      if (type === 'segment') {
+        const f = this.getKeyWordFilterFromName(e.name)
+        Object.assign(filters, f)
+        filters.winner = e.winner_name || ''
+        this.toProjectDetail(filters)
+      } else if (type === 'buyerclassTOP' || type === 'winnerTOP') {
+        const filters = {
+          buyerName: e.name,
+          winner: e.winner_name
+        }
+        this.toProjectDetail(filters)
+      } else if (type === 'areaScatter') {
+        const filters = {
+          area: {
+            [e.name]: []
+          },
+          winner: e.winner_name
+        }
+        this.toProjectDetail(filters)
+      } else if (type === 'userScatter') {
+        const filters = {
+          buyerClass: [e.name],
+          winner: e.winner_name
+        }
+        this.toProjectDetail(filters)
+      }
+    },
+    recoverKeyWordFilter (name) {
+      const filters = this.getKeyWordFilterFromName(name)
+      this.toProjectDetail(filters)
+    },
+    recoverBuyerClassFilter (name) {
+      const filters = {
+        buyerClass: [name]
+      }
+      this.toProjectDetail(filters)
+    },
+    recoverWinnerFilter (name) {
+      const filters = {
+        winner: name
+      }
+      this.toProjectDetail(filters)
+    },
+    recoverBuyerFilter (name) {
+      const filters = {
+        buyerName: name
+      }
+      this.toProjectDetail(filters)
+    },
+    recoverAreaCityFilter (areaName) {
+      const filters = {
+        area: {
+          [areaName]: []
+        }
+      }
+      this.toProjectDetail(filters)
+    },
+    toProjectDetail (filters = {}) {
+      // filter = {
+      //   subscribe: [],
+      //   area: {},
+      //   buyerClass: [],
+      //   industry: {},
+      //   winner: '',
+      //   sort: 0,
+      //   buyerName: ''
+      // }
+      this.projectDetailFilters.initFilters = filters
+      this.tabActiveName = '2'
+    },
     openBigPage (item) {
       try {
         // eslint-disable-next-line no-undef
@@ -1695,8 +1943,11 @@ export default {
       if (item.button && item.button === '联系客服') {
         this.contactCustomer(this)
       } else {
-        this.$refs.collectRef.noCallApiFn(item.source)
+        this.openLeaveSourceDialog(item.source)
       }
+    },
+    openLeaveSourceDialog (source) {
+      this.$refs.collectRef.noCallApiFn(source)
     }
   }
 }
@@ -1892,7 +2143,7 @@ i.el-icon-caret-bottom {
     height: 52px;
     font-size: 18px;
     color: #1D1D1D;
-    border-bottom: 1px solid #ECECEC;
+    // border-bottom: 1px solid #ECECEC;
     &::before {
       content: '';
       position: absolute;
@@ -1985,4 +2236,30 @@ i.el-icon-caret-bottom {
   background-color: #fff;
   box-shadow: 0px 4px 16px 1px rgba(0, 0, 0, 0.06);
 }
+
+.tabs {
+  ::v-deep {
+    .el-tabs__content {
+      overflow: unset;
+    }
+    .fixed-nav {
+      width: 920px;
+      box-shadow: inset 0 -1px 0 0 rgb(0, 0, 0, 0.05);
+    }
+    .el-tabs__header {
+      border-bottom: 1px solid #ECECEC;
+    }
+    .el-tabs__item {
+      height: 50px;
+      line-height: 50px;
+      font-size: 16px;
+      color: #686868;
+    }
+    .el-tabs__nav-wrap {
+      &::after {
+        content: unset;
+      }
+    }
+  }
+}
 </style>

+ 113 - 0
src/views/analysisReport/components/MarketAreaCityScatter.vue

@@ -0,0 +1,113 @@
+<template>
+  <div class="market-area-city-scatter">
+    <div class="scatter-item area-scatter" v-if="showAreaScatter">
+      <div class="scatter-title">省份分布</div>
+      <div class="scatter-chart">
+        <MarketAreaScatter class="area-chart" :chartData="chartData" @clickChart="clickChart" />
+      </div>
+    </div>
+    <div class="scatter-item area-scatter">
+      <div class="scatter-title">城市分布</div>
+      <div class="scatter-chart market-cty-scatter">
+        <MarketCityScatter class="city-chart" ref="marketCityScatter" :filterAreaMap="filterAreaMap" :dataList="areaDataList" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Select, Option } from 'element-ui'
+import MarketAreaScatter from './MarketAreaScatter'
+import MarketCityScatter from './MarketCityScatter.vue'
+
+export default {
+  name: 'market-area-city-scatter',
+  components: {
+    [Select.name]: Select,
+    [Option.name]: Option,
+    MarketAreaScatter,
+    MarketCityScatter
+  },
+  props: {
+    // 地图数据
+    chartData: {
+      type: Object,
+      default () {
+        return {
+          columns: ['项目所在地', '项目数量', '项目金额'],
+          rows: [
+          //   {
+          //     项目所在地: '河南',
+          //     项目数量: 2,
+          //     项目金额: 2222
+          //   },
+          //   {
+          //     项目所在地: '北京',
+          //     项目数量: 22,
+          //     项目金额: 565666
+          //   },
+          //   {
+          //     项目所在地: '浙江',
+          //     项目数量: 22,
+          //     项目金额: 765666
+          //   }
+          ]
+        }
+      }
+    },
+    // 省份备选项
+    filterAreaMap: {
+      type: Object,
+      default () {
+        return {}
+      }
+    },
+    // 省份城市数据
+    areaDataList: {
+      type: Array,
+      default () {
+        return []
+      }
+    }
+  },
+  computed: {
+    showAreaScatter () {
+      // 只选择了一个省,则省份分布不展示
+      return Object.keys(this.filterAreaMap).length !== 1
+    }
+  },
+  methods: {
+    clickChart (e) {
+      const area = e.name
+      const { marketCityScatter } = this.$refs
+      // eslint-disable-next-line no-unused-expressions
+      marketCityScatter?.setArea(area)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.market-area-city-scatter {
+  display: flex;
+  justify-content: space-between;
+}
+.scatter-item {
+  position: relative;
+  flex: 1;
+}
+.scatter-title {
+  position: absolute;
+  width: 100%;
+  left: 0;
+  top: 0;
+  font-weight: 700;
+  font-size: 14px;
+  line-height: 22px;
+  text-align: center;
+}
+
+.city-chart {
+  padding-top: 34px;
+}
+</style>

+ 15 - 2
src/views/analysisReport/components/MarketAreaScatter.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="market-area-scatter">
-    <MapChart id="area" :options="options" :datas="chartData"></MapChart>
+    <MapChart id="area" :options="options" :datas="chartData" :customDefaultOptions="customDefaultOptions" @clickChart="clickChart"></MapChart>
   </div>
 </template>
 
@@ -42,9 +42,19 @@ export default {
   data () {
     return {
       options: {
-        height: '800px',
+        height: '520px',
         colors: ['#05A6F3', '#0BD991', '#FF9F40'],
         config: this.configArea
+      },
+      customDefaultOptions: {
+        series: {
+          roam: false,
+          zoom: 0.6,
+          scaleLimit: {
+            min: 0.6,
+            max: 2
+          }
+        }
       }
     }
   },
@@ -78,6 +88,9 @@ export default {
         return tip
       }
       return options
+    },
+    clickChart (e) {
+      this.$emit('clickChart', e)
     }
   }
 }

+ 204 - 0
src/views/analysisReport/components/MarketCityScatter.vue

@@ -0,0 +1,204 @@
+<template>
+  <div class="scatter-chart market-cty-scatter">
+    <div class="market-city-scatter-hd">
+      <div class="select-group">
+        <el-select
+          class="selector-cascader area-selector no-select"
+          :popper-append-to-body="false"
+          v-model="filters.area">
+          <el-option
+            v-for="item in areaOptionsList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value">
+          </el-option>
+        </el-select>
+        <el-select
+          class="selector-cascader order-selector no-select"
+          :popper-append-to-body="false"
+          v-model="filters.order">
+          <el-option
+            v-for="item in orderList"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value">
+          </el-option>
+        </el-select>
+      </div>
+      <span class="unit-text">单位:{{ unit }}</span>
+    </div>
+    <div class="market-city-scatter-bd">
+      <MarketHistogram :list="cityRenderList" />
+    </div>
+  </div>
+</template>
+
+<script>
+import { Select, Option } from 'element-ui'
+import MarketHistogram from './MarketHistogram.vue'
+import { provinceListMapExp } from '@/assets/js/selector.js'
+import { formatMoney } from '@/utils/'
+
+export default {
+  name: 'market-city-scatter',
+  components: {
+    [Select.name]: Select,
+    [Option.name]: Option,
+    MarketHistogram
+  },
+  props: {
+    filterAreaMap: {
+      type: Object,
+      required: true,
+      default () {
+        return {}
+      }
+    },
+    // 原始数据
+    dataList: {
+      type: Array,
+      default () {
+        return []
+      }
+    }
+  },
+  data () {
+    return {
+      cityArea: ['北京', '上海', '重庆', '天津', '香港', '澳门', '台湾'],
+      filters: {
+        area: '',
+        order: 1
+      },
+      orderList: [
+        {
+          label: '项目数量由大到小排序',
+          unit: '个',
+          value: 1
+        },
+        {
+          label: '项目金额由大到小排序',
+          unit: '万元',
+          value: 2
+        }
+      ]
+    }
+  },
+  computed: {
+    // 省份备选项数组
+    areaList () {
+      if (this.filterAreaMap) {
+        const areaArr = Object.keys(this.filterAreaMap)
+        if (areaArr.length) {
+          return areaArr
+        } else {
+          let areaArr = []
+          for (const key in provinceListMapExp) {
+            if (key === '#') {
+              continue
+            } else {
+              areaArr = areaArr.concat(provinceListMapExp[key])
+            }
+          }
+          return areaArr
+        }
+      } else {
+        return []
+      }
+    },
+    // 整理省份备选项数据结构
+    areaOptionsList () {
+      return this.areaList.map(a => {
+        return {
+          label: a,
+          value: a
+        }
+      })
+    },
+    // 当前选中省份下城市数据
+    selectedAreaDataList () {
+      const target = this.dataList.find(d => this.filters.area === d.area)
+      if (target) {
+        return target.areaDetails || []
+      } else {
+        return []
+      }
+    },
+    // 当前选中省份下城市数据排序
+    cityRenderList () {
+      const { order } = this.filters
+      const copied = JSON.parse(JSON.stringify(this.selectedAreaDataList))
+      if (order === 1) {
+        // 项目数量由大到小排序
+        return copied.sort((a, b) => b.total - a.total).map(s => {
+          return {
+            label: s.city,
+            value: s.total
+          }
+        })
+      } else if (order === 2) {
+        // 项目金额由大到小排序
+        return copied.sort((a, b) => b.amount - a.amount).map(s => {
+          return {
+            label: s.city,
+            value: formatMoney(s.amount, {
+              type: 'number'
+            })
+          }
+        })
+      } else {
+        return []
+      }
+    },
+    activeOrder () {
+      return this.orderList.find(r => r.value === this.filters.order) || {}
+    },
+    unit () {
+      return this.activeOrder.unit || ''
+    }
+  },
+  watch: {
+    dataList: {
+      immediate: true,
+      handler (n) {
+        // console.log(n)
+        this.calcMaxArea(n)
+      }
+    }
+  },
+  methods: {
+    setArea (area) {
+      if (this.areaList.includes(area)) {
+        this.filters.area = area
+      }
+    },
+    calcMaxArea (n) {
+      const copied = JSON.parse(JSON.stringify(n))
+      const sorted = copied.sort((a, b) => b.total - a.total)
+      if (sorted) {
+        this.filters.area = sorted[0].area
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.market-city-scatter-hd {
+  margin-bottom: 16px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.area-selector {
+  width: 124px;
+  margin-right: 24px;
+}
+.order-selector {
+  width: 196px;
+}
+.unit-text {
+  font-size: 12px;
+  line-height: 18px;
+  color: #686868;
+}
+</style>

+ 155 - 0
src/views/analysisReport/components/MarketHistogram.vue

@@ -0,0 +1,155 @@
+<template>
+  <div class="market-histogram m-histogram-list" :class="{ scroll: renderList.length > 14 }">
+    <div class="m-histogram-item" v-for="(item, index) in renderList" :key="index">
+      <span class="m-histogram-label ellipsis" :title="item.label">{{ item.label }}</span>
+      <div class="m-histogram-value">
+        <div class="j-progress-bar blue" :class="{ pointer: pointer }">
+          <div class="j-progress-count" :style="{ width: item.percentText }">
+            <span class="j-progress-text inside" v-if="showInsideText(item.percent)">{{ item.value }}</span>
+            <span class="j-progress-text outside" v-if="!showInsideText(item.percent)">{{ item.value }}</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'market-histogram',
+  props: {
+    percentFull: {
+      type: Number,
+      default: 0
+    },
+    pointer: {
+      type: Boolean,
+      default: false
+    },
+    useCustomPercent: {
+      type: Boolean,
+      default: false
+    },
+    list: {
+      type: Array,
+      default () {
+        return [
+          // {
+          //   label: '',
+          //   value: 0
+          // }
+        ]
+      }
+    }
+  },
+  computed: {
+    sortedList () {
+      const copied = JSON.parse(JSON.stringify(this.list))
+      return copied.sort((a, b) => b.value - a.value)
+    },
+    renderList () {
+      const percent100 = this.percentFull || this.sortedList[0]?.value
+      if (this.useCustomPercent) {
+        return this.sortedList
+      } else {
+        return this.sortedList.map(s => {
+          const percent = s.value / percent100
+          const percentText = `${percent * 100}%`
+          return {
+            ...s,
+            percent,
+            percentText
+          }
+        })
+      }
+    }
+  },
+  methods: {
+    showInsideText (percent) {
+      return percent > 0.4
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.m-histogram-list {
+  padding-right: 12px;
+  max-height: 330px;
+  background-color: #fff;
+  &.scroll {
+    overflow-y: scroll;
+  }
+
+  &::-webkit-scrollbar {
+    /*滚动条整体样式*/
+    width: 20px;
+    background-color: #F5F6F7;
+    border: 1px solid #ECECEC;
+  }
+  &::-webkit-scrollbar-thumb {
+    /*滚动条里面小方块*/
+    border-radius: 3px;
+    background-color: #E3E4E6;
+  }
+}
+.m-histogram-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  &:not(:last-of-type) {
+    margin-bottom: 6px;
+  }
+}
+.m-histogram-label {
+  margin-right: 12px;
+  font-size: 12px;
+  line-height: 18px;
+  text-align: right;
+  width: 60px;
+  color: #686868;
+}
+.m-histogram-value {
+  flex: 1;
+}
+.j-progress-bar {
+  position: relative;
+  width: 100%;
+  height: 18px;
+  line-height: 18px;
+  background-color: #fff;
+  border-radius: 2px;
+  overflow: hidden;
+  &.blue {
+    $blue: #05A5F2;
+    .j-progress-count {
+      background-color: $blue;
+      .inside {
+        color: #fff;
+      }
+      .outside {
+        color: $blue;
+      }
+    }
+  }
+}
+.j-progress-count {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  padding: 0 16px;
+  font-size: 12px;
+  line-height: 18px;
+  border-radius: 2px;
+  text-align: right;
+  z-index: 9;
+  .j-progress-text {
+    &.outside {
+      position: absolute;
+      right: -8px;
+      transform: translateX(100%);
+    }
+  }
+}
+</style>

+ 5 - 2
src/views/analysisReport/components/MarketSegment.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="market-segment">
-    <BarLineChart class="market-bar-chart" :datas="projectCountData" :options="getProjectOptions('count')"></BarLineChart>
-    <BarLineChart class="market-bar-chart" :datas="projectAmountData" :options="getProjectOptions('amount')"></BarLineChart>
+    <BarLineChart class="market-bar-chart" :datas="projectCountData" :options="getProjectOptions('count')" @clickChart="clickChart"></BarLineChart>
+    <BarLineChart class="market-bar-chart" :datas="projectAmountData" :options="getProjectOptions('amount')" @clickChart="clickChart"></BarLineChart>
   </div>
 </template>
 
@@ -75,6 +75,9 @@ export default {
     }
   },
   methods: {
+    clickChart (e) {
+      this.$emit('clickChart', e)
+    },
     getProjectOptions (type) {
       const color = type === 'count' ? '#05A6F3' : '#FF9F3F'
       return {

+ 114 - 4
src/views/analysisReport/components/MarketTop3Table.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="market-top3-table">
     <el-table
-      :data="tableData.rows"
+      :data="renderList"
       header-row-class-name="ar-table-thead"
       row-class-name="ar-table-row"
       :span-method="objectSpanMethod"
@@ -37,7 +37,16 @@
           </div>
           <div class="area" v-else>{{ scope.row.name }}</div>
           <div class="area-info">
-            <span class="j-tag tag-lightblue" v-if="scope.row.value">{{ scope.row.value }}</span>
+            <span class="j-tag tag-lightblue" v-if="scope.row.value">
+              <template v-if="labelClickable">
+                <el-link
+                :underline="false"
+                class="tag-lightblue"
+                type="primary"
+                @click="clickLabel(scope.row)">{{ scope.row.value }}</el-link>
+              </template>
+              <template v-else>{{ scope.row.value }}</template>
+            </span>
             <span class="j-tag tag-orange" v-if="scope.row.percent">{{ scope.row.percent }}</span>
           </div>
         </template>
@@ -54,7 +63,15 @@
                 :disabled="!scope.row.winner_id"
                 @click="toOtherPage(scope.row.winner_id, scope.row.winner_type)">{{ scope.row.winner_name }}</el-link>
             </span>
-            <span v-if="scope.row.winner_value">:{{ scope.row.winner_value }}</span>
+            <span v-if="scope.row.winner_value">
+              <span>:</span>
+              <template v-if="valueClickable">
+                <el-link
+                :underline="false"
+                @click="clickValue(scope.row)">{{ scope.row.winner_value }}</el-link>
+              </template>
+              <template v-else>{{ scope.row.winner_value }}</template>
+            </span>
             <span v-if="scope.row.winner_value && scope.row.winner_percent">,</span>
             <span v-if="scope.row.winner_percent">{{ scope.row.winner_percent }}</span>
           </div>
@@ -62,19 +79,42 @@
         </template>
       </el-table-column>
     </el-table>
+    <div class="pagination-container pd-lr" v-if="listState.total > conf.minPageSize">
+      <el-pagination
+        background
+        popper-class="pagination-custom-select"
+        layout="prev, pager, next, sizes, jumper"
+        :page-size="listState.pageSize"
+        :current-page="listState.pageNum"
+        @current-change="onCurrentChange"
+        :total="listState.total"
+        :page-sizes="[5, 10, 50, 100]"
+        :show-confirm-btn="true"
+        @size-change="onSizeChange"
+      ></el-pagination>
+    </div>
   </div>
 </template>
 
 <script>
-import { Table, TableColumn, Link } from 'element-ui'
+import { Table, TableColumn, Link, Pagination } from 'element-ui'
 export default {
   name: 'market-top3-table',
   components: {
     [Link.name]: Link,
     [Table.name]: Table,
+    [Pagination.name]: Pagination,
     [TableColumn.name]: TableColumn
   },
   props: {
+    labelClickable: {
+      type: Boolean,
+      default: false
+    },
+    valueClickable: {
+      type: Boolean,
+      default: false
+    },
     tableData: {
       type: Object,
       default () {
@@ -116,7 +156,73 @@ export default {
       }
     }
   },
+  data () {
+    return {
+      conf: {
+        minPageSize: 5
+      },
+      listState: {
+        pageNum: 1,
+        pageSize: 5,
+        total: 0,
+        list: []
+      }
+    }
+  },
+  computed: {
+    renderList () {
+      if (Array.isArray(this.listState.list)) {
+        const { pageNum, pageSize } = this.listState
+        const start = (pageNum - 1) * pageSize
+        const end = pageNum * pageSize
+        // 循环计算当前需要展示数据列表
+        return this.listState.list.filter(r => {
+          return r.index > start && r.index <= end
+        })
+      } else {
+        return []
+      }
+    }
+  },
+  watch: {
+    tableData: {
+      immediate: true,
+      handler (n) {
+        this.resetListState()
+        this.calcListState(n.rows)
+      }
+    }
+  },
   methods: {
+    calcListState (list = []) {
+      this.listState.total = this.getListMaxIndex(list)
+      this.listState.list = list
+    },
+    // 获取tableData.rows数组中index的最大值
+    getListMaxIndex (list = []) {
+      const copied = JSON.parse(JSON.stringify(list))
+      const sorted = copied.sort((a, b) => b.index - a.index)
+      return sorted[0].index
+    },
+    resetListState () {
+      const r = this.$options.data().listState
+      delete r.list
+      delete r.total
+      Object.assign(this.listState, r)
+    },
+    onSizeChange (p) {
+      this.resetListState()
+      this.listState.pageSize = p
+    },
+    onCurrentChange (p) {
+      this.listState.pageNum = p
+    },
+    clickLabel (e) {
+      this.$emit('clickLabel', e)
+    },
+    clickValue (e) {
+      this.$emit('clickValue', e)
+    },
     toOtherPage (id, type) {
       let goLink = ''
       if (type === 'winner') {
@@ -146,4 +252,8 @@ export default {
     }
   }
 }
+.pagination-container {
+  margin-top: 16px;
+  text-align: right;
+}
 </style>

+ 4 - 1
src/views/analysisReport/components/MarketUserScatter.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="market-user-scatter-chart">
-    <RectTreeMapChart :min-height="minHeight" :datas="chartData" :options="options" />
+    <RectTreeMapChart :min-height="minHeight" :datas="chartData" :options="options" @clickChart="clickChart" />
   </div>
 </template>
 
@@ -47,6 +47,9 @@ export default {
     }
   },
   methods: {
+    clickChart (e) {
+      this.$emit('clickChart', e)
+    },
     tooltipFormatter (params) {
       if (!params.name) {
         return

+ 436 - 0
src/views/analysisReport/components/ProjectDetailsList.vue

@@ -0,0 +1,436 @@
+<template>
+  <section class="project-details">
+    <header class="project-details-header pd-lr">
+      <div class="filter-list line-1">
+        <SubscribeClassListCascader
+          class="filter-item"
+          :options="subscribeClassList"
+          v-model="filters.subscribe"
+          @change="onFilterChange"
+          ref="subscribeKey" />
+        <AreaCityCascader
+          class="filter-item"
+          v-model="filters.area"
+          @change="onFilterChange"
+          :showAreaMap="subscribeAreaMap"
+          ref="areaCity" />
+        <BuyerClassCascader
+          class="filter-item"
+          v-model="filters.buyerClass"
+          @change="onFilterChange"
+          :showOptions="subscribeBuyerClass"
+          ref="buyerClass" />
+        <IndustryCascader
+          class="filter-item"
+          @change="onFilterChange"
+          :showOptions="subscribeIndustryMap"
+          v-model="filters.industry"
+          ref="industry" />
+      </div>
+      <div class="filter-list line-2">
+        <div class="filter-item">
+          <span class="filter-item-label">中标单位:</span>
+          <AssociationInput
+            class="filter-item-input"
+            v-model.trim="filters.winner"
+            placeholder="请输入中标单位名称"
+            type="ent" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-item-label">采购单位:</span>
+          <AssociationInput
+            class="filter-item-input"
+            v-model.trim="filters.buyerName"
+            placeholder="请输入采购单位名称"
+            type="buyer" />
+        </div>
+      </div>
+    </header>
+    <main class="project-details-main">
+      <div class="project-details-actions">
+        <div class="project-details-actions-left">
+          <template v-if="searchTotal > 0">共 <span class="highlight-text">{{ listState.total }}</span> 个项目</template>
+        </div>
+        <div class="project-details-actions-right no-select">
+          <template v-if="listState.loaded && listState.list.length">
+            <span class="p-d-actions-label">排序:</span>
+            <el-radio-group class="sort-group" v-model="filters.sort" size="small">
+              <el-radio-button
+                v-for="item in conf.sortList"
+                :label="item.value"
+                :key="item.value"
+              >{{ item.name }}</el-radio-button>
+            </el-radio-group>
+          </template>
+        </div>
+      </div>
+      <div class="project-details-list" v-loading="listState.loading">
+        <ProjectItem
+          class="project-details-item pd-lr"
+          v-for="project in listState.list"
+          :buyer="project.buyer"
+          :title="project.name"
+          :budget="project.budget"
+          :bidAmount="project.bidAmount"
+          :tagList="project.tagList"
+          :winners="project.winners"
+          @clickTitle="toProjectDetail(project)"
+          :key="project.id">
+          <template #hd-right>
+            成交时间:{{ project.dealTime ? dateFormatter(project.dealTime * 1000, 'yyyy-MM-dd') : '-' }}
+          </template>
+        </ProjectItem>
+        <Empty v-show="listState.list.length === 0 && listState.loaded">暂无数据</Empty>
+      </div>
+    </main>
+    <div class="project-details-footer">
+      <div class="pagination-container pd-lr" v-show="listState.total > 0">
+        <el-pagination
+          background
+          popper-class="pagination-custom-select"
+          layout="prev, pager, next, sizes, jumper"
+          :page-size="listState.pageSize"
+          :current-page="listState.pageNum"
+          @current-change="onCurrentChange"
+          :total="searchTotal"
+          :page-sizes="[5, 10, 50, 100]"
+          :show-confirm-btn="true"
+          @size-change="onSizeChange"
+        ></el-pagination>
+        <div class="bottom-tip-text" v-show="listState.total > 5000">为您展示前{{ conf.listShowMaxCount }}条,可细化筛选条件查看更多信息</div>
+      </div>
+    </div>
+  </section>
+</template>
+
+<script>
+import { Input, RadioGroup, RadioButton, Table, TableColumn, Pagination } from 'element-ui'
+import SubscribeClassListCascader from '@/components/selector-cascader/SubscribeClassListCascader.vue'
+import AreaCityCascader from '@/components/selector-cascader/AreaCityCascader.vue'
+import BuyerClassCascader from '@/components/selector-cascader/BuyerClassCascader.vue'
+import IndustryCascader from '@/components/selector-cascader/IndustryCascader.vue'
+import AssociationInput from '@/views/reportData/components/AssociationInput.vue'
+import ProjectItem from '@/components/article-item/ProjectItem.vue'
+import Empty from '@/components/common/Empty'
+import { getMarketAnalysisReportProjectList } from '@/api/modules'
+import { formatMoney, dateFormatter } from '@/utils/'
+import { debounce } from 'lodash'
+
+export default {
+  name: 'ProjectDetailListForAnalysisReport',
+  components: {
+    [Input.name]: Input,
+    [RadioGroup.name]: RadioGroup,
+    [RadioButton.name]: RadioButton,
+    [Table.name]: Table,
+    [TableColumn.name]: TableColumn,
+    [Pagination.name]: Pagination,
+    SubscribeClassListCascader,
+    AreaCityCascader,
+    BuyerClassCascader,
+    IndustryCascader,
+    AssociationInput,
+    ProjectItem,
+    Empty
+  },
+  props: {
+    // 更改时会清空其他选择器并恢复initFilter中的选项
+    initFilters: {
+      type: Object,
+      default () {
+        return {}
+      }
+    },
+    // 父级筛选条件
+    reportFilters: {
+      type: Object,
+      default () {
+        return {}
+      }
+    },
+    subscribeClassList: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
+    subscribeBuyerClass: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
+    subscribeIndustryMap: {
+      type: Object,
+      default () {
+        return {}
+      }
+    },
+    subscribeAreaMap: {
+      type: Object,
+      default () {
+        return {}
+      }
+    }
+  },
+  data () {
+    return {
+      conf: {
+        sortList: [
+          {
+            name: '项目更新时间由晚到早',
+            value: 0
+          },
+          {
+            name: '项目金额由大到小',
+            value: 1
+          }
+        ],
+        listShowMaxCount: 5000
+      },
+      filters: {
+        subscribe: [],
+        area: {},
+        buyerClass: [],
+        industry: {},
+        winner: '',
+        sort: 0,
+        buyerName: ''
+      },
+      listState: {
+        loaded: false,
+        loading: false,
+        pageNum: 1,
+        pageSize: 10,
+        total: 0,
+        list: []
+      }
+    }
+  },
+  computed: {
+    searchTotal () {
+      const { total } = this.listState
+      const { listShowMaxCount } = this.conf
+      return total > listShowMaxCount ? listShowMaxCount : total
+    }
+  },
+  watch: {
+    initFilters: {
+      immediate: true,
+      handler  (n) {
+        this.$nextTick(() => {
+          this.recoverFilters(n)
+          this.onFilterChange()
+        })
+      }
+    },
+    'filters.winner': function () {
+      this.onFilterChange()
+    },
+    'filters.buyerName': function () {
+      this.onFilterChange()
+    },
+    'filters.sort': function () {
+      this.onFilterChange()
+    }
+  },
+  created () {
+    // this.doSearch()
+  },
+  methods: {
+    dateFormatter,
+    onFilterChange: debounce(function () {
+      this.doSearch()
+    }, 500),
+    recoverFilters (n) {
+      this.resetFilters()
+      Object.assign(this.filters, n)
+    },
+    resetFilters () {
+      Object.assign(this.filters, this.$options.data().filters)
+    },
+    resetListState () {
+      const r = this.$options.data().listState
+      delete r.list
+      Object.assign(this.listState, r)
+    },
+    doSearch () {
+      this.resetListState()
+      this.getList()
+    },
+    onSizeChange (p) {
+      this.listState.pageSize = p
+      this.getList()
+    },
+    onCurrentChange (p) {
+      this.listState.pageNum = p
+      this.getList()
+    },
+    getFilters () {
+      const { selectTime, selectTimeExtra } = this.reportFilters
+      const { area, buyerClass, industry, winner, buyerName, sort } = this.filters
+      let { subscribe } = this.filters
+
+      if (Array.isArray(subscribe) && subscribe.length === 0) {
+        subscribe = this.subscribeClassList
+      }
+
+      return {
+        rangeTime: selectTime,
+        rangeTimeExtra: selectTimeExtra,
+        keysItems: JSON.stringify(subscribe),
+        sort,
+        area: JSON.stringify(area),
+        buyerclass: buyerClass.join(','),
+        industry: JSON.stringify(industry),
+        buyer: buyerName,
+        winner
+      }
+    },
+    async getList () {
+      const filters = this.getFilters()
+      const params = {
+        pageNum: this.listState.pageNum,
+        pageSize: this.listState.pageSize,
+        ...filters
+      }
+      try {
+        this.listState.loading = true
+        // const { listShowMaxCount } = this.conf
+        const { data, error_code: code } = await getMarketAnalysisReportProjectList(params)
+        if (code === 0 && data && Array.isArray(data.list)) {
+          this.listState.list = data.list.map(item => {
+            // 整理标签
+            item.tagList = [
+              {
+                value: item.area === '其它' ? '' : item.area
+              },
+              {
+                value: item?.bidStatus === '其它' ? '' : item?.bidStatus
+              },
+              {
+                value: item?.buyerClass === '其它' ? '' : item?.buyerClass
+              },
+              {
+                // 有中标金额取中标金额,没有取预算,预算没有置空
+                value: formatMoney(item?.bidAmount || item?.budget)
+              }
+            ].filter(v => v.value)
+            // 整理中标企业
+            if (Array.isArray(item.winner)) {
+              const winners = item.winner.map((w, index) => {
+                let id = null
+                if (Array.isArray(item.winnerId)) {
+                  id = item.winnerId[index]
+                }
+                return {
+                  name: w,
+                  id
+                }
+              })
+              item.winners = winners.filter(w => w.name)
+            }
+            return item
+          })
+          this.listState.total = data.total
+        } else {
+          this.listState.list = []
+          this.listState.total = 0
+        }
+      } catch (error) {
+        console.log(error)
+      } finally {
+        this.listState.loading = false
+        this.listState.loaded = true
+      }
+    },
+    toProjectDetail (item) {
+      if (item.id) {
+        window.open(`/swordfish/page_big_pc/pro_follow_detail?sid=${item.id}`)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.pd-lr {
+  padding-left: 40px;
+  padding-right: 40px;
+}
+.project-details {
+  padding: 32px 0;
+  background-color: #fff;
+  &-header {
+    margin-bottom: 46px;
+  }
+}
+.project-details-actions {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 40px;
+  margin-bottom: 16px;
+}
+.project-details-actions-right {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  line-height: 22px;
+}
+.p-d-actions-label {
+  margin-right: 8px;
+}
+.project-details-list {
+  min-height: 420px;
+}
+
+.filter-list {
+  display: flex;
+  align-items: center;
+  .filter-item-label {
+    margin-right: 8px;
+    font-size: 14px;
+    line-height: 22px;
+  }
+  &.line-1 {
+    .filter-item {
+      &:not(:last-of-type) {
+        margin-right: 8px;
+      }
+    }
+  }
+  &.line-2 {
+    margin-top: 16px;
+    .filter-item {
+      &:not(:last-of-type) {
+        margin-right: 32px;
+      }
+    }
+  }
+}
+.project-details-footer {
+  text-align: right;
+  .bottom-tip-text {
+    margin-top: 8px;
+    padding: 0 5px;
+    font-size: 12px;
+    line-height: 18px;
+    color: #9B9CA3;
+  }
+}
+.sort-group {
+  ::v-deep {
+    .el-radio-button__inner {
+      padding: 8px 10px;
+      font-size: 14px;
+      width: 160px;
+    }
+  }
+}
+::v-deep {
+  .el-radio-button__inner {
+    color: #1d1d1d;
+  }
+}
+</style>

+ 5 - 0
src/views/analysisReport/index.vue

@@ -134,6 +134,11 @@ export default {
   .analysis-report {
     .report-content {
       background-color: #fff;
+      ::v-deep {
+        > .el-tabs__content {
+          overflow: unset;
+        }
+      }
     }
   }
 </style>

+ 8 - 0
src/views/reportData/components/AssociationInput.vue

@@ -22,6 +22,11 @@ export default {
     AssociationInput
   },
   props: {
+    // 是否开启联想
+    association: {
+      type: Boolean,
+      default: false
+    },
     value: {
       type: String,
       default: ''
@@ -40,6 +45,9 @@ export default {
   },
   methods: {
     async fetchListAsync ({ value }) {
+      if (!this.association) {
+        return []
+      }
       const params = {
         name: value.trim()
       }

+ 25 - 11
src/views/reportData/components/ProjectDetailsList.vue

@@ -51,13 +51,16 @@
           <template v-if="searchTotal > 0">共 <span class="highlight-text">{{ listState.total }}</span> 个项目</template>
         </div>
         <div class="project-details-actions-right no-select">
-          <el-radio-group class="sort-group" v-model="filters.sort" size="small">
-            <el-radio-button
-              v-for="item in conf.sortList"
-              :label="item.value"
-              :key="item.value"
-            >{{ item.name }}</el-radio-button>
-          </el-radio-group>
+          <template v-if="listState.loaded && listState.list.length">
+            <span class="p-d-actions-label">排序:</span>
+            <el-radio-group class="sort-group" v-model="filters.sort" size="small">
+              <el-radio-button
+                v-for="item in conf.sortList"
+                :label="item.value"
+                :key="item.value"
+              >{{ item.name }}</el-radio-button>
+            </el-radio-group>
+          </template>
         </div>
       </div>
       <div class="project-details-list" v-loading="listState.loading">
@@ -113,7 +116,7 @@ import { formatMoney } from '@/utils/'
 import { debounce } from 'lodash'
 
 export default {
-  name: 'ProjectDetails',
+  name: 'ProjectDetailListForWeekMonthReport',
   components: {
     [Input.name]: Input,
     [RadioGroup.name]: RadioGroup,
@@ -225,8 +228,10 @@ export default {
     initFilters: {
       immediate: true,
       handler  (n) {
-        this.recoverFilters(n)
-        this.onFilterChange()
+        this.$nextTick(() => {
+          this.recoverFilters(n)
+          this.onFilterChange()
+        })
       }
     },
     'filters.winner': function () {
@@ -264,7 +269,7 @@ export default {
     },
     onSizeChange (p) {
       this.listState.pageSize = p
-      this.doSearch()
+      this.getList()
     },
     onCurrentChange (p) {
       this.listState.pageNum = p
@@ -374,6 +379,15 @@ export default {
   padding: 0 40px;
   margin-bottom: 16px;
 }
+.project-details-actions-right {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  line-height: 22px;
+}
+.p-d-actions-label {
+  margin-right: 8px;
+}
 .project-details-list {
   min-height: 420px;
 }

+ 14 - 5
src/views/reportData/components/PurchaseRankingDialog.vue

@@ -10,7 +10,6 @@
     width="30%">
     <el-table :data="[]" border header-row-class-name="bg-header" class="header-table">
       <el-table-column
-        prop="name"
         label="排名"
         width="50"
         align="center"
@@ -33,7 +32,6 @@
     <div class="self-scroll-table">
       <el-table :data="dataList" border header-row-class-name="bg-header" :show-header="false">
         <el-table-column
-          prop="name"
           label="排名"
           width="50"
           align="center"
@@ -46,8 +44,11 @@
           prop="name"
           label="采购行业"
           align="center"
-          :resizable="false"
-        >
+          :resizable="false">
+          <template slot-scope="scope">
+            <el-link v-if="nameClickable" :underline="false" @click="clickBuyerClass(scope.row)">{{ scope.row.name || '--' }}</el-link>
+            <template v-else>{{ scope.row.name || '--' }}</template>
+          </template>
         </el-table-column>
         <el-table-column
           prop="count"
@@ -64,16 +65,21 @@
 </template>
 
 <script>
-import { Dialog, Table, TableColumn } from 'element-ui'
+import { Dialog, Table, TableColumn, Link } from 'element-ui'
 
 export default {
   name: 'PurchaseRankingDialog',
   components: {
+    [Link.name]: Link,
     [Dialog.name]: Dialog,
     [Table.name]: Table,
     [TableColumn.name]: TableColumn
   },
   props: {
+    nameClickable: {
+      type: Boolean,
+      default: false
+    },
     options: {
       type: Object,
       default: () => {
@@ -98,6 +104,9 @@ export default {
     this.otherData = this.options.data.filter(item => item?.name === '其它') || []
   },
   methods: {
+    clickBuyerClass (e) {
+      this.$emit('clickName', e)
+    },
     update (value) {
       this.$emit('update:visible', value)
     }

+ 18 - 1
src/views/reportData/pageMonth.vue

@@ -192,7 +192,12 @@
         </el-tabs>
       </div>
     </div>
-    <purchase-ranking-dialog v-if="rankingDialogOptions.visible" :options.sync="rankingDialogOptions"></purchase-ranking-dialog>
+    <purchase-ranking-dialog
+      v-if="rankingDialogOptions.visible"
+      :options.sync="rankingDialogOptions"
+      :nameClickable="useNewVersionReport"
+      @clickName="clickRankingDialogName"
+    ></purchase-ranking-dialog>
   </Layout>
 </template>
 
@@ -978,6 +983,13 @@ export default {
         window.open(`/swordfish/page_big_pc${goLink}`)
       }
     },
+    clickRankingDialogName (e) {
+      const { title } = this.rankingDialogOptions
+      if (title === '本月项目数量采购行业排行榜' || title === '本月项目规模采购行业排行榜') {
+        this.recoverBuyerClassFilter(e, title)
+        this.rankingDialogOptions.visible = false
+      }
+    },
     recoverKeyWordFilter (e, type) {
       const filters = {}
       if (type === '本月项目数量' || type === '本月项目规模') {
@@ -994,6 +1006,8 @@ export default {
       const filters = {}
       if (type === '本月项目数量采购行业排行榜' || type === '本月项目规模采购行业排行榜') {
         filters.buyerClass = [e.name]
+      } else {
+        filters.buyerClass = [e]
       }
       this.toProjectDetail(filters)
     },
@@ -1177,6 +1191,9 @@ export default {
     margin-top: 24px;
     background-color: #fff;
     ::v-deep {
+      .el-tabs__content {
+        overflow: unset;
+      }
       .fixed-nav {
         width: 920px;
         box-shadow: inset 0 -1px 0 0 rgb(0, 0, 0, 0.05);

+ 3 - 0
src/views/reportData/pageWeek.vue

@@ -734,6 +734,9 @@ export default {
     margin-top: 24px;
     background-color: #fff;
     ::v-deep {
+      .el-tabs__content {
+        overflow: unset;
+      }
       .fixed-nav {
         width: 920px;
         box-shadow: inset 0 -1px 0 0 rgb(0, 0, 0, 0.05);