bowen
2023-11-02 beed7f2e1172531fe691384a50c8b0457f3a80d8
提交 | 用户 | age
746d4a 1 <template>
J 2   <BasicModal
661db0 3     width="800px"
962f90 4     :title="t('component.upload.upload')"
V 5     :okText="t('component.upload.save')"
746d4a 6     v-bind="$attrs"
J 7     @register="register"
8     @ok="handleOk"
9     :closeFunc="handleCloseFunc"
10     :maskClosable="false"
661db0 11     :keyboard="false"
3fcfac 12     class="upload-modal"
661db0 13     :okButtonProps="getOkButtonProps"
746d4a 14     :cancelButtonProps="{ disabled: isUploadingRef }"
J 15   >
18ad1b 16     <template #centerFooter>
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>
bd6b20 26
815250 27     <div class="upload-modal-toolbar">
bd6b20 28       <Alert :message="getHelpText" type="info" banner class="upload-modal-toolbar__text" />
V 29
815250 30       <Upload
J 31         :accept="getStringAccept"
32         :multiple="multiple"
33         :before-upload="beforeUpload"
a462be 34         :show-upload-list="false"
815250 35         class="upload-modal-toolbar__btn"
J 36       >
9edc28 37         <a-button type="primary">
V 38           {{ t('component.upload.choose') }}
39         </a-button>
815250 40       </Upload>
J 41     </div>
beed7f 42     <FileList
B 43       v-model:dataSource="fileListRef"
44       :columns="columns"
45       :actionColumn="actionColumn"
46       :openDrag="fileListOpenDrag"
47       :dragOptions="fileListDragOptions"
48     />
746d4a 49   </BasicModal>
J 50 </template>
51 <script lang="ts">
91e004 52   import { defineComponent, reactive, ref, toRefs, unref, computed, PropType } from 'vue';
661db0 53   import { Upload, Alert } from 'ant-design-vue';
746d4a 54   import { BasicModal, useModalInner } from '/@/components/Modal';
J 55   // hooks
56   import { useUploadType } from './useUpload';
57   import { useMessage } from '/@/hooks/web/useMessage';
58   //   types
3f6920 59   import { FileItem, UploadResultStatus } from './typing';
746d4a 60   import { basicProps } from './props';
J 61   import { createTableColumns, createActionColumn } from './data';
62   // utils
3b3f6c 63   import { checkImgType, getBase64WithFile } from './helper';
746d4a 64   import { buildUUID } from '/@/utils/uuid';
661db0 65   import { isFunction } from '/@/utils/is';
V 66   import { warn } from '/@/utils/log';
3f6920 67   import FileList from './FileList.vue';
dc09de 68   import { useI18n } from '/@/hooks/web/useI18n';
3f6920 69
746d4a 70   export default defineComponent({
815250 71     components: { BasicModal, Upload, Alert, FileList },
91e004 72     props: {
V 73       ...basicProps,
74       previewFileList: {
75         type: Array as PropType<string[]>,
76         default: () => [],
77       },
78     },
3f6920 79     emits: ['change', 'register', 'delete'],
746d4a 80     setup(props, { emit }) {
661db0 81       const state = reactive<{ fileList: FileItem[] }>({
V 82         fileList: [],
746d4a 83       });
J 84
3f6920 85       //   是否正在上传
V 86       const isUploadingRef = ref(false);
87       const fileListRef = ref<FileItem[]>([]);
88       const { accept, helpText, maxNumber, maxSize } = toRefs(props);
89
90       const { t } = useI18n();
661db0 91       const [register, { closeModal }] = useModalInner();
V 92
3b3f6c 93       const { getStringAccept, getHelpText } = useUploadType({
661db0 94         acceptRef: accept,
V 95         helpTextRef: helpText,
96         maxNumberRef: maxNumber,
97         maxSizeRef: maxSize,
98       });
99
746d4a 100       const { createMessage } = useMessage();
661db0 101
V 102       const getIsSelectFile = computed(() => {
103         return (
104           fileListRef.value.length > 0 &&
105           !fileListRef.value.every((item) => item.status === UploadResultStatus.SUCCESS)
106         );
107       });
108
109       const getOkButtonProps = computed(() => {
110         const someSuccess = fileListRef.value.some(
56a966 111           (item) => item.status === UploadResultStatus.SUCCESS,
661db0 112         );
V 113         return {
114           disabled: isUploadingRef.value || fileListRef.value.length === 0 || !someSuccess,
115         };
116       });
117
118       const getUploadBtnText = computed(() => {
119         const someError = fileListRef.value.some(
56a966 120           (item) => item.status === UploadResultStatus.ERROR,
661db0 121         );
dc09de 122         return isUploadingRef.value
962f90 123           ? t('component.upload.uploading')
dc09de 124           : someError
962f90 125           ? t('component.upload.reUploadFailed')
V 126           : t('component.upload.startUpload');
661db0 127       });
V 128
746d4a 129       // 上传前校验
J 130       function beforeUpload(file: File) {
131         const { size, name } = file;
132         const { maxSize } = props;
133         // 设置最大值,则判断
134         if (maxSize && file.size / 1024 / 1024 >= maxSize) {
962f90 135           createMessage.error(t('component.upload.maxSizeMultiple', [maxSize]));
746d4a 136           return false;
J 137         }
138
661db0 139         const commonItem = {
V 140           uuid: buildUUID(),
141           file,
142           size,
143           name,
144           percent: 0,
145           type: name.split('.').pop(),
146         };
746d4a 147         // 生成图片缩略图
J 148         if (checkImgType(file)) {
149           // beforeUpload,如果异步会调用自带上传方法
150           // file.thumbUrl = await getBase64(file);
151           getBase64WithFile(file).then(({ result: thumbUrl }) => {
152             fileListRef.value = [
153               ...unref(fileListRef),
154               {
155                 thumbUrl,
661db0 156                 ...commonItem,
746d4a 157               },
J 158             ];
159           });
160         } else {
661db0 161           fileListRef.value = [...unref(fileListRef), commonItem];
746d4a 162         }
J 163         return false;
164       }
3f6920 165
746d4a 166       // 删除
J 167       function handleRemove(record: FileItem) {
168         const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid);
169         index !== -1 && fileListRef.value.splice(index, 1);
3f6920 170         emit('delete', record);
746d4a 171       }
661db0 172
746d4a 173       async function uploadApiByItem(item: FileItem) {
661db0 174         const { api } = props;
V 175         if (!api || !isFunction(api)) {
176           return warn('upload api must exist and be a function');
177         }
746d4a 178         try {
J 179           item.status = UploadResultStatus.UPLOADING;
ba2415 180           const ret = await props.api?.(
746d4a 181             {
935d4f 182               data: {
E 183                 ...(props.uploadParams || {}),
184               },
746d4a 185               file: item.file,
935d4f 186               name: props.name,
E 187               filename: props.filename,
746d4a 188             },
J 189             function onUploadProgress(progressEvent: ProgressEvent) {
190               const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
191               item.percent = complete;
56a966 192             },
746d4a 193           );
ba2415 194           const { data } = ret;
746d4a 195           item.status = UploadResultStatus.SUCCESS;
J 196           item.responseData = data;
197           return {
198             success: true,
199             error: null,
200           };
201         } catch (e) {
202           console.log(e);
203           item.status = UploadResultStatus.ERROR;
204           return {
205             success: false,
206             error: e,
207           };
208         }
209       }
661db0 210
746d4a 211       // 点击开始上传
J 212       async function handleStartUpload() {
661db0 213         const { maxNumber } = props;
91e004 214         if ((fileListRef.value.length + props.previewFileList?.length ?? 0) > maxNumber) {
962f90 215           return createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
661db0 216         }
746d4a 217         try {
J 218           isUploadingRef.value = true;
661db0 219           // 只上传不是成功状态的
V 220           const uploadFileList =
221             fileListRef.value.filter((item) => item.status !== UploadResultStatus.SUCCESS) || [];
746d4a 222           const data = await Promise.all(
661db0 223             uploadFileList.map((item) => {
746d4a 224               return uploadApiByItem(item);
56a966 225             }),
746d4a 226           );
J 227           isUploadingRef.value = false;
228           // 生产环境:抛出错误
661db0 229           const errorList = data.filter((item: any) => !item.success);
V 230           if (errorList.length > 0) throw errorList;
746d4a 231         } catch (e) {
J 232           isUploadingRef.value = false;
233           throw e;
234         }
235       }
661db0 236
746d4a 237       //   点击保存
J 238       function handleOk() {
661db0 239         const { maxNumber } = props;
V 240
241         if (fileListRef.value.length > maxNumber) {
962f90 242           return createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
661db0 243         }
746d4a 244         if (isUploadingRef.value) {
962f90 245           return createMessage.warning(t('component.upload.saveWarn'));
746d4a 246         }
J 247         const fileList: string[] = [];
248
249         for (const item of fileListRef.value) {
250           const { status, responseData } = item;
251           if (status === UploadResultStatus.SUCCESS && responseData) {
252             fileList.push(responseData.url);
253           }
254         }
255         // 存在一个上传成功的即可保存
256         if (fileList.length <= 0) {
962f90 257           return createMessage.warning(t('component.upload.saveError'));
746d4a 258         }
J 259         fileListRef.value = [];
260         closeModal();
661db0 261         emit('change', fileList);
746d4a 262       }
661db0 263
746d4a 264       // 点击关闭:则所有操作不保存,包括上传的
4f20d4 265       async function handleCloseFunc() {
746d4a 266         if (!isUploadingRef.value) {
J 267           fileListRef.value = [];
268           return true;
269         } else {
962f90 270           createMessage.warning(t('component.upload.uploadWait'));
746d4a 271           return false;
J 272         }
273       }
661db0 274
815250 275       return {
e7fbd7 276         columns: createTableColumns(),
B 277         actionColumn: createActionColumn(handleRemove),
746d4a 278         register,
J 279         closeModal,
280         getHelpText,
281         getStringAccept,
661db0 282         getOkButtonProps,
746d4a 283         beforeUpload,
J 284         fileListRef,
285         state,
286         isUploadingRef,
287         handleStartUpload,
288         handleOk,
289         handleCloseFunc,
661db0 290         getIsSelectFile,
V 291         getUploadBtnText,
dc09de 292         t,
746d4a 293       };
J 294     },
295   });
296 </script>
297 <style lang="less">
298   .upload-modal {
299     .ant-upload-list {
300       display: none;
301     }
302
303     .ant-table-wrapper .ant-spin-nested-loading {
304       padding: 0;
305     }
815250 306
J 307     &-toolbar {
308       display: flex;
309       align-items: center;
310       margin-bottom: 8px;
311
312       &__btn {
ba2415 313         flex: 1;
815250 314         margin-left: 8px;
J 315         text-align: right;
316       }
317     }
746d4a 318   }
J 319 </style>