import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { Card, Empty, Modal, Form, Input, Row, Col, Space, Button, Switch, message, Select, Table, Tag } from 'antd' import { PlusOutlined, FolderOutlined, TeamOutlined, EyeOutlined, ShareAltOutlined, CopyOutlined, DeleteOutlined, EditOutlined, FileOutlined } from '@ant-design/icons' import { getMyProjects, getOwnedProjects, getSharedProjects, createProject, deleteProject, updateProject, getProjectMembers, addProjectMember, removeProjectMember } from '@/api/project' import { getProjectShareInfo, updateShareSettings } from '@/api/share' import { getUserList } from '@/api/users' import { searchDocuments } from '@/api/search' import ListActionBar from '@/components/ListActionBar/ListActionBar' import Toast from '@/components/Toast/Toast' import './ProjectList.css' function ProjectList({ type = 'my' }) { const [projects, setProjects] = useState([]) const [loading, setLoading] = useState(false) const [modalVisible, setModalVisible] = useState(false) const [editModalVisible, setEditModalVisible] = useState(false) const [shareModalVisible, setShareModalVisible] = useState(false) const [membersModalVisible, setMembersModalVisible] = useState(false) const [currentProject, setCurrentProject] = useState(null) const [shareInfo, setShareInfo] = useState(null) const [hasPassword, setHasPassword] = useState(false) const [password, setPassword] = useState('') const [searchKeyword, setSearchKeyword] = useState('') const [searchResults, setSearchResults] = useState([]) const [searching, setSearching] = useState(false) const [hasSearched, setHasSearched] = useState(false) const [members, setMembers] = useState([]) const [users, setUsers] = useState([]) const [loadingMembers, setLoadingMembers] = useState(false) const [form] = Form.useForm() const [editForm] = Form.useForm() const [memberForm] = Form.useForm() const navigate = useNavigate() useEffect(() => { fetchProjects() }, [type]) const fetchProjects = async () => { setLoading(true) try { let res if (type === 'my') { res = await getOwnedProjects() } else if (type === 'share') { res = await getSharedProjects() } else { res = await getMyProjects() } setProjects(res.data || []) } catch (error) { console.error('Fetch projects error:', error) } finally { setLoading(false) } } const handleCreateProject = async (values) => { try { await createProject(values) Toast.success('创建成功', '项目已创建') setModalVisible(false) form.resetFields() fetchProjects() } catch (error) { console.error('Create project error:', error) } } const handleDeleteProject = async (projectId) => { Modal.confirm({ title: '确认删除', content: '确定要删除这个项目吗?如果项目中存在文件,将无法删除。删除后将无法恢复!', okText: '确定删除', okType: 'danger', cancelText: '取消', onOk: async () => { try { await deleteProject(projectId) Toast.success('删除成功', '项目已删除') fetchProjects() } catch (error) { console.error('Delete project error:', error) const errorMsg = error.response?.data?.detail || error.message || '删除失败' Toast.error('删除失败', errorMsg) } }, }) } // 打开编辑项目 const handleEdit = (e, project) => { e.stopPropagation() setCurrentProject(project) editForm.setFieldsValue({ name: project.name, description: project.description, is_public: project.is_public === 1, }) setEditModalVisible(true) } // 更新项目 const handleUpdateProject = async (values) => { try { await updateProject(currentProject.id, { ...values, is_public: values.is_public ? 1 : 0, }) message.success('项目更新成功') setEditModalVisible(false) editForm.resetFields() fetchProjects() } catch (error) { console.error('Update project error:', error) message.error('项目更新失败') } } const handleOpenProject = (projectId) => { navigate(`/projects/${projectId}/docs`) } // 打开分享设置 const handleShare = async (e, project) => { e.stopPropagation() setCurrentProject(project) try { const res = await getProjectShareInfo(project.id) setShareInfo(res.data) setHasPassword(res.data.has_password) setPassword('') setShareModalVisible(true) } catch (error) { console.error('Get share info error:', error) message.error('获取分享信息失败') } } // 复制分享链接 const handleCopyLink = () => { if (!shareInfo) return const fullUrl = `${window.location.origin}${shareInfo.share_url}` navigator.clipboard.writeText(fullUrl) message.success('分享链接已复制') } // 切换密码保护 const handlePasswordToggle = async (checked) => { if (!checked) { // 取消密码 try { await updateShareSettings(currentProject.id, { access_pass: null }) setHasPassword(false) setPassword('') message.success('已取消访问密码') // 刷新分享信息 const res = await getProjectShareInfo(currentProject.id) setShareInfo(res.data) } catch (error) { console.error('Update settings error:', error) message.error('操作失败') } } else { setHasPassword(true) } } // 保存密码 const handleSavePassword = async () => { if (!password.trim()) { message.warning('请输入访问密码') return } try { await updateShareSettings(currentProject.id, { access_pass: password }) message.success('访问密码已设置') // 刷新分享信息 const res = await getProjectShareInfo(currentProject.id) setShareInfo(res.data) setHasPassword(true) } catch (error) { console.error('Save password error:', error) message.error('设置密码失败') } } // 打开成员管理 const handleMembers = async (e, project) => { e.stopPropagation() setCurrentProject(project) setMembersModalVisible(true) setLoadingMembers(true) try { // 并行加载成员列表和用户列表(只获取普通用户 role_id=3) const [membersRes, usersRes] = await Promise.all([ getProjectMembers(project.id), getUserList({ page: 1, page_size: 100, status: 1, role_id: 3 }) ]) console.log('Members Response:', membersRes) console.log('Users Response:', usersRes) const membersData = membersRes.data || [] // 后端返回格式: { code: 200, message: "success", data: [...], total, page, page_size } const usersData = Array.isArray(usersRes.data) ? usersRes.data : [] console.log('Setting members:', membersData) console.log('Setting users:', usersData) setMembers(membersData) setUsers(usersData) } catch (error) { console.error('Get members error:', error) console.error('Error details:', error.response) message.error('获取数据失败: ' + (error.response?.data?.detail || error.message)) } finally { setLoadingMembers(false) } } // 添加成员 const handleAddMember = async (values) => { try { await addProjectMember(currentProject.id, values) message.success('成员添加成功') memberForm.resetFields() // 刷新成员列表(带用户名信息) const res = await getProjectMembers(currentProject.id) setMembers(res.data || []) } catch (error) { console.error('Add member error:', error) const errorMsg = error.response?.data?.detail || error.message || '添加成员失败' message.error(errorMsg) } } // 删除成员 const handleRemoveMember = async (userId) => { Modal.confirm({ title: '确认删除', content: '确定要删除这个成员吗?', onOk: async () => { try { await removeProjectMember(currentProject.id, userId) message.success('成员删除成功') // 刷新成员列表(带用户名信息) const res = await getProjectMembers(currentProject.id) setMembers(res.data || []) } catch (error) { console.error('Remove member error:', error) const errorMsg = error.response?.data?.detail || error.message || '删除成员失败' message.error(errorMsg) } }, }) } // 处理搜索输入变化 const handleSearchChange = (value) => { setSearchKeyword(value) // 如果清空了输入框,重置搜索状态 if (!value || !value.trim()) { setSearchResults([]) setHasSearched(false) } } // 处理搜索 const handleSearch = async (keyword) => { setSearchKeyword(keyword) if (!keyword || !keyword.trim()) { // 清空搜索,显示所有项目 setSearchResults([]) setSearching(false) setHasSearched(false) return } setSearching(true) setHasSearched(true) try { const res = await searchDocuments(keyword.trim()) setSearchResults(res.data || []) } catch (error) { console.error('Search error:', error) Toast.error('搜索失败', error.response?.data?.detail || error.message) } finally { setSearching(false) } } // 处理搜索结果点击 const handleSearchResultClick = (item) => { if (item.type === 'project') { // 跳转到项目文档页 navigate(`/projects/${item.project_id}/docs`) } else if (item.type === 'file') { // 跳转到文件 navigate(`/projects/${item.project_id}/docs?file=${encodeURIComponent(item.file_path)}`) } } // 过滤项目(仅在未使用全局搜索时进行本地过滤) const filteredProjects = !hasSearched && searchKeyword ? projects.filter((project) => project.name.toLowerCase().includes(searchKeyword.toLowerCase()) || (project.description && project.description.toLowerCase().includes(searchKeyword.toLowerCase())) ) : projects return (
, onClick: () => setModalVisible(true), }, ] : []} search={{ placeholder: '搜索项目或文件...', value: searchKeyword, onChange: handleSearchChange, onSearch: handleSearch, }} showRefresh onRefresh={fetchProjects} /> {/* 搜索结果 */} {hasSearched && searchResults.length > 0 && (
找到 {searchResults.length} 个结果
{searchResults.map((item, index) => ( handleSearchResultClick(item)} >
{item.type === 'project' ? ( ) : ( )}

{item.type === 'project' ? item.project_name : item.file_name}

{item.match_type}
{item.type === 'project' && (

{item.project_description || '暂无描述'}

)} {item.type === 'file' && (
项目: {item.project_name}
路径: {item.file_path}
)}
))}
)} {/* 正常项目列表 */} {!hasSearched && ( {filteredProjects.map((project) => ( handleOpenProject(project.id)} actions={type === 'my' ? [ handleEdit(e, project)} />, handleShare(e, project)} />, handleMembers(e, project)} />, ] : [ , handleShare(e, project)} />, ]} > {/* 公开项目标识 */} {project.is_public === 1 && (
公开
)}

{project.name}

{project.description || '暂无描述'}

访问: {project.visit_count} {type === 'share' && project.owner_name && ( 所有者: {project.owner_nickname || project.owner_name} )} {type === 'share' && project.user_role && ( 角色: {project.user_role === 'admin' ? '管理者' : project.user_role === 'editor' ? '编辑者' : '查看者'} )}
))} {filteredProjects.length === 0 && !loading && ( )}
)} {/* 搜索无结果提示 */} {hasSearched && !searching && searchResults.length === 0 && (
)} { setModalVisible(false) form.resetFields() }} footer={null} >
{ setEditModalVisible(false) editForm.resetFields() }} footer={null} >
setShareModalVisible(false)} footer={null} width={500} > {shareInfo && (
} />
{/* 只有在我的项目中才显示密码设置功能 */} {type === 'my' && ( <>
访问密码保护
{hasPassword && (
setPassword(e.target.value)} />
)} )} {/* 参与项目显示提示 */} {type === 'share' && shareInfo.has_password && (
该项目已设置访问密码保护
)}
)}
{ setMembersModalVisible(false) memberForm.resetFields() setMembers([]) setUsers([]) }} footer={null} width={700} >
{users.length === 0 ? (
{loadingMembers ? '正在加载用户列表...' : '没有可添加的用户'}
) : null}
管理员 编辑者 查看者

当前成员

{members.length === 0 ? ( ) : ( { if (record.nickname) { return `${username} (${record.nickname})` } return username || `用户ID: ${record.user_id}` }, }, { title: '角色', dataIndex: 'role', key: 'role', render: (role) => { const roleMap = { admin: '管理员', editor: '编辑者', viewer: '查看者', } return roleMap[role] || role }, }, { title: '加入时间', dataIndex: 'joined_at', key: 'joined_at', render: (time) => time ? new Date(time).toLocaleString('zh-CN') : '-', }, { title: '操作', key: 'action', render: (_, record) => { const isOwner = record.user_id === currentProject?.owner_id return ( ) }, }, ]} /> )} ) } export default ProjectList