|
|
|
@ -259,18 +259,12 @@ export default function DataCheck() {
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 计算统计数据(优先使用后端返回的 summary 数据)
|
|
|
|
// 计算统计数据(优先使用后端返回的 summary 数据)
|
|
|
|
const completionRate = summary?.completenessRate ?? (dataStatus.length > 0
|
|
|
|
// 新的 summary 格式: { stockCount, quoteCount, klineCount, localLatestDate, externalLatestDate, externalStatus, daysBehind, syncStatus }
|
|
|
|
? Math.round((dataStatus.filter(d => d.status === 'complete').length / dataStatus.length) * 100)
|
|
|
|
const completionRate = summary?.syncStatus === 'complete' ? 100 : summary?.syncStatus === 'incomplete' ? 70 : 0;
|
|
|
|
: 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const totalMissing = summary?.missingQuotesCount ?? dataStatus.reduce((acc, item) => {
|
|
|
|
const totalMissing = summary?.daysBehind ?? 0;
|
|
|
|
if (item.status !== 'complete') {
|
|
|
|
|
|
|
|
return acc + (item.total - item.current);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const estimatedTime = Math.ceil(totalMissing / 1000); // 估算时间(分钟)
|
|
|
|
const estimatedTime = Math.ceil(totalMissing * 1000); // 估算时间(分钟)
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<div className="space-y-6">
|
|
|
|
<div className="space-y-6">
|
|
|
|
@ -336,7 +330,7 @@ export default function DataCheck() {
|
|
|
|
<p className="text-sm text-[#666] mt-1">
|
|
|
|
<p className="text-sm text-[#666] mt-1">
|
|
|
|
{currentTask && (
|
|
|
|
{currentTask && (
|
|
|
|
<>
|
|
|
|
<>
|
|
|
|
已处理: {currentTask.processedRecords.toLocaleString()} / {currentTask.totalRecords.toLocaleString()}
|
|
|
|
已处理: {(currentTask.processedRecords || 0).toLocaleString()} / {(currentTask.totalRecords || 0).toLocaleString()}
|
|
|
|
</>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
</p>
|
|
|
|
</p>
|
|
|
|
@ -473,15 +467,17 @@ export default function DataCheck() {
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<p className="text-[#b0b0b0] text-sm">数据完整度</p>
|
|
|
|
<p className="text-[#b0b0b0] text-sm">数据同步状态</p>
|
|
|
|
<p className="text-2xl font-bold text-white mt-1">{summary.completenessRate}%</p>
|
|
|
|
<p className="text-2xl font-bold text-white mt-1">
|
|
|
|
|
|
|
|
{summary.syncStatus === 'complete' ? '已同步' : summary.syncStatus === 'incomplete' ? '部分同步' : '未同步'}
|
|
|
|
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center">
|
|
|
|
<div className="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center">
|
|
|
|
<CheckCircle className="w-6 h-6 text-green-400" />
|
|
|
|
<CheckCircle className="w-6 h-6 text-green-400" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="mt-3 h-2 bg-[#2a2a2a] rounded-full overflow-hidden">
|
|
|
|
<div className="mt-3 h-2 bg-[#2a2a2a] rounded-full overflow-hidden">
|
|
|
|
<div className="h-full bg-green-500 rounded-full transition-all" style={{ width: `${summary.completenessRate}%` }} />
|
|
|
|
<div className="h-full bg-green-500 rounded-full transition-all" style={{ width: `${completionRate}%` }} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</motion.div>
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
|
|
@ -493,15 +489,15 @@ export default function DataCheck() {
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<p className="text-[#b0b0b0] text-sm">缺失数据条数</p>
|
|
|
|
<p className="text-[#b0b0b0] text-sm">落后天数</p>
|
|
|
|
<p className="text-2xl font-bold text-white mt-1">{summary.missingQuotesCount.toLocaleString()}</p>
|
|
|
|
<p className="text-2xl font-bold text-white mt-1">{summary.daysBehind ?? '-'} 天</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="w-12 h-12 bg-red-500/20 rounded-xl flex items-center justify-center">
|
|
|
|
<div className="w-12 h-12 bg-red-500/20 rounded-xl flex items-center justify-center">
|
|
|
|
<AlertCircle className="w-6 h-6 text-red-400" />
|
|
|
|
<AlertCircle className="w-6 h-6 text-red-400" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<p className="text-sm text-[#666] mt-3">
|
|
|
|
<p className="text-sm text-[#666] mt-3">
|
|
|
|
{summary.missingQuotesCount > 0 ? '建议执行一键缓冲' : '数据完整无需缓冲'}
|
|
|
|
{summary.daysBehind && summary.daysBehind > 0 ? '建议执行一键缓冲' : '数据已同步'}
|
|
|
|
</p>
|
|
|
|
</p>
|
|
|
|
</motion.div>
|
|
|
|
</motion.div>
|
|
|
|
|
|
|
|
|
|
|
|
@ -513,15 +509,17 @@ export default function DataCheck() {
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<p className="text-[#b0b0b0] text-sm">一年内可交易日</p>
|
|
|
|
<p className="text-[#b0b0b0] text-sm">数据源状态</p>
|
|
|
|
<p className="text-2xl font-bold text-white mt-1">{summary.tradingDaysCount} 天</p>
|
|
|
|
<p className="text-2xl font-bold text-white mt-1">
|
|
|
|
|
|
|
|
{summary.externalStatus === 'connected' ? '已连接' : summary.externalStatus === 'disabled' ? '未配置' : '异常'}
|
|
|
|
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center">
|
|
|
|
<div className="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center">
|
|
|
|
<Calendar className="w-6 h-6 text-blue-400" />
|
|
|
|
<Calendar className="w-6 h-6 text-blue-400" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<p className="text-sm text-[#666] mt-3">
|
|
|
|
<p className="text-sm text-[#666] mt-3">
|
|
|
|
参考日期: {summary.referenceDate}, 当日股票数: {summary.dailyStockCount}只
|
|
|
|
本地: {summary.localLatestDate || '无'}, 数据源: {summary.externalLatestDate || '无'}
|
|
|
|
</p>
|
|
|
|
</p>
|
|
|
|
</motion.div>
|
|
|
|
</motion.div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@ -537,28 +535,26 @@ export default function DataCheck() {
|
|
|
|
<h3 className="text-white font-semibold mb-4">数据统计详情</h3>
|
|
|
|
<h3 className="text-white font-semibold mb-4">数据统计详情</h3>
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
|
|
<div className="p-3 bg-[#0a0a0a] rounded-lg">
|
|
|
|
<div className="p-3 bg-[#0a0a0a] rounded-lg">
|
|
|
|
<p className="text-[#666] text-xs">预期数据条数</p>
|
|
|
|
<p className="text-[#666] text-xs">股票数量</p>
|
|
|
|
<p className="text-white text-lg font-medium">{summary.expectedTotalQuotes.toLocaleString()}</p>
|
|
|
|
<p className="text-white text-lg font-medium">{(summary.stockCount || 0).toLocaleString()}</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="p-3 bg-[#0a0a0a] rounded-lg">
|
|
|
|
<div className="p-3 bg-[#0a0a0a] rounded-lg">
|
|
|
|
<p className="text-[#666] text-xs">实际数据条数</p>
|
|
|
|
<p className="text-[#666] text-xs">行情记录数</p>
|
|
|
|
<p className="text-white text-lg font-medium">{summary.actualTotalQuotes.toLocaleString()}</p>
|
|
|
|
<p className="text-white text-lg font-medium">{(summary.quoteCount || 0).toLocaleString()}</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="p-3 bg-[#0a0a0a] rounded-lg">
|
|
|
|
<div className="p-3 bg-[#0a0a0a] rounded-lg">
|
|
|
|
<p className="text-[#666] text-xs">缺失数据条数</p>
|
|
|
|
<p className="text-[#666] text-xs">落后天数</p>
|
|
|
|
<p className={`text-lg font-medium ${summary.missingQuotesCount > 0 ? 'text-red-400' : 'text-green-400'}`}>
|
|
|
|
<p className={`text-lg font-medium ${(summary.daysBehind || 0) > 0 ? 'text-red-400' : 'text-green-400'}`}>
|
|
|
|
{summary.missingQuotesCount.toLocaleString()}
|
|
|
|
{summary.daysBehind ?? '-'} 天
|
|
|
|
</p>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="p-3 bg-[#0a0a0a] rounded-lg">
|
|
|
|
<div className="p-3 bg-[#0a0a0a] rounded-lg">
|
|
|
|
<p className="text-[#666] text-xs">数据完整度</p>
|
|
|
|
<p className="text-[#666] text-xs">K线数据</p>
|
|
|
|
<p className={`text-lg font-medium ${summary.completenessRate >= 90 ? 'text-green-400' : summary.completenessRate >= 50 ? 'text-yellow-400' : 'text-red-400'}`}>
|
|
|
|
<p className="text-white text-lg font-medium">{(summary.klineCount || 0).toLocaleString()}</p>
|
|
|
|
{summary.completenessRate}%
|
|
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<p className="text-xs text-[#666] mt-3">
|
|
|
|
<p className="text-xs text-[#666] mt-3">
|
|
|
|
* 计算公式: 预期数据 = 单日股票数({summary.dailyStockCount}只) × 可交易日({summary.tradingDaysCount}天) = {summary.expectedTotalQuotes.toLocaleString()}条
|
|
|
|
* 本地最新数据: {summary.localLatestDate || '无'}, 数据源最新: {summary.externalLatestDate || '无'}
|
|
|
|
</p>
|
|
|
|
</p>
|
|
|
|
</motion.div>
|
|
|
|
</motion.div>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
@ -635,11 +631,11 @@ export default function DataCheck() {
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<span className="text-sm text-[#b0b0b0]">
|
|
|
|
<span className="text-sm text-[#b0b0b0]">
|
|
|
|
{((item.current / item.total) * 100).toFixed(1)}%
|
|
|
|
{item.total > 0 ? ((item.current / item.total) * 100).toFixed(1) : '0.0'}%
|
|
|
|
</span>
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<p className="text-xs text-[#666] mt-1">
|
|
|
|
<p className="text-xs text-[#666] mt-1">
|
|
|
|
{item.current.toLocaleString()} / {item.total.toLocaleString()}
|
|
|
|
{(item.current || 0).toLocaleString()} / {(item.total || 0).toLocaleString()}
|
|
|
|
</p>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{item.details && (
|
|
|
|
{item.details && (
|
|
|
|
|