Commit 66bf1bb7 by pangchong

fix(table-editor): 优化删除行时跨行单元格处理及快照管理

- 在 deleteRow 函数中新增 skipSnapshot 参数,避免多次无效快照
- 删除行时,先获取并解析表格全局布局结构,确保正确定位目标行
- 实现跨行单元格的物理节点转移和 MOREROWS 属性自愈,防止布局错乱
- 移除虚拟占位单元格时,正确扣减上方跨行单元格的 MOREROWS 值
- 用 deleteRow 处理批量删除时跳过重复快照,保证多行删除稳定高效
- 修改合并单元格时先缓存所有选中格 ID,避免提前重置导致合并缺失
- 总体增强了表格编辑器内跨行单元格操作的准确性和数据一致性
parent 9c439bf5
...@@ -424,30 +424,110 @@ export function useTableEditor(props: { node: XmlNode }) { ...@@ -424,30 +424,110 @@ export function useTableEditor(props: { node: XmlNode }) {
store.rebuildNodeMap() store.rebuildNodeMap()
} }
const deleteRow = (node: XmlNode, rowId: string): void => { const deleteRow = (node: XmlNode, rowId: string, skipSnapshot = false): void => {
const tgroup = findTgroup(node) const tgroup = findTgroup(node)
if (!tgroup) return if (!tgroup) return
store.saveSnapshot() // 1. 获取删除前的表格全局布局结构
const structureBefore = parseTable(node)
const allRows = [...structureBefore.theadRows, ...structureBefore.tbodyRows]
const targetRowModel = allRows.find((r) => r.id === rowId)
if (!targetRowModel) return
if (!skipSnapshot) {
store.saveSnapshot()
}
const thead = tgroup.children.find((c) => c.tagName === 'THEAD') const thead = tgroup.children.find((c) => c.tagName === 'THEAD')
const tbody = tgroup.children.find((c) => c.tagName === 'TBODY') const tbody = tgroup.children.find((c) => c.tagName === 'TBODY')
const removeNode = (sectionNode?: XmlNode): boolean => { const sectionNode = thead?.children.some((c) => c.id === rowId) ? thead : tbody
if (!sectionNode) return false if (!sectionNode) return
const idx = sectionNode.children.findIndex((c) => c.id === rowId)
if (idx !== -1) { const rowsList = sectionNode.children.filter((c) => c.tagName === 'ROW')
sectionNode.children.splice(idx, 1) const targetRowIdx = rowsList.findIndex((r) => r.id === rowId)
return true if (targetRowIdx === -1) return
// 2. 处理被跨行覆盖或自身跨行的单元格的转移与自愈
targetRowModel.cells.forEach((cell) => {
if (!cell.isDummy) {
// 如果当前格是物理存在的单元格,且其跨了多行
if (cell.rowspan && cell.rowspan > 1) {
const nextRowIdx = targetRowIdx + 1
const nextRowNode = rowsList[nextRowIdx]
if (nextRowNode) {
const cellXml = findXmlNodeById(tgroup, cell.id)
if (cellXml) {
// 大单元格向下移动一行,跨度减少 1
const nextMorerows = cell.rowspan - 2
if (nextMorerows > 0) {
cellXml.attributes.MOREROWS = nextMorerows.toString()
} else {
delete cellXml.attributes.MOREROWS
}
// 先从原行 children 中移除,再插入下一行,避免同一对象同时属于两个数组
const originRow = rowsList[targetRowIdx]
const originIdx = originRow.children.findIndex((c) => c.id === cell.id)
if (originIdx !== -1) {
originRow.children.splice(originIdx, 1)
}
cellXml.parentId = nextRowNode.id
// 在下一物理行中插到合适的物理位置以维持列顺序
const nextRowModel = allRows.find((r) => r.id === nextRowNode.id)
if (nextRowModel) {
const rightRealCell = nextRowModel.cells.find((c) => (c.colIdx ?? 0) > (cell.colIdx ?? 0) && !c.isDummy)
let insertIdx = -1
if (rightRealCell) {
insertIdx = nextRowNode.children.findIndex((c) => c.id === rightRealCell.id)
}
if (insertIdx !== -1) {
nextRowNode.children.splice(insertIdx, 0, cellXml)
} else {
nextRowNode.children.push(cellXml)
}
}
}
}
}
} else {
// 如果当前格是虚拟占位(它由上方某行的跨行单元格覆盖而来)
let foundSourceCellXml: XmlNode | null = null
for (let r = targetRowIdx - 1; r >= 0; r--) {
const rowModel = allRows.find((rModel) => rModel.id === rowsList[r].id)
if (rowModel) {
const sourceCell = rowModel.cells.find((c) => c.colIdx === cell.colIdx && !c.isDummy)
if (sourceCell) {
foundSourceCellXml = findXmlNodeById(tgroup, sourceCell.id)
break
}
}
}
// 将上方那个真正持有该物理节点的大单元格的 MOREROWS 扣减 1
if (foundSourceCellXml) {
const currentMorerows = parseInt(foundSourceCellXml.attributes.MOREROWS || '0', 10)
if (currentMorerows > 1) {
foundSourceCellXml.attributes.MOREROWS = (currentMorerows - 1).toString()
} else {
delete foundSourceCellXml.attributes.MOREROWS
}
}
} }
return false })
}
if (removeNode(tbody) || removeNode(thead)) { // 3. 物理删除当前行
store.rebuildNodeMap() const rowIdx = sectionNode.children.findIndex((c) => c.id === rowId)
if (rowIdx !== -1) {
sectionNode.children.splice(rowIdx, 1)
} }
store.rebuildNodeMap()
} }
const addColumn = (node: XmlNode, activeColIdx?: number, insertRight = true, cellChildTags?: string[] | null): void => { const addColumn = (node: XmlNode, activeColIdx?: number, insertRight = true, cellChildTags?: string[] | null): void => {
const tgroup = findTgroup(node) const tgroup = findTgroup(node)
if (!tgroup) return if (!tgroup) return
...@@ -660,12 +740,11 @@ export function useTableEditor(props: { node: XmlNode }) { ...@@ -660,12 +740,11 @@ export function useTableEditor(props: { node: XmlNode }) {
const rows = sectionNode.children.filter((c) => c.tagName === 'ROW') const rows = sectionNode.children.filter((c) => c.tagName === 'ROW')
const toDelete = rows.slice(startIdx, startIdx + count).map((r) => r.id) const toDelete = rows.slice(startIdx, startIdx + count).map((r) => r.id)
// 循环调用 deleteRow 逻辑(跳过重复快照),以触发跨行物理节点平移及 MOREROWS 扣减自愈
toDelete.forEach((id) => { toDelete.forEach((id) => {
const idx = sectionNode!.children.findIndex((c) => c.id === id) deleteRow(props.node, id, true)
if (idx !== -1) sectionNode!.children.splice(idx, 1)
}) })
store.rebuildNodeMap()
window.$message.success(`已删除 ${toDelete.length} 行`) window.$message.success(`已删除 ${toDelete.length} 行`)
} }
...@@ -1083,6 +1162,10 @@ export function useTableEditor(props: { node: XmlNode }) { ...@@ -1083,6 +1162,10 @@ export function useTableEditor(props: { node: XmlNode }) {
if (rIdx + rSpan - 1 > maxRow) maxRow = rIdx + rSpan - 1 if (rIdx + rSpan - 1 > maxRow) maxRow = rIdx + rSpan - 1
}) })
// 在更新 selectedCellIds 之前先快照所有被选格 ID,
// 避免提前重置后 mergeMultipleCells 拿到的 cellIds 只剩左上角一个
const allSelectedIds = cells.map((c) => c.id)
const targetRows = isThead ? structure.value.theadRows : structure.value.tbodyRows const targetRows = isThead ? structure.value.theadRows : structure.value.tbodyRows
const topLeftCell = targetRows[minRow]?.cells.find((c) => c.colIdx === minCol) const topLeftCell = targetRows[minRow]?.cells.find((c) => c.colIdx === minCol)
...@@ -1091,9 +1174,10 @@ export function useTableEditor(props: { node: XmlNode }) { ...@@ -1091,9 +1174,10 @@ export function useTableEditor(props: { node: XmlNode }) {
store.setSelectedNodeId(topLeftCell.id) store.setSelectedNodeId(topLeftCell.id)
} }
mergeMultipleCells(props.node, selectedCellIds.value, minCol, maxCol, minRow, maxRow, isThead) mergeMultipleCells(props.node, allSelectedIds, minCol, maxCol, minRow, maxRow, isThead)
} }
const handleSplitSelected = () => { const handleSplitSelected = () => {
if (selectedCellIds.value.length !== 1) return if (selectedCellIds.value.length !== 1) return
const cellId = selectedCellIds.value[0] const cellId = selectedCellIds.value[0]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment