From aec7fcabc96aa00bcccbadd8fd9723b9d8d48bb4 Mon Sep 17 00:00:00 2001 From: Zhoujiahui <539943419@qq.com> Date: Sun, 29 Sep 2024 11:50:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B9=9F=E4=B8=8D=E7=9F=A5=E9=81=93?= =?UTF-8?q?=E6=94=B9=E4=BA=86=E5=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/class/VirtualDirectory.ts | 390 +++++++++++--------- src/plugins/miaoDirectory/miaoDirectory.vue | 131 ++++--- 2 files changed, 295 insertions(+), 226 deletions(-) diff --git a/src/class/VirtualDirectory.ts b/src/class/VirtualDirectory.ts index 0fb1678..5aa21f1 100644 --- a/src/class/VirtualDirectory.ts +++ b/src/class/VirtualDirectory.ts @@ -1,203 +1,235 @@ -import config from "@/config"; -import generateId from "@/hooks/generateId"; -import useMiaoFetchApi from "@/hooks/useMiaoFetchApi"; -import type { stats, file, directory } from "@/types/type.ts"; +import config from '@/config' +import generateId from '@/hooks/generateId' +import useMiaoFetchApi from '@/hooks/useMiaoFetchApi' +import type { stats, file, directory } from '@/types/type.ts' +import difference from 'lodash/difference' -const miaoFetchApi = useMiaoFetchApi(); +const miaoFetchApi = useMiaoFetchApi() -const vItemMap = new Map(); +// const vDirectoryMap = new Map() +// const vFileMap = new Map() export interface Tree { - name: string; - type: "directory"; - path: string; - stats: stats; - files?: { - name: string; - type: "file"; - size: number; - stats: stats; - url: string; - }[]; - directories?: Tree[]; + name: string + type: 'directory' + path: string + stats: stats + files?: { + name: string + type: 'file' + size: number + stats: stats + url: string + }[] + directories?: Tree[] } export class VirtualFile { - constructor(info: file, parent: VirtualDirectory) { - this.name = info.name; - this.size = info.size; - this.stats = info.stats; - this.parent = parent; - this.type = "file"; - this.id = generateId(); - vItemMap.set(this.path, this); - } - name: string; - type: "file"; - size: number; - stats: stats; - parent: VirtualDirectory; - id: number; - storage: { [key: string]: any } = {}; - get path() { - return `${this.parent.path}${this.name}`; - } + constructor(info: file, parent: VirtualDirectory) { + this.name = info.name + this.size = info.size + this.stats = info.stats + this.parent = parent + this.type = 'file' + this.id = generateId() + // if (vFileMap.get(this.path) !== undefined) { + // throw `${this.path} is already exist` + // } + // vFileMap.set(this.path, this) + } + name: string + type: 'file' + size: number + stats: stats + parent: VirtualDirectory + id: number + storage: { [key: string]: any } = {} + get path() { + return `${this.parent.path}${this.name}` + } - get url() { - return `${config.api.get}${this.parent.path}${this.name}`; - } + get url() { + return `${config.api.get}${this.parent.path}${this.name}` + } + + // destroy() { + // vFileMap.delete(this.path) + // } } class VirtualDirectory { - /** - * 除非为根节点,否则必须设置parent - */ - constructor(info: directory, parent?: VirtualDirectory) { - this.name = info.name; - this.stats = info.stats; - this.parent = parent ?? undefined; - this.id = generateId(); - vItemMap.set(this.path, this); - } - // 文件夹名 - name: string; - // 子文件 - files?: VirtualFile[]; - // 子目录 - directories?: VirtualDirectory[]; - // 没啥意义,标注一下就是了 - type: "directory" = "directory"; - // 文件夹创建时间等杂项属性 - stats: stats; - // 父文件夹 - parent?: VirtualDirectory; + /** + * 除非为根节点,否则必须设置parent + */ + constructor(info: directory, parent?: VirtualDirectory) { + this.name = info.name + this.stats = info.stats + this.parent = parent ?? undefined + this.id = generateId() + // if (vDirectoryMap.get(this.path) !== undefined) { + // throw `${this.path} is already exist` + // } + // vDirectoryMap.set(this.path, this) + } + // 文件夹名 + name: string + // 子文件 + files: VirtualFile[] = [] + // 子目录 + directories: VirtualDirectory[] = [] + // 没啥意义,标注一下就是了 + type: 'directory' = 'directory' + // 文件夹创建时间等杂项属性 + stats: stats + // 父文件夹 + parent?: VirtualDirectory - _isUpdated: boolean = false; + _isUpdated: boolean = false - id: number; + id: number - // 用来存储一些标记信息啥的 - storage: { [key: string]: any } = {}; + // 用来存储一些标记信息啥的 + storage: { [key: string]: any } = {} - /** - * 获取相对于根目录的路径的层数 - * 用于目录下文件定位 - * @returns {string} - */ - get layer(): number { - return this.parent ? this.parent.layer + 1 : 0; - } - /** - * 相对于根目录的路径, 如: "/", "/123/321/" - * 用于目录下文件定位 - * @returns {string} - */ - get path(): string { - if (!this.parent) { - return "/"; - } - return `${this.parent.path}${this.name}/`; - } + /** + * 获取相对于根目录的路径的层数 + * 用于目录下文件定位 + * @returns {string} + */ + get layer(): number { + return this.parent ? this.parent.layer + 1 : 0 + } + /** + * 相对于根目录的路径, 如: "/", "/123/321/" + * 用于目录下文件定位 + * @returns {string} + */ + get path(): string { + if (!this.parent) { + return '/' + } + return `${this.parent.path}${this.name}/` + } - /** - * 获取从根目录开始的按顺序的文件夹 - * @returns {VirtualDirectory[]} - */ - get getParents(): VirtualDirectory[] { - if (!this.parent) { - return [this]; - } - return [...this.parent.getParents, this]; - } + /** + * 获取从根目录开始的按顺序的文件夹 + * @returns {VirtualDirectory[]} + */ + get getParents(): VirtualDirectory[] { + if (!this.parent) { + return [this] + } + return [...this.parent.getParents, this] + } - /** - * 获取当前文件夹是否已经请求过至少一次数据 - */ - get isUpdated() { - return this._isUpdated; - } + /** + * 获取当前文件夹是否已经请求过至少一次数据 + */ + get isUpdated() { + return this._isUpdated + } - /** - * 获取 layer 级父文件夹, 如getParent(1)就返回父级 - * @param {number} layer 需要跳转的层数 - * @returns {VirtualDirectory} - */ - getParent(layer: number): VirtualDirectory { - if (layer <= 0 || !this.parent) { - return this; - } - return this.parent.getParent(layer - 1); - } + /** + * 获取 layer 级父文件夹, 如getParent(1)就返回父级 + * @param {number} layer 需要跳转的层数 + * @returns {VirtualDirectory} + */ + getParent(layer: number): VirtualDirectory { + if (layer <= 0 || !this.parent) { + return this + } + return this.parent.getParent(layer - 1) + } - get tree(): Tree { - return { - name: this.name, - type: this.type, - path: this.path, - stats: this.stats, - files: - this.files?.map(v => ({ - name: v.name, - type: v.type, - size: v.size, - stats: v.stats, - url: v.url - })) ?? [], - directories: this.directories?.map(v => v.tree) ?? [] - }; - } + get tree(): Tree { + return { + name: this.name, + type: this.type, + path: this.path, + stats: this.stats, + files: + this.files?.map((v) => ({ + name: v.name, + type: v.type, + size: v.size, + stats: v.stats, + url: v.url + })) ?? [], + directories: this.directories?.map((v) => v.tree) ?? [] + } + } - // todo: 重构这个函数 - updateContent(content: (file | directory)[]) { - this._isUpdated = true; + // 时间复杂度大爆炸 + updateContent(content: (file | directory)[]) { + this._isUpdated = true - this.files = this.files ?? []; - this.directories = this.directories ?? []; - const fileNames: string[] = this.files.map(v => v.name); - const dirNames: string[] = this.directories.map(v => v.name); - const itemNames: string[] = content.map(v => v.name); - // 删去远程端已不存在的文件与文件夹 - const _delete = (target: (VirtualFile | VirtualDirectory)[]) => { - let index = 0; - while (index < target.length) { - if (!itemNames.includes(target[index].name)) { - target.splice(index, 1); - } else { - index += 1; - } - } - }; - _delete(this.files); - _delete(this.directories); - // 加入新的文件与文件夹 - for (let item of content) { - if (item.type === "file") { - if (!fileNames.includes(item.name)) { - this.files.push(new VirtualFile(item, this)); - } - } else if (item.type === "directory") { - if (!dirNames.includes(item.name)) { - this.directories.push(new VirtualDirectory(item, this)); - } - } - } - } + const itemNames: string[] = content.map((v) => v.name) + // 删去远程端已不存在的文件与文件夹 + const _delete = (target: (VirtualFile | VirtualDirectory)[]) => { + let index = 0 + while (index < target.length) { + if (!itemNames.includes(target[index].name)) { + target.splice(index, 1) + } else { + index += 1 + } + } + } + const _addFile = (item: file) => { + const nvf = + // vFileMap.get(`${this.path}${item.name}`) ?? + new VirtualFile(item, this) + this.files.push(nvf) + } + const _addDirectory = (item: directory) => { + const nvd = + // vDirectoryMap.get(`${this.path}${item.name}/`) ?? + new VirtualDirectory(item, this) + this.directories.push(nvd) + } + const newFileNames: string[] = difference( + content.filter((v) => v.type === 'file').map((v) => v.name), + this.files.map((v) => v.name) + ) + const newDirectoryNames: string[] = difference( + content.filter((v) => v.type === 'directory').map((v) => v.name), + this.files.map((v) => v.name) + ) + _delete(this.files) + _delete(this.directories) + // 加入新的文件与文件夹 + for (let item of content.filter((v) => newFileNames.includes(v.name))) { + _addFile(item as file) + } + for (let item of content.filter((v) => + newDirectoryNames.includes(v.name) + )) { + _addDirectory(item as directory) + } + } - hasChild(item: VirtualDirectory | VirtualFile): boolean { - if (item.type === "directory") { - return this.directories?.includes(item) ?? false; - } else { - return this.files?.includes(item) ?? false; - } - } + hasChild(item: VirtualDirectory | VirtualFile): boolean { + if (item.type === 'directory') { + return this.directories?.includes(item) ?? false + } else { + return this.files?.includes(item) ?? false + } + } - /** - * 调用后重新向远端拉取文件夹信息并进行更新 - * @returns {void} - */ - async update(): Promise { - const data: (file | directory)[] = await miaoFetchApi.get(this); - this.updateContent(data); - } + // destroy() { + // for(const item of [...this.files, ...this.directories]){ + // item.destroy() + // } + // vDirectoryMap.delete(this.path) + // } + + /** + * 调用后重新向远端拉取文件夹信息并进行更新 + * @returns {void} + */ + async update(): Promise { + const data: (file | directory)[] = await miaoFetchApi.get(this) + this.updateContent(data) + } } -export default VirtualDirectory; +export default VirtualDirectory diff --git a/src/plugins/miaoDirectory/miaoDirectory.vue b/src/plugins/miaoDirectory/miaoDirectory.vue index 1abe0d9..20ebab3 100644 --- a/src/plugins/miaoDirectory/miaoDirectory.vue +++ b/src/plugins/miaoDirectory/miaoDirectory.vue @@ -1,28 +1,49 @@ @@ -37,13 +58,13 @@ import useVirtualPages from '@/hooks/useVirtualPages' import { filePicker, renderIcon } from '@/hooks/miaoTools' import uniq from 'lodash/uniq' -// 导入子组件 import miaoDirectoryTop from './components/miaoDirectoryTop.vue' import MiaoDirectoryItems from './components/miaoDirectoryItemsContainer.vue' import MiaoDirectoryBottom from './components/miaoDirectoryBottom.vue' import MiaoMask from '@/components/miaoMask.vue' import MiaoPopupInput from '@/components/miaoPopupInput.vue' import MiaoDropHandler from '@/components/miaoDropHandler.vue' +import miaoAlertTipProvider from '@/components/miaoAlertTipProvider.vue' import { CloseOutline, SearchOutline, @@ -80,6 +101,7 @@ const searchText = ref('') const showDirs = ref(true) const showFiles = ref(true) const miaoDirectoryItemRef = ref() +const miaoAlertTip = ref>() class SelectedItem { constructor() { @@ -182,10 +204,10 @@ const topBarMenuOption = computed(() => { }, searchText.value !== '' ? { - label: '重置搜索', - key: 'searchReset', - icon: renderIcon(SearchOutline) - } + label: '重置搜索', + key: 'searchReset', + icon: renderIcon(SearchOutline) + } : undefined, // selectedItem.value.length !== 0 // ? { @@ -227,13 +249,14 @@ const topBarMenuOption = computed(() => { label: `反选`, key: 'multiSelect:inversion' }, - selectedItem.value.length === 2 ? - { - label: `区间内全选`, - key: 'multiSelect:range' - } : undefined + selectedItem.value.length === 2 + ? { + label: `区间内全选`, + key: 'multiSelect:range' + } + : undefined ].filter((v) => v !== undefined), - icon: renderIcon(CheckmarkDoneOutline), + icon: renderIcon(CheckmarkDoneOutline) }, { label: '插件', @@ -259,11 +282,9 @@ const topBarMenuOption = computed(() => { // 设置显示的dir const setCurrentDirectory = async (virtualDirectory: VirtualDirectory) => { + try { // 若没有缓存数据则同步刷新,有缓存数据则异步刷新 - if ( - virtualDirectory.files === undefined || - virtualDirectory.directories === undefined - ) { + if (!virtualDirectory.isUpdated) { await virtualDirectory.update() } else { virtualDirectory.update() @@ -279,6 +300,12 @@ const setCurrentDirectory = async (virtualDirectory: VirtualDirectory) => { }) } currentDirectories.value[0] = virtualDirectory + } catch(e) { + miaoAlertTip.value?.alertTip(`获取文件夹内容出错`, { + type: 'error', + timeout: 3000 + }) + } } const reload = () => { @@ -315,10 +342,12 @@ const handleTopBarMenuSelect = (key: string) => { // selectedItem.clear() } else if (key === 'showDirs') { showDirs.value = !showDirs.value - showDirs.value === false && selectedItem.clear(v => v.type !== 'directory') + showDirs.value === false && + selectedItem.clear((v) => v.type !== 'directory') } else if (key === 'showFiles') { showFiles.value = !showFiles.value - showFiles.value === false && selectedItem.clear(v => v.type !== 'file') + showFiles.value === false && + selectedItem.clear((v) => v.type !== 'file') } else if (key.startsWith('plugin:')) { const pluginName = key.split(':')[1] pluginCenter.usePlugin(pluginName, [currentDirectory.value], []) @@ -326,20 +355,29 @@ const handleTopBarMenuSelect = (key: string) => { const opName = key.split(':')[1] switch (opName) { case 'all': - selectedItem.pushArray([...showData_directory.value, ...showData_files.value]) - break; + selectedItem.pushArray([ + ...showData_directory.value, + ...showData_files.value + ]) + break case 'reset': selectedItem.clear() break case 'inversion': const sArr = selectedItem.value - const arr = [...showData_directory.value, ...showData_files.value].filter(i => !sArr.includes(i)) + const arr = [ + ...showData_directory.value, + ...showData_files.value + ].filter((i) => !sArr.includes(i)) selectedItem.clear() selectedItem.pushArray(arr) break - case "range": + case 'range': const _sArr = selectedItem.value - const _arr = [...showData_directory.value, ...showData_files.value] + const _arr = [ + ...showData_directory.value, + ...showData_files.value + ] const _nArr = [] let _counter = 0 for (const i of _arr) { @@ -357,7 +395,6 @@ const handleTopBarMenuSelect = (key: string) => { selectedItem.pushArray(_nArr) break } - } } @@ -377,7 +414,7 @@ const handleItemClick = (item: VirtualDirectory | VirtualFile) => { } else { if (item.type === 'file') { const useablePlugins = pluginCenter.getUsablePlugin([], [item]) - if(useablePlugins.length > 0){ + if (useablePlugins.length > 0) { pluginCenter.usePlugin(useablePlugins[0].key, [], [item]) } else { openUrl(`${baseUrl}${api.get}${item.path}`) @@ -396,7 +433,7 @@ const handleItemDownload = (item: VirtualFile) => { const handleItemDelete = async (_item: VirtualDirectory | VirtualFile) => { for (let item of uniq([...selectedItem.value, _item])) { - ; (async () => { + ;(async () => { const { response } = miaoFetchApi.delete(item, { retry: 5 }) @@ -485,7 +522,7 @@ const handleDropVDirectory = async (vDirs: VirtualDirectory[]) => { } } for (let dir of vDirs) { - ; (async () => { + ;(async () => { const _from = dir.parent const { response } = miaoFetchApi.cut(dir, currentDirectory.value) const id = (await response).eventId @@ -504,7 +541,7 @@ const handleDropVirtualFiles = (files: VirtualFile[]) => { return } for (let file of files) { - ; (async () => { + ;(async () => { const _from = file.parent const { response } = miaoFetchApi.cut(file, currentDirectory.value) const id = (await response).eventId