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:
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>
|
||||
Reference in new issue
Block a user