This commit is contained in:
2026-07-06 15:39:33 +08:00
parent fa4f48a62f
commit 9b213b1551
4 changed files with 139 additions and 13 deletions
+18
View File
@@ -782,3 +782,21 @@ export async function getJoinTeamInfo(code: string): Promise<any> {
export async function submitJoinRequest(code: string): Promise<void> {
await api.post('/team/join', { invitation_code: code });
}
export async function getTeamCreditRecords(params: {
page?: number;
pageSize?: number;
userId?: string;
recordType?: string;
startDate?: string;
endDate?: string;
}): Promise<any> {
const p = new URLSearchParams();
if (params.page) p.set('page', String(params.page));
if (params.pageSize) p.set('page_size', String(params.pageSize));
if (params.userId) p.set('user_id', params.userId);
if (params.recordType) p.set('record_type', params.recordType);
if (params.startDate) p.set('start_date', params.startDate);
if (params.endDate) p.set('end_date', params.endDate);
return api.get(`/team/credit-records?${p.toString()}`);
}
@@ -597,6 +597,7 @@ const AppLayout: React.FC = () => {
{ key: 'profile', icon: <UserOutlined />, label: `账号: ${user?.username}`, disabled: true },
{ key: 'credits', icon: <WalletOutlined style={{ color: '#c9a96e' }} />, label: `积分: ${user?.credits ?? 0}`, disabled: true },
{ type: 'divider' as const },
...(user?.isTeamManager ? [{ key: 'teamManagement' as const, icon: <TeamOutlined style={{ color: '#6366f1' }} />, label: '团队管理' }] : []),
{ key: 'myCredits', icon: <WalletOutlined />, label: '积分明细' },
{ key: 'orderRecords', icon: <FileTextOutlined />, label: '订单记录' },
{ key: 'messages', icon: <BellOutlined />, label: `消息中心${unreadCount > 0 ? `(${unreadCount})` : ''}` },
@@ -612,6 +613,7 @@ const AppLayout: React.FC = () => {
else if (key === 'changePwd') { setPwdModalOpen(true); }
else if (key === 'messages') { navigate('/messages'); }
else if (key === 'recharge') { setRechargeModalOpen(true); }
else if (key === 'teamManagement') { navigate('/team-management'); }
else if (key === 'myCredits') { navigate('/user-center?tab=credits'); }
else if (key === 'orderRecords') { navigate('/user-center?tab=orders'); }
else if (key === 'manual') { window.open(operationManualUrl, '_blank'); }
@@ -837,16 +839,6 @@ const AppLayout: React.FC = () => {
<div style={{ flex: 1, padding: '8px 8px', overflow: 'auto' }}>
{topLevelItems.map(item => renderMenuItem(item))}
{user?.isTeamManager && renderMenuItem({
id: 'team-management',
key: 'team-management',
label: '团队管理',
path: '/team-management',
icon: 'TeamOutlined',
sortOrder: 999,
isActive: true,
menuType: 'page',
} as any)}
</div>
<div onClick={() => setRechargeModalOpen(true)} style={{
+89 -3
View File
@@ -3,11 +3,11 @@ import {
Button, Card, Empty, Form, Input, InputNumber, message, Modal, Space, Table, Tabs, Tag, Tooltip, Typography,
} from 'antd';
import {
CopyOutlined, PlusOutlined, ReloadOutlined, UserOutlined, HistoryOutlined,
CopyOutlined, PlusOutlined, ReloadOutlined, UserOutlined, HistoryOutlined, WalletOutlined,
} from '@ant-design/icons';
import {
createTeamInvitation, getJoinTeamInfo, getManagedTeam, getPendingJoinRequests,
getTeamInvitations, getTeamMembers, handleJoinRequest, revokeInvitation, submitJoinRequest, transferCredits,
getTeamCreditRecords, getTeamInvitations, getTeamMembers, handleJoinRequest, revokeInvitation, submitJoinRequest, transferCredits,
} from '../api';
import type { ManagedTeam, TeamInvitation, TeamJoinRequest, TeamMember } from '../types';
import { formatDate } from '../utils/formatDate';
@@ -160,6 +160,32 @@ const TeamManagementPage: React.FC = () => {
}
};
// ── Tab 4: 团队积分变动 ──
const [creditRecords, setCreditRecords] = useState<any[]>([]);
const [creditTotal, setCreditTotal] = useState(0);
const [creditLoading, setCreditLoading] = useState(false);
const [creditPage, setCreditPage] = useState(1);
const [creditFilterType, setCreditFilterType] = useState<string>('');
const loadCreditRecords = useCallback(async () => {
setCreditLoading(true);
try {
const res = await getTeamCreditRecords({
page: creditPage,
pageSize: 20,
recordType: creditFilterType || undefined,
});
setCreditRecords(res.items || []);
setCreditTotal(res.total || 0);
} catch (e: any) {
message.error(e?.message || '加载积分记录失败');
} finally {
setCreditLoading(false);
}
}, [creditPage, creditFilterType]);
useEffect(() => { loadCreditRecords(); }, [loadCreditRecords]);
const memberColumns = [
{ title: '用户名', dataIndex: 'username', width: 150, render: (v: string) => <Typography.Text strong>{v}</Typography.Text> },
{ title: '手机号', dataIndex: 'phone', width: 130, render: (v: string) => v || '-' },
@@ -285,6 +311,66 @@ const TeamManagementPage: React.FC = () => {
/>
),
},
{
key: 'credits',
label: <Space><WalletOutlined /></Space>,
children: (
<div>
<div style={{ marginBottom: 12, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<Select
value={creditFilterType || undefined}
onChange={(v) => { setCreditFilterType(v || ''); setCreditPage(1); }}
allowClear
placeholder="全部类型"
style={{ width: 140 }}
options={[
{ value: 'recharge', label: '充值' },
{ value: 'consume', label: '消费' },
{ value: 'admin', label: '管理员调整' },
{ value: 'refund', label: '退款' },
]}
/>
</div>
<Table
size="small"
rowKey="id"
loading={creditLoading}
dataSource={creditRecords}
pagination={{
current: creditPage,
pageSize: 20,
total: creditTotal,
onChange: (p) => setCreditPage(p),
showTotal: (t) => `${t}`,
}}
scroll={{ x: 900 }}
columns={[
{ title: '用户名', key: 'username', width: 120, render: (_: any, r: any) => <Typography.Text strong>{r.username || r.user_id || '-'}</Typography.Text> },
{
title: '类型', key: 'type', width: 100,
render: (_: any, r: any) => {
const colorMap: Record<string, string> = { recharge: 'green', consume: 'red', admin: 'blue', refund: 'orange' };
const labelMap: Record<string, string> = { recharge: '充值', consume: '消费', admin: '管理员调整', refund: '退款' };
return <Tag color={colorMap[r.type] || 'default'}>{labelMap[r.type] || r.type}</Tag>;
},
},
{
title: '积分变动', key: 'amount', width: 100,
render: (_: any, r: any) => (
<Typography.Text style={{ color: r.amount >= 0 ? '#16a34a' : '#dc2626', fontWeight: 600 }}>
{r.amount >= 0 ? '+' : ''}{(r.amount ?? 0).toFixed(2)}
</Typography.Text>
),
},
{ title: '余额', key: 'balance_after', width: 100, render: (_: any, r: any) => (r.balance_after ?? 0).toFixed(2) },
{ title: '说明', dataIndex: 'description', ellipsis: true, render: (v: string) => v || '-' },
{ title: '时间', key: 'created_at', width: 160, render: (_: any, r: any) => formatDate(r.created_at) },
]}
locale={{ emptyText: <Empty description="暂无积分记录" /> }}
/>
</div>
),
},
];
return (
@@ -296,7 +382,7 @@ const TeamManagementPage: React.FC = () => {
<Typography.Title level={4} style={{ margin: 0 }}>{team.name}</Typography.Title>
<Typography.Text type="secondary">: {team.code || '-'} : {team.memberCount} </Typography.Text>
</div>
<Button icon={<ReloadOutlined />} onClick={() => { loadTeam(); loadMembers(); loadInvitations(); loadRequests(); }}></Button>
<Button icon={<ReloadOutlined />} onClick={() => { loadTeam(); loadMembers(); loadInvitations(); loadRequests(); loadCreditRecords(); }}></Button>
</div>
)}
</Card>