fix(web-ui): 参数寻优「查看」用寻优时的标的快照,避免输入框/路由切换污染 URL

- OptimizeView「查看」拼 URL 前从输入框实时值取 code/startDate/endDate/category,
  导致两个问题:(1) 寻优后改输入框代码再点查看,URL 指向没回测过的标的;
  (2) 切走再回 /optimize,组件 ref 丢失,URL 里 symbol/startDate/endDate 全空。
- 将标的上下文快照(取行情成功那一刻冻结)下沉到 Pinia store 的 optimizeContext,
  与 optimizeResult/optimizeAllResult 同生命周期持久化,跨路由保留。
- buildBacktestQuery 改读 store.optimizeContext,输入框改动与组件卸载都不再污染。
This commit is contained in:
Justin Gu
2026-07-05 18:03:12 +08:00
parent 173069d518
commit 6f121dc156
2 changed files with 42 additions and 6 deletions
+18
View File
@@ -18,6 +18,7 @@ import type {
BacktestRequest, BacktestRequest,
BacktestResult, BacktestResult,
Bar, Bar,
Category,
MultiStrategyBacktestRequest, MultiStrategyBacktestRequest,
PortfolioBacktestRequest, PortfolioBacktestRequest,
PortfolioResult, PortfolioResult,
@@ -162,6 +163,21 @@ export const useBacktestStore = defineStore('backtest', () => {
const optimizeResult = ref<OptimizeResult | null>(null) const optimizeResult = ref<OptimizeResult | null>(null)
const optimizeRunning = ref(false) 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) { async function runOptimize(req: OptimizeBacktestRequest) {
optimizeRunning.value = true optimizeRunning.value = true
@@ -240,6 +256,7 @@ export const useBacktestStore = defineStore('backtest', () => {
multiStrategyRunning, multiStrategyRunning,
optimizeResult, optimizeResult,
optimizeRunning, optimizeRunning,
optimizeContext,
optimizeAllResult, optimizeAllResult,
optimizeAllRunning, optimizeAllRunning,
// getters // getters
@@ -255,5 +272,6 @@ export const useBacktestStore = defineStore('backtest', () => {
clearMultiStrategy, clearMultiStrategy,
runOptimize, runOptimize,
runOptimizeAll, runOptimizeAll,
setOptimizeContext,
} }
}) })
+24 -6
View File
@@ -66,6 +66,13 @@ async function onRun() {
// 1. 先取行情 // 1. 先取行情
const ok = await symbolPicker.value?.loadBars() const ok = await symbolPicker.value?.loadBars()
if (!ok) return if (!ok) return
// 取行情成功 → 冻结本次寻优真正使用的标的上下文,供「查看」拼 URL(存 store,跨路由保留)
store.setOptimizeContext({
code: code.value,
category: category.value,
startDate: startDate.value,
endDate: endDate.value,
})
// 2. 校验寻优参数 // 2. 校验寻优参数
if (Object.keys(paramGrid.value).length === 0) { if (Object.keys(paramGrid.value).length === 0) {
store.error = '请勾选至少 1 个参数并填入取值' store.error = '请勾选至少 1 个参数并填入取值'
@@ -91,6 +98,13 @@ async function onRunAll() {
// 1. 先取行情 // 1. 先取行情
const ok = await symbolPicker.value?.loadBars() const ok = await symbolPicker.value?.loadBars()
if (!ok) return if (!ok) return
// 取行情成功 → 冻结本次寻优真正使用的标的上下文,供「查看」拼 URL(存 store,跨路由保留)
store.setOptimizeContext({
code: code.value,
category: category.value,
startDate: startDate.value,
endDate: endDate.value,
})
// 2. 一键寻优 // 2. 一键寻优
await store.runOptimizeAll({ await store.runOptimizeAll({
cash: cash.value, cash: cash.value,
@@ -99,16 +113,20 @@ async function onRunAll() {
}) })
} }
/** 「查看」跳转时,把当前标的 + 周期 + 日期范围一并塞进 query, /** 「查看」跳转时,把本次寻优实际使用的标的 + 周期 + 日期范围一并塞进 query,
* 让回测页能完整复现寻优时的行情(而非只带策略参数)。 */ * 让回测页能完整复现寻优时的行情(而非只带策略参数)。
* 注意:必须读 store.optimizeContext(取行情成功时冻结),不能读输入框实时值 ——
* 否则寻优完成后改了输入框代码,「查看」URL 会被污染,指向一个根本没被回测过的标的;
* 且不能读组件 ref,否则切走再回 /optimize 时排名还在但上下文已丢失。 */
function buildBacktestQuery(strategyName: string, params: Record<string, number | string>) { function buildBacktestQuery(strategyName: string, params: Record<string, number | string>) {
const ctx = store.optimizeContext
return { return {
strategy: strategyName, strategy: strategyName,
params: JSON.stringify(params), params: JSON.stringify(params),
symbol: code.value, symbol: ctx?.code ?? '',
startDate: startDate.value, startDate: ctx?.startDate ?? '',
endDate: endDate.value, endDate: ctx?.endDate ?? '',
category: category.value, category: ctx?.category ?? category.value,
} }
} }