前言
在项目中,ant-design是我们常用的UI库之一,今天就来二次封装常用的组件a-upload批量上传组件,让它用起来更方便。
a-upload组件二次封装
1. 功能分析
(1)自定义展示区,可按UI规范自定义展示
(2)上传失败,过滤成功的文件,重新赋值展示,不展示失败的文件
(3)上传之前的校验通过配置,在组件内统一管理
(4)通过传入defaultValue prop,进行初始化数据显示
(5)通过传入beforeChange prop,可以根据外部promise上传文件返回的结果,做一些处理
2. 代码+详细注释
// @/components/uploads/index.tsx
<template>
<a-upload v-model:file-list="state.fileList" :accept="accept" list-type="picture" class="avatar-uploader" multiple :max-count="3" :before-upload="beforeUpload" :customRequest="handlerUploadFile" @change="handlerChange" @remove="handlerRemove">
<a-button>
<UploadOutlined />
选择附件
</a-button>
<template #itemRender="{ file, actions }">
<div class="cd-upload-list">
<LoadingOutlined class="cd-upload-list-loading" v-if="file.status === 'uploading'" />
<template v-else>
<a class="cd-upload-list-item" :href="file?.response || file?.url" target="_blank">
<img v-if="isImage(file?.response)" class="cd-upload-list-img" :src="file?.response || file?.url" alt="" />
<FileOutlined v-else class="cd-upload-list-icon" />
<span class="cd-upload-list-name">{{ file.name }}</span>
</a>
</template>
<a class="cd-upload-list-remove" href="javascript:;" @click="actions.remove"> <DeleteOutlined /></a>
</div>
</template>
</a-upload>
</template>
<script lang="ts" setup>
import { reactive, ref, Ref, watch, PropType } from "vue";
import { message } from "ant-design-vue";
import { UploadOutlined, DeleteOutlined, FileOutlined, LoadingOutlined } from "@ant-design/icons-vue";
import type { UploadProps, UploadChangeParam } from "ant-design-vue";
import type { UploadRequestOption } from "ant-design-vue/es/vc-upload/interface";
/**
* 类型声明
*/
type ProgressProps = UploadProps["progress"];
type uploadFunProps<T> = Omit<UploadRequestOption<T>, "onSuccess"> &
Omit<UploadRequestOption<T>, "onError"> & {
onSuccess: any;
onError: any;
};
/**
* @param data 文件地址
* 是否是图片
*/
const isImage = (data: string | undefined) => {
if (!data) return false;
const fileExtensions = [".png", ".jpg", ".jpeg"];
return fileExtensions.some((extension) => data.endsWith(extension));
};
/**
* props
*/
const props = defineProps({
val: {
type: Array,
default: () => [],
},
accept: {
type: String,
default: ".doc,.docx,.xlsx,.xls,.pdf,.jpg,.png,.jpeg",
},
max: {
type: Number,
default: 1,
},
size: {
type: Number,
default: 2,
},
defaultValue: {
type: Array,
default: () => [],
},
format: Function,
text: {
type: String,
default: "",
},
params: {
type: Object,
default: {},
},
beforeChange: {
type: Function as PropType<(options: any) => Promise<any>>,
},
});
/**
* emit
*/
const emit = defineEmits(["update:val", "upload", "remove"]);
/**
* state
*/
type PreviewState = {
fileList: UploadProps["fileList"];
loading: boolean;
imageUrl: string;
dataList: any[];
};
const state = reactive<PreviewState>({
fileList: [],
loading: false,
imageUrl: "",
// 数据文件
dataList: [],
});
/**
* 文件状态改变时的回调
*/
const handlerChange = ({ file, fileList }: UploadChangeParam) => {
const status = file.status;
if (status === "uploading") {
}
if (status === "done") {
}
// 上传失败,过滤成功的文件,重新赋值展示
if (!status || status === "error") {
const files = fileList.filter((item: any) => item.status === "done");
state.fileList = files;
}
};
/**
* 上传之前检查文件
* @param file 文件对象
* @returns boolean
*/
const beforeUpload = (file: File) => {
const type = file.type.split("/")[1];
if (props.accept.indexOf(type) === -1) {
message.error(`请上传${props.accept}格式文件`);
return false;
}
const maxSize = file.size / 1024 / 1024 < props.size;
if (!maxSize) {
message.error(`图片大小须小于${props.size}MB`);
return false;
}
return type && maxSize;
};
/**
* 上传进度
* @param progressEvent 进度对象
*/
const progress: ProgressProps = {};
/**
* 上传
*/
const handlerUploadFile = (options: uploadFunProps<unknown>) => {
props?.beforeChange &&
props
.beforeChange({ file: options.file, params: props.params })
.then((res: any) => {
message.success(`上传成功`);
console.log("res777", res);
if (res?.url) {
options.onSuccess(res.url, options.file);
}
})
.catch(() => {
message.error(`上传失败`);
options.onError();
});
};
/**
* 删除文件的回调
* @param file 文件对象
*/
const handlerRemove = (file: File) => {
emit("remove", file);
};
/**
* 初始值
*/
const initValue = (list: string[]) => {
const file = [] as any[];
list.forEach((item: string) => {
file.push({
status: "done",
url: item,
});
});
// 更新
state.fileList = file;
};
watch(
() => props.defaultValue,
(val: any) => {
if (!val || (val && !val.length)) {
return false;
}
initValue(val);
},
{ immediate: true }
);
</script>
<style lang="scss" scoped>
.cd-upload-list {
display: flex;
justify-content: space-between;
position: relative;
height: 66px;
padding: 8px;
border: 1px solid #d9d9d9;
border-radius: 8px;
margin-top: 8px;
.cd-upload-list-loading {
font-size: 20px;
}
.cd-upload-list-item {
display: flex;
align-items: center;
.cd-upload-list-img {
width: 50px;
height: 50px;
margin-right: 10px;
}
.cd-upload-list-icon {
font-size: 20px;
margin-right: 10px;
}
.cd-upload-list-name {
}
}
.cd-upload-list-remove {
display: flex;
align-items: center;
font-size: 20px;
}
}
</style>
3. 使用到的全局上传hook代码
/**
* api
*/
import { uploadImage } from "@/api/index";
/**
* 上传资源全局hook
* @returns
*/
export function useUploadImage() {
const useUploadImageBeforeChange = ({ file, params }: { file: File; params: Record<string, any> }) => {
return new Promise((resolve, reject) => {
// 实例化表单对象
const form = new FormData();
// 表单添加 files 字段
for (let key in params) {
form.append(key, params[key]);
}
form.append("multipartFiles", file);
uploadImage(form)
.then((res: any) => {
const result = res && res.length;
resolve(result ? res[0] : null);
})
.catch(() => {
reject();
});
});
};
return {
useUploadImageBeforeChange,
};
}
4. 使用方式
// 引入组件,以及全局上传hook
import Upload from "@/components/upload/index.vue";
import { useUploadImage } from "@/hook/index";
const { useUploadImageBeforeChange } = useUploadImage();
// 使用
const defaultValue: string[] = ref(["http://xxx.pdf"]);
<Upload :defaultValue="defaultValue" :size="5" :params="{ fileType: 'xxxxxx' }" :before-change="useUploadImageBeforeChange" />
5. 效果展示
(1)上传
(2)预览、删除
总结
接下来会继续分享ant-design常用组件二次封装使用,请持续关注。