clddup
2024-07-19 baf406e7e271ac90faa3aec31ceb44715331d9d0
提交 | 用户 | age
dccc8f 1 <template>
Z 2   <div>
3     <Upload
4       v-bind="$attrs"
5       v-model:file-list="fileList"
6       :list-type="listType"
7       :accept="getStringAccept"
2991bb 8       :multiple="multiple"
Z 9       :maxCount="maxNumber"
dccc8f 10       :before-upload="beforeUpload"
Z 11       :custom-request="customRequest"
4348d2 12       :disabled="disabled"
dccc8f 13       @preview="handlePreview"
Z 14       @remove="handleRemove"
15     >
16       <div v-if="fileList && fileList.length < maxNumber">
17         <plus-outlined />
18         <div style="margin-top: 8px">{{ t('component.upload.upload') }}</div>
19       </div>
20     </Upload>
21     <Modal :open="previewOpen" :title="previewTitle" :footer="null" @cancel="handleCancel">
22       <img alt="" style="width: 100%" :src="previewImage" />
23     </Modal>
24   </div>
25 </template>
26
bab28a 27 <script lang="ts" setup>
dccc8f 28   import { ref, toRefs, watch } from 'vue';
Z 29   import { PlusOutlined } from '@ant-design/icons-vue';
8b516b 30   import type { UploadFile, UploadProps } from 'ant-design-vue';
1 31   import { Modal, Upload } from 'ant-design-vue';
dccc8f 32   import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
Z 33   import { useMessage } from '@/hooks/web/useMessage';
2991bb 34   import { isArray, isFunction, isObject, isString } from '@/utils/is';
dccc8f 35   import { warn } from '@/utils/log';
Z 36   import { useI18n } from '@/hooks/web/useI18n';
37   import { useUploadType } from '../hooks/useUpload';
38   import { uploadContainerProps } from '../props';
302e21 39   import { checkFileType } from '../helper';
8b516b 40   import { UploadResultStatus } from '@/components/Upload/src/types/typing';
302e21 41   import { get, omit } from 'lodash-es';
1 42
bab28a 43   defineOptions({ name: 'ImageUpload' });
X 44
dccc8f 45   const emit = defineEmits(['change', 'update:value', 'delete']);
Z 46   const props = defineProps({
302e21 47     ...omit(uploadContainerProps, ['previewColumns', 'beforePreviewData']),
dccc8f 48   });
Z 49   const { t } = useI18n();
50   const { createMessage } = useMessage();
51   const { accept, helpText, maxNumber, maxSize } = toRefs(props);
8b516b 52   const isInnerOperate = ref<boolean>(false);
dccc8f 53   const { getStringAccept } = useUploadType({
Z 54     acceptRef: accept,
55     helpTextRef: helpText,
56     maxNumberRef: maxNumber,
57     maxSizeRef: maxSize,
58   });
59   const previewOpen = ref<boolean>(false);
60   const previewImage = ref<string>('');
61   const previewTitle = ref<string>('');
62
63   const fileList = ref<UploadProps['fileList']>([]);
64   const isLtMsg = ref<boolean>(true);
65   const isActMsg = ref<boolean>(true);
baf406 66   const isFirstRender = ref<boolean>(true);
dccc8f 67
Z 68   watch(
69     () => props.value,
70     (v) => {
8b516b 71       if (isInnerOperate.value) {
1 72         isInnerOperate.value = false;
73         return;
74       }
06018a 75       let value: string[] = [];
8b516b 76       if (v) {
1 77         if (isArray(v)) {
78           value = v;
79         } else {
80           value.push(v);
81         }
82         fileList.value = value.map((item, i) => {
2991bb 83           if (item && isString(item)) {
Z 84             return {
85               uid: -i + '',
86               name: item.substring(item.lastIndexOf('/') + 1),
87               status: 'done',
88               url: item,
89             };
90           } else if (item && isObject(item)) {
91             return item;
92           } else {
93             return;
94           }
2cd5a4 95         }) as UploadProps['fileList'];
dccc8f 96       }
06018a 97       emit('update:value', value);
baf406 98       if (!isFirstRender.value) {
cca7f5 99         emit('change', value);
baf406 100         isFirstRender.value = false;
cca7f5 101       }
dccc8f 102     },
baf406 103     {
C 104       immediate: true,
b5c87c 105       deep: true,
1 106     },
dccc8f 107   );
Z 108
2cd5a4 109   function getBase64<T extends string | ArrayBuffer | null>(file: File) {
B 110     return new Promise<T>((resolve, reject) => {
dccc8f 111       const reader = new FileReader();
Z 112       reader.readAsDataURL(file);
2cd5a4 113       reader.onload = () => {
B 114         resolve(reader.result as T);
115       };
dccc8f 116       reader.onerror = (error) => reject(error);
Z 117     });
118   }
119
2cd5a4 120   const handlePreview = async (file: UploadFile) => {
dccc8f 121     if (!file.url && !file.preview) {
2cd5a4 122       file.preview = await getBase64<string>(file.originFileObj!);
dccc8f 123     }
2cd5a4 124     previewImage.value = file.url || file.preview || '';
dccc8f 125     previewOpen.value = true;
2cd5a4 126     previewTitle.value =
B 127       file.name || previewImage.value.substring(previewImage.value.lastIndexOf('/') + 1);
dccc8f 128   };
Z 129
2cd5a4 130   const handleRemove = async (file: UploadFile) => {
dccc8f 131     if (fileList.value) {
2cd5a4 132       const index = fileList.value.findIndex((item) => item.uid === file.uid);
dccc8f 133       index !== -1 && fileList.value.splice(index, 1);
8b516b 134       const value = getValue();
1 135       isInnerOperate.value = true;
b5c87c 136       emit('update:value', value);
8b516b 137       emit('change', value);
dccc8f 138       emit('delete', file);
Z 139     }
140   };
141
142   const handleCancel = () => {
143     previewOpen.value = false;
144     previewTitle.value = '';
145   };
146
147   const beforeUpload = (file: File) => {
148     const { maxSize, accept } = props;
302e21 149     const isAct = checkFileType(file, accept);
2991bb 150     if (!isAct) {
dccc8f 151       createMessage.error(t('component.upload.acceptUpload', [accept]));
Z 152       isActMsg.value = false;
153       // 防止弹出多个错误提示
154       setTimeout(() => (isActMsg.value = true), 1000);
155     }
2991bb 156     const isLt = file.size / 1024 / 1024 > maxSize;
Z 157     if (isLt) {
dccc8f 158       createMessage.error(t('component.upload.maxSizeMultiple', [maxSize]));
Z 159       isLtMsg.value = false;
160       // 防止弹出多个错误提示
161       setTimeout(() => (isLtMsg.value = true), 1000);
162     }
2991bb 163     return (isAct && !isLt) || Upload.LIST_IGNORE;
dccc8f 164   };
Z 165
166   async function customRequest(info: UploadRequestOption<any>) {
06018a 167     const { api, uploadParams = {}, name, filename, resultField } = props;
dccc8f 168     if (!api || !isFunction(api)) {
Z 169       return warn('upload api must exist and be a function');
170     }
171     try {
06018a 172       const res = await api?.({
dccc8f 173         data: {
06018a 174           ...uploadParams,
dccc8f 175         },
Z 176         file: info.file,
06018a 177         name: name,
E 178         filename: filename,
dccc8f 179       });
302e21 180       if (props.resultField) {
06018a 181         let result = get(res, resultField);
E 182         info.onSuccess!(result);
302e21 183       } else {
b28a46 184         // 不传入 resultField 的情况
E 185         info.onSuccess!(res.data);
186       }
8b516b 187       const value = getValue();
1 188       isInnerOperate.value = true;
b5c87c 189       emit('update:value', value);
8b516b 190       emit('change', value);
dccc8f 191     } catch (e: any) {
2991bb 192       console.log(e);
dccc8f 193       info.onError!(e);
Z 194     }
195   }
8b516b 196
1 197   function getValue() {
198     const list = (fileList.value || [])
199       .filter((item) => item?.status === UploadResultStatus.DONE)
200       .map((item: any) => {
ef5285 201         if (item?.response && props?.resultField) {
Z 202           return item?.response;
b28a46 203         }
8b516b 204         return item?.url || item?.response?.url;
1 205       });
06018a 206     return list;
8b516b 207   }
dccc8f 208 </script>
Z 209
210 <style lang="less">
211   .ant-upload-select-picture-card i {
212     color: #999;
213     font-size: 32px;
214   }
215
216   .ant-upload-select-picture-card .ant-upload-text {
217     margin-top: 8px;
218     color: #666;
219   }
220 </style>