终于是走向正题,引入业务使用的ui组件,Element,也是饿了么开放的组件,屁不多放,开始引入
具体的安装工作应用传送门
采用版本:element-ui@2.13.0
开发工具:WebStorm
引入sass支持,sass拓展语法写css特别方便
cnpm install sass-loader@latest
cnpm install node-sass@latest
布局部分
一、页面布局
通常为左-上-内容,三等分,如图
当然可以根据自己需要,来进行调整 布局传送门
①基本布局
形成上方的布局,只需要跟着官方文档,就能拼接出来
<el-container class="page-container">
<el-aside>
</el-aside>
<el-container>
<el-header>
</el-header>
<el-main>
<router-view /> //存放页面展示的地方
</el-main>
</el-container>
</el-container>
②样式调整
如果什么都不预先调整,页面只能是占一部分,无法全占比,我们可以尝试在main.js中引用公共样式来修正这个问题;
//main.js
import './style/common.scss'
//common.scss
//用以绽开全屏
html,body,#app,.page-container{ width:100%; height:100%; margin: 0;}
//用以关掉不必要的滑动条,可以不加
::-webkit-scrollbar-track-piece {
background-color: transparent;
}
::-webkit-scrollbar {
width: 0px;
height: 0px;
background-color: transparent;
}
::-webkit-scrollbar-thumb {
border-radius: 0px;
background-color: hsla(220, 4%, 58%, .3);
}
这样,一个基本的模版布局就出来了。
二、左侧导航栏
①引入NavMenu
现成的导航栏,我们其实已经可以直接拷过来,但是这里存在问题,每一个层级都要写进去。这样遍历是无法处理多重结构的,总不能一直自己叠加,这时候就要用到组件的递归调用了。
②菜单格式的确定
首先是确定,菜单的格式,影响下方菜单的制定,下方是简单制定的json传入格式
{
"children": [],
"href": "/test",
"icon": "el-icon-star-off",
"id": "1",
"label": "测试",
"parentId": "0"
}
③组件的自我递归
第一层 <el-menu> 毋庸置疑是必须的,无法预测的是子菜单中层级的数量,则这个层级可以作为父组件,向下传值,让不确定的子组件进行自我递归。
// menu/index.vue
<el-menu
style="height: 100%"
:default-active="$route.path"
:show-timeout="200"
background-color="#00142a"
text-color="hsla(0,0%,100%,.65)"
active-text-color="#409eff"
:collapse="isCollapse">
<child-menu :menu="menu" :isCollapse="isCollapse"/>
</el-menu>
对于子组件:
1、首先是需要判断是否本身就已经没有子菜单,没有则本身就属于一个链接菜单<el-menu-item>,有则是需要进入 <submenu> 组件,生成子菜单 A,
2、 这个子菜单A又需要进行判断,是否存在子菜单,若不存在则可以归类为<el-menu-item>,若存在,我们又要进行这两个步骤循环
3、这时候我们就需要用到 vue 中name的用法了,重复代码不需要一直叠加下去,要利用name属性进行自我调用
// menu/child-menu.vue
<template v-for="(item,index) in menu">
<el-menu-item v-if="item.children.length==0"
:index="item.label"
@click="open(item)"
:key="item.label">
<i :class="item.icon"/>
<span slot="title">{{item.label}}</span>
</el-menu-item>
<el-submenu v-else
:index="item.label"
:key="item.label">
<!-- 侧边栏标题 -->
<template slot="title">
<span slot="title" >
<i :class="item.icon"/>{{item.label}}
</span>
</template>
<!-- 侧边栏子项目 -->
<template v-for="(child,cindex) in item.children">
<el-menu-item
:index="child.id"
@click="open(child)"
v-if="child.children.length==0"
:key="child.label">
<span slot="title"> <i :class="child.icon"/>{{child.label}}</span>
</el-menu-item>
<child-menu v-else
:menu="[child]"
:key="cindex"
:isCollapse="isCollapse"/>
</template>
</el-submenu>
</template>
········
name:"childMenu"
①上述open(是用于模拟打开路由的)
②slot="title",引用官方: 【通过 el-menu-item-group
组件可以实现菜单进行分组,分组名可以通过title
属性直接设定,也可以通过具名 slot 来设定。】
③递归重新调用自己的时候 ,加上[],是因为递归的时候是数组,而传入的是对象,要转换一下类型
三、顶部tags
①分析如何构造顶部标签栏
1、element-tag
我们可以从element 中tags看到tag的形式,能不能做到路由展示的结果形成一个个标签栏,在头部滑动呢?
基本是可以满足,如果是固定的话则会出现
就会出现无法简单控制限制换行的情况,element中tag布局基础是flex布局,其实这个不是重点,重点是无法 达到我想要的H5触摸的方式
2、element-button
与上述是一致的,其实都可以使用,进行css调整以达到通常的标签支持
3、原生dom标签
为了支持我h5触摸滚动的高逼格方式,还是得用原生html标签。(样式上是可以通用到上述 tags、button的通常用标签格式的。)
②自定义tag
1、dom骨架
首先外层用于控制触发方法,内层才是内容展示
<div ref="tagsList"> //后面用于触发h5 api
<div ref="tag"> //后面触发切换
<span ></span> //放置内容
<i class="el-icon-close"/> //关闭图标展示
</div>
</div>
2、scss样式/骨架补充
// tags
<div class="tags-container"> //想了下还是要用定位
<div class="tags-box">
<div ref="tagsList" class="tags-list" >
<div ref="tag" class="el-tag el-tag--plain">
<span class="tag-text"></span>
<i class="el-icon-close "/>
</div>
</div>
</div>
</div>
scss
.tags-container {
//父级样式设置定位元素,为子项目定位提供参照系,不让子元素超过父元素的内容框
//阴影部分凸显不一样
position: relative;
box-sizing: border-box;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
.tags-box {
//给内容部分提供最大化展示,设置固定高度
position: relative;
box-sizing: border-box;
width: 100%;
height: 40px;
}
.tags-list {
//实际上这个开始已经是为单个独立标签进行设置,设置绝对定位是为了后续给其滑动使用
position: absolute;
padding: 2px 10px;
overflow: visible;//可以让其飘出去,左/右边
white-space: nowrap;//内容不换行
transition: left .3s ease;//纯粹简单滑动过渡
}
.tag-item {
//展示内容元素
position: relative;
display: inline-block; //限制为行内块级元素
height: 30px;
line-height: 30px;
margin: 2px 4px 2px 0;
padding: 0 10px;
border: 1px solid #eee;
border-radius: 3px;
color: #495060 !important;//字体颜色定义
font-size: 12px;
vertical-align: middle;//垂直居中对齐
opacity: 1;
overflow: hidden;//内容滑出同时隐藏
cursor: pointer;
//为后续用以区分是否展示或隐藏做准备的两个串联样式
&.is-active {
border-color: #409eff;
}
&.is-not-active {
border-color: #67C23A !important;
}
}
//下列两个纯粹优化展示格式,使得文字更显居中,同时关闭符号与内容存在一定间隙
.tag-text {
margin-left: 8px;
}
.tag-close {
margin-left: 8px;
}
//仅仅区别待选择时稍微隐藏一下,看起来好看点的样子
.tag-item:hover {
opacity: .5;
}
}
3、数据填充,展示初步效果
json数据:
[
{
"label": "标签一",
"type": ""
},
{
"label": "标签二",
"type": "success"
},
{
"label": "标签三",
"type": "info"
},
{
"label": "标签四",
"type": "danger"
},
{
"label": "标签五",
"type": "warning"
}
]
骨架填充:
//主要是增加了遍历数据而已
<div class="tags-container">
<div class="tags-box">
<div ref="tagsList" class="tags-list">
<div ref="tag" class="tag-item" v-for="item in items" >
<span class="tag-text">{{item.label}}</span>
<i class="el-icon-close tag-close"/>
</div>
</div>
</div>
</div>
临时效果:
③标签的滑动与隐藏(额外)
上面两步多出的样式,其实是为了这一步而存在的,标签总会长度到一定程度,会超出页面展示的范围,我们又想看到它,只能允许其滑动出视界外,同时能滑回来才是稳健的,合理的。
为此我们需要用到以下事件:
//鼠标四事件 ->电脑
1、mousewheel 监听转动鼠标滚轮
2、mouseup 释放按下的鼠标
3、mousemove 鼠标移动
4、mousedown 按下鼠标键
//触摸触发事件 -> 假装有手机用手指滑的样子
5、touchend 当用户手指从屏幕上离开时触发
6、touchmove 当用户手指在屏幕上移动时触发
7、touchstart 当用户手指放到屏幕上触发
其实mousewheel 触发的效果与 2、3、4 整合一起的效果及5、6、7的效果是一致的,为什么我要做出来呢,因为我脑抽了
成型后的骨架
<div class="tags-container">
<div class="tags-box" ref="tagBox">
<div ref="tagsList" class="tags-list"
@mousewheel="handleMouseWheel"
@mouseup="handleMouseUp"
@mousemove="handleMouse"
@mousedown="handleMouseStart"
@touchup="handleMouseUp"
@touchmove="handleMouse"
@touchstart="handleMouseStart">
</div>
<div ref="tag" class="tag-item" v-for="item in items" >
<span class="tag-text">{{item.label}}</span>
<i class="el-icon-close tag-close"/>
</div>
</div>
</div>
</div>
1、mousewheel
//mehtods:
handleMouseWheel(e) {
const step = 0.8 * 90; // 模拟一个tag长度,懒得一个个拿
const boundarystart = 0, //梦开始的地方
//外层包装减去后,给自己留100 余地
boundaryend = this.$refs.tagsList.offsetWidth - this.$refs.tagBox.offsetWidth + 100;
//最少给自己留大概的存在tag位置显示出来,当然这里可以自定义更好的数值
// Y>0向左滑动
if (e.deltaY > 0 && this.tagBodyLeft >= -boundaryend) {
this.tagBodyLeft = this.tagBodyLeft - step;
// Y<0向右滑动
} else if (e.deltaY < 0 && this.tagBodyLeft < boundarystart) {
this.tagBodyLeft = this.tagBodyLeft + step;
}
}
//data
tagBodyLeft:0 //用于保存最低限度
//watch
tagBodyLeft(value) { //给标签列表最小的宽度限定
this.$refs.tagsList.style.left = value + 'px'
}
2、handleMouseUp、handleMouse、handleMouseStart
//methods
handleMouseUp(e) {
this.lock = false;
},
handleMouse(e){
const boundarystart = 0,
boundaryend =
this.$refs.tagsList.offsetWidth - this.$refs.tagBox.offsetWidth + 100;
if (!this.lock) {
return;
}
//鼠标滑动
if (e.clientX && e.clientY) {
this.endX = e.clientX;
this.endY = e.clientY;
//触摸屏滑动
} else {
//获取滑动屏幕时的X,Y
this.endX = e.changedTouches[0].pageX;
this.endY = e.changedTouches[0].pageY;
}
//获取滑动距离
let distanceX = this.endX - this.startX;
//判断滑动方向——向右滑动
distanceX = parseInt(distanceX * 0.8);
if (distanceX > 0 && this.tagBodyLeft < boundarystart) {
this.tagBodyLeft = this.tagBodyLeft + distanceX;
//判断滑动方向——向左滑动
} else if (distanceX < 0 && this.tagBodyLeft >= -boundaryend) {
this.tagBodyLeft = this.tagBodyLeft + distanceX;
}
},
handleMouseStart(e){
this.lock = true;
//获取当前操作的位置
if (e.clientX && e.clientY) {
//针对鼠标
this.startX = e.clientX;
this.startY = e.clientY;
} else {
//针对手指,第一个点
this.startX = e.changedTouches[0].pageX;
this.startY = e.changedTouches[0].pageY;
}
},
结果: