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

@@ -0,0 +1,334 @@
<template>
<v-container>
<v-row>
<v-col cols="12">
<h2 class="mb-4">
{{ templateId ? '编辑模板' : '创建模板' }}
</h2>
<v-form v-model="formValid">
<v-text-field
v-model="template.name"
label="模板名称 *"
:rules="[(v) => !!v || '模板名称不能为空']"
required
data-testid="template-name-input"
/>
<v-textarea
v-model="template.description"
label="模板描述"
rows="2"
data-testid="template-description-input"
/>
<!-- Template content -->
<v-card class="mb-4">
<v-card-title>模板内容</v-card-title>
<v-card-text>
<!-- Files section -->
<v-expansion-panels class="mb-4">
<v-expansion-panel>
<v-expansion-panel-title>文件</v-expansion-panel-title>
<v-expansion-panel-text>
<v-data-table
:headers="fileHeaders"
:items="template.files"
class="elevation-1"
>
<template v-slot:item.actions="{ item }">
<v-btn
icon
color="error"
@click="removeFile(item)"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
</template>
</v-data-table>
<v-row class="mt-4">
<v-col cols="12">
<v-btn
color="primary"
@click="showAddFileDialog = true"
prepend-icon="mdi-file-plus"
>
添加文件
</v-btn>
</v-col>
</v-row>
</v-expansion-panel-text>
</v-expansion-panel>
<!-- Directories section -->
<v-expansion-panel>
<v-expansion-panel-title>目录</v-expansion-panel-title>
<v-expansion-panel-text>
<v-data-table
:headers="dirHeaders"
:items="template.directories"
class="elevation-1"
>
<template v-slot:item.actions="{ item }">
<v-btn
icon
color="error"
@click="removeDirectory(item)"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
</template>
</v-data-table>
<v-row class="mt-4">
<v-col cols="12">
<v-btn
color="primary"
@click="showAddDirDialog = true"
prepend-icon="mdi-folder-plus"
>
添加目录
</v-btn>
</v-col>
</v-row>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-card-text>
</v-card>
<v-btn
:color="templateId ? 'warning' : 'success'"
@click="saveTemplate"
:disabled="!formValid"
prepend-icon="mdi-content-save"
>
{{ templateId ? '更新模板' : '保存模板' }}
</v-btn>
<v-btn
class="ml-2"
@click="cancel"
prepend-icon="mdi-arrow-left"
>
返回
</v-btn>
</v-form>
</v-col>
</v-row>
<!-- Add file dialog -->
<v-dialog
v-model="showAddFileDialog"
max-width="600"
>
<v-card>
<v-card-title>添加文件</v-card-title>
<v-card-text>
<v-form v-model="fileFormValid">
<v-text-field
v-model="newFile.path"
label="文件路径 *"
placeholder="例如: src/index.js"
:rules="[(v) => !!v || '文件路径不能为空']"
data-testid="add-file-path-input"
/>
<v-textarea
v-model="newFile.content"
label="文件内容"
placeholder="输入文件内容..."
rows="6"
data-testid="add-file-content-input"
/>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn @click="showAddFileDialog = false"> 取消 </v-btn>
<v-btn
color="primary"
@click="addFile"
:disabled="!fileFormValid"
>
添加
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Add directory dialog -->
<v-dialog
v-model="showAddDirDialog"
max-width="600"
>
<v-card>
<v-card-title>添加目录</v-card-title>
<v-card-text>
<v-form v-model="dirFormValid">
<v-text-field
v-model="newDir.path"
label="目录路径 *"
placeholder="例如: src/components"
:rules="[(v) => !!v || '目录路径不能为空']"
data-testid="add-dir-path-input"
/>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn @click="showAddDirDialog = false"> 取消 </v-btn>
<v-btn
color="primary"
@click="addDirectory"
:disabled="!dirFormValid"
>
添加
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useTemplateStore } from '@/renderer/store/template'
const router = useRouter()
const route = useRoute()
const templateStore = useTemplateStore()
// Form validation
const formValid = ref(false)
const fileFormValid = ref(false)
const dirFormValid = ref(false)
// Template data
const templateId = ref(route.params.id as string)
const template = ref({
id: '',
name: '',
description: '',
files: [] as any[],
directories: [] as any[]
})
// Dialogs
const showAddFileDialog = ref(false)
const showAddDirDialog = ref(false)
// New file data
const newFile = ref({
path: '',
content: ''
})
// New directory data
const newDir = ref({
path: ''
})
// Table headers
const fileHeaders = ref([
{ title: '文件路径', key: 'path' },
{ title: '操作', key: 'actions', sortable: false }
])
const dirHeaders = ref([
{ title: '目录路径', key: 'path' },
{ title: '操作', key: 'actions', sortable: false }
])
onMounted(async () => {
await templateStore.loadTemplates()
if (templateId.value) {
// Edit existing template
const existingTemplate = templateStore.getTemplateById(templateId.value)
if (existingTemplate) {
template.value = { ...existingTemplate }
} else {
// If template doesn't exist, redirect to template list
router.push('/templates')
}
} else {
// New template
template.value = {
id: '',
name: '',
description: '',
files: [],
directories: []
}
}
})
const addFile = () => {
if (!newFile.value.path.trim()) return
// Check for duplicate file paths
const exists = template.value.files.some((f) => f.path === newFile.value.path)
if (exists) {
alert('该文件路径已存在!')
return
}
template.value.files.push({
path: newFile.value.path,
content: newFile.value.content
})
newFile.value = { path: '', content: '' }
showAddFileDialog.value = false
}
const removeFile = (file: any) => {
template.value.files = template.value.files.filter((f) => f !== file)
}
const addDirectory = () => {
if (!newDir.value.path.trim()) return
// Check for duplicate directory paths
const exists = template.value.directories.some(
(d) => d.path === newDir.value.path
)
if (exists) {
alert('该目录路径已存在!')
return
}
template.value.directories.push({
path: newDir.value.path
})
newDir.value = { path: '' }
showAddDirDialog.value = false
}
const removeDirectory = (dir: any) => {
template.value.directories = template.value.directories.filter(
(d) => d !== dir
)
}
const saveTemplate = () => {
if (!formValid.value) return
if (templateId.value) {
// Update existing template
templateStore.updateTemplate(template.value)
} else {
// Create new template
templateStore.addTemplate(template.value)
}
router.push('/templates')
}
const cancel = () => {
router.push('/templates')
}
</script>