# EstimatedProgressBar 预计进度条

> 无法获取真实进度时，使用 EstimatedProgressBar 为 Vue 3 异步任务展示模拟进度，了解 start、finish、reset 的调用方式与失败、取消处理。

原始页面：[EstimatedProgressBar 预计进度条](http://docs.quyongqing.cn/components/estimated-progress-bar.html)

## 说明

`EstimatedProgressBar` 是用于 Vue 3 异步任务的模拟进度条，适合文件导出、AI 生成、批量同步等无法获取真实完成比例的场景。调用 `start()` 后，进度先快后慢推进到 99%；业务任务成功后调用 `finish()` 补到 100%，失败或取消时调用 `reset()` 清理展示。

## 适用场景与边界

| 任务情况 | 建议处理 |
| --- | --- |
| 接口仅返回最终结果 | 开始任务时调用 `start()`，成功后调用一次 `finish()` |
| 已有上传字节数或后端真实进度 | 使用能够绑定真实百分比的进度组件 |
| 超时、失败或用户取消 | 由调用方处理请求状态，调用 `reset()` 并展示对应提示 |
| 多个任务同时执行 | 为各任务分别维护实例或由业务层汇总状态 |

显示的百分比是等待反馈，不代表服务端完成比例；`durationSeconds` 只控制模拟动画的预计时长，不会设置请求超时，也不会在时间到达后自动完成任务。

## 接入条件

组件只依赖 Vue。将 `src/components/EstimatedProgressBar/index.vue` 放入项目后直接导入即可；示例中的 `@` 指向使用方的 `src`。`text` 是必传文案，默认预计时长为 28 秒，默认完成后保留展示。组件通过 `ref` 提供 `start()`、`finish()`、`reset()`，无需调用方维护显示开关。

## 实时预览

组件内部会先快后慢推进到 99%，适合“真实任务未返回，但页面需要稳定反馈”的场景。

当外部任务完成后，只需要调用一次 `finish()`，组件会自动补到 100%。

## 源码位置

`src/components/EstimatedProgressBar/index.vue`

## 基本参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `text` | `string` | `-` | 进度条上方的说明文案，通常用于提示当前任务状态。 |
| `durationSeconds` | `number` | `28` | 模拟进度推进到 99% 的预计时长，单位为秒。 |
| `hideOnComplete` | `boolean` | `false` | 完成后是否自动隐藏进度条。 |

## 暴露方法

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `start()` | `() => void` | `-` | 开始模拟进度，按时长缓动推进到 99%。 |
| `finish()` | `() => Promise<void>` | `-` | 通知组件真实任务已完成，随后平滑补到 100%。 |
| `reset()` | `() => void` | `-` | 停止动画并重置为 0%，同时隐藏进度条。 |

## 事件

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `progress` | `(percent: number) => void` | `-` | 进度变化时触发，适合在父组件里同步当前百分比文本。 |

## 基本使用方法

下面使用延时模拟异步任务，可在接入组件后直接运行。实际使用时把延时替换为业务请求，并确认请求成功后再调用 `finish()`；如果使用 `fetch`，还需要检查响应的 `ok` 或业务状态码。

```vue
<template>
  <div class="task-progress">
    <button type="button" :disabled="loading" @click="handleTask">
      {{ loading ? '处理中…' : '开始任务' }}
    </button>

    <EstimatedProgressBar
      ref="progressBarRef"
      text="正在生成报告，请稍候..."
      :duration-seconds="20"
    />
    <p role="status">{{ taskMessage }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import EstimatedProgressBar from '@/components/EstimatedProgressBar/index.vue'

const progressBarRef = ref<InstanceType<typeof EstimatedProgressBar> | null>(null)
const loading = ref(false)
const taskMessage = ref('')

async function handleTask() {
  if (loading.value || !progressBarRef.value) return

  loading.value = true
  taskMessage.value = '正在处理任务'
  progressBarRef.value.start()

  try {
    await new Promise((resolve) => window.setTimeout(resolve, 5000))
    await progressBarRef.value?.finish()
    taskMessage.value = '任务已完成'
  } catch (error) {
    progressBarRef.value?.reset()
    taskMessage.value = error instanceof Error ? error.message : '任务失败，请重试'
  } finally {
    loading.value = false
  }
}
</script>
```

## 代码示例

**Estimated Progress Bar 基础示例**

```vue
<template>
  <div class="task-progress">
    <el-button type="primary" @click="handleTask">开始任务</el-button>

    <EstimatedProgressBar
      ref="progressBarRef"
      text="正在生成报告，请稍候..."
      :duration-seconds="20"
      @progress="currentPercent = $event"
    />

    <div class="task-progress__meta">当前进度：{{ Math.round(currentPercent) }}%</div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import EstimatedProgressBar from '@/components/EstimatedProgressBar/index.vue'

const progressBarRef = ref<any>(null)
const currentPercent = ref(0)

const handleTask = async () => {
  progressBarRef.value?.start()

  try {
    await new Promise((resolve) => window.setTimeout(resolve, 5000))
    await progressBarRef.value?.finish()
  } catch (error) {
    progressBarRef.value?.reset()
  }
}
</script>

<style scoped>
.task-progress {
  display: grid;
  gap: 12px;
}

.task-progress__meta {
  font-size: 13px;
  color: #6b7280;
}
</style>
```

## 常见问题

### 为什么进度停在 99%？

这是组件的预期行为。模拟时长结束后仍然等待真实任务结果，只有调用方调用 `finish()` 才会推进到 100%。请检查业务成功分支是否调用了该方法。

### 请求失败或取消时怎么处理？

调用 `reset()` 停止动画、隐藏进度条并把进度恢复到 0，同时在业务界面展示失败或取消提示。`reset()` 只管理组件状态，不会取消网络请求；使用可取消请求时，调用方还需取消请求，并避免旧请求结束后再次调用 `finish()`。

### finish() 返回的 Promise 表示什么？

它用于等待补齐进度的动画结束，不代表业务请求完成。组件在 `start()` 重新开始、`reset()` 或卸载时也会结束正在等待的完成 Promise，因此业务成功状态必须以请求结果为准。启用 `hideOnComplete` 后，完成动画结束还会短暂保留再隐藏。

### 可以重复点击开始或完成吗？

`start()` 会重置进度并重新开始。调用方应在任务期间禁用重复提交，并在成功分支只调用一次 `finish()`，避免多次完成动画互相覆盖。

## 相关文档

-   [QyqForm 动态表单](http://docs.quyongqing.cn/components/qyq-form.html)：收集任务参数并在提交前校验。
-   [QyqTable 数据表格](http://docs.quyongqing.cn/components/qyq-table.html)：展示任务列表或处理结果。
-   [组件目录](http://docs.quyongqing.cn/components/)：按业务场景选择组件。
