feat: Implement template management feature

- Added IPC handlers for selecting directories, loading, saving, and executing templates.
- Created a new TemplateListScreen for displaying and managing templates.
- Developed CreateTemplateScreen for creating and editing templates with file and directory management.
- Updated routing to include new template screens.
- Enhanced MainScreen with navigation to template management.
- Added localization for new template-related strings in English and Chinese.
- Introduced a Pinia store for managing template data.
- Implemented tests for template creation, listing, and detail viewing.
This commit is contained in:
zhangguoyun committed 2025-10-27 15:17:32 +08:00
1 parent 742eb7040f
commit 1aecd5f4b6
15 files changed
+1225 -140

No files matched your search

+275
View File
@@ -0,0 +1,275 @@
<template>
<v-container>
<v-row>
<v-col cols="12">
<h2 class="mb-4"> 模板列表 </h2>
<!-- No templates message -->
<v-card
v-if="templates.length === 0"
class="pa-6 mb-4 text-center"
>
<v-card-title class="text-h5 mb-4">
暂无模板
</v-card-title>
<v-card-text>
<p>还没有创建任何模板。创建您的第一个模板以开始批量生成文件和目录。</p>
</v-card-text>
<v-card-actions class="justify-center">
<v-btn
color="primary"
size="x-large"
@click="navigateToCreateTemplate"
prepend-icon="mdi-plus"
>
创建模板
</v-btn>
</v-card-actions>
</v-card>
<!-- Template list -->
<div v-else>
<v-row>
<v-col
v-for="template in templates"
:key="template.id"
cols="12"
md="6"
lg="4"
>
<v-card
class="d-flex flex-column"
height="100%"
>
<v-card-title>{{ template.name }}</v-card-title>
<v-card-subtitle>{{ template.description }}</v-card-subtitle>
<v-card-text class="flex-grow-1">
<p>文件数量: {{ template.files.length }}</p>
<p>目录数量: {{ template.directories.length }}</p>
</v-card-text>
<v-card-actions>
<v-btn
color="primary"
variant="outlined"
@click="viewTemplate(template)"
>
查看
</v-btn>
<v-btn
color="success"
variant="outlined"
@click="executeTemplate(template)"
>
执行
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</div>
</v-col>
</v-row>
<!-- Template detail dialog -->
<v-dialog
v-model="showTemplateDetail"
max-width="800"
>
<v-card>
<v-card-title>
<span>模板详情: {{ selectedTemplate?.name }}</span>
<v-spacer />
<v-btn
icon
@click="showTemplateDetail = false"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</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, open }">
<v-icon>
{{
item.type === 'directory'
? open
? 'mdi-folder-open'
: 'mdi-folder'
: 'mdi-file'
}}
</v-icon>
</template>
</v-treeview>
</v-card-text>
</v-card>
</v-dialog>
<!-- Execute template dialog -->
<v-dialog
v-model="showExecuteDialog"
max-width="600"
>
<v-card>
<v-card-title>执行模板</v-card-title>
<v-card-text>
<v-form v-model="executeFormValid">
<v-text-field
v-model="executePath"
label="选择目录"
readonly
@click="selectDirectory"
prepend-inner-icon="mdi-folder"
:rules="[(v) => !!v || '请选择目录']"
/>
<v-textarea
v-model="baseDirNames"
label="基础目录名 (每行一个,支持批量)"
placeholder="输入基础目录名,每行一个"
:rules="[(v) => !!v || '请输入基础目录名']"
/>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn @click="showExecuteDialog = false"> 取消 </v-btn>
<v-btn
color="success"
@click="confirmExecuteTemplate"
:disabled="!executeFormValid"
>
确认执行
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Floating Action Button for creating templates -->
<v-btn
v-if="templates.length > 0"
color="primary"
size="x-large"
icon="mdi-plus"
fixed
location="bottom end"
class="mb-8 mr-8"
style="z-index: 1000;"
@click="navigateToCreateTemplate"
>
<v-tooltip
activator="parent"
location="top"
>
创建模板
</v-tooltip>
</v-btn>
</v-container>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useTemplateStore } from '@/renderer/store/template'
const router = useRouter()
const templateStore = useTemplateStore()
// Initialize templates
onMounted(async () => {
await templateStore.loadTemplates()
})
const templates = computed(() => templateStore.templates)
// Template detail view
const showTemplateDetail = ref(false)
const selectedTemplate = ref(null as any)
// Execute template
const showExecuteDialog = ref(false)
const executePath = ref('')
const baseDirNames = ref('')
const executeFormValid = ref(false)
const templateTree = computed(() => {
if (!selectedTemplate.value) return []
const items = []
// Add directories
selectedTemplate.value.directories.forEach((dir: any) => {
items.push({
id: `dir-${dir.path}`,
name: dir.path,
type: 'directory',
children: []
})
})
// Add files
selectedTemplate.value.files.forEach((file: any) => {
items.push({
id: `file-${file.path}`,
name: file.path,
type: 'file'
})
})
return items
})
const navigateToCreateTemplate = () => {
router.push('/create-template')
}
const viewTemplate = (template: any) => {
selectedTemplate.value = template
showTemplateDetail.value = true
}
const executeTemplate = (template: any) => {
selectedTemplate.value = template
executePath.value = ''
baseDirNames.value = ''
showExecuteDialog.value = true
}
const selectDirectory = async () => {
const result = await window.mainApi.invoke('msgSelectDirectory')
if (!result.canceled) {
executePath.value = result.filePath
}
}
const confirmExecuteTemplate = () => {
if (!executePath.value || !baseDirNames.value.trim()) return
const dirNames = baseDirNames.value.split('\n').filter((name) => name.trim())
// Call main process to execute template
window.mainApi
.invoke(
'msgExecuteTemplate',
selectedTemplate.value.id,
executePath.value,
dirNames
)
.then((result: any) => {
if (result.success) {
alert('模板执行成功!')
showExecuteDialog.value = false
} else {
alert(`模板执行失败: ${result.error}`)
}
})
.catch((error: any) => {
alert(`模板执行出错: ${error.message}`)
})
}
</script>