lfqian commited on
Commit
f427ad0
·
1 Parent(s): 5f467b1

Stabilize per-page table binding and prevent tab-switch data thrash

Browse files
src/lib/dataService.js CHANGED
@@ -27,10 +27,19 @@ class DataService {
27
  this.modelOptions = []
28
  this.strategyOptions = []
29
  this.cacheResetPromise = null
 
30
  }
31
 
32
  setSourceTable(tableName) {
33
  const next = tableName || 'trading_decisions'
 
 
 
 
 
 
 
 
34
  if (this.sourceTable === next) return false
35
  this.sourceTable = next
36
  this.loaded = false
@@ -83,6 +92,7 @@ class DataService {
83
  this.listeners.forEach(callback => {
84
  try {
85
  callback({
 
86
  loading: this.loading,
87
  loaded: this.loaded,
88
  agents: this.agents,
@@ -682,6 +692,14 @@ class DataService {
682
  } finally {
683
  this.loading = false
684
  this._notify()
 
 
 
 
 
 
 
 
685
  }
686
  }
687
 
@@ -769,6 +787,7 @@ class DataService {
769
  */
770
  getState() {
771
  return {
 
772
  loading: this.loading,
773
  loaded: this.loaded,
774
  agents: this.agents,
 
27
  this.modelOptions = []
28
  this.strategyOptions = []
29
  this.cacheResetPromise = null
30
+ this.pendingSourceTable = null
31
  }
32
 
33
  setSourceTable(tableName) {
34
  const next = tableName || 'trading_decisions'
35
+ if (this.loading) {
36
+ this.pendingSourceTable = next
37
+ return false
38
+ }
39
+ return this._applySourceTable(next)
40
+ }
41
+
42
+ _applySourceTable(next) {
43
  if (this.sourceTable === next) return false
44
  this.sourceTable = next
45
  this.loaded = false
 
92
  this.listeners.forEach(callback => {
93
  try {
94
  callback({
95
+ sourceTable: this.getSourceTable(),
96
  loading: this.loading,
97
  loaded: this.loaded,
98
  agents: this.agents,
 
692
  } finally {
693
  this.loading = false
694
  this._notify()
695
+ const pending = this.pendingSourceTable
696
+ this.pendingSourceTable = null
697
+ if (pending && pending !== this.sourceTable) {
698
+ this._applySourceTable(pending)
699
+ this.load(false).catch((e) => {
700
+ console.error('[DataService] Error loading pending source table:', e)
701
+ })
702
+ }
703
  }
704
  }
705
 
 
787
  */
788
  getState() {
789
  return {
790
+ sourceTable: this.getSourceTable(),
791
  loading: this.loading,
792
  loaded: this.loaded,
793
  agents: this.agents,
src/router/index.js CHANGED
@@ -5,7 +5,6 @@ import LiveView from '../views/LiveView.vue'
5
  import AddAssetsView from '../views/AddAssetView.vue'
6
  import RequestView from '../views/RequestView.vue'
7
  import AssetRequestsView from '../views/AssetRequestsView.vue'
8
- import { dataService } from '../lib/dataService.js'
9
 
10
  const routes = [
11
  {
@@ -29,19 +28,4 @@ const router = createRouter({
29
  routes
30
  })
31
 
32
- // 全局路由守卫:确保数据在导航前开始加载
33
- router.beforeEach(async (to, from, next) => {
34
- const isChallengeRoute = to.path === '/live-challenge'
35
- dataService.setSourceTable(isChallengeRoute ? 'challenge_table' : 'trading_decisions')
36
- // 如果数据还未加载且不在加载中,则触发加载
37
- if (!dataService.loaded && !dataService.loading) {
38
- console.log('[Router] Triggering data load before navigation')
39
- // 不等待加载完成,让加载在后台进行
40
- dataService.load().catch(e => {
41
- console.error('[Router] Error loading data:', e)
42
- })
43
- }
44
- next()
45
- })
46
-
47
  export default router
 
5
  import AddAssetsView from '../views/AddAssetView.vue'
6
  import RequestView from '../views/RequestView.vue'
7
  import AssetRequestsView from '../views/AssetRequestsView.vue'
 
8
 
9
  const routes = [
10
  {
 
28
  routes
29
  })
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  export default router
src/views/LeaderboardView.vue CHANGED
@@ -126,6 +126,7 @@ export default {
126
  '$route.path': {
127
  immediate: true,
128
  handler() {
 
129
  if (!this.isChallengeLeaderboard) return
130
  const allow = new Set(['BTC', 'TSLA'])
131
  const scoped = (this.filters.assets || []).filter(a => allow.has(a))
@@ -205,6 +206,19 @@ export default {
205
  }
206
  },
207
  methods: {
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  /**
209
  * 从 dataService 同步状态
210
  */
@@ -357,28 +371,32 @@ export default {
357
  mounted() {
358
  // 订阅 dataService 状态变化
359
  this.unsubscribe = dataService.subscribe((state) => {
 
360
  this.syncFromDataService(state)
361
  })
362
-
 
 
363
  // 立即同步当前状态
364
- this.syncFromDataService(dataService.getState())
365
-
366
- // 如果数据还没加载,触发加载
367
- if (!dataService.loaded && !dataService.loading) {
368
- dataService.load()
369
  }
370
 
371
  // 定期更新 dateBounds(每30秒检查一次数据库的最新日期)
372
  this.dateBoundsUpdateTimer = setInterval(() => {
 
373
  dataService.updateDateBounds().catch(e => {
374
  console.error('[LeaderboardView] Error updating dateBounds:', e)
375
  })
376
  }, 30000) // 30秒
377
 
378
  // 组件挂载后立即更新一次
379
- dataService.updateDateBounds().catch(e => {
380
- console.error('[LeaderboardView] Error updating dateBounds on mount:', e)
381
- })
 
 
382
  },
383
  beforeUnmount() {
384
  // 取消订阅
 
126
  '$route.path': {
127
  immediate: true,
128
  handler() {
129
+ this.ensureSourceAndLoad()
130
  if (!this.isChallengeLeaderboard) return
131
  const allow = new Set(['BTC', 'TSLA'])
132
  const scoped = (this.filters.assets || []).filter(a => allow.has(a))
 
206
  }
207
  },
208
  methods: {
209
+ expectedSourceTable() {
210
+ return this.isChallengeLeaderboard ? 'challenge_table' : 'trading_decisions'
211
+ },
212
+ ensureSourceAndLoad() {
213
+ const target = this.expectedSourceTable()
214
+ const changed = dataService.setSourceTable(target)
215
+ if (dataService.getSourceTable() !== target) return
216
+ if ((changed || !dataService.loaded) && !dataService.loading) {
217
+ dataService.load().catch(e => {
218
+ console.error('[LeaderboardView] load failed:', e)
219
+ })
220
+ }
221
+ },
222
  /**
223
  * 从 dataService 同步状态
224
  */
 
371
  mounted() {
372
  // 订阅 dataService 状态变化
373
  this.unsubscribe = dataService.subscribe((state) => {
374
+ if (state?.sourceTable !== this.expectedSourceTable()) return
375
  this.syncFromDataService(state)
376
  })
377
+
378
+ this.ensureSourceAndLoad()
379
+
380
  // 立即同步当前状态
381
+ const state = dataService.getState()
382
+ if (state?.sourceTable === this.expectedSourceTable()) {
383
+ this.syncFromDataService(state)
 
 
384
  }
385
 
386
  // 定期更新 dateBounds(每30秒检查一次数据库的最新日期)
387
  this.dateBoundsUpdateTimer = setInterval(() => {
388
+ if (dataService.getSourceTable() !== this.expectedSourceTable()) return
389
  dataService.updateDateBounds().catch(e => {
390
  console.error('[LeaderboardView] Error updating dateBounds:', e)
391
  })
392
  }, 30000) // 30秒
393
 
394
  // 组件挂载后立即更新一次
395
+ if (dataService.getSourceTable() === this.expectedSourceTable()) {
396
+ dataService.updateDateBounds().catch(e => {
397
+ console.error('[LeaderboardView] Error updating dateBounds on mount:', e)
398
+ })
399
+ }
400
  },
401
  beforeUnmount() {
402
  // 取消订阅
src/views/LiveView.vue CHANGED
@@ -234,6 +234,7 @@ const refreshing = ref(false)
234
  let unsubscribe = null
235
  const route = useRoute()
236
  const isChallengePage = computed(() => route.path === '/live-challenge')
 
237
  const viewStrategy = computed(() => route.path === '/live-challenge' ? 'long_short' : 'long_only')
238
  const tabAssets = computed(() => route.path === '/live-challenge' ? challengeAssets : orderedAssets)
239
  const challengeDateRange = ref([0, 0])
@@ -353,18 +354,27 @@ const challengeLeaderboardRows = computed(() => {
353
  return rows.sort((a, b) => (Number(b.ret_with_fees) || 0) - (Number(a.ret_with_fees) || 0))
354
  })
355
 
 
 
 
 
 
 
 
 
 
356
  /* ---------- bootstrap ---------- */
357
  onMounted(async () => {
358
  unsubscribe = dataService.subscribe((state) => {
 
359
  rowsRef.value = Array.isArray(state.tableRows) ? state.tableRows : []
360
  })
361
 
362
- rowsRef.value = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
363
-
364
- // Only load when current source is not ready yet.
365
- if (!dataService.loaded && !dataService.loading) {
366
- dataService.load(false).catch(e => console.error('LiveView: load failed', e))
367
  }
 
 
368
 
369
  if (!tabAssets.value.includes(asset.value)) asset.value = tabAssets.value[0]
370
 
@@ -377,6 +387,11 @@ onMounted(async () => {
377
  }
378
  })
379
 
 
 
 
 
 
380
  onBeforeUnmount(() => {
381
  if (unsubscribe) { unsubscribe(); unsubscribe = null }
382
  })
 
234
  let unsubscribe = null
235
  const route = useRoute()
236
  const isChallengePage = computed(() => route.path === '/live-challenge')
237
+ const expectedSourceTable = computed(() => isChallengePage.value ? 'challenge_table' : 'trading_decisions')
238
  const viewStrategy = computed(() => route.path === '/live-challenge' ? 'long_short' : 'long_only')
239
  const tabAssets = computed(() => route.path === '/live-challenge' ? challengeAssets : orderedAssets)
240
  const challengeDateRange = ref([0, 0])
 
354
  return rows.sort((a, b) => (Number(b.ret_with_fees) || 0) - (Number(a.ret_with_fees) || 0))
355
  })
356
 
357
+ const ensureSourceAndLoad = () => {
358
+ const target = expectedSourceTable.value
359
+ const changed = dataService.setSourceTable(target)
360
+ if (dataService.getSourceTable() !== target) return
361
+ if ((changed || !dataService.loaded) && !dataService.loading) {
362
+ dataService.load(false).catch(e => console.error('LiveView: load failed', e))
363
+ }
364
+ }
365
+
366
  /* ---------- bootstrap ---------- */
367
  onMounted(async () => {
368
  unsubscribe = dataService.subscribe((state) => {
369
+ if (state?.sourceTable !== expectedSourceTable.value) return
370
  rowsRef.value = Array.isArray(state.tableRows) ? state.tableRows : []
371
  })
372
 
373
+ if (dataService.getSourceTable() === expectedSourceTable.value) {
374
+ rowsRef.value = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
 
 
 
375
  }
376
+
377
+ ensureSourceAndLoad()
378
 
379
  if (!tabAssets.value.includes(asset.value)) asset.value = tabAssets.value[0]
380
 
 
387
  }
388
  })
389
 
390
+ watch(expectedSourceTable, () => {
391
+ rowsRef.value = []
392
+ ensureSourceAndLoad()
393
+ })
394
+
395
  onBeforeUnmount(() => {
396
  if (unsubscribe) { unsubscribe(); unsubscribe = null }
397
  })
src/views/RequestView.vue CHANGED
@@ -366,13 +366,16 @@ export default {
366
  // Join Arena always uses the full default table (not challenge_table).
367
  dataService.setSourceTable('trading_decisions')
368
  this.unsubscribe = dataService.subscribe((state) => {
 
369
  this.rowsRef = Array.isArray(state.tableRows) ? state.tableRows : []
370
  this.dateBounds = state?.dateBounds || { min: null, max: null }
371
  this.rebuildAllDates()
372
  this.rebuildAssets().catch(() => {})
373
  })
374
- this.rowsRef = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
375
- this.dateBounds = dataService.dateBounds || { min: null, max: null }
 
 
376
  this.rebuildAllDates()
377
 
378
  // If another page is loading (e.g. live-challenge), wait and then reload
@@ -382,6 +385,7 @@ export default {
382
  triggerLoad()
383
  } else if (dataService.loading) {
384
  const stopWait = dataService.subscribe((state) => {
 
385
  if (!state.loading) {
386
  stopWait()
387
  if (!state.loaded) triggerLoad()
 
366
  // Join Arena always uses the full default table (not challenge_table).
367
  dataService.setSourceTable('trading_decisions')
368
  this.unsubscribe = dataService.subscribe((state) => {
369
+ if (state?.sourceTable !== 'trading_decisions') return
370
  this.rowsRef = Array.isArray(state.tableRows) ? state.tableRows : []
371
  this.dateBounds = state?.dateBounds || { min: null, max: null }
372
  this.rebuildAllDates()
373
  this.rebuildAssets().catch(() => {})
374
  })
375
+ if (dataService.getSourceTable() === 'trading_decisions') {
376
+ this.rowsRef = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
377
+ this.dateBounds = dataService.dateBounds || { min: null, max: null }
378
+ }
379
  this.rebuildAllDates()
380
 
381
  // If another page is loading (e.g. live-challenge), wait and then reload
 
385
  triggerLoad()
386
  } else if (dataService.loading) {
387
  const stopWait = dataService.subscribe((state) => {
388
+ if (state?.sourceTable !== 'trading_decisions') return
389
  if (!state.loading) {
390
  stopWait()
391
  if (!state.loaded) triggerLoad()