美文网首页
表单上传图片可不止选择这一种方式

表单上传图片可不止选择这一种方式

作者: 习惯水文的前端苏 | 来源:发表于2023-06-18 13:46 被阅读0次

    大家好,我是苏先生,一名热爱钻研、乐于分享的前端工程师,跟大家分享一句我很喜欢的话:人活着,其实就是一种心态,你若觉得快乐,幸福便无处不在

    前言

    通过表单上传图片发送到后台是挺常见的需求,一般来说有两种交互方式:

    1-点击按钮,到文件夹内选取指定的图片后上传

    2-将文件夹拖拽到展示区域后上传

    最近产品解锁了新的交互方式,在我看来这纯属脱裤子放屁,多此一举,不过我的经验告诉我,可行!!!

    需求说明

    对应需求在原型的第七条,要求从剪切板读取用户复制的图片并进行上传

    image.png

    头脑风暴

    该功能的实现由以下部分构成:

    1-复制图片

    复制图片是发生在操作系统的,因此这一个交互动作不需要我们关心

    2-读取剪切板

    这一定涉及到浏览器的权限问题和版本兼容性问题:对于版本问题,由于是内部系统可以强制要求更新到chrome最新版所以不用考虑。而权限问题则需要进行交互上的优化处理,比如什么时间去请求权限;用户赋权导致了粘贴中断应该如何处理;如果用户拒绝了又该如何处理等

    image.png

    3-过滤剪切板

    由于我们不参与图片复制的过程,也就无法保证用户规规矩矩的复制的是图片,如果是非图片的文件我们应该进行过滤

    4-监听剪切

    实现粘贴的方式无外乎两种:ctrl+v、鼠标右键。如果存在差异,需要进行特殊处理

    调研

    • 读取剪切板

    读取剪切板有4种方式:

    1-使用navigator的Clipboard对象

    2-使用document.execCommand

    3-使用window.clipboardData对象

    4-使用第三方库

    由于方式3是ie浏览器独有的,方式4会导致项目体积变大,故这俩我们直接不考虑。至于方式2,虽然也是原生的,且对浏览器的兼容性比较好,但是由于其是同步行为,所以我们也不考虑。因此我们选择方式一来进行实现

    • 监听剪切

    通过监听元素的paste事件可以监听到用户的粘贴行为,这包括ctrl+v和鼠标右键

    • 过滤剪切板

    paste事件的回调参数e中的clipboardData下的files可以拿到用户复制的文件,每个file的type可以对非图片进行过滤

    实现

    • 封装剪切板读取授权函数

    考虑到文本和图片的授权方式不同,我们通过type来进行区分

    export default (that, cb, type = 'read') => {
      const env = process.env.NODE_ENV;
      const errTip = '当前浏览器不支持(或用户拒绝授权)读取剪切板';
      if (!window.isSecureContext || ['development', 'test'].includes(env)) {
        that.$Message.warning(errTip);
        return;
      }
      navigator.clipboard[type]()
        .then((text) => {
          if (typeof cb === 'function') {
            cb(text);
          }
        })
        .catch(() => {
          that.$Message.warning(errTip);
        });
    };
    
    • 在组件的created中初始化拉取授权
    import getClipboardAuth from '@/utils/getClipboardAuth';
    getClipboardAuth(this,null,'read')
    
    • 在组件的mounted中为图片区域绑定事件

    1-将这部分逻辑提取到utils中,避免影响主流程观感

    export const bindEvent = (event) => {
      const boxId = 'img-box';
      const box = document.getElementById(boxId);
      box.addEventListener(
        'click',
        (e) => {
          let target = e.target;
          while (target.id !== boxId) {
            target = target.parentNode;
          }
          target.style.border = '1.5px solid green';
          target.addEventListener('paste', event);
        },
        true
      );
      box.addEventListener('mouseleave', (e) => {
        if (e.target.id !== boxId) return;
        e.target.style.border = '1px solid #ccc';
        e.target.removeEventListener('paste', event);
      });
    };
    

    2-引入并注册监听

    import { bindEvent } from './utils';
    bindEvent(this.handlePaste)
    
    • 读取图片并展示到指定区域

    这里我们通过type属性对非图片进行过滤,然后使用FileReader将其作为base64展示到页面中,其实是可以直接通过oss上传得到线上地址的,但是我感觉这样有点浪费资源

    methods: {
            handlePaste(e) {
                const clipboardData = e.clipboardData || {}
                let files = clipboardData.files || []
                files = [...files]
                files = files.filter((v) => v.type.startsWith('image'))
                if (files.length) {
                    for (let i = 0; i < files.length; i++) {
                        const reader = new FileReader()
                        reader.index = i
                        reader.onload = (ev) => {
                            const url = ev.target.result
                            this.previewImgs.push(url)
                            files[reader.index].base64Url = url
                            this.uploadImgs.push(files[reader.index])
                            this.formModel.prove = 'have'
                        }
                        reader.readAsDataURL(files[i])
                    }
                    return
                }
    
                this.$Message.warning("剪切板中不存在图片")
            },
        },
    
    • 预览图片时处理上传oss

    我选择在用户预览时再使用oss进行上传,且只会处理一次,后续再次预览就直接从缓存中读取,这样能一定程度避免浪费资源

    methods: {
            async handlePreview(id) {
                const previewItem = this.uploadImgs.find((v) => v.base64Url === id)
                if (previewItem && previewItem.httpUrl) {
                    previewImg(previewItem.httpUrl)
                    return
                }
                for (let i = 0; i < this.uploadImgs.length; i++) {
                    const v = this.uploadImgs[i]
                    const base64Url = v.base64Url
                    delete v.base64Url
                    // eslint-disable-next-line no-loop-func
                    this.uploadImg([v], i).then((imgs) => {
                        const urlDas = norImgs(imgs)
                        this.uploadImgs[urlDas[0].index].httpUrl = urlDas[0].url
                        this.uploadImgs[urlDas[0].index].base64Url = base64Url
                        if (base64Url === id) {
                            previewImg(urlDas[0].url)
                        }
                    })
                }
            },
    },
    
    • 提交表单时,上传oss

    由于业务员可能不会进行预览,所以在提交表单时还需要进行校验是否需要上传到oss,我个人习惯将处理过程提取出来,如下,在处理参数阶段,我对uploadImgs中的httpUrl进行了校验

    export async function processParams() {
      const { prove, orderNumber } = this.formModel;
      const params = {
        ...this.formModel,
      }; 
      if (prove === 'have') {
        if (!this.uploadImgs[0].httpUrl) {
          let imgs = await this.uploadImg(this.uploadImgs);
          imgs = norImgs(imgs);
          imgs = imgs.map((v) => v.url);
          params.prove = imgs.join('|');
        } else {
          params.prove = this.uploadImgs.map((v) => v.httpUrl).join('|');
        }
      }
      ...
      return params;
    }
    

    总结

    本文从需求分析到技术调研再到功能落地,一步步带领大家实现了从剪切板读取图片的完整示例


    如果本文对您有用,希望能得到您的点赞和收藏


    相关文章

      网友评论

          本文标题:表单上传图片可不止选择这一种方式

          本文链接:https://www.haomeiwen.com/subject/hyxsydtx.html