Files
order_site/apps/frontend/src/pages/claim/ClaimLewanSteps.tsx
T

344 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
)
}