mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-16 06:03:44 +08:00
发布v2.13.1版本,更新内容请查看:https://github.com/bufanyun/hotgo/blob/v2.0/docs/guide-zh-CN/start-update-log.md
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
<n-modal
|
||||
v-model:show="showFileModal"
|
||||
:show-icon="false"
|
||||
:mask-closable="false"
|
||||
preset="dialog"
|
||||
:style="{
|
||||
width: width,
|
||||
@@ -41,7 +42,7 @@
|
||||
import { NModal, UploadFileInfo, useMessage } from 'naive-ui';
|
||||
import componentSetting from '@/settings/componentSetting';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
import { Attachment, FileType, getFileType, UploadTag } from '@/components/FileChooser/src/model';
|
||||
import { Attachment, FileType, getFileType } from '@/components/FileChooser/src/model';
|
||||
|
||||
export interface Props {
|
||||
width?: string;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<n-button>
|
||||
<template #icon>
|
||||
<n-icon size="20">
|
||||
<component :is="formValue !== '' ? formValue : 'AntDesignOutlined'" />
|
||||
<component :is="formValue !== '' ? formValue : AntDesignOutlined" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
@@ -38,6 +38,8 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, shallowReactive } from 'vue';
|
||||
import * as AntdIcons from '@vicons/antd';
|
||||
import { AntDesignOutlined } from '@vicons/antd';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AntdSelector',
|
||||
components: AntdIcons,
|
||||
@@ -80,6 +82,7 @@
|
||||
onUpdatePage,
|
||||
onIconClick,
|
||||
formValue,
|
||||
AntDesignOutlined,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
<template v-else>
|
||||
<AntdSelector v-model:value="formValue" />
|
||||
</template>
|
||||
<n-input v-bind="$props" :value="formValue" :style="{ width: '70%' }" />
|
||||
<n-input
|
||||
v-bind="$props"
|
||||
:value="formValue"
|
||||
:style="{ width: '70%' }"
|
||||
placeholder="请选择图标"
|
||||
/>
|
||||
</n-input-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
197
web/src/components/Upload/multipartUpload.vue
Normal file
197
web/src/components/Upload/multipartUpload.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:on-after-leave="handleRemove"
|
||||
:style="{
|
||||
width: width,
|
||||
}"
|
||||
title="上传大文件"
|
||||
>
|
||||
<n-upload
|
||||
directory-dnd
|
||||
:custom-request="handleUpload"
|
||||
:on-remove="handleRemove"
|
||||
name="file"
|
||||
:disabled="uploadStatus != 0 && uploadStatus != 3"
|
||||
>
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<n-icon size="48" :depth="3">
|
||||
<FileAddOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
<template v-if="uploadStatus == 0 || uploadStatus == 3">
|
||||
<n-text style="font-size: 16px">点击或者拖动{{ typeTag }}到该区域来上传</n-text>
|
||||
<n-p depth="3" style="margin: 8px 0 0 0">支持大文件分片上传,支持断点续传</n-p>
|
||||
</template>
|
||||
<template v-else-if="uploadStatus == 1">
|
||||
<span style="font-weight: 600">解析中,请稍候...</span>
|
||||
</template>
|
||||
<template v-else-if="uploadStatus == 2">
|
||||
<span style="font-weight: 600">正在上传({{ progress }}%)...</span>
|
||||
<n-p depth="3" style="margin: 8px 0 0 0">文件大小:{{ sizeFormat }}</n-p>
|
||||
</template>
|
||||
</n-upload-dragger>
|
||||
</n-upload>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { NModal, UploadCustomRequestOptions, useMessage, useDialog } from 'naive-ui';
|
||||
import { FileAddOutlined } from '@vicons/antd';
|
||||
import SparkMD5 from 'spark-md5';
|
||||
import { Attachment, FileType, getFileType } from '@/components/FileChooser/src/model';
|
||||
import { CheckMultipart, UploadPart } from '@/api/base';
|
||||
import type { UploadFileParams } from '@/utils/http/axios/types';
|
||||
|
||||
export interface Props {
|
||||
width?: string;
|
||||
uploadType?: FileType;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
width: '60%',
|
||||
uploadType: 'default',
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onFinish']);
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const showModal = ref(false);
|
||||
const chunkSize = 2 * 1024 * 1024; // 每个分片大小限制,默认2M
|
||||
const uploadStatus = ref(0); // 上传状态 0等待上传 1解析中 2上传中 3已取消
|
||||
const progress = ref(0);
|
||||
const sizeFormat = ref('0B');
|
||||
const typeTag = computed(() => {
|
||||
return getFileType(props.uploadType);
|
||||
});
|
||||
|
||||
// 取消上传
|
||||
function handleRemove() {
|
||||
if (uploadStatus.value == 1 || uploadStatus.value == 2) {
|
||||
uploadStatus.value = 3;
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: '已取消大文件上传,已上传的文件不会自动删除,重新操作可进行断点续传',
|
||||
positiveText: '确定',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 开始上传
|
||||
function handleUpload(options: UploadCustomRequestOptions) {
|
||||
uploadStatus.value = 1;
|
||||
|
||||
// 初始化上传进度
|
||||
updateProgress(options, 0);
|
||||
|
||||
const file = options.file.file as File;
|
||||
const fileReader = new FileReader();
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
fileReader.onload = async (e) => {
|
||||
const spark = new SparkMD5.ArrayBuffer();
|
||||
spark.append(e.target.result);
|
||||
let md5 = spark.end();
|
||||
let start = 0;
|
||||
let end = 0;
|
||||
let index = 0;
|
||||
let shards: any[] = [];
|
||||
while (end < file.size) {
|
||||
start = index * chunkSize;
|
||||
end = (index + 1) * chunkSize;
|
||||
|
||||
const params: UploadFileParams = {
|
||||
uploadType: props.uploadType,
|
||||
md5: md5,
|
||||
index: index + 1,
|
||||
fileName: file.name,
|
||||
file: file.slice(start, end),
|
||||
};
|
||||
|
||||
const shard = { index: index + 1, params: params };
|
||||
shards.push(shard);
|
||||
index++;
|
||||
}
|
||||
|
||||
uploadStatus.value = 2;
|
||||
|
||||
const params = {
|
||||
uploadType: props.uploadType,
|
||||
fileName: file.name,
|
||||
size: file.size,
|
||||
md5: md5,
|
||||
shardCount: shards.length,
|
||||
};
|
||||
|
||||
CheckMultipart(params)
|
||||
.then(async (res) => {
|
||||
// 已存在
|
||||
if (!res.waitUploadIndex || res.waitUploadIndex.length == 0) {
|
||||
onFinish(options, res.attachment);
|
||||
return;
|
||||
}
|
||||
|
||||
// 断点续传,过滤掉已上传成功的分片文件
|
||||
shards = shards.filter((shard) => res.waitUploadIndex.includes(shard.index));
|
||||
if (shards.length == 0) {
|
||||
onFinish(options, res.attachment);
|
||||
return;
|
||||
}
|
||||
|
||||
// 导入断点续传进度
|
||||
updateProgress(options, res.progress);
|
||||
sizeFormat.value = res.sizeFormat;
|
||||
|
||||
for (const item of shards) {
|
||||
if (uploadStatus.value == 3) {
|
||||
break;
|
||||
}
|
||||
item.params.uploadId = res.uploadId;
|
||||
await handleUploadPart(options, item);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
uploadStatus.value = 0;
|
||||
options.onError();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 上传分片文件
|
||||
async function handleUploadPart(options: UploadCustomRequestOptions, item) {
|
||||
const res = await UploadPart(item.params);
|
||||
updateProgress(options, res.progress);
|
||||
if (res.finish) {
|
||||
onFinish(options, res.attachment);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新上传进度
|
||||
function updateProgress(options: UploadCustomRequestOptions, value: number) {
|
||||
options.onProgress({ percent: value });
|
||||
progress.value = value;
|
||||
}
|
||||
|
||||
// 上传成功后的回调
|
||||
function onFinish(options: UploadCustomRequestOptions, result: Attachment) {
|
||||
options.onFinish();
|
||||
message.success('上传成功');
|
||||
uploadStatus.value = 0;
|
||||
emit('onFinish', result, true);
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
showModal.value = true;
|
||||
uploadStatus.value = 0;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user