Files
TERES_web_frontend/src/pages/knowledge/list.tsx
guangfei.zhao 5c937df5ed feat(knowledge): add knowledge base management with dialog system
- Implement knowledge base list, create, and detail pages
- Add dialog provider and components for confirmation and alerts
- Include knowledge card and grid view components
- Enhance header with user menu and logout functionality
- Implement knowledge operations hooks for CRUD operations
2025-10-13 12:26:10 +08:00

275 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useCallback, useMemo } from 'react';
import {
Box,
Typography,
TextField,
Select,
MenuItem,
FormControl,
InputLabel,
Button,
InputAdornment,
Pagination,
Stack,
CircularProgress,
} from '@mui/material';
import {
Search as SearchIcon,
Add as AddIcon,
Refresh as RefreshIcon,
} from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
import { useKnowledgeList, useKnowledgeOperations } from '@/hooks/knowledge_hooks';
import { useUserData } from '@/hooks/useUserData';
import KnowledgeGridView from '@/components/knowledge/KnowledgeGridView';
import type { IKnowledge } from '@/interfaces/database/knowledge';
import { useDialog } from '@/hooks/useDialog';
const KnowledgeBaseList: React.FC = () => {
const navigate = useNavigate();
const {deleteKnowledge} = useKnowledgeOperations();
// 搜索和筛选状态
const [searchTerm, setSearchTerm] = useState('');
const [teamFilter, setTeamFilter] = useState('all');
const pageSize = 12; // 每页显示12个知识库
// 获取用户数据(包含租户列表)
const { tenantList } = useUserData();
// 使用knowledge_hooks获取数据
const {
knowledgeBases,
total,
loading,
error,
currentPage,
setKeywords,
setCurrentPage,
refresh,
} = useKnowledgeList({
keywords: searchTerm,
page: 1,
page_size: pageSize,
});
// 处理搜索
const handleSearch = useCallback((value: string) => {
setSearchTerm(value);
setKeywords(value);
}, [setKeywords]);
// 处理团队筛选
const handleTeamFilterChange = useCallback((value: string) => {
setTeamFilter(value);
setCurrentPage(1); // 筛选时重置到第一页
}, [setCurrentPage]);
// 处理分页变化
const handlePageChange = useCallback((_: React.ChangeEvent<unknown>, page: number) => {
setCurrentPage(page);
}, [setCurrentPage]);
// 处理刷新
const handleRefresh = useCallback(() => {
refresh();
}, [refresh]);
// 处理创建知识库
const handleCreateKnowledge = useCallback(() => {
navigate('/knowledge/create');
}, [navigate]);
const dialog = useDialog();
// 处理删除知识库
const handleDeleteKnowledge = useCallback(async (kb: IKnowledge) => {
// 需要确认删除
dialog.confirm({
title: '确认删除',
content: `是否确认删除知识库 ${kb.name}`,
onConfirm: async () => {
try {
await deleteKnowledge(kb.id);
// 删除成功后刷新列表
refresh();
} catch (err) {
console.error('Failed to delete knowledge:', err);
// 可以添加错误提示
}
},
});
}, [deleteKnowledge, refresh, dialog]);
// 处理查看知识库详情
const handleViewKnowledge = useCallback((kb: IKnowledge) => {
navigate(`/knowledge/${kb.id}`);
}, [navigate]);
// 根据团队筛选过滤知识库
const filteredKnowledgeBases = useMemo(() => {
if (!knowledgeBases) return [];
if (teamFilter === 'all') {
return knowledgeBases;
}
// 根据租户ID筛选
if (teamFilter !== 'all') {
return knowledgeBases.filter(kb => {
// 如果选择的是特定租户ID则筛选该租户的知识库
if (teamFilter === 'me') {
// 假设'me'表示当前用户的知识库,这里可以根据实际业务逻辑调整
return kb.permission === 'me' || kb.tenant_id === tenantList?.[0].tenant_id;
}
// 如果是具体的租户ID
return kb.tenant_id === teamFilter;
});
}
return knowledgeBases;
}, [knowledgeBases, teamFilter, tenantList]);
// 计算总页数(基于筛选后的结果)
const totalPages = Math.ceil(filteredKnowledgeBases.length / pageSize);
// 获取当前页的数据
const currentPageData = useMemo(() => {
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return filteredKnowledgeBases.slice(startIndex, endIndex);
}, [filteredKnowledgeBases, currentPage, pageSize]);
// 构建团队筛选选项
const teamFilterOptions = useMemo(() => {
const options = [
{ value: 'all', label: '全部' },
];
// 添加租户选项
if (tenantList && tenantList.length > 0) {
tenantList.forEach(tenant => {
options.push({
value: tenant.tenant_id,
label: tenant.nickname || tenant.tenant_id,
});
});
}
return options;
}, [tenantList]);
return (
<Box sx={{ p: 3 }}>
{/* 页面标题 */}
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Typography variant="h4" fontWeight={600}>
</Typography>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={handleCreateKnowledge}
sx={{ borderRadius: 2 }}
>
</Button>
</Box>
{/* 搜索和筛选区域 */}
<Box sx={{ display: 'flex', gap: 2, mb: 3, alignItems: 'center' }}>
<TextField
placeholder="搜索知识库..."
value={searchTerm}
onChange={(e) => handleSearch(e.target.value)}
sx={{ flex: 1, maxWidth: 400 }}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
/>
<FormControl sx={{ minWidth: 120 }}>
<InputLabel></InputLabel>
<Select
value={teamFilter}
label="团队筛选"
onChange={(e) => handleTeamFilterChange(e.target.value)}
>
{teamFilterOptions.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
<Button
variant="outlined"
startIcon={<RefreshIcon />}
onClick={handleRefresh}
disabled={loading}
>
</Button>
</Box>
{/* 错误提示 */}
{error && (
<Box sx={{ mb: 3, p: 2, bgcolor: 'error.light', borderRadius: 1 }}>
<Typography color="error">
: {error}
</Typography>
</Box>
)}
{/* 加载状态 */}
{loading && (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress />
</Box>
)}
{/* 知识库列表 */}
{!loading && (
<>
<KnowledgeGridView
knowledgeBases={currentPageData}
onView={handleViewKnowledge}
onDelete={handleDeleteKnowledge}
loading={loading}
searchTerm={searchTerm}
teamFilter={teamFilter}
onCreateKnowledge={handleCreateKnowledge}
/>
{/* 分页组件 */}
{totalPages > 1 && (
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 4 }}>
<Stack spacing={2}>
<Pagination
count={totalPages}
page={currentPage}
onChange={handlePageChange}
color="primary"
size="large"
showFirstButton
showLastButton
/>
<Typography variant="body2" color="text.secondary" textAlign="center">
{filteredKnowledgeBases.length} {currentPage} {totalPages}
</Typography>
</Stack>
</Box>
)}
</>
)}
</Box>
);
};
export default KnowledgeBaseList;