diff --git a/frontend/src/components/MarketSyncBar.vue b/frontend/src/components/MarketSyncBar.vue new file mode 100644 index 0000000..9bfe840 --- /dev/null +++ b/frontend/src/components/MarketSyncBar.vue @@ -0,0 +1,76 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/SyncStatusBar.vue b/frontend/src/components/SyncStatusBar.vue deleted file mode 100644 index 74eaab9..0000000 --- a/frontend/src/components/SyncStatusBar.vue +++ /dev/null @@ -1,64 +0,0 @@ - - - diff --git a/frontend/src/stores/marketSync.ts b/frontend/src/stores/marketSync.ts new file mode 100644 index 0000000..e735181 --- /dev/null +++ b/frontend/src/stores/marketSync.ts @@ -0,0 +1,53 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; +import { getScreenerSyncStatus, startScreenerSync } from '@/api/client'; +import type { ScreenerSyncStatus } from '@/api/types'; + +/** 全市场 A 股日线同步(首页手动触发)。状态轮询与选股 store 解耦,独立维护。 */ +export const useMarketSyncStore = defineStore('marketSync', () => { + const syncStatus = ref(null); + const error = ref(null); // 启动同步的请求级错误 + let pollTimer: ReturnType | null = null; + + async function fetchStatus() { + try { + syncStatus.value = await getScreenerSyncStatus(); + } catch { + /* 静默:状态拉取失败不阻塞页面 */ + } + } + + function stopPolling() { + if (pollTimer) { + clearInterval(pollTimer); + pollTimer = null; + } + } + + /** 正在同步时开始 2s 轮询;空闲则停轮询(进入页面时也调用,承接后台遗留任务)。 */ + function pollIfRunning() { + if (syncStatus.value?.running) { + if (!pollTimer) { + pollTimer = setInterval(async () => { + await fetchStatus(); + if (!syncStatus.value?.running) stopPolling(); + }, 2000); + } + } else { + stopPolling(); + } + } + + async function startSync(days = 90) { + error.value = null; + try { + await startScreenerSync({ days }); + await fetchStatus(); + pollIfRunning(); + } catch (e) { + error.value = e instanceof Error ? e.message : '启动同步失败'; + } + } + + return { syncStatus, error, fetchStatus, startSync, stopPolling, pollIfRunning }; +}); \ No newline at end of file diff --git a/frontend/src/stores/screener.ts b/frontend/src/stores/screener.ts index db00723..ae68e0f 100644 --- a/frontend/src/stores/screener.ts +++ b/frontend/src/stores/screener.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia'; import { ref } from 'vue'; -import { getScreenerSyncStatus, runScreener, startScreenerSync } from '@/api/client'; -import type { ScreenConditions, ScreenerRunResponse, ScreenerSyncStatus } from '@/api/types'; +import { runScreener } from '@/api/client'; +import type { ScreenConditions, ScreenerRunResponse } from '@/api/types'; export const useScreenerStore = defineStore('screener', () => { const loading = ref(false); @@ -9,8 +9,6 @@ export const useScreenerStore = defineStore('screener', () => { const note = ref(null); const error = ref(null); const result = ref(null); - const syncStatus = ref(null); - let pollTimer: ReturnType | null = null; async function run(text: string, conditions?: ScreenConditions | null) { loading.value = true; @@ -36,35 +34,5 @@ export const useScreenerStore = defineStore('screener', () => { } } - async function fetchStatus() { - try { - syncStatus.value = await getScreenerSyncStatus(); - } catch { - /* 静默:状态拉取失败不阻塞页面 */ - } - } - - function stopPolling() { - if (pollTimer) { - clearInterval(pollTimer); - pollTimer = null; - } - } - - async function startSync(days = 90) { - error.value = null; - try { - await startScreenerSync({ days }); - await fetchStatus(); - stopPolling(); - pollTimer = setInterval(async () => { - await fetchStatus(); - if (syncStatus.value && !syncStatus.value.running) stopPolling(); - }, 2000); - } catch (e) { - error.value = e instanceof Error ? e.message : '启动同步失败'; - } - } - - return { loading, stage, note, error, result, syncStatus, run, fetchStatus, startSync, stopPolling }; + return { loading, stage, note, error, result, run }; }); diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index 13484a9..8da844b 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -2,6 +2,7 @@ import { RouterLink } from 'vue-router'; import MarketOverview from '@/components/MarketOverview.vue'; +import MarketSyncBar from '@/components/MarketSyncBar.vue'; // 首页:大盘行情总览 + 三大功能入口(看股 / 选股 / 回测) const features = [ @@ -33,6 +34,8 @@ const features = [
+ +
-import { onBeforeUnmount, onMounted, ref } from 'vue'; +import { ref } from 'vue'; import { useScreenerStore } from '@/stores/screener'; import ScreenerForm from '@/components/ScreenerForm.vue'; import ConditionChips from '@/components/ConditionChips.vue'; -import SyncStatusBar from '@/components/SyncStatusBar.vue'; import ScreenerTable from '@/components/ScreenerTable.vue'; import StockDetailOverlay from '@/components/StockDetailOverlay.vue'; const store = useScreenerStore(); const previewCode = ref(null); const formRef = ref | null>(null); - -onMounted(() => { - // 仅查状态;同步由用户点击「同步市场数据」显式触发(避免反复触发接口限频) - store.fetchStatus(); -}); - -onBeforeUnmount(() => store.stopPolling());