feat(process): 添加5W1H记忆辅助信息并优化页面布局
- 在Process接口中添加5W1H可选字段 - 为所有过程添加5W1H记忆辅助信息 - 优化知识领域、过程组和过程详情页面的紧凑布局 - 在过程详情页添加5W1H记忆卡片展示 - 调整动画效果和间距提升用户体验
This commit is contained in:
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user