From 6f121dc156f05df6db73c0b2f8974b8430df2f27 Mon Sep 17 00:00:00 2001 From: Justin Gu <97915@qq.com> Date: Sun, 5 Jul 2026 18:03:12 +0800 Subject: [PATCH] =?UTF-8?q?fix(web-ui):=20=E5=8F=82=E6=95=B0=E5=AF=BB?= =?UTF-8?q?=E4=BC=98=E3=80=8C=E6=9F=A5=E7=9C=8B=E3=80=8D=E7=94=A8=E5=AF=BB?= =?UTF-8?q?=E4=BC=98=E6=97=B6=E7=9A=84=E6=A0=87=E7=9A=84=E5=BF=AB=E7=85=A7?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E8=BE=93=E5=85=A5=E6=A1=86/?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=88=87=E6=8D=A2=E6=B1=A1=E6=9F=93=20URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OptimizeView「查看」拼 URL 前从输入框实时值取 code/startDate/endDate/category, 导致两个问题:(1) 寻优后改输入框代码再点查看,URL 指向没回测过的标的; (2) 切走再回 /optimize,组件 ref 丢失,URL 里 symbol/startDate/endDate 全空。 - 将标的上下文快照(取行情成功那一刻冻结)下沉到 Pinia store 的 optimizeContext, 与 optimizeResult/optimizeAllResult 同生命周期持久化,跨路由保留。 - buildBacktestQuery 改读 store.optimizeContext,输入框改动与组件卸载都不再污染。 --- web-ui/src/stores/backtest.ts | 18 ++++++++++++++++++ web-ui/src/views/OptimizeView.vue | 30 ++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/web-ui/src/stores/backtest.ts b/web-ui/src/stores/backtest.ts index 1e7f182..2fec6fc 100644 --- a/web-ui/src/stores/backtest.ts +++ b/web-ui/src/stores/backtest.ts @@ -18,6 +18,7 @@ import type { BacktestRequest, BacktestResult, Bar, + Category, MultiStrategyBacktestRequest, PortfolioBacktestRequest, PortfolioResult, @@ -162,6 +163,21 @@ export const useBacktestStore = defineStore('backtest', () => { const optimizeResult = ref(null) const optimizeRunning = ref(false) + /** 寻优实际使用的标的上下文(取行情成功那一刻冻结)。 + * 放在 store 而非组件里,是为了在用户切走再回 /optimize 时,「查看」按钮仍能拼出正确 URL + * —— 组件 ref 在卸载后丢失,而 store 与 optimizeResult 同生命周期保留。 + * 由 OptimizeView 在 loadBars() 成功后通过 setOptimizeContext() 写入。 */ + const optimizeContext = ref<{ + code: string + category: Category + startDate: string + endDate: string + } | null>(null) + + function setOptimizeContext(ctx: { code: string; category: Category; startDate: string; endDate: string }) { + optimizeContext.value = ctx + } + /** 提交寻优后台任务并轮询直到完成。 */ async function runOptimize(req: OptimizeBacktestRequest) { optimizeRunning.value = true @@ -240,6 +256,7 @@ export const useBacktestStore = defineStore('backtest', () => { multiStrategyRunning, optimizeResult, optimizeRunning, + optimizeContext, optimizeAllResult, optimizeAllRunning, // getters @@ -255,5 +272,6 @@ export const useBacktestStore = defineStore('backtest', () => { clearMultiStrategy, runOptimize, runOptimizeAll, + setOptimizeContext, } }) diff --git a/web-ui/src/views/OptimizeView.vue b/web-ui/src/views/OptimizeView.vue index 96042d6..760aaf0 100644 --- a/web-ui/src/views/OptimizeView.vue +++ b/web-ui/src/views/OptimizeView.vue @@ -66,6 +66,13 @@ async function onRun() { // 1. 先取行情 const ok = await symbolPicker.value?.loadBars() if (!ok) return + // 取行情成功 → 冻结本次寻优真正使用的标的上下文,供「查看」拼 URL(存 store,跨路由保留) + store.setOptimizeContext({ + code: code.value, + category: category.value, + startDate: startDate.value, + endDate: endDate.value, + }) // 2. 校验寻优参数 if (Object.keys(paramGrid.value).length === 0) { store.error = '请勾选至少 1 个参数并填入取值' @@ -91,6 +98,13 @@ async function onRunAll() { // 1. 先取行情 const ok = await symbolPicker.value?.loadBars() if (!ok) return + // 取行情成功 → 冻结本次寻优真正使用的标的上下文,供「查看」拼 URL(存 store,跨路由保留) + store.setOptimizeContext({ + code: code.value, + category: category.value, + startDate: startDate.value, + endDate: endDate.value, + }) // 2. 一键寻优 await store.runOptimizeAll({ cash: cash.value, @@ -99,16 +113,20 @@ async function onRunAll() { }) } -/** 「查看」跳转时,把当前标的 + 周期 + 日期范围一并塞进 query, - * 让回测页能完整复现寻优时的行情(而非只带策略参数)。 */ +/** 「查看」跳转时,把本次寻优实际使用的标的 + 周期 + 日期范围一并塞进 query, + * 让回测页能完整复现寻优时的行情(而非只带策略参数)。 + * 注意:必须读 store.optimizeContext(取行情成功时冻结),不能读输入框实时值 —— + * 否则寻优完成后改了输入框代码,「查看」URL 会被污染,指向一个根本没被回测过的标的; + * 且不能读组件 ref,否则切走再回 /optimize 时排名还在但上下文已丢失。 */ function buildBacktestQuery(strategyName: string, params: Record) { + const ctx = store.optimizeContext return { strategy: strategyName, params: JSON.stringify(params), - symbol: code.value, - startDate: startDate.value, - endDate: endDate.value, - category: category.value, + symbol: ctx?.code ?? '', + startDate: ctx?.startDate ?? '', + endDate: ctx?.endDate ?? '', + category: ctx?.category ?? category.value, } }