后台管理跳过asin、后台相关数据做数据隔离

This commit is contained in:
super
2026-04-14 10:04:50 +08:00
parent 9ad6e037dc
commit ecc8b6ce6c
60 changed files with 1659 additions and 365 deletions

View File

@@ -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 给 PythonPython 会从 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;

View File

@@ -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}`}

View File

@@ -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']

View File

@@ -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)}`,
),
);

View File

@@ -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,