完善任务存储、权限及货源查询流程
Build Backend JAR / build (push) Has been cancelled

This commit is contained in:
supernijia
2026-08-14 22:49:15 +08:00
parent 5b1ccad40e
commit 7a7f1dfa21
21 changed files with 2584 additions and 566 deletions
@@ -35,6 +35,30 @@
</label>
</div>
<div class="aliprice-card">
<div class="section-title">货源账号</div>
<label class="aliprice-field">
<span>账号</span>
<input
v-model="alipriceUsername"
type="text"
autocomplete="username"
placeholder="请输入 Aliprice 账号"
required
/>
</label>
<label class="aliprice-field">
<span>密码</span>
<input
v-model="alipricePassword"
type="password"
autocomplete="current-password"
placeholder="请输入 Aliprice 密码"
required
/>
</label>
</div>
<div class="run-row">
<button type="button" class="btn-run" :disabled="parsing || !uploadedFiles.length" @click="parseFiles">
{{ parsing ? '解析中...' : '解析并创建任务' }}
@@ -182,7 +206,7 @@ import {
uploadTempFileToJava,
} from '@/shared/api/java-modules'
import { expandBrandFolderRecursive, type BrandExpandFolderItem } from '@/shared/api/brand'
import { getPywebviewApi } from '@/shared/bridges/pywebview'
import { getPywebviewApi, type ProxyMode } from '@/shared/bridges/pywebview'
import { getTaskPollIntervalMs } from '@/shared/task-progress-config'
import { getStoredApiSecret } from '@/shared/utils/api-secret-store'
import { createCategorizedTimers } from '@/shared/utils/categorized-timers'
@@ -197,6 +221,8 @@ const parsing = ref(false)
const pushing = ref(false)
const imgSwitch = ref(false)
const categorySwitch = ref(false)
const alipriceUsername = ref('')
const alipricePassword = ref('')
const queuePayloadText = ref('')
const pollingTaskIds = ref<number[]>([])
const pendingFileTaskIds = ref<number[]>([])
@@ -206,6 +232,7 @@ const HISTORY_CACHE_TTL_MS = 3000
let historyInFlight: Promise<void> | null = null
let lastHistoryLoadedAt = 0
let disposed = false
let pywebviewReadyHandler: (() => void) | null = null
const timers = createCategorizedTimers('similar-asin')
const dashboard = ref<SimilarAsinDashboardVo>({
@@ -280,6 +307,16 @@ function effectiveCozeApiKey() {
return getStoredApiSecret('similar-asin').trim()
}
function getRequiredAlipriceCredentials() {
const username = alipriceUsername.value.trim()
const password = alipricePassword.value
if (!username || !password.trim()) {
ElMessage.warning('请填写 Aliprice 账号和密码')
return null
}
return { username, password }
}
function formatDateTime(value?: string) {
if (!value) return '-'
const date = new Date(value)
@@ -306,6 +343,7 @@ function payloadForDisplay<T extends { data?: Record<string, unknown> }>(payload
? {
...payload.data,
api_key: maskSecret(String(payload.data.api_key || '')),
aliprice_pwd: maskSecret(String(payload.data.aliprice_pwd || '')),
}
: payload.data,
}
@@ -422,6 +460,7 @@ async function parseFiles() {
ElMessage.warning('请先在左上角设置中填写货源查询密钥')
return
}
if (!getRequiredAlipriceCredentials()) return
parsing.value = true
try {
const files: UploadedFileRef[] = uploadedFiles.value.map((f) => ({
@@ -458,8 +497,37 @@ async function pushToPythonQueue() {
ElMessage.warning('请先在左上角设置中填写货源查询密钥')
return
}
const alipriceCredentials = getRequiredAlipriceCredentials()
if (!alipriceCredentials) return
const alipriceUsename = alipriceCredentials.username
const alipricePwd = alipriceCredentials.password
pushing.value = true
try {
let proxyData: { proxy_url: string; proxy_mode: ProxyMode } | undefined
if (api.read_config) {
try {
const config = await api.read_config()
const proxyUrl = typeof config?.proxy_url === 'string' ? config.proxy_url.trim() : ''
if (proxyUrl) {
proxyData = {
proxy_url: proxyUrl,
proxy_mode: Number(config?.proxy_mode) === 2 ? 2 : 1,
}
}
} catch {
// Keep the existing queue behavior when proxy configuration is unavailable.
}
}
if (api.save_config) {
try {
await api.save_config({
aliprice_usename: alipriceUsename,
aliprice_pwd: alipricePwd,
})
} catch {
ElMessage.warning('Aliprice 账号配置保存失败,本次任务仍会继续')
}
}
const payload = {
type: 'similar-asin-run',
ts: Date.now(),
@@ -471,6 +539,9 @@ async function pushToPythonQueue() {
totalRows: currentParseResult.totalRows || 0,
acceptedRows: currentParseResult.acceptedRows || 0,
groupCount: currentParseResult.groupCount || 0,
aliprice_usename: alipriceUsename,
aliprice_pwd: alipricePwd,
...proxyData,
},
}
queuePayloadText.value = JSON.stringify(payloadForDisplay(payload), null, 2)
@@ -491,6 +562,19 @@ async function pushToPythonQueue() {
}
}
async function loadAlipriceConfig() {
const api = getPywebviewApi()
if (!api?.read_config) return
try {
const config = await api.read_config()
if (disposed) return
alipriceUsername.value = typeof config?.aliprice_usename === 'string' ? config.aliprice_usename : ''
alipricePassword.value = typeof config?.aliprice_pwd === 'string' ? config.aliprice_pwd : ''
} catch {
ElMessage.warning('Aliprice 账号配置读取失败')
}
}
function clearParsedTask() {
parseResult.value = null
queuePayloadText.value = ''
@@ -574,9 +658,14 @@ function scheduleNextPoll(immediate = false) {
pollTimer.value = null
if (disposed) return
if (!pollingTaskIds.value.length && !pendingFileTaskIds.value.length) return
await refreshTaskProgress()
if (!disposed && pollTimer.value == null && (pollingTaskIds.value.length || pendingFileTaskIds.value.length)) {
pollTimer.value = timers.setTimeout('task-poll', run, getTaskPollIntervalMs())
try {
await refreshTaskProgress()
} catch {
// A transient request failure must not stop progress polling permanently.
} finally {
if (!disposed && pollTimer.value == null && (pollingTaskIds.value.length || pendingFileTaskIds.value.length)) {
pollTimer.value = timers.setTimeout('task-poll', run, getTaskPollIntervalMs())
}
}
}
if (immediate) void run()
@@ -959,9 +1048,16 @@ async function deleteTaskRecord(item: SimilarAsinHistoryItem) {
onMounted(async () => {
loadPollingIds()
if (typeof window !== 'undefined') {
pywebviewReadyHandler = () => {
void loadAlipriceConfig()
}
window.addEventListener('pywebviewready', pywebviewReadyHandler)
}
await Promise.all([
loadDashboard().catch(() => undefined),
loadHistory().catch(() => undefined),
loadAlipriceConfig(),
])
seedPendingFileTasksFromHistory()
seedRunningTasksFromHistory()
@@ -970,6 +1066,10 @@ onMounted(async () => {
onUnmounted(() => {
disposed = true
if (typeof window !== 'undefined' && pywebviewReadyHandler) {
window.removeEventListener('pywebviewready', pywebviewReadyHandler)
pywebviewReadyHandler = null
}
stopPolling()
timers.clearScope()
})
@@ -1026,6 +1126,44 @@ onUnmounted(() => {
background: linear-gradient(135deg, #222a25, #202020);
}
.aliprice-card {
margin-bottom: 18px;
padding: 14px 16px;
border: 1px solid #343434;
border-radius: 8px;
background: #232323;
}
.aliprice-field {
display: grid;
grid-template-columns: 44px minmax(0, 1fr);
align-items: center;
gap: 10px;
color: #bbb;
font-size: 13px;
}
.aliprice-field + .aliprice-field {
margin-top: 10px;
}
.aliprice-field input {
width: 100%;
min-width: 0;
height: 34px;
padding: 0 10px;
border: 1px solid #3b3b3b;
border-radius: 6px;
outline: none;
background: #1b1b1b;
color: #ddd;
font: inherit;
}
.aliprice-field input:focus {
border-color: #4f91c7;
}
.switch-row {
display: flex;
align-items: center;