feat: add input upload

pull/116/head
xingyu4j 2025-05-26 18:46:06 +08:00
parent 3c4f954b77
commit ba18eb37da
7 changed files with 150 additions and 107 deletions

View File

@ -2,7 +2,7 @@
import type { UploadFile, UploadProps } from 'ant-design-vue'; import type { UploadFile, UploadProps } from 'ant-design-vue';
import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface'; 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'; import type { AxiosProgressEvent } from '#/api/infra/file';
@ -20,44 +20,19 @@ import { useUpload, useUploadType } from './use-upload';
defineOptions({ name: 'FileUpload', inheritAttrs: false }); defineOptions({ name: 'FileUpload', inheritAttrs: false });
const props = withDefaults( const props = withDefaults(defineProps<FileUploadProps>(), {
defineProps<{ value: () => [],
// directory: undefined,
accept?: string[]; disabled: false,
api?: ( helpText: '',
file: File, maxSize: 2,
onUploadProgress?: AxiosProgressEvent, maxNumber: 1,
) => Promise<AxiosResponse<any>>; accept: () => [],
// multiple: false,
directory?: string; api: undefined,
disabled?: boolean; resultField: '',
helpText?: string; showDescription: false,
// 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 emit = defineEmits(['change', 'update:value', 'delete', 'returnText']); const emit = defineEmits(['change', 'update:value', 'delete', 'returnText']);
const { accept, helpText, maxNumber, maxSize } = toRefs(props); const { accept, helpText, maxNumber, maxSize } = toRefs(props);
const isInnerOperate = ref<boolean>(false); const isInnerOperate = ref<boolean>(false);
@ -112,7 +87,7 @@ watch(
}, },
); );
const handleRemove = async (file: UploadFile) => { async function handleRemove(file: UploadFile) {
if (fileList.value) { if (fileList.value) {
const index = fileList.value.findIndex((item) => item.uid === file.uid); const index = fileList.value.findIndex((item) => item.uid === file.uid);
index !== -1 && fileList.value.splice(index, 1); index !== -1 && fileList.value.splice(index, 1);
@ -122,9 +97,9 @@ const handleRemove = async (file: UploadFile) => {
emit('change', value); emit('change', value);
emit('delete', file); emit('delete', file);
} }
}; }
const beforeUpload = async (file: File) => { async function beforeUpload(file: File) {
// 使Blob.text()FileReader // 使Blob.text()FileReader
const fileContent = await file.text(); const fileContent = await file.text();
emit('returnText', fileContent); emit('returnText', fileContent);
@ -145,7 +120,7 @@ const beforeUpload = async (file: File) => {
setTimeout(() => (isLtMsg.value = true), 1000); setTimeout(() => (isLtMsg.value = true), 1000);
} }
return (isAct && !isLt) || Upload.LIST_IGNORE; return (isAct && !isLt) || Upload.LIST_IGNORE;
}; }
async function customRequest(info: UploadRequestOption<any>) { async function customRequest(info: UploadRequestOption<any>) {
let { api } = props; let { api } = props;

View File

@ -1,3 +1,8 @@
/**
*
*/
export const defaultImageAccepts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
export function checkFileType(file: File, accepts: string[]) { export function checkFileType(file: File, accepts: string[]) {
if (!accepts || accepts.length === 0) { if (!accepts || accepts.length === 0) {
return true; return true;
@ -7,11 +12,6 @@ export function checkFileType(file: File, accepts: string[]) {
return reg.test(file.name); return reg.test(file.name);
} }
/**
*
*/
export const defaultImageAccepts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
export function checkImgType( export function checkImgType(
file: File, file: File,
accepts: string[] = defaultImageAccepts, accepts: string[] = defaultImageAccepts,

View File

@ -2,9 +2,7 @@
import type { UploadFile, UploadProps } from 'ant-design-vue'; import type { UploadFile, UploadProps } from 'ant-design-vue';
import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface'; import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import type { AxiosResponse } from '@vben/request'; import type { FileUploadProps } from './typing';
import type { UploadListType } from './typing';
import type { AxiosProgressEvent } from '#/api/infra/file'; import type { AxiosProgressEvent } from '#/api/infra/file';
@ -22,46 +20,20 @@ import { useUpload, useUploadType } from './use-upload';
defineOptions({ name: 'ImageUpload', inheritAttrs: false }); defineOptions({ name: 'ImageUpload', inheritAttrs: false });
const props = withDefaults( const props = withDefaults(defineProps<FileUploadProps>(), {
defineProps<{ value: () => [],
// directory: undefined,
accept?: string[]; disabled: false,
api?: ( listType: 'picture-card',
file: File, helpText: '',
onUploadProgress?: AxiosProgressEvent, maxSize: 2,
) => Promise<AxiosResponse<any>>; maxNumber: 1,
// accept: () => defaultImageAccepts,
directory?: string; multiple: false,
disabled?: boolean; api: undefined,
helpText?: string; resultField: '',
listType?: UploadListType; showDescription: true,
// 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 emit = defineEmits(['change', 'update:value', 'delete']); const emit = defineEmits(['change', 'update:value', 'delete']);
const { accept, helpText, maxNumber, maxSize } = toRefs(props); const { accept, helpText, maxNumber, maxSize } = toRefs(props);
const isInnerOperate = ref<boolean>(false); 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) { if (!file.url && !file.preview) {
file.preview = await getBase64<string>(file.originFileObj!); file.preview = await getBase64<string>(file.originFileObj!);
} }
@ -141,9 +113,9 @@ const handlePreview = async (file: UploadFile) => {
previewImage.value.slice( previewImage.value.slice(
Math.max(0, previewImage.value.lastIndexOf('/') + 1), Math.max(0, previewImage.value.lastIndexOf('/') + 1),
); );
}; }
const handleRemove = async (file: UploadFile) => { async function handleRemove(file: UploadFile) {
if (fileList.value) { if (fileList.value) {
const index = fileList.value.findIndex((item) => item.uid === file.uid); const index = fileList.value.findIndex((item) => item.uid === file.uid);
index !== -1 && fileList.value.splice(index, 1); index !== -1 && fileList.value.splice(index, 1);
@ -153,14 +125,14 @@ const handleRemove = async (file: UploadFile) => {
emit('change', value); emit('change', value);
emit('delete', file); emit('delete', file);
} }
}; }
const handleCancel = () => { function handleCancel() {
previewOpen.value = false; previewOpen.value = false;
previewTitle.value = ''; previewTitle.value = '';
}; }
const beforeUpload = async (file: File) => { async function beforeUpload(file: File) {
const { maxSize, accept } = props; const { maxSize, accept } = props;
const isAct = checkImgType(file, accept); const isAct = checkImgType(file, accept);
if (!isAct) { if (!isAct) {
@ -177,7 +149,7 @@ const beforeUpload = async (file: File) => {
setTimeout(() => (isLtMsg.value = true), 1000); setTimeout(() => (isLtMsg.value = true), 1000);
} }
return (isAct && !isLt) || Upload.LIST_IGNORE; return (isAct && !isLt) || Upload.LIST_IGNORE;
}; }
async function customRequest(info: UploadRequestOption<any>) { async function customRequest(info: UploadRequestOption<any>) {
let { api } = props; let { api } = props;

View File

@ -1,2 +1,3 @@
export { default as FileUpload } from './file-upload.vue'; export { default as FileUpload } from './file-upload.vue';
export { default as ImageUpload } from './image-upload.vue'; export { default as ImageUpload } from './image-upload.vue';
export { default as InputUpload } from './input-upload.vue';

View File

@ -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>

View File

@ -1,3 +1,7 @@
import type { AxiosResponse } from '@vben/request';
import type { AxiosProgressEvent } from '#/api/infra/file';
export enum UploadResultStatus { export enum UploadResultStatus {
DONE = 'done', DONE = 'done',
ERROR = 'error', ERROR = 'error',
@ -6,3 +10,28 @@ export enum UploadResultStatus {
} }
export type UploadListType = 'picture' | 'picture-card' | 'text'; 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[];
}

View File

@ -80,17 +80,17 @@ export function useUploadType({
} }
// TODO @芋艿:目前保持和 admin-vue3 一致,后续可能重构 // TODO @芋艿:目前保持和 admin-vue3 一致,后续可能重构
export const useUpload = (directory?: string) => { export function useUpload(directory?: string) {
// 后端上传地址 // 后端上传地址
const uploadUrl = getUploadUrl(); const uploadUrl = getUploadUrl();
// 是否使用前端直连上传 // 是否使用前端直连上传
const isClientUpload = const isClientUpload =
UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE; UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE;
// 重写ElUpload上传方法 // 重写ElUpload上传方法
const httpRequest = async ( async function httpRequest(
file: File, file: File,
onUploadProgress?: AxiosProgressEvent, onUploadProgress?: AxiosProgressEvent,
) => { ) {
// 模式一:前端上传 // 模式一:前端上传
if (isClientUpload) { if (isClientUpload) {
// 1.1 生成文件名称 // 1.1 生成文件名称
@ -114,20 +114,20 @@ export const useUpload = (directory?: string) => {
// 模式二:后端上传 // 模式二:后端上传
return uploadFile({ file, directory }, onUploadProgress); return uploadFile({ file, directory }, onUploadProgress);
} }
}; }
return { return {
uploadUrl, uploadUrl,
httpRequest, httpRequest,
}; };
}; }
/** /**
* URL * URL
*/ */
export const getUploadUrl = (): string => { export function getUploadUrl(): string {
return `${apiURL}/infra/file/upload`; return `${apiURL}/infra/file/upload`;
}; }
/** /**
* *
@ -135,7 +135,10 @@ export const getUploadUrl = (): string => {
* @param vo * @param vo
* @param file * @param file
*/ */
function createFile0(vo: InfraFileApi.FilePresignedUrlRespVO, file: File) { function createFile0(
vo: InfraFileApi.FilePresignedUrlRespVO,
file: File,
): InfraFileApi.File {
const fileVO = { const fileVO = {
configId: vo.configId, configId: vo.configId,
url: vo.url, url: vo.url,