Initial commit
This commit is contained in:
627
src/views/TradingSimulation/components/PositionsList.js
Normal file
627
src/views/TradingSimulation/components/PositionsList.js
Normal file
@@ -0,0 +1,627 @@
|
||||
// src/views/TradingSimulation/components/PositionsList.js - 持仓列表组件(现代化版本)
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
Heading,
|
||||
Text,
|
||||
Button,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
Badge,
|
||||
HStack,
|
||||
VStack,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper,
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Select,
|
||||
Alert,
|
||||
AlertIcon,
|
||||
AlertDescription,
|
||||
useDisclosure,
|
||||
useToast,
|
||||
useColorModeValue,
|
||||
useColorMode,
|
||||
Icon,
|
||||
Stat,
|
||||
StatLabel,
|
||||
StatNumber,
|
||||
StatHelpText,
|
||||
StatArrow,
|
||||
SimpleGrid,
|
||||
Flex
|
||||
} from '@chakra-ui/react';
|
||||
import { FiTrendingUp, FiTrendingDown, FiMinus, FiBarChart2, FiPieChart } from 'react-icons/fi';
|
||||
|
||||
// 导入现有的高质量组件
|
||||
import BarChart from '../../../components/Charts/BarChart';
|
||||
import PieChart from '../../../components/Charts/PieChart';
|
||||
import IconBox from '../../../components/Icons/IconBox';
|
||||
|
||||
// 计算涨跌幅的辅助函数
|
||||
const calculateChange = (currentPrice, avgPrice) => {
|
||||
if (!avgPrice || avgPrice === 0) return { change: 0, changePercent: 0 };
|
||||
const change = currentPrice - avgPrice;
|
||||
const changePercent = (change / avgPrice) * 100;
|
||||
return { change, changePercent };
|
||||
};
|
||||
|
||||
export default function PositionsList({ positions, account, onSellStock }) {
|
||||
const [selectedPosition, setSelectedPosition] = useState(null);
|
||||
const [sellQuantity, setSellQuantity] = useState(0);
|
||||
const [orderType, setOrderType] = useState('MARKET');
|
||||
const [limitPrice, setLimitPrice] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const toast = useToast();
|
||||
|
||||
const cardBg = useColorModeValue('white', 'gray.800');
|
||||
const textColor = useColorModeValue('gray.700', 'white');
|
||||
const secondaryColor = useColorModeValue('gray.500', 'gray.400');
|
||||
|
||||
// 格式化货币
|
||||
const formatCurrency = (amount) => {
|
||||
return new Intl.NumberFormat('zh-CN', {
|
||||
style: 'currency',
|
||||
currency: 'CNY',
|
||||
minimumFractionDigits: 2
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
// 计算持仓盈亏(使用后端返回的数据)
|
||||
const calculatePositionProfit = (position) => {
|
||||
return {
|
||||
profit: position.profit || 0,
|
||||
profitPercent: position.profitRate || 0,
|
||||
currentPrice: position.currentPrice || position.avgPrice,
|
||||
marketValue: position.marketValue || (position.currentPrice * position.quantity)
|
||||
};
|
||||
};
|
||||
|
||||
// 打开卖出对话框
|
||||
const handleSellClick = (position) => {
|
||||
setSelectedPosition(position);
|
||||
setSellQuantity(position.availableQuantity); // 默认全部可卖数量
|
||||
setLimitPrice(position.currentPrice?.toString() || position.avgPrice.toString());
|
||||
onOpen();
|
||||
};
|
||||
|
||||
// 执行卖出
|
||||
const handleSellConfirm = async () => {
|
||||
if (!selectedPosition || sellQuantity <= 0) return;
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const result = await onSellStock(
|
||||
selectedPosition.stockCode,
|
||||
sellQuantity,
|
||||
orderType,
|
||||
orderType === 'LIMIT' ? parseFloat(limitPrice) : null
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: '卖出成功',
|
||||
description: `已卖出 ${selectedPosition.stockName} ${sellQuantity} 股`,
|
||||
status: 'success',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
onClose();
|
||||
}
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: '卖出失败',
|
||||
description: error.message,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 计算卖出金额
|
||||
const calculateSellAmount = () => {
|
||||
if (!selectedPosition || !sellQuantity) return { totalAmount: 0, commission: 0, stampTax: 0, netAmount: 0 };
|
||||
|
||||
const price = orderType === 'LIMIT' ? parseFloat(limitPrice) || 0 : selectedPosition.currentPrice || selectedPosition.avgPrice;
|
||||
const totalAmount = price * sellQuantity;
|
||||
const commission = Math.max(totalAmount * 0.00025, 5); // 万分之2.5
|
||||
const stampTax = totalAmount * 0.001; // 千分之1
|
||||
const transferFee = totalAmount * 0.00002; // 万分之0.2
|
||||
const netAmount = totalAmount - commission - stampTax - transferFee;
|
||||
|
||||
return { totalAmount, commission, stampTax, transferFee, netAmount };
|
||||
};
|
||||
|
||||
// 安全地计算总持仓统计
|
||||
const calculateTotalStats = () => {
|
||||
if (!Array.isArray(positions) || positions.length === 0) {
|
||||
return { totalMarketValue: 0, totalCost: 0, totalProfit: 0, totalProfitPercent: 0 };
|
||||
}
|
||||
|
||||
let totalMarketValue = 0;
|
||||
let totalCost = 0;
|
||||
let totalProfit = 0;
|
||||
|
||||
positions.forEach(position => {
|
||||
if (position) {
|
||||
const { marketValue, profit } = calculatePositionProfit(position);
|
||||
totalMarketValue += marketValue || 0;
|
||||
totalCost += position.totalCost || (position.quantity * position.avgPrice) || 0;
|
||||
totalProfit += profit || 0;
|
||||
}
|
||||
});
|
||||
|
||||
const totalProfitPercent = totalCost > 0 ? (totalProfit / totalCost) * 100 : 0;
|
||||
|
||||
return { totalMarketValue, totalCost, totalProfit, totalProfitPercent };
|
||||
};
|
||||
|
||||
const { totalMarketValue, totalCost, totalProfit, totalProfitPercent } = calculateTotalStats();
|
||||
const { totalAmount, commission, stampTax, transferFee, netAmount } = calculateSellAmount();
|
||||
|
||||
if (!positions || positions.length === 0) {
|
||||
return (
|
||||
<Card bg={cardBg} shadow="lg">
|
||||
<CardBody>
|
||||
<VStack spacing={4} py={8}>
|
||||
<Icon as={FiMinus} size="48px" color={secondaryColor} />
|
||||
<Text color={secondaryColor} fontSize="lg">暂无持仓</Text>
|
||||
<Text color={secondaryColor} fontSize="sm">
|
||||
请前往交易面板买入股票
|
||||
</Text>
|
||||
</VStack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// 安全地准备持仓分布图表数据
|
||||
const safePositions = Array.isArray(positions) ? positions : [];
|
||||
const hasPositions = safePositions.length > 0;
|
||||
|
||||
const positionDistributionData = hasPositions ? safePositions.map(pos => pos?.marketValue || 0) : [];
|
||||
const positionDistributionLabels = hasPositions ? safePositions.map(pos => pos?.stockName || pos?.stockCode || '') : [];
|
||||
|
||||
const positionDistributionOptions = {
|
||||
labels: positionDistributionLabels,
|
||||
colors: ['#4299E1', '#48BB78', '#ED8936', '#9F7AEA', '#F56565', '#38B2AC', '#ECC94B'],
|
||||
chart: {
|
||||
width: "100%",
|
||||
height: "300px"
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
fontSize: '12px'
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
formatter: function (val) {
|
||||
return (val || 0).toFixed(1) + "%"
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
theme: "dark",
|
||||
y: {
|
||||
formatter: function(val) {
|
||||
return formatCurrency(val || 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 安全地准备盈亏分布柱状图数据
|
||||
const profitBarData = hasPositions ? [{
|
||||
name: '盈亏分布',
|
||||
data: safePositions.map(pos => pos?.profit || 0)
|
||||
}] : [];
|
||||
|
||||
const xAxisLabelColor = useColorModeValue('#718096', '#A0AEC0');
|
||||
const yAxisLabelColor = useColorModeValue('#718096', '#A0AEC0');
|
||||
const gridBorderColor = useColorModeValue('#E2E8F0', '#4A5568');
|
||||
|
||||
const profitBarOptions = {
|
||||
chart: {
|
||||
toolbar: { show: false },
|
||||
height: 300
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
borderRadius: 8,
|
||||
columnWidth: "60%",
|
||||
colors: {
|
||||
ranges: [{
|
||||
from: -1000000,
|
||||
to: 0,
|
||||
color: '#F56565'
|
||||
}, {
|
||||
from: 0.01,
|
||||
to: 1000000,
|
||||
color: '#48BB78'
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
xaxis: {
|
||||
categories: hasPositions ? safePositions.map(pos => pos?.stockCode || '') : [],
|
||||
labels: {
|
||||
style: {
|
||||
colors: xAxisLabelColor,
|
||||
fontSize: '12px'
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
style: {
|
||||
colors: yAxisLabelColor,
|
||||
fontSize: '12px'
|
||||
},
|
||||
formatter: function (val) {
|
||||
return '¥' + ((val || 0) / 1000).toFixed(1) + 'k'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
theme: "dark",
|
||||
y: {
|
||||
formatter: function(val) {
|
||||
return formatCurrency(val || 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
strokeDashArray: 5,
|
||||
borderColor: gridBorderColor
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<VStack spacing={6} align="stretch">
|
||||
{/* 现代化持仓概览 */}
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing={6}>
|
||||
{/* 左侧:核心统计 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="lg" fontWeight="bold" color={textColor}>持仓概览</Text>
|
||||
<Badge colorScheme="blue" variant="solid" borderRadius="full">
|
||||
{positions.length} 只股票
|
||||
</Badge>
|
||||
</HStack>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<SimpleGrid columns={1} spacing={4}>
|
||||
{/* 持仓市值 */}
|
||||
<Card variant="outline">
|
||||
<CardBody>
|
||||
<Flex direction="row" align="center" justify="center" w="100%">
|
||||
<Stat me="auto">
|
||||
<StatLabel fontSize="sm" color={secondaryColor} fontWeight="bold">
|
||||
持仓市值
|
||||
</StatLabel>
|
||||
<StatNumber fontSize="xl" color={textColor} fontWeight="bold">
|
||||
{formatCurrency(totalMarketValue)}
|
||||
</StatNumber>
|
||||
</Stat>
|
||||
<IconBox
|
||||
as="div"
|
||||
h={"40px"}
|
||||
w={"40px"}
|
||||
bg="linear-gradient(90deg, #4481EB 0%, #04BEFE 100%)"
|
||||
icon={<Icon as={FiBarChart2} color="white" w="20px" h="20px" />}
|
||||
/>
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* 总盈亏 */}
|
||||
<Card variant="outline">
|
||||
<CardBody>
|
||||
<Flex direction="row" align="center" justify="center" w="100%">
|
||||
<Stat me="auto">
|
||||
<StatLabel fontSize="sm" color={secondaryColor} fontWeight="bold">
|
||||
总盈亏
|
||||
</StatLabel>
|
||||
<StatNumber fontSize="xl" color={totalProfit >= 0 ? 'green.500' : 'red.500'} fontWeight="bold">
|
||||
{formatCurrency(totalProfit)}
|
||||
</StatNumber>
|
||||
<StatHelpText color={totalProfit >= 0 ? 'green.500' : 'red.500'} fontSize="sm" fontWeight="bold">
|
||||
<StatArrow type={totalProfit >= 0 ? 'increase' : 'decrease'} />
|
||||
{(totalProfitPercent || 0).toFixed(2)}%
|
||||
</StatHelpText>
|
||||
</Stat>
|
||||
<IconBox
|
||||
as="div"
|
||||
h={"40px"}
|
||||
w={"40px"}
|
||||
bg={totalProfit >= 0
|
||||
? "linear-gradient(90deg, #4FD1C7 0%, #81E6D9 100%)"
|
||||
: "linear-gradient(90deg, #FEB2B2 0%, #F56565 100%)"
|
||||
}
|
||||
icon={
|
||||
<Icon
|
||||
as={totalProfit >= 0 ? FiTrendingUp : FiTrendingDown}
|
||||
color="white"
|
||||
w="20px"
|
||||
h="20px"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</SimpleGrid>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* 右侧:持仓分布图 */}
|
||||
{hasPositions && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="lg" fontWeight="bold" color={textColor}>持仓分布</Text>
|
||||
<Icon as={FiPieChart} color="blue.500" />
|
||||
</HStack>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Box h="300px">
|
||||
<PieChart
|
||||
chartData={positionDistributionData}
|
||||
chartOptions={positionDistributionOptions}
|
||||
/>
|
||||
</Box>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
</SimpleGrid>
|
||||
|
||||
{/* 盈亏分析图表 */}
|
||||
{hasPositions && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="lg" fontWeight="bold" color={textColor}>盈亏分析</Text>
|
||||
<Badge
|
||||
colorScheme={totalProfit >= 0 ? 'green' : 'red'}
|
||||
variant="solid"
|
||||
borderRadius="full"
|
||||
>
|
||||
{totalProfit >= 0 ? '整体盈利' : '整体亏损'}
|
||||
</Badge>
|
||||
</HStack>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Box h="300px">
|
||||
<BarChart
|
||||
chartData={profitBarData}
|
||||
chartOptions={profitBarOptions}
|
||||
/>
|
||||
</Box>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 持仓列表 */}
|
||||
<Card bg={cardBg} shadow="lg">
|
||||
<CardHeader>
|
||||
<HStack justify="space-between">
|
||||
<Heading size="md">我的持仓</Heading>
|
||||
<Badge colorScheme="blue" variant="solid">
|
||||
{positions.length} 只股票
|
||||
</Badge>
|
||||
</HStack>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Box overflowX="auto">
|
||||
<Table variant="simple">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>股票代码</Th>
|
||||
<Th>股票名称</Th>
|
||||
<Th isNumeric>持仓数量</Th>
|
||||
<Th isNumeric>成本价</Th>
|
||||
<Th isNumeric>现价</Th>
|
||||
<Th isNumeric>市值</Th>
|
||||
<Th isNumeric>盈亏</Th>
|
||||
<Th isNumeric>盈亏比例</Th>
|
||||
<Th>操作</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{safePositions.map((position) => {
|
||||
if (!position) return null;
|
||||
|
||||
const { profit, profitPercent, currentPrice, marketValue } = calculatePositionProfit(position);
|
||||
const { change, changePercent } = calculateChange(currentPrice, position.avgPrice);
|
||||
|
||||
return (
|
||||
<Tr key={position.stockCode || position.id}>
|
||||
<Td fontWeight="medium">{position.stockCode || '-'}</Td>
|
||||
<Td>{position.stockName || '-'}</Td>
|
||||
<Td isNumeric>{(position.quantity || 0).toLocaleString()}</Td>
|
||||
<Td isNumeric>¥{(position.avgPrice || 0).toFixed(2)}</Td>
|
||||
<Td isNumeric>
|
||||
<VStack spacing={0} align="end">
|
||||
<Text>¥{(currentPrice || 0).toFixed(2)}</Text>
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={(change || 0) >= 0 ? 'green.500' : 'red.500'}
|
||||
>
|
||||
{(change || 0) >= 0 ? '+' : ''}{(changePercent || 0).toFixed(2)}%
|
||||
</Text>
|
||||
</VStack>
|
||||
</Td>
|
||||
<Td isNumeric>{formatCurrency(marketValue)}</Td>
|
||||
<Td isNumeric>
|
||||
<Text color={profit >= 0 ? 'green.500' : 'red.500'}>
|
||||
{formatCurrency(profit)}
|
||||
</Text>
|
||||
</Td>
|
||||
<Td isNumeric>
|
||||
<HStack justify="end" spacing={1}>
|
||||
<Icon
|
||||
as={profit >= 0 ? FiTrendingUp : FiTrendingDown}
|
||||
color={profit >= 0 ? 'green.500' : 'red.500'}
|
||||
/>
|
||||
<Text color={(profit || 0) >= 0 ? 'green.500' : 'red.500'}>
|
||||
{(profitPercent || 0).toFixed(2)}%
|
||||
</Text>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td>
|
||||
<Button
|
||||
colorScheme="red"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleSellClick(position)}
|
||||
isDisabled={(position.availableQuantity || 0) <= 0}
|
||||
>
|
||||
卖出
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
}).filter(Boolean)}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</VStack>
|
||||
|
||||
{/* 卖出对话框 */}
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="lg">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
卖出 {selectedPosition?.stockName}
|
||||
</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<VStack spacing={4} align="stretch">
|
||||
{selectedPosition && (
|
||||
<Alert status="info">
|
||||
<AlertIcon />
|
||||
<AlertDescription>
|
||||
当前持仓: {selectedPosition.quantity} 股,可卖: {selectedPosition.availableQuantity} 股,成本价: ¥{(selectedPosition.avgPrice || 0).toFixed(2)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>卖出数量(股)</FormLabel>
|
||||
<NumberInput
|
||||
value={sellQuantity}
|
||||
onChange={(value) => setSellQuantity(parseInt(value) || 0)}
|
||||
min={1}
|
||||
max={selectedPosition?.availableQuantity || 0}
|
||||
step={100}
|
||||
>
|
||||
<NumberInputField />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>订单类型</FormLabel>
|
||||
<Select value={orderType} onChange={(e) => setOrderType(e.target.value)}>
|
||||
<option value="MARKET">市价单</option>
|
||||
<option value="LIMIT">限价单</option>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{orderType === 'LIMIT' && (
|
||||
<FormControl>
|
||||
<FormLabel>限价价格(元)</FormLabel>
|
||||
<NumberInput
|
||||
value={limitPrice}
|
||||
onChange={(value) => setLimitPrice(value)}
|
||||
precision={2}
|
||||
min={0}
|
||||
>
|
||||
<NumberInputField />
|
||||
</NumberInput>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
{/* 卖出金额计算 */}
|
||||
{sellQuantity > 0 && (
|
||||
<Card bg={useColorModeValue('gray.50', 'gray.700')}>
|
||||
<CardBody py={3}>
|
||||
<VStack spacing={2} align="stretch">
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="sm" color={secondaryColor}>交易金额</Text>
|
||||
<Text fontSize="sm">{formatCurrency(totalAmount)}</Text>
|
||||
</HStack>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="sm" color={secondaryColor}>手续费</Text>
|
||||
<Text fontSize="sm">{formatCurrency(commission)}</Text>
|
||||
</HStack>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="sm" color={secondaryColor}>印花税</Text>
|
||||
<Text fontSize="sm">{formatCurrency(stampTax)}</Text>
|
||||
</HStack>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="sm" color={secondaryColor}>过户费</Text>
|
||||
<Text fontSize="sm">{formatCurrency(transferFee)}</Text>
|
||||
</HStack>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="md" fontWeight="bold">实收金额</Text>
|
||||
<Text fontSize="md" fontWeight="bold" color="green.500">
|
||||
{formatCurrency(netAmount)}
|
||||
</Text>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
</VStack>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant="ghost" mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
colorScheme="red"
|
||||
onClick={handleSellConfirm}
|
||||
isLoading={isLoading}
|
||||
loadingText="卖出中..."
|
||||
isDisabled={sellQuantity <= 0 || sellQuantity > (selectedPosition?.availableQuantity || 0)}
|
||||
>
|
||||
确认卖出
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user