affiliate-dash 领取界面优化(合并多次迭代为一次提交)

- 第 2 步绑定页:移除金额/单号/商品信息区,移除标题上方 affiliate-dash 标签
- 二维码区参考旧设计:左右竖排红字 Q区用Q扫/V区用V扫,去浅蓝背景,放大至 220px
- 第 4 步结果页:废弃 canvas 生成发货成功图,改用旧版结果卡片(绿勾+兑换成功+信息网格+商品明细)
- 开发 Mock:新增 affiliate-dash 领取五步 mock(uid/bind/submitted/completed/failed,不调真实平台)
This commit is contained in:
yml2213
2026-08-05 19:39:15 +08:00
parent 316ee6d135
commit 4e9c416235
10 changed files with 519 additions and 65 deletions
@@ -21,6 +21,7 @@ import { Link } from 'react-router'
import PageHeader from '@/components/admin/PageHeader'
import {
buildOpen91QueryDevMock,
createAffiliateDashDevMock,
createFeifeiDevMock,
createLewanDevMock,
fetchDevMockStatus,
@@ -64,6 +65,17 @@ export default function AdminDevMockPage() {
},
})
const affiliateDashMutation = useMutation({
mutationFn: createAffiliateDashDevMock,
onSuccess: (response) => {
setLastCreate(response.data)
message.success('affiliate-dash mock 已生成')
},
onError: (error) => {
message.error(error instanceof Error ? error.message : '生成失败')
},
})
const open91Mutation = useMutation({
mutationFn: buildOpen91QueryDevMock,
onSuccess: (response) => {
@@ -145,8 +157,17 @@ export default function AdminDevMockPage() {
</Card>
</Col>
<Col xs={24} xl={12}>
<Card title="3. affiliate-dash 领取(mock 二维码/提交发货)" bordered={false}>
<AffiliateDashMockForm
loading={affiliateDashMutation.isPending}
onSubmit={(values) => affiliateDashMutation.mutate(values)}
/>
</Card>
</Col>
<Col xs={24}>
<Card title="3. 模拟 91 查单(open-91/query" bordered={false}>
<Card title="4. 模拟 91 查单(open-91/query" bordered={false}>
<Form
layout="inline"
initialValues={{ orderNo: defaultOrderNo, execute: '1' }}
@@ -428,3 +449,70 @@ function FeifeiMockForm({
</Form>
)
}
function AffiliateDashMockForm({
loading,
onSubmit,
}: {
loading: boolean
onSubmit: (values: Record<string, unknown>) => void
}) {
const [form] = Form.useForm()
const step = Form.useWatch('step', form)
const uidRequired = step !== 'uid'
return (
<Form
form={form}
layout="vertical"
initialValues={{
step: 'bind',
uid: '166909256',
productName: '幸运币90个',
productSku: 'lucky_coin_x90',
}}
onFinish={onSubmit}
>
<Form.Item label="页面步骤" name="step">
<Select
options={[
{ value: 'uid', label: 'uid · 待填 UID(第一步)' },
{ value: 'bind', label: 'bind · 已填 UID + mock 绑定二维码(第二步,推荐)' },
{ value: 'submitted', label: 'submitted · 已绑定待提交发货(第三步)' },
{ value: 'completed', label: 'completed · 模拟发货成功(第四步)' },
{ value: 'failed', label: 'failed · 模拟发货失败' },
]}
/>
</Form.Item>
<Form.Item
label="游戏 UID"
name="uid"
required={uidRequired}
rules={
uidRequired
? [{ required: true, whitespace: true, message: '后续步骤请填写游戏 UID' }]
: []
}
extra={
step === 'uid'
? '「待填 UID」步骤可不填,打开领取页会停留在第一步让用户填写'
: '后续步骤用于模拟已提交的编号'
}
>
<Input allowClear placeholder="166909256" />
</Form.Item>
<Form.Item label="商品名" name="productName">
<Input />
</Form.Item>
<Form.Item label="affiliate-dash sku(可空)" name="productSku">
<Input placeholder="lucky_coin_x90" />
</Form.Item>
<Form.Item label="订单号 orderNo(可空)" name="orderNo">
<Input placeholder="留空自动 MOCKAD…" />
</Form.Item>
<Button type="primary" htmlType="submit" loading={loading} block>
affiliate-dash Mock
</Button>
</Form>
)
}
@@ -1,18 +1,13 @@
import { CheckCircleOutlined, LinkOutlined } from '@ant-design/icons'
import { CheckCircleOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
import { Alert, Button, Card, Typography } from 'antd'
import type {
ClaimAffiliateDashFlowInfo,
ClaimOrderInfo,
ClaimProductInfo,
} from '@/types/claim'
import { formatAdminDateTime } from '@/utils/admin-time'
import type { ClaimAffiliateDashFlowInfo } from '@/types/claim'
import type { ClaimSnapshot } from './claim-snapshot'
import { InfoTile, UidEditRow } from './claim-shared'
import { ClaimProductItems, InfoTile, UidEditRow } from './claim-shared'
export function AffiliateDashClaimPanel({
affiliateDash,
expectedUid,
order,
product,
taskLastError,
qrCodeDataUrl,
submittingUid,
@@ -21,12 +16,9 @@ export function AffiliateDashClaimPanel({
onUidChange,
onResubmitUid,
onSubmit,
onOpenBindUrl,
}: {
affiliateDash: ClaimAffiliateDashFlowInfo
expectedUid: string
order: ClaimOrderInfo | null
product: ClaimProductInfo | null
taskLastError?: string
qrCodeDataUrl: string
submittingUid: boolean
@@ -35,16 +27,13 @@ export function AffiliateDashClaimPanel({
onUidChange: (value: string) => void
onResubmitUid: () => void
onSubmit: () => void
onOpenBindUrl: () => void
}) {
const bindReady = Boolean(affiliateDash.bindUrl || affiliateDash.qrUrl)
const bound = Boolean(affiliateDash.bound)
const bindMismatch = Boolean(affiliateDash.bindMismatch)
return (
<Card className="claim-content-card">
<div className="claim-feifei-main">
<span className="claim-feifei-label">affiliate-dash</span>
<Typography.Title level={2}>
{bound ? '第 3 步:确认并提交发货' : '第 2 步:绑定领取账号'}
</Typography.Title>
@@ -58,14 +47,6 @@ export function AffiliateDashClaimPanel({
onSubmit={onResubmitUid}
/>
<div className="claim-info-grid">
<InfoTile label="填写 UID" value={expectedUid || '-'} />
<InfoTile label="订单号" value={order?.platformOrderId || '-'} />
<InfoTile label="affiliate-dash 单号" value={affiliateDash.orderNo || '-'} />
<InfoTile label="领取商品" value={product?.title || affiliateDash.productName || '-'} />
<InfoTile label="金额" value={`${affiliateDash.amount} ${affiliateDash.currency}`.trim() || '-'} />
</div>
{taskLastError ? (
<Alert
type="warning"
@@ -98,26 +79,49 @@ export function AffiliateDashClaimPanel({
) : null}
{!bound ? (
<div className="claim-bind-area" style={{ marginBottom: 16 }}>
<div
className="claim-bind-area"
style={{
marginBottom: 16,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 20,
}}
>
<span
style={{
color: '#ff4d4f',
writingMode: 'vertical-rl',
textOrientation: 'upright',
fontSize: 17,
fontWeight: 600,
letterSpacing: 8,
}}
>
Q区用Q扫
</span>
{qrCodeDataUrl ? (
<img
src={qrCodeDataUrl}
alt="绑定二维码"
style={{ width: 180, height: 180, borderRadius: 8 }}
style={{ width: 220, height: 220, borderRadius: 8 }}
/>
) : null}
{bindReady ? (
<Button
type="primary"
icon={<LinkOutlined />}
onClick={onOpenBindUrl}
style={{ marginLeft: 16 }}
>
</Button>
) : (
<Typography.Text type="secondary"></Typography.Text>
<Typography.Text type="secondary"></Typography.Text>
)}
<span
style={{
color: '#ff4d4f',
writingMode: 'vertical-rl',
textOrientation: 'upright',
fontSize: 17,
fontWeight: 600,
letterSpacing: 8,
}}
>
V区用V扫
</span>
</div>
) : (
<Button
@@ -141,21 +145,37 @@ export function AffiliateDashResultStep({
snapshot: ClaimSnapshot
}) {
const affiliateDash = snapshot.affiliateDash
const success = Boolean(affiliateDash && !snapshot.isRedeemFailed)
const flow = snapshot.flow
return (
<Card className="claim-content-card claim-result-card">
<div className="claim-result-title">{snapshot.resultTitle}</div>
<div className="claim-result-description">{snapshot.resultDescription}</div>
{affiliateDash ? (
<div className="claim-info-grid" style={{ marginTop: 24 }}>
<InfoTile label="领取商品" value={affiliateDash.productName || '-'} />
<InfoTile label="affiliate-dash 单号" value={affiliateDash.orderNo || '-'} />
<InfoTile label="订单状态" value={affiliateDash.orderStatus || '-'} />
<InfoTile label="发货状态" value={affiliateDash.submitStatus || '-'} />
<InfoTile label="金额" value={`${affiliateDash.amount} ${affiliateDash.currency}`.trim() || '-'} />
<Card className="claim-content-card">
<div className="claim-result-header">
{!success ? (
<ExclamationCircleOutlined className="claim-large-icon claim-icon-warning" />
) : (
<CheckCircleOutlined className="claim-large-icon claim-icon-success" />
)}
<div>
<Typography.Title level={2}>{snapshot.resultTitle}</Typography.Title>
<p className="claim-muted">{snapshot.resultDescription}</p>
</div>
) : null}
</div>
<div className="claim-info-grid">
<InfoTile label="领取商品" value={snapshot.product?.title || '-'} />
<InfoTile
label="绑定账号"
value={affiliateDash?.boundAccount || affiliateDash?.gameAccount || '-'}
/>
<InfoTile label="渠道" value={affiliateDash?.gameChannel || '-'} />
<InfoTile
label="结果时间"
value={formatAdminDateTime(flow?.dispatch.dispatchAt || null)}
/>
</div>
<ClaimProductItems product={snapshot.product} />
</Card>
)
}
@@ -377,8 +377,6 @@ export default function ClaimPage() {
<AffiliateDashClaimPanel
affiliateDash={snapshot.affiliateDash}
expectedUid={snapshot.expectedUid}
order={snapshot.order}
product={snapshot.product}
taskLastError={snapshot.task?.lastError}
qrCodeDataUrl={qrCodeDataUrl}
submittingUid={submittingUid}
@@ -387,7 +385,6 @@ export default function ClaimPage() {
onUidChange={setUidInput}
onResubmitUid={() => void handleSubmitUid()}
onSubmit={() => void handleSubmitAffiliateDash()}
onOpenBindUrl={openBindUrl}
/>
) : snapshot.isFeifeiFlow && snapshot.feifei ? (
<FeifeiClaimPanel
+11 -1
View File
@@ -13,7 +13,7 @@ export type DevMockStatus = {
}
export type DevMockCreateResult = {
platform: 'lewan' | 'feifei'
platform: 'lewan' | 'feifei' | 'affiliate_dash'
orderId: number
orderNo: string
taskId: number
@@ -59,6 +59,16 @@ export function createFeifeiDevMock(payload: {
return apiPost<DevMockCreateResult>('/api/v1/admin/dev-mock/feifei', payload)
}
export function createAffiliateDashDevMock(payload: {
step?: string
uid?: string
orderNo?: string
productName?: string
productSku?: string
}) {
return apiPost<DevMockCreateResult>('/api/v1/admin/dev-mock/affiliate-dash', payload)
}
export function buildOpen91QueryDevMock(payload: {
orderNo: string
execute?: boolean