更新处理这个外观部分
This commit is contained in:
@@ -173,6 +173,9 @@ const pollingTaskIds = ref<number[]>([])
|
||||
const pendingFileTaskIds = ref<number[]>([])
|
||||
const pollTimer = ref<number | null>(null)
|
||||
const pollingInFlight = ref(false)
|
||||
const HISTORY_CACHE_TTL_MS = 3000
|
||||
let historyInFlight: Promise<void> | null = null
|
||||
let lastHistoryLoadedAt = 0
|
||||
let disposed = false
|
||||
const timers = createCategorizedTimers('appearance-patent')
|
||||
|
||||
@@ -189,7 +192,12 @@ const parsedRows = computed<AppearancePatentParsedRow[]>(() =>
|
||||
)
|
||||
const previewRows = computed(() => parsedGroups.value[0]?.items || [])
|
||||
const currentItems = computed(() => historyItems.value.filter((i) => i.taskId != null && pollingTaskIds.value.includes(i.taskId)))
|
||||
const historyOnlyItems = computed(() => historyItems.value.filter((i) => (i.taskStatus || '') !== 'RUNNING'))
|
||||
const historyOnlyItems = computed(() =>
|
||||
historyItems.value.filter((i) => {
|
||||
const status = normalizeTaskStatus(i)
|
||||
return status !== 'RUNNING' && status !== 'PENDING'
|
||||
}),
|
||||
)
|
||||
|
||||
function effectiveAiPrompt() {
|
||||
return aiPrompt.value.trim() || defaultAiPrompt
|
||||
@@ -210,11 +218,17 @@ function savePollingIds() {
|
||||
else window.localStorage.setItem(pollingKey(), JSON.stringify(ids))
|
||||
}
|
||||
|
||||
function clearPollingIds() {
|
||||
pollingTaskIds.value = []
|
||||
pendingFileTaskIds.value = []
|
||||
savePollingIds()
|
||||
stopPolling()
|
||||
function loadPollingIds() {
|
||||
if (typeof window === 'undefined') return
|
||||
try {
|
||||
const raw = window.localStorage.getItem(pollingKey())
|
||||
const ids = raw ? JSON.parse(raw) : []
|
||||
pollingTaskIds.value = Array.isArray(ids)
|
||||
? ids.filter((id): id is number => typeof id === 'number' && id > 0)
|
||||
: []
|
||||
} catch {
|
||||
pollingTaskIds.value = []
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadAppearancePathsToJava(paths: Array<string | BrandExpandFolderItem>) {
|
||||
@@ -306,7 +320,7 @@ async function parseFiles() {
|
||||
: [])
|
||||
queuePayloadText.value = ''
|
||||
await loadDashboard()
|
||||
await loadHistory()
|
||||
await loadHistory({ force: true })
|
||||
ElMessage.success(`解析完成,共 ${res.groupCount || 0} 组 / ${res.acceptedRows} 条`)
|
||||
} catch (e) {
|
||||
ElMessage.error(e instanceof Error ? e.message : '解析失败')
|
||||
@@ -366,21 +380,28 @@ async function pushToPythonQueue() {
|
||||
},
|
||||
}
|
||||
queuePayloadText.value = JSON.stringify(payload, null, 2)
|
||||
await activateAppearancePatentTask(taskId)
|
||||
const result = await api.enqueue_json(payload)
|
||||
if (!result?.success) {
|
||||
ElMessage.error(result?.error || '推送失败')
|
||||
return
|
||||
}
|
||||
await activateAppearancePatentTask(taskId)
|
||||
addPollingTask(taskId)
|
||||
clearParsedTask()
|
||||
await loadDashboard()
|
||||
await loadHistory()
|
||||
await loadHistory({ force: true })
|
||||
ElMessage.success('已推送到 Python 队列')
|
||||
} finally {
|
||||
pushing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function clearParsedTask() {
|
||||
parseResult.value = null
|
||||
parsedGroups.value = []
|
||||
queuePayloadText.value = ''
|
||||
}
|
||||
|
||||
function addPollingTask(taskId: number) {
|
||||
if (!pollingTaskIds.value.includes(taskId)) {
|
||||
pollingTaskIds.value = [...pollingTaskIds.value, taskId]
|
||||
@@ -399,6 +420,7 @@ function addPendingFileTask(taskId: number) {
|
||||
pendingFileTaskIds.value = [...pendingFileTaskIds.value, taskId]
|
||||
savePollingIds()
|
||||
}
|
||||
ensurePolling()
|
||||
}
|
||||
|
||||
function removePendingFileTask(taskId: number) {
|
||||
@@ -446,16 +468,29 @@ async function refreshTaskProgress() {
|
||||
}
|
||||
pollingInFlight.value = true
|
||||
try {
|
||||
let shouldRefreshHistory = pendingFileTaskIds.value.length > 0
|
||||
if (pollingTaskIds.value.length) {
|
||||
const batch = await getAppearancePatentTaskProgressBatch(pollingTaskIds.value)
|
||||
let shouldRefreshDashboard = false
|
||||
let shouldRefreshHistory = false
|
||||
const taskIds = Array.from(new Set([...pollingTaskIds.value, ...pendingFileTaskIds.value]))
|
||||
if (taskIds.length) {
|
||||
const batch = await getAppearancePatentTaskProgressBatch(taskIds, { force: pendingFileTaskIds.value.length > 0 })
|
||||
for (const detail of batch.items || []) {
|
||||
const task = detail.task
|
||||
if (!task?.id) continue
|
||||
const item = detail.items?.[0]
|
||||
if (item) mergeHistoryItem(item)
|
||||
if (task.status === 'SUCCESS' || task.status === 'FAILED') {
|
||||
removePollingTask(task.id)
|
||||
shouldRefreshHistory = true
|
||||
shouldRefreshDashboard = true
|
||||
if (task.status === 'SUCCESS') addPendingFileTask(task.id)
|
||||
else removePendingFileTask(task.id)
|
||||
}
|
||||
if (item && task.status === 'SUCCESS' && pendingFileTaskIds.value.includes(task.id)) {
|
||||
const fileStatus = (item.fileStatus || '').toUpperCase()
|
||||
if (item.fileReady || fileStatus === 'SUCCESS' || fileStatus === 'FAILED') {
|
||||
removePendingFileTask(task.id)
|
||||
shouldRefreshDashboard = true
|
||||
shouldRefreshHistory = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (batch.missingTaskIds?.length) {
|
||||
@@ -464,33 +499,55 @@ async function refreshTaskProgress() {
|
||||
removePendingFileTask(taskId)
|
||||
})
|
||||
shouldRefreshHistory = true
|
||||
shouldRefreshDashboard = true
|
||||
}
|
||||
}
|
||||
if (shouldRefreshHistory) {
|
||||
if (shouldRefreshDashboard) {
|
||||
await loadDashboard()
|
||||
await loadHistory()
|
||||
settlePendingFileTasks()
|
||||
}
|
||||
if (shouldRefreshHistory) {
|
||||
await loadHistory()
|
||||
}
|
||||
settlePendingFileTasks()
|
||||
} finally {
|
||||
pollingInFlight.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function mergeHistoryItem(item: AppearancePatentHistoryItem) {
|
||||
if (!item.taskId && !item.resultId) return
|
||||
const index = historyItems.value.findIndex((row) =>
|
||||
(item.resultId != null && row.resultId === item.resultId)
|
||||
|| (item.taskId != null && row.taskId === item.taskId),
|
||||
)
|
||||
if (index >= 0) {
|
||||
historyItems.value[index] = { ...historyItems.value[index], ...item }
|
||||
} else {
|
||||
historyItems.value = [item, ...historyItems.value]
|
||||
}
|
||||
}
|
||||
|
||||
function settlePendingFileTasks() {
|
||||
if (!pendingFileTaskIds.value.length) return
|
||||
const remaining: number[] = []
|
||||
for (const taskId of pendingFileTaskIds.value) {
|
||||
const item = historyItems.value.find((row) => row.taskId === taskId)
|
||||
if (!item) continue
|
||||
if (!item) {
|
||||
remaining.push(taskId)
|
||||
continue
|
||||
}
|
||||
const taskStatus = (item.taskStatus || '').toUpperCase()
|
||||
const fileStatus = (item.fileStatus || '').toUpperCase()
|
||||
if (taskStatus === 'FAILED' || item.fileReady || fileStatus === 'SUCCESS' || fileStatus === 'FAILED') {
|
||||
continue
|
||||
}
|
||||
remaining.push(taskId)
|
||||
if (taskStatus === 'SUCCESS') {
|
||||
remaining.push(taskId)
|
||||
}
|
||||
}
|
||||
pendingFileTaskIds.value = remaining
|
||||
savePollingIds()
|
||||
if (pendingFileTaskIds.value.length) ensurePolling()
|
||||
}
|
||||
|
||||
function seedPendingFileTasksFromHistory() {
|
||||
@@ -506,14 +563,25 @@ async function loadDashboard() {
|
||||
dashboard.value = await getAppearancePatentDashboard()
|
||||
}
|
||||
|
||||
async function loadHistory() {
|
||||
const res = await getAppearancePatentHistory()
|
||||
historyItems.value = res.items || []
|
||||
async function loadHistory(options: { force?: boolean } = {}) {
|
||||
const now = Date.now()
|
||||
if (!options.force && historyItems.value.length && now - lastHistoryLoadedAt < HISTORY_CACHE_TTL_MS) return
|
||||
if (historyInFlight) return historyInFlight
|
||||
historyInFlight = getAppearancePatentHistory()
|
||||
.then((res) => {
|
||||
historyItems.value = res.items || []
|
||||
lastHistoryLoadedAt = Date.now()
|
||||
})
|
||||
.finally(() => {
|
||||
historyInFlight = null
|
||||
})
|
||||
return historyInFlight
|
||||
}
|
||||
|
||||
function statusText(item: AppearancePatentHistoryItem) {
|
||||
const status = item.taskStatus || ''
|
||||
const status = normalizeTaskStatus(item)
|
||||
if (status === 'RUNNING') return '执行中'
|
||||
if (status === 'PENDING') return '已解析待推送'
|
||||
if (isResultPreparing(item)) return '结果生成中'
|
||||
if (isResultBuildFailed(item)) return '结果生成失败'
|
||||
if (status === 'SUCCESS' || item.success) return '已完成'
|
||||
@@ -522,22 +590,26 @@ function statusText(item: AppearancePatentHistoryItem) {
|
||||
}
|
||||
|
||||
function statusClass(item: AppearancePatentHistoryItem) {
|
||||
const status = item.taskStatus || ''
|
||||
const status = normalizeTaskStatus(item)
|
||||
if (status === 'RUNNING' || isResultPreparing(item)) return 'running'
|
||||
if (isResultBuildFailed(item) || status === 'FAILED') return 'failed'
|
||||
if (status === 'SUCCESS' || item.success) return 'success'
|
||||
return 'running'
|
||||
return 'pending'
|
||||
}
|
||||
|
||||
function isResultPreparing(item: AppearancePatentHistoryItem) {
|
||||
const status = item.taskStatus || ''
|
||||
const status = normalizeTaskStatus(item)
|
||||
const fileStatus = (item.fileStatus || '').toUpperCase()
|
||||
if (!(status === 'SUCCESS' || item.success)) return false
|
||||
if (status !== 'SUCCESS') return false
|
||||
if (canDownload(item)) return false
|
||||
if (fileStatus === 'FAILED' || fileStatus === 'SUCCESS') return false
|
||||
return true
|
||||
}
|
||||
|
||||
function normalizeTaskStatus(item: AppearancePatentHistoryItem) {
|
||||
return (item.taskStatus || '').toUpperCase()
|
||||
}
|
||||
|
||||
function isResultBuildFailed(item: AppearancePatentHistoryItem) {
|
||||
return (item.fileStatus || '').toUpperCase() === 'FAILED'
|
||||
}
|
||||
@@ -587,7 +659,7 @@ async function deleteTaskRecord(item: AppearancePatentHistoryItem) {
|
||||
await deleteAppearancePatentHistory(item.resultId)
|
||||
}
|
||||
await loadDashboard()
|
||||
await loadHistory()
|
||||
await loadHistory({ force: true })
|
||||
ElMessage.success('已删除')
|
||||
} catch (e) {
|
||||
ElMessage.error(e instanceof Error ? e.message : '删除失败')
|
||||
@@ -595,12 +667,13 @@ async function deleteTaskRecord(item: AppearancePatentHistoryItem) {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
clearPollingIds()
|
||||
loadPollingIds()
|
||||
await Promise.all([
|
||||
loadDashboard().catch(() => undefined),
|
||||
loadHistory().catch(() => undefined),
|
||||
])
|
||||
seedPendingFileTasksFromHistory()
|
||||
if (pollingTaskIds.value.length || pendingFileTaskIds.value.length) ensurePolling()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -654,6 +727,7 @@ onUnmounted(() => {
|
||||
.status.success { background: rgba(46, 204, 113, .18); color: #2ecc71; }
|
||||
.status.failed { background: rgba(231, 76, 60, .18); color: #ff6b6b; }
|
||||
.status.running { background: rgba(52, 152, 219, .18); color: #3498db; }
|
||||
.status.pending { background: rgba(149, 165, 166, .18); color: #bdc3c7; }
|
||||
.result-hint { margin-top: 6px; color: #e0b96d; }
|
||||
.download { padding: 6px 10px; color: #d6ecff; background: rgba(52, 152, 219, .18); }
|
||||
.btn-delete { padding: 6px 10px; color: #ff8f8f; background: rgba(231, 76, 60, .12); }
|
||||
|
||||
@@ -15,6 +15,10 @@ type TaskProgressCacheEntry = {
|
||||
data: unknown;
|
||||
};
|
||||
|
||||
interface TaskProgressBatchOptions {
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
const taskProgressResponseCache = new Map<string, TaskProgressCacheEntry>();
|
||||
const taskProgressInflightRequests = new Map<string, Promise<unknown>>();
|
||||
|
||||
@@ -44,7 +48,11 @@ function buildTaskProgressRequestKey(path: string, taskIds: number[]) {
|
||||
return `${path}::${taskIds.join(",")}`;
|
||||
}
|
||||
|
||||
async function postTaskProgressBatch<T>(path: string, taskIds: number[]) {
|
||||
async function postTaskProgressBatch<T>(
|
||||
path: string,
|
||||
taskIds: number[],
|
||||
options: TaskProgressBatchOptions = {},
|
||||
) {
|
||||
const normalizedTaskIds = normalizeTaskIds(taskIds);
|
||||
if (!normalizedTaskIds.length) {
|
||||
return { items: [], missingTaskIds: [] } as T;
|
||||
@@ -53,12 +61,12 @@ async function postTaskProgressBatch<T>(path: string, taskIds: number[]) {
|
||||
const cacheKey = buildTaskProgressRequestKey(path, normalizedTaskIds);
|
||||
const now = Date.now();
|
||||
const cached = taskProgressResponseCache.get(cacheKey);
|
||||
if (cached && cached.expiresAt > now) {
|
||||
if (!options.force && cached && cached.expiresAt > now) {
|
||||
return cached.data as T;
|
||||
}
|
||||
|
||||
const inflight = taskProgressInflightRequests.get(cacheKey);
|
||||
if (inflight) {
|
||||
if (!options.force && inflight) {
|
||||
return (await inflight) as T;
|
||||
}
|
||||
|
||||
@@ -1500,15 +1508,19 @@ export function getAppearancePatentHistory() {
|
||||
return unwrapJavaResponse(
|
||||
get<JavaApiResponse<AppearancePatentHistoryVo>>(
|
||||
`${JAVA_API_PREFIX}/appearance-patent/history`,
|
||||
{ params: { user_id: getCurrentUserId() } },
|
||||
{ params: { user_id: getCurrentUserId(), limit: 50 } },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function getAppearancePatentTaskProgressBatch(taskIds: number[]) {
|
||||
export function getAppearancePatentTaskProgressBatch(
|
||||
taskIds: number[],
|
||||
options: TaskProgressBatchOptions = {},
|
||||
) {
|
||||
return postTaskProgressBatch<AppearancePatentTaskBatchVo>(
|
||||
`${JAVA_API_PREFIX}/appearance-patent/tasks/progress/batch`,
|
||||
taskIds,
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user