148 lines
4.1 KiB
Plaintext
148 lines
4.1 KiB
Plaintext
/**
|
||
* TaskPollingPanel —— 任务轮询通用面板(F3,Create 提交后与 Result 重新生成共用)。
|
||
*
|
||
* - 每 3 秒 GET /api/v1/tasks/{id},展示 progress 进度条 + stage_text
|
||
* - SUCCEEDED / FAILED 通过回调上抛,由宿主页面决定跳转或失败态
|
||
* - 连续 3 次网络异常视为失败(避免瞬时抖动误杀长任务)
|
||
* - 定时器随组件销毁自动清理(页面返回/跳转即停);taskId 切换时
|
||
* 通过 generation 计数丢弃旧任务的在途响应
|
||
*/
|
||
|
||
import { ApiClient } from '../../core/ApiClient'
|
||
import { ApiError } from '../../core/ApiTypes'
|
||
import { TaskWire } from './ReportTypes'
|
||
|
||
const POLL_INTERVAL_MS: number = 3000
|
||
const MAX_CONSECUTIVE_ERRORS: number = 3
|
||
const POLL_PRIMARY_COLOR: string = '#C2185B'
|
||
|
||
@Component
|
||
export struct TaskPollingPanel {
|
||
@Prop @Watch('onTaskIdChanged') taskId: string = ''
|
||
onSucceeded: () => void = () => {}
|
||
onFailed: (reason: string) => void = () => {}
|
||
|
||
@State progress: number = 0
|
||
@State stageText: string = '任务已提交,等待调度…'
|
||
|
||
private timerId: number = -1
|
||
private generation: number = 0
|
||
private errCount: number = 0
|
||
|
||
aboutToAppear(): void {
|
||
if (this.taskId !== '') {
|
||
this.startPolling()
|
||
}
|
||
}
|
||
|
||
aboutToDisappear(): void {
|
||
// 页面销毁/返回清理定时器:离开页面不会残留轮询
|
||
this.stopTimer()
|
||
}
|
||
|
||
private onTaskIdChanged(): void {
|
||
this.startPolling()
|
||
}
|
||
|
||
private startPolling(): void {
|
||
this.stopTimer()
|
||
this.generation += 1
|
||
this.errCount = 0
|
||
this.progress = 0
|
||
this.stageText = '任务已提交,等待调度…'
|
||
this.pollOnce(this.generation)
|
||
}
|
||
|
||
private stopTimer(): void {
|
||
if (this.timerId !== -1) {
|
||
clearTimeout(this.timerId)
|
||
this.timerId = -1
|
||
}
|
||
}
|
||
|
||
private async pollOnce(gen: number): Promise<void> {
|
||
const tid = this.taskId
|
||
if (tid === '') {
|
||
return
|
||
}
|
||
try {
|
||
const t = await ApiClient.get<TaskWire>(`/api/v1/tasks/${tid}`)
|
||
if (gen !== this.generation) {
|
||
return // 已切换到新任务,丢弃旧任务的在途响应
|
||
}
|
||
this.errCount = 0
|
||
const p = t.progress
|
||
if (p !== undefined && p !== null) {
|
||
this.progress = Math.min(100, Math.max(0, p))
|
||
}
|
||
const st = t.stage_text
|
||
if (st !== undefined && st !== null && st !== '') {
|
||
this.stageText = st
|
||
}
|
||
if (t.status === 'SUCCEEDED') {
|
||
this.stopTimer()
|
||
this.onSucceeded()
|
||
return
|
||
}
|
||
if (t.status === 'FAILED') {
|
||
this.stopTimer()
|
||
this.onFailed(this.stageText)
|
||
return
|
||
}
|
||
this.scheduleNext(gen)
|
||
} catch (e) {
|
||
if (gen !== this.generation) {
|
||
return
|
||
}
|
||
this.errCount += 1
|
||
if (this.errCount >= MAX_CONSECUTIVE_ERRORS) {
|
||
this.stopTimer()
|
||
let reason = '网络异常,连续查询任务状态失败'
|
||
if (e instanceof ApiError) {
|
||
reason = `${reason}(${e.message})`
|
||
}
|
||
this.onFailed(reason)
|
||
return
|
||
}
|
||
this.scheduleNext(gen)
|
||
}
|
||
}
|
||
|
||
private scheduleNext(gen: number): void {
|
||
this.stopTimer()
|
||
this.timerId = setTimeout((): void => {
|
||
this.pollOnce(gen)
|
||
}, POLL_INTERVAL_MS)
|
||
}
|
||
|
||
build() {
|
||
Column({ space: 10 }) {
|
||
Progress({ value: this.progress, total: 100, type: ProgressType.Linear })
|
||
.width('100%')
|
||
.height(8)
|
||
.color(POLL_PRIMARY_COLOR)
|
||
.backgroundColor('#F3D9E3')
|
||
.borderRadius(4)
|
||
Row({ space: 8 }) {
|
||
Text(this.stageText)
|
||
.fontSize(13)
|
||
.fontColor('#666666')
|
||
.layoutWeight(1)
|
||
.maxLines(2)
|
||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||
Text(`${Math.round(this.progress)}%`)
|
||
.fontSize(13)
|
||
.fontWeight(FontWeight.Medium)
|
||
.fontColor(POLL_PRIMARY_COLOR)
|
||
}
|
||
.width('100%')
|
||
Text(`任务 ID:${this.taskId}|每 3 秒自动刷新,返回不会中断生成`)
|
||
.fontSize(11)
|
||
.fontColor('#AAAAAA')
|
||
.maxLines(1)
|
||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||
}
|
||
.width('100%')
|
||
}
|
||
}
|