Files
bw-mini-app/src/pages/history/index.tsx
iHeyTang 64df42678f refactor: 移除下载部分组件及样式,优化项目结构
- 删除DownloadSection组件及其相关CSS,简化代码结构
- 在历史页面中添加生成中任务的进度计算和显示功能
- 更新历史页面样式,增强用户体验和视觉效果
- 添加缩略图毛玻璃蒙版和进度指示器,提升信息传达的清晰度
2025-09-11 15:24:35 +08:00

194 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Image, ScrollView, Text, View } from '@tarojs/components';
import Taro, { navigateTo } from '@tarojs/taro';
import { useEffect, useState } from 'react';
import { useServerSdk } from '../../hooks/index';
import './index.css';
export default function History() {
const [records, setRecords] = useState<any[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [loading, setLoading] = useState(true);
const serverSdk = useServerSdk();
// 计算生成中任务的进度
const calculateProgress = (record: any) => {
if (!record?.createdAt || record.status !== 'processing') return 0;
const createdAt = new Date(record.createdAt).getTime();
const now = Date.now();
const elapsedMinutes = (now - createdAt) / (1000 * 60);
// 根据任务类型确定预计时间
const isVideo = record.taskType === 'video' || record.outputType === 'video' || record.type === 'video';
const estimatedTime = isVideo ? 2.5 : 1; // 视频2.5分钟图片1分钟
// 计算进度百分比最大95%
const calculatedProgress = Math.min((elapsedMinutes / estimatedTime) * 100, 95);
return Math.max(calculatedProgress, 0);
};
const loadRecords = async () => {
try {
const logs = await serverSdk.getMineLogs();
setRecords(logs || []);
} catch (error) {
console.error('加载记录失败:', error);
Taro.showToast({
title: '加载记录失败',
icon: 'error',
duration: 2000,
});
} finally {
setLoading(false);
}
};
useEffect(() => {
loadRecords();
}, []);
// 定时更新生成中任务的进度
useEffect(() => {
const hasGeneratingTasks = records.some(record => record.status === 'processing');
if (!hasGeneratingTasks) return;
const interval = setInterval(() => {
// 触发重新渲染以更新进度
setRecords(prevRecords => [...prevRecords]);
}, 1000);
return () => clearInterval(interval);
}, [records]);
// 下拉刷新
const handleRefresh = async () => {
setRefreshing(true);
try {
await loadRecords();
Taro.showToast({
title: '刷新成功',
icon: 'success',
duration: 1500,
});
} catch (error) {
console.error('刷新失败:', error);
Taro.showToast({
title: '刷新失败',
icon: 'error',
duration: 1500,
});
} finally {
setRefreshing(false);
}
};
// 点击历史记录项
const handleItemClick = (record: any) => {
if (record.status === 'completed' && record.outputUrl) {
// 跳转到结果页面查看
navigateTo({
url: `/pages/result/index?taskId=${record.executionId}`,
});
} else if (record.status === 'failed') {
// 显示错误信息
Taro.showModal({
title: '处理失败',
content: record.errorMessage || '图片处理失败,请重试',
showCancel: false,
});
} else if (record.status === 'processing') {
// 跳转到生成页面查看进度
navigateTo({
url: `/pages/result/index?taskId=${record.executionId}`,
});
}
};
const formatTime = (timeStr: string) => {
const date = new Date(timeStr);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
return (
<View className="history">
<ScrollView
className="history-list"
scrollY
refresherEnabled
refresherTriggered={refreshing}
onRefresherRefresh={handleRefresh}
refresherBackground="transparent"
refresherDefaultStyle="white"
>
<View className="history-grid">
{loading ? (
<View className="loading-state">
<Text className="loading-text">...</Text>
</View>
) : records.length === 0 ? (
<View className="empty-state">
<Text className="empty-icon"></Text>
<Text className="empty-text"></Text>
<Text className="empty-desc">{'\n'}</Text>
</View>
) : (
records.map(record => {
const progress = calculateProgress(record);
const isGenerating = record.status === 'processing';
return (
<View key={record.id} className="history-item" onClick={() => handleItemClick(record)}>
<View className="item-thumbnail">
{record.status === 'completed' && record.outputResult ? (
<Image className="thumbnail-image" src={record.inputImageUrl} mode="aspectFill" />
) : (
<View className={`thumbnail-placeholder ${record.status}`}>
<Image className="thumbnail-image" src={record.inputImageUrl} mode="aspectFill" />
{/* 生成中状态的缩略图毛玻璃蒙版 */}
{isGenerating && (
<View className="thumbnail-overlay">
<View className="thumbnail-glassmorphism" />
<View className="thumbnail-progress">
<View className="thumbnail-progress-circle">
<View
className="thumbnail-progress-fill"
style={{
background: `conic-gradient(#ff9500 ${progress * 3.6}deg, transparent 0deg)`,
}}
/>
<View className="thumbnail-progress-inner">
<Text className="thumbnail-progress-text">{Math.round(progress)}%</Text>
</View>
</View>
</View>
</View>
)}
</View>
)}
</View>
<View className="item-content">
<View className="item-header">
<Text className="item-title">{record.templateName}</Text>
</View>
<View className="item-info">
<Text className="item-time">{formatTime(record.createdAt)}</Text>
</View>
</View>
</View>
);
})
)}
</View>
</ScrollView>
</View>
);
}