From 6e97b550153b343d9ca5ae994e3bd8ce86a6e2d6 Mon Sep 17 00:00:00 2001 From: "Logan.Li" Date: Tue, 27 Oct 2026 09:24:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20SPA=20=E7=9C=9F=E5=AE=9E=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=EF=BC=88/release=E3=80=81/pipelines/:id=20=E7=AD=89?= =?UTF-8?q?=E7=9B=B4=E8=BE=BE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 路由映射:/pipelines 列表、/pipelines/:id 编辑器、/release 批量发布、 /records 发布记录、/gitops、/environments、/credentials、/build-nodes、 /tools、/jenkins-import、/webhooks、/plugins、/settings - 侧边栏/助手跳转同步更新 URL;直接输入/刷新/分享链接恢复对应页面; 未知路径回 /pipelines --- backend/static/index.html | 2 +- frontend/src/App.tsx | 81 +++++++++++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/backend/static/index.html b/backend/static/index.html index fe38bc3..2b7519f 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -3,7 +3,7 @@ FlowStack
-

FlowStack 前端尚未构建

+

FlowStack 发布系统

请在项目根目录执行 ./run.sh build 生成前端产物后重新编译。

diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cac42b0..396f47e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,6 @@ -import { useState, useCallback } from 'react'; +import { useState, useCallback, useEffect } from 'react'; import { Toaster } from 'sonner'; +import { useLocation, useNavigate } from 'react-router'; import { useQuery } from '@tanstack/react-query'; import Sidebar from '@/components/Sidebar'; import TopBar from '@/components/TopBar'; @@ -26,7 +27,42 @@ import { apiClient } from '@/lib/api'; export type ModuleType = 'pipelines' | 'gitops' | 'batch-release' | 'release-records' | 'environments' | 'credentials' | 'build-nodes' | 'tools' | 'jenkins-import' | 'webhooks' | 'plugins' | 'settings'; export type EditorTab = '基本信息' | '流程配置' | '触发设置' | '变量和缓存' | '发布'; +// 路由 ↔ 模块映射(用户可直接访问 /release、/pipelines/1 等) +const ROUTE_TO_MODULE: Record = { + '/': 'pipelines', + '/pipelines': 'pipelines', + '/gitops': 'gitops', + '/release': 'batch-release', + '/records': 'release-records', + '/environments': 'environments', + '/credentials': 'credentials', + '/build-nodes': 'build-nodes', + '/tools': 'tools', + '/jenkins-import': 'jenkins-import', + '/webhooks': 'webhooks', + '/plugins': 'plugins', + '/settings': 'settings', +}; + +const MODULE_TO_ROUTE: Record = { + pipelines: '/pipelines', + gitops: '/gitops', + 'batch-release': '/release', + 'release-records': '/records', + environments: '/environments', + credentials: '/credentials', + 'build-nodes': '/build-nodes', + tools: '/tools', + 'jenkins-import': '/jenkins-import', + webhooks: '/webhooks', + plugins: '/plugins', + settings: '/settings', +}; + export default function App() { + const location = useLocation(); + const navigate = useNavigate(); + // Module navigation const [activeModule, setActiveModule] = useState('pipelines'); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); @@ -38,6 +74,35 @@ export default function App() { const [saveTick, setSaveTick] = useState(0); const [runTick, setRunTick] = useState(0); + // URL → 页面状态(直接输入/刷新/分享链接时恢复) + useEffect(() => { + const path = location.pathname.replace(/\/+$/, '') || '/'; + const editorMatch = path.match(/^\/pipelines\/(\d+)/); + if (editorMatch) { + setActiveModule('pipelines'); + setCurrentPipelineId(Number(editorMatch[1]) || 1); + setPipelineView('editor'); + return; + } + const mod = ROUTE_TO_MODULE[path]; + if (mod) { + setActiveModule(mod); + if (mod !== 'pipelines') { + setPipelineView('list'); + } else if (path === '/pipelines' || path === '/') { + setPipelineView('list'); + } + return; + } + // 未知路径回首页 + navigate('/pipelines', { replace: true }); + }, [location.pathname, navigate]); + + const goToModule = useCallback((m: ModuleType) => { + setActiveModule(m); + navigate(MODULE_TO_ROUTE[m]); + }, [navigate]); + const { data: pipelines = [] } = useQuery({ queryKey: ['pipelines'], queryFn: apiClient.listPipelines, @@ -59,12 +124,14 @@ export default function App() { setCurrentPipelineId(Number(id) || 1); setPipelineView('editor'); setActiveTab('流程配置'); - }, []); + navigate(`/pipelines/${Number(id) || 1}`); + }, [navigate]); const handlePipelineSwitch = useCallback((id: number) => { setCurrentPipelineId(id); setActiveTab('流程配置'); - }, []); + navigate(`/pipelines/${id}`); + }, [navigate]); const handleOnlySave = useCallback(() => { if (activeTab !== '流程配置') setActiveTab('流程配置'); @@ -97,7 +164,7 @@ export default function App() { onSaveAndRun={handleSaveAndRun} isRunning={isPipelineRunning} executionStatus={(latestStatus as 'running' | 'success' | 'failed' | 'cancelled') || 'idle'} - onBack={() => setPipelineView('list')} + onBack={() => { setPipelineView('list'); navigate('/pipelines'); }} />
{activeTab === '流程配置' && ( @@ -139,7 +206,7 @@ export default function App() { case 'jenkins-import': return ; case 'batch-release': - return setActiveModule('release-records')} />; + return goToModule('release-records')} />; case 'release-records': return ; case 'webhooks': @@ -160,14 +227,14 @@ export default function App() { setSidebarCollapsed(!sidebarCollapsed)} />
{renderContent()}
- setActiveModule(m as ModuleType)} /> + goToModule(m as ModuleType)} />
); }