refactor(模板管理): 移除未保存更改检测并提取模板卡片组件

移除CreateTemplateScreen中的未保存更改检测逻辑,简化保存和返回操作
将TemplateListScreen中的模板卡片提取为独立的TemplateCard组件
This commit is contained in:
zhanggy committed 2025-10-31 16:37:06 +08:00
1 parent f3e909923a
commit 35a0821153
3 files changed
+109 -103

No files matched your search

+100
View File
@@ -0,0 +1,100 @@
<template>
<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">
节点: {{ nodeCount.files }}
</v-chip>
<v-chip size="small" color="success" variant="tonal">
目录: {{ nodeCount.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="$emit('view', template)">
查看
</v-btn>
<v-btn color="success" variant="text" size="small" @click="$emit('execute', 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="$emit('edit', 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="$emit('delete', 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>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Template, TreeNode } from '@/renderer/store/template'
interface Props {
template: Template
}
interface Emits {
(e: 'view', template: Template): void
(e: 'execute', template: Template): void
(e: 'edit', template: Template): void
(e: 'delete', template: Template): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
// Count files and directories in tree structure
const nodeCount = computed(() => {
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(props.template.treeStructure || [])
return { files, directories }
})
</script>
+1 -26
View File
@@ -62,7 +62,7 @@
<v-btn <v-btn
color="primary" color="primary"
@click="saveTemplate" @click="saveTemplate"
:disabled="!formValid || !hasChanges" :disabled="!formValid"
prepend-icon="mdi-content-save" prepend-icon="mdi-content-save"
block block
> >
@@ -166,7 +166,6 @@ const { getTemplateById } = templateStore
const formValid = ref(false) const formValid = ref(false)
const templateName = ref('') const templateName = ref('')
const templateDescription = ref('') const templateDescription = ref('')
const hasChanges = ref(false)
// Dialogs // Dialogs
const showFileDialog = ref(false) const showFileDialog = ref(false)
@@ -195,14 +194,6 @@ const isEditMode = computed(() => !!route.params.id)
const requiredRule = (fieldName: string) => (v: string | null | undefined) => const requiredRule = (fieldName: string) => (v: string | null | undefined) =>
!!v || `${fieldName}为必填项` !!v || `${fieldName}为必填项`
// Watch for changes to detect unsaved changes
watch(
[templateName, templateDescription, treeStructure],
() => {
hasChanges.value = true
},
{ deep: true }
)
// Lifecycle // Lifecycle
onMounted(async () => { onMounted(async () => {
@@ -223,7 +214,6 @@ onMounted(async () => {
templateName.value = template.name templateName.value = template.name
templateDescription.value = template.description templateDescription.value = template.description
treeStructure.value = [...template.treeStructure] treeStructure.value = [...template.treeStructure]
hasChanges.value = false
} else { } else {
router.push('/templates') router.push('/templates')
} }
@@ -431,22 +421,8 @@ const handleFileDialogConfirm = (data: { path: string; sourcePath?: string; sour
} }
const goBack = async () => { const goBack = async () => {
if (hasChanges.value) {
const confirmed = await ConfirmDialogService.confirm({
title: '确认离开',
message: '您有未保存的更改,确定要离开吗?',
confirmText: '离开',
cancelText: '取消',
confirmColor: 'warning'
})
if (confirmed) {
router.push('/templates') router.push('/templates')
} }
} else {
router.push('/templates')
}
}
// Unified method to edit items (both files and directories) // Unified method to edit items (both files and directories)
@@ -540,7 +516,6 @@ const saveTemplate = () => {
templateStore.addTemplate(templateData) templateStore.addTemplate(templateData)
} }
hasChanges.value = false
router.push('/templates') router.push('/templates')
} }
</script> </script>
+8 -77
View File
@@ -28,62 +28,13 @@
<div v-else> <div v-else>
<v-row> <v-row>
<v-col v-for="template in templates" :key="template.id" cols="12" sm="6" md="4" lg="3"> <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%"> <TemplateCard
<v-card-item> :template="template"
<v-card-title>{{ template.name }}</v-card-title> @view="viewTemplate"
<v-card-subtitle>{{ template.description }}</v-card-subtitle> @execute="executeTemplate"
</v-card-item> @edit="editTemplate"
@delete="deleteTemplate"
<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-col>
</v-row> </v-row>
</div> </div>
@@ -141,6 +92,7 @@ import { useRouter } from 'vue-router'
import { useTemplateStore, TreeNode } from '@/renderer/store/template' import { useTemplateStore, TreeNode } from '@/renderer/store/template'
import { Template } from '@/renderer/store/template' import { Template } from '@/renderer/store/template'
import TemplateTree from '@/renderer/components/TemplateTree.vue' import TemplateTree from '@/renderer/components/TemplateTree.vue'
import TemplateCard from '@/renderer/components/TemplateCard.vue'
import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service' import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service'
const router = useRouter() const router = useRouter()
@@ -166,28 +118,7 @@ const executePath = ref('')
const baseDirNames = ref('') const baseDirNames = ref('')
const executeFormValid = ref(false) 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[]>(() => { const templateTree = computed<TreeNode[]>(() => {
if (!selectedTemplate.value) return [] if (!selectedTemplate.value) return []