feat: 重构模板管理,采用树状结构存储文件和目录,优化模板创建和编辑流程
This commit is contained in:
1 parent
f8890ff1e9
commit
5ca31a5348
4 files changed
+349
-244
No files matched your search
+32
-17
@@ -177,25 +177,40 @@ export default class IPCs {
|
||||
const baseDirPath = join(targetPath, baseDirName)
|
||||
ensureDirSync(baseDirPath)
|
||||
|
||||
// Create directories from template
|
||||
for (const dir of template.directories) {
|
||||
const dirPath = join(baseDirPath, dir.path)
|
||||
ensureDirSync(dirPath)
|
||||
}
|
||||
// Helper function to traverse the tree and collect all nodes
|
||||
const collectAllNodes = (nodes, allNodes = []) => {
|
||||
for (const node of nodes) {
|
||||
allNodes.push(node);
|
||||
if (node.children) {
|
||||
collectAllNodes(node.children, allNodes);
|
||||
}
|
||||
}
|
||||
return allNodes;
|
||||
};
|
||||
|
||||
// Create files from template
|
||||
for (const file of template.files) {
|
||||
const filePath = join(baseDirPath, file.path)
|
||||
const fileDir = dirname(filePath)
|
||||
ensureDirSync(fileDir)
|
||||
// Collect all nodes from the tree structure
|
||||
if (template.treeStructure && template.treeStructure.length > 0) {
|
||||
const allNodes = collectAllNodes(template.treeStructure);
|
||||
|
||||
if (file.sourceFilePath) {
|
||||
// Copy from source file
|
||||
const sourceContent = readFileSync(file.sourceFilePath)
|
||||
writeFileSync(filePath, sourceContent)
|
||||
} else if (file.content) {
|
||||
// Write content directly
|
||||
writeFileSync(filePath, file.content, 'utf-8')
|
||||
// Process all nodes, making sure directories are created before files
|
||||
for (const node of allNodes) {
|
||||
const nodePath = join(baseDirPath, node.path);
|
||||
|
||||
if (node.type === 'directory') {
|
||||
ensureDirSync(nodePath);
|
||||
} else if (node.type === 'file') {
|
||||
const fileDir = dirname(nodePath);
|
||||
ensureDirSync(fileDir);
|
||||
|
||||
if (node.sourceFilePath) {
|
||||
// Copy from source file
|
||||
const sourceContent = readFileSync(node.sourceFilePath);
|
||||
writeFileSync(nodePath, sourceContent);
|
||||
} else if (node.content) {
|
||||
// Write content directly
|
||||
writeFileSync(nodePath, node.content, 'utf-8');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,8 +348,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useTemplateStore } from '@/renderer/store/template'
|
||||
import { Template } from '@/renderer/store/template'
|
||||
import { useTemplateStore, TreeNode } from '@/renderer/store/template'
|
||||
import TemplateTree from '@/renderer/components/TemplateTree.vue'
|
||||
import { ConfirmDialogService } from '@/renderer/utils/confirm-dialog-service'
|
||||
|
||||
@@ -405,99 +404,37 @@ interface TreeItem {
|
||||
children?: TreeItem[]
|
||||
}
|
||||
|
||||
// Item state
|
||||
const directories = ref<{ path: string }[]>([])
|
||||
const files = ref<{ path: string; content?: string; sourceFilePath?: string }[]>([])
|
||||
// Item state - 使用树状结构
|
||||
const treeStructure = ref<TreeNode[]>([])
|
||||
|
||||
// Computed
|
||||
const isEditMode = computed(() => !!route.params.id)
|
||||
const treeItems = computed<TreeItem[]>(() => {
|
||||
// Create root structure
|
||||
const root: TreeItem = {
|
||||
id: 'root',
|
||||
name: 'root',
|
||||
type: 'directory',
|
||||
children: []
|
||||
// 直接返回树状结构
|
||||
return treeStructure.value.map(node => convertToTreeItem(node));
|
||||
})
|
||||
|
||||
// 转换 TreeNode 到 TreeItem 的辅助函数
|
||||
const convertToTreeItem = (node: TreeNode): TreeItem => {
|
||||
const treeItem: TreeItem = {
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
type: node.type
|
||||
};
|
||||
|
||||
if (node.children && node.children.length > 0) {
|
||||
treeItem.children = node.children.map(child => convertToTreeItem(child));
|
||||
}
|
||||
|
||||
// Process each directory
|
||||
directories.value.forEach((dir) => {
|
||||
const pathParts = dir.path.split('/').filter(part => part !== '') // Split path and remove empty parts
|
||||
let currentLevel = root.children || []
|
||||
|
||||
// Navigate/create the directory structure
|
||||
for (let i = 0; i < pathParts.length; i++) {
|
||||
const part = pathParts[i]
|
||||
const isLastPart = i === pathParts.length - 1
|
||||
|
||||
// Check if this part already exists in the current level
|
||||
let existingItem = currentLevel.find(item => item.name === part)
|
||||
|
||||
if (!existingItem) {
|
||||
// Create new directory item
|
||||
const newDir: TreeItem = {
|
||||
id: `dir-${part}-${Date.now()}-${Math.random()}`, // Unique ID
|
||||
name: part,
|
||||
type: 'directory',
|
||||
children: []
|
||||
}
|
||||
currentLevel.push(newDir)
|
||||
existingItem = newDir
|
||||
}
|
||||
|
||||
// Move to the next level if not the last part
|
||||
if (!isLastPart && existingItem.children) {
|
||||
currentLevel = existingItem.children
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Process each file and add to the appropriate directory
|
||||
files.value.forEach((file) => {
|
||||
const pathParts = file.path.split('/').filter(part => part !== '')
|
||||
const fileName = pathParts.pop() // Get the last part as filename
|
||||
|
||||
if (!fileName) return // Skip if no filename
|
||||
|
||||
let currentLevel = root.children || []
|
||||
|
||||
// Navigate to the appropriate directory for the file
|
||||
for (let i = 0; i < pathParts.length; i++) {
|
||||
const part = pathParts[i]
|
||||
const dirItem = currentLevel.find(item => item.name === part && item.type === 'directory')
|
||||
|
||||
if (dirItem && dirItem.children) {
|
||||
currentLevel = dirItem.children
|
||||
} else {
|
||||
// If the directory doesn't exist, create it
|
||||
const newDir: TreeItem = {
|
||||
id: `dir-${part}-${Date.now()}-${Math.random()}`,
|
||||
name: part,
|
||||
type: 'directory',
|
||||
children: []
|
||||
}
|
||||
currentLevel.push(newDir)
|
||||
currentLevel = newDir.children || []
|
||||
}
|
||||
}
|
||||
|
||||
// Add the file to the current level
|
||||
currentLevel.push({
|
||||
id: `file-${fileName}-${Date.now()}-${Math.random()}`,
|
||||
name: fileName,
|
||||
type: 'file'
|
||||
})
|
||||
})
|
||||
|
||||
return root.children || []
|
||||
})
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
// Validation rules
|
||||
const requiredRule = (v: string | null | undefined) => !!v || '此字段为必填项'
|
||||
|
||||
// Watch for changes to detect unsaved changes
|
||||
watch(
|
||||
[templateName, templateDescription, directories, files],
|
||||
[templateName, templateDescription, treeStructure],
|
||||
() => {
|
||||
hasChanges.value = true
|
||||
},
|
||||
@@ -522,8 +459,7 @@ onMounted(async () => {
|
||||
if (template) {
|
||||
templateName.value = template.name
|
||||
templateDescription.value = template.description
|
||||
directories.value = [...template.directories]
|
||||
files.value = [...template.files]
|
||||
treeStructure.value = [...template.treeStructure]
|
||||
hasChanges.value = false
|
||||
} else {
|
||||
router.push('/templates')
|
||||
@@ -601,29 +537,35 @@ const confirmAddDirectory = () => {
|
||||
fullPath = parentPath + newDirectoryPath.value
|
||||
}
|
||||
|
||||
const newId = `dir-${Date.now()}-${Math.random()}`
|
||||
|
||||
// Check if we're editing an existing directory
|
||||
if (currentEditingItem.value && currentEditingItem.value.type === 'directory') {
|
||||
const index = directories.value.findIndex(d => d.path === currentEditingItem.value?.path)
|
||||
if (index !== -1) {
|
||||
// Update the existing directory path
|
||||
directories.value[index].path = fullPath
|
||||
|
||||
// If the directory path changes, update any files/dirs that were inside it
|
||||
updatePathsForRenamedDirectory(currentEditingItem.value.path, fullPath)
|
||||
}
|
||||
// Find and update the existing directory in the tree
|
||||
const updatedNode: TreeNode = {
|
||||
id: currentEditingItem.value.id,
|
||||
name: newDirectoryPath.value,
|
||||
path: fullPath,
|
||||
type: 'directory'
|
||||
};
|
||||
treeStructure.value = updateNodeInTree(treeStructure.value, currentEditingItem.value.path, updatedNode)
|
||||
} else {
|
||||
// Check for duplicates only when creating a new directory
|
||||
const exists = directories.value.some(
|
||||
(d) => d.path === fullPath
|
||||
)
|
||||
if (exists) {
|
||||
showSnackbar('该目录路径已存在!')
|
||||
return
|
||||
// Create new directory node
|
||||
const newNode: TreeNode = {
|
||||
id: newId,
|
||||
name: newDirectoryPath.value,
|
||||
path: fullPath,
|
||||
type: 'directory'
|
||||
}
|
||||
|
||||
directories.value.push({
|
||||
path: fullPath
|
||||
})
|
||||
// Add to tree structure
|
||||
if (parentDirectoryForNewItems.value) {
|
||||
// Add as child to the parent directory
|
||||
treeStructure.value = addChildToNode(treeStructure.value, parentDirectoryForNewItems.value, newNode)
|
||||
} else {
|
||||
// Add as root node
|
||||
treeStructure.value.push(newNode)
|
||||
}
|
||||
}
|
||||
|
||||
showDirectoryDialog.value = false
|
||||
@@ -638,31 +580,44 @@ const confirmAddDirectory = () => {
|
||||
newDirectoryPath.value = ''
|
||||
}
|
||||
|
||||
// Helper function to update paths when a directory is renamed
|
||||
const updatePathsForRenamedDirectory = (oldPath: string, newPath: string) => {
|
||||
// Update all files that were in the old directory
|
||||
files.value = files.value.map(file => {
|
||||
if (file.path.startsWith(oldPath + '/') || file.path === oldPath) {
|
||||
return {
|
||||
...file,
|
||||
path: file.path.replace(oldPath, newPath)
|
||||
}
|
||||
// 在树状结构中更新节点的辅助函数
|
||||
const updateNodeInTree = (nodes: TreeNode[], oldPath: string, updatedNode: TreeNode): TreeNode[] => {
|
||||
return nodes.map(node => {
|
||||
if (node.path === oldPath) {
|
||||
return updatedNode;
|
||||
}
|
||||
return file
|
||||
})
|
||||
|
||||
// Update all directories that were under the old directory
|
||||
directories.value = directories.value.map(dir => {
|
||||
if (dir.path.startsWith(oldPath + '/') || dir.path === oldPath) {
|
||||
if (node.children) {
|
||||
return {
|
||||
...dir,
|
||||
path: dir.path.replace(oldPath, newPath)
|
||||
}
|
||||
...node,
|
||||
children: updateNodeInTree(node.children, oldPath, updatedNode)
|
||||
};
|
||||
}
|
||||
return dir
|
||||
})
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
// 向节点添加子节点的辅助函数
|
||||
const addChildToNode = (nodes: TreeNode[], parentName: string, childNode: TreeNode): TreeNode[] => {
|
||||
return nodes.map(node => {
|
||||
if (node.name === parentName || (node.path && node.path.endsWith('/' + parentName) || node.path === parentName)) {
|
||||
const children = node.children || [];
|
||||
return {
|
||||
...node,
|
||||
children: [...children, childNode]
|
||||
};
|
||||
}
|
||||
if (node.children) {
|
||||
return {
|
||||
...node,
|
||||
children: addChildToNode(node.children, parentName, childNode)
|
||||
};
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Functions to close dialogs and reset state
|
||||
const closeDirectoryDialog = () => {
|
||||
showDirectoryDialog.value = false
|
||||
@@ -693,20 +648,24 @@ const selectAndAddFile = async () => {
|
||||
fullPath = parentPath + newFilePath.value
|
||||
}
|
||||
|
||||
const newId = `file-${Date.now()}-${Math.random()}`
|
||||
|
||||
// Check if we're editing an existing file
|
||||
if (currentEditingItem.value && currentEditingItem.value.type === 'file') {
|
||||
const index = files.value.findIndex(f => f.path === currentEditingItem.value?.path)
|
||||
if (index !== -1) {
|
||||
// Update the existing file
|
||||
files.value[index].path = fullPath
|
||||
if (selectedSourcePath.value) {
|
||||
files.value[index].sourceFilePath = selectedSourcePath.value
|
||||
}
|
||||
}
|
||||
// Find and update the existing file in the tree
|
||||
const updatedNode: TreeNode = {
|
||||
id: currentEditingItem.value.id,
|
||||
name: newFilePath.value,
|
||||
path: fullPath,
|
||||
type: 'file',
|
||||
content: currentEditingItem.value.content,
|
||||
sourceFilePath: currentEditingItem.value.sourceFilePath
|
||||
};
|
||||
treeStructure.value = updateNodeInTree(treeStructure.value, currentEditingItem.value.path, updatedNode)
|
||||
} else {
|
||||
// Check for duplicates only when creating a new file
|
||||
const exists = files.value.some((f) => f.path === fullPath)
|
||||
if (exists) {
|
||||
const hasDuplicate = checkForDuplicateInTree(treeStructure.value, fullPath);
|
||||
if (hasDuplicate) {
|
||||
showSnackbar('该文件路径已存在!')
|
||||
return
|
||||
}
|
||||
@@ -723,13 +682,24 @@ const selectAndAddFile = async () => {
|
||||
const fullTargetPath = fullPath ? `${fullPath}/${relativePath}` : relativePath
|
||||
|
||||
// Check for duplicates
|
||||
const duplicate = files.value.some(f => f.path === fullTargetPath)
|
||||
if (!duplicate) {
|
||||
const newFile = {
|
||||
const hasDuplicateInTree = checkForDuplicateInTree(treeStructure.value, fullTargetPath);
|
||||
if (!hasDuplicateInTree) {
|
||||
const newFileNode: TreeNode = {
|
||||
id: `file-${Date.now()}-${Math.random()}`,
|
||||
name: fullTargetPath.split('/').pop() || fullTargetPath,
|
||||
path: fullTargetPath,
|
||||
type: 'file',
|
||||
sourceFilePath: foundFile
|
||||
}
|
||||
files.value.push(newFile)
|
||||
|
||||
// Add to tree structure
|
||||
if (parentDirectoryForNewItems.value) {
|
||||
// Add as child to the parent directory
|
||||
treeStructure.value = addChildToNode(treeStructure.value, parentDirectoryForNewItems.value, newFileNode)
|
||||
} else {
|
||||
// Add as root node
|
||||
treeStructure.value.push(newFileNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -752,12 +722,22 @@ const selectAndAddFile = async () => {
|
||||
|
||||
// If we have a selected file, just add it
|
||||
if (selectedSourcePath.value) {
|
||||
const newFile = {
|
||||
const newFileNode: TreeNode = {
|
||||
id: newId,
|
||||
name: newFilePath.value,
|
||||
path: fullPath,
|
||||
type: 'file',
|
||||
sourceFilePath: selectedSourcePath.value
|
||||
}
|
||||
|
||||
files.value.push(newFile)
|
||||
// Add to tree structure
|
||||
if (parentDirectoryForNewItems.value) {
|
||||
// Add as child to the parent directory
|
||||
treeStructure.value = addChildToNode(treeStructure.value, parentDirectoryForNewItems.value, newFileNode)
|
||||
} else {
|
||||
// Add as root node
|
||||
treeStructure.value.push(newFileNode)
|
||||
}
|
||||
|
||||
// Reset selection state
|
||||
selectedSourcePath.value = ''
|
||||
@@ -797,6 +777,21 @@ const selectAndAddFile = async () => {
|
||||
newFilePath.value = ''
|
||||
}
|
||||
|
||||
// 检查树中是否有重复路径的辅助函数
|
||||
const checkForDuplicateInTree = (nodes: TreeNode[], pathToCheck: string): boolean => {
|
||||
for (const node of nodes) {
|
||||
if (node.path === pathToCheck) {
|
||||
return true;
|
||||
}
|
||||
if (node.children) {
|
||||
if (checkForDuplicateInTree(node.children, pathToCheck)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const goBack = async () => {
|
||||
if (hasChanges.value) {
|
||||
const confirmed = await ConfirmDialogService.confirm({
|
||||
@@ -843,47 +838,56 @@ const editFile = (item: TreeItem) => {
|
||||
// Unified method to edit items (both files and directories)
|
||||
// This function needs to find the actual path of the item in our hierarchical tree
|
||||
const editItem = (item: TreeItem) => {
|
||||
if (item.type === 'directory') {
|
||||
// Find the directory path in our directories array
|
||||
// We need to find a directory that ends with the same name as this item
|
||||
const dir = directories.value.find(d => {
|
||||
// If the directory path ends with the item name, or the directory path is the same as item name
|
||||
return d.path === item.name || d.path.endsWith('/' + item.name) || d.path.endsWith('\\' + item.name)
|
||||
})
|
||||
if (dir) {
|
||||
// Show a dialog to edit the directory path
|
||||
showEditDirectoryDialog(dir)
|
||||
// Find the node in the tree structure
|
||||
const node = findNodeInTree(treeStructure.value, item.id);
|
||||
if (node) {
|
||||
const nodeData = {
|
||||
id: node.id,
|
||||
path: node.path,
|
||||
type: node.type,
|
||||
content: node.content, // For files
|
||||
sourceFilePath: node.sourceFilePath, // For files
|
||||
name: node.name,
|
||||
};
|
||||
if (node.type === 'directory') {
|
||||
showEditDirectoryDialog(nodeData)
|
||||
} else {
|
||||
showSnackbar('无法找到对应的目录', 'error')
|
||||
}
|
||||
} else if (item.type === 'file') {
|
||||
// Find the file in our files array
|
||||
const file = files.value.find(f => {
|
||||
// If the file path ends with the item name, or the file path is the same as item name
|
||||
return f.path === item.name || f.path.endsWith('/' + item.name) || f.path.endsWith('\\' + item.name)
|
||||
})
|
||||
if (file) {
|
||||
// Show a dialog to edit the file path
|
||||
showEditFileDialog(file)
|
||||
} else {
|
||||
showSnackbar('无法找到对应的文件', 'error')
|
||||
showEditFileDialog(nodeData)
|
||||
}
|
||||
} else {
|
||||
showSnackbar('无法找到对应的节点', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 在树状结构中查找节点的辅助函数
|
||||
const findNodeInTree = (nodes: TreeNode[], nodeId: string): TreeNode | null => {
|
||||
for (const node of nodes) {
|
||||
if (node.id === nodeId) {
|
||||
return node;
|
||||
}
|
||||
if (node.children) {
|
||||
const found = findNodeInTree(node.children, nodeId);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Show dialog to edit directory
|
||||
const showEditDirectoryDialog = (directory: { path: string }) => {
|
||||
const showEditDirectoryDialog = (directory: TreeNode) => {
|
||||
// Set up for editing an existing directory
|
||||
currentEditingItem.value = { ...directory, type: 'directory' }
|
||||
currentEditingItem.value = directory
|
||||
newDirectoryPath.value = directory.path // Set the current path for editing
|
||||
showDirectoryDialog.value = true
|
||||
autoFocusDirectoryInput()
|
||||
}
|
||||
|
||||
// Show dialog to edit file
|
||||
const showEditFileDialog = (file: { path: string; content?: string; sourceFilePath?: string }) => {
|
||||
const showEditFileDialog = (file: TreeNode) => {
|
||||
// Set up for editing an existing file
|
||||
currentEditingItem.value = { ...file, type: 'file' }
|
||||
currentEditingItem.value = file
|
||||
newFilePath.value = file.path // Set the current path for editing
|
||||
|
||||
// If the file has a source file path, set it in the selection state
|
||||
@@ -897,7 +901,7 @@ const showEditFileDialog = (file: { path: string; content?: string; sourceFilePa
|
||||
}
|
||||
|
||||
// Variable to track the current item being edited
|
||||
const currentEditingItem = ref<{ path: string; type: 'directory' | 'file'; content?: string; sourceFilePath?: string } | null>(null)
|
||||
const currentEditingItem = ref<TreeNode | null>(null)
|
||||
|
||||
const deleteItem = async (item: TreeItem) => {
|
||||
const confirmed = await ConfirmDialogService.confirm({
|
||||
@@ -910,30 +914,26 @@ const deleteItem = async (item: TreeItem) => {
|
||||
|
||||
if (!confirmed) return
|
||||
|
||||
if (item.type === 'directory') {
|
||||
// Find all directories that start with the item name (including the exact match)
|
||||
// This matches the directory itself and all its subdirectories
|
||||
directories.value = directories.value.filter(d => {
|
||||
// Exclude if exact match or it's a subdirectory
|
||||
return !isSubPath(d.path, item.name);
|
||||
});
|
||||
|
||||
// Also remove files that are inside this directory
|
||||
files.value = files.value.filter(f => {
|
||||
// Exclude if it's inside the directory being deleted
|
||||
return !isSubPath(f.path, item.name);
|
||||
});
|
||||
} else {
|
||||
// For files, just remove the file
|
||||
files.value = files.value.filter(f => f.path !== item.name);
|
||||
}
|
||||
// 从树状结构中删除项目
|
||||
treeStructure.value = removeItemFromTree(treeStructure.value, item.id);
|
||||
}
|
||||
|
||||
// Helper function to check if pathA is a subpath of pathB
|
||||
const isSubPath = (pathA: string, pathB: string) => {
|
||||
return pathA === pathB || pathA.startsWith(pathB + '/') || pathA.startsWith(pathB + '\\');
|
||||
// 从树状结构中删除项目的辅助函数
|
||||
const removeItemFromTree = (nodes: TreeNode[], itemId: string): TreeNode[] => {
|
||||
return nodes.filter(node => node.id !== itemId) // 过滤掉要删除的节点
|
||||
.map(node => {
|
||||
if (node.children) {
|
||||
return {
|
||||
...node,
|
||||
children: removeItemFromTree(node.children, itemId)
|
||||
};
|
||||
}
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Show snackbar message
|
||||
const showSnackbar = (message: string, color: string = 'error') => {
|
||||
snackbarMessage.value = message
|
||||
@@ -979,8 +979,7 @@ const saveTemplate = () => {
|
||||
const templateData = {
|
||||
name: templateName.value,
|
||||
description: templateDescription.value,
|
||||
files: files.value,
|
||||
directories: directories.value
|
||||
treeStructure: treeStructure.value
|
||||
}
|
||||
|
||||
if (isEditMode.value) {
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
<v-card-text class="flex-grow-1 py-2">
|
||||
<v-chip-group column>
|
||||
<v-chip size="small" color="primary" variant="tonal">
|
||||
文件: {{ template.files.length }}
|
||||
节点: {{ countTreeNodes(template.treeStructure).files }}
|
||||
</v-chip>
|
||||
<v-chip size="small" color="success" variant="tonal">
|
||||
目录: {{ template.directories.length }}
|
||||
目录: {{ countTreeNodes(template.treeStructure).directories }}
|
||||
</v-chip>
|
||||
</v-chip-group>
|
||||
</v-card-text>
|
||||
@@ -138,7 +138,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useTemplateStore } from '@/renderer/store/template'
|
||||
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'
|
||||
@@ -166,42 +166,34 @@ const executePath = ref('')
|
||||
const baseDirNames = ref('')
|
||||
const executeFormValid = ref(false)
|
||||
|
||||
// Define item type for the tree view
|
||||
interface TreeNode {
|
||||
id: string
|
||||
name: string
|
||||
type: 'directory' | 'file'
|
||||
children?: TreeNode[]
|
||||
// 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 []
|
||||
|
||||
const items: TreeNode[] = []
|
||||
|
||||
// Add directories
|
||||
selectedTemplate.value.directories.forEach((dir) => {
|
||||
items.push({
|
||||
id: `dir-${dir.path}`,
|
||||
name: dir.path.split('/').pop() || dir.path, // Show just the directory name, not full path
|
||||
type: 'directory',
|
||||
children: []
|
||||
})
|
||||
})
|
||||
|
||||
// Add files
|
||||
selectedTemplate.value.files.forEach((file) => {
|
||||
items.push({
|
||||
id: `file-${file.path}`,
|
||||
name: file.path.split('/').pop() || file.path, // Show just the file name, not full path
|
||||
type: 'file'
|
||||
})
|
||||
})
|
||||
|
||||
// Auto-expand all nodes when showing template details
|
||||
expandedTreeNodes.value = items.map(item => item.id)
|
||||
|
||||
return items
|
||||
// 直接返回模板的树状结构
|
||||
return selectedTemplate.value.treeStructure || []
|
||||
})
|
||||
|
||||
const navigateToCreateTemplate = () => {
|
||||
|
||||
@@ -1,22 +1,35 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface TemplateFile {
|
||||
// 保持旧的接口用于数据迁移
|
||||
interface OldTemplateFile {
|
||||
path: string
|
||||
content?: string // Content for text files that are created directly
|
||||
sourceFilePath?: string // Path to copy from for file copying
|
||||
}
|
||||
|
||||
interface TemplateDirectory {
|
||||
interface OldTemplateDirectory {
|
||||
path: string
|
||||
}
|
||||
|
||||
// 定义树状结构的节点
|
||||
export interface TreeNode {
|
||||
id: string
|
||||
name: string
|
||||
path: string // 完整路径
|
||||
type: 'directory' | 'file'
|
||||
children?: TreeNode[]
|
||||
// 文件特定属性
|
||||
content?: string
|
||||
sourceFilePath?: string
|
||||
}
|
||||
|
||||
export interface Template {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
files: TemplateFile[]
|
||||
directories: TemplateDirectory[]
|
||||
// 使用树状结构存储
|
||||
treeStructure: TreeNode[]
|
||||
}
|
||||
|
||||
export const useTemplateStore = defineStore('template', () => {
|
||||
@@ -95,6 +108,92 @@ export const useTemplateStore = defineStore('template', () => {
|
||||
}))
|
||||
}
|
||||
|
||||
// 将扁平结构转换为树状结构
|
||||
function convertFlatToTreeStructure(files: OldTemplateFile[], directories: OldTemplateDirectory[]): TreeNode[] {
|
||||
// 创建根节点
|
||||
const root: TreeNode = {
|
||||
id: 'root',
|
||||
name: 'root',
|
||||
path: '',
|
||||
type: 'directory',
|
||||
children: []
|
||||
}
|
||||
|
||||
// 先处理所有目录
|
||||
directories.forEach(dir => {
|
||||
const pathParts = dir.path.split('/').filter(part => part !== '') // Split path and remove empty parts
|
||||
let currentLevel = root.children || []
|
||||
|
||||
// Navigate/create the directory structure
|
||||
for (let i = 0; i < pathParts.length; i++) {
|
||||
const part = pathParts[i]
|
||||
const isLastPart = i === pathParts.length - 1
|
||||
|
||||
// Check if this part already exists in the current level
|
||||
let existingItem = currentLevel.find(item => item.name === part)
|
||||
|
||||
if (!existingItem) {
|
||||
// Create new directory item
|
||||
const newDir: TreeNode = {
|
||||
id: `dir-${part}-${Date.now()}-${Math.random()}`, // Unique ID
|
||||
name: part,
|
||||
path: dir.path.substring(0, dir.path.indexOf(part) + part.length), // Full path up to this part
|
||||
type: 'directory',
|
||||
children: []
|
||||
}
|
||||
currentLevel.push(newDir)
|
||||
existingItem = newDir
|
||||
}
|
||||
|
||||
// Move to the next level if not the last part
|
||||
if (!isLastPart && existingItem.children) {
|
||||
currentLevel = existingItem.children
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Process each file and add to the appropriate directory
|
||||
files.forEach(file => {
|
||||
const pathParts = file.path.split('/').filter(part => part !== '')
|
||||
const fileName = pathParts.pop() // Get the last part as filename
|
||||
|
||||
if (!fileName) return // Skip if no filename
|
||||
|
||||
let currentLevel = root.children || []
|
||||
|
||||
// Navigate to the appropriate directory for the file
|
||||
for (let i = 0; i < pathParts.length; i++) {
|
||||
const part = pathParts[i]
|
||||
const dirItem = currentLevel.find(item => item.name === part && item.type === 'directory')
|
||||
|
||||
if (dirItem && dirItem.children) {
|
||||
currentLevel = dirItem.children
|
||||
} else {
|
||||
// If the directory doesn't exist, create it
|
||||
const newDir: TreeNode = {
|
||||
id: `dir-${part}-${Date.now()}-${Math.random()}`,
|
||||
name: part,
|
||||
path: pathParts.slice(0, i+1).join('/'),
|
||||
type: 'directory',
|
||||
children: []
|
||||
}
|
||||
currentLevel.push(newDir)
|
||||
currentLevel = newDir.children || []
|
||||
}
|
||||
}
|
||||
|
||||
// Add the file to the current level
|
||||
currentLevel.push({
|
||||
id: `file-${fileName}-${Date.now()}-${Math.random()}`,
|
||||
name: fileName,
|
||||
path: file.path,
|
||||
type: 'file'
|
||||
})
|
||||
})
|
||||
|
||||
return root.children || []
|
||||
}
|
||||
|
||||
return {
|
||||
templates,
|
||||
loadTemplates,
|
||||
|
||||
Reference in new issue
Block a user