新增电竞手册独立绑定流程
This commit is contained in:
@@ -24,7 +24,9 @@ import { message } from '../utils/antdMessage';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const QUICK_ACTIONS = [
|
||||
type HandbookKind = 'elite' | 'esports';
|
||||
|
||||
const ELITE_QUICK_ACTIONS = [
|
||||
{ key: 'get_bind_qr', icon: <QrcodeOutlined /> },
|
||||
{ key: 'confirm_bind', icon: <CheckCircleOutlined /> },
|
||||
{ key: 'create_elite_qr', icon: <CreditCardOutlined /> },
|
||||
@@ -38,8 +40,37 @@ const QUICK_ACTIONS = [
|
||||
{ key: 'donate_elite_gift', icon: <GiftOutlined /> },
|
||||
];
|
||||
|
||||
const BIND_ACTION_KEYS = ['get_bind_qr', 'confirm_bind', 'query_game_name', 'query_change_bind_time'];
|
||||
const QUERY_ACTION_KEYS = ['query_points', 'query_limited_goods', 'query_gold_balance'];
|
||||
const ESPORTS_QUICK_ACTIONS = [
|
||||
{ key: 'prepare_esports_bind', icon: <QrcodeOutlined /> },
|
||||
{ key: 'query_esports_game_name', icon: <SearchOutlined /> },
|
||||
{ key: 'create_esports_qr', icon: <CreditCardOutlined /> },
|
||||
];
|
||||
|
||||
const ELITE_BIND_ACTION_KEYS = ['get_bind_qr', 'confirm_bind', 'query_game_name', 'query_change_bind_time'];
|
||||
const ELITE_QUERY_ACTION_KEYS = ['query_points', 'query_limited_goods', 'query_gold_balance'];
|
||||
const ELITE_TASK_TYPES = new Set([
|
||||
'get_bind_qr',
|
||||
'confirm_bind',
|
||||
'create_elite_qr',
|
||||
'create_gold_qr',
|
||||
'donate_elite_gift',
|
||||
'query_points',
|
||||
'exchange_goods',
|
||||
'query_game_name',
|
||||
'query_change_bind_time',
|
||||
'query_limited_goods',
|
||||
'query_gold_balance',
|
||||
'refresh_goods',
|
||||
'query_exchange_records',
|
||||
'prefetch_csrf_token',
|
||||
]);
|
||||
const ESPORTS_TASK_TYPES = new Set([
|
||||
'prepare_esports_bind',
|
||||
'get_esports_bind_qr',
|
||||
'query_esports_game_name',
|
||||
'confirm_esports_bind',
|
||||
'create_esports_qr',
|
||||
]);
|
||||
const DOUYU_GOLD_AMOUNT_STORAGE_KEY = 'douyu_task_gold_amount';
|
||||
const DOUYU_GIFT_COUNT_STORAGE_KEY = 'douyu_task_gift_count';
|
||||
|
||||
@@ -58,6 +89,14 @@ const BIND_STATUS_LABELS: Record<string, string> = {
|
||||
game_queried: '已识别角色',
|
||||
game_not_bound: '未绑定',
|
||||
bind_confirmed: '已绑定',
|
||||
esports_bind_qr_generated: '电竞待绑定',
|
||||
esports_role_queried: '已查电竞角色',
|
||||
esports_bind_ready: '电竞待确认',
|
||||
esports_role_switching: '电竞换角色',
|
||||
esports_bind_confirming: '电竞绑定中',
|
||||
esports_bound: '电竞已绑定',
|
||||
esports_qr_created: '电竞待开通',
|
||||
esports_opened: '电竞已开通',
|
||||
change_time_queried: '已查换绑',
|
||||
};
|
||||
|
||||
@@ -93,17 +132,25 @@ function bindReadyForConfirm(task: DouyuTaskItem | null | undefined): boolean {
|
||||
function hasBindQrcode(task: DouyuTaskItem | null | undefined): boolean {
|
||||
if (!task || task.task_type !== 'get_bind_qr') return false;
|
||||
// 有二维码,或已有待确认角色(无需再扫码)都应可展示绑定面板
|
||||
return Boolean(resultText(task.result, 'url')) || bindReadyForConfirm(task);
|
||||
return Boolean(resultText(task.result, 'url'))
|
||||
|| bindReadyForConfirm(task)
|
||||
|| resultFlag(task.result, 'esports_bound');
|
||||
}
|
||||
|
||||
function hasPaymentQrcode(task: DouyuTaskItem | null | undefined): boolean {
|
||||
return Boolean(task && ['create_elite_qr', 'create_gold_qr'].includes(task.task_type) && resultText(task.result, 'pay_url'));
|
||||
return Boolean(
|
||||
task
|
||||
&& ['create_elite_qr', 'create_esports_qr', 'create_gold_qr'].includes(task.task_type)
|
||||
&& resultText(task.result, 'pay_url'),
|
||||
);
|
||||
}
|
||||
|
||||
function paymentArrived(task: DouyuTaskItem | null | undefined): boolean {
|
||||
if (!task || !['create_elite_qr', 'create_gold_qr'].includes(task.task_type)) return false;
|
||||
if (!task || !['create_elite_qr', 'create_esports_qr', 'create_gold_qr'].includes(task.task_type)) return false;
|
||||
if (task.status !== 'success') return false;
|
||||
return task.result?.gold_recharged === true || task.result?.elite_opened === true;
|
||||
return task.result?.gold_recharged === true
|
||||
|| task.result?.elite_opened === true
|
||||
|| task.result?.esports_opened === true;
|
||||
}
|
||||
|
||||
function bindPhaseText(phase: string, polling: boolean): string {
|
||||
@@ -149,7 +196,14 @@ function savedPositiveInteger(key: string, fallback = 1): number {
|
||||
return Number.isInteger(value) && value >= 1 ? value : fallback;
|
||||
}
|
||||
|
||||
export default function DouyuTasksPage() {
|
||||
export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) {
|
||||
const isEsportsHandbook = handbook === 'esports';
|
||||
const handbookTitle = isEsportsHandbook ? '电竞手册工作台' : '精英宝典工作台';
|
||||
const handbookDescription = isEsportsHandbook
|
||||
? '电竞手册角色绑定与开通任务'
|
||||
: '精英宝典绑定、开通、积分与兑换任务';
|
||||
const activeTaskTypes = isEsportsHandbook ? ESPORTS_TASK_TYPES : ELITE_TASK_TYPES;
|
||||
const quickActions = isEsportsHandbook ? ESPORTS_QUICK_ACTIONS : ELITE_QUICK_ACTIONS;
|
||||
const { can } = usePermissions();
|
||||
const [accounts, setAccounts] = useState<DouyuTaskAccountItem[]>([]);
|
||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||
@@ -174,10 +228,13 @@ export default function DouyuTasksPage() {
|
||||
|
||||
// 绑定/支付弹窗以 task 为唯一数据源
|
||||
const [qrTask, setQrTask] = useState<DouyuTaskItem | null>(null);
|
||||
const [esportsBindTask, setEsportsBindTask] = useState<DouyuTaskItem | null>(null);
|
||||
const [payTask, setPayTask] = useState<DouyuTaskItem | null>(null);
|
||||
const autoOpenedQrTaskIds = useRef<Set<number>>(new Set());
|
||||
const autoOpenedEsportsBindTaskIds = useRef<Set<number>>(new Set());
|
||||
const autoOpenedPayTaskIds = useRef<Set<number>>(new Set());
|
||||
const autoOpenQrReady = useRef(false);
|
||||
const autoOpenEsportsBindReady = useRef(false);
|
||||
const autoOpenPayReady = useRef(false);
|
||||
|
||||
const logs = useWebSocketLogs();
|
||||
@@ -192,22 +249,41 @@ export default function DouyuTasksPage() {
|
||||
return map;
|
||||
}, [tasks]);
|
||||
|
||||
const latestEsportsStateTaskByAccount = useMemo(() => {
|
||||
const map = new Map<number, DouyuTaskItem>();
|
||||
for (const task of tasks) {
|
||||
if (!['prepare_esports_bind', 'get_esports_bind_qr', 'query_esports_game_name', 'confirm_esports_bind']
|
||||
.includes(task.task_type) || !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 visibleTasks = useMemo(
|
||||
() => tasks.filter((task) => activeTaskTypes.has(task.task_type)),
|
||||
[activeTaskTypes, tasks],
|
||||
);
|
||||
|
||||
// 换绑时间:优先最近一次查询/拦截任务结果,回退账号落库字段
|
||||
const latestChangeWaitByAccount = useMemo(() => {
|
||||
const map = new Map<number, { wait: number | null; text: string; taskId: number }>();
|
||||
for (const task of tasks) {
|
||||
if (!['query_change_bind_time', 'get_bind_qr'].includes(task.task_type)) continue;
|
||||
const taskTypes = isEsportsHandbook
|
||||
? ['prepare_esports_bind', 'get_esports_bind_qr', 'query_esports_game_name', 'confirm_esports_bind']
|
||||
: ['query_change_bind_time', 'get_bind_qr'];
|
||||
if (!taskTypes.includes(task.task_type)) continue;
|
||||
if (!['success', 'failed'].includes(task.status)) continue;
|
||||
const wait = resultNumber(task.result, 'change_role_wait_time');
|
||||
// get_bind_qr 只有冷却拦截时才有 wait;无 wait 时跳过,避免覆盖
|
||||
if (task.task_type === 'get_bind_qr' && wait == null) continue;
|
||||
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 });
|
||||
}
|
||||
return map;
|
||||
}, [tasks]);
|
||||
}, [isEsportsHandbook, tasks]);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -227,9 +303,13 @@ export default function DouyuTasksPage() {
|
||||
if (!autoOpenQrReady.current) {
|
||||
for (const task of taskResult.value) {
|
||||
if (hasBindQrcode(task)) autoOpenedQrTaskIds.current.add(task.id);
|
||||
if (task.task_type === 'prepare_esports_bind' && task.result?.esports_bind_dialog === true) {
|
||||
autoOpenedEsportsBindTaskIds.current.add(task.id);
|
||||
}
|
||||
if (hasPaymentQrcode(task)) autoOpenedPayTaskIds.current.add(task.id);
|
||||
}
|
||||
autoOpenQrReady.current = true;
|
||||
autoOpenEsportsBindReady.current = true;
|
||||
autoOpenPayReady.current = true;
|
||||
}
|
||||
}
|
||||
@@ -264,8 +344,8 @@ export default function DouyuTasksPage() {
|
||||
}, [giftCount]);
|
||||
|
||||
const hasActiveTasks = useMemo(
|
||||
() => tasks.some((task) => ['pending', 'running', 'planned'].includes(task.status)),
|
||||
[tasks],
|
||||
() => visibleTasks.some((task) => ['pending', 'running', 'planned'].includes(task.status)),
|
||||
[visibleTasks],
|
||||
);
|
||||
|
||||
// 有活跃任务或 WS 连接时 3 秒刷 tasks;空闲 15 秒轻量刷新
|
||||
@@ -277,33 +357,55 @@ export default function DouyuTasksPage() {
|
||||
|
||||
const openQrTask = useCallback((task: DouyuTaskItem) => {
|
||||
autoOpenedQrTaskIds.current.add(task.id);
|
||||
setEsportsBindTask(null);
|
||||
setPayTask(null);
|
||||
setQrTask(task);
|
||||
}, []);
|
||||
|
||||
const openEsportsBindTask = useCallback((task: DouyuTaskItem) => {
|
||||
autoOpenedEsportsBindTaskIds.current.add(task.id);
|
||||
setQrTask(null);
|
||||
setPayTask(null);
|
||||
setEsportsBindTask(task);
|
||||
}, []);
|
||||
|
||||
const openPayTask = useCallback((task: DouyuTaskItem) => {
|
||||
autoOpenedPayTaskIds.current.add(task.id);
|
||||
setQrTask(null);
|
||||
setEsportsBindTask(null);
|
||||
setPayTask(task);
|
||||
}, []);
|
||||
|
||||
// 自动弹出最新绑定二维码(running 有 url 即可)
|
||||
useEffect(() => {
|
||||
if (!autoOpenQrReady.current || qrTask) return;
|
||||
const nextQrTask = tasks
|
||||
const nextQrTask = visibleTasks
|
||||
.filter((task) => hasBindQrcode(task) && !autoOpenedQrTaskIds.current.has(task.id))
|
||||
.sort((a, b) => b.id - a.id)[0];
|
||||
if (nextQrTask) openQrTask(nextQrTask);
|
||||
}, [openQrTask, qrTask, tasks]);
|
||||
}, [openQrTask, qrTask, visibleTasks]);
|
||||
|
||||
// 电竞手册先查状态再打开独立绑定面板,不把切换角色二维码当作主流程。
|
||||
useEffect(() => {
|
||||
if (!autoOpenEsportsBindReady.current || esportsBindTask) return;
|
||||
const nextTask = visibleTasks
|
||||
.filter((task) => (
|
||||
task.task_type === 'prepare_esports_bind'
|
||||
&& task.result?.esports_bind_dialog === true
|
||||
&& !autoOpenedEsportsBindTaskIds.current.has(task.id)
|
||||
))
|
||||
.sort((a, b) => b.id - a.id)[0];
|
||||
if (nextTask) openEsportsBindTask(nextTask);
|
||||
}, [esportsBindTask, openEsportsBindTask, visibleTasks]);
|
||||
|
||||
// 自动弹出支付二维码
|
||||
useEffect(() => {
|
||||
if (!autoOpenPayReady.current || payTask) return;
|
||||
const nextPayTask = tasks
|
||||
const nextPayTask = visibleTasks
|
||||
.filter((task) => hasPaymentQrcode(task) && !paymentArrived(task) && !autoOpenedPayTaskIds.current.has(task.id))
|
||||
.sort((a, b) => b.id - a.id)[0];
|
||||
if (nextPayTask) openPayTask(nextPayTask);
|
||||
}, [openPayTask, payTask, tasks]);
|
||||
}, [openPayTask, payTask, visibleTasks]);
|
||||
|
||||
// 弹窗跟随最新 task 状态(后端 progress 写入后 UI 自动更新)
|
||||
useEffect(() => {
|
||||
@@ -312,6 +414,12 @@ export default function DouyuTasksPage() {
|
||||
if (latest && latest !== qrTask) setQrTask(latest);
|
||||
}, [qrTask, tasks]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!esportsBindTask) return;
|
||||
const latest = tasks.find((task) => task.id === esportsBindTask.id);
|
||||
if (latest && latest !== esportsBindTask) setEsportsBindTask(latest);
|
||||
}, [esportsBindTask, tasks]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!payTask) return;
|
||||
const latest = tasks.find((task) => task.id === payTask.id);
|
||||
@@ -325,14 +433,25 @@ export default function DouyuTasksPage() {
|
||||
|
||||
const batchBusy = Boolean(runningBatchId || logs.connected || hasActiveTasks);
|
||||
|
||||
const startTask = async (taskType: string, accountIds?: number[]) => {
|
||||
const startTask = async (
|
||||
taskType: string,
|
||||
accountIds?: number[],
|
||||
extraPayload: Record<string, unknown> = {},
|
||||
) => {
|
||||
const ids = accountIds || selectedIds;
|
||||
if (ids.length === 0) {
|
||||
message.warning('请先选择账号');
|
||||
return;
|
||||
}
|
||||
// 绑定确认/查询允许在其他账号仍轮询时继续,避免卡在多账号批次里
|
||||
const allowDuringBatch = taskType === 'confirm_bind' || taskType === 'query_game_name';
|
||||
const allowDuringBatch = [
|
||||
'confirm_bind',
|
||||
'confirm_esports_bind',
|
||||
'query_game_name',
|
||||
'query_esports_game_name',
|
||||
'prepare_esports_bind',
|
||||
'get_esports_bind_qr',
|
||||
].includes(taskType);
|
||||
if (batchBusy && !allowDuringBatch) {
|
||||
message.warning('当前已有批次在运行,请先停止或等待结束');
|
||||
return;
|
||||
@@ -341,7 +460,7 @@ export default function DouyuTasksPage() {
|
||||
message.warning('请先选择兑换商品');
|
||||
return;
|
||||
}
|
||||
const payload: Record<string, unknown> = {};
|
||||
const payload: Record<string, unknown> = { ...extraPayload };
|
||||
if (taskType === 'exchange_goods') payload.commodity_id = selectedGoodsId;
|
||||
if (taskType === 'create_gold_qr') payload.amount = goldAmount;
|
||||
if (taskType === 'donate_elite_gift') payload.gift_count = giftCount;
|
||||
@@ -460,12 +579,87 @@ export default function DouyuTasksPage() {
|
||||
void startTask(taskType, contextMenu.accountIds);
|
||||
};
|
||||
|
||||
// ---- 电竞手册绑定面板 ----
|
||||
const esportsRelatedTasks = esportsBindTask
|
||||
? tasks
|
||||
.filter((task) => (
|
||||
task.account_id === esportsBindTask.account_id
|
||||
&& task.id >= esportsBindTask.id
|
||||
&& ['prepare_esports_bind', 'get_esports_bind_qr', 'query_esports_game_name', 'confirm_esports_bind']
|
||||
.includes(task.task_type)
|
||||
))
|
||||
.sort((a, b) => b.id - a.id)
|
||||
: [];
|
||||
const esportsLatestStateTask = esportsRelatedTasks.find((task) => task.result) || esportsBindTask;
|
||||
const esportsLatestRoleTask = esportsRelatedTasks.find(
|
||||
(task) => task.task_type === 'query_esports_game_name',
|
||||
) || null;
|
||||
const esportsLatestQrTask = esportsRelatedTasks.find(
|
||||
(task) => Boolean(resultText(task.result, 'url')),
|
||||
) || null;
|
||||
const esportsConfirmRunning = esportsRelatedTasks.some(
|
||||
(task) => task.task_type === 'confirm_esports_bind' && ['planned', 'pending', 'running'].includes(task.status),
|
||||
);
|
||||
const esportsRoleQueryRunning = Boolean(
|
||||
esportsLatestRoleTask && ['planned', 'pending', 'running'].includes(esportsLatestRoleTask.status),
|
||||
);
|
||||
const esportsResult = esportsLatestStateTask?.result || esportsBindTask?.result || null;
|
||||
const esportsBound = resultFlag(esportsResult, 'esports_bound') || resultFlag(esportsResult, 'bind_confirmed');
|
||||
const esportsRoleName = resultText(esportsResult, 'role_name');
|
||||
const esportsAreaName = resultText(esportsResult, 'area_name');
|
||||
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 esportsQrUrl = resultText(esportsLatestQrTask?.result, 'url');
|
||||
const esportsAccountName = esportsBindTask
|
||||
? (
|
||||
esportsBindTask.account_nickname
|
||||
|| esportsBindTask.account_username
|
||||
|| esportsBindTask.account_uid
|
||||
|| `#${esportsBindTask.account_id}`
|
||||
)
|
||||
: '';
|
||||
const esportsStatusText = esportsBound
|
||||
? '电竞手册已绑定'
|
||||
: esportsConfirmRunning
|
||||
? '正在完成电竞手册绑定'
|
||||
: esportsRoleQueryRunning
|
||||
? '正在查询最新角色'
|
||||
: esportsLatestStateTask?.message || '加载电竞手册绑定状态';
|
||||
const esportsCanConfirm = Boolean(
|
||||
esportsBindTask
|
||||
&& !esportsBound
|
||||
&& esportsRoleName
|
||||
&& !esportsConfirmRunning,
|
||||
);
|
||||
|
||||
const queryEsportsRole = () => {
|
||||
if (!esportsBindTask || esportsRoleQueryRunning) return;
|
||||
void startTask('query_esports_game_name', [esportsBindTask.account_id]);
|
||||
};
|
||||
|
||||
const switchEsportsRole = () => {
|
||||
if (!esportsBindTask || !esportsCanChangeRole) return;
|
||||
void startTask('get_esports_bind_qr', [esportsBindTask.account_id]);
|
||||
};
|
||||
|
||||
const confirmEsportsBindFromDialog = () => {
|
||||
if (!esportsBindTask || !esportsCanConfirm) {
|
||||
message.warning('请先确认当前角色');
|
||||
return;
|
||||
}
|
||||
void startTask('confirm_esports_bind', [esportsBindTask.account_id]);
|
||||
};
|
||||
|
||||
// ---- QR panel derived state ----
|
||||
const qrResult = qrTask?.result || null;
|
||||
const qrUrl = resultText(qrResult, 'url');
|
||||
const qrBindPhase = resultText(qrResult, 'bind_phase');
|
||||
const qrAutoPolling = qrTask?.status === 'running' && qrResult?.bind_polling === true;
|
||||
const qrQueryTask = qrTask ? latestQueryGameTaskByAccount.get(qrTask.account_id) || null : null;
|
||||
const qrQueryTask = qrTask
|
||||
? latestQueryGameTaskByAccount.get(qrTask.account_id) || null
|
||||
: null;
|
||||
const qrQueryResult = qrTask && qrQueryTask && qrQueryTask.id > qrTask.id ? qrQueryTask.result : null;
|
||||
const qrQueryRunning = Boolean(qrQueryTask && qrTask && qrQueryTask.id > qrTask.id && qrQueryTask.status === 'running');
|
||||
const qrQueryRoleName = resultText(qrQueryResult, 'role_name');
|
||||
@@ -485,14 +679,29 @@ export default function DouyuTasksPage() {
|
||||
const qrRoleName = resultText(qrRoleSourceResult, 'role_name');
|
||||
const qrAreaName = resultText(qrRoleSourceResult, 'area_name');
|
||||
const qrPlatName = resultText(qrRoleSourceResult, 'plat_name');
|
||||
const qrCurrentRoleName = resultText(qrResult, 'current_role_name')
|
||||
|| (resultFlag(qrResult, 'is_bound_act') ? resultText(qrResult, 'role_name') : '')
|
||||
const qrCurrentRoleName = qrQueryRoleName
|
||||
|| resultText(qrResult, 'current_role_name')
|
||||
|| (
|
||||
resultFlag(qrResult, 'is_bound_act')
|
||||
? resultText(qrResult, 'role_name')
|
||||
: ''
|
||||
)
|
||||
|| (qrQueryIsBoundAct ? qrQueryRoleName : '');
|
||||
const qrCurrentAreaName = resultText(qrResult, 'current_area_name')
|
||||
|| (resultFlag(qrResult, 'is_bound_act') ? resultText(qrResult, 'area_name') : '')
|
||||
const qrCurrentAreaName = resultText(qrQueryResult, 'area_name')
|
||||
|| resultText(qrResult, 'current_area_name')
|
||||
|| (
|
||||
resultFlag(qrResult, 'is_bound_act')
|
||||
? resultText(qrResult, 'area_name')
|
||||
: ''
|
||||
)
|
||||
|| (qrQueryIsBoundAct ? resultText(qrQueryResult, 'area_name') : '');
|
||||
const qrCurrentPlatName = resultText(qrResult, 'current_plat_name')
|
||||
|| (resultFlag(qrResult, 'is_bound_act') ? resultText(qrResult, 'plat_name') : '')
|
||||
const qrCurrentPlatName = resultText(qrQueryResult, 'plat_name')
|
||||
|| resultText(qrResult, 'current_plat_name')
|
||||
|| (
|
||||
resultFlag(qrResult, 'is_bound_act')
|
||||
? resultText(qrResult, 'plat_name')
|
||||
: ''
|
||||
)
|
||||
|| (qrQueryIsBoundAct ? resultText(qrQueryResult, 'plat_name') : '');
|
||||
const qrRoleLine = qrBindReady
|
||||
? [qrPlatName, qrAreaName, qrRoleName].filter(Boolean).join(' - ')
|
||||
@@ -542,13 +751,18 @@ export default function DouyuTasksPage() {
|
||||
{
|
||||
title: '游戏名', dataIndex: 'game_name', width: 130,
|
||||
render: (_, record) => {
|
||||
const queryResult = latestQueryGameTaskByAccount.get(record.id)?.result || null;
|
||||
const roleName = resultText(queryResult, 'role_name') || record.game_name;
|
||||
const queryResult = (
|
||||
isEsportsHandbook
|
||||
? latestEsportsStateTaskByAccount.get(record.id)
|
||||
: latestQueryGameTaskByAccount.get(record.id)
|
||||
)?.result || null;
|
||||
const roleName = resultText(queryResult, 'role_name')
|
||||
|| (isEsportsHandbook ? record.esports_game_name : record.game_name);
|
||||
const channel = (
|
||||
[resultText(queryResult, 'area_name'), resultText(queryResult, 'plat_name')]
|
||||
.filter(Boolean)
|
||||
.join(' / ')
|
||||
|| record.game_channel
|
||||
|| (isEsportsHandbook ? record.esports_game_channel : record.game_channel)
|
||||
);
|
||||
return roleName ? (
|
||||
<Space direction="vertical" size={0}>
|
||||
@@ -559,18 +773,22 @@ export default function DouyuTasksPage() {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '积分', dataIndex: 'points', width: 70,
|
||||
title: isEsportsHandbook ? '电竞积分' : '积分',
|
||||
dataIndex: isEsportsHandbook ? 'esports_points' : 'points',
|
||||
width: 80,
|
||||
render: (v) => v ?? <Text type="secondary">-</Text>,
|
||||
},
|
||||
{
|
||||
...(isEsportsHandbook ? [] : [{
|
||||
title: '鱼翅', dataIndex: 'gold_balance', width: 70,
|
||||
render: (v) => v ?? <Text type="secondary">-</Text>,
|
||||
},
|
||||
render: (v: number | null) => v ?? <Text type="secondary">-</Text>,
|
||||
}]),
|
||||
{
|
||||
title: '换绑时间', dataIndex: 'change_role_wait_time', width: 130,
|
||||
render: (_, record) => {
|
||||
const fromTask = latestChangeWaitByAccount.get(record.id);
|
||||
const wait = fromTask?.wait ?? record.change_role_wait_time;
|
||||
const wait = fromTask?.wait ?? (
|
||||
isEsportsHandbook ? record.esports_change_role_wait_time : record.change_role_wait_time
|
||||
);
|
||||
const text = fromTask?.text || formatWaitSeconds(wait);
|
||||
if (wait == null && !text) {
|
||||
return <Text type="secondary">未查</Text>;
|
||||
@@ -591,16 +809,21 @@ export default function DouyuTasksPage() {
|
||||
return <Text type="secondary">{text || '-'}</Text>;
|
||||
},
|
||||
sorter: (a, b) => {
|
||||
const aw = latestChangeWaitByAccount.get(a.id)?.wait ?? a.change_role_wait_time ?? -1;
|
||||
const bw = latestChangeWaitByAccount.get(b.id)?.wait ?? b.change_role_wait_time ?? -1;
|
||||
const aw = 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
|
||||
?? (isEsportsHandbook ? b.esports_change_role_wait_time : b.change_role_wait_time) ?? -1;
|
||||
return aw - bw;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态', dataIndex: 'bind_status', width: 100,
|
||||
render: (v) => (v
|
||||
? <Tag>{BIND_STATUS_LABELS[v] || v}</Tag>
|
||||
: <Text type="secondary">-</Text>),
|
||||
render: (_, record) => {
|
||||
const value = isEsportsHandbook ? record.esports_bind_status : record.bind_status;
|
||||
return value
|
||||
? <Tag>{BIND_STATUS_LABELS[value] || value}</Tag>
|
||||
: <Text type="secondary">-</Text>;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -618,6 +841,22 @@ export default function DouyuTasksPage() {
|
||||
{
|
||||
title: '结果', dataIndex: 'message', ellipsis: true,
|
||||
render: (_, r) => {
|
||||
if (['prepare_esports_bind', 'get_esports_bind_qr'].includes(r.task_type)) {
|
||||
const roleName = resultText(r.result, 'role_name');
|
||||
const esportsBound = resultFlag(r.result, 'esports_bound') || resultFlag(r.result, 'bind_confirmed');
|
||||
const bindUrl = resultText(r.result, 'url');
|
||||
return (
|
||||
<Space wrap size={4}>
|
||||
<Text>{r.message || '-'}</Text>
|
||||
{roleName ? <Tag color={esportsBound ? 'green' : 'blue'}>{roleName}</Tag> : null}
|
||||
{bindUrl || r.result?.esports_bind_dialog === true ? (
|
||||
<Button size="small" icon={<QrcodeOutlined />} onClick={() => openEsportsBindTask(r)}>
|
||||
查看
|
||||
</Button>
|
||||
) : null}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
if (r.task_type === 'get_bind_qr') {
|
||||
const roleName = resultText(r.result, 'role_name');
|
||||
const phase = resultText(r.result, 'bind_phase');
|
||||
@@ -639,7 +878,7 @@ export default function DouyuTasksPage() {
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
if (r.task_type === 'query_game_name') {
|
||||
if (['query_game_name', 'query_esports_game_name'].includes(r.task_type)) {
|
||||
const roleName = resultText(r.result, 'role_name');
|
||||
const areaName = resultText(r.result, 'area_name');
|
||||
const platName = resultText(r.result, 'plat_name');
|
||||
@@ -663,6 +902,16 @@ export default function DouyuTasksPage() {
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
if (r.task_type === 'confirm_esports_bind') {
|
||||
const roleName = resultText(r.result, 'role_name');
|
||||
const confirmed = resultFlag(r.result, 'bind_confirmed');
|
||||
return (
|
||||
<Space wrap size={4}>
|
||||
<Text>{r.message || '-'}</Text>
|
||||
{roleName ? <Tag color={confirmed ? 'green' : 'orange'}>{roleName}</Tag> : null}
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
const url = taskPayUrl(r);
|
||||
return (
|
||||
<Space wrap size={4}>
|
||||
@@ -696,7 +945,8 @@ export default function DouyuTasksPage() {
|
||||
(a.nickname || '').toLowerCase().includes(kw)
|
||||
|| (a.username || '').toLowerCase().includes(kw)
|
||||
|| (a.uid || '').toLowerCase().includes(kw)
|
||||
|| (a.game_name || '').toLowerCase().includes(kw),
|
||||
|| (a.game_name || '').toLowerCase().includes(kw)
|
||||
|| (a.esports_game_name || '').toLowerCase().includes(kw),
|
||||
);
|
||||
}, [accounts, accountSearch]);
|
||||
|
||||
@@ -710,7 +960,7 @@ export default function DouyuTasksPage() {
|
||||
? (payTask.account_nickname || payTask.account_username || payTask.account_uid || `#${payTask.account_id}`)
|
||||
: '';
|
||||
const selectedText = selectedIds.length > 0 ? `已选 ${selectedIds.length} 个账号` : '请先勾选账号';
|
||||
const actionByKey = useMemo(() => new Map(QUICK_ACTIONS.map((item) => [item.key, item])), []);
|
||||
const actionByKey = useMemo(() => new Map(quickActions.map((item) => [item.key, item])), [quickActions]);
|
||||
const actionDisabled = (taskType: string) => {
|
||||
if (batchBusy || selectedIds.length === 0) return true;
|
||||
if (taskType === 'exchange_goods') return !selectedGoodsId;
|
||||
@@ -757,8 +1007,8 @@ export default function DouyuTasksPage() {
|
||||
{/* Header */}
|
||||
<Space style={{ justifyContent: 'space-between', width: '100%', marginBottom: 8, flexShrink: 0 }}>
|
||||
<div>
|
||||
<h2 style={{ margin: 0 }}>斗鱼任务操作台</h2>
|
||||
<Text type="secondary">绑定、宝典、鱼翅、积分、兑换和余额任务</Text>
|
||||
<h2 style={{ margin: 0 }}>{handbookTitle}</h2>
|
||||
<Text type="secondary">{handbookDescription}</Text>
|
||||
</div>
|
||||
<Space>
|
||||
<Button icon={<ReloadOutlined />} onClick={loadData} loading={loading}>刷新</Button>
|
||||
@@ -822,15 +1072,15 @@ export default function DouyuTasksPage() {
|
||||
size="small" title="任务记录"
|
||||
extra={
|
||||
<Space size={12}>
|
||||
<Tag color="success">成功 {tasks.filter((t) => t.status === 'success').length}</Tag>
|
||||
<Tag color="error">失败 {tasks.filter((t) => ['failed', 'error'].includes(t.status)).length}</Tag>
|
||||
<Tag color="success">成功 {visibleTasks.filter((t) => t.status === 'success').length}</Tag>
|
||||
<Tag color="error">失败 {visibleTasks.filter((t) => ['failed', 'error'].includes(t.status)).length}</Tag>
|
||||
</Space>
|
||||
}
|
||||
style={{ flex: '0 0 300px', minHeight: 260, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
||||
styles={{ body: { flex: 1, minHeight: 0, padding: 8, overflow: 'hidden', display: 'flex', flexDirection: 'column' } }}
|
||||
>
|
||||
<Table
|
||||
rowKey="id" size="small" columns={taskColumns} dataSource={tasks}
|
||||
rowKey="id" size="small" columns={taskColumns} dataSource={visibleTasks}
|
||||
style={{ flex: 1, minHeight: 0 }}
|
||||
loading={loading} pagination={{ pageSize: 4, showSizeChanger: false, size: 'small' }}
|
||||
scroll={{ x: 700, y: 150 }}
|
||||
@@ -842,7 +1092,7 @@ export default function DouyuTasksPage() {
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, overflow: 'auto' }}>
|
||||
<Card
|
||||
size="small"
|
||||
title="操作"
|
||||
title={isEsportsHandbook ? '电竞手册操作' : '精英宝典操作'}
|
||||
extra={<Tag color={selectedIds.length ? 'blue' : 'default'}>{selectedText}</Tag>}
|
||||
styles={{ body: { padding: 8 } }}
|
||||
>
|
||||
@@ -859,92 +1109,116 @@ export default function DouyuTasksPage() {
|
||||
/>
|
||||
</Space.Compact>
|
||||
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<QrcodeOutlined />
|
||||
<span>绑定与角色</span>
|
||||
</div>
|
||||
<div style={actionGridStyle}>
|
||||
{BIND_ACTION_KEYS.map((key) => renderActionButton(key))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<SearchOutlined />
|
||||
<span>查询</span>
|
||||
</div>
|
||||
<div style={actionGridStyle}>
|
||||
{QUERY_ACTION_KEYS.map((key) => renderActionButton(key))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<ShoppingOutlined />
|
||||
<span>兑换</span>
|
||||
</div>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
||||
<Select
|
||||
size="small"
|
||||
value={selectedGoodsId || undefined}
|
||||
onChange={setSelectedGoodsId}
|
||||
options={goods.map((item) => ({ value: item.commodity_id, label: goodsLabel(item) }))}
|
||||
placeholder="选择兑换商品"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<div style={actionGridStyle}>
|
||||
{renderActionButton('refresh_goods')}
|
||||
{renderActionButton('exchange_goods', 'primary')}
|
||||
{isEsportsHandbook ? (
|
||||
<>
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<QrcodeOutlined />
|
||||
<span>角色绑定</span>
|
||||
</div>
|
||||
<div style={actionGridStyle}>
|
||||
{renderActionButton('prepare_esports_bind', 'primary')}
|
||||
{renderActionButton('query_esports_game_name')}
|
||||
</div>
|
||||
</div>
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<CreditCardOutlined />
|
||||
<span>开通手册</span>
|
||||
</div>
|
||||
{renderActionButton('create_esports_qr', 'primary')}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<QrcodeOutlined />
|
||||
<span>绑定与角色</span>
|
||||
</div>
|
||||
<div style={actionGridStyle}>
|
||||
{ELITE_BIND_ACTION_KEYS.map((key) => renderActionButton(key))}
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<CreditCardOutlined />
|
||||
<span>充值与送礼</span>
|
||||
</div>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
||||
{renderActionButton('create_elite_qr')}
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={1}
|
||||
value={goldAmount}
|
||||
onChange={(v) => setGoldAmount(v || 1)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CreditCardOutlined />}
|
||||
onClick={() => startTask('create_gold_qr')}
|
||||
disabled={actionDisabled('create_gold_qr')}
|
||||
>
|
||||
充值鱼翅
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={1}
|
||||
value={giftCount}
|
||||
onChange={(v) => setGiftCount(v || 1)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<GiftOutlined />}
|
||||
onClick={() => startTask('donate_elite_gift')}
|
||||
disabled={actionDisabled('donate_elite_gift')}
|
||||
>
|
||||
赠送精英令
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
</div>
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<SearchOutlined />
|
||||
<span>查询</span>
|
||||
</div>
|
||||
<div style={actionGridStyle}>
|
||||
{ELITE_QUERY_ACTION_KEYS.map((key) => renderActionButton(key))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<ShoppingOutlined />
|
||||
<span>兑换</span>
|
||||
</div>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
||||
<Select
|
||||
size="small"
|
||||
value={selectedGoodsId || undefined}
|
||||
onChange={setSelectedGoodsId}
|
||||
options={goods.map((item) => ({ value: item.commodity_id, label: goodsLabel(item) }))}
|
||||
placeholder="选择兑换商品"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<div style={actionGridStyle}>
|
||||
{renderActionButton('refresh_goods')}
|
||||
{renderActionButton('exchange_goods', 'primary')}
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<div style={sectionStyle}>
|
||||
<div style={sectionTitleStyle}>
|
||||
<CreditCardOutlined />
|
||||
<span>开通、充值与送礼</span>
|
||||
</div>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={6}>
|
||||
{renderActionButton('create_elite_qr', 'primary')}
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={1}
|
||||
value={goldAmount}
|
||||
onChange={(v) => setGoldAmount(v || 1)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CreditCardOutlined />}
|
||||
onClick={() => startTask('create_gold_qr')}
|
||||
disabled={actionDisabled('create_gold_qr')}
|
||||
>
|
||||
充值鱼翅
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
<Space.Compact style={{ width: '100%' }}>
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={1}
|
||||
value={giftCount}
|
||||
onChange={(v) => setGiftCount(v || 1)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<GiftOutlined />}
|
||||
onClick={() => startTask('donate_elite_gift')}
|
||||
disabled={actionDisabled('donate_elite_gift')}
|
||||
>
|
||||
赠送精英令
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
勾选账号后可批量执行;右键账号行可快速执行单账号动作。
|
||||
</Text>
|
||||
@@ -965,7 +1239,7 @@ export default function DouyuTasksPage() {
|
||||
style={{ width: 200, boxShadow: '0 4px 12px rgba(0,0,0,0.12)' }}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
{QUICK_ACTIONS.map((item) => (
|
||||
{quickActions.map((item) => (
|
||||
<Button
|
||||
key={item.key} block size="small" type="text"
|
||||
icon={item.icon}
|
||||
@@ -1011,8 +1285,16 @@ export default function DouyuTasksPage() {
|
||||
columns={[
|
||||
{ title: '昵称', dataIndex: 'nickname', render: (_, r) => r.nickname || r.username },
|
||||
{ title: 'UID', dataIndex: 'uid' },
|
||||
{ title: '游戏名', dataIndex: 'game_name', render: (v) => v || '-' },
|
||||
{ title: '积分', dataIndex: 'points', render: (v) => v ?? '-' },
|
||||
{
|
||||
title: '游戏名',
|
||||
dataIndex: isEsportsHandbook ? 'esports_game_name' : 'game_name',
|
||||
render: (v) => v || '-',
|
||||
},
|
||||
{
|
||||
title: isEsportsHandbook ? '电竞积分' : '积分',
|
||||
dataIndex: isEsportsHandbook ? 'esports_points' : 'points',
|
||||
render: (v) => v ?? '-',
|
||||
},
|
||||
]}
|
||||
pagination={{ pageSize: 10 }}
|
||||
locale={{ emptyText: '没有可导入的账号(操作台已包含所有账号)' }}
|
||||
@@ -1021,7 +1303,7 @@ export default function DouyuTasksPage() {
|
||||
|
||||
{/* Config modal */}
|
||||
<Modal
|
||||
title="斗鱼活动配置"
|
||||
title={isEsportsHandbook ? '电竞手册配置' : '精英宝典配置'}
|
||||
open={configOpen}
|
||||
onCancel={() => setConfigOpen(false)}
|
||||
onOk={() => configFormValues && saveConfig(configFormValues)}
|
||||
@@ -1030,7 +1312,11 @@ export default function DouyuTasksPage() {
|
||||
>
|
||||
{configFormValues && (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={8}>
|
||||
{[
|
||||
{(isEsportsHandbook ? [
|
||||
{ label: '电竞手册 manualID', key: 'esports_manual_id' },
|
||||
{ label: '电竞手册活动', key: 'esports_act_alias' },
|
||||
{ label: '房间 ID', key: 'room_id' },
|
||||
] : [
|
||||
{ label: 'manualID', key: 'manual_id' },
|
||||
{ label: 'RID', key: 'rid' },
|
||||
{ label: '绑定二维码活动', key: 'bind_act_alias' },
|
||||
@@ -1039,7 +1325,7 @@ export default function DouyuTasksPage() {
|
||||
{ label: '房间 ID', key: 'room_id' },
|
||||
{ label: '精英令礼物 ID', key: 'gift_id' },
|
||||
{ label: '皮肤 ID', key: 'skin_id' },
|
||||
].map(({ label, key }) => (
|
||||
]).map(({ label, key }) => (
|
||||
<Space key={key} style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
<Text>{label}</Text>
|
||||
<Input
|
||||
@@ -1049,10 +1335,12 @@ export default function DouyuTasksPage() {
|
||||
/>
|
||||
</Space>
|
||||
))}
|
||||
{[
|
||||
{(isEsportsHandbook ? [
|
||||
{ label: '电竞手册金额(分)', key: 'esports_amount' },
|
||||
] : [
|
||||
{ label: '宝典金额(分)', key: 'elite_amount' },
|
||||
{ label: '鱼翅支付方式', key: 'gold_pay_type' },
|
||||
].map(({ label, key }) => (
|
||||
]).map(({ label, key }) => (
|
||||
<Space key={key} style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||
<Text>{label}</Text>
|
||||
<InputNumber
|
||||
@@ -1066,7 +1354,87 @@ export default function DouyuTasksPage() {
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* Bind QR Modal — driven by qrTask */}
|
||||
{/* 电竞手册绑定弹窗:活动绑定与角色选择是独立流程。 */}
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
<QrcodeOutlined />
|
||||
<span>绑定电竞手册角色</span>
|
||||
</Space>
|
||||
}
|
||||
open={!!esportsBindTask}
|
||||
onCancel={() => {
|
||||
setEsportsBindTask(null);
|
||||
}}
|
||||
footer={null}
|
||||
centered
|
||||
width={440}
|
||||
>
|
||||
{esportsBindTask && (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={14}>
|
||||
{esportsAccountName ? (
|
||||
<Text type="secondary">{esportsAccountName}</Text>
|
||||
) : null}
|
||||
<Tag color={esportsBound ? 'green' : esportsConfirmRunning ? 'processing' : 'blue'}>
|
||||
{esportsStatusText}
|
||||
</Tag>
|
||||
|
||||
{esportsRoleName ? (
|
||||
<div>
|
||||
<Text type="secondary" style={{ display: 'block', marginBottom: 4 }}>当前游戏角色</Text>
|
||||
<Text strong style={{ fontSize: 16 }}>{esportsRoleName}</Text>
|
||||
{esportsRoleLine ? (
|
||||
<Text type="secondary" style={{ display: 'block', marginTop: 4 }}>{esportsRoleLine}</Text>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<Text type="secondary">当前未检测到游戏角色,请先切换角色。</Text>
|
||||
)}
|
||||
|
||||
{esportsChangeWait != null && esportsChangeWait > 0 && esportsRoleName ? (
|
||||
<Text type="secondary">换绑冷却剩余:{formatWaitSeconds(esportsChangeWait)}</Text>
|
||||
) : null}
|
||||
|
||||
{esportsQrUrl ? (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<QRCode value={esportsQrUrl} size={220} bordered={false} />
|
||||
<Text type="secondary" style={{ display: 'block', marginTop: 8 }}>
|
||||
请在腾讯页面选择角色,完成后查询最新角色。
|
||||
</Text>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Space wrap>
|
||||
<Button
|
||||
icon={<SearchOutlined />}
|
||||
onClick={queryEsportsRole}
|
||||
loading={esportsRoleQueryRunning}
|
||||
disabled={esportsConfirmRunning}
|
||||
>
|
||||
查询最新角色
|
||||
</Button>
|
||||
<Button
|
||||
icon={<QrcodeOutlined />}
|
||||
onClick={switchEsportsRole}
|
||||
disabled={!esportsCanChangeRole || esportsConfirmRunning}
|
||||
>
|
||||
切换角色
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<CheckCircleOutlined />}
|
||||
onClick={confirmEsportsBindFromDialog}
|
||||
disabled={!esportsCanConfirm}
|
||||
loading={esportsConfirmRunning}
|
||||
>
|
||||
完成绑定
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* 精英宝典绑定二维码弹窗。 */}
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
|
||||
Reference in New Issue
Block a user