feat(process): 添加5W1H记忆辅助信息并优化页面布局

- 在Process接口中添加5W1H可选字段
- 为所有过程添加5W1H记忆辅助信息
- 优化知识领域、过程组和过程详情页面的紧凑布局
- 在过程详情页添加5W1H记忆卡片展示
- 调整动画效果和间距提升用户体验
This commit is contained in:
史悦
2026-02-03 10:14:24 +08:00
parent f0823fad30
commit 8651747c12
5 changed files with 679 additions and 392 deletions

View File

@@ -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>
)