feat(huya): 开通支付码多选账号时每个账号各出一个码并排展示
多选账号点「生成开通支付码」只出一个码:create_planned_tasks 把 create_recharge_order 也当成单笔任务(accounts[:1]),前端本来就支持多码并排,是被后端截断的。 - 后端:create_planned_tasks 不再截断 create_recharge_order(refresh_* 仍只建一个,商品快照是全局的),N 个账号 = N 个支付码任务,各自独立下单/监听支付 - 前端:支付码任务的并发取 min(10, max(并发, 账号数)),否则超出并发的码要等前面的码付完/超时才生成 - 前端:支付弹窗改成网格并排展示多个 150px 码,标题显示待扫码数;新增「生成失败(没有二维码)」点名区和「还有 N 个账号在排队生成」提示 - 前端:去掉每个码回包就自动弹支付弹窗的行为(多选时会反复把弹窗顶到最前);不做付完自动跳转 - 测试:多账号生成支付码 → 每账号一个任务,refresh_recharge_goods 仍单任务
This commit is contained in:
@@ -232,7 +232,11 @@ export default function HuyaElitePage() {
|
||||
const selected = rechargeGoods.find((item) => item.spu_id === selectedRechargeSpu);
|
||||
return { spu_id: selectedRechargeSpu, sku_id: Number(selected?.sku_id || 0), product_name: selected?.name || '精英宝典' };
|
||||
})() : {};
|
||||
const created = await huyaApi.createTasks({ account_ids: accountIds, task_type: taskType, handbook_scope: SCOPE, concurrency, payload });
|
||||
// 支付码要「同时弹出多个二维码」:并发必须 >= 账号数,否则排在后面的码要等前面的码付完/超时才开始生成
|
||||
const batchConcurrency = taskType === 'create_recharge_order'
|
||||
? Math.min(10, Math.max(concurrency, accountIds.length))
|
||||
: concurrency;
|
||||
const created = await huyaApi.createTasks({ account_ids: accountIds, task_type: taskType, handbook_scope: SCOPE, concurrency: batchConcurrency, payload });
|
||||
setActiveBatches((prev) => [...new Set([...prev, created.batch_id])]);
|
||||
if (taskType === 'create_recharge_order') {
|
||||
// 多选账号时每个账号一个支付码:收进同一列表,弹窗里一起展示
|
||||
@@ -245,7 +249,8 @@ export default function HuyaElitePage() {
|
||||
const next = raw as unknown as HuyaTaskItem;
|
||||
setTasks((prev) => [next, ...prev.filter((item) => item.id !== next.id)].slice(0, 300));
|
||||
const result = next.result || {};
|
||||
if (next.task_type === 'create_recharge_order' && result.pay_url) setPayOpen(true);
|
||||
// 支付弹窗只在「生成开通支付码」和点账号行时打开:多选时每个码都会回包,
|
||||
// 这里再自动弹会把用户刚关掉的弹窗反复顶出来(码会在弹窗里自动补齐)。
|
||||
if (next.task_type === 'get_bind_qr' && (result.mini_qrcode_image || result.bind_redirect_url)) setQrTask(next);
|
||||
},
|
||||
onResult: () => { setActiveBatches((prev) => prev.filter((id) => id !== created.batch_id)); void load(); },
|
||||
@@ -322,6 +327,15 @@ export default function HuyaElitePage() {
|
||||
}, [tasks, payBatchIds]);
|
||||
const payPendingTasks = payTasks.filter(needsScanPay);
|
||||
const payDoneTasks = payTasks.filter((task) => !needsScanPay(task));
|
||||
// 多选账号时一次生成多个码:个别账号失败(限购、CK 失效…)要单独点名,否则少一个码看不出原因
|
||||
const payFailedTasks = useMemo(() => tasks.filter((task) => task.task_type === 'create_recharge_order'
|
||||
&& payBatchIds.includes(task.batch_id)
|
||||
&& ['failed', 'error', 'stopped'].includes(task.status)), [tasks, payBatchIds]);
|
||||
// 还在排队/生成中的码(并发不够时,后面的码要等前面的码付完或超时才轮到)
|
||||
const payQueuedCount = useMemo(() => tasks.filter((task) => task.task_type === 'create_recharge_order'
|
||||
&& payBatchIds.includes(task.batch_id)
|
||||
&& ['planned', 'pending', 'running'].includes(task.status)
|
||||
&& !resultText(task, 'pay_url')).length, [tasks, payBatchIds]);
|
||||
|
||||
const openPayTasks = (batchId?: string) => {
|
||||
if (batchId) setPayBatchIds((prev) => (prev.includes(batchId) ? prev : [...prev, batchId]));
|
||||
@@ -379,35 +393,37 @@ export default function HuyaElitePage() {
|
||||
<Modal open={qrTask !== null} title="绑定二维码" footer={null} onCancel={() => setQrTask(null)} centered>{qrTask && <div style={{ textAlign: 'center' }}>{resultText(qrTask, 'mini_qrcode_image') ? <img src={`data:image/png;base64,${resultText(qrTask, 'mini_qrcode_image')}`} alt="绑定二维码" style={{ width: 240, height: 240 }} /> : <QRCode value={resultText(qrTask, 'bind_redirect_url') || 'https://zt.huya.com/b02faae1/pc/index.html'} size={240} color="#000" bgColor="#fff" />}<p><Tag color={TASK_COLORS[qrTask.status] || 'default'}>{qrTask.message || qrTask.status}</Tag></p></div>}</Modal>
|
||||
<Modal
|
||||
open={payOpen}
|
||||
title={payTasks.length ? `开通支付码(${payDoneTasks.length}/${payTasks.length} 已完成)` : '开通支付码'}
|
||||
width={880}
|
||||
title={payTasks.length ? `开通支付码(${payPendingTasks.length} 个待扫码 · 已完成 ${payDoneTasks.length}/${payTasks.length})` : '开通支付码'}
|
||||
width={920}
|
||||
centered
|
||||
onCancel={() => setPayOpen(false)}
|
||||
footer={<Space><Text type="secondary">支付后会自动确认开通状态;已开通的码自动收起</Text><Button onClick={() => setPayOpen(false)}>关闭</Button><Button onClick={() => { setPayBatchIds([]); setPayOpen(false); }}>清空列表</Button></Space>}
|
||||
footer={<Space><Text type="secondary">多选账号会同时生成多个码并排展示,各自独立等待支付;支付后自动确认开通状态</Text><Button onClick={() => setPayOpen(false)}>关闭</Button><Button onClick={() => { setPayBatchIds([]); setPayOpen(false); }}>清空列表</Button></Space>}
|
||||
>
|
||||
{payTasks.length === 0 ? <div style={{ padding: 24, textAlign: 'center' }}><Text type="secondary">正在为选中的账号生成支付码…</Text></div> : <div style={{ maxHeight: '62vh', overflowY: 'auto' }}>
|
||||
{payPendingTasks.length > 0 && <Space direction="vertical" size={10} style={{ width: '100%' }}>
|
||||
{payQueuedCount > 0 && <div style={{ marginBottom: 10, textAlign: 'left' }}><Text type="secondary" style={{ fontSize: 12 }}>还有 {payQueuedCount} 个账号在排队生成支付码(并发不够时会等已出的码付完/超时,可调大右侧「并发」)</Text></div>}
|
||||
{payPendingTasks.length > 0 && <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(230px, 1fr))', gap: 12 }}>
|
||||
{payPendingTasks.map((task) => {
|
||||
const qrValue = payQrValue(task);
|
||||
return (
|
||||
<div key={task.id} style={{ display: 'flex', gap: 16, alignItems: 'center', border: '1px solid rgba(128,128,128,.22)', borderRadius: 6, padding: 10, textAlign: 'left' }}>
|
||||
<div style={{ background: '#fff', padding: 8, borderRadius: 6, lineHeight: 0 }}>{resultText(task, 'mini_qrcode_image') ? <img src={`data:image/png;base64,${resultText(task, 'mini_qrcode_image')}`} alt="支付码" style={{ width: 180, height: 180 }} /> : <QRCode value={qrValue} size={180} type="svg" errorLevel="L" bordered={false} color="#000" bgColor="#fff" />}</div>
|
||||
<Space direction="vertical" size={2} style={{ flex: 1, minWidth: 0 }}>
|
||||
<Text strong ellipsis>{payTaskLabel(task)}</Text>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>UID {task.account_uid || '-'}</Text>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>{[resultText(task, 'product_name') || '精英宝典', resultText(task, 'amount_text')].filter(Boolean).join(' ')}</Text>
|
||||
<Tag color={TASK_COLORS[task.status] || 'default'}>{resultText(task, 'payment_status_label') || TASK_STATUS_LABELS[task.status] || task.status}</Tag>
|
||||
<Text type="secondary" ellipsis title={task.message}>{task.message || '-'}</Text>
|
||||
{isLoopbackUrl(qrValue) && <Text type="warning" style={{ fontSize: 12 }}>二维码是本机地址(localhost),手机扫不开;请用公网域名打开本页后重新生成</Text>}
|
||||
<Space size={6}>
|
||||
<Button size="small" icon={<ZoomInOutlined />} onClick={() => setZoomPayTask(task)}>放大扫码</Button>
|
||||
<Button size="small" icon={<CopyOutlined />} onClick={() => void copyPayLink(qrValue, '支付链接')}>复制链接</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
<div key={task.id} style={{ border: '1px solid rgba(128,128,128,.22)', borderRadius: 6, padding: 10, textAlign: 'center' }}>
|
||||
<div style={{ background: '#fff', padding: 6, borderRadius: 6, lineHeight: 0, display: 'inline-block' }}>{resultText(task, 'mini_qrcode_image') ? <img src={`data:image/png;base64,${resultText(task, 'mini_qrcode_image')}`} alt="支付码" style={{ width: 150, height: 150, objectFit: 'contain' }} /> : <QRCode value={qrValue} size={150} type="svg" errorLevel="L" bordered={false} color="#000" bgColor="#fff" />}</div>
|
||||
<div><Text strong ellipsis>{payTaskLabel(task)}</Text></div>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>UID {task.account_uid || '-'}</Text>
|
||||
<div><Text type="secondary" style={{ fontSize: 12 }}>{[resultText(task, 'product_name') || '精英宝典', resultText(task, 'amount_text')].filter(Boolean).join(' ')}</Text></div>
|
||||
<div style={{ marginTop: 4 }}><Tag color={TASK_COLORS[task.status] || 'default'}>{resultText(task, 'payment_status_label') || TASK_STATUS_LABELS[task.status] || task.status}</Tag></div>
|
||||
<Text type="secondary" style={{ fontSize: 12 }} ellipsis title={task.message}>{task.message || '-'}</Text>
|
||||
{isLoopbackUrl(qrValue) && <div><Text type="warning" style={{ fontSize: 12 }}>二维码是本机地址(localhost),手机扫不开</Text></div>}
|
||||
<div style={{ marginTop: 6 }}><Space size={6}><Button size="small" icon={<ZoomInOutlined />} onClick={() => setZoomPayTask(task)}>放大扫码</Button><Button size="small" icon={<CopyOutlined />} onClick={() => void copyPayLink(qrValue, '支付链接')}>复制链接</Button></Space></div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Space>}
|
||||
</div>}
|
||||
{payFailedTasks.length > 0 && <div style={{ marginTop: payPendingTasks.length ? 12 : 0, textAlign: 'left' }}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>生成失败(没有二维码)</Text>
|
||||
<div style={{ marginTop: 6, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
{payFailedTasks.map((task) => <Tag key={task.id} color="error" title={task.message}>{payTaskLabel(task)} · {task.message || '生成失败'}</Tag>)}
|
||||
</div>
|
||||
</div>}
|
||||
{payDoneTasks.length > 0 && <div style={{ marginTop: payPendingTasks.length ? 12 : 0, textAlign: 'left' }}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>已完成(二维码已收起)</Text>
|
||||
<div style={{ marginTop: 6, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
||||
|
||||
Reference in New Issue
Block a user