利用Vue3和element-plus实现图片上传组件

 更新时间:2022年03月15日 10:27:59   作者:之一Yo  
element-plus提供了uploader组件,但是不好定制化。所以本文将利用Vue3和element-plus实现一个图片上传的组件,感兴趣的可以了解一下

前言

element-plus 提供了 uploader 组件,但是不好定制化,所以自己又造了个轮子,实现了一个图片上传的组件,它的预期行为是:

1.还没上传图片时,显示上传卡片

2.上传图片时显示进度条,隐藏上传卡片

3.上传成功时显示图片缩略图,上传失败则显示失败提示

4.支持上传图片的预览和删除

具体如下图所示:

具体代码

图片上传

这里使用的图床是牛图网,无需注册,貌似也没有图片大小的限制,但是请不要上传违规图像。

<code>import axios from "axios"
import { ElMessage } from 'element-plus'


const service = axios.create({
    baseURL: "/image"
})

service.interceptors.response.use(response => {
    const code = response.data.code || 200
    if (code === 200) {
        return response.data.data
    }

    let msg = response.data.code + " " + response.data.msg
    ElMessage.error(msg)

    return Promise.reject('上传图片失败:' + msg)
})

/**
 * 上传图片
 * @param {File} file 图片文件
 * @param {RefImpl} progress 上传进度
 * @returns promise
 */
function uploadImage(file, progress) {
    let formData = new FormData();
    formData.append("file", file)
    return service({
        url: "/upload",
        method: "post",
        data: formData,
        onUploadProgress(event) {
            let v = Math.round(event.loaded / event.total * 100)
            progress.value = v == 100 ? 80 : v
        },

    })
}

export { uploadImage }

这里使用 onUploadProgress 来监视上传进度,但是实际上直接使用计算出来的进度往往会和实际的存在很大的偏差,也就是说:即使你还在上传,axios 也会告诉你已经上传完了,所以这里把 100 的进度换成了 80,真正的 100 进度应该在服务器返回 url 时设置。

受到同源策略的限制,我们需要在 vue.config.js 中配置一下代理服务器:

<code>module.exports = {
    devServer: {
        proxy: {
            "/image": {
                target: "https://niupic.com/api",
                pathRewrite: { "^/image": "" },
            },
        }
    }
}

上传组件

图片预览功能用的是 vue-easy-light-box,如果没有安装的话可以 npm install --save vue-easy-lightbox@next 安装一下。下面是具体代码:

<code><template>
    <div class="uploader">
        <input
            type="file"
            id="file-input"
            style="display: none"
            accept="image/*"
            @change="onImageAdded"
        />

        <div
            class="card upload-card"
            @click="openFileDialog"
            v-if="!isThumbnailVisible"
        >
            <svg
                class="icon"
                width="28"
                height="28"
                viewBox="0 0 1024 1024"
                xmlns="http://www.w3.org/2000/svg"
            >
                <path
                    fill="#8c939d"
                    d="M480 480V128a32 32 0 0164 0v352h352a32 32 0 110 64H544v352a32 32 0 11-64 0V544H128a32 32 0 010-64h352z"
                ></path>
            </svg>
        </div>

        <div class="card thumbnail-card" v-show="isThumbnailVisible">
            <img src="" alt="缩略图" id="thumbnail" />

            <label class="success-label" v-show="isSuccessLabelVisible"
                ><i class="success-icon"
                    ><svg
                        class="icon"
                        width="12"
                        height="12"
                        viewBox="0 0 1024 1024"
                        xmlns="http://www.w3.org/2000/svg"
                    >
                        <path
                            fill="white"
                            d="M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z"
                        ></path></svg
                ></i>
            </label>

            <!-- 图标 -->
            <div class="thumbnail-actions">
                <span class="thumbnail-preview" @click="handleThumbnailPreview">
                    <svg
                        class="icon"
                        width="20"
                        height="20"
                        viewBox="0 0 1024 1024"
                        xmlns="http://www.w3.org/2000/svg"
                    >
                        <path
                            fill="white"
                            d="M795.904 750.72l124.992 124.928a32 32 0 01-45.248 45.248L750.656 795.904a416 416 0 1145.248-45.248zM480 832a352 352 0 100-704 352 352 0 000 704zm-32-384v-96a32 32 0 0164 0v96h96a32 32 0 010 64h-96v96a32 32 0 01-64 0v-96h-96a32 32 0 010-64h96z"
                        ></path>
                    </svg>
                </span>

                <span class="thumbnail-delete" @click="handleThumbnailRemove">
                    <svg
                        class="icon"
                        width="20"
                        height="20"
                        viewBox="0 0 1024 1024"
                        xmlns="http://www.w3.org/2000/svg"
                    >
                        <path
                            fill="white"
                            d="M160 256H96a32 32 0 010-64h256V95.936a32 32 0 0132-32h256a32 32 0 0132 32V192h256a32 32 0 110 64h-64v672a32 32 0 01-32 32H192a32 32 0 01-32-32V256zm448-64v-64H416v64h192zM224 896h576V256H224v640zm192-128a32 32 0 01-32-32V416a32 32 0 0164 0v320a32 32 0 01-32 32zm192 0a32 32 0 01-32-32V416a32 32 0 0164 0v320a32 32 0 01-32 32z"
                        ></path>
                    </svg>
                </span>
            </div>

            <!-- 进度条 -->
            <el-progress
                type="circle"
                :percentage="progress"
                v-show="isProgressVisible"
                :width="110"
                id="progress"
            />
        </div>

        <vue-easy-lightbox
            moveDisabled
            :visible="isLightBoxVisible"
            :imgs="localImageUrl"
            :index="index"
            @hide="handleLightboxHide"
        />
    </div>
</template>

<script>
import { ref, computed } from "vue";
import { uploadImage } from "../api/image";
import { Plus } from "@element-plus/icons-vue";
import VueEasyLightbox from "vue-easy-lightbox";
import { ElMessage } from 'element-plus/lib/components';

export default {
    name: "KilaKilaUploader",
    emits: ["uploaded", "aboutToUpload", "removed"],
    components: { Plus, VueEasyLightbox },
    setup(props, context) {
        let progress = ref(0);
        let isLightBoxVisible = ref(false);
        let isProgressVisible = ref(false);
        let isSuccessLabelVisible = ref(false);
        let imageUrl = ref("");
        let localImageUrl = ref("");
        let index = ref(0);

        let isThumbnailVisible = computed(() => localImageUrl.value.length > 0);

        function openFileDialog() {
            document.getElementById("file-input").click();
        }

        function onImageAdded() {
            let fileInput = document.getElementById("file-input");
            if (fileInput.files.length == 0) {
                return;
            }

            context.emit("aboutToUpload");
            let file = fileInput.files[0];
            setImageUrl(URL.createObjectURL(file));
            upload(file);
        }

        function setImageUrl(url) {
            let thumbnailEl = document.getElementById("thumbnail");
            thumbnailEl.src = localImageUrl.value = url;
        }

        function handleThumbnailRemove(file) {
            imageUrl.value = "";
            localImageUrl.value = "";
            context.emit("removed", file);
        }

        function handleThumbnailPreview() {
            isLightBoxVisible.value = true;
        }

        function handleLightboxHide() {
            isLightBoxVisible.value = false;
        }

        function upload(file) {
            progress.value = 0;
            isProgressVisible.value = true;
            isSuccessLabelVisible.value = false;

            uploadImage(file, progress).then(
                (url) => {
                    progress.value = 100;
                    imageUrl.value = url;
                    document.getElementById("thumbnail").src = url;
                    context.emit("uploaded", url);

                    setTimeout(() => {
                        isProgressVisible.value = false;
                        isSuccessLabelVisible.value = true;
                    }, 200);
                },
                () => {
                    isProgressVisible.value = false;
                    localImageUrl.value = "";
                    context.emit("uploaded", "");
                    ElMessage.error("哎呀,图片上传出错啦~")
                }
            );
        }

        return {
            progress,
            imageUrl,
            localImageUrl,
            index,
            isLightBoxVisible,
            isThumbnailVisible,
            isProgressVisible,
            isSuccessLabelVisible,
            handleThumbnailRemove,
            handleThumbnailPreview,
            handleLightboxHide,
            openFileDialog,
            onImageAdded,
            setImageUrl,
        };
    },
};
</script>

<style lang="less" scoped>
.uploader {
    display: flex;
}

.card {
    background-color: #fbfdff;
    border: 1px dashed #c0ccda;
    border-radius: 6px;
    width: 148px;
    height: 148px;
    overflow: hidden;
}

.upload-card {
    display: flex;
    justify-content: center;
    align-items: center;
    transition: all 0.3s;
    cursor: pointer;

    &:hover {
        border-color: #409eff;
        color: #409eff;
    }
}

.thumbnail-card {
    border: 1px solid #c0ccda;
    position: relative;

    #thumbnail {
        width: 100%;
        height: 100%;
        object-fit: contain;
        display: inline;
    }

    .success-label {
        position: absolute;
        right: -15px;
        top: -6px;
        width: 40px;
        height: 24px;
        background: #67c23a;
        text-align: center;
        transform: rotate(45deg);
        box-shadow: 0 0 1pc 1px #0003;

        .success-icon {
            position: absolute;
            left: 13px;
            top: 1px;
            transform: rotate(-45deg);
        }
    }

    #progress {
        width: 100%;
        height: 100%;
        position: absolute;
        top: 0;
        left: 0;
        background: rgba(255, 255, 255, 0.7);

        :deep(.el-progress-circle) {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
    }

    .thumbnail-actions {
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.5);
        opacity: 0;
        transition: all 0.4s ease;
        display: flex;
        justify-content: center;
        align-items: center;
        position: absolute;
        top: 0;
        left: 0;
        border-radius: 6px;

        .thumbnail-preview,
        .thumbnail-delete {
            cursor: pointer;
            margin: 0 8px;
            display: inline-block;
        }

        &:hover {
            opacity: 1;
        }
    }
}

:deep(.vel-img) {
    box-shadow: 0 5px 20px 2px rgba(0, 0, 0, 0.35);
}
</style>

在图片上传之前、上传完成和移除图片的时候都会触发相应的自定义事件,父级组件可以处理这些事件来设置图片 url。

以上就是利用Vue3和element-plus实现图片上传组件的详细内容,更多关于Vue3 element-plus图片上传的资料请关注脚本之家其它相关文章!

相关文章

  • 详解VUE单页应用骨架屏方案

    详解VUE单页应用骨架屏方案

    这篇文章主要介绍了详解VUE单页应用骨架屏方案,详细的介绍了什么是骨架屏以及是宪法方案,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-01-01
  • Vue滚动到指定位置的多种方式示例详解

    Vue滚动到指定位置的多种方式示例详解

    当容器有滚动条时,有时需要将指定的内容自动滚动到可视区域,怎么实现呢,下面小编给大家带来了多种方法实现Vue滚动到指定位置,感兴趣的朋友跟随小编一起看看吧
    2023-08-08
  • vue源码学习之Object.defineProperty对象属性监听

    vue源码学习之Object.defineProperty对象属性监听

    这篇文章主要介绍了vue源码学习之Object.defineProperty对象属性监听,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • vue中关于checkbox使用的问题

    vue中关于checkbox使用的问题

    这篇文章主要介绍了vue中关于checkbox使用的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • Vue electron前端开启局域网接口实现流程详细介绍

    Vue electron前端开启局域网接口实现流程详细介绍

    用electron写了一个自己用的小软件,无后端,纯本地的数据。最近想着开发一个手机端app,将PC端的数据进行同步。为了这小小的功能单独写个后端又麻烦。干脆前后端不分离哈哈,直接在前端软件中开启接口
    2022-10-10
  • vue中注册组件的两种方式详解(全局注册&& 局部注册)

    vue中注册组件的两种方式详解(全局注册&& 局部注册)

    vue 是一个完全支持组件化开发的框架, 组件之间可以进行相互的引用,这篇文章主要介绍了vue中注册组件的两种方式详解(全局注册&& 局部注册),需要的朋友可以参考下
    2023-06-06
  • vue-week-picker实现支持按周切换的日历

    vue-week-picker实现支持按周切换的日历

    这篇文章主要为大家详细介绍了vue-week-picker实现支持按周切换的日历,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-06-06
  • 在vue或H5中如何使用复制粘贴功能

    在vue或H5中如何使用复制粘贴功能

    这篇文章主要介绍了在vue或H5中如何使用复制粘贴功能,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • vue框架制作购物车小球动画效果实例代码

    vue框架制作购物车小球动画效果实例代码

    最近在学习前端制作了一个购物车小球的动画效果,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友参考下吧
    2019-09-09
  • vue使用Canvas在画布上添加图片方式

    vue使用Canvas在画布上添加图片方式

    这篇文章主要介绍了vue使用Canvas在画布上添加图片方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04

最新评论