first-update
This commit is contained in:
264
easy-dataset-main/components/dataset-square/DatasetSearchBar.js
Normal file
264
easy-dataset-main/components/dataset-square/DatasetSearchBar.js
Normal file
@@ -0,0 +1,264 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
TextField,
|
||||
InputAdornment,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
Paper,
|
||||
Typography,
|
||||
ClickAwayListener,
|
||||
Fade,
|
||||
Avatar,
|
||||
useTheme,
|
||||
alpha
|
||||
} from '@mui/material';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import LaunchIcon from '@mui/icons-material/Launch';
|
||||
import TravelExploreIcon from '@mui/icons-material/TravelExplore';
|
||||
import sites from '@/constant/sites.json';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function DatasetSearchBar() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||
const [recentSearches, setRecentSearches] = useState([]);
|
||||
const searchRef = useRef(null);
|
||||
const suggestionsRef = useRef(null);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 从 localStorage 加载最近搜索
|
||||
useEffect(() => {
|
||||
const savedSearches = localStorage.getItem('recentDatasetSearches');
|
||||
if (savedSearches) {
|
||||
try {
|
||||
const searches = JSON.parse(savedSearches);
|
||||
setRecentSearches(searches);
|
||||
} catch (e) {
|
||||
console.error('解析最近搜索失败', e);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 处理搜索输入变化
|
||||
const handleSearchChange = event => {
|
||||
setSearchQuery(event.target.value);
|
||||
if (event.target.value) {
|
||||
setShowSuggestions(true);
|
||||
} else {
|
||||
setShowSuggestions(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理回车搜索
|
||||
const handleSearchSubmit = event => {
|
||||
if (event.key === 'Enter' && searchQuery.trim()) {
|
||||
// 默认使用第一个搜索引擎
|
||||
if (sites.length > 0) {
|
||||
handleSuggestionClick(sites[0]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 保存最近搜索
|
||||
const saveRecentSearch = query => {
|
||||
if (!query.trim()) return;
|
||||
|
||||
// 添加到最近搜索并去重
|
||||
const updatedSearches = [query, ...recentSearches.filter(s => s !== query)].slice(0, 5);
|
||||
setRecentSearches(updatedSearches);
|
||||
|
||||
// 保存到 localStorage
|
||||
try {
|
||||
localStorage.setItem('recentDatasetSearches', JSON.stringify(updatedSearches));
|
||||
} catch (e) {
|
||||
console.error('保存最近搜索失败', e);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理点击搜索建议
|
||||
const handleSuggestionClick = site => {
|
||||
if (searchQuery.trim()) {
|
||||
// 根据不同网站处理搜索参数
|
||||
let searchUrl = site.link;
|
||||
|
||||
// 如果链接中不包含问号,则添加搜索参数
|
||||
if (site.link.includes('huggingface.co')) {
|
||||
searchUrl = `${site.link}?sort=trending&search=${encodeURIComponent(searchQuery)}`;
|
||||
} else if (site.link.includes('kaggle.com')) {
|
||||
searchUrl = `${site.link}?search=${encodeURIComponent(searchQuery)}`;
|
||||
} else if (site.link.includes('datasetsearch.research.google.com')) {
|
||||
searchUrl = `${site.link}/search?query=${encodeURIComponent(searchQuery)}&src=0`;
|
||||
} else if (site.link.includes('paperswithcode.com')) {
|
||||
searchUrl = `${site.link}?q=${encodeURIComponent(searchQuery)}`;
|
||||
} else if (site.link.includes('modelscope.cn')) {
|
||||
searchUrl = `${site.link}?query=${encodeURIComponent(searchQuery)}`;
|
||||
} else if (site.link.includes('opendatalab.com')) {
|
||||
searchUrl = `${site.link}?keywords=${encodeURIComponent(searchQuery)}`;
|
||||
} else if (site.link.includes('tianchi.aliyun.com')) {
|
||||
searchUrl = `${site.link}?q=${encodeURIComponent(searchQuery)}`;
|
||||
} else {
|
||||
// 默认处理方式,在URL后添加搜索参数
|
||||
searchUrl = `${site.link}${site.link.includes('?') ? '&' : '?'}search=${encodeURIComponent(searchQuery)}`;
|
||||
}
|
||||
|
||||
// 保存最近搜索
|
||||
saveRecentSearch(searchQuery);
|
||||
|
||||
window.open(searchUrl, '_blank');
|
||||
}
|
||||
setShowSuggestions(false);
|
||||
};
|
||||
|
||||
// 处理点击外部关闭建议
|
||||
const handleClickAway = event => {
|
||||
// 确保点击的不是建议框本身
|
||||
if (suggestionsRef.current && !suggestionsRef.current.contains(event.target)) {
|
||||
setShowSuggestions(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ClickAwayListener onClickAway={handleClickAway}>
|
||||
<Box sx={{ position: 'relative', width: '100%', zIndex: 1300 }} ref={searchRef}>
|
||||
<TextField
|
||||
fullWidth
|
||||
placeholder={t('datasetSquare.searchPlaceholder')}
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
onKeyDown={handleSearchSubmit}
|
||||
onClick={() => searchQuery && setShowSuggestions(true)}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon color="primary" />
|
||||
</InputAdornment>
|
||||
),
|
||||
sx: {
|
||||
height: 56,
|
||||
borderRadius: 3,
|
||||
backgroundColor:
|
||||
theme.palette.mode === 'dark'
|
||||
? alpha(theme.palette.background.default, 0.6)
|
||||
: alpha(theme.palette.background.default, 0.8),
|
||||
backdropFilter: 'blur(8px)',
|
||||
px: 2,
|
||||
transition: 'all 0.3s ease',
|
||||
boxShadow: `0 0 0 1px ${alpha(theme.palette.primary.main, 0.15)}`,
|
||||
'&.MuiOutlinedInput-root': {
|
||||
'& fieldset': {
|
||||
borderColor: 'transparent'
|
||||
},
|
||||
'&:hover fieldset': {
|
||||
borderColor: 'transparent'
|
||||
},
|
||||
'&.Mui-focused': {
|
||||
boxShadow: `0 0 0 2px ${alpha(theme.palette.primary.main, 0.3)}`,
|
||||
backgroundColor:
|
||||
theme.palette.mode === 'dark'
|
||||
? alpha(theme.palette.background.paper, 0.8)
|
||||
: alpha(theme.palette.common.white, 0.95)
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: 'transparent'
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
mb: 1,
|
||||
'& .MuiInputBase-input': {
|
||||
fontSize: '1rem',
|
||||
fontWeight: 500,
|
||||
color: theme.palette.text.primary
|
||||
},
|
||||
'& .MuiInputBase-input::placeholder': {
|
||||
color: alpha(theme.palette.text.primary, 0.6),
|
||||
opacity: 0.7
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 搜索建议下拉框 - 使用绝对定位确保不被裁剪 */}
|
||||
{showSuggestions && searchQuery && (
|
||||
<Box
|
||||
ref={suggestionsRef}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
zIndex: 9999,
|
||||
top: 'calc(100% + 8px)',
|
||||
left: 0,
|
||||
boxShadow: '0 4px 20px rgba(0,0,0,0.1)',
|
||||
pointerEvents: 'auto' // 确保可以点击
|
||||
}}
|
||||
>
|
||||
<Fade in={showSuggestions}>
|
||||
<Paper
|
||||
elevation={6}
|
||||
sx={{
|
||||
width: '100%',
|
||||
maxHeight: 350,
|
||||
overflow: 'auto',
|
||||
borderRadius: 2,
|
||||
border: `1px solid ${alpha(theme.palette.primary.main, 0.1)}`,
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
{sites.slice(0, 5).map((site, index) => (
|
||||
<ListItem key={index} disablePadding>
|
||||
<ListItemButton
|
||||
onClick={() => handleSuggestionClick(site)}
|
||||
sx={{
|
||||
py: 1.5,
|
||||
'&:hover': {
|
||||
bgcolor: alpha(theme.palette.primary.main, 0.05)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Avatar
|
||||
sx={{
|
||||
width: 28,
|
||||
height: 28,
|
||||
mr: 1.5,
|
||||
bgcolor: alpha(theme.palette.primary.main, 0.1),
|
||||
color: theme.palette.primary.main
|
||||
}}
|
||||
>
|
||||
<TravelExploreIcon fontSize="small" />
|
||||
</Avatar>
|
||||
<Typography>
|
||||
{t('datasetSquare.searchVia')} <strong>{site.name}</strong> Search
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mr: 1 }}>
|
||||
"{searchQuery}"
|
||||
</Typography>
|
||||
<LaunchIcon fontSize="small" color="action" />
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Paper>
|
||||
</Fade>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</ClickAwayListener>
|
||||
);
|
||||
}
|
||||
197
easy-dataset-main/components/dataset-square/DatasetSiteCard.js
Normal file
197
easy-dataset-main/components/dataset-square/DatasetSiteCard.js
Normal file
@@ -0,0 +1,197 @@
|
||||
'use client';
|
||||
|
||||
import { Card, CardActionArea, CardContent, CardMedia, Typography, Box, Chip, useTheme, alpha } from '@mui/material';
|
||||
import LaunchIcon from '@mui/icons-material/Launch';
|
||||
import StorageIcon from '@mui/icons-material/Storage';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function DatasetSiteCard({ site }) {
|
||||
const { name, link, description, image, labels } = site;
|
||||
const theme = useTheme();
|
||||
|
||||
// 处理图片路径,如果没有图片则使用默认图片
|
||||
const imageUrl = image || `/imgs/default-dataset.png`;
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 处理卡片点击
|
||||
const handleCardClick = () => {
|
||||
window.open(link, '_blank');
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
sx={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
transition: 'all 0.3s ease',
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden',
|
||||
boxShadow: theme.palette.mode === 'dark' ? '0 4px 20px rgba(0,0,0,0.3)' : '0 4px 20px rgba(0,0,0,0.1)',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-6px)',
|
||||
boxShadow: theme.palette.mode === 'dark' ? '0 8px 30px rgba(0,0,0,0.4)' : '0 8px 30px rgba(0,0,0,0.15)',
|
||||
'& .MuiCardMedia-root': {
|
||||
transform: 'scale(1.05)'
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CardActionArea
|
||||
onClick={handleCardClick}
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
height: '100%',
|
||||
'&:hover': {
|
||||
'& .card-content': {
|
||||
background:
|
||||
theme.palette.mode === 'dark'
|
||||
? alpha(theme.palette.primary.dark, 0.1)
|
||||
: alpha(theme.palette.primary.light, 0.1)
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* 网站截图 */}
|
||||
<Box sx={{ position: 'relative', width: '100%', height: 160, overflow: 'hidden' }}>
|
||||
<CardMedia
|
||||
component="img"
|
||||
height="160"
|
||||
image={imageUrl}
|
||||
alt={name}
|
||||
sx={{
|
||||
objectFit: 'cover',
|
||||
bgcolor: theme.palette.mode === 'dark' ? 'grey.900' : 'grey.100',
|
||||
transition: 'transform 0.5s ease'
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background: `linear-gradient(to bottom, transparent 70%, ${theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.8)' : 'rgba(0,0,0,0.5)'})`,
|
||||
zIndex: 1
|
||||
}}
|
||||
/>
|
||||
<Chip
|
||||
icon={<StorageIcon fontSize="small" />}
|
||||
label={t('datasetSquare.dataset')}
|
||||
size="small"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10,
|
||||
zIndex: 2,
|
||||
backgroundColor: alpha(theme.palette.background.paper, 0.8),
|
||||
backdropFilter: 'blur(4px)',
|
||||
border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`,
|
||||
'& .MuiChip-icon': {
|
||||
color: theme.palette.primary.main
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* 网站信息 */}
|
||||
<CardContent
|
||||
className="card-content"
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
transition: 'background 0.3s ease',
|
||||
p: 2.5
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', mb: 1.5 }}>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component="div"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
lineHeight: 1.3,
|
||||
mb: 0.5,
|
||||
pr: 2, // 留出空间给图标
|
||||
color: theme.palette.mode === 'dark' ? theme.palette.primary.light : theme.palette.primary.dark
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</Typography>
|
||||
<LaunchIcon
|
||||
fontSize="small"
|
||||
sx={{
|
||||
color: theme.palette.mode === 'dark' ? theme.palette.primary.light : theme.palette.primary.main,
|
||||
opacity: 0.8,
|
||||
mt: 0.5
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 3,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: 1.6,
|
||||
mb: 1
|
||||
}}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mt: 'auto', pt: 1.5 }}>
|
||||
{/* 标签显示 */}
|
||||
{labels && labels.length > 0 && (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mb: 1.5 }}>
|
||||
{labels.map((label, index) => (
|
||||
<Chip
|
||||
key={index}
|
||||
label={label}
|
||||
size="small"
|
||||
sx={{
|
||||
borderRadius: 1,
|
||||
height: 20,
|
||||
fontSize: '0.65rem',
|
||||
backgroundColor: alpha(theme.palette.primary.main, 0.1),
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(theme.palette.primary.main, 0.2)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Chip
|
||||
label={t('datasetSquare.viewDataset')}
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
borderRadius: 1,
|
||||
height: 24,
|
||||
fontSize: '0.75rem',
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(theme.palette.primary.main, 0.1)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
211
easy-dataset-main/components/dataset-square/DatasetSiteList.js
Normal file
211
easy-dataset-main/components/dataset-square/DatasetSiteList.js
Normal file
@@ -0,0 +1,211 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Grid, Box, Typography, Skeleton, Divider, Tabs, Tab, Fade, Chip, useTheme, alpha, Paper } from '@mui/material';
|
||||
import StorageIcon from '@mui/icons-material/Storage';
|
||||
import CategoryIcon from '@mui/icons-material/Category';
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import { DatasetSiteCard } from './DatasetSiteCard';
|
||||
import sites from '@/constant/sites.json';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function DatasetSiteList() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 定义类别
|
||||
const CATEGORIES = {
|
||||
ALL: t('datasetSquare.categories.all'),
|
||||
POPULAR: t('datasetSquare.categories.popular'),
|
||||
CHINESE: t('datasetSquare.categories.chinese'),
|
||||
ENGLISH: t('datasetSquare.categories.english'),
|
||||
RESEARCH: t('datasetSquare.categories.research'),
|
||||
MULTIMODAL: t('datasetSquare.categories.multimodal')
|
||||
};
|
||||
const [activeCategory, setActiveCategory] = useState(CATEGORIES.ALL);
|
||||
|
||||
// 模拟加载效果
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 800);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// 处理类别切换
|
||||
const handleCategoryChange = (event, newValue) => {
|
||||
setActiveCategory(newValue);
|
||||
};
|
||||
|
||||
// 根据当前选中的类别过滤网站
|
||||
const getFilteredSites = () => {
|
||||
if (activeCategory === CATEGORIES.ALL) {
|
||||
return sites;
|
||||
} else if (activeCategory === CATEGORIES.POPULAR) {
|
||||
return sites.filter(site => site.labels && site.labels.includes(t('datasetSquare.categories.popular')));
|
||||
} else if (activeCategory === CATEGORIES.CHINESE) {
|
||||
return sites.filter(site => site.labels && site.labels.includes(t('datasetSquare.categories.chinese')));
|
||||
} else if (activeCategory === CATEGORIES.ENGLISH) {
|
||||
return sites.filter(site => site.labels && site.labels.includes(t('datasetSquare.categories.english')));
|
||||
} else if (activeCategory === CATEGORIES.RESEARCH) {
|
||||
return sites.filter(site => site.labels && site.labels.includes(t('datasetSquare.categories.research')));
|
||||
} else if (activeCategory === CATEGORIES.MULTIMODAL) {
|
||||
return sites.filter(site => site.labels && site.labels.includes(t('datasetSquare.categories.multimodal')));
|
||||
}
|
||||
return sites;
|
||||
};
|
||||
|
||||
const filteredSites = getFilteredSites();
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 4 }}>
|
||||
{/* 类别选择器 */}
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<CategoryIcon sx={{ mr: 1, color: 'primary.main' }} />
|
||||
<Typography variant="h5" component="h2" fontWeight={600}>
|
||||
{t('datasetSquare.categoryTitle')}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderRadius: 2,
|
||||
p: 0.5,
|
||||
bgcolor:
|
||||
theme.palette.mode === 'dark'
|
||||
? alpha(theme.palette.primary.dark, 0.1)
|
||||
: alpha(theme.palette.primary.light, 0.1),
|
||||
border: `1px solid ${alpha(theme.palette.primary.main, 0.1)}`,
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
value={activeCategory}
|
||||
onChange={handleCategoryChange}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
'& .MuiTabs-indicator': {
|
||||
display: 'none'
|
||||
},
|
||||
'& .MuiTab-root': {
|
||||
minHeight: 40,
|
||||
borderRadius: 2,
|
||||
mx: 0.5,
|
||||
transition: 'all 0.2s',
|
||||
'&.Mui-selected': {
|
||||
bgcolor:
|
||||
theme.palette.mode === 'dark' ? alpha(theme.palette.primary.main, 0.2) : theme.palette.primary.main,
|
||||
color: theme.palette.mode === 'dark' ? theme.palette.primary.light : 'white',
|
||||
fontWeight: 600
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tab
|
||||
value={CATEGORIES.ALL}
|
||||
label={CATEGORIES.ALL}
|
||||
icon={<StorageIcon fontSize="small" />}
|
||||
iconPosition="start"
|
||||
/>
|
||||
<Tab
|
||||
value={CATEGORIES.POPULAR}
|
||||
label={CATEGORIES.POPULAR}
|
||||
icon={<StarIcon fontSize="small" />}
|
||||
iconPosition="start"
|
||||
/>
|
||||
<Tab value={CATEGORIES.CHINESE} label={CATEGORIES.CHINESE} />
|
||||
<Tab value={CATEGORIES.ENGLISH} label={CATEGORIES.ENGLISH} />
|
||||
<Tab value={CATEGORIES.RESEARCH} label={CATEGORIES.RESEARCH} />
|
||||
<Tab value={CATEGORIES.MULTIMODAL} label={CATEGORIES.MULTIMODAL} />
|
||||
</Tabs>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
{/* 数据集网站列表 */}
|
||||
<Box sx={{ position: 'relative', minHeight: 300 }}>
|
||||
{loading ? (
|
||||
// 加载骨架屏
|
||||
<Grid container spacing={3}>
|
||||
{Array.from(new Array(8)).map((_, index) => (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} key={index}>
|
||||
<Box sx={{ width: '100%', height: '100%' }}>
|
||||
<Skeleton variant="rectangular" height={160} sx={{ borderRadius: 2 }} />
|
||||
<Box sx={{ pt: 1.5, px: 0.5 }}>
|
||||
<Skeleton width="80%" height={28} />
|
||||
<Skeleton width="100%" />
|
||||
<Skeleton width="100%" />
|
||||
<Skeleton width="60%" />
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
) : (
|
||||
<Fade in={!loading} timeout={500}>
|
||||
<Box>
|
||||
{/* 结果数量提示 */}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||
<Typography variant="subtitle1" color="text.secondary">
|
||||
{t('datasetSquare.foundResources', { count: filteredSites.length })}{' '}
|
||||
<Chip
|
||||
label={filteredSites.length}
|
||||
size="small"
|
||||
color="primary"
|
||||
sx={{ mx: 0.5, height: 20, fontSize: '0.75rem' }}
|
||||
/>
|
||||
</Typography>
|
||||
|
||||
{activeCategory !== CATEGORIES.ALL && (
|
||||
<Chip
|
||||
label={t('datasetSquare.currentFilter', { category: activeCategory })}
|
||||
size="small"
|
||||
onDelete={() => setActiveCategory(CATEGORIES.ALL)}
|
||||
sx={{ borderRadius: 1.5 }}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{filteredSites.length > 0 ? (
|
||||
<Grid container spacing={3}>
|
||||
{filteredSites.map((site, index) => (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} key={index}>
|
||||
<DatasetSiteCard site={site} />
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
py: 10,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
bgcolor:
|
||||
theme.palette.mode === 'dark'
|
||||
? alpha(theme.palette.background.paper, 0.2)
|
||||
: alpha(theme.palette.grey[100], 0.5),
|
||||
borderRadius: 2
|
||||
}}
|
||||
>
|
||||
<StorageIcon sx={{ fontSize: 60, color: 'text.disabled', mb: 2 }} />
|
||||
<Typography variant="h6" color="text.secondary" gutterBottom>
|
||||
{t('datasetSquare.noDatasets')}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.disabled">
|
||||
{t('datasetSquare.tryOtherCategories')}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Fade>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user