import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { ArrowLeft, ExternalLink, FolderGit2 } 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 { joinSEOKeywords, usePageSEO } from '../hooks/usePageSEO'; import { getCachedSiteBranding } from '../hooks/useSiteBranding'; import { InFlowSiteFooter } from '../components/SiteFooter'; function formatRemoteTime(raw?: string | null): string { if (!raw) return ''; const d = new Date(raw); if (Number.isNaN(d.getTime())) return ''; return d.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); } export default function ProjectsPage() { const nav = useNavigate(); const [list, setList] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(0); const [loading, setLoading] = useState(true); usePageSEO({ title: '项目', description: '公开项目列表', keywords: joinSEOKeywords('项目', getCachedSiteBranding().keywords), canonicalPath: '/projects', }); useEffect(() => { setLoading(true); api.projects({ page, limit: 30 }) .then(d => { setList(Array.isArray(d.projects) ? d.projects : []); setTotal(d.total ?? 0); setTotalPages(d.total_pages ?? 0); }) .catch(e => notify.error(e instanceof Error ? e.message : '加载失败')) .finally(() => setLoading(false)); }, [page]); return (

开源码桶

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

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

暂无同步到的公开项目

管理员可在「系统设置 → Gitea 同步」配置后执行同步

) : ( <>
{list.map(p => (

{p.full_name || p.name}

{p.description ? (

{p.description}

) : null}
{p.owner_login} {p.updated_at_remote && ( 更新于 {formatRemoteTime(p.updated_at_remote)} )}
在 Gitea 打开
))}
{totalPages > 1 && (
{page} / {totalPages}
)} )}
); }