feat(projects): 网盘全局上传组件静态封装完毕

This commit is contained in:
黄磊 2025-03-03 04:03:20 -05:00
parent a6cb692b9a
commit 75313878f5
12 changed files with 272 additions and 8 deletions

View File

@ -15,7 +15,15 @@ export default defineConfig(
'PascalCase',
{
registeredComponentsOnly: false,
ignores: ['/^icon-/']
ignores: [
'/^icon-/',
'uploader',
'uploader-unsupport',
'uploader-drop',
'uploader-btn',
'uploader-list',
'uploader-file'
]
}
],
'unocss/order-attributify': 'off'

View File

@ -68,7 +68,8 @@
"vue": "3.5.13",
"vue-draggable-plus": "0.6.0",
"vue-i18n": "11.1.1",
"vue-router": "4.5.0"
"vue-router": "4.5.0",
"vue-simple-uploader": "^1.0.3"
},
"devDependencies": {
"@elegant-router/vue": "0.3.8",

View File

@ -74,6 +74,9 @@ importers:
vue-router:
specifier: 4.5.0
version: 4.5.0(vue@3.5.13(typescript@5.7.3))
vue-simple-uploader:
specifier: ^1.0.3
version: 1.0.3(vue@3.5.13(typescript@5.7.3))
devDependencies:
'@elegant-router/vue':
specifier: 0.3.8
@ -3798,6 +3801,9 @@ packages:
resolution: {integrity: sha512-tgqwPUMDcNDhuf1Xf6KTUsyeqGdgKMhzaH4PAZZuzguOgTl5uuyeYe/8mWgAr6IBxB5V06uqEf6Dy37gIWDtDg==}
hasBin: true
simple-uploader.js@0.6.0:
resolution: {integrity: sha512-EXN+o+LD6PVnfzTq/usP8k8yYrI6wKrAx8e+fPcPLVzzttonkyn1KT+Ycx5JnPBSnp6lpiVhNG4JhDJucdPnhA==}
simplebar-core@1.3.0:
resolution: {integrity: sha512-LpWl3w0caz0bl322E68qsrRPpIn+rWBGAaEJ0lUJA7Xpr2sw92AkIhg6VWj988IefLXYh50ILatfAnbNoCFrlA==}
@ -4337,6 +4343,12 @@ packages:
peerDependencies:
vue: ^3.2.0
vue-simple-uploader@1.0.3:
resolution: {integrity: sha512-RIghV5rG1CaA41R7VlQP0UG9xevs+cRaCN0k7gH4cFHdG9yIf4206fGKA90NRKzlPxGSBTwLm5dCajLHfqd2+w==}
engines: {node: '>= 4.0.0', npm: '>= 3.0.0'}
peerDependencies:
vue: '>=3.1'
vue-tsc@2.2.0:
resolution: {integrity: sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==}
hasBin: true
@ -8250,6 +8262,8 @@ snapshots:
simple-git-hooks@2.11.1: {}
simple-uploader.js@0.6.0: {}
simplebar-core@1.3.0:
dependencies:
lodash: 4.17.21
@ -8856,6 +8870,11 @@ snapshots:
'@vue/devtools-api': 6.6.4
vue: 3.5.13(typescript@5.7.3)
vue-simple-uploader@1.0.3(vue@3.5.13(typescript@5.7.3)):
dependencies:
simple-uploader.js: 0.6.0
vue: 3.5.13(typescript@5.7.3)
vue-tsc@2.2.0(typescript@5.7.3):
dependencies:
'@volar/typescript': 2.4.11

View File

@ -0,0 +1,199 @@
<script setup lang="ts">
import type { ComponentPublicInstance } from 'vue';
import { nextTick, onMounted, ref } from 'vue';
import { simpleUploadURL } from '@/service/api/pan';
import { localStg } from '@/utils/storage';
defineOptions({
name: 'GlobalUploader'
});
/** 数据定义 */
const uploaderRef = ref();
const showUploader = ref(true);
const dragover = ref(false);
const enableDragUpload = ref(false);
const isPanelShow = ref(false);
const fileListLength = ref(0);
const process = ref(-10);
const uploadBtnRef = ref<ComponentPublicInstance | null>(null);
const folderBtnRef = ref<ComponentPublicInstance | null>(null);
/** 上传组件的配置 */
const options = {
target: simpleUploadURL, //
headers: {
Authorization: `Bearer ${localStg.get('token')}` //
},
chunkSize: localStg.get('uploaderChunkSize') || 1024 * 1024, //
forceChunkSize: true, // chunkSize false
// fileParameterName: 'file', // file
maxChunkRetries: 3, //
simultaneousUploads: 3, //
testChunks: true, //
//
checkChunkUploadedByResponse(chunk: any, message: any) {
const objMessage = JSON.parse(message);
const res = objMessage.data;
if (!res) {
return [];
}
if (res.pass) {
//
return true;
}
//
return (res.resume || []).includes(chunk.offset + 1);
},
//
parseTimeRemaining(parsedTimeRemaining: string) {
return parsedTimeRemaining
.replace(/\syears?/, '年')
.replace(/\days?/, '天')
.replace(/\shours?/, '小时')
.replace(/\sminutes?/, '分钟')
.replace(/\sseconds?/, '秒');
},
query() {}
};
/** 上传状态文本 */
const statusText = {
success: '上传成功',
error: '上传失败',
uploading: '正在上传',
paused: '已暂停',
waiting: '等待中'
};
//
const attrs = {
accept: '*'
};
//
const handleFileUpload = () => {
if (uploadBtnRef.value) {
uploadBtnRef.value?.$el.click();
}
};
//
const handleFolderUpload = () => {
if (folderBtnRef.value) {
folderBtnRef.value?.$el.click();
}
};
//
const closePanel = () => {
if (process.value === -10 || process.value === 100 || fileListLength.value === 0) {
isPanelShow.value = false;
} else {
window.$dialog?.warning({
title: '提示',
content: '文件正在上传中,是否关闭上传面板?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
isPanelShow.value = false;
}
});
}
};
//
const openPanelShow = () => {
if (isPanelShow.value) return;
isPanelShow.value = true;
};
//
const initUploader = () => {
nextTick(() => {
if (uploaderRef.value) {
window.$uploader = uploaderRef.value.uploader;
}
});
};
defineExpose({ handleFileUpload, handleFolderUpload, openPanelShow });
onMounted(() => {
initUploader();
});
</script>
<template>
<div class="pos-fixed bottom-15px right-15px z-1002">
<uploader
v-if="showUploader"
ref="uploaderRef"
:options="options"
:auto-start="false"
:file-status-text="statusText"
class="w-720px"
>
<uploader-unsupport>您的浏览器不支持上传组件</uploader-unsupport>
<uploader-drop
v-show="dragover && enableDragUpload"
class="pos-fixed left-0 top-0 size-full bg-[#ffffff99] text-center"
>
<span class="pos-relative top-48% text-34px text-[#616161] font-700">上传文件到当前目录下</span>
</uploader-drop>
<uploader-btn id="global-uploader-btn" ref="uploadBtnRef" :attrs="attrs">选择文件</uploader-btn>
<uploader-btn id="folder-uploader-btn" ref="folderBtnRef" :directory="true">选择文件夹</uploader-btn>
<uploader-list v-show="isPanelShow">
<template #default="props">
<div
class="pos-fixed bottom-[4%] right-[2%] m-auto h-300px w-720px overflow-hidden border-1 border-[#e2e2e2] rounded-7px border-solid bg-[length:100%_100%] bg-white shadow-[rgba(0,0,0,0.2)] transition-all duration-500 ease-in-out"
>
<div class="h-[2.8rem] flex border-b-1 border-primary border-solid p-[0px,10px]">
<h2 class="ml-[3%] text-18px/[2.8rem]">传输列表</h2>
<div class="flex-[1] text-align-right">
<NButton text class="ml-0 px-5px py-16px">
<template #icon>
<icon-ep:position />
</template>
</NButton>
<NButton text class="ml-0 mr-10px px-5px py-16px" @click="closePanel">
<template #icon>
<icon-ep:circle-close />
</template>
</NButton>
</div>
</div>
<ul class="pos-relative m-0 max-h-300px overflow-x-hidden overflow-y-auto bg-white p-0">
<li v-for="file in props.fileList" :key="file.id" class="bg-white">
<uploader-file :class="'file_' + file.id" :file="file" :list="true" />
</li>
<div
v-if="!props.fileList.length"
class="pos-relative left-50% top-50% mt-100px transform-translate-x-[-50%] translate-y-[-50%] text-center text-18px"
>
<icon-lineicons:empty-file />
暂无待上传文件
</div>
</ul>
</div>
</template>
</uploader-list>
</uploader>
</div>
</template>
<style scoped>
/* 隐藏上传按钮 */
#global-uploader-btn {
position: absolute;
clip: rect(0, 0, 0, 0);
}
/* 隐藏上传按钮 */
#folder-uploader-btn {
position: absolute;
clip: rect(0, 0, 0, 0);
}
</style>

View File

@ -1,5 +1,6 @@
import { createApp } from 'vue';
import './plugins/assets';
import uploader from 'vue-simple-uploader';
import { setupAppVersionNotification, setupDayjs, setupIconifyOffline, setupLoading, setupNProgress } from './plugins';
import { setupStore } from './store';
import { setupRouter } from './router';
@ -25,6 +26,8 @@ async function setupApp() {
setupAppVersionNotification();
app.use(uploader);
app.mount('#app');
}

View File

@ -1,3 +1,4 @@
import 'virtual:svg-icons-register';
import 'uno.css';
import '../styles/css/global.css';
import 'vue-simple-uploader/dist/style.css';

View File

@ -14,10 +14,13 @@ declare module 'vue' {
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
GlobalUploader: typeof import('./../components/common/global-uploader.vue')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
'IconCuida:swapVerticalArrowsOutline': typeof import('~icons/cuida/swap-vertical-arrows-outline')['default']
'IconEp:circleClose': typeof import('~icons/ep/circle-close')['default']
'IconEp:position': typeof import('~icons/ep/position')['default']
'IconFluent:multiselect20Filled': typeof import('~icons/fluent/multiselect20-filled')['default']
IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']
@ -28,6 +31,7 @@ declare module 'vue' {
IconIcRoundRefresh: typeof import('~icons/ic/round-refresh')['default']
IconIcRoundRemove: typeof import('~icons/ic/round-remove')['default']
IconIcRoundSearch: typeof import('~icons/ic/round-search')['default']
'IconLineicons:emptyFile': typeof import('~icons/lineicons/empty-file')['default']
IconLocalBanner: typeof import('~icons/local/banner')['default']
IconLocalGitee: typeof import('~icons/local/gitee')['default']
IconLocalLogo: typeof import('~icons/local/logo')['default']

View File

@ -12,6 +12,8 @@ declare global {
$message?: import('naive-ui').MessageProviderInst;
/** Notification instance */
$notification?: import('naive-ui').NotificationProviderInst;
/** Uploader instance */
$uploader?: import('vue-simple-uploader').Uploader;
}
/** Build time of the project */

View File

@ -38,5 +38,7 @@ declare namespace StorageType {
siderCollapse: boolean;
};
fileShowMode: UnionKey.FileListMode;
/** uploader chunk size */
uploaderChunkSize: number;
}
}

9
src/typings/uploader.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
declare module 'vue-simple-uploader' {
import type { Plugin } from 'vue';
const VueSimpleUploader: Plugin;
export default VueSimpleUploader;
export interface Uploader {
/** 文件列表 */
fileList: File[];
}
}

View File

@ -14,7 +14,7 @@ const gap = computed(() => (appStore.isMobile ? 0 : 16));
<div class="h-full flex pb[-28px]">
<NGrid :x-gap="gap" responsive="screen" item-responsive>
<NGridItem span="0 s:8 m:6 l:5 xl:4">
<FileAside></FileAside>
<FileAside />
</NGridItem>
<NGridItem span="24 s:16 m:18 l:19 xl:20">
<FileMain />

View File

@ -13,6 +13,8 @@ defineOptions({
const { SvgIconVNode } = useSvgIcon();
const panStore = usePanStore();
const globalUploaderRef = ref();
//
const isBatchMode = ref<boolean>(false);
//
@ -30,6 +32,21 @@ const toggleMode = () => {
panStore.toggleFileShowMode(newMode);
};
//
const handleFileUpload = () => {
globalUploaderRef.value?.handleFileUpload();
};
//
const handleFolderUpload = () => {
globalUploaderRef.value?.handleFolderUpload();
};
//
const handleOpenPanel = () => {
globalUploaderRef.value?.openPanelShow();
};
const uploadOptions = [
{
key: 'file',
@ -37,8 +54,7 @@ const uploadOptions = [
icon: SvgIconVNode({ localIcon: 'upload-file', fontSize: 25 }),
props: {
onClick: () => {
// handleFileUpload();
window.$message?.info('上传文件功能');
handleFileUpload();
}
}
},
@ -48,8 +64,7 @@ const uploadOptions = [
icon: SvgIconVNode({ localIcon: 'upload-folder', fontSize: 20 }),
props: {
onClick: () => {
// handleFolderUpload();
window.$message?.info('上传文件夹功能');
handleFolderUpload();
}
}
}
@ -72,6 +87,7 @@ onMounted(async () => {
<template>
<div class="h-full flex-col overflow-hidden">
<NCard class="h-full">
<GlobalUploader ref="globalUploaderRef" />
<FilePath />
<NSpace justify="space-between" class="mt-10px">
<NSpace>
@ -113,7 +129,7 @@ onMounted(async () => {
<NButtonGroup>
<NTooltip placement="bottom" trigger="hover">
<template #trigger>
<NButton>
<NButton @click="handleOpenPanel">
<template #icon>
<icon-cuida:swap-vertical-arrows-outline />
</template>