增加失败原因和优化进度条
This commit is contained in:
@@ -109,6 +109,9 @@ export function cancelLocalRebill(taskId) {
|
||||
})
|
||||
}
|
||||
|
||||
/** One in-flight poll per taskId — avoids stacked loops that flood XHR and freeze the page. */
|
||||
const localRebillPollControllers = new Map()
|
||||
|
||||
/**
|
||||
* Poll local rebill task until terminal status.
|
||||
* @param {string} taskId
|
||||
@@ -117,22 +120,48 @@ export function cancelLocalRebill(taskId) {
|
||||
export async function pollLocalRebillTask(taskId, onProgress) {
|
||||
const id = String(taskId || '')
|
||||
if (!id) throw new Error('missing local rebill taskId')
|
||||
|
||||
const prev = localRebillPollControllers.get(id)
|
||||
if (prev) prev.aborted = true
|
||||
const ctrl = { aborted: false }
|
||||
localRebillPollControllers.set(id, ctrl)
|
||||
|
||||
const started = Date.now()
|
||||
const maxMs = 30 * 60 * 1000
|
||||
while (Date.now() - started < maxMs) {
|
||||
const res = await getLocalRebillTask(id)
|
||||
const task = res.data || {}
|
||||
if (typeof onProgress === 'function') onProgress(task)
|
||||
if (task.status === 'SUCCESS') return { task }
|
||||
if (task.status === 'CANCELLED') return { task, cancelled: true }
|
||||
if (task.status === 'FAIL') {
|
||||
const err = new Error(task.error || task.message || 'local rebill failed')
|
||||
err.task = task
|
||||
throw err
|
||||
let lastSig = ''
|
||||
try {
|
||||
while (Date.now() - started < maxMs) {
|
||||
if (ctrl.aborted) {
|
||||
const err = new Error('local rebill poll superseded')
|
||||
err.superseded = true
|
||||
throw err
|
||||
}
|
||||
// Hidden tab: slow down; no need to hammer Redis while user is away.
|
||||
const hidden = typeof document !== 'undefined' && document.visibilityState === 'hidden'
|
||||
const res = await getLocalRebillTask(id)
|
||||
const task = res.data || {}
|
||||
const st = String(task.status || '').toUpperCase()
|
||||
const sig = [st, task.percent, task.success, task.fail, task.total].join('|')
|
||||
if (typeof onProgress === 'function' && sig !== lastSig) {
|
||||
lastSig = sig
|
||||
onProgress(task)
|
||||
}
|
||||
if (st === 'SUCCESS') return { task }
|
||||
if (st === 'CANCELLED') return { task, cancelled: true }
|
||||
if (st === 'FAIL') {
|
||||
const err = new Error(task.error || task.message || 'local rebill failed')
|
||||
err.task = task
|
||||
throw err
|
||||
}
|
||||
// 1s visible / 2.5s hidden — dense 400ms polls were freezing the list page.
|
||||
await new Promise(resolve => setTimeout(resolve, hidden ? 2500 : 1000))
|
||||
}
|
||||
throw new Error('local rebill timeout')
|
||||
} finally {
|
||||
if (localRebillPollControllers.get(id) === ctrl) {
|
||||
localRebillPollControllers.delete(id)
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 800))
|
||||
}
|
||||
throw new Error('local rebill timeout')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -345,7 +345,7 @@ const billingFailStatusOptions = [
|
||||
{ label: '产品价未匹配国家', value: 24 },
|
||||
{ label: '产品价未匹配重量段', value: 25 },
|
||||
{ label: '未绑定套餐', value: 4 },
|
||||
{ label: '无生效套餐版本', value: 5 },
|
||||
{ label: '套餐未启用/无生效版本', value: 5 },
|
||||
{ label: '已生成应收账单', value: 6 },
|
||||
{ label: '产品档案缺失', value: 7 },
|
||||
{ label: '未命中价格', value: 8 },
|
||||
|
||||
@@ -363,7 +363,7 @@ const billingFailStatusOptions = [
|
||||
{ label: '产品价未匹配国家', value: 24 },
|
||||
{ label: '产品价未匹配重量段', value: 25 },
|
||||
{ label: '未绑定套餐', value: 4 },
|
||||
{ label: '无生效套餐版本', value: 5 },
|
||||
{ label: '套餐未启用/无生效版本', value: 5 },
|
||||
{ label: '已生成应收账单', value: 6 },
|
||||
{ label: '产品档案缺失', value: 7 },
|
||||
{ label: '未命中价格', value: 8 },
|
||||
@@ -638,10 +638,15 @@ async function resumeRebillTask(taskId) {
|
||||
rebillProgressTotal.value = 0
|
||||
rebillProgressSuccess.value = 0
|
||||
rebillProgressFail.value = 0
|
||||
let superseded = false
|
||||
try {
|
||||
const result = await pollLocalRebillTask(taskId, applyRebillProgress)
|
||||
finishRebillFromTask(result && result.task, !!(result && result.cancelled))
|
||||
} catch (e) {
|
||||
if (e && e.superseded) {
|
||||
superseded = true
|
||||
return
|
||||
}
|
||||
const task = e && e.task
|
||||
rebillProgressStatus.value = 'exception'
|
||||
rebillProgressMessage.value = (e && e.message) || '重新计费失败'
|
||||
@@ -657,7 +662,8 @@ async function resumeRebillTask(taskId) {
|
||||
rebillProgressVisible.value = true
|
||||
scheduleHideRebillFloat()
|
||||
} finally {
|
||||
batchLoading.value = false
|
||||
// Keep loading if a newer poll replaced this one.
|
||||
if (!superseded) batchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,6 +717,7 @@ async function runLocalRebill(payload, confirmText) {
|
||||
window.setTimeout(() => {
|
||||
if (batchLoading.value) rebillProgressVisible.value = false
|
||||
}, 600)
|
||||
let superseded = false
|
||||
try {
|
||||
const result = await localRebillAsync(
|
||||
payload,
|
||||
@@ -719,6 +726,10 @@ async function runLocalRebill(payload, confirmText) {
|
||||
)
|
||||
finishRebillFromTask(result && result.task, !!(result && result.cancelled))
|
||||
} catch (e) {
|
||||
if (e && e.superseded) {
|
||||
superseded = true
|
||||
return
|
||||
}
|
||||
const task = e && e.task
|
||||
rebillProgressStatus.value = 'exception'
|
||||
rebillProgressMessage.value = (e && e.message) || '重新计费失败'
|
||||
@@ -734,7 +745,7 @@ async function runLocalRebill(payload, confirmText) {
|
||||
rebillProgressVisible.value = true
|
||||
scheduleHideRebillFloat()
|
||||
} finally {
|
||||
batchLoading.value = false
|
||||
if (!superseded) batchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,11 +44,11 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="build-choice">
|
||||
<!-- Exclusive quote: only Excel fresh build; hide the mode picker and default IMPORT_FRESH -->
|
||||
<div v-if="!onlyImportFresh" class="build-choice">
|
||||
<p class="build-choice__tip">创建价格方式</p>
|
||||
<div class="build-choice__cards">
|
||||
<button
|
||||
v-if="!onlyImportFresh"
|
||||
type="button"
|
||||
class="build-card build-card--pull"
|
||||
:class="{ selected: isPullBuildMode(priceBuildMode) }"
|
||||
@@ -59,7 +59,7 @@
|
||||
<div class="build-card__desc">按产品库基础价生成套餐产品列表,可再导入 Excel 修改价格</div>
|
||||
</button>
|
||||
<button
|
||||
v-if="showImportInherit && !onlyImportFresh"
|
||||
v-if="showImportInherit"
|
||||
type="button"
|
||||
class="build-card build-card--import"
|
||||
:class="{ selected: isImportBuildMode(priceBuildMode) }"
|
||||
@@ -626,7 +626,7 @@ watch(
|
||||
() => [onlyImportFresh.value, props.priceBuildMode, props.buildModeReady],
|
||||
() => {
|
||||
if (!onlyImportFresh.value || !props.buildModeReady) return
|
||||
if (props.priceBuildMode) return
|
||||
if (isImportFreshBuildMode(props.priceBuildMode)) return
|
||||
if (buildModeLocked.value) return
|
||||
emit('update:priceBuildMode', 'IMPORT_FRESH')
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user