第1关:大屏可视化实战

任务描述

本关任务:编写一个大屏可视化实战程序。

相关知识

为了完成本关任务,你需要掌握:

  1. 安装 echarts.js;

  2. 新建 vue 界面;

  3. 引入 ECharts;

  4. 创建 Vue 方法和图表信息;

  5. 效果样式;

  6. 通过官网查找自己喜欢的样式;

  7. 组件重复使用。

这一小节我们就会来说讲解一下 Vue 联动 Echarts 实现大屏的展示

安装 echarts.js

npm install echarts --save 或 cnpm install echarts --save

新建 vue 界面
 
  1. <template>
  2. <!-- 定义echarts需要控制的dom元素 -->
  3. <div :style="{height:height,width:width}" />
  4. </template>
引入 ECharts
 
  1. <script>
  2. import echarts from "echarts"
  3. require("echarts/theme/dark") // echarts主题
  4. //....后面就是配置vue的方法和图标样式,我打算拿出开单独讲解
  5. </script>
创建 Vue 方法和图表信息

创建一个 export default 模块

 
  1. export default{
  2. //方法和变量
  3. }

在以上基础添加 data()

 
  1. data() {
  2. return {
  3. chart: null,// echarts实例
  4. chartData: '',// 图表数据
  5. height: '250px',//设置默认高度
  6. width: '50%',//设置默认宽度
  7. }
  8. },

watch 监听

 
  1. watch: {//监听数据变化
  2. chartData: {
  3. deep: true,//深度监听
  4. handler(val) {
  5. this.setOptions(val)//重新绘制图表数据
  6. }
  7. }
  8. },

mounted 创建 echarts 实例

 
  1. mounted() {
  2. // dom元素加载完成后执行
  3. this.$nextTick(() => {
  4. // 初始化echarts实例
  5. this.initEcharts()
  6. })
  7. },

methods 的方法有 initEcharts 和 setOptions

 
  1. methods: {
  2. initEcharts(){},
  3. setOptions({expectedData,actualData}){},
  4. }

initEcharts 方法创建实例对象

 
  1. initEcharts() {
  2. // 定义一个图表数据
  3. this.chartData={
  4. expectedData:[190, 100, 10, 9, 170, 110, 160],
  5. actualData:[120, 200, 150, 80, 70, 110, 130]
  6. }
  7. //参数一:this.$el指向当前组件template模板中的根标签(在mounted生命周期中才有效)
  8. //参数二:echarts主题颜色
  9. this.chart = echarts.init(this.$el, "dark")
  10. this.setOptions(this.chartData)
  11. },

关于图标样式需要注意的是,需要引入了 echarts 主题否则无效.可以看官方的主题样式点击跳转,也可以根据自己的喜好进行定制化主题并且下载本地,引入项目中。

setOptions 方法渲染图表数据

 
  1. setOptions({expectedData,actualData}) {
  2. var option={//setOption方法可以接受一个对象作为参数更多配置参数可以参考官网
  3. title: {//标题组件
  4. text: 'ECharts 入门示例'
  5. },
  6. tooltip: {//提示框组件
  7. trigger: 'axis'//坐标轴触发,主要在柱状图,折线图等会使用类目轴的图表中使用,还有'item','none'
  8. },
  9. legend: {//图例组件
  10. data: ['预期', '实际']
  11. },
  12. grid: {//直角坐标系内绘图网格
  13. left: '3%',
  14. right: '4%',
  15. bottom: '3%',
  16. containLabel: true
  17. },
  18. toolbox: {//工具栏。内置有导出图片,数据视图,动态类型切换,数据区域缩放,重置五个工具。
  19. feature: {
  20. saveAsImage: {},//保存为图片。
  21. dataView: {},//数据视图工具,可以展现当前图表所用的数据,编辑后可以动态更新。
  22. }
  23. },
  24. xAxis: {//直角坐标系 grid 中的 x 轴
  25. type: 'category',
  26. boundaryGap: false,
  27. data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
  28. },
  29. yAxis: {//直角坐标系 grid 中的 y 轴
  30. type: 'value'
  31. },
  32. series: [//图表类型
  33. {
  34. name: '预期',
  35. type: 'line',
  36. stack: '总量',
  37. data: expectedData,//对象内,解析出来的参数
  38. },
  39. {
  40. name: '实际',
  41. type: 'line',
  42. stack: '总量',
  43. data: actualData,//对象内,解析出来的参数
  44. }
  45. ]
  46. }
  47. this.chart.setOption(option)
  48. }

更多配置项可以点击查看官网

可能你会对 setOptions 方法中 {expectedData,actualData} 产生疑问,为此打算单独拆分讲解下 在 initEcharts() 方法中我们调用了 setOptions 方法并且传递了一个对象信息,而{}内的元素就是解析对象里面对应的元素信息,接下来我用控制台展示下:

效果样式

设置了 tooltip 参数类型是 axis 所以移动到坐标轴触发,主要在柱状图,折线图等。

通过官网查找自己喜欢的样式

我们也可以通过官网查看自己需要的示例粘贴到项目中点击直达

找到自己需要的然后点击进去复制代码:

把 option 进行替换成官网的代码就可以了:

可以看出效果已经出来了,我们也可以对里面显示的数据进行替换:

相对来说很简单,但是有的时候我们在其他界面也需要使用到类似的样式,我们就应该使用这个模板然后替换里面的数据做展示,而不是重新创建新页面然后复制代码。

组件重复使用

我现在需要在主页面使用前面的模板,操作也很简单跟着步骤一起来吧! 组件重复使用定义

  1. 在需要引用的界面导入该模块
 
  1. <script>
  2. //引入模板
  3. import TestTemplate from "./dashboard/TestTemplate"
  4. <script>
  1. 添加模板到组件中
 
  1. export default {
  2. components: {
  3. //添加模板到组件
  4. TestTemplate
  5. },
  6. data() {
  7. return {
  8. }
  9. },
  10. }
  1. 添加组件标签 <test-template></test-template>

你可能在想引用的组件标签为什么叫这个,因为我们导入组件的名称是 TestTemplate 那么标签使用的 kebab-case (短横线命名)为什么用这个呢而不是使用驼峰命名,由于 HTML 的特性不识别大小写所以要使用短横线命名,首字母小写,遇到大写字母需要加短横并且改成小写,如 TestTemplate 改成短横 test-template。

此时可以看出首页已经可以展示我们前面写的组件了,简单来说就三个步骤哦!

当前页面数据传递子组件中 但是还没达到我们的要求,不同的页面应该展示不同的数据才对,所以我们需要把当前页面的数据传递到组件中去,简单来讲就是父的数据传递到子里面。 父界面改造

  1. 在父界面 data() 中添加需要传递的数据对象

  1. 在标签中添加创建的数据  <test-template :chart-data="LineChartData"></test-template>

子界面改造(重复使用的组件)

  1. 子界面定义 props 用于接收父组件传递的数据
 
  1. //接收父组件传递的参数
  2. props: {
  3. chartData: {//接收父组件传递的参数名称
  4. type: Object,//父组件传递的数据类型
  5. required: true //表示必须传递
  6. }
  7. }

  1. 需要把原本页面定义的数据进行注释,因为我们可以直接使用父传递过来的 chartData 数据

可以看出数据已经使用的是父页面传递的数据了:

我们还可以在同个页面添加多个一样的组件:

通过不同数据展示不同数据信息:

如图展示:

编程要求

根据提示,在右侧编辑器补充代码,完成大屏可视化的挑战。 具体步骤如下:

代码编写好后再打开 /data/workspace/myshixun/step1 文件夹,项目的主目录在此;

其次程序的数据都是后端API,所以需进行如下步骤安装依赖并运行后端:

  1. cd /data/workspace/myshixun/step1/koa_server
  2. npm install
  3. npm i -g nodemon——全局安装 nodemon
  4. nodemon app.js 即可运行后端

出现下图即为成功启动后端:


最后来启动前端: 我们打开另一个终端,运行如下命令:

  1. cd /data/workspace/myshixun/step1

  2. 采用 npm install 命令安装依赖;

  3. 采用命令 npm run serve 命令运行项目;

出现下图即为运行成功,看打开浏览器查看:

测试说明

平台会对你编写的代码进行测试,最终运行效果图如下:

注意:因随机数据不做图像对比,本关评测内容为测试 Vue 服务是否成功启动,

预期输出:

 
  1. {"step":"正在验证环境是否启动"}
  2. API 接 口 启 动 成 功
  3. Vue 程序启动成功!

开始你的任务吧,祝你成功!

代码文件

<template>
  <div class="screen-container" :style="containerStyle">
    <header class="screen-header">
      <div>
        <img alt="加载图片失败" src="../assets/img/header_border_dark.png" v-show="!item"/>
        <img alt="加载图片失败" src="../assets/img/header_border_light.png" v-show="item"/>
      </div>
      <span class="title" >电商平台实时监控系统</span>
      <div class="title-right">
        <img alt="加载图片失败" class="qiehuan" src="../assets/img/qiehuan_dark.png" v-show="!item"  @click = handleChangeTheme()>
        <img alt="加载图片失败" class="qiehuan" src="../assets/img/qiehuan_light.png" v-show="item"  @click = handleChangeTheme()>
      </div>
    </header>

    <div class="screen-body">
      <section class="screen-left">
        <div id="left-top" :class="fullScreenStatus.trend? 'fullscreen': ''">
          <!-- 销量趋势图表 -->
          <Trend ref="trend"/>
          <div class="resize">
            <span
                :class="['iconfont' ,fullScreenStatus.trend? 'icon-compress-alt':'icon-expand-alt']"
                @click = "changeSize('trend')"
            >
            </span>
          </div>
        </div>

        <div id="left-bottom" :class="fullScreenStatus.seller? 'fullscreen': ''">
          <!-- 商家销售金额图表 -->
          <Seller ref="seller"/>
          <div class="resize"  >
            <span
                :class="['iconfont' ,fullScreenStatus.seller? 'icon-compress-alt':'icon-expand-alt']"
                @click = "changeSize('seller')"
            >
            </span>
          </div>
        </div>

      </section>

      <section class="screen-middle">
        <div id="middle-top" :class="fullScreenStatus.map? 'fullscreen': ''">
          <!-- 商家分布图表 -->
          <Map ref="map"/>
          <div class="resize"  >
            <span
                :class="['iconfont' ,fullScreenStatus.map? 'icon-compress-alt':'icon-expand-alt']"
                @click = "changeSize('map')"
            >
            </span>
          </div>
        </div>

        <div id="middle-bottom" :class="fullScreenStatus.rank? 'fullscreen': ''">
          <!-- 地区销量排行图表 -->
          <Rank ref="rank"/>
          <div class="resize"  >
            <span
                :class="['iconfont' ,fullScreenStatus.rank? 'icon-compress-alt':'icon-expand-alt']"
                @click = "changeSize('rank')"
            >
            </span>
          </div>
        </div>
      </section>

      <section class="screen-right" >
        <div id="right-top" :class="fullScreenStatus.hot? 'fullscreen': ''">
          <!-- 热销商品占比图表 -->
          <Hot ref="hot"/>
          <div class="resize" >
            <span
                :class="['iconfont' , fullScreenStatus.hot? 'icon-compress-alt':'icon-expand-alt']"
                @click = "changeSize('hot')"
            >
            </span>
          </div>
        </div>

        <div id="right-bottom" :class="fullScreenStatus.stock? 'fullscreen': ''">
          <!-- 库存销量分析图表 -->
          <Stock ref="stock" ></Stock>
          <div class="resize"  >
            <span
                class="iconfont "
                :class="fullScreenStatus.stock? 'icon-compress-alt':'icon-expand-alt' "
                @click = "changeSize('stock')"
            >
            </span>
          </div>
        </div>
      </section>
    </div>
  </div>
</template>

<script setup>
/********** begin **********/
//导入 components 目录下的 Hot、Map、Rank、Seller、Stock和Trend 的vue 配置文件
import Hot from '@/components/Hot.vue'
import Map from '@/components/Map.vue'
import Rank from '@/components/Rank.vue'
import Seller from '@/components/Seller.vue'
import Stock from '@/components/Stock.vue'
import Trend from '@/components/Trend.vue'
export default {
  name: 'app',
  components: {
    Hot,
    Map,
    Rank,
    Seller,
    Stock,
    Trend
  }

/********** end **********/

import { reactive, ref, nextTick, onBeforeUnmount, computed } from "vue";
import { useStore } from 'vuex'
import * as dayjs from 'dayjs'
import { getThemeValue } from "@/utils/theme_utils";

let dateTime = ref()
let timer = setInterval(() => {
  dateTime.value = dayjs().format("YYYY-MM-DD HH:mm:ss")
},1000)

//定义每一个图表的全屏样式
let fullScreenStatus = reactive({
  trend:false,
  seller:false,
  map:false,
  rank:false,
  hot:false,
  stock:false
})

let trend = ref()
let seller = ref()
let map = ref()
let rank = ref()
let hot = ref()
let stock = ref()
let refArr = reactive({trend , seller, map, rank, hot, stock})
let item = ref(false)
const store = useStore()

let containerStyle = computed(() => {
  return {
    backgroundColor: getThemeValue(store.state.theme).backgroundColor,
    color: getThemeValue(store.state.theme).titleColor
  }

})

function handleChangeTheme() {
  item.value = !item.value
  store.commit( 'changeTheme')
}

function changeSize(val){
  //fullScreenStatus.stock = true
  fullScreenStatus[val] = !fullScreenStatus[val]

  //screenAdapter方法必须是fullScreenStatus对象中的改变且更新Dom后调用才有效
  nextTick(() => {
   refArr[val].screenAdapter();
  })
}

onBeforeUnmount(() => {
   clearInterval(timer)
})
</script>

<style scoped lang="less">
.fullscreen {
  position: fixed!important;
  top: 0 !important;
  left: 0 !important;
  width: 100% !important;
  height: 100% !important;
  margin: 0 !important;
  z-index: 100;
}

.screen-container {
  width: 100%;
  height: 100%;
  padding: 0 20px;
  background-color: #161522;
  color: #fff;
  box-sizing: border-box;
}

.screen-header {

  width: 100%;
  height: 64px;
  font-size: 20px;
  position: relative;
  > div {
    img {
      width: 100%;
    }
  }
  .title {
    position: absolute;
    left: 50%;
    top: 50%;
    font-size: 20px;
    transform: translate(-50%, -50%);
  }
  .title-right {
    display: flex;
    align-items: center;
    position:absolute;
    right: 0;
    top: 50%;
    transform: translateY(-80%);
  }
  .qiehuan {
    width: 28px;
    height: 21px;
    cursor: pointer;
  }
  .datetime {
    font-size: 15px;
    margin-left: 10px;
  }
  .logo {
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-80%);
  img {
    height: 35px;
    width: 128px;
  }
  }
}
.screen-body {
  width: 100%;
  height: 100%;
  display: flex;
  margin-top: 10px;
.screen-left {
  height: 100%;
  width: 27.6%;
#left-top {
  height: 53%;
  position: relative;
}
#left-bottom {
  height: 31%;
  margin-top: 25px;
  position: relative;
}
}
.screen-middle {
  height: 100%;
  width: 41.5%;
  margin-left: 1.6%;
  margin-right: 1.6%;
#middle-top {
  width: 100%;
  height: 56%;
  position: relative;
}
#middle-bottom {
  margin-top: 25px;
  width: 100%;
  height: 28%;
  position: relative;
}
}
.screen-right {
  height: 100%;
  width: 27.6%;
#right-top {
  height: 46%;
  position: relative;
}
#right-bottom {
  height: 38%;
  margin-top: 25px;
  position: relative;
}
}
}
.resize {
  position: absolute;
  right: 20px;
  top: 20px;
  cursor: pointer;
}
</style>

更多推荐