UI还原分析
从UI图上来看像极了excel,然后大部分操作也沿袭了excel
功能拆分
如何绘制网格
- 采⽤ div 绝对定位的⽅式,通过 translate 移动⽅位进⾏⽹格的绘制
- Canvas绘制减少重排
./grid-layout/core/gridCvs.js
// 初始化基础网格
drawGrid ({ stepy, stepx, color, lineWidth }) {
const { ctx, el } = this
const { width, height } = el
const gridsy = Object.keys(stepy).length
const gridsx = Object.keys(stepx).length
this.clear()
ctx.beginPath()
// rows
for (let i = 0, j = 0; i < gridsy + 1; i++) {
ctx.moveTo(0, j + 0.5)
ctx.lineTo(width, j + 0.5)
if (i === gridsy) {
ctx.moveTo(0, j - 0.5)
ctx.lineTo(width, j - 0.5)
}
if (i < gridsy) {
j += stepy[i].height
}
}
// cols
for (let i = 0, j = 0; i < gridsx + 1; i++) {
ctx.moveTo(j + 0.5, 0)
ctx.lineTo(j + 0.5, height)
if (i === gridsx) {
ctx.moveTo(j - 0.5, 0)
ctx.lineTo(j - 0.5, height)
}
if (i < gridsx) {
j += stepx[i].width
}
}
// 设置绘制颜色
ctx.strokeStyle = color
// ctx.strokeStyle = '#cccccc'
// 设置绘制线段的宽度
ctx.lineWidth = lineWidth
// 绘制格网
ctx.stroke()
// 清除路径
ctx.closePath()
}
./grid-layout/core/cvsdraw.js
// 绘制合并的单元格
rect (clipFillBox, cb) {
const { ctx } = this
const {
x, y, width, height, bgcolor
} = clipFillBox
ctx.save()
ctx.beginPath()
ctx.fillStyle = bgcolor || '#fff'
// // 设置绘制颜色
// ctx.strokeStyle = 'black'
// // 设置绘制线段的宽度
// ctx.lineWidth = 1
const offsetWidth = (x + 1 + width) > this.cvsWidth ? 2 : 1
const offsetHeight = (y + 1 + height) > this.cvsHeight ? 2 : 1
ctx.rect(x + 1, y + 1, width - offsetWidth, height - offsetHeight)
ctx.stroke()
ctx.clip()
ctx.fill()
cb()
ctx.restore()
}
主文件
./grid-layout/LayoutGrid.vue
区域划分
网格区
<div ref="xlayoutContent" class="xlayout-content">
<!-- 网格绘制区 -->
<canvas ref="canvasSheet"></canvas>
<!-- 高亮选中区 -->
<div class='xlayout-selectors' :style="xlayoutSelectors"></div>
<!-- 高亮插入选区 -->
<div class='add-row-selectors' :style="addRowSelectors"></div>
<!-- 事件操作区 -->
<div
class='xlayout-overlay'
@mousedown.self="overlayerMousedown"
@mouseup.self="overlayerMouseup"
@mousemove.self="overlayerMousemove"
>
</div>
</div>
左标尺区
./grid-layout/AssistRule
<AssistRule ref="vRuler" :isVertical="true" :cvsView="cvsView" :layoutController="layoutController" @resetSeleced="resetXlayoutSelectors" @updateRowItems='updateRowItems'/>
行操作区
<div class="rows-layout">
<div class="row-index"
v-for="(row, key, index) in rowItems" :key="index"
:class="{'selectedRow': selectRowIndex === index}"
:style="{height: row.height + 'px'}"
@click="selectRow(row, index)"
@contextmenu.prevent="openMenu(row, $event, index)"
>
{{index + 1}}
<div class="drag-item"
@mousedown.self="dragMousedown(row, $event, index)">
</div>
</div>
<div class='assist-resizer' :style="dragInfo.assistResizerHighlight">
</div>
<div
class='assist-rule-overlay'
v-show="dragInfo.assistRuleOverlayShow"
@mouseup.self="dragRuleMouseup"
@mousemove.self="dragRuleMouseDownMove"
:style="{width: canvasSheetWidth + 20 + 'px'}"
></div>
</div>
列操作区
<div class="cols-layout" :style="{width: canvasSheetWidth + 'px'}">
<div class="col-index" v-for="index in 12" :key="index" :style="{width: (canvasSheetWidth / 12) + 'px'}">{{index}}</div>
</div>
核心操作类 ./grid-layout/core/controller.js
constructor() {
this.mRanges = new Mergeds(mRanges) // [CellRange, ...]
this.rows = new Rows(rows)
this.cols = new Cols({ gridsx, cvsView })
......
}
通过⿏标点击x,y坐标获取当前cell选区
controller.js => getCellRectByXY(x, y, isMoving)
// 通过鼠标点击x,y坐标获取当前cell选区
getCellRectByXY (x, y, isMoving) {
const { ri, top, height } = this.getCellRowByY.call(this, y)
const { ci, left, width } = this.getCellColByX.call(this, x)
let crLTWH = {}
let cMerged = ''
if (!isMoving) {
cMerged = this.mRanges.getFirstIncludes(ri, ci)
// 点击在已合并的选区上
if (cMerged) {
console.log('getCellRectByXY >>>>>> cMerged', cMerged)
const { sri, sci, eri, eci } = cMerged.getCellRange
crLTWH = this.getCellRangeLTWH({ sri, sci, eri, eci })
}
}
return {
ri, ci, left, top, width, height, ...crLTWH, cMerged
}
}
选区对象CellRange
class CellRange {
// sri:开始行坐标 sci:开始列坐标 eri:结束行坐标 eci:结束行坐标
#sri
#sci
#eri
#eci
constructor (sri, sci, eri, eci) {
this.#sri = sri
this.#sci = sci
this.#eri = eri
this.#eci = eci
}
// cell-index: ri, ci
// cell-ref: A10
// 当前cell是否完全包含在cellRange中,用于在已有合并选区中点击时候扩大选区
isIncludes (...args) {
let [ri, ci] = [0, 0]
// 解析两种不同的入参
if (args.length === 1) {
[ci, ri] = expr2xy(args[0])
} else if (args.length === 2) {
[ri, ci] = args
}
return this.#sri <= ri && ri <= this.#eri && this.#sci <= ci && ci <= this.#eci
}
// 是否有交叉
isIntersects (cr) {
const ncr = cr.getCellRange
return this.#sri <= ncr.eri &&
this.#sci <= ncr.eci &&
ncr.sri <= this.#eri &&
ncr.sci <= this.#eci
}
功能实现
./grid-layout/LayoutGrid.vue
1.普通点击选中 overlayerMousedown => overlayerMouseup
overlayerMousedown (evt) {
this.selectRowIndex = -1
this.dragInfo.isMouseDown = true
if (!evt) return
// console.log('overlayerMousedown >>>> evt',evt)
const { layoutController, selectedRange } = this
const { offsetX, offsetY } = evt
const sRect = layoutController.getCellRectByXY(offsetX, offsetY)
// 当前鼠标落下当前选区的信息
console.log('overlayerMousedown >>>> sRect', sRect)
const { ri, ci, width, height, left, top, cMerged } = sRect
this.xlayoutSelectors = {
display: 'block',
width: `${width + 1}px`,
height: `${height + 1}px`,
left: `${left}px`,
top: `${top}px`
}
this.cMerged = cMerged
this.cCell = { ri, ci }
if (selectedRange && selectedRange.cellRange) {
this.selectedRange.cellRange = ''
}
}
2.普通点击选中 overlayerMousedown => overlayerMousemove=> overlayerMouseup
overlayerMousemove (evt, pickRow) {
let nCell
let cCell
const isPickRow = pickRow && pickRow.nCell && pickRow.cCell
const { layoutController } = this
if (isPickRow) {
({ nCell, cCell } = pickRow)
} else {
if (!this.dragInfo.isMouseDown) return
const { offsetX, offsetY } = evt
const sRect = layoutController.getCellRectByXY(offsetX, offsetY, true)
const { ri: nri, ci: nci } = sRect
// 不在布局容器中移动不执行下一步逻辑
if (nri === -1 || nci === -1) return
// 行列索引相同不执行逻辑 同一cell反复移动
if (this.nCell.ri === nri && this.nCell.ci === nci) return
this.nCell = { ri: nri, ci: nci }
nCell = this.nCell
// 计算当前热选区
cCell = this.cCell // 点击位cell
}
const { ri: nri, ci: nci } = nCell || {}
const { ri: cri, ci: cci } = cCell || {}
// 计算当前热选区
let _sri = ''
let _sci = ''
let _eri = ''
let _eci = ''
if (nri > cri) [_sri, _eri] = [cri, nri]
else [_sri, _eri] = [nri, cri]
if (nci > cci) [_sci, _eci] = [cci, nci]
else [_sci, _eci] = [nci, cci]
// 获得选区
let cellRange = layoutController.mRanges.union(new CellRange(_sri, _sci, _eri, _eci))
console.log('cellRange >>>>', cellRange)
cellRange = layoutController.mRanges.union(cellRange)
console.log('cellRange >>>>', cellRange)
const { sri, eri, sci, eci } = cellRange.getCellRange
// 计算划选范围
const { rows, cols } = layoutController
const crLeft = cols.getColLeftByCi(sci)
const crTop = rows.getRowTopByRi(sri)
const crHeight = rows.sumHeightBetween(sri, eri)
const crWidth = cols.sumWidthBetweenCi(sci, eci)
console.log('overlayerMousemove >>>> cellRange crLeft,crTop,crHeight,crWidth', crLeft, crTop, crHeight, crWidth)
this.selectedRange = { sri, eri, crLeft, crTop, crHeight, crWidth, multi: cellRange.isMultiple(), cellRange }
this.cMerged = ''
// 高亮选区
this.xlayoutSelectors = {
display: 'block',
width: `${crWidth + 1}px`,
height: `${crHeight + 1}px`,
left: `${crLeft}px`,
top: `${crTop}px`
}
}
合并/拆分
./grid-layout/index.vue
// 合并
mergeCRHandler () {
if (this.actionsDtnDisable.merge) {
return
}
// console.log('mergeCRHandler >>>>',this.$refs.layoutGrid)
const { selectedRange, layoutController } = this.$refs.layoutGrid
const { crLeft, crTop, crHeight, crWidth, multi, cellRange } = selectedRange || {}
const { draw, mRanges } = layoutController || {}
// 没有划动选区或者单cell不执行合并
if (!selectedRange || !multi) return
draw.rect({
x: crLeft,
y: crTop,
width: crWidth,
height: crHeight,
bgcolor: '#fff'
}, () => {
// 更新之前记录
this.record()
// 存入已合并选区
mRanges.addData(cellRange)
this.refreshActionsDtnDisable()
})
}
// 拆分
unMergeCRHandler () {
if (this.actionsDtnDisable.unMerge) {
return
}
console.log('unMergeCRHandler')
const { layoutController: lc, cMerged, selectedRange } = this.$refs.layoutGrid
const { mRanges } = lc
const { multi, cellRange } = selectedRange
// 更新之前记录
this.record()
if (cMerged || (multi && cellRange)) {
mRanges.removeData(cellRange || cMerged)
lc.fresh()
}
this.refreshActionsDtnDisable()
}
行操作
./grid-layout/LayoutGrid.vue
insertRowAfterHandler () { // 下增行
const { layoutController: lc, proxyChildren } = this
const { vRuler } = proxyChildren()
// 记录
// lc.record()
this.$emit('record')
// 功能操作
lc.rows.insertRowBottom(this.selectedRange.eri, { height: 150 })
// lc.rows.insertRowBottom(this.selectedRange.eri, { height: 100 })
this.updateMRanges(this.selectedRange.eri, false)
console.log('this.layoutType')
console.log(this.layoutType)
// 刷新
lc.fresh()
this.updateRowItems(lc.rows.getRowsHeight)
// this.$emit('record')
vRuler.fresh()
// 插入行start
// 获得选区
const { layoutController } = this
const { rows } = layoutController
const crTop = rows.getRowTopByRi(this.selectedRange.eri + 1)
// 高亮插入行选区
this.addRowSelectors = {
display: 'block',
width: `${this.$refs.xlayoutContent.offsetWidth}px`,
height: '150px',
left: '0px',
top: `${crTop}px`
}
setTimeout(() => {
this.addRowSelectors = {
display: 'none'
}
}, 500)
}
insertRowBeforeHandler () { // 上增行
console.log('insertRowAfterHandler')
console.log(this.selectedRange.sri)
console.log(this.selectedRange.eri)
const { layoutController: lc, proxyChildren } = this
const { vRuler } = proxyChildren()
// 记录
// lc.record()
this.$emit('record')
// 功能操作
lc.rows.insertRowTop(this.selectedRange.sri, { height: 150 })
// lc.rows.insertRowTop(this.selectedRange.sri, { height: 100 })
this.updateMRanges(this.selectedRange.sri, true)
// 刷新
lc.fresh()
this.updateRowItems(lc.rows.getRowsHeight)
vRuler.fresh()
// 获得选区
let cellRange = lc.mRanges.union(new CellRange(this.selectedRange.sri + 1, 0, this.selectedRange.eri + 1, 11))
cellRange = lc.mRanges.union(cellRange)
console.log('cellRange >>>>', cellRange)
const { sri, eri, sci, eci } = cellRange.getCellRange
// 计算划选范围
const { rows, cols } = lc
const crLeft = cols.getColLeftByCi(sci)
const crTop = rows.getRowTopByRi(sri)
const crHeight = rows.sumHeightBetween(sri, eri)
const crWidth = cols.sumWidthBetweenCi(sci, eci)
console.log('overlayerMousemove >>>> cellRange crLeft,crTop,crHeight,crWidth', crLeft, crTop, crHeight, crWidth)
this.selectedRange = { sri, eri, crLeft, crTop, crHeight, crWidth, multi: cellRange.isMultiple(), cellRange }
this.cMerged = ''
// 高亮选区
this.xlayoutSelectors = {
display: 'block',
width: `${crWidth + 1}px`,
height: `${crHeight + 1}px`,
left: `${crLeft}px`,
top: `${crTop}px`
}
this.selectRowIndex = this.selectRowIndex + 1
// 插入行start
// 获得选区
const addCrTop = rows.getRowTopByRi(this.selectedRange.sri - 1)
// 高亮插入行选区
this.addRowSelectors = {
display: 'block',
width: `${this.$refs.xlayoutContent.offsetWidth}px`,
height: '150px',
left: '0px',
top: `${addCrTop}px`
}
setTimeout(() => {
this.addRowSelectors = {
display: 'none'
}
}, 500)
// 插入行end
}
deleteRow () {
MessageBox.confirm('您确定要删除行吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
console.log('deleteRow')
console.log(this.selectedRange.sri)
console.log(this.selectedRange.eri)
const { layoutController: lc, resetSelect, proxyChildren } = this
const { vRuler } = proxyChildren()
// 记录
// lc.record()
this.$emit('record')
// 功能操作 从后往前删 不然删除合并的最后行有bug
for (let index = this.selectedRange.eri; index >= this.selectedRange.sri; index--) {
console.log('delRowByRi')
lc.rows.delRowByRi(index)
}
this.deleteAndUpdateMRanges(this.selectedRange.sri, this.selectedRange.eri)
// 刷新
lc.fresh()
this.updateRowItems(lc.rows.getRowsHeight)
resetSelect()
vRuler.fresh()
}).catch(() => {
console.log('取消退出模板编辑')
})
}
拖拉改变行高
./grid-layout/LayoutGrid.vue
<div class="rows-layout">
<div class="row-index"
v-for="(row, key, index) in rowItems" :key="index"
:class="{'selectedRow': selectRowIndex === index}"
:style="{height: row.height + 'px'}"
@click="selectRow(row, index)"
@contextmenu.prevent="openMenu(row, $event, index)"
>
{{index + 1}}
<!-- 拖拉触发区域 -->
<div class="drag-item"
@mousedown.self="dragMousedown(row, $event, index)">
</div>
</div>
<div class='assist-resizer' :style="dragInfo.assistResizerHighlight">
</div>
<!-- 拖拉移动区域 -->
<div
class='assist-rule-overlay'
v-show="dragInfo.assistRuleOverlayShow"
@mouseup.self="dragRuleMouseup"
@mousemove.self="dragRuleMouseDownMove"
:style="{width: canvasSheetWidth + 20 + 'px'}"
></div>
</div>
触发事件顺序 dragMousedown => dragRuleMouseDownMove =>dragRuleMouseup
dragMousedown (row, e, index) {
const { layoutController } = this
this.resetXlayoutSelectors()
this.isMosueDown = true
this.dragRuleCanMove = true
this.dragRow = row
this.cRowIndex = index
this.dragInfo.offsetHeight = 0
const top = layoutController.rows.getRowTopByRi(index)
this.dragInfo.assistResizerHighlight = {
display: 'block',
width: `${this.cvsView.width + 20}px`,
top: `${top + row.height + 20}px`
}
this.dragStartY = top + row.height + 20
// 计算行信息位置
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 15 // 15: margin right
if (left > maxLeft) {
this.dragInfo.rowInfoLeft = maxLeft
} else {
this.dragInfo.rowInfoLeft = left
}
this.dragInfo.rowInfoTop = e.pageY - 170
this.rowHeights = layoutController.rows.getRowsHeight
const cheight = this.rowHeights[this.cRowIndex].height
this.dragInfo.curRowHeight = cheight
if (this.dragInfo.curRowHeight === 10) {
this.dragRuleCanMoveUp = false
}
this.dragInfo.rowInfoVisible = true
this.dragInfo.assistRuleOverlayShow = true
},
dragRuleMouseDownMove: _.throttle(function (e) {
console.log('dragRuleMouseDownMove')
if (!this.isMosueDown) return
if (!this.dragRuleCanMove) return
const { layoutController } = this
const top = layoutController.rows.getRowTopByRi(this.cRowIndex)
const nHeight = e.offsetY
// 10px拉动单位吸附
const roundHeight = this.roundHeight = Math.round(nHeight / 10) * 10
this.dragInfo.curRowHeight = roundHeight - 20 - top
this.dragInfo.offsetHeight = roundHeight - this.dragStartY
this.dragInfo.rowInfoTop = e.pageY - 170
if (this.dragInfo.curRowHeight < 15) {
this.dragInfo.curRowHeight = 10
this.dragInfo.offsetHeight = 10 - layoutController.rows.getRowHeightByRi(this.cRowIndex)
this.dragInfo.assistResizerHighlight = {
display: 'block',
width: `${this.cvsView.width + 20}px`,
top: `${top + 20 + 10}px`
}
return
}
this.dragInfo.assistResizerHighlight = {
display: 'block',
width: `${this.cvsView.width + 20}px`,
top: `${nHeight - 1}px`
}
}, 50),
dragRuleMouseup (row, e) {
this.selectRowIndex = -1
this.isMosueDown = false
this.dragRuleCanMove = true
this.dragInfo.assistRuleOverlayShow = false
this.dragInfo.assistResizerHighlight = {
display: 'none'
}
const { layoutController } = this
const nowy = this.roundHeight - 20 || 0
if (this.dragInfo.offsetHeight === 0) {
this.dragInfo.rowInfoVisible = false
return
} else {
this.$emit('record')
}
this.rowTops = layoutController.rows.getRowsTops
const crowTop = this.rowTops[this.cRowIndex].top
// if (nowy - crowTop <= 0) {
// this.dragInfo.rowInfoVisible = false
// return
// }
console.log(this.rows)
layoutController.rows.setRowByRi(this.cRowIndex, { height: this.dragInfo.curRowHeight })
// 网格更新
layoutController.fresh()
this.updateRowItems(layoutController.rows.getRowsHeight)
this.$refs.vRuler.fresh()
this.dragInfo.rowInfoVisible = false
}
前进/后退
class History {
undoStack
redoStack
static hInstance = null
constructor () {
// if (History.hInstance) return History.hInstance
this.undoStack = []
this.redoStack = []
History.hInstance = this
}
// 增加撤销数据
// 注意这个数据是操作数据进行更新之前的数据
add (data) {
if (!data) return
this.undoStack.push(JSON.stringify(data))
// 对表格进行操作之后恢复撤销置空 恢复撤销是针对撤销连贯操作 中间穿插其他操作 就不允许恢复撤销了
this.redoStack = []
}
// 是否能撤销
canUndo () {
return this.undoStack.length > 0
}
// 是否能重做
canRedo () {
return this.redoStack.length > 0
}
// 后退
undo (currentd, cb) {
if (this.canUndo()) {
// 当前数据压入前进栈
this.redoStack.push(JSON.stringify(currentd))
console.log('>>>> this.redoStack', this.redoStack)
// 弹出撤销栈的最新数据 传入回调函数
cb(JSON.parse(this.undoStack.pop()))
} else {
alert('没有撤销数据了')
}
}
// 前进
redo (currentd, cb) {
if (this.canRedo()) {
// 当前数据压入撤销栈
this.undoStack.push(JSON.stringify(currentd))
// 弹出重做栈的最新数据 传入回调函数
cb(JSON.parse(this.redoStack.pop()))
} else {
alert('没有重做数据了')
}
}
}
关于输出结果 与 二次编辑思考
输出结果
layoutData: { // canvas 布局数据
cols: { // 列
gridsx: 12 // 多少列
},
rows: { // 行高和数量不限 超出滚动
rowItems: {
0: { height: 150 },
1: { height: 150 },
2: { height: 150 },
3: { height: 150 },
4: { height: 150 },
5: { height: 150 },
6: { height: 150 },
7: { height: 150 },
8: { height: 150 },
9: { height: 150 },
10: { height: 150 },
11: { height: 150 }
}
},
mRanges: ['A0:B0', 'C0:D0'] // 合并信息 其中A代表列'第0列'
},
divAbsoluteLayoutData: { // canvas 转换成绝对定位div数据
width: 1200, // 编辑布局时的基线宽度
height: 1800, // 编辑布局时的基线高度
layoutItems: [ // 容器列表
{
top: 100,
left: 100,
width: 100
height: 100,
}
]
}
二次编辑
1.数据存储与转换
reportInfo:
pageList: [
containerList: {
'0b598969-abe7-4140-837c-350ccd35fdf8': {
componentIds: []
image: "base64"
uuid: "0b598969-abe7-4140-837c-350ccd35fdf8"
}
},
// 同上布局模板输出数据
pageLayoutInfo: {
layoutData: [...]
divAbsoluteLayoutData: [...]
}
]
把containerList元素中的image字段同步到divAbsoluteLayoutData元素中
2.如何还原图例?
- canvas上画图片
- 再增加一个Absolute div层只做展示(目前采用方案)
LayoutGrid.vue 跟'高亮选中区' 等区域一样增加'报表二次编辑布局图例展示区'
- 如何保证操作后containerList元素中已有图例的uuid不变
- layoutData 转 divAbsoluteLayoutData 需要改造 reportLayoutEdit.vue 中 save() 方法
const { layoutController: lc } = this.$refs.layoutGrid
const divs = []
const { cols, mRanges, rows } = lc.getData()
const rowItemsHeightArry = []
for (const i in rows.rowItems) {
rowItemsHeightArry.push(rows.rowItems[i].height)
}
const colsItemsWidthArry = []
for (const i in cols.colItems) {
colsItemsWidthArry.push(cols.colItems[i].width)
}
for (const i in rows.rowItems) {
const colTemp = []
for (const j in cols.colItems) {
colTemp.push({
width: cols.colItems[j].width,
height: rows.rowItems[i].height,
top: _.dropRight(rowItemsHeightArry, rowItemsHeightArry.length - i).reduce((t, i) => { return t + i }, 0),
left: j * cols.colItems[j].width,
show: true
})
}
// todo 与老的 divAbsoluteLayoutData 比较是否审查新的uuid
divs.push(colTemp)
}
const merges = mRanges.map(item => {
return mexpr2mxy(item)
})
const mergedivs = []
merges.forEach(item => {
const mergeInfo = {
show: true,
width: 0,
height: 0
}
for (let i = item.sri; i <= item.eri; i++) {
mergeInfo.top = divs[item.sri][item.sci].top
mergeInfo.left = divs[item.sri][item.sci].left
const opRow = divs[i]
for (let j = item.sci; j <= item.eci; j++) {
opRow[j].show = false
mergeInfo.width += opRow[j].width
mergeInfo.height += opRow[j].height
}
}
mergeInfo.width = mergeInfo.width / (item.eri - item.sri + 1)
mergeInfo.height = mergeInfo.height / (item.eci - item.sci + 1)
// todo 与老的 divAbsoluteLayoutData 比较是否审查新的uuid
mergedivs.push(mergeInfo)
})
const final = divs.flat().filter(item => item.show).concat(mergedivs)
// 删除 不能统一生成uuid
final.forEach(item => {
item.uuid = uuidv4()
})
4.哪些操作会影响老的divAbsoluteLayoutData中的元素top和height
- 增加行
- 删除行
- 拖拉行