import { useEffect, useMemo, useState } from 'react' import type { ReactNode } from 'react' import { Alert, Button, Card, Empty, Grid, Input, Modal, Select, 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 const { TextArea } = Input type BuiltinDataSourceID = 'all' | 'manual' | 'requirements' | 'architecture' type DataSourceID = BuiltinDataSourceID | `custom:${number}` type DataSourceKind = 'manual' | 'link' | 'rss' type ExploreArticle = InboxItem & { project: Project sourceID: BuiltinDataSourceID } type DataSourceConfig = { id: DataSourceID name: string icon: ReactNode color: string kind: DataSourceKind url: string description: string } type DataSourceCard = DataSourceConfig & { count: number } type SourceDraft = { name: string kind: DataSourceKind url: string description: string } const INITIAL_SOURCES: DataSourceConfig[] = [ { id: 'all', name: '全部', icon: , color: 'blue', kind: 'manual', url: '', description: '全部探索内容' }, { id: 'manual', name: '手动收集', icon: , color: 'green', kind: 'manual', url: '', description: '手动收集的文章与线索' }, { id: 'requirements', name: '需求文档', icon: , color: 'orange', kind: 'link', url: '', description: '产品需求与业务文档' }, { id: 'architecture', name: '架构讨论', icon: , color: 'purple', kind: 'link', url: '', description: '架构方案与系统设计讨论' }, ] const EMPTY_SOURCE_DRAFT: SourceDraft = { name: '', kind: 'link', url: '', description: '', } 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 [sourceConfigs, setSourceConfigs] = useState(INITIAL_SOURCES) const [activeSourceID, setActiveSourceID] = useState('all') const [sourceModalMode, setSourceModalMode] = useState<'add' | 'edit' | null>(null) const [editingSourceID, setEditingSourceID] = useState(null) const [sourceDraft, setSourceDraft] = useState(EMPTY_SOURCE_DRAFT) const [sourceError, setSourceError] = useState('') const filteredArticles = useMemo( () => (activeSourceID === 'all' ? articles : articles.filter((article) => article.sourceID === activeSourceID)), [activeSourceID, articles], ) const sources = useMemo(() => dataSources(articles, sourceConfigs), [articles, sourceConfigs]) 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 = sourceByID(sources, activeSourceID) const selectedSource = selected ? sourceByID(sources, selected.sourceID) : activeSource function openAddSource() { setEditingSourceID(null) setSourceDraft(EMPTY_SOURCE_DRAFT) setSourceError('') setSourceModalMode('add') } function openEditSource(source: DataSourceCard) { setEditingSourceID(source.id) setSourceDraft({ name: source.name, kind: source.kind, url: source.url, description: source.description, }) setSourceError('') setSourceModalMode('edit') } function saveSource() { const name = sourceDraft.name.trim() if (!name) { setSourceError('请输入数据源名称') return } const nextDraft = { ...sourceDraft, name, url: sourceDraft.url.trim(), description: sourceDraft.description.trim() } if (sourceModalMode === 'edit' && editingSourceID) { setSourceConfigs((current) => current.map((source) => source.id === editingSourceID ? { ...source, ...nextDraft } : source)) } else { setSourceConfigs((current) => [ ...current, { id: `custom:${Date.now()}`, ...nextDraft, icon: sourceIcon(nextDraft.kind), color: sourceColor(nextDraft.kind), }, ]) } setSourceModalMode(null) } function deleteSource(source: DataSourceCard) { Modal.confirm({ title: `删除“${source.name}”数据源?`, content: '删除后,该数据源将从探索页移除。', okButtonProps: { status: 'danger' }, onOk: () => { setSourceConfigs((current) => current.filter((item) => item.id !== source.id)) if (activeSourceID === source.id) setActiveSourceID('all') }, }) } return (
探索 多个外部数据源的集中阅读页,采集文章并沉淀到项目。
{sources.map((source) => ( setActiveSourceID(source.id)} > {source.icon} {source.name} {source.count} 条 {source.id !== 'all' ? ( event.stopPropagation()}>
{filteredArticles.map((article) => { const articleSource = sourceByID(sources, article.sourceID) return ( ) })}
{selected.title}
{sourceInitial(selectedSource.name)} {selectedSource.name} {selected.project.name} {selected.time} {selected.summary || '暂无正文'}
推荐:将外部文章中的项目机会、风险提示和资料线索归档到对应项目,形成可追踪的计划和知识资产。
) : ( )} setSourceModalMode(null)} onOk={saveSource} > {sourceError && }