首先, 我先来看看效果
steps 组件的封装和 tabs 组件还是相似的
都会去指定两个组件来完成(仿Element UI), 都会去使用 jsx 的语法
让其中一个组件去规定样式和排版, 另外一个组件去接收父组件传入的动态数据
但和面包屑组件还是有区别的(面包屑组件封装):
相同点都是使用两个组件来完成(一个指定排版, 另外一个指定内容), 都是使用 render 函数来进行渲染
不同点是, bread 组件使用的是 h 函数来创建虚拟节点; 然后使用 render 来进行渲染
而 tabs 和 steps 使用的是 jsx 语法来指定标签(使用 jsx 的原因是, 需要指定两个以上的标签且有嵌套关系, 如果使用 h 函数的话太麻烦了)
第一步:
1. 创建 steps 和 steps-item 组件
2. steps-item 组件接收 title 和 desc 数据
<template>
<div class="steps">
<div class="steps-item active" v-for="i in 5" :key="i">
<div class="step"><span>{{i}}</span></div>
<div class="title">提交订单</div>
<div class="desc">2021-03-18 02:11:47</div>
</div>
</div>
</template>
<script>
export default {
name: 'Steps'
}
</script>
<style lang="less">
.steps {
display: flex;
text-align: center;
&-item {
flex: 1;
&:first-child {
.step {
&::before {
display: none;
}
}
}
&:last-child {
.step {
&::after {
display: none;
}
}
}
&.active {
.step {
> span {
border-color: @xtxColor;
background: @xtxColor;
color: #fff
}
&::before,&::after {
background: @xtxColor;
}
}
.title {
color: @xtxColor;
}
}
.step {
position: relative;
> span {
width: 48px;
height: 48px;
font-size: 28px;
border: 2px solid #e4e4e4;
background: #fff;
border-radius: 50%;
line-height: 44px;
color: #ccc;
display: inline-block;
position: relative;
z-index: 1;
}
&::after,&::before{
content: "";
position: absolute;
top: 23px;
width: 50%;
height: 2px;
background: #e4e4e4;
}
&::before {
left: 0;
}
&::after {
right: 0;
}
}
.title {
color: #999;
padding-top: 12px;
}
.desc {
font-size: 12px;
color: #999;
padding-top: 6px;
}
}
}
</style>
steps-item 主要是去接收外部传入的 title 和 desc 的动态数据, 然后将这些数据直接给到 steps 来进行出来
所以不需要使用 <slot />
<script>
export default {
name: 'StepsItem',
props: {
title: {
type: String,
default: ''
},
desc: {
type: String,
default: ''
}
}
}
</script>
第二步:
1. 显示 steps 组件数据
也就是在 steps 组件中接收到一个 active 的数据(父组件发送请求获取过来的节点数据)
首先需要拿到所有的 steps-item 组件数据(通过 $slots.default)
然后, 判断 $slots.default 返回的数据中对象中的 type 数据是否为代码片段(这里的详细讲解在 tabs 组件封装时讲过了(tabs 组件封装)
然后指定显示内容
<script>
import { getCurrentInstance } from 'vue'
export default {
name: 'Steps',
render () {
// 这是在proxy的作用相当于vue2中的this
// 这里其实也可以直接使用this, 因为不是在setup中; 都无所谓的
const { proxy } = getCurrentInstance()
const items = proxy.$slots.default()
const dynamicItems = []
items.forEach(item => {
if (item.type.name === 'StepsItem') {
dynamicItems.push(item)
} else {
item.children.forEach(c => {
dynamicItems.push(c)
})
}
})
const itemsJsx = dynamicItems.map((item, i) => {
return <div class="steps-item">
<div class="step"><span>{i + 1}</span></div>
<div class="title">{item.props.title}</div>
<div class="desc">{item.props.desc}</div>
</div>
})
return <div class="steps">{itemsJsx}</div>
}
}
</script>
<style lang="less">
.steps {
display: flex;
text-align: center;
&-item {
flex: 1;
&:first-child {
.step {
&::before {
display: none;
}
}
}
&:last-child {
.step {
&::after {
display: none;
}
}
}
&.active {
.step {
> span {
border-color: @xtxColor;
background: @xtxColor;
color: #fff
}
&::before,&::after {
background: @xtxColor;
}
}
.title {
color: @xtxColor;
}
}
.step {
position: relative;
> span {
width: 48px;
height: 48px;
font-size: 28px;
border: 2px solid #e4e4e4;
background: #fff;
border-radius: 50%;
line-height: 44px;
color: #ccc;
display: inline-block;
position: relative;
z-index: 1;
}
&::after,&::before{
content: "";
position: absolute;
top: 23px;
width: 50%;
height: 2px;
background: #e4e4e4;
}
&::before {
left: 0;
}
&::after {
right: 0;
}
}
.title {
color: #999;
padding-top: 12px;
}
.desc {
font-size: 12px;
color: #999;
padding-top: 6px;
}
}
}
</style>
第三步:
1. 在 steps 组件中接收 active 数据, 然后根据 active 数据动态显示 steps 的节点
<script>
import { getCurrentInstance } from 'vue'
export default {
name: 'Steps',
props: {
active: {
type: Number,
default: 1
}
},
render () {
// 这是在proxy的作用相当于vue2中的this
// 这里其实也可以直接使用this, 因为不是在setup中; 都无所谓的
const { proxy } = getCurrentInstance()
const items = proxy.$slots.default()
const dynamicItems = []
items.forEach(item => {
if (item.type.name === 'StepsItem') {
dynamicItems.push(item)
} else {
item.children.forEach(c => {
dynamicItems.push(c)
})
}
})
const itemsJsx = dynamicItems.map((item, i) => {
// 只要是下标值i小于active的div标签都会添加上active类
return <div class="xtx-steps-item" class={{ active: i < props.active }}>
<div class="step"><span>{i + 1}</span></div>
<div class="title">{item.props.title}</div>
<div class="desc">{item.props.desc}</div>
</div>
})
return <div class="steps">{itemsJsx}</div>
}
}
</script>
<style lang="less">
.steps {
display: flex;
text-align: center;
&-item {
flex: 1;
&:first-child {
.step {
&::before {
display: none;
}
}
}
&:last-child {
.step {
&::after {
display: none;
}
}
}
&.active {
.step {
> span {
border-color: @xtxColor;
background: @xtxColor;
color: #fff
}
&::before,&::after {
background: @xtxColor;
}
}
.title {
color: @xtxColor;
}
}
.step {
position: relative;
> span {
width: 48px;
height: 48px;
font-size: 28px;
border: 2px solid #e4e4e4;
background: #fff;
border-radius: 50%;
line-height: 44px;
color: #ccc;
display: inline-block;
position: relative;
z-index: 1;
}
&::after,&::before{
content: "";
position: absolute;
top: 23px;
width: 50%;
height: 2px;
background: #e4e4e4;
}
&::before {
left: 0;
}
&::after {
right: 0;
}
}
.title {
color: #999;
padding-top: 12px;
}
.desc {
font-size: 12px;
color: #999;
padding-top: 6px;
}
}
}
</style>
父组件中使用情况
根据后端的数据来决定 steps 的节点数据
<template>
<div class="detail-steps">
<Steps :active="order.orderState===6 ? 1: order.orderState">
<StepsItem title="提交订单" :desc="order.createTime" />
<StepsItem title="付款成功" :desc="order.payTime" />
<StepsItem title="商品发货" :desc="order.consignTime" />
<StepsItem title="确认收货" :desc="order.evaluationTime" />
<StepsItem title="订单完成" :desc="order.endTime" />
</Steps>
</div>
</template>