ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LiguiUI表格编辑权限实战:单元格可编辑性从列到行的精细控制

LiguiUI表格编辑权限实战:单元格可编辑性从列到行的精细控制 做后台管理系统这些年表格里的“能不能编辑”从来都不是一个简单布尔值。很多时候我们需要的不是整张表开编辑或锁编辑而是这一列能改、那一列不能改这几行能进编辑态、那几行只能干瞪眼甚至同一个单元格在不同状态下都有不同的编辑权限。最近我用 LiguiUI 做项目时正好把“设置单元格是否可编辑”这事从简单到复杂完整捋了一遍从列配置到行级拦截再到单元格级动态判断和编辑态恢复顺手还解决了几个和编辑强相关的联动问题。这篇就把我的实现思路、踩过的坑、以及和合并单元格、空值自动填充、可编辑下拉框这些热词配套的处理方案一起整理出来给正在跟 LiguiUI 表格编辑权限较劲的朋友一点参考。1. 内容整体设计与思路拆解1.1 先从需求说起为什么“能否编辑”需要三层控制如果只是做一个内部管理系统表格编辑最常见的需求是这样用户点一个按钮进入“编辑模式”然后把某些列改一改点保存提交。这种粗粒度控制用 LiguiUI 的话一句editEnable: true就完事了。但实际上一旦面对真实业务需求往往会长成这样订单列表里只有“未审核”状态的订单允许改数量“已审核”的只能看不能动。同一列里管理员能编辑所有人普通用户只能编辑自己创建的那几行。某个字段在新增时有默认值在编辑时却禁止修改比如单据编号。部分单元格的值来自公式或上级系统推送改完之后如果不满足校验需要立刻还原或给出提示。这些需求背后其实指向同一个问题编辑权限不是一个全局开关而是一个三维度的叠加判断。横向看不同列权限不同纵向看不同行权限不同再加上“当前这条数据本身的状态”就变成了单元格级的动态权限。LiguiUI 的表格编辑体系正好是按这个思路设计的从列配置、行规则、单元格事件三个层面分别提供了控制点。理解了这个分层模型后面所有配置才不容易乱。1.2 编辑事件流一次点击编辑的背后发生了什么在写配置之前我觉得有必要先把 LiguiUI 表格进入编辑态的流程讲清楚因为我发现很多人在设置可编辑时卡住不是不会写 API而是不知道 LiguiUI 在“能不能编辑”这件事上经过了哪些判断环节。LiguiUI 的表格编辑大体上是这样的表格初始化时读取列的editType配置决定哪些列具备编辑能力当用户双击单元格或按配置使用单击触发时LiguiUI 并不会直接渲染输入框而是先跑一遍“编辑前校验”逻辑——包括行级isEdit判断、列级allowEdit判断、以及单元格级beforeEdit事件回调。只有这些校验全部通过单元格才会切换成编辑态渲染出输入框、下拉框或日期选择器。这个流程的价值在于它把“能不能编辑”拆成了多个可插拔的拦截点而不是一把锁锁死。你可以在任意一个环节返回false来阻止编辑也可以在beforeEdit里做动态逻辑甚至可以在返回值里附带提示信息。后面我实际演示的时候你会发现 LiguiUI 的这套设计非常贴合真实项目里“各种条件叠加”的场景。// LiguiUI 表格编辑拦截的简化示意 onBeforeEdit: function (cellInfo) { // cellInfo 里包含 row、column、rowIndex、columnIndex 等 // 返回 false 则单元格不进入编辑态 }2. 核心 API 解析从列配置到动态拦截2.1 列级别设置editType 与 allowEdit 的组合用法先看最基础的一层——列配置。在 LiguiUI 中一列能不能编辑第一个判断依据就是有没有配editType。常见值有text、select、date、number等也可以配自定义编辑器。如果列上没有editType那这一列默认就是只读的双击也不会进入编辑态。假如你想让“备注”列可编辑列配置大概是这个样子{ field: remark, title: 备注, editType: text, allowEdit: true }这个allowEdit是列级别的总开关。和“不配 editType”不同allowEdit: false表示这一列本身具备编辑能力但在当前条件下被关闭了。这个区别在动态切换权限时很有用。比如一个权限开关切到“只读模式”时我们可以批量遍历列配置把所有列的allowEdit设为false之后这些列立刻不再允许编辑切回“编辑模式”时再恢复true。相比粗暴地重新渲染表格这种做法的开销小得多也不会导致用户已填但未保存的数据丢失。不过要注意allowEdit只是列级开关它代表的是“这列是否允许编辑”。真正决定当前某个单元格能不能编辑还要看行级和单元格级的判断。我见过有人以为把allowEdit设成动态函数就能实现行级控制实际上 LiguiUI 里allowEdit更建议传布尔值行维度的事情交给行级配置和事件回调去处理职责分开代码才不容易绕晕。2.2 行级别控制同一列在不同行上的差异化处理列级配置解决了“哪些列能编辑”的问题但很快你就会遇到更细的需求同样是“数量”这一列第一行能改第二行不能改。这种场景下LiguiUI 提供行的编辑状态控制能力。我常用的做法是在表格配置里加一个rowEditRule不同版本可能叫isEditable或rowEditable具体以当前版本 API 为准通过函数返回当前行是否允许编辑rowEditRule: function (row, index) { // 比如订单已关闭不允许编辑 if (row.status closed) return false; return true; }这个规则的执行时机是在单元格进入编辑态之前也就是说它天然充当了一个行级过滤器。如果某一行不允许编辑那这一行的所有单元格双击后都不会出现输入框。实际项目中我还会配合行状态来使用比如列表左侧有单选框或行内开关切换行的“锁定”状态时重新刷新该行的编辑规则。不过我要提醒一句rowEditRule适合做“静态行属性”的判断比如根据某行数据里的状态字段来决定。如果判断条件依赖另一个异步结果比如“需要请求接口确认该行今天是否还有剩余额度”那这个方法就不够用因为返回false只能阻止编辑却没法让你在判断期间给出“正在校验”的反馈或者处理接口超时。这种情况我会放到后面的beforeEdit事件里手动处理因为事件回调里更容易做异步流程和 UI 提示。2.3 单元格级别beforeEdit 事件里完成最终裁决列级和行级判断完之后最后一道关卡就是单元格级的beforeEdit事件。这也是我实际开发中用得最多的一个拦截点因为它能拿到最完整的上下文行数据、列字段、行列索引、旧值、单元格 DOM 等。我在一个项目里的需求是这样的物料列表里“含税单价”这一列如果该物料的“税率”字段为空就不允许直接改单价必须先填税率。用beforeEdit处理就非常直观beforeEdit: function (cellInfo) { var row cellInfo.row; if (cellInfo.field taxPrice !row.taxRate) { liguiUI.msg(请先填写税率再修改含税单价); return false; } return true; }注意beforeEdit返回false时除了阻止编辑还会让这次点击不触发其他副作用。有时候我们想在阻止编辑的同时做点别的比如弹一个“无权限”的提示框或者把焦点跳到有权限的单元格上。这些逻辑都可以直接写在beforeEdit里。另外beforeEdit里也可以支持异步判断。比如我先调用接口确认批次号是否锁定在请求期间可以先短暂地进入一个 loading 状态等结果回来再决定是否放行。这种动态校验在纯前端配置里是做不到的所以beforeEdit基本是“单元格最终能不能编辑”的最强裁决点。3. 实操过程设置单元格是否可编辑的几种典型实现3.1 五分钟快速实现通过列配置和表格开关控制编辑模式为了让刚接触 LiguiUI 的朋友能快速跑通我先给一个最简单可用的完整示例。这个示例里做了一个“是否允许编辑”的总开关以及一个“名称列不可编辑、数量列可编辑”的列级限制。你可以直接复制到页面里跑一下。var table liguiUI({ container: #tableContainer, data: [ { id: 1, name: 物料A, num: 10 }, { id: 2, name: 物料B, num: 20 } ], columns: [ { field: name, title: 物料名称, editType: text, allowEdit: false }, { field: num, title: 数量, editType: number, allowEdit: true } ], editConfig: { enable: true, // 表格编辑总开关 trigger: dblclick // 双击进入编辑 } }); // 切换只读模式 function setReadOnly(flag) { var cols table.getColumns(); cols.forEach(function (col) { if (col.field num) { col.allowEdit !flag; table.updateColumn(col); } }); }这个例子里其实已经包含了“设置单元格是否可编辑”最核心的两个手段总开关enable控制整个表格是否开启编辑能力列上的allowEdit控制某一列是否允许编辑。如果你只需要这种颗粒度那这两处配置就完全够用了剩下的就是业务联动的细节。3.2 动态控制实战根据行状态字段决定当前行可编辑列接着上面说很多业务里列的权限是相对固定的但行是动态的。我在做采购订单列表时遇到过这样一个需求订单有三种状态——“草稿”“待审核”“已审核”。只有“草稿”状态下的订单允许编辑数量和交期“待审核”只能看“已审核”不但不能编辑连双击都不应该有反应。用 LiguiUI 实现的话行级规则大概是这样rowEditRule: function (row) { if (row.status draft) return true; return false; }如果是只读状态双击没有反应这体验上有点突兀。我后来又加了一个全局提示当用户双击了不可编辑的单元格时给一个 toast 说明原因。做法是在beforeEdit里补一个兜底判断beforeEdit: function (cellInfo) { if (cellInfo.row.status ! draft) { liguiUI.msg({ type: warning, text: 当前订单状态不可编辑 }); return false; } return true; }也许你会问既然rowEditRule已经返回false了beforeEdit还能被触发吗这就要看 LiguiUI 版本的处理顺序了。在我使用的版本里beforeEdit事件是最后一道判断即便行级规则禁止了编辑事件回调依然会执行因此适合用来做提示和统计。当然不同版本行为可能有差异建议实际测试一下事件顺序再决定把提示放在哪一层。3.3 进阶组合行、列、单元格三层规则叠加的完整示例当行级、列级、单元格级三层规则同时存在时常见的问题是“到底谁先谁后”。以我个人的经验最好在代码里让三层判断职责分明避免同一个逻辑散落多处。我在项目里的习惯是列配置里的allowEdit只负责“这个字段类型是否允许编辑”比如主键、创建人这种永远不让编辑的字段。rowEditRule只负责“这一行能不能动”比如行的状态是终态就不能编辑。beforeEdit负责所有“动态、跨字段、异步”的判断比如“当 colA 为空时 colB 不能编辑”。下面是一个综合示例把三层规则叠在一个表格上var table liguiUI({ container: #tableContainer, data: [ { id: 1, code: PO001, price: 100, taxRate: 13, status: draft }, { id: 2, code: PO002, price: 200, taxRate: , status: draft } ], columns: [ { field: code, title: 单号, editType: text, allowEdit: false }, { field: price, title: 单价, editType: number, allowEdit: true }, { field: taxRate, title: 税率, editType: number, allowEdit: true } ], editConfig: { enable: true, trigger: dblclick }, rowEditRule: function (row) { return row.status draft; }, beforeEdit: function (cellInfo) { // 单价不允许大于 1000 if (cellInfo.field price cellInfo.row.price 1000) { liguiUI.msg({ type: warning, text: 单价不能超过 1000 }); return false; } // 税率未填时不能修改单价 if (cellInfo.field price !cellInfo.row.taxRate) { liguiUI.msg({ type: warning, text: 请先填写税率 }); return false; } return true; } });这个示例中“单号”列永远不可编辑“草稿”状态的行才有机会编辑而“单价”还额外受到“税率必填”和“价格上限”两个规则约束。实际项目里写代码时建议把beforeEdit里的逻辑抽成独立函数或规则数组否则数据量大之后这个回调会变得很臃肿。4. 关联能力的联动处理合并单元格与编辑权限4.1 合并单元格场景下的可编辑冲突LiguiUI 的表格是支持合并单元格的通常通过spanMethod配置实现。比如物料分组渲染时相同分类合并成一格效果上挺清爽。但合并单元格和“单元格是否可编辑”放一起时很容易出现一个尴尬情况合并的格子占了多行位置但它到底对应哪一行数据点了合并格子之后编辑器又该挂在哪一行我实际遇到的场景是这样一个订单明细表按“仓库”合并单元格同一仓库的多条明细共用第一个单元格。双击合并后的“仓库”单元格时LiguiUI 会把它当作合并区域左上角的那个单元格来解析。如果这个合并格被设置为可编辑那么修改的值会自动作用到合并区域的第一行数据上其他被合并的行并不会同步更新。这种情况下我的建议是合并列的allowEdit一律设为false。原因很简单合并单元格的语义是多行共用同一个值如果需要修改应该去修改背后的数据源比如通过弹出框编辑分组信息而不是直接双击合并格否则很容易产生“看似改了实际没同步”的数据不一致问题。如果你确实需要支持直接在合并格上修改并且希望值同步到所有被合并的行上那就得在beforeEdit、编辑完成事件里手动处理。比如在编辑完成后遍历所有匹配行的数据把新值赋给它们再刷新表格。这种方案在 LiguiUI 里能做但逻辑量和边界情况都不少除非产品明确要求不然我建议尽量规避。4.2 空白自动填充前值一个让编辑体验提升很多的配置热词里有一个很实用的点“如果是空白则等于前一个单元格的内容”。这个需求在录入场景里特别常见。比如用户连续录入几行“客户名称”相同的明细如果每行都重新输入一遍效率很低。LiguiUI 的表格编辑虽然默认不会自动做这个但实现起来也很简单。我的做法是在单元格编辑完成事件里判断如果新值等于空字符串就自动取上一行同列的值填入本行然后刷新单元格显示。afterEdit: function (cellInfo) { if (cellInfo.field customerName !cellInfo.value) { var rows table.getData(); var prevRow rows[cellInfo.rowIndex - 1]; if (prevRow prevRow.customerName) { cellInfo.row.customerName prevRow.customerName; table.updateCell(cellInfo.row, customerName, prevRow.customerName); liguiUI.msg({ type: success, text: 已自动填充为上一行值 }); } } }我还会配合一个“记忆上一次输入”的功能如果上一行没有值就直接把用户在这一列最近一次输入的值作为默认值填入。这些都是在事件层实现的扩展不影响 LiguiUI 本身的编辑逻辑。不过做自动填充时有一点要格外注意不要把“填充空值”和“清除值”的操作混淆。比如用户本意是想把某单元格的内容清空结果系统自动补成了上一行的值那就会很崩溃。我的规避方式是加一个判断条件只有从空单元格进入编辑并提交空值时才触发自动填充如果原本有值、被用户删空了则保持空值不做自动填充。4.3 可编辑下拉框既让选又让填的实现细节关于热词里的“可编辑下拉框”LiguiUI 处理得还算方便。有些字段的值不固定除了从预设列表里选还允许用户输入一个自定义值。LiguiUI 的select编辑器默认是只能选的但加一个editable: true配置后下拉框就会变成可输入模式用户既能从列表选也能直接敲字。{ field: category, title: 分类, editType: select, editor: { type: select, options: [ { value: A, text: 电子产品 }, { value: B, text: 办公用品 } ], editable: true, // 允许手动输入 allowNoMatch: true // 允许输入不存在的值 } }这种可编辑下拉框结合“单元格是否可编辑”时有一个细节值得注意虽然下拉框可编辑但在只读列上它依然不会进入编辑态也就是说不存在“单元格不可编辑但下拉框能点开”的情况。这一点 LiguiUI 处理得比较统一编辑态入口始终被拦截层控制。而在实际使用中我踩过一个小坑可编辑下拉框输入了自定义值之后如果服务端没有这个分类保存时容易因为字段限制报错。后来我在保存前加了一道校验判断该列的值是否在预设列表里如果不在需要用户二次确认防止脏数据混入。5. 性能问题排查可编辑栏位卡顿的处理实录5.1 卡顿的常见原因分析热词里专门提到了“vxe-colgroup的可编辑栏位卡顿”这类问题在 LiguiUI 里同样值得警惕。大列表开启编辑后最容易出现卡顿的场景集中在三个地方第一渲染层的问题。如果表格一次性渲染几千行每一行都绑定了编辑器那么首次加载和切换编辑模式时的 DOM 创建开销会非常大。LiguiUI 如果开启了编辑通常只会在进入编辑态的单元格上渲染输入框但如果是整行列编辑器常驻模式那性能就会明显下降。第二单元格编辑事件触发的全局刷新。很多人写编辑完成回调时喜欢调用整个表格的刷新方法比如table.reload()或table.render()这样会导致所有行、所有列重新渲染哪怕你只改了一个数字浏览器也会把所有 DOM 重建一遍几千行数据下不卡才怪。第三计算密集型逻辑放在编辑高频事件里。比如在beforeEdit、afterEdit里做大量数据遍历、深度拷贝、或者复杂的数组排序这些高频触发的回调一旦耗时长用户的每一次点击都会感觉像卡住了一样。5.2 我的优化方案局部刷新 编辑态隔离针对上面的第三个原因我在项目里做的第一件事就是杜绝在编辑事件里调用全表刷新。改成用 LiguiUI 提供的updateCell或updateRow方法只更新变动的那一行。比如批量录入时用户改完数量后我希望同一行的“金额”列自动更新afterEdit: function (cellInfo) { if (cellInfo.field num) { var amount cellInfo.row.price * cellInfo.value; cellInfo.row.amount amount; table.updateCell(cellInfo.row, amount, amount); // 只更新这一格 } }这种局部更新只重绘一个单元格性能开销非常小实测在几千行数据下也没有明显卡顿。但如果之前用的是table.reload()那体验差距是非常明显的所以这一点我觉得值得单独拿出来说一下。第二个优化点是减少编辑器常驻。LiguiUI 默认是双击单元格才进入编辑态这个模式本身性能压力不大。但如果某些列配置了“单击即编辑”或者“行内所有单元格常驻编辑框”那就要权衡一下 UI 体验和性能了。我这边遇到的大数据量表会尽量保持双击进入编辑避免页面上同时存在几千个 input。还有一点是关于虚拟滚动的。LiguiUI 在开启大数据量模式时通常会配合虚拟滚动来渲染可视区域的行。如果你的表格数据超过一千行建议确认一下是否已经开启这个能力。虚拟滚动开启之后编辑器只存在于当前可视的行里编辑完成后滚动出视野时 DOM 会被销毁滚动回来时重新创建这就避免了几千行同时持有编辑器的极端情况。5.3 从卡顿出发谈编辑状态的管理思路处理完卡顿之后我又意识到另一个问题当用户编辑了大量单元格但没有保存然后滚动出可视区域再滚回来时编辑状态是否还能保留LiguiUI 默认情况下编辑完成后的值会写入行数据所以即使单元格重新渲染显示的还是编辑后的值。但如果是那种“进入编辑态但没提交”的中间状态虚拟滚动下可能会丢失输入框里的内容。这类问题处理起来比较麻烦我的建议是尽量缩短编辑态的生命周期要么进入编辑后立刻把值写入数据要么就明确要求用户编辑完成后必须回车或失焦提交不要在编辑态里长时间停留。另外编辑权限的判断在虚拟滚动下也会频繁执行因为每一行进入可视区域时都要重新走一遍rowEditRule。如果你的rowEditRule里做了很重的计算同样会造成滚动卡顿。所以这类纯判断函数尽量保持轻量把依赖的数据提前算好不要每次调用都去搜索数组、深拷贝对象。6. 常见问题速查表与避坑经验6.1 高频问题与排查思路我把做 LiguiUI 单元格编辑权限控制过程中最容易踩的几个问题整理成了表格方便排查时直接查阅。问题现象常见原因排查与解决建议双击单元格没有反应不进编辑态编辑总开关未开启或该列未配置editType检查editConfig.enable确认列上有editType某一列始终无法编辑但其他列正常该列allowEdit: false或列字段名拼写错误打印列配置检查allowEdit和field某一行始终无法编辑rowEditRule返回了false在rowEditRule里加日志确认行状态判断是否符合预期编辑完值后显示不对编辑完成回调中修改了数据但渲染没同步使用updateCell或updateRow刷新对应单元格填入空值时变成上一行的值自动填充逻辑触发了加判断只有原本就是空值时填充删除已有内容不算填充合并单元格里的编辑值不同步合并格编辑作用在第一行数据上建议合并列关闭编辑或手动同步所有被合并行的值表格数据量大编辑卡顿编辑事件中触发了全表刷新改成局部刷新避免在afterEdit里调用reload可编辑下拉框输入值后保存报错输入值不在预设列表中服务端不认在保存前校验或把allowNoMatch配置明确为需要后端兼容6.2 我更推荐的一组配置习惯除了排查问题我还想分享几组在实际项目中比较顺手的配置习惯这些来自我多次踩坑后沉淀下来的经验。第一所有字段的编辑能力先在列配置里定义好不要依赖事件里临时判断。列配置就像是“能力清单”事件里做的是“权限判定”两者分离后代码可读性和可维护性都会好很多。如果你的项目里权限规则特别复杂甚至可以单独维护一份“字段编辑权限映射表”在渲染表格之前动态生成列配置。第二凡是涉及跨字段联动的编辑限制统一放到beforeEdit里处理不要分散在多个事件里。这些联动规则通常比较依赖业务上下文只有beforeEdit能拿到完整的行数据和列信息。分散处理的话不仅排查困难还容易出现两个规则互相覆盖的情况。第三编辑权限的提示信息要有分级。行级不可编辑、列级不可编辑、单元格级不可编辑用户看到的现象可能都是一样的“双击没反应”但原因各不相同。我会在提示里写明具体原因比如“该订单已审核不能修改”“税率未填禁止改价”“当前账号无编辑权限”而不是统一弹一个“不可编辑”。这对使用方来说体验差别很大。6.3 配套场景扩展从单元格权限到导出与进度展示最后再说一个延伸场景。热词里提到的 “xlsx单元格如何显示进度条”“excel vba 单元格内图片自适应” 这类话题虽然更像表格导出和 Excel 处理但它们背后的需求逻辑和前端表格编辑其实是连着的。比如你在 LiguiUI 表格里编辑完数据后要导出成 Excel就很容易遇到两个问题一是单元格宽度默认不够导出后文字被挤到换行二是某些列想带一个进度条效果纯数字导出之后就变成普通数字了。LiguiUI 处理前端的展示和交互导出时则需要借助后端或独立的导出库配合设置单元格宽度、样式和数据格式。这些并没有一个万能银弹更多是“前端表格负责录入展示导出模板负责排版还原”的思路。我做导出时一般会把列的宽度配置和前端列宽保持一致尽量让导出效果接近页面上看到的样式。进度条这事前端可以先用一个独立字段存百分比数值导出时再把该列设置成带数据条的单元格格式这样两边都能满足。回到“设置单元格是否可编辑”这个主题我个人的体会是LiguiUI 最大的价值不是给了你一个现成的“可编辑/不可编辑”开关而是把编辑权限拆成了多个层级和事件让你能在不同颗粒度上实现业务规则。关键是要把这些层级的作用边界理清楚列配置管能力、行规则管状态、事件回调管动态逻辑这样哪怕后续权限规则越加越多代码也不会乱成一团。如果你现在正在做类似的管理系统我建议别急着把allowEdit: true写满所有列。先跟产品确认清楚哪些字段天然不可编辑哪些行状态锁定哪些单元格需要联动校验。把这些规则梳理成一张表再翻译成 LiguiUI 的配置和事件整个过程会顺畅很多。等这个基础打磨好了再考虑合并单元格同步、空值自动填充、导出样式这类锦上添花的功能就不会被某个细节坑得返工了。
返回列表