|
@@ -0,0 +1,355 @@
|
|
|
+<template>
|
|
|
+ <CollapseContainer
|
|
|
+ class="attachment-container"
|
|
|
+ title="附件管理"
|
|
|
+ :canExpan="false"
|
|
|
+ helpMessage="主要用于管理上传到服务器或第三方存储的数据"
|
|
|
+ >
|
|
|
+ <a-button type="default" class="mr-2"> 全部 </a-button>
|
|
|
+ <a-button type="default" class="mr-2"> 图片 </a-button>
|
|
|
+ <a-button type="default" class="mr-2"> 音频 </a-button>
|
|
|
+ <a-button type="default" class="mr-2"> 视频 </a-button>
|
|
|
+ <a-button type="default" class="mr-2"> 文档 </a-button>
|
|
|
+ <a-button type="default" class="mr-2"> 应用 </a-button>
|
|
|
+ <a-button type="default" class="mr-2"> 压缩包 </a-button>
|
|
|
+ <BasicTable ref="tableRef" :canResize="true" @register="registerTable">
|
|
|
+ <template #action="{ record, column }">
|
|
|
+ <TableAction :actions="createActions(record, column)" stopButtonPropagation />
|
|
|
+ </template>
|
|
|
+ <template #toolbar>
|
|
|
+ <a-upload
|
|
|
+ :showUploadList="false"
|
|
|
+ :multiple="false"
|
|
|
+ :before-upload="beforeUpload"
|
|
|
+ @change="handleChange"
|
|
|
+ >
|
|
|
+ <a-button type="primary" :disabled="disabled">
|
|
|
+ {{ t('component.upload.upload') }}
|
|
|
+ </a-button>
|
|
|
+ </a-upload>
|
|
|
+ <a-button color="error" @click="deleteBatches">
|
|
|
+ {{ t('common.delText') }}
|
|
|
+ </a-button>
|
|
|
+ </template>
|
|
|
+ <a-modal :visible="progress_show" :footer="null" :closable="false">
|
|
|
+ <div class="upload-progress">
|
|
|
+ <p><strong>文件上传中</strong></p>
|
|
|
+ <a-progress :percent="percent" />
|
|
|
+ </div>
|
|
|
+ </a-modal>
|
|
|
+ </BasicTable>
|
|
|
+ </CollapseContainer>
|
|
|
+</template>
|
|
|
+<script lang="ts">
|
|
|
+ import { useMessage } from '/@/hooks/web/useMessage';
|
|
|
+ import { defineComponent, nextTick, reactive, ref, toRefs, unref } from 'vue';
|
|
|
+ // import Upload from '/@/components/customComponents/upload.vue';
|
|
|
+ import { Upload, Progress, Modal } from 'ant-design-vue';
|
|
|
+ import { CollapseContainer } from '/@/components/Container/index';
|
|
|
+ import { adapt } from '/@/utils/adapt';
|
|
|
+ import { useI18n } from '/@/hooks/web/useI18n';
|
|
|
+ import md5 from 'crypto-js/md5';
|
|
|
+ import {
|
|
|
+ BasicTable,
|
|
|
+ useTable,
|
|
|
+ TableAction,
|
|
|
+ ActionItem,
|
|
|
+ EditRecordRow,
|
|
|
+ TableActionType,
|
|
|
+ } from '/@/components/Table';
|
|
|
+ import { columns } from './data';
|
|
|
+ import { getAttachmentList } from '/@/api/sys/general';
|
|
|
+ import { uploadApi } from '/@/api/sys/upload';
|
|
|
+
|
|
|
+ interface FileItem {
|
|
|
+ uid: string;
|
|
|
+ name?: string;
|
|
|
+ status?: string;
|
|
|
+ response?: string;
|
|
|
+ url?: string;
|
|
|
+ type?: string;
|
|
|
+ size: number;
|
|
|
+ originFileObj: any;
|
|
|
+ }
|
|
|
+
|
|
|
+ export default defineComponent({
|
|
|
+ name: 'Attchment',
|
|
|
+ components: {
|
|
|
+ CollapseContainer,
|
|
|
+ BasicTable,
|
|
|
+ TableAction,
|
|
|
+ [Upload.name]: Upload,
|
|
|
+ [Modal.name]: Modal,
|
|
|
+ [Progress.name]: Progress,
|
|
|
+ },
|
|
|
+ setup() {
|
|
|
+ const { t } = useI18n();
|
|
|
+ const { createMessage } = useMessage();
|
|
|
+ const { success /*, error */ } = createMessage;
|
|
|
+ const tableHeight = adapt().tableHeight;
|
|
|
+ const state = reactive({
|
|
|
+ groupList: [] as object[],
|
|
|
+ group: 'basic',
|
|
|
+ disabled: false,
|
|
|
+ tempThreads: 5,
|
|
|
+ progress_show: false,
|
|
|
+ percent: 0,
|
|
|
+ chunkRetry: 4, // 重试次数限制
|
|
|
+ });
|
|
|
+ const tableRef = ref<Nullable<TableActionType>>(null);
|
|
|
+ const [registerTable] = useTable({
|
|
|
+ columns: columns,
|
|
|
+ maxHeight: tableHeight,
|
|
|
+ afterFetch: afterFetch,
|
|
|
+ api: getAttachmentList,
|
|
|
+ actionColumn: {
|
|
|
+ width: 160,
|
|
|
+ title: '操作',
|
|
|
+ dataIndex: 'action',
|
|
|
+ slots: { customRender: 'action' },
|
|
|
+ fixed: undefined,
|
|
|
+ },
|
|
|
+ showIndexColumn: false,
|
|
|
+ pagination: true,
|
|
|
+ });
|
|
|
+ function afterFetch(res) {
|
|
|
+ console.log(`res`, res);
|
|
|
+ }
|
|
|
+ function getTableAction() {
|
|
|
+ // 获取组件
|
|
|
+ const tableAction = unref(tableRef);
|
|
|
+ if (!tableAction) {
|
|
|
+ throw new Error('tableAction is null');
|
|
|
+ }
|
|
|
+ return tableAction;
|
|
|
+ }
|
|
|
+
|
|
|
+ function getFileMD5(file) {
|
|
|
+ return md5(file.name + file.size + file.lastModifiedDate);
|
|
|
+ }
|
|
|
+
|
|
|
+ async function mergeRequest(fileMd5) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const mergeFormData = new FormData();
|
|
|
+ mergeFormData.append('fileMd5', fileMd5);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function createFileChunk(file, size) {
|
|
|
+ const fileChunkList: object[] = [];
|
|
|
+ let count = 0;
|
|
|
+ let index = 0;
|
|
|
+ while (count < file.size) {
|
|
|
+ fileChunkList.push({
|
|
|
+ file: file.slice(count, count + size),
|
|
|
+ hash: file.name + '-' + index, // 生成切片名称
|
|
|
+ });
|
|
|
+ count += size;
|
|
|
+ index += 1;
|
|
|
+ }
|
|
|
+ return fileChunkList;
|
|
|
+ }
|
|
|
+ // 判断文件是否上传过,获取fileId
|
|
|
+ async function checkFile(file) {
|
|
|
+ const md5 = getFileMD5(file);
|
|
|
+ file.md5 = md5;
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append('fileMd5', md5);
|
|
|
+ formData.append('fileName', file.name);
|
|
|
+ formData.append('fileSize', file.size);
|
|
|
+ await uploadChunks(file);
|
|
|
+ }
|
|
|
+ async function uploadChunks(file) {
|
|
|
+ // let chunkSize = updateCchunkSize(file);
|
|
|
+ let chunkSize = 64 * 1024;
|
|
|
+ console.log(`chunkSize`, chunkSize);
|
|
|
+ const fileChunkList = createFileChunk(file, chunkSize);
|
|
|
+ console.log(`fileChunkList`, fileChunkList);
|
|
|
+ file.chunkList = fileChunkList.map(({ file, hash }, index) => {
|
|
|
+ console.log(`filessssss`, file);
|
|
|
+ return {
|
|
|
+ index,
|
|
|
+ source: file,
|
|
|
+ hash: hash,
|
|
|
+ size: file.size,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ var chunkData = file.chunkList;
|
|
|
+ console.log(`chunkData`, chunkData);
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const requestDataList = chunkData.map((value) => {
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append('fileMd5', file.md5);
|
|
|
+ formData.append('chunk', value.index);
|
|
|
+ formData.append('hash', value.hash);
|
|
|
+ formData.append('file', value.source);
|
|
|
+ console.log(`formData`, formData);
|
|
|
+ return { formData, index: value.index, md5: file.md5, file };
|
|
|
+ });
|
|
|
+ try {
|
|
|
+ console.log(`requestDataList1111`, requestDataList);
|
|
|
+ const ret = sendRequest(requestDataList);
|
|
|
+ // console.log('上传结束,参数:', ret, chunkData, file.md5);
|
|
|
+ resolve(ret);
|
|
|
+ } catch (error) {
|
|
|
+ // reject('sendRequest 失败', error);
|
|
|
+ console.log(`error`, error);
|
|
|
+ reject(error);
|
|
|
+ }
|
|
|
+ }).then(async (res) => {
|
|
|
+ if (res == file.md5) {
|
|
|
+ // console.log('--- ' + file.name + ' 文件开始合并----')
|
|
|
+ await mergeRequest(file.md5);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function beforeUpload(file: FileItem) {
|
|
|
+ file.status = 'uploading';
|
|
|
+ checkFile(file);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 根据文件大小,分配上传分片大小
|
|
|
+ function updateCchunkSize(file) {
|
|
|
+ let chunkSize = 0;
|
|
|
+ if (file.size > 2000 * 1024 * 1024) {
|
|
|
+ chunkSize = 1024 * 1024 * 15;
|
|
|
+ } else if (file.size > 1000 * 1024 * 1024) {
|
|
|
+ chunkSize = 1024 * 1024 * 10;
|
|
|
+ } else if (file.size > 500 * 1024 * 1024) {
|
|
|
+ chunkSize = 1024 * 1024 * 8;
|
|
|
+ } else {
|
|
|
+ chunkSize = 2 * 1024 * 1024;
|
|
|
+ }
|
|
|
+ return chunkSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 并发,重试请求
|
|
|
+ async function sendRequest(list) {
|
|
|
+ var finished = 0;
|
|
|
+ const retryArr: any[] = []; // retryArr.length代表请求数,值代表重试次数
|
|
|
+ var currentFileInfo;
|
|
|
+ const total = list.length;
|
|
|
+ // 所有请求都存放这个promise中
|
|
|
+ console.log(`list`, list);
|
|
|
+ console.log(`total`, total);
|
|
|
+ state.progress_show = true;
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ const handler = () => {
|
|
|
+ if (list.length) {
|
|
|
+ const formInfo = list.shift();
|
|
|
+ const index = formInfo.index;
|
|
|
+ console.log(`list`, list);
|
|
|
+ console.log(`formInfo`, formInfo);
|
|
|
+ uploadApi(formInfo)
|
|
|
+ .then((res) => {
|
|
|
+ console.log(`res`, res);
|
|
|
+ if (res) {
|
|
|
+ state.percent = parseInt((finished / total) * 100);
|
|
|
+ currentFileInfo = formInfo;
|
|
|
+ finished++;
|
|
|
+ console.log(`finished`, finished);
|
|
|
+ console.log(`total`, total);
|
|
|
+ handler();
|
|
|
+ }
|
|
|
+ return res;
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ console.log(`err`, err);
|
|
|
+ if (typeof retryArr[index] !== 'number') {
|
|
|
+ retryArr[index] = 1;
|
|
|
+ }
|
|
|
+ if (retryArr[index] >= state.chunkRetry) {
|
|
|
+ return reject(retryArr);
|
|
|
+ }
|
|
|
+ retryArr[index]++; // 累加
|
|
|
+ state.tempThreads++; // 释放当前占用的通道
|
|
|
+ list.push(formInfo); // 将失败的重新加入队列
|
|
|
+ handler();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (finished >= total) {
|
|
|
+ state.progress_show = false;
|
|
|
+ state.percent = 0;
|
|
|
+ getTableAction().reload();
|
|
|
+ success('文件上传成功');
|
|
|
+ resolve(currentFileInfo.md5); // 输出当前完成上传的文件信息
|
|
|
+ }
|
|
|
+ };
|
|
|
+ // 控制并发
|
|
|
+ for (let i = 0; i < state.tempThreads; i++) {
|
|
|
+ handler();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleChange(info) {
|
|
|
+ console.log(`info`, info);
|
|
|
+ }
|
|
|
+
|
|
|
+ async function handleGroupBtn(group) {
|
|
|
+ await nextTick();
|
|
|
+ getTableAction().reload();
|
|
|
+ state.group = group.toLowerCase();
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleTableReset() {
|
|
|
+ getTableAction().reload();
|
|
|
+ }
|
|
|
+
|
|
|
+ async function handleEdit(record: Recordable) {
|
|
|
+ console.log(`record`, record);
|
|
|
+ console.log('=====编辑');
|
|
|
+ }
|
|
|
+ async function handleDelete(record: Recordable) {
|
|
|
+ console.log(`record`, record);
|
|
|
+ console.log('删除=====');
|
|
|
+ }
|
|
|
+
|
|
|
+ function createActions(record: EditRecordRow): ActionItem[] {
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ label: '编辑',
|
|
|
+ icon: 'ant-design:edit-outlined',
|
|
|
+ color: 'warning',
|
|
|
+ onClick: handleEdit.bind(null, record),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '删除',
|
|
|
+ color: 'error',
|
|
|
+ icon: 'ic:outline-delete-outline',
|
|
|
+ popConfirm: {
|
|
|
+ title: '是否确认删除',
|
|
|
+ confirm: handleDelete.bind(null, record),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ t,
|
|
|
+ createActions,
|
|
|
+ tableRef,
|
|
|
+ registerTable,
|
|
|
+ beforeUpload,
|
|
|
+ handleChange,
|
|
|
+ handleGroupBtn,
|
|
|
+ handleTableReset,
|
|
|
+ ...toRefs(state),
|
|
|
+ };
|
|
|
+ },
|
|
|
+ });
|
|
|
+</script>
|
|
|
+<style>
|
|
|
+ .attachment-container {
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ .vben-collapse-container__body > .mr-2 {
|
|
|
+ margin-top: 5px;
|
|
|
+ font-weight: 550 !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .upload-progress {
|
|
|
+ padding: 20px 30px;
|
|
|
+ }
|
|
|
+</style>
|