You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3点击父组件按钮调用子组件upload函数的问题排查

问题分析与解决方案

当前代码存在的问题

  • 子组件Ref未正确声明:父组件模板中给MediaImport设置了ref="MediaImport",但setup函数里没有定义对应的ref变量,无法获取子组件实例调用其upload方法。
  • 自动上传与手动触发冲突:MediaImport组件设置了:auto="true",文件选择后会自动上传,不符合“点击父组件按钮才触发上传”的需求。
  • 上传完成事件触发时机错误:子组件upload方法中,XHR请求发送后立即$emit('has-uploaded', true),但XHR是异步请求,此时上传并未真正完成,会导致父组件误判上传状态。
  • 跳转逻辑时机错误:父组件goToRecognitionView函数中同步判断hasUploaded.value,但上传是异步操作,无法正确捕获上传完成的状态。

正确实现方式

1. 父组件(VideoRecognition)修改

<script lang="ts">
import Button from 'primevue/button';
import MediaImport from '@/components/MediaImport';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import WebSocketService from '@/utils/WebSocketService';

export default {
  name: "VideoRecognition",
  emits: ['upload-media'],
  setup(_, { emit }) {
    const hasFiles = ref<Boolean>(false);
    const mediaObjectUrl = ref<String>("");
    // 声明子组件Ref,用于调用子组件方法
    const mediaImportRef = ref<InstanceType<typeof MediaImport> | null>(null);

    const handleHasFiles = (value: boolean) => {
      hasFiles.value = value;
    };

    const handleSendMediaObjectUrl = (path: string) => {
      mediaObjectUrl.value = path;
    };

    const router = useRouter();
    const ws = new WebSocketService();
    
    // 上传成功后的跳转逻辑
    const handleUploadSuccess = () => {
      ws.send("video_recognition_media_object_url", mediaObjectUrl.value);
      ws.close();
      router.push('/recognition');
    };

    const goToRecognitionView = () => {
      emit('upload-media', true);
      // 调用子组件的upload方法触发上传
      if (mediaImportRef.value && hasFiles.value) {
        mediaImportRef.value.upload();
      } else {
        console.log('请先选择文件');
      }
    };

    return { 
      hasFiles, 
      handleHasFiles, 
      handleSendMediaObjectUrl, 
      goToRecognitionView,
      mediaImportRef,
      handleUploadSuccess
    };
  },
  components: {
    Button,
    MediaImport,
  },
};
</script>

<template>
    <div class="container">
        <div class="flex flex-col gap-4">
            <div class="flex flex-col justify-center w-full">
                <div class="w-full flex-auto flex flex-col justify-center items-center border-2 border-solid border-surface-200 dark:border-surface-700 rounded-md bg-[#1e2d4b] font-medium p-4">
                    <p class="pb-4">Select video to recognize</p>
                    <!-- 绑定Ref,关闭自动上传,监听上传成功事件 -->
                    <MediaImport 
                        ref="mediaImportRef" 
                        name="media[]" 
                        url="/api/upload" 
                        mediaType="video" 
                        accept="video/*" 
                        @has-files="handleHasFiles" 
                        @media-object-url="handleSendMediaObjectUrl" 
                        @upload="handleUploadSuccess"
                        :maxFileSize="512000000" 
                        :auto="false" 
                        chooseLabel="Browse" 
                    />
                </div>
                <div class="flex pt-4 justify-end">
                    <Button label="Next" icon="pi pi-arrow-right" iconPos="right" @click="goToRecognitionView" />
                </div>
            </div>
        </div>
    </div>
</template>

2. 子组件(MediaImport)修改

调整upload方法,确保上传成功后才触发完成事件:

upload() {
    if (this.customUpload) {
        if (this.fileLimit) {
            this.uploadedFileCount += this.files.length;
        }

        this.$emit('uploader', { files: this.files });
        this.clear();
        // 自定义上传成功后触发完成事件
        this.$emit('upload', { files: this.files });
    } else {
        let xhr = new XMLHttpRequest();
        let formData = new FormData();

        this.$emit('before-upload', {
            xhr: xhr,
            formData: formData
        });

        for (let file of this.files) {
            const minFileSizeToCreatePart = 80 * 1024 * 1024 // 80 MB
            const fileName = this.generateUniqueFileName(file.name);
            if (file.size >= minFileSizeToCreatePart) {
                let start = 0;
                let partSize = 60 * 1024 * 1024; // 60 MB
                while (start < file.size) {
                    let chunk = file.slice(start, start + partSize);
                    formData.append(`${this.name}[]`, chunk, `${fileName}.part${Math.ceil((start + 1) / partSize)}`);
                    start += partSize;
                }
            } else {
                formData.append(this.name, file, fileName);
            }
        }

        xhr.upload.addEventListener('progress', (event) => {
            if (event.lengthComputable) {
                this.progress = Math.round((event.loaded * 100) / event.total);
            }

            this.$emit('progress', {
                originalEvent: event,
                progress: this.progress
            });
        });

        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                this.progress = 0;

                if (xhr.status >= 200 && xhr.status < 300) {
                    if (this.fileLimit) {
                        this.uploadedFileCount += this.files.length;
                    }

                    this.$emit('upload', {
                        xhr: xhr,
                        files: this.files
                    });
                } else {
                    this.$emit('error', {
                        xhr: xhr,
                        files: this.files
                    });
                }

                this.uploadedFiles.push(...this.files);
                this.clear();
            }
        };

        xhr.open('POST', this.url, true);

        this.$emit('before-send', {
            xhr: xhr,
            formData: formData
        });

        xhr.withCredentials = this.withCredentials;

        xhr.send(formData);
    }
},

内容的提问来源于stack exchange,提问作者Tebyy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 12:34:54