44 import { getApi } from ' $lib/netio' ;
55 import { alerts } from " $lib/stores/alerts" ;
66 import CodeMirror from ' $lib/dev/CodeMirror.svelte' ;
7+ import Icon from ' $lib/Icon.svelte' ;
78 import { oneDark } from ' @codemirror/theme-one-dark' ;
89 import type { Extension } from ' @codemirror/state' ;
910 import * as m from ' $lib/paraglide/messages.js' ;
1314 import type { BacktestDetail , BackTestTask , ExSymbol } from ' $lib/dev/common' ;
1415 import { InOutOrderDetail , type InOutOrder } from ' $lib/order' ;
1516 import { TreeView , type Tree , type Node , buildTree } from ' $lib/treeview' ;
16- import { writable } from ' svelte/store' ;
17+ import { writable } from ' svelte/store' ;
1718 import RangeSlider from ' $lib/dev/RangeSlider.svelte' ;
1819 import { ChartCtx , ChartSave } from ' $lib/kline/chart' ;
1920 import { persisted } from ' svelte-persisted-store' ;
2324 import type { OverlayCreate } from ' klinecharts' ;
2425 import type { TradeInfo } from ' $lib/kline/types' ;
2526 import { pagination , orderCard } from ' $lib/Snippets.svelte' ;
26- import {getFirstValid } from " $lib/common" ;
27+ import {getFirstValid , fmtNumber } from " $lib/common" ;
2728
2829 let id = $state (' ' );
2930 let btPath = $state (' ' );
@@ -448,32 +449,55 @@ ${m.holding()}: ${fmtDuration((td.exit_at - td.enter_at) / 1000)}`;
448449
449450 // 定义导航菜单项
450451 let navItems = $derived .by (() => {
451- const items = [];
452+ const items = []
452453
453454 if (task ?.status == 3 ) {
454455 if (detail ) {
455456 items .push (
456- { id: ' overview' , label: m .overview () },
457- { id: ' assets' , label: m .bt_assets () },
458- { id: ' enters' , label: m .bt_enters () }
457+ { id: ' overview' , label: m .overview (), icon: ' home ' },
458+ { id: ' assets' , label: m .bt_assets (), icon: ' chart-bar ' },
459+ { id: ' enters' , label: m .bt_enters (), icon: ' double-right ' }
459460 );
460461 }
461462
462- items .push ({ id: ' config' , label: m .configuration () });
463+ items .push ({ id: ' config' , label: m .configuration (), icon: ' config ' });
463464
464465 if (detail ) {
465466 items .push (
466- { id: ' orders' , label: m .orders () },
467- { id: ' analysis' , label: m .bt_analysis () },
468- { id: ' strat_code' , label: m .bt_strat_code () }
467+ { id: ' orders' , label: m .orders (), icon: ' number-list ' },
468+ { id: ' analysis' , label: m .bt_analysis (), icon: ' calculate ' },
469+ { id: ' strat_code' , label: m .bt_strat_code (), icon: ' code ' }
469470 );
470471 }
471472 }
472473
473- items .push ({ id: ' logs' , label: m .bt_logs () });
474+ items .push ({ id: ' logs' , label: m .bt_logs (), icon: ' document-text ' });
474475 return items ;
475476 });
476477
478+ // 组织次要统计信息的数据,返回最大3列的二维数组
479+ function getInfoColumns() {
480+ if (! detail || ! task ) return [];
481+ return [
482+ [
483+ { label: m .strategy (), value: task ?.strats || ' -' },
484+ { label: m .time_period (), value: task ?.periods || ' -' },
485+ { label: m .start_time (), value: fmtDateStr (detail .startMS ), value_tip: curTZ () },
486+ { label: m .end_time (), value: fmtDateStr (detail .endMS ), value_tip: curTZ () },
487+ ],[
488+ { label: m .total_invest (), value: task ?.walletAmount ? formatNumber (task .walletAmount ) : formatNumber (detail .totalInvest ) },
489+ { label: m .final_balance (), value: formatNumber (detail .finBalance ) },
490+ { label: m .total_withdraw (), value: formatNumber (detail .finWithdraw ) },
491+ { label: m .tot_fee (), value: ` ${formatNumber (detail .totFee )} (${formatPercent (detail .totFee / detail .totProfit * 100 )}) ` },
492+ ],[
493+ { label: m .max_drawdown (), value: ` ${formatPercent (detail .maxDrawDownPct )} (${formatNumber (detail .maxDrawDownVal )} USD) ` },
494+ { label: m .bar_num (), value: detail .barNum .toString () },
495+ { label: m .symbol (), value: task ?.pairs ? showPairs (task .pairs ) : ' -' },
496+ { label: m .max_open_orders (), value: detail .maxOpenOrders .toString () }
497+ ]
498+ ];
499+ }
500+
477501 function setActiveTab(tab : string ) {
478502 activeTab = tab ;
479503 if (activeTab === ' orders' ) {
@@ -499,8 +523,14 @@ ${m.holding()}: ${fmtDuration((td.exit_at - td.enter_at) / 1000)}`;
499523
500524<div class =" px-4 py-6 flex-1 flex flex-col" >
501525 <div class =" flex justify-between items-center mb-6" >
502- <h1 class ="text-2xl font-bold" >{m .bt_report ()}: {id }</h1 >
503- <div class ="text-sm opacity-75" >{btPath }</div >
526+ <div class =" flex items-center gap-4" >
527+ <h1 class ="text-2xl font-bold" >{m .bt_report ()}: {id }</h1 >
528+ <div class ="text-sm opacity-75" >{btPath }</div >
529+ </div >
530+ <button class ="btn btn-sm btn-ghost gap-2" onclick ={() => history .back ()}>
531+ <Icon name =" double-left" class =" size-4" />
532+ {m .back ()}
533+ </button >
504534 </div >
505535
506536 <div class =" flex gap-4 flex-1" >
@@ -510,6 +540,7 @@ ${m.holding()}: ${fmtDuration((td.exit_at - td.enter_at) / 1000)}`;
510540 {#each navItems as item }
511541 <li >
512542 <button class:menu-active ={activeTab === item .id } onclick ={() => setActiveTab (item .id )}>
543+ <Icon name ={item .icon } class =" size-4" />
513544 {item .label }
514545 </button >
515546 </li >
@@ -582,16 +613,9 @@ ${m.holding()}: ${fmtDuration((td.exit_at - td.enter_at) / 1000)}`;
582613 formatPercent (detail .winRatePct ),
583614 ` ${m .order_num ()} ${detail .orderNum } `
584615 )}
585-
586- {@render statCard (
587- m .max_drawdown (),
588- ' text-warning' ,
589- formatPercent (detail .maxDrawDownPct ),
590- ` ${formatNumber (detail .maxDrawDownVal )} USD `
591- )}
592616
593617 {@render statCard (
594- m .show_drawdown (),
618+ m .max_drawdown (),
595619 ' text-warning' ,
596620 formatPercent (detail .showDrawDownPct ),
597621 ` ${formatNumber (detail .showDrawDownVal )} USD `
@@ -605,16 +629,22 @@ ${m.holding()}: ${fmtDuration((td.exit_at - td.enter_at) / 1000)}`;
605629 </div >
606630
607631 <!-- 次要统计信息 -->
608- <div class =" grid grid-cols-2 lg:grid-cols-3 gap-4 mb-6" >
609- {@render infoCard (m .bt_range () + ` (${curTZ ()}) ` , ` ${fmtDateStr (detail .startMS )} ~ ${fmtDateStr (detail .endMS )} ` )}
610- {@render infoCard (
611- ` ${m .total_invest ()}/${m .final_balance ()}/${m .total_withdraw ()} ` ,
612- ` ${task ?.walletAmount ? formatNumber (task .walletAmount ) : formatNumber (detail .totalInvest )} / ${formatNumber (detail .finBalance )} / ${formatNumber (detail .finWithdraw )} `
613- )}
614- {@render infoCard (m .tot_fee (), ` ${formatNumber (detail .totFee )} (${formatPercent (detail .totFee / detail .totProfit * 100 )}) ` )}
615- {@render infoCard (m .strategy (), task ?.strats || ' -' )}
616- {@render infoCard (` ${m .time_period ()}/${m .bar_num ()} ` , ` ${task ?.periods || ' -' } / ${detail .barNum } ` )}
617- {@render infoCard (m .symbol () + ' /' + m .max_open_orders (), ` ${task ?.pairs ? showPairs (task .pairs ) : ' -' } / ${detail .maxOpenOrders } ` )}
632+ {@const infoColumns = getInfoColumns ()}
633+ <div class =" grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6 mb-6" >
634+ {#each infoColumns as column , idx }
635+ <!-- 使用响应式类控制显示 -->
636+ <div class =" bg-base-200 rounded-box p-4
637+ {idx === 2 ? 'lg:col-span-2 xl:col-span-1' : ''}" >
638+ <div class =" space-y-3" >
639+ {#each column as item }
640+ <div class =" flex justify-between items-center" >
641+ <span class ="text-sm opacity-75" >{item .label }</span >
642+ <span class ="text-sm font-medium" title =" {item .value_tip }" >{item .value }</span >
643+ </div >
644+ {/each }
645+ </div >
646+ </div >
647+ {/each }
618648 </div >
619649
620650 <!-- 分组统计 -->
@@ -625,30 +655,35 @@ ${m.holding()}: ${fmtDuration((td.exit_at - td.enter_at) / 1000)}`;
625655 class =" tab"
626656 class:tab-active ={activeGroupTab === ' pairs' }
627657 onclick ={() => activeGroupTab = ' pairs' }>
658+ <Icon name =" chart-bar" class =" size-4 mr-2" />
628659 {m .stat_by_pairs ()}
629660 </button >
630661 <button role =" tab"
631662 class =" tab"
632663 class:tab-active ={activeGroupTab === ' dates' }
633664 onclick ={() => activeGroupTab = ' dates' }>
665+ <Icon name =" calender" class =" size-4 mr-2" />
634666 {m .stat_by_dates ()}
635667 </button >
636668 <button role =" tab"
637669 class =" tab"
638670 class:tab-active ={activeGroupTab === ' profits' }
639671 onclick ={() => activeGroupTab = ' profits' }>
672+ <Icon name =" dollar" class =" size-4 mr-2" />
640673 {m .stat_by_profits ()}
641674 </button >
642675 <button role =" tab"
643676 class =" tab"
644677 class:tab-active ={activeGroupTab === ' enters' }
645678 onclick ={() => activeGroupTab = ' enters' }>
679+ <Icon name =" chevron-right" class =" size-4 mr-2" />
646680 {m .stat_by_enters ()}
647681 </button >
648682 <button role =" tab"
649683 class =" tab"
650684 class:tab-active ={activeGroupTab === ' exits' }
651685 onclick ={() => activeGroupTab = ' exits' }>
686+ <Icon name =" chevron-down" class =" size-4 mr-2" />
652687 {m .stat_by_exits ()}
653688 </button >
654689 </div >
@@ -915,12 +950,12 @@ ${m.holding()}: ${fmtDuration((td.exit_at - td.enter_at) / 1000)}`;
915950 <td >{order .leverage }x</td >
916951 <td >{fmtDateStr (order .enter_at )}</td >
917952 <td >{order .enter_tag }</td >
918- <td >{formatNumber (order .enter ?.average || order .enter ?.price || 0 , 8 )}</td >
919- <td >{formatNumber (order .enter ?.filled || order .enter ?.amount || 0 , 8 )}</td >
953+ <td >{fmtNumber (order .enter ?.average || order .enter ?.price || 0 )}</td >
954+ <td >{fmtNumber (order .enter ?.filled || order .enter ?.amount || 0 )}</td >
920955 <td >{fmtDateStr (order .exit_at )}</td >
921956 <td >{order .exit_tag }</td >
922- <td >{formatNumber (order .exit ?.average || order .exit ?.price || 0 , 8 )}</td >
923- <td >{formatNumber (order .exit ?.filled || order .exit ?.amount || 0 , 8 )}</td >
957+ <td >{fmtNumber (order .exit ?.average || order .exit ?.price || 0 )}</td >
958+ <td >{fmtNumber (order .exit ?.filled || order .exit ?.amount || 0 )}</td >
924959 <td >{formatNumber (order .profit )}</td >
925960 </tr >
926961 {/each }
0 commit comments