refactor(components): 将目录对话框提取为独立组件
提取原CreateTemplateScreen中的目录对话框逻辑到独立的DirectoryDialog组件 简化主屏幕代码,提高可维护性和复用性
This commit is contained in:
1 parent
ae20a30616
commit
0080de8e0d
2 files changed
+150
-79
No files matched your search
@@ -0,0 +1,124 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog
|
||||||
|
v-model="isVisible"
|
||||||
|
max-width="500"
|
||||||
|
>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
{{ isEditMode ? '编辑目录' : '添加目录' }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form v-model="formValid">
|
||||||
|
<v-text-field
|
||||||
|
v-model="directoryPath"
|
||||||
|
label="目录名称"
|
||||||
|
placeholder="请输入目录名称"
|
||||||
|
:rules="[requiredRule('目录名称')]"
|
||||||
|
required
|
||||||
|
density="comfortable"
|
||||||
|
ref="inputRef"
|
||||||
|
@keydown.enter="confirm"
|
||||||
|
/>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn @click="close">取消</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
@click="confirm"
|
||||||
|
:disabled="!formValid"
|
||||||
|
>
|
||||||
|
{{ 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 }): 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 directoryPath = ref('')
|
||||||
|
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
|
||||||
|
directoryPath.value = props.editingItem?.path || ''
|
||||||
|
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 confirm = () => {
|
||||||
|
if (!directoryPath.value.trim()) return
|
||||||
|
|
||||||
|
// Construct the full path if there's a parent directory
|
||||||
|
let fullPath = directoryPath.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 + directoryPath.value
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('confirm', {
|
||||||
|
path: directoryPath.value,
|
||||||
|
fullPath
|
||||||
|
})
|
||||||
|
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
isVisible.value = false
|
||||||
|
directoryPath.value = ''
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -151,42 +151,14 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
<!-- Add Directory Dialog -->
|
<!-- Directory Dialog -->
|
||||||
<v-dialog
|
<DirectoryDialog
|
||||||
v-model="showDirectoryDialog"
|
v-model="showDirectoryDialog"
|
||||||
max-width="500"
|
:is-edit-mode="!!currentEditingItem"
|
||||||
>
|
:parent-directory="parentDirectoryForNewItems"
|
||||||
<v-card>
|
:editing-item="currentEditingItem"
|
||||||
<v-card-title>
|
@confirm="handleDirectoryDialogConfirm"
|
||||||
{{ currentEditingItem ? '编辑目录' : '添加目录' }}
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-text>
|
|
||||||
<v-form v-model="directoryFormValid">
|
|
||||||
<v-text-field
|
|
||||||
v-model="newDirectoryPath"
|
|
||||||
:label="'目录名称'"
|
|
||||||
:placeholder="'请输入目录名称'"
|
|
||||||
:rules="[requiredRule('目录名称')]"
|
|
||||||
required
|
|
||||||
density="comfortable"
|
|
||||||
ref="directoryInputRef"
|
|
||||||
@keydown.enter="confirmAddDirectory"
|
|
||||||
/>
|
/>
|
||||||
</v-form>
|
|
||||||
</v-card-text>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer />
|
|
||||||
<v-btn @click="closeDirectoryDialog"> 取消 </v-btn>
|
|
||||||
<v-btn
|
|
||||||
color="primary"
|
|
||||||
@click="confirmAddDirectory"
|
|
||||||
:disabled="!directoryFormValid"
|
|
||||||
>
|
|
||||||
{{ currentEditingItem ? '保存' : '添加' }}
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
|
|
||||||
<!-- Add File Dialog -->
|
<!-- Add File Dialog -->
|
||||||
<v-dialog
|
<v-dialog
|
||||||
@@ -265,6 +237,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
import { useTemplateStore, TreeNode } from '@/renderer/store/template'
|
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 { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service'
|
import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -282,21 +255,22 @@ const templateDescription = ref('')
|
|||||||
const hasChanges = ref(false)
|
const hasChanges = ref(false)
|
||||||
|
|
||||||
// Dialogs
|
// Dialogs
|
||||||
const showDirectoryDialog = ref(false)
|
|
||||||
const showFileDialog = ref(false)
|
const showFileDialog = ref(false)
|
||||||
const directoryFormValid = ref(false)
|
|
||||||
const fileFormValid = ref(false)
|
const fileFormValid = ref(false)
|
||||||
|
|
||||||
// Input refs for auto-focus
|
// Input refs for auto-focus
|
||||||
const directoryInputRef = ref<HTMLInputElement | null>(null)
|
|
||||||
const fileInputRef = ref<HTMLInputElement | null>(null)
|
const fileInputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
|
// Directory dialog state
|
||||||
|
const showDirectoryDialog = ref(false)
|
||||||
|
const directoryFormValid = ref(false)
|
||||||
|
const parentDirectoryForNewItems = ref<string | null>(null)
|
||||||
|
const currentEditingItem = ref<TreeNode | null>(null)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// New item state
|
// New item state
|
||||||
const newDirectoryPath = ref('')
|
|
||||||
const newFilePath = ref('')
|
const newFilePath = ref('')
|
||||||
const parentDirectoryForNewItems = ref<string | null>(null) // Track parent context
|
|
||||||
const selectedSourcePath = ref<string>('') // Track the selected source file or folder
|
const selectedSourcePath = ref<string>('') // Track the selected source file or folder
|
||||||
|
|
||||||
// Tree state
|
// Tree state
|
||||||
@@ -390,9 +364,9 @@ const updateTreeHeight = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const addRootDirectory = () => {
|
const addRootDirectory = () => {
|
||||||
newDirectoryPath.value = ''
|
parentDirectoryForNewItems.value = null
|
||||||
|
currentEditingItem.value = null
|
||||||
showDirectoryDialog.value = true
|
showDirectoryDialog.value = true
|
||||||
autoFocusDirectoryInput()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const addRootFile = () => {
|
const addRootFile = () => {
|
||||||
@@ -413,23 +387,12 @@ const addFileToDirectory = (directoryItem: TreeItem) => {
|
|||||||
|
|
||||||
const addNewItemToCurrentDirectory = (directoryItem: TreeItem) => {
|
const addNewItemToCurrentDirectory = (directoryItem: TreeItem) => {
|
||||||
parentDirectoryForNewItems.value = directoryItem.name // Store parent context
|
parentDirectoryForNewItems.value = directoryItem.name // Store parent context
|
||||||
newDirectoryPath.value = '' // Don't pre-fill the path
|
currentEditingItem.value = null
|
||||||
showDirectoryDialog.value = true
|
showDirectoryDialog.value = true
|
||||||
autoFocusDirectoryInput()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmAddDirectory = () => {
|
const handleDirectoryDialogConfirm = (data: { path: string; fullPath: string }) => {
|
||||||
if (!newDirectoryPath.value.trim()) return
|
const { path, fullPath } = data
|
||||||
|
|
||||||
// Construct the full path if there's a parent directory
|
|
||||||
let fullPath = newDirectoryPath.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 + newDirectoryPath.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for duplicate names in the same parent directory
|
// Check for duplicate names in the same parent directory
|
||||||
if (parentDirectoryForNewItems.value) {
|
if (parentDirectoryForNewItems.value) {
|
||||||
@@ -440,7 +403,7 @@ const confirmAddDirectory = () => {
|
|||||||
)
|
)
|
||||||
if (parentNode && parentNode.children) {
|
if (parentNode && parentNode.children) {
|
||||||
const duplicateExists = parentNode.children.some(
|
const duplicateExists = parentNode.children.some(
|
||||||
(child) => child.name === newDirectoryPath.value
|
(child) => child.name === path
|
||||||
)
|
)
|
||||||
if (duplicateExists) {
|
if (duplicateExists) {
|
||||||
snackbar.error('当前目录下已存在同名节点!')
|
snackbar.error('当前目录下已存在同名节点!')
|
||||||
@@ -450,7 +413,7 @@ const confirmAddDirectory = () => {
|
|||||||
} 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 === newDirectoryPath.value
|
(node) => node.name === path
|
||||||
)
|
)
|
||||||
if (duplicateExists) {
|
if (duplicateExists) {
|
||||||
snackbar.error('根目录下已存在同名节点!')
|
snackbar.error('根目录下已存在同名节点!')
|
||||||
@@ -468,7 +431,7 @@ const confirmAddDirectory = () => {
|
|||||||
// Find and update the existing directory in the tree
|
// Find and update the existing directory in the tree
|
||||||
const updatedNode: TreeNode = {
|
const updatedNode: TreeNode = {
|
||||||
id: currentEditingItem.value.id,
|
id: currentEditingItem.value.id,
|
||||||
name: newDirectoryPath.value,
|
name: path,
|
||||||
path: fullPath,
|
path: fullPath,
|
||||||
type: 'directory'
|
type: 'directory'
|
||||||
}
|
}
|
||||||
@@ -481,7 +444,7 @@ const confirmAddDirectory = () => {
|
|||||||
// Create new directory node
|
// Create new directory node
|
||||||
const newNode: TreeNode = {
|
const newNode: TreeNode = {
|
||||||
id: newId,
|
id: newId,
|
||||||
name: newDirectoryPath.value,
|
name: path,
|
||||||
path: fullPath,
|
path: fullPath,
|
||||||
type: 'directory'
|
type: 'directory'
|
||||||
}
|
}
|
||||||
@@ -508,8 +471,6 @@ const confirmAddDirectory = () => {
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
expandAll()
|
expandAll()
|
||||||
})
|
})
|
||||||
|
|
||||||
newDirectoryPath.value = ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在树状结构中更新节点的辅助函数
|
// 在树状结构中更新节点的辅助函数
|
||||||
@@ -560,13 +521,7 @@ const addChildToNode = (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions to close dialogs and reset state
|
|
||||||
const closeDirectoryDialog = () => {
|
|
||||||
showDirectoryDialog.value = false
|
|
||||||
newDirectoryPath.value = ''
|
|
||||||
parentDirectoryForNewItems.value = null
|
|
||||||
currentEditingItem.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeFileDialog = () => {
|
const closeFileDialog = () => {
|
||||||
showFileDialog.value = false
|
showFileDialog.value = false
|
||||||
@@ -846,9 +801,8 @@ const findNodeInTreeByName = (
|
|||||||
const showEditDirectoryDialog = (directory: TreeNode) => {
|
const showEditDirectoryDialog = (directory: TreeNode) => {
|
||||||
// Set up for editing an existing directory
|
// Set up for editing an existing directory
|
||||||
currentEditingItem.value = directory
|
currentEditingItem.value = directory
|
||||||
newDirectoryPath.value = directory.path // Set the current path for editing
|
parentDirectoryForNewItems.value = null
|
||||||
showDirectoryDialog.value = true
|
showDirectoryDialog.value = true
|
||||||
autoFocusDirectoryInput()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show dialog to edit file
|
// Show dialog to edit file
|
||||||
@@ -866,8 +820,7 @@ const showEditFileDialog = (file: TreeNode) => {
|
|||||||
autoFocusFileInput()
|
autoFocusFileInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variable to track the current item being edited
|
|
||||||
const currentEditingItem = ref<TreeNode | null>(null)
|
|
||||||
|
|
||||||
const deleteItem = async (item: TreeItem) => {
|
const deleteItem = async (item: TreeItem) => {
|
||||||
const confirmed = await ConfirmDialogService.confirm({
|
const confirmed = await ConfirmDialogService.confirm({
|
||||||
@@ -911,13 +864,7 @@ const selectSourceFileOrFolder = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto focus methods
|
|
||||||
const autoFocusDirectoryInput = async () => {
|
|
||||||
await nextTick()
|
|
||||||
if (directoryInputRef.value) {
|
|
||||||
;(directoryInputRef.value as HTMLInputElement).focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const autoFocusFileInput = async () => {
|
const autoFocusFileInput = async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|||||||
Reference in new issue
Block a user