265 lines
6.9 KiB
TypeScript
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;
|