b6f15f82d8
- 新增 QuantaNote 完整项目数据(特性描述、截图、Logo) - 为所有项目添加 logo 和 websiteUrl 字段支持 - 移除 stars/forks 相关展示与排序逻辑 - ScreenshotCarousel 增加左右切换箭头和 Lightbox 全屏预览(支持键盘导航) - 更新项目创建文档,补充 logo 和 installGuide 配置说明
105 lines
3.2 KiB
TypeScript
105 lines
3.2 KiB
TypeScript
import { useSearchParams } from 'react-router-dom';
|
|
import { useI18n } from '../hooks/useI18n';
|
|
import { useProjectFilters } from '../hooks/useProjectFilters';
|
|
import { siteData } from '../data/siteData';
|
|
import ProjectCard from '../components/ProjectCard';
|
|
import SelectControl from '../components/SelectControl';
|
|
|
|
export default function ProjectsPage() {
|
|
const { t, bi } = useI18n();
|
|
const [searchParams] = useSearchParams();
|
|
const catParam = searchParams.get('cat');
|
|
|
|
const {
|
|
search,
|
|
setSearch,
|
|
tech,
|
|
setTech,
|
|
platform,
|
|
setPlatform,
|
|
status,
|
|
setStatus,
|
|
sort,
|
|
setSort,
|
|
allTech,
|
|
allPlatforms,
|
|
allStatuses,
|
|
filteredProjects,
|
|
} = useProjectFilters();
|
|
|
|
const displayedProjects = catParam
|
|
? filteredProjects.filter((p) => p.type.includes(catParam))
|
|
: filteredProjects;
|
|
|
|
const techOptions = [
|
|
{ value: '', label: t('projects.filter.all') },
|
|
...allTech.map((item) => ({ value: item, label: item })),
|
|
];
|
|
const platformOptions = [
|
|
{ value: '', label: t('projects.filter.all') },
|
|
...allPlatforms.map((item) => ({ value: item, label: item })),
|
|
];
|
|
const statusOptions = [
|
|
{ value: '', label: t('projects.filter.all') },
|
|
...allStatuses.map((item) => ({ value: item, label: bi(siteData.statuses[item]?.label) })),
|
|
];
|
|
const sortOptions = [
|
|
{ value: 'updated', label: t('projects.sort.updated') },
|
|
{ value: 'name', label: t('projects.sort.name') },
|
|
];
|
|
|
|
return (
|
|
<div className="container fade-in">
|
|
<div className="page-header">
|
|
<h1 className="page-title">{t('projects.title')}</h1>
|
|
<p className="page-subtitle">{t('projects.subtitle')}</p>
|
|
</div>
|
|
|
|
<div className="filter-bar">
|
|
<input
|
|
type="text"
|
|
className="search-input"
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
placeholder={t('projects.search')}
|
|
/>
|
|
|
|
<div className="filter-group">
|
|
<span className="filter-group-label">{t('projects.filter.tech')}</span>
|
|
<SelectControl
|
|
value={tech}
|
|
options={techOptions}
|
|
onChange={setTech}
|
|
searchable={techOptions.length > 8}
|
|
searchPlaceholder={t('projects.filter.tech')}
|
|
emptyLabel={t('projects.noResults')}
|
|
/>
|
|
</div>
|
|
|
|
<div className="filter-group">
|
|
<span className="filter-group-label">{t('projects.filter.platform')}</span>
|
|
<SelectControl value={platform} options={platformOptions} onChange={setPlatform} />
|
|
</div>
|
|
|
|
<div className="filter-group">
|
|
<span className="filter-group-label">{t('projects.filter.status')}</span>
|
|
<SelectControl value={status} options={statusOptions} onChange={setStatus} />
|
|
</div>
|
|
|
|
<div className="filter-group filter-group-sort">
|
|
<span className="filter-group-label">Sort</span>
|
|
<SelectControl value={sort} options={sortOptions} onChange={setSort} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="card-grid">
|
|
{displayedProjects.length === 0 ? (
|
|
<div className="empty-state">{t('projects.noResults')}</div>
|
|
) : (
|
|
displayedProjects.map((p) => <ProjectCard key={p.id} project={p} />)
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|