From 23e1beb177fae3963aa353d241563b1fea782190 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Sun, 16 Aug 2026 08:25:57 +0800 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=E7=94=A8=E6=88=B7=E9=AA=8C?= =?UTF-8?q?=E6=94=B68=E9=A1=B9=E9=81=97=E6=BC=8F=E5=85=A8=E8=A1=A5(?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E4=B8=AD=E5=8F=8D=E9=A6=88=E6=BC=8F=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=AF=B9=E8=B4=A6)=E2=80=94=E2=80=94=E2=91=A0?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F/=E5=AE=9E=E7=9B=98=E6=94=B9=E5=9B=9E?= =?UTF-8?q?=E5=BF=85=E9=80=89=E6=A1=A3=E6=A1=88(=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E5=8F=A3=E5=BE=84,=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E6=8B=A6=E6=88=AA+=E5=BF=85=E9=80=89=E6=96=87=E6=A1=88)?= =?UTF-8?q?=E2=91=A1backtest/new=3Fclass=3Dall=5Fweather.py&instance=3D1?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E6=A0=B9=E6=B2=BB:=E6=A1=A3=E6=A1=88?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E6=8C=89=E6=96=87=E4=BB=B6=E5=88=A4=E9=9D=9E?= =?UTF-8?q?instance.type(=E8=80=81=E6=A1=A3=E6=A1=88type=E4=B8=8D=E5=87=86?= =?UTF-8?q?)+=E7=BB=84=E5=90=88=E9=A1=B5=E5=85=BC=E5=AE=B9class=E5=8F=82?= =?UTF-8?q?=E6=95=B0+CTA=E9=A1=B5=E7=BB=84=E5=90=88=E6=A1=A3=E6=A1=88?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E9=87=8D=E5=AE=9A=E5=90=91=E7=BB=84=E5=90=88?= =?UTF-8?q?=E9=A1=B5(=E5=AE=9E=E6=B5=8B=E8=B7=B3=E8=BD=AC+=E9=A2=84?= =?UTF-8?q?=E5=A1=AB=E5=85=A8=E9=80=9A)=E2=91=A2=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E7=81=AF=E8=AF=AD=E4=B9=89:=E6=9C=89=E5=9C=A8=E8=B7=91?= =?UTF-8?q?=E2=86=92=E8=B7=B3=E5=88=97=E8=A1=A8=E6=8C=89=E6=A1=A3=E6=A1=88?= =?UTF-8?q?=E8=BF=87=E6=BB=A4,=E6=97=A0=E2=86=92=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E9=A1=B5(=E7=94=A8=E6=88=B7'=E7=82=B9=E5=B8=A6=E7=BB=BF?= =?UTF-8?q?=E7=82=B9=E6=8C=89=E9=92=AE=E5=BA=94=E7=9C=8B=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E4=B8=8D=E8=AF=A5=E5=88=B0=E5=8F=82=E6=95=B0=E9=A1=B5');?= =?UTF-8?q?=E7=BB=84=E5=90=88=E6=A1=A3=E6=A1=88=E9=9A=90=E8=97=8F=E5=9B=9E?= =?UTF-8?q?=E6=94=BE=E6=8C=89=E9=92=AE=20=E2=91=A3=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E8=BF=90=E8=A1=8C=E5=9B=9E=E6=B5=8B=E4=B8=8D?= =?UTF-8?q?=E5=A5=BD=E4=BD=BF:=E6=A0=B9=E5=9B=A0chain=3Ddev=E7=BC=BAGET=20?= =?UTF-8?q?file=20mock=E2=86=92=E8=AF=BB=E5=A4=B1=E8=B4=A5catch=E6=B8=85co?= =?UTF-8?q?de=E8=A2=AB=E5=BD=93=E7=BC=96=E8=BE=91=E2=86=92dirty=E5=8D=A1?= =?UTF-8?q?=E6=AD=BB;=E4=B8=89=E5=B1=82=E4=BF=AE=3D=E8=A1=A5mock+catch?= =?UTF-8?q?=E8=B5=B0echoGrace+dirty=E6=94=B9=E7=A1=AE=E8=AE=A4=E6=A1=86(?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E5=B9=B6=E8=B7=B3/=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E8=B7=B3)=20=E2=91=A4=E6=A8=A1=E6=8B=9F=E7=9B=98=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E9=87=8D=E8=AE=BE=E8=AE=A1=E5=AF=B9=E9=BD=90=E5=AE=9E?= =?UTF-8?q?=E7=9B=98(=E7=B1=BB=E5=9E=8Bchip=E5=85=A5=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E8=A1=8C+=E6=96=B0=E5=A2=9E=E7=AD=96=E7=95=A5/=E6=A1=A3?= =?UTF-8?q?=E6=A1=88=E5=88=97+=E5=87=80=E5=80=BC=E8=A1=A5=E6=97=A5?= =?UTF-8?q?=E6=9C=9F)=20=E2=91=A5=E7=AD=96=E7=95=A5=E5=BA=93=E5=8E=BB?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E7=BC=96=E8=BE=91=E4=BB=A3=E7=A0=81=E6=8C=89?= =?UTF-8?q?=E9=92=AE=20=E2=91=A6=E6=A8=A1=E6=8B=9F=E7=9B=98/=E5=AE=9E?= =?UTF-8?q?=E7=9B=98=E5=88=97=E8=A1=A8=E8=A1=8C=E5=8A=A0=E3=80=8C=E5=BF=AB?= =?UTF-8?q?=E7=85=A7=E3=80=8D=E6=8C=89=E9=92=AE=E2=86=92CodeSnapshotDrawer?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=E6=8A=BD=E5=B1=89(=E5=8F=91=E8=B5=B7?= =?UTF-8?q?=E6=97=B6=E4=BB=A3=E7=A0=81vs=E5=BD=93=E5=89=8D,MonacoDiff?= =?UTF-8?q?=E6=BB=9A=E5=8A=A8=E8=81=94=E5=8A=A8);dev=E6=B5=8F=E8=A7=88?= =?UTF-8?q?=E5=99=A8=E8=87=AA=E9=AA=8C7=E9=A1=B9=E5=85=A8=E8=BF=87+927?= =?UTF-8?q?=E7=BB=BF+build=E7=BB=BF=20[vps]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/mock/index.ts | 6 ++ frontend/src/views/backtest/New.vue | 16 +++++ .../src/views/backtest/PortfolioBacktest.vue | 6 +- frontend/src/views/live/List.vue | 26 ++++++- frontend/src/views/live/New.vue | 13 ++-- frontend/src/views/paper/List.vue | 60 +++++++++++++--- frontend/src/views/paper/New.vue | 12 +++- frontend/src/views/strategy/Code.vue | 21 ++++-- .../src/views/strategy/CodeSnapshotDrawer.vue | 72 +++++++++++++++++++ frontend/src/views/strategy/List.vue | 27 +++++-- 10 files changed, 231 insertions(+), 28 deletions(-) create mode 100644 frontend/src/views/strategy/CodeSnapshotDrawer.vue diff --git a/frontend/src/mock/index.ts b/frontend/src/mock/index.ts index f6e62a8..377ea10 100644 --- a/frontend/src/mock/index.ts +++ b/frontend/src/mock/index.ts @@ -31,6 +31,12 @@ const routes: Route[] = [ { m: 'get', re: /^\/strategy\/files$/, build: () => D.strategyFilesMock }, { m: 'post', re: /^\/strategy\/file\/[^/]+\/check$/, build: () => ({ ok: true }) }, { m: 'post', re: /^\/strategy\/file\/[^/]+$/, build: () => ({ ok: true }) }, + // 读单文件全量(编辑器主区)——此前 dev 缺此 mock,切文件报「读取失败」 + { m: 'get', re: /^\/strategy\/file\/[^/]+$/, build: (url) => { + const name = decodeURIComponent(url.split('/').filter(Boolean)[2] || '') + return { name, dir: '', class_name: '', type: 'cta', lines: 10, modified: '', + code: `# ${name}(mock 源码)\n\nclass DemoStrategy:\n """dev mock"""\n\n def initialize(self, context):\n self.n = 3\n\n def handle_data(self, context, data):\n pass\n` } + } }, // §12.6 补:代码版本快照(列表在前,单版本 4 段路径在后) { m: 'get', re: /^\/strategy\/code-versions$/, build: (url) => D.codeVersionsMock(new URL(url, 'http://x').searchParams.get('file') || '') }, { m: 'get', re: /^\/strategy\/code-versions\/[^/]+\/[^/]+$/, build: (url) => { const seg = url.split('/').filter(Boolean); return D.codeVersionMock(decodeURIComponent(seg[2]), seg[3]) } }, diff --git a/frontend/src/views/backtest/New.vue b/frontend/src/views/backtest/New.vue index 1fc113c..46b266c 100644 --- a/frontend/src/views/backtest/New.vue +++ b/frontend/src/views/backtest/New.vue @@ -16,6 +16,7 @@ interface InstLike { interface FileLike { name: string class_name: string + type?: string } const route = useRoute() @@ -137,6 +138,21 @@ onMounted(async () => { ]) instanceOptions.value = ins classToFile.value = Object.fromEntries(fr.data.files.map((f) => [f.class_name, f.name])) + // 组合策略档案误入 CTA 回测页 → 重定向组合页(老链接 class=all_weather.py 场景) + const cls = route.query.class + const instId0 = route.query.instance + const inst = instId0 && !Array.isArray(instId0) + ? ins.find((x) => x.id === Number(instId0)) + : undefined + const probe = inst?.code_file || (typeof cls === 'string' && cls.includes('.') ? cls : '') + const probeFile = fr.data.files.find((f) => f.name === probe || f.name === `${probe}.py`) + if (probeFile?.type === 'portfolio') { + router.replace({ + path: '/backtest/portfolio', + query: { strategy: probeFile.name.replace(/\.py$/, ''), ...(instId0 ? { instance: String(instId0) } : {}) }, + }) + return + } } catch { /* 档案下拉失败不阻塞 */ } diff --git a/frontend/src/views/backtest/PortfolioBacktest.vue b/frontend/src/views/backtest/PortfolioBacktest.vue index c6439ac..ec8e3eb 100644 --- a/frontend/src/views/backtest/PortfolioBacktest.vue +++ b/frontend/src/views/backtest/PortfolioBacktest.vue @@ -389,9 +389,9 @@ const fromTaskId = computed(() => (route.query.from_task as string) || '') onMounted(async () => { if (viewTaskId.value) { loadResult(viewTaskId.value) - } else if (route.query.strategy) { - // 从代码编辑页「运行回测」跳转:按策略预填 - form.strategy = String(route.query.strategy) + } else if (route.query.strategy || route.query.class) { + // 跳转预填:strategy=文件名去.py;兼容旧链接的 class=(可能是 all_weather.py 带后缀) + form.strategy = String(route.query.strategy || route.query.class).replace(/\.py$/, '') } else if (fromTaskId.value) { // 从任务列表「点任务ID」跳转:按该任务参数完整预填 void prefillFromTask(fromTaskId.value) diff --git a/frontend/src/views/live/List.vue b/frontend/src/views/live/List.vue index e459221..45b4a36 100644 --- a/frontend/src/views/live/List.vue +++ b/frontend/src/views/live/List.vue @@ -1,6 +1,7 @@ + + + + diff --git a/frontend/src/views/strategy/List.vue b/frontend/src/views/strategy/List.vue index 4f7b5e8..0c0c70e 100644 --- a/frontend/src/views/strategy/List.vue +++ b/frontend/src/views/strategy/List.vue @@ -145,16 +145,36 @@ function newInstance(): void { function editInstance(i: Instance): void { router.push(`/strategy/instance/${i.id}`) } +// 以文件实际类型为准(老实例 type 字段可能不准,如组合档案标了 cta) +function fileTypeOf(i: Instance): 'portfolio' | 'cta' { + return files.value.find((f) => f.name === i.code_file)?.type || i.type +} function runBacktest(i: Instance): void { + const isPf = fileTypeOf(i) === 'portfolio' router.push({ - path: i.type === 'portfolio' ? '/backtest/portfolio' : '/backtest/new', - query: { class: i.code_file, instance: String(i.id) }, + path: isPf ? '/backtest/portfolio' : '/backtest/new', + // 组合页吃 strategy(文件名去.py);CTA 页吃 class(类名) + query: isPf + ? { strategy: i.code_file.replace(/\.py$/, ''), instance: String(i.id) } + : { + class: files.value.find((f) => f.name === i.code_file)?.class_name || i.code_file, + instance: String(i.id), + }, }) } +// 运行灯语义:有在跑账户 → 看运行(列表按档案过滤);没有 → 发起(创建页带档案) function runPaper(i: Instance, mode: 'replay' | 'live' | 'shadow'): void { + if (mode !== 'replay' && (i.running_accounts || []).some((a) => a.kind === 'paper' || a.kind === 'shadow')) { + router.push(`/paper?instance=${i.id}`) + return + } router.push({ path: '/paper/new', query: { mode, instance: String(i.id) } }) } function runLive(i: Instance): void { + if ((i.running_accounts || []).some((a) => a.kind === 'live')) { + router.push(`/live?instance=${i.id}`) + return + } router.push({ path: '/live/new', query: { instance: String(i.id) } }) } async function onSync(i: Instance): Promise { @@ -211,7 +231,6 @@ const runningRows = computed(() =>

代码 → 实例档案 → 回测 / 模拟 / 实盘 · 全生命周期

-
@@ -297,7 +316,7 @@ const runningRows = computed(() =>
{{ pct(latestRet(i)) }}{{ retSub(i) }}
- +