完成查询asin开发
This commit is contained in:
13
frontend-vue/query-asin.html
Normal file
13
frontend-vue/query-asin.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>巡店删除 - 数富AI</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/query-asin-main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -620,6 +620,11 @@ function clearActiveQueueTask() {
|
||||
saveQueueState();
|
||||
}
|
||||
|
||||
function isRecordMissingError(error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error || "");
|
||||
return /记录不存在|任务不存在|不存在|已删除|not\s*found|404/i.test(message);
|
||||
}
|
||||
|
||||
function removeMatchedRowLocally(row: PatrolDeleteShopQueueItem) {
|
||||
matchedItems.value = matchedItems.value.filter(
|
||||
(item) => rowKeyForMatch(item) !== rowKeyForMatch(row),
|
||||
@@ -627,6 +632,14 @@ function removeMatchedRowLocally(row: PatrolDeleteShopQueueItem) {
|
||||
saveMatchedItems();
|
||||
}
|
||||
|
||||
function removeHistoryItemLocally(item: PatrolDeleteHistoryItem) {
|
||||
historyItems.value = historyItems.value.filter((row) => {
|
||||
if (item.resultId != null && row.resultId === item.resultId) return false;
|
||||
if (item.taskId != null && row.taskId === item.taskId) return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function mergeQueueItems(
|
||||
base: PatrolDeleteShopQueueItem[],
|
||||
incoming: PatrolDeleteShopQueueItem[],
|
||||
@@ -1013,6 +1026,15 @@ async function deleteTaskRecord(item: PatrolDeleteHistoryItem) {
|
||||
await Promise.all([loadCandidates(), refreshTaskViews()]);
|
||||
ElMessage.success("已删除");
|
||||
} catch (error) {
|
||||
if (isRecordMissingError(error)) {
|
||||
if (item.taskId && activeTaskId.value === item.taskId) {
|
||||
clearActiveQueueTask();
|
||||
}
|
||||
removeHistoryItemLocally(item);
|
||||
await loadCandidates();
|
||||
ElMessage.success("后端记录已不存在,已同步移除本地记录");
|
||||
return;
|
||||
}
|
||||
ElMessage.error(error instanceof Error ? error.message : "删除失败");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,6 +818,11 @@ function rowKeyForMatch(row: { shopName?: string; shopId?: number | string | nul
|
||||
return `${(row.shopName || '').trim()}\u0001${row.shopId ?? ''}`
|
||||
}
|
||||
|
||||
function isRecordMissingError(error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error || '')
|
||||
return /记录不存在|不存在|已删除|not\s*found|404/i.test(message)
|
||||
}
|
||||
|
||||
function buildSkipAsinDeletePolicy() {
|
||||
const enabled = statusModeEnabled.value && !asinModeEnabled.value
|
||||
return {
|
||||
@@ -843,6 +848,14 @@ function removeMatchedRowsLocally(rows: Array<{ shopName?: string; shopId?: numb
|
||||
saveMatchedItemsToStorage()
|
||||
}
|
||||
|
||||
function removeHistoryItemLocally(item: PriceTrackHistoryItem) {
|
||||
historyItems.value = historyItems.value.filter((row) => {
|
||||
if (item.resultId != null && row.resultId === item.resultId) return false
|
||||
if (item.taskId != null && row.taskId === item.taskId) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
async function removeMatchedRow(row: PriceTrackShopQueueItem) {
|
||||
const backup = [...matchedItems.value]
|
||||
removeMatchedRowsLocally([row])
|
||||
@@ -860,6 +873,13 @@ async function removeMatchedRow(row: PriceTrackShopQueueItem) {
|
||||
}
|
||||
ElMessage.success('已从匹配结果中移除')
|
||||
} catch (e) {
|
||||
if (isRecordMissingError(e)) {
|
||||
await loadHistory()
|
||||
await loadDashboard()
|
||||
syncPollingIdsWithHistory()
|
||||
ElMessage.success('后端记录已不存在,已同步移除本地记录')
|
||||
return
|
||||
}
|
||||
matchedItems.value = backup
|
||||
saveMatchedItemsToStorage()
|
||||
ElMessage.error(e instanceof Error ? e.message : '同步后端失败')
|
||||
@@ -1587,6 +1607,15 @@ async function deleteTaskRecord(item: PriceTrackHistoryItem) {
|
||||
syncPollingIdsWithHistory()
|
||||
ElMessage.success('已删除')
|
||||
} catch (e) {
|
||||
if (isRecordMissingError(e)) {
|
||||
if (item.taskId != null) removePollingTask(item.taskId)
|
||||
removeMatchedRowsLocally([item])
|
||||
removeHistoryItemLocally(item)
|
||||
await loadDashboard()
|
||||
syncPollingIdsWithHistory()
|
||||
ElMessage.success('后端记录已不存在,已同步移除本地记录')
|
||||
return
|
||||
}
|
||||
ElMessage.error(e instanceof Error ? e.message : '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,6 +469,11 @@ function rowKeyForMatch(row: { shopName?: string; shopId?: number | string | nul
|
||||
return `${name}\u0001${id}`
|
||||
}
|
||||
|
||||
function isRecordMissingError(error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error || '')
|
||||
return /记录不存在|任务不存在|不存在|已删除|not\s*found|404/i.test(message)
|
||||
}
|
||||
|
||||
function removeMatchedRowsLocally(rows: Array<{ shopName?: string; shopId?: number | string | null }>) {
|
||||
if (!rows.length) return
|
||||
const keys = new Set(rows.map((row) => rowKeyForMatch(row)))
|
||||
@@ -476,6 +481,14 @@ function removeMatchedRowsLocally(rows: Array<{ shopName?: string; shopId?: numb
|
||||
saveMatchedItemsToStorage()
|
||||
}
|
||||
|
||||
function removeHistoryItemLocally(item: ProductRiskHistoryItem) {
|
||||
historyItems.value = historyItems.value.filter((row) => {
|
||||
if (item.resultId != null && row.resultId === item.resultId) return false
|
||||
if (item.taskId != null && row.taskId === item.taskId) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
async function removeMatchedRow(row: ProductRiskShopQueueItem) {
|
||||
const key = rowKeyForMatch(row)
|
||||
const backup = [...matchedItems.value]
|
||||
@@ -495,6 +508,13 @@ async function removeMatchedRow(row: ProductRiskShopQueueItem) {
|
||||
}
|
||||
ElMessage.success('已从匹配结果中移除')
|
||||
} catch (e) {
|
||||
if (isRecordMissingError(e)) {
|
||||
await loadHistory()
|
||||
await loadDashboard()
|
||||
syncPollingIdsWithHistory()
|
||||
ElMessage.success('后端记录已不存在,已同步移除本地记录')
|
||||
return
|
||||
}
|
||||
matchedItems.value = backup
|
||||
saveMatchedItemsToStorage()
|
||||
ElMessage.error(e instanceof Error ? e.message : '同步后端失败')
|
||||
@@ -530,6 +550,17 @@ async function deleteTaskRecord(item: ProductRiskHistoryItem) {
|
||||
syncPollingIdsWithHistory()
|
||||
ElMessage.success('已删除')
|
||||
} catch (e) {
|
||||
if (isRecordMissingError(e)) {
|
||||
if (item.taskId != null && item.taskId > 0) {
|
||||
removePollingTask(item.taskId)
|
||||
}
|
||||
removeMatchedRowsLocally([item])
|
||||
removeHistoryItemLocally(item)
|
||||
await loadDashboard()
|
||||
syncPollingIdsWithHistory()
|
||||
ElMessage.success('后端记录已不存在,已同步移除本地记录')
|
||||
return
|
||||
}
|
||||
ElMessage.error(e instanceof Error ? e.message : '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
1167
frontend-vue/src/pages/brand/components/BrandQueryAsinTab.vue
Normal file
1167
frontend-vue/src/pages/brand/components/BrandQueryAsinTab.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -191,7 +191,7 @@ function loadTaskSnapshotsFromStorage() { try { const raw = window.localStorage.
|
||||
function saveTaskSnapshotsToStorage() { setStorageJson(taskSnapshotsStorageKey(), taskSnapshots.value, Object.keys(taskSnapshots.value).length === 0) }
|
||||
function loadMatchedItemsFromStorage() { try { const raw = window.localStorage.getItem(matchedItemsStorageKey()); matchedItems.value = raw ? JSON.parse(raw) : [] } catch { matchedItems.value = [] } }
|
||||
function saveMatchedItemsToStorage() { setStorageJson(matchedItemsStorageKey(), matchedItems.value, matchedItems.value.length === 0) }
|
||||
function isTaskMissingError(error: unknown) { const message = error instanceof Error ? error.message : String(error || ''); return message.includes('任务不存在') }
|
||||
function isTaskMissingError(error: unknown) { const message = error instanceof Error ? error.message : String(error || ''); return /记录不存在|任务不存在|不存在|已删除|not\s*found|404/i.test(message) }
|
||||
function syncPollingIdsWithTaskState() { const nextIds = pollingTaskIds.value.filter((taskId) => (taskDetails.value[taskId] || taskSnapshots.value[taskId]?.task?.status) === 'RUNNING'); if (nextIds.length === pollingTaskIds.value.length) return; pollingTaskIds.value = nextIds; savePollingIds() }
|
||||
function addPollingTask(taskId: number) { if (!pollingTaskIds.value.includes(taskId)) { pollingTaskIds.value = [...pollingTaskIds.value, taskId]; savePollingIds() } }
|
||||
function clearDispatchTimer(taskId: number) { const timer = dispatchTimers.get(taskId); if (timer) { window.clearTimeout(timer); dispatchTimers.delete(taskId) } }
|
||||
@@ -334,10 +334,15 @@ async function deleteTaskRecord(item: ShopMatchHistoryItem) {
|
||||
await Promise.allSettled([loadHistory(), loadCandidates(), loadDashboard()])
|
||||
ElMessage.success('删除成功')
|
||||
} catch (error) {
|
||||
if (taskId > 0 && isTaskMissingError(error)) {
|
||||
removeTaskLocally(taskId)
|
||||
if (isTaskMissingError(error)) {
|
||||
if (taskId > 0) {
|
||||
removeTaskLocally(taskId)
|
||||
} else if (resultId > 0) {
|
||||
historyItems.value = historyItems.value.filter((row) => Number(row.resultId || 0) !== resultId)
|
||||
}
|
||||
removeMatchedRowsLocally([item])
|
||||
await Promise.allSettled([loadHistory(), loadCandidates(), loadDashboard()])
|
||||
ElMessage.success('任务已在服务端删除,本地记录已同步清理')
|
||||
ElMessage.success('后端记录已不存在,本地记录已同步清理')
|
||||
return
|
||||
}
|
||||
ElMessage.error(error instanceof Error ? error.message : '删除失败')
|
||||
|
||||
@@ -53,6 +53,7 @@ type ActiveNavKey =
|
||||
| 'shop-match'
|
||||
| 'pricing'
|
||||
| 'patrol-delete'
|
||||
| 'query-asin'
|
||||
|
||||
type NavItem = {
|
||||
key: string
|
||||
@@ -95,6 +96,7 @@ const navGroups: ReadonlyArray<NavGroup> = [
|
||||
{ key: 'shop-match', label: '定时匹配', href: '/new_web_source/shop-match.html' },
|
||||
{ key: 'pricing', label: '跟价', href: '/new_web_source/price-track.html' },
|
||||
{ key: 'patrol-delete', label: '巡店删除', href: '/new_web_source/patrol-delete.html' },
|
||||
{ key: 'query-asin', label: '查询ASIN', href: '/new_web_source/query-asin.html' },
|
||||
{ key: 'withdraw', label: '取款' },
|
||||
{ key: 'shop-status', label: '店铺状态查询' },
|
||||
],
|
||||
|
||||
13
frontend-vue/src/query-asin-main.ts
Normal file
13
frontend-vue/src/query-asin-main.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createApp } from 'vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
import 'element-plus/dist/index.css'
|
||||
import '@/styles/main.css'
|
||||
import BrandQueryAsinTab from '@/pages/brand/components/BrandQueryAsinTab.vue'
|
||||
|
||||
dayjs.locale('zh-cn')
|
||||
|
||||
createApp(BrandQueryAsinTab).use(ElementPlus, { locale: zhCn }).mount('#app')
|
||||
|
||||
@@ -581,6 +581,7 @@ export interface ProductRiskShopQueueItem {
|
||||
matchedUserId?: number;
|
||||
matchStatus?: string;
|
||||
matchMessage?: string;
|
||||
queryAsins?: QueryAsinCountryAsins[];
|
||||
}
|
||||
|
||||
export interface ProductRiskMatchShopsVo {
|
||||
@@ -1189,6 +1190,165 @@ export function deletePatrolDeleteHistory(resultId: number) {
|
||||
);
|
||||
}
|
||||
|
||||
export type QueryAsinCandidateVo = ProductRiskCandidateVo;
|
||||
export type QueryAsinDashboardVo = ProductRiskDashboardVo;
|
||||
export type QueryAsinShopQueueItem = ProductRiskShopQueueItem;
|
||||
|
||||
export interface QueryAsinCountryAsins {
|
||||
country: string;
|
||||
asins: string[];
|
||||
}
|
||||
|
||||
export interface QueryAsinTaskItem {
|
||||
shopName?: string;
|
||||
matched?: boolean;
|
||||
shopId?: string;
|
||||
platform?: string;
|
||||
companyName?: string;
|
||||
matchStatus?: string;
|
||||
matchMessage?: string;
|
||||
queryAsins: QueryAsinCountryAsins[];
|
||||
}
|
||||
|
||||
export interface QueryAsinHistoryItem {
|
||||
resultId?: number;
|
||||
taskId?: number;
|
||||
shopName?: string;
|
||||
shopId?: string;
|
||||
platform?: string;
|
||||
companyName?: string;
|
||||
matched?: boolean;
|
||||
matchStatus?: string;
|
||||
matchMessage?: string;
|
||||
taskStatus?: string;
|
||||
success?: boolean;
|
||||
error?: string;
|
||||
createdAt?: string;
|
||||
finishedAt?: string;
|
||||
outputFilename?: string;
|
||||
downloadUrl?: string;
|
||||
queryAsins: QueryAsinCountryAsins[];
|
||||
}
|
||||
|
||||
export interface QueryAsinHistoryVo {
|
||||
items: QueryAsinHistoryItem[];
|
||||
}
|
||||
|
||||
export interface QueryAsinTaskBatchVo {
|
||||
items: QueryAsinHistoryItem[];
|
||||
missingTaskIds: number[];
|
||||
}
|
||||
|
||||
export interface QueryAsinCreateTaskVo {
|
||||
taskId: number;
|
||||
items: QueryAsinHistoryItem[];
|
||||
}
|
||||
|
||||
export function listQueryAsinCandidates() {
|
||||
return unwrapJavaResponse(
|
||||
get<JavaApiResponse<QueryAsinCandidateVo[]>>(`${JAVA_API_PREFIX}/query-asin/candidates`, {
|
||||
params: { user_id: getCurrentUserId() },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function addQueryAsinCandidate(shopName: string) {
|
||||
return unwrapJavaResponse(
|
||||
post<JavaApiResponse<QueryAsinCandidateVo>, { user_id: number; shop_name: string }>(
|
||||
`${JAVA_API_PREFIX}/query-asin/candidates`,
|
||||
{ user_id: getCurrentUserId(), shop_name: shopName },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function deleteQueryAsinCandidate(id: number) {
|
||||
return unwrapJavaResponse(
|
||||
del<JavaApiResponse<null>>(`${JAVA_API_PREFIX}/query-asin/candidates/${id}`, {
|
||||
params: { user_id: getCurrentUserId() },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function matchQueryAsinShops(shopNames: string[]) {
|
||||
return unwrapJavaResponse(
|
||||
post<JavaApiResponse<ProductRiskMatchShopsVo>, { user_id: number; shop_names: string[] }>(
|
||||
`${JAVA_API_PREFIX}/query-asin/match-shops`,
|
||||
{ user_id: getCurrentUserId(), shop_names: shopNames },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function getQueryAsinDashboard() {
|
||||
return unwrapJavaResponse(
|
||||
get<JavaApiResponse<QueryAsinDashboardVo>>(`${JAVA_API_PREFIX}/query-asin/dashboard`, {
|
||||
params: { user_id: getCurrentUserId() },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function getQueryAsinHistory() {
|
||||
return unwrapJavaResponse(
|
||||
get<JavaApiResponse<QueryAsinHistoryVo>>(`${JAVA_API_PREFIX}/query-asin/history`, {
|
||||
params: { user_id: getCurrentUserId() },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function getQueryAsinTaskProgressBatch(taskIds: number[]) {
|
||||
return postTaskProgressBatch<QueryAsinTaskBatchVo>(
|
||||
`${JAVA_API_PREFIX}/query-asin/tasks/progress/batch`,
|
||||
taskIds,
|
||||
);
|
||||
}
|
||||
|
||||
export function createQueryAsinTask(items: QueryAsinTaskItem[]) {
|
||||
return unwrapJavaResponse(
|
||||
post<JavaApiResponse<QueryAsinCreateTaskVo>, { user_id: number; items: QueryAsinTaskItem[] }>(
|
||||
`${JAVA_API_PREFIX}/query-asin/tasks`,
|
||||
{ user_id: getCurrentUserId(), items },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function submitQueryAsinTaskResult(
|
||||
taskId: number,
|
||||
payload: {
|
||||
shops: Array<{
|
||||
shopName: string;
|
||||
error?: string;
|
||||
queryAsins: QueryAsinCountryAsins[];
|
||||
shopDone?: boolean;
|
||||
submissionId?: string;
|
||||
chunkIndex?: number;
|
||||
chunkTotal?: number;
|
||||
}>;
|
||||
},
|
||||
) {
|
||||
return unwrapJavaResponse(
|
||||
post<JavaApiResponse<null>, typeof payload>(`${JAVA_API_PREFIX}/query-asin/tasks/${taskId}/result`, payload),
|
||||
);
|
||||
}
|
||||
|
||||
export function getQueryAsinResultDownloadUrl(resultId: number) {
|
||||
return getJavaDownloadUrl(`/query-asin/results/${resultId}/download`);
|
||||
}
|
||||
|
||||
export function deleteQueryAsinTask(taskId: number) {
|
||||
return unwrapJavaResponse(
|
||||
del<JavaApiResponse<null>>(`${JAVA_API_PREFIX}/query-asin/tasks/${taskId}`, {
|
||||
params: { user_id: getCurrentUserId() },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function deleteQueryAsinHistory(resultId: number) {
|
||||
return unwrapJavaResponse(
|
||||
del<JavaApiResponse<null>>(`${JAVA_API_PREFIX}/query-asin/history/${resultId}`, {
|
||||
params: { user_id: getCurrentUserId() },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// ========== 跟价 ==========
|
||||
|
||||
export interface PriceTrackCandidateVo {
|
||||
|
||||
@@ -50,6 +50,7 @@ export default defineConfig({
|
||||
'shop-match': resolve(__dirname, 'shop-match.html'),
|
||||
'price-track': resolve(__dirname, 'price-track.html'),
|
||||
'patrol-delete': resolve(__dirname, 'patrol-delete.html'),
|
||||
'query-asin': resolve(__dirname, 'query-asin.html'),
|
||||
},
|
||||
output: {
|
||||
entryFileNames: 'assets/[name].js',
|
||||
|
||||
Reference in New Issue
Block a user