Skip to content

Commit b73495e

Browse files
committed
refactor(screen): 实现AI选股页面状态管理与界面中文化优化
- 引入了screenStore用于选股页面状态集中管理,支持跨路由保留输入和结果状态 - 优化选股结果表的列排序,支持数字和字符串混合排序,默认按涨跌幅降序排序 - 实现前端分页,分页大小固定20条,添加分页组件支持页码切换 - 解析并格式化列名中的时间戳,统一到页面meta区域显示数据时间 - 调整列展示顺序,隐藏内部ID列,交换流通市值和证券类型列位置 - 替换BacktestPage中所有相关文本为中文,提升界面本地化体验 - 更新测试用例中对应文本,确保测试覆盖中文化后的界面元素 - 后台调用和错误逻辑保持不变,增强用户体验和易用性
1 parent 32ff14f commit b73495e

5 files changed

Lines changed: 387 additions & 121 deletions

File tree

apps/dsa-web/src/pages/BacktestPage.tsx

Lines changed: 55 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ function outcomeBadge(outcome?: string) {
2727
if (!outcome) return <Badge variant="default">--</Badge>;
2828
switch (outcome) {
2929
case 'win':
30-
return <Badge variant="success" glow>WIN</Badge>;
30+
return <Badge variant="success" glow>盈利</Badge>;
3131
case 'loss':
32-
return <Badge variant="danger" glow>LOSS</Badge>;
32+
return <Badge variant="danger" glow>亏损</Badge>;
3333
case 'neutral':
34-
return <Badge variant="warning">NEUTRAL</Badge>;
34+
return <Badge variant="warning">持平</Badge>;
3535
default:
3636
return <Badge variant="default">{outcome}</Badge>;
3737
}
@@ -40,12 +40,12 @@ function outcomeBadge(outcome?: string) {
4040
function statusBadge(status: string) {
4141
switch (status) {
4242
case 'completed':
43-
return <Badge variant="success">completed</Badge>;
43+
return <Badge variant="success">已完成</Badge>;
4444
case 'insufficient':
4545
case 'insufficient_data':
46-
return <Badge variant="warning">insufficient</Badge>;
46+
return <Badge variant="warning">数据不足</Badge>;
4747
case 'error':
48-
return <Badge variant="danger">error</Badge>;
48+
return <Badge variant="danger">错误</Badge>;
4949
default:
5050
return <Badge variant="default">{status}</Badge>;
5151
}
@@ -54,11 +54,11 @@ function statusBadge(status: string) {
5454
function actualMovementBadge(movement?: string | null) {
5555
switch (movement) {
5656
case 'up':
57-
return <Badge variant="success">UP</Badge>;
57+
return <Badge variant="success">上涨</Badge>;
5858
case 'down':
59-
return <Badge variant="danger">DOWN</Badge>;
59+
return <Badge variant="danger">下跌</Badge>;
6060
case 'flat':
61-
return <Badge variant="warning">FLAT</Badge>;
61+
return <Badge variant="warning">横盘</Badge>;
6262
default:
6363
return <Badge variant="default">--</Badge>;
6464
}
@@ -116,15 +116,15 @@ const PerformanceCard: React.FC<{ metrics: PerformanceMetrics; title: string }>
116116
<div className="mb-3">
117117
<span className="label-uppercase">{title}</span>
118118
</div>
119-
<MetricRow label="Direction Accuracy" value={pct(metrics.directionAccuracyPct)} accent />
120-
<MetricRow label="Win Rate" value={pct(metrics.winRatePct)} accent />
121-
<MetricRow label="Avg Sim. Return" value={pct(metrics.avgSimulatedReturnPct)} />
122-
<MetricRow label="Avg Stock Return" value={pct(metrics.avgStockReturnPct)} />
123-
<MetricRow label="SL Trigger Rate" value={pct(metrics.stopLossTriggerRate)} />
124-
<MetricRow label="TP Trigger Rate" value={pct(metrics.takeProfitTriggerRate)} />
125-
<MetricRow label="Avg Days to Hit" value={metrics.avgDaysToFirstHit != null ? metrics.avgDaysToFirstHit.toFixed(1) : '--'} />
119+
<MetricRow label="方向准确率" value={pct(metrics.directionAccuracyPct)} accent />
120+
<MetricRow label="胜率" value={pct(metrics.winRatePct)} accent />
121+
<MetricRow label="平均模拟收益" value={pct(metrics.avgSimulatedReturnPct)} />
122+
<MetricRow label="平均股价收益" value={pct(metrics.avgStockReturnPct)} />
123+
<MetricRow label="止损触发率" value={pct(metrics.stopLossTriggerRate)} />
124+
<MetricRow label="止盈触发率" value={pct(metrics.takeProfitTriggerRate)} />
125+
<MetricRow label="平均触发天数" value={metrics.avgDaysToFirstHit != null ? metrics.avgDaysToFirstHit.toFixed(1) : '--'} />
126126
<div className="backtest-metric-footer">
127-
<span className="text-xs text-muted-text">Evaluations</span>
127+
<span className="text-xs text-muted-text">评估数</span>
128128
<span className="text-xs text-secondary-text font-mono">
129129
{Number(metrics.completedCount)} / {Number(metrics.totalEvaluations)}
130130
</span>
@@ -146,12 +146,12 @@ const PerformanceCard: React.FC<{ metrics: PerformanceMetrics; title: string }>
146146

147147
const RunSummary: React.FC<{ data: BacktestRunResponse }> = ({ data }) => (
148148
<div className="backtest-summary animate-fade-in">
149-
<span className="label">Processed: <span className="value">{data.processed}</span></span>
150-
<span className="label">Saved: <span className="value primary">{data.saved}</span></span>
151-
<span className="label">Completed: <span className="value success">{data.completed}</span></span>
152-
<span className="label">Insufficient: <span className="value warning">{data.insufficient}</span></span>
149+
<span className="label">已处理:<span className="value">{data.processed}</span></span>
150+
<span className="label">已保存:<span className="value primary">{data.saved}</span></span>
151+
<span className="label">已完成:<span className="value success">{data.completed}</span></span>
152+
<span className="label">数据不足:<span className="value warning">{data.insufficient}</span></span>
153153
{data.errors > 0 && (
154-
<span className="label">Errors: <span className="value danger">{data.errors}</span></span>
154+
<span className="label">错误:<span className="value danger">{data.errors}</span></span>
155155
)}
156156
</div>
157157
);
@@ -337,7 +337,7 @@ const BacktestPage: React.FC = () => {
337337
value={codeFilter}
338338
onChange={(e) => setCodeFilter(e.target.value.toUpperCase())}
339339
onKeyDown={handleKeyDown}
340-
placeholder="Filter by stock code (leave empty for all)"
340+
placeholder="按股票代码筛选(留空查全部)"
341341
disabled={isRunning}
342342
className={BACKTEST_INPUT_CLASS}
343343
/>
@@ -348,10 +348,10 @@ const BacktestPage: React.FC = () => {
348348
disabled={isLoadingResults}
349349
className="btn-secondary flex items-center gap-1.5 whitespace-nowrap"
350350
>
351-
Filter
351+
筛选
352352
</button>
353353
<div className="flex items-center gap-2 whitespace-nowrap lg:w-40 lg:justify-between">
354-
<span className="text-xs text-muted-text">Window</span>
354+
<span className="text-xs text-muted-text">窗口</span>
355355
<input
356356
type="number"
357357
min={1}
@@ -364,10 +364,10 @@ const BacktestPage: React.FC = () => {
364364
/>
365365
</div>
366366
<div className="flex items-center gap-2 whitespace-nowrap">
367-
<span className="text-xs text-muted-text">From</span>
367+
<span className="text-xs text-muted-text">起始</span>
368368
<input
369369
type="date"
370-
aria-label="Analysis date from"
370+
aria-label="分析起始日"
371371
value={analysisDateFrom}
372372
onChange={(e) => setAnalysisDateFrom(e.target.value)}
373373
onKeyDown={handleKeyDown}
@@ -376,10 +376,10 @@ const BacktestPage: React.FC = () => {
376376
/>
377377
</div>
378378
<div className="flex items-center gap-2 whitespace-nowrap">
379-
<span className="text-xs text-muted-text">To</span>
379+
<span className="text-xs text-muted-text">截止</span>
380380
<input
381381
type="date"
382-
aria-label="Analysis date to"
382+
aria-label="分析截止日"
383383
value={analysisDateTo}
384384
onChange={(e) => setAnalysisDateTo(e.target.value)}
385385
onKeyDown={handleKeyDown}
@@ -394,7 +394,7 @@ const BacktestPage: React.FC = () => {
394394
className={`backtest-force-btn ${isNextDayValidation ? 'active' : ''}`}
395395
>
396396
<span className="dot" />
397-
1D Validation
397+
次日验证
398398
</button>
399399
<button
400400
type="button"
@@ -403,7 +403,7 @@ const BacktestPage: React.FC = () => {
403403
className={`backtest-force-btn ${forceRerun ? 'active' : ''}`}
404404
>
405405
<span className="dot" />
406-
Force
406+
强制重跑
407407
</button>
408408
<button
409409
type="button"
@@ -417,10 +417,10 @@ const BacktestPage: React.FC = () => {
417417
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
418418
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
419419
</svg>
420-
Running...
420+
运行中...
421421
</>
422422
) : (
423-
'Run Backtest'
423+
'运行回测'
424424
)}
425425
</button>
426426
</div>
@@ -434,8 +434,8 @@ const BacktestPage: React.FC = () => {
434434
)}
435435
<p className="mt-2 text-xs text-muted-text">
436436
{isNextDayValidation
437-
? 'Next-day validation mode compares AI predictions with the next trading day close.'
438-
: 'Use window = 1 to review AI predictions against the next trading day close.'}
437+
? '次日验证模式:将 AI 预测与下一交易日收盘价对比。'
438+
: '将窗口设为 1 可对照次日收盘价复核 AI 预测。'}
439439
</p>
440440
</header>
441441

@@ -448,11 +448,11 @@ const BacktestPage: React.FC = () => {
448448
<div className="backtest-spinner sm" />
449449
</div>
450450
) : overallPerf ? (
451-
<PerformanceCard metrics={overallPerf} title="Overall Performance" />
451+
<PerformanceCard metrics={overallPerf} title="整体表现" />
452452
) : (
453453
<EmptyState
454-
title="No Metrics Yet"
455-
description="Run a backtest to generate portfolio-level performance metrics."
454+
title="暂无指标"
455+
description="运行回测以生成组合级表现指标。"
456456
className="h-full min-h-[12rem] border-dashed bg-card/45 shadow-none"
457457
/>
458458
)}
@@ -470,12 +470,12 @@ const BacktestPage: React.FC = () => {
470470
{isLoadingResults ? (
471471
<div className="flex flex-col items-center justify-center h-64">
472472
<div className="backtest-spinner md" />
473-
<p className="mt-3 text-secondary-text text-sm">Loading results...</p>
473+
<p className="mt-3 text-secondary-text text-sm">加载结果中...</p>
474474
</div>
475475
) : results.length === 0 ? (
476476
<EmptyState
477-
title="No Results"
478-
description="Run a backtest to evaluate historical analysis accuracy"
477+
title="暂无结果"
478+
description="运行回测以评估历史分析准确度"
479479
className="backtest-empty-state border-dashed"
480480
icon={(
481481
<svg className="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -487,31 +487,31 @@ const BacktestPage: React.FC = () => {
487487
<div className="animate-fade-in">
488488
<div className="backtest-table-toolbar">
489489
<div className="backtest-table-toolbar-meta">
490-
<span className="label-uppercase">{isNextDayValidation ? 'Next-Day Validation' : 'Result Set'}</span>
490+
<span className="label-uppercase">{isNextDayValidation ? '次日验证' : '结果集'}</span>
491491
<span className="text-xs text-secondary-text">
492-
{codeFilter.trim() ? `Filtered by ${codeFilter.trim()}` : 'All stocks'}
493-
{evalDays ? ` · ${evalDays} day window` : ''}
494-
{analysisDateFrom ? ` · from ${analysisDateFrom}` : ''}
495-
{analysisDateTo ? ` · to ${analysisDateTo}` : ''}
492+
{codeFilter.trim() ? `已按 ${codeFilter.trim()} 筛选` : '全部股票'}
493+
{evalDays ? ` · ${evalDays} 日窗口` : ''}
494+
{analysisDateFrom ? ` · ${analysisDateFrom}` : ''}
495+
{analysisDateTo ? ` · ${analysisDateTo}` : ''}
496496
</span>
497497
</div>
498-
<span className="backtest-table-scroll-hint">Scroll horizontally on small screens</span>
498+
<span className="backtest-table-scroll-hint">小屏幕请左右滚动</span>
499499
</div>
500500
<div className="backtest-table-wrapper">
501501
<table className="backtest-table min-w-[840px] w-full text-sm">
502502
<thead className="backtest-table-head">
503503
<tr className="text-left">
504-
<th className="backtest-table-head-cell">Stock</th>
505-
<th className="backtest-table-head-cell">Analysis Date</th>
506-
<th className="backtest-table-head-cell">AI Prediction</th>
504+
<th className="backtest-table-head-cell">股票</th>
505+
<th className="backtest-table-head-cell">分析日期</th>
506+
<th className="backtest-table-head-cell">AI 预测</th>
507507
<th className="backtest-table-head-cell">
508-
{showNextDayActualColumns ? 'Actual' : 'Window Return'}
508+
{showNextDayActualColumns ? '实际' : '区间收益'}
509509
</th>
510510
<th className="backtest-table-head-cell">
511-
{showNextDayActualColumns ? 'Accuracy' : 'Direction Match'}
511+
{showNextDayActualColumns ? '准确度' : '方向匹配'}
512512
</th>
513-
<th className="backtest-table-head-cell">Outcome</th>
514-
<th className="backtest-table-head-cell">Status</th>
513+
<th className="backtest-table-head-cell">结果</th>
514+
<th className="backtest-table-head-cell">状态</th>
515515
</tr>
516516
</thead>
517517
<tbody>
@@ -578,7 +578,7 @@ const BacktestPage: React.FC = () => {
578578
</div>
579579

580580
<p className="text-xs text-muted-text text-center mt-2">
581-
{totalResults} result{totalResults !== 1 ? 's' : ''} total · page {currentPage} of {Math.max(totalPages, 1)}
581+
{totalResults} · {currentPage} / {Math.max(totalPages, 1)}
582582
</p>
583583
</div>
584584
)}

0 commit comments

Comments
 (0)