From b199428619d21e76a985583c76c78e6ea066c4df Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 4 Oct 2026 20:26:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(account):=20=E4=B8=80=E9=94=AE=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E5=B1=95=E7=A4=BA=E3=80=8C=E8=A7=92=E8=89=B2=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E6=98=8E=E7=BB=86=E3=80=8D=E2=80=94=E2=80=94=20?= =?UTF-8?q?=E4=B8=8D=E5=8F=AA=E4=B8=80=E4=B8=AA"=E5=B7=B2=E7=BB=91?= =?UTF-8?q?=E5=AE=9A"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户看到绑定状态后问:「是不是在详情中增加详细的绑定信息啥的」。 `bind_info` 的返回里本来就有明细(`_parse_bind_info` 会解析出来), 之前只取了 `role_name` 一项。现在把运营真正要看的都带上: - **角色名**(`role_name`) - **区服 / 平台**(`area_name` / `plat_name`) - 换绑冷却(`change_role_wait_time`)与可换绑时间(`can_change_time`) 实测(账号 61): 精英: 柿子洋音 / 微信 / 安卓(android) 电竞: 资跳 / 微信 / 苹果(iOS),可换绑 1788624000 面板新增「🎮 角色绑定明细」卡片,三个作用域并排; 冷却时间格式化成"无 / x 小时 / x 天 y 小时", 时间戳过 `formatUnixSeconds` 那套(`0`/空显示 `-`,不显示 1970)。 ## 验证(本地) TZ=Asia/Shanghai **615 passed / 6 skipped**;ruff 全绿;前端 build 通过。 --- web/backend/services/douyu_runner_overview.py | 7 ++ .../douyu-tasks/AccountOverviewPanel.tsx | 65 ++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/web/backend/services/douyu_runner_overview.py b/web/backend/services/douyu_runner_overview.py index 422e543..cd7c56a 100644 --- a/web/backend/services/douyu_runner_overview.py +++ b/web/backend/services/douyu_runner_overview.py @@ -452,9 +452,16 @@ class OverviewMixin: try: info = self._fetch_bind_info(client, scope, alias) derived = derive_from_bind_info(info) + # 把**明细**也带上:运营要看的是"绑的哪个角色、哪个区服、 + # 什么时候能换绑",不是一个"已绑定"就完事。 overview["bind"][scope] = { "derived": derived, "role_name": str(info.get("role_name") or info.get("role") or ""), + "area_name": str(info.get("area_name") or ""), + "plat_name": str(info.get("plat_name") or ""), + "nickname": str(info.get("nickname") or ""), + "change_role_wait_time": _ts(info.get("change_role_wait_time")), + "can_change_time": _ts(info.get("can_change_time")), } if derived is not None: # 唯一写入者;推不出来(None)时**不动原值** —— 宁可留 unknown, diff --git a/web/frontend/src/pages/douyu-tasks/AccountOverviewPanel.tsx b/web/frontend/src/pages/douyu-tasks/AccountOverviewPanel.tsx index 0796b9e..8d32682 100644 --- a/web/frontend/src/pages/douyu-tasks/AccountOverviewPanel.tsx +++ b/web/frontend/src/pages/douyu-tasks/AccountOverviewPanel.tsx @@ -1,4 +1,4 @@ -import { Alert, Card, Empty, Space, Table, Tag, Tooltip, Typography } from 'antd'; +import { Alert, Card, Col, Empty, Row, Space, Table, Tag, Tooltip, Typography } from 'antd'; import type { ColumnsType } from 'antd/es/table'; const { Text } = Typography; @@ -45,6 +45,19 @@ export interface AccountOverviewResult { /** 按作用域分组:elite / esports / peace */ exchange_records?: Record>>; exchange_record_count?: number; + /** 三个作用域的绑定明细(一键查询会真的去查,以前从不查 → 97% 显示未知) */ + bind?: Record< + string, + { + derived?: string | null; + role_name?: string; + area_name?: string; + plat_name?: string; + nickname?: string; + change_role_wait_time?: number | null; + can_change_time?: number | null; + } + >; /** 单项失败:**必须展示**,否则「没查到」会被读成「没有」 */ errors?: Record; } @@ -182,6 +195,22 @@ const EXCHANGE_SCOPES: Array<{ key: string; label: string }> = [ { key: 'peace', label: '和平小店' }, ]; +/** 三个作用域的展示顺序(与后端 bind 的 key 对应)。 */ +const BIND_SCOPES: Array<{ key: string; label: string }> = [ + { key: 'elite', label: '精英宝典' }, + { key: 'esports', label: '电竞手册' }, + { key: 'peace', label: '和平小店' }, +]; + +/** 换绑冷却(秒)→ 可读;0 / 空表示没有冷却。 */ +function formatWait(seconds: number | null | undefined): string { + if (!seconds || seconds <= 0) return '无'; + const hours = Math.floor(seconds / 3600); + if (hours >= 24) return `${Math.floor(hours / 24)} 天 ${hours % 24} 小时`; + if (hours >= 1) return `${hours} 小时`; + return `${Math.floor(seconds / 60)} 分钟`; +} + export default function AccountOverviewPanel({ result, }: { @@ -256,6 +285,40 @@ export default function AccountOverviewPanel({ )} + + + {BIND_SCOPES.map(({ key, label }) => { + const info = (result.bind ?? {})[key]; + if (!info) { + return ( + + {label}:未查询 + + ); + } + return ( + + + {label} + + {info.role_name ? info.role_name : '(未绑定角色)'} + + + {[info.plat_name, info.area_name].filter(Boolean).join(' · ') || '-'} + + + 换绑冷却 {formatWait(info.change_role_wait_time)} + {info.can_change_time + ? ` · 可换绑 ${formatTime(info.can_change_time)}` + : ''} + + + + ); + })} + + + {EXCHANGE_SCOPES.map(({ key, label }) => { const rows = recordGroups[key] ?? []; return (