From f07a4f8dcb5f2f66c5f0ef50e566b72eae258240 Mon Sep 17 00:00:00 2001 From: yml Date: Sun, 3 May 2026 23:03:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B1=A5=E7=BA=A6=E9=85=8D=E7=BD=AE=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/data/khhao-sync-state.json | 12 +- apps/backend/src/routes/admin/tasks.js | 26 + .../admin/admin-platform-config-service.js | 22 + .../src/services/admin/admin-read-service.js | 3 + .../admin/admin-read-shared-helpers.js | 10 + .../src/services/admin/admin-write-service.js | 101 + .../services/claim/claim-session-service.js | 2 + .../claim/kuaishou-cloud-claim-service.js | 1 + .../services/order/delivery-task-service.js | 9 + ...aishou-cloud-fulfillment-config-service.js | 2 + .../platforms/cloudtentacles/shared.js | 2 + .../cloudtentacles/virtual-number-service.js | 38 +- .../platforms/khhao/auto-sync-service.js | 92 +- .../platforms/khhao/auto-sync-service.test.js | 79 + .../platforms/khhao/order-sync-service.js | 21 +- .../platforms/khhao/sync-state-service.js | 37 + apps/backend/src/types/admin-write-inputs.js | 1 + apps/frontend/src/components.d.ts | 2 + apps/frontend/src/router/index.ts | 4 +- apps/frontend/src/services/admin.ts | 7 +- apps/frontend/src/types/admin.ts | 43 + .../admin/AdminFulfillmentBindingsView.vue | 180 +- .../admin/AdminFulfillmentConfigHubView.vue | 135 ++ .../AdminKuaishouCloudFulfillmentView.vue | 2062 ++++++++++++++--- apps/frontend/src/views/admin/AdminLayout.vue | 1 - .../views/admin/AdminPlatformShopsView.vue | 8 + .../src/views/admin/AdminTaskDetailView.vue | 500 +++- 27 files changed, 2767 insertions(+), 633 deletions(-) create mode 100644 apps/backend/src/services/platforms/khhao/auto-sync-service.test.js create mode 100644 apps/frontend/src/views/admin/AdminFulfillmentConfigHubView.vue diff --git a/apps/backend/data/khhao-sync-state.json b/apps/backend/data/khhao-sync-state.json index 5feede57..9bff21f7 100644 --- a/apps/backend/data/khhao-sync-state.json +++ b/apps/backend/data/khhao-sync-state.json @@ -1,11 +1,13 @@ { "syncFromCreatedAt": "2026-05-02T10:53:28.550Z", - "lastRunStartedAt": "2026-05-03T12:57:26.689Z", - "lastRunFinishedAt": "2026-05-03T12:57:27.309Z", + "lastRunStartedAt": "2026-05-03T15:01:47.533Z", + "lastRunFinishedAt": "2026-05-03T15:01:48.438Z", "lastRunStatus": "success", "lastErrorMessage": "", "fetchedCount": 40, - "syncedCount": 1, - "ignoredCount": 39, - "lastOrderCreatedAt": "2026-05-03T12:57:20.000Z" + "syncedCount": 0, + "ignoredCount": 40, + "lastOrderCreatedAt": "2026-05-03T15:01:23.000Z", + "watchMode": "normal", + "watchOrders": [] } diff --git a/apps/backend/src/routes/admin/tasks.js b/apps/backend/src/routes/admin/tasks.js index 7f34ceb6..2284752e 100644 --- a/apps/backend/src/routes/admin/tasks.js +++ b/apps/backend/src/routes/admin/tasks.js @@ -9,6 +9,7 @@ import { dispatchAdminTaskKuaishouCloudFulfillment, markAdminTaskManualReview, prepareAdminTaskKuaishouCloudFulfillment, + refreshAdminTaskKuaishouCloudRoleInfo, regenerateAdminTaskClaimLink, redeemAdminTaskAssisted, releaseAdminTaskInventoryBinding, @@ -299,6 +300,31 @@ router.post('/tasks/:taskId/kuaishou-cloud/prepare', requireAdminRoles(['admin', }, )) +router.post('/tasks/:taskId/kuaishou-cloud/refresh-role-info', requireAdminRoles(['admin', 'operator', 'support']), createJsonHandler( + (req) => refreshAdminTaskKuaishouCloudRoleInfo( + (/** @type {AdminTaskRouteParams} */ (req.params)).taskId, + /** @type {AdminRouteAdminSession | null} */ (req.adminSession), + ), + { + successMessage: '角色信息已刷新', + errorMessage: '刷新角色信息失败', + scope: '[admin/tasks/:taskId/kuaishou-cloud/refresh-role-info]', + audit: (req, data) => { + const result = /** @type {AdminTaskActionResponse} */ (data) + return { + action: 'task_kuaishou_cloud_role_info_refreshed', + targetType: 'task', + targetId: String((/** @type {AdminTaskRouteParams} */ (req.params)).taskId), + data: { + taskId: result.task.taskId, + taskNo: result.task.taskNo, + status: result.task.status, + }, + } + }, + }, +)) + router.post('/tasks/:taskId/kuaishou-cloud/dispatch', requireAdminRoles(['admin', 'operator']), createJsonHandler( (req) => dispatchAdminTaskKuaishouCloudFulfillment( (/** @type {AdminTaskRouteParams} */ (req.params)).taskId, diff --git a/apps/backend/src/services/admin/admin-platform-config-service.js b/apps/backend/src/services/admin/admin-platform-config-service.js index 9d1deef7..b60be883 100644 --- a/apps/backend/src/services/admin/admin-platform-config-service.js +++ b/apps/backend/src/services/admin/admin-platform-config-service.js @@ -281,6 +281,16 @@ export function getAdminKhhaoSourceConfig() { fetchedCount: Number(syncState.fetchedCount || 0), syncedCount: Number(syncState.syncedCount || 0), ignoredCount: Number(syncState.ignoredCount || 0), + watchMode: String(syncState.watchMode || 'normal').trim() || 'normal', + watchOrders: Array.isArray(syncState.watchOrders) ? syncState.watchOrders.map((item) => ({ + platformOrderId: String(item?.platformOrderId || '').trim(), + shopId: String(item?.shopId || '').trim(), + shopName: String(item?.shopName || '').trim(), + itemTitle: String(item?.itemTitle || '').trim(), + payStatus: String(item?.payStatus || '').trim(), + detectedAt: String(item?.detectedAt || '').trim(), + lastSeenAt: String(item?.lastSeenAt || '').trim(), + })) : [], }, } } @@ -358,6 +368,16 @@ export function updateAdminKhhaoSourceConfig(payload = /** @type {AdminKhhaoSour fetchedCount: Number(syncState.fetchedCount || 0), syncedCount: Number(syncState.syncedCount || 0), ignoredCount: Number(syncState.ignoredCount || 0), + watchMode: String(syncState.watchMode || 'normal').trim() || 'normal', + watchOrders: Array.isArray(syncState.watchOrders) ? syncState.watchOrders.map((item) => ({ + platformOrderId: String(item?.platformOrderId || '').trim(), + shopId: String(item?.shopId || '').trim(), + shopName: String(item?.shopName || '').trim(), + itemTitle: String(item?.itemTitle || '').trim(), + payStatus: String(item?.payStatus || '').trim(), + detectedAt: String(item?.detectedAt || '').trim(), + lastSeenAt: String(item?.lastSeenAt || '').trim(), + })) : [], }, } } @@ -814,6 +834,7 @@ export function getAdminKuaishouCloudFulfillmentConfig() { autoReturnNumberAfterDispatch: item.autoReturnNumberAfterDispatch === true, autoConsumeAfterDispatch: item.autoConsumeAfterDispatch === true, kuaishouConsumeShopId: String(item.kuaishouConsumeShopId || '').trim(), + kuaishouConsumeShopName: String(item.kuaishouConsumeShopName || '').trim(), notes: String(item.notes || '').trim(), })), }, @@ -857,6 +878,7 @@ export async function updateAdminKuaishouCloudFulfillmentConfig( autoReturnNumberAfterDispatch: item.autoReturnNumberAfterDispatch === true, autoConsumeAfterDispatch: item.autoConsumeAfterDispatch === true, kuaishouConsumeShopId: String(item.kuaishouConsumeShopId || '').trim(), + kuaishouConsumeShopName: String(item.kuaishouConsumeShopName || '').trim(), notes: String(item.notes || '').trim(), })), }, diff --git a/apps/backend/src/services/admin/admin-read-service.js b/apps/backend/src/services/admin/admin-read-service.js index 7ecf7353..ea8ec21b 100644 --- a/apps/backend/src/services/admin/admin-read-service.js +++ b/apps/backend/src/services/admin/admin-read-service.js @@ -297,6 +297,9 @@ export async function getAdminTaskDetail(taskId, session = null) { && String(kuaishouCloudFulfillment?.dispatch.status || 'pending').trim() === 'pending' && String(kuaishouCloudFulfillment?.returnNumber.status || 'pending').trim() === 'pending' && ['pending_binding_prepare', 'manual_review', 'failed'].includes(String(task.task_status || '').trim()), + canRefreshKuaishouCloudRoleInfo: viewerContext.canOperateAssistedTask + && String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' + && Number(kuaishouCloudFulfillment?.binding.vnId || 0) > 0, canDispatchKuaishouCloudFulfillment: viewerContext.canManageTaskLifecycle && String(task.executor_key || '').trim() === 'kuaishou_ct_assisted' && ['waiting_binding'].includes(String(task.task_status || '').trim()), diff --git a/apps/backend/src/services/admin/admin-read-shared-helpers.js b/apps/backend/src/services/admin/admin-read-shared-helpers.js index 5c694442..8a7b27d8 100644 --- a/apps/backend/src/services/admin/admin-read-shared-helpers.js +++ b/apps/backend/src/services/admin/admin-read-shared-helpers.js @@ -32,6 +32,7 @@ export function mapKuaishouCloudFulfillmentContext(value) { const ticket = value.ticket && typeof value.ticket === 'object' ? value.ticket : {} const binding = value.binding && typeof value.binding === 'object' ? value.binding : {} + const role = value.role && typeof value.role === 'object' ? value.role : {} const purchase = value.purchase && typeof value.purchase === 'object' ? value.purchase : {} const dispatch = value.dispatch && typeof value.dispatch === 'object' ? value.dispatch : {} const returnNumber = value.returnNumber && typeof value.returnNumber === 'object' ? value.returnNumber : {} @@ -61,6 +62,14 @@ export function mapKuaishouCloudFulfillmentContext(value) { bindUrl: String(binding.bindUrl || '').trim(), bindPreparedAt: binding.bindPreparedAt || null, }, + role: { + status: String(role.status || 'pending').trim() || 'pending', + name: String(role.name || '').trim(), + rid: String(role.rid || '').trim(), + refreshedAt: role.refreshedAt || null, + errorMessage: String(role.errorMessage || '').trim(), + rawInfo: role.rawInfo && typeof role.rawInfo === 'object' ? role.rawInfo : null, + }, purchase: { autoBuyEnabled: purchase.autoBuyEnabled !== false, minAssetReserve: Number(purchase.minAssetReserve || 0) || 0, @@ -84,6 +93,7 @@ export function mapKuaishouCloudFulfillmentContext(value) { consume: { status: String(consume.status || 'pending').trim() || 'pending', shopId: String(consume.shopId || '').trim(), + shopName: String(consume.shopName || '').trim(), autoConsumeEnabled: consume.autoConsumeEnabled === true, consumedAt: consume.consumedAt || null, errorMessage: String(consume.errorMessage || '').trim(), diff --git a/apps/backend/src/services/admin/admin-write-service.js b/apps/backend/src/services/admin/admin-write-service.js index a7f67651..592a74da 100644 --- a/apps/backend/src/services/admin/admin-write-service.js +++ b/apps/backend/src/services/admin/admin-write-service.js @@ -40,6 +40,7 @@ import { import { appointCloudtentaclesVirtualNumber, backCloudtentaclesVirtualNumber, + getCloudtentaclesBindInfo, fetchCloudtentaclesVirtualNumberCode, generateCloudtentaclesLoginCode, getCloudtentaclesBindUrl, @@ -973,6 +974,85 @@ export async function dispatchAdminTaskKuaishouCloudFulfillment(taskId, payload } } +/** @returns {Promise} */ +/** @param {AdminEntityIdInput} taskId */ +/** @param {AdminViewerSessionInput | null} [session] */ +export async function refreshAdminTaskKuaishouCloudRoleInfo(taskId, session = null) { + const task = await getRequiredTask(taskId) + const now = nowIso() + const viewerContext = createAdminViewerContext(session) + + if (!viewerContext.canOperateAssistedTask) { + throw createHttpError('当前账号没有权限刷新角色信息', { + statusCode: 403, + errorCode: 'admin_task_refresh_kuaishou_cloud_role_forbidden', + }) + } + + if (!isKuaishouCloudTask(task)) { + throw createHttpError('当前任务不是快手 cloud 履约任务', { + statusCode: 409, + errorCode: 'admin_task_not_kuaishou_cloud', + }) + } + + const taskContext = parseTaskContext(task) + const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment) + if (!flow.binding.vnId || !flow.binding.vnKey) { + throw createHttpError('当前任务还没有可查询的绑定角色信息,请先准备绑定资源', { + statusCode: 409, + errorCode: 'admin_task_kuaishou_cloud_missing_bind_info_context', + }) + } + + const cloudContext = resolvePersistedCloudtentaclesContext() + const bindInfoResult = await getCloudtentaclesBindInfo({ + ...cloudContext, + key: flow.binding.vnKey, + id: flow.binding.vnId, + }) + + const bindInfo = normalizeKuaishouCloudRoleInfo(bindInfoResult.bindInfo) + const nextContext = { + ...taskContext, + kuaishouCloudFulfillment: { + ...flow, + role: { + status: bindInfo.name || bindInfo.rid ? 'ready' : 'pending', + name: bindInfo.name, + rid: bindInfo.rid, + refreshedAt: now, + errorMessage: bindInfo.name || bindInfo.rid ? '' : '当前还没有查询到角色信息,请让客户完成绑定后再刷新', + rawInfo: bindInfo.rawInfo, + }, + }, + } + + const updatedTask = await updateTask(task.id, { + role_id: bindInfo.rid || task.role_id || '', + role_name: bindInfo.name || task.role_name || '', + context_json: JSON.stringify(nextContext), + updated_at: now, + }) + + await createTaskEvent(task.id, 'kuaishou_cloud_role_info_refreshed', { + roleName: bindInfo.name, + roleId: bindInfo.rid, + vnId: flow.binding.vnId, + refreshedBy: session + ? { + userId: Number(session.userId || 0) || 0, + username: String(session.username || '').trim(), + role: String(session.role || '').trim(), + } + : null, + }, now) + + return { + task: mapTaskActionPayload(updatedTask), + } +} + /** @returns {Promise} */ /** @param {AdminEntityIdInput} taskId */ /** @param {AdminViewerSessionInput | null} [session] */ @@ -1085,6 +1165,7 @@ export async function returnNumberAdminTaskKuaishouCloudFulfillment(taskId, sess ...flow.consume, status: consumeStatus, shopId: shopId || flow.consume.shopId, + shopName: String(flow.consume.shopName || order?.shop_name || '').trim(), autoConsumeEnabled: flow.consume.autoConsumeEnabled === true, consumedAt, errorMessage: consumeErrorMessage, @@ -1202,6 +1283,7 @@ function resolvePersistedCloudtentaclesContext() { function normalizeKuaishouCloudFlow(value) { const source = value && typeof value === 'object' ? value : {} const binding = source.binding && typeof source.binding === 'object' ? source.binding : {} + const role = source.role && typeof source.role === 'object' ? source.role : {} const purchase = source.purchase && typeof source.purchase === 'object' ? source.purchase : {} const dispatch = source.dispatch && typeof source.dispatch === 'object' ? source.dispatch : {} const returnNumber = source.returnNumber && typeof source.returnNumber === 'object' ? source.returnNumber : {} @@ -1235,6 +1317,14 @@ function normalizeKuaishouCloudFlow(value) { bindUrl: String(binding.bindUrl || '').trim(), bindPreparedAt: binding.bindPreparedAt || null, }, + role: { + status: String(role.status || 'pending').trim() || 'pending', + name: String(role.name || '').trim(), + rid: String(role.rid || '').trim(), + refreshedAt: role.refreshedAt || null, + errorMessage: String(role.errorMessage || '').trim(), + rawInfo: role.rawInfo && typeof role.rawInfo === 'object' ? role.rawInfo : null, + }, purchase: { autoBuyEnabled: purchase.autoBuyEnabled !== false, minAssetReserve: Number(purchase.minAssetReserve || 0) || 0, @@ -1260,6 +1350,7 @@ function normalizeKuaishouCloudFlow(value) { consume: { status: String(consume.status || 'pending').trim() || 'pending', shopId: String(consume.shopId || '').trim(), + shopName: String(consume.shopName || '').trim(), autoConsumeEnabled: consume.autoConsumeEnabled === true, consumedAt: consume.consumedAt || null, errorMessage: String(consume.errorMessage || '').trim(), @@ -1267,6 +1358,16 @@ function normalizeKuaishouCloudFlow(value) { } } +function normalizeKuaishouCloudRoleInfo(value) { + const rawInfo = value && typeof value === 'object' ? value : null + + return { + name: String(rawInfo?.name || rawInfo?.roleName || rawInfo?.nickname || '').trim(), + rid: String(rawInfo?.rid || rawInfo?.roleId || rawInfo?.uid || '').trim(), + rawInfo, + } +} + function resolveKuaishouCloudBindingResources(flow, { skuItems = [], knapsackItems = [] } = {}) { const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : [] const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : [] diff --git a/apps/backend/src/services/claim/claim-session-service.js b/apps/backend/src/services/claim/claim-session-service.js index 2b3849b5..e2f7a185 100644 --- a/apps/backend/src/services/claim/claim-session-service.js +++ b/apps/backend/src/services/claim/claim-session-service.js @@ -918,6 +918,8 @@ function mapClaimKuaishouCloudFulfillment(task, order) { }, consume: { status: String(consume.status || 'pending').trim() || 'pending', + shopId: String(consume.shopId || '').trim(), + shopName: String(consume.shopName || '').trim(), consumedAt: consume.consumedAt || null, errorMessage: String(consume.errorMessage || '').trim(), }, diff --git a/apps/backend/src/services/claim/kuaishou-cloud-claim-service.js b/apps/backend/src/services/claim/kuaishou-cloud-claim-service.js index a9b9f39c..aa7592eb 100644 --- a/apps/backend/src/services/claim/kuaishou-cloud-claim-service.js +++ b/apps/backend/src/services/claim/kuaishou-cloud-claim-service.js @@ -161,6 +161,7 @@ function normalizeKuaishouCloudFlow(value) { consume: { status: String(consume.status || 'pending').trim() || 'pending', shopId: String(consume.shopId || '').trim(), + shopName: String(consume.shopName || '').trim(), }, } } diff --git a/apps/backend/src/services/order/delivery-task-service.js b/apps/backend/src/services/order/delivery-task-service.js index c99c7493..99a6d550 100644 --- a/apps/backend/src/services/order/delivery-task-service.js +++ b/apps/backend/src/services/order/delivery-task-service.js @@ -116,6 +116,14 @@ export async function syncDeliveryTasksForOrder(order, orderItems) { bindUrl: '', bindPreparedAt: null, }, + role: { + status: 'pending', + name: '', + rid: '', + refreshedAt: null, + errorMessage: '', + rawInfo: null, + }, purchase: { autoBuyEnabled: fulfillmentConfig.cloudtentacles?.autoBuyEnabled !== false, minAssetReserve: Number(fulfillmentConfig.cloudtentacles?.minAssetReserve || 0) || 0, @@ -141,6 +149,7 @@ export async function syncDeliveryTasksForOrder(order, orderItems) { consume: { status: 'pending', shopId: String(fulfillmentConfig.kuaishouConsume?.shopId || kuaishouShopConfig.shopId || '').trim(), + shopName: String(fulfillmentConfig.kuaishouConsume?.shopName || kuaishouShopConfig.kshopName || order.shop_name || '').trim(), autoConsumeEnabled: fulfillmentConfig.kuaishouConsume?.autoConsumeAfterDispatch === true, consumedAt: null, errorMessage: '', diff --git a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js index 90368b99..6347aef9 100644 --- a/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js +++ b/apps/backend/src/services/order/kuaishou-cloud-fulfillment-config-service.js @@ -52,6 +52,7 @@ export function mapKuaishouCloudFulfillmentItemsToBindings(config = {}) { }, kuaishouConsume: { shopId: String(item.kuaishouConsumeShopId || '').trim(), + shopName: String(item.kuaishouConsumeShopName || '').trim(), autoConsumeAfterDispatch: item.autoConsumeAfterDispatch === true, }, notes: String(item.notes || '').trim(), @@ -137,6 +138,7 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue) { autoReturnNumberAfterDispatch: rawValue.autoReturnNumberAfterDispatch === true, autoConsumeAfterDispatch: rawValue.autoConsumeAfterDispatch === true, kuaishouConsumeShopId: String(rawValue.kuaishouConsumeShopId || '').trim(), + kuaishouConsumeShopName: String(rawValue.kuaishouConsumeShopName || '').trim(), notes: String(rawValue.notes || '').trim(), } } diff --git a/apps/backend/src/services/platforms/cloudtentacles/shared.js b/apps/backend/src/services/platforms/cloudtentacles/shared.js index f7b64576..a584c319 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/shared.js +++ b/apps/backend/src/services/platforms/cloudtentacles/shared.js @@ -25,6 +25,7 @@ export function resolveCloudtentaclesConfig(overrides = {}) { vnVerifCodePath: '/public/vn_verif_code', vnVerifyLoginCodePath: '/vn/verify_login_code', vnBindUrlPath: '/vn/bind_url', + vnBindInfoPath: '/vn/bind_info', vnBackPath: '/vn/back', publicKeyPem: '', clientSource: 'ct-client', @@ -57,6 +58,7 @@ export function resolveCloudtentaclesConfig(overrides = {}) { '/vn/verify_login_code', ), vnBindUrlPath: normalizePath(overrides.vnBindUrlPath || baseConfig.vnBindUrlPath, '/vn/bind_url'), + vnBindInfoPath: normalizePath(overrides.vnBindInfoPath || baseConfig.vnBindInfoPath, '/vn/bind_info'), vnBackPath: normalizePath(overrides.vnBackPath || baseConfig.vnBackPath, '/vn/back'), publicKeyPem: normalizePem(overrides.publicKeyPem || baseConfig.publicKeyPem), clientSource: String(overrides.clientSource || baseConfig.clientSource || 'ct-client').trim() || 'ct-client', diff --git a/apps/backend/src/services/platforms/cloudtentacles/virtual-number-service.js b/apps/backend/src/services/platforms/cloudtentacles/virtual-number-service.js index 6167baff..b398a88c 100644 --- a/apps/backend/src/services/platforms/cloudtentacles/virtual-number-service.js +++ b/apps/backend/src/services/platforms/cloudtentacles/virtual-number-service.js @@ -159,6 +159,35 @@ export async function getCloudtentaclesBindUrl(payload = {}) { } } +export async function getCloudtentaclesBindInfo(payload = {}) { + const token = requireToken(payload.token, 'cloudtentacles 获取绑定信息缺少 token', 'cloudtentacles_vn_bind_info_missing_token') + const key = requireKey(payload.key, 'cloudtentacles 获取绑定信息缺少 key', 'cloudtentacles_vn_bind_info_missing_key') + const id = requireId(payload.id, 'cloudtentacles 获取绑定信息缺少 id', 'cloudtentacles_vn_bind_info_missing_id') + const config = resolveCloudtentaclesConfig(payload) + + const result = await cloudtentaclesRequest(config.vnBindInfoPath, { + ...config, + method: 'POST', + token, + body: [{ id, key }], + businessErrorStatusCode: 401, + businessErrorCode: 'cloudtentacles_vn_bind_info_failed', + }) + + const items = Array.isArray(result.payload?.data) ? result.payload.data : [] + const matchedItem = items.find((item) => Number(item?.id || 0) === id) || items[0] || {} + const bindInfo = parseBindInfo(matchedItem?.bind_info) + + return { + baseUrl: config.baseUrl, + key, + id, + bindInfo, + rawItem: isPlainObject(matchedItem) ? matchedItem : {}, + raw: result.payload ?? null, + } +} + export async function backCloudtentaclesVirtualNumber(payload = {}) { const token = requireToken(payload.token, 'cloudtentacles 退还号码缺少 token', 'cloudtentacles_vn_back_missing_token') const key = requireKey(payload.key, 'cloudtentacles 退还号码缺少 key', 'cloudtentacles_vn_back_missing_key') @@ -185,14 +214,14 @@ export async function backCloudtentaclesVirtualNumber(payload = {}) { function mapVirtualNumberItem(item) { const source = isPlainObject(item) ? item : {} - const bindInfoText = String(source.bind_info || '').trim() + const bindInfo = parseBindInfo(source.bind_info) return { id: Number(source.id || 0), phone: String(source.phone || '').trim(), status: Number(source.status || 0), countTime: Number(source.count_time || 0), - bindInfo: tryParseJson(bindInfoText) || bindInfoText || null, + bindInfo, raw: source, } } @@ -241,6 +270,11 @@ function tryParseJson(text) { } } +function parseBindInfo(value) { + const bindInfoText = String(value || '').trim() + return tryParseJson(bindInfoText) || bindInfoText || null +} + function isPlainObject(value) { return Boolean(value) && typeof value === 'object' && !Array.isArray(value) } diff --git a/apps/backend/src/services/platforms/khhao/auto-sync-service.js b/apps/backend/src/services/platforms/khhao/auto-sync-service.js index c12de8a5..35fab853 100644 --- a/apps/backend/src/services/platforms/khhao/auto-sync-service.js +++ b/apps/backend/src/services/platforms/khhao/auto-sync-service.js @@ -8,6 +8,8 @@ import { nowIso } from '../../../utils/time.js' let timer = null let running = false +const BOOST_INTERVAL_MIN_MS = 3_000 +const BOOST_INTERVAL_MAX_MS = 5_000 export function startKhhaoAutoSyncLoop() { scheduleNextRun(5_000) @@ -22,6 +24,7 @@ export function stopKhhaoAutoSyncLoop() { async function runKhhaoAutoSyncCycle() { const source = getKhhaoSourceConfig() + const currentState = getKhhaoSyncState() if (!source.enabled || !source.autoSync?.enabled) { scheduleNextRun(resolveIntervalMs(source.autoSync?.intervalMinutes)) @@ -30,7 +33,7 @@ async function runKhhaoAutoSyncCycle() { if (running) { logWarn('[khhao/auto-sync]', '检测到上一次自动同步仍在运行,本轮跳过') - scheduleNextRun(resolveIntervalMs(source.autoSync?.intervalMinutes)) + scheduleNextRun(resolveNextDelayMs(source, currentState)) return } @@ -63,6 +66,8 @@ async function runKhhaoAutoSyncCycle() { }) const latestCreatedAt = resolveLatestOrderCreatedAt(result.results) + const nextWatchOrders = reconcileKhhaoWatchOrders(baselineState.watchOrders, result.results, startedAt) + const watchMode = nextWatchOrders.length > 0 ? 'boosted' : 'normal' saveKhhaoSyncState({ ...getKhhaoSyncState(), @@ -75,6 +80,8 @@ async function runKhhaoAutoSyncCycle() { syncedCount: result.syncedCount, ignoredCount: result.ignoredCount, lastOrderCreatedAt: latestCreatedAt, + watchMode, + watchOrders: nextWatchOrders, }) logInfo('[khhao/auto-sync]', 'khhao 自动同步完成', { @@ -82,6 +89,13 @@ async function runKhhaoAutoSyncCycle() { syncedCount: result.syncedCount, ignoredCount: result.ignoredCount, syncFromCreatedAt: baselineState.syncFromCreatedAt, + watchMode, + watchOrderCount: nextWatchOrders.length, + watchOrders: nextWatchOrders.map((item) => ({ + platformOrderId: item.platformOrderId, + shopName: item.shopName, + payStatus: item.payStatus, + })), }) } catch (error) { const message = error instanceof Error ? error.message : String(error || 'khhao 自动同步失败') @@ -96,7 +110,7 @@ async function runKhhaoAutoSyncCycle() { logError('[khhao/auto-sync]', 'khhao 自动同步失败', error) } finally { running = false - scheduleNextRun(resolveIntervalMs(source.autoSync?.intervalMinutes)) + scheduleNextRun(resolveNextDelayMs(source, getKhhaoSyncState())) } } @@ -113,6 +127,80 @@ function resolveIntervalMs(value) { return normalized * 60 * 1000 } +function resolveNextDelayMs(source, syncState) { + if (hasActiveKhhaoWatchOrders(syncState?.watchOrders)) { + return resolveBoostIntervalMs() + } + + return resolveIntervalMs(source?.autoSync?.intervalMinutes) +} + +function resolveBoostIntervalMs() { + const delta = BOOST_INTERVAL_MAX_MS - BOOST_INTERVAL_MIN_MS + return BOOST_INTERVAL_MIN_MS + Math.floor(Math.random() * (delta + 1)) +} + +export function reconcileKhhaoWatchOrders(currentWatchOrders = [], results = [], now = nowIso()) { + const nextMap = new Map(normalizeKhhaoWatchOrders(currentWatchOrders).map((item) => [item.platformOrderId, item])) + + for (const item of Array.isArray(results) ? results : []) { + const platformOrderId = String(item?.platformOrderId || '').trim() + if (!platformOrderId || item?.ignored) { + continue + } + + const payStatus = normalizePayStatus(item?.payStatus) + if (payStatus === 'paid') { + nextMap.delete(platformOrderId) + continue + } + + if (payStatus !== 'unpaid') { + continue + } + + const previous = nextMap.get(platformOrderId) + nextMap.set(platformOrderId, { + platformOrderId, + shopId: String(item?.shopId || previous?.shopId || '').trim(), + shopName: String(item?.shopName || previous?.shopName || '').trim(), + itemTitle: String(item?.itemTitle || previous?.itemTitle || '').trim(), + payStatus, + detectedAt: previous?.detectedAt || now, + lastSeenAt: now, + }) + } + + return [...nextMap.values()] +} + +export function hasActiveKhhaoWatchOrders(watchOrders = []) { + return normalizeKhhaoWatchOrders(watchOrders).length > 0 +} + +function normalizeKhhaoWatchOrders(value) { + return (Array.isArray(value) ? value : []) + .map((item) => ({ + platformOrderId: String(item?.platformOrderId || '').trim(), + shopId: String(item?.shopId || '').trim(), + shopName: String(item?.shopName || '').trim(), + itemTitle: String(item?.itemTitle || '').trim(), + payStatus: normalizePayStatus(item?.payStatus), + detectedAt: String(item?.detectedAt || '').trim(), + lastSeenAt: String(item?.lastSeenAt || '').trim(), + })) + .filter((item) => item.platformOrderId && item.payStatus === 'unpaid') +} + +function normalizePayStatus(value) { + const normalized = String(value || '').trim().toLowerCase() + if (normalized === 'paid') { + return 'paid' + } + + return normalized === 'unpaid' ? 'unpaid' : 'unknown' +} + function resolveLatestOrderCreatedAt(results = []) { let latestText = '' let latestTime = 0 diff --git a/apps/backend/src/services/platforms/khhao/auto-sync-service.test.js b/apps/backend/src/services/platforms/khhao/auto-sync-service.test.js new file mode 100644 index 00000000..72f341ee --- /dev/null +++ b/apps/backend/src/services/platforms/khhao/auto-sync-service.test.js @@ -0,0 +1,79 @@ +import test from 'node:test' +import assert from 'node:assert/strict' + +import { + hasActiveKhhaoWatchOrders, + reconcileKhhaoWatchOrders, +} from './auto-sync-service.js' + +test('reconcileKhhaoWatchOrders adds new unpaid synced order into watch list', () => { + const now = '2026-05-03T12:00:00.000Z' + const next = reconcileKhhaoWatchOrders([], [ + { + platformOrderId: '2612300323173226', + shopId: '4269276762', + shopName: '稚嫩游戏交易店', + itemTitle: '暗影哥特', + payStatus: 'unpaid', + ignored: false, + }, + ], now) + + assert.equal(next.length, 1) + assert.deepEqual(next[0], { + platformOrderId: '2612300323173226', + shopId: '4269276762', + shopName: '稚嫩游戏交易店', + itemTitle: '暗影哥特', + payStatus: 'unpaid', + detectedAt: now, + lastSeenAt: now, + }) + assert.equal(hasActiveKhhaoWatchOrders(next), true) +}) + +test('reconcileKhhaoWatchOrders removes watched order after payment is detected', () => { + const current = [ + { + platformOrderId: '2612300323173226', + shopId: '4269276762', + shopName: '稚嫩游戏交易店', + itemTitle: '暗影哥特', + payStatus: 'unpaid', + detectedAt: '2026-05-03T12:00:00.000Z', + lastSeenAt: '2026-05-03T12:00:00.000Z', + }, + ] + + const next = reconcileKhhaoWatchOrders(current, [ + { + platformOrderId: '2612300323173226', + shopId: '4269276762', + shopName: '稚嫩游戏交易店', + itemTitle: '暗影哥特', + payStatus: 'paid', + ignored: false, + }, + ], '2026-05-03T12:00:04.000Z') + + assert.deepEqual(next, []) + assert.equal(hasActiveKhhaoWatchOrders(next), false) +}) + +test('reconcileKhhaoWatchOrders ignores ignored and unknown-pay-status rows', () => { + const now = '2026-05-03T12:00:00.000Z' + const next = reconcileKhhaoWatchOrders([], [ + { + platformOrderId: 'ignored-order', + payStatus: 'unpaid', + ignored: true, + }, + { + platformOrderId: 'unknown-order', + payStatus: 'unknown', + ignored: false, + }, + ], now) + + assert.deepEqual(next, []) +}) diff --git a/apps/backend/src/services/platforms/khhao/order-sync-service.js b/apps/backend/src/services/platforms/khhao/order-sync-service.js index 540b8253..b5e1fd15 100644 --- a/apps/backend/src/services/platforms/khhao/order-sync-service.js +++ b/apps/backend/src/services/platforms/khhao/order-sync-service.js @@ -1,7 +1,12 @@ // @ts-check import { upsertOrderFromSource } from '../../order/order-service.js' -import { mapKhhaoOrderPreview, mapKhhaoOrderToSourceEvent } from './order-mapper-service.js' +import { + mapKhhaoOrderPreview, + mapKhhaoOrderToSourceEvent, + resolveKhhaoOrderStatus, + resolveKhhaoPayStatus, +} from './order-mapper-service.js' import { queryKhhaoOrderList } from './order-query-service.js' import { loginKhhaoSession } from './session-service.js' @@ -57,8 +62,11 @@ export async function syncKhhaoOrders(payload = {}) { provider: 'khhao', platform: preview.platform || 'unknown', shopId: preview.shopId, + shopName: preview.shopName, skuCode: preview.skuCode, itemTitle: preview.itemTitle, + orderStatus: resolvePreviewOrderStatus(preview), + payStatus: resolvePreviewPayStatus(preview), rawOrderCreatedAt: preview.orderCreatedAt, ignored: true, ignoreReason: 'before_sync_baseline', @@ -77,8 +85,11 @@ export async function syncKhhaoOrders(payload = {}) { provider: sourceEvent.provider, platform: sourceEvent.platform, shopId: sourceEvent.shopId, + shopName: sourceEvent.shopName, skuCode: preview.skuCode, itemTitle: preview.itemTitle, + orderStatus: sourceEvent.orderStatus, + payStatus: sourceEvent.payStatus, rawOrderCreatedAt: preview.orderCreatedAt, ignored: Boolean(upsertResult.ignored), ignoreReason: String(upsertResult.ignoreReason || '').trim(), @@ -129,3 +140,11 @@ function parseKhhaoOrderCreatedAt(value) { const isoText = text.replace(' ', 'T') return Date.parse(`${isoText}+08:00`) } + +function resolvePreviewOrderStatus(preview) { + return resolveKhhaoOrderStatus(preview?.status) +} + +function resolvePreviewPayStatus(preview) { + return resolveKhhaoPayStatus(preview?.status) +} diff --git a/apps/backend/src/services/platforms/khhao/sync-state-service.js b/apps/backend/src/services/platforms/khhao/sync-state-service.js index 9ab31797..74a3d0dc 100644 --- a/apps/backend/src/services/platforms/khhao/sync-state-service.js +++ b/apps/backend/src/services/platforms/khhao/sync-state-service.js @@ -58,6 +58,8 @@ function createDefaultKhhaoSyncState() { syncedCount: 0, ignoredCount: 0, lastOrderCreatedAt: '', + watchMode: 'normal', + watchOrders: [], } } @@ -74,6 +76,8 @@ function normalizeKhhaoSyncState(rawValue) { syncedCount: normalizeNonNegativeInteger(source.syncedCount), ignoredCount: normalizeNonNegativeInteger(source.ignoredCount), lastOrderCreatedAt: String(source.lastOrderCreatedAt || '').trim(), + watchMode: normalizeWatchMode(source.watchMode), + watchOrders: normalizeWatchOrders(source.watchOrders), } } @@ -87,6 +91,39 @@ function normalizeNonNegativeInteger(value) { return Number.isInteger(parsed) && parsed >= 0 ? parsed : 0 } +function normalizeWatchMode(value) { + return String(value || '').trim() === 'boosted' ? 'boosted' : 'normal' +} + +function normalizeWatchOrders(value) { + return (Array.isArray(value) ? value : []) + .map((item) => normalizeWatchOrder(item)) + .filter((item) => item.platformOrderId) +} + +function normalizeWatchOrder(value) { + const source = isPlainObject(value) ? value : {} + + return { + platformOrderId: String(source.platformOrderId || '').trim(), + shopId: String(source.shopId || '').trim(), + shopName: String(source.shopName || '').trim(), + itemTitle: String(source.itemTitle || '').trim(), + payStatus: normalizeWatchPayStatus(source.payStatus), + detectedAt: String(source.detectedAt || '').trim(), + lastSeenAt: String(source.lastSeenAt || '').trim(), + } +} + +function normalizeWatchPayStatus(value) { + const normalized = String(value || '').trim().toLowerCase() + if (normalized === 'paid') { + return 'paid' + } + + return normalized === 'unpaid' ? 'unpaid' : 'unknown' +} + function isPlainObject(value) { return Object.prototype.toString.call(value) === '[object Object]' } diff --git a/apps/backend/src/types/admin-write-inputs.js b/apps/backend/src/types/admin-write-inputs.js index cddf5f5e..1ec0a77f 100644 --- a/apps/backend/src/types/admin-write-inputs.js +++ b/apps/backend/src/types/admin-write-inputs.js @@ -90,6 +90,7 @@ export {} * autoReturnNumberAfterDispatch?: boolean * autoConsumeAfterDispatch?: boolean * kuaishouConsumeShopId?: string + * kuaishouConsumeShopName?: string * notes?: string * }} AdminKuaishouCloudFulfillmentItemInput */ diff --git a/apps/frontend/src/components.d.ts b/apps/frontend/src/components.d.ts index 5128cae1..d8e20e2f 100644 --- a/apps/frontend/src/components.d.ts +++ b/apps/frontend/src/components.d.ts @@ -22,6 +22,8 @@ declare module 'vue' { ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] + ElTabPane: typeof import('element-plus/es')['ElTabPane'] + ElTabs: typeof import('element-plus/es')['ElTabs'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] TencentAuthCard: typeof import('./components/tencent/TencentAuthCard.vue')['default'] diff --git a/apps/frontend/src/router/index.ts b/apps/frontend/src/router/index.ts index 3e71fdc3..47c429f3 100644 --- a/apps/frontend/src/router/index.ts +++ b/apps/frontend/src/router/index.ts @@ -91,12 +91,12 @@ const router = createRouter({ { path: 'platform-fulfillment', meta: { allowedRoles: ['admin'] }, - component: () => import('@/views/admin/AdminFulfillmentBindingsView.vue'), + component: () => import('@/views/admin/AdminFulfillmentConfigHubView.vue'), }, { path: 'platform-kuaishou-cloud-fulfillment', meta: { allowedRoles: ['admin'] }, - component: () => import('@/views/admin/AdminKuaishouCloudFulfillmentView.vue'), + redirect: '/admin/platform-fulfillment?tab=kuaishou-cloud', }, { path: 'webhook-events/:eventId', diff --git a/apps/frontend/src/services/admin.ts b/apps/frontend/src/services/admin.ts index 6a645905..d89efd45 100644 --- a/apps/frontend/src/services/admin.ts +++ b/apps/frontend/src/services/admin.ts @@ -6,6 +6,7 @@ import type { AdminAuditLogItem, AdminCloudtentaclesLoginTestResult, AdminCloudtentaclesPersistedSession, + AdminCloudtentaclesSkuListResult, AdminCloudtentaclesSendSmsResult, AdminCloudtentaclesSourceConfig, AdminCloudtentaclesValidateSessionResult, @@ -272,7 +273,7 @@ export function fetchAdminCloudtentaclesSkuList(payload: { deviceId?: string deviceType?: number }) { - return apiPost>('/api/v1/admin/platform-config/cloudtentacles/sku/list', payload) + return apiPost('/api/v1/admin/platform-config/cloudtentacles/sku/list', payload) } export function buyAdminCloudtentaclesSku(payload: { @@ -504,6 +505,10 @@ export function prepareAdminTaskKuaishouCloudFulfillment(taskId: number | string return apiPost(`/api/v1/admin/tasks/${taskId}/kuaishou-cloud/prepare`, {}) } +export function refreshAdminTaskKuaishouCloudRoleInfo(taskId: number | string) { + return apiPost(`/api/v1/admin/tasks/${taskId}/kuaishou-cloud/refresh-role-info`, {}) +} + export function dispatchAdminTaskKuaishouCloudFulfillment( taskId: number | string, payload: { diff --git a/apps/frontend/src/types/admin.ts b/apps/frontend/src/types/admin.ts index bc3ad960..b781a2ce 100644 --- a/apps/frontend/src/types/admin.ts +++ b/apps/frontend/src/types/admin.ts @@ -204,6 +204,16 @@ export interface AdminKhhaoSyncState { fetchedCount: number syncedCount: number ignoredCount: number + watchMode: string + watchOrders: Array<{ + platformOrderId: string + shopId: string + shopName: string + itemTitle: string + payStatus: string + detectedAt: string + lastSeenAt: string + }> } export interface AdminKhhaoOrderPreview { @@ -319,6 +329,28 @@ export interface AdminCloudtentaclesValidateSessionResult { asset: number } +export interface AdminCloudtentaclesSkuItem { + id: number + categoriesId: number + name: string + description: string + image: string + inventory: number + price: number + listingTime: string + delistingTime: string + buyLimitMin: number + buyLimitMax: number + raw: Record +} + +export interface AdminCloudtentaclesSkuListResult { + baseUrl: string + itemCount: number + items: AdminCloudtentaclesSkuItem[] + rawItems: Record[] +} + export interface AdminFulfillmentBindingConfigItem { provider: string platform: string @@ -361,6 +393,7 @@ export interface AdminKuaishouCloudFulfillmentItem { autoReturnNumberAfterDispatch: boolean autoConsumeAfterDispatch: boolean kuaishouConsumeShopId: string + kuaishouConsumeShopName: string notes: string } @@ -532,6 +565,7 @@ export interface AdminTaskOperations { canMarkManualReview: boolean canCompleteManualDispatch: boolean canPrepareKuaishouCloudFulfillment: boolean + canRefreshKuaishouCloudRoleInfo: boolean canDispatchKuaishouCloudFulfillment: boolean canReturnKuaishouCloudFulfillment: boolean canSupportConfirmRole: boolean @@ -713,6 +747,14 @@ export interface AdminTaskDetail { bindUrl: string bindPreparedAt: string | null } + role: { + status: string + name: string + rid: string + refreshedAt: string | null + errorMessage: string + rawInfo: Record | null + } purchase: { autoBuyEnabled: boolean minAssetReserve: number @@ -736,6 +778,7 @@ export interface AdminTaskDetail { consume: { status: string shopId: string + shopName: string autoConsumeEnabled: boolean consumedAt: string | null errorMessage: string diff --git a/apps/frontend/src/views/admin/AdminFulfillmentBindingsView.vue b/apps/frontend/src/views/admin/AdminFulfillmentBindingsView.vue index 710d6ffe..07f80a71 100644 --- a/apps/frontend/src/views/admin/AdminFulfillmentBindingsView.vue +++ b/apps/frontend/src/views/admin/AdminFulfillmentBindingsView.vue @@ -4,9 +4,7 @@ import { computed, nextTick, onMounted, reactive, ref } from 'vue' import { showSuccess } from '@/lib/feedback' import { fetchAdminFulfillmentBindingConfigs, - fetchAdminKhhaoSourceConfig, fetchAdminKuaishouEticketSourceConfig, - queryAdminKhhaoOrders, lookupAdminFulfillmentBindingOrder, saveAdminFulfillmentBindingConfigs, } from '@/services/admin' @@ -14,7 +12,6 @@ import type { AdminFulfillmentBindingConfigItem, AdminFulfillmentLookupItem, AdminFulfillmentLookupResult, - AdminKhhaoOrderPreview, AdminKuaishouEticketShopConfigItem, AdminObservedProductItem, } from '@/types/admin' @@ -93,18 +90,6 @@ const lookupForm = reactive({ shopId: '', platformOrderId: '', }) -const khhaoLookupLoading = ref(false) -const khhaoLookupErrorMessage = ref('') -const khhaoLookupResults = ref([]) -const importedKhhaoOrderIds = ref([]) -const khhaoLookupForm = reactive({ - baseUrl: 'https://admin.khhao.com', - username: '', - password: '', - page: 1, - limit: 10, - maxCaptchaAttempts: 3, -}) const ruleMetrics = computed(() => { const completedCount = bindings.value.filter(isEditableBindingComplete).length @@ -288,9 +273,8 @@ async function loadConfigs() { errorMessage.value = '' try { - const [response, khhaoSourceResponse, kuaishouSourceResponse] = await Promise.all([ + const [response, kuaishouSourceResponse] = await Promise.all([ fetchAdminFulfillmentBindingConfigs(), - fetchAdminKhhaoSourceConfig(), fetchAdminKuaishouEticketSourceConfig(), ]) filePath.value = response.data.filePath @@ -299,10 +283,6 @@ async function loadConfigs() { bindings.value = nextBindings collapsedBindingIds.value = buildCollapsedIds(nextBindings) observedProducts.value = response.data.observedProducts - khhaoLookupForm.baseUrl = khhaoSourceResponse.data.source.baseUrl || 'https://admin.khhao.com' - khhaoLookupForm.username = khhaoSourceResponse.data.source.username || '' - khhaoLookupForm.password = khhaoSourceResponse.data.source.password || '' - khhaoLookupForm.maxCaptchaAttempts = khhaoSourceResponse.data.source.maxCaptchaAttempts || 3 } catch (error) { errorMessage.value = error instanceof Error ? error.message : '读取履约配置失败' } finally { @@ -366,29 +346,6 @@ function isLookupProductImported(lineId: string) { return importedLookupLineIds.value.includes(lineId) } -function importKhhaoProduct(item: AdminKhhaoOrderPreview) { - importObservedProduct({ - provider: item.provider || 'khhao', - platform: item.platform || 'unknown', - shopId: isKhhaoKuaishouBinding({ provider: item.provider || 'khhao', platform: item.platform || 'unknown' }) - ? (item.kuaishouShopId || resolveKuaishouShopByName(item.shopName)?.shopId || '') - : item.shopId, - shopName: item.shopName, - khhaoShopId: item.khhaoShopId || item.shopId, - externalSkuCode: item.skuCode, - externalItemId: item.itemId, - externalSkuName: item.itemTitle, - }) - - if (!importedKhhaoOrderIds.value.includes(item.platformOrderId)) { - importedKhhaoOrderIds.value = [...importedKhhaoOrderIds.value, item.platformOrderId] - } -} - -function isKhhaoProductImported(platformOrderId: string) { - return importedKhhaoOrderIds.value.includes(platformOrderId) -} - function normalizeBindingForSave(item: EditableBinding): SaveBindingPayload { syncBindingShopSelection(item) @@ -615,34 +572,6 @@ async function lookupOrderProducts() { } } -async function lookupKhhaoProducts() { - if (!khhaoLookupForm.username.trim() || !khhaoLookupForm.password.trim()) { - khhaoLookupErrorMessage.value = '请先填写 khhao 账号和密码' - return - } - - khhaoLookupLoading.value = true - khhaoLookupErrorMessage.value = '' - - try { - const response = await queryAdminKhhaoOrders({ - baseUrl: khhaoLookupForm.baseUrl.trim() || 'https://admin.khhao.com', - username: khhaoLookupForm.username.trim(), - password: khhaoLookupForm.password.trim(), - page: Number(khhaoLookupForm.page || 1), - limit: Number(khhaoLookupForm.limit || 10), - maxCaptchaAttempts: Number(khhaoLookupForm.maxCaptchaAttempts || 3), - }) - khhaoLookupResults.value = response.data.items - importedKhhaoOrderIds.value = [] - } catch (error) { - khhaoLookupResults.value = [] - khhaoLookupErrorMessage.value = error instanceof Error ? error.message : 'khhao 订单查询失败' - } finally { - khhaoLookupLoading.value = false - } -} - onMounted(loadConfigs) @@ -938,113 +867,6 @@ onMounted(loadConfigs) -
-
-
-

khhao 订单取样导入

-

先用 khhao 账号查订单样本,再直接把商品导入成规则草稿,适合首批建规则。

-
- khhao / kuaishou -
- -
- - - - - - - - - - - - -
- 操作 - 查询 khhao 订单 -
-
- -

这里直接读取 khhao 订单列表,不写库,只用于导入规则草稿。

-

{{ khhaoLookupErrorMessage }}

-
正在查询 khhao 订单…
- - - - - - - - - - - - - - - - - - - - - - - -
订单店铺商品金额 / 状态操作
-
- {{ item.platformOrderId }} - {{ item.platformLabel || item.platform || '-' }} -
-
-
- {{ item.shopName || item.shopId || '-' }} - khhao ID: {{ item.khhaoShopId || item.shopId || '-' }} - 快手 ID: {{ item.kuaishouShopId }} -
-
-
- {{ item.itemTitle || '-' }} - SKU: {{ item.skuCode || '-' }} - ItemId: {{ item.itemId || '-' }} -
-
-
- {{ (item.totalAmountFen / 100).toFixed(2) }} - {{ item.statusLabel || item.status || '-' }} -
-
- - 导入到规则 - - 已导入草稿 -
还没有 khhao 查询结果。
-
-
diff --git a/apps/frontend/src/views/admin/AdminFulfillmentConfigHubView.vue b/apps/frontend/src/views/admin/AdminFulfillmentConfigHubView.vue new file mode 100644 index 00000000..37a23270 --- /dev/null +++ b/apps/frontend/src/views/admin/AdminFulfillmentConfigHubView.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/apps/frontend/src/views/admin/AdminKuaishouCloudFulfillmentView.vue b/apps/frontend/src/views/admin/AdminKuaishouCloudFulfillmentView.vue index 1ec5f975..90b0ebd7 100644 --- a/apps/frontend/src/views/admin/AdminKuaishouCloudFulfillmentView.vue +++ b/apps/frontend/src/views/admin/AdminKuaishouCloudFulfillmentView.vue @@ -1,12 +1,17 @@