// dashboard/src/features/search/SearchPanel.tsx import React from 'react'; import { Search, Loader, CheckCircle, Copy, Download, ChevronLeft, ChevronRight, SlidersHorizontal, AlertTriangle, Lightbulb, } from 'lucide-react'; import type { StandardPaper } from '../types'; import { CustomSelect } from '../components/CustomSelect'; import { PaperCard } from '../components/PaperCard'; import type { TabId } from '../components/layout/Sidebar'; interface SearchPanelProps { searchQuery: string; setSearchQuery: (query: string) => void; searchSource: 'all' | 'ads' | 'arxiv'; setSearchSource: (src: 'all' | 'ads' | 'arxiv') => void; searchRows: number; searchStart: number; searchSort: string; handlePageChange: (newStart: number) => void; handleSortChange: (newSort: string) => void; handleRowsChange: (newRows: number) => void; searching: boolean; handleSearch: (e: React.FormEvent) => void; searchResults: StandardPaper[]; exportingList: string[]; toggleExportItem: (bibcode: string) => void; handleExportBibtex: () => void; exporting: boolean; bibtexContent: string | null; downloadingBibcodes: Record; handleDownload: (bibcode: string, force?: boolean) => void; selectedPaper: StandardPaper | null; setSelectedPaper: (paper: StandardPaper | null) => void; openReader: (paper: StandardPaper) => void; setActiveTab: (tab: TabId) => void; loadCitations: (bibcode: string, reset?: boolean) => void; showAlert: (msg: string, title?: string) => void; onShowDetail: (paper: StandardPaper) => void; } export function SearchPanel({ searchQuery, setSearchQuery, searchSource, setSearchSource, searchRows, searchStart, searchSort, handlePageChange, handleSortChange, handleRowsChange, searching, handleSearch, searchResults, exportingList, toggleExportItem, handleExportBibtex, exporting, bibtexContent, downloadingBibcodes, handleDownload, selectedPaper, setSelectedPaper, openReader, setActiveTab, loadCitations, showAlert, onShowDetail, }: SearchPanelProps) { const currentPage = Math.floor(searchStart / searchRows) + 1; const hasPreviousPage = searchStart > 0; const hasNextPage = searchResults.length >= searchRows; const [showBuilder, setShowBuilder] = React.useState(false); const [rules, setRules] = React.useState< Array<{ field: string; op: string; val: string }> >([{ field: 'all', op: 'AND', val: '' }]); const updateQueryFromRules = (currentRules: typeof rules) => { const qParts: string[] = []; currentRules.forEach((rule, idx) => { if (!rule.val.trim()) return; let valStr = rule.val.trim(); if ( valStr.includes(' ') && !valStr.startsWith('"') && !valStr.startsWith('(') ) { valStr = `"${valStr}"`; } const fieldPart = rule.field !== 'all' ? `${rule.field}:${valStr}` : valStr; if (idx === 0) { qParts.push(fieldPart); } else { qParts.push(`${rule.op} ${fieldPart}`); } }); setSearchQuery(qParts.join(' ')); }; const handleAddRule = () => { setRules((prev) => [...prev, { field: 'all', op: 'AND', val: '' }]); }; const handleRemoveRule = (idx: number) => { const next = rules.filter((_, i) => i !== idx); setRules(next); updateQueryFromRules(next); }; const handleRuleChange = ( idx: number, key: 'field' | 'op' | 'val', value: string ) => { const next = rules.map((r, i) => (i === idx ? { ...r, [key]: value } : r)); setRules(next); updateQueryFromRules(next); }; return (
{/* 标题 */}

统一文献检索平台

快速检索 NASA ADS 和 arXiv 预印本学术文献,支持一键下载、格式转换与星系引用分析。

{/* 检索控制面板 */}
setSearchQuery(e.target.value)} placeholder="检索天文学文献 (支持关键字、作者、发表年份,如 'hot subdwarf year:2020-2023')" className="w-full pl-12 pr-4 py-3 rounded-md bg-slate-50 border border-slate-300 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-slate-500 focus:bg-white transition-all text-sm font-medium" />
{/* 条件构造器 */} {showBuilder && (
高级检索式条件构造器
{rules.map((rule, idx) => (
{idx > 0 ? ( handleRuleChange(idx, 'op', String(val)) } className="w-full sm:w-24" options={[ { value: 'AND', label: '并且 (AND)' }, { value: 'OR', label: '或者 (OR)' }, { value: 'NOT', label: '排除 (NOT)' }, ]} /> ) : (
条件过滤
)} handleRuleChange(idx, 'field', String(val)) } className="w-full sm:w-32" options={[ { value: 'all', label: '任意字段' }, { value: 'title', label: '标题名称' }, { value: 'author', label: '作者名称' }, { value: 'abs', label: '摘要内容' }, { value: 'year', label: '年份范围' }, ]} /> handleRuleChange(idx, 'val', e.target.value) } placeholder={ rule.field === 'year' ? '例如: 2020-2023 或 2022' : rule.field === 'author' ? '例如: Althaus' : '请输入检索词...' } className="w-full sm:flex-1 px-3 py-1.5 rounded-md bg-white border border-slate-300 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-slate-500 text-xs font-medium" /> {rules.length > 1 && ( )}
))}
)}
检索语法小提示: 作者检索:{' '} author:"Althaus" 标题检索:{' '} title:"hot subdwarf" 年份范围:{' '} year:2020-2023
{/* 数据源平台 */}
{[ { id: 'all', label: '全部数据源' }, { id: 'ads', label: 'NASA ADS' }, { id: 'arxiv', label: 'arXiv 预印本' }, ].map((src) => ( ))}
{/* 排序及每页条数 */}
排序: handleSortChange(val)} options={[ { value: 'relevance', label: '相关度' }, { value: 'date_desc', label: '发表日期 (由新到旧)' }, { value: 'date_asc', label: '发表日期 (由旧到新)' }, { value: 'citations_desc', label: '被引频次 (从高到低)' }, ]} />
每页显示: handleRowsChange(Number(val))} options={[ { value: 10, label: '10 条' }, { value: 15, label: '15 条' }, { value: 30, label: '30 条' }, { value: 50, label: '50 条' }, ]} />
{exportingList.length > 0 && ( )}
{/* BibTeX 导出显示 */} {bibtexContent && (

BibTeX 导出成功

            {bibtexContent}
          
)} {/* 检索列表 */}
{searching && (
正在与学术服务器通讯,检索文献数据中...
)}
{searchResults.map((paper) => { const isDownloading = downloadingBibcodes[paper.bibcode] || false; const isSelected = selectedPaper?.bibcode === paper.bibcode; return ( openReader(paper)} variant="list" headerActions={ <> {paper.is_downloaded ? ( 已下载 ) : (
{paper.pdf_error === 'no_resource' && paper.html_error === 'no_resource' ? ( {' '} 无资源 ) : ( (paper.pdf_error || paper.html_error) && ( {' '} 下载失败 ) )}
)} } abstract={

{paper.abstract_text || '暂无文献摘要数据。'}

} footerLeft={ <> } footerRight={ <> {paper.doi && DOI: {paper.doi}} Bibcode:{' '} {paper.bibcode} {paper.citation_count > 0 && ( 被引次数:{' '} {paper.citation_count} )} } /> ); })}
{/* 分页控制栏 */} {searchResults.length > 0 && (
第 {currentPage} 页 (当前显示 {searchStart + 1} -{' '} {searchStart + searchResults.length} 条)
)}
); }