效果图
完整代码
<template><div><div class="container" :style="{ width: `${spacingX * (colNum - 1) + itemWidth * colNum}px` }"><divv-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.lengththis.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.listLenconst remainder = (arrLen - 1) % dbnif (remainder >= n && remainder < dbn) {const diff = dbn - 1 - remainderfor (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>