972 lines
48 KiB
TypeScript
972 lines
48 KiB
TypeScript
'use client';
|
||
|
||
import { useState } from 'react';
|
||
import { QRCodeSVG } from 'qrcode.react';
|
||
import { PageHeader } from '@/components/layout/page-header';
|
||
import {
|
||
useMarketMakerConfig,
|
||
useInitializeMarketMaker,
|
||
useUpdateMarketMakerConfig,
|
||
useDepositCash,
|
||
useWithdrawCash,
|
||
useDepositShares,
|
||
useWithdrawShares,
|
||
useBlockchainWithdrawCash,
|
||
useBlockchainWithdrawShares,
|
||
useStartTaker,
|
||
useStopTaker,
|
||
useTakeOrder,
|
||
useStartMaker,
|
||
useStopMaker,
|
||
useRefreshOrders,
|
||
useCancelAllOrders,
|
||
useDepth,
|
||
useDepthEnabled,
|
||
useSetDepthEnabled,
|
||
} 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';
|
||
import { Input } from '@/components/ui/input';
|
||
import { Label } from '@/components/ui/label';
|
||
import { Skeleton } from '@/components/ui/skeleton';
|
||
import { Badge } from '@/components/ui/badge';
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||
import { Switch } from '@/components/ui/switch';
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogTrigger,
|
||
} from '@/components/ui/dialog';
|
||
import {
|
||
Play,
|
||
Pause,
|
||
AlertCircle,
|
||
CheckCircle2,
|
||
RefreshCw,
|
||
Wallet,
|
||
TrendingUp,
|
||
TrendingDown,
|
||
BarChart3,
|
||
Zap,
|
||
PlusCircle,
|
||
MinusCircle,
|
||
Copy,
|
||
Check,
|
||
} from 'lucide-react';
|
||
|
||
export default function MarketMakerPage() {
|
||
const { data: configData, isLoading: configLoading, refetch } = useMarketMakerConfig();
|
||
const { data: depthData, isLoading: depthLoading } = useDepth(10);
|
||
const { data: depthEnabled, isLoading: depthEnabledLoading } = useDepthEnabled();
|
||
|
||
const runningStatus = configData?.runningStatus;
|
||
|
||
const initializeMutation = useInitializeMarketMaker();
|
||
const depositCashMutation = useDepositCash();
|
||
const withdrawCashMutation = useWithdrawCash();
|
||
const depositSharesMutation = useDepositShares();
|
||
const withdrawSharesMutation = useWithdrawShares();
|
||
const blockchainWithdrawCashMutation = useBlockchainWithdrawCash();
|
||
const blockchainWithdrawSharesMutation = useBlockchainWithdrawShares();
|
||
const startTakerMutation = useStartTaker();
|
||
const stopTakerMutation = useStopTaker();
|
||
const takeOrderMutation = useTakeOrder();
|
||
const startMakerMutation = useStartMaker();
|
||
const stopMakerMutation = useStopMaker();
|
||
const refreshOrdersMutation = useRefreshOrders();
|
||
const cancelAllOrdersMutation = useCancelAllOrders();
|
||
const setDepthEnabledMutation = useSetDepthEnabled();
|
||
const updateConfigMutation = useUpdateMarketMakerConfig();
|
||
|
||
const [initAccountSeq, setInitAccountSeq] = useState('MM001');
|
||
const [walletAddressInput, setWalletAddressInput] = useState('');
|
||
const [depositCashAmount, setDepositCashAmount] = useState('');
|
||
const [withdrawCashAmount, setWithdrawCashAmount] = useState('');
|
||
const [depositSharesAmount, setDepositSharesAmount] = useState('');
|
||
const [withdrawSharesAmount, setWithdrawSharesAmount] = useState('');
|
||
const [copiedAddress, setCopiedAddress] = useState(false);
|
||
// 区块链提现
|
||
const [blockchainWithdrawCashAddress, setBlockchainWithdrawCashAddress] = useState('');
|
||
const [blockchainWithdrawCashAmount, setBlockchainWithdrawCashAmount] = useState('');
|
||
const [blockchainWithdrawSharesAddress, setBlockchainWithdrawSharesAddress] = useState('');
|
||
const [blockchainWithdrawSharesAmount, setBlockchainWithdrawSharesAmount] = useState('');
|
||
|
||
const handleCopyAddress = async (address: string) => {
|
||
await navigator.clipboard.writeText(address);
|
||
setCopiedAddress(true);
|
||
setTimeout(() => setCopiedAddress(false), 2000);
|
||
};
|
||
|
||
const config = configData?.config;
|
||
|
||
const formatNumber = (value: string | undefined, decimals: number = 2) => {
|
||
if (!value) return '0';
|
||
const num = parseFloat(value);
|
||
if (isNaN(num)) return '0';
|
||
return num.toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<PageHeader title="做市商管理" description="管理做市商配置、资金和深度挂单" />
|
||
<Button variant="outline" size="sm" onClick={() => refetch()}>
|
||
<RefreshCw className="h-4 w-4 mr-2" />
|
||
刷新
|
||
</Button>
|
||
</div>
|
||
|
||
{/* 未初始化状态 */}
|
||
{!configLoading && !config && (
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>初始化做市商</CardTitle>
|
||
<CardDescription>做市商尚未初始化,请先创建做市商配置</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
<div className="space-y-2">
|
||
<Label>账户序列号</Label>
|
||
<Input
|
||
value={initAccountSeq}
|
||
onChange={(e) => setInitAccountSeq(e.target.value)}
|
||
placeholder="做市商专用账户序列号"
|
||
/>
|
||
</div>
|
||
<Button
|
||
onClick={() => initializeMutation.mutate({ accountSequence: initAccountSeq })}
|
||
disabled={initializeMutation.isPending || !initAccountSeq}
|
||
>
|
||
{initializeMutation.isPending ? '初始化中...' : '初始化做市商'}
|
||
</Button>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
|
||
{configLoading && (
|
||
<div className="space-y-6">
|
||
<Skeleton className="h-48 w-full" />
|
||
<Skeleton className="h-48 w-full" />
|
||
</div>
|
||
)}
|
||
|
||
{config && (
|
||
<>
|
||
{/* 资金状态卡片 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
{/* 现金余额 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<div className="flex items-center justify-between">
|
||
<CardTitle className="text-lg flex items-center gap-2">
|
||
<Wallet className="h-5 w-5 text-green-500" />
|
||
现金余额(积分值)
|
||
</CardTitle>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">总余额</p>
|
||
<p className="text-2xl font-bold">{formatNumber(config.cashBalance, 2)}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">可用余额</p>
|
||
<p className="text-2xl font-bold text-green-500">{formatNumber(config.availableCash, 2)}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">冻结中</p>
|
||
<p className="text-lg text-muted-foreground">{formatNumber(config.frozenCash, 2)}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-2 pt-4 border-t">
|
||
<Dialog>
|
||
<DialogTrigger asChild>
|
||
<Button size="sm" variant="outline">
|
||
<PlusCircle className="h-4 w-4 mr-1" />
|
||
充值
|
||
</Button>
|
||
</DialogTrigger>
|
||
<DialogContent className="max-w-md">
|
||
<DialogHeader>
|
||
<DialogTitle>充值现金(积分值)</DialogTitle>
|
||
<DialogDescription>选择充值方式向做市商账户充值</DialogDescription>
|
||
</DialogHeader>
|
||
<Tabs defaultValue="centralized" className="w-full">
|
||
<TabsList className="grid w-full grid-cols-2">
|
||
<TabsTrigger value="centralized">中心化充值</TabsTrigger>
|
||
<TabsTrigger value="blockchain">区块链充值</TabsTrigger>
|
||
</TabsList>
|
||
<TabsContent value="centralized" className="space-y-4 pt-4">
|
||
<div>
|
||
<Label>充值金额</Label>
|
||
<Input
|
||
type="number"
|
||
value={depositCashAmount}
|
||
onChange={(e) => setDepositCashAmount(e.target.value)}
|
||
placeholder="请输入金额"
|
||
/>
|
||
</div>
|
||
<Button
|
||
className="w-full"
|
||
onClick={() => {
|
||
depositCashMutation.mutate({ amount: depositCashAmount });
|
||
setDepositCashAmount('');
|
||
}}
|
||
disabled={depositCashMutation.isPending || !depositCashAmount}
|
||
>
|
||
{depositCashMutation.isPending ? '处理中...' : '确认充值'}
|
||
</Button>
|
||
</TabsContent>
|
||
<TabsContent value="blockchain" className="space-y-4 pt-4">
|
||
<div className="text-sm text-muted-foreground text-center">
|
||
向以下地址转入 <strong>fUSDT</strong> (积分值代币)
|
||
</div>
|
||
{config.kavaWalletAddress ? (
|
||
<div className="flex flex-col items-center space-y-4">
|
||
<div className="p-4 bg-white rounded-lg">
|
||
<QRCodeSVG value={config.kavaWalletAddress} size={180} />
|
||
</div>
|
||
<div className="w-full">
|
||
<Label className="text-xs text-muted-foreground">钱包地址 (Kava EVM)</Label>
|
||
<div className="flex items-center gap-2 mt-1">
|
||
<code className="flex-1 text-xs bg-muted p-2 rounded break-all">
|
||
{config.kavaWalletAddress}
|
||
</code>
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
onClick={() => handleCopyAddress(config.kavaWalletAddress!)}
|
||
>
|
||
{copiedAddress ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
<div className="text-xs text-yellow-600 bg-yellow-50 p-2 rounded w-full">
|
||
<AlertCircle className="h-3 w-3 inline mr-1" />
|
||
转账后系统将自动检测并入账(约需12个区块确认)
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-4">
|
||
<div className="text-center text-muted-foreground py-2">
|
||
<AlertCircle className="h-6 w-6 mx-auto mb-2 text-yellow-500" />
|
||
<p className="text-sm">做市商钱包地址未配置</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-xs">配置钱包地址 (Kava EVM)</Label>
|
||
<Input
|
||
value={walletAddressInput}
|
||
onChange={(e) => setWalletAddressInput(e.target.value)}
|
||
placeholder="0x..."
|
||
className="mt-1"
|
||
/>
|
||
</div>
|
||
<Button
|
||
className="w-full"
|
||
onClick={() => {
|
||
updateConfigMutation.mutate({ kavaWalletAddress: walletAddressInput });
|
||
setWalletAddressInput('');
|
||
}}
|
||
disabled={updateConfigMutation.isPending || !walletAddressInput || !walletAddressInput.startsWith('0x')}
|
||
>
|
||
{updateConfigMutation.isPending ? '保存中...' : '保存钱包地址'}
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</TabsContent>
|
||
</Tabs>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog>
|
||
<DialogTrigger asChild>
|
||
<Button size="sm" variant="outline">
|
||
<MinusCircle className="h-4 w-4 mr-1" />
|
||
提现
|
||
</Button>
|
||
</DialogTrigger>
|
||
<DialogContent className="max-w-md">
|
||
<DialogHeader>
|
||
<DialogTitle>提现现金(积分值)</DialogTitle>
|
||
<DialogDescription>选择提现方式从做市商账户提取</DialogDescription>
|
||
</DialogHeader>
|
||
<Tabs defaultValue="centralized" className="w-full">
|
||
<TabsList className="grid w-full grid-cols-2">
|
||
<TabsTrigger value="centralized">中心化提现</TabsTrigger>
|
||
<TabsTrigger value="blockchain">区块链提现</TabsTrigger>
|
||
</TabsList>
|
||
<TabsContent value="centralized" className="space-y-4 pt-4">
|
||
<div>
|
||
<Label>提现金额</Label>
|
||
<Input
|
||
type="number"
|
||
value={withdrawCashAmount}
|
||
onChange={(e) => setWithdrawCashAmount(e.target.value)}
|
||
placeholder="请输入金额"
|
||
/>
|
||
</div>
|
||
<Button
|
||
className="w-full"
|
||
onClick={() => {
|
||
withdrawCashMutation.mutate({ amount: withdrawCashAmount });
|
||
setWithdrawCashAmount('');
|
||
}}
|
||
disabled={withdrawCashMutation.isPending || !withdrawCashAmount}
|
||
>
|
||
{withdrawCashMutation.isPending ? '处理中...' : '确认提现'}
|
||
</Button>
|
||
</TabsContent>
|
||
<TabsContent value="blockchain" className="space-y-4 pt-4">
|
||
<div className="text-sm text-muted-foreground text-center">
|
||
转账 <strong>fUSDT</strong>(积分值代币)到指定地址
|
||
</div>
|
||
<div>
|
||
<Label>目标地址 (Kava EVM)</Label>
|
||
<Input
|
||
value={blockchainWithdrawCashAddress}
|
||
onChange={(e) => setBlockchainWithdrawCashAddress(e.target.value)}
|
||
placeholder="0x..."
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label>提现金额</Label>
|
||
<Input
|
||
type="number"
|
||
value={blockchainWithdrawCashAmount}
|
||
onChange={(e) => setBlockchainWithdrawCashAmount(e.target.value)}
|
||
placeholder="请输入金额"
|
||
/>
|
||
</div>
|
||
<Button
|
||
className="w-full"
|
||
onClick={() => {
|
||
blockchainWithdrawCashMutation.mutate({
|
||
toAddress: blockchainWithdrawCashAddress,
|
||
amount: blockchainWithdrawCashAmount,
|
||
});
|
||
setBlockchainWithdrawCashAddress('');
|
||
setBlockchainWithdrawCashAmount('');
|
||
}}
|
||
disabled={
|
||
blockchainWithdrawCashMutation.isPending ||
|
||
!blockchainWithdrawCashAddress ||
|
||
!blockchainWithdrawCashAmount
|
||
}
|
||
>
|
||
{blockchainWithdrawCashMutation.isPending ? '链上转账中...' : '确认区块链提现'}
|
||
</Button>
|
||
<div className="text-xs text-yellow-600 bg-yellow-50 p-2 rounded">
|
||
<AlertCircle className="h-3 w-3 inline mr-1" />
|
||
区块链提现将从做市商钱包直接转账到目标地址
|
||
</div>
|
||
</TabsContent>
|
||
</Tabs>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 积分股余额 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<div className="flex items-center justify-between">
|
||
<CardTitle className="text-lg flex items-center gap-2">
|
||
<BarChart3 className="h-5 w-5 text-blue-500" />
|
||
积分股余额
|
||
</CardTitle>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">总余额</p>
|
||
<p className="text-2xl font-bold">{formatNumber(config.shareBalance, 2)}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">可用余额</p>
|
||
<p className="text-2xl font-bold text-blue-500">{formatNumber(config.availableShares, 2)}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">冻结中</p>
|
||
<p className="text-lg text-muted-foreground">{formatNumber(config.frozenShares, 2)}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-2 pt-4 border-t">
|
||
<Dialog>
|
||
<DialogTrigger asChild>
|
||
<Button size="sm" variant="outline">
|
||
<PlusCircle className="h-4 w-4 mr-1" />
|
||
充值
|
||
</Button>
|
||
</DialogTrigger>
|
||
<DialogContent className="max-w-md">
|
||
<DialogHeader>
|
||
<DialogTitle>充值积分股</DialogTitle>
|
||
<DialogDescription>选择充值方式向做市商账户充值积分股</DialogDescription>
|
||
</DialogHeader>
|
||
<Tabs defaultValue="centralized" className="w-full">
|
||
<TabsList className="grid w-full grid-cols-2">
|
||
<TabsTrigger value="centralized">中心化充值</TabsTrigger>
|
||
<TabsTrigger value="blockchain">区块链充值</TabsTrigger>
|
||
</TabsList>
|
||
<TabsContent value="centralized" className="space-y-4 pt-4">
|
||
<div>
|
||
<Label>充值数量</Label>
|
||
<Input
|
||
type="number"
|
||
value={depositSharesAmount}
|
||
onChange={(e) => setDepositSharesAmount(e.target.value)}
|
||
placeholder="请输入数量"
|
||
/>
|
||
</div>
|
||
<Button
|
||
className="w-full"
|
||
onClick={() => {
|
||
depositSharesMutation.mutate({ amount: depositSharesAmount });
|
||
setDepositSharesAmount('');
|
||
}}
|
||
disabled={depositSharesMutation.isPending || !depositSharesAmount}
|
||
>
|
||
{depositSharesMutation.isPending ? '处理中...' : '确认充值'}
|
||
</Button>
|
||
</TabsContent>
|
||
<TabsContent value="blockchain" className="space-y-4 pt-4">
|
||
<div className="text-sm text-muted-foreground text-center">
|
||
向以下地址转入 <strong>eUSDT</strong> (积分股代币)
|
||
</div>
|
||
{config.kavaWalletAddress ? (
|
||
<div className="flex flex-col items-center space-y-4">
|
||
<div className="p-4 bg-white rounded-lg">
|
||
<QRCodeSVG value={config.kavaWalletAddress} size={180} />
|
||
</div>
|
||
<div className="w-full">
|
||
<Label className="text-xs text-muted-foreground">钱包地址 (Kava EVM)</Label>
|
||
<div className="flex items-center gap-2 mt-1">
|
||
<code className="flex-1 text-xs bg-muted p-2 rounded break-all">
|
||
{config.kavaWalletAddress}
|
||
</code>
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
onClick={() => handleCopyAddress(config.kavaWalletAddress!)}
|
||
>
|
||
{copiedAddress ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
<div className="text-xs text-yellow-600 bg-yellow-50 p-2 rounded w-full">
|
||
<AlertCircle className="h-3 w-3 inline mr-1" />
|
||
转账后系统将自动检测并入账(约需12个区块确认)
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-4">
|
||
<div className="text-center text-muted-foreground py-2">
|
||
<AlertCircle className="h-6 w-6 mx-auto mb-2 text-yellow-500" />
|
||
<p className="text-sm">做市商钱包地址未配置</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-xs">配置钱包地址 (Kava EVM)</Label>
|
||
<Input
|
||
value={walletAddressInput}
|
||
onChange={(e) => setWalletAddressInput(e.target.value)}
|
||
placeholder="0x..."
|
||
className="mt-1"
|
||
/>
|
||
</div>
|
||
<Button
|
||
className="w-full"
|
||
onClick={() => {
|
||
updateConfigMutation.mutate({ kavaWalletAddress: walletAddressInput });
|
||
setWalletAddressInput('');
|
||
}}
|
||
disabled={updateConfigMutation.isPending || !walletAddressInput || !walletAddressInput.startsWith('0x')}
|
||
>
|
||
{updateConfigMutation.isPending ? '保存中...' : '保存钱包地址'}
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</TabsContent>
|
||
</Tabs>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
<Dialog>
|
||
<DialogTrigger asChild>
|
||
<Button size="sm" variant="outline">
|
||
<MinusCircle className="h-4 w-4 mr-1" />
|
||
提取
|
||
</Button>
|
||
</DialogTrigger>
|
||
<DialogContent className="max-w-md">
|
||
<DialogHeader>
|
||
<DialogTitle>提取积分股</DialogTitle>
|
||
<DialogDescription>选择提现方式从做市商账户提取</DialogDescription>
|
||
</DialogHeader>
|
||
<Tabs defaultValue="centralized" className="w-full">
|
||
<TabsList className="grid w-full grid-cols-2">
|
||
<TabsTrigger value="centralized">中心化提取</TabsTrigger>
|
||
<TabsTrigger value="blockchain">区块链提现</TabsTrigger>
|
||
</TabsList>
|
||
<TabsContent value="centralized" className="space-y-4 pt-4">
|
||
<div>
|
||
<Label>提取数量</Label>
|
||
<Input
|
||
type="number"
|
||
value={withdrawSharesAmount}
|
||
onChange={(e) => setWithdrawSharesAmount(e.target.value)}
|
||
placeholder="请输入数量"
|
||
/>
|
||
</div>
|
||
<Button
|
||
className="w-full"
|
||
onClick={() => {
|
||
withdrawSharesMutation.mutate({ amount: withdrawSharesAmount });
|
||
setWithdrawSharesAmount('');
|
||
}}
|
||
disabled={withdrawSharesMutation.isPending || !withdrawSharesAmount}
|
||
>
|
||
{withdrawSharesMutation.isPending ? '处理中...' : '确认提取'}
|
||
</Button>
|
||
</TabsContent>
|
||
<TabsContent value="blockchain" className="space-y-4 pt-4">
|
||
<div className="text-sm text-muted-foreground text-center">
|
||
转账 <strong>eUSDT</strong>(积分股代币)到指定地址
|
||
</div>
|
||
<div>
|
||
<Label>目标地址 (Kava EVM)</Label>
|
||
<Input
|
||
value={blockchainWithdrawSharesAddress}
|
||
onChange={(e) => setBlockchainWithdrawSharesAddress(e.target.value)}
|
||
placeholder="0x..."
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label>提取数量</Label>
|
||
<Input
|
||
type="number"
|
||
value={blockchainWithdrawSharesAmount}
|
||
onChange={(e) => setBlockchainWithdrawSharesAmount(e.target.value)}
|
||
placeholder="请输入数量"
|
||
/>
|
||
</div>
|
||
<Button
|
||
className="w-full"
|
||
onClick={() => {
|
||
blockchainWithdrawSharesMutation.mutate({
|
||
toAddress: blockchainWithdrawSharesAddress,
|
||
amount: blockchainWithdrawSharesAmount,
|
||
});
|
||
setBlockchainWithdrawSharesAddress('');
|
||
setBlockchainWithdrawSharesAmount('');
|
||
}}
|
||
disabled={
|
||
blockchainWithdrawSharesMutation.isPending ||
|
||
!blockchainWithdrawSharesAddress ||
|
||
!blockchainWithdrawSharesAmount
|
||
}
|
||
>
|
||
{blockchainWithdrawSharesMutation.isPending ? '链上转账中...' : '确认区块链提现'}
|
||
</Button>
|
||
<div className="text-xs text-yellow-600 bg-yellow-50 p-2 rounded">
|
||
<AlertCircle className="h-3 w-3 inline mr-1" />
|
||
区块链提现将从做市商钱包直接转账到目标地址
|
||
</div>
|
||
</TabsContent>
|
||
</Tabs>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* 运行模式状态 */}
|
||
<Card className="border-2 border-primary/20">
|
||
<CardHeader className="pb-3">
|
||
<div className="flex items-center justify-between">
|
||
<CardTitle className="text-lg">当前运行模式</CardTitle>
|
||
{runningStatus?.mode === 'idle' && (
|
||
<Badge variant="secondary" className="text-base px-3 py-1">
|
||
<Pause className="h-4 w-4 mr-1" />
|
||
空闲
|
||
</Badge>
|
||
)}
|
||
{runningStatus?.mode === 'taker' && (
|
||
<Badge variant="default" className="bg-green-500 text-base px-3 py-1">
|
||
<Zap className="h-4 w-4 mr-1" />
|
||
吃单模式运行中
|
||
</Badge>
|
||
)}
|
||
{runningStatus?.mode === 'maker' && (
|
||
<Badge variant="default" className="bg-blue-500 text-base px-3 py-1">
|
||
<BarChart3 className="h-4 w-4 mr-1" />
|
||
挂单模式运行中
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="flex items-center gap-6 text-sm text-muted-foreground">
|
||
<div className="flex items-center gap-2">
|
||
<span className={`w-2 h-2 rounded-full ${runningStatus?.takerRunning ? 'bg-green-500' : 'bg-gray-300'}`} />
|
||
<span>吃单模式: {runningStatus?.takerRunning ? '运行中' : '未运行'}</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<span className={`w-2 h-2 rounded-full ${runningStatus?.makerRunning ? 'bg-blue-500' : 'bg-gray-300'}`} />
|
||
<span>挂单模式: {runningStatus?.makerRunning ? '运行中' : '未运行'}</span>
|
||
</div>
|
||
<div className="ml-auto text-xs bg-yellow-100 text-yellow-800 px-2 py-1 rounded">
|
||
<AlertCircle className="h-3 w-3 inline mr-1" />
|
||
两种模式互斥,启动一个会自动停止另一个
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 运行模式控制 */}
|
||
<Tabs defaultValue="taker">
|
||
<TabsList>
|
||
<TabsTrigger value="taker">吃单模式</TabsTrigger>
|
||
<TabsTrigger value="maker">挂单模式(深度)</TabsTrigger>
|
||
<TabsTrigger value="depth">深度显示</TabsTrigger>
|
||
</TabsList>
|
||
|
||
{/* 吃单模式 */}
|
||
<TabsContent value="taker">
|
||
<Card>
|
||
<CardHeader>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<CardTitle className="text-lg flex items-center gap-2">
|
||
<Zap className="h-5 w-5" />
|
||
吃单模式
|
||
</CardTitle>
|
||
<CardDescription>
|
||
自动买入用户卖单(Taker 策略)— 做市商作为买方,不触发额外销毁
|
||
</CardDescription>
|
||
</div>
|
||
{runningStatus?.takerRunning ? (
|
||
<Badge variant="default" className="bg-green-500">
|
||
<CheckCircle2 className="h-3 w-3 mr-1" />
|
||
运行中
|
||
</Badge>
|
||
) : (
|
||
<Badge variant="secondary">
|
||
<Pause className="h-3 w-3 mr-1" />
|
||
已停止
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">单次最大买入比例</p>
|
||
<p className="font-semibold">{formatNumber(String(parseFloat(config.maxBuyRatio) * 100), 2)}%</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">最小间隔</p>
|
||
<p className="font-semibold">{config.minIntervalMs}ms</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">最大间隔</p>
|
||
<p className="font-semibold">{config.maxIntervalMs}ms</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">价格策略</p>
|
||
<p className="font-semibold">{config.priceStrategy}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-2 pt-4 border-t">
|
||
{runningStatus?.takerRunning ? (
|
||
<Button
|
||
variant="destructive"
|
||
onClick={() => stopTakerMutation.mutate()}
|
||
disabled={stopTakerMutation.isPending}
|
||
>
|
||
<Pause className="h-4 w-4 mr-2" />
|
||
{stopTakerMutation.isPending ? '停止中...' : '停止吃单'}
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
onClick={() => startTakerMutation.mutate()}
|
||
disabled={startTakerMutation.isPending}
|
||
>
|
||
<Play className="h-4 w-4 mr-2" />
|
||
{startTakerMutation.isPending ? '启动中...' : '启动吃单'}
|
||
</Button>
|
||
)}
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => takeOrderMutation.mutate()}
|
||
disabled={takeOrderMutation.isPending}
|
||
>
|
||
<Zap className="h-4 w-4 mr-2" />
|
||
{takeOrderMutation.isPending ? '执行中...' : '手动吃单'}
|
||
</Button>
|
||
</div>
|
||
|
||
{runningStatus?.makerRunning && (
|
||
<div className="text-sm text-yellow-600 bg-yellow-50 p-3 rounded">
|
||
<AlertCircle className="h-4 w-4 inline mr-1" />
|
||
启动吃单模式将自动停止当前运行的挂单模式
|
||
</div>
|
||
)}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</TabsContent>
|
||
|
||
{/* 挂单模式 */}
|
||
<TabsContent value="maker">
|
||
<Card>
|
||
<CardHeader>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<CardTitle className="text-lg flex items-center gap-2">
|
||
<BarChart3 className="h-5 w-5" />
|
||
挂单模式(双边深度)
|
||
</CardTitle>
|
||
<CardDescription>
|
||
自动挂买卖单形成深度(Maker 策略)— 做市商卖单被吃会触发销毁
|
||
</CardDescription>
|
||
</div>
|
||
{runningStatus?.makerRunning ? (
|
||
<Badge variant="default" className="bg-blue-500">
|
||
<CheckCircle2 className="h-3 w-3 mr-1" />
|
||
运行中
|
||
</Badge>
|
||
) : (
|
||
<Badge variant="secondary">
|
||
<Pause className="h-3 w-3 mr-1" />
|
||
已停止
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="space-y-4">
|
||
{/* 警告提示 */}
|
||
<div className="text-sm text-orange-600 bg-orange-50 p-3 rounded border border-orange-200">
|
||
<AlertCircle className="h-4 w-4 inline mr-1" />
|
||
<strong>注意:</strong>挂单模式下做市商会挂卖单,当卖单被用户买入时会触发销毁机制导致价格上涨。
|
||
如果只想提供流动性而不影响价格,请使用吃单模式。
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">买单档位</p>
|
||
<p className="font-semibold">{config.bidLevels || 5}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">买单价差</p>
|
||
<p className="font-semibold">{formatNumber(String(parseFloat(config.bidSpread || '0.01') * 100), 2)}%</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">每档买单量</p>
|
||
<p className="font-semibold">{formatNumber(config.bidQuantityPerLevel || '1000', 0)}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">卖单档位</p>
|
||
<p className="font-semibold">{config.askLevels || 5}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">卖单价差</p>
|
||
<p className="font-semibold">{formatNumber(String(parseFloat(config.askSpread || '0.01') * 100), 2)}%</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-muted-foreground">每档卖单量</p>
|
||
<p className="font-semibold">{formatNumber(config.askQuantityPerLevel || '1000', 0)}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-2 pt-4 border-t">
|
||
{runningStatus?.makerRunning ? (
|
||
<Button
|
||
variant="destructive"
|
||
onClick={() => stopMakerMutation.mutate()}
|
||
disabled={stopMakerMutation.isPending}
|
||
>
|
||
<Pause className="h-4 w-4 mr-2" />
|
||
{stopMakerMutation.isPending ? '停止中...' : '停止挂单'}
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => startMakerMutation.mutate()}
|
||
disabled={startMakerMutation.isPending}
|
||
>
|
||
<Play className="h-4 w-4 mr-2" />
|
||
{startMakerMutation.isPending ? '启动中...' : '启动挂单'}
|
||
</Button>
|
||
)}
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => refreshOrdersMutation.mutate()}
|
||
disabled={refreshOrdersMutation.isPending || !runningStatus?.makerRunning}
|
||
>
|
||
<RefreshCw className="h-4 w-4 mr-2" />
|
||
{refreshOrdersMutation.isPending ? '刷新中...' : '刷新挂单'}
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => cancelAllOrdersMutation.mutate()}
|
||
disabled={cancelAllOrdersMutation.isPending}
|
||
>
|
||
{cancelAllOrdersMutation.isPending ? '取消中...' : '取消所有挂单'}
|
||
</Button>
|
||
</div>
|
||
|
||
{runningStatus?.takerRunning && (
|
||
<div className="text-sm text-yellow-600 bg-yellow-50 p-3 rounded">
|
||
<AlertCircle className="h-4 w-4 inline mr-1" />
|
||
启动挂单模式将自动停止当前运行的吃单模式
|
||
</div>
|
||
)}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</TabsContent>
|
||
|
||
{/* 深度显示控制 */}
|
||
<TabsContent value="depth">
|
||
<div className="space-y-6">
|
||
{/* 深度开关 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<CardTitle className="text-lg">深度显示开关</CardTitle>
|
||
<CardDescription>控制App端是否显示买卖深度</CardDescription>
|
||
</div>
|
||
{depthEnabledLoading ? (
|
||
<Skeleton className="h-6 w-16" />
|
||
) : depthEnabled?.enabled ? (
|
||
<Badge variant="default" className="bg-green-500">
|
||
<CheckCircle2 className="h-3 w-3 mr-1" />
|
||
已开启
|
||
</Badge>
|
||
) : (
|
||
<Badge variant="secondary">
|
||
<Pause className="h-3 w-3 mr-1" />
|
||
已关闭
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="flex items-center justify-between p-4 bg-muted rounded-lg">
|
||
<div>
|
||
<p className="font-medium">深度显示</p>
|
||
<p className="text-sm text-muted-foreground">
|
||
{depthEnabled?.enabled ? '用户可以在App中查看买卖深度' : '深度功能已关闭,用户无法查看'}
|
||
</p>
|
||
</div>
|
||
<Switch
|
||
checked={depthEnabled?.enabled ?? false}
|
||
onCheckedChange={(checked) => setDepthEnabledMutation.mutate(checked)}
|
||
disabled={setDepthEnabledMutation.isPending || depthEnabledLoading}
|
||
/>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 当前深度 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="text-lg">当前深度</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
{depthLoading ? (
|
||
<Skeleton className="h-64 w-full" />
|
||
) : (
|
||
<div className="grid grid-cols-2 gap-6">
|
||
{/* 买单深度 */}
|
||
<div>
|
||
<h4 className="font-medium text-green-500 mb-2 flex items-center gap-1">
|
||
<TrendingUp className="h-4 w-4" />
|
||
买单深度
|
||
</h4>
|
||
{depthData?.bids?.length ? (
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>价格</TableHead>
|
||
<TableHead>数量</TableHead>
|
||
<TableHead>累计</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{depthData.bids.map((bid, idx) => (
|
||
<TableRow key={idx}>
|
||
<TableCell className="font-mono text-green-500">{formatNumber(bid.price, 8)}</TableCell>
|
||
<TableCell className="font-mono">{formatNumber(bid.quantity, 2)}</TableCell>
|
||
<TableCell className="font-mono text-muted-foreground">{formatNumber(bid.total, 2)}</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
) : (
|
||
<p className="text-center text-muted-foreground py-4">暂无买单</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* 卖单深度 */}
|
||
<div>
|
||
<h4 className="font-medium text-red-500 mb-2 flex items-center gap-1">
|
||
<TrendingDown className="h-4 w-4" />
|
||
卖单深度
|
||
</h4>
|
||
{depthData?.asks?.length ? (
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>价格</TableHead>
|
||
<TableHead>数量</TableHead>
|
||
<TableHead>累计</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{depthData.asks.map((ask, idx) => (
|
||
<TableRow key={idx}>
|
||
<TableCell className="font-mono text-red-500">{formatNumber(ask.price, 8)}</TableCell>
|
||
<TableCell className="font-mono">{formatNumber(ask.quantity, 2)}</TableCell>
|
||
<TableCell className="font-mono text-muted-foreground">{formatNumber(ask.total, 2)}</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
) : (
|
||
<p className="text-center text-muted-foreground py-4">暂无卖单</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</TabsContent>
|
||
</Tabs>
|
||
</>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|