Files
TERES_web_frontend/src/pages/knowledge/KnowledgeBaseList.tsx
guangfei.zhao 650c41dc41 refactor(layout): migrate styled components to sx prop in Header and Sidebar
feat(pages): add new KnowledgeBaseList and Login pages with complete functionality

feat(services): implement knowledge service with comprehensive API methods

feat(hooks): add login hooks for authentication and form management
2025-10-11 11:31:24 +08:00

265 lines
6.9 KiB
TypeScript

import React, { useState } from 'react';
import {
Box,
Typography,
Button,
Card,
CardContent,
Grid,
Chip,
IconButton,
Menu,
MenuItem,
TextField,
InputAdornment,
Fab,
} from '@mui/material';
import {
Search as SearchIcon,
Add as AddIcon,
MoreVert as MoreVertIcon,
Folder as FolderIcon,
Description as DocumentIcon,
} from '@mui/icons-material';
import { styled } from '@mui/material/styles';
const PageContainer = styled(Box)(({ theme }) => ({
padding: '1.5rem',
backgroundColor: '#F8F9FA',
minHeight: 'calc(100vh - 60px)',
}));
const PageHeader = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '1.5rem',
}));
const SearchContainer = styled(Box)(({ theme }) => ({
display: 'flex',
gap: '1rem',
marginBottom: '1.5rem',
}));
const KBCard = styled(Card)(({ theme }) => ({
height: '100%',
transition: 'all 0.2s ease-in-out',
border: '1px solid #E5E5E5',
'&:hover': {
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
transform: 'translateY(-2px)',
},
}));
const StatusChip = styled(Chip)<{ status: string }>(({ status, theme }) => ({
fontSize: '0.75rem',
height: '24px',
backgroundColor:
status === 'active' ? '#E8F5E8' :
status === 'processing' ? '#FFF3CD' : '#F8D7DA',
color:
status === 'active' ? '#155724' :
status === 'processing' ? '#856404' : '#721C24',
}));
const StatsBox = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
marginTop: '1rem',
padding: '0.75rem',
backgroundColor: '#F8F9FA',
borderRadius: '6px',
}));
const StatItem = styled(Box)(({ theme }) => ({
textAlign: 'center',
'& .number': {
fontSize: '1.25rem',
fontWeight: 600,
color: theme.palette.primary.main,
},
'& .label': {
fontSize: '0.75rem',
color: '#666',
marginTop: '0.25rem',
},
}));
const mockKnowledgeBases = [
{
id: 1,
name: '产品文档库',
description: '包含所有产品相关的技术文档和用户手册',
status: 'active',
documents: 156,
size: '2.3 GB',
lastUpdated: '2024-01-15',
},
{
id: 2,
name: '客服FAQ',
description: '常见问题解答和客服对话记录',
status: 'processing',
documents: 89,
size: '1.1 GB',
lastUpdated: '2024-01-14',
},
{
id: 3,
name: '法律合规',
description: '法律条文、合规要求和相关政策文档',
status: 'active',
documents: 234,
size: '3.7 GB',
lastUpdated: '2024-01-13',
},
{
id: 4,
name: '培训资料',
description: '员工培训材料和学习资源',
status: 'inactive',
documents: 67,
size: '890 MB',
lastUpdated: '2024-01-10',
},
];
const KnowledgeBaseList: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [selectedKB, setSelectedKB] = useState<number | null>(null);
const handleMenuClick = (event: React.MouseEvent<HTMLElement>, kbId: number) => {
setAnchorEl(event.currentTarget);
setSelectedKB(kbId);
};
const handleMenuClose = () => {
setAnchorEl(null);
setSelectedKB(null);
};
const filteredKBs = mockKnowledgeBases.filter(kb =>
kb.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
kb.description.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<PageContainer>
<PageHeader>
<Typography variant="h4" fontWeight={600} color="#333">
</Typography>
<Button
variant="contained"
startIcon={<AddIcon />}
sx={{ borderRadius: '6px' }}
>
</Button>
</PageHeader>
<SearchContainer>
<TextField
placeholder="搜索知识库..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon color="action" />
</InputAdornment>
),
}}
sx={{ width: '400px' }}
/>
</SearchContainer>
<Grid container spacing={3}>
{filteredKBs.map((kb) => (
<Grid key={kb.id} size={{xs:12, sm:6, md:4}}>
<KBCard>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="flex-start">
<Box display="flex" alignItems="center" gap={1}>
<FolderIcon color="primary" />
<Typography variant="h6" fontWeight={600}>
{kb.name}
</Typography>
</Box>
<Box>
<StatusChip
status={kb.status}
label={
kb.status === 'active' ? '活跃' :
kb.status === 'processing' ? '处理中' : '未激活'
}
size="small"
/>
<IconButton
size="small"
onClick={(e) => handleMenuClick(e, kb.id)}
>
<MoreVertIcon />
</IconButton>
</Box>
</Box>
<Typography
variant="body2"
color="text.secondary"
sx={{ mt: 1, mb: 2 }}
>
{kb.description}
</Typography>
<StatsBox>
<StatItem>
<div className="number">{kb.documents}</div>
<div className="label"></div>
</StatItem>
<StatItem>
<div className="number">{kb.size}</div>
<div className="label"></div>
</StatItem>
</StatsBox>
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
: {kb.lastUpdated}
</Typography>
</CardContent>
</KBCard>
</Grid>
))}
</Grid>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose} sx={{ color: 'error.main' }}>
</MenuItem>
</Menu>
<Fab
color="primary"
aria-label="add"
sx={{
position: 'fixed',
bottom: 24,
right: 24,
}}
>
<AddIcon />
</Fab>
</PageContainer>
);
};
export default KnowledgeBaseList;