Merge branch 'main' of https://gitee.com/wg123/video-gen
This commit is contained in:
+94
-94
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-DlbOvnzQ.js"></script>
|
<script type="module" crossorigin src="/assets/index-DlCxaJzD.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-D9_3MPsN.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-D9_3MPsN.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Button, Card, Empty, Form, Input, InputNumber, message, Modal, Radio, Select, Space, Table, Tabs, Tag, Tooltip, Typography,
|
Button, Empty, Form, Input, InputNumber, message, Modal, Pagination, Radio, Select, Space, Table, Tabs, Tag, Tooltip, Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { DatePicker } from 'antd';
|
import { DatePicker } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@@ -14,9 +14,26 @@ import {
|
|||||||
getTeamCreditExportUrl, getTeamCreditRecords, getTeamInvitations, getTeamMembers, handleJoinRequest, revokeInvitation, submitJoinRequest, transferCredits,
|
getTeamCreditExportUrl, getTeamCreditRecords, getTeamInvitations, getTeamMembers, handleJoinRequest, revokeInvitation, submitJoinRequest, transferCredits,
|
||||||
} from '../api';
|
} from '../api';
|
||||||
import type { ManagedTeam, TeamInvitation, TeamJoinRequest, TeamMember } from '../types';
|
import type { ManagedTeam, TeamInvitation, TeamJoinRequest, TeamMember } from '../types';
|
||||||
import { formatDate } from '../utils/formatDate';
|
|
||||||
import { useAuthStore } from '../store/useAuthStore';
|
import { useAuthStore } from '../store/useAuthStore';
|
||||||
|
|
||||||
|
/* ── 工具函数 ────────────────────────────────────────── */
|
||||||
|
function formatDateTime(value: any): string {
|
||||||
|
if (!value) return '-';
|
||||||
|
try {
|
||||||
|
return new Date(value).toLocaleString('zh-CN', { hour12: false });
|
||||||
|
} catch {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RECORD_TYPE_CONFIG: Record<string, { color: string; label: string }> = {
|
||||||
|
recharge: { color: 'green', label: '充值' },
|
||||||
|
consume: { color: 'red', label: '消费' },
|
||||||
|
refund: { color: 'orange', label: '退款' },
|
||||||
|
team_internal: { color: 'blue', label: '团队内部' },
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ── 主组件 ──────────────────────────────────────────── */
|
||||||
const TeamManagementPage: React.FC = () => {
|
const TeamManagementPage: React.FC = () => {
|
||||||
const [team, setTeam] = useState<ManagedTeam | null>(null);
|
const [team, setTeam] = useState<ManagedTeam | null>(null);
|
||||||
const [teamLoading, setTeamLoading] = useState(false);
|
const [teamLoading, setTeamLoading] = useState(false);
|
||||||
@@ -57,6 +74,7 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => { loadMembers(); }, [loadMembers]);
|
useEffect(() => { loadMembers(); }, [loadMembers]);
|
||||||
|
|
||||||
|
// ── 调整积分弹窗 ──
|
||||||
const [creditModal, setCreditModal] = useState<{ open: boolean; member: TeamMember | null }>({ open: false, member: null });
|
const [creditModal, setCreditModal] = useState<{ open: boolean; member: TeamMember | null }>({ open: false, member: null });
|
||||||
const [creditForm] = Form.useForm();
|
const [creditForm] = Form.useForm();
|
||||||
const [creditSaving, setCreditSaving] = useState(false);
|
const [creditSaving, setCreditSaving] = useState(false);
|
||||||
@@ -65,14 +83,20 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
if (!creditModal.member) return;
|
if (!creditModal.member) return;
|
||||||
try {
|
try {
|
||||||
const values = await creditForm.validateFields();
|
const values = await creditForm.validateFields();
|
||||||
|
// 二次校验:确保是正数
|
||||||
|
const amount = Number(values.amount);
|
||||||
|
if (!amount || amount <= 0 || amount > 9999999) {
|
||||||
|
message.error('请输入有效的正数积分数量');
|
||||||
|
return;
|
||||||
|
}
|
||||||
setCreditSaving(true);
|
setCreditSaving(true);
|
||||||
await transferCredits(
|
await transferCredits(
|
||||||
creditModal.member.id,
|
creditModal.member.id,
|
||||||
values.amount,
|
amount,
|
||||||
values.direction || "increase",
|
values.direction || 'increase',
|
||||||
values.description,
|
values.description,
|
||||||
);
|
);
|
||||||
message.success(values.direction === "decrease" ? '积分扣减成功' : '积分增加成功');
|
message.success(values.direction === 'decrease' ? '积分扣减成功' : '积分增加成功');
|
||||||
setCreditModal({ open: false, member: null });
|
setCreditModal({ open: false, member: null });
|
||||||
creditForm.resetFields();
|
creditForm.resetFields();
|
||||||
loadMembers();
|
loadMembers();
|
||||||
@@ -179,19 +203,18 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
const [creditPage, setCreditPage] = useState(1);
|
const [creditPage, setCreditPage] = useState(1);
|
||||||
const [creditFilterType, setCreditFilterType] = useState<string>('');
|
const [creditFilterType, setCreditFilterType] = useState<string>('');
|
||||||
const [creditFilterPhone, setCreditFilterPhone] = useState<string>('');
|
const [creditFilterPhone, setCreditFilterPhone] = useState<string>('');
|
||||||
const [creditFilterStart, setCreditFilterStart] = useState<string>('');
|
const [creditDateRange, setCreditDateRange] = useState<[string, string] | null>(null);
|
||||||
const [creditFilterEnd, setCreditFilterEnd] = useState<string>('');
|
|
||||||
|
|
||||||
const loadCreditRecords = useCallback(async () => {
|
const loadCreditRecords = useCallback(async () => {
|
||||||
setCreditLoading(true);
|
setCreditLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await getTeamCreditRecords({
|
const res = await getTeamCreditRecords({
|
||||||
page: creditPage,
|
page: creditPage,
|
||||||
pageSize: 20,
|
pageSize: 10,
|
||||||
phone: creditFilterPhone || undefined,
|
phone: creditFilterPhone || undefined,
|
||||||
recordType: creditFilterType || undefined,
|
recordType: creditFilterType || undefined,
|
||||||
startDate: creditFilterStart || undefined,
|
startDate: creditDateRange?.[0] || undefined,
|
||||||
endDate: creditFilterEnd || undefined,
|
endDate: creditDateRange?.[1] || undefined,
|
||||||
});
|
});
|
||||||
setCreditRecords(res.items || []);
|
setCreditRecords(res.items || []);
|
||||||
setCreditTotal(res.total || 0);
|
setCreditTotal(res.total || 0);
|
||||||
@@ -201,15 +224,14 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setCreditLoading(false);
|
setCreditLoading(false);
|
||||||
}
|
}
|
||||||
}, [creditPage, creditFilterType, creditFilterPhone, creditFilterStart, creditFilterEnd]);
|
}, [creditPage, creditFilterType, creditFilterPhone, creditDateRange]);
|
||||||
|
|
||||||
useEffect(() => { loadCreditRecords(); }, [loadCreditRecords]);
|
useEffect(() => { loadCreditRecords(); }, [loadCreditRecords]);
|
||||||
|
|
||||||
const resetCreditFilters = () => {
|
const resetCreditFilters = () => {
|
||||||
setCreditFilterType('');
|
setCreditFilterType('');
|
||||||
setCreditFilterPhone('');
|
setCreditFilterPhone('');
|
||||||
setCreditFilterStart('');
|
setCreditDateRange(null);
|
||||||
setCreditFilterEnd('');
|
|
||||||
setCreditPage(1);
|
setCreditPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -217,10 +239,9 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
const url = getTeamCreditExportUrl({
|
const url = getTeamCreditExportUrl({
|
||||||
phone: creditFilterPhone || undefined,
|
phone: creditFilterPhone || undefined,
|
||||||
recordType: creditFilterType || undefined,
|
recordType: creditFilterType || undefined,
|
||||||
startDate: creditFilterStart || undefined,
|
startDate: creditDateRange?.[0] || undefined,
|
||||||
endDate: creditFilterEnd || undefined,
|
endDate: creditDateRange?.[1] || undefined,
|
||||||
});
|
});
|
||||||
// 携带 token 下载
|
|
||||||
const token = localStorage.getItem('auth_token');
|
const token = localStorage.getItem('auth_token');
|
||||||
const headers: Record<string, string> = token ? { Authorization: `Bearer ${token}` } : {};
|
const headers: Record<string, string> = token ? { Authorization: `Bearer ${token}` } : {};
|
||||||
fetch(url, { headers })
|
fetch(url, { headers })
|
||||||
@@ -228,23 +249,24 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
.then((blob) => {
|
.then((blob) => {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = URL.createObjectURL(blob);
|
a.href = URL.createObjectURL(blob);
|
||||||
a.download = `team_credits_${Date.now()}.csv`;
|
a.download = `team_credits_${dayjs().format('YYYYMMDD_HHmmss')}.csv`;
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(a.href);
|
URL.revokeObjectURL(a.href);
|
||||||
})
|
})
|
||||||
.catch(() => message.error('导出失败'));
|
.catch(() => message.error('导出失败'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* ── 表格列定义 ──────────────────────────────────────── */
|
||||||
const memberColumns = [
|
const memberColumns = [
|
||||||
{ title: '用户名', dataIndex: 'username', width: 150, render: (v: string) => <Typography.Text strong>{v}</Typography.Text> },
|
{ title: '用户名', dataIndex: 'username', width: 140, render: (v: string) => <Typography.Text strong>{v}</Typography.Text> },
|
||||||
{ title: '手机号', dataIndex: 'phone', width: 130, render: (v: string) => v || '-' },
|
{ title: '手机号', dataIndex: 'phone', width: 130, render: (v: string) => v || '-' },
|
||||||
{ title: '积分', dataIndex: 'credits', width: 100, render: (v: number) => <Typography.Text style={{ color: '#6366f1' }}>{(v ?? 0).toFixed(2)}</Typography.Text> },
|
{ title: '积分', dataIndex: 'credits', width: 100, render: (v: number) => <Typography.Text style={{ color: '#6366f1' }}>{(v ?? 0).toFixed(2)}</Typography.Text> },
|
||||||
{ title: '状态', dataIndex: 'isActive', width: 80, render: (v: boolean) => <Tag color={v ? 'green' : 'red'}>{v ? '启用' : '禁用'}</Tag> },
|
{ title: '状态', dataIndex: 'isActive', width: 80, render: (v: boolean) => <Tag color={v ? 'green' : 'red'}>{v ? '启用' : '禁用'}</Tag> },
|
||||||
{ title: '加入时间', dataIndex: 'joinedAt', width: 160, render: (v: string) => formatDate(v) },
|
{ title: '加入时间', dataIndex: 'joinedAt', width: 170, render: (v: string) => formatDateTime(v) },
|
||||||
{
|
{
|
||||||
title: '操作', key: 'action', width: 100,
|
title: '操作', key: 'action', width: 100,
|
||||||
render: (_: any, r: TeamMember) => (
|
render: (_: any, r: TeamMember) => (
|
||||||
<Button size="small" type="link" onClick={() => { setCreditModal({ open: true, member: r }); creditForm.resetFields(); }}>调整积分</Button>
|
<Button size="small" type="link" style={{ padding: 0 }} onClick={() => { setCreditModal({ open: true, member: r }); creditForm.resetFields(); }}>调整积分</Button>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -264,7 +286,7 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
},
|
},
|
||||||
{ title: '状态', dataIndex: 'status', width: 80, render: (v: string) => <Tag color={v === 'active' ? 'green' : 'default'}>{v === 'active' ? '有效' : '已撤销'}</Tag> },
|
{ title: '状态', dataIndex: 'status', width: 80, render: (v: string) => <Tag color={v === 'active' ? 'green' : 'default'}>{v === 'active' ? '有效' : '已撤销'}</Tag> },
|
||||||
{ title: '使用次数', key: 'uses', width: 100, render: (_: any, r: TeamInvitation) => `${r.useCount}${r.maxUses ? `/${r.maxUses}` : ''}` },
|
{ title: '使用次数', key: 'uses', width: 100, render: (_: any, r: TeamInvitation) => `${r.useCount}${r.maxUses ? `/${r.maxUses}` : ''}` },
|
||||||
{ title: '过期时间', dataIndex: 'expiresAt', width: 160, render: (v: string) => v ? formatDate(v) : '永不过期' },
|
{ title: '过期时间', dataIndex: 'expiresAt', width: 170, render: (v: string) => v ? formatDateTime(v) : '永不过期' },
|
||||||
{
|
{
|
||||||
title: '操作', key: 'action', width: 80,
|
title: '操作', key: 'action', width: 80,
|
||||||
render: (_: any, r: TeamInvitation) => r.status === 'active' ? (
|
render: (_: any, r: TeamInvitation) => r.status === 'active' ? (
|
||||||
@@ -274,9 +296,9 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const reqColumns = [
|
const reqColumns = [
|
||||||
{ title: '申请人', dataIndex: 'username', width: 150, render: (v: string) => <Typography.Text strong>{v}</Typography.Text> },
|
{ title: '申请人', dataIndex: 'username', width: 140, render: (v: string) => <Typography.Text strong>{v}</Typography.Text> },
|
||||||
{ title: '手机号', dataIndex: 'phone', width: 130, render: (v: string) => v || '-' },
|
{ title: '手机号', dataIndex: 'phone', width: 130, render: (v: string) => v || '-' },
|
||||||
{ title: '申请时间', dataIndex: 'createdAt', width: 160, render: (v: string) => formatDate(v) },
|
{ title: '申请时间', dataIndex: 'createdAt', width: 170, render: (v: string) => formatDateTime(v) },
|
||||||
{
|
{
|
||||||
title: '操作', key: 'action', width: 160,
|
title: '操作', key: 'action', width: 160,
|
||||||
render: (_: any, r: TeamJoinRequest) => (
|
render: (_: any, r: TeamJoinRequest) => (
|
||||||
@@ -303,6 +325,30 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const creditColumns = [
|
||||||
|
{ title: '用户名', dataIndex: 'username', width: 120, render: (v: string) => <Typography.Text strong>{v || '-'}</Typography.Text> },
|
||||||
|
{ title: '手机号', dataIndex: 'phone', width: 120, render: (v: string) => v || '-' },
|
||||||
|
{
|
||||||
|
title: '类型', dataIndex: 'type', width: 100,
|
||||||
|
render: (_: any, r: any) => {
|
||||||
|
const cfg = RECORD_TYPE_CONFIG[r.type] || { color: 'default', label: r.type || '-' };
|
||||||
|
return <Tag color={cfg.color}>{cfg.label}</Tag>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '变动积分', dataIndex: 'amount', width: 110, align: 'right' as const,
|
||||||
|
render: (_: any, r: any) => (
|
||||||
|
<Typography.Text strong style={{ color: r.amount >= 0 ? '#10b981' : '#ef4444', fontSize: 14 }}>
|
||||||
|
{r.amount >= 0 ? '+' : ''}{(r.amount ?? 0).toFixed(2)}
|
||||||
|
</Typography.Text>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ title: '余额', dataIndex: 'balanceAfter', width: 100, align: 'right' as const, render: (v: number) => (v ?? 0).toFixed(2) },
|
||||||
|
{ title: '说明', dataIndex: 'description', ellipsis: true, minWidth: 160, render: (v: string) => v || '-' },
|
||||||
|
{ title: '时间', dataIndex: 'createdAt', width: 170, render: (v: string) => formatDateTime(v) },
|
||||||
|
];
|
||||||
|
|
||||||
|
/* ── Tab 配置 ────────────────────────────────────────── */
|
||||||
const tabItems = [
|
const tabItems = [
|
||||||
{
|
{
|
||||||
key: 'members',
|
key: 'members',
|
||||||
@@ -313,14 +359,7 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
dataSource={members}
|
dataSource={members}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={membersLoading}
|
loading={membersLoading}
|
||||||
pagination={{
|
pagination={false}
|
||||||
current: membersPage,
|
|
||||||
pageSize: 20,
|
|
||||||
total: membersTotal,
|
|
||||||
onChange: (p) => setMembersPage(p),
|
|
||||||
showTotal: (t) => `共 ${t} 人`,
|
|
||||||
position: ['bottomLeft'],
|
|
||||||
}}
|
|
||||||
scroll={{ x: 800 }}
|
scroll={{ x: 800 }}
|
||||||
locale={{ emptyText: <Empty description="暂无成员" /> }}
|
locale={{ emptyText: <Empty description="暂无成员" /> }}
|
||||||
/>
|
/>
|
||||||
@@ -354,14 +393,12 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
allowClear
|
allowClear
|
||||||
/>
|
/>
|
||||||
<RangePicker
|
<RangePicker
|
||||||
value={creditFilterStart && creditFilterEnd ? [dayjs(creditFilterStart), dayjs(creditFilterEnd)] : undefined}
|
value={creditDateRange ? [dayjs(creditDateRange[0]), dayjs(creditDateRange[1])] : undefined}
|
||||||
onChange={(dates) => {
|
onChange={(dates) => {
|
||||||
if (dates && dates[0] && dates[1]) {
|
if (dates && dates[0] && dates[1]) {
|
||||||
setCreditFilterStart(dates[0].format('YYYY-MM-DD'));
|
setCreditDateRange([dates[0].format('YYYY-MM-DD'), dates[1].format('YYYY-MM-DD')]);
|
||||||
setCreditFilterEnd(dates[1].format('YYYY-MM-DD'));
|
|
||||||
} else {
|
} else {
|
||||||
setCreditFilterStart('');
|
setCreditDateRange(null);
|
||||||
setCreditFilterEnd('');
|
|
||||||
}
|
}
|
||||||
setCreditPage(1);
|
setCreditPage(1);
|
||||||
}}
|
}}
|
||||||
@@ -371,48 +408,18 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 汇总统计 */}
|
{/* 汇总统计 */}
|
||||||
<div style={{ marginBottom: 12, padding: '8px 12px', background: '#f8f9fc', borderRadius: 8, display: 'flex', gap: 20, flexWrap: 'wrap', fontSize: 13 }}>
|
<div style={{ marginBottom: 12, padding: '8px 16px', background: '#f8f9fc', borderRadius: 8, display: 'flex', gap: 24, flexWrap: 'wrap', fontSize: 13 }}>
|
||||||
<span>总充值:<strong style={{ color: '#16a34a' }}>+{creditSummary?.total_recharge ?? 0}</strong></span>
|
<span>总充值:<strong style={{ color: '#10b981', fontSize: 15 }}>+{creditSummary?.total_recharge ?? 0}</strong></span>
|
||||||
<span>总消费:<strong style={{ color: '#dc2626' }}>-{creditSummary?.total_consume ?? 0}</strong></span>
|
<span>总消费:<strong style={{ color: '#ef4444', fontSize: 15 }}>-{creditSummary?.total_consume ?? 0}</strong></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
size="small"
|
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={creditLoading}
|
loading={creditLoading}
|
||||||
dataSource={creditRecords}
|
dataSource={creditRecords}
|
||||||
pagination={{
|
pagination={false}
|
||||||
current: creditPage,
|
|
||||||
pageSize: 20,
|
|
||||||
total: creditTotal,
|
|
||||||
onChange: (p) => setCreditPage(p),
|
|
||||||
showTotal: (t) => `共 ${t} 条`,
|
|
||||||
position: ['bottomLeft'],
|
|
||||||
}}
|
|
||||||
scroll={{ x: 950 }}
|
scroll={{ x: 950 }}
|
||||||
columns={[
|
columns={creditColumns}
|
||||||
{ title: '用户名', key: 'username', width: 120, render: (_: any, r: any) => <Typography.Text strong>{r.username || '-'}</Typography.Text> },
|
|
||||||
{ title: '手机号', key: 'phone', width: 120, render: (_: any, r: any) => r.phone || '-' },
|
|
||||||
{
|
|
||||||
title: '类型', key: 'type', width: 100,
|
|
||||||
render: (_: any, r: any) => {
|
|
||||||
const colorMap: Record<string, string> = { recharge: 'green', consume: 'red', refund: 'orange', team_internal: 'blue' };
|
|
||||||
const labelMap: Record<string, string> = { recharge: '充值', consume: '消费', refund: '退款', team_internal: '团队内部' };
|
|
||||||
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="暂无积分记录" /> }}
|
locale={{ emptyText: <Empty description="暂无积分记录" /> }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -455,10 +462,14 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/* ── 渲染 ────────────────────────────────────────────── */
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 24, maxWidth: 1200, margin: '0 auto' }}>
|
<div style={{ padding: 24 }}>
|
||||||
<Card variant="outlined" style={{ borderRadius: 12, marginBottom: 16 }} loading={teamLoading}>
|
{/* 顶部团队信息 */}
|
||||||
{team && (
|
<div style={{ marginBottom: 24 }}>
|
||||||
|
{teamLoading ? (
|
||||||
|
<Typography.Text type="secondary">加载中...</Typography.Text>
|
||||||
|
) : team ? (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 12 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 12 }}>
|
||||||
<div>
|
<div>
|
||||||
<Typography.Title level={4} style={{ margin: 0 }}>{team.name}</Typography.Title>
|
<Typography.Title level={4} style={{ margin: 0 }}>{team.name}</Typography.Title>
|
||||||
@@ -466,12 +477,41 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Button icon={<ReloadOutlined />} onClick={() => { loadTeam(); loadMembers(); loadInvitations(); loadRequests(); loadCreditRecords(); }}>刷新</Button>
|
<Button icon={<ReloadOutlined />} onClick={() => { loadTeam(); loadMembers(); loadInvitations(); loadRequests(); loadCreditRecords(); }}>刷新</Button>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<Typography.Text type="secondary">无法获取团队信息</Typography.Text>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</div>
|
||||||
|
|
||||||
<Card variant="outlined" style={{ borderRadius: 12 }}>
|
{/* 标签页 */}
|
||||||
<Tabs items={tabItems} defaultActiveKey="members" />
|
<Tabs items={tabItems} defaultActiveKey="members" size="large" />
|
||||||
</Card>
|
|
||||||
|
{/* 成员列表分页(左下角) */}
|
||||||
|
{membersTotal > 0 && (
|
||||||
|
<div style={{ marginTop: 16, display: 'flex', justifyContent: 'flex-start' }}>
|
||||||
|
<Pagination
|
||||||
|
current={membersPage}
|
||||||
|
pageSize={20}
|
||||||
|
total={membersTotal}
|
||||||
|
onChange={(p) => setMembersPage(p)}
|
||||||
|
showTotal={(t) => `共 ${t} 人`}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 积分记录分页(左下角) */}
|
||||||
|
{creditTotal > 0 && (
|
||||||
|
<div style={{ marginTop: 16, display: 'flex', justifyContent: 'flex-start' }}>
|
||||||
|
<Pagination
|
||||||
|
current={creditPage}
|
||||||
|
pageSize={10}
|
||||||
|
total={creditTotal}
|
||||||
|
onChange={(p) => setCreditPage(p)}
|
||||||
|
showTotal={(t) => `共 ${t} 条`}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 调整积分弹窗 */}
|
{/* 调整积分弹窗 */}
|
||||||
<Modal
|
<Modal
|
||||||
@@ -485,9 +525,9 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Form form={creditForm} layout="vertical" style={{ marginTop: 16 }} initialValues={{ direction: 'increase' }}>
|
<Form form={creditForm} layout="vertical" style={{ marginTop: 16 }} initialValues={{ direction: 'increase' }}>
|
||||||
{/* 显示管理人当前积分 */}
|
{/* 显示管理人当前积分 */}
|
||||||
<div style={{ marginBottom: 16, padding: '8px 12px', background: '#f0f4ff', borderRadius: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{ marginBottom: 16, padding: '10px 16px', background: '#f0f4ff', borderRadius: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<Typography.Text type="secondary">我的当前积分</Typography.Text>
|
<Typography.Text type="secondary">我的当前积分</Typography.Text>
|
||||||
<Typography.Text strong style={{ fontSize: 18, color: '#6366f1' }}>
|
<Typography.Text strong style={{ fontSize: 20, color: '#6366f1' }}>
|
||||||
{useAuthStore.getState().user?.credits?.toFixed(2) ?? '0.00'}
|
{useAuthStore.getState().user?.credits?.toFixed(2) ?? '0.00'}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
@@ -504,17 +544,18 @@ const TeamManagementPage: React.FC = () => {
|
|||||||
{ required: true, message: '请输入积分数量' },
|
{ required: true, message: '请输入积分数量' },
|
||||||
{ type: 'number', min: 0.01, message: '必须大于 0' },
|
{ type: 'number', min: 0.01, message: '必须大于 0' },
|
||||||
{ type: 'number', max: 9999999, message: '单次不能超过 9999999' },
|
{ type: 'number', max: 9999999, message: '单次不能超过 9999999' },
|
||||||
{
|
|
||||||
validator: (_, value) => {
|
|
||||||
if (!value || value <= 0) return Promise.reject(new Error('请输入有效的正数'));
|
|
||||||
const decimals = String(value).split('.')[1];
|
|
||||||
if (decimals && decimals.length > 2) return Promise.reject(new Error('最多两位小数'));
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<InputNumber style={{ width: '100%' }} step={1} min={0.01} precision={2} placeholder="请输入正数积分数量" size="large" />
|
<InputNumber
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
step={1}
|
||||||
|
min={0.01}
|
||||||
|
max={9999999}
|
||||||
|
precision={2}
|
||||||
|
placeholder="请输入正数积分数量"
|
||||||
|
size="large"
|
||||||
|
formatter={(value) => value ? `${value}`.replace(/[^0-9.]/g, '') : ''}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="description" label="备注">
|
<Form.Item name="description" label="备注">
|
||||||
<Input.TextArea rows={2} maxLength={256} placeholder="选填,例如:活动奖励" />
|
<Input.TextArea rows={2} maxLength={256} placeholder="选填,例如:活动奖励" />
|
||||||
|
|||||||
Reference in New Issue
Block a user