feat(app): report flow with polling and 32-dimension result view(F3)
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 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%')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user