feat: 文件管理器操作替换为自定义模态框
This commit is contained in:
@@ -14,9 +14,10 @@ import { useFileManagerSelection } from '../composables/file-manager/useFileMana
|
||||
import { useFileManagerDragAndDrop } from '../composables/file-manager/useFileManagerDragAndDrop';
|
||||
import { useFileManagerKeyboardNavigation } from '../composables/file-manager/useFileManagerKeyboardNavigation';
|
||||
import FileUploadPopup from './FileUploadPopup.vue';
|
||||
import FileManagerContextMenu from './FileManagerContextMenu.vue';
|
||||
import FileManagerContextMenu from './FileManagerContextMenu.vue';
|
||||
import FileManagerActionModal from './FileManagerActionModal.vue'; // +++ 新增导入 +++
|
||||
import type { FileListItem } from '../types/sftp.types';
|
||||
import type { WebSocketMessage } from '../types/websocket.types';
|
||||
import type { WebSocketMessage } from '../types/websocket.types';
|
||||
|
||||
|
||||
type SftpManagerInstance = ReturnType<typeof createSftpActionsManager>;
|
||||
@@ -124,6 +125,13 @@ const fileListContainerRef = ref<HTMLDivElement | null>(null); // 文件列表
|
||||
const dropOverlayRef = ref<HTMLDivElement | null>(null); // +++ 新增:拖拽蒙版引用 +++
|
||||
// const scrollIntervalId = ref<number | null>(null); // 已移至 useFileManagerDragAndDrop
|
||||
|
||||
// +++ 新增:操作模态框状态 +++
|
||||
const isActionModalVisible = ref(false);
|
||||
const currentActionType = ref<'delete' | 'rename' | 'chmod' | 'newFile' | 'newFolder' | null>(null);
|
||||
const actionItem = ref<FileListItem | null>(null); // For single item operations
|
||||
const actionItems = ref<FileListItem[]>([]); // For multi-item operations (e.g., delete)
|
||||
const actionInitialValue = ref(''); // For pre-filling input in modal
|
||||
|
||||
// +++ 新增:剪贴板状态 +++
|
||||
const clipboardState = ref<ClipboardState>({ hasContent: false });
|
||||
const clipboardSourcePaths = ref<string[]>([]); // 存储源完整路径
|
||||
@@ -275,6 +283,90 @@ const {
|
||||
});
|
||||
|
||||
|
||||
// --- 操作模态框辅助函数 ---
|
||||
const openActionModal = (
|
||||
type: 'delete' | 'rename' | 'chmod' | 'newFile' | 'newFolder',
|
||||
item?: FileListItem | null, // For single item operations like rename, chmod
|
||||
items?: FileListItem[], // For multi-item operations like delete
|
||||
initialValue?: string // For pre-filling input, e.g., old name for rename
|
||||
) => {
|
||||
currentActionType.value = type;
|
||||
actionItem.value = item || null;
|
||||
actionItems.value = items || (item ? [item] : []); // Ensure actionItems has the item(s)
|
||||
actionInitialValue.value = initialValue || '';
|
||||
isActionModalVisible.value = true;
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
isActionModalVisible.value = false;
|
||||
// Reset states if needed, though they'll be overwritten on next open
|
||||
currentActionType.value = null;
|
||||
actionItem.value = null;
|
||||
actionItems.value = [];
|
||||
actionInitialValue.value = '';
|
||||
};
|
||||
|
||||
const handleModalConfirm = (value?: string) => {
|
||||
if (!currentSftpManager.value || !currentActionType.value) {
|
||||
handleModalClose();
|
||||
return;
|
||||
}
|
||||
const manager = currentSftpManager.value;
|
||||
|
||||
switch (currentActionType.value) {
|
||||
case 'delete':
|
||||
if (actionItems.value.length > 0) {
|
||||
manager.deleteItems(actionItems.value);
|
||||
selectedItems.value.clear(); // Clear selection after delete
|
||||
}
|
||||
break;
|
||||
case 'rename':
|
||||
if (actionItem.value && value && value !== actionItem.value.filename) {
|
||||
manager.renameItem(actionItem.value, value);
|
||||
}
|
||||
break;
|
||||
case 'chmod':
|
||||
if (actionItem.value && value && /^[0-7]{3,4}$/.test(value)) {
|
||||
const newMode = parseInt(value, 8);
|
||||
manager.changePermissions(actionItem.value, newMode);
|
||||
} else if (value) { // value exists but is invalid
|
||||
// Optionally, re-open modal with error or use a notification
|
||||
// For now, just log and close
|
||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Invalid chmod value from modal: ${value}`);
|
||||
// It might be better to show an error in the modal itself and not close it.
|
||||
// The modal currently has its own validation, so this path might not be hit often.
|
||||
}
|
||||
break;
|
||||
case 'newFile':
|
||||
if (value) {
|
||||
if (manager.fileList.value.some((item: FileListItem) => item.filename === value)) {
|
||||
// alert(t('fileManager.errors.fileExists', { name: value })); // Consider using modal for this error too
|
||||
console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] File ${value} already exists. Modal should prevent this.`);
|
||||
// Re-open modal or show error in modal
|
||||
// For now, we rely on modal's internal logic or a notification system
|
||||
// To prevent closing, we can avoid calling handleModalClose here if an error occurs.
|
||||
// However, the current modal design closes on confirm.
|
||||
// A more robust solution would be for the modal to emit 'error' or handle validation internally.
|
||||
return; // Prevent closing if error
|
||||
}
|
||||
manager.createFile(value);
|
||||
}
|
||||
break;
|
||||
case 'newFolder':
|
||||
if (value) {
|
||||
if (manager.fileList.value.some((item: FileListItem) => item.filename === value)) {
|
||||
// alert(t('fileManager.errors.folderExists', { name: value }));
|
||||
console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Folder ${value} already exists. Modal should prevent this.`);
|
||||
return; // Prevent closing if error
|
||||
}
|
||||
manager.createDirectory(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
handleModalClose(); // Close modal after action
|
||||
};
|
||||
|
||||
|
||||
// --- SFTP 操作处理函数 (定义在此处,供 Composable 使用) ---
|
||||
const handleDeleteSelectedClick = () => {
|
||||
// 修改:检查 currentSftpManager 是否存在
|
||||
@@ -282,92 +374,36 @@ const handleDeleteSelectedClick = () => {
|
||||
// 使用 props.wsDeps 和 currentSftpManager.value.fileList
|
||||
if (!props.wsDeps.isConnected.value || selectedItems.value.size === 0) return;
|
||||
const itemsToDelete = Array.from(selectedItems.value)
|
||||
.map(filename => currentSftpManager.value?.fileList.value.find((f: FileListItem) => f.filename === filename)) // 从 manager 获取列表
|
||||
.map(filename => currentSftpManager.value?.fileList.value.find((f: FileListItem) => f.filename === filename))
|
||||
.filter((item): item is FileListItem => item !== undefined);
|
||||
if (itemsToDelete.length === 0) return;
|
||||
if (itemsToDelete.length === 0) return;
|
||||
|
||||
const names = itemsToDelete.map(i => i.filename).join(', ');
|
||||
const confirmMsg = itemsToDelete.length > 1
|
||||
? t('fileManager.prompts.confirmDeleteMultiple', { count: itemsToDelete.length, names: names })
|
||||
: itemsToDelete[0].attrs.isDirectory
|
||||
? t('fileManager.prompts.confirmDeleteFolder', { name: itemsToDelete[0].filename })
|
||||
: t('fileManager.prompts.confirmDeleteFile', { name: itemsToDelete[0].filename });
|
||||
|
||||
if (confirm(confirmMsg)) {
|
||||
// 修改:使用 currentSftpManager.value.deleteItems
|
||||
currentSftpManager.value?.deleteItems(itemsToDelete);
|
||||
selectedItems.value.clear();
|
||||
}
|
||||
openActionModal('delete', null, itemsToDelete);
|
||||
};
|
||||
|
||||
const handleRenameContextMenuClick = (item: FileListItem) => { // item 已有类型
|
||||
if (!props.wsDeps.isConnected.value || !item) return; // 恢复使用 props.wsDeps
|
||||
// 修改:检查 currentSftpManager 是否存在
|
||||
if (!currentSftpManager.value) return;
|
||||
const newName = prompt(t('fileManager.prompts.enterNewName', { oldName: item.filename }), item.filename);
|
||||
if (newName && newName !== item.filename) {
|
||||
// 修改:添加 ?. 访问
|
||||
currentSftpManager.value?.renameItem(item, newName);
|
||||
}
|
||||
openActionModal('rename', item, undefined, item.filename);
|
||||
};
|
||||
|
||||
const handleChangePermissionsContextMenuClick = (item: FileListItem) => { // item 已有类型
|
||||
if (!props.wsDeps.isConnected.value || !item) return; // 恢复使用 props.wsDeps
|
||||
if (!currentSftpManager.value) return;
|
||||
const currentModeOctal = (item.attrs.mode & 0o777).toString(8).padStart(3, '0');
|
||||
const newModeStr = prompt(t('fileManager.prompts.enterNewPermissions', { name: item.filename, currentMode: currentModeOctal }), currentModeOctal);
|
||||
if (newModeStr) {
|
||||
if (!/^[0-7]{3,4}$/.test(newModeStr)) {
|
||||
alert(t('fileManager.errors.invalidPermissionsFormat'));
|
||||
return;
|
||||
}
|
||||
const newMode = parseInt(newModeStr, 8);
|
||||
// 修改:在调用前检查 currentSftpManager
|
||||
if (!currentSftpManager.value) {
|
||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot change permissions: SFTP manager not available.`);
|
||||
return;
|
||||
}
|
||||
currentSftpManager.value.changePermissions(item, newMode);
|
||||
}
|
||||
openActionModal('chmod', item, undefined, currentModeOctal);
|
||||
};
|
||||
|
||||
const handleNewFolderContextMenuClick = () => {
|
||||
if (!props.wsDeps.isConnected.value) return; // 恢复使用 props.wsDeps
|
||||
// 修改:检查 currentSftpManager 是否存在
|
||||
if (!currentSftpManager.value) return;
|
||||
const folderName = prompt(t('fileManager.prompts.enterFolderName'));
|
||||
if (folderName) {
|
||||
// 修改:使用 currentSftpManager.value.fileList
|
||||
if (currentSftpManager.value.fileList.value.some((item: FileListItem) => item.filename === folderName)) {
|
||||
alert(t('fileManager.errors.folderExists', { name: folderName }));
|
||||
return;
|
||||
}
|
||||
// 修改:确保在检查后调用,并检查 manager
|
||||
if (!currentSftpManager.value) {
|
||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot create directory: SFTP manager not available.`);
|
||||
return;
|
||||
}
|
||||
currentSftpManager.value.createDirectory(folderName);
|
||||
}
|
||||
openActionModal('newFolder');
|
||||
};
|
||||
|
||||
const handleNewFileContextMenuClick = () => {
|
||||
if (!props.wsDeps.isConnected.value) return; // 恢复使用 props.wsDeps
|
||||
// 修改:检查 currentSftpManager 是否存在
|
||||
if (!currentSftpManager.value) return;
|
||||
const fileName = prompt(t('fileManager.prompts.enterFileName'));
|
||||
if (fileName) {
|
||||
// 修改:使用 currentSftpManager.value.fileList
|
||||
if (currentSftpManager.value.fileList.value.some((item: FileListItem) => item.filename === fileName)) {
|
||||
alert(t('fileManager.errors.fileExists', { name: fileName }));
|
||||
return;
|
||||
}
|
||||
// 修改:确保在检查后调用,并检查 manager
|
||||
if (!currentSftpManager.value) {
|
||||
console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot create file: SFTP manager not available.`);
|
||||
return;
|
||||
}
|
||||
currentSftpManager.value.createFile(fileName);
|
||||
}
|
||||
openActionModal('newFile');
|
||||
};
|
||||
|
||||
// +++ 新增:复制、剪切、粘贴处理函数 +++
|
||||
@@ -1497,11 +1533,22 @@ const handleOpenEditorClick = () => {
|
||||
:is-visible="contextMenuVisible"
|
||||
:position="contextMenuPosition"
|
||||
:items="contextMenuItems"
|
||||
@close-request="hideContextMenu"
|
||||
/>
|
||||
@close-request="hideContextMenu"
|
||||
/>
|
||||
|
||||
<!-- Action Modal -->
|
||||
<FileManagerActionModal
|
||||
:is-visible="isActionModalVisible"
|
||||
:action-type="currentActionType"
|
||||
:item="actionItem"
|
||||
:items="actionItems"
|
||||
:initial-value="actionInitialValue"
|
||||
@close="handleModalClose"
|
||||
@confirm="handleModalConfirm"
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, type PropType, nextTick } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import type { FileListItem } from '../types/sftp.types';
|
||||
|
||||
const props = defineProps({
|
||||
isVisible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
actionType: {
|
||||
type: String as PropType<'delete' | 'rename' | 'chmod' | 'newFile' | 'newFolder' | null>,
|
||||
default: null,
|
||||
},
|
||||
item: { // Used for delete, rename, chmod
|
||||
type: Object as PropType<FileListItem | null>,
|
||||
default: null,
|
||||
},
|
||||
items: { // Used for multi-item delete
|
||||
type: Array as PropType<FileListItem[]>,
|
||||
default: () => [],
|
||||
},
|
||||
initialValue: { // For pre-filling input, e.g., old name for rename, current perms for chmod
|
||||
type: String,
|
||||
default: '',
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: 'confirm', value?: string): void; // value can be new name, new permissions, etc. For delete, value is not needed.
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const inputValue = ref('');
|
||||
const inputRef = ref<HTMLInputElement | null>(null);
|
||||
|
||||
watch(() => props.isVisible, (newValue) => {
|
||||
if (newValue) {
|
||||
inputValue.value = props.initialValue || '';
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus();
|
||||
inputRef.value?.select();
|
||||
});
|
||||
document.addEventListener('keydown', handleGlobalKeydown);
|
||||
} else {
|
||||
document.removeEventListener('keydown', handleGlobalKeydown);
|
||||
}
|
||||
});
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
switch (props.actionType) {
|
||||
case 'delete':
|
||||
if (props.items.length > 1) {
|
||||
return t('fileManager.modals.titles.deleteMultiple', { count: props.items.length });
|
||||
} else if (props.items.length === 1) {
|
||||
// 当删除单个项目时,从 props.items[0] 获取文件名
|
||||
return t('fileManager.modals.titles.delete', { name: props.items[0]?.filename || '' });
|
||||
} else {
|
||||
// Fallback or error case if items is empty, though this shouldn't happen for delete
|
||||
return t('fileManager.modals.titles.delete', { name: '' });
|
||||
}
|
||||
case 'rename':
|
||||
return t('fileManager.modals.titles.rename', { name: props.item?.filename || '' });
|
||||
case 'chmod':
|
||||
return t('fileManager.modals.titles.chmod', { name: props.item?.filename || '' });
|
||||
case 'newFile':
|
||||
return t('fileManager.modals.titles.newFile', 'Create New File');
|
||||
case 'newFolder':
|
||||
return t('fileManager.modals.titles.newFolder', 'Create New Folder');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
const confirmButtonText = computed(() => {
|
||||
switch (props.actionType) {
|
||||
case 'delete':
|
||||
return t('fileManager.modals.buttons.delete', 'Delete');
|
||||
case 'rename':
|
||||
return t('fileManager.modals.buttons.rename', 'Rename');
|
||||
case 'chmod':
|
||||
return t('fileManager.modals.buttons.changePermissions', 'Set Permissions');
|
||||
case 'newFile':
|
||||
case 'newFolder':
|
||||
return t('fileManager.modals.buttons.create', 'Create');
|
||||
default:
|
||||
return t('fileManager.modals.buttons.confirm', 'Confirm');
|
||||
}
|
||||
});
|
||||
|
||||
const messageText = computed(() => {
|
||||
if (props.actionType === 'delete') {
|
||||
if (props.items.length > 1) {
|
||||
const names = props.items.map(i => i.filename).join(', ');
|
||||
return t('fileManager.modals.messages.confirmDeleteMultiple', { count: props.items.length, names: names });
|
||||
} else if (props.items.length === 1 && props.items[0]) {
|
||||
// 当删除单个项目时,从 props.items[0] 获取信息
|
||||
const singleItem = props.items[0];
|
||||
const type = singleItem.attrs.isDirectory
|
||||
? t('fileManager.modals.labels.folder', 'folder')
|
||||
: t('fileManager.modals.labels.file', 'file');
|
||||
return t('fileManager.modals.messages.confirmDelete', { type: type, name: singleItem.filename });
|
||||
}
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
const showInput = computed(() => {
|
||||
return ['rename', 'chmod', 'newFile', 'newFolder'].includes(props.actionType || '');
|
||||
});
|
||||
|
||||
const inputLabel = computed(() => {
|
||||
switch (props.actionType) {
|
||||
case 'rename':
|
||||
return t('fileManager.modals.labels.newName', 'New name:');
|
||||
case 'chmod':
|
||||
return t('fileManager.modals.labels.newPermissions', 'New permissions (octal):');
|
||||
case 'newFile':
|
||||
return t('fileManager.modals.labels.fileName', 'File name:');
|
||||
case 'newFolder':
|
||||
return t('fileManager.modals.labels.folderName', 'Folder name:');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
const inputPlaceholder = computed(() => {
|
||||
switch (props.actionType) {
|
||||
case 'rename':
|
||||
return props.item?.filename || t('fileManager.modals.placeholders.newName', 'Enter new name');
|
||||
case 'chmod':
|
||||
return props.initialValue || '0755';
|
||||
case 'newFile':
|
||||
return t('fileManager.modals.placeholders.newFile', 'Enter file name');
|
||||
case 'newFolder':
|
||||
return t('fileManager.modals.placeholders.newFolder', 'Enter folder name');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
const isConfirmDisabled = computed(() => {
|
||||
if (!showInput.value) return false; // For delete, button is never disabled by input
|
||||
if (!inputValue.value.trim()) return true; // Disable if input is empty
|
||||
if (props.actionType === 'rename' && inputValue.value.trim() === props.item?.filename) return true; // Disable if name is unchanged
|
||||
if (props.actionType === 'chmod' && !/^[0-7]{3,4}$/.test(inputValue.value.trim())) return true; // Disable for invalid chmod format
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
const closeModal = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const confirmAction = () => {
|
||||
if (isConfirmDisabled.value && showInput.value) return; // Re-check, though button should be disabled
|
||||
|
||||
if (props.actionType === 'chmod' && inputValue.value.trim() && !/^[0-7]{3,4}$/.test(inputValue.value.trim())) {
|
||||
// This case should ideally be handled by disabling the button, but as a fallback:
|
||||
// Consider showing an inline error message instead of alert
|
||||
console.warn('Invalid chmod format submitted');
|
||||
return;
|
||||
}
|
||||
emit('confirm', inputValue.value.trim());
|
||||
// closeModal(); // Parent component will close it after action
|
||||
};
|
||||
|
||||
const handleGlobalKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
closeModal();
|
||||
} else if (event.key === 'Enter' && props.isVisible) {
|
||||
if (showInput.value) {
|
||||
if (!isConfirmDisabled.value) {
|
||||
confirmAction();
|
||||
}
|
||||
} else { // For delete confirmation
|
||||
confirmAction();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
import { onUnmounted } from 'vue';
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleGlobalKeydown);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="isVisible" class="fixed inset-0 bg-overlay flex justify-center items-center z-[100] p-4" @click.self="closeModal">
|
||||
<div class="bg-background text-foreground p-5 rounded-lg shadow-xl border border-border w-full max-w-md flex flex-col relative">
|
||||
<!-- Close Button -->
|
||||
<button class="absolute top-3 right-3 p-1 text-text-secondary hover:text-foreground z-10" @click="closeModal" :title="t('fileManager.modals.buttons.close', 'Close')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Title -->
|
||||
<h3 class="text-xl font-semibold text-center mb-4 flex-shrink-0">{{ modalTitle }}</h3>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-grow mb-6 text-sm">
|
||||
<p v-if="actionType === 'delete'" class="text-center whitespace-pre-wrap">
|
||||
{{ messageText }}
|
||||
</p>
|
||||
|
||||
<div v-if="showInput">
|
||||
<label :for="`fileManagerActionInput-${actionType}`" class="block text-sm font-medium text-text-secondary mb-1">
|
||||
{{ inputLabel }}
|
||||
</label>
|
||||
<input
|
||||
:id="`fileManagerActionInput-${actionType}`"
|
||||
ref="inputRef"
|
||||
type="text"
|
||||
v-model="inputValue"
|
||||
:placeholder="inputPlaceholder"
|
||||
class="w-full px-3 py-2 bg-input border border-border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary sm:text-sm text-foreground"
|
||||
/>
|
||||
<p v-if="actionType === 'chmod' && inputValue.trim() && !/^[0-7]{3,4}$/.test(inputValue.trim())" class="mt-1 text-xs text-red-500">
|
||||
{{ t('fileManager.errors.invalidPermissionsFormat', 'Invalid octal format (e.g., 755 or 0755).') }}
|
||||
</p>
|
||||
<p v-else-if="actionType === 'chmod'" class="mt-1 text-xs text-text-tertiary">
|
||||
{{ t('fileManager.modals.chmodHelp', 'Enter permissions in octal format (e.g., 755 or 0755).') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex justify-end gap-3 flex-shrink-0">
|
||||
<button
|
||||
@click="closeModal"
|
||||
type="button"
|
||||
class="px-4 py-2 text-sm font-medium text-text-primary bg-button hover:bg-button-hover border border-border rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 dark:focus:ring-offset-background-dark transition-colors"
|
||||
>
|
||||
{{ t('fileManager.modals.buttons.cancel', 'Cancel') }}
|
||||
</button>
|
||||
<button
|
||||
@click="confirmAction"
|
||||
type="button"
|
||||
:disabled="isConfirmDisabled"
|
||||
class="px-4 py-2 text-sm font-medium text-white rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-background-dark transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
:class="{
|
||||
'bg-red-600 hover:bg-red-700 focus:ring-red-500': actionType === 'delete',
|
||||
'bg-primary hover:bg-primary-hover focus:ring-primary': actionType !== 'delete'
|
||||
}"
|
||||
>
|
||||
{{ confirmButtonText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Styles can be further refined or rely on global Tailwind utility classes */
|
||||
</style>
|
||||
@@ -338,7 +338,45 @@
|
||||
"dropFilesHere": "Drop files here to upload",
|
||||
"changeEncodingTooltip": "Change file encoding",
|
||||
"loadingEncoding": "Loading...",
|
||||
"noSearchResults": "No search results found"
|
||||
"noSearchResults": "No search results found",
|
||||
"modals": {
|
||||
"titles": {
|
||||
"delete": "Delete \"{name}\"",
|
||||
"deleteMultiple": "Delete {count} Items",
|
||||
"rename": "Rename \"{name}\"",
|
||||
"chmod": "Change Permissions for \"{name}\"",
|
||||
"newFile": "Create New File",
|
||||
"newFolder": "Create New Folder"
|
||||
},
|
||||
"buttons": {
|
||||
"delete": "Delete",
|
||||
"rename": "Rename",
|
||||
"changePermissions": "Set Permissions",
|
||||
"create": "Create",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"close": "Close"
|
||||
},
|
||||
"messages": {
|
||||
"confirmDelete": "Are you sure you want to delete the {type} \"{name}\"? This action cannot be undone.",
|
||||
"confirmDeleteMultiple": "Are you sure you want to delete these {count} items? This action cannot be undone.\nItems: {names}"
|
||||
},
|
||||
"labels": {
|
||||
"newName": "New name:",
|
||||
"newPermissions": "New permissions (octal):",
|
||||
"fileName": "File name:",
|
||||
"folderName": "Folder name:",
|
||||
"folder": "folder",
|
||||
"file": "file"
|
||||
},
|
||||
"placeholders": {
|
||||
"newName": "Enter new name",
|
||||
"newPermissions": "e.g., 755 or 0755",
|
||||
"newFile": "Enter file name",
|
||||
"newFolder": "Enter folder name"
|
||||
},
|
||||
"chmodHelp": "Enter permissions in octal format (e.g., 755 or 0755)."
|
||||
}
|
||||
},
|
||||
"statusMonitor": {
|
||||
"title": "Server Status",
|
||||
|
||||
@@ -336,9 +336,47 @@
|
||||
},
|
||||
"changeEncodingTooltip": "ファイルエンコーディングを変更",
|
||||
"loadingEncoding": "読み込み中...",
|
||||
"noSearchResults": "検索結果が見つかりませんでした"
|
||||
},
|
||||
"focusSwitcher": {
|
||||
"noSearchResults": "検索結果が見つかりませんでした",
|
||||
"modals": {
|
||||
"titles": {
|
||||
"delete": "\"{name}\" を削除",
|
||||
"deleteMultiple": "{count} 個のアイテムを削除",
|
||||
"rename": "\"{name}\" の名前を変更",
|
||||
"chmod": "\"{name}\" の権限を変更",
|
||||
"newFile": "新しいファイルを作成",
|
||||
"newFolder": "新しいフォルダーを作成"
|
||||
},
|
||||
"buttons": {
|
||||
"delete": "削除",
|
||||
"rename": "名前を変更",
|
||||
"changePermissions": "権限を設定",
|
||||
"create": "作成",
|
||||
"confirm": "確認",
|
||||
"cancel": "キャンセル",
|
||||
"close": "閉じる"
|
||||
},
|
||||
"messages": {
|
||||
"confirmDelete": "{type} \"{name}\" を削除してもよろしいですか?この操作は元に戻せません。",
|
||||
"confirmDeleteMultiple": "これらの {count} 個のアイテムを削除してもよろしいですか?この操作は元に戻せません。\nアイテム: {names}"
|
||||
},
|
||||
"labels": {
|
||||
"newName": "新しい名前:",
|
||||
"newPermissions": "新しい権限 (8進数):",
|
||||
"fileName": "ファイル名:",
|
||||
"folderName": "フォルダー名:",
|
||||
"folder": "フォルダー",
|
||||
"file": "ファイル"
|
||||
},
|
||||
"placeholders": {
|
||||
"newName": "新しい名前を入力",
|
||||
"newPermissions": "例: 755 または 0755",
|
||||
"newFile": "ファイル名を入力",
|
||||
"newFolder": "フォルダー名を入力"
|
||||
},
|
||||
"chmodHelp": "8進数形式で権限を入力してください (例: 755 または 0755)。"
|
||||
}
|
||||
},
|
||||
"focusSwitcher": {
|
||||
"allInputsConfigured": "すべての利用可能な入力ソースが設定されました",
|
||||
"altSwitchHint": "ヒント:Alt キーを押すと、設定された入力ソース間でフォーカスを素早く切り替えることができます。",
|
||||
"availableInputs": "利用可能な入力ソース",
|
||||
|
||||
@@ -338,7 +338,45 @@
|
||||
"dropFilesHere": "将文件拖拽到此处上传",
|
||||
"changeEncodingTooltip": "更改文件编码",
|
||||
"loadingEncoding": "加载中...",
|
||||
"noSearchResults": "未找到匹配的搜索结果"
|
||||
"noSearchResults": "未找到匹配的搜索结果",
|
||||
"modals": {
|
||||
"titles": {
|
||||
"delete": "删除 \"{name}\"",
|
||||
"deleteMultiple": "删除 {count} 个项目",
|
||||
"rename": "重命名 \"{name}\"",
|
||||
"chmod": "修改 \"{name}\" 的权限",
|
||||
"newFile": "创建新文件",
|
||||
"newFolder": "创建新文件夹"
|
||||
},
|
||||
"buttons": {
|
||||
"delete": "删除",
|
||||
"rename": "重命名",
|
||||
"changePermissions": "设置权限",
|
||||
"create": "创建",
|
||||
"confirm": "确认",
|
||||
"cancel": "取消",
|
||||
"close": "关闭"
|
||||
},
|
||||
"messages": {
|
||||
"confirmDelete": "您确定要删除{type} \"{name}\" 吗?此操作无法撤销。",
|
||||
"confirmDeleteMultiple": "您确定要删除这 {count} 个项目吗?此操作无法撤销。\n项目: {names}"
|
||||
},
|
||||
"labels": {
|
||||
"newName": "新名称:",
|
||||
"newPermissions": "新权限 (八进制):",
|
||||
"fileName": "文件名:",
|
||||
"folderName": "文件夹名称:",
|
||||
"folder": "文件夹",
|
||||
"file": "文件"
|
||||
},
|
||||
"placeholders": {
|
||||
"newName": "输入新名称",
|
||||
"newPermissions": "例如 755 或 0755",
|
||||
"newFile": "输入文件名",
|
||||
"newFolder": "输入文件夹名称"
|
||||
},
|
||||
"chmodHelp": "请输入八进制格式的权限 (例如 755 或 0755)。"
|
||||
}
|
||||
},
|
||||
"statusMonitor": {
|
||||
"title": "服务器状态",
|
||||
|
||||
Reference in New Issue
Block a user