步骤条的实现
开始之前
今天公司的一个项目里面有一个步骤条要实现,这个项目前端是基于 Vue 和 ElementUI开发。饿了么UI里面是有步骤条(step)这个组件的,之前的一个项目是有用过这个东西,当时是基于饿了么的那个组件封装了一层,做那个的时候也挺费劲的。今天这个组件还复杂一些,再去封装感觉还要费很长时间,衡量了一下还是自己写一个来的比较直接,以后自己修改也方便一些。
代码开撸
开撸之前先放一张项目里步骤条的图看看长什么样子。
步骤条大致就是这个样子里,上面的标记大家可以忽略,上面是用一个前端开发辅助软件标注的,我会在末尾的时候告诉大家这个软件。
步骤条的功能大家都知到,比如说一个审批流程,创建人发起,然后到直属领导审批,直属领导审批完成了直属领导的节点就会由白色变成一个蓝色,连接创建人和直属领导之间的连线也会由虚线变成实线,后面的节点依次类推,步骤条大致就是这些功能了。
因为不同项目对与生成步骤条的接口的数据结构不一样,所以先不考虑通过拿到后台接口的数据去渲染步骤条,咱们先放一张静态的页面看看怎么去实现。
<ul class="steps">
<li class="active">
<el-row class="text">
<el-col :span="12">步骤一</el-col>
<el-col :span="12">
<div>
<el-avatar size="small" :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
<div>
<el-avatar size="small" :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
<div>
<el-avatar size="small" :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
</el-col>
</el-row>
</li>
<li class="active">
<el-row class="text">
<el-col :span="12">步骤一</el-col>
<el-col :span="12">
<div>
<el-avatar :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
</el-col>
</el-row>
</li>
<li class="">
<el-row class="text">
<el-col :span="12">步骤三</el-col>
<el-col :span="12">
<div>
<el-avatar size="small" :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
</el-col>
</el-row>
</li>
<li class="">
<el-row class="text">
<el-col :span="12">步骤二</el-col>
<el-col :span="12">
<div>
<el-avatar size="small" :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
</el-col>
</el-row>
</li>
</ul>
通过这个DOM树的结构相信做过前端的朋友应该大致猜到了要实现我们项目的要求应该如何控制渲染。步骤条节点数据必定要有一个表示状态的标志,我们通过这个标志来判断是否该给 li 节点添加 active 这个类,达到点亮节点的效果。而节点数据的其他部分就是负责渲染步骤条下面的信息。我们的项目中有一个➕,这个就是触发一个事件,至于什么样的事件,咱们在里面添加一个图标,然后再在图标上放上一个 click 事件,根据业务需求执行相关操作就可以了。
那么我们怎么去实现节点点亮和连线的效果呢?那当然还是靠 CSS 样式去渲染它。那么我们把 CSS 的代码放上来。
<style lang="scss" scoped>
.steps{
position: relative;
list-style: none;
text-align: center;
counter-reset: step;
}
.steps li{
float: left;
display: inline-block;
width: 20%;
text-align: center;
height: auto;
position: relative;
}
/* 控制节点那个圆圈的形成 */
.steps li:before{
counter-increment: step;
content: counter(step);
display: block;
height: 20px;
width: 20px;
font-size:0;
border-radius: 20px;
border:1px solid #6d99ff;
background-color: #fff;
margin: 0 auto;
line-height: 20px;
text-align: center;
margin-bottom: 10px;
}
/* 控制节点之间的连线 */
.steps li ~ li:after{
content: "";
width: 100%;
border: 1px dotted #8db0ff;
position: absolute;
top:10px;
left: calc(-40% - 25px);
z-index: -1;
}
/* 控制节点点亮的效果渲染 */
.steps li.active:before{
background-color: #1a5fff;
box-shadow:0 0 0 2px #bacfff;
}
.steps li.active:after{
border: 1px solid #8db0ff
}
/* 控制节点下面的信息样式渲染 */
.steps .text{
position: relative;
}
.steps .text .el-col, .steps .text .el-col div{
height: 28px !important;
}
.steps .text .el-col{
position: relative;
left: -14px;
}
.steps .text .el-col:first-child{
text-align: right;
height: 28px;
line-height: 28px;
padding-right: 10px;
}
.steps .text .el-col:last-child {
& > div {
margin-bottom: 10px;
& > span {
float: left;
&:last-child {
height: 28px;
line-height: 28px;
padding-left: 10px;
}
}
}
}
</style>
关于步骤条这块其实主要还是样式的问题。写过前端的一看这些代码肯定是都了解,但是如果有没做过前端或者对前端还没那么熟悉的不知什么机缘巧合看到了这篇日记(表示自己很幸运,感谢)我班门弄斧的稍微说一我当时的思路,ul li 的那一部分是前端常见清除原生样式,自己手写过菜单生成代码的朋友对这块应该很熟悉。这一部分就是通过 list-style清除无序列表前面的那个小黑点,然后将 li 做成行内块级元素,加一个浮动,使原来的竖排列变成行排列。
然后生成节点的时候我们使用 :before 伪元素,这个属性的作用是在目标元素的内容前面插入一个虚拟的元素,它其实不是真实存在的,不会出现DOM中,不会改变文档内容,不可复制,仅仅是在CSS渲染出加入。所以它经常用做修饰行的内容,比如说添加个图标什么的。它必须通过content属性来添加内容,然后根据设置不同元素形式(块级,行内元素等),来设置其它的属性。这个地方我设置了 content ,然后将这个伪类设置成一个块级元素,设置宽高。对于怎么成一个圆形哪就是 border-radius。我这里设置的是相同的宽高,是一个正方形,我们做成一个圆只要将 broder-radius 设置成超过正方形边长的一半即可。现在说说这个content值的设置,其实相关的counter-reset 和 counter-increment 这两个属性我平时很少用到,上次做步骤条看 element-ui 样式表的时候发现了这两个属性。这两个属性的作用创建计数器和递增计数器,常用来排序替代用有序表来排序。咱们这里其实是用来生成节点的 1 2 3 4 数字,这样如果要显示这些数字的我们就不用程序去控制了。最后面我会放上页面的最终渲染效果,发现没有显示这些数字是因为我在生成圆圈那段CSS代码中把font-size设置成了0,大家注掉会发现里面的数字。
其他地方需要需要注意的属性是 box-shadow,生成盒子的阴影,我们的很多项目都会带有阴影效果(我也不知道为什么😂),我会经常用到,要在周围形成一圈阴影,大家只要把这个属性前三个属性都设置为0 ,第四个属性设置一下想要阴影的大小,然后给个颜色就可以了。后面的连线就是使用 :after 位元素,生成一个没有宽高,只有边框的。宽度为整个父级宽度一跟线,它其实是连接两个圆心的。连线这里有一个算是比较巧妙的地方,就是你看到我的代码里设置 :after 伪类的时候我是从第二个 li 元素开始的, 就是 li ~ li:after
这个代码,如果我们不这样设置,当我们给第二个 li
元素添加 active 类名时,节点后面的线也会点亮,而且最后一个节点还会有一个尾巴,那就不是一个步骤条正确的效果。既然从第二条开始那怎么连接第一个节点呢,就是改变定位方式,然后加一个偏移量就搞定了。因为父级li
元素是一个相对定位,我们这个伪元素设置成绝对定位时参照的是相对父级的绝对定位。
关于下面信息的展示,就没啥可以说的了,借助element的栅格,然后计算一下位置的偏移量,最终到达我们想要的效果就可以了。下面例子静态页面想象了最多有5个节点,所以每个li
元素宽度是20%,如果需要更多的节点就需要动态调整这个宽度。下面放上最终的效果图:
写在最后
这篇日记写完,发现跟我开发这个组件用的时间几乎差不多。再写一遍这个思路的时候脑子会重现出当时写出来的BUG,然后对自己不了解的一些属性会重新巩固一遍。上面写的不一定很好,朋友们如果发现不合适的地方还请在下面留言,先谢谢大家的批评指正了。
最后说一下开始项目截图里面用的软件,是一个叫 PxCook 的软件,它分为设计和开发模式,只要我们把高保真图放进去就可以测量出每个区域的宽度,取色也很方便,会生成一些代码辅助开发。最后的最后附上这个实例的全部代码。
<template>
<ul class="steps">
<li class="active">
<el-row class="text">
<el-col :span="12">步骤一</el-col>
<el-col :span="12">
<div>
<el-avatar size="small" :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
<div>
<el-avatar size="small" :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
<div>
<el-avatar size="small" :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
</el-col>
</el-row>
</li>
<li class="active">
<el-row class="text">
<el-col :span="12">步骤一</el-col>
<el-col :span="12">
<div>
<el-avatar size="small" :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
</el-col>
</el-row>
</li>
<li class="">
<el-row class="text">
<el-col :span="12">步骤三</el-col>
<el-col :span="12">
<div>
<el-avatar size="small" :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
</el-col>
</el-row>
</li>
<li class="">
<el-row class="text">
<el-col :span="12">步骤二</el-col>
<el-col :span="12">
<div>
<el-avatar size="small" :src="imgUrl"></el-avatar>
<span>部门经理</span>
</div>
</el-col>
</el-row>
</li>
</ul>
</template>
<script>
export default {
name: "Steps",
data(){
return {
imgUrl: 'https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png'
}
}
}
</script>
<style lang="scss" scoped>
.steps{
position: relative;
list-style: none;
text-align: center;
counter-reset: step;
}
.steps li{
float: left;
display: inline-block;
width: 20%;
text-align: center;
height: auto;
position: relative;
}
.steps li:before{
counter-increment: step;
content: counter(step);
display: block;
height: 20px;
width: 20px;
font-size:0;
border-radius: 20px;
border:1px solid #6d99ff;
background-color: #fff;
margin: 0 auto;
line-height: 20px;
text-align: center;
margin-bottom: 10px;
}
.steps li ~ li:after{
content: "";
width: 100%;
border: 1px dotted #8db0ff;
position: absolute;
top:10px;
left: calc(-40% - 25px);
z-index: -1;
}
.steps li.active:before{
background-color: #1a5fff;
box-shadow:0 0 0 2px #bacfff;
}
.steps li.active:after{
border: 1px solid #8db0ff
}
.steps .text{
position: relative;
}
.steps .text .el-col, .steps .text .el-col div{
height: 28px !important;
}
.steps .text .el-col{
position: relative;
left: -14px;
}
.steps .text .el-col:first-child{
text-align: right;
height: 28px;
line-height: 28px;
padding-right: 10px;
}
.steps .text .el-col:last-child {
& > div {
margin-bottom: 10px;
& > span {
float: left;
&:last-child {
height: 28px;
line-height: 28px;
padding-left: 10px;
}
}
}
}
</style>