feat(矩阵): 添加知识领域和过程组的显示/隐藏功能

feat(详情页): 为ITTO内容添加显示/隐藏控制功能

refactor: 优化状态管理使用localStorage持久化
This commit is contained in:
史悦
2026-02-14 00:42:45 +08:00
parent 033ae6b121
commit 6505f977d9
3 changed files with 439 additions and 103 deletions

View File

@@ -5,6 +5,7 @@
import { Link } from 'react-router-dom'
import { motion } from 'framer-motion'
import { clsx } from 'clsx'
import { Eye, EyeOff } from 'lucide-react'
import {
knowledgeAreas,
processGroups,
@@ -14,9 +15,20 @@ import {
interface ProcessMatrixProps {
className?: string
isFullScreen?: boolean
hiddenKnowledgeAreaIds?: Set<string>
hiddenProcessGroupIds?: Set<string>
onToggleKnowledgeArea?: (id: string) => void
onToggleProcessGroup?: (id: string) => void
}
export function ProcessMatrix({ className, isFullScreen = false }: ProcessMatrixProps) {
export function ProcessMatrix({
className,
isFullScreen = false,
hiddenKnowledgeAreaIds = new Set(),
hiddenProcessGroupIds = new Set(),
onToggleKnowledgeArea,
onToggleProcessGroup,
}: ProcessMatrixProps) {
// 构建矩阵数据knowledgeAreaId -> processGroupId -> Process[]
const matrix = new Map<string, Map<string, typeof processes>>()
@@ -47,62 +59,105 @@ export function ProcessMatrix({ className, isFullScreen = false }: ProcessMatrix
<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>
))}
{processGroups.map(pg => {
const isHidden = hiddenProcessGroupIds.has(pg.id)
return (
<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 className="flex flex-col items-center gap-2">
<div className={clsx("transition-opacity duration-150", isHidden && "opacity-30")}>
<div>{pg.name}</div>
<div className="text-xs opacity-80 font-normal mt-1">
{pg.processCount}
</div>
</div>
{onToggleProcessGroup && (
<button
type="button"
onClick={() => onToggleProcessGroup(pg.id)}
aria-label={isHidden ? `显示${pg.name}` : `隐藏${pg.name}`}
aria-pressed={!isHidden}
className="flex items-center gap-1 px-2 py-1 rounded text-xs font-medium bg-white/20 hover:bg-white/30 transition-colors"
>
{isHidden ? <Eye size={12} /> : <EyeOff size={12} />}
<span>{isHidden ? '显示' : '隐藏'}</span>
</button>
)}
</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,
}}
{knowledgeAreas.map((ka, kaIndex) => {
const isKaHidden = hiddenKnowledgeAreaIds.has(ka.id)
return (
<motion.tr
key={ka.id}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: kaIndex * 0.05 }}
>
<Link
to={`/knowledge-areas/${ka.id}`}
className="hover:underline text-gray-900 dark:text-white"
{/* 知识领域名称 */}
<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,
}}
>
<span className="font-bold mr-2" style={{ color: ka.color }}>
{ka.order}
</span>
{ka.name}
</Link>
</td>
<div className="flex items-center justify-between gap-2">
<Link
to={`/knowledge-areas/${ka.id}`}
className={clsx(
"hover:underline text-gray-900 dark:text-white transition-opacity duration-150",
isKaHidden && "opacity-30"
)}
>
<span className="font-bold mr-2" style={{ color: ka.color }}>
{ka.order}
</span>
{ka.name}
</Link>
{onToggleKnowledgeArea && (
<button
type="button"
onClick={() => onToggleKnowledgeArea(ka.id)}
aria-label={isKaHidden ? `显示${ka.name}` : `隐藏${ka.name}`}
aria-pressed={!isKaHidden}
className="flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-medium hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors shrink-0"
style={{ color: ka.color }}
>
{isKaHidden ? <Eye size={12} /> : <EyeOff size={12} />}
</button>
)}
</div>
</td>
{/* 每个过程组的单元格 */}
{processGroups.map(pg => {
const cellProcesses = matrix.get(ka.id)?.get(pg.id) || []
const isCellHidden = isKaHidden || hiddenProcessGroupIds.has(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={clsx(
"gap-1",
isFullScreen ? "grid grid-cols-2" : "space-y-1"
)}>
<div
className={clsx(
"gap-1 transition-opacity duration-150",
isFullScreen ? "grid grid-cols-2" : "space-y-1",
isCellHidden && "opacity-0 pointer-events-none"
)}
style={isCellHidden ? { visibility: 'hidden' } : undefined}
>
{cellProcesses.map(p => (
<Link
key={p.id}
@@ -142,7 +197,7 @@ export function ProcessMatrix({ className, isFullScreen = false }: ProcessMatrix
)
})}
</motion.tr>
))}
)})}
</tbody>
{/* 表尾:统计 */}