feat(admin-web): 做市商充值记录流水查询功能
在"充值现金(积分值)"弹窗底部新增"查看充值记录"入口按钮,
点击后打开独立的充值流水记录弹窗,支持以下功能:
- 筛选 Tab:全部 / 中心化充值 / 区块链充值(通过 memo 字段区分)
- 表格列:时间、充值方式(Badge)、金额、变动前余额、变动后余额、备注
- 分页控件:上一页/下一页 + 页码显示 + 总记录数
改动文件:
- market-maker.api.ts: 新增 LedgerEntry 类型定义和 getLedgers() API 函数
- use-market-maker.ts: 新增 useCashDepositLedgers() hook
- page.tsx: 充值弹窗底部入口 + 充值记录 Dialog UI
后端 API(GET /admin/market-maker/{name}/ledgers)已存在,无需改动。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
254796b08d
commit
7564c1151d
|
|
@ -23,7 +23,9 @@ import {
|
|||
useDepth,
|
||||
useDepthEnabled,
|
||||
useSetDepthEnabled,
|
||||
useCashDepositLedgers,
|
||||
} from '@/features/market-maker';
|
||||
import type { LedgerEntry } from '@/features/market-maker';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -57,6 +59,9 @@ import {
|
|||
MinusCircle,
|
||||
Copy,
|
||||
Check,
|
||||
History,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
} from 'lucide-react';
|
||||
|
||||
export default function MarketMakerPage() {
|
||||
|
|
@ -95,6 +100,11 @@ export default function MarketMakerPage() {
|
|||
const [blockchainWithdrawCashAmount, setBlockchainWithdrawCashAmount] = useState('');
|
||||
const [blockchainWithdrawSharesAddress, setBlockchainWithdrawSharesAddress] = useState('');
|
||||
const [blockchainWithdrawSharesAmount, setBlockchainWithdrawSharesAmount] = useState('');
|
||||
// 充值记录
|
||||
const [ledgerDialogOpen, setLedgerDialogOpen] = useState(false);
|
||||
const [ledgerFilter, setLedgerFilter] = useState<'all' | 'centralized' | 'blockchain'>('all');
|
||||
const [ledgerPage, setLedgerPage] = useState(1);
|
||||
const ledgerPageSize = 20;
|
||||
|
||||
const handleCopyAddress = async (address: string) => {
|
||||
await navigator.clipboard.writeText(address);
|
||||
|
|
@ -102,6 +112,21 @@ export default function MarketMakerPage() {
|
|||
setTimeout(() => setCopiedAddress(false), 2000);
|
||||
};
|
||||
|
||||
const { data: ledgerData, isLoading: ledgerLoading } = useCashDepositLedgers(ledgerPage, ledgerPageSize);
|
||||
|
||||
const filteredLedgers = (ledgerData?.data ?? []).filter((entry: LedgerEntry) => {
|
||||
if (ledgerFilter === 'centralized') return !entry.memo?.includes('区块链');
|
||||
if (ledgerFilter === 'blockchain') return entry.memo?.includes('区块链');
|
||||
return true;
|
||||
});
|
||||
|
||||
const ledgerTotalPages = Math.max(1, Math.ceil((ledgerData?.total ?? 0) / ledgerPageSize));
|
||||
|
||||
const formatDateTime = (dateStr: string) => {
|
||||
const d = new Date(dateStr);
|
||||
return d.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
};
|
||||
|
||||
const config = configData?.config;
|
||||
|
||||
const formatNumber = (value: string | undefined, decimals: number = 2) => {
|
||||
|
|
@ -284,6 +309,21 @@ export default function MarketMakerPage() {
|
|||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<div className="pt-3 border-t mt-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full text-muted-foreground hover:text-foreground"
|
||||
onClick={() => {
|
||||
setLedgerPage(1);
|
||||
setLedgerFilter('all');
|
||||
setLedgerDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<History className="h-4 w-4 mr-1" />
|
||||
查看充值记录
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
|
@ -372,6 +412,109 @@ export default function MarketMakerPage() {
|
|||
</Tabs>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 充值记录 Dialog */}
|
||||
<Dialog open={ledgerDialogOpen} onOpenChange={setLedgerDialogOpen}>
|
||||
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>现金(积分值)充值记录</DialogTitle>
|
||||
<DialogDescription>查看做市商账户的充值流水明细</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Tabs value={ledgerFilter} onValueChange={(v) => { setLedgerFilter(v as typeof ledgerFilter); setLedgerPage(1); }}>
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="all">全部</TabsTrigger>
|
||||
<TabsTrigger value="centralized">中心化充值</TabsTrigger>
|
||||
<TabsTrigger value="blockchain">区块链充值</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
{ledgerLoading ? (
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-10 w-full" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
</div>
|
||||
) : filteredLedgers.length === 0 ? (
|
||||
<div className="text-center text-muted-foreground py-12">
|
||||
暂无充值记录
|
||||
</div>
|
||||
) : (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>时间</TableHead>
|
||||
<TableHead>充值方式</TableHead>
|
||||
<TableHead className="text-right">金额</TableHead>
|
||||
<TableHead className="text-right">变动前余额</TableHead>
|
||||
<TableHead className="text-right">变动后余额</TableHead>
|
||||
<TableHead>备注</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredLedgers.map((entry: LedgerEntry) => {
|
||||
const isBlockchain = entry.memo?.includes('区块链');
|
||||
return (
|
||||
<TableRow key={entry.id}>
|
||||
<TableCell className="whitespace-nowrap text-sm">
|
||||
{formatDateTime(entry.createdAt)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{isBlockchain ? (
|
||||
<Badge variant="outline" className="text-blue-600 border-blue-300">链上</Badge>
|
||||
) : (
|
||||
<Badge variant="outline" className="text-green-600 border-green-300">中心化</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-green-600">
|
||||
+{formatNumber(entry.amount, 2)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-muted-foreground">
|
||||
{formatNumber(entry.balanceBefore, 2)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono">
|
||||
{formatNumber(entry.balanceAfter, 2)}
|
||||
</TableCell>
|
||||
<TableCell className="max-w-[200px] truncate text-xs text-muted-foreground" title={entry.memo ?? ''}>
|
||||
{entry.memo ?? '-'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
|
||||
{/* 分页 */}
|
||||
{(ledgerData?.total ?? 0) > 0 && (
|
||||
<div className="flex items-center justify-between pt-4 border-t">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
共 {ledgerData?.total ?? 0} 条记录
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={ledgerPage <= 1}
|
||||
onClick={() => setLedgerPage((p) => p - 1)}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<span className="text-sm">
|
||||
{ledgerPage} / {ledgerTotalPages}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={ledgerPage >= ledgerTotalPages}
|
||||
onClick={() => setLedgerPage((p) => p + 1)}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
|||
|
|
@ -106,6 +106,22 @@ export interface DepthData {
|
|||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface LedgerEntry {
|
||||
id: string;
|
||||
type: string;
|
||||
assetType: string;
|
||||
amount: string;
|
||||
balanceBefore: string;
|
||||
balanceAfter: string;
|
||||
tradeNo: string | null;
|
||||
orderNo: string | null;
|
||||
counterpartySeq: string | null;
|
||||
price: string | null;
|
||||
quantity: string | null;
|
||||
memo: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export const marketMakerApi = {
|
||||
// 获取做市商配置(包含运行状态)
|
||||
getConfig: async (name: string = 'MAIN_MARKET_MAKER'): Promise<{
|
||||
|
|
@ -320,4 +336,23 @@ export const marketMakerApi = {
|
|||
const response = await tradingClient.post('/admin/trading/depth-enabled', { enabled });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// 获取做市商分类账流水
|
||||
getLedgers: async (
|
||||
name: string,
|
||||
params?: {
|
||||
type?: string;
|
||||
assetType?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
},
|
||||
): Promise<{ success: boolean; data: LedgerEntry[]; total: number }> => {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.type) searchParams.append('type', params.type);
|
||||
if (params?.assetType) searchParams.append('assetType', params.assetType);
|
||||
if (params?.page) searchParams.append('page', params.page.toString());
|
||||
if (params?.pageSize) searchParams.append('pageSize', params.pageSize.toString());
|
||||
const response = await tradingClient.get(`/admin/market-maker/${name}/ledgers?${searchParams.toString()}`);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -324,6 +324,19 @@ export function useMarketMakerStats() {
|
|||
});
|
||||
}
|
||||
|
||||
export function useCashDepositLedgers(page: number = 1, pageSize: number = 20) {
|
||||
return useQuery({
|
||||
queryKey: ['marketMaker', 'ledgers', 'cash-deposit', MARKET_MAKER_NAME, page, pageSize],
|
||||
queryFn: () =>
|
||||
marketMakerApi.getLedgers(MARKET_MAKER_NAME, {
|
||||
type: 'DEPOSIT',
|
||||
assetType: 'CASH',
|
||||
page,
|
||||
pageSize,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDepthEnabled() {
|
||||
return useQuery({
|
||||
queryKey: ['trading', 'depthEnabled'],
|
||||
|
|
|
|||
Loading…
Reference in New Issue