Appearance
BizTaskCenter 任务中心
任务中心:批量任务进度面板与下载任务弹层的统一组件(由 BizBatchProgress 批量任务进度与 BizDownloadCenter 下载任务中心合并而来,两组件能力 100% 保留)。两种形态经 mode 切换:
- panel 面板直出(原 BizBatchProgress 形态):总进度条 + 分状态统计(等待 / 进行中 / 成功 / 失败 / 已取消)+ 明细列表——行内进度、失败原因、单任务重试 / 取消 / 移除,整体「重试失败 / 取消全部 / 清空已完成」,全部终态触发一次
done; - trigger 触发按钮 + 弹层面板(原 BizDownloadCenter 形态):触发按钮携带进行中数量角标,弹出任务面板,success 行提供下载动作,
polling开启且存在未完成任务时周期外抛refresh由业务侧拉列表回填。
明细行按文件扩展名渲染类型角标(xlsx 绿 / pdf 红 / doc 蓝 / zip 橙,可经 type-colors 自定义映射)。纯受控组件:任务推进由业务层驱动(定时器 / 轮询 / SSE)经 tasks 回写,retry / cancel / remove / download 等操作全部事件外抛,组件不含任何任务接口。批量导出报表、对账单下载、批量发货、夜间批处理等场景。
面板基础(panel 形态)
明细列表
整体操作
任务中心
共 0
0%
暂无任务
发起批量导出,定时器模拟推进:总进度按终态满格 + 进行中 progress 折算;
done 全部终态触发一次;开关演示纯总进度(show-tasks=false)与收起整体操作(show-actions=false)。<script setup lang="ts">
/** 场景:panel 面板基础——批量导出季度报表,定时器推进 + done 完成提醒,明细/整体操作开关 */
import { computed, onBeforeUnmount, ref } from 'vue'
import { ElButton, ElMessage, ElSwitch } from 'element-plus'
import { BizTaskCenter } from 'vue3-ep-biz-lib'
import type { BizTaskItem, BizTaskStats } from 'vue3-ep-biz-lib'
const tasks = ref<BizTaskItem[]>([])
const showTasks = ref(true)
const showActions = ref(true)
let timer: ReturnType<typeof setInterval> | null = null
const stopTimer = (): void => {
if (timer !== null) {
clearInterval(timer)
timer = null
}
}
const busy = computed(() =>
tasks.value.some((task) => task.status === 'waiting' || task.status === 'processing'),
)
const start = (): void => {
tasks.value = Array.from({ length: 5 }, (_, i) => ({
id: `r${i + 1}`,
name: `季度经营报表-第 ${i + 1} 分区.xlsx`,
status: 'waiting' as const,
size: `${(1.2 + i * 0.4).toFixed(1)}MB`,
}))
stopTimer()
timer = setInterval(() => {
for (const task of tasks.value) {
if (task.status === 'processing') {
task.progress = Math.min(100, (task.progress ?? 0) + 8 + Math.round(Math.random() * 10))
if ((task.progress ?? 0) >= 100) {
task.status = 'success'
task.progress = 100
}
} else if (task.status === 'waiting' && Math.random() > 0.5) {
task.status = 'processing'
task.progress = 0
}
}
}, 400)
}
const onDone = (stats: BizTaskStats): void => {
stopTimer()
ElMessage.success(`全部完成:成功 ${stats.success} / 共 ${stats.total}`)
}
onBeforeUnmount(stopTimer)
</script>
<template>
<div>
<div style="margin-bottom: 10px; display: flex; align-items: center; gap: 16px">
<ElButton type="primary" :disabled="busy" @click="start">发起批量导出</ElButton>
<span style="display: inline-flex; align-items: center; gap: 6px; font-size: 13px">
明细列表 <ElSwitch v-model="showTasks" size="small" />
</span>
<span style="display: inline-flex; align-items: center; gap: 6px; font-size: 13px">
整体操作 <ElSwitch v-model="showActions" size="small" />
</span>
</div>
<BizTaskCenter mode="panel" :tasks="tasks" :show-tasks="showTasks" :show-actions="showActions" @done="onDone" />
</div>
</template>
显示代码
触发按钮 + 角标弹层(trigger 形态)
2
触发按钮带进行中数量角标,弹层四态全展示:已完成(可下载 / 可移除)、进行中(进度条)、排队中、失败(原因 + 重试);
open() / close() 编程控制弹层,「清空已完成」由业务侧过滤列表。<script setup lang="ts">
/** 场景:trigger 触发按钮 + 进行中数量角标弹层——对账单下载四态任务(下载 / 重试 / 移除 / 清空已完成) */
import { ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizTaskCenter } from 'vue3-ep-biz-lib'
import type { BizTaskCenterInstance, BizTaskItem } from 'vue3-ep-biz-lib'
const tasks = ref<BizTaskItem[]>([
{ id: 1, name: '供应商对账单-2026-09.xlsx', status: 'success', size: '2.3MB', createTime: '10-08 09:12' },
{ id: 2, name: '渠道对账单-2026-09.xlsx', status: 'processing', progress: 62, createTime: '10-08 10:03' },
{ id: 3, name: '银企对账明细.csv', status: 'waiting', createTime: '10-08 10:05' },
{
id: 4,
name: '三年往来账全量.pdf',
status: 'failed',
error: '数据量超过单次导出上限(10 万行),请缩小时间范围',
createTime: '10-08 08:40',
},
])
const centerRef = ref<BizTaskCenterInstance | null>(null)
const onDownload = (task: BizTaskItem): void => {
ElMessage.success(`拉起「${task.name}」下载——业务侧在此请求文件流`)
}
const onRetry = (task: BizTaskItem): void => {
tasks.value = tasks.value.map((item) =>
item.id === task.id ? { ...item, status: 'waiting', error: undefined } : item,
)
ElMessage.info(`已重新提交「${task.name}」`)
}
const onRemove = (task: BizTaskItem): void => {
tasks.value = tasks.value.filter((item) => item.id !== task.id)
}
const onClear = (): void => {
tasks.value = tasks.value.filter((item) => item.status !== 'success' && item.status !== 'failed')
ElMessage.info('已清空已结束任务')
}
const onCancel = (task: BizTaskItem): void => {
tasks.value = tasks.value.map((item) =>
item.id === task.id ? { ...item, status: 'canceled', message: '人工取消' } : item,
)
}
</script>
<template>
<div style="display: flex; align-items: center; gap: 12px">
<BizTaskCenter
ref="centerRef"
:tasks="tasks"
@download="onDownload"
@retry="onRetry"
@cancel="onCancel"
@remove="onRemove"
@clear="onClear"
/>
<ElButton link type="primary" size="small" @click="centerRef?.open()">open() 编程打开</ElButton>
<ElButton link size="small" @click="centerRef?.close()">close()</ElButton>
</div>
</template>
显示代码
文件类型角标
xlsx / csv 绿、pdf 红、doc 蓝、zip / rar 橙;「归档材料」无扩展名,经 fileType 显式指定为 rar;未知扩展名兜底灰
导出文件批次
共 7等待 1进行中 2成功 2失败 1已取消 1
xlsx
商品价目表-全量.xlsx
3.1MB10-09 14:20已完成
csv48%
门店销售明细.csv
docx
品类经营分析报告.docx
10-09 14:25等待中
pdf
质检报告-批次0917.pdf
860KB10-09 11:02已完成
zip12%
商品主图包-秋装.zip
rar
归档材料
10-09 09:31失败
压缩包损坏,请重新上传源文件
file
结算单-无扩展名文件
10-09 08:15已取消
已手动取消
按扩展名映射配色:xls / xlsx / csv 绿、pdf 红、doc / docx 蓝、zip / rar / 7z 橙,未知兜底灰;
fileType 可对无扩展名任务显式指定类型。<script setup lang="ts">
/** 场景:文件类型角标——按扩展名映射配色(xlsx 绿 / csv 绿 / pdf 红 / doc 蓝 / zip 橙),fileType 可显式指定 */
import { ref } from 'vue'
import { BizTaskCenter } from 'vue3-ep-biz-lib'
import type { BizTaskItem } from 'vue3-ep-biz-lib'
const tasks = ref<BizTaskItem[]>([
{ id: 'e1', name: '商品价目表-全量.xlsx', status: 'success', size: '3.1MB', createTime: '10-09 14:20' },
{ id: 'e2', name: '门店销售明细.csv', status: 'processing', progress: 48, createTime: '10-09 14:22' },
{ id: 'e3', name: '品类经营分析报告.docx', status: 'waiting', createTime: '10-09 14:25' },
{ id: 'e4', name: '质检报告-批次0917.pdf', status: 'success', size: '860KB', createTime: '10-09 11:02' },
{ id: 'e5', name: '商品主图包-秋装.zip', status: 'processing', progress: 12, createTime: '10-09 14:26' },
{ id: 'e6', name: '归档材料', status: 'failed', error: '压缩包损坏,请重新上传源文件', fileType: 'rar', createTime: '10-09 09:31' },
{ id: 'e7', name: '结算单-无扩展名文件', status: 'canceled', message: '已手动取消', createTime: '10-09 08:15' },
])
</script>
<template>
<div>
<div style="margin-bottom: 8px; font-size: 12px; color: var(--el-text-color-secondary)">
xlsx / csv 绿、pdf 红、doc 蓝、zip / rar 橙;「归档材料」无扩展名,经 fileType 显式指定为 rar;未知扩展名兜底灰
</div>
<BizTaskCenter mode="panel" :tasks="tasks" title="导出文件批次" @retry="() => {}" @cancel="() => {}" @remove="() => {}" @download="() => {}" />
</div>
</template>
显示代码
失败重试流
夜间批处理任务
共 4进行中 1成功 1失败 2
file
会员数据同步
4.2MB已完成
file
积分清算
失败
下游接口超时(code 504),已重试 3 次
file
优惠券核销对账
失败
对账差异 12 条,人工介入后可重试
file62%
客户标签重算
失败原因经
error 展示为红色文案;单条「重试」与头部「重试失败」批量恢复——首次重试仍失败(原因更新),第二次入队推进至完成。<script setup lang="ts">
/** 场景:失败重试流——失败原因展示(error 字段),单条重试与「重试失败」批量恢复,首试仍失败、再试成功 */
import { onBeforeUnmount, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizTaskCenter } from 'vue3-ep-biz-lib'
import type { BizTaskItem } from 'vue3-ep-biz-lib'
const tasks = ref<BizTaskItem[]>([
{ id: 't1', name: '会员数据同步', status: 'success', size: '4.2MB' },
{
id: 't2',
name: '积分清算',
status: 'failed',
error: '下游接口超时(code 504),已重试 3 次',
},
{
id: 't3',
name: '优惠券核销对账',
status: 'failed',
error: '对账差异 12 条,人工介入后可重试',
},
{ id: 't4', name: '客户标签重算', status: 'processing', progress: 62 },
])
/** 每任务独立定时器,避免并发重试互相顶掉导致任务卡在进行中 */
const timers = new Map<string, ReturnType<typeof setInterval>>()
const stopTask = (id: string): void => {
const timer = timers.get(id)
if (timer !== undefined) {
clearInterval(timer)
timers.delete(id)
}
}
/** 首次重试仍失败(更新原因),之后入队推进至完成 */
const retriedOnce = new Set<string>()
const runTask = (task: BizTaskItem): void => {
stopTask(String(task.id))
task.status = 'processing'
task.progress = 0
task.error = undefined
timers.set(
String(task.id),
setInterval(() => {
task.progress = Math.min(100, (task.progress ?? 0) + 18)
if ((task.progress ?? 0) >= 100) {
task.status = 'success'
task.progress = 100
stopTask(String(task.id))
}
}, 300),
)
}
const doRetry = (task: BizTaskItem): void => {
const target = tasks.value.find((item) => item.id === task.id)
if (target === undefined) return
if (!retriedOnce.has(String(target.id))) {
retriedOnce.add(String(target.id))
target.error = '仍有慢查询未优化:SQL 扫描行数超限,建议先建索引'
ElMessage.warning('重试失败,行内红色文案为最新失败原因')
return
}
runTask(target)
}
const onRetryFailed = (failed: BizTaskItem[]): void => {
for (const item of failed) doRetry(item)
}
onBeforeUnmount(() => {
for (const id of [...timers.keys()]) stopTask(id)
})
</script>
<template>
<div>
<BizTaskCenter mode="panel" :tasks="tasks" title="夜间批处理任务" @retry="doRetry" @retry-failed="onRetryFailed" />
</div>
</template>
显示代码
取消 / 移除 / 取消全部
批量发货
共 0
0%
暂无任务
批量发货批处理:进行中 / 等待任务单条取消(记为已取消),终态任务逐条移除,「取消全部」一次清场。
<script setup lang="ts">
/** 场景:取消 / 移除 / 取消全部——批量发货批处理:进行中单条取消、终态任务移除、一次取消全部 */
import { onBeforeUnmount, ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizTaskCenter } from 'vue3-ep-biz-lib'
import type { BizTaskItem } from 'vue3-ep-biz-lib'
const tasks = ref<BizTaskItem[]>([])
let timer: ReturnType<typeof setInterval> | null = null
const stopTimer = (): void => {
if (timer !== null) {
clearInterval(timer)
timer = null
}
}
const start = (): void => {
tasks.value = Array.from({ length: 4 }, (_, i) => ({
id: `s${i + 1}`,
name: `批量发货-波次 ${i + 1}.zip`,
status: (i === 0 ? 'processing' : 'waiting') as BizTaskItem['status'],
progress: i === 0 ? 15 : undefined,
size: `${(2 + i * 0.8).toFixed(1)}MB`,
}))
stopTimer()
timer = setInterval(() => {
const running = tasks.value.find((task) => task.status === 'processing')
if (running === undefined) return
running.progress = Math.min(100, (running.progress ?? 0) + 9)
if ((running.progress ?? 0) >= 100) {
running.status = 'success'
const next = tasks.value.find((task) => task.status === 'waiting')
if (next !== undefined) {
next.status = 'processing'
next.progress = 0
}
}
}, 300)
}
const onCancel = (task: BizTaskItem): void => {
const target = tasks.value.find((item) => item.id === task.id)
if (target === undefined) return
target.status = 'canceled'
target.message = '人工取消'
if (!tasks.value.some((item) => item.status === 'waiting' || item.status === 'processing')) stopTimer()
}
const onCancelAll = (pending: BizTaskItem[]): void => {
stopTimer()
const ids = new Set(pending.map((item) => item.id))
tasks.value = tasks.value.map((item) =>
ids.has(item.id) ? { ...item, status: 'canceled', message: '批量取消' } : item,
)
ElMessage.info('已取消全部未完成任务')
}
const onRemove = (task: BizTaskItem): void => {
tasks.value = tasks.value.filter((item) => item.id !== task.id)
ElMessage.info(`已移除「${task.name}」`)
}
onBeforeUnmount(stopTimer)
</script>
<template>
<div>
<ElButton type="primary" style="margin-bottom: 10px" @click="start">开始批处理</ElButton>
<BizTaskCenter mode="panel" :tasks="tasks" title="批量发货" @cancel="onCancel" @cancel-all="onCancelAll" @remove="onRemove" />
</div>
</template>
显示代码
定时器模拟任务推进(业务驱动、可暂停)
业务定时器推进中
发货单生成
共 3等待 2进行中 1
13%
xlsx40%
发货单-华东仓.xlsx
xlsx
发货单-华南仓.xlsx
等待中
xlsx
发货单-西南仓.xlsx
等待中
组件不含任务推进逻辑:业务侧定时器回写
tasks(waiting 依次放行、processing 按 progress 推进),支持暂停 / 继续 / 重新发起。<script setup lang="ts">
/** 场景:定时器模拟任务推进(业务驱动、可暂停/继续)——发货单生成由业务定时器回写 tasks */
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { ElButton, ElTag } from 'element-plus'
import { BizTaskCenter } from 'vue3-ep-biz-lib'
import type { BizTaskItem } from 'vue3-ep-biz-lib'
const tasks = ref<BizTaskItem[]>([
{ id: 'p1', name: '发货单-华东仓.xlsx', status: 'processing', progress: 40, size: '1.6MB' },
{ id: 'p2', name: '发货单-华南仓.xlsx', status: 'waiting' },
{ id: 'p3', name: '发货单-西南仓.xlsx', status: 'waiting' },
])
const paused = ref(false)
let timer: ReturnType<typeof setInterval> | null = null
/** 业务侧驱动推进:waiting 依次放行,processing 按 progress 推进 */
const tick = (): void => {
const running = tasks.value.find((task) => task.status === 'processing')
if (running === undefined) {
const waiting = tasks.value.find((task) => task.status === 'waiting')
if (waiting === undefined) return
waiting.status = 'processing'
waiting.progress = 0
return
}
running.progress = Math.min(100, (running.progress ?? 0) + 12)
if ((running.progress ?? 0) >= 100) {
running.status = 'success'
running.progress = 100
}
}
const startTimer = (): void => {
if (timer === null) timer = setInterval(tick, 350)
}
const stopTimer = (): void => {
if (timer !== null) {
clearInterval(timer)
timer = null
}
}
const toggle = (): void => {
paused.value = !paused.value
if (paused.value) stopTimer()
else startTimer()
}
const restart = (): void => {
tasks.value = tasks.value.map((task) => ({ ...task, status: 'waiting', progress: undefined }))
paused.value = false
stopTimer()
startTimer()
}
/* 定时器仅在客户端挂载后启动(SSG 预渲染不执行 onMounted) */
onMounted(startTimer)
onBeforeUnmount(stopTimer)
</script>
<template>
<div>
<div style="margin-bottom: 10px; display: flex; align-items: center; gap: 8px">
<ElButton size="small" type="primary" plain @click="toggle">
{{ paused ? '继续推进' : '暂停推进' }}
</ElButton>
<ElButton size="small" @click="restart">重新发起</ElButton>
<ElTag size="small" :type="paused ? 'warning' : 'success'" effect="light">
{{ paused ? '已暂停(tasks 不再更新)' : '业务定时器推进中' }}
</ElTag>
</div>
<BizTaskCenter mode="panel" :tasks="tasks" title="发货单生成" @cancel="() => {}" />
</div>
</template>
显示代码
完成回调与统计
图片转存
共 3等待 1进行中 1成功 1
47%
jpg
商品图片转存-01.jpg
已完成
jpg40%
商品图片转存-02.jpg
jpg
商品图片转存-03.jpg
等待中
(未取值)
done 在全部任务进入终态时触发一次,注入新批次后重新武装;getStats() 实时取分状态统计(含总进度与 finished)。<script setup lang="ts">
/** 场景:done 完成回调 + getStats 实时统计——全部终态触发一次,注入新批次后重新武装 */
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { ElButton, ElMessage } from 'element-plus'
import { BizTaskCenter } from 'vue3-ep-biz-lib'
import type { BizTaskCenterInstance, BizTaskItem, BizTaskStats } from 'vue3-ep-biz-lib'
const tasks = ref<BizTaskItem[]>([
{ id: 'a1', name: '商品图片转存-01.jpg', status: 'success' },
{ id: 'a2', name: '商品图片转存-02.jpg', status: 'processing', progress: 40 },
{ id: 'a3', name: '商品图片转存-03.jpg', status: 'waiting' },
])
const centerRef = ref<BizTaskCenterInstance | null>(null)
const statsText = ref('(未取值)')
const doneCount = ref(0)
let timer: ReturnType<typeof setInterval> | null = null
/* 定时器仅在客户端挂载后启动(SSG 预渲染不执行 onMounted) */
onMounted(() => {
timer = setInterval(() => {
const running = tasks.value.find((task) => task.status === 'processing')
if (running === undefined) {
const waiting = tasks.value.find((task) => task.status === 'waiting')
if (waiting === undefined) return
waiting.status = 'processing'
waiting.progress = 0
return
}
running.progress = Math.min(100, (running.progress ?? 0) + 25)
if ((running.progress ?? 0) >= 100) {
running.status = 'success'
running.progress = 100
}
}, 400)
})
const onDone = (stats: BizTaskStats): void => {
doneCount.value += 1
ElMessage.success(`第 ${doneCount.value} 次全部完成(总进度 ${stats.percent}%,成功 ${stats.success})`)
}
const readStats = (): void => {
const stats = centerRef.value?.getStats()
statsText.value = stats === undefined ? '(无实例)' : JSON.stringify(stats)
}
const injectBatch = (): void => {
doneCount.value = 0
tasks.value = Array.from({ length: 3 }, (_, i) => ({
id: `n${Date.now()}-${i}`,
name: `图片转存-新批次 ${i + 1}.jpg`,
status: 'waiting' as const,
}))
}
onBeforeUnmount(() => {
if (timer !== null) clearInterval(timer)
})
</script>
<template>
<div>
<BizTaskCenter ref="centerRef" mode="panel" :tasks="tasks" title="图片转存" @done="onDone" />
<div style="margin-top: 10px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap">
<ElButton size="small" @click="readStats">getStats()</ElButton>
<ElButton size="small" type="primary" plain @click="injectBatch">注入新批次(done 重新武装)</ElButton>
<span style="font-size: 12px; color: var(--el-text-color-secondary)">{{ statsText }}</span>
</div>
</div>
</template>
显示代码
空态兜底
对账单导出
共 0
0%
暂无执行中的导出任务
无任务时默认空态占位;
empty 插槽自定义空态(含引导动作),按钮注入新批次演示从空到有的切换。<script setup lang="ts">
/** 场景:空态兜底与批次注入——默认空态占位,empty 插槽自定义,按钮注入新批次演示从空到有 */
import { ref } from 'vue'
import { ElButton } from 'element-plus'
import { BizTaskCenter } from 'vue3-ep-biz-lib'
import type { BizTaskItem } from 'vue3-ep-biz-lib'
const tasks = ref<BizTaskItem[]>([])
let seq = 0
const injectBatch = (): void => {
seq += 1
tasks.value = Array.from({ length: 3 }, (_, i) => ({
id: `b${seq}-${i}`,
name: `第 ${seq} 批对账单-${i + 1}.xlsx`,
status: 'waiting' as const,
}))
}
</script>
<template>
<div>
<BizTaskCenter mode="panel" :tasks="tasks" title="对账单导出" @cancel="() => {}" @remove="() => {}">
<template #empty>
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 16px 0">
<span style="font-size: 13px; color: var(--el-text-color-secondary)">暂无执行中的导出任务</span>
<ElButton size="small" type="primary" plain @click="injectBatch">注入一批任务</ElButton>
</div>
</template>
</BizTaskCenter>
<ElButton v-if="tasks.length === 0" size="small" style="margin-top: 10px" @click="injectBatch">注入一批任务</ElButton>
</div>
</template>
显示代码
refresh 轮询外抛
已收到 refresh 0 次——全部任务进入终态后轮询自动停止
3
polling + poll-interval="2000":存在 waiting / processing 任务时面板底部提示自动刷新并周期外抛 refresh,业务侧在其间拉取任务接口回填 tasks;全部完成后轮询自动停止。<script setup lang="ts">
/** 场景:refresh 轮询外抛——polling 开启且存在未完成任务时按 poll-interval 外抛,业务侧模拟任务接口回填 tasks */
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizTaskCenter } from 'vue3-ep-biz-lib'
import type { BizTaskItem } from 'vue3-ep-biz-lib'
const tasks = ref<BizTaskItem[]>([
{ id: 1, name: '经营月报-2026-09.xlsx', status: 'processing', progress: 20, createTime: '10-08 10:10' },
{ id: 2, name: '商品主图包-秋装.zip', status: 'processing', progress: 45, createTime: '10-08 10:11' },
{ id: 3, name: '客服通话录音清单.csv', status: 'waiting', createTime: '10-08 10:12' },
])
const refreshCount = ref(0)
/** 模拟任务接口(组件库不含业务接口):refresh 到来时拉取进度、完成任务 */
const onRefresh = (): void => {
refreshCount.value += 1
tasks.value = tasks.value.map((task) => {
if (task.status === 'waiting') return { ...task, status: 'processing', progress: 5 }
if (task.status !== 'processing') return task
const next = (task.progress ?? 0) + 18 + Math.round(Math.random() * 10)
if (next >= 100) {
return { ...task, status: 'success', progress: 100, size: `${(1 + Math.random() * 4).toFixed(1)}MB` }
}
return { ...task, progress: next }
})
}
const onDownload = (task: BizTaskItem): void => {
ElMessage.success(`下载「${task.name}」`)
}
const onRemove = (task: BizTaskItem): void => {
tasks.value = tasks.value.filter((item) => item.id !== task.id)
}
const onClear = (): void => {
tasks.value = tasks.value.filter((item) => item.status !== 'success' && item.status !== 'failed')
}
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 8px">
<span style="font-size: 12px; color: var(--el-text-color-secondary)">
已收到 refresh {{ refreshCount }} 次——全部任务进入终态后轮询自动停止
</span>
<BizTaskCenter
:tasks="tasks"
polling
:poll-interval="2000"
trigger-text="导出任务"
@refresh="onRefresh"
@download="onDownload"
@remove="onRemove"
@clear="onClear"
/>
</div>
</template>
显示代码
自定义类型映射与外观
2
type-colors 覆盖默认配色(pdf 蓝 / csv 橙,仅允许主题色名);trigger-text 换文案、badge-max=9(超出显示 9+)、max-height 限制长列表滚动。<script setup lang="ts">
/** 场景:自定义类型映射 + 触发器外观——typeColors 覆盖默认配色,trigger-text / badge-max / max-height 定制 */
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BizTaskCenter } from 'vue3-ep-biz-lib'
import type { BizTaskItem, BizTaskTypeColor } from 'vue3-ep-biz-lib'
const files = [
'2026年1月发票汇总.pdf', '2026年2月发票汇总.pdf', '2026年3月发票汇总.pdf',
'2026年4月发票汇总.pdf', '2026年5月发票汇总.pdf', '2026年6月发票汇总.pdf',
'2026年7月发票汇总.pdf', '2026年8月发票汇总.pdf',
]
const tasks = ref<BizTaskItem[]>([
...files.map((name, index) => ({
id: index + 1,
name,
status: 'success' as const,
size: `${(0.8 + index * 0.3).toFixed(1)}MB`,
createTime: `09-${String(index + 1).padStart(2, '0')} 10:0${index % 10}`,
})),
{ id: 99, name: '发票原件扫描包-执行中.zip', status: 'processing', progress: 48 },
{ id: 100, name: '开票明细-执行中.csv', status: 'processing', progress: 15 },
])
/** 自定义映射:pdf 改蓝色调、csv 改橙色(仅允许主题色名 primary / success / warning / danger / info) */
const typeColors: Record<string, BizTaskTypeColor> = {
pdf: 'primary',
csv: 'warning',
}
let timer: ReturnType<typeof setInterval> | null = null
/* 定时器仅在客户端挂载后启动(SSG 预渲染不执行 onMounted) */
onMounted(() => {
timer = setInterval(() => {
tasks.value = tasks.value.map((task) => {
if (task.status !== 'processing') return task
const next = (task.progress ?? 0) + 6
if (next >= 100) return { ...task, status: 'success', progress: 100, size: '5.2MB' }
return { ...task, progress: next }
})
}, 600)
})
onBeforeUnmount(() => {
if (timer !== null) clearInterval(timer)
})
const onDownload = (task: BizTaskItem): void => {
ElMessage.success(`下载「${task.name}」`)
}
const onRemove = (task: BizTaskItem): void => {
tasks.value = tasks.value.filter((item) => item.id !== task.id)
}
const onClear = (): void => {
tasks.value = tasks.value.filter((item) => item.status !== 'success' && item.status !== 'failed')
}
</script>
<template>
<BizTaskCenter
:tasks="tasks"
:type-colors="typeColors"
trigger-text="发票下载"
:badge-max="9"
:max-height="200"
@download="onDownload"
@remove="onRemove"
@clear="onClear"
/>
</template>
显示代码
自定义行内容
资质文件处理
共 4等待 1进行中 1成功 1失败 1
jpg
图片 营业执照.jpg2.4 MB
已完成
pdf55%
文档 审计报告.pdf18.1 MB
zip
压缩包 开户资料.zip6.8 MB
失败
含敏感词,转人工审核
jpg
图片 法人身份证.jpg1.2 MB
等待中
name / extra 插槽接管任务行(业务类型标记、文件信息等,meta 泛型透传)。<script setup lang="ts">
/** 场景:name / extra 插槽——任务名接管展示业务标记,extra 追加文件信息(meta 透传) */
import { ref } from 'vue'
import { BizTaskCenter } from 'vue3-ep-biz-lib'
import type { BizTaskItem } from 'vue3-ep-biz-lib'
interface FileMeta {
size: string
kind: string
}
const tasks = ref<Array<BizTaskItem<FileMeta>>>([
{ id: 'f1', name: '营业执照.jpg', status: 'success', meta: { size: '2.4 MB', kind: '图片' } },
{ id: 'f2', name: '审计报告.pdf', status: 'processing', progress: 55, meta: { size: '18.1 MB', kind: '文档' } },
{ id: 'f3', name: '开户资料.zip', status: 'failed', error: '含敏感词,转人工审核', meta: { size: '6.8 MB', kind: '压缩包' } },
{ id: 'f4', name: '法人身份证.jpg', status: 'waiting', meta: { size: '1.2 MB', kind: '图片' } },
])
/* 插槽作用域按 unknown 收窄,取 meta 走类型化出口 */
const metaOf = (task: BizTaskItem): FileMeta | undefined => task.meta as FileMeta | undefined
</script>
<template>
<BizTaskCenter mode="panel" :tasks="tasks" title="资质文件处理" @cancel="() => {}" @download="() => {}">
<template #name="{ task }">
<span class="demo-tc-name">
<span v-if="metaOf(task) !== undefined" class="demo-tc-kind">{{ metaOf(task)?.kind }}</span>
{{ task.filename ?? task.name }}
</span>
</template>
<template #extra="{ task }">
<span class="demo-tc-size">{{ metaOf(task)?.size }}</span>
</template>
</BizTaskCenter>
</template>
<style scoped>
.demo-tc-name {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--el-text-color-primary);
}
.demo-tc-kind {
padding: 0 5px;
border-radius: var(--el-border-radius-base);
background-color: var(--el-fill-color);
color: var(--el-text-color-secondary);
font-size: 11px;
}
.demo-tc-size {
flex-shrink: 0;
font-size: 12px;
color: var(--el-text-color-placeholder);
}
</style>
显示代码
Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| mode | 'panel' | 'trigger' | 'trigger' | 展示形态:panel 面板直出;trigger 触发按钮 + 弹层面板 |
| tasks | BizTaskItem[] | [] | 任务列表(受控:推进 / 重试 / 取消 / 移除全部由业务侧回写) |
| title | string | '任务中心' | 面板标题 |
| trigger-text | string | '任务中心' | trigger 模式触发按钮文案 |
| badge-max | number | 99 | trigger 模式进行中数量角标最大值 |
| panel-width | number | 400 | trigger 模式弹层面板宽度 |
| show-overall | boolean | true | 总进度条 + 分状态统计行 |
| show-actions | boolean | true | 面板头部整体操作(重试失败 / 取消全部 / 清空已完成) |
| show-tasks | boolean | true | 明细列表(false 仅总进度) |
| max-height | number | string | 320 | 明细列表最大高度(超出滚动) |
| empty-text | string | '暂无任务' | 空态文案 |
| polling | boolean | false | 存在 waiting / processing 任务时按 poll-interval 外抛 refresh |
| poll-interval | number | 5000 | 轮询间隔(ms,最小 1000) |
| type-colors | Record<string, 'primary' | 'success' | 'warning' | 'danger' | 'info'> | 见说明 | 自定义扩展名 → 主题色映射,覆盖默认(xls/xlsx/csv=success、pdf=danger、doc/docx=primary、zip/rar/7z=warning、其余=info) |
BizTaskItem:
| 属性名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | string | number | 是 | 任务标识(事件回传定位) |
| name | string | 是 | 任务名 / 文件名(带扩展名时用于推导类型角标) |
| status | 'waiting' | 'processing' | 'success' | 'failed' | 'canceled' | 是 | 任务状态 |
| progress | number | 否 | 进度百分比 0-100(processing 时展示行内进度条) |
| error | string | 否 | 失败原因(failed 时展示为红色文案,优先于 message) |
| message | string | 否 | 附加消息(失败原因 / 完成说明) |
| file-type | string | 否 | 文件类型标识(如 'xlsx');缺省按 filename / name 扩展名推导 |
| filename | string | 否 | 展示用文件名(缺省取 name) |
| size | string | 否 | 展示用文件大小(如 '2.3MB') |
| create-time | string | 否 | 展示用创建时间文本 |
| meta | T(泛型) | 否 | 业务透传(交给 name / extra 插槽渲染) |
Events
| 事件名 | 参数 | 说明 |
|---|---|---|
| retry | (task: BizTaskItem) | 单任务重试(失败任务) |
| cancel | (task: BizTaskItem) | 单任务取消(等待 / 进行中) |
| remove | (task: BizTaskItem) | 单任务移除(终态任务,业务侧过滤 tasks) |
| retry-failed | (tasks: BizTaskItem[]) | 批量重试全部失败任务(载荷为当前失败任务数组) |
| cancel-all | (tasks: BizTaskItem[]) | 批量取消全部等待 + 进行中任务(载荷为当前未完成任务数组) |
| download | (task: BizTaskItem) | 下载已完成任务(业务侧请求文件流) |
| clear | — | 清空已完成(业务侧过滤 tasks 中的已结束记录) |
| refresh | — | 轮询刷新(业务侧在此拉取任务列表并回填 tasks) |
| done | (stats: BizTaskStats) | 全部任务进入终态触发一次(注入新批次后重新武装),载荷含总数 / 分状态计数 / 总进度 / finished |
Slots
| 插槽名 | 作用域 | 说明 |
|---|---|---|
| name | { task: BizTaskItem } | 任务名接管 |
| extra | { task: BizTaskItem } | 任务名与操作区之间追加内容 |
| empty | — | 空态接管 |
Expose
| 名称 | 类型 | 说明 |
|---|---|---|
| open | () => void | 打开任务面板(仅 trigger 模式弹层生效) |
| close | () => void | 收起任务面板(仅 trigger 模式弹层生效) |
| visible | Ref<boolean> | 弹层展开状态(只读快照用途) |
| getStats | () => BizTaskStats | 分状态统计(总数 / 各状态计数 / 总进度 / 是否全部终态) |
| getSummary | () => BizTaskStats | getStats 别名(兼容原 BizBatchProgress 语义) |
TIP
与 BizExportButton(同步导出)互补:大数据量导出按钮发起异步任务,任务中心承接任务全生命周期(排队 / 进度 / 下载 / 重试 / 清理);panel 形态亦可嵌入任意批处理(导出 / 上传 / 计算)结果页做进度反馈。