refactor(module-management): R2a UI 归一——小文件替换为 ui 组件
- ui Input 补 defineExpose(focus/select/blur),供内联重命名程序化聚焦 - index.vue 树头新建按钮、ModuleBasicPanel 多语言按钮 → ui Button - StructureTreeNode 内联重命名 input → ui Input,六个操作钮 → ui Button - ModuleStructureTree 自绘右键菜单 → ui Dropdown(trigger=contextmenu), 删除自绘定位/关闭监听与 .mst-context-* 样式
This commit is contained in:
1 parent
2dba857880
commit
ce3a9d87f8
5 files changed
+108
-221
No files matched your search
@@ -34,6 +34,7 @@ const slots = useSlots()
|
|||||||
/* ---------- 公共状态 ---------- */
|
/* ---------- 公共状态 ---------- */
|
||||||
const focused = ref(false)
|
const focused = ref(false)
|
||||||
const hovered = ref(false)
|
const hovered = ref(false)
|
||||||
|
const inputEl = ref(null)
|
||||||
|
|
||||||
/* ---------- 公共 computed ---------- */
|
/* ---------- 公共 computed ---------- */
|
||||||
const isTextarea = computed(() => props.type === 'textarea')
|
const isTextarea = computed(() => props.type === 'textarea')
|
||||||
@@ -255,6 +256,13 @@ onMounted(() => {
|
|||||||
nextTick(measureDefaultTextareaHeight)
|
nextTick(measureDefaultTextareaHeight)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/* 程序化聚焦/选中文本(内联重命名等场景需要 focus + select) */
|
||||||
|
defineExpose({
|
||||||
|
focus: () => inputEl.value?.focus(),
|
||||||
|
select: () => inputEl.value?.select(),
|
||||||
|
blur: () => inputEl.value?.blur(),
|
||||||
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [props.rows, isTextarea.value],
|
() => [props.rows, isTextarea.value],
|
||||||
() => {
|
() => {
|
||||||
@@ -285,6 +293,7 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<input
|
<input
|
||||||
v-if="!isTextarea"
|
v-if="!isTextarea"
|
||||||
|
ref="inputEl"
|
||||||
class="input"
|
class="input"
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
:type="inputType"
|
:type="inputType"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { Languages } from '@lucide/vue'
|
import { Languages } from '@lucide/vue'
|
||||||
import { Input, Select, Switch, Form, FormItem } from '@/components/ui'
|
import { Input, Select, Switch, Form, FormItem, Button } from '@/components/ui'
|
||||||
import { buildModuleI18nKey, validateModuleCode } from '@/utils/i18n'
|
import { buildModuleI18nKey, validateModuleCode } from '@/utils/i18n'
|
||||||
import I18nQuickSetModal from './I18nQuickSetModal.vue'
|
import I18nQuickSetModal from './I18nQuickSetModal.vue'
|
||||||
|
|
||||||
@@ -152,14 +152,14 @@ function onI18nSaved(key) {
|
|||||||
<FormItem label="多语言键" name="b_i18n" :span="2">
|
<FormItem label="多语言键" name="b_i18n" :span="2">
|
||||||
<div class="mm-basic__i18n">
|
<div class="mm-basic__i18n">
|
||||||
<Input :model-value="formData.b_i18n" readonly />
|
<Input :model-value="formData.b_i18n" readonly />
|
||||||
<button
|
<Button
|
||||||
type="button"
|
type="ghost"
|
||||||
class="mm-basic__i18n-btn"
|
class="mm-basic__i18n-btn"
|
||||||
title="快速设置多语言"
|
title="快速设置多语言"
|
||||||
@click="openI18nModal"
|
@click="openI18nModal"
|
||||||
>
|
>
|
||||||
<Languages :size="16" />
|
<Languages :size="16" />
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
|
|||||||
+48
-172
@@ -14,7 +14,7 @@
|
|||||||
* - 选中:受控 selectedKey,双向同步面板属性面板 / 预览高亮。
|
* - 选中:受控 selectedKey,双向同步面板属性面板 / 预览高亮。
|
||||||
* - 键盘导航:roving tabindex(↑↓←→ / Enter / Space)。
|
* - 键盘导航:roving tabindex(↑↓←→ / Enter / Space)。
|
||||||
*/
|
*/
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue'
|
import { computed, nextTick, onBeforeUnmount, provide, ref, watch } from 'vue'
|
||||||
import {
|
import {
|
||||||
Clipboard,
|
Clipboard,
|
||||||
Copy,
|
Copy,
|
||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
Trash2,
|
Trash2,
|
||||||
} from '@lucide/vue'
|
} from '@lucide/vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
|
import { Dropdown } from '@/components/ui'
|
||||||
import StructureTreeNode from './StructureTreeNode.vue'
|
import StructureTreeNode from './StructureTreeNode.vue'
|
||||||
import { useGroupTree } from '../../composables/useGroupTree'
|
import { useGroupTree } from '../../composables/useGroupTree'
|
||||||
import { useFieldLookup } from '../../composables/useFieldLookup'
|
import { useFieldLookup } from '../../composables/useFieldLookup'
|
||||||
@@ -523,12 +524,17 @@ function resetDragVisual() {
|
|||||||
removeDragBadge()
|
removeDragBadge()
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================= 右键菜单 ================= */
|
/* ================= 右键菜单(ui Dropdown,trigger=contextmenu,鼠标位置锚定) ================= */
|
||||||
const contextState = ref(null) // { items, x, y, node, key }
|
// 右键目标节点:由 StructureTreeNode 在 contextmenu 冒泡前写入
|
||||||
const contextMenuEl = ref(null)
|
const contextNode = ref(null)
|
||||||
|
|
||||||
function buildContextItems(node) {
|
function setContextNode(node) {
|
||||||
if (node.key === 'g:ungrouped') return []
|
contextNode.value = node
|
||||||
|
}
|
||||||
|
|
||||||
|
const contextItems = computed(() => {
|
||||||
|
const node = contextNode.value
|
||||||
|
if (!node || node.key === 'g:ungrouped') return []
|
||||||
if (node.kind === 'group') {
|
if (node.kind === 'group') {
|
||||||
return [
|
return [
|
||||||
{ key: 'add-sub', label: '新增子分组', icon: FolderPlus },
|
{ key: 'add-sub', label: '新增子分组', icon: FolderPlus },
|
||||||
@@ -543,58 +549,14 @@ function buildContextItems(node) {
|
|||||||
{ key: 'paste', label: '粘贴列属性', icon: Clipboard, disabled: !props.pasteEnabled },
|
{ key: 'paste', label: '粘贴列属性', icon: Clipboard, disabled: !props.pasteEnabled },
|
||||||
{ key: 'delete', label: '删除列', icon: Trash2, danger: true },
|
{ 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(() => {
|
onBeforeUnmount(() => {
|
||||||
document.removeEventListener('mousedown', onDocMouseDown, true)
|
|
||||||
document.removeEventListener('keydown', onDocKeydown, true)
|
|
||||||
// 拖拽进行中卸载:清理鼠标/键盘监听与跟手卡片,避免泄漏
|
// 拖拽进行中卸载:清理鼠标/键盘监听与跟手卡片,避免泄漏
|
||||||
if (dragKey.value) cleanupDrag()
|
if (dragKey.value) cleanupDrag()
|
||||||
})
|
})
|
||||||
@@ -668,66 +630,44 @@ provide('moduleStructureTree', {
|
|||||||
onNodeMove,
|
onNodeMove,
|
||||||
onNodeDelete,
|
onNodeDelete,
|
||||||
onNodeAddSub,
|
onNodeAddSub,
|
||||||
openContextMenu,
|
setContextNode,
|
||||||
commitRename,
|
commitRename,
|
||||||
cancelRename,
|
cancelRename,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mst-tree" role="tree" @keydown="onTreeKeydown" @dragleave="onRootDragLeave">
|
<!-- 右键菜单:Dropdown 以鼠标位置为锚点弹出,节点 contextmenu 冒泡至此触发 -->
|
||||||
<draggable
|
<Dropdown
|
||||||
:list="treeData"
|
trigger="contextmenu"
|
||||||
item-key="key"
|
placement="bottom-start"
|
||||||
handle=".mst-node__grip"
|
:items="contextItems"
|
||||||
:force-fallback="true"
|
@click="onMenuItemClick"
|
||||||
:fallback-on-body="true"
|
>
|
||||||
fallback-class="mst-drag-fallback"
|
<div class="mst-tree" role="tree" @keydown="onTreeKeydown" @dragleave="onRootDragLeave">
|
||||||
:animation="0"
|
<draggable
|
||||||
:move="onSortMove"
|
:list="treeData"
|
||||||
@start="onSortStart"
|
item-key="key"
|
||||||
@end="onSortEnd"
|
handle=".mst-node__grip"
|
||||||
>
|
:force-fallback="true"
|
||||||
<template #item="{ element: n }">
|
:fallback-on-body="true"
|
||||||
<StructureTreeNode :node="n" :level="0" />
|
fallback-class="mst-drag-fallback"
|
||||||
</template>
|
:animation="0"
|
||||||
<template #footer>
|
:move="onSortMove"
|
||||||
<div v-if="!treeData.length" class="mst-tree__empty">
|
@start="onSortStart"
|
||||||
<slot name="empty">暂无分组</slot>
|
@end="onSortEnd"
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</draggable>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 右键菜单(挂载到 body,fixed 定位) -->
|
|
||||||
<Teleport to="body">
|
|
||||||
<Transition name="mst-context-fade">
|
|
||||||
<div
|
|
||||||
v-if="contextState"
|
|
||||||
ref="contextMenuEl"
|
|
||||||
class="mst-context-menu"
|
|
||||||
:style="{ left: contextState.x + 'px', top: contextState.y + 'px' }"
|
|
||||||
role="menu"
|
|
||||||
@mousedown.stop
|
|
||||||
>
|
>
|
||||||
<template v-for="(item, index) in contextState.items" :key="item.key || index">
|
<template #item="{ element: n }">
|
||||||
<div v-if="item.divider" class="mst-context-menu__divider" role="separator"></div>
|
<StructureTreeNode :node="n" :level="0" />
|
||||||
<button
|
|
||||||
v-else
|
|
||||||
type="button"
|
|
||||||
class="mst-context-menu__item"
|
|
||||||
:class="{ 'is-danger': item.danger, 'is-disabled': item.disabled }"
|
|
||||||
role="menuitem"
|
|
||||||
:disabled="item.disabled"
|
|
||||||
@click="(event) => onMenuItemClick(item, event)"
|
|
||||||
>
|
|
||||||
<component :is="item.icon" v-if="item.icon" :size="14" class="mst-context-menu__icon" />
|
|
||||||
<span>{{ item.label }}</span>
|
|
||||||
</button>
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
<template #footer>
|
||||||
</Transition>
|
<div v-if="!treeData.length" class="mst-tree__empty">
|
||||||
</Teleport>
|
<slot name="empty">暂无分组</slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -747,70 +687,6 @@ provide('moduleStructureTree', {
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 右键菜单 ---------- */
|
|
||||||
.mst-context-menu {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 3000;
|
|
||||||
min-width: 140px;
|
|
||||||
padding: 4px;
|
|
||||||
background: var(--fms-card);
|
|
||||||
border: 1px solid var(--fms-border);
|
|
||||||
border-radius: var(--fms-control-radius);
|
|
||||||
box-shadow: var(--fms-shadow);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mst-context-menu__item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px 10px;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--fms-text);
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.5;
|
|
||||||
text-align: left;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
|
||||||
background: var(--fms-secondary-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-danger {
|
|
||||||
color: #f53f3f;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-disabled {
|
|
||||||
color: var(--fms-disabled-text);
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mst-context-menu__icon {
|
|
||||||
flex: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mst-context-menu__divider {
|
|
||||||
height: 1px;
|
|
||||||
margin: 4px 8px;
|
|
||||||
background: var(--fms-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mst-context-fade-enter-active {
|
|
||||||
transition: opacity 0.12s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mst-context-fade-leave-active {
|
|
||||||
transition: opacity 0.08s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mst-context-fade-enter-from,
|
|
||||||
.mst-context-fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- 拖拽标题卡片(全局样式):createDragBadge 用 JS 动态创建元素并挂到 body,
|
<!-- 拖拽标题卡片(全局样式):createDragBadge 用 JS 动态创建元素并挂到 body,
|
||||||
|
|||||||
+45
-43
@@ -6,6 +6,7 @@
|
|||||||
import { computed, inject, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, inject, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import { ArrowDown, ArrowUp, Check, ChevronRight, Folder, FolderPlus, GripVertical, Inbox, Table2, Trash2, X } from '@lucide/vue'
|
import { ArrowDown, ArrowUp, Check, ChevronRight, Folder, FolderPlus, GripVertical, Inbox, Table2, Trash2, X } from '@lucide/vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
|
import { Button, Input } from '@/components/ui'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
node: { type: Object, required: true },
|
node: { type: Object, required: true },
|
||||||
@@ -93,8 +94,13 @@ function onDrop(e) {
|
|||||||
ctx.handleDrop(props.node, e)
|
ctx.handleDrop(props.node, e)
|
||||||
}
|
}
|
||||||
function onContextMenu(e) {
|
function onContextMenu(e) {
|
||||||
if (isUngrouped.value || ctx.loading.value) return
|
if (isUngrouped.value || ctx.loading.value) {
|
||||||
ctx.openContextMenu(props.node, e)
|
// 无菜单目标:阻止冒泡到 Dropdown 触发层,避免弹出空菜单
|
||||||
|
e.stopPropagation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 记录右键目标节点后放行冒泡,由父级 Dropdown(trigger=contextmenu) 在鼠标位置弹出
|
||||||
|
ctx.setContextNode(props.node)
|
||||||
}
|
}
|
||||||
function setEditInput(el) {
|
function setEditInput(el) {
|
||||||
ctx.editInputRef.value = el
|
ctx.editInputRef.value = el
|
||||||
@@ -133,7 +139,7 @@ function onRenameCancel() {
|
|||||||
@dragover="onDragOver"
|
@dragover="onDragOver"
|
||||||
@dragleave="onDragLeave"
|
@dragleave="onDragLeave"
|
||||||
@drop="onDrop"
|
@drop="onDrop"
|
||||||
@contextmenu.stop="onContextMenu"
|
@contextmenu="onContextMenu"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="canDrag"
|
v-if="canDrag"
|
||||||
@@ -160,13 +166,12 @@ function onRenameCancel() {
|
|||||||
<div class="mst-node__content">
|
<div class="mst-node__content">
|
||||||
<!-- 分组带内联重命名 -->
|
<!-- 分组带内联重命名 -->
|
||||||
<template v-if="isEditing">
|
<template v-if="isEditing">
|
||||||
<input
|
<Input
|
||||||
:ref="setEditInput"
|
:ref="setEditInput"
|
||||||
v-model="draft"
|
v-model="draft"
|
||||||
class="mst-node__edit-input"
|
class="mst-node__edit-input"
|
||||||
maxlength="100"
|
maxlength="100"
|
||||||
placeholder="分组名称"
|
placeholder="分组名称"
|
||||||
draggable="false"
|
|
||||||
@click.stop
|
@click.stop
|
||||||
@keydown.stop
|
@keydown.stop
|
||||||
@keydown.enter.prevent="onRenameCommit"
|
@keydown.enter.prevent="onRenameCommit"
|
||||||
@@ -174,24 +179,24 @@ function onRenameCancel() {
|
|||||||
@blur="onRenameCommit"
|
@blur="onRenameCommit"
|
||||||
/>
|
/>
|
||||||
<span class="mst-node__edit-actions">
|
<span class="mst-node__edit-actions">
|
||||||
<button
|
<Button
|
||||||
type="button"
|
type="ghost"
|
||||||
class="mst-node__edit-btn"
|
class="mst-node__edit-btn"
|
||||||
title="确认"
|
title="确认"
|
||||||
aria-label="确认修改分组名"
|
aria-label="确认修改分组名"
|
||||||
@mousedown.prevent="onRenameCommit"
|
@mousedown.prevent="onRenameCommit"
|
||||||
>
|
>
|
||||||
<Check :size="13" />
|
<Check :size="13" />
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
type="button"
|
type="ghost"
|
||||||
class="mst-node__edit-btn"
|
class="mst-node__edit-btn"
|
||||||
title="取消"
|
title="取消"
|
||||||
aria-label="取消修改分组名"
|
aria-label="取消修改分组名"
|
||||||
@mousedown.prevent="onRenameCancel"
|
@mousedown.prevent="onRenameCancel"
|
||||||
>
|
>
|
||||||
<X :size="13" />
|
<X :size="13" />
|
||||||
</button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 普通标题 -->
|
<!-- 普通标题 -->
|
||||||
@@ -205,51 +210,47 @@ function onRenameCancel() {
|
|||||||
|
|
||||||
<!-- 常驻操作:上移 / 下移 / 删除 -->
|
<!-- 常驻操作:上移 / 下移 / 删除 -->
|
||||||
<span v-if="!isUngrouped" class="mst-node__actions">
|
<span v-if="!isUngrouped" class="mst-node__actions">
|
||||||
<button
|
<Button
|
||||||
type="button"
|
type="ghost"
|
||||||
class="mst-node__act"
|
class="mst-node__act"
|
||||||
title="上移"
|
title="上移"
|
||||||
aria-label="上移"
|
aria-label="上移"
|
||||||
draggable="false"
|
|
||||||
:disabled="!canMoveUp"
|
:disabled="!canMoveUp"
|
||||||
@click.stop="onMove(-1)"
|
@click.stop="onMove(-1)"
|
||||||
>
|
>
|
||||||
<ArrowUp :size="12" />
|
<ArrowUp :size="12" />
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
type="button"
|
type="ghost"
|
||||||
class="mst-node__act"
|
class="mst-node__act"
|
||||||
title="下移"
|
title="下移"
|
||||||
aria-label="下移"
|
aria-label="下移"
|
||||||
draggable="false"
|
|
||||||
:disabled="!canMoveDown"
|
:disabled="!canMoveDown"
|
||||||
@click.stop="onMove(1)"
|
@click.stop="onMove(1)"
|
||||||
>
|
>
|
||||||
<ArrowDown :size="12" />
|
<ArrowDown :size="12" />
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
v-if="isGroup && !isUngrouped"
|
v-if="isGroup && !isUngrouped"
|
||||||
type="button"
|
type="ghost"
|
||||||
class="mst-node__act"
|
class="mst-node__act"
|
||||||
title="新增子分组"
|
title="新增子分组"
|
||||||
aria-label="新增子分组"
|
aria-label="新增子分组"
|
||||||
draggable="false"
|
|
||||||
:disabled="!canAddSub"
|
:disabled="!canAddSub"
|
||||||
@click.stop="onAddSub"
|
@click.stop="onAddSub"
|
||||||
>
|
>
|
||||||
<FolderPlus :size="12" />
|
<FolderPlus :size="12" />
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
type="button"
|
type="ghost"
|
||||||
class="mst-node__act is-danger"
|
class="mst-node__act is-danger"
|
||||||
title="删除"
|
title="删除"
|
||||||
aria-label="删除"
|
aria-label="删除"
|
||||||
draggable="false"
|
|
||||||
:disabled="!canDelete"
|
:disabled="!canDelete"
|
||||||
@click.stop="onDelete"
|
@click.stop="onDelete"
|
||||||
>
|
>
|
||||||
<Trash2 :size="12" />
|
<Trash2 :size="12" />
|
||||||
</button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -438,22 +439,20 @@ function onRenameCancel() {
|
|||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mst-node__act {
|
/* 常驻操作按钮:20px 方形图标钮(覆盖 ui Button 默认尺寸) */
|
||||||
|
.mst-node__act.button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex: none;
|
flex: none;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
min-height: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--fms-text-secondary);
|
color: var(--fms-text-secondary);
|
||||||
cursor: pointer;
|
transform: none;
|
||||||
transition:
|
|
||||||
background-color 0.15s ease,
|
|
||||||
color 0.15s ease;
|
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
background: color-mix(in srgb, var(--fms-primary) 10%, transparent);
|
background: color-mix(in srgb, var(--fms-primary) 10%, transparent);
|
||||||
@@ -471,18 +470,15 @@ function onRenameCancel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mst-node__edit-input {
|
/* 内联重命名输入框:压缩 ui Input 的默认高度/内边距以适配 28px 行 */
|
||||||
|
.mst-node__edit-input.input-wrapper {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
|
min-height: 0;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
border: 1px solid var(--fms-primary);
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: var(--fms-card);
|
|
||||||
color: var(--fms-text);
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-family: inherit;
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--fms-primary) 12%, transparent);
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--fms-primary) 12%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,25 +489,31 @@ function onRenameCancel() {
|
|||||||
flex: none;
|
flex: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mst-node__edit-btn {
|
/* 确认/取消:20px 方形图标钮(覆盖 ui Button 默认尺寸) */
|
||||||
|
.mst-node__edit-btn.button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
min-height: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 1px solid var(--fms-border);
|
border: 1px solid var(--fms-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: var(--fms-card);
|
background: var(--fms-card);
|
||||||
color: var(--fms-text-secondary);
|
color: var(--fms-text-secondary);
|
||||||
cursor: pointer;
|
transition:
|
||||||
|
border-color 150ms ease,
|
||||||
|
color 150ms ease;
|
||||||
|
transform: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover:not(:disabled) {
|
||||||
border-color: var(--fms-primary);
|
border-color: var(--fms-primary);
|
||||||
color: var(--fms-primary);
|
color: var(--fms-primary);
|
||||||
|
background: var(--fms-card);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child:hover {
|
&:last-child:hover:not(:disabled) {
|
||||||
border-color: #d95757;
|
border-color: #d95757;
|
||||||
color: #d95757;
|
color: #d95757;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -371,9 +371,9 @@ loadModuleTree()
|
|||||||
</Input>
|
</Input>
|
||||||
<div class="mm-tree-header__actions">
|
<div class="mm-tree-header__actions">
|
||||||
<Dropdown :items="moduleNewItems" @click="onModuleNewClick">
|
<Dropdown :items="moduleNewItems" @click="onModuleNewClick">
|
||||||
<button type="button" class="mm-tree-header__icon-btn" title="新建模块" aria-label="新建模块">
|
<Button type="ghost" class="mm-tree-header__icon-btn" title="新建模块" aria-label="新建模块">
|
||||||
<Plus :size="14" />
|
<Plus :size="14" />
|
||||||
</button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user