Vue2+Echarts+koa2+websocket电商平台数据可视化实时检测系统(一)

  • Post author:
  • Post category:vue










最终的效果如图所示



最终效果涉及





6


个图表


, 5


种图表类型


,


它们分别是折线图


,


柱状图


,


地图


,


散点图


,


饼图.。每个图表的数据都是从后端推送到前端来的


,


不过在项目的初期


,


我们会先使用



ajax



由前端主动获取数





,


后续会使用



WebSocket



进行改造。整个项目的架构





基于



Vue






,


所以我们需要创建



Vue



项目


,


然后在



Vue



项目中开发各个图表组件






1.




前端项目的准备






1.1.




vue









c


1


i


脚手架创建项









1.1.






1




脚手架环境的安装




在全局环境中安装


vue





cli


脚手




npm install -g @vue/cli





1






.






1.2.




工程的创建




使用命令行执




vue create vision






体的配置项如下


:



手动选择特性



集成



Router



,



Vuex



,



CSS




Pre









processors




是否选用历史模式的路由



选择



Less










CSS



的预处理器



选择



ESLint



的配置






么时候进行



Lint



提示



如何存放



Babel



,



ESLint



等配置文









否保存以上配置以便下次创建项目时使用






置选择完之后


,


就开始创建项目了


,


这个过程需要一些时间


:



当项目就创建完成了


,


会看到这个提示



运行默认的项




cd vision
npm run serve





1.1.3.




删除无关代







将目录使用



vscode



打开










App




.




vue



中的代码


,


将布局和样式删除


,


变成如下代码


:



<


template


>



<


div


id


=





app





>



<


router





view


/


>



</


div


>



<


/


template


>



<


style


lang


=





less





>



<


/


style


>



删除



components




/




HelloWorld




.




vue



这个文件



删除



views




/




About




.




vue







views




/




Home




.




vue



这两个文






修改



router




/




index




.




js



中的代码


,


去除路由配置和



Home



组件导入的代码



import


Vue


from





vue






import


VueRouter


from





vue





router






Vue


.


use


(


VueRouter


)



const


routes


=


[]



const


router


=


new


VueRouter


({




r


outes



}


)



export


default


router





1






.2.




项目的基本配置




在项目根目录下创建



vue




.




config




.




js



文件



在文件中增加代码



//


使





vue





cli


创建出来的


vue


工程


,


Webpack


的配置是被隐藏起来了的



//


如果想覆盖


Webpack


中的默认配置


,


需要在项目的根路径下增加


vue


.


config


.


js


文件



module


.


exports


=


{




devServer


: {




port


:


8999


,


//


端口号的配置



open


:


true


//


自动打开浏览






}



}





1.3.




全局



echarts



对象






1.3.1.




引入



echarts








将资料文件夹中的



static



目录复制到



public



目录之










public




/




index




.




html



文件中引入



echarts




.




min




.




js



文件





1






.3.2.




挂载到



Vue



原型上








src




/




main




.




js



文件中挂









于在



index




.




html



中已经通过


script


标签引入了



echarts




.




js



文件夹


,


故在



window



全局对象中是


存在



echarts



全局对象


,


将其挂载到



Vue



的原型对象



…..


.



//


将全局


echarts


对象挂载到


Vue


的原型对象上



Vue


.


prototype


.


$


echarts


=


window


.


echarts



…..


.





1






.3.3.




使用全局



echarts



对象




在其他组件中使用



this


.


$


echarts





1






.4.





axios



的处理






1.4.1.




安装



axios








npm


install


axios





1






.4.2.




封装



axios



对象








src




/




main




.




js



文件中配置



axios



并且挂载到


Vue


的原型对象上



…..


.



import


axios


from





axios






axios


.


defaults


.


baseURL


=





http


://127.0.0.1:8888/


api


/’



/


/





axios


挂载到


Vue


的原型对象上



Vue


.


prototype


.


$


http


=


axios



…..


.





1






.4.3.




使用



axios



对象




在其他组件中使用

this.$http





2.




单独图表组件


的开发







项目的初期


,


我们会每个图表单独的进行开发


,


最后再将所有的图表合并到一个界面中


.





单独开发每个图表的时候


,


一个图表会用一个单独的路径进行全屏展示


,


他们分别是


:



商家销售统






http




://127.0.0.1:8999/#




/




sellerpage




销量趋势分析

http




://127.0.0.1:8999/#




/




trendpage




商家地图分




http




:/




/




127.0.0.1:8999/#/




mappage




地区销量排行

http




://




127.0.0.1:8999/#/




rankpage




热销商品占




http




:/




/




127.0.0.1:8999/#/




hotpage




库存销量分析

http




://127.0.0.1:8999/#




/




stockpage






2






.1.




商家销量排行







终的效果如下图所示


:





2






.1.1.




组件结构设计








src




/




components




/



目录下建立



Seller




.




vue



,


这个组件是真实展示图表的组件



给外层


div


增加类样式



com









container




建立一个显示图表的


div


元素



给新增的这个


div


增加类样式



com









chart








src




/




views




/



目录下建立



SellerPage




.




vue



,


这个组件是对应于路由


/


seller


而展示的



给外层


div


元素增加样式



com









page








SellerPage



中引入



Seller



组件


,


并且注册和使用



<


!–


这个组件是对应于路由规则中


/


seller


这条路径的



在这个组件中


,


需要展示


Seller


.


vue


这个组件



Seller


.


vue





是真正显示图表的组件









>



<


template


>



<


div


class


=





com





page





>



<


seller


></


seller


>



</


div


>



<


/


template


>



<


script


>



import


Seller


from


‘@/


components


/


Seller






export


default


{




data


() {




return


{}



}


,



methods


: {}


,



components


:


{




seller


:


Seller



}



}



<


/


script


>



<


style


lang


=





less





scoped


>



<


/


style


>







src




/




views




/



目录下建立



SellerPage




.




vue



,


这个组件是对应于路由


/


seller


而展示的



给外层


div


元素增加样式



com









page








SellerPage



中引入



Seller



组件


,


并且注册和使用



<


!–


这个组件是对应于路由规则中


/


seller


这条路径的



在这个组件中


,


需要展示


Seller


.


vue


这个组件



Seller


.


vue





是真正显示图表的组件









>



<


template


>



<


div


class


=





com





page





>



<


seller


></


seller


>



</


div


>



<


/


template


>



<


script


>



import


Seller


from


‘@/


components


/


Seller






export


default


{




data


() {




return


{}



}


,



methods


: {}


,



components


:


{




seller


:


Seller



}



}



<


/


script


>



<


style


lang


=





less





scoped


>



<


/


style


>






加路由规则


,






src




/




router




/




index




.




js



文件中修改



…..


.



import


SellerPage


from


‘@/


views


/


SellerPage






…..


.



const


routes


=


[



{




path


:


‘/


sellerpage





,



component


:


SellerPage



}



]



新建



src




/




assets




/




css




/




global




.




less



增加宽高样式



原则就是将所有的容器的宽度和高度设置为占满父容器

html,
body,
#app {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
}
.com-page {
width: 100%;
height: 100%;
overflow: hidden;
}
.com-container {
width: 100%;
height: 100%;
overflow: hidden;
}
.com-chart {
width: 100%;
height: 100%;
overflow: hidden;
}







main.js



中引入样式



import


‘./


assets


/


css


/


global


.


less






打开浏








,


输入



http




://127.0.0.1:8999/#/




sellerpage






Seller


组件是否能够显示





2.1.2.




图表


Se

ll

er


.


vue


基本功能的实







1.





mounted


生命周期中初始化



echartsInstance



对象



2.





mounted


中获取服务器的数据



3.


将获取到的数据设置到图表上

<script>
export default {
data () {
return {
chartInstance: null, // echarts实例对象
allData: [] // 服务器获取的所有数据
}
},
mounted () {
// 由于初始化echarts实例对象需要使用到dom元素 ,因此必须要放到mounted中 , 而不是created
this.initChart()
this.getData()
},
methods: {
initChart () {
this.chartInstance = this.$echarts.init(this.$refs.seller_ref) // 初始化 echarts实例对象
},
async getData () {
const { data: res } = await this.$http.get('seller') // 获取数据 this.allData = res
// 对allData进行从大到小的排序
this.allData.sort((a, b) => {
return a.value - b.value
})
this.updateChart()
},
updateChart () {
// 处理数据并且更新界面图表
const sellerNames = this.allData.map((item) => {
return item.name
})
const sellerValues = this.allData.map((item) => {
return item.value
})
const option = {
xAxis: {
type: 'value'
},
yAxis: {
type: 'category',
data: sellerNames
},
series: [
{
type: 'bar',
data: sellerValues
}
]
}
this.chartInstance.setOption(option)
}
}
}
</script>



4.


拆分配置项



option




初始化配置项



拥有数据之后的配置项





2






.






1.3.




分页动画的实现




数据的处理


,





5


个元素显示一






数据的处理



动画的启动和停止



鼠标事件的处理





2






.1.4.





UI



效果调整




主题文件的导入




public/index.html



中引入






题的指定


,


在初始化



echarts




实例对象



的时候指定




src




/




components




/




Seller




.




vue




边框圆角的设







src




/




assets




/




css




/




global




.




less




canvas


{




border





radius


:


20


px


;



}









图表样式的配置



标题的位置和颜色



const


initOption


=


{




title


:


{




text


:





▎ 商家销量排行





,



left


:


20


,



top


:


20


,



textStyle


: {




textStyle


: {







color





:


“#


fff






}



}



}


,






标轴的大小



const


initOption


=


{




…..


.



grid


:


{




top


:


‘20%’


,



left


:


‘3%’


,



right


:


‘6%’


,



bottom


:


‘3%’


,



containLabel


:


true



}


,






具提示和背景



const


initOption


=


{




…..


.



tooltip


: {




trigger


:





axis





,



axisPointer


: {




type


:





line





,



z


:


0


,



lineStyle


: {




width


:


66


,



color


:


‘#2


D


3443′



}



}



}


,



const


initOption


=


{




…..


.



series


:


[



{




…..


.



label


:


{




show


:


true


,



position


:





right





,



textStyle


: {




color


:


‘#


fff






}



}


,



柱宽度和柱圆角的实现



const


initOption


=


{




…..


.



series


:


[



{




…..


.



barWidth


:


66


,



itemStyle


: {




barBorderRadius


:


[


0


,


33


,


33


,


0


]



}



}



]



}



柱颜色渐变的实现



线


性渐变可以通过



LinearGradient



进行实现




LinearGradient



需要








5


个参数


,


前四个代表两个点的相对位置


,


第五个参数代表颜色变化





范围




0, 0, 1, 0



代表的是从左往右的方向

const initOption = {
series: [
{
......
itemStyle: {
barBorderRadius: [0, 33, 33, 0],
color: new this.$echarts.graphic.LinearGradient(0, 0, 1,
0, [
{
offset: 0,
color: '#5052EE'
},
{
offset: 1,
color: '#AB6EE5'
}
])
}
}
]





2






.1.5.




分辨率适配




对窗口大小变化的事件进行监听




mount




ed



时候监听



mounted


()


{




this


.


initChart


()



this


.


getData


(


)



window


.


addEventListener


(





resize





,


this


.


screenAdapter


)



}









销毁时取消监听



destroyed


() {




clearInterval


(


this


.


timerId


)



//


在组件销毁的时候


,


需要将监听器


取消掉



window


.


removeEventListener


(





resize





,


this


.


screenAdapter


)



}


,



获取图表容器的宽度计算字体大小



//


当浏览器的大小发生变化的时候


,


会调用的方法


,


来完成屏幕的适配



screenAdapter


() {




/


/


console


.


log


(


this


.$


refs


.


seller


_


ref


.


offsetWidth


)



const


titleFontSize


=


this


.


$


refs


.


seller


_


ref


.


offsetWidth


/


100


*


3.6

2.1.6完整代码Seller.vue

<!-- 商家销量统计的横向柱状图 -->
<template>
  <div class="com-container">
    <div class="com-chart" ref="seller_ref"></div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      chartInstance: null,
      allData: null, // 服务器返回的数据
      currentPage: 1, // 当前显示的页数
      totalPage: 0, // 一共有多少页
      timerId: null // 定时器的标识
    }
  },
  mounted () {
    this.initChart()
    this.getData()
    window.addEventListener('resize', this.screenAdapter)
    // 在页面加载完成的时候, 主动进行屏幕的适配
    this.screenAdapter()
  },
  destroyed () {
    clearInterval(this.timerId)
    // 在组件销毁的时候, 需要将监听器取消掉
    window.removeEventListener('resize', this.screenAdapter)
  },
  methods: {
    // 初始化echartInstance对象
    initChart () {
      this.chartInstance = this.$echarts.init(this.$refs.seller_ref, 'chalk')
      // 对图表初始化配置的控制
      const initOption = {
        title: {
          text: '▎商家销售统计',
          left: 20,
          top: 20
        },
        grid: {
          top: '20%',
          left: '3%',
          right: '6%',
          bottom: '3%',
          containLabel: true // 距离是包含坐标轴上的文字
        },
        xAxis: {
          type: 'value'
        },
        yAxis: {
          type: 'category'
        },
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'line',
            z: 0,
            lineStyle: {
              color: '#2D3443'
            }
          }
        },
        series: [
          {
            type: 'bar',
            label: {
              show: true,
              position: 'right',
              textStyle: {
                color: 'white'
              }
            },
            itemStyle: {
              // 指明颜色渐变的方向
              // 指明不同百分比之下颜色的值
              color: new this.$echarts.graphic.LinearGradient(0, 0, 1, 0, [
                // 百分之0状态之下的颜色值
                {
                  offset: 0,
                  color: '#5052EE'
                },
                // 百分之100状态之下的颜色值
                {
                  offset: 1,
                  color: '#AB6EE5'
                }
              ])
            }
          }
        ]
      }
      this.chartInstance.setOption(initOption)
      // 对图表对象进行鼠标事件的监听
      this.chartInstance.on('mouseover', () => {
        clearInterval(this.timerId)
      })
      this.chartInstance.on('mouseout', () => {
        this.startInterval()
      })
    },
    // 获取服务器的数据
    async getData () {
      // http://127.0.0.1:8888/api/seller
      const { data: ret } = await this.$http.get('seller')
      this.allData = ret
      // 对数据排序
      this.allData.sort((a, b) => {
        return a.value - b.value // 从小到大的排序
      })
      // 每5个元素显示一页
      this.totalPage = this.allData.length % 5 === 0 ? this.allData.length / 5 : this.allData.length / 5 + 1
      this.updateChart()
      // 启动定时器
      this.startInterval()
    },
    // 更新图表
    updateChart () {
      const start = (this.currentPage - 1) * 5
      const end = this.currentPage * 5
      const showData = this.allData.slice(start, end)
      const sellerNames = showData.map((item) => {
        return item.name
      })
      const sellerValues = showData.map((item) => {
        return item.value
      })
      const dataOption = {
        yAxis: {
          data: sellerNames
        },
        series: [
          {
            data: sellerValues
          }
        ]
      }
      this.chartInstance.setOption(dataOption)
    },
    startInterval () {
      if (this.timerId) {
        clearInterval(this.timerId)
      }
      this.timerId = setInterval(() => {
        this.currentPage++
        if (this.currentPage > this.totalPage) {
          this.currentPage = 1
        }
        this.updateChart()
      }, 3000)
    },
    // 当浏览器的大小发生变化的时候, 会调用的方法, 来完成屏幕的适配
    screenAdapter () {
      // console.log(this.$refs.seller_ref.offsetWidth)
      const titleFontSize = this.$refs.seller_ref.offsetWidth / 100 * 3.6
      // 和分辨率大小相关的配置项
      const adapterOption = {
        title: {
          textStyle: {
            fontSize: titleFontSize
          }
        },
        tooltip: {
          axisPointer: {
            lineStyle: {
              width: titleFontSize
            }
          }
        },
        series: [
          {
            barWidth: titleFontSize,
            itemStyle: {
              barBorderRadius: [0, titleFontSize / 2, titleFontSize / 2, 0]
            }
          }
        ]
      }
      this.chartInstance.setOption(adapterOption)
      // 手动的调用图表对象的resize 才能产生效果
      this.chartInstance.resize()
    }
  }
}
</script>

<style lang="less" scoped>
</style>





2






.2.




销量趋势分析




最终的效果如下


:





2






.






2.1.




代码环境的准备





TrendPage




.




vue


<!--
针对于 /trendpage 这条路径而显示出来的
在这个组件中 , 通过子组件注册的方式 , 要显示出Trend.vue这个组件
-->
<template>
<div class="com-page">
<trend></trend>
</div>
</template>

<script>
import Trend from '@/components/Trend'
export default {
data () {
return {}
},
methods: {},
components: {
trend: Trend
}
}
</script>

<style lang="less" scoped>
</style>




Trend




.




vue


<template>
  <div class="com-container">
    <div class="title" :style="comStyle">
      <span>{{ '▎ ' +  showTitle }}</span>
      <span class="iconfont title-icon" :style="comStyle"  @click="showChoice = !showChoice">&#xe6eb;</span>
      <div class="select-con" v-show="showChoice" :style="marginStyle">
        <div class="select-item" v-for="item in selectTypes" :key="item.key" @click="handleSelect(item.key)">
          {{ item.text }}
        </div>
      </div>
    </div>
    <div class="com-chart" ref="trend_ref"></div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      chartInstane: null,
      allData: null, // 从服务器中获取的所有数据
      showChoice: false, // 是否显示可选项
      choiceType: 'map', // 显示的数据类型
      titleFontSize: 0 // 指明标题的字体大小
    }
  },
  mounted () {
    this.initChart()
    this.getData()
    window.addEventListener('resize', this.screenAdapter)
    this.screenAdapter()
  },
  destroyed () {
    window.removeEventListener('resize', this.screenAdapter)
  },
  computed: {
    selectTypes () {
      if (!this.allData) {
        return []
      } else {
        return this.allData.type.filter(item => {
          return item.key !== this.choiceType
        })
      }
    },
    showTitle () {
      if (!this.allData) {
        return ''
      } else {
        return this.allData[this.choiceType].title
      }
    },
    // 设置给标题的样式
    comStyle () {
      return {
        fontSize: this.titleFontSize + 'px'
      }
    },
    marginStyle () {
      return {
        marginLeft: this.titleFontSize + 'px'
      }
    }
  },
  methods: {
    initChart () {
      this.chartInstane = this.$echarts.init(this.$refs.trend_ref, 'chalk')
      const initOption = {
        grid: {
          left: '3%',
          top: '35%',
          right: '4%',
          bottom: '1%',
          containLabel: true
        },
        tooltip: {
          trigger: 'axis'
        },
        legend: {
          left: 20,
          top: '15%',
          icon: 'circle'
        },
        xAxis: {
          type: 'category',
          boundaryGap: false
        },
        yAxis: {
          type: 'value'
        }
      }
      this.chartInstane.setOption(initOption)
    },
    async getData () {
      // await this.$http.get()
      // 对allData进行赋值
      const { data: ret } = await this.$http.get('trend')
      this.allData = ret
      console.log(this.allData)
      this.updateChart()
    },
    updateChart () {
      // 半透明的颜色值
      const colorArr1 = [
        'rgba(11, 168, 44, 0.5)',
        'rgba(44, 110, 255, 0.5)',
        'rgba(22, 242, 217, 0.5)',
        'rgba(254, 33, 30, 0.5)',
        'rgba(250, 105, 0, 0.5)'
      ]
      // 全透明的颜色值
      const colorArr2 = [
        'rgba(11, 168, 44, 0)',
        'rgba(44, 110, 255, 0)',
        'rgba(22, 242, 217, 0)',
        'rgba(254, 33, 30, 0)',
        'rgba(250, 105, 0, 0)'
      ]
      // 处理数据
      // 类目轴的数据
      const timeArr = this.allData.common.month
      // y轴的数据 series下的数据
      const valueArr = this.allData[this.choiceType].data
      const seriesArr = valueArr.map((item, index) => {
        return {
          name: item.name,
          type: 'line',
          data: item.data,
          stack: this.choiceType,
          areaStyle: {
            color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
              {
                offset: 0,
                color: colorArr1[index]
              }, // %0的颜色值
              {
                offset: 1,
                color: colorArr2[index]
              } // 100%的颜色值
            ])
          }
        }
      })
      // 图例的数据
      const legendArr = valueArr.map(item => {
        return item.name
      })
      const dataOption = {
        xAxis: {
          data: timeArr
        },
        legend: {
          data: legendArr
        },
        series: seriesArr
      }
      this.chartInstane.setOption(dataOption)
    },
    screenAdapter () {
      this.titleFontSize = this.$refs.trend_ref.offsetWidth / 100 * 3.6
      const adapterOption = {
        legend: {
          itemWidth: this.titleFontSize,
          itemHeight: this.titleFontSize,
          itemGap: this.titleFontSize,
          textStyle: {
            fontSize: this.titleFontSize / 2
          }
        }
      }
      this.chartInstane.setOption(adapterOption)
      this.chartInstane.resize()
    },
    handleSelect (currentType) {
      this.choiceType = currentType
      this.updateChart()
      this.showChoice = false
    }
  }
}
</script>

<style lang="less" scoped>
.title {
  position: absolute;
  left: 20px;
  top: 20px;
  z-index: 10;
  color: white;
  .title-icon {
    margin-left: 10px;
    cursor: pointer;
  }
  .select-con {
    background-color: #222733;
  }
}
</style>




router




/




index




.




js


......
import TrendPage from '@/views/TrendPage'
......
const routes = [
......
{
path: '/trendpage',
component: TrendPage
}
]
......





2.






2.2.




图表基本功能的实现




数据的获取



async


getData


()


{




//


获取服务器的数据


,





this


.


allData


进行赋


值之后


,


调用


updateChart


方法更新图表



const


{



data


:


ret


}


=


await


this


.


$


http


.


get


(





trend





)



this


.


allData


=


ret



this


.


updateChart


()



}



数据的处理

updateChart () {
// x轴的数据
const timeArrs = this.allData.common.month
// y轴的数据 , 暂时先取出map这个节点的数据
// map代表地区销量趋势
// seller代表商家销量趋势
// commodity代表商品销量趋势
const valueArrs = this.allData.map.data
// 图表数据 , 一个图表中显示5条折线图
const seriesArr = valueArrs.map((item, index) => {
return {
type: 'line', // 折线图
name: item.name,
data: item.data,
}
})
const dataOption = {
xAxis: {
data: timeArrs
},
legend: {
data: legendArr
},
series: seriesArr
}
this.chartInstance.setOption(dataOption)
}



初始化配置



const


initOption


=


{




xAxis


:


{




type


:





category





,



boundaryGap


:


false



}


,



yAxis


:


{




type


:





value






}



}






叠图效果






实现堆叠图的效果


,


series


下的每个对象都需要配置上相同的


stack


属性



updateChart


() {




const


timeArrs


=


this


.


allData


.


common


.


month



const


valueArrs


=


this


.


allData


.


map


.


data



const


seriesArr


=


valueArrs


.


map


((


item


,


index


)


=>


{




return


{




type


:





line





,



name


:


item


.


name


,



data


:


item


.


data


,



stack


:





map





//


stack


值相同


,


可以形成堆叠图


效果



}



}


)



…..


.



}



图例效果

updateChart () {
......
const valueArrs = this.allData.map.data
const seriesArr = valueArrs.map((item, index) => {
return {
type: 'line',
name: item.name,
data: item.data,
stack: 'map'
}
})
// 准备图例数据 , 它需要和series下的每个对象的name属性保持一致
const legendArr = valueArrs.map(item => {
return item.name
})
const dataOption = {
......
legend: {
data: legendArr
}
......
}
this.chartInstance.setOption(dataOption) }





2






.2.3.





UI



效果的调整




主题的使用



initChart


() {




this


.


chartInstance


=


this


.


$


echarts


.


init


(


this


.


$


refs


.


trend


_


ref


,





chalk





)


}



主题使用完之后


,


发现折线图都变成了平滑折线图了


,


这是因为在



chalk




.




js



主题文件中


,


设置







smooth




:




true




.





2






.2.4.




切换图表



布局的实现



增加类样式为



title






容器



<


template


>



<


div


class


=





com





container





>



<


div


class


=





title





>



<


span


>


我是标题


</


span


>



<


span


class


=





iconfont


title





icon





>


&#


xe


6


eb


;


</


span


>



<


div


class


=





select





con





>



<


div


class


=





select





item





>






题选择


1



</


div


>



<


div


class


=





select





item





>






题选择


2



</


div


>



<


div


class


=





select





item





>






题选择


3



</


div


>



</


div


>



</


div


>



<


div


class


=





com





chart





ref


=





trend


_


ref





></


div


>



</


div


>



<


/


template


>






体文件的引入



将资料文件夹下的字体文件


夹中的



font



复制到


asset


目录下


,


然后在



main




.




js



中引入字体样式文











Trend




.




vue



中的


style


标签中增加一些样式



<


style


lang


=





less





scoped


>



.


title


{




position


:


absolute


;



left


:


20


px


;



top


:


20


px


;



z





index


:


10


;



color


:


white


;



.


title





icon


{




margin





left


:


10


px


;



cursor


:


pointer


;



}



.


select





item


{




cursor


:


pointer


;



}



}



<


/


style


>



数据动态渲染



使用计算属性



title



控制标题的内容和标题的可选择项



<


script


>



export


default


{




data


() {




return


{




chartInstance


:


null


,



allData


:


null


,



dataType


:





map





/


/


这项数据代表目前选择的数据类型


,


可选值有


map


seller


commodi


ty



}



}


,



computed


: {




selectTypes


()


{




if


(


!


this


.


allData


| | !


this


.


allData


.


type


)


{




return


[]



}


else


{




return


this


.


allData


.


type


.


filter


(


item


=>


{




return


item


.


key


!==


this


.


dataType



}


)



}



}


,



title


()


{




if


(


!


this


.


allData


)


{




return






}


else


{




return


this


.


allData


[


this


.


dataType


]


.


title



}



}



}


,



…..


.









三角控制显示隐藏



增加一项变量控制可选容器的显示与隐






export


default


{




data


() {




return


{




showChoice


:


false


//


控制可选面板的显示或者隐藏



}



}


,



使


用指令



v









if



和点击事件的监听



<


template


>



<


div


class


=





com





container





>



<


div


class


=





title





>



<


span


>


{

{


title


}}


</


span


>



<


span


class


=





iconfont


title





icon





@


click


=





showChoice


=



!


showChoice





>


&#


xe


6


eb


;


</


span


>



<


div


class


=





select





con





v





if


=





showChoice





>



<


div


class


=





select





item





v





for


=





item


in


selectTypes






:


key


=





item


.


key





>



{



{


item


.


text


}}



</


div


>



</


div


>



</


div


>



<


div


class


=





com





chart





ref


=





trend


_


ref





></


div


>



</


div


>



<


/


template


>



点击可选条目的控




<template>
<div class='com-container'>
<div class="title">
<span>{{ title }}</span>
<span class="iconfont title-icon" @click="showChoice =
!showChoice">&#xe6eb;</span>
<div class="select-con" v-if="showChoice">
<div class="select-item" v-for="item in selectTypes" :key="item.key" @click="handleSelect(item.key)">
{{ item.text }}
</div>
</div>
</div>
<div class='com-chart' ref='trend_ref'></div>
</div>
</template>
<script>
export default {
......
methods: {
handleSelect (key) {
this.dataType = key
this.updateChart()
this.showChoice = false
}
}
}
</script>







updateChart






,


之前写死的


map


变成



dataType




const


valueArrs


=


this


.


allData


[


this


.


dataType


]


.


data



const


seriesArr


=


valueArrs


.


map


((


item


,


index


)


=>


{




return


{




…..


.



stack


:


this


.


dataType



}



}


)





2






.2.5.




分辨率适配




分辨率适配主要就是在



screenAdapter



方法中进行


,


需要获取图表容器的宽度


,


计算出标题字体大小


,






字体的大小赋值给



titleFontSize




<


script


>



export


default


{




data


() {




return


{




titleFontSize


:


0



}



}


,



…..


.



screenAdapter


() {




this


.


titleFontSize


=


this


.


$


refs


.


trend


_


ref


.


offsetWidth


/


100


*


3.6


}










titleFontSize



从而设置给标题文字的大小和图例的大小



标题文字的大






增加计





属性



comStyle



并设置给对应的



div



,


如下


:



<!–


销量趋势图表





>



<


template


>



<


div


class


=





com





container





>



<


div


class


=





title





:


style


=





comStyle





>



<


span


>


{

{


title


}}


</


span


>



<


span


class


=





iconfont


title





icon





@


click


=





showChoice


= !


showChoice






:


style


=





comStyle





>


&#


xe


6


eb


;


</


span


>



…..


.



<


script


>



export


default


{




…..


.



computed


: {




…..


.



comStyle


() {




return


{




fontSize


:


this


.


titleFontSize


+





px






}



}



}


,






例的大小

screenAdapter () {
this.titleFontSize = this.$refs.trend_ref.offsetWidth / 100 * 3.6 const adapterOption = {
legend: {
itemWidth: this.titleFontSize,
itemHeight: this.titleFontSize,
itemGap: this.titleFontSize,
textStyle: {
fontSize: this.titleFontSize / 2
}
}
}
this.chartInstance.setOption(adapterOption)
this.chartInstance.resize()
},





2






.2.6.




细节调整




可选条目的背景色



<


style


lang


=





less





scoped


>



.


title


{




…..


.



.


select





con


{




background





color


:


#222733


;



}



.


select





item


{




cursor


:


pointer


;



}



}



<


/


style


>



增加标题左侧的小竖杆



<


template


>



<


div


class


=





com





container





>



<


div


class


=





title





:


style


=





comStyle





>



<


span


>


{

{‘





‘ +


title


}}


</


span


>



<


span


class


=





iconfont


title





icon





@


click


=





showChoice


= !


showChoice





:


style


=





comStyle





>


&#


xe


6


eb


;


</


span


>



<


div


class


=





select





con





v





if


=





showChoice





:


style


=





marginStyle





>



…..


.



<


script


>



export


default


{




…..


.



computed


: {




marginStyle


() {




return


{




marginLeft


:


this


.


titleFontSize


+





px






}



}



}


,





2






.3.




商家地图分布




最终的效果如下


:





2






.






3.1.




代码环境的准备




MapPage




.




vue




<!–






对于


/


mappage


这条路径而显示出来的



在这个组件中


,


通过子组件注册的方式


,


要显示出


Map.vue


这个组件









>



<


template


>



<


div


class


=





com





page





>



<


single





map


></


single





map


>



</


div


>



<


/


template


>



<


script


>



import


Map


from


‘@/


components


/


Map






export


default


{




data


() {




return


{}



}


,



methods


: {}


,



components


:


{







single





map





:


Map



}



}



<


/


script


>



<


style


lang


=





less





scoped


>



<


/


style


>




Map




.




vue


<!-- 商家分布图表 -->
<template>
  <div class='com-container' @dblclick="revertMap">
    <div class='com-chart' ref='map_ref'></div>
  </div>
</template>

<script>
import axios from 'axios'
import { getProvinceMapInfo } from '@/utils/map_utils'
export default {
  data () {
    return {
      chartInstance: null,
      allData: null,
      mapData: {} // 所获取的省份的地图矢量数据
    }
  },
  mounted () {
    this.initChart()
    this.getData()
    window.addEventListener('resize', this.screenAdapter)
    this.screenAdapter()
  },
  destroyed () {
    window.removeEventListener('resize', this.screenAdapter)
  },
  methods: {
    async initChart () {
      this.chartInstance = this.$echarts.init(this.$refs.map_ref, 'chalk')
      // 获取中国地图的矢量数据
      // http://localhost:8999/static/map/china.json
      // 由于我们现在获取的地图矢量数据并不是位于KOA2的后台, 所以咱们不能使用this.$http
      const ret = await axios.get('http://localhost:8999/static/map/china.json')
      this.$echarts.registerMap('china', ret.data)
      const initOption = {
        title: {
          text: '▎ 商家分布',
          left: 20,
          top: 20
        },
        geo: {
          type: 'map',
          map: 'china',
          top: '5%',
          bottom: '5%',
          itemStyle: {
            areaColor: '#2E72BF',
            borderColor: '#333'
          }
        },
        legend: {
          left: '5%',
          bottom: '5%',
          orient: 'vertical'
        }
      }
      this.chartInstance.setOption(initOption)
      this.chartInstance.on('click', async arg => {
        // arg.name 得到所点击的省份, 这个省份他是中文
        const provinceInfo = getProvinceMapInfo(arg.name)
        console.log(provinceInfo)
        // 需要获取这个省份的地图矢量数据
        // 判断当前所点击的这个省份的地图矢量数据在mapData中是否存在
        if (!this.mapData[provinceInfo.key]) {
          const ret = await axios.get('http://localhost:8999' + provinceInfo.path)
          this.mapData[provinceInfo.key] = ret.data
          this.$echarts.registerMap(provinceInfo.key, ret.data)
        }
        const changeOption = {
          geo: {
            map: provinceInfo.key
          }
        }
        this.chartInstance.setOption(changeOption)
      })
    },
    async getData () {
      // 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
      const { data: ret } = await this.$http.get('map')
      this.allData = ret
      console.log(this.allData)
      this.updateChart()
    },
    updateChart () {
      // 处理图表需要的数据
      // 图例的数据
      const legendArr = this.allData.map(item => {
        return item.name
      })
      const seriesArr = this.allData.map(item => {
        // return的这个对象就代表的是一个类别下的所有散点数据
        // 如果想在地图中显示散点的数据, 我们需要给散点的图表增加一个配置, coordinateSystem:geo
        return {
          type: 'effectScatter',
          rippleEffect: {
            scale: 5,
            brushType: 'stroke'
          },
          name: item.name,
          data: item.children,
          coordinateSystem: 'geo'
        }
      })
      const dataOption = {
        legend: {
          data: legendArr
        },
        series: seriesArr
      }
      this.chartInstance.setOption(dataOption)
    },
    screenAdapter () {
      const titleFontSize = this.$refs.map_ref.offsetWidth / 100 * 3.6
      const adapterOption = {
        title: {
          textStyle: {
            fontSize: titleFontSize
          }
        },
        legend: {
          itemWidth: titleFontSize / 2,
          itemHeight: titleFontSize / 2,
          itemGap: titleFontSize / 2,
          textStyle: {
            fontSize: titleFontSize / 2
          }
        }
      }
      this.chartInstance.setOption(adapterOption)
      this.chartInstance.resize()
    },
    // 回到中国地图
    revertMap () {
      const revertOption = {
        geo: {
          map: 'china'
        }
      }
      this.chartInstance.setOption(revertOption)
    }
  }
}
</script>

<style lang='less' scoped>
</style>




router




/




index




.




js




…..


.



import


MapPage


from


‘@/


views


/


MapPage






…..


.



const


routes


=


[



…..


.



{




path


:


‘/


mappage





,



component


:


MapPage



}



]



…..


.





2






.3.2.




显示地图




获取中国地图矢量数据



注册地图数据到



全局




echarts




对象







配置



geo




<


script


>



//


获取的是


Vue


环境之下的数据


,


而不是我们后台的数






import


axios


from





axios






export


default


{




…..


.



methods


: {




async


initChart


()


{




this


.


chartInstance


=


this


.


$


echarts


.


init


(


this


.


$


refs


.


map


_


ref


)


const


{


data


:


mapData


}


=


await



axios


.


get


(





http


:


//127.0.0.1:8999/


static


/


map


/


china


.


json





)



this


.


$


echarts


.


registerMap


(





china





,


mapData


)



const


initOption


=


{




geo


:


{




type


:





map





,



map


:





china






}



}



this


.


chartInstance


.


setOption


(


initOption


)



}


,





2






.3.3.




显示散点图




获取散点数据



async


getScatterData


() {




//


获取服务器的数据


,





this


.


allData


进行赋


值之后


,


调用


updateChart


方法更新图表



const


{



data


:


ret


}


=


await


this


.


$


http


.


get


(





map





)



this


.


allData


=


ret



this


.


updateChart


()



}



处理数据并且更新图表

updateChart () {
// 处理图表需要的数据
// 图例数据
const legendData = this.allData.map(item => {
return item.name
})
// 散点数据
const seriesArr = this.allData.map(item => {
return {
type: 'effectScatter',
coordinateSystem: 'geo',
name: item.name,
data: item.children
}
})
const dataOption = {
legend: {
data: legendData
},
series: seriesArr
}
this.chartInstance.setOption(dataOption)
},





2






.3.4.





UI



效果的调整




主题的使用



methods


: {




async


initChart


()


{




this


.


chartInstance


=


this


.


$


echarts


.


init


(


this


.


$


refs


.


map


_


ref


,





chalk





)






题显示



const


initOption


=


{




title


:


{




text


:





▎ 商家分布





,



left


:


20


,



top


:


20



}


,



地图位置和颜






const


initOption


=


{




…..


.



geo


:


{




type


:





map





,



map


:





china





,



top


:


‘5%’


,



bottom


:


‘5%’


,



itemStyle


: {




areaColor


:


‘#2


E


72


BF





,



borderColor


:


‘#333’



}



}



}



图例控制



const


initOption


=


{




…..


.



legend


:


{




left


:


‘5%’


,



bottom


:


‘5%’


,



orient


:





vertical






}



}



涟漪效果



updateChart


() {




…..


.



const


seriesArr


=


this


.


allData


.


map


(


item


=>


{




return


{




type


:





effectScatter





,



rippleEffect


:


{




scale


:


5


,



brushType


:





stroke






}


,



…..


.



}



}


)





2






.3.5.




分辨率适配




计算



titleFontSize




screenAdapter


() {




const


titleFontSize


=


this


.


$


refs


.


map


_


ref


.


offsetWidth


/


100


*


3.6


const


adapterOption


=


{




}



this


.


chartInstance


.


setOption


(


adapterOption


)



this


.


chartInstance


.


resize


(


)



}







titleFontSize



设置给图表的某些区域



标题的大小



图例大小

screenAdapter () {
const titleFontSize = this.$refs.map_ref.offsetWidth / 100 * 3.6 const adapterOption = {
title: {
textStyle: {
fontSize: titleFontSize
}
},
legend: {
itemWidth: titleFontSize / 2,
itemHeight: titleFontSize / 2,
itemGap: titleFontSize / 2,
textStyle: {
fontSize: titleFontSize / 2 }
}
}
this.chartInstance.setOption(adapterOption) this.chartInstance.resize()





2






.3.6.




地图点击事件




响应





表的点击事件


,


并获取点击项相关的数据



async


initChart


()


{




…..


.



this


.


chartInstance


.


on


(





click





,


arg


=>


{




//


arg


.


name


就是


所点击的省份名称


,


是中文



}


)






资料中的



map




_




utils




.




js



复制到



src




/




utils




/



目录之下



得到地图所点击项的拼音和地图矢量数据的路






<


script


>



//


获取的是


Vue


环境之下的数据


,


而不是我们后台的数






import


axios


from





axios






import


{


getProvinceMapInfo


}


from


‘@/


utils


/


map


_


utils






export


default


{




…..


.



methods


: {




async


initChart


()


{




…..


.



this


.


chartInstance


.


setOption


(


initOption


)



this


.


chartInstance


.


on


(





click





,


async


arg


=>


{




//


arg


.


name


就是


所点击的省份名称


,


是中文



const


provinceInfo


=


getProvinceMapInfo


(


arg


.


name


)



const


{


data


:


ret


}


=


await


axios


.


get


(





http


://127.0.0.1:8999′


+


provinceInfo


.


path


)



this


.


$


echarts


.


registerMap


(


provinceInfo


.


key


,


ret


)



this


.


chartInstance


.


setOption


({




geo


:


{




map


:


provinceInfo


.


key



}



}


)



}


)



this


.


getScatterData


()



}



}



}



<


/


script


>



回到


中国地图

<template>
<div class='com-container' @dblclick="revertMap">
<div class='com-chart' ref='map_ref'></div>
</div>
</template>
<script>
export default {
......
methods: {
......
revertMap () {
this.chartInstance.setOption({ geo: {
map: 'china'
}
})
}
}
}
</script>





2






.4.




地区销量排行




最终的效果如下


:





2






.






4.1.




代码环境的准备




RankPage




.




vue


<!--
针对于 /rankpage 这条路径而显示出来的
在这个组件中 , 通过子组件注册的方式 , 要显示出Rank.vue这个组件 -->
<template>
<div class="com-page">
<rank></rank>
</div>
</template>

<script>
import Rank from '@/components/Rank'
export default {
data () {
return {}
},
methods: {},
components: {
rank: Rank
}
}
</script>

<style lang="less" scoped>
</style>




Rank




.




vue


<!-- 地区销售排行 -->
<template>
  <div class='com-container'>
    <div class='com-chart' ref='rank_ref'></div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      chartInstance: null,
      allData: null,
      startValue: 0, // 区域缩放的起点值
      endValue: 9, // 区域缩放的终点值
      timerId: null // 定时器的标识
    }
  },
  mounted () {
    this.initChart()
    this.getData()
    window.addEventListener('resize', this.screenAdapter)
    this.screenAdapter()
  },
  destroyed () {
    window.removeEventListener('resize', this.screenAdapter)
    clearInterval(this.timerId)
  },
  methods: {
    initChart () {
      this.chartInstance = this.$echarts.init(this.$refs.rank_ref, 'chalk')
      const initOption = {
        title: {
          text: '▎ 地区销售排行',
          left: 20,
          top: 20
        },
        grid: {
          top: '40%',
          left: '5%',
          right: '5%',
          bottom: '5%',
          containLabel: true
        },
        tooltip: {
          show: true
        },
        xAxis: {
          type: 'category'
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            type: 'bar'
          }
        ]
      }
      this.chartInstance.setOption(initOption)
      this.chartInstance.on('mouseover', () => {
        clearInterval(this.timerId)
      })
      this.chartInstance.on('mouseout', () => {
        this.startInterval()
      })
    },
    async getData () {
      // 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
      const { data: ret } = await this.$http.get('rank')
      this.allData = ret
      // 对allData里面的每一个元素进行排序, 从大到小进行
      this.allData.sort((a, b) => {
        return b.value - a.value
      })
      console.log(this.allData)
      this.updateChart()
      this.startInterval()
    },
    updateChart () {
      const colorArr = [
        ['#0BA82C', '#4FF778'],
        ['#2E72BF', '#23E5E5'],
        ['#5052EE', '#AB6EE5']
      ]
      // 处理图表需要的数据
      // 所有省份所形成的数组
      const provinceArr = this.allData.map(item => {
        return item.name
      })
      // 所有省份对应的销售金额
      const valueArr = this.allData.map(item => {
        return item.value
      })
      const dataOption = {
        xAxis: {
          data: provinceArr
        },
        dataZoom: {
          show: false,
          startValue: this.startValue,
          endValue: this.endValue
        },
        series: [
          {
            data: valueArr,
            itemStyle: {
              color: arg => {
                let targetColorArr = null
                if (arg.value > 300) {
                  targetColorArr = colorArr[0]
                } else if (arg.value > 200) {
                  targetColorArr = colorArr[1]
                } else {
                  targetColorArr = colorArr[2]
                }
                return new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
                  {
                    offset: 0,
                    color: targetColorArr[0]
                  },
                  {
                    offset: 1,
                    color: targetColorArr[1]
                  }
                ])
              }
            }
          }
        ]
      }
      this.chartInstance.setOption(dataOption)
    },
    screenAdapter () {
      const titleFontSize = this.$refs.rank_ref.offsetWidth / 100 * 3.6
      const adapterOption = {
        title: {
          textStyle: {
            fontSize: titleFontSize
          }
        },
        series: [
          {
            barWidth: titleFontSize,
            itemStyle: {
              barBorderRadius: [titleFontSize / 2, titleFontSize / 2, 0, 0]
            }
          }
        ]
      }
      this.chartInstance.setOption(adapterOption)
      this.chartInstance.resize()
    },
    startInterval () {
      if (this.timerId) {
        clearInterval(this.timerId)
      }
      this.timerId = setInterval(() => {
        this.startValue++
        this.endValue++
        if (this.endValue > this.allData.length - 1) {
          this.startValue = 0
          this.endValue = 9
        }
        this.updateChart()
      }, 2000)
    }
  }
}
</script>

<style lang='less' scoped>
</style>




router




/




index




.




js




…..


.



import


RankPage


from


‘@/


views


/


RankPage






…..


.



const


routes


=


[



…..


.



{




path


:


‘/


rankpage





,



component


:


RankPage



}



]



…..


.





2.






4.2.




图表基本功能的实现




数据的获取



async


getData


()


{




//


获取服务器的数据


,





this


.


allData


进行赋


值之后


,


调用


updateChart


方法更新图表



const


{


data


:


ret


}


=


await


this


.


$


http


.


get


(





rank





)



this


.


allData


=


ret



//


对数据进行排序


,


从大到小排序



this


.


allData


.


sort


((


a


,


b


)


=>


{




return


b


.


value





a


.


value



}


)



this


.


updateChart


()



}


,



数据的处理

updateChart () {
// 处理图表需要的数据
const provinceArr = this.allData.map(item => {
return item.name
})
const valueArr = this.allData.map(item => {
return item.value
})
const dataOption = {
xAxis: {
data: provinceArr
},
series: [
{
data: valueArr
}
]
}
this.chartInstance.setOption(dataOption)
},



初始化配置



initChart


() {




this


.


chartInstance


=


this


.


$


echarts


.


init


(


this


.


$


refs


.


rank


_


ref


)


const


initOption


=


{




xAxis


:


{




type


:





category






}


,



yAxis


:


{




type


:





value






}


,



series


:


[



{




type


:





bar






}



]



}



this


.


chartInstance


.


setOption


(


initOption


)



}





2






.4.3.





UI



效果调整




主题的使用



initChart


() {




this


.


chartInstance


=


this


.


$


echarts


.


init


(


this


.


$


refs


.


rank


_


ref


,





chalk





)






题的设置



initChart


() {




this


.


chartInstance


=


this


.


$


echarts


.


init


(


this


.


$


refs


.


rank


_


ref


,





chalk





)


const


initOption


=


{




title


:


{




text


:





▎ 地区销售排行





,



left


:


20


,



top


:


20



}



颜色的设置



不同柱显示不同颜色



渐变的控制

updateChart () {
// 处理图表需要的数据
const colorArr = [
['#0BA82C', '#4FF778'],
['#2E72BF', '#23E5E5'],
['#5052EE', '#AB6EE5']
]
......
const dataOption = {
xAxis: {
data: provinceArr
},
series: [
{
data: valueArr,
itemStyle: {
color: arg => {
let targetColorArr = colorArr[0]
if (arg.vaule >= 300) {
targetColorArr = colorArr[0]
} else if (arg.value >= 200) {
targetColorArr = colorArr[1]
} else {
targetColorArr = colorArr[2]
}
return new this.$echarts.graphic.LinearGradient(0,
1, 0, 0, [
{
offset: 0,
color: targetColorArr[0]
},
{
offset: 1,
color: targetColorArr[1] }
])
}
}
}
]
}
this.chartInstance.setOption(dataOption)
},





2






.






4.4.




平移动画的实现






移动画可以使用



dataZoom



中的



startValue







endValue



来实现



定义数据



<


script


>



export


default


{




data


() {




return


{




chartInstance


:


null


,



allData


:


null


,



startValue


:


0


,



endValue


:


9



}



}


,







startValue







endValue



应用在



dataZoom






,


并隐藏



dataZoom



的显示



updateChart


() {




…..


.



const


dataOption


=


{




xAxis


:


{




data


:


provinceArr



}


,



dataZoom


: {




show


:


false


,



startValue


:


this


.


startValue


,



endValue


:


this


.


endValue



}


,



启动和停止定时






增加



timerId



的变量


,


并且增加一个方法



startInterval



,


来控制



startValue







endValue



的值

<script>
export default {
data () {
return {
chartInstance: null,
allData: null,
startValue: 0,
endValue: 9,
timerId: null
}
},
......
methods: {
......
startInterval () {
if (this.timerId) {
clearInterval(this.timerId)
}
this.timerId = setInterval(() => {
this.startValue++
this.endValue++
if (this.endValue > this.allData.length - 1) {
this.startValue = 0
this.endValue = 9
}
this.updateChart()
}, 3000)
}
}
}



获取数据之后启






async


getData


()


{




…..


.



this


.


updateChart


()



this


.


startInterval


()



}


,






件销毁停止



destroyed


() {




window


.


removeEventListener


(





resize





,


this


.


screenAdapter


)


clearInterval


(


this


.


timerId


)



}


,



鼠标移入停






methods


: {




initChart


() {




…..


.



this


.


chartInstance


.


setOption


(


initOption


)



this


.


chartInstance


.


on


(





mouseover





, ()


=>


{




clearInterval


(


this


.


timerId


)



}


)



鼠标离开启






methods


: {




initChart


() {




…..


.



this


.


chartInstance


.


on


(





mouseout





, ()


=>


{




this


.


startInterval


()



}


)



}


,





2






.4.5.




分辨率适配




计算



titleFontSize








titleFontSize



设置给图表的某些区域



screenAdapter


() {




const


titleFontSize


=


this


.


$


refs


.


rank


_


ref


.


offsetWidth


/


100


*


3.6


const


adapterOption


=


{




title


:


{




textStyle


: {




fontSize


:


titleFontSize



}



}


,



series


:


[



{




barWidth


:


titleFontSize


,



itemStyle


: {




barBorderRadius


:


[


0.5


*


titleFontSize


,


0.5


*


titleFontSize


,


0


,


0


]



}



}



]



}



this


.


chartInstance


.


setOption


(


adapterOption


)



this


.


chartInstance


.


resize


(


)



}


,





2






.5.




热销商品占比




最终的效果如下


:






2






.






5.1.




代码环境的准备





HotPage




.




vue


<!--
针对于 /hotpage 这条路径而显示出来的
在这个组件中 , 通过子组件注册的方式 , 要显示出Hot.vue这个组件
-->
<template>
<div class="com-page">
<hot></hot>
</div>
</template>

<script>
import Hot from '@/components/Hot'
export default {
data () {
return {}
},
methods: {},
components: {
hot: Hot
}
}
</script>

<style lang="less" scoped>
</style>




Hot




.




vue


<!-- 热销商品图表 -->
<template>
<div class='com-container'>
<div class='com-chart' ref='hot_ref'></div>
</div>
</template>

<script>
export default {
data () {
return {
chartInstance: null,
allData: null
}
},
mounted () {
this.initChart()
this.getData()
window.addEventListener('resize', this.screenAdapter)
this.screenAdapter()
},
destroyed () {
window.removeEventListener('resize', this.screenAdapter)
},
methods: {
initChart () {
this.chartInstance = this.$echarts.init(this.$refs.hot_ref)
const initOption = {}
this.chartInstance.setOption(initOption)
},
async getData () {
// 获取服务器的数据 , 对this.allData进行赋值之后 , 调用updateChart方法更新图表
this.updateChart()
},
updateChart () {
// 处理图表需要的数据
const dataOption = {}
this.chartInstance.setOption(dataOption)
},
screenAdapter () {
const adapterOption = {}
this.chartInstance.setOption(adapterOption)
this.chartInstance.resize()
}
}
}
</script>

<style lang='less' scoped>
</style>




router




/




index




.




js




…..


.



import


HotPage


from


‘@/


views


/


HotPage






…..


.



const


routes


=


[



…..


.



{




path


:


‘/


hotpage





,



component


:


HotPage



}



]



…..


.





2.






5.2.




图表基本功能的实现




Hot.vue

<!-- 热销商品图表 -->
<template>
  <div class='com-container'>
    <div class='com-chart' ref='hot_ref'></div>
    <span class="iconfont arr-left" @click="toLeft" :style="comStyle">&#xe6ef;</span>
    <span class="iconfont arr-right" @click="toRight" :style="comStyle">&#xe6ed;</span>
    <span class="cat-name" :style="comStyle">{{ catName }}</span>
  </div>
</template>

<script>
export default {
  data () {
    return {
      chartInstance: null,
      allData: null,
      currentIndex: 0, // 当前所展示出的一级分类数据
      titleFontSize: 0
    }
  },
  computed: {
    catName () {
      if (!this.allData) {
        return ''
      } else {
        return this.allData[this.currentIndex].name
      }
    },
    comStyle () {
      return {
        fontSize: this.titleFontSize + 'px'
      }
    }
  },
  mounted () {
    this.initChart()
    this.getData()
    window.addEventListener('resize', this.screenAdapter)
    this.screenAdapter()
  },
  destroyed () {
    window.removeEventListener('resize', this.screenAdapter)
  },
  methods: {
    initChart () {
      this.chartInstance = this.$echarts.init(this.$refs.hot_ref, 'chalk')
      const initOption = {
        title: {
          text: '▎ 热销商品的占比',
          left: 20,
          top: 20
        },
        legend: {
          top: '15%',
          icon: 'circle'
        },
        tooltip: {
          show: true,
          formatter: arg => {
            // console.log(arg)
            const thirdCategory = arg.data.children
            // 计算出所有三级分类的数值总和
            let total = 0
            thirdCategory.forEach(item => {
              total += item.value
            })
            let retStr = ''
            thirdCategory.forEach(item => {
              retStr += `
              ${item.name}:${parseInt(item.value / total * 100) + '%'}
              <br/>
              `
            })
            return retStr
          }
        },
        series: [
          {
            type: 'pie',
            label: {
              show: false
            },
            emphasis: {
              label: {
                show: true
              },
              labelLine: {
                show: false
              }
            }
          }
        ]
      }
      this.chartInstance.setOption(initOption)
    },
    async getData () {
      // 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
      const { data: ret } = await this.$http.get('hotproduct')
      this.allData = ret
      console.log(this.allData)
      this.updateChart()
    },
    updateChart () {
      // 处理图表需要的数据
      const legendData = this.allData[this.currentIndex].children.map(item => {
        return item.name
      })
      const seriesData = this.allData[this.currentIndex].children.map(item => {
        return {
          name: item.name,
          value: item.value,
          children: item.children // 新增加children的原因是为了在tooltip中的formatter的回调函数中,来拿到这个二级分类下的三级分类数据
        }
      })
      const dataOption = {
        legend: {
          data: legendData
        },
        series: [
          {
            data: seriesData
          }
        ]
      }
      this.chartInstance.setOption(dataOption)
    },
    screenAdapter () {
      this.titleFontSize = this.$refs.hot_ref.offsetWidth / 100 * 3.6
      const adapterOption = {
        title: {
          textStyle: {
            fontSize: this.titleFontSize
          }
        },
        legend: {
          itemWidth: this.titleFontSize / 2,
          itemHeight: this.titleFontSize / 2,
          itemGap: this.titleFontSize / 2,
          textStyle: {
            fontSize: this.titleFontSize / 2
          }
        },
        series: [
          {
            radius: this.titleFontSize * 4.5,
            center: ['50%', '60%']
          }
        ]
      }
      this.chartInstance.setOption(adapterOption)
      this.chartInstance.resize()
    },
    toLeft () {
      this.currentIndex--
      if (this.currentIndex < 0) {
        this.currentIndex = this.allData.length - 1
      }
      this.updateChart()
    },
    toRight () {
      this.currentIndex++
      if (this.currentIndex > this.allData.length - 1) {
        this.currentIndex = 0
      }
      this.updateChart()
    }
  }
}
</script>

<style lang='less' scoped>
.arr-left {
  position:absolute;
  left: 10%;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
  color: white;
}
.arr-right {
  position:absolute;
  right: 10%;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
  color: white;
}
.cat-name {
  position:absolute;
  left: 80%;
  bottom: 20px;
  color: white;
}
</style>






据的处理



增加



currentIndex



索引代表当前显示的数据索引


,


后期通过左右箭头改变



currentIndex



的值

<script>
export default {
data () {
return {
chartInstance: null,
allData: null,
currentIndex: 0
}
},
......
updateChart () {
// 处理图表需要的数据
// 饼图数据
const seriesData = this.allData[this.currentIndex].children.map(item
=> {
return {
value: item.value,
name: item.name
}
})
// 图例数据
const legendData = this.allData[this.currentIndex].children.map(item
=> {
return item.name
})
const dataOption = {
legend: {
data: legendData
},
series: [
{
data: seriesData
}
]
}
this.chartInstance.setOption(dataOption)
},






始化配置



methods


: {




initChart


() {




this


.


chartInstance


=


this


.


$


echarts


.


init


(


this


.


$


refs


.


hot


_


ref


)


const


initOption


=


{




title


:


{




text


:





▎ 热销商品销售金额占比统计





,



left


:


20


,



top


:


20



}


,



series


:


[



{




type


:





pie






}



]



}



this


.


chartInstance


.


setOption


(


initOption


)



}


,

2.5.3切换数据的实现










<!–


热销商品图表





>



<


template


>



<


div


class


=





com





container





>



<


div


class


=





com





chart





ref


=





hot


_


ref





></


div


>



<


span


class


=





iconfont


arr


_


left





>


&#


xe


6


ef


;


</


span


>



<


span


class


=





iconfont


arr


_


right





>


&#


xe


6


ed


;


</


span


>



</


div


>



<


/


template


>










<


style


lang


=





less





scoped


>



.


arr


_


left


{




position


:


absolute


;



left


:


10%


;



top


:


50%


;



transform


:


translateY


(


-50%


);



cursor


:


pointer


;



}



.


arr


_


right


{




position


:


absolute


;



right


:


10


%


;



top


:


50%


;



transform


:


translateY


(


-50%


);



cursor


:


pointer


;



}



<


/


style


>






击事件



<


span


class


=





iconfont


arr


_


left





@


click


=





toLeft





>&


#


xe


6


ef


;</


span


>



<


span


class


=





iconfont


arr


_


right





@


click


=





toRight





>&


#


xe


6


ed


;</


span


>


methods


: {




toLeft


() {




this


.


currentIndex






if


(


this


.


currentIndex


<


0


) {




this


.


currentIndex


=


this


.


allData


.


length





1



}



this


.


updateChart


()



}


,



toRight


() {




this


.


currentIndex


++



if


(


this


.


currentIndex


>


this


.


allData


.


length





1


) {




this


.


currentIndex


=


0



}



this


.


updateChart


()



}



}



分类名称的显






布局和样式

<template>
<div class='com-container'>
......
<span class="cat_name">分类名称</span>
</div>
</template>

<style lang='less' scoped>
.cat_name {
position: absolute;
left: 80%;
bottom: 20px;
font-weight: bold;
}
</style>



名称的改变






加计算属性



catTitle




<


script


>



export


default


{




…..


.



computed


: {




catTitle


() {




if


(


!


this


.


allData


)


{




return






}



return


this


.


allData


[


this


.


currentIndex


].


name



}



}


,









中使用计算属性



<!–


热销商品图表





>



<


template


>



<


div


class


=





com





container





>



…..


.



<


span


class


=





cat


_


name





>


{

{


catTitle


}}


</


span


>



</


div


>



<


/


template


>





2






.5.4.





UI



效果的调整



主题的使用



methods


: {




initChart


() {




this


.


chartInstance


=


this


.


$


echarts


.


init


(


this


.


$


refs


.


hot


_


ref


,





chalk





)



分类名称和箭头的颜色

<style lang='less' scoped>
.arr_left {
......
color: white;
}
.arr_right {
......
color: white;
}
.cat_name {
.....
color: white;
}
</style>



默认隐藏文字


,


高亮显示文字



methods


: {




initChart


() {




this


.


chartInstance


=


this


.


$


echarts


.


init


(


this


.


$


refs


.


hot


_


ref


,





chalk





)


const


initOption


=


{




…..


.



series


:


[



{




type


:





pie





,



label


:


{


//


隐藏文字



show


:


false



}


,



labelLine


:


{


//


隐藏线



show


:


false



}


,



emphasis


:


{




label


:


{


//


高亮显示文字



show


:


true



}



}



}



]



}



this


.


chartInstance


.


setOption


(


initOption


)



}


,



图例形状和位置



methods


: {




initChart


() {




this


.


chartInstance


=


this


.


$


echarts


.


init


(


this


.


$


refs


.


hot


_


ref


,





chalk





)


const


initOption


=


{




legend


:


{




top


:


‘5%’


,



icon


:





circle






}


,






具提示



当鼠





移入某个扇区的时候


,


需要将该二级分类之下的三级分类数据进行展示










series



下饼图每一个扇区的数据



updateChart


() {




/


/


处理图表需要的数据



const


seriesData


=


this


.


allData


[


this


.


currentIndex


].


children


.


map


(


item


=


>


{




return


{




…..


.



children


:


item


.


children



}



}


)



显示



tooltip



,


并控制显


示内容



methods


: {




initChart


() {




this


.


chartInstance


=


this


.


$


echarts


.


init


(


this


.


$


refs


.


hot


_


ref


,





chalk





)


const


initOption


=


{




…..


.



tooltip


: {




trigger


:





item





,



formatter


:


function


(


params


)


{




let


tipArray


=


[


]



params


.


data


.


children


.


forEach


(


function


(


item


)


{




let


childStr


=


`



$


{



item


.


name


}&


nbsp


;&


nbsp


;&


nbsp


;



$


{



parseInt


((


item


.


value


/


params


.


value


)


*


100


)


+


‘%’


}


`



tipArray


.


push


(


childStr


)



}


)



return


tipArray


.


join


(


‘<


br


/>’


)



}



}





2






.5.5.




分辨率适配




分辨率适配主要就是在



screenAdapter



方法中进行


,


需要获取图表容器的宽度


,


计算出标题字体大小


,






字体的大小赋值给



titleFontSize




<


script


>



export


default


{




data


() {




return


{




titleFontSize


:


0



}



}


,



…..


.



screenAdapter


() {




this


.


titleFontSize


=


this


.


$


refs


.


hot


_


ref


.


offsetWidth


/


100


*


3.6


}





2






.6.




库存销量分析




最终的效果如下


:





2






.






6.1.




代码环境的准备




StockPage




.




vue


<!--
针对于 /stockpage 这条路径而显示出来的
在这个组件中 , 通过子组件注册的方式 , 要显示出Stock.vue这个组件
-->
<template>
<div class="com-page">
<stock></stock>
</div>
</template>
<script>
import Stock from '@/components/Stock' export default {
data () {
return {}
},
methods: {},
components: {
stock: Stock
}
}
</script>

<style lang="less" scoped>
</style>




Stock




.




vue


<!-- 库存销量分析 -->
<template>
  <div class='com-container'>
    <div class='com-chart' ref='stock_ref'></div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      chartInstance: null,
      allData: null,
      currentIndex: 0, // 当前显示的数据
      timerId: null // 定时器的标识
    }
  },
  mounted () {
    this.initChart()
    this.getData()
    window.addEventListener('resize', this.screenAdapter)
    this.screenAdapter()
  },
  destroyed () {
    window.removeEventListener('resize', this.screenAdapter)
    clearInterval(this.timerId)
  },
  methods: {
    initChart () {
      this.chartInstance = this.$echarts.init(this.$refs.stock_ref, 'chalk')
      const initOption = {
        title: {
          text: '▎库存和销量分析',
          left: 20,
          top: 20
        }
      }
      this.chartInstance.setOption(initOption)
      this.chartInstance.on('mouseover', () => {
        clearInterval(this.timerId)
      })
      this.chartInstance.on('mouseout', () => {
        this.startInterval()
      })
    },
    async getData () {
      // 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
      const { data: ret } = await this.$http.get('stock')
      this.allData = ret
      console.log(this.allData)
      this.updateChart()
      this.startInterval()
    },
    updateChart () {
      const centerArr = [
        ['18%', '40%'],
        ['50%', '40%'],
        ['82%', '40%'],
        ['34%', '75%'],
        ['66%', '75%']
      ]
      const colorArr = [
        ['#4FF778', '#0BA82C'],
        ['#E5DD45', '#E8B11C'],
        ['#E8821C', '#E55445'],
        ['#5052EE', '#AB6EE5'],
        ['#23E5E5', '#2E72BF']
      ]
      // 处理图表需要的数据
      const start = this.currentIndex * 5
      const end = (this.currentIndex + 1) * 5
      const showData = this.allData.slice(start, end)
      const seriesArr = showData.map((item, index) => {
        return {
          type: 'pie',
          radius: [110, 100],
          center: centerArr[index],
          hoverAnimation: false, // 关闭鼠标移入到饼图时的动画效果
          labelLine: {
            show: false // 隐藏指示线
          },
          label: {
            position: 'center',
            color: colorArr[index][0]
          },
          data: [
            {
              name: item.name + '\n' + item.sales,
              value: item.sales,
              itemStyle: {
                color: new this.$echarts.graphic.LinearGradient(0, 1, 0, 0, [
                  {
                    offset: 0,
                    color: colorArr[index][0]
                  },
                  {
                    offset: 1,
                    color: colorArr[index][1]
                  }
                ])
              }
            },
            {
              value: item.stock,
              itemStyle: {
                color: '#333843'
              }
            }
          ]
        }
      })
      const dataOption = {
        series: seriesArr
      }
      this.chartInstance.setOption(dataOption)
    },
    screenAdapter () {
      const titleFontSize = this.$refs.stock_ref.offsetWidth / 100 * 3.6
      const innerRadius = titleFontSize * 2
      const outterRadius = innerRadius * 1.125
      const adapterOption = {
        title: {
          textStyle: {
            fontSize: titleFontSize
          }
        },
        series: [
          {
            type: 'pie',
            radius: [outterRadius, innerRadius],
            label: {
              fontSize: titleFontSize / 2
            }
          },
          {
            type: 'pie',
            radius: [outterRadius, innerRadius],
            label: {
              fontSize: titleFontSize / 2
            }
          },
          {
            type: 'pie',
            radius: [outterRadius, innerRadius],
            label: {
              fontSize: titleFontSize / 2
            }
          },
          {
            type: 'pie',
            radius: [outterRadius, innerRadius],
            label: {
              fontSize: titleFontSize / 2
            }
          },
          {
            type: 'pie',
            radius: [outterRadius, innerRadius],
            label: {
              fontSize: titleFontSize / 2
            }
          }
        ]
      }
      this.chartInstance.setOption(adapterOption)
      this.chartInstance.resize()
    },
    startInterval () {
      if (this.timerId) {
        clearInterval(this.timerId)
      }
      this.timerId = setInterval(() => {
        this.currentIndex++
        if (this.currentIndex > 1) {
          this.currentIndex = 0
        }
        this.updateChart() // 在更改完currentIndex之后 , 需要更新界面
      }, 5000)
    }
  }
}
</script>

<style lang='less' scoped>
</style>



版权声明:本文为qq_36384657原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。