344 lines
10 KiB
TypeScript
344 lines
10 KiB
TypeScript
import {
|
||
CheckCircleOutlined,
|
||
ExclamationCircleOutlined,
|
||
LinkOutlined,
|
||
LoadingOutlined,
|
||
ReloadOutlined,
|
||
SwapOutlined,
|
||
} from '@ant-design/icons'
|
||
import { Alert, Button, Card, Space, Tooltip, Typography } from 'antd'
|
||
import type { ClaimProductInfo } from '@/types/claim'
|
||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||
import type { ClaimSnapshot } from './claim-snapshot'
|
||
import { ClaimProductItems, InfoRow, InfoTile, UidEditRow } from './claim-shared'
|
||
|
||
export function KuaishouCloudClaimSteps({
|
||
snapshot,
|
||
qrCodeDataUrl,
|
||
refreshingRole,
|
||
confirmingRole,
|
||
rebindingRole,
|
||
redeeming,
|
||
onOpenBindUrl,
|
||
onRefreshRole,
|
||
onRebindRole,
|
||
onConfirmRole,
|
||
onConfirmRedeem,
|
||
uidInput,
|
||
submittingUid,
|
||
onUidChange,
|
||
onResubmitUid,
|
||
}: {
|
||
snapshot: ClaimSnapshot
|
||
qrCodeDataUrl: string
|
||
refreshingRole: boolean
|
||
confirmingRole: boolean
|
||
rebindingRole: boolean
|
||
redeeming: boolean
|
||
onOpenBindUrl: (useCurrentPage?: boolean) => void
|
||
onRefreshRole: () => void
|
||
onRebindRole: () => void
|
||
onConfirmRole: () => void
|
||
onConfirmRedeem: () => void
|
||
uidInput: string
|
||
submittingUid: boolean
|
||
onUidChange: (value: string) => void
|
||
onResubmitUid: () => void
|
||
}) {
|
||
if (!snapshot.flow) {
|
||
return null
|
||
}
|
||
|
||
if (snapshot.currentStep === 3) {
|
||
return (
|
||
<ClaimConfirmStep
|
||
roleName={snapshot.roleName}
|
||
roleId={snapshot.roleId}
|
||
expectedUid={snapshot.expectedUid}
|
||
product={snapshot.product}
|
||
redeeming={redeeming}
|
||
rebindingRole={rebindingRole}
|
||
onConfirmRedeem={onConfirmRedeem}
|
||
onRebindRole={onRebindRole}
|
||
/>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<ClaimBindingStep
|
||
snapshot={snapshot}
|
||
qrCodeDataUrl={qrCodeDataUrl}
|
||
refreshingRole={refreshingRole}
|
||
confirmingRole={confirmingRole}
|
||
rebindingRole={rebindingRole}
|
||
redeeming={redeeming}
|
||
onOpenBindUrl={onOpenBindUrl}
|
||
onRefreshRole={onRefreshRole}
|
||
onRebindRole={onRebindRole}
|
||
onConfirmRole={onConfirmRole}
|
||
onConfirmRedeem={onConfirmRedeem}
|
||
uidInput={uidInput}
|
||
submittingUid={submittingUid}
|
||
onUidChange={onUidChange}
|
||
onResubmitUid={onResubmitUid}
|
||
/>
|
||
)
|
||
}
|
||
|
||
function ClaimBindingStep({
|
||
snapshot,
|
||
qrCodeDataUrl,
|
||
refreshingRole,
|
||
confirmingRole,
|
||
rebindingRole,
|
||
redeeming,
|
||
onOpenBindUrl,
|
||
onRefreshRole,
|
||
onRebindRole,
|
||
onConfirmRole,
|
||
onConfirmRedeem,
|
||
uidInput,
|
||
submittingUid,
|
||
onUidChange,
|
||
onResubmitUid,
|
||
}: {
|
||
snapshot: ClaimSnapshot
|
||
qrCodeDataUrl: string
|
||
refreshingRole: boolean
|
||
confirmingRole: boolean
|
||
rebindingRole: boolean
|
||
redeeming: boolean
|
||
onOpenBindUrl: (useCurrentPage?: boolean) => void
|
||
onRefreshRole: () => void
|
||
onRebindRole: () => void
|
||
onConfirmRole: () => void
|
||
onConfirmRedeem: () => void
|
||
uidInput: string
|
||
submittingUid: boolean
|
||
onUidChange: (value: string) => void
|
||
onResubmitUid: () => void
|
||
}) {
|
||
const flow = snapshot.flow
|
||
if (!flow) {
|
||
return null
|
||
}
|
||
|
||
return (
|
||
<Card className="claim-content-card claim-binding-card">
|
||
<Typography.Title level={2}>第 2 步:绑定角色</Typography.Title>
|
||
<p className="claim-muted">
|
||
请使用与 UID <strong>{snapshot.expectedUid || '-'}</strong> 对应的角色扫码绑定。
|
||
</p>
|
||
|
||
<UidEditRow
|
||
uidInput={uidInput}
|
||
submitting={submittingUid}
|
||
onChange={onUidChange}
|
||
onSubmit={onResubmitUid}
|
||
/>
|
||
|
||
{snapshot.isBindingPrepared && qrCodeDataUrl ? (
|
||
<div className="claim-qr-block">
|
||
<div className="claim-qr-guide-panel">
|
||
<div className="claim-scan-label claim-scan-label-left">Q区用Q扫</div>
|
||
<img src={qrCodeDataUrl} alt="绑定二维码" className="claim-qr-code" />
|
||
<div className="claim-scan-label claim-scan-label-right">V区用V扫</div>
|
||
<p className="claim-save-hint">长按保存相册进行扫码绑定</p>
|
||
</div>
|
||
|
||
<Button icon={<LinkOutlined />} onClick={() => onOpenBindUrl(false)}>
|
||
打开绑定链接
|
||
</Button>
|
||
</div>
|
||
) : (
|
||
<Alert
|
||
type="info"
|
||
showIcon
|
||
icon={<LoadingOutlined spin />}
|
||
message="系统正在准备绑定资源,请稍后自动刷新。"
|
||
/>
|
||
)}
|
||
|
||
<div className={snapshot.isUidMatched ? 'claim-role-panel' : 'claim-role-panel pending'}>
|
||
<InfoRow label="填写 UID" value={snapshot.expectedUid || '-'} />
|
||
<InfoRow label="当前角色" value={snapshot.roleName || '待识别'} />
|
||
<InfoRow label="角色 ID" value={snapshot.roleId || '-'} />
|
||
<InfoRow label="匹配状态" value={snapshot.isUidMatched ? '已匹配' : '未匹配'} />
|
||
</div>
|
||
|
||
{!snapshot.isUidMatched && snapshot.roleId ? (
|
||
<Alert
|
||
type="warning"
|
||
showIcon
|
||
message={`当前绑定角色 ID(${snapshot.roleId})与填写 UID(${snapshot.expectedUid})不一致,请换绑正确角色。`}
|
||
/>
|
||
) : null}
|
||
|
||
<Space size={12} wrap className="claim-action-row">
|
||
<Button
|
||
size="large"
|
||
icon={<ReloadOutlined />}
|
||
loading={refreshingRole}
|
||
onClick={onRefreshRole}
|
||
>
|
||
刷新角色信息
|
||
</Button>
|
||
<Tooltip title="角色绑定错误时,请点击「换绑角色」,然后重新扫码绑定正确的角色">
|
||
<span className="claim-action-tooltip-wrap">
|
||
<Button
|
||
size="large"
|
||
icon={<SwapOutlined />}
|
||
loading={rebindingRole}
|
||
disabled={confirmingRole || redeeming}
|
||
onClick={onRebindRole}
|
||
>
|
||
换绑角色
|
||
</Button>
|
||
</span>
|
||
</Tooltip>
|
||
{snapshot.isUidMatched ? (
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
danger
|
||
icon={<CheckCircleOutlined />}
|
||
loading={redeeming}
|
||
disabled={rebindingRole}
|
||
onClick={onConfirmRedeem}
|
||
>
|
||
UID 已匹配,确认兑换
|
||
</Button>
|
||
) : (
|
||
<Tooltip title={snapshot.confirmRoleDisabledReason || undefined}>
|
||
<span className="claim-action-tooltip-wrap">
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
icon={<CheckCircleOutlined />}
|
||
loading={confirmingRole}
|
||
disabled={Boolean(snapshot.confirmRoleDisabledReason)}
|
||
onClick={onConfirmRole}
|
||
>
|
||
刷新后重试匹配
|
||
</Button>
|
||
</span>
|
||
</Tooltip>
|
||
)}
|
||
</Space>
|
||
|
||
<div className="claim-meta-footer">
|
||
<span>最近刷新:{formatAdminDateTime(flow.role.refreshedAt)}</span>
|
||
<span>链接有效期:{formatAdminDateTime(flow.binding.bindExpiresAt)}</span>
|
||
</div>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
function ClaimConfirmStep({
|
||
roleName,
|
||
roleId,
|
||
expectedUid,
|
||
product,
|
||
redeeming,
|
||
rebindingRole,
|
||
onConfirmRedeem,
|
||
onRebindRole,
|
||
}: {
|
||
roleName: string
|
||
roleId: string
|
||
expectedUid: string
|
||
product: ClaimProductInfo | null
|
||
redeeming: boolean
|
||
rebindingRole: boolean
|
||
onConfirmRedeem: () => void
|
||
onRebindRole: () => void
|
||
}) {
|
||
return (
|
||
<Card className="claim-content-card">
|
||
<Typography.Title level={2}>第 3 步:确认兑换信息</Typography.Title>
|
||
<p className="claim-muted">请再次确认 UID 和商品信息,确认无误后再继续兑换。</p>
|
||
|
||
<div className="claim-info-grid">
|
||
<InfoTile label="填写 UID" value={expectedUid || '-'} />
|
||
<InfoTile label="角色名称" value={roleName || '-'} />
|
||
<InfoTile label="角色 ID" value={roleId || '-'} />
|
||
<InfoTile label="领取商品" value={product?.title || '-'} />
|
||
<InfoTile label="商品数量" value={String(product?.quantity || 0)} />
|
||
</div>
|
||
|
||
<ClaimProductItems product={product} />
|
||
|
||
<Alert type="warning" showIcon message="兑换后不可取消,也不可退货。" />
|
||
|
||
<Space size={12} wrap className="claim-action-row">
|
||
<Tooltip title="角色绑定错误时,请点击「换绑角色」,然后重新扫码绑定正确的角色">
|
||
<span className="claim-action-tooltip-wrap">
|
||
<Button
|
||
size="large"
|
||
icon={<SwapOutlined />}
|
||
loading={rebindingRole}
|
||
disabled={redeeming}
|
||
onClick={onRebindRole}
|
||
>
|
||
换绑角色
|
||
</Button>
|
||
</span>
|
||
</Tooltip>
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
icon={<CheckCircleOutlined />}
|
||
loading={redeeming}
|
||
disabled={rebindingRole}
|
||
onClick={onConfirmRedeem}
|
||
>
|
||
确认兑换
|
||
</Button>
|
||
</Space>
|
||
</Card>
|
||
)
|
||
}
|
||
|
||
export function ClaimResultStep({ snapshot }: { snapshot: ClaimSnapshot }) {
|
||
const flow = snapshot.flow
|
||
|
||
return (
|
||
<Card className="claim-content-card">
|
||
<div className="claim-result-header">
|
||
{snapshot.resultVariant === 'warning' ? (
|
||
<ExclamationCircleOutlined className="claim-large-icon claim-icon-warning" />
|
||
) : (
|
||
<CheckCircleOutlined
|
||
className={`claim-large-icon claim-icon-${snapshot.resultVariant}`}
|
||
/>
|
||
)}
|
||
<div>
|
||
<Typography.Title level={2}>{snapshot.resultTitle}</Typography.Title>
|
||
<p className="claim-muted">{snapshot.resultDescription}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="claim-info-grid">
|
||
<InfoTile label="填写 UID" value={snapshot.expectedUid || '-'} />
|
||
<InfoTile label="结果时间" value={formatAdminDateTime(flow?.dispatch.dispatchAt || null)} />
|
||
<InfoTile label="领取商品" value={snapshot.product?.title || '-'} />
|
||
<InfoTile label="角色名称" value={snapshot.roleName || '-'} />
|
||
<InfoTile label="角色 ID" value={snapshot.roleId || '-'} />
|
||
<InfoTile label="订单号" value={snapshot.order?.platformOrderId || '-'} />
|
||
</div>
|
||
|
||
<ClaimProductItems product={snapshot.product} />
|
||
|
||
<Alert
|
||
type={snapshot.resultVariant}
|
||
showIcon
|
||
message={
|
||
snapshot.resultVariant === 'warning'
|
||
? '当前兑换未完成,客服会根据后台任务记录继续处理。'
|
||
: '客户侧操作已经完成,后续履约结果请以后台任务界面为准。'
|
||
}
|
||
/>
|
||
</Card>
|
||
)
|
||
}
|