我在Vue 3中的for循环中遇到了一些问题。
我有一个文件数组,每个文件都有进度和状态等属性。
当我用axios上传文件时,我还用状态和进度更新了当前上传的文件。v-for
循环没有捕捉到这些变化,并且仍然向我显示旧的状态值,比如“pending”,即使当axios正在进行/完成上传时,属性被更改为“uploading”和“uploaded”。
我试着用reactive()
Package 数据,但运气不好。我也不能使用Vue.$set
,因为它从Vue 3中删除了。有人知道我该如何解决这个问题吗?
我使用以下方法更新当前上传的文件: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>
1条答案
按热度按时间webghufk1#
通过用一个新对象替换整个文件对象来解决它。这似乎出于某种原因起作用了。