diff --git a/src/components/visualize/ProcessMatrix.tsx b/src/components/visualize/ProcessMatrix.tsx index 5f9d06a..158e45c 100644 --- a/src/components/visualize/ProcessMatrix.tsx +++ b/src/components/visualize/ProcessMatrix.tsx @@ -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 + hiddenProcessGroupIds?: Set + 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>() @@ -47,62 +59,105 @@ export function ProcessMatrix({ className, isFullScreen = false }: ProcessMatrix 知识领域 / 过程组 - {processGroups.map(pg => ( - -
{pg.name}
-
- {pg.processCount} 个过程 -
- - ))} + {processGroups.map(pg => { + const isHidden = hiddenProcessGroupIds.has(pg.id) + return ( + +
+
+
{pg.name}
+
+ {pg.processCount} 个过程 +
+
+ {onToggleProcessGroup && ( + + )} +
+ + ) + })} {/* 表体:知识领域 × 过程 */} - {knowledgeAreas.map((ka, kaIndex) => ( - - {/* 知识领域名称 */} - { + const isKaHidden = hiddenKnowledgeAreaIds.has(ka.id) + return ( + - - - {ka.order} - - {ka.name} - - +
+ + + {ka.order} + + {ka.name} + + {onToggleKnowledgeArea && ( + + )} +
+ {/* 每个过程组的单元格 */} {processGroups.map(pg => { const cellProcesses = matrix.get(ka.id)?.get(pg.id) || [] + const isCellHidden = isKaHidden || hiddenProcessGroupIds.has(pg.id) return ( -
+
{cellProcesses.map(p => ( - ))} + )})} {/* 表尾:统计 */} diff --git a/src/pages/ProcessDetailPage.tsx b/src/pages/ProcessDetailPage.tsx index 5ad2d56..41c4926 100644 --- a/src/pages/ProcessDetailPage.tsx +++ b/src/pages/ProcessDetailPage.tsx @@ -1,7 +1,8 @@ import { useParams, Link, useLocation, useNavigate } from 'react-router-dom' -import { motion } from 'framer-motion' -import { ArrowLeft, ArrowRight, FileText, Wrench, FileOutput, LayoutGrid, Workflow, User, Target, Clock, MapPin, HelpCircle, Cog } from 'lucide-react' +import { motion, AnimatePresence } from 'framer-motion' +import { ArrowLeft, ArrowRight, FileText, Wrench, FileOutput, LayoutGrid, Workflow, User, Target, Clock, MapPin, HelpCircle, Cog, Eye, EyeOff } from 'lucide-react' import { getProcessDetail, processes, artifactMap, toolMap } from '@/data' +import { useState, useEffect } from 'react' // 5W1H图标和标签配置 const w5h1Config = { @@ -13,12 +14,54 @@ const w5h1Config = { how: { icon: Cog, label: 'How', title: '如何做', color: 'text-indigo-600 dark:text-indigo-400' }, } +type IttoSection = 'inputs' | 'tools' | 'outputs' +const STORAGE_KEY = 'ittoview:process-detail:itto-visibility' + export function ProcessDetailPage() { const { id } = useParams() const location = useLocation() const navigate = useNavigate() const processDetail = id ? getProcessDetail(id) : null + // ITTO 显示/隐藏状态管理 + const [visible, setVisible] = useState>(() => { + const defaultVisible = { inputs: true, tools: true, outputs: true } + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (!raw) return defaultVisible + const parsed = JSON.parse(raw) + // 数据校验:确保是对象且包含正确的键 + if (typeof parsed !== 'object' || parsed === null) return defaultVisible + return { + inputs: typeof parsed.inputs === 'boolean' ? parsed.inputs : true, + tools: typeof parsed.tools === 'boolean' ? parsed.tools : true, + outputs: typeof parsed.outputs === 'boolean' ? parsed.outputs : true, + } + } catch { + return defaultVisible + } + }) + + // 持久化状态 + useEffect(() => { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(visible)) + } catch (error) { + // 在隐私模式或受限环境下,localStorage 可能不可用 + console.warn('无法保存 ITTO 显示状态:', error) + } + }, [visible]) + + const toggleSection = (key: IttoSection) => { + setVisible(prev => ({ ...prev, [key]: !prev[key] })) + } + + const allVisible = Object.values(visible).every(Boolean) + const toggleAll = () => { + const newState = !allVisible + setVisible({ inputs: newState, tools: newState, outputs: newState }) + } + const fromMatrix = location.state?.from === 'matrix' const fromRoadmap = location.state?.from === 'roadmap' @@ -140,6 +183,25 @@ export function ProcessDetailPage() { )} + {/* 全局控制按钮 */} + + + + {/* ITTO表格 - 更紧凑 */} {/* 输入 */}
-
- -

输入 ({processDetail.inputs.length})

+
+
+ +

输入 ({processDetail.inputs.length})

+
+
-
    - {processDetail.inputs.map((inputId) => { - const artifact = artifactMap.get(inputId) - return ( -
  • -
    {artifact?.name || inputId}
    - {artifact &&
    {artifact.nameEn}
    } -
  • - ) - })} -
+ + {visible.inputs ? ( + + {processDetail.inputs.map((inputId) => { + const artifact = artifactMap.get(inputId) + return ( +
  • +
    {artifact?.name || inputId}
    + {artifact &&
    {artifact.nameEn}
    } +
  • + ) + })} +
    + ) : ( + + {processDetail.inputs.length} 项已隐藏 + + )} +
    {/* 工具与技术 */}
    -
    - -

    工具与技术 ({processDetail.tools.length})

    +
    +
    + +

    工具与技术 ({processDetail.tools.length})

    +
    +
    -
      - {processDetail.tools.map((toolId) => { - const tool = toolMap.get(toolId) - return ( -
    • -
      {tool?.name || toolId}
      - {tool &&
      {tool.nameEn}
      } -
    • - ) - })} -
    + + {visible.tools ? ( + + {processDetail.tools.map((toolId) => { + const tool = toolMap.get(toolId) + return ( +
  • +
    {tool?.name || toolId}
    + {tool &&
    {tool.nameEn}
    } +
  • + ) + })} +
    + ) : ( + + {processDetail.tools.length} 项已隐藏 + + )} +
    {/* 输出 */}
    -
    - -

    输出 ({processDetail.outputs.length})

    +
    +
    + +

    输出 ({processDetail.outputs.length})

    +
    +
    -
      - {processDetail.outputs.map((outputId) => { - const artifact = artifactMap.get(outputId) - return ( -
    • -
      {artifact?.name || outputId}
      - {artifact &&
      {artifact.nameEn}
      } -
    • - ) - })} -
    + + {visible.outputs ? ( + + {processDetail.outputs.map((outputId) => { + const artifact = artifactMap.get(outputId) + return ( +
  • +
    {artifact?.name || outputId}
    + {artifact &&
    {artifact.nameEn}
    } +
  • + ) + })} +
    + ) : ( + + {processDetail.outputs.length} 项已隐藏 + + )} +
    diff --git a/src/pages/ProcessMatrixPage.tsx b/src/pages/ProcessMatrixPage.tsx index f2a738d..5ffcc81 100644 --- a/src/pages/ProcessMatrixPage.tsx +++ b/src/pages/ProcessMatrixPage.tsx @@ -1,17 +1,102 @@ /** * 49过程矩阵页面 */ -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import { ProcessMatrix } from '@/components/visualize' -import { Maximize2, Minimize2 } from 'lucide-react' +import { Maximize2, Minimize2, Eye } from 'lucide-react' import { useAppStore } from '@/stores/useAppStore' import { clsx } from 'clsx' +import { knowledgeAreas, processGroups } from '@/data' + +const STORAGE_KEY = 'ittoview:process-matrix:hidden-items' + +interface HiddenIds { + knowledgeAreas: Set + processGroups: Set +} + +// 从 localStorage 加载并清洗无效 ID +function loadHiddenIds(): HiddenIds { + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (!raw) return { knowledgeAreas: new Set(), processGroups: new Set() } + const parsed = JSON.parse(raw) + if (typeof parsed !== 'object' || !parsed) { + return { knowledgeAreas: new Set(), processGroups: new Set() } + } + + // 清洗无效 ID + const validKaIds = new Set(knowledgeAreas.map(ka => ka.id)) + const validPgIds = new Set(processGroups.map(pg => pg.id)) + + const kaIds = Array.isArray(parsed.knowledgeAreas) + ? parsed.knowledgeAreas.filter((id: string) => validKaIds.has(id)) + : [] + const pgIds = Array.isArray(parsed.processGroups) + ? parsed.processGroups.filter((id: string) => validPgIds.has(id)) + : [] + + return { + knowledgeAreas: new Set(kaIds), + processGroups: new Set(pgIds), + } + } catch { + return { knowledgeAreas: new Set(), processGroups: new Set() } + } +} export function ProcessMatrixPage() { const isFullScreen = useAppStore((s) => s.matrixFullScreen) const setMatrixFullScreen = useAppStore((s) => s.setMatrixFullScreen) const setSidebarOpen = useAppStore((s) => s.setSidebarOpen) + // 显示/隐藏状态管理 + const [hiddenIds, setHiddenIds] = useState(() => loadHiddenIds()) + const hiddenKnowledgeAreaIds = hiddenIds.knowledgeAreas + const hiddenProcessGroupIds = hiddenIds.processGroups + + // 持久化状态 + useEffect(() => { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify({ + knowledgeAreas: Array.from(hiddenKnowledgeAreaIds), + processGroups: Array.from(hiddenProcessGroupIds), + })) + } catch (error) { + console.warn('无法保存矩阵显示状态:', error) + } + }, [hiddenKnowledgeAreaIds, hiddenProcessGroupIds]) + + const toggleKnowledgeArea = (id: string) => { + setHiddenIds(prev => { + const next = new Set(prev.knowledgeAreas) + if (next.has(id)) { + next.delete(id) + } else { + next.add(id) + } + return { ...prev, knowledgeAreas: next } + }) + } + + const toggleProcessGroup = (id: string) => { + setHiddenIds(prev => { + const next = new Set(prev.processGroups) + if (next.has(id)) { + next.delete(id) + } else { + next.add(id) + } + return { ...prev, processGroups: next } + }) + } + + const showAll = () => { + setHiddenIds({ knowledgeAreas: new Set(), processGroups: new Set() }) + } + + const hasHidden = hiddenKnowledgeAreaIds.size > 0 || hiddenProcessGroupIds.size > 0 + const toggleFullScreen = () => { if (!isFullScreen) { setSidebarOpen(false) @@ -46,20 +131,34 @@ export function ProcessMatrixPage() { )} {!isFullScreen && ( -
    +

    49过程矩阵

    知识领域 × 过程组 的全景矩阵视图,点击过程可查看详情

    - +
    + {hasHidden && ( + + )} + +
    )} @@ -71,13 +170,27 @@ export function ProcessMatrixPage() { {isFullScreen && (
    49过程矩阵全景图 - +
    + {hasHidden && ( + + )} + +
    )} @@ -85,6 +198,10 @@ export function ProcessMatrixPage() {