feat: 添加文件选择和自动查找功能,优化模板创建界面

This commit is contained in:
zhanggy committed 2025-10-28 14:00:16 +08:00
1 parent de7af2f650
commit d6d86e5042
4 files changed
+169 -41

No files matched your search

+49 -1
View File
@@ -5,7 +5,8 @@ import {
writeFileSync, writeFileSync,
readFileSync, readFileSync,
existsSync, existsSync,
ensureDirSync ensureDirSync,
copyFileSync
} from 'fs-extra' } from 'fs-extra'
/* /*
@@ -35,6 +36,8 @@ export default class IPCs {
filters.push({ name: 'Text', extensions: ['txt', 'json'] }) filters.push({ name: 'Text', extensions: ['txt', 'json'] })
} else if (filter === 'zip') { } else if (filter === 'zip') {
filters.push({ name: 'Zip', extensions: ['zip'] }) filters.push({ name: 'Zip', extensions: ['zip'] })
} else if (filter === 'any') {
filters.push({ name: 'All Files', extensions: ['*'] })
} }
const dialogResult = await dialog.showOpenDialog({ const dialogResult = await dialog.showOpenDialog({
properties: ['openFile'], properties: ['openFile'],
@@ -60,6 +63,43 @@ export default class IPCs {
return { canceled: true } 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 // Load data from local file
ipcMain.handle( ipcMain.handle(
'msgLoadData', 'msgLoadData',
@@ -148,9 +188,17 @@ export default class IPCs {
const filePath = join(baseDirPath, file.path) const filePath = join(baseDirPath, file.path)
const fileDir = dirname(filePath) const fileDir = dirname(filePath)
ensureDirSync(fileDir) ensureDirSync(fileDir)
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') writeFileSync(filePath, file.content, 'utf-8')
} }
} }
}
return { success: true } return { success: true }
} catch (error) { } catch (error) {
+2 -1
View File
@@ -13,7 +13,8 @@ const mainAvailChannels: string[] = [
'msgSelectDirectory', 'msgSelectDirectory',
'msgLoadData', 'msgLoadData',
'msgSaveData', 'msgSaveData',
'msgExecuteTemplate' 'msgExecuteTemplate',
'msgFindFilesInFolder'
] ]
const rendererAvailChannels: string[] = [] const rendererAvailChannels: string[] = []
+116 -38
View File
@@ -196,7 +196,7 @@
<!-- Add File Dialog --> <!-- Add File Dialog -->
<v-dialog <v-dialog
v-model="showFileDialog" v-model="showFileDialog"
max-width="500" max-width="600"
> >
<v-card> <v-card>
<v-card-title>添加文件</v-card-title> <v-card-title>添加文件</v-card-title>
@@ -204,14 +204,52 @@
<v-form v-model="fileFormValid"> <v-form v-model="fileFormValid">
<v-text-field <v-text-field
v-model="newFilePath" v-model="newFilePath"
label="文件路径 *" label="目标文件路径 *"
placeholder="例如: src/index.js" placeholder="例如: src/index.js"
:rules="[requiredRule]" :rules="[requiredRule]"
required required
density="comfortable" density="comfortable"
ref="fileInputRef" ref="fileInputRef"
@keydown.enter="confirmAddFile" @keydown.enter="selectAndAddFile"
/> />
<div class="d-flex align-center my-4">
<v-btn
@click="selectSourceFile"
variant="outlined"
class="mr-2"
>
选择文件
</v-btn>
<v-btn
@click="selectSourceFolder"
variant="outlined"
class="mr-2"
>
选择文件夹
</v-btn>
</div>
<!-- Display selected source -->
<div v-if="selectedSourcePath" class="d-flex align-center">
<v-icon :color="isAutoFindMode ? 'warning' : 'info'" class="mr-2">
{{ isAutoFindMode ? 'mdi-folder' : 'mdi-file' }}
</v-icon>
<span class="text-truncate">{{ selectedSourcePath }}</span>
</div>
<!-- Auto-find indicator -->
<v-alert
v-if="isAutoFindMode"
type="info"
variant="tonal"
class="mt-2"
>
<div class="d-flex align-center">
<v-icon class="mr-2">mdi-auto-fix</v-icon>
<span>自动寻找文件夹内的文件</span>
</div>
</v-alert>
</v-form> </v-form>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
@@ -219,10 +257,10 @@
<v-btn @click="showFileDialog = false"> 取消 </v-btn> <v-btn @click="showFileDialog = false"> 取消 </v-btn>
<v-btn <v-btn
color="primary" color="primary"
@click="confirmAddFile" @click="selectAndAddFile"
:disabled="!fileFormValid" :disabled="!fileFormValid || !selectedSourcePath"
> >
添加并编辑 添加
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@@ -248,9 +286,9 @@
<v-spacer /> <v-spacer />
<v-btn <v-btn
variant="text" variant="text"
@click="saveFileContent" @click="showEditorDialog = false"
> >
保存 关闭
</v-btn> </v-btn>
</v-toolbar> </v-toolbar>
@@ -337,6 +375,8 @@ const newFilePath = ref('')
const editingFile = ref<{ path: string; content: string } | null>(null) const editingFile = ref<{ path: string; content: string } | null>(null)
const editingFileContent = ref('') const editingFileContent = ref('')
const parentDirectoryForNewItems = ref<string | null>(null) // Track parent context const parentDirectoryForNewItems = ref<string | null>(null) // Track parent context
const selectedSourcePath = ref<string>('') // Track the selected source file or folder
const isAutoFindMode = ref(false) // Track if auto-find mode is enabled
// Tree state // Tree state
const expandedNodes = ref<string[]>([]) const expandedNodes = ref<string[]>([])
@@ -352,7 +392,7 @@ interface TreeItem {
// Item state // Item state
const directories = ref<{ path: string }[]>([]) const directories = ref<{ path: string }[]>([])
const files = ref<{ path: string; content: string }[]>([]) const files = ref<{ path: string; content?: string; sourceFilePath?: string }[]>([])
// Computed // Computed
const isEditMode = computed(() => !!route.params.id) const isEditMode = computed(() => !!route.params.id)
@@ -570,7 +610,7 @@ const confirmAddDirectory = () => {
currentContextItem.value = null currentContextItem.value = null
} }
const confirmAddFile = () => { const selectAndAddFile = async () => {
if (!newFilePath.value.trim()) return if (!newFilePath.value.trim()) return
// Construct the full path if there's a parent directory // Construct the full path if there's a parent directory
@@ -590,20 +630,61 @@ const confirmAddFile = () => {
return 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 = { const newFile = {
path: fullPath, path: fullTargetPath,
content: '' sourceFilePath: foundFile
}
files.value.push(newFile)
}
} }
files.value.push(newFile) // Reset selection state
selectedSourcePath.value = ''
// Open editor for the new file isAutoFindMode.value = false
editFileContent(newFile)
showFileDialog.value = false showFileDialog.value = false
newFilePath.value = '' newFilePath.value = ''
parentDirectoryForNewItems.value = null // Clear parent context after creation parentDirectoryForNewItems.value = null // Clear parent context after creation
currentContextItem.value = null 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)
// 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
}
// Otherwise, show file selection dialog
showSnackbar('请先选择一个文件或文件夹', 'warning')
} }
const goBack = () => { const goBack = () => {
@@ -625,28 +706,8 @@ const collapseAll = () => {
} }
const editFile = (item: TreeItem) => { const editFile = (item: TreeItem) => {
const file = files.value.find((f) => `file-${f.path}` === item.id) // For now, just show a message that files are selected, not edited
if (file) { showSnackbar('该文件是从源文件复制的,无法直接编辑', 'info')
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
} }
const deleteItem = (item: TreeItem) => { const deleteItem = (item: TreeItem) => {
@@ -668,6 +729,23 @@ const showSnackbar = (message: string, color: string = 'error') => {
snackbarVisible.value = true 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 // Auto focus methods
const autoFocusDirectoryInput = async () => { const autoFocusDirectoryInput = async () => {
await nextTick() await nextTick()
+2 -1
View File
@@ -3,7 +3,8 @@ import { ref } from 'vue'
interface TemplateFile { interface TemplateFile {
path: string 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 { interface TemplateDirectory {