feat: 去掉 Feed 顶栏统计并强化开源码桶
按帖子列表风格展示码桶,补齐语言/Star/Fork 与论坛作者关联回填,避免未绑定仓库与空列表。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,27 +1,15 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ArrowLeft, ExternalLink, FolderGit2 } from 'lucide-react';
|
||||
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 { 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();
|
||||
@@ -30,24 +18,46 @@ export default function ProjectsPage() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [queryInput, setQueryInput] = useState('');
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
usePageSEO({
|
||||
title: '项目',
|
||||
description: '公开项目列表',
|
||||
keywords: joinSEOKeywords('项目', getCachedSiteBranding().keywords),
|
||||
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]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
api.projects({ page, limit: 30 })
|
||||
api.projects({ page, limit: 30, q: query || undefined })
|
||||
.then(d => {
|
||||
if (cancelled) return;
|
||||
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]);
|
||||
.catch(e => {
|
||||
if (!cancelled) notify.error(e instanceof Error ? e.message : '加载失败');
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [page, query]);
|
||||
|
||||
return (
|
||||
<div className="page-wrap">
|
||||
@@ -62,6 +72,17 @@ export default function ProjectsPage() {
|
||||
论坛会员在 Gitea 上的公开仓库
|
||||
{total > 0 ? ` · 共 ${total} 个` : ''}
|
||||
</p>
|
||||
<label className="projects-search">
|
||||
<Search className="projects-search__icon" size={16} aria-hidden />
|
||||
<input
|
||||
type="search"
|
||||
className="projects-search__input"
|
||||
placeholder="搜索仓库名、描述或所有者…"
|
||||
value={queryInput}
|
||||
onChange={e => setQueryInput(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</label>
|
||||
</header>
|
||||
|
||||
{loading ? (
|
||||
@@ -69,38 +90,18 @@ export default function ProjectsPage() {
|
||||
) : list.length === 0 ? (
|
||||
<div className="empty-state list-page-panel__empty">
|
||||
<FolderGit2 className="empty-state-icon" aria-hidden size={36} strokeWidth={1.5} />
|
||||
<p>暂无同步到的公开项目</p>
|
||||
<p>{query ? '没有匹配的仓库' : '暂无同步到的公开项目'}</p>
|
||||
<p className="page-desc" style={{ marginTop: 8 }}>
|
||||
管理员可在「系统设置 → Gitea 同步」配置后执行同步
|
||||
{query
|
||||
? '试试其他关键词,或清空搜索'
|
||||
: '需管理员在「系统设置 → Gitea 同步」开启并执行同步后才会出现仓库'}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="content-surface projects-list">
|
||||
<div className="content-surface post-list projects-list">
|
||||
{list.map(p => (
|
||||
<article key={p.id} className="project-row">
|
||||
<div className="project-row-body">
|
||||
<h2 className="project-row-title">{p.full_name || p.name}</h2>
|
||||
{p.description ? (
|
||||
<p className="project-row-desc">{p.description}</p>
|
||||
) : null}
|
||||
<div className="project-row-meta">
|
||||
<span>{p.owner_login}</span>
|
||||
{p.updated_at_remote && (
|
||||
<span>更新于 {formatRemoteTime(p.updated_at_remote)}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
className="project-row-link"
|
||||
href={p.html_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
在 Gitea 打开
|
||||
<ExternalLink size={14} aria-hidden />
|
||||
</a>
|
||||
</article>
|
||||
<ProjectListItem key={p.id} project={p} />
|
||||
))}
|
||||
</div>
|
||||
{totalPages > 1 && (
|
||||
|
||||
Reference in New Issue
Block a user