完善虎牙已有绑定角色确认与重新扫码换绑流程

This commit is contained in:
yml2213
2026-11-10 13:20:51 +08:00
parent a9937ef8c6
commit 08c3834a1c
11 changed files with 287 additions and 41 deletions
@@ -54,7 +54,9 @@ export default function CustomerFulfillmentPage() {
const [loginInfo, setLoginInfo] = useState<CustomerLoginInfo | null>(null)
const [bindingInfo, setBindingInfo] = useState<CustomerBindingInfo | null>(null)
const [loginQrImage, setLoginQrImage] = useState('')
const [bindingActionLoading, setBindingActionLoading] = useState(false)
const startingRef = useRef(false)
const hydratingBindingRef = useRef(false)
const selectedTask = useMemo(
() => tasks.find((task) => task.id === selectedTaskId) || tasks[0] || null,
@@ -182,6 +184,34 @@ export default function CustomerFulfillmentPage() {
return () => window.clearInterval(timer)
}, [message, orderNo, selectedTask?.bindingStatus, selectedTask?.id, selectedTask?.loginStatus])
useEffect(() => {
if (!selectedTask || !orderNo || bindingInfo || hydratingBindingRef.current) return
if (
selectedTask.loginStatus !== 'authenticated' ||
selectedTask.bindingStatus !== 'role_ready'
) {
return
}
hydratingBindingRef.current = true
void pollCustomerBinding(orderNo, selectedTask.id)
.then((response) => {
setTasks((current) =>
current.map((task) => (task.id === response.data.task.id ? response.data.task : task)),
)
if (response.data.binding) setBindingInfo(response.data.binding)
})
.catch(() => undefined)
.finally(() => {
hydratingBindingRef.current = false
})
}, [
bindingInfo,
orderNo,
selectedTask?.bindingStatus,
selectedTask?.id,
selectedTask?.loginStatus,
])
async function handleStartLogin() {
if (!selectedTask) return
setLoading(true)
@@ -201,6 +231,7 @@ export default function CustomerFulfillmentPage() {
async function handleConfirmBinding() {
if (!selectedTask) return
setBindingActionLoading(true)
try {
const response = await confirmCustomerBinding(orderNo, selectedTask.id)
setTasks((current) =>
@@ -210,6 +241,26 @@ export default function CustomerFulfillmentPage() {
message.success('绑定已确认,等待客服处理')
} catch (error) {
message.error(error instanceof Error ? error.message : '确认绑定失败')
} finally {
setBindingActionLoading(false)
}
}
async function handleStartRebind() {
if (!selectedTask) return
setBindingActionLoading(true)
try {
const response = await startCustomerBinding(orderNo, selectedTask.id, {
forceRebind: true,
})
setTasks((current) =>
current.map((task) => (task.id === response.data.task.id ? response.data.task : task)),
)
setBindingInfo(response.data.binding)
} catch (error) {
message.error(error instanceof Error ? error.message : '创建换绑二维码失败')
} finally {
setBindingActionLoading(false)
}
}
@@ -265,6 +316,14 @@ export default function CustomerFulfillmentPage() {
const bindingQrVisible =
selectedTask?.loginStatus === 'authenticated' &&
['qr_pending', 'scanned', 'role_ready'].includes(selectedTask.bindingStatus)
const bindingQrPending = ['qr_pending', 'scanned'].includes(selectedTask?.bindingStatus || '')
const bindingRole = bindingInfo?.bindGameRole || selectedTask?.bindGameRole || ''
const bindingAccount = bindingInfo?.bindGameAccount || selectedTask?.bindGameAccount || ''
const bindingChannel = bindingInfo?.bindGameChannel || selectedTask?.bindGameChannel || ''
const roleChoiceRequired =
selectedTask?.bindingStatus === 'role_ready' &&
bindingInfo?.hasExistingBinding === true &&
bindingInfo?.rebindRequested !== true
return (
<div className="customer-fulfillment-page">
@@ -374,32 +433,66 @@ export default function CustomerFulfillmentPage() {
) : null}
{bindingQrVisible ? (
<Card title="第二步:扫码绑定游戏角色">
<Card
title={
selectedTask.bindingStatus === 'role_ready'
? '第二步:确认游戏角色'
: '第二步:扫码绑定游戏角色'
}
>
<Space direction="vertical" align="center" size={14} style={{ width: '100%' }}>
{bindingInfo?.qrImage ? (
{bindingQrPending && bindingInfo?.qrImage ? (
<Image src={bindingInfo.qrImage} alt="游戏角色绑定二维码" width={260} />
) : (
) : bindingQrPending ? (
<Spin tip="生成绑定二维码..." />
)}
) : null}
<Text type="secondary">
{bindingInfo?.message ||
config.bindingInstructions ||
'请使用虎牙 App 扫码完成游戏角色绑定。'}
</Text>
{bindingInfo?.bindGameRole ? (
{bindingRole ? (
<Alert
type="success"
type={roleChoiceRequired ? 'warning' : 'success'}
showIcon
message={`已识别角色:${bindingInfo.bindGameRole}`}
description={[bindingInfo.bindGameAccount, bindingInfo.bindGameChannel]
.filter(Boolean)
.join(' · ')}
message={
roleChoiceRequired
? `当前已绑定角色:${bindingRole}`
: `已识别角色:${bindingRole}`
}
description={[bindingAccount, bindingChannel].filter(Boolean).join(' · ')}
/>
) : null}
{selectedTask.bindingStatus === 'role_ready' ? (
<Button type="primary" onClick={() => void handleConfirmBinding()}>
确认绑定角色
</Button>
<Space direction="vertical" align="center" size={10}>
{roleChoiceRequired ? (
<Text type="secondary">
请确认这是本次需要的游戏角色;角色不正确时请重新扫码换绑。
</Text>
) : null}
<Space wrap>
<Button
type="primary"
loading={bindingActionLoading}
onClick={() => void handleConfirmBinding()}
>
{roleChoiceRequired
? '使用当前角色'
: bindingInfo?.rebindRequested
? '确认换绑角色'
: '确认绑定角色'}
</Button>
{roleChoiceRequired ? (
<Button
danger
loading={bindingActionLoading}
onClick={() => void handleStartRebind()}
>
角色不对,重新扫码换绑
</Button>
) : null}
</Space>
</Space>
) : null}
</Space>
</Card>
@@ -31,10 +31,14 @@ export function pollCustomerLogin(orderNo: string, taskId: number) {
)
}
export function startCustomerBinding(orderNo: string, taskId: number) {
export function startCustomerBinding(
orderNo: string,
taskId: number,
options: { forceRebind?: boolean } = {},
) {
return apiPost<{ task: CustomerSemiAutoTask; binding: CustomerBindingInfo }>(
`/api/v1/customer-fulfillment/${encodeURIComponent(orderNo)}/tasks/${taskId}/binding/start`,
{},
options.forceRebind ? { forceRebind: true } : {},
)
}
@@ -41,5 +41,7 @@ export type CustomerBindingInfo = {
bindGameAccount: string
bindGameRole: string
bindGameChannel: string
hasExistingBinding?: boolean
rebindRequested?: boolean
message: string
}