refactor(components): 将文件对话框提取为独立组件

将CreateTemplateScreen中的文件对话框逻辑提取到独立的FileDialog组件中
简化主屏幕代码,提高可维护性和复用性
This commit is contained in:
zhanggy committed 2025-10-31 10:46:44 +08:00
1 parent 0080de8e0d
commit ce849b8d66
2 files changed
+191 -170

No files matched your search

+163
View File
@@ -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>
+28 -170
View File
@@ -160,71 +160,15 @@
@confirm="handleDirectoryDialogConfirm" @confirm="handleDirectoryDialogConfirm"
/> />
<!-- Add File Dialog --> <!-- File Dialog -->
<v-dialog <FileDialog
v-model="showFileDialog" v-model="showFileDialog"
max-width="600" :is-edit-mode="!!currentEditingItem"
> :parent-directory="parentDirectoryForNewItems"
<v-card> :editing-item="currentEditingItem"
<v-card-title> @confirm="handleFileDialogConfirm"
{{ 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"
/> />
<!-- 文件路径选择输入框 -->
<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 { useSnackbarStore } from '@/renderer/store/snackbar'
import TemplateTree from '@/renderer/components/TemplateTree.vue' import TemplateTree from '@/renderer/components/TemplateTree.vue'
import DirectoryDialog from '@/renderer/components/DirectoryDialog.vue' import DirectoryDialog from '@/renderer/components/DirectoryDialog.vue'
import FileDialog from '@/renderer/components/FileDialog.vue'
import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service' import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service'
const router = useRouter() const router = useRouter()
@@ -256,10 +201,6 @@ const hasChanges = ref(false)
// Dialogs // Dialogs
const showFileDialog = ref(false) const showFileDialog = ref(false)
const fileFormValid = ref(false)
// Input refs for auto-focus
const fileInputRef = ref<HTMLInputElement | null>(null)
// Directory dialog state // Directory dialog state
const showDirectoryDialog = ref(false) 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 // Tree state
const expandedNodes = ref<string[]>([]) const expandedNodes = ref<string[]>([])
@@ -370,9 +309,9 @@ const addRootDirectory = () => {
} }
const addRootFile = () => { const addRootFile = () => {
newFilePath.value = '' parentDirectoryForNewItems.value = null
currentEditingItem.value = null
showFileDialog.value = true showFileDialog.value = true
autoFocusFileInput()
} }
@@ -380,9 +319,8 @@ const addRootFile = () => {
// Add a file directly to a specific directory // Add a file directly to a specific directory
const addFileToDirectory = (directoryItem: TreeItem) => { const addFileToDirectory = (directoryItem: TreeItem) => {
parentDirectoryForNewItems.value = directoryItem.name // Store the directory as parent parentDirectoryForNewItems.value = directoryItem.name // Store the directory as parent
newFilePath.value = '' // Don't pre-fill the path currentEditingItem.value = null
showFileDialog.value = true showFileDialog.value = true
autoFocusFileInput()
} }
const addNewItemToCurrentDirectory = (directoryItem: TreeItem) => { 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 const handleFileDialogConfirm = (data: { path: string; fullPath: string; sourceFilePath?: string }) => {
let fullPath = newFilePath.value const { path, fullPath, sourceFilePath } = data
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 newId = `file-${Date.now()}-${Math.random()}` const newId = `file-${Date.now()}-${Math.random()}`
@@ -551,10 +473,10 @@ const selectAndAddFile = async () => {
// Find and update the existing file in the tree // Find and update the existing file in the tree
const updatedNode: TreeNode = { const updatedNode: TreeNode = {
id: currentEditingItem.value.id, id: currentEditingItem.value.id,
name: newFilePath.value, name: path,
path: fullPath, path: fullPath,
type: 'file', type: 'file',
sourceFilePath: currentEditingItem.value.sourceFilePath sourceFilePath: sourceFilePath
} }
treeStructure.value = updateNodeInTree( treeStructure.value = updateNodeInTree(
treeStructure.value, treeStructure.value,
@@ -571,7 +493,7 @@ const selectAndAddFile = async () => {
) )
if (parentNode && parentNode.children) { if (parentNode && parentNode.children) {
const duplicateExists = parentNode.children.some( const duplicateExists = parentNode.children.some(
(child) => child.name === newFilePath.value (child) => child.name === path
) )
if (duplicateExists) { if (duplicateExists) {
snackbar.error('当前目录下已存在同名节点!') snackbar.error('当前目录下已存在同名节点!')
@@ -581,7 +503,7 @@ const selectAndAddFile = async () => {
} else { } else {
// Check at root level for duplicates // Check at root level for duplicates
const duplicateExists = treeStructure.value.some( const duplicateExists = treeStructure.value.some(
(node) => node.name === newFilePath.value (node) => node.name === path
) )
if (duplicateExists) { if (duplicateExists) {
snackbar.error('根目录下已存在同名节点!') snackbar.error('根目录下已存在同名节点!')
@@ -597,36 +519,13 @@ const selectAndAddFile = async () => {
} }
// If we have a selected file, just add it // If we have a selected file, just add it
if (selectedSourcePath.value) { if (sourceFilePath) {
// 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 {
const newFileNode: TreeNode = { const newFileNode: TreeNode = {
id: newId, id: newId,
name: newFilePath.value, name: path,
path: fullPath, path: fullPath,
type: 'file', type: 'file',
sourceFilePath: selectedSourcePath.value sourceFilePath
} }
// Add to tree structure // Add to tree structure
@@ -641,10 +540,12 @@ const selectAndAddFile = async () => {
// Add as root node // Add as root node
treeStructure.value.push(newFileNode) treeStructure.value.push(newFileNode)
} }
} else {
// Otherwise, show warning
snackbar.warning('请先选择一个文件或文件夹')
return
}
} }
// Reset selection state
selectedSourcePath.value = ''
showFileDialog.value = false showFileDialog.value = false
parentDirectoryForNewItems.value = null // Clear parent context after creation parentDirectoryForNewItems.value = null // Clear parent context after creation
@@ -654,29 +555,6 @@ const selectAndAddFile = async () => {
nextTick(() => { nextTick(() => {
expandAll() 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) => { const showEditFileDialog = (file: TreeNode) => {
// Set up for editing an existing file // Set up for editing an existing file
currentEditingItem.value = file currentEditingItem.value = file
newFilePath.value = file.path // Set the current path for editing parentDirectoryForNewItems.value = null
// If the file has a source file path, set it in the selection state
if (file.sourceFilePath) {
selectedSourcePath.value = file.sourceFilePath
}
showFileDialog.value = true 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 = () => { const saveTemplate = () => {
if (!formValid.value) return if (!formValid.value) return