feat: SPA 真实路由(/release、/pipelines/:id 等直达)
- 路由映射:/pipelines 列表、/pipelines/:id 编辑器、/release 批量发布、 /records 发布记录、/gitops、/environments、/credentials、/build-nodes、 /tools、/jenkins-import、/webhooks、/plugins、/settings - 侧边栏/助手跳转同步更新 URL;直接输入/刷新/分享链接恢复对应页面; 未知路径回 /pipelines
This commit is contained in:
1 parent
a3b4637e19
commit
6e97b55015
2 files changed
+75
-8
No files matched your search
+74
-7
@@ -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<string, ModuleType> = {
|
||||
'/': '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<ModuleType, string> = {
|
||||
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<ModuleType>('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'); }}
|
||||
/>
|
||||
<div className="flex-1 flex overflow-hidden">
|
||||
{activeTab === '流程配置' && (
|
||||
@@ -139,7 +206,7 @@ export default function App() {
|
||||
case 'jenkins-import':
|
||||
return <JenkinsImport />;
|
||||
case 'batch-release':
|
||||
return <BatchRelease onViewRecords={() => setActiveModule('release-records')} />;
|
||||
return <BatchRelease onViewRecords={() => goToModule('release-records')} />;
|
||||
case 'release-records':
|
||||
return <ReleaseRecords />;
|
||||
case 'webhooks':
|
||||
@@ -160,14 +227,14 @@ export default function App() {
|
||||
<Toaster position="top-right" richColors />
|
||||
<Sidebar
|
||||
activeModule={activeModule}
|
||||
onModuleChange={setActiveModule}
|
||||
onModuleChange={goToModule}
|
||||
collapsed={sidebarCollapsed}
|
||||
onToggleCollapse={() => setSidebarCollapsed(!sidebarCollapsed)}
|
||||
/>
|
||||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
||||
{renderContent()}
|
||||
</div>
|
||||
<AiAssistant onNavigate={(m) => setActiveModule(m as ModuleType)} />
|
||||
<AiAssistant onNavigate={(m) => goToModule(m as ModuleType)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user