refactor(components): 将文件对话框提取为独立组件
将CreateTemplateScreen中的文件对话框逻辑提取到独立的FileDialog组件中 简化主屏幕代码,提高可维护性和复用性
This commit is contained in:
1 parent
0080de8e0d
commit
ce849b8d66
2 files changed
+191
-170
No files matched your search
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="isVisible"
|
||||
max-width="600"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ isEditMode ? '编辑文件' : '添加文件' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form v-model="formValid">
|
||||
<v-text-field
|
||||
v-model="filePath"
|
||||
label="文件名"
|
||||
placeholder="文件名需要带上后缀,如:style skech.doc"
|
||||
:rules="[requiredRule('文件名')]"
|
||||
required
|
||||
density="comfortable"
|
||||
ref="inputRef"
|
||||
/>
|
||||
|
||||
<!-- 文件路径选择输入框 -->
|
||||
<v-text-field
|
||||
v-model="selectedSourcePath"
|
||||
label="文件或文件夹"
|
||||
placeholder="点击输入框选择文件或文件夹"
|
||||
readonly
|
||||
:rules="isEditMode ? [] : [requiredRule('文件或文件夹')]"
|
||||
:append-inner-icon="'mdi-file'"
|
||||
density="comfortable"
|
||||
class="mt-4"
|
||||
@click="selectSourceFileOrFolder()"
|
||||
/>
|
||||
|
||||
<!-- 选择提示 -->
|
||||
<v-alert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="mt-2"
|
||||
>
|
||||
<div class="d-flex align-start flex-column">
|
||||
<span>选择文件会直接复制到目标位置</span>
|
||||
<span>
|
||||
选择文件夹则会自动匹配该文件夹下的文件进行复制并且重命名
|
||||
</span>
|
||||
</div>
|
||||
</v-alert>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn @click="close">取消</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="confirm"
|
||||
:disabled="!formValid || (!selectedSourcePath && !isEditMode)"
|
||||
>
|
||||
{{ isEditMode ? '保存' : '添加' }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import { TreeNode } from '@/renderer/store/template'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
isEditMode?: boolean
|
||||
parentDirectory?: string | null
|
||||
editingItem?: TreeNode | null
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'confirm', data: { path: string; fullPath: string; sourceFilePath?: string }): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isEditMode: false,
|
||||
parentDirectory: null,
|
||||
editingItem: null
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
// Internal state
|
||||
const isVisible = ref(false)
|
||||
const formValid = ref(false)
|
||||
const filePath = ref('')
|
||||
const selectedSourcePath = ref<string>('')
|
||||
const inputRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
// Computed
|
||||
const isEditMode = computed(() => props.isEditMode)
|
||||
|
||||
// Watch for visibility changes
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
isVisible.value = newValue
|
||||
if (newValue) {
|
||||
// Reset form when dialog opens
|
||||
filePath.value = props.editingItem?.path || ''
|
||||
selectedSourcePath.value = props.editingItem?.sourceFilePath || ''
|
||||
autoFocusInput()
|
||||
}
|
||||
})
|
||||
|
||||
// Watch for internal visibility changes and emit update
|
||||
watch(isVisible, (newValue) => {
|
||||
emit('update:modelValue', newValue)
|
||||
})
|
||||
|
||||
// Validation rules
|
||||
const requiredRule = (fieldName: string) => (v: string | null | undefined) =>
|
||||
!!v || `${fieldName}为必填项`
|
||||
|
||||
// Methods
|
||||
const autoFocusInput = async () => {
|
||||
await nextTick()
|
||||
if (inputRef.value) {
|
||||
;(inputRef.value as HTMLInputElement).focus()
|
||||
}
|
||||
}
|
||||
|
||||
const selectSourceFileOrFolder = async () => {
|
||||
// 使用现有的主进程API来选择文件或文件夹
|
||||
const result = await window.mainApi.invoke('msgSelectFileOrFolder')
|
||||
|
||||
if (result && result.filePath) {
|
||||
selectedSourcePath.value = result.filePath
|
||||
}
|
||||
}
|
||||
|
||||
const confirm = () => {
|
||||
if (!filePath.value.trim()) return
|
||||
|
||||
// Construct the full path if there's a parent directory
|
||||
let fullPath = filePath.value
|
||||
if (props.parentDirectory) {
|
||||
// Make sure parent directory ends with a slash before appending
|
||||
const parentPath = props.parentDirectory.endsWith('/')
|
||||
? props.parentDirectory
|
||||
: props.parentDirectory + '/'
|
||||
fullPath = parentPath + filePath.value
|
||||
}
|
||||
|
||||
emit('confirm', {
|
||||
path: filePath.value,
|
||||
fullPath,
|
||||
sourceFilePath: selectedSourcePath.value || undefined
|
||||
})
|
||||
|
||||
close()
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
isVisible.value = false
|
||||
filePath.value = ''
|
||||
selectedSourcePath.value = ''
|
||||
}
|
||||
</script>
|
||||
@@ -160,71 +160,15 @@
|
||||
@confirm="handleDirectoryDialogConfirm"
|
||||
/>
|
||||
|
||||
<!-- Add File Dialog -->
|
||||
<v-dialog
|
||||
<!-- File Dialog -->
|
||||
<FileDialog
|
||||
v-model="showFileDialog"
|
||||
max-width="600"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ currentEditingItem ? '编辑文件' : '添加文件' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form v-model="fileFormValid">
|
||||
<v-text-field
|
||||
v-model="newFilePath"
|
||||
:label="'文件名'"
|
||||
:placeholder="'文件名需要带上后缀,如:style skech.doc'"
|
||||
:rules="[requiredRule('文件名')]"
|
||||
required
|
||||
density="comfortable"
|
||||
ref="fileInputRef"
|
||||
:is-edit-mode="!!currentEditingItem"
|
||||
:parent-directory="parentDirectoryForNewItems"
|
||||
:editing-item="currentEditingItem"
|
||||
@confirm="handleFileDialogConfirm"
|
||||
/>
|
||||
|
||||
<!-- 文件路径选择输入框 -->
|
||||
<v-text-field
|
||||
v-model="selectedSourcePath"
|
||||
label="文件或文件夹"
|
||||
placeholder="点击输入框选择文件或文件夹"
|
||||
readonly
|
||||
:rules="currentEditingItem ? [] : [requiredRule('文件或文件夹')]"
|
||||
:append-inner-icon="'mdi-file'"
|
||||
density="comfortable"
|
||||
class="mt-4"
|
||||
@click="selectSourceFileOrFolder()"
|
||||
/>
|
||||
|
||||
<!-- 选择提示 -->
|
||||
<v-alert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="mt-2"
|
||||
>
|
||||
<div class="d-flex align-start flex-column">
|
||||
<span>选择文件会直接复制到目标位置</span>
|
||||
<span>
|
||||
选择文件夹则会自动匹配该文件夹下的文件进行复制并且重命名
|
||||
</span>
|
||||
</div>
|
||||
</v-alert>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn @click="closeFileDialog"> 取消 </v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="selectAndAddFile"
|
||||
:disabled="
|
||||
!fileFormValid || (!selectedSourcePath && !currentEditingItem)
|
||||
"
|
||||
>
|
||||
{{ currentEditingItem ? '保存' : '添加' }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -238,6 +182,7 @@ import { useTemplateStore, TreeNode } from '@/renderer/store/template'
|
||||
import { useSnackbarStore } from '@/renderer/store/snackbar'
|
||||
import TemplateTree from '@/renderer/components/TemplateTree.vue'
|
||||
import DirectoryDialog from '@/renderer/components/DirectoryDialog.vue'
|
||||
import FileDialog from '@/renderer/components/FileDialog.vue'
|
||||
import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -256,10 +201,6 @@ const hasChanges = ref(false)
|
||||
|
||||
// Dialogs
|
||||
const showFileDialog = ref(false)
|
||||
const fileFormValid = ref(false)
|
||||
|
||||
// Input refs for auto-focus
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
// Directory dialog state
|
||||
const showDirectoryDialog = ref(false)
|
||||
@@ -269,9 +210,7 @@ const currentEditingItem = ref<TreeNode | null>(null)
|
||||
|
||||
|
||||
|
||||
// New item state
|
||||
const newFilePath = ref('')
|
||||
const selectedSourcePath = ref<string>('') // Track the selected source file or folder
|
||||
|
||||
|
||||
// Tree state
|
||||
const expandedNodes = ref<string[]>([])
|
||||
@@ -370,9 +309,9 @@ const addRootDirectory = () => {
|
||||
}
|
||||
|
||||
const addRootFile = () => {
|
||||
newFilePath.value = ''
|
||||
parentDirectoryForNewItems.value = null
|
||||
currentEditingItem.value = null
|
||||
showFileDialog.value = true
|
||||
autoFocusFileInput()
|
||||
}
|
||||
|
||||
|
||||
@@ -380,9 +319,8 @@ const addRootFile = () => {
|
||||
// Add a file directly to a specific directory
|
||||
const addFileToDirectory = (directoryItem: TreeItem) => {
|
||||
parentDirectoryForNewItems.value = directoryItem.name // Store the directory as parent
|
||||
newFilePath.value = '' // Don't pre-fill the path
|
||||
currentEditingItem.value = null
|
||||
showFileDialog.value = true
|
||||
autoFocusFileInput()
|
||||
}
|
||||
|
||||
const addNewItemToCurrentDirectory = (directoryItem: TreeItem) => {
|
||||
@@ -523,26 +461,10 @@ const addChildToNode = (
|
||||
|
||||
|
||||
|
||||
const closeFileDialog = () => {
|
||||
showFileDialog.value = false
|
||||
newFilePath.value = ''
|
||||
parentDirectoryForNewItems.value = null
|
||||
currentEditingItem.value = null
|
||||
selectedSourcePath.value = ''
|
||||
}
|
||||
|
||||
const selectAndAddFile = async () => {
|
||||
if (!newFilePath.value.trim()) return
|
||||
|
||||
// Construct the full path if there's a parent directory
|
||||
let fullPath = newFilePath.value
|
||||
if (parentDirectoryForNewItems.value) {
|
||||
// Make sure parent directory ends with a slash before appending
|
||||
const parentPath = parentDirectoryForNewItems.value.endsWith('/')
|
||||
? parentDirectoryForNewItems.value
|
||||
: parentDirectoryForNewItems.value + '/'
|
||||
fullPath = parentPath + newFilePath.value
|
||||
}
|
||||
const handleFileDialogConfirm = (data: { path: string; fullPath: string; sourceFilePath?: string }) => {
|
||||
const { path, fullPath, sourceFilePath } = data
|
||||
|
||||
const newId = `file-${Date.now()}-${Math.random()}`
|
||||
|
||||
@@ -551,10 +473,10 @@ const selectAndAddFile = async () => {
|
||||
// Find and update the existing file in the tree
|
||||
const updatedNode: TreeNode = {
|
||||
id: currentEditingItem.value.id,
|
||||
name: newFilePath.value,
|
||||
name: path,
|
||||
path: fullPath,
|
||||
type: 'file',
|
||||
sourceFilePath: currentEditingItem.value.sourceFilePath
|
||||
sourceFilePath: sourceFilePath
|
||||
}
|
||||
treeStructure.value = updateNodeInTree(
|
||||
treeStructure.value,
|
||||
@@ -571,7 +493,7 @@ const selectAndAddFile = async () => {
|
||||
)
|
||||
if (parentNode && parentNode.children) {
|
||||
const duplicateExists = parentNode.children.some(
|
||||
(child) => child.name === newFilePath.value
|
||||
(child) => child.name === path
|
||||
)
|
||||
if (duplicateExists) {
|
||||
snackbar.error('当前目录下已存在同名节点!')
|
||||
@@ -581,7 +503,7 @@ const selectAndAddFile = async () => {
|
||||
} else {
|
||||
// Check at root level for duplicates
|
||||
const duplicateExists = treeStructure.value.some(
|
||||
(node) => node.name === newFilePath.value
|
||||
(node) => node.name === path
|
||||
)
|
||||
if (duplicateExists) {
|
||||
snackbar.error('根目录下已存在同名节点!')
|
||||
@@ -597,36 +519,13 @@ const selectAndAddFile = async () => {
|
||||
}
|
||||
|
||||
// If we have a selected file, just add it
|
||||
if (selectedSourcePath.value) {
|
||||
// Check for duplicate names in the same parent directory
|
||||
let duplicateNameExists = false
|
||||
if (parentDirectoryForNewItems.value) {
|
||||
// In the parent directory, check if a node with the same name already exists
|
||||
const parentNode = findNodeInTreeByName(
|
||||
treeStructure.value,
|
||||
parentDirectoryForNewItems.value
|
||||
)
|
||||
if (parentNode && parentNode.children) {
|
||||
duplicateNameExists = parentNode.children.some(
|
||||
(child) => child.name === newFilePath.value
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Check at root level for duplicates
|
||||
duplicateNameExists = treeStructure.value.some(
|
||||
(node) => node.name === newFilePath.value
|
||||
)
|
||||
}
|
||||
|
||||
if (duplicateNameExists) {
|
||||
snackbar.error('当前目录下已存在同名节点!')
|
||||
} else {
|
||||
if (sourceFilePath) {
|
||||
const newFileNode: TreeNode = {
|
||||
id: newId,
|
||||
name: newFilePath.value,
|
||||
name: path,
|
||||
path: fullPath,
|
||||
type: 'file',
|
||||
sourceFilePath: selectedSourcePath.value
|
||||
sourceFilePath
|
||||
}
|
||||
|
||||
// Add to tree structure
|
||||
@@ -641,10 +540,12 @@ const selectAndAddFile = async () => {
|
||||
// Add as root node
|
||||
treeStructure.value.push(newFileNode)
|
||||
}
|
||||
} else {
|
||||
// Otherwise, show warning
|
||||
snackbar.warning('请先选择一个文件或文件夹')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Reset selection state
|
||||
selectedSourcePath.value = ''
|
||||
|
||||
showFileDialog.value = false
|
||||
parentDirectoryForNewItems.value = null // Clear parent context after creation
|
||||
@@ -654,29 +555,6 @@ const selectAndAddFile = async () => {
|
||||
nextTick(() => {
|
||||
expandAll()
|
||||
})
|
||||
|
||||
newFilePath.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, show file selection dialog
|
||||
snackbar.warning('请先选择一个文件或文件夹')
|
||||
return
|
||||
}
|
||||
|
||||
// Reset selection state after editing
|
||||
selectedSourcePath.value = ''
|
||||
|
||||
showFileDialog.value = false
|
||||
parentDirectoryForNewItems.value = null
|
||||
currentEditingItem.value = null
|
||||
|
||||
// After adding a new file, expand all nodes to show the new structure
|
||||
nextTick(() => {
|
||||
expandAll()
|
||||
})
|
||||
|
||||
newFilePath.value = ''
|
||||
}
|
||||
|
||||
// 检查树中是否有重复路径的辅助函数
|
||||
@@ -809,15 +687,8 @@ const showEditDirectoryDialog = (directory: TreeNode) => {
|
||||
const showEditFileDialog = (file: TreeNode) => {
|
||||
// Set up for editing an existing file
|
||||
currentEditingItem.value = file
|
||||
newFilePath.value = file.path // Set the current path for editing
|
||||
|
||||
// If the file has a source file path, set it in the selection state
|
||||
if (file.sourceFilePath) {
|
||||
selectedSourcePath.value = file.sourceFilePath
|
||||
}
|
||||
|
||||
parentDirectoryForNewItems.value = null
|
||||
showFileDialog.value = true
|
||||
autoFocusFileInput()
|
||||
}
|
||||
|
||||
|
||||
@@ -854,24 +725,11 @@ const removeItemFromTree = (nodes: TreeNode[], itemId: string): TreeNode[] => {
|
||||
|
||||
|
||||
|
||||
// Methods for file/folder selection
|
||||
const selectSourceFileOrFolder = async () => {
|
||||
// 使用现有的主进程API来选择文件或文件夹
|
||||
const result = await window.mainApi.invoke('msgSelectFileOrFolder')
|
||||
|
||||
if (result && result.filePath) {
|
||||
selectedSourcePath.value = result.filePath
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const autoFocusFileInput = async () => {
|
||||
await nextTick()
|
||||
if (fileInputRef.value) {
|
||||
;(fileInputRef.value as HTMLInputElement).focus()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const saveTemplate = () => {
|
||||
if (!formValid.value) return
|
||||
|
||||
Reference in new issue
Block a user