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

最终的效果如图所示最终效果涉及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 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({

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

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

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

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.分辨率适配

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

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

<!-- 商家销量统计的横向柱状图 -->
<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">&#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>

  点击可选条目的控

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

<!-- 商家分布图表 -->
<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: '#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设置给图表的某些区域

  标题的大小

   图例大小

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">&#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()

}

}

分类名称的显

  布局和样式

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

猜你喜欢

转载自blog.csdn.net/qq_36384657/article/details/129291505