import { useEffect, useMemo, useState } from 'react' import type { ReactNode } from 'react' import { Button, Card, Empty, Grid, Space, Typography } from '@arco-design/web-react' import { IconBook, IconCheckCircle, IconCompass, IconDelete, IconEdit, IconFile, IconLink, IconRefresh, IconStar, IconStorage, } from '@arco-design/web-react/icon' import type { InboxItem, Project, ProjectWorkspace } from './projects/project-types' const { Row, Col } = Grid const { Title, Text, Paragraph } = Typography type DataSourceID = 'all' | 'manual' | 'requirements' | 'architecture' type ExploreArticle = InboxItem & { project: Project sourceID: DataSourceID } type DataSourceCard = { id: DataSourceID name: string count: number icon: ReactNode color: string } const SOURCE_META: Record = { all: { name: '全部', icon: , color: 'blue' }, manual: { name: '手动收集', icon: , color: 'green' }, requirements: { name: '需求文档', icon: , color: 'orange' }, architecture: { name: '架构讨论', icon: , color: 'purple' }, } export function WorkspaceExplorePage({ workspaces, onSelectItem, }: { workspaces: ProjectWorkspace[] onSelectItem: (title: string) => void }) { const articles = useMemo( () => workspaces.flatMap((workspace) => workspace.inbox.map((item) => ({ ...item, project: workspace.project, sourceID: detectSource(item), })), ), [workspaces], ) const [activeSourceID, setActiveSourceID] = useState('all') const filteredArticles = useMemo( () => (activeSourceID === 'all' ? articles : articles.filter((article) => article.sourceID === activeSourceID)), [activeSourceID, articles], ) const sources = useMemo(() => dataSources(articles), [articles]) const [activeArticleID, setActiveArticleID] = useState(filteredArticles[0]?.id ?? null) useEffect(() => { if (filteredArticles.length === 0) { setActiveArticleID(null) return } if (!activeArticleID || !filteredArticles.some((article) => article.id === activeArticleID)) { setActiveArticleID(filteredArticles[0].id) } }, [activeArticleID, filteredArticles]) const selected = filteredArticles.find((article) => article.id === activeArticleID) ?? filteredArticles[0] const activeSource = SOURCE_META[activeSourceID] return (
探索 多个外部数据源的集中阅读页,采集文章并沉淀到项目。
{sources.map((source) => ( setActiveSourceID(source.id)} > {source.icon} {source.name} {source.count} 条 {source.id !== 'all' ? ( event.stopPropagation()}>
{filteredArticles.map((article) => ( ))}
{selected.title}
{sourceInitial(SOURCE_META[selected.sourceID].name)} {SOURCE_META[selected.sourceID].name} {selected.project.name} {selected.time} {selected.summary || '暂无正文'}
推荐:将外部文章中的项目机会、风险提示和资料线索归档到对应项目,形成可追踪的计划和知识资产。
) : ( )} ) } function dataSources(articles: ExploreArticle[]): DataSourceCard[] { const counts = new Map([ ['all', articles.length], ['manual', 0], ['requirements', 0], ['architecture', 0], ]) articles.forEach((article) => counts.set(article.sourceID, (counts.get(article.sourceID) ?? 0) + 1)) return (Object.keys(SOURCE_META) as DataSourceID[]).map((id) => ({ id, ...SOURCE_META[id], count: counts.get(id) ?? 0, })) } function detectSource(article: InboxItem): DataSourceID { const text = `${article.title} ${article.meta} ${article.tag} ${article.summary}` if (/架构|技术方案|系统设计|architecture/i.test(text)) { return 'architecture' } if (/需求|PRD|产品文档|requirement/i.test(text)) { return 'requirements' } return 'manual' } function sourceInitial(name: string) { return name.trim().slice(0, 1) || '源' }