Files
page-agent/pages/test-pages/async-test.tsx
2025-09-29 16:33:15 +08:00

521 lines
16 KiB
TypeScript

import { useState, useEffect } from 'react'
import { Link } from 'wouter'
interface UploadProgress {
id: string
name: string
progress: number
status: 'uploading' | 'completed' | 'error'
speed: string
timeRemaining: string
}
interface DataItem {
id: number
title: string
content: string
timestamp: string
status: 'loading' | 'loaded' | 'error'
}
export default function AsyncTestPage() {
const [uploads, setUploads] = useState<UploadProgress[]>([])
const [dataItems, setDataItems] = useState<DataItem[]>([])
const [isLoadingData, setIsLoadingData] = useState(false)
const [realTimeData, setRealTimeData] = useState<string[]>([])
const [isRealTimeActive, setIsRealTimeActive] = useState(false)
const [longRunningTask, setLongRunningTask] = useState<{
isRunning: boolean
progress: number
currentStep: string
logs: string[]
}>({
isRunning: false,
progress: 0,
currentStep: '',
logs: []
})
// 模拟实时数据更新
useEffect(() => {
let interval: NodeJS.Timeout
if (isRealTimeActive) {
interval = setInterval(() => {
const newData = `数据更新 ${new Date().toLocaleTimeString()}: ${Math.floor(Math.random() * 1000)}`
setRealTimeData(prev => [newData, ...prev.slice(0, 9)]) // 保持最新10条
}, 2000)
}
return () => {
if (interval) clearInterval(interval)
}
}, [isRealTimeActive])
// 模拟文件上传
const simulateFileUpload = (fileName: string) => {
const uploadId = Date.now().toString()
const newUpload: UploadProgress = {
id: uploadId,
name: fileName,
progress: 0,
status: 'uploading',
speed: '0 KB/s',
timeRemaining: '计算中...'
}
setUploads(prev => [...prev, newUpload])
// 模拟上传进度
const interval = setInterval(() => {
setUploads(prev => prev.map(upload => {
if (upload.id === uploadId) {
const newProgress = Math.min(upload.progress + Math.random() * 15, 100)
const speed = `${(Math.random() * 500 + 100).toFixed(0)} KB/s`
const timeRemaining = newProgress >= 100 ? '完成' : `${Math.ceil((100 - newProgress) / 10)}`
// 模拟随机失败
if (newProgress > 50 && Math.random() < 0.1) {
clearInterval(interval)
return {
...upload,
status: 'error' as const,
speed: '0 KB/s',
timeRemaining: '失败'
}
}
if (newProgress >= 100) {
clearInterval(interval)
return {
...upload,
progress: 100,
status: 'completed' as const,
speed,
timeRemaining
}
}
return {
...upload,
progress: newProgress,
speed,
timeRemaining
}
}
return upload
}))
}, 500)
}
// 模拟数据加载
const loadData = async () => {
setIsLoadingData(true)
setDataItems([])
// 创建骨架屏数据
const skeletonItems: DataItem[] = Array.from({ length: 6 }, (_, i) => ({
id: i,
title: '',
content: '',
timestamp: '',
status: 'loading'
}))
setDataItems(skeletonItems)
// 逐个加载数据项
for (let i = 0; i < 6; i++) {
await new Promise(resolve => setTimeout(resolve, 800 + Math.random() * 1000))
setDataItems(prev => prev.map(item => {
if (item.id === i) {
// 模拟随机加载失败
if (Math.random() < 0.15) {
return {
...item,
status: 'error',
title: '加载失败',
content: '数据加载失败,请重试'
}
}
return {
...item,
status: 'loaded',
title: `数据项 ${i + 1}`,
content: `这是第 ${i + 1} 个数据项的内容,包含了一些示例文本用于展示加载效果。`,
timestamp: new Date().toLocaleString()
}
}
return item
}))
}
setIsLoadingData(false)
}
// 模拟长时间运行的任务
const startLongRunningTask = async () => {
setLongRunningTask({
isRunning: true,
progress: 0,
currentStep: '初始化任务...',
logs: ['任务开始']
})
const steps = [
{ name: '初始化任务...', duration: 2000 },
{ name: '连接服务器...', duration: 1500 },
{ name: '验证权限...', duration: 1000 },
{ name: '下载数据...', duration: 3000 },
{ name: '处理数据...', duration: 2500 },
{ name: '生成报告...', duration: 2000 },
{ name: '保存结果...', duration: 1000 },
{ name: '清理资源...', duration: 500 }
]
for (let i = 0; i < steps.length; i++) {
const step = steps[i]
setLongRunningTask(prev => ({
...prev,
currentStep: step.name,
logs: [...prev.logs, `开始: ${step.name}`]
}))
// 模拟步骤执行时间
const startTime = Date.now()
while (Date.now() - startTime < step.duration) {
await new Promise(resolve => setTimeout(resolve, 100))
const elapsed = Date.now() - startTime
const stepProgress = Math.min((elapsed / step.duration) * 100, 100)
const totalProgress = ((i + stepProgress / 100) / steps.length) * 100
setLongRunningTask(prev => ({
...prev,
progress: totalProgress
}))
}
setLongRunningTask(prev => ({
...prev,
logs: [...prev.logs, `完成: ${step.name}`]
}))
// 模拟随机失败
if (i === 3 && Math.random() < 0.2) {
setLongRunningTask(prev => ({
...prev,
isRunning: false,
currentStep: '任务失败',
logs: [...prev.logs, '错误: 数据下载失败,请重试']
}))
return
}
}
setLongRunningTask(prev => ({
...prev,
isRunning: false,
progress: 100,
currentStep: '任务完成',
logs: [...prev.logs, '任务成功完成!']
}))
}
const clearUploads = () => {
setUploads([])
}
const retryFailedUpload = (uploadId: string) => {
const failedUpload = uploads.find(u => u.id === uploadId)
if (failedUpload) {
setUploads(prev => prev.filter(u => u.id !== uploadId))
simulateFileUpload(failedUpload.name)
}
}
const retryDataLoad = (itemId: number) => {
setDataItems(prev => prev.map(item => {
if (item.id === itemId) {
return { ...item, status: 'loading', title: '', content: '', timestamp: '' }
}
return item
}))
setTimeout(() => {
setDataItems(prev => prev.map(item => {
if (item.id === itemId) {
return {
...item,
status: 'loaded',
title: `数据项 ${itemId + 1}`,
content: `这是重新加载的第 ${itemId + 1} 个数据项的内容。`,
timestamp: new Date().toLocaleString()
}
}
return item
}))
}, 1000)
}
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-6xl mx-auto px-4">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
</h1>
<p className="text-gray-600 dark:text-gray-300">
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* 文件上传进度 */}
<div className="space-y-6">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
</h2>
<div className="space-x-2">
<button
type="button"
onClick={() => simulateFileUpload(`文件_${Date.now()}.pdf`)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors text-sm"
>
</button>
<button
type="button"
onClick={clearUploads}
className="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-md transition-colors text-sm"
>
</button>
</div>
</div>
<div className="space-y-4">
{uploads.length === 0 ? (
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
"开始上传"
</div>
) : (
uploads.map(upload => (
<div key={upload.id} className="border border-gray-200 dark:border-gray-600 rounded-lg p-4">
<div className="flex justify-between items-center mb-2">
<span className="font-medium text-gray-900 dark:text-white">
{upload.name}
</span>
<span className={`text-sm ${
upload.status === 'completed' ? 'text-green-600 dark:text-green-400' :
upload.status === 'error' ? 'text-red-600 dark:text-red-400' :
'text-blue-600 dark:text-blue-400'
}`}>
{upload.status === 'completed' ? '✓ 完成' :
upload.status === 'error' ? '✗ 失败' :
'上传中...'}
</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mb-2">
<div
className={`h-2 rounded-full transition-all duration-300 ${
upload.status === 'completed' ? 'bg-green-500' :
upload.status === 'error' ? 'bg-red-500' :
'bg-blue-500'
}`}
style={{ width: `${upload.progress}%` }}
/>
</div>
<div className="flex justify-between text-sm text-gray-600 dark:text-gray-300">
<span>{upload.progress.toFixed(1)}%</span>
<span>{upload.speed}</span>
<span>{upload.timeRemaining}</span>
</div>
{upload.status === 'error' && (
<button
type="button"
onClick={() => retryFailedUpload(upload.id)}
className="mt-2 px-3 py-1 bg-red-600 hover:bg-red-700 text-white rounded text-sm transition-colors"
>
</button>
)}
</div>
))
)}
</div>
</div>
{/* 实时数据更新 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
</h2>
<button
type="button"
onClick={() => setIsRealTimeActive(!isRealTimeActive)}
className={`px-4 py-2 rounded-md transition-colors text-sm ${
isRealTimeActive
? 'bg-red-600 hover:bg-red-700 text-white'
: 'bg-green-600 hover:bg-green-700 text-white'
}`}
>
{isRealTimeActive ? '停止更新' : '开始更新'}
</button>
</div>
<div className="space-y-2 max-h-64 overflow-y-auto">
{realTimeData.length === 0 ? (
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
"开始更新"
</div>
) : (
realTimeData.map((data, index) => (
<div
key={index}
className={`p-3 rounded-lg border transition-all duration-300 ${
index === 0
? 'bg-blue-50 dark:bg-blue-900 border-blue-200 dark:border-blue-700'
: 'bg-gray-50 dark:bg-gray-700 border-gray-200 dark:border-gray-600'
}`}
>
<span className="text-sm text-gray-900 dark:text-white">
{data}
</span>
</div>
))
)}
</div>
</div>
</div>
{/* 数据加载和长时间任务 */}
<div className="space-y-6">
{/* 数据加载骨架屏 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
</h2>
<button
type="button"
onClick={loadData}
disabled={isLoadingData}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md transition-colors text-sm"
>
{isLoadingData ? '加载中...' : '加载数据'}
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{dataItems.map(item => (
<div key={item.id} className="border border-gray-200 dark:border-gray-600 rounded-lg p-4">
{item.status === 'loading' ? (
<div className="animate-pulse">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-3/4 mb-2"></div>
<div className="h-3 bg-gray-300 dark:bg-gray-600 rounded w-full mb-1"></div>
<div className="h-3 bg-gray-300 dark:bg-gray-600 rounded w-2/3"></div>
</div>
) : item.status === 'error' ? (
<div>
<h3 className="font-medium text-red-600 dark:text-red-400 mb-2">
{item.title}
</h3>
<p className="text-sm text-red-500 dark:text-red-400 mb-2">
{item.content}
</p>
<button
type="button"
onClick={() => retryDataLoad(item.id)}
className="px-3 py-1 bg-red-600 hover:bg-red-700 text-white rounded text-sm transition-colors"
>
</button>
</div>
) : (
<div>
<h3 className="font-medium text-gray-900 dark:text-white mb-2">
{item.title}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-300 mb-2">
{item.content}
</p>
<span className="text-xs text-gray-500 dark:text-gray-400">
{item.timestamp}
</span>
</div>
)}
</div>
))}
</div>
</div>
{/* 长时间运行任务 */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
</h2>
<button
type="button"
onClick={startLongRunningTask}
disabled={longRunningTask.isRunning}
className="px-4 py-2 bg-purple-600 hover:bg-purple-700 disabled:bg-purple-400 text-white rounded-md transition-colors text-sm"
>
{longRunningTask.isRunning ? '执行中...' : '开始任务'}
</button>
</div>
{longRunningTask.progress > 0 && (
<div className="mb-4">
<div className="flex justify-between items-center mb-2">
<span className="text-sm font-medium text-gray-900 dark:text-white">
{longRunningTask.currentStep}
</span>
<span className="text-sm text-gray-600 dark:text-gray-300">
{longRunningTask.progress.toFixed(1)}%
</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<div
className="bg-purple-500 h-2 rounded-full transition-all duration-300"
style={{ width: `${longRunningTask.progress}%` }}
/>
</div>
</div>
)}
{longRunningTask.logs.length > 0 && (
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 max-h-48 overflow-y-auto">
<h4 className="text-sm font-medium text-gray-900 dark:text-white mb-2">
:
</h4>
<div className="space-y-1">
{longRunningTask.logs.map((log, index) => (
<div key={index} className="text-sm text-gray-600 dark:text-gray-300 font-mono">
{log}
</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
{/* 返回链接 */}
<div className="mt-12 pt-8 border-t border-gray-200 dark:border-gray-700">
<Link href="/" className="text-blue-600 hover:text-blue-500 dark:text-blue-400">
</Link>
</div>
</div>
</div>
)
}