后台管理跳过asin、后台相关数据做数据隔离
This commit is contained in:
@@ -90,26 +90,6 @@
|
||||
</div>
|
||||
<div v-if="countryPrefSaving" class="country-pref-status">保存中…</div>
|
||||
|
||||
<!-- 价格监控设置 -->
|
||||
<div class="section-title">价格监控设置</div>
|
||||
<div class="price-filter-zone">
|
||||
<div class="price-input-row">
|
||||
<span class="price-label">最低价</span>
|
||||
<el-input v-model.number="minPrice" type="number" clearable placeholder="可不填" class="price-input" />
|
||||
<span class="price-unit">EUR</span>
|
||||
</div>
|
||||
<div class="price-input-row">
|
||||
<span class="price-label">最高价</span>
|
||||
<el-input v-model.number="maxPrice" type="number" clearable placeholder="可不填" class="price-input" />
|
||||
<span class="price-unit">EUR</span>
|
||||
</div>
|
||||
<div class="price-input-row">
|
||||
<span class="price-label">价差比例</span>
|
||||
<el-input v-model.number="priceDiffPercent" type="number" clearable placeholder="如 5 表示 5%" class="price-input" />
|
||||
<span class="price-unit">%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态指示 -->
|
||||
<div v-if="statusModeEnabled" class="mode-status-bar">
|
||||
<span class="mode-status-dot active"></span>
|
||||
@@ -257,6 +237,7 @@ import {
|
||||
getPriceTrackDashboard,
|
||||
getPriceTrackHistory,
|
||||
getPriceTrackResultDownloadUrl,
|
||||
getPriceTrackTasksBatch,
|
||||
listPriceTrackCandidates,
|
||||
matchPriceTrackShops,
|
||||
putPriceTrackCountryPreference,
|
||||
@@ -264,6 +245,7 @@ import {
|
||||
type PriceTrackDashboardVo,
|
||||
type PriceTrackHistoryItem,
|
||||
type PriceTrackShopQueueItem,
|
||||
type PriceTrackTaskDetailVo,
|
||||
} from '@/shared/api/java-modules'
|
||||
|
||||
// ========== 类型定义 ==========
|
||||
@@ -293,11 +275,6 @@ const asinModeEnabled = ref(false) // 按指定ASIN文档(自定义)
|
||||
// 上传文件
|
||||
const asinFiles = ref<string[]>([]) // ASIN文档
|
||||
|
||||
// 价格监控
|
||||
const minPrice = ref<number | undefined>(undefined)
|
||||
const maxPrice = ref<number | undefined>(undefined)
|
||||
const priceDiffPercent = ref<number | undefined>(undefined)
|
||||
|
||||
// 是否有有效模式
|
||||
const hasValidMode = computed(() => statusModeEnabled.value || asinModeEnabled.value)
|
||||
|
||||
@@ -317,10 +294,113 @@ const dashboard = ref<PriceTrackDashboardVo>({
|
||||
const historyItems = ref<PriceTrackHistoryItem[]>([])
|
||||
const pollingTaskIds = ref<number[]>([])
|
||||
const taskDetails = ref<Record<number, string>>({})
|
||||
const taskSnapshots = ref<Record<number, any>>({})
|
||||
const taskSnapshots = ref<Record<number, PriceTrackTaskDetailVo>>({})
|
||||
const pollTimer = ref<number | null>(null)
|
||||
const pollingInFlight = ref(false)
|
||||
|
||||
function uidForStorage() {
|
||||
return typeof window !== 'undefined' ? window.localStorage.getItem('uid') || '0' : '0'
|
||||
}
|
||||
|
||||
function sessionKey() {
|
||||
return `price-track:tasks:${uidForStorage()}`
|
||||
}
|
||||
|
||||
function taskStatusStorageKey() {
|
||||
return `price-track:task-status:${uidForStorage()}`
|
||||
}
|
||||
|
||||
function taskSnapshotsStorageKey() {
|
||||
return `price-track:tasks-batch-snapshot:${uidForStorage()}`
|
||||
}
|
||||
|
||||
function matchedItemsStorageKey() {
|
||||
return `price-track:matched-items:${uidForStorage()}`
|
||||
}
|
||||
|
||||
function setStorageJson(key: string, value: unknown, shouldRemove: boolean) {
|
||||
if (typeof window === 'undefined') return
|
||||
try {
|
||||
if (shouldRemove) window.localStorage.removeItem(key)
|
||||
else window.localStorage.setItem(key, JSON.stringify(value))
|
||||
} catch {
|
||||
/* quota */
|
||||
}
|
||||
}
|
||||
|
||||
function loadPollingIdsFromStorage() {
|
||||
try {
|
||||
const raw = typeof window !== 'undefined' ? window.localStorage.getItem(sessionKey()) : null
|
||||
if (!raw) {
|
||||
pollingTaskIds.value = []
|
||||
return
|
||||
}
|
||||
const parsed = JSON.parse(raw) as unknown
|
||||
pollingTaskIds.value = Array.isArray(parsed) ? parsed.filter((n): n is number => typeof n === 'number' && n > 0) : []
|
||||
} catch {
|
||||
pollingTaskIds.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function savePollingIds() {
|
||||
setStorageJson(sessionKey(), pollingTaskIds.value, pollingTaskIds.value.length === 0)
|
||||
}
|
||||
|
||||
function loadTaskDetailsFromStorage() {
|
||||
try {
|
||||
const raw = typeof window !== 'undefined' ? window.localStorage.getItem(taskStatusStorageKey()) : null
|
||||
if (!raw) return
|
||||
const parsed = JSON.parse(raw) as Record<string, unknown>
|
||||
const out: Record<number, string> = {}
|
||||
for (const [k, v] of Object.entries(parsed)) {
|
||||
const id = Number(k)
|
||||
if (id > 0 && typeof v === 'string') out[id] = v
|
||||
}
|
||||
taskDetails.value = out
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function saveTaskDetailsToStorage() {
|
||||
setStorageJson(taskStatusStorageKey(), taskDetails.value, Object.keys(taskDetails.value).length === 0)
|
||||
}
|
||||
|
||||
function loadTaskSnapshotsFromStorage() {
|
||||
try {
|
||||
const raw = typeof window !== 'undefined' ? window.localStorage.getItem(taskSnapshotsStorageKey()) : null
|
||||
if (!raw) return
|
||||
const parsed = JSON.parse(raw) as Record<string, unknown>
|
||||
const out: Record<number, PriceTrackTaskDetailVo> = {}
|
||||
for (const [k, v] of Object.entries(parsed)) {
|
||||
const id = Number(k)
|
||||
if (id > 0 && v && typeof v === 'object') out[id] = v as PriceTrackTaskDetailVo
|
||||
}
|
||||
taskSnapshots.value = out
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function saveTaskSnapshotsToStorage() {
|
||||
setStorageJson(taskSnapshotsStorageKey(), taskSnapshots.value, Object.keys(taskSnapshots.value).length === 0)
|
||||
}
|
||||
|
||||
function loadMatchedItemsFromStorage() {
|
||||
try {
|
||||
const raw = typeof window !== 'undefined' ? window.localStorage.getItem(matchedItemsStorageKey()) : null
|
||||
if (!raw) return
|
||||
const parsed = JSON.parse(raw) as unknown
|
||||
if (Array.isArray(parsed)) matchedItems.value = parsed as PriceTrackShopQueueItem[]
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function saveMatchedItemsToStorage() {
|
||||
setStorageJson(matchedItemsStorageKey(), matchedItems.value, matchedItems.value.length === 0)
|
||||
}
|
||||
|
||||
// ========== 文件上传 ==========
|
||||
async function selectAsinFile() {
|
||||
const api = getPywebviewApi()
|
||||
@@ -463,6 +543,15 @@ async function loadHistory() {
|
||||
}
|
||||
}
|
||||
|
||||
function syncPollingIdsWithHistory() {
|
||||
for (const taskId of [...pollingTaskIds.value]) {
|
||||
const any = historyItems.value.some((r) => r.taskId === taskId)
|
||||
if (!any && !taskSnapshots.value[taskId]) {
|
||||
removePollingTask(taskId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onSelectionChange(rows: PriceTrackCandidateVo[]) {
|
||||
selectedCandidates.value = rows || []
|
||||
}
|
||||
@@ -491,6 +580,7 @@ async function removeCandidate(row: PriceTrackCandidateVo) {
|
||||
await deletePriceTrackCandidate(row.id)
|
||||
await loadCandidates()
|
||||
matchedItems.value = []
|
||||
saveMatchedItemsToStorage()
|
||||
ElMessage.success('已删除')
|
||||
} catch (e) {
|
||||
ElMessage.error(e instanceof Error ? e.message : '删除失败')
|
||||
@@ -519,6 +609,7 @@ async function runMatch() {
|
||||
const res = await matchPriceTrackShops(names)
|
||||
const batch = res.items || []
|
||||
matchedItems.value = [...matchedItems.value, ...batch.filter((n) => !matchedItems.value.some((e) => e.shopName === n.shopName))]
|
||||
saveMatchedItemsToStorage()
|
||||
ElMessage.success(`匹配 ${batch.length} 个店铺`)
|
||||
} catch (e) {
|
||||
ElMessage.error(e instanceof Error ? e.message : '匹配失败')
|
||||
@@ -549,11 +640,32 @@ function removeMatchedRowsLocally(rows: Array<{ shopName?: string; shopId?: numb
|
||||
if (!rows.length) return
|
||||
const keys = new Set(rows.map((row) => rowKeyForMatch(row)))
|
||||
matchedItems.value = matchedItems.value.filter((row) => !keys.has(rowKeyForMatch(row)))
|
||||
saveMatchedItemsToStorage()
|
||||
}
|
||||
|
||||
async function removeMatchedRow(row: PriceTrackShopQueueItem) {
|
||||
removeMatchedRowsLocally([row])
|
||||
ElMessage.success('已从匹配结果中移除')
|
||||
const key = rowKeyForMatch(row)
|
||||
const backup = [...matchedItems.value]
|
||||
matchedItems.value = matchedItems.value.filter((r) => rowKeyForMatch(r) !== key)
|
||||
saveMatchedItemsToStorage()
|
||||
const name = (row.shopName || '').trim()
|
||||
if (!name) {
|
||||
ElMessage.success('已从匹配结果中移除')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await deletePendingPriceTrackShopResult(name)
|
||||
if (res?.removed) {
|
||||
await loadHistory()
|
||||
await loadDashboard()
|
||||
syncPollingIdsWithHistory()
|
||||
}
|
||||
ElMessage.success('已从匹配结果中移除')
|
||||
} catch (e) {
|
||||
matchedItems.value = backup
|
||||
saveMatchedItemsToStorage()
|
||||
ElMessage.error(e instanceof Error ? e.message : '同步后端失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function pushToPythonQueue() {
|
||||
@@ -582,11 +694,21 @@ async function pushToPythonQueue() {
|
||||
items: matchedRows as unknown as Record<string, unknown>[],
|
||||
asinFiles: asinFiles.value,
|
||||
countryCodes: [...orderedCountryCodes.value],
|
||||
minPrice: minPrice.value,
|
||||
maxPrice: maxPrice.value,
|
||||
priceDiffPercent: priceDiffPercent.value,
|
||||
}
|
||||
const taskVo = await createPriceTrackTask(taskReq)
|
||||
taskSnapshots.value = {
|
||||
...taskSnapshots.value,
|
||||
[taskVo.taskId]: {
|
||||
task: { id: taskVo.taskId, status: 'RUNNING' },
|
||||
items: taskVo.items,
|
||||
},
|
||||
}
|
||||
taskDetails.value = {
|
||||
...taskDetails.value,
|
||||
[taskVo.taskId]: 'RUNNING',
|
||||
}
|
||||
saveTaskSnapshotsToStorage()
|
||||
saveTaskDetailsToStorage()
|
||||
|
||||
// 2. 推送 taskId 给 Python,Python 会从 Java 拉取完整任务上下文
|
||||
const queuePayload = {
|
||||
@@ -605,6 +727,7 @@ async function pushToPythonQueue() {
|
||||
if (pushResult?.success) {
|
||||
queuePushResult.value = `任务 ${taskVo.taskId} 已入队,等待执行完成...`
|
||||
addPollingTask(taskVo.taskId)
|
||||
scheduleNextPoll(true)
|
||||
ElMessage.success('已推送到 Python 队列')
|
||||
} else {
|
||||
queuePushResult.value = `推送失败:${pushResult?.error || '未知错误'}`
|
||||
@@ -627,24 +750,37 @@ async function refreshTaskBatch() {
|
||||
const ids = pollingTaskIds.value.filter((id) => id > 0)
|
||||
if (!ids.length) return
|
||||
try {
|
||||
await loadHistory()
|
||||
// 检查是否有任务结束,停止轮询
|
||||
const terminalIds = ids.filter((id) => isTaskTerminal(id))
|
||||
for (const id of terminalIds) {
|
||||
removePollingTask(id)
|
||||
const batch = await getPriceTrackTasksBatch(ids)
|
||||
let changed = false
|
||||
const nextSnapshots = { ...taskSnapshots.value }
|
||||
for (const missingId of batch.missingTaskIds || []) {
|
||||
removePollingTask(missingId)
|
||||
delete nextSnapshots[missingId]
|
||||
}
|
||||
for (const detail of batch.items || []) {
|
||||
const taskId = detail.task?.id
|
||||
const status = detail.task?.status
|
||||
if (typeof taskId !== 'number' || taskId <= 0) continue
|
||||
nextSnapshots[taskId] = detail
|
||||
if (status) {
|
||||
taskDetails.value[taskId] = status
|
||||
if (status === 'SUCCESS' || status === 'FAILED') {
|
||||
removePollingTask(taskId)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
taskSnapshots.value = nextSnapshots
|
||||
saveTaskSnapshotsToStorage()
|
||||
saveTaskDetailsToStorage()
|
||||
await loadHistory()
|
||||
syncPollingIdsWithHistory()
|
||||
if (changed) await loadDashboard()
|
||||
} catch {
|
||||
/* polling noise */
|
||||
}
|
||||
}
|
||||
|
||||
function isTaskTerminal(taskId: number) {
|
||||
const rows = historyItems.value.filter((r) => r.taskId === taskId)
|
||||
if (!rows.length) return false
|
||||
// 全部行都有终态才停止
|
||||
return rows.every((r) => r.taskStatus === 'SUCCESS' || r.taskStatus === 'FAILED')
|
||||
}
|
||||
|
||||
function scheduleNextPoll(immediate = false) {
|
||||
if (pollTimer.value) {
|
||||
if (!immediate) return
|
||||
@@ -670,17 +806,33 @@ function stopPolling() {
|
||||
function addPollingTask(taskId: number) {
|
||||
if (!pollingTaskIds.value.includes(taskId)) {
|
||||
pollingTaskIds.value = [...pollingTaskIds.value, taskId]
|
||||
savePollingIds()
|
||||
}
|
||||
}
|
||||
|
||||
function removePollingTask(taskId: number) {
|
||||
pollingTaskIds.value = pollingTaskIds.value.filter((id) => id !== taskId)
|
||||
savePollingIds()
|
||||
delete taskDetails.value[taskId]
|
||||
saveTaskDetailsToStorage()
|
||||
if (taskSnapshots.value[taskId]) {
|
||||
const next = { ...taskSnapshots.value }
|
||||
delete next[taskId]
|
||||
taskSnapshots.value = next
|
||||
saveTaskSnapshotsToStorage()
|
||||
}
|
||||
}
|
||||
|
||||
const currentSectionItems = computed(() =>
|
||||
historyItems.value.filter((row) => row.taskId && pollingTaskIds.value.includes(row.taskId) && !isTaskTerminalById(row.taskId))
|
||||
)
|
||||
const currentSectionItems = computed(() => {
|
||||
const out = historyItems.value.filter((row) => row.taskId && pollingTaskIds.value.includes(row.taskId) && !isTaskTerminalById(row.taskId))
|
||||
const existingTaskIds = new Set(out.map((row) => row.taskId).filter((id): id is number => typeof id === 'number'))
|
||||
for (const taskId of pollingTaskIds.value) {
|
||||
if (existingTaskIds.has(taskId)) continue
|
||||
const first = taskSnapshots.value[taskId]?.items?.[0]
|
||||
if (first) out.push(first)
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
const historySectionItems = computed(() =>
|
||||
historyItems.value.filter((row) => !row.taskId || !pollingTaskIds.value.includes(row.taskId) || isTaskTerminalById(row.taskId))
|
||||
@@ -690,7 +842,7 @@ const hasVisibleList = computed(() => currentSectionItems.value.length > 0 || hi
|
||||
|
||||
function taskStatusOf(taskId?: number) {
|
||||
if (!taskId) return ''
|
||||
return taskDetails.value[taskId] || ''
|
||||
return taskDetails.value[taskId] || taskSnapshots.value[taskId]?.task?.status || ''
|
||||
}
|
||||
|
||||
function isTaskTerminalById(taskId?: number) {
|
||||
@@ -729,7 +881,7 @@ function canDownload(item: PriceTrackHistoryItem) {
|
||||
async function downloadResult(item: PriceTrackHistoryItem) {
|
||||
if (!item.resultId) return
|
||||
const api = getPywebviewApi()
|
||||
const filename = item.outputFilename || `${item.shopName || 'result'}.zip`
|
||||
const filename = item.outputFilename || `${item.shopName || 'result'}.xlsx`
|
||||
if (!api?.save_file_from_url_new) {
|
||||
ElMessage.error('当前客户端未提供 save_file_from_url_new,无法下载文件')
|
||||
return
|
||||
@@ -747,10 +899,14 @@ async function deleteTaskRecord(item: PriceTrackHistoryItem) {
|
||||
try {
|
||||
if (item.resultId != null) {
|
||||
await deletePriceTrackHistory(item.resultId)
|
||||
if (item.taskId != null) removePollingTask(item.taskId)
|
||||
} else if (item.taskId != null) {
|
||||
await deletePriceTrackTask(item.taskId)
|
||||
}
|
||||
if (item.taskId != null) removePollingTask(item.taskId)
|
||||
removeMatchedRowsLocally([item])
|
||||
await loadHistory()
|
||||
await loadDashboard()
|
||||
syncPollingIdsWithHistory()
|
||||
ElMessage.success('已删除')
|
||||
} catch (e) {
|
||||
ElMessage.error(e instanceof Error ? e.message : '删除失败')
|
||||
@@ -759,6 +915,10 @@ async function deleteTaskRecord(item: PriceTrackHistoryItem) {
|
||||
|
||||
// ========== 生命周期 ==========
|
||||
onMounted(() => {
|
||||
loadPollingIdsFromStorage()
|
||||
loadTaskDetailsFromStorage()
|
||||
loadTaskSnapshotsFromStorage()
|
||||
loadMatchedItemsFromStorage()
|
||||
loadCandidates().catch(() => undefined)
|
||||
loadCountryPreference().catch(() => undefined)
|
||||
loadHistory().catch(() => undefined)
|
||||
@@ -1085,49 +1245,12 @@ onUnmounted(() => {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.price-filter-zone {
|
||||
border: 1px dashed #3a3a3a;
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
background: #252525;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.price-input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-size: 12px;
|
||||
color: #aaa;
|
||||
width: 60px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.price-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.price-unit {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
width: 30px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.price-input-row :deep(.el-input__wrapper) {
|
||||
background-color: #2a2a2a;
|
||||
box-shadow: 0 0 0 1px #3a3a3a inset;
|
||||
}
|
||||
|
||||
.price-input-row :deep(.el-input__inner) {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.empty-candidates {
|
||||
color: #666;
|
||||
|
||||
@@ -215,6 +215,7 @@ async function loadCandidates() { candidates.value = await listShopMatchCandidat
|
||||
async function loadDashboard() { dashboard.value = await getShopMatchDashboard() }
|
||||
async function loadHistory() { const data = await getShopMatchHistory(); historyItems.value = data.items || [] }
|
||||
async function loadCountryPreference() { try { const data = await getShopMatchCountryPreference(); if (countryPrefUserTouched.value) return; const codes = (data.country_codes || []).filter((item) => COUNTRY_OPTIONS.some((option) => option.code === item)); if (codes.length) orderedCountryCodes.value = codes } catch {} }
|
||||
async function refreshTaskViewsBestEffort() { await Promise.allSettled([loadHistory(), loadDashboard()]) }
|
||||
function scheduleSaveCountryPreference() { if (countryPrefSaveTimer) clearTimeout(countryPrefSaveTimer); countryPrefSaveTimer = setTimeout(() => { countryPrefSaveTimer = null; void saveCountryPreference() }, 450) }
|
||||
async function saveCountryPreference() { if (!orderedCountryCodes.value.length) return; countryPrefSaving.value = true; try { await putShopMatchCountryPreference(orderedCountryCodes.value) } catch (error) { ElMessage.error(error instanceof Error ? error.message : '保存国家配置失败') } finally { countryPrefSaving.value = false } }
|
||||
function onSelectionChange(rows: ShopMatchCandidateVo[]) { selectedCandidates.value = rows }
|
||||
@@ -252,7 +253,7 @@ function getPollIntervalMs() { return document.visibilityState === 'visible' ? 4
|
||||
function scheduleNextPoll(immediate = false) { if (pollTimer.value) { if (!immediate) return; window.clearTimeout(pollTimer.value); pollTimer.value = null } const run = async () => { pollTimer.value = null; if (pollingInFlight.value || !pollingTaskIds.value.length) { if (pollingTaskIds.value.length) scheduleNextPoll(); return } pollingInFlight.value = true; try { const { settledTaskIds } = await refreshTaskBatch(); if (settledTaskIds.length) await Promise.allSettled([loadHistory(), loadDashboard()]) } finally { pollingInFlight.value = false } if (pollingTaskIds.value.length) pollTimer.value = window.setTimeout(run, getPollIntervalMs()) }; if (immediate) void run(); else pollTimer.value = window.setTimeout(run, getPollIntervalMs()) }
|
||||
function ensurePolling(immediate = false) { scheduleNextPoll(immediate) }
|
||||
function stopPolling() { if (pollTimer.value) { window.clearTimeout(pollTimer.value); pollTimer.value = null } }
|
||||
async function waitForTaskTerminal(taskId: number) { while (true) { const batch = await getShopMatchTasksBatch([taskId]); if ((batch.missingTaskIds || []).includes(taskId)) { removeTaskLocally(taskId); await loadHistory(); await loadDashboard(); restoreScheduledDispatches(); return 'FAILED' } const detail = (batch.items || []).find((item) => item.task?.id === taskId); const status = detail?.task?.status || ''; if (detail) { taskSnapshots.value = { ...taskSnapshots.value, [taskId]: detail }; saveTaskSnapshotsToStorage() } if (status) { taskDetails.value[taskId] = status; saveTaskDetailsToStorage() } if (isTaskTerminalStatus(status)) { removePollingTask(taskId); await loadHistory(); await loadDashboard(); restoreScheduledDispatches(); return status } await new Promise<void>((resolve) => { window.setTimeout(() => resolve(), getPollIntervalMs()) }) } }
|
||||
async function waitForTaskTerminal(taskId: number) { while (true) { const batch = await getShopMatchTasksBatch([taskId]); if ((batch.missingTaskIds || []).includes(taskId)) { removeTaskLocally(taskId); await refreshTaskViewsBestEffort(); restoreScheduledDispatches(); return 'FAILED' } const detail = (batch.items || []).find((item) => item.task?.id === taskId); const status = detail?.task?.status || ''; if (detail) { taskSnapshots.value = { ...taskSnapshots.value, [taskId]: detail }; saveTaskSnapshotsToStorage() } if (status) { taskDetails.value[taskId] = status; saveTaskDetailsToStorage() } if (isTaskTerminalStatus(status)) { removePollingTask(taskId); await refreshTaskViewsBestEffort(); restoreScheduledDispatches(); return status } await new Promise<void>((resolve) => { window.setTimeout(() => resolve(), getPollIntervalMs()) }) } }
|
||||
function resolvedTaskStatus(item: ShopMatchHistoryItem) { const taskId = normalizeTaskId(item.taskId); if (!taskId) return item.taskStatus || ''; return taskDetails.value[taskId] || taskSnapshots.value[taskId]?.task?.status || item.taskStatus || '' }
|
||||
function taskSnapshotOf(item: ShopMatchHistoryItem) { const taskId = normalizeTaskId(item.taskId); return taskId ? taskSnapshots.value[taskId] : undefined }
|
||||
function currentTaskStageText(item: ShopMatchHistoryItem) { const snapshot = taskSnapshotOf(item); const task = snapshot?.task; const stages = task?.scheduleStages || []; if (!stages.length) return ''; const total = stages.length; const activeIndex = typeof task?.activeStageIndex === 'number' ? task.activeStageIndex : undefined; const currentIndex = typeof task?.currentStageIndex === 'number' ? task.currentStageIndex : undefined; if (typeof activeIndex === 'number') return `执行进度: 第 ${activeIndex + 1}/${total} 次执行中`; if (typeof currentIndex === 'number') return `执行进度: 等待第 ${currentIndex + 1}/${total} 次`; return `执行进度: 共 ${total} 次`}
|
||||
|
||||
@@ -2,6 +2,7 @@ export const LISTING_FILTER_OPTIONS = [
|
||||
{ value: 'SearchSuppressed', label: '在搜索结果中禁止显示' },
|
||||
{ value: 'ApprovalRequired', label: '需要批准' },
|
||||
{ value: 'Active', label: '在售' },
|
||||
{ value: 'DetailPageRemoved', label: '详情页面已删除' },
|
||||
] as const
|
||||
|
||||
export type ListingFilterValue = (typeof LISTING_FILTER_OPTIONS)[number]['value']
|
||||
|
||||
@@ -972,9 +972,38 @@ export interface PriceTrackHistoryVo {
|
||||
|
||||
export interface PriceTrackCreateTaskVo {
|
||||
taskId: number;
|
||||
items: PriceTrackShopQueueItem[];
|
||||
items: PriceTrackHistoryItem[];
|
||||
}
|
||||
|
||||
export interface PriceTrackTaskSummary {
|
||||
id?: number;
|
||||
taskNo?: string;
|
||||
status?: string;
|
||||
errorMessage?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
finishedAt?: string;
|
||||
}
|
||||
|
||||
export interface PriceTrackTaskDetailVo {
|
||||
task?: PriceTrackTaskSummary;
|
||||
items?: PriceTrackHistoryItem[];
|
||||
}
|
||||
|
||||
export interface PriceTrackTaskBatchVo {
|
||||
items: PriceTrackTaskDetailVo[];
|
||||
missingTaskIds?: number[];
|
||||
}
|
||||
|
||||
export interface PriceTrackPendingDeleteVo {
|
||||
removed: boolean;
|
||||
}
|
||||
|
||||
export type PriceTrackCreateTaskPayload = Omit<
|
||||
PriceTrackCreateTaskRequest,
|
||||
"userId"
|
||||
>;
|
||||
|
||||
export function listPriceTrackCandidates() {
|
||||
return unwrapJavaResponse(
|
||||
get<JavaApiResponse<PriceTrackCandidateVo[]>>(
|
||||
@@ -985,9 +1014,9 @@ export function listPriceTrackCandidates() {
|
||||
|
||||
export function addPriceTrackCandidate(shopName: string) {
|
||||
return unwrapJavaResponse(
|
||||
post<JavaApiResponse<PriceTrackCandidateVo>, { user_id: number; shopName: string }>(
|
||||
post<JavaApiResponse<PriceTrackCandidateVo>, { userId: number; shopName: string }>(
|
||||
`${JAVA_API_PREFIX}/price-track/candidates`,
|
||||
{ user_id: getCurrentUserId(), shopName },
|
||||
{ userId: getCurrentUserId(), shopName },
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1010,18 +1039,18 @@ export function getPriceTrackCountryPreference() {
|
||||
|
||||
export function putPriceTrackCountryPreference(countryCodes: string[]) {
|
||||
return unwrapJavaResponse(
|
||||
put<JavaApiResponse<PriceTrackCountryPreferenceVo>, { user_id: number; countryCodes: string[] }>(
|
||||
put<JavaApiResponse<PriceTrackCountryPreferenceVo>, { userId: number; countryCodes: string[] }>(
|
||||
`${JAVA_API_PREFIX}/price-track/country-preference`,
|
||||
{ user_id: getCurrentUserId(), countryCodes },
|
||||
{ userId: getCurrentUserId(), countryCodes },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function matchPriceTrackShops(shopNames: string[]) {
|
||||
return unwrapJavaResponse(
|
||||
post<JavaApiResponse<PriceTrackMatchShopsVo>, { user_id: number; shopNames: string[] }>(
|
||||
post<JavaApiResponse<PriceTrackMatchShopsVo>, { userId: number; shopNames: string[] }>(
|
||||
`${JAVA_API_PREFIX}/price-track/match-shops`,
|
||||
{ user_id: getCurrentUserId(), shopNames },
|
||||
{ userId: getCurrentUserId(), shopNames },
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1057,16 +1086,15 @@ export interface PriceTrackCreateTaskRequest {
|
||||
items: PriceTrackShopQueueItem[];
|
||||
asinFiles: string[];
|
||||
countryCodes: string[];
|
||||
minPrice?: number;
|
||||
maxPrice?: number;
|
||||
priceDiffPercent?: number;
|
||||
}
|
||||
|
||||
export function createPriceTrackTask(request: PriceTrackCreateTaskRequest) {
|
||||
export function createPriceTrackTask(
|
||||
request: PriceTrackCreateTaskPayload | PriceTrackCreateTaskRequest,
|
||||
) {
|
||||
return unwrapJavaResponse(
|
||||
post<JavaApiResponse<PriceTrackCreateTaskVo>, PriceTrackCreateTaskRequest>(
|
||||
`${JAVA_API_PREFIX}/price-track/tasks`,
|
||||
{ ...request, user_id: getCurrentUserId() },
|
||||
{ ...request, userId: getCurrentUserId() },
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1079,13 +1107,22 @@ export function deletePriceTrackTask(taskId: number) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getPriceTrackTasksBatch(taskIds: number[]) {
|
||||
return unwrapJavaResponse(
|
||||
post<JavaApiResponse<PriceTrackTaskBatchVo>, { taskIds: number[] }>(
|
||||
`${JAVA_API_PREFIX}/price-track/tasks/batch`,
|
||||
{ taskIds },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function getPriceTrackResultDownloadUrl(resultId: number) {
|
||||
return getJavaDownloadUrl(`/price-track/results/${resultId}/download`);
|
||||
}
|
||||
|
||||
export function deletePendingPriceTrackShopResult(shopName: string) {
|
||||
return unwrapJavaResponse(
|
||||
del<JavaApiResponse<null>>(
|
||||
del<JavaApiResponse<PriceTrackPendingDeleteVo>>(
|
||||
`${JAVA_API_PREFIX}/price-track/pending-shop-result?user_id=${encodeURIComponent(String(getCurrentUserId()))}&shop_name=${encodeURIComponent(shopName)}`,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -11,6 +11,15 @@ export interface PywebviewApi {
|
||||
minimize?: () => Promise<void>;
|
||||
maximize?: () => Promise<void>;
|
||||
toggle_maximize?: () => Promise<void>;
|
||||
select_files?: (options?: {
|
||||
filters?: Array<{
|
||||
name?: string;
|
||||
extensions?: string;
|
||||
}>;
|
||||
multiple?: boolean;
|
||||
}) => Promise<{
|
||||
paths?: string[];
|
||||
} | null>;
|
||||
save_image?: (
|
||||
urlOrData: string,
|
||||
filename?: string,
|
||||
|
||||
Reference in New Issue
Block a user