后端优化修复问题

This commit is contained in:
super
2026-06-18 18:53:16 +08:00
parent 021b0c618b
commit 0ecf5cd45a
44 changed files with 4275 additions and 81 deletions

View File

@@ -849,9 +849,9 @@ function buildSkipAsinDeletePolicy() {
return {
enabled,
mode: enabled ? 'DELETE_WHEN_PRICE_BELOW_MINIMUM' : 'NONE',
deleteWhenPriceBelowMinimum: enabled,
compareField: 'price',
thresholdField: 'minimumPrice',
delete_when_price_below_minimum: enabled,
compare_field: 'price',
threshold_field: 'minimum_price',
target: 'skip_asin',
source: 'frontend-price-track',
}
@@ -959,12 +959,14 @@ async function pushToPythonQueueLegacy() {
ts: Date.now(),
data: {
task_id: taskVo.taskId,
// 店铺简要信息(用于展示)
shop_names: matchedRows.map((i) => i.shopName).filter(Boolean),
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,
deleteSkipAsinWhenPriceBelowMinimum: statusModeEnabled.value && !asinModeEnabled.value,
},
}
queuePayloadText.value = JSON.stringify(queuePayload, null, 2)
@@ -992,6 +994,7 @@ function buildQueuePayload(taskVo: PriceTrackCreateTaskVo, row: PriceTrackShopQu
const taskItem = taskVo.items?.[0]
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
@@ -1024,28 +1027,12 @@ function buildQueuePayload(taskVo: PriceTrackCreateTaskVo, row: PriceTrackShopQu
round_index: roundIndex,
country_codes: resolveCountryCodesForRequest(),
mode: statusModeEnabled.value ? 'status' : 'asin',
// 分页拉取配置Python 端统一处理)
use_paginated_skip_asins: true,
skip_asin_page_size: 1000,
// 移除:不再传递任何 ASIN 数据Python 端统一分页拉取
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.deleteWhenPriceBelowMinimum,
deleteSkipAsinWhenPriceBelowMinimum: skipAsinDeletePolicy.deleteWhenPriceBelowMinimum,
// 兼容字段
taskId: taskVo.taskId,
resultId: resultId,
shopName,
shopId: row.shopId ?? null,
platformName: row.platform || '',
companyName: row.companyName || '',
shopMallName: row.shopMallName || '',
matchStatus: row.matchStatus || '',
matchMessage: row.matchMessage || null,
outputFilename: outputFilename,
downloadUrl: downloadUrl,
taskStatus,
loopRunId: loopRunId,
roundIndex: roundIndex,
delete_skip_asin_when_price_below_minimum: skipAsinDeletePolicy.delete_when_price_below_minimum,
},
}
}

View File

@@ -227,7 +227,18 @@ function isTaskTerminalItem(item: ShopMatchHistoryItem) {
}
async function loadCandidates() { candidates.value = await listShopMatchCandidates() }
async function loadDashboard() { dashboard.value = await getShopMatchDashboard() }
async function loadHistory() { const data = await getShopMatchHistory(); historyItems.value = data.items || [] }
async function loadHistory() { const data = await getShopMatchHistory(); const items = data.items || []; historyItems.value = items; reconcileTerminalTasksFromHistory(items) }
function reconcileTerminalTasksFromHistory(items: ShopMatchHistoryItem[]) {
const terminalTaskIds = new Set<number>()
for (const item of items) {
const taskId = normalizeTaskId(item.taskId)
if (!taskId) continue
if (isTaskTerminalStatus(item.taskStatus) || item.success === true || (item.fileStatus === 'SUCCESS' && item.fileReady)) {
terminalTaskIds.add(taskId)
}
}
for (const taskId of terminalTaskIds) removePollingTask(taskId)
}
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) timers.clearTimer('preference-save', countryPrefSaveTimer); countryPrefSaveTimer = timers.setTimeout('preference-save', () => { countryPrefSaveTimer = null; void saveCountryPreference() }, 450) }
@@ -394,7 +405,18 @@ async function waitForScheduledTaskStageExit(taskId: number) {
}
}
}
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 resolvedTaskStatus(item: ShopMatchHistoryItem) {
const itemStatus = item.taskStatus || ''
if (isTaskTerminalStatus(itemStatus)) return itemStatus
if (item.success === true || (item.fileStatus === 'SUCCESS' && item.fileReady)) return 'SUCCESS'
const taskId = normalizeTaskId(item.taskId)
if (!taskId) return itemStatus
const cachedStatus = taskDetails.value[taskId] || ''
const snapshotStatus = taskSnapshots.value[taskId]?.task?.status || ''
if (isTaskTerminalStatus(cachedStatus)) return cachedStatus
if (isTaskTerminalStatus(snapshotStatus)) return snapshotStatus
return cachedStatus || snapshotStatus || itemStatus
}
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}`}
function nextScheduledDisplay(item: ShopMatchHistoryItem) { const snapshot = taskSnapshotOf(item); const task = snapshot?.task; const stages = task?.scheduleStages || []; if (typeof task?.currentStageIndex === 'number') { const stage = stages.find((entry) => entry.stageIndex === task.currentStageIndex); if (stage?.scheduledAt) return formatMonthDayTime(stage.scheduledAt) } if (item.scheduledAt) return formatMonthDayTime(item.scheduledAt); return '' }

View File

@@ -51,6 +51,7 @@ type ActiveNavKey =
| 'pricing'
| 'patrol-delete'
| 'query-asin'
| 'withdraw'
| 'collect-data'
| 'image-video'
@@ -111,7 +112,7 @@ const navGroups: ReadonlyArray<NavGroup> = [
{ key: 'pricing', label: '跟价', href: '/new_web_source/price-track.html', aliases: ['price-track'] },
{ 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: 'withdraw', label: '取款', href: '/new_web_source/withdraw.html' },
{ key: 'shop-status', label: '店铺状态查询' },
],
},

File diff suppressed because it is too large Load Diff

View File

@@ -1485,6 +1485,180 @@ export function deleteQueryAsinHistory(resultId: number) {
);
}
// ========== 取款 ==========
export type WithdrawCandidateVo = QueryAsinCandidateVo;
export type WithdrawDashboardVo = QueryAsinDashboardVo;
export type WithdrawShopQueueItem = ProductRiskShopQueueItem;
export type WithdrawStatusCode =
| "ZERO_AVAILABLE"
| "SUCCESS"
| "BALANCE_FORBIDDEN"
| "NEGATIVE_WITHDRAW_BLANK";
export interface WithdrawRow {
country?: string;
shopAmount?: number | string | null;
withdrawAmount?: number | string | null;
status?: WithdrawStatusCode | string;
}
export interface WithdrawTaskItem {
shopName: string;
matched: boolean;
shopId?: string;
platform?: string;
companyName?: string;
matchStatus?: string;
matchMessage?: string;
}
export interface WithdrawHistoryItem extends WithdrawTaskItem {
resultId?: number;
taskId?: number;
taskStatus?: string;
reservedAmount?: number | string | null;
success?: boolean;
error?: string;
outputFilename?: string;
downloadUrl?: string;
fileJobId?: number;
fileStatus?: string;
fileError?: string;
fileReady?: boolean;
createdAt?: string;
finishedAt?: string;
rows?: WithdrawRow[];
shops?: WithdrawHistoryItem[];
}
export interface WithdrawHistoryVo {
items: WithdrawHistoryItem[];
}
export interface WithdrawTaskBatchVo {
items: WithdrawHistoryItem[];
missingTaskIds: number[];
}
export interface WithdrawCreateTaskVo {
taskId: number;
items: WithdrawHistoryItem[];
}
export function listWithdrawCandidates() {
return unwrapJavaResponse(
get<JavaApiResponse<WithdrawCandidateVo[]>>(`${JAVA_API_PREFIX}/withdraw/candidates`, {
params: { user_id: getCurrentUserId() },
}),
);
}
export function addWithdrawCandidate(shopName: string) {
return unwrapJavaResponse(
post<JavaApiResponse<WithdrawCandidateVo>, { user_id: number; shop_name: string }>(
`${JAVA_API_PREFIX}/withdraw/candidates`,
{ user_id: getCurrentUserId(), shop_name: shopName },
),
);
}
export function deleteWithdrawCandidate(id: number) {
return unwrapJavaResponse(
del<JavaApiResponse<null>>(`${JAVA_API_PREFIX}/withdraw/candidates/${id}`, {
params: { user_id: getCurrentUserId() },
}),
);
}
export function clearWithdrawCandidates(shopNames: string[]) {
return unwrapJavaResponse(
post<JavaApiResponse<null>, { user_id: number; shop_names: string[] }>(
`${JAVA_API_PREFIX}/withdraw/candidates/clear`,
{ user_id: getCurrentUserId(), shop_names: shopNames },
),
);
}
export function matchWithdrawShops(shopNames: string[]) {
return unwrapJavaResponse(
post<JavaApiResponse<ProductRiskMatchShopsVo>, { user_id: number; shop_names: string[] }>(
`${JAVA_API_PREFIX}/withdraw/match-shops`,
{ user_id: getCurrentUserId(), shop_names: shopNames },
),
);
}
export function getWithdrawDashboard() {
return unwrapJavaResponse(
get<JavaApiResponse<WithdrawDashboardVo>>(`${JAVA_API_PREFIX}/withdraw/dashboard`, {
params: { user_id: getCurrentUserId() },
}),
);
}
export function getWithdrawHistory() {
return unwrapJavaResponse(
get<JavaApiResponse<WithdrawHistoryVo>>(`${JAVA_API_PREFIX}/withdraw/history`, {
params: { user_id: getCurrentUserId() },
}),
);
}
export function getWithdrawTaskProgressBatch(taskIds: number[]) {
return postTaskProgressBatch<WithdrawTaskBatchVo>(
`${JAVA_API_PREFIX}/withdraw/tasks/progress/batch`,
taskIds,
);
}
export function createWithdrawTask(items: WithdrawTaskItem[], reservedAmount: number | string | null) {
return unwrapJavaResponse(
post<JavaApiResponse<WithdrawCreateTaskVo>, { user_id: number; reserved_amount: number | string | null; items: WithdrawTaskItem[] }>(
`${JAVA_API_PREFIX}/withdraw/tasks`,
{ user_id: getCurrentUserId(), reserved_amount: reservedAmount, items },
),
);
}
export function submitWithdrawTaskResult(
taskId: number,
payload: {
shops: Array<{
shopName: string;
error?: string;
rows?: WithdrawRow[];
shopDone?: boolean;
submissionId?: string;
}>;
},
) {
return unwrapJavaResponse(
post<JavaApiResponse<null>, typeof payload>(`${JAVA_API_PREFIX}/withdraw/tasks/${taskId}/result`, payload),
);
}
export function getWithdrawResultDownloadUrl(resultId: number) {
return getJavaDownloadUrl(`/withdraw/results/${resultId}/download`);
}
export function deleteWithdrawTask(taskId: number) {
return unwrapJavaResponse(
del<JavaApiResponse<null>>(`${JAVA_API_PREFIX}/withdraw/tasks/${taskId}`, {
params: { user_id: getCurrentUserId() },
}),
);
}
export function deleteWithdrawHistory(resultId: number) {
return unwrapJavaResponse(
del<JavaApiResponse<null>>(`${JAVA_API_PREFIX}/withdraw/history/${resultId}`, {
params: { user_id: getCurrentUserId() },
}),
);
}
// ========== 外观专利检测 ==========
export interface AppearancePatentParsedRow {
@@ -2235,6 +2409,27 @@ export function getTaskSkipPriceAsinsPaginated(
);
}
export function checkTaskSkipPriceAsin(taskId: number, country: string, asin: string) {
return unwrapJavaResponse(
get<JavaApiResponse<SkipPriceAsinCheckVo>>(
`${JAVA_API_PREFIX}/price-track/tasks/${taskId}/skip-asin/check`,
{
params: {
country,
asin,
},
},
),
);
}
export interface SkipPriceAsinCheckVo {
country: string;
asin: string;
exists: boolean;
minimumPrice?: string;
}
export interface SkipPriceAsinPageVo {
page: number;
pageSize: number;

View File

@@ -0,0 +1,14 @@
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 BrandWithdrawTab from '@/pages/brand/components/BrandWithdrawTab.vue'
dayjs.locale('zh-cn')
createApp(BrandWithdrawTab).use(ElementPlus, { locale: zhCn }).mount('#app')

View File

@@ -53,6 +53,7 @@ export default defineConfig({
'price-track': resolve(__dirname, 'price-track.html'),
'patrol-delete': resolve(__dirname, 'patrol-delete.html'),
'query-asin': resolve(__dirname, 'query-asin.html'),
withdraw: resolve(__dirname, 'withdraw.html'),
'collect-data': resolve(__dirname, 'collect-data.html'),
'image-video': resolve(__dirname, 'image-video.html'),
},

View File

@@ -0,0 +1,14 @@
<!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/withdraw-main.ts"></script>
</body>
</html>