| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023 |
- 'use client'
- import { TickMath } from '@/lib/byreal-clmm-sdk/src/instructions/utils/tickMath'
- import { useEffect, useState, useRef } from 'react'
- import {
- Table,
- Select,
- Typography,
- Tag,
- Button,
- InputNumber,
- App,
- Tooltip,
- } from 'antd'
- import {
- ExportOutlined,
- CopyOutlined,
- ThunderboltOutlined,
- } from '@ant-design/icons'
- // 黑名单表格不显示(小额/BOT地址)
- const BLACK_LIST_ADDRESSES = ['LoVe', 'HZEQ', 'mVBk', 'enVr', 'MV9K']
- interface TableData {
- key: string
- walletAddress: string
- liquidity: string
- earnedUsd: string
- positionAgeMs: number
- tokenAaddress?: string
- tokenBaddress?: string
- priceRange?: string
- isInrange?: boolean
- [key: string]: unknown
- }
- interface ApiResponse {
- retCode: number
- result: {
- data: {
- records: TableData[]
- total: number
- current: number
- pageSize: number
- pages: number
- poolMap?: Record<string, PoolInfo>
- }
- }
- retMsg?: string
- }
- interface PoolOption {
- label: string
- value: string
- }
- interface PoolInfo {
- poolAddress: string
- displayReversed?: boolean
- feeUsd1d?: number
- feeUsd1h?: number
- baseMint?: {
- price?: number
- mintInfo: {
- address?: string
- symbol?: string
- decimals?: number
- }
- }
- mintA?: {
- address: string
- symbol?: string
- decimals?: number
- price?: number
- mintInfo?: {
- address?: string
- symbol?: string
- decimals?: number
- }
- }
- mintB?: {
- address: string
- symbol?: string
- decimals?: number
- price?: number
- mintInfo?: {
- address?: string
- symbol?: string
- decimals?: number
- }
- }
- }
- interface PoolsListResponse {
- retCode: number
- result?: {
- data?: {
- records?: PoolInfo[]
- }
- }
- retMsg?: string
- }
- function DataTableContent() {
- const { message } = App.useApp()
- const [data, setData] = useState<TableData[]>([])
- const [loading, setLoading] = useState(true)
- const [poolOptions, setPoolOptions] = useState<PoolOption[]>([])
- const [quickCopyAmount, setQuickCopyAmount] = useState<number>(1)
- const [balance, setBalance] = useState<number>(0)
- const [tokenName, setTokenName] = useState<string>('')
- const [userAddress, setUserAddress] = useState<string>('')
- const userAddressRef = useRef<string>('')
- const poolsListRef = useRef<PoolsListResponse>({
- retCode: 0,
- result: {
- data: {
- records: [],
- },
- },
- })
- const [selectedPoolAddress, setSelectedPoolAddress] = useState<string>(
- 'FPBW9dtVRoUug2BeUKZAzaknd6iiet9jHM8RcTvwUkyC'
- )
- const [pagination, setPagination] = useState({
- current: 1,
- pageSize: 100,
- total: 0,
- })
- const [sortField] = useState<string>('liquidity')
- const [expandedRowKeys, setExpandedRowKeys] = useState<React.Key[]>([])
- const [childTableData, setChildTableData] = useState<
- Record<string, TableData[]>
- >({})
- const [childTableLoading, setChildTableLoading] = useState<
- Record<string, boolean>
- >({})
- const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
- const [batchCopying, setBatchCopying] = useState(false)
- // const [priceLower, setPriceLower] = useState<number>(0)
- // const [priceUpper, setPriceUpper] = useState<number>(0)
- const [balanceUsd, setBalanceUsd] = useState<number>(0)
- const [currentPrice, setCurrentPrice] = useState<number>(0)
- const [feeUsd1d, setFeeUsd1d] = useState<number>(0)
- const [feeUsd1h, setFeeUsd1h] = useState<number>(0)
- const fetchBalance = async (
- tokenAddress: string,
- userAddress: string,
- price: number
- ) => {
- const response = await fetch(
- `/api/my-lp/getBalanceByToken?tokenAddress=${tokenAddress}&accountAddress=${userAddress}`
- )
- const result = await response.json()
- setBalance(result.result.data.balance)
- setBalanceUsd(
- Number((Number(result.result.data.balance) * price).toFixed(2))
- )
- return true
- }
- function fetchAddress() {
- fetch('/api/my-lp/getAddress')
- .then((res) => res.json())
- .then((data) => {
- if (data.address) {
- userAddressRef.current = data.address
- setUserAddress(data.address)
- }
- })
- .catch((err) => {
- console.error('Error fetching address:', err)
- message.error('获取地址失败')
- })
- }
- const getTokenBalance = async (
- result: PoolsListResponse,
- selectedPoolAddress: string
- ) => {
- const poolInfo = result?.result?.data?.records?.find(
- (option) => option.poolAddress === selectedPoolAddress
- )
- const price = poolInfo?.baseMint?.price || 0
- const label = poolInfo?.baseMint?.mintInfo?.symbol || ''
- const address = poolInfo?.baseMint?.mintInfo?.address || ''
- setCurrentPrice(price)
- setTokenName(label)
- setFeeUsd1d(poolInfo?.feeUsd1d || 0)
- setFeeUsd1h(poolInfo?.feeUsd1h || 0)
- return fetchBalance(address, userAddressRef.current, price)
- }
- // 获取 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 || ''
- // 如果 displayReversed 为 true,调换 AB 位置
- const label = pool.displayReversed
- ? `${symbolB}/${symbolA}`
- : `${symbolA}/${symbolB}`
- return {
- label,
- value: pool.poolAddress,
- }
- })
- poolsListRef.current = result
- setPoolOptions(options)
- await getTokenBalance(result, selectedPoolAddress)
- }
- } 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,
- }),
- })
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`)
- }
- const result: ApiResponse = await response.json()
- 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
- const getPriceRange = (
- item: Record<string, unknown>,
- poolMapData: PoolInfo | undefined,
- displayReversed?: boolean
- ) => {
- const priceUpper = TickMath.getPriceFromTick({
- tick: item.upperTick as number,
- decimalsA: poolMapData?.mintA?.decimals || 0,
- decimalsB: poolMapData?.mintB?.decimals || 0,
- baseIn: !displayReversed,
- })
- const priceLower = TickMath.getPriceFromTick({
- tick: item.lowerTick as number,
- decimalsA: poolMapData?.mintA?.decimals || 0,
- decimalsB: poolMapData?.mintB?.decimals || 0,
- baseIn: !displayReversed,
- })
- // 如果 displayReversed 为 true,调换 priceLower 和 priceUpper 位置
- if (displayReversed) {
- return `${priceUpper.toFixed(6)} - ${priceLower.toFixed(6)}`
- }
- return `${priceLower.toFixed(6)} - ${priceUpper.toFixed(6)}`
- }
- const filteredRecords = records.filter((item) => {
- return !BLACK_LIST_ADDRESSES.some((address) =>
- item.walletAddress.toLowerCase().includes(address.toLowerCase())
- )
- }) as TableData[]
- setData(
- filteredRecords.map((item, index) => ({
- ...item,
- key: `${item.id || index}`,
- priceRange: getPriceRange(
- item,
- poolMapData,
- poolMapData?.displayReversed
- ),
- tokenAaddress,
- tokenBaddress,
- })) 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 fetchAddress()
- 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,
- needSwap: true,
- }),
- })
- .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('快速复制失败')
- })
- }
- // 批量快速复制
- const handleBatchQuickCopy = async () => {
- if (selectedRowKeys.length === 0) {
- message.warning('请先选择要复制的行')
- return
- }
- const selectedRecords = data.filter((record) =>
- selectedRowKeys.includes(record.key)
- )
- if (selectedRecords.length === 0) {
- message.warning('未找到选中的记录')
- return
- }
- setBatchCopying(true)
- let successCount = 0
- let failCount = 0
- message.loading({
- key: 'batchCopy',
- content: `批量复制中... (0/${selectedRecords.length})`,
- duration: 0,
- })
- // 依次处理每条记录
- for (let i = 0; i < selectedRecords.length; i++) {
- const record = selectedRecords[i]
- try {
- const response = await fetch('/api/lp-copy', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- positionAddress: record.positionAddress,
- nftMintAddress: record.nftMintAddress,
- maxUsdValue: quickCopyAmount,
- needSwap: true,
- }),
- })
- const result = await response.json()
- if (result.success) {
- successCount++
- } else {
- failCount++
- console.error(`复制失败: ${record.positionAddress}`, result.error)
- }
- // 更新进度
- message.loading({
- key: 'batchCopy',
- content: `批量复制中... (${i + 1}/${selectedRecords.length})`,
- duration: 0,
- })
- // 添加延迟,避免请求过快
- if (i < selectedRecords.length - 1) {
- await new Promise((resolve) => setTimeout(resolve, 500))
- }
- } catch (error) {
- failCount++
- console.error(`复制失败: ${record.positionAddress}`, error)
- }
- }
- message.destroy('batchCopy')
- setBatchCopying(false)
- setSelectedRowKeys([])
- if (failCount === 0) {
- message.success(`批量复制完成!成功 ${successCount} 条`)
- } else {
- message.warning(
- `批量复制完成!成功 ${successCount} 条,失败 ${failCount} 条`
- )
- }
- }
- useEffect(() => {
- init()
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [])
- const renderPositionAgeMs = (text: string) => {
- const rawAgeMs = Number(text)
- const ageMs = Number.isFinite(rawAgeMs) ? Math.max(0, rawAgeMs) : 0
- const days = Math.floor(ageMs / 86400000)
- const hours = Math.floor((ageMs % 86400000) / 3600000)
- const minutes = Math.floor((ageMs % 3600000) / 60000)
- return (
- <span
- className="font-mono text-sm"
- style={{
- color: days > 0 ? '#2a9d61' : '#FF0000',
- }}
- >
- {days > 0 ? `${days}天/` : ''}
- {hours > 0 || days > 0 ? `${hours}小时/` : ''}
- {minutes}分钟
- </span>
- )
- }
- // 当地址获取后,设置页面标题为地址后四位字母
- useEffect(() => {
- if (userAddress) {
- const lastFour = userAddress.slice(-4)
- document.title = `${lastFour} - All Pools`
- }
- }, [userAddress])
- const columns = [
- {
- title: '创建地址',
- dataIndex: 'walletAddress',
- key: 'walletAddress',
- render: (text: string) => {
- return (
- <span className="font-bold text-lg">
- {text.slice(0, 6)}...{text.slice(-4)}
- </span>
- )
- },
- },
- {
- title: 'Liquidity',
- dataIndex: 'liquidityUsd',
- key: 'liquidityUsd',
- sorter: (a: TableData, b: TableData) => {
- return Number(a.liquidityUsd) - Number(b.liquidityUsd)
- },
- render: (text: string) => {
- return (
- <span
- className="text-orange-500 font-bold text-lg"
- style={{ color: '#00B098' }}
- >
- ${Number(text).toFixed(2)}
- </span>
- )
- },
- },
- {
- title: '区间',
- dataIndex: 'priceRange',
- key: 'priceRange',
- render: (text: string) => {
- if (
- currentPrice > Number(text.split('-')[0]) &&
- currentPrice < Number(text.split('-')[1])
- ) {
- return (
- <span className="font-bold text-lg text-green-500">{text}</span>
- )
- } else {
- return <span className="font-bold text-lg text-red-500">{text}</span>
- }
- },
- },
- {
- title: '复制数',
- dataIndex: 'copies',
- key: 'copies',
- render: (text: string) => {
- return (
- <span
- className={`font-bold text-lg ${Number(text) === 0 ? 'text-red-500' : 'text-green-500'}`}
- >
- {text}
- </span>
- )
- },
- },
- {
- title: '奖励',
- dataIndex: 'bonusUsd',
- key: 'bonusUsd',
- render: (text: string) => {
- return (
- <span className="text-orange-500 font-bold text-lg">
- ${Number(text).toFixed(2)}
- </span>
- )
- },
- },
- {
- title: 'APR',
- dataIndex: 'apr',
- key: 'apr',
- sorter: (a: TableData, b: TableData) => {
- return calculateAPRValue(a) - calculateAPRValue(b)
- },
- render: (_text: string, record: TableData) => {
- return (
- <span className="font-bold text-lg" style={{ color: '#00B098' }}>
- {calculateAPR(record)}
- </span>
- )
- },
- },
- {
- title: 'FEE',
- dataIndex: 'earnedUsd',
- key: 'earnedUsd',
- render: (text: string) => {
- return (
- <span
- className="text-orange-500 font-bold text-lg"
- style={{ color: Number(text) > 0 ? '#00B098' : '#FF0000' }}
- >
- ${Number(text).toFixed(2)}
- </span>
- )
- },
- },
- {
- title: 'PNL',
- dataIndex: 'pnlUsd',
- key: 'pnlUsd',
- render: (text: string) => {
- return (
- <span
- className="text-orange-500 font-bold text-lg"
- style={{ color: Number(text) > 0 ? '#00B098' : '#FF0000' }}
- >
- ${Number(text).toFixed(2)}
- </span>
- )
- },
- },
- {
- title: '创建时间',
- dataIndex: 'positionAgeMs',
- key: 'positionAgeMs',
- sorter: (a: TableData, b: TableData) => {
- return Number(a.positionAgeMs) - Number(b.positionAgeMs)
- },
- render: renderPositionAgeMs,
- },
- {
- title: '操作',
- dataIndex: 'walletAddress',
- key: 'walletAddress',
- width: 100,
- render: (text: string, record: TableData) => {
- return (
- <div className="flex items-center gap-3">
- <Tooltip title="复制">
- <Typography.Link
- href={`https://www.byreal.io/en/portfolio?userAddress=${text}&tab=current&positionAddress=${record.positionAddress}&tokenAddress=${record.tokenAaddress}&tokenAddress=${record.tokenBaddress}`}
- target="_blank"
- >
- <ExportOutlined style={{ fontSize: '18px' }} />
- </Typography.Link>
- </Tooltip>
- <Tooltip title="快速复制">
- <Typography.Link
- onClick={() => handleQuickCopy(record)}
- style={{ color: '#1890ff' }}
- >
- <ThunderboltOutlined
- style={{ fontSize: '18px', color: '#1890ff' }}
- />
- </Typography.Link>
- </Tooltip>
- </div>
- )
- },
- },
- ]
- const handleTableChange = (
- newPagination: {
- current?: number
- pageSize?: number
- },
- _filters: unknown,
- sorter:
- | {
- field?: string
- order?: 'ascend' | 'descend' | null
- }
- | unknown
- ) => {
- let currentSortField = sortField
- 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<string, string> = {
- copies: 'copies',
- earnedUsd: 'earnedUsd',
- pnlUsd: 'pnlUsd',
- liquidityUsd: 'liquidity',
- positionAgeMs: 'positionAgeMs',
- }
- const newSortField = fieldMap[sorterObj.field] || sortField
- if (newSortField !== sortField) {
- currentSortField = newSortField
- }
- }
- }
- }
- }
- const handlePoolChange = async (value: string) => {
- setBalance(0)
- setBalanceUsd(0)
- setSelectedPoolAddress(value)
- await getTokenBalance(poolsListRef.current, 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) => ({
- ...item,
- key: `${parentPositionAddress}-${item.id || index}`,
- })) 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 = async () => {
- setLoading(true)
- await fetchPoolsList()
- fetchData(1, 100, selectedPoolAddress, sortField)
- }
- // function handleReset() {
- // setPriceLower(0)
- // setPriceUpper(0)
- // setQuickCopyAmount(1)
- // }
- // 子表格的列定义
- const childColumns = [
- {
- title: '创建地址',
- dataIndex: 'walletAddress',
- key: 'walletAddress',
- render: (text: string) => {
- return (
- <span className="font-bold text-base">
- {text.slice(0, 6)}...{text.slice(-4)}
- </span>
- )
- },
- },
- {
- title: 'Liquidity',
- dataIndex: 'liquidityUsd',
- key: 'liquidityUsd',
- render: (text: string) => {
- return (
- <span
- className="text-orange-500 font-bold text-base"
- style={{ color: '#00B098' }}
- >
- ${Number(text).toFixed(2)}
- </span>
- )
- },
- },
- {
- title: '复制数',
- dataIndex: 'copies',
- key: 'copies',
- render: (text: string) => {
- return (
- <span className="text-green-500 font-bold text-base">{text}</span>
- )
- },
- },
- {
- title: '奖励',
- dataIndex: 'bonusUsd',
- key: 'bonusUsd',
- render: (text: string) => {
- return (
- <span className="text-orange-500 font-bold text-base">
- ${Number(text).toFixed(2)}
- </span>
- )
- },
- },
- {
- title: 'FEE',
- dataIndex: 'earnedUsd',
- key: 'earnedUsd',
- render: (text: string) => {
- return (
- <span
- className="text-orange-500 font-bold text-base"
- style={{ color: Number(text) > 0 ? '#00B098' : '#FF0000' }}
- >
- ${Number(text).toFixed(2)}
- </span>
- )
- },
- },
- {
- title: 'PNL',
- dataIndex: 'pnlUsd',
- key: 'pnlUsd',
- render: (text: string) => {
- return (
- <span
- className="text-orange-500 font-bold text-base"
- style={{ color: Number(text) > 0 ? '#00B098' : '#FF0000' }}
- >
- ${Number(text).toFixed(2)}
- </span>
- )
- },
- },
- {
- title: '创建时间',
- dataIndex: 'positionAgeMs',
- key: 'positionAgeMs',
- render: renderPositionAgeMs,
- },
- {
- title: '状态',
- dataIndex: 'status',
- key: 'status',
- render: (status: number) => {
- return status === 0 ? (
- <Tag color="green">Active</Tag>
- ) : (
- <Tag color="red">Inactive</Tag>
- )
- },
- },
- {
- title: '操作',
- dataIndex: 'walletAddress',
- key: 'walletAddress',
- render: (text: string, record: TableData) => {
- return (
- <Typography.Link
- href={`https://www.byreal.io/en/portfolio?userAddress=${text}&tab=current&positionAddress=${record.positionAddress}`}
- target="_blank"
- >
- 复制
- </Typography.Link>
- )
- },
- },
- ]
- return (
- <div style={{ padding: '24px' }}>
- <div style={{ marginBottom: '16px' }}>
- <Select
- style={{ width: 300 }}
- placeholder="选择 Pool"
- value={selectedPoolAddress}
- onChange={handlePoolChange}
- options={poolOptions}
- showSearch
- filterOption={(input, option) =>
- (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
- }
- />
- <span className="ml-2 mr-2 text-lg font-bold text-green-500">
- 余额:{balance} {tokenName} (${balanceUsd}) | 当前价格:$
- {Number(currentPrice).toFixed(4)} | 24h Fee:
- <span className="text-orange-500 font-bold text-xl">
- ${Number(feeUsd1d).toFixed(2)}
- </span>{' '}
- | 1h Fee:
- <span className="text-orange-500 font-bold text-xl">
- ${Number(feeUsd1h).toFixed(2)}
- </span>
- </span>
- <Button type="primary" className="ml-2 mr-2" onClick={handleRefresh}>
- 刷新
- </Button>
- <span className="mr-2">快速复制金额($):</span>
- <InputNumber
- placeholder="快速复制金额($)"
- value={quickCopyAmount}
- min={0.01}
- step={0.1}
- onChange={(value) => setQuickCopyAmount(value as number)}
- />
- {/* <span className="mr-2 ml-2">价格下拉(%):</span>
- <InputNumber
- placeholder="价格下拉(%)"
- value={priceLower}
- min={0}
- max={99.99}
- onChange={(value) => setPriceLower(value as number)}
- />
- <span className="mr-2 ml-2">价格上浮(%):</span>
- <InputNumber
- placeholder="价格上浮(%)"
- value={priceUpper}
- min={0}
- max={100000}
- onChange={(value) => setPriceUpper(value as number)}
- />
- <Button type="primary" className="ml-2 mr-2" onClick={handleReset}>
- 重置
- </Button> */}
- {selectedRowKeys.length > 0 && (
- <Button
- type="primary"
- danger
- className="ml-2"
- onClick={handleBatchQuickCopy}
- loading={batchCopying}
- >
- 批量快速复制 ({selectedRowKeys.length})
- </Button>
- )}
- </div>
- <Table
- size="small"
- columns={columns}
- dataSource={data}
- loading={loading}
- pagination={{
- ...pagination,
- showSizeChanger: true,
- showTotal: (total) => `共 ${total} 条`,
- }}
- onChange={handleTableChange}
- scroll={{ x: 'max-content' }}
- rowClassName={(record: TableData) => {
- return Number(record.copies) === 0 ? 'bg-red-100' : ''
- }}
- rowSelection={{
- selectedRowKeys,
- onChange: (newSelectedRowKeys) => {
- setSelectedRowKeys(newSelectedRowKeys)
- },
- }}
- expandable={{
- expandedRowKeys,
- onExpand: handleExpand,
- expandedRowRender: (record: TableData) => {
- const positionAddress = record.positionAddress as string
- const childData = childTableData[positionAddress] || []
- const isLoading = childTableLoading[positionAddress] || false
- return (
- <Table
- columns={childColumns}
- dataSource={childData}
- loading={isLoading}
- pagination={false}
- size="small"
- scroll={{ x: 'max-content' }}
- rowClassName={(childRecord: TableData) => {
- return Number(childRecord.copies) === 0 ? 'bg-red-100' : ''
- }}
- />
- )
- },
- }}
- />
- </div>
- )
- }
- export default function DataTable() {
- return <DataTableContent />
- }
|