3 Commits
8 changed files with 196 additions and 123 deletions

No files matched your search

+3 -3
View File
@@ -1,11 +1,11 @@
todo: 标签页,miaoDirectory切换动画 [finish] todo: 标签页,miaoDirectory切换动画 [finish]
todo: 重构virtualPage,所有page以插件形式使用 [finish] todo: 重构virtualPage,所有page以插件形式使用 [finish]
todo: miaoDirectory插件化,为后续插件功能开发做准备 [finish] todo: miaoDirectory插件化,为后续插件功能开发做准备 [finish]
todo: miaoPopupInput, 弹出一个输入框 [finish] todo: miaoPopupInput, 弹出一个输入框 [finish]
todo: miaoDirectory的重命名功能 [finish] todo: miaoDirectory的重命名功能 [finish]
todo: miaoDirectory的剪切功能 [finish] todo: miaoDirectory的剪切功能 [finish]
todo: miaoDirectory的上传按钮 [finish] todo: miaoDirectory的上传按钮 [finish]
todo: miaoDirectory的新建文件/文件夹功能 [finish] todo: miaoDirectory的新建文件/文件夹功能 [finish]
todo: miaoDirectory的多选功能 [finish]
todo: VirtualDirectory更新时更新文件信息 todo: VirtualDirectory更新时更新文件信息
todo: miaoDirectory的复制文件/文件夹功能 todo: miaoDirectory的复制文件/文件夹功能
todo: miaoDirectory的多选功能
+1
View File
@@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@vicons/ionicons5": "^0.12.0", "@vicons/ionicons5": "^0.12.0",
"axios": "^1.7.3", "axios": "^1.7.3",
"lodash": "^4.17.21",
"naive-ui": "^2.39.0", "naive-ui": "^2.39.0",
"sass": "^1.77.8", "sass": "^1.77.8",
"vue": "^3.4.35" "vue": "^3.4.35"
+3
View File
@@ -14,6 +14,9 @@ importers:
axios: axios:
specifier: ^1.7.3 specifier: ^1.7.3
version: 1.7.3 version: 1.7.3
lodash:
specifier: ^4.17.21
version: 4.17.21
naive-ui: naive-ui:
specifier: ^2.39.0 specifier: ^2.39.0
version: 2.39.0(vue@3.4.37(typescript@5.5.4)) version: 2.39.0(vue@3.4.37(typescript@5.5.4))
+1
View File
@@ -1,5 +1,6 @@
import generateId from "@/hooks/generateId"; import generateId from "@/hooks/generateId";
import useMiaoFetchApi from "@/hooks/useMiaoFetchApi"; import useMiaoFetchApi from "@/hooks/useMiaoFetchApi";
// import { debounce } from "lodash";
import type { stats, file, directory } from "@/types/type.ts"; import type { stats, file, directory } from "@/types/type.ts";
const miaoFetchApi = useMiaoFetchApi(); const miaoFetchApi = useMiaoFetchApi();
+2 -1
View File
@@ -37,7 +37,7 @@ const onClickoutside = async () => {
const handleMainMouseLeave = async () => { const handleMainMouseLeave = async () => {
setTimeout(() => { setTimeout(() => {
showContextMenu.value -= 1 showContextMenu.value -= 1
}, 100) }, 20)
// nextTick(()=>{ // nextTick(()=>{
// showContextMenu.value -= 1 // showContextMenu.value -= 1
// }) // })
@@ -45,6 +45,7 @@ const handleMainMouseLeave = async () => {
const handleSelect = async (key: string) => { const handleSelect = async (key: string) => {
emit('select', key) emit('select', key)
showContextMenu.value = 0
} }
const handleContextMenu = (e: MouseEvent) => { const handleContextMenu = (e: MouseEvent) => {
@@ -1,12 +1,13 @@
<template> <template>
<div class="miao-item" draggable="true" ref="itemRef" @dragstart="handleDragStart"> <div class="miao-item" draggable="true" ref="itemRef" @dragstart="handleDragStart">
<miaoContextMenu :options="dropDownOptions" :touch-time-out="500" @select="handleDropdownSelect"> <miaoContextMenu :options="dropDownOptions" :touch-time-out="500" @select="handleDropdownSelect">
<div class="item-main"> <div class="item-main" :class="props.selected ? 'item-main-selected' : ''" ref="mainRef">
<div class="item-main-front" :style="{ backgroundColor: props.color }"></div> <div class="item-main-front" :style="{ backgroundColor: props.color }"></div>
<Icon class="item-main-icon"></Icon> <Icon class="item-main-icon" v-show="clientWidth > 250"></Icon>
<div class="item-main-info"> <div class="item-main-info">
<div class="item-main-info-name"> <div class="item-main-info-name">
{{ name }} <Icon class="item-main-info-name-icon" v-show="clientWidth <= 250"></Icon>
<n-ellipsis style="max-width: 140px;">{{ name }}</n-ellipsis>
</div> </div>
<div class="item-main-info-date" v-if="time"> <div class="item-main-info-date" v-if="time">
{{ time }} {{ time }}
@@ -38,16 +39,18 @@ import {
PlayCircle, PlayCircle,
Disc Disc
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import { computed, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import dateFormatter from '@/hooks/dateFormatter' import dateFormatter from '@/hooks/dateFormatter'
import useMiaoFetchApi from '@/hooks/useMiaoFetchApi' import { NEllipsis } from 'naive-ui'
// import useMiaoFetchApi from '@/hooks/useMiaoFetchApi'
const miaoFetchApi = useMiaoFetchApi() // const miaoFetchApi = useMiaoFetchApi()
const props = defineProps<{ const props = defineProps<{
item: VirtualDirectory | VirtualFile item: VirtualDirectory | VirtualFile
name?: string name?: string
color: string color: string
selected: boolean
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -55,10 +58,16 @@ const emit = defineEmits<{
delete: [] delete: []
rename: [] rename: []
dragStart: [event: DragEvent, item: VirtualDirectory | VirtualFile] dragStart: [event: DragEvent, item: VirtualDirectory | VirtualFile]
onSelected: []
}>() }>()
const itemType = ref<'file' | 'directory'>(props.item.type) const itemType = ref<'file' | 'directory'>(props.item.type)
const mainRef = ref<HTMLDivElement>()
// 使用ResizeObserver实现
const clientWidth = ref<number>(666)
const name = props.name ?? props.item.name const name = props.name ?? props.item.name
const time = computed(() => { const time = computed(() => {
@@ -89,6 +98,25 @@ const dropDownOptions = computed(() => {
] ]
) )
} }
if (props.selected === false) {
options.push(
...[
{
label: '多选:选择',
key: 'select'
}
]
)
} else {
options.push(
...[
{
label: '多选:取消',
key: 'select'
}
]
)
}
return options return options
}) })
@@ -100,6 +128,8 @@ const handleDropdownSelect = (key: string) => {
emit('download') emit('download')
} else if (key === 'delete') { } else if (key === 'delete') {
emit('delete') emit('delete')
} else if (key === 'select') {
emit('onSelected')
} }
} }
@@ -164,13 +194,24 @@ const handleDragStart = (event: DragEvent) => {
emit('dragStart', event, props.item) emit('dragStart', event, props.item)
// dataBus.set('dragData', [props.item]) // dataBus.set('dragData', [props.item])
} }
onMounted(() => {
const observer = new ResizeObserver(entries => {
for (let entry of entries) {
clientWidth.value = entry.contentRect.width;
}
});
if (mainRef.value) {
observer.observe(mainRef.value);
}
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.miao-item { .miao-item {
cursor: pointer; cursor: pointer;
width: calc(100% - 20px); width: calc(100% - 20px);
min-width: 250px; min-width: 170px;
// aspect-ratio: 66/9; // aspect-ratio: 66/9;
height: 50px; height: 50px;
position: relative; position: relative;
@@ -187,7 +228,7 @@ const handleDragStart = (event: DragEvent) => {
border-radius: 7px; border-radius: 7px;
display: flex; display: flex;
align-items: center; align-items: center;
transition: background-color 0.3s; transition: background-color 0.3s ease;
.item-main-front { .item-main-front {
height: 100%; height: 100%;
@@ -200,6 +241,7 @@ const handleDragStart = (event: DragEvent) => {
margin-left: 10px; margin-left: 10px;
height: 80%; height: 80%;
color: #00403e; color: #00403e;
transition: color 0.15s ease-out;
} }
.item-main-info { .item-main-info {
@@ -209,7 +251,18 @@ const handleDragStart = (event: DragEvent) => {
flex-direction: column; flex-direction: column;
&-name { &-name {
position: relative;
user-select: none; user-select: none;
display: flex;
align-items: center;
margin-top: 3px;
&-icon {
color: #00403e;
width: 20px;
margin-right: 5px;
transition: color 0.15s ease-out;
}
} }
&-date { &-date {
@@ -252,5 +305,11 @@ const handleDragStart = (event: DragEvent) => {
background-color: #d2d2d2; background-color: #d2d2d2;
} }
} }
.item-main-selected {
background-color: rgba(0, 128, 207, 0.32);
&:hover {
background-color: rgba(0, 128, 207, 0.64);;
}
}
} }
</style> </style>
+2
View File
@@ -70,8 +70,10 @@ defineExpose({ popup });
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
flex-wrap: wrap;
.popup-button-container-button { .popup-button-container-button {
margin-top: 3px;
&:not(:last-child) { &:not(:last-child) {
margin-right: 5px; margin-right: 5px;
} }
+117 -111
View File
@@ -1,18 +1,12 @@
<template> <template>
<miao-drop-handler <miao-drop-handler @on-virtual-directory="handleDropVDirectory" @on-files="handleDropFiles"
@on-virtual-directory="handleDropVDirectory"
@on-files="handleDropFiles"
@on-virtual-files="handleDropVirtualFiles"> @on-virtual-files="handleDropVirtualFiles">
<div class="miao-directory-item-container" ref="rootDomRef"> <div class="miao-directory-item-container" ref="rootDomRef">
<div <div class="container-top" :style="{
class="container-top" backgroundColor:
:style="{ index % 2 === 0 ? '#f8f8f8' : 'rgb(240 240 240)'
backgroundColor: }">
index % 2 === 0 ? '#f8f8f8' : 'rgb(240 240 240)' <div class="container-top-colorfulBar" :style="{ backgroundColor: props.color }"></div>
}">
<div
class="container-top-colorfulBar"
:style="{ backgroundColor: props.color }"></div>
<div class="container-top-breadcrumb"> <div class="container-top-breadcrumb">
<!-- <n-scrollbar x-scrollable> --> <!-- <n-scrollbar x-scrollable> -->
<div class="container-top-breadcrumb-container"> <div class="container-top-breadcrumb-container">
@@ -22,10 +16,8 @@
<CloudOutline /> <CloudOutline />
</n-icon> </n-icon>
</n-breadcrumb-item> </n-breadcrumb-item>
<n-breadcrumb-item <n-breadcrumb-item v-for="dir in currentDirectory?.getParents"
v-for="dir in currentDirectory?.getParents" @click="setCurrentDirectory(dir)" :clickable="true">
@click="setCurrentDirectory(dir)"
:clickable="true">
<div>{{ dir.name }}</div> <div>{{ dir.name }}</div>
</n-breadcrumb-item> </n-breadcrumb-item>
</n-breadcrumb> </n-breadcrumb>
@@ -33,87 +25,60 @@
<!-- </n-scrollbar> --> <!-- </n-scrollbar> -->
</div> </div>
<div class="container-top-tools"> <div class="container-top-tools">
<n-icon <n-icon class="container-top-tools-item" size="15" @click="handleBack">
class="container-top-tools-item"
size="15"
@click="handleBack">
<ChevronBackOutline /> <ChevronBackOutline />
</n-icon> </n-icon>
<n-icon <n-icon class="container-top-tools-item" size="14" @click="handleReload">
class="container-top-tools-item" <ReloadOutline class="container-top-tools-item-reload" />
size="14"
@click="handleReload">
<ReloadOutline
class="container-top-tools-item-reload" />
</n-icon> </n-icon>
<n-icon <n-icon class="container-top-tools-item" size="20" @click="emit('exit')">
class="container-top-tools-item"
size="20"
@click="emit('exit')">
<CloseOutline /> <CloseOutline />
</n-icon> </n-icon>
</div> </div>
</div> </div>
<div <div class="container-items" :style="{
class="container-items" backgroundColor:
:style="{ index % 2 === 0 ? '#f8f8f8' : 'rgb(240 240 240)'
backgroundColor: }">
index % 2 === 0 ? '#f8f8f8' : 'rgb(240 240 240)'
}">
<n-scrollbar> <n-scrollbar>
<transition-group name="dirItem"> <transition-group name="dirItem">
<miaoDirectoryItem <miaoDirectoryItem v-for="dir in showData_directory" :item="dir" :key="dir.uid"
v-for="dir in showData_directory" :color="props.color" :selected="selectedItem.value.includes(dir)"
@click="setCurrentDirectory(dir)" @delete="handleItemDelete(dir)" @click="handleItemClick(dir)"
:item="dir" @drag-start="handleItemDragStart" @rename="handleItemRename(dir)"
:key="dir.uid" @on-selected="handleItemSelect(dir)" />
:color="props.color"
@delete="handleItemDelete(dir)"
@drag-start="handleItemDragStart"
@rename="handleItemRename(dir)" />
</transition-group> </transition-group>
<transition-group name="dirItem"> <transition-group name="dirItem">
<miaoDirectoryItem <miaoDirectoryItem v-for="file in showData_files" :item="file" :key="file.uid"
v-for="file in showData_files" :color="props.color" :selected="selectedItem.value.includes(file)"
:item="file" @click="handleItemClick(file)" @download="handleItemDownload(file)"
:key="file.uid" @delete="handleItemDelete(file)" @drag-start="handleItemDragStart"
@click="openUrl(`${baseUrl}${api.get}${file.path}`)" @rename="handleItemRename(file)" @on-selected="handleItemSelect(file)" />
@download="handleItemDownload(file)"
@delete="handleItemDelete(file)"
@drag-start="handleItemDragStart"
@rename="handleItemRename(file)"
:color="props.color" />
</transition-group> </transition-group>
</n-scrollbar> </n-scrollbar>
</div> </div>
<div <div class="container-bottom" :style="{
class="container-bottom" backgroundColor:
:style="{ index % 2 === 1 ? 'rgb(61 61 61)' : 'rgb(162 162 162)'
backgroundColor: }">
index % 2 === 1 ? 'rgb(61 61 61)' : 'rgb(162 162 162)'
}">
<div class="container-bottom-item"> <div class="container-bottom-item">
<div class="container-bottom-item-icon"> <div class="container-bottom-item-icon">
<add-outline <add-outline :style="{
:style="{ color:
color: index % 2 === 0
index % 2 === 0 ? 'rgb(0 0 0)'
? 'rgb(0 0 0)' : 'rgb(255 255 255)'
: 'rgb(255 255 255)' }" @click="handleAddNewItem" />
}"
@click="handleAddNewItem" />
</div> </div>
</div> </div>
<div class="container-bottom-item"> <div class="container-bottom-item">
<div class="container-bottom-item-icon"> <div class="container-bottom-item-icon">
<cloud-upload-outline <cloud-upload-outline :style="{
:style="{ color:
color: index % 2 === 0
index % 2 === 0 ? 'rgb(0 0 0)'
? 'rgb(0 0 0)' : 'rgb(255 255 255)'
: 'rgb(255 255 255)' }" @click="handlePickFilesUpload" />
}"
@click="handlePickFilesUpload" />
</div> </div>
</div> </div>
</div> </div>
@@ -125,9 +90,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { NBreadcrumbItem, NBreadcrumb, NIcon, NScrollbar } from 'naive-ui' import { NBreadcrumbItem, NBreadcrumb, NIcon, NScrollbar } from 'naive-ui'
import type { file, directory } from '@/types/type.ts'
import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory' import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory'
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, reactive, ref } from 'vue'
import Config from '@/config' import Config from '@/config'
import miaoMask from '@/components/miaoMask.vue' import miaoMask from '@/components/miaoMask.vue'
import miaoDirectoryItem from '@/components/miaoDirectory/miaoDirectoryItem.vue' import miaoDirectoryItem from '@/components/miaoDirectory/miaoDirectoryItem.vue'
@@ -146,6 +110,7 @@ import {
import useUploadQueue from '@/hooks/useUploadQueue' import useUploadQueue from '@/hooks/useUploadQueue'
import useVirtualPages from '@/hooks/useVirtualPages' import useVirtualPages from '@/hooks/useVirtualPages'
import { filePicker } from '@/hooks/miaoTools' import { filePicker } from '@/hooks/miaoTools'
import { uniq } from 'lodash'
const { baseUrl, api } = Config const { baseUrl, api } = Config
const dataBus = useDataBus() const dataBus = useDataBus()
@@ -166,6 +131,34 @@ const popupInput = ref()
// 是否显示模态框 // 是否显示模态框
const showModel = ref(0) const showModel = ref(0)
class SelectedItem {
constructor() {
this._selectedItem = []
}
_selectedItem: (VirtualDirectory | VirtualFile)[]
push(item: VirtualDirectory | VirtualFile) {
this._selectedItem = [item, ...this._selectedItem]
}
remove(item: VirtualDirectory | VirtualFile) {
this._selectedItem = this._selectedItem.filter(v => v !== item)
}
clear() {
this._selectedItem = []
}
// 保证内容仍然存在于dir中且各个元素不重复
get value() {
return uniq(this._selectedItem).filter(v => {
if (currentDirectory.value === undefined) {
return false
}
// @ts-ignore
return [...currentDirectory.value?.directorys, ...currentDirectory.value?.files].includes(v)
})
}
}
// 已选择的item,用于多选功能
const selectedItem = reactive(new SelectedItem())
const currentObjects = defineModel<VirtualDirectory[]>('currentObjects', { const currentObjects = defineModel<VirtualDirectory[]>('currentObjects', {
required: true required: true
}) })
@@ -211,17 +204,9 @@ const setCurrentDirectory = async (virtualDirectory: VirtualDirectory) => {
virtualDirectory.files === undefined || virtualDirectory.files === undefined ||
virtualDirectory.directorys === undefined virtualDirectory.directorys === undefined
) { ) {
const data: (file | directory)[] = await miaoFetchApi.get( await virtualDirectory.update()
virtualDirectory
)
virtualDirectory.updateContent(data)
} else { } else {
;(async () => { virtualDirectory.update()
const data: (file | directory)[] = await miaoFetchApi.get(
virtualDirectory
)
virtualDirectory.updateContent(data)
})()
} }
currentObjects.value[0] = virtualDirectory currentObjects.value[0] = virtualDirectory
} }
@@ -255,22 +240,38 @@ const handleReload = () => {
reload() reload()
} }
const handleItemClick = (item: VirtualDirectory | VirtualFile) => {
if (selectedItem.value.length > 0) {
handleItemSelect(item)
} else {
if (item.type === 'file') {
openUrl(`${baseUrl}${api.get}${item.path}`)
} else if (item.type === 'directory') {
setCurrentDirectory(item)
}
}
}
const handleItemDownload = (item: VirtualFile) => { const handleItemDownload = (item: VirtualFile) => {
openUrl(`${baseUrl}${api.get}${item.path}`, { openUrl(`${baseUrl}${api.get}${item.path}`, {
download: item.name download: item.name
}) })
} }
const handleItemDelete = async (item: VirtualDirectory | VirtualFile) => { const handleItemDelete = async (_item: VirtualDirectory | VirtualFile) => {
const { response } = miaoFetchApi.delete(item, { for (let item of uniq([...selectedItem.value, _item])) {
retry: 5 ; (async () => {
}) const { response } = miaoFetchApi.delete(item, {
// todo: 将删除事件统一用一个事件管理中心管理 retry: 5
const id = (await response).eventId })
const { response: res } = miaoFetchApi.query(id) // todo: 将删除事件统一用一个事件管理中心管理
const eventResult = await res const id = (await response).eventId
if (eventResult.status === 'success') { const { response: res } = miaoFetchApi.query(id)
reload() const eventResult = await res
if (eventResult.status === 'success') {
reload()
}
})()
} }
} }
@@ -314,11 +315,10 @@ const handleItemDragStart = async (
_event: DragEvent, _event: DragEvent,
item: VirtualDirectory | VirtualFile item: VirtualDirectory | VirtualFile
) => { ) => {
if (item.type === 'file') { const items = uniq([item, ...selectedItem.value])
dataBus.set('dragData_vFiles', [item]) dataBus.set('dragData_vFiles', items.filter(v => v.type === 'file'))
} else { dataBus.set('dragData_vDirectory', items.filter(v => v.type === 'directory'))
dataBus.set('dragData_vDirectory', [item]) selectedItem.clear()
}
} }
const handleDropFiles = async (files: File[]) => { const handleDropFiles = async (files: File[]) => {
@@ -338,7 +338,7 @@ const handleDropVDirectory = async (vDirs: VirtualDirectory[]) => {
} }
} }
for (let dir of vDirs) { for (let dir of vDirs) {
;(async () => { ; (async () => {
const _from = dir.parent const _from = dir.parent
const { response } = miaoFetchApi.cut(dir, currentDirectory.value) const { response } = miaoFetchApi.cut(dir, currentDirectory.value)
const id = (await response).eventId const id = (await response).eventId
@@ -350,7 +350,6 @@ const handleDropVDirectory = async (vDirs: VirtualDirectory[]) => {
} }
})() })()
} }
// setCurrentDirectory(vDirs[0])
} }
const handleDropVirtualFiles = (files: VirtualFile[]) => { const handleDropVirtualFiles = (files: VirtualFile[]) => {
@@ -358,7 +357,7 @@ const handleDropVirtualFiles = (files: VirtualFile[]) => {
return return
} }
for (let file of files) { for (let file of files) {
;(async () => { ; (async () => {
const _from = file.parent const _from = file.parent
const { response } = miaoFetchApi.cut(file, currentDirectory.value) const { response } = miaoFetchApi.cut(file, currentDirectory.value)
const id = (await response).eventId const id = (await response).eventId
@@ -413,7 +412,6 @@ const handleAddNewItem = async () => {
if (popupRes.key === 'cancel' || popupRes.text === '') { if (popupRes.key === 'cancel' || popupRes.text === '') {
return return
} }
console.log(popupRes)
const _currDir = currentDirectory.value const _currDir = currentDirectory.value
const name = popupRes.text const name = popupRes.text
const opType: 'file' | 'dir' = popupRes.key const opType: 'file' | 'dir' = popupRes.key
@@ -430,6 +428,14 @@ const handleAddNewItem = async () => {
} }
} }
} }
const handleItemSelect = (item: VirtualDirectory | VirtualFile) => {
if (!selectedItem.value.includes(item)) {
selectedItem.push(item)
} else {
selectedItem.remove(item)
}
}
onMounted(async () => { onMounted(async () => {
await setCurrentDirectory(currentDirectory.value) await setCurrentDirectory(currentDirectory.value)
}) })
@@ -443,7 +449,7 @@ $bottom-bar-height: 60px;
position: relative; position: relative;
height: 100%; height: 100%;
width: inherit; width: inherit;
min-width: 266px; // min-width: 236px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
@@ -557,6 +563,7 @@ $bottom-bar-height: 60px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
.container-bottom-item-icon { .container-bottom-item-icon {
svg { svg {
width: 30px; width: 30px;
@@ -595,5 +602,4 @@ $bottom-bar-height: 60px;
// position: absolute; // position: absolute;
// } // }
// } // }
// } // }</style>
</style>