feat: 优化订单列表筛选功能,支持按市场标题搜索并改进分组体验
- 后端:MarketGroupedOrdersRequest 添加 marketId 和 marketTitle 字段 - 后端:买入/卖出订单分组接口支持市场 ID 模糊匹配和市场标题关键字筛选 - 前端:BuyOrdersTab 添加市场标题筛选,移除方向筛选,记录用户分组偏好 - 前端:SellOrdersTab 添加市场标题筛选,将方向筛选改为状态筛选,移除方向列 - 前端:MatchedOrdersTab 添加市场标题筛选和市场列显示 - 前端:所有搜索输入框添加 0.5 秒防抖优化 - 前端:分组偏好保存到 localStorage 以便跨会话持久化
This commit is contained in:
@@ -110,7 +110,7 @@ data class OrderTrackingRequest(
|
||||
val page: Int? = 1,
|
||||
val limit: Int? = 20,
|
||||
val marketId: String? = null,
|
||||
val side: String? = null,
|
||||
val marketTitle: String? = null, // 市场标题关键字筛选
|
||||
val status: String? = null,
|
||||
val sellOrderId: String? = null,
|
||||
val buyOrderId: String? = null
|
||||
@@ -123,7 +123,9 @@ data class MarketGroupedOrdersRequest(
|
||||
val copyTradingId: Long,
|
||||
val type: String, // buy, sell, matched
|
||||
val page: Int? = 1,
|
||||
val limit: Int? = 20
|
||||
val limit: Int? = 20,
|
||||
val marketId: String? = null,
|
||||
val marketTitle: String? = null
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
+88
-25
@@ -134,12 +134,21 @@ class CopyTradingStatisticsService(
|
||||
private fun getBuyOrderList(request: OrderTrackingRequest): Pair<List<BuyOrderInfo>, Long> {
|
||||
var orders = copyOrderTrackingRepository.findByCopyTradingId(request.copyTradingId)
|
||||
|
||||
// 批量获取市场信息(用于筛选)
|
||||
val allMarketIds = orders.map { it.marketId }.distinct()
|
||||
val markets = marketService.getMarkets(allMarketIds)
|
||||
|
||||
// 筛选
|
||||
if (!request.marketId.isNullOrBlank()) {
|
||||
orders = orders.filter { it.marketId == request.marketId }
|
||||
// marketId 支持模糊匹配
|
||||
orders = orders.filter { it.marketId.contains(request.marketId!!, ignoreCase = true) }
|
||||
}
|
||||
if (!request.side.isNullOrBlank()) {
|
||||
orders = orders.filter { it.side == request.side }
|
||||
if (!request.marketTitle.isNullOrBlank()) {
|
||||
// marketTitle 关键字筛选
|
||||
orders = orders.filter { order ->
|
||||
val market = markets[order.marketId]
|
||||
market?.title?.contains(request.marketTitle!!, ignoreCase = true) == true
|
||||
}
|
||||
}
|
||||
if (!request.status.isNullOrBlank()) {
|
||||
orders = orders.filter { it.status == request.status }
|
||||
@@ -157,10 +166,6 @@ class CopyTradingStatisticsService(
|
||||
val end = minOf(start + limit, orders.size)
|
||||
val pagedOrders = if (start < orders.size) orders.subList(start, end) else emptyList()
|
||||
|
||||
// 批量获取市场信息
|
||||
val marketIds = pagedOrders.map { it.marketId }.distinct()
|
||||
val markets = marketService.getMarkets(marketIds)
|
||||
|
||||
// 转换为DTO
|
||||
val list = pagedOrders.map { order ->
|
||||
val amount = order.quantity.toSafeBigDecimal().multi(order.price)
|
||||
@@ -193,12 +198,21 @@ class CopyTradingStatisticsService(
|
||||
private fun getSellOrderList(request: OrderTrackingRequest): Pair<List<SellOrderInfo>, Long> {
|
||||
var records = sellMatchRecordRepository.findByCopyTradingId(request.copyTradingId)
|
||||
|
||||
// 批量获取市场信息(用于筛选)
|
||||
val allMarketIds = records.map { it.marketId }.distinct()
|
||||
val markets = marketService.getMarkets(allMarketIds)
|
||||
|
||||
// 筛选
|
||||
if (!request.marketId.isNullOrBlank()) {
|
||||
records = records.filter { it.marketId == request.marketId }
|
||||
// marketId 支持模糊匹配
|
||||
records = records.filter { it.marketId.contains(request.marketId!!, ignoreCase = true) }
|
||||
}
|
||||
if (!request.side.isNullOrBlank()) {
|
||||
records = records.filter { it.side == request.side }
|
||||
if (!request.marketTitle.isNullOrBlank()) {
|
||||
// marketTitle 关键字筛选
|
||||
records = records.filter { record ->
|
||||
val market = markets[record.marketId]
|
||||
market?.title?.contains(request.marketTitle!!, ignoreCase = true) == true
|
||||
}
|
||||
}
|
||||
|
||||
val total = records.size.toLong()
|
||||
@@ -213,10 +227,6 @@ class CopyTradingStatisticsService(
|
||||
val end = minOf(start + limit, records.size)
|
||||
val pagedRecords = if (start < records.size) records.subList(start, end) else emptyList()
|
||||
|
||||
// 批量获取市场信息
|
||||
val marketIds = pagedRecords.map { it.marketId }.distinct()
|
||||
val markets = marketService.getMarkets(marketIds)
|
||||
|
||||
// 转换为DTO
|
||||
val list = pagedRecords.map { record ->
|
||||
val amount = record.totalMatchedQuantity.toSafeBigDecimal().multi(record.sellPrice)
|
||||
@@ -247,6 +257,14 @@ class CopyTradingStatisticsService(
|
||||
private fun getMatchedOrderList(request: OrderTrackingRequest): Pair<List<MatchedOrderInfo>, Long> {
|
||||
val matchDetails = sellMatchDetailRepository.findByCopyTradingId(request.copyTradingId)
|
||||
|
||||
// 获取所有相关的卖出记录(用于筛选)
|
||||
val matchRecordIds = matchDetails.map { it.matchRecordId }.distinct()
|
||||
val matchRecords = matchRecordIds.mapNotNull { id ->
|
||||
sellMatchRecordRepository.findById(id).orElse(null)
|
||||
}
|
||||
val marketIds = matchRecords.map { it.marketId }.distinct()
|
||||
val markets = marketService.getMarkets(marketIds)
|
||||
|
||||
// 筛选
|
||||
var filtered = matchDetails
|
||||
if (!request.sellOrderId.isNullOrBlank()) {
|
||||
@@ -260,6 +278,21 @@ class CopyTradingStatisticsService(
|
||||
if (!request.buyOrderId.isNullOrBlank()) {
|
||||
filtered = filtered.filter { it.buyOrderId == request.buyOrderId }
|
||||
}
|
||||
if (!request.marketId.isNullOrBlank()) {
|
||||
// marketId 支持模糊匹配
|
||||
filtered = filtered.filter { detail ->
|
||||
val matchRecord = matchRecords.find { it.id == detail.matchRecordId }
|
||||
matchRecord?.marketId?.contains(request.marketId!!, ignoreCase = true) == true
|
||||
}
|
||||
}
|
||||
if (!request.marketTitle.isNullOrBlank()) {
|
||||
// marketTitle 关键字筛选
|
||||
filtered = filtered.filter { detail ->
|
||||
val matchRecord = matchRecords.find { it.id == detail.matchRecordId }
|
||||
val market = matchRecord?.let { markets[it.marketId] }
|
||||
market?.title?.contains(request.marketTitle!!, ignoreCase = true) == true
|
||||
}
|
||||
}
|
||||
|
||||
val total = filtered.size.toLong()
|
||||
|
||||
@@ -274,17 +307,17 @@ class CopyTradingStatisticsService(
|
||||
val pagedDetails = if (start < filtered.size) filtered.subList(start, end) else emptyList()
|
||||
|
||||
// 获取匹配记录以获取市场ID
|
||||
val matchRecordIds = pagedDetails.map { it.matchRecordId }.distinct()
|
||||
val matchRecords = matchRecordIds.mapNotNull { id ->
|
||||
val pagedMatchRecordIds = pagedDetails.map { it.matchRecordId }.distinct()
|
||||
val pagedMatchRecords = pagedMatchRecordIds.mapNotNull { id ->
|
||||
sellMatchRecordRepository.findById(id).orElse(null)
|
||||
}
|
||||
val marketIds = matchRecords.map { it.marketId }.distinct()
|
||||
val markets = marketService.getMarkets(marketIds)
|
||||
val pagedMarketIds = pagedMatchRecords.map { it.marketId }.distinct()
|
||||
val pagedMarkets = marketService.getMarkets(pagedMarketIds)
|
||||
|
||||
// 转换为DTO
|
||||
val list = pagedDetails.map { detail ->
|
||||
val matchRecord = matchRecords.find { it.id == detail.matchRecordId }
|
||||
val market = matchRecord?.let { markets[it.marketId] }
|
||||
val matchRecord = pagedMatchRecords.find { it.id == detail.matchRecordId }
|
||||
val market = matchRecord?.let { pagedMarkets[it.marketId] }
|
||||
MatchedOrderInfo(
|
||||
sellOrderId = matchRecord?.sellOrderId ?: "",
|
||||
buyOrderId = detail.buyOrderId,
|
||||
@@ -550,7 +583,23 @@ class CopyTradingStatisticsService(
|
||||
// 2. 获取所有买入订单
|
||||
var orders = copyOrderTrackingRepository.findByCopyTradingId(request.copyTradingId)
|
||||
|
||||
// 3. 按市场ID分组
|
||||
// 3. 批量获取市场信息(用于筛选)
|
||||
val allMarketIds = orders.map { it.marketId }.distinct()
|
||||
val markets = marketService.getMarkets(allMarketIds)
|
||||
|
||||
// 4. 筛选
|
||||
if (!request.marketId.isNullOrBlank()) {
|
||||
// marketId 支持模糊匹配
|
||||
orders = orders.filter { it.marketId.contains(request.marketId!!, ignoreCase = true) }
|
||||
}
|
||||
if (!request.marketTitle.isNullOrBlank()) {
|
||||
// marketTitle 关键字筛选
|
||||
orders = orders.filter { order ->
|
||||
val market = markets[order.marketId]
|
||||
market?.title?.contains(request.marketTitle!!, ignoreCase = true) == true
|
||||
}
|
||||
}
|
||||
// 5. 按市场ID分组
|
||||
val groups = mutableMapOf<String, MutableList<CopyOrderTracking>>()
|
||||
orders.forEach { order ->
|
||||
val marketId = order.marketId
|
||||
@@ -562,7 +611,6 @@ class CopyTradingStatisticsService(
|
||||
|
||||
// 4. 转换为分组数据并计算统计信息
|
||||
val marketIds = groups.keys.toList()
|
||||
val markets = marketService.getMarkets(marketIds)
|
||||
|
||||
val list = marketIds.map { marketId ->
|
||||
val marketOrders = groups[marketId] ?: mutableListOf()
|
||||
@@ -659,9 +707,25 @@ class CopyTradingStatisticsService(
|
||||
?: return Result.failure(IllegalArgumentException("跟单关系不存在: ${request.copyTradingId}"))
|
||||
|
||||
// 2. 获取所有卖出记录
|
||||
val sellRecords = sellMatchRecordRepository.findByCopyTradingId(request.copyTradingId)
|
||||
var sellRecords = sellMatchRecordRepository.findByCopyTradingId(request.copyTradingId)
|
||||
|
||||
// 3. 按市场ID分组
|
||||
// 3. 批量获取市场信息(用于筛选)
|
||||
val allMarketIds = sellRecords.map { it.marketId }.distinct()
|
||||
val markets = marketService.getMarkets(allMarketIds)
|
||||
|
||||
// 4. 筛选
|
||||
if (!request.marketId.isNullOrBlank()) {
|
||||
// marketId 支持模糊匹配
|
||||
sellRecords = sellRecords.filter { it.marketId.contains(request.marketId!!, ignoreCase = true) }
|
||||
}
|
||||
if (!request.marketTitle.isNullOrBlank()) {
|
||||
// marketTitle 关键字筛选
|
||||
sellRecords = sellRecords.filter { record ->
|
||||
val market = markets[record.marketId]
|
||||
market?.title?.contains(request.marketTitle!!, ignoreCase = true) == true
|
||||
}
|
||||
}
|
||||
// 5. 按市场ID分组
|
||||
val groups = mutableMapOf<String, MutableList<SellMatchRecord>>()
|
||||
sellRecords.forEach { record ->
|
||||
val marketId = record.marketId
|
||||
@@ -673,7 +737,6 @@ class CopyTradingStatisticsService(
|
||||
|
||||
// 4. 转换为分组数据并计算统计信息
|
||||
val marketIds = groups.keys.toList()
|
||||
val markets = marketService.getMarkets(marketIds)
|
||||
|
||||
val list = marketIds.map { marketId ->
|
||||
val marketRecords = groups[marketId] ?: mutableListOf()
|
||||
|
||||
@@ -1089,6 +1089,7 @@
|
||||
"price": "价格",
|
||||
"amount": "金额",
|
||||
"filterMarketId": "筛选市场ID",
|
||||
"filterMarketTitle": "筛选市场标题",
|
||||
"filterSide": "筛选方向",
|
||||
"filterStatus": "筛选状态",
|
||||
"filterSellOrderId": "筛选卖出订单ID",
|
||||
|
||||
@@ -25,19 +25,44 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId, active = fal
|
||||
const [limit, setLimit] = useState(20)
|
||||
const [filters, setFilters] = useState<{
|
||||
marketId?: string
|
||||
side?: string
|
||||
marketTitle?: string
|
||||
status?: string
|
||||
}>({})
|
||||
const [groupByMarket, setGroupByMarket] = useState(false) // 是否按市场分组
|
||||
// 从 localStorage 读取用户分组偏好,如果没有则默认为 false
|
||||
const [groupByMarket, setGroupByMarket] = useState(() => {
|
||||
const savedPreference = localStorage.getItem('copyTradingOrders_groupByMarket')
|
||||
return savedPreference === 'true'
|
||||
})
|
||||
const [expandedMarkets, setExpandedMarkets] = useState<Set<string>>(new Set()) // 展开的市场ID集合
|
||||
const [groupedOrders, setGroupedOrders] = useState<MarketOrderGroup[]>([]) // 分组数据(从后端获取)
|
||||
const [groupedTotal, setGroupedTotal] = useState(0) // 分组总数(市场数量)
|
||||
|
||||
const handleMarketTitleChange = (value: string) => {
|
||||
setFilters({ ...filters, marketTitle: value || undefined })
|
||||
}
|
||||
|
||||
// 切换分组模式时,保存用户偏好到 localStorage
|
||||
const handleToggleGroupByMarket = (checked: boolean) => {
|
||||
setGroupByMarket(checked)
|
||||
localStorage.setItem('copyTradingOrders_groupByMarket', String(checked))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (copyTradingId && active) {
|
||||
fetchOrders()
|
||||
}
|
||||
}, [copyTradingId, active, page, limit, filters, groupByMarket])
|
||||
}, [copyTradingId, active, page, limit, filters.marketId, filters.status, groupByMarket])
|
||||
|
||||
// 防抖搜索 - marketTitle 和 marketId 变化时延迟0.5秒再搜索
|
||||
useEffect(() => {
|
||||
if (!copyTradingId || !active) return
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
fetchOrders()
|
||||
}, 500) // 0.5秒延迟
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [filters.marketTitle])
|
||||
|
||||
const fetchOrders = async () => {
|
||||
if (!copyTradingId) return
|
||||
@@ -45,12 +70,14 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId, active = fal
|
||||
setLoading(true)
|
||||
try {
|
||||
if (groupByMarket) {
|
||||
// 调用分组接口
|
||||
// 调用分组接口(支持市场筛选)
|
||||
const request: MarketGroupedOrdersRequest = {
|
||||
copyTradingId: parseInt(copyTradingId),
|
||||
type: 'buy',
|
||||
page,
|
||||
limit
|
||||
limit,
|
||||
marketId: filters.marketId,
|
||||
marketTitle: filters.marketTitle
|
||||
}
|
||||
|
||||
const response = await apiService.orderTracking.listGroupedByMarket(request)
|
||||
@@ -123,7 +150,7 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId, active = fal
|
||||
}
|
||||
}
|
||||
|
||||
const columns = [
|
||||
const columns = [
|
||||
{
|
||||
title: t('copyTradingOrders.orderId') || '订单ID',
|
||||
dataIndex: 'orderId',
|
||||
@@ -208,16 +235,6 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId, active = fal
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('copyTradingOrders.side') || '方向',
|
||||
dataIndex: 'side',
|
||||
key: 'side',
|
||||
width: isMobile ? 60 : 80,
|
||||
render: (side: string) => {
|
||||
const displaySide = side === '0' ? 'YES' : side === '1' ? 'NO' : side
|
||||
return <Tag style={{ fontSize: isMobile ? 11 : 12 }}>{displaySide}</Tag>
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('copyTradingOrders.buyQuantity') || '买入数量',
|
||||
dataIndex: 'quantity',
|
||||
@@ -400,7 +417,6 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId, active = fal
|
||||
minute: '2-digit'
|
||||
})
|
||||
const amount = (parseFloat(order.quantity) * parseFloat(order.price)).toString()
|
||||
const displaySide = order.side === '0' ? 'YES' : order.side === '1' ? 'NO' : order.side
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -436,7 +452,6 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId, active = fal
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', alignItems: 'center' }}>
|
||||
<Tag style={{ fontSize: '11px' }}>{displaySide}</Tag>
|
||||
{getStatusTag(order.status)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -485,18 +500,17 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId, active = fal
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||
{orders.map((order) => {
|
||||
const date = new Date(order.createdAt)
|
||||
const formattedDate = date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
const amount = (parseFloat(order.quantity) * parseFloat(order.price)).toString()
|
||||
const displaySide = order.side === '0' ? 'YES' : order.side === '1' ? 'NO' : order.side
|
||||
const date = new Date(order.createdAt)
|
||||
const formattedDate = date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
const amount = (parseFloat(order.quantity) * parseFloat(order.price)).toString()
|
||||
|
||||
return (
|
||||
return (
|
||||
<Card
|
||||
key={order.orderId}
|
||||
style={{
|
||||
@@ -529,7 +543,6 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId, active = fal
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', alignItems: 'center' }}>
|
||||
<Tag>{displaySide}</Tag>
|
||||
{getStatusTag(order.status)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -633,23 +646,20 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId, active = fal
|
||||
<div>
|
||||
<div style={{ marginBottom: 16, display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<Input
|
||||
placeholder={t('copyTradingOrders.filterMarketId') || '筛选市场ID'}
|
||||
placeholder={t('copyTradingOrders.filterMarketId') || '筛选市场ID(支持模糊匹配)'}
|
||||
allowClear
|
||||
style={{ width: isMobile ? '100%' : 200 }}
|
||||
value={filters.marketId}
|
||||
onChange={(e) => setFilters({ ...filters, marketId: e.target.value || undefined })}
|
||||
/>
|
||||
|
||||
<Select
|
||||
placeholder={t('copyTradingOrders.filterSide') || '筛选方向'}
|
||||
<Input
|
||||
placeholder={t('copyTradingOrders.filterMarketTitle') || '筛选市场标题'}
|
||||
allowClear
|
||||
style={{ width: isMobile ? '100%' : 150 }}
|
||||
value={filters.side}
|
||||
onChange={(value) => setFilters({ ...filters, side: value || undefined })}
|
||||
>
|
||||
<Option value="0">YES</Option>
|
||||
<Option value="1">NO</Option>
|
||||
</Select>
|
||||
style={{ width: isMobile ? '100%' : 200 }}
|
||||
value={filters.marketTitle}
|
||||
onChange={(e) => handleMarketTitleChange(e.target.value)}
|
||||
/>
|
||||
|
||||
<Select
|
||||
placeholder={t('copyTradingOrders.filterStatus') || '筛选状态'}
|
||||
@@ -669,7 +679,7 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId, active = fal
|
||||
</span>
|
||||
<Switch
|
||||
checked={groupByMarket}
|
||||
onChange={setGroupByMarket}
|
||||
onChange={handleToggleGroupByMarket}
|
||||
checkedChildren={<AppstoreOutlined />}
|
||||
unCheckedChildren={<UnorderedListOutlined />}
|
||||
/>
|
||||
|
||||
@@ -24,13 +24,29 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId, acti
|
||||
const [filters, setFilters] = useState<{
|
||||
sellOrderId?: string
|
||||
buyOrderId?: string
|
||||
marketTitle?: string
|
||||
}>({})
|
||||
|
||||
const handleMarketTitleChange = (value: string) => {
|
||||
setFilters({ ...filters, marketTitle: value || undefined })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (copyTradingId && active) {
|
||||
fetchOrders()
|
||||
}
|
||||
}, [copyTradingId, active, page, limit, filters])
|
||||
}, [copyTradingId, active, page, limit])
|
||||
|
||||
// 防抖搜索 - marketTitle 和 orderId 变化时延迟0.5秒再搜索
|
||||
useEffect(() => {
|
||||
if (!copyTradingId || !active) return
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
fetchOrders()
|
||||
}, 500) // 0.5秒延迟
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [filters.marketTitle, filters.buyOrderId, filters.sellOrderId])
|
||||
|
||||
const fetchOrders = async () => {
|
||||
if (!copyTradingId) return
|
||||
@@ -193,6 +209,14 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId, acti
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: 16, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
|
||||
<Input
|
||||
placeholder={t('copyTradingOrders.filterMarketTitle') || '筛选市场标题'}
|
||||
allowClear
|
||||
style={{ width: isMobile ? '100%' : 200 }}
|
||||
value={filters.marketTitle}
|
||||
onChange={(e) => handleMarketTitleChange(e.target.value)}
|
||||
/>
|
||||
|
||||
<Input
|
||||
placeholder={t('copyTradingOrders.filterSellOrderId') || '筛选卖出订单ID'}
|
||||
allowClear
|
||||
|
||||
@@ -25,18 +25,44 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId, active = f
|
||||
const [limit, setLimit] = useState(20)
|
||||
const [filters, setFilters] = useState<{
|
||||
marketId?: string
|
||||
side?: string
|
||||
marketTitle?: string
|
||||
status?: string
|
||||
}>({})
|
||||
const [groupByMarket, setGroupByMarket] = useState(false) // 是否按市场分组
|
||||
// 从 localStorage 读取用户分组偏好,如果没有则默认为 false
|
||||
const [groupByMarket, setGroupByMarket] = useState(() => {
|
||||
const savedPreference = localStorage.getItem('copyTradingOrders_groupByMarket_sell')
|
||||
return savedPreference === 'true'
|
||||
})
|
||||
const [expandedMarkets, setExpandedMarkets] = useState<Set<string>>(new Set()) // 展开的市场ID集合
|
||||
const [groupedOrders, setGroupedOrders] = useState<MarketOrderGroup[]>([]) // 分组数据(从后端获取)
|
||||
const [groupedTotal, setGroupedTotal] = useState(0) // 分组总数(市场数量)
|
||||
|
||||
const handleMarketTitleChange = (value: string) => {
|
||||
setFilters({ ...filters, marketTitle: value || undefined })
|
||||
}
|
||||
|
||||
// 切换分组模式时,保存用户偏好到 localStorage
|
||||
const handleToggleGroupByMarket = (checked: boolean) => {
|
||||
setGroupByMarket(checked)
|
||||
localStorage.setItem('copyTradingOrders_groupByMarket_sell', String(checked))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (copyTradingId && active) {
|
||||
fetchOrders()
|
||||
}
|
||||
}, [copyTradingId, active, page, limit, filters, groupByMarket])
|
||||
}, [copyTradingId, active, page, limit, filters.marketId, filters.status, groupByMarket])
|
||||
|
||||
// 防抖搜索 - marketTitle 变化时延迟0.5秒再搜索
|
||||
useEffect(() => {
|
||||
if (!copyTradingId || !active) return
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
fetchOrders()
|
||||
}, 500) // 0.5秒延迟
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [filters.marketTitle])
|
||||
|
||||
const fetchOrders = async () => {
|
||||
if (!copyTradingId) return
|
||||
@@ -49,7 +75,9 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId, active = f
|
||||
copyTradingId: parseInt(copyTradingId),
|
||||
type: 'sell',
|
||||
page,
|
||||
limit
|
||||
limit,
|
||||
marketId: filters.marketId,
|
||||
marketTitle: filters.marketTitle
|
||||
}
|
||||
|
||||
const response = await apiService.orderTracking.listGroupedByMarket(request)
|
||||
@@ -204,16 +232,6 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId, active = f
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('copyTradingOrders.side') || '方向',
|
||||
dataIndex: 'side',
|
||||
key: 'side',
|
||||
width: isMobile ? 60 : 80,
|
||||
render: (side: string) => {
|
||||
const displaySide = side === '0' ? 'YES' : side === '1' ? 'NO' : side
|
||||
return <Tag style={{ fontSize: isMobile ? 11 : 12 }}>{displaySide}</Tag>
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('copyTradingOrders.sellQuantity') || '卖出数量',
|
||||
dataIndex: 'quantity',
|
||||
@@ -378,7 +396,6 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId, active = f
|
||||
minute: '2-digit'
|
||||
})
|
||||
const amount = (parseFloat(order.quantity) * parseFloat(order.price)).toString()
|
||||
const displaySide = order.side === '0' ? 'YES' : order.side === '1' ? 'NO' : order.side
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -413,9 +430,6 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId, active = f
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', alignItems: 'center' }}>
|
||||
<Tag style={{ fontSize: '11px' }}>{displaySide}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
@@ -473,7 +487,6 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId, active = f
|
||||
minute: '2-digit'
|
||||
})
|
||||
const amount = (parseFloat(order.quantity) * parseFloat(order.price)).toString()
|
||||
const displaySide = order.side === '0' ? 'YES' : order.side === '1' ? 'NO' : order.side
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -507,9 +520,6 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId, active = f
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', alignItems: 'center' }}>
|
||||
<Tag>{displaySide}</Tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider style={{ margin: '12px 0' }} />
|
||||
@@ -622,17 +632,24 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId, active = f
|
||||
onChange={(e) => setFilters({ ...filters, marketId: e.target.value || undefined })}
|
||||
/>
|
||||
|
||||
<Input
|
||||
placeholder={t('copyTradingOrders.filterMarketTitle') || '筛选市场标题'}
|
||||
allowClear
|
||||
style={{ width: isMobile ? '100%' : 200 }}
|
||||
value={filters.marketTitle}
|
||||
onChange={(e) => handleMarketTitleChange(e.target.value)}
|
||||
/>
|
||||
|
||||
<Select
|
||||
placeholder={t('copyTradingOrders.filterSide') || '筛选方向'}
|
||||
placeholder={t('copyTradingOrders.filterStatus') || '筛选状态'}
|
||||
allowClear
|
||||
style={{ width: isMobile ? '100%' : 150 }}
|
||||
value={filters.side}
|
||||
onChange={(value) => setFilters({ ...filters, side: value || undefined })}
|
||||
value={filters.status}
|
||||
onChange={(value) => setFilters({ ...filters, status: value || undefined })}
|
||||
>
|
||||
<Option value="0">YES</Option>
|
||||
<Option value="1">NO</Option>
|
||||
<Option value="YES">YES</Option>
|
||||
<Option value="NO">NO</Option>
|
||||
<Option value="filled">{t('copyTradingOrders.statusFilled') || '未成交'}</Option>
|
||||
<Option value="partially_matched">{t('copyTradingOrders.statusPartiallyMatched') || '部分成交'}</Option>
|
||||
<Option value="fully_matched">{t('copyTradingOrders.statusFullyMatched') || '完全成交'}</Option>
|
||||
</Select>
|
||||
|
||||
<Space>
|
||||
@@ -641,7 +658,7 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId, active = f
|
||||
</span>
|
||||
<Switch
|
||||
checked={groupByMarket}
|
||||
onChange={setGroupByMarket}
|
||||
onChange={handleToggleGroupByMarket}
|
||||
checkedChildren={<AppstoreOutlined />}
|
||||
unCheckedChildren={<UnorderedListOutlined />}
|
||||
/>
|
||||
|
||||
@@ -670,6 +670,7 @@ export interface SellOrderInfo {
|
||||
price: string
|
||||
amount: string
|
||||
realizedPnl: string
|
||||
status?: string // 卖出状态(filled, partially_matched, fully_matched)
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
@@ -710,7 +711,7 @@ export interface OrderTrackingRequest {
|
||||
page?: number
|
||||
limit?: number
|
||||
marketId?: string
|
||||
side?: string
|
||||
marketTitle?: string
|
||||
status?: string
|
||||
sellOrderId?: string
|
||||
buyOrderId?: string
|
||||
@@ -724,6 +725,8 @@ export interface MarketGroupedOrdersRequest {
|
||||
type: 'buy' | 'sell'
|
||||
page?: number
|
||||
limit?: number
|
||||
marketId?: string
|
||||
marketTitle?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user