feat: 添加重复名称检查功能,防止在同一目录下创建同名文件或目录,优化用户体验
This commit is contained in:
1 parent
5ca31a5348
commit
ac5488a21d
3 files changed
+149
-189
No files matched your search
@@ -10,9 +10,7 @@
|
|||||||
item-title="name"
|
item-title="name"
|
||||||
item-key="id"
|
item-key="id"
|
||||||
item-children="children"
|
item-children="children"
|
||||||
activatable
|
|
||||||
open-on-click
|
open-on-click
|
||||||
:return-object="false"
|
|
||||||
:hoverable="true"
|
:hoverable="true"
|
||||||
>
|
>
|
||||||
<template #prepend="{ item }">
|
<template #prepend="{ item }">
|
||||||
@@ -144,6 +142,7 @@ interface TreeItem {
|
|||||||
name: string
|
name: string
|
||||||
type: 'directory' | 'file'
|
type: 'directory' | 'file'
|
||||||
children?: TreeItem[]
|
children?: TreeItem[]
|
||||||
|
open?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -163,10 +162,6 @@ const localExpanded = defineModel<string[]>('expanded', {
|
|||||||
default: () => []
|
default: () => []
|
||||||
})
|
})
|
||||||
|
|
||||||
const localShowContextMenuVar = defineModel<boolean>('showContextMenuVar', {
|
|
||||||
default: false
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
'addNewItemToCurrentDirectory',
|
'addNewItemToCurrentDirectory',
|
||||||
'editFile',
|
'editFile',
|
||||||
@@ -181,62 +176,4 @@ const emit = defineEmits([
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tree node styling */
|
|
||||||
:deep(.v-treeview-node__root),
|
|
||||||
:deep(.v-treeview-node__children .v-treeview-node__root) {
|
|
||||||
padding: 4px;
|
|
||||||
margin: 2px 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.v-treeview-node__root:hover),
|
|
||||||
:deep(.v-treeview-node__children .v-treeview-node__root:hover) {
|
|
||||||
background-color: rgba(0, 0, 0, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Indentation guides to show hierarchy */
|
|
||||||
:deep(.v-treeview-node__level) {
|
|
||||||
margin-right: 8px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Vertical connector lines */
|
|
||||||
:deep(.v-treeview-node) {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add connector lines */
|
|
||||||
:deep(.v-treeview-node::before) {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 12px;
|
|
||||||
width: 1px;
|
|
||||||
background: rgba(0, 0, 0, 0.12);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Horizontal connector lines for children */
|
|
||||||
:deep(.v-treeview-node__children) {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.v-treeview-node__children::before) {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: -16px;
|
|
||||||
width: 16px;
|
|
||||||
height: 1px;
|
|
||||||
background: rgba(0, 0, 0, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide connector for root level */
|
|
||||||
:deep(.v-treeview > .v-treeview-node > .v-treeview-node__root::before),
|
|
||||||
:deep(.v-treeview > .v-treeview-node > .v-treeview-node__children::before) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -537,6 +537,30 @@ const confirmAddDirectory = () => {
|
|||||||
fullPath = parentPath + newDirectoryPath.value
|
fullPath = parentPath + newDirectoryPath.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for duplicate names in the same parent directory
|
||||||
|
if (parentDirectoryForNewItems.value) {
|
||||||
|
// In the parent directory, check if a node with the same name already exists
|
||||||
|
const parentNode = findNodeInTreeByName(treeStructure.value, parentDirectoryForNewItems.value);
|
||||||
|
if (parentNode && parentNode.children) {
|
||||||
|
const duplicateExists = parentNode.children.some(child =>
|
||||||
|
child.name === newDirectoryPath.value
|
||||||
|
);
|
||||||
|
if (duplicateExists) {
|
||||||
|
showSnackbar('当前目录下已存在同名节点!', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check at root level for duplicates
|
||||||
|
const duplicateExists = treeStructure.value.some(node =>
|
||||||
|
node.name === newDirectoryPath.value
|
||||||
|
);
|
||||||
|
if (duplicateExists) {
|
||||||
|
showSnackbar('根目录下已存在同名节点!', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newId = `dir-${Date.now()}-${Math.random()}`
|
const newId = `dir-${Date.now()}-${Math.random()}`
|
||||||
|
|
||||||
// Check if we're editing an existing directory
|
// Check if we're editing an existing directory
|
||||||
@@ -663,7 +687,31 @@ const selectAndAddFile = async () => {
|
|||||||
};
|
};
|
||||||
treeStructure.value = updateNodeInTree(treeStructure.value, currentEditingItem.value.path, updatedNode)
|
treeStructure.value = updateNodeInTree(treeStructure.value, currentEditingItem.value.path, updatedNode)
|
||||||
} else {
|
} else {
|
||||||
// Check for duplicates only when creating a new file
|
// Check for duplicate names in the same parent directory
|
||||||
|
if (parentDirectoryForNewItems.value) {
|
||||||
|
// In the parent directory, check if a node with the same name already exists
|
||||||
|
const parentNode = findNodeInTreeByName(treeStructure.value, parentDirectoryForNewItems.value);
|
||||||
|
if (parentNode && parentNode.children) {
|
||||||
|
const duplicateExists = parentNode.children.some(child =>
|
||||||
|
child.name === newFilePath.value
|
||||||
|
);
|
||||||
|
if (duplicateExists) {
|
||||||
|
showSnackbar('当前目录下已存在同名节点!', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check at root level for duplicates
|
||||||
|
const duplicateExists = treeStructure.value.some(node =>
|
||||||
|
node.name === newFilePath.value
|
||||||
|
);
|
||||||
|
if (duplicateExists) {
|
||||||
|
showSnackbar('根目录下已存在同名节点!', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for duplicates using the full path (for backward compatibility)
|
||||||
const hasDuplicate = checkForDuplicateInTree(treeStructure.value, fullPath);
|
const hasDuplicate = checkForDuplicateInTree(treeStructure.value, fullPath);
|
||||||
if (hasDuplicate) {
|
if (hasDuplicate) {
|
||||||
showSnackbar('该文件路径已存在!')
|
showSnackbar('该文件路径已存在!')
|
||||||
@@ -681,24 +729,47 @@ const selectAndAddFile = async () => {
|
|||||||
const relativePath = foundFile.replace(selectedSourcePath.value, '').substring(1) // Remove leading slash
|
const relativePath = foundFile.replace(selectedSourcePath.value, '').substring(1) // Remove leading slash
|
||||||
const fullTargetPath = fullPath ? `${fullPath}/${relativePath}` : relativePath
|
const fullTargetPath = fullPath ? `${fullPath}/${relativePath}` : relativePath
|
||||||
|
|
||||||
// Check for duplicates
|
// Check for duplicates by full path
|
||||||
const hasDuplicateInTree = checkForDuplicateInTree(treeStructure.value, fullTargetPath);
|
const hasDuplicateInTree = checkForDuplicateInTree(treeStructure.value, fullTargetPath);
|
||||||
if (!hasDuplicateInTree) {
|
if (!hasDuplicateInTree) {
|
||||||
const newFileNode: TreeNode = {
|
const fileName = fullTargetPath.split('/').pop() || fullTargetPath;
|
||||||
id: `file-${Date.now()}-${Math.random()}`,
|
|
||||||
name: fullTargetPath.split('/').pop() || fullTargetPath,
|
// Additionally, check for duplicate names in the same parent directory
|
||||||
path: fullTargetPath,
|
let duplicateNameExists = false;
|
||||||
type: 'file',
|
|
||||||
sourceFilePath: foundFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to tree structure
|
|
||||||
if (parentDirectoryForNewItems.value) {
|
if (parentDirectoryForNewItems.value) {
|
||||||
// Add as child to the parent directory
|
// In the parent directory, check if a node with the same name already exists
|
||||||
treeStructure.value = addChildToNode(treeStructure.value, parentDirectoryForNewItems.value, newFileNode)
|
const parentNode = findNodeInTreeByName(treeStructure.value, parentDirectoryForNewItems.value);
|
||||||
|
if (parentNode && parentNode.children) {
|
||||||
|
duplicateNameExists = parentNode.children.some(child =>
|
||||||
|
child.name === fileName
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add as root node
|
// Check at root level for duplicates
|
||||||
treeStructure.value.push(newFileNode)
|
duplicateNameExists = treeStructure.value.some(node =>
|
||||||
|
node.name === fileName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicateNameExists) {
|
||||||
|
showSnackbar(`当前目录下已存在同名节点:${fileName}`, 'error');
|
||||||
|
} else {
|
||||||
|
const newFileNode: TreeNode = {
|
||||||
|
id: `file-${Date.now()}-${Math.random()}`,
|
||||||
|
name: fileName,
|
||||||
|
path: fullTargetPath,
|
||||||
|
type: 'file',
|
||||||
|
sourceFilePath: foundFile
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -722,21 +793,42 @@ const selectAndAddFile = async () => {
|
|||||||
|
|
||||||
// If we have a selected file, just add it
|
// If we have a selected file, just add it
|
||||||
if (selectedSourcePath.value) {
|
if (selectedSourcePath.value) {
|
||||||
const newFileNode: TreeNode = {
|
// Check for duplicate names in the same parent directory
|
||||||
id: newId,
|
let duplicateNameExists = false;
|
||||||
name: newFilePath.value,
|
|
||||||
path: fullPath,
|
|
||||||
type: 'file',
|
|
||||||
sourceFilePath: selectedSourcePath.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to tree structure
|
|
||||||
if (parentDirectoryForNewItems.value) {
|
if (parentDirectoryForNewItems.value) {
|
||||||
// Add as child to the parent directory
|
// In the parent directory, check if a node with the same name already exists
|
||||||
treeStructure.value = addChildToNode(treeStructure.value, parentDirectoryForNewItems.value, newFileNode)
|
const parentNode = findNodeInTreeByName(treeStructure.value, parentDirectoryForNewItems.value);
|
||||||
|
if (parentNode && parentNode.children) {
|
||||||
|
duplicateNameExists = parentNode.children.some(child =>
|
||||||
|
child.name === newFilePath.value
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Add as root node
|
// Check at root level for duplicates
|
||||||
treeStructure.value.push(newFileNode)
|
duplicateNameExists = treeStructure.value.some(node =>
|
||||||
|
node.name === newFilePath.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicateNameExists) {
|
||||||
|
showSnackbar('当前目录下已存在同名节点!', 'error');
|
||||||
|
} else {
|
||||||
|
const newFileNode: TreeNode = {
|
||||||
|
id: newId,
|
||||||
|
name: newFilePath.value,
|
||||||
|
path: fullPath,
|
||||||
|
type: 'file',
|
||||||
|
sourceFilePath: selectedSourcePath.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Reset selection state
|
||||||
@@ -875,6 +967,34 @@ const findNodeInTree = (nodes: TreeNode[], nodeId: string): TreeNode | null => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通过节点名称查找节点的辅助函数
|
||||||
|
const findNodeInTreeByName = (nodes: TreeNode[], nodeName: string): TreeNode | null => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.name === nodeName) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
if (node.children) {
|
||||||
|
const found = findNodeInTreeByName(node.children, nodeName);
|
||||||
|
if (found) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在指定节点中通过名称查找子节点的辅助函数
|
||||||
|
const findChildNodeByName = (parentNode: TreeNode, childName: string): TreeNode | null => {
|
||||||
|
if (parentNode.children) {
|
||||||
|
for (const child of parentNode.children) {
|
||||||
|
if (child.name === childName) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Show dialog to edit directory
|
// Show dialog to edit directory
|
||||||
const showEditDirectoryDialog = (directory: TreeNode) => {
|
const showEditDirectoryDialog = (directory: TreeNode) => {
|
||||||
// Set up for editing an existing directory
|
// Set up for editing an existing directory
|
||||||
|
|||||||
@@ -1,17 +1,6 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
// 保持旧的接口用于数据迁移
|
|
||||||
interface OldTemplateFile {
|
|
||||||
path: string
|
|
||||||
content?: string // Content for text files that are created directly
|
|
||||||
sourceFilePath?: string // Path to copy from for file copying
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OldTemplateDirectory {
|
|
||||||
path: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义树状结构的节点
|
// 定义树状结构的节点
|
||||||
export interface TreeNode {
|
export interface TreeNode {
|
||||||
id: string
|
id: string
|
||||||
@@ -108,92 +97,6 @@ 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 {
|
return {
|
||||||
templates,
|
templates,
|
||||||
loadTemplates,
|
loadTemplates,
|
||||||
|
|||||||
Reference in new issue
Block a user