feat: 为压缩菜单项添加二级菜单
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type PropType } from 'vue';
|
import { ref, type PropType } from 'vue';
|
||||||
import type { ContextMenuItem } from '../composables/file-manager/useFileManagerContextMenu'; // 导入菜单项类型
|
import type { ContextMenuItem } from '../composables/file-manager/useFileManagerContextMenu'; // 导入菜单项类型
|
||||||
|
import { onUnmounted } from 'vue';
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
isVisible: {
|
isVisible: {
|
||||||
@@ -22,12 +23,38 @@ defineProps({
|
|||||||
const emit = defineEmits(['item-click', 'close-request']); // 添加 close-request
|
const emit = defineEmits(['item-click', 'close-request']); // 添加 close-request
|
||||||
|
|
||||||
const handleItemClick = (item: ContextMenuItem) => {
|
const handleItemClick = (item: ContextMenuItem) => {
|
||||||
if (!item.disabled) {
|
if (!item.disabled && item.action) {
|
||||||
item.action(); // 直接执行 action
|
item.action(); // 只有当 action 存在时才执行
|
||||||
emit('close-request'); // <-- 发出关闭请求
|
emit('close-request'); // <-- 发出关闭请求
|
||||||
// 不需要 emit('item-click', item) 了
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 管理二级菜单的展开状态
|
||||||
|
const expandedSubmenu = ref<string | null>(null);
|
||||||
|
let closeTimeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
const showSubmenu = (label: string) => {
|
||||||
|
if (closeTimeout) {
|
||||||
|
clearTimeout(closeTimeout);
|
||||||
|
closeTimeout = null;
|
||||||
|
}
|
||||||
|
expandedSubmenu.value = label;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideSubmenu = () => {
|
||||||
|
closeTimeout = setTimeout(() => {
|
||||||
|
expandedSubmenu.value = null;
|
||||||
|
closeTimeout = null;
|
||||||
|
}, 300); // 延迟300ms关闭
|
||||||
|
};
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (closeTimeout) {
|
||||||
|
clearTimeout(closeTimeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -41,17 +68,46 @@ const handleItemClick = (item: ContextMenuItem) => {
|
|||||||
<template v-for="(menuItem, index) in items" :key="index">
|
<template v-for="(menuItem, index) in items" :key="index">
|
||||||
<li v-if="menuItem.separator" class="border-t border-border/50 my-1 mx-1"></li>
|
<li v-if="menuItem.separator" class="border-t border-border/50 my-1 mx-1"></li>
|
||||||
<li
|
<li
|
||||||
v-else
|
v-else-if="!menuItem.submenu"
|
||||||
@click.stop="handleItemClick(menuItem)"
|
@click.stop="handleItemClick(menuItem)"
|
||||||
:class="[
|
:class="[
|
||||||
'px-4 py-1.5 cursor-pointer text-foreground text-sm flex items-center transition-colors duration-150 rounded mx-1', // Added mx-1 for consistency
|
'px-4 py-1.5 cursor-pointer text-foreground text-sm flex items-center transition-colors duration-150 rounded mx-1',
|
||||||
menuItem.disabled
|
menuItem.disabled
|
||||||
? 'text-text-secondary cursor-not-allowed opacity-60' // Removed bg-background for disabled
|
? 'text-text-secondary cursor-not-allowed opacity-60'
|
||||||
: 'hover:bg-primary/10 hover:text-primary' // Use primary hover like TabBarContextMenu
|
: 'hover:bg-primary/10 hover:text-primary'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
{{ menuItem.label }}
|
{{ menuItem.label }}
|
||||||
</li>
|
</li>
|
||||||
|
<li
|
||||||
|
v-else
|
||||||
|
class="px-4 py-1.5 text-foreground text-sm flex items-center justify-between transition-colors duration-150 rounded mx-1 hover:bg-primary/10 hover:text-primary relative"
|
||||||
|
@mouseenter="showSubmenu(menuItem.label)"
|
||||||
|
@mouseleave="hideSubmenu()"
|
||||||
|
>
|
||||||
|
{{ menuItem.label }}
|
||||||
|
<span class="ml-2">›</span>
|
||||||
|
<ul
|
||||||
|
v-if="expandedSubmenu === menuItem.label"
|
||||||
|
class="absolute left-full top-0 mt-0 ml-1 bg-background border border-border shadow-lg rounded-md z-[1003] min-w-[150px] list-none p-1"
|
||||||
|
@mouseenter="showSubmenu(menuItem.label)"
|
||||||
|
@mouseleave="hideSubmenu()"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="(subItem, subIndex) in menuItem.submenu"
|
||||||
|
:key="subIndex"
|
||||||
|
@click.stop="handleItemClick(subItem)"
|
||||||
|
:class="[
|
||||||
|
'px-4 py-1.5 cursor-pointer text-foreground text-sm flex items-center transition-colors duration-150 rounded mx-1',
|
||||||
|
subItem.disabled
|
||||||
|
? 'text-text-secondary cursor-not-allowed opacity-60'
|
||||||
|
: 'hover:bg-primary/10 hover:text-primary'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ subItem.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import type FileManagerContextMenu from '../../components/FileManagerContextMenu
|
|||||||
// 定义菜单项类型 (可以根据需要扩展)
|
// 定义菜单项类型 (可以根据需要扩展)
|
||||||
export interface ContextMenuItem {
|
export interface ContextMenuItem {
|
||||||
label: string;
|
label: string;
|
||||||
action: () => void;
|
action?: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
separator?: boolean; // 添加分隔符类型
|
separator?: boolean; // 添加分隔符类型
|
||||||
|
submenu?: ContextMenuItem[]; // 添加二级菜单支持
|
||||||
}
|
}
|
||||||
|
|
||||||
// 支持的压缩格式
|
// 支持的压缩格式
|
||||||
@@ -137,11 +138,14 @@ export function useFileManagerContextMenu(options: UseFileManagerContextMenuOpti
|
|||||||
|
|
||||||
|
|
||||||
// --- 多选压缩 ---
|
// --- 多选压缩 ---
|
||||||
menu.push(
|
menu.push({
|
||||||
{ label: t('fileManager.contextMenu.compressZip'), action: () => onCompressRequest(selectedFileItems, 'zip'), disabled: !(isConnected.value && isSftpReady.value) },
|
label: t('fileManager.contextMenu.compress'),
|
||||||
{ label: t('fileManager.contextMenu.compressTarGz'), action: () => onCompressRequest(selectedFileItems, 'targz'), disabled: !(isConnected.value && isSftpReady.value) },
|
submenu: [
|
||||||
{ label: t('fileManager.contextMenu.compressTarBz2'), action: () => onCompressRequest(selectedFileItems, 'tarbz2'), disabled: !(isConnected.value && isSftpReady.value) },
|
{ label: t('fileManager.contextMenu.compressZip'), action: () => onCompressRequest(selectedFileItems, 'zip'), disabled: !(isConnected.value && isSftpReady.value) },
|
||||||
);
|
{ label: t('fileManager.contextMenu.compressTarGz'), action: () => onCompressRequest(selectedFileItems, 'targz'), disabled: !(isConnected.value && isSftpReady.value) },
|
||||||
|
{ label: t('fileManager.contextMenu.compressTarBz2'), action: () => onCompressRequest(selectedFileItems, 'tarbz2'), disabled: !(isConnected.value && isSftpReady.value) }
|
||||||
|
]
|
||||||
|
});
|
||||||
menu.push({ label: '', action: () => {}, disabled: true, separator: true }); // Separator
|
menu.push({ label: '', action: () => {}, disabled: true, separator: true }); // Separator
|
||||||
|
|
||||||
|
|
||||||
@@ -192,10 +196,15 @@ export function useFileManagerContextMenu(options: UseFileManagerContextMenuOpti
|
|||||||
const canCompress = isConnected.value && isSftpReady.value;
|
const canCompress = isConnected.value && isSftpReady.value;
|
||||||
const canDecompress = isConnected.value && isSftpReady.value && targetItem.attrs.isFile && isSupportedArchive(targetItem.filename);
|
const canDecompress = isConnected.value && isSftpReady.value && targetItem.attrs.isFile && isSupportedArchive(targetItem.filename);
|
||||||
|
|
||||||
// menu.push({ label: t('fileManager.contextMenu.compress'), action: () => {}, disabled: true }); // Removed isSubmenuHeader
|
// 添加压缩选项作为二级菜单
|
||||||
menu.push({ label: t('fileManager.contextMenu.compressZip'), action: () => onCompressRequest([targetItem], 'zip'), disabled: !canCompress });
|
menu.push({
|
||||||
menu.push({ label: t('fileManager.contextMenu.compressTarGz'), action: () => onCompressRequest([targetItem], 'targz'), disabled: !canCompress });
|
label: t('fileManager.contextMenu.compress'),
|
||||||
menu.push({ label: t('fileManager.contextMenu.compressTarBz2'), action: () => onCompressRequest([targetItem], 'tarbz2'), disabled: !canCompress });
|
submenu: [
|
||||||
|
{ label: t('fileManager.contextMenu.compressZip'), action: () => onCompressRequest([targetItem], 'zip'), disabled: !canCompress },
|
||||||
|
{ label: t('fileManager.contextMenu.compressTarGz'), action: () => onCompressRequest([targetItem], 'targz'), disabled: !canCompress },
|
||||||
|
{ label: t('fileManager.contextMenu.compressTarBz2'), action: () => onCompressRequest([targetItem], 'tarbz2'), disabled: !canCompress }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
// 只有在支持解压的文件上才显示解压选项
|
// 只有在支持解压的文件上才显示解压选项
|
||||||
if (canDecompress) {
|
if (canDecompress) {
|
||||||
|
|||||||
Reference in New Issue
Block a user