From 2735ff1fd9e063270529539db9d51526e7a97c57 Mon Sep 17 00:00:00 2001 From: cirry <812852553@qq.com> Date: Wed, 2 Sep 2026 23:10:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=85=A5=E5=8F=A3=E7=A7=BB?= =?UTF-8?q?=E8=87=B3=E9=A6=96=E9=A1=B5=EF=BC=9AMarketSyncBar=20=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=90=8C=E6=AD=A5A=E8=82=A1=E5=BD=93=E6=97=A5?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=B9=B6=E5=B1=95=E7=A4=BA=E6=9C=80=E6=96=B0?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=97=A5=E6=9C=9F=EF=BC=9B=E9=80=89=E8=82=A1?= =?UTF-8?q?=E9=A1=B5=E7=A7=BB=E9=99=A4=E6=95=B0=E6=8D=AE=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=9D=A1=E4=B8=8E=E5=90=8C=E6=AD=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/MarketSyncBar.vue | 76 +++++++++++++++++++++++ frontend/src/components/SyncStatusBar.vue | 64 ------------------- frontend/src/stores/marketSync.ts | 53 ++++++++++++++++ frontend/src/stores/screener.ts | 38 +----------- frontend/src/views/HomeView.vue | 3 + frontend/src/views/ScreenerView.vue | 12 +--- 6 files changed, 136 insertions(+), 110 deletions(-) create mode 100644 frontend/src/components/MarketSyncBar.vue delete mode 100644 frontend/src/components/SyncStatusBar.vue create mode 100644 frontend/src/stores/marketSync.ts 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 @@ + + + + + + + + + + + + + + A股日线数据更新至 + {{ fmtDate(latestDate) }} + + + + + + + + + + {{ store.syncStatus?.step || '同步中…' }}({{ store.syncStatus?.done_days }}/{{ store.syncStatus?.total_days }}) + + + + + + + + + + 同步A股当日数据 + + + + + + {{ errText }} + + + \ 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 @@ - - - - - - - - - - - - - - - - {{ freshness.text }} - - - - - - {{ status.step || '同步中…' }}({{ status.done_days }}/{{ status.total_days }}) - - - - - - - - - - 同步市场数据 - - - - - - {{ status.error }} - - - 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()); - - {{ store.error }}