精华内容
下载资源
问答
  • Vue实现文件上传和文件下载

    万次阅读 多人点赞 2018-08-22 22:09:55
    文件下载: 文件下载通常有几种方法 1.通过url下载 2.location.href ...前端第一个实现是使用a标签, 第二种方式: 这个方法是直接把 DataURLs 或者 BlogURLs 传到浏览器地址中触发下载。有两种...

    文件下载:
    文件下载通常有几种方法
    1.通过url下载
    2.location.href
    3.form提交直接下载
    4.HTML5 a.download结合blob对象进行下载
    第一种方式:
    第一种方法是前后端的接口只给了一个API请求:
    前端第一个实现是使用a标签,

    第二种方式:
    这个方法是直接把 DataURLs 或者 BlogURLs 传到浏览器地址中触发下载。有两种方式:

    window.location.href = urls; // 本窗口打开下载
    window.open(urls, '_blank'); // 新开窗口下载
    

    第三种:
    标签的download是HTML5标准新增的属性,作用是指示浏览器下载URL而不是导航到URL,因此将提示用户将其保存为本地文件。
    这种是定义的接口不是下载文件的路径,而是通过API可以获得文件的内容,由前端把内容写入到文件中,这种方法是通过获取文件信息,在网页上利用click事件,创建一个文件,然后将文件信息写入到文件中,然后保存

          this.content = content
          this.filename = filename
          const blob = new Blob([this.content])
          if (window.navigator.msSaveOrOpenBlob) {
            // 兼容IE10
            navigator.msSaveBlob(blob, this.filename)
          } else {
            //  chrome/firefox
            let aTag = document.createElement('a')
            aTag.download = this.filename
            aTag.href = URL.createObjectURL(blob)
            aTag.click()
            URL.revokeObjectURL(aTag.href)
          }
    

    第四种form表单
    不需要我们处理返回二进制流直接下载,非常方便
    form的action设置为接口地址,method设置为post,Post到后台的数据设置为input的属性 name = key,value = value的形式,如果有多个key、value的值要传递,那么就设置多个input来分别储存单个的key、value;
    如果请求的接口可以不需要参数,那么input还是必须要一个,如果不要得话 会引起接口报错
    原理:form的action相当于一个浏览器本页签/页面的一个请求,不会被后台,前台的路由拦截。所以能够提交成功。

    注意点:如果设置method为get,在action中的uri添加了参数的话,想用这个参数替代input的key、value形式来提交到后台,这参数是没有效果的,后台拿不到这些参数,真正的参数还是以input的name、value的形式储存,在submit方法执行后传递到后台。
    这样我们就是实现了文件下载,但是表单提交的数据一般是简单的键值对,如果传参比较复杂可以考虑将表单序列化提交。

    因为项目是基于vue的,而且提交的请求参数涉及很多参数,比较复杂,所以采用了方法三来实现

          axios.post('/rest/inventory/oh_status/info/excel', {
            site: this.selectedsite,
            bu: this.selectedbu,
            ohHealthStatus: this.selectedtitle,
            ohHealthRootcause: this.selectedblock,
            search: this.search,
            sort: this.sort
          }, {responseType: 'arraybuffer'}).then(this.ExportFile).catch(function (error) {
            console.log(error)
          })
    
         this.content = content
          this.filename = filename
          const blob = new Blob([this.content])
          if (window.navigator.msSaveOrOpenBlob) {
            // 兼容IE10
            navigator.msSaveBlob(blob, this.filename)
          } else {
            //  chrome/firefox
            let aTag = document.createElement('a')
            aTag.download = this.filename
            aTag.href = URL.createObjectURL(blob)
            aTag.click()
            URL.revokeObjectURL(aTag.href)
          }
    

    注意我们发送请求的时候一定要写上responseType,{responseType: 'arraybuffer'} 否则下载下来的文件打不开!!!

    文件上传
    文件上传通常使用form表单,但是有时候我们不想要用表单,ES6的fromData来实现

        handleGetFile (data) {
          this.file = data
          let formdata = new FormData()
          formdata.append('file', this.file)
          formdata.append('submit', false)
          let config = {
            headers: {
              'Content-Type': 'multipart/form-data'
            }
          }
          axios.post('/rest/master_data/ct2r/odin_delivery_metrics/cvc/file_upload_review', formdata, config).then(this.sendFileSucc)
        },
    

    首先 我们获取完文件之后,创建FormData对象,配置头部,发送该请求就OK了,别忘了让后台将接收头部请求改为formdata的格式

    如果文件是图片或者视频的话,部分浏览器会直接打开,非下载,这个时候我们可以在下载链接 url 后面加上 ?response-content-type=application/octet-stream 这个参数可以实现点击下载功能。

    展开全文
  • vue实现文件上传

    万次阅读 2018-03-28 10:57:55
    文件上传的传统方式是通过 form 表单提交,但是今天我们将换一种方式来实现这个功能。同一口味的东西吃多了还会腻呢,所以适当的时候换一换口味还是有必要的,好了,直接进入正文。首先,来介绍一下我们今天的主角 -...

    文件上传的传统方式是通过 form 表单提交,但是今天我们将换一种方式来实现这个功能。同一口味的东西吃多了还会腻呢,所以适当的时候换一换口味还是有必要的,好了,直接进入正文。

    首先,来介绍一下我们今天的主角 --- FormData,是的,就是他,虽然他和 form长得有点像,但是是2个不一样的东西。对于form相信大家一定不陌生,所以我们重点介绍一下 FormData。

    FormData对象可以组装一组用 XMLHttpRequest 发送请求的键/值对.它可以灵活方便的发送表单数据,因此可以独立于表单使用.如果把表单的编码类型设置为 multipart/form-data,则通过 FormData 传输的数据格式和表单通过 submit() 方法传输的数据格式相同.
    <input type="file" class="file"> // 通过file来选择需要上传的文件
    var formData = new FormData() // 声明一个FormData对象
    var formData = new window.FormData() // vue 中使用 window.FormData(),否则会报 'FormData isn't definded'
    formData.append('userfile', document.querySelector('input[type=file]').files[0]) // 'userfile' 这个名字要和后台获取文件的名字一样;
                                                                                     //'userfile'是formData这个对象的键名
    var options = {  // 设置axios的参数
             url: '请求地址',
             data: formData,
             method: 'post',
             headers: { 
              'Content-Type': 'multipart/form-data'
             }
        }
      this.axios(options).then((res) => {}) // 发送请求
    到这里,我们就已经成功实现了文件的上传,是不是觉得很简单?小伙伴们,赶紧动手试一试吧。





    展开全文
  • Vue实现文件上传

    2020-03-23 18:06:57
    Vue结合Vant进行文件上传 html: 调起手机文件夹及录像机上传视频: <van-uploader :after-read="afterRead" preview-size="38" upload-icon="plus" accept="video/*"> <span class="icon">&#xaa710;...

    Vue结合Vant进行文件上传

    html:

    调起手机文件夹及录像机上传视频:

    <van-uploader :after-read="afterRead" preview-size="38" upload-icon="plus" accept="video/*">
        <span class="icon">&#xaa710;</span>
        <span class="name">添加视频</span>
    </van-uploader>
    

    调起手机文件夹及摄像头上传图片:

    <van-uploader :after-read="afterRead" preview-size="38" upload-icon="plus" accept="image/*">
        <span class="icon">&#xaa710;</span>
        <span class="name">添加图片</span>
    </van-uploader>
    

    调起手机文件夹及录音上传音频:

    <van-uploader :after-read="afterRead" preview-size="38" upload-icon="plus" accept="audio/*">
        <span class="icon">&#xaa710;</span>
        <span class="name">添加音频</span>
    </van-uploader>
    

    script:

    data() {
    	return {
    		imgList: [], // 图片
            videoList: [], // 视频
            audioList: [], // 音频
    	}
    }
    methods: {
    	afterRead(file, detail) {
          Toast.loading({
            message: "文件上传中,请稍后~",
            forbidClick: true,
            duration: 0
          });
          file.status = "uploading";
          file.message = "上传中";
          var param = { type: "image" };
          var that = this;
          console.log("file", file);
    
          if (file.file.type.indexOf("video") >= 0) {
            param.type = "video";
          }
          if (file.file.type.indexOf("image") >= 0) {
            param.type = "image";
          }
          if (file.file.type.indexOf("audio") >= 0) {
            param.type = "audio";
          }
          this.upload(file, param, res => {
            if (param.type == "audio") {
              that.audioList.push({
                seconds: res.seconds,
                url: res.url
              });
            } else if (param.type == "video") {
              that.videoList.push({
                type: param.type,
                url: res
              });
            } else {
              that.imgList.push({
                type: param.type,
                url: res
              });
            }
            file.status = "succeed";
            file.message = "";
            Toast.clear();
            console.log(this.imgList, '图片')
    		console.log(this.videoList, '视频')
    		console.log(this.audioList, '音频')
          });
        },
    }
    
    upload() {
    	// oss上传
    import OSS from 'ali-oss'
    import md5 from 'js-md5'
    import {Toast} from 'vant';
    
    const clientOss = new OSS({
        region: 'oss-cn-beijing',
        endpoint: 'https://xxxx.xxxxxxxxxxxxxx.com', // 地址
        cname: true,
        // 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,部署在服务端使用RAM子账号或STS,部署在客户端使用STS。
        accessKeyId: 'xxxxxxxxxxxx',  // ID
        accessKeySecret: 'xxxxxxxxxxxxx',
        bucket: 'xxxxxxxxxxxxxx',
        secure:true
    })
    
    // oss上传图片与视频
    export function upload(params, data, callback) {
        // 文件类型
    
    
        const file_type = params.file.type.split('/')[0]
        // 文件格式
        const file_format = params.file.type.split('/')[1]
        // 图片格式
        const acceptImage = ['jpg', 'jpeg', 'png', 'gif']
        // 视频格式
        const acceptVideo = ['mp4']
        // 音频格式
        const acceptAudio = ['mp3']
    
    
        // 1、判断文件类型是否正确
        if (data.type !== file_type) {
            Toast('请上传正确的文件类型!')
            callback({ status: 'error' })
            return false
        }
    
        // 2、判断文件格式
        if (file_type === 'image') {
            const isAccept = acceptImage.includes(file_format.toLowerCase(), 0)
            if (!isAccept) {
                Toast('文件格式只支持PNG,JPG,GIF,请重新上传!')
                callback({ status: 'error' })
                return false
            }
        } else if (file_type === 'video') {
            const isAccept = acceptVideo.includes(file_format.toLowerCase(), 0)
        } else if (file_type === 'audio') {
            const isAccept = acceptAudio.includes(file_format.toLowerCase(), 0)
            if (!isAccept) {
                Toast('文件格式只支持MP3,请重新上传!')
                callback({ status: 'error' })
                return false
            }
        }
    
        // 3、判断文件大小是否超出限制
        if (file_type === 'image') {
    
            const file_size = params.file.size / (1 * 1024 * 1024).toFixed(1)
    
            if (file_size >= 5) {
    
                Toast('文件大小超出5M,请重新上传!')
                callback({ status: 'error' })
                return false
            } else {
                // 4、上传文件
                // 文件存储名称,key
                const file_name = md5(params.file.name.split('.')[0] + new Date().getTime()) + '.' + file_format
                // 第一个参数表示上传到OSS的Object名称,第二个参数表示本地文件或者文件路径
                clientOss.put('images/' + file_name, params.file).then(res => {
                    callback(res.url)
                }).catch((err) => {
                    console.log('error:', err)
                    return false
                })
            }
        } else if (file_type === 'video') {
            const url = URL.createObjectURL(params.file)
            const audioElement = new Audio(url)
            audioElement.addEventListener('loadedmetadata', () => {
                const file_time = Math.floor(audioElement.duration)
                if (file_time > data.len) {
                    Toast('视频时长不可超过' + data.len + '秒,请重新上传!')
                    callback({ status: 'error' })
                    return false
                } else {
                    // 4、上传文件
                    // 文件存储名称,key
                    const file_name = md5(params.file.name.split('.')[0] + new Date().getTime()) + new Date().getTime() + '.' + file_format
                    // 第一个参数表示上传到OSS的Object名称,第二个参数表示本地文件或者文件路径
                    clientOss.put('images/' + file_name, params.file).then(res => {
                        callback(res.url)
                    }).catch((err) => {
                        console.log('error:', err)
                        return false
                    })
                }
            })
        } else if (file_type === 'audio') {
    
            const url = URL.createObjectURL(params.file)
            const audioElement = new Audio(url)
            audioElement.addEventListener('loadedmetadata', () => {
                const file_time = Math.floor(audioElement.duration)
                if (file_time > data.len) {
                    Toast('音频时长不可超过' + data.len + '秒,请重新上传!')
                    callback({ status: 'error' })
                    return false
                } else {
                    // 4、上传文件
                    // 文件存储名称,key
                    var name = "";
                    if (params.file.name) {
                        name = params.file.name
                    } else {
                        var date=new Date()
                        name =date.getFullYear()+"_"+ new Date().getTime()
                    }
                    const file_name = md5(name.split('.')[0] + new Date().getTime()) + new Date().getTime() + '.' + file_format
                    // 第一个参数表示上传到OSS的Object名称,第二个参数表示本地文件或者文件路径
                    clientOss.put('images/' + file_name, params.file).then(res => {
                        const param = {url: res.url, seconds: file_time}
                        callback(param)
                    }).catch((err) => {
                        console.log('error:', err)
                        return false
                    })
                }
            })
        }
    }
    
    export function imgOverSize(params, data, callback) {
        const file_size = params.file.size / (1 * 1024 * 1024).toFixed(1)
    
        if (file_size >= 5) {
    
            Toast('文件大小超出5M,请重新上传!')
    
            return true
        }else{
            return false;
        }
    }
    

    在这里插入图片描述
    ok,完成!

    展开全文
  • vue实现文件上传功能

    2020-10-18 05:59:38
    主要为大家详细介绍了vue实现文件上传功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 主要介绍了node+vue实现文件上传功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 本文实例为大家分享了springboot+vue实现文件上传下载的具体代码,供大家参考,具体内容如下 一、文件上传(基于axios的简单上传) 所使用的技术:axios、springboot、vue; 实现思路:通过h5 :input元素标签进行...
  • 主要为大家详细介绍了vue实现文件上传读取及下载功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • vue 实现文件上传

    2020-11-14 18:53:21
    (一)首先实现html、css布局 代码: <template> <div class="image-uploader"> <img :src="uploadIcon" class="icon"/> <p>Drag your images here</p> <p>OR</p&...

    (一)首先实现html、css布局

    代码:

    <template>
      <div class="image-uploader">
        <img :src="uploadIcon" class="icon"/> 
        <p>Drag your images here</p>
        <p>OR</p>
        <div class="real-btn">
          SELECT A FiLE
          <input type="file" @change="inputChange" class="hide-btn">
        </div>
      </div>
    </template>
    
    <script>
    import uploadIcon from '../assets/upload.png'
    export default {
      name: 'imageUpload',
      data () {
        return {
          uploadIcon: uploadIcon
        }
      }
    }
    </script>
    
    <style scoped>
    .image-uploader {
      margin: 20px;
      border: 3px dashed #fff;
      background: #2196F3;
      padding: 10px;
      text-align: center;
      color: #fff;
      border-radius: 10px;
      font-weight: 500;
    }
    .real-btn {
      cursor: pointer;
      position: relative;
      padding: 10px 20px;
      background: #fff;
      border-radius: 10px;
      color: #2196F3;
    }
    .hide-btn {
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
      position: absolute;
      opacity: 0;
    }
    .icon {
      width: 50px;
      height: 50px;
    }
    </style>
    

    到这一步,样式就算是写完了

    注意!!

    由于只有 input 的 type 等于 file 的时候才能调起电脑文件夹,所以 SELECT A FiLE 这个按钮是与 <input type="file"> 这个元素重叠的,点击 SELECT A FiLE 这个按钮其实是 点击 <input type="file">,然后将 input 这个标签隐藏

    最后通过 opacity: 0;将真实的input 隐藏

    (二)实现拖拽上传

    (1)实现拖拽的效果需要 使用两个方法:dragEnter、dragLeave

    这时候又遇到坑了,我在最外层父元素上绑定 dragEnter、dragLeave

    当将图片在父元素区域内拖动时,会不断触发 dragEnter、dragLeave,而不是 只有进来和出去触发,经过了解解释如下:

    它是每进入一个新元素的同时就退出上一个元素,当退出父元素时,就不会有下一个进入的元素了, 所以进入的元素与退出的的元素相等,所以我们需要记录上次移入的节点

    // 拖拽进入上传文件区
        dragEnter (e) {
          this.currentTarget = e.target // 当前进入的元素
          this.isDragging = true
        },
        // 拖拽离开上传文件区
        dragLeave (e) {
          if (e.target === this.currentTarget) {
            this.isDragging = false
          }
        }

     (2)拖拽进去松手之后

    触发 drop 事件

    drop (e) {
          this.isDragging = false
          // 解决 e.dataTransfer.files 经常为空的问题
          var file = []
          file.forEach.call(e.dataTransfer.files, function (item) {
            file.push(item)
          }, false)
        },

    拖进去的文件 在 e.dataTransfer.files 这个字段里,我们需要转成数组

     

    (3)通过 input 上传

    <input type="file" @change="inputChange" class="hide-btn">
     async inputChange (e) {
       const base64 = await this.getBase64(e.target.files[0])
       this.getImageUrl(base64, e.target.files[0].name)
     },

    上传结果图:

    完整代码:

    <template>
      <div class="image-uploader"
        @dragenter="dragEnter"
        @dragleave="dragLeave"
        @drop.prevent="drop"
        @dragover.prevent
        :class="[ isDragging ? 'isDraging' : '' ]"
      > 
        <img :src="uploadIcon" class="icon"/>
        <p>Drag your images here</p>
        <p>OR</p>
        <div
          class="real-btn"
          :class="[ isDragging ? 'isDraging-btn' : '' ]"
        >
          SELECT A FILE
          <input type="file" @change="inputChange" class="hide-btn">
        </div>
        <!-- 上传图片展示列表 -->
        <div v-show="files.length > 0" class="file-list">
          <div
            v-for="(fileItem) in files"
            :key="fileItem.index"
            class="list-item"
          >
            <img :src="fileItem.url" alt="">
            <div>{{ fileItem.name }}</div>
          </div>
        </div>
      </div>
    </template>
    
    <script>
    import uploadIcon from '../assets/upload.png'
    import axios from 'axios'
    export default {
      name: 'imageUpload',
      data () {
        return {
          uploadIcon: uploadIcon,
          isDragging: false, // 是否正在拖拽
          currentTarget: null, // 上一个dragEnter的元素
          files: [] // 文件上传列表
        }
      },
      methods: {
        // 拖拽进入上传文件区
        dragEnter (e) {
          this.currentTarget = e.target
          this.isDragging = true
        },
        // 拖拽离开上传文件区
        dragLeave (e) {
          if (e.target === this.currentTarget) {
            this.isDragging = false
          }
        },
        // drop 是指将文件拖入父元素 松手触发的操作
        // 必须阻止 dragover 的默认行为 不然 drop 不生效
        // drop 也要阻止默认行为 不然拖进去后 浏览器自动打开该文件
        drop (e) {
          this.isDragging = false
          // 解决 e.dataTransfer.files 经常为空的问题
          // 把累数组转化成数组
          var file = []
          file.forEach.call(e.dataTransfer.files, function (item) {
            file.push(item)
          }, false)
          // Array.from(e.dataTransfer.files).forEach((item) => { file.push(item) })
          // 遍历对象 存入文件数组
          file.forEach(async (item) => {
            const base64 = await this.getBase64(item)
            this.getImageUrl(base64, item.name)
          })
        },
        async inputChange (e) {
          const base64 = await this.getBase64(e.target.files[0])
          this.getImageUrl(base64, e.target.files[0].name)
        },
        // 图片上传cdn 生成链接
        getImageUrl (base64, fileName) {
          const url = 'https://bird.ioliu.cn/v1?url=http://hn216.api.yesapi.cn'
          axios.post(url, {
            file: base64,
            s: 'App.CDN.UploadImgByBase64',
            app_key: '228290AC6E185D2121CD5878EDC4D010',
            file_name: fileName
          }).then((res) => {
            this.files.push({
              url: res.url,
              name: fileName,
              index: Math.random()
            })
          }).catch(() => {
            // 使用默认的
            const res = {url: 'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=191936283,2923048863&fm=26&gp=0.jpg'}
            this.files.push({
              url: res.url,
              name: fileName,
              index: Math.random()
            })
          })
        },
        // 图片转化成base64 便于上传cdn转成链接
        getBase64 (file) {
          return new Promise((resolve, reject) => {
            let reader = new FileReader()
            if (file) {
              // 将文件以Data URL形式读入页面
              let imgUrlBase64 = reader.readAsDataURL(file)
              reader.onload = function (e) {
                resolve(reader.result)
              }
            }
          })
        }
      }
    }
    </script>
    
    <style scoped>
    .image-uploader {
      margin: 20px;
      border: 3px dashed #fff;
      background: #2196F3;
      padding: 10px;
      text-align: center;
      color: #fff;
      border-radius: 10px;
      font-weight: 500;
    }
    .image-uploader.isDraging {
      background: #fff;
      border: 3px dashed #2196F3;
      color: #2196F3;
    }
    .real-btn {
      cursor: pointer;
      position: relative;
      padding: 10px 20px;
      background: #fff;
      border-radius: 10px;
      color: #2196F3;
    }
    .real-btn.isDraging-btn {
      background: #2196F3;
      color: #fff;
    }
    .hide-btn {
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
      position: absolute;
      opacity: 0;
    }
    .icon {
      width: 50px;
      height: 50px;
    }
    .file-list {
      display: flex;
      flex-wrap: wrap;
      margin-top: 10px;
    }
    .file-list img {
      width: 100%;
    }
    .list-item {
      width: 50%;
    }
    </style>
    

    (1)可以判断一下上传的文件类型,通过type字段,然后做相应的处理

    比如类型不正确,然后进行报错提示

    (2)还可以拿到上传文件的大小,通过size字段

     

     

    展开全文
  • vue实现文件上传和文件下载 转载于:https://www.cnblogs.com/knuzy/p/9569167.html
  • 本文实例为大家分享了vue实现文件上传读取及下载的具体代码,供大家参考,具体内容如下文件的上传利用input标签的type="file"属性,读取用FileReader对象,下载通过创建a标签实现下载export default {data () {...
  • springmvc+vue实现文件上传

    千次阅读 2018-06-10 10:46:34
    需求:spring+vue实现文件上传思路使用Element UI中的上传插件,在进行分装,然后传入java,使用java上传文件之后,保存文件所对应的url地址,以便下次访问时候使用。前端代码插件的使用具体见element UI的官方文档 ...
  • 利用spring boot和vue实现文件上传以及在线预览
  • 本文实例为大家分享了vue实现文件上传读取及下载的具体代码,供大家参考,具体内容如下文件的上传利用input标签的type="file"属性,读取用FileReader对象,下载通过创建a标签实现下载export default {data () {...
  • SpringBoot+Vue实现文件上传+预览

    千次阅读 2020-09-02 22:46:32
    从松哥的微信公众号上面看到了SpringBoot+Vue实现文件上传+预览的视频教程,如下图所示: 跟着做,使用IDEA一遍看一边做,没想到由于自己马虎将日期SimpleDateFormat simpleDateFormat = new SimpleDateFormat("/...
  • vue 文件上传,供大家参考,具体内容如下首先 先说一下想要实现的效果就如截图所见,需要将企业和需要上传的文件提交到后台处理,那么接下来就说如何实现vue 实现vue 页面代码class="upload-demo"ref="upload"action...
  • vue实现文件上传

    2019-09-16 09:46:47
    使用Upload组件实现文件上传,如果要使用upload,需要引入ElementUI,所以一般建议,如果使用了ElementUI做UI控件的话,则可以考虑使用Upload组件来实现文件上传。 <el-upload style="display:inline" :show-...
  • (给DotNet加星标,提升.Net技能)转自:吕小不cnblogs....在接下来的内容主要还是针对单文件上传,对于多文件的上传,我暂且尚未研究成功。其中pictureoptions类,由于我把关于图片上传相关的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,806
精华内容 722
关键字:

vue实现文件上传

vue 订阅