feat(admin): 彻底优化任务列表与详情界面,集成到账存证凭单与联盟直充面板

- 优化任务列表角色列:支持从 affiliateDash 及 claimIdentity 中自动提取并展示角色名与 UID(含一键复制)
- 列表操作栏支持一键查看到账存证凭单,支持直接复制图片发给买家或下载保存
- 任务详情页全面现代化重构:
  - 新增「兑换成功 · 官方直充到账存证凭据」大卡片,实时生成 640x960 Retina 高清卡片,支持一键写入系统剪贴板(微信/QQ/客服窗口直接 Ctrl+V 发送)、下载 PNG 大图与全屏预览
  - 新增「联盟直充履约 (affiliate_dash)」专属面板,结构化展示上游订单号、直充状态、点数积分、核销状态及供应商回执
  - 优化凭据生成器 (voucher.ts):强制优先读取本地高清商品图,彻底消除上游图片文字水印与圆环破图问题
- 验证方式:npm run build & npm test (133 项单测全量通过)
This commit is contained in:
yml2213
2026-10-10 21:04:01 +08:00
parent 215f3351ba
commit f3f2550b19
7 changed files with 1136 additions and 228 deletions
@@ -139,7 +139,7 @@ export async function getAdminOrders(
}
}
function groupRowsByOrderId<T extends { order_id: number | string }>(rows: T[]): Map<number, T[]> {
function groupRowsByOrderId<T extends { order_id?: number | string | null }>(rows: T[]): Map<number, T[]> {
const grouped = new Map<number, T[]>()
for (const row of rows) {
// pg BIGINT 常以 string 返回;统一 Number 键,避免列表挂不上 order_items →「商品 0」。
@@ -431,6 +431,7 @@ export async function getAdminTaskDetail(
kuaishouCloudFulfillment,
kuaishouIndustryVoucher,
manualDispatch: mapManualDispatchContext(taskContext.manualDispatch, viewerContext),
affiliateDash: taskContext.affiliateDash || null,
events: taskEvents.map(mapAdminTaskEvent),
operations: {
canRetry:
@@ -285,9 +285,53 @@ function getTaskRetryCount(task: TaskRow | null | undefined): number {
}
function resolveTaskDisplayRoleName(task: TaskRow): string {
return String(task.kuaishou_role_name || task.role_name || '').trim()
if (task.kuaishou_role_name || task.role_name) {
return String(task.kuaishou_role_name || task.role_name).trim()
}
const context = safeParseJson(task.context_json)
const affiliateDash = context.affiliateDash
if (affiliateDash && typeof affiliateDash === 'object') {
const dash = affiliateDash as Record<string, unknown>
const name = String(dash.roleName || dash.boundRoleName || dash.game_account_role_name || '').trim()
if (name) return name
}
const kuaishouCloud = context.kuaishouCloudFulfillment
if (kuaishouCloud && typeof kuaishouCloud === 'object') {
const cloud = kuaishouCloud as Record<string, unknown>
const roleObj = cloud.role as Record<string, unknown> | undefined
if (roleObj?.name) return String(roleObj.name).trim()
}
return ''
}
function resolveTaskDisplayRoleId(task: TaskRow): string {
return String(task.kuaishou_role_id || task.role_id || '').trim()
if (task.kuaishou_role_id || task.role_id) {
return String(task.kuaishou_role_id || task.role_id).trim()
}
const context = safeParseJson(task.context_json)
const affiliateDash = context.affiliateDash
if (affiliateDash && typeof affiliateDash === 'object') {
const dash = affiliateDash as Record<string, unknown>
const id = String(
dash.boundAccount ||
dash.gameAccount ||
dash.expectedGameAccount ||
dash.game_uid ||
dash.game_account ||
'',
).trim()
if (id) return id
}
const claimIdentity = context.claimIdentity
if (claimIdentity && typeof claimIdentity === 'object') {
const id = String((claimIdentity as Record<string, unknown>).expectedUid || '').trim()
if (id) return id
}
const kuaishouCloud = context.kuaishouCloudFulfillment
if (kuaishouCloud && typeof kuaishouCloud === 'object') {
const cloud = kuaishouCloud as Record<string, unknown>
const roleObj = cloud.role as Record<string, unknown> | undefined
if (roleObj?.rid) return String(roleObj.rid).trim()
}
return ''
}
@@ -20,6 +20,7 @@ export type AdminTaskDetailView = {
kuaishouCloudFulfillment: JsonObject | null
kuaishouIndustryVoucher: JsonObject | null
manualDispatch: JsonObject | null
affiliateDash: JsonObject | null
events: JsonObject[]
operations: JsonObject
}