bowen
2023-11-21 2cd5a40322e9b7946e789d7c5023fda0e712af4d
提交 | 用户 | 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"
12       @preview="handlePreview"
13       @remove="handleRemove"
14     >
15       <div v-if="fileList && fileList.length < maxNumber">
16         <plus-outlined />
17         <div style="margin-top: 8px">{{ t('component.upload.upload') }}</div>
18       </div>
19     </Upload>
20     <Modal :open="previewOpen" :title="previewTitle" :footer="null" @cancel="handleCancel">
21       <img alt="" style="width: 100%" :src="previewImage" />
22     </Modal>
23   </div>
24 </template>
25
bab28a 26 <script lang="ts" setup>
dccc8f 27   import { ref, toRefs, watch } from 'vue';
Z 28   import { PlusOutlined } from '@ant-design/icons-vue';
29   import { Upload, Modal } from 'ant-design-vue';
2cd5a4 30   import type { UploadProps, UploadFile } from 'ant-design-vue';
dccc8f 31   import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
Z 32   import { useMessage } from '@/hooks/web/useMessage';
2991bb 33   import { isArray, isFunction, isObject, isString } from '@/utils/is';
dccc8f 34   import { warn } from '@/utils/log';
Z 35   import { useI18n } from '@/hooks/web/useI18n';
36   import { useUploadType } from '../hooks/useUpload';
37   import { uploadContainerProps } from '../props';
38   import { isImgTypeByName } from '../helper';
39
bab28a 40   defineOptions({ name: 'ImageUpload' });
X 41
dccc8f 42   const emit = defineEmits(['change', 'update:value', 'delete']);
Z 43   const props = defineProps({
44     ...uploadContainerProps,
45   });
46   const { t } = useI18n();
47   const { createMessage } = useMessage();
48   const { accept, helpText, maxNumber, maxSize } = toRefs(props);
49   const { getStringAccept } = useUploadType({
50     acceptRef: accept,
51     helpTextRef: helpText,
52     maxNumberRef: maxNumber,
53     maxSizeRef: maxSize,
54   });
55   const previewOpen = ref<boolean>(false);
56   const previewImage = ref<string>('');
57   const previewTitle = ref<string>('');
58
59   const fileList = ref<UploadProps['fileList']>([]);
60   const isLtMsg = ref<boolean>(true);
61   const isActMsg = ref<boolean>(true);
62
63   watch(
64     () => props.value,
65     (v) => {
2991bb 66       if (v && isArray(v)) {
Z 67         fileList.value = v.map((item, i) => {
68           if (item && isString(item)) {
69             return {
70               uid: -i + '',
71               name: item.substring(item.lastIndexOf('/') + 1),
72               status: 'done',
73               url: item,
74             };
75           } else if (item && isObject(item)) {
76             return item;
77           } else {
78             return;
79           }
2cd5a4 80         }) as UploadProps['fileList'];
dccc8f 81       }
Z 82     },
83   );
84
2cd5a4 85   function getBase64<T extends string | ArrayBuffer | null>(file: File) {
B 86     return new Promise<T>((resolve, reject) => {
dccc8f 87       const reader = new FileReader();
Z 88       reader.readAsDataURL(file);
2cd5a4 89       reader.onload = () => {
B 90         resolve(reader.result as T);
91       };
dccc8f 92       reader.onerror = (error) => reject(error);
Z 93     });
94   }
95
2cd5a4 96   const handlePreview = async (file: UploadFile) => {
dccc8f 97     if (!file.url && !file.preview) {
2cd5a4 98       file.preview = await getBase64<string>(file.originFileObj!);
dccc8f 99     }
2cd5a4 100     previewImage.value = file.url || file.preview || '';
dccc8f 101     previewOpen.value = true;
2cd5a4 102     previewTitle.value =
B 103       file.name || previewImage.value.substring(previewImage.value.lastIndexOf('/') + 1);
dccc8f 104   };
Z 105
2cd5a4 106   const handleRemove = async (file: UploadFile) => {
dccc8f 107     if (fileList.value) {
2cd5a4 108       const index = fileList.value.findIndex((item) => item.uid === file.uid);
dccc8f 109       index !== -1 && fileList.value.splice(index, 1);
Z 110       emit('change', fileList.value);
111       emit('delete', file);
112     }
113   };
114
115   const handleCancel = () => {
116     previewOpen.value = false;
117     previewTitle.value = '';
118   };
119
120   const beforeUpload = (file: File) => {
121     const { maxSize, accept } = props;
122     const { name } = file;
2991bb 123     const isAct = isImgTypeByName(name);
Z 124     if (!isAct) {
dccc8f 125       createMessage.error(t('component.upload.acceptUpload', [accept]));
Z 126       isActMsg.value = false;
127       // 防止弹出多个错误提示
128       setTimeout(() => (isActMsg.value = true), 1000);
129     }
2991bb 130     const isLt = file.size / 1024 / 1024 > maxSize;
Z 131     if (isLt) {
dccc8f 132       createMessage.error(t('component.upload.maxSizeMultiple', [maxSize]));
Z 133       isLtMsg.value = false;
134       // 防止弹出多个错误提示
135       setTimeout(() => (isLtMsg.value = true), 1000);
136     }
2991bb 137     return (isAct && !isLt) || Upload.LIST_IGNORE;
dccc8f 138   };
Z 139
140   async function customRequest(info: UploadRequestOption<any>) {
141     const { api } = props;
142     if (!api || !isFunction(api)) {
143       return warn('upload api must exist and be a function');
144     }
145     try {
146       const res = await props.api?.({
147         data: {
148           ...(props.uploadParams || {}),
149         },
150         file: info.file,
151         name: props.name,
152         filename: props.filename,
153       });
154       info.onSuccess!(res.data);
155       emit('change', fileList.value);
156     } catch (e: any) {
2991bb 157       console.log(e);
dccc8f 158       info.onError!(e);
Z 159     }
160   }
161 </script>
162
163 <style lang="less">
164   .ant-upload-select-picture-card i {
165     color: #999;
166     font-size: 32px;
167   }
168
169   .ant-upload-select-picture-card .ant-upload-text {
170     margin-top: 8px;
171     color: #666;
172   }
173 </style>