407407 .quick-form {
408408 display : grid;
409409 grid-template-columns : minmax (0 , 1.2fr ) minmax (0 , 1fr );
410+ align-items : start;
410411 gap : 14px ;
411412 }
412413
413414 .control-block {
414415 display : grid;
416+ grid-template-rows : auto 48px minmax (20px , auto);
417+ align-content : start;
415418 gap : 8px ;
416419 min-width : 0 ;
417420 color : var (--muted );
418421 font-size : 12px ;
419422 font-weight : 760 ;
420423 }
421424
425+ .mode-block {
426+ grid-column : 1 ;
427+ }
428+
429+ .min-reserve-block ,
430+ .reserve-ratio-block {
431+ grid-column : 2 ;
432+ }
433+
422434 select ,
423435 input {
424436 width : 100% ;
635647 grid-template-columns : 1fr ;
636648 }
637649
650+ .mode-block ,
651+ .min-reserve-block ,
652+ .reserve-ratio-block {
653+ grid-column : auto;
654+ }
655+
638656 .switch-panel {
639657 padding : 18px ;
640658 }
@@ -736,21 +754,21 @@ <h2 id="platform-title">LongBridge</h2>
736754 < span class ="selection-meta " id ="strategy-meta "> </ span >
737755 </ label >
738756
739- < div class ="control-block ">
757+ < div class ="control-block mode-block ">
740758 < span data-i18n ="mode "> 模式</ span >
741759 < div class ="mode " id ="mode-control ">
742760 < button type ="button " data-mode ="live " class ="active " data-i18n ="live "> 实盘</ button >
743761 < button type ="button " data-mode ="paper " data-i18n ="paper "> 模拟</ button >
744762 </ div >
745763 </ div >
746764
747- < label class ="control-block ">
765+ < label class ="control-block min-reserve-block ">
748766 < span id ="min-reserved-cash-label "> 最小预留现金</ span >
749767 < input id ="min-reserved-cash-input " type ="number " inputmode ="decimal " min ="0 " step ="1 " placeholder ="150 ">
750768 < span class ="selection-meta " data-i18n ="reservedCashMeta "> 留空则沿用当前平台默认值。</ span >
751769 </ label >
752770
753- < label class ="control-block ">
771+ < label class ="control-block reserve-ratio-block ">
754772 < span data-i18n ="reservedCashRatio "> 预留现金比例</ span >
755773 < input id ="reserved-cash-ratio-input " type ="number " inputmode ="decimal " min ="0 " max ="1 " step ="0.01 " placeholder ="0.03 ">
756774 < span class ="selection-meta " data-i18n ="reservedCashRatioMeta "> 例如 0.03 表示 3%。</ span >
@@ -940,7 +958,11 @@ <h2 data-i18n="summary">切换摘要</h2>
940958 ? storedLang
941959 : ( ( navigator . language || "" ) . toLowerCase ( ) . startsWith ( "zh" ) ? "zh" : "en" ) ;
942960 const clone = ( value ) => JSON . parse ( JSON . stringify ( value ) ) ;
943- const defaultReserveForm = ( ) => ( { minReservedCashUsd : "" , reservedCashRatio : "" } ) ;
961+ const defaultReserveForm = ( ) => ( {
962+ minReservedCashUsd : "" ,
963+ reservedCashRatio : "" ,
964+ reservedCashTouched : false ,
965+ } ) ;
944966
945967 const state = {
946968 selected : "longbridge" ,
@@ -1103,6 +1125,33 @@ <h2 data-i18n="summary">切换摘要</h2>
11031125 return profile || "" ;
11041126 }
11051127
1128+ function currentReservePolicyForAccount ( platform , account ) {
1129+ const entry = currentEntryForAccount ( platform , account ) ;
1130+ return reservePolicyFromEntry ( entry ) ;
1131+ }
1132+
1133+ function reservePolicyFromEntry ( entry ) {
1134+ return {
1135+ minReservedCashUsd : cleanDisplayNumber ( entry ?. min_reserved_cash_usd ?? entry ?. reserved_cash_floor_usd ) ,
1136+ reservedCashRatio : cleanDisplayRatio ( entry ?. reserved_cash_ratio ) ,
1137+ } ;
1138+ }
1139+
1140+ function cleanDisplayNumber ( value ) {
1141+ const text = String ( value ?? "" ) . trim ( ) ;
1142+ if ( ! text || text . length > 32 || ! / ^ (?: \d + | \d * \. \d + ) $ / . test ( text ) ) return "" ;
1143+ const numeric = Number ( text ) ;
1144+ if ( ! Number . isFinite ( numeric ) || numeric < 0 ) return "" ;
1145+ return text ;
1146+ }
1147+
1148+ function cleanDisplayRatio ( value ) {
1149+ const text = cleanDisplayNumber ( value ) ;
1150+ if ( ! text ) return "" ;
1151+ const numeric = Number ( text ) ;
1152+ return numeric >= 0 && numeric <= 1 ? text : "" ;
1153+ }
1154+
11061155 function normalizeExecutionMode ( value , dryRunOnly ) {
11071156 const mode = String ( value || "" ) . trim ( ) . toLowerCase ( ) ;
11081157 if ( mode === "live" || mode === "paper" ) return mode ;
@@ -1160,14 +1209,25 @@ <h2 data-i18n="summary">切换摘要</h2>
11601209 platform ,
11611210 account ,
11621211 ) ;
1212+ syncReservePolicyForAccount ( platform ) ;
1213+ }
1214+
1215+ function syncReservePolicyForAccount ( platform ) {
1216+ const form = state . forms [ platform ] ;
1217+ if ( ! form || form . reservedCashTouched ) return ;
1218+ const policy = currentReservePolicyForAccount ( platform , selectedAccount ( platform ) ) ;
1219+ form . minReservedCashUsd = policy . minReservedCashUsd ;
1220+ form . reservedCashRatio = policy . reservedCashRatio ;
11631221 }
11641222
11651223 function ensureAccountSelection ( platform ) {
11661224 const options = optionsFor ( platform ) ;
11671225 if ( ! options . length ) return ;
11681226 if ( ! options . some ( ( option ) => option . key === state . forms [ platform ] . accountKey ) ) {
11691227 state . forms [ platform ] . accountKey = options [ 0 ] . key ;
1228+ state . forms [ platform ] . reservedCashTouched = false ;
11701229 state . forms [ platform ] . strategy = defaultStrategyForAccount ( platform , options [ 0 ] , state . forms [ platform ] . strategy ) ;
1230+ syncReservePolicyForAccount ( platform ) ;
11711231 }
11721232 }
11731233
@@ -1226,16 +1286,29 @@ <h2 data-i18n="summary">切换摘要</h2>
12261286 return inputs ;
12271287 }
12281288
1229- function reservedCashPolicyText ( inputs ) {
1289+ function reservedCashPolicyText ( inputs , platform = state . selected , account = selectedAccount ( platform ) , fallback = t ( "unchanged" ) ) {
12301290 const floor = inputs . min_reserved_cash_usd ;
12311291 const ratio = inputs . reserved_cash_ratio ;
1232- const currency = selectedCashCurrency ( ) ;
1233- if ( ! floor && ! ratio ) return t ( "unchanged" ) ;
1292+ const currency = selectedCashCurrency ( platform , account ) ;
1293+ if ( ! floor && ! ratio ) return fallback ;
12341294 if ( floor && ratio ) return `max(${ floor } ${ currency } , ${ formatRatioPercent ( ratio ) } )` ;
12351295 if ( floor ) return `${ floor } ${ currency } ` ;
12361296 return formatRatioPercent ( ratio ) ;
12371297 }
12381298
1299+ function currentReservedCashPolicyText ( platform = state . selected , account = selectedAccount ( platform ) ) {
1300+ const policy = currentReservePolicyForAccount ( platform , account ) ;
1301+ return reservedCashPolicyText (
1302+ {
1303+ min_reserved_cash_usd : policy . minReservedCashUsd ,
1304+ reserved_cash_ratio : policy . reservedCashRatio ,
1305+ } ,
1306+ platform ,
1307+ account ,
1308+ t ( "notRead" ) ,
1309+ ) ;
1310+ }
1311+
12391312 function formatRatioPercent ( value ) {
12401313 const numeric = Number ( value ) ;
12411314 if ( ! Number . isFinite ( numeric ) ) return String ( value ) ;
@@ -1249,7 +1322,7 @@ <h2 data-i18n="summary">切换摘要</h2>
12491322 [ t ( "repository" ) , state . repositories [ state . selected ] || defaultRepositories [ state . selected ] ] ,
12501323 [ t ( "selectedAccount" ) , account . label ] ,
12511324 [ t ( "selectedMarket" ) , supportedDomainLabel ( state . selected , account ) ] ,
1252- [ t ( "reservedCashPolicy" ) , reservedCashPolicyText ( inputs ) ] ,
1325+ [ t ( "reservedCashPolicy" ) , currentReservedCashPolicyText ( state . selected , account ) ] ,
12531326 [ t ( "nextStrategy" ) , strategyLabel ( inputs . strategy_profile ) ] ,
12541327 ] ;
12551328 if ( state . auth . allowed ) {
@@ -1562,6 +1635,8 @@ <h2 data-i18n="summary">切换摘要</h2>
15621635 strategy_profile : profile ,
15631636 execution_mode : normalizeExecutionMode ( entry ?. execution_mode , entry ?. dry_run_only ) ,
15641637 dry_run_only : entry ?. dry_run_only === true || entry ?. dry_run_only === "true" || entry ?. dry_run_only === "1" ,
1638+ min_reserved_cash_usd : cleanDisplayNumber ( entry ?. min_reserved_cash_usd ?? entry ?. reserved_cash_floor_usd ) ,
1639+ reserved_cash_ratio : cleanDisplayRatio ( entry ?. reserved_cash_ratio ) ,
15651640 source : entry ?. source ? String ( entry . source ) : "" ,
15661641 } ;
15671642 }
@@ -1619,6 +1694,7 @@ <h2 data-i18n="summary">切换摘要</h2>
16191694
16201695 el ( "account-select" ) . addEventListener ( "change" , ( ) => {
16211696 state . forms [ state . selected ] . accountKey = el ( "account-select" ) . value ;
1697+ state . forms [ state . selected ] . reservedCashTouched = false ;
16221698 syncStrategyForAccount ( state . selected ) ;
16231699 render ( ) ;
16241700 } ) ;
@@ -1636,11 +1712,13 @@ <h2 data-i18n="summary">切换摘要</h2>
16361712 } ) ;
16371713
16381714 el ( "min-reserved-cash-input" ) . addEventListener ( "input" , ( ) => {
1715+ state . forms [ state . selected ] . reservedCashTouched = true ;
16391716 state . forms [ state . selected ] . minReservedCashUsd = el ( "min-reserved-cash-input" ) . value . trim ( ) ;
16401717 render ( ) ;
16411718 } ) ;
16421719
16431720 el ( "reserved-cash-ratio-input" ) . addEventListener ( "input" , ( ) => {
1721+ state . forms [ state . selected ] . reservedCashTouched = true ;
16441722 state . forms [ state . selected ] . reservedCashRatio = el ( "reserved-cash-ratio-input" ) . value . trim ( ) ;
16451723 render ( ) ;
16461724 } ) ;
0 commit comments