echarts、dataV 数据可视化大屏

一、项目描述 (包含echarts中国地图、dataV科技炫酷边框等等)

  • 一个基于 Vue、Datav、Echart 框架的 " 数据大屏项目 ",通过 Vue 组件实现数据动态刷新渲染,内部图表可实现自由替换。部分图表使用 DataV 自带组件, 组件库基于Vue (React版) ,主要用于构建大屏(全屏)数据展示页面即数据可视化,具有多种类型组件可供使用。

  • 项目环境:Vue-cli、DataV、Echarts、node

友情链接:

  1. Vue 官 方文档
  2. Vue CLI
  3. DataV 官方文档
  4. echarts 实例,echarts API 文档

项目展示

二、主要文件介绍

文件 作用/功能
main.js 主目录文件,引入 Echart/DataV 等文件
utils 工具函数与 mixins 函数等
components/ HomeWord.vue 项目主结构
assets 静态资源目录,放置 logo 与背景图片

三、项目注意点

引用的模块(先下载 install)

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import * as echarts from 'echarts'  //echarts 引用使用断言,否则可能报错
import ElementUI from 'element-ui'; 
import 'element-ui/lib/theme-chalk/index.css';  
import dataV from '@jiaminghi/data-view'
Vue.use(ElementUI); //部分图标使用 element-ui
Vue.use(dataV)   //全局启用 dataV

Vue.prototype.$echarts=echarts  //将echarts挂载到Vue原型上,全局可使用this.$echarts 调用
Vue.config.productionTip = false
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

封装组件渲染图表

所有的 ECharts 图表已经对数据和屏幕改动进行了监听,能够动态渲染图表数据。在监听窗口小大的模块。

中间部分中国地图json数据来源 (地址)

我这边直接复制内容存到本地 (把复制的链接在网页打开Ctrl+A Ctrl+C 全选复制粘贴): assets > json > china.json
echarts、dataV 数据可视化大屏_第1张图片

渲染地图的子组件

有几个注意点:

  1. 需要地图json文件
  2. 需要监听数据变化
  3. tooltip: formatter 鼠标移入的提示信息框
<template>
  <div
    id="map"
    :style="{
      width: '700px',
      height: '730px',
      marginTop: '10px',
    }"
  >div>
template>

<script>
import china from "../assets/json/china.json"; //引入地图json文件
export default {
  mounted() {
    this.initChart();
  },

  props: {
    mapData: {  //接收父组件传过来的值
      type: Object,
      default: () => ({}),
    },
  },
  data() {
    return {
      options: {},
      chart: null,
    };
  
  methods: {
    // 地图
    initChart() {
      this.$echarts.registerMap("china", china); //echarts的map需要注册,根据引入的json文件名,并定义map的名称
      this.chart = this.$echarts.init(document.getElementById("map"), null, {
        renderer: "svg",
      }); //init()挂载在某个元素,所以还需要在mounted调用, { renderer: "svg" } 将原来的canvas绘图改为svg 清晰度更高
      this.drawMap();
    },
    drawMap() {
      this.chart.setOption(
        {
          title: {
            text: "平台运营实时数据",
            textStyle: {
              color: "#fff",
              fontSize: 28,
            },
          },

          tooltip: { //鼠标移入的提示信息框
            show: true,
            trigger: "item",
            formatter: function (a, b) {
              // 将人数改千分位
              let a2 = "";
              let olda = Number(a["data"].value);

              if (olda >= 0) {
                a2 = olda;
                if (olda > 999) {
                  let parts = olda.toString().split(".");
                  parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
                  a2 = parts.join(".");
                }
              }
              // 日活兼容
              let a3 = a["data"].ratio ? a["data"].ratio : 0;
              let a1 = a["name"];
              if (a1 == "台湾省" && !a2) { // 台湾省显示暂无数据
                return `${a1}
暂无数据
`
; } return `${a1}
累计注册:
${a2}
日活:
${a3} %`
; }, }, series: [ { type: "map", map: "china", //对应registerMap() 对应的名称 top: "150", //调整地图在页面的位置 zoom: 1.3, //缩放比例 emphasis: { label: { show: false }, itemStyle: { areaColor: "rgba(136, 132, 216)", }, }, itemStyle: { borderColor: "#fff", }, data: this.mapData.mapArr, //有数据才显示颜色 }, ], visualMap: { show: true, type: "continuous", calculable: true, orient: "horizontal", textStyle: { color: "#fff", }, min: 0, max: 10000000, text: ["累计注册/人", ""], color: ["#df3d20", "#fff"], inRange: { // color: [ "#fff","#44effb", "#3399ff","#2b8afe", "#006699"], color: ["#44effb", "#3399ff", "#2b8afe", "#006699"], //地图颜色 }, }, }, true ); }, }, watch: { // handler 监听数据发生变化需要具体执行的方法 // deep 需要监听的数据的深度,一般用来监听对象中某个属性的变化 mapData: { handler() { this.drawMap(); }, deep: true, }, }, };
script> <style>style>

复用图表组件(可以研究下)

更换边框 (炫酷的科技感动态边框)

边框是使用了 DataV 自带的组件,只需要去 views 目录下去寻找对应的位置去查找并替换就可以,具体的种类请去 DavaV 官网查看
如:

<dv-border-box-1> 内容撑开 dv-border-box-1>
<dv-border-box-2> 内容撑开 dv-border-box-2>
<dv-border-box-3> 内容撑开 dv-border-box-3>

echarts、dataV 数据可视化大屏_第2张图片

Mixins 解决自适应适配功能

使用 mixins 注入解决了界面大小变动图表自适应适配的功能,函数在 utils/resizeMixins.js 中,应用在 common/echart/index.vue 的封装渲染组件,主要是对 this.chart 进行了功能注入。

屏幕适配

使用更流程通用的 css3:scale 缩放方案,通过 ref 指向 主页面的元素,基准尺寸是 1980px*1080px,所以支持同比例屏幕 100% 填充,如果非同比例则会自动计算比例居中填充,不足的部分则留白。实现代码在 `src/utils/drawMixin ,如果有其它的适配方案,欢迎交流。

src > utils > drawMixin.js

// 屏幕适配 mixin 函数
//需要先设置index.html meta 标签 user-scalable=no
//
//并且需要绑定ref 

// * 默认缩放值
const scale = {
    width: '1',
    height: '1',
  }
  
  // * 设计稿尺寸(px)
  // 1920×1080
  const baseWidth =1920
  const baseHeight = 1080
  
  
  // * 需保持的比例(默认1.77778)
  const baseProportion = parseFloat((baseWidth / baseHeight).toFixed(5))
  
  export default {
    data() {
      return {
        // * 定时函数
        drawTiming: null
      }
    },
    mounted () {
      this.calcRate()
      window.addEventListener('resize', this.resize)
    },
    beforeDestroy () {
      window.removeEventListener('resize', this.resize)
    },
    methods: {
      calcRate () {
        const appRef = this.$refs["appRef"]
        if (!appRef) return 
        // 当前宽高比
        const currentRate = parseFloat((window.innerWidth / window.innerHeight).toFixed(5))
        if (appRef) {
          if (currentRate > baseProportion) {
            // 表示更宽
            scale.width = ((window.innerHeight * baseProportion) / baseWidth).toFixed(5)
            scale.height = (window.innerHeight / baseHeight).toFixed(5)
            appRef.style.transform = `scale(${scale.width}, ${scale.height}) translate(-50%, -50%)`
          } else {
            // 表示更高
            scale.height = ((window.innerWidth / baseProportion) / baseHeight).toFixed(5)
            scale.width = (window.innerWidth / baseWidth).toFixed(5)
            appRef.style.transform = `scale(${scale.width}, ${scale.height}) translate(-50%, -50%)`
          }
        }
      },
      resize () {
        clearTimeout(this.drawTiming)
        this.drawTiming = setTimeout(() => {
          this.calcRate()
        }, 200)
      }
    },
  }

homeword.vue

<template>
  <div id="index" ref="appRef">
  ...页面内容...
  div>
template>

<script>
import drawMixin from "../utils/drawMixin";
export default {
  components: {
   //.....
  },
  mixins: [drawMixin], //混入 (保持页面缩放比例)
 } 
 script>

请求数据

使用 axios 进行数据请求,在 main.js 位置进行全局配置。

src > request 文件统一处理所有的请求

src > request > request.js 请求和响应拦截

// 封装axios实例的拦截器(请求, 响应)
import axios from 'axios';


// 1. 创建axios实例
const instance = axios.create({
    timeout: 15000, // 超时时间15s
    baseURL: '这里是你请求是ip地址', // ip+端口, 公用的前缀路径
});

// 重写实例请求前拦截器
instance.interceptors.request.use((config) => {

    return config;
}, (err) => {
    return Promise.reject(err);
})

// 重写实例响应后拦截器
instance.interceptors.response.use((result) => {
   
    return result.data;
}, (err) => {
    return Promise.reject(err);
})

// 导出axios实例
export default instance;

src > request > request.js 请求拼接地址

import request from './request';

// 主要指标 /mcpbd-data/data/mainIndex
export const getMainIndex= () => request.get('/mainIndex')  //这里是get请求,"/mainIndex" 是接口文档的请求拼接字段

//......

homeword.vue 引入,并发送请求数据

由于一个页面要发送多个请求,并且成功获取数据再渲染,我使用了promise.all

并发请求,但是发现请求很慢(不知道有什么方法可以优化)

<template>
    ...
template>

<script>
import drawMixin from "../utils/drawMixin";

//接口
import { 
  getMainIndex,
} from "../request/httpApi";

export default {
  components: {
    //...
  },
  mixins: [drawMixin],
  name: "HelloWorld",
  props: {},
  data() {
    return {
      //渲染子组件,还未请求到数据的时候,需要放数据的所有元素不显示
      falg: false,
      loading: true,
      loadTimer: null,
      resd: [],
      realVal: 0,
      MainIndicators: {
        // 主要指标 默认数据
        userAllCnt: "",
        userNewCnt: "",
        userDailyActvCnt: "",
        userDailyActvRatio: "",
        userMonthlyActvCnt: "",
        userMonthlyActvRatio: "",
        mctAllCnt: "",
      },

      //累计注册用户数折线
     
      //....

      mapData: {
        //地图数据
        mapArr: [],
      },
    };
  },
  mounted() {
    this.cancelLoading();
  },
  filters: {
    //过滤数据
    numFilter(val) {
      return (parseFloat(val) * 100).toFixed(1);
    },
  },
  created() {
    this.getJ();
    this.getShishi();
    // 实时更新数据(隔一个小时请求数据)
    setInterval(() => {
      this.getJ();
    }, 3600000);
    //左下角数据实时更新(1分钟)
    setInterval(() => {
      this.getShishi();
    }, 60000);
  },
  methods: {
    getShishi() {
      //左下角数据 (因为这部分数据需要每分钟更新一次所以单独拎出来)
      getRealTimeIndex()  //发送请求
        .then((res) => {
          this.RealTimeIndex.newUserAllUserCnt = [];
          this.RealTimeIndex.newUserAllUserDate = [];
          this.resd = res;
          this.resd.forEach((item) => {
            this.RealTimeIndex.newUserAllUserCnt.push(
              Number(item.userAllCnt).toFixed()
            ),
              this.RealTimeIndex.newUserAllUserDate.push(
                item.calTime.substring(11)
              );
          });
        })
        .catch((err) => {
          return;
        });
    },
    sortData(attr) {
      return function (a, b) {
        return b[attr] - a[attr];
      };
    },
    getJ() {
      // 累计注册用户
      let p1 = new Promise((resolve, reject) => {
        getUserAllCnt()
          .then((res) => {
            resolve(res);
          })
          .catch((err) => {
            reject(err);
          });
      });

      // 用户
      let p2 = new Promise((resolve, reject) => {
       //....
      });
      // 累计用户数
      let p3 = new Promise((resolve, reject) => {
        //....
      });
      // 日活用户数
      let p4 = new Promise((resolve, reject) => {
         //....
      });

      // 新增用户数
      let p5 = new Promise((resolve, reject) => {
        //....
      });
      // 指标排行表
      let p6 = new Promise((resolve, reject) => {
        //....
      });

      // 主要指标
      let p7 = new Promise((resolve, reject) => {
        getMainIndex()
          .then((res) => {
            setTimeout(() => {
              resolve(res);
            }, 700);
          })
          .catch((err) => {
            reject(err);
          });
      });
      // 地图数据省份
      let p8 = new Promise((resolve, reject) => {
        //....
      });

      Promise.all([p1, p2, p3, p4, p5, p6, p7, p8])
        .then((res) => {
          // 累计注册用户
       	  //数据处理。。。

          // 日活
         //数据处理。。。

          // 累计用户数 (只展示前5条数据)
         //数据处理。。。

          // 日活用户数
       //数据处理。。。

          // 新增用户数
          //数据处理。。。

          // 指标排行表
          //数据处理。。。

          // 主要指标
          let mainIndex = res[6];
          this.MainIndicators.userAllCnt = Number(
            mainIndex.userAllCnt
          ).toLocaleString("en-US"); //使用千分符
          this.MainIndicators.userNewCnt = Number(
            mainIndex.userNewCnt
          ).toLocaleString("en-US");
          this.MainIndicators.userDailyActvCnt = Number(
            mainIndex.userDailyActvCnt
          ).toLocaleString("en-US");
          this.MainIndicators.userDailyActvRatio = mainIndex.userDailyActvRatio;
          this.MainIndicators.userMonthlyActvCnt = Number(
            mainIndex.userMonthlyActvCnt
          ).toLocaleString("en-US");
          this.MainIndicators.userMonthlyActvRatio =
            mainIndex.userMonthlyActvRatio;
          this.MainIndicators.mctAllCnt = Number(
            mainIndex.mctAllCnt
          ).toLocaleString("en-US");

          //  地图省份指标
          let FenUserMap = res[7];
          this.mapData.mapArr = FenUserMap.map((item) => ({
            name: item.regionName,
            value: Number(item.userAllCnt).toFixed(),
            ratio: (Number(item.userActvDailyRatio) * 100).toFixed(1),
          }));
          //由于暂无数据隐藏南海诸岛,不太好,后面想到既然可以隐藏也可以将提示信息改成暂无数据
          this.mapData.mapArr.push({  
            name: "南海诸岛",
            value: 0,
            itemStyle: { opacity: 0, label: { show: false } },
          });

          this.falg = true; //子组件渲染
        })
        .catch((err) => {});
    },

    cancelLoading() {
      if (!this.loadTimer) {
        this.loadTimer = setTimeout(() => {
          this.loading = false;
        }, 500);
      } else {
        clearTimeout(this.loadTimer);
      }
    },
   
  },
  destroyed() {},
};
script>

你可能感兴趣的:(echarts,vue.js,javascript)