feat: 添加 @vueuse/core 依赖并重构模板树组件,优化创建模板界面

This commit is contained in:
zhanggy committed 2025-10-27 23:28:22 +08:00
1 parent 2f85832b89
commit 87ce5cb24f
5 files changed
+306 -156

No files matched your search

+224
View File
@@ -0,0 +1,224 @@
<template>
<v-card
variant="outlined"
class="pa-2"
:height="height"
@contextmenu.prevent="emit('showContextMenu', $event, null)"
>
<v-treeview
v-model:expanded="localExpanded"
:items="items"
item-title="name"
item-key="id"
item-children="children"
activatable
open-on-click
:return-object="false"
>
<template #prepend="{ item }">
<v-icon
:color="item.type === 'directory' ? 'warning' : 'info'"
size="large"
>
{{
item.type === 'directory'
? 'mdi-folder-outline'
: 'mdi-file-document-outline'
}}
</v-icon>
</template>
<template #title="{ item }">
<span
@contextmenu.prevent="emit('showContextMenu', $event, item)"
class="cursor-pointer d-flex align-center"
>
{{ item.name }}
<v-btn
v-if="!readOnly && item.type === 'directory'"
icon
variant="text"
size="small"
class="ml-2"
@click.stop="emit('addNewItemToCurrentDirectory', item)"
>
<v-icon size="small">mdi-plus</v-icon>
<v-tooltip
v-if="!readOnly"
activator="parent"
location="top"
>
添加到目录
</v-tooltip>
</v-btn>
</span>
</template>
<template #append="{ item }">
<div
v-if="!readOnly"
class="d-flex align-center ga-1"
>
<v-btn
v-if="item.type === 'file'"
icon
variant="text"
size="small"
@click.stop="emit('editFile', item)"
>
<v-icon size="small"> mdi-pencil </v-icon>
<v-tooltip
activator="parent"
location="top"
>
编辑内容
</v-tooltip>
</v-btn>
<v-btn
icon
variant="text"
size="small"
@click.stop="emit('deleteItem', item)"
>
<v-icon
v-if="!readOnly"
size="small"
color="error"
>
mdi-delete
</v-icon>
<v-tooltip
activator="parent"
location="top"
>
删除
</v-tooltip>
</v-btn>
</div>
</template>
</v-treeview>
<div
v-if="items.length === 0"
class="text-center pa-4"
>
<v-icon
size="48"
color="grey-lighten-1"
>
mdi-folder-open-outline
</v-icon>
<p class="text-body-2 mt-2">
{{ emptyText }}
</p>
<p
v-if="!readOnly"
class="text-caption"
>
右键点击空白区域添加根目录或文件
</p>
</div>
</v-card>
<!-- Context Menu -->
<v-menu
v-if="!readOnly"
v-model="localShowContextMenuVar"
:position-x="contextMenuX"
:position-y="contextMenuY"
absolute
>
<v-list density="compact">
<v-list-item @click="emit('addDirectory', currentContextItem)">
<template #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="emit('addFile', currentContextItem)">
<template #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>
</template>
<script setup lang="ts">
import { useVModel } from '@vueuse/core'
interface TreeItem {
id: string
name: string
type: 'directory' | 'file'
children?: TreeItem[]
}
interface Props {
items: TreeItem[]
height?: number
readOnly?: boolean
emptyText?: string
expanded?: string[]
showContextMenuVar?: boolean
contextMenuX?: number
contextMenuY?: number
currentContextItem?: TreeItem | null
}
const props = withDefaults(defineProps<Props>(), {
height: 600,
readOnly: false,
emptyText: '暂无文件或目录',
expanded: () => [],
showContextMenuVar: false,
contextMenuX: 0,
contextMenuY: 0,
currentContextItem: null
})
const emit = defineEmits([
'showContextMenu',
'addNewItemToCurrentDirectory',
'editFile',
'deleteItem',
'addDirectory',
'addFile',
'update:showContextMenuVar',
'update:expanded'
])
// Use useVModel to create two-way binding for props
const localShowContextMenuVar = useVModel(props, 'showContextMenuVar', emit)
const localExpanded = useVModel(props, 'expanded', emit)
</script>
<style scoped>
.cursor-pointer {
cursor: pointer;
}
:deep(.v-treeview-node__root),
:deep(.v-treeview-node__children .v-treeview-node__root) {
padding: 4px;
margin: 2px 0;
border-radius: 4px;
transition: background-color 0.2s;
}
:deep(.v-treeview-node__root:hover),
:deep(.v-treeview-node__children .v-treeview-node__root:hover) {
background-color: rgba(0, 0, 0, 0.04);
}
:deep(.v-treeview-node__level) {
margin-right: 4px;
}
</style>
+28 -138
View File
@@ -138,143 +138,25 @@
</v-btn>
</div>
<v-card
variant="outlined"
class="pa-2"
<TemplateTree
:items="treeItems"
:height="treeHeight"
@contextmenu.prevent="showContextMenu($event, null)"
>
<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 #prepend="{ item }">
<v-icon
:color="item.type === 'directory' ? 'warning' : 'info'"
size="large"
>
{{
item.type === 'directory'
? 'mdi-folder-outline'
: 'mdi-file-document-outline'
}}
</v-icon>
</template>
<template #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 #append="{ item }">
<div class="d-flex align-center ga-1">
<v-btn
v-if="item.type === 'file'"
icon
variant="text"
size="small"
@click.stop="editFile(item)"
>
<v-icon size="small"> mdi-pencil </v-icon>
<v-tooltip
activator="parent"
location="top"
>
编辑内容
</v-tooltip>
</v-btn>
<v-btn
icon
variant="text"
size="small"
@click.stop="deleteItem(item)"
>
<v-icon
size="small"
color="error"
>
mdi-delete
</v-icon>
<v-tooltip
activator="parent"
location="top"
>
删除
</v-tooltip>
</v-btn>
</div>
</template>
</v-treeview>
<div
v-if="treeItems.length === 0"
class="text-center pa-4"
>
<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-caption"> 右键点击空白区域添加根目录或文件 </p>
</div>
</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 #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 #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>
:expanded="expandedNodes"
:show-context-menu-var="showContextMenuVar"
:context-menu-x="contextMenuX"
:context-menu-y="contextMenuY"
:current-context-item="currentContextItem"
:empty-text="'暂无文件或目录'"
:read-only="false"
@showContextMenu="showContextMenu"
@add-new-item-to-current-directory="addNewItemToCurrentDirectory"
@edit-file="editFile"
@delete-item="deleteItem"
@add-directory="addDirectory"
@add-file="addFile"
@update:showContextMenuVar="handleContextMenuUpdate"
@update:expanded="expandedNodes = $event"
/>
</v-col>
</v-row>
@@ -295,6 +177,7 @@
required
density="comfortable"
ref="directoryInputRef"
@keydown.enter="confirmAddDirectory"
/>
</v-form>
</v-card-text>
@@ -329,6 +212,7 @@
required
density="comfortable"
ref="fileInputRef"
@keydown.enter="confirmAddFile"
/>
</v-form>
</v-card-text>
@@ -394,6 +278,7 @@ import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTemplateStore } from '@/renderer/store/template'
import { Template } from '@/renderer/store/template'
import TemplateTree from '@/renderer/components/TemplateTree.vue'
const router = useRouter()
const route = useRoute()
@@ -425,6 +310,11 @@ const contextMenuX = ref(0)
const contextMenuY = ref(0)
const currentContextItem = ref<TreeItem | null>(null)
// Handler for context menu visibility updates from TemplateTree component
const handleContextMenuUpdate = (value: boolean) => {
showContextMenuVar.value = value
}
// New item state
const newDirectoryPath = ref('')
const newFilePath = ref('')
@@ -677,14 +567,14 @@ const deleteItem = (item: TreeItem) => {
const autoFocusDirectoryInput = async () => {
await nextTick()
if (directoryInputRef.value) {
(directoryInputRef.value as HTMLInputElement).focus()
;(directoryInputRef.value as HTMLInputElement).focus()
}
}
const autoFocusFileInput = async () => {
await nextTick()
if (fileInputRef.value) {
(fileInputRef.value as HTMLInputElement).focus()
;(fileInputRef.value as HTMLInputElement).focus()
}
}
+15 -14
View File
@@ -98,18 +98,14 @@
<v-spacer />
</v-card-title>
<v-card-text>
<v-treeview :items="templateTree" item-title="name" item-key="id" item-children="children" activatable
open-all>
<template v-slot:prepend="{ item }">
<v-icon>
{{
item.type === 'directory'
? 'mdi-folder'
: 'mdi-file'
}}
</v-icon>
</template>
</v-treeview>
<TemplateTree
:items="templateTree"
:height="400"
:read-only="true"
:empty-text="'模板结构为空'"
:expanded="expandedTreeNodes"
@update:expanded="expandedTreeNodes = $event"
/>
</v-card-text>
</v-card>
</v-dialog>
@@ -144,6 +140,7 @@ import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useTemplateStore } from '@/renderer/store/template'
import { Template } from '@/renderer/store/template'
import TemplateTree from '@/renderer/components/TemplateTree.vue'
const router = useRouter()
const templateStore = useTemplateStore()
@@ -160,6 +157,7 @@ const templates = computed(() => templateStore.templates)
// Template detail view
const showTemplateDetail = ref(false)
const selectedTemplate = ref<Template | null>(null)
const expandedTreeNodes = ref<string[]>([])
// Execute template
const showExecuteDialog = ref(false)
@@ -184,7 +182,7 @@ const templateTree = computed<TreeNode[]>(() => {
selectedTemplate.value.directories.forEach((dir) => {
items.push({
id: `dir-${dir.path}`,
name: dir.path,
name: dir.path.split('/').pop() || dir.path, // Show just the directory name, not full path
type: 'directory',
children: []
})
@@ -194,11 +192,14 @@ const templateTree = computed<TreeNode[]>(() => {
selectedTemplate.value.files.forEach((file) => {
items.push({
id: `file-${file.path}`,
name: file.path,
name: file.path.split('/').pop() || file.path, // Show just the file name, not full path
type: 'file'
})
})
// Auto-expand all nodes when showing template details
expandedTreeNodes.value = items.map(item => item.id)
return items
})