Initial commit
This commit is contained in:
10
.dockerignore
Normal file
10
.dockerignore
Normal file
@@ -0,0 +1,10 @@
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.gitignore
|
||||
*.md
|
||||
.vscode
|
||||
.venv
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.venv/
|
||||
*.log
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
*.pdf
|
||||
pdf_images/
|
||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM node:18-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
429
docs/需求设计文档.md
Normal file
429
docs/需求设计文档.md
Normal file
@@ -0,0 +1,429 @@
|
||||
# ITTOView - PMP项目管理ITTO可视化学习平台
|
||||
|
||||
## 需求设计文档 v1.0
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
### 1.1 项目背景
|
||||
|
||||
PMP(Project Management Professional)认证考试中,ITTO(Input-Tools & Techniques-Output)是核心知识点。PMBOK第6版包含:
|
||||
|
||||
- **10大知识领域**:整合、范围、进度、成本、质量、资源、沟通、风险、采购、相关方管理
|
||||
- **5大过程组**:启动、规划、执行、监控、收尾
|
||||
- **49个项目管理过程**:每个过程都有对应的输入、工具与技术、输出
|
||||
|
||||
传统的ITTO学习方式(PDF/表格)存在以下痛点:
|
||||
|
||||
1. **信息孤立**:难以看到过程之间的数据流向关系
|
||||
2. **记忆困难**:大量术语和对应关系难以记忆
|
||||
3. **缺乏互动**:无法进行自测和针对性复习
|
||||
4. **查找低效**:无法快速定位特定输入/输出的使用场景
|
||||
|
||||
### 1.2 项目目标
|
||||
|
||||
构建一个**精美、交互式**的ITTO可视化学习平台,帮助PMP考生:
|
||||
|
||||
1. **直观理解**:通过流程图可视化ITTO关系
|
||||
2. **高效记忆**:基于间隔重复的记忆卡片系统
|
||||
3. **精准评估**:完整的测评、错题、统计功能
|
||||
4. **快速查询**:强大的搜索和关联分析能力
|
||||
|
||||
### 1.3 目标用户
|
||||
|
||||
- PMP/CAPM认证备考人员
|
||||
- 项目管理学习者
|
||||
- 企业项目管理培训机构
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能需求
|
||||
|
||||
### 2.1 核心功能模块
|
||||
|
||||
#### 2.1.1 知识浏览模块
|
||||
|
||||
| 功能 | 描述 | 优先级 |
|
||||
|------|------|--------|
|
||||
| 知识领域视图 | 按10大知识领域分类展示所有过程 | P0 |
|
||||
| 过程组视图 | 按5大过程组分类展示所有过程 | P0 |
|
||||
| 过程详情页 | 展示单个过程的完整ITTO信息 | P0 |
|
||||
| 表格视图 | 传统表格形式展示,支持排序筛选 | P1 |
|
||||
| 卡片视图 | 卡片形式展示,适合快速浏览 | P1 |
|
||||
|
||||
#### 2.1.2 可视化模块
|
||||
|
||||
| 功能 | 描述 | 优先级 |
|
||||
|------|------|--------|
|
||||
| ITTO流程图 | 输入→工具技术→输出的流程图展示 | P0 |
|
||||
| 数据流向图 | 展示输出如何流向其他过程作为输入 | P0 |
|
||||
| 知识领域全景图 | 单个知识领域内所有过程的关系图 | P1 |
|
||||
| 全局关系图谱 | 49个过程的完整关系网络 | P2 |
|
||||
|
||||
#### 2.1.3 搜索与筛选模块
|
||||
|
||||
| 功能 | 描述 | 优先级 |
|
||||
|------|------|--------|
|
||||
| 全局搜索 | 搜索过程名、输入、工具、输出 | P0 |
|
||||
| 分类筛选 | 按知识领域/过程组筛选 | P0 |
|
||||
| 类型筛选 | 仅搜索输入/工具/输出 | P1 |
|
||||
| 关联查询 | 查看某项作为输入/输出的所有过程 | P1 |
|
||||
|
||||
#### 2.1.4 学习模块
|
||||
|
||||
| 功能 | 描述 | 优先级 |
|
||||
|------|------|--------|
|
||||
| 记忆卡片 | 基于间隔重复算法的闪卡系统 | P0 |
|
||||
| 自测模式 | 多种题型(选择、填空、连线) | P0 |
|
||||
| 错题本 | 自动收集错题,支持针对性复习 | P0 |
|
||||
| 掌握度标记 | 熟悉/模糊/陌生三级标记 | P1 |
|
||||
| 学习统计 | 学习时长、正确率、进度可视化 | P1 |
|
||||
| 复习计划 | 基于艾宾浩斯遗忘曲线的复习提醒 | P2 |
|
||||
|
||||
#### 2.1.5 用户体验模块
|
||||
|
||||
| 功能 | 描述 | 优先级 |
|
||||
|------|------|--------|
|
||||
| 响应式布局 | 适配PC和平板设备 | P0 |
|
||||
| 深色模式 | 支持明暗主题切换 | P1 |
|
||||
| 键盘导航 | 支持快捷键操作 | P2 |
|
||||
| 数据导出 | 导出学习记录为JSON/CSV | P2 |
|
||||
|
||||
### 2.2 页面结构
|
||||
|
||||
```
|
||||
ITTOView/
|
||||
├── 首页 (Dashboard)
|
||||
│ ├── 学习进度概览
|
||||
│ ├── 今日复习任务
|
||||
│ └── 快速入口
|
||||
├── 知识浏览
|
||||
│ ├── 知识领域视图
|
||||
│ ├── 过程组视图
|
||||
│ └── 过程详情页
|
||||
├── 可视化
|
||||
│ ├── ITTO流程图
|
||||
│ ├── 数据流向图
|
||||
│ └── 全局关系图谱
|
||||
├── 学习中心
|
||||
│ ├── 记忆卡片
|
||||
│ ├── 自测模式
|
||||
│ ├── 错题本
|
||||
│ └── 学习统计
|
||||
└── 设置
|
||||
├── 主题设置
|
||||
└── 数据管理
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 技术架构
|
||||
|
||||
### 3.1 技术选型
|
||||
|
||||
| 类别 | 技术方案 | 选型理由 |
|
||||
|------|----------|----------|
|
||||
| 框架 | React 18 + TypeScript | 生态成熟、类型安全、组件化开发 |
|
||||
| 构建工具 | Vite | 快速冷启动、HMR、现代化构建 |
|
||||
| UI组件库 | Radix UI + Tailwind CSS | 无样式组件+原子化CSS,高度可定制 |
|
||||
| 可视化 | React Flow + ELK.js | 流程图交互完善 + 自动布局算法 |
|
||||
| 状态管理 | Zustand | 轻量、简洁、TypeScript友好 |
|
||||
| 搜索引擎 | FlexSearch | 高性能全文搜索,支持中文 |
|
||||
| 路由 | React Router v6 | 官方路由方案,功能完善 |
|
||||
| 动画 | Framer Motion | 声明式动画,流畅交互 |
|
||||
| 图表 | Recharts | 基于React的图表库,学习统计使用 |
|
||||
| 持久化 | localStorage + IndexedDB | 本地存储学习进度和用户数据 |
|
||||
|
||||
### 3.2 项目结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── assets/ # 静态资源
|
||||
│ ├── icons/
|
||||
│ └── images/
|
||||
├── components/ # 通用组件
|
||||
│ ├── ui/ # 基础UI组件
|
||||
│ ├── layout/ # 布局组件
|
||||
│ └── shared/ # 共享业务组件
|
||||
├── features/ # 功能模块
|
||||
│ ├── browse/ # 知识浏览
|
||||
│ ├── visualize/ # 可视化
|
||||
│ ├── learn/ # 学习中心
|
||||
│ └── search/ # 搜索功能
|
||||
├── data/ # 数据层
|
||||
│ ├── itto.json # ITTO核心数据
|
||||
│ ├── models/ # 数据模型定义
|
||||
│ └── index/ # 搜索索引
|
||||
├── stores/ # 状态管理
|
||||
│ ├── useAppStore.ts
|
||||
│ ├── useLearnStore.ts
|
||||
│ └── useSearchStore.ts
|
||||
├── hooks/ # 自定义Hooks
|
||||
├── utils/ # 工具函数
|
||||
├── styles/ # 全局样式
|
||||
├── types/ # TypeScript类型
|
||||
└── App.tsx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据模型
|
||||
|
||||
### 4.1 核心数据结构
|
||||
|
||||
#### 4.1.1 知识领域 (KnowledgeArea)
|
||||
|
||||
```typescript
|
||||
interface KnowledgeArea {
|
||||
id: string; // 如 "KA01"
|
||||
name: string; // 如 "项目整合管理"
|
||||
nameEn: string; // 如 "Project Integration Management"
|
||||
order: number; // 排序序号 1-10
|
||||
color: string; // 主题色 "#3B82F6"
|
||||
description: string; // 简要描述
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.1.2 过程组 (ProcessGroup)
|
||||
|
||||
```typescript
|
||||
interface ProcessGroup {
|
||||
id: string; // 如 "PG01"
|
||||
name: string; // 如 "启动过程组"
|
||||
nameEn: string; // 如 "Initiating Process Group"
|
||||
order: number; // 排序序号 1-5
|
||||
color: string; // 主题色
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.1.3 过程 (Process)
|
||||
|
||||
```typescript
|
||||
interface Process {
|
||||
id: string; // 如 "P001"
|
||||
name: string; // 如 "制定项目章程"
|
||||
nameEn: string; // 如 "Develop Project Charter"
|
||||
knowledgeAreaId: string; // 所属知识领域
|
||||
processGroupId: string; // 所属过程组
|
||||
order: number; // 在知识领域内的序号
|
||||
inputs: ArtifactRef[]; // 输入列表
|
||||
tools: ToolRef[]; // 工具与技术列表
|
||||
outputs: ArtifactRef[]; // 输出列表
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.1.4 工件/文档 (Artifact)
|
||||
|
||||
```typescript
|
||||
interface Artifact {
|
||||
id: string; // 如 "A0001"
|
||||
name: string; // 如 "项目章程"
|
||||
nameEn: string; // 如 "Project Charter"
|
||||
category: ArtifactCategory;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
type ArtifactCategory =
|
||||
| 'document' // 文档
|
||||
| 'plan' // 计划
|
||||
| 'baseline' // 基准
|
||||
| 'report' // 报告
|
||||
| 'register' // 登记册
|
||||
| 'log' // 日志
|
||||
| 'deliverable' // 可交付成果
|
||||
| 'other'; // 其他
|
||||
```
|
||||
|
||||
#### 4.1.5 工具与技术 (ToolTechnique)
|
||||
|
||||
```typescript
|
||||
interface ToolTechnique {
|
||||
id: string; // 如 "TT0001"
|
||||
name: string; // 如 "专家判断"
|
||||
nameEn: string; // 如 "Expert Judgment"
|
||||
type: ToolType;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
type ToolType =
|
||||
| 'tool' // 工具
|
||||
| 'technique' // 技术
|
||||
| 'method' // 方法
|
||||
| 'skill' // 技能
|
||||
| 'meeting'; // 会议
|
||||
```
|
||||
|
||||
### 4.2 学习数据结构
|
||||
|
||||
```typescript
|
||||
// 学习记录
|
||||
interface LearningRecord {
|
||||
id: string;
|
||||
userId: string;
|
||||
processId: string;
|
||||
masteryLevel: 'familiar' | 'fuzzy' | 'unfamiliar';
|
||||
reviewCount: number;
|
||||
correctCount: number;
|
||||
lastReviewAt: Date;
|
||||
nextReviewAt: Date; // 基于间隔重复算法计算
|
||||
}
|
||||
|
||||
// 错题记录
|
||||
interface WrongAnswer {
|
||||
id: string;
|
||||
questionId: string;
|
||||
questionType: 'choice' | 'fill' | 'match';
|
||||
userAnswer: string;
|
||||
correctAnswer: string;
|
||||
createdAt: Date;
|
||||
reviewed: boolean;
|
||||
}
|
||||
|
||||
// 学习统计
|
||||
interface LearningStats {
|
||||
totalStudyTime: number; // 总学习时长(分钟)
|
||||
totalQuestions: number; // 总答题数
|
||||
correctRate: number; // 正确率
|
||||
masteredCount: number; // 已掌握过程数
|
||||
streakDays: number; // 连续学习天数
|
||||
lastStudyDate: Date;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 实施计划
|
||||
|
||||
### 5.1 里程碑规划
|
||||
|
||||
| 阶段 | 名称 | 主要产出 | 验收标准 |
|
||||
|------|------|----------|----------|
|
||||
| M0 | 数据准备 | ITTO数据JSON、数据校验 | 49个过程数据完整可用 |
|
||||
| M1 | 基础架构 | 项目骨架、路由、布局 | 可浏览10大领域和5大过程组 |
|
||||
| M2 | 核心浏览 | 过程详情页、ITTO表格、搜索 | 可搜索并查看任意过程详情 |
|
||||
| M3 | 可视化 | ITTO流程图、数据流向图 | 图谱支持缩放/高亮/筛选 |
|
||||
| M4 | 学习系统 | 卡片、测评、错题、统计 | 完整学习闭环可用 |
|
||||
| M5 | 体验优化 | 动效、响应式、性能优化 | 流畅度达标、视觉统一 |
|
||||
|
||||
### 5.2 详细任务分解
|
||||
|
||||
#### M0: 数据准备
|
||||
- [ ] 从PDF提取49个过程的ITTO数据
|
||||
- [ ] 设计并实现数据JSON结构
|
||||
- [ ] 建立输入/输出的关联关系
|
||||
- [ ] 数据校验与完整性检查
|
||||
|
||||
#### M1: 基础架构
|
||||
- [ ] 初始化Vite + React + TypeScript项目
|
||||
- [ ] 配置Tailwind CSS和Radix UI
|
||||
- [ ] 实现基础布局组件(Header、Sidebar、Content)
|
||||
- [ ] 配置React Router路由
|
||||
- [ ] 实现知识领域列表页
|
||||
- [ ] 实现过程组列表页
|
||||
|
||||
#### M2: 核心浏览
|
||||
- [ ] 实现过程详情页
|
||||
- [ ] 实现ITTO表格组件
|
||||
- [ ] 集成FlexSearch搜索引擎
|
||||
- [ ] 实现全局搜索功能
|
||||
- [ ] 实现分类筛选功能
|
||||
|
||||
#### M3: 可视化
|
||||
- [ ] 集成React Flow
|
||||
- [ ] 实现ITTO流程图组件
|
||||
- [ ] 实现数据流向分析
|
||||
- [ ] 实现知识领域全景图
|
||||
- [ ] 优化图谱交互体验
|
||||
|
||||
#### M4: 学习系统
|
||||
- [ ] 实现记忆卡片组件
|
||||
- [ ] 实现间隔重复算法
|
||||
- [ ] 实现自测模式(多种题型)
|
||||
- [ ] 实现错题本功能
|
||||
- [ ] 实现学习统计页面
|
||||
- [ ] 实现数据持久化
|
||||
|
||||
#### M5: 体验优化
|
||||
- [ ] 添加页面过渡动画
|
||||
- [ ] 实现深色模式
|
||||
- [ ] 响应式布局适配
|
||||
- [ ] 性能优化(懒加载、虚拟化)
|
||||
- [ ] 用户体验细节打磨
|
||||
|
||||
---
|
||||
|
||||
## 6. UI设计规范
|
||||
|
||||
### 6.1 色彩系统
|
||||
|
||||
#### 知识领域主题色
|
||||
|
||||
| 知识领域 | 主色 | 用途 |
|
||||
|----------|------|------|
|
||||
| 整合管理 | `#6366F1` (Indigo) | 统领全局 |
|
||||
| 范围管理 | `#8B5CF6` (Violet) | 边界定义 |
|
||||
| 进度管理 | `#EC4899` (Pink) | 时间紧迫 |
|
||||
| 成本管理 | `#10B981` (Emerald) | 财务绿色 |
|
||||
| 质量管理 | `#F59E0B` (Amber) | 金牌品质 |
|
||||
| 资源管理 | `#3B82F6` (Blue) | 人力资源 |
|
||||
| 沟通管理 | `#06B6D4` (Cyan) | 信息流动 |
|
||||
| 风险管理 | `#EF4444` (Red) | 风险警示 |
|
||||
| 采购管理 | `#84CC16` (Lime) | 采购绿通 |
|
||||
| 相关方管理 | `#F97316` (Orange) | 人际温暖 |
|
||||
|
||||
#### 过程组主题色
|
||||
|
||||
| 过程组 | 主色 | 含义 |
|
||||
|--------|------|------|
|
||||
| 启动 | `#22C55E` | 绿灯启动 |
|
||||
| 规划 | `#3B82F6` | 蓝图规划 |
|
||||
| 执行 | `#F59E0B` | 黄金执行 |
|
||||
| 监控 | `#8B5CF6` | 紫色监控 |
|
||||
| 收尾 | `#6B7280` | 灰色收尾 |
|
||||
|
||||
### 6.2 组件设计原则
|
||||
|
||||
1. **卡片化设计**:信息以卡片形式呈现,层次分明
|
||||
2. **色彩编码**:通过颜色快速识别知识领域和过程组
|
||||
3. **渐进式披露**:先展示概要,点击展开详情
|
||||
4. **一致性交互**:相同操作保持一致的交互反馈
|
||||
5. **无障碍设计**:确保足够的对比度和可访问性
|
||||
|
||||
---
|
||||
|
||||
## 7. 附录
|
||||
|
||||
### 7.1 10大知识领域
|
||||
|
||||
1. **项目整合管理** - 7个过程
|
||||
2. **项目范围管理** - 6个过程
|
||||
3. **项目进度管理** - 6个过程
|
||||
4. **项目成本管理** - 4个过程
|
||||
5. **项目质量管理** - 3个过程
|
||||
6. **项目资源管理** - 6个过程
|
||||
7. **项目沟通管理** - 3个过程
|
||||
8. **项目风险管理** - 7个过程
|
||||
9. **项目采购管理** - 3个过程
|
||||
10. **项目相关方管理** - 4个过程
|
||||
|
||||
### 7.2 5大过程组
|
||||
|
||||
1. **启动过程组** - 2个过程
|
||||
2. **规划过程组** - 24个过程
|
||||
3. **执行过程组** - 10个过程
|
||||
4. **监控过程组** - 12个过程
|
||||
5. **收尾过程组** - 1个过程
|
||||
|
||||
### 7.3 参考资料
|
||||
|
||||
- PMBOK第6版指南
|
||||
- ITTO输入输出工具手册(本项目源PDF)
|
||||
- React Flow官方文档
|
||||
- Tailwind CSS设计系统
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0
|
||||
**创建日期**: 2026-02-02
|
||||
**最后更新**: 2026-02-02
|
||||
14
index.html
Normal file
14
index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ITTOView - PMP项目管理ITTO可视化学习平台</title>
|
||||
<meta name="description" content="PMP认证考试ITTO可视化学习平台,帮助您高效掌握49个项目管理过程" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
6557
package-lock.json
generated
Normal file
6557
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
package.json
Normal file
47
package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "ittoview",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/g6": "^4.8.25",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"clsx": "^2.1.0",
|
||||
"elkjs": "^0.9.2",
|
||||
"flexsearch": "^0.7.43",
|
||||
"framer-motion": "^11.0.3",
|
||||
"lucide-react": "^0.344.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"reactflow": "^11.10.4",
|
||||
"recharts": "^2.12.0",
|
||||
"zustand": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.0"
|
||||
}
|
||||
}
|
||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
33
src/App.tsx
Normal file
33
src/App.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Routes, Route } from 'react-router-dom'
|
||||
import { Layout } from './components/layout/Layout'
|
||||
import { HomePage } from './pages/HomePage'
|
||||
import { KnowledgeAreasPage } from './pages/KnowledgeAreasPage'
|
||||
import { ProcessGroupsPage } from './pages/ProcessGroupsPage'
|
||||
import { ProcessDetailPage } from './pages/ProcessDetailPage'
|
||||
import { ProcessMatrixPage } from './pages/ProcessMatrixPage'
|
||||
import { ProcessGraphPage } from './pages/ProcessGraphPage'
|
||||
import { ArtifactDetailPage } from './pages/ArtifactDetailPage'
|
||||
import { ToolDetailPage } from './pages/ToolDetailPage'
|
||||
import { SettingsPage } from './pages/SettingsPage'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Layout>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/knowledge-areas" element={<KnowledgeAreasPage />} />
|
||||
<Route path="/knowledge-areas/:id" element={<KnowledgeAreasPage />} />
|
||||
<Route path="/process-groups" element={<ProcessGroupsPage />} />
|
||||
<Route path="/process-groups/:id" element={<ProcessGroupsPage />} />
|
||||
<Route path="/process/:id" element={<ProcessDetailPage />} />
|
||||
<Route path="/process-matrix" element={<ProcessMatrixPage />} />
|
||||
<Route path="/process-graph" element={<ProcessGraphPage />} />
|
||||
<Route path="/artifact/:id" element={<ArtifactDetailPage />} />
|
||||
<Route path="/tool/:id" element={<ToolDetailPage />} />
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
</Routes>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
255
src/components/layout/Header.tsx
Normal file
255
src/components/layout/Header.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
import { useAppStore } from '@/stores/useAppStore'
|
||||
import { Menu, Search, Sun, Moon, X } from 'lucide-react'
|
||||
import { useState, useMemo, useRef, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { processes, artifacts, tools, knowledgeAreaMap } from '@/data'
|
||||
|
||||
interface SearchResult {
|
||||
type: 'process' | 'artifact' | 'tool'
|
||||
id: string
|
||||
name: string
|
||||
nameEn: string
|
||||
extra?: string
|
||||
link: string
|
||||
}
|
||||
|
||||
export function Header() {
|
||||
const sidebarOpen = useAppStore((s) => s.sidebarOpen)
|
||||
const toggleSidebar = useAppStore((s) => s.toggleSidebar)
|
||||
const darkMode = useAppStore((s) => s.darkMode)
|
||||
const toggleDarkMode = useAppStore((s) => s.toggleDarkMode)
|
||||
const searchQuery = useAppStore((s) => s.searchQuery)
|
||||
const setSearchQuery = useAppStore((s) => s.setSearchQuery)
|
||||
const [isSearchOpen, setIsSearchOpen] = useState(false)
|
||||
const searchRef = useRef<HTMLDivElement>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const navigate = useNavigate()
|
||||
|
||||
// 搜索结果
|
||||
const searchResults = useMemo<SearchResult[]>(() => {
|
||||
if (!searchQuery.trim()) return []
|
||||
|
||||
const query = searchQuery.toLowerCase()
|
||||
const results: SearchResult[] = []
|
||||
|
||||
// 搜索过程
|
||||
processes.forEach((p) => {
|
||||
if (
|
||||
p.name.toLowerCase().includes(query) ||
|
||||
p.nameEn.toLowerCase().includes(query) ||
|
||||
p.code.toLowerCase().includes(query)
|
||||
) {
|
||||
const ka = knowledgeAreaMap.get(p.knowledgeAreaId)
|
||||
results.push({
|
||||
type: 'process',
|
||||
id: p.id,
|
||||
name: `${p.code} ${p.name}`,
|
||||
nameEn: p.nameEn,
|
||||
extra: ka?.name,
|
||||
link: `/process/${p.id}`,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 搜索工件/文档
|
||||
artifacts.forEach((a) => {
|
||||
if (
|
||||
a.name.toLowerCase().includes(query) ||
|
||||
a.nameEn.toLowerCase().includes(query)
|
||||
) {
|
||||
results.push({
|
||||
type: 'artifact',
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
nameEn: a.nameEn,
|
||||
extra: '工件/文档',
|
||||
link: `/artifact/${a.id}`,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 搜索工具与技术
|
||||
tools.forEach((t) => {
|
||||
if (
|
||||
t.name.toLowerCase().includes(query) ||
|
||||
t.nameEn.toLowerCase().includes(query)
|
||||
) {
|
||||
results.push({
|
||||
type: 'tool',
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
nameEn: t.nameEn,
|
||||
extra: '工具与技术',
|
||||
link: `/tool/${t.id}`,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return results.slice(0, 10) // 限制结果数量
|
||||
}, [searchQuery])
|
||||
|
||||
// 点击外部关闭搜索
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (searchRef.current && !searchRef.current.contains(event.target as Node)) {
|
||||
setIsSearchOpen(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [])
|
||||
|
||||
// 键盘快捷键
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
|
||||
event.preventDefault()
|
||||
setIsSearchOpen(true)
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
setIsSearchOpen(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
return () => document.removeEventListener('keydown', handleKeyDown)
|
||||
}, [])
|
||||
|
||||
const handleResultClick = (result: SearchResult) => {
|
||||
navigate(result.link)
|
||||
setIsSearchOpen(false)
|
||||
}
|
||||
|
||||
const getTypeColor = (type: string) => {
|
||||
switch (type) {
|
||||
case 'process':
|
||||
return 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/50 dark:text-indigo-300'
|
||||
case 'artifact':
|
||||
return 'bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300'
|
||||
case 'tool':
|
||||
return 'bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300'
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
|
||||
}
|
||||
}
|
||||
|
||||
const getTypeLabel = (type: string) => {
|
||||
switch (type) {
|
||||
case 'process':
|
||||
return '过程'
|
||||
case 'artifact':
|
||||
return '工件'
|
||||
case 'tool':
|
||||
return '工具'
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<header className={`
|
||||
sticky top-0 z-10 flex h-16 items-center justify-between gap-4
|
||||
border-b border-gray-200 dark:border-gray-700
|
||||
bg-white dark:bg-gray-800 px-4
|
||||
transition-all duration-300
|
||||
${sidebarOpen ? 'lg:ml-64' : 'lg:ml-20'}
|
||||
`}>
|
||||
{/* 左侧:菜单按钮和搜索 */}
|
||||
<div className="flex items-center gap-4 flex-1">
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
className="lg:hidden flex h-10 w-10 items-center justify-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-500"
|
||||
aria-label="切换菜单"
|
||||
>
|
||||
<Menu size={20} />
|
||||
</button>
|
||||
|
||||
{/* 搜索框 */}
|
||||
<div ref={searchRef} className="relative flex-1 max-w-lg">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" size={18} />
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
placeholder="搜索过程、输入、工具、输出..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => {
|
||||
setSearchQuery(e.target.value)
|
||||
setIsSearchOpen(true)
|
||||
}}
|
||||
onFocus={() => setIsSearchOpen(true)}
|
||||
onClick={() => setIsSearchOpen(true)}
|
||||
className="w-full h-10 pl-10 pr-20 rounded-lg border border-gray-200 dark:border-gray-600
|
||||
bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white
|
||||
placeholder-gray-400 dark:placeholder-gray-500
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent
|
||||
transition-colors"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchQuery('')
|
||||
inputRef.current?.focus()
|
||||
}}
|
||||
className="absolute right-12 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
)}
|
||||
<kbd className="absolute right-3 top-1/2 -translate-y-1/2 hidden sm:inline-flex
|
||||
h-5 items-center gap-1 rounded border border-gray-200 dark:border-gray-600
|
||||
bg-gray-100 dark:bg-gray-600 px-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
⌘K
|
||||
</kbd>
|
||||
|
||||
{/* 搜索结果下拉 */}
|
||||
{isSearchOpen && searchQuery && (
|
||||
<div className="absolute top-full left-0 right-0 mt-2 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 overflow-hidden z-50">
|
||||
{searchResults.length > 0 ? (
|
||||
<ul className="max-h-96 overflow-y-auto">
|
||||
{searchResults.map((result) => (
|
||||
<li key={`${result.type}-${result.id}`}>
|
||||
<button
|
||||
onClick={() => handleResultClick(result)}
|
||||
className="w-full px-4 py-3 flex items-center gap-3 hover:bg-gray-50 dark:hover:bg-gray-700/50 text-left transition-colors"
|
||||
>
|
||||
<span className={`px-2 py-0.5 rounded text-xs font-medium ${getTypeColor(result.type)}`}>
|
||||
{getTypeLabel(result.type)}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-gray-900 dark:text-white truncate">
|
||||
{result.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 truncate">
|
||||
{result.nameEn}
|
||||
{result.extra && ` · ${result.extra}`}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
|
||||
未找到匹配的结果
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧:操作按钮 */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* 主题切换 */}
|
||||
<button
|
||||
onClick={toggleDarkMode}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-lg
|
||||
hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-500 dark:text-gray-400"
|
||||
aria-label="切换主题"
|
||||
>
|
||||
{darkMode ? <Sun size={20} /> : <Moon size={20} />}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
41
src/components/layout/Layout.tsx
Normal file
41
src/components/layout/Layout.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { Header } from './Header'
|
||||
import { Sidebar } from './Sidebar'
|
||||
import { useAppStore } from '@/stores/useAppStore'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
interface LayoutProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function Layout({ children }: LayoutProps) {
|
||||
const sidebarOpen = useAppStore((s) => s.sidebarOpen)
|
||||
const darkMode = useAppStore((s) => s.darkMode)
|
||||
|
||||
return (
|
||||
<div className={clsx('min-h-screen', darkMode && 'dark')}>
|
||||
<div className="flex h-screen bg-gray-50 dark:bg-gray-900">
|
||||
{/* 侧边栏 */}
|
||||
<Sidebar />
|
||||
|
||||
{/* 主内容区 */}
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
{/* 顶部导航 */}
|
||||
<Header />
|
||||
|
||||
{/* 页面内容 */}
|
||||
<main
|
||||
className={clsx(
|
||||
'flex-1 overflow-y-auto p-6 transition-all duration-300',
|
||||
sidebarOpen ? 'lg:ml-64' : 'lg:ml-20'
|
||||
)}
|
||||
>
|
||||
<div className="mx-auto max-w-7xl">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
111
src/components/layout/Sidebar.tsx
Normal file
111
src/components/layout/Sidebar.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { clsx } from 'clsx'
|
||||
import { useAppStore } from '@/stores/useAppStore'
|
||||
import {
|
||||
Home,
|
||||
BookOpen,
|
||||
Layers,
|
||||
LayoutGrid,
|
||||
Share2,
|
||||
Settings,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
} from 'lucide-react'
|
||||
|
||||
const navItems = [
|
||||
{ path: '/', label: '首页', icon: Home },
|
||||
{ path: '/knowledge-areas', label: '知识领域', icon: BookOpen },
|
||||
{ path: '/process-groups', label: '过程组', icon: Layers },
|
||||
{ path: '/process-matrix', label: '49过程矩阵', icon: LayoutGrid },
|
||||
{ path: '/process-graph', label: '过程关系图', icon: Share2 },
|
||||
{ path: '/settings', label: '设置', icon: Settings },
|
||||
]
|
||||
|
||||
export function Sidebar() {
|
||||
const location = useLocation()
|
||||
const sidebarOpen = useAppStore((s) => s.sidebarOpen)
|
||||
const toggleSidebar = useAppStore((s) => s.toggleSidebar)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 移动端遮罩 */}
|
||||
{sidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 z-20 bg-black/50 lg:hidden"
|
||||
onClick={toggleSidebar}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 侧边栏 */}
|
||||
<aside
|
||||
className={clsx(
|
||||
'fixed left-0 top-0 z-30 h-full bg-white dark:bg-gray-800 shadow-lg transition-all duration-300',
|
||||
sidebarOpen ? 'w-64' : 'w-20',
|
||||
'lg:translate-x-0',
|
||||
sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'
|
||||
)}
|
||||
>
|
||||
{/* Logo */}
|
||||
<div className="flex h-16 items-center justify-between px-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<Link to="/" className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 text-white font-bold text-lg">
|
||||
IT
|
||||
</div>
|
||||
{sidebarOpen && (
|
||||
<span className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
ITTOView
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
<button
|
||||
onClick={toggleSidebar}
|
||||
className="hidden lg:flex h-8 w-8 items-center justify-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-500"
|
||||
aria-label="切换侧边栏"
|
||||
>
|
||||
{sidebarOpen ? <ChevronLeft size={18} /> : <ChevronRight size={18} />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 导航菜单 */}
|
||||
<nav className="mt-4 px-3">
|
||||
<ul className="space-y-1">
|
||||
{navItems.map((item) => {
|
||||
const isActive = location.pathname === item.path ||
|
||||
(item.path !== '/' && location.pathname.startsWith(item.path))
|
||||
const Icon = item.icon
|
||||
|
||||
return (
|
||||
<li key={item.path}>
|
||||
<Link
|
||||
to={item.path}
|
||||
className={clsx(
|
||||
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
|
||||
isActive
|
||||
? 'bg-indigo-50 text-indigo-600 dark:bg-indigo-900/50 dark:text-indigo-400'
|
||||
: 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700'
|
||||
)}
|
||||
>
|
||||
<Icon size={20} />
|
||||
{sidebarOpen && <span>{item.label}</span>}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{/* 底部信息 */}
|
||||
{sidebarOpen && (
|
||||
<div className="absolute bottom-4 left-4 right-4">
|
||||
<div className="rounded-lg bg-gray-50 dark:bg-gray-700/50 p-4">
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 mb-1">PMBOK 第6版</div>
|
||||
<div className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
49个过程 · 10大知识领域
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</aside>
|
||||
</>
|
||||
)
|
||||
}
|
||||
3
src/components/layout/index.ts
Normal file
3
src/components/layout/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { Layout } from './Layout'
|
||||
export { Header } from './Header'
|
||||
export { Sidebar } from './Sidebar'
|
||||
148
src/components/visualize/ProcessMatrix.tsx
Normal file
148
src/components/visualize/ProcessMatrix.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 49过程矩阵图组件
|
||||
* 展示知识领域(行) × 过程组(列) 的矩阵视图
|
||||
*/
|
||||
import { Link } from 'react-router-dom'
|
||||
import { motion } from 'framer-motion'
|
||||
import {
|
||||
knowledgeAreas,
|
||||
processGroups,
|
||||
processes,
|
||||
} from '@/data'
|
||||
|
||||
export function ProcessMatrix() {
|
||||
// 构建矩阵数据:knowledgeAreaId -> processGroupId -> Process[]
|
||||
const matrix = new Map<string, Map<string, typeof processes>>()
|
||||
|
||||
knowledgeAreas.forEach(ka => {
|
||||
const row = new Map<string, typeof processes>()
|
||||
processGroups.forEach(pg => {
|
||||
row.set(pg.id, [])
|
||||
})
|
||||
matrix.set(ka.id, row)
|
||||
})
|
||||
|
||||
processes.forEach(p => {
|
||||
const row = matrix.get(p.knowledgeAreaId)
|
||||
if (row) {
|
||||
const cell = row.get(p.processGroupId)
|
||||
if (cell) {
|
||||
cell.push(p)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse min-w-[900px]">
|
||||
{/* 表头:过程组 */}
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="sticky left-0 z-10 bg-gray-100 dark:bg-gray-800 p-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700 min-w-[160px]">
|
||||
知识领域 / 过程组
|
||||
</th>
|
||||
{processGroups.map(pg => (
|
||||
<th
|
||||
key={pg.id}
|
||||
className="p-3 text-center text-sm font-semibold text-white border border-gray-200 dark:border-gray-700 min-w-[140px]"
|
||||
style={{ backgroundColor: pg.color }}
|
||||
>
|
||||
<div>{pg.name}</div>
|
||||
<div className="text-xs opacity-80 font-normal mt-1">
|
||||
{pg.processCount} 个过程
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{/* 表体:知识领域 × 过程 */}
|
||||
<tbody>
|
||||
{knowledgeAreas.map((ka, kaIndex) => (
|
||||
<motion.tr
|
||||
key={ka.id}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: kaIndex * 0.05 }}
|
||||
>
|
||||
{/* 知识领域名称 */}
|
||||
<td
|
||||
className="sticky left-0 z-10 p-3 border border-gray-200 dark:border-gray-700 font-medium"
|
||||
style={{
|
||||
backgroundColor: `${ka.color}15`,
|
||||
borderLeftWidth: 4,
|
||||
borderLeftColor: ka.color,
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to={`/knowledge-areas/${ka.id}`}
|
||||
className="hover:underline text-gray-900 dark:text-white"
|
||||
>
|
||||
<span className="font-bold mr-2" style={{ color: ka.color }}>
|
||||
{ka.order}
|
||||
</span>
|
||||
{ka.name}
|
||||
</Link>
|
||||
</td>
|
||||
|
||||
{/* 每个过程组的单元格 */}
|
||||
{processGroups.map(pg => {
|
||||
const cellProcesses = matrix.get(ka.id)?.get(pg.id) || []
|
||||
return (
|
||||
<td
|
||||
key={pg.id}
|
||||
className="p-2 border border-gray-200 dark:border-gray-700 align-top bg-white dark:bg-gray-800"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
{cellProcesses.map(p => (
|
||||
<Link
|
||||
key={p.id}
|
||||
to={`/process/${p.id}`}
|
||||
state={{ from: 'matrix' }}
|
||||
className="block px-2 py-1.5 rounded text-xs hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<span
|
||||
className="inline-block px-1.5 py-0.5 rounded text-white font-medium mr-1"
|
||||
style={{ backgroundColor: ka.color, fontSize: '10px' }}
|
||||
>
|
||||
{p.code}
|
||||
</span>
|
||||
<span className="text-gray-700 dark:text-gray-300">
|
||||
{p.name}
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
{cellProcesses.length === 0 && (
|
||||
<div className="text-gray-300 dark:text-gray-600 text-xs text-center py-2">
|
||||
-
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</motion.tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
{/* 表尾:统计 */}
|
||||
<tfoot>
|
||||
<tr className="bg-gray-50 dark:bg-gray-800/50">
|
||||
<td className="sticky left-0 z-10 p-3 border border-gray-200 dark:border-gray-700 font-semibold text-gray-700 dark:text-gray-300 bg-gray-50 dark:bg-gray-800/50">
|
||||
合计:{processes.length} 个过程
|
||||
</td>
|
||||
{processGroups.map(pg => (
|
||||
<td
|
||||
key={pg.id}
|
||||
className="p-3 border border-gray-200 dark:border-gray-700 text-center font-semibold"
|
||||
style={{ color: pg.color }}
|
||||
>
|
||||
{pg.processCount}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
1
src/components/visualize/index.ts
Normal file
1
src/components/visualize/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { ProcessMatrix } from './ProcessMatrix'
|
||||
95
src/data/artifacts.json
Normal file
95
src/data/artifacts.json
Normal file
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{ "id": "A001", "name": "项目章程", "nameEn": "Project Charter", "category": "document" },
|
||||
{ "id": "A002", "name": "商业论证", "nameEn": "Business Case", "category": "document" },
|
||||
{ "id": "A003", "name": "效益管理计划", "nameEn": "Benefits Management Plan", "category": "plan" },
|
||||
{ "id": "A004", "name": "协议", "nameEn": "Agreements", "category": "document" },
|
||||
{ "id": "A005", "name": "事业环境因素", "nameEn": "Enterprise Environmental Factors", "category": "other" },
|
||||
{ "id": "A006", "name": "组织过程资产", "nameEn": "Organizational Process Assets", "category": "other" },
|
||||
{ "id": "A007", "name": "假设日志", "nameEn": "Assumption Log", "category": "log" },
|
||||
{ "id": "A008", "name": "项目管理计划", "nameEn": "Project Management Plan", "category": "plan" },
|
||||
{ "id": "A009", "name": "范围管理计划", "nameEn": "Scope Management Plan", "category": "plan" },
|
||||
{ "id": "A010", "name": "需求管理计划", "nameEn": "Requirements Management Plan", "category": "plan" },
|
||||
{ "id": "A011", "name": "进度管理计划", "nameEn": "Schedule Management Plan", "category": "plan" },
|
||||
{ "id": "A012", "name": "成本管理计划", "nameEn": "Cost Management Plan", "category": "plan" },
|
||||
{ "id": "A013", "name": "质量管理计划", "nameEn": "Quality Management Plan", "category": "plan" },
|
||||
{ "id": "A014", "name": "资源管理计划", "nameEn": "Resource Management Plan", "category": "plan" },
|
||||
{ "id": "A015", "name": "沟通管理计划", "nameEn": "Communications Management Plan", "category": "plan" },
|
||||
{ "id": "A016", "name": "风险管理计划", "nameEn": "Risk Management Plan", "category": "plan" },
|
||||
{ "id": "A017", "name": "采购管理计划", "nameEn": "Procurement Management Plan", "category": "plan" },
|
||||
{ "id": "A018", "name": "相关方参与计划", "nameEn": "Stakeholder Engagement Plan", "category": "plan" },
|
||||
{ "id": "A019", "name": "变更管理计划", "nameEn": "Change Management Plan", "category": "plan" },
|
||||
{ "id": "A020", "name": "配置管理计划", "nameEn": "Configuration Management Plan", "category": "plan" },
|
||||
{ "id": "A021", "name": "范围基准", "nameEn": "Scope Baseline", "category": "baseline" },
|
||||
{ "id": "A022", "name": "进度基准", "nameEn": "Schedule Baseline", "category": "baseline" },
|
||||
{ "id": "A023", "name": "成本基准", "nameEn": "Cost Baseline", "category": "baseline" },
|
||||
{ "id": "A024", "name": "绩效测量基准", "nameEn": "Performance Measurement Baseline", "category": "baseline" },
|
||||
{ "id": "A025", "name": "项目生命周期描述", "nameEn": "Project Life Cycle Description", "category": "document" },
|
||||
{ "id": "A026", "name": "开发方法", "nameEn": "Development Approach", "category": "document" },
|
||||
{ "id": "A027", "name": "项目范围说明书", "nameEn": "Project Scope Statement", "category": "document" },
|
||||
{ "id": "A028", "name": "需求文件", "nameEn": "Requirements Documentation", "category": "document" },
|
||||
{ "id": "A029", "name": "需求跟踪矩阵", "nameEn": "Requirements Traceability Matrix", "category": "document" },
|
||||
{ "id": "A030", "name": "工作分解结构(WBS)", "nameEn": "Work Breakdown Structure", "category": "document" },
|
||||
{ "id": "A031", "name": "WBS词典", "nameEn": "WBS Dictionary", "category": "document" },
|
||||
{ "id": "A032", "name": "活动清单", "nameEn": "Activity List", "category": "document" },
|
||||
{ "id": "A033", "name": "活动属性", "nameEn": "Activity Attributes", "category": "document" },
|
||||
{ "id": "A034", "name": "里程碑清单", "nameEn": "Milestone List", "category": "document" },
|
||||
{ "id": "A035", "name": "项目进度网络图", "nameEn": "Project Schedule Network Diagram", "category": "document" },
|
||||
{ "id": "A036", "name": "活动资源需求", "nameEn": "Activity Resource Requirements", "category": "document" },
|
||||
{ "id": "A037", "name": "资源分解结构", "nameEn": "Resource Breakdown Structure", "category": "document" },
|
||||
{ "id": "A038", "name": "持续时间估算", "nameEn": "Duration Estimates", "category": "document" },
|
||||
{ "id": "A039", "name": "估算依据", "nameEn": "Basis of Estimates", "category": "document" },
|
||||
{ "id": "A040", "name": "项目进度计划", "nameEn": "Project Schedule", "category": "document" },
|
||||
{ "id": "A041", "name": "进度数据", "nameEn": "Schedule Data", "category": "document" },
|
||||
{ "id": "A042", "name": "项目日历", "nameEn": "Project Calendars", "category": "document" },
|
||||
{ "id": "A043", "name": "成本估算", "nameEn": "Cost Estimates", "category": "document" },
|
||||
{ "id": "A044", "name": "项目资金需求", "nameEn": "Project Funding Requirements", "category": "document" },
|
||||
{ "id": "A045", "name": "质量测量指标", "nameEn": "Quality Metrics", "category": "document" },
|
||||
{ "id": "A046", "name": "质量报告", "nameEn": "Quality Reports", "category": "report" },
|
||||
{ "id": "A047", "name": "测试与评估文件", "nameEn": "Test and Evaluation Documents", "category": "document" },
|
||||
{ "id": "A048", "name": "团队章程", "nameEn": "Team Charter", "category": "document" },
|
||||
{ "id": "A049", "name": "资源日历", "nameEn": "Resource Calendars", "category": "document" },
|
||||
{ "id": "A050", "name": "项目团队派工单", "nameEn": "Project Team Assignments", "category": "document" },
|
||||
{ "id": "A051", "name": "物质资源分配单", "nameEn": "Physical Resource Assignments", "category": "document" },
|
||||
{ "id": "A052", "name": "团队绩效评价", "nameEn": "Team Performance Assessments", "category": "document" },
|
||||
{ "id": "A053", "name": "变更请求", "nameEn": "Change Requests", "category": "document" },
|
||||
{ "id": "A054", "name": "项目沟通记录", "nameEn": "Project Communications", "category": "document" },
|
||||
{ "id": "A055", "name": "风险登记册", "nameEn": "Risk Register", "category": "register" },
|
||||
{ "id": "A056", "name": "风险报告", "nameEn": "Risk Report", "category": "report" },
|
||||
{ "id": "A057", "name": "采购文件", "nameEn": "Procurement Documentation", "category": "document" },
|
||||
{ "id": "A058", "name": "采购工作说明书", "nameEn": "Procurement Statement of Work", "category": "document" },
|
||||
{ "id": "A059", "name": "招标文件", "nameEn": "Bid Documents", "category": "document" },
|
||||
{ "id": "A060", "name": "供方选择标准", "nameEn": "Source Selection Criteria", "category": "document" },
|
||||
{ "id": "A061", "name": "自制或外购决策", "nameEn": "Make-or-Buy Decisions", "category": "document" },
|
||||
{ "id": "A062", "name": "独立成本估算", "nameEn": "Independent Cost Estimates", "category": "document" },
|
||||
{ "id": "A063", "name": "选定的卖方", "nameEn": "Selected Sellers", "category": "document" },
|
||||
{ "id": "A064", "name": "相关方登记册", "nameEn": "Stakeholder Register", "category": "register" },
|
||||
{ "id": "A065", "name": "问题日志", "nameEn": "Issue Log", "category": "log" },
|
||||
{ "id": "A066", "name": "经验教训登记册", "nameEn": "Lessons Learned Register", "category": "register" },
|
||||
{ "id": "A067", "name": "工作绩效数据", "nameEn": "Work Performance Data", "category": "document" },
|
||||
{ "id": "A068", "name": "工作绩效信息", "nameEn": "Work Performance Information", "category": "document" },
|
||||
{ "id": "A069", "name": "工作绩效报告", "nameEn": "Work Performance Reports", "category": "report" },
|
||||
{ "id": "A070", "name": "可交付成果", "nameEn": "Deliverables", "category": "deliverable" },
|
||||
{ "id": "A071", "name": "核实的可交付成果", "nameEn": "Verified Deliverables", "category": "deliverable" },
|
||||
{ "id": "A072", "name": "验收的可交付成果", "nameEn": "Accepted Deliverables", "category": "deliverable" },
|
||||
{ "id": "A073", "name": "最终产品、服务或成果移交", "nameEn": "Final Product, Service, or Result Transition", "category": "deliverable" },
|
||||
{ "id": "A074", "name": "最终报告", "nameEn": "Final Report", "category": "report" },
|
||||
{ "id": "A075", "name": "组织过程资产更新", "nameEn": "Organizational Process Assets Updates", "category": "other" },
|
||||
{ "id": "A076", "name": "项目文件", "nameEn": "Project Documents", "category": "document" },
|
||||
{ "id": "A077", "name": "项目文件更新", "nameEn": "Project Documents Updates", "category": "document" },
|
||||
{ "id": "A078", "name": "项目管理计划更新", "nameEn": "Project Management Plan Updates", "category": "plan" },
|
||||
{ "id": "A079", "name": "批准的变更请求", "nameEn": "Approved Change Requests", "category": "document" },
|
||||
{ "id": "A080", "name": "变更日志", "nameEn": "Change Log", "category": "log" },
|
||||
{ "id": "A081", "name": "卖方建议书", "nameEn": "Seller Proposals", "category": "document" },
|
||||
{ "id": "A082", "name": "资源需求", "nameEn": "Resource Requirements", "category": "document" },
|
||||
{ "id": "A083", "name": "活动成本估算", "nameEn": "Activity Cost Estimates", "category": "document" },
|
||||
{ "id": "A084", "name": "质量控制测量结果", "nameEn": "Quality Control Measurements", "category": "document" },
|
||||
{ "id": "A085", "name": "预测", "nameEn": "Forecasts", "category": "document" },
|
||||
{ "id": "A086", "name": "成本预测", "nameEn": "Cost Forecasts", "category": "document" },
|
||||
{ "id": "A087", "name": "进度预测", "nameEn": "Schedule Forecasts", "category": "document" },
|
||||
{ "id": "A088", "name": "关闭的采购", "nameEn": "Closed Procurements", "category": "document" },
|
||||
{ "id": "A089", "name": "卖方绩效评价文档", "nameEn": "Seller Performance Evaluation Documentation", "category": "document" },
|
||||
{ "id": "A090", "name": "采购策略", "nameEn": "Procurement Strategy", "category": "document" },
|
||||
{ "id": "A091", "name": "采购文档更新", "nameEn": "Procurement Documentation Updates", "category": "document" }
|
||||
]
|
||||
}
|
||||
126
src/data/index.ts
Normal file
126
src/data/index.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* ITTO数据索引
|
||||
* 统一导出所有ITTO核心数据
|
||||
*/
|
||||
|
||||
import knowledgeAreasData from './knowledge-areas.json';
|
||||
import processGroupsData from './process-groups.json';
|
||||
import processesData from './processes.json';
|
||||
import artifactsData from './artifacts.json';
|
||||
import toolsData from './tools.json';
|
||||
|
||||
import type {
|
||||
KnowledgeArea,
|
||||
ProcessGroup,
|
||||
Process,
|
||||
Artifact,
|
||||
ToolTechnique,
|
||||
DataFlow,
|
||||
} from '../types/itto';
|
||||
|
||||
// 导出原始数据
|
||||
export const knowledgeAreas: KnowledgeArea[] = knowledgeAreasData.knowledgeAreas as KnowledgeArea[];
|
||||
export const processGroups: ProcessGroup[] = processGroupsData.processGroups as ProcessGroup[];
|
||||
export const processes: Process[] = processesData.processes as Process[];
|
||||
export const artifacts: Artifact[] = artifactsData.artifacts as Artifact[];
|
||||
export const tools: ToolTechnique[] = toolsData.tools as ToolTechnique[];
|
||||
|
||||
// 创建查找映射表
|
||||
export const knowledgeAreaMap = new Map<string, KnowledgeArea>(
|
||||
knowledgeAreas.map(ka => [ka.id, ka])
|
||||
);
|
||||
|
||||
export const processGroupMap = new Map<string, ProcessGroup>(
|
||||
processGroups.map(pg => [pg.id, pg])
|
||||
);
|
||||
|
||||
export const processMap = new Map<string, Process>(
|
||||
processes.map(p => [p.id, p])
|
||||
);
|
||||
|
||||
export const artifactMap = new Map<string, Artifact>(
|
||||
artifacts.map(a => [a.id, a])
|
||||
);
|
||||
|
||||
export const toolMap = new Map<string, ToolTechnique>(
|
||||
tools.map(t => [t.id, t])
|
||||
);
|
||||
|
||||
// 按知识领域分组的过程
|
||||
export const processesByKnowledgeArea = new Map<string, Process[]>();
|
||||
knowledgeAreas.forEach(ka => {
|
||||
processesByKnowledgeArea.set(
|
||||
ka.id,
|
||||
processes.filter(p => p.knowledgeAreaId === ka.id).sort((a, b) => a.order - b.order)
|
||||
);
|
||||
});
|
||||
|
||||
// 按过程组分组的过程
|
||||
export const processesByProcessGroup = new Map<string, Process[]>();
|
||||
processGroups.forEach(pg => {
|
||||
processesByProcessGroup.set(
|
||||
pg.id,
|
||||
processes.filter(p => p.processGroupId === pg.id)
|
||||
);
|
||||
});
|
||||
|
||||
// 计算数据流向关系
|
||||
export function computeDataFlows(): DataFlow[] {
|
||||
const flows: DataFlow[] = [];
|
||||
|
||||
processes.forEach(sourceProcess => {
|
||||
sourceProcess.outputs.forEach(outputId => {
|
||||
processes.forEach(targetProcess => {
|
||||
if (targetProcess.id !== sourceProcess.id && targetProcess.inputs.includes(outputId)) {
|
||||
flows.push({
|
||||
sourceProcessId: sourceProcess.id,
|
||||
targetProcessId: targetProcess.id,
|
||||
artifactId: outputId,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return flows;
|
||||
}
|
||||
|
||||
// 获取工件的使用情况
|
||||
export function getArtifactUsage(artifactId: string): {
|
||||
asInput: Process[];
|
||||
asOutput: Process[];
|
||||
} {
|
||||
return {
|
||||
asInput: processes.filter(p => p.inputs.includes(artifactId)),
|
||||
asOutput: processes.filter(p => p.outputs.includes(artifactId)),
|
||||
};
|
||||
}
|
||||
|
||||
// 获取工具的使用情况
|
||||
export function getToolUsage(toolId: string): Process[] {
|
||||
return processes.filter(p => p.tools.includes(toolId));
|
||||
}
|
||||
|
||||
// 获取过程的完整信息(包含关联数据)
|
||||
export function getProcessDetail(processId: string) {
|
||||
const process = processMap.get(processId);
|
||||
if (!process) return null;
|
||||
|
||||
return {
|
||||
...process,
|
||||
knowledgeArea: knowledgeAreaMap.get(process.knowledgeAreaId),
|
||||
processGroup: processGroupMap.get(process.processGroupId),
|
||||
inputDetails: process.inputs.map(id => artifactMap.get(id)).filter(Boolean),
|
||||
toolDetails: process.tools.map(id => toolMap.get(id)).filter(Boolean),
|
||||
outputDetails: process.outputs.map(id => artifactMap.get(id)).filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
// 统计信息
|
||||
export const stats = {
|
||||
knowledgeAreaCount: knowledgeAreas.length,
|
||||
processGroupCount: processGroups.length,
|
||||
processCount: processes.length,
|
||||
artifactCount: artifacts.length,
|
||||
toolCount: tools.length,
|
||||
};
|
||||
104
src/data/knowledge-areas.json
Normal file
104
src/data/knowledge-areas.json
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"knowledgeAreas": [
|
||||
{
|
||||
"id": "KA01",
|
||||
"name": "项目整合管理",
|
||||
"nameEn": "Project Integration Management",
|
||||
"chapter": 4,
|
||||
"order": 1,
|
||||
"color": "#6366F1",
|
||||
"description": "包括识别、定义、组合、统一和协调各项目管理过程组的各种过程和活动",
|
||||
"processCount": 7
|
||||
},
|
||||
{
|
||||
"id": "KA02",
|
||||
"name": "项目范围管理",
|
||||
"nameEn": "Project Scope Management",
|
||||
"chapter": 5,
|
||||
"order": 2,
|
||||
"color": "#8B5CF6",
|
||||
"description": "确保项目包含且只包含成功完成项目所需的全部工作",
|
||||
"processCount": 6
|
||||
},
|
||||
{
|
||||
"id": "KA03",
|
||||
"name": "项目进度管理",
|
||||
"nameEn": "Project Schedule Management",
|
||||
"chapter": 6,
|
||||
"order": 3,
|
||||
"color": "#EC4899",
|
||||
"description": "管理项目按时完成所需的各个过程",
|
||||
"processCount": 6
|
||||
},
|
||||
{
|
||||
"id": "KA04",
|
||||
"name": "项目成本管理",
|
||||
"nameEn": "Project Cost Management",
|
||||
"chapter": 7,
|
||||
"order": 4,
|
||||
"color": "#10B981",
|
||||
"description": "规划、估算、预算、融资、筹资、管理和控制成本的各过程",
|
||||
"processCount": 4
|
||||
},
|
||||
{
|
||||
"id": "KA05",
|
||||
"name": "项目质量管理",
|
||||
"nameEn": "Project Quality Management",
|
||||
"chapter": 8,
|
||||
"order": 5,
|
||||
"color": "#F59E0B",
|
||||
"description": "把组织的质量政策应用于规划、管理、控制项目和产品质量要求",
|
||||
"processCount": 3
|
||||
},
|
||||
{
|
||||
"id": "KA06",
|
||||
"name": "项目资源管理",
|
||||
"nameEn": "Project Resource Management",
|
||||
"chapter": 9,
|
||||
"order": 6,
|
||||
"color": "#3B82F6",
|
||||
"description": "识别、获取和管理所需资源以成功完成项目",
|
||||
"processCount": 6
|
||||
},
|
||||
{
|
||||
"id": "KA07",
|
||||
"name": "项目沟通管理",
|
||||
"nameEn": "Project Communications Management",
|
||||
"chapter": 10,
|
||||
"order": 7,
|
||||
"color": "#06B6D4",
|
||||
"description": "确保项目信息及时且恰当地规划、收集、生成、发布、存储、检索、管理、控制、监督和最终处置",
|
||||
"processCount": 3
|
||||
},
|
||||
{
|
||||
"id": "KA08",
|
||||
"name": "项目风险管理",
|
||||
"nameEn": "Project Risk Management",
|
||||
"chapter": 11,
|
||||
"order": 8,
|
||||
"color": "#EF4444",
|
||||
"description": "规划风险管理、识别风险、开展风险分析、规划风险应对、实施风险应对和监督风险的各个过程",
|
||||
"processCount": 7
|
||||
},
|
||||
{
|
||||
"id": "KA09",
|
||||
"name": "项目采购管理",
|
||||
"nameEn": "Project Procurement Management",
|
||||
"chapter": 12,
|
||||
"order": 9,
|
||||
"color": "#84CC16",
|
||||
"description": "从项目团队外部采购或获取所需产品、服务或成果",
|
||||
"processCount": 3
|
||||
},
|
||||
{
|
||||
"id": "KA10",
|
||||
"name": "项目相关方管理",
|
||||
"nameEn": "Project Stakeholder Management",
|
||||
"chapter": 13,
|
||||
"order": 10,
|
||||
"color": "#F97316",
|
||||
"description": "识别影响或受项目影响的人员、团体或组织,分析其期望和影响,制定管理策略",
|
||||
"processCount": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
49
src/data/process-groups.json
Normal file
49
src/data/process-groups.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"processGroups": [
|
||||
{
|
||||
"id": "PG01",
|
||||
"name": "启动过程组",
|
||||
"nameEn": "Initiating Process Group",
|
||||
"order": 1,
|
||||
"color": "#22C55E",
|
||||
"description": "定义一个新项目或现有项目的一个新阶段,授权开始该项目或阶段",
|
||||
"processCount": 2
|
||||
},
|
||||
{
|
||||
"id": "PG02",
|
||||
"name": "规划过程组",
|
||||
"nameEn": "Planning Process Group",
|
||||
"order": 2,
|
||||
"color": "#3B82F6",
|
||||
"description": "明确项目范围,优化目标,为实现目标制定行动方案",
|
||||
"processCount": 24
|
||||
},
|
||||
{
|
||||
"id": "PG03",
|
||||
"name": "执行过程组",
|
||||
"nameEn": "Executing Process Group",
|
||||
"order": 3,
|
||||
"color": "#F59E0B",
|
||||
"description": "完成项目管理计划中确定的工作,以满足项目要求",
|
||||
"processCount": 10
|
||||
},
|
||||
{
|
||||
"id": "PG04",
|
||||
"name": "监控过程组",
|
||||
"nameEn": "Monitoring and Controlling Process Group",
|
||||
"order": 4,
|
||||
"color": "#8B5CF6",
|
||||
"description": "跟踪、审查和调整项目进展与绩效,识别必要的计划变更并启动相应变更",
|
||||
"processCount": 12
|
||||
},
|
||||
{
|
||||
"id": "PG05",
|
||||
"name": "收尾过程组",
|
||||
"nameEn": "Closing Process Group",
|
||||
"order": 5,
|
||||
"color": "#6B7280",
|
||||
"description": "正式完成或关闭项目、阶段或合同",
|
||||
"processCount": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
593
src/data/processes.json
Normal file
593
src/data/processes.json
Normal file
@@ -0,0 +1,593 @@
|
||||
{
|
||||
"processes": [
|
||||
{
|
||||
"id": "P1.1",
|
||||
"code": "1.1",
|
||||
"name": "制定项目章程",
|
||||
"nameEn": "Develop Project Charter",
|
||||
"knowledgeAreaId": "KA01",
|
||||
"processGroupId": "PG01",
|
||||
"order": 1,
|
||||
"inputs": ["A002", "A004", "A003", "A005", "A006"],
|
||||
"tools": ["TT001", "TT002", "TT022", "TT032"],
|
||||
"outputs": ["A001", "A007"]
|
||||
},
|
||||
{
|
||||
"id": "P1.2",
|
||||
"code": "1.2",
|
||||
"name": "制定项目管理计划",
|
||||
"nameEn": "Develop Project Management Plan",
|
||||
"knowledgeAreaId": "KA01",
|
||||
"processGroupId": "PG02",
|
||||
"order": 2,
|
||||
"inputs": ["A001", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT002", "TT022", "TT032"],
|
||||
"outputs": ["A008"]
|
||||
},
|
||||
{
|
||||
"id": "P1.3",
|
||||
"code": "1.3",
|
||||
"name": "指导与管理项目工作",
|
||||
"nameEn": "Direct and Manage Project Work",
|
||||
"knowledgeAreaId": "KA01",
|
||||
"processGroupId": "PG03",
|
||||
"order": 3,
|
||||
"inputs": ["A008", "A076", "A079", "A005", "A006"],
|
||||
"tools": ["TT001", "TT033", "TT032"],
|
||||
"outputs": ["A070", "A067", "A065", "A053", "A078", "A077", "A075"]
|
||||
},
|
||||
{
|
||||
"id": "P1.4",
|
||||
"code": "1.4",
|
||||
"name": "管理项目知识",
|
||||
"nameEn": "Manage Project Knowledge",
|
||||
"knowledgeAreaId": "KA01",
|
||||
"processGroupId": "PG03",
|
||||
"order": 4,
|
||||
"inputs": ["A008", "A076", "A070", "A005", "A006"],
|
||||
"tools": ["TT001", "TT110", "TT111", "TT022"],
|
||||
"outputs": ["A066", "A078", "A075"]
|
||||
},
|
||||
{
|
||||
"id": "P1.5",
|
||||
"code": "1.5",
|
||||
"name": "监控项目工作",
|
||||
"nameEn": "Monitor and Control Project Work",
|
||||
"knowledgeAreaId": "KA01",
|
||||
"processGroupId": "PG04",
|
||||
"order": 5,
|
||||
"inputs": ["A008", "A076", "A068", "A004", "A005", "A006"],
|
||||
"tools": ["TT001", "TT008", "TT018", "TT032"],
|
||||
"outputs": ["A069", "A053", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P1.6",
|
||||
"code": "1.6",
|
||||
"name": "实施整体变更控制",
|
||||
"nameEn": "Perform Integrated Change Control",
|
||||
"knowledgeAreaId": "KA01",
|
||||
"processGroupId": "PG04",
|
||||
"order": 6,
|
||||
"inputs": ["A008", "A076", "A069", "A053", "A005", "A006"],
|
||||
"tools": ["TT001", "TT108", "TT008", "TT018", "TT032"],
|
||||
"outputs": ["A079", "A080", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P1.7",
|
||||
"code": "1.7",
|
||||
"name": "结束项目或阶段",
|
||||
"nameEn": "Close Project or Phase",
|
||||
"knowledgeAreaId": "KA01",
|
||||
"processGroupId": "PG05",
|
||||
"order": 7,
|
||||
"inputs": ["A001", "A008", "A076", "A072", "A005", "A006"],
|
||||
"tools": ["TT001", "TT008", "TT032"],
|
||||
"outputs": ["A078", "A073", "A074", "A075"]
|
||||
},
|
||||
{
|
||||
"id": "P2.1",
|
||||
"code": "2.1",
|
||||
"name": "规划范围管理",
|
||||
"nameEn": "Plan Scope Management",
|
||||
"knowledgeAreaId": "KA02",
|
||||
"processGroupId": "PG02",
|
||||
"order": 1,
|
||||
"inputs": ["A001", "A008", "A005", "A006"],
|
||||
"tools": ["TT001", "TT008", "TT032"],
|
||||
"outputs": ["A009", "A010"]
|
||||
},
|
||||
{
|
||||
"id": "P2.2",
|
||||
"code": "2.2",
|
||||
"name": "收集需求",
|
||||
"nameEn": "Collect Requirements",
|
||||
"knowledgeAreaId": "KA02",
|
||||
"processGroupId": "PG02",
|
||||
"order": 2,
|
||||
"inputs": ["A001", "A008", "A076", "A004", "A005", "A006"],
|
||||
"tools": ["TT001", "TT002", "TT008", "TT018", "TT034", "TT022", "TT115"],
|
||||
"outputs": ["A028", "A029"]
|
||||
},
|
||||
{
|
||||
"id": "P2.3",
|
||||
"code": "2.3",
|
||||
"name": "定义范围",
|
||||
"nameEn": "Define Scope",
|
||||
"knowledgeAreaId": "KA02",
|
||||
"processGroupId": "PG02",
|
||||
"order": 3,
|
||||
"inputs": ["A001", "A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT008", "TT018", "TT022", "TT114"],
|
||||
"outputs": ["A027", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P2.4",
|
||||
"code": "2.4",
|
||||
"name": "创建WBS",
|
||||
"nameEn": "Create WBS",
|
||||
"knowledgeAreaId": "KA02",
|
||||
"processGroupId": "PG02",
|
||||
"order": 4,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT059"],
|
||||
"outputs": ["A021", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P2.5",
|
||||
"code": "2.5",
|
||||
"name": "确认范围",
|
||||
"nameEn": "Validate Scope",
|
||||
"knowledgeAreaId": "KA02",
|
||||
"processGroupId": "PG04",
|
||||
"order": 5,
|
||||
"inputs": ["A008", "A076", "A071", "A067", "A005", "A006"],
|
||||
"tools": ["TT065", "TT018"],
|
||||
"outputs": ["A072", "A068", "A053", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P2.6",
|
||||
"code": "2.6",
|
||||
"name": "控制范围",
|
||||
"nameEn": "Control Scope",
|
||||
"knowledgeAreaId": "KA02",
|
||||
"processGroupId": "PG04",
|
||||
"order": 6,
|
||||
"inputs": ["A008", "A076", "A067", "A005", "A006"],
|
||||
"tools": ["TT008"],
|
||||
"outputs": ["A068", "A053", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P3.1",
|
||||
"code": "3.1",
|
||||
"name": "规划进度管理",
|
||||
"nameEn": "Plan Schedule Management",
|
||||
"knowledgeAreaId": "KA03",
|
||||
"processGroupId": "PG02",
|
||||
"order": 1,
|
||||
"inputs": ["A001", "A008", "A005", "A006"],
|
||||
"tools": ["TT001", "TT008", "TT032"],
|
||||
"outputs": ["A011"]
|
||||
},
|
||||
{
|
||||
"id": "P3.2",
|
||||
"code": "3.2",
|
||||
"name": "定义活动",
|
||||
"nameEn": "Define Activities",
|
||||
"knowledgeAreaId": "KA03",
|
||||
"processGroupId": "PG02",
|
||||
"order": 2,
|
||||
"inputs": ["A008", "A005", "A006"],
|
||||
"tools": ["TT001", "TT059", "TT060", "TT032"],
|
||||
"outputs": ["A032", "A033", "A034", "A053", "A078"]
|
||||
},
|
||||
{
|
||||
"id": "P3.3",
|
||||
"code": "3.3",
|
||||
"name": "排列活动顺序",
|
||||
"nameEn": "Sequence Activities",
|
||||
"knowledgeAreaId": "KA03",
|
||||
"processGroupId": "PG02",
|
||||
"order": 3,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT061", "TT062", "TT050", "TT033"],
|
||||
"outputs": ["A035", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P3.4",
|
||||
"code": "3.4",
|
||||
"name": "估算活动持续时间",
|
||||
"nameEn": "Estimate Activity Durations",
|
||||
"knowledgeAreaId": "KA03",
|
||||
"processGroupId": "PG02",
|
||||
"order": 4,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT055", "TT056", "TT057", "TT058", "TT018", "TT032"],
|
||||
"outputs": ["A038", "A039", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P3.5",
|
||||
"code": "3.5",
|
||||
"name": "制定进度计划",
|
||||
"nameEn": "Develop Schedule",
|
||||
"knowledgeAreaId": "KA03",
|
||||
"processGroupId": "PG02",
|
||||
"order": 5,
|
||||
"inputs": ["A008", "A076", "A004", "A005", "A006"],
|
||||
"tools": ["TT045", "TT046", "TT047", "TT008", "TT050", "TT051", "TT033", "TT063"],
|
||||
"outputs": ["A022", "A040", "A041", "A042", "A053", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P3.6",
|
||||
"code": "3.6",
|
||||
"name": "控制进度",
|
||||
"nameEn": "Control Schedule",
|
||||
"knowledgeAreaId": "KA03",
|
||||
"processGroupId": "PG04",
|
||||
"order": 6,
|
||||
"inputs": ["A008", "A076", "A067", "A005", "A006"],
|
||||
"tools": ["TT008", "TT046", "TT047", "TT050", "TT051", "TT033"],
|
||||
"outputs": ["A068", "A087", "A053", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P4.1",
|
||||
"code": "4.1",
|
||||
"name": "规划成本管理",
|
||||
"nameEn": "Plan Cost Management",
|
||||
"knowledgeAreaId": "KA04",
|
||||
"processGroupId": "PG02",
|
||||
"order": 1,
|
||||
"inputs": ["A001", "A008", "A005", "A006"],
|
||||
"tools": ["TT001", "TT008", "TT032"],
|
||||
"outputs": ["A012"]
|
||||
},
|
||||
{
|
||||
"id": "P4.2",
|
||||
"code": "4.2",
|
||||
"name": "估算成本",
|
||||
"nameEn": "Estimate Costs",
|
||||
"knowledgeAreaId": "KA04",
|
||||
"processGroupId": "PG02",
|
||||
"order": 2,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT055", "TT056", "TT057", "TT058", "TT008", "TT018", "TT033"],
|
||||
"outputs": ["A043", "A039", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P4.3",
|
||||
"code": "4.3",
|
||||
"name": "制定预算",
|
||||
"nameEn": "Determine Budget",
|
||||
"knowledgeAreaId": "KA04",
|
||||
"processGroupId": "PG02",
|
||||
"order": 3,
|
||||
"inputs": ["A008", "A076", "A004", "A005", "A006"],
|
||||
"tools": ["TT001", "TT010", "TT008", "TT016", "TT044"],
|
||||
"outputs": ["A023", "A044", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P4.4",
|
||||
"code": "4.4",
|
||||
"name": "控制成本",
|
||||
"nameEn": "Control Costs",
|
||||
"knowledgeAreaId": "KA04",
|
||||
"processGroupId": "PG04",
|
||||
"order": 4,
|
||||
"inputs": ["A008", "A076", "A044", "A067", "A005", "A006"],
|
||||
"tools": ["TT001", "TT008", "TT011", "TT013", "TT014", "TT016", "TT033"],
|
||||
"outputs": ["A068", "A086", "A053", "A078", "A077"]
|
||||
}
|
||||
,
|
||||
{
|
||||
"id": "P5.1",
|
||||
"code": "5.1",
|
||||
"name": "规划质量管理",
|
||||
"nameEn": "Plan Quality Management",
|
||||
"knowledgeAreaId": "KA05",
|
||||
"processGroupId": "PG02",
|
||||
"order": 1,
|
||||
"inputs": ["A001", "A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT002", "TT008", "TT018", "TT034", "TT066", "TT064", "TT032"],
|
||||
"outputs": ["A013", "A045", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P5.2",
|
||||
"code": "5.2",
|
||||
"name": "管理质量",
|
||||
"nameEn": "Manage Quality",
|
||||
"knowledgeAreaId": "KA05",
|
||||
"processGroupId": "PG03",
|
||||
"order": 2,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT002", "TT008", "TT018", "TT034", "TT067", "TT068", "TT069", "TT070"],
|
||||
"outputs": ["A046", "A047", "A053", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P5.3",
|
||||
"code": "5.3",
|
||||
"name": "控制质量",
|
||||
"nameEn": "Control Quality",
|
||||
"knowledgeAreaId": "KA05",
|
||||
"processGroupId": "PG04",
|
||||
"order": 3,
|
||||
"inputs": ["A008", "A076", "A079", "A070", "A067", "A005", "A006"],
|
||||
"tools": ["TT002", "TT008", "TT065", "TT066", "TT034"],
|
||||
"outputs": ["A084", "A071", "A068", "A053", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P6.1",
|
||||
"code": "6.1",
|
||||
"name": "规划资源管理",
|
||||
"nameEn": "Plan Resource Management",
|
||||
"knowledgeAreaId": "KA06",
|
||||
"processGroupId": "PG02",
|
||||
"order": 1,
|
||||
"inputs": ["A001", "A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT034", "TT112", "TT032"],
|
||||
"outputs": ["A014", "A048", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P6.2",
|
||||
"code": "6.2",
|
||||
"name": "估算活动资源",
|
||||
"nameEn": "Estimate Activity Resources",
|
||||
"knowledgeAreaId": "KA06",
|
||||
"processGroupId": "PG02",
|
||||
"order": 2,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT057", "TT055", "TT056", "TT008", "TT033", "TT032"],
|
||||
"outputs": ["A082", "A039", "A037", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P6.3",
|
||||
"code": "6.3",
|
||||
"name": "获取资源",
|
||||
"nameEn": "Acquire Resources",
|
||||
"knowledgeAreaId": "KA06",
|
||||
"processGroupId": "PG03",
|
||||
"order": 3,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT018", "TT022", "TT107", "TT102"],
|
||||
"outputs": ["A051", "A050", "A049", "A053", "A078", "A077", "A005"]
|
||||
},
|
||||
{
|
||||
"id": "P6.4",
|
||||
"code": "6.4",
|
||||
"name": "建设团队",
|
||||
"nameEn": "Develop Team",
|
||||
"knowledgeAreaId": "KA06",
|
||||
"processGroupId": "PG03",
|
||||
"order": 4,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT101", "TT102", "TT086", "TT022", "TT103", "TT104", "TT105", "TT032"],
|
||||
"outputs": ["A052", "A053", "A078", "A077", "A005"]
|
||||
},
|
||||
{
|
||||
"id": "P6.5",
|
||||
"code": "6.5",
|
||||
"name": "管理团队",
|
||||
"nameEn": "Manage Team",
|
||||
"knowledgeAreaId": "KA06",
|
||||
"processGroupId": "PG03",
|
||||
"order": 5,
|
||||
"inputs": ["A008", "A076", "A052", "A067", "A005", "A006"],
|
||||
"tools": ["TT022", "TT033"],
|
||||
"outputs": ["A053", "A078", "A077", "A005"]
|
||||
},
|
||||
{
|
||||
"id": "P6.6",
|
||||
"code": "6.6",
|
||||
"name": "控制资源",
|
||||
"nameEn": "Control Resources",
|
||||
"knowledgeAreaId": "KA06",
|
||||
"processGroupId": "PG04",
|
||||
"order": 6,
|
||||
"inputs": ["A008", "A076", "A067", "A004", "A005", "A006"],
|
||||
"tools": ["TT008", "TT069", "TT022", "TT033"],
|
||||
"outputs": ["A068", "A053", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P7.1",
|
||||
"code": "7.1",
|
||||
"name": "规划沟通管理",
|
||||
"nameEn": "Plan Communications Management",
|
||||
"knowledgeAreaId": "KA07",
|
||||
"processGroupId": "PG02",
|
||||
"order": 1,
|
||||
"inputs": ["A001", "A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT085", "TT086", "TT087", "TT008", "TT022", "TT034", "TT032"],
|
||||
"outputs": ["A015", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P7.2",
|
||||
"code": "7.2",
|
||||
"name": "管理沟通",
|
||||
"nameEn": "Manage Communications",
|
||||
"knowledgeAreaId": "KA07",
|
||||
"processGroupId": "PG03",
|
||||
"order": 2,
|
||||
"inputs": ["A008", "A076", "A069", "A005", "A006"],
|
||||
"tools": ["TT086", "TT085", "TT088", "TT033", "TT090", "TT032"],
|
||||
"outputs": ["A054", "A078", "A077", "A075"]
|
||||
},
|
||||
{
|
||||
"id": "P7.3",
|
||||
"code": "7.3",
|
||||
"name": "监督沟通",
|
||||
"nameEn": "Monitor Communications",
|
||||
"knowledgeAreaId": "KA07",
|
||||
"processGroupId": "PG04",
|
||||
"order": 3,
|
||||
"inputs": ["A008", "A076", "A067", "A005", "A006"],
|
||||
"tools": ["TT001", "TT033", "TT022", "TT032"],
|
||||
"outputs": ["A068", "A053", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P8.1",
|
||||
"code": "8.1",
|
||||
"name": "规划风险管理",
|
||||
"nameEn": "Plan Risk Management",
|
||||
"knowledgeAreaId": "KA08",
|
||||
"processGroupId": "PG02",
|
||||
"order": 1,
|
||||
"inputs": ["A001", "A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT008", "TT032"],
|
||||
"outputs": ["A016"]
|
||||
},
|
||||
{
|
||||
"id": "P8.2",
|
||||
"code": "8.2",
|
||||
"name": "识别风险",
|
||||
"nameEn": "Identify Risks",
|
||||
"knowledgeAreaId": "KA08",
|
||||
"processGroupId": "PG02",
|
||||
"order": 2,
|
||||
"inputs": ["A008", "A076", "A004", "A057", "A005", "A006"],
|
||||
"tools": ["TT001", "TT002", "TT008", "TT022", "TT120", "TT032"],
|
||||
"outputs": ["A055", "A056", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P8.3",
|
||||
"code": "8.3",
|
||||
"name": "实施定性风险分析",
|
||||
"nameEn": "Perform Qualitative Risk Analysis",
|
||||
"knowledgeAreaId": "KA08",
|
||||
"processGroupId": "PG02",
|
||||
"order": 3,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT002", "TT008", "TT022", "TT071", "TT044", "TT072", "TT034", "TT032"],
|
||||
"outputs": ["A077"]
|
||||
},
|
||||
{
|
||||
"id": "P8.4",
|
||||
"code": "8.4",
|
||||
"name": "实施定量风险分析",
|
||||
"nameEn": "Perform Quantitative Risk Analysis",
|
||||
"knowledgeAreaId": "KA08",
|
||||
"processGroupId": "PG02",
|
||||
"order": 4,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT002", "TT022", "TT034", "TT075", "TT079", "TT080", "TT077"],
|
||||
"outputs": ["A077"]
|
||||
},
|
||||
{
|
||||
"id": "P8.5",
|
||||
"code": "8.5",
|
||||
"name": "规划风险应对",
|
||||
"nameEn": "Plan Risk Responses",
|
||||
"knowledgeAreaId": "KA08",
|
||||
"processGroupId": "PG02",
|
||||
"order": 5,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT002", "TT022", "TT081", "TT082", "TT083", "TT084", "TT008", "TT018"],
|
||||
"outputs": ["A053", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P8.6",
|
||||
"code": "8.6",
|
||||
"name": "实施风险应对",
|
||||
"nameEn": "Implement Risk Responses",
|
||||
"knowledgeAreaId": "KA08",
|
||||
"processGroupId": "PG03",
|
||||
"order": 6,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT022", "TT033"],
|
||||
"outputs": ["A053", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P8.7",
|
||||
"code": "8.7",
|
||||
"name": "监督风险",
|
||||
"nameEn": "Monitor Risks",
|
||||
"knowledgeAreaId": "KA08",
|
||||
"processGroupId": "PG04",
|
||||
"order": 7,
|
||||
"inputs": ["A008", "A076", "A067", "A069", "A005", "A006"],
|
||||
"tools": ["TT008", "TT067", "TT032"],
|
||||
"outputs": ["A068", "A053", "A078", "A077", "A075"]
|
||||
},
|
||||
{
|
||||
"id": "P9.1",
|
||||
"code": "9.1",
|
||||
"name": "规划采购管理",
|
||||
"nameEn": "Plan Procurement Management",
|
||||
"knowledgeAreaId": "KA09",
|
||||
"processGroupId": "PG02",
|
||||
"order": 1,
|
||||
"inputs": ["A001", "A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT002", "TT092", "TT008", "TT032"],
|
||||
"outputs": ["A017", "A091", "A059", "A058", "A060", "A061", "A062", "A053", "A077", "A075"]
|
||||
},
|
||||
{
|
||||
"id": "P9.2",
|
||||
"code": "9.2",
|
||||
"name": "实施采购",
|
||||
"nameEn": "Conduct Procurements",
|
||||
"knowledgeAreaId": "KA09",
|
||||
"processGroupId": "PG03",
|
||||
"order": 2,
|
||||
"inputs": ["A008", "A076", "A057", "A081", "A005", "A006"],
|
||||
"tools": ["TT001", "TT093", "TT008", "TT022", "TT094"],
|
||||
"outputs": ["A063", "A004", "A053", "A078", "A077", "A075"]
|
||||
},
|
||||
{
|
||||
"id": "P9.3",
|
||||
"code": "9.3",
|
||||
"name": "控制采购",
|
||||
"nameEn": "Control Procurements",
|
||||
"knowledgeAreaId": "KA09",
|
||||
"processGroupId": "PG04",
|
||||
"order": 3,
|
||||
"inputs": ["A008", "A076", "A057", "A004", "A079", "A067", "A005", "A006"],
|
||||
"tools": ["TT001", "TT095", "TT008", "TT065", "TT067"],
|
||||
"outputs": ["A088", "A068", "A057", "A053", "A078", "A077", "A075"]
|
||||
},
|
||||
{
|
||||
"id": "P10.1",
|
||||
"code": "10.1",
|
||||
"name": "识别相关方",
|
||||
"nameEn": "Identify Stakeholders",
|
||||
"knowledgeAreaId": "KA10",
|
||||
"processGroupId": "PG01",
|
||||
"order": 1,
|
||||
"inputs": ["A001", "A008", "A076", "A004", "A005", "A006"],
|
||||
"tools": ["TT001", "TT002", "TT008", "TT034", "TT032"],
|
||||
"outputs": ["A064", "A053", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P10.2",
|
||||
"code": "10.2",
|
||||
"name": "规划相关方参与",
|
||||
"nameEn": "Plan Stakeholder Engagement",
|
||||
"knowledgeAreaId": "KA10",
|
||||
"processGroupId": "PG02",
|
||||
"order": 2,
|
||||
"inputs": ["A001", "A008", "A076", "A004", "A005", "A006"],
|
||||
"tools": ["TT001", "TT002", "TT018", "TT034", "TT032"],
|
||||
"outputs": ["A018"]
|
||||
},
|
||||
{
|
||||
"id": "P10.3",
|
||||
"code": "10.3",
|
||||
"name": "管理相关方参与",
|
||||
"nameEn": "Manage Stakeholder Engagement",
|
||||
"knowledgeAreaId": "KA10",
|
||||
"processGroupId": "PG03",
|
||||
"order": 3,
|
||||
"inputs": ["A008", "A076", "A005", "A006"],
|
||||
"tools": ["TT001", "TT088", "TT022", "TT100", "TT032"],
|
||||
"outputs": ["A053", "A078", "A077"]
|
||||
},
|
||||
{
|
||||
"id": "P10.4",
|
||||
"code": "10.4",
|
||||
"name": "监督相关方参与",
|
||||
"nameEn": "Monitor Stakeholder Engagement",
|
||||
"knowledgeAreaId": "KA10",
|
||||
"processGroupId": "PG04",
|
||||
"order": 4,
|
||||
"inputs": ["A008", "A076", "A067", "A005", "A006"],
|
||||
"tools": ["TT008", "TT018", "TT034", "TT088", "TT032"],
|
||||
"outputs": ["A068", "A053", "A078", "A077"]
|
||||
}
|
||||
]
|
||||
}
|
||||
0
src/data/processes_new.json
Normal file
0
src/data/processes_new.json
Normal file
124
src/data/tools.json
Normal file
124
src/data/tools.json
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"tools": [
|
||||
{ "id": "TT001", "name": "专家判断", "nameEn": "Expert Judgment", "type": "technique", "category": "general" },
|
||||
{ "id": "TT002", "name": "数据收集", "nameEn": "Data Gathering", "type": "technique", "category": "general" },
|
||||
{ "id": "TT003", "name": "头脑风暴", "nameEn": "Brainstorming", "type": "technique", "category": "data-gathering" },
|
||||
{ "id": "TT004", "name": "焦点小组", "nameEn": "Focus Groups", "type": "technique", "category": "data-gathering" },
|
||||
{ "id": "TT005", "name": "访谈", "nameEn": "Interviews", "type": "technique", "category": "data-gathering" },
|
||||
{ "id": "TT006", "name": "问卷调查", "nameEn": "Questionnaires and Surveys", "type": "technique", "category": "data-gathering" },
|
||||
{ "id": "TT007", "name": "标杆对照", "nameEn": "Benchmarking", "type": "technique", "category": "data-gathering" },
|
||||
{ "id": "TT008", "name": "数据分析", "nameEn": "Data Analysis", "type": "technique", "category": "general" },
|
||||
{ "id": "TT009", "name": "备选方案分析", "nameEn": "Alternatives Analysis", "type": "technique", "category": "data-analysis" },
|
||||
{ "id": "TT010", "name": "成本效益分析", "nameEn": "Cost-Benefit Analysis", "type": "technique", "category": "data-analysis" },
|
||||
{ "id": "TT011", "name": "挣值分析", "nameEn": "Earned Value Analysis", "type": "technique", "category": "data-analysis" },
|
||||
{ "id": "TT012", "name": "根本原因分析", "nameEn": "Root Cause Analysis", "type": "technique", "category": "data-analysis" },
|
||||
{ "id": "TT013", "name": "趋势分析", "nameEn": "Trend Analysis", "type": "technique", "category": "data-analysis" },
|
||||
{ "id": "TT014", "name": "偏差分析", "nameEn": "Variance Analysis", "type": "technique", "category": "data-analysis" },
|
||||
{ "id": "TT015", "name": "假设情景分析", "nameEn": "What-If Scenario Analysis", "type": "technique", "category": "data-analysis" },
|
||||
{ "id": "TT016", "name": "储备分析", "nameEn": "Reserve Analysis", "type": "technique", "category": "data-analysis" },
|
||||
{ "id": "TT017", "name": "文件分析", "nameEn": "Document Analysis", "type": "technique", "category": "data-analysis" },
|
||||
{ "id": "TT018", "name": "决策", "nameEn": "Decision Making", "type": "technique", "category": "general" },
|
||||
{ "id": "TT019", "name": "投票", "nameEn": "Voting", "type": "technique", "category": "decision-making" },
|
||||
{ "id": "TT020", "name": "多标准决策分析", "nameEn": "Multicriteria Decision Analysis", "type": "technique", "category": "decision-making" },
|
||||
{ "id": "TT021", "name": "独裁型决策", "nameEn": "Autocratic Decision Making", "type": "technique", "category": "decision-making" },
|
||||
{ "id": "TT022", "name": "人际关系与团队技能", "nameEn": "Interpersonal and Team Skills", "type": "skill", "category": "general" },
|
||||
{ "id": "TT023", "name": "冲突管理", "nameEn": "Conflict Management", "type": "skill", "category": "interpersonal" },
|
||||
{ "id": "TT024", "name": "引导", "nameEn": "Facilitation", "type": "skill", "category": "interpersonal" },
|
||||
{ "id": "TT025", "name": "会议管理", "nameEn": "Meeting Management", "type": "skill", "category": "interpersonal" },
|
||||
{ "id": "TT026", "name": "谈判", "nameEn": "Negotiation", "type": "skill", "category": "interpersonal" },
|
||||
{ "id": "TT027", "name": "影响力", "nameEn": "Influencing", "type": "skill", "category": "interpersonal" },
|
||||
{ "id": "TT028", "name": "积极倾听", "nameEn": "Active Listening", "type": "skill", "category": "interpersonal" },
|
||||
{ "id": "TT029", "name": "领导力", "nameEn": "Leadership", "type": "skill", "category": "interpersonal" },
|
||||
{ "id": "TT030", "name": "激励", "nameEn": "Motivation", "type": "skill", "category": "interpersonal" },
|
||||
{ "id": "TT031", "name": "团队建设", "nameEn": "Team Building", "type": "skill", "category": "interpersonal" },
|
||||
{ "id": "TT032", "name": "会议", "nameEn": "Meetings", "type": "meeting", "category": "general" },
|
||||
{ "id": "TT033", "name": "项目管理信息系统", "nameEn": "Project Management Information System", "type": "tool", "category": "general" },
|
||||
{ "id": "TT034", "name": "数据表现", "nameEn": "Data Representation", "type": "technique", "category": "general" },
|
||||
{ "id": "TT035", "name": "亲和图", "nameEn": "Affinity Diagrams", "type": "technique", "category": "data-representation" },
|
||||
{ "id": "TT036", "name": "因果图", "nameEn": "Cause-and-Effect Diagrams", "type": "technique", "category": "data-representation" },
|
||||
{ "id": "TT037", "name": "流程图", "nameEn": "Flowcharts", "type": "technique", "category": "data-representation" },
|
||||
{ "id": "TT038", "name": "直方图", "nameEn": "Histograms", "type": "technique", "category": "data-representation" },
|
||||
{ "id": "TT039", "name": "矩阵图", "nameEn": "Matrix Diagrams", "type": "technique", "category": "data-representation" },
|
||||
{ "id": "TT040", "name": "思维导图", "nameEn": "Mind Mapping", "type": "technique", "category": "data-representation" },
|
||||
{ "id": "TT041", "name": "散点图", "nameEn": "Scatter Diagrams", "type": "technique", "category": "data-representation" },
|
||||
{ "id": "TT042", "name": "层级图", "nameEn": "Hierarchical Charts", "type": "technique", "category": "data-representation" },
|
||||
{ "id": "TT043", "name": "责任分配矩阵", "nameEn": "Responsibility Assignment Matrix", "type": "technique", "category": "data-representation" },
|
||||
{ "id": "TT044", "name": "概率和影响矩阵", "nameEn": "Probability and Impact Matrix", "type": "technique", "category": "data-representation" },
|
||||
{ "id": "TT045", "name": "进度网络分析", "nameEn": "Schedule Network Analysis", "type": "technique", "category": "scheduling" },
|
||||
{ "id": "TT046", "name": "关键路径法", "nameEn": "Critical Path Method", "type": "technique", "category": "scheduling" },
|
||||
{ "id": "TT047", "name": "资源优化", "nameEn": "Resource Optimization", "type": "technique", "category": "scheduling" },
|
||||
{ "id": "TT048", "name": "资源平衡", "nameEn": "Resource Leveling", "type": "technique", "category": "scheduling" },
|
||||
{ "id": "TT049", "name": "资源平滑", "nameEn": "Resource Smoothing", "type": "technique", "category": "scheduling" },
|
||||
{ "id": "TT050", "name": "提前量和滞后量", "nameEn": "Leads and Lags", "type": "technique", "category": "scheduling" },
|
||||
{ "id": "TT051", "name": "进度压缩", "nameEn": "Schedule Compression", "type": "technique", "category": "scheduling" },
|
||||
{ "id": "TT052", "name": "赶工", "nameEn": "Crashing", "type": "technique", "category": "scheduling" },
|
||||
{ "id": "TT053", "name": "快速跟进", "nameEn": "Fast Tracking", "type": "technique", "category": "scheduling" },
|
||||
{ "id": "TT054", "name": "估算", "nameEn": "Estimating", "type": "technique", "category": "general" },
|
||||
{ "id": "TT055", "name": "类比估算", "nameEn": "Analogous Estimating", "type": "technique", "category": "estimating" },
|
||||
{ "id": "TT056", "name": "参数估算", "nameEn": "Parametric Estimating", "type": "technique", "category": "estimating" },
|
||||
{ "id": "TT057", "name": "自下而上估算", "nameEn": "Bottom-Up Estimating", "type": "technique", "category": "estimating" },
|
||||
{ "id": "TT058", "name": "三点估算", "nameEn": "Three-Point Estimating", "type": "technique", "category": "estimating" },
|
||||
{ "id": "TT059", "name": "分解", "nameEn": "Decomposition", "type": "technique", "category": "general" },
|
||||
{ "id": "TT060", "name": "滚动式规划", "nameEn": "Rolling Wave Planning", "type": "technique", "category": "general" },
|
||||
{ "id": "TT061", "name": "紧前关系绘图法", "nameEn": "Precedence Diagramming Method", "type": "technique", "category": "scheduling" },
|
||||
{ "id": "TT062", "name": "确定和整合依赖关系", "nameEn": "Dependency Determination and Integration", "type": "technique", "category": "scheduling" },
|
||||
{ "id": "TT063", "name": "进度计划编制工具", "nameEn": "Scheduling Tool", "type": "tool", "category": "scheduling" },
|
||||
{ "id": "TT064", "name": "质量成本", "nameEn": "Cost of Quality", "type": "technique", "category": "quality" },
|
||||
{ "id": "TT065", "name": "检查", "nameEn": "Inspection", "type": "technique", "category": "quality" },
|
||||
{ "id": "TT066", "name": "测试/产品评估", "nameEn": "Testing/Product Evaluations", "type": "technique", "category": "quality" },
|
||||
{ "id": "TT067", "name": "审计", "nameEn": "Audits", "type": "technique", "category": "quality" },
|
||||
{ "id": "TT068", "name": "面向X的设计", "nameEn": "Design for X", "type": "technique", "category": "quality" },
|
||||
{ "id": "TT069", "name": "问题解决", "nameEn": "Problem Solving", "type": "technique", "category": "quality" },
|
||||
{ "id": "TT070", "name": "质量改进方法", "nameEn": "Quality Improvement Methods", "type": "technique", "category": "quality" },
|
||||
{ "id": "TT071", "name": "风险概率和影响评估", "nameEn": "Risk Probability and Impact Assessment", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT072", "name": "风险分类", "nameEn": "Risk Categorization", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT073", "name": "风险数据质量评估", "nameEn": "Risk Data Quality Assessment", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT074", "name": "风险紧迫性评估", "nameEn": "Risk Urgency Assessment", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT075", "name": "敏感性分析", "nameEn": "Sensitivity Analysis", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT076", "name": "预期货币价值分析", "nameEn": "Expected Monetary Value Analysis", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT077", "name": "模拟", "nameEn": "Simulations", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT078", "name": "蒙特卡洛模拟", "nameEn": "Monte Carlo Simulation", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT079", "name": "决策树分析", "nameEn": "Decision Tree Analysis", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT080", "name": "影响图", "nameEn": "Influence Diagrams", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT081", "name": "威胁应对策略", "nameEn": "Strategies for Threats", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT082", "name": "机会应对策略", "nameEn": "Strategies for Opportunities", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT083", "name": "应急应对策略", "nameEn": "Contingent Response Strategies", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT084", "name": "整体项目风险应对策略", "nameEn": "Strategies for Overall Project Risk", "type": "technique", "category": "risk" },
|
||||
{ "id": "TT085", "name": "沟通方法", "nameEn": "Communication Methods", "type": "technique", "category": "communication" },
|
||||
{ "id": "TT086", "name": "沟通技术", "nameEn": "Communication Technology", "type": "tool", "category": "communication" },
|
||||
{ "id": "TT087", "name": "沟通模型", "nameEn": "Communication Models", "type": "technique", "category": "communication" },
|
||||
{ "id": "TT088", "name": "沟通技能", "nameEn": "Communication Skills", "type": "skill", "category": "communication" },
|
||||
{ "id": "TT089", "name": "反馈", "nameEn": "Feedback", "type": "skill", "category": "communication" },
|
||||
{ "id": "TT090", "name": "演示", "nameEn": "Presentations", "type": "skill", "category": "communication" },
|
||||
{ "id": "TT091", "name": "采购策略", "nameEn": "Procurement Strategy", "type": "technique", "category": "procurement" },
|
||||
{ "id": "TT092", "name": "供方选择分析", "nameEn": "Source Selection Analysis", "type": "technique", "category": "procurement" },
|
||||
{ "id": "TT093", "name": "投标人会议", "nameEn": "Bidder Conferences", "type": "meeting", "category": "procurement" },
|
||||
{ "id": "TT094", "name": "建议书评价", "nameEn": "Proposal Evaluation", "type": "technique", "category": "procurement" },
|
||||
{ "id": "TT095", "name": "索赔管理", "nameEn": "Claims Administration", "type": "technique", "category": "procurement" },
|
||||
{ "id": "TT096", "name": "相关方分析", "nameEn": "Stakeholder Analysis", "type": "technique", "category": "stakeholder" },
|
||||
{ "id": "TT097", "name": "相关方映射分析/表现", "nameEn": "Stakeholder Mapping/Representation", "type": "technique", "category": "stakeholder" },
|
||||
{ "id": "TT098", "name": "权力利益方格", "nameEn": "Power/Interest Grid", "type": "technique", "category": "stakeholder" },
|
||||
{ "id": "TT099", "name": "凸显模型", "nameEn": "Salience Model", "type": "technique", "category": "stakeholder" },
|
||||
{ "id": "TT100", "name": "基本规则", "nameEn": "Ground Rules", "type": "technique", "category": "interpersonal" },
|
||||
{ "id": "TT101", "name": "集中办公", "nameEn": "Colocation", "type": "technique", "category": "resource" },
|
||||
{ "id": "TT102", "name": "虚拟团队", "nameEn": "Virtual Teams", "type": "technique", "category": "resource" },
|
||||
{ "id": "TT103", "name": "认可与奖励", "nameEn": "Recognition and Rewards", "type": "technique", "category": "resource" },
|
||||
{ "id": "TT104", "name": "培训", "nameEn": "Training", "type": "technique", "category": "resource" },
|
||||
{ "id": "TT105", "name": "个人和团队评估", "nameEn": "Individual and Team Assessments", "type": "technique", "category": "resource" },
|
||||
{ "id": "TT106", "name": "资源日历", "nameEn": "Resource Calendars", "type": "tool", "category": "resource" },
|
||||
{ "id": "TT107", "name": "预分派", "nameEn": "Pre-Assignment", "type": "technique", "category": "resource" },
|
||||
{ "id": "TT108", "name": "变更控制工具", "nameEn": "Change Control Tools", "type": "tool", "category": "integration" },
|
||||
{ "id": "TT109", "name": "配置管理系统", "nameEn": "Configuration Management System", "type": "tool", "category": "integration" },
|
||||
{ "id": "TT110", "name": "知识管理", "nameEn": "Knowledge Management", "type": "technique", "category": "integration" },
|
||||
{ "id": "TT111", "name": "信息管理", "nameEn": "Information Management", "type": "technique", "category": "integration" },
|
||||
{ "id": "TT112", "name": "组织理论", "nameEn": "Organizational Theory", "type": "technique", "category": "resource" },
|
||||
{ "id": "TT113", "name": "需求跟踪矩阵", "nameEn": "Requirements Traceability Matrix", "type": "tool", "category": "scope" },
|
||||
{ "id": "TT114", "name": "产品分析", "nameEn": "Product Analysis", "type": "technique", "category": "scope" },
|
||||
{ "id": "TT115", "name": "原型法", "nameEn": "Prototypes", "type": "technique", "category": "scope" },
|
||||
{ "id": "TT116", "name": "观察/交谈", "nameEn": "Observation/Conversation", "type": "technique", "category": "data-gathering" },
|
||||
{ "id": "TT117", "name": "引导式研讨会", "nameEn": "Facilitated Workshops", "type": "technique", "category": "data-gathering" },
|
||||
{ "id": "TT118", "name": "群体创新技术", "nameEn": "Group Creativity Techniques", "type": "technique", "category": "data-gathering" },
|
||||
{ "id": "TT119", "name": "群体决策技术", "nameEn": "Group Decision-Making Techniques", "type": "technique", "category": "decision-making" },
|
||||
{ "id": "TT120", "name": "核对单", "nameEn": "Checklists", "type": "tool", "category": "general" }
|
||||
]
|
||||
}
|
||||
72
src/index.css
Normal file
72
src/index.css
Normal file
@@ -0,0 +1,72 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--color-bg: #ffffff;
|
||||
--color-bg-secondary: #f9fafb;
|
||||
--color-text: #111827;
|
||||
--color-text-secondary: #6b7280;
|
||||
--color-border: #e5e7eb;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-bg: #111827;
|
||||
--color-bg-secondary: #1f2937;
|
||||
--color-text: #f9fafb;
|
||||
--color-text-secondary: #9ca3af;
|
||||
--color-border: #374151;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* 自定义滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 过渡动画 */
|
||||
.page-transition-enter {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
.page-transition-enter-active {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transition: opacity 200ms ease-out, transform 200ms ease-out;
|
||||
}
|
||||
|
||||
.page-transition-exit {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.page-transition-exit-active {
|
||||
opacity: 0;
|
||||
transition: opacity 150ms ease-in;
|
||||
}
|
||||
13
src/main.tsx
Normal file
13
src/main.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import App from './App'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
163
src/pages/ArtifactDetailPage.tsx
Normal file
163
src/pages/ArtifactDetailPage.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* 工件详情页面
|
||||
* 显示工件被哪些过程使用(作为输入或输出)
|
||||
*/
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { motion } from 'framer-motion'
|
||||
import { ArrowLeft, FileInput, FileOutput, ArrowRight } from 'lucide-react'
|
||||
import { artifactMap, getArtifactUsage, knowledgeAreaMap } from '@/data'
|
||||
|
||||
export function ArtifactDetailPage() {
|
||||
const { id } = useParams()
|
||||
const artifact = id ? artifactMap.get(id) : null
|
||||
const usage = id ? getArtifactUsage(id) : { asInput: [], asOutput: [] }
|
||||
|
||||
if (!artifact) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-20">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
工件未找到
|
||||
</h1>
|
||||
<Link to="/" className="text-indigo-600 dark:text-indigo-400 hover:underline">
|
||||
返回首页
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 返回按钮 */}
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-indigo-600 dark:hover:text-indigo-400"
|
||||
>
|
||||
<ArrowLeft size={16} />
|
||||
返回
|
||||
</Link>
|
||||
|
||||
{/* 工件信息 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm border border-gray-100 dark:border-gray-700"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-xl bg-blue-500 text-white">
|
||||
<FileInput size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{artifact.name}
|
||||
</h1>
|
||||
<p className="text-gray-500 dark:text-gray-400">{artifact.nameEn}</p>
|
||||
</div>
|
||||
</div>
|
||||
{artifact.description && (
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-300">{artifact.description}</p>
|
||||
)}
|
||||
<div className="mt-4">
|
||||
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300">
|
||||
{artifact.category}
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* 作为输出的过程 */}
|
||||
{usage.asOutput.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden"
|
||||
>
|
||||
<div className="flex items-center gap-3 px-6 py-4 border-b border-gray-100 dark:border-gray-700 bg-emerald-50 dark:bg-emerald-900/20">
|
||||
<FileOutput size={20} className="text-emerald-600 dark:text-emerald-400" />
|
||||
<h2 className="font-semibold text-gray-900 dark:text-white">
|
||||
产出此工件的过程({usage.asOutput.length})
|
||||
</h2>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{usage.asOutput.map((process) => {
|
||||
const ka = knowledgeAreaMap.get(process.knowledgeAreaId)
|
||||
return (
|
||||
<li key={process.id}>
|
||||
<Link
|
||||
to={`/process/${process.id}`}
|
||||
className="flex items-center justify-between px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="flex h-10 w-10 items-center justify-center rounded-lg text-white font-medium"
|
||||
style={{ backgroundColor: ka?.color }}
|
||||
>
|
||||
{process.code}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900 dark:text-white">
|
||||
{process.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{ka?.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight size={18} className="text-gray-400" />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* 作为输入的过程 */}
|
||||
{usage.asInput.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden"
|
||||
>
|
||||
<div className="flex items-center gap-3 px-6 py-4 border-b border-gray-100 dark:border-gray-700 bg-blue-50 dark:bg-blue-900/20">
|
||||
<FileInput size={20} className="text-blue-600 dark:text-blue-400" />
|
||||
<h2 className="font-semibold text-gray-900 dark:text-white">
|
||||
使用此工件作为输入的过程({usage.asInput.length})
|
||||
</h2>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{usage.asInput.map((process) => {
|
||||
const ka = knowledgeAreaMap.get(process.knowledgeAreaId)
|
||||
return (
|
||||
<li key={process.id}>
|
||||
<Link
|
||||
to={`/process/${process.id}`}
|
||||
className="flex items-center justify-between px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="flex h-10 w-10 items-center justify-center rounded-lg text-white font-medium"
|
||||
style={{ backgroundColor: ka?.color }}
|
||||
>
|
||||
{process.code}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900 dark:text-white">
|
||||
{process.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{ka?.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight size={18} className="text-gray-400" />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
163
src/pages/HomePage.tsx
Normal file
163
src/pages/HomePage.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import { motion } from 'framer-motion'
|
||||
import { BookOpen, Layers, GitBranch, ArrowRight } from 'lucide-react'
|
||||
import { stats } from '@/data'
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: BookOpen,
|
||||
title: '知识领域',
|
||||
description: '按10大知识领域浏览49个项目管理过程',
|
||||
link: '/knowledge-areas',
|
||||
color: 'from-indigo-500 to-purple-500',
|
||||
count: stats.knowledgeAreaCount,
|
||||
},
|
||||
{
|
||||
icon: Layers,
|
||||
title: '过程组',
|
||||
description: '按5大过程组分类查看项目管理流程',
|
||||
link: '/process-groups',
|
||||
color: 'from-blue-500 to-cyan-500',
|
||||
count: stats.processGroupCount,
|
||||
},
|
||||
{
|
||||
icon: GitBranch,
|
||||
title: '可视化',
|
||||
description: 'ITTO流程图和数据流向可视化分析',
|
||||
link: '/visualize',
|
||||
color: 'from-emerald-500 to-teal-500',
|
||||
count: stats.processCount,
|
||||
},
|
||||
]
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.5,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function HomePage() {
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* 欢迎区域 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 p-8 text-white"
|
||||
>
|
||||
<div className="relative z-10">
|
||||
<h1 className="text-3xl font-bold mb-2">欢迎使用 ITTOView</h1>
|
||||
<p className="text-lg text-white/80 mb-6">
|
||||
PMP项目管理ITTO可视化学习平台,助您高效掌握49个项目管理过程
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<Link
|
||||
to="/knowledge-areas"
|
||||
className="inline-flex items-center gap-2 px-6 py-3 bg-white text-indigo-600 rounded-lg font-medium hover:bg-white/90 transition-colors"
|
||||
>
|
||||
开始浏览
|
||||
<ArrowRight size={18} />
|
||||
</Link>
|
||||
<Link
|
||||
to="/visualize"
|
||||
className="inline-flex items-center gap-2 px-6 py-3 bg-white/20 text-white rounded-lg font-medium hover:bg-white/30 transition-colors"
|
||||
>
|
||||
查看可视化
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{/* 装饰背景 */}
|
||||
<div className="absolute top-0 right-0 w-64 h-64 bg-white/10 rounded-full -translate-y-1/2 translate-x-1/2" />
|
||||
<div className="absolute bottom-0 left-0 w-48 h-48 bg-white/10 rounded-full translate-y-1/2 -translate-x-1/2" />
|
||||
</motion.div>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="grid grid-cols-2 md:grid-cols-4 gap-4"
|
||||
>
|
||||
{[
|
||||
{ label: '知识领域', value: stats.knowledgeAreaCount, color: 'text-indigo-600' },
|
||||
{ label: '过程组', value: stats.processGroupCount, color: 'text-blue-600' },
|
||||
{ label: '项目过程', value: stats.processCount, color: 'text-emerald-600' },
|
||||
{ label: '工具技术', value: stats.toolCount, color: 'text-amber-600' },
|
||||
].map((stat) => (
|
||||
<motion.div
|
||||
key={stat.label}
|
||||
variants={itemVariants}
|
||||
className="bg-white dark:bg-gray-800 rounded-xl p-4 shadow-sm border border-gray-100 dark:border-gray-700"
|
||||
>
|
||||
<div className={`text-3xl font-bold ${stat.color}`}>{stat.value}</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-1">{stat.label}</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* 功能入口 */}
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="grid md:grid-cols-3 gap-6"
|
||||
>
|
||||
{features.map((feature) => {
|
||||
const Icon = feature.icon
|
||||
return (
|
||||
<motion.div key={feature.title} variants={itemVariants}>
|
||||
<Link
|
||||
to={feature.link}
|
||||
className="group block bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm border border-gray-100 dark:border-gray-700 hover:shadow-md hover:border-gray-200 dark:hover:border-gray-600 transition-all h-full"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br ${feature.color} text-white`}>
|
||||
<Icon size={24} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{feature.title}
|
||||
</h3>
|
||||
{feature.count !== null && (
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{feature.count} 项
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-gray-500 dark:text-gray-400 mt-1">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center text-indigo-600 dark:text-indigo-400 text-sm font-medium">
|
||||
进入查看
|
||||
<ArrowRight
|
||||
size={16}
|
||||
className="ml-1 group-hover:translate-x-1 transition-transform"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
)
|
||||
})}
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
190
src/pages/KnowledgeAreasPage.tsx
Normal file
190
src/pages/KnowledgeAreasPage.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { motion } from 'framer-motion'
|
||||
import { ArrowRight, FileText, Wrench, FileOutput } from 'lucide-react'
|
||||
import { knowledgeAreas, processesByKnowledgeArea, knowledgeAreaMap, processGroupMap } from '@/data'
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: { staggerChildren: 0.05 },
|
||||
},
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
visible: { opacity: 1, y: 0 },
|
||||
}
|
||||
|
||||
export function KnowledgeAreasPage() {
|
||||
const { id } = useParams()
|
||||
const selectedKA = id ? knowledgeAreaMap.get(id) : null
|
||||
const processes = id ? processesByKnowledgeArea.get(id) || [] : []
|
||||
|
||||
if (selectedKA) {
|
||||
// 显示知识领域详情
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 面包屑 */}
|
||||
<nav className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<Link to="/knowledge-areas" className="hover:text-indigo-600 dark:hover:text-indigo-400">
|
||||
知识领域
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-gray-900 dark:text-white">{selectedKA.name}</span>
|
||||
</nav>
|
||||
|
||||
{/* 知识领域标题 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="rounded-xl p-6"
|
||||
style={{ backgroundColor: `${selectedKA.color}15` }}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="flex h-14 w-14 items-center justify-center rounded-xl text-white font-bold text-xl"
|
||||
style={{ backgroundColor: selectedKA.color }}
|
||||
>
|
||||
{selectedKA.order}
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{selectedKA.name}
|
||||
</h1>
|
||||
<p className="text-gray-500 dark:text-gray-400">{selectedKA.nameEn}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-300">{selectedKA.description}</p>
|
||||
</motion.div>
|
||||
|
||||
{/* 过程列表 */}
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="space-y-4"
|
||||
>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
包含 {processes.length} 个过程
|
||||
</h2>
|
||||
{processes.map((process) => {
|
||||
const pg = processGroupMap.get(process.processGroupId)
|
||||
return (
|
||||
<motion.div key={process.id} variants={itemVariants}>
|
||||
<Link
|
||||
to={`/process/${process.id}`}
|
||||
className="group block bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm border border-gray-100 dark:border-gray-700 hover:shadow-md hover:border-gray-200 dark:hover:border-gray-600 transition-all"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="flex h-10 w-10 items-center justify-center rounded-lg text-white font-medium"
|
||||
style={{ backgroundColor: selectedKA.color }}
|
||||
>
|
||||
{process.code}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white">
|
||||
{process.name}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{process.nameEn}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
{pg && (
|
||||
<span
|
||||
className="px-3 py-1 rounded-full text-xs font-medium text-white"
|
||||
style={{ backgroundColor: pg.color }}
|
||||
>
|
||||
{pg.name}
|
||||
</span>
|
||||
)}
|
||||
<ArrowRight
|
||||
size={20}
|
||||
className="text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 group-hover:translate-x-1 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* ITTO统计 */}
|
||||
<div className="mt-4 flex items-center gap-6 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span className="flex items-center gap-1">
|
||||
<FileText size={14} />
|
||||
{process.inputs.length} 输入
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Wrench size={14} />
|
||||
{process.tools.length} 工具技术
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<FileOutput size={14} />
|
||||
{process.outputs.length} 输出
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
)
|
||||
})}
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 显示知识领域列表
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">知识领域</h1>
|
||||
<p className="text-gray-500 dark:text-gray-400 mt-1">
|
||||
PMBOK第6版定义的10大项目管理知识领域
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="grid md:grid-cols-2 gap-4"
|
||||
>
|
||||
{knowledgeAreas.map((ka) => (
|
||||
<motion.div key={ka.id} variants={itemVariants}>
|
||||
<Link
|
||||
to={`/knowledge-areas/${ka.id}`}
|
||||
className="group block bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm border border-gray-100 dark:border-gray-700 hover:shadow-md transition-all"
|
||||
style={{ borderLeftWidth: 4, borderLeftColor: ka.color }}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="flex h-12 w-12 items-center justify-center rounded-lg text-white font-bold text-lg"
|
||||
style={{ backgroundColor: ka.color }}
|
||||
>
|
||||
{ka.order}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white">{ka.name}</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">{ka.nameEn}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{ka.processCount} 个过程
|
||||
</span>
|
||||
<ArrowRight
|
||||
size={20}
|
||||
className="text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 group-hover:translate-x-1 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-3 text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
|
||||
{ka.description}
|
||||
</p>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
239
src/pages/ProcessDetailPage.tsx
Normal file
239
src/pages/ProcessDetailPage.tsx
Normal file
@@ -0,0 +1,239 @@
|
||||
import { useParams, Link, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { motion } from 'framer-motion'
|
||||
import { ArrowLeft, ArrowRight, FileText, Wrench, FileOutput, LayoutGrid } from 'lucide-react'
|
||||
import { getProcessDetail, processes, artifactMap, toolMap } from '@/data'
|
||||
|
||||
export function ProcessDetailPage() {
|
||||
const { id } = useParams()
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const processDetail = id ? getProcessDetail(id) : null
|
||||
|
||||
// 检查是否从矩阵页面来
|
||||
const fromMatrix = location.state?.from === 'matrix'
|
||||
|
||||
if (!processDetail) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-20">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
|
||||
未找到该过程
|
||||
</h2>
|
||||
<Link
|
||||
to="/knowledge-areas"
|
||||
className="text-indigo-600 dark:text-indigo-400 hover:underline"
|
||||
>
|
||||
返回知识领域
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ka = processDetail.knowledgeArea
|
||||
const pg = processDetail.processGroup
|
||||
|
||||
// 获取前后过程
|
||||
const currentIndex = processes.findIndex(p => p.id === id)
|
||||
const prevProcess = currentIndex > 0 ? processes[currentIndex - 1] : null
|
||||
const nextProcess = currentIndex < processes.length - 1 ? processes[currentIndex + 1] : null
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 返回按钮 + 面包屑 */}
|
||||
<div className="flex items-center gap-4">
|
||||
{fromMatrix && (
|
||||
<button
|
||||
onClick={() => navigate('/process-matrix')}
|
||||
className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 hover:bg-indigo-100 dark:hover:bg-indigo-900/50 transition-colors text-sm font-medium"
|
||||
>
|
||||
<LayoutGrid size={16} />
|
||||
返回矩阵
|
||||
</button>
|
||||
)}
|
||||
<nav className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<Link to="/knowledge-areas" className="hover:text-indigo-600 dark:hover:text-indigo-400">
|
||||
知识领域
|
||||
</Link>
|
||||
<span>/</span>
|
||||
{ka && (
|
||||
<>
|
||||
<Link
|
||||
to={`/knowledge-areas/${ka.id}`}
|
||||
className="hover:text-indigo-600 dark:hover:text-indigo-400"
|
||||
>
|
||||
{ka.name}
|
||||
</Link>
|
||||
<span>/</span>
|
||||
</>
|
||||
)}
|
||||
<span className="text-gray-900 dark:text-white">{processDetail.name}</span>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* 过程标题 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="rounded-xl p-6 bg-white dark:bg-gray-800 shadow-sm border border-gray-100 dark:border-gray-700"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div
|
||||
className="flex h-16 w-16 items-center justify-center rounded-xl text-white font-bold text-xl shrink-0"
|
||||
style={{ backgroundColor: ka?.color || '#6366F1' }}
|
||||
>
|
||||
{processDetail.code}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{processDetail.name}
|
||||
</h1>
|
||||
<p className="text-gray-500 dark:text-gray-400">{processDetail.nameEn}</p>
|
||||
<div className="flex items-center gap-3 mt-3">
|
||||
{ka && (
|
||||
<span
|
||||
className="px-3 py-1 rounded-full text-xs font-medium text-white"
|
||||
style={{ backgroundColor: ka.color }}
|
||||
>
|
||||
{ka.name}
|
||||
</span>
|
||||
)}
|
||||
{pg && (
|
||||
<span
|
||||
className="px-3 py-1 rounded-full text-xs font-medium text-white"
|
||||
style={{ backgroundColor: pg.color }}
|
||||
>
|
||||
{pg.name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* ITTO表格 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="grid lg:grid-cols-3 gap-6"
|
||||
>
|
||||
{/* 输入 */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
|
||||
<div className="flex items-center gap-2 px-4 py-3 bg-blue-50 dark:bg-blue-900/30 border-b border-blue-100 dark:border-blue-800">
|
||||
<FileText size={18} className="text-blue-600 dark:text-blue-400" />
|
||||
<h3 className="font-semibold text-blue-900 dark:text-blue-100">
|
||||
输入 ({processDetail.inputs.length})
|
||||
</h3>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{processDetail.inputs.map((inputId) => {
|
||||
const artifact = artifactMap.get(inputId)
|
||||
return (
|
||||
<li key={inputId} className="px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<div className="font-medium text-gray-900 dark:text-white">
|
||||
{artifact?.name || inputId}
|
||||
</div>
|
||||
{artifact && (
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{artifact.nameEn}
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* 工具与技术 */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
|
||||
<div className="flex items-center gap-2 px-4 py-3 bg-amber-50 dark:bg-amber-900/30 border-b border-amber-100 dark:border-amber-800">
|
||||
<Wrench size={18} className="text-amber-600 dark:text-amber-400" />
|
||||
<h3 className="font-semibold text-amber-900 dark:text-amber-100">
|
||||
工具与技术 ({processDetail.tools.length})
|
||||
</h3>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{processDetail.tools.map((toolId) => {
|
||||
const tool = toolMap.get(toolId)
|
||||
return (
|
||||
<li key={toolId} className="px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<div className="font-medium text-gray-900 dark:text-white">
|
||||
{tool?.name || toolId}
|
||||
</div>
|
||||
{tool && (
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{tool.nameEn}
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* 输出 */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
|
||||
<div className="flex items-center gap-2 px-4 py-3 bg-emerald-50 dark:bg-emerald-900/30 border-b border-emerald-100 dark:border-emerald-800">
|
||||
<FileOutput size={18} className="text-emerald-600 dark:text-emerald-400" />
|
||||
<h3 className="font-semibold text-emerald-900 dark:text-emerald-100">
|
||||
输出 ({processDetail.outputs.length})
|
||||
</h3>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{processDetail.outputs.map((outputId) => {
|
||||
const artifact = artifactMap.get(outputId)
|
||||
return (
|
||||
<li key={outputId} className="px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<div className="font-medium text-gray-900 dark:text-white">
|
||||
{artifact?.name || outputId}
|
||||
</div>
|
||||
{artifact && (
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{artifact.nameEn}
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* 前后导航 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="flex items-center justify-between pt-6 border-t border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
{prevProcess ? (
|
||||
<Link
|
||||
to={`/process/${prevProcess.id}`}
|
||||
className="flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
|
||||
>
|
||||
<ArrowLeft size={18} />
|
||||
<div>
|
||||
<div className="text-xs text-gray-400">上一个过程</div>
|
||||
<div className="font-medium">{prevProcess.code} {prevProcess.name}</div>
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
{nextProcess ? (
|
||||
<Link
|
||||
to={`/process/${nextProcess.id}`}
|
||||
className="flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors text-right"
|
||||
>
|
||||
<div>
|
||||
<div className="text-xs text-gray-400">下一个过程</div>
|
||||
<div className="font-medium">{nextProcess.code} {nextProcess.name}</div>
|
||||
</div>
|
||||
<ArrowRight size={18} />
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
576
src/pages/ProcessGraphPage.tsx
Normal file
576
src/pages/ProcessGraphPage.tsx
Normal file
@@ -0,0 +1,576 @@
|
||||
/**
|
||||
* 过程关系图页面 (Force Graph)
|
||||
* 展示所有实体(过程、工件、工具)及其相互关系
|
||||
*/
|
||||
import { useEffect, useRef, useState, useMemo } from 'react'
|
||||
import G6, { Graph, INode } from '@antv/g6'
|
||||
import { motion } from 'framer-motion'
|
||||
import {
|
||||
ZoomIn, ZoomOut, Maximize2,
|
||||
Activity, FileText, Wrench,
|
||||
ChevronRight, ChevronLeft
|
||||
} from 'lucide-react'
|
||||
import {
|
||||
processes,
|
||||
artifacts,
|
||||
tools,
|
||||
knowledgeAreaMap,
|
||||
getProcessDetail,
|
||||
getArtifactUsage,
|
||||
getToolUsage,
|
||||
} from '@/data'
|
||||
|
||||
export function ProcessGraphPage() {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const graphRef = useRef<Graph | null>(null)
|
||||
const [selectedNode, setSelectedNode] = useState<any>(null)
|
||||
const [details, setDetails] = useState<any>(null)
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false)
|
||||
|
||||
// 更新详情数据
|
||||
useEffect(() => {
|
||||
if (!selectedNode) {
|
||||
setDetails(null)
|
||||
setIsSidebarCollapsed(false)
|
||||
return
|
||||
}
|
||||
|
||||
// 选中新节点时自动展开
|
||||
setIsSidebarCollapsed(false)
|
||||
|
||||
if (selectedNode.type === 'process') {
|
||||
setDetails(getProcessDetail(selectedNode.id))
|
||||
} else if (selectedNode.type === 'artifact') {
|
||||
setDetails({
|
||||
...selectedNode,
|
||||
usage: getArtifactUsage(selectedNode.id)
|
||||
})
|
||||
} else if (selectedNode.type === 'tool') {
|
||||
setDetails({
|
||||
...selectedNode,
|
||||
usage: getToolUsage(selectedNode.id)
|
||||
})
|
||||
}
|
||||
}, [selectedNode])
|
||||
|
||||
// 构建全量数据
|
||||
const graphData = useMemo(() => {
|
||||
const nodes: any[] = []
|
||||
const edges: any[] = []
|
||||
const addedNodeIds = new Set<string>()
|
||||
|
||||
// 1. 添加过程节点
|
||||
processes.forEach(p => {
|
||||
const ka = knowledgeAreaMap.get(p.knowledgeAreaId)
|
||||
nodes.push({
|
||||
id: p.id,
|
||||
label: `${p.code}\n${p.name}`, // 添加编号
|
||||
type: 'circle',
|
||||
size: 50, // 过程节点最大
|
||||
style: {
|
||||
fill: ka?.color || '#6366F1',
|
||||
stroke: '#fff',
|
||||
lineWidth: 3,
|
||||
shadowColor: 'rgba(0,0,0,0.2)',
|
||||
shadowBlur: 5,
|
||||
},
|
||||
labelCfg: {
|
||||
position: 'center', // 文字居中
|
||||
style: {
|
||||
fontSize: 12,
|
||||
fill: '#fff', // 白色文字
|
||||
fontWeight: 700,
|
||||
stroke: '#000', // 黑色描边
|
||||
lineWidth: 2,
|
||||
}
|
||||
},
|
||||
data: { ...p, type: 'process' },
|
||||
})
|
||||
addedNodeIds.add(p.id)
|
||||
})
|
||||
|
||||
// 2. 添加工件节点和关系
|
||||
const usedArtifacts = new Set<string>()
|
||||
processes.forEach(p => {
|
||||
p.inputs.forEach(id => usedArtifacts.add(id))
|
||||
p.outputs.forEach(id => usedArtifacts.add(id))
|
||||
})
|
||||
|
||||
artifacts.forEach(a => {
|
||||
if (usedArtifacts.has(a.id)) {
|
||||
nodes.push({
|
||||
id: a.id,
|
||||
label: a.name,
|
||||
type: 'rect', // 工件使用矩形
|
||||
size: [80, 30],
|
||||
style: {
|
||||
fill: '#10B981', // Green
|
||||
stroke: '#fff',
|
||||
lineWidth: 1,
|
||||
radius: 4,
|
||||
},
|
||||
labelCfg: {
|
||||
position: 'center',
|
||||
style: {
|
||||
fontSize: 11,
|
||||
fill: '#fff',
|
||||
stroke: '#000',
|
||||
lineWidth: 2,
|
||||
}
|
||||
},
|
||||
data: { ...a, type: 'artifact' },
|
||||
})
|
||||
addedNodeIds.add(a.id)
|
||||
}
|
||||
})
|
||||
|
||||
// 3. 添加工具节点和关系
|
||||
const usedTools = new Set<string>()
|
||||
processes.forEach(p => {
|
||||
p.tools.forEach(id => usedTools.add(id))
|
||||
})
|
||||
|
||||
tools.forEach(t => {
|
||||
if (usedTools.has(t.id)) {
|
||||
nodes.push({
|
||||
id: t.id,
|
||||
label: t.name,
|
||||
type: 'diamond', // 工具使用菱形
|
||||
size: [80, 40],
|
||||
style: {
|
||||
fill: '#8B5CF6', // Purple
|
||||
stroke: '#fff',
|
||||
lineWidth: 1,
|
||||
},
|
||||
labelCfg: {
|
||||
position: 'center',
|
||||
style: {
|
||||
fontSize: 11,
|
||||
fill: '#fff',
|
||||
stroke: '#000',
|
||||
lineWidth: 2,
|
||||
}
|
||||
},
|
||||
data: { ...t, type: 'tool' },
|
||||
})
|
||||
addedNodeIds.add(t.id)
|
||||
}
|
||||
})
|
||||
|
||||
// 4. 构建边
|
||||
processes.forEach(p => {
|
||||
// 输入关系: Artifact -> Process
|
||||
p.inputs.forEach(inputId => {
|
||||
if (addedNodeIds.has(inputId)) {
|
||||
edges.push({
|
||||
source: inputId,
|
||||
target: p.id,
|
||||
type: 'line',
|
||||
style: {
|
||||
stroke: '#94a3b8',
|
||||
opacity: 0.3,
|
||||
endArrow: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 输出关系: Process -> Artifact
|
||||
p.outputs.forEach(outputId => {
|
||||
if (addedNodeIds.has(outputId)) {
|
||||
edges.push({
|
||||
source: p.id,
|
||||
target: outputId,
|
||||
type: 'line',
|
||||
style: {
|
||||
stroke: '#94a3b8',
|
||||
opacity: 0.3,
|
||||
endArrow: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 工具关系: Tool -> Process
|
||||
p.tools.forEach(toolId => {
|
||||
if (addedNodeIds.has(toolId)) {
|
||||
edges.push({
|
||||
source: toolId,
|
||||
target: p.id,
|
||||
type: 'line',
|
||||
style: {
|
||||
stroke: '#a78bfa',
|
||||
opacity: 0.3,
|
||||
lineDash: [4, 2],
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return { nodes, edges }
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return
|
||||
|
||||
if (graphRef.current) {
|
||||
graphRef.current.destroy()
|
||||
}
|
||||
|
||||
const width = containerRef.current.offsetWidth
|
||||
const height = containerRef.current.offsetHeight || 800
|
||||
|
||||
const graph = new G6.Graph({
|
||||
container: containerRef.current,
|
||||
width,
|
||||
height,
|
||||
fitView: true,
|
||||
modes: {
|
||||
default: ['drag-canvas', 'zoom-canvas', 'drag-node'], // 移除 activate-relations,改为手动控制
|
||||
},
|
||||
layout: {
|
||||
type: 'gForce',
|
||||
preventOverlap: true,
|
||||
nodeSize: 50,
|
||||
linkDistance: () => {
|
||||
// 过程节点之间的距离远一点,工件和工具近一点
|
||||
return 140
|
||||
},
|
||||
nodeStrength: 1200,
|
||||
edgeStrength: 200,
|
||||
gpuEnabled: true,
|
||||
},
|
||||
defaultNode: {
|
||||
size: 30,
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
stroke: '#5B8FF9',
|
||||
fill: '#C6E5FF',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
labelCfg: {
|
||||
style: {
|
||||
fontSize: 11,
|
||||
fontWeight: 600,
|
||||
fill: '#fff',
|
||||
stroke: '#000',
|
||||
lineWidth: 2, // 文字描边,增加对比度
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultEdge: {
|
||||
size: 1,
|
||||
color: '#e2e2e2',
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
}
|
||||
},
|
||||
nodeStateStyles: {
|
||||
active: {
|
||||
opacity: 1,
|
||||
shadowColor: 'rgba(0,0,0,0.5)',
|
||||
shadowBlur: 10,
|
||||
},
|
||||
inactive: {
|
||||
opacity: 0.1,
|
||||
},
|
||||
selected: {
|
||||
lineWidth: 4,
|
||||
stroke: '#F59E0B', // 选中时金色边框
|
||||
shadowColor: '#F59E0B',
|
||||
shadowBlur: 15,
|
||||
opacity: 1,
|
||||
}
|
||||
},
|
||||
edgeStateStyles: {
|
||||
active: {
|
||||
opacity: 1,
|
||||
stroke: '#333',
|
||||
lineWidth: 2,
|
||||
},
|
||||
inactive: {
|
||||
opacity: 0.05,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
graph.data(graphData)
|
||||
graph.render()
|
||||
|
||||
// 监听节点点击
|
||||
graph.on('node:click', (e) => {
|
||||
const item = e.item as INode
|
||||
const model = item.getModel()
|
||||
|
||||
// 更新选中状态
|
||||
setSelectedNode({
|
||||
...model.data as any,
|
||||
type: (model.data as any).type
|
||||
})
|
||||
|
||||
// 高亮逻辑
|
||||
const nodes = graph.getNodes()
|
||||
const edges = graph.getEdges()
|
||||
|
||||
// 1. 重置所有状态
|
||||
nodes.forEach(n => {
|
||||
graph.clearItemStates(n, ['active', 'inactive', 'selected'])
|
||||
graph.setItemState(n, 'inactive', true) // 默认全部变暗
|
||||
})
|
||||
edges.forEach(e => {
|
||||
graph.clearItemStates(e, ['active', 'inactive'])
|
||||
graph.setItemState(e, 'inactive', true) // 默认全部变暗
|
||||
})
|
||||
|
||||
// 2. 高亮当前节点
|
||||
graph.setItemState(item, 'inactive', false)
|
||||
graph.setItemState(item, 'selected', true)
|
||||
|
||||
// 3. 高亮邻居节点和边
|
||||
item.getNeighbors().forEach(neighbor => {
|
||||
graph.setItemState(neighbor, 'inactive', false)
|
||||
graph.setItemState(neighbor, 'active', true)
|
||||
})
|
||||
|
||||
item.getEdges().forEach(edge => {
|
||||
graph.setItemState(edge, 'inactive', false)
|
||||
graph.setItemState(edge, 'active', true)
|
||||
})
|
||||
})
|
||||
|
||||
// 画布点击清除选中
|
||||
graph.on('canvas:click', () => {
|
||||
setSelectedNode(null)
|
||||
// 恢复所有节点和边
|
||||
const nodes = graph.getNodes()
|
||||
const edges = graph.getEdges()
|
||||
nodes.forEach(n => {
|
||||
graph.clearItemStates(n, ['active', 'inactive', 'selected'])
|
||||
})
|
||||
edges.forEach(e => {
|
||||
graph.clearItemStates(e, ['active', 'inactive'])
|
||||
})
|
||||
})
|
||||
|
||||
graphRef.current = graph
|
||||
|
||||
const handleResize = () => {
|
||||
if (containerRef.current && graphRef.current) {
|
||||
graphRef.current.changeSize(
|
||||
containerRef.current.offsetWidth,
|
||||
containerRef.current.offsetHeight
|
||||
)
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
if (graphRef.current) {
|
||||
graphRef.current.destroy()
|
||||
}
|
||||
}
|
||||
}, [graphData])
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-100px)] bg-gray-50 dark:bg-gray-900 rounded-xl overflow-hidden border border-gray-200 dark:border-gray-700 relative">
|
||||
|
||||
{/* 图例 */}
|
||||
<div className="absolute top-4 left-4 bg-white/90 dark:bg-gray-800/90 p-3 rounded-lg shadow-md border border-gray-100 dark:border-gray-700 z-10 flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2 text-xs font-medium">
|
||||
<div className="w-3 h-3 rounded-full bg-indigo-500"></div>
|
||||
<span>过程 (Process) - 圆形</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs font-medium">
|
||||
<div className="w-3 h-3 rounded bg-emerald-500"></div>
|
||||
<span>工件 (Artifact) - 矩形</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs font-medium">
|
||||
<div className="w-3 h-3 rotate-45 bg-purple-500"></div>
|
||||
<span>工具 (Tool) - 菱形</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 画布 */}
|
||||
<div ref={containerRef} className="w-full h-full" />
|
||||
|
||||
{/* 缩放控制 */}
|
||||
<div className="absolute bottom-4 left-4 flex flex-col gap-2 bg-white dark:bg-gray-800 p-1 rounded-lg shadow-md border border-gray-200 dark:border-gray-700">
|
||||
<button onClick={() => graphRef.current?.zoom(1.2)} className="p-2 hover:bg-gray-100 rounded"><ZoomIn size={18}/></button>
|
||||
<button onClick={() => graphRef.current?.zoom(0.8)} className="p-2 hover:bg-gray-100 rounded"><ZoomOut size={18}/></button>
|
||||
<button onClick={() => graphRef.current?.fitView()} className="p-2 hover:bg-gray-100 rounded"><Maximize2 size={18}/></button>
|
||||
</div>
|
||||
|
||||
{/* 右侧详情面板 */}
|
||||
{selectedNode && details && (
|
||||
<>
|
||||
{/* 折叠/展开按钮 */}
|
||||
<motion.button
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
onClick={() => setIsSidebarCollapsed(!isSidebarCollapsed)}
|
||||
className={`absolute top-1/2 -translate-y-1/2 z-30 p-1.5 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-lg rounded-l-lg text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-all ${
|
||||
isSidebarCollapsed ? 'right-0' : 'right-96'
|
||||
}`}
|
||||
>
|
||||
{isSidebarCollapsed ? <ChevronLeft size={20} /> : <ChevronRight size={20} />}
|
||||
</motion.button>
|
||||
|
||||
<motion.div
|
||||
initial={{ x: '100%' }}
|
||||
animate={{ x: isSidebarCollapsed ? '100%' : 0 }}
|
||||
exit={{ x: '100%' }}
|
||||
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
|
||||
className="absolute right-0 top-0 bottom-0 w-96 bg-white dark:bg-gray-800 border-l border-gray-200 dark:border-gray-700 shadow-2xl z-20 flex flex-col"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="p-6 border-b border-gray-100 dark:border-gray-700 bg-gray-50/50 dark:bg-gray-800/50">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
{selectedNode.type === 'process' && <Activity className="text-indigo-600 dark:text-indigo-400" size={20} />}
|
||||
{selectedNode.type === 'artifact' && <FileText className="text-emerald-600 dark:text-emerald-400" size={20} />}
|
||||
{selectedNode.type === 'tool' && <Wrench className="text-purple-600 dark:text-purple-400" size={20} />}
|
||||
<span className="text-xs font-bold uppercase text-gray-500 dark:text-gray-400 tracking-wider">
|
||||
{selectedNode.type === 'process' ? '过程 Process' : selectedNode.type === 'artifact' ? '工件 Artifact' : '工具 Tool'}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-1 leading-tight">{selectedNode.name}</h2>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 font-medium">{selectedNode.nameEn}</div>
|
||||
|
||||
{selectedNode.type === 'process' && (
|
||||
<div className="mt-3 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800 dark:bg-indigo-900/30 dark:text-indigo-300">
|
||||
{selectedNode.code}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto p-6 space-y-6">
|
||||
|
||||
{/* Process Details */}
|
||||
{selectedNode.type === 'process' && details.inputDetails && (
|
||||
<>
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-gray-900 dark:text-white uppercase tracking-wider mb-3 flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
|
||||
输入 (Inputs)
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{details.inputDetails.length > 0 ? (
|
||||
details.inputDetails.map((item: any) => (
|
||||
<div key={item.id} className="p-3 bg-gray-50 dark:bg-gray-700/30 rounded-lg border border-gray-100 dark:border-gray-700 hover:border-emerald-200 dark:hover:border-emerald-800 transition-colors">
|
||||
<div className="text-sm font-medium text-gray-800 dark:text-gray-200">{item.name}</div>
|
||||
</div>
|
||||
))
|
||||
) : <div className="text-sm text-gray-400 italic">无输入</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-gray-900 dark:text-white uppercase tracking-wider mb-3 flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-purple-500"></span>
|
||||
工具与技术 (Tools)
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{details.toolDetails.length > 0 ? (
|
||||
details.toolDetails.map((item: any) => (
|
||||
<div key={item.id} className="p-3 bg-gray-50 dark:bg-gray-700/30 rounded-lg border border-gray-100 dark:border-gray-700 hover:border-purple-200 dark:hover:border-purple-800 transition-colors">
|
||||
<div className="text-sm font-medium text-gray-800 dark:text-gray-200">{item.name}</div>
|
||||
</div>
|
||||
))
|
||||
) : <div className="text-sm text-gray-400 italic">无工具</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-gray-900 dark:text-white uppercase tracking-wider mb-3 flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-blue-500"></span>
|
||||
输出 (Outputs)
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{details.outputDetails.length > 0 ? (
|
||||
details.outputDetails.map((item: any) => (
|
||||
<div key={item.id} className="p-3 bg-gray-50 dark:bg-gray-700/30 rounded-lg border border-gray-100 dark:border-gray-700 hover:border-blue-200 dark:hover:border-blue-800 transition-colors">
|
||||
<div className="text-sm font-medium text-gray-800 dark:text-gray-200">{item.name}</div>
|
||||
</div>
|
||||
))
|
||||
) : <div className="text-sm text-gray-400 italic">无输出</div>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Artifact Details */}
|
||||
{selectedNode.type === 'artifact' && details.usage && (
|
||||
<>
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-gray-900 dark:text-white uppercase tracking-wider mb-3 flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-indigo-500"></span>
|
||||
作为输入 (Used By)
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{details.usage.asInput.length > 0 ? (
|
||||
details.usage.asInput.map((p: any) => (
|
||||
<div key={p.id} className="p-3 bg-gray-50 dark:bg-gray-700/30 rounded-lg border border-gray-100 dark:border-gray-700">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-xs font-bold text-indigo-600 dark:text-indigo-400">{p.code}</span>
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-800 dark:text-gray-200">{p.name}</div>
|
||||
</div>
|
||||
))
|
||||
) : <div className="text-sm text-gray-400 italic">无引用</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-gray-900 dark:text-white uppercase tracking-wider mb-3 flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-blue-500"></span>
|
||||
作为输出 (Produced By)
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{details.usage.asOutput.length > 0 ? (
|
||||
details.usage.asOutput.map((p: any) => (
|
||||
<div key={p.id} className="p-3 bg-gray-50 dark:bg-gray-700/30 rounded-lg border border-gray-100 dark:border-gray-700">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-xs font-bold text-blue-600 dark:text-blue-400">{p.code}</span>
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-800 dark:text-gray-200">{p.name}</div>
|
||||
</div>
|
||||
))
|
||||
) : <div className="text-sm text-gray-400 italic">无来源</div>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Tool Details */}
|
||||
{selectedNode.type === 'tool' && details.usage && (
|
||||
<>
|
||||
<div>
|
||||
<h3 className="text-sm font-bold text-gray-900 dark:text-white uppercase tracking-wider mb-3 flex items-center gap-2">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-indigo-500"></span>
|
||||
使用于 (Used In)
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{details.usage.length > 0 ? (
|
||||
details.usage.map((p: any) => (
|
||||
<div key={p.id} className="p-3 bg-gray-50 dark:bg-gray-700/30 rounded-lg border border-gray-100 dark:border-gray-700">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="text-xs font-bold text-indigo-600 dark:text-indigo-400">{p.code}</span>
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-800 dark:text-gray-200">{p.name}</div>
|
||||
</div>
|
||||
))
|
||||
) : <div className="text-sm text-gray-400 italic">未使用</div>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
193
src/pages/ProcessGroupsPage.tsx
Normal file
193
src/pages/ProcessGroupsPage.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { motion } from 'framer-motion'
|
||||
import { ArrowRight, FileText, Wrench, FileOutput } from 'lucide-react'
|
||||
import { processGroups, processesByProcessGroup, processGroupMap, knowledgeAreaMap } from '@/data'
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: { staggerChildren: 0.05 },
|
||||
},
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
visible: { opacity: 1, y: 0 },
|
||||
}
|
||||
|
||||
export function ProcessGroupsPage() {
|
||||
const { id } = useParams()
|
||||
const selectedPG = id ? processGroupMap.get(id) : null
|
||||
const processes = id ? processesByProcessGroup.get(id) || [] : []
|
||||
|
||||
if (selectedPG) {
|
||||
// 显示过程组详情
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 面包屑 */}
|
||||
<nav className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<Link to="/process-groups" className="hover:text-indigo-600 dark:hover:text-indigo-400">
|
||||
过程组
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-gray-900 dark:text-white">{selectedPG.name}</span>
|
||||
</nav>
|
||||
|
||||
{/* 过程组标题 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="rounded-xl p-6"
|
||||
style={{ backgroundColor: `${selectedPG.color}15` }}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="flex h-14 w-14 items-center justify-center rounded-xl text-white font-bold text-xl"
|
||||
style={{ backgroundColor: selectedPG.color }}
|
||||
>
|
||||
{selectedPG.order}
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{selectedPG.name}
|
||||
</h1>
|
||||
<p className="text-gray-500 dark:text-gray-400">{selectedPG.nameEn}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-300">{selectedPG.description}</p>
|
||||
</motion.div>
|
||||
|
||||
{/* 过程列表 */}
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="space-y-4"
|
||||
>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
包含 {processes.length} 个过程
|
||||
</h2>
|
||||
{processes.map((process) => {
|
||||
const ka = knowledgeAreaMap.get(process.knowledgeAreaId)
|
||||
return (
|
||||
<motion.div key={process.id} variants={itemVariants}>
|
||||
<Link
|
||||
to={`/process/${process.id}`}
|
||||
className="group block bg-white dark:bg-gray-800 rounded-xl p-5 shadow-sm border border-gray-100 dark:border-gray-700 hover:shadow-md hover:border-gray-200 dark:hover:border-gray-600 transition-all"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="flex h-10 w-10 items-center justify-center rounded-lg text-white font-medium"
|
||||
style={{ backgroundColor: ka?.color || selectedPG.color }}
|
||||
>
|
||||
{process.code}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white">
|
||||
{process.name}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{process.nameEn}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
{ka && (
|
||||
<span
|
||||
className="px-3 py-1 rounded-full text-xs font-medium text-white"
|
||||
style={{ backgroundColor: ka.color }}
|
||||
>
|
||||
{ka.name}
|
||||
</span>
|
||||
)}
|
||||
<ArrowRight
|
||||
size={20}
|
||||
className="text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 group-hover:translate-x-1 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* ITTO统计 */}
|
||||
<div className="mt-4 flex items-center gap-6 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span className="flex items-center gap-1">
|
||||
<FileText size={14} />
|
||||
{process.inputs.length} 输入
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Wrench size={14} />
|
||||
{process.tools.length} 工具技术
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<FileOutput size={14} />
|
||||
{process.outputs.length} 输出
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
)
|
||||
})}
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 显示过程组列表
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">过程组</h1>
|
||||
<p className="text-gray-500 dark:text-gray-400 mt-1">
|
||||
PMBOK第6版定义的5大项目管理过程组
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="space-y-4"
|
||||
>
|
||||
{processGroups.map((pg) => (
|
||||
<motion.div key={pg.id} variants={itemVariants}>
|
||||
<Link
|
||||
to={`/process-groups/${pg.id}`}
|
||||
className="group block bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm border border-gray-100 dark:border-gray-700 hover:shadow-md transition-all"
|
||||
style={{ borderLeftWidth: 4, borderLeftColor: pg.color }}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="flex h-14 w-14 items-center justify-center rounded-xl text-white font-bold text-xl"
|
||||
style={{ backgroundColor: pg.color }}
|
||||
>
|
||||
{pg.order}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{pg.name}</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">{pg.nameEn}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-right">
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{pg.processCount}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">个过程</div>
|
||||
</div>
|
||||
<ArrowRight
|
||||
size={24}
|
||||
className="text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 group-hover:translate-x-1 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-4 text-gray-500 dark:text-gray-400">
|
||||
{pg.description}
|
||||
</p>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
20
src/pages/ProcessMatrixPage.tsx
Normal file
20
src/pages/ProcessMatrixPage.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 49过程矩阵页面
|
||||
*/
|
||||
import { ProcessMatrix } from '@/components/visualize'
|
||||
|
||||
export function ProcessMatrixPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">49过程矩阵</h1>
|
||||
<p className="text-gray-500 dark:text-gray-400 mt-1">
|
||||
知识领域 × 过程组 的全景矩阵视图,点击过程可查看详情
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 p-4 overflow-hidden">
|
||||
<ProcessMatrix />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
87
src/pages/SettingsPage.tsx
Normal file
87
src/pages/SettingsPage.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { motion } from 'framer-motion'
|
||||
import { Sun, Moon, Palette } from 'lucide-react'
|
||||
import { useAppStore } from '@/stores/useAppStore'
|
||||
|
||||
export function SettingsPage() {
|
||||
const { darkMode, setDarkMode } = useAppStore()
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">设置</h1>
|
||||
<p className="text-gray-500 dark:text-gray-400 mt-1">
|
||||
自定义您的使用体验
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 主题设置 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden"
|
||||
>
|
||||
<div className="flex items-center gap-3 px-6 py-4 border-b border-gray-100 dark:border-gray-700">
|
||||
<Palette size={20} className="text-indigo-600 dark:text-indigo-400" />
|
||||
<h2 className="font-semibold text-gray-900 dark:text-white">外观设置</h2>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="mb-4">
|
||||
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
主题模式
|
||||
</label>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
选择您喜欢的界面主题
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 max-w-xs">
|
||||
{[
|
||||
{ value: false, label: '浅色', icon: Sun },
|
||||
{ value: true, label: '深色', icon: Moon },
|
||||
].map((option) => {
|
||||
const Icon = option.icon
|
||||
const isSelected = darkMode === option.value
|
||||
return (
|
||||
<button
|
||||
key={option.label}
|
||||
onClick={() => setDarkMode(option.value)}
|
||||
className={`flex flex-col items-center gap-2 p-4 rounded-lg border-2 transition-all ${
|
||||
isSelected
|
||||
? 'border-indigo-500 bg-indigo-50 dark:bg-indigo-900/30'
|
||||
: 'border-gray-200 dark:border-gray-600 hover:border-gray-300 dark:hover:border-gray-500'
|
||||
}`}
|
||||
>
|
||||
<Icon
|
||||
size={24}
|
||||
className={isSelected ? 'text-indigo-600 dark:text-indigo-400' : 'text-gray-500'}
|
||||
/>
|
||||
<span
|
||||
className={`text-sm font-medium ${
|
||||
isSelected ? 'text-indigo-600 dark:text-indigo-400' : 'text-gray-700 dark:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* 关于 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 p-6"
|
||||
>
|
||||
<h2 className="font-semibold text-gray-900 dark:text-white mb-4">关于 ITTOView</h2>
|
||||
<div className="space-y-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<p>版本:1.0.0</p>
|
||||
<p>基于 PMBOK 第6版</p>
|
||||
<p>包含 49 个项目管理过程的完整 ITTO 数据</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
124
src/pages/ToolDetailPage.tsx
Normal file
124
src/pages/ToolDetailPage.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 工具与技术详情页面
|
||||
* 显示工具被哪些过程使用
|
||||
*/
|
||||
import { useParams, Link } from 'react-router-dom'
|
||||
import { motion } from 'framer-motion'
|
||||
import { ArrowLeft, Wrench, ArrowRight } from 'lucide-react'
|
||||
import { toolMap, getToolUsage, knowledgeAreaMap } from '@/data'
|
||||
|
||||
export function ToolDetailPage() {
|
||||
const { id } = useParams()
|
||||
const tool = id ? toolMap.get(id) : null
|
||||
const usedByProcesses = id ? getToolUsage(id) : []
|
||||
|
||||
if (!tool) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-20">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
工具未找到
|
||||
</h1>
|
||||
<Link to="/" className="text-indigo-600 dark:text-indigo-400 hover:underline">
|
||||
返回首页
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 返回按钮 */}
|
||||
<Link
|
||||
to="/"
|
||||
className="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-indigo-600 dark:hover:text-indigo-400"
|
||||
>
|
||||
<ArrowLeft size={16} />
|
||||
返回
|
||||
</Link>
|
||||
|
||||
{/* 工具信息 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm border border-gray-100 dark:border-gray-700"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-xl bg-amber-500 text-white">
|
||||
<Wrench size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{tool.name}
|
||||
</h1>
|
||||
<p className="text-gray-500 dark:text-gray-400">{tool.nameEn}</p>
|
||||
</div>
|
||||
</div>
|
||||
{tool.description && (
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-300">{tool.description}</p>
|
||||
)}
|
||||
<div className="mt-4 flex gap-2">
|
||||
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300">
|
||||
{tool.type}
|
||||
</span>
|
||||
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300">
|
||||
{tool.category}
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* 使用此工具的过程 */}
|
||||
{usedByProcesses.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden"
|
||||
>
|
||||
<div className="flex items-center gap-3 px-6 py-4 border-b border-gray-100 dark:border-gray-700 bg-amber-50 dark:bg-amber-900/20">
|
||||
<Wrench size={20} className="text-amber-600 dark:text-amber-400" />
|
||||
<h2 className="font-semibold text-gray-900 dark:text-white">
|
||||
使用此工具的过程({usedByProcesses.length})
|
||||
</h2>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{usedByProcesses.map((process) => {
|
||||
const ka = knowledgeAreaMap.get(process.knowledgeAreaId)
|
||||
return (
|
||||
<li key={process.id}>
|
||||
<Link
|
||||
to={`/process/${process.id}`}
|
||||
className="flex items-center justify-between px-6 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="flex h-10 w-10 items-center justify-center rounded-lg text-white font-medium"
|
||||
style={{ backgroundColor: ka?.color }}
|
||||
>
|
||||
{process.code}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-gray-900 dark:text-white">
|
||||
{process.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{ka?.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight size={18} className="text-gray-400" />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{usedByProcesses.length === 0 && (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl p-8 shadow-sm border border-gray-100 dark:border-gray-700 text-center text-gray-500 dark:text-gray-400">
|
||||
暂无使用此工具的过程记录
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
9
src/pages/index.ts
Normal file
9
src/pages/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export { HomePage } from './HomePage'
|
||||
export { KnowledgeAreasPage } from './KnowledgeAreasPage'
|
||||
export { ProcessGroupsPage } from './ProcessGroupsPage'
|
||||
export { ProcessDetailPage } from './ProcessDetailPage'
|
||||
export { ProcessMatrixPage } from './ProcessMatrixPage'
|
||||
export { ProcessGraphPage } from './ProcessGraphPage'
|
||||
export { ArtifactDetailPage } from './ArtifactDetailPage'
|
||||
export { ToolDetailPage } from './ToolDetailPage'
|
||||
export { SettingsPage } from './SettingsPage'
|
||||
42
src/stores/useAppStore.ts
Normal file
42
src/stores/useAppStore.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
interface AppState {
|
||||
// UI状态
|
||||
sidebarOpen: boolean
|
||||
darkMode: boolean
|
||||
searchQuery: string
|
||||
|
||||
// 操作
|
||||
toggleSidebar: () => void
|
||||
setSidebarOpen: (open: boolean) => void
|
||||
toggleDarkMode: () => void
|
||||
setDarkMode: (dark: boolean) => void
|
||||
setSearchQuery: (query: string) => void
|
||||
}
|
||||
|
||||
export const useAppStore = create<AppState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
// 初始状态
|
||||
sidebarOpen: true,
|
||||
darkMode: false,
|
||||
searchQuery: '',
|
||||
|
||||
// 操作方法
|
||||
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
|
||||
setSidebarOpen: (open) => set({ sidebarOpen: open }),
|
||||
toggleDarkMode: () => set((state) => ({ darkMode: !state.darkMode })),
|
||||
setDarkMode: (dark) => set({ darkMode: dark }),
|
||||
setSearchQuery: (query) => set({ searchQuery: query }),
|
||||
}),
|
||||
{
|
||||
name: 'ittoview-app-storage',
|
||||
partialize: (state) => ({
|
||||
sidebarOpen: state.sidebarOpen,
|
||||
darkMode: state.darkMode,
|
||||
// searchQuery 不持久化到 localStorage,刷新后重置
|
||||
}),
|
||||
}
|
||||
)
|
||||
)
|
||||
124
src/types/itto.ts
Normal file
124
src/types/itto.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* ITTO数据类型定义
|
||||
* PMP项目管理ITTO可视化学习平台
|
||||
*/
|
||||
|
||||
// 知识领域
|
||||
export interface KnowledgeArea {
|
||||
id: string; // 如 "KA01"
|
||||
name: string; // 如 "项目整合管理"
|
||||
nameEn: string; // 如 "Project Integration Management"
|
||||
chapter: number; // PMBOK章节号 4-13
|
||||
order: number; // 排序序号 1-10
|
||||
color: string; // 主题色
|
||||
description: string; // 简要描述
|
||||
processCount: number; // 包含的过程数量
|
||||
}
|
||||
|
||||
// 过程组
|
||||
export interface ProcessGroup {
|
||||
id: string; // 如 "PG01"
|
||||
name: string; // 如 "启动过程组"
|
||||
nameEn: string; // 如 "Initiating Process Group"
|
||||
order: number; // 排序序号 1-5
|
||||
color: string; // 主题色
|
||||
description: string; // 简要描述
|
||||
processCount: number; // 包含的过程数量
|
||||
}
|
||||
|
||||
// 过程
|
||||
export interface Process {
|
||||
id: string; // 如 "P4.1"
|
||||
code: string; // 如 "4.1"
|
||||
name: string; // 如 "制定项目章程"
|
||||
nameEn: string; // 如 "Develop Project Charter"
|
||||
knowledgeAreaId: string; // 所属知识领域ID
|
||||
processGroupId: string; // 所属过程组ID
|
||||
order: number; // 在知识领域内的序号
|
||||
inputs: string[]; // 输入工件ID列表
|
||||
tools: string[]; // 工具与技术ID列表
|
||||
outputs: string[]; // 输出工件ID列表
|
||||
}
|
||||
|
||||
// 工件类别
|
||||
export type ArtifactCategory =
|
||||
| 'document' // 文档
|
||||
| 'plan' // 计划
|
||||
| 'baseline' // 基准
|
||||
| 'report' // 报告
|
||||
| 'register' // 登记册
|
||||
| 'log' // 日志
|
||||
| 'deliverable' // 可交付成果
|
||||
| 'other'; // 其他
|
||||
|
||||
// 工件/文档
|
||||
export interface Artifact {
|
||||
id: string; // 如 "A001"
|
||||
name: string; // 如 "项目章程"
|
||||
nameEn: string; // 如 "Project Charter"
|
||||
category: ArtifactCategory;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// 工具类型
|
||||
export type ToolType =
|
||||
| 'tool' // 工具
|
||||
| 'technique' // 技术
|
||||
| 'method' // 方法
|
||||
| 'skill' // 技能
|
||||
| 'meeting'; // 会议
|
||||
|
||||
// 工具与技术
|
||||
export interface ToolTechnique {
|
||||
id: string; // 如 "TT001"
|
||||
name: string; // 如 "专家判断"
|
||||
nameEn: string; // 如 "Expert Judgment"
|
||||
type: ToolType;
|
||||
category: string; // 分类
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// 学习掌握程度
|
||||
export type MasteryLevel = 'familiar' | 'fuzzy' | 'unfamiliar';
|
||||
|
||||
// 学习记录
|
||||
export interface LearningRecord {
|
||||
id: string;
|
||||
processId: string;
|
||||
masteryLevel: MasteryLevel;
|
||||
reviewCount: number;
|
||||
correctCount: number;
|
||||
lastReviewAt: Date;
|
||||
nextReviewAt: Date; // 基于间隔重复算法计算
|
||||
}
|
||||
|
||||
// 题目类型
|
||||
export type QuestionType = 'choice' | 'fill' | 'match';
|
||||
|
||||
// 错题记录
|
||||
export interface WrongAnswer {
|
||||
id: string;
|
||||
questionId: string;
|
||||
questionType: QuestionType;
|
||||
userAnswer: string;
|
||||
correctAnswer: string;
|
||||
createdAt: Date;
|
||||
reviewed: boolean;
|
||||
}
|
||||
|
||||
// 学习统计
|
||||
export interface LearningStats {
|
||||
totalStudyTime: number; // 总学习时长(分钟)
|
||||
totalQuestions: number; // 总答题数
|
||||
correctRate: number; // 正确率
|
||||
masteredCount: number; // 已掌握过程数
|
||||
streakDays: number; // 连续学习天数
|
||||
lastStudyDate: Date;
|
||||
}
|
||||
|
||||
// 数据流向关系
|
||||
export interface DataFlow {
|
||||
sourceProcessId: string; // 源过程ID
|
||||
targetProcessId: string; // 目标过程ID
|
||||
artifactId: string; // 流转的工件ID
|
||||
}
|
||||
52
tailwind.config.js
Normal file
52
tailwind.config.js
Normal file
@@ -0,0 +1,52 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// 知识领域主题色
|
||||
ka: {
|
||||
integration: '#6366F1', // 整合管理 - Indigo
|
||||
scope: '#8B5CF6', // 范围管理 - Violet
|
||||
schedule: '#EC4899', // 进度管理 - Pink
|
||||
cost: '#10B981', // 成本管理 - Emerald
|
||||
quality: '#F59E0B', // 质量管理 - Amber
|
||||
resource: '#3B82F6', // 资源管理 - Blue
|
||||
communication: '#06B6D4', // 沟通管理 - Cyan
|
||||
risk: '#EF4444', // 风险管理 - Red
|
||||
procurement: '#84CC16', // 采购管理 - Lime
|
||||
stakeholder: '#F97316', // 相关方管理 - Orange
|
||||
},
|
||||
// 过程组主题色
|
||||
pg: {
|
||||
initiating: '#22C55E', // 启动 - Green
|
||||
planning: '#3B82F6', // 规划 - Blue
|
||||
executing: '#F59E0B', // 执行 - Amber
|
||||
monitoring: '#8B5CF6', // 监控 - Violet
|
||||
closing: '#6B7280', // 收尾 - Gray
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Segoe UI"',
|
||||
'Roboto',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'"Noto Sans"',
|
||||
'sans-serif',
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"',
|
||||
'"Noto Color Emoji"',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
31
tsconfig.json
Normal file
31
tsconfig.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
/* Path mapping */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
10
tsconfig.node.json
Normal file
10
tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
18
vite.config.ts
Normal file
18
vite.config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
base: './',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
open: true,
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user