瀏覽代碼

Merge branch 'master' into dev1.3

luwenna 3 年之前
父節點
當前提交
6b9e73ac3a
共有 48 個文件被更改,包括 5148 次插入284 次删除
  1. 0 2
      README.md
  2. 6 0
      src/App.vue
  3. 30 0
      src/api/modules/report.js
  4. 二進制
      src/assets/images/icon/report-active.png
  5. 9 1
      src/assets/style/reset-ele.scss
  6. 2 4
      src/components/chart/BarLineChart.vue
  7. 16 2
      src/components/chart/LineChart.vue
  8. 6 5
      src/components/chart/ProgressChart.vue
  9. 124 0
      src/components/chart/RectTreeMapChart.vue
  10. 200 0
      src/components/chart/SimpleHistogramChart.vue
  11. 2 1
      src/components/common/Empty.vue
  12. 329 0
      src/components/report-data/ReportList.vue
  13. 10 0
      src/components/selector/IndustrySelectorContent.vue
  14. 279 28
      src/components/selector/PopSelector.vue
  15. 3 3
      src/components/selector/SelectorCard.vue
  16. 3 1
      src/components/selector/TimeSelector.vue
  17. 139 37
      src/components/selector/TimeSelectorContent.vue
  18. 41 6
      src/components/work-desktop/Slidebar.vue
  19. 3 1
      src/main.js
  20. 0 1
      src/router/router-interceptors.js
  21. 22 3
      src/router/routers.js
  22. 1 1
      src/utils/bigmember/powerMap.js
  23. 13 0
      src/utils/common.js
  24. 33 0
      src/utils/globalDirectives.js
  25. 17 1
      src/utils/globalFunctions.js
  26. 490 0
      src/views/analysisReport/MarketAnalysis.vue
  27. 1790 0
      src/views/analysisReport/MarketAnalysisResult.vue
  28. 64 0
      src/views/analysisReport/components/BidderScaleScatter.vue
  29. 46 0
      src/views/analysisReport/components/BuyerScaleScatter.vue
  30. 84 0
      src/views/analysisReport/components/MarketAreaScatter.vue
  31. 93 0
      src/views/analysisReport/components/MarketLineChart.vue
  32. 137 0
      src/views/analysisReport/components/MarketOverview.vue
  33. 147 0
      src/views/analysisReport/components/MarketSegment.vue
  34. 115 0
      src/views/analysisReport/components/MarketTimeScatter.vue
  35. 155 0
      src/views/analysisReport/components/MarketTop3Table.vue
  36. 55 0
      src/views/analysisReport/components/MarketUserScatter.vue
  37. 162 0
      src/views/analysisReport/components/ProjectScatter.vue
  38. 127 0
      src/views/analysisReport/index.vue
  39. 2 5
      src/views/bid-policy/components/PolicyLimit.vue
  40. 56 16
      src/views/portrayal/EntSearchPortrayal.vue
  41. 45 10
      src/views/portrayal/UnitPortrayal.vue
  42. 5 1
      src/views/portrayal/components/FreeExpBanner.vue
  43. 11 0
      src/views/project/ProjectInfo.vue
  44. 67 1
      src/views/public/Buy.vue
  45. 12 1
      src/views/subscribe/Config.vue
  46. 103 138
      src/views/work-desktop/WorkDesktop.vue
  47. 60 0
      src/views/work-desktop/index.vue
  48. 34 15
      vue.config.js

+ 0 - 2
README.md

@@ -1,5 +1,3 @@
-v1.3.1
-附件下载包
 ## 剑鱼大会员PC端
 v1.3
 广东移动单位画像

+ 6 - 0
src/App.vue

@@ -34,4 +34,10 @@ export default {
 .big-member-page .el-loading-mask {
   z-index: 98;
 }
+.fixed-nav {
+  width: 100%;
+  position: fixed;
+  top: 63px;
+  z-index: 99;
+}
 </style>

+ 30 - 0
src/api/modules/report.js

@@ -30,3 +30,33 @@ export function getReportStartTime (data) {
     data: data
   })
 }
+
+// 获取定制化分析报告历史记录
+export function getReportHistoryList (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/marketAnalysis/analysisHistory',
+    method: 'post',
+    data: data
+  })
+}
+
+// 定制化报告开始分析
+export function doReportAnalysis (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/marketAnalysis/doAnalysis',
+    method: 'post',
+    data: data
+  })
+}
+
+// 定制化报告闲情
+export function getReportAnalysisInfo (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/marketAnalysis/getAnalysisResult',
+    method: 'post',
+    data: data
+  })
+}

二進制
src/assets/images/icon/report-active.png


+ 9 - 1
src/assets/style/reset-ele.scss

@@ -51,9 +51,17 @@
       color: #fff;
     }
   }
+
+  .el-link {
+    &.el-link--default{
+      &:hover {
+        color: $color-text--highlight;
+      }
+    }
+  }
 }
 .el-popper {
   li{
     float: none;
   }
-}
+}

+ 2 - 4
src/components/chart/BarLineChart.vue

@@ -2,6 +2,7 @@
   <ve-histogram
     :id="id"
     :height="options.height"
+    :width="options.width ? options.width : null"
     :colors="options.colors"
     :data="datas"
     :settings="options.settings"
@@ -127,15 +128,12 @@ export default {
       }
     }
   },
-  computed: {},
-  watch: {},
-  mounted () {},
   methods: {
     extend (chart) {
       chart.setOption({
         series: [{
           type: 'bar',
-          barWidth: 20
+          barMaxWidth: 20
         }, {
           type: 'line',
           smooth: false,

+ 16 - 2
src/components/chart/LineChart.vue

@@ -3,6 +3,8 @@
 </template>
 <script>
 import VeLine from 'v-charts/lib/line'
+import _ from 'lodash'
+
 export default {
   name: 'line-chart',
   props: {
@@ -13,6 +15,12 @@ export default {
       height: String,
       colors: Array || Object,
       config: Function
+    },
+    extend: {
+      type: Object,
+      default () {
+        return {}
+      }
     }
   },
   components: {
@@ -119,8 +127,14 @@ export default {
       deep: true
     }
   },
-  mounted () {},
-  methods: {}
+  mounted () {
+    this.mergeExtendOptions()
+  },
+  methods: {
+    mergeExtendOptions () {
+      _.merge(this.defaultOption, this.extend)
+    }
+  }
 }
 </script>
 <style lang="scss" scoped>

+ 6 - 5
src/components/chart/ProgressChart.vue

@@ -73,11 +73,12 @@ export default {
       window.open('/swordfish/page_big_pc/unit_portrayal/' + encodeURIComponent(name))
     },
     goEnt (id) {
-      if (this.info.power.indexOf(5) > -1) {
-        window.open('/swordfish/page_big_pc/ent_portrait/' + id)
-      } else {
-        window.open('/swordfish/page_big_pc/svip/ent_ser_portrait/' + id)
-      }
+      // if (this.info.power.indexOf(5) > -1) {
+      //   window.open('/swordfish/page_big_pc/ent_portrait/' + id)
+      // } else {
+      //   window.open('/swordfish/page_big_pc/svip/ent_ser_portrait/' + id)
+      // }
+      window.open('/swordfish/page_big_pc/svip/ent_ser_portrait/' + id)
     }
   }
 }

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

@@ -0,0 +1,124 @@
+<template>
+  <div class="rect-tree-map-chart" style="min-height: 400px"></div>
+</template>
+<script>
+import _ from 'lodash'
+
+export default {
+  name: 'rect-tree-map-chart',
+  props: {
+    datas: {
+      type: Array,
+      default: () => []
+    },
+    options: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data () {
+    return {
+      myChart: null,
+      height: '500px',
+      defaultOptions: {
+        legend: {
+          show: true,
+          selectedMode: 'single',
+          data: [],
+          bottom: 0,
+          itemGap: 5
+        },
+        tooltip: {
+          backgroundColor: '#fff',
+          confine: true,
+          textStyle: {
+            color: '#171826',
+            fontSize: 12
+          },
+          padding: [7, 12],
+          extraCssText: 'box-shadow: 0px 4px 16px rgba(8, 31, 38, 0.08)',
+          borderWidth: 2,
+          borderColor: '#F5F6F7'
+        },
+        series: [
+          {
+            type: 'treemap',
+            width: '100%',
+            // height: '85%',
+            // top: '15%',
+            roam: false, // 是否开启拖拽漫游(移动和缩放)
+            nodeClick: false, // 点击节点后的行为,false无反应
+            breadcrumb: {
+              show: false
+            },
+            label: { // 描述了每个矩形中,文本标签的样式。
+              normal: {
+                show: true,
+                position: ['10%', '40%']
+              }
+            },
+            itemStyle: {
+              normal: {
+                show: true,
+                textStyle: {
+                  color: '#fff',
+                  fontSize: 16
+                },
+                borderWidth: 1,
+                borderColor: '#fff'
+              },
+              emphasis: {
+                label: {
+                  show: true
+                }
+              }
+            },
+            data: [
+              // {
+              //   name: 'xxx',
+              //   value: 122
+              // },
+              // {
+              //   name: 'yyy',
+              //   value: 122
+              // }
+            ]
+          }
+        ]
+      }
+    }
+  },
+  watch: {},
+  mounted () {
+    this.updateChartView()
+  },
+  methods: {
+    updateChartView () {
+      this.myChart = this.$echarts.init(this.$el, 'light')
+      if (!this.myChart) return
+      this.mergeOptions()
+      this.myChart.setOption(this.defaultOptions)
+      window.addEventListener('resize', () => {
+        // this.myChart.resize()
+      })
+    },
+    mergeOptions () {
+      // 设置数据
+      this.defaultOptions.series[0].data = this.datas
+      // 设置图例?
+      this.defaultOptions.legend.data = this.datas.map(item => {
+        return item.name
+      })
+
+      _.merge(this.defaultOptions, this.options)
+    }
+  },
+  beforeDestroy () {
+    if (this.myChart) {
+      this.myChart.dispose()
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+</style>

+ 200 - 0
src/components/chart/SimpleHistogramChart.vue

@@ -0,0 +1,200 @@
+<template>
+  <ve-histogram
+    :id="id"
+    :colors="options.colors"
+    :width="options.width"
+    :height="options.height"
+    :data="data"
+    :settings="options.settings"
+    :after-config="options.config"
+    :after-set-option="extend"
+    :extend="defaultOptions"
+    >
+  </ve-histogram>
+</template>
+<script>
+import VeHistogram from 'v-charts/lib/histogram'
+export default {
+  name: 'simple-histogram-chart',
+  components: {
+    VeHistogram
+  },
+  props: {
+    id: {
+      type: String,
+      default: 've-histogram'
+    },
+    data: {
+      type: Object,
+      default () {
+        return {
+          // columns: ['项目规模', '项目总金额占比', '项目总数占比'],
+          // rows: [
+          //   {
+          //     项目规模: '≥1亿',
+          //     项目总金额占比: 20,
+          //     项目总数占比: 10
+          //   },
+          //   {
+          //     项目规模: '1000万-1亿',
+          //     项目总金额占比: 50,
+          //     项目总数占比: 40
+          //   },
+          //   {
+          //     项目规模: '500万-1000万',
+          //     项目总金额占比: 20,
+          //     项目总数占比: 30
+          //   },
+          //   {
+          //     项目规模: '100万-500万',
+          //     项目总金额占比: 20,
+          //     项目总数占比: 30
+          //   }
+          // ]
+        }
+      }
+    }
+  },
+  data () {
+    return {
+      options: {
+        colors: ['#FF9F3F', '#05A5F2'],
+        width: '100%',
+        height: '300px',
+        settings: {}
+      },
+      defaultOptions: {
+        grid: {
+          top: 20
+        },
+        xAxis: {
+          axisLabel: {
+            margin: 10,
+            interval: 0, // 强制显示x轴所有刻度
+            fontSize: 12
+          },
+          nameLocation: 'start',
+          nameTextStyle: {
+            fontSize: 12,
+            align: 'left',
+            padding: [70, 0, 0, 50],
+            color: '#9B9CA3'
+          }
+        },
+        yAxis (item) {
+          Object.assign(item[0], {
+            splitLine: {
+              lineStyle: {
+                type: 'dashed',
+                width: 0.5
+              }
+            },
+            axisLabel: {
+              margin: 2,
+              fontSize: 12,
+              formatter: (value, index) => value.toString().replace(/,/, '') + '%'
+            }
+          })
+
+          Object.assign(item[1], {
+            splitLine: {
+              show: false
+            },
+            axisLabel: {
+              show: false,
+              fontSize: 12,
+              formatter: (value, index) => value.toString().replace(/,/, '')
+            }
+          })
+          return item
+        },
+        tooltip: {
+          trigger: 'axis',
+          confine: true,
+          backgroundColor: '#fff',
+          axisPointer: {
+            type: 'shadow',
+            shadowStyle: {
+              color: 'rgba(42, 190, 209,0.1)'
+            },
+            z: 3
+          },
+          textStyle: {
+            color: '#171826',
+            fontSize: 12
+          },
+          padding: [7, 12],
+          extraCssText: 'box-shadow: 0px 4px 16px rgba(8, 31, 38, 0.08);transform: translate3d(0,0,0)',
+          formatter: this.tooltipFormatter
+        },
+        legend: {
+          orient: 'horizontal',
+          icon: 'rect',
+          bottom: 20,
+          align: 'left',
+          itemGap: 20,
+          itemWidth: 9,
+          itemHeight: 9,
+          textStyle: {
+            fontSize: 10,
+            lineHeight: 14,
+            verticalAlign: 'bottom',
+            rich: {
+              a: {
+                fontSize: 16,
+                verticalAlign: 'top',
+                align: 'center',
+                padding: [0, 15, 28, 0]
+              },
+              b: {
+                fontSize: 14,
+                align: 'center',
+                padding: [0, 15, 0, 0],
+                lineHeight: 25
+              }
+            }
+          },
+          formatter: name => name
+        }
+      }
+    }
+  },
+  computed: {},
+  watch: {},
+  mounted () {},
+  methods: {
+    extend (chart) {
+      chart.setOption({
+        series: [
+          {
+            type: 'bar',
+            barMaxWidth: 20
+          },
+          {
+            type: 'bar',
+            barMaxWidth: 20
+          }
+        ]
+      })
+    },
+    tooltipFormatter (params) {
+      let tip = `<div style="padding-top:2px;color:#9B9CA3;">${params[0].name}</div>`
+      for (let i = 0; i < params.length; i++) {
+        if (params[i].value === undefined || params[i].value === '') {
+          params[i].value = 0
+        }
+        if (i === 0) {
+          tip = tip + params[i].marker + params[i].seriesName + ':' + params[i].value + '%<br/>'
+        } else if (i === 1) {
+          tip = tip + params[i].marker + params[i].seriesName + ':' + params[i].value + '%<br/>'
+        }
+      }
+      return tip
+    }
+  },
+  beforeDestroy () {}
+}
+</script>
+<style lang="scss" scoped>
+
+</style>

+ 2 - 1
src/components/common/Empty.vue

@@ -25,7 +25,8 @@ export default {
     images: {
       type: String,
       default () {
-        return require('@/assets/images/empty.png')
+        // return require('@/assets/images/empty.png')
+        return require('@/assets/images/empty/jy-back.png')
       }
     }
   }

+ 329 - 0
src/components/report-data/ReportList.vue

@@ -0,0 +1,329 @@
+<template>
+  <div class="info-list-group">
+    <div class="info-list" v-loading="listState.loading">
+      <el-table
+        v-show="!showEmpty"
+        :data="listState.list"
+        style="width: 960px;margin: 0 auto;">
+        <el-table-column
+          label="报告生成时间"
+          width="170">
+          <template slot-scope="scope">
+            <span class="max-line-3">{{(scope.row.createTime * 1000)|formatTime('yyyy.MM.dd HH:mm:ss')}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="分析内容">
+          <template slot-scope="scope">
+            <el-tooltip class="item" effect="dark" :content="scope.row.keysItems|FormatKeys">
+              <span class="max-line-3">{{scope.row.keysItems|FormatKeys}}</span>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <el-table-column
+          width="180"
+          label="分析时间">
+          <template slot-scope="scope">
+            <span class="max-line-3">{{scope.row.rangeTimeStart|formatTime('yyyy.MM.dd')}}-{{scope.row.rangeTimeEnd|formatTime('yyyy.MM.dd')}}</span>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="分析区域">
+          <template slot-scope="scope">
+            <el-tooltip class="item" effect="dark">
+              <span slot="content">{{scope.row.area|formatArea}}</span>
+              <span class="max-line-3">{{scope.row.area|formatArea}}</span>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="分析行业">
+          <template slot-scope="scope">
+            <el-tooltip class="item" effect="dark" :content="scope.row.industry | formatIndustry">
+              <span class="max-line-3">{{ scope.row.industry | formatIndustry }}</span>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <el-table-column
+          label="采购单位类型">
+          <template slot-scope="scope">
+            <el-tooltip class="item" effect="dark" :disabled="!scope.row.buyerclass" :content="scope.row.buyerclass | formatBuyerclass">
+              <span class="max-line-3">{{scope.row.buyerclass | formatBuyerclass}}</span>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <el-table-column
+          width="80"
+          label="操作">
+          <template slot-scope="scope">
+            <span class="go-info-text" @click="goReport(scope.row.id)" >查看报告</span>
+          </template>
+        </el-table-column>
+      </el-table>
+      <empty v-show="showEmpty">
+        <div class="flex-c-c center">
+          <span>暂无数据</span>
+        </div>
+      </empty>
+    </div>
+    <div class="el-pagination-container"  v-if="(listState.total > listState.pageSize)">
+      <el-pagination
+        background
+        layout="prev, pager, next, ->"
+        :hide-on-single-page="true"
+        :current-page="listState.pageNum"
+        :page-size="listState.pageSize"
+        :total="listState.total"
+        @current-change="onPageChange"
+      >
+      </el-pagination>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Pagination, Card, Button, Table, TableColumn, Tooltip } from 'element-ui'
+import Empty from '@/components/common/Empty.vue'
+import { dateFromNow } from '@/utils/'
+import { getReportHistoryList } from '@/api/modules/'
+
+export default {
+  name: 'report-list',
+  components: {
+    [Pagination.name]: Pagination,
+    [Card.name]: Card,
+    [Button.name]: Button,
+    [Table.name]: Table,
+    [TableColumn.name]: TableColumn,
+    [Tooltip.name]: Tooltip,
+    Empty
+  },
+  filters: {
+    formatTime: function (value, fmt) {
+      if (!value) return ''
+      return new Date(value).pattern(fmt)
+    },
+    formatBuyerclass: function (value) {
+      if (!value) return '全部'
+      return String(value).replace(/,/g, ',')
+    },
+    formatArea: function (value) {
+      if (!value) return '-'
+      value = JSON.parse(value)
+
+      if (Object.keys(value).length === 0) {
+        return '全国'
+      }
+
+      const area = []
+      const citys = []
+      for (const key in value) {
+        if (!value[key].length) {
+          area.push(key)
+        } else {
+          citys.push(...value[key])
+        }
+      }
+
+      const concatList = area.concat(citys)
+
+      if (concatList.length) {
+        return concatList.join(',')
+      } else {
+        return '-'
+      }
+    },
+    formatIndustry: function (value) {
+      if (!value) return '-'
+      value = JSON.parse(value)
+
+      if (Object.keys(value).length === 0) {
+        return '全行业'
+      }
+
+      const keyArr = []
+      const valueArr = []
+      for (const key in value) {
+        if (!value[key].length) {
+          keyArr.push(key)
+        } else {
+          valueArr.push(...value[key])
+        }
+      }
+
+      return valueArr.join(',')
+    },
+    FormatKeys: function (keys) {
+      var tempStr = '-'
+      try {
+        var tempResult = []
+        var tempList = JSON.parse(keys)
+        tempList.forEach(function (v) {
+          v.a_key.forEach(function (k) {
+            tempResult.push({
+              key: [].concat(k.key, k.appendkey),
+              type: k.matchway === 1 ? '精准' : '模糊'
+            })
+          })
+        })
+        tempStr = tempResult.map(function (v) {
+          return v.key.join(' ') + '(' + v.type + ')'
+        }).join(',')
+      } catch (e) {
+
+      }
+      return tempStr
+    }
+  },
+  computed: {
+    showEmpty () {
+      return this.listState.list.length === 0 && this.listState.loaded
+    }
+  },
+  data () {
+    return {
+      listState: {
+        loaded: true, // 是否已经搜索过
+        loading: false,
+        pageNum: 1, // 当前页
+        pageSize: 20, // 每页多少条数据
+        total: 0, // 一共多少条数据
+        list: [] // 查询请求返回的数据
+      }
+    }
+  },
+  created () {
+    this.doQuery()
+  },
+  methods: {
+    dateFromNow,
+    goReport (id) {
+      const routeUrl = this.$router.resolve({
+        path: '/desktop/report_analysis',
+        query: {
+          id: decodeURIComponent(id)
+        }
+      })
+      window.open(routeUrl.href)
+    },
+    // 恢复数据至第一次请求的状态(页码等)
+    resetListState () {
+      const state = {
+        loaded: false,
+        loading: false,
+        pageNum: 1,
+        total: 0,
+        list: []
+      }
+      Object.assign(this.listState, state)
+    },
+    doQuery (filters) {
+      this.resetListState()
+      this.getList(filters)
+    },
+    async getList (filters) {
+      const query = {
+        pageNum: this.listState.pageNum,
+        pageSize: this.listState.pageSize
+      }
+
+      if (filters && Object.keys(filters).length > 0) {
+        Object.keys(filters).forEach(v => {
+          if (typeof filters[v] !== 'undefined') {
+            query[v] = filters[v]
+          }
+        })
+      }
+
+      this.listState.loading = true
+      this.listState.loaded = false
+
+      try {
+        const { data, error_code: code } = await getReportHistoryList(query)
+        this.listState.loading = false
+        this.listState.loaded = true
+
+        if (code === 0 && data) {
+          if (data.total !== -1) {
+            this.listState.total = data.total
+          }
+          const list = data.list.map(function (v) {
+            if (v.rangeTime) {
+              const rangeTimeArr = v.rangeTime.split('-')
+              v.rangeTimeStart = new Date(rangeTimeArr[0] * 1000).getTime()
+              v.rangeTimeEnd = new Date(rangeTimeArr[1] * 1000).getTime()
+            }
+            return v
+          })
+          this.listState.list = list || []
+        } else {
+          throw new Error(code)
+        }
+      } catch (error) {
+        console.log(error)
+        this.listState.loading = false
+        this.listState.loaded = true
+      }
+    },
+    onPageChange (p) {
+      this.listState.pageNum = p
+      this.getList()
+    }
+  }
+}
+</script>
+<style>
+.el-tooltip__popper.is-dark{
+  max-width: 300px;
+  line-height: 18px;
+}
+</style>
+<style lang="scss" scoped>
+  .info-list-group {
+    width: 1000px;
+    margin: 0 auto;
+    margin-top: 24px;
+    padding: 0 20px;
+    box-sizing: border-box;
+    background: #fff;
+    box-shadow: 0px 0px 18px 1px rgba(0, 0, 0, 0.019);
+    border-radius: 4px;
+    ::v-deep {
+      .el-table .cell {
+        font-size: 14px;
+        font-weight: 400;
+        color: #1D1D1D;
+        line-height: 22px;
+        text-align: center;
+        border-bottom-color: rgba(255, 255, 255, 0.5);
+      }
+      .el-table th.is-leaf {
+        line-height: 18px;
+        background: #F7F9FC;
+        text-align: center;
+        border-bottom-color: transparent;
+      }
+    }
+    .go-info-text {
+      font-size: 14px;
+      font-weight: 400;
+      color: #2CB7CA;
+      line-height: 22px;
+      cursor: pointer;
+    }
+    .max-line-3 {
+      display: -webkit-box;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      -webkit-line-clamp: 3;
+      -webkit-box-orient: vertical;
+    }
+    .el-pagination-container.center  {
+      .el-pagination {
+        left: 50%;
+        right: unset;
+        transform: translateX(-50%);
+      }
+    }
+  }
+</style>

+ 10 - 0
src/components/selector/IndustrySelectorContent.vue

@@ -302,6 +302,9 @@ export default {
      * @param { Array | undefined } data 要恢复的数据
      */
     setIndustryState (data) {
+      if (Array.isArray(data)) {
+        return this.setBuyerclassState(data)
+      }
       // 设置全部按钮
       if (!data || Object.keys(data).length === 0) {
         // 其他全部设置不选中,全部按钮设置选中
@@ -332,6 +335,13 @@ export default {
         })
       }
     },
+    setBuyerclassState (data) {
+      this.getIndustryListMap.forEach(item => {
+        if (data.includes(item.name)) {
+          this.changeIndustryState(item)
+        }
+      })
+    },
     // 获取选中的数据
     getSelected () {
       const map = {}

+ 279 - 28
src/components/selector/PopSelector.vue

@@ -1,17 +1,41 @@
 <template>
   <selector-card
     class="pop-selector"
-    :cardType="selectorType"
-    @onConfirm="onConfirm"
-    @onCancel="onCancel">
+    :cardType="selectorType">
     <div slot="header" :class="{ 's-header': selectorType === 'line' }">
       <slot name="header">关键词</slot>
     </div>
-    <div class="p-selector--group">
+    <div class="p-selector--group" :class="{ 'show-key-info': showMoreKeyInfoWithCard }">
       <div class="p-selector--item all" :class="{ checked: !noSelectTop }" @click="selectAllForTop">全部</div>
-      <div class="p-selector--item" :class="{'checked': selectInfo.originMap[item].selects.length, 'select': selectInfo.popStatus.show && item === selectInfo.popStatus.key}" @click="openChildren(item, $event)" v-for="(item, index) in getTopList" :key="index">{{item}}</div>
+      <div
+        class="p-selector--item"
+        :class="{
+          'checked': getSelectedKeysWithClassifyName(item.name).length,
+          'select': selectInfo.popStatus.show && item.name === selectInfo.popStatus.key,
+          'checked-dashed': getSelectedKeysWithClassifyName(item.name).length && getSelectedKeysWithClassifyName(item.name).length !== item.count
+        }"
+        @click="openChildren(item.name, $event)"
+        v-for="(item, index) in getTopList" :key="index">
+          <span class="classify-name">{{ item.name }}</span>
+          <span class="classify-checked-num" v-if="showMoreKeyInfoWithCard && getSelectedKeysWithClassifyName(item.name).length">{{ getSelectedKeysWithClassifyName(item.name).length }}/{{ item.count }}</span>
+        </div>
       <div class="p-selector--pop-group flex-c-c center" v-show="selectInfo.popStatus.show">
-        <div class="flex-r-c c-l">
+        <div class="flex-r-c center card-list" v-if="showMoreKeyInfoWithCard" key="key-detail">
+          <div class="p-selector--pop-item all" :class="{checked: selectInfo.popStatus.selectAllForPop}" @click="selectAllForPop">全部</div>
+          <div class="p-selector--pop-list flex flex-r-c">
+            <div class="p-selector--card-item" @click="toggleCheckStatus(keys)" v-for="(keys, i) in nowOpenData" :key="keys + i" :class="{ checked: keys.checked }">
+              <div class="tag" :class="keys.matchway === 1 ? 'tag-orange': 'tag-blue'">{{ keys.matchway === 1 ? '模糊': '精准' }}</div>
+              <div class="key-info">
+                <div class="key-text ellipsis">{{ keys.key }}</div>
+                <div class="notkey-text" v-if="keys.notkey">
+                  <span class="notkey-label">排除词:</span>
+                  <span class="ellipsis">{{ keys.notkey }}</span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="flex-r-c c-l" v-else key="key-detail">
           <div class="p-selector--pop-item all" :class="{checked: selectInfo.popStatus.selectAllForPop}" @click="selectAllForPop">全部</div>
           <div class="p-selector--pop-item" @click="toggleCheckStatus(keys)" v-for="(keys, i) in nowOpenData" :key="keys + i" :class="{ checked: keys.checked }">{{keys.key}}</div>
         </div>
@@ -36,8 +60,8 @@ export default {
       type: String,
       default: 'line' // card/line
     },
-    singleChoice: { // 是是否单选? 只有在selectorType=line下才会生效
-      // type: Boolean,
+    showMoreKeyInfoWithCard: { // 是否以卡片展示关键词项的详细内容
+      type: Boolean,
       default: false
     }
   },
@@ -61,18 +85,25 @@ export default {
   mounted () {},
   computed: {
     getTopList () {
-      return Object.keys(this.selectInfo.origin || {})
+      const cArr = []
+      for (const key in this.selectInfo.origin) {
+        cArr.push({
+          name: key,
+          count: this.selectInfo.origin[key].length
+        })
+      }
+      return cArr
     },
     getTopListLength () {
       const allSelectStatus = {}
-      this.getTopList.forEach(v => {
+      this.getTopList.forEach(({ name: v }) => {
         allSelectStatus[v] = this.selectInfo.originMap[v].selects.length
       })
       return allSelectStatus
     },
     getPopListLength () {
       const allSelectStatus = {}
-      this.getTopList.forEach(v => {
+      this.getTopList.forEach(({ name: v }) => {
         allSelectStatus[v] = this.selectInfo.popMap[v].filter(v => v.checked).length
       })
       return allSelectStatus
@@ -96,7 +127,19 @@ export default {
       }
       for (const k in dataMap) {
         tempData.origin[k] = dataMap[k]
-        tempData.popMap[k] = dataMap[k].map(v => ({ key: v, checked: false }))
+        tempData.popMap[k] = dataMap[k].map(v => {
+          if (typeof v === 'string') {
+            return {
+              key: v,
+              checked: false
+            }
+          } else {
+            return {
+              ...v,
+              checked: false
+            }
+          }
+        })
         tempData.originMap[k] = {
           select: false,
           selectAll: false,
@@ -106,9 +149,7 @@ export default {
       this.$set(this.selectInfo, 'origin', tempData.origin)
       this.$set(this.selectInfo, 'originMap', tempData.originMap)
       this.$set(this.selectInfo, 'popMap', tempData.popMap)
-      this.$nextTick(() => {
-        this.computRow()
-      })
+      this.computRow()
     },
     submitChange (type) {
       if (type) {
@@ -162,12 +203,14 @@ export default {
       }
     },
     computRow () {
-      $('.p-selector--group .p-selector--item').each(function () {
-        $(this).attr('data-row', parseInt($(this).position().top / 34))
+      this.$nextTick(() => {
+        $('.p-selector--group .p-selector--item').each(function () {
+          $(this).attr('data-row', parseInt($(this).position().top / 34))
+        })
       })
     },
     changePosition (e) {
-      const nowRow = $(e.target).attr('data-row')
+      const nowRow = $(e.currentTarget).attr('data-row')
       const tempDom = $(`.p-selector--group .p-selector--item[data-row='${parseInt(nowRow) + 1}']`)
       if (tempDom.length) {
         tempDom.eq(0).before($('.p-selector--pop-group'))
@@ -183,6 +226,52 @@ export default {
       this.selectInfo.popStatus.selectAllForPop = this.selectInfo.originMap[this.selectInfo.popStatus.key].selectAll
       this.recoverPopCache(false)
     },
+    getSelectedDetailList () {
+      const selectedArr = []
+      // [
+      //   {
+      //     s_item: '分类名',
+      //     a_key: [
+      //       {
+      //         key: ['1'],
+      //         notkey: [],
+      //         matchway: 1
+      //       },
+      //       {
+      //         key: ['2'],
+      //         notkey: [],
+      //         matchway: 1
+      //       }
+      //     ]
+      //   }
+      // ]
+      for (const cname in this.selectInfo.popMap) {
+        if (!Array.isArray(this.selectInfo.popMap[cname])) {
+          continue
+        }
+
+        const classify = {
+          s_item: cname,
+          a_key: []
+        }
+
+        const originItem = this.selectInfo.originMap[cname]
+        this.selectInfo.popMap[cname].forEach(key => {
+          if (key.checked || originItem.selectAll) {
+            classify.a_key.push({
+              key: key.key.split(' '),
+              notkey: key.notkey ? key.notkey.split(' ') : null,
+              matchway: key.matchway
+            })
+          }
+        })
+
+        if (classify.a_key.length) {
+          selectedArr.push(classify)
+        }
+      }
+      return selectedArr
+    },
     getSelected () {
       let tempStr = ''
       const tempData = this.getTopListLength
@@ -196,12 +285,58 @@ export default {
       }
       return tempStr
     },
-    onCancel () {
-      this.$emit('onCancel')
+    getSelectedKeysWithClassifyName (name) {
+      return this.selectInfo.originMap[name].selects || []
     },
-    onConfirm () {
-      const selectedCity = this.getSelectedCity()
-      this.$emit('onConfirm', selectedCity)
+    // 选中的key的字符串数组,key用空格拼接
+    setState (keysList) {
+      for (const cname in this.selectInfo.popMap) {
+        const keysArr = this.selectInfo.popMap[cname]
+        if (Array.isArray(keysArr)) {
+          if (keysArr.length === 0) {
+            continue
+          } else {
+            keysArr.forEach(key => {
+              key.checked = keysList.includes(key.key)
+            })
+          }
+        }
+      }
+      this.refreshOriginMap()
+    },
+    refreshOriginMap () {
+      const classifySelectedState = []
+      for (const cname in this.selectInfo.popMap) {
+        const keysArr = this.selectInfo.popMap[cname]
+        if (!Array.isArray(keysArr)) {
+          continue
+        }
+
+        const selectedKeysArr = []
+        if (keysArr.length === 0) {
+          continue
+        } else {
+          keysArr.forEach(key => {
+            if (key.checked) {
+              selectedKeysArr.push(key.key)
+            }
+          })
+        }
+
+        const selectedCount = selectedKeysArr.length
+        this.selectInfo.originMap[cname].select = selectedCount > 0
+        this.selectInfo.originMap[cname].selectAll = selectedCount > 0 && selectedCount === keysArr.length
+        if (this.selectInfo.originMap[cname].selectAll) {
+          keysArr.forEach(key => (key.checked = false))
+        }
+        this.selectInfo.originMap[cname].selects = selectedKeysArr
+
+        classifySelectedState.push(this.selectInfo.originMap[cname])
+      }
+
+      if (classifySelectedState.indexOf(false) === -1) {
+        this.selectAllForTop()
+      }
     },
     onChange (selected) {
       this.$emit('onChange', selected)
@@ -216,6 +351,7 @@ export default {
     display: flex;
     flex-direction: row;
     flex-wrap: wrap;
+    width: 100%;
   }
   &item {
     font-size: 14px;
@@ -237,6 +373,9 @@ export default {
     max-width: 14em;
     border: 1px solid transparent;
     cursor: pointer;
+    &.all {
+      margin-left: 0;
+    }
     &.checked {
       color: #fff;
       background: #2cb7ca;
@@ -260,6 +399,12 @@ export default {
         background: #f5f6f7;
       }
     }
+
+    .classify-checked-num {
+      margin-left: 4px;
+      font-size: 13px;
+      font-weight: normal;
+    }
   }
   &pop-group {
     background: #f5f6f7;
@@ -287,7 +432,7 @@ export default {
       font-weight: 400;
       line-height: 22px;
       border-radius: 2px;
-      margin-top: 20px;
+      margin-top: 18px;
       cursor: pointer;
       &.button-submit {
         background: #2cb7ca;
@@ -301,6 +446,75 @@ export default {
         }
       }
     }
+
+    .p-selector--card-item {
+      min-height: 50px;
+      margin: 0 0 12px 12px;
+      position: relative;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 4px 12px;
+      background-color: #fff;
+      border-radius: 4px;
+      border: 1px solid transparent;
+      cursor: pointer;
+      &.checked {
+        border-color: #2abed1;
+        &::after {
+          content: '';
+          position: absolute;
+          right: -1px;
+          bottom: -1px;
+          width: 14px;
+          height: 14px;
+          background-image: url(~@/assets/images/spec-active.png);
+          background-repeat: no-repeat;
+          background-size: contain;
+        }
+      }
+
+      .tag {
+        width: 40px;
+        height: 22px;
+        line-height: 22px;
+        font-size: 12px;
+        text-align: center;
+        box-sizing: border-box;
+        border: 1px solid transparent;
+        border-radius: 4px;
+        &.tag-orange {
+          border-color: #ff9f40;
+          color: #ff9f40;
+        }
+        &.tag-blue {
+          border-color: #2cb7ca;
+          color: #2cb7ca;
+        }
+      }
+
+      .key-info {
+        max-width: 8em;
+        margin-left: 8px;
+        font-size: 12px;
+        color: #686868;
+        line-height: 18px;
+        word-break: break-all;
+        white-space: nowrap;
+        .key-text {
+          font-size: 14px;
+          color: #1D1D1D;
+          line-height: 22px;
+        }
+        .notkey-text {
+          display: flex;
+          align-items: center;
+          .notkey-label {
+            white-space: nowrap;
+          }
+        }
+      }
+    }
     .p-selector--pop-item {
       border-radius: 4px;
       background: transparent;
@@ -311,12 +525,49 @@ export default {
       text-overflow: ellipsis;
       white-space: nowrap;
       text-align: justify;
-      max-width: 14em;
+      &.checked {
+        color: #fff;
+        background: #2cb7ca;
+      }
     }
-    .checked {
-      color: #ffffff;
-      background: #2cb7ca;
+  }
+}
+
+// key-card
+.show-key-info {
+  .p-selector--item {
+    &:not(.select).checked-dashed {
+      border-color: #2cb7ca;
+      border-style: dashed;
+      background-color: rgba(44, 183, 202, 0.08);
+      color: #2cb7ca;
+    }
+  }
+
+  .p-selector--pop-group {
+    justify-content: flex-start;
+  }
+  .card-list {
+    align-items: flex-start;
+    width: 100%;
+    max-height: 124px;
+    overflow-y: scroll;
+  }
+  .p-selector--pop-list {
+    flex-wrap: wrap;
+    align-items: flex-start;
+    justify-content: flex-start;
+  }
+  .p-selector--pop-item {
+    &.all {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-top: 12px;
     }
   }
+  .button-submit {
+    margin-top: 6px;
+  }
 }
 </style>

+ 3 - 3
src/components/selector/SelectorCard.vue

@@ -12,10 +12,10 @@
     <div class="selector-card-content scrollbar">
       <slot name="default"></slot>
     </div>
-    <div class="selector-card-footer" v-if="cardType === 'card'">
+    <div class="selector-card-footer">
       <slot name="footer">
-        <el-button type="primary" class="confirm" @click="onConfirm">{{ confirmText }}</el-button>
-        <el-button class="cancel" @click="onCancel">{{ cancelText }}</el-button>
+        <el-button v-if="cardType === 'card'" type="primary" class="confirm" @click="onConfirm">{{ confirmText }}</el-button>
+        <el-button v-if="cardType === 'card'" class="cancel" @click="onCancel">{{ cancelText }}</el-button>
       </slot>
     </div>
   </div>

+ 3 - 1
src/components/selector/TimeSelector.vue

@@ -12,6 +12,7 @@
       ref="content"
       :selectorTime="selectorTime"
       :selectorType="selectorType"
+      :defaultSelectedKey="defaultSelectedKey"
       @onChange="onChange"
     />
   </selector-card>
@@ -34,7 +35,8 @@ export default {
     selectorTime: {
       type: String,
       default: 'default'
-    }
+    },
+    defaultSelectedKey: String
   },
   data () {
     return {}

+ 139 - 37
src/components/selector/TimeSelectorContent.vue

@@ -14,7 +14,7 @@
         @click="clickTimeButton(item)"
       >{{ item.name }}</div>
     </div>
-    <div class="date-time-container">
+    <div class="date-time-container" :class="{ active: state.exact === 'exact' }">
       <el-date-picker
         v-model="dateTimePickerState.start"
         class="date-time-item left"
@@ -98,6 +98,33 @@ const timeSelectMap = {
       value: 'lately30',
       selected: false
     }
+  ],
+  more: [
+    {
+      name: '近3个月',
+      value: 'lately90',
+      selected: true
+    },
+    {
+      name: '近半年',
+      value: 'lately180',
+      selected: false
+    },
+    {
+      name: '今年全年',
+      value: 'thisYear',
+      selected: false
+    },
+    {
+      name: '去年至今',
+      value: 'sinceLastYear',
+      selected: false
+    },
+    {
+      name: '前年至今',
+      value: 'sinceYearBeforeLast',
+      selected: false
+    }
   ]
 }
 export default {
@@ -112,11 +139,16 @@ export default {
     },
     selectorTime: {
       type: String,
-      default: 'default'
+      default: 'default' // default/sub/more
+    },
+    defaultSelectedKey: {
+      type: String,
+      default: 'all' // all/lately30/lately90...
     }
   },
   data () {
     return {
+      debug: false,
       timeSelectList: timeSelectMap[this.selectorTime],
       dateTimePickerConf: {
         type: 'date',
@@ -149,9 +181,37 @@ export default {
       }
     }
   },
-  created () {},
+  computed: {
+    state () {
+      return this.getState()
+    }
+  },
+  created () {
+    this.calcLastTime()
+  },
   methods: {
     dateFormatter,
+    calcLastTime () {
+      if (this.selectorTime === 'more') {
+        const renameList = [
+          'thisYear', // 今年全年
+          'sinceLastYear', // 去年至今
+          'sinceYearBeforeLast' // 前年至今
+        ]
+        const thisYear = new Date().getFullYear()
+        this.timeSelectList.forEach(item => {
+          if (renameList.indexOf(item.value) !== -1) {
+            if (item.value === renameList[0]) {
+              item.name = `${thisYear}年全年`
+            } else if (item.value === renameList[1]) {
+              item.name = `${thisYear - 1}年至今`
+            } else if (item.value === renameList[2]) {
+              item.name = `${thisYear - 2}年至今`
+            }
+          }
+        })
+      }
+    },
     setState (data) {
       // {
       //   start: 1620230400000, // 2021-5-6
@@ -159,42 +219,28 @@ export default {
       //   exact: 'all'
       // }
       if (!data || Object.keys(data).length === 0) {
-        this.setTimeSelectListState('all')
+        this.setTimeSelectListState(this.defaultSelectedKey)
       } else {
         switch (data.exact) {
-          case 'all': {
-            this.setTimeSelectListState('all')
-            this.clearDateTimePicker()
-            break
-          }
-          case 'today': {
-            this.setTimeSelectListState('today')
-            this.clearDateTimePicker()
-            break
-          }
-          case 'yesterday': {
-            this.setTimeSelectListState('yesterday')
-            this.clearDateTimePicker()
-            break
-          }
-          case 'lately7': {
-            this.setTimeSelectListState('lately7')
-            this.clearDateTimePicker()
-            break
-          }
-          case 'lately30': {
-            this.setTimeSelectListState('lately30')
-            this.clearDateTimePicker()
-            break
-          }
+          case 'all':
+          case 'today':
+          case 'yesterday':
+          case 'lately7':
+          case 'lately30':
+          case 'lately90':
+          case 'lately180':
+          case 'thisYear':
+          case 'sinceLastYear':
+          case 'sinceYearBeforeLast':
           case 'lastYear': {
-            this.setTimeSelectListState('lastYear')
+            this.setTimeSelectListState(data.exact)
             this.clearDateTimePicker()
             break
           }
           case 'exact': {
             if (!data.start || !data.end) break
             if (data.start < data.end) {
+              this.timeSelectList.forEach(v => (v.selected = false))
               this.dateTimePickerState.start = new Date(data.start)
               this.dateTimePickerState.end = new Date(data.end)
             }
@@ -214,19 +260,39 @@ export default {
         end: 0,
         exact: 'exact'
       }
+      const durations = {
+        hour1: 60 * 60 * 1000,
+        day1: 60 * 60 * 1000 * 24 * 1,
+        day7: 60 * 60 * 1000 * 24 * 7,
+        day30: 60 * 60 * 1000 * 24 * 30
+      }
       const selectButton = this.timeSelectList.find(item => item.selected)
       if (selectButton) {
         timeState.exact = selectButton.value
         Object.assign(timeState, this.calcNotExactTime(timeState.exact))
       } else {
         timeState.exact = 'exact'
-        timeState.start = this.dateTimePickerState.start.getTime()
-        timeState.end = this.dateTimePickerState.end.getTime()
+        if (this.dateTimePickerState.start) {
+          timeState.start = this.dateTimePickerState.start.getTime()
+        }
+        if (this.dateTimePickerState.end) {
+          // 结束时间为当天23:59:59
+          timeState.end = this.dateTimePickerState.end.getTime() + (durations.day1 - 1000)
+        }
       }
       return timeState
     },
     onChange () {
       const state = this.getState()
+      if (this.debug) {
+        state.startFormatted = this.dateFormatter(state.start)
+        state.endFormatted = this.dateFormatter(state.end)
+
+        console.table({
+          start: state.startFormatted,
+          end: state.endFormatted
+        })
+      }
       this.$emit('onChange', state)
     },
     setTimeSelectListState (value, callback) {
@@ -239,7 +305,7 @@ export default {
       this.dateTimePickerState.start = ''
       this.dateTimePickerState.end = ''
     },
-    // 计算lately7/lately30/lastYear的开始和结束时间
+    // 计算lately7/lately30/lastYear的开始和结束时间
     // endTime传入一个时间戳
     calcNotExactTime (exact = 'lately7', endTime = Date.now()) {
       const t = {
@@ -271,13 +337,38 @@ export default {
           t.start = t.end - durations.day30
           break
         }
-        case 'lastYear': {
+        case 'lately90': { // 近90天
+          t.start = t.end - (durations.day30 * 3)
+          break
+        }
+        case 'lately180': { // 180天
+          t.start = t.end - (durations.day30 * 6)
+          break
+        }
+        case 'thisYear': { // 今年全年
+          const year = new Date(t.end).getFullYear()
+          t.start = +new Date(`${year}`)
+          t.end = +new Date(`${year + 1}`) - durations.hour1 * 8 - 1
+          break
+        }
+        case 'lastYear': { // 去年全年
           const year = new Date(t.end).getFullYear()
           const lastYear = year - 1
           t.start = +new Date(`${lastYear}`)
           t.end = +new Date(`${year}`) - durations.hour1 * 8 - 1
           break
         }
+        case 'sinceLastYear': { // 去年至今
+          const year = new Date(t.end).getFullYear()
+          const lastYear = year - 1
+          t.start = +new Date(`${lastYear}`)
+          break
+        }
+        case 'sinceYearBeforeLast': { // 前年至今
+          const year = new Date(t.end).getFullYear()
+          t.start = +new Date(`${year - 2}`)
+          break
+        }
         default: {
           t.start = 0
           t.end = 0
@@ -299,7 +390,7 @@ export default {
         this.setTimeSelectListState()
         this.onChange()
       } else if (!start && !end) { // start和end都没值
-        this.setTimeSelectListState('all')
+        this.setTimeSelectListState(this.defaultSelectedKey)
         this.onChange()
       }
     },
@@ -309,7 +400,7 @@ export default {
         this.setTimeSelectListState()
         this.onChange()
       } else if (!start && !end) { // start和end都没值
-        this.setTimeSelectListState('all')
+        this.setTimeSelectListState(this.defaultSelectedKey)
         this.onChange()
       }
     }
@@ -331,6 +422,17 @@ export default {
   }
   .date-time-container {
     display: flex;
+    padding: 6px;
+    background-color: #F4F5F7;
+    border-radius: 4px;
+    &.active {
+      background-color: #2CB7CA;
+      .date-time-item {
+        &.left::after {
+          background-color: #e0e0e0;
+        }
+      }
+    }
     .clear-icon {
       display: none;
     }
@@ -346,7 +448,7 @@ export default {
           content: '';
           position: absolute;
           width: 12px;
-          height: 1px;
+          height: 2px;
           top: 50%;
           right: -10px;
           transform: translate(100%,-50%);

+ 41 - 6
src/components/work-desktop/Slidebar.vue

@@ -1,21 +1,23 @@
 <template>
   <div class="work-slidebar">
     <el-menu
-      default-active="0"
+      :default-active="defaultActive"
       class="el-menu-vertical-demo"
       :default-openeds="defalutOpends"
+      ref="menu"
+      @select="onMenuSelect"
     >
-      <el-menu-item index="0" class="is-active">
+      <el-menu-item index="我的主页" @click.native="toIndex">
         <i class="iconfont icon-hui7"></i>
         <span slot="title">我的主页</span>
       </el-menu-item>
-      <el-submenu :index="(index + 1) + ''" v-for="(item, index) in menus" :key="'0'+index">
+      <el-submenu :index="item.firstlevel" v-for="item in menus" :key="item.firstlevel">
         <template slot="title">
           <i class="iconfont" :class="item.icon"></i>
           <span>{{item.firstlevel}}</span>
         </template>
         <el-menu-item-group>
-          <el-menu-item :index="(index+1) +'-'+j" v-for="(v, j) in item.secondarylevel" :key="'00' + j" @click="goRouteItem(v)">
+          <el-menu-item :index="v.name" v-for="v in item.secondarylevel" :key="v.name" @click="goRouteItem(v)">
             <span class="router-name" v-html="v.name"></span>
           </el-menu-item>
         </el-menu-item-group>
@@ -40,8 +42,20 @@ export default {
   },
   data () {
     return {
+      menuKey: '0', // 用来强制刷新menu
       menus: [],
-      defalutOpends: []
+      defalutOpends: [],
+      defaultActive: '我的主页',
+      // 使用SideBar的index列表
+      useSideBar: [
+        '我的主页',
+        '定制化分析报告'
+      ]
+    }
+  },
+  watch: {
+    $router (to, from) {
+      this.setDefaultActive()
     }
   },
   created () {
@@ -76,7 +90,7 @@ export default {
                 break
             }
             if (v.firstlevel === '会员服务') {
-              this.defalutOpends.push(i + 1 + '') // 加空字符串
+              this.defalutOpends.push(v.firstlevel) // 加空字符串
               v.secondarylevel.forEach(s => {
                 if (s.name.indexOf('/') > -1) {
                   s.name = s.name.replace('/', '/<br>')
@@ -85,6 +99,7 @@ export default {
             }
           })
           this.menus = res.data
+          this.setDefaultActive()
         } else {
           this.menus = []
         }
@@ -93,6 +108,7 @@ export default {
     goRouteItem (item) {
       if (item.isusable) {
         window.open(item.url)
+        this.$refs.menu.activeIndex = this.defaultActive
       } else {
         MessageBox.confirm('您未购买此服务,如需使用请联系您的销售人员或客服升级套餐,谢谢!', '提示信息', {
           customClass: 'custom-message-box',
@@ -106,6 +122,25 @@ export default {
           console.log('关闭')
         })
       }
+    },
+    onMenuSelect (e) {
+      this.$nextTick(() => {
+        this.$refs.menu.activeIndex = this.defaultActive
+      })
+    },
+    toIndex () {
+      if (this.$route.name === 'desktop') return
+      const routeUrl = this.$router.resolve({
+        path: '/'
+      })
+      window.open(routeUrl.href)
+    },
+    setDefaultActive () {
+      if (this.$route.name === 'desktop') {
+        this.defaultActive = '我的主页'
+      } else if (this.$route.path.indexOf('report_analysis') !== -1) {
+        this.defaultActive = '定制化分析报告'
+      }
     }
   }
 }

+ 3 - 1
src/main.js

@@ -4,11 +4,12 @@ import store from './store/'
 import router from './router/'
 import VueCookies from 'vue-cookies'
 import singleSpaVue from 'single-spa-vue'
-import { Loading, Message } from 'element-ui'
+import { Loading, Message, MessageBox } from 'element-ui'
 import echarts from 'echarts'
 // import axios from 'axios'
 import Toast from './components/toast/index'
 import '@/utils/'
+import '@/utils/common'
 
 Vue.use(VueCookies)
 Vue.use(Loading.directive)
@@ -16,6 +17,7 @@ Vue.use(Toast)
 
 Vue.prototype.$message = Message
 Vue.prototype.$echarts = echarts
+Vue.prototype.$alert = MessageBox.alert
 Vue.config.productionTip = false
 
 // 正式环境下屏蔽console.log

+ 0 - 1
src/router/router-interceptors.js

@@ -42,7 +42,6 @@ router.beforeEach(async (to, from, next) => {
     } else {
       next()
     }
-    next()
   } else {
     let href = '/big/page/index'
     const { pass, anchor } = powerCheck(info, power, to, from)

+ 22 - 3
src/router/routers.js

@@ -1,9 +1,28 @@
 export default [
-  // 工作台桌面
   {
     path: '/',
-    name: 'desktop',
-    component: () => import('@/views/work-desktop/WorkDesktop.vue')
+    redirect: '/desktop'
+  },
+  {
+    path: '/desktop',
+    component: () => import('@/views/work-desktop/index.vue'),
+    children: [
+      {
+        path: '',
+        redirect: 'index'
+      },
+      // 工作台桌面
+      {
+        path: 'index',
+        name: 'desktop',
+        component: () => import('@/views/work-desktop/WorkDesktop.vue')
+      },
+      {
+        path: 'report_analysis',
+        name: 'report_analysis',
+        component: () => import('@/views/analysisReport/index.vue')
+      }
+    ]
   },
   // 原大会员首页
   {

+ 1 - 1
src/utils/bigmember/powerMap.js

@@ -44,7 +44,7 @@ export const powerMap = {
   },
   10: {
     tip: '周报/月报',
-    url: 'bigvip_subreport_week&bigvip_subreport_month&report_detail_week&report_detail_month',
+    url: 'bigvip_subreport_week&bigvip_subreport_month&report_detail_week&report_detail_month&report_analysis',
     anchor: 'fx'
   },
   11: {

+ 13 - 0
src/utils/common.js

@@ -0,0 +1,13 @@
+import Vue from 'vue'
+
+Vue.prototype.$checkLogin = function () {
+  try {
+    var moduleOpen = $('body').hasClass('modal-open')
+    if (moduleOpen) return
+    if (!loginflag) {
+      $('#bidLogin').modal('show')
+    }
+  } catch (error) {
+    console.log(error)
+  }
+}

+ 33 - 0
src/utils/globalDirectives.js

@@ -24,3 +24,36 @@ Vue.directive('auto-focus', {
     }, 30)
   }
 })
+
+// fix固定元素
+// 传入距离顶部高度x,当滚动高度大于x则添加fix定位
+Vue.directive('stickyed', {
+  inserted (el, binding) {
+    const $el = $(el)
+    const conf = {
+      className: ['fixed-nav'], // 置顶后添加的默认类名
+      startFixedTop: el.offsetTop // 从高度为startFixedTop处开始置顶,默认为元素距离顶部高度
+    }
+
+    el.handleWindowScrollStickyEvent = function (e) {
+      const value = binding.value
+      if (value.className) {
+        conf.className = conf.className.concat(value.className)
+      }
+      if (value.startFixedTop) {
+        conf.startFixedTop = value.startFixedTop
+      }
+
+      const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
+      if (scrollTop >= conf.startFixedTop) {
+        $el.addClass(conf.className.join(' '))
+      } else {
+        $el.removeClass(conf.className.join(' '))
+      }
+    }
+    window.addEventListener('scroll', el.handleWindowScrollStickyEvent, false)
+  },
+  unbind (el) {
+    el && window.removeEventListener('scroll', el.handleWindowScrollStickyEvent, false)
+  }
+})

+ 17 - 1
src/utils/globalFunctions.js

@@ -289,6 +289,9 @@ export function moneyUnit (m, type = 'string', lv = 0) {
         if (type === 'index') {
           return lv
         }
+        if (type === 'transfer') {
+          return 0
+        }
       }
 
       var result = num / Math.pow(10000, lv)
@@ -309,12 +312,25 @@ export function moneyUnit (m, type = 'string', lv = 0) {
           return lv
         }
       }
+    },
+    // 需要传入固定的lv(此时lv为 levelArr 中的一个)
+    transfer (num, lvString) {
+      const index = this.levelArr.indexOf(lvString)
+      if (index === -1 || index === 0) {
+        return num
+      } else {
+        return (num / Math.pow(10000, index)).toFixed(2) + lvString
+      }
     }
   }
   if (m === undefined || m === null) {
     return ''
   } else {
-    return mUnit.test(m, type, lv)
+    if (type === 'transfer') {
+      return mUnit.transfer(m, lv)
+    } else {
+      return mUnit.test(m, type, lv)
+    }
   }
 }
 

+ 490 - 0
src/views/analysisReport/MarketAnalysis.vue

@@ -0,0 +1,490 @@
+<template>
+  <div class="market-analysis">
+    <div class="market-title pd-lr20">
+      <h3 class="market-title-text">分析条件</h3>
+      <div class="more-filters" @click="showMoreFilters = !showMoreFilters">
+        <i :class="showMoreFilters ? 'el-icon-caret-top' : 'el-icon-caret-bottom'"></i>
+      </div>
+    </div>
+    <el-collapse-transition>
+      <div class="market-filter-container pd-lr20" v-show="showMoreFilters" v-loading="loading">
+        <PopSelector @onChange="changeKeys" ref="keySelector" selectorType="line" :showMoreKeyInfoWithCard="true" v-show="showKeyCount">
+          <div slot="header" class="filter-label">分析内容:</div>
+        </PopSelector>
+        <SelectorCard class="bottom-divider" cardType="line">
+          <div slot="header" class="filter-label">{{ showKeyCount ? '' : '分析内容:' }}</div>
+          <div slot="default" class="tip-container button-group">
+            <div class="tip-text" :class="{ 'color-red': !showKeyCount }">注:如需新增分析内容,请完善您的订阅关键词组</div>
+            <button class="button-submit" @click="toSubManage">订阅管理<i class="el-icon-arrow-right"></i></button>
+          </div>
+        </SelectorCard>
+        <TimeSelector class="bottom-divider" @onChange="changeTime" ref="timeSelector" defaultSelectedKey="sinceYearBeforeLast" selectorTime="more" selectorType="line">
+          <div slot="header" class="filter-label">时间:</div>
+        </TimeSelector>
+        <AreaSelector class="bottom-divider" @onChange="changeArea" ref="areaSelector" selectorType="line">
+          <div slot="header" class="filter-label">项目地区:</div>
+        </AreaSelector>
+        <IndustrySelector class="bottom-divider" @onChange="changeIndustry" ref="industrySelector" selectorType="line">
+          <div slot="header" class="filter-label">行业:</div>
+        </IndustrySelector>
+        <IndustrySelector class="bottom-divider solid" @onChange="changeBuyer" ref="buyerclassSelector" dataType="buyer" selectorType="line">
+          <div slot="header" class="filter-label">采购单位类型:</div>
+        </IndustrySelector>
+        <div class="action-button-group flex-r-c center button-group">
+          <button class="button-default" @click="resetFilters">重置</button>
+          <button class="button-submit" @click="confirmSubmit">开始分析</button>
+        </div>
+      </div>
+    </el-collapse-transition>
+    <div class="bg-grey-h24" v-if="loaded && analysisReportId"></div>
+    <MarketAnalysisResult ref="result" v-if="resultShow" :rid="analysisReportId" @loadedFilters="loadedFilters" @onEmpty="onEmpty" />
+    <Empty v-if="loaded && !analysisReportId">{{ empty.msg }}</Empty>
+  </div>
+</template>
+
+<script>
+import { Button, Icon } from 'element-ui'
+import CollapseTransition from 'element-ui/lib/transitions/collapse-transition'
+import SelectorCard from '@/components/selector/SelectorCard.vue'
+import AreaSelector from '@/components/selector/AreaSelector.vue'
+import PopSelector from '@/components/selector/PopSelector.vue'
+import TimeSelector from '@/components/selector/TimeSelector.vue'
+import IndustrySelector from '@/components/selector/IndustrySelector.vue'
+import MarketAnalysisResult from '@/views/analysisReport/MarketAnalysisResult.vue'
+import Empty from '@/components/common/Empty.vue'
+import { mapState } from 'vuex'
+import { doReportAnalysis } from '@/api/modules/'
+import qs from 'qs'
+
+export default {
+  name: 'analysis-report-filters',
+  components: {
+    [Button.name]: Button,
+    [Icon.name]: Icon,
+    [CollapseTransition.name]: CollapseTransition,
+    SelectorCard,
+    AreaSelector,
+    PopSelector,
+    TimeSelector,
+    IndustrySelector,
+    MarketAnalysisResult,
+    Empty
+  },
+  data () {
+    return {
+      showKeyCount: true,
+      timerId: '',
+      analysisDone: false, // 当前页面是否进行自己选择分析过
+      showMoreFilters: true,
+      loaded: false,
+      loading: false,
+      filters: {
+        keys: [],
+        selectTime: '',
+        selectTimeExtra: '',
+        area: {},
+        industry: {},
+        buyerclass: []
+      },
+      empty: {
+        defaultMsg: '对不起,没有匹配到相关信息,请修改您的分析条件',
+        msg: ''
+      },
+      analysisReportId: '' // 分析报告结果id
+    }
+  },
+  computed: {
+    ...mapState({
+      bigKeywordsData: state => state.user.bigKeywordsData,
+      isSubCount: state => state.user.info.isSubCount
+    }),
+    resultShow () {
+      return this.loaded && this.analysisReportId
+    }
+  },
+  watch: {
+    showMoreFilters (newVal) {
+      try {
+        if (this.timerId) clearTimeout(this.timerId)
+        this.timerId = setTimeout(() => {
+          if (newVal) {
+            this.$refs.keySelector.computRow()
+          }
+          if (this.resultShow) {
+            this.$refs.result.calcStickyNav()
+          }
+        }, 200)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    bigKeywordsData () {
+      this.initKeyMap()
+    }
+  },
+  created () {
+    this.getUrlQuery()
+  },
+  mounted () {
+    this.initKeyMap()
+    this.initDefaultTime()
+  },
+  methods: {
+    changeUrl (id) {
+      const query = this.$route.query
+      query.id = id
+      history.replaceState({}, '', `?${qs.stringify(query)}`)
+    },
+    getUrlQuery () {
+      const { id } = this.$route.query
+      if (id) {
+        this.analysisReportId = id
+        this.showMoreFilters = false
+        this.loaded = true
+      }
+    },
+    getAllKeyMap () {
+      // eslint-disable-next-line no-unused-expressions
+      return this.bigKeywordsData?.member_jy?.a_items
+    },
+    initKeyMap () {
+      const tempKeys = {}
+      let keyCount = 0
+      this.getAllKeyMap().forEach(v => {
+        const tempArr = []
+        if (v && Array.isArray(v.a_key)) {
+          v.a_key.forEach(s => {
+            keyCount++
+            let key = s?.key || []
+            if (s?.appendkey && s?.key) {
+              key = key.concat(s?.appendkey)
+            }
+            tempArr.push({
+              ...s,
+              key: key.join(' '),
+              notkey: Array.isArray(s.notkey) ? s.notkey.join(' ') : ''
+            })
+          })
+          tempKeys[v.s_item] = tempArr
+        }
+      })
+
+      if (keyCount <= 0) {
+        this.showKeyCount = false
+      } else {
+        this.$refs.keySelector.initDataMap(tempKeys)
+      }
+    },
+    initDefaultTime () {
+      const timeSelector = this.$refs.timeSelector
+      timeSelector.setState({ exact: 'sinceYearBeforeLast' })
+      const selected = timeSelector.getState()
+      this.changeTime(selected)
+    },
+    getSelectedKeys () {
+      const keys = this.filters.keys
+      if (Array.isArray(keys) && keys.length) {
+        return JSON.stringify(keys)
+      } else {
+        const allKeys = this.getAllKeyMap()
+        return JSON.stringify(allKeys)
+      }
+    },
+    async doAnalysis () {
+      const query = {
+        keysItems: this.getSelectedKeys(),
+        rangeTime: this.filters.selectTime,
+        rangeTimeExtra: this.filters.selectTimeExtra,
+        area: JSON.stringify(this.filters.area),
+        industry: JSON.stringify(this.filters.industry),
+        buyerclass: this.filters.buyerclass.join(',')
+      }
+
+      this.loading = true
+      this.loaded = false
+      this.analysisReportId = ''
+
+      const { data, error_code: code } = await doReportAnalysis(query)
+
+      this.loading = false
+      this.loaded = true
+
+      if (code === 0 && data) {
+        this.showAnalysisResult(data)
+      }
+    },
+    showAnalysisResult (id) {
+      this.analysisDone = true
+      this.analysisReportId = id
+    },
+    changeKeys () {
+      this.filters.keys = this.$refs.keySelector.getSelectedDetailList()
+    },
+    changeTime (item) {
+      let time = parseInt(item.start / 1000)
+      if (item.end) {
+        time += '-' + parseInt(item.end / 1000)
+      }
+      if (item.start === 0 && item.end === 0) {
+        this.filters.selectTime = ''
+      } else {
+        this.filters.selectTime = time
+      }
+
+      if (item.exact === 'exact') {
+        this.filters.selectTimeExtra = ''
+      } else {
+        this.filters.selectTimeExtra = item.exact
+      }
+    },
+    changeArea (item) {
+      this.filters.area = item
+    },
+    changeIndustry (item) {
+      this.filters.industry = item
+    },
+    changeBuyer (item) {
+      this.filters.buyerclass = this.formatIndustryMap(item).map(v => v.split('_')[1])
+    },
+    resetFilters () {
+      this.initKeyMap()
+      this.$refs.areaSelector.setCitySelected()
+      this.$refs.industrySelector.setIndustryState()
+      this.$refs.buyerclassSelector.setIndustryState()
+      this.filters = Object.assign({}, this.$options.data.bind(this)().filters)
+      this.initDefaultTime()
+    },
+    confirmSubmit () {
+      this.analysisReportId = ''
+      this.doAnalysis()
+    },
+    formatIndustryMap (item) {
+      const tempArr = []
+      Object.keys(item).forEach(v => {
+        const tempItem = item[v]
+        if (Array.isArray(tempItem)) {
+          tempItem.forEach(vv => {
+            tempArr.push(`${v}_${vv}`)
+          })
+        }
+      })
+      return tempArr
+    },
+    onEmpty (info) {
+      this.analysisReportId = ''
+      if (info && info.msg) {
+        this.empty.msg = info.msg
+      } else {
+        this.empty.msg = this.empty.defaultMsg
+      }
+      this.showMoreFilters = true
+    },
+    // 整理数据,并赋值给filters
+    loadedFilters (filters) {
+      this.showMoreFilters = false
+      this.$emit('refreshTab2')
+      // 当前页面分析过,则不恢复filters
+      if (this.analysisDone) return
+      if (filters.keys) {
+        this.filters.keys = filters.keys
+        this.restoreFilterKeys(filters.keys)
+      }
+      if (filters.selectTime) {
+        this.filters.selectTime = filters.selectTime
+      }
+      // if (filters.selectTimeExtra) {
+      //   this.filters.selectTimeExtra = filters.selectTimeExtra
+      // }
+      // this.restoreFilterTimes(filters.selectTime, filters.selectTimeExtra)
+      this.restoreFilterTimes(filters.selectTime)
+
+      if (filters.area) {
+        this.filters.area = filters.area
+        this.restoreFilterArea(filters.area)
+      }
+      if (filters.industry) {
+        this.filters.industry = filters.industry
+        this.restoreFilterIndustry(filters.industry)
+      }
+      if (filters.buyerclass) {
+        this.filters.buyerclass = filters.buyerclass
+        this.restoreFilterBuyerclass(filters.buyerclass)
+      }
+    },
+    restoreFilterKeys (keys) {
+      if (!Array.isArray(keys)) return
+      const keysArr = []
+      keys.forEach(classify => {
+        if (Array.isArray(classify.a_key)) {
+          classify.a_key.forEach(item => {
+            if (Array.isArray(item.appendkey)) {
+              item.key = item.key.concat(item.appendkey)
+            }
+            keysArr.push(item.key.join(' '))
+          })
+        }
+      })
+      this.$refs.keySelector.setState(keysArr)
+    },
+    restoreFilterTimes (selectTime, extra) {
+      const selectTimeArr = selectTime.split('-')
+      const struct = {
+        start: selectTimeArr[0] * 1000,
+        end: selectTimeArr[1] * 1000,
+        exact: extra ? extra : 'exact'
+      }
+
+      if (!extra) {
+        const date = new Date(struct.end)
+        const timeString = date.pattern('yyyy/MM/dd')
+        struct.end = new Date(timeString).getTime()
+      }
+      this.$refs.timeSelector.setState(struct)
+    },
+    restoreFilterArea (area) {
+      this.$refs.areaSelector.setCitySelected(area)
+    },
+    restoreFilterIndustry (i) {
+      this.$refs.industrySelector.setIndustryState(i)
+    },
+    restoreFilterBuyerclass (b) {
+      this.$refs.buyerclassSelector.setIndustryState(b)
+    },
+    toSubManage () {
+      if (this.isSubCount) {
+        return this.$alert('请联系管理员完善订阅的关键词', '', {
+          confirmButtonText: '我知道了',
+          confirmButtonColor: '#2ABDD1',
+          showClose: false,
+          center: true
+        })
+      } else {
+        const routeUrl = this.$router.resolve({ path: '/set_subscribe/config?scroll=setkey' })
+        window.open(routeUrl.href)
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep {
+  .selector-card {
+    &.s-line {
+      padding-left: 0;
+      padding-right: 0;
+      .s-header {
+        text-align: right;
+      }
+      .selector-card-header {
+        min-width: 114px;
+      }
+      .select-group-container.right-line {
+        &:last-child {
+          &::after {
+            content: unset !important;
+          }
+        }
+      }
+    }
+
+    &.industry-selector .selector-card-content .j-button-item.active {
+      color: #fff;
+      background-color: #2CB7CA;
+    }
+  }
+}
+
+.pd-lr20 {
+  padding: 0 20px;
+}
+.market {
+  &-filter-container {
+    border-top-left-radius: 4px;
+    border-top-right-radius: 4px;
+  }
+  &-title {
+    display: flex;
+    align-items: center;
+    padding-top: 24px;
+    padding-bottom: 24px;
+    &-text {
+      margin-right: 12px;
+      color: #1D1D1D;
+      font-size: 16px;
+      line-height: 24px;
+    }
+    .more-filters {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 24px;
+      height: 18px;
+      background-color: #F4F5F7;
+      border-radius: 2px;
+      border: 1px solid transparent;
+      cursor: pointer;
+      &:hover {
+        color: #2CB7CA;
+        border-color: #2CB7CA;
+      }
+    }
+  }
+}
+
+.filter-label {
+  color: #686868;
+  font-size: 14px;
+}
+
+.button-group {
+  background-color: #fff;
+  padding: 20px 0;
+  button {
+    padding: 3px 44px;
+    box-sizing: border-box;
+    border-radius: 4px;
+    font-size: 14px;
+    line-height: 22px;
+    border: 1px solid #2CB7CA;
+    background-color: inherit;
+    text-align: center;
+    & + button {
+      margin-left: 40px;
+    }
+    &.button-default {
+      color: #2CB7CA;
+    }
+    &.button-submit {
+      background-color: #2cb7ca;
+      color: #fff;
+    }
+  }
+}
+
+.tip-container {
+  display: flex;
+  align-items: center;
+  padding: 0;
+  width: 100%;
+  .tip-text {
+    margin-right: 12px;
+    color: #686868;
+    font-size: 14px;
+    line-height: 22px;
+    &.color-red {
+      color: #FF3A20;
+    }
+  }
+  .button-submit {
+    padding: 3px 14px;
+  }
+}
+
+.bottom-divider {
+  border-bottom: 1px dashed #e0e0e0;
+  &.solid {
+    border-style: solid;
+  }
+}
+</style>

+ 1790 - 0
src/views/analysisReport/MarketAnalysisResult.vue

@@ -0,0 +1,1790 @@
+<template>
+  <div class="market-analysis-result">
+    <div class="analysis-result-anchors" v-loading="!sections.loaded.overview">
+      <div class="analysis-dimensions analysis-wrap" v-stickyed="stickyed">
+        <div class="analysis-label">报告分析维度:</div>
+        <div class="analysis-content">
+          <el-dropdown
+            class="dimensions-options"
+            @command="dropDownClick"
+            placement="bottom"
+            :class="{
+              highlight: value.dropDownShow
+            }"
+            v-for="(value, key) in dimensionsTitle"
+            :key="key"
+            size="small">
+            <span class="el-dropdown-link" @click="dropDownClick(value)">
+              <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-item
+                v-for="(item, index) in dimensionsOptions[key]"
+                :key="index"
+                v-show="item.show"
+                :command="item">
+                {{ item.name }}
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </div>
+      </div>
+      <div class="analysis-limit-time analysis-wrap">
+        <div class="analysis-label">数据统计范围:</div>
+        <div class="analysis-content">
+          <span class="limit-time">{{ formatSelectTime(reportFilters.selectTime) }}</span>
+          <span>(注:分析报告涉及的排行完全依据您的分析条件)</span>
+        </div>
+      </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">
+            <div class="sub-section-header">
+              <div class="sub-section-title">市场概况</div>
+            </div>
+            <div class="sub-section-content">
+              <MarketOverview :overviewList="sections.market.overview" />
+            </div>
+          </div>
+          <!-- 项目规模分布 -->
+          <div class="sub-section project-scatter"
+            v-if="!sections.loaded.top3 || (sections.projectScatter.dataAlready && sections.loaded.top3)"
+            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>
+          <!-- 时间分布 -->
+          <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" 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) || (sections.areaScatter.dataAlready && sections.loaded.top3 && notOneAreaFilter)"
+            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 || (sections.areaScatter.projectCountTop3 && sections.loaded.top3)"
+              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>
+          <!-- 客户分布 -->
+          <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>
+            <div class="sub-section-content">
+              <MarketUserScatter v-if="sections.userScatter.list.length" :chartData="sections.userScatter.list" />
+            </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="!sections.loaded.segment || (sections.market.refine.dataAlready && sections.loaded.segment)"
+            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">
+            <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>
+        </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">
+              <p class="ar-table-title">项目数量TOP3采购单位及其重点合作中标单位</p>
+              <Top3Table v-if="buyerclassSectionShow" :tableData="sections.buyerclass.projectCountTop3" />
+            </div>
+            <div class="ar-table buyerclass-amount-top3" v-if="sections.buyerclass.projectAmountTop3">
+              <p class="ar-table-title">采购金额TOP3采购单位及其重点合作中标单位</p>
+              <Top3Table v-if="buyerclassSectionShow" :tableData="sections.buyerclass.projectAmountTop3" />
+            </div>
+          </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">
+              <p class="ar-table-title">项目数量TOP3中标单位及其重点合作采购单位</p>
+              <Top3Table v-if="sections.winner.projectCountTop3" :tableData="sections.winner.projectCountTop3" />
+            </div>
+            <div class="ar-table winner-amount-top3">
+              <p class="ar-table-title">中标金额TOP3中标单位及其重点合作采购单位</p>
+              <Top3Table v-if="sections.winner.projectAmountTop3" :tableData="sections.winner.projectAmountTop3" />
+            </div>
+          </div>
+        </div>
+      </section>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Dropdown, DropdownMenu, DropdownItem, Icon } 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 MarketSegment from '@/views/analysisReport/components/MarketSegment.vue'
+import BuyerScaleScatter from '@/views/analysisReport/components/BuyerScaleScatter.vue'
+import BidderScaleScatter from '@/views/analysisReport/components/BidderScaleScatter.vue'
+import Top3Table from '@/views/analysisReport/components/MarketTop3Table'
+import MarketUserScatter from '@/views/analysisReport/components/MarketUserScatter'
+import { getReportAnalysisInfo } from '@/api/modules/'
+import { moneyUnit, dateFormatter, formatPrice } from '@/utils/globalFunctions'
+
+export default {
+  name: 'analysis-report-result',
+  components: {
+    [Icon.name]: Icon,
+    [Dropdown.name]: Dropdown,
+    [DropdownMenu.name]: DropdownMenu,
+    [DropdownItem.name]: DropdownItem,
+    MarketOverview,
+    ProjectScatter,
+    MarketTimeScatter,
+    MarketAreaScatter,
+    MarketSegment,
+    BuyerScaleScatter,
+    BidderScaleScatter,
+    Top3Table,
+    MarketUserScatter
+  },
+  props: {
+    rid: String
+  },
+  data () {
+    return {
+      loaded: false,
+      loading: false,
+      stickyed: {
+        className: ['dim-fixed'],
+        startFixedTop: 0 // 从高度为startFixedTop处开始置顶
+      },
+      reportFilters: {
+        keys: [],
+        selectTime: '',
+        selectTimeExtra: '',
+        area: {},
+        industry: {},
+        buyerclass: []
+      },
+      dimensionsTitle: {
+        market: {
+          name: '市场规模',
+          anchor: 'section-market',
+          show: true,
+          dropDownShow: false
+        },
+        buyer: {
+          name: '采购单位',
+          anchor: 'section-buyer',
+          show: true,
+          dropDownShow: false
+        },
+        winner: {
+          name: '中标单位',
+          anchor: 'section-winner',
+          show: true,
+          dropDownShow: false
+        }
+      },
+      dimensionsOptions: {
+        market: [
+          {
+            name: '市场概况',
+            show: false,
+            anchor: 'market-overview'
+          },
+          {
+            name: '项目规模',
+            show: false,
+            anchor: 'project-scatter'
+          },
+          {
+            name: '时间分布',
+            show: false,
+            anchor: 'time-scatter'
+          },
+          {
+            name: '地区分布',
+            show: false,
+            anchor: 'area-scatter'
+          },
+          {
+            name: '客户分布',
+            show: false,
+            anchor: 'user-scatter'
+          },
+          {
+            name: '细分市场',
+            show: false,
+            anchor: 'market-refine'
+          }
+        ],
+        buyer: [
+          {
+            name: '采购规模分布',
+            show: false,
+            anchor: 'buyerclass-scatter'
+          },
+          {
+            name: '项目数量TOP3',
+            show: false,
+            anchor: 'buyerclass-count-top3'
+          },
+          {
+            name: '采购金额TOP3',
+            show: false,
+            anchor: 'buyerclass-amount-top3'
+          }
+        ],
+        winner: [
+          {
+            name: '中标规模分布',
+            show: false,
+            anchor: 'winner-scatter'
+          },
+          {
+            name: '项目数量TOP3',
+            show: false,
+            anchor: 'winner-count-top3'
+          },
+          {
+            name: '中标金额TOP3',
+            show: false,
+            anchor: 'winner-amount-top3'
+          }
+        ]
+      },
+      sections: {
+        loaded: {
+          overview: false, // 1, 市场概括与时间分布
+          top10: false, // 2, 项目规模Top10
+          top3: false, // 3, 项目规模分布/地区规模分布/客户分布/地区分布及客户分布&Top3(table+chart)
+          segment: false, // 4, 细分市场
+          buyerWinner: false // 5, 采购单位/中标单位&Top3(table+chart)
+        },
+        market: {
+          overview: [],
+          refine: {
+            dataAlready: false,
+            projectCountData: null,
+            projectAmountData: null,
+            // 项目数量Top3
+            projectCountTop3: null,
+            // 项目金额Top3
+            projectAmountTop3: null
+          }
+        },
+        projectScatter: {
+          dataAlready: false,
+          chartData: null,
+          tableData: []
+        },
+        timeScatter: {
+          dataAlready: false, // 数据准备好之后才能开始渲染
+          activeAction: 'month',
+          actionList: [
+            {
+              label: '月度数据',
+              value: 'month'
+            },
+            {
+              label: '年度数据',
+              value: 'year'
+            }
+          ],
+          month: {
+            count: {},
+            amount: {}
+          },
+          year: {
+            count: {},
+            amount: {}
+          }
+        },
+        areaScatter: {
+          dataAlready: false,
+          chartData: null,
+          // 项目数量Top3
+          projectCountTop3: null,
+          // 项目金额Top3
+          projectAmountTop3: null
+        },
+        userScatter: {
+          list: [],
+          // 项目数量Top3
+          projectCountTop3: null,
+          // 项目金额Top3
+          projectAmountTop3: null
+        },
+        buyerclass: {
+          dataAlready: false,
+          chartData: null,
+          // 项目数量Top3
+          projectCountTop3: null,
+          // 项目金额Top3
+          projectAmountTop3: null
+        },
+        winner: {
+          dataAlready: false,
+          chartData: null,
+          // 项目数量Top3
+          projectCountTop3: null,
+          // 项目金额Top3
+          projectAmountTop3: null
+        }
+      }
+    }
+  },
+  computed: {
+    notOneAreaFilter () {
+      const area = this.reportFilters.area
+      const showArea = area && (Object.keys(area).length > 1 || Object.keys(area).length === 0)
+      return showArea
+    },
+    buyerclassSectionShow () {
+      const winnerState = this.sections.buyerclass
+      return winnerState.dataAlready && winnerState.projectCountTop3 && winnerState.projectAmountTop3
+    },
+    winnerSectionShow () {
+      const winnerState = this.sections.winner
+      return winnerState.dataAlready && winnerState.projectCountTop3 && winnerState.projectAmountTop3
+    }
+  },
+  watch: {},
+  created () {
+    this.sendRequest()
+  },
+  mounted () {
+    setTimeout(this.calcStickyNav, 1000)
+  },
+  methods: {
+    calcStickyNav () {
+      // const offset = $(`.${this.dimensionsTitle.market.anchor}`).offset()
+      const offset = $('.analysis-result-list').offset()
+      if (offset) {
+        this.stickyed.startFixedTop = offset.top + 5
+      }
+    },
+    onEmpty (info) {
+      this.$emit('onEmpty', info)
+    },
+    async sendRequest () {
+      // 先请求概况(1),判断报告是否为空
+      const query = {
+        rid: this.rid,
+        flag: 1
+      }
+      if (!query.rid) {
+        return this.onEmpty()
+      }
+      const { data, error_code: code, error_msg: msg } = await getReportAnalysisInfo(query)
+
+      if (data && code === 0) {
+        const empty = this.formatterData(query.flag, data)
+        if (empty) {
+          return this.onEmpty()
+        }
+      } else {
+        if (msg.indexOf('项目数量超出上限') === -1) {
+          return this.onEmpty()
+        } else {
+          return this.onEmpty({ msg: '当前分析条件涉及项目数量已超过最大限制,请修改分析条件进行精确分析' })
+        }
+      }
+
+      const flagArr = [
+        0, // 筛选条件
+        // 1, // 市场概括与时间分布
+        2, // 项目规模Top10
+        3, // 项目规模分布/地区规模分布/客户分布/地区分布及客户分布&Top3(table+chart)
+        4, // 细分市场
+        5 // 采购单位/中标单位&Top3(table+chart)
+      ]
+
+      flagArr.forEach(this.getReport)
+    },
+    async getReport (flag) {
+      const query = {
+        rid: this.rid,
+        flag
+      }
+      if (!query.rid) {
+        return
+      }
+
+      this.loading = true
+      this.loaded = false
+
+      const { data, error_code: code } = await getReportAnalysisInfo(query)
+
+      this.loading = false
+      this.loaded = true
+
+      if (data && code === 0) {
+        this.formatterData(flag, data)
+      }
+    },
+    clickTimeScatterTab (item) {
+      this.sections.timeScatter.activeAction = item.value
+    },
+    formatterData (flag, data) {
+      if (flag === 0) {
+        this.sortReportFilters(data)
+      } else if (flag === 1) {
+        // 市场概况
+        const totalCount = this.sortMarketOverview(data.market_profile)
+        if (!totalCount) {
+          return true
+        }
+        // 时间分布
+        this.sortTimeScatter(data)
+        this.sections.loaded.overview = true
+      } else if (flag === 2) {
+        // 项目规模Top10
+        this.sortProjectTop10(data.ProjectTop10)
+        this.sections.loaded.top10 = true
+      } else if (flag === 3) {
+        // 项目规模分布/地区规模分布/客户分布/地区分布及客户分布&Top3(table+chart)
+        // 项目规模分布
+        this.sortProjectScatter(data.projectScale)
+        // 地区规模分布
+        this.sortAreaScatter(data.area_infos)
+        // 客户分布
+        this.sortUserScatter(data.customer_scale)
+        // 地区分布及客户分布Top3
+        this.sortAreaUserTop3(data)
+        this.sections.loaded.top3 = true
+      } else if (flag === 4) {
+        // 细分市场
+        this.sortMarketRefineData(data)
+        this.sections.loaded.segment = true
+      } else if (flag === 5) {
+        // 采购单位/中标单位&Top3(table+chart)
+        this.sortBuyerclassData(data)
+        // 中标单位分析
+        this.sortWinnerData(data)
+        this.sections.loaded.buyerWinner = true
+      }
+    },
+    formatSelectTime (value) {
+      if (!value) return '-'
+      const timeArr = value.split('-')
+      return `${dateFormatter(timeArr[0] * 1000, 'yyyy/MM/dd')}-${dateFormatter(timeArr[1] * 1000, 'yyyy/MM/dd')}`
+    },
+    sortReportFilters (data) {
+      if (data.keysItems && data.keysItems !== '[]') {
+        this.reportFilters.keys = JSON.parse(data.keysItems)
+      }
+      if (data.rangeTime) {
+        this.reportFilters.selectTime = data.rangeTime
+      }
+      if (data.s_rangeTimeExtra) {
+        this.reportFilters.selectTimeExtra = data.s_rangeTimeExtra
+      }
+      if (data.area && data.area !== '{}') {
+        this.reportFilters.area = JSON.parse(data.area)
+      }
+      if (data.industry && data.industry !== '{}') {
+        this.reportFilters.industry = JSON.parse(data.industry)
+      }
+      if (data.buyerclass) {
+        this.reportFilters.buyerclass = data.buyerclass.split(',')
+      }
+      this.$emit('loadedFilters', this.reportFilters)
+    },
+    // 市场概况
+    sortMarketOverview (profile) {
+      if (!profile) return
+      const list = [
+        {
+          label: '项目总数',
+          unit: '个',
+          count: 0,
+          ringRatio: 0
+        },
+        {
+          label: '项目总金额',
+          unit: '万元',
+          count: 0,
+          ringRatio: 0
+        },
+        {
+          label: '项目平均金额',
+          unit: '万元',
+          count: 0,
+          ringRatio: 0
+        },
+        {
+          label: '中标单位数',
+          unit: '家',
+          count: 10628,
+          ringRatio: 0
+        },
+        {
+          label: '采购单位数',
+          unit: '家',
+          count: 16215,
+          ringRatio: 0
+        }
+      ]
+
+      // 项目总数
+      list[0].count = profile.project_count ? profile.project_count : 0
+      list[0].ringRatio = profile.project_count_ratio ? (profile.project_count_ratio * 100).toFixed(2) : 0
+      // 项目总金额
+      const projectTotalMoney = this.moneyUnit(profile.projctamout ? profile.projctamout : 0)
+      list[1].count = projectTotalMoney.count
+      list[1].unit = projectTotalMoney.unit
+
+      list[1].ringRatio = profile.projctamount_ratio ? (profile.projctamount_ratio * 100).toFixed(2) : 0
+      // 项目平均金额
+      const projectAvgMoney = this.moneyUnit(profile.projectavgmoney ? profile.projectavgmoney : 0)
+      list[2].count = projectAvgMoney.count
+      list[2].unit = projectAvgMoney.unit
+      list[2].ringRatio = profile.projectavgmoney_ratio ? (profile.projectavgmoney_ratio * 100).toFixed(2) : 0
+      // 中标单位数
+      list[3].count = profile.winnercount ? profile.winnercount : 0
+      list[3].ringRatio = profile.winnercount_ratio ? (profile.winnercount_ratio * 100).toFixed(2) : 0
+      // 采购单位数
+      list[4].count = profile.buyercount ? profile.buyercount : 0
+      list[4].ringRatio = profile.buyercount_ratio ? (profile.buyercount_ratio * 100).toFixed(2) : 0
+
+      const totalCount = list.reduce((total, item) => item.count + total, 0)
+
+      if (totalCount) {
+        this.sections.market.overview = list
+        this.showDimensionsOptions('market', 'market-overview')
+      }
+
+      return totalCount
+    },
+    // 时间分布
+    sortTimeScatter (data) {
+      const hasDataM = this.sortTimeScatterData('month', data.month_distribution)
+      const hasDataY = this.sortTimeScatterData('year', data.year_distribution)
+
+      const hasData = hasDataM && hasDataY
+      this.sections.timeScatter.dataAlready = hasData
+      if (hasData) {
+        this.showDimensionsOptions('market', 'time-scatter')
+      }
+    },
+    sortTimeScatterData (type, data) {
+      // columns: ['日期', '项目规模', '环比增长率(%)'],
+      // rows: [
+      //   {
+      //     日期: '6月',
+      //     项目规模: 0,
+      //     '环比增长率(%)': -99
+      //   },
+      //   {
+      //     日期: '7月',
+      //     项目规模: 736325,
+      //     '环比增长率(%)': 0
+      //   },
+      // ]
+      if (!data) return
+      // 项目数量
+      const mDCount = {
+        columns: ['日期', '项目数量(个)', '项目数量环比'],
+        rows: []
+      }
+      let mDCountTotal = 0
+      if (Array.isArray(data.project_count)) {
+        const field = {
+          [mDCount.columns[0]]: 'minth',
+          [mDCount.columns[1]]: 'value',
+          [mDCount.columns[2]]: 'ratio'
+        }
+        data.project_count.forEach(item => {
+          const row = {}
+          mDCount.columns.forEach(column => {
+            const value = item[field[column]]
+            if (value) {
+              if (field[column] === 'ratio') {
+                row[column] = formatPrice(value * 100) - 0
+              } else {
+                row[column] = value
+              }
+            } else {
+              row[column] = null
+            }
+
+            if (typeof value === 'number') {
+              mDCountTotal += value
+            }
+          })
+          mDCount.rows.push(row)
+        })
+      }
+
+      if (mDCountTotal || isNaN(mDCountTotal)) {
+        this.$set(this.sections.timeScatter[type], 'count', mDCount)
+      }
+
+      // 项目规模
+      const mDAmount = {
+        columns: ['日期', '项目金额(万元)', '项目金额环比'],
+        rows: []
+      }
+      let mDAmuntTotal = 0
+      if (Array.isArray(data.project_amount)) {
+        const field = {
+          [mDAmount.columns[0]]: 'minth',
+          [mDAmount.columns[1]]: 'value',
+          [mDAmount.columns[2]]: 'ratio'
+        }
+        data.project_amount.forEach(item => {
+          const row = {}
+          mDAmount.columns.forEach(column => {
+            const value = item[field[column]]
+            if (value) {
+              if (field[column] === 'value') {
+                row[column] = formatPrice(value / 10000) - 0
+              } else if (field[column] === 'ratio') {
+                row[column] = formatPrice(value * 100)
+              } else {
+                row[column] = value
+              }
+            } else {
+              row[column] = null
+            }
+
+            if (typeof value === 'number') {
+              mDAmuntTotal += value
+            }
+          })
+          mDAmount.rows.push(row)
+        })
+      }
+
+      if (mDAmuntTotal || !isNaN(mDAmuntTotal)) {
+        this.$set(this.sections.timeScatter[type], 'amount', mDAmount)
+      }
+
+      const r = !!(mDCountTotal + mDAmuntTotal)
+      const hasOneNaN = isNaN(mDCountTotal) || isNaN(mDAmuntTotal)
+      return hasOneNaN || r
+    },
+    // 项目规模分布
+    sortProjectScatter (data) {
+      // const chartData = {
+      //   columns: ['项目规模', '项目总金额占比', '项目总数占比'],
+      //   rows: [
+      //     {
+      //       项目规模: '≥1亿',
+      //       项目总金额占比: 20,
+      //       项目总数占比: 10
+      //     },
+      //     {
+      //       项目规模: '1000万-1亿',
+      //       项目总金额占比: 50,
+      //       项目总数占比: 40
+      //     },
+      //     {
+      //       项目规模: '500万-1000万',
+      //       项目总金额占比: 20,
+      //       项目总数占比: 30
+      //     },
+      //     {
+      //       项目规模: '100万-500万',
+      //       项目总金额占比: 20,
+      //       项目总数占比: 30
+      //     }
+      //   ]
+      // }
+
+      const scaleList = data
+      const scaleData = {
+        columns: ['项目规模', '项目总金额占比', '项目总数占比'],
+        rows: []
+      }
+      let total = 0
+
+      if (scaleList && Array.isArray(scaleList)) {
+        const field = {
+          [scaleData.columns[0]]: 'Name',
+          [scaleData.columns[1]]: 'Persent_c',
+          [scaleData.columns[2]]: 'Persent_a'
+        }
+        scaleList.forEach(item => {
+          const row = {}
+          scaleData.columns.forEach(column => {
+            if (field[column] === 'Persent_c' || field[column] === 'Persent_a') {
+              row[column] = (item[field[column]] * 100).toFixed(2)
+              total += (item[field[column]] - 0)
+            } else {
+              row[column] = item[field[column]]
+            }
+          })
+          scaleData.rows.push(row)
+        })
+      }
+
+      if (total) {
+        scaleData.rows.reverse()
+        this.$set(this.sections.projectScatter, 'chartData', scaleData)
+        if (this.sections.projectScatter.tableData.length) {
+          this.showDimensionsOptions('market', 'project-scatter')
+          this.sections.projectScatter.dataAlready = true
+        }
+      }
+    },
+    // 项目规模Top10
+    sortProjectTop10 (top10List) {
+      if (!Array.isArray(top10List)) return
+
+      this.sections.projectScatter.tableData = top10List.map(top => {
+        let winners = top.winner_s ? top.winner_s.join(',') : ''
+        if (!winners) {
+          winners = []
+        } else {
+          winners = top.winner_s
+        }
+
+        winners = winners.map((item, index) => {
+          return {
+            name: item,
+            id: Array.isArray(top.eidlist) ? top.eidlist[index] : null
+          }
+        })
+
+        return {
+          ...top,
+          area: top.area ? top.area : '-',
+          city: top.city ? top.city : '-',
+          sortprice: top.sortprice ? formatPrice(top.sortprice / 10000) : '-',
+          jgtime: top.jgtime ? dateFormatter(top.jgtime * 1000, 'yyyy-MM-dd') : '-',
+          winner_s: winners
+        }
+      })
+
+      if (this.sections.projectScatter.chartData) {
+        this.showDimensionsOptions('market', 'project-scatter')
+        this.sections.projectScatter.dataAlready = true
+      }
+    },
+    // 地区规模分布
+    sortAreaScatter (areaList) {
+      // const chartData = {
+      //   columns: ['项目所在地', '项目数量', '项目金额'],
+      //   rows: [
+      //     {
+      //       项目所在地: '河南',
+      //       项目数量: 2,
+      //       项目金额: 2222
+      //     },
+      //     {
+      //       项目所在地: '北京',
+      //       项目数量: 22,
+      //       项目金额: 565666
+      //     },
+      //     {
+      //       项目所在地: '浙江',
+      //       项目数量: 22,
+      //       项目金额: 765666
+      //     }
+      //   ]
+      // }
+
+      const areaChartData = {
+        columns: ['项目所在地', '项目数量'],
+        sColumns: ['项目金额'],
+        rows: []
+      }
+      let total = 0
+
+      if (areaList && Array.isArray(areaList)) {
+        const field = {
+          [areaChartData.columns[0]]: 'area',
+          [areaChartData.columns[1]]: 'total',
+          [areaChartData.sColumns[0]]: 'amount'
+        }
+        areaList.forEach(item => {
+          const row = {}
+          areaChartData.columns.concat(areaChartData.sColumns).forEach(column => {
+            if (field[column] === 'amount') {
+              row[column] = formatPrice(item[field[column]] / 10000) - 0
+            } else {
+              row[column] = item[field[column]]
+            }
+
+            if (field[column] === 'amount' || field[column] === 'total') {
+              total += (item[field[column]] - 0)
+            }
+          })
+          areaChartData.rows.push(row)
+        })
+      }
+
+      if (total) {
+        this.$set(this.sections.areaScatter, 'chartData', areaChartData)
+        this.sections.areaScatter.dataAlready = true
+        this.showDimensionsOptions('market', 'area-scatter')
+      }
+    },
+    // 客户分布
+    sortUserScatter (userList) {
+      if (Array.isArray(userList)) {
+        this.sections.userScatter.list = userList.map(item => {
+          return {
+            ...item,
+            name: item.buyclass,
+            value: item.total,
+            amount: formatPrice(item.amount / 10000)
+          }
+        })
+        if (this.sections.userScatter.list.length) {
+          this.showDimensionsOptions('market', 'user-scatter')
+        }
+      }
+    },
+    // 地区分布及客户分布Top3
+    sortAreaUserTop3 (data) {
+      if (data.scaleAreaCountTop || data.scaleAreaAmountTop) {
+        this.sorAreaTop3(data)
+      }
+      if (data.scaleBuyclassCountTop || data.scaleBuyclassAmountTop) {
+        this.sorUserTop3(data)
+      }
+    },
+    sorAreaTop3 (data) {
+      const tableDataCount = {
+        columns: ['序号', '地区:项目数量(个),占比', '前3中标单位:中标数量(个)'], // ,该地区占比
+        rows: []
+      }
+      const tableDataAmount = {
+        columns: ['序号', '地区:项目金额(万元),占比', '前3中标单位:中标金额(万元)'], // ,该地区占比
+        rows: []
+      }
+
+      const scaleAreaCountTop3 = data.scaleAreaCountTop
+      if (Array.isArray(scaleAreaCountTop3)) {
+        scaleAreaCountTop3.forEach((item, index) => {
+          if (Array.isArray(item.winner)) {
+            item.winner.forEach((w, i) => {
+              const row = {
+                index: index + 1,
+                name: item.name,
+                value: item.area_count + '个',
+                percent: item.area_scale ? `${formatPrice(item.area_scale * 100)}%` : '',
+                winner_id: w.id,
+                winner_type: 'winner', // 用于判断采购单位或者中标单位
+                winner_index: i + 1,
+                winner_name: w.winner,
+                winner_value: w.winner_total ? `${w.winner_total}个` : 0,
+                // winner_percent: w.total_scale ? `${formatPrice(w.total_scale * 100)}%` : 0,
+                rowspan: i === 0 ? item.winner.length : 0
+              }
+              tableDataCount.rows.push(row)
+            })
+          } else {
+            const row = {
+              index: index + 1,
+              name: item.name,
+              value: item.area_count + '个',
+              percent: item.area_scale ? `${formatPrice(item.area_scale * 100)}%` : '',
+              rowspan: 1
+            }
+            tableDataCount.rows.push(row)
+          }
+        })
+      }
+
+      const scaleAreaAmountTop3 = data.scaleAreaAmountTop
+      if (Array.isArray(scaleAreaAmountTop3)) {
+        scaleAreaAmountTop3.forEach((item, index) => {
+          if (Array.isArray(item.winner)) {
+            item.winner.forEach((w, i) => {
+              const row = {
+                index: index + 1,
+                name: item.name,
+                value: formatPrice(item.area_amount / 10000) + '万元',
+                percent: item.area_scale ? `${formatPrice(item.area_scale * 100)}%` : '',
+                winner_id: w.id,
+                winner_type: 'winner',
+                winner_index: i + 1,
+                winner_name: w.winner,
+                winner_value: w.winner_amount ? `${formatPrice(w.winner_amount / 10000)}万元` : '-',
+                // winner_percent: w.amount_scale ? `${formatPrice(w.amount_scale * 100)}%` : 0,
+                rowspan: i === 0 ? item.winner.length : 0
+              }
+              tableDataAmount.rows.push(row)
+            })
+          } else {
+            const row = {
+              index: index + 1,
+              name: item.name,
+              value: formatPrice(item.area_amount / 10000) + '万元',
+              percent: item.area_scale ? `${formatPrice(item.area_scale * 100)}%` : '',
+              rowspan: 1
+            }
+            tableDataAmount.rows.push(row)
+          }
+        })
+      }
+
+      if (tableDataCount.rows.length) {
+        this.$set(this.sections.areaScatter, 'projectCountTop3', tableDataCount)
+      }
+      if (tableDataAmount.rows.length) {
+        this.$set(this.sections.areaScatter, 'projectAmountTop3', tableDataAmount)
+      }
+    },
+    sorUserTop3 (data) {
+      const tableDataCount = {
+        columns: ['序号', '客户类型:项目数量(个),占比', '前3中标单位:中标数量(个)'], // ,该客户类型占比
+        rows: []
+      }
+      const tableDataAmount = {
+        columns: ['序号', '客户类型:项目金额(万元),占比', '前3中标单位:中标金额(万元)'], // ,该客户类型占比
+        rows: []
+      }
+
+      const countTop3 = data.scaleBuyclassCountTop
+      if (Array.isArray(countTop3)) {
+        countTop3.forEach((item, index) => {
+          if (Array.isArray(item.winner)) {
+            item.winner.forEach((w, i) => {
+              const row = {
+                index: index + 1,
+                name: item.name,
+                value: item.buyclass_count + '个',
+                percent: item.buyclass_scale ? `${formatPrice(item.buyclass_scale * 100)}%` : '',
+                winner_id: w.id,
+                winner_type: 'winner', // 用于判断采购单位或者中标单位
+                winner_index: i + 1,
+                winner_name: w.winner,
+                winner_value: w.winner_total ? `${w.winner_total}个` : 0,
+                // winner_percent: w.total_scale ? `${formatPrice(w.total_scale * 100)}%` : 0,
+                rowspan: i === 0 ? item.winner.length : 0
+              }
+              tableDataCount.rows.push(row)
+            })
+          } else {
+            const row = {
+              index: index + 1,
+              name: item.name,
+              value: item.area_count + '个',
+              percent: item.buyclass_scale ? `${formatPrice(item.buyclass_scale * 100)}%` : '',
+              rowspan: 1
+            }
+            tableDataCount.rows.push(row)
+          }
+        })
+      }
+
+      const amountTop3 = data.scaleBuyclassAmountTop
+      if (Array.isArray(amountTop3)) {
+        amountTop3.forEach((item, index) => {
+          if (Array.isArray(item.winner)) {
+            item.winner.forEach((w, i) => {
+              const row = {
+                index: index + 1,
+                name: item.name,
+                value: formatPrice(item.buyclass_amount / 10000) + '万元',
+                percent: item.buyclass_scale ? `${formatPrice(item.buyclass_scale * 100)}%` : '',
+                winner_id: w.id,
+                winner_type: 'winner', // 用于判断采购单位或者中标单位
+                winner_index: i + 1,
+                winner_name: w.winner,
+                winner_value: w.winner_amount ? `${formatPrice(w.winner_amount / 10000)}万元` : '-',
+                // winner_percent: w.amount_scale ? `${formatPrice(w.amount_scale * 100)}%` : 0,
+                rowspan: i === 0 ? item.winner.length : 0
+              }
+              tableDataAmount.rows.push(row)
+            })
+          } else {
+            const row = {
+              index: index + 1,
+              name: item.name,
+              value: formatPrice(item.area_amount / 10000) + '万元',
+              percent: item.buyclass_scale ? `${formatPrice(item.buyclass_scale * 100)}%` : '',
+              rowspan: 1
+            }
+            tableDataAmount.rows.push(row)
+          }
+        })
+      }
+
+      if (tableDataCount.rows.length) {
+        this.$set(this.sections.userScatter, 'projectCountTop3', tableDataCount)
+      }
+      if (tableDataAmount.rows.length) {
+        this.$set(this.sections.userScatter, 'projectAmountTop3', tableDataAmount)
+      }
+    },
+    // 细分市场
+    sortMarketRefineData (data) {
+      const refineCount = {
+        columns: ['行业', '项目数量'],
+        rows: []
+      }
+      const refineAmount = {
+        columns: ['行业', '项目金额'],
+        rows: []
+      }
+      let total = 0
+      const refineAll = data.scaleRefineAll
+      if (Array.isArray(refineAll)) {
+        const field = {
+          行业: 'name',
+          项目数量: 'total',
+          项目金额: 'amount'
+        }
+        refineAll.forEach(item => {
+          const row = {}
+          for (const key in field) {
+            if (field[key] === 'amount') {
+              row[key] = formatPrice(item[field[key]] / 10000)
+            } else {
+              row[key] = item[field[key]]
+            }
+
+            if (field[key] === 'total' || field[key] === 'amount') {
+              total += (item[field[key]] - 0)
+            }
+          }
+          refineCount.rows.push(row)
+          refineAmount.rows.push(row)
+        })
+      }
+
+      if (total) {
+        this.$set(this.sections.market.refine, 'projectCountData', refineCount)
+        this.$set(this.sections.market.refine, 'projectAmountData', refineAmount)
+
+        this.sections.market.refine.dataAlready = true
+        this.showDimensionsOptions('market', 'market-refine')
+      }
+
+      this.sortRefineTop3(data)
+    },
+    sortRefineTop3 (data) {
+      const tableDataCount = {
+        columns: ['序号', '细分市场:项目数量(个)', '前3中标单位:中标数量(个)'], // ,占比,该细分市场占比
+        rows: []
+      }
+      const tableDataAmount = {
+        columns: ['序号', '细分市场:项目金额(万元)', '前3中标单位:中标金额(万元)'], // ,占比 ,该细分市场占比
+        rows: []
+      }
+
+      const countTop3 = data.scaleRefineTotalTop
+      if (Array.isArray(countTop3)) {
+        countTop3.forEach((item, index) => {
+          if (Array.isArray(item.topList)) {
+            item.topList.forEach((w, i) => {
+              const row = {
+                index: index + 1,
+                name: item.name,
+                value: item.value + '个',
+                // percent: item.prop ? `${formatPrice(item.prop * 100)}%` : '',
+                winner_id: w.id,
+                winner_type: 'winner',
+                winner_index: i + 1,
+                winner_name: w.name,
+                winner_value: w.value ? `${w.value}个` : 0,
+                // winner_percent: w.prop ? `${formatPrice(w.prop * 100)}%` : 0,
+                rowspan: i === 0 ? item.topList.length : 0
+              }
+              tableDataCount.rows.push(row)
+            })
+          } else {
+            const row = {
+              index: index + 1,
+              name: item.name,
+              value: item.value + '个',
+              // percent: item.prop ? `${formatPrice(item.prop * 100)}%` : '',
+              rowspan: 1
+            }
+            tableDataCount.rows.push(row)
+          }
+        })
+      }
+
+      const amountTop3 = data.scaleRefineAmountTop
+      if (Array.isArray(amountTop3)) {
+        amountTop3.forEach((item, index) => {
+          if (Array.isArray(item.topList)) {
+            item.topList.forEach((w, i) => {
+              const row = {
+                index: index + 1,
+                name: item.name,
+                value: formatPrice(item.value / 10000) + '万元',
+                // percent: item.prop ? `${formatPrice(item.prop * 100)}%` : '',
+                winner_id: w.id,
+                winner_type: 'winner',
+                winner_index: i + 1,
+                winner_name: w.name,
+                winner_value: w.value ? `${formatPrice(w.value / 10000)}万元` : '-',
+                // winner_percent: w.prop ? `${formatPrice(w.prop * 100)}%` : 0,
+                rowspan: i === 0 ? item.topList.length : 0
+              }
+              tableDataAmount.rows.push(row)
+            })
+          } else {
+            const row = {
+              index: index + 1,
+              name: item.name,
+              value: formatPrice(item.value / 10000) + '万元',
+              // percent: item.prop ? `${formatPrice(item.prop * 100)}%` : '',
+              rowspan: 1
+            }
+            tableDataAmount.rows.push(row)
+          }
+        })
+      }
+
+      if (tableDataCount.rows.length) {
+        this.$set(this.sections.market.refine, 'projectCountTop3', tableDataCount)
+      }
+      if (tableDataAmount.rows.length) {
+        this.$set(this.sections.market.refine, 'projectAmountTop3', tableDataAmount)
+      }
+    },
+    // 采购单位
+    sortBuyerclassData (data) {
+      const buyerclassChartData = {
+        columns: ['金额区间', '采购总金额占比', '采购单位数量占比'],
+        rows: []
+      }
+      let total = 0
+
+      const buyerclassList = data.buyer_time_distribution
+      if (Array.isArray(buyerclassList)) {
+        const field = {
+          [buyerclassChartData.columns[0]]: 'key',
+          [buyerclassChartData.columns[1]]: 'total_amount',
+          [buyerclassChartData.columns[2]]: 'total_number'
+        }
+        buyerclassList.forEach(item => {
+          const row = {}
+          buyerclassChartData.columns.forEach(column => {
+            if (field[column] === 'total_amount' || field[column] === 'total_number') {
+              row[column] = (item[field[column]] * 100).toFixed(2)
+              total += (item[field[column]] - 0)
+            } else {
+              row[column] = item[field[column]]
+            }
+          })
+          buyerclassChartData.rows.push(row)
+        })
+      }
+
+      if (total) {
+        buyerclassChartData.rows.reverse()
+        this.$set(this.sections.buyerclass, 'chartData', buyerclassChartData)
+
+        this.sections.buyerclass.dataAlready = true
+        this.showDimensionsOptions('buyer', 'buyerclass-scatter')
+      }
+
+      this.sortBuyerclassTableData(data)
+    },
+    sortBuyerclassTableData (data) {
+      const dataCount = {
+        columns: ['序号', '采购单位:采购数量(个)', '前3中标单位:中标数量(个)'], // ,占比 | ,占该采购单位
+        rows: []
+      }
+      const dataAmount = {
+        columns: ['序号', '采购单位:采购金额(万元)', '前3中标单位:中标金额(万元)'], // ,占比 |  ,占该采购单位
+        rows: []
+      }
+
+      const countTop3 = data.buyer_count_top3
+      if (Array.isArray(countTop3)) {
+        countTop3.forEach((item, index) => {
+          if (Array.isArray(item.winnertop3)) {
+            item.winnertop3.forEach((w, i) => {
+              const row = {
+                index: index + 1,
+                name: item.name,
+                name_type: 'buyer',
+                name_id: item.name,
+                value: item.number + '个',
+                // percent: item.accounted ? `${formatPrice(item.accounted * 100)}%` : '',
+                winner_id: w.id,
+                winner_type: 'winner',
+                winner_index: i + 1,
+                winner_name: w.name,
+                winner_value: w.number ? `${w.number}个` : 0,
+                // winner_percent: w.accounted ? `${formatPrice(w.accounted * 100)}%` : 0,
+                rowspan: i === 0 ? item.winnertop3.length : 0
+              }
+              dataCount.rows.push(row)
+            })
+          } else {
+            const row = {
+              index: index + 1,
+              name: item.name,
+              name_type: 'buyer',
+              name_id: item.name,
+              value: item.number + '个',
+              // percent: item.accounted ? `${formatPrice(item.accounted * 100)}%` : '',
+              rowspan: 1
+            }
+            dataCount.rows.push(row)
+          }
+        })
+      }
+
+      const amountTop3 = data.buyer_amount_top3
+      if (Array.isArray(amountTop3)) {
+        amountTop3.forEach((item, index) => {
+          if (Array.isArray(item.winnertop3)) {
+            item.winnertop3.forEach((w, i) => {
+              const row = {
+                index: index + 1,
+                name: item.name,
+                name_type: 'buyer',
+                name_id: item.name,
+                value: formatPrice(item.amount / 10000) + '万元',
+                // percent: item.accounted ? `${formatPrice(item.accounted * 100)}%` : '',
+                winner_id: w.id,
+                winner_type: 'winner',
+                winner_index: i + 1,
+                winner_name: w.name,
+                winner_value: w.amount ? `${formatPrice(w.amount / 10000)}万元` : '-',
+                // winner_percent: w.accounted ? `${formatPrice(w.accounted * 100)}%` : 0,
+                rowspan: i === 0 ? item.winnertop3.length : 0
+              }
+              dataAmount.rows.push(row)
+            })
+          } else {
+            const row = {
+              index: index + 1,
+              name: item.name,
+              name_type: 'buyer',
+              name_id: item.name,
+              value: formatPrice(item.amount / 10000) + '万元',
+              // percent: item.accounted ? `${formatPrice(item.accounted * 100)}%` : '',
+              rowspan: 1
+            }
+            dataAmount.rows.push(row)
+          }
+        })
+      }
+      if (dataCount.rows.length) {
+        this.$set(this.sections.buyerclass, 'projectCountTop3', dataCount)
+        this.showDimensionsOptions('buyer', 'buyerclass-count-top3')
+      }
+      if (dataAmount.rows.length) {
+        this.$set(this.sections.buyerclass, 'projectAmountTop3', dataAmount)
+        this.showDimensionsOptions('buyer', 'buyerclass-amount-top3')
+      }
+    },
+    // 中标单位
+    sortWinnerData (data) {
+      const chartData = {
+        columns: ['金额区间', '中标总金额占比', '中标单位数量占比'],
+        rows: []
+      }
+      let total = 0
+
+      const chartLIst = data.winner_time_distribution
+      if (Array.isArray(chartLIst)) {
+        const field = {
+          [chartData.columns[0]]: 'key',
+          [chartData.columns[1]]: 'total_amount',
+          [chartData.columns[2]]: 'total_number'
+        }
+        chartLIst.forEach(item => {
+          const row = {}
+          chartData.columns.forEach(column => {
+            if (field[column] === 'total_amount' || field[column] === 'total_number') {
+              row[column] = (item[field[column]] * 100).toFixed(2)
+              total += (item[field[column]] - 0)
+            } else {
+              row[column] = item[field[column]]
+            }
+          })
+          chartData.rows.push(row)
+        })
+      }
+
+      if (total) {
+        chartData.rows.reverse()
+        this.$set(this.sections.winner, 'chartData', chartData)
+
+        this.sections.winner.dataAlready = true
+        this.showDimensionsOptions('winner', 'winner-scatter')
+      }
+
+      this.sortWinnerTableData(data)
+    },
+    sortWinnerTableData (data) {
+      const dataCount = {
+        columns: ['序号', '中标单位:中标数量(个)', '前3采购单位:采购数量(个)'], // ,占比 | ,占该中标单位
+        rows: []
+      }
+      const dataAmount = {
+        columns: ['序号', '中标单位:中标金额(万元)', '前3采购单位:采购金额(万元)'], // ,占比 | ,占该中标单位
+        rows: []
+      }
+
+      const countTop3 = data.winner_count_top3
+      if (Array.isArray(countTop3)) {
+        countTop3.forEach((item, index) => {
+          if (Array.isArray(item.buyertop3)) {
+            item.buyertop3.forEach((w, i) => {
+              const row = {
+                index: index + 1,
+                name: item.name,
+                name_type: 'winner',
+                name_id: item.id,
+                value: item.number + '个',
+                // percent: item.accounted ? `${formatPrice(item.accounted * 100)}%` : '',
+                winner_id: w.name,
+                winner_type: 'buyer',
+                winner_index: i + 1,
+                winner_name: w.name,
+                winner_value: w.number ? `${w.number}个` : 0,
+                // winner_percent: w.accounted ? `${formatPrice(w.accounted * 100)}%` : 0,
+                rowspan: i === 0 ? item.buyertop3.length : 0
+              }
+              dataCount.rows.push(row)
+            })
+          } else {
+            const row = {
+              index: index + 1,
+              name: item.name,
+              value: item.number + '个',
+              name_type: 'winner',
+              name_id: item.id,
+              // percent: item.accounted ? `${formatPrice(item.accounted * 100)}%` : '',
+              rowspan: 1
+            }
+            dataCount.rows.push(row)
+          }
+        })
+      }
+
+      const amountTop3 = data.winner_amount_top3
+      if (Array.isArray(amountTop3)) {
+        amountTop3.forEach((item, index) => {
+          if (Array.isArray(item.buyertop3)) {
+            item.buyertop3.forEach((w, i) => {
+              const row = {
+                index: index + 1,
+                name: item.name,
+                name_type: 'winner',
+                name_id: item.id,
+                value: formatPrice(item.amount / 10000) + '万元',
+                // percent: formatPrice(item.accounted * 100) + '%',
+                winner_id: w.name,
+                winner_type: 'buyer',
+                winner_index: i + 1,
+                winner_name: w.name,
+                winner_value: w.amount ? `${formatPrice(w.amount / 10000)}万元` : '-',
+                // winner_percent: w.accounted ? `${formatPrice(w.accounted * 100)}%` : 0,
+                rowspan: i === 0 ? item.buyertop3.length : 0
+              }
+              dataAmount.rows.push(row)
+            })
+          } else {
+            const row = {
+              index: index + 1,
+              name: item.name,
+              name_type: 'winner',
+              name_id: item.id,
+              value: formatPrice(item.amount / 10000) + '万元',
+              // percent: formatPrice(item.accounted * 100) + '%',
+              rowspan: 1
+            }
+            dataAmount.rows.push(row)
+          }
+        })
+      }
+
+      if (dataCount.rows.length) {
+        this.$set(this.sections.winner, 'projectCountTop3', dataCount)
+        this.showDimensionsOptions('winner', 'winner-count-top3')
+      }
+      if (dataAmount.rows.length) {
+        this.$set(this.sections.winner, 'projectAmountTop3', dataAmount)
+        this.showDimensionsOptions('winner', 'winner-amount-top3')
+      }
+    },
+    showDimensionsOptions (module, anchor) {
+      this.dimensionsOptions[module].find(item => {
+        if (item.anchor === anchor) {
+          item.show = true
+        }
+        return item.anchor === 'anchor'
+      })
+    },
+    dropDownClick (item) {
+      if (!item.show) return
+      const anchor = item.anchor
+      const offset = $('.' + anchor).offset()
+      const headerH = 64
+      if (offset) {
+        $('body,html').animate({ scrollTop: offset.top - headerH - 64 })
+      }
+    },
+    moneyUnit (...args) {
+      const m = moneyUnit(...args)
+      let unit = String(m).match(/[\u4e00-\u9fa5]/g)
+      if (unit && Array.isArray(unit)) {
+        unit = unit.join('')
+      } else {
+        unit = ''
+      }
+      let count = ''
+      if (unit) {
+        count = m.replace(unit, '') - 0
+      }
+      return {
+        unit,
+        count
+      }
+    }
+  }
+}
+</script>
+<style lang="scss">
+.report-dropdown-menu {
+  border-color: $color-text--highlight;
+  min-width: 100px;
+  .el-dropdown-menu__item {
+    padding-left: 0;
+    padding-right: 0;
+    text-align: center;
+    white-space: nowrap;
+    &:hover {
+      color: $color-text--highlight;
+    }
+  }
+}
+</style>
+<style lang="scss" scoped>
+::v-deep {
+  .el-button {
+    border-radius: 0;
+  }
+
+  .cursor {
+    cursor: pointer;
+  }
+
+  .market-time-scatter {
+    display: inline-block;
+    // border: 1px dashed #000;
+  }
+
+  // reset-ele-table
+  .ar-table {
+    margin: 20px 0;
+    .ar-table-title {
+      padding: 10px 0;
+      font-size: 16px;
+      color: #1D1D1D;
+      line-height: 24px;
+      text-align: center;
+    }
+    .ar-table-thead {
+      th {
+        background-color: #F9FAFB;
+        font-size: 14px;
+        color: #686868;
+        line-height: 22px;
+      }
+    }
+    .ar-table-row {
+      font-size: 14px;
+      color: #1d1d1d;
+      line-height: 22px;
+    }
+
+    // 表格首页索引
+    .table-index-rect {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin: 0 auto;
+      width: 20px;
+      height: 20px;
+      color: #fff;
+      font-size: 14px;
+      border-radius: 2px;
+      &.red {
+        background-color: #FF3A20;
+      }
+      &.orange {
+        background-color: #FF9F3F;
+      }
+      &.soft-orange {
+        background-color: #EED08C;
+      }
+    }
+  }
+
+  .j-tag {
+    padding: 2px 8px;
+    font-size: 14px;
+    line-height: 22px;
+    border-radius: 4px;
+    &:not(:last-of-type) {
+      margin-right: 6px;
+    }
+    &.tag-lightblue {
+      color: #2CB7CA;
+      background-color: rgba(44, 183, 202, 0.08);
+    }
+    &.tag-orange {
+      color: #F56500;
+      background-color: rgba(255, 159, 63, 0.08);
+    }
+  }
+}
+
+.el-loading-parent--relative {
+  min-height: 300px;
+}
+
+.pd-lr20 {
+  padding: 0 20px;
+}
+
+i.el-icon-caret-bottom {
+  color: #686868;
+  &.highlight {
+    color: #fff;
+  }
+}
+
+.analysis-result-anchors {
+  padding: 20px;
+  height: 120px;
+  border-radius: 4px;
+}
+
+.analysis-wrap {
+  display: flex;
+  align-items: center;
+  padding: 6px 0;
+  color: #686868;
+  line-height: 22px;
+  .analysis-label {
+    font-size: 14px;
+    max-width: 130px;
+  }
+  .analysis-content {
+    margin-left: 20px;
+    font-size: 12px;
+    .limit-time {
+      color: #1d1d1d;
+      font-size: 14px;
+    }
+  }
+}
+
+.analysis-result-section {
+  position: relative;
+  border-radius: 4px;
+  .analysis-result-title {
+    position: relative;
+    display: flex;
+    align-items: center;
+    height: 52px;
+    font-size: 18px;
+    color: #1D1D1D;
+    border-bottom: 1px solid #ECECEC;
+    &::before {
+      content: '';
+      position: absolute;
+      top: 50%;
+      left: 0;
+      width: 3px;
+      height: 24px;
+      background-color: $color-text--highlight;
+      border-radius: 2px;
+      transform: translateY(-50%);
+    }
+  }
+}
+
+.sub-section {
+  padding: 20px;
+  .sub-section-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 20px 0;
+    .sub-section-title {
+      font-size: 16px;
+      color: #1D1D1D;
+      line-height: 24px;
+    }
+    .sub-section-action {
+      display: flex;
+      font-size: 14px;
+      line-height: 22px;
+      .s-a-item {
+        padding: 6px 16px;
+        color: #1D1D1D;
+        border: 1px solid #e0e0e0;
+        cursor: pointer;
+        &.active {
+          color: #fff;
+          border-color: #2CB7CA;
+          background-color: #2CB7CA;
+        }
+      }
+    }
+  }
+}
+
+.analysis-dimensions {
+  .dimensions-options {
+    position: relative;
+    padding: 4px 14px;
+    color: #1D1D1D;
+    border: 1px solid #E0E0E0;
+    &:not(:last-of-type) {
+      border-right: none;
+    }
+    &:hover,
+    &.highlight {
+      color: #fff;
+      background-color: #2CB7CA;
+      i {
+        color: #fff;
+      }
+    }
+    &:first-of-child {
+      border-radius: 0px 2px 2px 0px;
+    }
+    &:last-of-child {
+      border-radius: 2px 0px 0px 2px;
+    }
+    &:not(:first-of-child) {
+      margin-left: -1px
+    }
+  }
+}
+
+.dim-fixed {
+  left: calc(50% + 100px);
+  transform: translateX(-50%);
+  width: 1000px;
+  padding: 16px;
+  background-color: #fff;
+  box-shadow: 0px 4px 16px 1px rgba(0, 0, 0, 0.06);
+}
+</style>

+ 64 - 0
src/views/analysisReport/components/BidderScaleScatter.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="bidder-scale-scatter">
+    <MarketLineChart :chartData="chartData" />
+  </div>
+</template>
+
+<script>
+import MarketLineChart from '@/views/analysisReport/components/MarketLineChart'
+export default {
+  name: 'bidder-scale-scatter',
+  components: {
+    MarketLineChart
+  },
+  props: {
+    chartData: {
+      type: Object,
+      default () {
+        return {
+          columns: ['金额区间', '采购总金额占比', '采购单位数量占比'],
+          rows: [
+            // {
+            //   金额区间: '>=1亿',
+            //   采购总金额占比: 2,
+            //   采购单位数量占比: 222
+            // },
+            // {
+            //   金额区间: '1000万-1亿',
+            //   采购总金额占比: 322,
+            //   采购单位数量占比: 2222
+            // },
+            // {
+            //   金额区间: '500万-1000万',
+            //   采购总金额占比: 1,
+            //   采购单位数量占比: 111
+            // }
+          ]
+        }
+      }
+    },
+    projectCountTableData: {
+      type: Object,
+      default () {
+        return {
+          column: [],
+          row: []
+        }
+      }
+    },
+    projectAmountTableData: {
+      type: Object,
+      default () {
+        return {
+          column: [],
+          row: []
+        }
+      }
+    }
+  },
+  data () {
+    return {}
+  },
+  methods: {}
+}
+</script>

+ 46 - 0
src/views/analysisReport/components/BuyerScaleScatter.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="buyer-scale-scatter">
+    <MarketLineChart :chartData="chartData" />
+  </div>
+</template>
+
+<script>
+import MarketLineChart from '@/views/analysisReport/components/MarketLineChart'
+export default {
+  name: 'buyer-scale-scatter',
+  components: {
+    MarketLineChart
+  },
+  props: {
+    chartData: {
+      type: Object,
+      default () {
+        return {
+          columns: ['金额区间', '采购总金额占比', '采购单位数量占比'],
+          rows: [
+            // {
+            //   金额区间: '>=1亿',
+            //   采购总金额占比: 2,
+            //   采购单位数量占比: 222
+            // },
+            // {
+            //   金额区间: '1000万-1亿',
+            //   采购总金额占比: 322,
+            //   采购单位数量占比: 2222
+            // },
+            // {
+            //   金额区间: '500万-1000万',
+            //   采购总金额占比: 1,
+            //   采购单位数量占比: 111
+            // }
+          ]
+        }
+      }
+    }
+  },
+  data () {
+    return {}
+  },
+  methods: {}
+}
+</script>

+ 84 - 0
src/views/analysisReport/components/MarketAreaScatter.vue

@@ -0,0 +1,84 @@
+<template>
+  <div class="market-area-scatter">
+    <MapChart id="area" :options="options" :datas="chartData"></MapChart>
+  </div>
+</template>
+
+<script>
+import MapChart from '@/components/chart/MapChart'
+
+export default {
+  name: 'market-area-scatter',
+  components: {
+    MapChart
+  },
+  props: {
+    chartData: {
+      type: Object,
+      default () {
+        return {
+          columns: ['项目所在地', '项目数量', '项目金额'],
+          rows: [
+          //   {
+          //     项目所在地: '河南',
+          //     项目数量: 2,
+          //     项目金额: 2222
+          //   },
+          //   {
+          //     项目所在地: '北京',
+          //     项目数量: 22,
+          //     项目金额: 565666
+          //   },
+          //   {
+          //     项目所在地: '浙江',
+          //     项目数量: 22,
+          //     项目金额: 765666
+          //   }
+          ]
+        }
+      }
+    }
+  },
+  data () {
+    return {
+      options: {
+        height: '570px',
+        colors: ['#05A6F3', '#0BD991', '#FF9F40'],
+        config: this.configArea
+      }
+    }
+  },
+  methods: {
+    configArea (options) {
+      const arr = this.chartData.rows || []
+      const maxNum = Math.max.apply(Math, arr.map((o) => { return o['项目数量'] }))
+      options.visualMap.min = 1
+      options.visualMap.max = maxNum < 100 ? 100 : maxNum
+      options.graphic[0].children[0].style.text = '项目数量(个)'
+      options.graphic[options.graphic.length - 1].children[0].style.text = maxNum > 100 ? maxNum : 100
+      options.graphic[options.graphic.length - 1].children[1].style.text = 1
+      options.tooltip.formatter = params => {
+        const obj = {}
+        let tip = ''
+        arr.forEach((v) => {
+          if (v['项目所在地'] === params.name) {
+            for (const key in v) {
+              obj[key] = v[key]
+            }
+          }
+        })
+        if (Object.keys(obj).length > 0) {
+          const regArea = '<span style="color: #999">' + obj['项目所在地'] + '</span></br>'
+          const count = '<span>项目数量:' + obj['项目数量'] + '个</span></br>'
+          const scale = '<span>项目金额:' + obj['项目金额'] + '万元</span></br>'
+          tip = regArea + count + scale
+        } else {
+          tip = ''
+        }
+        return tip
+      }
+      return options
+    }
+  }
+}
+</script>

+ 93 - 0
src/views/analysisReport/components/MarketLineChart.vue

@@ -0,0 +1,93 @@
+<template>
+  <div class="market-line-chart">
+    <LineChart :options="options" :datas="chartData" :extend="extend" />
+  </div>
+</template>
+
+<script>
+import LineChart from '@/components/chart/LineChart'
+export default {
+  name: 'market-line-chart',
+  components: {
+    LineChart
+  },
+  props: {
+    chartData: {
+      type: Object,
+      default () {
+        return {
+          columns: [],
+          rows: []
+        }
+      }
+    }
+  },
+  data () {
+    return {
+      extend: {
+        yAxis: {
+          axisLabel: {
+            formatter: p => p + '%'
+          }
+        }
+      },
+      options: {
+        height: '326px',
+        colors: ['#05A6F3', '#FF9F40'],
+        config: this.configOptions
+      }
+    }
+  },
+  methods: {
+    configOptions (options) {
+      // 面积颜色-渐变
+      Object.assign(options.series[0], {
+        areaStyle: {
+          normal: {
+            color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              {
+                offset: 0,
+                color: 'rgba(42, 190, 209, 0.12)'
+              },
+              {
+                offset: 1,
+                color: 'rgba(42, 190, 209, 0)'
+              }
+            ], false)
+          }
+        }
+      })
+      Object.assign(options.series[1], {
+        areaStyle: {
+          normal: {
+            color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              {
+                offset: 0,
+                color: 'rgba(255, 159, 63, 0.12)'
+              },
+              {
+                offset: 1,
+                color: 'rgba(255, 159, 63, 0)'
+              }
+            ], false)
+          }
+        }
+      })
+
+      Object.assign(options.legend, {
+        icon: 'rect'
+      })
+
+      options.tooltip.formatter = params => {
+        let tip = `<div style="padding-top:2px;color:#9B9CA3;">${params[0].name}</div>`
+        for (let i = 0; i < params.length; i++) {
+          params[i].marker = '<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:' + params[i].color + ';"></span>'
+          tip += params[i].marker + params[i].seriesName + ':' + params[i].value[1].toString().replace(/,/, '') + '%' + '<br/>'
+        }
+        return tip
+      }
+      return options
+    }
+  }
+}
+</script>

+ 137 - 0
src/views/analysisReport/components/MarketOverview.vue

@@ -0,0 +1,137 @@
+<template>
+  <div class="market-overview">
+    <div class="overview-list">
+      <div class="overview-item flex-c-c center"
+        v-for="(item, index) in overviewList"
+        :key="index">
+        <div class="o-item-label">{{ item.label }}({{ item.unit }})</div>
+        <div class="o-item-count">{{ item.count }}</div>
+        <div
+          class="o-item-percent flex-r-c center"
+          v-show="overviewRateTotal"
+          :class="{
+            'color-red': item.ringRatio > 0,
+            'icon-reverse color-green': item.ringRatio < 0,
+          }">
+          <span class="o-item-percent-text">环比:{{ item.ringRatio ? (Math.abs(item.ringRatio) + '%') : '-' }}</span>
+          <span class="el-icon-top" v-if="item.ringRatio"></span>
+        </div>
+      </div>
+    </div>
+    <div class="overview-tip">环比:统计学术语,是表示连续2个统计周期(比如连续两月)内的量的变化比。</div>
+  </div>
+</template>
+
+<script>
+import { Icon } from 'element-ui'
+export default {
+  name: 'market-overview',
+  components: {
+    [Icon.name]: Icon
+  },
+  props: {
+    overviewList: {
+      type: Array,
+      default () {
+        return [
+          // {
+          //   label: '项目总数',
+          //   unit: '个',
+          //   count: 31772,
+          //   ringRatio: 0.2,
+          // },
+          // {
+          //   label: '项目总金额',
+          //   unit: '亿元',
+          //   count: 29.82,
+          //   ringRatio: -0.2,
+          // },
+          // {
+          //   label: '项目平均金额',
+          //   unit: '万元',
+          //   count: 10.04,
+          //   ringRatio: 0.3,
+          // },
+          // {
+          //   label: '中标单位数',
+          //   unit: '家',
+          //   count: 10628,
+          //   ringRatio: -0.3,
+          // },
+          // {
+          //   label: '采购单位数',
+          //   unit: '家',
+          //   count: 16215,
+          //   ringRatio: 0.4,
+          // }
+        ]
+      }
+    }
+  },
+  computed: {
+    overviewRateTotal () {
+      var total = 0
+      this.overviewList.forEach(function (item) {
+        if (item.ringRatio !== undefined && item.ringRatio !== null) {
+          total += item.ringRatio
+        }
+      })
+      return total
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.overview-list {
+  display: flex;
+  align-items: center;
+  .overview-item {
+    padding: 10px 0;
+    flex: 1;
+    &:not(:first-of-type) {
+      border-left: 1px solid #ececec;
+    }
+
+    .o-item-label {
+      font-size: 14px;
+      color: #686868;
+      line-height: 22px;
+    }
+    .o-item-count {
+      margin: 4px 0;
+      font-size: 24px;
+      color: #1d1d1d;
+      line-height: 28px;
+    }
+    .o-item-percent {
+      font-size: 16px;
+      line-height: 20px;
+      color: #686868;
+      &.color {
+        &-red {
+          color: #FF3A20;
+        }
+        &-green {
+          color: #04B15E;
+        }
+      }
+      &-text {
+        margin-right: 2px;
+      }
+    }
+  }
+  .icon-reverse {
+    .el-icon-top {
+      transform: rotate(180deg);
+    }
+  }
+}
+.overview-tip {
+  margin-top: 14px;
+  font-size: 12px;
+  color: #999;
+  line-height: 18px;
+  text-align: center;
+}
+
+</style>

+ 147 - 0
src/views/analysisReport/components/MarketSegment.vue

@@ -0,0 +1,147 @@
+<template>
+  <div class="market-segment">
+    <BarLineChart :datas="projectCountData" :options="getProjectOptions('count')"></BarLineChart>
+    <BarLineChart :datas="projectAmountData" :options="getProjectOptions('amount')"></BarLineChart>
+  </div>
+</template>
+
+<script>
+import BarLineChart from '@/components/chart/BarLineChart'
+export default {
+  name: 'market-segment',
+  components: {
+    BarLineChart
+  },
+  props: {
+    projectCountData: {
+      type: Object,
+      default () {
+        return {
+          columns: ['行业', '项目数量'],
+          rows: [
+          //   {
+          //     行业: '6月3',
+          //     项目数量: 0
+          //   },
+          //   {
+          //     行业: '6月2',
+          //     项目数量: 0
+          //   },
+          //   {
+          //     行业: '6月2',
+          //     项目数量: 0
+          //   },
+          //   {
+          //     行业: '6月4',
+          //     项目数量: 0
+          //   },
+          //   {
+          //     行业: '65月',
+          //     项目数量: 0
+          //   }
+          ]
+        }
+      }
+    },
+    projectAmountData: {
+      type: Object,
+      default () {
+        return {
+          columns: ['行业', '项目金额'],
+          rows: [
+            // {
+            //   行业: '6月2',
+            //   项目金额: 2
+            // },
+            // {
+            //   行业: '6月3',
+            //   项目金额: 3
+            // },
+            // {
+            //   行业: '6月4',
+            //   项目金额: 4
+            // },
+            // {
+            //   行业: '6月4',
+            //   项目金额: 0
+            // },
+            // {
+            //   行业: '6月5',
+            //   项目金额: 5
+            // }
+          ]
+        }
+      }
+    }
+  },
+  methods: {
+    getProjectOptions (type) {
+      const color = type === 'count' ? '#05A6F3' : '#FF9F3F'
+      return {
+        height: '320px',
+        width: '460px',
+        colors: [color],
+        config: this.config(type)
+      }
+    },
+    config (type) {
+      return options => {
+        const suffix = type === 'count' ? '个' : '万元'
+        options.xAxis[0].axisLabel.rotate = 60
+        options.xAxis[0].axisLabel.formatter = name => {
+          if (name && name.length > 4) {
+            return `${name.slice(0, 4)}...`
+          } else {
+            return name
+          }
+        }
+
+        // 设置时间轴
+        Object.assign(options, {
+          dataZoom: {
+            show: true, // 显示滚动条
+            realtime: true, // 拖动时,是否实时更新系列的视图
+            type: 'slider',
+            height: 20,
+            bottom: 0
+          }
+        })
+        options.legend.bottom = 30
+        options.legend.formatter = name => {
+          return `${name}(${suffix})`
+        }
+
+        if (type === 'count') {
+          options.legend.data = [
+            { icon: 'rect', name: this.projectCountData.columns[1] }
+          ]
+        } else if (type === 'amount') {
+          options.legend.data = [
+            { icon: 'rect', name: this.projectAmountData.columns[1] }
+          ]
+        }
+
+        options.tooltip.axisPointer.shadowStyle.color = 'rgba(5,166,243,0.1)'
+        options.tooltip.formatter = params => {
+          let tip = `<div style="padding-top:2px;color:#9B9CA3;">${params[0].name}</div>`
+          for (let i = 0; i < params.length; i++) {
+            if (params[i].value === undefined) {
+              params[i].value = 0
+            }
+            if (i === 0) {
+              tip = tip + params[i].marker + params[i].seriesName + ':' + params[i].value + suffix + '<br/>'
+            }
+          }
+          return tip
+        }
+        return options
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.market-segment {
+  display: flex;
+}
+</style>

+ 115 - 0
src/views/analysisReport/components/MarketTimeScatter.vue

@@ -0,0 +1,115 @@
+<template>
+  <div class="market-time-scatter">
+    <BarLineChart :datas="chartData" :options="options"></BarLineChart>
+  </div>
+</template>
+
+<script>
+import BarLineChart from '@/components/chart/BarLineChart'
+export default {
+  name: 'market-time-scatter',
+  components: {
+    BarLineChart
+  },
+  props: {
+    chartData: {
+      type: Object,
+      default () {
+        return {
+          columns: ['日期', '项目规模', '环比增长率(%)'],
+          rows: [
+          //   {
+          //     日期: '6月',
+          //     项目规模: 0,
+          //     '环比增长率(%)': -99
+          //   },
+          //   {
+          //     日期: '7月',
+          //     项目规模: 736325,
+          //     '环比增长率(%)': 0
+          //   }
+          ]
+        }
+      }
+    },
+    width: null,
+    // 柱状图颜色
+    barChartColor: {
+      type: String,
+      default: '#05A6F3'
+    }
+  },
+  data () {
+    return {
+      options: {
+        height: '320px',
+        width: this.width,
+        colors: [this.barChartColor, '#FF3A20'],
+        settings: {
+          showLine: [this.chartData.columns[2]],
+          axisSite: { right: [this.chartData.columns[2]] }
+        },
+        config: this.newTimeConfig
+      }
+    }
+  },
+  methods: {
+    newTimeConfig (options) {
+      options.xAxis[0].axisLabel.rotate = 60
+      options.xAxis[0].axisLabel.interval = 'auto'
+
+      options.yAxis[1].axisLabel.show = true
+
+      if (this.chartData.rows.length > 12) {
+        // 设置时间轴
+        Object.assign(options, {
+          // grid解决dataZoom文字被隐藏的问题
+          // https://github.com/apache/echarts/issues/11601
+          grid: {
+            left: '5%',
+            right: '13%'
+          },
+          dataZoom: {
+            show: true, // 显示滚动条
+            realtime: true, // 拖动时,是否实时更新系列的视图
+            type: 'slider',
+            height: 20,
+            bottom: 0,
+            textStyle: {
+              fontSize: 10
+            }
+          }
+        })
+      }
+
+      options.legend.bottom = 30
+      options.legend.data = [
+        { icon: 'rect', name: this.chartData.columns[1] },
+        { icon: 'line', name: this.chartData.columns[2] }
+      ]
+
+      options.tooltip.axisPointer.shadowStyle.color = 'rgba(5,166,243,0.1)'
+      options.tooltip.formatter = params => {
+        let tip = `<div style="padding-top:2px;color:#9B9CA3;">${params[0].name}</div>`
+        for (let i = 0; i < params.length; i++) {
+          if (params[i].value === undefined || params[i].value === '') {
+            params[i].value = 0
+          }
+          if (params[i].seriesName.indexOf('环比') === -1) {
+            const match = params[i].seriesName.match(/((.*))$/g)
+            let unit = ''
+            if (Array.isArray(match)) {
+              unit = match.join('').replace(/[()]/g, '')
+            }
+            tip = tip + params[i].marker + params[i].seriesName.replace(`(${unit})`, '') + ':' + params[i].value + unit + '<br/>'
+          } else {
+            tip = tip + params[i].marker + params[i].seriesName + ':' + params[i].value + '%' + '<br/>'
+          }
+        }
+        return tip
+      }
+      return options
+    }
+  }
+}
+</script>

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

@@ -0,0 +1,155 @@
+<template>
+  <div class="market-top3-table">
+    <el-table
+      :data="tableData.rows"
+      header-row-class-name="ar-table-thead"
+      row-class-name="ar-table-row"
+      :span-method="objectSpanMethod"
+      border
+      style="width: 100%">
+      <el-table-column
+        align="center"
+        header-align="center"
+        type="index"
+        :label="tableData.columns[0]"
+        width="50">
+        <template slot-scope="scope">
+          <span
+            :class="{
+              'table-index-rect': scope.row.index <= 3,
+              red: scope.row.index === 1,
+              orange: scope.row.index === 2,
+              'soft-orange': scope.row.index === 3
+            }"
+            >{{ scope.row.index }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        header-align="left"
+        width="320"
+        :label="tableData.columns[1]">
+        <template slot-scope="scope">
+          <div class="area" v-if="scope.row.name_type">
+            <el-link
+              :underline="false"
+              :disabled="!scope.row.name_id"
+              @click="toOtherPage(scope.row.name_id, scope.row.name_type)">{{ scope.row.name }}</el-link>
+          </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-orange" v-if="scope.row.percent">{{ scope.row.percent }}</span>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column
+        header-align="left"
+        :label="tableData.columns[2]">
+        <template slot-scope="scope">
+          <div class="area" v-if="scope.row.winner_name">
+            <span>
+              {{ scope.row.winner_index }}.&nbsp;
+              <el-link
+                :underline="false"
+                :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 && scope.row.winner_percent">,</span>
+            <span v-if="scope.row.winner_percent">{{ scope.row.winner_percent }}</span>
+          </div>
+          <div v-else>-</div>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+
+<script>
+import { Table, TableColumn, Link } from 'element-ui'
+export default {
+  name: 'market-top3-table',
+  components: {
+    [Link.name]: Link,
+    [Table.name]: Table,
+    [TableColumn.name]: TableColumn
+  },
+  props: {
+    tableData: {
+      type: Object,
+      default () {
+        return {
+          columns: ['序号', '地区:项目数量(个),占比', '前3中标单位:中标数量(个),该地区占比'],
+          rows: [
+          //   {
+          //     name: '北京',
+          //     value: 43232,
+          //     percent: '12.52%',
+          //     winner_name: '北京剑鱼',
+          //     winner_value: 45,
+          //     winner_percent: '3%',
+          //     winnerCount: 3,
+          //     winner_id: 1
+          //   },
+          //   {
+          //     name: '北京',
+          //     value: 43232,
+          //     percent: '12.52%',
+          //     winner_name: '北京剑鱼',
+          //     winner_value: 45,
+          //     winner_percent: '3%',
+          //     winnerCount: 3,
+          //     winner_id: 2
+          //   },
+          //   {
+          //     name: '北京',
+          //     value: 43232,
+          //     percent: '12.52%',
+          //     winner_name: '北京剑鱼',
+          //     winner_value: 45,
+          //     winner_percent: '3%',
+          //     winnerCount: 3,
+          //     winner_id: 3
+          //   }
+          ]
+        }
+      }
+    }
+  },
+  methods: {
+    toOtherPage (id, type) {
+      let routeUrl = {
+        url: ''
+      }
+      if (type === 'winner') {
+        routeUrl = this.$router.resolve({
+          path: `/svip/ent_ser_portrait/${id}`
+        })
+      } else if (type === 'buyer') {
+        routeUrl = this.$router.resolve({
+          path: `/unit_portrayal/${id}`
+        })
+      }
+      window.open(routeUrl.href)
+    },
+    objectSpanMethod ({ row, column, rowIndex, columnIndex }) {
+      if (columnIndex === 0 || columnIndex === 1) {
+        return {
+          rowspan: row.rowspan,
+          colspan: 1
+        }
+      }
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+::v-deep {
+  // reset-ele
+  .area {
+    a {
+      vertical-align: unset;
+    }
+  }
+}
+</style>

+ 55 - 0
src/views/analysisReport/components/MarketUserScatter.vue

@@ -0,0 +1,55 @@
+<template>
+  <div class="market-user-scatter-chart">
+    <RectTreeMapChart :datas="chartData" :options="options" />
+  </div>
+</template>
+
+<script>
+import RectTreeMapChart from '@/components/chart/RectTreeMapChart'
+import { moneyUnit } from '@/utils/globalFunctions'
+
+export default {
+  name: 'market-line-chart',
+  components: {
+    RectTreeMapChart
+  },
+  props: {
+    chartData: {
+      type: Array,
+      default () {
+        return []
+      }
+    }
+  },
+  data () {
+    return {
+      options: {
+        title: {
+          subtext: '单位:个、万元',
+          left: '0',
+          textStyle: {
+            color: '#000',
+            fontWeight: 'normal'
+          }
+        },
+        tooltip: {
+          formatter: this.tooltipFormatter
+        }
+      }
+    }
+  },
+  methods: {
+    tooltipFormatter (params) {
+      const item = params.data
+      let tip = `<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:${params.color};"></span>${params.name}<br />`
+      if (item && item.value) {
+        tip += `<div>项目数量:${item.value}个<div>`
+      }
+      if (item && item.amount) {
+        tip += `<div>项目金额:${item.amount}万元<div>`
+      }
+      return tip
+    }
+  }
+}
+</script>

+ 162 - 0
src/views/analysisReport/components/ProjectScatter.vue

@@ -0,0 +1,162 @@
+<template>
+  <div class="project-scatter">
+    <div class="project-scatter-chart" v-if="chartData.rows.length">
+      <SimpleHistogramChart
+        :id="id"
+        :data="chartData"
+      />
+    </div>
+    <div class="ar-table" v-if="tableData.length">
+      <p class="ar-table-title">项目规模TOP10</p>
+      <el-table
+        :data="tableData"
+        header-row-class-name="ar-table-thead"
+        row-class-name="ar-table-row"
+        border
+        style="width: 100%">
+        <el-table-column
+          align="center"
+          header-align="center"
+          type="index"
+          label="序号"
+          width="50">
+          <template slot-scope="scope">
+            <span
+              :class="{
+                'table-index-rect': scope.$index < 3,
+                red: scope.$index === 0,
+                orange: scope.$index === 1,
+                'soft-orange': scope.$index === 2
+              }"
+              >{{ scope.$index + 1 }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column
+          align="left"
+          header-align="center"
+          class-name="cursor"
+          label="项目名称">
+          <template slot-scope="scope">
+            <div @click="toContentPage(scope.row)">{{ scope.row.projectname }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column
+          align="center"
+          header-align="center"
+          width="100px"
+          prop="area"
+          label="省份">
+        </el-table-column>
+        <el-table-column
+          align="center"
+          header-align="center"
+          prop="city"
+          width="100px"
+          label="城市">
+        </el-table-column>
+        <el-table-column
+          align="right"
+          header-align="center"
+          width="140px"
+          prop="sortprice"
+          label="项目金额(万元)">
+        </el-table-column>
+        <el-table-column
+          align="center"
+          header-align="center"
+          prop="jgtime"
+          width="120px"
+          label="成交时间">
+        </el-table-column>
+        <el-table-column
+          header-align="center"
+          class-name="cursor"
+          label="中标单位">
+          <template slot-scope="scope">
+            <el-link
+              @click="toEntProPage(item.id)"
+              :underline="false"
+              :disabled="!item.id"
+              v-for="(item, index) in (scope.row.winner_s || [])"
+              :key="index"
+            >{{ item.name }}<span v-if="index !== scope.row.winner_s.length - 1">,</span></el-link>
+            <div v-if="scope.row.winner_s.length === 0">-</div>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+  </div>
+</template>
+
+<script>
+import { Table, TableColumn, Link } from 'element-ui'
+import SimpleHistogramChart from '@/components/chart/SimpleHistogramChart.vue'
+export default {
+  name: 'project-scatter',
+  components: {
+    [Link.name]: Link,
+    [Table.name]: Table,
+    [TableColumn.name]: TableColumn,
+    SimpleHistogramChart
+  },
+  props: {
+    chartData: {
+      type: Object,
+      default () {
+        return {
+          columns: ['项目规模', '项目总金额占比', '项目总数占比'],
+          rows: [
+          //   {
+          //     项目规模: '≥1亿',
+          //     项目总金额占比: 20,
+          //     项目总数占比: 10
+          //   },
+          //   {
+          //     项目规模: '1000万-1亿',
+          //     项目总金额占比: 50,
+          //     项目总数占比: 40
+          //   },
+          //   {
+          //     项目规模: '500万-1000万',
+          //     项目总金额占比: 20,
+          //     项目总数占比: 30
+          //   },
+          //   {
+          //     项目规模: '100万-500万',
+          //     项目总金额占比: 20,
+          //     项目总数占比: 30
+          //   }
+          ]
+        }
+      }
+    },
+    tableData: {
+      type: Array,
+      default () {
+        return []
+      }
+    }
+  },
+  data () {
+    return {
+      id: `ve-histogram-${Date.now()}`
+    }
+  },
+  methods: {
+    // 去三级页
+    toContentPage (row) {
+      window.open(`/article/content/${row._id}.html`)
+    },
+    // 去企业画像
+    toEntProPage (id) {
+      const routeUrl = this.$router.resolve({
+        path: `/svip/ent_ser_portrait/${id}`
+      })
+      window.open(routeUrl.href)
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+
+</style>

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

@@ -0,0 +1,127 @@
+<template>
+  <div class="analysis-report">
+    <el-tabs v-model="activeTabName">
+      <el-tab-pane label="定制化市场分析" name="analysis" lazy>
+        <MarketAnalysis @refreshTab2="needRefreshTab2 = true" ref="marketAnalysis" />
+      </el-tab-pane>
+      <el-tab-pane label="历史报告" name="history" lazy>
+        <ReportHistoryList :key="historyKey" />
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import { Tabs, TabPane } from 'element-ui'
+import ReportHistoryList from '@/components/report-data/ReportList.vue'
+import MarketAnalysis from '@/views/analysisReport/MarketAnalysis.vue'
+export default {
+  name: 'analysis-report-index',
+  components: {
+    [Tabs.name]: Tabs,
+    [TabPane.name]: TabPane,
+    ReportHistoryList,
+    MarketAnalysis
+  },
+  data () {
+    return {
+      activeTabName: 'analysis',
+      needRefreshTab2: false, // 需不需要实时刷新tab2
+      historyKey: '',
+      analysisTab: {
+        scrollTop: 0
+      },
+      historyTab: {
+        scrollTop: 0
+      }
+    }
+  },
+  watch: {
+    activeTabName (newVal, oldVal) {
+      this.tabChange(newVal, oldVal)
+    }
+  },
+  async created () {
+    this.getTabQuery()
+    this.refreshKey()
+    // 把用户订阅信息先请求下来,marketAnalysis 里面要用到
+    await this.$store.dispatch('user/getKeywordsList')
+  },
+  methods: {
+    refreshKey () {
+      this.historyKey = Date.now() + ''
+    },
+    refreshTab2 () {
+      if (this.needRefreshTab2) {
+        this.refreshKey()
+        this.needRefreshTab2 = false
+      }
+    },
+    getTabQuery () {
+      const { tab } = this.$route.query
+      if (tab) {
+        this.activeTabName = tab
+      }
+    },
+    tabChange (newVal, oldVal) {
+      // 设置URL参数
+      this.changeURLQuery(newVal)
+      this.refreshTab2()
+      // 保存并滚动高度
+      // this.saveScrollTop()
+    },
+    changeURLQuery (query) {
+      const path = this.$route.path
+      const { tab } = this.$route.query
+      if (tab === query) return
+      const routerQuery = {
+        tab: query
+      }
+
+      if (this.activeTabName === 'analysis') {
+        try {
+          const report = this.$refs.marketAnalysis
+          if (report) {
+            routerQuery.id = report.analysisReportId
+          }
+        } catch (error) {}
+      }
+
+      this.$router.replace({
+        path,
+        query: routerQuery
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  ::v-deep {
+    .bg-grey-h24 {
+      height: 24px;
+      background-color: #f2f2f4;
+    }
+    .el-tabs__nav {
+      margin: 0 20px;
+    }
+    .el-tabs__header{
+      margin: 0;
+    }
+    .el-tabs__item {
+      height: 50px;
+      line-height: 50px;
+      font-size: 16px;
+      color: #686868;
+      &.is-active {
+        color: #2CB7CA;
+      }
+    }
+    .el-tabs__active-bar {
+      background-color: #2CB7CA;
+    }
+  }
+  .analysis-report {
+    background-color: #fff;
+  }
+</style>

+ 2 - 5
src/views/bid-policy/components/PolicyLimit.vue

@@ -342,11 +342,8 @@ export default {
           this.buyClass.forEach(function (item) {
             console.log(item, scopeArr)
             if (content === item.key[0]) {
-              const conList = {
-                key: item.key,
-                appendkey: item.appendkey,
-                notkey: item.notkey
-              }
+              const conList = {}
+              Object.assign(conList, item)
               newScopearr.push(conList)
             }
           })

+ 56 - 16
src/views/portrayal/EntSearchPortrayal.vue

@@ -26,7 +26,16 @@
         <FreeExpBanner
           v-show="isFreeExper"
           contentText="免费赠送1次【企业中标分析】权益体验机会"
-          @rightButtonClick="goExp"
+          @rightButtonClick="goExp('去解锁')"
+        />
+        <FreeExpBanner
+          class="fixed-has-exper"
+          :class="freeBannerFixed ? 'fixedBanner' : ''"
+          v-if="freeHasExper"
+          :isGiving="false"
+          contentText="如需查看更多【企业中标分析】,请开通超级订阅"
+          rightButtonText="去开通 >>"
+          @rightButtonClick="goExp('去开通')"
         />
         <div id="chartInfo" class="tab-content-item">
           <div class="ent-tab2-content" v-if="showTab2Content" key="tab2">
@@ -222,7 +231,9 @@ export default {
       unitlistshow: true,
       isFreeExper: false,
       freeEntPort: 0, // 免费用户可查看企业画像次数(-1:已使用;0:未使用;1:可使用次数)
-      freeTrialStatus: false
+      freeTrialStatus: false,
+      freeHasExper: false,
+      freeBannerFixed: false
     }
   },
   computed: {
@@ -355,32 +366,40 @@ export default {
     window.removeEventListener('scroll', this.watchScroll)
   },
   // 路由组件内导航守卫
-  beforeRouteEnter (to, from, next) {
-    next(vm => {
-      if (vm.info.memberStatus > 0 && vm.info.vipStatus > 1 && vm.info.viper && vm.pagePowerInfo.usage >= vm.pagePowerInfo.total) {
-        vm.$router.push({
-          path: `/ent_portrait/${encodeURIComponent(to.params.eId)}`,
-          replace: true
-        })
-      }
-    })
-  },
+  // beforeRouteEnter (to, from, next) {
+  //   next(vm => {
+  //     if (vm.info.memberStatus > 0 && vm.info.vipStatus > 1 && vm.info.viper && vm.pagePowerInfo.usage >= vm.pagePowerInfo.total) {
+  //       vm.$router.push({
+  //         path: `/ent_portrait/${encodeURIComponent(to.params.eId)}`,
+  //         replace: true
+  //       })
+  //     }
+  //   })
+  // },
   methods: {
     checkShowExp (callback) {
       getUserPower().then(res => {
         if (res && res.data) {
           // 免费用户体验权限
           this.freeEntPort = res.data.freeEntPort
-          if (res.data.isFree && res.data.freeEntPort === 0) {
-            this.isFreeExper = true
+          if (res.data.isFree) {
+            if (res.data.freeEntPort === 0) {
+              this.isFreeExper = true
+            } else {
+              this.freeHasExper = true
+            }
           }
           // eslint-disable-next-line no-unused-expressions
           typeof callback === 'function' ? callback(res.data) : null
         }
       })
     },
-    goExp () {
-      this.$refs.collectRef.noCallApiFn('ent_portrait_freeuser', true)
+    goExp (str) {
+      if (str === '去解锁') {
+        this.$refs.collectRef.noCallApiFn('ent_portrait_freeuser', true)
+      } else if (str === '去开通') {
+        window.open('/swordfish/page_big_pc/free/svip/buy')
+      }
     },
     // 页面滚动方法
     watchScroll () {
@@ -401,6 +420,18 @@ export default {
         this.navFixed = false
         this.activeName = '1'
       }
+      if (this.freeHasExper && this.activeName === '2') {
+        const freeDom = document.querySelector('#chartInfo')
+        const freeBanner = freeDom.offsetTop - entHeader - navHeight
+        // console.log(freeDom.offsetTop, freeBanner, scrollTop);
+        if (scrollTop >= freeBanner) {
+          this.freeBannerFixed = true
+        } else {
+          this.freeBannerFixed = false
+        }
+      } else {
+        this.freeBannerFixed = false
+      }
     },
     loadingChart (data) {
       this.loading = data
@@ -617,6 +648,15 @@ export default {
     background: linear-gradient(104deg, #D69C06 0%, #B16C05 100%);
   }
 }
+.fixedBanner{
+  position: fixed;
+  top: 111px;
+  left: 50%;
+  width: 920px;
+  margin-left: -140px!important;
+  transform: translateX(-50%);
+  z-index: 9999;
+}
 .ent-portrayal{
   margin: 32px auto;
   .ent-header{

+ 45 - 10
src/views/portrayal/UnitPortrayal.vue

@@ -25,13 +25,19 @@
         <div class="free-bg" v-if="userInfo.isFree && userInfo.freeBuyerPort === 0">
           <span class="give-tip">赠送</span>
           <div class="free-text">免费赠送1次【采购单位全景分析】权益体验机会!</div>
-          <div class="unlock-btn" @click="goUnlock">去解锁>></div>
+          <div class="unlock-btn" @click="goUnlock('去解锁')">去解锁>></div>
         </div>
         <div class="supervip-bg" v-if="vipStatusNoMember">
           <div class="vip-balance">当月采购单位画像余额:<em class="highlight-text">{{usageInfo.surplus}}</em></div>
           <div class="update-btn" @click.stop="goUpdate">{{vipStatusBtn}}</div>
         </div>
       </div>
+      <div >
+        <div class="free-bg" :class="showFreeOpen ? 'fixedBanner' : ''" v-if="userInfo.isFree && userInfo.freeBuyerPort != 0">
+          <div class="free-text">如需查看更多【采购单位全景分析】,请开通超级订阅</div>
+          <div class="unlock-btn" @click="goUnlock('去开通')">去开通>></div>
+        </div>
+      </div>
       <!-- 采购单位通讯录 -->
       <ContactList name="buyer" titlename="采购单位通讯录" style="padding:32px 40px 32px;" v-if="!noBuyerAuth"></ContactList>
       <div class="mask-bg-group" v-else>
@@ -245,7 +251,8 @@ export default {
         visited: false,
         surplus: 0
       }, // 画像访问量
-      freeTrial: false
+      freeTrial: false,
+      showFreeOpen: false
     }
   },
   watch: {
@@ -319,9 +326,9 @@ export default {
       tempMap.button = this.shadeBottomBtnText
       return tempMap
     },
-    // 纯超级订阅用户
+    // 纯超级订阅用户 =》 修改为是大会员没有画像权限同时是新超级订阅用户
     vipStatusNoMember () {
-      return this.userInfo.vipStatus > 0 && this.userInfo.viper && this.userInfo.memberStatus <= 0
+      return this.userInfo.vipStatus > 0 && this.userInfo.viper && this.userInfo.power.indexOf(5) === -1
     },
     // 超级订阅余额提醒 对应文案
     vipStatusBtn () {
@@ -342,7 +349,7 @@ export default {
         //   tempButton = '免费体验'
         // }
         // this.bigUpgradeMap[v].title = tempTitle
-        console.log(this.shadeBottomBtnText)
+        // console.log(this.shadeBottomBtnText)
         this.bigUpgradeMap[v].button = this.shadeBottomBtnText
       }
       return  this.bigUpgradeMap
@@ -430,11 +437,26 @@ export default {
         })
       }
     })
+    this.$nextTick(() => {
+      window.addEventListener('scroll', this.watchScroll)
+    })
+  },
+  destroyed () {
+    window.removeEventListener('scroll', this.watchScroll)
   },
   methods: {
+    // 页面滚动方法
+    watchScroll () {
+      const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
+      const watchDom = document.querySelector('.unit-type').offsetHeight + 32
+      if (scrollTop >= watchDom) {
+        this.showFreeOpen = true
+      } else {
+        this.showFreeOpen = false
+      }
+    },
     // 获取免费用户是否体验过该采购单位
     getIsTrail (data) {
-      console.log(data, 'dadadada')
       this.freeTrial = data ? data : false
     },
     // 采购单位画像访问量查询
@@ -559,9 +581,13 @@ export default {
         this.follow.loading = false
       })
     },
-    // 去解锁
-    goUnlock () {
-      this.$refs.collectRef.noCallApiFn('buyer_portrait_freeuser', true)
+    // 去解锁 去开通
+    goUnlock (type) {
+      if (type === '去解锁') {
+        this.$refs.collectRef.noCallApiFn('buyer_portrait_freeuser', true)
+      } else {
+        window.open('/swordfish/page_big_pc/free/svip/buy')
+      }
     },
     goEmitClick (data) {
       this.$refs.collectRef.noCallApiFn(data, true)
@@ -829,7 +855,7 @@ export default {
     justify-content: center;
     width: 100%;
     height: 48px;
-    margin-top: 16px;
+    // margin-top: 16px;
     background: url('~@/assets/images/free-bg.png') no-repeat center center;
     background-size: 100% 100%;
     border-radius: 8px;
@@ -889,5 +915,14 @@ export default {
       cursor: pointer;
     }
   }
+  .fixedBanner{
+    position: fixed;
+    top: 63px;
+    left: 50%;
+    width: 920px;
+    margin-left: -140px;
+    transform: translateX(-50%);
+    z-index: 9999;
+  }
 }
 </style>

+ 5 - 1
src/views/portrayal/components/FreeExpBanner.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="free-exp-banner before-give-text">
+  <div class="free-exp-banner" :class="isGiving ? 'before-give-text' : ''">
     <div class="flex-r-c left">
       <span class="content-text">{{ contentText }}</span>
       <div class="exp-button" @click="toUnlock">{{ rightButtonText }}</div>
@@ -11,6 +11,10 @@
 export default {
   name: 'free-exp-banner',
   props: {
+    isGiving: {
+      type: Boolean,
+      default: true
+    },
     contentText: [String],
     rightButtonText: {
       type: String,

+ 11 - 0
src/views/project/ProjectInfo.vue

@@ -19,6 +19,7 @@
           </div>
           <div class="flex-r-c center left down-report" @click="goDownReport" v-loading="loadingReport">
             <i class="el-icon-jy-report"></i>
+            <i class="el-icon-jy-report-active"></i>
             <span>下载项目报告</span>
             <i class="el-icon-jy-vip"></i>
           </div>
@@ -274,6 +275,7 @@ export default {
 @include diy-icon('heart_stroke', 20, 20);
 @include diy-icon('vip', 38, 18);
 @include diy-icon('report', 20, 20);
+@include diy-icon('report-active', 20, 20);
 ::v-deep {
   .el-button+.el-button {
     margin-left: 20px;
@@ -362,8 +364,17 @@ $border-color: #ececec;
           }
         }
       }
+      .el-icon-jy-report-active {
+        display: none;
+      }
       &:hover {
         color: #2CB7CA;
+        .el-icon-jy-report-active {
+          display: inline-block;
+        }
+        .el-icon-jy-report {
+          display: none;
+        }
       }
       > span {
         display: inline-block;

+ 67 - 1
src/views/public/Buy.vue

@@ -10,6 +10,7 @@
           <div slot="header" class="vip-sub-item-title">{{ buySpecLabel }}</div>
           <div class="vip-sub-item-content">
             <SpecList :list="specList" v-model="specIdActive" @change="specChange" />
+            <div class="top-tip-banner">使用有效期至{{endTime}}<span v-if="endDay > 0">(剩 {{endDay}} 天)</span></div>
           </div>
         </SelectorCard>
         <SelectorCard
@@ -98,6 +99,8 @@ export default {
   },
   data () {
     return {
+      endTime: '',
+      endDay: '',
       preTitle: '',
       buyType: 'buy', // buy/upgrade
       buySpecLabel: '选择附件个数',
@@ -229,6 +232,7 @@ export default {
     }
   },
   created () {
+    this.getVipEndTime()
     this.getType()
     this.getGoods()
     this.getUserPower() // 获取最新的power
@@ -253,6 +257,56 @@ export default {
     } catch (error) {}
   },
   methods: {
+    getMonthEndTime: function (date) {
+      var tempDate = new Date(date)
+      let month = tempDate.getMonth()
+      var new_year = tempDate.getFullYear(); //取当前的年份
+      var new_month = ++month; //取下一个月的第一天,方便计算(最后一天不固定)
+      if (month > 12) {
+        new_month -= 12; //月份减
+        new_year++; //年份增
+      }
+      var new_date = new Date(new_year, new_month, 1); //取当年当月中的第一天
+      return (new Date(new_date.getTime() - 1000));
+    },
+    getDayDiff: function (date1, date2, unit) {
+      var myDate1 = typeof date1 === 'string' && date1.includes('-') ? date1.replace(/-/g, '/') : date1;
+      var myDate2 = typeof date2 === 'string' && date2.includes('-') ? date2.replace(/-/g, '/') : date2;
+      var map = {
+        day: 1000 * 60 * 60 * 24,
+        hour: 1000 * 60 * 60,
+        minute: 1000 * 60,
+        second: 1000,
+        ms: 1,
+      };
+      return ((new Date(myDate2) - new Date(myDate1)) / (map[unit]));
+    },
+    getEndTime: function (vipEndTime) {
+      var nowDate = new Date().getTime()
+      var monthEndDate = this.getMonthEndTime(nowDate)
+      if (vipEndTime) {
+        var vipEndDate = new Date(vipEndTime)
+        if (vipEndDate.getTime() < monthEndDate.getTime()) {
+          monthEndDate = vipEndDate
+        }
+      }
+      var diffDay = this.getDayDiff(nowDate, monthEndDate.getTime(), 'day')
+      const result = Math.ceil(diffDay)
+      this.endTime = monthEndDate.pattern('yyyy.MM.dd')
+      this.endDay = result
+    },
+    getVipEndTime: function () {
+      $.ajax({
+        url: '/subscribepay/vipsubscribe/getSubBuyMsg',
+        type: 'GET',
+        success: function (res) {
+          console.log(res)
+          if (res && res.data && res.data.endTime) {
+            this.getEndTime(res.data.endTime * 1000)
+          }
+        }.bind(this)
+      })
+    },
     getType () {
       var type = this.$route.query.type
       var types = ['buy', 'upgrade']
@@ -395,7 +449,7 @@ export default {
         pay_price: this.specActiveItem.price * 100
       }
       if (this.couponActiveItem?.disCount) {
-        tempPrice.pay_price = Math.floor(tempPrice.original_price * this.couponActiveItem.disCount / 10)
+        tempPrice.pay_price = tempPrice.original_price - Math.floor(tempPrice.original_price * (1 - this.couponActiveItem.disCount / 10))
       }
       if (this.couponActiveItem?.reduce) {
         tempPrice.pay_price = Math.floor(tempPrice.original_price - this.couponActiveItem.reduce * 100)
@@ -466,6 +520,18 @@ export default {
     justify-content: center;
   }
 }
+.top-tip-banner {
+  margin-top: 12px;
+  width: 1012px;
+  height: 32px;
+  padding: 0 16px;
+  background: rgba(255, 58, 32, 0.06);
+  border-radius: 4px 4px 4px 4px;
+  font-size: 14px;
+  font-weight: 400;
+  color: #FF3A20;
+  line-height: 32px;
+}
 .buy-tip {
   margin-top: 28px;
   font-size: 12px;

+ 12 - 1
src/views/subscribe/Config.vue

@@ -3,7 +3,7 @@
     <!-- 订阅设置 -->
     <sub-config :datas="setData" @update="getUpdate"></sub-config>
     <!-- 关键词设置 -->
-    <key-config :datas="setData" @update="getUpdate"></key-config>
+    <key-config id="setkey" :datas="setData" @update="getUpdate"></key-config>
     <!-- 关键词列表 -->
     <key-list v-if="setData.keyList.length > 0" ref="keyConfigRef" :datas="setData"></key-list>
     <el-dialog
@@ -84,8 +84,19 @@ export default {
       // this.updateKeyWordsApi(data)
       this.getBigInfo()
     })
+    setTimeout(this.scrollToId, 100)
   },
   methods: {
+    scrollToId () {
+      const { scroll } = this.$route.query
+      if (scroll === 'setkey') {
+        const headerH = $('#public-nav').height() || 0
+        const offset = $('#setkey').offset()
+        if (offset) {
+          $('body,html').animate({ scrollTop: offset.top + headerH })
+        }
+      }
+    },
     parentGetCurEdit () {
       const t = this.$refs.keyConfigRef.getCurEdit()
       return t

+ 103 - 138
src/views/work-desktop/WorkDesktop.vue

@@ -1,72 +1,63 @@
 <template>
-  <div class="work-desktop">
-    <img class="desktop-bg" src="@/assets/images/bg_1.png">
-    <div class="desktop-container">
-      <div class="d-slidebar">
-        <Slidebar></Slidebar>
-      </div>
-      <div class="d-content">
-        <UserInfo></UserInfo>
-        <CommonUse></CommonUse>
-        <div class="d-con-flex">
-          <HomeList ref="sublist1" :loading="setload.loading1" @getDetail="getDetail" @addSet="addSet" @linkMore="linkMore" :getlist="getList" model="model-1">
-            <span slot="header">订阅信息</span>
-          </HomeList>
-          <HomeList :loading="setload.loading2" ref="sublist2" @getDetail="getDetail" @addSet="addSet" @linkMore="linkMore" :getlist="entList" model="model-2">
-            <span slot="header">企业情报监控</span>
-          </HomeList>
-          <HomeList :loading="setload.loading3" ref="sublist3" @getDetail="getDetail" @addSet="addSet" @linkMore="linkMore" :getlist="collectionList" model="model-3">
-            <span slot="header">最近收藏的标讯</span>
-          </HomeList>
-          <HomeList :loading="setload.loading4" ref="sublist4" @getDetail="getDetail" :stepList="stepList" @thisAnnouncement="thisAnnouncement" @addSet="addSet" @linkMore="linkMore" :getlist="followList" model="model-4">
-            <span slot="header">项目关注</span>
-          </HomeList>
-          <HomeList :loading="setload.loading5" ref="sublist5" v-if="info.power.indexOf(10) !== -1" @getDetail="getDetail" @addSet="addSet" @linkMore="linkMore" :getlist="weekList" model="model-week">
-            <span slot="header">周报</span>
-          </HomeList>
-          <HomeList :loading="setload.loading6" ref="sublist6" v-if="info.power.indexOf(10) !== -1" @getDetail="getDetail" @addSet="addSet" @linkMore="linkMore" :getlist="monthList" model="model-month">
-            <span slot="header">月报</span>
-          </HomeList>
+  <div>
+    <UserInfo></UserInfo>
+    <CommonUse></CommonUse>
+    <div class="d-con-flex">
+      <HomeList ref="sublist1" :loading="setload.loading1" @getDetail="getDetail" @addSet="addSet" @linkMore="linkMore" :getlist="getList" model="model-1">
+        <span slot="header">订阅信息</span>
+      </HomeList>
+      <HomeList :loading="setload.loading2" ref="sublist2" @getDetail="getDetail" @addSet="addSet" @linkMore="linkMore" :getlist="entList" model="model-2">
+        <span slot="header">企业情报监控</span>
+      </HomeList>
+      <HomeList :loading="setload.loading3" ref="sublist3" @getDetail="getDetail" @addSet="addSet" @linkMore="linkMore" :getlist="collectionList" model="model-3">
+        <span slot="header">最近收藏的标讯</span>
+      </HomeList>
+      <HomeList :loading="setload.loading4" ref="sublist4" @getDetail="getDetail" :stepList="stepList" @thisAnnouncement="thisAnnouncement" @addSet="addSet" @linkMore="linkMore" :getlist="followList" model="model-4">
+        <span slot="header">项目关注</span>
+      </HomeList>
+      <HomeList :loading="setload.loading5" ref="sublist5" v-if="info.power.indexOf(10) !== -1" @getDetail="getDetail" @addSet="addSet" @linkMore="linkMore" :getlist="weekList" model="model-week">
+        <span slot="header">周报</span>
+      </HomeList>
+      <HomeList :loading="setload.loading6" ref="sublist6" v-if="info.power.indexOf(10) !== -1" @getDetail="getDetail" @addSet="addSet" @linkMore="linkMore" :getlist="monthList" model="model-month">
+        <span slot="header">月报</span>
+      </HomeList>
+    </div>
+    <div>
+      <HomePotenList
+      v-if="info.power.indexOf(7) !== -1"
+      :loading="setload.loading7"
+      :getlist="potenEntListOne"
+      model="C"
+      @goDetail="goDetail"
+      @follow="changeFollow"
+      @delete="changeDelete"
+      @addSet="addSet">
+        <span slot="header">潜在客户推荐</span>
+        <div slot="more" class="h-header-r">
+          <button class="myfollow" :class="potenEntListOne.length===0?'potenEmpty':''" @click="linkMore('C')">我关注的客户</button>
+          <button class="potenfollw" @click="addSet('C')" v-if="potenEntListOne.length !== 0">潜在客户挖掘<span class="el-icon-jy-blue-more"></span></button>
         </div>
-        <div>
-          <HomePotenList
-          v-if="info.power.indexOf(7) !== -1"
-          :loading="setload.loading7"
-          :getlist="potenEntListOne"
-          model="C"
-          @goDetail="goDetail"
-          @follow="changeFollow"
-          @delete="changeDelete"
-          @addSet="addSet">
-            <span slot="header">潜在客户推荐</span>
-            <div slot="more" class="h-header-r">
-              <button class="myfollow" :class="potenEntListOne.length===0?'potenEmpty':''" @click="linkMore('C')">我关注的客户</button>
-              <button class="potenfollw" @click="addSet('C')" v-if="potenEntListOne.length !== 0">潜在客户挖掘<span class="el-icon-jy-blue-more"></span></button>
-            </div>
-          </HomePotenList>
-          <HomePotenList
-          v-if="info.power.indexOf(8) !== -1"
-          ref="homelist"
-          :loading="setload.loading8"
-          :getlist="potenPerListOne"
-          model="R"
-          @goDetail="goDetail"
-          @follow="changeFollow"
-          @delete="changeDelete"
-          @addSet="addSet">
-            <span slot="header">潜在竞争对手/合作伙伴推荐</span>
-            <div slot="more" class="h-header-r">
-              <button class="potenfollw" @click="addSet('R')">潜在竞争对手/合作伙伴挖掘<span class="el-icon-jy-blue-more"></span></button>
-            </div>
-          </HomePotenList>
+      </HomePotenList>
+      <HomePotenList
+      v-if="info.power.indexOf(8) !== -1"
+      ref="homelist"
+      :loading="setload.loading8"
+      :getlist="potenPerListOne"
+      model="R"
+      @goDetail="goDetail"
+      @follow="changeFollow"
+      @delete="changeDelete"
+      @addSet="addSet">
+        <span slot="header">潜在竞争对手/合作伙伴推荐</span>
+        <div slot="more" class="h-header-r">
+          <button class="potenfollw" @click="addSet('R')">潜在竞争对手/合作伙伴挖掘<span class="el-icon-jy-blue-more"></span></button>
         </div>
-      </div>
+      </HomePotenList>
     </div>
   </div>
 </template>
 
 <script>
-import Slidebar from '@/components/work-desktop/Slidebar.vue'
 import UserInfo from '@/components/work-desktop/UserInfo.vue'
 import CommonUse from '@/components/work-desktop/CommonUse.vue'
 import HomeList from '@/components/home/HomeList.vue'
@@ -77,7 +68,6 @@ import { mapState } from 'vuex'
 export default {
   name: 'work-desktop',
   components: {
-    Slidebar,
     UserInfo,
     CommonUse,
     HomeList,
@@ -504,84 +494,59 @@ export default {
 }
 </script>
 
-<style lang="scss">
+<style lang="scss" scoped>
 @include diy-icon('blue-more', 16, 16);
-.work-desktop {
-  padding-bottom: 78px;
-  .desktop-bg{
-    width: 100%;
-    height: 240px;
-  }
-  .desktop-container{
-    width: 1200px;
-    display: flex;
-    justify-content: space-between;
-    margin: -80px auto 0;
-    .d-slidebar{
-      width: 180px;
-      height: 100%;
-      padding: 16px;
-      background: #fff;
-      border-radius: 4px;
-    }
-    .d-content{
-      margin-left: 20px;
-      flex: 1;
-      width: 1000px;
-      .d-con-flex{
-        display: flex;
-        justify-content: space-between;
-        flex-wrap: wrap;
-      }
-    }
-  }
-  .h-header-r{
-    display: flex;
-  }
-  .myfollow{
-    margin-right: 12px;
-    padding: 4px 12px;
-    background: #2cb7ca;
-    border-radius: 4px;
-    font-size: 14px;
-    font-family: Microsoft YaHei, Microsoft YaHei-Regular;
-    font-weight: 400;
-    text-align: CENTER;
-    color: #ffffff;
-    line-height: 22px;
-  }
-  .potenEmpty{
-    height: 30px;
-    background: #ffffff;
-    border: 1px solid #2cb7ca;
-    border-radius: 4px;
-    font-size: 14px;
-    font-family: Microsoft YaHei, Microsoft YaHei-Regular;
-    font-weight: 400;
-    text-align: CENTER;
-    color: #2cb7ca;
-    line-height: 22px;
-    &:hover{
-      background: #eaf8fa;
-    }
+.d-con-flex{
+  display: flex;
+  justify-content: space-between;
+  flex-wrap: wrap;
+}
+.h-header-r{
+  display: flex;
+}
+.myfollow{
+  margin-right: 12px;
+  padding: 4px 12px;
+  background: #2cb7ca;
+  border-radius: 4px;
+  font-size: 14px;
+  font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+  font-weight: 400;
+  text-align: CENTER;
+  color: #ffffff;
+  line-height: 22px;
+}
+.potenEmpty{
+  height: 30px;
+  background: #ffffff;
+  border: 1px solid #2cb7ca;
+  border-radius: 4px;
+  font-size: 14px;
+  font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+  font-weight: 400;
+  text-align: CENTER;
+  color: #2cb7ca;
+  line-height: 22px;
+  &:hover{
+    background: #eaf8fa;
   }
-  .potenfollw{
-    display: flex;
-    align-items: center;
-    padding: 4px 12px;
-    font-size: 14px;
-    font-family: Microsoft YaHei, Microsoft YaHei-Regular;
-    font-weight: 400;
-    text-align: CENTER;
-    color: #2cb7ca;
-    line-height: 22px;
-    background: #fff;
-    border: 1px solid #2cb7ca;
-    border-radius: 4px;
-    height: 30px;
-    &:hover{
-      background: #eaf8fa;
-    }
+}
+.potenfollw{
+  display: flex;
+  align-items: center;
+  padding: 4px 12px;
+  font-size: 14px;
+  font-family: Microsoft YaHei, Microsoft YaHei-Regular;
+  font-weight: 400;
+  text-align: CENTER;
+  color: #2cb7ca;
+  line-height: 22px;
+  background: #fff;
+  border: 1px solid #2cb7ca;
+  border-radius: 4px;
+  height: 30px;
+  &:hover{
+    background: #eaf8fa;
   }
 }
 .custom-message-box{

+ 60 - 0
src/views/work-desktop/index.vue

@@ -0,0 +1,60 @@
+<template>
+  <div class="work-desktop">
+    <img class="desktop-bg" src="@/assets/images/bg_1.png">
+    <div class="desktop-container">
+      <div class="d-slidebar">
+        <Slidebar></Slidebar>
+      </div>
+      <router-view class="d-content"></router-view>
+    </div>
+  </div>
+</template>
+
+<script>
+import Slidebar from '@/components/work-desktop/Slidebar.vue'
+import { mapState } from 'vuex'
+export default {
+  name: 'desktop-index',
+  components: {
+    Slidebar
+  },
+  data () {
+    return {}
+  },
+  computed: {
+    ...mapState({
+      info: state => state.user.info
+    })
+  },
+  created () {},
+  methods: {}
+}
+</script>
+
+<style lang="scss">
+.work-desktop {
+  padding-bottom: 78px;
+  .desktop-bg{
+    width: 100%;
+    height: 240px;
+  }
+  .desktop-container{
+    width: 1200px;
+    display: flex;
+    justify-content: space-between;
+    margin: -80px auto 0;
+    .d-slidebar{
+      width: 180px;
+      height: 100%;
+      padding: 16px;
+      background: #fff;
+      border-radius: 4px;
+    }
+    .d-content{
+      margin-left: 20px;
+      flex: 1;
+      width: 1000px;
+    }
+  }
+}
+</style>

+ 34 - 15
vue.config.js

@@ -83,24 +83,43 @@ module.exports = {
   chainWebpack: config => {
     // single-spa
     if (!process.env.VUE_APP_ALONE) {
-      config.devServer.set('inline', false)
-      config.devServer.set('hot', true)
 
       if (process.env.NODE_ENV === 'production') {
-        config.externals([
-          'vue',
-          'vue-router',
-          'vuex',
-          'axios',
-          'lodash',
-          'moment'
-        ])
-        config.plugin('html').tap(args => {
-          // html中添加cdn
-          args[0].cdn = cdn
-          return args
-        })
+          // 生产环境配置
+          config.externals([
+            'vue',
+            'vue-router',
+            'vuex',
+            'axios',
+            'lodash',
+            'moment'
+          ])
+          config.plugin('html').tap(args => {
+            // html中添加cdn
+            args[0].cdn = cdn
+            return args
+          })
+          // config.output.filename('./js/[name].[chunkhash:8].js')
+          config.output.filename = function (pathData) {
+            return pathData.chunk.name === 'app' ? './js/[name].js' : './js/[name].[chunkhash:8].js';
+          };
+          config.output.chunkFilename('./js/[name].[chunkhash:8].js');
+          // config.output.chunkFilename = function (pathData) {
+          //   console.log(JSON.stringify(pathData.chunk), '11')
+          //   return pathData.chunk.name === 'app' ? './js/[name].js' : './js/[name].[chunkhash:8].js';
+          // };
+
+          config.optimization.minimize(true)
+            .minimizer('terser')
+            .tap(args => {
+              let { terserOptions } = args[0];
+              terserOptions.compress.drop_console = true;
+              terserOptions.compress.drop_debugger = true;
+              return args
+            });
       } else {
+        config.devServer.set('inline', false)
+        config.devServer.set('hot', true)
         config.output.filename('js/[name].js')
       }
     }