使用 Vue 完成上传文件页面

23,031 阅读6分钟

需求描述

最近加班加点完成了一个上传文件的需求,听起来很简单想着也很简单,但是知易行难实际操作起来,还是遇到了很多问题,现在就来总结一下,使用vue来构建一个上传文件的页面中会遇到哪些问题,其中的一些问题也可能是用别的框架也会遇到的问题。

平台:主要是在安卓和苹果的微信webview中运行。

先来看下主要的技术点:

  1. 父子组件的动态渲染
  2. 初始化数据和判断数据
  3. input表单隐藏联动
  4. 上传图片后可以预览

遇到的问题:

  1. 在iPhone中,click()事件无效
  2. 在微信安卓webview中,img标签使用background属性无效
  3. 预览上传图片保证宽高比
  4. 在某些安卓机型用rem单位不显示边框

设计图大概是这样的:

完成后的效果是这样:

  1. 首先登陆后,如果没有选择两个选项的话提交按钮点击不了并会有提示
  2. 当选择图片后,下面会显示选择图片的缩略图
  3. 当点击提交后,会显示loading成功后会刷新页面
  4. 提交过的部分不会显示示意图会改为一张提示图片,刚上传过所有图片时会提示已经上传过所有图片

大概就是这么一个步骤流程吧。

技术点分析

这个页面主要使用的UI包括:提示、loading、选项栏等都是从WEUI样式库中扒出来的,因为是在微信公众号中使用的,体验上会一致。上传部分使用的是post表单上传,我把form表单隐藏了起来,通过模拟上传按钮,当点击上传按钮的时候同步触发表单中的input type=‘file’标签,调起原生的选择图片的界面,选择图片后,点击上传按钮的同时触发一个post请求把图片传到后台,完成整个功能。

父子组件的动态渲染

可以从设计图看出上传图片的部分基本上设计是一样的,所以用一个vue组件来复用是最好的,顺便说一下这个页面用到了三个组件,一个是dialog提示的组件,一个是loading的组件,还有一个就是上传这部分用到的组件。

主要说一下上传部分的组件,在vue中,子父组件要传递的参数需要在子组件的props中声明,下面是我的组件中的模板,我主要用到了以下参数:


	


然后在父组件中我们可以引入这个组件,并声明它,然后在标签中把子组件需要用到的参数传递进去就好了。我这里是使用了v-for去遍历了一个result_data的数组,把所有需要上传的组件遍历渲染了出来。如下:

	
		


	

这样子父组件就可以传递数据了,具体的大家可以去看一下官方的文档。

初始化数据和判断数据

这是这个任务里最坑的一个部分。因为后台只会把已经上传过图片的字段告诉我,所以我需要在前端去造一些初始化的数据,去和后台返回回来的比较,再去判断哪些上传过需要展示出来,那些没上传需要上传。

注意:可以造数据的前提是,数据量不是很大,且数据是固定的。其实最好是后台可以完成这些判断,因为前端本就不应该去处理数据方面的逻辑。

我初始化的数据大概是这个样子的:

	
		init_data: [
//          0
  {
    name: 'shopphoto',
    nameCn: '店铺门头照片',
    bgUrl: {
      backgroundImage: "url('http://near.m1img.com/op_upload/62/146502860635.jpg')",
      backgroundSize: '100% 100%'
    },
    imgUrl: '',
    tip: '',
    isUpload: false
  },
	
//          1
  {
    name: 'goodsphoto',
    nameCn: '店铺内景照片',
    bgUrl: {
      backgroundImage: "url('http://near.m1img.com/op_upload/62/146502865726.jpg')",
      backgroundSize: '100% 100%'
    },
    imgUrl: '',
    tip: '',
    isUpload: false
  }
]
	

然后我去遍历后台返回的那个数组和我自己初始化得数组,用name这个字段产生关联去比较,因为后台只返回了上传过图片的字段,所以我就可以判断出哪些是没有上传的部分。判断的逻辑如下:

	
		compareArr (oldArr, newArr) {
    var _this = this
    var _oldArr = oldArr
    var _newArr = newArr
    for (let i = 0, len1 = _newArr.length; i < len1; i++) {
      for (let j = 0, len2 = _oldArr.length; j < len2; j++) {
        if (_newArr[i].name === _oldArr[j].name) {
          let initData = _this.init_data
          initData[i].bgUrl = {
            backgroundImage: "url('http://near.m1img.com/op_upload/62/146520417256.png')",
            backgroundSize: '100% 100%'
          }
          initData[i].isUpload = true
          _this.$set('init_data', initData)
        }
      }
    }
  }
	

我会给这个方法传入两个数组。oldArr是后台的数组,newArr是我自己初始化的数组。
循环遍历比较两个数组,把我自己初始化的数据做出一些修改,主要是提示图片和是否上传标识字段的更改。

input表单隐藏联动

这个逻辑也不是很复杂,就是在点击上传按钮的时候,调一个方法,在方法中选择到对应的input标签出发一个click()事件,主要是在iphone上面,可能会不支持input type=‘file’的标签,这里有一个坑,在网上找到了一些解决办法,但是试了一下没有效果,最后自己试验了一下找到了一个解决办法,在下面遇到的问题中会叙述。

第二是要注意在form表单中要禁止事件冒泡,不然你点击一次input表单就会提交一次。

上传图片后可以预览

可以我是汲取了广大人民的智慧。。。

主要思路就是把图片读取成一个base64格式,然后再把他装到一个img标签中。实现预览功能,这里我给几个参考的资料,有的demo直接就可以跑,改一改就可以运用到自己的项目中。

  1. 移动前端—图片压缩上传实践
  2. 使用HTML5的两个api,前端js完成图片压缩
  3. 使用HTML5 FILE API上传图片移动端缩略图兼容问题

大概看完他们的思路以后,就能够实现这个功能了,但是他并不能保持宽高比适配屏幕,后面我优化了一下这部分。

遇到的问题

遇到的这四个问题我们来一一解决:

  1. 在iPhone中,click()事件无效
  2. 在微信安卓webview中,img标签使用background属性无效
  3. 预览上传图片保证宽高比
  4. 在某些安卓机型用rem单位不显示边框

在iPhone中click()事件无效

这个问题在网上也有类似问题,地址,但是我依照网上的办法都不行,但是我发现显式的在标签内通过onclick=”document.querySelector(‘input’).click()这种方式是可以调用的,所以我把所有的我需要隐式调用input的地方都绑定了这个onclick="document.querySelector('input').click(),然后逻辑方面我又用vue的@click绑定了一个方法来处理逻辑方面。

img标签使用background属性无效

这个可能是安卓的微信webview支持的不好,因为在iphone和别的手机浏览器当中都可以支持,但是在安卓的微信webview不能显示,这个是兼容性的问题,所以最后我选择在div中加background。

预览上传图片保证宽高比

这个其实也不难优化,因为上面的方法是把base64的图片放到一个img标签当中,所以没法去适配这个宽高比,如果把这个放到divbackground-image属性当中,并把background-size设置成contain,把background-color设置成页面背景一样的颜色,这样就能模拟出浏览图片的功能了。

在某些安卓机型用rem单位不显示边框

因为移动端使用的是rem为单位,用postcss去处理(就是在css中写px,后处理器去处理成rem),在1px时转换为rem单位可能不足屏幕显示的1px,则不会显示边框。解决办法就是当写border时加注释去避免处理器去处理这句css(/*px*/

整个过程大概就是这样吧,我把这个页面放到了github上,但是没有vue的环境跑不起来,如果有需要可以拷到自己vue项目下去尝试。代码垃圾,轻喷。。。。。。

顺嘴说一句,那天晚上加班到两点,然后坐车回家,到地方付款的时候,发现银行维护没法付款,而且微信里也没有余额,正在想怎么办司机师傅说,没事赶紧回家吧等能付款的时候付了就好了,一句赶紧回家吧真实一阵感动啊。。。