feat: 优化模板创建界面,添加编辑文件和目录功能,增强用户体验
This commit is contained in:
1 parent
d6d86e5042
commit
4367fd6ed1
3 files changed
+263
-22
No files matched your search
@@ -3,6 +3,7 @@
|
|||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
Vutron is a preconfigured template for developing cross-platform desktop applications using modern web technologies. The project combines:
|
Vutron is a preconfigured template for developing cross-platform desktop applications using modern web technologies. The project combines:
|
||||||
|
|
||||||
- **Vite** as the build tool and development server
|
- **Vite** as the build tool and development server
|
||||||
- **Vue 3** with TypeScript for the user interface
|
- **Vue 3** with TypeScript for the user interface
|
||||||
- **Electron** for creating desktop applications
|
- **Electron** for creating desktop applications
|
||||||
@@ -18,17 +19,20 @@ The template provides a ready-to-use development environment with hot-reload, cr
|
|||||||
The project follows the typical Electron architecture with three main processes:
|
The project follows the typical Electron architecture with three main processes:
|
||||||
|
|
||||||
### Main Process (`src/main/`)
|
### Main Process (`src/main/`)
|
||||||
|
|
||||||
- Handles application lifecycle
|
- Handles application lifecycle
|
||||||
- Manages windows and system integration
|
- Manages windows and system integration
|
||||||
- Located in `src/main/index.ts`
|
- Located in `src/main/index.ts`
|
||||||
- Also includes utilities, tray functionality, and IPC handlers
|
- Also includes utilities, tray functionality, and IPC handlers
|
||||||
|
|
||||||
### Preload Process (`src/preload/`)
|
### Preload Process (`src/preload/`)
|
||||||
|
|
||||||
- Acts as an isolated bridge between main and renderer processes
|
- Acts as an isolated bridge between main and renderer processes
|
||||||
- Uses `contextBridge` to securely expose APIs to the renderer
|
- Uses `contextBridge` to securely expose APIs to the renderer
|
||||||
- Located in `src/preload/index.ts`
|
- Located in `src/preload/index.ts`
|
||||||
|
|
||||||
### Renderer Process (`src/renderer/`)
|
### Renderer Process (`src/renderer/`)
|
||||||
|
|
||||||
- Contains the Vue 3 application
|
- Contains the Vue 3 application
|
||||||
- Uses Vuetify for UI components
|
- Uses Vuetify for UI components
|
||||||
- Located in `src/renderer/` with:
|
- Located in `src/renderer/` with:
|
||||||
@@ -42,10 +46,12 @@ The project follows the typical Electron architecture with three main processes:
|
|||||||
## Building and Running
|
## Building and Running
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Node.js >= 20.0.0
|
- Node.js >= 20.0.0
|
||||||
- pnpm package manager (recommended)
|
- pnpm package manager (recommended)
|
||||||
|
|
||||||
### Development
|
### Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
pnpm install
|
pnpm install
|
||||||
@@ -61,6 +67,7 @@ pnpm dev:debug:force
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build for all platforms
|
# Build for all platforms
|
||||||
pnpm build:all
|
pnpm build:all
|
||||||
@@ -78,6 +85,7 @@ pnpm build
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run Playwright tests
|
# Run Playwright tests
|
||||||
pnpm test
|
pnpm test
|
||||||
@@ -87,6 +95,7 @@ pnpm test:linux
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Linting and Formatting
|
### Linting and Formatting
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check for linting issues
|
# Check for linting issues
|
||||||
pnpm lint
|
pnpm lint
|
||||||
@@ -185,6 +194,7 @@ pnpm format:fix
|
|||||||
## Internationalization
|
## Internationalization
|
||||||
|
|
||||||
The application supports multiple languages with the following locale files:
|
The application supports multiple languages with the following locale files:
|
||||||
|
|
||||||
- English (`en`)
|
- English (`en`)
|
||||||
- Korean (`ko`)
|
- Korean (`ko`)
|
||||||
- Simplified Chinese (`zh-hans`)
|
- Simplified Chinese (`zh-hans`)
|
||||||
@@ -200,6 +210,7 @@ The application supports multiple languages with the following locale files:
|
|||||||
## Electron Integration
|
## Electron Integration
|
||||||
|
|
||||||
The application integrates with Electron features through:
|
The application integrates with Electron features through:
|
||||||
|
|
||||||
- IPC communication for sending messages between processes
|
- IPC communication for sending messages between processes
|
||||||
- Native file dialog support
|
- Native file dialog support
|
||||||
- External link opening
|
- External link opening
|
||||||
@@ -213,8 +224,11 @@ The project includes Playwright for end-to-end testing, with configuration in `p
|
|||||||
## Build Outputs
|
## Build Outputs
|
||||||
|
|
||||||
Building creates different distribution formats based on the platform:
|
Building creates different distribution formats based on the platform:
|
||||||
|
|
||||||
- **macOS**: DMG installer for x64, arm64, and universal architectures
|
- **macOS**: DMG installer for x64, arm64, and universal architectures
|
||||||
- **Windows**: APPX, ZIP, Portable, and NSIS installers (x64)
|
- **Windows**: APPX, ZIP, Portable, and NSIS installers (x64)
|
||||||
- **Linux**: SNAP, DEB, and RPM packages (x64)
|
- **Linux**: SNAP, DEB, and RPM packages (x64)
|
||||||
|
|
||||||
Artifacts are named with the format `${productName} ${version}_${arch}.${ext}` and stored in the `release/${version}` directory.
|
Artifacts are named with the format `${productName} ${version}_${arch}.${ext}` and stored in the `release/${version}` directory.
|
||||||
|
|
||||||
|
Always respond in 中文
|
||||||
@@ -61,18 +61,34 @@
|
|||||||
class="d-flex align-center ga-1"
|
class="d-flex align-center ga-1"
|
||||||
>
|
>
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="item.type === 'file'"
|
v-if="!readOnly"
|
||||||
icon
|
icon
|
||||||
variant="text"
|
variant="text"
|
||||||
size="small"
|
size="small"
|
||||||
@click.stop="emit('editFile', item)"
|
@click.stop="emit('editItem', item)"
|
||||||
>
|
>
|
||||||
<v-icon size="small"> mdi-pencil </v-icon>
|
<v-icon size="small"> mdi-pencil </v-icon>
|
||||||
<v-tooltip
|
<v-tooltip
|
||||||
activator="parent"
|
activator="parent"
|
||||||
location="top"
|
location="top"
|
||||||
>
|
>
|
||||||
编辑内容
|
{{ item.type === 'file' ? '编辑文件' : '编辑目录' }}
|
||||||
|
</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-if="item.type === 'directory' && !readOnly"
|
||||||
|
icon
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
@click.stop="emit('addFileToDirectory', item)"
|
||||||
|
>
|
||||||
|
<v-icon size="small"> mdi-file-plus </v-icon>
|
||||||
|
<v-tooltip
|
||||||
|
activator="parent"
|
||||||
|
location="top"
|
||||||
|
>
|
||||||
|
在目录下创建文件
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
|
||||||
@@ -192,9 +208,11 @@ const emit = defineEmits([
|
|||||||
'showContextMenu',
|
'showContextMenu',
|
||||||
'addNewItemToCurrentDirectory',
|
'addNewItemToCurrentDirectory',
|
||||||
'editFile',
|
'editFile',
|
||||||
|
'editItem',
|
||||||
'deleteItem',
|
'deleteItem',
|
||||||
'addDirectory',
|
'addDirectory',
|
||||||
'addFile'
|
'addFile',
|
||||||
|
'addFileToDirectory'
|
||||||
])
|
])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -151,9 +151,11 @@
|
|||||||
@showContextMenu="showContextMenu"
|
@showContextMenu="showContextMenu"
|
||||||
@add-new-item-to-current-directory="addNewItemToCurrentDirectory"
|
@add-new-item-to-current-directory="addNewItemToCurrentDirectory"
|
||||||
@edit-file="editFile"
|
@edit-file="editFile"
|
||||||
|
@edit-item="editItem"
|
||||||
@delete-item="deleteItem"
|
@delete-item="deleteItem"
|
||||||
@add-directory="addDirectory"
|
@add-directory="addDirectory"
|
||||||
@add-file="addFile"
|
@add-file="addFile"
|
||||||
|
@add-file-to-directory="addFileToDirectory"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -164,13 +166,13 @@
|
|||||||
max-width="500"
|
max-width="500"
|
||||||
>
|
>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title>添加目录</v-card-title>
|
<v-card-title>{{ currentEditingItem ? '编辑目录' : '添加目录' }}</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-form v-model="directoryFormValid">
|
<v-form v-model="directoryFormValid">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="newDirectoryPath"
|
v-model="newDirectoryPath"
|
||||||
label="目录路径 *"
|
:label="currentEditingItem ? '新目录路径 *' : '目录路径 *'"
|
||||||
placeholder="例如: src/components"
|
:placeholder="currentEditingItem ? '例如: src/new-components' : '例如: src/components'"
|
||||||
:rules="[requiredRule]"
|
:rules="[requiredRule]"
|
||||||
required
|
required
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
@@ -181,13 +183,17 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn @click="showDirectoryDialog = false"> 取消 </v-btn>
|
<v-btn
|
||||||
|
@click="closeDirectoryDialog"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
@click="confirmAddDirectory"
|
@click="confirmAddDirectory"
|
||||||
:disabled="!directoryFormValid"
|
:disabled="!directoryFormValid"
|
||||||
>
|
>
|
||||||
添加
|
{{ currentEditingItem ? '保存' : '添加' }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -199,13 +205,13 @@
|
|||||||
max-width="600"
|
max-width="600"
|
||||||
>
|
>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title>添加文件</v-card-title>
|
<v-card-title>{{ currentEditingItem ? '编辑文件' : '添加文件' }}</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-form v-model="fileFormValid">
|
<v-form v-model="fileFormValid">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="newFilePath"
|
v-model="newFilePath"
|
||||||
label="目标文件路径 *"
|
:label="currentEditingItem ? '新文件路径 *' : '目标文件路径 *'"
|
||||||
placeholder="例如: src/index.js"
|
:placeholder="currentEditingItem ? '例如: src/new-index.js' : '例如: src/index.js'"
|
||||||
:rules="[requiredRule]"
|
:rules="[requiredRule]"
|
||||||
required
|
required
|
||||||
density="comfortable"
|
density="comfortable"
|
||||||
@@ -218,6 +224,7 @@
|
|||||||
@click="selectSourceFile"
|
@click="selectSourceFile"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
|
:disabled="!!currentEditingItem"
|
||||||
>
|
>
|
||||||
选择文件
|
选择文件
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -225,6 +232,7 @@
|
|||||||
@click="selectSourceFolder"
|
@click="selectSourceFolder"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
|
:disabled="!!currentEditingItem"
|
||||||
>
|
>
|
||||||
选择文件夹
|
选择文件夹
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -250,17 +258,34 @@
|
|||||||
<span>自动寻找文件夹内的文件</span>
|
<span>自动寻找文件夹内的文件</span>
|
||||||
</div>
|
</div>
|
||||||
</v-alert>
|
</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-form>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn @click="showFileDialog = false"> 取消 </v-btn>
|
<v-btn
|
||||||
|
@click="closeFileDialog"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
@click="selectAndAddFile"
|
@click="selectAndAddFile"
|
||||||
:disabled="!fileFormValid || !selectedSourcePath"
|
:disabled="!fileFormValid || (!selectedSourcePath && !currentEditingItem)"
|
||||||
>
|
>
|
||||||
添加
|
{{ currentEditingItem ? '保存' : '添加' }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -570,6 +595,15 @@ const addFile = (parentItem: TreeItem | null = null) => {
|
|||||||
autoFocusFileInput()
|
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) => {
|
const addNewItemToCurrentDirectory = (directoryItem: TreeItem) => {
|
||||||
currentContextItem.value = directoryItem
|
currentContextItem.value = directoryItem
|
||||||
parentDirectoryForNewItems.value = directoryItem.name // Store parent context
|
parentDirectoryForNewItems.value = directoryItem.name // Store parent context
|
||||||
@@ -591,7 +625,18 @@ const confirmAddDirectory = () => {
|
|||||||
fullPath = parentPath + newDirectoryPath.value
|
fullPath = parentPath + newDirectoryPath.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicates
|
// 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(
|
const exists = directories.value.some(
|
||||||
(d) => d.path === fullPath
|
(d) => d.path === fullPath
|
||||||
)
|
)
|
||||||
@@ -603,11 +648,64 @@ const confirmAddDirectory = () => {
|
|||||||
directories.value.push({
|
directories.value.push({
|
||||||
path: fullPath
|
path: fullPath
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
showDirectoryDialog.value = false
|
showDirectoryDialog.value = false
|
||||||
newDirectoryPath.value = ''
|
newDirectoryPath.value = ''
|
||||||
parentDirectoryForNewItems.value = null // Clear parent context after creation
|
parentDirectoryForNewItems.value = null // Clear parent context after creation
|
||||||
currentContextItem.value = null
|
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 () => {
|
const selectAndAddFile = async () => {
|
||||||
@@ -623,7 +721,18 @@ const selectAndAddFile = async () => {
|
|||||||
fullPath = parentPath + newFilePath.value
|
fullPath = parentPath + newFilePath.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicates
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check for duplicates only when creating a new file
|
||||||
const exists = files.value.some((f) => f.path === fullPath)
|
const exists = files.value.some((f) => f.path === fullPath)
|
||||||
if (exists) {
|
if (exists) {
|
||||||
showSnackbar('该文件路径已存在!')
|
showSnackbar('该文件路径已存在!')
|
||||||
@@ -660,6 +769,7 @@ const selectAndAddFile = async () => {
|
|||||||
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
|
||||||
|
currentEditingItem.value = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,11 +790,24 @@ const selectAndAddFile = async () => {
|
|||||||
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
|
||||||
|
currentEditingItem.value = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, show file selection dialog
|
// Otherwise, show file selection dialog
|
||||||
showSnackbar('请先选择一个文件或文件夹', 'warning')
|
showSnackbar('请先选择一个文件或文件夹', 'warning')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset selection state after editing
|
||||||
|
selectedSourcePath.value = ''
|
||||||
|
isAutoFindMode.value = false
|
||||||
|
|
||||||
|
showFileDialog.value = false
|
||||||
|
newFilePath.value = ''
|
||||||
|
parentDirectoryForNewItems.value = null
|
||||||
|
currentContextItem.value = null
|
||||||
|
currentEditingItem.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
@@ -698,7 +821,19 @@ const goBack = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const expandAll = () => {
|
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 = () => {
|
const collapseAll = () => {
|
||||||
@@ -710,18 +845,92 @@ const editFile = (item: TreeItem) => {
|
|||||||
showSnackbar('该文件是从源文件复制的,无法直接编辑', 'info')
|
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) => {
|
const deleteItem = (item: TreeItem) => {
|
||||||
if (!confirm('确定要删除此项吗?')) return
|
if (!confirm('确定要删除此项吗?')) return
|
||||||
|
|
||||||
if (item.type === 'directory') {
|
if (item.type === 'directory') {
|
||||||
directories.value = directories.value.filter(
|
// Find all directories that start with the item name (including the exact match)
|
||||||
(d) => `dir-${d.path}` !== item.id
|
// 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 {
|
} 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
|
// Show snackbar message
|
||||||
const showSnackbar = (message: string, color: string = 'error') => {
|
const showSnackbar = (message: string, color: string = 'error') => {
|
||||||
snackbarMessage.value = message
|
snackbarMessage.value = message
|
||||||
|
|||||||
Reference in new issue
Block a user