| | |
| | | <template> |
| | | <BasicModal |
| | | width="800px" |
| | | title="上传" |
| | | okText="保存" |
| | | v-bind="$attrs" |
| | | @register="register" |
| | | @ok="handleOk" |
| | | :closeFunc="handleCloseFunc" |
| | | :maskClosable="false" |
| | | width="800px" |
| | | title="上传组件" |
| | | :keyboard="false" |
| | | wrapClassName="upload-modal" |
| | | :okButtonProps="{ disabled: isUploadingRef }" |
| | | :okButtonProps="getOkButtonProps" |
| | | :cancelButtonProps="{ disabled: isUploadingRef }" |
| | | > |
| | | <template #centerdFooter> |
| | | <a-button @click="handleStartUpload" color="success" :loading="isUploadingRef"> |
| | | {{ isUploadingRef ? '上传中' : '开始上传' }} |
| | | <a-button |
| | | @click="handleStartUpload" |
| | | color="success" |
| | | :disabled="!getIsSelectFile" |
| | | :loading="isUploadingRef" |
| | | > |
| | | {{ getUploadBtnText }} |
| | | </a-button> |
| | | </template> |
| | | <Upload :accept="getStringAccept" :multiple="multiple" :before-upload="beforeUpload"> |
| | | <a-button type="primary"> 选择文件 </a-button> |
| | | <span class="px-2">{{ getHelpText }}</span> |
| | | </Upload> |
| | | <BasicTable @register="registerTable" :dataSource="fileListRef" /> |
| | | |
| | | <BasicTable @register="registerTable" :dataSource="fileListRef"> |
| | | <template #toolbar> |
| | | <Upload :accept="getStringAccept" :multiple="multiple" :before-upload="beforeUpload"> |
| | | <a-button type="primary"> 选择文件 </a-button> |
| | | </Upload> |
| | | </template> |
| | | <template #tableTitle> |
| | | <Alert :message="getHelpText" type="info" banner></Alert> |
| | | </template> |
| | | </BasicTable> |
| | | </BasicModal> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent, reactive, ref, toRef, unref } from 'vue'; |
| | | import { Upload } from 'ant-design-vue'; |
| | | import { defineComponent, reactive, ref, toRefs, unref, computed } from 'vue'; |
| | | import { Upload, Alert } from 'ant-design-vue'; |
| | | import { BasicModal, useModalInner } from '/@/components/Modal'; |
| | | import { BasicTable, useTable } from '/@/components/Table'; |
| | | // hooks |
| | |
| | | import { checkFileType, checkImgType, getBase64WithFile } from './utils'; |
| | | import { buildUUID } from '/@/utils/uuid'; |
| | | import { createImgPreview } from '/@/components/Preview/index'; |
| | | import { uploadApi } from '/@/api/demo/upload'; |
| | | import { uploadApi } from '/@/api/sys/upload'; |
| | | import { isFunction } from '/@/utils/is'; |
| | | import { warn } from '/@/utils/log'; |
| | | |
| | | export default defineComponent({ |
| | | components: { BasicModal, Upload, BasicTable }, |
| | | components: { BasicModal, Upload, BasicTable, Alert }, |
| | | props: basicProps, |
| | | setup(props, { emit }) { |
| | | const [register, { closeModal }] = useModalInner(); |
| | | const { getAccept, getStringAccept, getHelpText } = useUploadType({ |
| | | acceptRef: toRef(props, 'accept'), |
| | | helpTextRef: toRef(props, 'helpText'), |
| | | maxNumberRef: toRef(props, 'maxNumber'), |
| | | maxSizeRef: toRef(props, 'maxSize'), |
| | | // 是否正在上传 |
| | | const isUploadingRef = ref(false); |
| | | const fileListRef = ref<FileItem[]>([]); |
| | | const state = reactive<{ fileList: FileItem[] }>({ |
| | | fileList: [], |
| | | }); |
| | | |
| | | const fileListRef = ref<FileItem[]>([]); |
| | | const state = reactive<{ fileList: FileItem[] }>({ fileList: [] }); |
| | | const [register, { closeModal }] = useModalInner(); |
| | | |
| | | const { accept, helpText, maxNumber, maxSize } = toRefs(props); |
| | | const { getAccept, getStringAccept, getHelpText } = useUploadType({ |
| | | acceptRef: accept, |
| | | helpTextRef: helpText, |
| | | maxNumberRef: maxNumber, |
| | | maxSizeRef: maxSize, |
| | | }); |
| | | |
| | | const { createMessage } = useMessage(); |
| | | |
| | | const getIsSelectFile = computed(() => { |
| | | return ( |
| | | fileListRef.value.length > 0 && |
| | | !fileListRef.value.every((item) => item.status === UploadResultStatus.SUCCESS) |
| | | ); |
| | | }); |
| | | |
| | | const getOkButtonProps = computed(() => { |
| | | const someSuccess = fileListRef.value.some( |
| | | (item) => item.status === UploadResultStatus.SUCCESS |
| | | ); |
| | | return { |
| | | disabled: isUploadingRef.value || fileListRef.value.length === 0 || !someSuccess, |
| | | }; |
| | | }); |
| | | |
| | | const getUploadBtnText = computed(() => { |
| | | const someError = fileListRef.value.some( |
| | | (item) => item.status === UploadResultStatus.ERROR |
| | | ); |
| | | return isUploadingRef.value ? '上传中' : someError ? '重新上传失败文件' : '开始上传'; |
| | | }); |
| | | |
| | | // 上传前校验 |
| | | function beforeUpload(file: File) { |
| | | const { size, name } = file; |
| | |
| | | createMessage.error!(`只能上传${accept.join(',')}格式文件`); |
| | | return false; |
| | | } |
| | | const commonItem = { |
| | | uuid: buildUUID(), |
| | | file, |
| | | size, |
| | | name, |
| | | percent: 0, |
| | | type: name.split('.').pop(), |
| | | }; |
| | | // 生成图片缩略图 |
| | | if (checkImgType(file)) { |
| | | // beforeUpload,如果异步会调用自带上传方法 |
| | |
| | | fileListRef.value = [ |
| | | ...unref(fileListRef), |
| | | { |
| | | uuid: buildUUID(), |
| | | file, |
| | | thumbUrl, |
| | | size, |
| | | name, |
| | | percent: 0, |
| | | type: name.split('.').pop(), |
| | | ...commonItem, |
| | | }, |
| | | ]; |
| | | }); |
| | | } else { |
| | | fileListRef.value = [ |
| | | ...unref(fileListRef), |
| | | { |
| | | uuid: buildUUID(), |
| | | |
| | | file, |
| | | size, |
| | | name, |
| | | percent: 0, |
| | | type: name.split('.').pop(), |
| | | }, |
| | | ]; |
| | | fileListRef.value = [...unref(fileListRef), commonItem]; |
| | | } |
| | | return false; |
| | | } |
| | |
| | | const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid); |
| | | index !== -1 && fileListRef.value.splice(index, 1); |
| | | } |
| | | |
| | | // 预览 |
| | | function handlePreview(record: FileItem) { |
| | | const { thumbUrl = '' } = record; |
| | |
| | | imageList: [thumbUrl], |
| | | }); |
| | | } |
| | | const [registerTable] = useTable({ |
| | | columns: createTableColumns(), |
| | | actionColumn: createActionColumn(handleRemove, handlePreview), |
| | | pagination: false, |
| | | }); |
| | | // 是否正在上传 |
| | | const isUploadingRef = ref(false); |
| | | |
| | | async function uploadApiByItem(item: FileItem) { |
| | | const { api } = props; |
| | | if (!api || !isFunction(api)) { |
| | | return warn('upload api must exist and be a function'); |
| | | } |
| | | try { |
| | | item.status = UploadResultStatus.UPLOADING; |
| | | |
| | | const { data } = await uploadApi( |
| | | { |
| | | ...(props.uploadParams || {}), |
| | | file: item.file, |
| | | }, |
| | | function onUploadProgress(progressEvent: ProgressEvent) { |
| | |
| | | }; |
| | | } |
| | | } |
| | | |
| | | // 点击开始上传 |
| | | async function handleStartUpload() { |
| | | const { maxNumber } = props; |
| | | if (fileListRef.value.length > maxNumber) { |
| | | return createMessage.warning(`最多只能上传${maxNumber}个文件`); |
| | | } |
| | | try { |
| | | isUploadingRef.value = true; |
| | | // 只上传不是成功状态的 |
| | | const uploadFileList = |
| | | fileListRef.value.filter((item) => item.status !== UploadResultStatus.SUCCESS) || []; |
| | | const data = await Promise.all( |
| | | unref(fileListRef).map((item) => { |
| | | uploadFileList.map((item) => { |
| | | return uploadApiByItem(item); |
| | | }) |
| | | ); |
| | | isUploadingRef.value = false; |
| | | // 生产环境:抛出错误 |
| | | const errorList = data.filter((item) => !item.success); |
| | | if (errorList.length > 0) { |
| | | throw errorList; |
| | | } |
| | | const errorList = data.filter((item: any) => !item.success); |
| | | if (errorList.length > 0) throw errorList; |
| | | } catch (e) { |
| | | isUploadingRef.value = false; |
| | | throw e; |
| | | } |
| | | } |
| | | |
| | | // 点击保存 |
| | | function handleOk() { |
| | | // TODO: 没起作用:okButtonProps={{ disabled: state.isUploading }} |
| | | const { maxNumber } = props; |
| | | |
| | | if (fileListRef.value.length > maxNumber) { |
| | | return createMessage.warning(`最多只能上传${maxNumber}个文件`); |
| | | } |
| | | if (isUploadingRef.value) { |
| | | createMessage.warning('请等待文件上传后,保存'); |
| | | return; |
| | | return createMessage.warning('请等待文件上传后,保存'); |
| | | } |
| | | const fileList: string[] = []; |
| | | |
| | |
| | | fileList.push(responseData.url); |
| | | } |
| | | } |
| | | |
| | | // 存在一个上传成功的即可保存 |
| | | |
| | | if (fileList.length <= 0) { |
| | | createMessage.warning('没有上传成功的文件,无法保存'); |
| | | return; |
| | | return createMessage.warning('没有上传成功的文件,无法保存'); |
| | | } |
| | | console.log(fileList); |
| | | emit('change', fileList); |
| | | fileListRef.value = []; |
| | | closeModal(); |
| | | emit('change', fileList); |
| | | } |
| | | |
| | | // 点击关闭:则所有操作不保存,包括上传的 |
| | | function handleCloseFunc() { |
| | | if (!isUploadingRef.value) { |
| | |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | const [registerTable] = useTable({ |
| | | columns: createTableColumns(), |
| | | actionColumn: createActionColumn(handleRemove, handlePreview), |
| | | pagination: false, |
| | | inset: true, |
| | | scroll: { |
| | | y: 3000, |
| | | }, |
| | | }); |
| | | return { |
| | | register, |
| | | closeModal, |
| | | getHelpText, |
| | | getStringAccept, |
| | | getOkButtonProps, |
| | | beforeUpload, |
| | | registerTable, |
| | | fileListRef, |
| | |
| | | handleStartUpload, |
| | | handleOk, |
| | | handleCloseFunc, |
| | | getIsSelectFile, |
| | | getUploadBtnText, |
| | | }; |
| | | }, |
| | | }); |
| | | </script> |
| | | <style lang="less"> |
| | | // /deep/ .ant-upload-list { |
| | | // display: none; |
| | | // } |
| | | .upload-modal { |
| | | .ant-upload-list { |
| | | display: none; |