feat(process): 添加5W1H记忆辅助信息并优化页面布局
- 在Process接口中添加5W1H可选字段 - 为所有过程添加5W1H记忆辅助信息 - 优化知识领域、过程组和过程详情页面的紧凑布局 - 在过程详情页添加5W1H记忆卡片展示 - 调整动画效果和间距提升用户体验
This commit is contained in:
@@ -5,14 +5,11 @@ import { knowledgeAreas, processesByKnowledgeArea, knowledgeAreaMap, processGrou
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: { staggerChildren: 0.05 },
|
||||
},
|
||||
visible: { opacity: 1, transition: { staggerChildren: 0.03 } },
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
hidden: { opacity: 0, y: 10 },
|
||||
visible: { opacity: 1, y: 0 },
|
||||
}
|
||||
|
||||
@@ -22,106 +19,71 @@ export function KnowledgeAreasPage() {
|
||||
const processes = id ? processesByKnowledgeArea.get(id) || [] : []
|
||||
|
||||
if (selectedKA) {
|
||||
// 显示知识领域详情
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
{/* 面包屑 */}
|
||||
<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>
|
||||
<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 }}
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="rounded-xl p-6"
|
||||
className="rounded-xl p-4"
|
||||
style={{ backgroundColor: `${selectedKA.color}15` }}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="flex h-14 w-14 items-center justify-center rounded-xl text-white font-bold text-xl"
|
||||
className="flex h-12 w-12 items-center justify-center rounded-lg text-white font-bold text-lg"
|
||||
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 className="flex-1">
|
||||
<h1 className="text-xl font-bold text-gray-900 dark:text-white">{selectedKA.name}</h1>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">{selectedKA.nameEn}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">{processes.length}</div>
|
||||
<div className="text-xs text-gray-500">个过程</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-300">{selectedKA.description}</p>
|
||||
<p className="mt-2 text-sm 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>
|
||||
{/* 过程列表 - 紧凑版 */}
|
||||
<motion.div variants={containerVariants} initial="hidden" animate="visible" className="space-y-2">
|
||||
{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"
|
||||
className="group flex items-center gap-3 bg-white dark:bg-gray-800 rounded-lg p-3 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
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-white font-medium text-sm shrink-0"
|
||||
style={{ backgroundColor: selectedKA.color }}
|
||||
>
|
||||
{process.code}
|
||||
</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 className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-gray-900 dark:text-white text-sm truncate">{process.name}</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 truncate">{process.nameEn}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400 shrink-0">
|
||||
<span className="flex items-center gap-1"><FileText size={12} />{process.inputs.length}</span>
|
||||
<span className="flex items-center gap-1"><Wrench size={12} />{process.tools.length}</span>
|
||||
<span className="flex items-center gap-1"><FileOutput size={12} />{process.outputs.length}</span>
|
||||
{pg && (
|
||||
<span className="px-2 py-0.5 rounded-full text-xs font-medium text-white hidden sm:inline" style={{ backgroundColor: pg.color }}>
|
||||
{pg.name}
|
||||
</span>
|
||||
)}
|
||||
<ArrowRight size={16} className="text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 group-hover:translate-x-0.5 transition-all" />
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
@@ -132,55 +94,41 @@ export function KnowledgeAreasPage() {
|
||||
)
|
||||
}
|
||||
|
||||
// 显示知识领域列表
|
||||
// 显示知识领域列表 - 紧凑版双列
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<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>
|
||||
<h1 className="text-xl font-bold text-gray-900 dark:text-white">知识领域</h1>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">PMBOK第6版定义的10大项目管理知识领域</p>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="grid md:grid-cols-2 gap-4"
|
||||
className="grid md:grid-cols-2 gap-3"
|
||||
>
|
||||
{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 }}
|
||||
className="group flex items-center gap-3 bg-white dark:bg-gray-800 rounded-lg p-3 shadow-sm border border-gray-100 dark:border-gray-700 hover:shadow-md transition-all"
|
||||
style={{ borderLeftWidth: 3, 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
|
||||
className="flex h-10 w-10 items-center justify-center rounded-lg text-white font-bold shrink-0"
|
||||
style={{ backgroundColor: ka.color }}
|
||||
>
|
||||
{ka.order}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white text-sm">{ka.name}</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 truncate">{ka.nameEn}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-300">{ka.processCount}个</span>
|
||||
<ArrowRight size={16} className="text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 group-hover:translate-x-0.5 transition-all" />
|
||||
</div>
|
||||
<p className="mt-3 text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
|
||||
{ka.description}
|
||||
</p>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
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 { ArrowLeft, ArrowRight, FileText, Wrench, FileOutput, LayoutGrid, User, Target, Clock, MapPin, HelpCircle, Cog } from 'lucide-react'
|
||||
import { getProcessDetail, processes, artifactMap, toolMap } from '@/data'
|
||||
|
||||
// 5W1H图标和标签配置
|
||||
const w5h1Config = {
|
||||
who: { icon: User, label: 'Who', title: '谁负责', color: 'text-blue-600 dark:text-blue-400' },
|
||||
what: { icon: Target, label: 'What', title: '做什么', color: 'text-emerald-600 dark:text-emerald-400' },
|
||||
when: { icon: Clock, label: 'When', title: '何时', color: 'text-amber-600 dark:text-amber-400' },
|
||||
where: { icon: MapPin, label: 'Where', title: '何地', color: 'text-purple-600 dark:text-purple-400' },
|
||||
why: { icon: HelpCircle, label: 'Why', title: '为什么', color: 'text-rose-600 dark:text-rose-400' },
|
||||
how: { icon: Cog, label: 'How', title: '如何做', color: 'text-indigo-600 dark:text-indigo-400' },
|
||||
}
|
||||
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
@@ -30,38 +34,31 @@ export function ProcessDetailPage() {
|
||||
|
||||
const ka = processDetail.knowledgeArea
|
||||
const pg = processDetail.processGroup
|
||||
const w5h1 = (processDetail as any).w5h1
|
||||
|
||||
// 获取前后过程
|
||||
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="space-y-4">
|
||||
{/* 返回按钮 + 面包屑 */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
{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"
|
||||
className="flex items-center gap-1.5 px-2.5 py-1 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 font-medium"
|
||||
>
|
||||
<LayoutGrid size={16} />
|
||||
<LayoutGrid size={14} />
|
||||
返回矩阵
|
||||
</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>
|
||||
<nav className="flex items-center gap-1.5 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>
|
||||
<Link to={`/knowledge-areas/${ka.id}`} className="hover:text-indigo-600 dark:hover:text-indigo-400">{ka.name}</Link>
|
||||
<span>/</span>
|
||||
</>
|
||||
)}
|
||||
@@ -69,74 +66,90 @@ export function ProcessDetailPage() {
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* 过程标题 */}
|
||||
{/* 过程标题 - 更紧凑 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
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"
|
||||
className="rounded-xl p-4 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 items-center gap-3">
|
||||
<div
|
||||
className="flex h-16 w-16 items-center justify-center rounded-xl text-white font-bold text-xl shrink-0"
|
||||
className="flex h-12 w-12 items-center justify-center rounded-lg text-white font-bold text-lg 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 className="flex-1 min-w-0">
|
||||
<h1 className="text-xl font-bold text-gray-900 dark:text-white truncate">{processDetail.name}</h1>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">{processDetail.nameEn}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{ka && (
|
||||
<span className="px-2.5 py-1 rounded-full text-xs font-medium text-white" style={{ backgroundColor: ka.color }}>
|
||||
{ka.name}
|
||||
</span>
|
||||
)}
|
||||
{pg && (
|
||||
<span className="px-2.5 py-1 rounded-full text-xs font-medium text-white" style={{ backgroundColor: pg.color }}>
|
||||
{pg.name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* ITTO表格 */}
|
||||
{/* 5W1H记忆卡片 */}
|
||||
{w5h1 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.05 }}
|
||||
className="bg-gradient-to-r from-slate-50 to-gray-50 dark:from-gray-800 dark:to-gray-800 rounded-xl p-4 border border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3 flex items-center gap-2">
|
||||
<span className="px-2 py-0.5 bg-indigo-100 dark:bg-indigo-900/50 text-indigo-700 dark:text-indigo-300 rounded text-xs">5W1H</span>
|
||||
记忆要点
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
{(Object.keys(w5h1Config) as Array<keyof typeof w5h1Config>).map((key) => {
|
||||
const config = w5h1Config[key]
|
||||
const Icon = config.icon
|
||||
const value = w5h1[key]
|
||||
return (
|
||||
<div key={key} className="bg-white dark:bg-gray-700/50 rounded-lg p-2.5 border border-gray-100 dark:border-gray-600">
|
||||
<div className="flex items-center gap-1.5 mb-1">
|
||||
<Icon size={14} className={config.color} />
|
||||
<span className={`text-xs font-semibold ${config.color}`}>{config.label}</span>
|
||||
<span className="text-xs text-gray-400">({config.title})</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-700 dark:text-gray-300 leading-relaxed">{value}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* ITTO表格 - 更紧凑 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="grid lg:grid-cols-3 gap-6"
|
||||
className="grid lg:grid-cols-3 gap-4"
|
||||
>
|
||||
{/* 输入 */}
|
||||
<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 className="flex items-center gap-2 px-3 py-2 bg-blue-50 dark:bg-blue-900/30 border-b border-blue-100 dark:border-blue-800">
|
||||
<FileText size={16} className="text-blue-600 dark:text-blue-400" />
|
||||
<h3 className="font-semibold text-blue-900 dark:text-blue-100 text-sm">输入 ({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 key={inputId} className="px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<div className="font-medium text-gray-900 dark:text-white text-sm">{artifact?.name || inputId}</div>
|
||||
{artifact && <div className="text-xs text-gray-500 dark:text-gray-400">{artifact.nameEn}</div>}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
@@ -145,25 +158,17 @@ export function ProcessDetailPage() {
|
||||
|
||||
{/* 工具与技术 */}
|
||||
<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 className="flex items-center gap-2 px-3 py-2 bg-amber-50 dark:bg-amber-900/30 border-b border-amber-100 dark:border-amber-800">
|
||||
<Wrench size={16} className="text-amber-600 dark:text-amber-400" />
|
||||
<h3 className="font-semibold text-amber-900 dark:text-amber-100 text-sm">工具与技术 ({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 key={toolId} className="px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<div className="font-medium text-gray-900 dark:text-white text-sm">{tool?.name || toolId}</div>
|
||||
{tool && <div className="text-xs text-gray-500 dark:text-gray-400">{tool.nameEn}</div>}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
@@ -172,25 +177,17 @@ export function ProcessDetailPage() {
|
||||
|
||||
{/* 输出 */}
|
||||
<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 className="flex items-center gap-2 px-3 py-2 bg-emerald-50 dark:bg-emerald-900/30 border-b border-emerald-100 dark:border-emerald-800">
|
||||
<FileOutput size={16} className="text-emerald-600 dark:text-emerald-400" />
|
||||
<h3 className="font-semibold text-emerald-900 dark:text-emerald-100 text-sm">输出 ({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 key={outputId} className="px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<div className="font-medium text-gray-900 dark:text-white text-sm">{artifact?.name || outputId}</div>
|
||||
{artifact && <div className="text-xs text-gray-500 dark:text-gray-400">{artifact.nameEn}</div>}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
@@ -198,41 +195,37 @@ export function ProcessDetailPage() {
|
||||
</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"
|
||||
transition={{ delay: 0.15 }}
|
||||
className="flex items-center justify-between pt-3 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"
|
||||
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-sm"
|
||||
>
|
||||
<ArrowLeft size={18} />
|
||||
<ArrowLeft size={16} />
|
||||
<div>
|
||||
<div className="text-xs text-gray-400">上一个过程</div>
|
||||
<div className="text-xs text-gray-400">上一个</div>
|
||||
<div className="font-medium">{prevProcess.code} {prevProcess.name}</div>
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
) : <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"
|
||||
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 text-sm"
|
||||
>
|
||||
<div>
|
||||
<div className="text-xs text-gray-400">下一个过程</div>
|
||||
<div className="text-xs text-gray-400">下一个</div>
|
||||
<div className="font-medium">{nextProcess.code} {nextProcess.name}</div>
|
||||
</div>
|
||||
<ArrowRight size={18} />
|
||||
<ArrowRight size={16} />
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
) : <div />}
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,14 +5,11 @@ import { processGroups, processesByProcessGroup, processGroupMap, knowledgeAreaM
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: { staggerChildren: 0.05 },
|
||||
},
|
||||
visible: { opacity: 1, transition: { staggerChildren: 0.03 } },
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
hidden: { opacity: 0, y: 10 },
|
||||
visible: { opacity: 1, y: 0 },
|
||||
}
|
||||
|
||||
@@ -22,106 +19,71 @@ export function ProcessGroupsPage() {
|
||||
const processes = id ? processesByProcessGroup.get(id) || [] : []
|
||||
|
||||
if (selectedPG) {
|
||||
// 显示过程组详情
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
{/* 面包屑 */}
|
||||
<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>
|
||||
<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 }}
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="rounded-xl p-6"
|
||||
className="rounded-xl p-4"
|
||||
style={{ backgroundColor: `${selectedPG.color}15` }}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="flex h-14 w-14 items-center justify-center rounded-xl text-white font-bold text-xl"
|
||||
className="flex h-12 w-12 items-center justify-center rounded-lg text-white font-bold text-lg"
|
||||
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 className="flex-1">
|
||||
<h1 className="text-xl font-bold text-gray-900 dark:text-white">{selectedPG.name}</h1>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">{selectedPG.nameEn}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">{processes.length}</div>
|
||||
<div className="text-xs text-gray-500">个过程</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-300">{selectedPG.description}</p>
|
||||
<p className="mt-2 text-sm 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>
|
||||
{/* 过程列表 - 紧凑版 */}
|
||||
<motion.div variants={containerVariants} initial="hidden" animate="visible" className="space-y-2">
|
||||
{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"
|
||||
className="group flex items-center gap-3 bg-white dark:bg-gray-800 rounded-lg p-3 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
|
||||
className="flex h-9 w-9 items-center justify-center rounded-lg text-white font-medium text-sm shrink-0"
|
||||
style={{ backgroundColor: ka?.color || selectedPG.color }}
|
||||
>
|
||||
{process.code}
|
||||
</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 className="flex-1 min-w-0">
|
||||
<h3 className="font-medium text-gray-900 dark:text-white text-sm truncate">{process.name}</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 truncate">{process.nameEn}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400 shrink-0">
|
||||
<span className="flex items-center gap-1"><FileText size={12} />{process.inputs.length}</span>
|
||||
<span className="flex items-center gap-1"><Wrench size={12} />{process.tools.length}</span>
|
||||
<span className="flex items-center gap-1"><FileOutput size={12} />{process.outputs.length}</span>
|
||||
{ka && (
|
||||
<span className="px-2 py-0.5 rounded-full text-xs font-medium text-white hidden sm:inline" style={{ backgroundColor: ka.color }}>
|
||||
{ka.name}
|
||||
</span>
|
||||
)}
|
||||
<ArrowRight size={16} className="text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 group-hover:translate-x-0.5 transition-all" />
|
||||
</div>
|
||||
</Link>
|
||||
</motion.div>
|
||||
@@ -132,58 +94,40 @@ export function ProcessGroupsPage() {
|
||||
)
|
||||
}
|
||||
|
||||
// 显示过程组列表
|
||||
// 显示过程组列表 - 紧凑版
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<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>
|
||||
<h1 className="text-xl font-bold text-gray-900 dark:text-white">过程组</h1>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">PMBOK第6版定义的5大项目管理过程组</p>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="space-y-4"
|
||||
>
|
||||
<motion.div variants={containerVariants} initial="hidden" animate="visible" className="space-y-2">
|
||||
{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"
|
||||
className="group flex items-center gap-4 bg-white dark:bg-gray-800 rounded-xl p-4 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
|
||||
className="flex h-12 w-12 items-center justify-center rounded-lg text-white font-bold text-lg shrink-0"
|
||||
style={{ backgroundColor: pg.color }}
|
||||
>
|
||||
{pg.order}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="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>
|
||||
<p className="text-xs text-gray-400 dark:text-gray-500 mt-1 line-clamp-1">{pg.description}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 shrink-0">
|
||||
<div className="text-right">
|
||||
<div className="text-xl font-bold text-gray-900 dark:text-white">{pg.processCount}</div>
|
||||
<div className="text-xs text-gray-500">个过程</div>
|
||||
</div>
|
||||
<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>
|
||||
<p className="mt-4 text-gray-500 dark:text-gray-400">
|
||||
{pg.description}
|
||||
</p>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user