/** * 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 { const tid = this.taskId if (tid === '') { return } try { const t = await ApiClient.get(`/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%') } }