diff --git a/src/main/IPCs.ts b/src/main/IPCs.ts index 8ae92b4..34c9dba 100644 --- a/src/main/IPCs.ts +++ b/src/main/IPCs.ts @@ -5,7 +5,8 @@ import { writeFileSync, readFileSync, existsSync, - ensureDirSync + ensureDirSync, + copyFileSync } from 'fs-extra' /* @@ -35,6 +36,8 @@ export default class IPCs { filters.push({ name: 'Text', extensions: ['txt', 'json'] }) } else if (filter === 'zip') { filters.push({ name: 'Zip', extensions: ['zip'] }) + } else if (filter === 'any') { + filters.push({ name: 'All Files', extensions: ['*'] }) } const dialogResult = await dialog.showOpenDialog({ properties: ['openFile'], @@ -60,6 +63,43 @@ export default class IPCs { return { canceled: true } }) + // Find all files in a folder recursively + ipcMain.handle( + 'msgFindFilesInFolder', + async (event: IpcMainEvent, folderPath: string) => { + try { + const { readdirSync, statSync } = require('fs') + const { join } = require('path') + + const foundFiles: string[] = [] + + const searchFiles = (dir: string) => { + const items = readdirSync(dir) + + for (const item of items) { + const fullPath = join(dir, item) + const stats = statSync(fullPath) + + if (stats.isDirectory()) { + // Recursively search in subdirectories + searchFiles(fullPath) + } else if (stats.isFile()) { + // Add file to the list + foundFiles.push(fullPath) + } + } + } + + searchFiles(folderPath) + + return foundFiles + } catch (error) { + console.error('Error finding files in folder:', error) + return [] + } + } + ) + // Load data from local file ipcMain.handle( 'msgLoadData', @@ -148,7 +188,15 @@ export default class IPCs { const filePath = join(baseDirPath, file.path) const fileDir = dirname(filePath) ensureDirSync(fileDir) - writeFileSync(filePath, file.content, 'utf-8') + + if (file.sourceFilePath) { + // Copy from source file + const sourceContent = readFileSync(file.sourceFilePath) + writeFileSync(filePath, sourceContent) + } else if (file.content) { + // Write content directly + writeFileSync(filePath, file.content, 'utf-8') + } } } diff --git a/src/preload/index.ts b/src/preload/index.ts index cc5328a..a812abc 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -13,7 +13,8 @@ const mainAvailChannels: string[] = [ 'msgSelectDirectory', 'msgLoadData', 'msgSaveData', - 'msgExecuteTemplate' + 'msgExecuteTemplate', + 'msgFindFilesInFolder' ] const rendererAvailChannels: string[] = [] diff --git a/src/renderer/screens/CreateTemplateScreen.vue b/src/renderer/screens/CreateTemplateScreen.vue index 6aee554..7133dee 100644 --- a/src/renderer/screens/CreateTemplateScreen.vue +++ b/src/renderer/screens/CreateTemplateScreen.vue @@ -196,7 +196,7 @@ 添加文件 @@ -204,14 +204,52 @@ + +
+ + 选择文件 + + + 选择文件夹 + +
+ + +
+ + {{ isAutoFindMode ? 'mdi-folder' : 'mdi-file' }} + + {{ selectedSourcePath }} +
+ + + +
+ mdi-auto-fix + 自动寻找文件夹内的文件 +
+
@@ -219,10 +257,10 @@ 取消 - 添加并编辑 + 添加
@@ -248,9 +286,9 @@ - 保存 + 关闭 @@ -337,6 +375,8 @@ const newFilePath = ref('') const editingFile = ref<{ path: string; content: string } | null>(null) const editingFileContent = ref('') const parentDirectoryForNewItems = ref(null) // Track parent context +const selectedSourcePath = ref('') // Track the selected source file or folder +const isAutoFindMode = ref(false) // Track if auto-find mode is enabled // Tree state const expandedNodes = ref([]) @@ -352,7 +392,7 @@ interface TreeItem { // Item state const directories = ref<{ path: string }[]>([]) -const files = ref<{ path: string; content: string }[]>([]) +const files = ref<{ path: string; content?: string; sourceFilePath?: string }[]>([]) // Computed const isEditMode = computed(() => !!route.params.id) @@ -570,7 +610,7 @@ const confirmAddDirectory = () => { currentContextItem.value = null } -const confirmAddFile = () => { +const selectAndAddFile = async () => { if (!newFilePath.value.trim()) return // Construct the full path if there's a parent directory @@ -590,20 +630,61 @@ const confirmAddFile = () => { return } - const newFile = { - path: fullPath, - content: '' + // 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 + return } + + // If we have a selected file, just add it + if (selectedSourcePath.value) { + const newFile = { + path: fullPath, + sourceFilePath: selectedSourcePath.value + } - files.value.push(newFile) + files.value.push(newFile) - // Open editor for the new file - editFileContent(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 + showFileDialog.value = false + newFilePath.value = '' + parentDirectoryForNewItems.value = null // Clear parent context after creation + currentContextItem.value = null + return + } + + // Otherwise, show file selection dialog + showSnackbar('请先选择一个文件或文件夹', 'warning') } const goBack = () => { @@ -625,28 +706,8 @@ const collapseAll = () => { } const editFile = (item: TreeItem) => { - const file = files.value.find((f) => `file-${f.path}` === item.id) - if (file) { - editFileContent(file) - } -} - -const editFileContent = (file: { path: string; content: string }) => { - editingFile.value = file - editingFileContent.value = file.content - showEditorDialog.value = true -} - -const saveFileContent = () => { - if (editingFile.value) { - const index = files.value.findIndex( - (f) => f.path === editingFile.value?.path - ) - if (index !== -1) { - files.value[index].content = editingFileContent.value - } - } - showEditorDialog.value = false + // For now, just show a message that files are selected, not edited + showSnackbar('该文件是从源文件复制的,无法直接编辑', 'info') } const deleteItem = (item: TreeItem) => { @@ -668,6 +729,23 @@ const showSnackbar = (message: string, color: string = 'error') => { snackbarVisible.value = true } +// Methods for file/folder selection +const selectSourceFile = async () => { + const result = await window.mainApi.invoke('msgOpenFile', 'any') + if (!result.canceled && result.filePaths && result.filePaths.length > 0) { + selectedSourcePath.value = result.filePaths[0] + isAutoFindMode.value = false // Not in auto-find mode when selecting a specific file + } +} + +const selectSourceFolder = async () => { + const result = await window.mainApi.invoke('msgSelectDirectory') + if (!result.canceled && result.filePath) { + selectedSourcePath.value = result.filePath + isAutoFindMode.value = true // In auto-find mode when selecting a folder + } +} + // Auto focus methods const autoFocusDirectoryInput = async () => { await nextTick() diff --git a/src/renderer/store/template.ts b/src/renderer/store/template.ts index 9673843..a497795 100644 --- a/src/renderer/store/template.ts +++ b/src/renderer/store/template.ts @@ -3,7 +3,8 @@ import { ref } from 'vue' interface TemplateFile { path: string - content: string + content?: string // Content for text files that are created directly + sourceFilePath?: string // Path to copy from for file copying } interface TemplateDirectory {