Vue3当数据发生变化时,v-for无法在循环中更新项



我在Vue3的for循环中遇到了一些麻烦。
我有这个文件数组。每个文件还具有进度和状态等属性。

当我用axios上传文件时,我也会用状态和进度更新当前上传的文件。

v-for循环没有捕捉到变化,仍然向我显示状态的旧值,如"pending";即使属性更改为"uploading"one_answers";uploaded"当axios正在进行/完成上传时。

我试图用reactive()包装数据没有运气。我也不能使用Vue.$set,因为它是从Vue3中删除的。有人知道怎么解吗?

我用方法更新当前上传文件:updateFileStatus()
它是在uploadFile方法中完成的axios请求中调用的。

整个组件如下。

如果有人能帮我,我先谢谢你。

<template>
<div class="flex items-center">
<div id="dropzone-wrapper" class="flex flex-col w-full">
<input
type="file"
ref="legacyFileSelect"
class="hidden"
@change="filesSelected"
:multiple="multiple"
:accept="extensions.join(', ')" />
<div
ref="dropzone"
class="flex items-center justify-center py-8 border-2 rounded border-dotted border-nord-frost-300 dark:border-nord-frost-300 w-full"
@dragover="dragOver"
@dragleave="dragLeave"
@drop="drop"
@click="$refs.legacyFileSelect.click()">
<span
class="text-nord-300/50 dark:text-nord-snow-storm-300/25 text-sm italic">
{{ placeholder }}
</span>
</div>
<div id="uploadResults" class="flex flex-col space-y-1 mt-2">
<template v-for="(file, fileIndex) in uploadResults" :key="fileIndex">
<div class="flex flex-col rounded border border-nord-snow-storm-300 dark:border-nord-snow-storm-300/25">
<div class="flex space-x-1 items-center p-1.5 px-2 pr-3">
<span v-if="!uploadOnSelect" class="mt-1">
<VButton
@click="removeFile(fileIndex)"
icon="delete"
color="red"
:noBackground="true"
size="lg"
:class="{ 'hidden': file.status !== 'pending' }"
/>
</span>
<span class="text-nord-300/50 dark:text-nord-snow-storm-300/50 text-xs mt-1">
{{ formatBytes(file.size) }}
</span>
<span class="text-nord-300 dark:text-nord-snow-storm-300 text-sm mt-0.5">
{{ file.name }}
</span>
<div class="flex grow justify-end">
<span
v-if="file.status === 'uploaded'"
class="text-nord-aurora-1100 dark:text-nord-aurora-1100 text-sm mt-0.5">
Uploaded
</span>
<span
v-if="file.status === 'uploading'"
class="text-nord-aurora-1100 dark:text-nord-aurora-1100 text-sm mt-0.5">
Uploading
</span>
<span
v-if="file.status === 'failed'"
class="text-nord-aurora-200 dark:text-nord-aurora-200 text-sm mt-0.5">
Failed
</span>
<span
v-if="file.status === 'pending'"
class="text-nord-300/50 dark:text-nord-snow-storm-300/50 text-sm mt-0.5">
Pending
</span>
</div>
</div>
<div class="flex-grow">
<div class="h-1">
<div
class="h-1 rounded-b"
:class="{
'bg-nord-snow-storm-300/25': file.status === 'pending',
'bg-nord-aurora-1100': file.status === 'uploading',
'bg-nord-aurora-1100': file.status === 'uploaded',
'bg-nord-aurora-200': file.status === 'failed',
}"
:style="{ width: `${file.progress}%` }">
</div>
</div>
</div>
</div>
</template>
</div>
<VButton v-if="!uploadOnSelect" @click="upload" color="blue" size="sm" class="mt-2">
Upload
</VButton>
</div>
</div>
</template>
<script>
import axios from 'axios'
import VButton from './V-Button.vue'
export default {
components: {
VButton,
},
props: {
multiple: {
type: Boolean,
required: false,
default: false,
},
url: {
type: String,
required: true,
default: null,
},
uploadOnSelect: {
type: Boolean,
required: false,
default: false,
},
extensions: {
type: Array,
required: false,
default: ['jpg', 'png', 'jpeg'],
},
maxFiles: {
type: Number,
required: false,
default: 5,
},
maxFileSize: {
type: Number,
required: false,
default: 5 * 1024 * 1024,
},
placeholder: {
type: String,
required: false,
default: 'Drag and drop file(s) here or click to select files',
},
headers: {
type: Object,
required: false,
default: {
'Content-Type': 'multipart/form-data' 
}
}
},
data() {
return {
uploadResults: [],
}
},
methods: {
/**
* Remove a file from the uploadResults array
* 
* @param {Number} fileIndex
* @return {void}
*/
removeFile(fileIndex) {
this.uploadResults.splice(fileIndex, 1)
},
/**
* Update the progress and status of a given
* file in the uploadResults array
* 
* @param {String} status 
* @param {Number} progress 
* @param {Number} fileIndex
* @return {void} 
*/
updateFileStatus(status, progress, fileIndex) {
this.uploadResults[fileIndex].status = status
this.uploadResults[fileIndex].progress = progress
},
upload() {
this.uploadResults.forEach((file, index) => {
this.uploadFile(file, index)
})
},
/**
* Upload a file to the server using axios
* 
* @param {File} file 
* @param {Number} fileIndex 
* @return {void}
*/
uploadFile(file, fileIndex) {
let formData = new FormData()
formData.append('file', file)
axios.post(this.url, formData, {
headers: this.headers,

onUploadProgress: (progressEvent) => {
let percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
this.updateFileStatus('uploading', percentCompleted, fileIndex)
},
}).then((response) => {
this.updateFileStatus('uploaded', 100, fileIndex)
this.$emit('success', response.data)
}).catch((error) => {
this.updateFileStatus('failed', 0, fileIndex)
this.$emit('error', error.response.data.message)
})
return
},
/**
* When files are dragged over dropzone area this method is called
* 
* @param {*} event
* @returns {void}
*/
dragOver(event) {
event.preventDefault()
event.stopPropagation()
this.$refs.dropzone.classList.add('border-nord-frost-400')
this.$refs.dropzone.classList.remove('border-nord-frost-300')
},
/**
* When files are dragged out of dropzone area this method is called
* 
* @param {*} event 
* @returns {void}
*/
dragLeave(event) {
event.preventDefault()
event.stopPropagation()
this.$refs.dropzone.classList.add('border-nord-frost-300')
this.$refs.dropzone.classList.remove('border-nord-frost-400')
},
/**
* When files are dropped in dropzone area this method is called
* 
* @param {*} event 
* @returns {void}
*/
drop(event) {
event.preventDefault()
event.stopPropagation()
this.$refs.dropzone.classList.add('border-nord-frost-300')
this.$refs.dropzone.classList.remove('border-nord-frost-400')
this.prepareValidate(event.dataTransfer.files)
},
/**
* When files are selected from input file or
* dropped in dropzone area this method is called
* 
* @param {*} event 
* @returns {void}
*/
filesSelected(event) {
this.prepareValidate(event.target.files)
},
/**
* Prepare and validate files before upload or send to
* upload method if uploadOnSelect is set to true
* 
* @param {FileList} files 
* @returns {void}
*/
prepareValidate(files) {
if (!this.multiple && files.length > 1) {
this.$emit('error', 'Only one file can be uploaded')
return
}
if (!this.multiple && this.uploadResults.length > 0) {
this.uploadResults = []
}
if (files.length > this.maxFiles && this.multiple) {
this.$emit('error', `Max files allowed is ${this.maxFiles}`)
return
}
for (let i = 0; i < files.length; i++) {
let file = files[i]

if (!this.validateFile(file)) {
return
}
file.status = 'pending'
file.progress = 0
this.uploadResults.push(file)
}
if (this.uploadOnSelect) {
this.upload()
}
return
},
/**
* Validate single file
* 
* @param {File} file 
* @returns {Boolean}
*/
validateFile(file) {
if (!this.extensions.includes(file.name.split('.').pop())) {
this.$emit('error', 'File type not allowed')
return false
}

if (file.size > this.maxFileSize) {
this.$emit('error', `File size too large. Max file size is ${this.formatBytes(this.maxFileSize)}`)
return false
}
return true
},
/**
* Format bytes to human readable format
* 
* @param {Number} bytes 
* @param {Number} decimals
* @returns {String}
*/
formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const i = Math.floor(Math.log(bytes) / Math.log(k));
const units = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const unit = units[i] || 'Bytes';
return `${(bytes / Math.pow(k, i)).toFixed(dm)} ${unit}`;
},
},
emits: ['error', 'success']
}
</script>

通过用一个新对象替换整个File对象来解决。由于某种原因,这似乎行得通。

updateFileStatus(status, progress, fileIndex) {
let file = this.uploadResults[fileIndex]
file = {
...file,
name: file.name,
size: file.size,
status: status,
progress: progress,
}
this.uploadResults.splice(fileIndex, 1, file)
},

最新更新