前言

🚀 基于 vue、datav、Echart 框架的大数据可视化(大屏展示)源码,基于VUE+Echarts 制作,实现大数据可视化。通过 vue 组件实现数据动态刷新渲染,内部图表可自由替换。部分图表使用 DataV 自带组件,可自由进行更改, 可以在此基础上重新开发。

本项目中使用的是echarts图表库,ECharts 提供了常规的折线图、柱状图、散点图、饼图、K线图,用于统计的盒形图,用于地理数据可视化的地图、热力图、线图,用于关系数据可视化的关系图、treemap、旭日图,多维数据可视化的平行坐标,还有用于 BI 的漏斗图,仪表盘,并且支持图与图之间的混搭。

文章目录

  • 前言
  • 一、Echart是什么
  • 二、ECharts入门教程
  • 三、作品演示
  • 四、代码实现
  • main.js
  • App.vue
  • index.vue
  • 五、更多干货


一、Echart是什么

ECharts是一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。

二、ECharts入门教程


三、作品演示

vue数据可视化大屏模板 vue 数据可视化_vue数据可视化大屏模板


四、代码实现

main.js

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import * as echarts from 'echarts'
import './mock/mockServer'
Vue.prototype.$echarts = echarts
Vue.config.productionTip = false
Vue.use(ElementUI);
/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})

App.vue

<template>
  <div id="app">
    <Echarts></Echarts>
  </div>
</template>

<script>
import Echarts from '@/views/echarts/index'
export default {
  name: "App",
  components: {
    Echarts
  },
  data() {
    return {
    };
  },
  watch: {},
  methods: {

  },
};
</script>

<style lang="scss">
#app {
  height: 100%;
}
</style>

index.vue

<template>
  <div class="data-container">
    <div class="header">
      智慧体检数据中心
      <div class="date-time-wrap">
        <div class="time">{{ dateObj.time }}</div>
        <div class="date-wrap">
          <span class="date">{{ dateObj.date }}</span>
          <span class="week">{{ dateObj.week }}</span>
        </div>
      </div>
    </div>
    <div class="content">
      <div class="column left">
        <div class="chart-box peno-num">
          <div class="title-bg">
            <span class="title">当日实时体检人数</span>
          </div>
          <div class="wrap">
            <div>
              <span class="ele-number">{{ peNum }}</span>
              <span class="unit">人</span>
            </div>
          </div>
        </div>
        <div class="chart-box customer-analysis">
          <div class="title-bg">
            <span class="title">体检客户分析</span>
          </div>
          <Chart v-for="(item, index) in pieDataList" :key="index" chart-type="pie" :extra-option="item">
          </Chart>
        </div>
      </div>
      <div class="column center">
        <div class="chart-box time-distribution">
          <Chart chart-type="line" :extra-option="lineOption"></Chart>
        </div>
        <div class="chart-box customer-distribution">
          <Chart chart-type="bar" :extra-option="barOption1"></Chart>
        </div>
        <div class="chart-box type-distribution">
          <Chart chart-type="bar" :extra-option="barOption2"></Chart>
        </div>
      </div>
      <div class="column right">
        <div class="chart-box peno-income">
          <div class="title-bg">
            <span class="title">体检收入</span>
          </div>
          <div class="total-income">
            <span class="ele-number">{{incomeData.totalMoney}}</span>
            <span class="unit">元</span>
          </div>
          <div class="wrap">
            <div class="income">
              <span class="label">个检收入</span>
              <span class="money">{{incomeData.peMoney}}</span>
            </div>
            <div class="income">
              <span class="label">团检收入</span>
              <span class="money">{{incomeData.teamMoney}}</span>
            </div>
            <div class="income">
              <span class="label">人均金额</span>
              <span class="money">{{incomeData.averageMoney}}</span>
            </div>
          </div>
        </div>
        <div class="chart-box dept-workload">
          <div class="title">科室工作量分布</div>
          <AutoScrollTable :data-list="deptWorkload"></AutoScrollTable>
        </div>
        <div class="chart-box item-workload">
          <Chart chart-type="bar" :extra-option="barOption3"></Chart>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import Chart from "@/views/echarts/components/chart";
import AutoScrollTable from "@/views/echarts/components/autoScrollTable";
import { fontSize, parseTime } from "@/util.js";
import { getCurrentPeNum, getPieData, getLineData, getBarData, getIncome,getDeptWorkload } from "@/api/getData";
export default {
  name: "",
  components: {
    Chart,
    AutoScrollTable,
  },
  props: {},
  data() {
    return {
      dateObj: {
        date: "",
        week: "",
        time: ""
      },
      dateTimer: null,
      peNum: 0,
      incomeData: {
        totalMoney: 0,
        peMoney: 0,
        teamMoney: 0,
        averageMoney: 0,
      },
      deptWorkload: [],
      pieDataList: [
        {
          color: ["#FF7006", "#005AFF"],
          series: [
            {
              label: {
                // formatter: "客户来源",
              },
              data: [
                // { value: 400, name: "团体" },
                // { value: 200, name: "个人" },
              ],
            },
          ],
          legend: {
            formatter: null
          },
        },
        {
          color: ["#03B6FF", "#C530FD"],
          series: [
            {
              label: {
                // formatter: "客户性别",
              },
              data: [
                // { value: 400, name: "男性" },
                // { value: 200, name: "女性" },
              ],
            },
          ],
          legend: {
            formatter: null
          },
        },
        {
          color: ["#35D263", "#F19610"],
            series: [
              {
                label: {
                  // formatter: "服务类型",
                },
                data: [
                  // { value: 400, name: "贵宾" },
                  // { value: 200, name: "普通" },
                ],
              },
            ],
            legend: {
              formatter: null
            },
        },
      ],
      lineOption: {
        title: {
          text: "体检客户时段分布",
        },
        legend: {
          data: []
          // data: ["开始体检数", "结束体检数"],
          // width: 200
        },
        xAxis: {
          data: ["7", "8", "9", "10", "11", "12", "13", "14", "15", "16"],
        },
        series: [
          // {
          //   name: "开始体检数",
          //   data: [120, 132, 101, 134, 90, 230, 210, 150, 200, 120],
          // },
          // {
          //   name: "结束体检数",
          //   data: [220, 182, 191, 234, 290, 330, 310, 230, 210, 200],
          // },
        ],
      },
      barOption1: {
        color: ["#01B6FF", "#C530FD"],
        title: {
          text: "体检区域客户分布",
        },
        legend: {
          // data: ["男性", "女性"],
        },
        xAxis: {
          data: ["一楼", "二楼", "三楼", "四楼"],
        },
        series: [
          // {
          //   name: "男性",
          //   data: [320, 302, 301, 334],
          //   type: "bar",
          //   label: {
          //     show: true,
          //     fontSize: fontSize(18),
          //   },
          //   emphasis: {
          //     focus: "series",
          //   },
          //   barWidth: "40%",
          //   stack: "total",
          // },
          // {
          //   name: "女性",
          //   data: [120, 132, 101, 134],
          //   type: "bar",
          //   label: {
          //     show: true,
          //     fontSize: fontSize(18),
          //   },
          //   emphasis: {
          //     focus: "series",
          //   },
          //   barWidth: "40%",
          //   stack: "total",
          // },
        ],
      },
      barOption2: {
        color: ["#F19610", "#01B6FF", "#C530FD"],
        title: {
          text: "体检类型分布",
        },
        legend: {
          // data: ["总人数", "男性", "女性"],
        },
        xAxis: {
          type: "value",
          splitLine: {
            show: true,
            lineStyle: {
              color: "#031E77",
            },
          },
        },
        yAxis: {
          type: "category",
          data: ["健康体检", "公务员体检", "招工体检", "儿童体检", "合计"],
        },
        series: [
          // {
          //   data: [100, 150, 200, 220, 670],
          //   name: "总人数",
          //   type: "bar",
          //   label: {
          //     show: true,
          //     position: "right",
          //     color: "#0EFCFF",
          //     fontSize: fontSize(14),
          //   },
          //   emphasis: {
          //     focus: "series",
          //   },
          //   itemStyle: {
          //     borderRadius: [0, 50, 50, 0],
          //   },
          // },
          // {
          //   data: [30, 80, 100, 120, 330],
          //   name: "男性",
          //   type: "bar",
          //   label: {
          //     show: true,
          //     position: "right",
          //     color: "#0EFCFF",
          //     fontSize: fontSize(14),
          //   },
          //   emphasis: {
          //     focus: "series",
          //   },
          //   itemStyle: {
          //     borderRadius: [0, 50, 50, 0],
          //   },
          // },
          // {
          //   data: [70, 70, 100, 100, 340],
          //   name: "女性",
          //   type: "bar",
          //   label: {
          //     show: true,
          //     position: "right",
          //     color: "#0EFCFF",
          //     fontSize: fontSize(14),
          //   },
          //   emphasis: {
          //     focus: "series",
          //   },
          //   itemStyle: {
          //     borderRadius: [0, 50, 50, 0],
          //   },
          // },
        ],
      },
      barOption3: {
        color: ["#01B6FF", "#C530FD"],
        title: {
          text: "主要体检项目工作量分布",
        },
        legend: {
          // data: ["男性", "女性"],
        },
        xAxis: {
          type: "value",
          splitLine: {
            show: false,
            lineStyle: {
              color: "#031E77",
            },
          },
        },
        yAxis: {
          type: "category",
          data: ["DR", "CT", "心脏彩超", "腹部超声", "MR"],
          splitLine: {
            show: false,
            lineStyle: {
              color: "#031E77",
            },
          },
          axisLabel: {
            color: "#fff",
          },
        },
        series: [
          // {
          //   data: [30, 80, 100, 120, 330],
          //   name: "男性",
          //   type: "bar",
          //   stack: "total",
          //   label: {
          //     show: true,
          //     position: "inside",
          //     color: "#fff",
          //     fontSize: fontSize(14),
          //   },
          //   emphasis: {
          //     focus: "series",
          //   },
          //   itemStyle: {
          //     borderRadius: 0,
          //   },
          //   barWidth: "30%",
          // },
          // {
          //   data: [70, 70, 100, 100, 340],
          //   name: "女性",
          //   type: "bar",
          //   stack: "total",
          //   label: {
          //     show: true,
          //     position: "inside",
          //     // color: "#0EFCFF",
          //     fontSize: fontSize(14),
          //   },
          //   emphasis: {
          //     focus: "series",
          //   },
          //   itemStyle: {
          //     borderRadius: [0, 10, 10, 0],
          //   },
          // },
        ],
      },
      dataTimer: null
    };
  },
  computed: {},
  watch: {},
  created() {
    this.getCurrentTime();
    this.getAllData();
    this.dataTimer = setInterval(() => {
      this.getAllData();
    }, 10* 60 * 1000)
  },
  mounted() {
    this.theme("dark");
  },
  beforeDestroy() {
    this.dataTimer && clearInterval(this.dataTimer)
  },
  methods: {
    theme(type) {
      window.document.documentElement.setAttribute("data-theme", type);
    },
    // 实时获取当前时间
    getCurrentTime() {
      this.dateTimer && clearTimeout(this.dateTimer);
      this.dateTimer = setTimeout(() => {
        this.dateObj.date = parseTime(new Date(), "{y}/{m}/{d}");
        this.dateObj.week = `星期${parseTime(new Date(), "{a}")}`;
        this.dateObj.time = parseTime(new Date(), "{h}:{i}:{s}");
        this.getCurrentTime();
      }, 1000);
    },
    // 获取全部数据
    getAllData() {
      // 获取实时体检人数
      getCurrentPeNum().then((res) => {
        console.log("实时体检人数", res);
        this.peNum = res.data.peNum;
      });
      // 获取饼图数据
      getPieData().then((res) => {
        console.log("饼图数据", res);
        const pieData = res.data;
        pieData.forEach((item,index) => {
          this.pieDataList[index].series[0].label.formatter = item.label
          this.pieDataList[index].series[0].data = item.list
          this.pieDataList[index].legend.formatter = (name) => {
            // 业务数据源
            let data = this.pieDataList[index].series[0].data;
            let totalVal = 0;
            let targetVal = 0;
            for (let i = 0; i < data.length; i++) {
              totalVal += data[i].value;
              if (data[i].name === name) {
                targetVal = data[i].value;
              }
            }
            let arr = [
              `{a|${name} ${targetVal}人}`,
              `{b|${((targetVal / totalVal) * 100).toFixed()}%}`,
            ];
            return arr.join("  ");
          }
        })
      });
      // 获取折线图数据
      getLineData().then(res => {
        console.log("折线图数据", res);
        const legendName = []
        const series = []
        res.data.forEach((item) => {
          legendName.push(item.name)
          series.push({
            name: item.name,
            data: item.data
          })
        })
        this.lineOption.legend.data = legendName
        this.lineOption.series = series
      })
      // 获取柱状图数据
      getBarData().then(res => {
        console.log("柱状图数据", res);
        // barOption1
        let legendName = []
        let series = []
        res.barOption1.forEach((item) => {
          legendName.push(item.name)
          series.push({
            name: item.name,
            data: item.data,
            type: "bar",
            label: {
              show: true,
              fontSize: fontSize(18),
            },
            emphasis: {
              focus: "series",
            },
            barWidth: "40%",
            stack: "total"
          })
        })
        this.barOption1.legend.data = legendName
        this.barOption1.series = series
        // barOption2
        legendName = []
        series = []
        res.barOption2.forEach((item) => {
          legendName.push(item.name)
          series.push({
            name: item.name,
            data: item.data,
            type: "bar",
            label: {
              show: true,
              position: "right",
              color: "#0EFCFF",
              fontSize: fontSize(14),
            },
            emphasis: {
              focus: "series",
            },
            itemStyle: {
              borderRadius: [0, 50, 50, 0],
            }
          })
        })
        this.barOption2.legend.data = legendName
        this.barOption2.series = series
        // barOption3
        legendName = []
        series = []
        res.barOption3.forEach((item, index) => {
          legendName.push(item.name)
          series.push({
            name: item.name,
            data: item.data,
            type: "bar",
            stack: "total",
            label: {
              show: true,
              position: "inside",
              color: "#fff",
              fontSize: fontSize(14),
            },
            emphasis: {
              focus: "series",
            },
            itemStyle: {
              borderRadius: index+1 === res.barOption3.length ? [0, 10, 10, 0] : 0,
            },
            barWidth: "30%"
          })
        })
        this.barOption3.legend.data = legendName
        this.barOption3.series = series
      })
      // 获取体检收入
      getIncome().then((res) => {
        console.log("实时体检收入", res);
        this.incomeData = res.data.incomeData;
      });
      // 获取科室工作量
      getDeptWorkload().then(res => {
        console.log("科室工作量", res);
        this.deptWorkload = res.data
      })
    },
  },
};
</script>

<style scoped lang='scss'>
@font-face {
  font-family: electronicFont;
  src: url(/static/font/DS-DIGIT.TTF);
}
@import "@/style/_handle.scss";
// 数据大屏容器
.data-container {
  height: 100%;
  background-image: url(/static/img/bg.png);
  background-size: contain;
  // 头部标题
  .header {
    position: relative;
    height: 1.025rem;
    background: url(/static/img/title_bg.png) no-repeat;
    background-size: 100% 100%;
    box-sizing: border-box;
    font-size: 0.45rem;
    text-align: center;
    @include font_color("title_color");
    line-height: 1.025rem;
    font-weight: 700;
    font-family: Microsoft YaHei Bold, Microsoft YaHei Bold-Bold;

    // 顶部右侧时钟
    .date-time-wrap {
      position: absolute;
      top: 0;
      right: 0;
      height: 0.5625rem;
      display: flex;
      align-items: center;

      .time {
        height: 100%;
        width: 1.25rem;
        font-size: 0.3rem;
        font-weight: 700;
        @include font_color("time_color");
        display: flex;
        align-items: center;
      }

      .date-wrap {
        height: 100%;
        display: flex;
        flex-flow: column;
        align-items: center;
        font-size: 0.175rem;
        @include font_color("time_color");
        margin: 0 0.125rem 0 0.25rem;

        .date,
        .week {
          height: 100%;
          line-height: 0;
          display: flex;
          align-items: center;
        }
      }
    }
  }

  // 内容
  .content {
    height: calc(100vh - 1.025rem);
    box-sizing: border-box;
    display: flex;

    .column {
      // flex: 1;
      box-sizing: border-box;
      display: flex;
      flex-flow: column;
      margin: 0.1875rem 0.25rem;

      &.left {
        min-width: 5.3375rem;
      }
      &.center {
        min-width: 8.125rem;
      }
      &.right {
        min-width: 9.125rem;
      }

      // 图标盒子通用样式
      .chart-box {
        position: relative;
        border-width: 1px;
        border-style: solid;
        @include border_color("chart_box_border_color");
        @include themeify {
          box-shadow: 0 0 20px 0 themed("box_shadow_color") inset;
        }

        // 菱形标题框样式
        .title-bg {
          height: 0.675rem;
          width: 3.0625rem;
          position: absolute;
          top: 0;
          left: 50%;
          transform: translate(-50%, -50%);
          z-index: 1;
          background: url(/static/img/box_title_bg.png) no-repeat;
          background-size: 100% 100%;

          .title {
            height: 0.675rem;
            width: 3.0625rem;
            display: flex;
            justify-content: center;
            align-items: center;
            @include themeify {
              background: linear-gradient(
                to right,
                themed("chart_box_left_color") 0%,
                themed("chart_box_center_color") 49%,
                themed("chart_box_right_color") 100%
              );
              -webkit-background-clip: text;
            }
            color: transparent;
            font-size: 0.3rem;
            font-family: Microsoft YaHei Bold, Microsoft YaHei Bold-Bold;
            font-weight: 700;
          }
        }
      }

      // 当日体检人数
      .peno-num {
        // flex: 1;
        height: 2.5rem;

        .wrap {
          height: 100%;
          display: flex;
          align-items: center;
          justify-content: center;
        }
      }

      // 体检客户分析
      .customer-analysis {
        flex: 1;
        margin-top: 0.6rem;
        padding: 0.7125rem 0.25rem 0.125rem 0.25rem;
        display: flex;
        flex-flow: column;
        justify-content: space-around;

        .chart {
          flex: 1;
        }
      }

      // 时段分布
      .time-distribution {
        flex: 1;
      }

      // 客户分布
      .customer-distribution {
        flex: 1;
        margin-top: 0.2rem;
      }

      // 类型分布
      .type-distribution {
        flex: 1;
        margin-top: 0.2rem;
      }

      // 体检收入
      .peno-income {
        flex: 1;
        display: flex;
        flex-flow: column;
        justify-content: center;
        align-items: center;

        .total-income {
          //
        }
        .wrap {
          width: 100%;
          display: flex;
          justify-content: space-around;

          .income {
            flex: 1;
            display: flex;
            flex-flow: column;
            align-items: center;

            .label {
              @include font_color("income_lable_color");
              font-size: 0.25rem;
              font-weight: 700;
            }
            .money {
              @include font_color("income_money_color");
              font-size: 0.3rem;
              font-weight: 700;
            }
          }
        }
      }

      // 科室工作量
      .dept-workload {
        flex: 1;
        margin-top: 0.2rem;
        overflow: hidden;
        .title {
          text-align: center;
          @include font_color("box_title_color");
          font-size: 0.3rem;
          font-weight: 700;
        }
      }

      // 项目工作量
      .item-workload {
        flex: 1;
        margin-top: 0.2rem;
      }
    }
  }

  // 电子字体
  .ele-number {
    font-size: 0.9rem;
    @include font_color("ele_color");
    font-family: electronicFont;
    font-weight: Digital-Bold;
  }
  .unit {
    font-size: 0.225rem;
    @include font_color("ele_unit_color");
  }
}
</style>