import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { ArrowLeft, FolderGit2, Search } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Spinner } from '@/components/ui/spinner'; import { notify } from '@/lib/notify'; import { api } from '../api/client'; import type { GiteaProject } from '../api/types'; import ProjectListItem from '../components/ProjectListItem'; import { InFlowSiteFooter } from '../components/SiteFooter'; import { joinSEOKeywords, usePageSEO } from '../hooks/usePageSEO'; import { getCachedSiteBranding } from '../hooks/useSiteBranding'; import { useSessionResource } from '../hooks/useSessionResource'; type ProjectsSnap = { list: GiteaProject[]; total: number; totalPages: number }; export default function ProjectsPage() { const nav = useNavigate(); const [page, setPage] = useState(1); const [queryInput, setQueryInput] = useState(''); const [query, setQuery] = useState(''); usePageSEO({ title: '开源码桶', description: '论坛会员在 Gitea 上的公开仓库', keywords: joinSEOKeywords('开源码桶', '项目', getCachedSiteBranding().keywords), canonicalPath: '/projects', }); useEffect(() => { const t = window.setTimeout(() => { const next = queryInput.trim(); setQuery(prev => { if (prev === next) return prev; setPage(1); return next; }); }, 300); return () => window.clearTimeout(t); }, [queryInput]); const { data, loading } = useSessionResource( `projects:${page}:${query}`, () => api.projects({ page, limit: 30, q: query || undefined }).then(d => ({ list: Array.isArray(d.projects) ? d.projects : [], total: d.total ?? 0, totalPages: d.total_pages ?? 0, })), { onError: (e) => notify.error(e instanceof Error ? e.message : '加载失败'), }, ); const list = data?.list ?? []; const total = data?.total ?? 0; const totalPages = data?.totalPages ?? 0; return (

开源码桶

论坛会员在 Gitea 上的公开仓库 {total > 0 ? ` · 共 ${total} 个` : ''}

{loading ? (
) : list.length === 0 ? (

{query ? '没有匹配的仓库' : '暂无同步到的公开项目'}

{query ? '试试其他关键词,或清空搜索' : '需管理员在「系统设置 → Gitea 同步」开启并执行同步后才会出现仓库'}

) : ( <>
{list.map(p => ( ))}
{totalPages > 1 && (
{page} / {totalPages}
)} )}
); }