iView Table 中 Progress 进度条换行问题解决方案

一、问题背景

在 iView(View UI)Table 组件中,通过 render 函数渲染 Progress 进度条时,当进度值较大(如超过 100%)或列宽有限时,进度条与百分比文字发生换行,导致单元格布局错乱、视觉异常。

问题现象:

  • 进度条与百分比文字上下换行显示,而非同一行水平排列

  • 进度值超过 100% 时,iView Progress 组件渲染异常(percent 属性最大只接受 100)

  • 单元格高度在数据加载后突变,页面出现"抖动"

二、问题根因分析

2.1 直接在 render 中返回 Progress 组件

典型问题写法:

render (h, { row }) {
    const rate = (row.actual / row.plan) * 100
    row.rate = parseFloat(rate.toFixed(2))
    const color = row.rate < 70 ? '#ff2239' : row.rate < 90 ? '#ff9e00' : '#00bf00'
    return h('Progress', { props: { percent: row.rate, 'stroke-color': color, 'hide-info': false, vertical: false }})
}

问题在于:

  1. 无外层容器约束 — 直接返回 Progress VNode,没有 flex 容器控制进度条和文字的排列方式

  2. iView Progress 内置百分比文字与进度条争空间hide-info: false 时,百分比文字由 Progress 组件内部渲染,与进度条共享同一行空间;当列宽不足且百分比值较长(如 105.04%)时,文字被挤到下一行

  3. percent 超过 100 的边界问题 — iView Progress 的 percent 属性设计上限为 100,传入超过 100 的值会导致进度条溢出或渲染异常

2.2 iView Table 渲染流程的时序问题

iView Table 的渲染流程:

初始渲染 → 列宽计算 → 行高测量 → 异步数据更新触发重渲染

当 Progress 组件嵌套在 render(h, params) 中时:

  • Progress 内部包含相对定位和弹性布局

  • 外层容器未明确设置对齐方式和尺寸约束

  • Vue 的异步批量更新机制可能导致 $nextTick 执行前 DOM 尚未完成挂载,造成行高测量偏差

三、解决方案

采用 "封装组件 + Flex 布局 + $nextTick 控制 + 自定义百分比文字" 的综合策略。

3.1 封装 ProgressCell 组件

将进度条封装为独立组件,拥有完整的生命周期控制:

const ProgressCell = {
    props: {
        percent: {
            type: Number,
            default: 0
        },
        strokeColor: {
            type: String,
            default: '#2db7f5'
        }
    },
    data() {
        return {
            localPercent: 0
        }
    },
    watch: {
        percent: {
            handler(newVal) {
                this.localPercent = newVal
            },
            immediate: true
        }
    },
    mounted() {
        this.$nextTick(() => {
            // 确保父级表格完成布局后再赋值,避免行高计算偏差
            this.localPercent = this.percent
        })
    },
    render(h) {
        // iView Progress 的 percent 最大 100,超过 100 时用 100 渲染条,文字显示真实值
        const displayPercent = Math.min(this.localPercent, 100)
        const status = this.localPercent >= 100 ? 'success' : 'normal'
        const text = this.localPercent.toFixed(2) + '%'
        return h('div', {
            class: 'progress-cell'
        }, [
            h('Progress', {
                class: 'progress-bar',
                props: {
                    percent: displayPercent,
                    strokeWidth: 10,
                    'stroke-color': this.strokeColor,
                    'hide-info': true,  // 隐藏内置百分比,使用自定义文字避免换行
                    vertical: false,
                    status: status
                }
            }),
            h('span', {
                class: 'progress-text'
            }, text)
        ])
    }
}

3.2 Flex 布局 CSS

.progress-cell {
    display: flex;
    align-items: center;
    width: 100%;
    height: 100%;
    min-height: 28px;
    padding: 2px 0;
    .progress-bar {
        flex: 1;
        min-width: 0; // 允许 flex 子项收缩
        /deep/ .ivu-progress-outer {
            padding-right: 0;
        }
        /deep/ .ivu-progress-show-info .ivu-progress-outer {
            padding-right: 50px; // 为百分比文字预留空间
        }
        /deep/ .ivu-progress-inner {
            background-color: #f3f3f3;
        }
        /deep/ .ivu-progress-bg {
            border-radius: 3px;
        }
    }
    .progress-text {
        flex-shrink: 0;
        width: 50px;
        text-align: right;
        font-size: 12px;
        color: #515a6e;
        margin-left: 4px;
    }
}

3.3 列定义中使用封装组件

{
    title: '完成率',
    key: 'rate',
    width: 220,
    align: 'center',
    render (h, { row }) {
        const rate = (row.actual / row.plan) * 100
        row.rate = parseFloat(rate.toFixed(2))
        const color = row.rate < 70 ? '#ff2239' : row.rate < 90 ? '#ff9e00' : '#00bf00'
        return h(ProgressCell, {
            props: {
                percent: row.rate,
                strokeColor: color
            }
        })
    }
}

四、关键设计决策说明

决策

原因

hide-info: true + 自定义 <span>

iView Progress 内置百分比文字与进度条共享空间,列宽不足时必然换行;将文字独立为 flex 子项,各自占据固定空间,彻底避免换行

flex: 1 + min-width: 0

进度条占据剩余空间min-width: 0 允许 flex 子项在空间不足时收缩而非溢出

flex-shrink: 0 + 固定 width: 50px

百分比文字不收缩,始终完整显示

Math.min(localPercent, 100)

iView Progress 的 percent 上限为 100,超过会导致进度条溢出;用 100 渲染满格条,文字仍显示真实百分比

$nextTick 延迟赋值

确保父级 Table 完成行高计算后再触发 Progress 渲染,避免布局抖动

localPercent 中间变量 + watch

解耦 props 与渲染,使 $nextTick 延迟赋值成为可能

五、方案对比

方案

优点

缺点

适用场景

CSS 强制居中 + flex 布局

简单直接,兼容性好

无法解决动态高度和 >100% 问题

静态数据、固定行高

延迟渲染 Progress($nextTick)

确保 DOM 已挂载

增加代码复杂度

异步数据加载

设置 table 的 table 的 height 属性

启用虚拟滚动,稳定布局

牺牲自动高度特性

大数据量列表

封装 Progress 为独立组件

提升复用性和生命周期控制,彻底解决换行和 >100% 问题

需额外维护组件

多列需进度条、进度值可能超 100%

六、渲染流程图

Table 数据更新
    │
    ├─ 首次渲染 → Vue 编译模板 → 执行 render 函数
    │                                │
    │                                ▼
    │                        创建 ProgressCell VNode
    │                                │
    │                                ▼
    │                        挂载组件到 DOM
    │                                │
    │                                ▼
    │                        调用 $nextTick 延迟赋值
    │                                │
    │                                ▼
    │                        Progress 正常显示 + 自定义文字
    │                                │
    │                                ▼
    │                        表格布局稳定(flex 约束无换行)
    │
    └─ 数据更新 → 触发响应式更新 → watch 重新赋值 localPercent
                                       │
                                       ▼
                               Progress 重绘(flex 布局保持稳定)

七、复用指引

若其他页面也需在 iView Table 中渲染进度条,可将 ProgressCell 提取为公共组件:

  1. ProgressCell 定义移至 src/components/ 目录下独立文件(如 ProgressCell.js

  2. 全局注册或按需引入

  3. 在任意 Table 列的 render 函数中使用 h(ProgressCell, { props: { percent, strokeColor } })

注意事项:

  • 百分比文字宽度 50px 可容纳 999.99%,若需显示更大值需调整宽度

  • 颜色阈值(70/90)为业务约定,可根据实际需求调整

  • strokeWidth: 10 为当前视觉设定,可根据设计稿调整


iView Table 中 Progress 进度条换行问题解决方案
https://blog.cikaros.cn/archives/iview-table-zhong-progress-jin-du-tiao-huan-xing-wen-ti-jie-jue-fang-an
作者
Cikaros
发布于
2026年07月13日
许可协议