ReactJS结合WEB API实现批量员工数据导入进度条方案咨询
实现批量员工数据提交的实时进度展示方案
针对批量提交数千条员工数据后无法感知后端处理进度的问题,结合你使用的React和C#后端场景,提供三个实用的实现方案:
方案1:拆分批量请求,前端自主计算进度
如果后端不想做过多改动,这是最快速的实现方式——把大数组拆分成小批次提交,前端根据已完成的批次占比计算进度。
前端实现步骤:
- 安装你提到的进度条组件:
npm install @ramonak/react-progress-bar
- React组件内的完整逻辑:
import { useState } from 'react'; import ProgressBar from '@ramonak/react-progress-bar'; const EmployeeUpload = () => { const [progress, setProgress] = useState(0); const [isUploading, setIsUploading] = useState(false); const usr = '你的用户Token'; // 替换为实际Token // 将数据拆分为指定大小的批次 const splitIntoBatches = (data, batchSize) => { const batches = []; for (let i = 0; i < data.length; i += batchSize) { batches.push(data.slice(i, i + batchSize)); } return batches; }; const saveEmployees = async (empData) => { setIsUploading(true); setProgress(0); const batches = splitIntoBatches(empData, 50); // 可根据后端性能调整批次大小 const totalBatches = batches.length; for (let i = 0; i < totalBatches; i++) { const batch = batches[i]; const requestOptions = { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ Token: usr, EmpData: batch }) }; await fetch('http://localhost:5000/api/services/app/Employee/AddEmployees', requestOptions); // 计算并更新进度 setProgress(Math.round(((i + 1) / totalBatches) * 100)); } setIsUploading(false); alert('数据提交完成'); }; // 示例员工数据,替换为你的实际数据 const empData = [ { id: 5495, employeeName: "Sarwen", department: "Production", factoryLocation: "Denmark" }, { id: 5496, employeeName: "Kate", department: "HR", factoryLocation: "Denmark" } ]; return ( <div> <button onClick={() => saveEmployees(empData)} disabled={isUploading}> {isUploading ? '提交中...' : '批量提交员工数据'} </button> {isUploading && ( <ProgressBar completed={progress} bgColor="#36a2eb" labelColor="#fff" height="20px" width="100%" /> )} </div> ); }; export default EmployeeUpload;
方案2:后端提供进度查询接口,前端轮询获取进度
如果必须一次性提交所有数据给后端,需要后端配合添加进度追踪能力:
后端修改:
- 定义进度追踪模型和缓存(生产环境建议用Redis替代内存缓存):
// 进度追踪模型 public class ProcessProgress { public string TaskId { get; set; } public int TotalCount { get; set; } public int ProcessedCount { get; set; } public bool IsCompleted { get; set; } } // 内存缓存容器(多服务器部署需替换为分布式缓存) public static class ProgressCache { public static Dictionary<string, ProcessProgress> Progresses = new Dictionary<string, ProcessProgress>(); }
- 修改原
AddEmployees接口,返回任务ID并异步处理数据更新进度:
public async Task<string> AddEmployees(InputDto input) { var taskId = Guid.NewGuid().ToString(); var totalCount = input.EmpData.Length; // 初始化进度记录 ProgressCache.Progresses[taskId] = new ProcessProgress { TaskId = taskId, TotalCount = totalCount, ProcessedCount = 0, IsCompleted = false }; // 异步处理数据,避免阻塞接口返回 _ = Task.Run(async () => { foreach (var emp in input.EmpData) { // 执行数据库插入逻辑 // await _dbContext.Employees.AddAsync(emp); // await _dbContext.SaveChangesAsync(); // 更新已处理数量 ProgressCache.Progresses[taskId].ProcessedCount++; await Task.Delay(10); // 模拟数据库操作耗时 } ProgressCache.Progresses[taskId].IsCompleted = true; }); // 立即返回任务ID,供前端查询进度 return taskId; }
- 新增进度查询接口:
public IActionResult GetProcessProgress(string taskId) { if (ProgressCache.Progresses.TryGetValue(taskId, out var progress)) { return Ok(new { progress.TotalCount, progress.ProcessedCount, progress.IsCompleted, Percentage = (int)Math.Round(((double)progress.ProcessedCount / progress.TotalCount) * 100) }); } return NotFound("任务不存在"); }
前端实现:
import { useState, useEffect } from 'react'; import ProgressBar from '@ramonak/react-progress-bar'; const EmployeeUpload = () => { const [progress, setProgress] = useState(0); const [isUploading, setIsUploading] = useState(false); const [taskId, setTaskId] = useState(''); const usr = '你的用户Token'; const saveEmployees = async (empData) => { setIsUploading(true); setProgress(0); const requestOptions = { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ Token: usr, EmpData: empData }) }; // 提交数据并获取任务ID const response = await fetch('http://localhost:5000/api/services/app/Employee/AddEmployees', requestOptions); const newTaskId = await response.text(); setTaskId(newTaskId); }; // 轮询进度接口 useEffect(() => { let interval; if (taskId && isUploading) { interval = setInterval(async () => { const response = await fetch(`http://localhost:5000/api/services/app/Employee/GetProcessProgress?taskId=${taskId}`); const progressData = await response.json(); setProgress(progressData.Percentage); if (progressData.IsCompleted) { clearInterval(interval); setIsUploading(false); alert('数据处理完成'); // 可选:通知后端清理任务缓存 } }, 500); // 每500ms查询一次,可调整频率 } return () => clearInterval(interval); }, [taskId, isUploading]); // 示例员工数据 const empData = [ { id: 5495, employeeName: "Sarwen", department: "Production", factoryLocation: "Denmark" }, { id: 5496, employeeName: "Kate", department: "HR", factoryLocation: "Denmark" } ]; return ( <div> <button onClick={() => saveEmployees(empData)} disabled={isUploading}> {isUploading ? '处理中...' : '批量提交员工数据'} </button> {isUploading && ( <ProgressBar completed={progress} bgColor="#36a2eb" labelColor="#fff" height="20px" width="100%" /> )} </div> ); }; export default EmployeeUpload;
方案3:使用Server-Sent Events(SSE)实现实时进度推送
如果需要更实时的进度更新,可采用SSE让后端主动向前端推送进度:
后端SSE接口示例:
public async Task GetProcessProgressSSE(string taskId, HttpContext context) { context.Response.Headers.Add("Content-Type", "text/event-stream"); context.Response.Headers.Add("Cache-Control", "no-cache"); context.Response.Headers.Add("Connection", "keep-alive"); while (true) { if (!ProgressCache.Progresses.TryGetValue(taskId, out var progress)) { await context.Response.WriteAsync("event: error\ndata: 任务不存在\n\n"); await context.Response.Body.FlushAsync(); break; } if (progress.IsCompleted) { await context.Response.WriteAsync($"event: complete\ndata: {JsonSerializer.Serialize(progress)}\n\n"); await context.Response.Body.FlushAsync(); break; } // 推送当前进度 await context.Response.WriteAsync($"event: progress\ndata: {JsonSerializer.Serialize(progress)}\n\n"); await context.Response.Body.FlushAsync(); await Task.Delay(500); } }
前端SSE实现:
import { useState } from 'react'; import ProgressBar from '@ramonak/react-progress-bar'; const EmployeeUpload = () => { const [progress, setProgress] = useState(0); const [isUploading, setIsUploading] = useState(false); const usr = '你的用户Token'; const saveEmployees = async (empData) => { setIsUploading(true); setProgress(0); const requestOptions = { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ Token: usr, EmpData: empData }) }; const response = await fetch('http://localhost:5000/api/services/app/Employee/AddEmployees', requestOptions); const taskId = await response.text(); // 建立SSE连接 const eventSource = new EventSource(`http://localhost:5000/api/services/app/Employee/GetProcessProgressSSE?taskId=${taskId}`); eventSource.addEventListener('progress', (e) => { const progressData = JSON.parse(e.data); const percentage = Math.round(((progressData.ProcessedCount / progressData.TotalCount) * 100)); setProgress(percentage); }); eventSource.addEventListener('complete', () => { setIsUploading(false); eventSource.close(); alert('数据处理完成'); }); eventSource.addEventListener('error', () => { eventSource.close(); setIsUploading(false); alert('进度获取失败'); }); }; // 示例员工数据 const empData = [ { id: 5495, employeeName: "Sarwen", department: "Production", factoryLocation: "Denmark" }, { id: 5496, employeeName: "Kate", department: "HR", factoryLocation: "Denmark" } ]; return ( <div> <button onClick={() => saveEmployees(empData)} disabled={isUploading}> {isUploading ? '处理中...' : '批量提交员工数据'} </button> {isUploading && ( <ProgressBar completed={progress} bgColor="#36a2eb" labelColor="#fff" height="20px" width="100%" /> )} </div> ); }; export default EmployeeUpload;
额外说明:
- 方案1适合快速落地,但需考虑单批次失败后的重试逻辑
- 方案2的内存缓存仅适用于单服务器部署,生产环境建议用Redis等分布式缓存
- 方案3的SSE是单向通信,若需要前端控制任务(暂停/取消),可改用WebSocket
内容的提问来源于stack exchange,提问作者weeraa
相关产品推荐
相关产品推荐

