更新带货视频工作流接口和页面

This commit is contained in:
super
2026-07-09 00:27:49 +08:00
parent 0ecf5cd45a
commit aab4ce942c
51 changed files with 4777 additions and 820 deletions

View File

@@ -284,6 +284,7 @@ import {
getPriceTrackResultDownloadUrl,
getPriceTrackTaskProgressBatch,
getPriceTrackTasksBatch,
getTaskSkipPriceAsinsPaginated,
listPriceTrackCandidates,
matchPriceTrackShops,
putPriceTrackCountryPreference,
@@ -844,17 +845,64 @@ function isRecordMissingError(error: unknown) {
return /记录不存在|不存在|已删除|not\s*found|404/i.test(message)
}
function buildSkipAsinDeletePolicy() {
const enabled = statusModeEnabled.value && !asinModeEnabled.value
return {
enabled,
mode: enabled ? 'DELETE_WHEN_PRICE_BELOW_MINIMUM' : 'NONE',
delete_when_price_below_minimum: enabled,
compare_field: 'price',
threshold_field: 'minimum_price',
target: 'skip_asin',
source: 'frontend-price-track',
function priceTrackModeForAppClient() {
return statusModeEnabled.value ? 'status' : 'asin'
}
function buildMinimumPriceByCountryAndAsin(rowsByCountry: Record<string, PriceTrackAsinParsedRow[]>) {
const out: Record<string, Record<string, string>> = {}
for (const [country, rows] of Object.entries(rowsByCountry || {})) {
const bucket: Record<string, string> = {}
for (const row of rows || []) {
const asin = (row.asin || '').trim()
if (!asin) continue
bucket[asin] = row.minimumPrice == null ? '' : String(row.minimumPrice)
}
out[country] = bucket
}
return out
}
function hasCountryRows(rowsByCountry: Record<string, PriceTrackAsinParsedRow[]> | undefined) {
return Object.values(rowsByCountry || {}).some((rows) => Array.isArray(rows) && rows.length > 0)
}
function hasMinimumPriceMap(map: Record<string, Record<string, string>> | undefined) {
return Object.values(map || {}).some((rows) => rows && Object.keys(rows).length > 0)
}
function buildMinimumPriceMapForAppClient(taskVo: PriceTrackCreateTaskVo, asinRowsByCountry: Record<string, PriceTrackAsinParsedRow[]>) {
return hasMinimumPriceMap(taskVo.minimumPriceByCountryAndAsin)
? taskVo.minimumPriceByCountryAndAsin || {}
: buildMinimumPriceByCountryAndAsin(asinRowsByCountry)
}
async function loadAsinRowsForAppClient(taskVo: PriceTrackCreateTaskVo) {
const fromTask = taskVo.asinRowsByCountry || {}
if (hasCountryRows(fromTask)) {
return fromTask
}
if (!asinModeEnabled.value) {
return {}
}
const merged: Record<string, PriceTrackAsinParsedRow[]> = {}
let page = 1
let totalPages = 1
do {
const res = await getTaskSkipPriceAsinsPaginated(taskVo.taskId, page, 2000)
totalPages = Math.max(1, Number(res.totalPages) || 1)
const pageRows = (res.asin_rows_by_country || res.skipAsinDetailsByCountry || res.skip_asin_details_by_country || {}) as Record<
string,
PriceTrackAsinParsedRow[]
>
for (const [country, rows] of Object.entries(pageRows)) {
merged[country] = [...(merged[country] || []), ...(rows || [])]
}
page += 1
} while (page <= totalPages)
return merged
}
function removeMatchedRowsLocally(rows: Array<{ shopName?: string; shopId?: number | string | null }>) {
@@ -953,20 +1001,24 @@ async function pushToPythonQueueLegacy() {
saveTaskSnapshotsToStorage()
saveTaskDetailsToStorage()
// 2. 推送 taskId 给 PythonPython 会从 Java 拉取完整任务上下文
const firstRow = matchedRows[0]
const asinRowsByCountry = await loadAsinRowsForAppClient(taskVo)
const minimumPriceByCountryAndAsin = buildMinimumPriceMapForAppClient(taskVo, asinRowsByCountry)
// 2. 推送 app_client 当前消费的字段
const queuePayload = {
type: 'price-track-run',
ts: Date.now(),
data: {
task_id: taskVo.taskId,
shop_names: matchedRows.map((i) => i.shopName).filter(Boolean),
shop_name: (firstRow.shopName || '').trim(),
shopName: (firstRow.shopName || '').trim(),
companyName: firstRow.companyName || '',
shopMallName: firstRow.shopMallName || '',
country_codes: resolveCountryCodesForRequest(),
use_skip_asin_check: statusModeEnabled.value && !asinModeEnabled.value,
skip_asin_check_url: `/api/price-track/tasks/${taskVo.taskId}/skip-asin/check`,
use_paginated_skip_asins: false,
skip_asin_page_size: 0,
skip_asin_delete_policy: buildSkipAsinDeletePolicy(),
delete_skip_asin_when_price_below_minimum: statusModeEnabled.value && !asinModeEnabled.value,
mode: priceTrackModeForAppClient(),
asin_rows_by_country: asinRowsByCountry,
minimum_price_by_country_and_asin: minimumPriceByCountryAndAsin,
},
}
queuePayloadText.value = JSON.stringify(queuePayload, null, 2)
@@ -990,49 +1042,24 @@ async function pushToPythonQueueLegacy() {
}
// ========== 任务状态轮询 ==========
function buildQueuePayload(taskVo: PriceTrackCreateTaskVo, row: PriceTrackShopQueueItem) {
const taskItem = taskVo.items?.[0]
async function buildQueuePayload(taskVo: PriceTrackCreateTaskVo, row: PriceTrackShopQueueItem) {
const shopName = (row.shopName || '').trim()
const skipAsinDeletePolicy = buildSkipAsinDeletePolicy()
const skipAsinCheckPath = `/api/price-track/tasks/${taskVo.taskId}/skip-asin/check`
const resultId = taskItem?.resultId ?? null
const loopRunId = taskItem?.loopRunId ?? null
const roundIndex = taskItem?.roundIndex ?? null
const taskStatus = taskItem?.taskStatus || 'RUNNING'
const success = taskItem?.success ?? false
const error = taskItem?.error || null
const outputFilename = taskItem?.outputFilename || null
const downloadUrl = taskItem?.downloadUrl || null
const asinRowsByCountry = await loadAsinRowsForAppClient(taskVo)
const minimumPriceByCountryAndAsin = buildMinimumPriceMapForAppClient(taskVo, asinRowsByCountry)
return {
type: 'price-track-run',
ts: Date.now(),
data: {
task_id: taskVo.taskId,
result_id: resultId,
shop_name: shopName,
shop_id: row.shopId ?? null,
platform: row.platform || '',
company_name: row.companyName || '',
shop_mall_name: row.shopMallName || '',
matched: !!row.matched,
match_status: row.matchStatus || '',
match_message: row.matchMessage || null,
success: success,
error: error,
output_filename: outputFilename,
download_url: downloadUrl,
task_status: taskStatus,
loop_run_id: loopRunId,
round_index: roundIndex,
country_codes: resolveCountryCodesForRequest(),
mode: statusModeEnabled.value ? 'status' : 'asin',
use_skip_asin_check: statusModeEnabled.value && !asinModeEnabled.value,
skip_asin_check_url: skipAsinCheckPath,
use_paginated_skip_asins: false,
skip_asin_page_size: 0,
skip_asin_delete_policy: skipAsinDeletePolicy,
delete_skip_asin_when_price_below_minimum: skipAsinDeletePolicy.delete_when_price_below_minimum,
mode: priceTrackModeForAppClient(),
shopName,
companyName: row.companyName || '',
shopMallName: row.shopMallName || '',
asin_rows_by_country: asinRowsByCountry,
minimum_price_by_country_and_asin: minimumPriceByCountryAndAsin,
},
}
}
@@ -1174,7 +1201,7 @@ async function processMatchedQueue() {
}
saveTaskSnapshotsToStorage()
saveTaskDetailsToStorage()
const queuePayload = buildQueuePayload(taskVo, row)
const queuePayload = await buildQueuePayload(taskVo, row)
queuePayloadText.value = JSON.stringify(queuePayload, null, 2)
const pushResult = await api.enqueue_json(queuePayload)
if (!pushResult?.success) {
@@ -1375,7 +1402,7 @@ async function runLoopExecution(loopId?: number) {
}
})()
recordCreatedTask(taskVo)
const queuePayload = buildQueuePayload(taskVo, row)
const queuePayload = await buildQueuePayload(taskVo, row)
queuePayloadText.value = JSON.stringify(queuePayload, null, 2)
const pushResult = await api.enqueue_json(queuePayload)
if (!pushResult?.success) {