一、项目描述 (包含echarts中国地图、dataV科技炫酷边框等等)
一个基于 Vue、Datav、Echart 框架的 "数据大屏项目",通过 Vue 组件实现数据动态刷新渲染,内部图表可实现自由替换。部分图表使用 DataV 自带组件, 组件库基于Vue (React版) ,主要用于构建大屏(全屏)数据展示页面即数据可视化,具有多种类型组件可供使用。
项目环境:Vue-cli、DataV、Echarts、node
友情链接:
Vue 官 方文档Vue CLIDataV 官方文档echarts 实例,echarts API 文档
项目展示
二、主要文件介绍
三、项目注意点
引用的模块(先下载 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-uiVue.use(dataV) //全局启用 dataVVue.prototype.$echarts=echarts //将echarts挂载到Vue原型上,全局可使用this.$echarts 调用Vue.config.productionTip = falsenew Vue({router,store,render: h => h(App)}).$mount('#app')
封装组件渲染图表
所有的 ECharts 图表已经对数据和屏幕改动进行了监听,能够动态渲染图表数据。在监听窗口小大的模块。
中间部分中国地图json数据来源 (地址)
我这边直接复制内容存到本地 (把复制的链接在网页打开Ctrl+A Ctrl+C 全选复制粘贴): assets > json > china.json
渲染地图的子组件
有几个注意点:
需要地图json文件需要监听数据变化tooltip: formatter 鼠标移入的提示信息框
<template><divid="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}<br/> 暂无数据`;}return `${a1}<br/>累计注册: ${a2} <br/>日活: ${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>
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//<meta name="viewport" content="width=device-width,initial-scale=1.0 ,user-scalable=no">//并且需要绑定ref // * 默认缩放值const scale = {width: '1',height: '1',}// * 设计稿尺寸(px)// 1920×1080const baseWidth =1920const 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, // 超时时间15sbaseURL: '这里是你请求是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/mainIndexexport 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>