From f9cb27b14e914f802d4a60dbbad7801bce336a6e Mon Sep 17 00:00:00 2001 From: ittoview Date: Mon, 18 May 2026 16:12:41 +0100 Subject: [PATCH] feat: add ITTO collections view --- src/App.tsx | 2 + src/pages/IttoCollectionsPage.tsx | 197 ++++++++++++++++++++++++++++++ src/pages/ProcessDetailPage.tsx | 4 +- src/pages/ToolDetailPage.tsx | 2 +- src/pages/index.ts | 1 + 5 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 src/pages/IttoCollectionsPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 487f9b0..df8082d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,6 +17,7 @@ import PerformanceDomainPracticePage from './pages/PerformanceDomainPracticePage import KnowledgeAreasTailoringPage from './pages/KnowledgeAreasTailoringPage' import { LearningMapsPage } from './pages/LearningMapsPage' import { ApiDocPage } from './pages/ApiDocPage' +import { IttoCollectionsPage } from './pages/IttoCollectionsPage' function App() { return ( @@ -30,6 +31,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/pages/IttoCollectionsPage.tsx b/src/pages/IttoCollectionsPage.tsx new file mode 100644 index 0000000..0143a87 --- /dev/null +++ b/src/pages/IttoCollectionsPage.tsx @@ -0,0 +1,197 @@ +import { useMemo, useState } from 'react' +import { motion } from 'framer-motion' +import { FileOutput, FileText, Wrench } from 'lucide-react' +import { + artifactMap, + knowledgeAreas, + processGroups, + processes, + toolMap, + normalizeProcessRef, +} from '@/data' +import type { Process, ProcessRef } from '@/types/itto' + +type ViewKey = 'inputs' | 'tools' | 'outputs' + +type CollectionRow = { + processGroupId: string + processGroupName: string + color: string + items: string[] +} + +type CollectionArea = { + id: string + order: number + name: string + nameEn: string + color: string + rows: CollectionRow[] +} + +const tabs: Array<{ key: ViewKey; label: string; icon: typeof FileText }> = [ + { key: 'inputs', label: '输入', icon: FileText }, + { key: 'tools', label: '工具', icon: Wrench }, + { key: 'outputs', label: '输出', icon: FileOutput }, +] + +const containerVariants = { + hidden: { opacity: 0 }, + visible: { opacity: 1, transition: { staggerChildren: 0.03 } }, +} + +const itemVariants = { + hidden: { opacity: 0, y: 10 }, + visible: { opacity: 1, y: 0 }, +} + +function formatRef(ref: ProcessRef, viewKey: ViewKey) { + const normalized = normalizeProcessRef(ref) + const entity = viewKey === 'tools' ? toolMap.get(normalized.id) : artifactMap.get(normalized.id) + const name = entity?.name ?? normalized.id + const detail = normalized.detail?.map((item) => item.label).filter(Boolean) ?? [] + + if (!detail.length) return name + return `${name}(${detail.join('、')})` +} + +function getRefsByView(process: Process, viewKey: ViewKey) { + if (viewKey === 'inputs') return process.inputs + if (viewKey === 'tools') return process.tools + return process.outputs +} + +function uniqueItems(items: string[]) { + return Array.from(new Set(items)) +} + +function buildCollection(viewKey: ViewKey): CollectionArea[] { + return knowledgeAreas.map((area) => { + const areaProcesses = processes.filter((process) => process.knowledgeAreaId === area.id) + const rows = processGroups.map((group) => { + const groupProcesses = areaProcesses.filter((process) => process.processGroupId === group.id) + const items = uniqueItems( + groupProcesses.flatMap((process) => + getRefsByView(process, viewKey).map((ref) => formatRef(ref, viewKey)) + ) + ) + + return { + processGroupId: group.id, + processGroupName: group.name, + color: group.color, + items, + } + }).filter((row) => row.items.length > 0) + + return { + id: area.id, + order: area.order, + name: area.name, + nameEn: area.nameEn, + color: area.color, + rows, + } + }) +} + +export function IttoCollectionsPage() { + const [activeTab, setActiveTab] = useState('inputs') + const collection = useMemo(() => buildCollection(activeTab), [activeTab]) + + return ( +
+
+
+

输入 · 工具 · 输出

+

按知识领域与过程组汇总

+
+
+ {tabs.map((tab) => { + const Icon = tab.icon + const isActive = activeTab === tab.key + return ( + + ) + })} +
+
+ + + {collection.map((area) => ( + +
+
+ {area.order} +
+
+

{area.name}

+

{area.nameEn}

+
+
+ +
+ + + + + + + + + {area.rows.map((row) => ( + + + + + ))} + +
+ 过程组 + + 对应集合 +
+ + {row.processGroupName} + + + {row.items.join(';')} +
+
+
+ ))} +
+
+ ) +} diff --git a/src/pages/ProcessDetailPage.tsx b/src/pages/ProcessDetailPage.tsx index 81a3d4a..a7bbfeb 100644 --- a/src/pages/ProcessDetailPage.tsx +++ b/src/pages/ProcessDetailPage.tsx @@ -795,7 +795,7 @@ export function ProcessDetailPage() { {showAnswer && practiceMode === 'proficient' && (
- 当前显示:{SECTION_LABELS[currentSection]}分组未完成答案({currentSectionRemainingItems.length} 项) + {SECTION_LABELS[currentSection]}分组未完成答案({currentSectionRemainingItems.length} 项)
)} {showAnswer && practiceMode === 'standard' && currentPracticeItem && ( @@ -1021,7 +1021,7 @@ function PracticeList({ {'_'.repeat(item.name.length)} {mode === 'proficient' && isCurrentSection && ( - 待输入 + 待作答 )} )} diff --git a/src/pages/ToolDetailPage.tsx b/src/pages/ToolDetailPage.tsx index 1a3283e..4166fed 100644 --- a/src/pages/ToolDetailPage.tsx +++ b/src/pages/ToolDetailPage.tsx @@ -116,7 +116,7 @@ export function ToolDetailPage() { {usedByProcesses.length === 0 && (
- 暂无使用此工具的过程记录 + 未找到相关过程
)} diff --git a/src/pages/index.ts b/src/pages/index.ts index c4f54c9..fc8a1bb 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -11,3 +11,4 @@ export { SettingsPage } from './SettingsPage' export { PerformanceDomainsPage } from './PerformanceDomainsPage' export { LearningMapsPage } from './LearningMapsPage' +export { IttoCollectionsPage } from './IttoCollectionsPage'