Phase 1 - 消灭巨石页面: - 拆分 AdminKuaishouCloudFulfillmentView (1,224行→kuaishou-cloud/子目录) - 拆分 AdminFulfillmentBindingsView (989行→bindings/子目录) - 拆分 AdminTaskDetailView (1,007行→9个子组件+2个composable) - 合并去重 useClaimPage + useAdminManualRedeemPage (1,554行→共享模块+差异化薄层) - 拆分 services/admin/platform-config.ts (477行→8个领域子模块) Phase 2 - 架构收口: - 拆分 types/admin.ts (925行→13个子文件+platform-config/子目录) - 统一 API code 检查(http.ts拦截器统一处理业务错误) - 修改 apiPost 签名消除 as unknown as 类型断言(6处) - 新增 BusinessError 类型便于错误分类处理 所有改动通过 vue-tsc --noEmit 零错误和 vite build 验证
245 lines
7.0 KiB
TypeScript
245 lines
7.0 KiB
TypeScript
import { computed, type ComputedRef, type Ref } from 'vue'
|
|
|
|
import type { ClaimDetailData } from '@/types/claim'
|
|
import type { TencentLoginType } from '@/types/tencent/session'
|
|
|
|
import { DEFAULT_LOGIN_TYPE } from './sessionConstants'
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// syncLoginTypeFromDetail
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function syncLoginTypeFromDetail<T extends ClaimDetailData>(
|
|
detail: T,
|
|
fallback: TencentLoginType = DEFAULT_LOGIN_TYPE,
|
|
): TencentLoginType {
|
|
const nextLoginType = String(detail.session?.loginType || detail.task.loginType || '').trim()
|
|
if (nextLoginType === 'wx') {
|
|
return 'wx'
|
|
}
|
|
|
|
if (nextLoginType === 'qq') {
|
|
return 'qq'
|
|
}
|
|
|
|
return fallback
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// resolveTaskStatusLabel
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export type TaskStatusLabelContext = 'claim' | 'admin'
|
|
|
|
/**
|
|
* Resolves a human-readable task status label.
|
|
*
|
|
* The `context` parameter controls wording differences between the
|
|
* consumer-facing claim page and the admin manual-redeem page.
|
|
*/
|
|
export function resolveTaskStatusLabel(
|
|
taskStatus: string | undefined,
|
|
sessionStatus: string | undefined,
|
|
context: TaskStatusLabelContext,
|
|
): string {
|
|
switch (taskStatus) {
|
|
case 'link_generated':
|
|
return context === 'admin' ? '待生成二维码' : '等待开始领取'
|
|
case 'claimed':
|
|
if (sessionStatus === 'scanned') {
|
|
return context === 'admin' ? '客户待确认' : '确认中'
|
|
}
|
|
if (sessionStatus === 'logged_in' || sessionStatus === 'ready_to_redeem') {
|
|
return context === 'admin' ? '已登录待复核' : '已登录'
|
|
}
|
|
return context === 'admin' ? '等待客户登录' : '领取中'
|
|
case 'role_confirmed':
|
|
return '角色已确认'
|
|
case 'redeeming':
|
|
return '正在兑换'
|
|
case 'redeemed':
|
|
return context === 'admin' ? '兑换完成' : '兑换成功'
|
|
case 'waiting_inventory':
|
|
return '等待库存'
|
|
case 'retry_pending':
|
|
return '等待重试'
|
|
case 'manual_review':
|
|
return '等待人工处理'
|
|
case 'expired':
|
|
return '链接已过期'
|
|
case 'closed':
|
|
return '任务已关闭'
|
|
default:
|
|
return sessionStatus || taskStatus || '等待中'
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// roleFacts / resultFacts helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface FactItem {
|
|
label: string
|
|
value: string
|
|
accent?: boolean
|
|
}
|
|
|
|
export interface UseSessionPresentationOptions {
|
|
activityInfo: ComputedRef<{
|
|
nickname?: string
|
|
role?: {
|
|
roleId?: string
|
|
roleName?: string
|
|
area?: string
|
|
ready?: boolean
|
|
} | null
|
|
} | null>
|
|
statusLabel: ComputedRef<string>
|
|
/** Context-specific default values for roleFacts */
|
|
roleFactDefaults: {
|
|
nickname: string
|
|
}
|
|
}
|
|
|
|
export function useSessionRoleFacts(options: UseSessionPresentationOptions) {
|
|
const { activityInfo, statusLabel, roleFactDefaults } = options
|
|
|
|
const roleFacts = computed<FactItem[]>(() => [
|
|
{
|
|
label: '登录昵称',
|
|
value: activityInfo.value?.nickname || roleFactDefaults.nickname,
|
|
},
|
|
{
|
|
label: '当前角色',
|
|
value: activityInfo.value?.role?.roleName || '后端浏览器同步中',
|
|
accent: true,
|
|
},
|
|
{
|
|
label: '角色 ID',
|
|
value: activityInfo.value?.role?.roleId || '未识别',
|
|
},
|
|
{
|
|
label: '所在大区',
|
|
value: activityInfo.value?.role?.area || '未识别',
|
|
},
|
|
])
|
|
|
|
return { roleFacts }
|
|
}
|
|
|
|
export interface UseSessionResultFactsOptions {
|
|
order: ComputedRef<{ platformOrderId?: string } | null>
|
|
orderItem: ComputedRef<{ skuName?: string } | null>
|
|
result: ComputedRef<{ resultCode?: string; resultMessage?: string } | null>
|
|
/** Admin context has manualRequest.proofValue as an alternative first label */
|
|
resultFactOverrides?: {
|
|
firstLabel?: string
|
|
firstValue?: ComputedRef<string>
|
|
}
|
|
}
|
|
|
|
export function useSessionResultFacts(options: UseSessionResultFactsOptions) {
|
|
const { order, orderItem, result, resultFactOverrides } = options
|
|
|
|
const resultFacts = computed<FactItem[]>(() => [
|
|
{
|
|
label: resultFactOverrides?.firstLabel || '订单号',
|
|
value: resultFactOverrides?.firstValue?.value || order.value?.platformOrderId || '-',
|
|
},
|
|
{
|
|
label: '商品',
|
|
value: orderItem.value?.skuName || '-',
|
|
},
|
|
{
|
|
label: '业务返回码',
|
|
value: result.value?.resultCode || '-',
|
|
},
|
|
{
|
|
label: '业务消息',
|
|
value: result.value?.resultMessage || '尚未兑换',
|
|
},
|
|
])
|
|
|
|
return { resultFacts }
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// redeemBlockedReason — builds the "why can't I redeem" explanation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function buildRedeemBlockedReason(options: {
|
|
detail: ComputedRef<unknown | null>
|
|
hasSession: ComputedRef<boolean>
|
|
loginTypeLabel: ComputedRef<string>
|
|
redeemLoading: ComputedRef<boolean>
|
|
roleReady: ComputedRef<boolean>
|
|
roleConfirmed: ComputedRef<boolean>
|
|
sessionNotice: ComputedRef<string>
|
|
context: 'claim' | 'admin'
|
|
/** Claim-specific fields */
|
|
tokenStatus?: ComputedRef<string>
|
|
requiresSupportReview?: ComputedRef<boolean>
|
|
}): string {
|
|
const {
|
|
detail,
|
|
hasSession,
|
|
loginTypeLabel,
|
|
redeemLoading,
|
|
roleReady,
|
|
roleConfirmed,
|
|
sessionNotice,
|
|
context,
|
|
tokenStatus,
|
|
requiresSupportReview,
|
|
} = options
|
|
|
|
if (context === 'claim') {
|
|
if (!detail.value) {
|
|
return '正在加载领取信息'
|
|
}
|
|
if (tokenStatus && tokenStatus.value !== 'active') {
|
|
return '当前领取链接不可用'
|
|
}
|
|
} else {
|
|
// admin context
|
|
if (!detail.value) {
|
|
return '请先创建人工兑换任务并预占库存'
|
|
}
|
|
}
|
|
|
|
if (!hasSession.value) {
|
|
return context === 'claim'
|
|
? `请先选择${loginTypeLabel.value}并初始化登录会话`
|
|
: `请先生成${loginTypeLabel.value}二维码并等待客户登录`
|
|
}
|
|
|
|
// Claim-specific: support review flow
|
|
if (context === 'claim' && requiresSupportReview?.value) {
|
|
if (!roleReady.value) {
|
|
return '扫码成功后,系统会自动同步登录信息,准备发给客服复核'
|
|
}
|
|
return '当前商品需要客服复核角色并代你发起兑换,请联系人工继续'
|
|
}
|
|
|
|
if (redeemLoading.value) {
|
|
return '兑换任务正在执行中'
|
|
}
|
|
|
|
if (!roleReady.value) {
|
|
return (
|
|
sessionNotice.value ||
|
|
(context === 'claim'
|
|
? '扫码成功后,后端正在同步角色和大区信息'
|
|
: '客户登录后,系统会自动同步角色和大区信息')
|
|
)
|
|
}
|
|
|
|
if (!roleConfirmed.value) {
|
|
return context === 'claim'
|
|
? '请先确认当前角色与大区无误,再开始兑换'
|
|
: '请先让客户确认角色截图,再点击确认当前角色'
|
|
}
|
|
|
|
return ''
|
|
}
|