# 二次封装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>

Last Updated: 5/13/2023, 4:54:36 PM