Commit 6a89bf01 by pangchong

refactor(dtd): 优化子节点删除校验逻辑,加入位置特许规则支持

- 调整 canDeleteChild 函数,仅做纯 DTD 约束校验,不包含位置特许规则
- 在编辑器删除权限判断中,新增最后一个子节点可删除的特许规则判断
- 批量删除校验时,新增对父节点原始子节点列表的缓存及最后位置特许判断
- 其他相关删除判断处统一应用最后一个子节点允许删除的规则
- 保留对虚拟文本节点的删除权限判定,避免受 DTD 限制影响
parent 9eac7b69
......@@ -143,7 +143,8 @@ export function canAddChild(parentTagName: string, childTagName: string, current
}
/**
* 判断某个子节点是否可以被删除(考虑 occurrence: once 约束)
* 判断某个子节点是否可以被删除(纯 DTD 校验,不含位置特许规则)
* 位置特许规则(最后一个子节点允许删除)需由调用方在调用此函数前自行判断。
*/
export function canDeleteChild(parentTagName: string, childTagName: string, existingChildTags: string[]): boolean {
const rule = getElementRule(parentTagName)
......
......@@ -243,11 +243,24 @@ export function useEditAreaContextMenu(props: EditAreaContextMenuProps, emit: (e
return false
})
// 是否允许删除(非根节点)
// 是否允许删除(非根节点 + DTD 约束校验
const canDeleteActive = computed(() => {
if (!activeNodeId.value || !editorStore.xmlTree) return false
// 根节点不允许删除
if (activeNodeId.value === editorStore.xmlTree.id) return false
// 虚拟文本节点(不受 DTD 约束)
if (activeNodeId.value.includes('-txt-')) return true
const realId = activeNodeId.value
const mapped = editorStore.nodeMap.get(realId)
if (!mapped || !mapped.parent) return false
const existingTags = mapped.parent.children.map((c) => c.tagName)
// 位置特许规则:父级中最后一个子节点(按位置)允许删除
if (existingTags.length > 0 && existingTags[existingTags.length - 1] === mapped.node.tagName) {
return true
}
return canDeleteChild(mapped.parent.tagName, mapped.node.tagName, existingTags)
})
// 根据 DTD 获取允许添加的子标签列表
......
......@@ -66,6 +66,10 @@ export function useSelectionToolbar() {
if (isVirtualText.value) return true
if (!parentNode.value) return false
const existingTags = parentNode.value.children.map((c) => c.tagName)
// 位置特许规则:父级中最后一个子节点(按位置)允许删除
if (existingTags.length > 0 && existingTags[existingTags.length - 1] === activeNode.value.tagName) {
return true
}
return canDeleteChild(parentNode.value.tagName, activeNode.value.tagName, existingTags)
})
......
......@@ -193,7 +193,29 @@ const partitionBatchDelete = (selectedNodeIds: string[], nodeMap: Map<string, {
}
const remaining = parentRemainingTagsMap.get(parentId)!
// 使用 canDeleteChild 校验在当前 remaining 状态下删除该节点是否合法
// 获取或初始化父节点的原始子节点列表(用于位置判断,不随批量删除而变化)
const parentOriginalTagsKey = `orig:${parentId}`
if (!(parentRemainingTagsMap as any).has(parentOriginalTagsKey)) {
;(parentRemainingTagsMap as any).set(parentOriginalTagsKey, item.parent.children.map((c) => c.tagName))
}
const originalTags: string[] = (parentRemainingTagsMap as any).get(parentOriginalTagsKey)!
// 如果该节点在父级原始子节点列表中是最后一个(按位置),允许删除
const isLastPositionally =
originalTags.length > 0 && originalTags[originalTags.length - 1] === item.node.tagName
if (isLastPositionally) {
const index = remaining.indexOf(item.node.tagName)
if (index !== -1) {
remaining.splice(index, 1)
}
parentRemainingTagsMap.set(parentId, remaining)
safeIds.push(nodeId)
confirmedSafeSet.add(nodeId)
continue
}
// 使用 canDeleteChild 校验在当前 remaining 状态下删除该节点是否合法(排除最后一个位置特许)
const isValid = canDeleteChild(item.parent.tagName, item.node.tagName, remaining)
if (isValid) {
......
......@@ -841,6 +841,10 @@ export function useNodeTree(
? true
: (() => {
const existingTags = parent.children.map((c) => c.tagName)
// 位置特许规则:父级中最后一个子节点(按位置)允许删除
if (existingTags.length > 0 && existingTags[existingTags.length - 1] === node.tagName) {
return true
}
return canDeleteChild(parent.tagName, node.tagName, existingTags)
})()
options.push({
......
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