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 (