| |
| |
| |
| |
| |
| import { ref, computed, reactive, type Ref, type ComputedRef } from 'vue'; |
| import type { |
| Folder, |
| FolderTreeNode, |
| FolderOperations, |
| CreateFolderData, |
| UpdateFolderData, |
| BreadcrumbItem, |
| } from './types'; |
|
|
| export interface UseFolderManagerOptions { |
| |
| operations: FolderOperations; |
| |
| |
| rootFolderName?: string; |
| |
| |
| autoLoad?: boolean; |
| } |
|
|
| export interface UseFolderManagerReturn { |
| |
| folderTree: Ref<FolderTreeNode[]>; |
| currentFolderId: Ref<string | null>; |
| currentFolders: Ref<Folder[]>; |
| breadcrumbPath: Ref<FolderTreeNode[]>; |
| expandedFolderIds: Ref<string[]>; |
| loading: Ref<boolean>; |
| treeLoading: Ref<boolean>; |
| |
| |
| currentFolderName: ComputedRef<string>; |
| breadcrumbItems: ComputedRef<BreadcrumbItem[]>; |
| |
| |
| loadFolderTree: () => Promise<void>; |
| navigateToFolder: (folderId: string | null) => Promise<void>; |
| refreshCurrentFolder: () => Promise<void>; |
| |
| createFolder: (data: CreateFolderData) => Promise<Folder>; |
| updateFolder: (data: UpdateFolderData) => Promise<void>; |
| deleteFolder: (folderId: string) => Promise<void>; |
| moveFolder: (folderId: string, targetParentId: string | null) => Promise<void>; |
| |
| toggleFolderExpansion: (folderId: string) => void; |
| setFolderExpansion: (folderId: string, expanded: boolean) => void; |
| |
| findFolderInTree: (folderId: string) => FolderTreeNode | null; |
| findPathToFolder: (folderId: string) => FolderTreeNode[]; |
| |
| filterTreeBySearch: (query: string) => FolderTreeNode[]; |
| } |
|
|
| |
| |
| |
| export function useFolderManager(options: UseFolderManagerOptions): UseFolderManagerReturn { |
| const { operations, rootFolderName = '根目录', autoLoad = false } = options; |
| |
| |
| const folderTree = ref<FolderTreeNode[]>([]); |
| const currentFolderId = ref<string | null>(null); |
| const currentFolders = ref<Folder[]>([]); |
| const breadcrumbPath = ref<FolderTreeNode[]>([]); |
| const expandedFolderIds = ref<string[]>([]); |
| const loading = ref(false); |
| const treeLoading = ref(false); |
| |
| |
| const currentFolderName = computed(() => { |
| if (breadcrumbPath.value.length === 0) { |
| return rootFolderName; |
| } |
| return breadcrumbPath.value[breadcrumbPath.value.length - 1]?.name || rootFolderName; |
| }); |
| |
| const breadcrumbItems = computed((): BreadcrumbItem[] => { |
| const items: BreadcrumbItem[] = [ |
| { |
| title: rootFolderName, |
| folderId: null, |
| disabled: currentFolderId.value === null, |
| isRoot: true, |
| }, |
| ]; |
| |
| breadcrumbPath.value.forEach((folder, index) => { |
| items.push({ |
| title: folder.name, |
| folderId: folder.folder_id, |
| disabled: index === breadcrumbPath.value.length - 1, |
| isRoot: false, |
| }); |
| }); |
| |
| return items; |
| }); |
| |
| |
| const findPathToFolderInternal = ( |
| nodes: FolderTreeNode[], |
| targetId: string, |
| path: FolderTreeNode[] = [] |
| ): FolderTreeNode[] | null => { |
| for (const node of nodes) { |
| if (node.folder_id === targetId) { |
| return [...path, node]; |
| } |
| if (node.children && node.children.length > 0) { |
| const result = findPathToFolderInternal(node.children, targetId, [...path, node]); |
| if (result) return result; |
| } |
| } |
| return null; |
| }; |
| |
| const updateBreadcrumb = (folderId: string | null): void => { |
| if (folderId === null) { |
| breadcrumbPath.value = []; |
| return; |
| } |
| |
| const path = findPathToFolderInternal(folderTree.value, folderId); |
| breadcrumbPath.value = path || []; |
| }; |
| |
| |
| const loadFolderTree = async (): Promise<void> => { |
| treeLoading.value = true; |
| try { |
| folderTree.value = await operations.loadFolderTree(); |
| } finally { |
| treeLoading.value = false; |
| } |
| }; |
| |
| const navigateToFolder = async (folderId: string | null): Promise<void> => { |
| loading.value = true; |
| try { |
| currentFolderId.value = folderId; |
| currentFolders.value = await operations.loadSubFolders(folderId); |
| updateBreadcrumb(folderId); |
| } finally { |
| loading.value = false; |
| } |
| }; |
| |
| const refreshCurrentFolder = async (): Promise<void> => { |
| await navigateToFolder(currentFolderId.value); |
| }; |
| |
| const createFolder = async (data: CreateFolderData): Promise<Folder> => { |
| const folder = await operations.createFolder({ |
| ...data, |
| parent_id: data.parent_id ?? currentFolderId.value, |
| }); |
| |
| await Promise.all([refreshCurrentFolder(), loadFolderTree()]); |
| |
| return folder; |
| }; |
| |
| const updateFolder = async (data: UpdateFolderData): Promise<void> => { |
| await operations.updateFolder(data); |
| await Promise.all([refreshCurrentFolder(), loadFolderTree()]); |
| }; |
| |
| const deleteFolder = async (folderId: string): Promise<void> => { |
| await operations.deleteFolder(folderId); |
| await Promise.all([refreshCurrentFolder(), loadFolderTree()]); |
| }; |
| |
| const moveFolder = async (folderId: string, targetParentId: string | null): Promise<void> => { |
| if (operations.moveFolder) { |
| await operations.moveFolder(folderId, targetParentId); |
| } else { |
| |
| await operations.updateFolder({ |
| folder_id: folderId, |
| parent_id: targetParentId, |
| }); |
| } |
| await Promise.all([refreshCurrentFolder(), loadFolderTree()]); |
| }; |
| |
| const toggleFolderExpansion = (folderId: string): void => { |
| const index = expandedFolderIds.value.indexOf(folderId); |
| if (index === -1) { |
| expandedFolderIds.value.push(folderId); |
| } else { |
| expandedFolderIds.value.splice(index, 1); |
| } |
| }; |
| |
| const setFolderExpansion = (folderId: string, expanded: boolean): void => { |
| const index = expandedFolderIds.value.indexOf(folderId); |
| if (expanded && index === -1) { |
| expandedFolderIds.value.push(folderId); |
| } else if (!expanded && index !== -1) { |
| expandedFolderIds.value.splice(index, 1); |
| } |
| }; |
| |
| const findFolderInTree = (folderId: string): FolderTreeNode | null => { |
| const findNode = (nodes: FolderTreeNode[]): FolderTreeNode | null => { |
| for (const node of nodes) { |
| if (node.folder_id === folderId) { |
| return node; |
| } |
| if (node.children && node.children.length > 0) { |
| const found = findNode(node.children); |
| if (found) return found; |
| } |
| } |
| return null; |
| }; |
| return findNode(folderTree.value); |
| }; |
| |
| const findPathToFolder = (folderId: string): FolderTreeNode[] => { |
| return findPathToFolderInternal(folderTree.value, folderId) || []; |
| }; |
| |
| const filterTreeBySearch = (query: string): FolderTreeNode[] => { |
| if (!query) return folderTree.value; |
| |
| const lowerQuery = query.toLowerCase(); |
| |
| const filterNodes = (nodes: FolderTreeNode[]): FolderTreeNode[] => { |
| return nodes |
| .filter((node) => { |
| const matches = node.name.toLowerCase().includes(lowerQuery); |
| const childMatches = filterNodes(node.children || []); |
| return matches || childMatches.length > 0; |
| }) |
| .map((node) => ({ |
| ...node, |
| children: filterNodes(node.children || []), |
| })); |
| }; |
| |
| return filterNodes(folderTree.value); |
| }; |
| |
| |
| if (autoLoad) { |
| loadFolderTree(); |
| navigateToFolder(null); |
| } |
| |
| return { |
| |
| folderTree, |
| currentFolderId, |
| currentFolders, |
| breadcrumbPath, |
| expandedFolderIds, |
| loading, |
| treeLoading, |
| |
| |
| currentFolderName, |
| breadcrumbItems, |
| |
| |
| loadFolderTree, |
| navigateToFolder, |
| refreshCurrentFolder, |
| createFolder, |
| updateFolder, |
| deleteFolder, |
| moveFolder, |
| toggleFolderExpansion, |
| setFolderExpansion, |
| findFolderInTree, |
| findPathToFolder, |
| filterTreeBySearch, |
| }; |
| } |
|
|
| |
| |
| |
| |
| export function collectFolderAndChildrenIds( |
| folderTree: FolderTreeNode[], |
| folderId: string |
| ): string[] { |
| const ids: string[] = [folderId]; |
| |
| const collectChildIds = (nodes: FolderTreeNode[]): boolean => { |
| for (const node of nodes) { |
| if (node.folder_id === folderId) { |
| const collectAllChildren = (children: FolderTreeNode[]) => { |
| for (const child of children) { |
| ids.push(child.folder_id); |
| if (child.children) { |
| collectAllChildren(child.children); |
| } |
| } |
| }; |
| if (node.children) { |
| collectAllChildren(node.children); |
| } |
| return true; |
| } |
| if (node.children && collectChildIds(node.children)) { |
| return true; |
| } |
| } |
| return false; |
| }; |
| |
| collectChildIds(folderTree); |
| return ids; |
| } |
|
|
| export default useFolderManager; |
|
|