20260915221013
This commit is contained in:
1 parent
1f56cc3efb
commit
dce29f250e
54 files changed
+3732
-574
No files matched your search
@@ -21,17 +21,14 @@ import { FolderTree, Save, RefreshCw } from '@lucide/vue'
|
||||
import { Button, Splitter, SplitterPanel, Modal, Message, Spin } from '@/components/ui'
|
||||
import { loadDataApi, saveObjectApi } from '@/services/api'
|
||||
import {
|
||||
addRowUpdate,
|
||||
applyTreePosition,
|
||||
buildSortedTree,
|
||||
computeTreeMove,
|
||||
decorateTreeNodes,
|
||||
expandAncestors,
|
||||
findNodePosition,
|
||||
findTreeNode,
|
||||
insertTreeNode,
|
||||
rebuildTreePositions,
|
||||
removeTreeNode,
|
||||
renumberSiblings,
|
||||
sortTree,
|
||||
} from '@/utils/tree'
|
||||
import { cloneData, diffRow, diffRows } from '@/utils/dataChanges'
|
||||
@@ -41,12 +38,7 @@ import I18nNameModal from '@/components/I18nNameModal.vue'
|
||||
import MenuTreePanel from './MenuTreePanel.vue'
|
||||
import MenuBasicPanel from './MenuBasicPanel.vue'
|
||||
import ModulePickerModal from './ModulePickerModal.vue'
|
||||
import {
|
||||
MENU_NON_LEAF_TYPES,
|
||||
MENU_TYPE_ICON_MAP,
|
||||
buildMenuI18nKey,
|
||||
menuRowOf,
|
||||
} from './utils'
|
||||
import { MENU_NON_LEAF_TYPES, MENU_TYPE_ICON_MAP, buildMenuI18nKey, menuRowOf } from './utils'
|
||||
import { useI18nStore } from '@/stores/i18n'
|
||||
|
||||
/** 生成安全 SQL 字符串字面量(search_condition 为后端拼 SQL,需要转义引号) */
|
||||
@@ -210,73 +202,25 @@ function onMenuAdd({ parentId, menuType: newType }) {
|
||||
}
|
||||
|
||||
async function onMenuDrop({ dragKey, dropKey, dropPosition }) {
|
||||
const dragId = String(dragKey)
|
||||
const dropId = String(dropKey)
|
||||
if (dragId === dropId) return
|
||||
|
||||
const dragPos = findNodePosition(menuTreeData.value, dragId)
|
||||
const dropPos = findNodePosition(menuTreeData.value, dropId)
|
||||
if (!dragPos || !dropPos) return
|
||||
|
||||
const dragNode = dragPos.list[dragPos.index]
|
||||
const dropNode = dropPos.list[dropPos.index]
|
||||
// 落点不能是被拖节点自身或它的后代:父子关系成环后树与 b_path 都会失真
|
||||
if (findTreeNode([dragNode], dropId)) {
|
||||
Message.warning('不能把菜单移动到它自己的下级')
|
||||
// 成环 / 类型 / 未保存守卫与 b_xh、b_depth、b_path、b_parent_id 的重算都在 computeTreeMove 内
|
||||
const result = computeTreeMove(menuTreeData.value, {
|
||||
dragKey,
|
||||
dropKey,
|
||||
dropPosition,
|
||||
typeField: 'b_menu_type',
|
||||
nonLeafValues: MENU_NON_LEAF_TYPES,
|
||||
isUnsaved: (id) => unsavedMenuIds.has(id),
|
||||
messages: {
|
||||
cycle: '不能把菜单移动到它自己的下级',
|
||||
notContainer: '只能将菜单放入目录下',
|
||||
unsavedTarget: '请先保存目标菜单,再调整层级',
|
||||
},
|
||||
})
|
||||
if (!result.ok) {
|
||||
if (result.message) Message.warning(result.message)
|
||||
return
|
||||
}
|
||||
// 只有目录能承载子菜单:页面/外链是末端节点,放入会形成「页面挂子菜单」的非法层级。
|
||||
// 必须在移除被拖节点之前拦截,否则中途 return 会让节点凭空消失。
|
||||
if (dropPosition === 'inside' && !MENU_NON_LEAF_TYPES.includes(dropNode.b_menu_type)) {
|
||||
Message.warning('只能将菜单放入目录下')
|
||||
return
|
||||
}
|
||||
// 未保存的目录尚未落库,其临时 id 不能作为持久化的 b_parent_id
|
||||
if (dropPosition === 'inside' && unsavedMenuIds.has(dropId)) {
|
||||
Message.warning('请先保存目标菜单,再调整层级')
|
||||
return
|
||||
}
|
||||
// 先记录落点节点信息,再从原位置移除被拖节点
|
||||
const dropParentId = dropNode.b_parent_id ?? null
|
||||
const [moved] = dragPos.list.splice(dragPos.index, 1)
|
||||
const movedParentId = moved.b_parent_id ?? null
|
||||
|
||||
const updatedRows = []
|
||||
const renumberSets = new Set()
|
||||
|
||||
if (dropPosition === 'inside') {
|
||||
// 成为落点节点的子节点
|
||||
moved.b_parent_id = dropNode.b_id
|
||||
if (!dropNode.children) {
|
||||
dropNode.children = []
|
||||
dropNode.isLeaf = false
|
||||
}
|
||||
dropNode.children.push(moved)
|
||||
renumberSets.add(dropNode.children)
|
||||
renumberSets.add(dragPos.list)
|
||||
} else {
|
||||
// 同级插入:落点节点前 / 后
|
||||
moved.b_parent_id = dropParentId
|
||||
let targetList = dropPos.list
|
||||
let targetIndex = dropPos.index + (dropPosition === 'after' ? 1 : 0)
|
||||
// 同一组内拖拽且被拖节点在落点之前:移除后索引需前移一位
|
||||
if (dragPos.list === dropPos.list && dragPos.index < dropPos.index) {
|
||||
targetIndex -= 1
|
||||
}
|
||||
targetList.splice(targetIndex, 0, moved)
|
||||
renumberSets.add(targetList)
|
||||
}
|
||||
|
||||
renumberSets.forEach((list) => renumberSiblings(list, updatedRows))
|
||||
// 父子关系变化后整树重建 b_depth / b_path,仅收集实际变化的行
|
||||
rebuildTreePositions(menuTreeData.value, updatedRows)
|
||||
// b_parent_id 是权威关系,必须与本节点的派生字段同批落库,
|
||||
// 否则会留下「路径按新父级、父级列还是旧值」的不一致。
|
||||
// 单独补一条:renumberSiblings / rebuildTreePositions 只收集实际变化的字段值,
|
||||
// 它俩不负责父级列本身。
|
||||
if (String(moved.b_parent_id ?? '') !== String(movedParentId ?? '')) {
|
||||
addRowUpdate(updatedRows, { b_id: moved.b_id, b_parent_id: moved.b_parent_id ?? null })
|
||||
}
|
||||
const { updatedRows } = result
|
||||
|
||||
// 排序即时落库(下方 saveObjectApi),草稿与基线需一起同步:
|
||||
// 只改 maindata 会让已落库的 b_xh / b_parent_id / b_depth / b_path 被
|
||||
|
||||
Reference in new issue
Block a user