feat: 添加miaoDropHandler組件用於統一處理drop事件,格式化代碼

This commit is contained in:
539943419 committed 2024-08-15 13:05:21 +08:00
1 parent 0c501c800b
commit 2b378b0343
7 files changed
+892 -653

No files matched your search

+325 -276
View File
@@ -1,150 +1,202 @@
<script setup lang="ts">
import { h, onMounted, reactive, ref, shallowRef } from 'vue';
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, { VirtualFile } from './class/VirtualDirectory';
import MiaoMask from './components/miaoMask.vue';
import config from './config';
import useViretualPages from './hooks/useVirtualPages';
import { h, onMounted, reactive, ref, shallowRef } from 'vue'
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, { VirtualFile } from './class/VirtualDirectory'
import MiaoMask from './components/miaoMask.vue'
import config from './config'
import useViretualPages from './hooks/useVirtualPages'
const { baseUrl } = config
const showModal = ref<boolean>(false)
const modalData = shallowRef<{
component: any
props: any
component: any
props: any
}>()
const views = useViretualPages()
const rootDirectory = reactive(new VirtualDirectory({
name: '根目录',
stats: {
atimeMs: 0,
birthtimeMs: 0,
ctimeMs: 0,
mtimeMs: 0,
}
}))
const rootDirectory = reactive(
new VirtualDirectory({
name: '根目录',
stats: {
atimeMs: 0,
birthtimeMs: 0,
ctimeMs: 0,
mtimeMs: 0
}
})
)
// component就是一个vue组件,类似于miaoDirectory
const createView = (component: any, currentObjects: (VirtualDirectory | VirtualFile)[], index?: number) => {
index ?? (index = views.length)
views.splice(index, 0, reactive<VirtualPage>(new VirtualPage(component, [...currentObjects])))
const createView = (
component: any,
currentObjects: (VirtualDirectory | VirtualFile)[],
index?: number
) => {
index ?? (index = views.length)
views.splice(
index,
0,
reactive<VirtualPage>(new VirtualPage(component, [...currentObjects]))
)
}
const deleteView = (index: number) => {
views.splice(index, 1)
if (views.length === 0) {
createView(miaoDirectory, [rootDirectory])
}
views.splice(index, 1)
if (views.length === 0) {
createView(miaoDirectory, [rootDirectory])
}
}
// 点击标题是隐藏其他标签或显示其他标签,还挺好用的
const handleClickTitle = (index: number) => {
let count = views.filter(v => v.visible).length
let op = !views[index].visible || count > 1 ? 'hideOthers' : 'showOthers'
let _index = 0
while (_index < views.length) {
if (_index !== index) {
views[_index].visible = op === 'hideOthers' ? false : true
} else {
views[_index].visible = true
let count = views.filter((v) => v.visible).length
let op = !views[index].visible || count > 1 ? 'hideOthers' : 'showOthers'
let _index = 0
while (_index < views.length) {
if (_index !== index) {
views[_index].visible = op === 'hideOthers' ? false : true
} else {
views[_index].visible = true
}
_index += 1
}
_index += 1
}
}
// 顶部菜单相关
function renderIcon(icon: any) {
return () => {
return h(NIcon, null, {
default: () => h(icon)
})
}
return () => {
return h(NIcon, null, {
default: () => h(icon)
})
}
}
const openMenuOption = [
{
label: '分享',
key: 'share',
icon: renderIcon(QrCodeOutline)
}
{
label: '分享',
key: 'share',
icon: renderIcon(QrCodeOutline)
}
]
const handleMenuSelect = (key: string) => {
if (key === 'share') {
showModal.value = true
modalData.value = {
component: NQrCode,
props: {
value: baseUrl,
size: 300,
errorCorrectionLevel: 'H'
}
if (key === 'share') {
showModal.value = true
modalData.value = {
component: NQrCode,
props: {
value: baseUrl,
size: 300,
errorCorrectionLevel: 'H'
}
}
}
}
}
onMounted(() => {
createView(miaoDirectory, [rootDirectory])
createView(miaoDirectory, [rootDirectory])
})
</script>
<template>
<div class="view">
<div class="view-controler">
<n-scrollbar x-scrollable>
<div class="tags-container">
<transition-group name="tab">
<div class="tab" v-for="(view, index) of views" :key="view.uid">
<div class="tab-title" @click="handleClickTitle(index)">{{ view.title }}</div>
<div class="tab-point" :style="{ backgroundColor: view.color }"></div>
<div class="tab-control">
<n-icon class="tag-control-icon icon" @click="view.switchShow()">
<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.component, view.currentObjects, index + 1)">
<CopyOutline />
</n-icon>
<n-icon class="tag-control-icon icon" @click="deleteView(index)">
<CloseOutline class="icon-inner" />
</n-icon>
</div>
<div class="view">
<div class="view-controler">
<n-scrollbar x-scrollable>
<div class="tags-container">
<transition-group name="tab">
<div
class="tab"
v-for="(view, index) of views"
:key="view.uid">
<div
class="tab-title"
@click="handleClickTitle(index)">
{{ view.title }}
</div>
<div
class="tab-point"
:style="{ backgroundColor: view.color }"></div>
<div class="tab-control">
<n-icon
class="tag-control-icon icon"
@click="view.switchShow()">
<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.component,
view.currentObjects,
index + 1
)
">
<CopyOutline />
</n-icon>
<n-icon
class="tag-control-icon icon"
@click="deleteView(index)">
<CloseOutline class="icon-inner" />
</n-icon>
</div>
</div>
</transition-group>
</div>
</n-scrollbar>
<div class="view-controller-menu">
<div class="view-controller-menu-item">
<n-dropdown
trigger="click"
:options="openMenuOption"
@select="handleMenuSelect">
<n-icon class="icon">
<EllipsisVertical class="icon-inner" />
</n-icon>
</n-dropdown>
</div>
</div>
</transition-group>
</div>
</n-scrollbar>
<div class="view-controler-menu">
<div class="view-controler-menu-item">
<n-dropdown trigger="click" :options="openMenuOption" @select="handleMenuSelect">
<n-icon class="icon">
<EllipsisVertical class="icon-inner" />
</n-icon>
</n-dropdown>
<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">
<component
:is="view.component"
v-model:current-objects="view.currentObjects"
:id="view.uid"
:color="view.color"
@exit="deleteView(index)"></component>
</div>
</transition-group>
</div>
</div>
</div>
<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">
<component :is="view.component" v-model:current-objects="view.currentObjects" :id="view.uid"
:color="view.color" @exit="deleteView(index)"></component>
</div>
</transition-group>
</div>
</div>
<!-- 模态框展示,用来显示分享二维码,设置菜单之类的东西 -->
<MiaoMask v-model:show="showModal" @click="showModal = false">
<component :is="modalData?.component" v-bind="modalData?.props"></component>
</MiaoMask>
<!-- 模态框展示,用来显示分享二维码,设置菜单之类的东西 -->
<MiaoMask v-model:show="showModal" @click="showModal = false">
<component
:is="modalData?.component"
v-bind="modalData?.props"></component>
</MiaoMask>
</template>
<style lang="scss" scoped>
@@ -152,197 +204,194 @@ $controler-height: 25px;
$tag-width: 170px;
.view {
position: relative;
height: 100%;
width: 100%;
.view-controler {
display: flex;
height: $controler-height;
width: 100%;
background-color: #dadada;
// overflow: hidden;
.tags-container {
display: flex;
margin-left: 5px;
flex: 1;
// overflow-x: scroll;
.tab {
position: relative;
width: $tag-width;
margin: 0 5px;
// flex: 1;
display: flex;
border-radius: 5px 5px 0 0;
align-items: center;
.tab-title {
margin-left: 4px;
// width: 70%;
user-select: none;
cursor: pointer;
}
.tab-point {
height: 5px;
aspect-ratio: 1;
border-radius: 66px;
margin-left: 5px;
}
.tab-control {
position: absolute;
right: 10px;
display: flex;
align-items: center;
.tab-control-icon:not(:last-child) {
margin-right: 5px;
}
}
&:not(:last-child)::after {
content: '';
height: calc($controler-height*0.6);
width: 1px;
border-right: 1px solid black;
position: absolute;
right: 0px;
}
}
}
.view-controler-menu {
position: relative;
display: flex;
align-items: center;
padding: 0 5px;
.view-controler-menu-item {
height: 100%;
aspect-ratio: 1;
display: flex;
justify-content: center;
align-items: center;
}
&::before {
content: '';
height: calc($controler-height*0.6);
width: 1px;
border-right: 1px solid black;
position: absolute;
left: 2px;
}
}
.icon {
height: calc($controler-height * 0.7);
width: calc($controler-height * 0.7);
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
background-color: rgba(128, 128, 128, 0);
transition: background-color 0.3s;
&:hover {
background-color: rgba(128, 128, 128, 0.4);
}
}
}
.view-container {
position: relative;
display: flex;
// flex-direction: column;
height: calc(100% - $controler-height);
overflow-y: overlay;
overflow-x: overlay;
height: 100%;
width: 100%;
.view-container-item {
flex: 1;
height: 100%;
.view-controler {
display: flex;
height: $controler-height;
width: 100%;
background-color: #dadada;
// overflow: hidden;
.tags-container {
display: flex;
margin-left: 5px;
flex: 1;
// overflow-x: scroll;
.tab {
position: relative;
width: $tag-width;
margin: 0 5px;
// flex: 1;
display: flex;
border-radius: 5px 5px 0 0;
align-items: center;
.tab-title {
margin-left: 4px;
// width: 70%;
user-select: none;
cursor: pointer;
}
.tab-point {
height: 5px;
aspect-ratio: 1;
border-radius: 66px;
margin-left: 5px;
}
.tab-control {
position: absolute;
right: 10px;
display: flex;
align-items: center;
.tab-control-icon:not(:last-child) {
margin-right: 5px;
}
}
&:not(:last-child)::after {
content: '';
height: calc($controler-height * 0.6);
width: 1px;
border-right: 1px solid black;
position: absolute;
right: 0px;
}
}
}
.view-controller-menu {
position: relative;
display: flex;
align-items: center;
padding: 0 5px;
.view-controller-menu-item {
height: 100%;
aspect-ratio: 1;
display: flex;
justify-content: center;
align-items: center;
}
&::before {
content: '';
height: calc($controler-height * 0.6);
width: 1px;
border-right: 1px solid black;
position: absolute;
left: 2px;
}
}
.icon {
height: calc($controler-height * 0.7);
width: calc($controler-height * 0.7);
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
background-color: rgba(128, 128, 128, 0);
transition: background-color 0.3s;
&:hover {
background-color: rgba(128, 128, 128, 0.4);
}
}
}
&::-webkit-scrollbar {
position: absolute;
width: 4px;
}
.view-container {
position: relative;
display: flex;
// flex-direction: column;
height: calc(100% - $controler-height);
overflow-y: overlay;
overflow-x: overlay;
// 滚动条轨道
&::-webkit-scrollbar-track {
// background: rgb(239, 239, 239);
border-radius: 2px;
}
.view-container-item {
flex: 1;
height: 100%;
}
// 小滑块
&::-webkit-scrollbar-thumb {
background: #e0e0e0;
// border-radius: 5px;
}
&::-webkit-scrollbar {
position: absolute;
width: 4px;
}
&::-webkit-scrollbar-thumb:hover {
background: #999999;
// 滚动条轨道
&::-webkit-scrollbar-track {
// background: rgb(239, 239, 239);
border-radius: 2px;
}
// 小滑块
&::-webkit-scrollbar-thumb {
background: #e0e0e0;
// border-radius: 5px;
}
&::-webkit-scrollbar-thumb:hover {
background: #999999;
}
}
}
}
// transition动画相关
.view {
.view-controler {
.tags-container {
.view-controler {
.tags-container {
.tab-move,
.tab-enter-active {
transition: all 0.2s ease;
}
.tab-move,
.tab-enter-active {
transition: all 0.2s ease;
}
.tab-leave-active {
transition: none;
}
.tab-leave-active {
transition: none;
}
.tab-enter-from {
opacity: 0;
transform: translateY(-30px);
}
.tab-enter-from {
opacity: 0;
transform: translateY(-30px);
}
// .tab-leave-to {
// opacity: 0;
// transform: translateY(-80px) translateX(calc($tag-width + 10px));
// }
// .tab-leave-to {
// opacity: 0;
// transform: translateY(-80px) translateX(calc($tag-width + 10px));
// }
.tab-leave-active {
position: absolute;
}
.tab-leave-active {
position: absolute;
}
}
}
}
.view-container {
// .view-container {
// .page-move {
// transition: all 0.2s ease;
// }
// .page-move {
// transition: all 0.2s ease;
// .page-enter-active {
// }
// .page-leave-active {
// transition: none;
// }
// .page-enter-from {
// opacity: 0;
// }
// .page-leave-active {
// position: absolute;
// }
// }
// .page-enter-active {
// }
// .page-leave-active {
// transition: none;
// }
// .page-enter-from {
// opacity: 0;
// }
// .page-leave-active {
// position: absolute;
// }
}
}
</style>
File renamed without changes.
File renamed without changes.
@@ -1,116 +1,169 @@
<template>
<div class="miao-item" draggable="true" ref="itemRef" @dragstart="handleDragStart">
<div class="item-main">
<div class="item-main-front" :style="{ backgroundColor: props.color }"></div>
<Icon class="item-main-icon"></Icon>
<div class="item-main-info">
<div class="item-main-info-name">
{{ name }}
</div>
<div class="item-main-info-date" v-if="time">
{{ time }}
</div>
</div>
<n-dropdown trigger="click" :options="dropDownOptions" @select="handleDropdownSelect">
<div class="item-main-option" @click="e=>e.stopPropagation()">
<EllipsisVertical class="item-main-option-icon" />
</div>
</n-dropdown>
</div>
</div>
<div
class="miao-item"
draggable="true"
ref="itemRef"
@dragstart="handleDragStart">
<div class="item-main">
<div
class="item-main-front"
:style="{ backgroundColor: props.color }"></div>
<Icon class="item-main-icon"></Icon>
<div class="item-main-info">
<div class="item-main-info-name">
{{ name }}
</div>
<div class="item-main-info-date" v-if="time">
{{ time }}
</div>
</div>
<n-dropdown
trigger="click"
:options="dropDownOptions"
@select="handleDropdownSelect">
<div
class="item-main-option"
@click="(e) => e.stopPropagation()">
<EllipsisVertical class="item-main-option-icon" />
</div>
</n-dropdown>
</div>
</div>
</template>
<script setup lang="ts">
import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory'
import {
DocumentText,
FileTray,
Help,
Image,
LogoChrome,
LogoCss3,
LogoJavascript,
LogoVue,
PlayCircle,
EllipsisVertical,
Disc
} from '@vicons/ionicons5'
import { computed, ref } from 'vue'
import dateFormatter from '@/hooks/dateFormatter'
// import useDataBus from '@/hooks/useDataBus'
import { NDropdown } from 'naive-ui'
import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory';
import { DocumentText, FileTray, Help, Image, LogoChrome, LogoCss3, LogoJavascript, LogoVue, PlayCircle, EllipsisVertical, Disc } from '@vicons/ionicons5';
import { computed, ref } from 'vue';
import dateFormatter from '@/hooks/dateFormatter';
import useDataBus from '@/hooks/useDataBus';
import { NDropdown } from 'naive-ui';
// const dataBus = useDataBus()
const dataBus = useDataBus()
const props = defineProps<{
item: VirtualDirectory | VirtualFile
name?: string
color: string
}>()
const emit = defineEmits<{
download: []
delete: []
dragStart: []
}>()
const props = defineProps<{
item: VirtualDirectory | VirtualFile
name?: string
color:string
dragStart: [event: DragEvent, item: VirtualDirectory | VirtualFile]
}>()
const itemType = ref<'file' | 'directory'>(props.item.type)
const name = props.name ?? props.item.name
const time = computed(()=>{
return dateFormatter(new Date(props.item.stats.mtimeMs), "yyyy-MM-dd hh:mm:ss");
const time = computed(() => {
return dateFormatter(
new Date(props.item.stats.mtimeMs),
'yyyy-MM-dd hh:mm:ss'
)
})
const dropDownOptions = computed(()=> {
const options = [{
label: '删除',
key: 'delete'
}]
if(itemType.value === 'file'){
options.push(...[
{
label: '下载',
key: 'download',
}
])
const dropDownOptions = computed(() => {
const options = [
{
label: '删除',
key: 'delete'
}
]
if (itemType.value === 'file') {
options.push(
...[
{
label: '下载',
key: 'download'
}
]
)
}
return options
})
const handleDropdownSelect = (key: 'download' | 'delete') => {
if(key === 'download'){
if (key === 'download') {
emit('download')
} else if (key === 'delete') {
emit('delete')
}
}
const Icon = computed(()=>{
try{
if(props.item.type === 'directory') {
const Icon = computed(() => {
try {
if (props.item.type === 'directory') {
return FileTray
} else if(props.item.type === 'file') {
} else if (props.item.type === 'file') {
const aft = props.item.name.split('.').at(-1)?.toLowerCase()
if(!aft){
throw(1)
}else if(['txt', 'doc', 'docs', 'docx', 'xls', 'xlsx'].includes(aft)){
if (!aft) {
throw 1
} else if (
['txt', 'doc', 'docs', 'docx', 'xls', 'xlsx'].includes(aft)
) {
return DocumentText
}else if(['mp3', 'wav', 'ape', 'acc', 'ogg', 'flac'].includes(aft)){
} else if (
['mp3', 'wav', 'ape', 'acc', 'ogg', 'flac'].includes(aft)
) {
return Disc
}else if(['.xbm','.tif','pjp','.svgz','jpg','jpeg','ico','tiff','.gif','svg','.jfif','.webp','.png','.bmp','pjpeg','.avif'].includes(aft)){
} else if (
[
'.xbm',
'.tif',
'pjp',
'.svgz',
'jpg',
'jpeg',
'ico',
'tiff',
'.gif',
'svg',
'.jfif',
'.webp',
'.png',
'.bmp',
'pjpeg',
'.avif'
].includes(aft)
) {
return Image
}else if(['mp4', 'm2v', 'mkv'].includes(aft)){
} else if (['mp4', 'm2v', 'mkv'].includes(aft)) {
return PlayCircle
}else if(['js', 'jsx', 'ts', 'tsx'].includes(aft)){
} else if (['js', 'jsx', 'ts', 'tsx'].includes(aft)) {
return LogoJavascript
}else if(['html'].includes(aft)){
} else if (['html'].includes(aft)) {
return LogoChrome
}else if(['css', 'scss', 'less'].includes(aft)){
} else if (['css', 'scss', 'less'].includes(aft)) {
return LogoCss3
}else if(['vue'].includes(aft)){
} else if (['vue'].includes(aft)) {
return LogoVue
}
return DocumentText
}
}catch{
} catch {
return Help
}
})
const itemRef = ref()
const handleDragStart = () => {
dataBus.set('dragData', [ props.item ])
const handleDragStart = (event: DragEvent) => {
emit('dragStart', event, props.item)
// dataBus.set('dragData', [props.item])
}
</script>
@@ -138,7 +191,7 @@ const handleDragStart = () => {
.item-main-front {
height: 100%;
width: 10px;
border-radius: 7px 0 0 7px ;
border-radius: 7px 0 0 7px;
background-color: rgb(0, 0, 0);
}
.item-main-icon {
@@ -176,17 +229,18 @@ const handleDragStart = () => {
color: #000;
transition: color 0.15s;
}
&:hover, &:active {
&:hover,
&:active {
background-color: #8b8b8b;
.item-main-option-icon {
color: #FFF;
color: #fff;
}
}
}
&:hover, &:active {
&:hover,
&:active {
background-color: #d2d2d2;
}
}
}
</style>
+106
View File
@@ -0,0 +1,106 @@
<template>
<div
class="container"
@dragover="handleDragOver"
@dragleave="handleDragLeave"
@drop="handleDrop">
<slot></slot>
<miao-mask v-model:show="showMask"></miao-mask>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import miaoMask from './miaoMask.vue'
import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory'
import config from '@/config'
import useDataBus from '@/hooks/useDataBus'
const dataBus = useDataBus()
const { uploadSizeLimit } = config
const emit = defineEmits<{
onFiles: [files: File[]]
onVirtualFiles: [vFiles: VirtualFile[]]
onVirtualDirectory: [vDirectory: VirtualDirectory[]]
}>()
const showMask = ref<number>(0)
// File類型的數據可能爲文件夾,通過FileReader進行判斷
const isFile = (file: File) =>
new Promise((resolve) => {
const reader = new FileReader()
reader.onload = () => {
resolve(true)
reader.abort()
}
reader.onerror = () => {
resolve(false)
reader.abort()
}
reader.readAsText(file)
})
/**
* 只保留文件,移去文件夹
* @param {File[]} files
*/
const removeDirectory = async (files: File[]) => {
let index = 0
while (index < files.length) {
const currentFile = files[index]
const _isFile = await isFile(currentFile)
if (_isFile) {
index += 1
continue
} else {
files.splice(index, 1)
continue
}
}
}
const handleDragOver = async (event: DragEvent) => {
showMask.value += 1
event.preventDefault()
}
const handleDragLeave = async () => {
showMask.value = 0
}
const handleDrop = async (event: DragEvent) => {
showMask.value = 0
event.preventDefault()
event.stopPropagation()
// todo: uploadSize的限制不應該在這裏
const files = [...(event.dataTransfer?.files ?? [])].filter(
(file) => file.size < uploadSizeLimit
)
await removeDirectory(files)
const virtualFiles: VirtualFile[] = dataBus.pop('dragData_vFiles')
const virtualDirectory: VirtualDirectory[] = dataBus.pop(
'dragData_vDirectory'
)
// console.log({ virtualDirectory, virtualFiles })
if (files && files.length) {
emit('onFiles', files)
}
if (virtualFiles && virtualFiles.length) {
emit('onVirtualFiles', virtualFiles)
}
if (virtualDirectory && virtualDirectory.length) {
emit('onVirtualDirectory', virtualDirectory)
}
}
</script>
<style lang="scss" scoped>
.container {
width: 100%;
height: 100%;
position: relative;
}
</style>
+1 -1
View File
@@ -2,7 +2,7 @@
<transition name="transition">
<div class="mask-body" ref="maskBodyRef" v-show="showMask">
<div @click="e=>e.stopPropagation()">
<slot> {{}} </slot>
<slot></slot>
</div>
</div>
</transition>
+336 -306
View File
@@ -1,240 +1,270 @@
<template>
<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>
<miao-drop-handler @on-virtual-directory="handleDropVDirectory" @on-files="handleDropFiles">
<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-colorfulBar"
: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>
<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)"
@drag-start="handleItemDragStart" />
</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)"
@drag-start="handleItemDragStart"
: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>
<!-- </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>
</miao-drop-handler>
</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 { 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 miaoMask from '@/components/miaoMask.vue'
import miaoDirectoryItem from '@/components/miaoDirectory/miaoDirectoryItem.vue'
// import miaoPopupInput from "@/components/miaoPopupInput.vue";
import miaoDropHandler from '@/components/miaoDropHandler.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";
CloudOutline,
ChevronBackOutline,
ReloadOutline,
CloseOutline
} from '@vicons/ionicons5'
import useUploadQueue from '@/hooks/useUploadQueue'
import useVirtualPages from '@/hooks/useVirtualPages'
const { baseUrl, api } = Config;
const dataBus = useDataBus();
const views = useViretualPages();
const { baseUrl, api } = Config
const dataBus = useDataBus()
const views = useVirtualPages()
const miaoFetchApi = useMiaoFetchApi();
const miaoFetchApi = useMiaoFetchApi()
const props = defineProps<{
color: string;
id: 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,
});
const currentObjects = defineModel<VirtualDirectory[]>('currentObjects', {
required: true
})
// 目前展示的Dir
const currentDirectory = computed(() => {
return currentObjects.value[0];
});
return currentObjects.value[0]
})
const index = computed(() => {
for (let i in views) {
if (views[i].uid === props.id) {
return parseInt(i);
for (let i in views) {
if (views[i].uid === props.id) {
return parseInt(i)
}
}
}
return 0;
});
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);
})();
}
currentObjects.value[0] = 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;
}
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();
};
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()
}
}
const handleItemDragStart = async (
_event: DragEvent,
item: VirtualDirectory | VirtualFile
) => {
if (item.type === 'file') {
dataBus.set('dragData_vFiles', [item])
} else {
dataBus.set('dragData_vDirectory', [item])
}
}
const handleDropVDirectory = async (vDirs: VirtualDirectory[]) => {
setCurrentDirectory(vDirs[0])
}
const handleDropFiles = async (files: File[]) => {
currentDirectory.value &&
uploadQueue.push(files, currentDirectory.value, {
onFinish() {
reload()
}
})
}
onMounted(async () => {
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())
});
await setCurrentDirectory(currentDirectory.value)
})
</script>
<style scoped lang="scss">
@@ -242,137 +272,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;
align-items: center;
flex-direction: column;
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%;
.container-top {
height: $top-height;
width: 100%;
user-select: none;
position: relative;
display: flex;
justify-content: flex-start;
align-items: center;
}
overflow: hidden;
background-color: #f1f1f1;
.container-top-colorfulBar {
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-top-tools {
position: relative;
height: 100%;
width: 70px;
padding: 0 5px;
display: flex;
justify-content: space-around;
align-items: center;
.container-items {
width: 100%;
height: calc(100% - $top-height - $bottom-bar-height);
// flex: 1;
}
.container-top-tools-item {
width: 20px;
height: 20px;
.container-bottom {
height: $bottom-bar-height;
background-color: bisque;
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;
// }
user-select: none;
box-shadow: 0px 1px 10px #888888;
z-index: 2;
}
}
.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;
}
// .miao-directory-item-container {
// .container-items {
// .dirItem-move {
// transition: all 0.2s ease;
// }
.dirItem-leave-active {
transition: none;
}
// .dirItem-leave-active {
// transition: none;
// }
.dirItem-enter-from {
opacity: 0;
}
// .dirItem-enter-from {
// opacity: 0;
// }
.dirItem-leave-active {
position: absolute;
}
}
}
// .dirItem-leave-active {
// position: absolute;
// }
// }
// }
</style>