在vue中通过flex布局实现css的s型结构
通过数组截取循环布局,奇数行从左到右,在偶数行从右到左实现s型结构
主要内容分为三部分
中间内容部分
数据格式
items: [
{
nodeList: [1, 2, 3, 4, 5, 6]
},
{
nodeList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}, {
nodeList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
},
{
nodeList: [1, 2, 3, 4]
}
],
<template>
<a-row class="row" id="row" :class="alignItem(index)" :style="styleRow(index)">
<template v-for="(ite,inde) in it">
<a-col :xxl="4" :xl="6" :lg="8" :md="12">
<div class="lineBg" :style="{height: lineHeight+'px'}"
:class="{
'lineBorderLeft':it.length<row && inde===0 && index!=0 && inde!=it.length-1 && index%2!=0 || (item.nodeList.length===row*index+1 && index % 2 !== 0),
'lineBorderRight':(it.length<row && inde==it.length-1 && index%2==0)}">
<div class="line-title">
<div class="boxGrey">{{ ite }}</div>
</div>
</div>
<template>
<div class="item">
<div>
row:{{ row }}<br/>
it:{{ it.length }}<br/>
index:{{ index }}<br/>
</div>
</div>
</template>
<!-- 半圆的时候设置占位 -->
<div v-if="!hasRightAngle" :style="{height: lineHeight+'px'}"></div>
</a-col>
</template>
</a-row>
</template>
computed:{
styleRow() {
return function (index) {
if (index > 0) {
return {
marginTop: !this.hasRightAngle ? `-${this.lineHeight}px` : 0
}
}
}
},
alignItem() {
return function (index) {
return index % 2 !== 0 ? 'justify-end' : ''
}
},
}
两端拼接的直接和半圆处理
linexx-round-r 和 linexx-round-l 代表拐角处的半圆处理
linexx-l和linexx-r两端的直角
可以切换为直角和半圆
Vue.component('linexx-round-r', {
template: `
<div class="linexx-body" :style="styleRoundRow(index)">
<div class="linexxrRound"
v-if="index%2==0 && index!=splitArrayByLength(item.nodeList,row).length-1">
<div class="innerxx-inner"></div>
</div>
<template v-else>
<div style="width: 130px" class="linexxr-box" v-if="index<=0 && it.length>=row"
:style="styleRoundRow(index)">
</div>
<div style="width: 130px" v-else :style="styleRoundRow(index)"></div>
</template>
</div>`,
props: ['it', 'item', 'index', 'row', "lineHeight"],
inject: ['splitArrayByLength'],
computed: {
styleRoundRow() {
return function (index) {
if (index > 0) {
return {
marginTop: `-${this.lineHeight}px`
}
}
}
},
}
})
Vue.component('linexx-round-l', {
template: `
<div class="linexx-body" :style="styleRoundRow(index)">
<div class="linexxlRound"
v-if="index%2!=0 && index!=splitArrayByLength(item.nodeList,row).length-1">
<div class="innelxx-inner"></div>
</div>
<template v-else>
<div style="width: 130px" class="linexxl-box" v-if="index<=0"
:style="styleRoundRow(index)"></div>
<div v-else style="width: 130px" :style="styleRoundRow(index)"></div>
</template>
</div>`,
props: ['it', 'item', 'index', 'row', 'lineHeight'],
inject: ['splitArrayByLength'],
computed: {
styleRoundRow() {
return function (index) {
if (index > 0) {
return {
marginTop: `-${this.lineHeight}px`
// marginTop: `-17px`
}
}
}
},
}
})
Vue.component('linexx-l', {
template: `
<div class="linexx-body">
<div class="linexxl linexxLeft"
v-if="index%2!=0 && index!=splitArrayByLength(item.nodeList,row).length-1">
</div>
<template v-else>
<div class="linexxl-box" v-if=" index%2==0 || it.length===row"></div>
<div v-else style="width: 25px"></div>
</template>
</div>`,
props: ['it', 'item', 'index', 'row'],
inject: ['splitArrayByLength'],
})
Vue.component('linexx-r', {
template: `
<div class="linexx-body">
<div class="linexxr linexxRight"
v-if="index%2==0 && index!=splitArrayByLength(item.nodeList,row).length-1">
</div>
<template v-else>
<div class="linexxr-box" v-if=" index%2!=0 || it.length===row" style="width: 25px"></div>
<div v-else style="width: 25px"></div>
</template>
</div>`,
props: ['it', 'item', 'index', 'row'],
inject: ['splitArrayByLength'],
})
顶点处理
判断有点复杂 应该还可以优化优化
<div class="lineBg" :style="{height: lineHeight+'px'}"
:class="{
'lineBorderLeft':it.length<row && inde===0 && index!=0 && inde!=it.length-1 && index%2!=0 || (item.nodeList.length===row*index+1 && index % 2 !== 0),
'lineBorderRight':(it.length<row && inde==it.length-1 && index%2==0)}">
<div class="line-title">
<div class="boxGrey">{{ ite }}</div>
</div>
</div>
完整代码
<div id="app">
<div class="container-bg">
<div class="container" :style="containerStyle">
<div class="container-list" v-for="(item,inx) in items">
<div class="row-title">
<div class="row-title-text">{{ inx }}</div>
</div>
<div style="width: 100%" :id="`row${inx}`">
<template v-for="(it,index) in splitArrayByLength(item.nodeList,row)">
<div style="width: 100%;position: relative;display: flex">
<template v-if="hasRightAngle">
<linexx-l :it="it" :item="item" :index="index"
:row="row"></linexx-l>
</template>
<template v-else>
<linexx-round-l :line-height="lineHeight" :it="it" :item="item" :index="index"
:row="row"></linexx-round-l>
</template>
<a-row class="row" id="row" :class="alignItem(index)" :style="styleRow(index)">
<template v-for="(ite,inde) in it">
<a-col :xxl="4" :xl="6" :lg="8" :md="12">
<div class="lineBg" :style="{height: lineHeight+'px'}"
:class="{
'lineBorderLeft':it.length<row && inde===0 && index!=0 && inde!=it.length-1 && index%2!=0 || (item.nodeList.length===row*index+1 && index % 2 !== 0),
'lineBorderRight':(it.length<row && inde==it.length-1 && index%2==0)}">
<div class="line-title">
<div class="boxGrey">{{ ite }}</div>
</div>
</div>
<template>
<div class="item">
<div>
row:{{ row }}<br/>
it:{{ it.length }}<br/>
index:{{ index }}<br/>
</div>
</div>
</template>
<!-- -->
<div v-if="!hasRightAngle" :style="{height: lineHeight+'px'}"></div>
</a-col>
</template>
</a-row>
<template v-if="hasRightAngle">
<linexx-r :it="it" :item="item" :index="index"
:row="row"></linexx-r>
</template>
<template v-else>
<linexx-round-r :line-height="lineHeight" :it="it" :item="item" :index="index"
:row="row"></linexx-round-r>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
js部分
new Vue({
el: '#app',
data() {
return {
hasRightAngle: false,
lineHeight: 25,
row: 6,
hasAcknowledgeAlarm: false,
items: [
{
nodeList: [1, 2, 3, 4, 5, 6]
},
{
nodeList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}, {
nodeList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
},
{
nodeList: [1, 2, 3, 4]
}
],
}
},
provide() {
return {
splitArrayByLength: this.splitArrayByLength,
styleRoundRow: this.styleRoundRow
}
},
computed: {
splitArrayByLength() {
return function (array, length) {
let result = []
for (let i = 0; i < array.length; i += length) {
let chunk = array.slice(i, i + length)
// 反转偶数位置的子数组
if ((i / length) % 2 === 1) {
chunk = chunk.reverse()
}
result.push(chunk)
}
// console.log(result)
return result
}
},
alignItem() {
return function (index) {
return index % 2 !== 0 ? 'justify-end' : ''
}
},
containerStyle() {
if (this.hasAcknowledgeAlarm) {
return {
width: `calc(100% - 280px)`,
marginRight: '10px'
}
} else {
return {
width: '100%',
marginRight: '0'
}
}
},
styleRow() {
return function (index) {
if (index > 0) {
return {
marginTop: !this.hasRightAngle ? `-${this.lineHeight}px` : 0
}
}
}
}
},
mounted() {
this.handleResize()
// 监听浏览器窗口
window.addEventListener('resize', this.handleResize);
},
methods: {
handleResize() {
const bp = window.innerWidth >= 1600 ? 'xxl' :
window.innerWidth >= 1200 ? 'xl' :
window.innerWidth >= 992 ? 'lg' :
window.innerWidth >= 768 ? 'md' : 'sm'
let emunRow = {
xxl: 4,
xl: 6,
lg: 8,
md: 12,
sm: 12
}
this.row = 24 / emunRow[bp]
console.log('handleResize', bp)
},
}
})
css部分
<style lang="less">
.container-bg {
background: #f2f2f2;
}
.container {
box-sizing: border-box;
padding: 20px;
width: 100%;
background: linear-gradient(to right, #e8e8e8 1px, transparent 1px),
linear-gradient(to bottom, #e8e8e8 1px, transparent 1px);
background-repeat: repeat;
background-size: 50px 50px;
overflow-y: auto;
}
.container-list {
display: flex;
//background: #f2f2f2;
}
.lineBg {
box-sizing: border-box;
height: 25px;
background: #bec8d1;
width: 100%;
border-top: 4px solid #ffffff;
border-bottom: 4px solid #ffffff;
cursor: pointer;
}
.boxGrey {
max-width: 80%;
position: relative;
top: -5px;
padding: 5px 15px;
//background-color: rgba(40, 140, 115, 0.1);
background-color: #768997;
color: #ffffff;
border-radius: 25px;
&::before {
content: "";
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-radius: 25px;
border: 1px solid #768997;
clip-path: polygon(calc(50% - 7px) 0, 50% 5px, calc(50% + 7px) 0, 100% 0, 100% 100%, 0 100%, 0 0);
}
&::after {
content: "";
position: absolute;
/*top: -5px;*/
bottom: -4px;
left: calc(50% - 5px);
width: 8px;
height: 8px;
/*transform: rotate(135deg);*/
transform: rotate(315deg);
//border: 1px solid #fc5454;
background-color: #768997;
clip-path: polygon(0 0, 0 100%, 100% 100%);
}
}
.lineBorderLeft {
border-left: 4px solid lawngreen;
}
.lineBorderRight {
border-right: 4px solid rebeccapurple;
}
.line-title {
display: flex;
justify-content: space-around;
}
.row-title {
align-self: flex-start;
width: 80px;
min-width: 80px;
background: #ffffff;
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.16);
text-align: center;
margin-right: 25px;
}
.row-title-text {
box-sizing: border-box;
padding: 5px 0;
}
.row-title-num {
background: #fc5454;
color: #ffffff;
box-sizing: border-box;
padding: 3px 0;
}
.row {
display: flex;
flex-wrap: wrap;
width: calc(100% - 260px);
margin-bottom: 10px;
position: relative;
}
.justify-end {
justify-content: end;
}
.item {
box-sizing: border-box;
text-align: center;
min-height: 63px;
color: #333333;
box-sizing: border-box;
padding: 10px 0;
margin: 5px 30px;
box-shadow: 0px 0px 16px rgba(0, 0, 0, 0.08);
cursor: pointer;
}
.value {
margin-bottom: 8px;
}
.bgGrey {
background: #f0f0f0;
}
.itemBorder {
border: 1px solid #666666;
}
.setWidth {
width: 50%
}
.linexxlRound {
display: flex;
justify-content: end;
align-items: center;
box-sizing: border-box;
background: #bec8d1;
//background: antiquewhite;
width: 130px;
height: 100%;
border-radius: 200px 0 0 200px;
border: 4px solid #ffffff;
border-right: none;
}
.innelxx-inner {
height: calc(100% - 34px);
box-sizing: border-box;
width: 105px;
background: #f2f2f2;
//background: green;
border-radius: 200px 0 0 200px;
border: 4px solid #ffffff;
border-right: none;
}
.linexxrRound {
box-sizing: border-box;
background: #bec8d1;
//background: antiquewhite;
width: 130px;
height: 100%;
display: flex;
justify-content: start;
align-items: center;
border-radius: 0 200px 200px 0;
border: 4px solid #ffffff;
border-left: none;
.innerxx-inner {
height: calc(100% - 34px);
box-sizing: border-box;
width: 105px;
background: #F2F2F2;
//background: green;
border-radius: 0 200px 200px 0;
border: 4px solid #ffffff;
border-left: none;
}
}
.linexxr {
box-sizing: border-box;
width: 25px;
height: 100%;
background: #bec8d1;
border-top: 4px solid #ffffff;
border-bottom: none;
border-left: none;
position: relative;
&::after {
content: "";
position: absolute;
top: 17px;
left: 0;
width: 4px;
height: calc(100% - 17px);
background: #ffffff;
}
&::before {
content: "";
position: absolute;
left: 0px;
bottom: -4px;
width: 100%;
height: 4px;
background: #bec8d1;
z-index: 1;
//border-right: 4px solid #ffffff;
box-sizing: border-box;
border-left: 4px solid #ffffff;
}
}
.linexxr-box {
width: 25px;
height: 25px;
background: #bec8d1;
//background: red;
border: 4px solid #ffffff;
border-left: none;
}
.linexxl {
box-sizing: border-box;
width: 25px;
height: 100%;
background: #bec8d1;
border-top: 4px solid #ffffff;
border-bottom: none;
border-left: none;
position: relative;
&::after {
content: "";
position: absolute;
top: 17px;
right: 0;
width: 4px;
height: calc(100% - 17px);
background: #ffffff;
}
&::before {
content: "";
position: absolute;
right: 0;
bottom: -4px;
width: 100%;
height: 4px;
background: #bec8d1;
z-index: 1;
border-right: 4px solid #ffffff;
box-sizing: border-box;
//border-left: 4px solid #ffffff;
}
}
.linexxl-box {
width: 25px;
height: 25px;
box-sizing: border-box;
background: #bec8d1;
//background: red;
border: 4px solid #ffffff;
border-right: none;
}
.linexxRight {
border-right: 4px solid #ffffff;
}
.linexxLeft {
border-left: 4px solid #ffffff;
border-bottom: none;
}
</style>