wip: add upload component
| | |
| | | VITE_PUBLIC_PATH = / |
| | | |
| | | # Cross-domain proxy, you can configure multiple |
| | | VITE_PROXY=[["/api","http://localhost:3000"]] |
| | | VITE_PROXY=[["/api","http://localhost:3000"],["/upload","http://localhost:3001/upload"]] |
| | | # VITE_PROXY=[["/api","https://vvbin.cn/test"]] |
| | | |
| | | # Delete console |
New file |
| | |
| | | export interface UploadApiResult { |
| | | message: string; |
| | | code: number; |
| | | url: string; |
| | | } |
New file |
| | |
| | | import { UploadApiResult } from './model/uploadModel'; |
| | | import { defHttp } from '/@/utils/http/axios'; |
| | | import { UploadFileParams } from '/@/utils/http/axios/types'; |
| | | |
| | | enum Api { |
| | | UPLOAD_URL = '/upload', |
| | | } |
| | | |
| | | /** |
| | | * @description: 上传接口 |
| | | */ |
| | | export function uploadApi( |
| | | params: UploadFileParams, |
| | | onUploadProgress: (progressEvent: ProgressEvent) => void |
| | | ) { |
| | | return defHttp.uploadFile<UploadApiResult>( |
| | | { |
| | | url: Api.UPLOAD_URL, |
| | | onUploadProgress, |
| | | }, |
| | | params |
| | | ); |
| | | } |
| | |
| | | export interface ActionItem { |
| | | on?: any; |
| | | onClick?: any; |
| | | label: string; |
| | | disabled?: boolean; |
| | | color?: 'success' | 'error' | 'warning'; |
New file |
| | |
| | | export { default as UploadContainer } from './src/UploadContainer.vue'; |
| | | // export * from './src/types'; |
New file |
| | |
| | | <template> |
| | | <span> |
| | | <img v-if="fileUrl" :src="fileUrl" /> |
| | | <span v-else>{{ fileType }}</span> |
| | | </span> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent } from 'vue'; |
| | | |
| | | export default defineComponent({ |
| | | props: { |
| | | fileUrl: { |
| | | type: String, |
| | | default: '', |
| | | }, |
| | | fileType: { |
| | | type: String, |
| | | default: '', |
| | | }, |
| | | fileName: { |
| | | type: String, |
| | | default: '', |
| | | }, |
| | | }, |
| | | setup() { |
| | | return {}; |
| | | }, |
| | | }); |
| | | </script> |
New file |
| | |
| | | <template> |
| | | <div> |
| | | <a-button-group> |
| | | <a-button type="primary" @click="openUploadModal">上传</a-button> |
| | | <a-button @click="openPreviewModal"> |
| | | <Icon icon="ant-design:eye-outlined" /> |
| | | </a-button> |
| | | </a-button-group> |
| | | <UploadModal v-bind="$props" @register="registerUploadModal" @change="handleChange" /> |
| | | <UploadPreviewModal |
| | | :value="fileListRef" |
| | | @register="registerPreviewModal" |
| | | @change="handlePreviewChange" |
| | | /> |
| | | </div> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent, ref, watch, unref } from 'vue'; |
| | | import { useModal } from '/@/components/Modal'; |
| | | import UploadModal from './UploadModal.vue'; |
| | | import { uploadContainerProps } from './props'; |
| | | import UploadPreviewModal from './UploadPreviewModal.vue'; |
| | | import Icon from '/@/components/Icon/index'; |
| | | export default defineComponent({ |
| | | components: { UploadModal, UploadPreviewModal, Icon }, |
| | | props: uploadContainerProps, |
| | | setup(props, { emit }) { |
| | | // 上传modal |
| | | const [registerUploadModal, { openModal: openUploadModal }] = useModal(); |
| | | // 预览modal |
| | | const [registerPreviewModal, { openModal: openPreviewModal }] = useModal(); |
| | | |
| | | const fileListRef = ref<string[]>([]); |
| | | watch( |
| | | () => props.value, |
| | | (value) => { |
| | | fileListRef.value = [...(value || [])]; |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | // 上传modal保存操作 |
| | | function handleChange(urls: string[]) { |
| | | fileListRef.value = [...unref(fileListRef), ...(urls || [])]; |
| | | emit('change', fileListRef.value); |
| | | } |
| | | // 预览modal保存操作 |
| | | function handlePreviewChange(urls: string[]) { |
| | | fileListRef.value = [...(urls || [])]; |
| | | emit('change', fileListRef.value); |
| | | } |
| | | return { |
| | | registerUploadModal, |
| | | openUploadModal, |
| | | handleChange, |
| | | handlePreviewChange, |
| | | registerPreviewModal, |
| | | openPreviewModal, |
| | | fileListRef, |
| | | }; |
| | | }, |
| | | }); |
| | | </script> |
New file |
| | |
| | | <template> |
| | | <BasicModal |
| | | v-bind="$attrs" |
| | | @register="register" |
| | | @ok="handleOk" |
| | | :closeFunc="handleCloseFunc" |
| | | :maskClosable="false" |
| | | width="800px" |
| | | title="上传组件" |
| | | wrapClassName="upload-modal" |
| | | :okButtonProps="{ disabled: isUploadingRef }" |
| | | :cancelButtonProps="{ disabled: isUploadingRef }" |
| | | > |
| | | <template #centerdFooter> |
| | | <a-button @click="handleStartUpload" color="success" :loading="isUploadingRef"> |
| | | {{ isUploadingRef ? '上传中' : '开始上传' }} |
| | | </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" /> |
| | | </BasicModal> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent, reactive, ref, toRef, unref } from 'vue'; |
| | | import { Upload } from 'ant-design-vue'; |
| | | import { BasicModal, useModalInner } from '/@/components/Modal'; |
| | | import { BasicTable, useTable } from '/@/components/Table'; |
| | | // hooks |
| | | import { useUploadType } from './useUpload'; |
| | | import { useMessage } from '/@/hooks/web/useMessage'; |
| | | // types |
| | | import { FileItem, UploadResultStatus } from './types'; |
| | | import { basicProps } from './props'; |
| | | import { createTableColumns, createActionColumn } from './data'; |
| | | // utils |
| | | import { checkFileType, checkImgType, getBase64WithFile } from './utils'; |
| | | import { buildUUID } from '/@/utils/uuid'; |
| | | import { createImgPreview } from '/@/components/Preview/index'; |
| | | import { uploadApi } from '/@/api/demo/upload'; |
| | | |
| | | export default defineComponent({ |
| | | components: { BasicModal, Upload, BasicTable }, |
| | | 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 fileListRef = ref<FileItem[]>([]); |
| | | const state = reactive<{ fileList: FileItem[] }>({ fileList: [] }); |
| | | const { createMessage } = useMessage(); |
| | | // 上传前校验 |
| | | function beforeUpload(file: File) { |
| | | const { size, name } = file; |
| | | const { maxSize } = props; |
| | | const accept = unref(getAccept); |
| | | |
| | | // 设置最大值,则判断 |
| | | if (maxSize && file.size / 1024 / 1024 >= maxSize) { |
| | | createMessage.error(`只能上传不超过${maxSize}MB的文件!`); |
| | | return false; |
| | | } |
| | | |
| | | // 设置类型,则判断 |
| | | if (accept.length > 0 && !checkFileType(file, accept)) { |
| | | createMessage.error!(`只能上传${accept.join(',')}格式文件`); |
| | | return false; |
| | | } |
| | | // 生成图片缩略图 |
| | | if (checkImgType(file)) { |
| | | // beforeUpload,如果异步会调用自带上传方法 |
| | | // file.thumbUrl = await getBase64(file); |
| | | getBase64WithFile(file).then(({ result: thumbUrl }) => { |
| | | fileListRef.value = [ |
| | | ...unref(fileListRef), |
| | | { |
| | | uuid: buildUUID(), |
| | | file, |
| | | thumbUrl, |
| | | size, |
| | | name, |
| | | percent: 0, |
| | | type: name.split('.').pop(), |
| | | }, |
| | | ]; |
| | | }); |
| | | } else { |
| | | fileListRef.value = [ |
| | | ...unref(fileListRef), |
| | | { |
| | | uuid: buildUUID(), |
| | | |
| | | file, |
| | | size, |
| | | name, |
| | | percent: 0, |
| | | type: name.split('.').pop(), |
| | | }, |
| | | ]; |
| | | } |
| | | return false; |
| | | } |
| | | // 删除 |
| | | function handleRemove(record: FileItem) { |
| | | const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid); |
| | | index !== -1 && fileListRef.value.splice(index, 1); |
| | | } |
| | | // 预览 |
| | | function handlePreview(record: FileItem) { |
| | | const { thumbUrl = '' } = record; |
| | | createImgPreview({ |
| | | imageList: [thumbUrl], |
| | | }); |
| | | } |
| | | const [registerTable] = useTable({ |
| | | columns: createTableColumns(), |
| | | actionColumn: createActionColumn(handleRemove, handlePreview), |
| | | pagination: false, |
| | | }); |
| | | // 是否正在上传 |
| | | const isUploadingRef = ref(false); |
| | | async function uploadApiByItem(item: FileItem) { |
| | | try { |
| | | item.status = UploadResultStatus.UPLOADING; |
| | | |
| | | const { data } = await uploadApi( |
| | | { |
| | | file: item.file, |
| | | }, |
| | | function onUploadProgress(progressEvent: ProgressEvent) { |
| | | const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0; |
| | | item.percent = complete; |
| | | } |
| | | ); |
| | | item.status = UploadResultStatus.SUCCESS; |
| | | item.responseData = data; |
| | | return { |
| | | success: true, |
| | | error: null, |
| | | }; |
| | | } catch (e) { |
| | | console.log(e); |
| | | item.status = UploadResultStatus.ERROR; |
| | | return { |
| | | success: false, |
| | | error: e, |
| | | }; |
| | | } |
| | | } |
| | | // 点击开始上传 |
| | | async function handleStartUpload() { |
| | | try { |
| | | isUploadingRef.value = true; |
| | | const data = await Promise.all( |
| | | unref(fileListRef).map((item) => { |
| | | return uploadApiByItem(item); |
| | | }) |
| | | ); |
| | | isUploadingRef.value = false; |
| | | // 生产环境:抛出错误 |
| | | const errorList = data.filter((item) => !item.success); |
| | | if (errorList.length > 0) { |
| | | throw errorList; |
| | | } |
| | | } catch (e) { |
| | | isUploadingRef.value = false; |
| | | throw e; |
| | | } |
| | | } |
| | | // 点击保存 |
| | | function handleOk() { |
| | | // TODO: 没起作用:okButtonProps={{ disabled: state.isUploading }} |
| | | if (isUploadingRef.value) { |
| | | createMessage.warning('请等待文件上传后,保存'); |
| | | return; |
| | | } |
| | | const fileList: string[] = []; |
| | | |
| | | for (const item of fileListRef.value) { |
| | | const { status, responseData } = item; |
| | | if (status === UploadResultStatus.SUCCESS && responseData) { |
| | | fileList.push(responseData.url); |
| | | } |
| | | } |
| | | |
| | | // 存在一个上传成功的即可保存 |
| | | |
| | | if (fileList.length <= 0) { |
| | | createMessage.warning('没有上传成功的文件,无法保存'); |
| | | return; |
| | | } |
| | | console.log(fileList); |
| | | emit('change', fileList); |
| | | fileListRef.value = []; |
| | | closeModal(); |
| | | } |
| | | // 点击关闭:则所有操作不保存,包括上传的 |
| | | function handleCloseFunc() { |
| | | if (!isUploadingRef.value) { |
| | | fileListRef.value = []; |
| | | return true; |
| | | } else { |
| | | createMessage.warning('请等待文件上传结束后操作'); |
| | | return false; |
| | | } |
| | | } |
| | | return { |
| | | register, |
| | | closeModal, |
| | | getHelpText, |
| | | getStringAccept, |
| | | beforeUpload, |
| | | registerTable, |
| | | fileListRef, |
| | | state, |
| | | isUploadingRef, |
| | | handleStartUpload, |
| | | handleOk, |
| | | handleCloseFunc, |
| | | }; |
| | | }, |
| | | }); |
| | | </script> |
| | | <style lang="less"> |
| | | // /deep/ .ant-upload-list { |
| | | // display: none; |
| | | // } |
| | | .upload-modal { |
| | | .ant-upload-list { |
| | | display: none; |
| | | } |
| | | |
| | | .ant-table-wrapper .ant-spin-nested-loading { |
| | | padding: 0; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <BasicModal |
| | | wrapClassName="upload-preview-modal" |
| | | v-bind="$attrs" |
| | | width="800px" |
| | | @register="register" |
| | | title="预览" |
| | | :showOkBtn="false" |
| | | > |
| | | <BasicTable @register="registerTable" :dataSource="fileListRef" /> |
| | | </BasicModal> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent, watch, ref, unref } from 'vue'; |
| | | import { BasicTable, useTable } from '/@/components/Table'; |
| | | import { createPreviewColumns, createPreviewActionColumn } from './data'; |
| | | import { BasicModal, useModalInner } from '/@/components/Modal'; |
| | | import { priviewProps } from './props'; |
| | | import { PreviewFileItem } from './types'; |
| | | import { createImgPreview } from '/@/components/Preview/index'; |
| | | import { downloadByUrl } from '/@/utils/file/FileDownload'; |
| | | |
| | | export default defineComponent({ |
| | | components: { BasicModal, BasicTable }, |
| | | props: priviewProps, |
| | | setup(props, { emit }) { |
| | | const [register, { closeModal }] = useModalInner(); |
| | | const fileListRef = ref<PreviewFileItem[]>([]); |
| | | watch( |
| | | () => props.value, |
| | | (value) => { |
| | | fileListRef.value = []; |
| | | value.forEach((item) => { |
| | | fileListRef.value = [ |
| | | ...unref(fileListRef), |
| | | { |
| | | url: item, |
| | | type: item.split('.').pop() || '', |
| | | name: item.split('/').pop() || '', |
| | | }, |
| | | ]; |
| | | }); |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | // 删除 |
| | | function handleRemove(record: PreviewFileItem) { |
| | | const index = fileListRef.value.findIndex((item) => item.url === record.url); |
| | | if (index !== -1) { |
| | | fileListRef.value.splice(index, 1); |
| | | emit( |
| | | 'change', |
| | | fileListRef.value.map((item) => item.url) |
| | | ); |
| | | } |
| | | } |
| | | // 预览 |
| | | function handlePreview(record: PreviewFileItem) { |
| | | const { url = '' } = record; |
| | | createImgPreview({ |
| | | imageList: [url], |
| | | }); |
| | | } |
| | | // 下载 |
| | | function handleDownload(record: PreviewFileItem) { |
| | | const { url = '' } = record; |
| | | downloadByUrl({ url }); |
| | | } |
| | | const [registerTable] = useTable({ |
| | | columns: createPreviewColumns(), |
| | | pagination: false, |
| | | actionColumn: createPreviewActionColumn({ handleRemove, handlePreview, handleDownload }), |
| | | }); |
| | | return { |
| | | register, |
| | | closeModal, |
| | | fileListRef, |
| | | registerTable, |
| | | }; |
| | | }, |
| | | }); |
| | | </script> |
| | | <style lang="less"> |
| | | .upload-preview-modal { |
| | | .ant-upload-list { |
| | | display: none; |
| | | } |
| | | |
| | | .ant-table-wrapper .ant-spin-nested-loading { |
| | | padding: 0; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | // import { BasicColumn, TableAction, ActionItem } from '@/components/table'; |
| | | import { checkImgType, isImgTypeByName } from './utils'; |
| | | // import ThumnUrl from './ThumbUrl.vue'; |
| | | import { Progress } from 'ant-design-vue'; |
| | | import { FileItem, PreviewFileItem, UploadResultStatus } from './types'; |
| | | // import { ElecArchivesSaveResult } from '@/api/biz/file/model/fileModel'; |
| | | // import { quryFile } from '@/api/biz/file/file'; |
| | | import { BasicColumn, ActionItem, TableAction } from '/@/components/Table/index'; |
| | | |
| | | // 文件上传列表 |
| | | export function createTableColumns(): BasicColumn[] { |
| | | return [ |
| | | { |
| | | dataIndex: 'thumbUrl', |
| | | title: '图例', |
| | | width: 100, |
| | | customRender: ({ record }) => { |
| | | const { thumbUrl, type } = (record as FileItem) || {}; |
| | | return <span>{thumbUrl ? <img src={thumbUrl} style={{ width: '50px' }} /> : type}</span>; |
| | | // return <ThumnUrl fileUrl={thumbUrl} fileType={type} fileName={type} />; |
| | | }, |
| | | }, |
| | | { |
| | | dataIndex: 'name', |
| | | title: '文件名', |
| | | align: 'left', |
| | | customRender: ({ text, record }) => { |
| | | const { percent, status: uploadStatus } = (record as FileItem) || {}; |
| | | let status = 'normal'; |
| | | if (uploadStatus === UploadResultStatus.ERROR) { |
| | | status = 'exception'; |
| | | } else if (uploadStatus === UploadResultStatus.UPLOADING) { |
| | | status = 'active'; |
| | | } else if (uploadStatus === UploadResultStatus.SUCCESS) { |
| | | status = 'success'; |
| | | } |
| | | return ( |
| | | <span> |
| | | <p class="ellipsis mb-1" title={text}> |
| | | {text} |
| | | </p> |
| | | <Progress percent={percent} size="small" status={status} /> |
| | | </span> |
| | | ); |
| | | }, |
| | | }, |
| | | { |
| | | dataIndex: 'size', |
| | | title: '文件大小', |
| | | width: 100, |
| | | customRender: ({ text = 0 }) => { |
| | | return text && (text / 1024).toFixed(2) + 'KB'; |
| | | }, |
| | | }, |
| | | // { |
| | | // dataIndex: 'type', |
| | | // title: '文件类型', |
| | | // width: 100, |
| | | // }, |
| | | { |
| | | dataIndex: 'status', |
| | | title: '状态', |
| | | width: 100, |
| | | customRender: ({ text }) => { |
| | | if (text === UploadResultStatus.SUCCESS) { |
| | | return '上传成功'; |
| | | } else if (text === UploadResultStatus.ERROR) { |
| | | return '上传失败'; |
| | | } else if (text === UploadResultStatus.UPLOADING) { |
| | | return '上传中'; |
| | | } |
| | | |
| | | return text; |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | export function createActionColumn(handleRemove: Function, handlePreview: Function): BasicColumn { |
| | | return { |
| | | width: 120, |
| | | title: '操作', |
| | | dataIndex: 'action', |
| | | fixed: false, |
| | | customRender: ({ record }) => { |
| | | const actions: ActionItem[] = [ |
| | | { |
| | | label: '删除', |
| | | onClick: handleRemove.bind(null, record), |
| | | }, |
| | | ]; |
| | | if (checkImgType(record)) { |
| | | actions.unshift({ |
| | | label: '预览', |
| | | onClick: handlePreview.bind(null, record), |
| | | }); |
| | | } |
| | | return <TableAction actions={actions} />; |
| | | }, |
| | | }; |
| | | } |
| | | // 文件预览列表 |
| | | export function createPreviewColumns(): BasicColumn[] { |
| | | return [ |
| | | { |
| | | dataIndex: 'url', |
| | | title: '图例', |
| | | width: 100, |
| | | customRender: ({ record }) => { |
| | | const { url, type } = (record as PreviewFileItem) || {}; |
| | | return ( |
| | | <span>{isImgTypeByName(url) ? <img src={url} style={{ width: '50px' }} /> : type}</span> |
| | | ); |
| | | }, |
| | | }, |
| | | { |
| | | dataIndex: 'name', |
| | | title: '文件名', |
| | | align: 'left', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | export function createPreviewActionColumn({ |
| | | handleRemove, |
| | | handlePreview, |
| | | handleDownload, |
| | | }: { |
| | | handleRemove: Function; |
| | | handlePreview: Function; |
| | | handleDownload: Function; |
| | | }): BasicColumn { |
| | | return { |
| | | width: 160, |
| | | title: '操作', |
| | | dataIndex: 'action', |
| | | fixed: false, |
| | | customRender: ({ record }) => { |
| | | const { url } = (record as PreviewFileItem) || {}; |
| | | |
| | | const actions: ActionItem[] = [ |
| | | { |
| | | label: '删除', |
| | | onClick: handleRemove.bind(null, record), |
| | | }, |
| | | { |
| | | label: '下载', |
| | | onClick: handleDownload.bind(null, record), |
| | | }, |
| | | ]; |
| | | if (isImgTypeByName(url)) { |
| | | actions.unshift({ |
| | | label: '预览', |
| | | onClick: handlePreview.bind(null, record), |
| | | }); |
| | | } |
| | | return <TableAction actions={actions} />; |
| | | }, |
| | | }; |
| | | } |
New file |
| | |
| | | import type { PropType } from 'vue'; |
| | | |
| | | export const basicProps = { |
| | | helpText: { |
| | | type: String as PropType<string>, |
| | | default: '', |
| | | }, |
| | | // 文件最大多少MB |
| | | maxSize: { |
| | | type: Number as PropType<number>, |
| | | default: 2, |
| | | }, |
| | | // 最大数量的文件,0不限制 |
| | | maxNumber: { |
| | | type: Number as PropType<number>, |
| | | default: 0, |
| | | }, |
| | | // 根据后缀,或者其他 |
| | | accept: { |
| | | type: Array as PropType<string[]>, |
| | | default: () => [], |
| | | }, |
| | | multiple: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | }; |
| | | |
| | | export const uploadContainerProps = { |
| | | value: { |
| | | type: Array as PropType<string[]>, |
| | | default: () => [], |
| | | }, |
| | | ...basicProps, |
| | | }; |
| | | |
| | | export const priviewProps = { |
| | | value: { |
| | | type: Array as PropType<string[]>, |
| | | default: () => [], |
| | | }, |
| | | }; |
New file |
| | |
| | | import { UploadApiResult } from '/@/api/demo/model/uploadModel'; |
| | | |
| | | export enum UploadResultStatus { |
| | | SUCCESS = 'success', |
| | | ERROR = 'error', |
| | | UPLOADING = 'uploading', |
| | | } |
| | | |
| | | export interface FileItem { |
| | | thumbUrl?: string; |
| | | name: string; |
| | | size: string | number; |
| | | type?: string; |
| | | percent: number; |
| | | file: File; |
| | | status?: UploadResultStatus; |
| | | responseData?: UploadApiResult; |
| | | uuid: string; |
| | | } |
| | | |
| | | export interface PreviewFileItem { |
| | | url: string; |
| | | name: string; |
| | | type: string; |
| | | } |
New file |
| | |
| | | import { Ref, unref, computed } from 'vue'; |
| | | |
| | | export function useUploadType({ |
| | | acceptRef, |
| | | // uploadTypeRef, |
| | | helpTextRef, |
| | | maxNumberRef, |
| | | maxSizeRef, |
| | | }: { |
| | | acceptRef: Ref<string[]>; |
| | | // uploadTypeRef: Ref<UploadTypeEnum>; |
| | | helpTextRef: Ref<string>; |
| | | maxNumberRef: Ref<number>; |
| | | maxSizeRef: Ref<number>; |
| | | }) { |
| | | // 文件类型限制 |
| | | const getAccept = computed(() => { |
| | | // const uploadType = unref(uploadTypeRef); |
| | | const accept = unref(acceptRef); |
| | | if (accept && accept.length > 0) { |
| | | return accept; |
| | | } |
| | | return []; |
| | | }); |
| | | const getStringAccept = computed(() => { |
| | | return unref(getAccept) |
| | | .map((item) => `.${item}`) |
| | | .join(','); |
| | | }); |
| | | // 支持jpg、jpeg、png格式,不超过2M,最多可选择10张图片,。 |
| | | const getHelpText = computed(() => { |
| | | const helpText = unref(helpTextRef); |
| | | if (helpText) { |
| | | return helpText; |
| | | } |
| | | const helpTexts: string[] = []; |
| | | |
| | | const accept = unref(acceptRef); |
| | | if (accept.length > 0) { |
| | | helpTexts.push(`支持${accept.join(',')}格式`); |
| | | } |
| | | |
| | | const maxSize = unref(maxSizeRef); |
| | | if (maxSize) { |
| | | helpTexts.push(`不超过${maxSize}MB`); |
| | | } |
| | | |
| | | const maxNumber = unref(maxNumberRef); |
| | | if (maxNumber) { |
| | | helpTexts.push(`最多可选择${maxNumber}个文件`); |
| | | } |
| | | return helpTexts.join(','); |
| | | }); |
| | | return { getAccept, getStringAccept, getHelpText }; |
| | | } |
New file |
| | |
| | | export function checkFileType(file: File, accepts: string[]) { |
| | | const newTypes = accepts.join('|'); |
| | | // const reg = /\.(jpg|jpeg|png|gif|txt|doc|docx|xls|xlsx|xml)$/i; |
| | | const reg = new RegExp('\\.(' + newTypes + ')$', 'i'); |
| | | |
| | | if (!reg.test(file.name)) { |
| | | return false; |
| | | } else { |
| | | return true; |
| | | } |
| | | } |
| | | export function checkImgType(file: File) { |
| | | return /\.(jpg|jpeg|png|gif)$/i.test(file.name); |
| | | } |
| | | export function isImgTypeByName(name: string) { |
| | | return /\.(jpg|jpeg|png|gif)$/i.test(name); |
| | | } |
| | | export function getBase64WithFile(file: File) { |
| | | return new Promise<{ |
| | | result: string; |
| | | file: File; |
| | | }>((resolve, reject) => { |
| | | const reader = new FileReader(); |
| | | reader.readAsDataURL(file); |
| | | reader.onload = () => resolve({ result: reader.result as string, file }); |
| | | reader.onerror = (error) => reject(error); |
| | | }); |
| | | } |
| | |
| | | name: '密码强度组件', |
| | | }, |
| | | { |
| | | path: 'upload', |
| | | name: '上传组件', |
| | | }, |
| | | { |
| | | path: 'scroll', |
| | | name: '滚动组件', |
| | | children: [ |
| | |
| | | title: '密码强度组件', |
| | | }, |
| | | }, |
| | | { |
| | | path: '/upload', |
| | | name: 'UploadDemo', |
| | | component: () => import('/@/views/demo/comp/upload/index.vue'), |
| | | meta: { |
| | | title: '上传组件', |
| | | }, |
| | | }, |
| | | ], |
| | | } as AppRouteModule; |
| | |
| | | import { isFunction } from '/@/utils/is'; |
| | | import { cloneDeep } from 'lodash-es'; |
| | | |
| | | import type { RequestOptions, CreateAxiosOptions, Result } from './types'; |
| | | import type { RequestOptions, CreateAxiosOptions, Result, UploadFileParams } from './types'; |
| | | // import { ContentTypeEnum } from '/@/enums/httpEnum'; |
| | | import { errorResult } from './const'; |
| | | import { ContentTypeEnum } from '/@/enums/httpEnum'; |
| | | |
| | | export * from './axiosTransform'; |
| | | |
| | |
| | | this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch); |
| | | } |
| | | |
| | | // /** |
| | | // * @description: 文件上传 |
| | | // */ |
| | | // uploadFiles(config: AxiosRequestConfig, params: File[]) { |
| | | // const formData = new FormData(); |
| | | /** |
| | | * @description: 文件上传 |
| | | */ |
| | | uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) { |
| | | const formData = new window.FormData(); |
| | | |
| | | // Object.keys(params).forEach((key) => { |
| | | // formData.append(key, params[key as any]); |
| | | // }); |
| | | if (params.data) { |
| | | Object.keys(params.data).forEach((key) => { |
| | | if (!params.data) return; |
| | | const value = params.data[key]; |
| | | // support key-value array data |
| | | if (Array.isArray(value)) { |
| | | value.forEach((item) => { |
| | | // { list: [ 11, 22 ] } |
| | | // formData.append('list[]', 11); |
| | | formData.append(`${key}[]`, item); |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // return this.request({ |
| | | // ...config, |
| | | // method: 'POST', |
| | | // data: formData, |
| | | // headers: { |
| | | // 'Content-type': ContentTypeEnum.FORM_DATA, |
| | | // }, |
| | | // }); |
| | | // } |
| | | formData.append(key, params.data[key]); |
| | | }); |
| | | } |
| | | |
| | | formData.append(params.name || 'file', params.file, params.filename); |
| | | |
| | | return this.axiosInstance.request<T>({ |
| | | ...config, |
| | | method: 'POST', |
| | | data: formData, |
| | | headers: { |
| | | 'Content-type': ContentTypeEnum.FORM_DATA, |
| | | ignoreCancelToken: true, |
| | | }, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * @description: 请求方法 |
| | |
| | | message: string; |
| | | result: T; |
| | | } |
| | | // multipart/form-data:上传文件 |
| | | export interface UploadFileParams { |
| | | // 其他参数 |
| | | data?: { [key: string]: any }; |
| | | // 文件参数的接口字段名 |
| | | name?: string; |
| | | // 文件 |
| | | file: File | Blob; |
| | | // 文件名 |
| | | filename?: string; |
| | | } |
New file |
| | |
| | | <template> |
| | | <div class="p-4"> |
| | | <UploadContainer :maxSize="5" /> |
| | | </div> |
| | | </template> |
| | | <script lang="ts"> |
| | | import { defineComponent } from 'vue'; |
| | | import { UploadContainer } from '/@/components/Upload/index'; |
| | | |
| | | // import { Alert } from 'ant-design-vue'; |
| | | export default defineComponent({ |
| | | components: { UploadContainer }, |
| | | setup() { |
| | | return {}; |
| | | }, |
| | | }); |
| | | </script> |