diff --git a/code/fms/fms-vue/src/components/ui/input/input.vue b/code/fms/fms-vue/src/components/ui/input/input.vue index efe4cc16..8853990a 100644 --- a/code/fms/fms-vue/src/components/ui/input/input.vue +++ b/code/fms/fms-vue/src/components/ui/input/input.vue @@ -34,6 +34,7 @@ const slots = useSlots() /* ---------- 公共状态 ---------- */ const focused = ref(false) const hovered = ref(false) +const inputEl = ref(null) /* ---------- 公共 computed ---------- */ const isTextarea = computed(() => props.type === 'textarea') @@ -255,6 +256,13 @@ onMounted(() => { nextTick(measureDefaultTextareaHeight) }) +/* 程序化聚焦/选中文本(内联重命名等场景需要 focus + select) */ +defineExpose({ + focus: () => inputEl.value?.focus(), + select: () => inputEl.value?.select(), + blur: () => inputEl.value?.blur(), +}) + watch( () => [props.rows, isTextarea.value], () => { @@ -285,6 +293,7 @@ onBeforeUnmount(() => {
- +
diff --git a/code/fms/fms-vue/src/views/system/module-management/components/structure-tree/ModuleStructureTree.vue b/code/fms/fms-vue/src/views/system/module-management/components/structure-tree/ModuleStructureTree.vue index e9a6da51..15defeb4 100644 --- a/code/fms/fms-vue/src/views/system/module-management/components/structure-tree/ModuleStructureTree.vue +++ b/code/fms/fms-vue/src/views/system/module-management/components/structure-tree/ModuleStructureTree.vue @@ -14,7 +14,7 @@ * - 选中:受控 selectedKey,双向同步面板属性面板 / 预览高亮。 * - 键盘导航:roving tabindex(↑↓←→ / Enter / Space)。 */ -import { computed, nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue' +import { computed, nextTick, onBeforeUnmount, provide, ref, watch } from 'vue' import { Clipboard, Copy, @@ -25,6 +25,7 @@ import { Trash2, } from '@lucide/vue' import draggable from 'vuedraggable' +import { Dropdown } from '@/components/ui' import StructureTreeNode from './StructureTreeNode.vue' import { useGroupTree } from '../../composables/useGroupTree' import { useFieldLookup } from '../../composables/useFieldLookup' @@ -523,12 +524,17 @@ function resetDragVisual() { removeDragBadge() } -/* ================= 右键菜单 ================= */ -const contextState = ref(null) // { items, x, y, node, key } -const contextMenuEl = ref(null) +/* ================= 右键菜单(ui Dropdown,trigger=contextmenu,鼠标位置锚定) ================= */ +// 右键目标节点:由 StructureTreeNode 在 contextmenu 冒泡前写入 +const contextNode = ref(null) -function buildContextItems(node) { - if (node.key === 'g:ungrouped') return [] +function setContextNode(node) { + contextNode.value = node +} + +const contextItems = computed(() => { + const node = contextNode.value + if (!node || node.key === 'g:ungrouped') return [] if (node.kind === 'group') { return [ { key: 'add-sub', label: '新增子分组', icon: FolderPlus }, @@ -543,58 +549,14 @@ function buildContextItems(node) { { key: 'paste', label: '粘贴列属性', icon: Clipboard, disabled: !props.pasteEnabled }, { key: 'delete', label: '删除列', icon: Trash2, danger: true }, ] -} - -function openContextMenu(node, event) { - const items = buildContextItems(node) - if (!items || !items.length) return - contextState.value = { items, x: event.clientX, y: event.clientY, node, key: node.key } - nextTick(() => { - const el = contextMenuEl.value - if (!el || !contextState.value) return - const rect = el.getBoundingClientRect() - const vw = window.innerWidth - const vh = window.innerHeight - let { x, y } = contextState.value - if (x + rect.width > vw - 8) x = Math.max(8, x - rect.width) - if (y + rect.height > vh - 8) y = Math.max(8, y - rect.height) - contextState.value.x = x - contextState.value.y = y - }) -} - -function hideContextMenu() { - contextState.value = null -} - -function onMenuItemClick(item, event) { - if (item.disabled) return - const state = contextState.value - emit('context-menu-click', { - item, - node: state?.node ?? null, - key: state?.key ?? null, - event, - }) - hideContextMenu() -} - -function onDocMouseDown(e) { - if (!contextState.value) return - const el = contextMenuEl.value - if (el && el.contains(e.target)) return - hideContextMenu() -} -function onDocKeydown(e) { - if (e.key === 'Escape') hideContextMenu() -} -onMounted(() => { - document.addEventListener('mousedown', onDocMouseDown, true) - document.addEventListener('keydown', onDocKeydown, true) }) + +function onMenuItemClick({ item }) { + const node = contextNode.value + emit('context-menu-click', { item, node, key: node?.key ?? null }) +} + onBeforeUnmount(() => { - document.removeEventListener('mousedown', onDocMouseDown, true) - document.removeEventListener('keydown', onDocKeydown, true) // 拖拽进行中卸载:清理鼠标/键盘监听与跟手卡片,避免泄漏 if (dragKey.value) cleanupDrag() }) @@ -668,66 +630,44 @@ provide('moduleStructureTree', { onNodeMove, onNodeDelete, onNodeAddSub, - openContextMenu, + setContextNode, commitRename, cancelRename, })