解决app/api代码合并冲突
This commit is contained in:
@@ -11,6 +11,7 @@ import AdminSettings from './pages/AdminSettings';
|
||||
import AdminNotificationManager from './pages/AdminNotificationManager';
|
||||
import AdminCreditRecords from './pages/AdminCreditRecords';
|
||||
import AdminPaymentConfig from './pages/AdminPaymentConfig';
|
||||
import AdminPaymentStats from './pages/AdminPaymentStats';
|
||||
import AdminIndustries from './pages/AdminIndustries';
|
||||
import AdminVideoEngines from './pages/AdminVideoEngines';
|
||||
import AdminImageEngines from './pages/AdminImageEngines';
|
||||
@@ -76,6 +77,7 @@ const App = () => {
|
||||
<Route path="menu-configs" element={<AdminMenuConfig />} />
|
||||
<Route path="recharge-packages" element={<AdminRechargePackages />} />
|
||||
<Route path="payment" element={<AdminPaymentConfig />} />
|
||||
<Route path="payment-stats" element={<AdminPaymentStats />} />
|
||||
<Route path="settings" element={<AdminSettings />} />
|
||||
<Route path="notifications" element={<AdminNotificationManager />} />
|
||||
<Route path="oauthapp-list" element={<AdminOauthAppList />} />
|
||||
|
||||
@@ -207,6 +207,43 @@ export async function updatePaymentConfig(id: string, value: string): Promise<vo
|
||||
await api.put(`/admin/payment-configs/${id}`, { value });
|
||||
}
|
||||
|
||||
export async function batchUpdatePaymentConfigs(configs: Record<string, string>): Promise<void> {
|
||||
await api.put('/admin/payment-configs/batch', configs);
|
||||
}
|
||||
|
||||
export async function getPaymentStats(params?: {
|
||||
paymentMethod?: string;
|
||||
status?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}): Promise<{
|
||||
byStatus: Record<string, { count: number; amount: number }>;
|
||||
today: { paidCount: number; paidAmount: number };
|
||||
month: { paidCount: number; paidAmount: number };
|
||||
recent: any[];
|
||||
}> {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.paymentMethod) searchParams.set('payment_method', params.paymentMethod);
|
||||
if (params?.status) searchParams.set('status', params.status);
|
||||
if (params?.startDate) searchParams.set('start_date', params.startDate);
|
||||
if (params?.endDate) searchParams.set('end_date', params.endDate);
|
||||
const queryString = searchParams.toString();
|
||||
const url = queryString ? `/admin/payment-stats?${queryString}` : '/admin/payment-stats';
|
||||
return api.get(url);
|
||||
}
|
||||
|
||||
export async function getAdminPaymentOrders(params?: { method?: string; status?: string }): Promise<{ items: any[] }> {
|
||||
const qs = new URLSearchParams();
|
||||
if (params?.method) qs.set('method', params.method);
|
||||
if (params?.status) qs.set('status', params.status);
|
||||
const suffix = qs.toString() ? `?${qs.toString()}` : '';
|
||||
return api.get(`/admin/payment-orders${suffix}`);
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -1,32 +1,25 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Button, Card, Form, Input, message, Switch, Typography,
|
||||
Button, Card, Form, Input, message, Switch, Typography, InputNumber,
|
||||
} from 'antd';
|
||||
import {
|
||||
SaveOutlined, WechatOutlined, AlipayCircleOutlined, DollarOutlined,
|
||||
SaveOutlined, WechatOutlined, AlipayCircleOutlined, DollarOutlined, ClockCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { getPaymentConfigs, updatePaymentConfig } from '../api';
|
||||
|
||||
interface PaymentConfig {
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
}
|
||||
import { getPaymentConfigs, batchUpdatePaymentConfigs } from '../api';
|
||||
|
||||
const AdminPaymentConfig: React.FC = () => {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [configs, setConfigs] = useState<PaymentConfig[]>([]);
|
||||
const [wechatEnabled, setWechatEnabled] = useState(false);
|
||||
const [alipayEnabled, setAlipayEnabled] = useState(false);
|
||||
const [mockMode, setMockMode] = useState(false);
|
||||
const [orderTimeout, setOrderTimeout] = useState(180);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
const data = await getPaymentConfigs();
|
||||
setConfigs(data);
|
||||
const map: Record<string, string> = {};
|
||||
data.forEach((c: PaymentConfig) => { map[c.key] = c.value; });
|
||||
data.forEach((c: any) => { map[c.key] = c.value; });
|
||||
form.setFieldsValue({
|
||||
wechat_mch_id: map['payment_wechat_mch_id'] || '',
|
||||
wechat_api_key: map['payment_wechat_api_key'] || '',
|
||||
@@ -36,9 +29,13 @@ const AdminPaymentConfig: React.FC = () => {
|
||||
alipay_private_key: map['payment_alipay_private_key'] || '',
|
||||
alipay_public_key: map['payment_alipay_public_key'] || '',
|
||||
alipay_notify_url: map['payment_alipay_notify_url'] || '',
|
||||
alipay_gateway: map['payment_alipay_gateway'] || '',
|
||||
order_timeout: map['payment_order_timeout'] || '180',
|
||||
});
|
||||
setWechatEnabled(map['payment_wechat_enabled'] === 'true');
|
||||
setAlipayEnabled(map['payment_alipay_enabled'] === 'true');
|
||||
setMockMode(map['payment_mock'] === 'true');
|
||||
setOrderTimeout(parseInt(map['payment_order_timeout'] || '180', 10));
|
||||
} catch {
|
||||
message.error('加载支付配置失败');
|
||||
}
|
||||
@@ -50,24 +47,21 @@ const AdminPaymentConfig: React.FC = () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
setSaving(true);
|
||||
const updates: [string, string][] = [
|
||||
['payment_wechat_enabled', String(wechatEnabled)],
|
||||
['payment_wechat_mch_id', values.wechat_mch_id || ''],
|
||||
['payment_wechat_api_key', values.wechat_api_key || ''],
|
||||
['payment_wechat_cert_path', values.wechat_cert_path || ''],
|
||||
['payment_wechat_notify_url', values.wechat_notify_url || ''],
|
||||
['payment_alipay_enabled', String(alipayEnabled)],
|
||||
['payment_alipay_app_id', values.alipay_app_id || ''],
|
||||
['payment_alipay_private_key', values.alipay_private_key || ''],
|
||||
['payment_alipay_public_key', values.alipay_public_key || ''],
|
||||
['payment_alipay_notify_url', values.alipay_notify_url || ''],
|
||||
];
|
||||
for (const [key, value] of updates) {
|
||||
const cfg = configs.find(c => c.key === key);
|
||||
if (cfg) {
|
||||
await updatePaymentConfig(cfg.id, value);
|
||||
}
|
||||
}
|
||||
await batchUpdatePaymentConfigs({
|
||||
payment_mock: String(mockMode),
|
||||
payment_wechat_enabled: String(wechatEnabled),
|
||||
payment_wechat_mch_id: values.wechat_mch_id || '',
|
||||
payment_wechat_api_key: values.wechat_api_key || '',
|
||||
payment_wechat_cert_path: values.wechat_cert_path || '',
|
||||
payment_wechat_notify_url: values.wechat_notify_url || '',
|
||||
payment_alipay_enabled: String(alipayEnabled),
|
||||
payment_alipay_app_id: values.alipay_app_id || '',
|
||||
payment_alipay_private_key: values.alipay_private_key || '',
|
||||
payment_alipay_public_key: values.alipay_public_key || '',
|
||||
payment_alipay_notify_url: values.alipay_notify_url || '',
|
||||
payment_alipay_gateway: values.alipay_gateway || '',
|
||||
payment_order_timeout: String(values.order_timeout || 180),
|
||||
});
|
||||
message.success('支付配置已保存');
|
||||
load();
|
||||
} catch {
|
||||
@@ -79,6 +73,61 @@ const AdminPaymentConfig: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 720 }}>
|
||||
{/* 通用设置 */}
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5', marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{
|
||||
width: 44, height: 44, borderRadius: 10,
|
||||
background: 'rgba(99,102,241,0.08)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 22, color: '#6366f1',
|
||||
}}><ClockCircleOutlined /></div>
|
||||
<div>
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>通用设置</Typography.Title>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>订单超时和测试模式配置</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="order_timeout"
|
||||
label={<span style={{ fontWeight: 500 }}>订单超时时间</span>}
|
||||
extra="订单创建后超过此时间未支付将自动取消(秒)"
|
||||
>
|
||||
<InputNumber
|
||||
min={30}
|
||||
max={86400}
|
||||
placeholder="180"
|
||||
style={{ width: '100%' }}
|
||||
size="large"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
{/* Mock Mode Toggle */}
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5', marginBottom: 16, background: mockMode ? '#fff7e6' : '#fafbff' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<div style={{
|
||||
width: 44, height: 44, borderRadius: 10,
|
||||
background: mockMode ? 'rgba(245,158,11,0.15)' : 'rgba(99,102,241,0.08)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 22, color: mockMode ? '#f59e0b' : '#6366f1',
|
||||
}}><DollarOutlined /></div>
|
||||
<div>
|
||||
<Typography.Title level={5} style={{ margin: 0 }}>模拟支付模式</Typography.Title>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{mockMode ? '⚠️ 开启后所有充值会直接成功(仅用于测试)' : '关闭 - 使用真实支付渠道'}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
<Switch checked={mockMode} onChange={setMockMode} checkedChildren="已开启" unCheckedChildren="已关闭" />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* WeChat Pay */}
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5', marginBottom: 16 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 }}>
|
||||
@@ -141,7 +190,10 @@ const AdminPaymentConfig: React.FC = () => {
|
||||
<Form.Item name="alipay_public_key" label="支付宝公钥">
|
||||
<Input.TextArea rows={3} placeholder="支付宝公钥" disabled={!alipayEnabled} />
|
||||
</Form.Item>
|
||||
<Form.Item name="alipay_notify_url" label="回调地址">
|
||||
<Form.Item name="alipay_gateway" label="网关地址" extra="正式环境: https://openapi.alipay.com/gateway.do 沙箱环境: https://openapi-sandbox.dl.alipaydev.com/gateway.do">
|
||||
<Input placeholder="https://openapi.alipay.com/gateway.do" size="large" disabled={!alipayEnabled} />
|
||||
</Form.Item>
|
||||
<Form.Item name="alipay_notify_url" label="回调地址" extra="用户支付成功后,支付宝会主动通知此地址,服务器收到通知后给用户加积分">
|
||||
<Input placeholder="https://yourdomain.com/api/payments/alipay/callback" size="large" disabled={!alipayEnabled} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Card, Col, Row, Space, Table, Tag, Typography, Statistic, message, Select, DatePicker, Button, ConfigProvider, Popconfirm
|
||||
} from 'antd';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import {
|
||||
DollarOutlined, CheckCircleOutlined, ClockCircleOutlined, CloseCircleOutlined, ReloadOutlined, UndoOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { getPaymentStats, refundPaymentOrder } from '../api';
|
||||
import { formatDate } from '../utils/formatDate';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const { Option } = Select;
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
const AdminPaymentStats: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [stats, setStats] = useState<any>(null);
|
||||
const [filters, setFilters] = useState<{
|
||||
paymentMethod?: string;
|
||||
status?: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}>({
|
||||
startDate: dayjs().format('YYYY-MM-DD'),
|
||||
endDate: dayjs().format('YYYY-MM-DD'),
|
||||
});
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await getPaymentStats(filters);
|
||||
setStats(data);
|
||||
} catch {
|
||||
message.error('加载支付统计失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => { load(); }, [filters]);
|
||||
|
||||
const handleReset = () => {
|
||||
setFilters({
|
||||
startDate: dayjs().format('YYYY-MM-DD'),
|
||||
endDate: dayjs().format('YYYY-MM-DD'),
|
||||
});
|
||||
};
|
||||
|
||||
const handleRefund = async (orderNo: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await refundPaymentOrder(orderNo);
|
||||
message.success('退款成功');
|
||||
await load();
|
||||
} catch (e: any) {
|
||||
message.error(e?.response?.data?.detail || '退款失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDateChange = (dates: any) => {
|
||||
if (dates && dates.length === 2) {
|
||||
setFilters(prev => ({
|
||||
...prev,
|
||||
startDate: dates[0].format('YYYY-MM-DD'),
|
||||
endDate: dates[1].format('YYYY-MM-DD'),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const statusConfig: Record<string, { color: string; label: string; icon: React.ReactNode }> = {
|
||||
paid: { color: 'green', label: '已支付', icon: <CheckCircleOutlined /> },
|
||||
pending: { color: 'gold', label: '待支付', icon: <ClockCircleOutlined /> },
|
||||
cancelled: { color: 'default', label: '已取消', icon: <CloseCircleOutlined /> },
|
||||
refunded: { color: 'red', label: '已退款', icon: <UndoOutlined /> },
|
||||
};
|
||||
|
||||
const methodConfig: Record<string, { color: string; label: string }> = {
|
||||
alipay: { color: 'blue', label: '支付宝' },
|
||||
wechat: { color: 'green', label: '微信' },
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ title: '订单号', dataIndex: 'orderNo', key: 'orderNo', width: 200 },
|
||||
{ title: '用户', dataIndex: 'username', key: 'username', width: 120 },
|
||||
{
|
||||
title: '支付方式', dataIndex: 'paymentMethod', key: 'paymentMethod', width: 100,
|
||||
render: (m: string) => {
|
||||
const c = methodConfig[m] || { color: 'default', label: m };
|
||||
return <Tag color={c.color}>{c.label}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '金额', dataIndex: 'amount', key: 'amount', width: 100,
|
||||
render: (a: number) => <Typography.Text strong style={{ color: '#10b981' }}>¥{a.toFixed(2)}</Typography.Text>,
|
||||
},
|
||||
{ title: '积分', dataIndex: 'credits', key: 'credits', width: 80 },
|
||||
{
|
||||
title: '状态', dataIndex: 'status', key: 'status', width: 100,
|
||||
render: (s: string) => {
|
||||
const c = statusConfig[s] || { color: 'default', label: s, icon: null };
|
||||
return <Tag color={c.color} icon={c.icon}>{c.label}</Tag>;
|
||||
},
|
||||
},
|
||||
{ title: '支付宝交易号', dataIndex: 'tradeNo', key: 'tradeNo', width: 200, render: (v: string) => v || '-' },
|
||||
{
|
||||
title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', width: 160,
|
||||
render: (d: string) => <span className="date-display" style={{ color: '#94a3b8' }}>{d ? formatDate(d) : '-'}</span>,
|
||||
},
|
||||
{
|
||||
title: '支付时间', dataIndex: 'paidAt', key: 'paidAt', width: 160,
|
||||
render: (d: string) => <span className="date-display" style={{ color: '#94a3b8' }}>{d ? formatDate(d) : '-'}</span>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 120,
|
||||
render: (_: any, record: any) => {
|
||||
if (record.status === 'paid') {
|
||||
return (
|
||||
<Popconfirm
|
||||
title="确认要退款该订单吗?"
|
||||
description="退款后积分会扣除,金额会原路返回"
|
||||
onConfirm={() => handleRefund(record.orderNo)}
|
||||
okText="确认"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button type="link" danger size="small" icon={<UndoOutlined />}>
|
||||
退款
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (!stats) {
|
||||
return <div style={{ padding: 24, color: '#94a3b8' }}>加载中…</div>;
|
||||
}
|
||||
|
||||
const paidInfo = stats.byStatus?.paid || { count: 0, amount: 0 };
|
||||
const pendingInfo = stats.byStatus?.pending || { count: 0, amount: 0 };
|
||||
const cancelledInfo = stats.byStatus?.cancelled || { count: 0, amount: 0 };
|
||||
const refundedInfo = stats.byStatus?.refunded || { count: 0, amount: 0 };
|
||||
const totalOrders = paidInfo.count + pendingInfo.count + cancelledInfo.count + refundedInfo.count;
|
||||
const monthInfo = stats.month || { count: 0, amount: 0 };
|
||||
|
||||
return (
|
||||
<ConfigProvider locale={zhCN}>
|
||||
<div>
|
||||
{/* Summary cards */}
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||
<Col xs={24} sm={12} lg={12}>
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
||||
<Statistic
|
||||
title="今日收入"
|
||||
value={stats.today.paidAmount}
|
||||
precision={2}
|
||||
prefix={<DollarOutlined style={{ color: '#10b981' }} />}
|
||||
suffix="元"
|
||||
valueStyle={{ color: '#10b981', fontWeight: 700 }}
|
||||
/>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{stats.today.paidCount} 笔订单
|
||||
</Typography.Text>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} lg={12}>
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
||||
<Statistic
|
||||
title="本月累计"
|
||||
value={monthInfo.paidAmount}
|
||||
precision={2}
|
||||
prefix={<DollarOutlined style={{ color: '#6366f1' }} />}
|
||||
suffix="元"
|
||||
valueStyle={{ color: '#6366f1', fontWeight: 700 }}
|
||||
/>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{monthInfo.paidCount} 笔订单
|
||||
</Typography.Text>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* Status breakdown */}
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5', marginBottom: 16 }}
|
||||
title={<Space><DollarOutlined />订单状态分布</Space>}>
|
||||
<Row gutter={16}>
|
||||
{['paid', 'pending', 'cancelled', 'refunded'].map(s => {
|
||||
const info = stats.byStatus?.[s] || { count: 0, amount: 0 };
|
||||
const c = statusConfig[s];
|
||||
const pct = totalOrders > 0 ? ((info.count / totalOrders) * 100).toFixed(1) : '0.0';
|
||||
return (
|
||||
<Col span={6} key={s}>
|
||||
<div style={{
|
||||
padding: 16, borderRadius: 10,
|
||||
background: '#fafbff', border: '1px solid #f0f0f5',
|
||||
}}>
|
||||
<Space>
|
||||
<Tag color={c.color} icon={c.icon}>{c.label}</Tag>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>{pct}%</Typography.Text>
|
||||
</Space>
|
||||
<div style={{ marginTop: 8, fontSize: 20, fontWeight: 700 }}>
|
||||
{info.count} <span style={{ fontSize: 13, color: '#94a3b8', fontWeight: 400 }}>笔</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 13, color: '#64748b', marginTop: 4 }}>
|
||||
¥{info.amount.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{/* Recent orders table */}
|
||||
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}
|
||||
title={<Space><DollarOutlined />订单列表</Space>}>
|
||||
{/* Filters */}
|
||||
<Row gutter={[16, 16]} align="middle" style={{ marginBottom: 24 }}>
|
||||
<Col xs={24} sm={12} md={6}>
|
||||
<span style={{ marginRight: 8 }}>支付方式:</span>
|
||||
<Select
|
||||
placeholder="全部"
|
||||
allowClear
|
||||
style={{ width: 150 }}
|
||||
value={filters.paymentMethod}
|
||||
onChange={(value) => setFilters(prev => ({ ...prev, paymentMethod: value }))}
|
||||
>
|
||||
<Option value="alipay">支付宝</Option>
|
||||
<Option value="wechat">微信</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={6}>
|
||||
<span style={{ marginRight: 8 }}>状态:</span>
|
||||
<Select
|
||||
placeholder="全部"
|
||||
allowClear
|
||||
style={{ width: 150 }}
|
||||
value={filters.status}
|
||||
onChange={(value) => setFilters(prev => ({ ...prev, status: value }))}
|
||||
>
|
||||
<Option value="paid">已支付</Option>
|
||||
<Option value="pending">待支付</Option>
|
||||
<Option value="cancelled">已取消</Option>
|
||||
<Option value="refunded">已退款</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={8}>
|
||||
<span style={{ marginRight: 8 }}>日期范围:</span>
|
||||
<RangePicker
|
||||
value={[
|
||||
dayjs(filters.startDate),
|
||||
dayjs(filters.endDate),
|
||||
]}
|
||||
onChange={handleDateChange}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={12} md={4}>
|
||||
<Button icon={<ReloadOutlined />} onClick={handleReset}>
|
||||
重置
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={stats.recent}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 10, size: 'small' }}
|
||||
scroll={{ x: 1000 }}
|
||||
size="middle"
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminPaymentStats;
|
||||
@@ -117,6 +117,29 @@ export interface AdminStats {
|
||||
creditsConsumedToday: number;
|
||||
}
|
||||
|
||||
export interface PaymentStats {
|
||||
byStatus: Record<string, { count: number; amount: number }>;
|
||||
today: { paidCount: number; paidAmount: number };
|
||||
month: { paidCount: number; paidAmount: number };
|
||||
recent: PaymentOrder[];
|
||||
}
|
||||
|
||||
export interface PaymentOrder {
|
||||
id: string;
|
||||
orderNo: string;
|
||||
userId: string;
|
||||
username?: string;
|
||||
amount: number;
|
||||
credits: number;
|
||||
paymentMethod: string;
|
||||
status: string;
|
||||
tradeNo?: string;
|
||||
paidAt?: string;
|
||||
createdAt: string;
|
||||
refundedAt?: string;
|
||||
refundAmount?: number;
|
||||
}
|
||||
|
||||
export interface ModelConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
Reference in New Issue
Block a user