fix(claim): 领取页展示游戏角色名,并说明乐玩免扫码场景

持久化 affiliate_dash roleName/gameChannel;免扫码授权时明确提示,有绑定链接时提交前仍可显示二维码。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-09-15 12:39:47 +08:00
co-authored by Cursor
parent 8eef95fe97
commit 8e0cb7e295
6 changed files with 88 additions and 25 deletions
@@ -224,6 +224,7 @@ async function submitAffiliateDashClaimUid(
orderNo: flow.orderNo, orderNo: flow.orderNo,
gameAccount: expectedUid, gameAccount: expectedUid,
}) })
const alreadyBound = Boolean(bindResult.bound)
const nextFlow = { const nextFlow = {
...flow, ...flow,
bindUuid: bindResult.bindUuid, bindUuid: bindResult.bindUuid,
@@ -231,10 +232,13 @@ async function submitAffiliateDashClaimUid(
qrUrl: bindResult.qrUrl, qrUrl: bindResult.qrUrl,
gameAccount: expectedUid, gameAccount: expectedUid,
expectedGameAccount: expectedUid, expectedGameAccount: expectedUid,
// 重新绑定:清空旧绑定状态,等待用户用新账号扫码完成新绑定。 // 乐玩「已授权免扫码」会直接 bound=true;否则清空等待扫码。
bound: false, bound: alreadyBound,
boundAccount: '', boundAccount: alreadyBound
gameChannel: '', ? String(bindResult.gameAccount || expectedUid || '').trim()
: '',
roleName: alreadyBound ? String(bindResult.roleName || '').trim() : '',
gameChannel: alreadyBound ? String(bindResult.gameChannel || '').trim() : '',
bindMismatch: false, bindMismatch: false,
} }
@@ -516,6 +520,7 @@ function clearAffiliateDashBindSnapshot(
expectedGameAccount: gameAccount || flow.expectedGameAccount, expectedGameAccount: gameAccount || flow.expectedGameAccount,
bound: false, bound: false,
boundAccount: '', boundAccount: '',
roleName: '',
gameChannel: '', gameChannel: '',
bindMismatch: false, bindMismatch: false,
} }
@@ -570,7 +575,12 @@ async function refreshAffiliateDashBindState(task: TaskRow): Promise<TaskRow | n
expectedGameAccount: expectedGameAccount || flow.expectedGameAccount, expectedGameAccount: expectedGameAccount || flow.expectedGameAccount,
bound: result.bound, bound: result.bound,
boundAccount, boundAccount,
gameChannel: result.bound ? result.gameChannel || flow.gameChannel : '', roleName: result.bound
? String(result.roleName || flow.roleName || '').trim()
: flow.roleName,
gameChannel: result.bound
? String(result.gameChannel || flow.gameChannel || '').trim()
: flow.gameChannel,
bindMismatch: Boolean(result.mismatch) || localMismatch, bindMismatch: Boolean(result.mismatch) || localMismatch,
} }
const now = nowIso() const now = nowIso()
@@ -324,6 +324,8 @@ export type AffiliateDashFlow = {
/** 是否已在 affiliate-dash 侧完成绑定(bind-result.bound) */ /** 是否已在 affiliate-dash 侧完成绑定(bind-result.bound) */
bound: boolean bound: boolean
boundAccount: string boundAccount: string
/** 绑定角色昵称(乐玩/DLC 返回) */
roleName: string
gameChannel: string gameChannel: string
submitStatus: string submitStatus: string
consumeStatus: string consumeStatus: string
@@ -337,6 +339,10 @@ export function normalizeAffiliateDashFlow(value: unknown): AffiliateDashFlow {
const source = const source =
value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonObject) : {} value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonObject) : {}
const raw = source.raw && typeof source.raw === 'object' ? (source.raw as JsonObject) : null
const rawResult =
raw && raw.result && typeof raw.result === 'object' ? (raw.result as JsonObject) : null
return { return {
flowType: 'affiliate_dash', flowType: 'affiliate_dash',
sku: String(source.sku || '').trim(), sku: String(source.sku || '').trim(),
@@ -358,11 +364,16 @@ export function normalizeAffiliateDashFlow(value: unknown): AffiliateDashFlow {
bindMismatch: Boolean(source.bindMismatch), bindMismatch: Boolean(source.bindMismatch),
bound: Boolean(source.bound), bound: Boolean(source.bound),
boundAccount: String(source.boundAccount || '').trim(), boundAccount: String(source.boundAccount || '').trim(),
gameChannel: String(source.gameChannel || '').trim(), roleName: String(
source.roleName || rawResult?.role_name || raw?.role_name || '',
).trim(),
gameChannel: String(
source.gameChannel || rawResult?.game_channel || raw?.game_channel || '',
).trim(),
submitStatus: String(source.submitStatus || '').trim(), submitStatus: String(source.submitStatus || '').trim(),
consumeStatus: String(source.consumeStatus || 'pending').trim(), consumeStatus: String(source.consumeStatus || 'pending').trim(),
lastSyncedAt: source.lastSyncedAt || null, lastSyncedAt: source.lastSyncedAt || null,
raw: source.raw && typeof source.raw === 'object' ? source.raw : null, raw,
mock: mock:
source.mock && typeof source.mock === 'object' source.mock && typeof source.mock === 'object'
? { ? {
@@ -46,6 +46,10 @@ export function AffiliateDashClaimPanel({
) )
const canSubmit = Boolean(bound && boundAccountMatched && !bindMismatch) const canSubmit = Boolean(bound && boundAccountMatched && !bindMismatch)
const displayBoundAccount = boundAccount || '-' const displayBoundAccount = boundAccount || '-'
const roleName = String(affiliateDash.roleName || '').trim()
const gameChannel = String(affiliateDash.gameChannel || '').trim()
const hasBindQr = Boolean(qrCodeDataUrl)
const skipScanAuthorized = Boolean(canSubmit && !hasBindQr)
return ( return (
<Card className="claim-content-card"> <Card className="claim-content-card">
@@ -55,7 +59,9 @@ export function AffiliateDashClaimPanel({
</Typography.Title> </Typography.Title>
<p> <p>
{canSubmit {canSubmit
? '账号绑定成功,确认无误后提交发货' ? skipScanAuthorized
? '账号已在平台授权,确认无误后提交发货(无需再次扫码)'
: '账号绑定成功,确认无误后提交发货'
: bindMismatch : bindMismatch
? '绑定账号与填写 UID 不一致,请核对 UID 后用正确账号重新绑定' ? '绑定账号与填写 UID 不一致,请核对 UID 后用正确账号重新绑定'
: bound : bound
@@ -107,7 +113,13 @@ export function AffiliateDashClaimPanel({
showIcon showIcon
icon={<CheckCircleOutlined />} icon={<CheckCircleOutlined />}
message={`已绑定账号:${displayBoundAccount}`} message={`已绑定账号:${displayBoundAccount}`}
description={affiliateDash.gameChannel ? `渠道:${affiliateDash.gameChannel}` : undefined} description={[
roleName ? `游戏角色:${roleName}` : '',
gameChannel ? `渠道:${gameChannel}` : '',
skipScanAuthorized ? '该账号此前已授权,本次免扫码' : '',
]
.filter(Boolean)
.join(' · ') || undefined}
style={{ marginBottom: 16 }} style={{ marginBottom: 16 }}
/> />
) : null} ) : null}
@@ -135,7 +147,7 @@ export function AffiliateDashClaimPanel({
> >
Q区用Q扫 Q区用Q扫
</span> </span>
{qrCodeDataUrl ? ( {hasBindQr ? (
<img <img
src={qrCodeDataUrl} src={qrCodeDataUrl}
alt="绑定二维码" alt="绑定二维码"
@@ -158,16 +170,30 @@ export function AffiliateDashClaimPanel({
</span> </span>
</div> </div>
) : ( ) : (
<Button <>
type="primary" <Button
size="large" type="primary"
loading={submittingSubmit} size="large"
disabled={submittingSubmit} loading={submittingSubmit}
onClick={onSubmit} disabled={submittingSubmit}
style={{ marginBottom: 16 }} onClick={onSubmit}
> style={{ marginBottom: 16 }}
提交发货 >
</Button> 提交发货
</Button>
{hasBindQr ? (
<div style={{ marginBottom: 16, textAlign: 'center' }}>
<Typography.Text type="secondary" style={{ display: 'block', marginBottom: 8 }}>
如需换号可重新扫码绑定
</Typography.Text>
<img
src={qrCodeDataUrl}
alt="绑定二维码"
style={{ width: 160, height: 160, borderRadius: 8 }}
/>
</div>
) : null}
</>
)} )}
</Card> </Card>
) )
@@ -176,6 +202,11 @@ export function AffiliateDashClaimPanel({
export function AffiliateDashResultStep({ snapshot }: { snapshot: ClaimSnapshot }) { export function AffiliateDashResultStep({ snapshot }: { snapshot: ClaimSnapshot }) {
const affiliateDash = snapshot.affiliateDash const affiliateDash = snapshot.affiliateDash
const success = Boolean(affiliateDash && !snapshot.isRedeemFailed) const success = Boolean(affiliateDash && !snapshot.isRedeemFailed)
const roleName = String(affiliateDash?.roleName || '').trim()
const gameChannel = String(affiliateDash?.gameChannel || '').trim()
const boundAccount = String(
affiliateDash?.boundAccount || affiliateDash?.gameAccount || '',
).trim()
return ( return (
<Card className="claim-content-card"> <Card className="claim-content-card">
@@ -193,10 +224,9 @@ export function AffiliateDashResultStep({ snapshot }: { snapshot: ClaimSnapshot
<div className="claim-info-grid"> <div className="claim-info-grid">
<InfoTile label="领取商品" value={snapshot.product?.title || '-'} /> <InfoTile label="领取商品" value={snapshot.product?.title || '-'} />
<InfoTile <InfoTile label="绑定账号" value={boundAccount || '-'} />
label="绑定账号" <InfoTile label="游戏角色" value={roleName || '-'} />
value={affiliateDash?.boundAccount || affiliateDash?.gameAccount || '-'} <InfoTile label="游戏渠道" value={gameChannel || '-'} />
/>
<InfoTile label="结果时间" value={formatAdminDateTime(snapshot.task?.redeemedAt || null)} /> <InfoTile label="结果时间" value={formatAdminDateTime(snapshot.task?.redeemedAt || null)} />
</div> </div>
+9 -1
View File
@@ -67,7 +67,15 @@ export default function ClaimPage() {
if (expectedUid) { if (expectedUid) {
setUidInput(expectedUid) setUidInput(expectedUid)
} }
await generateQRCode(String(nextDetail.affiliateDash?.bindUrl || '').trim()) const bindUrl = String(nextDetail.affiliateDash?.bindUrl || '').trim()
const qrUrl = String(nextDetail.affiliateDash?.qrUrl || '').trim()
if (bindUrl) {
await generateQRCode(bindUrl)
} else if (qrUrl) {
setQrCodeDataUrl(qrUrl)
} else {
setQrCodeDataUrl('')
}
}, },
[generateQRCode], [generateQRCode],
) )
@@ -193,6 +193,9 @@ function resolveProgressText(options: {
if (!options.isAffiliateDashReadyToSubmit) { if (!options.isAffiliateDashReadyToSubmit) {
return '绑定账号确认中,请稍候' return '绑定账号确认中,请稍候'
} }
if (!(options.affiliateDash?.bindUrl || options.affiliateDash?.qrUrl)) {
return '账号已授权,请确认提交发货'
}
return '已绑定,请提交发货' return '已绑定,请提交发货'
} }
return '当前领取流程不可用' return '当前领取流程不可用'
+1
View File
@@ -183,6 +183,7 @@ export interface ClaimAffiliateDashFlowInfo {
bindMismatch: boolean bindMismatch: boolean
bound: boolean bound: boolean
boundAccount: string boundAccount: string
roleName: string
gameChannel: string gameChannel: string
submitStatus: string submitStatus: string
consumeStatus: string consumeStatus: string