更新处理相关内容
This commit is contained in:
@@ -32,7 +32,7 @@
|
||||
{{ pushing ? '推送中...' : '推送到 Python 队列' }}
|
||||
</button>
|
||||
</div>
|
||||
<p class="loading-msg">Python 回传字段按 asin、国家、url、标题提交;后端接收分片后按 10 条一批调用 Coze,结果区展示真实 Coze 批次进度。</p>
|
||||
<p class="loading-msg">Python 回传字段按 asin、国家、url、标题提交;后端接收分片后累计 50 条调用 Coze,当前任务区展示 Coze 回流和结果组装进度。</p>
|
||||
|
||||
<div v-if="visibleTaskSummary" class="parse-card">
|
||||
<div>任务 ID:{{ visibleTaskSummary.taskId }}</div>
|
||||
@@ -75,6 +75,19 @@
|
||||
<span class="id">{{ item.sourceFilename || '外观专利检测' }}</span>
|
||||
<div class="files">任务 ID:{{ item.taskId }}</div>
|
||||
<div class="files">行数:{{ item.rowCount ?? '-' }}</div>
|
||||
<div v-if="pendingResultHint(item)" class="files result-hint">{{ pendingResultHint(item) }}</div>
|
||||
<div v-if="showFileProgress(item)" class="file-progress">
|
||||
<div class="file-progress-meta">
|
||||
<span>{{ displayFileProgressMessage(item, '处理中') }}</span>
|
||||
<span>{{ fileProgressPercent(item) }}%</span>
|
||||
</div>
|
||||
<div class="file-progress-track">
|
||||
<div class="file-progress-bar" :style="{ width: `${fileProgressPercent(item)}%` }"></div>
|
||||
</div>
|
||||
<div class="file-progress-count">
|
||||
{{ item.fileProgressCurrent || 0 }}/{{ item.fileProgressTotal || 0 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-right">
|
||||
<span class="status running">{{ statusText(item) }}</span>
|
||||
@@ -100,7 +113,7 @@
|
||||
<div v-if="pendingResultHint(item)" class="files result-hint">{{ pendingResultHint(item) }}</div>
|
||||
<div v-if="showFileProgress(item)" class="file-progress">
|
||||
<div class="file-progress-meta">
|
||||
<span>{{ item.fileProgressMessage || '结果生成中' }}</span>
|
||||
<span>{{ displayFileProgressMessage(item, '结果生成中') }}</span>
|
||||
<span>{{ fileProgressPercent(item) }}%</span>
|
||||
</div>
|
||||
<div class="file-progress-track">
|
||||
@@ -201,8 +214,14 @@ const pendingParseItem = computed<AppearancePatentHistoryItem | null>(() => {
|
||||
}
|
||||
})
|
||||
const currentItems = computed(() => {
|
||||
const runningItems = historyItems.value.filter((i) => i.taskId != null && pollingTaskIds.value.includes(i.taskId))
|
||||
return pendingParseItem.value ? [pendingParseItem.value, ...runningItems] : runningItems
|
||||
const activeIds = new Set([...pollingTaskIds.value, ...pendingFileTaskIds.value])
|
||||
const activeItems = historyItems.value.filter((item) => {
|
||||
const status = normalizeTaskStatus(item)
|
||||
return item.taskId != null && (activeIds.has(item.taskId) || status === 'RUNNING' || status === 'PENDING')
|
||||
})
|
||||
if (!pendingParseItem.value) return activeItems
|
||||
const exists = activeItems.some((item) => item.taskId === pendingParseItem.value?.taskId)
|
||||
return exists ? activeItems : [pendingParseItem.value, ...activeItems]
|
||||
})
|
||||
const historyOnlyItems = computed(() =>
|
||||
historyItems.value.filter((i) => {
|
||||
@@ -594,6 +613,16 @@ function seedPendingFileTasksFromHistory() {
|
||||
if (pendingFileTaskIds.value.length) ensurePolling()
|
||||
}
|
||||
|
||||
function seedRunningTasksFromHistory() {
|
||||
const ids = historyItems.value
|
||||
.filter((item) => item.taskId != null && normalizeTaskStatus(item) === 'RUNNING')
|
||||
.map((item) => item.taskId as number)
|
||||
if (!ids.length) return
|
||||
pollingTaskIds.value = Array.from(new Set([...pollingTaskIds.value, ...ids]))
|
||||
savePollingIds()
|
||||
ensurePolling()
|
||||
}
|
||||
|
||||
async function loadDashboard() {
|
||||
dashboard.value = await getAppearancePatentDashboard()
|
||||
}
|
||||
@@ -653,6 +682,9 @@ function pendingResultHint(item: AppearancePatentHistoryItem) {
|
||||
if (isResultBuildFailed(item)) {
|
||||
return item.fileError || '结果文件生成失败,请稍后重试或检查后端日志'
|
||||
}
|
||||
if (showFileProgress(item)) {
|
||||
return ''
|
||||
}
|
||||
if (!isResultPreparing(item)) return ''
|
||||
const fileStatus = (item.fileStatus || '').toUpperCase()
|
||||
if (fileStatus === 'RUNNING') {
|
||||
@@ -674,8 +706,37 @@ function fileProgressPercent(item: AppearancePatentHistoryItem) {
|
||||
return Math.max(0, Math.min(100, Math.round(percent)))
|
||||
}
|
||||
|
||||
function displayFileProgressMessage(item: AppearancePatentHistoryItem, fallback: string) {
|
||||
const message = (item.fileProgressMessage || '').trim()
|
||||
if (!message) return fallback
|
||||
return translateFileProgressMessage(message)
|
||||
}
|
||||
|
||||
function translateFileProgressMessage(message: string) {
|
||||
const submittedMatch = message.match(/^Coze submitted (\d+) batches, completed (\d+), waiting for results$/)
|
||||
if (submittedMatch) {
|
||||
return `已提交 Coze ${submittedMatch[1]} 批,已完成 ${submittedMatch[2]} 批,等待结果回流`
|
||||
}
|
||||
const completedMatch = message.match(/^Coze completed (\d+) batches, waiting for remaining Python data$/)
|
||||
if (completedMatch) {
|
||||
return `Coze 已完成 ${completedMatch[1]} 批,等待 Python 继续回传数据`
|
||||
}
|
||||
const translations: Record<string, string> = {
|
||||
'Receiving Python data, waiting for 50 rows before Coze': '正在接收 Python 数据,累计 50 条后提交 Coze',
|
||||
'Submitting Coze': '正在提交 Coze',
|
||||
'Coze submitted, waiting for result': 'Coze 已提交,等待结果回流',
|
||||
'Waiting for Python upload': '等待 Python 继续回传数据',
|
||||
'Coze results ready, assembling xlsx': 'Coze 结果已回流,正在组装 xlsx',
|
||||
'Assembling xlsx': '正在组装 xlsx',
|
||||
'Uploading result file': '正在上传结果文件',
|
||||
'Result file generated': '结果文件已生成',
|
||||
}
|
||||
return translations[message] || message
|
||||
}
|
||||
|
||||
function showFileProgress(item: AppearancePatentHistoryItem) {
|
||||
return isResultPreparing(item) && (item.fileProgressTotal || 0) > 0
|
||||
const status = normalizeTaskStatus(item)
|
||||
return (status === 'RUNNING' || isResultPreparing(item)) && (item.fileProgressTotal || 0) > 0
|
||||
}
|
||||
|
||||
async function downloadResult(item: AppearancePatentHistoryItem) {
|
||||
@@ -717,6 +778,7 @@ onMounted(async () => {
|
||||
loadDashboard().catch(() => undefined),
|
||||
loadHistory().catch(() => undefined),
|
||||
])
|
||||
seedRunningTasksFromHistory()
|
||||
seedPendingFileTasksFromHistory()
|
||||
if (pollingTaskIds.value.length || pendingFileTaskIds.value.length) ensurePolling()
|
||||
})
|
||||
|
||||
@@ -291,7 +291,9 @@ function sanitizeSchedulePickerValues(values: string[], ensureOne: boolean) {
|
||||
if (!deduped.length && ensureOne) return [getDefaultScheduleValue()]
|
||||
return deduped
|
||||
}
|
||||
function buildQueuePayload(taskId: number, item: Pick<ShopMatchHistoryItem, 'shopName' | 'shopId' | 'platform' | 'companyName' | 'matchStatus' | 'matchMessage'>, countryCodes: string[], stageIndex?: number, finalStage = true) { return { type: 'shop-match-run', ts: Date.now(), data: { taskId, user_id: currentUserId(), items: [{ shopName: item.shopName, shopId: item.shopId, platform: item.platform, companyName: item.companyName, matched: true, matchStatus: item.matchStatus, matchMessage: item.matchMessage }], country_codes: [...countryCodes], risk_listing_filter: shopMatchListingFilter.value, stage_index: stageIndex, final_stage: finalStage } } }
|
||||
function readSkipAsinsByCountry(item: ShopMatchHistoryItem) { return item.skipAsinsByCountry || item.skip_asins_by_country || {} }
|
||||
function readSkipAsinDetailsByCountry(item: ShopMatchHistoryItem) { return item.skipAsinDetailsByCountry || item.skip_asin_details_by_country || {} }
|
||||
function buildQueuePayload(taskId: number, item: Pick<ShopMatchHistoryItem, 'shopName' | 'shopId' | 'platform' | 'companyName' | 'matchStatus' | 'matchMessage' | 'skipAsinsByCountry' | 'skip_asins_by_country' | 'skipAsinDetailsByCountry' | 'skip_asin_details_by_country'>, countryCodes: string[], stageIndex?: number, finalStage = true) { const skipAsinsByCountry = readSkipAsinsByCountry(item as ShopMatchHistoryItem); const skipAsinDetailsByCountry = readSkipAsinDetailsByCountry(item as ShopMatchHistoryItem); return { type: 'shop-match-run', ts: Date.now(), data: { taskId, user_id: currentUserId(), items: [{ shopName: item.shopName, shopId: item.shopId, platform: item.platform, companyName: item.companyName, matched: true, matchStatus: item.matchStatus, matchMessage: item.matchMessage, skip_asins_by_country: skipAsinsByCountry, skipAsinsByCountry, skip_asin_details_by_country: skipAsinDetailsByCountry, skipAsinDetailsByCountry }], country_codes: [...countryCodes], skip_asins_by_country: skipAsinsByCountry, skipAsinsByCountry, skip_asin_details_by_country: skipAsinDetailsByCountry, skipAsinDetailsByCountry, risk_listing_filter: shopMatchListingFilter.value, stage_index: stageIndex, final_stage: finalStage } } }
|
||||
function nextScheduleStage(taskId: number) { const task = taskSnapshots.value[taskId]?.task; const stages = task?.scheduleStages || []; const currentStageIndex = task?.currentStageIndex; if (typeof currentStageIndex !== 'number') return null; const stage = stages.find((item) => item.stageIndex === currentStageIndex); if (!stage?.scheduledAt) return null; return { stageIndex: currentStageIndex, scheduledAt: stage.scheduledAt, finalStage: currentStageIndex === stages.length - 1 } }
|
||||
async function dispatchScheduledTask(taskId: number, item: ShopMatchHistoryItem) { const api = getPywebviewApi(); if (!api?.enqueue_json) throw new Error('当前客户端未提供 enqueue_json'); const stage = nextScheduleStage(taskId); const snapshot = taskSnapshots.value[taskId]; const countryCodes = snapshot?.task?.countryCodes || orderedCountryCodes.value; if (!stage) throw new Error(`任务 ${taskId} 缺少待执行阶段`); clearDispatchTimer(taskId); await withTransientRetry(() => activateShopMatchTask(taskId, stage.stageIndex), (attempt, maxAttempts) => { queuePushResult.value = `任务 ${taskId} 等待后端恢复后再激活执行(${attempt}/${maxAttempts})...` }); taskDetails.value = { ...taskDetails.value, [taskId]: 'RUNNING' }; if (snapshot?.task) taskSnapshots.value = { ...taskSnapshots.value, [taskId]: { ...snapshot, task: { ...snapshot.task, status: 'RUNNING', activeStageIndex: stage.stageIndex } } }; saveTaskSnapshotsToStorage(); saveTaskDetailsToStorage(); const payload = buildQueuePayload(taskId, item, countryCodes, stage.stageIndex, stage.finalStage); queuePayloadText.value = JSON.stringify(payload, null, 2); const result = await api.enqueue_json(payload); if (!result?.success) throw new Error(result?.error || `任务 ${taskId} 推送失败`); addPollingTask(taskId); queuePushResult.value = `任务 ${taskId} 已推送第 ${stage.stageIndex + 1} 轮`; ensurePolling(true) }
|
||||
function scheduledTimestamp(value?: string) { const date = parseScheduleDateTime(value); return date ? date.getTime() : Number.NaN }
|
||||
|
||||
@@ -631,6 +631,28 @@ export interface ProductRiskShopQueueItem {
|
||||
matchStatus?: string;
|
||||
matchMessage?: string;
|
||||
queryAsins?: QueryAsinCountryAsins[];
|
||||
skipAsinsByCountry?: Record<string, string[]>;
|
||||
skip_asins_by_country?: Record<string, string[]>;
|
||||
skipAsinDetailsByCountry?: Record<string, Array<{ asin?: string; minimumPrice?: string }>>;
|
||||
skip_asin_details_by_country?: Record<string, Array<{ asin?: string; minimumPrice?: string }>>;
|
||||
}
|
||||
|
||||
export interface ShopMatchResultRow {
|
||||
asin?: string;
|
||||
minimumPrice?: string;
|
||||
minimum_price?: string;
|
||||
status?: string;
|
||||
done?: boolean;
|
||||
}
|
||||
|
||||
export interface ShopMatchSubmitShopPayload {
|
||||
shopName?: string;
|
||||
error?: string;
|
||||
countries?: Record<string, ShopMatchResultRow[]>;
|
||||
skipAsinsByCountry?: Record<string, string[]>;
|
||||
skip_asins_by_country?: Record<string, string[]>;
|
||||
skipAsinDetailsByCountry?: Record<string, Array<{ asin?: string; minimumPrice?: string }>>;
|
||||
skip_asin_details_by_country?: Record<string, Array<{ asin?: string; minimumPrice?: string }>>;
|
||||
}
|
||||
|
||||
export interface ProductRiskMatchShopsVo {
|
||||
@@ -737,6 +759,10 @@ export interface ProductRiskHistoryItem {
|
||||
fileError?: string;
|
||||
fileReady?: boolean;
|
||||
scheduledAt?: string;
|
||||
skipAsinsByCountry?: Record<string, string[]>;
|
||||
skip_asins_by_country?: Record<string, string[]>;
|
||||
skipAsinDetailsByCountry?: Record<string, Array<{ asin?: string; minimumPrice?: string }>>;
|
||||
skip_asin_details_by_country?: Record<string, Array<{ asin?: string; minimumPrice?: string }>>;
|
||||
}
|
||||
|
||||
export interface ProductRiskHistoryVo {
|
||||
|
||||
Reference in New Issue
Block a user