feat(account): 一键查询展示「角色绑定明细」—— 不只一个"已绑定"
用户看到绑定状态后问:「是不是在详情中增加详细的绑定信息啥的」。
`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 通过。
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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<string, Array<Record<string, unknown>>>;
|
||||
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<string, string>;
|
||||
}
|
||||
@@ -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({
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Card size="small" title="🎮 角色绑定明细">
|
||||
<Row gutter={[16, 12]}>
|
||||
{BIND_SCOPES.map(({ key, label }) => {
|
||||
const info = (result.bind ?? {})[key];
|
||||
if (!info) {
|
||||
return (
|
||||
<Col span={8} key={key}>
|
||||
<Text type="secondary">{label}:未查询</Text>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Col span={8} key={key}>
|
||||
<Space direction="vertical" size={2}>
|
||||
<Text strong>{label}</Text>
|
||||
<Text>
|
||||
{info.role_name ? info.role_name : '(未绑定角色)'}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{[info.plat_name, info.area_name].filter(Boolean).join(' · ') || '-'}
|
||||
</Text>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
换绑冷却 {formatWait(info.change_role_wait_time)}
|
||||
{info.can_change_time
|
||||
? ` · 可换绑 ${formatTime(info.can_change_time)}`
|
||||
: ''}
|
||||
</Text>
|
||||
</Space>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{EXCHANGE_SCOPES.map(({ key, label }) => {
|
||||
const rows = recordGroups[key] ?? [];
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user