# 二次封装ant单张图片上传组件
# 组件props
props | 描述 |
---|---|
imageUrl | 双向绑定的 imageUrl 值,使用示例: `v-model:image-url="avatar" |
api | 上传图片的 api 方法,一般项目上传都是同一个 api 方法,在组件里直接引入即可(非必传) |
drag | 是否支持拖拽上传图片,默认为 true |
disabled | 是否禁用 上传、删除 功能,可查看图片 |
fileSize | 单个图片文件大小限制,默认为 5M |
fileType | 图片类型限制,默认类型为 ["image/jpeg", "image/png", "image/jpg"] |
height | 组件高度样式,默认为 "150px" |
width | 组件宽度样式,默认为 "150px" |
borderRadius | 组件边框圆角样式,默认为 "8px" |
bgColor | 组件背景颜色 |
# 代码如下
<template>
<div class="upload-box">
<a-upload
action="#"
:id="uuid"
:drag="drag"
:multiple="false"
:disabled="self_disabled"
:show-upload-list="false"
:accept="fileType.join(',')"
:customRequest="customRequest"
:before-upload="beforeUpload"
>
<template v-if="imageUrl">
<img :src="imageUrl" class="upload-image" />
<div class="upload-handle" @click.stop>
<div class="handle-icon" @click="editImg" v-if="!self_disabled">
<EditOutlined />
<span>编辑</span>
</div>
<div class="handle-icon" @click="handlePreview">
<EyeOutlined />
<span>查看</span>
</div>
<div class="handle-icon" @click="deleteImg">
<DeleteOutlined />
<span>删除</span>
</div>
</div>
</template>
<template v-else>
<div class="upload-empty">
<slot name="empty">
<PlusOutlined />
上传图片
</slot>
</div>
</template>
</a-upload>
<div class="upload-tip">
<slot name="tip"></slot>
</div>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
<img alt="example" style="width: 100%" :src="imageUrl" />
</a-modal>
</div>
</template>
<script setup lang="ts" name="UploadSingleImg">
import { ref, computed } from "vue";
import { message } from "ant-design-vue";
import type { UploadProps } from "ant-design-vue";
import { uploadImg } from "@/api/modules/qbsc/index";
import { CloseOutlined, PlusOutlined, EyeOutlined, CloseCircleOutlined, DeleteOutlined, EditOutlined } from "@ant-design/icons-vue";
interface UploadFileProps {
imageUrl: string; // 图片地址 ==> 必传
api?: (params: any) => Promise<any>; // 上传图片的 api 方法,一般项目上传都是同一个 api 方法,在组件里直接引入即可 ==> 非必传
drag?: boolean; // 是否支持拖拽上传 ==> 非必传(默认为 true)
disabled?: boolean; // 是否禁用上传组件 ==> 非必传(默认为 false)
fileSize?: number; // 图片大小限制 ==> 非必传(默认为 5M)
fileType?: string[]; // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/jpg"])
height?: string; // 组件高度 ==> 非必传(默认为 150px)
width?: string; // 组件宽度 ==> 非必传(默认为 150px)
borderRadius?: string; // 组件边框圆角 ==> 非必传(默认为 8px)
bgColor: string;
}
// 接受父组件参数
const props = withDefaults(defineProps<UploadFileProps>(), {
imageUrl: "",
drag: true,
disabled: false,
fileSize: 5,
fileType: () => ["image/jpeg", "image/png", "image/gif"],
height: "150px",
width: "150px",
borderRadius: "8px",
bgColor: "#fff",
});
// 判断是否禁用上传和删除
const self_disabled = computed(() => {
return props.disabled;
});
/**
* @description 生成唯一 uuid
* @returns {String}
*/
const generateUUID = () => {
let uuid = "";
for (let i = 0; i < 32; i++) {
let random = (Math.random() * 16) | 0;
if (i === 8 || i === 12 || i === 16 || i === 20) uuid += "-";
uuid += (i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random).toString(16);
}
return uuid;
};
// 生成组件唯一id
const uuid = ref("id-" + generateUUID());
/**
* @description 文件上传之前判断
* @param rawFile 选择的文件
* */
const beforeUpload: UploadProps["beforeUpload"] = rawFile => {
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize;
const imgType = props.fileType.includes(rawFile.type);
if (!imgType) message.warning("上传图片不符合所需的格式!");
if (!imgSize)
setTimeout(() => {
message.warning(`上传图片大小不能超过 ${props.fileSize}M!`);
}, 0);
return imgType && imgSize;
};
/**
* @description 图片上传
* @param options upload 所有配置项
* */
interface UploadEmits {
(e: "update:imageUrl", value: string): void;
}
const emit = defineEmits<UploadEmits>();
const customRequest = async options => {
let formData = new FormData();
formData.append("file", options.file);
try {
const api = props.api ?? uploadImg;
const { data } = await api(formData);
emit("update:imageUrl", data.url);
} catch (error) {
options.onError(error as any);
}
};
/**
* @description 编辑图片
* */
const editImg = () => {
const dom = document.querySelector(`#${uuid.value}`);
dom && dom.dispatchEvent(new MouseEvent("click"));
};
const previewVisible = ref(false);
/**
* @description 预览图片
*/
const handlePreview = () => {
previewVisible.value = true;
};
/**
* @description 取消预览
* */
const handleCancel = () => {
previewVisible.value = false;
};
/**
* @description 删除图片
* */
const deleteImg = () => {
emit("update:imageUrl", "");
};
</script>
<style lang="less" scoped>
.upload-image {
width: 100%;
height: 100%;
object-fit: contain;
}
.upload-empty {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
line-height: 30px;
}
:deep(.ant-upload) {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: v-bind(width);
height: v-bind(height);
overflow: hidden;
border: 1px dashed #eee;
border-radius: v-bind(borderRadius);
transition: all 300ms;
user-select: none;
cursor: pointer;
background: v-bind(bgColor);
&:hover {
border-color: #eee;
.upload-handle {
opacity: 1;
}
}
}
.upload-handle {
position: absolute;
top: 0;
right: 0;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
cursor: pointer;
background: rgb(0 0 0 / 60%);
opacity: 0;
transition: all 300ms;
.handle-icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 6%;
color: aliceblue;
text-align: center;
letter-spacing: 1px;
.anticon {
margin-bottom: 40%;
font-size: 130%;
line-height: 130%;
}
span {
font-size: 85%;
line-height: 85%;
}
}
}
.upload-tip {
text-align: center;
color: #595959;
font-size: 16px;
line-height: 2em;
}
</style>