重构VirtualPage,所有miaoDirectory以插件形式使用

This commit is contained in:
539943419 committed 2024-08-14 01:43:24 +08:00
1 parent 8830e1aa3c
commit 0c501c800b
6 files changed
+397 -284

No files matched your search

+3 -2
View File
@@ -1,7 +1,8 @@
todo: 标签页,miaoDirectory切换动画 [finish]
todo: 重构virtualPage,所有page以插件形式使用 []
todo: miaoDirectory插件化,为后续插件功能开发做准备 []
todo: 重构virtualPage,所有page以插件形式使用 [finish]
todo: miaoDirectory插件化,为后续插件功能开发做准备 [finish]
todo: miaoPopupInput, 弹出一个输入框
todo: miaoDirectory的新建文件/文件夹功能
todo: miaoDirectory的重命名功能
todo: miaoDirectory的多选功能
todo: VirtualDirectory更新时更新文件信息
+10 -10
View File
@@ -5,7 +5,7 @@ import { NIcon, NScrollbar, NDropdown, NQrCode } from 'naive-ui';
import { EyeOffOutline, EyeOutline, CloseOutline, CopyOutline, EllipsisVertical, QrCodeOutline } from '@vicons/ionicons5'
import miaoDirectory from './views/miaoDirectory.vue';
import VirtualPage from './class/VirtualPage';
import VirtualDirectory from './class/VirtualDirectory';
import VirtualDirectory, { VirtualFile } from './class/VirtualDirectory';
import MiaoMask from './components/miaoMask.vue';
import config from './config';
import useViretualPages from './hooks/useVirtualPages';
@@ -31,16 +31,16 @@ const rootDirectory = reactive(new VirtualDirectory({
}
}))
const createView = (initDirectory: VirtualDirectory, index?: number) => {
// component就是一个vue组件,类似于miaoDirectory
const createView = (component: any, currentObjects: (VirtualDirectory | VirtualFile)[], index?: number) => {
index ?? (index = views.length)
console.log(initDirectory)
views.splice(index, 0, reactive<VirtualPage>(new VirtualPage(initDirectory)))
views.splice(index, 0, reactive<VirtualPage>(new VirtualPage(component, [...currentObjects])))
}
const deleteView = (index: number) => {
views.splice(index, 1)
if (views.length === 0) {
createView(rootDirectory)
createView(miaoDirectory, [rootDirectory])
}
}
@@ -78,7 +78,6 @@ const openMenuOption = [
]
const handleMenuSelect = (key: string) => {
console.log(key)
if (key === 'share') {
showModal.value = true
modalData.value = {
@@ -93,7 +92,7 @@ const handleMenuSelect = (key: string) => {
}
onMounted(() => {
createView(rootDirectory)
createView(miaoDirectory, [rootDirectory])
})
</script>
@@ -111,7 +110,8 @@ onMounted(() => {
<EyeOutline v-show="view.visible" class="icon-inner" />
<EyeOffOutline v-show="!view.visible" class="icon-inner" />
</n-icon>
<n-icon class="tag-control-icon icon" @click="createView(view.currentDirectory, index + 1)">
<n-icon class="tag-control-icon icon"
@click="createView(view.component, view.currentObjects, index + 1)">
<CopyOutline />
</n-icon>
<n-icon class="tag-control-icon icon" @click="deleteView(index)">
@@ -135,8 +135,8 @@ onMounted(() => {
<div class="view-container">
<transition-group name="page">
<div class="view-container-item" v-for="(view, index) of views" v-show="view.visible" :key="view.uid">
<miao-directory v-model:current-directory="view.currentDirectory" :color="view.color" :index="index"
@exit="deleteView(index)"></miao-directory>
<component :is="view.component" v-model:current-objects="view.currentObjects" :id="view.uid"
:color="view.color" @exit="deleteView(index)"></component>
</div>
</transition-group>
</div>
+24 -14
View File
@@ -1,35 +1,50 @@
import VirtualDirectory from "./VirtualDirectory";
import { shallowRef } from "vue";
import VirtualDirectory, { VirtualFile } from "./VirtualDirectory";
import generateId from "@/hooks/generateId";
const getRandomWebSafeColor = (() => {
const webSafeColors = ["#009900", "#999900", "#009999", "#990099", "#0000ee"];
let counter = 0
let counter = 0;
return () => {
const color = webSafeColors[counter % webSafeColors.length]
counter += 1
const color = webSafeColors[counter % webSafeColors.length];
counter += 1;
return color;
};
})();
export default class VirtualPage {
constructor(initDirectory: VirtualDirectory) {
constructor(component: any, initObjects: (VirtualDirectory | VirtualFile)[]) {
this.component = shallowRef(component);
this.currentObjects = initObjects;
this.visible = true;
this.uid = generateId();
this.currentDirectory = initDirectory;
this.refreshKey = Math.random();
this.color = getRandomWebSafeColor();
}
component: any;
// 是否可见
visible: boolean;
currentDirectory: VirtualDirectory;
// 当前正在使用的文件或是文件夹
currentObjects: (VirtualDirectory | VirtualFile)[];
// 区分每一个页面的唯一id
uid: number;
// 当refreshKey改变时需要刷新页面,目前还没接入刷新
refreshKey: number;
// 为了区分,每个虚拟页面都会分配一个color,这个color会体现在tab上
color: string;
// 用于展示的title
_title?: string;
get title() {
return this.currentDirectory.name;
if (this._title) {
return this._title;
}
return this.currentObjects[0].name;
}
setTitle(title: string) {
this._title = title;
}
// 切换显示状态
@@ -37,11 +52,6 @@ export default class VirtualPage {
this.visible = !this.visible;
}
// 设置(同步)正在展示的文件夹
setCurrrentDirectory(currDir: VirtualDirectory) {
this.currentDirectory = currDir;
}
// 刷新,不过好像没有用得到的时候吧
// 事实上也不应该用得到,因为更新应该是响应式的
refresh() {
@@ -34,6 +34,7 @@ const dataBus = useDataBus()
const emit = defineEmits<{
download: []
delete: []
dragStart: []
}>()
const props = defineProps<{
+43
View File
@@ -0,0 +1,43 @@
<template>
<miao-mask v-model:show="showMask">
<n-card title="">
<n-input v-model:value="inputValue" type="text" placeholder="基本的 Input" />
<n-button @click="_resolve && _resolve(inputValue)">确认</n-button>
</n-card>
</miao-mask>
</template>
<script lang="ts" setup>
import { reactive, ref } from "vue";
import { NCard, NInput, NButton } from "naive-ui";
import miaoMask from "./miaoMask.vue";
const showMask = ref(true);
const inputValue = ref<string>("");
const _resolve = ref<(text: string) => void>()
type popupConfig = {
title: string
placeholder: string
options: {
label
}
}
const _config = reactive<popupConfig>({
title: '',
placeholder: ''
})
const popup = () => {
showMask.value = true
return new Promise((resolve: (text: string) => void) => { _resolve.value = resolve }).then((text: string) => {
showMask.value = false
return text;
});
};
defineExpose({ popup });
</script>
<style scoped lang="scss"></style>
+316 -258
View File
@@ -1,186 +1,240 @@
<template>
<div class="miao-directory-item-container" ref="rootDomRef">
<div class="container-top" :style="{ backgroundColor: props.index % 2 === 0 ? '#f8f8f8' : 'rgb(240 240 240)' }">
<div class="container-top-colorbar" :style="{ backgroundColor: props.color }"></div>
<div class="container-top-breadcrumb">
<!-- <n-scrollbar x-scrollable> -->
<div class="container-top-breadcrumb-container">
<n-breadcrumb separator=">" style="margin-left: 5px;">
<n-breadcrumb-item :clickable="false">
<n-icon size="18">
<CloudOutline />
</n-icon>
</n-breadcrumb-item>
<n-breadcrumb-item v-for="(dir) in currentDirectory?.directoryArray"
@click="setCurrentDirectory(dir)" :clickable="true">
<div>{{ dir.name }}</div>
</n-breadcrumb-item>
</n-breadcrumb>
</div>
<!-- </n-scrollbar> -->
</div>
<div class="container-top-tools">
<n-icon class="container-top-tools-item" size="15" @click="handleBack">
<ChevronBackOutline />
</n-icon>
<n-icon class="container-top-tools-item" size="14" @click="handleReload">
<ReloadOutline class="container-top-tools-item-reload" />
</n-icon>
<n-icon class="container-top-tools-item" size="20" @click="emit('exit')">
<CloseOutline />
</n-icon>
</div>
<div class="miao-directory-item-container" ref="rootDomRef">
<div
class="container-top"
:style="{ backgroundColor: index % 2 === 0 ? '#f8f8f8' : 'rgb(240 240 240)' }"
>
<div class="container-top-colorbar" :style="{ backgroundColor: props.color }"></div>
<div class="container-top-breadcrumb">
<!-- <n-scrollbar x-scrollable> -->
<div class="container-top-breadcrumb-container">
<n-breadcrumb separator=">" style="margin-left: 5px">
<n-breadcrumb-item :clickable="false">
<n-icon size="18">
<CloudOutline />
</n-icon>
</n-breadcrumb-item>
<n-breadcrumb-item
v-for="dir in currentDirectory?.directoryArray"
@click="setCurrentDirectory(dir)"
:clickable="true"
>
<div>{{ dir.name }}</div>
</n-breadcrumb-item>
</n-breadcrumb>
</div>
<div class="container-items"
:style="{ backgroundColor: props.index % 2 === 0 ? '#f8f8f8' : 'rgb(240 240 240)' }">
<n-scrollbar>
<transition-group name="dirItem">
<miaoDirectoryItem v-for="(dir) in showData_directory" @click="setCurrentDirectory(dir)" :item="dir"
:key="dir.uid" :color="props.color" @delete="handleItemDelete(dir)" />
</transition-group>
<transition-group name="dirItem">
<miaoDirectoryItem v-for="(file) in showData_files" :item="file" :key="file.uid"
@click="openUrl(`${baseUrl}${api.get}${file.path}`)"
@download="openUrl(`${baseUrl}${api.get}${file.path}`, { download: file.name })"
@delete="handleItemDelete(file)" :color="props.color" />
</transition-group>
</n-scrollbar>
</div>
<div class="container-bottom"
:style="{ backgroundColor: props.index % 2 === 1 ? 'rgb(61 61 61)' : 'rgb(162 162 162)' }"></div>
<miaoMask v-model:show="showModel"></miaoMask>
<!-- </n-scrollbar> -->
</div>
<div class="container-top-tools">
<n-icon class="container-top-tools-item" size="15" @click="handleBack">
<ChevronBackOutline />
</n-icon>
<n-icon class="container-top-tools-item" size="14" @click="handleReload">
<ReloadOutline class="container-top-tools-item-reload" />
</n-icon>
<n-icon class="container-top-tools-item" size="20" @click="emit('exit')">
<CloseOutline />
</n-icon>
</div>
</div>
<div
class="container-items"
:style="{ backgroundColor: index % 2 === 0 ? '#f8f8f8' : 'rgb(240 240 240)' }"
>
<n-scrollbar>
<transition-group name="dirItem">
<miaoDirectoryItem
v-for="dir in showData_directory"
@click="setCurrentDirectory(dir)"
:item="dir"
:key="dir.uid"
:color="props.color"
@delete="handleItemDelete(dir)"
/>
</transition-group>
<transition-group name="dirItem">
<miaoDirectoryItem
v-for="file in showData_files"
:item="file"
:key="file.uid"
@click="openUrl(`${baseUrl}${api.get}${file.path}`)"
@download="
openUrl(`${baseUrl}${api.get}${file.path}`, { download: file.name })
"
@delete="handleItemDelete(file)"
:color="props.color"
/>
</transition-group>
</n-scrollbar>
</div>
<div
class="container-bottom"
:style="{ backgroundColor: index % 2 === 1 ? 'rgb(61 61 61)' : 'rgb(162 162 162)' }"
></div>
<miao-mask v-model:show="showModel"></miao-mask>
<!-- <miao-popup-input ref="popupInput"></miao-popup-input> -->
</div>
</template>
<script setup lang="ts">
import { NBreadcrumbItem, NBreadcrumb, NIcon, NScrollbar } from "naive-ui";
import type { file, directory } from "@/types/type.ts";
import VirtualDirectory, { VirtualFile } from "@/class/VirtualDirectory";
import { computed, onMounted, ref } from "vue";
import Config from "@/config";
import dropHandler, { dropHandlerHooks } from "@/hooks/dropHandler";
import miaoMask from "@/components/miaoMask.vue";
import miaoPopupInput from "@/components/miaoPopupInput.vue";
import miaoDirectoryItem from "@/components/miaoDirectory/miaoDirectoryItem.vue";
import useMiaoFetchApi from "@/hooks/useMiaoFetchApi";
import useDataBus from "@/hooks/useDataBus";
import {
CloudOutline,
ChevronBackOutline,
ReloadOutline,
CloseOutline,
} from "@vicons/ionicons5";
import useUploadQueue from "@/hooks/useUploadQueue";
import useViretualPages from "@/hooks/useVirtualPages";
import { NBreadcrumbItem, NBreadcrumb, NIcon, NScrollbar } from 'naive-ui'
import type { file, directory } from '@/types/type.ts'
import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory';
import { computed, onMounted, ref } from 'vue';
import Config from '@/config'
import dropHandler, { dropHandlerHooks } from '@/hooks/dropHandler';
import miaoMask from '@/components/miaoMask.vue';
import miaoDirectoryItem from '@/components/miaoDirectory/miaoDirectoryItem.vue';
import useMiaoFetchApi from '@/hooks/useMiaoFetchApi';
import useDataBus from '@/hooks/useDataBus';
import { CloudOutline, ChevronBackOutline, ReloadOutline, CloseOutline } from '@vicons/ionicons5'
import useUploadQueue from '@/hooks/useUploadQueue';
const { baseUrl, api } = Config;
const dataBus = useDataBus();
const views = useViretualPages();
const { baseUrl, api } = Config
const dataBus = useDataBus()
const miaoFetchApi = useMiaoFetchApi()
const miaoFetchApi = useMiaoFetchApi();
const props = defineProps<{
color: string
index: number
}>()
color: string;
id: number;
}>();
const emit = defineEmits<{
exit: []
}>()
exit: [];
}>();
const popupInput = ref()
// 是否显示模态框
const showModel = ref(0)
const showModel = ref(0);
const currentObjects = defineModel<VirtualDirectory[]>("currentObjects", {
required: true,
});
// 目前展示的Dir
const currentDirectory = defineModel<VirtualDirectory>('currentDirectory', { required: true })
const currentDirectory = computed(() => {
return currentObjects.value[0];
});
const index = computed(() => {
for (let i in views) {
if (views[i].uid === props.id) {
return parseInt(i);
}
}
return 0;
});
// 用于展示的数据,后续会增加排序等操作
const showData_directory = computed<VirtualDirectory[]>(() => {
const data = currentDirectory.value?.directorys ? [...currentDirectory.value?.directorys] : []
return data
})
const data = currentDirectory.value?.directorys
? [...currentDirectory.value?.directorys]
: [];
return data;
});
const showData_files = computed<VirtualFile[]>(() => {
const data = currentDirectory.value?.files ? [...currentDirectory.value?.files] : []
return data
})
const data = currentDirectory.value?.files ? [...currentDirectory.value?.files] : [];
return data;
});
// 用于监听拖拽文件事件
const rootDomRef = ref<HTMLDivElement>()
const rootDomRef = ref<HTMLDivElement>();
const uploadQueue = useUploadQueue()
const uploadQueue = useUploadQueue();
// 设置显示的dir
const setCurrentDirectory = async (virtualDirectory: VirtualDirectory) => {
// 若没有缓存数据则同步刷新,有缓存数据则异步刷新
if (virtualDirectory.files === undefined || virtualDirectory.directorys === undefined) {
const data: (file | directory)[] = await miaoFetchApi.get(virtualDirectory)
virtualDirectory.updateContent(data)
} else {
; (async () => {
const data: (file | directory)[] = await miaoFetchApi.get(virtualDirectory)
virtualDirectory.updateContent(data)
})()
}
currentDirectory.value = virtualDirectory
}
// 若没有缓存数据则同步刷新,有缓存数据则异步刷新
if (virtualDirectory.files === undefined || virtualDirectory.directorys === undefined) {
const data: (file | directory)[] = await miaoFetchApi.get(virtualDirectory);
virtualDirectory.updateContent(data);
} else {
(async () => {
const data: (file | directory)[] = await miaoFetchApi.get(virtualDirectory);
virtualDirectory.updateContent(data);
})();
}
currentObjects.value[0] = virtualDirectory;
};
const reload = () => {
setCurrentDirectory(currentDirectory.value)
}
setCurrentDirectory(currentDirectory.value);
};
const openUrl = (href: string, config?: {
download?: string
target?: string
}) => {
let { target, download } = config ?? {}
console.log({ target, download, href })
const a = document.createElement('a');
a.setAttribute('href', href);
a.setAttribute('target', target ?? '_blank')
download && a.setAttribute('download', download);
a.click();
}
const openUrl = (
href: string,
config?: {
download?: string;
target?: string;
}
) => {
let { target, download } = config ?? {};
const a = document.createElement("a");
a.setAttribute("href", href);
a.setAttribute("target", target ?? "_blank");
download && a.setAttribute("download", download);
a.click();
};
const handleBack = () => {
if (currentDirectory.value?.parent) {
setCurrentDirectory(currentDirectory.value.parent)
}
}
if (currentDirectory.value?.parent) {
setCurrentDirectory(currentDirectory.value.parent);
}
};
const handleReload = () => {
reload()
}
reload();
};
const handleItemDelete = async (item: VirtualDirectory | VirtualFile) => {
const { response } = miaoFetchApi.delete(item, {
retry: 5
})
// todo: 将删除事件统一用一个事件管理中心管理
const id = (await response).eventId
const { response: res } = miaoFetchApi.query(id)
const eventResult = await res
if (eventResult.status === 'success') {
reload()
}
}
const { response } = miaoFetchApi.delete(item, {
retry: 5,
});
// todo: 将删除事件统一用一个事件管理中心管理
const id = (await response).eventId;
const { response: res } = miaoFetchApi.query(id);
const eventResult = await res;
if (eventResult.status === "success") {
reload();
}
};
onMounted(async () => {
currentDirectory.value && await setCurrentDirectory(currentDirectory.value)
// 拖动事件处理
const dropFileOption: dropHandlerHooks = {
onDropFiles(files: File[]) {
const dragData = dataBus.pop('dragData')
console.log({ dragData, files })
if (files && files.length > 0) {
currentDirectory.value && uploadQueue.push(files, currentDirectory.value, {
onFinish() {
reload()
},
})
}
},
onEnter() {
showModel.value += 1
},
onEnd() {
showModel.value -= 1
},
}
rootDomRef.value && dropHandler(rootDomRef.value, dropFileOption)
})
await setCurrentDirectory(currentDirectory.value);
// 拖动事件处理
const dropFileOption: dropHandlerHooks = {
onDropFiles(files: File[]) {
const dragData = dataBus.pop("dragData");
console.log({ dragData, files });
if (files && files.length > 0) {
currentDirectory.value &&
uploadQueue.push(files, currentDirectory.value, {
onFinish() {
reload();
},
});
}
},
onEnter() {
showModel.value += 1;
},
onEnd() {
showModel.value -= 1;
},
};
rootDomRef.value && dropHandler(rootDomRef.value, dropFileOption);
console.log(await popupInput.value.popup())
});
</script>
<style scoped lang="scss">
@@ -188,133 +242,137 @@ $top-height: 30px;
$bottom-bar-height: 60px;
.miao-directory-item-container {
position: relative;
height: 100%;
width: inherit;
min-width: 266px;
display: flex;
flex-direction: column;
overflow: hidden;
.container-top {
height: $top-height;
width: 100%;
user-select: none;
position: relative;
height: 100%;
width: inherit;
min-width: 266px;
display: flex;
flex-direction: column;
align-items: center;
overflow: hidden;
background-color: #f1f1f1;
.container-top {
height: $top-height;
width: 100%;
user-select: none;
position: relative;
.container-top-colorbar {
height: 70%;
width: 5px;
margin-left: 10px;
border-radius: 3px 0 0 3px;
}
.container-top-breadcrumb {
position: relative;
height: 70%;
width: calc(100% - 95px);
display: flex;
justify-content: flex-start;
align-items: center;
// overflow: hidden;
overflow-y: hidden;
overflow-x: hidden;
background-color: #ffffff;
padding: 0 5px;
border-radius: 0 3px 3px 0;
.container-top-breadcrumb-container {
position: absolute;
height: 100%;
right: 0;
top: 0;
min-width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
overflow: hidden;
background-color: #f1f1f1;
.container-top-colorbar {
height: 70%;
width: 5px;
margin-left: 10px;
border-radius: 3px 0 0 3px;
}
.container-top-breadcrumb {
position: relative;
height: 70%;
width: calc(100% - 95px);
display: flex;
justify-content: flex-start;
align-items: center;
// overflow: hidden;
overflow-y: hidden;
overflow-x: hidden;
background-color: #ffffff;
padding: 0 5px;
border-radius: 0 3px 3px 0;
.container-top-breadcrumb-container {
position: absolute;
height: 100%;
right: 0;
top: 0;
min-width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
}
}
.container-top-tools {
position: relative;
height: 100%;
width: 70px;
padding: 0 5px;
display: flex;
justify-content: space-around;
align-items: center;
.container-top-tools-item {
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
background-color: #ffffff00;
border-radius: 5px;
transition: background-color 0.3s;
cursor: pointer;
.container-top-tools-item-reload {
transition: transform 0.6s ease-in-out;
}
&:hover {
background-color: rgba(128, 128, 128, 0.4);
}
&:active .container-top-tools-item-reload {
transform: rotate(360deg);
}
}
// &::after {
// content: '';
// height: 60%;
// position: absolute;
// right: -2px;
// width: 2px;
// background-color: #828282;
// }
}
}
}
.container-items {
width: 100%;
height: calc(100% - $top-height - $bottom-bar-height);
// flex: 1;
}
.container-top-tools {
position: relative;
height: 100%;
width: 70px;
padding: 0 5px;
display: flex;
justify-content: space-around;
align-items: center;
.container-bottom {
height: $bottom-bar-height;
background-color: bisque;
.container-top-tools-item {
width: 20px;
height: 20px;
display: flex;
justify-content: center;
align-items: center;
user-select: none;
box-shadow: 0px 1px 10px #888888;
z-index: 2;
background-color: #ffffff00;
border-radius: 5px;
transition: background-color 0.3s;
cursor: pointer;
.container-top-tools-item-reload {
transition: transform 0.6s ease-in-out;
}
&:hover {
background-color: rgba(128, 128, 128, 0.4);
}
&:active .container-top-tools-item-reload {
transform: rotate(360deg);
}
}
// &::after {
// content: '';
// height: 60%;
// position: absolute;
// right: -2px;
// width: 2px;
// background-color: #828282;
// }
}
}
.container-items {
width: 100%;
height: calc(100% - $top-height - $bottom-bar-height);
// flex: 1;
}
.container-bottom {
height: $bottom-bar-height;
background-color: bisque;
display: flex;
justify-content: center;
align-items: center;
user-select: none;
box-shadow: 0px 1px 10px #888888;
z-index: 2;
}
}
// transition动画相关
.miao-directory-item-container {
.container-items {
.dirItem-move {
transition: all 0.2s ease;
}
.dirItem-leave-active {
transition: none;
}
.dirItem-enter-from {
opacity: 0;
}
.dirItem-leave-active {
position: absolute;
}
.container-items {
.dirItem-move {
transition: all 0.2s ease;
}
.dirItem-leave-active {
transition: none;
}
.dirItem-enter-from {
opacity: 0;
}
.dirItem-leave-active {
position: absolute;
}
}
}
</style>