修复虎牙宝典开通中状态与支付监听异常
This commit is contained in:
@@ -1214,6 +1214,7 @@ class HuyaWssClient:
|
||||
page_size: int = 10,
|
||||
order_type: int = 1,
|
||||
status: int = 0,
|
||||
timeout: float = 15.0,
|
||||
):
|
||||
from .shop_structs import QueryUserOrderListReq, QueryUserOrderListRsp
|
||||
|
||||
@@ -1229,7 +1230,7 @@ class HuyaWssClient:
|
||||
"queryUserOrderList",
|
||||
req,
|
||||
QueryUserOrderListRsp,
|
||||
timeout=15.0,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
async def order_detail(
|
||||
@@ -1240,6 +1241,7 @@ class HuyaWssClient:
|
||||
order_id: int,
|
||||
source_id: str = "yellowcarlist",
|
||||
app_scene: int = SHOP_APP_SCENE,
|
||||
timeout: float = 15.0,
|
||||
):
|
||||
"""按订单号查询商城订单详情,匹配 9.1 支付后轮询。"""
|
||||
from .shop_structs import OrderDetailReq, OrderDetailRsp
|
||||
@@ -1249,7 +1251,7 @@ class HuyaWssClient:
|
||||
req.shopAppInfo = self._build_shop_app(source_id, app_scene)
|
||||
req.orderId = int(order_id or 0)
|
||||
return await self.call_rpc(
|
||||
"shopMiddleUI", "orderDetailV5", req, OrderDetailRsp, timeout=15.0
|
||||
"shopMiddleUI", "orderDetailV5", req, OrderDetailRsp, timeout=timeout
|
||||
)
|
||||
|
||||
async def create_order(
|
||||
|
||||
+13
-1
@@ -1,6 +1,6 @@
|
||||
# 虎牙精英宝典联调指南
|
||||
|
||||
更新时间:2026-09-02
|
||||
更新时间:2026-11-11
|
||||
|
||||
> 当前 Cookie 字段状态见 [HUYA_COOKIE路线状态.md](HUYA_COOKIE路线状态.md)。
|
||||
|
||||
@@ -37,6 +37,18 @@
|
||||
4. 兑换成功后再次执行 `query_points` 和 `query_exchange_records`,核对余额扣减与新增记录。
|
||||
5. 充值/开通链路单独联调:先 `refresh_recharge_goods`,再只生成支付码 `create_recharge_order`。支付确认不要和查询任务混在同一批次。
|
||||
|
||||
### 2026-11-11 订单状态补充
|
||||
|
||||
- `revenueWebUI.queryUserOrderList` 是订单查询主入口;返回的宝典订单:
|
||||
`order_status=10` = 待支付,`pay_time>0`/已支付状态 = 已付款,`order_status=90` = 已取消。
|
||||
- WSS 的 `queryUserOrderList` / `orderDetailV5` 必须保留 `timeout` 参数;
|
||||
支付监听统一传该参数,缺了会在支付码生成后直接
|
||||
`unexpected keyword argument 'timeout'`。
|
||||
- “已付款但积分中心 `getRedeemNeedGoods.tag5` 仍为 0”不能直接判成未开通:
|
||||
24 小时交付窗口内显示“开通中”,超过窗口仍为 0 才回落到未开通。
|
||||
- 待支付旧单继续走复用支付;已取消订单不能复用。订单详情里的“还剩 N 分钟取消订单”
|
||||
只是官方倒计时,不是开通状态。
|
||||
|
||||
## 如何判定传输通道
|
||||
|
||||
执行器只检查字段是否存在,不记录字段值:
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
|
||||
from core.huya.frame_decoder import decode_struct_bytes
|
||||
@@ -396,6 +397,7 @@ def test_refresh_elite_open_after_payment_retries_until_opened():
|
||||
|
||||
|
||||
def test_refresh_elite_open_after_payment_gives_up_after_attempts():
|
||||
"""支付已确认但权益未到账:不能再写死「未开通」,必须标成「开通中」。"""
|
||||
client = _StubClient(redeem=_redeem(REDEEM_NOT_OPENED_HEX))
|
||||
runner = HuyaBatchRunner(db=None, batch_id="t", task_type="create_recharge_order")
|
||||
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
|
||||
@@ -412,11 +414,66 @@ def test_refresh_elite_open_after_payment_gives_up_after_attempts():
|
||||
)
|
||||
|
||||
assert client.redeem_calls == 2 # 重试到上限
|
||||
assert state["elite_opened"] is False
|
||||
assert account.elite_opened is False
|
||||
assert state["elite_opened"] is None
|
||||
assert state["elite_opening"] is True
|
||||
assert state["elite_status_label"] == "开通中"
|
||||
assert account.elite_opened is None
|
||||
assert account.elite_open_time is None
|
||||
|
||||
|
||||
def test_query_all_marks_recent_paid_elite_order_as_opening(monkeypatch):
|
||||
"""已支付但 redeem 还是 0:订单列表证明已付款,应显示「开通中」。"""
|
||||
now_ms = int(time.time() * 1000)
|
||||
opening_order = {
|
||||
**PAID_ELITE_ORDER,
|
||||
"order_id": "12001621",
|
||||
"pay_time": now_ms - 60_000,
|
||||
"create_time": now_ms - 120_000,
|
||||
}
|
||||
shop_client = _StubClient(
|
||||
redeem=_redeem(REDEEM_NOT_OPENED_HEX),
|
||||
order_pages=[[opening_order]],
|
||||
season=True,
|
||||
)
|
||||
runner, marks, _logs = _runner(monkeypatch, shop_client, activity_client=object())
|
||||
monkeypatch.setattr(
|
||||
runner,
|
||||
"_probe_bind_status",
|
||||
lambda **kwargs: (_FakeBindStatus(status=601, msg="活动已下线"), {}),
|
||||
raising=False,
|
||||
)
|
||||
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
|
||||
|
||||
runner._execute_query_all(
|
||||
None, None, account, {"uid": UID, "cookie": COOKIE}, {"bind_act_id": "9271"}
|
||||
)
|
||||
|
||||
status, message, result = marks[-1]
|
||||
assert status == "success"
|
||||
assert result["elite_opening"] is True
|
||||
assert result["elite_status"] == "opening"
|
||||
assert result["elite_status_label"] == "开通中"
|
||||
assert result["elite_order"]["order_id"] == "12001621"
|
||||
assert account.elite_opened is None
|
||||
assert "开通中宝典" in message
|
||||
|
||||
|
||||
def test_elite_order_state_labels_pending_and_cancelled_orders():
|
||||
runner = HuyaBatchRunner(db=None, batch_id="t", task_type="query_all")
|
||||
assert (
|
||||
runner._elite_order_state(
|
||||
{**UNPAID_ELITE_ORDER, "order_id": "1"}, opened=False
|
||||
)["status"]
|
||||
== "pending_payment"
|
||||
)
|
||||
assert (
|
||||
runner._elite_order_state(
|
||||
{**PAID_ELITE_ORDER, "order_status": 90, "pay_time": 0}, opened=False
|
||||
)["status"]
|
||||
== "cancelled"
|
||||
)
|
||||
|
||||
|
||||
def test_create_recharge_order_confirms_elite_open_after_payment(db, monkeypatch):
|
||||
"""支付成功后自动确认开通状态:任务结果与账号字段都带上宝典状态。"""
|
||||
|
||||
@@ -676,7 +733,7 @@ def test_query_all_combines_points_elite_and_role(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def test_query_all_reports_not_opened_without_order_lookup(monkeypatch):
|
||||
def test_query_all_reports_not_opened_and_checks_order_state(monkeypatch):
|
||||
shop_client = _StubClient(redeem=_redeem(REDEEM_NOT_OPENED_HEX))
|
||||
runner, marks, _logs = _runner(monkeypatch, shop_client, activity_client=object())
|
||||
monkeypatch.setattr(
|
||||
@@ -699,8 +756,11 @@ def test_query_all_reports_not_opened_without_order_lookup(monkeypatch):
|
||||
assert account.elite_open_time is None
|
||||
assert "未开通宝典" in message
|
||||
assert "活动已下线" in message
|
||||
# 未开通不查订单(省一次请求)
|
||||
assert [name for name, _kwargs in shop_client.calls] == ["get_redeem_need_goods"]
|
||||
# 未开通也要查最近订单:否则无法区分「待支付 / 开通中 / 已取消」
|
||||
assert [name for name, _kwargs in shop_client.calls] == [
|
||||
"get_redeem_need_goods",
|
||||
"query_user_order_list",
|
||||
]
|
||||
|
||||
|
||||
def test_query_all_fails_when_points_center_unreachable(monkeypatch):
|
||||
|
||||
@@ -253,6 +253,47 @@ def test_pay_order_submit_v5_callback_points_page():
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_order_poll_rpcs_accept_timeout_keyword():
|
||||
"""支付监听统一传 timeout;WSS 方法缺这个参数会在生成二维码后立刻崩。"""
|
||||
|
||||
async def run():
|
||||
client = _points_client()
|
||||
|
||||
await client.query_user_order_list(
|
||||
uid=UID,
|
||||
guid=GUID,
|
||||
cookie=COOKIE,
|
||||
offset=0,
|
||||
page_size=20,
|
||||
order_type=1,
|
||||
status=0,
|
||||
timeout=10.0,
|
||||
)
|
||||
decoded = client.ws.last_request()
|
||||
assert (decoded["servant"], decoded["func"]) == (
|
||||
"revenueWebUI",
|
||||
"queryUserOrderList",
|
||||
)
|
||||
assert decoded["tReq"]["tag2"] == 1
|
||||
assert decoded["tReq"]["tag3"] == 20
|
||||
|
||||
await client.order_detail(
|
||||
uid=UID,
|
||||
guid=GUID,
|
||||
cookie=COOKIE,
|
||||
order_id=12001621,
|
||||
timeout=10.0,
|
||||
)
|
||||
decoded = client.ws.last_request()
|
||||
assert (decoded["servant"], decoded["func"]) == (
|
||||
"shopMiddleUI",
|
||||
"orderDetailV5",
|
||||
)
|
||||
assert decoded["tReq"]["tag2"] == 12001621
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_new_points_rpcs_match_capture():
|
||||
async def run():
|
||||
client = _points_client()
|
||||
|
||||
@@ -39,6 +39,12 @@ ELITE_HANDBOOK_SPU_IDS = frozenset(
|
||||
ELITE_HANDBOOK_NAME_KEYWORD = "精英宝典"
|
||||
ELITE_ORDER_PAGE_SIZE = 20
|
||||
ELITE_ORDER_MAX_PAGES = 3
|
||||
# 已支付但 redeem 还没翻成已开通时,在这个窗口内视为「开通中」而不是「未开通」。
|
||||
ELITE_OPENING_WINDOW_MS = 24 * 60 * 60 * 1000
|
||||
# 官方订单状态:10=待支付、1000=尾款待支付;90=已取消。
|
||||
ELITE_PENDING_ORDER_STATUSES = frozenset({10, 1000})
|
||||
ELITE_CANCELLED_ORDER_STATUSES = frozenset({90, 1001})
|
||||
ELITE_PAID_ORDER_STATUSES = frozenset({20, 30, 40, 50, 55, 60, 70, 80})
|
||||
# 赛季(福利季)节点的毫秒时间戳下限:2001-09 之后的值才认
|
||||
ELITE_SEASON_MS_FLOOR = 1_000_000_000_000
|
||||
|
||||
@@ -126,6 +132,79 @@ class EliteMixin:
|
||||
offset += len(orders)
|
||||
return latest or None
|
||||
|
||||
def _latest_elite_order(self, client: Any, uid: int, cookie: str) -> dict | None:
|
||||
"""最近一张宝典订单(不要求已支付),用于识别开通中/待支付/已取消。"""
|
||||
latest: dict | None = None
|
||||
latest_key = -1
|
||||
offset = 0
|
||||
for _page in range(ELITE_ORDER_MAX_PAGES):
|
||||
resp = client.query_user_order_list(
|
||||
uid=uid,
|
||||
guid="",
|
||||
cookie=cookie,
|
||||
offset=offset,
|
||||
page_size=ELITE_ORDER_PAGE_SIZE,
|
||||
order_type=1,
|
||||
status=0,
|
||||
)
|
||||
if resp is None:
|
||||
break
|
||||
orders = list(getattr(resp, "orders", None) or [])
|
||||
for order in orders:
|
||||
item = order.to_dict() if hasattr(order, "to_dict") else dict(order)
|
||||
if not self._is_elite_order(item):
|
||||
continue
|
||||
key = int(item.get("create_time") or 0)
|
||||
if key >= latest_key:
|
||||
latest = item
|
||||
latest_key = key
|
||||
total = self._to_int(getattr(resp, "totalCount", 0))
|
||||
if len(orders) < ELITE_ORDER_PAGE_SIZE or offset + len(orders) >= total:
|
||||
break
|
||||
offset += len(orders)
|
||||
return latest
|
||||
|
||||
@staticmethod
|
||||
def _elite_order_state(
|
||||
order: dict | None, *, opened: bool | None, now_ms: int | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""把「redeem 开通位 + 最近订单」解释成可展示状态。
|
||||
|
||||
订单状态 10 是待支付,不代表开通中;只有已支付/有 pay_time 且权益尚未到账,
|
||||
才归为「开通中」。超出交付窗口后仍没开通,不再继续误导成开通中。
|
||||
"""
|
||||
if opened is True:
|
||||
return {"status": "opened", "label": "已开通"}
|
||||
if not order:
|
||||
return {"status": "unopened", "label": "未开通"}
|
||||
|
||||
status = int(order.get("order_status") or 0)
|
||||
pay_time = int(order.get("pay_time") or 0)
|
||||
current_ms = int(now_ms if now_ms is not None else time.time() * 1000)
|
||||
paid = pay_time > 0 or status in ELITE_PAID_ORDER_STATUSES
|
||||
observed_ms = pay_time or int(order.get("create_time") or 0)
|
||||
if paid and (
|
||||
observed_ms <= 0 or current_ms - observed_ms <= ELITE_OPENING_WINDOW_MS
|
||||
):
|
||||
return {
|
||||
"status": "opening",
|
||||
"label": "开通中",
|
||||
"order": order,
|
||||
}
|
||||
if status in ELITE_PENDING_ORDER_STATUSES:
|
||||
return {
|
||||
"status": "pending_payment",
|
||||
"label": "待支付",
|
||||
"order": order,
|
||||
}
|
||||
if status in ELITE_CANCELLED_ORDER_STATUSES:
|
||||
return {
|
||||
"status": "cancelled",
|
||||
"label": "订单已取消",
|
||||
"order": order,
|
||||
}
|
||||
return {"status": "unopened", "label": "未开通", "order": order}
|
||||
|
||||
@classmethod
|
||||
def _walk_season_nodes(cls, node: Any, found: list[dict]) -> None:
|
||||
"""递归找「赛季」节点:同时带 tag10/tag11 毫秒起止时间的结构。"""
|
||||
@@ -210,9 +289,38 @@ class EliteMixin:
|
||||
if redeem is not None and getattr(redeem, "code", 0) == 200:
|
||||
state["redeem"] = redeem.to_dict()
|
||||
state["redeem_points"] = self._to_int(redeem.value("tag2"))
|
||||
latest_order = self._latest_elite_order(client, uid, cookie)
|
||||
elite_state = self._elite_order_state(
|
||||
latest_order,
|
||||
opened=opened,
|
||||
now_ms=pay_time_ms,
|
||||
)
|
||||
if latest_order:
|
||||
state["elite_order"] = latest_order
|
||||
|
||||
def mark_opening() -> None:
|
||||
state.update(
|
||||
{
|
||||
"elite_opened": None,
|
||||
"elite_opening": True,
|
||||
"elite_status": "opening",
|
||||
"elite_status_label": "开通中",
|
||||
}
|
||||
)
|
||||
if pay_time_ms:
|
||||
state["elite_order_paid_time"] = int(pay_time_ms) // 1000
|
||||
|
||||
if opened is None:
|
||||
if pay_time_ms or elite_state["status"] == "opening":
|
||||
mark_opening()
|
||||
return state
|
||||
|
||||
if not opened:
|
||||
opening = bool(pay_time_ms) or elite_state["status"] == "opening"
|
||||
if opening:
|
||||
mark_opening()
|
||||
return state
|
||||
|
||||
open_time: int | None = pay_time_ms or None
|
||||
if opened and not open_time:
|
||||
open_time = self._resolve_elite_open_time(client, uid, cookie)
|
||||
@@ -220,10 +328,17 @@ class EliteMixin:
|
||||
expire_time = self._to_int(season["end_ms"]) if season else None
|
||||
self._apply_elite_state(account, opened, open_time, expire_time)
|
||||
|
||||
state["elite_opened"] = opened
|
||||
state["elite_open_time"] = (open_time // 1000) if open_time else None
|
||||
state["elite_open_time_text"] = (
|
||||
self._format_local_time(open_time // 1000) if open_time else ""
|
||||
state.update(
|
||||
{
|
||||
"elite_opened": opened,
|
||||
"elite_opening": False,
|
||||
"elite_status": elite_state["status"],
|
||||
"elite_status_label": elite_state["label"],
|
||||
"elite_open_time": (open_time // 1000) if open_time else None,
|
||||
"elite_open_time_text": (
|
||||
self._format_local_time(open_time // 1000) if open_time else ""
|
||||
),
|
||||
}
|
||||
)
|
||||
if season:
|
||||
state["elite_season"] = season
|
||||
@@ -379,17 +494,32 @@ class EliteMixin:
|
||||
)
|
||||
message_parts.append(f"积分 {points}")
|
||||
|
||||
latest_order = (
|
||||
self._latest_elite_order(shop_client, uid, cookie)
|
||||
if opened is not None
|
||||
else None
|
||||
)
|
||||
elite_state = self._elite_order_state(latest_order, opened=opened)
|
||||
open_time: int | None = None
|
||||
if opened:
|
||||
# 已开通才去翻订单:既省一次请求,也避免给未开通账号显示旧订单时间
|
||||
open_time = self._resolve_elite_open_time(shop_client, uid, cookie)
|
||||
season = (
|
||||
self._resolve_elite_season(shop_client, uid, cookie) if opened else None
|
||||
)
|
||||
expire_time = self._to_int(season["end_ms"]) if season else None
|
||||
if opened is not None:
|
||||
self._apply_elite_state(account, opened, open_time, expire_time)
|
||||
result["elite_opened"] = opened
|
||||
opening = elite_state["status"] == "opening"
|
||||
if not opening:
|
||||
self._apply_elite_state(account, opened, open_time, expire_time)
|
||||
result["elite_opened"] = None if opening else opened
|
||||
result["elite_opening"] = opening
|
||||
result["elite_status"] = elite_state["status"]
|
||||
result["elite_status_label"] = elite_state["label"]
|
||||
if latest_order:
|
||||
result["elite_order"] = latest_order
|
||||
paid_at = int(latest_order.get("pay_time") or 0)
|
||||
if paid_at:
|
||||
result["elite_order_paid_time"] = paid_at // 1000
|
||||
result["elite_open_time"] = (open_time // 1000) if open_time else None
|
||||
result["elite_open_time_text"] = (
|
||||
self._format_local_time(open_time // 1000) if open_time else ""
|
||||
@@ -408,8 +538,21 @@ class EliteMixin:
|
||||
opened_text += (
|
||||
f",有效期至 {self._format_local_time(expire_time // 1000)}"
|
||||
)
|
||||
elif opening:
|
||||
order_id = (latest_order or {}).get("order_id") or ""
|
||||
opened_text = "开通中宝典"
|
||||
if order_id:
|
||||
opened_text += f"(订单 #{order_id},等待权益到账)"
|
||||
else:
|
||||
opened_text = "未开通宝典"
|
||||
order_id = (latest_order or {}).get("order_id") or ""
|
||||
if elite_state["status"] == "pending_payment":
|
||||
opened_text = (
|
||||
f"宝典订单 #{order_id} 待支付" if order_id else "宝典待支付"
|
||||
)
|
||||
elif elite_state["status"] == "cancelled":
|
||||
opened_text = "未开通宝典(最近订单已取消)"
|
||||
else:
|
||||
opened_text = "未开通宝典"
|
||||
message_parts.append(opened_text)
|
||||
|
||||
account.points = points if points is not None else account.points
|
||||
|
||||
@@ -115,7 +115,11 @@ function miniQrcodeSrc(image: string): string {
|
||||
/** 开通支付码是否还需要扫码:已支付或已确认开通就不用再展示了 */
|
||||
function needsScanPay(task: HuyaTaskItem): boolean {
|
||||
const result = task.result || {};
|
||||
return !(result.payment_paid === true || result.elite_opened === true);
|
||||
return !(
|
||||
result.payment_paid === true
|
||||
|| result.elite_opened === true
|
||||
|| result.elite_opening === true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,6 +132,7 @@ function needsScanPay(task: HuyaTaskItem): boolean {
|
||||
function qrDoneLabel(task: HuyaTaskItem): string | null {
|
||||
if (task.task_type !== 'create_recharge_order') return null;
|
||||
if (needsScanPay(task)) return null;
|
||||
if (task.result?.elite_opening === true) return '开通中';
|
||||
return task.result?.elite_opened === true ? '已开通' : '已支付';
|
||||
}
|
||||
|
||||
@@ -538,7 +543,7 @@ export default function HuyaElitePage() {
|
||||
{ title: '账号', width: '15%', align: 'center', ellipsis: true, render: (_value, row) => <Space direction="vertical" align="center" size={0} style={{ width: '100%' }}><Text strong ellipsis title={row.nickname || row.username || row.uid}>{row.nickname || row.username || row.uid}</Text><Text type="secondary" style={{ fontSize: 12 }} ellipsis title={row.username}>UID {row.uid || '-'}</Text></Space> },
|
||||
{ title: '手机号', width: '10%', align: 'center', ellipsis: true, render: (_value, row) => row.game_phone || <Text type="secondary">-</Text> },
|
||||
{ title: '游戏名', width: '13%', align: 'center', ellipsis: true, render: (_value, row) => { const expired = ckExpired(latestTask(tasks, row.id), row); return <Space direction="vertical" align="center" size={0} style={{ width: '100%' }}><Text ellipsis title={row.game_name || row.game_channel}>{row.game_name || <Text type={expired ? 'danger' : 'secondary'}>{expired ? '角色未查到' : '未查询'}</Text>}</Text>{row.game_channel && <Text type="secondary" style={{ fontSize: 12 }} ellipsis>{row.game_channel}</Text>}</Space>; } },
|
||||
{ title: '精英宝典', width: '13%', align: 'center', ellipsis: true, render: (_value, row) => <Space direction="vertical" align="center" size={0} style={{ width: '100%' }}><Tag color={row.elite_opened === true ? 'success' : row.elite_opened === false ? 'default' : undefined}>{row.elite_opened === true ? '已开通' : row.elite_opened === false ? '未开通' : '未查询'}</Tag>{row.elite_opened === true && row.elite_open_time ? <Text type="secondary" style={{ fontSize: 12 }} ellipsis>{`开通 ${formatUnixSeconds(row.elite_open_time)}`}</Text> : null}{row.elite_expire_time ? <Text type="secondary" style={{ fontSize: 12 }} ellipsis>{`有效期至 ${formatUnixSeconds(row.elite_expire_time)}`}</Text> : null}</Space> },
|
||||
{ title: '精英宝典', width: '13%', align: 'center', ellipsis: true, render: (_value, row) => { const task = latestTask(tasks, row.id); const opening = task?.result?.elite_opening === true; return <Space direction="vertical" align="center" size={0} style={{ width: '100%' }}><Tag color={opening ? 'processing' : row.elite_opened === true ? 'success' : row.elite_opened === false ? 'default' : undefined}>{opening ? '开通中' : row.elite_opened === true ? '已开通' : row.elite_opened === false ? '未开通' : '未查询'}</Tag>{row.elite_opened === true && row.elite_open_time ? <Text type="secondary" style={{ fontSize: 12 }} ellipsis>{`开通 ${formatUnixSeconds(row.elite_open_time)}`}</Text> : null}{row.elite_expire_time ? <Text type="secondary" style={{ fontSize: 12 }} ellipsis>{`有效期至 ${formatUnixSeconds(row.elite_expire_time)}`}</Text> : null}</Space>; } },
|
||||
{ title: '积分', dataIndex: 'points', width: '7%', align: 'center', render: (value: number | null) => value ?? <Text type="secondary">-</Text> },
|
||||
{ title: '最近操作', width: '25%', align: 'center', ellipsis: true, render: (_value, row) => { const task = latestTask(tasks, row.id); const expired = ckExpired(task, row); return task ? <Space direction="vertical" align="center" size={1} style={{ width: '100%' }}><Text strong ellipsis>{TASK_LABELS[task.task_type] || task.task_type}</Text><Text type={expired ? 'danger' : 'secondary'} ellipsis title={task.message}>{task.message || '-'}</Text><Text type="secondary" style={{ fontSize: 12 }}>{formatTime(task.finished_at || task.created_at)}</Text></Space> : <Text type="secondary">暂无操作</Text>; } },
|
||||
{ title: '二维码', width: '6%', align: 'center', render: (_value, row) => { const task = latestTask(tasks, row.id); if (!task) return <Text type="secondary">-</Text>; const url = taskQrUrl(task); const image = resultText(task, 'mini_qrcode_image'); if (task.task_type === 'get_bind_qr' && !image) { const hasMini = task.result?.has_mini_qrcode === true; if (!hasMini) return <Text type="secondary">-</Text>; return <div onClick={(event) => { event.stopPropagation(); void openQrTask(task); }} style={{ cursor: 'pointer', display: 'inline-block', lineHeight: 0, background: '#fff', padding: 6, borderRadius: 4 }} title="点击查看小程序码"><QrcodeOutlined style={{ fontSize: 26, color: '#000' }} /></div>; } if (!url && !image) return <Text type="secondary">-</Text>; const doneLabel = qrDoneLabel(task); if (doneLabel) return <Text type="secondary">{doneLabel}</Text>; return <div onClick={(event) => { event.stopPropagation(); if (!task) return; if (task.task_type === 'create_recharge_order') setZoomPayTask(task); else void openQrTask(task); }} style={{ cursor: 'pointer', display: 'inline-block', lineHeight: 0, background: '#fff', padding: 3, borderRadius: 4 }} title="点击放大扫码">{image ? <img src={miniQrcodeSrc(image)} alt="二维码" style={{ width: 52, height: 52, objectFit: 'contain' }} /> : <QRCode value={url} size={52} type="svg" errorLevel="L" bordered={false} color="#000" bgColor="#fff" />}</div>; } },
|
||||
@@ -612,7 +617,7 @@ export default function HuyaElitePage() {
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>已完成(二维码已收起)</Text>
|
||||
<div style={{ marginTop: 6, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
{payDoneTasks.map((task) => (
|
||||
<Tag key={task.id} color={task.result?.elite_opened === true ? 'success' : 'blue'}>{payTaskLabel(task)} · {task.result?.elite_opened === true ? '已开通' : resultText(task, 'payment_status_label') || '已支付'}</Tag>
|
||||
<Tag key={task.id} color={task.result?.elite_opened === true ? 'success' : task.result?.elite_opening === true ? 'processing' : 'blue'}>{payTaskLabel(task)} · {task.result?.elite_opened === true ? '已开通' : task.result?.elite_opening === true ? '开通中' : resultText(task, 'payment_status_label') || '已支付'}</Tag>
|
||||
))}
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
Reference in New Issue
Block a user