vben
2020-11-15 661db0c767772bb7a30da9d3eeaf2b47858ccf0b
提交 | 用户 | age
746d4a 1 <template>
J 2   <BasicModal
661db0 3     width="800px"
V 4     title="上传"
5     okText="保存"
746d4a 6     v-bind="$attrs"
J 7     @register="register"
8     @ok="handleOk"
9     :closeFunc="handleCloseFunc"
10     :maskClosable="false"
661db0 11     :keyboard="false"
746d4a 12     wrapClassName="upload-modal"
661db0 13     :okButtonProps="getOkButtonProps"
746d4a 14     :cancelButtonProps="{ disabled: isUploadingRef }"
J 15   >
16     <template #centerdFooter>
661db0 17       <a-button
V 18         @click="handleStartUpload"
19         color="success"
20         :disabled="!getIsSelectFile"
21         :loading="isUploadingRef"
22       >
23         {{ getUploadBtnText }}
746d4a 24       </a-button>
J 25     </template>
661db0 26
V 27     <BasicTable @register="registerTable" :dataSource="fileListRef">
28       <template #toolbar>
29         <Upload :accept="getStringAccept" :multiple="multiple" :before-upload="beforeUpload">
30           <a-button type="primary"> 选择文件 </a-button>
31         </Upload>
32       </template>
33       <template #tableTitle>
34         <Alert :message="getHelpText" type="info" banner></Alert>
35       </template>
36     </BasicTable>
746d4a 37   </BasicModal>
J 38 </template>
39 <script lang="ts">
661db0 40   import { defineComponent, reactive, ref, toRefs, unref, computed } from 'vue';
V 41   import { Upload, Alert } from 'ant-design-vue';
746d4a 42   import { BasicModal, useModalInner } from '/@/components/Modal';
J 43   import { BasicTable, useTable } from '/@/components/Table';
44   // hooks
45   import { useUploadType } from './useUpload';
46   import { useMessage } from '/@/hooks/web/useMessage';
47   //   types
48   import { FileItem, UploadResultStatus } from './types';
49   import { basicProps } from './props';
50   import { createTableColumns, createActionColumn } from './data';
51   // utils
52   import { checkFileType, checkImgType, getBase64WithFile } from './utils';
53   import { buildUUID } from '/@/utils/uuid';
54   import { createImgPreview } from '/@/components/Preview/index';
661db0 55   import { uploadApi } from '/@/api/sys/upload';
V 56   import { isFunction } from '/@/utils/is';
57   import { warn } from '/@/utils/log';
746d4a 58
J 59   export default defineComponent({
661db0 60     components: { BasicModal, Upload, BasicTable, Alert },
746d4a 61     props: basicProps,
J 62     setup(props, { emit }) {
661db0 63       //   是否正在上传
V 64       const isUploadingRef = ref(false);
65       const fileListRef = ref<FileItem[]>([]);
66       const state = reactive<{ fileList: FileItem[] }>({
67         fileList: [],
746d4a 68       });
J 69
661db0 70       const [register, { closeModal }] = useModalInner();
V 71
72       const { accept, helpText, maxNumber, maxSize } = toRefs(props);
73       const { getAccept, getStringAccept, getHelpText } = useUploadType({
74         acceptRef: accept,
75         helpTextRef: helpText,
76         maxNumberRef: maxNumber,
77         maxSizeRef: maxSize,
78       });
79
746d4a 80       const { createMessage } = useMessage();
661db0 81
V 82       const getIsSelectFile = computed(() => {
83         return (
84           fileListRef.value.length > 0 &&
85           !fileListRef.value.every((item) => item.status === UploadResultStatus.SUCCESS)
86         );
87       });
88
89       const getOkButtonProps = computed(() => {
90         const someSuccess = fileListRef.value.some(
91           (item) => item.status === UploadResultStatus.SUCCESS
92         );
93         return {
94           disabled: isUploadingRef.value || fileListRef.value.length === 0 || !someSuccess,
95         };
96       });
97
98       const getUploadBtnText = computed(() => {
99         const someError = fileListRef.value.some(
100           (item) => item.status === UploadResultStatus.ERROR
101         );
102         return isUploadingRef.value ? '上传中' : someError ? '重新上传失败文件' : '开始上传';
103       });
104
746d4a 105       // 上传前校验
J 106       function beforeUpload(file: File) {
107         const { size, name } = file;
108         const { maxSize } = props;
109         const accept = unref(getAccept);
110
111         // 设置最大值,则判断
112         if (maxSize && file.size / 1024 / 1024 >= maxSize) {
113           createMessage.error(`只能上传不超过${maxSize}MB的文件!`);
114           return false;
115         }
116
117         // 设置类型,则判断
118         if (accept.length > 0 && !checkFileType(file, accept)) {
119           createMessage.error!(`只能上传${accept.join(',')}格式文件`);
120           return false;
121         }
661db0 122         const commonItem = {
V 123           uuid: buildUUID(),
124           file,
125           size,
126           name,
127           percent: 0,
128           type: name.split('.').pop(),
129         };
746d4a 130         // 生成图片缩略图
J 131         if (checkImgType(file)) {
132           // beforeUpload,如果异步会调用自带上传方法
133           // file.thumbUrl = await getBase64(file);
134           getBase64WithFile(file).then(({ result: thumbUrl }) => {
135             fileListRef.value = [
136               ...unref(fileListRef),
137               {
138                 thumbUrl,
661db0 139                 ...commonItem,
746d4a 140               },
J 141             ];
142           });
143         } else {
661db0 144           fileListRef.value = [...unref(fileListRef), commonItem];
746d4a 145         }
J 146         return false;
147       }
148       // 删除
149       function handleRemove(record: FileItem) {
150         const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid);
151         index !== -1 && fileListRef.value.splice(index, 1);
152       }
661db0 153
746d4a 154       // 预览
J 155       function handlePreview(record: FileItem) {
156         const { thumbUrl = '' } = record;
157         createImgPreview({
158           imageList: [thumbUrl],
159         });
160       }
661db0 161
746d4a 162       async function uploadApiByItem(item: FileItem) {
661db0 163         const { api } = props;
V 164         if (!api || !isFunction(api)) {
165           return warn('upload api must exist and be a function');
166         }
746d4a 167         try {
J 168           item.status = UploadResultStatus.UPLOADING;
169
170           const { data } = await uploadApi(
171             {
661db0 172               ...(props.uploadParams || {}),
746d4a 173               file: item.file,
J 174             },
175             function onUploadProgress(progressEvent: ProgressEvent) {
176               const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
177               item.percent = complete;
178             }
179           );
180           item.status = UploadResultStatus.SUCCESS;
181           item.responseData = data;
182           return {
183             success: true,
184             error: null,
185           };
186         } catch (e) {
187           console.log(e);
188           item.status = UploadResultStatus.ERROR;
189           return {
190             success: false,
191             error: e,
192           };
193         }
194       }
661db0 195
746d4a 196       // 点击开始上传
J 197       async function handleStartUpload() {
661db0 198         const { maxNumber } = props;
V 199         if (fileListRef.value.length > maxNumber) {
200           return createMessage.warning(`最多只能上传${maxNumber}个文件`);
201         }
746d4a 202         try {
J 203           isUploadingRef.value = true;
661db0 204           // 只上传不是成功状态的
V 205           const uploadFileList =
206             fileListRef.value.filter((item) => item.status !== UploadResultStatus.SUCCESS) || [];
746d4a 207           const data = await Promise.all(
661db0 208             uploadFileList.map((item) => {
746d4a 209               return uploadApiByItem(item);
J 210             })
211           );
212           isUploadingRef.value = false;
213           // 生产环境:抛出错误
661db0 214           const errorList = data.filter((item: any) => !item.success);
V 215           if (errorList.length > 0) throw errorList;
746d4a 216         } catch (e) {
J 217           isUploadingRef.value = false;
218           throw e;
219         }
220       }
661db0 221
746d4a 222       //   点击保存
J 223       function handleOk() {
661db0 224         const { maxNumber } = props;
V 225
226         if (fileListRef.value.length > maxNumber) {
227           return createMessage.warning(`最多只能上传${maxNumber}个文件`);
228         }
746d4a 229         if (isUploadingRef.value) {
661db0 230           return createMessage.warning('请等待文件上传后,保存');
746d4a 231         }
J 232         const fileList: string[] = [];
233
234         for (const item of fileListRef.value) {
235           const { status, responseData } = item;
236           if (status === UploadResultStatus.SUCCESS && responseData) {
237             fileList.push(responseData.url);
238           }
239         }
240         // 存在一个上传成功的即可保存
241         if (fileList.length <= 0) {
661db0 242           return createMessage.warning('没有上传成功的文件,无法保存');
746d4a 243         }
J 244         fileListRef.value = [];
245         closeModal();
661db0 246         emit('change', fileList);
746d4a 247       }
661db0 248
746d4a 249       // 点击关闭:则所有操作不保存,包括上传的
J 250       function handleCloseFunc() {
251         if (!isUploadingRef.value) {
252           fileListRef.value = [];
253           return true;
254         } else {
255           createMessage.warning('请等待文件上传结束后操作');
256           return false;
257         }
258       }
661db0 259
V 260       const [registerTable] = useTable({
261         columns: createTableColumns(),
262         actionColumn: createActionColumn(handleRemove, handlePreview),
263         pagination: false,
264         inset: true,
265         scroll: {
266           y: 3000,
267         },
268       });
746d4a 269       return {
J 270         register,
271         closeModal,
272         getHelpText,
273         getStringAccept,
661db0 274         getOkButtonProps,
746d4a 275         beforeUpload,
J 276         registerTable,
277         fileListRef,
278         state,
279         isUploadingRef,
280         handleStartUpload,
281         handleOk,
282         handleCloseFunc,
661db0 283         getIsSelectFile,
V 284         getUploadBtnText,
746d4a 285       };
J 286     },
287   });
288 </script>
289 <style lang="less">
290   .upload-modal {
291     .ant-upload-list {
292       display: none;
293     }
294
295     .ant-table-wrapper .ant-spin-nested-loading {
296       padding: 0;
297     }
298   }
299 </style>