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 { 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;

View File

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

View File

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

View File

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

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 {
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[];
}

View File

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