当我在 vue 组件上输入另一个输入时,为啥输入文件的值丢失?
Posted
技术标签:
【中文标题】当我在 vue 组件上输入另一个输入时,为啥输入文件的值丢失?【英文标题】: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)"
。它在输入类型文本、输入类型文件、输入类型日期和输入类型编号上运行。因为我把它组合成一个组件
【参考方案1】:
所以问题是:
在
<form-input type="file">
中选择文件后,如果您在<form-input type="type">
中输入内容,<form-input type="file">
将被删除。这是为什么呢?
这是因为当您编辑 <form-input type="text">
时,Vue 会“重新绘制”组件。
当它重新绘制<form-input type="file">
时,它会回到“未选择任何内容”,因为它是一个新的<input type="file">
。
解决方案:保留文件的值
由于Kaiido 指向comments,latest versions of browsers, you can set 以标准方式指向<input type="file">
的文件。
这就是下面的代码所做的。它监视value
属性(当父级使用v-model
并将其值设置为<input type="file">
的.files
属性时。
我们必须使用两个<input>
(和v-if
/v-else
),因为当它是<input type="file">
时,可以设置: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
是什么意思?以上是关于当我在 vue 组件上输入另一个输入时,为啥输入文件的值丢失?的主要内容,如果未能解决你的问题,请参考以下文章
为啥当我在我的 ArrayList 上输入确切的名称时,“包含”方法返回 false? [复制]
如何在输入文本区域中创建 Enter 事件以使用 Vue.js 和 Electron 按下按钮
当我在 Mac 终端中输入 `route -n` 时,为啥不显示路由表? [关闭]