手写顺序流程图组件
效果图
完整代码
<template>
<div>
<div class="container" :style="{ width: `${spacingX * (colNum - 1) + itemWidth * colNum}px` }">
<div
v-for="(item, i) in recordList"
:key="i"
class="list-box"
:style="{
marginTop: i < colNum ? '0' : `${spacingY}px`,
marginRight: i % (2 * colNum) === colNum - 1 || i % (2 * colNum) === colNum ? '0' : `${spacingX}px`,
order: orderList[i] && orderList[i].order,
visibility: orderList[i] && orderList[i].itemHide ? 'hidden' : 'visible'
}"
>
<div class="cont-box" :style="{ width: itemWidth + 'px', height: itemHeight + 'px', backgroundColor: '#16a085' }">{{ item }}</div>
<div v-if="i !== listLen - 1" class="arrow-box" :style="arrowStyle[orderList[i] && orderList[i].arrow]">
<div class="line-tip" />
<div class="arrow-tip" />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'FlowPath',
data() {
return {
itemWidth: 75, // item宽度
itemHeight: 75, // item高度
colNum: 1, // 显示的列数
spacingX: 40, // 列间距
spacingY: 40, // 行间距
rawList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], // 原始数据
recordList: [], // 列表数据
arrowStyle: { right: {}, down: {}, left: {}}, // 箭头样式
orderList: [], // 列表排序序号
listLen: '' // 列表数据长度
}
},
mounted() {
this.listLen = this.rawList.length
this.initFun() // 初始化方法
window.addEventListener('resize', this.initFun) // 页面宽度变化监听器
},
beforeDestroy() {
window.removeEventListener('resize', this.initFun) // 组件销毁时移除 resize 事件监听器,避免内存泄漏
},
methods: {
/* 初始化方法 */
initFun() {
const pageWidth = document.documentElement.clientWidth // 获取页面宽度(可视区域宽度)
const minTotalWidth = this.itemWidth + this.spacingX // 每个 item(包含间距) 期望的最小总宽度为 minTotalWidth(单位:px)
const newNum = Math.floor(pageWidth / minTotalWidth) // 计算 colNum,向下取整
this.colNum = Math.max(newNum, 1) // 限制 colNum 的最小值,比如至少为 1 列
// 更新箭头样式和列表样式,因为 colNum 变化了,相关布局依赖 colNum 列数
this.setArrowStyle() // 设置箭头样式
this.setOrderList() // 设置列表样式
},
/* 设置箭头样式 */
setArrowStyle() {
const left = {
width: this.spacingX + 'px',
top: this.itemHeight / 2 + 'px',
left: -this.spacingX + 'px'
}
const right = {
width: this.spacingX + 'px',
top: this.itemHeight / 2 + 'px',
right: -this.spacingX + 'px',
transform: 'rotate(180deg)'
}
const down = {
width: this.spacingY + 'px',
left: this.itemWidth / 2 + 'px',
bottom: -this.spacingY + 'px',
transform: 'rotate(-90deg)',
transformOrigin: 0
}
this.arrowStyle = { right, left, down }
},
/* 设置列表样式 */
setOrderList() {
this.recordList = JSON.parse(JSON.stringify(this.rawList))
this.orderList = [] // 列表排序序号
const n = this.colNum // 显示的列数
const dbn = n * 2 // 列数 * 2
// 添加占位的 item 项
const arrLen = this.listLen
const remainder = (arrLen - 1) % dbn
if (remainder >= n && remainder < dbn) {
const diff = dbn - 1 - remainder
for (let i = 0; i < diff; i++) {
this.orderList[arrLen + i] = {
itemHide: true,
order: arrLen + i
}
this.recordList[arrLen + i] = null
}
}
// 设置 item 的箭头方向和顺序
this.recordList.map((item, index) => {
const i = index % dbn // 余数
if (i >= 0 && i < n) {
this.orderList[index] = {
order: index,
arrow: i !== n - 1 ? 'right' : 'down'
} // 不用改变顺序
} else {
this.orderList[index] = {
order: index + ((n - 1) - 2 * (i - n)), // i - n 是与最近一侧的距离
arrow: i !== dbn - 1 ? 'left' : 'down',
itemHide: this.orderList[index]?.itemHide
} // 需要改变顺序
}
})
}
}
}
</script>
<style scoped lang="scss">
.container {
display: flex;
flex-wrap: wrap;
box-sizing: border-box;
overflow: hidden;
.list-box {
position: relative;
font-size: 20px;
box-sizing: border-box;
.cont-box {
}
}
}
/* 箭头区域 */
.arrow-box {
$bgColor: #303133;
position: absolute;
// 线条样式
.line-tip {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 3px;
width: 90%;
height: 2px;
background-color: $bgColor;
}
// 箭头样式
.arrow-tip {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 1px;
width: 0;
height: 0;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 8px solid $bgColor;
}
}
</style>