优化电竞手册换绑时间展示

This commit is contained in:
yml2213
2026-07-28 17:27:35 +08:00
parent d7a8666fe4
commit 624198bca4
8 changed files with 98 additions and 8 deletions
@@ -0,0 +1,31 @@
"""新增电竞手册可换绑时间
Revision ID: 20260728_0012
Revises: 20260728_0011
Create Date: 2026-07-28
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "20260728_0012"
down_revision: Union[str, None] = "20260728_0011"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
bind = op.get_bind()
columns = {column["name"] for column in sa.inspect(bind).get_columns("accounts")}
if "esports_can_change_time" not in columns:
op.add_column("accounts", sa.Column("esports_can_change_time", sa.Integer(), nullable=True))
def downgrade() -> None:
bind = op.get_bind()
columns = {column["name"] for column in sa.inspect(bind).get_columns("accounts")}
if "esports_can_change_time" in columns:
op.drop_column("accounts", "esports_can_change_time")
+1
View File
@@ -69,6 +69,7 @@ class Account(Base):
esports_game_channel = Column(String(128), default="")
esports_bind_status = Column(String(32), default="")
esports_change_role_wait_time = Column(Integer, nullable=True)
esports_can_change_time = Column(Integer, nullable=True)
created_at = Column(DateTime, default=_utcnow)
updated_at = Column(DateTime, default=_utcnow, onupdate=_utcnow)
+1
View File
@@ -112,6 +112,7 @@ def _account_out(account: Account) -> DouyuTaskAccountOut:
esports_game_channel=account.esports_game_channel or "",
esports_bind_status=account.esports_bind_status or "",
esports_change_role_wait_time=account.esports_change_role_wait_time,
esports_can_change_time=account.esports_can_change_time,
assigned_to=account.assigned_to,
assigned_username=account.assigned_user.username if account.assigned_user else None,
)
+1
View File
@@ -671,6 +671,7 @@ class DouyuTaskAccountOut(BaseModel):
esports_game_channel: str = ""
esports_bind_status: str = ""
esports_change_role_wait_time: Optional[int] = None
esports_can_change_time: Optional[int] = None
assigned_to: Optional[int] = None
assigned_username: Optional[str] = None
+7
View File
@@ -174,6 +174,10 @@ class DouyuBatchRunner:
"""综合 can_change_role 与换绑倒计时判断是否允许换绑。"""
if not bind_info:
return True
if str(bind_info.get("api_version") or "") == "esports":
can_change_time = cls._to_int(bind_info.get("can_change_time"))
if can_change_time is not None:
return can_change_time <= int(datetime.now(timezone.utc).timestamp())
wait_time = cls._to_int(bind_info.get("change_role_wait_time"))
if wait_time is not None and wait_time > 0:
return False
@@ -247,6 +251,7 @@ class DouyuBatchRunner:
"need_bind_act": self._is_truthy_flag(info.get("need_bind_act")),
"need_bind_role": self._is_truthy_flag(info.get("need_bind_role")),
"change_role_wait_time": self._to_int(info.get("change_role_wait_time")),
"can_change_time": self._to_int(info.get("can_change_time")),
"can_change_role": self._can_change_role(info),
"bind_info": info,
}
@@ -266,6 +271,7 @@ class DouyuBatchRunner:
f" need_act={1 if snap['need_bind_act'] else 0}"
f" need_role={1 if snap['need_bind_role'] else 0}"
f" wait={snap['change_role_wait_time'] if snap['change_role_wait_time'] is not None else '-'}"
f" can_change_time={snap['can_change_time'] if snap['can_change_time'] is not None else '-'}"
f"{pending_text}"
)
@@ -283,6 +289,7 @@ class DouyuBatchRunner:
account.esports_game_name = role_name or account.esports_game_name
account.esports_game_channel = self._role_channel(bind_info) or account.esports_game_channel
account.esports_change_role_wait_time = self._to_int(bind_info.get("change_role_wait_time"))
account.esports_can_change_time = self._to_int(bind_info.get("can_change_time"))
account.esports_bind_status = status
account.updated_at = datetime.now(timezone.utc)
+1
View File
@@ -201,6 +201,7 @@ export interface DouyuTaskAccountItem {
esports_game_channel: string;
esports_bind_status: string;
esports_change_role_wait_time: number | null;
esports_can_change_time: number | null;
assigned_to: number | null;
assigned_username: string | null;
}
+48 -8
View File
@@ -181,6 +181,21 @@ function formatWaitSeconds(seconds: number | null | undefined): string {
return `${minutes}${secs}`;
}
function esportsRebindStatus(canChangeTime: number | null | undefined): {
available: boolean;
text: string;
} | null {
if (canChangeTime == null || Number.isNaN(Number(canChangeTime))) return null;
const timestamp = Math.floor(Number(canChangeTime));
if (timestamp <= 0 || timestamp <= Math.floor(Date.now() / 1000)) {
return { available: true, text: '可换绑' };
}
return {
available: false,
text: new Date(timestamp * 1000).toLocaleString('zh-CN', { hour12: false }),
};
}
function resultNumber(result: Record<string, unknown> | null | undefined, key: string): number | null {
const value = result?.[key];
if (typeof value === 'number' && Number.isFinite(value)) return value;
@@ -267,7 +282,9 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
// 换绑时间:优先最近一次查询/拦截任务结果,回退账号落库字段
const latestChangeWaitByAccount = useMemo(() => {
const map = new Map<number, { wait: number | null; text: string; taskId: number }>();
const map = new Map<number, {
wait: number | null; canChangeTime: number | null; text: string; taskId: number;
}>();
for (const task of tasks) {
const taskTypes = isEsportsHandbook
? ['prepare_esports_bind', 'get_esports_bind_qr', 'query_esports_game_name', 'confirm_esports_bind']
@@ -275,12 +292,14 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
if (!taskTypes.includes(task.task_type)) continue;
if (!['success', 'failed'].includes(task.status)) continue;
const wait = resultNumber(task.result, 'change_role_wait_time');
const canChangeTime = resultNumber(task.result, 'can_change_time');
// get_bind_qr 只有冷却拦截时才有 wait;无 wait 时跳过,避免覆盖
if (!isEsportsHandbook && task.task_type === 'get_bind_qr' && wait == null) continue;
const existing = map.get(task.account_id);
if (existing && existing.taskId > task.id) continue;
const text = resultText(task.result, 'change_role_wait_text') || formatWaitSeconds(wait);
map.set(task.account_id, { wait, text, taskId: task.id });
const esportsStatus = isEsportsHandbook ? esportsRebindStatus(canChangeTime) : null;
const text = esportsStatus?.text || resultText(task.result, 'change_role_wait_text') || formatWaitSeconds(wait);
map.set(task.account_id, { wait, canChangeTime, text, taskId: task.id });
}
return map;
}, [isEsportsHandbook, tasks]);
@@ -610,7 +629,8 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
const esportsPlatName = resultText(esportsResult, 'plat_name');
const esportsRoleLine = [esportsPlatName, esportsAreaName].filter(Boolean).join(' / ');
const esportsCanChangeRole = Boolean(esportsBindTask && !esportsConfirmRunning);
const esportsChangeWait = resultNumber(esportsResult, 'change_role_wait_time');
const esportsCanChangeTime = resultNumber(esportsResult, 'can_change_time');
const esportsRebind = esportsRebindStatus(esportsCanChangeTime);
const esportsQrUrl = resultText(esportsLatestQrTask?.result, 'url');
const esportsAccountName = esportsBindTask
? (
@@ -786,6 +806,20 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
title: '换绑时间', dataIndex: 'change_role_wait_time', width: 130,
render: (_, record) => {
const fromTask = latestChangeWaitByAccount.get(record.id);
const canChangeTime = fromTask?.canChangeTime ?? (
isEsportsHandbook ? record.esports_can_change_time : null
);
const esportsStatus = isEsportsHandbook ? esportsRebindStatus(canChangeTime) : null;
if (esportsStatus) {
return esportsStatus.available
? <Tag color="success"></Tag>
: (
<Space direction="vertical" size={0}>
<Tag color="orange"></Tag>
<Text type="secondary" style={{ fontSize: 12 }}>{esportsStatus.text}</Text>
</Space>
);
}
const wait = fromTask?.wait ?? (
isEsportsHandbook ? record.esports_change_role_wait_time : record.change_role_wait_time
);
@@ -809,9 +843,13 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
return <Text type="secondary">{text || '-'}</Text>;
},
sorter: (a, b) => {
const aw = latestChangeWaitByAccount.get(a.id)?.wait
const aw = isEsportsHandbook
? latestChangeWaitByAccount.get(a.id)?.canChangeTime ?? a.esports_can_change_time ?? -1
: latestChangeWaitByAccount.get(a.id)?.wait
?? (isEsportsHandbook ? a.esports_change_role_wait_time : a.change_role_wait_time) ?? -1;
const bw = latestChangeWaitByAccount.get(b.id)?.wait
const bw = isEsportsHandbook
? latestChangeWaitByAccount.get(b.id)?.canChangeTime ?? b.esports_can_change_time ?? -1
: latestChangeWaitByAccount.get(b.id)?.wait
?? (isEsportsHandbook ? b.esports_change_role_wait_time : b.change_role_wait_time) ?? -1;
return aw - bw;
},
@@ -1391,8 +1429,10 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
<Text type="secondary"></Text>
)}
{esportsChangeWait != null && esportsChangeWait > 0 && esportsRoleName ? (
<Text type="secondary">{formatWaitSeconds(esportsChangeWait)}</Text>
{esportsRebind && esportsRoleName ? (
<Text type="secondary">
{esportsRebind.available ? '当前可换绑' : `可换绑时间:${esportsRebind.text}`}
</Text>
) : null}
{esportsQrUrl ? (