feat: add xpd purchase records view
This commit is contained in:
@@ -823,6 +823,7 @@ class DouyuActivityClient:
|
|||||||
XPD_BIND_INFO_API = "https://www.douyu.com/japi/carnivalApi/v2/tencent/bindInfo"
|
XPD_BIND_INFO_API = "https://www.douyu.com/japi/carnivalApi/v2/tencent/bindInfo"
|
||||||
XPD_DAOJU_REFERER = "https://app.daoju.qq.com/"
|
XPD_DAOJU_REFERER = "https://app.daoju.qq.com/"
|
||||||
XPD_GET_ROLE_API = "https://apps.game.qq.com/daoju/igw/live"
|
XPD_GET_ROLE_API = "https://apps.game.qq.com/daoju/igw/live"
|
||||||
|
XPD_ORDER_API = "https://apps.game.qq.com/daoju/igw/live/"
|
||||||
# 2026-08 抓包(8.6-和平小店)实证:本期走 xn_live_cjm 变体(旧期为 recommend_live/common)
|
# 2026-08 抓包(8.6-和平小店)实证:本期走 xn_live_cjm 变体(旧期为 recommend_live/common)
|
||||||
XPD_RECOMMEND_API = "https://apps.game.qq.com/daoju/v3/recommend_xn_live_cjm/common"
|
XPD_RECOMMEND_API = "https://apps.game.qq.com/daoju/v3/recommend_xn_live_cjm/common"
|
||||||
XPD_BALANCE_API = "https://apps.game.qq.com/daoju/igw/live/"
|
XPD_BALANCE_API = "https://apps.game.qq.com/daoju/igw/live/"
|
||||||
@@ -1036,6 +1037,105 @@ class DouyuActivityClient:
|
|||||||
|
|
||||||
return {"goods": goods, "raw": {"pages": pages}}
|
return {"goods": goods, "raw": {"pages": pages}}
|
||||||
|
|
||||||
|
def xpd_purchase_records(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
embed_query: dict[str, str],
|
||||||
|
act_id: str,
|
||||||
|
page_size: int = 10,
|
||||||
|
max_pages: int = 100,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""查询和平小店购买记录,接口参数来自道聚城 H5 抓包。"""
|
||||||
|
page_size = max(1, min(int(page_size), 50))
|
||||||
|
records: list[dict[str, Any]] = []
|
||||||
|
pages: list[dict[str, Any]] = []
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
for page in range(1, max_pages + 1):
|
||||||
|
params = {
|
||||||
|
"_service": "order.list",
|
||||||
|
"_jsvar": "info",
|
||||||
|
"acctype": "livelink",
|
||||||
|
"_app_id": "2123",
|
||||||
|
"hist": "0",
|
||||||
|
"ps": str(page_size),
|
||||||
|
"pn": str(page),
|
||||||
|
"_biz_code": "cjm",
|
||||||
|
"_act_id": str(act_id),
|
||||||
|
"authType": "delegate",
|
||||||
|
"actId": embed_query.get("actId") or "",
|
||||||
|
"appId": embed_query.get("appId") or "bp_cf",
|
||||||
|
"gameId": embed_query.get("gameId") or "cjm",
|
||||||
|
"livePlatId": embed_query.get("livePlatId") or "douyu",
|
||||||
|
"code": embed_query.get("code") or "",
|
||||||
|
"timestamp": embed_query.get("timestamp") or "",
|
||||||
|
"v": embed_query.get("v") or "",
|
||||||
|
"sig": embed_query.get("sig") or "",
|
||||||
|
"_sid": "8",
|
||||||
|
"set": "8",
|
||||||
|
}
|
||||||
|
response = self._request(
|
||||||
|
"get",
|
||||||
|
self.XPD_ORDER_API,
|
||||||
|
source=f"查询小店购买记录第 {page} 页",
|
||||||
|
params=params,
|
||||||
|
headers=self._xpd_daoju_headers(),
|
||||||
|
)
|
||||||
|
info = self._xpd_parse_var(response.text, "info")
|
||||||
|
if str(info.get("ret") or "0") != "0":
|
||||||
|
raise DouyuActivityError(str(info.get("msg") or "小店购买记录查询失败"))
|
||||||
|
data = info.get("data") if isinstance(info.get("data"), dict) else {}
|
||||||
|
page_records = data.get("list") if isinstance(data.get("list"), list) else []
|
||||||
|
total = self._xpd_int(data.get("cnt")) or total
|
||||||
|
pages.append(info)
|
||||||
|
|
||||||
|
for record in page_records:
|
||||||
|
if not isinstance(record, dict):
|
||||||
|
continue
|
||||||
|
goods_info = record.get("sGoodsInfo")
|
||||||
|
if isinstance(goods_info, str):
|
||||||
|
try:
|
||||||
|
goods_info = json.loads(goods_info)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
goods_info = {}
|
||||||
|
goods_list = goods_info.get("list") if isinstance(goods_info, dict) else []
|
||||||
|
goods = (
|
||||||
|
goods_list[0]
|
||||||
|
if isinstance(goods_list, list) and goods_list and isinstance(goods_list[0], dict)
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
app_ext = record.get("sAppExt")
|
||||||
|
if isinstance(app_ext, str):
|
||||||
|
try:
|
||||||
|
app_ext = json.loads(app_ext)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
app_ext = {}
|
||||||
|
records.append({
|
||||||
|
"serial": str(record.get("sSerialNum") or ""),
|
||||||
|
"goods_name": str(
|
||||||
|
goods.get("sGoodsName")
|
||||||
|
or goods.get("sPacketName")
|
||||||
|
or record.get("sActionDesc")
|
||||||
|
or ""
|
||||||
|
),
|
||||||
|
"goods_image": str(goods.get("sGoodsPic") or (app_ext or {}).get("sGoodsPic") or ""),
|
||||||
|
"buy_time": str(record.get("dtBuyTime") or ""),
|
||||||
|
"pay_time": str(record.get("dtPayTime") or ""),
|
||||||
|
"status": str(record.get("iStatus") or ""),
|
||||||
|
"price": self._xpd_int(record.get("iPrice")),
|
||||||
|
"pay_amount": self._xpd_int(record.get("iPayAmount")),
|
||||||
|
"pay_type": str(record.get("sPayType") or ""),
|
||||||
|
"role_name": str(record.get("sRoleName") or ""),
|
||||||
|
"send_status": str(goods.get("iSendStatus") or ""),
|
||||||
|
"delivery_message": str(goods.get("deliveryMsg") or ""),
|
||||||
|
"raw": record,
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(page_records) < page_size or len(records) >= total:
|
||||||
|
break
|
||||||
|
|
||||||
|
return {"records": records, "total": total or len(records), "raw": {"pages": pages}}
|
||||||
|
|
||||||
def xpd_bind_qr(self, *, act_alias: str) -> dict[str, Any]:
|
def xpd_bind_qr(self, *, act_alias: str) -> dict[str, Any]:
|
||||||
"""生成和平小店绑定二维码链接(微信扫码进入 livelink 小程序绑定角色)。
|
"""生成和平小店绑定二维码链接(微信扫码进入 livelink 小程序绑定角色)。
|
||||||
|
|
||||||
|
|||||||
@@ -106,6 +106,43 @@ class XpdExchangeTests(unittest.TestCase):
|
|||||||
self.assertEqual((first_params['page_begin'], first_params['page_num']), ('0', '1'))
|
self.assertEqual((first_params['page_begin'], first_params['page_num']), ('0', '1'))
|
||||||
self.assertEqual((second_params['page_begin'], second_params['page_num']), ('10', '2'))
|
self.assertEqual((second_params['page_begin'], second_params['page_num']), ('10', '2'))
|
||||||
|
|
||||||
|
def test_purchase_records_parses_order_list_and_paginates(self):
|
||||||
|
first_record = {
|
||||||
|
'sSerialNum': 'order-1',
|
||||||
|
'dtBuyTime': '2026-08-08 01:00:42',
|
||||||
|
'dtPayTime': '2026-08-08 01:00:55',
|
||||||
|
'iStatus': '3',
|
||||||
|
'iPrice': '800',
|
||||||
|
'iPayAmount': '800',
|
||||||
|
'sRoleName': '测试角色',
|
||||||
|
'sGoodsInfo': json.dumps({
|
||||||
|
'list': [{'sGoodsName': '改名卡', 'sGoodsPic': 'https://cdn.example/item.jpg'}],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
self.client._request = Mock(side_effect=[
|
||||||
|
SimpleNamespace(text=f'var info={{"ret":"0","msg":"ok","data":{{"cnt":11,"list":{json.dumps([first_record])}}}}};'),
|
||||||
|
SimpleNamespace(text='var info={"ret":"0","msg":"ok","data":{"cnt":11,"list":[]}};'),
|
||||||
|
])
|
||||||
|
|
||||||
|
result = self.client.xpd_purchase_records(
|
||||||
|
embed_query={
|
||||||
|
'gameId': 'cjm', 'actId': '18882', 'appId': 'bp_cf',
|
||||||
|
'livePlatId': 'douyu', 'code': 'fresh-code',
|
||||||
|
'timestamp': '123', 'v': '2.0', 'sig': 'fresh-signature',
|
||||||
|
},
|
||||||
|
act_id='46195',
|
||||||
|
page_size=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result['total'], 11)
|
||||||
|
self.assertEqual(result['records'][0]['goods_name'], '改名卡')
|
||||||
|
self.assertEqual(result['records'][0]['serial'], 'order-1')
|
||||||
|
self.assertEqual(self.client._request.call_count, 2)
|
||||||
|
params = self.client._request.call_args_list[0].kwargs['params']
|
||||||
|
self.assertEqual(params['_service'], 'order.list')
|
||||||
|
self.assertEqual(params['pn'], '1')
|
||||||
|
self.assertEqual(params['code'], 'fresh-code')
|
||||||
|
|
||||||
|
|
||||||
def test_exchange_keeps_raw_price_from_goods_list(self):
|
def test_exchange_keeps_raw_price_from_goods_list(self):
|
||||||
self.client._request = Mock(side_effect=[
|
self.client._request = Mock(side_effect=[
|
||||||
|
|||||||
@@ -1429,6 +1429,35 @@ class DouyuBatchRunner:
|
|||||||
{"fragments": fragments, "role": role, "area_id": int(areaid)},
|
{"fragments": fragments, "role": role, "area_id": int(areaid)},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _execute_query_xpd_purchase_records(
|
||||||
|
self,
|
||||||
|
db: Session,
|
||||||
|
task: DouyuTask,
|
||||||
|
account: Account,
|
||||||
|
cookie: str,
|
||||||
|
config: dict,
|
||||||
|
):
|
||||||
|
"""查询和平小店道聚城购买记录。"""
|
||||||
|
client = self._client(cookie)
|
||||||
|
embed = client.xpd_embed_query(
|
||||||
|
act_alias=str(config["xpd_act_alias"]),
|
||||||
|
rid=str(config["xpd_rid"]),
|
||||||
|
)
|
||||||
|
result = client.xpd_purchase_records(
|
||||||
|
embed_query=embed["query"],
|
||||||
|
act_id=str(config["xpd_act_id"]),
|
||||||
|
)
|
||||||
|
account.xpd_bind_status = "xpd_purchase_records_queried"
|
||||||
|
account.updated_at = datetime.now(timezone.utc)
|
||||||
|
total = result.get("total") or len(result.get("records") or [])
|
||||||
|
self._mark_task(
|
||||||
|
db,
|
||||||
|
task,
|
||||||
|
"success",
|
||||||
|
f"小店兑换记录 {total} 条" if total else "暂无小店兑换记录",
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
|
||||||
def _execute_exchange_xpd_goods(
|
def _execute_exchange_xpd_goods(
|
||||||
self,
|
self,
|
||||||
db: Session,
|
db: Session,
|
||||||
@@ -2821,6 +2850,7 @@ class DouyuBatchRunner:
|
|||||||
"refresh_xpd_goods": self._execute_refresh_xpd_goods,
|
"refresh_xpd_goods": self._execute_refresh_xpd_goods,
|
||||||
"query_xpd_balance": self._execute_query_xpd_balance,
|
"query_xpd_balance": self._execute_query_xpd_balance,
|
||||||
"query_xpd_fragments": self._execute_query_xpd_fragments,
|
"query_xpd_fragments": self._execute_query_xpd_fragments,
|
||||||
|
"query_xpd_purchase_records": self._execute_query_xpd_purchase_records,
|
||||||
"exchange_xpd_goods": self._execute_exchange_xpd_goods,
|
"exchange_xpd_goods": self._execute_exchange_xpd_goods,
|
||||||
}.get(task.task_type)
|
}.get(task.task_type)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ SUPPORTED_DOUYU_TASK_TYPES = { "get_bind_qr": "获取绑定二维码",
|
|||||||
"refresh_xpd_goods": "刷新小店商品列表",
|
"refresh_xpd_goods": "刷新小店商品列表",
|
||||||
"query_xpd_balance": "查询小店点券余额",
|
"query_xpd_balance": "查询小店点券余额",
|
||||||
"query_xpd_fragments": "查询小店扭蛋碎片",
|
"query_xpd_fragments": "查询小店扭蛋碎片",
|
||||||
|
"query_xpd_purchase_records": "查询小店兑换记录",
|
||||||
"exchange_xpd_goods": "兑换小店商品",
|
"exchange_xpd_goods": "兑换小店商品",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Button, Card, Input, InputNumber, Modal, QRCode, Select, Space, Table, Tag, Tooltip, Typography, theme,
|
Alert, Button, Card, Input, InputNumber, Modal, QRCode, Select, Space, Table, Tag, Tooltip, Typography, theme,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import type { TableProps } from 'antd';
|
import type { TableProps } from 'antd';
|
||||||
import {
|
import {
|
||||||
@@ -90,6 +90,7 @@ const PEACE_QUICK_ACTIONS = [
|
|||||||
{ key: 'query_xpd_role', icon: <SearchOutlined /> },
|
{ key: 'query_xpd_role', icon: <SearchOutlined /> },
|
||||||
{ key: 'query_xpd_balance', icon: <SearchOutlined /> },
|
{ key: 'query_xpd_balance', icon: <SearchOutlined /> },
|
||||||
{ key: 'query_xpd_fragments', icon: <SearchOutlined /> },
|
{ key: 'query_xpd_fragments', icon: <SearchOutlined /> },
|
||||||
|
{ key: 'query_xpd_purchase_records', icon: <FieldTimeOutlined /> },
|
||||||
{ key: 'refresh_xpd_goods', icon: <ReloadOutlined /> },
|
{ key: 'refresh_xpd_goods', icon: <ReloadOutlined /> },
|
||||||
{ key: 'exchange_xpd_goods', icon: <ShoppingOutlined /> },
|
{ key: 'exchange_xpd_goods', icon: <ShoppingOutlined /> },
|
||||||
];
|
];
|
||||||
@@ -134,6 +135,7 @@ const PEACE_TASK_TYPES = new Set([
|
|||||||
'refresh_xpd_goods',
|
'refresh_xpd_goods',
|
||||||
'query_xpd_balance',
|
'query_xpd_balance',
|
||||||
'query_xpd_fragments',
|
'query_xpd_fragments',
|
||||||
|
'query_xpd_purchase_records',
|
||||||
'exchange_xpd_goods',
|
'exchange_xpd_goods',
|
||||||
]);
|
]);
|
||||||
const DOUYU_GOLD_AMOUNT_STORAGE_KEY = 'douyu_task_gold_amount';
|
const DOUYU_GOLD_AMOUNT_STORAGE_KEY = 'douyu_task_gold_amount';
|
||||||
@@ -418,6 +420,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
const [confirmFailTip, setConfirmFailTip] = useState<{ taskId: number; message: string } | null>(null);
|
const [confirmFailTip, setConfirmFailTip] = useState<{ taskId: number; message: string } | null>(null);
|
||||||
const [confirmFailCountdown, setConfirmFailCountdown] = useState(0);
|
const [confirmFailCountdown, setConfirmFailCountdown] = useState(0);
|
||||||
const [exchangePreview, setExchangePreview] = useState<{ task: DouyuTaskItem; url: string } | null>(null);
|
const [exchangePreview, setExchangePreview] = useState<{ task: DouyuTaskItem; url: string } | null>(null);
|
||||||
|
const [xpdPurchaseRecordsAccountId, setXpdPurchaseRecordsAccountId] = useState<number | null>(null);
|
||||||
const autoOpenQrReady = useRef(false);
|
const autoOpenQrReady = useRef(false);
|
||||||
const autoOpenEsportsBindReady = useRef(false);
|
const autoOpenEsportsBindReady = useRef(false);
|
||||||
const autoOpenPayReady = useRef(false);
|
const autoOpenPayReady = useRef(false);
|
||||||
@@ -566,6 +569,16 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
return map;
|
return map;
|
||||||
}, [visibleTasks]);
|
}, [visibleTasks]);
|
||||||
|
|
||||||
|
const latestXpdPurchaseRecordsTaskByAccount = useMemo(() => {
|
||||||
|
const map = new Map<number, DouyuTaskItem>();
|
||||||
|
for (const task of tasks) {
|
||||||
|
if (task.task_type !== 'query_xpd_purchase_records') continue;
|
||||||
|
const previous = map.get(task.account_id);
|
||||||
|
if (!previous || task.id > previous.id) map.set(task.account_id, task);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}, [tasks]);
|
||||||
|
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -1540,6 +1553,26 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
...(isPeaceHandbook ? [{
|
||||||
|
title: '兑换记录', width: 100, align: 'center' as const,
|
||||||
|
render: (_: unknown, record: DouyuTaskAccountItem) => {
|
||||||
|
const task = latestXpdPurchaseRecordsTaskByAccount.get(record.id);
|
||||||
|
const running = task && ['planned', 'pending', 'running'].includes(task.status);
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<FieldTimeOutlined />}
|
||||||
|
loading={running}
|
||||||
|
onClick={() => {
|
||||||
|
setXpdPurchaseRecordsAccountId(record.id);
|
||||||
|
void startTask('query_xpd_purchase_records', [record.id]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
查询
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}] : []),
|
||||||
];
|
];
|
||||||
|
|
||||||
const [configFormValues, setConfigFormValues] = useState<DouyuConfig | null>(null);
|
const [configFormValues, setConfigFormValues] = useState<DouyuConfig | null>(null);
|
||||||
@@ -1866,7 +1899,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
tableLayout="fixed"
|
tableLayout="fixed"
|
||||||
scroll={{ x: 1180, y: Math.max(120, accountTableAreaHeight - 80) }}
|
scroll={{ x: 1280, y: Math.max(120, accountTableAreaHeight - 80) }}
|
||||||
onRow={(record) => ({
|
onRow={(record) => ({
|
||||||
onContextMenu: (e) => handleRowContextMenu(record, e),
|
onContextMenu: (e) => handleRowContextMenu(record, e),
|
||||||
style: { cursor: 'context-menu' },
|
style: { cursor: 'context-menu' },
|
||||||
@@ -1934,6 +1967,16 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const xpdPurchaseTask = xpdPurchaseRecordsAccountId == null
|
||||||
|
? null
|
||||||
|
: latestXpdPurchaseRecordsTaskByAccount.get(xpdPurchaseRecordsAccountId) || null;
|
||||||
|
const xpdPurchaseRecords = Array.isArray(xpdPurchaseTask?.result?.records)
|
||||||
|
? xpdPurchaseTask.result.records.filter(
|
||||||
|
(item): item is Record<string, unknown> => Boolean(item && typeof item === 'object'),
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
const xpdPurchaseAccount = accounts.find((account) => account.id === xpdPurchaseRecordsAccountId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
<div style={{ height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
||||||
<style>{taskRecordTableStyle}</style>
|
<style>{taskRecordTableStyle}</style>
|
||||||
@@ -2390,6 +2433,93 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
|||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* 和平小店兑换记录 Modal */}
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
<Space>
|
||||||
|
<FieldTimeOutlined />
|
||||||
|
<span>和平小店兑换记录</span>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
open={xpdPurchaseRecordsAccountId != null}
|
||||||
|
onCancel={() => setXpdPurchaseRecordsAccountId(null)}
|
||||||
|
footer={null}
|
||||||
|
width={980}
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
<Text type="secondary" style={{ display: 'block', marginBottom: 12 }}>
|
||||||
|
{xpdPurchaseAccount?.nickname || xpdPurchaseAccount?.username || `#${xpdPurchaseRecordsAccountId}`}
|
||||||
|
{xpdPurchaseTask?.result?.total != null ? `,共 ${String(xpdPurchaseTask.result.total)} 条` : ''}
|
||||||
|
</Text>
|
||||||
|
{xpdPurchaseTask && ['failed', 'error'].includes(xpdPurchaseTask.status) ? (
|
||||||
|
<Alert
|
||||||
|
type="error"
|
||||||
|
showIcon
|
||||||
|
message={xpdPurchaseTask.message || '查询兑换记录失败'}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Table
|
||||||
|
rowKey={(record, index) => String(record.serial || `${record.buy_time || ''}-${index}`)}
|
||||||
|
size="small"
|
||||||
|
loading={!xpdPurchaseTask || ['planned', 'pending', 'running'].includes(xpdPurchaseTask.status)}
|
||||||
|
dataSource={xpdPurchaseRecords}
|
||||||
|
pagination={{ pageSize: 10, showLessItems: true }}
|
||||||
|
scroll={{ x: 850 }}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: '商品',
|
||||||
|
dataIndex: 'goods_name',
|
||||||
|
width: 240,
|
||||||
|
render: (value: unknown) => String(value || '-'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '兑换时间',
|
||||||
|
dataIndex: 'buy_time',
|
||||||
|
width: 170,
|
||||||
|
render: (value: unknown) => String(value || '-'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '支付时间',
|
||||||
|
dataIndex: 'pay_time',
|
||||||
|
width: 170,
|
||||||
|
render: (value: unknown) => String(value || '-'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '价格',
|
||||||
|
width: 90,
|
||||||
|
render: (_: unknown, record: Record<string, unknown>) =>
|
||||||
|
record.pay_amount ?? record.price ?? '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
width: 90,
|
||||||
|
render: (value: unknown) => (
|
||||||
|
<Tag color={String(value) === '3' ? 'success' : 'default'}>
|
||||||
|
{String(value) === '3' ? '已完成' : String(value || '未知')}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '角色',
|
||||||
|
dataIndex: 'role_name',
|
||||||
|
width: 150,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (value: unknown) => String(value || '-'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '订单号',
|
||||||
|
dataIndex: 'serial',
|
||||||
|
width: 250,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (value: unknown) => String(value || '-'),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
locale={{ emptyText: xpdPurchaseTask ? '暂无兑换记录' : '正在查询兑换记录...' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
|
||||||
{/* 兑换结果图片预览 Modal */}
|
{/* 兑换结果图片预览 Modal */}
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
|
|||||||
Reference in New Issue
Block a user