'use client' import { useEffect, useState } from 'react' import { Table, Select, Typography, Tag, Button, InputNumber, App } from 'antd' interface TableData { key: string liquidity: string earnedUsd: string positionAgeMs: number tokenAaddress?: string tokenBaddress?: string [key: string]: unknown } interface ApiResponse { retCode: number result: { data: { records: Record[] total: number current: number pageSize: number pages: number poolMap?: Record } } retMsg?: string } interface PoolOption { label: string value: string } interface PoolInfo { poolAddress: string mintA?: { address: string symbol?: string mintInfo?: { symbol?: string } } mintB?: { address: string symbol?: string mintInfo?: { symbol?: string } } } interface PoolsListResponse { retCode: number result?: { data?: { records?: PoolInfo[] } } retMsg?: string } function DataTableContent() { const { message } = App.useApp() const [data, setData] = useState([]) const [loading, setLoading] = useState(true) const [poolOptions, setPoolOptions] = useState([]) const [quickCopyAmount, setQuickCopyAmount] = useState(1) const [selectedPoolAddress, setSelectedPoolAddress] = useState( 'FPBW9dtVRoUug2BeUKZAzaknd6iiet9jHM8RcTvwUkyC' ) const [pagination, setPagination] = useState({ current: 1, pageSize: 100, total: 0, }) const [sortField, setSortField] = useState('liquidity') const [expandedRowKeys, setExpandedRowKeys] = useState([]) const [childTableData, setChildTableData] = useState< Record >({}) const [childTableLoading, setChildTableLoading] = useState< Record >({}) // 获取 pools 列表 const fetchPoolsList = async () => { try { const response = await fetch('/api/pools/list?page=1&pageSize=500') if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const result: PoolsListResponse = await response.json() if (result.result?.data?.records) { const options: PoolOption[] = result.result.data.records.map((pool) => { const symbolA = pool.mintA?.mintInfo?.symbol || '' const symbolB = pool.mintB?.mintInfo?.symbol || '' const label = `${symbolA}/${symbolB}` return { label, value: pool.poolAddress, } }) setPoolOptions(options) } } catch (error) { console.error('Error fetching pools list:', error) message.error('获取 pools 列表失败') } } const fetchData = async ( page: number = 1, pageSize: number = 100, poolAddress?: string, currentSortField?: string ) => { setLoading(true) try { const response = await fetch('/api/top-positions', { method: 'POST', headers: { 'content-type': 'application/json', }, body: JSON.stringify({ poolAddress: poolAddress || selectedPoolAddress, page, pageSize, sortField: currentSortField || sortField, status: 0, }), }) // console.log(response.json()) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const result: ApiResponse = await response.json() console.log(result) if (result.result) { const { records, total, current, pageSize, poolMap } = result.result.data const poolMapData = poolMap ? (Object.values(poolMap)[0] as PoolInfo) : undefined const tokenAaddress = poolMapData?.mintA?.address const tokenBaddress = poolMapData?.mintB?.address setData( records.map((item, index) => ({ key: `${item.id || index}`, tokenAaddress, tokenBaddress, ...item, })) as TableData[] ) setExpandedRowKeys([]) setPagination({ current: current, pageSize: pageSize, total: total, }) } else { message.error(result.retMsg || '获取数据失败') } } catch (error) { console.error('Error fetching data:', error) message.error('网络请求失败,请稍后重试') } finally { setLoading(false) } } const init = async () => { await fetchPoolsList() await fetchData(1, 100) } const calculateAPR = (record: TableData) => { const useEarnSecond = Number(record.earnedUsd) / (Number(record.positionAgeMs) / 1000) const apr = (useEarnSecond * 60 * 60 * 24 * 365) / Number(record.liquidityUsd) return (apr * 100).toFixed(2) + '%' } const calculateAPRValue = (record: TableData) => { const useEarnSecond = Number(record.earnedUsd) / (Number(record.positionAgeMs) / 1000) const apr = (useEarnSecond * 60 * 60 * 24 * 365) / Number(record.liquidityUsd) return apr * 100 } function handleQuickCopy(record: TableData) { message.loading({ key: 'quickCopy', content: '复制中...', duration: 0, }) fetch('/api/lp-copy', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ positionAddress: record.positionAddress, nftMintAddress: record.nftMintAddress, maxUsdValue: quickCopyAmount, }), }) .then((res) => res.json()) .then((data) => { message.destroy('quickCopy') if (data.success) { message.success('快速复制成功') } else { message.error(data.error || '快速复制失败') } }) .catch((err) => { console.error('Error quick copying:', err) message.error('快速复制失败') }) } useEffect(() => { init() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const columns = [ { title: '创建地址', dataIndex: 'walletAddress', key: 'walletAddress', render: (text: string) => { return ( {text.slice(0, 6)}...{text.slice(-4)} ) }, }, { title: 'Liquidity', dataIndex: 'liquidityUsd', key: 'liquidityUsd', sorter: (a: TableData, b: TableData) => { return Number(a.liquidityUsd) - Number(b.liquidityUsd) }, render: (text: string) => { return ( ${Number(text).toFixed(2)} ) }, }, { title: '复制数', dataIndex: 'copies', key: 'copies', render: (text: string) => { return ( {text} ) }, }, { title: '奖励', dataIndex: 'bonusUsd', key: 'bonusUsd', render: (text: string) => { return ( ${Number(text).toFixed(2)} ) }, }, { title: 'APR', dataIndex: 'apr', key: 'apr', sorter: (a: TableData, b: TableData) => { return calculateAPRValue(a) - calculateAPRValue(b) }, render: (_text: string, record: TableData) => { return ( {calculateAPR(record)} ) }, }, { title: 'FEE', dataIndex: 'earnedUsd', key: 'earnedUsd', render: (text: string) => { return ( 0 ? '#00B098' : '#FF0000' }} > ${Number(text).toFixed(2)} ) }, }, { title: 'PNL', dataIndex: 'pnlUsd', key: 'pnlUsd', render: (text: string) => { return ( 0 ? '#00B098' : '#FF0000' }} > ${Number(text).toFixed(2)} ) }, }, { title: '创建时间', dataIndex: 'positionAgeMs', key: 'positionAgeMs', sorter: (a: TableData, b: TableData) => { return Number(a.positionAgeMs) - Number(b.positionAgeMs) }, render: (text: string) => { return ( {Math.floor(Number(text) / 1000 / 60 / 60)} 小时 ) }, }, { title: '操作', dataIndex: 'walletAddress', key: 'walletAddress', render: (text: string, record: TableData) => { return (
复制 handleQuickCopy(record)} target="_blank" > 快速复制
) }, }, ] const handleTableChange = ( newPagination: { current?: number pageSize?: number }, _filters: unknown, sorter: | { field?: string order?: 'ascend' | 'descend' | null } | unknown ) => { let currentSortField = sortField let shouldResetPage = false if (newPagination.current && newPagination.current !== pagination.current) { const targetPage = newPagination.current const targetPageSize = newPagination.pageSize || pagination.pageSize setPagination({ ...pagination, current: targetPage, pageSize: targetPageSize, }) fetchData( targetPage, targetPageSize, selectedPoolAddress, currentSortField ) } else { if (sorter && typeof sorter === 'object' && 'field' in sorter) { const sorterObj = sorter as { field?: string order?: 'ascend' | 'descend' | null } if (sorterObj.field && sorterObj.order) { // 映射字段名到 API 的 sortField const fieldMap: Record = { copies: 'copies', earnedUsd: 'earnedUsd', pnlUsd: 'pnlUsd', liquidityUsd: 'liquidity', positionAgeMs: 'positionAgeMs', } const newSortField = fieldMap[sorterObj.field] || sortField if (newSortField !== sortField) { currentSortField = newSortField shouldResetPage = true } } } } } const handlePoolChange = (value: string) => { setSelectedPoolAddress(value) fetchData(1, 100, value) } // 获取子表格数据 const fetchChildData = async (parentPositionAddress: string) => { if (childTableData[parentPositionAddress]) { // 如果已经加载过,直接返回 return } setChildTableLoading((prev) => ({ ...prev, [parentPositionAddress]: true, })) try { const response = await fetch('/api/top-positions', { method: 'POST', headers: { 'content-type': 'application/json', }, body: JSON.stringify({ poolAddress: selectedPoolAddress, parentPositionAddress, page: 1, pageSize: 500, sortField: 'liquidity', }), }) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const result: ApiResponse = await response.json() if (result.result) { const { records } = result.result.data const childData: TableData[] = records.map((item, index) => ({ key: `${parentPositionAddress}-${item.id || index}`, ...item, })) as TableData[] setChildTableData((prev) => ({ ...prev, [parentPositionAddress]: childData, })) } } catch (error) { console.error('Error fetching child data:', error) message.error('获取子表格数据失败') } finally { setChildTableLoading((prev) => ({ ...prev, [parentPositionAddress]: false, })) } } // 处理展开/收起 const handleExpand = (expanded: boolean, record: TableData) => { const positionAddress = record.positionAddress as string if (expanded && positionAddress) { setExpandedRowKeys((prev) => [...prev, record.key]) fetchChildData(positionAddress) } else { setExpandedRowKeys((prev) => prev.filter((key) => key !== record.key)) } } const handleRefresh = () => { fetchData(1, 100, selectedPoolAddress, sortField) } // 子表格的列定义 const childColumns = [ { title: '创建地址', dataIndex: 'walletAddress', key: 'walletAddress', render: (text: string) => { return ( {text.slice(0, 6)}...{text.slice(-4)} ) }, }, { title: 'Liquidity', dataIndex: 'liquidityUsd', key: 'liquidityUsd', render: (text: string) => { return ( ${Number(text).toFixed(2)} ) }, }, { title: '复制数', dataIndex: 'copies', key: 'copies', render: (text: string) => { return ( {text} ) }, }, { title: '奖励', dataIndex: 'bonusUsd', key: 'bonusUsd', render: (text: string) => { return ( ${Number(text).toFixed(2)} ) }, }, { title: 'FEE', dataIndex: 'earnedUsd', key: 'earnedUsd', render: (text: string) => { return ( 0 ? '#00B098' : '#FF0000' }} > ${Number(text).toFixed(2)} ) }, }, { title: 'PNL', dataIndex: 'pnlUsd', key: 'pnlUsd', render: (text: string) => { return ( 0 ? '#00B098' : '#FF0000' }} > ${Number(text).toFixed(2)} ) }, }, { title: '创建时间', dataIndex: 'positionAgeMs', key: 'positionAgeMs', render: (text: string) => { return ( {Math.floor(Number(text) / 1000 / 60 / 60)} 小时 ) }, }, { title: '状态', dataIndex: 'status', key: 'status', render: (status: number) => { return status === 0 ? ( Active ) : ( Inactive ) }, }, { title: '操作', dataIndex: 'walletAddress', key: 'walletAddress', render: (text: string, record: TableData) => { return ( 复制 ) }, }, ] return (