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:
@@ -2,6 +2,7 @@ import { Router } from 'express'
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
buildOpen91QueryMock,
|
buildOpen91QueryMock,
|
||||||
|
createAffiliateDashMockClaim,
|
||||||
createFeifeiMockClaim,
|
createFeifeiMockClaim,
|
||||||
createLewanMockClaim,
|
createLewanMockClaim,
|
||||||
getDevMockStatus,
|
getDevMockStatus,
|
||||||
@@ -81,6 +82,25 @@ router.post(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/dev-mock/affiliate-dash',
|
||||||
|
createJsonHandler((req) => createAffiliateDashMockClaim(req.body || {}), {
|
||||||
|
successMessage: 'affiliate-dash mock 已生成',
|
||||||
|
errorMessage: '生成 affiliate-dash mock 失败',
|
||||||
|
scope: '[admin/dev-mock/affiliate-dash]',
|
||||||
|
audit: (_req, data) => ({
|
||||||
|
action: 'dev_mock_create_affiliate_dash',
|
||||||
|
targetType: 'dev_mock',
|
||||||
|
targetId: String((data as { orderNo?: string })?.orderNo || ''),
|
||||||
|
data: {
|
||||||
|
platform: 'affiliate_dash',
|
||||||
|
orderNo: (data as { orderNo?: string })?.orderNo,
|
||||||
|
step: (data as { step?: string })?.step,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/dev-mock/open91/query',
|
'/dev-mock/open91/query',
|
||||||
createJsonHandler((req) => buildOpen91QueryMock(req.body || {}), {
|
createJsonHandler((req) => buildOpen91QueryMock(req.body || {}), {
|
||||||
|
|||||||
@@ -414,11 +414,18 @@ export async function submitAffiliateDashClaim(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const now = nowIso()
|
const now = nowIso()
|
||||||
const result = await submitAffiliateDashDelivery({
|
const isMock = Boolean(flow.mock?.enabled)
|
||||||
orderNo: flow.orderNo,
|
const result = isMock
|
||||||
gameAccount,
|
? {
|
||||||
bindUuid,
|
status: 'delivered',
|
||||||
})
|
message: '开发 mock 已模拟提交发货',
|
||||||
|
providerOrderNo: `MOCKAD-${flow.orderNo}`,
|
||||||
|
}
|
||||||
|
: await submitAffiliateDashDelivery({
|
||||||
|
orderNo: flow.orderNo,
|
||||||
|
gameAccount,
|
||||||
|
bindUuid,
|
||||||
|
})
|
||||||
|
|
||||||
const nextFlow = {
|
const nextFlow = {
|
||||||
...flow,
|
...flow,
|
||||||
@@ -505,6 +512,10 @@ async function bindAffiliateDashClaimForTask(task: TaskRow, gameAccount: string)
|
|||||||
async function refreshAffiliateDashBindState(task: TaskRow): Promise<TaskRow | null> {
|
async function refreshAffiliateDashBindState(task: TaskRow): Promise<TaskRow | null> {
|
||||||
const taskContext = parseTaskContextValue(task)
|
const taskContext = parseTaskContextValue(task)
|
||||||
const flow = normalizeAffiliateDashFlow(taskContext.affiliateDash)
|
const flow = normalizeAffiliateDashFlow(taskContext.affiliateDash)
|
||||||
|
if (flow.mock?.enabled) {
|
||||||
|
// dev-mock:不请求 affiliate-dash 平台,绑定状态以 context 为准
|
||||||
|
return task
|
||||||
|
}
|
||||||
if (!flow.orderNo || !flow.bindUuid) {
|
if (!flow.orderNo || !flow.bindUuid) {
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export type LewanMockStep = 'uid' | 'binding' | 'confirm' | 'result'
|
|||||||
export type FeifeiMockStep = 'uid' | 'ready' | 'completed' | 'failed'
|
export type FeifeiMockStep = 'uid' | 'ready' | 'completed' | 'failed'
|
||||||
|
|
||||||
export type DevMockCreateResult = {
|
export type DevMockCreateResult = {
|
||||||
platform: 'lewan' | 'feifei'
|
platform: 'lewan' | 'feifei' | 'affiliate_dash'
|
||||||
orderId: number
|
orderId: number
|
||||||
orderNo: string
|
orderNo: string
|
||||||
taskId: number
|
taskId: number
|
||||||
@@ -84,6 +84,12 @@ export function getDevMockStatus() {
|
|||||||
description: '本站 claim + 拼 uid 的 H5 链接(不调真实 feifei 下单)',
|
description: '本站 claim + 拼 uid 的 H5 链接(不调真实 feifei 下单)',
|
||||||
steps: ['uid', 'ready', 'completed', 'failed'],
|
steps: ['uid', 'ready', 'completed', 'failed'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'affiliate_dash',
|
||||||
|
name: 'affiliate-dash',
|
||||||
|
description: '本站 claim + mock 绑定二维码/提交发货(不调真实 affiliate-dash 平台)',
|
||||||
|
steps: ['uid', 'bind', 'submitted', 'completed', 'failed'],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
defaultUid: '10001',
|
defaultUid: '10001',
|
||||||
@@ -356,6 +362,198 @@ export async function createFeifeiMockClaim(input: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AffiliateDashMockStep = 'uid' | 'bind' | 'submitted' | 'completed' | 'failed'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 affiliate-dash 领取 mock:不调用真实 affiliate-dash 平台。
|
||||||
|
* 上下文带 mock 标记,sync/refresh/submit 全部短路,领取页可走完四步。
|
||||||
|
*/
|
||||||
|
export async function createAffiliateDashMockClaim(input: {
|
||||||
|
step?: unknown
|
||||||
|
orderNo?: unknown
|
||||||
|
productName?: unknown
|
||||||
|
productSku?: unknown
|
||||||
|
uid?: unknown
|
||||||
|
frontendBaseUrl?: unknown
|
||||||
|
} = {}): Promise<DevMockCreateResult> {
|
||||||
|
assertDevMockEnabled()
|
||||||
|
|
||||||
|
const step = normalizeAffiliateDashStep(input.step)
|
||||||
|
const orderNo = String(input.orderNo || `MOCKAD${Date.now()}`).trim()
|
||||||
|
const productName = String(input.productName || '幸运币90个').trim()
|
||||||
|
const productSku = String(input.productSku || 'lucky_coin_x90').trim()
|
||||||
|
const expectedUid = resolveMockUid(input.uid, step === 'uid' ? '' : '166909256')
|
||||||
|
const fallbackUid = expectedUid || '166909256'
|
||||||
|
const bindUrl =
|
||||||
|
step === 'uid'
|
||||||
|
? ''
|
||||||
|
: `https://skin.khhao.com/mock/bind?order=${encodeURIComponent(orderNo)}&uid=${encodeURIComponent(fallbackUid)}`
|
||||||
|
const bound = step === 'submitted' || step === 'completed' || step === 'failed'
|
||||||
|
const orderStatus =
|
||||||
|
step === 'completed'
|
||||||
|
? 'delivered'
|
||||||
|
: step === 'failed'
|
||||||
|
? 'ship_failed'
|
||||||
|
: step === 'submitted'
|
||||||
|
? 'delivering'
|
||||||
|
: 'paid'
|
||||||
|
const now = nowIso()
|
||||||
|
|
||||||
|
await assertOrderNoAvailable(orderNo)
|
||||||
|
|
||||||
|
const profile = await ensureProfile('affiliate_dash', 'affiliate-dash 履约', true)
|
||||||
|
const order = await createBaseOrder({
|
||||||
|
orderNo,
|
||||||
|
productNo: productSku,
|
||||||
|
productName,
|
||||||
|
now,
|
||||||
|
platformTag: 'affiliate-dash',
|
||||||
|
})
|
||||||
|
const orderItem = await createBaseOrderItem({
|
||||||
|
orderId: order.id,
|
||||||
|
orderNo,
|
||||||
|
productNo: productSku,
|
||||||
|
productName,
|
||||||
|
consumeShopId: '',
|
||||||
|
deliveryItems: [],
|
||||||
|
now,
|
||||||
|
executor: 'affiliate_dash',
|
||||||
|
productCode: productSku,
|
||||||
|
})
|
||||||
|
|
||||||
|
const task = await createTask({
|
||||||
|
orderId: order.id,
|
||||||
|
orderItemId: orderItem.id,
|
||||||
|
unitIndex: 1,
|
||||||
|
provider: OPEN_91_PROVIDER,
|
||||||
|
platform: OPEN_91_PLATFORM,
|
||||||
|
shopId: OPEN_91_PROVIDER,
|
||||||
|
shopName: '91卡券',
|
||||||
|
platformOrderId: orderNo,
|
||||||
|
taskNo: randomId('DT'),
|
||||||
|
profileId: profile.id,
|
||||||
|
executorKey: 'affiliate_dash',
|
||||||
|
taskStatus:
|
||||||
|
step === 'completed'
|
||||||
|
? TASK_STATUS.REDEEMED
|
||||||
|
: step === 'failed'
|
||||||
|
? TASK_STATUS.RETRY_PENDING
|
||||||
|
: step === 'submitted'
|
||||||
|
? TASK_STATUS.REDEEMING
|
||||||
|
: TASK_STATUS.LINK_GENERATED,
|
||||||
|
deliveryStatus:
|
||||||
|
step === 'completed'
|
||||||
|
? 'delivered'
|
||||||
|
: step === 'submitted'
|
||||||
|
? 'delivering'
|
||||||
|
: 'pending',
|
||||||
|
resultCode:
|
||||||
|
step === 'completed'
|
||||||
|
? 'affiliate_dash_delivered'
|
||||||
|
: step === 'failed'
|
||||||
|
? 'affiliate_dash_ship_failed'
|
||||||
|
: '',
|
||||||
|
resultMessage:
|
||||||
|
step === 'completed'
|
||||||
|
? 'affiliate-dash 履约成功'
|
||||||
|
: step === 'failed'
|
||||||
|
? '开发 mock 模拟发货失败'
|
||||||
|
: '等待用户领取',
|
||||||
|
claimToken: '',
|
||||||
|
claimExpiresAt: null,
|
||||||
|
automationMode: 'manual',
|
||||||
|
requiresClaim: true,
|
||||||
|
userActionStatus: step === 'completed' ? 'not_required' : 'pending_claim',
|
||||||
|
attemptCount: 0,
|
||||||
|
lastError: step === 'failed' ? '开发 mock 模拟发货失败' : '',
|
||||||
|
contextJson: JSON.stringify({
|
||||||
|
profileKey: 'affiliate_dash',
|
||||||
|
profileName: 'affiliate-dash 履约',
|
||||||
|
skuCode: productName,
|
||||||
|
skuName: productName,
|
||||||
|
claimIdentity: expectedUid
|
||||||
|
? {
|
||||||
|
expectedUid,
|
||||||
|
submittedAt: now,
|
||||||
|
source: 'dev_mock',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
expectedUid: '',
|
||||||
|
submittedAt: null,
|
||||||
|
source: '',
|
||||||
|
},
|
||||||
|
affiliateDash: {
|
||||||
|
flowType: 'affiliate_dash',
|
||||||
|
sku: productSku,
|
||||||
|
productName,
|
||||||
|
orderNo: `AD-${orderNo}`,
|
||||||
|
clientOrderNo: '',
|
||||||
|
orderStatus,
|
||||||
|
canShip: true,
|
||||||
|
cannotShipReason: '',
|
||||||
|
providerOrderNo: '',
|
||||||
|
failureReason: step === 'failed' ? '开发 mock 模拟发货失败' : '',
|
||||||
|
amount: 20,
|
||||||
|
currency: 'POINT',
|
||||||
|
bindUuid: bound ? `mock-bind-${orderNo}` : '',
|
||||||
|
bindUrl,
|
||||||
|
qrUrl: '',
|
||||||
|
gameAccount: bound ? fallbackUid : '',
|
||||||
|
expectedGameAccount: expectedUid || '',
|
||||||
|
bindMismatch: false,
|
||||||
|
bound,
|
||||||
|
boundAccount: bound ? fallbackUid : '',
|
||||||
|
gameChannel: 'Android',
|
||||||
|
submitStatus:
|
||||||
|
step === 'completed'
|
||||||
|
? 'delivered'
|
||||||
|
: step === 'failed'
|
||||||
|
? 'ship_failed'
|
||||||
|
: step === 'submitted'
|
||||||
|
? 'submitting'
|
||||||
|
: '',
|
||||||
|
consumeStatus: step === 'completed' ? 'not_required' : 'pending',
|
||||||
|
lastSyncedAt: now,
|
||||||
|
mock: {
|
||||||
|
enabled: true,
|
||||||
|
orderNo,
|
||||||
|
createdAt: now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
throw createHttpError('履约任务创建失败', { statusCode: 500, errorCode: 'dev_mock_task_failed' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimToken = await createTaskClaimToken(task.id)
|
||||||
|
await updateTask(task.id, {
|
||||||
|
claim_token: claimToken.token,
|
||||||
|
claim_expires_at: claimToken.expired_at,
|
||||||
|
claimed_at: expectedUid ? now : null,
|
||||||
|
redeemed_at: step === 'completed' ? now : null,
|
||||||
|
updated_at: now,
|
||||||
|
})
|
||||||
|
|
||||||
|
return buildCreateResult({
|
||||||
|
platform: 'affiliate_dash',
|
||||||
|
orderId: order.id,
|
||||||
|
orderNo,
|
||||||
|
taskId: task.id,
|
||||||
|
taskNo: task.task_no,
|
||||||
|
claimToken: claimToken.token,
|
||||||
|
claimUrl: buildClaimUrl(claimToken.token),
|
||||||
|
frontendBaseUrl: input.frontendBaseUrl,
|
||||||
|
expectedUid: fallbackUid,
|
||||||
|
step,
|
||||||
|
productName,
|
||||||
|
tips: buildAffiliateDashTips(step, fallbackUid),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成 91 查询请求体(带签名),可选对本机发起查询。
|
* 生成 91 查询请求体(带签名),可选对本机发起查询。
|
||||||
*/
|
*/
|
||||||
@@ -515,7 +713,7 @@ async function createBaseOrderItem(input: {
|
|||||||
consumeShopId: string
|
consumeShopId: string
|
||||||
deliveryItems: DeliveryItem[]
|
deliveryItems: DeliveryItem[]
|
||||||
now: string
|
now: string
|
||||||
executor: 'lewan' | 'feifei'
|
executor: 'lewan' | 'feifei' | 'affiliate_dash'
|
||||||
productCode?: string
|
productCode?: string
|
||||||
}) {
|
}) {
|
||||||
const snapshot =
|
const snapshot =
|
||||||
@@ -531,7 +729,19 @@ async function createBaseOrderItem(input: {
|
|||||||
matchMode: 'mock',
|
matchMode: 'mock',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {
|
: input.executor === 'affiliate_dash'
|
||||||
|
? {
|
||||||
|
source: OPEN_91_PROVIDER,
|
||||||
|
orderNo: input.orderNo,
|
||||||
|
productNo: input.productNo,
|
||||||
|
productName: input.productName,
|
||||||
|
affiliateDash: {
|
||||||
|
sku: input.productCode || input.productNo,
|
||||||
|
productName: input.productName,
|
||||||
|
matchMode: 'mock',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
source: OPEN_91_PROVIDER,
|
source: OPEN_91_PROVIDER,
|
||||||
orderNo: input.orderNo,
|
orderNo: input.orderNo,
|
||||||
productNo: input.productNo,
|
productNo: input.productNo,
|
||||||
@@ -725,6 +935,14 @@ function normalizeFeifeiStep(value: unknown): FeifeiMockStep {
|
|||||||
return 'uid'
|
return 'uid'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeAffiliateDashStep(value: unknown): AffiliateDashMockStep {
|
||||||
|
const step = String(value || 'uid').trim()
|
||||||
|
if (['uid', 'bind', 'submitted', 'completed', 'failed'].includes(step)) {
|
||||||
|
return step as AffiliateDashMockStep
|
||||||
|
}
|
||||||
|
return 'uid'
|
||||||
|
}
|
||||||
|
|
||||||
function resolveMockUid(value: unknown, fallback: string) {
|
function resolveMockUid(value: unknown, fallback: string) {
|
||||||
const raw = String(value ?? '').trim()
|
const raw = String(value ?? '').trim()
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
@@ -774,7 +992,7 @@ function parseDeliveryItems(value: unknown): DeliveryItem[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildCreateResult(input: {
|
function buildCreateResult(input: {
|
||||||
platform: 'lewan' | 'feifei'
|
platform: 'lewan' | 'feifei' | 'affiliate_dash'
|
||||||
orderId: number
|
orderId: number
|
||||||
orderNo: string
|
orderNo: string
|
||||||
taskId: number
|
taskId: number
|
||||||
@@ -844,6 +1062,22 @@ function buildFeifeiTips(step: FeifeiMockStep, uid: string) {
|
|||||||
return ['已模拟 feifei 成功完成态']
|
return ['已模拟 feifei 成功完成态']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildAffiliateDashTips(step: AffiliateDashMockStep, uid: string) {
|
||||||
|
if (step === 'uid') {
|
||||||
|
return [`打开领取链接,Step1 填 UID:${uid}(或自定义)`, '提交后进入绑定步,mock 会给出二维码']
|
||||||
|
}
|
||||||
|
if (step === 'bind') {
|
||||||
|
return [`已预填 UID=${uid},绑定二维码为 mock 生成(扫描无效)`, '正常流程:扫码完成真实绑定后自动进入下一步']
|
||||||
|
}
|
||||||
|
if (step === 'submitted') {
|
||||||
|
return ['已模拟绑定成功,可点「提交发货」(mock 直接模拟发货成功)']
|
||||||
|
}
|
||||||
|
if (step === 'failed') {
|
||||||
|
return ['已模拟发货失败态,用于看领取页结果展示']
|
||||||
|
}
|
||||||
|
return ['已模拟 affiliate-dash 发货完成态']
|
||||||
|
}
|
||||||
|
|
||||||
function signOpen91Payload(payload: Record<string, unknown>, secret: string) {
|
function signOpen91Payload(payload: Record<string, unknown>, secret: string) {
|
||||||
const queryString = Object.entries(payload)
|
const queryString = Object.entries(payload)
|
||||||
.filter(([key]) => key !== 'sign')
|
.filter(([key]) => key !== 'sign')
|
||||||
|
|||||||
@@ -166,7 +166,16 @@ export async function syncAffiliateDashTaskStatus(task: TaskRow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const now = nowIso()
|
const now = nowIso()
|
||||||
const order = await getAffiliateDashOrder(flow.orderNo)
|
// dev-mock 任务:不请求 affiliate-dash 平台,直接用本地快照(flow 自身)收敛状态
|
||||||
|
const order = flow.mock?.enabled
|
||||||
|
? ({
|
||||||
|
orderNo: flow.orderNo,
|
||||||
|
orderStatus: flow.orderStatus,
|
||||||
|
failureReason: flow.failureReason,
|
||||||
|
canShip: flow.canShip,
|
||||||
|
raw: null,
|
||||||
|
} as unknown as AffiliateDashOrder)
|
||||||
|
: await getAffiliateDashOrder(flow.orderNo)
|
||||||
let nextTaskStatus = task.task_status
|
let nextTaskStatus = task.task_status
|
||||||
let deliveryStatus = task.delivery_status
|
let deliveryStatus = task.delivery_status
|
||||||
let redeemedAt = task.redeemed_at
|
let redeemedAt = task.redeemed_at
|
||||||
@@ -286,6 +295,8 @@ export type AffiliateDashFlow = {
|
|||||||
consumeStatus: string
|
consumeStatus: string
|
||||||
lastSyncedAt: unknown
|
lastSyncedAt: unknown
|
||||||
raw: unknown
|
raw: unknown
|
||||||
|
/** dev-mock 标记:enabled 时所有 affiliate-dash 平台调用短路(建单/绑定/提交/同步均不请求外部) */
|
||||||
|
mock: { enabled: boolean; orderNo: string; createdAt: string } | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeAffiliateDashFlow(value: unknown): AffiliateDashFlow {
|
export function normalizeAffiliateDashFlow(value: unknown): AffiliateDashFlow {
|
||||||
@@ -319,6 +330,14 @@ export function normalizeAffiliateDashFlow(value: unknown): AffiliateDashFlow {
|
|||||||
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: source.raw && typeof source.raw === 'object' ? source.raw : null,
|
||||||
|
mock:
|
||||||
|
source.mock && typeof source.mock === 'object'
|
||||||
|
? {
|
||||||
|
enabled: Boolean((source.mock as JsonObject).enabled),
|
||||||
|
orderNo: String((source.mock as JsonObject).orderNo || '').trim(),
|
||||||
|
createdAt: String((source.mock as JsonObject).createdAt || '').trim(),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { getAffiliateDashConfig } from '../platforms/affiliate-dash/config.js'
|
|||||||
import { listAllAffiliateDashProducts } from '../platforms/affiliate-dash/product-service.js'
|
import { listAllAffiliateDashProducts } from '../platforms/affiliate-dash/product-service.js'
|
||||||
import type { AffiliateDashSkuMapping } from '../../types/runtime-config.js'
|
import type { AffiliateDashSkuMapping } from '../../types/runtime-config.js'
|
||||||
import { FULFILLMENT_EXECUTOR_KEYS } from './executors/types.js'
|
import { FULFILLMENT_EXECUTOR_KEYS } from './executors/types.js'
|
||||||
|
import { logIntegration } from '../../utils/logger.js'
|
||||||
|
|
||||||
export type FulfillmentItem = {
|
export type FulfillmentItem = {
|
||||||
itemId?: string
|
itemId?: string
|
||||||
@@ -378,8 +379,13 @@ async function getAffiliateDashSkuSet(): Promise<Set<string>> {
|
|||||||
const skus = new Set<string>(result.list.map((product) => String(product.sku || '').trim()).filter(Boolean))
|
const skus = new Set<string>(result.list.map((product) => String(product.sku || '').trim()).filter(Boolean))
|
||||||
affiliateDashSkuCache = { skus, fetchedAt: now }
|
affiliateDashSkuCache = { skus, fetchedAt: now }
|
||||||
return skus
|
return skus
|
||||||
} catch {
|
} catch (error) {
|
||||||
// 拉取失败:返回上次缓存(即使过期)或空集合,匹配 miss 走其他通道,不阻塞下单
|
// 拉取失败:返回上次缓存(即使过期)或空集合,匹配 miss 走其他通道,不阻塞下单;
|
||||||
|
// warn 日志便于线上排查(密钥未配/平台不可用都会导致透传降级为 miss)。
|
||||||
|
logIntegration('[affiliate-dash]', 'affiliate-dash 商品列表拉取失败,透传降级为未命中', {
|
||||||
|
cachedSkuCount: affiliateDashSkuCache?.skus.size || 0,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
}, { level: 'warn' })
|
||||||
return affiliateDashSkuCache?.skus || new Set<string>()
|
return affiliateDashSkuCache?.skus || new Set<string>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { Link } from 'react-router'
|
|||||||
import PageHeader from '@/components/admin/PageHeader'
|
import PageHeader from '@/components/admin/PageHeader'
|
||||||
import {
|
import {
|
||||||
buildOpen91QueryDevMock,
|
buildOpen91QueryDevMock,
|
||||||
|
createAffiliateDashDevMock,
|
||||||
createFeifeiDevMock,
|
createFeifeiDevMock,
|
||||||
createLewanDevMock,
|
createLewanDevMock,
|
||||||
fetchDevMockStatus,
|
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({
|
const open91Mutation = useMutation({
|
||||||
mutationFn: buildOpen91QueryDevMock,
|
mutationFn: buildOpen91QueryDevMock,
|
||||||
onSuccess: (response) => {
|
onSuccess: (response) => {
|
||||||
@@ -145,8 +157,17 @@ export default function AdminDevMockPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</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}>
|
<Col xs={24}>
|
||||||
<Card title="3. 模拟 91 查单(open-91/query)" bordered={false}>
|
<Card title="4. 模拟 91 查单(open-91/query)" bordered={false}>
|
||||||
<Form
|
<Form
|
||||||
layout="inline"
|
layout="inline"
|
||||||
initialValues={{ orderNo: defaultOrderNo, execute: '1' }}
|
initialValues={{ orderNo: defaultOrderNo, execute: '1' }}
|
||||||
@@ -428,3 +449,70 @@ function FeifeiMockForm({
|
|||||||
</Form>
|
</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 { Alert, Button, Card, Typography } from 'antd'
|
||||||
import type {
|
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||||
ClaimAffiliateDashFlowInfo,
|
import type { ClaimAffiliateDashFlowInfo } from '@/types/claim'
|
||||||
ClaimOrderInfo,
|
|
||||||
ClaimProductInfo,
|
|
||||||
} from '@/types/claim'
|
|
||||||
import type { ClaimSnapshot } from './claim-snapshot'
|
import type { ClaimSnapshot } from './claim-snapshot'
|
||||||
import { InfoTile, UidEditRow } from './claim-shared'
|
import { ClaimProductItems, InfoTile, UidEditRow } from './claim-shared'
|
||||||
|
|
||||||
export function AffiliateDashClaimPanel({
|
export function AffiliateDashClaimPanel({
|
||||||
affiliateDash,
|
affiliateDash,
|
||||||
expectedUid,
|
expectedUid,
|
||||||
order,
|
|
||||||
product,
|
|
||||||
taskLastError,
|
taskLastError,
|
||||||
qrCodeDataUrl,
|
qrCodeDataUrl,
|
||||||
submittingUid,
|
submittingUid,
|
||||||
@@ -21,12 +16,9 @@ export function AffiliateDashClaimPanel({
|
|||||||
onUidChange,
|
onUidChange,
|
||||||
onResubmitUid,
|
onResubmitUid,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onOpenBindUrl,
|
|
||||||
}: {
|
}: {
|
||||||
affiliateDash: ClaimAffiliateDashFlowInfo
|
affiliateDash: ClaimAffiliateDashFlowInfo
|
||||||
expectedUid: string
|
expectedUid: string
|
||||||
order: ClaimOrderInfo | null
|
|
||||||
product: ClaimProductInfo | null
|
|
||||||
taskLastError?: string
|
taskLastError?: string
|
||||||
qrCodeDataUrl: string
|
qrCodeDataUrl: string
|
||||||
submittingUid: boolean
|
submittingUid: boolean
|
||||||
@@ -35,16 +27,13 @@ export function AffiliateDashClaimPanel({
|
|||||||
onUidChange: (value: string) => void
|
onUidChange: (value: string) => void
|
||||||
onResubmitUid: () => void
|
onResubmitUid: () => void
|
||||||
onSubmit: () => void
|
onSubmit: () => void
|
||||||
onOpenBindUrl: () => void
|
|
||||||
}) {
|
}) {
|
||||||
const bindReady = Boolean(affiliateDash.bindUrl || affiliateDash.qrUrl)
|
|
||||||
const bound = Boolean(affiliateDash.bound)
|
const bound = Boolean(affiliateDash.bound)
|
||||||
const bindMismatch = Boolean(affiliateDash.bindMismatch)
|
const bindMismatch = Boolean(affiliateDash.bindMismatch)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="claim-content-card">
|
<Card className="claim-content-card">
|
||||||
<div className="claim-feifei-main">
|
<div className="claim-feifei-main">
|
||||||
<span className="claim-feifei-label">affiliate-dash</span>
|
|
||||||
<Typography.Title level={2}>
|
<Typography.Title level={2}>
|
||||||
{bound ? '第 3 步:确认并提交发货' : '第 2 步:绑定领取账号'}
|
{bound ? '第 3 步:确认并提交发货' : '第 2 步:绑定领取账号'}
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
@@ -58,14 +47,6 @@ export function AffiliateDashClaimPanel({
|
|||||||
onSubmit={onResubmitUid}
|
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 ? (
|
{taskLastError ? (
|
||||||
<Alert
|
<Alert
|
||||||
type="warning"
|
type="warning"
|
||||||
@@ -98,26 +79,49 @@ export function AffiliateDashClaimPanel({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!bound ? (
|
{!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 ? (
|
{qrCodeDataUrl ? (
|
||||||
<img
|
<img
|
||||||
src={qrCodeDataUrl}
|
src={qrCodeDataUrl}
|
||||||
alt="绑定二维码"
|
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>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
@@ -141,21 +145,37 @@ export function AffiliateDashResultStep({
|
|||||||
snapshot: ClaimSnapshot
|
snapshot: ClaimSnapshot
|
||||||
}) {
|
}) {
|
||||||
const affiliateDash = snapshot.affiliateDash
|
const affiliateDash = snapshot.affiliateDash
|
||||||
|
const success = Boolean(affiliateDash && !snapshot.isRedeemFailed)
|
||||||
|
const flow = snapshot.flow
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="claim-content-card claim-result-card">
|
<Card className="claim-content-card">
|
||||||
<div className="claim-result-title">{snapshot.resultTitle}</div>
|
<div className="claim-result-header">
|
||||||
<div className="claim-result-description">{snapshot.resultDescription}</div>
|
{!success ? (
|
||||||
|
<ExclamationCircleOutlined className="claim-large-icon claim-icon-warning" />
|
||||||
{affiliateDash ? (
|
) : (
|
||||||
<div className="claim-info-grid" style={{ marginTop: 24 }}>
|
<CheckCircleOutlined className="claim-large-icon claim-icon-success" />
|
||||||
<InfoTile label="领取商品" value={affiliateDash.productName || '-'} />
|
)}
|
||||||
<InfoTile label="affiliate-dash 单号" value={affiliateDash.orderNo || '-'} />
|
<div>
|
||||||
<InfoTile label="订单状态" value={affiliateDash.orderStatus || '-'} />
|
<Typography.Title level={2}>{snapshot.resultTitle}</Typography.Title>
|
||||||
<InfoTile label="发货状态" value={affiliateDash.submitStatus || '-'} />
|
<p className="claim-muted">{snapshot.resultDescription}</p>
|
||||||
<InfoTile label="金额" value={`${affiliateDash.amount} ${affiliateDash.currency}`.trim() || '-'} />
|
|
||||||
</div>
|
</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>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -377,8 +377,6 @@ export default function ClaimPage() {
|
|||||||
<AffiliateDashClaimPanel
|
<AffiliateDashClaimPanel
|
||||||
affiliateDash={snapshot.affiliateDash}
|
affiliateDash={snapshot.affiliateDash}
|
||||||
expectedUid={snapshot.expectedUid}
|
expectedUid={snapshot.expectedUid}
|
||||||
order={snapshot.order}
|
|
||||||
product={snapshot.product}
|
|
||||||
taskLastError={snapshot.task?.lastError}
|
taskLastError={snapshot.task?.lastError}
|
||||||
qrCodeDataUrl={qrCodeDataUrl}
|
qrCodeDataUrl={qrCodeDataUrl}
|
||||||
submittingUid={submittingUid}
|
submittingUid={submittingUid}
|
||||||
@@ -387,7 +385,6 @@ export default function ClaimPage() {
|
|||||||
onUidChange={setUidInput}
|
onUidChange={setUidInput}
|
||||||
onResubmitUid={() => void handleSubmitUid()}
|
onResubmitUid={() => void handleSubmitUid()}
|
||||||
onSubmit={() => void handleSubmitAffiliateDash()}
|
onSubmit={() => void handleSubmitAffiliateDash()}
|
||||||
onOpenBindUrl={openBindUrl}
|
|
||||||
/>
|
/>
|
||||||
) : snapshot.isFeifeiFlow && snapshot.feifei ? (
|
) : snapshot.isFeifeiFlow && snapshot.feifei ? (
|
||||||
<FeifeiClaimPanel
|
<FeifeiClaimPanel
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export type DevMockStatus = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type DevMockCreateResult = {
|
export type DevMockCreateResult = {
|
||||||
platform: 'lewan' | 'feifei'
|
platform: 'lewan' | 'feifei' | 'affiliate_dash'
|
||||||
orderId: number
|
orderId: number
|
||||||
orderNo: string
|
orderNo: string
|
||||||
taskId: number
|
taskId: number
|
||||||
@@ -59,6 +59,16 @@ export function createFeifeiDevMock(payload: {
|
|||||||
return apiPost<DevMockCreateResult>('/api/v1/admin/dev-mock/feifei', 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: {
|
export function buildOpen91QueryDevMock(payload: {
|
||||||
orderNo: string
|
orderNo: string
|
||||||
execute?: boolean
|
execute?: boolean
|
||||||
|
|||||||
@@ -755,3 +755,52 @@ order_site 处理要求:
|
|||||||
**未新增**:履约配置页不加独立「affiliate-dash 履约面板」——feifei/lewan 有独立面板是因为履约参数多(productRules、deliveryItems 等),affiliate_dash 履约参数只有路由+优先级+规则,已覆盖,加面板属重复。
|
**未新增**:履约配置页不加独立「affiliate-dash 履约面板」——feifei/lewan 有独立面板是因为履约参数多(productRules、deliveryItems 等),affiliate_dash 履约参数只有路由+优先级+规则,已覆盖,加面板属重复。
|
||||||
|
|
||||||
**验证**:前端 `tsc -b --noEmit` 通过;后端 typecheck 通过;224 测试全绿。
|
**验证**:前端 `tsc -b --noEmit` 通过;后端 typecheck 通过;224 测试全绿。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 26. affiliate-dash 领取界面优化(v2.11 · 已完成)
|
||||||
|
|
||||||
|
**需求**:精简 affiliate_dash 领取页四步的信息展示。
|
||||||
|
|
||||||
|
| 步骤 | 改动 |
|
||||||
|
| --- | --- |
|
||||||
|
| 第 1 步 | 不动(与其他平台一致) |
|
||||||
|
| 第 2 步(绑定领取账号) | 信息区只保留三项:**affiliate-dash 单号 / 领取商品 / 金额**(移除「填写 UID」「订单号」tile);二维码右侧「打开绑定链接」按钮**移除**(二维码直接展示) |
|
||||||
|
| 第 3 步(确认并提交发货) | 移除信息区(含三项),仅保留已绑定提示 + 提交按钮 |
|
||||||
|
| 第 4 步(结果页) | 移除信息区(领取商品/单号/订单状态/发货状态/金额全部移除),仅保留结果标题 + 描述 |
|
||||||
|
|
||||||
|
**实现**:`ClaimAffiliateDashSteps.tsx` —— `AffiliateDashClaimPanel` 移除 `onOpenBindUrl` prop 与按钮、info grid 精简为三项且仅在未绑定(第 2 步)显示;`AffiliateDashResultStep` 移除 info grid;`ClaimPage.tsx` 同步移除 `onOpenBindUrl` 传参(`openBindUrl` 函数保留,lewan 仍用)。
|
||||||
|
|
||||||
|
**附带修复**:`product-resolution-service.ts` 透传拉取失败 warn 日志补 `logIntegration` import(上一轮遗漏导致后端 typecheck 报错)。
|
||||||
|
|
||||||
|
**验证**:后端 typecheck 通过、224 测试全绿;前端 `tsc -b --noEmit` 通过。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 27. 开发 Mock 支持 affiliate-dash(v2.12 · 已完成)
|
||||||
|
|
||||||
|
**需求**:开发 Mock 页新增 affiliate-dash 领取 mock,无需真实 affiliate-dash 平台即可走完领取页四步。
|
||||||
|
|
||||||
|
### 能力
|
||||||
|
|
||||||
|
后台「开发 Mock」新增 **3. affiliate-dash 领取**(5 个步骤可生成):
|
||||||
|
|
||||||
|
| step | 领取页表现 |
|
||||||
|
| --- | --- |
|
||||||
|
| `uid` | 第一步:待填 UID(无二维码) |
|
||||||
|
| `bind` | 第二步:已填 UID + mock 绑定二维码(前端用 bindUrl 现生成) |
|
||||||
|
| `submitted` | 第三步:已绑定待「提交发货」(点击后 mock 短路直接模拟发货成功) |
|
||||||
|
| `completed` | 第四步:发货成功结果 |
|
||||||
|
| `failed` | 第四步:发货失败结果(ship_failed) |
|
||||||
|
|
||||||
|
### 实现
|
||||||
|
|
||||||
|
- **mock 标记**:`AffiliateDashFlow` 新增 `mock: { enabled, orderNo, createdAt } | null`(normalize 透传),dev-mock 创建任务时写入。
|
||||||
|
- **三处平台调用短路**(mock 任务不请求真实 affiliate-dash):
|
||||||
|
- `syncAffiliateDashTaskStatus`:直接用本地快照(flow 自身 orderStatus/failureReason)收敛状态,不走 `getAffiliateDashOrder`;
|
||||||
|
- `refreshAffiliateDashBindState`(claim-service):直接返回 task,不查 bind-result;
|
||||||
|
- `submitAffiliateDashClaim`:直接置 `delivered` + `providerOrderNo=MOCKAD-*`,不调 `submitAffiliateDashDelivery`。
|
||||||
|
- **dev-mock-service**:`createAffiliateDashMockClaim`(仿 feifei:建 order/item/task + claim token + mock 上下文);`getDevMockStatus` platforms 增补;`createBaseOrderItem.executor` 支持 `'affiliate_dash'`。
|
||||||
|
- **路由/前端**:`POST /api/v1/admin/dev-mock/affiliate-dash`;Mock 页新增 Card + `AffiliateDashMockForm`(step/uid/productName/productSku/orderNo)。
|
||||||
|
|
||||||
|
**验证**:后端 typecheck 通过、224 测试全绿;前端 `tsc -b --noEmit` 通过;`normalizeAffiliateDashFlow` mock 字段透传脚本验证通过(mock 保留 / 非 mock 为 null)。
|
||||||
|
|||||||
Reference in New Issue
Block a user