1
This commit is contained in:
@@ -94,9 +94,12 @@ export async function getAdminStats(startDate?: string, endDate?: string): Promi
|
||||
return api.get(`/admin/stats${query ? `?${query}` : ''}`);
|
||||
}
|
||||
|
||||
export async function getAdminUsers(search?: string): Promise<AdminUser[]> {
|
||||
const q = search ? `?search=${encodeURIComponent(search)}` : '';
|
||||
return api.get(`/admin/users${q}`);
|
||||
export async function getAdminUsers(page = 1, pageSize = 20, search?: string): Promise<{ items: AdminUser[]; total: number }> {
|
||||
const params = new URLSearchParams();
|
||||
params.set('page', String(page));
|
||||
params.set('page_size', String(pageSize));
|
||||
if (search) params.set('search', search);
|
||||
return api.get(`/admin/users?${params.toString()}`);
|
||||
}
|
||||
|
||||
export async function adjustCredits(userId: string, amount: number, description: string): Promise<void> {
|
||||
@@ -245,20 +248,26 @@ export async function getPaymentStats(params?: {
|
||||
return api.get(url);
|
||||
}
|
||||
|
||||
export async function getAdminPaymentOrders(params?: { method?: string; status?: string }): Promise<{ items: any[] }> {
|
||||
export async function getAdminPaymentOrders(params?: { method?: string; status?: string; startDate?: string; endDate?: string; page?: number; pageSize?: number }): Promise<{ items: any[]; total: number }> {
|
||||
const qs = new URLSearchParams();
|
||||
if (params?.method) qs.set('method', params.method);
|
||||
if (params?.method) qs.set('payment_method', params.method);
|
||||
if (params?.status) qs.set('status', params.status);
|
||||
const suffix = qs.toString() ? `?${qs.toString()}` : '';
|
||||
return api.get(`/admin/payment-orders${suffix}`);
|
||||
if (params?.startDate) qs.set('start_date', params.startDate);
|
||||
if (params?.endDate) qs.set('end_date', params.endDate);
|
||||
if (params?.page) qs.set('page', String(params.page));
|
||||
if (params?.pageSize) qs.set('page_size', String(params.pageSize));
|
||||
return api.get(`/admin/payment-orders?${qs.toString()}`);
|
||||
}
|
||||
|
||||
export async function refundPaymentOrder(orderNo: string): Promise<void> {
|
||||
await api.post(`/admin/payment-orders/${orderNo}/refund`);
|
||||
}
|
||||
|
||||
export async function getAdminNotifications(): Promise<{ total: number; items: any[] }> {
|
||||
return api.get('/admin/notifications');
|
||||
export async function getAdminNotifications(page = 1, pageSize = 20): Promise<{ total: number; items: any[] }> {
|
||||
const params = new URLSearchParams();
|
||||
params.set('page', String(page));
|
||||
params.set('page_size', String(pageSize));
|
||||
return api.get(`/admin/notifications?${params.toString()}`);
|
||||
}
|
||||
|
||||
export async function createAdminNotification(data: { title: string; content: string; type: string; target_user_id?: string }): Promise<void> {
|
||||
|
||||
@@ -29,6 +29,8 @@ const AdminCreditRecords: React.FC = () => {
|
||||
const [records, setRecords] = useState<CreditRecord[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [typeFilter, setTypeFilter] = useState<string>('');
|
||||
const [userNameFilter, setUserNameFilter] = useState<string>('');
|
||||
const [dateRange, setDateRange] = useState<[dayjs.Dayjs | null, dayjs.Dayjs | null]>([null, null]);
|
||||
@@ -36,11 +38,13 @@ const AdminCreditRecords: React.FC = () => {
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const filters: { type?: string; user_name?: string; start_date?: string; end_date?: string } = {};
|
||||
const filters: { type?: string; user_name?: string; start_date?: string; end_date?: string; page?: number; page_size?: number } = {};
|
||||
if (typeFilter) filters.type = typeFilter;
|
||||
if (userNameFilter) filters.user_name = userNameFilter;
|
||||
if (dateRange[0]) filters.start_date = dateRange[0].format('YYYY-MM-DD');
|
||||
if (dateRange[1]) filters.end_date = dateRange[1].format('YYYY-MM-DD');
|
||||
filters.page = page;
|
||||
filters.page_size = pageSize;
|
||||
const res = await getCreditRecords(Object.keys(filters).length > 0 ? filters : undefined);
|
||||
setRecords(res.items || []);
|
||||
setTotal(res.total || 0);
|
||||
@@ -51,6 +55,13 @@ const AdminCreditRecords: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => { load(); }, [page, pageSize, typeFilter, userNameFilter, dateRange]);
|
||||
|
||||
const handlePageChange = (p: number, ps: number) => {
|
||||
setPage(p);
|
||||
setPageSize(ps);
|
||||
};
|
||||
|
||||
useEffect(() => { load(); }, []);
|
||||
|
||||
const handleSearch = () => {
|
||||
@@ -191,7 +202,14 @@ const AdminCreditRecords: React.FC = () => {
|
||||
dataSource={records}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 10, showTotal: (t) => `共 ${t} 条记录` }}
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize: pageSize,
|
||||
total: total,
|
||||
onChange: handlePageChange,
|
||||
showSizeChanger: true,
|
||||
showTotal: (t) => `共 ${t} 条记录`,
|
||||
}}
|
||||
scroll={{ x: 800 }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
@@ -38,12 +38,16 @@ const AdminNotificationManager: React.FC = () => {
|
||||
const [readModal, setReadModal] = useState<{ open: boolean; notifId: string; title: string }>({ open: false, notifId: '', title: '' });
|
||||
const [readUsers, setReadUsers] = useState<ReadUser[]>([]);
|
||||
const [readLoading, setReadLoading] = useState(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getAdminNotifications();
|
||||
const userList = await getAdminUsers();
|
||||
const res = await getAdminNotifications(page, pageSize);
|
||||
const userRes = await getAdminUsers(1, 1000);
|
||||
const userList = userRes.items || [];
|
||||
const userMap: Record<string, string> = {};
|
||||
userList.forEach((u: any) => { userMap[u.id] = u.username; });
|
||||
const items = (res.items || []).map((n: any) => ({
|
||||
@@ -56,6 +60,7 @@ const AdminNotificationManager: React.FC = () => {
|
||||
createdAt: n.createdAt,
|
||||
}));
|
||||
setNotifications(items);
|
||||
setTotal(res.total || 0);
|
||||
setUsers(userList.map((u: any) => ({ id: u.id, username: u.username })));
|
||||
} catch {
|
||||
message.error('加载通知列表失败');
|
||||
@@ -64,7 +69,12 @@ const AdminNotificationManager: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => { load(); }, []);
|
||||
useEffect(() => { load(); }, [page, pageSize]);
|
||||
|
||||
const handlePageChange = (p: number, ps: number) => {
|
||||
setPage(p);
|
||||
setPageSize(ps);
|
||||
};
|
||||
|
||||
const handleSend = async () => {
|
||||
try {
|
||||
@@ -161,7 +171,7 @@ const AdminNotificationManager: React.FC = () => {
|
||||
<Space>
|
||||
<BellOutlined style={{ fontSize: 18, color: '#6366f1' }} />
|
||||
<Typography.Text strong style={{ fontSize: 16 }}>消息推送管理</Typography.Text>
|
||||
<Tag color="purple">{notifications.length} 条消息</Tag>
|
||||
<Tag color="purple">共 {total} 条消息</Tag>
|
||||
</Space>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setModalOpen(true)}
|
||||
style={{ borderRadius: 8 }}>
|
||||
@@ -174,7 +184,14 @@ const AdminNotificationManager: React.FC = () => {
|
||||
dataSource={notifications}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 10, showTotal: (t) => `共 ${t} 条消息` }}
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize: pageSize,
|
||||
total: total,
|
||||
onChange: handlePageChange,
|
||||
showSizeChanger: true,
|
||||
showTotal: (t) => `共 ${t} 条消息`,
|
||||
}}
|
||||
scroll={{ x: 900 }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
@@ -6,7 +6,7 @@ import zhCN from 'antd/locale/zh_CN';
|
||||
import {
|
||||
DollarOutlined, CheckCircleOutlined, ClockCircleOutlined, CloseCircleOutlined, ReloadOutlined, UndoOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { getPaymentStats, refundPaymentOrder } from '../api';
|
||||
import { getPaymentStats, getAdminPaymentOrders, refundPaymentOrder } from '../api';
|
||||
import { formatDate } from '../utils/formatDate';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -16,6 +16,9 @@ const { RangePicker } = DatePicker;
|
||||
const AdminPaymentStats: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [stats, setStats] = useState<any>(null);
|
||||
const [orderPage, setOrderPage] = useState(1);
|
||||
const [orderPageSize, setOrderPageSize] = useState(10);
|
||||
const [orderTotal, setOrderTotal] = useState(0);
|
||||
const [filters, setFilters] = useState<{
|
||||
paymentMethod?: string;
|
||||
status?: string;
|
||||
@@ -29,8 +32,16 @@ const AdminPaymentStats: React.FC = () => {
|
||||
const load = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await getPaymentStats(filters);
|
||||
setStats(data);
|
||||
const [statsData, ordersData] = await Promise.all([
|
||||
getPaymentStats(filters),
|
||||
getAdminPaymentOrders({
|
||||
...filters,
|
||||
page: orderPage,
|
||||
pageSize: orderPageSize,
|
||||
}),
|
||||
]);
|
||||
setStats({ ...statsData, recent: ordersData.items || [] });
|
||||
setOrderTotal(ordersData.total);
|
||||
} catch {
|
||||
message.error('加载支付统计失败');
|
||||
} finally {
|
||||
@@ -38,7 +49,12 @@ const AdminPaymentStats: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => { load(); }, [filters]);
|
||||
useEffect(() => { load(); }, [filters, orderPage, orderPageSize]);
|
||||
|
||||
const handleOrderPageChange = (p: number, ps: number) => {
|
||||
setOrderPage(p);
|
||||
setOrderPageSize(ps);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setFilters({
|
||||
@@ -269,10 +285,17 @@ const AdminPaymentStats: React.FC = () => {
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={stats.recent}
|
||||
dataSource={stats.recent || []}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 10, size: 'small' }}
|
||||
pagination={{
|
||||
current: orderPage,
|
||||
pageSize: orderPageSize,
|
||||
total: orderTotal,
|
||||
onChange: handleOrderPageChange,
|
||||
showSizeChanger: true,
|
||||
showTotal: (t) => `共 ${t} 条`,
|
||||
}}
|
||||
scroll={{ x: 1000 }}
|
||||
size="middle"
|
||||
/>
|
||||
|
||||
@@ -25,16 +25,26 @@ const AdminUsers: React.FC = () => {
|
||||
const [createForm] = Form.useForm();
|
||||
const [resetPwdForm] = Form.useForm();
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await getAdminUsers(search || undefined);
|
||||
setUsers(data);
|
||||
const data = await getAdminUsers(page, pageSize, search || undefined);
|
||||
setUsers(data.items || []);
|
||||
setTotal(data.total || 0);
|
||||
} catch { /* auth error handled by client */ }
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => { load(); }, []);
|
||||
useEffect(() => { load(); }, [page, pageSize, search]);
|
||||
|
||||
const handlePageChange = (p: number, ps: number) => {
|
||||
setPage(p);
|
||||
setPageSize(ps);
|
||||
};
|
||||
|
||||
const handleSearch = () => load();
|
||||
|
||||
@@ -250,7 +260,14 @@ const AdminUsers: React.FC = () => {
|
||||
dataSource={filteredUsers}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 10, showTotal: (t) => `共 ${t} 个用户` }}
|
||||
pagination={{
|
||||
current: page,
|
||||
pageSize: pageSize,
|
||||
total: total,
|
||||
onChange: handlePageChange,
|
||||
showSizeChanger: true,
|
||||
showTotal: (t) => `共 ${t} 个用户`,
|
||||
}}
|
||||
scroll={{ x: 1000 }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user