feat: add input upload
							parent
							
								
									3c4f954b77
								
							
						
					
					
						commit
						ba18eb37da
					
				| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
import type { UploadFile, UploadProps } from 'ant-design-vue';
 | 
			
		||||
import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
 | 
			
		||||
 | 
			
		||||
import type { AxiosResponse } from '@vben/request';
 | 
			
		||||
import type { FileUploadProps } from './typing';
 | 
			
		||||
 | 
			
		||||
import type { AxiosProgressEvent } from '#/api/infra/file';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -20,44 +20,19 @@ import { useUpload, useUploadType } from './use-upload';
 | 
			
		|||
 | 
			
		||||
defineOptions({ name: 'FileUpload', inheritAttrs: false });
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(
 | 
			
		||||
  defineProps<{
 | 
			
		||||
    // 根据后缀,或者其他
 | 
			
		||||
    accept?: string[];
 | 
			
		||||
    api?: (
 | 
			
		||||
      file: File,
 | 
			
		||||
      onUploadProgress?: AxiosProgressEvent,
 | 
			
		||||
    ) => Promise<AxiosResponse<any>>;
 | 
			
		||||
    // 上传的目录
 | 
			
		||||
    directory?: string;
 | 
			
		||||
    disabled?: boolean;
 | 
			
		||||
    helpText?: string;
 | 
			
		||||
    // 最大数量的文件,Infinity不限制
 | 
			
		||||
    maxNumber?: number;
 | 
			
		||||
    // 文件最大多少MB
 | 
			
		||||
    maxSize?: number;
 | 
			
		||||
    // 是否支持多选
 | 
			
		||||
    multiple?: boolean;
 | 
			
		||||
    // support xxx.xxx.xx
 | 
			
		||||
    resultField?: string;
 | 
			
		||||
    // 是否显示下面的描述
 | 
			
		||||
    showDescription?: boolean;
 | 
			
		||||
    value?: string | string[];
 | 
			
		||||
  }>(),
 | 
			
		||||
  {
 | 
			
		||||
    value: () => [],
 | 
			
		||||
    directory: undefined,
 | 
			
		||||
    disabled: false,
 | 
			
		||||
    helpText: '',
 | 
			
		||||
    maxSize: 2,
 | 
			
		||||
    maxNumber: 1,
 | 
			
		||||
    accept: () => [],
 | 
			
		||||
    multiple: false,
 | 
			
		||||
    api: undefined,
 | 
			
		||||
    resultField: '',
 | 
			
		||||
    showDescription: false,
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
const props = withDefaults(defineProps<FileUploadProps>(), {
 | 
			
		||||
  value: () => [],
 | 
			
		||||
  directory: undefined,
 | 
			
		||||
  disabled: false,
 | 
			
		||||
  helpText: '',
 | 
			
		||||
  maxSize: 2,
 | 
			
		||||
  maxNumber: 1,
 | 
			
		||||
  accept: () => [],
 | 
			
		||||
  multiple: false,
 | 
			
		||||
  api: undefined,
 | 
			
		||||
  resultField: '',
 | 
			
		||||
  showDescription: false,
 | 
			
		||||
});
 | 
			
		||||
const emit = defineEmits(['change', 'update:value', 'delete', 'returnText']);
 | 
			
		||||
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
 | 
			
		||||
const isInnerOperate = ref<boolean>(false);
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +87,7 @@ watch(
 | 
			
		|||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const handleRemove = async (file: UploadFile) => {
 | 
			
		||||
async function handleRemove(file: UploadFile) {
 | 
			
		||||
  if (fileList.value) {
 | 
			
		||||
    const index = fileList.value.findIndex((item) => item.uid === file.uid);
 | 
			
		||||
    index !== -1 && fileList.value.splice(index, 1);
 | 
			
		||||
| 
						 | 
				
			
			@ -122,9 +97,9 @@ const handleRemove = async (file: UploadFile) => {
 | 
			
		|||
    emit('change', value);
 | 
			
		||||
    emit('delete', file);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const beforeUpload = async (file: File) => {
 | 
			
		||||
async function beforeUpload(file: File) {
 | 
			
		||||
  // 使用现代的Blob.text()方法替代FileReader
 | 
			
		||||
  const fileContent = await file.text();
 | 
			
		||||
  emit('returnText', fileContent);
 | 
			
		||||
| 
						 | 
				
			
			@ -145,7 +120,7 @@ const beforeUpload = async (file: File) => {
 | 
			
		|||
    setTimeout(() => (isLtMsg.value = true), 1000);
 | 
			
		||||
  }
 | 
			
		||||
  return (isAct && !isLt) || Upload.LIST_IGNORE;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function customRequest(info: UploadRequestOption<any>) {
 | 
			
		||||
  let { api } = props;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,8 @@
 | 
			
		|||
/**
 | 
			
		||||
 * 默认图片类型
 | 
			
		||||
 */
 | 
			
		||||
export const defaultImageAccepts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
 | 
			
		||||
 | 
			
		||||
export function checkFileType(file: File, accepts: string[]) {
 | 
			
		||||
  if (!accepts || accepts.length === 0) {
 | 
			
		||||
    return true;
 | 
			
		||||
| 
						 | 
				
			
			@ -7,11 +12,6 @@ export function checkFileType(file: File, accepts: string[]) {
 | 
			
		|||
  return reg.test(file.name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 默认图片类型
 | 
			
		||||
 */
 | 
			
		||||
export const defaultImageAccepts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
 | 
			
		||||
 | 
			
		||||
export function checkImgType(
 | 
			
		||||
  file: File,
 | 
			
		||||
  accepts: string[] = defaultImageAccepts,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,9 +2,7 @@
 | 
			
		|||
import type { UploadFile, UploadProps } from 'ant-design-vue';
 | 
			
		||||
import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
 | 
			
		||||
 | 
			
		||||
import type { AxiosResponse } from '@vben/request';
 | 
			
		||||
 | 
			
		||||
import type { UploadListType } from './typing';
 | 
			
		||||
import type { FileUploadProps } from './typing';
 | 
			
		||||
 | 
			
		||||
import type { AxiosProgressEvent } from '#/api/infra/file';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -22,46 +20,20 @@ import { useUpload, useUploadType } from './use-upload';
 | 
			
		|||
 | 
			
		||||
defineOptions({ name: 'ImageUpload', inheritAttrs: false });
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(
 | 
			
		||||
  defineProps<{
 | 
			
		||||
    // 根据后缀,或者其他
 | 
			
		||||
    accept?: string[];
 | 
			
		||||
    api?: (
 | 
			
		||||
      file: File,
 | 
			
		||||
      onUploadProgress?: AxiosProgressEvent,
 | 
			
		||||
    ) => Promise<AxiosResponse<any>>;
 | 
			
		||||
    // 上传的目录
 | 
			
		||||
    directory?: string;
 | 
			
		||||
    disabled?: boolean;
 | 
			
		||||
    helpText?: string;
 | 
			
		||||
    listType?: UploadListType;
 | 
			
		||||
    // 最大数量的文件,Infinity不限制
 | 
			
		||||
    maxNumber?: number;
 | 
			
		||||
    // 文件最大多少MB
 | 
			
		||||
    maxSize?: number;
 | 
			
		||||
    // 是否支持多选
 | 
			
		||||
    multiple?: boolean;
 | 
			
		||||
    // support xxx.xxx.xx
 | 
			
		||||
    resultField?: string;
 | 
			
		||||
    // 是否显示下面的描述
 | 
			
		||||
    showDescription?: boolean;
 | 
			
		||||
    value?: string | string[];
 | 
			
		||||
  }>(),
 | 
			
		||||
  {
 | 
			
		||||
    value: () => [],
 | 
			
		||||
    directory: undefined,
 | 
			
		||||
    disabled: false,
 | 
			
		||||
    listType: 'picture-card',
 | 
			
		||||
    helpText: '',
 | 
			
		||||
    maxSize: 2,
 | 
			
		||||
    maxNumber: 1,
 | 
			
		||||
    accept: () => defaultImageAccepts,
 | 
			
		||||
    multiple: false,
 | 
			
		||||
    api: undefined,
 | 
			
		||||
    resultField: '',
 | 
			
		||||
    showDescription: true,
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
const props = withDefaults(defineProps<FileUploadProps>(), {
 | 
			
		||||
  value: () => [],
 | 
			
		||||
  directory: undefined,
 | 
			
		||||
  disabled: false,
 | 
			
		||||
  listType: 'picture-card',
 | 
			
		||||
  helpText: '',
 | 
			
		||||
  maxSize: 2,
 | 
			
		||||
  maxNumber: 1,
 | 
			
		||||
  accept: () => defaultImageAccepts,
 | 
			
		||||
  multiple: false,
 | 
			
		||||
  api: undefined,
 | 
			
		||||
  resultField: '',
 | 
			
		||||
  showDescription: true,
 | 
			
		||||
});
 | 
			
		||||
const emit = defineEmits(['change', 'update:value', 'delete']);
 | 
			
		||||
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
 | 
			
		||||
const isInnerOperate = ref<boolean>(false);
 | 
			
		||||
| 
						 | 
				
			
			@ -130,7 +102,7 @@ function getBase64<T extends ArrayBuffer | null | string>(file: File) {
 | 
			
		|||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handlePreview = async (file: UploadFile) => {
 | 
			
		||||
async function handlePreview(file: UploadFile) {
 | 
			
		||||
  if (!file.url && !file.preview) {
 | 
			
		||||
    file.preview = await getBase64<string>(file.originFileObj!);
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -141,9 +113,9 @@ const handlePreview = async (file: UploadFile) => {
 | 
			
		|||
    previewImage.value.slice(
 | 
			
		||||
      Math.max(0, previewImage.value.lastIndexOf('/') + 1),
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handleRemove = async (file: UploadFile) => {
 | 
			
		||||
async function handleRemove(file: UploadFile) {
 | 
			
		||||
  if (fileList.value) {
 | 
			
		||||
    const index = fileList.value.findIndex((item) => item.uid === file.uid);
 | 
			
		||||
    index !== -1 && fileList.value.splice(index, 1);
 | 
			
		||||
| 
						 | 
				
			
			@ -153,14 +125,14 @@ const handleRemove = async (file: UploadFile) => {
 | 
			
		|||
    emit('change', value);
 | 
			
		||||
    emit('delete', file);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handleCancel = () => {
 | 
			
		||||
function handleCancel() {
 | 
			
		||||
  previewOpen.value = false;
 | 
			
		||||
  previewTitle.value = '';
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const beforeUpload = async (file: File) => {
 | 
			
		||||
async function beforeUpload(file: File) {
 | 
			
		||||
  const { maxSize, accept } = props;
 | 
			
		||||
  const isAct = checkImgType(file, accept);
 | 
			
		||||
  if (!isAct) {
 | 
			
		||||
| 
						 | 
				
			
			@ -177,7 +149,7 @@ const beforeUpload = async (file: File) => {
 | 
			
		|||
    setTimeout(() => (isLtMsg.value = true), 1000);
 | 
			
		||||
  }
 | 
			
		||||
  return (isAct && !isLt) || Upload.LIST_IGNORE;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function customRequest(info: UploadRequestOption<any>) {
 | 
			
		||||
  let { api } = props;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,2 +1,3 @@
 | 
			
		|||
export { default as FileUpload } from './file-upload.vue';
 | 
			
		||||
export { default as ImageUpload } from './image-upload.vue';
 | 
			
		||||
export { default as InputUpload } from './input-upload.vue';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
import type { InputProps, TextAreaProps } from 'ant-design-vue';
 | 
			
		||||
 | 
			
		||||
import type { FileUploadProps } from './typing';
 | 
			
		||||
 | 
			
		||||
import { computed, ref } from 'vue';
 | 
			
		||||
 | 
			
		||||
import { Col, Input, Row, Textarea } from 'ant-design-vue';
 | 
			
		||||
 | 
			
		||||
import FileUpload from './file-upload.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps<{
 | 
			
		||||
  fileUploadProps?: FileUploadProps;
 | 
			
		||||
  inputProps?: InputProps;
 | 
			
		||||
  inputType?: 'input' | 'textarea';
 | 
			
		||||
  textareaProps?: TextAreaProps;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['change', 'update:value']);
 | 
			
		||||
 | 
			
		||||
const value = ref('');
 | 
			
		||||
 | 
			
		||||
function handleReturnText(text: string) {
 | 
			
		||||
  value.value = text;
 | 
			
		||||
  emit('change', value.value);
 | 
			
		||||
  emit('update:value', value.value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const inputProps = computed(() => {
 | 
			
		||||
  return {
 | 
			
		||||
    ...props.inputProps,
 | 
			
		||||
    value: value.value,
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const textareaProps = computed(() => {
 | 
			
		||||
  return {
 | 
			
		||||
    ...props.textareaProps,
 | 
			
		||||
    value: value.value,
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const fileUploadProps = computed(() => {
 | 
			
		||||
  return {
 | 
			
		||||
    ...props.fileUploadProps,
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
  <Row>
 | 
			
		||||
    <Col :span="18">
 | 
			
		||||
      <Input v-if="inputType === 'input'" v-bind="inputProps" />
 | 
			
		||||
      <Textarea v-else :row="4" v-bind="textareaProps" />
 | 
			
		||||
    </Col>
 | 
			
		||||
    <Col :span="6">
 | 
			
		||||
      <FileUpload
 | 
			
		||||
        class="ml-4"
 | 
			
		||||
        v-bind="fileUploadProps"
 | 
			
		||||
        @return-text="handleReturnText"
 | 
			
		||||
      />
 | 
			
		||||
    </Col>
 | 
			
		||||
  </Row>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,7 @@
 | 
			
		|||
import type { AxiosResponse } from '@vben/request';
 | 
			
		||||
 | 
			
		||||
import type { AxiosProgressEvent } from '#/api/infra/file';
 | 
			
		||||
 | 
			
		||||
export enum UploadResultStatus {
 | 
			
		||||
  DONE = 'done',
 | 
			
		||||
  ERROR = 'error',
 | 
			
		||||
| 
						 | 
				
			
			@ -6,3 +10,28 @@ export enum UploadResultStatus {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
export type UploadListType = 'picture' | 'picture-card' | 'text';
 | 
			
		||||
 | 
			
		||||
export interface FileUploadProps {
 | 
			
		||||
  // 根据后缀,或者其他
 | 
			
		||||
  accept?: string[];
 | 
			
		||||
  api?: (
 | 
			
		||||
    file: File,
 | 
			
		||||
    onUploadProgress?: AxiosProgressEvent,
 | 
			
		||||
  ) => Promise<AxiosResponse<any>>;
 | 
			
		||||
  // 上传的目录
 | 
			
		||||
  directory?: string;
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  helpText?: string;
 | 
			
		||||
  listType?: UploadListType;
 | 
			
		||||
  // 最大数量的文件,Infinity不限制
 | 
			
		||||
  maxNumber?: number;
 | 
			
		||||
  // 文件最大多少MB
 | 
			
		||||
  maxSize?: number;
 | 
			
		||||
  // 是否支持多选
 | 
			
		||||
  multiple?: boolean;
 | 
			
		||||
  // support xxx.xxx.xx
 | 
			
		||||
  resultField?: string;
 | 
			
		||||
  // 是否显示下面的描述
 | 
			
		||||
  showDescription?: boolean;
 | 
			
		||||
  value?: string | string[];
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,17 +80,17 @@ export function useUploadType({
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// TODO @芋艿:目前保持和 admin-vue3 一致,后续可能重构
 | 
			
		||||
export const useUpload = (directory?: string) => {
 | 
			
		||||
export function useUpload(directory?: string) {
 | 
			
		||||
  // 后端上传地址
 | 
			
		||||
  const uploadUrl = getUploadUrl();
 | 
			
		||||
  // 是否使用前端直连上传
 | 
			
		||||
  const isClientUpload =
 | 
			
		||||
    UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE;
 | 
			
		||||
  // 重写ElUpload上传方法
 | 
			
		||||
  const httpRequest = async (
 | 
			
		||||
  async function httpRequest(
 | 
			
		||||
    file: File,
 | 
			
		||||
    onUploadProgress?: AxiosProgressEvent,
 | 
			
		||||
  ) => {
 | 
			
		||||
  ) {
 | 
			
		||||
    // 模式一:前端上传
 | 
			
		||||
    if (isClientUpload) {
 | 
			
		||||
      // 1.1 生成文件名称
 | 
			
		||||
| 
						 | 
				
			
			@ -114,20 +114,20 @@ export const useUpload = (directory?: string) => {
 | 
			
		|||
      // 模式二:后端上传
 | 
			
		||||
      return uploadFile({ file, directory }, onUploadProgress);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    uploadUrl,
 | 
			
		||||
    httpRequest,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获得上传 URL
 | 
			
		||||
 */
 | 
			
		||||
export const getUploadUrl = (): string => {
 | 
			
		||||
export function getUploadUrl(): string {
 | 
			
		||||
  return `${apiURL}/infra/file/upload`;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 创建文件信息
 | 
			
		||||
| 
						 | 
				
			
			@ -135,7 +135,10 @@ export const getUploadUrl = (): string => {
 | 
			
		|||
 * @param vo 文件预签名信息
 | 
			
		||||
 * @param file 文件
 | 
			
		||||
 */
 | 
			
		||||
function createFile0(vo: InfraFileApi.FilePresignedUrlRespVO, file: File) {
 | 
			
		||||
function createFile0(
 | 
			
		||||
  vo: InfraFileApi.FilePresignedUrlRespVO,
 | 
			
		||||
  file: File,
 | 
			
		||||
): InfraFileApi.File {
 | 
			
		||||
  const fileVO = {
 | 
			
		||||
    configId: vo.configId,
 | 
			
		||||
    url: vo.url,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue