头歌(Educoder)——Vue基础——大屏可视化实战
第1关:大屏可视化实战
任务描述
本关任务:编写一个大屏可视化实战程序。
相关知识
为了完成本关任务,你需要掌握:
-
安装 echarts.js;
-
新建 vue 界面;
-
引入 ECharts;
-
创建 Vue 方法和图表信息;
-
效果样式;
-
通过官网查找自己喜欢的样式;
-
组件重复使用。
这一小节我们就会来说讲解一下 Vue 联动 Echarts 实现大屏的展示
安装 echarts.js
npm install echarts --save 或 cnpm install echarts --save
新建 vue 界面
<template><!-- 定义echarts需要控制的dom元素 --><div :style="{height:height,width:width}" /></template>
引入 ECharts
<script>import echarts from "echarts"require("echarts/theme/dark") // echarts主题//....后面就是配置vue的方法和图标样式,我打算拿出开单独讲解</script>
创建 Vue 方法和图表信息
创建一个 export default 模块
export default{//方法和变量}
在以上基础添加 data()
data() {return {chart: null,// echarts实例chartData: '',// 图表数据height: '250px',//设置默认高度width: '50%',//设置默认宽度}},
watch 监听
watch: {//监听数据变化chartData: {deep: true,//深度监听handler(val) {this.setOptions(val)//重新绘制图表数据}}},
mounted 创建 echarts 实例
mounted() {// dom元素加载完成后执行this.$nextTick(() => {// 初始化echarts实例this.initEcharts()})},
methods 的方法有 initEcharts 和 setOptions
methods: {initEcharts(){},setOptions({expectedData,actualData}){},}
initEcharts 方法创建实例对象
initEcharts() {// 定义一个图表数据this.chartData={expectedData:[190, 100, 10, 9, 170, 110, 160],actualData:[120, 200, 150, 80, 70, 110, 130]}//参数一:this.$el指向当前组件template模板中的根标签(在mounted生命周期中才有效)//参数二:echarts主题颜色this.chart = echarts.init(this.$el, "dark")this.setOptions(this.chartData)},
关于图标样式需要注意的是,需要引入了 echarts 主题否则无效.可以看官方的主题样式点击跳转,也可以根据自己的喜好进行定制化主题并且下载本地,引入项目中。
setOptions 方法渲染图表数据
setOptions({expectedData,actualData}) {var option={//setOption方法可以接受一个对象作为参数更多配置参数可以参考官网title: {//标题组件text: 'ECharts 入门示例'},tooltip: {//提示框组件trigger: 'axis'//坐标轴触发,主要在柱状图,折线图等会使用类目轴的图表中使用,还有'item','none'},legend: {//图例组件data: ['预期', '实际']},grid: {//直角坐标系内绘图网格left: '3%',right: '4%',bottom: '3%',containLabel: true},toolbox: {//工具栏。内置有导出图片,数据视图,动态类型切换,数据区域缩放,重置五个工具。feature: {saveAsImage: {},//保存为图片。dataView: {},//数据视图工具,可以展现当前图表所用的数据,编辑后可以动态更新。}},xAxis: {//直角坐标系 grid 中的 x 轴type: 'category',boundaryGap: false,data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']},yAxis: {//直角坐标系 grid 中的 y 轴type: 'value'},series: [//图表类型{name: '预期',type: 'line',stack: '总量',data: expectedData,//对象内,解析出来的参数},{name: '实际',type: 'line',stack: '总量',data: actualData,//对象内,解析出来的参数}]}this.chart.setOption(option)}
更多配置项可以点击查看官网
可能你会对 setOptions 方法中 {expectedData,actualData} 产生疑问,为此打算单独拆分讲解下 在 initEcharts() 方法中我们调用了 setOptions 方法并且传递了一个对象信息,而{}内的元素就是解析对象里面对应的元素信息,接下来我用控制台展示下:
效果样式
设置了 tooltip 参数类型是 axis 所以移动到坐标轴触发,主要在柱状图,折线图等。
通过官网查找自己喜欢的样式
我们也可以通过官网查看自己需要的示例粘贴到项目中点击直达
找到自己需要的然后点击进去复制代码:
把 option 进行替换成官网的代码就可以了:
可以看出效果已经出来了,我们也可以对里面显示的数据进行替换:
相对来说很简单,但是有的时候我们在其他界面也需要使用到类似的样式,我们就应该使用这个模板然后替换里面的数据做展示,而不是重新创建新页面然后复制代码。
组件重复使用
我现在需要在主页面使用前面的模板,操作也很简单跟着步骤一起来吧! 组件重复使用定义
- 在需要引用的界面导入该模块
<script>//引入模板import TestTemplate from "./dashboard/TestTemplate"<script>
- 添加模板到组件中
export default {components: {//添加模板到组件TestTemplate},data() {return {}},}
- 添加组件标签
<test-template></test-template>
你可能在想引用的组件标签为什么叫这个,因为我们导入组件的名称是 TestTemplate 那么标签使用的 kebab-case (短横线命名)为什么用这个呢而不是使用驼峰命名,由于 HTML 的特性不识别大小写所以要使用短横线命名,首字母小写,遇到大写字母需要加短横并且改成小写,如 TestTemplate 改成短横 test-template。
此时可以看出首页已经可以展示我们前面写的组件了,简单来说就三个步骤哦!
当前页面数据传递子组件中 但是还没达到我们的要求,不同的页面应该展示不同的数据才对,所以我们需要把当前页面的数据传递到组件中去,简单来讲就是父的数据传递到子里面。 父界面改造
- 在父界面
data()中添加需要传递的数据对象
- 在标签中添加创建的数据
<test-template :chart-data="LineChartData"></test-template>
子界面改造(重复使用的组件)
- 子界面定义
props用于接收父组件传递的数据
//接收父组件传递的参数props: {chartData: {//接收父组件传递的参数名称type: Object,//父组件传递的数据类型required: true //表示必须传递}}
- 需要把原本页面定义的数据进行注释,因为我们可以直接使用父传递过来的
chartData数据
可以看出数据已经使用的是父页面传递的数据了:
我们还可以在同个页面添加多个一样的组件:
通过不同数据展示不同数据信息:
如图展示:
编程要求
根据提示,在右侧编辑器补充代码,完成大屏可视化的挑战。 具体步骤如下:
代码编写好后再打开 /data/workspace/myshixun/step1 文件夹,项目的主目录在此;
其次程序的数据都是后端API,所以需进行如下步骤安装依赖并运行后端:
cd /data/workspace/myshixun/step1/koa_servernpm installnpm i -g nodemon——全局安装nodemonnodemon app.js即可运行后端
出现下图即为成功启动后端:
最后来启动前端: 我们打开另一个终端,运行如下命令:
-
cd /data/workspace/myshixun/step1 -
采用
npm install命令安装依赖; -
采用命令
npm run serve命令运行项目;
出现下图即为运行成功,看打开浏览器查看:
测试说明
平台会对你编写的代码进行测试,最终运行效果图如下:
注意:因随机数据不做图像对比,本关评测内容为测试 Vue 服务是否成功启动,
预期输出:
{"step":"正在验证环境是否启动"}API 接 口 启 动 成 功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>
更多推荐

所有评论(0)