diff --git a/apps/backend/src/services/admin/admin-read-service.ts b/apps/backend/src/services/admin/admin-read-service.ts index 57db7e79..5d44c2b5 100644 --- a/apps/backend/src/services/admin/admin-read-service.ts +++ b/apps/backend/src/services/admin/admin-read-service.ts @@ -4,6 +4,7 @@ import { listOrderItemsByOrderId } from '../../repositories/order-item-repo.js' import { getOrderById, listOrders } from '../../repositories/order-repo.js' import { getTaskById, listTasks, listTasksByOrderId } from '../../repositories/task-repo.js' import { listTaskEventsByTaskId } from '../../repositories/task-event-repo.js' +import { listCloudtentaclesSources } from '../platforms/cloudtentacles/source-config-service.js' import { createHttpError } from '../../utils/http.js' import { formatFenToAmount, normalizeFen } from '../../utils/money.js' import { normalizeDateQuery, normalizePage, normalizePageSize, safeParseJson } from './admin-query-utils.js' @@ -171,7 +172,10 @@ export async function getAdminTaskDetail( const viewerContext = createAdminViewerContext(session) const claimUrl = claimToken ? buildClaimUrl(claimToken.token) : '' const screenshotUrl = await resolveAdminTaskScreenshotUrl(task, viewerContext) - const kuaishouCloudFulfillment = mapKuaishouCloudFulfillmentContext(taskContext.kuaishouCloudFulfillment) + const kuaishouCloudFulfillment = mapKuaishouCloudFulfillmentContext( + taskContext.kuaishouCloudFulfillment, + { cloudSourceLabelMap: buildCloudSourceLabelMap() }, + ) return { task: mapAdminTaskListItem({ @@ -248,6 +252,20 @@ export async function getAdminTaskDetail( } } +function buildCloudSourceLabelMap() { + const labelMap = new Map() + + for (const source of listCloudtentaclesSources().sources) { + const key = String(source.key || '').trim() + if (!key) continue + + const label = String(source.label || source.username || key).trim() || key + labelMap.set(key, label) + } + + return labelMap +} + export async function getAdminTaskScreenshotPath( taskId: AdminEntityIdInput, session: AdminViewerSessionInput | null = null, diff --git a/apps/backend/src/services/admin/admin-read-shared-helpers.test.ts b/apps/backend/src/services/admin/admin-read-shared-helpers.test.ts index 697ac409..5a1496ae 100644 --- a/apps/backend/src/services/admin/admin-read-shared-helpers.test.ts +++ b/apps/backend/src/services/admin/admin-read-shared-helpers.test.ts @@ -4,6 +4,7 @@ import assert from 'node:assert/strict' import { canViewerCloseTask, createAdminViewerContext, + mapKuaishouCloudFulfillmentContext, resolveAdminTaskScreenshotUrl, } from './admin-read-shared-helpers.js' @@ -37,3 +38,24 @@ test('canViewerCloseTask allows support to close active task but not redeemed ta assert.equal(canViewerCloseTask({ task_status: 'link_generated' }, viewerContext), true) assert.equal(canViewerCloseTask({ task_status: 'redeemed' }, viewerContext), false) }) + +test('mapKuaishouCloudFulfillmentContext exposes cloud account display labels', () => { + const flow = mapKuaishouCloudFulfillmentContext( + { + binding: { + cloudSourceKeys: [' account_a ', 'account_b'], + resolvedSourceKey: 'account_b', + }, + }, + { + cloudSourceLabelMap: new Map([ + ['account_a', '白班账号'], + ['account_b', '夜班账号'], + ]), + }, + ) + + assert.deepEqual(flow?.binding.cloudSourceKeys, ['account_a', 'account_b']) + assert.deepEqual(flow?.binding.cloudSourceLabels, ['白班账号', '夜班账号']) + assert.equal(flow?.binding.resolvedSourceLabel, '夜班账号') +}) diff --git a/apps/backend/src/services/admin/admin-read-shared-helpers.ts b/apps/backend/src/services/admin/admin-read-shared-helpers.ts index 5e419522..8415442b 100644 --- a/apps/backend/src/services/admin/admin-read-shared-helpers.ts +++ b/apps/backend/src/services/admin/admin-read-shared-helpers.ts @@ -9,6 +9,11 @@ import type { AdminViewerSessionInput } from '../../types/admin/read-inputs.js' import type { OrderItemRow, TaskRow } from '../../types/repository/rows.js' type JsonRecord = Record +type CloudSourceLabelMap = Map | Record + +type KuaishouCloudFulfillmentMapOptions = { + cloudSourceLabelMap?: CloudSourceLabelMap +} type TaskLike = Partial & { state_json?: string | JsonRecord @@ -55,7 +60,10 @@ export function mapManualDispatchContext( } } -export function mapKuaishouCloudFulfillmentContext(value: unknown): JsonRecord | null { +export function mapKuaishouCloudFulfillmentContext( + value: unknown, + options: KuaishouCloudFulfillmentMapOptions = {}, +): JsonRecord | null { if (!value || typeof value !== 'object') { return null } @@ -68,6 +76,10 @@ export function mapKuaishouCloudFulfillmentContext(value: unknown): JsonRecord | const dispatch = record.dispatch && typeof record.dispatch === 'object' ? record.dispatch : {} const returnNumber = record.returnNumber && typeof record.returnNumber === 'object' ? record.returnNumber : {} const consume = record.consume && typeof record.consume === 'object' ? record.consume : {} + const cloudSourceKeys = Array.isArray(binding.cloudSourceKeys) + ? binding.cloudSourceKeys.map((value: unknown) => String(value || '').trim()).filter(Boolean) + : [] + const resolvedSourceKey = String(binding.resolvedSourceKey || '').trim() return { flowType: String(record.flowType || '').trim(), @@ -84,10 +96,14 @@ export function mapKuaishouCloudFulfillmentContext(value: unknown): JsonRecord | }, binding: { prepareStatus: String(binding.prepareStatus || 'pending').trim() || 'pending', - cloudSourceKeys: Array.isArray(binding.cloudSourceKeys) - ? binding.cloudSourceKeys.map((value: unknown) => String(value || '').trim()).filter(Boolean) - : [], - resolvedSourceKey: String(binding.resolvedSourceKey || '').trim(), + cloudSourceKeys, + cloudSourceLabels: cloudSourceKeys.map((sourceKey: string) => + resolveCloudSourceLabel(sourceKey, options.cloudSourceLabelMap), + ), + resolvedSourceKey, + resolvedSourceLabel: resolvedSourceKey + ? resolveCloudSourceLabel(resolvedSourceKey, options.cloudSourceLabelMap) + : '', skuId: Number(binding.skuId || 0) || 0, skuName: String(binding.skuName || '').trim(), vnKey: String(binding.vnKey || '').trim(), @@ -140,6 +156,17 @@ export function mapKuaishouCloudFulfillmentContext(value: unknown): JsonRecord | } } +function resolveCloudSourceLabel(sourceKey: string, labelMap: CloudSourceLabelMap | undefined) { + const key = String(sourceKey || '').trim() + if (!key) return '' + + const label = labelMap instanceof Map + ? labelMap.get(key) + : labelMap?.[key] + + return String(label || key).trim() || key +} + export function mapRedeemResolutionContext(value: unknown): JsonRecord | null { if (!value || typeof value !== 'object') { return null diff --git a/apps/frontend/src/types/admin/tasks.ts b/apps/frontend/src/types/admin/tasks.ts index 4994f88d..cd478812 100644 --- a/apps/frontend/src/types/admin/tasks.ts +++ b/apps/frontend/src/types/admin/tasks.ts @@ -127,7 +127,9 @@ export interface AdminTaskDetail { binding: { prepareStatus: string cloudSourceKeys: string[] + cloudSourceLabels: string[] resolvedSourceKey: string + resolvedSourceLabel: string skuId: number skuName: string vnKey: string diff --git a/apps/frontend/src/views/admin/tasks/components/AdminTaskKuaishouCloudPanel.vue b/apps/frontend/src/views/admin/tasks/components/AdminTaskKuaishouCloudPanel.vue index 5fe10ed4..a2745b36 100644 --- a/apps/frontend/src/views/admin/tasks/components/AdminTaskKuaishouCloudPanel.vue +++ b/apps/frontend/src/views/admin/tasks/components/AdminTaskKuaishouCloudPanel.vue @@ -34,6 +34,30 @@ const emit = defineEmits<{ copyBindUrl: [url: string] openBindUrl: [url: string] }>() + +function formatCloudSourcePriority(flow: KuaishouCloudFlow) { + return formatAccountNames(flow.binding.cloudSourceLabels, flow.binding.cloudSourceKeys) +} + +function formatResolvedCloudSource(flow: KuaishouCloudFlow) { + return flow.binding.resolvedSourceLabel || flow.binding.resolvedSourceKey || '-' +} + +function formatAccountNames(labels: string[] | undefined, keys: string[] | undefined) { + const displayNames = Array.isArray(labels) + ? labels.map((value) => String(value || '').trim()).filter(Boolean) + : [] + + if (displayNames.length > 0) { + return displayNames.join(' / ') + } + + const fallbackKeys = Array.isArray(keys) + ? keys.map((value) => String(value || '').trim()).filter(Boolean) + : [] + + return fallbackKeys.length > 0 ? fallbackKeys.join(' / ') : '-' +}