306 lines
16 KiB
Markdown
306 lines
16 KiB
Markdown
|
|
# list-vxe 500条数据渲染卡顿性能分析
|
|||
|
|
|
|||
|
|
## 背景
|
|||
|
|
|
|||
|
|
PC端列表在将分页从20条切换到500条时,渲染卡顿约8-10秒。后端API响应仅660ms,36列 + 373条数据量不大,未开启虚拟滚动也不应如此卡顿。
|
|||
|
|
|
|||
|
|
## 实测数据
|
|||
|
|
|
|||
|
|
### 第一轮(2026-03-12):粗粒度定位
|
|||
|
|
|
|||
|
|
测试条件:374 rows × 37 columns = 13,838 cells,分页从20切换到500。
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
[perf] fetch::unknownNameCheck: 1.46ms
|
|||
|
|
[perf] fetch::dataProcessing: 1.73ms
|
|||
|
|
[perf] fetch::assignData: 2.77ms
|
|||
|
|
[perf] useCacheComputed#3::stableStringify: 17.74ms + 13.06ms
|
|||
|
|
[perf] useCacheComputed#3::func + isEqual: 0.11ms → update
|
|||
|
|
[perf] useMergeCells::total: 11.58ms (0 merge rules)
|
|||
|
|
[perf] useCacheComputed#4::stableStringify: 12.14ms
|
|||
|
|
[perf] useCacheComputed#4::func + isEqual: 0.30ms → update
|
|||
|
|
[perf] vxeGrid::render: 24,428.40ms ⚠️
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**结论**: JS 计算层 (~56ms) 不是瓶颈,vxe-grid 渲染 (24.4s) 占 99.75%。
|
|||
|
|
|
|||
|
|
### 第二轮(2026-03-12):细粒度定位 vxe-grid 内部
|
|||
|
|
|
|||
|
|
在 `getCellRender` 中包装所有 slot 函数,测量单元格渲染函数的调用次数与耗时。
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
[perf] vxeGrid: data=20 rows, columns=37 cols, key=37 (切换前)
|
|||
|
|
[perf] cellSlot: 700 calls, total 8.5ms, avg 0.012ms/call (切换前,20行正常)
|
|||
|
|
|
|||
|
|
[perf] vxeGrid: data=374 rows, columns=37 cols, key=37 (切换后)
|
|||
|
|
[perf] cellSlot: 65,450 calls, total 1,055.7ms, avg 0.016ms/call
|
|||
|
|
[perf] vxeGrid::render: 55,310.5ms ⚠️
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
VxeTable 未触发 onUnmounted/onMounted(`:key=37` 前后不变,未发生销毁重建)。
|
|||
|
|
|
|||
|
|
### 第三轮(2026-03-12):按轮次追踪渲染触发源
|
|||
|
|
|
|||
|
|
在 slot 包装器中加入 `console.trace` 追踪每轮首次调用的触发栈,并按轮次分别统计。
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
[perf] vxeGrid: data=20 rows, columns=37 cols, key=37 (loading=true 阶段)
|
|||
|
|
[perf] cellSlot round#2: 700 calls, slotJs=6.9ms, wallTime=631.5ms
|
|||
|
|
→ trigger: useCacheComputed#3 set value → VxeTable body renderVN
|
|||
|
|
|
|||
|
|
Timer '[perf] vxeGrid::render' already exists (两次 useCacheComputed 串联更新)
|
|||
|
|
|
|||
|
|
[perf] vxeGrid: data=375 rows, columns=37 cols, key=37 (数据到达)
|
|||
|
|
[perf] cellSlot round#3: 39,375 calls, slotJs=528.6ms, wallTime=36,563.1ms
|
|||
|
|
→ trigger: useCacheComputed#4 set value → VxeTable body renderVN
|
|||
|
|
|
|||
|
|
[perf] vxeGrid::render: 24,613.5ms
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
ResizeObserver → recalculate **未出现**。
|
|||
|
|
|
|||
|
|
### 各轮次分析
|
|||
|
|
|
|||
|
|
| 轮次 | 触发源 | 数据量 | slot 调用次数 | slotJs | wallTime |
|
|||
|
|
| --- | --- | --- | --- | --- | --- |
|
|||
|
|
| round#2 | useCacheComputed#3 (vxeTableProps) | 20 rows | 700 | 6.9ms | 631.5ms |
|
|||
|
|
| round#3 | useCacheComputed#4 (gridProps) | 375 rows | 39,375 | 528.6ms | 36,563ms |
|
|||
|
|
|
|||
|
|
### 关键发现
|
|||
|
|
|
|||
|
|
1. **只有 1 轮有效渲染**(round#3),不是多轮重复渲染。round#2 是 loading 阶段的过渡渲染(仍为旧 20 行数据)
|
|||
|
|
2. **slot 函数本身很快**:平均 0.013ms/次,528.6ms 总计,仅占 wallTime 的 1.4%
|
|||
|
|
3. **vxe-grid 内部 + DOM 操作占 98.6%**:36,563ms wallTime - 528.6ms slotJs ≈ **36,034ms**
|
|||
|
|
4. **每个单元格 slot 被调用 ~2.84 次**:39,375 / (375×37) = 2.84。部分是 header cell 渲染,部分可能是 vxe-grid 内部测量
|
|||
|
|
5. **`:key` 未触发重建**:key=37 不变
|
|||
|
|
6. **ResizeObserver recalculate 未触发**:不是重复渲染的原因
|
|||
|
|
7. **两个 useCacheComputed 串联**导致了 2 次 gridProps 更新(round#2 + round#3),其中 round#2 是浪费的过渡渲染
|
|||
|
|
|
|||
|
|
### 根因确认
|
|||
|
|
|
|||
|
|
**瓶颈是 vxe-grid 在无虚拟滚动下渲染 375×37=13,875 个 DOM 单元格的固有开销**。
|
|||
|
|
这不是我们的代码问题,而是 vxe-grid 框架在全量 DOM 渲染模式下的性能限制。
|
|||
|
|
|
|||
|
|
两个 useCacheComputed 的串联更新造成了额外的 1 次过渡渲染(round#2),但主要开销在 round#3。
|
|||
|
|
|
|||
|
|
## 数据流:分页切换时的完整触发链
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
用户切换分页 500条
|
|||
|
|
→ currentPageSize 变化
|
|||
|
|
→ useOffsetPagination.onPageSizeChange → getTableData (debounce 50ms)
|
|||
|
|
→ API 请求 (660ms)
|
|||
|
|
→ data.value = records (shallowRef 赋值)
|
|||
|
|
┌──────────────────────────────────────────────────┐
|
|||
|
|
│ 以下全部在主线程执行,互相串联 │
|
|||
|
|
├──────────────────────────────────────────────────┤
|
|||
|
|
│ ① vxeTableProps useCacheComputed 重算 (~31ms) │
|
|||
|
|
│ → stableStringify → debounce 30ms → func+isEqual│
|
|||
|
|
│ → vxeTableProps.value 更新 │
|
|||
|
|
│ │
|
|||
|
|
│ ② VxeTable 接收新 props │
|
|||
|
|
│ → useMergeCells computed 同步重算 (~12ms) │
|
|||
|
|
│ │
|
|||
|
|
│ ③ gridProps useCacheComputed 重算 (~12ms) │
|
|||
|
|
│ → stableStringify → debounce 30ms → func+isEqual │
|
|||
|
|
│ → gridProps.value 更新 │
|
|||
|
|
│ │
|
|||
|
|
│ ④ vxe-grid 接收新 props → 渲染 13,838 个单元格 │
|
|||
|
|
│ ⚠️ 耗时 24,428ms ← 唯一瓶颈! │
|
|||
|
|
└──────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 已排除的可能原因
|
|||
|
|
|
|||
|
|
| 可能原因 | 排查结果 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| slot 渲染函数(renderItem)本身慢 | ❌ 排除。0.013ms/次,528ms 总计,仅占 1.4% |
|
|||
|
|
| `:key="columns.length"` 导致重建 | ❌ 排除。key=37 前后不变,未触发 onUnmounted |
|
|||
|
|
| ResizeObserver recalculate 触发二次渲染 | ❌ 排除。recalculate 日志未出现 |
|
|||
|
|
| 多轮重复渲染 | ❌ 排除。只有 1 轮有效渲染(round#3),无多轮 |
|
|||
|
|
| stableStringify / useMergeCells 等 JS 计算 | ❌ 排除。总计 ~56ms,可忽略 |
|
|||
|
|
|
|||
|
|
## 已确认的根因
|
|||
|
|
|
|||
|
|
**vxe-grid 在无虚拟滚动下渲染 375×37 个 DOM 单元格的固有开销 = ~36 秒**
|
|||
|
|
|
|||
|
|
375 行 × 37 列 = 13,875 单元格 → 每个单元格包含 `<td>` + `<div.vxe-cell>` + slot 内容 ≈ 3-5 个 DOM 节点
|
|||
|
|
总计约 **42,000-70,000 个 DOM 节点**的创建、插入、样式计算、布局。
|
|||
|
|
|
|||
|
|
额外开销:两个 useCacheComputed 串联导致 1 次过渡渲染(round#2,旧 20 行数据),浪费 ~631ms。
|
|||
|
|
|
|||
|
|
### 第四轮(2026-03-12):自定义渲染 vs 纯文本对比 + 按类型拆解
|
|||
|
|
|
|||
|
|
#### 4a. 纯文本 vs 自定义渲染对比
|
|||
|
|
|
|||
|
|
将 `getCellRender` 中所有 slot 替换为 `<span>{原始值}</span>`,对比渲染耗时。
|
|||
|
|
|
|||
|
|
| 模式 | vxeGrid::render | 减去基线 | 占比 |
|
|||
|
|
| --- | --- | --- | --- |
|
|||
|
|
| 纯文本 `<span>` | **3,221ms** | 基线 | - |
|
|||
|
|
| 自定义渲染(正常) | **23,577ms** | ~20,356ms | 100% |
|
|||
|
|
|
|||
|
|
**结论:自定义渲染的 VNode/DOM 复杂度贡献了 ~20s(86%)的渲染开销。**
|
|||
|
|
|
|||
|
|
#### 4b. 按列类型拆解
|
|||
|
|
|
|||
|
|
选择性启用某一类列的自定义渲染,其余用纯文本,对比耗时。
|
|||
|
|
|
|||
|
|
| 模式 | vxeGrid::render | 减去基线(3.2s) | 占自定义渲染总开销 |
|
|||
|
|
| --- | --- | --- | --- |
|
|||
|
|
| only-action(仅操作列自定义渲染) | 3,642ms | ~421ms | 2% |
|
|||
|
|
| only-merge(仅合并列自定义渲染) | 3,081ms | ~0ms | 0% |
|
|||
|
|
| only-column(仅普通数据列自定义渲染) | 22,808ms | ~19,587ms | **96%** |
|
|||
|
|
|
|||
|
|
**结论:`renderItem`(普通数据列)占自定义渲染开销的 96%。操作列和合并列可忽略。**
|
|||
|
|
|
|||
|
|
#### 4c. renderItem 按字段类型耗时(slot JS 执行时间)
|
|||
|
|
|
|||
|
|
在 `renderItem` 闭包内按 `fieldType` 统计每次 slot 函数的 JS 执行时间(不含 Vue patch/DOM)。
|
|||
|
|
|
|||
|
|
375 rows × 37 cols 切换后数据:
|
|||
|
|
|
|||
|
|
| 字段类型 | 调用次数 | slot JS 总耗时 | 每次耗时 |
|
|||
|
|
| --- | --- | --- | --- |
|
|||
|
|
| SINGLE_SELECTOR | 13,508 | 564.4ms | 0.042ms |
|
|||
|
|
| TEXT_INPUT | 19,648 | 44.4ms | 0.002ms |
|
|||
|
|
| NUMERIC | 3,684 | 10.0ms | 0.003ms |
|
|||
|
|
| DATETIME | 2,456 | 5.6ms | 0.002ms |
|
|||
|
|
| DATE | 1,228 | 2.1ms | 0.002ms |
|
|||
|
|
| EMPLOYEE | 1,228 | 1.4ms | 0.001ms |
|
|||
|
|
| **合计** | **41,752** | **627.9ms** | - |
|
|||
|
|
|
|||
|
|
**关键发现:**
|
|||
|
|
|
|||
|
|
1. **slot JS 总共 628ms,但 vxeGrid::render 为 25,611ms**。差额 ~25,000ms 是 Vue patch + DOM 操作开销
|
|||
|
|
2. **SINGLE_SELECTOR 每次 0.042ms**(TEXT_INPUT 的 21 倍),但绝对值仍很小
|
|||
|
|
3. **瓶颈不在 slot 函数执行,而在 slot 返回的 VNode 被 Vue patch 到 DOM 的开销**
|
|||
|
|
|
|||
|
|
#### 4d. 各字段类型的渲染组件分析
|
|||
|
|
|
|||
|
|
| 字段类型 | 渲染组件 | DOM 结构 | 复杂度 |
|
|||
|
|
| --- | --- | --- | --- |
|
|||
|
|
| SINGLE_SELECTOR | `Tag` (el-tag + el-popover) / `TextTag` / `LightText` | span + popover/tag | 中等 |
|
|||
|
|
| TEXT_INPUT | `h('span', content)` | 单个 span | 极轻 |
|
|||
|
|
| NUMERIC | `h('span', content)` | 单个 span | 极轻 |
|
|||
|
|
| DATETIME | `FieldRenderMap[DATETIME]` | 格式化 span | 轻量 |
|
|||
|
|
| DATE | `FieldRenderMap[DATE]` | 格式化 span | 轻量 |
|
|||
|
|
| EMPLOYEE | `FieldRenderMap[EMPLOYEE]` | span | 轻量 |
|
|||
|
|
|
|||
|
|
**结论:单个组件都不重,但 41,752 次调用 × 每次 3-10 DOM 节点 = 125,000-400,000 DOM 节点的总量是根因。**
|
|||
|
|
|
|||
|
|
### 第四轮总结
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
渲染耗时分解(375 rows × 37 cols):
|
|||
|
|
|
|||
|
|
vxe-grid 自身 DOM 基线(td + div.vxe-cell): ~3,200ms (13%)
|
|||
|
|
自定义渲染 VNode 的 Vue patch + DOM 开销: ~20,400ms (83%)
|
|||
|
|
├── renderItem(普通数据列): ~19,600ms (96%)
|
|||
|
|
│ ├── SINGLE_SELECTOR 相关 DOM: 占比最大(调用最多、VNode 最复杂)
|
|||
|
|
│ ├── TEXT_INPUT 相关 DOM: 调用最多但 VNode 简单
|
|||
|
|
│ └── 其他类型: 占比小
|
|||
|
|
├── renderAction(操作列): ~420ms (2%)
|
|||
|
|
└── renderMerge(合并列): ~0ms (0%)
|
|||
|
|
slot JS 函数执行: ~628ms (3%)
|
|||
|
|
JS 计算层(useCacheComputed 等): ~56ms (< 1%)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 第五轮(2026-03-12):Chrome Performance Trace 分析
|
|||
|
|
|
|||
|
|
通过保存 Chrome Performance 录制的 Trace 文件(Trace-20260312T161653.json.gz),用脚本解析浏览器底层的事件分布。
|
|||
|
|
|
|||
|
|
#### 两个 Long Task
|
|||
|
|
|
|||
|
|
| Long Task | 耗时 | 对应阶段 |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| Task 1 | **11,019ms** | 主渲染(375行数据到达后的 vxe-grid 渲染) |
|
|||
|
|
| Task 2 | **3,033ms** | 过渡渲染(loading 阶段旧 20 行数据的 round#2) |
|
|||
|
|
| Task 3 | 646ms | 其他 |
|
|||
|
|
| Task 4 | 608ms | 其他 |
|
|||
|
|
|
|||
|
|
#### Task 1(11,019ms)浏览器底层耗时分解
|
|||
|
|
|
|||
|
|
| 类别 | 耗时 | 次数 | 说明 |
|
|||
|
|
| --- | --- | --- | --- |
|
|||
|
|
| JS 执行(v8.callFunction) | ~11,019ms | 1 | Vue patch + slot 函数,占据整个 task |
|
|||
|
|
| GC - MinorGC(Scavenger) | ~436ms | 114 次 | 新生代回收,大量短生命周期 VNode/闭包对象 |
|
|||
|
|
| GC - 后台 Scavenge | ~1,962ms | 995 次 | 后台并行 scavenge |
|
|||
|
|
| GC - MajorGC 后台标记 | ~870ms | 174 次 | 老生代标记 |
|
|||
|
|
| UpdateLayoutTree(样式计算) | **~823ms** | **4,431 次** | 每批 DOM 插入触发样式重算 |
|
|||
|
|
| Layout(布局计算) | ~146ms | 12 次 | 实际布局计算 |
|
|||
|
|
|
|||
|
|
#### Task 2(3,033ms)浏览器底层耗时分解
|
|||
|
|
|
|||
|
|
| 类别 | 耗时 | 次数 | 说明 |
|
|||
|
|
| --- | --- | --- | --- |
|
|||
|
|
| JS 执行 | ~3,033ms | 1 | 过渡渲染(round#2) |
|
|||
|
|
| GC - MinorGC | ~124ms | 31 次 | |
|
|||
|
|
| UpdateLayoutTree | ~226ms | 1,110 次 | |
|
|||
|
|
| Layout | ~41ms | 4 次 | |
|
|||
|
|
|
|||
|
|
#### Trace 分析关键发现
|
|||
|
|
|
|||
|
|
1. **GC 压力巨大**:主渲染期间 MinorGC 触发 114 次(~436ms on-thread + ~1,962ms 后台),说明 Vue patch 过程中创建了大量短生命周期对象(VNode、h() 返回值、闭包、临时数组等)
|
|||
|
|
2. **样式重算频繁**:UpdateLayoutTree 被触发 4,431 次共 823ms。vxe-grid 每插入一批 DOM 节点都会触发浏览器样式重算,这是 DOM 数量大的连锁反应
|
|||
|
|
3. **Layout 不贵**:仅 146ms / 12 次,浏览器布局计算本身不是瓶颈
|
|||
|
|
4. **过渡渲染浪费 3s**:Task 2 的 3,033ms 完全是 useCacheComputed 串联导致的无效渲染(round#2),这是可以消除的
|
|||
|
|
5. **总阻塞 ~14s**(Task 1 + Task 2),与 console.time 测得的 ~25s 有差异,差异部分是 trace 录制本身的采样误差和后台 GC 时间
|
|||
|
|
|
|||
|
|
## 已确认的根因(更新)
|
|||
|
|
|
|||
|
|
**根因是 375×37 = 13,875 个单元格的自定义渲染产生的 DOM 节点总量过大。**
|
|||
|
|
|
|||
|
|
- 每个单元格的 slot 函数本身很快(0.002-0.042ms),不是瓶颈
|
|||
|
|
- 每个单元格的渲染组件也不重(span/tag/轻量组件),不是瓶颈
|
|||
|
|
- 瓶颈是 **数量 × 复杂度的乘积效应**:41,752 次 slot 调用 × 每次 3-10 DOM 节点 = 巨量 DOM 操作
|
|||
|
|
- vxe-grid 自身基线(纯文本 span)约 3.2s,自定义渲染额外增加 ~20s
|
|||
|
|
|
|||
|
|
## 已排除的可能原因(更新)
|
|||
|
|
|
|||
|
|
| 可能原因 | 排查结果 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| slot 渲染函数(renderItem)本身慢 | ❌ 排除。0.002-0.042ms/次,628ms 总计,仅占 2.5% |
|
|||
|
|
| 某个特定字段类型的组件特别重 | ❌ 排除。所有类型都是轻量 span/tag,无重量级组件 |
|
|||
|
|
| `:key="columns.length"` 导致重建 | ❌ 排除。key=37 前后不变,未触发 onUnmounted |
|
|||
|
|
| ResizeObserver recalculate 触发二次渲染 | ❌ 排除。recalculate 日志未出现 |
|
|||
|
|
| 多轮重复渲染 | ❌ 排除。只有 1 轮有效渲染(round#3),无多轮 |
|
|||
|
|
| stableStringify / useMergeCells 等 JS 计算 | ❌ 排除。总计 ~56ms,可忽略 |
|
|||
|
|
| renderAction(操作列)开销大 | ❌ 排除。仅 ~421ms(2%) |
|
|||
|
|
| renderMerge(合并列)开销大 | ❌ 排除。~0ms |
|
|||
|
|
|
|||
|
|
## useCacheComputed 所有调用点
|
|||
|
|
|
|||
|
|
| # | 实例 | 文件 | watcher 内容 | 分页切换时触发 | 实测耗时 |
|
|||
|
|
| --- | --- | --- | --- | --- | --- |
|
|||
|
|
| 1 | #1 | Columns.ts:298 | schema.children, Maps, pageConfig | 不触发 | - |
|
|||
|
|
| 2 | #2 | ListVxePreview.vue:184 | `[]` 空数组 | 不触发 | - |
|
|||
|
|
| 3 | #3 | ListVxePreview.vue:263 | data, schema, columns, loading | **触发** | stringify 31ms |
|
|||
|
|
| 4 | #4 | VxeTable.vue:113 | mergeCells, data, columns, loading | **触发** | stringify 12ms |
|
|||
|
|
|
|||
|
|
## 优化方向(基于四轮实测数据)
|
|||
|
|
|
|||
|
|
| 优先级 | 方向 | 方案 | 预期收益 |
|
|||
|
|
| --- | --- | --- | --- |
|
|||
|
|
| **P0** | **减少 DOM 节点总量** | 开启虚拟滚动 virtualY(仅渲染可视区域行) | 从 375 行降至 ~20 行可视,理论降至 < 2s |
|
|||
|
|
| **P0** | **简化 SINGLE_SELECTOR 渲染** | Tag/el-tag/el-popover 替换为纯 span 显示(列表模式不需要交互组件) | 减少最大类型的 DOM 节点数 |
|
|||
|
|
| P1 | **消除过渡渲染** | 两个 useCacheComputed 串联导致 round#2 浪费 ~631ms | 省 ~631ms |
|
|||
|
|
| P1 | **减少 slot 调用次数** | 41,752 次调用 / 13,875 单元格 = 3x,排查 vxe-grid 内部重复调用原因 | 可能省 1/3 渲染时间 |
|
|||
|
|
| P2 | v-bind="gridProps" 拆分 | 拆分为独立 props,避免全量对象替换 | 减少内部更新次数 |
|
|||
|
|
| P3 | stableStringify 移除 | 改用 Vue 原生响应式追踪 | 省 ~43ms |
|
|||
|
|
|
|||
|
|
## 关键文件清单
|
|||
|
|
|
|||
|
|
| 文件 | 作用 |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `packages/form-designer/src/config/widget/list-vxe/composables/useCacheComputed.ts` | 缓存计算核心 |
|
|||
|
|
| `packages/form-designer/src/config/widget/list-vxe/composables/Columns.ts` | 列计算逻辑 |
|
|||
|
|
| `packages/form-designer/src/config/widget/list-vxe/composables/fetch.ts` | 数据获取 |
|
|||
|
|
| `packages/form-designer/src/config/widget/list-vxe/preview/ListVxePreview.vue` | 列表预览组件 |
|
|||
|
|
| `packages/form-designer/src/config/widget/list-vxe/preview/core-table/VxeTable.vue` | VxeTable 封装 |
|
|||
|
|
| `packages/form-designer/src/config/widget/list-vxe/preview/core-table/useMergeCells.ts` | 合并单元格计算 |
|
|||
|
|
| `packages/form-designer/src/config/widget/list-vxe/render/RenderItem.tsx` | 单元格渲染 |
|
|||
|
|
| `packages/form-designer/src/config/widget/list-vxe/render/render.tsx` | 渲染器入口 |
|
|||
|
|
| `packages/form-designer/src/config/widget/list-vxe/render/RenderAction.tsx` | 操作列渲染 |
|
|||
|
|
| `packages/form-designer/src/config/widget/list-vxe/render/RenderMerge.tsx` | 合并列渲染 |
|