refactor: 重构页面组件移除冗余Layout组件 feat: 实现WebSocket和事件总线系统 feat: 添加队列和调度系统 docs: 更新架构文档和服务映射 style: 清理重复接口定义使用数据源 chore: 更新依赖项配置 feat: 添加运行时系统和领域引导 ci: 配置ESLint边界检查规则 build: 添加Redis和WebSocket依赖 test: 添加MSW浏览器环境入口 perf: 优化数据获取逻辑使用统一数据源 fix: 修复类型定义和状态管理问题
278 lines
8.0 KiB
TypeScript
278 lines
8.0 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import { Table, Tag, Spin, Alert, Typography, Card, Button, Modal, Descriptions } from 'antd';
|
||
import { useRequest } from 'umi';
|
||
|
||
const { Title, Text } = Typography;
|
||
|
||
interface BillingRecord {
|
||
id: string;
|
||
merchantId: string;
|
||
totalAmount: number;
|
||
status: string;
|
||
createdAt: Date;
|
||
paidAt?: Date;
|
||
}
|
||
|
||
interface BillingItem {
|
||
id: string;
|
||
billingId: string;
|
||
feature: string;
|
||
amount: number;
|
||
quantity: number;
|
||
unitPrice: number;
|
||
}
|
||
|
||
const statusColorMap: Record<string, string> = {
|
||
pending: 'orange',
|
||
paid: 'green'
|
||
};
|
||
|
||
const statusTextMap: Record<string, string> = {
|
||
pending: '待支付',
|
||
paid: '已支付'
|
||
};
|
||
|
||
const featureMap: Record<string, string> = {
|
||
AI_OPTIMIZE: 'AI优化',
|
||
ADS_AUTO: '自动广告',
|
||
SYNC_INVENTORY: '库存同步',
|
||
CALCULATE_PROFIT: '利润计算'
|
||
};
|
||
|
||
export default function Billing() {
|
||
const [bills, setBills] = useState<BillingRecord[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [selectedBill, setSelectedBill] = useState<BillingRecord | null>(null);
|
||
const [billItems, setBillItems] = useState<BillingItem[]>([]);
|
||
const [itemsLoading, setItemsLoading] = useState(false);
|
||
const [modalVisible, setModalVisible] = useState(false);
|
||
|
||
// 获取账单列表
|
||
const { run: fetchBills } = useRequest(async () => {
|
||
try {
|
||
// 从本地存储获取merchantId(实际应用中应该从认证信息中获取)
|
||
const merchantId = localStorage.getItem('merchantId') || 'anonymous';
|
||
const response = await fetch(`/api/billing?merchantId=${merchantId}`);
|
||
const data = await response.json();
|
||
setBills(data.list || []);
|
||
} catch (error) {
|
||
console.error('Error fetching bills:', error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, {
|
||
manual: true
|
||
});
|
||
|
||
// 获取账单明细
|
||
const { run: fetchBillItems } = useRequest(async (billingId: string) => {
|
||
try {
|
||
setItemsLoading(true);
|
||
const response = await fetch(`/api/billing/items?billingId=${billingId}`);
|
||
const data = await response.json();
|
||
setBillItems(data.list || []);
|
||
} catch (error) {
|
||
console.error('Error fetching bill items:', error);
|
||
} finally {
|
||
setItemsLoading(false);
|
||
}
|
||
}, {
|
||
manual: true
|
||
});
|
||
|
||
// 初始加载账单列表
|
||
useEffect(() => {
|
||
fetchBills();
|
||
}, [fetchBills]);
|
||
|
||
// 查看账单明细
|
||
const handleViewDetails = async (bill: BillingRecord) => {
|
||
setSelectedBill(bill);
|
||
await fetchBillItems(bill.id);
|
||
setModalVisible(true);
|
||
};
|
||
|
||
// 标记账单为已支付
|
||
const handleMarkAsPaid = async (bill: BillingRecord) => {
|
||
try {
|
||
const response = await fetch(`/api/billing/paid`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ billingId: bill.id })
|
||
});
|
||
|
||
if (response.ok) {
|
||
// 刷新账单列表
|
||
fetchBills();
|
||
}
|
||
} catch (error) {
|
||
console.error('Error marking bill as paid:', error);
|
||
}
|
||
};
|
||
|
||
// 列定义
|
||
const columns = [
|
||
{
|
||
title: '账单ID',
|
||
dataIndex: 'id',
|
||
key: 'id',
|
||
ellipsis: true,
|
||
},
|
||
{
|
||
title: '总金额',
|
||
dataIndex: 'totalAmount',
|
||
key: 'totalAmount',
|
||
render: (amount: number) => `$${amount.toFixed(2)}`,
|
||
},
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
key: 'status',
|
||
render: (status: string) => (
|
||
<Tag color={statusColorMap[status]}>
|
||
{statusTextMap[status]}
|
||
</Tag>
|
||
),
|
||
},
|
||
{
|
||
title: '创建时间',
|
||
dataIndex: 'createdAt',
|
||
key: 'createdAt',
|
||
render: (date: Date) => new Date(date).toLocaleString(),
|
||
},
|
||
{
|
||
title: '支付时间',
|
||
dataIndex: 'paidAt',
|
||
key: 'paidAt',
|
||
render: (date: Date) => date ? new Date(date).toLocaleString() : '-',
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
render: (_: any, record: BillingRecord) => (
|
||
<div>
|
||
<Button
|
||
type="link"
|
||
onClick={() => handleViewDetails(record)}
|
||
style={{ marginRight: '8px' }}
|
||
>
|
||
查看明细
|
||
</Button>
|
||
{record.status === 'pending' && (
|
||
<Button
|
||
type="primary"
|
||
onClick={() => handleMarkAsPaid(record)}
|
||
>
|
||
标记为已支付
|
||
</Button>
|
||
)}
|
||
</div>
|
||
),
|
||
},
|
||
];
|
||
|
||
// 计算总金额
|
||
const totalAmount = bills.reduce((sum, bill) => sum + bill.totalAmount, 0);
|
||
const pendingAmount = bills.filter(bill => bill.status === 'pending').reduce((sum, bill) => sum + bill.totalAmount, 0);
|
||
|
||
return (
|
||
<div style={{ padding: '24px' }}>
|
||
<Title level={2}>账单管理</Title>
|
||
|
||
<Card style={{ marginBottom: '24px' }}>
|
||
<Descriptions bordered>
|
||
<Descriptions.Item label="总账单金额">${totalAmount.toFixed(2)}</Descriptions.Item>
|
||
<Descriptions.Item label="待支付金额">${pendingAmount.toFixed(2)}</Descriptions.Item>
|
||
<Descriptions.Item label="已支付金额">${(totalAmount - pendingAmount).toFixed(2)}</Descriptions.Item>
|
||
<Descriptions.Item label="账单数量">{bills.length}</Descriptions.Item>
|
||
</Descriptions>
|
||
</Card>
|
||
|
||
{loading ? (
|
||
<div style={{ textAlign: 'center', padding: '48px' }}>
|
||
<Spin size="large" />
|
||
</div>
|
||
) : bills.length === 0 ? (
|
||
<Alert message="暂无账单记录" type="info" />
|
||
) : (
|
||
<Table
|
||
rowKey="id"
|
||
columns={columns}
|
||
dataSource={bills}
|
||
pagination={{
|
||
pageSize: 10,
|
||
showSizeChanger: true,
|
||
showQuickJumper: true,
|
||
}}
|
||
scroll={{ x: 1000 }}
|
||
/>
|
||
)}
|
||
|
||
{/* 账单明细 modal */}
|
||
<Modal
|
||
title="账单明细"
|
||
open={modalVisible}
|
||
onCancel={() => setModalVisible(false)}
|
||
footer={[
|
||
<Button key="close" onClick={() => setModalVisible(false)}>
|
||
关闭
|
||
</Button>
|
||
]}
|
||
width={800}
|
||
>
|
||
{selectedBill && (
|
||
<div>
|
||
<Descriptions bordered style={{ marginBottom: '24px' }}>
|
||
<Descriptions.Item label="账单ID">{selectedBill.id}</Descriptions.Item>
|
||
<Descriptions.Item label="总金额">${selectedBill.totalAmount.toFixed(2)}</Descriptions.Item>
|
||
<Descriptions.Item label="状态">
|
||
<Tag color={statusColorMap[selectedBill.status]}>
|
||
{statusTextMap[selectedBill.status]}
|
||
</Tag>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="创建时间">{new Date(selectedBill.createdAt).toLocaleString()}</Descriptions.Item>
|
||
<Descriptions.Item label="支付时间">{selectedBill.paidAt ? new Date(selectedBill.paidAt).toLocaleString() : '-'}</Descriptions.Item>
|
||
</Descriptions>
|
||
|
||
<Title level={4}>明细列表</Title>
|
||
{itemsLoading ? (
|
||
<Spin />
|
||
) : billItems.length === 0 ? (
|
||
<Alert message="暂无明细记录" type="info" />
|
||
) : (
|
||
<Table
|
||
rowKey="id"
|
||
columns={[
|
||
{
|
||
title: '功能',
|
||
dataIndex: 'feature',
|
||
render: (feature: string) => featureMap[feature] || feature,
|
||
},
|
||
{
|
||
title: '数量',
|
||
dataIndex: 'quantity',
|
||
},
|
||
{
|
||
title: '单价',
|
||
dataIndex: 'unitPrice',
|
||
render: (price: number) => `$${price.toFixed(2)}`,
|
||
},
|
||
{
|
||
title: '金额',
|
||
dataIndex: 'amount',
|
||
render: (amount: number) => `$${amount.toFixed(2)}`,
|
||
},
|
||
]}
|
||
dataSource={billItems}
|
||
pagination={false}
|
||
/>
|
||
)}
|
||
</div>
|
||
)}
|
||
</Modal>
|
||
</div>
|
||
);
|
||
}
|