AG Grid 校验增强说明

steelTradeAggrid 数值编辑 / 粘贴校验能力增强 · 2026-09-11
目录

1. 背景与本次目标

项目 AG Grid 表格统一使用 steelTradeAggrid 组件,数值列的编辑与粘贴校验通过 numberValidator + extractNumericRule 体系完成(详见 docs/AG-Grid编辑功能改造说明.md)。

在期现业务弹窗(分配方式 / 分配本次使用金额)的落地过程中,暴露了 4 个问题:

序号问题类型
1 数值列需要「上限取自本行另一字段(如可用金额 sPickAmt)」的能力,旧组件只能通过外部 TableInput 的 v-limit 实现 能力缺口
2 列配置里既要写编辑器 props、又要重复写 numericRule,规则来源不统一 可优化
3 粘贴报错只说「格式不正确」,不告诉用户到底是超上限还是小数位超限 缺陷
4 批量粘贴时校验漏检:选中多行粘贴,超限值能绕过校验写入 严重缺陷

2. 增强点总览

#增强点文件属性
1 CndInputNumber 新增 maxField,支持「取本行字段值为上限」+ 输入时实时截断 src/components/agComponent/AgInputNumber.vue 新增
2 extractNumericRule 反推规则时纳入 maxField,粘贴/填充自动带上限,无需 numericRule src/utils/numberValidator.js 增强
3 粘贴报错带上具体原因(复用 validateNumber.message) src/components/steelTradeAggrid/index.vue 修复
4 重写粘贴校验行列映射,逐条对齐 AG Grid v25 真实写入规则;行数据获取加虚拟滚动兜底 src/components/steelTradeAggrid/index.vue 修复

3. 增强点一:CndInputNumber 新增 maxField 上限能力 新增

src/components/agComponent/AgInputNumber.vue

3.1 能力说明

新增 prop maxField(String,默认 '',不传时行为完全不变)。 语义是「上限取自当前行的某个字段」——例如本次使用金额不能超过本行的可用金额 sPickAmt。

为什么叫 maxField 而不是 max?
组件内已有 range: [min, max] 表达静态取值范围,若再叫 max 会产生两套上限语义冲突; 且「取本行某字段」这一跨字段引用含义,maxField 表达更准确。

3.2 使用方式(列配置)

cellEditorFramework: Vue.extend(customMiddleware.createComponent(
  'CndInputNumber',
  {
    mark: 'vCurAmt',
    type: 'number',
    decimalDigit: 2,
    maxField: 'sPickAmt'   // ← 上限 = 本行 sPickAmt 字段值
  }
))

3.3 生效效果:输入即截断 + 气泡提示

在 setVal() 中实时判断(每次输入都执行,等效旧 TableInput 的 v-limit,但改为组件内实现):

分配本次使用金额
收款日期 可用金额 本次使用金额 备注
2026-09-01 29.70 不能超过29.7。 29.7 输入 100 时被实时压回 29.7
2026-09-02 49.70 0.00
图 1 · 输入超过「可用金额」时立即截断到上限,并弹出气泡提示(仅气泡,不红框)

3.4 实现要点

// 取 maxField 指定字段的行值作为上限
getMaxFieldValue() {
  if (!this.maxField) return null
  const data = this.params && this.params.data
  if (!data) return null
  const max = Number(data[this.maxField])
  return Number.isFinite(max) ? max : null
}

// setVal() 中(整数位截断之后、emit 之前)
const maxFieldVal = this.getMaxFieldValue()
if (maxFieldVal !== null && this.childValue !== '' && ... && Number(this.childValue) > maxFieldVal) {
  this.childValue = String(maxFieldVal)
  this.showLimitTip(`不能超过${maxFieldVal}。`)
}
截断仅置 visible1(气泡),不置 visible2(红框)—— 因为 setVal 的 getVal emit 在 visible2 为真时会回写 oldChildValue(旧值), 置红框会导致截断值无法写回表格。

4. 增强点二:编辑器配置自动反推校验规则 增强

src/utils/numberValidator.js

4.1 改动

extractNumericRule 的 CndInputNumber 分支新增 maxField 映射:

// 改前
max: props.range && props.range[1]

// 改后:maxField(字段引用)优先于 range[1](静态上限)
max: props.maxField ? props.maxField : (props.range && props.range[1])

4.2 效果:单一配置来源

改前 · 双份配置(易不一致)
createComponent('CndInputNumber', {
  decimalDigit: 2,
  ...
}),
numericRule: {          // ← 需重复声明
  scale: 2,
  max: 'sPickAmt',
  allowNegative: false
}
改后 · 单份配置
createComponent('CndInputNumber', {
  decimalDigit: 2,
  maxField: 'sPickAmt'   // ← 校验规则由此反推
})
列配置只要给 CndInputNumber 配了 maxField,粘贴 / 拖拽填充的校验就自动带上限, 无需再写 numericRule。纯增强,不影响任何既有列行为。

5. 修复点三:粘贴报错信息完善 修复

src/components/steelTradeAggrid/index.vue · validateClipboardCell

validateNumber 本已算出具体原因(result.message),但旧实现把它丢弃了, 报错只显示行列和原值,用户不知道「为什么不合法」。

改前
提示
粘贴失败,以下单元格格式不正确: 第5行「本次使用金额」:9958011.9
确定
改后
提示
粘贴失败,以下单元格数据不合法: 第5行【本次使用金额】不能大于 29.7(当前值:9958011.9)
确定
图 2 · 报错信息由「只有值」升级为「具体原因 + 当前值」

各校验维度均可透出(由 validateNumber 提供):

场景提示文案
超上限【本次使用金额】不能大于 29.7
小数位超限【本次使用金额】最多保留 2 位小数
负数【本次使用金额】不允许输入负数
非数值【本次使用金额】必须是有效数值

弹窗标题同步由「以下单元格格式不正确」改为「以下单元格数据不合法」(原因可能是超金额而非格式)。

6. 修复点四:批量粘贴校验漏检 修复

src/components/steelTradeAggrid/index.vue · processClipboardData(重写)

6.1 问题现象

选中多行后批量粘贴,超限值绕过校验直接写入;只有点中单个单元格粘贴才会校验。

6.2 根本原因

旧实现假设「剪贴板第 r 行 → 选区起点 + r 行」的简单线性映射;而 AG Grid v25 真实写入规则是 逐行遍历选区、剪贴板循环填充,且行数不整除时超出部分不写入。两者错位,导致校验行号与 实际写入行号不一致,取到的上限字段值(sPickAmt)张冠李戴。

场景:选中第 2~5 行(4 行),粘贴 5 个值
行 可用金额 sPickAmt 粘贴值 结果
229.709958011.9 超上限
349.70390 超上限
49958011.90390 合法
5390.0090 合法
6390.00(第5个值 300 被 AG Grid 丢弃:4 % 5 ≠ 0) —
图 3 · 修复后:按 AG Grid 实际写入规则映射,正确拦截超限的 2 行,整次粘贴取消

6.3 修复:逐条对齐 AG Grid v25 写入规则

重写 processClipboardData,按三种规则收集「待校验单元格」,再统一校验:

规则场景映射方式
① 单值 + 有选区 值铺满选区所有行 × 列
② 多值 + 选区多格 逐行遍历选区,clipboardData[index] 循环填充;行数不被剪贴板行数整除时,超出部分跳过(对齐 AG Grid 不写入)
③ 从焦点格往下 按剪贴板行数逐行往下,列自焦点列依次对应
// 规则② 核心(对齐 ag-grid pasteIntoActiveRange)
const abortRepeating = rangeRowCount % data.length !== 0
let indexOffset = 0, dataRowIndex = 0
for (let rowIndex = startRow; rowIndex <= endRow; rowIndex++) {
  const index = rowIndex - startRow
  let currentIndex = index - indexOffset
  if (currentIndex >= data.length) {
    if (abortRepeating) continue      // 不整除,该行不写入、不校验
    indexOffset += dataRowIndex; dataRowIndex = 0
    currentIndex = index - indexOffset
  }
  const clipboardRow = data[currentIndex]
  range.columns.forEach((column, colIdx) => {
    if (colIdx >= clipboardRow.length) return
    cells.push({ value: clipboardRow[colIdx], column, rowIndex })
  })
  dataRowIndex++
}

6.4 附带修复:行数据获取的虚拟滚动兜底

getRowDataByIndex 原先只用 getDisplayedRowAtIndex,虚拟滚动下未渲染行返回空, 导致上限字段取不到 → 上限校验被静默跳过(另一处漏检原因)。现增加行模型兜底:

getRowDataByIndex(rowIndex) {
  const api = this._lastFocusedGrid.api
  let node = api.getDisplayedRowAtIndex(rowIndex)
  if (!node && api.getModel) node = api.getModel().getRow(rowIndex)  // 兜底
  return node ? node.data : null
}

7. 业务落地:期现弹窗 vCurAmt 列

src/views/contract/domesticSales/waitinvalidDialog.vue
src/views/contract/domesticSales/waitinvalidDialoghz.vue

「本次使用金额」(vCurAmt)列由旧 TableInput 迁移至 CndInputNumber,并配置 maxField:

// 改前
{
  field: 'vCurAmt',
  editable: true,
  max: 'sPickAmt',
  cellEditor: 'TableInput'
}

// 改后
{
  field: 'vCurAmt',
  editable: true,
  cellEditorFramework: Vue.extend(customMiddleware.createComponent(
    'CndInputNumber',
    { mark: 'vCurAmt', type: 'number', decimalDigit: 2, maxField: 'sPickAmt' }
  ))
}

7.1 三条路径的最终行为

路径规则来源超限行为
输入(手动编辑)组件 prop maxField实时截断 + 气泡提示
粘贴extractNumericRule 反推 maxField报错并取消整次粘贴(原因可见)
拖拽填充同上不合规单元格保持原值

7.2 与旧 TableInput 的差异

对比项TableInput(旧)CndInputNumber + maxField(新)
上限实现外部 v-limit 指令组件内 setVal 实时判断
小数位默认4 位由列配 decimalDigit 决定(当前 2 位)
超限提示静默截断截断 + 气泡提示
粘贴校验靠 numericRule自动反推,无需 numericRule

8. 文件清单与影响面

#文件改动影响面
1 src/components/agComponent/AgInputNumber.vue 新增 maxField prop + getMaxFieldValue + setVal 实时钳制 公共组件,默认不传 = 行为不变
2 src/utils/numberValidator.js extractNumericRule 反推纳入 maxField 公共工具,纯增强
3 src/components/steelTradeAggrid/index.vue 报错信息完善;processClipboardData 重写;getRowDataByIndex 兜底 公共组件,所有表格粘贴校验生效
4 src/views/contract/domesticSales/waitinvalidDialog.vue
src/views/contract/domesticSales/waitinvalidDialoghz.vue
vCurAmt 列迁移至 CndInputNumber + maxField 业务页面
影响面提醒:第 2、3 项是 steelTradeAggrid 公共组件改动, 所有使用该组件的表格粘贴校验都会跟着变化——属修复缺陷 / 信息增强,不改变正常粘贴行为。 上线后建议对高频粘贴场景(批量导入、跨列出货单等)做一轮回归。
后续演进建议:

本文档为 docs/AG-Grid编辑功能改造说明.md 的演进补充,描述 2026-09-11 的校验增强。