2026-03-27 16:56:06 +08:00
|
|
|
|
import {
|
|
|
|
|
|
useState,
|
|
|
|
|
|
useEffect,
|
|
|
|
|
|
useMemo,
|
|
|
|
|
|
Table,
|
|
|
|
|
|
Button,
|
|
|
|
|
|
Input,
|
|
|
|
|
|
Select,
|
|
|
|
|
|
DatePicker,
|
|
|
|
|
|
message,
|
|
|
|
|
|
Card,
|
|
|
|
|
|
Row,
|
|
|
|
|
|
Col,
|
|
|
|
|
|
Tag,
|
|
|
|
|
|
Space,
|
|
|
|
|
|
Modal,
|
|
|
|
|
|
Drawer,
|
|
|
|
|
|
Descriptions,
|
|
|
|
|
|
Typography,
|
|
|
|
|
|
Alert,
|
|
|
|
|
|
Statistic,
|
|
|
|
|
|
Tooltip,
|
|
|
|
|
|
SearchOutlined,
|
|
|
|
|
|
FilterOutlined,
|
|
|
|
|
|
ExportOutlined,
|
|
|
|
|
|
DownloadOutlined,
|
|
|
|
|
|
EyeOutlined,
|
|
|
|
|
|
CheckCircleOutlined,
|
|
|
|
|
|
CloseCircleOutlined,
|
|
|
|
|
|
SyncOutlined,
|
|
|
|
|
|
DollarOutlined,
|
|
|
|
|
|
ArrowUpOutlined,
|
|
|
|
|
|
ArrowDownOutlined,
|
|
|
|
|
|
ReloadOutlined,
|
|
|
|
|
|
LineChart,
|
|
|
|
|
|
Line,
|
|
|
|
|
|
Bar,
|
|
|
|
|
|
RechartsPie,
|
|
|
|
|
|
ResponsiveContainer,
|
|
|
|
|
|
Legend,
|
|
|
|
|
|
XAxis,
|
|
|
|
|
|
YAxis,
|
|
|
|
|
|
CartesianGrid,
|
|
|
|
|
|
Cell,
|
|
|
|
|
|
useNavigate,
|
|
|
|
|
|
Option,
|
|
|
|
|
|
RangePicker,
|
|
|
|
|
|
Search,
|
|
|
|
|
|
Title,
|
|
|
|
|
|
Text,
|
|
|
|
|
|
FC,
|
|
|
|
|
|
} from '@/imports';
|
2026-03-23 12:41:35 +08:00
|
|
|
|
import moment from 'moment';
|
2026-03-27 16:56:06 +08:00
|
|
|
|
import { financeDataSource } from '@/services/financeDataSource';
|
|
|
|
|
|
import type { Transaction } from '@/services/financeDataSource';
|
2026-03-18 19:12:38 +08:00
|
|
|
|
|
2026-03-27 16:56:06 +08:00
|
|
|
|
const Transactions: FC = () => {
|
2026-03-18 19:12:38 +08:00
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
2026-03-23 12:41:35 +08:00
|
|
|
|
const [selectedRows, setSelectedRows] = useState<Transaction[]>([]);
|
|
|
|
|
|
const [detailDrawerVisible, setDetailDrawerVisible] = useState(false);
|
|
|
|
|
|
const [currentTransaction, setCurrentTransaction] = useState<Transaction | null>(null);
|
2026-03-18 19:12:38 +08:00
|
|
|
|
const [filters, setFilters] = useState({
|
|
|
|
|
|
type: '',
|
|
|
|
|
|
category: '',
|
|
|
|
|
|
status: '',
|
|
|
|
|
|
search: '',
|
|
|
|
|
|
dateRange: null as any,
|
2026-03-23 12:41:35 +08:00
|
|
|
|
minAmount: null as number | null,
|
|
|
|
|
|
maxAmount: null as number | null,
|
2026-03-18 19:12:38 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-03-27 16:56:06 +08:00
|
|
|
|
const fetchTransactions = async (filterParams?: any) => {
|
2026-03-18 19:12:38 +08:00
|
|
|
|
setLoading(true);
|
2026-03-19 01:39:34 +08:00
|
|
|
|
try {
|
2026-03-27 16:56:06 +08:00
|
|
|
|
// 转换过滤参数以匹配 financeDataSource 期望的格式
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
type: filterParams?.type || filters.type,
|
|
|
|
|
|
startDate: filterParams?.dateRange?.[0]?.format('YYYY-MM-DD'),
|
|
|
|
|
|
endDate: filterParams?.dateRange?.[1]?.format('YYYY-MM-DD'),
|
|
|
|
|
|
};
|
|
|
|
|
|
const data = await financeDataSource.fetchTransactions(params);
|
2026-03-19 01:39:34 +08:00
|
|
|
|
setTransactions(data);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
message.error('Failed to load transactions');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-03-18 19:12:38 +08:00
|
|
|
|
|
2026-03-27 16:56:06 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
fetchTransactions();
|
|
|
|
|
|
}, []); // 只在组件挂载时执行一次
|
|
|
|
|
|
|
2026-03-18 19:12:38 +08:00
|
|
|
|
const handleExport = () => {
|
|
|
|
|
|
message.success('交易记录已导出');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-23 12:41:35 +08:00
|
|
|
|
const handleBatchExport = () => {
|
|
|
|
|
|
if (selectedRows.length === 0) {
|
|
|
|
|
|
message.warning('请先选择要导出的交易');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
message.success(`成功导出 ${selectedRows.length} 条交易记录`);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleViewDetail = (record: Transaction) => {
|
|
|
|
|
|
setCurrentTransaction(record);
|
|
|
|
|
|
setDetailDrawerVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleRefresh = () => {
|
|
|
|
|
|
fetchTransactions();
|
|
|
|
|
|
message.success('数据已刷新');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const stats = useMemo(() => {
|
|
|
|
|
|
const income = transactions.filter(t => t.type === 'income').reduce((sum, t) => sum + t.amount, 0);
|
|
|
|
|
|
const expense = transactions.filter(t => t.type === 'expense').reduce((sum, t) => sum + t.amount, 0);
|
|
|
|
|
|
const pending = transactions.filter(t => t.status === 'pending').reduce((sum, t) => sum + t.amount, 0);
|
|
|
|
|
|
return {
|
|
|
|
|
|
income,
|
|
|
|
|
|
expense,
|
|
|
|
|
|
profit: income - expense,
|
|
|
|
|
|
pending,
|
|
|
|
|
|
total: transactions.length,
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [transactions]);
|
|
|
|
|
|
|
|
|
|
|
|
const trendData = useMemo(() => {
|
|
|
|
|
|
const trendData = Array.from({ length: 7 }, (_, i) => {
|
|
|
|
|
|
const date = moment().subtract(i, 'days').format('YYYY-MM-DD');
|
|
|
|
|
|
const dayTransactions = transactions.filter(t => moment(t.createdAt).format('YYYY-MM-DD') === date);
|
|
|
|
|
|
return {
|
|
|
|
|
|
date,
|
|
|
|
|
|
income: dayTransactions.filter(t => t.type === 'income').reduce((sum, t) => sum + t.amount, 0),
|
|
|
|
|
|
expense: dayTransactions.filter(t => t.type === 'expense').reduce((sum, t) => sum + t.amount, 0),
|
|
|
|
|
|
};
|
|
|
|
|
|
}).reverse();
|
|
|
|
|
|
return trendData;
|
|
|
|
|
|
}, [transactions]);
|
|
|
|
|
|
|
|
|
|
|
|
const categoryData = useMemo(() => {
|
|
|
|
|
|
const categoryMap: Record<string, number> = {};
|
|
|
|
|
|
transactions.filter(t => t.type === 'expense').forEach(t => {
|
|
|
|
|
|
categoryMap[t.category] = (categoryMap[t.category] || 0) + t.amount;
|
|
|
|
|
|
});
|
|
|
|
|
|
return Object.entries(categoryMap).map(([name, value]) => ({ name, value }));
|
|
|
|
|
|
}, [transactions]);
|
|
|
|
|
|
|
2026-03-18 19:12:38 +08:00
|
|
|
|
const columns = [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '交易ID',
|
|
|
|
|
|
dataIndex: 'transactionId',
|
|
|
|
|
|
key: 'transactionId',
|
2026-03-23 12:41:35 +08:00
|
|
|
|
render: (text: string) => <Text strong>{text}</Text>,
|
2026-03-18 19:12:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '金额',
|
|
|
|
|
|
dataIndex: 'amount',
|
|
|
|
|
|
key: 'amount',
|
2026-03-23 12:41:35 +08:00
|
|
|
|
sorter: (a: Transaction, b: Transaction) => a.amount - b.amount,
|
2026-03-18 19:12:38 +08:00
|
|
|
|
render: (amount: number, record: Transaction) => (
|
2026-03-23 12:41:35 +08:00
|
|
|
|
<Text style={{ color: record.type === 'income' ? '#52c41a' : '#ff4d4f', fontWeight: 500 }}>
|
2026-03-25 13:46:26 +08:00
|
|
|
|
{record.type === 'income' ? '+' : '-'}${(amount || 0).toFixed(2)}
|
2026-03-23 12:41:35 +08:00
|
|
|
|
</Text>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '类型',
|
|
|
|
|
|
dataIndex: 'type',
|
|
|
|
|
|
key: 'type',
|
2026-03-23 12:41:35 +08:00
|
|
|
|
filters: [
|
|
|
|
|
|
{ text: '收入', value: 'income' },
|
|
|
|
|
|
{ text: '支出', value: 'expense' },
|
2026-03-25 13:46:26 +08:00
|
|
|
|
{ text: '退款', value: 'refund' },
|
|
|
|
|
|
{ text: '费用', value: 'fee' },
|
2026-03-23 12:41:35 +08:00
|
|
|
|
],
|
2026-03-18 19:12:38 +08:00
|
|
|
|
render: (type: string) => {
|
2026-03-23 12:41:35 +08:00
|
|
|
|
const typeConfig = {
|
|
|
|
|
|
income: { text: '收入', color: 'success' },
|
|
|
|
|
|
expense: { text: '支出', color: 'error' },
|
2026-03-25 13:46:26 +08:00
|
|
|
|
refund: { text: '退款', color: 'warning' },
|
|
|
|
|
|
fee: { text: '费用', color: 'processing' },
|
2026-03-23 12:41:35 +08:00
|
|
|
|
};
|
2026-03-25 13:46:26 +08:00
|
|
|
|
const config = typeConfig[type as keyof typeof typeConfig] || { text: type, color: 'default' };
|
2026-03-23 12:41:35 +08:00
|
|
|
|
return <Tag color={config.color}>{config.text}</Tag>;
|
2026-03-18 19:12:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '分类',
|
|
|
|
|
|
dataIndex: 'category',
|
|
|
|
|
|
key: 'category',
|
2026-03-23 12:41:35 +08:00
|
|
|
|
filters: [
|
|
|
|
|
|
{ text: '销售', value: 'Sales' },
|
|
|
|
|
|
{ text: '供应商', value: 'Suppliers' },
|
|
|
|
|
|
{ text: '营销', value: 'Marketing' },
|
|
|
|
|
|
{ text: '物流', value: 'Shipping' },
|
|
|
|
|
|
{ text: '平台费用', value: 'Platform' },
|
|
|
|
|
|
{ text: '退款', value: 'Refund' },
|
|
|
|
|
|
],
|
2026-03-18 19:12:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '描述',
|
|
|
|
|
|
dataIndex: 'description',
|
|
|
|
|
|
key: 'description',
|
2026-03-23 12:41:35 +08:00
|
|
|
|
ellipsis: true,
|
2026-03-18 19:12:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '日期',
|
|
|
|
|
|
dataIndex: 'date',
|
|
|
|
|
|
key: 'date',
|
2026-03-23 12:41:35 +08:00
|
|
|
|
sorter: (a: Transaction, b: Transaction) => moment(a.createdAt).unix() - moment(b.createdAt).unix(),
|
|
|
|
|
|
render: (date: string) => moment(date).format('YYYY-MM-DD HH:mm'),
|
2026-03-18 19:12:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: '状态',
|
|
|
|
|
|
dataIndex: 'status',
|
|
|
|
|
|
key: 'status',
|
2026-03-23 12:41:35 +08:00
|
|
|
|
filters: [
|
|
|
|
|
|
{ text: '已完成', value: 'completed' },
|
|
|
|
|
|
{ text: '待处理', value: 'pending' },
|
|
|
|
|
|
{ text: '失败', value: 'failed' },
|
|
|
|
|
|
],
|
2026-03-18 19:12:38 +08:00
|
|
|
|
render: (status: string) => {
|
2026-03-23 12:41:35 +08:00
|
|
|
|
const statusConfig = {
|
|
|
|
|
|
completed: { text: '已完成', color: 'success', icon: <CheckCircleOutlined /> },
|
|
|
|
|
|
pending: { text: '待处理', color: 'warning', icon: <SyncOutlined spin /> },
|
|
|
|
|
|
failed: { text: '失败', color: 'error', icon: <CloseCircleOutlined /> },
|
2026-03-18 19:12:38 +08:00
|
|
|
|
};
|
2026-03-23 12:41:35 +08:00
|
|
|
|
const config = statusConfig[status as keyof typeof statusConfig];
|
|
|
|
|
|
return <Tag color={config.color} icon={config.icon}>{config.text}</Tag>;
|
2026-03-18 19:12:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2026-03-23 12:41:35 +08:00
|
|
|
|
{
|
|
|
|
|
|
title: '操作',
|
|
|
|
|
|
key: 'action',
|
|
|
|
|
|
render: (_: any, record: Transaction) => (
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => handleViewDetail(record)}>
|
|
|
|
|
|
查看
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
2026-03-18 19:12:38 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-03-27 16:56:06 +08:00
|
|
|
|
<div className="transactions">
|
2026-03-23 12:41:35 +08:00
|
|
|
|
<Row justify="space-between" align="middle" style={{ marginBottom: 24 }}>
|
|
|
|
|
|
<Col>
|
|
|
|
|
|
<Title level={4}>交易记录</Title>
|
|
|
|
|
|
<Text type="secondary">查看和管理所有财务交易</Text>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col>
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button icon={<ReloadOutlined />} onClick={handleRefresh}>
|
|
|
|
|
|
刷新
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button icon={<ExportOutlined />} onClick={handleExport}>
|
|
|
|
|
|
导出全部
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
|
|
<Row gutter={16} style={{ marginBottom: 24 }}>
|
|
|
|
|
|
<Col xs={24} sm={12} lg={6}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Statistic
|
|
|
|
|
|
title="总收入"
|
|
|
|
|
|
value={stats.income}
|
|
|
|
|
|
precision={2}
|
|
|
|
|
|
prefix={<ArrowUpOutlined />}
|
|
|
|
|
|
valueStyle={{ color: '#52c41a' }}
|
|
|
|
|
|
suffix="USD"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col xs={24} sm={12} lg={6}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Statistic
|
|
|
|
|
|
title="总支出"
|
|
|
|
|
|
value={stats.expense}
|
|
|
|
|
|
precision={2}
|
|
|
|
|
|
prefix={<ArrowDownOutlined />}
|
|
|
|
|
|
valueStyle={{ color: '#ff4d4f' }}
|
|
|
|
|
|
suffix="USD"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col xs={24} sm={12} lg={6}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Statistic
|
|
|
|
|
|
title="净利润"
|
|
|
|
|
|
value={stats.profit}
|
|
|
|
|
|
precision={2}
|
|
|
|
|
|
prefix={<DollarOutlined />}
|
|
|
|
|
|
valueStyle={{ color: stats.profit >= 0 ? '#52c41a' : '#ff4d4f' }}
|
|
|
|
|
|
suffix="USD"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col xs={24} sm={12} lg={6}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Statistic
|
|
|
|
|
|
title="待处理"
|
|
|
|
|
|
value={stats.pending}
|
|
|
|
|
|
precision={2}
|
|
|
|
|
|
prefix={<SyncOutlined />}
|
|
|
|
|
|
valueStyle={{ color: '#faad14' }}
|
|
|
|
|
|
suffix="USD"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
|
|
<Row gutter={16} style={{ marginBottom: 24 }}>
|
|
|
|
|
|
<Col span={16}>
|
|
|
|
|
|
<Card title="收支趋势(最近7天)" size="small">
|
|
|
|
|
|
<ResponsiveContainer width="100%" height={250}>
|
|
|
|
|
|
<LineChart
|
|
|
|
|
|
data={trendData}
|
|
|
|
|
|
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<CartesianGrid strokeDasharray="3 3" />
|
|
|
|
|
|
<XAxis dataKey="date" />
|
|
|
|
|
|
<YAxis />
|
|
|
|
|
|
<Tooltip />
|
|
|
|
|
|
<Legend />
|
|
|
|
|
|
<Line type="monotone" dataKey="income" name="收入" stroke="#52c41a" activeDot={{ r: 8 }} />
|
|
|
|
|
|
<Line type="monotone" dataKey="expense" name="支出" stroke="#ff4d4f" />
|
|
|
|
|
|
</LineChart>
|
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={8}>
|
|
|
|
|
|
<Card title="支出分类" size="small">
|
|
|
|
|
|
<ResponsiveContainer width="100%" height={250}>
|
2026-03-27 16:56:06 +08:00
|
|
|
|
<RechartsPie
|
2026-03-23 12:41:35 +08:00
|
|
|
|
data={categoryData}
|
|
|
|
|
|
cx="50%"
|
|
|
|
|
|
cy="50%"
|
|
|
|
|
|
labelLine={false}
|
|
|
|
|
|
outerRadius={80}
|
|
|
|
|
|
fill="#8884d8"
|
|
|
|
|
|
dataKey="value"
|
|
|
|
|
|
label={({ name, percent }) => `${name}: ${((percent || 0) * 100).toFixed(0)}%`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{categoryData.map((entry, index) => (
|
|
|
|
|
|
<Cell key={`cell-${index}`} fill={['#1890ff', '#52c41a', '#faad14', '#ff4d4f', '#722ed1', '#eb2f96'][index % 6]} />
|
|
|
|
|
|
))}
|
2026-03-27 16:56:06 +08:00
|
|
|
|
</RechartsPie>
|
2026-03-23 12:41:35 +08:00
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
|
|
|
|
{selectedRows.length > 0 && (
|
|
|
|
|
|
<Alert
|
|
|
|
|
|
message={`已选择 ${selectedRows.length} 项`}
|
|
|
|
|
|
action={
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button icon={<ExportOutlined />} onClick={handleBatchExport}>
|
|
|
|
|
|
批量导出
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button onClick={() => setSelectedRows([])}>
|
|
|
|
|
|
取消选择
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
}
|
|
|
|
|
|
style={{ marginBottom: 16 }}
|
2026-03-18 19:12:38 +08:00
|
|
|
|
/>
|
2026-03-23 12:41:35 +08:00
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<Card>
|
|
|
|
|
|
<div style={{ marginBottom: 16, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
|
|
|
|
|
|
<Search
|
|
|
|
|
|
placeholder="搜索交易ID或描述"
|
|
|
|
|
|
style={{ width: 300 }}
|
|
|
|
|
|
onSearch={(value) => setFilters({ ...filters, search: value })}
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
placeholder="类型"
|
|
|
|
|
|
style={{ width: 120 }}
|
|
|
|
|
|
value={filters.type}
|
|
|
|
|
|
onChange={(value) => setFilters({ ...filters, type: value })}
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
>
|
|
|
|
|
|
<Option value="income">收入</Option>
|
|
|
|
|
|
<Option value="expense">支出</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
placeholder="分类"
|
|
|
|
|
|
style={{ width: 120 }}
|
|
|
|
|
|
value={filters.category}
|
|
|
|
|
|
onChange={(value) => setFilters({ ...filters, category: value })}
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
>
|
|
|
|
|
|
<Option value="Sales">销售</Option>
|
|
|
|
|
|
<Option value="Suppliers">供应商</Option>
|
|
|
|
|
|
<Option value="Marketing">营销</Option>
|
|
|
|
|
|
<Option value="Shipping">物流</Option>
|
|
|
|
|
|
<Option value="Platform">平台费用</Option>
|
|
|
|
|
|
<Option value="Refund">退款</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
placeholder="状态"
|
|
|
|
|
|
style={{ width: 120 }}
|
|
|
|
|
|
value={filters.status}
|
|
|
|
|
|
onChange={(value) => setFilters({ ...filters, status: value })}
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
>
|
|
|
|
|
|
<Option value="completed">已完成</Option>
|
|
|
|
|
|
<Option value="pending">待处理</Option>
|
|
|
|
|
|
<Option value="failed">失败</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
<RangePicker
|
|
|
|
|
|
style={{ width: 300 }}
|
|
|
|
|
|
value={filters.dateRange}
|
|
|
|
|
|
onChange={(dates) => setFilters({ ...filters, dateRange: dates })}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
icon={<FilterOutlined />}
|
2026-03-27 16:56:06 +08:00
|
|
|
|
onClick={() => fetchTransactions(filters)}
|
2026-03-23 12:41:35 +08:00
|
|
|
|
>
|
|
|
|
|
|
筛选
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Table
|
|
|
|
|
|
columns={columns}
|
|
|
|
|
|
dataSource={transactions}
|
|
|
|
|
|
loading={loading}
|
|
|
|
|
|
rowKey="id"
|
|
|
|
|
|
rowSelection={{
|
|
|
|
|
|
selectedRowKeys: selectedRows.map(r => r.id),
|
|
|
|
|
|
onChange: (selectedRowKeys: React.Key[], selectedRows: Transaction[]) => {
|
|
|
|
|
|
setSelectedRows(selectedRows);
|
|
|
|
|
|
},
|
|
|
|
|
|
}}
|
|
|
|
|
|
pagination={{
|
|
|
|
|
|
pageSize: 20,
|
|
|
|
|
|
showSizeChanger: true,
|
|
|
|
|
|
showQuickJumper: true,
|
|
|
|
|
|
showTotal: (total) => `共 ${total} 条`,
|
|
|
|
|
|
pageSizeOptions: ['10', '20', '50', '100'],
|
|
|
|
|
|
}}
|
|
|
|
|
|
scroll={{ x: 1200 }}
|
2026-03-18 19:12:38 +08:00
|
|
|
|
/>
|
2026-03-23 12:41:35 +08:00
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
<Drawer
|
|
|
|
|
|
title="交易详情"
|
|
|
|
|
|
placement="right"
|
|
|
|
|
|
onClose={() => setDetailDrawerVisible(false)}
|
2026-03-27 16:56:06 +08:00
|
|
|
|
open={detailDrawerVisible}
|
2026-03-23 12:41:35 +08:00
|
|
|
|
width={600}
|
|
|
|
|
|
>
|
|
|
|
|
|
{currentTransaction && (
|
|
|
|
|
|
<Descriptions column={1} bordered>
|
|
|
|
|
|
<Descriptions.Item label="交易ID">{currentTransaction.id}</Descriptions.Item>
|
|
|
|
|
|
<Descriptions.Item label="金额">
|
|
|
|
|
|
<Text style={{ color: currentTransaction.type === 'income' ? '#52c41a' : '#ff4d4f', fontSize: 18, fontWeight: 500 }}>
|
|
|
|
|
|
{currentTransaction.type === 'income' ? '+' : '-'}${currentTransaction.amount.toFixed(2)}
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
|
<Descriptions.Item label="类型">
|
|
|
|
|
|
<Tag color={currentTransaction.type === 'income' ? 'success' : 'error'}>
|
|
|
|
|
|
{currentTransaction.type === 'income' ? '收入' : '支出'}
|
|
|
|
|
|
</Tag>
|
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
|
<Descriptions.Item label="分类">{currentTransaction.category}</Descriptions.Item>
|
|
|
|
|
|
<Descriptions.Item label="描述">{currentTransaction.description}</Descriptions.Item>
|
|
|
|
|
|
<Descriptions.Item label="日期">{moment(currentTransaction.createdAt).format('YYYY-MM-DD HH:mm:ss')}</Descriptions.Item>
|
|
|
|
|
|
<Descriptions.Item label="状态">
|
|
|
|
|
|
<Tag color={currentTransaction.status === 'completed' ? 'success' : currentTransaction.status === 'pending' ? 'warning' : 'error'}>
|
|
|
|
|
|
{currentTransaction.status === 'completed' ? '已完成' : currentTransaction.status === 'pending' ? '待处理' : '失败'}
|
|
|
|
|
|
</Tag>
|
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
|
</Descriptions>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Drawer>
|
2026-03-18 19:12:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default Transactions;
|