feat(模板): 添加模板复制功能并优化文件操作

- 在模板列表和创建模板界面添加复制模板功能
- 将模板卡片样式改为outlined并修正节点统计标签
- 将同步文件操作改为异步操作提升性能
- 在执行模板对话框显示当前模板名称
This commit is contained in:
zhanggy committed 2025-10-31 18:42:31 +08:00
1 parent 466c02773f
commit 5d7d31c04d
4 files changed
+59 -29

No files matched your search

+25 -26
View File
@@ -2,12 +2,13 @@ import { ipcMain, shell, IpcMainEvent, dialog } from 'electron'
import Constants from './utils/Constants' import Constants from './utils/Constants'
import { join, dirname } from 'path' import { join, dirname } from 'path'
import { import {
writeFileSync, writeFile,
readFileSync, readFile,
existsSync, pathExists,
ensureDirSync, ensureDir,
copyFileSync, copy,
readdirSync readdir,
stat,
} from 'fs-extra' } from 'fs-extra'
import { autoUpdater } from 'electron-updater' import { autoUpdater } from 'electron-updater'
import { Template, TreeNode } from '../types' import { Template, TreeNode } from '../types'
@@ -95,21 +96,19 @@ export default class IPCs {
'msgFindFilesInFolder', 'msgFindFilesInFolder',
async (event: IpcMainEvent, folderPath: string) => { async (event: IpcMainEvent, folderPath: string) => {
try { try {
const { readdirSync, statSync } = require('fs')
const { join } = require('path')
const foundFiles: string[] = [] const foundFiles: string[] = []
const searchFiles = (dir: string) => { const searchFiles = async (dir: string) => {
const items = readdirSync(dir) const items = await readdir(dir)
for (const item of items) { for (const item of items) {
const fullPath = join(dir, item) const fullPath = join(dir, item)
const stats = statSync(fullPath) const stats = await stat(fullPath)
if (stats.isDirectory()) { if (stats.isDirectory()) {
// Recursively search in subdirectories // Recursively search in subdirectories
searchFiles(fullPath) await searchFiles(fullPath)
} else if (stats.isFile()) { } else if (stats.isFile()) {
// Add file to the list // Add file to the list
foundFiles.push(fullPath) foundFiles.push(fullPath)
@@ -117,7 +116,7 @@ export default class IPCs {
} }
} }
searchFiles(folderPath) await searchFiles(folderPath)
return foundFiles return foundFiles
} catch (error) { } catch (error) {
@@ -136,10 +135,10 @@ export default class IPCs {
const filePath = join(userDataPath, fileName) const filePath = join(userDataPath, fileName)
// Ensure directory exists // Ensure directory exists
ensureDirSync(userDataPath) await ensureDir(userDataPath)
if (existsSync(filePath)) { if (await pathExists(filePath)) {
const data = readFileSync(filePath, 'utf-8') const data = await readFile(filePath, 'utf-8')
return data return data
} }
return null return null
@@ -159,9 +158,9 @@ export default class IPCs {
const filePath = join(userDataPath, fileName) const filePath = join(userDataPath, fileName)
// Ensure directory exists // Ensure directory exists
ensureDirSync(userDataPath) await ensureDir(userDataPath)
writeFileSync(filePath, data, 'utf-8') await writeFile(filePath, data, 'utf-8')
return true return true
} catch (error) { } catch (error) {
console.error('Error saving data:', error) console.error('Error saving data:', error)
@@ -185,8 +184,8 @@ export default class IPCs {
const filePath = join(userDataPath, 'templates.json') const filePath = join(userDataPath, 'templates.json')
let templates: Template[] = [] let templates: Template[] = []
if (existsSync(filePath)) { if (await pathExists(filePath)) {
const templatesData = readFileSync(filePath, 'utf-8') const templatesData = await readFile(filePath, 'utf-8')
templates = JSON.parse(templatesData) templates = JSON.parse(templatesData)
} }
@@ -202,7 +201,7 @@ export default class IPCs {
// Create base directory // Create base directory
const baseDirPath = join(targetPath, baseDirName) const baseDirPath = join(targetPath, baseDirName)
ensureDirSync(baseDirPath) await ensureDir(baseDirPath)
// Helper function to traverse the tree and collect all nodes // Helper function to traverse the tree and collect all nodes
const collectAllNodes = (nodes: TreeNode[], allNodes: TreeNode[] = [], parent?: TreeNode) => { const collectAllNodes = (nodes: TreeNode[], allNodes: TreeNode[] = [], parent?: TreeNode) => {
@@ -227,20 +226,20 @@ export default class IPCs {
const nodePath = join(baseDirPath, node.path); const nodePath = join(baseDirPath, node.path);
if (node.type === 'directory') { if (node.type === 'directory') {
ensureDirSync(nodePath); await ensureDir(nodePath);
} else if (node.type === 'file') { } else if (node.type === 'file') {
const fileDir = dirname(nodePath); const fileDir = dirname(nodePath);
ensureDirSync(fileDir); await ensureDir(fileDir);
if (node.sourceType === 'file') { if (node.sourceType === 'file') {
copyFileSync(node.sourcePath, nodePath); await copy(node.sourcePath, nodePath);
} else { } else {
// Write content directly // Write content directly
const files = readdirSync(node.sourcePath, { withFileTypes: true }) const files = await readdir(node.sourcePath, { withFileTypes: true })
for (const file of files) { for (const file of files) {
if (file.isFile() && file.name.startsWith(baseDirName)) { if (file.isFile() && file.name.startsWith(baseDirName)) {
const filePath = join(node.sourcePath, file.name); const filePath = join(node.sourcePath, file.name);
copyFileSync(filePath, nodePath); await copy(filePath, nodePath);
} }
} }
} }
+10 -2
View File
@@ -1,5 +1,5 @@
<template> <template>
<v-card class="d-flex flex-column" height="100%"> <v-card class="d-flex flex-column" height="100%" variant="outlined">
<v-card-item> <v-card-item>
<v-card-title>{{ template.name }}</v-card-title> <v-card-title>{{ template.name }}</v-card-title>
<v-card-subtitle>{{ template.description }}</v-card-subtitle> <v-card-subtitle>{{ template.description }}</v-card-subtitle>
@@ -8,7 +8,7 @@
<v-card-text class="flex-grow-1 py-2"> <v-card-text class="flex-grow-1 py-2">
<v-chip-group column> <v-chip-group column>
<v-chip size="small" color="primary" variant="tonal"> <v-chip size="small" color="primary" variant="tonal">
节点: {{ nodeCount.files }} 文件: {{ nodeCount.files }}
</v-chip> </v-chip>
<v-chip size="small" color="success" variant="tonal"> <v-chip size="small" color="success" variant="tonal">
目录: {{ nodeCount.directories }} 目录: {{ nodeCount.directories }}
@@ -43,6 +43,13 @@
<v-list-item-title>修改</v-list-item-title> <v-list-item-title>修改</v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item @click="$emit('copy', template)">
<template v-slot:prepend>
<v-icon color="primary">mdi-content-copy</v-icon>
</template>
<v-list-item-title>复制</v-list-item-title>
</v-list-item>
<v-divider></v-divider> <v-divider></v-divider>
<v-list-item @click="$emit('delete', template)"> <v-list-item @click="$emit('delete', template)">
@@ -70,6 +77,7 @@ interface Emits {
(e: 'execute', template: Template): void (e: 'execute', template: Template): void
(e: 'edit', template: Template): void (e: 'edit', template: Template): void
(e: 'delete', template: Template): void (e: 'delete', template: Template): void
(e: 'copy', template: Template): void
} }
const props = defineProps<Props>() const props = defineProps<Props>()
@@ -163,6 +163,9 @@ const snackbar = useSnackbarStore()
// Extract methods from store // Extract methods from store
const { getTemplateById } = templateStore const { getTemplateById } = templateStore
// Check if we're copying a template
const isCopyingTemplate = computed(() => !route.params.id && !!route.query.copyId)
// Form state // Form state
const formValid = ref(false) const formValid = ref(false)
const templateName = ref('') const templateName = ref('')
@@ -218,6 +221,20 @@ onMounted(async () => {
} else { } else {
router.push('/templates') router.push('/templates')
} }
} else if (isCopyingTemplate.value) {
// Handle copy mode
const copyId = route.query.copyId as string
if (copyId) {
const template = getTemplateById(copyId)
if (template) {
templateName.value = `${template.name} - 副本`
templateDescription.value = template.description
treeStructure.value = [...template.treeStructure]
} else {
snackbar.error('找不到要复制的模板')
router.push('/templates')
}
}
} }
+7 -1
View File
@@ -33,6 +33,7 @@
@view="viewTemplate" @view="viewTemplate"
@execute="executeTemplate" @execute="executeTemplate"
@edit="editTemplate" @edit="editTemplate"
@copy="copyTemplate"
@delete="deleteTemplate" @delete="deleteTemplate"
/> />
</v-col> </v-col>
@@ -64,7 +65,7 @@
<!-- Execute template dialog --> <!-- Execute template dialog -->
<v-dialog v-model="showExecuteDialog" max-width="600"> <v-dialog v-model="showExecuteDialog" max-width="600">
<v-card> <v-card>
<v-card-title>执行模板</v-card-title> <v-card-title>执行模板: {{ selectedTemplate?.name }}</v-card-title>
<v-card-text> <v-card-text>
<v-form v-model="executeFormValid"> <v-form v-model="executeFormValid">
<v-text-field v-model="executePath" label="选择目录" readonly @click="selectDirectory" <v-text-field v-model="executePath" label="选择目录" readonly @click="selectDirectory"
@@ -140,6 +141,11 @@ const editTemplate = (template: Template) => {
router.push(`/create-template/${template.id}`) router.push(`/create-template/${template.id}`)
} }
const copyTemplate = (template: Template) => {
router.push(`/create-template?copyId=${template.id}`)
}
const deleteTemplate = async (template: Template) => { const deleteTemplate = async (template: Template) => {
const confirmed = await ConfirmDialogService.confirm({ const confirmed = await ConfirmDialogService.confirm({
title: '删除模板', title: '删除模板',