import { Alert, Card, Descriptions, Empty, Space, Table, Tag, Typography } from 'antd'; import type { ColumnsType } from 'antd/es/table'; const { Text } = Typography; /** 一键查询的结果结构(后端 `query_account_overview` 写入 task.result)。 */ export interface HighValueGoods { name: string; commodity_id?: string; score?: number | null; count?: number | null; store_status?: number | null; count_limited?: number | null; start_time?: string | number | null; end_time?: string | number | null; pic?: string; /** **是否已获得** —— 依据是斗鱼官方的账号级兑换记录,不是全局库存 */ owned?: boolean | null; /** 官方兑换记录里命中的商品名(可能是「破片手榴弹-兔兔白日梦」这类变体) */ obtained_names?: string[]; /** 该商品当前是否在限兑列表里(全局信息,与"是否已获得"无关) */ in_goods_list?: boolean; /** 限兑列表和兑换记录里都没有这个商品 */ missing?: boolean; reason?: string; } export interface AccountOverviewResult { queried_at?: string; assets?: { gold_balance?: number | null; exchange_balance?: number | null; points?: number | null; points_expire_at?: string | number | null; }; elite?: { opened?: boolean | null; expire_time?: number | null; points?: number | null }; esports?: { opened?: boolean | null; manual_type?: number | null; points?: number | null; expire_time?: string | number | null; }; high_value_goods?: HighValueGoods[]; limited_goods_total?: number; exchange_records?: Array>; exchange_record_count?: number; /** 单项失败:**必须展示**,否则「没查到」会被读成「没有」 */ errors?: Record; } const ERROR_LABELS: Record = { gold: '鱼翅', points: '积分', elite: '精英宝典', esports: '电竞手册', high_value_goods: '高价值套装', exchange_records: '兑换记录', }; /** 时间戳(秒/毫秒/字符串)→ 可读文本;认不出来就原样显示。 */ function formatTime(value: string | number | null | undefined): string { if (value === null || value === undefined || value === '') return '-'; const text = String(value).trim(); let seconds: number | null = null; if (/^\d+$/.test(text)) { seconds = Number(text); if (seconds > 10_000_000_000) seconds /= 1000; } if (seconds !== null) { const d = new Date(seconds * 1000); if (!Number.isNaN(d.getTime())) return d.toLocaleString('zh-CN'); return text; } const d = new Date(text); return Number.isNaN(d.getTime()) ? text : d.toLocaleString('zh-CN'); } function yesNo(value: boolean | null | undefined): string { if (value === true) return '已开通'; if (value === false) return '未开通'; return '未知'; } /** * 高价值套装卡片。 * * ⚠️ 措辞刻意不用「已拥有」:`storeStatus=0` 也可能是**全局售罄/活动下架**, * 我们没法只凭它断言账号真的拿到了。所以写「不可再兑」并把 count 一起显示, * 让运营自己判断 —— 这比给一个可能错的结论好。 */ function HighValueCard({ goods }: { goods: HighValueGoods }) { if (goods.missing) { return ( {goods.name} 未查到该商品 {goods.reason || '限兑列表和官方兑换记录里都没有它'} ); } // ⚠️ 「是否已获得」只看官方兑换记录。`count`/`store_status` 是**全局库存**, // 同一时刻不同账号看到的都一样,**不能**用来判断这个账号有没有。 const owned = goods.owned === true; return ( {goods.name}} extra={ {owned ? '✅ 已获得' : '未获得'} } > {owned && ( {(goods.obtained_names || []).join('、') || '-'} )} {goods.score ?? '-'} {goods.count ?? '-'} {goods.count === 0 ? '(已售罄)' : ''} {formatTime(goods.start_time)} ~ {formatTime(goods.end_time)} {!goods.in_goods_list && ( 当前限兑列表里没有它(活动窗口可能已结束),但兑换记录仍在。 )} ); } export default function AccountOverviewPanel({ result }: { result: AccountOverviewResult }) { if (!result) return ; const assets = result.assets || {}; const errors = result.errors || {}; const goods = result.high_value_goods || []; const records = result.exchange_records || []; const recordColumns: ColumnsType> = [ { title: '商品', dataIndex: 'commodityName', key: 'commodityName', render: (v: unknown) => String(v ?? '-'), }, { title: '时间', key: 'time', render: (_, row) => formatTime( (row.createTime ?? row.create_time ?? row.time) as string | number | null, ), }, ]; return ( {Object.keys(errors).length > 0 && ( {Object.entries(errors).map(([key, msg]) => (
  • {ERROR_LABELS[key] || key}:{msg}
  • ))} } /> )} {assets.gold_balance ?? '-'} {assets.exchange_balance ?? '-'} {assets.points ?? '-'} {formatTime(assets.points_expire_at)} {yesNo(result.elite?.opened)} {formatTime(result.elite?.expire_time)} {yesNo(result.esports?.opened)} {result.esports?.points ?? '-'} {goods.length === 0 ? ( 没有配置关注的高价值套装,或本次没取到 ) : ( {goods.map((g) => ( ))} )} {records.length === 0 ? ( 官方暂无兑换记录 ) : ( String(i)} columns={recordColumns} dataSource={records} pagination={{ pageSize: 10, size: 'small' }} /> )} 查询时间:{formatTime(result.queried_at)} ); }