feat: 优化模板创建界面,添加右键菜单功能以支持目录和文件的快速添加

This commit is contained in:
zhanggy committed 2025-10-27 19:13:47 +08:00
1 parent 4f859a4933
commit 02610224a1
1 file changed
+145 -188
+145 -188
View File
@@ -8,81 +8,28 @@
{{ isEditMode ? '编辑模板' : '创建模板' }} {{ isEditMode ? '编辑模板' : '创建模板' }}
</h2> </h2>
<v-spacer /> <v-spacer />
<v-btn <v-btn icon variant="text" @click="goBack">
icon
variant="text"
@click="goBack"
>
<v-icon>mdi-close</v-icon> <v-icon>mdi-close</v-icon>
<v-tooltip <v-tooltip activator="parent" location="bottom">
activator="parent"
location="bottom"
>
关闭 关闭
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
</div> </div>
<v-form v-model="formValid" class="mb-4"> <v-form v-model="formValid" class="mb-4">
<v-text-field <v-text-field v-model="templateName" label="模板名称 *" placeholder="输入模板名称" :rules="[requiredRule]" required
v-model="templateName" clearable density="comfortable" />
label="模板名称 *"
placeholder="输入模板名称"
:rules="[requiredRule]"
required
clearable
density="comfortable"
/>
<v-textarea <v-textarea v-model="templateDescription" label="模板描述" placeholder="描述这个模板的用途..." rows="2" auto-grow clearable
v-model="templateDescription" density="comfortable" />
label="模板描述"
placeholder="描述这个模板的用途..."
rows="2"
auto-grow
clearable
density="comfortable"
/>
</v-form> </v-form>
<v-divider class="my-4" /> <v-divider class="my-4" />
<!-- File Operations -->
<div class="mb-4">
<h3 class="text-subtitle-1 mb-2">添加项</h3>
<div class="d-flex flex-column ga-2">
<v-btn
color="primary"
variant="outlined"
prepend-icon="mdi-folder-plus"
@click="showAddDirectoryDialog"
block
>
添加目录
</v-btn>
<v-btn
color="success"
variant="outlined"
prepend-icon="mdi-file-plus"
@click="showAddFileDialog"
block
>
添加文件
</v-btn>
</div>
</div>
<v-divider class="my-4" />
<!-- Template Actions --> <!-- Template Actions -->
<div class="d-flex ga-2"> <div class="d-flex ga-2">
<v-btn <v-btn color="primary" @click="saveTemplate" :disabled="!formValid || !hasChanges"
color="primary" prepend-icon="mdi-content-save" block>
@click="saveTemplate"
:disabled="!formValid || !hasChanges"
prepend-icon="mdi-content-save"
block
>
保存模板 保存模板
</v-btn> </v-btn>
</div> </div>
@@ -93,56 +40,38 @@
<div class="d-flex align-center mb-4"> <div class="d-flex align-center mb-4">
<h3 class="text-h6 mr-2">模板结构</h3> <h3 class="text-h6 mr-2">模板结构</h3>
<v-spacer /> <v-spacer />
<v-btn <v-btn icon variant="text" @click="addRootDirectory" size="small">
icon <v-icon>mdi-folder-plus</v-icon>
variant="text" <v-tooltip activator="parent" location="bottom">
@click="expandAll" 添加根目录
size="small" </v-tooltip>
> </v-btn>
<v-btn icon variant="text" @click="addRootFile" size="small">
<v-icon>mdi-file-plus</v-icon>
<v-tooltip activator="parent" location="bottom">
添加根文件
</v-tooltip>
</v-btn>
<v-btn icon variant="text" @click="expandAll" size="small">
<v-icon>mdi-arrow-expand-down</v-icon> <v-icon>mdi-arrow-expand-down</v-icon>
<v-tooltip <v-tooltip activator="parent" location="bottom">
activator="parent"
location="bottom"
>
展开全部 展开全部
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
<v-btn <v-btn icon variant="text" @click="collapseAll" size="small">
icon
variant="text"
@click="collapseAll"
size="small"
>
<v-icon>mdi-arrow-collapse-up</v-icon> <v-icon>mdi-arrow-collapse-up</v-icon>
<v-tooltip <v-tooltip activator="parent" location="bottom">
activator="parent"
location="bottom"
>
收起全部 收起全部
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
</div> </div>
<v-card <v-card variant="outlined" class="pa-2" :height="treeHeight"
variant="outlined" @contextmenu.prevent="showContextMenu($event, null)">
class="pa-2" <v-treeview v-model:expanded="expandedNodes" :items="treeItems" item-title="name" item-key="id"
:height="treeHeight" item-children="children" activatable open-on-click :return-object="false">
>
<v-treeview
v-model:expanded="expandedNodes"
:items="treeItems"
item-title="name"
item-key="id"
item-children="children"
activatable
open-on-click
:return-object="false"
>
<template v-slot:prepend="{ item }"> <template v-slot:prepend="{ item }">
<v-icon <v-icon :color="item.type === 'directory' ? 'warning' : 'info'" size="large">
:color="item.type === 'directory' ? 'warning' : 'info'"
size="large"
>
{{ {{
item.type === 'directory' item.type === 'directory'
? 'mdi-folder-outline' ? 'mdi-folder-outline'
@@ -151,40 +80,33 @@
</v-icon> </v-icon>
</template> </template>
<template v-slot:title="{ item }">
<span @contextmenu.prevent="showContextMenu($event, item)" class="cursor-pointer d-flex align-center">
{{ item.name }}
<v-btn v-if="item.type === 'directory'" icon variant="text" size="small" class="ml-2"
@click.stop="addNewItemToCurrentDirectory(item)">
<v-icon size="small">mdi-plus</v-icon>
<v-tooltip activator="parent" location="top">
添加到目录
</v-tooltip>
</v-btn>
</span>
</template>
<template v-slot:append="{ item }"> <template v-slot:append="{ item }">
<div class="d-flex align-center ga-1"> <div class="d-flex align-center ga-1">
<v-btn <v-btn v-if="item.type === 'file'" icon variant="text" size="small" @click.stop="editFile(item)">
v-if="item.type === 'file'"
icon
variant="text"
size="small"
@click.stop="editFile(item)"
>
<v-icon size="small">mdi-pencil</v-icon> <v-icon size="small">mdi-pencil</v-icon>
<v-tooltip <v-tooltip activator="parent" location="top">
activator="parent"
location="top"
>
编辑内容 编辑内容
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
<v-btn <v-btn icon variant="text" size="small" @click.stop="deleteItem(item)">
icon <v-icon size="small" color="error">
variant="text"
size="small"
@click.stop="deleteItem(item)"
>
<v-icon
size="small"
color="error"
>
mdi-delete mdi-delete
</v-icon> </v-icon>
<v-tooltip <v-tooltip activator="parent" location="top">
activator="parent"
location="top"
>
删除 删除
</v-tooltip> </v-tooltip>
</v-btn> </v-btn>
@@ -195,11 +117,34 @@
<div class="text-center pa-4"> <div class="text-center pa-4">
<v-icon size="48" color="grey-lighten-1">mdi-folder-open-outline</v-icon> <v-icon size="48" color="grey-lighten-1">mdi-folder-open-outline</v-icon>
<p class="text-body-2 mt-2">暂无文件或目录</p> <p class="text-body-2 mt-2">暂无文件或目录</p>
<p class="text-caption">点击左侧"添加目录"或"添加文件"开始创建</p> <p class="text-caption">右键点击空白区域添加根目录或文件</p>
</div> </div>
</template> </template>
</v-treeview> </v-treeview>
</v-card> </v-card>
<!-- Context Menu -->
<v-menu v-model="showContextMenuVar" :position-x="contextMenuX" :position-y="contextMenuY" absolute>
<v-list density="compact">
<v-list-item @click="addDirectory(currentContextItem)">
<template v-slot:prepend>
<v-icon>mdi-folder-plus</v-icon>
</template>
<v-list-item-title>
{{ currentContextItem ? '添加子目录' : '添加根目录' }}
</v-list-item-title>
</v-list-item>
<v-list-item @click="addFile(currentContextItem)">
<template v-slot:prepend>
<v-icon>mdi-file-plus</v-icon>
</template>
<v-list-item-title>
{{ currentContextItem ? '添加子文件' : '添加根文件' }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-col> </v-col>
</v-row> </v-row>
@@ -209,14 +154,8 @@
<v-card-title>添加目录</v-card-title> <v-card-title>添加目录</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" label="目录路径 *" placeholder="例如: src/components"
v-model="newDirectoryPath" :rules="[requiredRule]" required density="comfortable" />
label="目录路径 *"
placeholder="例如: src/components"
:rules="[requiredRule]"
required
density="comfortable"
/>
</v-form> </v-form>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
@@ -224,11 +163,7 @@
<v-btn @click="showDirectoryDialog = false"> <v-btn @click="showDirectoryDialog = false">
取消 取消
</v-btn> </v-btn>
<v-btn <v-btn color="primary" @click="confirmAddDirectory" :disabled="!directoryFormValid">
color="primary"
@click="addDirectory"
:disabled="!directoryFormValid"
>
添加 添加
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@@ -241,14 +176,8 @@
<v-card-title>添加文件</v-card-title> <v-card-title>添加文件</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" label="文件路径 *" placeholder="例如: src/index.js" :rules="[requiredRule]"
v-model="newFilePath" required density="comfortable" />
label="文件路径 *"
placeholder="例如: src/index.js"
:rules="[requiredRule]"
required
density="comfortable"
/>
</v-form> </v-form>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
@@ -256,11 +185,7 @@
<v-btn @click="showFileDialog = false"> <v-btn @click="showFileDialog = false">
取消 取消
</v-btn> </v-btn>
<v-btn <v-btn color="primary" @click="confirmAddFile" :disabled="!fileFormValid">
color="primary"
@click="addFile"
:disabled="!fileFormValid"
>
添加并编辑 添加并编辑
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@@ -278,25 +203,14 @@
编辑文件: {{ editingFile?.path }} 编辑文件: {{ editingFile?.path }}
</v-toolbar-title> </v-toolbar-title>
<v-spacer /> <v-spacer />
<v-btn <v-btn variant="text" @click="saveFileContent">
variant="text"
@click="saveFileContent"
>
保存 保存
</v-btn> </v-btn>
</v-toolbar> </v-toolbar>
<v-card-text class="pa-0 fill-height"> <v-card-text class="pa-0 fill-height">
<v-textarea <v-textarea v-model="editingFileContent" class="fill-height" style="height: calc(100vh - 120px);" no-resize
v-model="editingFileContent" spellcheck="false" placeholder="在此输入文件内容..." variant="solo-filled" density="comfortable" />
class="fill-height"
style="height: calc(100vh - 120px);"
no-resize
spellcheck="false"
placeholder="在此输入文件内容..."
variant="solo-filled"
density="comfortable"
/>
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-dialog> </v-dialog>
@@ -304,7 +218,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue' import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useTemplateStore } from '@/renderer/store/template' import { useTemplateStore } from '@/renderer/store/template'
import { Template } from '@/renderer/store/template' import { Template } from '@/renderer/store/template'
@@ -329,6 +243,12 @@ const showEditorDialog = ref(false)
const directoryFormValid = ref(false) const directoryFormValid = ref(false)
const fileFormValid = ref(false) const fileFormValid = ref(false)
// Context menu
const showContextMenuVar = ref(false)
const contextMenuX = ref(0)
const contextMenuY = ref(0)
const currentContextItem = ref<any>(null)
// New item state // New item state
const newDirectoryPath = ref('') const newDirectoryPath = ref('')
const newFilePath = ref('') const newFilePath = ref('')
@@ -417,36 +337,49 @@ const updateTreeHeight = () => {
treeHeight.value = window.innerHeight - 200 treeHeight.value = window.innerHeight - 200
} }
// Methods const showContextMenu = (event: MouseEvent, item: any) => {
const goBack = () => { event.preventDefault()
if (hasChanges.value) { contextMenuX.value = event.clientX
if (confirm('您有未保存的更改,确定要离开吗?')) { contextMenuY.value = event.clientY
router.push('/templates') currentContextItem.value = item
} showContextMenuVar.value = false // Close first to reset position
} else { nextTick(() => {
router.push('/templates') contextMenuX.value = event.clientX
} contextMenuY.value = event.clientY
showContextMenuVar.value = true
})
} }
const expandAll = () => { const addRootDirectory = () => {
expandedNodes.value = treeItems.value.map(item => item.id)
}
const collapseAll = () => {
expandedNodes.value = []
}
const showAddDirectoryDialog = () => {
newDirectoryPath.value = '' newDirectoryPath.value = ''
showDirectoryDialog.value = true showDirectoryDialog.value = true
} }
const showAddFileDialog = () => { const addRootFile = () => {
newFilePath.value = '' newFilePath.value = ''
showFileDialog.value = true showFileDialog.value = true
} }
const addDirectory = () => { const addDirectory = (parentItem: any = null) => {
showContextMenuVar.value = false
currentContextItem.value = parentItem
newDirectoryPath.value = parentItem ? `${parentItem.name}/` : ''
showDirectoryDialog.value = true
}
const addFile = (parentItem: any = null) => {
showContextMenuVar.value = false
newFilePath.value = parentItem ? `${parentItem.name}/` : ''
showFileDialog.value = true
}
const addNewItemToCurrentDirectory = (directoryItem: any) => {
currentContextItem.value = directoryItem
newDirectoryPath.value = directoryItem ? `${directoryItem.name}/` : ''
showDirectoryDialog.value = true
}
const confirmAddDirectory = () => {
if (!newDirectoryPath.value.trim()) return if (!newDirectoryPath.value.trim()) return
// Check for duplicates // Check for duplicates
@@ -462,9 +395,10 @@ const addDirectory = () => {
showDirectoryDialog.value = false showDirectoryDialog.value = false
newDirectoryPath.value = '' newDirectoryPath.value = ''
currentContextItem.value = null
} }
const addFile = () => { const confirmAddFile = () => {
if (!newFilePath.value.trim()) return if (!newFilePath.value.trim()) return
// Check for duplicates // Check for duplicates
@@ -486,6 +420,25 @@ const addFile = () => {
showFileDialog.value = false showFileDialog.value = false
newFilePath.value = '' newFilePath.value = ''
currentContextItem.value = null
}
const goBack = () => {
if (hasChanges.value) {
if (confirm('您有未保存的更改,确定要离开吗?')) {
router.push('/templates')
}
} else {
router.push('/templates')
}
}
const expandAll = () => {
expandedNodes.value = treeItems.value.map(item => item.id)
}
const collapseAll = () => {
expandedNodes.value = []
} }
const editFile = (item: any) => { const editFile = (item: any) => {
@@ -554,4 +507,8 @@ const saveTemplate = () => {
.fill-height { .fill-height {
height: 100%; height: 100%;
} }
.cursor-pointer {
cursor: pointer;
}
</style> </style>