文章目录
- Vuex实现左侧菜单栏折叠
- 效果展示
- 思路分析及代码实现
- 漏洞完善
- Home组件的实现
- 名片部分
- table数据展示部分
- 订单统计部分实现
- 首页可视化图表样式调整
- ECharts基本使用
- 折线图
- 柱状图
- 饼状图
- 附:项目中对axios的使用(二次封装)
- 第一次封装
- 第二次封装
- 附:使用Mock.js完成数据模拟
Vuex实现左侧菜单栏折叠
效果展示
点击菜单图标之前:
点击菜单图标之后:
思路分析及代码实现
首先我们要知道菜单栏的收缩,由el-menu的collapse属性控制:
我们通过分析可以知道:
菜单按钮的点击是在CommonHeader.vue组件中,而我们修改的collapse属性却在CommonAside.vue中,这是两个不同的组件。很明显这涉及到了组件间的通信问题,而我们很快就能想到几种处理组件间通信的方式:
- props
- 全局事件总线
- vuex
- ···
这里我们就使用第三种Vuex。
提前创建好store文件夹,然后在文件夹中放入index.js文件
第一步:在store文件夹中创建tab.js文件
这里我们使用了Vuex的模块化
export default { state:{ isCollapse: false }, mutations:{ COLLAPSE_MENU(state){ state.isCollapse = !state.isCollapse } } }
复制
第二步:在index文件中引入
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); import tab from "./tab" export default new Vuex.Store({ //注意模块化是modules不要把s掉了 //模块化之后共享数据获得方式要注意一下 modules:{ tab } })
复制
第三步:将store挂载到Vue的实例对象上:
第四步:绑定菜单按钮的点击事件handleMenu
在CommonHeader组件中:
每次点击都调用store身上的commit方法,从而引动mutations身上的方法去更新state中的数据
完成了这些之后我们会发现我们的菜单栏确实可以收缩了,但是仍有两个问题:
- 菜单栏的文字不符合要求
- CommonSide组件和CommonHeader组件之间有一个大的空隙
接下来我们就来解决这个问题
漏洞完善
首先我们来解决第一个问题:文字问题
我们通过三元表达式来实现:
第二问题空隙问题
这个问题的产生原因是因为我们el-aside的宽度写死了,不管我们是否折叠菜单栏,这个盒子总会撑开300px,这也就导致了我们的CommonHeader一直被挤着伸展不开。所以我们把Main路由组件中的elside中的width由300px改为auto:
注意:这里不能把width属性删了,或者说不填。在css中我们不写默认值就是auto:
但这里el-side是Element-UI中的组件,这里的width默认值是300px:
Home组件的实现
完成Home组件之前我们先要进行布局操作,这里我们可以使用Element-UI的基本布局:
通过基础的 24 分栏,迅速简便地创建布局。
我们大致可以看到左右的大小关系为1:2:
所以我们可以用如下代码进行布局:
<el-row> <el-col :span="8"></el-col> <el-col :span="16"></el-col> </el-row>
复制
这样布局就完成了
这个时候如果你想去使用backgroundcolor上色看看布局的成效是看不出来的,因为布局所使用的容器只有宽没有高,我们需要往里面放东西把他慢慢地撑开
名片部分
首先我们实现左上角这个名片样式。
名片样式在Element-UI中也有涉及:
其实我们发现在Home组件中我们的每一部分都使用了名片作为基底(提供阴影效果和内容隔离):
我们将代码粘出来,然后进行相关的修正,最终代码如下:
<template> <el-row> <el-col :span="8"> <el-card class="box-card" shadow="always"> <div class="user-info"> <img src="../assets/user.png" alt=""> <div> <p class="user-name">Admin</p> <p class="user-identity">超级管理员</p> </div> </div> <div > <p>上次登陆时间:<span>2021-7-19</span></p> <p>上次登陆地点:<span>武汉</span></p> </div> </el-card> </el-col> <el-col :span="16"><div class="grid-content bg-purple-light"></div></el-col> </el-row> </template> <script> export default { name: "Home", data(){ return{ } } } </script> <style lang="less" scoped> img { height: 150px; margin-right: 40px; border-radius: 50%; } .user-info { display: flex; align-items: center; padding-bottom: 20px; margin-bottom: 20px; border-bottom: 1px solid #ccc; .user-name { font-size: 32px; margin: 0; margin-bottom: 10px; } .user-identity { margin: 0; } } span { margin-left: 60px; } </style>
复制
效果:
代码注意点:
名片的上半部分我们分为了两个部分:
- 一个是用户头像
- 还有一个div装着“Admin”和“超级管理员”
这个“Admin”和“超级管理员”我们不能分开放,因为我们发现他们两个整体都是在头像的中轴附近。
然后我们使用弹性布局让他们在交叉轴上进行居中对齐:
上半部分还有一个注意点,那就是我们使用了p标签,而p标签有一个默认样式就是上下都有一个外边距:
在这里我们要记得把他们去掉,达到与效果的最大契合度。
名片的下方部分,我们采用块级元素中放入行内块元素的方式进行构建,然后我们再给行内元素span设置做外边距即可。
table数据展示部分
效果展示:
完成表格我们直接在Element-UI中选取基础表格样式:
代码如下:
<template> <el-table :data="tableData" style="width: 100%"> <el-table-column prop="date" label="日期" width="180"> </el-table-column> <el-table-column prop="name" label="姓名" width="180"> </el-table-column> <el-table-column prop="address" label="地址"> </el-table-column> </el-table> </template> <script> export default { data() { return { tableData: [{ date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄' }, { date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1519 弄' }, { date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1516 弄' }] } } } </script>
复制
当
el-table
元素中注入data
对象数组后,在el-table-column
中用prop
属性来对应对象中的键名即可填入数据,用label
属性来定义表格的列名。可以使用width
属性来定义列宽。
这里我们使用循环简化代码:
<el-card shadow="always" class="dataShow"> <el-table :data="tableData" style="width: 100%;height: 335px"> <el-table-column v-for="(label,content) in dataLabel" :prop="content" :label="label" > </el-table-column> </el-table> </el-card>
复制
数据:
<script> export default { name: "Home", data() { return { tableData: [ { name: 'oppo', todayBuy: 100, monthBuy: 300, totalBuy: 800 }, { name: 'vivo', todayBuy: 100, monthBuy: 300, totalBuy: 800 }, { name: '苹果', todayBuy: 100, monthBuy: 300, totalBuy: 800 }, { name: '小米', todayBuy: 100, monthBuy: 300, totalBuy: 800 }, { name: '三星', todayBuy: 100, monthBuy: 300, totalBuy: 800 }, { name: '魅族', todayBuy: 100, monthBuy: 300, totalBuy: 800 } ], dataLabel: { name:"课程", todayBuy:"今日购买", monthBuy:"本月购买", totalBuy:"总购买", } } } } </script>
复制
v-for可以将对象中的键值对均进行遍历
订单统计部分实现
效果展示:
首先我们这里也是采用六个名片作为基底,然后再来看看我们的数据:
countData: [ { name: "今日支付订单", value: 1234, icon: "success", color: "#2ec7c9", }, { name: "今日收藏订单", value: 210, icon: "star-on", color: "#ffb980", }, { name: "今日未支付订单", value: 1234, icon: "s-goods", color: "#5ab1ef", }, { name: "本月支付订单", value: 1234, icon: "success", color: "#2ec7c9", }, { name: "本月收藏订单", value: 210, icon: "star-on", color: "#ffb980", }, { name: "本月未支付订单", value: 1234, icon: "s-goods", color: "#5ab1ef", }, ],
复制
- name:对应下方的文字部分
- value:对应价格部分
- color:对应左端方框的背景颜色
- icon:对应左端方框的图标
接下来我们使用for循环对大致的内容进行填充:
<el-card v-for="item in countData" :key="item.name" class="countData" > <i :class="`el-icon-${item.icon}`" :style="{backgroundColor:item.color,color:'#fff' }"></i> <div> <p class="value">¥{{item.value}}</p> <p class="name">{{item.name}}</p> </div> </el-card>
复制
我们完成以上步骤之后,接下来我们还要解决如下几个问题:
- 如何将图标放大并且居中
- 怎么给card添加样式
- 六个卡片如何布局
这里我们直击重点,就不再对margin、padding的一些细微调整花费篇幅说明
首先我们解决第一个问题如何将图标放大并且居中:
我们这里使用通过如下的三个样式对图标进行处理:
- font-size:对图标的大小进行规定
- line-height:让图标上下居中
- text-align:让图标在行内居中
我们知道如上的三个样式一般是对文字进行处理,但实际上他们是对字符框进行的一系列操作,而在这里他们同样可以对i标签中的图标进行作用
代码如下:
i { height: 80px; width: 80px; /*可以通过font-size控制i中的图标大小,为什么??font-size本质上是控制字符框的高度 */ font-size: 30px; line-height: 80px; text-align: center; }
复制
然后我们解决第二个问题:怎么给card添加样式:
如果我们尝试直接给el-card添加class属性然后再使用css对其进行样式的添加这样是行不通的!
因为这个el-card在被编译之后里面还会有一层div,class为el-card_body:
那么我们怎么给card添加样式呢?我们可以看看Element-UI的文档:
通过文档我们知道我们可以使用body-style进行样式的添加,往里面传入一个对象即可
然后我们就可以通过这种语法完成弹性布局。因为我们发现在card里面图标和文字是处于一行的,正好契合了弹性布局的特点。
<el-col :span="16" class="rightPart"> <el-card v-for="item in countData" :key="item.name" class="countData" :body-style="{display:'flex',padding:0}"> <i :class="`el-icon-${item.icon}`" :style="{backgroundColor:item.color,color:'#fff' }"></i> <div> <p class="value">¥{{item.value}}</p> <p class="name">{{item.name}}</p> </div> </el-card>
复制
这里我们还有一个注意点:
我们在给i标签添加样式的时候,是选择直接将样式写在标签里面。要注意这里我们传入的是一个对象,这是Vue中绑定样式的写法,如果想要深入了解可以移步我的另一篇文章:
Vue核心⑥(绑定样式)
如果我们还是使用原来的键值对形式然后使用分号隔开这是行不通的。因为我们的键也会被动态的解析,而这个值并不在我们的Vue实例对象身上所代理,所以会报错。
最后我们来解决六个卡片如何布局的问题:
还是使用流体布局,但是我们要添加一个属性flex-wrap
属性让他换行,否则六个卡片全部挤在了一行。
然后这里我们将卡片的宽使用百分比进行界定,这样我们不用通过精密的计算就可以大致的在一行排上三个卡片。这个百分比我取的是32%
,我之所以不取33%是因为其中还要考虑卡片的外边距等相关因素,导致行内的第三个卡片可能会被挤到下一行去。
最后我们订单统计部分的代码如下:
<template> ·······前一部分代码省略 <el-col :span="16" class="rightPart"> <el-card v-for="item in countData" :key="item.name" class="countData" :body-style="{display:'flex',padding:0}"> <i :class="`el-icon-${item.icon}`" :style="{backgroundColor:item.color,color:'#fff' }"></i> <!-- 这里注意不能使用常规的style的动态的绑定语法,否则你的左值也会被解析,但他会发现Vue实例对象上没有这个值--> <!-- 在Vue中如果对style做绑定要传入一个对象--> <div> <p class="value">¥{{item.value}}</p> <p class="name">{{item.name}}</p> </div> </el-card> </el-col> </template> <script> export default { name: "Home", data() { return { countData: [ { name: "今日支付订单", value: 1234, icon: "success", color: "#2ec7c9", }, { name: "今日收藏订单", value: 210, icon: "star-on", color: "#ffb980", }, { name: "今日未支付订单", value: 1234, icon: "s-goods", color: "#5ab1ef", }, { name: "本月支付订单", value: 1234, icon: "success", color: "#2ec7c9", }, { name: "本月收藏订单", value: 210, icon: "star-on", color: "#ffb980", }, { name: "本月未支付订单", value: 1234, icon: "s-goods", color: "#5ab1ef", }, ], } } } </script> <style lang="less" scoped> p { margin: 0; } .countData { width: 32%; margin-right: 9px; margin-bottom: 14px; i { height: 80px; width: 80px; font-size: 30px; line-height: 80px; text-align: center; } .value { font-size: 30px; margin-bottom: 10px; } .name { text-align: center; color: #999; font-size: 14px; } div { display: flex; flex-direction: column; justify-content:center; margin-left: 15px; } } .rightPart { display: flex; flex-wrap:wrap; } </style>
复制
首页可视化图表样式调整
这一个部分我们主要干两件事:
- 将我们的table静态数据全部替换成mock的数据
- 将我们ECharts图标的位置进行布局:
首先我们将静态数据全部替换成mock的数据,直接在mounted钩子函数中对tableData中的数据进行赋值即可:
布局方面无非就是调调大小,设设边距,结合开发者工具很容易办到,这里就不使用大篇幅的内容进行赘述,我们直接上代码:
<el-card class="zhexian"> 折线图部分 </el-card> <div class="smallpics"> <el-card class="pic" > 柱状图 </el-card> <el-card class="pic"> 扇形图 </el-card> </div>
复制
代码位置:
样式:
.el-col { padding: 10px; } .zhexian { width: 100%; height: 250px; } .smallpics { display: flex; justify-content: space-between; width: 100%; margin-top: 10px; .pic { height: 195px; width: 48%; } }
复制
ECharts基本使用
ECharts是Apache旗下一个基于 JavaScript 的开源可视化图表库:
使用方法也很简单:
- 在绘图前我们需要为 ECharts 准备一个定义了高宽的 DOM 容器
- 然后就可以通过 echarts.init 方法初始化一个 echarts 实例并通过 setOption 方法生成一个简单的柱状图
下面是完整代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>ECharts</title> <!-- 引入刚刚下载的 ECharts 文件 --> <!-- 当然你也可以使用cdn引入 --> <!-- <script src="https://cdn.staticfile.org/echarts/4.3.0/echarts.min.js"></script> --> <script src="echarts.js"></script> </head> <body> <!-- 为 ECharts 准备一个定义了宽高的 DOM --> <div id="main" style="width: 600px;height:400px;"></div> <script type="text/javascript"> // 基于准备好的dom,初始化echarts实例 var myChart = echarts.init(document.getElementById('main')); // 指定图表的配置项和数据 var option = { title: { text: 'ECharts 入门示例' }, tooltip: {}, legend: { data: ['销量'] }, xAxis: { data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'] }, yAxis: {}, series: [ { name: '销量', type: 'bar', data: [5, 20, 36, 10, 10, 20] } ] }; // 使用刚指定的配置项和数据显示图表。 myChart.setOption(option); </script> </body> </html>
复制
其中option中的参数如果我们有看不懂的地方可以直接查看官方文档:
在本项目中我们安装的ECharts依赖是5.1.2版本的:
npm i echarts@5.1.2
复制
折线图
首先我们先在我们的Home组件种引入Echarts:
在动手之前有几个注意点要注意:
- 盛放图的DOM要有宽高
- option中的xAxis、yAxis配置项不能缺少
官网有相应的配置模板:
首先我们先拿到DOM,这里我们使用vue中的ref:
然后我们对其进行初始化并指定图表的配置项和数据:
// 基于准备好的dom,初始化echarts实例 const echarts1 = echarts.init(this.$refs.echarts1) // 指定图表的配置项和数据 var echarts1Option = {}
复制
注意:
这里有可能会报错TypeError: this.dom.getContext is not a function
如果是这样的话就使用document.getElement方法获取标签!
然后我们就要往里面放数据了,先来分析一下我们拿到的数据结构:
要拿到折线图的legend我们需要遍历Array中一个对象的全部key,这里我们直接使用Object身上的keys方法,然后横坐标我们直接手敲:
const {orderData} = data.data const legend = Object.keys(orderData[0]) echarts1Option.xAxis = { data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], type:'category', boundaryGap:true } echarts1Option.legend = { data:legend } echarts1Option.yAxis = {}
复制
注意:
这里的legend也要配置成对象!!!
基本上所有的配置项都是以对象的形式出现!!!
在这里我对xAxis中的配置项做出解释:
- data:横坐标内容
- type:坐标轴类型。这里的category适用于离散类型数据
- boundaryGap:坐标轴两边留白策略。默认是true也就是留白
我们可以看看留白的区别:
不留白:
留白:
然后我们需要将每个横坐标对应的数据拿到进行series项的配置:
echarts1Option.series = [] xAxis.forEach(key => { echarts1Option.series.push({ name: key, data: orderData.data.map(item => item[key]), type: 'line' }) })
复制
这里的map函数返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值:
最后我们使用刚指定的配置项和数据显示图表:
echarts1.setOption(echarts1Option)
复制
最终效果:
柱状图
我们实现了折现图之后,柱状图和饼状图的制作流程应该就已经不是问题了,我们简单地说一下流程。
仍然是在ECharts中找一个例子,这里我们以下图的这个柱状图为例子:
我们的代码部分:
// 接下来我们制作柱状图 const echarts2 = echarts.init(document.getElementsByClassName('zhuzhuang').item(0)) const {userData} = data.data let echarts2Option = {} echarts2Option.xAxis = { type: 'category', data: userData.map(item => item['date'] ) } echarts2Option.yAxis = {}; echarts2Option.series = { data: userData.map(item => item['active'] ), type: 'bar', showBackground: true, backgroundStyle: { color: 'rgba(250, 220, 95, 0.8)' } } // 使用刚指定的配置项和数据显示图表。 echarts2.setOption(echarts2Option)
复制
饼状图
先找一个饼状图的实例,这里我们使用南丁格尔玫瑰图:
代码如下:
//接下来我们来制作饼图 // 基于准备好的dom,初始化echarts实例 const echarts3 = echarts.init(document.getElementsByClassName('bingzhuang').item(0)) // 指定图表的配置项和数据 let echarts3Option = {} const {videoData} = data.data echarts3Option.tooltip = { trigger: 'item', } echarts3Option.legend = { type: 'scroll', orient: 'vertical', right: 5, top: 20, bottom: 20, data: videoData.map(item => item['name']) } echarts3Option.series = [ { name: 'Area Mode', type: 'pie', radius: [20, 100], roseType: 'area', itemStyle: { borderRadius: 5 }, data: videoData } ] // 使用刚指定的配置项和数据显示图表。 echarts3.setOption(echarts3Option)
复制
最终效果:
附:项目中对axios的使用(二次封装)
其原理就是创建axios的实例:
在请求配置中只有 url 是必需的。如果没有指定 method,请求将默认使用 get 方法。
请求配置非常的多,我们可以自行在axios官网上进行查找:
第一次封装
第一步:首先在src目录下新建一个utils文件夹(文件夹名字按个人喜好)并在其目录下创建一个index.js文件用于axios第一次封装。
在src目录下index.js中代码如下图,它里面有请求拦截器和响应拦截器,对请求过程和响应响应过程做出基本的提交和反馈,方便我们直观的观察到请求过程中所遇到的问题:
拦截器:
在请求或响应被 then 或 catch 处理前拦截它们。
import axios from 'axios' const http = axios.create({ // 通用请求的地址前缀 baseURL: '/api', timeout: 10000, // 超时时间 }) // 添加请求拦截器 http.interceptors.request.use(function (config) { // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); // 添加响应拦截器 http.interceptors.response.use(function (response) { // 对响应数据做点什么 return response; }, function (error) { // 对响应错误做点什么 return Promise.reject(error); }); export default http
复制
第二次封装
第二步:在src文件夹下创建api文件,用于axios的二次封装,通过export将方法暴露出去,方便在所需页面调用。
import http from '../utils/request' // 请求首页数据 export const getData = () => { // 返回一个promise对象 return http.get('/home/getData') } export const getUser = (params) => { console.log(params, 'params') // 返回用户列表 //请求参数 (注意这里写的是params是因为是get请求,如果是其他请求例如post、put请求要写为data) return http.get('/user/getUser', params) }
复制
我们在页面中调用这里的getData方法之后得到的直接就是一个Promise对象,我们可以直接用它
.then
拿到我们的数据
请求参数 (注意这里写的是params是因为是get请求,如果是其他请求例如post、put请求要写为data):
第三步:在页面中引用Http.js文件,调用自己所配置好的请求方法,配合asnyc和awit异步请求方法,最终我们就可以直接的获取到请求的数据信息!
这个地方你在created钩子或者mounted钩子里面使用都可以
附:使用Mock.js完成数据模拟
如果我们测试的时候没有自己的后端提供数据,或者说后端没有提供接口,这个时候我们就可以使用Mock.js这个工具!
首先我们安装依赖:
npm install mockjs
复制
然后我们在api文件夹下创建一个mock.js文件,在里面来引入我们的mock:
方法详解:
Mock.mock( rurl?, rtype?, template|function( options ) )
复制
rurl
:可选。表示需要拦截的 URL,可以是 URL 字符串或 URL 正则。例如 //domain/list.json/、‘/domian/list.json’。rtype
:可选。表示需要拦截的 Ajax 请求类型。例如 GET、POST、PUT、DELETE 等。template
:可选。表示数据模板,可以是对象或字符串。例如 { ‘data|1-10’:[{}] }、‘@EMAIL’。function(options)
:可选。表示用于生成响应数据的函数。options
:指向本次请求的 Ajax 选项集,含有 url、type 和 body 三个属性
然后我们需要在main.js对mock进行引入:
通过这个控制台可以看到我们的请求被成功拦截了:
但是data是空的,我们可以让方法返回一些东西,这样data里面就有数据了:
然后再开看看相应的数据:
接下来我们就可以使用mock进行数据的测试了。但是此时我们发现我们mock中的function写死在这里了,按照我们整个项目中的规则,我们是希望把一些相同的逻辑和功能封装在一起。
我们首先在api下面新建一个文件目录,例如这里我就取名mockServerData,我们在下面新建两个文件:
- home.js:用来存储Home组件中的数据
- permission.js:用来存储用户的数据
这里我们就拿home.js举个例子:
// mock数据模拟 import Mock from 'mockjs' // 图表数据 let List = [] export default { getStatisticalData: () => { //Mock.Random.float 产生随机数100到8000之间 保留小数 最小0位 最大0位 for (let i = 0; i < 7; i++) { List.push( Mock.mock({ 苹果: Mock.Random.float(100, 8000, 0, 0), vivo: Mock.Random.float(100, 8000, 0, 0), oppo: Mock.Random.float(100, 8000, 0, 0), 魅族: Mock.Random.float(100, 8000, 0, 0), 三星: Mock.Random.float(100, 8000, 0, 0), 小米: Mock.Random.float(100, 8000, 0, 0) }) ) } return { code: 20000, data: { // 饼图 videoData: [ { name: '小米', value: 2999 }, { name: '苹果', value: 5999 }, { name: 'vivo', value: 1500 }, { name: 'oppo', value: 1999 }, { name: '魅族', value: 2200 }, { name: '三星', value: 4500 } ], // 柱状图 userData: [ { date: '周一', new: 5, active: 200 }, { date: '周二', new: 10, active: 500 }, { date: '周三', new: 12, active: 550 }, { date: '周四', new: 60, active: 800 }, { date: '周五', new: 65, active: 550 }, { date: '周六', new: 53, active: 770 }, { date: '周日', new: 33, active: 170 } ], // 折线图 orderData: { date: ['20191001', '20191002', '20191003', '20191004', '20191005', '20191006', '20191007'], data: List }, tableData: [ { name: 'oppo', todayBuy: 500, monthBuy: 3500, totalBuy: 22000 }, { name: 'vivo', todayBuy: 300, monthBuy: 2200, totalBuy: 24000 }, { name: '苹果', todayBuy: 800, monthBuy: 4500, totalBuy: 65000 }, { name: '小米', todayBuy: 1200, monthBuy: 6500, totalBuy: 45000 }, { name: '三星', todayBuy: 300, monthBuy: 2000, totalBuy: 34000 }, { name: '魅族', todayBuy: 350, monthBuy: 3000, totalBuy: 22000 } ] } } } }
复制
然后我们回到mock.js文件对刚才的function进行修改:
此时我们来测试一下:
ok大功告成!