278 lines
8.9 KiB
Vue
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>
|