fix(claim): 领取页展示游戏角色名,并说明乐玩免扫码场景
持久化 affiliate_dash roleName/gameChannel;免扫码授权时明确提示,有绑定链接时提交前仍可显示二维码。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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 '当前领取流程不可用'
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user