feat: 优化模板创建界面,添加编辑文件和目录功能,增强用户体验
This commit is contained in:
1 parent
d6d86e5042
commit
4367fd6ed1
3 files changed
+327
-86
No files matched your search
@@ -151,9 +151,11 @@
|
||||
@showContextMenu="showContextMenu"
|
||||
@add-new-item-to-current-directory="addNewItemToCurrentDirectory"
|
||||
@edit-file="editFile"
|
||||
@edit-item="editItem"
|
||||
@delete-item="deleteItem"
|
||||
@add-directory="addDirectory"
|
||||
@add-file="addFile"
|
||||
@add-file-to-directory="addFileToDirectory"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -164,13 +166,13 @@
|
||||
max-width="500"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>添加目录</v-card-title>
|
||||
<v-card-title>{{ currentEditingItem ? '编辑目录' : '添加目录' }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form v-model="directoryFormValid">
|
||||
<v-text-field
|
||||
v-model="newDirectoryPath"
|
||||
label="目录路径 *"
|
||||
placeholder="例如: src/components"
|
||||
:label="currentEditingItem ? '新目录路径 *' : '目录路径 *'"
|
||||
:placeholder="currentEditingItem ? '例如: src/new-components' : '例如: src/components'"
|
||||
:rules="[requiredRule]"
|
||||
required
|
||||
density="comfortable"
|
||||
@@ -181,13 +183,17 @@
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn @click="showDirectoryDialog = false"> 取消 </v-btn>
|
||||
<v-btn
|
||||
@click="closeDirectoryDialog"
|
||||
>
|
||||
取消
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="confirmAddDirectory"
|
||||
:disabled="!directoryFormValid"
|
||||
>
|
||||
添加
|
||||
{{ currentEditingItem ? '保存' : '添加' }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
@@ -199,13 +205,13 @@
|
||||
max-width="600"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>添加文件</v-card-title>
|
||||
<v-card-title>{{ currentEditingItem ? '编辑文件' : '添加文件' }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form v-model="fileFormValid">
|
||||
<v-text-field
|
||||
v-model="newFilePath"
|
||||
label="目标文件路径 *"
|
||||
placeholder="例如: src/index.js"
|
||||
:label="currentEditingItem ? '新文件路径 *' : '目标文件路径 *'"
|
||||
:placeholder="currentEditingItem ? '例如: src/new-index.js' : '例如: src/index.js'"
|
||||
:rules="[requiredRule]"
|
||||
required
|
||||
density="comfortable"
|
||||
@@ -218,6 +224,7 @@
|
||||
@click="selectSourceFile"
|
||||
variant="outlined"
|
||||
class="mr-2"
|
||||
:disabled="!!currentEditingItem"
|
||||
>
|
||||
选择文件
|
||||
</v-btn>
|
||||
@@ -225,6 +232,7 @@
|
||||
@click="selectSourceFolder"
|
||||
variant="outlined"
|
||||
class="mr-2"
|
||||
:disabled="!!currentEditingItem"
|
||||
>
|
||||
选择文件夹
|
||||
</v-btn>
|
||||
@@ -250,17 +258,34 @@
|
||||
<span>自动寻找文件夹内的文件</span>
|
||||
</div>
|
||||
</v-alert>
|
||||
|
||||
<!-- Note when editing -->
|
||||
<v-alert
|
||||
v-if="currentEditingItem"
|
||||
type="warning"
|
||||
variant="tonal"
|
||||
class="mt-2"
|
||||
>
|
||||
<div class="d-flex align-center">
|
||||
<v-icon class="mr-2">mdi-information</v-icon>
|
||||
<span>正在编辑现有文件路径,文件源不会改变</span>
|
||||
</div>
|
||||
</v-alert>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn @click="showFileDialog = false"> 取消 </v-btn>
|
||||
<v-btn
|
||||
@click="closeFileDialog"
|
||||
>
|
||||
取消
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="selectAndAddFile"
|
||||
:disabled="!fileFormValid || !selectedSourcePath"
|
||||
:disabled="!fileFormValid || (!selectedSourcePath && !currentEditingItem)"
|
||||
>
|
||||
添加
|
||||
{{ currentEditingItem ? '保存' : '添加' }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
@@ -570,6 +595,15 @@ const addFile = (parentItem: TreeItem | null = null) => {
|
||||
autoFocusFileInput()
|
||||
}
|
||||
|
||||
// Add a file directly to a specific directory
|
||||
const addFileToDirectory = (directoryItem: TreeItem) => {
|
||||
showContextMenuVar.value = false
|
||||
parentDirectoryForNewItems.value = directoryItem.name // Store the directory as parent
|
||||
newFilePath.value = '' // Don't pre-fill the path
|
||||
showFileDialog.value = true
|
||||
autoFocusFileInput()
|
||||
}
|
||||
|
||||
const addNewItemToCurrentDirectory = (directoryItem: TreeItem) => {
|
||||
currentContextItem.value = directoryItem
|
||||
parentDirectoryForNewItems.value = directoryItem.name // Store parent context
|
||||
@@ -591,23 +625,87 @@ const confirmAddDirectory = () => {
|
||||
fullPath = parentPath + newDirectoryPath.value
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
const exists = directories.value.some(
|
||||
(d) => d.path === fullPath
|
||||
)
|
||||
if (exists) {
|
||||
showSnackbar('该目录路径已存在!')
|
||||
return
|
||||
}
|
||||
// Check if we're editing an existing directory
|
||||
if (currentEditingItem.value && currentEditingItem.value.type === 'directory') {
|
||||
const index = directories.value.findIndex(d => d.path === currentEditingItem.value?.path)
|
||||
if (index !== -1) {
|
||||
// Update the existing directory path
|
||||
directories.value[index].path = fullPath
|
||||
|
||||
// If the directory path changes, update any files/dirs that were inside it
|
||||
updatePathsForRenamedDirectory(currentEditingItem.value.path, fullPath)
|
||||
}
|
||||
} else {
|
||||
// Check for duplicates only when creating a new directory
|
||||
const exists = directories.value.some(
|
||||
(d) => d.path === fullPath
|
||||
)
|
||||
if (exists) {
|
||||
showSnackbar('该目录路径已存在!')
|
||||
return
|
||||
}
|
||||
|
||||
directories.value.push({
|
||||
path: fullPath
|
||||
})
|
||||
directories.value.push({
|
||||
path: fullPath
|
||||
})
|
||||
}
|
||||
|
||||
showDirectoryDialog.value = false
|
||||
newDirectoryPath.value = ''
|
||||
parentDirectoryForNewItems.value = null // Clear parent context after creation
|
||||
currentContextItem.value = null
|
||||
currentEditingItem.value = null
|
||||
|
||||
// If we added to a parent directory, make sure it's expanded
|
||||
if (parentDirectoryForNewItems.value) {
|
||||
if (!expandedNodes.value.includes(`dir-${parentDirectoryForNewItems.value}`)) {
|
||||
expandedNodes.value.push(`dir-${parentDirectoryForNewItems.value}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to update paths when a directory is renamed
|
||||
const updatePathsForRenamedDirectory = (oldPath: string, newPath: string) => {
|
||||
// Update all files that were in the old directory
|
||||
files.value = files.value.map(file => {
|
||||
if (file.path.startsWith(oldPath + '/') || file.path === oldPath) {
|
||||
return {
|
||||
...file,
|
||||
path: file.path.replace(oldPath, newPath)
|
||||
}
|
||||
}
|
||||
return file
|
||||
})
|
||||
|
||||
// Update all directories that were under the old directory
|
||||
directories.value = directories.value.map(dir => {
|
||||
if (dir.path.startsWith(oldPath + '/') || dir.path === oldPath) {
|
||||
return {
|
||||
...dir,
|
||||
path: dir.path.replace(oldPath, newPath)
|
||||
}
|
||||
}
|
||||
return dir
|
||||
})
|
||||
}
|
||||
|
||||
// Functions to close dialogs and reset state
|
||||
const closeDirectoryDialog = () => {
|
||||
showDirectoryDialog.value = false
|
||||
newDirectoryPath.value = ''
|
||||
parentDirectoryForNewItems.value = null
|
||||
currentContextItem.value = null
|
||||
currentEditingItem.value = null
|
||||
}
|
||||
|
||||
const closeFileDialog = () => {
|
||||
showFileDialog.value = false
|
||||
newFilePath.value = ''
|
||||
parentDirectoryForNewItems.value = null
|
||||
currentContextItem.value = null
|
||||
currentEditingItem.value = null
|
||||
selectedSourcePath.value = ''
|
||||
isAutoFindMode.value = false
|
||||
}
|
||||
|
||||
const selectAndAddFile = async () => {
|
||||
@@ -623,68 +721,93 @@ const selectAndAddFile = async () => {
|
||||
fullPath = parentPath + newFilePath.value
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
const exists = files.value.some((f) => f.path === fullPath)
|
||||
if (exists) {
|
||||
showSnackbar('该文件路径已存在!')
|
||||
return
|
||||
}
|
||||
|
||||
// Check if we're in auto-find mode and have a selected folder
|
||||
if (isAutoFindMode.value && selectedSourcePath.value) {
|
||||
// Auto-find files in the selected folder
|
||||
const foundFiles = await window.mainApi.invoke('msgFindFilesInFolder', selectedSourcePath.value)
|
||||
|
||||
// Add all found files as template files
|
||||
for (const foundFile of foundFiles) {
|
||||
// Calculate relative path from selected folder to the found file
|
||||
const relativePath = foundFile.replace(selectedSourcePath.value, '').substring(1) // Remove leading slash
|
||||
const fullTargetPath = fullPath ? `${fullPath}/${relativePath}` : relativePath
|
||||
|
||||
// Check for duplicates
|
||||
const duplicate = files.value.some(f => f.path === fullTargetPath)
|
||||
if (!duplicate) {
|
||||
const newFile = {
|
||||
path: fullTargetPath,
|
||||
sourceFilePath: foundFile
|
||||
}
|
||||
files.value.push(newFile)
|
||||
// Check if we're editing an existing file
|
||||
if (currentEditingItem.value && currentEditingItem.value.type === 'file') {
|
||||
const index = files.value.findIndex(f => f.path === currentEditingItem.value?.path)
|
||||
if (index !== -1) {
|
||||
// Update the existing file
|
||||
files.value[index].path = fullPath
|
||||
if (selectedSourcePath.value) {
|
||||
files.value[index].sourceFilePath = selectedSourcePath.value
|
||||
}
|
||||
}
|
||||
|
||||
// Reset selection state
|
||||
selectedSourcePath.value = ''
|
||||
isAutoFindMode.value = false
|
||||
|
||||
showFileDialog.value = false
|
||||
newFilePath.value = ''
|
||||
parentDirectoryForNewItems.value = null // Clear parent context after creation
|
||||
currentContextItem.value = null
|
||||
return
|
||||
}
|
||||
|
||||
// If we have a selected file, just add it
|
||||
if (selectedSourcePath.value) {
|
||||
const newFile = {
|
||||
path: fullPath,
|
||||
sourceFilePath: selectedSourcePath.value
|
||||
} else {
|
||||
// Check for duplicates only when creating a new file
|
||||
const exists = files.value.some((f) => f.path === fullPath)
|
||||
if (exists) {
|
||||
showSnackbar('该文件路径已存在!')
|
||||
return
|
||||
}
|
||||
|
||||
files.value.push(newFile)
|
||||
// Check if we're in auto-find mode and have a selected folder
|
||||
if (isAutoFindMode.value && selectedSourcePath.value) {
|
||||
// Auto-find files in the selected folder
|
||||
const foundFiles = await window.mainApi.invoke('msgFindFilesInFolder', selectedSourcePath.value)
|
||||
|
||||
// Add all found files as template files
|
||||
for (const foundFile of foundFiles) {
|
||||
// Calculate relative path from selected folder to the found file
|
||||
const relativePath = foundFile.replace(selectedSourcePath.value, '').substring(1) // Remove leading slash
|
||||
const fullTargetPath = fullPath ? `${fullPath}/${relativePath}` : relativePath
|
||||
|
||||
// Check for duplicates
|
||||
const duplicate = files.value.some(f => f.path === fullTargetPath)
|
||||
if (!duplicate) {
|
||||
const newFile = {
|
||||
path: fullTargetPath,
|
||||
sourceFilePath: foundFile
|
||||
}
|
||||
files.value.push(newFile)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset selection state
|
||||
selectedSourcePath.value = ''
|
||||
isAutoFindMode.value = false
|
||||
|
||||
showFileDialog.value = false
|
||||
newFilePath.value = ''
|
||||
parentDirectoryForNewItems.value = null // Clear parent context after creation
|
||||
currentContextItem.value = null
|
||||
currentEditingItem.value = null
|
||||
return
|
||||
}
|
||||
|
||||
// If we have a selected file, just add it
|
||||
if (selectedSourcePath.value) {
|
||||
const newFile = {
|
||||
path: fullPath,
|
||||
sourceFilePath: selectedSourcePath.value
|
||||
}
|
||||
|
||||
// Reset selection state
|
||||
selectedSourcePath.value = ''
|
||||
isAutoFindMode.value = false
|
||||
files.value.push(newFile)
|
||||
|
||||
showFileDialog.value = false
|
||||
newFilePath.value = ''
|
||||
parentDirectoryForNewItems.value = null // Clear parent context after creation
|
||||
currentContextItem.value = null
|
||||
// Reset selection state
|
||||
selectedSourcePath.value = ''
|
||||
isAutoFindMode.value = false
|
||||
|
||||
showFileDialog.value = false
|
||||
newFilePath.value = ''
|
||||
parentDirectoryForNewItems.value = null // Clear parent context after creation
|
||||
currentContextItem.value = null
|
||||
currentEditingItem.value = null
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, show file selection dialog
|
||||
showSnackbar('请先选择一个文件或文件夹', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
// Reset selection state after editing
|
||||
selectedSourcePath.value = ''
|
||||
isAutoFindMode.value = false
|
||||
|
||||
// Otherwise, show file selection dialog
|
||||
showSnackbar('请先选择一个文件或文件夹', 'warning')
|
||||
showFileDialog.value = false
|
||||
newFilePath.value = ''
|
||||
parentDirectoryForNewItems.value = null
|
||||
currentContextItem.value = null
|
||||
currentEditingItem.value = null
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
@@ -698,7 +821,19 @@ const goBack = () => {
|
||||
}
|
||||
|
||||
const expandAll = () => {
|
||||
expandedNodes.value = treeItems.value.map((item) => item.id)
|
||||
const allIds: string[] = []
|
||||
|
||||
const collectIds = (items: TreeItem[]) => {
|
||||
items.forEach(item => {
|
||||
if (item.children && item.children.length > 0) {
|
||||
allIds.push(item.id)
|
||||
collectIds(item.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
collectIds(treeItems.value)
|
||||
expandedNodes.value = allIds
|
||||
}
|
||||
|
||||
const collapseAll = () => {
|
||||
@@ -710,18 +845,92 @@ const editFile = (item: TreeItem) => {
|
||||
showSnackbar('该文件是从源文件复制的,无法直接编辑', 'info')
|
||||
}
|
||||
|
||||
// Unified method to edit items (both files and directories)
|
||||
// This function needs to find the actual path of the item in our hierarchical tree
|
||||
const editItem = (item: TreeItem) => {
|
||||
if (item.type === 'directory') {
|
||||
// Find the directory path in our directories array
|
||||
// We need to find a directory that ends with the same name as this item
|
||||
const dir = directories.value.find(d => {
|
||||
// If the directory path ends with the item name, or the directory path is the same as item name
|
||||
return d.path === item.name || d.path.endsWith('/' + item.name) || d.path.endsWith('\\' + item.name)
|
||||
})
|
||||
if (dir) {
|
||||
// Show a dialog to edit the directory path
|
||||
showEditDirectoryDialog(dir)
|
||||
} else {
|
||||
showSnackbar('无法找到对应的目录', 'error')
|
||||
}
|
||||
} else if (item.type === 'file') {
|
||||
// Find the file in our files array
|
||||
const file = files.value.find(f => {
|
||||
// If the file path ends with the item name, or the file path is the same as item name
|
||||
return f.path === item.name || f.path.endsWith('/' + item.name) || f.path.endsWith('\\' + item.name)
|
||||
})
|
||||
if (file) {
|
||||
// Show a dialog to edit the file path
|
||||
showEditFileDialog(file)
|
||||
} else {
|
||||
showSnackbar('无法找到对应的文件', 'error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show dialog to edit directory
|
||||
const showEditDirectoryDialog = (directory: { path: string }) => {
|
||||
// Set up for editing an existing directory
|
||||
currentEditingItem.value = { ...directory, type: 'directory' }
|
||||
newDirectoryPath.value = directory.path // Set the current path for editing
|
||||
showDirectoryDialog.value = true
|
||||
autoFocusDirectoryInput()
|
||||
}
|
||||
|
||||
// Show dialog to edit file
|
||||
const showEditFileDialog = (file: { path: string; content?: string; sourceFilePath?: string }) => {
|
||||
// Set up for editing an existing file
|
||||
currentEditingItem.value = { ...file, type: '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
|
||||
isAutoFindMode.value = false
|
||||
}
|
||||
|
||||
showFileDialog.value = true
|
||||
autoFocusFileInput()
|
||||
}
|
||||
|
||||
// Variable to track the current item being edited
|
||||
const currentEditingItem = ref<{ path: string; type: 'directory' | 'file'; content?: string; sourceFilePath?: string } | null>(null)
|
||||
|
||||
const deleteItem = (item: TreeItem) => {
|
||||
if (!confirm('确定要删除此项吗?')) return
|
||||
|
||||
if (item.type === 'directory') {
|
||||
directories.value = directories.value.filter(
|
||||
(d) => `dir-${d.path}` !== item.id
|
||||
)
|
||||
// Find all directories that start with the item name (including the exact match)
|
||||
// This matches the directory itself and all its subdirectories
|
||||
directories.value = directories.value.filter(d => {
|
||||
// Exclude if exact match or it's a subdirectory
|
||||
return !isSubPath(d.path, item.name);
|
||||
});
|
||||
|
||||
// Also remove files that are inside this directory
|
||||
files.value = files.value.filter(f => {
|
||||
// Exclude if it's inside the directory being deleted
|
||||
return !isSubPath(f.path, item.name);
|
||||
});
|
||||
} else {
|
||||
files.value = files.value.filter((f) => `file-${f.path}` !== item.id)
|
||||
// For files, just remove the file
|
||||
files.value = files.value.filter(f => f.path !== item.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check if pathA is a subpath of pathB
|
||||
const isSubPath = (pathA: string, pathB: string) => {
|
||||
return pathA === pathB || pathA.startsWith(pathB + '/') || pathA.startsWith(pathB + '\\');
|
||||
}
|
||||
|
||||
// Show snackbar message
|
||||
const showSnackbar = (message: string, color: string = 'error') => {
|
||||
snackbarMessage.value = message
|
||||
|
||||
Reference in new issue
Block a user