feat(模板): 添加模板复制功能并优化文件操作
- 在模板列表和创建模板界面添加复制模板功能 - 将模板卡片样式改为outlined并修正节点统计标签 - 将同步文件操作改为异步操作提升性能 - 在执行模板对话框显示当前模板名称
This commit is contained in:
1 parent
466c02773f
commit
5d7d31c04d
4 files changed
+60
-30
No files matched your search
+25
-26
@@ -2,12 +2,13 @@ import { ipcMain, shell, IpcMainEvent, dialog } from 'electron'
|
||||
import Constants from './utils/Constants'
|
||||
import { join, dirname } from 'path'
|
||||
import {
|
||||
writeFileSync,
|
||||
readFileSync,
|
||||
existsSync,
|
||||
ensureDirSync,
|
||||
copyFileSync,
|
||||
readdirSync
|
||||
writeFile,
|
||||
readFile,
|
||||
pathExists,
|
||||
ensureDir,
|
||||
copy,
|
||||
readdir,
|
||||
stat,
|
||||
} from 'fs-extra'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { Template, TreeNode } from '../types'
|
||||
@@ -95,21 +96,19 @@ export default class IPCs {
|
||||
'msgFindFilesInFolder',
|
||||
async (event: IpcMainEvent, folderPath: string) => {
|
||||
try {
|
||||
const { readdirSync, statSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
|
||||
const foundFiles: string[] = []
|
||||
|
||||
const searchFiles = (dir: string) => {
|
||||
const items = readdirSync(dir)
|
||||
const searchFiles = async (dir: string) => {
|
||||
const items = await readdir(dir)
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = join(dir, item)
|
||||
const stats = statSync(fullPath)
|
||||
const stats = await stat(fullPath)
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
// Recursively search in subdirectories
|
||||
searchFiles(fullPath)
|
||||
await searchFiles(fullPath)
|
||||
} else if (stats.isFile()) {
|
||||
// Add file to the list
|
||||
foundFiles.push(fullPath)
|
||||
@@ -117,7 +116,7 @@ export default class IPCs {
|
||||
}
|
||||
}
|
||||
|
||||
searchFiles(folderPath)
|
||||
await searchFiles(folderPath)
|
||||
|
||||
return foundFiles
|
||||
} catch (error) {
|
||||
@@ -136,10 +135,10 @@ export default class IPCs {
|
||||
const filePath = join(userDataPath, fileName)
|
||||
|
||||
// Ensure directory exists
|
||||
ensureDirSync(userDataPath)
|
||||
await ensureDir(userDataPath)
|
||||
|
||||
if (existsSync(filePath)) {
|
||||
const data = readFileSync(filePath, 'utf-8')
|
||||
if (await pathExists(filePath)) {
|
||||
const data = await readFile(filePath, 'utf-8')
|
||||
return data
|
||||
}
|
||||
return null
|
||||
@@ -159,9 +158,9 @@ export default class IPCs {
|
||||
const filePath = join(userDataPath, fileName)
|
||||
|
||||
// Ensure directory exists
|
||||
ensureDirSync(userDataPath)
|
||||
await ensureDir(userDataPath)
|
||||
|
||||
writeFileSync(filePath, data, 'utf-8')
|
||||
await writeFile(filePath, data, 'utf-8')
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Error saving data:', error)
|
||||
@@ -185,8 +184,8 @@ export default class IPCs {
|
||||
const filePath = join(userDataPath, 'templates.json')
|
||||
|
||||
let templates: Template[] = []
|
||||
if (existsSync(filePath)) {
|
||||
const templatesData = readFileSync(filePath, 'utf-8')
|
||||
if (await pathExists(filePath)) {
|
||||
const templatesData = await readFile(filePath, 'utf-8')
|
||||
templates = JSON.parse(templatesData)
|
||||
}
|
||||
|
||||
@@ -202,7 +201,7 @@ export default class IPCs {
|
||||
|
||||
// Create base directory
|
||||
const baseDirPath = join(targetPath, baseDirName)
|
||||
ensureDirSync(baseDirPath)
|
||||
await ensureDir(baseDirPath)
|
||||
|
||||
// Helper function to traverse the tree and collect all nodes
|
||||
const collectAllNodes = (nodes: TreeNode[], allNodes: TreeNode[] = [], parent?: TreeNode) => {
|
||||
@@ -227,20 +226,20 @@ export default class IPCs {
|
||||
const nodePath = join(baseDirPath, node.path);
|
||||
|
||||
if (node.type === 'directory') {
|
||||
ensureDirSync(nodePath);
|
||||
await ensureDir(nodePath);
|
||||
} else if (node.type === 'file') {
|
||||
const fileDir = dirname(nodePath);
|
||||
ensureDirSync(fileDir);
|
||||
await ensureDir(fileDir);
|
||||
|
||||
if (node.sourceType === 'file') {
|
||||
copyFileSync(node.sourcePath, nodePath);
|
||||
await copy(node.sourcePath, nodePath);
|
||||
} else {
|
||||
// Write content directly
|
||||
const files = readdirSync(node.sourcePath, { withFileTypes: true })
|
||||
const files = await readdir(node.sourcePath, { withFileTypes: true })
|
||||
for (const file of files) {
|
||||
if (file.isFile() && file.name.startsWith(baseDirName)) {
|
||||
const filePath = join(node.sourcePath, file.name);
|
||||
copyFileSync(filePath, nodePath);
|
||||
await copy(filePath, nodePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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-title>{{ template.name }}</v-card-title>
|
||||
<v-card-subtitle>{{ template.description }}</v-card-subtitle>
|
||||
@@ -8,7 +8,7 @@
|
||||
<v-card-text class="flex-grow-1 py-2">
|
||||
<v-chip-group column>
|
||||
<v-chip size="small" color="primary" variant="tonal">
|
||||
节点: {{ nodeCount.files }}
|
||||
文件: {{ nodeCount.files }}
|
||||
</v-chip>
|
||||
<v-chip size="small" color="success" variant="tonal">
|
||||
目录: {{ nodeCount.directories }}
|
||||
@@ -43,6 +43,13 @@
|
||||
<v-list-item-title>修改</v-list-item-title>
|
||||
</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-list-item @click="$emit('delete', template)">
|
||||
@@ -70,6 +77,7 @@ interface Emits {
|
||||
(e: 'execute', template: Template): void
|
||||
(e: 'edit', template: Template): void
|
||||
(e: 'delete', template: Template): void
|
||||
(e: 'copy', template: Template): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -97,4 +105,4 @@ const nodeCount = computed(() => {
|
||||
|
||||
return { files, directories }
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
@@ -163,6 +163,9 @@ const snackbar = useSnackbarStore()
|
||||
// Extract methods from store
|
||||
const { getTemplateById } = templateStore
|
||||
|
||||
// Check if we're copying a template
|
||||
const isCopyingTemplate = computed(() => !route.params.id && !!route.query.copyId)
|
||||
|
||||
// Form state
|
||||
const formValid = ref(false)
|
||||
const templateName = ref('')
|
||||
@@ -218,6 +221,20 @@ onMounted(async () => {
|
||||
} else {
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
@view="viewTemplate"
|
||||
@execute="executeTemplate"
|
||||
@edit="editTemplate"
|
||||
@copy="copyTemplate"
|
||||
@delete="deleteTemplate"
|
||||
/>
|
||||
</v-col>
|
||||
@@ -64,7 +65,7 @@
|
||||
<!-- Execute template dialog -->
|
||||
<v-dialog v-model="showExecuteDialog" max-width="600">
|
||||
<v-card>
|
||||
<v-card-title>执行模板</v-card-title>
|
||||
<v-card-title>执行模板: {{ selectedTemplate?.name }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form v-model="executeFormValid">
|
||||
<v-text-field v-model="executePath" label="选择目录" readonly @click="selectDirectory"
|
||||
@@ -140,6 +141,11 @@ const editTemplate = (template: Template) => {
|
||||
router.push(`/create-template/${template.id}`)
|
||||
}
|
||||
|
||||
const copyTemplate = (template: Template) => {
|
||||
router.push(`/create-template?copyId=${template.id}`)
|
||||
}
|
||||
|
||||
|
||||
const deleteTemplate = async (template: Template) => {
|
||||
const confirmed = await ConfirmDialogService.confirm({
|
||||
title: '删除模板',
|
||||
|
||||
Reference in new issue
Block a user