1- import { useState , useCallback , useRef } from 'react' ;
2- import { useWasmEngine } from './hooks/useWasmEngine' ;
1+ import { useState , useCallback , useRef , useEffect } from 'react' ;
2+ import { useWasmEngine , type DataSource } from './hooks/useWasmEngine' ;
33import type {
44 Timeframe ,
55 Indicator ,
@@ -25,11 +25,44 @@ const MAIN_INDICATORS = ['MA', 'EMA', 'BOLL'] as const;
2525/** 副图指标: VOL, MACD, RSI */
2626const SUB_INDICATORS = [ 'VOL' , 'MACD' , 'RSI' ] as const ;
2727
28+ /** localStorage key for data source preference */
29+ const DATA_SOURCE_KEY = 'rustquantlab_data_source' ;
30+
31+ /**
32+ * 获取初始数据源
33+ * 优先级: URL 参数 > localStorage > 默认 mock
34+ */
35+ function getInitialDataSource ( ) : DataSource {
36+ // 检查 URL 参数
37+ const params = new URLSearchParams ( window . location . search ) ;
38+ const urlSource = params . get ( 'source' ) ;
39+ if ( urlSource === 'binance' || urlSource === 'mock' ) {
40+ return urlSource ;
41+ }
42+
43+ // 检查 localStorage
44+ const savedSource = localStorage . getItem ( DATA_SOURCE_KEY ) ;
45+ if ( savedSource === 'binance' || savedSource === 'mock' ) {
46+ return savedSource ;
47+ }
48+
49+ // 默认使用模拟数据
50+ return 'mock' ;
51+ }
52+
2853/* ============================================
2954 Main App Component (Composition Root)
3055 ============================================ */
3156
3257function App ( ) {
58+ // ========== 数据源状态 ==========
59+ const [ dataSource , setDataSource ] = useState < DataSource > ( getInitialDataSource ) ;
60+
61+ // 持久化数据源偏好
62+ useEffect ( ( ) => {
63+ localStorage . setItem ( DATA_SOURCE_KEY , dataSource ) ;
64+ } , [ dataSource ] ) ;
65+
3366 // ========== 统一的 Wasm 引擎 Hook ==========
3467 // 整合市场数据 + 交易状态,React 只做 UI 搬运工
3568 const {
@@ -51,6 +84,8 @@ function App() {
5184 priceColorClass,
5285 toggleFeed,
5386 setTimeframe,
87+ dataSource : engineDataSource ,
88+ connectionStatus,
5489
5590 // 交易状态 (Rust 管理)
5691 tradingState,
@@ -65,7 +100,11 @@ function App() {
65100 setLeverage,
66101 cancelOrder,
67102 addMargin,
68- } = useWasmEngine ( 100 ) ;
103+ } = useWasmEngine ( {
104+ tickInterval : 100 ,
105+ dataSource,
106+ historyCount : 5000 , // 获取更多历史数据(5000 根 1m K 线 ≈ 3.5 天,足够计算所有指标)
107+ } ) ;
69108
70109 // ========== 图表引用 ==========
71110 const chartRef = useRef < KLineChartHandle > ( null ) ;
@@ -179,11 +218,14 @@ function App() {
179218 < div className = "h-screen w-screen bg-[#161a1e] flex flex-col overflow-hidden" >
180219 < Header
181220 isRunning = { isRunning }
182- onToggle = { toggleFeed }
221+ onToggle = { dataSource === 'mock' ? toggleFeed : undefined } // LIVE 模式下禁用切换
183222 price = { latestData ?. price }
184223 symbol = { latestData ?. symbol }
185224 priceTrend = { priceTrend }
186225 priceColorClass = { priceColorClass }
226+ dataSource = { dataSource }
227+ onDataSourceChange = { setDataSource }
228+ connectionStatus = { connectionStatus }
187229 />
188230
189231 { /* ========== 主内容区域 ========== */ }
@@ -202,13 +244,24 @@ function App() {
202244 "
203245 >
204246 { /* 响应式网格容器 */ }
247+ { /*
248+ - 移动端: 单列垂直布局
249+ - 平板端 (md): 2列 (图表 + 订单簿)
250+ - 桌面端 (xl):
251+ * 有交易表单 (MOCK): 3列 (图表 + 订单簿自适应 + 交易表单固定300px)
252+ * 无交易表单 (LIVE): 2列 (图表 + 订单簿自适应占满)
253+ */ }
205254 < div
206- className = "
255+ className = { `
207256 flex flex-col
208- md:grid md:grid-cols-[1fr_280px] md:h-full
209- xl:grid-cols-[1fr_260px_300px]
257+ md:grid md:h-full
210258 gap-px bg-[#2b2f36]
211- "
259+ ${
260+ dataSource === 'mock'
261+ ? 'md:grid-cols-[1fr_240px] xl:grid-cols-[1fr_240px_300px]'
262+ : 'md:grid-cols-[1fr_240px] xl:grid-cols-[1fr_auto]'
263+ }
264+ ` }
212265 >
213266 { /* ========== 图表区域 (Chart + Toolbar + Stats) ========== */ }
214267 < section className = "flex flex-col min-h-0 bg-terminal-bg" >
@@ -267,7 +320,13 @@ function App() {
267320
268321 { /* ========== 订单簿区域 ========== */ }
269322 { /* 移动端高度压缩: 工具栏(28) + 表头(18) + 卖单(70) + Ticker(28) + 买单(70) = 214px */ }
270- < section className = "h-[214px] md:h-full min-h-0 bg-terminal-bg border-t md:border-t-0 md:border-l border-[#2b2f36]" >
323+ { /* MOCK 模式: 固定宽度 240px | LIVE 模式: 自动宽度占满剩余空间 */ }
324+ < section
325+ className = { `
326+ h-[214px] md:h-full min-h-0 bg-terminal-bg border-t md:border-t-0 md:border-l border-[#2b2f36]
327+ ${ dataSource === 'binance' ? 'xl:min-w-[200px]' : '' }
328+ ` }
329+ >
271330 < OrderBook
272331 bids = { latestData ?. bids ?? [ ] }
273332 asks = { latestData ?. asks ?? [ ] }
@@ -278,46 +337,50 @@ function App() {
278337 />
279338 </ section >
280339
281- { /* ========== 交易表单区域 (仅平板/桌面端显示) ========== */ }
282- { /* 🔴 使用 Wasm 交易状态 */ }
283- < section className = "hidden xl:block h-full min-h-0 border-l border-[#2b2f36]" >
284- < TradeForm
285- symbol = "BTC"
286- currentPrice = { latestData ?. price ?? 40000 }
287- // Wasm Trading State
288- balance = { tradingState ?. balance ?? 10000 }
289- availableBalance = { tradingState ?. availableBalance ?? 10000 }
290- currentLeverage = { tradingState ?. leverage ?? 10 }
291- position = { position }
292- positions = { tradingState ?. positions ?? [ ] }
293- closedPositions = { tradingState ?. closedPositions ?? [ ] }
294- riskAssessment = { riskAssessment }
295- hasPosition = { hasPosition }
296- pendingOrders = { pendingOrders }
297- // Wasm Actions
298- onPlaceOrder = { placeOrder }
299- onClosePosition = { ( positionId ) => closePosition ( positionId ) }
300- onSetLeverage = { setLeverage }
301- onCancelOrder = { cancelOrder }
302- onAddMargin = { addMargin }
303- />
304- </ section >
340+ { /* ========== 交易表单区域 (仅 MOCK 模式下显示) ========== */ }
341+ { /* LIVE 模式下隐藏交易表单,只展示数据 */ }
342+ { dataSource === 'mock' && (
343+ < section className = "hidden xl:block h-full min-h-0 border-l border-[#2b2f36]" >
344+ < TradeForm
345+ symbol = "BTC"
346+ currentPrice = { latestData ?. price ?? 40000 }
347+ // Wasm Trading State
348+ balance = { tradingState ?. balance ?? 10000 }
349+ availableBalance = { tradingState ?. availableBalance ?? 10000 }
350+ currentLeverage = { tradingState ?. leverage ?? 10 }
351+ position = { position }
352+ positions = { tradingState ?. positions ?? [ ] }
353+ closedPositions = { tradingState ?. closedPositions ?? [ ] }
354+ riskAssessment = { riskAssessment }
355+ hasPosition = { hasPosition }
356+ pendingOrders = { pendingOrders }
357+ // Wasm Actions
358+ onPlaceOrder = { placeOrder }
359+ onClosePosition = { ( positionId ) => closePosition ( positionId ) }
360+ onSetLeverage = { setLeverage }
361+ onCancelOrder = { cancelOrder }
362+ onAddMargin = { addMargin }
363+ />
364+ </ section >
365+ ) }
305366 </ div >
306367 </ main >
307368
308- { /* ========== 移动端 Sticky 底部交易栏 ========== */ }
309- { /* 🔴 使用 Wasm placeOrder */ }
310- < MobileTradebar
311- currentPrice = { latestData ?. price ?? 40000 }
312- onBuy = { ( ) => {
313- // 移动端快速买入:市价单,固定数量
314- placeOrder ( 'LONG' , 0.01 , tradingState ?. leverage ?? 10 ) ;
315- } }
316- onSell = { ( ) => {
317- // 移动端快速卖出:市价单,固定数量
318- placeOrder ( 'SHORT' , 0.01 , tradingState ?. leverage ?? 10 ) ;
319- } }
320- />
369+ { /* ========== 移动端 Sticky 底部交易栏 (仅 MOCK 模式下显示) ========== */ }
370+ { /* LIVE 模式下隐藏交易栏,只展示数据 */ }
371+ { dataSource === 'mock' && (
372+ < MobileTradebar
373+ currentPrice = { latestData ?. price ?? 40000 }
374+ onBuy = { ( ) => {
375+ // 移动端快速买入:市价单,固定数量
376+ placeOrder ( 'LONG' , 0.01 , tradingState ?. leverage ?? 10 ) ;
377+ } }
378+ onSell = { ( ) => {
379+ // 移动端快速卖出:市价单,固定数量
380+ placeOrder ( 'SHORT' , 0.01 , tradingState ?. leverage ?? 10 ) ;
381+ } }
382+ />
383+ ) }
321384 </ div >
322385 ) ;
323386}
0 commit comments