Files
ittoview/src/pages/ProcessRoadmapPage.tsx
史悦 409e388403 feat: 新增流程总览图页面及导航功能
添加流程总览图页面,包含五组十域可交互SVG流程图,支持模块点击跳转至对应流程详情页。同时在侧边栏和首页添加导航入口,优化流程详情页的返回逻辑和布局样式。
2026-02-06 10:59:26 +08:00

362 lines
19 KiB
TypeScript

import { useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { processes } from '@/data'
interface Hotspot {
id: string
label: string
x: number
y: number
w: number
h: number
to: string
}
const svgStyles = `
.text { font-family: "Microsoft YaHei", "PingFang SC", sans-serif; font-size: 11px; fill: #333; text-anchor: middle; }
.group-title { font-size: 16px; font-weight: bold; }
.box { stroke-width: 1; rx: 4; ry: 4; }
.box-init { fill: #FFF9E6; stroke: #FAD266; }
.box-plan { fill: #EBF5FF; stroke: #91C7F2; }
.box-exec { fill: #F0F9EB; stroke: #C2E7B0; }
.box-moni { fill: #FFF9E6; stroke: #FAD266; }
.box-close { fill: #EBF5FF; stroke: #91C7F2; }
.box-red { fill: #FF0000; stroke: #CC0000; }
.text-white { fill: #FFFFFF; }
.arrow { stroke: #FF0000; stroke-width: 2; fill: none; }
.arrow-grey { stroke: #CCC; stroke-width: 1.5; fill: none; }
.marker { fill: #FF0000; }
.marker-grey { fill: #CCC; }
`
const staticSvgBody = `
<!-- 1. 启动过程组 -->
<rect x="20" y="20" width="100" height="350" class="box box-init" />
<rect x="30" y="30" width="80" height="40" class="box box-init" />
<text x="70" y="55" class="text group-title">启动</text>
<rect x="30" y="85" width="80" height="40" class="box" style="fill:white; stroke:#EEE;" />
<text x="70" y="110" class="text">10.1识别干系人</text>
<rect x="30" y="140" width="80" height="30" class="box box-red" />
<text x="70" y="159" class="text text-white">1.1制定项目章程</text>
<!-- 2. 规划过程组 -->
<rect x="130" y="20" width="450" height="350" class="box box-plan" />
<rect x="140" y="30" width="80" height="40" class="box" style="fill:#A0B7CC; stroke:none;" />
<text x="180" y="55" class="text group-title" style="fill:white;">规划</text>
<!-- 规划左侧列表 -->
<rect x="140" y="80" width="100" height="230" class="box" style="fill:#B8D5EB; stroke:none;" />
<text x="190" y="100" class="text">2.1规划范围管理</text>
<text x="190" y="123" class="text">3.1规划进度管理</text>
<text x="190" y="146" class="text">4.1规划成本管理</text>
<text x="190" y="169" class="text">5.1规划质量管理</text>
<text x="190" y="192" class="text">6.1规划资源管理</text>
<text x="190" y="215" class="text">7.1规划沟通管理</text>
<text x="190" y="238" class="text">8.1规划风险管理</text>
<text x="190" y="261" class="text">9.1规划采购管理</text>
<text x="190" y="284" class="text">10.2规划干系人</text>
<!-- 规划中部流程 -->
<rect x="250" y="30" width="70" height="30" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="285" y="49" class="text">2.2收集需求</text>
<path d="M320,45 L345,45" class="arrow-grey" marker-end="url(#arrowhead-grey)" />
<rect x="350" y="30" width="70" height="30" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="385" y="49" class="text">2.3定义范围</text>
<path d="M420,45 L445,45" class="arrow-grey" marker-end="url(#arrowhead-grey)" />
<rect x="450" y="30" width="70" height="30" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="485" y="49" class="text">2.4创建WBS</text>
<path d="M485,60 L485,85" class="arrow" marker-end="url(#arrowhead)" />
<rect x="250" y="90" width="70" height="35" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="285" y="102" class="text">6.2估算活动</text>
<text x="285" y="117" class="text">资源</text>
<path d="M285,125 L285,145" class="arrow" marker-end="url(#arrowhead)" />
<rect x="350" y="90" width="70" height="35" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="385" y="102" class="text">3.3排列活动</text>
<text x="385" y="117" class="text">顺序</text>
<path d="M350,107 L325,107" class="arrow-grey" marker-end="url(#arrowhead-grey)" />
<rect x="450" y="90" width="70" height="35" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="485" y="110" class="text">3.2定义活动</text>
<path d="M450,107 L425,107" class="arrow-grey" marker-end="url(#arrowhead-grey)" />
<rect x="250" y="150" width="70" height="35" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="285" y="162" class="text">3.4估算活动</text>
<text x="285" y="177" class="text">持续时间</text>
<path d="M320,167 L345,167" class="arrow-grey" marker-end="url(#arrowhead-grey)" />
<rect x="350" y="150" width="70" height="35" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="385" y="162" class="text">3.5制定进度</text>
<text x="385" y="177" class="text">计划</text>
<path d="M420,167 L445,167" class="arrow-grey" marker-end="url(#arrowhead-grey)" />
<rect x="450" y="150" width="70" height="35" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="485" y="167" class="text">4.2估算成本</text>
<path d="M485,185 L485,205" class="arrow" marker-end="url(#arrowhead)" />
<rect x="450" y="210" width="70" height="30" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="485" y="229" class="text">4.3制定预算</text>
<rect x="250" y="235" width="70" height="30" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="285" y="254" class="text">8.2识别风险</text>
<path d="M320,250 L345,250" class="arrow" marker-end="url(#arrowhead)" />
<rect x="350" y="235" width="70" height="35" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="385" y="247" class="text">8.3实施定性</text>
<text x="385" y="262" class="text">风险分析</text>
<path d="M420,250 L445,250" class="arrow" marker-end="url(#arrowhead)" />
<rect x="450" y="235" width="70" height="35" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="485" y="247" class="text">8.4实施定量</text>
<text x="485" y="262" class="text">风险分析</text>
<path d="M485,270 L485,285" class="arrow" marker-end="url(#arrowhead)" />
<rect x="450" y="290" width="70" height="35" class="box" style="fill:#C7E1F5; stroke:none;" />
<text x="485" y="302" class="text">8.5规划风险</text>
<text x="485" y="317" class="text">应对</text>
<rect x="140" y="325" width="430" height="30" class="box box-red" />
<text x="355" y="344" class="text text-white">1.2 制定项目管理计划</text>
<!-- 3. 执行过程组 -->
<rect x="590" y="20" width="220" height="350" class="box box-exec" />
<rect x="660" y="30" width="80" height="40" class="box" style="fill:#A5C294; stroke:none;" />
<text x="700" y="55" class="text group-title" style="fill:white;">执行</text>
<rect x="600" y="35" width="50" height="30" class="box" style="fill:#E2F0D9; stroke:none;" />
<text x="625" y="54" class="text" style="font-size:9px">6.3获取资源</text>
<path d="M625,65 L625,85" class="arrow" marker-end="url(#arrowhead)" />
<rect x="600" y="90" width="70" height="30" class="box" style="fill:#E2F0D9; stroke:none;" />
<text x="635" y="109" class="text">6.4建设团队</text>
<path d="M670,105 L695,105" class="arrow" marker-end="url(#arrowhead)" />
<rect x="700" y="90" width="70" height="30" class="box" style="fill:#E2F0D9; stroke:none;" />
<text x="735" y="109" class="text">6.5管理团队</text>
<rect x="600" y="140" width="70" height="35" class="box" style="fill:#E2F0D9; stroke:none;" />
<text x="635" y="152" class="text">8.6实施风险</text>
<text x="635" y="167" class="text">应对</text>
<rect x="700" y="140" width="70" height="30" class="box" style="fill:#E2F0D9; stroke:none;" />
<text x="735" y="159" class="text">9.2实施采购</text>
<rect x="600" y="190" width="70" height="30" class="box" style="fill:#E2F0D9; stroke:none;" />
<text x="635" y="209" class="text">7.2管理沟通</text>
<rect x="700" y="190" width="70" height="35" class="box" style="fill:#E2F0D9; stroke:none;" />
<text x="735" y="202" class="text">10.3干系人</text>
<text x="735" y="217" class="text">参与管理</text>
<rect x="600" y="245" width="200" height="30" class="box" style="fill:#E2F0D9; stroke:none;" />
<text x="700" y="264" class="text">5.2管理质量</text>
<rect x="600" y="285" width="80" height="35" class="box box-red" />
<text x="640" y="297" class="text text-white">1.4 管理项目</text>
<text x="640" y="312" class="text text-white">知识</text>
<rect x="600" y="325" width="200" height="30" class="box box-red" />
<text x="700" y="344" class="text text-white">1.3 指导与管理项目工作</text>
<!-- 4. 监控过程组 -->
<rect x="130" y="380" width="530" height="150" class="box box-moni" />
<rect x="140" y="470" width="80" height="40" class="box" style="fill:#FCDD8B; stroke:none;" />
<text x="180" y="495" class="text group-title">监控</text>
<g transform="translate(230, 390)">
<rect x="0" y="0" width="75" height="30" class="box" style="fill:#FFF2CC; stroke:none;" />
<text x="37" y="19" class="text">2.6控制范围</text>
<rect x="85" y="0" width="75" height="30" class="box" style="fill:#FFF2CC; stroke:none;" />
<text x="122" y="19" class="text">6.6控制资源</text>
<rect x="170" y="0" width="75" height="30" class="box" style="fill:#FFF2CC; stroke:none;" />
<text x="207" y="19" class="text">9.3控制采购</text>
<rect x="255" y="0" width="75" height="30" class="box" style="fill:#FFF2CC; stroke:none;" />
<text x="292" y="19" class="text">5.3控制质量</text>
<rect x="0" y="45" width="75" height="30" class="box" style="fill:#FFF2CC; stroke:none;" />
<text x="37" y="64" class="text">3.6控制进度</text>
<rect x="85" y="45" width="75" height="30" class="box" style="fill:#FFF2CC; stroke:none;" />
<text x="122" y="64" class="text">7.3监督沟通</text>
<rect x="170" y="45" width="85" height="35" class="box" style="fill:#FFF2CC; stroke:none;" />
<text x="212" y="57" class="text">10.4监督</text>
<text x="212" y="72" class="text">干系人参与</text>
<rect x="85" y="95" width="75" height="30" class="box" style="fill:#FFF2CC; stroke:none;" />
<text x="122" y="114" class="text">8.7监督风险</text>
<rect x="170" y="95" width="75" height="30" class="box" style="fill:#FFF2CC; stroke:none;" />
<text x="207" y="114" class="text">4.4控制成本</text>
<rect x="255" y="95" width="75" height="30" class="box" style="fill:#FFF2CC; stroke:none;" />
<text x="292" y="114" class="text">2.5确认范围</text>
</g>
<rect x="575" y="400" width="75" height="35" class="box box-red" />
<text x="612" y="412" class="text text-white">1.5监控项目</text>
<text x="612" y="427" class="text text-white">工作</text>
<rect x="575" y="445" width="75" height="35" class="box box-red" />
<text x="612" y="457" class="text text-white">1.6实施整体</text>
<text x="612" y="472" class="text text-white">变更控制</text>
<!-- 5. 收尾过程组 -->
<rect x="670" y="380" width="140" height="150" class="box box-close" />
<rect x="700" y="470" width="80" height="40" class="box" style="fill:#A0B7CC; stroke:none;" />
<text x="740" y="495" class="text group-title" style="fill:white;">收尾</text>
<rect x="690" y="400" width="100" height="35" class="box box-red" />
<text x="740" y="412" class="text text-white">1.7 结束项目</text>
<text x="740" y="427" class="text text-white">或阶段</text>
<!-- 核心流程箭头 -->
<path d="M110,155 L140,325" class="arrow" marker-end="url(#arrowhead)" />
<path d="M570,340 L590,340" class="arrow" marker-end="url(#arrowhead)" />
<path d="M700,355 L700,380 L612,380 L612,395" class="arrow" marker-end="url(#arrowhead)" />
<path d="M650,417 L685,417" class="arrow" marker-end="url(#arrowhead)" />
`
export function ProcessRoadmapPage() {
const navigate = useNavigate()
const [hoveredId, setHoveredId] = useState<string | null>(null)
const processIdByCode = useMemo(
() => new Map(processes.map((process) => [process.code, process.id])),
[]
)
const hotspots = useMemo<Hotspot[]>(() => {
const routeByCode = (code: string) => {
const processId = processIdByCode.get(code)
return processId ? `/process/${processId}` : '/process-matrix'
}
return [
{ id: '10.1', label: '10.1 识别干系人', x: 30, y: 85, w: 80, h: 40, to: routeByCode('10.1') },
{ id: '1.1', label: '1.1 制定项目章程', x: 30, y: 140, w: 80, h: 30, to: routeByCode('1.1') },
{ id: '2.1', label: '2.1 规划范围管理', x: 142, y: 87, w: 96, h: 20, to: routeByCode('2.1') },
{ id: '3.1', label: '3.1 规划进度管理', x: 142, y: 110, w: 96, h: 20, to: routeByCode('3.1') },
{ id: '4.1', label: '4.1 规划成本管理', x: 142, y: 133, w: 96, h: 20, to: routeByCode('4.1') },
{ id: '5.1', label: '5.1 规划质量管理', x: 142, y: 156, w: 96, h: 20, to: routeByCode('5.1') },
{ id: '6.1', label: '6.1 规划资源管理', x: 142, y: 179, w: 96, h: 20, to: routeByCode('6.1') },
{ id: '7.1', label: '7.1 规划沟通管理', x: 142, y: 202, w: 96, h: 20, to: routeByCode('7.1') },
{ id: '8.1', label: '8.1 规划风险管理', x: 142, y: 225, w: 96, h: 20, to: routeByCode('8.1') },
{ id: '9.1', label: '9.1 规划采购管理', x: 142, y: 248, w: 96, h: 20, to: routeByCode('9.1') },
{ id: '10.2', label: '10.2 规划相关方参与', x: 142, y: 271, w: 96, h: 20, to: routeByCode('10.2') },
{ id: '2.2', label: '2.2 收集需求', x: 250, y: 30, w: 70, h: 30, to: routeByCode('2.2') },
{ id: '2.3', label: '2.3 定义范围', x: 350, y: 30, w: 70, h: 30, to: routeByCode('2.3') },
{ id: '2.4', label: '2.4 创建WBS', x: 450, y: 30, w: 70, h: 30, to: routeByCode('2.4') },
{ id: '6.2', label: '6.2 估算活动资源', x: 250, y: 90, w: 70, h: 35, to: routeByCode('6.2') },
{ id: '3.3', label: '3.3 排列活动顺序', x: 350, y: 90, w: 70, h: 35, to: routeByCode('3.3') },
{ id: '3.2', label: '3.2 定义活动', x: 450, y: 90, w: 70, h: 35, to: routeByCode('3.2') },
{ id: '3.4', label: '3.4 估算活动持续时间', x: 250, y: 150, w: 70, h: 35, to: routeByCode('3.4') },
{ id: '3.5', label: '3.5 制定进度计划', x: 350, y: 150, w: 70, h: 35, to: routeByCode('3.5') },
{ id: '4.2', label: '4.2 估算成本', x: 450, y: 150, w: 70, h: 35, to: routeByCode('4.2') },
{ id: '4.3', label: '4.3 制定预算', x: 450, y: 210, w: 70, h: 30, to: routeByCode('4.3') },
{ id: '8.2', label: '8.2 识别风险', x: 250, y: 235, w: 70, h: 30, to: routeByCode('8.2') },
{ id: '8.3', label: '8.3 实施定性风险分析', x: 350, y: 235, w: 70, h: 35, to: routeByCode('8.3') },
{ id: '8.4', label: '8.4 实施定量风险分析', x: 450, y: 235, w: 70, h: 35, to: routeByCode('8.4') },
{ id: '8.5', label: '8.5 规划风险应对', x: 450, y: 290, w: 70, h: 35, to: routeByCode('8.5') },
{ id: '1.2', label: '1.2 制定项目管理计划', x: 140, y: 325, w: 430, h: 30, to: routeByCode('1.2') },
{ id: '6.3', label: '6.3 获取资源', x: 600, y: 35, w: 50, h: 30, to: routeByCode('6.3') },
{ id: '6.4', label: '6.4 建设团队', x: 600, y: 90, w: 70, h: 30, to: routeByCode('6.4') },
{ id: '6.5', label: '6.5 管理团队', x: 700, y: 90, w: 70, h: 30, to: routeByCode('6.5') },
{ id: '8.6', label: '8.6 实施风险应对', x: 600, y: 140, w: 70, h: 35, to: routeByCode('8.6') },
{ id: '9.2', label: '9.2 实施采购', x: 700, y: 140, w: 70, h: 30, to: routeByCode('9.2') },
{ id: '7.2', label: '7.2 管理沟通', x: 600, y: 190, w: 70, h: 30, to: routeByCode('7.2') },
{ id: '10.3', label: '10.3 管理相关方参与', x: 700, y: 190, w: 70, h: 35, to: routeByCode('10.3') },
{ id: '5.2', label: '5.2 管理质量', x: 600, y: 245, w: 200, h: 30, to: routeByCode('5.2') },
{ id: '1.4', label: '1.4 管理项目知识', x: 600, y: 285, w: 80, h: 35, to: routeByCode('1.4') },
{ id: '1.3', label: '1.3 指导与管理项目工作', x: 600, y: 325, w: 200, h: 30, to: routeByCode('1.3') },
{ id: '2.6', label: '2.6 控制范围', x: 230, y: 390, w: 75, h: 30, to: routeByCode('2.6') },
{ id: '6.6', label: '6.6 控制资源', x: 315, y: 390, w: 75, h: 30, to: routeByCode('6.6') },
{ id: '9.3', label: '9.3 控制采购', x: 400, y: 390, w: 75, h: 30, to: routeByCode('9.3') },
{ id: '5.3', label: '5.3 控制质量', x: 485, y: 390, w: 75, h: 30, to: routeByCode('5.3') },
{ id: '3.6', label: '3.6 控制进度', x: 230, y: 435, w: 75, h: 30, to: routeByCode('3.6') },
{ id: '7.3', label: '7.3 监督沟通', x: 315, y: 435, w: 75, h: 30, to: routeByCode('7.3') },
{ id: '10.4', label: '10.4 监督相关方参与', x: 400, y: 435, w: 85, h: 35, to: routeByCode('10.4') },
{ id: '8.7', label: '8.7 监督风险', x: 315, y: 485, w: 75, h: 30, to: routeByCode('8.7') },
{ id: '4.4', label: '4.4 控制成本', x: 400, y: 485, w: 75, h: 30, to: routeByCode('4.4') },
{ id: '2.5', label: '2.5 确认范围', x: 485, y: 485, w: 75, h: 30, to: routeByCode('2.5') },
{ id: '1.5', label: '1.5 监控项目工作', x: 575, y: 400, w: 75, h: 35, to: routeByCode('1.5') },
{ id: '1.6', label: '1.6 实施整体变更控制', x: 575, y: 445, w: 75, h: 35, to: routeByCode('1.6') },
{ id: '1.7', label: '1.7 结束项目或阶段', x: 690, y: 400, w: 100, h: 35, to: routeByCode('1.7') },
]
}, [processIdByCode])
const handleOpen = (to: string) => {
if (to.startsWith('/process/')) {
navigate(to, { state: { from: 'roadmap' } })
return
}
navigate(to)
}
return (
<div className="space-y-4">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white"></h1>
</div>
<div className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-2">
<svg
width="1260"
height="840"
viewBox="0 0 840 560"
className="block w-full h-auto max-w-[1260px] mx-auto"
>
<style>{svgStyles}</style>
<defs>
<marker id="arrowhead" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
<path d="M0,0 L6,3 L0,6 Z" className="marker" />
</marker>
<marker id="arrowhead-grey" markerWidth="5" markerHeight="5" refX="4" refY="2.5" orient="auto">
<path d="M0,0 L5,2.5 L0,5 Z" className="marker-grey" />
</marker>
</defs>
<g dangerouslySetInnerHTML={{ __html: staticSvgBody }} />
{hotspots.map((spot) => {
const isHovered = hoveredId === spot.id
return (
<g
key={spot.id}
role="button"
tabIndex={0}
onClick={() => handleOpen(spot.to)}
onMouseEnter={() => setHoveredId(spot.id)}
onMouseLeave={() => setHoveredId(null)}
onFocus={() => setHoveredId(spot.id)}
onBlur={() => setHoveredId(null)}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
handleOpen(spot.to)
}
}}
style={{ cursor: 'pointer' }}
>
<title>{`${spot.label}(点击进入)`}</title>
<rect
x={spot.x}
y={spot.y}
width={spot.w}
height={spot.h}
fill={isHovered ? 'rgba(245, 158, 11, 0.12)' : 'transparent'}
stroke={isHovered ? '#F59E0B' : 'transparent'}
strokeWidth={1.5}
rx={4}
/>
</g>
)
})}
</svg>
</div>
</div>
)
}