import { useState, useEffect, useCallback } from 'react'; import { useParams } from 'react-router-dom'; import { Card, Table, Tag, Button, Space, message, Modal, Form, Select, InputNumber, Input, Descriptions, Drawer, theme } from 'antd'; import { PlusOutlined, UserOutlined, EditOutlined, DeleteOutlined, UnorderedListOutlined, BankOutlined } from '@ant-design/icons'; import { useStore } from '../store'; import { useCharacterSync } from '../store/hooks'; import axios from 'axios'; interface Organization { id: string; character_id: string; name: string; type: string; purpose: string; member_count: number; power_level: number; location?: string; motto?: string; color?: string; } interface OrganizationMember { id: string; character_id: string; character_name: string; position: string; rank: number; loyalty: number; contribution: number; status: string; joined_at?: string; left_at?: string; notes?: string; } interface Character { id: string; name: string; is_organization: boolean; } export default function Organizations() { const { projectId } = useParams<{ projectId: string }>(); const { currentProject } = useStore(); const { refreshCharacters } = useCharacterSync(); const [organizations, setOrganizations] = useState([]); const [selectedOrg, setSelectedOrg] = useState(null); const [members, setMembers] = useState([]); const [characters, setCharacters] = useState([]); const [loading, setLoading] = useState(false); const [isAddMemberModalOpen, setIsAddMemberModalOpen] = useState(false); const [isEditMemberModalOpen, setIsEditMemberModalOpen] = useState(false); const [isEditOrgModalOpen, setIsEditOrgModalOpen] = useState(false); const [editingMember, setEditingMember] = useState(null); const [form] = Form.useForm(); const [editMemberForm] = Form.useForm(); const [editOrgForm] = Form.useForm(); const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); const [modal, contextHolder] = Modal.useModal(); const [orgListVisible, setOrgListVisible] = useState(false); const { token } = theme.useToken(); useEffect(() => { const handleResize = () => { setIsMobile(window.innerWidth <= 768); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); const loadOrganizations = useCallback(async () => { setLoading(true); try { const res = await axios.get(`/api/organizations/project/${projectId}`); setOrganizations(res.data); if (res.data.length > 0 && !selectedOrg) { setSelectedOrg(res.data[0]); loadMembers(res.data[0].id); } } catch (error) { message.error('加载组织列表失败'); console.error(error); } finally { setLoading(false); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [projectId]); const loadCharacters = useCallback(async () => { try { const res = await axios.get(`/api/characters?project_id=${projectId}`); setCharacters(res.data.items || []); } catch (error) { console.error('加载角色列表失败', error); } }, [projectId]); useEffect(() => { if (projectId) { loadOrganizations(); loadCharacters(); } }, [projectId, loadOrganizations, loadCharacters]); const loadMembers = async (orgId: string) => { try { const res = await axios.get(`/api/organizations/${orgId}/members`); setMembers(res.data); } catch (error) { message.error('加载成员列表失败'); console.error(error); } }; const handleSelectOrganization = (org: Organization) => { setSelectedOrg(org); loadMembers(org.id); }; const handleAddMember = async (values: Record) => { if (!selectedOrg) return; try { await axios.post(`/api/organizations/${selectedOrg.id}/members`, values); message.success('成员添加成功'); setIsAddMemberModalOpen(false); form.resetFields(); loadMembers(selectedOrg.id); loadOrganizations(); // 刷新成员计数 } catch (error) { message.error('添加成员失败'); console.error(error); } }; const handleRemoveMember = async (memberId: string) => { modal.confirm({ title: '确认移除', content: '确定要移除该成员吗?', centered: true, okText: '移除', okType: 'danger', cancelText: '取消', onOk: async () => { try { await axios.delete(`/api/organizations/members/${memberId}`); message.success('成员移除成功'); if (selectedOrg) { loadMembers(selectedOrg.id); loadOrganizations(); // 刷新成员计数 } } catch (error) { message.error('移除失败'); console.error(error); } } }); }; const handleEditMember = (member: OrganizationMember) => { setEditingMember(member); editMemberForm.setFieldsValue({ position: member.position, rank: member.rank, loyalty: member.loyalty, contribution: member.contribution, status: member.status, notes: member.notes, joined_at: member.joined_at }); setIsEditMemberModalOpen(true); }; const handleUpdateMember = async (values: Record) => { if (!editingMember) return; try { await axios.put(`/api/organizations/members/${editingMember.id}`, values); message.success('成员信息更新成功'); setIsEditMemberModalOpen(false); editMemberForm.resetFields(); setEditingMember(null); if (selectedOrg) { loadMembers(selectedOrg.id); } } catch (error) { message.error('更新失败'); console.error(error); } }; const getStatusColor = (status: string) => { const colors: Record = { active: 'green', retired: 'default', expelled: 'red', deceased: 'black' }; return colors[status] || 'default'; }; const getStatusText = (status: string) => { const texts: Record = { active: '在职', retired: '退休', expelled: '除名', deceased: '已故' }; return texts[status] || status; }; const memberColumns = [ { title: '姓名', dataIndex: 'character_name', key: 'name', render: (name: string) => ( {name} ), width: isMobile ? 100 : undefined, }, { title: '职位', dataIndex: 'position', key: 'position', render: (position: string, record: OrganizationMember) => ( {position} {!isMobile && `(级别 ${record.rank})`} ), width: isMobile ? 120 : undefined, }, { title: '忠诚度', dataIndex: 'loyalty', key: 'loyalty', render: (loyalty: number) => ( = 70 ? 'green' : loyalty >= 40 ? 'orange' : 'red' }}> {loyalty}% ), width: isMobile ? 80 : undefined, }, { title: '贡献度', dataIndex: 'contribution', key: 'contribution', render: (contribution: number) => `${contribution}%`, width: isMobile ? 80 : undefined, }, { title: '状态', dataIndex: 'status', key: 'status', render: (status: string) => ( {getStatusText(status)} ), width: isMobile ? 80 : undefined, }, { title: '加入时间', dataIndex: 'joined_at', key: 'joined_at', render: (time: string) => time || '-', width: isMobile ? 120 : undefined, }, { title: '操作', key: 'action', render: (_: unknown, record: OrganizationMember) => ( ), width: isMobile ? 50 : undefined, fixed: isMobile ? 'right' as const : undefined, }, ]; // 过滤掉已是成员的角色 const availableCharacters = characters.filter( c => !c.is_organization && !members.some(m => m.character_id === c.id) ); return (
{contextHolder} {/* 页面标题 - 仅桌面端显示 */} {!isMobile && (

组织管理

)}
{/* 左侧组织列表 - 桌面端 */} {!isMobile && ( {organizations.length === 0 ? (
暂无组织
) : ( {organizations.map(org => ( handleSelectOrganization(org)} > {org.name} {org.type}
成员: {org.member_count} | 势力: {org.power_level}
))}
)}
)} {/* 移动端组织列表抽屉 */} {isMobile && ( setOrgListVisible(false)} open={orgListVisible} width="85%" styles={{ body: { padding: 0 } }} > {organizations.length === 0 ? (
暂无组织
) : ( {organizations.map(org => ( { handleSelectOrganization(org); setOrgListVisible(false); }} > {org.name} {org.type}
成员: {org.member_count} | 势力: {org.power_level}
))}
)}
)} {/* 右侧内容区域 */}
{!selectedOrg ? (
{isMobile && organizations.length > 0 && ( )}
请选择一个组织查看详情
) : ( <> {/* 工具栏 - 移动端显示项目标题和组织列表按钮 */} {isMobile && (
组织管理 {currentProject?.title}
)} {/* 内容区域 */}
} onClick={() => { editOrgForm.setFieldsValue({ power_level: selectedOrg.power_level, location: selectedOrg.location, motto: selectedOrg.motto, color: selectedOrg.color }); setIsEditOrgModalOpen(true); }} > 编辑 } > {selectedOrg.name} {selectedOrg.type} {selectedOrg.member_count} = 70 ? 'red' : selectedOrg.power_level >= 50 ? 'orange' : 'default'}> {selectedOrg.power_level} {selectedOrg.location && ( {selectedOrg.location} )} {selectedOrg.color && ( {selectedOrg.color} )} {selectedOrg.motto && ( {selectedOrg.motto} )} {selectedOrg.purpose} } onClick={() => setIsAddMemberModalOpen(true)} disabled={availableCharacters.length === 0} > 添加成员 } > 5 ? { defaultPageSize: 5, showSizeChanger: true, showQuickJumper: !isMobile, showTotal: (total) => `共 ${total} 名成员`, pageSizeOptions: [5, 10, 20], simple: isMobile, position: ['bottomCenter'], } : false } size="small" scroll={{ x: isMobile ? 'max-content' : undefined, y: members.length > 10 ? 500 : undefined, }} /> )} {/* 添加成员模态框 */} { setIsAddMemberModalOpen(false); form.resetFields(); }} footer={null} centered={!isMobile} width={isMobile ? '100%' : 500} style={isMobile ? { top: 0, paddingBottom: 0, maxWidth: '100vw' } : undefined} styles={isMobile ? { body: { maxHeight: 'calc(100vh - 110px)', overflowY: 'auto' } } : undefined} >
{/* 编辑成员模态框 */} { setIsEditMemberModalOpen(false); editMemberForm.resetFields(); setEditingMember(null); }} footer={null} centered={true} width={isMobile ? '90%' : 500} style={isMobile ? { maxWidth: '90vw', margin: '0 auto' } : undefined} styles={isMobile ? { body: { maxHeight: 'calc(80vh - 110px)', overflowY: 'auto', padding: '20px 16px' } } : undefined} >
{/* 编辑组织模态框 */} { setIsEditOrgModalOpen(false); editOrgForm.resetFields(); }} footer={null} centered={!isMobile} width={isMobile ? '100%' : 500} style={isMobile ? { top: 0, paddingBottom: 0, maxWidth: '100vw' } : undefined} styles={isMobile ? { body: { maxHeight: 'calc(100vh - 110px)', overflowY: 'auto' } } : undefined} >
{ if (!selectedOrg) return; try { await axios.put(`/api/organizations/${selectedOrg.id}`, values); message.success('组织信息更新成功'); setIsEditOrgModalOpen(false); editOrgForm.resetFields(); // 重新获取更新后的组织列表 const res = await axios.get(`/api/organizations/project/${projectId}`); setOrganizations(res.data); // 更新当前选中的组织详情 const updatedOrg = res.data.find((org: Organization) => org.id === selectedOrg.id); if (updatedOrg) { setSelectedOrg(updatedOrg); } // 刷新全局 store await refreshCharacters(); } catch (error) { message.error('更新失败'); console.error(error); } }} >
); }