Files
meida/app/entry/src/main/ets/components/f3/TaskPollingPanel.ets
T

148 lines
4.1 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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%')
}
}