效果展示

源码
<template>
<div class="container">
<div class="search"></div>
<div class="content">
<div class="left">
<div class="info">
<div class="layout-list" v-for="item in layoutList" :key="item.id">
<div v-if="item.type === 'column'">
<div class="layout-item" v-for="child in item.children" :key="child.id">
<div class="code-box">
<div class="code-number">{{ child.code }}</div>
</div>
</div>
</div>
<div v-else class="aisle">
<div class="aisle-line"></div>
<div class="aisle-middle">
<div>
<div class="aisle-reat" v-for="r in 3" :key="r"></div>
<div class="aisle-id">{{ item.id }}</div>
<div class="aisle-reat" v-for="r in 3" :key="r"></div>
</div>
</div>
<div class="aisle-line transform-rotate"></div>
</div>
</div>
<div class="axis-x">
<div class="x" v-for="(_, index) in layoutList" :key="index">
{{ index + 1 }}
</div>
</div>
<div class="axis-y">
<div class="y" v-for="(_, index) in y" :key="index">
{{ index + 1 }}
</div>
</div>
<div class="axis-label">
<div class="row">排</div>
<div class="split"></div>
<div class="col">列</div>
</div>
</div>
<div class="tips">
<a-space>
<span class="bg-green btn">已设置</span>
<span class="bg-gray btn">未设置</span>
<span>
库位合计:68个,已使用:30个,未使用:38个
</span>
</a-space>
</div>
</div>
<div class="right">
<div style="height: 100%; width: 100%">
<sv-table style="height: 100%" :data="tableData" ref="tableRef">
<sv-column title="排名" field="sequence"></sv-column>
<sv-column title="货主" field="owner"></sv-column>
<sv-column title="物料" field="material"></sv-column>
<sv-column title="描述" field="description"></sv-column>
<sv-column title="类别" field="category"></sv-column>
<sv-column title="ABC" field="abc"></sv-column>
<sv-column title="出库频次" field="frequency"></sv-column>
<sv-column title="拣货堆叠层级" field="stack"></sv-column>
</sv-table>
</div>
</div>
</div>
</div>
</template>
<script>
import Sortable from 'sortablejs'
export default {
name: 'layoutTable',
data() {
return {
layoutList: [],
tableData: []
}
},
computed: {
y() {
return this.layoutList[0]?.children || []
}
},
mounted() {
this.fetchTableData(10)
this.generateLayout(5, 5, [1])
this.$nextTick(() => {
this.initSortable()
})
},
methods: {
initSortable() {
const tbody = this.$refs.tableRef.$el.querySelector('tbody')
Sortable.create(tbody, {
animation: 150,
ghostClass: 'ghost',
draggable: '.vxe-body--row'
})
},
fetchTableData(num) {
const tableDataArray = []
for (let i = 0; i < num; i++) {
tableDataArray.push({
sequence: i + 1,
owner: '货主' + i,
material: '物料' + i,
description: '描述' + i,
category: '类别' + i,
abc: 'ABC' + i,
frequency: '出库频次' + i,
stack: '拣货堆叠层级' + i
})
}
this.tableData = tableDataArray
},
generateLayout(row, col, aislePositions) {
const layout = []
let code = 0
for (let i = 0; i < col; i++) {
const columnObj = {
id: i,
name: '0' + i,
type: 'column',
children: []
}
for (let j = 0; j < row; j++) {
code++
columnObj.children.push({
id: `${i}-${j}`,
code
})
}
layout.push(columnObj)
}
for (let i = 0; i < layout.length; i++) {
const item = layout[i]
if (item.type === 'column') {
item.children.sort((a, b) => {
return i % 2 === 0 ? a.code - b.code : b.code - a.code
})
}
}
if (aislePositions && aislePositions.length) {
aislePositions.forEach((index) => {
layout.splice(index, 0, {
id: `aisle-${index}`,
type: 'aisle',
children: []
})
})
}
this.layoutList = layout
}
}
}
</script>
<style scoped lang="less">
@base-width: 160px;
@base-height: 96px;
@base-gap: 10px;
@w: 50px;
@h: 50px;
.container {
padding: @base-gap;
.content {
display: flex;
.left {
flex: 1;
margin-right: @base-gap;
position: relative;
padding-top: @w;
padding-left: @w;
.axis-x,
.axis-y,
.axis-label {
position: absolute;
top: 0;
left: 0;
}
.axis-x {
display: flex;
left: @w;
top: calc(@h - 20px);
border-bottom: 1px solid #ccc;
padding-left: 10px;
.x {
width: @base-width;
text-align: center;
margin-right: 20px;
}
}
.axis-y {
top: @h;
left: calc(@w - 20px);
border-right: 1px solid #ccc;
padding-right: 10px;
padding-top: 10px;
width: 20px;
.y {
height: @base-height;
width: 20px;
margin-bottom: @base-gap;
line-height: @base-height;
}
}
.axis-label {
width: @w;
height: @h;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
top: 2px;
left: 0;
.row,
.col,
.split {
position: absolute;
}
.row {
right: 2px;
top: 2px;
}
.col {
bottom: 2px;
left: 2px;
}
.split {
top: 0;
right: 0;
width: 1px;
height: 100%;
background: #ccc;
transform-origin: bottom right;
transform: rotate(-45deg);
}
}
.info {
display: flex;
}
.layout-list {
padding: @base-gap;
.layout-item {
width: @base-width;
height: @base-height;
border: 1px solid #ccc;
margin-bottom: @base-gap;
background: #bebebe;
border-radius: 10px;
}
.code-box {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
}
.code-number {
font-size: 12px;
}
}
.aisle {
display: flex;
justify-content: space-evenly;
align-items: center;
height: 100%;
width: @base-width;
border-radius: 10px;
background-color: #efefef;
border: 1px solid #bbbbbb;
.aisle-line {
height: 80%;
width: 1px;
background: red;
position: relative;
&::after {
position: absolute;
content: '';
top: 0;
left: -4px;
width: 10px;
height: 10px;
border-radius: 50%;
background: red;
}
&::before {
position: absolute;
content: '';
bottom: -12px;
left: -10px;
border: 10px solid transparent;
border-top-color: red;
}
}
.transform-rotate {
transform: rotate(180deg);
}
.aisle-reat {
width: 20px;
height: 40px;
border: 1px solid #ccc;
margin: 0 auto;
margin-bottom: @base-gap;
}
.aisle-middle {
display: flex;
align-items: center;
justify-content: center;
margin: 0 4px;
}
.aisle-id {
font-size: 14px;
}
}
}
.right {
width: 600px;
border: 1px solid #ccc;
}
}
.tips {
.btn {
padding: 4px 10px;
color: #fff;
}
}
.bg-gray {
background-color: #bbbbbb;
}
.bg-green {
background-color: #73d32e;
}
.bg-yellow {
background-color: #FCCA00;
}
.bg-blue {
background-color: #2A87FF;
}
}
</style>