在 Vue 中实现拖放式图片上传组件


上篇教程中,学院君给大家演示了如何基于 Laravel + Vue 框架快速实现图片上传并且给文章添加了封面图片功能。除了传统的点击按钮选择图片上传之外,我们还可以基于 HTML5 原生提供的文件拖放 API 让文件上传变得更简单一些,接下来,我们就来演示如何在 Vue 框架中实现一个图片拖放上传组件。

后端接口和表单外围功能都是现成的,只需要在 resources/js/components/form 目录下编写一个独立的 DragImage 子组件,然后在 PostForm 中引入它即可。

模板代码

首先,我们在 DragImage.vue 中编写 HTML 模板代码如下:

<template>
    <div class="form-group">
        <div class="dropbox">
            <input class="drag-file" type="file" ref="image" :name="name" :disabled="isSaving" @change="fileChange" accept="image/*">
            <p v-if="isInitial">
                拖放图片开始上传<br>或者点击选择图片上传
            </p>
            <p v-if="isSaving">
                图片上传中...
            </p>
        </div>
        <div v-if="isSuccess">
            <div class="alert alert-success" role="alert">
                图片上传成功!<a href="javascript:void(0)" @click="reset()">重新上传</a>
            </div>
        </div>
        <div v-if="isFailed">
            <div class="alert alert-danger" role="alert">
                图片上传失败!<a href="javascript:void(0)" @click="reset()">重新上传</a>
            </div>
        </div>
        <InputText type="hidden" :name="field" v-model="filePath"></InputText>
        <img :src="filePath" class="img-responsive img-thumbnail" style="width: 50%;" v-if="filePath">
    </div>
</template>

我们通过 div[class=dropbox] 这个容器定义拖放上传区域,并且通过 accept 属性设置只支持图片上传,如果已经有图片在上传,则该组件处于禁用状态,在空闲状态下,将图片文件拖放到该区域会触发 fileChange 事件发送图片上传请求。在图片拖放区域,会根据不同的状态显示不同的文案(这些状态和方法我们马上会提供)。

然后是图片上传成功或失败的消息提示,并且提供了重新上传链接清空已上传的图片。

最后和上篇教程实现的文件上传组件一样,是隐藏的图片路径字段和图片预览功能,如果图片上传成功,或者父级作用域已经包含封面图片,就会在图片预览区域渲染出来。

脚本代码

为了让上面的模板代码可以正常渲染和工作,需要在 Vue 组件中编写脚本代码:

<script>
import InputText from './InputText';

const STATUS_INITIAL = 0, STATUS_SAVING = 1, STATUS_SUCCESS = 2, STATUS_FAILED = 3;

export default {
    props: ['name', 'field', 'path'],
    components: {InputText},
    data() {
        return {
            currentStatus: null,
            filePath: this.path
        }
    },
    computed: {
        isInitial() {
            return this.currentStatus === STATUS_INITIAL;
        },
        isSaving() {
            return this.currentStatus === STATUS_SAVING;
        },
        isSuccess() {
            return this.currentStatus === STATUS_SUCCESS;
        },
        isFailed() {
            return this.currentStatus === STATUS_FAILED;
        }
    },
    mounted() {
        this.reset();
    },
    methods: {
        reset() {
            // 重置状态字段值
            this.currentStatus = STATUS_INITIAL;
            this.$emit('clear');  //  清理父级作用域报错信息
        },
        fileChange() {
            // 上传文件后才会执行后续步骤
            if (this.$refs.image.files.length === 0) {
                return;
            }

            // 清理父级作用域错误信息
            this.$emit('clear');

            // 填充表单数据
            const formData = new FormData();
            formData.append(this.name, this.$refs.image.files[0]);

            // 开始上传
            this.currentStatus = STATUS_SAVING;
            axios.post('/image/upload', formData, {
                headers: {
                    'Content-Type': 'multipart/form-data'
                }
            }).then(resp => {
                this.currentStatus = STATUS_SUCCESS;
                this.filePath = resp.data.path;
                this.$emit('success', this.field, this.filePath);
            }).catch(error => {
                this.currentStatus = STATUS_FAILED;
                let errors = {};
                errors[this.field] = error.response.data.errors[this.name];
                this.$emit('error', errors);
            });
        }
    }
}
</script>

除了新增一个状态字段 currentStatus 和对应的维护逻辑,以及与之相关的计算属性定义外,其他逻辑和上篇教程创建的文件上传组件都差不多,就不多做介绍了。

样式代码

现在我们的拖放式图片上传组件基本功能就已经编写好了,我们再编写如下样式代码让其看起来更美观一些:

<style scoped>
.dropbox {
    outline: 2px dashed grey;
    outline-offset: -10px;
    background: lightcyan;
    color: dimgray;
    padding: 10px 10px;
    min-height: 200px;
    position: relative;
    cursor: pointer;
}

.drag-file {
    opacity: 0;
    width: 100%;
    height: 200px;
    position: absolute;
    cursor: pointer;
}

.dropbox:hover {
    background: lightblue;
}

.dropbox p {
    font-size: 1.2em;
    text-align: center;
    padding: 50px 0;
}

.alert {
    margin-top: 10px;
}
</style>

在文章表单中引入

最后我们需要在文章发布/编辑表单中引入 DragImage 组件让其生效:

<div class="form-group">
    <Label name="title" label="封面图片"></Label>
    <!--
    <InputFile name="image" field="image_path" :path="form.image_path"
               @clear="clear('image_path')" @success="uploadSuccess" @error="uploadError">
    </InputFile>
    -->
    <DragImage name="image" field="image_path" :path="form.image_path"
               @clear="clear('image_path')" @success="uploadSuccess" @error="uploadError">
    </DragImage>
    <ErrorMsg :error="form.errors.get('image_path')"></ErrorMsg>
</div>

...

<script>
import DragImage from "./form/DragImage";

export default {

    components: {DragImage, FormSection, InputText, InputFile, TextArea, Label, ErrorMsg, Button, ToastMsg},
    
    ...
}
</script>

这个时候,再打开文章发布/编辑页面,就可以看到如下的封面图片上传 UI 了:

vue-drag-image-component

vue-drag-image-component

需要注意的是,除了拖放上传之外,DragImage 组件也支持传统的点击->选择图片->上传流程,不同于之前的点击按钮,现在点击拖放区域就可以了。

演示图片拖放式上传

我们可以简单演示下图片拖放式上传:

vue-drag-image-component

你可以将右侧本地文件系统中的任意图片拖到左侧浏览器封面图片对应的上传区域,完成图片拖放上传:

vue-drag-image-component

你还可以在此基础上让 DragImage 组件支持上传多张图片,感兴趣的同学可以自行去研究下如何实现。


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 基于 Laravel + Vue 框架实现文件异步上传组件和文章封面图片功能

>> 下一篇: 基于 Flickity 在 Vue 中实现轮播图组件并设置简单的博客布局