一、使用场景
在业务中经常会遇到一些库房储物柜需求,这种需求中,我们可能会维护多个柜子,如果我们遇到的柜子是正常的3x3 、4x4这种,那么需求是简单的,但是在现实生活中,我们可能遇到的柜子是存在着部分格子占据了好几个小格子的位置,这种情况下正常的table将不是很容易满足需求。
二、效果展示
设计器展示
1.通过行列快速设计格子的形状
2.根据现实需求对单元格进项合并
3.合并之后,柜子效果
4.对合并的单元格进进行拆分,还原柜格子原本的样子
柜子渲染展示
本组件将提供渲染方法,页面可根据自身需求,可以自行设计成任意的风格 本例子只作为展示
三、设计原理
前端在设计完成格子的基本结构后,组件会生成一个结构的json结构将这个json以字符串的形式传输给后台数据库,用来作为柜子的基本结构,每个柜子的格子都有自己的唯一id,用来绑定每个格子的数据,真是的格子里的物品数据,将会通过组件方法自动绑定到各个格子中
四、设计器代码
CabinetLatticeSet.vue 设计器的主要代码
<template>
<div class="cabinet_latte_contain">
<div class="cabinet_latte_title">柜子设计器</div>
<table
border="1"
id="cabinet_latte_table"
align="center"
width="100%"
cellpadding="20"
cellspacing="0"
:key="renderTable"
>
<tr v-for="(row, index) in tableData" :key="index" :id="row.id">
<td
@mousedown="handleMouseDown"
@mouseup="handleMouseUp"
v-for="(col, j) in row.col"
:key="j"
:id="col.id"
:rowspan="col.rowspan"
:colspan="col.colspan"
:initRow="col.initRow"
:initCol="col.initCol"
:col="JSON.stringify(col)"
v-show="!col.isMerge"
@click="cellClick(col)"
@contextmenu="openContextMenu($event)"
>
<div class="cell_info">
<slot name="cellCard" :cell="col.cell" :tableData="tableData"></slot>
</div>
</td>
</tr>
</table>
<ContextMenuTool
ref="contextMenuTool"
@mergeCell="mergeCell"
@splitCell="splitCell"
@delRow="delRow"
@delCol="delCol"
></ContextMenuTool>
</div>
</template>
<script>
import guid from '@/utils/generator';
import ContextMenuTool from './ContextMenuTool.vue';
const STATUS = {
CREATE: 0,
UPDATE: 1,
DETAIL: 2,
};
export default {
components: {
ContextMenuTool,
},
props: {
setConfig: {
type: Object,
default: () => {
return {
row: 0,
col: 0,
};
},
},
dialogStatus: {
type: Number,
default: 0,
},
cellData: {
type: Array,
default: () => [],
},
cellConfigList: {
type: Array,
default: () => [],
},
entity: {
type: Object,
default: () => {
return {};
},
},
},
data() {
return {
dragging: false,
clickCellID: '',
startCell: null,
columns: [], // 您的列配置
chooseCellsArea: [],
chooseCellIds: [],
cellConfig: [],
renderTable: 0,
tableData: [],
endCell: {},
};
},
mounted() {
this.setCellConfigList();
},
methods: {
setCellConfigList() {
console.log('this.cellConfigList', this.cellConfigList);
if (this.cellConfigList.length > 0) {
this.tableData = this.cellConfigList;
this.renderTable += 1;
this.setCellDataList();
}
},
getCellConfigList() {
const localTableData = JSON.parse(JSON.stringify(this.tableData));
localTableData.forEach(row => {
row.col.forEach(col => {
delete col.cell; // 删除cell对象
});
});
return JSON.stringify(localTableData);
},
setCellDataList() {
if (this.cellData.length > 0) {
this.cellData.forEach(cell => {
// 遍历 tableData 数组
this.tableData.forEach(row => {
// 在当前 row 的 col 数组中查找匹配的 cell
const colItem = row.col.find(col => col.id === cell.id);
if (colItem) {
// 替换 cell 对象
colItem.cell = cell;
}
});
});
this.renderTable += 1;
}
console.log('this.tableData12313', this.tableData);
},
updateCellData(cell) {
this.tableData.forEach(row => {
// 在当前 row 的 col 数组中查找匹配的 cell
const colItem = row.col.find(col => col.id === cell.id);
if (colItem) {
// 替换 cell 对象
colItem.cell = {
...colItem.cell,
...cell,
};
}
});
this.renderTable += 1;
},
getCellDataList() {
const cells = this.tableData.reduce((acc, currentItem) => {
// 对每个元素的 col 数组进行遍历,提取 cell 对象
const cellsOfCurrentItem = currentItem.col.map(colItem => colItem.cell);
// 将当前项的 cells 添加到累积器数组中
return acc.concat(cellsOfCurrentItem);
}, []); // 初始累积器是一个空数组
console.log('cells', cells);
return cells;
},
openContextMenu(event) {
if (this.dialogStatus !== STATUS.CREATE) {
// 不是新增就不可以打开右击菜单
return;
}
this.$nextTick(() => {
event.preventDefault();
const rowspan = parseInt(event.currentTarget.getAttribute('rowspan'), 10);
const colspan = parseInt(event.currentTarget.getAttribute('colspan'), 10);
let flag = false;
flag = (rowspan > 1 || colspan > 1) && this.chooseCellsArea.length === 1;
if (flag) {
// 拆分
this.cellClick(event.currentTarget);
}
if (this.chooseCellsArea.length < 2 && !flag) {
this.cellClick(event.currentTarget);
}
this.$refs.contextMenuTool.openContextMenu(event, flag, this.chooseCellsArea.length < 2 && !flag);
});
},
mergeCell() {
// 计算起始单元格位置
console.log('行,列', this.getCellRowCol(this.chooseCellsArea[0]));
const chooseIds = this.chooseCellsArea.map(item => {
return item.id;
});
// console.log(' chooseIds', chooseIds);
// 计算合并单元格的大小
const mergeDimensions = this.calculateMergeDimensions(this.chooseCellsArea);
console.log(`合并后的单元格将占据 ${mergeDimensions.numRows} 行 和 ${mergeDimensions.numCols} 列。`);
this.mergeCellMethod(chooseIds, this.startCell.cellId, mergeDimensions.numRows, mergeDimensions.numCols);
// this.mergeCellMethod();
this.chooseCellsArea = [];
console.log('this.tableData', this.tableData);
this.renderTable += 1;
},
splitCell() {
// 拆分单元格
// console.log(this.chooseCellsArea[0]);
const cellId = this.clickCellID;
let idsToUpdate = [cellId];
this.tableData.forEach(row => {
row.col.forEach(col => {
if (col.id === cellId) {
idsToUpdate = idsToUpdate.concat(col.mergeids);
}
});
});
// 更新tableData数组中的对象,如果它们的ID在idsToUpdate中
this.tableData.forEach(row => {
row.col.forEach(col => {
if (idsToUpdate.includes(col.id)) {
col.mergeids = [];
col.rowspan = 1;
col.colspan = 1;
col.isMerge = false;
}
});
});
this.chooseCellsArea = [];
console.log('this.tableData', this.tableData);
this.renderTable += 1;
},
delRow() {
const cellId = this.clickCellID;
// 查找包含该col id的对象的索引
const parentIndex = this.tableData.findIndex(item => item.col.some(col => col.id === cellId));
if (parentIndex !== -1) {
this.tableData.splice(parentIndex, 1);
}
this.chooseCellsArea = [];
this.renderTable += 1;
},
delCol() {
const cellId = this.clickCellID;
let targetInitCol;
// 第一步:找到对应ID的对象并记录其initCol
for (const item of this.tableData) {
const found = item.col.find(col => col.id === cellId);
if (found) {
targetInitCol = found.initCol;
break;
}
}
// 第二步:删除所有initCol等于targetInitCol的对象
if (targetInitCol !== undefined) {
for (const item of this.tableData) {
item.col = item.col.filter(col => col.initCol !== targetInitCol);
}
}
this.chooseCellsArea = [];
this.renderTable += 1;
},
// 合并单元格
// mergeCellMethod() {
// const chooseIds = this.chooseCellsArea.map(item => {
// return item.id;
// });
// this.chooseCellsArea.forEach(cell => {
// const colObj = JSON.parse(cell.getAttribute('col'));
// if (colObj.mergeids.length > 0) {
// chooseIds.push(...colObj.mergeids);
// }
// });
// const idsToMergeExceptFirst = chooseIds.slice(1);
// this.tableData.forEach(item => {
// item.col.forEach(colItem => {
// if (idsToMergeExceptFirst.includes(colItem.id)) {
// colItem.isMerge = true;
// }
// if (chooseIds[0] === colItem.id) {
// colItem.mergeids = [...colItem.mergeids, ...idsToMergeExceptFirst];
// }
// });
// });
// },
// // 合并单元格
mergeCellMethod(chooseIds, id, rowspan, colspan) {
// 获取被合并的id
const idsToMergeExceptFirst = chooseIds.slice(1);
console.log('被合并的id', idsToMergeExceptFirst);
let endRowspan = 1;
let endColspan = 1;
this.tableData.forEach(row => {
row.col.forEach((col, index) => {
if (col.id === this.endCell.cellId) {
endRowspan = col.rowspan;
endColspan = col.colspan;
}
});
});
this.tableData.forEach(row => {
row.col.forEach((col, index) => {
if (col.id === id) {
col.rowspan = rowspan;
// col.colspan = col.colspan > 1 && colspan < col.colspan ? col.colspan : colspan;
col.colspan = colspan;
}
});
});
this.tableData.forEach(item => {
item.col.forEach(colItem => {
if (idsToMergeExceptFirst.includes(colItem.id)) {
colItem.isMerge = true;
}
if (chooseIds[0] === colItem.id) {
colItem.mergeids = [...colItem.mergeids, ...idsToMergeExceptFirst];
}
});
});
},
// 获取单元格位置信息
getCellRowCol(cell) {
const row = cell.parentNode.rowIndex;
const column = cell.cellIndex;
return {
row,
col: column,
};
},
// 计算合并单元格的大小
calculateMergeDimensions(chooseCellsArea) {
let minRowIndex = Infinity;
let maxRowIndex = -Infinity;
let minCellIndex = Infinity;
let maxCellIndex = -Infinity;
chooseCellsArea.forEach(cell => {
const rowIndex = parseInt(cell.getAttribute('initrow'), 10);
const cellIndex = parseInt(cell.getAttribute('initcol'), 10);
// const { rowIndex } = cell.parentNode; // 获取当前单元格的行索引
// const { cellIndex } = cell; // 获取当前单元格的列索引
// 更新行索引的最小和最大值
minRowIndex = Math.min(minRowIndex, rowIndex);
maxRowIndex = Math.max(maxRowIndex, rowIndex);
// 更新列索引的最小和最大值
minCellIndex = Math.min(minCellIndex, cellIndex);
maxCellIndex = Math.max(maxCellIndex, cellIndex);
});
// 计算合并后单元格应占据的行数和列数
const numRows = maxRowIndex - minRowIndex + 1;
const numCols = maxCellIndex - minCellIndex + 1;
return { numRows, numCols };
},
handleMouseDown(event) {
this.$nextTick(() => {
if (event.button === 0) {
this.dragging = true;
// 获取起始单元格信息,这里可能需要一些逻辑来确定单元格的行和列
const target = event.currentTarget;
console.log('摁下target', target);
if (target) {
const row = Number(target.attributes.initrow.value);
const col = Number(target.attributes.initcol.value);
const cellId = target.id;
this.startCell = { cellId, row, col };
// console.log('初始单元格', this.startCell);
}
// 添加全局鼠标移动事件监听
window.addEventListener('mousemove', this.handleMouseMove);
}
});
},
handleMouseUp(event) {
this.dragging = false;
// 移除全局鼠标移动事件监听
window.removeEventListener('mousemove', this.handleMouseMove);
},
handleMouseMove(event) {
// if (!this.dragging) {
// return;
// }
// console.log('的高亮显示');
// 这里可以根据需要添加逻辑,例如更新当前单元格的高亮显示
this.$nextTick(() => {});
if (event.button === 0) {
const { target } = event;
// console.log('target.parentNode', event);
if (target) {
const row = Number(target.attributes.initrow.value);
const col = Number(target.attributes.initcol.value);
// 根据 startCell 和当前单元格计算覆盖的单元格
console.log(`Selected cells from (${this.startCell.row}, ${this.startCell.col}) to (${row}, ${col})`);
// const cell = this.getTableCellByIdx('cabinet_latte_table', row, column);
this.endCell = {
cellId: target.id,
row,
col,
};
// this.applyBorderStyles('cabinet_latte_table', this.startCell.row, this.startCell.col, row, col, target.id);
this.getChooseCellRange('cabinet_latte_table', this.startCell.row, this.startCell.col, row, col, target.id);
}
}
},
// eslint-disable-next-line consistent-return
objectSpanMethod({ row, column, rowIndex, columnIndex }) {
// :span-method="objectSpanMethod"
if (this.cellConfig.length > 0) {
if (columnIndex === 2) {
if (rowIndex === 2) {
return [5, 2];
}
}
}
},
cellClick(col) {
this.clickCellID = col.id;
const cell = document.getElementById(col.id);
console.log(cell);
this.chooseCellsArea = [cell];
this.clearBorderStyles('cabinet_latte_table');
cell.style.border = '4px solid #409eff';
},
clearBorderStyles(tableId) {
const rowList = document.getElementById(tableId).querySelectorAll('tr');
if (!rowList) return;
// 遍历所有行和单元格,清除边框样式
for (const row of rowList) {
for (const cell of row.cells) {
cell.style.borderTop = '';
cell.style.borderBottom = '';
cell.style.borderLeft = '';
cell.style.borderRight = '';
cell.style.backgroundColor = 'transparent';
}
}
},
getChooseCellRange(tableId, startRow, startCol, endRow, endCol, endId) {
this.clearBorderStyles(tableId);
const cellList = document.getElementById(tableId).querySelectorAll('td');
const endRowNum = endRow;
const endColNum = endCol;
const cellsInRange = [];
const cellsIdsRange = [];
cellList.forEach(cell => {
const initRow = parseInt(cell.getAttribute('initrow'), 10);
const initCol = parseInt(cell.getAttribute('initcol'), 10);
// if (cell.id === endId && (cell.rowSpan !== 1 || cell.rowCol !== 1)) {
// endRowNum = cell.rowSpan === 1 ? endRow : cell.rowSpan;
// endColNum = cell.colSpan === 1 ? endCol : cell.colSpan;
// }
// 判断单元格是否在指定范围内
if (initRow >= startRow && initRow <= endRowNum && initCol >= startCol && initCol <= endColNum) {
cellsInRange.push(cell);
const endCell = this.getComputedStyleRanges(cellsInRange, tableId, startRow, startCol, endRow, endCol);
// this.chooseCellsArea = cellsInRange;
// cell.style.backgroundColor = '#c0bfbf';
this.applyBorderStyles(tableId, startRow, startCol, endCell.endRow, endCell.endCol);
}
});
},
// 获取范围并且将改变边框颜色
applyBorderStyles(tableId, startRow, startCol, endRow, endCol) {
this.clearBorderStyles(tableId);
const cellList = document.getElementById(tableId).querySelectorAll('td');
// let endRowNum = endRow;
// let endColNum = endCol;
const cellsInRange = [];
const cellsIdsRange = [];
cellList.forEach(cell => {
const initRow = parseInt(cell.getAttribute('initrow'), 10);
const initCol = parseInt(cell.getAttribute('initcol'), 10);
// 判断单元格是否在指定范围内
if (initRow >= startRow && initRow <= endRow && initCol >= startCol && initCol <= endCol) {
cellsInRange.push(cell);
this.chooseCellsArea = cellsInRange;
cell.style.backgroundColor = '#c0bfbf';
// 应用顶部边框样式
if (initRow === startRow) {
cell.style.borderTop = '4px solid #409eff';
}
// 应用底部边框样式
if (initRow === endRow) {
cell.style.borderBottom = '4px solid #409eff';
}
// 应用左侧边框样式
if (initCol === startCol) {
cell.style.borderLeft = '4px solid #409eff';
}
// 应用右侧边框样式
if (initCol === endCol) {
cell.style.borderRight = '4px solid #409eff';
}
}
});
console.log('选中的单元格', this.chooseCellsArea);
},
findElementByInitPosition(row, col) {
// 遍历tableData数组
for (const rowData of this.tableData) {
// 使用filter方法查找col数组中符合条件的元素
const matchedElements = rowData.col.filter(element => element.initRow === row && element.initCol === col);
// 如果找到匹配的元素,返回第一个匹配的元素
if (matchedElements.length > 0) {
return matchedElements[0]; // 返回找到的第一个匹配元素
}
}
// 如果没有找到匹配的元素,返回null
return null;
},
// 获取包含选中单元格的最大范围
getComputedStyleRanges(cells, tableId, startRow, startCol, endRow, endCol) {
let maxCol = 0;
let maxRow = 0;
cells.forEach(cell => {
const colObj = JSON.parse(cell.getAttribute('col'));
const colnum = colObj.initCol + (colObj.colspan - 1);
const rownum = colObj.initRow + (colObj.rowspan - 1);
if (colnum > maxCol) {
maxCol = colnum;
}
if (rownum > maxRow) {
maxRow = rownum;
}
});
return {
endCol: maxCol,
endRow: maxRow,
};
},
generate2DArray(setConfig) {
// 创建一个空数组用于存储结果
const result = [];
// 遍历行数
for (let i = 0; i < setConfig.row; i += 1) {
// 为每一行创建一个对象,使用guid()函数生成id
const rowObject = {
id: guid(), // 假设guid()是你提到的用于生成唯一id的函数
col: [],
};
// 遍历列数
for (let j = 0; j < setConfig.col; j += 1) {
// 为每一列创建一个对象,同样使用guid()函数生成id
const cellId = guid();
rowObject.col.push({
id: cellId,
rowspan: 1,
colspan: 1,
initRow: i,
initCol: j,
isMerge: false,
mergeids: [],
cell: {
id: cellId,
no: this.entity.containerName
? `${this.entity.containerName} ${i + 1} - ${j + 1}`
: `${i + 1} - ${j + 1}`,
storageLimit: this.entity.defaultStorageLimit,
rowIndex: i + 1,
columnIndex: j + 1,
},
});
}
// 将这一行的对象添加到结果数组中
result.push(rowObject);
}
// 返回生成的二维数组
return result;
},
// 设置柜子名称
setContainerName(containerName) {
this.tableData.forEach(row => {
row.col.forEach(col => {
col.cell.no = `${containerName + col.cell.rowIndex}-${col.cell.columnIndex}`; // 为每个cell对象添加containerName属性
});
});
this.renderTable += 1;
},
SetCellProperty(propName, propValue) {
this.tableData.forEach(row => {
row.col.forEach(col => {
col.cell[propName] = propValue; // 为每个cell对象添加containerName属性
});
});
this.renderTable += 1;
},
},
watch: {
setConfig: {
handler(val) {
if (this.cellConfigList.length < 1) {
if (val.row && val.col) {
if (this.tableData.length > val.row) {
this.tableData = this.tableData.splice(0, val.row);
} else {
this.tableData = this.generate2DArray(val);
}
}
}
console.log(' this.tableDatathis.cellConfigList', this.cellConfigList);
},
deep: true,
immediate: true,
},
'entity.containerName': {
handler(val) {
if (this.tableData.length > 0 && this.cellConfigList.length < 1) {
this.setContainerName(val);
}
},
deep: true,
},
'entity.defaultStorageLimit': {
handler(val) {
if (this.tableData.length > 0 && this.cellConfigList.length < 1) {
this.SetCellProperty('storageLimit', val);
}
},
deep: true,
},
},
};
</script>
<style lang="scss" scoped>
.cabinet_latte_contain {
.cabinet_latte_title {
width: 100%;
padding: 0 20px;
font-family: KaiTi, Microsoft YaHei;
font-size: 20px;
color: #606266;
line-height: 32px;
font-weight: 600;
}
#cabinet_latte_table {
margin-top: 20px;
user-select: none;
td {
// padding: 0;
.cell_info {
width: 100%;
height: 100%;
}
.cell_btn {
width: fit-content;
}
}
::v-deep .el-descriptions__body {
background-color: transparent;
}
}
::v-deep .el-table thead {
display: none;
::v-deep .el-table__row {
border: 1px solid black;
border-bottom: 0px;
}
}
::v-deep .el-table .el-table__body td,
.el-table .el-table__header,
.el-table .el-table__body tr,
.el-table .el-table__header-wrapper {
border: 1px solid black;
}
.el-table__body-wrapper {
border: 1px solid rgb(0, 0, 0);
border-bottom: 0px;
}
::v-deep .el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
background-color: transparent;
}
}
</style>
右击菜单组件ContextMenuTool.vue
<template>
<div>
<!-- <div class="context-menu-trigger">右击我试试</div> -->
<div v-if="showContextMenu" :style="menuStyle" class="context-menu">
<template v-if="cellOperate">
<div v-if="isMarge" @click="mergeCell()">合并单元格</div>
<div v-else @click="splitCell()">拆分单元格</div>
</template>
<div @click="delRow()">删除本行</div>
<div @click="delCol()">删除本列</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
showContextMenu: false,
menuStyle: {
top: '0px',
left: '0px',
},
isMarge: true,
cellOperate: false,
};
},
methods: {
openContextMenu(event, flag, cellOperate) {
this.isMarge = !flag;
this.cellOperate = !cellOperate;
this.menuStyle.top = `${event.clientY}px`;
this.menuStyle.left = `${event.clientX}px`;
this.menuStyle.position = 'fixed';
this.menuStyle.zIndex = 9999999;
this.showContextMenu = true;
// 监听一次点击事件,用于关闭菜单
window.addEventListener('click', this.closeContextMenu, { once: true });
},
mergeCell() {
this.$emit('mergeCell');
},
splitCell() {
this.$emit('splitCell');
},
delRow() {
this.$emit('delRow');
},
delCol() {
this.$emit('delCol');
},
closeContextMenu() {
this.showContextMenu = false;
},
menuItemClicked(item) {
console.log(`${item} 被点击了`);
this.closeContextMenu();
},
},
};
</script>
<style scoped>
.context-menu {
position: absolute;
border: 1px solid #ccc;
background-color: #fff;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15);
}
.context-menu div {
padding: 8px;
cursor: pointer;
}
.context-menu div:hover {
background-color: #f5f5f5;
}
</style>
五、设计器组件的使用
使用CabinetLatticeSet ,其中CellCard插槽表示自定义的格子内容,
组件的必要参数 和一些内置方法,会在下面进行细讲
<CabinetLatticeSet
ref="cabinetLatticeSet"
:cellConfigList="cellConfigList"
:setConfig="setConfig"
:entity.sync="entity"
:dialogStatus="dialog.status"
:cellData="cellData"
>
<template #cellCard="{ cell, tableData }">
<CellCard
:cell="cell"
:tableData="tableData"
@updateCell="updateCell"
:dialogStatus="dialog.status"
></CellCard>
</template>
</CabinetLatticeSet>
CellCard.vue 自定义格子的内容卡片组件,可以按照自己需求修改
<template>
<div class="cell_container">
<el-descriptions
class="margin-top"
style="zindex: 0"
:title="`编号:${cell.no} `"
:column="1"
direction="horizontal"
>
<el-descriptions-item label="容量">{{ cell.storageLimit }}</el-descriptions-item>
<el-descriptions-item label="锁ID">{{ cell.lockId }}</el-descriptions-item>
</el-descriptions>
<div class="cell_btn">
<el-button type="success" size="small" @click="setCell(cell)">{{ getBtnText }}</el-button>
</div>
<CellDialog ref="CellDialog" dialogFormDesignerName="柜格管理" @updateCell="updateCell"></CellDialog>
</div>
</template>
<script>
import CellDialog from './CellDialog.vue';
const STATUS = {
CREATE: 0,
UPDATE: 1,
DETAIL: 2,
};
export default {
props: {
cell: {
type: Object,
default: () => {},
},
tableData: {
type: Array,
default: () => [],
},
dialogStatus: {
type: Number,
default: 0,
},
},
components: {
CellDialog,
},
computed: {
getBtnText() {
if (this.dialogStatus === STATUS.UPDATE || this.dialogStatus === STATUS.CREATE) {
return '设置柜子';
}
return '查看柜子';
},
},
data() {
return {};
},
methods: {
setCell(cell) {
if (this.dialogStatus === STATUS.DETAIL) {
this.$refs.CellDialog.showDialog(cell, 2, cell);
} else {
this.$refs.CellDialog.showDialog(cell, 1, cell);
}
},
updateCell(cell) {
this.$emit('updateCell', cell);
},
},
};
</script>
<style lang="scss" scoped>
// .cell_container {
// width: 100%;
// height: 100%;
// background-image: url('board.png'); /* 设置背景图片 */
// background-size: 100% 100%; /* 覆盖整个div,也可以用contain保持图片的比例 */
// background-position: center; /* 图片居中显示 */
// background-repeat: no-repeat; /* 防止图片重复 */
// padding: 20px 20px;
// }
</style>
六、CabinetLatticeSet 组件的参数和方法
属性
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
cellConfigList | 柜子结构基础json | Array | - | [] |
cellData | 柜子格子数据list | Array | - | [] |
setConfig | 控制生成行数列数 | Object | - | {row:0,col:0} |
entity | 格子的基本类,可根据自身项目在组件中自行修改 | Object | - | - |
dialogStatus | 当前设计器状态 | Number | 新增: 0, 编辑: 1,查看: 2, | 0 |
方法
方法名 | 说明 | 参数 |
---|---|---|
updateCellData | 更新某个格子数据,传入新的格子对象cell,用来更新结构 | cell |
getCellConfigList | 获取柜子结构的json数据 | - |
getCellDataList | 获取柜子中所有格子的实例数据 | - |
七、展示渲染 柜子组件的代码演示
下面是代码 经过了精简直接使用可能会用问题,请注意使用,自行修改
将数据库中存在的柜子结构和数据渲染出来的页面
<template>
<div class="ware-house-container" ref="warehouseContainer">
<div class="lattice_content" v-if="chooseCabinetItem.isShowInfo" v-loading="loadingTable">
<table border="1" id="cabinet_latte_table" align="center" cellpadding="20" cellspacing="0" :key="renderTable">
<tr v-for="(row, index) in cellDesign" :key="index" :id="row.id">
<td
v-for="(col, j) in row.col"
:key="j"
:id="col.id"
:rowspan="col.rowspan"
:colspan="col.colspan"
:initRow="col.initRow"
:initCol="col.initCol"
v-show="!col.isMerge"
>
<div
class="lattice_card"
:style="{ backgroundColor: getCabinetColor(j + 1) }"
@mouseenter="mouseEnterHandler(col.cell, `${index},${j}`)"
@mouseleave="mouseLeaveHandler(col.cell, `${index},${j}`)"
>
<div class="info_item_no">{{ col.cell.no }}</div>
<div class="goods_sum">
<template v-if="col.cell.usedStorage > 0"> {{ col.cell.usedStorage }}(件) </template>
<template v-else> <div class="goods_tip">空</div> </template>
</div>
<transition name="slide-up">
<div class="card_box__hover" v-show="hoverXY === `${index},${j}`">
<template v-if="type">
<!-- <el-button class="big_btn" v-if="type === '存放位置'" type="primary" @click="saveLocation(col)"
>保存位置</el-button
> -->
</template>
<template v-else>
<!-- <el-button type="primary" class="big_btn" v-if="col.cell.usedStorage > 0" @click="goodsInfo(col)"
>查看物品</el-button
>
<el-button type="primary" class="big_btn" @click="btnDetailOnClick(item)">开柜</el-button> -->
</template>
</div>
</transition>
</div>
</td>
</tr>
</table>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isOverFlow: false,
toggleFlag: false,
cabinetContentTop: '',
cabinetColor: ['#cfe7e9', '#e2f5fb', '#eae7f8', '#def1e0', '#fbfce0'],
menustyle: {},
menuHeight: '140px',
chooseIndex: 0,
chooseCabinetItem: {},
chooseCellItem: {},
hoverXY: null,
cabinetList: [],
cellDesign: [],
cellData: [],
cabinetContentStyle: {},
loadingTable: false,
renderTable: 0,
};
},
methods: {
async getCellInfoById(id) {
// 获取柜子信息
this.loadingTable = true;
const res = await this.axios({
url: 'xxxxxx',
method: 'post',
params: {
id,
},
});
this.cellDesign = res.data.conf ? JSON.parse(res.data.conf) : [];
this.cellData = res.data.containerInfoList || [];
this.setCellConfigList();
this.loadingTable = false;
},
setCellConfigList() {
if (this.cellDesign.length > 0) {
// this.tableData = this.cellDesign;
this.renderTable += 1;
this.setCellDataList();
}
},
// 解析结合数据渲染页面
setCellDataList() {
if (this.cellData.length > 0) {
this.cellData.forEach(cell => {
// 遍历 tableData 数组
this.cellDesign.forEach(row => {
// 在当前 row 的 col 数组中查找匹配的 cell
const colItem = row.col.find(col => col.id === cell.id);
if (colItem) {
// 替换 cell 对象
colItem.cell = cell;
}
});
});
this.renderTable += 1;
}
},
getCabinetColor(col) {
const num = col / this.cabinetColor.length;
if (num > 1) {
const q = col % this.cabinetColor.length;
return this.cabinetColor[q];
}
return this.cabinetColor[col - 1];
},
mouseEnterHandler(item, index) {
this.hoverXY = index;
},
mouseLeaveHandler(item, index) {
if (this.hoverXY === index) {
this.hoverXY = null;
}
},
},
};
</script>
<style lang="scss" scoped>
.lattice_content {
// background-color: #cfe7e9;
float: left;
width: 100%;
height: calc(100% - 40px);
padding: 10px 15px 20px 15px;
overflow: auto;
#cabinet_latte_table {
// border: 1px solid #eeeeee;
border-color: #eeeeee;
td {
height: 200px;
padding: 0;
position: relative;
.cell_info {
width: 100%;
height: 100%;
}
.lattice_card {
// width: 100%;
min-width: 200px;
height: 100%;
position: relative;
padding: 10px 20px;
.info_item_no {
float: left;
margin-left: 20px;
font-size: 1vw;
line-height: 40px;
font-weight: 600;
}
.goods_sum {
float: left;
width: 100%;
height: calc(100% - 60px);
display: flex;
justify-content: center;
align-items: center;
font-size: 2vw;
font-weight: 600;
.goods_tip {
font-size: 4vw;
font-weight: 800;
color: #aaaaaa;
}
}
.card_box__hover {
border-radius: 10px;
width: 100%;
height: 100%;
position: absolute;
bottom: 0;
left: 0;
background-color: rgba(124, 203, 209, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
}
/* 自定义滚动条的宽度和背景颜色 */
::-webkit-scrollbar {
width: 10px;
background-color: #f5f5f5;
}
/* 自定义滚动条滑块的颜色、边框和滑动时的颜色 */
::-webkit-scrollbar-thumb {
background-color: #555;
border-radius: 5px;
border: 2px solid #f5f5f5;
}
/* 自定义滚动条轨道的颜色 */
::-webkit-scrollbar-track {
background-color: #f0f0f0;
}
.slide-up-enter-active {
animation: height 0.5s;
}
::v-deep .big_btn {
font-size: 23px !important;
}
@keyframes height {
from {
height: 0%;
}
to {
height: 100%;
}
}
</style>
八、柜子结构json demo展示
下面将根据图片所示的柜子结构给出对应代码生成的结构json,并对json的各个参数做解释
row对象
id:行id 唯一值
col:本行的列数组
col数组下的cell格子对象
id:每个格子的唯一id,数据库中要跟格子中的物品做关联使用!!!很重要
rowspan:本单元格占据的行数
colspan:本单元格占据的列数
initRow:初始单元格的行位置
initCol: 初始单元格的列位置
isMerge:这个单元格是否被合并掉了false表示页面需要显示 true表示这个单元格被合并了,不再做显示
mergeids:被合并的单元格的id集合
[
{
"id": "e255d896-d62f-4501-9946-cf38c395111f",
"col": [
{
"id": "5d40900a-791b-4add-a815-08c16ece6e08",
"rowspan": 2,
"colspan": 2,
"initRow": 0,
"initCol": 0,
"isMerge": false,
"mergeids": [
"d1b08873-0c3a-41af-b48e-c5365ca51fad",
"304d1ea2-c550-4864-b3ad-0b0599fe3ca9",
"decf8dd7-5b24-437f-848a-78ebc31ad0b7"
]
},
{
"id": "d1b08873-0c3a-41af-b48e-c5365ca51fad",
"rowspan": 1,
"colspan": 1,
"initRow": 0,
"initCol": 1,
"isMerge": true,
"mergeids": []
},
{
"id": "b9901b3b-ab19-4eac-ae6e-61b70f0e16ba",
"rowspan": 1,
"colspan": 1,
"initRow": 0,
"initCol": 2,
"isMerge": false,
"mergeids": []
},
{
"id": "9635c3ab-4594-4969-a00f-45c7ad4a3b4a",
"rowspan": 1,
"colspan": 1,
"initRow": 0,
"initCol": 3,
"isMerge": false,
"mergeids": []
}
]
},
{
"id": "9ce8ff5e-5125-41ee-bc6e-7de9bb4980aa",
"col": [
{
"id": "304d1ea2-c550-4864-b3ad-0b0599fe3ca9",
"rowspan": 1,
"colspan": 1,
"initRow": 1,
"initCol": 0,
"isMerge": true,
"mergeids": []
},
{
"id": "decf8dd7-5b24-437f-848a-78ebc31ad0b7",
"rowspan": 1,
"colspan": 1,
"initRow": 1,
"initCol": 1,
"isMerge": true,
"mergeids": []
},
{
"id": "b632f4fb-4ec0-4776-b007-dc9e124faa69",
"rowspan": 2,
"colspan": 1,
"initRow": 1,
"initCol": 2,
"isMerge": false,
"mergeids": [
"7f30a380-c8dc-467f-b849-13c6b0c94ab6"
]
},
{
"id": "8d7eed53-d0e7-4329-b50b-a936b445de62",
"rowspan": 1,
"colspan": 1,
"initRow": 1,
"initCol": 3,
"isMerge": false,
"mergeids": []
}
]
},
{
"id": "981b8507-0e78-4a3e-bf48-4eafadefa5e7",
"col": [
{
"id": "5acfc116-1fa2-4b45-a445-25b7700381b8",
"rowspan": 1,
"colspan": 1,
"initRow": 2,
"initCol": 0,
"isMerge": false,
"mergeids": []
},
{
"id": "5a4448dd-d968-4300-bdc3-d899bffbe214",
"rowspan": 1,
"colspan": 1,
"initRow": 2,
"initCol": 1,
"isMerge": false,
"mergeids": []
},
{
"id": "7f30a380-c8dc-467f-b849-13c6b0c94ab6",
"rowspan": 1,
"colspan": 1,
"initRow": 2,
"initCol": 2,
"isMerge": true,
"mergeids": []
},
{
"id": "5cee411f-6761-437b-8ebc-2763ef3752d8",
"rowspan": 1,
"colspan": 1,
"initRow": 2,
"initCol": 3,
"isMerge": false,
"mergeids": []
}
]
},
{
"id": "19acc767-a559-4fad-b5f5-047c142b9549",
"col": [
{
"id": "ef369fda-de70-4e37-8ab5-20f509af1c31",
"rowspan": 1,
"colspan": 2,
"initRow": 3,
"initCol": 0,
"isMerge": false,
"mergeids": [
"657a7c99-a10c-49e1-a179-997a708b42ac"
]
},
{
"id": "657a7c99-a10c-49e1-a179-997a708b42ac",
"rowspan": 1,
"colspan": 1,
"initRow": 3,
"initCol": 1,
"isMerge": true,
"mergeids": []
},
{
"id": "efa6f4eb-7353-4582-bed9-e2b50ccc8cbc",
"rowspan": 1,
"colspan": 1,
"initRow": 3,
"initCol": 2,
"isMerge": false,
"mergeids": []
},
{
"id": "787a7e5d-96d3-46c1-8fea-220c675c05cd",
"rowspan": 1,
"colspan": 1,
"initRow": 3,
"initCol": 3,
"isMerge": false,
"mergeids": []
}
]
}
]