Jelajahi Sumber

feat: 图表组件封装及应用

yangfeng 4 tahun lalu
induk
melakukan
bc524d1253

+ 3 - 0
package.json

@@ -12,9 +12,12 @@
   "dependencies": {
     "axios": "^0.21.1",
     "core-js": "^3.4.3",
+    "echarts": "4.8.0",
     "element-ui": "^2.15.1",
+    "lodash": "^4.17.21",
     "single-spa-vue": "^1.5.4",
     "systemjs-webpack-interop": "^1.1.2",
+    "v-charts": "1.19.0",
     "vue": "^2.6.10",
     "vue-router": "^3.1.3",
     "vuex": "^3.6.2"

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

@@ -0,0 +1,32 @@
+import request from '@/api'
+import qs from 'qs'
+
+// 获取采购单位画像信息
+export function getUnitChart (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/portrait/buyer/getData',
+    method: 'post',
+    data
+  })
+}
+
+// 获取地图json文件
+export function getMapJson (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '@/assets/js/china.json',
+    method: 'get',
+    data
+  })
+}
+
+// 获取企业画像图表信息
+export function getEntChart (data) {
+  data = qs.stringify(data)
+  return request({
+    url: '/portrait/winner/getData',
+    method: 'post',
+    data
+  })
+}

+ 1 - 0
src/api/modules/index.js

@@ -1,2 +1,3 @@
 export * from './home'
 export * from './user'
+export * from './chart'

+ 1 - 1
src/api/modules/subscribe.js

@@ -12,7 +12,7 @@ export function getBigMemberInfo (data) {
 
 // 修改关键词
 export function updateKey (data) {
-  data = qs.stringify(data)
+  // data = qs.stringify(data)
   return request({
     url: '/subscribe/key/update',
     method: 'post',

File diff ditekan karena terlalu besar
+ 156 - 0
src/assets/js/china.json


+ 136 - 0
src/components/chart/AgencyChart.vue

@@ -0,0 +1,136 @@
+<template>
+  <div class="progess-chart">
+    <div class="client-list">
+      <div class="c-thead">
+          <strong class="c-name"></strong>
+          <span class="c-count">项目数量</span>
+          <span class="c-time">最近合作日期</span>
+        </div>
+        <div class="progress-bar-container">
+          <div class="progress-bar-item" v-for="(item,index) in datas" :key="index">
+            <div class="item-label">
+              <span class="ellipsis-2 item-name">{{item.agency_name}}</span>
+              <span class="item-count">{{item.project_count}}个</span>
+              <span class="item-time">{{item.last_with_time}}</span>
+            </div>
+            <div class="item-progress">
+              <span class="item-progress-count active-progress" :style="{width: item.parent}"></span>
+            </div>
+          </div>
+        </div>
+    </div>
+  </div>
+</template>
+<script>
+export default {
+  name: 'progess-chart',
+  props: {
+    // 传入的数据
+    datas: Array
+  },
+  data () {
+    return {}
+  },
+  computed: {},
+  watch: {},
+  mounted () {
+    console.log(this.datas)
+  },
+  methods: {}
+}
+</script>
+<style lang="scss" scoped>
+.client-list{
+  margin-top: 20px;
+  background-color: #fff;
+  .c-thead{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    line-height: 22px;
+  }
+  .c-name,.item-name{
+    font-size: 14px;
+    color: #1D1D1D;
+    flex: 8;
+  }
+  .item-name{
+    cursor: pointer;
+  }
+  .c-count,.item-count{
+    flex: 1;
+    font-size: 12px;
+    text-align: center;
+  }
+  .c-rate,.item-rate{
+    font-size: 12px;
+    text-align: center;
+  }
+  .c-time,.item-time{
+    flex: 1;
+    font-size: 12px;
+    text-align: center;
+  }
+  .c-name{
+    font-weight: bold;
+    font-size: 16px;
+    line-height: 40px;
+    color: #1D1D1D;
+  }
+  .c-count,.c-rate,.c-time{
+    color: #686868;
+    white-space: nowrap;
+  }
+  .progress-bar-container {
+    background-color: #fff;
+  }
+  .progress-bar-item {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-around;
+    margin-bottom: 14px;
+  }
+  .item-label {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    line-height: 20px;
+    padding-bottom: 6px;
+  }
+  .item-name {
+    color: #171826;
+  }
+  .item-count {
+    font-size: 12px;
+    color: #171826;
+  }
+  .item-progress {
+    position: relative;
+    height: 20px;
+    line-height: 20px;
+    background-color: #EDEFF2;
+    border-radius: 0 10px 10px 0;
+    overflow: hidden;
+  }
+  .item-money{
+    position: absolute;
+    left: 16px;
+    top: 50%;
+    transform: translateY(-50%);
+    color: #8F5828;
+    font-size: 14px;
+    z-index: 10;
+  }
+  .item-progress-count {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 100%;
+    border-radius: 0 10px 10px 0;
+    z-index: 9;
+  }
+  .active-progress{
+    background: linear-gradient(270deg, #2ABED1 0.81%, #8DE0EB 100%);
+  }
+}
+</style>

+ 143 - 0
src/components/chart/BarLineChart.vue

@@ -0,0 +1,143 @@
+<template>
+  <ve-histogram
+    :id="id"
+    :height="options.height"
+    :colors="options.colors"
+    :data="datas"
+    :settings="options.settings"
+    :after-config="options.config"
+    :after-set-option="extend"
+    :extend="defaultOptions"
+    >
+  </ve-histogram>
+</template>
+<script>
+export default {
+  name: 'chart',
+  props: {
+    id: String,
+    datas: Object,
+    options: {
+      height: String,
+      colors: Array || Object,
+      settings: Object,
+      config: Function
+    }
+  },
+  data () {
+    return {
+      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) {
+          item[0].splitLine = {
+            lineStyle: {
+              type: 'dashed',
+              width: 0.5
+            }
+          }
+          item[0].axisLabel = {
+            margin: 2,
+            fontSize: 12
+          }
+          item[1].splitLine = {
+            show: false
+          }
+          item[1].axisLabel = {
+            show: false,
+            fontSize: 12
+          }
+          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)'
+        },
+        legend: {
+          orient: 'horizontal',
+          icon: 'circle',
+          bottom: 0,
+          align: 'left',
+          itemWidth: 8,
+          itemHeight: 8,
+          itemGap: 20,
+          textStyle: {
+            fontSize: 12,
+            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) => {
+            if (name === '企业数量') {
+              name = name + '(个)'
+            }
+            return name
+          }
+        }
+      }
+    }
+  },
+  computed: {},
+  watch: {},
+  mounted () {
+    console.log(this.datas)
+  },
+  methods: {
+    extend (chart) {
+      chart.setOption({
+        series: [{
+          type: 'bar',
+          barWidth: 20
+        }, {
+          type: 'line',
+          smooth: false,
+          symbol: 'none'
+        }]
+      })
+    }
+  },
+  beforeDestroy () {}
+}
+</script>
+<style lang="scss" scoped>
+</style>

+ 239 - 0
src/components/chart/DoubleBarChart.vue

@@ -0,0 +1,239 @@
+<template>
+  <div class="double-bar">
+    <div class="bar-content">
+      <div class="rate-item"
+        v-for="(item, index) in datas"
+        :key="item.type"
+        @mouseover="tooltipShow(item,index)"
+        @mouseout="tooltipHide"
+      >
+        <div class="item-label">{{item.type}}</div>
+        <div class="item-chart">
+          <div class="left-chart">
+            <div class="item-progress" v-if="item.lPercent > 30">
+              <span class="blue-progress-bg blue-padding" :style="{width: item.lPercent + '%'}">{{item.num}}({{item.numShare  + '%'}})</span>
+            </div>
+            <div class="item-progress" v-else>
+              <span class="blue-progress-text">{{item.num}}({{item.numShare  + '%'}})</span>
+              <span class="blue-progress-bg" :style="{width: item.lPercent + '%'}"></span>
+            </div>
+          </div>
+          <div class="right-chart">
+            <div class="item-progress" v-if="item.rPercent > 30">
+              <span class="yellow-progress-bg yellow-padding" :style="{width: item.rPercent + '%'}">{{item.money}}({{item.moneyShare + '%'}})</span>
+            </div>
+            <div class="item-progress" v-else>
+              <span class="yellow-progress-bg" :style="{width: item.rPercent + '%'}"></span>
+              <span class="yellow-progress-text">{{item.money}}({{item.moneyShare + '%'}})</span>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="custom-tooltip" v-show="doubleBar.showTooltip" :style="{left: doubleBar.info.positionLeft, top:doubleBar.info.positionTop}">
+          <div class="tooltip-item">
+            <span class="tooltip-circle blue-circle"></span>
+            <span>数量(个)及占比:{{doubleBar.info.num}}({{doubleBar.info.numShare + '%'}})</span>
+          </div>
+          <div class="tooltip-item">
+            <span class="tooltip-circle yellow-circle"></span>
+            <span>金额(万元)及占比:{{doubleBar.info.money}}({{doubleBar.info.moneyShare + '%'}})</span>
+          </div>
+          <div class="tooltip-name">- {{doubleBar.info.type}} -</div>
+      </div>
+      <div class="custom-legend">
+        <div class="legend-item">
+          <span class="tooltip-circle blue-circle"></span>
+          <span>数量(个)及占比</span>
+        </div>
+        <div class="legend-item">
+          <span class="tooltip-circle yellow-circle"></span>
+          <span>金额(万元)及占比</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import { Popover } from 'element-ui'
+export default {
+  name: 'double-bar-chart',
+  props: {
+    datas: Array
+  },
+  components: {
+    [Popover.name]: Popover
+  },
+  data () {
+    return {
+      doubleBar: {
+        info: {},
+        showTooltip: false
+      }
+    }
+  },
+  computed: {},
+  watch: {
+    datas: {
+      handler (newVal, oldVal) {
+        this.datas = newVal
+      },
+      deep: true
+    }
+  },
+  mounted () {},
+  methods: {
+    tooltipShow (item, index) {
+      this.doubleBar.showTooltip = true
+      item.positionLeft = Math.random() * 500 + 'px'
+      item.positionTop = (index + 1) * 20 + 'px'
+      this.doubleBar.info = item
+    },
+    tooltipHide () {
+      this.doubleBar.showTooltip = false
+      this.doubleBar.info = {}
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.double-bar{
+  .bar-content{
+    position: relative;
+    background: #fff;
+  }
+  .rate-item{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-top: 12px;
+  }
+  .item-label{
+    width: 80px;
+    height: 20px;
+    line-height: 20px;
+    white-space: nowrap;
+    text-align: right;
+    font-size: 12px;
+    color: #5F5E64;
+    margin-right: 8px;
+  }
+  .item-chart{
+    position: relative;
+    flex: 1;
+    height: 20px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .item-progress{
+    position: relative;
+    flex: 1;
+    width: 100%;
+    height: 20px;
+    overflow: hidden;
+    font-size: 12px;
+    line-height: 20px;
+    display: flex;
+    justify-content: flex-end;
+    cursor: pointer;
+  }
+  .left-chart,.right-chart{
+    flex: 1;
+    height: 100%;
+    overflow: hidden;
+  }
+  .left-chart{
+    margin-right: 2px;
+  }
+  .right-chart{
+    & .item-progress{
+      justify-content: flex-start;
+    }
+  }
+  .blue-progress-bg,.yellow-progress-bg{
+    color: #fff;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    box-sizing: border-box;
+  }
+  .blue-progress-bg{
+    background: #05A6F3;
+  }
+  .yellow-progress-bg{
+    text-align: right;
+    background: #FF9F40;
+  }
+  .blue-padding{
+    padding-left: 8px;
+  }
+  .yellow-padding{
+    padding-right: 8px;
+  }
+  .blue-progress-text{
+    padding-right: 8px;
+    color: #05A6F3;
+  }
+  .yellow-progress-text{
+    padding-left: 8px;
+    color: #FF9F40;
+  }
+  .custom-tooltip{
+    position: absolute;
+    display: block;
+    white-space: nowrap;
+    z-index: 9999999;
+    transition: left 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0s, top 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0s;
+    background-color: rgb(255, 255, 255);
+    border: 2px solid #F5F6F7;
+    box-sizing: border-box;
+    border-radius: 4px;
+    color: rgb(23, 24, 38);
+    font: 12px / 18px "Microsoft YaHei";
+    padding: 7px 12px;
+    box-shadow: 0px 4px 16px rgba(8, 31, 38, 0.08);
+    pointer-events: none;
+  }
+  .tooltip-item{
+    display: flex;
+    align-items: center;
+    font-size: 12px;
+    color: #171826;
+    line-height: 20px;
+  }
+  .tooltip-circle{
+    display: inline-block;
+    width: 8px;
+    height: 8px;
+    border-radius: 8px;
+    margin-right: 4px;
+  }
+  .custom-legend{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-top: 16px;
+    padding: 0 8px 0 80px;
+  }
+  .tooltip-name{
+    text-align: center;
+    color: #9B9CA3;
+    font-size: 12px;
+    margin-top: 4px;
+  }
+  .legend-item{
+    display: flex;
+    align-items: center;
+    margin: 0 40px;
+    font-size: 12px;
+    color: #5F5E64;
+    line-height: 20px;
+  }
+  .blue-circle{
+    background-color: #05A6F3;
+  }
+  .yellow-circle{
+    background-color: #FF9F40;
+  }
+}
+</style>

+ 490 - 0
src/components/chart/HotChart.vue

@@ -0,0 +1,490 @@
+<template>
+  <div class="hot-chart" :style="{'height': height}"></div>
+</template>
+<script>
+// import { merge } from 'lodash'
+import { bSort, moneyUnit } from '@/utils/'
+export default {
+  name: 'hot-chart',
+  props: {
+    datas: {
+      type: Array,
+      default: () => ({})
+    }
+  },
+  data () {
+    return {
+      myChart: null,
+      minusRows: 0,
+      height: null,
+      defaultOptions: {
+        tooltip: {
+          backgroundColor: '#fff',
+          confine: true,
+          axisPointer: {
+            type: 'shadow',
+            shadowStyle: {
+              color: 'rgba(5,166,243,0.1)'
+            }
+          },
+          textStyle: {
+            color: '#171826',
+            fontSize: 12
+          },
+          padding: [7, 12],
+          extraCssText: 'box-shadow: 0px 4px 16px rgba(8, 31, 38, 0.08)',
+          borderWidth: 2,
+          borderColor: '#F5F6F7',
+          formatter: (params) => {
+            return params
+          }
+        },
+        grid: {
+          top: '10%',
+          left: 55,
+          right: 55
+        },
+        animation: false,
+        xAxis: {
+          show: false,
+          type: 'category',
+          data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+          splitArea: {
+            show: true
+          }
+        },
+        yAxis: [
+          {
+            name: '单位(元)',
+            nameTextStyle: {
+              fontSize: 12,
+              color: '#9B9CA3',
+              padding: [0, 8, 0, -30]
+            },
+            nameGap: 10,
+            type: 'category',
+            data: ['0', '1万', '10万', '50万', '100万', '500万', '1000万', '5000万', '1亿', '10亿以上'].reverse(),
+            splitArea: {
+              show: false
+            },
+            axisTick: {
+              show: false,
+              lineStyle: {
+                color: '#5F5E64'
+              }
+            },
+            axisLine: {
+              show: false
+            },
+            splitLine: {
+              show: false
+            },
+            axisLabel: {
+              fontSize: 12,
+              margin: 4
+            }
+          }, {
+            type: 'category',
+            data: ['1万', '10万', '50万', '100万', '500万', '1000万', '5000万', '1亿', '10亿', ''].reverse(),
+            splitArea: {
+              show: false
+            },
+            axisTick: {
+              show: false,
+              lineStyle: {
+                color: '#5F5E64'
+              }
+            },
+            axisLine: {
+              show: false
+            },
+            splitLine: {
+              show: false
+            },
+            axisLabel: {
+              fontSize: 12,
+              margin: 4
+            }
+          }
+        ],
+        graphic: [
+          {
+            type: 'group',
+            bottom: 40,
+            children: [{
+              type: 'text',
+              z: 100,
+              left: 50,
+              top: 'middle',
+              style: {
+                fill: '#333',
+                text: '类似项目数量(个)',
+                font: '12px Microsoft YaHei'
+              }
+            }]
+          },
+          {
+            type: 'rect',
+            z: 101,
+            left: 50,
+            bottom: 14,
+            shape: {
+              width: 150,
+              height: 24
+            },
+            style: {
+              stroke: '#FB483D',
+              fill: 'transparent',
+              lineWidth: 0.5
+            }
+          },
+          this.rectGroup(0, '#F06326'),
+          this.rectGroup(25, '#F48A5D'),
+          this.rectGroup(50, '#FFB366'),
+          this.rectGroup(75, '#FFCF9F'),
+          this.rectGroup(100, '#FFE7CF'),
+          this.rectGroup(125, '#FFF4EB'),
+          {
+            type: 'group',
+            width: 150,
+            bottom: 0,
+            left: 50,
+            children: [{
+              type: 'text',
+              z: 100,
+              left: 'left',
+              top: 'middle',
+              style: {
+                fill: '#333',
+                font: '12px Microsoft YaHei'
+              }
+            },
+            {
+              type: 'text',
+              z: 100,
+              left: 'right',
+              top: 'middle',
+              style: {
+                fill: '#333',
+                text: '0',
+                font: '12px Microsoft YaHei'
+              }
+            }]
+          },
+          {
+            type: 'group',
+            bottom: 0,
+            right: 50
+            // children: [{
+            //   type: 'circle',
+            //   z: 100,
+            //   top: 'middle',
+            //   style: {
+            //     fill: '#2ABED1'
+            //   },
+            //   shape: {
+            //     cx: 0,
+            //     cy: 0,
+            //     r: 4
+            //   }
+            // },
+            // {
+            //   type: 'text',
+            //   z: 100,
+            //   top: 'middle',
+            //   style: {
+            //     fill: '#333',
+            //     text: '当前项目预算所在区间',
+            //     font: '12px Microsoft YaHei',
+            //     x: 12
+            //   }
+            // }]
+          }
+        ],
+        visualMap: [{
+          show: false,
+          type: 'piecewise',
+          splitNumber: 5,
+          showLabel: false,
+          itemWidth: 16,
+          itemHeight: 12,
+          itemGap: 0,
+          inverse: true,
+          min: 0,
+          dimension: 3,
+          seriesIndex: [0],
+          orient: 'horizontal',
+          left: '5%',
+          bottom: '5%',
+          inRange: {
+            color: ['#FFE7CF', '#FFCF9F', '#FFB366', '#F48A5D', '#F06326']
+          },
+          outOfRange: {
+            color: ['#FFF4EB']
+          }
+        }],
+        series: [{
+          name: '类似项目数量',
+          type: 'heatmap',
+          label: {
+            show: false
+          },
+          itemStyle: {
+            normal: {
+              color: '#fff',
+              borderWidth: 1,
+              borderColor: '#fff'
+            },
+            emphasis: {
+              borderWidth: 1,
+              borderColor: '#f75441'
+            }
+          },
+          zlevel: 2,
+          tooltip: {
+            confine: true,
+            formatter: (params) => {
+              return params
+            }
+          }
+        }, {
+          name: '当前项目预算所在区间',
+          type: 'effectScatter',
+          showEffectOn: 'render',
+          rippleEffect: {
+            brushType: 'stroke',
+            color: '#2CB7CA'
+          },
+          emphasis: {
+            itemStyle: {
+              color: '#2ABED1'
+            }
+          },
+          effectType: 'ripple',
+          coordinateSystem: 'cartesian2d',
+          hoverAnimation: true,
+          itemStyle: {
+            color: '#2ABED1',
+            shadowBlur: 8,
+            shadowColor: '#2ABED1'
+          },
+          zlevel: 3,
+          symbol: 'circle',
+          symbolSize: 8,
+          tooltip: {
+            confine: true,
+            formatter: (params) => {
+              return params
+            }
+          }
+        }]
+      }
+    }
+  },
+  computed: {},
+  watch: {
+    // 监听父组件传入数据, 有变化则重新渲染图表实例
+    // datas: {
+    //   handler (newVal, oldVal) {
+    //     console.log(newVal)
+    //     this.updateChartView()
+    //   },
+    //   deep: true
+    // },
+    minusRows: {
+      handler (newVal, oldVal) {
+        // this.height = 500 - 48 * newVal + 'px'
+        this.myChart.resize()
+      },
+      deep: true
+    }
+  },
+  created () {},
+  mounted () {
+    this.formatHotChartData(this.datas)
+    this.updateChartView()
+  },
+  methods: {
+    updateChartView () {
+      this.myChart = this.$echarts.init(this.$el)
+      if (!this.myChart) return
+      this.myChart.getDom().style.height = this.height
+      this.formatHotChartData(this.datas)
+      this.formatHotOptions()
+      this.myChart.setOption(this.defaultOptions)
+      window.addEventListener('resize', () => {
+        this.myChart.resize()
+      })
+    },
+    rectGroup (left, color) {
+      return {
+        type: 'rect',
+        z: 90,
+        left: left + 50,
+        bottom: 14,
+        shape: {
+          width: 25,
+          height: 24
+        },
+        style: {
+          fill: color
+        }
+      }
+    },
+    formatHotOptions () {
+      const options = this.defaultOptions
+      // const yAxis = options.yAxis
+      const series = options.series
+      const visualMap = options.visualMap
+      const graphic = options.graphic
+      const maxNum = Math.max.apply(Math, series[0].data.map((o) => { return o[3] }))
+      visualMap.max = maxNum < 10 ? 10 : maxNum
+      graphic[graphic.length - 2].children[0].style.text = maxNum > 10 ? maxNum.toString() : '10'
+      graphic[0].children[0].style.text = '项目数量(个)'
+      const pj = Math.floor((visualMap.max / 5) * 100) / 100
+      visualMap[0].pieces = [
+        { min: 0, max: 0, color: '#FFF4EB', symbol: 'rect' },
+        { min: 1, max: pj * 1, color: '#FFE7CF', symbol: 'rect' },
+        { min: pj * 1, max: pj * 2, color: '#FFCF9F', symbol: 'rect' },
+        { min: pj * 2, max: pj * 3, color: '#FFB366', symbol: 'rect' },
+        { min: pj * 3, max: pj * 4, color: '#F48A5D', symbol: 'rect' },
+        { min: pj * 4, color: '#F06326', symbol: 'rect' }
+      ]
+      series[0].tooltip.formatter = (params) => {
+        const data = params.value[4]
+        const newRange = data.split('-')
+        let sRange = moneyUnit(newRange[0])
+        let mRange = moneyUnit(newRange[1])
+        let totalRange
+        if (sRange === '10000元') {
+          sRange = '1万元'
+        }
+        if (mRange === '10000元') {
+          mRange = '1万元'
+        }
+        if (sRange === '10000万元') {
+          sRange = '1亿元'
+        }
+        if (mRange === '10000万元') {
+          mRange = '1亿元'
+        }
+        if (sRange === '100亿元') {
+          totalRange = sRange + '以上'
+        } else {
+          totalRange = sRange + '-' + mRange
+        }
+        let tip = ''
+        const count = '<span>项目数量:' + params.value[3] + '个</span></br>'
+        const rate = (typeof params.value[2] === 'number' && !isNaN(params.value[2])) ? '<span>平均节支率:' + (params.value[2] * 100).fixed(2) + '%</span></br>' : ''
+        const budget = '<span>采购规模:' + totalRange + '</span></br>'
+        tip = budget + count + rate
+        return tip
+      }
+      series.pop()
+      graphic.pop()
+      let index
+      const myChart = this.myChart
+      this.$nextTick(() => {
+        setTimeout(() => {
+          // 默认展示某一个tooltip
+          myChart.dispatchAction({
+            type: 'highlight',
+            seriesIndex: 0,
+            dataIndex: this.getMaxProjectCount(series[0].data)
+          })
+        }, 20)
+        myChart.on('mouseover', (e) => {
+          index = this.getMaxProjectCount(series[0].data)
+          if (e.dataIndex !== index) {
+            myChart.dispatchAction({
+              type: 'downplay',
+              seriesIndex: 0,
+              dataIndex: index
+            })
+          }
+        })
+        myChart.on('mouseout', (e) => {
+          index = this.getMaxProjectCount(series[0].data)
+          if (e.dataIndex !== index) {
+            myChart.dispatchAction({
+              type: 'highlight',
+              seriesIndex: 0,
+              dataIndex: this.getMaxProjectCount(series[0].data)
+            })
+          }
+        })
+      })
+    },
+    formatHotChartData (arr) {
+      if (!arr) return
+      const options = this.defaultOptions
+      const yAxis = options.yAxis
+      const series = options.series
+      let data = []
+      let waitDelCount = 0
+      data = arr.map((v, i) => {
+        v.bidamount_avg = v.bidamount_avg === null ? '0' : v.bidamount_avg
+        const x = i % 10
+        const y = 10 - Math.ceil((i + 1) / 10)
+        return [x, y, v.bidamount_avg, v.project_count, v.range]
+      })
+      for (let i = 9; i > -1; i--) {
+        const nowArr = data.slice(i * 10, (i + 1) * 10)
+        const nowDelStatus = nowArr.filter((v) => { return v[3] > 0 })
+        if (nowDelStatus.length > 0) {
+          break
+        } else {
+          waitDelCount++
+          data.splice(i * 10, 10)
+          yAxis[0].data.splice(0, 1)
+          yAxis[1].data.splice(0, 1)
+        }
+      }
+      data.map(v => {
+        v[1] = v[1] - waitDelCount
+        return v
+      })
+      console.log(waitDelCount, '减掉几行')
+      this.minusRows = waitDelCount
+      this.height = 500 - 48 * waitDelCount + 'px'
+      series[0].data = data
+    },
+    // 求最大项目数量
+    getMaxProjectCount (analysisArr) {
+      if (!analysisArr || !$.isArray(analysisArr)) return
+      const itemMaxKey = 3 // 每一项中要比较大小的索引
+      const arr = JSON.parse(JSON.stringify(analysisArr))
+      bSort(arr, itemMaxKey)
+      const maxCount = arr[arr.length - 1][itemMaxKey]
+      let maxIndex = -1
+      analysisArr.some((item, index) => {
+        const gotIt = item[itemMaxKey] === maxCount
+        if (gotIt) maxIndex = index
+        return gotIt
+      })
+      return maxIndex
+    },
+    // 一维数组转换为二维数组
+    arrTrans (num, arr) {
+      const newArr = []
+      arr.forEach((item, index) => {
+        // 计算该元素为第几个素组内
+        const page = Math.floor(index / num)
+        // 判断是否存在
+        if (!newArr[page]) {
+          newArr[page] = []
+        }
+        newArr[page].push(item)
+      })
+      return newArr
+    }
+  },
+  beforeDestroy () {
+    if (this.myChart) {
+      this.myChart.dispose()
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+</style>

+ 123 - 0
src/components/chart/LineChart.vue

@@ -0,0 +1,123 @@
+<template>
+  <ve-line :data="datas" :height="options.height" :after-config="options.config" :extend="defaultOption"></ve-line>
+</template>
+<script>
+export default {
+  name: 'line-chart',
+  props: {
+    id: String,
+    height: String,
+    datas: Object,
+    options: {
+      height: String,
+      colors: Array || Object,
+      config: Function
+    }
+  },
+  data () {
+    return {
+      defaultOption: {
+        color: this.options.colors,
+        xAxis: {
+          axisLabel: {
+            textStyle: {
+              color: '#626262',
+              fontSize: 12
+            },
+            interval: 0
+          }
+        },
+        grid: {
+          top: 10,
+          left: 12,
+          right: 12
+        },
+        yAxis: {
+          splitLine: {
+            lineStyle: {
+              type: 'dashed',
+              width: 0.5
+            }
+          },
+          nameGap: 15,
+          nameTextStyle: {
+            fontSize: 12,
+            align: 'left',
+            color: '#9B9CA3',
+            padding: [0, 0, 0, -30]
+          },
+          axisLabel: {
+            margin: 2,
+            fontSize: 12,
+            color: '#5F5E64',
+            interval: 'auto',
+            formatter: (value, index) => {
+              return value.toString().replace(/,/, '')
+            }
+          }
+        },
+        legend: {
+          orient: 'horizontal',
+          icon: 'circle',
+          bottom: 10,
+          itemWidth: 8,
+          itemHeight: 8,
+          itemGap: 40,
+          textStyle: {
+            color: '#5F5E64',
+            fontSize: 12
+          }
+        },
+        series: {
+          type: 'line',
+          showSymbol: false,
+          smooth: false,
+          symbol: 'circle',
+          symbolSize: 3,
+          itemStyle: {
+            borderColor: '#fff',
+            borderWidth: 1
+          }
+        },
+        tooltip: {
+          backgroundColor: '#fff',
+          confine: true,
+          axisPointer: {
+            type: 'line',
+            lineStyle: {
+              width: 2,
+              color: '#2ABED1'
+            },
+            z: 3
+          },
+          textStyle: {
+            color: '#171826',
+            fontSize: 12
+          },
+          padding: [7, 12],
+          extraCssText: 'box-shadow: 0px 4px 16px rgba(8, 31, 38, 0.08)',
+          borderWidth: 2,
+          borderColor: '#F5F6F7'
+        },
+        lineStyle: {
+          width: 0.5
+        }
+      }
+    }
+  },
+  computed: {},
+  watch: {
+    datas: {
+      handler (newVal, oldVal) {
+        this.datas = newVal
+      },
+      immediate: true,
+      deep: true
+    }
+  },
+  mounted () {},
+  methods: {}
+}
+</script>
+<style lang="scss" scoped>
+</style>

+ 208 - 0
src/components/chart/MapChart.vue

@@ -0,0 +1,208 @@
+<template>
+  <ve-map
+    :height="options.height"
+    :after-config="options.config"
+    :data="datas"
+    :settings="defaultSettings"
+    :extend="defaultOptions">
+</ve-map>
+</template>
+<script>
+const mapJson = require('../../assets/js/china.json')
+console.log(mapJson)
+export default {
+  name: 'chart',
+  props: {
+    id: String,
+    datas: Object,
+    options: {
+      height: String,
+      colors: Array || Object,
+      settings: Object,
+      config: Function
+    }
+  },
+  data () {
+    return {
+      initRendererSvg: {
+        renderer: 'svg'
+      },
+      defaultSettings: {
+        // positionJsonLink: '/page_big_pc/assets/js/china.json',
+        mapOrigin: mapJson,
+        label: {
+          show: true,
+          fontSize: 12
+        },
+        selectedMode: false, // 去掉省份小圆点
+        itemStyle: {
+          normal: {
+            borderColor: '#F06326',
+            areaColor: '#FFFFFF'
+          }
+        },
+        mapGrid: {
+          left: 82,
+          right: 82
+        }
+      },
+      defaultOptions: {
+        tooltip: {
+          confine: true,
+          backgroundColor: '#fff',
+          axisPointer: {
+            type: 'shadow',
+            shadowStyle: {
+              color: 'rgba(5,166,243,0.1)'
+            },
+            z: 3
+          },
+          textStyle: {
+            color: '#171826',
+            fontSize: 12
+          },
+          padding: [8, 12],
+          extraCssText: 'box-shadow: 0px 4px 16px rgba(8, 31, 38, 0.08)',
+          formatter: (params, ticket, callback) => {
+            if (params.data === null) {
+              return params.name + ':0'
+            } else {
+              return params.name + ':' + params.value
+            }
+          }
+        },
+        grid: {
+          left: 32,
+          right: 16,
+          containLabel: true
+        },
+        legend: {
+          selectedMode: false,
+          textStyle: {
+            color: 'transparent'
+          },
+          itemWidth: 0,
+          itemHeight: 0
+        },
+        silent: false, // 禁用鼠标点击、滑过事件
+        series: {
+          showLegendSymbol: false,
+          selectedMode: false
+        },
+        graphic: [
+          {
+            type: 'group',
+            bottom: 40,
+            children: [
+              {
+                type: 'text',
+                z: 100,
+                left: 'left',
+                top: 'middle',
+                style: {
+                  fill: '#333',
+                  text: '市场分布数量',
+                  font: '12px Microsoft YaHei'
+                }
+              }]
+          },
+          {
+            type: 'rect',
+            z: 101,
+            left: 0,
+            bottom: 14,
+            shape: {
+              width: 150,
+              height: 24
+            },
+            style: {
+              stroke: '#FB483D',
+              fill: 'transparent',
+              lineWidth: 0.5
+            }
+          },
+          this.rectGroup(0, '#F06326'),
+          this.rectGroup(25, '#F48A5D'),
+          this.rectGroup(50, '#FFB366'),
+          this.rectGroup(75, '#FFCF9F'),
+          this.rectGroup(100, '#FFE7CF'),
+          this.rectGroup(125, '#FFF4EB'),
+          {
+            type: 'group',
+            width: 150,
+            bottom: 0,
+            left: 0,
+            children: [{
+              type: 'text',
+              z: 100,
+              left: 'left',
+              top: 'middle',
+              style: {
+                fill: '#333',
+                font: '12px Microsoft YaHei'
+              }
+            },
+            {
+              type: 'text',
+              z: 100,
+              left: 'right',
+              top: 'middle',
+              style: {
+                fill: '#333',
+                text: '0',
+                font: '12px Microsoft YaHei'
+              }
+            }]
+          }
+        ],
+        visualMap: {
+          show: false,
+          inRange: {
+            color: ['#FFF4EB', '#FFE7CF', '#FFCF9F', '#FFB366', '#F48A5D', '#F06326']
+          },
+          outOfRange: {
+            color: ['#F06326']
+          }
+        }
+      }
+    }
+  },
+  computed: {},
+  watch: {},
+  mounted () {
+    console.log(this.datas)
+  },
+  methods: {
+    extend (chart) {
+      chart.setOption({
+        series: [{
+          type: 'bar',
+          barWidth: 20
+        }, {
+          type: 'line',
+          smooth: false,
+          symbol: 'none'
+        }]
+      })
+    },
+    rectGroup (left, color) {
+      return {
+        type: 'rect',
+        z: 90,
+        left: left,
+        bottom: 14,
+        shape: {
+          width: 25,
+          height: 24
+        },
+        style: {
+          fill: color
+        }
+      }
+    }
+  },
+  beforeDestroy () {}
+}
+</script>
+<style lang="scss" scoped>
+</style>

+ 155 - 0
src/components/chart/PieChart.vue

@@ -0,0 +1,155 @@
+<template>
+  <div class="pie-chart" :style="{'height': height}"></div>
+</template>
+<script>
+import { merge } from 'lodash'
+export default {
+  name: 'pie-chart',
+  props: {
+    height: String,
+    // 传入的数据
+    datas: Array,
+    // 传入的配置
+    options: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data () {
+    return {
+      myChart: null,
+      // 默认配置
+      defaultOptions: {
+        dataset: {
+          source: this.datas
+        },
+        legend: {
+          bottom: 0,
+          align: 'left',
+          orient: 'horizontal',
+          icon: 'circle',
+          itemWidth: 8,
+          itemHeight: 8,
+          itemGap: 32,
+          textStyle: {
+            color: '#909399',
+            fontSize: 12,
+            rich: {
+              x: {
+                width: 48,
+                color: '#909399',
+                fontSize: 12
+              }
+            }
+          }
+        },
+        calculable: false,
+        tooltip: {
+          backgroundColor: '#fff',
+          confine: true,
+          axisPointer: {
+            type: 'shadow',
+            shadowStyle: {
+              color: 'rgba(5,166,243,0.1)'
+            }
+          },
+          textStyle: {
+            color: '#171826',
+            fontSize: 12
+          },
+          padding: [7, 12],
+          extraCssText: 'box-shadow: 0px 4px 16px rgba(8, 31, 38, 0.08)',
+          borderWidth: 2,
+          borderColor: '#F5F6F7'
+          // formatter: this.options.tooltip.formatter
+        },
+        series: {
+          name: '半径模式',
+          type: 'pie',
+          bottom: 20,
+          avoidLabelOverlap: true,
+          stillShowZeroSum: false,
+          radius: [2, '90%'],
+          clockWise: false,
+          startAngle: 60,
+          center: ['50%', '50%'],
+          roseType: 'area',
+          encode: {
+            x: 0,
+            y: 1,
+            itemName: '行业',
+            value: '类似项目规模'
+          },
+          labelLine: {
+            show: false
+          },
+          itemStyle: {
+            normal: {
+              color: (params) => {
+                const colorList = ['#05A6F3', '#0BD991', '#8E6DF2', '#5B6E96', '#F06326', '#FF8040', '#FF9F40', '#FFCF9F', '#67E5B9', '#67C7F5', '#CEC2F2', '#C3CAD9']
+                return colorList[params.dataIndex]
+              },
+              borderWidth: 1,
+              borderColor: '#fff',
+              label: {
+                position: 'inside',
+                rotate: true,
+                textStyle: {
+                  fontSize: 11
+                },
+                formatter: (params) => {
+                  if (params.percent < 10) {
+                    return ''
+                  } else {
+                    return params.percent + '%'
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  computed: {},
+  watch: {
+    // 监听父组件传入数据, 有变化则重新渲染图表实例
+    datas: {
+      handler (newVal, oldVal) {
+        this.defaultOptions.dataset.source = newVal
+        this.updateChartView()
+      },
+      deep: true
+    }
+  },
+  mounted () {
+    this.updateChartView()
+  },
+  methods: {
+    updateChartView () {
+      this.myChart = this.$echarts.init(this.$el)
+      if (!this.myChart) return
+      const fullOption = this.mergeOptionFn()
+      this.myChart.setOption(fullOption)
+      window.addEventListener('resize', () => {
+        this.myChart.resize()
+      })
+    },
+    // 通过js工具库的merge方法合并传入的配置和默认配置
+    mergeOptionFn () {
+      return merge(
+        {},
+        this.defaultOptions,
+        this.options
+      )
+    }
+  },
+  beforeDestroy () {
+    if (this.myChart) {
+      this.myChart.dispose()
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+</style>

+ 152 - 0
src/components/chart/ProgressChart.vue

@@ -0,0 +1,152 @@
+<template>
+  <div class="progess-chart">
+    <div class="client-list" v-for="c in datas" :key="c.class">
+      <div class="c-thead">
+          <strong class="c-name">{{c.class}}</strong>
+          <span class="c-count">项目数量</span>
+          <span class="c-time">最近合作日期</span>
+        </div>
+        <div class="progress-bar-container">
+          <div class="progress-bar-item" v-for="(item,index) in c.topData" :key="index">
+            <div class="item-label">
+              <span @click="goEntInfo(item.winnerName)" class="ellipsis-2 item-name">{{item.winnerName}}</span>
+              <span class="item-count">{{item.countProject}}个</span>
+              <!-- <span class="item-rate">{{item.rate}}</span> -->
+              <span class="item-time">{{item.lastTime}}</span>
+            </div>
+            <div class="item-progress">
+              <span class="item-money" v-if="item.countMoney">{{item.countMoney}}</span>
+              <span class="item-progress-count active-progress" :style="{width: item.parent}"></span>
+            </div>
+          </div>
+        </div>
+    </div>
+  </div>
+</template>
+<script>
+export default {
+  name: 'progess-chart',
+  props: {
+    // 传入的数据
+    datas: Array
+  },
+  data () {
+    return {}
+  },
+  computed: {
+    computedClientList () {
+      return this.datas.map((v) => {
+        v.topData = v.topData.filter((s) => {
+          return s.winnerName && s.winnerName.trim().length
+        })
+        return v
+      }).filter(function (v, i) {
+        return v.topData.length
+      })
+    }
+  },
+  watch: {},
+  mounted () {},
+  methods: {
+    goEntInfo (name) {
+      // location.href = '/ent_portrait?name=' + encodeURIComponent(name)
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.client-list{
+  margin-top: 20px;
+  background-color: #fff;
+  .c-thead{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    line-height: 22px;
+  }
+  .c-name,.item-name{
+    font-size: 14px;
+    color: #1D1D1D;
+    flex: 8;
+  }
+  .item-name{
+    cursor: pointer;
+  }
+  .c-count,.item-count{
+    flex: 1;
+    font-size: 12px;
+    text-align: center;
+  }
+  .c-rate,.item-rate{
+    font-size: 12px;
+    text-align: center;
+  }
+  .c-time,.item-time{
+    flex: 1;
+    font-size: 12px;
+    text-align: center;
+  }
+  .c-name{
+    font-weight: bold;
+    font-size: 16px;
+    line-height: 40px;
+    color: #1D1D1D;
+  }
+  .c-count,.c-rate,.c-time{
+    color: #686868;
+    white-space: nowrap;
+  }
+  .progress-bar-container {
+    background-color: #fff;
+  }
+  .progress-bar-item {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-around;
+    margin-bottom: 14px;
+  }
+  .item-label {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    line-height: 20px;
+    padding-bottom: 6px;
+  }
+  .item-name {
+    color: #171826;
+    text-decoration: underline;
+  }
+  .item-count {
+    font-size: 12px;
+    color: #171826;
+  }
+  .item-progress {
+    position: relative;
+    height: 20px;
+    line-height: 20px;
+    background-color: #EDEFF2;
+    border-radius: 0 10px 10px 0;
+    overflow: hidden;
+  }
+  .item-money{
+    position: absolute;
+    left: 16px;
+    top: 50%;
+    transform: translateY(-50%);
+    color: #8F5828;
+    font-size: 14px;
+    z-index: 10;
+  }
+  .item-progress-count {
+    position: absolute;
+    top: 0;
+    left: 0;
+    height: 100%;
+    border-radius: 0 10px 10px 0;
+    z-index: 9;
+  }
+  .active-progress{
+    background: #FAE7CA;
+  }
+}
+</style>

+ 4 - 1
src/main.js

@@ -4,11 +4,14 @@ import store from './store'
 import router from './router'
 import singleSpaVue from 'single-spa-vue'
 import { Loading, Message } from 'element-ui'
+import echarts from 'echarts'
+import VCharts from 'v-charts'
 import '@/utils/'
 import axios from 'axios'
 
-Vue.use(Loading.directive).use(Message)
+Vue.use(Loading.directive).use(Message).use(VCharts)
 Vue.prototype.$message = Message
+Vue.prototype.$echarts = echarts
 Vue.config.productionTip = false
 
 // 正式环境下屏蔽console.log

+ 10 - 0
src/router.js

@@ -26,6 +26,16 @@ export default new Router({
       path: '/subscribe/config',
       name: 'config',
       component: () => import('@/views/subscribe/Config.vue')
+    },
+    {
+      path: '/unitChart',
+      name: 'unitChart',
+      component: () => import('@/views/portrayal/UnitChart.vue')
+    },
+    {
+      path: '/entChart',
+      name: 'entChart',
+      component: () => import('@/views/portrayal/EntChart.vue')
     }
   ],
   scrollBehavior (to, from, savedPosition) {

+ 73 - 0
src/utils/globalFunctions.js

@@ -365,3 +365,76 @@ export function replaceKeyword (value, oldChar, newChar) {
 export function recoveryPageData (key, defaultValues = {}) {
   return sessionStorage.getItem(key) ? JSON.parse(sessionStorage.getItem(key) || '') : defaultValues
 }
+
+export function getParam (name) {
+  let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i')
+  let r = window.location.search.substr(1).match(reg)
+  let context = '' // eslint-disable-line no-unused-vars
+  if (r !== null) context = r[2]
+  // 释放变量
+  reg = null
+  r = null
+  return context === null || context === '' || context === 'undefined' ? '' : context
+}
+
+/* eslint-disable */
+Number.prototype.fixed = function (len) {
+  len = isNaN(len) ? 0 : len
+  const num = Math.pow(10, len)
+  return Math.round(this * num) / num
+}
+
+/* eslint-disable */
+Date.prototype.pattern = function (fmt) {
+  if (!fmt) return ''
+  const o = {
+    'y+': this.getFullYear(),
+    'M+': this.getMonth() + 1, // 月份
+    'd+': this.getDate(), // 日
+    // 12小时制
+    'h+': this.getHours() % 12 === 0 ? 12 : this.getHours() % 12, // 小时
+    // 24小时制
+    'H+': this.getHours(), // 小时
+    'm+': this.getMinutes(), // 分
+    's+': this.getSeconds(), // 秒
+    'q+': Math.floor((this.getMonth() + 3) / 3), // 季度
+    'S': this.getMilliseconds(), // 毫秒
+    'E+': this.getDay() // 周
+  }
+  const week = {
+    '0': '日',
+    '1': '一',
+    '2': '二',
+    '3': '三',
+    '4': '四',
+    '5': '五',
+    '6': '六'
+  }
+  if (/(y+)/.test(fmt)) {
+    fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length))
+  }
+  if (/(E+)/.test(fmt)) {
+    fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '星期' : '周') : '') + week[this.getDay() + ''])
+  }
+  for (var k in o) {
+    if (new RegExp('(' + k + ')').test(fmt)) {
+      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
+    }
+  }
+  return fmt
+}
+
+export function bSort (arr, value) {
+  const len = arr.length
+  for (let i = 0; i < len - 1; i++) {
+    for (let j = 0; j < len - 1 - i; j++) {
+      // 相邻元素两两对比,元素交换,大的元素交换到后面
+      if (arr[j][value] > arr[j + 1][value]) {
+        const temp = arr[j]
+        arr[j] = arr[j + 1]
+        arr[j + 1] = temp
+      }
+    }
+  }
+  return arr
+}

+ 24 - 0
src/views/portrayal/AnalysisChart.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="analysis-chart">
+    <!-- 年度项目统计 -->
+    <!-- 月度中标金额统计 -->
+    <!-- 市场区域分布 -->
+    <!-- 各类客户平均折扣率 -->
+    <!-- 客户类型分布 -->
+    <!-- 重点客户 -->
+  </div>
+</template>
+<script>
+export default {
+  name: 'analysis-chart',
+  components: {},
+  data () {
+    return {}
+  },
+  computed: {},
+  mounted () {},
+  methods: {}
+}
+</script>
+<style lang="scss" scoped>
+</style>

+ 207 - 0
src/views/portrayal/EntChart.vue

@@ -0,0 +1,207 @@
+<template>
+  <div class="business-chart">
+    <!-- 年度项目统计 -->
+    <div v-if="annual.show">
+      <div class="chart-title">年度项目统计</div>
+      <bar-chart id="annual" :options="annual.options" :datas="annual.data"></bar-chart>
+      <div class="chart-tips text-center">注:项目金额指所有项目的中标金额之和,少数缺失的中标金额,用项目预算补充。</div>
+    </div>
+    <!-- 月度中标金额统计 -->
+    <!-- 市场区域分布 -->
+    <!-- 各类客户平均折扣率 -->
+    <!-- 客户类型分布 -->
+    <!-- 重点客户 -->
+  </div>
+</template>
+<script>
+import BarChart from '@/components/chart/BarLineChart'
+import { getEntChart } from '@/api/modules/'
+import { getParam, bSort, moneyUnit } from '@/utils/'
+export default {
+  name: 'business-chart',
+  components: {
+    BarChart
+  },
+  data () {
+    return {
+      entPortraitInfo: {},
+      // 年度项目统计
+      annual: { 
+        show: false,
+        data: {
+          columns: ['日期', '项目数量', '项目金额'],
+          rows: []
+        },
+        options: {
+          height: '326px',
+          colors: [new this.$echarts.graphic.LinearGradient(
+            0, 1, 0, 0,
+            [
+              { offset: 1, color: '#2ABED1' },
+              { offset: 0.5, color: '#2ABED1' },
+              { offset: 0, color: '#8DE0EB' }
+            ], false
+          ), '#FF9F40'],
+          settings: {
+            showLine: ['项目金额'],
+            axisSite: { right: ['项目金额'] }
+          },
+          config: this.annualConfig
+        }
+      },
+      getEntPortraitInfoTimes: 0
+    }
+  },
+  computed: {},
+  created() {},
+  mounted () {
+    this.getChartData()
+    console.log(this.annual.data)
+  },
+  methods: {
+    async getChartData () {
+      this.getEntPortraitInfoTimes++
+      const res = await getEntChart({
+        entId: decodeURIComponent(getParam('eId'))
+      })
+      if (res.error_code === 0) {
+        if (res.data && Object.keys(res.data).length !== 0) {
+          // 将数据保存到data中
+          for (var key in res.data) {
+            this.entPortraitInfo[key] = res.data[key]
+          }
+          if (res.data.timeRange && res.data.timeRange.start) {
+            this.entPortraitInfo.timeRangeStart = res.data.timeRange.start
+          }
+          if (res.data.timeRange && res.data.timeRange.end) {
+            this.entPortraitInfo.timeRangeEnd = res.data.timeRange.end
+          }
+          // 初始化图表数据
+          // if (this.tabActiveName === '2') {
+          //   this.initChartsData()
+          // }
+          this.initChartData()
+        } else {
+          if (this.getEntPortraitInfoTimes < 3) {
+            this.getChartData()
+          }
+        }
+      }
+    },
+    // 初始化图表数据
+    initChartData () {
+      const dataSet = this.entPortraitInfo
+      // 初始化 年度项目统计数据
+      this.arrangeAnnualData(dataSet.yearData)
+    },
+    /* *********** 画像数据处理 ********** */
+    // 整理年度项目统计数据
+    arrangeAnnualData (data) {
+      if (!data) return
+      let rows = []
+      let count = 0
+      for (var key in data) {
+        const item = data[key]
+        // 统计数据总数是否为0
+        count += item.Count
+        count += item.Money
+        rows.push({
+          '日期': parseInt(key),
+          '项目数量': item.Count,
+          '项目金额': Math.round(item.Money / 10000)
+        })
+      }
+      rows = bSort(rows, '日期')
+      // 数据总量为0,不赋值
+      if (count != 0) {
+        this.annual.data.rows = rows
+        this.annual.show = true
+      } else {
+        this.annual.show = false
+      }
+    },
+    /* 配置项 */
+    // 1. 年度项目统计图表单独配置
+    annualConfig (options) {
+      console.log(options)
+      options.grid.bottom = 60
+      options.tooltip.axisPointer.shadowStyle.color = 'rgba(42, 190, 209,0.1)' // 修改点击柱条后背景颜色
+      options.legend.show = true // 显示底部数量、金额、圆点
+      options.yAxis[1].axisLabel.show = true; // 显示右侧y轴刻度
+      options.yAxis[1].axisLabel.formatter = (value, index) => {
+        return value.toString().replace(/,/, '')
+      }  // 去掉右轴逗号分割
+      // options.series[1].lineStyle.width = 2 // 折线
+      // options.series[1].lineStyle.color = '#FF9F40'
+      // 以下代码为:处理左侧y轴与右侧y轴刻度保持在同一X轴水平线上
+      const maxCountList = this.annual.data.rows.map((v) => {
+        return v['项目数量']
+      })
+      const maxPriceList = this.annual.data.rows.map((v) => {
+        return v['项目金额']
+      })
+      var maxCount = Math.ceil(Math.max.apply(null,maxCountList)).toString() // 取出数量中最大值
+      var maxPrice = Math.ceil(Math.max.apply(null,maxPriceList)).toString() // 取出金额中最大值
+      maxPrice = Math.ceil(maxPrice / (Math.pow(10, maxPrice.length -1))) * Math.pow(10,maxPrice.length - 1)
+      maxCount = Math.ceil(maxCount / (Math.pow(10, maxCount.length -1))) * Math.pow(10,maxCount.length - 1)
+      var item = options.yAxis
+      item[0].min = 0
+      item[1].min = 0
+      item[0].max = maxCount
+      item[0].interval = Math.ceil(maxCount / 5)
+      item[1].max = maxPrice;
+      item[1].interval = Math.ceil((maxPrice - 0) / 5)
+      // 处理点击浮窗显示效果
+      options.tooltip.formatter = (params) => {
+        var tip = '';
+        for (var i = 0; i < params.length; i++) {
+          // 因柱状图颜色为渐变色,此处获取到的柱状图颜色,css不能识别,需单独设置小圆点的颜色
+          params[0].marker = '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:#2ABED1"></span>'
+          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/>';
+          }
+        }
+        tip += '<div style="padding-top:2px;text-align:center;color:#9B9CA3;">' + ' - ' + params[0].name + ' - ' + '</div>'
+        return tip
+      }
+      options.legend.formatter =  (name) => {
+        var wnYuan = ['金额', '项目金额']
+        var ge = ['数量', '项目数量']
+        if (wnYuan.indexOf(name) !== -1) {
+          name += '(万元)-右轴'
+        } else if (ge.indexOf(name) !== -1) {
+          name += '(个)'
+        }
+        return name
+      }
+      return options
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.business-chart{
+  width: 1200px;
+  padding: 32px 40px;
+  margin: 0 auto;
+  background: #fff;
+  .chart-title{
+    padding: 32px 0 16px;
+    font-size: 18px;
+    color: #1d1d1d;
+    line-height: 28px;
+    font-family: 'Microsoft YaHei, Microsoft YaHei-Regular';
+  }
+  .chart-tips{
+    padding: 0 0 32px 46px;
+    font-size: 12px;
+    color: #999999;
+    line-height: 20px;
+  }
+  .text-center{
+    text-align: center;
+  }
+}
+</style>

+ 909 - 0
src/views/portrayal/UnitChart.vue

@@ -0,0 +1,909 @@
+<template>
+  <div class="unit-chart">
+    <!-- 年度项目统计 -->
+    <div v-if="years.show">
+      <div class="chart-title">年度项目统计</div>
+      <bar-chart id="years" :options="years.options" :datas="years.data"></bar-chart>
+    </div>
+    <!-- 月度采购规模 -->
+    <div v-if="monthScale.show">
+      <div class="chart-title">月度采购规模统计</div>
+      <line-chart ref="monthScale" :options="monthScale.options" :datas="monthScale.data"></line-chart>
+      <div class="chart-tips text-center">注:采购规模指中标金额,少数缺失的中标金额,用项目预算补充。</div>
+    </div>
+    <!-- 采购规模分布 -->
+    <div v-if="buyScaleFb.show">
+      <div class="chart-title">采购规模分布</div>
+      <hot-chart v-if="buyScaleFb.flag" ref="hotChart" :datas="buyScaleFb.data"></hot-chart>
+      <div class="chart-tips">注:平均节支率=(全部项目预算-全部中标金额)/全部项目预算,是指价格减让部分与原价的比率,仅统计预算和中标金额同时存在的项目。</div>
+    </div>
+    <!-- 各类招标方式占比 -->
+    <div v-if="bidType.show">
+      <div class="chart-title">各类招标方式占比</div>
+      <double-bar-chart :datas="bidType.data"></double-bar-chart>
+    </div>
+    <!-- 各行业项目平均节支率 -->
+    <div v-if="savingsRate.show">
+      <div class="chart-title">各行业项目平均节支率</div>
+      <line-chart v-if="savingsRate.flag" :options="savingsRate.options" :datas="savingsRate.data"></line-chart>
+    </div>
+    <!-- 各行业项目规模占比 -->
+    <div v-if="projectScale.show">
+      <div class="chart-title">各行业项目规模占比</div>
+      <pie-chart v-if="projectScale.flag" :height="'326px'" :options="projectScale.options" :datas="projectScale.data"></pie-chart>
+    </div>
+    <!-- 重点合作企业 -->
+    <div v-if="client.data.length > 0">
+      <div class="chart-title">重点合作企业</div>
+      <progress-chart ref="clientChart"  :datas="client.data"></progress-chart>
+    </div>
+    <!-- 合作企业注册资本分布 -->
+    <div v-if="capital.show">
+      <div class="chart-title">合作企业注册资本分布</div>
+      <bar-chart v-if="capital.flag" id="capital" :options="capital.options" :datas="capital.data"></bar-chart>
+    </div>
+    <!-- 合作企业年龄分布 -->
+    <div v-if="age.show">
+      <div class="chart-title">合作企业年龄分布</div>
+      <bar-chart v-if="age.flag" id="age" :options="age.options" :datas="age.data"></bar-chart>
+    </div>
+    <!-- 合作企业注册地分布 -->
+    <div v-if="area.show">
+      <div class="chart-title">合作企业注册地分布</div>
+      <map-chart v-if="area.flag" id="area" :options="area.options" :datas="area.data"></map-chart>
+    </div>
+    <!-- 重点合作代理机构 -->
+    <div v-if="agency.show">
+      <div class="chart-title">重点合作代理机构</div>
+      <agency-chart v-if="agency.flag" :datas="agency.data"></agency-chart>
+    </div>
+  </div>
+</template>
+<script>
+import BarChart from '@/components/chart/BarLineChart'
+import HotChart from '@/components/chart/HotChart'
+import LineChart from '@/components/chart/LineChart'
+import DoubleBarChart from '@/components/chart/DoubleBarChart'
+import pieChart from '@/components/chart/PieChart'
+import ProgressChart from '@/components/chart/ProgressChart'
+import MapChart from '@/components/chart/MapChart'
+import AgencyChart from '@/components/chart/AgencyChart'
+import { getUnitChart } from '@/api/modules/'
+import { getParam, bSort, moneyUnit } from '@/utils/'
+export default {
+  name: 'unit-chart',
+  components: {
+    BarChart,
+    HotChart,
+    LineChart,
+    DoubleBarChart,
+    pieChart,
+    ProgressChart,
+    MapChart,
+    AgencyChart
+  },
+  data () {
+    return {
+      // 年度项目统计
+      years: {
+        show: true,
+        data: {
+          columns: [],
+          rows: []
+        },
+        options: {
+          height: '326px',
+          colors: [new this.$echarts.graphic.LinearGradient(
+            0, 1, 0, 0,
+            [
+              { offset: 1, color: '#2ABED1' },
+              { offset: 0.5, color: '#2ABED1' },
+              { offset: 0, color: '#8DE0EB' }
+            ], false
+          ), '#FF9F40'],
+          config: this.configYears,
+          settings: {
+            showLine: ['项目金额'],
+            axisSite: { right: ['项目金额'] }
+          }
+        }
+      },
+      // 月度采购规模
+      monthScale: {
+        show: true,
+        data: {
+          columns: [],
+          rows: []
+        },
+        options: {
+          height: '326px',
+          colors: ['#05A6F3', '#0BD991', '#FF9F40'],
+          config: this.configMonthScle
+        }
+      },
+      // 采购规模分布
+      buyScaleFb: {
+        show: true,
+        flag: false,
+        data: []
+      },
+      // 各类招标方式占比
+      bidType: {
+        show: true,
+        data: []
+      },
+      // 各行业项目平均节支率
+      savingsRate: {
+        show: true,
+        flag: false,
+        data: {
+          columns: [],
+          rows: []
+        },
+        options: {
+          height: '326px',
+          colors: ['#FB483D', '#05A6F3', '#0BD991', '#FF9F40', '#8E6DF2', '#C0C4CC'],
+          config: this.configSavingsRate
+        }
+      },
+      // 各行业项目规模占比(饼图)
+      projectScale: {
+        show: true,
+        flag: false,
+        data: [],
+        // 传入的配置
+        options: {
+          tooltip: {
+            formatter: event
+          }
+        }
+      },
+      // 重点合作企业
+      client: {
+        show: true,
+        flag: false,
+        data: []
+      },
+      // 合作企业注册资本分布
+      capital: {
+        show: true,
+        flag: false,
+        data: {
+          columns: [],
+          rows: [],
+          customData: []
+        },
+        options: {
+          height: '320px',
+          colors: ['#05a6f3', '#FF9F40'],
+          config: this.configCapital,
+          settings: {
+            showLine: ['累计采购规模'],
+            axisSite: { right: ['累计采购规模'] }
+          }
+        }
+      },
+      // 合作企业年龄分布
+      age: {
+        show: true,
+        flag: false,
+        data: {
+          columns: ['企业年龄', '企业数量'],
+          rows: [],
+          customData: []
+        },
+        options: {
+          height: '320px',
+          colors: ['#05a6f3'],
+          config: this.configAge
+        }
+      },
+      // 合作企业年龄分布
+      area: {
+        show: false,
+        flag: false,
+        data: {
+          columns: ['企业注册地', '企业数量', '累计采购规模', '采购项目数量', '平均节支率'],
+          rows: [],
+          customData: []
+        },
+        options: {
+          height: '570px',
+          colors: ['#05a6f3'],
+          config: this.configArea
+        }
+      },
+      // 重点合作代理机构
+      agency: {
+        show: false,
+        flag: false,
+        data: []
+      },
+      reqCount: 0,
+      timer: null
+    }
+  },
+  computed: {},
+  mounted () {
+    this.getChartData()
+  },
+  methods: {
+    // 画像数据
+    async getChartData () {
+      const res = await getUnitChart({
+        buyer: decodeURIComponent(getParam('entName'))
+      })
+      if (res.error_code === 0) {
+        this.reqCount++
+        if (res.data && Object.keys(res.data).length > 0) {
+          const info = {}
+          info.province = res.data.province ? res.data.province.replace(/省|市|自治区|特别行政区|壮族|回族|维吾尔/g, '') : '--'
+          info.city = res.data.city
+          info.buyerClass = res.data.buyerclass ? res.data.buyerclass : '--'
+          info.start = new Date(Number(res.data.timeRange.start + '000')).pattern('yyyy/MM/dd')
+          info.end = new Date(Number(res.data.timeRange.end + '000')).pattern('yyyy/MM/dd')
+          info.buyerCount = res.data.project_count ? res.data.project_count + '个' : '--'
+          info.winnerCount = res.data.winner_count ? res.data.winner_count + '个' : '--'
+          info.otherWinner = res.data.otherProvincesWinnerCount ? res.data.otherProvincesWinnerCount + '个' : '--'
+          info.buyerScale = res.data.bidamount_count ? moneyUnit(res.data.bidamount_count) : '--'
+          info.fail_count = res.data.fail_count ? res.data.fail_count + '条' : '--'
+          console.log(info, 'info')
+          this.$emit('base-info', info)
+          // 年度项目统计图表数据
+          if (res.data.yearData && Object.keys(res.data.yearData).length > 0) {
+            this.formatYearsData(res.data.yearData)
+          } else {
+            this.years.show = false
+          }
+          // 月度采购规模
+          if (res.data.monthData && Object.keys(res.data.monthData).length > 0) {
+            this.formatMonthScaleData(res.data.monthData)
+          } else {
+            this.monthScale.show = false
+          }
+          // 采购规模分布
+          if (res.data.moneyRange && res.data.moneyRange.length > 0) {
+            const countArr = res.data.moneyRange.map((v) => {
+              return v.project_count
+            })
+            const max = Math.max.apply(null, countArr)
+            if (max === 0) {
+              this.buyScaleFb.show = false
+            }
+            this.buyScaleFb.data = res.data.moneyRange
+            this.buyScaleFb.flag = true
+            // this.formatHotChartData(res.data.moneyRange)
+          } else {
+            this.buyScaleFb.show = false
+          }
+          // 各类招标方式占比
+          if (res.data.bidtypeData && res.data.bidtypeData.length > 0) {
+            this.bidType.data = this.formatterBarChart(res.data.bidtypeData)
+          } else {
+            this.bidType.show = false
+          }
+          // 各行业项目平均节支率
+          if (res.data.rate && Object.keys(res.data.rate).length > 0) {
+            this.formatSavingsRateData(res.data.rate)
+            this.savingsRate.flag = true
+          } else {
+            this.savingsRate.show = false
+          }
+          // 各行业项目规模占比
+          if (res.data.top12 && res.data.top12.length > 0) {
+            this.formatProjectScaleData(res.data.top12)
+            this.projectScale.flag = true
+          } else {
+            this.projectScale.show = false
+          }
+          // 重点合作企业
+          if (res.data.topShow && res.data.topShow.length > 0) {
+            this.client.data = this.formatClientData(res.data.topShow)
+            this.client.flag = true
+            this.client.show = true
+          } else {
+            this.client.show = false
+          }
+          // 合作企业注册资本分布
+          if (res.data.withCapitalData && res.data.withCapitalData.length > 0) {
+            this.formatCapitalData(res.data.withCapitalData)
+            this.capital.flag = true
+          } else {
+            this.capital.show = false
+          }
+          // 合作企业年龄分布
+          if (res.data.withEstablishData && res.data.withEstablishData) {
+            this.formatAgeData(res.data.withEstablishData)
+            this.age.flag = true
+          } else {
+            this.age.show = false
+          }
+          // 合作企业注册地分布
+          if (res.data.withAreaData && res.data.withAreaData.length > 0) {
+            this.formatAreaData(res.data.withAreaData)
+            this.area.flag = true
+            this.area.show = true
+          } else {
+            this.area.show = false
+          }
+          // 重点合作代理机构
+          if (res.data.topAgencyData && res.data.topAgencyData.length > 0) {
+            const data = res.data.topAgencyData
+            data.forEach((v, i) => {
+              v.last_with_time = new Date(Number(v.last_with_time + '000')).pattern('yyyy/MM/dd')
+              v.parent = v.project_count / data[0].project_count * 100 + '%'
+            })
+            this.agency.data = data
+            this.agency.flag = true
+            this.agency.show = true
+          } else {
+            this.agency.show = false
+          }
+        } else {
+          if (this.reqCount < 3) {
+            this.timer = setTimeout(() => {
+              this.getChartData()
+            })
+          } else {
+            console.log('请求了仍获取不到图表数据,不再请求')
+            clearTimeout(this.timer)
+          }
+        }
+      }
+    },
+    /* ******** 配置项部分 ******* */
+    configYears (options) {
+      options.yAxis[1].axisLabel.formatter = (value, index) => {
+        return value.toString().replace(/,/, '')
+      }
+      options.legend.show = true
+      options.yAxis[1].axisLabel.show = true // 显示右侧y轴刻度
+      var maxCountList = this.years.data.rows.map((v) => {
+        return v['项目数量']
+      })
+      var maxPriceList = this.years.data.rows.map((v) => {
+        return v['项目金额']
+      })
+      let maxCount = Math.ceil(Math.max.apply(null, maxCountList)).toString()
+      let maxPrice = Math.ceil(Math.max.apply(null, maxPriceList)).toString()
+      maxPrice = Math.ceil(maxPrice / (Math.pow(10, maxPrice.length - 1))) * Math.pow(10, maxPrice.length - 1)
+      maxCount = Math.ceil(maxCount / (Math.pow(10, maxCount.length - 1))) * Math.pow(10, maxCount.length - 1)
+      const item = options.yAxis
+      item[0].min = 0
+      item[1].min = 0
+      item[0].max = maxCount
+      item[0].interval = Math.ceil(maxCount / 5)
+      item[1].max = maxPrice
+      item[1].interval = Math.ceil((maxPrice - 0) / 5)
+      options.tooltip.formatter = (params) => {
+        let tip = ''
+        for (let i = 0; i < params.length; i++) {
+          // 因柱状图颜色为渐变色,此处获取到的柱状图颜色,css不能识别,需单独设置小圆点的颜色
+          params[0].marker = '<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:#2ABED1"></span>'
+          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/>'
+          } else {}
+        }
+        tip += '<div style="padding-top:2px;text-align:center;color:#9B9CA3;">' + ' - ' + params[0].name + ' - ' + '</div>'
+        return tip
+      }
+      options.legend.formatter = (name) => {
+        if (name === '项目金额') {
+          name = name + '(万元)-右轴'
+        }
+        if (name === '项目数量') {
+          name = name + '(个)'
+        }
+        return name
+      }
+      return options
+    },
+    // 月度采购规模数据及修改配置
+    configMonthScle (options) {
+      options.tooltip.formatter = (params) => {
+        let tip = ''
+        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>'
+          if (!params[i].value[1] && params[i].value[1] !== 0) {
+            tip = tip + ''
+          } else {
+            tip = tip + params[i].marker + params[i].seriesName + params[i].name + '采购规模:' + params[i].value[1].toString().replace(/,/, '') + '万元' + '<br/>'
+          }
+        }
+        tip += '<div style="padding-top:2px;text-align:center;color:#9B9CA3;">' + ' - ' + params[0].name + ' - ' + '</div>'
+        return tip
+      }
+      return options
+    },
+    configSavingsRate (options) {
+      const data = this.savingsRate.data.rows
+      const arr = []
+      for (let i = 0; i < data.length; i++) {
+        var obj = data[i]
+        for (var key in obj) {
+          if (key !== '日期' && obj[key]) {
+            arr.push(obj[key])
+          }
+        }
+      }
+      let maxRate = Math.ceil(Math.max.apply(null, arr)).toString()
+      maxRate = Math.ceil(maxRate / (Math.pow(10, maxRate.length - 1))) * Math.pow(10, maxRate.length - 1)
+      options.xAxis[0].axisLabel.margin = 12
+      options.yAxis[0].axisLabel.formatter = '{value}%'
+      options.yAxis[0].min = 0
+      options.yAxis[0].max = maxRate
+      options.yAxis[0].interval = Math.ceil(maxRate / 5)
+      options.grid = {
+        top: 20,
+        right: 16,
+        bottom: 70,
+        left: 10
+      }
+      options.legend.textStyle.padding = [0, 18, 0, 0]
+      options.legend.bottom = 10
+      options.tooltip.formatter = (params) => {
+        let tip = ''
+        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>'
+          if (params[i].value[1] === undefined || params[i].value[1] === null || isNaN(params[i].value[1])) {
+            tip = tip + params[i].marker + params[i].seriesName + ':--<br/>'
+          } else {
+            tip = tip + params[i].marker + params[i].seriesName + ':' + params[i].value[1] + '%<br/>'
+          }
+        }
+        tip += '<div style="padding-top:2px;text-align:center;color:#9B9CA3;">' + ' - ' + params[0].name + ' - ' + '</div>'
+        return tip
+      }
+      return options
+    },
+    configCapital (options) {
+      const customData = this.capital.data.customData
+      var maxCountList = this.capital.data.rows.map((v) => {
+        return v['企业数量']
+      })
+      var maxPriceList = this.capital.data.rows.map((v) => {
+        return v['累计采购规模']
+      })
+      var maxLeft = Math.max.apply(null, maxCountList)
+      var maxRight = Math.max.apply(null, maxPriceList)
+      maxLeft = Math.ceil(maxLeft).toString()
+      maxRight = Math.ceil(maxRight).toString()
+      var l = Math.ceil(maxLeft / (Math.pow(10, maxLeft.length - 1))) * Math.pow(10, maxLeft.length - 1)
+      var r = Math.ceil(maxRight / (Math.pow(10, maxRight.length - 1))) * Math.pow(10, maxRight.length - 1)
+      var item = options.yAxis
+      item[0].min = 0
+      item[1].min = 0
+      item[0].max = l
+      item[0].interval = Math.ceil(l / 5)
+      item[1].max = Math.ceil(r)
+      item[1].interval = Math.ceil((r - 0) / 5)
+      item[0].minInterval = 1
+      options.tooltip.axisPointer.shadowStyle.color = 'rgba(5, 166, 243,0.1)'
+      options.tooltip.formatter = (params) => {
+        const obj = {}
+        let tip = ''
+        customData.forEach((v) => {
+          if (v['注册资本'] === params[0].name) {
+            for (const key in v) {
+              obj[key] = v[key]
+            }
+          }
+        })
+        const regMoney = '<span>注册资本:' + obj['注册资本'] + '</span></br>'
+        const count = '<span>企业数量:' + obj['企业数量'] + '个</span></br>'
+        const scale = '<span>累计采购规模:' + obj['累计采购规模'] + '万元</span></br>'
+        const pCount = '<span>采购项目数量:' + obj['采购项目数量'] + '个</span></br>'
+        const rate = (typeof obj['平均节支率'] === 'number' && !isNaN(obj['平均节支率'])) ? '<span>平均节支率:' + (obj['平均节支率'] * 100).fixed(2) + '%</span></br>' : ''
+        tip = regMoney + count + scale + pCount + rate
+        return tip
+      }
+      options.series.forEach((item) => {
+        if (item.name === '累计采购规模') {
+          item.type = 'line'
+        } else {
+          item.type = 'bar'
+          item.barWidth = 12
+        }
+      })
+      options.legend.formatter = (name) => {
+        if (name === '累计采购规模') {
+          name = '累计采购规模(万元)-右轴'
+        }
+        if (name === '企业数量') {
+          name = '企业数量(个)'
+        }
+        return name
+      }
+      return options
+    },
+    configAge (options) {
+      for (let i = 0; i < options.series.length; i++) {
+        options.series[i].barWidth = 20
+        options.series[i].stack = '企业年龄分布'
+      }
+      const arr = this.age.data.customData
+      options.yAxis[0].minInterval = 1
+      options.xAxis[0].name = '企业年龄(年)'
+      options.xAxis[0].axisLabel.interval = 0
+      options.tooltip.axisPointer.shadowStyle.color = 'rgba(5, 166, 243,0.1)'
+      options.tooltip.formatter = (params) => {
+        const obj = {}
+        let tip = ''
+        arr.forEach((v) => {
+          if (v['企业年龄'] === params[0].name) {
+            for (const key in v) {
+              obj[key] = v[key]
+            }
+          }
+        })
+        const regMoney = '<span>企业年龄:' + obj['企业年龄'] + '</span></br>'
+        const count = '<span>企业数量:' + obj['企业数量'] + '个</span></br>'
+        const scale = '<span>累计采购规模:' + obj['累计采购规模'] + '万元</span></br>'
+        const pCount = '<span>采购项目数量:' + obj['采购项目数量'] + '个</span></br>'
+        const rate = (typeof obj['平均节支率'] === 'number' && !isNaN(obj['平均节支率'])) ? '<span>平均节支率:' + (obj['平均节支率'] * 100).fixed(2) + '%</span></br>' : ''
+        tip = regMoney + count + scale + pCount + rate
+        return tip
+      }
+      return options
+    },
+    configArea (options) {
+      const arr = this.area.data.customData
+      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>企业注册地:' + obj['企业注册地'] + '</span></br>'
+          const count = '<span>企业数量:' + obj['企业数量'] + '个</span></br>'
+          const scale = '<span>累计采购规模:' + obj['累计采购规模'] + '万元</span></br>'
+          const pCount = '<span>采购项目数量:' + obj['采购项目数量'] + '个</span></br>'
+          const rate = (typeof obj['平均节支率'] === 'number' && !isNaN(obj['平均节支率'])) ? '<span>平均节支率:' + (obj['平均节支率'] * 100).fixed(2) + '%</span></br>' : ''
+          tip = regArea + count + scale + pCount + rate
+        } else {
+          tip = ''
+        }
+        return tip
+      }
+      return options
+    },
+    /* ********  数据格式化部分 ******* */
+    // 格式化年度项目统计数据
+    formatYearsData (data) {
+      if (!data) return
+      const rows = []
+      let count = 0
+      /* eslint-disable */
+      for (const key in data) {
+        rows.push({
+          '年份': key,
+          '项目数量': data[key].Count,
+          '项目金额': (data[key].Money / 10000).fixed(0)
+        })
+        count += data[key].Count
+      }
+      /* eslint-enable */
+      if (count > 0) {
+        this.years.data.columns = ['年份', '项目数量', '项目金额']
+        this.years.data.rows = rows
+      } else {
+        this.years.show = false
+      }
+    },
+    // 格式化月度采购规模数据
+    formatMonthScaleData (data) {
+      if (!data) return
+      const rows = []
+      const columns = ['月份']
+      let count = 0
+      const years = []
+      for (var key in data) {
+        years.push(parseInt(key))
+      }
+      years.sort((a, b) => { return a - b })
+      years.forEach(function (item) {
+        columns.push(item + '年')
+      })
+      for (let i = 1; i <= 12; i++) {
+        const columnsItem = {}
+        columns.forEach((item) => {
+          let value = ''
+          if (item === '月份') {
+            value = i + '月'
+          } else if (/年/g.test(item)) {
+            if (data[item.slice(0, -1)]) {
+              value = data[item.slice(0, -1)][i]
+              value = (value / 10000).fixed(2)
+              count += value
+            }
+          }
+          columnsItem[item] = value
+        })
+        rows.push(columnsItem)
+      }
+      if (count > 0) {
+        this.monthScale.data.columns = columns
+        this.monthScale.data.rows = rows
+      } else {
+        this.monthScale.show = false
+      }
+    },
+    // 格式化采购规模分布(热力图)数据
+    // 格式化各类招标方式占比数据(双向柱图)
+    formatterBarChart (data) {
+      if (!data) return
+      data.forEach((item) => {
+        item.money = (item.money / 10000).fixed(2)
+        item.moneyShare = (item.moneyShare * 100).fixed(2)
+        item.numShare = (item.numShare * 100).fixed(2)
+        switch (item.type) {
+          case '招标':
+            item.type = '公开招标'
+            break
+          case '邀标':
+            item.type = '邀请招标'
+            break
+          case '询价':
+            item.type = '询价采购'
+            break
+          case '单一':
+            item.type = '单一来源采购'
+            break
+          case '竞价':
+            item.type = '竞价采购'
+            break
+          case '竞谈':
+            item.type = '竞争性谈判'
+            break
+        }
+      })
+      const newData = data
+      const maxLeftNum = Math.max.apply(Math, data.map((o) => { return o.numShare }))
+      const maxRightNum = Math.max.apply(Math, data.map((o) => { return o.moneyShare }))
+      newData.forEach((v) => {
+        v.lPercent = (v.numShare / maxLeftNum * 100).fixed(2)
+        v.rPercent = (v.moneyShare / maxRightNum * 100).fixed(2)
+      })
+      return newData
+    },
+    // 格式化平均节支率数据
+    formatSavingsRateData (data) {
+      if (!data) return
+      const rows = []
+      const columns = ['日期', '全部行业']
+      let count = 0
+      for (const key in data) {
+        if (key !== '全部行业') {
+          columns.push(key)
+        }
+      }
+      const industryArr = []
+      for (const key in data['全部行业']) {
+        industryArr.push(key)
+      }
+      industryArr.sort((a, b) => { return a - b })
+      industryArr.forEach((item) => {
+        const rowsItem = {
+          '日期': item + '年' // eslint-disable-line
+        }
+        for (const k in data) {
+          count += (data[k][item] * 100)
+          rowsItem[k] = data[k][item] == null ? null : (data[k][item] * 100).fixed(2)
+        }
+        rows.push(rowsItem)
+      })
+      if (count > 0) {
+        this.savingsRate.data.columns = columns
+        this.savingsRate.data.rows = rows
+      } else {
+        this.savingsRate.show = false
+      }
+    },
+    // 格式化各行业项目规模占比数据
+    formatProjectScaleData (data) {
+      if (!data) return
+      const arr = []
+      // 降序排列
+      data.sort((a, b) => {
+        return b.bidamount_share - a.bidamount_share
+      })
+      data.forEach((item) => {
+        arr.push(item.scopeclassName, item.bidamount_share, item.bidamount_count, item.project_count, item.rate_avg)
+      })
+      const normal = ['行业', '采购规模占比', '采购规模', '采购项目数量', '平均节支率']
+      var newArr = this.arrTrans(5, arr)
+      newArr.unshift(normal)
+      this.projectScale.data = newArr
+      this.projectScale.options.tooltip.formatter = (params) => {
+        var tip = ''
+        var data = params.data
+        params.marker = '<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:' + params.color + '"></span>'
+        var percent = '<span style="padding-left:13px;">采购规模占比:' + (data[1] * 100).fixed(2) + '%</span></br>'
+        var scale = '<span style="padding-left:13px;">采购规模:' + moneyUnit(data[2].fixed(2)) + '</span></br>'
+        var count = '<span style="padding-left:13px;">采购项目数量:' + data[3] + '个</span></br>'
+        var rate = (typeof data[4] === 'number' && !isNaN(data[4])) ? '<span style="padding-left:13px;">平均节支率:' + (data[4] * 100).fixed(2) + '%</span></br>' : ''
+        tip = params.marker + params.name + '<br/>' + percent + scale + count + rate
+        return tip
+      }
+    },
+    // 格式化注册资本数据
+    formatCapitalData (data) {
+      if (!data) return
+      const columns = ['注册资本', '企业数量', '累计采购规模']
+      const rows = []
+      const capData = []
+      data.forEach((item) => {
+        /* eslint-disable */
+        rows.push({
+          '注册资本': this.formatMoneyRange(item.key),
+          '企业数量': item.ent_count,
+          '累计采购规模': (item.money_count /10000).fixed(2)
+        })
+        capData.push({
+          '注册资本': this.formatMoneyRange(item.key),
+          '企业数量': item.ent_count,
+          '累计采购规模': (item.money_count /10000).fixed(2),
+          '采购项目数量': item.project_count,
+          '平均节支率': item.rate_avg
+        })
+        /* eslint-enable */
+      })
+      this.capital.data.columns = columns
+      this.capital.data.rows = rows
+      this.capital.data.customData = capData
+    },
+    // 格式化企业年龄分布数据
+    formatAgeData (data) {
+      if (!data) return
+      const ageRows = []
+      const customRows = []
+      data.forEach((item) => {
+        if (item.key === '0_1') {
+          item.key = '<1'
+        }
+        if (item.key === '40') {
+          item.key = '≥40'
+        }
+        /* eslint-disable */
+        ageRows.push({
+          '企业年龄': item.key.replace('_', '-'),
+          '企业数量': item.ent_count
+        })
+        customRows.push({
+          '企业年龄': item.key.replace('_', '-'),
+          '企业数量': item.ent_count,
+          '累计采购规模': (item.money_count /10000).fixed(2),
+          '采购项目数量': item.project_count,
+          '平均节支率': item.rate_avg
+        })
+        /* eslint-enable */
+      })
+      this.age.data.rows = ageRows
+      this.age.data.customData = customRows
+    },
+    // 格式化企业注册地分布数据
+    formatAreaData (data) {
+      if (!data) return
+      const rows = []
+      const custom = []
+      data.forEach((item) => {
+        /* eslint-disable */
+        rows.push({
+          '企业注册地': item.area_name,
+          '企业数量': item.ent_count
+        })
+        custom.push({
+          '企业注册地': item.area_name,
+          '企业数量': item.ent_count,
+          '累计采购规模': (item.money_count / 10000).fixed(2),
+          '采购项目数量': item.project_count,
+          '平均节支率': item.rate_avg
+        })
+        /* eslint-enable */
+      })
+      this.area.data.rows = rows
+      this.area.data.customData = custom
+    },
+    // 格式化重点合作企业数据
+    formatClientData (data) {
+      if (!data) return
+      data.forEach((v, i) => {
+        v.topData.forEach((s, j) => {
+          s.parent = s.countMoney / v.topData[0].countMoney * 100 + '%'
+          s.lastTime = new Date(Number(s.lastTime + '000')).pattern('yyyy/MM/dd')
+          s.countMoney = s.countMoney ? moneyUnit(s.countMoney) : ''
+        })
+      })
+      const newData = data.map((v) => {
+        v.topData = v.topData.filter((s) => {
+          return s.winnerName && s.winnerName.trim().length
+        })
+        return v
+      }).filter(function (v, i) {
+        return v.topData.length
+      })
+      return newData
+    },
+    /* ******** 公共函数部分 ******* */
+    // 求最大项目数量
+    getMaxProjectCount (analysisArr) {
+      if (!analysisArr || !$.isArray(analysisArr)) return
+      const itemMaxKey = 3 // 每一项中要比较大小的索引
+      const arr = JSON.parse(JSON.stringify(analysisArr))
+      bSort(arr, itemMaxKey)
+      const maxCount = arr[arr.length - 1][itemMaxKey]
+      let maxIndex = -1
+      analysisArr.some((item, index) => {
+        const gotIt = item[itemMaxKey] === maxCount
+        if (gotIt) maxIndex = index
+        return gotIt
+      })
+      return maxIndex
+    },
+    // 一维数组转换为二维数组
+    arrTrans (num, arr) {
+      const newArr = []
+      arr.forEach((item, index) => {
+        // 计算该元素为第几个素组内
+        const page = Math.floor(index / num)
+        // 判断是否存在
+        if (!newArr[page]) {
+          newArr[page] = []
+        }
+        newArr[page].push(item)
+      })
+      return newArr
+    },
+    // 处理金额区间转换
+    formatMoneyRange (key) {
+      if (!key) return
+      if (key.indexOf('_')) {
+        key = key.split('_')
+      } else {
+        key = key.split('-')
+      }
+      if (!key[1]) return '>' + (key[0] / 100000000).fixed(2) + '亿'
+      key[0] = key[0] >= 100000000 ? (key[0] / 100000000).fixed(2) : (key[0] / 10000).fixed(2)
+      key[1] = key[1] >= 100000000 ? (key[1] / 100000000).fixed(2) + '亿' : (key[1] / 10000).fixed(2) + '万'
+      return key[0] + '-' + key[1]
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.unit-chart{
+  width: 1200px;
+  padding: 32px 40px;
+  margin: 0 auto;
+  background: #fff;
+  .chart-title{
+    padding: 32px 0 16px;
+    font-size: 18px;
+    color: #1d1d1d;
+    line-height: 28px;
+    font-family: 'Microsoft YaHei, Microsoft YaHei-Regular';
+  }
+  .chart-tips{
+    padding: 0 0 32px 46px;
+    font-size: 12px;
+    color: #999999;
+    line-height: 20px;
+  }
+  .text-center{
+    text-align: center;
+  }
+}
+</style>

+ 121 - 23
src/views/subscribe/Config.vue

@@ -1,11 +1,11 @@
 <template>
   <div class="config">
     <!-- 订阅设置 -->
-    <sub-config></sub-config>
+    <sub-config :datas="setData"></sub-config>
     <!-- 关键词设置 -->
-    <key-config></key-config>
+    <key-config :datas="setData"></key-config>
     <!-- 关键词列表 -->
-    <key-list v-if="config.list && config.list.length > 0" :list="config.list" @openKeyDialog="openKeyDialog"></key-list>
+    <key-list v-if="setData.keywordsList.length > 0" :list="setData.keywordsList" @openKeyDialog="openKeyDialog"></key-list>
   </div>
 </template>
 
@@ -23,37 +23,135 @@ export default {
   },
   data () {
     return {
-      // 父组件传子组件值
-      config: {
-        list: [
-          {
-            a_key: [
-              {
-                appendkey: [],
-                key: ['计算机'],
-                notkey: []
-              }
-            ],
-            s_item: '调试分类1'
-          }
-        ]
-      }
+      setData: {
+        area: '全国',
+        industry: '全部',
+        infotypes: '全部类型',
+        keywordsList: [],
+        projectmatch: null,
+        buyclassmatch: null,
+        mathway: 1,
+        maxKeyCounts: 0
+      },
+      // 区域对象
+      areaobj: {},
+      // 行业对象
+      insturyarr: [],
+      // 关键词
+      keyarr: [],
+      // 信息类型
+      infotypearr: []
     }
   },
   computed: {},
   created () {
-    // this.getBigInfo()
+    this.getBigInfo()
   },
   methods: {
-    getBigInfo () {
-      getBigMemberInfo().then((res) => {
-        console.log(res)
-      })
+    async getBigInfo () {
+      const provinceArr = []
+      let areaArr = []
+      const data = await getBigMemberInfo()
+      const res = data.data
+      if (res) {
+        this.setData.maxKeyCounts = res.key_max_length ? res.key_max_length : 0
+        if (res.member_jy && JSON.stringify(res.member_jy) === '{}') {
+          this.setdata.area = '全国'
+          this.setdata.industry = '全部'
+          this.setdata.getkeywords = '未分类'
+          this.setdata.infotypes = '全部类型'
+        } else {
+          // 项目匹配
+          if (res.member_jy.i_projectmatch === 1) {
+            this.setData.projectmatch = true
+          } else {
+            this.setData.projectmatch = false
+          }
+          // 其他采购单位
+          if (res.member_jy.i_matchbuyerclass_other === 1) {
+            this.setData.buyclassmatch = true
+          } else {
+            this.setData.buyclassmatch = false
+          }
+          // 匹配方式
+          this.setData.mathWay = res.member_jy.i_matchway
+          // 区域
+          if (res.member_jy.o_area && JSON.stringify(res.member_jy.o_area) !== '{}') {
+            this.areaobj = res.member_jy.o_area
+            for (const key in res.member_jy.o_area) {
+              provinceArr.push({
+                province: key,
+                citys: res.member_jy.o_area[key]
+              })
+              provinceArr.forEach((province, i) => {
+                const cityArr = []
+                let pStr = ''
+                province.citys.forEach((city, ii) => {
+                  cityArr.push(city)
+                })
+                if (cityArr.length === 0) {
+                  pStr = province.province
+                } else {
+                  pStr = province.province + '(' + cityArr.join('、') + ')'
+                }
+                areaArr.push(pStr)
+              })
+              areaArr = areaArr.join('、')
+              this.setdata.area = areaArr
+            }
+          } else {
+            this.setData.area = '全国'
+          }
+          // 采购单位行业
+          if (res.member_jy.a_buyerclass && JSON.stringify(res.member_jy.a_buyerclass) !== '[]') {
+            this.insturyarr = res.member_jy.a_buyerclass
+            this.setData.industry = res.member_jy.a_buyerclass.join('、')
+          } else {
+            this.setData.industry = '全部'
+          }
+          // 信息类型
+          if (res.member_jy.a_infotype && JSON.stringify(res.member_jy.a_infotype) !== '[]') {
+            this.setData.infotypes = res.member_jy.a_infotype.join('、')
+            this.infotypearr = res.member_jy.a_infotype
+          } else {
+            this.setData.infotypes = '全部类型'
+          }
+          // 关键词
+          if (res.member_jy.a_items && JSON.stringify(res.member_jy.a_items) !== '[]') {
+            this.keyarr = res.member_jy.a_items
+            this.setData.keywordsList = res.member_jy.a_items
+            // this.setData.getkeywords = this.getKeyWordnames(this.keyarr)
+          } else {
+            // this.setData.getkeywords = '未分类'
+            this.setData.keywordsList = []
+          }
+        }
+      } else {
+        console.log('状态值为:' + res.status)
+      }
     },
     // 打开关键词弹框
     openKeyDialog (data) {
       console.log(data)
       this.dialog.keyDialog = data.showDialog
+    },
+    // 关键词
+    getKeyWordnames (group) {
+      const keysArr = []
+      if (group) {
+        if (group.length === 0) {
+          return '未分类'
+        } else {
+          group.forEach((classList) => {
+            if (classList && classList.s_item) {
+              keysArr.push(classList.s_item)
+            }
+          })
+          return keysArr.join('、')
+        }
+      } else {
+        return '未分类'
+      }
     }
   }
 

+ 164 - 19
src/views/subscribe/components/KeyConfig.vue

@@ -1,31 +1,31 @@
 <template>
   <div class="keywords">
-    <div class="key-title">关键词设置 <span style="font-size:14px;"><em style="color: #2cb7ca;">{{counts}}</em>/300</span></div>
+    <div class="key-title">关键词设置 <span style="font-size:14px;"><em style="color: #2cb7ca;">{{keyCounts}}</em>/{{datas.maxKeyCounts}}</span></div>
     <div class="key-content">
       <div class="item">
         <div class="item-label">关键词分类:</div>
         <div class="item-value">
-          <el-input class="custom-long-input" v-model="keywords.classify" placeholder="请输入关键词分类"></el-input>
+          <el-input class="custom-long-input" v-model.trim="cur.classify" maxlength="20" placeholder="请输入关键词分类"></el-input>
         </div>
       </div>
       <div class="item">
         <div class="item-label item-label-required">关键词:</div>
         <div class="item-value">
-          <el-input class="custom-long-input" v-model="keywords.keyword" placeholder="请输入关键词"></el-input>
+          <el-input class="custom-long-input" v-model.trim="cur.key" maxlength="20" placeholder="请输入关键词"></el-input>
         </div>
       </div>
       <div class="item">
         <div class="item-label"></div>
         <div class="item-value item-keywords-value">
           <div>
-            <div class="add-word-list" v-for="(a,i) in appendkeyData" :key="i">
-              <el-input class="custom-short-input" placeholder="请输入附加词"></el-input>
+            <div class="add-word-list" v-for="(add,index) in cur.appendkey" :key="'add' + index">
+              <el-input v-model.trim="cur.appendkey[index]" class="custom-short-input" maxlength="20" placeholder="请输入附加词"></el-input>
             </div>
             <div class="add-tag" @click="addAttachWordsFn">+添加附加词</div>
           </div>
           <div>
-            <div class="add-word-list" v-for="(n,j) in notKeyData" :key="'0' + j">
-              <el-input class="custom-short-input" placeholder="请输入排除词"></el-input>
+            <div class="add-word-list" v-for="(not,index) in cur.notkey" :key="'0' + index">
+              <el-input v-model.trim="cur.notkey[index]" class="custom-short-input" maxlength="20" placeholder="请输入排除词"></el-input>
             </div>
             <div class="add-tag" @click="addExcludeWordsFn">+添加排除词</div>
           </div>
@@ -34,7 +34,7 @@
       <div class="item">
         <div class="item-label"></div>
         <div class="item-value">
-          <button type="button" class="save-btn">保存关键词</button>
+          <button type="button" :disabled="keyDisabled" class="save-btn" @click="submitKeywords">保存关键词</button>
           <div class="keywords-help">
             <p>例:某公司主管业务为软件系统开发 </p>
             <p>关键词:目标信息钟的关键性词语,如“软件系统” </p>
@@ -48,35 +48,177 @@
 </template>
 <script>
 import { Input, Button } from 'element-ui'
+import { updateKey } from '@/api/modules/subscribe'
 export default {
   name: 'key-config',
-  props: [],
+  props: {
+    datas: {
+      keywordsList: Array,
+      maxKeyCounts: Number
+    }
+  },
   components: {
     [Input.name]: Input,
     [Button.name]: Button
   },
   data () {
     return {
-      counts: 300,
-      keywords: {
+      cur: {
         classify: '',
         key: '',
-        appendkey: '',
-        notKey: ''
+        appendkey: [''],
+        notkey: ['']
       },
-      appendkeyData: [],
-      notKeyData: []
+      curArr: {
+        a_key: [],
+        s_item: ''
+      }
     }
   },
-  mounted () {},
+  computed: {
+    keyCounts () {
+      let count = 0
+      this.datas.keywordsList.forEach(v => {
+        if (v && v.a_key) {
+          count += v.a_key.length
+        }
+      })
+      return count
+    },
+    keyDisabled () {
+      return !this.cur.key
+    }
+  },
+  mounted () {
+    console.log(this.datas)
+  },
   methods: {
+    // 保存数据
+    saveCurData () {
+      let newData = []
+      this.curArr.s_item = this.cur.classify
+      this.curArr.a_key = [{
+        appendkey: this.cur.appendkey,
+        key: [this.cur.key],
+        notkey: this.cur.notkey
+      }]
+      this.datas.keywordsList.push(this.curArr)
+      newData = this.datas.keywordsList
+      console.log(newData)
+      return newData
+    },
+    // 提交关键词
+    submitKeywords () {
+      const classArr = this.getClassArray()
+      const keyArr = this.getKeyTotalArray()
+      if (classArr.indexOf(this.cur.classify) > -1) {
+        return this.$message({
+          type: 'warning',
+          message: '分类名不能重复'
+        })
+      }
+      if (keyArr.indexOf(this.cur.key) > -1) {
+        return this.$message({
+          type: 'warning',
+          message: '关键词不能重复'
+        })
+      }
+      const params = {
+        a_items: this.saveCurData()
+      }
+      console.log(params, 'data')
+      updateKey({
+        a_items: params.a_items
+      }).then((res) => {
+        console.log(res)
+      })
+    },
     // 添加附加词
     addAttachWordsFn () {
-      this.appendkeyData.push('')
+      const arr = this.cur.appendkey
+      if (this.inputIsEmpty(arr)) {
+        this.$message({
+          message: '请输入附加词',
+          type: 'warning'
+        })
+      } else {
+        this.cur.appendkey.push('')
+      }
     },
     // 添加排除词
     addExcludeWordsFn () {
-      this.notKeyData.push('')
+      const arr = this.cur.notkey
+      if (this.inputIsEmpty(arr)) {
+        this.$message({
+          message: '请输入排除词',
+          type: 'warning'
+        })
+      } else {
+        this.cur.notkey.push('')
+      }
+    },
+    // 判断附加词排除词输入框是否有空
+    inputIsEmpty (arr) {
+      return arr.some((v) => !v)
+    },
+    getClassArray () {
+      const classArr = []
+      this.datas.keywordsList.forEach((v) => {
+        if (v.s_item) {
+          classArr.push(v.s_item)
+        }
+      })
+      return classArr
+    },
+    // 获取所有关键词的key的属性,并返回一个数组(主要用于判断添加关键词是否重复)
+    getKeyTotalArray () {
+      const keysArr = []
+      this.datas.keywordsList.forEach((s) => {
+        if (s && s.a_key && this.isArray(s.a_key)) {
+          s.a_key.forEach((v) => {
+            if (this.isArray(v.key)) {
+              v.key.forEach(x => {
+                keysArr.push(x)
+              })
+            } else {
+              keysArr.push(v.key)
+            }
+          })
+        }
+      })
+      return keysArr
+    },
+    // 判断变量是否是数组
+    isArray (o) {
+      return Object.prototype.toString.call(o) === '[object Array]'
+    },
+    groupListKeyArrToString (list, state) {
+      let mList = JSON.parse(JSON.stringify(list))
+      if (this.isArray(mList)) {
+        mList.forEach((keyList) => {
+          if (keyList && keyList.a_key && this.isArray(keyList.a_key)) {
+            keyList.a_key.forEach((item) => {
+              if (!state && this.isArray(item.key)) {
+                // 数组拼接
+                item.key = item.key.join(' ')
+              } else if (state === 1) {
+                // 字符串切割
+                item.key = item.key.replace(/\s+/g, ' ').split(' ')
+              } else {
+                if (state) {
+                  console.error('关键词key,必须为字符串。并且state参数只有两种数值0和1')
+                } else {
+                  console.error('关键词key,必须为数组')
+                }
+              }
+            })
+          }
+        })
+      } else {
+        mList = []
+      }
+      console.log(mList)
+      return mList
     }
   }
 }
@@ -164,6 +306,10 @@ export default {
       background: #2cb7ca;
       border-radius: 6px;
       color: #fff;
+      &:disabled{
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
     }
     .keywords-help{
       width: 412px;
@@ -174,7 +320,6 @@ export default {
       text-align: justify;
     }
   }
-  
   // element-ui样式修改
   ::v-deep.el-input__inner{
     font-size: 14px;

+ 28 - 4
src/views/subscribe/components/KeyList.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="classify">
-    <div class="classify-list" v-for="(item,index) in list" :key="'1' + index">
+    <div class="classify-list" v-for="(item,index) in getList" :key="'1' + index">
       <div class="classify-title">
         <span class="title-text">{{item.s_item}}</span>
         <span class="icon-edit"></span>
@@ -10,9 +10,9 @@
         <div class="list" v-for="(v,i) in item.a_key" :key="'2' + i">
           <div class="list-box">
             <div class="list-value">
-              <p>关键词: {{v.key.join('、')}}</p>
-              <p>附加词: {{v.appendkey.join('、')}}</p>
-              <p>排除词: {{v.notkey.join('、')}}</p>
+              <p>关键词: {{v.key || '--'}}</p>
+              <p>附加词: {{v.appendkey || '--'}}</p>
+              <p>排除词: {{v.notkey || '--'}}</p>
             </div>
             <div class="list-icon"></div>
             <div class="list-edit" @click="editHandle(v)">
@@ -39,6 +39,27 @@ export default {
   data () {
     return {}
   },
+  computed: {
+    getList () {
+      const newArr = this.list
+      newArr.forEach((v) => {
+        if (v.a_key) {
+          v.a_key.forEach((s) => {
+            if (this.isArray(s.key)) {
+              s.key = s.key.join('、')
+            }
+            if (this.isArray(s.appendkey)) {
+              s.appendkey = s.appendkey.join('、')
+            }
+            if (this.isArray(s.notkey)) {
+              s.notkey = s.notkey.join('、')
+            }
+          })
+        }
+      })
+      return newArr
+    }
+  },
   mounted () {},
   methods: {
     editHandle (item) {
@@ -46,6 +67,9 @@ export default {
         showDialog: true,
         data: item
       })
+    },
+    isArray (o) {
+      return Object.prototype.toString.call(o) === '[object Array]'
     }
   }
 }

+ 16 - 15
src/views/subscribe/components/SubConfig.vue

@@ -5,19 +5,19 @@
       <div class="item">
         <div class="item-label item-label-required">区域:</div>
         <div class="item-value">
-          <el-input class="custom-long-input" v-model="form.region" disabled></el-input>
+          <el-input class="custom-long-input" v-model="datas.area" disabled></el-input>
         </div>
       </div>
       <div class="item">
         <div class="item-label item-label-required">采购单位行业:</div>
         <div class="item-value">
-          <el-input class="custom-long-input" v-model="form.industry" disabled></el-input>
+          <el-input class="custom-long-input" v-model="datas.industry" disabled></el-input>
         </div>
       </div>
       <div class="item">
         <div class="item-label item-label-required">信息类型:</div>
         <div class="item-value">
-          <el-input class="custom-long-input" v-model="form.infotype" disabled></el-input>
+          <el-input class="custom-long-input" v-model="datas.infotypes" disabled></el-input>
         </div>
       </div>
       <div class="item-other">
@@ -27,7 +27,7 @@
             :disabled="projectSwitch.disabled"
             v-loading="projectSwitch.loading"
             @change="switchProject"
-            v-model="form.matchProject"
+            v-model="datas.projectmatch"
             :width="44"
             active-text="关"
             inactive-text="开"
@@ -44,7 +44,7 @@
             :disabled="unitSwitch.disabled"
             v-loading="unitSwitch.loading"
             @change="switchUnit"
-            v-model="form.otherUnit"
+            v-model="datas.buyclassmatch"
             :width="44"
             active-text="关"
             inactive-text="开"
@@ -57,7 +57,7 @@
       <div class="item-other">
         <div class="item-label">关键词匹配方式:</div>
         <div class="item-value item-other-value">
-          <el-radio-group v-model="form.mathWay" @change="chooseMathWay">
+          <el-radio-group v-model="datas.mathWay" @change="chooseMathWay">
             <div class="radio-item">
               <el-radio :label="1">按标题匹配</el-radio>
             </div>
@@ -75,7 +75,16 @@
 import { Switch, Input, RadioGroup, Radio, Button } from 'element-ui'
 export default {
   name: 'sub-config',
-  props: [],
+  props: {
+    datas: {
+      area: String,
+      industry: String,
+      infotypes: String,
+      projectmatch: Boolean,
+      buyclassmatch: Boolean,
+      mathway: Number
+    }
+  },
   components: {
     [Switch.name]: Switch,
     [Input.name]: Input,
@@ -85,14 +94,6 @@ export default {
   },
   data () {
     return {
-      form: {
-        region: '安徽、北京、重庆、河南(郑州、许昌)、河北、海南、新疆、内蒙',
-        industry: '安监、保监、财政',
-        infotype: '全部',
-        matchProject: true,
-        otherUnit: true,
-        mathWay: 1
-      },
       projectSwitch: {
         loading: false,
         disabled: false

+ 49 - 1
yarn.lock

@@ -3347,6 +3347,28 @@ ecc-jsbn@~0.1.1:
     jsbn "~0.1.0"
     safer-buffer "^2.1.0"
 
+echarts-amap@1.0.0-rc.6:
+  version "1.0.0-rc.6"
+  resolved "https://registry.npm.taobao.org/echarts-amap/download/echarts-amap-1.0.0-rc.6.tgz#5782a74daee52ed44ce3f8f62577561783f09e16"
+  integrity sha1-V4KnTa7lLtRM4/j2JXdWF4PwnhY=
+
+echarts-liquidfill@^2.0.2:
+  version "2.0.6"
+  resolved "https://registry.npm.taobao.org/echarts-liquidfill/download/echarts-liquidfill-2.0.6.tgz#0668dc61d87a6262003090bd32c55aa8108c252e"
+  integrity sha1-BmjcYdh6YmIAMJC9MsVaqBCMJS4=
+
+echarts-wordcloud@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.npm.taobao.org/echarts-wordcloud/download/echarts-wordcloud-1.1.3.tgz?cache=0&sync_timestamp=1610779172014&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fecharts-wordcloud%2Fdownload%2Fecharts-wordcloud-1.1.3.tgz#07b140c8ba76b19c317b43c310f3d5dc99289ff2"
+  integrity sha1-B7FAyLp2sZwxe0PDEPPV3Jkon/I=
+
+echarts@4.8.0:
+  version "4.8.0"
+  resolved "https://registry.nlark.com/echarts/download/echarts-4.8.0.tgz#b2c1cfb9229b13d368ee104fc8eea600b574d4c4"
+  integrity sha1-ssHPuSKbE9No7hBPyO6mALV01MQ=
+  dependencies:
+    zrender "4.3.1"
+
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -5455,7 +5477,7 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.npm.taobao.org/lodash.uniq/download/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.3, lodash@^4.2.0:
+lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.2.0:
   version "4.17.21"
   resolved "https://registry.npm.taobao.org/lodash/download/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha1-Z5WRxWTDv/quhFTPCz3zcMPWkRw=
@@ -5990,6 +6012,11 @@ num2fraction@^1.2.2:
   resolved "https://registry.npm.taobao.org/num2fraction/download/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
   integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=
 
+numerify@1.2.9:
+  version "1.2.9"
+  resolved "https://registry.npm.taobao.org/numerify/download/numerify-1.2.9.tgz#af4696bb1d57f8d3970a615d8b0cd53d932bd559"
+  integrity sha1-r0aWux1X+NOXCmFdiwzVPZMr1Vk=
+
 oauth-sign@~0.9.0:
   version "0.9.0"
   resolved "https://registry.npm.taobao.org/oauth-sign/download/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
@@ -8496,6 +8523,11 @@ utila@~0.4:
   resolved "https://registry.npm.taobao.org/utila/download/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
   integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=
 
+utils-lite@0.1.10:
+  version "0.1.10"
+  resolved "https://registry.npm.taobao.org/utils-lite/download/utils-lite-0.1.10.tgz#d2908c0482e23c31e6b082558540e7134ffad7d7"
+  integrity sha1-0pCMBILiPDHmsIJVhUDnE0/619c=
+
 utils-merge@1.0.1:
   version "1.0.1"
   resolved "https://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
@@ -8506,6 +8538,17 @@ uuid@^3.3.2, uuid@^3.4.0:
   resolved "https://registry.npm.taobao.org/uuid/download/uuid-3.4.0.tgz?cache=0&sync_timestamp=1607460077975&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuuid%2Fdownload%2Fuuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
   integrity sha1-sj5DWK+oogL+ehAK8fX4g/AgB+4=
 
+v-charts@1.19.0:
+  version "1.19.0"
+  resolved "https://registry.npm.taobao.org/v-charts/download/v-charts-1.19.0.tgz#07b701800b159bd514264ffc8bf12b0405142da3"
+  integrity sha1-B7cBgAsVm9UUJk/8i/ErBAUULaM=
+  dependencies:
+    echarts-amap "1.0.0-rc.6"
+    echarts-liquidfill "^2.0.2"
+    echarts-wordcloud "^1.1.3"
+    numerify "1.2.9"
+    utils-lite "0.1.10"
+
 v8-compile-cache@^2.0.3:
   version "2.3.0"
   resolved "https://registry.npm.taobao.org/v8-compile-cache/download/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
@@ -8968,3 +9011,8 @@ yorkie@^2.0.0:
     is-ci "^1.0.10"
     normalize-path "^1.0.0"
     strip-indent "^2.0.0"
+
+zrender@4.3.1:
+  version "4.3.1"
+  resolved "https://registry.npm.taobao.org/zrender/download/zrender-4.3.1.tgz?cache=0&sync_timestamp=1618215769844&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fzrender%2Fdownload%2Fzrender-4.3.1.tgz#baf8aa6dc8187a2f819692d7d5f9bedfa2b90fa3"
+  integrity sha1-uviqbcgYei+BlpLX1fm+36K5D6M=

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini