优化斗鱼绑定状态同步
This commit is contained in:
@@ -120,7 +120,7 @@ class DouyuConfig(Base):
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
manual_id = Column(String(64), default="G4KA4Qnz4LDp7")
|
||||
rid = Column(String(64), default="9263298")
|
||||
bind_act_alias = Column(String(64), default="20250213NQCYX")
|
||||
bind_act_alias = Column(String(64), default="20260120QYOOB")
|
||||
confirm_act_alias = Column(String(64), default="20260120QYOOB")
|
||||
legacy_act_alias = Column(String(64), default="cjm")
|
||||
room_id = Column(String(64), default="9263298")
|
||||
|
||||
@@ -542,7 +542,7 @@ class HuyaRechargeGoodsOut(BaseModel):
|
||||
class DouyuConfigOut(BaseModel):
|
||||
manual_id: str = "G4KA4Qnz4LDp7"
|
||||
rid: str = "9263298"
|
||||
bind_act_alias: str = "20250213NQCYX"
|
||||
bind_act_alias: str = "20260120QYOOB"
|
||||
confirm_act_alias: str = "20260120QYOOB"
|
||||
legacy_act_alias: str = "cjm"
|
||||
room_id: str = "9263298"
|
||||
|
||||
@@ -150,6 +150,16 @@ class DouyuBatchRunner:
|
||||
def _current_bind_act_alias(cls, config: dict) -> str:
|
||||
return cls._confirm_act_alias(config) or cls._bind_qr_act_alias(config)
|
||||
|
||||
@classmethod
|
||||
def _action_act_aliases(cls, config: dict) -> list[str]:
|
||||
"""当前活动动作 alias;不包含只用于查询最新扫码态的 legacy/cjm。"""
|
||||
aliases: list[str] = []
|
||||
for key in ("confirm_act_alias", "bind_act_alias"):
|
||||
alias = cls._action_act_alias(config, key)
|
||||
if alias and alias not in aliases:
|
||||
aliases.append(alias)
|
||||
return aliases
|
||||
|
||||
@staticmethod
|
||||
def _role_channel(bind_info: dict) -> str:
|
||||
return " / ".join(
|
||||
@@ -194,8 +204,7 @@ class DouyuBatchRunner:
|
||||
if not bind_info:
|
||||
return False
|
||||
role_name = str(bind_info.get("role_name") or "").strip()
|
||||
is_bound = cls._is_bound_act(bind_info) or bool(role_name)
|
||||
if not is_bound:
|
||||
if not role_name or not cls._is_bound_act(bind_info):
|
||||
return False
|
||||
return not cls._can_change_role(bind_info)
|
||||
|
||||
@@ -768,6 +777,32 @@ class DouyuBatchRunner:
|
||||
return info
|
||||
return ordered[0]
|
||||
|
||||
def _pick_current_bound_info(
|
||||
self,
|
||||
candidates: list[dict],
|
||||
config: dict,
|
||||
*,
|
||||
extra_prefer_aliases: list[str] | None = None,
|
||||
) -> dict | None:
|
||||
"""选当前活动已生效绑定,避免把 legacy/cjm 的待确认态当成当前角色。"""
|
||||
if not candidates:
|
||||
return None
|
||||
prefer = []
|
||||
for alias in [*(extra_prefer_aliases or []), *self._action_act_aliases(config)]:
|
||||
alias = str(alias or "").strip()
|
||||
if alias and alias not in prefer:
|
||||
prefer.append(alias)
|
||||
|
||||
bound = [
|
||||
info
|
||||
for info in candidates
|
||||
if self._is_bound_act(info) and str(info.get("role_name") or "").strip()
|
||||
and str(info.get("act_alias") or "") in prefer
|
||||
]
|
||||
if not bound:
|
||||
return None
|
||||
return sorted(bound, key=lambda info: prefer.index(str(info.get("act_alias") or "")))[0]
|
||||
|
||||
def _pick_baseline_bind_info(
|
||||
self,
|
||||
candidates: list[dict],
|
||||
@@ -781,26 +816,7 @@ class DouyuBatchRunner:
|
||||
"""
|
||||
if not candidates:
|
||||
return None
|
||||
prefer = []
|
||||
for key in ("confirm_act_alias", "bind_act_alias", "legacy_act_alias"):
|
||||
alias = str(config.get(key) or "").strip()
|
||||
if alias and alias not in prefer:
|
||||
prefer.append(alias)
|
||||
|
||||
# 先找“已绑定且有角色”的活动结果
|
||||
for alias in prefer:
|
||||
for info in candidates:
|
||||
if str(info.get("act_alias") or "") != alias:
|
||||
continue
|
||||
if self._is_bound_act(info) and str(info.get("role_name") or "").strip():
|
||||
return info
|
||||
|
||||
# 再退回任意有角色的结果(仍按活动 alias 优先)
|
||||
return self._pick_bind_info(
|
||||
candidates,
|
||||
prefer_pending=False,
|
||||
prefer_aliases=prefer,
|
||||
)
|
||||
return self._pick_current_bound_info(candidates, config)
|
||||
|
||||
def _wait_bind_role_result(
|
||||
self,
|
||||
@@ -992,19 +1008,21 @@ class DouyuBatchRunner:
|
||||
self._mark_task(db, task, "failed", "查询绑定信息失败,请检查 Cookie 或 actAlias")
|
||||
return
|
||||
# baseline 用活动 alias 的“当前已绑定”;pending 检测优先 cjm 的换绑最新态
|
||||
before = self._pick_baseline_bind_info(before_candidates, config) or before_candidates[0]
|
||||
before = self._pick_baseline_bind_info(before_candidates, config) or {}
|
||||
# 换绑冷却必须看活动当前绑定(QYOOB),不要用 cjm
|
||||
cooldown_info = self._pick_change_wait_bind_info(before_candidates, config) or before
|
||||
cooldown_info = self._pick_change_wait_bind_info(before_candidates, config)
|
||||
pending_before = self._pick_bind_info(
|
||||
before_candidates,
|
||||
baseline_role_name=str(before.get("role_name") or ""),
|
||||
baseline_is_bound_act=self._is_bound_act(before),
|
||||
prefer_pending=True,
|
||||
prefer_aliases=query_aliases,
|
||||
) or before
|
||||
) or before or before_candidates[0]
|
||||
before_snapshot = self._bind_snapshot(before)
|
||||
cooldown_snapshot = self._bind_snapshot(cooldown_info)
|
||||
current_role_name = before_snapshot["role_name"] or cooldown_snapshot["role_name"]
|
||||
current_role_name = before_snapshot["role_name"] or (
|
||||
cooldown_snapshot["role_name"] if cooldown_snapshot["is_bound_act"] else ""
|
||||
)
|
||||
wait_time = cooldown_snapshot["change_role_wait_time"]
|
||||
self._push_log(
|
||||
"info",
|
||||
@@ -1118,6 +1136,9 @@ class DouyuBatchRunner:
|
||||
query_aliases = self._query_bind_act_aliases(config)
|
||||
if confirm_alias and confirm_alias not in query_aliases:
|
||||
query_aliases = [confirm_alias, *query_aliases]
|
||||
if not confirm_alias:
|
||||
self._mark_task(db, task, "failed", "请先配置确认绑定活动 actAlias")
|
||||
return
|
||||
if not query_aliases:
|
||||
self._mark_task(db, task, "failed", "请先配置绑定活动 actAlias")
|
||||
return
|
||||
@@ -1137,13 +1158,10 @@ class DouyuBatchRunner:
|
||||
role_name = before_snapshot["role_name"]
|
||||
query_alias = str(before.get("act_alias") or "")
|
||||
# 确认前“已生效绑定”角色(bound_act=1),确认失败/回查失败时写库用,避免待确认角色污染 game_name
|
||||
before_bound = next(
|
||||
(
|
||||
info
|
||||
for info in before_candidates
|
||||
if self._is_bound_act(info) and str(info.get("role_name") or "").strip()
|
||||
),
|
||||
None,
|
||||
before_bound = self._pick_current_bound_info(
|
||||
before_candidates,
|
||||
config,
|
||||
extra_prefer_aliases=[confirm_alias],
|
||||
)
|
||||
self._push_log(
|
||||
"info",
|
||||
@@ -1169,8 +1187,13 @@ class DouyuBatchRunner:
|
||||
)
|
||||
return
|
||||
|
||||
if before_snapshot["is_bound_act"]:
|
||||
self._apply_bind_info_to_account(account, before, "bind_confirmed")
|
||||
before_is_current_bound = (
|
||||
before_bound is not None
|
||||
and str(before.get("act_alias") or "") == str(before_bound.get("act_alias") or "")
|
||||
and role_name == str(before_bound.get("role_name") or "")
|
||||
)
|
||||
if before_is_current_bound:
|
||||
self._apply_bind_info_to_account(account, before_bound, "bind_confirmed")
|
||||
self._mark_task(
|
||||
db,
|
||||
task,
|
||||
@@ -1180,8 +1203,8 @@ class DouyuBatchRunner:
|
||||
"act_alias": confirm_alias or query_alias,
|
||||
"query_act_alias": query_alias,
|
||||
"query_act_aliases": query_aliases,
|
||||
"before_bind_info": before,
|
||||
**before_snapshot,
|
||||
"before_bind_info": before_bound,
|
||||
**self._bind_snapshot(before_bound),
|
||||
"bind_ready_for_confirm": True,
|
||||
"bind_confirmed": True,
|
||||
"bind_phase": "confirmed",
|
||||
@@ -1197,7 +1220,11 @@ class DouyuBatchRunner:
|
||||
confirm_msg = str(exc)
|
||||
self._push_log("warning", f"确认绑定接口失败: {confirm_msg}")
|
||||
# 待绑定游戏账号侧换绑限制(未到换绑时间等)导致确认失败:保留原绑定并给出明确提示
|
||||
self._apply_bind_info_to_account(account, before_bound or before, "game_queried")
|
||||
if before_bound is not None:
|
||||
self._apply_bind_info_to_account(account, before_bound, "game_queried")
|
||||
else:
|
||||
account.bind_status = "game_queried"
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
self._mark_task(
|
||||
db,
|
||||
task,
|
||||
@@ -1223,18 +1250,11 @@ class DouyuBatchRunner:
|
||||
f"error={confirm_raw.get('error')} msg={confirm_raw.get('msg') or '-'}",
|
||||
)
|
||||
def _pick_bound_after(candidates: list[dict]) -> dict | None:
|
||||
"""确认后回查:只认已生效绑定(bound_act=1)。
|
||||
|
||||
cjm 的角色名恒有且 bound_act 恒为 0,若按 alias 顺序挑选会永远
|
||||
判定“确认未生效”,必须按 bound_act=1 判定确认结果。
|
||||
"""
|
||||
return next(
|
||||
(
|
||||
info
|
||||
for info in candidates
|
||||
if self._is_bound_act(info) and str(info.get("role_name") or "").strip()
|
||||
),
|
||||
None,
|
||||
"""确认后回查:只认活动 alias 上的已生效绑定(bound_act=1)。"""
|
||||
return self._pick_current_bound_info(
|
||||
candidates,
|
||||
config,
|
||||
extra_prefer_aliases=[use_confirm_alias],
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -1255,7 +1275,11 @@ class DouyuBatchRunner:
|
||||
break
|
||||
if after is None:
|
||||
# 已生效绑定始终未出现:确认未生效(或同步延迟超时),保留原绑定
|
||||
self._apply_bind_info_to_account(account, before_bound or before, "game_queried")
|
||||
if before_bound is not None:
|
||||
self._apply_bind_info_to_account(account, before_bound, "game_queried")
|
||||
else:
|
||||
account.bind_status = "game_queried"
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
self._mark_task(
|
||||
db,
|
||||
task,
|
||||
@@ -1284,7 +1308,11 @@ class DouyuBatchRunner:
|
||||
except DouyuActivityError as exc:
|
||||
# 查询接口异常:确认接口已成功时按成功处理,但保留错误信息。
|
||||
# 写库优先确认前已生效绑定,避免待确认角色被误写入。
|
||||
self._apply_bind_info_to_account(account, before_bound or before, "bind_confirmed")
|
||||
if before_bound is not None:
|
||||
self._apply_bind_info_to_account(account, before_bound, "bind_confirmed")
|
||||
else:
|
||||
account.bind_status = "bind_confirmed"
|
||||
account.updated_at = datetime.now(timezone.utc)
|
||||
self._mark_task(
|
||||
db,
|
||||
task,
|
||||
@@ -2006,14 +2034,7 @@ class DouyuBatchRunner:
|
||||
|
||||
# 1) 优先“已生效绑定”角色(bound_act=1 且有角色名,通常是活动 alias)。
|
||||
# 避免把扫码后未确认的新角色当成当前绑定结果。
|
||||
bound_info = next(
|
||||
(
|
||||
info
|
||||
for info in candidates
|
||||
if self._is_bound_act(info) and str(info.get("role_name") or "").strip()
|
||||
),
|
||||
None,
|
||||
)
|
||||
bound_info = self._pick_current_bound_info(candidates, config)
|
||||
# 2) 待确认角色(cjm 扫码后未确认;无已绑定时也用于首次绑定展示)
|
||||
pending_info = self._pick_bind_info(
|
||||
candidates,
|
||||
@@ -2089,30 +2110,15 @@ class DouyuBatchRunner:
|
||||
"""换绑倒计时优先看活动当前绑定(QYOOB),不是 cjm 换绑最新态。"""
|
||||
if not candidates:
|
||||
return None
|
||||
# 1) 优先活动 alias 上有角色的结果
|
||||
baseline = self._pick_baseline_bind_info(candidates, config)
|
||||
if baseline is not None:
|
||||
wait = self._to_int(baseline.get("change_role_wait_time"))
|
||||
if wait is not None:
|
||||
return baseline
|
||||
# 2) 任意带 wait_time 的结果里取 wait 最大的
|
||||
with_wait = []
|
||||
for info in candidates:
|
||||
wait = self._to_int(info.get("change_role_wait_time"))
|
||||
if wait is not None:
|
||||
with_wait.append((wait, info))
|
||||
if with_wait:
|
||||
with_wait.sort(key=lambda item: item[0], reverse=True)
|
||||
return with_wait[0][1]
|
||||
# 3) 回退 baseline / 首个有角色
|
||||
return baseline or self._pick_bind_info(
|
||||
candidates,
|
||||
# 冷却是“当前已生效绑定”的属性,不能用 legacy/cjm 的待确认角色判断。
|
||||
current_bound = self._pick_current_bound_info(candidates, config)
|
||||
if current_bound is not None:
|
||||
return current_bound
|
||||
action_aliases = self._action_act_aliases(config)
|
||||
return self._pick_bind_info(
|
||||
[info for info in candidates if str(info.get("act_alias") or "") in action_aliases],
|
||||
prefer_pending=False,
|
||||
prefer_aliases=[
|
||||
str(config.get("confirm_act_alias") or "").strip(),
|
||||
str(config.get("bind_act_alias") or "").strip(),
|
||||
str(config.get("legacy_act_alias") or "").strip(),
|
||||
],
|
||||
prefer_aliases=action_aliases,
|
||||
)
|
||||
|
||||
def _execute_query_change_bind_time(self, db: Session, task: DouyuTask, account: Account, cookie: str, config: dict):
|
||||
|
||||
@@ -334,6 +334,29 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
return map;
|
||||
}, [tasks]);
|
||||
|
||||
const latestConfirmBindTaskByAccount = useMemo(() => {
|
||||
const map = new Map<number, DouyuTaskItem>();
|
||||
for (const task of tasks) {
|
||||
if (task.task_type !== 'confirm_bind') continue;
|
||||
if (!task.result) continue;
|
||||
const previous = map.get(task.account_id);
|
||||
if (!previous || task.id > previous.id) map.set(task.account_id, task);
|
||||
}
|
||||
return map;
|
||||
}, [tasks]);
|
||||
|
||||
const latestConfirmBindFailureByAccount = useMemo(() => {
|
||||
const map = new Map<number, DouyuTaskItem>();
|
||||
for (const task of tasks) {
|
||||
if (task.task_type !== 'confirm_bind') continue;
|
||||
if (!['failed', 'error'].includes(task.status)) continue;
|
||||
if (!(task.message || '').includes('待绑定游戏账号')) continue;
|
||||
const previous = map.get(task.account_id);
|
||||
if (!previous || task.id > previous.id) map.set(task.account_id, task);
|
||||
}
|
||||
return map;
|
||||
}, [tasks]);
|
||||
|
||||
const latestEsportsStateTaskByAccount = useMemo(() => {
|
||||
const map = new Map<number, DouyuTaskItem>();
|
||||
for (const task of tasks) {
|
||||
@@ -356,7 +379,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
[tasks, activeQrTaskId],
|
||||
);
|
||||
|
||||
// 换绑时间:优先最近一次查询/拦截任务结果,回退账号落库字段
|
||||
// 换绑时间:优先最近一次查询/拦截/确认成功结果,回退账号落库字段
|
||||
const latestChangeWaitByAccount = useMemo(() => {
|
||||
const map = new Map<number, {
|
||||
wait: number | null; canChangeTime: number | null; text: string; taskId: number;
|
||||
@@ -364,9 +387,20 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
for (const task of tasks) {
|
||||
const taskTypes = isEsportsHandbook
|
||||
? ['prepare_esports_bind', 'get_esports_bind_qr', 'query_esports_game_name', 'confirm_esports_bind']
|
||||
: ['query_change_bind_time', 'get_bind_qr'];
|
||||
: ['query_change_bind_time', 'get_bind_qr', 'confirm_bind'];
|
||||
if (!taskTypes.includes(task.task_type)) continue;
|
||||
if (!['success', 'failed'].includes(task.status)) continue;
|
||||
if (
|
||||
!isEsportsHandbook
|
||||
&& task.task_type === 'confirm_bind'
|
||||
&& (
|
||||
task.status !== 'success'
|
||||
|| !(
|
||||
resultFlag(task.result, 'bind_confirmed')
|
||||
|| resultFlag(task.result, 'is_bound_act')
|
||||
)
|
||||
)
|
||||
) continue;
|
||||
const wait = resultNumber(task.result, 'change_role_wait_time');
|
||||
const canChangeTime = resultNumber(task.result, 'can_change_time');
|
||||
// get_bind_qr 只有冷却拦截时才有 wait;无 wait 时跳过,避免覆盖
|
||||
@@ -644,6 +678,14 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
for (const task of failedTasks) autoNotifiedConfirmFailedTaskIds.current.add(task.id);
|
||||
const latest = failedTasks[0];
|
||||
setConfirmFailTip({ taskId: latest.id, message: latest.message });
|
||||
setQrTaskIds((prev) => {
|
||||
const next = prev.filter((id) => {
|
||||
const task = visibleTasks.find((item) => item.id === id);
|
||||
return !task || task.account_id !== latest.account_id;
|
||||
});
|
||||
setActiveQrTaskId((cur) => (cur != null && !next.includes(cur) ? (next[next.length - 1] ?? null) : cur));
|
||||
return next;
|
||||
});
|
||||
}, [visibleTasks]);
|
||||
|
||||
// 弹窗倒计时自动关闭
|
||||
@@ -1019,32 +1061,76 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '游戏名', dataIndex: 'game_name', width: 130,
|
||||
title: '游戏名', dataIndex: 'game_name', width: 170,
|
||||
render: (_, record) => {
|
||||
const queryResult = (
|
||||
isEsportsHandbook
|
||||
? latestEsportsStateTaskByAccount.get(record.id)
|
||||
: latestQueryGameTaskByAccount.get(record.id)
|
||||
)?.result || null;
|
||||
// role_name = 当前已生效绑定角色;pending_role_name = 扫码后待确认的新角色
|
||||
const roleName = resultText(queryResult, 'role_name')
|
||||
|| (isEsportsHandbook ? record.esports_game_name : record.game_name);
|
||||
const pendingRoleName = resultText(queryResult, 'pending_role_name');
|
||||
const channel = (
|
||||
[resultText(queryResult, 'area_name'), resultText(queryResult, 'plat_name')]
|
||||
.filter(Boolean)
|
||||
.join(' / ')
|
||||
|| (isEsportsHandbook ? record.esports_game_channel : record.game_channel)
|
||||
const queryTask = isEsportsHandbook
|
||||
? latestEsportsStateTaskByAccount.get(record.id) || null
|
||||
: latestQueryGameTaskByAccount.get(record.id) || null;
|
||||
const confirmTask = isEsportsHandbook ? null : latestConfirmBindTaskByAccount.get(record.id) || null;
|
||||
const confirmSucceeded = Boolean(
|
||||
confirmTask
|
||||
&& confirmTask.status === 'success'
|
||||
&& (
|
||||
resultFlag(confirmTask.result, 'bind_confirmed')
|
||||
|| resultFlag(confirmTask.result, 'is_bound_act')
|
||||
),
|
||||
);
|
||||
return roleName ? (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text>{roleName}</Text>
|
||||
const stateTask = (
|
||||
confirmSucceeded
|
||||
&& (!queryTask || confirmTask!.id > queryTask.id)
|
||||
) ? confirmTask : queryTask;
|
||||
const confirmFailureTask = latestConfirmBindFailureByAccount.get(record.id);
|
||||
const clearPending = Boolean(
|
||||
!isEsportsHandbook
|
||||
&& confirmFailureTask
|
||||
&& stateTask
|
||||
&& confirmFailureTask.id > stateTask.id,
|
||||
);
|
||||
const stateResult = stateTask?.result || null;
|
||||
const stateRoleConfirmed = resultFlag(stateResult, 'is_bound_act') || resultFlag(stateResult, 'bind_confirmed');
|
||||
const stateRoleName = clearPending && !stateRoleConfirmed ? '' : resultText(stateResult, 'role_name');
|
||||
// role_name = 当前已生效绑定角色;pending_role_name = 扫码后待确认的新角色
|
||||
const roleName = stateRoleName
|
||||
|| (isEsportsHandbook ? record.esports_game_name : record.game_name);
|
||||
const stateChannel = clearPending && !stateRoleConfirmed
|
||||
? ''
|
||||
: [resultText(stateResult, 'area_name'), resultText(stateResult, 'plat_name')]
|
||||
.filter(Boolean)
|
||||
.join(' / ');
|
||||
const pendingRoleName = clearPending || confirmSucceeded ? '' : resultText(stateResult, 'pending_role_name');
|
||||
const channel = stateChannel || (isEsportsHandbook ? record.esports_game_channel : record.game_channel);
|
||||
const pendingChannel = clearPending || confirmSucceeded ? '' : [resultText(stateResult, 'pending_area_name'), resultText(stateResult, 'pending_plat_name')]
|
||||
.filter(Boolean)
|
||||
.join(' / ');
|
||||
if (!roleName && !pendingRoleName) return <Text type="secondary">未查</Text>;
|
||||
return (
|
||||
<Space direction="vertical" size={2} style={{ lineHeight: 1.35 }}>
|
||||
{roleName ? (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text ellipsis title={roleName}>
|
||||
{pendingRoleName ? `当前: ${roleName}` : roleName}
|
||||
</Text>
|
||||
{channel ? (
|
||||
<Text type="secondary" style={{ fontSize: 12 }} ellipsis title={channel}>
|
||||
{channel}
|
||||
</Text>
|
||||
) : null}
|
||||
</Space>
|
||||
) : null}
|
||||
{pendingRoleName ? (
|
||||
<Text type="warning" style={{ fontSize: 12 }}>待确认: {pendingRoleName}</Text>
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text type="warning" style={{ fontSize: 12 }} ellipsis title={pendingRoleName}>
|
||||
待确认: {pendingRoleName}
|
||||
</Text>
|
||||
{pendingChannel ? (
|
||||
<Text type="warning" style={{ fontSize: 12 }} ellipsis title={pendingChannel}>
|
||||
{pendingChannel}
|
||||
</Text>
|
||||
) : null}
|
||||
</Space>
|
||||
) : null}
|
||||
{channel ? <Text type="secondary" style={{ fontSize: 12 }}>{channel}</Text> : null}
|
||||
</Space>
|
||||
) : <Text type="secondary">未查</Text>;
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user