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"
|
@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
|
||||||
|
|||||||
Reference in new issue
Block a user