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

React如何获取Flask后端转换后的文件并提供下载功能

实现React组件DisplayConvertedFile的完整方案

针对开发类似Convertio的文件转换服务需求,以下是实现DisplayConvertedFile组件的具体方案,同时调整前后端配合逻辑以支持异步转换状态跟踪和文件下载:

一、前端部分实现

1. 扩展AppContext状态

首先在全局Context中添加任务ID和转换状态的管理,用于组件间共享数据:

import { createContext, useState } from 'react';

export const AppContext = createContext();

export function AppProvider({ children }) {
  const [file, setFile] = useState(null);
  const [taskId, setTaskId] = useState(null);
  const [conversionStatus, setConversionStatus] = useState(null); // 可选值:pending/completed/failed

  return (
    <AppContext.Provider value={{ 
      file, setFile, 
      taskId, setTaskId, 
      conversionStatus, setConversionStatus 
    }}>
      {children}
    </AppContext.Provider>
  );
}

2. 修改ConvertFileButton组件

调整原按钮组件,处理后端响应获取任务ID并更新全局状态:

export default function ConvertFileButton() {
  const { file, setTaskId, setConversionStatus } = useContext(AppContext);

  const handleConvert = async () => {
    if (!file) return;
    
    const formData = new FormData();
    formData.append('file', file);
    
    try {
      const response = await fetch('http://localhost:5000/transcript', {
        method: 'POST',
        body: formData
      });
      // 从后端重定向的Location头中提取task_id
      const locationHeader = response.headers.get('Location');
      if (locationHeader) {
        const taskId = new URL(locationHeader).searchParams.get('id');
        setTaskId(taskId);
        setConversionStatus('pending');
      }
    } catch (error) {
      console.error('转换请求失败:', error);
      setConversionStatus('failed');
    }
  };

  return (
    <MyButton onClick={handleConvert} disabled={!file}>CONVERT</MyButton>
  );
}

3. 实现DisplayConvertedFile组件

该组件负责轮询任务状态,转换完成后提供下载按钮:

import { useEffect, useContext } from 'react';
import { AppContext } from './AppContext';
import MyButton from './MyButton'; // 假设你的按钮组件路径

export default function DisplayConvertedFile() {
  const { taskId, conversionStatus, setConversionStatus } = useContext(AppContext);

  // 轮询任务状态,每2秒查询一次
  useEffect(() => {
    if (!taskId || ['completed', 'failed'].includes(conversionStatus)) return;

    const interval = setInterval(async () => {
      try {
        const res = await fetch(`http://localhost:5000/status?id=${taskId}`);
        const data = await res.json();
        
        if (data.status === 'completed') {
          setConversionStatus('completed');
          clearInterval(interval);
        } else if (data.status === 'failed') {
          setConversionStatus('failed');
          clearInterval(interval);
        }
      } catch (err) {
        console.error('查询任务状态失败:', err);
        clearInterval(interval);
      }
    }, 2000);

    return () => clearInterval(interval);
  }, [taskId, conversionStatus, setConversionStatus]);

  // 触发文件下载逻辑
  const handleDownload = async () => {
    try {
      const res = await fetch(`http://localhost:5000/get?id=${taskId}`);
      const blob = await res.blob();
      const downloadUrl = window.URL.createObjectURL(blob);
      
      // 创建临时a标签触发下载
      const link = document.createElement('a');
      link.href = downloadUrl;
      // 从响应头获取文件名,无则使用默认命名
      const fileName = res.headers.get('Content-Disposition')?.split('filename=')[1] || `converted_${taskId}`;
      link.download = fileName;
      document.body.appendChild(link);
      link.click();
      
      // 清理资源
      window.URL.revokeObjectURL(downloadUrl);
      document.body.removeChild(link);
    } catch (err) {
      console.error('下载文件失败:', err);
    }
  };

  // 根据状态渲染不同内容
  if (!taskId) return <div>请先上传并转换文件</div>;
  if (conversionStatus === 'pending') return <div>转换中,请稍候...</div>;
  if (conversionStatus === 'failed') return <div>转换失败,请重试</div>;

  return (
    <div className="converted-file-container">
      <p>转换完成!</p>
      <MyButton onClick={handleDownload}>下载转换后文件</MyButton>
    </div>
  );
}

二、后端接口调整

原后端需要添加任务状态查询接口,并调整现有接口逻辑以支持前端交互:

1. 添加任务状态查询接口

from flask import jsonify, request
import uuid

# 示例任务存储(生产环境建议用数据库/Redis)
task_storage = {}

@mod.route('/status', methods=['GET'])
def get_task_status():
    task_id = request.args.get('id')
    task = task_storage.get(task_id)
    if not task:
        return jsonify({'status': 'not_found'}), 404
    return jsonify({'status': task['status']})

2. 调整/get接口,根据task_id返回对应文件

@mod.route('/get', methods=['GET'])
def get_result():
    task_id = request.args.get('id')
    task = task_storage.get(task_id)
    if not task or task['status'] != 'completed':
        return jsonify({'error': '任务未完成或不存在'}), 400
    # 设置下载文件名,方便用户识别
    return send_file(
        task['file_path'],
        as_attachment=True,
        download_name=f"converted_{task['original_filename']}"
    )

3. 完善/transcript接口的任务存储逻辑

@mod.route('/transcript', methods=['POST'])
def create_transcript_task():
    # 处理上传文件
    uploaded_file = request.files.get('file')
    if not uploaded_file:
        return jsonify({'error': '未上传文件'}), 400
    
    # 生成唯一任务ID
    task_id = str(uuid.uuid4())
    # 初始化任务状态,保存到存储
    task_storage[task_id] = {
        'status': 'pending',
        'original_filename': uploaded_file.filename,
        'file_path': '' # 后续转换完成后更新为实际文件路径
    }
    
    # 启动异步转换任务(生产环境建议用Celery等异步框架)
    # convert_file.delay(uploaded_file, task_id)
    
    # 保持原重定向逻辑,或直接返回task_id的JSON响应
    return redirect(url_for('backend.status_page') + f'?id={task_id}')

注意:生产环境中,异步转换任务建议使用Celery+Redis/RabbitMQ实现,避免阻塞Flask主线程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 19:48:23