【问题标题】:Why the value of input file missing when I input the another input on the vue component?当我在 vue 组件上输入另一个输入时,为什么输入文件的值丢失?
【发布时间】:2018-08-26 12:38:36
【问题描述】:

我有两个组件

我的第一个组件(父组件)是这样的:

<template>
    <div>
        ...
            <form-input id="name" name="name" v-model="name">Name</form-input>
            <form-input id="birth-date" name="birth_date" type="date" v-model="birthDate">Date of Birth</form-input>
            <form-input id="avatar" name="avatar" type="file" v-on:triggerChange="onFileChange($event)">Avatar</form-input>
            <form-input id="mobile-number" name="mobile_number" type="number" v-model="mobileNumber">Mobile Number</form-input>
        ...
    </div>
</template>
<script>
    export default {
        data() {
            return {
                name: null,
                birthDate:  null,
                mobileNumber: null
            }
        },
        methods: {
            onFileChange(e) {
                let self = this
                this.validate(e.target.files[0])
                .then(function(res) {
                    let files = e.target.files,
                    reader = new FileReader()
                    // if any values
                    if (files.length) {
                        self.removeErrorMessageUpload()
                        self.files = files[0]
                        reader.onload = (e) => {
                            self.updateProfileAvatar(e.target.result)
                        }
                        reader.readAsDataURL(files[0])
                    }
                })
                .catch(function() {
                    // do something in the case where the image is not valid
                    self.displayErrorMessageUpload()
                })
            },
            validate(image) {
                let self = this
                return new Promise(function(resolve, reject) {
                    // validation file type
                    if (!self.allowableTypes.includes(image.name.split(".").pop().toLowerCase())) {
                        reject()
                    }
                    // validation file size
                    if (image.size > self.maximumSize) {
                        reject()
                    }
                    // validation image resolution
                    let img = new Image()
                    img.src = window.URL.createObjectURL(image)
                    img.onload = function() {
                        let width = img.naturalWidth,
                            height = img.naturalHeight

                        window.URL.revokeObjectURL(img.src)

                        if (width != 100 && height != 100) {
                            reject()
                        } 
                        else {
                            resolve()
                        }
                    }
                })         
            },
        }
    }
</script>

从父组件调用子组件(表单输入组件)

我的子组件是输入类型文本、输入类型日期、输入类型文件和输入类型编号。我将它们全部组合成一个组件

子组件是这样的:

<template>
    <div class="form-group">
        <label :for="id" class="col-sm-3 control-label"><slot></slot></label>
        <div class="col-sm-9">
            <input :type="type" :name="name" :id="id" class="form-control" :value="value" v-on:change="applySelected($event)" @input="$emit('input', $event.target.value)">
        </div>
    </div>
</template>

<script>
    export default {
        name: "form-input",
        props: {
            'id': String,
            'name': String,
            'isRequired': {
                type: Boolean,
                default: true
            },
            'type': {
                type: String,
                default() {
                    if(this.type == 'number')
                        return 'number'
                    return 'text'
                }
            },
            'value': {
                type: [String, Number]
            }
        },
        methods: {
            applySelected(e) {
                this.$emit('triggerChange', e)
            }
        }
    }
</script>

因为我合并到 1 个组件中,所以我遇到了一个新问题

如果我输入输入类型文件,文件的值将显示在输入类型文件中

但是如果我在输入类型文本中输入,输入类型文件的值丢失了

为什么输入类型文件的值不见了?

演示:

Vue.component('form-input', {
  template: "#form-input-tpl",
  name: "form-input",
  props: {
    'id': String,
    'name': String,
    'isRequired': {type: Boolean, default: true},
    'type': { type: String, default () {if (this.type == 'number') {return 'number'} else {return 'text'}}},
    'value': { type: [String, Number] }
  },
  methods: {
    applySelected(e) { this.$emit('triggerChange', e) }
  }
});

new Vue({
  el: '#app',
  data: {
    name: null,
    birthDate: null,
    mobileNumber: null
  },
  methods: {
    onFileChange(e) {
      // ...
    }
  }
})
<script src="https://unpkg.com/vue"></script>

<template id="form-input-tpl">
    <div class="form-group">
        <label :for="id" class="col-sm-3 control-label"><slot></slot></label>
        <div class="col-sm-9">
            <input :type="type" :name="name" :id="id" class="form-control" :value="value" v-on:change="applySelected($event)" @input="$emit('input', $event.target.value)">
        </div>
    </div>
</template>

<div id="app">
  <h3>Select a file, then type a name. The file will be reset.</h3>
  <div>
    <form-input id="name" name="name" v-model="name">Name</form-input>
    <form-input id="birth-date" name="birth_date" type="date" v-model="birthDate">Date of Birth</form-input>
    <form-input id="avatar" name="avatar" type="file" v-on:triggerChange="onFileChange($event)">Avatar</form-input>
    <form-input id="mobile-number" name="mobile_number" type="number" v-model="mobileNumber">Mobile Number</form-input>
  </div>
</div>

【问题讨论】:

  • "为什么输入类型文件的值丢失?"与之前的陈述冲突,即是?
  • @Mark Schultheiss 你什么意思?我不明白
  • 你说“如果我输入输入类型文件,文件的值会显示在输入类型文件中但是如果我输入输入类型文本,输入类型文件的值丢失为什么值输入类型文件丢失?”但是那句“但是如果我在输入类型文本中输入,输入类型文件的值丢失”是什么意思? text 类型字段正在影响file 类型字段?这是没有意义的,一个领域会影响另一个领域,这表明这里可能还有其他东西在起作用。请注意,如果未指定,则默认 html 输入类型为 type="text"
  • 也许将呈现的 HTML 显示为附加信息。
  • @MarkSchultheiss 这显然是因为v-on:change="applySelected($event)"@input="$emit('input', $event.target.value)"。它在输入类型文本、输入类型文件、输入类型日期和输入类型编号上运行。因为我把它组合成一个组件

标签: javascript vue.js vuejs2 vue-component vuex


【解决方案1】:

所以问题是:

&lt;form-input type="file"&gt; 中选择文件后,如果您在&lt;form-input type="type"&gt; 中输入内容,&lt;form-input type="file"&gt; 将被删除。这是为什么呢?

这是因为当您编辑 &lt;form-input type="text"&gt; 时,Vue 会“重新绘制”组件。

当它重新绘制&lt;form-input type="file"&gt; 时,它会回到“未选择任何内容”,因为它是一个新的&lt;input type="file"&gt;


解决方案:保留文件的值

由于Kaiido 指向commentslatest versions of browsers, you can set 以标准方式指向&lt;input type="file"&gt; 的文件。

这就是下面的代码所做的。它监视value 属性(当父级使用v-model 并将其值设置为&lt;input type="file"&gt;.files 属性时。

我们必须使用两个&lt;input&gt;(和v-if/v-else),因为当它是&lt;input type="file"&gt;时,可以设置:value属性,事件处理程序应该不同(@change="$emit('input', $event.target.files)")我们想保留一个ref,这样我们就可以设置files

完整的工作演示如下

Vue.component('form-input', {
  template: "#form-input-tpl",
  name: "form-input",
  props: {
    'id': String,
    'name': String,
    'isRequired': {type: Boolean, default: true},
    'type': {type: String, default: 'text'},
    'value': {type: [String, Number, FileList, DataTransfer]}
  },
  mounted() {
    // set files upon creation or update if parent's value changes
    this.$watch('value', () => {
      if (this.type === "file") { this.$refs.inputFile.files = this.value; }
    }, { immediate: true });
  }
});

new Vue({
  el: '#app',
  data: {
    name: null,
    birthDate: null,
    mobileNumber: null,
    files: null
  }
})
<script src="https://unpkg.com/vue"></script>

<template id="form-input-tpl">
    <div class="form-group">
        <label :for="id" class="col-sm-3 control-label"><slot></slot></label>
        <div class="col-sm-9">
           <input v-if="type !== 'file'" :type="type" :name="name" :id="id" class="form-control" :value="value" @input="$emit('input', $event.target.value)">
            
           <input v-else :type="type" :name="name" :id="id" class="form-control" @change="$emit('input', $event.target.files)" ref="inputFile">
        </div>
    </div>
</template>

<div id="app">
  <div>
    <form-input id="name" name="name" v-model="name">Name</form-input>
    <form-input id="birth-date" name="birth_date" type="date" v-model="birthDate">Date of Birth</form-input>
    <form-input id="avatar" name="avatar" type="file" v-model="files">Avatar</form-input>
    <form-input id="mobile-number" name="mobile_number" type="number" v-model="mobileNumber">Mobile Number</form-input>
  </div>
</div>

使用您的file-change 事件和validate 函数:

Vue.component('form-input', {
  template: "#form-input-tpl",
  name: "form-input",
  props: {
    'id': String,
    'name': String,
    'isRequired': {type: Boolean, default: true},
    'type': {type: String, default: 'text'},
    'value': {type: [String, Number, FileList, DataTransfer]}
  },
  mounted() {
    // set files upon creation or update if parent's value changes
    this.$watch('value', () => {
      if (this.type === "file") { this.$refs.inputFile.files = this.value; }
    }, { immediate: true });
  }
});

new Vue({
  el: '#app',
  data: {
    name: null,
    birthDate: null,
    mobileNumber: null,
    filesVModel: null,
    allowableTypes: ['jpg', 'jpeg', 'png'],
    maximumSize: 1000,
    files: null
  },
  methods: {
    onFileChange(e) {
      console.log('onfilechange!');
      let self = this
      this.validate(e.target.files[0])
        .then(function(res) {
          let files = e.target.files,
            reader = new FileReader()
          // if any values
          if (files.length) {
            self.removeErrorMessageUpload()
            self.files = files[0]
            reader.onload = (e) => {
              self.updateProfileAvatar(e.target.result)
            }
            reader.readAsDataURL(files[0])
          }
        })
        .catch(function(err) {
          // do something in the case where the image is not valid
          self.displayErrorMessageUpload(err)
        })
    },
    validate(image) {
      let self = this
      return new Promise(function(resolve, reject) {
        // validation file type
        if (!self.allowableTypes.includes(image.name.split(".").pop().toLowerCase())) {
          reject("Type " + image.name.split(".").pop().toLowerCase() + " is not allowed.")
        }
        // validation file size
        if (image.size > self.maximumSize) {
          reject("Image size " + image.size + " is larger than allowed " + self.maximumSize + ".")
        }
        // validation image resolution
        let img = new Image()
        img.src = window.URL.createObjectURL(image)
        img.onload = function() {
          let width = img.naturalWidth,
            height = img.naturalHeight

          window.URL.revokeObjectURL(img.src)

          if (width != 100  && height != 100) {
            reject("Width and height are " + width + " and " + height + " and not both 100")
          } else {
            resolve()
          }
        }
      })
    },
    displayErrorMessageUpload(msg) {
      console.log('displayErrorMessageUpload', msg);
    },
    removeErrorMessageUpload() {
      console.log('removeErrorMessageUpload');
    },
    updateProfileAvatar(result) {
      console.log('updateProfileAvatar', result);
    }
  }
})
<script src="https://unpkg.com/vue"></script>

<template id="form-input-tpl">
    <div class="form-group">
        <label :for="id" class="col-sm-3 control-label"><slot></slot></label>
        <div class="col-sm-9">
           <input v-if="type !== 'file'" :type="type" :name="name" :id="id" class="form-control" :value="value" @input="$emit('input', $event.target.value)">
            
           <input v-else :type="type" :name="name" :id="id" class="form-control" @change="$emit('input', $event.target.files)" ref="inputFile" v-on:change="$emit('file-change', $event)">
        </div>
    </div>
</template>

<div id="app">
  <div>
    <form-input id="name" name="name" v-model="name">Name</form-input>
    <form-input id="birth-date" name="birth_date" type="date" v-model="birthDate">Date of Birth</form-input>
    <form-input id="avatar" name="avatar" type="file" v-model="filesVModel" @file-change="onFileChange">Avatar</form-input>
    <form-input id="mobile-number" name="mobile_number" type="number" v-model="mobileNumber">Mobile Number</form-input>
  </div>
</div>

【讨论】:

  • You can(在最新的浏览器中)将input[type=file]files 属性设置为另一个文件列表。我不了解 vue.js,因此无法自己捕获渲染事件,也无法访问新输入,只是想让您知道它实际上是可行的。
  • @Kaiido 哼哼,我实际上知道这是通过故障或其他原因实现的。由于它是非标准的(至少 v-model 不支持),我认为它不值得追求。但是感谢您告知我们,在较新的浏览器中使用标准方法是个好消息!
  • @acdcjunior 没有别的办法吗?我正在使用所需的 html5。如果使用您的方式,它将始终显示验证 html5。以及我是否可以使用您的方式将选择的文件发送到服务器端?
  • 嘿,伙计们,感谢您的推动 :) 我最终研究了更多,我认为最终版本非常干净!它使用标准接口设置.files!没有黑客!在这个过程中我学到了很多!非常感谢! @SuccessMan @Kaiido
  • @acdcjunior 欢迎您。我会试试看。顺便说一句,immediate: true 是什么意思?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-08
  • 1970-01-01
  • 2019-09-27
  • 1970-01-01
相关资源
最近更新 更多