Files
gdt-tool/src/renderer/screens/TemplateListScreen.vue
T

278 lines
8.9 KiB
Vue

<template>
<v-container>
<v-row>
<v-col cols="12">
<div class="d-flex align-center mb-4 justify-space-between flex-wrap">
<h2 class="mr-2 mb-2"> 文件创建 </h2>
<v-btn @click="navigateToCreateTemplate" color="primary" prepend-icon="mdi-plus" class="mb-2">
创建模板
</v-btn>
</div>
<!-- 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="default" @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" sm="6" md="4" lg="3">
<v-card class="d-flex flex-column" height="100%">
<v-card-item>
<v-card-title>{{ template.name }}</v-card-title>
<v-card-subtitle>{{ template.description }}</v-card-subtitle>
</v-card-item>
<v-card-text class="flex-grow-1 py-2">
<v-chip-group column>
<v-chip size="small" color="primary" variant="tonal">
节点: {{ countTreeNodes(template.treeStructure).files }}
</v-chip>
<v-chip size="small" color="success" variant="tonal">
目录: {{ countTreeNodes(template.treeStructure).directories }}
</v-chip>
</v-chip-group>
</v-card-text>
<v-divider class="mt-auto"></v-divider>
<v-card-actions class="pa-2 justify-space-between">
<div>
<v-btn color="primary" variant="text" size="small" @click="viewTemplate(template)">
查看
</v-btn>
<v-btn color="success" variant="text" size="small" @click="executeTemplate(template)">
执行
</v-btn>
</div>
<v-menu location="bottom end">
<template v-slot:activator="{ props }">
<v-btn v-bind="props" icon variant="text" size="small" density="comfortable">
<v-icon>mdi-dots-vertical</v-icon>
</v-btn>
</template>
<v-list density="compact">
<v-list-item @click="editTemplate(template)">
<template v-slot:prepend>
<v-icon>mdi-pencil</v-icon>
</template>
<v-list-item-title>修改</v-list-item-title>
</v-list-item>
<v-divider></v-divider>
<v-list-item @click="deleteTemplate(template)">
<template v-slot:prepend>
<v-icon color="error">mdi-delete</v-icon>
</template>
<v-list-item-title class="text-error">删除</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</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-card-title>
<v-card-text>
<TemplateTree
:items="templateTree"
:height="400"
:read-only="true"
:empty-text="'模板结构为空'"
:expanded="expandedTreeNodes"
@update:expanded="expandedTreeNodes = $event"
/>
</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>
</v-container>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useTemplateStore, TreeNode } from '@/renderer/store/template'
import { Template } from '@/renderer/store/template'
import TemplateTree from '@/renderer/components/TemplateTree.vue'
import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service'
const router = useRouter()
const templateStore = useTemplateStore()
// Initialize templates
onMounted(async () => {
await templateStore.loadTemplates()
})
const { deleteTemplate: storeDeleteTemplate } = templateStore
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)
const executePath = ref('')
const baseDirNames = ref('')
const executeFormValid = ref(false)
// Count files and directories in tree structure
const countTreeNodes = (nodes: TreeNode[]) => {
let files = 0;
let directories = 0;
const count = (nodeList: TreeNode[]) => {
nodeList.forEach(node => {
if (node.type === 'file') {
files++;
} else if (node.type === 'directory') {
directories++;
if (node.children) {
count(node.children);
}
}
});
};
count(nodes || []);
return { files, directories };
}
const templateTree = computed<TreeNode[]>(() => {
if (!selectedTemplate.value) return []
// 直接返回模板的树状结构
return selectedTemplate.value.treeStructure || []
})
const navigateToCreateTemplate = () => {
router.push('/create-template')
}
const viewTemplate = (template: Template) => {
selectedTemplate.value = template
showTemplateDetail.value = true
}
const editTemplate = (template: Template) => {
router.push(`/create-template/${template.id}`)
}
const deleteTemplate = async (template: Template) => {
const confirmed = await ConfirmDialogService.confirm({
title: '删除模板',
message: `确定要删除模板 "${template.name}" 吗?此操作不可撤销。`,
confirmText: '删除',
cancelText: '取消',
confirmColor: 'error'
})
if (confirmed) {
storeDeleteTemplate(template.id)
}
}
const executeTemplate = (template: Template) => {
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() || !selectedTemplate.value) 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(async (result: any) => {
if (result.success) {
await ConfirmDialogService.success({
title: '执行成功',
message: '模板执行成功!',
confirmText: '确定'
})
showExecuteDialog.value = false
} else {
await ConfirmDialogService.error({
title: '执行失败',
message: `模板执行失败: ${result.error}`,
confirmText: '确定'
})
}
})
.catch(async (error: any) => {
await ConfirmDialogService.error({
title: '执行出错',
message: `模板执行出错: ${error.message}`,
confirmText: '确定'
})
})
}
</script>