feat: 1.给插件新增一些属性。2.重构图片浏览组件,根据时间分组浏览

This commit is contained in:
539943419 committed 2024-09-02 17:59:49 +08:00
1 parent 78dac7482c
commit 6b8f7aa6b4
10 files changed
+1111 -757

No files matched your search

+455 -417
View File
@@ -1,37 +1,59 @@
<template>
<miao-drop-handler @on-virtual-directory="handleDropVDirectory" @on-files="handleDropFiles"
@on-virtual-files="handleDropVirtualFiles">
<div class="miao-directory-item-container" ref="rootDomRef">
<!-- 顶部面包屑导航和工具栏 -->
<miao-directory-top :color="props.color" :current-directory="currentDirectory" @back="handleBack"
:open-menu-option="topBarMenuOption" @reload="handleReload" @exit="emit('exit')" :index="index"
@set-current-directory="setCurrentDirectory" @search="handleSearch" @menu-select="handleTopBarMenuSelect" />
<miao-drop-handler
@on-virtual-directory="handleDropVDirectory"
@on-files="handleDropFiles"
@on-virtual-files="handleDropVirtualFiles">
<div class="miao-directory-item-container" ref="rootDomRef">
<!-- 顶部面包屑导航和工具栏 -->
<miao-directory-top
:color="props.color"
:current-directory="currentDirectory"
@back="handleBack"
:open-menu-option="topBarMenuOption"
@reload="handleReload"
@exit="emit('exit')"
:index="index"
@set-current-directory="setCurrentDirectory"
@search="handleSearch"
@menu-select="handleTopBarMenuSelect" />
<!-- 文件和文件夹列表 -->
<miao-directory-items ref='miaoDirectoryItemRef' :color="props.color" :showData_directory="showData_directory"
:showData_files="showData_files" :selected-item="selectedItem.value" :index="index"
@item-click="handleItemClick" @item-download="handleItemDownload" @item-delete="handleItemDelete"
@item-drag-start="handleItemDragStart" @item-rename="handleItemRename" @item-select="handleItemSelect" />
<!-- 文件和文件夹列表 -->
<miao-directory-items
ref="miaoDirectoryItemRef"
:color="props.color"
:showData_directory="showData_directory"
:showData_files="showData_files"
:selected-item="selectedItem.value"
:index="index"
@item-click="handleItemClick"
@item-download="handleItemDownload"
@item-delete="handleItemDelete"
@item-drag-start="handleItemDragStart"
@item-rename="handleItemRename"
@item-select="handleItemSelect" />
<!-- 底部工具栏 -->
<miao-directory-bottom :index="index" @add-new-item="handleAddNewItem" v-if="false"
@pick-files-upload="handlePickFilesUpload" />
<!-- 底部工具栏 -->
<miao-directory-bottom
:index="index"
@add-new-item="handleAddNewItem"
v-if="false"
@pick-files-upload="handlePickFilesUpload" />
<miao-mask v-model:show="showModel"></miao-mask>
<miao-popup-input ref="popupInput"></miao-popup-input>
</div>
</miao-drop-handler>
<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 { computed, h, nextTick, onMounted, reactive, ref } from 'vue'
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory'
import Config from '@/config'
import useMiaoFetchApi from '@/hooks/useMiaoFetchApi'
import useDataBus from '@/hooks/useDataBus'
import useUploadQueue from '@/hooks/useUploadQueue'
import useVirtualPages from '@/hooks/useVirtualPages'
import { filePicker } from '@/hooks/miaoTools'
import { filePicker, renderIcon } from '@/hooks/miaoTools'
import { uniq } from 'lodash'
// 导入子组件
@@ -41,13 +63,16 @@ import MiaoDirectoryBottom from '@/components/miaoDirectory/miaoDirectoryBottom.
import MiaoMask from '@/components/miaoMask.vue'
import MiaoPopupInput from '@/components/miaoPopupInput.vue'
import MiaoDropHandler from '@/components/miaoDropHandler.vue'
import { NIcon } from 'naive-ui'
import {
CloseOutline, SearchOutline, FilterOutline, CheckmarkDoneOutline,
DocumentText, ExtensionPuzzleOutline,
FileTray,
AddOutline,
CloudUploadOutline
CloseOutline,
SearchOutline,
FilterOutline,
CheckmarkDoneOutline,
DocumentText,
ExtensionPuzzleOutline,
FileTray,
AddOutline,
CloudUploadOutline
} from '@vicons/ionicons5'
import usePluginCenter from '@/hooks/usePluginCenter'
@@ -59,12 +84,12 @@ const miaoFetchApi = useMiaoFetchApi()
const pluginCenter = usePluginCenter()
const props = defineProps<{
color: string
id: number
color: string
id: number
}>()
const emit = defineEmits<{
exit: []
exit: []
}>()
const popupInput = ref<InstanceType<typeof MiaoPopupInput>>()
@@ -76,464 +101,477 @@ const showFiles = ref<boolean>(true)
const miaoDirectoryItemRef = ref()
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() {
const _filterList = [
...(currentDirectory.value?.directories ?? []),
...(currentDirectory.value?.files ?? []),
]
return uniq(this._selectedItem).filter((v) => {
if (currentDirectory.value === undefined) {
return false
}
// @ts-ignore
return _filterList.includes(v)
})
}
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() {
const _filterList = [
...(currentDirectory.value?.directories ?? []),
...(currentDirectory.value?.files ?? [])
]
return uniq(this._selectedItem).filter((v) => {
if (currentDirectory.value === undefined) {
return false
}
// @ts-ignore
return _filterList.includes(v)
})
}
}
// 已选择的item,用于多选功能
const selectedItem = reactive(new SelectedItem())
const currentDirectories = defineModel<VirtualDirectory[]>('currentDirectories', {
required: true,
})
const currentDirectories = defineModel<VirtualDirectory[]>(
'currentDirectories',
{
required: true
}
)
// 目前展示的Dir
const currentDirectory = computed(() => {
return currentDirectories.value[0]
return currentDirectories.value[0]
})
// 当前组件在views中的序号
const index = computed(() => {
return views.getIndex(props.id)
return views.getIndex(props.id)
})
// 用于展示的数据,后续会增加排序等操作
const showData_directory = computed<VirtualDirectory[]>(() => {
if (showDirs.value === false) {
return []
}
let data = currentDirectory.value?.directories
? [...currentDirectory.value?.directories]
: []
if (searchText.value !== '') {
data = data.filter(vd => vd.name.includes(searchText.value))
}
return data
if (showDirs.value === false) {
return []
}
let data = currentDirectory.value?.directories
? [...currentDirectory.value?.directories]
: []
if (searchText.value !== '') {
data = data.filter((vd) => vd.name.includes(searchText.value))
}
return data
})
const showData_files = computed<VirtualFile[]>(() => {
if (showFiles.value === false) {
return []
}
let data = currentDirectory.value?.files ? [...currentDirectory.value?.files] : []
if (searchText.value !== '') {
data = data.filter(vd => vd.name.includes(searchText.value))
}
return data
if (showFiles.value === false) {
return []
}
let data = currentDirectory.value?.files
? [...currentDirectory.value?.files]
: []
if (searchText.value !== '') {
data = data.filter((vd) => vd.name.includes(searchText.value))
}
return data
})
const topBarMenuOption = computed(() => {
return [
{
label: '新建',
key: 'create',
icon: renderIcon(AddOutline)
},
{
label: '上传',
key: 'upload',
icon: renderIcon(CloudUploadOutline)
},
{
label: '搜索',
key: 'search',
icon: renderIcon(SearchOutline)
},
searchText.value !== '' ? {
label: '重置搜索',
key: 'searchReset',
icon: renderIcon(SearchOutline)
} : undefined,
selectedItem.value.length !== 0 ? {
label: '重置多选',
key: 'selectedItemReset',
icon: renderIcon(CheckmarkDoneOutline)
} : undefined,
{
label: '筛选',
key: 'filter',
children: [
return [
{
label: `${showDirs.value ? '隐藏' : '显示'}文件夹`,
key: 'showDirs',
icon: renderIcon(FileTray)
label: '新建',
key: 'create',
icon: renderIcon(AddOutline)
},
{
label: `${showFiles.value ? '隐藏' : '显示'}文件`,
key: 'showFiles',
icon: renderIcon(DocumentText)
label: '上传',
key: 'upload',
icon: renderIcon(CloudUploadOutline)
},
],
icon: renderIcon(FilterOutline)
},
{
label: '插件',
key: 'plugin',
children: [
...pluginCenter.getUsablePlugin([currentDirectories.value[0]], []).map(v => ({
label: `${v.name}`,
key: `plugin:${v.key}`
})),
],
icon: renderIcon(ExtensionPuzzleOutline)
},
{
label: '关闭',
key: 'close',
icon: renderIcon(CloseOutline)
},
].filter(v => v !== undefined)
{
label: '搜索',
key: 'search',
icon: renderIcon(SearchOutline)
},
searchText.value !== ''
? {
label: '重置搜索',
key: 'searchReset',
icon: renderIcon(SearchOutline)
}
: undefined,
selectedItem.value.length !== 0
? {
label: '重置多选',
key: 'selectedItemReset',
icon: renderIcon(CheckmarkDoneOutline)
}
: undefined,
{
label: '筛选',
key: 'filter',
children: [
{
label: `${showDirs.value ? '隐藏' : '显示'}文件夹`,
key: 'showDirs',
icon: renderIcon(FileTray)
},
{
label: `${showFiles.value ? '隐藏' : '显示'}文件`,
key: 'showFiles',
icon: renderIcon(DocumentText)
}
],
icon: renderIcon(FilterOutline)
},
{
label: '插件',
key: 'plugin',
children: [
...pluginCenter
.getUsablePlugin([currentDirectories.value[0]], [])
.map((v) => ({
label: `${v.name}`,
key: `plugin:${v.key}`,
icon: v.icon ? renderIcon(v.icon) : undefined
}))
],
icon: renderIcon(ExtensionPuzzleOutline)
},
{
label: '关闭',
key: 'close',
icon: renderIcon(CloseOutline)
}
].filter((v) => v !== undefined)
})
const renderIcon = (icon: any) => {
return () => {
return h(NIcon, null, {
default: () => h(icon)
})
}
}
// 设置显示的dir
const setCurrentDirectory = async (virtualDirectory: VirtualDirectory) => {
// 若没有缓存数据则同步刷新,有缓存数据则异步刷新
if (virtualDirectory.files === undefined || virtualDirectory.directories === undefined) {
await virtualDirectory.update()
} else {
virtualDirectory.update()
}
if (currentDirectories.value[0] !== virtualDirectory) {
selectedItem.clear()
handleSearchReset()
nextTick(() => {
miaoDirectoryItemRef.value.scrollTo({
top: 0,
behavior: "instant"
})
})
}
currentDirectories.value[0] = virtualDirectory
// 若没有缓存数据则同步刷新,有缓存数据则异步刷新
if (
virtualDirectory.files === undefined ||
virtualDirectory.directories === undefined
) {
await virtualDirectory.update()
} else {
virtualDirectory.update()
}
if (currentDirectories.value[0] !== virtualDirectory) {
selectedItem.clear()
handleSearchReset()
nextTick(() => {
miaoDirectoryItemRef.value.scrollTo({
top: 0,
behavior: 'instant'
})
})
}
currentDirectories.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 handleTopBarMenuSelect = (key: string) => {
if (key === 'close') {
emit('exit')
} else if (key === 'search') {
handleSearch()
} else if (key === 'upload') {
handlePickFilesUpload()
} else if (key === 'create') {
handleAddNewItem()
} else if (key === 'searchReset') {
handleSearchReset()
} else if (key === 'selectedItemReset') {
selectedItem.clear()
} else if (key === 'showDirs') {
showDirs.value = !showDirs.value
} else if (key === 'showFiles') {
showFiles.value = !showFiles.value
} else if (key.startsWith('plugin:')) {
const pluginName = key.split(':')[1]
pluginCenter.usePlugin(pluginName, [currentDirectory.value], [])
}
if (key === 'close') {
emit('exit')
} else if (key === 'search') {
handleSearch()
} else if (key === 'upload') {
handlePickFilesUpload()
} else if (key === 'create') {
handleAddNewItem()
} else if (key === 'searchReset') {
handleSearchReset()
} else if (key === 'selectedItemReset') {
selectedItem.clear()
} else if (key === 'showDirs') {
showDirs.value = !showDirs.value
} else if (key === 'showFiles') {
showFiles.value = !showFiles.value
} else if (key.startsWith('plugin:')) {
const pluginName = key.split(':')[1]
pluginCenter.usePlugin(pluginName, [currentDirectory.value], [])
}
}
const handleBack = () => {
if (currentDirectory.value?.parent) {
setCurrentDirectory(currentDirectory.value.parent)
}
if (currentDirectory.value?.parent) {
setCurrentDirectory(currentDirectory.value.parent)
}
}
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)
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) => {
openUrl(`${baseUrl}${api.get}${item.path}`, {
download: item.name,
})
}
const handleItemDelete = async (_item: VirtualDirectory | VirtualFile) => {
for (let item of uniq([...selectedItem.value, _item])) {
; (async () => {
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 handleItemRename = async (item: VirtualDirectory | VirtualFile) => {
const popupRes = await popupInput.value?.popup({
title: '重命名',
inputValue: item.name,
inputProps: {
placeholder: item.name,
},
options: [
{
label: '取消',
key: false,
type: 'error',
},
{
label: '确认',
key: true,
type: 'success',
},
],
})
if (popupRes?.key === false) {
return
}
const newName = popupRes?.text
if (!newName) {
return
}
const { response } = miaoFetchApi.rename(item, newName, {
retry: 5,
})
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,
) => {
const items = uniq([item, ...selectedItem.value])
dataBus.set('dragData_vFiles', items.filter((v) => v.type === 'file'))
dataBus.set('dragData_vDirectory', items.filter((v) => v.type === 'directory'))
// selectedItem.clear()
}
const handleDropFiles = async (files: File[]) => {
currentDirectory.value &&
uploadQueue.push(files, currentDirectory.value, {
onFinish() {
reload()
},
openUrl(`${baseUrl}${api.get}${item.path}`, {
download: item.name
})
}
const handleDropVDirectory = async (vDirs: VirtualDirectory[]) => {
// 不能移到自己或自己的子文件夹内
for (let vDir of vDirs) {
if (currentDirectory.value.getParents.includes(vDir)) {
return
const handleItemDelete = async (_item: VirtualDirectory | VirtualFile) => {
for (let item of uniq([...selectedItem.value, _item])) {
;(async () => {
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()
}
})()
}
if (vDir.parent === currentDirectory.value) {
return
}
const handleItemRename = async (item: VirtualDirectory | VirtualFile) => {
const popupRes = await popupInput.value?.popup({
title: '重命名',
inputValue: item.name,
inputProps: {
placeholder: item.name
},
options: [
{
label: '取消',
key: false,
type: 'error'
},
{
label: '确认',
key: true,
type: 'success'
}
]
})
if (popupRes?.key === false) {
return
}
}
for (let dir of vDirs) {
; (async () => {
const _from = dir.parent
const { response } = miaoFetchApi.cut(dir, currentDirectory.value)
const id = (await response).eventId
const { response: res } = miaoFetchApi.query(id)
const eventResult = await res
if (eventResult.status === 'success') {
const newName = popupRes?.text
if (!newName) {
return
}
const { response } = miaoFetchApi.rename(item, newName, {
retry: 5
})
const id = (await response).eventId
const { response: res } = miaoFetchApi.query(id)
const eventResult = await res
if (eventResult.status === 'success') {
reload()
_from && _from.update()
}
})()
}
}
}
const handleItemDragStart = async (
_event: DragEvent,
item: VirtualDirectory | VirtualFile
) => {
const items = uniq([item, ...selectedItem.value])
dataBus.set(
'dragData_vFiles',
items.filter((v) => v.type === 'file')
)
dataBus.set(
'dragData_vDirectory',
items.filter((v) => v.type === 'directory')
)
// selectedItem.clear()
}
const handleDropFiles = async (files: File[]) => {
currentDirectory.value &&
uploadQueue.push(files, currentDirectory.value, {
onFinish() {
reload()
}
})
}
const handleDropVDirectory = async (vDirs: VirtualDirectory[]) => {
// 不能移到自己或自己的子文件夹内
for (let vDir of vDirs) {
if (currentDirectory.value.getParents.includes(vDir)) {
return
}
if (vDir.parent === currentDirectory.value) {
return
}
}
for (let dir of vDirs) {
;(async () => {
const _from = dir.parent
const { response } = miaoFetchApi.cut(dir, currentDirectory.value)
const id = (await response).eventId
const { response: res } = miaoFetchApi.query(id)
const eventResult = await res
if (eventResult.status === 'success') {
reload()
_from && _from.update()
}
})()
}
}
const handleDropVirtualFiles = (files: VirtualFile[]) => {
if (files[0].parent === currentDirectory.value) {
return
}
for (let file of files) {
; (async () => {
const _from = file.parent
const { response } = miaoFetchApi.cut(file, currentDirectory.value)
const id = (await response).eventId
const { response: res } = miaoFetchApi.query(id)
const eventResult = await res
if (eventResult.status === 'success') {
reload()
_from.update()
}
})()
}
if (files[0].parent === currentDirectory.value) {
return
}
for (let file of files) {
;(async () => {
const _from = file.parent
const { response } = miaoFetchApi.cut(file, currentDirectory.value)
const id = (await response).eventId
const { response: res } = miaoFetchApi.query(id)
const eventResult = await res
if (eventResult.status === 'success') {
reload()
_from.update()
}
})()
}
}
const handlePickFilesUpload = async () => {
const files = await filePicker({
multiple: true,
})
const _currDir = currentDirectory.value
uploadQueue.push(files, _currDir, {
onFinish() {
_currDir.update()
},
})
const files = await filePicker({
multiple: true
})
const _currDir = currentDirectory.value
uploadQueue.push(files, _currDir, {
onFinish() {
_currDir.update()
}
})
}
const handleAddNewItem = async () => {
const popupRes = await popupInput.value?.popup({
title: '新建文件/文件夹',
inputProps: {
placeholder: '',
},
options: [
{
label: '取消',
key: 'cancel',
type: 'error',
},
{
label: '文件',
key: 'file',
type: 'success',
},
{
label: '文件夹',
key: 'dir',
type: 'success',
},
],
})
// todo: 上传前如重名文件夹等等的错误提前检查
if (popupRes?.key === 'cancel' || popupRes?.text === '') {
return
}
const _currDir = currentDirectory.value
const name = popupRes?.text
if (!name) {
return
}
// @ts-ignore
const opType: 'file' | 'dir' = popupRes?.key
if (opType === 'dir') {
const res = await miaoFetchApi.mkdir(currentDirectory.value, name)
if (res.message === 'success') {
_currDir.update()
const popupRes = await popupInput.value?.popup({
title: '新建文件/文件夹',
inputProps: {
placeholder: ''
},
options: [
{
label: '取消',
key: 'cancel',
type: 'error'
},
{
label: '文件',
key: 'file',
type: 'success'
},
{
label: '文件夹',
key: 'dir',
type: 'success'
}
]
})
// todo: 上传前如重名文件夹等等的错误提前检查
if (popupRes?.key === 'cancel' || popupRes?.text === '') {
return
}
} else {
const { response } = miaoFetchApi.mkFile(currentDirectory.value, name)
const res = await response
if (res.message === 'success') {
_currDir.update()
const _currDir = currentDirectory.value
const name = popupRes?.text
if (!name) {
return
}
// @ts-ignore
const opType: 'file' | 'dir' = popupRes?.key
if (opType === 'dir') {
const res = await miaoFetchApi.mkdir(currentDirectory.value, name)
if (res.message === 'success') {
_currDir.update()
}
} else {
const { response } = miaoFetchApi.mkFile(currentDirectory.value, name)
const res = await response
if (res.message === 'success') {
_currDir.update()
}
}
}
}
const handleItemSelect = (item: VirtualDirectory | VirtualFile) => {
if (!selectedItem.value.includes(item)) {
selectedItem.push(item)
} else {
selectedItem.remove(item)
}
if (!selectedItem.value.includes(item)) {
selectedItem.push(item)
} else {
selectedItem.remove(item)
}
}
const handleSearch = async () => {
const popupRes = await popupInput.value?.popup({
title: '搜索当前文件夹',
inputValue: searchText.value,
inputProps: {
placeholder: '',
},
options: [
{
label: '取消',
key: 'cancel',
type: 'default',
},
{
label: '重置',
key: 'reset',
type: 'default',
},
{
label: '搜索',
key: 'search',
type: 'success',
},
],
})
if (!popupRes) {
return
}
if (popupRes.key === 'search') {
searchText.value = popupRes.text
} else if (popupRes.key === 'reset') {
handleSearchReset()
}
const popupRes = await popupInput.value?.popup({
title: '搜索当前文件夹',
inputValue: searchText.value,
inputProps: {
placeholder: ''
},
options: [
{
label: '取消',
key: 'cancel',
type: 'default'
},
{
label: '重置',
key: 'reset',
type: 'default'
},
{
label: '搜索',
key: 'search',
type: 'success'
}
]
})
if (!popupRes) {
return
}
if (popupRes.key === 'search') {
searchText.value = popupRes.text
} else if (popupRes.key === 'reset') {
handleSearchReset()
}
}
const handleSearchReset = () => {
searchText.value = ''
searchText.value = ''
}
onMounted(async () => {
await setCurrentDirectory(currentDirectory.value)
await setCurrentDirectory(currentDirectory.value)
})
</script>
@@ -542,12 +580,12 @@ $top-height: 30px;
$bottom-bar-height: 60px;
.miao-directory-item-container {
position: relative;
height: 100%;
width: inherit;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 170px;
position: relative;
height: 100%;
width: inherit;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 170px;
}
</style>
</style>
+145 -33
View File
@@ -1,33 +1,61 @@
<template>
<miao-drop-handler @on-virtual-directory="handleDrop" @on-virtual-files="handleDrop">
<miao-drop-handler
@on-virtual-directory="handleDrop"
@on-virtual-files="handleDrop">
<div class="miaoImage" ref="rootRef">
<n-scrollbar ref="NScrollbarRef">
<div class="miaoImage-container">
<div class="miaoImage-container-row" v-for="(src, index) in imageSrcList" :key="index">
<img :src="src" class="miaoImage-container-row-image" loading="lazy"
:class="activeImageIndex === index ? 'miaoImage-container-row-image-active' : ''"
@click="activeImageIndex = index" />
<div
class="miaoImage-container"
v-for="imageGroup in imageGroupList">
<div class="miaoImage-container-day">
<span class="miaoImage-container-day-text">
{{ imageGroup.day }}
</span>
</div>
<div
class="miaoImage-container-row"
v-for="vFile in imageGroup.items"
:key="vFile.id">
<img
:src="vFile.url"
class="miaoImage-container-row-image"
loading="lazy"
:class="
activeImage === vFile
? 'miaoImage-container-row-image-active'
: ''
"
@click="activeImage = vFile" />
</div>
</div>
</n-scrollbar>
<miao-mask :show="activeImageIndex !== -1" @click="activeImageIndex = -1" class="miaoImage-mask">
<ChevronBack class="miaoImage-mask-btn miaoImage-mask-btn-back" @click="handleActiveImageIndexBack" />
<img :src="imageSrcList[activeImageIndex]" class="miaoImage-mask-active"
@click="e => e.stopPropagation()" draggable="false">
<ChevronForward class="miaoImage-mask-btn miaoImage-mask-btn-forward"
@click="handleActiveImageIndexForward" />
<miao-mask
:show="activeImage !== undefined"
@click="activeImage = undefined"
class="miaoImage-mask">
<ChevronBack
class="miaoImage-mask-btn miaoImage-mask-btn-back"
@click="handleActiveImageBack" />
<img
:src="activeImage?.url"
class="miaoImage-mask-active"
@click="(e) => e.stopPropagation()"
draggable="false" />
<ChevronForward
class="miaoImage-mask-btn miaoImage-mask-btn-forward"
@click="handleActiveImageForward" />
</miao-mask>
</div>
</miao-drop-handler>
</template>
<script setup lang="ts">
import miaoMask from '@/components/miaoMask.vue';
import miaoDropHandler from '@/components/miaoDropHandler.vue';
import miaoMask from '@/components/miaoMask.vue'
import miaoDropHandler from '@/components/miaoDropHandler.vue'
import VirtualDirectory, { VirtualFile } from '@/class/VirtualDirectory'
import { uniq } from 'lodash';
import { nextTick, onMounted, ref } from 'vue'
import { NScrollbar } from 'naive-ui';
import { uniq } from 'lodash'
import { computed, nextTick, onMounted, ref } from 'vue'
import { NScrollbar } from 'naive-ui'
import { ChevronBack, ChevronForward } from '@vicons/ionicons5'
const imageSuffixList = [
@@ -49,38 +77,95 @@ const imageSuffixList = [
'avif'
]
const currentDirectories = defineModel<VirtualDirectory[]>('currentDirectories', {
required: true
})
const currentDirectories = defineModel<VirtualDirectory[]>(
'currentDirectories',
{
required: true
}
)
const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
required: true
})
const NScrollbarRef = ref()
const rootRef = ref<any>()
const imageSrcList = ref<string[]>([])
const imageFileList = ref<VirtualFile[]>([])
const activeImageIndex = ref(-1)
const imageGroupList = computed(() => {
return groupByDay(imageFileList.value)
})
const activeImage = ref<VirtualFile>()
const groupByDay = (array: VirtualFile[]) => {
const result = []
let currentDay = null
let currentGroup: VirtualFile[] = []
for (const item of array) {
const date = new Date(item.stats.mtimeMs)
const day = `${date.getFullYear()}年 ${String(
date.getMonth() + 1
).padStart(2, '0')}月 ${String(date.getDate()).padStart(2, '0')}日`
if (day !== currentDay) {
if (currentGroup.length > 0) {
result.push({
day: currentDay,
items: currentGroup
})
}
currentDay = day
currentGroup = [item]
} else {
currentGroup.push(item)
}
}
if (currentGroup.length > 0) {
result.push({
day: currentDay,
items: currentGroup
})
}
return result
}
const scrollToActiveImage = () => {
const dom = rootRef.value?.querySelector('.miaoImage-container-row-image-active')
const dom = rootRef.value?.querySelector(
'.miaoImage-container-row-image-active'
)
NScrollbarRef.value.scrollTo({
top: dom.offsetTop - 30,
behavior: "smooth"
behavior: 'smooth'
})
}
const handleActiveImageIndexBack = (e: any) => {
const handleActiveImageBack = (e: any) => {
e.stopPropagation()
activeImageIndex.value = (activeImageIndex.value - 1 + imageSrcList.value.length) % imageSrcList.value.length
if (!activeImage.value) {
return
}
const index = imageFileList.value.indexOf(activeImage.value)
activeImage.value =
imageFileList.value[
(index - 1 + imageFileList.value.length) %
imageFileList.value.length
]
nextTick(scrollToActiveImage)
}
const handleActiveImageIndexForward = (e: any) => {
const handleActiveImageForward = (e: any) => {
e.stopPropagation()
activeImageIndex.value = (activeImageIndex.value + 1 + imageSrcList.value.length) % imageSrcList.value.length
if (!activeImage.value) {
return
}
const index = imageFileList.value.indexOf(activeImage.value)
activeImage.value =
imageFileList.value[
(index + 1 + imageFileList.value.length) %
imageFileList.value.length
]
nextTick(scrollToActiveImage)
}
@@ -92,27 +177,43 @@ const handleDrop = (vItems: (VirtualFile | VirtualDirectory)[]) => {
// @ts-ignore
currentFiles.value = [...currentFiles, ...vItems]
}
vItems.forEach(v => {
vItems.forEach((v) => {
updateImageSrcList(v)
})
}
const updateImageSrcList = async (source: VirtualFile | VirtualDirectory) => {
let newSrcList: string[]
let newFileList: VirtualFile[]
if (source.type === 'directory') {
await source.update()
newSrcList = source.files?.filter(v => imageSuffixList.includes(v.name.split('.').pop() ?? '')).map(v => v.url) ?? []
newFileList = [
...(source.files?.filter((v) =>
imageSuffixList.includes(v.name.split('.').pop() ?? '')
) ?? [])
]
newSrcList =
source.files
?.filter((v) =>
imageSuffixList.includes(v.name.split('.').pop() ?? '')
)
.map((v) => v.url) ?? []
} else {
if (!imageSuffixList.includes(source.name.split('.').pop() ?? '')) {
return
}
newFileList = [source]
newSrcList = [source.url]
}
imageSrcList.value = uniq([...imageSrcList.value, ...newSrcList])
imageFileList.value = uniq([...imageFileList.value, ...newFileList]).sort(
(a, b) => a.stats.mtimeMs - b.stats.mtimeMs
)
}
onMounted(() => {
[...currentDirectories.value, ...currentFiles.value].forEach(updateImageSrcList)
;[...currentFiles.value, ...currentDirectories.value].forEach(
updateImageSrcList
)
})
</script>
@@ -129,6 +230,17 @@ onMounted(() => {
width: 100%;
display: flex;
flex-wrap: wrap;
margin-bottom: 20px;
margin-top: 20px;
.miaoImage-container-day {
width: 100%;
.miaoImage-container-day-text {
margin-left: 40px;
font-size: 20px;
user-select: none;
letter-spacing: 1px;
}
}
.miaoImage-container-row {
display: flex;
+62 -29
View File
@@ -1,44 +1,74 @@
<template>
<div class="miao-container">
<div class="miao-container-topBar">
<div class="miao-container-topBar-btn" @click="() => handleZoom(10)" title="快速缩小">--</div>
<div class="miao-container-topBar-btn" @click="() => handleZoom()" title="缩小">-</div>
<div class="miao-container-topBar-btn" @click="() => handleResetMargin()" title="还原">还原缩放</div>
<div class="miao-container-topBar-btn" @click="() => handleShrink()" title="放大">+</div>
<div class="miao-container-topBar-btn" @click="() => handleShrink(10)" title="快速放大">++</div>
</div>
<NScrollbar>
<div class="miao-container-pdf" :style="{alignItems: pdfMargin < 0 ? 'baseline' : 'center'}">
<div ref="pdfEl" class="miao-container-pdf-content" :style="{ width: `calc( 100% - ${pdfMargin}px)`}">
<miao-message-provider ref="miaoMessageRef">
<div class="miao-container">
<div class="miao-container-topBar">
<div
class="miao-container-topBar-btn"
@click="() => handleZoom(10)"
title="快速缩小">
--
</div>
<div
class="miao-container-topBar-btn"
@click="() => handleZoom()"
title="缩小">
-
</div>
<div
class="miao-container-topBar-btn"
@click="() => handleResetMargin()"
title="还原">
还原缩放
</div>
<div
class="miao-container-topBar-btn"
@click="() => handleShrink()"
title="放大">
+
</div>
<div
class="miao-container-topBar-btn"
@click="() => handleShrink(10)"
title="快速放大">
++
</div>
</div>
</NScrollbar>
<div class="miao-container-loading" v-if="isLoading">{{ loadingPercent === -1 ? '加载插件中' :
`渲染进度:${canvasList.length}/${pdfPageCount}` }}</div>
</div>
<NScrollbar>
<div
class="miao-container-pdf"
:style="{
alignItems: pdfMargin < 0 ? 'baseline' : 'center'
}">
<div
ref="pdfEl"
class="miao-container-pdf-content"
:style="{
width: `calc( 100% - ${pdfMargin}px)`
}"></div>
</div>
</NScrollbar>
</div>
</miao-message-provider>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, shallowReactive } from 'vue'
import miaoMessageProvider from '@/components/miaoMessageProvider.vue'
import { onMounted, ref, shallowReactive } from 'vue'
import PdfjsWorker from 'pdfjs-dist/build/pdf.worker.mjs?worker'
import { VirtualFile } from '@/class/VirtualDirectory'
import { NScrollbar } from 'naive-ui';
import { NScrollbar } from 'naive-ui'
const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
required: true
})
const pdfEl = ref<HTMLDivElement>()
const miaoMessageRef = ref<InstanceType<typeof miaoMessageProvider>>()
// 通过margin来实现缩小功能
const pdfMargin = ref<number>(0)
const pdfPageCount = ref<number>(-1)
const isLoading = computed(() => {
return canvasList.length !== pdfPageCount.value
})
const canvasList = shallowReactive<HTMLCanvasElement[]>([])
const handleZoom = (index?: number) => {
@@ -58,14 +88,11 @@ const handleResetMargin = () => {
pdfMargin.value = 0
}
const loadingPercent = computed(() => {
if (pdfPageCount.value === -1) {
return -1
}
return canvasList.length / pdfPageCount.value
})
onMounted(async () => {
const setLoadingMessage = miaoMessageRef.value?.message('加载PDF插件中', {
type: 'info',
timeout: -1
})
const pdfSrc = currentFiles.value[0].url
// @ts-ignore
const PDFJS = await import('pdfjs-dist/build/pdf.mjs')
@@ -79,6 +106,7 @@ onMounted(async () => {
pdfPageCount.value = pdf.numPages
// 循环渲染每一页
for (let i = 1; i <= pdf.numPages; i++) {
setLoadingMessage && setLoadingMessage(`渲染进度:${i}/${pdf.numPages}`)
const page = await pdf.getPage(i)
let pixelRatio = 3
let viewport = page.getViewport({ scale: 1 })
@@ -96,6 +124,11 @@ onMounted(async () => {
}
await page.render(renderContext).promise // 一页一页的渲染
}
setLoadingMessage &&
setLoadingMessage(`渲染PDF完成`, {
type: 'success',
timeout: 3456
})
})
</script>