当前位置:   article > 正文

Vue2+Echarts+koa2+websocket电商平台数据可视化实时检测系统(一)_vue 调佣echarts 和 websocket 时需要分开写js文件吗

vue 调佣echarts 和 websocket 时需要分开写js文件吗

最终的效果如图所示最终效果涉及6个图表, 5种图表类型,它们分别是折线图,柱状图,地图,散点图,饼图.。每个图表的数据都是从后端推送到前端来的, 不过在项目的初期,我们会先使用 ajax 由前端主动获取数 , 后续会使用 WebSocket进行改造。整个项目的架构基于 Vue , 所以我们需要创建 Vue项目, 然后在 Vue项目中开发各个图表组件

 1.前端项目的准备

1.1. vue-c1i脚手架创建项

1.1.1 脚手架环境的安装

  在全局环境中安装 vue-cli脚手

npm install -g @vue/cli

1.1.2. 工程的创建

  使用命令行执

vue create 项目名

体的配置项如下:

  手动选择特性

 集成 Router , Vuex , CSS Pre-processors

   是否选用历史模式的路由

 选择 Less CSS 的预处理器

 选择 ESLint 的配置

  置选择完之后, 就开始创建项目了, 这个过程需要一些时间:

 当项目就创建完成了

  1. cd vision
  2. 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({

routes

})

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 增加宽高样式

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

  1. html,
  2. body,
  3. #app {
  4. width: 100%;
  5. height: 100%;
  6. padding: 0;
  7. margin: 0;
  8. overflow: hidden;
  9. }
  10. .com-page {
  11. width: 100%;
  12. height: 100%;
  13. overflow: hidden;
  14. }
  15. .com-container {
  16. width: 100%;
  17. height: 100%;
  18. overflow: hidden;
  19. }
  20. .com-chart {
  21. width: 100%;
  22. height: 100%;
  23. overflow: hidden;
  24. }

 main.js 中引入样式

import './assets/css/global.less'

打开浏, 输入 http://127.0.0.1:8999/#/sellerpage Seller组件是否能够显示

2.1.2.图表 Seller .vue 基本功能的实

   1.mounted生命周期中初始化 echartsInstance对象

   2.mounted中获取服务器的数据

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

  1. <script>
  2. export default {
  3. data () {
  4. return {
  5. chartInstance: null, // echarts实例对象
  6. allData: [] // 服务器获取的所有数据
  7. }
  8. },
  9. mounted () {
  10. // 由于初始化echarts实例对象需要使用到dom元素 ,因此必须要放到mounted中 , 而不是created
  11. this.initChart()
  12. this.getData()
  13. },
  14. methods: {
  15. initChart () {
  16. this.chartInstance = this.$echarts.init(this.$refs.seller_ref) // 初始化 echarts实例对象
  17. },
  18. async getData () {
  19. const { data: res } = await this.$http.get('seller') // 获取数据 this.allData = res
  20. // 对allData进行从大到小的排序
  21. this.allData.sort((a, b) => {
  22. return a.value - b.value
  23. })
  24. this.updateChart()
  25. },
  26. updateChart () {
  27. // 处理数据并且更新界面图表
  28. const sellerNames = this.allData.map((item) => {
  29. return item.name
  30. })
  31. const sellerValues = this.allData.map((item) => {
  32. return item.value
  33. })
  34. const option = {
  35. xAxis: {
  36. type: 'value'
  37. },
  38. yAxis: {
  39. type: 'category',
  40. data: sellerNames
  41. },
  42. series: [
  43. {
  44. type: 'bar',
  45. data: sellerValues
  46. }
  47. ]
  48. }
  49. this.chartInstance.setOption(option)
  50. }
  51. }
  52. }
  53. </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: 20px;

}

图表样式的配置

  标题的位置和颜色

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: '#2D3443'

}

}

},

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 代表的是从左往右的方向

  1. const initOption = {
  2. series: [
  3. {
  4. ......
  5. itemStyle: {
  6. barBorderRadius: [0, 33, 33, 0],
  7. color: new this.$echarts.graphic.LinearGradient(0, 0, 1,
  8. 0, [
  9. {
  10. offset: 0,
  11. color: '#5052EE'
  12. },
  13. {
  14. offset: 1,
  15. color: '#AB6EE5'
  16. }
  17. ])
  18. }
  19. }
  20. ]

2.1.5.分辨率适配

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

mounted 时候监听

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

  1. <!-- 商家销量统计的横向柱状图 -->
  2. <template>
  3. <div class="com-container">
  4. <div class="com-chart" ref="seller_ref"></div>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. data () {
  10. return {
  11. chartInstance: null,
  12. allData: null, // 服务器返回的数据
  13. currentPage: 1, // 当前显示的页数
  14. totalPage: 0, // 一共有多少页
  15. timerId: null // 定时器的标识
  16. }
  17. },
  18. mounted () {
  19. this.initChart()
  20. this.getData()
  21. window.addEventListener('resize', this.screenAdapter)
  22. // 在页面加载完成的时候, 主动进行屏幕的适配
  23. this.screenAdapter()
  24. },
  25. destroyed () {
  26. clearInterval(this.timerId)
  27. // 在组件销毁的时候, 需要将监听器取消掉
  28. window.removeEventListener('resize', this.screenAdapter)
  29. },
  30. methods: {
  31. // 初始化echartInstance对象
  32. initChart () {
  33. this.chartInstance = this.$echarts.init(this.$refs.seller_ref, 'chalk')
  34. // 对图表初始化配置的控制
  35. const initOption = {
  36. title: {
  37. text: '▎商家销售统计',
  38. left: 20,
  39. top: 20
  40. },
  41. grid: {
  42. top: '20%',
  43. left: '3%',
  44. right: '6%',
  45. bottom: '3%',
  46. containLabel: true // 距离是包含坐标轴上的文字
  47. },
  48. xAxis: {
  49. type: 'value'
  50. },
  51. yAxis: {
  52. type: 'category'
  53. },
  54. tooltip: {
  55. trigger: 'axis',
  56. axisPointer: {
  57. type: 'line',
  58. z: 0,
  59. lineStyle: {
  60. color: '#2D3443'
  61. }
  62. }
  63. },
  64. series: [
  65. {
  66. type: 'bar',
  67. label: {
  68. show: true,
  69. position: 'right',
  70. textStyle: {
  71. color: 'white'
  72. }
  73. },
  74. itemStyle: {
  75. // 指明颜色渐变的方向
  76. // 指明不同百分比之下颜色的值
  77. color: new this.$echarts.graphic.LinearGradient(0, 0, 1, 0, [
  78. // 百分之0状态之下的颜色值
  79. {
  80. offset: 0,
  81. color: '#5052EE'
  82. },
  83. // 百分之100状态之下的颜色值
  84. {
  85. offset: 1,
  86. color: '#AB6EE5'
  87. }
  88. ])
  89. }
  90. }
  91. ]
  92. }
  93. this.chartInstance.setOption(initOption)
  94. // 对图表对象进行鼠标事件的监听
  95. this.chartInstance.on('mouseover', () => {
  96. clearInterval(this.timerId)
  97. })
  98. this.chartInstance.on('mouseout', () => {
  99. this.startInterval()
  100. })
  101. },
  102. // 获取服务器的数据
  103. async getData () {
  104. // http://127.0.0.1:8888/api/seller
  105. const { data: ret } = await this.$http.get('seller')
  106. this.allData = ret
  107. // 对数据排序
  108. this.allData.sort((a, b) => {
  109. return a.value - b.value // 从小到大的排序
  110. })
  111. // 每5个元素显示一页
  112. this.totalPage = this.allData.length % 5 === 0 ? this.allData.length / 5 : this.allData.length / 5 + 1
  113. this.updateChart()
  114. // 启动定时器
  115. this.startInterval()
  116. },
  117. // 更新图表
  118. updateChart () {
  119. const start = (this.currentPage - 1) * 5
  120. const end = this.currentPage * 5
  121. const showData = this.allData.slice(start, end)
  122. const sellerNames = showData.map((item) => {
  123. return item.name
  124. })
  125. const sellerValues = showData.map((item) => {
  126. return item.value
  127. })
  128. const dataOption = {
  129. yAxis: {
  130. data: sellerNames
  131. },
  132. series: [
  133. {
  134. data: sellerValues
  135. }
  136. ]
  137. }
  138. this.chartInstance.setOption(dataOption)
  139. },
  140. startInterval () {
  141. if (this.timerId) {
  142. clearInterval(this.timerId)
  143. }
  144. this.timerId = setInterval(() => {
  145. this.currentPage++
  146. if (this.currentPage > this.totalPage) {
  147. this.currentPage = 1
  148. }
  149. this.updateChart()
  150. }, 3000)
  151. },
  152. // 当浏览器的大小发生变化的时候, 会调用的方法, 来完成屏幕的适配
  153. screenAdapter () {
  154. // console.log(this.$refs.seller_ref.offsetWidth)
  155. const titleFontSize = this.$refs.seller_ref.offsetWidth / 100 * 3.6
  156. // 和分辨率大小相关的配置项
  157. const adapterOption = {
  158. title: {
  159. textStyle: {
  160. fontSize: titleFontSize
  161. }
  162. },
  163. tooltip: {
  164. axisPointer: {
  165. lineStyle: {
  166. width: titleFontSize
  167. }
  168. }
  169. },
  170. series: [
  171. {
  172. barWidth: titleFontSize,
  173. itemStyle: {
  174. barBorderRadius: [0, titleFontSize / 2, titleFontSize / 2, 0]
  175. }
  176. }
  177. ]
  178. }
  179. this.chartInstance.setOption(adapterOption)
  180. // 手动的调用图表对象的resize 才能产生效果
  181. this.chartInstance.resize()
  182. }
  183. }
  184. }
  185. </script>
  186. <style lang="less" scoped>
  187. </style>

2.2.销量趋势分析

最终的效果如下:

2.2.1.代码环境的准备

TrendPage.vue

  1. <!--
  2. 针对于 /trendpage 这条路径而显示出来的
  3. 在这个组件中 , 通过子组件注册的方式 , 要显示出Trend.vue这个组件
  4. -->
  5. <template>
  6. <div class="com-page">
  7. <trend></trend>
  8. </div>
  9. </template>
  10. <script>
  11. import Trend from '@/components/Trend'
  12. export default {
  13. data () {
  14. return {}
  15. },
  16. methods: {},
  17. components: {
  18. trend: Trend
  19. }
  20. }
  21. </script>
  22. <style lang="less" scoped>
  23. </style>

Trend.vue

  1. <template>
  2. <div class="com-container">
  3. <div class="title" :style="comStyle">
  4. <span>{{ '▎ ' + showTitle }}</span>
  5. <span class="iconfont title-icon" :style="comStyle" @click="showChoice = !showChoice">&#xe6eb;</span>
  6. <div class="select-con" v-show="showChoice" :style="marginStyle">
  7. <div class="select-item" v-for="item in selectTypes" :key="item.key" @click="handleSelect(item.key)">
  8. {{ item.text }}
  9. </div>
  10. </div>
  11. </div>
  12. <div class="com-chart" ref="trend_ref"></div>
  13. </div>
  14. </template>
  15. <script>
  16. export default {
  17. data () {
  18. return {
  19. chartInstane: null,
  20. allData: null, // 从服务器中获取的所有数据
  21. showChoice: false, // 是否显示可选项
  22. choiceType: 'map', // 显示的数据类型
  23. titleFontSize: 0 // 指明标题的字体大小
  24. }
  25. },
  26. mounted () {
  27. this.initChart()
  28. this.getData()
  29. window.addEventListener('resize', this.screenAdapter)
  30. this.screenAdapter()
  31. },
  32. destroyed () {
  33. window.removeEventListener('resize', this.screenAdapter)
  34. },
  35. computed: {
  36. selectTypes () {
  37. if (!this.allData) {
  38. return []
  39. } else {
  40. return this.allData.type.filter(item => {
  41. return item.key !== this.choiceType
  42. })
  43. }
  44. },
  45. showTitle () {
  46. if (!this.allData) {
  47. return ''
  48. } else {
  49. return this.allData[this.choiceType].title
  50. }
  51. },
  52. // 设置给标题的样式
  53. comStyle () {
  54. return {
  55. fontSize: this.titleFontSize + 'px'
  56. }
  57. },
  58. marginStyle () {
  59. return {
  60. marginLeft: this.titleFontSize + 'px'
  61. }
  62. }
  63. },
  64. methods: {
  65. initChart () {
  66. this.chartInstane = this.$echarts.init(this.$refs.trend_ref, 'chalk')
  67. const initOption = {
  68. grid: {
  69. left: '3%',
  70. top: '35%',
  71. right: '4%',
  72. bottom: '1%',
  73. containLabel: true
  74. },
  75. tooltip: {
  76. trigger: 'axis'
  77. },
  78. legend: {
  79. left: 20,
  80. top: '15%',
  81. icon: 'circle'
  82. },
  83. xAxis: {
  84. type: 'category',
  85. boundaryGap: false
  86. },
  87. yAxis: {
  88. type: 'value'
  89. }
  90. }
  91. this.chartInstane.setOption(initOption)
  92. },
  93. async getData () {
  94. // await this.$http.get()
  95. // 对allData进行赋值
  96. const { data: ret } = await this.$http.get('trend')
  97. this.allData = ret
  98. console.log(this.allData)
  99. this.updateChart()
  100. },
  101. updateChart () {
  102. // 半透明的颜色值
  103. const colorArr1 = [
  104. 'rgba(11, 168, 44, 0.5)',
  105. 'rgba(44, 110, 255, 0.5)',
  106. 'rgba(22, 242, 217, 0.5)',
  107. 'rgba(254, 33, 30, 0.5)',
  108. 'rgba(250, 105, 0, 0.5)'
  109. ]
  110. // 全透明的颜色值
  111. const colorArr2 = [
  112. 'rgba(11, 168, 44, 0)',
  113. 'rgba(44, 110, 255, 0)',
  114. 'rgba(22, 242, 217, 0)',
  115. 'rgba(254, 33, 30, 0)',
  116. 'rgba(250, 105, 0, 0)'
  117. ]
  118. // 处理数据
  119. // 类目轴的数据
  120. const timeArr = this.allData.common.month
  121. // y轴的数据 series下的数据
  122. const valueArr = this.allData[this.choiceType].data
  123. const seriesArr = valueArr.map((item, index) => {
  124. return {
  125. name: item.name,
  126. type: 'line',
  127. data: item.data,
  128. stack: this.choiceType,
  129. areaStyle: {
  130. color: new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
  131. {
  132. offset: 0,
  133. color: colorArr1[index]
  134. }, // %0的颜色值
  135. {
  136. offset: 1,
  137. color: colorArr2[index]
  138. } // 100%的颜色值
  139. ])
  140. }
  141. }
  142. })
  143. // 图例的数据
  144. const legendArr = valueArr.map(item => {
  145. return item.name
  146. })
  147. const dataOption = {
  148. xAxis: {
  149. data: timeArr
  150. },
  151. legend: {
  152. data: legendArr
  153. },
  154. series: seriesArr
  155. }
  156. this.chartInstane.setOption(dataOption)
  157. },
  158. screenAdapter () {
  159. this.titleFontSize = this.$refs.trend_ref.offsetWidth / 100 * 3.6
  160. const adapterOption = {
  161. legend: {
  162. itemWidth: this.titleFontSize,
  163. itemHeight: this.titleFontSize,
  164. itemGap: this.titleFontSize,
  165. textStyle: {
  166. fontSize: this.titleFontSize / 2
  167. }
  168. }
  169. }
  170. this.chartInstane.setOption(adapterOption)
  171. this.chartInstane.resize()
  172. },
  173. handleSelect (currentType) {
  174. this.choiceType = currentType
  175. this.updateChart()
  176. this.showChoice = false
  177. }
  178. }
  179. }
  180. </script>
  181. <style lang="less" scoped>
  182. .title {
  183. position: absolute;
  184. left: 20px;
  185. top: 20px;
  186. z-index: 10;
  187. color: white;
  188. .title-icon {
  189. margin-left: 10px;
  190. cursor: pointer;
  191. }
  192. .select-con {
  193. background-color: #222733;
  194. }
  195. }
  196. </style>

router/index.js

  1. ......
  2. import TrendPage from '@/views/TrendPage'
  3. ......
  4. const routes = [
  5. ......
  6. {
  7. path: '/trendpage',
  8. component: TrendPage
  9. }
  10. ]
  11. ......

2.2.2.图表基本功能的实现

  数据的获取

async getData () {

// 获取服务器的数据 , this.allData进行赋值之后 , 调用updateChart方法更新图表

const { data: ret } = await this.$http.get('trend')

this.allData = ret

this.updateChart()

}

  数据的处理

  1. updateChart () {
  2. // x轴的数据
  3. const timeArrs = this.allData.common.month
  4. // y轴的数据 , 暂时先取出map这个节点的数据
  5. // map代表地区销量趋势
  6. // seller代表商家销量趋势
  7. // commodity代表商品销量趋势
  8. const valueArrs = this.allData.map.data
  9. // 图表数据 , 一个图表中显示5条折线图
  10. const seriesArr = valueArrs.map((item, index) => {
  11. return {
  12. type: 'line', // 折线图
  13. name: item.name,
  14. data: item.data,
  15. }
  16. })
  17. const dataOption = {
  18. xAxis: {
  19. data: timeArrs
  20. },
  21. legend: {
  22. data: legendArr
  23. },
  24. series: seriesArr
  25. }
  26. this.chartInstance.setOption(dataOption)
  27. }

 初始化配置

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值相同 , 可以形成堆叠图效果

}

})

......

}

   图例效果

  1. updateChart () {
  2. ......
  3. const valueArrs = this.allData.map.data
  4. const seriesArr = valueArrs.map((item, index) => {
  5. return {
  6. type: 'line',
  7. name: item.name,
  8. data: item.data,
  9. stack: 'map'
  10. }
  11. })
  12. // 准备图例数据 , 它需要和series下的每个对象的name属性保持一致
  13. const legendArr = valueArrs.map(item => {
  14. return item.name
  15. })
  16. const dataOption = {
  17. ......
  18. legend: {
  19. data: legendArr
  20. }
  21. ......
  22. }
  23. 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">&#xe6eb;</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: 20px;

top: 20px;

z-index: 10;

color: white;

.title-icon {

margin-left: 10px;

cursor: pointer;

}

.select-item {

cursor: pointer;

}

}

</style>

  数据动态渲染

使用计算属性 title控制标题的内容和标题的可选择项

<script>

export default {

data () {

return {

chartInstance: null,

allData: null,

dataType: 'map' // 这项数据代表目前选择的数据类型 , 可选值有map seller commodity

}

},

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">&#xe6eb;</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>

  点击可选条目的控

  1. <template>
  2. <div class='com-container'>
  3. <div class="title">
  4. <span>{{ title }}</span>
  5. <span class="iconfont title-icon" @click="showChoice =
  6. !showChoice">&#xe6eb;</span>
  7. <div class="select-con" v-if="showChoice">
  8. <div class="select-item" v-for="item in selectTypes" :key="item.key" @click="handleSelect(item.key)">
  9. {{ item.text }}
  10. </div>
  11. </div>
  12. </div>
  13. <div class='com-chart' ref='trend_ref'></div>
  14. </div>
  15. </template>
  16. <script>
  17. export default {
  18. ......
  19. methods: {
  20. handleSelect (key) {
  21. this.dataType = key
  22. this.updateChart()
  23. this.showChoice = false
  24. }
  25. }
  26. }
  27. </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">&#xe6eb;</span>

......

<script>

export default {

......

computed: {

......

comStyle () {

return {

fontSize: this.titleFontSize + 'px'

}

}

},

例的大小

  1. screenAdapter () {
  2. this.titleFontSize = this.$refs.trend_ref.offsetWidth / 100 * 3.6 const adapterOption = {
  3. legend: {
  4. itemWidth: this.titleFontSize,
  5. itemHeight: this.titleFontSize,
  6. itemGap: this.titleFontSize,
  7. textStyle: {
  8. fontSize: this.titleFontSize / 2
  9. }
  10. }
  11. }
  12. this.chartInstance.setOption(adapterOption)
  13. this.chartInstance.resize()
  14. },

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">&#xe6eb;</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

  1. <!-- 商家分布图表 -->
  2. <template>
  3. <div class='com-container' @dblclick="revertMap">
  4. <div class='com-chart' ref='map_ref'></div>
  5. </div>
  6. </template>
  7. <script>
  8. import axios from 'axios'
  9. import { getProvinceMapInfo } from '@/utils/map_utils'
  10. export default {
  11. data () {
  12. return {
  13. chartInstance: null,
  14. allData: null,
  15. mapData: {} // 所获取的省份的地图矢量数据
  16. }
  17. },
  18. mounted () {
  19. this.initChart()
  20. this.getData()
  21. window.addEventListener('resize', this.screenAdapter)
  22. this.screenAdapter()
  23. },
  24. destroyed () {
  25. window.removeEventListener('resize', this.screenAdapter)
  26. },
  27. methods: {
  28. async initChart () {
  29. this.chartInstance = this.$echarts.init(this.$refs.map_ref, 'chalk')
  30. // 获取中国地图的矢量数据
  31. // http://localhost:8999/static/map/china.json
  32. // 由于我们现在获取的地图矢量数据并不是位于KOA2的后台, 所以咱们不能使用this.$http
  33. const ret = await axios.get('http://localhost:8999/static/map/china.json')
  34. this.$echarts.registerMap('china', ret.data)
  35. const initOption = {
  36. title: {
  37. text: '▎ 商家分布',
  38. left: 20,
  39. top: 20
  40. },
  41. geo: {
  42. type: 'map',
  43. map: 'china',
  44. top: '5%',
  45. bottom: '5%',
  46. itemStyle: {
  47. areaColor: '#2E72BF',
  48. borderColor: '#333'
  49. }
  50. },
  51. legend: {
  52. left: '5%',
  53. bottom: '5%',
  54. orient: 'vertical'
  55. }
  56. }
  57. this.chartInstance.setOption(initOption)
  58. this.chartInstance.on('click', async arg => {
  59. // arg.name 得到所点击的省份, 这个省份他是中文
  60. const provinceInfo = getProvinceMapInfo(arg.name)
  61. console.log(provinceInfo)
  62. // 需要获取这个省份的地图矢量数据
  63. // 判断当前所点击的这个省份的地图矢量数据在mapData中是否存在
  64. if (!this.mapData[provinceInfo.key]) {
  65. const ret = await axios.get('http://localhost:8999' + provinceInfo.path)
  66. this.mapData[provinceInfo.key] = ret.data
  67. this.$echarts.registerMap(provinceInfo.key, ret.data)
  68. }
  69. const changeOption = {
  70. geo: {
  71. map: provinceInfo.key
  72. }
  73. }
  74. this.chartInstance.setOption(changeOption)
  75. })
  76. },
  77. async getData () {
  78. // 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
  79. const { data: ret } = await this.$http.get('map')
  80. this.allData = ret
  81. console.log(this.allData)
  82. this.updateChart()
  83. },
  84. updateChart () {
  85. // 处理图表需要的数据
  86. // 图例的数据
  87. const legendArr = this.allData.map(item => {
  88. return item.name
  89. })
  90. const seriesArr = this.allData.map(item => {
  91. // return的这个对象就代表的是一个类别下的所有散点数据
  92. // 如果想在地图中显示散点的数据, 我们需要给散点的图表增加一个配置, coordinateSystem:geo
  93. return {
  94. type: 'effectScatter',
  95. rippleEffect: {
  96. scale: 5,
  97. brushType: 'stroke'
  98. },
  99. name: item.name,
  100. data: item.children,
  101. coordinateSystem: 'geo'
  102. }
  103. })
  104. const dataOption = {
  105. legend: {
  106. data: legendArr
  107. },
  108. series: seriesArr
  109. }
  110. this.chartInstance.setOption(dataOption)
  111. },
  112. screenAdapter () {
  113. const titleFontSize = this.$refs.map_ref.offsetWidth / 100 * 3.6
  114. const adapterOption = {
  115. title: {
  116. textStyle: {
  117. fontSize: titleFontSize
  118. }
  119. },
  120. legend: {
  121. itemWidth: titleFontSize / 2,
  122. itemHeight: titleFontSize / 2,
  123. itemGap: titleFontSize / 2,
  124. textStyle: {
  125. fontSize: titleFontSize / 2
  126. }
  127. }
  128. }
  129. this.chartInstance.setOption(adapterOption)
  130. this.chartInstance.resize()
  131. },
  132. // 回到中国地图
  133. revertMap () {
  134. const revertOption = {
  135. geo: {
  136. map: 'china'
  137. }
  138. }
  139. this.chartInstance.setOption(revertOption)
  140. }
  141. }
  142. }
  143. </script>
  144. <style lang='less' scoped>
  145. </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()

}

  处理数据并且更新图表

  1. updateChart () {
  2. // 处理图表需要的数据
  3. // 图例数据
  4. const legendData = this.allData.map(item => {
  5. return item.name
  6. })
  7. // 散点数据
  8. const seriesArr = this.allData.map(item => {
  9. return {
  10. type: 'effectScatter',
  11. coordinateSystem: 'geo',
  12. name: item.name,
  13. data: item.children
  14. }
  15. })
  16. const dataOption = {
  17. legend: {
  18. data: legendData
  19. },
  20. series: seriesArr
  21. }
  22. this.chartInstance.setOption(dataOption)
  23. },

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: '#2E72BF',

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设置给图表的某些区域

  标题的大小

   图例大小

  1. screenAdapter () {
  2. const titleFontSize = this.$refs.map_ref.offsetWidth / 100 * 3.6 const adapterOption = {
  3. title: {
  4. textStyle: {
  5. fontSize: titleFontSize
  6. }
  7. },
  8. legend: {
  9. itemWidth: titleFontSize / 2,
  10. itemHeight: titleFontSize / 2,
  11. itemGap: titleFontSize / 2,
  12. textStyle: {
  13. fontSize: titleFontSize / 2 }
  14. }
  15. }
  16. 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>

回到中国地图

  1. <template>
  2. <div class='com-container' @dblclick="revertMap">
  3. <div class='com-chart' ref='map_ref'></div>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. ......
  9. methods: {
  10. ......
  11. revertMap () {
  12. this.chartInstance.setOption({ geo: {
  13. map: 'china'
  14. }
  15. })
  16. }
  17. }
  18. }
  19. </script>

2.4.地区销量排行

最终的效果如下:

2.4.1.代码环境的准备

RankPage.vue

  1. <!--
  2. 针对于 /rankpage 这条路径而显示出来的
  3. 在这个组件中 , 通过子组件注册的方式 , 要显示出Rank.vue这个组件 -->
  4. <template>
  5. <div class="com-page">
  6. <rank></rank>
  7. </div>
  8. </template>
  9. <script>
  10. import Rank from '@/components/Rank'
  11. export default {
  12. data () {
  13. return {}
  14. },
  15. methods: {},
  16. components: {
  17. rank: Rank
  18. }
  19. }
  20. </script>
  21. <style lang="less" scoped>
  22. </style>

Rank.vue

  1. <!-- 地区销售排行 -->
  2. <template>
  3. <div class='com-container'>
  4. <div class='com-chart' ref='rank_ref'></div>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. data () {
  10. return {
  11. chartInstance: null,
  12. allData: null,
  13. startValue: 0, // 区域缩放的起点值
  14. endValue: 9, // 区域缩放的终点值
  15. timerId: null // 定时器的标识
  16. }
  17. },
  18. mounted () {
  19. this.initChart()
  20. this.getData()
  21. window.addEventListener('resize', this.screenAdapter)
  22. this.screenAdapter()
  23. },
  24. destroyed () {
  25. window.removeEventListener('resize', this.screenAdapter)
  26. clearInterval(this.timerId)
  27. },
  28. methods: {
  29. initChart () {
  30. this.chartInstance = this.$echarts.init(this.$refs.rank_ref, 'chalk')
  31. const initOption = {
  32. title: {
  33. text: '▎ 地区销售排行',
  34. left: 20,
  35. top: 20
  36. },
  37. grid: {
  38. top: '40%',
  39. left: '5%',
  40. right: '5%',
  41. bottom: '5%',
  42. containLabel: true
  43. },
  44. tooltip: {
  45. show: true
  46. },
  47. xAxis: {
  48. type: 'category'
  49. },
  50. yAxis: {
  51. type: 'value'
  52. },
  53. series: [
  54. {
  55. type: 'bar'
  56. }
  57. ]
  58. }
  59. this.chartInstance.setOption(initOption)
  60. this.chartInstance.on('mouseover', () => {
  61. clearInterval(this.timerId)
  62. })
  63. this.chartInstance.on('mouseout', () => {
  64. this.startInterval()
  65. })
  66. },
  67. async getData () {
  68. // 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
  69. const { data: ret } = await this.$http.get('rank')
  70. this.allData = ret
  71. // 对allData里面的每一个元素进行排序, 从大到小进行
  72. this.allData.sort((a, b) => {
  73. return b.value - a.value
  74. })
  75. console.log(this.allData)
  76. this.updateChart()
  77. this.startInterval()
  78. },
  79. updateChart () {
  80. const colorArr = [
  81. ['#0BA82C', '#4FF778'],
  82. ['#2E72BF', '#23E5E5'],
  83. ['#5052EE', '#AB6EE5']
  84. ]
  85. // 处理图表需要的数据
  86. // 所有省份所形成的数组
  87. const provinceArr = this.allData.map(item => {
  88. return item.name
  89. })
  90. // 所有省份对应的销售金额
  91. const valueArr = this.allData.map(item => {
  92. return item.value
  93. })
  94. const dataOption = {
  95. xAxis: {
  96. data: provinceArr
  97. },
  98. dataZoom: {
  99. show: false,
  100. startValue: this.startValue,
  101. endValue: this.endValue
  102. },
  103. series: [
  104. {
  105. data: valueArr,
  106. itemStyle: {
  107. color: arg => {
  108. let targetColorArr = null
  109. if (arg.value > 300) {
  110. targetColorArr = colorArr[0]
  111. } else if (arg.value > 200) {
  112. targetColorArr = colorArr[1]
  113. } else {
  114. targetColorArr = colorArr[2]
  115. }
  116. return new this.$echarts.graphic.LinearGradient(0, 0, 0, 1, [
  117. {
  118. offset: 0,
  119. color: targetColorArr[0]
  120. },
  121. {
  122. offset: 1,
  123. color: targetColorArr[1]
  124. }
  125. ])
  126. }
  127. }
  128. }
  129. ]
  130. }
  131. this.chartInstance.setOption(dataOption)
  132. },
  133. screenAdapter () {
  134. const titleFontSize = this.$refs.rank_ref.offsetWidth / 100 * 3.6
  135. const adapterOption = {
  136. title: {
  137. textStyle: {
  138. fontSize: titleFontSize
  139. }
  140. },
  141. series: [
  142. {
  143. barWidth: titleFontSize,
  144. itemStyle: {
  145. barBorderRadius: [titleFontSize / 2, titleFontSize / 2, 0, 0]
  146. }
  147. }
  148. ]
  149. }
  150. this.chartInstance.setOption(adapterOption)
  151. this.chartInstance.resize()
  152. },
  153. startInterval () {
  154. if (this.timerId) {
  155. clearInterval(this.timerId)
  156. }
  157. this.timerId = setInterval(() => {
  158. this.startValue++
  159. this.endValue++
  160. if (this.endValue > this.allData.length - 1) {
  161. this.startValue = 0
  162. this.endValue = 9
  163. }
  164. this.updateChart()
  165. }, 2000)
  166. }
  167. }
  168. }
  169. </script>
  170. <style lang='less' scoped>
  171. </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()

},

  数据的处理

  1. updateChart () {
  2. // 处理图表需要的数据
  3. const provinceArr = this.allData.map(item => {
  4. return item.name
  5. })
  6. const valueArr = this.allData.map(item => {
  7. return item.value
  8. })
  9. const dataOption = {
  10. xAxis: {
  11. data: provinceArr
  12. },
  13. series: [
  14. {
  15. data: valueArr
  16. }
  17. ]
  18. }
  19. this.chartInstance.setOption(dataOption)
  20. },

初始化配置

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

}

  颜色的设置

  不同柱显示不同颜色

  渐变的控制

  1. updateChart () {
  2. // 处理图表需要的数据
  3. const colorArr = [
  4. ['#0BA82C', '#4FF778'],
  5. ['#2E72BF', '#23E5E5'],
  6. ['#5052EE', '#AB6EE5']
  7. ]
  8. ......
  9. const dataOption = {
  10. xAxis: {
  11. data: provinceArr
  12. },
  13. series: [
  14. {
  15. data: valueArr,
  16. itemStyle: {
  17. color: arg => {
  18. let targetColorArr = colorArr[0]
  19. if (arg.vaule >= 300) {
  20. targetColorArr = colorArr[0]
  21. } else if (arg.value >= 200) {
  22. targetColorArr = colorArr[1]
  23. } else {
  24. targetColorArr = colorArr[2]
  25. }
  26. return new this.$echarts.graphic.LinearGradient(0,
  27. 1, 0, 0, [
  28. {
  29. offset: 0,
  30. color: targetColorArr[0]
  31. },
  32. {
  33. offset: 1,
  34. color: targetColorArr[1] }
  35. ])
  36. }
  37. }
  38. }
  39. ]
  40. }
  41. this.chartInstance.setOption(dataOption)
  42. },

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 的值

  1. <script>
  2. export default {
  3. data () {
  4. return {
  5. chartInstance: null,
  6. allData: null,
  7. startValue: 0,
  8. endValue: 9,
  9. timerId: null
  10. }
  11. },
  12. ......
  13. methods: {
  14. ......
  15. startInterval () {
  16. if (this.timerId) {
  17. clearInterval(this.timerId)
  18. }
  19. this.timerId = setInterval(() => {
  20. this.startValue++
  21. this.endValue++
  22. if (this.endValue > this.allData.length - 1) {
  23. this.startValue = 0
  24. this.endValue = 9
  25. }
  26. this.updateChart()
  27. }, 3000)
  28. }
  29. }
  30. }

获取数据之后启

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

  1. <!--
  2. 针对于 /hotpage 这条路径而显示出来的
  3. 在这个组件中 , 通过子组件注册的方式 , 要显示出Hot.vue这个组件
  4. -->
  5. <template>
  6. <div class="com-page">
  7. <hot></hot>
  8. </div>
  9. </template>
  10. <script>
  11. import Hot from '@/components/Hot'
  12. export default {
  13. data () {
  14. return {}
  15. },
  16. methods: {},
  17. components: {
  18. hot: Hot
  19. }
  20. }
  21. </script>
  22. <style lang="less" scoped>
  23. </style>

Hot.vue

  1. <!-- 热销商品图表 -->
  2. <template>
  3. <div class='com-container'>
  4. <div class='com-chart' ref='hot_ref'></div>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. data () {
  10. return {
  11. chartInstance: null,
  12. allData: null
  13. }
  14. },
  15. mounted () {
  16. this.initChart()
  17. this.getData()
  18. window.addEventListener('resize', this.screenAdapter)
  19. this.screenAdapter()
  20. },
  21. destroyed () {
  22. window.removeEventListener('resize', this.screenAdapter)
  23. },
  24. methods: {
  25. initChart () {
  26. this.chartInstance = this.$echarts.init(this.$refs.hot_ref)
  27. const initOption = {}
  28. this.chartInstance.setOption(initOption)
  29. },
  30. async getData () {
  31. // 获取服务器的数据 , 对this.allData进行赋值之后 , 调用updateChart方法更新图表
  32. this.updateChart()
  33. },
  34. updateChart () {
  35. // 处理图表需要的数据
  36. const dataOption = {}
  37. this.chartInstance.setOption(dataOption)
  38. },
  39. screenAdapter () {
  40. const adapterOption = {}
  41. this.chartInstance.setOption(adapterOption)
  42. this.chartInstance.resize()
  43. }
  44. }
  45. }
  46. </script>
  47. <style lang='less' scoped>
  48. </style>

router/index.js

......

import HotPage from '@/views/HotPage'

......

const routes = [

......

{

path: '/hotpage',

component: HotPage

}

]

......

2.5.2.图表基本功能的实现

  Hot.vue

  1. <!-- 热销商品图表 -->
  2. <template>
  3. <div class='com-container'>
  4. <div class='com-chart' ref='hot_ref'></div>
  5. <span class="iconfont arr-left" @click="toLeft" :style="comStyle">&#xe6ef;</span>
  6. <span class="iconfont arr-right" @click="toRight" :style="comStyle">&#xe6ed;</span>
  7. <span class="cat-name" :style="comStyle">{{ catName }}</span>
  8. </div>
  9. </template>
  10. <script>
  11. export default {
  12. data () {
  13. return {
  14. chartInstance: null,
  15. allData: null,
  16. currentIndex: 0, // 当前所展示出的一级分类数据
  17. titleFontSize: 0
  18. }
  19. },
  20. computed: {
  21. catName () {
  22. if (!this.allData) {
  23. return ''
  24. } else {
  25. return this.allData[this.currentIndex].name
  26. }
  27. },
  28. comStyle () {
  29. return {
  30. fontSize: this.titleFontSize + 'px'
  31. }
  32. }
  33. },
  34. mounted () {
  35. this.initChart()
  36. this.getData()
  37. window.addEventListener('resize', this.screenAdapter)
  38. this.screenAdapter()
  39. },
  40. destroyed () {
  41. window.removeEventListener('resize', this.screenAdapter)
  42. },
  43. methods: {
  44. initChart () {
  45. this.chartInstance = this.$echarts.init(this.$refs.hot_ref, 'chalk')
  46. const initOption = {
  47. title: {
  48. text: '▎ 热销商品的占比',
  49. left: 20,
  50. top: 20
  51. },
  52. legend: {
  53. top: '15%',
  54. icon: 'circle'
  55. },
  56. tooltip: {
  57. show: true,
  58. formatter: arg => {
  59. // console.log(arg)
  60. const thirdCategory = arg.data.children
  61. // 计算出所有三级分类的数值总和
  62. let total = 0
  63. thirdCategory.forEach(item => {
  64. total += item.value
  65. })
  66. let retStr = ''
  67. thirdCategory.forEach(item => {
  68. retStr += `
  69. ${item.name}:${parseInt(item.value / total * 100) + '%'}
  70. <br/>
  71. `
  72. })
  73. return retStr
  74. }
  75. },
  76. series: [
  77. {
  78. type: 'pie',
  79. label: {
  80. show: false
  81. },
  82. emphasis: {
  83. label: {
  84. show: true
  85. },
  86. labelLine: {
  87. show: false
  88. }
  89. }
  90. }
  91. ]
  92. }
  93. this.chartInstance.setOption(initOption)
  94. },
  95. async getData () {
  96. // 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
  97. const { data: ret } = await this.$http.get('hotproduct')
  98. this.allData = ret
  99. console.log(this.allData)
  100. this.updateChart()
  101. },
  102. updateChart () {
  103. // 处理图表需要的数据
  104. const legendData = this.allData[this.currentIndex].children.map(item => {
  105. return item.name
  106. })
  107. const seriesData = this.allData[this.currentIndex].children.map(item => {
  108. return {
  109. name: item.name,
  110. value: item.value,
  111. children: item.children // 新增加children的原因是为了在tooltip中的formatter的回调函数中,来拿到这个二级分类下的三级分类数据
  112. }
  113. })
  114. const dataOption = {
  115. legend: {
  116. data: legendData
  117. },
  118. series: [
  119. {
  120. data: seriesData
  121. }
  122. ]
  123. }
  124. this.chartInstance.setOption(dataOption)
  125. },
  126. screenAdapter () {
  127. this.titleFontSize = this.$refs.hot_ref.offsetWidth / 100 * 3.6
  128. const adapterOption = {
  129. title: {
  130. textStyle: {
  131. fontSize: this.titleFontSize
  132. }
  133. },
  134. legend: {
  135. itemWidth: this.titleFontSize / 2,
  136. itemHeight: this.titleFontSize / 2,
  137. itemGap: this.titleFontSize / 2,
  138. textStyle: {
  139. fontSize: this.titleFontSize / 2
  140. }
  141. },
  142. series: [
  143. {
  144. radius: this.titleFontSize * 4.5,
  145. center: ['50%', '60%']
  146. }
  147. ]
  148. }
  149. this.chartInstance.setOption(adapterOption)
  150. this.chartInstance.resize()
  151. },
  152. toLeft () {
  153. this.currentIndex--
  154. if (this.currentIndex < 0) {
  155. this.currentIndex = this.allData.length - 1
  156. }
  157. this.updateChart()
  158. },
  159. toRight () {
  160. this.currentIndex++
  161. if (this.currentIndex > this.allData.length - 1) {
  162. this.currentIndex = 0
  163. }
  164. this.updateChart()
  165. }
  166. }
  167. }
  168. </script>
  169. <style lang='less' scoped>
  170. .arr-left {
  171. position:absolute;
  172. left: 10%;
  173. top: 50%;
  174. transform: translateY(-50%);
  175. cursor: pointer;
  176. color: white;
  177. }
  178. .arr-right {
  179. position:absolute;
  180. right: 10%;
  181. top: 50%;
  182. transform: translateY(-50%);
  183. cursor: pointer;
  184. color: white;
  185. }
  186. .cat-name {
  187. position:absolute;
  188. left: 80%;
  189. bottom: 20px;
  190. color: white;
  191. }
  192. </style>

据的处理

增加 currentIndex索引代表当前显示的数据索引, 后期通过左右箭头改变 currentIndex 的值

  1. <script>
  2. export default {
  3. data () {
  4. return {
  5. chartInstance: null,
  6. allData: null,
  7. currentIndex: 0
  8. }
  9. },
  10. ......
  11. updateChart () {
  12. // 处理图表需要的数据
  13. // 饼图数据
  14. const seriesData = this.allData[this.currentIndex].children.map(item
  15. => {
  16. return {
  17. value: item.value,
  18. name: item.name
  19. }
  20. })
  21. // 图例数据
  22. const legendData = this.allData[this.currentIndex].children.map(item
  23. => {
  24. return item.name
  25. })
  26. const dataOption = {
  27. legend: {
  28. data: legendData
  29. },
  30. series: [
  31. {
  32. data: seriesData
  33. }
  34. ]
  35. }
  36. this.chartInstance.setOption(dataOption)
  37. },

始化配置

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">&#xe6ef;</span>

<span class="iconfont arr_right">&#xe6ed;</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">&#xe6ef;</span>

<span class="iconfont arr_right" @click="toRight">&#xe6ed;</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()

}

}

分类名称的显

  布局和样式

  1. <template>
  2. <div class='com-container'>
  3. ......
  4. <span class="cat_name">分类名称</span>
  5. </div>
  6. </template>
  7. <style lang='less' scoped>
  8. .cat_name {
  9. position: absolute;
  10. left: 80%;
  11. bottom: 20px;
  12. font-weight: bold;
  13. }
  14. </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')

  分类名称和箭头的颜色

  1. <style lang='less' scoped>
  2. .arr_left {
  3. ......
  4. color: white;
  5. }
  6. .arr_right {
  7. ......
  8. color: white;
  9. }
  10. .cat_name {
  11. .....
  12. color: white;
  13. }
  14. </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

  1. <!--
  2. 针对于 /stockpage 这条路径而显示出来的
  3. 在这个组件中 , 通过子组件注册的方式 , 要显示出Stock.vue这个组件
  4. -->
  5. <template>
  6. <div class="com-page">
  7. <stock></stock>
  8. </div>
  9. </template>
  10. <script>
  11. import Stock from '@/components/Stock' export default {
  12. data () {
  13. return {}
  14. },
  15. methods: {},
  16. components: {
  17. stock: Stock
  18. }
  19. }
  20. </script>
  21. <style lang="less" scoped>
  22. </style>

 Stock.vue

  1. <!-- 库存销量分析 -->
  2. <template>
  3. <div class='com-container'>
  4. <div class='com-chart' ref='stock_ref'></div>
  5. </div>
  6. </template>
  7. <script>
  8. export default {
  9. data () {
  10. return {
  11. chartInstance: null,
  12. allData: null,
  13. currentIndex: 0, // 当前显示的数据
  14. timerId: null // 定时器的标识
  15. }
  16. },
  17. mounted () {
  18. this.initChart()
  19. this.getData()
  20. window.addEventListener('resize', this.screenAdapter)
  21. this.screenAdapter()
  22. },
  23. destroyed () {
  24. window.removeEventListener('resize', this.screenAdapter)
  25. clearInterval(this.timerId)
  26. },
  27. methods: {
  28. initChart () {
  29. this.chartInstance = this.$echarts.init(this.$refs.stock_ref, 'chalk')
  30. const initOption = {
  31. title: {
  32. text: '▎库存和销量分析',
  33. left: 20,
  34. top: 20
  35. }
  36. }
  37. this.chartInstance.setOption(initOption)
  38. this.chartInstance.on('mouseover', () => {
  39. clearInterval(this.timerId)
  40. })
  41. this.chartInstance.on('mouseout', () => {
  42. this.startInterval()
  43. })
  44. },
  45. async getData () {
  46. // 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
  47. const { data: ret } = await this.$http.get('stock')
  48. this.allData = ret
  49. console.log(this.allData)
  50. this.updateChart()
  51. this.startInterval()
  52. },
  53. updateChart () {
  54. const centerArr = [
  55. ['18%', '40%'],
  56. ['50%', '40%'],
  57. ['82%', '40%'],
  58. ['34%', '75%'],
  59. ['66%', '75%']
  60. ]
  61. const colorArr = [
  62. ['#4FF778', '#0BA82C'],
  63. ['#E5DD45', '#E8B11C'],
  64. ['#E8821C', '#E55445'],
  65. ['#5052EE', '#AB6EE5'],
  66. ['#23E5E5', '#2E72BF']
  67. ]
  68. // 处理图表需要的数据
  69. const start = this.currentIndex * 5
  70. const end = (this.currentIndex + 1) * 5
  71. const showData = this.allData.slice(start, end)
  72. const seriesArr = showData.map((item, index) => {
  73. return {
  74. type: 'pie',
  75. radius: [110, 100],
  76. center: centerArr[index],
  77. hoverAnimation: false, // 关闭鼠标移入到饼图时的动画效果
  78. labelLine: {
  79. show: false // 隐藏指示线
  80. },
  81. label: {
  82. position: 'center',
  83. color: colorArr[index][0]
  84. },
  85. data: [
  86. {
  87. name: item.name + '\n' + item.sales,
  88. value: item.sales,
  89. itemStyle: {
  90. color: new this.$echarts.graphic.LinearGradient(0, 1, 0, 0, [
  91. {
  92. offset: 0,
  93. color: colorArr[index][0]
  94. },
  95. {
  96. offset: 1,
  97. color: colorArr[index][1]
  98. }
  99. ])
  100. }
  101. },
  102. {
  103. value: item.stock,
  104. itemStyle: {
  105. color: '#333843'
  106. }
  107. }
  108. ]
  109. }
  110. })
  111. const dataOption = {
  112. series: seriesArr
  113. }
  114. this.chartInstance.setOption(dataOption)
  115. },
  116. screenAdapter () {
  117. const titleFontSize = this.$refs.stock_ref.offsetWidth / 100 * 3.6
  118. const innerRadius = titleFontSize * 2
  119. const outterRadius = innerRadius * 1.125
  120. const adapterOption = {
  121. title: {
  122. textStyle: {
  123. fontSize: titleFontSize
  124. }
  125. },
  126. series: [
  127. {
  128. type: 'pie',
  129. radius: [outterRadius, innerRadius],
  130. label: {
  131. fontSize: titleFontSize / 2
  132. }
  133. },
  134. {
  135. type: 'pie',
  136. radius: [outterRadius, innerRadius],
  137. label: {
  138. fontSize: titleFontSize / 2
  139. }
  140. },
  141. {
  142. type: 'pie',
  143. radius: [outterRadius, innerRadius],
  144. label: {
  145. fontSize: titleFontSize / 2
  146. }
  147. },
  148. {
  149. type: 'pie',
  150. radius: [outterRadius, innerRadius],
  151. label: {
  152. fontSize: titleFontSize / 2
  153. }
  154. },
  155. {
  156. type: 'pie',
  157. radius: [outterRadius, innerRadius],
  158. label: {
  159. fontSize: titleFontSize / 2
  160. }
  161. }
  162. ]
  163. }
  164. this.chartInstance.setOption(adapterOption)
  165. this.chartInstance.resize()
  166. },
  167. startInterval () {
  168. if (this.timerId) {
  169. clearInterval(this.timerId)
  170. }
  171. this.timerId = setInterval(() => {
  172. this.currentIndex++
  173. if (this.currentIndex > 1) {
  174. this.currentIndex = 0
  175. }
  176. this.updateChart() // 在更改完currentIndex之后 , 需要更新界面
  177. }, 5000)
  178. }
  179. }
  180. }
  181. </script>
  182. <style lang='less' scoped>
  183. </style>

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/630560
推荐阅读
相关标签
  

闽ICP备14008679号