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

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

No files matched your search

+50 -2
View File
@@ -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')
}
}
}
+2 -1
View File
@@ -13,7 +13,8 @@ const mainAvailChannels: string[] = [
'msgSelectDirectory',
'msgLoadData',
'msgSaveData',
'msgExecuteTemplate'
'msgExecuteTemplate',
'msgFindFilesInFolder'
]
const rendererAvailChannels: string[] = []
+120 -42
View File
@@ -196,7 +196,7 @@
<!-- Add File Dialog -->
<v-dialog
v-model="showFileDialog"
max-width="500"
max-width="600"
>
<v-card>
<v-card-title>添加文件</v-card-title>
@@ -204,14 +204,52 @@
<v-form v-model="fileFormValid">
<v-text-field
v-model="newFilePath"
label="文件路径 *"
label="目标文件路径 *"
placeholder="例如: src/index.js"
:rules="[requiredRule]"
required
density="comfortable"
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-card-text>
<v-card-actions>
@@ -219,10 +257,10 @@
<v-btn @click="showFileDialog = false"> 取消 </v-btn>
<v-btn
color="primary"
@click="confirmAddFile"
:disabled="!fileFormValid"
@click="selectAndAddFile"
:disabled="!fileFormValid || !selectedSourcePath"
>
添加并编辑
添加
</v-btn>
</v-card-actions>
</v-card>
@@ -248,9 +286,9 @@
<v-spacer />
<v-btn
variant="text"
@click="saveFileContent"
@click="showEditorDialog = false"
>
保存
关闭
</v-btn>
</v-toolbar>
@@ -337,6 +375,8 @@ const newFilePath = ref('')
const editingFile = ref<{ path: string; content: string } | null>(null)
const editingFileContent = ref('')
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
const expandedNodes = ref<string[]>([])
@@ -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
}
files.value.push(newFile)
// If we have a selected file, just add it
if (selectedSourcePath.value) {
const newFile = {
path: fullPath,
sourceFilePath: selectedSourcePath.value
}
// Open editor for the new file
editFileContent(newFile)
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
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()
+2 -1
View File
@@ -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 {