feat: 添加文件选择和自动查找功能,优化模板创建界面
This commit is contained in:
1 parent
de7af2f650
commit
d6d86e5042
4 files changed
+174
-46
No files matched your search
+50
-2
@@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ const mainAvailChannels: string[] = [
|
||||
'msgSelectDirectory',
|
||||
'msgLoadData',
|
||||
'msgSaveData',
|
||||
'msgExecuteTemplate'
|
||||
'msgExecuteTemplate',
|
||||
'msgFindFilesInFolder'
|
||||
]
|
||||
const rendererAvailChannels: string[] = []
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user