20260915221013

This commit is contained in:
oneao committed 2026-09-15 22:10:13 +08:00
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 被